diff --git a/Assets/XCharts/CHANGELOG.md b/Assets/XCharts/CHANGELOG.md index 407116e6..9ebb7c08 100644 --- a/Assets/XCharts/CHANGELOG.md +++ b/Assets/XCharts/CHANGELOG.md @@ -1,6 +1,7 @@ # 更新日志 +* (2020.06.07) 优化`PieChart`在数据全为`0`时的显示为等份的效果 * (2020.06.04) 增加`SerieLabel`的`autoOffset`参数设置是否自动判断上下偏移 * (2020.06.04) 增加`Tooltip`的`alwayShow`参数设置触发后一直显示 * (2020.06.04) 优化`Tooltip`的`formatter`支持`{.1}`通配符 diff --git a/Assets/XCharts/Runtime/Component/Main/Serie.cs b/Assets/XCharts/Runtime/Component/Main/Serie.cs index 2dbc4ce5..87a6a115 100644 --- a/Assets/XCharts/Runtime/Component/Main/Serie.cs +++ b/Assets/XCharts/Runtime/Component/Main/Serie.cs @@ -1326,6 +1326,17 @@ namespace XCharts } } + public float GetDataTotal(int dimension) + { + float total = 0; + foreach (var sdata in data) + { + if (sdata.show) + total += sdata.GetData(dimension); + } + return total; + } + /// /// 获得系列的数据列表 /// diff --git a/Assets/XCharts/Runtime/Helper/FormatterHelper.cs b/Assets/XCharts/Runtime/Helper/FormatterHelper.cs new file mode 100644 index 00000000..a62e7cfa --- /dev/null +++ b/Assets/XCharts/Runtime/Helper/FormatterHelper.cs @@ -0,0 +1,174 @@ +/******************************************/ +/* */ +/* Copyright (c) 2018 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/******************************************/ + +using System.Text; +using System.Text.RegularExpressions; +using UnityEngine; +using System.Linq; + +namespace XCharts +{ + public static class FormatterHelper + { + public const string PH_NN = "\n"; + private static Regex s_Regex = new Regex(@"{([a-d|.]\d*)(:\d+(-\d+)?)?(:[c-g|x|p|r]\d*)?}", RegexOptions.IgnoreCase); + private static Regex s_RegexSub = new Regex(@"(\w?-?\d+)|(\w)|(\.)", RegexOptions.IgnoreCase); + private static Regex s_RegexN = new Regex(@"^\d+", RegexOptions.IgnoreCase); + private static Regex s_RegexN_N = new Regex(@"\d+-\d+", RegexOptions.IgnoreCase); + private static Regex s_RegexFn = new Regex(@"[c-g|x|p|r]\d*", RegexOptions.IgnoreCase); + private static Regex s_RegexNewLine = new Regex(@"[\\|/]+n", RegexOptions.IgnoreCase); + + /// + /// 替换字符串中的通配符,支持的通配符有{.}、{a}、{b}、{c}、{d}。 + /// + /// 要替换的字符串 + /// 选中的数据项serieData索引 + /// 默认的数字格式化 + /// 选中的serie + /// 所有serie + /// 用来获取指定index的颜色 + /// 选中的类目,一般用在折线图和柱状图 + /// dataZoom + /// + public static bool ReplaceContent(ref string content, int dataIndex, string numericFormatter, Serie serie, Series series, + ThemeInfo themeInfo, string category = null, DataZoom dataZoom = null) + { + var foundDot = false; + var mc = s_Regex.Matches(content); + foreach (var m in mc) + { + var old = m.ToString(); + var args = s_RegexSub.Matches(m.ToString()); + var argsCount = args.Count; + if (argsCount <= 0) continue; + int targetIndex = 0; + char p = GetSerieIndex(args[0].ToString(), ref targetIndex); + if (serie == null) + { + if (targetIndex == -1) continue; + serie = series.GetSerie(targetIndex); + if (serie == null) continue; + } + else + { + targetIndex = serie.index; + } + if (p == '.') + { + var bIndex = targetIndex; + if (argsCount >= 2) + { + var args1Str = args[1].ToString(); + if (s_RegexN.IsMatch(args1Str)) bIndex = int.Parse(args1Str); + } + content = content.Replace(old, ChartCached.ColorToDotStr(themeInfo.GetColor(bIndex))); + foundDot = true; + } + else if (p == 'a' || p == 'A') + { + if (argsCount == 1) + { + content = content.Replace(old, serie.name); + } + } + else if (p == 'b' || p == 'B') + { + var bIndex = dataIndex; + if (argsCount >= 2) + { + var args1Str = args[1].ToString(); + if (s_RegexN.IsMatch(args1Str)) bIndex = int.Parse(args1Str); + } + var needCategory = serie.type == SerieType.Line || serie.type == SerieType.Bar; + if (needCategory) + { + content = content.Replace(old, category); + } + else + { + var serieData = serie.GetSerieData(bIndex, dataZoom); + content = content.Replace(old, serieData.name); + } + } + else if (p == 'c' || p == 'C' || p == 'd' || p == 'D') + { + var isPercent = p == 'd' || p == 'D'; + var bIndex = dataIndex; + var dimensionIndex = -1; + if (argsCount >= 2) + { + var args1Str = args[1].ToString(); + if (s_RegexFn.IsMatch(args1Str)) + { + numericFormatter = args1Str; + } + else if (s_RegexN_N.IsMatch(args1Str)) + { + var temp = args1Str.Split('-'); + bIndex = int.Parse(temp[0]); + dimensionIndex = int.Parse(temp[1]); + } + else if (s_RegexN.IsMatch(args1Str)) + { + dimensionIndex = int.Parse(args1Str); + } + else + { + Debug.LogError("unmatch:" + args1Str); + continue; + } + } + if (argsCount >= 3) + { + numericFormatter = args[2].ToString(); + } + if (dimensionIndex == -1) dimensionIndex = 1; + if (numericFormatter == string.Empty) + { + numericFormatter = SerieHelper.GetNumericFormatter(serie, serie.GetSerieData(bIndex)); + } + var value = serie.GetData(bIndex, dimensionIndex, dataZoom); + if (isPercent) + { + var total = serie.GetDataTotal(dimensionIndex); + var percent = total == 0 ? 0 : value / serie.yTotal * 100; + content = content.Replace(old, ChartCached.FloatToStr(percent, numericFormatter)); + } + else + { + content = content.Replace(old, ChartCached.FloatToStr(value, numericFormatter)); + } + } + } + content = s_RegexNewLine.Replace(content, PH_NN); + return foundDot; + } + + private static char GetSerieIndex(string strType, ref int index) + { + index = 0; + if (strType.Length > 1) + { + if (!int.TryParse(strType.Substring(1), out index)) + { + index = -1; + } + } + return strType.ElementAt(0); + } + + public static string TrimAndReplaceLine(StringBuilder sb) + { + return TrimAndReplaceLine(sb.ToString()); + } + + public static string TrimAndReplaceLine(string content) + { + return s_RegexNewLine.Replace(content.Trim(), PH_NN); + } + } +} \ No newline at end of file diff --git a/Assets/XCharts/Runtime/Helper/FormatterHelper.cs.meta b/Assets/XCharts/Runtime/Helper/FormatterHelper.cs.meta new file mode 100644 index 00000000..6c2e5d35 --- /dev/null +++ b/Assets/XCharts/Runtime/Helper/FormatterHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0fddcb81df44148ed86496564b120261 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/XCharts/Runtime/Internal/Helper/SerieHelper.cs b/Assets/XCharts/Runtime/Internal/Helper/SerieHelper.cs index c5698961..2f99460f 100644 --- a/Assets/XCharts/Runtime/Internal/Helper/SerieHelper.cs +++ b/Assets/XCharts/Runtime/Internal/Helper/SerieHelper.cs @@ -306,5 +306,21 @@ namespace XCharts serie.runtimeDataMin = ChartHelper.GetMinDivisibleValue(min, ceilRate); serie.runtimeDataMax = ChartHelper.GetMaxDivisibleValue(max, ceilRate); } + + public static bool IsAllZeroValue(Serie serie, int dimension = 1) + { + foreach (var serieData in serie.data) + { + if (serieData.GetData(dimension) != 0) return false; + } + return true; + } + + public static string GetNumericFormatter(Serie serie, SerieData serieData) + { + var itemStyle = SerieHelper.GetItemStyle(serie, serieData); + if (!string.IsNullOrEmpty(itemStyle.numericFormatter)) return itemStyle.numericFormatter; + else return string.Empty; + } } } \ No newline at end of file diff --git a/Assets/XCharts/Runtime/Internal/Helper/SerieLabelHelper.cs b/Assets/XCharts/Runtime/Internal/Helper/SerieLabelHelper.cs index 908c869d..ff7bb4b3 100644 --- a/Assets/XCharts/Runtime/Internal/Helper/SerieLabelHelper.cs +++ b/Assets/XCharts/Runtime/Internal/Helper/SerieLabelHelper.cs @@ -105,14 +105,11 @@ namespace XCharts content = content.Replace("{c:f0}", ChartCached.IntToStr((int)Mathf.Round(dataValue))); content = content.Replace("{c:f1}", ChartCached.FloatToStr(dataValue, string.Empty, 1)); content = content.Replace("{c:f2}", ChartCached.FloatToStr(dataValue, string.Empty, 2)); - if (dataTotal > 0) - { - var percent = dataValue / dataTotal * 100; - content = content.Replace("{d}", ChartCached.NumberToStr(percent, numericFormatter)); - content = content.Replace("{d:f0}", ChartCached.IntToStr((int)Mathf.Round(percent))); - content = content.Replace("{d:f1}", ChartCached.FloatToStr(percent, string.Empty, 1)); - content = content.Replace("{d:f2}", ChartCached.FloatToStr(percent, string.Empty, 2)); - } + var percent = dataValue == 0 && dataTotal == 0 ? 0 : dataValue / dataTotal * 100; + content = content.Replace("{d}", ChartCached.NumberToStr(percent, numericFormatter)); + content = content.Replace("{d:f0}", ChartCached.IntToStr((int)Mathf.Round(percent))); + content = content.Replace("{d:f1}", ChartCached.FloatToStr(percent, string.Empty, 1)); + content = content.Replace("{d:f2}", ChartCached.FloatToStr(percent, string.Empty, 2)); content = content.Replace("\\n", "\n"); content = content.Replace("
", "\n"); return content; diff --git a/Assets/XCharts/Runtime/Internal/Helper/TooltipHelper.cs b/Assets/XCharts/Runtime/Internal/Helper/TooltipHelper.cs index 28e90ffa..79767af3 100644 --- a/Assets/XCharts/Runtime/Internal/Helper/TooltipHelper.cs +++ b/Assets/XCharts/Runtime/Internal/Helper/TooltipHelper.cs @@ -4,23 +4,14 @@ /* https://github.com/monitor1394 */ /* */ /******************************************/ + using System.Text; -using System.Text.RegularExpressions; using UnityEngine; -using System.Linq; namespace XCharts { internal static class TooltipHelper { - private const string PH_NN = "\n"; - private static Regex s_Regex = new Regex(@"{([a-d|.]\d*)(:\d+(-\d+)?)?(:[c-g|x|p|r]\d*)?}", RegexOptions.IgnoreCase); - private static Regex s_RegexSub = new Regex(@"(\w?-?\d+)|(\w)|(\.)", RegexOptions.IgnoreCase); - private static Regex s_RegexN = new Regex(@"^\d+", RegexOptions.IgnoreCase); - private static Regex s_RegexN_N = new Regex(@"\d+-\d+", RegexOptions.IgnoreCase); - private static Regex s_RegexFn = new Regex(@"[c-g|x|p|r]\d*", RegexOptions.IgnoreCase); - private static Regex s_RegexNewLine = new Regex(@"[\\|/]+n", RegexOptions.IgnoreCase); - private static void InitScatterTooltip(ref StringBuilder sb, Tooltip tooltip, Serie serie, int index, ThemeInfo themeInfo) { @@ -28,7 +19,7 @@ namespace XCharts var dataIndexList = tooltip.runtimeSerieDataIndex[serie.index]; if (!string.IsNullOrEmpty(serie.name)) { - sb.Append(serie.name).Append(PH_NN); + sb.Append(serie.name).Append(FormatterHelper.PH_NN); } for (int i = 0; i < dataIndexList.Count; i++) { @@ -62,7 +53,7 @@ namespace XCharts sb.Length = 0; if (!string.IsNullOrEmpty(serie.name)) { - sb.Append(serie.name).Append(PH_NN); + sb.Append(serie.name).Append(FormatterHelper.PH_NN); } sb.Append("● "); if (!string.IsNullOrEmpty(key)) @@ -102,7 +93,7 @@ namespace XCharts { string key = radar.indicatorList[i].name; float value = serieData.GetData(i); - if ((i == 0 && !string.IsNullOrEmpty(serieData.name)) || i > 0) sb.Append(PH_NN); + if ((i == 0 && !string.IsNullOrEmpty(serieData.name)) || i > 0) sb.Append(FormatterHelper.PH_NN); sb.AppendFormat("{0}: {1}", key, ChartCached.FloatToStr(value, numericFormatter)); } break; @@ -213,27 +204,27 @@ namespace XCharts var numericFormatter = GetItemNumericFormatter(tooltip, serie, null); if (string.IsNullOrEmpty(itemFormatter)) { - if (!first) sb.Append(PH_NN); - InitDefaultContent(ref sb, tooltip, serie, dataIndex, category, themeInfo, dataZoom, isCartesian); + if (!first) sb.Append(FormatterHelper.PH_NN); + InitDefaultContent(ref sb, tooltip, serie, dataIndex, category, themeInfo, dataZoom); first = false; continue; } var itemTitle = title; if (!string.IsNullOrEmpty(itemTitle)) { - ReplaceContent(ref itemTitle, dataIndex, tooltip, serie, null, themeInfo, category, dataZoom, isCartesian); - sb.Append(itemTitle).Append(PH_NN); + FormatterHelper.ReplaceContent(ref itemTitle, dataIndex, tooltip.numericFormatter, serie, null, themeInfo, category, dataZoom); + sb.Append(itemTitle).Append(FormatterHelper.PH_NN); } var dataIndexList = tooltip.runtimeSerieDataIndex[serie.index]; foreach (var tempIndex in dataIndexList) { string content = itemFormatter; - var foundDot = ReplaceContent(ref content, tempIndex, tooltip, serie, null, themeInfo, category, dataZoom, isCartesian); + var foundDot = FormatterHelper.ReplaceContent(ref content, tempIndex, tooltip.numericFormatter, serie, null, themeInfo, category, dataZoom); if (!foundDot) { sb.Append(ChartCached.ColorToDotStr(themeInfo.GetColor(serie.index))); } - sb.Append(content).Append(PH_NN); + sb.Append(content).Append(FormatterHelper.PH_NN); } } } @@ -245,20 +236,20 @@ namespace XCharts needCategory = needCategory || (serie.type == SerieType.Line || serie.type == SerieType.Bar); if (formatTitle) { - ReplaceContent(ref title, dataIndex, tooltip, null, series, themeInfo, category, dataZoom, isCartesian); + FormatterHelper.ReplaceContent(ref title, dataIndex, tooltip.numericFormatter, null, series, themeInfo, category, dataZoom); } if (serie.show) { if (string.IsNullOrEmpty(itemFormatter)) { - if (!first) sb.Append(PH_NN); - InitDefaultContent(ref sb, tooltip, serie, dataIndex, category, themeInfo, dataZoom, isCartesian); + if (!first) sb.Append(FormatterHelper.PH_NN); + InitDefaultContent(ref sb, tooltip, serie, dataIndex, category, themeInfo, dataZoom); first = false; continue; } string content = itemFormatter; - ReplaceContent(ref content, dataIndex, tooltip, serie, null, themeInfo, category, dataZoom, isCartesian); - if (!first) sb.Append(PH_NN); + FormatterHelper.ReplaceContent(ref content, dataIndex, tooltip.numericFormatter, serie, null, themeInfo, category, dataZoom); + if (!first) sb.Append(FormatterHelper.PH_NN); sb.Append(ChartCached.ColorToDotStr(themeInfo.GetColor(i))); sb.Append(content); first = false; @@ -267,159 +258,27 @@ namespace XCharts } if (isScatter) { - return TrimAndReplaceLine(sb); + return FormatterHelper.TrimAndReplaceLine(sb); } else if (string.IsNullOrEmpty(title)) { - if (needCategory) return category + PH_NN + TrimAndReplaceLine(sb); - else return TrimAndReplaceLine(sb); + if (needCategory) return category + FormatterHelper.PH_NN + FormatterHelper.TrimAndReplaceLine(sb); + else return FormatterHelper.TrimAndReplaceLine(sb); } else { - title = s_RegexNewLine.Replace(title, PH_NN); - return title + PH_NN + TrimAndReplaceLine(sb); + title = FormatterHelper.TrimAndReplaceLine(title); + return title + FormatterHelper.PH_NN + FormatterHelper.TrimAndReplaceLine(sb); } } else { string content = tooltip.formatter; - ReplaceContent(ref content, dataIndex, tooltip, null, series, themeInfo, category, dataZoom, isCartesian); + FormatterHelper.ReplaceContent(ref content, dataIndex, tooltip.numericFormatter, null, series, themeInfo, category, dataZoom); return content; } } - public static bool ReplaceContent(ref string content, int dataIndex, Tooltip tooltip, Serie serie, Series series, - ThemeInfo themeInfo, string category = null, DataZoom dataZoom = null, bool isCartesian = false) - { - var foundDot = false; - var mc = s_Regex.Matches(content); - foreach (var m in mc) - { - var old = m.ToString(); - var args = s_RegexSub.Matches(m.ToString()); - var argsCount = args.Count; - if (argsCount <= 0) continue; - int targetIndex = 0; - char p = GetSerieIndex(args[0].ToString(), ref targetIndex); - if (serie == null) - { - if (targetIndex == -1) continue; - serie = series.GetSerie(targetIndex); - if (serie == null) continue; - } - else - { - targetIndex = serie.index; - } - if (p == '.') - { - var bIndex = targetIndex; - if (argsCount >= 2) - { - var args1Str = args[1].ToString(); - if (s_RegexN.IsMatch(args1Str)) bIndex = int.Parse(args1Str); - } - content = content.Replace(old, ChartCached.ColorToDotStr(themeInfo.GetColor(bIndex))); - foundDot = true; - } - else if (p == 'a' || p == 'A') - { - if (argsCount == 1) - { - content = content.Replace(old, serie.name); - } - } - else if (p == 'b' || p == 'B') - { - var bIndex = dataIndex; - if (argsCount >= 2) - { - var args1Str = args[1].ToString(); - if (s_RegexN.IsMatch(args1Str)) bIndex = int.Parse(args1Str); - } - var needCategory = serie.type == SerieType.Line || serie.type == SerieType.Bar; - if (needCategory) - { - content = content.Replace(old, category); - } - else - { - var serieData = serie.GetSerieData(bIndex, dataZoom); - content = content.Replace(old, serieData.name); - } - } - else if (p == 'c' || p == 'C' || p == 'd' || p == 'D') - { - var isPercent = p == 'd' || p == 'D'; - var bIndex = dataIndex; - var dimensionIndex = -1; - var numericFormatter = string.Empty; - if (argsCount >= 2) - { - var args1Str = args[1].ToString(); - if (s_RegexFn.IsMatch(args1Str)) - { - numericFormatter = args1Str; - } - else if (s_RegexN_N.IsMatch(args1Str)) - { - var temp = args1Str.Split('-'); - bIndex = int.Parse(temp[0]); - dimensionIndex = int.Parse(temp[1]); - } - else if (s_RegexN.IsMatch(args1Str)) - { - dimensionIndex = int.Parse(args1Str); - } - else - { - Debug.LogError("unmatch:" + args1Str); - continue; - } - } - if (argsCount >= 3) - { - numericFormatter = args[2].ToString(); - } - if (dimensionIndex == -1) dimensionIndex = 1; - if (numericFormatter == string.Empty) - { - numericFormatter = GetItemNumericFormatter(tooltip, serie, serie.GetSerieData(bIndex)); - } - var value = serie.GetData(bIndex, dimensionIndex, dataZoom); - if (isPercent) - { - var percent = value / serie.yTotal * 100; - content = content.Replace(old, ChartCached.FloatToStr(percent, numericFormatter)); - } - else - { - content = content.Replace(old, ChartCached.FloatToStr(value, numericFormatter)); - } - } - } - content = s_RegexNewLine.Replace(content, PH_NN); - return foundDot; - } - - private static char GetSerieIndex(string strType, ref int index) - { - index = 0; - if (strType.Length > 1) - { - if (!int.TryParse(strType.Substring(1), out index)) - { - index = -1; - } - } - return strType.ElementAt(0); - } - - private static string TrimAndReplaceLine(StringBuilder sb) - { - return s_RegexNewLine.Replace(sb.ToString().Trim(), PH_NN); - } - private static bool IsSelectedSerie(Tooltip tooltip, int serieIndex) { if (tooltip.runtimeSerieDataIndex.ContainsKey(serieIndex)) diff --git a/Assets/XCharts/Runtime/PieChart.cs b/Assets/XCharts/Runtime/PieChart.cs index 4a6d9714..0db31f2e 100644 --- a/Assets/XCharts/Runtime/PieChart.cs +++ b/Assets/XCharts/Runtime/PieChart.cs @@ -79,12 +79,19 @@ namespace XCharts } bool dataChanging = false; float dataChangeDuration = serie.animation.GetUpdateAnimationDuration(); + bool isAllZeroValue = SerieHelper.IsAllZeroValue(serie, 1); + float zeroReplaceValue = 360f / data.Count; + if (isAllZeroValue) + { + serie.runtimeDataMax = zeroReplaceValue; + serie.runtimePieDataTotal = 360f; + } for (int n = 0; n < data.Count; n++) { var serieData = data[n]; var itemStyle = SerieHelper.GetItemStyle(serie, serieData, serieData.highlighted); serieData.index = n; - float value = serieData.GetCurrData(1, dataChangeDuration); + float value = isAllZeroValue ? zeroReplaceValue : serieData.GetCurrData(1, dataChangeDuration); if (serieData.IsDataChanged()) dataChanging = true; serieNameCount = m_LegendRealShowName.IndexOf(serieData.legendName); var color = SerieHelper.GetItemColor(serie, serieData, m_ThemeInfo, serieNameCount, serieData.highlighted); @@ -152,7 +159,7 @@ namespace XCharts serieData.runtimePieOutsideRadius, color, toColor, Color.clear, startDegree, drawEndDegree, borderWidth, borderColor, serie.pieSpace / 2, m_Settings.cicleSmoothness, needRoundCap, serie.clockwise); } - else //if(n==0) + else { var drawEndDegree = serieData.runtimePieCurrAngle; var needRoundCap = serie.roundCap && serieData.runtimePieInsideRadius > 0; @@ -331,14 +338,6 @@ namespace XCharts if (!serie.show) continue; var data = serie.data; - int showdataCount = 0; - if (serie.pieRoseType == RoseType.Area) - { - foreach (var sd in serie.data) - { - if (sd.show) showdataCount++; - } - } for (int n = 0; n < data.Count; n++) { var serieData = data[n];