527 lines
18 KiB
C#
Raw Normal View History

2025-09-08 14:51:28 +08:00
#define Graph_And_Chart_PRO
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
namespace ChartAndGraph
{
/// <summary>
/// the graph chart class.
/// </summary>
[ExecuteInEditMode]
public abstract class GraphChartBase : ScrollableAxisChart
{
[SerializeField]
[Tooltip("The height ratio of the chart")]
protected float heightRatio = 300;
[SerializeField]
[Tooltip("The width ratio of the chart")]
protected float widthRatio = 600;
/// <summary>
/// event arguments for a bar chart event
/// </summary>
public class GraphEventArgs
{
public GraphEventArgs(int index,Vector3 position, DoubleVector2 value,float magnitude, string category,string xString,string yString)
{
Position = position;
Value = value;
Category = category;
XString = xString;
YString = yString;
Index = index;
Magnitude = magnitude;
}
public float Magnitude { get; private set; }
public int Index { get; private set; }
public string XString { get; private set; }
public string YString { get; private set; }
public Vector3 Position { get; private set; }
public DoubleVector2 Value { get; private set; }
public string Category { get; private set; }
public string Group { get; private set; }
}
/// <summary>
/// a graph chart event
/// </summary>
[Serializable]
public class GraphEvent : UnityEvent<GraphEventArgs>
{
}
/// <summary>
/// occures when a point is clicked
/// </summary>
public GraphEvent PointClicked = new GraphEvent();
/// <summary>
/// occurs when a point is hovered
/// </summary>
public GraphEvent PointHovered = new GraphEvent();
/// <summary>
/// occurs when no point is hovered any longer
/// </summary>
public UnityEvent NonHovered = new UnityEvent();
/// <summary>
/// The height ratio of the chart
/// </summary>
[SerializeField]
public float HeightRatio
{
get { return heightRatio; }
set
{
heightRatio = value;
Invalidate();
}
}
/// <summary>
/// The width ratio of the chart
/// </summary>
public float WidthRatio
{
get { return widthRatio; }
set
{
widthRatio = value;
Invalidate();
}
}
[SerializeField]
private string itemFormat = "<?x>:<?y>";
public string ItemFormat
{
get { return itemFormat; }
set
{
itemFormat = value;
Invalidate();
}
}
protected bool mRealtimeUpdateIndex = false;
protected Dictionary<string, int> mMinimumUpdateIndex = new Dictionary<string, int>();
/// <summary>
/// the graph data
/// </summary>
[HideInInspector]
[SerializeField]
protected GraphData Data = new GraphData();
/// <summary>
/// Holds the graph chart data. including values and categories
/// </summary>
public GraphData DataSource { get { return Data; } }
protected override LegenedData LegendInfo
{
get
{
LegenedData data = new LegenedData();
if (Data == null)
return data;
foreach(var cat in ((IInternalGraphData)Data).Categories)
{
LegenedData.LegenedItem item = new LegenedData.LegenedItem();
item.Name = cat.Name;
if (cat.FillMaterial != null)
item.Material = cat.FillMaterial;
else if (cat.LineMaterial != null)
item.Material = cat.LineMaterial;
else
item.Material = cat.PointMaterial;
data.AddLegenedItem(item);
}
return data;
}
}
protected override IChartData DataLink
{
get
{
return Data;
}
}
public abstract void ClearCache();
private void HookEvents()
{
((IInternalGraphData)Data).InternalDataChanged -= GraphChart_InternalDataChanged;
((IInternalGraphData)Data).InternalRealTimeDataChanged -= GraphChartBase_InternalRealTimeDataChanged; ;
((IInternalGraphData)Data).InternalViewPortionChanged -= GraphChartBase_InternalViewPortionChanged;
((IInternalGraphData)Data).InternalViewPortionChanged += GraphChartBase_InternalViewPortionChanged;
((IInternalGraphData)Data).InternalDataChanged += GraphChart_InternalDataChanged;
((IInternalGraphData)Data).InternalRealTimeDataChanged += GraphChartBase_InternalRealTimeDataChanged; ;
}
private void GraphChartBase_InternalViewPortionChanged(object sender, EventArgs e)
{
ViewPortionChanged();
}
protected abstract void ViewPortionChanged();
private void GraphChartBase_InternalRealTimeDataChanged(int index,string category)
{
if (category == null) // full invalidtion
{
mRealtimeUpdateIndex = false;
}
if (mRealtimeUpdateIndex)
{
mRealtimeUpdateIndex = true;
int minIndex;
if (mMinimumUpdateIndex.TryGetValue(category, out minIndex) == false)
minIndex = int.MaxValue;
if (minIndex > index)
minIndex = index;
mMinimumUpdateIndex[category] = minIndex;
}
InvalidateRealtime();
}
protected void ClearRealtimeIndexdata()
{
mMinimumUpdateIndex.Clear();
mRealtimeUpdateIndex = true;
}
public override void Invalidate()
{
base.Invalidate();
mRealtimeUpdateIndex = false; // trigger a full invalidation
mMinimumUpdateIndex.Clear();
}
private void GraphChart_InternalDataChanged(object sender, EventArgs e)
{
Invalidate();
}
protected override void Start()
{
base.Start();
if (ChartCommon.IsInEditMode == false)
{
HookEvents();
}
Invalidate();
}
protected override void OnValidate()
{
base.OnValidate();
if (ChartCommon.IsInEditMode == false)
{
HookEvents();
}
Data.RestoreDataValues();
Invalidate();
}
protected override void OnLabelSettingChanged()
{
base.OnLabelSettingChanged();
Invalidate();
}
protected override void OnAxisValuesChanged()
{
base.OnAxisValuesChanged();
Invalidate();
}
protected override void OnLabelSettingsSet()
{
base.OnLabelSettingsSet();
Invalidate();
}
protected DoubleVector4 TransformPoint(Rect viewRect,Vector3 point ,DoubleVector2 min, DoubleVector2 range)
{
return ChartCommon.interpolateInRect(viewRect, new DoubleVector3((point.x - min.x) / range.x, (point.y - min.y) / range.y));
}
protected override void Update()
{
base.Update();
}
private void UpdateMinMax(DoubleVector3 point,ref double minX,ref double minY,ref double maxX,ref double maxY)
{
minX = Math.Min(minX, point.x);
maxX = Math.Max(maxX, point.x);
minY = Math.Min(minY, point.y);
maxY = Math.Max(maxY, point.y);
}
protected override float GetScrollingRange(int axis)
{
float min = (float)((IInternalGraphData)Data).GetMinValue(axis, false);
float max = (float)((IInternalGraphData)Data).GetMaxValue(axis, false);
return max - min;
}
private Rect CreateUvRect(Rect completeRect,Rect lineRect)
{
if (completeRect.width < 0.0001f || completeRect.height < 0.0001f)
return new Rect();
if(float.IsInfinity(lineRect.xMax) || float.IsInfinity(lineRect.xMin) || float.IsInfinity(lineRect.yMin) || float.IsInfinity(lineRect.yMax))
return new Rect();
float x = (lineRect.xMin - completeRect.xMin) / completeRect.width;
float y = (lineRect.yMin - completeRect.yMin) / completeRect.height;
float w = lineRect.width / completeRect.width;
float h = lineRect.height / completeRect.height;
return new Rect(x, y, w, h);
}
protected int ClipPoints(IList<DoubleVector3> points,List<DoubleVector4> res,out Rect uv)
{
double minX, minY, maxX, maxY, xScroll, yScroll, xSize, ySize, xOut;
GetScrollParams(out minX, out minY, out maxX, out maxY, out xScroll, out yScroll, out xSize, out ySize, out xOut);
double direction = 1.0;
if (minX > maxX)
direction = -1.0;
bool prevOut = false;
bool prevIn = false;
double maxXLocal = double.MinValue, minXLocal = double.MaxValue, maxYLocal = double.MinValue, minYLocal = double.MaxValue;
minX = double.MaxValue;
minY = double.MaxValue;
maxX = double.MinValue;
maxY = double.MinValue;
int refrenceIndex = 0;
for(int i=0; i<points.Count; i++)
{
bool pOut = prevOut;
bool pIn = prevIn;
prevOut = false;
prevIn = false;
DoubleVector3 point = points[i];
UpdateMinMax(points[i], ref minX, ref minY, ref maxX, ref maxY);
if (point.x* direction < xScroll* direction || point.x* direction > xOut* direction) // || point.y < yScroll || point.y > yOut)
{
prevOut = true;
if (pIn)
{
res.Add(point.ToDoubleVector4());
// uv = CreateUvRect(new Rect(minX, minY, maxX - minX, maxY - minY), new Rect(minXLocal, minYLocal, maxXLocal - minXLocal, maxYLocal - minYLocal));
// return refrenceIndex;
}
if(pOut)
{
if(point.x* direction > xOut* direction && points[i-1].x* direction < xScroll* direction)
{
UpdateMinMax(points[i - 1], ref minXLocal, ref minYLocal, ref maxXLocal, ref maxYLocal);
UpdateMinMax(point, ref minXLocal, ref minYLocal, ref maxXLocal, ref maxYLocal);
res.Add(points[i - 1].ToDoubleVector4());
res.Add(point.ToDoubleVector4());
}
}
}
else
{
prevIn = true;
if (pOut)
{
refrenceIndex = i - 1;
UpdateMinMax(points[i - 1], ref minXLocal, ref minYLocal, ref maxXLocal, ref maxYLocal);
res.Add(points[i - 1].ToDoubleVector4());
}
UpdateMinMax(point, ref minXLocal, ref minYLocal, ref maxXLocal, ref maxYLocal);
res.Add(point.ToDoubleVector4());
}
}
for(int i=0; i<res.Count; i++)
{
DoubleVector4 p = res[i];
p.w = p.z;
p.z = 0f;
res[i] = p;
}
uv = CreateUvRect(new Rect((float)minX, (float)minY, (float)(maxX - minX), (float)(maxY - minY)), new Rect((float)minXLocal, (float)yScroll, (float)(maxXLocal - minXLocal), (float)(ySize)));
return refrenceIndex;
}
protected void TransformPoints(IList<DoubleVector3> points, Rect viewRect, DoubleVector3 min, DoubleVector3 max)
{
DoubleVector3 range = max - min;
if (Math.Abs(range.x) <= 0.0001f || Math.Abs(range.y) < 0.0001f)
return;
double radiusMultiplier = Math.Min(viewRect.width / Math.Abs(range.x), viewRect.height / Math.Abs(range.y));
for (int i = 0; i < points.Count; i++)
{
DoubleVector3 point = points[i];
DoubleVector4 res = ChartCommon.interpolateInRect(viewRect, new DoubleVector3((point.x - min.x) / range.x, (point.y - min.y) / range.y));
res.z = point.z * radiusMultiplier;
points[i] = res.ToDoubleVector3();
}
}
protected bool TransformPoints(IList<DoubleVector4> points,List<Vector4> output,Rect viewRect, DoubleVector3 min, DoubleVector3 max)
{
output.Clear();
DoubleVector3 range = max - min;
if (Math.Abs(range.x) <= 0.0001f || Math.Abs(range.y) < 0.0001f)
return false;
double radiusMultiplier = Math.Min(viewRect.width / Math.Abs(range.x), viewRect.height / Math.Abs(range.y));
bool HasSize = false;
for(int i=0; i<points.Count; i++)
{
DoubleVector4 point = points[i];
DoubleVector4 res = ChartCommon.interpolateInRect(viewRect,new DoubleVector3((point.x - min.x) / range.x, (point.y - min.y) / range.y));
res.z = 0.0;
res.w = point.w* radiusMultiplier;
if (point.w > 0f)
HasSize = true;
output.Add(res.ToVector4());
}
return HasSize;
}
protected override void ValidateProperties()
{
base.ValidateProperties();
if (heightRatio < 0f)
heightRatio = 0f;
if (widthRatio < 0f)
widthRatio = 0f;
}
protected override bool SupportsCategoryLabels
{
get
{
return false;
}
}
protected override bool SupportsGroupLables
{
get
{
return false;
}
}
protected override bool SupportsItemLabels
{
get
{
return true;
}
}
protected override float TotalHeightLink
{
get
{
return heightRatio;
}
}
protected override float TotalWidthLink
{
get
{
return widthRatio;
}
}
protected override float TotalDepthLink
{
get
{
return 0.0f;
}
}
protected override bool HasValues(AxisBase axis)
{
return true; //all axis have values in the graph chart
}
protected override double MaxValue(AxisBase axis)
{
if (axis == null)
return 0.0;
if (axis == mHorizontalAxis)
return ((IInternalGraphData)Data).GetMaxValue(0, false);
if (axis == mVerticalAxis)
return ((IInternalGraphData)Data).GetMaxValue(1, false);
return 0.0;
}
protected override double MinValue(AxisBase axis)
{
if (axis == null)
return 0.0;
if (axis == mHorizontalAxis)
return ((IInternalGraphData)Data).GetMinValue(0, false);
if (axis == mVerticalAxis)
return ((IInternalGraphData)Data).GetMinValue(1, false);
return 0.0;
}
protected override void OnItemHoverted(object userData)
{
base.OnItemHoverted(userData);
GraphEventArgs args = userData as GraphEventArgs;
if (PointHovered != null)
PointHovered.Invoke(args);
}
StringBuilder mTmpBuilder = new StringBuilder();
public string FormatItem(double x, double y)
{
DoubleVector3 p = new DoubleVector3(x, y);
string xFormat = StringFromAxisFormat(p, mHorizontalAxis, true);
string yFormat = StringFromAxisFormat(p, mVerticalAxis, false);
FormatItem(mTmpBuilder, xFormat, yFormat);
return mTmpBuilder.ToString();
}
public string FormatItem(string x, string y)
{
FormatItem(mTmpBuilder, x, y);
return mTmpBuilder.ToString();
}
protected void FormatItem(StringBuilder builder, string x, string y)
{
builder.Length = 0;
builder.Append(itemFormat);
builder.Replace("<?x>", x).Replace("<?y>", y).Replace("\\n", Environment.NewLine);
}
protected override void OnItemSelected(object userData)
{
base.OnItemSelected(userData);
GraphEventArgs args = userData as GraphEventArgs;
if (PointClicked != null)
PointClicked.Invoke(args);
}
}
}