using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace xchart { [System.Serializable] public class LineData { [SerializeField] public string name; [SerializeField] public string key; [SerializeField] public Color lineColor; [SerializeField] public Color pointColor; [SerializeField] public Button button; private List _dataList = new List(); public List dataList { get { return _dataList; } } private bool _visible = true; public bool visible { get { return _visible; } set { _visible = value; if (button) { button.GetComponent().color = visible ? lineColor : Color.grey; } } } private int _min = 0; public int min { get { return _min; } } private int _max = 10; public int max { get { return _max; } } private int _step = 10; public int step { get { return _step; } set { _step = value; } } public void AddData(float data, int maxCount) { dataList.Add(data); if (dataList.Count > maxCount) { dataList.RemoveAt(0); UpdateMinMax(); } else { if (data < _min) { _min = (int)data; } if (data > _max) { _max = (int)data; } CheckMax(); } } public void ClearData() { _dataList.Clear(); } public void UpdateMinMax() { _min = 0; _max = 4; foreach (var data in dataList) { if (data < _min) { _min = (int)data; } if (data > _max) { _max = (int)data; } } CheckMax(); } private void CheckMax() { if (_max <= 10) { if (_max < 4) _max = 4; } else { int diff = _max % _step; if (diff > 1) { _max = (_max - diff) + _step; } } } } public class LineChart : MaskableGraphic { [SerializeField] private int pointWidth = 15; [SerializeField] private float lineSize = 1f; [SerializeField] private float pointSize = 1.5f; [SerializeField] private int graduationStep = 10; [SerializeField] private float arrowLen = 12; [SerializeField] private float arrowSize = 6; [SerializeField] private Color backgroundColor; [SerializeField] private Font font; [SerializeField] private List lineList = new List(); private List graduationList = new List(); private Dictionary lineMap = new Dictionary(); private float lastMax = 0; private float lastHig = 0; protected override void Awake() { base.Awake(); for (int i = 0; i < lineList.Count; i++) { LineData line = lineList[i]; line.dataList.Clear(); if (line.button) { line.button.GetComponent().color = line.visible ? line.lineColor : Color.grey; line.button.GetComponentInChildren().text = line.name; line.button.onClick.AddListener(delegate () { OnClickButton(line.key); }); } AddLineToLineMap(line); } InitGraduation(); } private void InitGraduation() { float chartHigh = rectTransform.sizeDelta.y; float graduationHig = chartHigh / 4; for (int i = 0; i < 5; i++) { GameObject g1; if (transform.Find("graduation" + i)) { g1 = transform.Find("graduation" + i).gameObject; graduationList.Add(g1.GetComponent()); } else { g1 = new GameObject(); g1.name = "graduation" + i; g1.transform.parent = transform; g1.transform.localPosition = Vector3.zero; g1.transform.localScale = Vector3.one; g1.AddComponent(); Text txtg1 = g1.GetComponent(); txtg1.font = font; txtg1.text = (i * 100).ToString(); txtg1.alignment = TextAnchor.MiddleRight; RectTransform rect = g1.GetComponent(); rect.anchorMax = Vector2.zero; rect.anchorMin = Vector2.zero; rect.sizeDelta = new Vector2(50, 20); rect.localPosition = new Vector3(-33, i * graduationHig, 0); graduationList.Add(txtg1); } } } private float time; private void Update() { time += Time.deltaTime; if (time >= 1) { time = 0; AddPoint("fps", Random.Range(24.0f, 60.0f)); AddPoint("rtt", Random.Range(15, 30)); AddPoint("ping", Random.Range(0, 100)); } } void OnClickButton(string key) { LineData line = lineMap[key]; line.visible = !line.visible; if (line.visible) { line.step = graduationStep; line.UpdateMinMax(); } UpdateGradution(); UpdateMesh(); } private void AddLineToLineMap(LineData line) { if (lineMap.ContainsKey(line.key)) { Debug.LogError("LineChart:line key is duplicated:" + line.key); } else { lineMap[line.key] = line; } } private float GetAllLineMax() { float max = 4; foreach (var line in lineList) { if (line.visible && line.max > max) { max = line.max; } } return max; } public int GetMaxPointCount() { Vector2 size = rectTransform.sizeDelta; int max = (int)(size.x / pointWidth); return max; } public void AddLine(string key, string name, Color lineColor, Color pointColor) { LineData line = new LineData(); line.key = key; line.name = name; line.lineColor = lineColor; line.pointColor = pointColor; lineList.Add(line); AddLineToLineMap(line); } public void AddPoint(string key, float point) { if (!lineMap.ContainsKey(key)) { Debug.LogError("LineChart:not contain line key:" + key); return; } LineData line = lineMap[key]; line.AddData(point, GetMaxPointCount()); UpdateMesh(); UpdateGradution(); } public void ResetDataStart() { foreach (var line in lineList) { line.ClearData(); } } public void ResetData(string key, float data) { if (!lineMap.ContainsKey(key)) { Debug.LogError("LineChart:not contain line key:" + key); return; } LineData line = lineMap[key]; line.AddData(data, GetMaxPointCount()); } public void ResetDataEnd() { foreach (var line in lineList) { line.UpdateMinMax(); } UpdateMesh(); UpdateGradution(); } private void UpdateMesh() { Vector2 size = rectTransform.sizeDelta; rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, (int)size.x - 1); rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, (int)size.x); } private void UpdateGradution() { float dataMax = GetAllLineMax(); if (lastMax != dataMax) { lastMax = dataMax; for (int i = 0; i < graduationList.Count; i++) { graduationList[i].text = ((int)(dataMax * i / graduationList.Count)).ToString(); } } float chartHigh = rectTransform.sizeDelta.y; Debug.LogError("hig:"+chartHigh); if (lastHig != chartHigh) { lastHig = chartHigh; for (int i = 0; i < graduationList.Count; i++) { Vector3 pos = graduationList[i].rectTransform.localPosition; print("i:"+pos); graduationList[i].rectTransform.localPosition = new Vector3(pos.x, lastHig * i / (graduationList.Count-1), pos.z); } } } protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); float chartHigh = rectTransform.sizeDelta.y; int chartWid = (int)(rectTransform.sizeDelta.x / pointWidth) * pointWidth; float dataMax = GetAllLineMax(); // draw bg Vector3 p1 = new Vector3(-50, chartHigh + 30); Vector3 p2 = new Vector3(chartWid + 50, chartHigh + 30); Vector3 p3 = new Vector3(chartWid + 50, -20); Vector3 p4 = new Vector3(-50, -20); DrawCube(vh, p1, p2, p3, p4, backgroundColor); // draw coordinate Vector3 coordZero = Vector3.zero; DrawLine(vh, new Vector3(chartWid + 5, -5), new Vector3(chartWid + 5, chartHigh + 0.5f), 1, Color.grey); // draw graduation for (int i = 0; i < graduationList.Count; i++) { Vector3 sp = new Vector3(-5, chartHigh * i / (graduationList.Count - 1)); Vector3 ep = new Vector3(chartWid + 5, chartHigh * i / (graduationList.Count - 1)); DrawLine(vh, sp, ep, 0.5f, Color.grey); } // draw line for (int index = 0; index < lineList.Count; index++) { LineData line = lineList[index]; if (!line.visible) continue; Vector3 lp = Vector3.zero; Vector3 np = Vector3.zero; for (int i = 0; i < line.dataList.Count; i++) { float data = line.dataList[i] * chartHigh / dataMax; np = new Vector3(i * pointWidth, data); if (i > 0) { DrawLine(vh, lp, np, lineSize, line.lineColor); } lp = np; } // draw point for (int i = 0; i < line.dataList.Count; i++) { UIVertex[] quadverts = new UIVertex[4]; float data = line.dataList[i] * chartHigh / dataMax; Vector3 p = new Vector3(i * pointWidth, data); DrawCube(vh, p, pointSize, line.pointColor); } } //draw x,y axis float xLen = chartWid + 25; float yLen = chartHigh + 15; float xPos = 0; float yPos = -5; DrawLine(vh, new Vector3(xPos, yPos - 1.5f), new Vector3(xPos, yLen), 1.5f, Color.white); DrawLine(vh, new Vector3(xPos, yPos), new Vector3(xLen, yPos), 1.5f, Color.white); //draw arrows DrawTriangle(vh, new Vector3(xPos - arrowSize, yLen - arrowLen), new Vector3(xPos, yLen + 4), new Vector3(xPos + arrowSize, yLen - arrowLen), Color.white); DrawTriangle(vh, new Vector3(xLen - arrowLen, yPos + arrowSize), new Vector3(xLen + 4, yPos), new Vector3(xLen - arrowLen, yPos - arrowSize), Color.white); } private void DrawLine(VertexHelper vh, Vector3 p1, Vector3 p2, float size, Color color) { Vector3 v = Vector3.Cross(p2 - p1, Vector3.forward).normalized * size; UIVertex[] vertex = new UIVertex[4]; vertex[0].position = p1 + v; vertex[1].position = p2 + v; vertex[2].position = p2 - v; vertex[3].position = p1 - v; for (int j = 0; j < 4; j++) { vertex[j].color = color; vertex[j].uv0 = Vector2.zero; } vh.AddUIVertexQuad(vertex); } private void DrawCube(VertexHelper vh, Vector3 p, float size, Color color) { UIVertex[] vertex = new UIVertex[4]; vertex[0].position = new Vector3(p.x - pointSize, p.y - pointSize); vertex[1].position = new Vector3(p.x + pointSize, p.y - pointSize); vertex[2].position = new Vector3(p.x + pointSize, p.y + pointSize); vertex[3].position = new Vector3(p.x - pointSize, p.y + pointSize); for (int j = 0; j < 4; j++) { vertex[j].color = color; vertex[j].uv0 = Vector2.zero; } vh.AddUIVertexQuad(vertex); } private void DrawCube(VertexHelper vh, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, Color color) { UIVertex[] vertex = new UIVertex[4]; vertex[0].position = p1; vertex[1].position = p2; vertex[2].position = p3; vertex[3].position = p4; for (int j = 0; j < 4; j++) { vertex[j].color = color; vertex[j].uv0 = Vector2.zero; } vh.AddUIVertexQuad(vertex); } private void DrawTriangle(VertexHelper vh, Vector3 p1, Vector3 p2, Vector3 p3, Color color) { List vertexs = new List(); vh.GetUIVertexStream(vertexs); DrawTriangle(vh, vertexs, p1, p2, p3, color); } private void DrawTriangle(VertexHelper vh, List vertexs, Vector3 p1, Vector3 p2, Vector3 p3, Color color) { UIVertex v1 = new UIVertex(); v1.position = p1; v1.color = color; v1.uv0 = Vector3.zero; vertexs.Add(v1); UIVertex v2 = new UIVertex(); v2.position = p2; v2.color = color; v2.uv0 = Vector3.zero; vertexs.Add(v2); UIVertex v3 = new UIVertex(); v3.position = p3; v3.color = color; v3.uv0 = Vector3.zero; vertexs.Add(v3); vh.AddUIVertexTriangleStream(vertexs); } private void DrawCricle(VertexHelper vh, Vector3 p, float radius, Color color, int segments) { List vertexs = new List(); vh.GetUIVertexStream(vertexs); float angle = 2 * Mathf.PI / segments; float cos = Mathf.Cos(angle); float sin = Mathf.Sin(angle); float cx = radius, cy = 0; List vs = new List(); segments--; Vector3 p2, p3; for (int i = 0; i < segments; i++) { p2 = new Vector3(p.x + cx, p.y + cy); float temp = cx; cx = cos * cx - sin * cy; cy = sin * temp + cos * cy; p3 = new Vector3(p.x + cx, p.y + cy); DrawTriangle(vh, vertexs, p, p2, p3, color); } p2 = new Vector3(p.x + cx, p.y + cy); cx = radius; cy = 0; p3 = new Vector3(p.x + cx, p.y + cy); DrawTriangle(vh, vertexs, p, p2, p3, color); } } }