#define Graph_And_Chart_PRO using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace ChartAndGraph { /// /// this class is used internally in order to draw lines, line fill and line points into a mesh /// public partial class CanvasLines : EventHandlingGraphic { /// /// thickness of the lines being drawn /// public float Thickness = 2f; float innerTile = 1f; int mMinModifyIndex = 0; /// /// Tiling value for the graphic. The tiliing is the total length of the lines being drawn. It is set by the parent graph /// public float Tiling { get { return innerTile; } set { innerTile = value; } } bool mNegativeFill = true; /// /// true if this graphic is in fill render mode (drawing the inner fill of a line graph) /// bool mFillRender = false; /// /// true if this graph is in point drawing render mode /// bool mPointRender = false; /// /// for point render mode , sets the point size /// float mPointSize = 5f; /// /// for fill render mode , this sets the boundries of the fill. (so the bottom of the fill matches the bottom of the graph) /// Rect mFillRect; float mFillZero; /// /// if true , the Y of the fill is stretched /// bool mStretchY; /// /// The material for the graphic object /// Material mCachedMaterial; Material mOriginalMaterial; /// /// list of lines for the line object , this will be used to render either dots , fill or lines depending on the render mode /// List mLines; /// /// bounding box for the value of mLines, used for event handling /// float mMinX, mMinY, mMaxX, mMaxY; public Rect? ClipRect { get; set; } /// /// Sets point render mode /// /// public void MakePointRender(float pointSize) { mPointSize = pointSize; mPointRender = true; } public void SetFillZero(float zero) { mFillZero = zero; } /// /// sets inner fill render mode /// /// /// public void MakeFillRender(Rect fillRect,float fillZero, bool stretchY,bool negative) { mFillZero = fillZero; mFillRect = fillRect; mFillRender = true; mStretchY = stretchY; mNegativeFill = negative; } UIVertex[] mTmpVerts = new UIVertex[4]; /// /// holds line data and pre cacultates normal and speration /// internal struct Line { public Line(Vector3 from, Vector3 to, float halfThickness, bool hasNext, bool hasPrev) : this() { Vector3 diff = (to - from); float magDec = 0; if (hasNext) magDec += halfThickness; if (hasPrev) magDec += halfThickness; Mag = diff.magnitude - magDec * 2; Degenerated = false; if (Mag <= 0) Degenerated = true; Dir = diff.normalized; Vector3 add = halfThickness * 2 * Dir; if (hasPrev) from += add; if (hasNext) to -= add; From = from; To = to; Normal = new Vector3(Dir.y, -Dir.x, Dir.z); // this part calculates the line inset and points based on thichkness P1 = From + Normal * halfThickness; P2 = from - Normal * halfThickness; P3 = to + Normal * halfThickness; P4 = to - Normal * halfThickness; } public bool Degenerated { get; private set; } public Vector3 P1 { get; private set; } public Vector3 P2 { get; private set; } public Vector3 P3 { get; private set; } public Vector3 P4 { get; private set; } public Vector3 From { get; private set; } public Vector3 To { get; private set; } public Vector3 Dir { get; private set; } public float Mag { get; private set; } public Vector3 Normal { get; private set; } } /// /// represents one line segemenet. /// internal class LineSegement { List mLines = new List(); public LineSegement(IList lines) { mLines.AddRange(lines.Select(x=>new Vector4(x.x,x.y,x.z,-1f))); } public LineSegement(IList lines) { mLines.AddRange(lines); } /// /// reset the value of the linesSegment to the specified list /// /// public void ModifiyLines(List v) { mLines.Clear(); mLines.AddRange(v); } /// /// The total amount of points in this line segmenet /// public int PointCount { get { if (mLines == null) return 0; return mLines.Count; } } /// /// Line count is point count minus one. Since all dots have to be connected /// public int LineCount { get { if (mLines == null) return 0; if (mLines.Count < 2) return 0; return mLines.Count - 1; } } /// /// Gets a point /// /// /// public Vector4 getPoint(int index) { Vector4 p = mLines[index]; return p; } /// /// gets the line at the specified index.index Must be below LineCount /// /// /// /// public void GetLine(int index, out Vector3 from, out Vector3 to) { from = mLines[index]; to = mLines[index + 1]; } /// /// gets the line at the specified index.index Must be below LineCount. if the line has a previous or a next line an inset is calculated for better visual aperance /// /// /// /// public Line GetLine(int index, float halfThickness, bool hasPrev, bool hasNext) { Vector3 from = mLines[index]; Vector3 to = mLines[index + 1]; return new Line(from, to, halfThickness, false, false); } public double GetLineMag(int index) { return (mLines[index] - mLines[index + 1]).magnitude; } } public CanvasLines() { } /// /// finds the minimum and maximum values of the currently set data /// void FindBoundingValues() { mMinX = float.PositiveInfinity; mMinY = float.PositiveInfinity; mMaxX = float.NegativeInfinity; mMaxY = float.NegativeInfinity; // this part finds the bounding box of the lines if (mLines != null) { for (int i = 0; i < mLines.Count; i++) { LineSegement seg = mLines[i]; int totalPoints = seg.PointCount; for (int j = 0; j < totalPoints; j++) { Vector3 point = seg.getPoint(j); mMinX = Mathf.Min(mMinX, point.x); mMinY = Mathf.Min(mMinY, point.y); mMaxX = Mathf.Max(mMaxX, point.x); mMaxY = Mathf.Max(mMaxY, point.y); } } } } internal void ModifyLines(int minModifyIndex,List lines) { if (mLines.Count == 0) { mLines.Add(new LineSegement(lines.ToArray())); return; } mMinModifyIndex = minModifyIndex; FindBoundingValues(); mLines[0].ModifiyLines(lines); SetVerticesDirty(); // clear previous animations Rebuild(CanvasUpdate.PostLayout); RefreshInputs(); } public bool EnableOptimization { get; set; } /// /// sets the lines for this renderer /// /// internal void SetLines(List lines) { mLines = lines; FindBoundingValues(); mMinModifyIndex = 0; SetAllDirty(); ClearEvents(); // clear previous animations if(EnableOptimization) Rebuild(CanvasUpdate.PostLayout); else Rebuild(CanvasUpdate.PreRender); } protected override void UpdateMaterial() { base.UpdateMaterial(); canvasRenderer.SetTexture(material.mainTexture); } protected override void Awake() { base.Awake(); this.raycastTarget = false; } /// /// this method inflate one point in a line with the specified distantce. This enable the graphic to control the thickness of lines /// /// /// /// /// /// /// /// /// void GetSide(Vector3 point, Vector3 dir,Vector3 normal,float dist,float size,float z,out Vector3 p1,out Vector3 p2) { point.z = z; point += dir * dist; normal *= size; p1 = point + normal; p2 = point - normal; } protected override void OnDestroy() { base.OnDestroy(); ChartCommon.SafeDestroy(mCachedMaterial); } protected override void OnDisable() { base.OnDisable(); } /// /// overriding the default implementation to support materials with _ChartTiling property. This is used to tile texture along the graphic lines /// public override Material material { get { return base.material; } set { ChartCommon.SafeDestroy(mCachedMaterial); if (value == null) { mCachedMaterial = null; base.material = null; return; } mOriginalMaterial = value; mCachedMaterial = new Material(value); mCachedMaterial.hideFlags = HideFlags.DontSave; if (mCachedMaterial.HasProperty("_ChartTiling")) mCachedMaterial.SetFloat("_ChartTiling", Tiling); base.material = mCachedMaterial; } } protected override Vector2 Min { get { return new Vector2(mMinX, mMinY); } } protected override Vector2 Max { get { return new Vector2(mMaxX, mMaxY); } } protected override float MouseInThreshold { get { return Mathf.Max(Thickness, mPointSize) + Sensitivity; } } /// /// sets the transform of the hover object based on the data in this graphic /// /// /// protected override void SetUpHoverObject(ChartItemEffect hover, int index, int type,object data) { if (hover == null) return; if (mLines == null || mLines.Count == 0) return; if (index < 0) return; if (mPointRender) { if (index >= mLines[0].PointCount) return; Vector4 point = mLines[0].getPoint(index); RectTransform transform = hover.GetComponent(); transform.localScale = new Vector3(1f, 1f, 1f); float size = mPointSize; if (point.w >= 0f) size = point.w; transform.sizeDelta = new Vector2(size, size); transform.anchoredPosition3D = new Vector3(point.x, point.y, 0f); } else { if (index >= mLines[0].LineCount) return; Vector3 from; Vector3 to; mLines[0].GetLine(index, out from, out to); if (ViewRect.HasValue) { Vector2 vFrom = from; Vector2 vTo = to; TrimLine(ViewRect.Value, ref vFrom, ref vTo); from = new Vector3(vFrom.x, vFrom.y, from.z); to = new Vector3(vTo.x, vTo.y, to.z); } Vector3 dir = (to - from); float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg; RectTransform transform = hover.GetComponent(); transform.sizeDelta = new Vector2(dir.magnitude, Thickness); transform.localScale = new Vector3(1f, 1f, 1f); transform.localRotation = Quaternion.Euler(0f, 0f, angle); Vector3 point = (from + to) * 0.5f; transform.anchoredPosition3D = new Vector3(point.x, point.y, 0f); } } protected override void Pick(Vector3 mouse, out int pickedIndex, out int pickedType, out object selectionData) { if (mPointRender) PickDot(mouse, out pickedType, out pickedIndex,out selectionData); else PickLine(mouse, out pickedType, out pickedIndex, out selectionData); if (pickedIndex >= 0) pickedIndex += refrenceIndex; } protected override void Update() { base.Update(); Material mat = material; if (mCachedMaterial != null && mat != null && mCachedMaterial.HasProperty("_ChartTiling")) { if (mCachedMaterial != mat) mCachedMaterial.CopyPropertiesFromMaterial(mat); mCachedMaterial.SetFloat("_ChartTiling", Tiling); } if(mOriginalMaterial != null && mCachedMaterial!=null) { mCachedMaterial.CopyPropertiesFromMaterial(mOriginalMaterial); } } partial void ProcesssPoint(ref Vector4 point, ref float halfSize); IEnumerable getDotVeritces() { if (mLines == null) yield break; float z = 0f; float halfSize = mPointSize * 0.5f; for (int i = 0; i < mLines.Count; ++i) { LineSegement seg = mLines[i]; int total = seg.PointCount; for (int j = mMinModifyIndex; j < total; ++j) { Vector4 magPoint = seg.getPoint(j); if (magPoint.w == 0f) continue; Vector3 point = (Vector3)magPoint; halfSize = mPointSize * 0.5f; ProcesssPoint(ref magPoint, ref halfSize); Vector3 p1 = point + new Vector3(-halfSize, -halfSize, 0f); Vector3 p2 = point + new Vector3(halfSize, -halfSize, 0f); Vector3 p3 = point + new Vector3(-halfSize, halfSize, 0f); Vector3 p4 = point + new Vector3(halfSize, halfSize, 0f); Vector2 uv1 = new Vector2(0f, 0f); Vector2 uv2 = new Vector2(1f, 0f); Vector2 uv3 = new Vector2(0f, 1f); Vector2 uv4 = new Vector2(1f, 1f); UIVertex v1 = ChartCommon.CreateVertex(p1, uv1, z); UIVertex v2 = ChartCommon.CreateVertex(p2, uv2, z); UIVertex v3 = ChartCommon.CreateVertex(p3, uv3, z); UIVertex v4 = ChartCommon.CreateVertex(p4, uv4, z); if(ClipRect.HasValue== false || ClipRect.Value.Contains(p1) || ClipRect.Value.Contains(p2) || ClipRect.Value.Contains(p3) || ClipRect.Value.Contains(p4)) { yield return v1; yield return v2; yield return v3; yield return v4; } } } } Vector2 TransformUv(Vector2 uv) { return uv; //if (mUvRect.HasValue == false) // return uv; //Rect r = mUvRect.Value; //float x = r.x + uv.x * r.width; //float y = r.y + uv.y * r.height; //return new Vector2(x, y); } IEnumerable getFillVeritces() { if (mLines == null) yield break; float z = 0f; for (int i = 0; i < mLines.Count; ++i) { LineSegement seg = mLines[i]; int totalLines = seg.LineCount; for (int j = mMinModifyIndex; j < totalLines; ++j) { Vector3 from; Vector3 to; seg.GetLine(j,out from, out to); Vector2 toTrim = to; Vector2 fromTrim = from; // TrimItem(mFillRect.xMin, mFillRect.yMin, mFillRect.xMax, mFillRect.yMin, true, false, ref fromTrim, ref toTrim); to = new Vector3(toTrim.x, toTrim.y, to.z); from = new Vector3(fromTrim.x, fromTrim.y, from.z); Vector3 fromBottom = from; Vector3 toBottom = to; fromBottom.y = mFillZero;// mFillRect.yMin; toBottom.y = mFillZero;// mFillZero; ;// mFillRect.yMin; float fromV = 1f; float toV = 1f; if (mStretchY == false) { fromV = Mathf.Abs((from.y - mFillZero) / mFillRect.height); toV = Mathf.Abs((to.y - mFillZero) / mFillRect.height); } float fromU = ((from.x - mFillRect.xMin) / mFillRect.width); float toU = ((to.x - mFillRect.xMin) / mFillRect.width); Vector2 uv1 = TransformUv(new Vector2(fromU, fromV)); Vector2 uv2 = TransformUv(new Vector2(toU, toV)); Vector2 uv3 = TransformUv(new Vector2(fromU, 0f)); Vector2 uv4 = TransformUv(new Vector2(toU, 0f)); UIVertex v1 = ChartCommon.CreateVertex(from, uv1, z); UIVertex v2 = ChartCommon.CreateVertex(to, uv2, z); UIVertex v3 = ChartCommon.CreateVertex(fromBottom, uv3, z); UIVertex v4 = ChartCommon.CreateVertex(toBottom, uv4, z); if ((from.y < mFillZero) ^ (to.y < mFillZero)) { Vector3 crossing = ChartCommon.LineCrossing(from, to, mFillZero); float crossU = ((crossing.x - mFillRect.xMin) / mFillRect.width); Vector2 uvCross = TransformUv(new Vector2(crossU, 0f)); UIVertex vCross = ChartCommon.CreateVertex(crossing, uvCross, z); yield return v1; yield return vCross; yield return v3; yield return v3; yield return v2; yield return vCross; yield return v4; yield return v4; } else { yield return v1; yield return v2; yield return v3; yield return v4; } } } } IEnumerable getLineVertices() { if (mLines == null) yield break; float halfThickness = Thickness * 0.5f; float z = 0f; for (int i = 0; i < mLines.Count; ++i) { LineSegement seg = mLines[i]; int totalLines = seg.LineCount; Line? peek = null; Line? prev = null; float tileUv = 0f; float totalUv = 0f; for (int j = mMinModifyIndex; j < totalLines; ++j) totalUv += (float)seg.GetLineMag(j); for (int j = mMinModifyIndex; j < totalLines; ++j) { Line line; bool hasNext = j + 1 < totalLines; if (peek.HasValue) line = peek.Value; else line = seg.GetLine(j, halfThickness, prev.HasValue, hasNext); peek = null; if (j + 1 < totalLines) peek = seg.GetLine(j + 1, halfThickness, true, j + 2 < totalLines); Vector3 p1 = line.P1; Vector3 p2 = line.P2; Vector3 p3 = line.P3; Vector3 p4 = line.P4; Vector2 uv1 = new Vector2(tileUv * Tiling, 0f); Vector2 uv2 = new Vector2(tileUv * Tiling, 1f); tileUv += line.Mag / totalUv; Vector2 uv3 = new Vector2(tileUv * Tiling, 0f); Vector2 uv4 = new Vector2(tileUv * Tiling, 1f); UIVertex v1 = ChartCommon.CreateVertex(p1, uv1, z); UIVertex v2 = ChartCommon.CreateVertex(p2, uv2, z); UIVertex v3 = ChartCommon.CreateVertex(p3, uv3, z); UIVertex v4 = ChartCommon.CreateVertex(p4, uv4, z); yield return v1; yield return v2; yield return v3; yield return v4; if (peek.HasValue) { float myZ = z + 0.2f; Vector3 a1, a2; GetSide(line.To, line.Dir, line.Normal, halfThickness * 0.5f, halfThickness * 0.6f, v3.position.z, out a1, out a2); yield return v3; yield return v4; yield return ChartCommon.CreateVertex(a1, v3.uv0, myZ); yield return ChartCommon.CreateVertex(a2, v4.uv0, myZ); } if (prev.HasValue) { float myZ = z + 0.2f; Vector3 a1, a2; GetSide(line.From, -line.Dir, line.Normal, halfThickness * 0.5f, halfThickness * 0.6f, v1.position.z, out a1, out a2); yield return ChartCommon.CreateVertex(a1, v1.uv0, myZ); yield return ChartCommon.CreateVertex(a2, v2.uv0, myZ); yield return v1; yield return v2; } //z -= 0.05f; prev = line; } } } IEnumerable getVerices() { IEnumerable vertices; if (mPointRender) vertices = getDotVeritces(); else { if (mFillRender) vertices = getFillVeritces(); else vertices = getLineVertices(); } return vertices; } Mesh mVHMesh; List mPositions = new List(); List mUvs = new List(); List mTringles = new List(); void WriteTo (List list,int index,T val) { if (list.Count == index) list.Add(val); else list[index] = val; } protected override void UpdateGeometry() { if (EnableOptimization == false) { mMinModifyIndex = 0; base.UpdateGeometry(); return; } if (mVHMesh == null) mVHMesh = new Mesh(); if (mMinModifyIndex == int.MaxValue) mMinModifyIndex = mPositions.Count; if (mMinModifyIndex <= 0) { mMinModifyIndex = 0; mPositions.Clear(); mUvs.Clear(); mTringles.Clear(); foreach (UIVertex v in getVerices()) { mPositions.Add(v.position); mUvs.Add(v.uv0); } int quads = (mPositions.Count / 4) ; int baseIndex = 0; for (int i = 0; i < quads; i++) { mTringles.Add(baseIndex); mTringles.Add(baseIndex + 1); mTringles.Add(baseIndex + 3); mTringles.Add(baseIndex + 3); mTringles.Add(baseIndex + 2); mTringles.Add(baseIndex); baseIndex += 4; } } else { // Debug.Log("adding"); int total = 0; int index = mMinModifyIndex; foreach (UIVertex v in getVerices()) { // Debug.Log("extra"); WriteTo(mPositions, index, v.position); WriteTo(mUvs, index, v.uv0); total++; } int quads = total / 4; for (int i = 0; i < quads; i++) { // Debug.Log("extra tring"); mTringles.Add(0); mTringles.Add(1); mTringles.Add(3); mTringles.Add(3); mTringles.Add(2); mTringles.Add(0); } } mVHMesh.Clear(); mVHMesh.SetVertices(mPositions); mVHMesh.SetUVs(0, mUvs); mVHMesh.SetTriangles(mTringles, 0); //at the end mMinModifyIndex = int.MaxValue; GetComponent().SetMesh(mVHMesh); } #if (!UNITY_5_2_0) && (!UNITY_5_2_1) protected override void OnPopulateMesh(VertexHelper vh) { base.OnPopulateMesh(vh); vh.Clear(); int vPos = 0; foreach (UIVertex v in getVerices()) { mTmpVerts[vPos++] = v; if (vPos == 4) { UIVertex tmp = mTmpVerts[2]; mTmpVerts[2] = mTmpVerts[3]; mTmpVerts[3] = tmp; vPos = 0; vh.AddUIVertexQuad(mTmpVerts); } } } #endif #pragma warning disable 0672 #if !UNITY_2017_1_OR_NEWER /// /// A chart mesh used for populating the /// WorldSpaceChartMesh mMesh = null; protected override void OnPopulateMesh(Mesh m) { if (mMesh == null) mMesh = new WorldSpaceChartMesh(1); else mMesh.Clear(); int vPos = 0; mMinModifyIndex = 0; // not supported here foreach (UIVertex v in getVerices()) { mTmpVerts[vPos++] = v; if (vPos == 4) { vPos = 0; mMesh.AddQuad(mTmpVerts[0], mTmpVerts[1], mTmpVerts[2], mTmpVerts[3]); } } mMesh.ApplyToMesh(m); } #endif void PickLine(Vector3 mouse, out int segment, out int line, out object selectionData) { float minDist = Mathf.Infinity; segment = -1; line = -1; selectionData = null; if (mLines == null) { return; } for (int i = 0; i < mLines.Count; ++i) { LineSegement seg = mLines[i]; int total = seg.LineCount; for (int j = 0; j < total; ++j) { Vector3 from; Vector3 to; seg.GetLine(j, out from, out to); float dist = ChartCommon.SegmentPointSqrDistance(from,to,mouse); if (dist < minDist) { minDist = dist; segment = i; line = j; } } } float thresh = (Thickness + Sensitivity); if ((ViewRect.HasValue && !ViewRect.Value.Contains(mouse)) || minDist > thresh * thresh) { segment = -1; line = -1; } } void PickDot(Vector3 mouse, out int segment , out int point, out object selectionData) { float minDist = Mathf.Infinity; segment = -1; point = -1; selectionData = null; if (mLines == null) return; float mag = mPointSize; for (int i = 0; i < mLines.Count; ++i) { LineSegement seg = mLines[i]; int total = seg.PointCount; for (int j = 0; j < total; ++j) { Vector4 p = seg.getPoint(j); if (p.w == 0f) continue; float dist = (mouse - ((Vector3)p)).sqrMagnitude; if(dist < minDist) { mag = p.w; if (mag < 0f) mag = mPointSize; minDist = dist; segment = i; point = j; } } } float thresh = mag + Sensitivity; if ((ViewRect.HasValue && !ViewRect.Value.Contains(mouse)) || minDist > thresh * thresh) { segment = -1; point = -1; } } void TrimItem(float x1, float y1 ,float x2,float y2,bool xAxis,bool oposite, ref Vector2 from, ref Vector2 to) { Vector2 seg1 = new Vector2(x1, y1); Vector2 seg2 = new Vector2(x2, y2); Vector2 point; if (ChartCommon.SegmentIntersection(seg1, seg2, from, to, out point) == false) return; if(xAxis) { if ((to.y > from.y) ^ oposite) from = point; else to = point; return; } if ((to.x > from.x) ^ oposite) from = point; else to = point; } void TrimLine(Rect r,ref Vector2 from, ref Vector2 to) { TrimItem(r.xMin, r.yMin, r.xMax, r.yMin, true, false, ref from, ref to); TrimItem(r.xMin, r.yMax, r.xMax, r.yMax, true, true, ref from, ref to); TrimItem(r.xMin, r.yMin, r.xMin, r.yMax, false, false, ref from, ref to); TrimItem(r.xMax, r.yMin, r.xMax, r.yMax, false, true, ref from, ref to); } #pragma warning restore 0672 } }