XChart重构:共享数据模型快照、时间序列与多分辨率系列,伪3D散点图/雷达图接入共享数据
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f7b5fd4b6b20b64aaf1f424b4022c1a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using XericLibrary.Runtime.UIGraph;
|
||||
|
||||
namespace XericUI.XChart.Line
|
||||
{
|
||||
[AddComponentMenu("Xeric/XChart/Line/Line Chart")]
|
||||
[ExecuteAlways]
|
||||
public sealed class XLineChart : XChartBase
|
||||
{
|
||||
[SerializeField] private Color m_LineColor = new Color(0.33f, 0.67f, 1f, 1f);
|
||||
[SerializeField] private float m_LineThickness = 2f;
|
||||
[SerializeField] private int m_MaximumBuckets = 1024;
|
||||
[SerializeField] private float m_WindowDuration = 60f;
|
||||
private UILineRendererV2 m_Renderer;
|
||||
private long m_LastVersion = -1;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
EnsureRenderer();
|
||||
}
|
||||
|
||||
private void EnsureRenderer()
|
||||
{
|
||||
if (m_Renderer != null) return;
|
||||
var layer = new GameObject("LineRenderer", typeof(RectTransform), typeof(UILineRendererV2));
|
||||
var rect = layer.GetComponent<RectTransform>();
|
||||
rect.SetParent(ToolsRoot, false);
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.offsetMin = Vector2.zero;
|
||||
rect.offsetMax = Vector2.zero;
|
||||
layer.hideFlags = CurrentFlags;
|
||||
m_Renderer = layer.GetComponent<UILineRendererV2>();
|
||||
}
|
||||
|
||||
protected override void LateUpdate()
|
||||
{
|
||||
base.LateUpdate();
|
||||
EnsureRenderer();
|
||||
var snapshot = DataModel.Snapshot;
|
||||
if (snapshot == null || snapshot.RecordCount < 2 || snapshot.NumericDimensionCount == 0 || snapshot.Version == m_LastVersion) return;
|
||||
m_LastVersion = snapshot.Version;
|
||||
Draw(snapshot);
|
||||
}
|
||||
|
||||
public bool TryAddSample(double timestamp, float value)
|
||||
{
|
||||
EnsureDefaultDimension();
|
||||
return DataModel.TryAddRecord(timestamp, new[] { value });
|
||||
}
|
||||
|
||||
private void EnsureDefaultDimension()
|
||||
{
|
||||
if (DataModel.NumericDimensionCount > 0) return;
|
||||
DataModel.SetDimensions(new List<XChartDimensionDefinition>
|
||||
{
|
||||
new XChartDimensionDefinition("value", "Value", XChartDimensionKind.Number),
|
||||
});
|
||||
}
|
||||
|
||||
private void Draw(XChartDataSnapshot snapshot)
|
||||
{
|
||||
m_Renderer.ClearAll();
|
||||
float width = Mathf.Max(1f, ((RectTransform)transform).rect.width);
|
||||
float height = Mathf.Max(1f, ((RectTransform)transform).rect.height);
|
||||
int start = Mathf.Max(0, snapshot.RecordCount - Mathf.Clamp(Mathf.CeilToInt(width), 2, m_MaximumBuckets));
|
||||
float min = float.MaxValue;
|
||||
float max = float.MinValue;
|
||||
for (int i = start; i < snapshot.RecordCount; i++)
|
||||
{
|
||||
float value = snapshot.Values[i * snapshot.NumericDimensionCount];
|
||||
min = Mathf.Min(min, value);
|
||||
max = Mathf.Max(max, value);
|
||||
}
|
||||
if (Mathf.Approximately(min, max)) max = min + 1f;
|
||||
var points = new Vector2[snapshot.RecordCount - start];
|
||||
for (int i = start; i < snapshot.RecordCount; i++)
|
||||
{
|
||||
float x = (i - start) * width / Mathf.Max(1, snapshot.RecordCount - start - 1) - width * 0.5f;
|
||||
float value = snapshot.Values[i * snapshot.NumericDimensionCount];
|
||||
float y = Mathf.InverseLerp(min, max, value) * height - height * 0.5f;
|
||||
points[i - start] = new Vector2(x, y);
|
||||
}
|
||||
m_Renderer.DrawPolyline(points, m_LineColor, m_LineThickness);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2dfb5c0c7a4c6f141a4f20b3115b21ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -275,11 +275,10 @@ namespace XericUI.XChart.Pseudo3D
|
||||
if (ShowGridLines)
|
||||
{
|
||||
DrawGridLines();
|
||||
GridRenderer.RootObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
GridRenderer.RootObject.SetActive(false);
|
||||
GridRenderer.ClearAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -458,4 +457,4 @@ namespace XericUI.XChart.Pseudo3D
|
||||
GridRenderer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,6 @@ namespace XericUI.XChart.Pseudo3D
|
||||
}
|
||||
|
||||
SyncShapeParams();
|
||||
SetVisible(true);
|
||||
}
|
||||
|
||||
// ── 形状参数同步 ──────────────────────────────────────────
|
||||
@@ -258,4 +257,4 @@ namespace XericUI.XChart.Pseudo3D
|
||||
m_PointColors = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +74,8 @@ namespace XericUI.XChart.Pseudo3D
|
||||
|
||||
private XChartDataModel3D m_DataModel = new XChartDataModel3D();
|
||||
|
||||
/// <summary>数据模型(只读)</summary>
|
||||
public XChartDataModel3D DataModel => m_DataModel;
|
||||
/// <summary>3D 数据模型(只读)</summary>
|
||||
public XChartDataModel3D DataModel3D => m_DataModel;
|
||||
|
||||
// ── 初始化 ────────────────────────────────────────────────
|
||||
|
||||
@@ -203,6 +203,7 @@ namespace XericUI.XChart.Pseudo3D
|
||||
public void SetData(double[] xs, double[] ys, double[] zs)
|
||||
{
|
||||
m_DataModel.SetPoints(xs, ys, zs);
|
||||
SetSharedData(xs, ys, zs);
|
||||
RefreshChart();
|
||||
}
|
||||
|
||||
@@ -225,9 +226,31 @@ namespace XericUI.XChart.Pseudo3D
|
||||
public void AddDataPoint(double x, double y, double z)
|
||||
{
|
||||
m_DataModel.AddPoint(x, y, z);
|
||||
EnsureSharedDimensions();
|
||||
DataModel.TryAddRecord(m_DataModel.PointCount, new[] { (float)x, (float)y, (float)z });
|
||||
RefreshChart();
|
||||
}
|
||||
|
||||
private void SetSharedData(double[] xs, double[] ys, double[] zs)
|
||||
{
|
||||
EnsureSharedDimensions();
|
||||
DataModel.Clear();
|
||||
int count = System.Math.Min(System.Math.Min(xs.Length, ys.Length), zs.Length);
|
||||
for (int i = 0; i < count; i++)
|
||||
DataModel.TryAddRecord(i, new[] { (float)xs[i], (float)ys[i], (float)zs[i] });
|
||||
}
|
||||
|
||||
private void EnsureSharedDimensions()
|
||||
{
|
||||
if (DataModel.NumericDimensionCount == 3) return;
|
||||
DataModel.SetDimensions(new System.Collections.Generic.List<XChartDimensionDefinition>
|
||||
{
|
||||
new XChartDimensionDefinition("x", "X", XChartDimensionKind.Number),
|
||||
new XChartDimensionDefinition("y", "Y", XChartDimensionKind.Number),
|
||||
new XChartDimensionDefinition("z", "Z", XChartDimensionKind.Number),
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>清空所有数据</summary>
|
||||
public void ClearData()
|
||||
{
|
||||
@@ -384,4 +407,4 @@ namespace XericUI.XChart.Pseudo3D
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,6 @@ namespace XericUI.XChart.Radar
|
||||
var labelsObj = CreateChild("Labels");
|
||||
LabelsRoot = labelsObj;
|
||||
|
||||
SetVisible(true);
|
||||
}
|
||||
|
||||
// ── 标记脏 ────────────────────────────────────────────────
|
||||
@@ -281,7 +280,7 @@ namespace XericUI.XChart.Radar
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var labelObj = new GameObject($"Label_{i}");
|
||||
var labelObj = new GameObject($"Label_{i}", typeof(RectTransform));
|
||||
labelObj.transform.SetParent(LabelsRoot, false);
|
||||
labelObj.hideFlags = CurrentFlags;
|
||||
|
||||
@@ -311,4 +310,4 @@ namespace XericUI.XChart.Radar
|
||||
LabelTexts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ namespace XericUI.XChart.Radar
|
||||
rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
SetVisible(true);
|
||||
}
|
||||
|
||||
// ── 数据更新 ──────────────────────────────────────────────
|
||||
@@ -133,4 +132,4 @@ namespace XericUI.XChart.Radar
|
||||
m_Series = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ namespace XericUI.XChart.Radar
|
||||
|
||||
private XChartRadarDataModel m_DataModel = new XChartRadarDataModel();
|
||||
|
||||
/// <summary>数据模型(只读)</summary>
|
||||
public XChartRadarDataModel DataModel => m_DataModel;
|
||||
/// <summary>雷达数据模型(只读)</summary>
|
||||
public XChartRadarDataModel RadarDataModel => m_DataModel;
|
||||
|
||||
// ── 预置颜色轮(用于自动分配颜色) ────────────────────────
|
||||
|
||||
@@ -208,6 +208,10 @@ namespace XericUI.XChart.Radar
|
||||
public void SetDimensions(params string[] dimensionNames)
|
||||
{
|
||||
m_DataModel.SetDimensions(dimensionNames);
|
||||
var dimensions = new System.Collections.Generic.List<XChartDimensionDefinition>(dimensionNames.Length);
|
||||
for (int i = 0; i < dimensionNames.Length; i++)
|
||||
dimensions.Add(new XChartDimensionDefinition($"radar_{i}", dimensionNames[i], XChartDimensionKind.Number));
|
||||
DataModel.SetDimensions(dimensions);
|
||||
// 由 Update 自动同步
|
||||
}
|
||||
|
||||
@@ -222,12 +226,14 @@ namespace XericUI.XChart.Radar
|
||||
{
|
||||
Color color = s_ColorWheel[m_DataModel.SeriesCount % s_ColorWheel.Length];
|
||||
m_DataModel.AddSeries(name, values, color, m_DefaultLineWidth);
|
||||
DataModel.TryAddRecord(m_DataModel.SeriesCount, values, new[] { name });
|
||||
}
|
||||
|
||||
/// <summary>添加一个数据系列(指定颜色)</summary>
|
||||
public void AddSeries(string name, float[] values, Color color, float lineWidth = 2f)
|
||||
{
|
||||
m_DataModel.AddSeries(name, values, color, lineWidth);
|
||||
DataModel.TryAddRecord(m_DataModel.SeriesCount, values, new[] { name });
|
||||
}
|
||||
|
||||
/// <summary>批量设置数据系列</summary>
|
||||
@@ -360,4 +366,4 @@ namespace XericUI.XChart.Radar
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@ namespace XericUI.XChart
|
||||
[Header("工具管理")]
|
||||
[SerializeField] private bool m_ShowDebug;
|
||||
[SerializeField] private bool m_HideAllTools;
|
||||
[SerializeField] private XChartDataModel m_DataModel = new XChartDataModel();
|
||||
|
||||
public XChartToolsManager ToolManager { get; private set; }
|
||||
public Transform ToolsRoot { get; private set; }
|
||||
public XChartDataModel DataModel => m_DataModel;
|
||||
|
||||
private bool m_IsInitialized;
|
||||
|
||||
@@ -48,10 +50,21 @@ namespace XericUI.XChart
|
||||
|
||||
var flags = CurrentFlags;
|
||||
|
||||
var toolsObj = new GameObject("[XChartTools]");
|
||||
toolsObj.transform.SetParent(transform, false);
|
||||
toolsObj.hideFlags = flags;
|
||||
ToolsRoot = toolsObj.transform;
|
||||
var toolsRoot = transform.Find("[XChartTools]") as RectTransform;
|
||||
if (toolsRoot == null)
|
||||
{
|
||||
var toolsObj = new GameObject("[XChartTools]", typeof(RectTransform));
|
||||
toolsRoot = toolsObj.GetComponent<RectTransform>();
|
||||
toolsRoot.SetParent(transform, false);
|
||||
}
|
||||
|
||||
toolsRoot.anchorMin = Vector2.zero;
|
||||
toolsRoot.anchorMax = Vector2.one;
|
||||
toolsRoot.offsetMin = Vector2.zero;
|
||||
toolsRoot.offsetMax = Vector2.zero;
|
||||
toolsRoot.pivot = new Vector2(0.5f, 0.5f);
|
||||
toolsRoot.gameObject.hideFlags = flags;
|
||||
ToolsRoot = toolsRoot;
|
||||
|
||||
ToolManager = new XChartToolsManager
|
||||
{
|
||||
@@ -94,6 +107,7 @@ namespace XericUI.XChart
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
m_DataModel?.DisposeRuntime();
|
||||
if (ToolManager != null)
|
||||
{
|
||||
ToolManager.Dispose();
|
||||
@@ -115,4 +129,4 @@ namespace XericUI.XChart
|
||||
// 由派生类按需扩展
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+177
-116
@@ -1,22 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.XChart
|
||||
{
|
||||
/// <summary>
|
||||
/// 3D 散点图数据模型。
|
||||
///
|
||||
/// 存储 XYZ 三维坐标点数据,支持脏标记机制。
|
||||
/// 每个数据点包含 (X, Y, Z) 三个浮点坐标值。
|
||||
/// </summary>
|
||||
public enum XChartDimensionKind
|
||||
{
|
||||
Number,
|
||||
Time,
|
||||
Label,
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class XChartDimensionDefinition
|
||||
{
|
||||
[SerializeField] private string m_Key;
|
||||
[SerializeField] private string m_DisplayName;
|
||||
[SerializeField] private XChartDimensionKind m_Kind;
|
||||
[SerializeField] private string m_Format = "0.##";
|
||||
[SerializeField] private bool m_Visible = true;
|
||||
|
||||
public string Key => m_Key;
|
||||
public string DisplayName => m_DisplayName;
|
||||
public XChartDimensionKind Kind => m_Kind;
|
||||
public string Format => m_Format;
|
||||
public bool Visible => m_Visible;
|
||||
|
||||
public XChartDimensionDefinition(string key, string displayName, XChartDimensionKind kind)
|
||||
{
|
||||
m_Key = key;
|
||||
m_DisplayName = displayName;
|
||||
m_Kind = kind;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class XChartRecord
|
||||
{
|
||||
[SerializeField] private long m_RecordId;
|
||||
[SerializeField] private double m_Timestamp;
|
||||
[SerializeField] private float[] m_Values = Array.Empty<float>();
|
||||
[SerializeField] private string[] m_Labels = Array.Empty<string>();
|
||||
|
||||
public long RecordId => m_RecordId;
|
||||
public double Timestamp => m_Timestamp;
|
||||
public IReadOnlyList<float> Values => m_Values;
|
||||
public IReadOnlyList<string> Labels => m_Labels;
|
||||
|
||||
internal XChartRecord(long recordId, double timestamp, float[] values, string[] labels)
|
||||
{
|
||||
m_RecordId = recordId;
|
||||
m_Timestamp = timestamp;
|
||||
m_Values = values;
|
||||
m_Labels = labels;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class XChartDataSnapshot : IDisposable
|
||||
{
|
||||
public NativeArray<double> Times { get; private set; }
|
||||
public NativeArray<long> RecordIds { get; private set; }
|
||||
public NativeArray<float> Values { get; private set; }
|
||||
public int RecordCount { get; private set; }
|
||||
public int NumericDimensionCount { get; private set; }
|
||||
public long Version { get; private set; }
|
||||
|
||||
internal XChartDataSnapshot(IReadOnlyList<XChartRecord> records, int numericDimensionCount, long version)
|
||||
{
|
||||
RecordCount = records.Count;
|
||||
NumericDimensionCount = numericDimensionCount;
|
||||
Version = version;
|
||||
var times = new NativeArray<double>(RecordCount, Allocator.Persistent);
|
||||
var recordIds = new NativeArray<long>(RecordCount, Allocator.Persistent);
|
||||
var values = new NativeArray<float>(RecordCount * numericDimensionCount, Allocator.Persistent);
|
||||
for (int i = 0; i < RecordCount; i++)
|
||||
{
|
||||
times[i] = records[i].Timestamp;
|
||||
recordIds[i] = records[i].RecordId;
|
||||
for (int j = 0; j < numericDimensionCount; j++) values[i * numericDimensionCount + j] = records[i].Values[j];
|
||||
}
|
||||
Times = times;
|
||||
RecordIds = recordIds;
|
||||
Values = values;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Times.IsCreated) Times.Dispose();
|
||||
if (RecordIds.IsCreated) RecordIds.Dispose();
|
||||
if (Values.IsCreated) Values.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class XChartDataModel
|
||||
{
|
||||
[SerializeField] private int m_Capacity = 4096;
|
||||
[SerializeField] private List<XChartDimensionDefinition> m_Dimensions = new List<XChartDimensionDefinition>();
|
||||
[SerializeField] private List<XChartRecord> m_Records = new List<XChartRecord>();
|
||||
[NonSerialized] private XChartDataSnapshot m_Snapshot;
|
||||
[NonSerialized] private long m_Version;
|
||||
[NonSerialized] private long m_NextRecordId = 1;
|
||||
|
||||
public IReadOnlyList<XChartDimensionDefinition> Dimensions => m_Dimensions;
|
||||
public IReadOnlyList<XChartRecord> Records => m_Records;
|
||||
public XChartDataSnapshot Snapshot => m_Snapshot;
|
||||
public long Version => m_Version;
|
||||
public int Capacity { get => m_Capacity; set => m_Capacity = Mathf.Max(1, value); }
|
||||
public int NumericDimensionCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i < m_Dimensions.Count; i++) if (m_Dimensions[i].Kind == XChartDimensionKind.Number) count++;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDimensions(IList<XChartDimensionDefinition> dimensions)
|
||||
{
|
||||
m_Dimensions.Clear();
|
||||
if (dimensions != null) for (int i = 0; i < dimensions.Count; i++) m_Dimensions.Add(dimensions[i]);
|
||||
int numericCount = NumericDimensionCount;
|
||||
for (int i = 0; i < m_Records.Count; i++)
|
||||
{
|
||||
var values = new float[numericCount];
|
||||
int copyCount = Math.Min(values.Length, m_Records[i].Values.Count);
|
||||
for (int j = 0; j < copyCount; j++) values[j] = m_Records[i].Values[j];
|
||||
m_Records[i] = new XChartRecord(m_Records[i].RecordId, m_Records[i].Timestamp, values, CopyLabels(m_Records[i].Labels));
|
||||
}
|
||||
Publish();
|
||||
}
|
||||
|
||||
public bool TryAddRecord(double timestamp, float[] values, string[] labels = null)
|
||||
{
|
||||
if (double.IsNaN(timestamp) || double.IsInfinity(timestamp) || values == null || values.Length != NumericDimensionCount) return false;
|
||||
for (int i = 0; i < values.Length; i++) if (float.IsNaN(values[i]) || float.IsInfinity(values[i])) return false;
|
||||
if (m_Records.Count > 0 && timestamp < m_Records[m_Records.Count - 1].Timestamp) return false;
|
||||
if (m_Records.Count == Capacity) m_Records.RemoveAt(0);
|
||||
var copy = new float[values.Length];
|
||||
Array.Copy(values, copy, values.Length);
|
||||
m_Records.Add(new XChartRecord(m_NextRecordId++, timestamp, copy, labels ?? Array.Empty<string>()));
|
||||
Publish();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_Records.Clear();
|
||||
Publish();
|
||||
}
|
||||
|
||||
public void Publish()
|
||||
{
|
||||
var oldSnapshot = m_Snapshot;
|
||||
m_Version++;
|
||||
m_Snapshot = new XChartDataSnapshot(m_Records, NumericDimensionCount, m_Version);
|
||||
oldSnapshot?.Dispose();
|
||||
}
|
||||
|
||||
public void DisposeRuntime()
|
||||
{
|
||||
m_Snapshot?.Dispose();
|
||||
m_Snapshot = null;
|
||||
}
|
||||
|
||||
private static string[] CopyLabels(IReadOnlyList<string> labels)
|
||||
{
|
||||
var copy = new string[labels.Count];
|
||||
for (int i = 0; i < labels.Count; i++) copy[i] = labels[i];
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
public class XChartDataModel3D
|
||||
{
|
||||
// ── 数据点结构 ────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 单个数据点。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct DataPoint
|
||||
{
|
||||
@@ -34,46 +193,21 @@ namespace XericUI.XChart
|
||||
public Vector3 ToVector3() => new Vector3((float)x, (float)y, (float)z);
|
||||
}
|
||||
|
||||
// ── 数据存储 ──────────────────────────────────────────────
|
||||
|
||||
private List<DataPoint> m_Points = new List<DataPoint>();
|
||||
|
||||
/// <summary>所有数据点列表(只读)</summary>
|
||||
public IReadOnlyList<DataPoint> Points => m_Points;
|
||||
|
||||
/// <summary>数据点数量</summary>
|
||||
public int PointCount => m_Points.Count;
|
||||
|
||||
/// <summary>是否有数据</summary>
|
||||
public bool HasData => m_Points.Count > 0;
|
||||
|
||||
// ── 数据范围(自动追踪) ──────────────────────────────────
|
||||
|
||||
/// <summary>X 轴最小值</summary>
|
||||
public double MinX { get; private set; }
|
||||
/// <summary>X 轴最大值</summary>
|
||||
public double MaxX { get; private set; }
|
||||
/// <summary>Y 轴最小值</summary>
|
||||
public double MinY { get; private set; }
|
||||
/// <summary>Y 轴最大值</summary>
|
||||
public double MaxY { get; private set; }
|
||||
/// <summary>Z 轴最小值</summary>
|
||||
public double MinZ { get; private set; }
|
||||
/// <summary>Z 轴最大值</summary>
|
||||
public double MaxZ { get; private set; }
|
||||
|
||||
/// <summary>X 轴跨度</summary>
|
||||
public double RangeX => MaxX - MinX;
|
||||
/// <summary>Y 轴跨度</summary>
|
||||
public double RangeY => MaxY - MinY;
|
||||
/// <summary>Z 轴跨度</summary>
|
||||
public double RangeZ => MaxZ - MinZ;
|
||||
|
||||
/// <summary>所有轴的最大跨度(用于归一化)</summary>
|
||||
public double MaxRange => Math.Max(Math.Max(RangeX, RangeY), RangeZ);
|
||||
|
||||
// ── 脏标记 ────────────────────────────────────────────────
|
||||
|
||||
[Flags]
|
||||
public enum DirtyFlags
|
||||
{
|
||||
@@ -84,46 +218,27 @@ namespace XericUI.XChart
|
||||
}
|
||||
|
||||
public DirtyFlags CurrentDirty { get; private set; }
|
||||
|
||||
public void MarkDirty(DirtyFlags flag)
|
||||
{
|
||||
CurrentDirty |= flag;
|
||||
}
|
||||
|
||||
public void ClearDirty(DirtyFlags flag)
|
||||
{
|
||||
CurrentDirty &= ~flag;
|
||||
}
|
||||
|
||||
public void MarkDirty(DirtyFlags flag) { CurrentDirty |= flag; }
|
||||
public void ClearDirty(DirtyFlags flag) { CurrentDirty &= ~flag; }
|
||||
public bool IsDirty(DirtyFlags flag) => (CurrentDirty & flag) != 0;
|
||||
|
||||
// ── 数据操作 ──────────────────────────────────────────────
|
||||
|
||||
/// <summary>设置所有数据点(覆盖模式)</summary>
|
||||
public void SetPoints(IList<DataPoint> points)
|
||||
{
|
||||
m_Points.Clear();
|
||||
if (points != null)
|
||||
{
|
||||
foreach (var p in points)
|
||||
m_Points.Add(p);
|
||||
}
|
||||
if (points != null) for (int i = 0; i < points.Count; i++) m_Points.Add(points[i]);
|
||||
RecalculateRange();
|
||||
MarkDirty(DirtyFlags.All);
|
||||
}
|
||||
|
||||
/// <summary>设置原始坐标数据(批量)</summary>
|
||||
public void SetPoints(double[] xs, double[] ys, double[] zs)
|
||||
{
|
||||
m_Points.Clear();
|
||||
int count = Math.Min(Math.Min(xs.Length, ys.Length), zs.Length);
|
||||
for (int i = 0; i < count; i++)
|
||||
m_Points.Add(new DataPoint(xs[i], ys[i], zs[i]));
|
||||
for (int i = 0; i < count; i++) m_Points.Add(new DataPoint(xs[i], ys[i], zs[i]));
|
||||
RecalculateRange();
|
||||
MarkDirty(DirtyFlags.All);
|
||||
}
|
||||
|
||||
/// <summary>添加一个数据点</summary>
|
||||
public void AddPoint(double x, double y, double z)
|
||||
{
|
||||
m_Points.Add(new DataPoint(x, y, z));
|
||||
@@ -131,27 +246,6 @@ namespace XericUI.XChart
|
||||
MarkDirty(DirtyFlags.DataChanged);
|
||||
}
|
||||
|
||||
/// <summary>批量添加数据点</summary>
|
||||
public void AddPoints(IList<DataPoint> points)
|
||||
{
|
||||
foreach (var p in points)
|
||||
{
|
||||
m_Points.Add(p);
|
||||
UpdateRangeForPoint(p.x, p.y, p.z);
|
||||
}
|
||||
MarkDirty(DirtyFlags.DataChanged);
|
||||
}
|
||||
|
||||
/// <summary>移除指定索引的数据点</summary>
|
||||
public void RemovePoint(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_Points.Count) return;
|
||||
m_Points.RemoveAt(index);
|
||||
RecalculateRange();
|
||||
MarkDirty(DirtyFlags.All);
|
||||
}
|
||||
|
||||
/// <summary>清空所有数据</summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
m_Points.Clear();
|
||||
@@ -159,48 +253,20 @@ namespace XericUI.XChart
|
||||
MarkDirty(DirtyFlags.All);
|
||||
}
|
||||
|
||||
/// <summary>获取指定索引的数据点</summary>
|
||||
public DataPoint GetPoint(int index)
|
||||
{
|
||||
return m_Points[index];
|
||||
}
|
||||
|
||||
/// <summary>获取所有数据点作为 Vector3 数组</summary>
|
||||
public Vector3[] GetPointsAsVector3()
|
||||
{
|
||||
var result = new Vector3[m_Points.Count];
|
||||
for (int i = 0; i < m_Points.Count; i++)
|
||||
result[i] = m_Points[i].ToVector3();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将数据点归一化到 [-1, 1] 立方体空间。
|
||||
/// 基于各轴的最大跨度进行等比例缩放,保持比例。
|
||||
/// </summary>
|
||||
public Vector3[] GetNormalizedPoints()
|
||||
{
|
||||
if (m_Points.Count == 0) return Array.Empty<Vector3>();
|
||||
double range = MaxRange;
|
||||
if (range < double.Epsilon) range = 1.0;
|
||||
|
||||
double cx = (MinX + MaxX) * 0.5;
|
||||
double cy = (MinY + MaxY) * 0.5;
|
||||
double cz = (MinZ + MaxZ) * 0.5;
|
||||
|
||||
var result = new Vector3[m_Points.Count];
|
||||
for (int i = 0; i < m_Points.Count; i++)
|
||||
{
|
||||
result[i] = new Vector3(
|
||||
(float)((m_Points[i].x - cx) / range * 2.0),
|
||||
(float)((m_Points[i].y - cy) / range * 2.0),
|
||||
(float)((m_Points[i].z - cz) / range * 2.0));
|
||||
}
|
||||
result[i] = new Vector3((float)((m_Points[i].x - cx) / range * 2.0), (float)((m_Points[i].y - cy) / range * 2.0), (float)((m_Points[i].z - cz) / range * 2.0));
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── 范围计算 ──────────────────────────────────────────────
|
||||
|
||||
private void RecalculateRange()
|
||||
{
|
||||
if (m_Points.Count == 0)
|
||||
@@ -208,15 +274,10 @@ namespace XericUI.XChart
|
||||
MinX = MaxX = MinY = MaxY = MinZ = MaxZ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
MinX = MaxX = m_Points[0].x;
|
||||
MinY = MaxY = m_Points[0].y;
|
||||
MinZ = MaxZ = m_Points[0].z;
|
||||
|
||||
for (int i = 1; i < m_Points.Count; i++)
|
||||
{
|
||||
UpdateRangeForPoint(m_Points[i].x, m_Points[i].y, m_Points[i].z);
|
||||
}
|
||||
for (int i = 1; i < m_Points.Count; i++) UpdateRangeForPoint(m_Points[i].x, m_Points[i].y, m_Points[i].z);
|
||||
}
|
||||
|
||||
private void UpdateRangeForPoint(double x, double y, double z)
|
||||
@@ -229,4 +290,4 @@ namespace XericUI.XChart
|
||||
if (z > MaxZ) MaxZ = z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace XericUI.XChart
|
||||
{
|
||||
public struct XChartAggregateBucket
|
||||
{
|
||||
public int StartIndex;
|
||||
public int SampleCount;
|
||||
public float First;
|
||||
public float Last;
|
||||
public float Minimum;
|
||||
public float Maximum;
|
||||
public float Mean;
|
||||
}
|
||||
|
||||
public sealed class XChartMultiResolutionSeries
|
||||
{
|
||||
private readonly float[] m_Values;
|
||||
private readonly List<XChartAggregateBucket[]> m_Levels = new List<XChartAggregateBucket[]>();
|
||||
private int m_Head;
|
||||
private int m_Count;
|
||||
private long m_Version;
|
||||
private long m_BuiltVersion = -1;
|
||||
|
||||
public XChartMultiResolutionSeries(int capacity)
|
||||
{
|
||||
if (capacity < 2) throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
m_Values = new float[capacity];
|
||||
}
|
||||
|
||||
public int Capacity => m_Values.Length;
|
||||
public int Count => m_Count;
|
||||
public long Version => m_Version;
|
||||
|
||||
public void Add(float value)
|
||||
{
|
||||
if (float.IsNaN(value) || float.IsInfinity(value)) return;
|
||||
int index = (m_Head + m_Count) % Capacity;
|
||||
if (m_Count == Capacity)
|
||||
{
|
||||
m_Values[m_Head] = value;
|
||||
m_Head = (m_Head + 1) % Capacity;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Values[index] = value;
|
||||
m_Count++;
|
||||
}
|
||||
m_Version++;
|
||||
}
|
||||
|
||||
public void GetBuckets(int maximumBucketCount, List<XChartAggregateBucket> output)
|
||||
{
|
||||
if (output == null) throw new ArgumentNullException(nameof(output));
|
||||
if (maximumBucketCount < 1) throw new ArgumentOutOfRangeException(nameof(maximumBucketCount));
|
||||
RebuildLevels();
|
||||
output.Clear();
|
||||
if (m_Count == 0) return;
|
||||
int level = 0;
|
||||
while (level + 1 < m_Levels.Count && m_Levels[level + 1].Length >= maximumBucketCount) level++;
|
||||
var buckets = m_Levels[level];
|
||||
for (int i = 0; i < buckets.Length; i++) output.Add(buckets[i]);
|
||||
}
|
||||
|
||||
private void RebuildLevels()
|
||||
{
|
||||
if (m_BuiltVersion == m_Version) return;
|
||||
m_Levels.Clear();
|
||||
var level = new XChartAggregateBucket[m_Count];
|
||||
for (int i = 0; i < m_Count; i++)
|
||||
{
|
||||
float value = m_Values[(m_Head + i) % Capacity];
|
||||
level[i] = new XChartAggregateBucket { StartIndex = i, SampleCount = 1, First = value, Last = value, Minimum = value, Maximum = value, Mean = value };
|
||||
}
|
||||
m_Levels.Add(level);
|
||||
while (level.Length > 1)
|
||||
{
|
||||
var next = new XChartAggregateBucket[(level.Length + 1) / 2];
|
||||
for (int i = 0; i < next.Length; i++)
|
||||
{
|
||||
var left = level[i * 2];
|
||||
if (i * 2 + 1 >= level.Length) { next[i] = left; continue; }
|
||||
var right = level[i * 2 + 1];
|
||||
int count = left.SampleCount + right.SampleCount;
|
||||
next[i] = new XChartAggregateBucket
|
||||
{
|
||||
StartIndex = left.StartIndex,
|
||||
SampleCount = count,
|
||||
First = left.First,
|
||||
Last = right.Last,
|
||||
Minimum = Math.Min(left.Minimum, right.Minimum),
|
||||
Maximum = Math.Max(left.Maximum, right.Maximum),
|
||||
Mean = (left.Mean * left.SampleCount + right.Mean * right.SampleCount) / count,
|
||||
};
|
||||
}
|
||||
m_Levels.Add(next);
|
||||
level = next;
|
||||
}
|
||||
m_BuiltVersion = m_Version;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5da2dfee6a6ec904cb16de6d035c7416
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,297 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace XericUI.XChart
|
||||
{
|
||||
/// <summary>输入值不可用于绘图或统计时的处理方式。</summary>
|
||||
public enum XChartInvalidSamplePolicy
|
||||
{
|
||||
Reject,
|
||||
StoreGap,
|
||||
ClampToLastFinite,
|
||||
}
|
||||
|
||||
/// <summary>按时间排列的单个时序样本。无效样本是折线断开标记,不参与数值统计。</summary>
|
||||
public struct XChartTimeSample
|
||||
{
|
||||
public readonly double Time;
|
||||
public readonly double Value;
|
||||
public readonly bool IsValid;
|
||||
|
||||
public XChartTimeSample(double time, double value, bool isValid = true)
|
||||
{
|
||||
Time = time;
|
||||
Value = value;
|
||||
IsValid = isValid;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum XChartStatisticMask
|
||||
{
|
||||
None = 0,
|
||||
First = 1 << 0,
|
||||
Last = 1 << 1,
|
||||
Minimum = 1 << 2,
|
||||
Maximum = 1 << 3,
|
||||
Mean = 1 << 4,
|
||||
Sum = 1 << 5,
|
||||
Rms = 1 << 6,
|
||||
Count = 1 << 7,
|
||||
Default = First | Last | Minimum | Maximum | Mean | Count,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一个屏幕列/时间桶的统计快照。绘制包络时使用 Minimum/Maximum;普通折线可选择 Mean 或 Last。
|
||||
/// BucketIndex 对应请求窗口的 [0, BucketCount) 横向列;HadInvalid 用于在此处断线。
|
||||
/// </summary>
|
||||
public struct XChartTimeBucket
|
||||
{
|
||||
public int BucketIndex;
|
||||
public int BucketCount;
|
||||
public int ValidCount;
|
||||
public int InvalidCount;
|
||||
public double StartTime;
|
||||
public double EndTime;
|
||||
public double First;
|
||||
public double Last;
|
||||
public double Minimum;
|
||||
public double Maximum;
|
||||
public double Sum;
|
||||
public double Mean;
|
||||
public double Rms;
|
||||
|
||||
public bool HasValue => ValidCount > 0;
|
||||
public bool HadInvalid => InvalidCount > 0;
|
||||
public double NormalizedCenter => (BucketIndex + 0.5d) / BucketCount;
|
||||
|
||||
internal void Add(in XChartTimeSample sample)
|
||||
{
|
||||
if (!sample.IsValid)
|
||||
{
|
||||
InvalidCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ValidCount == 0)
|
||||
{
|
||||
First = Minimum = Maximum = sample.Value;
|
||||
}
|
||||
|
||||
Last = sample.Value;
|
||||
Minimum = Math.Min(Minimum, sample.Value);
|
||||
Maximum = Math.Max(Maximum, sample.Value);
|
||||
Sum += sample.Value;
|
||||
Rms += sample.Value * sample.Value;
|
||||
ValidCount++;
|
||||
}
|
||||
|
||||
internal void FinalizeStatistics()
|
||||
{
|
||||
if (ValidCount == 0)
|
||||
{
|
||||
First = Last = Minimum = Maximum = Sum = Mean = Rms = double.NaN;
|
||||
return;
|
||||
}
|
||||
|
||||
Mean = Sum / ValidCount;
|
||||
Rms = Math.Sqrt(Rms / ValidCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 面向实时图表的固定容量时序存储。
|
||||
///
|
||||
/// 写入达到容量后覆盖最早数据;读取按显示窗口映射到固定数量的像素桶,并仅在数据版本或查询参数改变时重算。
|
||||
/// 它不持有 Unity 对象,可被后台采集线程调用;返回的数据被复制到调用方列表,避免暴露环形数组。
|
||||
/// </summary>
|
||||
public sealed class XChartTimeSeriesData
|
||||
{
|
||||
private readonly object m_Gate = new object();
|
||||
private readonly XChartTimeSample[] m_Ring;
|
||||
private readonly XChartTimeBucket[] m_WorkBuckets;
|
||||
private readonly List<XChartTimeBucket> m_CachedBuckets;
|
||||
private readonly int[] m_TouchedBucketIndices;
|
||||
|
||||
private int m_Head;
|
||||
private int m_Count;
|
||||
private long m_Version;
|
||||
private bool m_HasLastFinite;
|
||||
private double m_LastFiniteValue;
|
||||
|
||||
private long m_CachedVersion = -1;
|
||||
private double m_CachedStart;
|
||||
private double m_CachedEnd;
|
||||
private int m_CachedBucketCount;
|
||||
|
||||
public XChartTimeSeriesData(int capacity, int maximumBucketCount = 2048,
|
||||
XChartInvalidSamplePolicy invalidSamplePolicy = XChartInvalidSamplePolicy.StoreGap)
|
||||
{
|
||||
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
if (maximumBucketCount <= 0) throw new ArgumentOutOfRangeException(nameof(maximumBucketCount));
|
||||
|
||||
Capacity = capacity;
|
||||
MaximumBucketCount = maximumBucketCount;
|
||||
InvalidSamplePolicy = invalidSamplePolicy;
|
||||
m_Ring = new XChartTimeSample[capacity];
|
||||
m_WorkBuckets = new XChartTimeBucket[maximumBucketCount];
|
||||
m_TouchedBucketIndices = new int[maximumBucketCount];
|
||||
m_CachedBuckets = new List<XChartTimeBucket>(maximumBucketCount);
|
||||
}
|
||||
|
||||
public int Capacity { get; }
|
||||
public int MaximumBucketCount { get; }
|
||||
public XChartInvalidSamplePolicy InvalidSamplePolicy { get; set; }
|
||||
public int Count { get { lock (m_Gate) return m_Count; } }
|
||||
public long Version { get { lock (m_Gate) return m_Version; } }
|
||||
private long m_DroppedSampleCount;
|
||||
public long DroppedSampleCount { get { lock (m_Gate) return m_DroppedSampleCount; } }
|
||||
|
||||
/// <summary>写入一条样本。时间必须为有限数;非递增时间会被拒绝,保证窗口映射可预测。</summary>
|
||||
public bool TryAdd(double time, double value)
|
||||
{
|
||||
lock (m_Gate)
|
||||
{
|
||||
if (!IsFinite(time))
|
||||
{
|
||||
m_DroppedSampleCount++;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Count > 0 && time <= GetLogicalSample(m_Count - 1).Time)
|
||||
{
|
||||
m_DroppedSampleCount++;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool valid = IsFinite(value);
|
||||
if (!valid && InvalidSamplePolicy == XChartInvalidSamplePolicy.Reject)
|
||||
{
|
||||
m_DroppedSampleCount++;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!valid && InvalidSamplePolicy == XChartInvalidSamplePolicy.ClampToLastFinite && m_HasLastFinite)
|
||||
{
|
||||
value = m_LastFiniteValue;
|
||||
valid = true;
|
||||
}
|
||||
|
||||
AddUnchecked(new XChartTimeSample(time, valid ? value : double.NaN, valid));
|
||||
if (valid)
|
||||
{
|
||||
m_LastFiniteValue = value;
|
||||
m_HasLastFinite = true;
|
||||
}
|
||||
m_Version++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>清除样本和缓存,不改变已配置容量。</summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (m_Gate)
|
||||
{
|
||||
m_Head = m_Count = 0;
|
||||
m_HasLastFinite = false;
|
||||
m_CachedVersion = -1;
|
||||
m_CachedBuckets.Clear();
|
||||
m_Version++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回 [startTime, endTime] 内的非空统计桶。bucketCount 通常设为绘图区实际像素宽度,超过上限会抛出异常以杜绝隐式扩容。
|
||||
/// 调用方应复用 output;方法不会分配新的托管数组。
|
||||
/// </summary>
|
||||
public void GetBuckets(double startTime, double endTime, int bucketCount, List<XChartTimeBucket> output)
|
||||
{
|
||||
if (output == null) throw new ArgumentNullException(nameof(output));
|
||||
if (!IsFinite(startTime) || !IsFinite(endTime) || endTime <= startTime)
|
||||
throw new ArgumentException("显示窗口必须为有限且递增的时间区间。");
|
||||
if (bucketCount <= 0 || bucketCount > MaximumBucketCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(bucketCount));
|
||||
|
||||
lock (m_Gate)
|
||||
{
|
||||
if (!IsCacheValid(startTime, endTime, bucketCount))
|
||||
RebuildBuckets(startTime, endTime, bucketCount);
|
||||
|
||||
output.Clear();
|
||||
for (int i = 0; i < m_CachedBuckets.Count; i++) output.Add(m_CachedBuckets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCacheValid(double startTime, double endTime, int bucketCount)
|
||||
{
|
||||
return m_CachedVersion == m_Version && m_CachedStart == startTime && m_CachedEnd == endTime && m_CachedBucketCount == bucketCount;
|
||||
}
|
||||
|
||||
private void RebuildBuckets(double startTime, double endTime, int bucketCount)
|
||||
{
|
||||
m_CachedBuckets.Clear();
|
||||
int touchedCount = 0;
|
||||
double duration = endTime - startTime;
|
||||
|
||||
for (int logicalIndex = 0; logicalIndex < m_Count; logicalIndex++)
|
||||
{
|
||||
XChartTimeSample sample = GetLogicalSample(logicalIndex);
|
||||
if (sample.Time < startTime || sample.Time > endTime) continue;
|
||||
|
||||
int index = sample.Time == endTime
|
||||
? bucketCount - 1
|
||||
: Math.Min(bucketCount - 1, (int)((sample.Time - startTime) / duration * bucketCount));
|
||||
ref XChartTimeBucket bucket = ref m_WorkBuckets[index];
|
||||
if (bucket.ValidCount == 0 && bucket.InvalidCount == 0)
|
||||
{
|
||||
bucket.BucketIndex = index;
|
||||
bucket.BucketCount = bucketCount;
|
||||
bucket.StartTime = startTime + duration * index / bucketCount;
|
||||
bucket.EndTime = startTime + duration * (index + 1) / bucketCount;
|
||||
m_TouchedBucketIndices[touchedCount++] = index;
|
||||
}
|
||||
bucket.Add(sample);
|
||||
}
|
||||
|
||||
Array.Sort(m_TouchedBucketIndices, 0, touchedCount);
|
||||
for (int i = 0; i < touchedCount; i++)
|
||||
{
|
||||
int index = m_TouchedBucketIndices[i];
|
||||
m_WorkBuckets[index].FinalizeStatistics();
|
||||
m_CachedBuckets.Add(m_WorkBuckets[index]);
|
||||
m_WorkBuckets[index] = default(XChartTimeBucket);
|
||||
}
|
||||
|
||||
m_CachedStart = startTime;
|
||||
m_CachedEnd = endTime;
|
||||
m_CachedBucketCount = bucketCount;
|
||||
m_CachedVersion = m_Version;
|
||||
}
|
||||
|
||||
private void AddUnchecked(in XChartTimeSample sample)
|
||||
{
|
||||
int writeIndex = (m_Head + m_Count) % Capacity;
|
||||
if (m_Count == Capacity)
|
||||
{
|
||||
m_Ring[m_Head] = sample;
|
||||
m_Head = (m_Head + 1) % Capacity;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Ring[writeIndex] = sample;
|
||||
m_Count++;
|
||||
}
|
||||
}
|
||||
|
||||
private XChartTimeSample GetLogicalSample(int logicalIndex)
|
||||
{
|
||||
return m_Ring[(m_Head + logicalIndex) % Capacity];
|
||||
}
|
||||
|
||||
private static bool IsFinite(double value)
|
||||
{
|
||||
return !double.IsNaN(value) && !double.IsInfinity(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ba8f20974171534ea339419729afcc2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -79,7 +79,7 @@ namespace XericUI.XChart
|
||||
|
||||
protected RectTransform CreateChild(string childName)
|
||||
{
|
||||
var child = new GameObject(childName);
|
||||
var child = new GameObject(childName, typeof(RectTransform));
|
||||
child.transform.SetParent(RootTransform, false);
|
||||
child.hideFlags = CurrentFlags;
|
||||
|
||||
@@ -88,6 +88,16 @@ namespace XericUI.XChart
|
||||
return rt;
|
||||
}
|
||||
|
||||
protected RectTransform CreateStretchChild(string childName)
|
||||
{
|
||||
var child = CreateChild(childName);
|
||||
child.anchorMin = Vector2.zero;
|
||||
child.anchorMax = Vector2.one;
|
||||
child.offsetMin = Vector2.zero;
|
||||
child.offsetMax = Vector2.zero;
|
||||
return child;
|
||||
}
|
||||
|
||||
protected T CreateChildComponent<T>(string childName) where T : Component
|
||||
{
|
||||
var child = CreateChild(childName);
|
||||
@@ -101,4 +111,4 @@ namespace XericUI.XChart
|
||||
if (RootObject != null) RootObject.SetActive(visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,13 @@ namespace XericUI.XChart
|
||||
? HideFlags.HideAndDontSave
|
||||
: HideFlags.DontSaveInEditor;
|
||||
|
||||
var rootObject = new GameObject(toolName);
|
||||
rootObject.transform.SetParent(ToolsRoot, false);
|
||||
var rootObject = new GameObject(toolName, typeof(RectTransform));
|
||||
var rootTransform = rootObject.GetComponent<RectTransform>();
|
||||
rootTransform.SetParent(ToolsRoot, false);
|
||||
rootTransform.anchorMin = Vector2.zero;
|
||||
rootTransform.anchorMax = Vector2.one;
|
||||
rootTransform.offsetMin = Vector2.zero;
|
||||
rootTransform.offsetMax = Vector2.zero;
|
||||
rootObject.hideFlags = flags;
|
||||
|
||||
var tool = new T();
|
||||
@@ -155,4 +160,4 @@ namespace XericUI.XChart
|
||||
m_Tools.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user