203 lines
5.6 KiB
C#
203 lines
5.6 KiB
C#
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Globalization;
|
||
|
using System.Linq;
|
||
|
using Newtonsoft.Json;
|
||
|
using SkiaSharp;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.UIElements;
|
||
|
|
||
|
|
||
|
namespace BITKit.UX
|
||
|
{
|
||
|
public class SkiaChart : 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 = (SkiaChart)ve;
|
||
|
x.Json = m_JsonAttribute.GetValueFromBag(bag, cc);
|
||
|
x.AllowWarning = m_allowWarningsAttribute.GetValueFromBag(bag, cc);
|
||
|
|
||
|
}
|
||
|
}
|
||
|
public new class UxmlFactory : UxmlFactory<SkiaChart, UxmlTraits> { }
|
||
|
public SkiaChart()
|
||
|
{
|
||
|
RegisterCallback<GeometryChangedEvent>(x =>
|
||
|
{
|
||
|
if (visible)
|
||
|
{
|
||
|
Rebuild();
|
||
|
}
|
||
|
}); 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";
|
||
|
}
|
||
|
|
||
|
private void OnCustomStyleResolved(CustomStyleResolvedEvent evt)
|
||
|
{
|
||
|
if(evt.customStyle.TryGetValue(new CustomStyleProperty<Color>("--chart-secondary-color"), out var helpLineColor))
|
||
|
{
|
||
|
_secondaryColor = helpLineColor;
|
||
|
}
|
||
|
Rebuild();
|
||
|
}
|
||
|
|
||
|
public string Json
|
||
|
{
|
||
|
get => _json;
|
||
|
set
|
||
|
{
|
||
|
_json = value;
|
||
|
Rebuild();
|
||
|
}
|
||
|
}
|
||
|
private string _json;
|
||
|
public bool AllowWarning { get; set; }
|
||
|
public Color SecondaryColor
|
||
|
{
|
||
|
get => _secondaryColor;
|
||
|
set
|
||
|
{
|
||
|
_secondaryColor = value;
|
||
|
Rebuild();
|
||
|
}
|
||
|
}
|
||
|
private Color _secondaryColor;
|
||
|
|
||
|
private readonly VisualElement _dataContainer;
|
||
|
private readonly VisualElement _chartContainer;
|
||
|
private void Rebuild()
|
||
|
{
|
||
|
var data = Array.Empty<float>();
|
||
|
try
|
||
|
{
|
||
|
data = JsonConvert.DeserializeObject<float[]>(Json);
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (AllowWarning)
|
||
|
Debug.LogException(e);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(float.IsNaN(layout.width) || float.IsNaN(layout.height))
|
||
|
return;
|
||
|
|
||
|
if (data is null or { Length: 0 }) return;
|
||
|
|
||
|
|
||
|
|
||
|
_dataContainer.Clear();
|
||
|
_chartContainer.Clear();
|
||
|
|
||
|
var max = data.Max();
|
||
|
var min = data.Min();
|
||
|
//假如min是x,max是y,将中间的差用data.Length划分
|
||
|
|
||
|
_dataContainer.style.flexDirection = FlexDirection.ColumnReverse;
|
||
|
for (var i = 0; i < data.Length; i++)
|
||
|
{
|
||
|
var label = _dataContainer.Create<Label>();
|
||
|
|
||
|
var value = (max - min) / (data.Length - 1) * i + min;
|
||
|
label.text = value.ToString();
|
||
|
}
|
||
|
|
||
|
|
||
|
if(_chartContainer.layout.width is float.NaN or <10 || _chartContainer.layout.height is float.NaN or <10)
|
||
|
return;
|
||
|
var info = new SKImageInfo(
|
||
|
width: (int)_chartContainer.layout.width,
|
||
|
height: (int)_chartContainer.layout.height,
|
||
|
colorType: SKColorType.Rgba8888,
|
||
|
alphaType: SKAlphaType.Premul
|
||
|
);
|
||
|
|
||
|
using var surface = SKSurface.Create(info);
|
||
|
|
||
|
using var canvas = surface.Canvas;
|
||
|
|
||
|
using var linePaint = new SKPaint();
|
||
|
linePaint.Color = resolvedStyle.color.ToSKColor();
|
||
|
linePaint.StrokeWidth = resolvedStyle.unityTextOutlineWidth;
|
||
|
linePaint.IsAntialias = true;
|
||
|
linePaint.Style = SKPaintStyle.Stroke;
|
||
|
|
||
|
using var helpLinePaint = new SKPaint();
|
||
|
helpLinePaint.Color = resolvedStyle.unityTextOutlineColor.ToSKColor();
|
||
|
helpLinePaint.StrokeWidth = 1;
|
||
|
helpLinePaint.Style = SKPaintStyle.Stroke;
|
||
|
|
||
|
using var fillPaint = new SKPaint();
|
||
|
fillPaint.Color = new SKColor(200, 200, 200, 200);
|
||
|
fillPaint.Style=SKPaintStyle.Fill;
|
||
|
|
||
|
DoubleBuffer<SKPoint> buffer = new();
|
||
|
|
||
|
var path = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||
|
|
||
|
path.MoveTo(0,0);
|
||
|
path.LineTo(0,0);
|
||
|
|
||
|
for (var i = 0; i < data.Length; i++)
|
||
|
{
|
||
|
var value = data[i];
|
||
|
var posX = (float)info.Width / (data.Length - 1) * (i);
|
||
|
var d = max - min;
|
||
|
var p = (value - min) / d;
|
||
|
var poxY = info.Height * p;
|
||
|
|
||
|
var currentPoint = new SKPoint(posX, poxY);
|
||
|
if (buffer.TryGetRelease(out var previousPoint))
|
||
|
{
|
||
|
canvas.DrawLine(previousPoint, currentPoint, linePaint);
|
||
|
}
|
||
|
|
||
|
var label = _chartContainer.Create<Label>();
|
||
|
label.text = value.ToString(CultureInfo.InvariantCulture);
|
||
|
label.style.position = Position.Absolute;
|
||
|
label.style.left = posX;//-label.layout.width/2;
|
||
|
label.style.bottom = poxY;//- label.layout.height/2;
|
||
|
|
||
|
canvas.DrawLine(posX, 0, posX, poxY, helpLinePaint);
|
||
|
|
||
|
path.LineTo(posX,poxY);
|
||
|
|
||
|
buffer.Release(currentPoint);
|
||
|
}
|
||
|
|
||
|
path.LineTo(info.Width,0);
|
||
|
path.Close();
|
||
|
|
||
|
|
||
|
//canvas.DrawPath(path,filePaint);
|
||
|
|
||
|
var texture = info.ToTexture2D(surface);
|
||
|
|
||
|
_chartContainer.style.backgroundImage = texture;
|
||
|
}
|
||
|
}
|
||
|
}
|