#define Graph_And_Chart_PRO using ChartAndGraph; using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; namespace ChartAndGraph { [Serializable] public class CandleChartData : ScrollableChartData, IInternalCandleData { [Serializable] public struct CandleValue { public CandleValue(double open, double high, double low, double close, DateTime start, TimeSpan duration) { Open = open; High = high; Low = low; Close = close; Start = ChartDateUtility.DateToValue(start); Duration = ChartDateUtility.TimeSpanToValue(duration); } public CandleValue(double open, double high, double low, double close, double start, double duration) { Open = open; High = high; Low = low; Close = close; Start = start; Duration = duration; } public double Open; public double High; public double Low; public double Close; public double Start; public double Duration; public bool isUp { get { return Close > Open; } } public double End { get { return Start + Duration; } } public DoubleVector2 MidPoint { get { return new DoubleVector2(Start + (Duration * 0.5), (Open + Close) * 0.5); } } public double Max { get { return Math.Max(Open, Close); } } public double LowBound { get { return Math.Min(Math.Min(High, Low), Math.Min(Open, Close)); } } public double HighBound { get { return Math.Max(Math.Max(High, Low), Math.Max(Open, Close)); } } public double Min { get { return Math.Min(Open, Close); } } } [Serializable] public class CandleSettings { public double LineThickness = 2.0; public double CandleThicknessMultiplier = 1.0; public double OutlineThickness = 0.0; public ChartItemEffect CandleHoverPrefab; public Material Outline; public Material Line; public Material Fill; public GameObject CandlePrefab; } [Serializable] public class CategoryData : BaseScrollableCategoryData { public List Data = new List(); public CandleSettings UpCandle = new CandleSettings(); public CandleSettings DownCandle = new CandleSettings(); public double Depth = 0f; } class CandleComparer : IComparer { public int Compare(CandleValue x, CandleValue y) { if (x.Open < y.Open) return -1; if (x.Open > y.Open) return 1; return 0; } } [Serializable] class SerializedCategory { public string Name; [HideInInspector] public CandleValue[] Data; [HideInInspector] public double? MaxX, MaxY, MinX, MinY; public CandleSettings UpCandle = new CandleSettings(); public CandleSettings DownCandle = new CandleSettings(); public double Depth = 0f; } CandleComparer mComparer = new CandleComparer(); [SerializeField] SerializedCategory[] mSerializedData = new SerializedCategory[0]; event EventHandler IInternalCandleData.InternalViewPortionChanged { add { ViewPortionChanged += value; } remove { ViewPortionChanged -= value; } } event EventHandler IInternalCandleData.InternalDataChanged { add { DataChanged += value; } remove { DataChanged -= value; } } /// /// rename a category. throws and exception on error /// /// /// public void RenameCategory(string prevName, string newName) { if (prevName == newName) return; if (mData.ContainsKey(newName)) throw new ArgumentException(String.Format("A category named {0} already exists", newName)); CategoryData cat = (CategoryData)mData[prevName]; mData.Remove(prevName); cat.Name = newName; mData.Add(newName, cat); RaiseDataChanged(); } /// /// Adds a new category to the candle chart. /// /// /// /// public void AddCategory(string category, CandleSettings up, CandleSettings down, double depth) { AddCategory3DCandle(category, up, down, 0f); } /// /// add category to the candle chart /// /// /// /// /// public void AddCategory3DCandle(string category, CandleSettings up, CandleSettings down, double depth) { if (mData.ContainsKey(category)) throw new ArgumentException(String.Format("A category named {0} already exists", category)); CategoryData data = new CategoryData(); mData.Add(category, data); data.Name = category; data.DownCandle = down; data.UpCandle = up; data.Depth = depth; RaiseDataChanged(); } /// /// removed a category from the DataSource. returnes true on success , or false if the category does not exist /// /// /// public bool RemoveCategory(string category) { return mData.Remove(category); } public void SetDownCandle(string category, CandleSettings down) { if (mData.ContainsKey(category) == false) { Debug.LogWarning("Invalid category name. Make sure the category is present in the chart"); return; } CategoryData data = (CategoryData)mData[category]; data.DownCandle = down; RaiseDataChanged(); } public void SetUpCandle(string category, CandleSettings up) { if (mData.ContainsKey(category) == false) { Debug.LogWarning("Invalid category name. Make sure the category is present in the chart"); return; } CategoryData data = (CategoryData)mData[category]; data.UpCandle = up; RaiseDataChanged(); } /// /// sets the depth for a 3d graph category /// /// /// public void Set3DCategoryDepth(string category, double depth) { if (mData.ContainsKey(category) == false) { Debug.LogWarning("Invalid category name. Make sure the category is present in the chart"); return; } if (depth < 0) depth = 0f; CategoryData data = (CategoryData)mData[category]; data.Depth = depth; RaiseDataChanged(); } /// /// clears all the data for the selected category /// /// public void ClearCategory(string category) { if (mData.ContainsKey(category) == false) { Debug.LogWarning("Invalid category name. Make sure the category is present in the chart"); return; } mData[category].MaxX = null; mData[category].MaxY = null; mData[category].MinX = null; mData[category].MinY = null; ((CategoryData)mData[category]).Data.Clear(); RaiseDataChanged(); } /// /// gets the candle at the specified index for a given category /// /// /// /// public int GetCandleCount(string category) { if (mData.ContainsKey(category) == false) { Debug.LogWarning("Invalid category name. Make sure the category is present in the chart"); return 0; } CategoryData data = (CategoryData)mData[category]; return data.Data.Count; } /// /// gets the candle at the specified index for a given category /// /// /// /// public CandleValue GetCandle(string category, int index) { if (mData.ContainsKey(category) == false) { Debug.LogWarning("Invalid category name. Make sure the category is present in the chart"); return new CandleValue(); } CategoryData data = (CategoryData)mData[category]; return data.Data[index]; } /// /// returns the total amount of candles in the given category /// /// /// public int GetTotalCandlesInCategory(string category) { if (mData.ContainsKey(category) == false) { Debug.LogWarning("Invalid category name. Make sure the category is present in the chart"); return 0; } CategoryData data = (CategoryData)mData[category]; return data.Data.Count; } /// /// use this to modify candles in realtime. /// /// /// public void ModifyCandleInCategory(string category, int candleIndex, double open, double high, double low, double close) { if (mData.ContainsKey(category) == false) { Debug.LogWarning("Invalid category name. Make sure the category is present in the chart"); return; } CategoryData data = (CategoryData)mData[category]; List candles = data.Data; if (candleIndex == -1) candleIndex = data.Data.Count - 1; if (candleIndex < 0 || candleIndex >= candles.Count) { Debug.LogWarning("Candle index is out of range, call is ignored"); return; } double candleMax = Math.Max(Math.Max(open, close), Math.Max(high, low)); double candleMin = Math.Min(Math.Min(open, close), Math.Min(high, low)); if (data.MaxY.HasValue == false || data.MaxY.Value < candleMax) data.MaxY = candleMax; if (data.MinY.HasValue == false || data.MinY.Value > candleMin) data.MinY = candleMin; CandleValue candle = data.Data[candleIndex]; candle.Open = open; candle.Close = close; candle.High = high; candle.Low = low; data.Data[candleIndex] = candle; RaiseRealtimeDataChanged(candleIndex, category); } /// /// removes a candle from a category /// /// /// public void RemoveCandleInCategory(string category, int candleIndex) { if (mData.ContainsKey(category) == false) { Debug.LogWarning("Invalid category name. Make sure the category is present in the chart"); return; } CategoryData data = (CategoryData)mData[category]; List candles = data.Data; if (candleIndex < 0 || candleIndex >= candles.Count) { Debug.LogWarning("Candle index is out of range, call is ignored"); return; } data.Data.RemoveAt(candleIndex); RaiseRealtimeDataChanged(candleIndex, category); } /// /// use this to modify candles in realtime. this overload modifies the last candle and can be used for realtime candle data stream /// /// /// public void ModifyLastCandleInCategory(string category, double open, double high, double low, double close) { ModifyCandleInCategory(category, -1, open, high, low, close); } class Slider : BaseSlider { public string category; public double from; public int index; public double to; public double current; public double y; private CandleChartData mParent; public Slider(CandleChartData parent) { mParent = parent; } public override DoubleVector2 Max { get { return new DoubleVector2(current, y); } } public override DoubleVector2 Min { get { return new DoubleVector2(current, y); } } public override string Category { get { return category; } } public override int MinIndex { get { return index; } } public override bool Update() { BaseScrollableCategoryData baseData; if (mParent.mData.TryGetValue(category, out baseData) == false) return true; double time = Time.time; time -= StartTime; if (Duration <= 0.0001f) time = 1f; else { time /= Duration; Math.Max(0.0, Math.Min(time, 1.0)); } current = from * (1.0 - time) + to * time; if (time >= 1f) { mParent.ModifyMinMax(baseData, new DoubleVector3(current, y, 0.0)); return true; } return false; } } /// /// adds a point to the category. The points are sorted by their x value automatically /// /// /// public void AddCandleToCategory(string category, CandleValue candle, float autoScrollSlideTime = 0f) { if (mData.ContainsKey(category) == false) { Debug.LogWarning("Invalid category name. Make sure the category is present in the chart"); return; } CategoryData data = (CategoryData)mData[category]; List candles = data.Data; double start = candle.Start; double end = candle.Start + candle.Duration; double candleMax = Math.Max(Math.Max(candle.Open, candle.Close), Math.Max(candle.High, candle.Low)); double candleMin = Math.Min(Math.Min(candle.Open, candle.Close), Math.Min(candle.High, candle.Low)); if (autoScrollSlideTime <= 0f) { if (data.MaxX.HasValue == false || data.MaxX.Value < end) data.MaxX = end; } if (data.MinX.HasValue == false || data.MinX.Value > start) data.MinX = start; if (data.MaxY.HasValue == false || data.MaxY.Value < candleMax) data.MaxY = candleMax; if (data.MinY.HasValue == false || data.MinY.Value > candleMin) data.MinY = candleMin; if (candles.Count > 0) { if (candles[candles.Count - 1].Start < candle.Start) { if (autoScrollSlideTime > 0f) { Slider s = new Slider(this); s.category = category; s.from = candles[candles.Count - 1].End; s.to = end; s.StartTime = Time.time; s.Duration = autoScrollSlideTime; s.y = (candleMax + candleMin) * 0.5; s.index = candles.Count - 1; mSliders.Add(s); } candles.Add(candle); RaiseRealtimeDataChanged(candles.Count-1, category); return; } } int search = candles.BinarySearch(candle, mComparer); if (search < 0) search = ~search; candles.Insert(search, candle); RaiseRealtimeDataChanged(search, category); } double IInternalCandleData.GetMaxValue(int axis, bool dataValue) { return GetMaxValue(axis, dataValue); } double IInternalCandleData.GetMinValue(int axis, bool dataValue) { return GetMinValue(axis, dataValue); } public override void OnAfterDeserialize() { if (mSerializedData == null) return; mData.Clear(); mSuspendEvents = true; for (int i = 0; i < mSerializedData.Length; i++) { SerializedCategory cat = mSerializedData[i]; if (cat.Depth < 0) cat.Depth = 0f; string name = cat.Name; AddCategory3DCandle(name, cat.UpCandle, cat.DownCandle, cat.Depth); CategoryData data = (CategoryData)mData[name]; data.Data.AddRange(cat.Data); data.MaxX = cat.MaxX; data.MaxY = cat.MaxY; data.MinX = cat.MinX; data.MinY = cat.MinY; } mSuspendEvents = false; } public override void OnBeforeSerialize() { List serialized = new List(); foreach (KeyValuePair pair in mData.Select(x => new KeyValuePair(x.Key, (CategoryData)x.Value))) { SerializedCategory cat = new SerializedCategory(); cat.Name = pair.Key; cat.MaxX = pair.Value.MaxX; cat.MinX = pair.Value.MinX; cat.MaxY = pair.Value.MaxY; cat.MinY = pair.Value.MinY; cat.UpCandle = pair.Value.UpCandle; cat.DownCandle = pair.Value.DownCandle; cat.Depth = pair.Value.Depth; cat.Data = pair.Value.Data.ToArray(); if (cat.Depth < 0) cat.Depth = 0f; serialized.Add(cat); } mSerializedData = serialized.ToArray(); } public override BaseScrollableCategoryData GetDefaultCategory() { throw new NotImplementedException(); } protected override void InnerClearCategory(string category) { throw new NotImplementedException(); } protected override void AppendDatum(string category, MixedSeriesGenericValue value) { throw new NotImplementedException(); } protected override void AppendDatum(string category, IList value) { throw new NotImplementedException(); } protected override bool AddCategory(string category, BaseScrollableCategoryData data) { throw new NotImplementedException(); } int IInternalCandleData.TotalCategories { get { return mData.Count; } } event Action IInternalCandleData.InternalRealTimeDataChanged { add { RealtimeDataChanged += value; } remove { RealtimeDataChanged -= value; } } IEnumerable IInternalCandleData.Categories { get { return mData.Values.Select(x => (CategoryData)x); } } } }