#define Graph_And_Chart_PRO using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; namespace ChartAndGraph { /// /// Base class for all axis charts that are scrollable. This class implements some important functionallity that is common to all scrollable charts /// public abstract class ScrollableAxisChart : AxisChart { private GameObject mMask; private Vector2? mLastPosition; private GraphicRaycaster mCaster; protected Dictionary> mTexts = new Dictionary>(); protected HashSet mActiveTexts = new HashSet(); // bool mMaskCreated = false; public ScrollableChartData ScrollableData { get { return (ScrollableChartData)DataLink; } } [HideInInspector] [SerializeField] protected bool autoScrollHorizontally = false; public bool AutoScrollHorizontally { get { return autoScrollHorizontally; } set { autoScrollHorizontally = value; InvalidateRealtime(); } } protected override void Update() { base.Update(); if(IsCanvas) { // handle mouse events for canvas charts HandleMouseDrag(); } } public override void GenerateRealtime() { base.GenerateRealtime(); if(SupportRealtimeGeneration) { GenerateAxis(false); } } protected override double GetScrollOffset(int axis) { if (Scrollable == false) return 0f; if ((autoScrollHorizontally && axis == 0) || (autoScrollVertically && axis == 1)) { double sMax = ScrollableData.GetMaxValue(axis, false); //float sMin = (float)((IInternalGraphData)Data).GetMinValue(axis,false); double dMax = ScrollableData.GetMaxValue(axis, true); //float dMin = (float)((IInternalGraphData)Data).GetMinValue(axis, true); double scrolling = dMax - sMax; if (axis == 1) { if (ScrollableData.VerticalViewSize < 0) scrolling += ScrollableData.VerticalViewSize; verticalScrolling = scrolling; } else if (axis == 0) { if (ScrollableData.HorizontalViewSize < 0) scrolling += ScrollableData.HorizontalViewSize; horizontalScrolling = scrolling; } return scrolling; } if (axis == 1) return verticalScrolling; else if (axis == 0) return horizontalScrolling; return base.GetScrollOffset(axis); } protected void AddBillboardText(string cat, int index, BillboardText text) { if (text.UIText != null) ChartCommon.MakeMaskable(text.UIText, true); Dictionary addTo; if (mTexts.TryGetValue(cat, out addTo) == false) { addTo = new Dictionary(ChartCommon.DefaultIntComparer); mTexts.Add(cat, addTo); } addTo.Add(index, text); } protected void ClearBillboard() { mTexts.Clear(); } protected void ClearBillboardCategories() { foreach (Dictionary d in mTexts.Values) d.Clear(); } [HideInInspector] // [SerializeField] [NonSerialized] protected bool scrollable = true; public bool Scrollable { get { return true; } set { scrollable = value; Invalidate(); } } [HideInInspector] [SerializeField] protected double horizontalScrolling = 0f; public double HorizontalScrolling { get { return horizontalScrolling; } set { horizontalScrolling = value; InvalidateRealtime(); } } [HideInInspector] [SerializeField] protected bool autoScrollVertically = false; public bool AutoScrollVertically { get { return autoScrollVertically; } set { autoScrollVertically = value; InvalidateRealtime(); } } [HideInInspector] [SerializeField] protected double verticalScrolling = 0f; public double VerticalScrolling { get { return verticalScrolling; } set { verticalScrolling = value; InvalidateRealtime(); } } [SerializeField] private bool raycastTarget = true; public bool RaycastTarget { get { return raycastTarget; ; } set { raycastTarget = value; Invalidate(); } } public bool PointToClient(Vector3 worldPoint, out double x, out DateTime y) { double dx, dy; bool res = PointToClient(worldPoint, out dx, out dy); x = dx; y = ChartDateUtility.ValueToDate(dy); return res; } public bool PointToClient(Vector3 worldPoint, out DateTime x, out DateTime y) { double dx, dy; bool res = PointToClient(worldPoint, out dx, out dy); x = ChartDateUtility.ValueToDate(dx); y = ChartDateUtility.ValueToDate(dy); return res; } public bool PointToClient(Vector3 worldPoint, out DateTime x, out double y) { double dx, dy; bool res = PointToClient(worldPoint, out dx, out dy); x = ChartDateUtility.ValueToDate(dx); y = dy; return res; } /// /// transform a point from axis units into world space. returns true on success and false on failure (failure should never happen for this implementation) /// /// the resulting world space point /// x coordinate in axis units /// y coodinate in axis units /// for 3d chart specifing a catgory will return a point with the proper depth setting /// public bool PointToWorldSpace(out Vector3 result,DateTime x, double y, string category = null) { return PointToWorldSpace(out result,ChartDateUtility.DateToValue(x), y, category); } /// /// transform a point from axis units into world space. returns true on success and false on failure (failure should never happen for this implementation) /// /// the resulting world space point /// x coordinate in axis units /// y coodinate in axis units /// for 3d chart specifing a catgory will return a point with the proper depth setting /// public bool PointToWorldSpace(out Vector3 result,double x, DateTime y, string category = null) { return PointToWorldSpace(out result,x, ChartDateUtility.DateToValue(y), category); } /// /// transform a point from axis units into world space. returns true on success and false on failure (failure should never happen for this implementation) /// /// the resulting world space point /// x coordinate in axis units /// y coodinate in axis units /// for 3d chart specifing a catgory will return a point with the proper depth setting /// public bool PointToWorldSpace(out Vector3 result,DateTime x, DateTime y, string category = null) { return PointToWorldSpace(out result,ChartDateUtility.DateToValue(x), ChartDateUtility.DateToValue(y), category); } protected abstract double GetCategoryDepth(string category); /// /// internal method used bu the mixed series chart to set this chart to default settings /// internal abstract void SetAsMixedSeries(); private DoubleVector3 PointToNormalized(double x, double y) { 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 resX = ((x - xScroll) / xSize); double resY = ((y - yScroll) / ySize); return new DoubleVector3(resX, resY,0.0); } private DoubleVector3 NormalizedToPoint(double x, double y) { 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 resX = x * xSize + xScroll; double resY = y * ySize + yScroll; return new DoubleVector3(resX, resY, 0.0); } private Vector3 PointShift { get { if(IsCanvas) return CanvasFitOffset; return new Vector3(); } } protected override Vector3 CanvasFitOffset { get { return new Vector3(0.5f, 0.5f, 0f); } } /// /// transform a point from axis units into world space. returns true on success and false on failure (failure should never happen for this implementation) /// /// the resulting world space point /// x coordinate in axis units /// y coodinate in axis units /// for 3d chart specifing a catgory will return a point with the proper depth setting /// public bool PointToWorldSpace(out Vector3 result,double x,double y, string category = null) { Vector3 fit = PointShift; 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 resX = (((x - xScroll) / xSize)- fit.x) * ((IInternalUse)this).InternalTotalWidth; double resY = (((y-yScroll) / ySize) - fit.y) * ((IInternalUse)this).InternalTotalHeight; double resZ = 0.0; if(category != null) resZ = GetCategoryDepth(category); Transform t = transform; if (FixPosition != null) t = FixPosition.transform; result = t.TransformPoint(new Vector3((float)resX, (float)resY, (float)resZ)); return true; } /// /// gets the mouse position on the graph in axis units. returns true if the mouse is in bounds of the chart , false otherwise /// /// /// /// public bool MouseToClient(out double x,out double y) { Vector2 mousePos; x = y = 0.0; mCaster = GetComponentInParent(); if (mCaster == null) return false; RectTransformUtility.ScreenPointToLocalPointInRectangle(transform as RectTransform, Input.mousePosition, mCaster.eventCamera, out mousePos); if(FixPosition != null) { mousePos = transform.TransformPoint(mousePos); mousePos = FixPosition.transform.InverseTransformPoint(mousePos); } mousePos.x /= TotalWidth; mousePos.y /= TotalHeight; Vector3 fit = PointShift; mousePos.x += fit.x; mousePos.y += fit.y; bool mouseIn = RectTransformUtility.RectangleContainsScreenPoint(transform as RectTransform, Input.mousePosition); DoubleVector3 res = NormalizedToPoint(mousePos.x, mousePos.y); x = res.x; y = res.y; return mouseIn; } /// /// transform a point from world space into axis units. for best use , provide a point which is place on the canvas plane /// /// /// /// /// public bool PointToClient(Vector3 worldPoint, out double x, out double y) { Vector3 fit = PointShift; 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); Transform t = transform; if (FixPosition != null) t = FixPosition.transform; worldPoint = t.InverseTransformPoint(worldPoint); x = xScroll + xSize * ((((double)worldPoint.x) / ((IInternalUse)this).InternalTotalWidth) + fit.x); y = yScroll + ySize * ((((double)worldPoint.y) / ((IInternalUse)this).InternalTotalHeight) + fit.y); return true; } /// /// Trims a rect in axis units into the visible portion of it in axis units. returns false if the parameter rect is completely out of view , true otherwise /// /// /// /// public bool TrimRect(DoubleRect rect, out DoubleRect trimmed) { DoubleVector3 min = rect.min; DoubleVector3 max = rect.max; trimmed = new DoubleRect(); min = PointToNormalized(min.x, min.y); max = PointToNormalized(max.x, max.y); if (min.x > 1f || min.y > 1f) return false; if (max.x < 0f || max.y < 0f) return false; double minX = ChartCommon.Clamp(Math.Min(min.x, max.x)); double minY = ChartCommon.Clamp(Math.Min(min.y, max.y)); double maxX = ChartCommon.Clamp(Math.Max(min.x, max.x)); double maxY = ChartCommon.Clamp(Math.Max(min.y, max.y)); min = NormalizedToPoint(minX, minY); max = NormalizedToPoint(maxX, maxY); trimmed = new DoubleRect(min.x,min.y, max.x -min.x,max.y - min.y); return true; } /// /// returns true if the axis unity rect is visible on the chart, even if it is only partially visible. false otherwise /// /// /// public bool IsRectVisible(DoubleRect rect) { DoubleVector3 min = rect.min; DoubleVector3 max = rect.max; min = PointToNormalized(min.x, min.y); max = PointToNormalized(max.x, max.y); if (min.x > 1f || min.y > 1f) return false; if (max.x < 0f || max.y < 0f) return false; return true; } /// /// tranforms an axis unit rect into a recttransform. The rect transform must have a parent assigned to it. Also the chart and the rectTransform must share a common ancestor canvas /// /// The rect tranform to which the result is assigned to /// /// /// public bool RectToCanvas(RectTransform assignTo, DoubleRect rect, string catgeory = null) { DoubleVector3 min = rect.min; DoubleVector3 max = rect.max; Vector3 worldMin, worldMax; if(PointToWorldSpace(out worldMin, min.x, min.y, catgeory) == false) return false; if (PointToWorldSpace(out worldMax, max.x, max.y, catgeory) == false) return false; Transform parent = assignTo.parent; if (parent == null) return false; worldMin = parent.transform.InverseTransformPoint(worldMin); worldMax = parent.transform.InverseTransformPoint(worldMax); float minX = Math.Min(worldMin.x, worldMax.x); float minY = Math.Min(worldMin.y, worldMax.y); float sizeX = Math.Max(worldMin.x, worldMax.x) - minX; float sizeY = Math.Max(worldMin.y, worldMax.y) - minY; assignTo.anchorMin = new Vector2(0.5f, 0.5f); assignTo.anchorMax = new Vector2(0.5f, 0.5f); assignTo.pivot = new Vector2(0f, 0f); assignTo.anchoredPosition = new Vector2(minX, minY); assignTo.sizeDelta = new Vector2(sizeX, sizeY); return true; } protected abstract float GetScrollingRange(int axis); public UnityEvent MousePan; [SerializeField] private bool horizontalPanning; public bool HorizontalPanning { get { return horizontalPanning; ; } set { horizontalPanning = value; Invalidate(); } } [SerializeField] private bool verticalPanning; public bool VerticalPanning { get { return verticalPanning; ; } set { verticalPanning = value; Invalidate(); } } bool mStencilMask = false; public bool StencilMask { get { return mStencilMask; } set { mStencilMask = value; Invalidate(); } } protected GameObject CreateRectMask(Rect viewRect) { //GameObject obj = Instantiate(Resources.Load("Chart And Graph/RectMask") as GameObject); GameObject obj = ChartCommon.CreateCanvasChartItem(); obj.name = "rectMask2D";; ChartCommon.HideObject(obj, hideHierarchy); if (mStencilMask) { var mask = obj.AddComponent(); mask.showMaskGraphic = false; var image = obj.AddComponent(); image.maskable = false; image.raycastTarget = false; } else obj.AddComponent(); //obj.AddComponent(); obj.transform.SetParent(transform, false); var rectTransform = obj.GetComponent(); rectTransform.anchorMin = new Vector2(0f, 0f); rectTransform.anchorMax = new Vector2(0f, 0f); rectTransform.pivot = new Vector2(0f, 1f); rectTransform.sizeDelta = viewRect.size; rectTransform.anchoredPosition = new Vector2(0f, viewRect.size.y); mMask = obj; return obj; } protected override void ClearChart() { base.ClearChart(); } protected string StringFromAxisFormat(DoubleVector3 val, AxisBase axis,bool isX) { double itemVal = isX ? val.x : val.y; if (axis == null) return ChartAdancedSettings.Instance.FormatFractionDigits(2, itemVal); return StringFromAxisFormat(val, axis, axis.MainDivisions.FractionDigits, isX); } protected string StringFromAxisFormat(DoubleVector3 val, AxisBase axis, int fractionDigits,bool isX) { val.z = 0; double itemVal = isX ? val.x : val.y; var dic = VectorValueToStringMap; KeyValuePair res; // Debug.Log("try get " + val + " count is " + dic.Count); if (dic.TryGetValue(val, out res)) { if (isX && res.Key != null) return res.Key; if (isX == false && res.Value != null) return res.Value; } if (axis == null) return ChartAdancedSettings.Instance.FormatFractionDigits(fractionDigits, itemVal, CustomNumberFormat); string toSet = ""; if (axis.Format == AxisFormat.Number) toSet = ChartAdancedSettings.Instance.FormatFractionDigits(fractionDigits, itemVal, CustomNumberFormat); else { DateTime date = ChartDateUtility.ValueToDate(itemVal); if (axis.Format == AxisFormat.DateTime) toSet = ChartDateUtility.DateToDateTimeString(date,CustomDateTimeFormat); else { if (axis.Format == AxisFormat.Date) toSet = ChartDateUtility.DateToDateString(date); else toSet = ChartDateUtility.DateToTimeString(date); } } return toSet; } protected void GetScrollParams(out double minX,out double minY,out double maxX,out double maxY,out double xScroll,out double yScroll,out double xSize,out double ySize,out double xOut) { minX = ScrollableData.GetMinValue(0, false); minY = ScrollableData.GetMinValue(1, false); maxX = ScrollableData.GetMaxValue(0, false); maxY = ScrollableData.GetMaxValue(1, false); xScroll = GetScrollOffset(0) + minX; yScroll = GetScrollOffset(1) + minY; xSize = maxX - minX; ySize = maxY - minY; xOut = xScroll + xSize; } private void MouseDraged(Vector2 delta) { bool drag = false; if (VerticalPanning) { float range = GetScrollingRange(1); VerticalScrolling -= (delta.y / TotalHeightLink) * range; if (Mathf.Abs(delta.y) > 1f) drag = true; } if (HorizontalPanning) { float range = GetScrollingRange(0); HorizontalScrolling -= (delta.x / TotalWidthLink) * range; if (Mathf.Abs(delta.x) > 1f) drag = true; } if (drag) { InvalidateRealtime(); if (MousePan != null) MousePan.Invoke(); } } private void HandleMouseDrag() { if (verticalPanning == false && horizontalPanning == false) return; mCaster = GetComponentInParent(); if (mCaster == null) return; if (Application.isPlaying == false) return; Vector2 mousePos; var chart = GetComponentInParent(); var pointer = ChartCommon.EnsureComponent(chart.gameObject); if (pointer == null) return; Vector3 checkMousePos = pointer.ScreenPosition; RectTransformUtility.ScreenPointToLocalPointInRectangle(transform as RectTransform, checkMousePos, mCaster.eventCamera, out mousePos); var cam = mCaster.eventCamera; bool mouseIn = RectTransformUtility.RectangleContainsScreenPoint(transform as RectTransform, checkMousePos, cam); if (( (pointer!=null && pointer.IsMouseDown)) && mouseIn) { if (mLastPosition.HasValue) { Vector2 delta = mousePos - mLastPosition.Value; MouseDraged(delta); } mLastPosition = mousePos; } else mLastPosition = null; } protected void TriggerActiveTextsOut() { foreach (BillboardText t in mActiveTexts) { if (t == null) continue; if (t.UIText == null) continue; foreach (ChartItemEffect effect in t.UIText.GetComponents()) { if (effect != null) effect.TriggerOut(false); } } mActiveTexts.Clear(); } protected void AddActiveText(BillboardText b) { mActiveTexts.Add(b); } protected void SelectActiveText(BillboardText b) { TriggerActiveTextsOut(); GameObject tx = b.UIText; if (tx != null) { ChartItemEvents e = tx.GetComponent(); if (e != null) { e.OnMouseHover.Invoke(e.gameObject); AddActiveText(b); } } } } }