using System.Net.Mime; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace XCharts { /// /// The axis in rectangular coordinate. /// 直角坐标系的坐标轴组件。 /// [System.Serializable] public class Axis : JsonDataSupport, IEquatable { /// /// the type of axis. /// 坐标轴类型。 /// public enum AxisType { /// /// Numerical axis, suitable for continuous data. /// 数值轴,适用于连续数据。 /// Value, /// /// Category axis, suitable for discrete category data. Data should only be set via data for this type. /// 类目轴,适用于离散的类目数据,为该类型时必须通过 data 设置类目数据。 /// Category } /// /// the type of axis min and max value. /// 坐标轴最大最小刻度显示类型。 /// public enum AxisMinMaxType { /// /// 0 - maximum. /// 0-最大值。 /// Default, /// /// minimum - maximum. /// 最小值-最大值。 /// MinMax, /// /// Customize the minimum and maximum. /// 自定义最小值最大值。 /// Custom } /// /// the type of split line. /// 分割线类型 /// public enum SplitLineType { /// /// 不显示分割线 /// None, /// /// 实线 /// Solid, /// /// 虚线 /// Dashed, /// /// 点线 /// Dotted, /// /// 点划线 /// DashDot, /// /// 双点划线 /// DashDotDot } [SerializeField] protected bool m_Show = true; [SerializeField] protected AxisType m_Type; [SerializeField] protected AxisMinMaxType m_MinMaxType; [SerializeField] protected int m_Min; [SerializeField] protected int m_Max; [SerializeField] protected int m_SplitNumber = 5; [SerializeField] protected float m_Interval = 0; [SerializeField] protected bool m_ShowSplitLine = false; [SerializeField] protected SplitLineType m_SplitLineType = SplitLineType.Dashed; [SerializeField] protected bool m_BoundaryGap = true; [SerializeField] protected int m_MaxCache = 0; [SerializeField] protected List m_Data = new List(); [SerializeField] protected AxisLine m_AxisLine = AxisLine.defaultAxisLine; [SerializeField] protected AxisName m_AxisName = AxisName.defaultAxisName; [SerializeField] protected AxisTick m_AxisTick = AxisTick.defaultTick; [SerializeField] protected AxisLabel m_AxisLabel = AxisLabel.defaultAxisLabel; [SerializeField] protected AxisSplitArea m_SplitArea = AxisSplitArea.defaultSplitArea; [NonSerialized] private float m_ValueRange; [NonSerialized] private bool m_NeedUpdateFilterData; /// /// Set this to false to prevent the axis from showing. /// 是否显示坐标轴。 /// public bool show { get { return m_Show; } set { m_Show = value; } } /// /// the type of axis. /// 坐标轴类型。 /// public AxisType type { get { return m_Type; } set { m_Type = value; } } /// /// the type of axis minmax. /// 坐标轴刻度最大最小值显示类型。 /// public AxisMinMaxType minMaxType { get { return m_MinMaxType; } set { m_MinMaxType = value; } } /// /// The minimun value of axis. /// 设定的坐标轴刻度最小值,当minMaxType为Custom时有效。 /// public int min { get { return m_Min; } set { m_Min = value; } } /// /// The maximum value of axis. /// 设定的坐标轴刻度最大值,当minMaxType为Custom时有效。 /// public int max { get { return m_Max; } set { m_Max = value; } } /// /// Number of segments that the axis is split into. /// 坐标轴的分割段数。 /// public int splitNumber { get { return m_SplitNumber; } set { m_SplitNumber = value; } } /// /// 强制设置坐标轴分割间隔。无法在类目轴中使用。 /// Compulsively set segmentation interval for axis.This is unavailable for category axis. /// public float interval { get { return m_Interval; } set { m_Interval = value; } } /// /// showSplitLineSet this to false to prevent the splitLine from showing. value type axes are shown by default, while category type axes are hidden. /// 是否显示分隔线。默认数值轴显示,类目轴不显示。 /// public bool showSplitLine { get { return m_ShowSplitLine; } set { m_ShowSplitLine = value; } } /// /// the type of split line. /// 分割线类型。 /// public SplitLineType splitLineType { get { return m_SplitLineType; } set { m_SplitLineType = value; } } /// /// The boundary gap on both sides of a coordinate axis. /// 坐标轴两边是否留白。 /// public bool boundaryGap { get { return m_BoundaryGap; } set { m_BoundaryGap = value; } } /// /// The max number of axis data cache. /// The first data will be remove when the size of axis data is larger then maxCache. /// 可缓存的最大数据量。默认为0没有限制,大于0时超过指定值会移除旧数据再插入新数据。 /// public int maxCache { get { return m_MaxCache; } set { m_MaxCache = value < 0 ? 0 : value; } } /// /// Category data, available in type: 'Category' axis. /// 类目数据,在类目轴(type: 'category')中有效。 /// public List data { get { return m_Data; } } /// /// axis Line. /// 坐标轴轴线。 /// public AxisLine axisLine { get { return m_AxisLine; } set { m_AxisLine = value; } } /// /// axis name. /// 坐标轴名称。 /// public AxisName axisName { get { return m_AxisName; } set { m_AxisName = value; } } /// /// axis tick. /// 坐标轴刻度。 /// public AxisTick axisTick { get { return m_AxisTick; } set { m_AxisTick = value; } } /// /// axis label. /// 坐标轴刻度标签。 /// public AxisLabel axisLabel { get { return m_AxisLabel; } set { m_AxisLabel = value; } } /// /// axis split area. /// 坐标轴分割区域。 /// public AxisSplitArea splitArea { get { return m_SplitArea; } set { m_SplitArea = value; } } /// /// the axis label text list. /// 坐标轴刻度标签的Text列表。 /// public List axisLabelTextList { get { return m_AxisLabelTextList; } set { m_AxisLabelTextList = value; } } /// /// the current minimun value. /// 当前最小值。 /// public float minValue { get; set; } /// /// the current maximum value. /// 当前最大值。 /// public float maxValue { get; set; } /// /// the x offset of zero position. /// 坐标轴原点在X轴的偏移。 /// public float zeroXOffset { get; set; } /// /// the y offset of zero position. /// 坐标轴原点在Y轴的偏移。 /// public float zeroYOffset { get; set; } private int filterStart; private int filterEnd; private List filterData; private List m_AxisLabelTextList = new List(); private GameObject m_TooltipLabel; private Text m_TooltipLabelText; private RectTransform m_TooltipLabelRect; public void Copy(Axis other) { m_Show = other.show; m_Type = other.type; m_Min = other.min; m_Max = other.max; m_SplitNumber = other.splitNumber; m_Interval = other.interval; m_ShowSplitLine = other.showSplitLine; m_SplitLineType = other.splitLineType; m_BoundaryGap = other.boundaryGap; m_AxisName.Copy(other.axisName); m_AxisLabel.Copy(other.axisLabel); m_Data.Clear(); m_Data.Capacity = m_Data.Count; foreach (var d in other.data) m_Data.Add(d); } /// /// 清空类目数据 /// public void ClearData() { m_Data.Clear(); } /// /// 当前坐标轴是否时类目轴 /// /// public bool IsCategory() { return type == AxisType.Category; } /// /// 当前坐标轴是否时数值轴 /// /// public bool IsValue() { return type == AxisType.Value; } /// /// 添加一个类目到类目数据列表 /// /// public void AddData(string category) { if (maxCache > 0) { while (m_Data.Count > maxCache) { m_NeedUpdateFilterData = true; m_Data.RemoveAt(0); } } m_Data.Add(category); } /// /// 获得在dataZoom范围内指定索引的类目数据 /// /// 类目数据索引 /// 区域缩放 /// public string GetData(int index, DataZoom dataZoom) { var showData = GetDataList(dataZoom); if (index >= 0 && index < showData.Count) return showData[index]; else return ""; } /// /// 获得指定区域缩放的类目数据列表 /// /// 区域缩放 /// public List GetDataList(DataZoom dataZoom) { if (dataZoom != null && dataZoom.enable) { var startIndex = (int)((data.Count - 1) * dataZoom.start / 100); var endIndex = (int)((data.Count - 1) * dataZoom.end / 100); var count = endIndex == startIndex ? 1 : endIndex - startIndex + 1; UpdateFilterData(dataZoom); return filterData; } else { return m_Data; } } private List emptyFliter = new List(); /// /// 更新dataZoom对应的类目数据列表 /// /// public void UpdateFilterData(DataZoom dataZoom) { if (dataZoom != null && dataZoom.enable) { var startIndex = (int)((data.Count - 1) * dataZoom.start / 100); var endIndex = (int)((data.Count - 1) * dataZoom.end / 100); if (startIndex != filterStart || endIndex != filterEnd || m_NeedUpdateFilterData) { filterStart = startIndex; filterEnd = endIndex; m_NeedUpdateFilterData = false; if (m_Data.Count > 0) { var count = endIndex == startIndex ? 1 : endIndex - startIndex + 1; filterData = m_Data.GetRange(startIndex, count); } else { filterData = m_Data; } } else if (endIndex == 0) { filterData = emptyFliter; } } } /// /// 获得分割段数 /// /// /// public int GetSplitNumber(float coordinateWid, DataZoom dataZoom) { if (type == AxisType.Value) { if (m_Interval > 0) { if (coordinateWid <= 0) return 0; int num = Mathf.CeilToInt(m_ValueRange / m_Interval) + 1; int maxNum = Mathf.CeilToInt(coordinateWid / 15); if (num > maxNum) { m_Interval = m_ValueRange / (maxNum - 1); num = Mathf.CeilToInt(m_ValueRange / m_Interval) + 1; } return num; } else return m_SplitNumber; } int dataCount = GetDataList(dataZoom).Count; if (m_SplitNumber <= 0) return dataCount; if (dataCount > 2 * m_SplitNumber || dataCount <= 0) return m_SplitNumber; else return dataCount; } /// /// 获得分割段的宽度 /// /// /// /// public float GetSplitWidth(float coordinateWidth, DataZoom dataZoom) { int split = GetSplitNumber(coordinateWidth, dataZoom); int segment = (m_BoundaryGap ? split : split - 1); segment = segment <= 0 ? 1 : segment; return coordinateWidth / segment; } /// /// 获得类目数据个数 /// /// /// public int GetDataNumber(DataZoom dataZoom) { return GetDataList(dataZoom).Count; } /// /// 获得一个类目数据在坐标系中代表的宽度 /// /// /// /// public float GetDataWidth(float coordinateWidth, DataZoom dataZoom) { var dataCount = GetDataNumber(dataZoom); int segment = (m_BoundaryGap ? dataCount : dataCount - 1); segment = segment <= 0 ? 1 : segment; return coordinateWidth / segment; } /// /// 获得标签显示的名称 /// /// /// /// /// /// public string GetLabelName(float coordinateWidth, int index, float minValue, float maxValue, DataZoom dataZoom) { int split = GetSplitNumber(coordinateWidth, dataZoom); if (m_Type == AxisType.Value) { float value = 0; if (m_Interval > 0) { if (index == split - 1) value = maxValue; else value = minValue + index * m_Interval; } else { value = (minValue + (maxValue - minValue) * index / (split - 1)); } return m_AxisLabel.GetFormatterContent(value); } var showData = GetDataList(dataZoom); int dataCount = showData.Count; if (dataCount <= 0) return ""; if (index == split - 1 && !m_BoundaryGap) { return m_AxisLabel.GetFormatterContent(showData[dataCount - 1]); } else { float rate = dataCount / split; if (rate < 1) rate = 1; int offset = m_BoundaryGap ? (int)(rate / 2) : 0; int newIndex = (int)(index * rate >= dataCount - 1 ? dataCount - 1 : offset + index * rate); return m_AxisLabel.GetFormatterContent(showData[newIndex]); } } /// /// 获得分割线条数 /// /// /// public int GetScaleNumber(float coordinateWidth, DataZoom dataZoom) { if (type == AxisType.Value) { int splitNum = GetSplitNumber(coordinateWidth, dataZoom); return m_BoundaryGap ? splitNum + 1 : splitNum; } else { var showData = GetDataList(dataZoom); int dataCount = showData.Count; if (m_SplitNumber <= 0) return m_BoundaryGap ? dataCount + 1 : dataCount; if (dataCount > 2 * splitNumber || dataCount <= 0) return m_BoundaryGap ? m_SplitNumber + 1 : m_SplitNumber; else return m_BoundaryGap ? dataCount + 1 : dataCount; } } /// /// 获得分割段宽度 /// /// /// /// public float GetScaleWidth(float coordinateWidth, int index, DataZoom dataZoom) { int num = GetScaleNumber(coordinateWidth, dataZoom) - 1; if (num <= 0) num = 1; if (type == AxisType.Value && m_Interval > 0) { if (index == num - 1) return coordinateWidth - (num - 1) * m_Interval * coordinateWidth / m_ValueRange; else return m_Interval * coordinateWidth / m_ValueRange; } else { return coordinateWidth / num; } } /// /// 更新刻度标签文字 /// /// public void UpdateLabelText(float coordinateWidth, DataZoom dataZoom) { for (int i = 0; i < axisLabelTextList.Count; i++) { if (axisLabelTextList[i] != null) { axisLabelTextList[i].text = GetLabelName(coordinateWidth, i, minValue, maxValue, dataZoom); } } } public void SetTooltipLabel(GameObject label) { m_TooltipLabel = label; m_TooltipLabelRect = label.GetComponent(); m_TooltipLabelText = label.GetComponentInChildren(); m_TooltipLabel.SetActive(true); } public void SetTooltipLabelColor(Color bgColor, Color textColor) { m_TooltipLabel.GetComponent().color = bgColor; m_TooltipLabelText.color = textColor; } public void SetTooltipLabelActive(bool flag) { if (m_TooltipLabel && m_TooltipLabel.activeInHierarchy != flag) { m_TooltipLabel.SetActive(flag); } } public void UpdateTooptipLabelText(string text) { if (m_TooltipLabelText) { m_TooltipLabelText.text = text; m_TooltipLabelRect.sizeDelta = new Vector2(m_TooltipLabelText.preferredWidth + 8, m_TooltipLabelText.preferredHeight + 8); } } public void UpdateTooltipLabelPos(Vector2 pos) { if (m_TooltipLabel) { m_TooltipLabel.transform.localPosition = pos; } } /// /// 调整最大最小值 /// /// /// public void AdjustMinMaxValue(ref int minValue, ref int maxValue) { if (minMaxType == Axis.AxisMinMaxType.Custom) { if (min != 0 || max != 0) { minValue = min; maxValue = max; } } else { switch (minMaxType) { case Axis.AxisMinMaxType.Default: if (minValue > 0 && maxValue > 0) { minValue = 0; maxValue = ChartHelper.GetMaxDivisibleValue(maxValue); } else if (minValue < 0 && maxValue < 0) { minValue = ChartHelper.GetMinDivisibleValue(minValue); maxValue = 0; } else { minValue = ChartHelper.GetMinDivisibleValue(minValue); maxValue = ChartHelper.GetMaxDivisibleValue(maxValue); } break; case Axis.AxisMinMaxType.MinMax: minValue = ChartHelper.GetMinDivisibleValue(minValue); maxValue = ChartHelper.GetMaxDivisibleValue(maxValue); break; } } m_ValueRange = maxValue - minValue; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } else if (obj is Axis) { return Equals((Axis)obj); } else { return false; } } public bool Equals(Axis other) { if (ReferenceEquals(null, other)) { return false; } return show == other.show && type == other.type && min == other.min && max == other.max && splitNumber == other.splitNumber && interval == other.interval && showSplitLine == other.showSplitLine && m_AxisLabel.Equals(other.axisLabel) && splitLineType == other.splitLineType && boundaryGap == other.boundaryGap && axisName.Equals(other.axisName) && ChartHelper.IsValueEqualsList(m_Data, other.data); } public static bool operator ==(Axis left, Axis right) { if (ReferenceEquals(left, null) && ReferenceEquals(right, null)) { return true; } else if (ReferenceEquals(left, null) || ReferenceEquals(right, null)) { return false; } return Equals(left, right); } public static bool operator !=(Axis left, Axis right) { return !(left == right); } public override int GetHashCode() { return base.GetHashCode(); } public override void ParseJsonData(string jsonData) { if (string.IsNullOrEmpty(jsonData) || !m_DataFromJson) return; m_Data = ChartHelper.ParseStringFromString(jsonData); } } /// /// The x axis in cartesian(rectangular) coordinate. a grid component can place at most 2 x axis, /// one on the bottom and another on the top. /// 直角坐标系 grid 中的 x 轴,单个 grid 组件最多只能放上下两个 x 轴。 /// [System.Serializable] public class XAxis : Axis { public XAxis Clone() { var axis = XAxisPool.Get(); axis.show = show; axis.type = type; axis.min = min; axis.max = max; axis.splitNumber = splitNumber; axis.interval = interval; axis.showSplitLine = showSplitLine; axis.splitLineType = splitLineType; axis.boundaryGap = boundaryGap; axis.axisName.Copy(axisName); axis.axisLabel.Copy(axisLabel); axis.data.Clear(); if (axis.data.Capacity < data.Count) axis.data.Capacity = data.Count; foreach (var d in data) axis.data.Add(d); return axis; } public static XAxis defaultXAxis { get { var axis = new XAxis { m_Show = true, m_Type = AxisType.Category, m_Min = 0, m_Max = 0, m_SplitNumber = 5, m_ShowSplitLine = false, m_SplitLineType = SplitLineType.Dashed, m_BoundaryGap = true, m_Data = new List() { "x1","x2","x3","x4","x5" } }; return axis; } } } /// /// The x axis in cartesian(rectangular) coordinate. a grid component can place at most 2 x axis, /// one on the bottom and another on the top. /// 直角坐标系 grid 中的 y 轴,单个 grid 组件最多只能放左右两个 y 轴 /// [System.Serializable] public class YAxis : Axis { public YAxis Clone() { var axis = YAxisPool.Get(); axis.show = show; axis.type = type; axis.min = min; axis.max = max; axis.splitNumber = splitNumber; axis.interval = interval; axis.showSplitLine = showSplitLine; axis.splitLineType = splitLineType; axis.boundaryGap = boundaryGap; axis.axisName.Copy(axisName); axis.axisLabel.Copy(axisLabel); axis.data.Clear(); if (axis.data.Capacity < data.Count) axis.data.Capacity = data.Count; foreach (var d in data) axis.data.Add(d); return axis; } public static YAxis defaultYAxis { get { var axis = new YAxis { m_Show = true, m_Type = AxisType.Value, m_Min = 0, m_Max = 0, m_SplitNumber = 5, m_ShowSplitLine = true, m_SplitLineType = SplitLineType.Dashed, m_BoundaryGap = false, m_Data = new List(5), }; return axis; } } } }