#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 mClipped = new List(); List mTransformed = new List(); List mCurrentSeries = new List(); int mTmpCurrentIndex = 0; List mUpToIndex = new List(); List mDownToIndex = new List(); Dictionary mCategoryObjects = new Dictionary(); StringBuilder mRealtimeStringBuilder = new StringBuilder(); HashSet mOccupiedCateogies = new HashSet(); List mTmpToRemove = new List(); 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 mCahced = new Dictionary(); public HashSet mOccupiedCandles = new HashSet(); private CanvasCandleChart mParent; /// /// The selected index is hovered about the specified point /// public event EventHandlingGraphic.GraphicEvent Hover; /// /// The selected index is clicked about the specified point /// public event EventHandlingGraphic.GraphicEvent Click; /// /// The currently hovered and selected objects are no longer selected or hovered. /// 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(); RectTransform t = obj.GetComponent(); obj.AddComponent(); CanvasCandle candles = obj.AddComponent(); 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(); 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 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)); } /// /// handles click of the category /// /// /// /// /// /// void Category_Click(string category, int index, int type, object data, Vector2 position) { CandleChartData.CandleValue candle = Data.GetCandle(category, index); Dictionary 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(); } } }