From f5037dafb140bba7a1f6ec374f4d31bbc7dacaa7 Mon Sep 17 00:00:00 2001 From: monitor1394 Date: Wed, 15 Jan 2020 19:41:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8A=98=E7=BA=BF=E5=9B=BE?= =?UTF-8?q?=E5=AF=B9=E6=95=B0=E8=BD=B4`Log`=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + Documentation/XCharts配置项手册.md | 5 +- Editor/PropertyDrawers/AxisDrawer.cs | 21 ++++++- Runtime/API/CoordinateChart_API.cs | 6 +- Runtime/Component/Main/Axis.cs | 65 +++++++++++++++++-- Runtime/Component/Main/Series.cs | 18 ++---- Runtime/Component/Sub/AxisLabel.cs | 9 ++- Runtime/Internal/CoordinateChart_DrawLine.cs | 66 +++++++++++++++++--- Runtime/Utility/ChartHelper.cs | 41 +++++++++++- 9 files changed, 198 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c48c095f..7d2280ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 更新日志 +* (2020.01.15) 增加折线图对数轴`Log`的支持 * (2020.01.09) 修复当设置`DataZoom`的`minShowNum`时可能异常的问题 * (2020.01.08) 修复当设置`AxisLine`的`onZero`时刻度显示异常的问题 * (2020.01.08) 增加`Mask`遮罩遮挡支持 diff --git a/Documentation/XCharts配置项手册.md b/Documentation/XCharts配置项手册.md index 5cb61093..f2581de0 100644 --- a/Documentation/XCharts配置项手册.md +++ b/Documentation/XCharts配置项手册.md @@ -290,9 +290,12 @@ 相关参数: * `show`:是否显示 `X` 轴。默认 `xAxises[0]` 为 `true`,`xAxises[1]` 为 `false`。 -* `type`:坐标轴类型。默认为 `Category`。有以下两种类型: +* `type`:坐标轴类型。默认为 `Category`。支持以下类型: * `Value`:数值轴,用于连续数据。 * `Category`:类目轴,适用于离散的类目数据,为该类型时必须通过 `data` 设置类目数据。 + * `Log`:对数轴,适用于对数数据。 +* `logBaseE`:对数轴是否以自然数 `e` 为底数,为 `true` 时 `logBase` 失效,只在对数轴(`type:'Log'`)中有效。 +* `logBase`:对数轴的底数,只在对数轴(`type:'Log'`)中有效。 * `minMaxType`:坐标轴刻度最大最小值显示类型。默认为 `Default`。有以下三种类型: * `Default`:0-最大值。 * `MinMax`:最小值-最大值。 diff --git a/Editor/PropertyDrawers/AxisDrawer.cs b/Editor/PropertyDrawers/AxisDrawer.cs index e0ea30b3..0da1e580 100644 --- a/Editor/PropertyDrawers/AxisDrawer.cs +++ b/Editor/PropertyDrawers/AxisDrawer.cs @@ -33,6 +33,8 @@ namespace XCharts SerializedProperty m_Show = prop.FindPropertyRelative("m_Show"); SerializedProperty m_Type = prop.FindPropertyRelative("m_Type"); + SerializedProperty m_LogBaseE = prop.FindPropertyRelative("m_LogBaseE"); + SerializedProperty m_LogBase = prop.FindPropertyRelative("m_LogBase"); SerializedProperty m_SplitNumber = prop.FindPropertyRelative("m_SplitNumber"); SerializedProperty m_Interval = prop.FindPropertyRelative("m_Interval"); SerializedProperty m_AxisLabel = prop.FindPropertyRelative("m_AxisLabel"); @@ -59,6 +61,13 @@ namespace XCharts EditorGUI.indentLevel++; EditorGUI.PropertyField(drawRect, m_Type); drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + if (type == Axis.AxisType.Log) + { + EditorGUI.PropertyField(drawRect, m_LogBaseE); + drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + EditorGUI.PropertyField(drawRect, m_LogBase); + drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + } if (type == Axis.AxisType.Value) { EditorGUI.PropertyField(drawRect, m_MinMaxType); @@ -80,6 +89,7 @@ namespace XCharts break; } } + EditorGUI.PropertyField(drawRect, m_SplitNumber); drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; EditorGUI.PropertyField(drawRect, m_Interval); @@ -180,6 +190,15 @@ namespace XCharts height += EditorGUIUtility.singleLineHeight * 2 + EditorGUIUtility.standardVerticalSpacing; } } + else if (type == Axis.AxisType.Log) + { + height += 2 * EditorGUIUtility.singleLineHeight + 1 * EditorGUIUtility.standardVerticalSpacing; + SerializedProperty m_MinMaxType = prop.FindPropertyRelative("m_MinMaxType"); + if (m_MinMaxType.enumValueIndex == (int)Axis.AxisMinMaxType.Custom) + { + height += EditorGUIUtility.singleLineHeight * 2 + EditorGUIUtility.standardVerticalSpacing; + } + } height += EditorGUI.GetPropertyHeight(m_AxisName); height += EditorGUI.GetPropertyHeight(m_AxisLine); height += EditorGUI.GetPropertyHeight(m_AxisTick); @@ -192,7 +211,7 @@ namespace XCharts private int InitToggle(SerializedProperty prop) { int index = 0; - int.TryParse(prop.displayName.Split(' ')[1],out index); + int.TryParse(prop.displayName.Split(' ')[1], out index); if (index >= m_DataFoldout.Count) { m_DataFoldout.Add(false); diff --git a/Runtime/API/CoordinateChart_API.cs b/Runtime/API/CoordinateChart_API.cs index f5ba58d2..858b33d6 100644 --- a/Runtime/API/CoordinateChart_API.cs +++ b/Runtime/API/CoordinateChart_API.cs @@ -146,18 +146,18 @@ namespace XCharts /// /// reutrn true when all the show axis is `Value` type. - /// 纯数值坐标。 + /// 纯数值坐标轴(数值轴或对数轴)。 /// /// public bool IsValue() { foreach (var axis in m_XAxises) { - if (axis.show && !axis.IsValue()) return false; + if (axis.show && !axis.IsValue() && !axis.IsLog()) return false; } foreach (var axis in m_YAxises) { - if (axis.show && !axis.IsValue()) return false; + if (axis.show && !axis.IsValue() && !axis.IsLog()) return false; } return true; } diff --git a/Runtime/Component/Main/Axis.cs b/Runtime/Component/Main/Axis.cs index 185996f8..52bfb436 100644 --- a/Runtime/Component/Main/Axis.cs +++ b/Runtime/Component/Main/Axis.cs @@ -27,14 +27,19 @@ namespace XCharts { /// /// 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 设置类目数据。 + /// 类目轴。适用于离散的类目数据,为该类型时必须通过 data 设置类目数据。 /// - Category + Category, + /// + /// Log axis, suitable for log data. + /// 对数轴。适用于对数数据。 + /// + Log } /// @@ -103,6 +108,8 @@ namespace XCharts [SerializeField] protected SplitLineType m_SplitLineType = SplitLineType.Dashed; [SerializeField] protected bool m_BoundaryGap = true; [SerializeField] protected int m_MaxCache = 0; + [SerializeField] protected float m_LogBase = 10; + [SerializeField] protected bool m_LogBaseE = false; [SerializeField] protected List m_Data = new List(); [SerializeField] protected AxisLine m_AxisLine = AxisLine.defaultAxisLine; [SerializeField] protected AxisName m_AxisName = AxisName.defaultAxisName; @@ -164,6 +171,15 @@ namespace XCharts /// public bool boundaryGap { get { return m_BoundaryGap; } set { m_BoundaryGap = value; } } /// + /// Base of logarithm, which is valid only for numeric axes with type: 'Log'. + /// 对数轴的底数,只在对数轴(type:'Log')中有效。 + /// + public float logBase { get { return m_LogBase; } set { m_LogBase = value; } } + /// + /// 对数轴是否以自然数 e 为底数,为 true 时 logBase 失效。 + /// + public bool logBaseE { get { return m_LogBaseE; } set { m_LogBaseE = 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时超过指定值会移除旧数据再插入新数据。 @@ -264,6 +280,8 @@ namespace XCharts /// 坐标轴原点在Y轴的偏移。 /// public float runtimeZeroYOffset { get; internal set; } + public int runtimeMinLogIndex { get { return logBaseE ? (int)Mathf.Log(runtimeMinValue) : (int)Mathf.Log(runtimeMinValue, logBase); } } + public int runtimeMaxLogIndex { get { return logBaseE ? (int)Mathf.Log(runtimeMaxValue) : (int)Mathf.Log(runtimeMaxValue, logBase); } } private int filterStart; private int filterEnd; @@ -312,7 +330,7 @@ namespace XCharts } /// - /// 当前坐标轴是否时类目轴 + /// 是否为类目轴。 /// /// public bool IsCategory() @@ -321,7 +339,7 @@ namespace XCharts } /// - /// 当前坐标轴是否时数值轴 + /// 是否为数值轴。 /// /// public bool IsValue() @@ -329,6 +347,15 @@ namespace XCharts return type == AxisType.Value; } + /// + /// 是否为对数轴。 + /// + /// + public bool IsLog() + { + return type == AxisType.Log; + } + /// /// 添加一个类目到类目数据列表 /// @@ -447,6 +474,10 @@ namespace XCharts } else return m_SplitNumber; } + else if (type == AxisType.Log) + { + return m_SplitNumber; + } int dataCount = GetDataList(dataZoom).Count; if (m_SplitNumber <= 0) return dataCount; if (dataCount > 2 * m_SplitNumber || dataCount <= 0) @@ -506,6 +537,7 @@ namespace XCharts DataZoom dataZoom, bool forcePercent) { int split = GetSplitNumber(coordinateWidth, dataZoom); + if (m_Type == AxisType.Value) { if (minValue == 0 && maxValue == 0) return string.Empty; @@ -523,6 +555,12 @@ namespace XCharts if (forcePercent) return string.Format("{0}%", (int)value); else return m_AxisLabel.GetFormatterContent(value, minValue, maxValue); } + else if (m_Type == AxisType.Log) + { + float value = m_LogBaseE ? Mathf.Exp(runtimeMinLogIndex + index) : + Mathf.Pow(m_LogBase, runtimeMinLogIndex + index); + return m_AxisLabel.GetFormatterContent(value, minValue, maxValue, true); + } var showData = GetDataList(dataZoom); int dataCount = showData.Count; if (dataCount <= 0) return ""; @@ -549,7 +587,7 @@ namespace XCharts /// internal int GetScaleNumber(float coordinateWidth, DataZoom dataZoom) { - if (type == AxisType.Value) + if (type == AxisType.Value || type == AxisType.Log) { int splitNum = GetSplitNumber(coordinateWidth, dataZoom); return m_BoundaryGap ? splitNum + 1 : splitNum; @@ -660,6 +698,15 @@ namespace XCharts /// internal void AdjustMinMaxValue(ref float minValue, ref float maxValue, bool needFormat) { + if (m_Type == AxisType.Log) + { + int minSplit = 0; + int maxSplit = 0; + maxValue = ChartHelper.GetMaxLogValue(maxValue, m_LogBase, m_LogBaseE, out maxSplit); + minValue = ChartHelper.GetMinLogValue(minValue, m_LogBase, m_LogBaseE, out minSplit); + splitNumber = (minSplit > 0 && maxSplit > 0) ? (maxSplit + minSplit - 1) : (maxSplit + minSplit); + return; + } if (minMaxType == Axis.AxisMinMaxType.Custom) { if (min != 0 || max != 0) @@ -749,6 +796,12 @@ namespace XCharts } } + public float GetLogValue(float value) + { + if (value <= 0) return 0; + return logBaseE ? Mathf.Log(value) : Mathf.Log(value, logBase); + } + public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) diff --git a/Runtime/Component/Main/Series.cs b/Runtime/Component/Main/Series.cs index 4489384a..cf1cfe3e 100644 --- a/Runtime/Component/Main/Series.cs +++ b/Runtime/Component/Main/Series.cs @@ -452,7 +452,8 @@ namespace XCharts return false; } - public bool UpdateData(string serieName,int dataIndex,List values){ + public bool UpdateData(string serieName, int dataIndex, List values) + { var serie = GetSerie(serieName); if (serie != null) { @@ -460,7 +461,8 @@ namespace XCharts } return false; } - public bool UpdateData(int serieIndex,int dataIndex,List values){ + public bool UpdateData(int serieIndex, int dataIndex, List values) + { var serie = GetSerie(serieIndex); if (serie != null) { @@ -746,16 +748,8 @@ namespace XCharts } else { - if (max > 1) - { - minVaule = Mathf.FloorToInt(min); - maxValue = Mathf.CeilToInt(max); - } - else - { - minVaule = min; - maxValue = max; - } + minVaule = min > 1 ? Mathf.FloorToInt(min) : min; + maxValue = max > 1 ? Mathf.CeilToInt(max) : max; } } diff --git a/Runtime/Component/Sub/AxisLabel.cs b/Runtime/Component/Sub/AxisLabel.cs index 408f7ffc..e01941ee 100644 --- a/Runtime/Component/Sub/AxisLabel.cs +++ b/Runtime/Component/Sub/AxisLabel.cs @@ -146,10 +146,17 @@ namespace XCharts } } - public string GetFormatterContent(float value, float minValue, float maxValue) + public string GetFormatterContent(float value, float minValue, float maxValue, bool isLog = false) { if (string.IsNullOrEmpty(m_Formatter)) { + if (isLog) + { + if (value - (int)value == 0) + return ChartCached.IntToStr((int)value); + else + return ChartCached.FloatToStr(value); + } if (minValue >= -1 && minValue <= 1 && maxValue >= -1 && maxValue <= 1) { int minAcc = ChartHelper.GetFloatAccuracy(minValue); diff --git a/Runtime/Internal/CoordinateChart_DrawLine.cs b/Runtime/Internal/CoordinateChart_DrawLine.cs index ac836e5e..fc6701f2 100644 --- a/Runtime/Internal/CoordinateChart_DrawLine.cs +++ b/Runtime/Internal/CoordinateChart_DrawLine.cs @@ -310,24 +310,52 @@ namespace XCharts float xMaxValue = xAxis.GetCurrMaxValue(duration); float yMinValue = yAxis.GetCurrMinValue(duration); float yMaxValue = yAxis.GetCurrMaxValue(duration); - if (xAxis.IsValue()) + if (xAxis.IsValue() || xAxis.IsLog()) { float xValue = i > showData.Count - 1 ? 0 : showData[i].data[0]; float pX = coordinateX + xAxis.axisLine.width; float pY = serieHig + coordinateY + xAxis.axisLine.width; - if ((xMaxValue - xMinValue) <= 0) xDataHig = 0; - else xDataHig = (xValue - xMinValue) / (xMaxValue - xMinValue) * coordinateWidth; - if ((yMaxValue - yMinValue) <= 0) yDataHig = 0; - else yDataHig = (yValue - yMinValue) / (yMaxValue - yMinValue) * coordinateHeight; + if (xAxis.IsLog()) + { + int minIndex = xAxis.runtimeMinLogIndex; + float nowIndex = xAxis.GetLogValue(xValue); + xDataHig = (nowIndex - minIndex) / (xAxis.splitNumber - 1) * coordinateWidth; + } + else + { + if ((xMaxValue - xMinValue) <= 0) xDataHig = 0; + else xDataHig = (xValue - xMinValue) / (xMaxValue - xMinValue) * coordinateWidth; + } + if (yAxis.IsLog()) + { + int minIndex = yAxis.runtimeMinLogIndex; + float nowIndex = yAxis.GetLogValue(yValue); + yDataHig = (nowIndex - minIndex) / (yAxis.splitNumber - 1) * coordinateHeight; + } + else + { + if ((yMaxValue - yMinValue) <= 0) yDataHig = 0; + else yDataHig = (yValue - yMinValue) / (yMaxValue - yMinValue) * coordinateHeight; + } np = new Vector3(pX + xDataHig, pY + yDataHig); } else { float pX = startX + i * scaleWid; float pY = serieHig + coordinateY + yAxis.axisLine.width; - if ((yMaxValue - yMinValue) <= 0) yDataHig = 0; - else yDataHig = (yValue - yMinValue) / (yMaxValue - yMinValue) * coordinateHeight; + if (yAxis.IsLog()) + { + int minIndex = yAxis.runtimeMinLogIndex; + float nowIndex = yAxis.GetLogValue(yValue); + yDataHig = (nowIndex - minIndex) / (yAxis.splitNumber - 1) * coordinateHeight; + } + else + { + if ((yMaxValue - yMinValue) <= 0) yDataHig = 0; + else yDataHig = (yValue - yMinValue) / (yMaxValue - yMinValue) * coordinateHeight; + } np = new Vector3(pX, pY + yDataHig); + } return yDataHig; } @@ -381,7 +409,17 @@ namespace XCharts float value = showData[i].GetCurrData(1, updateDuration); float pY = startY + i * scaleWid; float pX = seriesHig[i] + coordinateX + yAxis.axisLine.width; - float dataHig = (value - xMinValue) / (xMaxValue - xMinValue) * coordinateWidth; + float dataHig = 0; + if (xAxis.IsLog()) + { + int minIndex = xAxis.runtimeMinLogIndex; + float nowIndex = xAxis.GetLogValue(value); + dataHig = (nowIndex - minIndex) / (xAxis.splitNumber - 1) * coordinateWidth; + } + else + { + dataHig = (value - xMinValue) / (xMaxValue - xMinValue) * coordinateWidth; + } np = new Vector3(pX + dataHig, pY); serie.dataPoints.Add(np); seriesHig[i] += dataHig; @@ -398,7 +436,17 @@ namespace XCharts float value = showData[i].GetCurrData(1, updateDuration); float pY = startY + i * scaleWid; float pX = seriesHig[i] + coordinateX + yAxis.axisLine.width; - float dataHig = (value - xMinValue) / (xMaxValue - xMinValue) * coordinateWidth; + float dataHig = 0; + if (xAxis.IsLog()) + { + int minIndex = xAxis.runtimeMinLogIndex; + float nowIndex = xAxis.GetLogValue(value); + dataHig = (nowIndex - minIndex) / (xAxis.splitNumber - 1) * coordinateWidth; + } + else + { + dataHig = (value - xMinValue) / (xMaxValue - xMinValue) * coordinateWidth; + } np = new Vector3(pX + dataHig, pY); serie.dataPoints.Add(np); seriesHig[i] += dataHig; diff --git a/Runtime/Utility/ChartHelper.cs b/Runtime/Utility/ChartHelper.cs index 8b8fb886..091260c5 100644 --- a/Runtime/Utility/ChartHelper.cs +++ b/Runtime/Utility/ChartHelper.cs @@ -416,7 +416,6 @@ namespace XCharts } return list; } - } public static List ParseStringFromString(string jsonData) @@ -504,6 +503,46 @@ namespace XCharts else return Mathf.FloorToInt(mm); } + public static float GetMaxLogValue(float value, float logBase, bool isLogBaseE, out int splitNumber) + { + splitNumber = 0; + if (value <= 0) return 0; + float max = 0; + while (max < value) + { + if (isLogBaseE) + { + max = Mathf.Exp(splitNumber); + } + else + { + max = Mathf.Pow(logBase, splitNumber); + } + splitNumber++; + } + return max; + } + + public static float GetMinLogValue(float value, float logBase, bool isLogBaseE, out int splitNumber) + { + splitNumber = 0; + if (value > 1) return 1; + float min = 1; + while (splitNumber < 12 && min > value) + { + if (isLogBaseE) + { + min = Mathf.Exp(-splitNumber); + } + else + { + min = Mathf.Pow(logBase, -splitNumber); + } + splitNumber++; + } + return min; + } + public static int GetFloatAccuracy(float value) { if (value > 1 || value < -1) return 0;