296 lines
6.6 KiB
C#
296 lines
6.6 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
|
|
namespace BITKit.UX
|
|
{
|
|
public class LineChart : VisualElement
|
|
{
|
|
public new class UxmlTraits : VisualElement.UxmlTraits
|
|
{
|
|
private readonly UxmlStringAttributeDescription m_JsonAttribute = new()
|
|
{
|
|
name = "Json"
|
|
};
|
|
|
|
private readonly UxmlBoolAttributeDescription m_allowWarningsAttribute = new()
|
|
{
|
|
name = "allowWarnings",
|
|
defaultValue = false
|
|
};
|
|
|
|
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
|
|
{
|
|
base.Init(ve, bag, cc);
|
|
var x = (LineChart)ve;
|
|
x.Json = m_JsonAttribute.GetValueFromBag(bag, cc);
|
|
x.AllowWarning = m_allowWarningsAttribute.GetValueFromBag(bag, cc);
|
|
|
|
}
|
|
}
|
|
|
|
public new class UxmlFactory : UxmlFactory<LineChart, UxmlTraits>
|
|
{
|
|
}
|
|
|
|
public LineChart()
|
|
{
|
|
RegisterCallback<CustomStyleResolvedEvent>(OnCustomStyleResolved);
|
|
|
|
|
|
style.flexDirection = FlexDirection.Row;
|
|
_dataContainer = this.Create<VisualElement>();
|
|
_chartContainer = this.Create<VisualElement>();
|
|
_chartContainer.style.flexGrow = 1;
|
|
|
|
_dataContainer.style.justifyContent = Justify.SpaceBetween;
|
|
|
|
_dataContainer.name = "Data";
|
|
_chartContainer.name = "Chart";
|
|
|
|
_chartContainer.generateVisualContent+= GenerateVisualContent;
|
|
|
|
_chartContainer.style.marginBottom = 0;
|
|
_chartContainer.style.marginLeft = 8;
|
|
_chartContainer.style.marginRight = 8;
|
|
_chartContainer.style.marginTop = 0 ;
|
|
|
|
pickingMode = PickingMode.Ignore;
|
|
_dataContainer.pickingMode = PickingMode.Ignore;
|
|
|
|
_chartContainer.style.flexDirection = FlexDirection.RowReverse;
|
|
_dataContainer.style.flexDirection = FlexDirection.ColumnReverse;
|
|
|
|
_chartContainer.RegisterCallback<PointerMoveEvent>(OnPointerMove);
|
|
}
|
|
|
|
private void OnPointerMove(PointerMoveEvent evt)
|
|
{
|
|
//_chartContainer.tooltip = evt.localPosition.ToString();
|
|
}
|
|
|
|
private void OnCustomStyleResolved(CustomStyleResolvedEvent evt)
|
|
{
|
|
if (evt.customStyle.TryGetValue(new CustomStyleProperty<Color>("--chart-secondary-color"),
|
|
out var helpLineColor))
|
|
{
|
|
_secondaryColor = helpLineColor;
|
|
}
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
if (evt.customStyle.TryGetValue(new CustomStyleProperty<Color>($"--chart-line-color--{i}"),
|
|
out var lineColor))
|
|
{
|
|
_lineColors[i] = lineColor;
|
|
}
|
|
}
|
|
|
|
MarkDirtyRepaint();
|
|
}
|
|
|
|
public string Json
|
|
{
|
|
get => _json;
|
|
set
|
|
{
|
|
_json = value;
|
|
GenerateLayout();
|
|
}
|
|
}
|
|
|
|
private string _json;
|
|
public bool AllowWarning { get; set; }
|
|
|
|
public Color SecondaryColor
|
|
{
|
|
get => _secondaryColor;
|
|
set
|
|
{
|
|
_secondaryColor = value;
|
|
GenerateLayout();
|
|
}
|
|
}
|
|
|
|
private Color _secondaryColor;
|
|
|
|
private readonly VisualElement _dataContainer;
|
|
private readonly VisualElement _chartContainer;
|
|
|
|
private readonly Color[] _lineColors = new[]
|
|
{
|
|
Color.red,
|
|
Color.red,
|
|
Color.red,
|
|
Color.red,
|
|
Color.red,
|
|
Color.red,
|
|
Color.red,
|
|
Color.red,
|
|
};
|
|
|
|
private void GenerateLayout()
|
|
{
|
|
JArray[] array;
|
|
float[] data;
|
|
try
|
|
{
|
|
array = JsonConvert.DeserializeObject<JArray[]>(Json);
|
|
data = array.SelectMany(x => x.ToObject<float[]>()).ToArray();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (AllowWarning)
|
|
Debug.LogException(e);
|
|
return;
|
|
}
|
|
|
|
if (data is null or { Length: 0 })
|
|
{
|
|
if (AllowWarning)
|
|
BIT4Log.Log<LineChart>("Data is null or empty");
|
|
return;
|
|
}
|
|
|
|
|
|
//假如min是x,max是y,将中间的差用data.Length划分
|
|
var max = data.Max();
|
|
var min = data.Min();
|
|
|
|
var difference = max - min;
|
|
|
|
var maxLength = array.Select(x => x.Count).Max();
|
|
|
|
max += difference / maxLength;
|
|
min -= difference / maxLength;
|
|
|
|
//Debug.Log($"min:{min} max:{max} difference:{difference} max:{max}");
|
|
|
|
_dataContainer.Clear();
|
|
for (var i = 0; i <= maxLength; i++)
|
|
{
|
|
var label = _dataContainer.Create<Label>();
|
|
|
|
//var value = (max - min) / (data.Length - 1) * i + min;
|
|
|
|
var t = (float)i / maxLength;
|
|
var value = Mathf.Lerp(min, max, t);
|
|
label.text = Math.Round(value, 2).ToString();
|
|
}
|
|
MarkDirtyRepaint();
|
|
}
|
|
|
|
private void GenerateVisualContent(MeshGenerationContext context)
|
|
{
|
|
|
|
if (float.IsNaN(layout.width) || float.IsNaN(layout.height))
|
|
{
|
|
if (AllowWarning)
|
|
BIT4Log.Log<LineChart>("Width or height is NaN");
|
|
return;
|
|
}
|
|
|
|
JArray[] array;
|
|
float[] data;
|
|
try
|
|
{
|
|
array = JsonConvert.DeserializeObject<JArray[]>(Json);
|
|
data = array.SelectMany(x => x.ToObject<float[]>()).ToArray();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (AllowWarning)
|
|
Debug.LogException(e);
|
|
return;
|
|
}
|
|
|
|
if (data is null or { Length: 0 })
|
|
{
|
|
if (AllowWarning)
|
|
BIT4Log.Log<LineChart>("Data is null or empty");
|
|
return;
|
|
}
|
|
|
|
//假如min是x,max是y,将中间的差用data.Length划分
|
|
var max = data.Max();
|
|
var min = data.Min();
|
|
|
|
var difference = max - min;
|
|
|
|
var maxLength = array.Select(x => x.Count).Max();
|
|
|
|
max += difference / maxLength;
|
|
min -= difference / maxLength;
|
|
difference = max - min;
|
|
|
|
if (_chartContainer.layout.width is float.NaN or < 10 || _chartContainer.layout.height is float.NaN or < 10)
|
|
{
|
|
if (AllowWarning)
|
|
BIT4Log.Log<LineChart>("Width or height is NaN or less than 10");
|
|
return;
|
|
}
|
|
|
|
|
|
var linePaint = context.painter2D;
|
|
linePaint.lineWidth = resolvedStyle.unityTextOutlineWidth;
|
|
var width = contentRect.width;
|
|
var height = contentRect.height;
|
|
|
|
|
|
for (var index = 0; index < array.Length; index++)
|
|
{
|
|
var row = array[index];
|
|
DoubleBuffer<Vector2> buffer = new();
|
|
|
|
data = row.ToObject<float[]>();
|
|
|
|
var lastWidth = 0f;
|
|
|
|
linePaint.strokeColor = _lineColors[index];
|
|
var tempColor = _lineColors[index];
|
|
tempColor.a *= 0.2f;
|
|
linePaint.fillColor = tempColor;
|
|
|
|
linePaint.BeginPath();
|
|
|
|
linePaint.MoveTo(new(0,height));
|
|
linePaint.lineJoin = LineJoin.Round;
|
|
|
|
for (var i = 0; i < data.Length; i++)
|
|
{
|
|
var value = data[i];
|
|
var posX = width / data.Length * i;
|
|
var p = (value - min) / difference;
|
|
var poxY = height-height * p;
|
|
|
|
var currentPoint = new Vector2(posX, poxY);
|
|
if (buffer.TryGetRelease(out var previousPoint))
|
|
{
|
|
linePaint.LineTo(previousPoint);
|
|
}
|
|
|
|
linePaint.LineTo(currentPoint);
|
|
|
|
buffer.Release(currentPoint);
|
|
|
|
if(i == data.Length-1)
|
|
{
|
|
lastWidth = posX;
|
|
}
|
|
}
|
|
linePaint.LineTo(new (lastWidth,height));
|
|
linePaint.ClosePath();
|
|
linePaint.Stroke();
|
|
linePaint.Fill();
|
|
}
|
|
}
|
|
}
|
|
}
|