549 lines
20 KiB
C#
Raw Permalink 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;
namespace ChartAndGraph
{
class CanvasCandleChart : CandleChart
{
List<CandleChartData.CandleValue> mClipped = new List<CandleChartData.CandleValue>();
List<CandleChartData.CandleValue> mTransformed = new List<CandleChartData.CandleValue>();
List<CandleChartData.CandleValue> mCurrentSeries = new List<CandleChartData.CandleValue>();
int mTmpCurrentIndex = 0;
List<int> mUpToIndex = new List<int>();
List<int> mDownToIndex = new List<int>();
Dictionary<string, CategoryObject> mCategoryObjects = new Dictionary<string, CategoryObject>();
StringBuilder mRealtimeStringBuilder = new StringBuilder();
HashSet<string> mOccupiedCateogies = new HashSet<string>();
List<int> mTmpToRemove = new List<int>();
private bool SupressRealtimeGeneration = false;
[SerializeField]
private bool fitToContainer = false;
public bool FitToContainer
{
get { return fitToContainer; }
set
{
fitToContainer = value;
OnPropertyUpdated();
}
}
[SerializeField]
private ChartMagin fitMargin;
public ChartMagin FitMargin
{
get { return fitMargin; }
set
{
fitMargin = value;
OnPropertyUpdated();
}
}
protected override float TotalDepthLink
{
get
{
return 0f;
}
}
public override bool SupportRealtimeGeneration
{
get
{
return true;
}
}
public override bool IsCanvas
{
get
{
return true;
}
}
class CategoryObject
{
public CanvasChartMesh mItemLabels;
public CanvasCandle mUp;
public CanvasCandle mDown;
public Dictionary<int, string> mCahced = new Dictionary<int, string>();
public HashSet<CanvasCandle> mOccupiedCandles = new HashSet<CanvasCandle>();
private CanvasCandleChart mParent;
/// <summary>
/// The selected index is hovered about the specified point
/// </summary>
public event EventHandlingGraphic.GraphicEvent Hover;
/// <summary>
/// The selected index is clicked about the specified point
/// </summary>
public event EventHandlingGraphic.GraphicEvent Click;
/// <summary>
/// The currently hovered and selected objects are no longer selected or hovered.
/// </summary>
public event Action Leave;
public CategoryObject(CanvasCandleChart parent)
{
mParent = parent;
}
public void HookEvents()
{
HookEvents(mUp, true);
HookEvents(mDown, false);
}
void HookEvents(CanvasCandle candle, bool isUp)
{
if (candle == null)
return;
candle.Leave += () => mLeave(candle);
candle.Hover += (index, type, data, position) => { mHover(candle, mParent.MapIndex(index, isUp), type, data, position); };
candle.Click += (index, type, data, position) => { mClick(candle, mParent.MapIndex(index, isUp), type, data, position); };
}
private void mClick(CanvasCandle candle, int index, int type, object data, Vector2 position)
{
mOccupiedCandles.Add(candle);
if (Click != null)
Click(index, type, data, position);
}
private void mHover(CanvasCandle candle, int index, int type, object data, Vector2 position)
{
mOccupiedCandles.Add(candle);
if (Hover != null)
Hover(index, type, data, position);
}
private void mLeave(CanvasCandle candle)
{
if (mOccupiedCandles.Remove(candle))
{
if (mOccupiedCandles.Count == 0)
{
if (Leave != null)
Leave();
}
}
}
}
protected override bool ShouldFitCanvas { get { return true; } }
protected override FitType FitAspectCanvas
{
get
{
return FitType.Aspect;
}
}
private CanvasCandle CreateDataObject(CandleChartData.CategoryData data, GameObject rectMask)
{
GameObject obj = new GameObject("Candles", typeof(RectTransform));
ChartCommon.HideObject(obj, hideHierarchy);
obj.AddComponent<ChartItem>();
RectTransform t = obj.GetComponent<RectTransform>();
obj.AddComponent<CanvasRenderer>();
CanvasCandle candles = obj.AddComponent<CanvasCandle>();
t.SetParent(rectMask.transform, false);
t.localScale = new Vector3(1f, 1f, 1f);
t.anchorMin = new Vector2(0f, 0f);
t.anchorMax = new Vector2(1f, 1f);
t.sizeDelta = new Vector2(0f, 0f);
t.anchorMin = new Vector2(0f, 0f);
t.anchorMax = new Vector2(0f, 0f);
t.anchoredPosition = Vector3.zero;
return candles;
}
protected override double GetCategoryDepth(string category)
{
return 0.0;
}
public override void GenerateRealtime()
{
if (SupressRealtimeGeneration)
return;
base.GenerateRealtime();
double minX = ((IInternalCandleData)Data).GetMinValue(0, false);
double minY = ((IInternalCandleData)Data).GetMinValue(1, false);
double maxX = ((IInternalCandleData)Data).GetMaxValue(0, false);
double maxY = ((IInternalCandleData)Data).GetMaxValue(1, false);
double xScroll = GetScrollOffset(0);
double yScroll = GetScrollOffset(1);
double xSize = maxX - minX;
double ySize = maxY - minY;
double xOut = minX + xScroll + xSize;
double yOut = minY + yScroll + ySize;
DoubleVector3 min = new DoubleVector3(xScroll + minX, yScroll + minY);
DoubleVector3 max = new DoubleVector3(xOut, yOut);
Rect viewRect = new Rect(0f, 0f, widthRatio, heightRatio);
ClearBillboardCategories();
bool edit = false;
foreach (CandleChartData.CategoryData data in ((IInternalCandleData)Data).Categories)
{
CategoryObject obj = null;
if (mCategoryObjects.TryGetValue(data.Name, out obj) == false)
continue;
mClipped.Clear();
mTransformed.Clear();
int refrenceIndex = ClipCandles(data.Data, mClipped);
TransformCandles(mClipped, mTransformed, viewRect, min, max);
if (data.Data.Count == 0 && ChartCommon.IsInEditMode)
{
edit = true;
}
mTmpToRemove.Clear();
int range = refrenceIndex + mClipped.Count;
foreach (int key in obj.mCahced.Keys)
{
if (key < refrenceIndex || key > range)
mTmpToRemove.Add(key);
}
for (int i = 0; i < mTmpToRemove.Count; i++)
obj.mCahced.Remove(mTmpToRemove[i]);
obj.mCahced.Remove(data.Data.Count - 1); // never store the last point cache , it might be intepolating by the realtime feature
if (data.Data.Count == 0)
continue;
GenerateItemLabels(true, obj, data, viewRect, refrenceIndex, edit);
if (obj.mUp != null)
{
FillCurrentSeries(true, refrenceIndex);
obj.mUp.Generate(this, viewRect, mCurrentSeries, data.UpCandle);
}
if (obj.mDown != null)
{
FillCurrentSeries(false, refrenceIndex);
obj.mDown.Generate(this, viewRect, mCurrentSeries, data.DownCandle);
}
}
}
protected override void ClearChart()
{
base.ClearChart();
ClearBillboard();
mActiveTexts.Clear();
mCategoryObjects.Clear();
}
void GenerateItemLabels(bool realTime, CategoryObject categoryObj, CandleChartData.CategoryData data, Rect viewRect, int refrenceIndex, bool edit)
{
if (mItemLabels != null && mItemLabels.isActiveAndEnabled)
{
CanvasChartMesh m = null;
if (realTime)
{
m = categoryObj.mItemLabels;
if (m == null)
return;
m.Clear();
}
else
{
m = new CanvasChartMesh(true);
m.RecycleText = true;
categoryObj.mItemLabels = m;
}
Rect textRect = viewRect;
textRect.xMin -= 1f;
textRect.yMin -= 1f;
textRect.xMax += 1f;
textRect.yMax += 1f;
for (int i = 0; i < mTransformed.Count; i++)
{
Vector2 pointValue = mTransformed[i].MidPoint.ToVector2();
if (textRect.Contains(pointValue) == false)
continue;
CandleChartData.CandleValue candleVal = mTransformed[i];
int candleIndex = i + refrenceIndex;
if (edit == false)
candleVal = data.Data[i + refrenceIndex];
Vector3 labelPos = ((Vector3)pointValue) + new Vector3(mItemLabels.Location.Breadth, mItemLabels.Seperation, mItemLabels.Location.Depth);
if (mItemLabels.Alignment == ChartLabelAlignment.Base)
labelPos.y -= (float)mTransformed[i].MidPoint.y;
string toSet;
if (realTime == false || categoryObj.mCahced.TryGetValue(candleIndex, out toSet) == false)
{
string Open = StringFromAxisFormat(new DoubleVector3(candleVal.Start, candleVal.Open, 0.0), mVerticalAxis, false);
string Close = StringFromAxisFormat(new DoubleVector3(candleVal.Start, candleVal.Close, 0.0), mVerticalAxis, false);
string High = StringFromAxisFormat(new DoubleVector3(candleVal.Start, candleVal.High, 0.0), mVerticalAxis, false);
string Low = StringFromAxisFormat(new DoubleVector3(candleVal.Start, candleVal.Low, 0.0), mVerticalAxis, false);
string Start = StringFromAxisFormat(new DoubleVector3(candleVal.Start, candleVal.Open, 0.0), mHorizontalAxis, true);
string Duration = StringFromAxisFormat(new DoubleVector3(candleVal.Duration, candleVal.Open, 0.0), mHorizontalAxis, true);
FormatItem(mRealtimeStringBuilder, Open, Close, High, Low, Start, Duration);
toSet = mItemLabels.TextFormat.Format(mRealtimeStringBuilder.ToString(), data.Name, "");
categoryObj.mCahced[candleIndex] = toSet;
}
labelPos -= new Vector3(CanvasFitOffset.x * TotalWidth, CanvasFitOffset.y * TotalHeight, 0f);
BillboardText billboard = m.AddText(this, mItemLabels.TextPrefab, transform, mItemLabels.FontSize, mItemLabels.FontSharpness, toSet, labelPos.x, labelPos.y, labelPos.z, 0f, null);
TextController.AddText(billboard);
AddBillboardText(data.Name, i + refrenceIndex, billboard);
}
if (realTime)
{
m.DestoryRecycled();
if (m.TextObjects != null)
{
foreach (BillboardText text in m.TextObjects)
{
((IInternalUse)this).InternalTextController.AddText(text);
}
}
}
}
}
bool UpPredicate(CandleChartData.CandleValue x)
{
if (x.isUp)
mUpToIndex.Add(mTmpCurrentIndex);
mTmpCurrentIndex++;
return x.isUp;
}
bool DownPredicate(CandleChartData.CandleValue x)
{
if (x.isUp == false)
mDownToIndex.Add(mTmpCurrentIndex);
mTmpCurrentIndex++;
return !x.isUp;
}
void FillCurrentSeries(bool isUp, int refrenceIndex)
{
mTmpCurrentIndex = refrenceIndex;
mCurrentSeries.Clear();
if (isUp)
{
mUpToIndex.Clear();
mCurrentSeries.AddRange(mTransformed.Where(UpPredicate));
}
else
{
mDownToIndex.Clear();
mCurrentSeries.AddRange(mTransformed.Where(DownPredicate));
}
}
int MapIndex(int index, bool isUp)
{
if (isUp)
{
if (mUpToIndex.Count <= index)
return index;
return mUpToIndex[index];
}
if (mDownToIndex.Count <= index)
return index;
return mDownToIndex[index];
}
public override void InternalGenerateChart()
{
if (gameObject.activeInHierarchy == false)
return;
base.InternalGenerateChart();
if (FitToContainer)
{
RectTransform trans = GetComponent<RectTransform>();
widthRatio = trans.rect.width;
heightRatio = trans.rect.height;
}
ClearChart();
if (Data == null)
return;
GenerateAxis(true);
double minX = ((IInternalCandleData)Data).GetMinValue(0, false);
double minY = ((IInternalCandleData)Data).GetMinValue(1, false);
double maxX = ((IInternalCandleData)Data).GetMaxValue(0, false);
double maxY = ((IInternalCandleData)Data).GetMaxValue(1, false);
double xScroll = GetScrollOffset(0);
double yScroll = GetScrollOffset(1);
double xSize = maxX - minX;
double ySize = maxY - minY;
double xOut = minX + xScroll + xSize;
double yOut = minY + yScroll + ySize;
DoubleVector3 min = new DoubleVector3(xScroll + minX, yScroll + minY);
DoubleVector3 max = new DoubleVector3(xOut, yOut);
Rect viewRect = new Rect(0f, 0f, widthRatio, heightRatio);
int total = ((IInternalCandleData)Data).TotalCategories + 1;
bool edit = false;
ClearBillboard();
mActiveTexts.Clear();
int index = 0;
GameObject mask = CreateRectMask(viewRect);
foreach (CandleChartData.CategoryData data in ((IInternalCandleData)Data).Categories)
{
mClipped.Clear();
mTransformed.Clear();
int refrenceIndex = ClipCandles(data.Data, mClipped);
TransformCandles(mClipped, mTransformed, viewRect, min, max);
if (data.Data.Count == 0 && ChartCommon.IsInEditMode)
{
int tmpIndex = total - 1 - index;
float low = (((float)tmpIndex) / (float)total);
float high = (((float)tmpIndex + 1) / (float)total);
float y1 = Mathf.Lerp(high, low, 0.6f);
float y2 = Mathf.Lerp(high, low, 0.3f);
mTransformed.Clear();
mTransformed.Add(InterpolateCandleInRect(new CandleChartData.CandleValue(y1, high, low, y2, 0.1, 0.1), viewRect));
mTransformed.Add(InterpolateCandleInRect(new CandleChartData.CandleValue(y1, low, high, y2, 0.5, 0.2), viewRect));
edit = true;
index++;
}
CategoryObject categoryObj = new CategoryObject(this);
CanvasCandle up = CreateDataObject(data, mask);
CanvasCandle down = CreateDataObject(data, mask);
FillCurrentSeries(true, refrenceIndex);
up.Generate(this, viewRect, mCurrentSeries, data.UpCandle);
FillCurrentSeries(false, refrenceIndex);
down.Generate(this, viewRect, mCurrentSeries, data.DownCandle);
string catName = data.Name;
categoryObj.mUp = up;
categoryObj.mDown = down;
categoryObj.HookEvents();
GenerateItemLabels(false, categoryObj, data, viewRect, refrenceIndex, edit);
categoryObj.Hover += (idx, t, d, pos) => { Category_Hover(catName, idx, t, d, pos); };
categoryObj.Click += (idx, t, d, pos) => { Category_Click(catName, idx, t, d, pos); };
categoryObj.Leave += () => { Category_Leave(catName); };
mCategoryObjects[catName] = categoryObj;
}
}
void Category_Hover(string category, int index, int type, object data, Vector2 position)
{
CandleChartData.CandleValue candle = Data.GetCandle(category, index);
Dictionary<int, BillboardText> catgoryTexts;
BillboardText b;
if (mTexts.TryGetValue(category, out catgoryTexts))
{
if (catgoryTexts.TryGetValue(index, out b))
SelectActiveText(b);
}
Rect selection = new Rect();
if (data != null && data is Rect)
selection = (Rect)data;
OnItemHoverted(new CandleEventArgs(index, type, selection, position, candle, category));
}
/// <summary>
/// handles click of the category
/// </summary>
/// <param name="category"></param>
/// <param name="index"></param>
/// <param name="type"></param>
/// <param name="data"></param>
/// <param name="position"></param>
void Category_Click(string category, int index, int type, object data, Vector2 position)
{
CandleChartData.CandleValue candle = Data.GetCandle(category, index);
Dictionary<int, BillboardText> catgoryTexts;
BillboardText b;
if (mTexts.TryGetValue(category, out catgoryTexts))
{
if (catgoryTexts.TryGetValue(index, out b))
SelectActiveText(b);
}
Rect selection = new Rect();
if (data != null && data is Rect)
selection = (Rect)data;
OnItemSelected(new CandleEventArgs(index, type, selection, position, candle, category));
}
void Category_Leave(string cat)
{
TriggerActiveTextsOut();
OnItemLeave(new CandleEventArgs(0, 0, new Rect(), new Vector3(), new CandleChartData.CandleValue(), cat),"none");
}
protected override void OnItemHoverted(object userData)
{
base.OnItemHoverted(userData);
var args = userData as CandleEventArgs;
mOccupiedCateogies.Add(args.Category);
}
protected override void OnItemSelected(object userData)
{
base.OnItemSelected(userData);
var args = userData as CandleEventArgs;
mOccupiedCateogies.Add(args.Category);
}
protected override void OnItemLeave(object userData,string type)
{
//base.OnItemLeave(userData);
CandleEventArgs args = userData as CandleEventArgs;
if (args == null)
return;
string category = args.Category;
mOccupiedCateogies.Remove(category);
mOccupiedCateogies.RemoveWhere(x => !Data.HasCategory(x));
if (mOccupiedCateogies.Count == 0)
{
if (NonHovered != null)
NonHovered.Invoke();
}
}
internal override void SetAsMixedSeries()
{
throw new NotImplementedException();
}
}
}