diff --git a/CHANGELOG-EN.md b/CHANGELOG-EN.md index f257937a..6d918c6f 100644 --- a/CHANGELOG-EN.md +++ b/CHANGELOG-EN.md @@ -32,6 +32,7 @@ ## Latest +* (2021.03.25) Added `Ganttchart` * (2021.03.22) Added `Theme` `Unbind` button to unbind theme when copying chart #118 * (2021.03.18) Fixed an issue where the check box after `Foldout` in `Inspector` could not be checked * (2021.03.18) Fixed an issue with `BarChart` displaying an exception in the `0` value diff --git a/CHANGELOG.md b/CHANGELOG.md index 095c5502..7d44c99c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ ## Latest +* (2021.03.25) 增加`GanttChart`甘特图 * (2021.03.22) 增加`Theme`的`Unbind`按钮用于解绑复制图表时的主题 #118 * (2021.03.18) 修复`Inspector`下`Foldout`后的勾选框无法选中的问题 * (2021.03.18) 修复`BarChart`在`0`数值时显示异常的问题 diff --git a/Documentation/XCharts配置项手册.md b/Documentation/XCharts配置项手册.md index 70cfeb9f..1ec65948 100644 --- a/Documentation/XCharts配置项手册.md +++ b/Documentation/XCharts配置项手册.md @@ -24,6 +24,7 @@ * [Serie-Ring 环形图](#Serie-Ring) * [Serie-Liquid 水位图](#Serie-Liquid) * [Serie-Candlestick K线图](#Serie-Candlestick) +* [Serie-Gantt 甘特图](#Serie-Gantt) * [Settings 设置](#Settings) * [Theme 主题](#Theme) * [Tooltip 提示框](#Tooltip) @@ -774,6 +775,24 @@ K线图系列。 * `animation`:起始动画 [SerieAnimation](#SerieAnimation)。 * `data`:系列中的数据项 [SerieData](#SerieData) 数组,K线图至少需要4个维度的数组`[open, close, lowest, highest]`。 +## `Serie-Gantt` + +甘特图系列。支持类目轴和时间轴的甘特图,当 `X` 轴为类目轴时,数据为类目的索引,`X` 轴为时间轴时,数据为时间戳(秒为单位)。`Y` 轴默认为类目轴,显示的数据来源于`Serie`的`Data`的`Name`。 +甘特图默认支持开始和结束时间,也可以额外支持实际开始和结束时间。 + +* `show`:系列是否显示在图表上。 +* `type`:`Gantt`。 +* `name`:系列名称。用于 `tooltip` 的显示,`legend` 的图例筛选。 +* `xAxisIndex`:使用的坐标轴X轴的 `index`,在单个图表实例中存在多个坐标轴的时候有用。 +* `yAxisIndex`:使用的坐标轴Y轴的 `index`,在单个图表实例中存在多个坐标轴的时候有用。 +* `clip`:是否裁剪超出坐标系部分的图形。 +* `large`:是否开启大数据量优化,在数据图形特别多而出现卡顿时候可以开启。开启后配合 largeThreshold 在数据量大于指定阈值的时候对绘制进行优化。缺点:优化后不能自定义设置单个数据项的样式,不能显示Label,折线图不绘制Symbol。 +* `largeThreshold`:开启大数量优化的阈值。只有当开启了large并且数据量大于该阀值时才进入性能模式。 +* `itemStyle`:甘特图的柱条样式,包括设置背景颜色和边框等 [ItemStyle](#ItemStyle)。 +* `emphasis`:高亮样式 [Emphasis](#Emphasis)。 +* `animation`:起始动画 [SerieAnimation](#SerieAnimation)。 +* `data`:系列中的数据项 [SerieData](#SerieData) 数组,甘特图至少需要2个维度的数组`[start, end]`,也支持4个维度的数组`[start, end, actualStart, actualEnd]`。当 X 轴为类目轴时,数据为类目的索引,X 轴为时间轴时,数据为时间戳(秒为单位)。 + ## `Settings` 全局参数设置组件。一般情况下可使用默认值,当有需要时可进行调整。 diff --git a/Documentation/xcharts-configuration-EN.md b/Documentation/xcharts-configuration-EN.md index 2d89fc0e..e24991ee 100644 --- a/Documentation/xcharts-configuration-EN.md +++ b/Documentation/xcharts-configuration-EN.md @@ -26,6 +26,7 @@ __Main component:__ * [Serie-Ring](#Serie-Ring) * [Serie-Liquid](#Serie-Liquid) * [Serie-Candlestick](#Serie-Candlestick) +* [Serie-Gantt](#Serie-Gantt) * [Settings](#Settings) * [Theme](#Theme) * [Title](#Title) @@ -667,6 +668,24 @@ K线图系列。 * `animation`:起始动画 [SerieAnimation](#SerieAnimation)。 * `data`:系列中的数据项 [SerieData](#SerieData) 数组,K线图至少需要4个维度的数组`[open, close, lowest, highest]`。 +## `Serie-Gantt` + +甘特图系列。支持类目轴和时间轴的甘特图,当 `X` 轴为类目轴时,数据为类目的索引,`X` 轴为时间轴时,数据为时间戳(秒为单位)。`Y` 轴默认为类目轴,显示的数据来源于`Serie`的`Data`的`Name`。 +甘特图默认支持开始和结束时间,也可以额外支持实际开始和结束时间。 + +* `show`:系列是否显示在图表上。 +* `type`:`Gantt`。 +* `name`:系列名称。用于 `tooltip` 的显示,`legend` 的图例筛选。 +* `xAxisIndex`:使用的坐标轴X轴的 `index`,在单个图表实例中存在多个坐标轴的时候有用。 +* `yAxisIndex`:使用的坐标轴Y轴的 `index`,在单个图表实例中存在多个坐标轴的时候有用。 +* `clip`:是否裁剪超出坐标系部分的图形。 +* `large`:是否开启大数据量优化,在数据图形特别多而出现卡顿时候可以开启。开启后配合 largeThreshold 在数据量大于指定阈值的时候对绘制进行优化。缺点:优化后不能自定义设置单个数据项的样式,不能显示Label,折线图不绘制Symbol。 +* `largeThreshold`:开启大数量优化的阈值。只有当开启了large并且数据量大于该阀值时才进入性能模式。 +* `itemStyle`:甘特图的柱条样式,包括设置背景颜色和边框等 [ItemStyle](#ItemStyle)。 +* `emphasis`:高亮样式 [Emphasis](#Emphasis)。 +* `animation`:起始动画 [SerieAnimation](#SerieAnimation)。 +* `data`:系列中的数据项 [SerieData](#SerieData) 数组,甘特图至少需要2个维度的数组`[start, end]`,也支持4个维度的数组`[start, end, actualStart, actualEnd]`。当 X 轴为类目轴时,数据为类目的索引,X 轴为时间轴时,数据为时间戳(秒为单位)。 + ## `Settings` 全局参数设置组件。一般情况下可使用默认值,当有需要时可进行调整。 diff --git a/Editor/BarChartEditor.cs b/Editor/BarChartEditor.cs index 96435653..db83e0eb 100644 --- a/Editor/BarChartEditor.cs +++ b/Editor/BarChartEditor.cs @@ -19,6 +19,7 @@ namespace XCharts protected override void OnEnable() { base.OnEnable(); + if(target == null) return; m_Chart = (BarChart)target; } diff --git a/Editor/BaseChartEditor.cs b/Editor/BaseChartEditor.cs index f6904a12..5ca8f65c 100644 --- a/Editor/BaseChartEditor.cs +++ b/Editor/BaseChartEditor.cs @@ -123,7 +123,7 @@ namespace XCharts BlockEnd(); BlockStart(); - m_BaseFoldout = EditorGUILayout.Foldout(m_BaseFoldout, "Base"); + m_BaseFoldout = EditorGUILayout.Foldout(m_BaseFoldout, "Base", true); if (m_BaseFoldout) { EditorGUILayout.PropertyField(m_Script); @@ -196,7 +196,7 @@ namespace XCharts if (all) { var flag = m_Flodouts.ContainsKey(prop.displayName) && m_Flodouts[prop.displayName]; - m_Flodouts[prop.displayName] = EditorGUILayout.Foldout(flag, prop.displayName); + m_Flodouts[prop.displayName] = EditorGUILayout.Foldout(flag, prop.displayName, true); if (m_Flodouts[prop.displayName]) { EditorGUI.indentLevel++; diff --git a/Editor/CandlestickChartEditor.cs b/Editor/CandlestickChartEditor.cs index 6d3c047f..52a04dd7 100644 --- a/Editor/CandlestickChartEditor.cs +++ b/Editor/CandlestickChartEditor.cs @@ -18,6 +18,7 @@ namespace XCharts protected override void OnEnable() { base.OnEnable(); + if(target == null) return; m_Chart = (CandlestickChart)target; } } diff --git a/Editor/CoordinateChartEditor.cs b/Editor/CoordinateChartEditor.cs index 437b5f2e..deffe8b7 100644 --- a/Editor/CoordinateChartEditor.cs +++ b/Editor/CoordinateChartEditor.cs @@ -27,6 +27,7 @@ namespace XCharts protected override void OnEnable() { base.OnEnable(); + if(target == null) return; m_Chart = (CoordinateChart)target; m_Grids = serializedObject.FindProperty("m_Grids"); m_XAxes = serializedObject.FindProperty("m_XAxes"); diff --git a/Editor/GanttChartEditor.cs b/Editor/GanttChartEditor.cs new file mode 100644 index 00000000..d1a33604 --- /dev/null +++ b/Editor/GanttChartEditor.cs @@ -0,0 +1,25 @@ +/************************************************/ +/* */ +/* Copyright (c) 2018 - 2021 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/************************************************/ + +using UnityEditor; + +namespace XCharts +{ + /// + /// Editor class used to edit UI GanttChart. + /// + [CustomEditor(typeof(GanttChart), false)] + public class GanttChartEditor : CoordinateChartEditor + { + protected override void OnEnable() + { + base.OnEnable(); + if(target == null) return; + m_Chart = (GanttChart)target; + } + } +} \ No newline at end of file diff --git a/Editor/GanttChartEditor.cs.meta b/Editor/GanttChartEditor.cs.meta new file mode 100644 index 00000000..336d56c4 --- /dev/null +++ b/Editor/GanttChartEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c1b5a1dfca8e5476b98c807c66783d85 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/GaugeChartEditor.cs b/Editor/GaugeChartEditor.cs index a89de203..0d9d6d42 100644 --- a/Editor/GaugeChartEditor.cs +++ b/Editor/GaugeChartEditor.cs @@ -18,6 +18,7 @@ namespace XCharts protected override void OnEnable() { base.OnEnable(); + if(target == null) return; m_Chart = (GaugeChart)target; } } diff --git a/Editor/HeatmapChartEditor.cs b/Editor/HeatmapChartEditor.cs index b1e81067..ed765800 100644 --- a/Editor/HeatmapChartEditor.cs +++ b/Editor/HeatmapChartEditor.cs @@ -18,6 +18,7 @@ namespace XCharts protected override void OnEnable() { base.OnEnable(); + if(target == null) return; m_Chart = (HeatmapChart)target; } } diff --git a/Editor/LineChartEditor.cs b/Editor/LineChartEditor.cs index d321bb74..70f2d635 100644 --- a/Editor/LineChartEditor.cs +++ b/Editor/LineChartEditor.cs @@ -18,6 +18,7 @@ namespace XCharts protected override void OnEnable() { base.OnEnable(); + if(target == null) return; m_Chart = (LineChart)target; } } diff --git a/Editor/PieChartEditor.cs b/Editor/PieChartEditor.cs index d410a4b7..1fc0f6f5 100644 --- a/Editor/PieChartEditor.cs +++ b/Editor/PieChartEditor.cs @@ -18,6 +18,7 @@ namespace XCharts protected override void OnEnable() { base.OnEnable(); + if(target == null) return; m_Chart = (PieChart)target; } } diff --git a/Editor/PolarChartEditor.cs b/Editor/PolarChartEditor.cs index 57b3b476..b1f02ced 100644 --- a/Editor/PolarChartEditor.cs +++ b/Editor/PolarChartEditor.cs @@ -22,6 +22,7 @@ namespace XCharts protected override void OnEnable() { base.OnEnable(); + if(target == null) return; m_Chart = (PolarChart)target; m_Polars = serializedObject.FindProperty("m_Polars"); m_RadiusAxes = serializedObject.FindProperty("m_RadiusAxes"); diff --git a/Editor/PropertyDrawers/AxisDrawer.cs b/Editor/PropertyDrawers/AxisDrawer.cs index c93aa7a5..a9f29c5c 100644 --- a/Editor/PropertyDrawers/AxisDrawer.cs +++ b/Editor/PropertyDrawers/AxisDrawer.cs @@ -42,7 +42,7 @@ namespace XCharts } EditorGUI.EndChangeCheck(); } - if (type == Axis.AxisType.Value) + if (type == Axis.AxisType.Value || type == Axis.AxisType.Time) { PropertyField(prop, "m_MinMaxType"); Axis.AxisMinMaxType minMaxType = (Axis.AxisMinMaxType)m_MinMaxType.enumValueIndex; @@ -60,7 +60,10 @@ namespace XCharts break; } PropertyField(prop, "m_CeilRate"); - PropertyField(prop, "m_Inverse"); + if (type == Axis.AxisType.Value) + { + PropertyField(prop, "m_Inverse"); + } } PropertyField(prop, "m_SplitNumber"); if (type == Axis.AxisType.Category) diff --git a/Editor/PropertyDrawers/SerieDrawer.cs b/Editor/PropertyDrawers/SerieDrawer.cs index cee241b9..cd96e351 100644 --- a/Editor/PropertyDrawers/SerieDrawer.cs +++ b/Editor/PropertyDrawers/SerieDrawer.cs @@ -197,6 +197,17 @@ namespace XCharts PropertyField(prop, "m_Label"); PropertyField(prop, "m_Emphasis"); break; + case SerieType.Gantt: + PropertyField(prop, "m_XAxisIndex"); + PropertyField(prop, "m_YAxisIndex"); + PropertyField(prop, "m_BarWidth"); + PropertyField(prop, "m_Clip"); + PropertyField(prop, "m_Large"); + PropertyField(prop, "m_LargeThreshold"); + PropertyField(prop, "m_ItemStyle"); + PropertyField(prop, "m_Label"); + PropertyField(prop, "m_Emphasis"); + break; } PropertyField(prop, "m_Animation"); DrawData(pos, prop, serieType, ref m_DrawRect); @@ -340,7 +351,7 @@ namespace XCharts var startX = drawRect.x + EditorGUIUtility.labelWidth - EditorGUI.indentLevel * 15 + gap; var dataWidTotal = (currentWidth - (startX + 20.5f + 1)); var dataWid = dataWidTotal / fieldCount; - var xWid = dataWid - 4; + var xWid = dataWid - 2; for (int i = 0; i < dimension; i++) { var dataCount = i < 1 ? 2 : i + 1; @@ -407,7 +418,7 @@ namespace XCharts var str = prop.propertyPath.Substring(sindex + 1, eindex - sindex - 1); int.TryParse(str, out index); } - if (index >= m_DataFoldout.Count) + while (index >= m_DataFoldout.Count) { m_DataFoldout.Add(false); } diff --git a/Editor/RingChartEditor.cs b/Editor/RingChartEditor.cs index cf054b61..2c76dd68 100644 --- a/Editor/RingChartEditor.cs +++ b/Editor/RingChartEditor.cs @@ -18,6 +18,7 @@ namespace XCharts protected override void OnEnable() { base.OnEnable(); + if(target == null) return; m_Chart = (RingChart)target; } } diff --git a/Editor/ScatterChartEditor.cs b/Editor/ScatterChartEditor.cs index e6c0494b..1453d79c 100644 --- a/Editor/ScatterChartEditor.cs +++ b/Editor/ScatterChartEditor.cs @@ -18,6 +18,7 @@ namespace XCharts protected override void OnEnable() { base.OnEnable(); + if(target == null) return; m_Chart = (ScatterChart)target; } } diff --git a/Editor/XChartEditor.cs b/Editor/XChartEditor.cs index e7364e1a..884f4b06 100644 --- a/Editor/XChartEditor.cs +++ b/Editor/XChartEditor.cs @@ -146,11 +146,11 @@ namespace XCharts AddChart("LiquidChart"); } - [MenuItem("XCharts/CandlestickChart", priority = 54)] - [MenuItem("GameObject/XCharts/CandlestickChart", priority = 54)] - public static void AddCandlestickChart() + [MenuItem("XCharts/GanttChart", priority = 54)] + [MenuItem("GameObject/XCharts/GanttChart", priority = 54)] + public static void AddGanttChart() { - AddChart("CandlestickChart"); + AddChart("GanttChart"); } [MenuItem("XCharts/Themes Reload")] diff --git a/Examples/Runtime/Example100_Gantt_Category.cs b/Examples/Runtime/Example100_Gantt_Category.cs new file mode 100644 index 00000000..6b5d666b --- /dev/null +++ b/Examples/Runtime/Example100_Gantt_Category.cs @@ -0,0 +1,80 @@ +/************************************************/ +/* */ +/* Copyright (c) 2018 - 2021 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/************************************************/ + + +using UnityEngine; + +namespace XCharts.Examples +{ + [DisallowMultipleComponent] + [ExecuteInEditMode] + public class Example100_Gantt_Category : MonoBehaviour + { + private GanttChart chart; + private float updateTime; + public int dayCount = 10; + public int taskCount = 5; + + void Awake() + { + chart = gameObject.GetComponent(); + if (chart == null) + { + chart = gameObject.AddComponent(); + } + GenerateCategoryData(); + } + + void Update() + { + if (Input.GetKeyDown(KeyCode.Space)) + { + AddData(); + } + } + + void AddData() + { + for (int i = 0; i < taskCount; i++) + { + var taskName = "task-" + (i + 1); + var startIndex = Random.Range(0, (int)(dayCount * 2.0f / 3)); + var endIndex = Random.Range(startIndex, dayCount); + chart.UpdateData(0, i, 0, startIndex); + chart.UpdateData(0, i, 1, endIndex); + } + } + + void GenerateCategoryData() + { + chart.RemoveData(); + + chart.grid.left = 100; + chart.xAxis0.type = Axis.AxisType.Category; + chart.xAxis0.boundaryGap = false; + chart.xAxis0.splitNumber = dayCount; + + chart.yAxis0.type = Axis.AxisType.Category; + chart.yAxis0.boundaryGap = true; + chart.yAxis0.splitNumber = 0; + + for (int i = 0; i < dayCount; i++) + { + chart.AddXAxisData("day" + (i + 1)); + } + + var serie = chart.AddSerie(SerieType.Gantt, "任务进度表"); + for (int i = 0; i < taskCount; i++) + { + var taskName = "task-" + (i + 1); + var startIndex = Random.Range(0, (int)(dayCount * 2.0f / 3)); + var endIndex = Random.Range(startIndex, dayCount); + chart.AddData(0, startIndex, endIndex, taskName); + } + } + } +} \ No newline at end of file diff --git a/Examples/Runtime/Example100_Gantt_Category.cs.meta b/Examples/Runtime/Example100_Gantt_Category.cs.meta new file mode 100644 index 00000000..1c2b67ac --- /dev/null +++ b/Examples/Runtime/Example100_Gantt_Category.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c383c3eae67ed461693e18a807b2e599 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/Runtime/Example101_Gantt_Time.cs b/Examples/Runtime/Example101_Gantt_Time.cs new file mode 100644 index 00000000..ebe47aa8 --- /dev/null +++ b/Examples/Runtime/Example101_Gantt_Time.cs @@ -0,0 +1,99 @@ +/************************************************/ +/* */ +/* Copyright (c) 2018 - 2021 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/************************************************/ + + +using UnityEngine; + +namespace XCharts.Examples +{ + [DisallowMultipleComponent] + [ExecuteInEditMode] + public class Example101_Gantt_Time : MonoBehaviour + { + private GanttChart chart; + private float updateTime; + public int taskCount = 5; + + void Awake() + { + chart = gameObject.GetComponent(); + if (chart == null) + { + chart = gameObject.AddComponent(); + } + GenerateTimeData(); + } + + void Update() + { + if (Input.GetKeyDown(KeyCode.Space)) + { + AddData(); + } + } + + void AddData() + { + chart.ClearData(); + for (int i = 0; i < taskCount; i++) + { + var taskName = "张三-任务-" + (i + 1); + var nowTimestamp = DateTimeUtil.GetTimestamp(); + var startTimestamp = nowTimestamp + Random.Range(1, 6) * 3600 * 24; + var endTimestamp = startTimestamp + Random.Range(1, 10) * 3600 * 24; + chart.AddData(0, startTimestamp, endTimestamp, taskName); + } + var serie2 = chart.AddSerie(SerieType.Gantt, "李四"); + for (int i = 0; i < taskCount; i++) + { + var taskName = "李四-任务-" + (i + 1); + var nowTimestamp = DateTimeUtil.GetTimestamp(); + var startTimestamp = nowTimestamp + Random.Range(1, 6) * 3600 * 24; + var endTimestamp = startTimestamp + Random.Range(1, 10) * 3600 * 24; + chart.AddData(1, startTimestamp, endTimestamp, taskName); + } + } + + void GenerateTimeData() + { + chart.RemoveData(); + + chart.grid.left = 100; + chart.xAxis0.type = Axis.AxisType.Time; + chart.xAxis0.boundaryGap = false; + chart.xAxis0.splitNumber = 5; + + chart.xAxis0.axisLabel.numericFormatter = "HH:mm:ss"; + chart.xAxis0.axisLabel.formatter = "time:{value}"; + + chart.yAxis0.type = Axis.AxisType.Category; + chart.yAxis0.boundaryGap = true; + chart.yAxis0.splitNumber = 0; + + + var serie1 = chart.AddSerie(SerieType.Gantt, "张三"); + serie1.label.show = true; + for (int i = 0; i < taskCount; i++) + { + var taskName = "张三-任务-" + (i + 1); + var nowTimestamp = DateTimeUtil.GetTimestamp(); + var startTimestamp = nowTimestamp + Random.Range(1, 6) * 3600 * 24; + var endTimestamp = startTimestamp + Random.Range(1, 10) * 3600 * 24; + chart.AddData(0, startTimestamp, endTimestamp, taskName); + } + var serie2 = chart.AddSerie(SerieType.Gantt, "李四"); + for (int i = 0; i < taskCount; i++) + { + var taskName = "李四-任务-" + (i + 1); + var nowTimestamp = DateTimeUtil.GetTimestamp(); + var startTimestamp = nowTimestamp + Random.Range(1, 6) * 3600 * 24; + var endTimestamp = startTimestamp + Random.Range(1, 10) * 3600 * 24; + chart.AddData(1, startTimestamp, endTimestamp, taskName); + } + } + } +} \ No newline at end of file diff --git a/Examples/Runtime/Example101_Gantt_Time.cs.meta b/Examples/Runtime/Example101_Gantt_Time.cs.meta new file mode 100644 index 00000000..8adb651e --- /dev/null +++ b/Examples/Runtime/Example101_Gantt_Time.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d546ff7dfa6104a739c1accdb415ef54 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/Runtime/Example90_Candlestick.cs b/Examples/Runtime/Example90_Candlestick.cs index 5da1af93..1ab23d78 100644 --- a/Examples/Runtime/Example90_Candlestick.cs +++ b/Examples/Runtime/Example90_Candlestick.cs @@ -45,7 +45,6 @@ namespace XCharts.Examples chart.ClearData(); var xValue = System.DateTime.Now; - var minute = 60 * 1000; var baseValue = Random.Range(0f, 1f) * 12000; var boxVals = new float[4]; var dayRange = 12; diff --git a/Runtime/API/BaseGraph_API.cs b/Runtime/API/BaseGraph_API.cs index fe954bdb..4c99d1ca 100644 --- a/Runtime/API/BaseGraph_API.cs +++ b/Runtime/API/BaseGraph_API.cs @@ -134,6 +134,7 @@ namespace XCharts public void RefreshAllComponent() { SetAllComponentDirty(); + RefreshGraph(); } /// diff --git a/Runtime/API/CoordinateChart_API.cs b/Runtime/API/CoordinateChart_API.cs index c42289ae..357905d1 100644 --- a/Runtime/API/CoordinateChart_API.cs +++ b/Runtime/API/CoordinateChart_API.cs @@ -89,8 +89,16 @@ namespace XCharts /// public void ClearAxisData() { - foreach (var item in m_XAxes) item.data.Clear(); - foreach (var item in m_YAxes) item.data.Clear(); + foreach (var axis in m_XAxes) + { + axis.data.Clear(); + axis.SetAllDirty(); + } + foreach (var axis in m_YAxes) + { + axis.data.Clear(); + axis.SetAllDirty(); + } } /// diff --git a/Runtime/Component/Main/Axis.cs b/Runtime/Component/Main/Axis.cs index da7078dd..c80e4374 100644 --- a/Runtime/Component/Main/Axis.cs +++ b/Runtime/Component/Main/Axis.cs @@ -39,7 +39,12 @@ namespace XCharts /// Log axis, suitable for log data. /// 对数轴。适用于对数数据。 /// - Log + Log, + /// + /// Time axis, suitable for continuous time series data. + /// 时间轴。适用于连续的时序数据。 + /// + Time } /// @@ -408,6 +413,7 @@ namespace XCharts public int runtimeMaxLogIndex { get { return logBaseE ? (int)Mathf.Log(runtimeMaxValue) : (int)Mathf.Log(runtimeMaxValue, logBase); } } internal bool runtimeLastCheckInverse { get; set; } internal float runtimeMinMaxRange { get { return m_MinMaxValueRange; } set { m_MinMaxValueRange = value; } } + internal List runtimeData { get { return m_RuntimeData; } } private int filterStart; private int filterEnd; private int filterMinShow; @@ -426,6 +432,7 @@ namespace XCharts private float m_RuntimeMaxValueUpdateTime; private bool m_RuntimeMinValueFirstChanged = true; private bool m_RuntimeMaxValueFirstChanged = true; + protected List m_RuntimeData = new List(); public Axis Clone() { @@ -484,6 +491,7 @@ namespace XCharts public void ClearData() { m_Data.Clear(); + m_RuntimeData.Clear(); SetAllDirty(); } @@ -574,10 +582,14 @@ namespace XCharts } else { - return m_Data; + return m_Data.Count > 0 ? m_Data : m_RuntimeData; } } + internal List GetDataList(){ + return m_Data.Count > 0 ? m_Data : m_RuntimeData; + } + private List emptyFliter = new List(); /// /// 更新dataZoom对应的类目数据列表 @@ -596,24 +608,25 @@ namespace XCharts filterEnd = endIndex; filterMinShow = dataZoom.minShowNum; m_NeedUpdateFilterData = false; - if (m_Data.Count > 0) + var data = GetDataList(); + if (data.Count > 0) { var count = endIndex == startIndex ? 1 : endIndex - startIndex + 1; if (count < dataZoom.minShowNum) { - if (dataZoom.minShowNum > m_Data.Count) count = m_Data.Count; + if (dataZoom.minShowNum > data.Count) count = data.Count; else count = dataZoom.minShowNum; } - if (startIndex + count > m_Data.Count) + if (startIndex + count > data.Count) { int start = endIndex - count; - filterData = m_Data.GetRange(start < 0 ? 0 : start, count); + filterData = data.GetRange(start < 0 ? 0 : start, count); } - else filterData = m_Data.GetRange(startIndex, count); + else filterData = data.GetRange(startIndex, count); } else { - filterData = m_Data; + filterData = data; } } else if (endIndex == 0) diff --git a/Runtime/Component/Main/Serie.cs b/Runtime/Component/Main/Serie.cs index a89a273e..3811bc88 100644 --- a/Runtime/Component/Main/Serie.cs +++ b/Runtime/Component/Main/Serie.cs @@ -64,6 +64,10 @@ namespace XCharts /// K线图。K线图的data至少包含四个数据:[open, close, lowest, highest] /// Candlestick, + /// + /// 甘特图。甘特图的data至少包含两个数据:[start, end] + /// + Gantt, } /// @@ -781,7 +785,7 @@ namespace XCharts /// /// 数据项里的数据维数。 /// - public int showDataDimension { get { return m_ShowDataDimension; } } + public int showDataDimension { get { return m_ShowDataDimension; } internal set { m_ShowDataDimension = value; } } /// /// 在Editor的inpsector上是否显示name参数 /// @@ -1234,6 +1238,7 @@ namespace XCharts { CheckMaxCache(); var serieData = SerieDataPool.Get(); + serieData.data.Clear(); serieData.data.Add(xValue); serieData.data.Add(yValue); serieData.name = dataName; @@ -1245,10 +1250,20 @@ namespace XCharts return serieData; } + /// + /// 添加 (open, close, lowest, heighest) 数据 + /// + /// + /// + /// + /// + /// + /// public SerieData AddData(float open, float close, float lowest, float heighest, string dataName = null) { CheckMaxCache(); var serieData = SerieDataPool.Get(); + serieData.data.Clear(); serieData.data.Add(open); serieData.data.Add(close); serieData.data.Add(lowest); @@ -1678,7 +1693,9 @@ namespace XCharts return type == SerieType.Line || type == SerieType.Bar || type == SerieType.Scatter - || type == SerieType.Heatmap; + || type == SerieType.Heatmap + || type == SerieType.Gantt + || type == SerieType.Candlestick; } /// diff --git a/Runtime/Component/Sub/AxisLabel.cs b/Runtime/Component/Sub/AxisLabel.cs index 148351d9..822776e4 100644 --- a/Runtime/Component/Sub/AxisLabel.cs +++ b/Runtime/Component/Sub/AxisLabel.cs @@ -188,9 +188,8 @@ namespace XCharts } else { - var content = m_Formatter.Replace("{value}", category); - content = content.Replace("\\n", "\n"); - content = content.Replace("
", "\n"); + var content = m_Formatter; + FormatterHelper.ReplaceAxisLabelContent(ref content, category); return m_TextLimit.GetLimitContent(content); } } @@ -224,5 +223,21 @@ namespace XCharts return content; } } + + public string GetFormatterDateTime(DateTime dateTime) + { + var format = string.IsNullOrEmpty(numericFormatter) ? "yyyy/M/d" : numericFormatter; + if (!string.IsNullOrEmpty(m_Formatter)) + { + var content = m_Formatter; + FormatterHelper.ReplaceAxisLabelContent(ref content, dateTime.ToString(format)); + return m_TextLimit.GetLimitContent(content); + } + else + { + var content = dateTime.ToString(format); + return m_TextLimit.GetLimitContent(content); + } + } } } \ No newline at end of file diff --git a/Runtime/Component/Sub/SerieData.cs b/Runtime/Component/Sub/SerieData.cs index 6966a7f5..ad09a74b 100644 --- a/Runtime/Component/Sub/SerieData.cs +++ b/Runtime/Component/Sub/SerieData.cs @@ -5,11 +5,8 @@ /* */ /************************************************/ -using System.Linq; using System.Collections.Generic; using UnityEngine; -using UnityEngine.UI; -using System; namespace XCharts { @@ -191,6 +188,10 @@ namespace XCharts ///
public float runtimePieOffsetRadius { get; internal set; } public Vector3 runtimePosition { get; internal set; } + /// + /// 绘制区域。 + /// + public Rect runtimeRect { get; internal set; } public float runtimeAngle { get; internal set; } public Vector3 runtiemPieOffsetCenter { get; internal set; } public float runtimeStackHig { get; internal set; } diff --git a/Runtime/Component/Sub/TitleStyle.cs b/Runtime/Component/Sub/TitleStyle.cs index fd532327..a460d9bd 100644 --- a/Runtime/Component/Sub/TitleStyle.cs +++ b/Runtime/Component/Sub/TitleStyle.cs @@ -85,5 +85,13 @@ namespace XCharts runtimeText.SetText(text); } } + + public void SetColor(Color color) + { + if (runtimeText != null) + { + runtimeText.SetColor(color); + } + } } } \ No newline at end of file diff --git a/Runtime/GanttChart.cs b/Runtime/GanttChart.cs new file mode 100644 index 00000000..f3c2525b --- /dev/null +++ b/Runtime/GanttChart.cs @@ -0,0 +1,137 @@ + +/************************************************/ +/* */ +/* Copyright (c) 2018 - 2021 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/************************************************/ + +using UnityEngine; + +namespace XCharts +{ + [AddComponentMenu("XCharts/GanttChart", 22)] + [ExecuteInEditMode] + [RequireComponent(typeof(RectTransform))] + [DisallowMultipleComponent] + public partial class GanttChart : CoordinateChart + { + +#if UNITY_EDITOR + protected override void Reset() + { + base.Reset(); + title.text = "GanttChart"; + var xCount = 5; + var yCount = 5; + + m_Grids[0].left = 60; + m_Grids[0].right = 50; + m_XAxes[0].type = Axis.AxisType.Time; + m_XAxes[0].boundaryGap = false; + m_XAxes[0].splitNumber = xCount; + m_YAxes[0].type = Axis.AxisType.Category; + m_YAxes[0].boundaryGap = true; + m_YAxes[0].splitNumber = 0; + + RemoveData(); + SerieTemplate.AddDefaultTimeGanttSerie(this, "task", yCount); + } +#endif + protected override void GetSeriesMinMaxValue(Axis axis, int axisIndex, out float tempMinValue, out float tempMaxValue) + { + tempMinValue = int.MaxValue; + tempMaxValue = int.MinValue; + foreach (var serie in m_Series.list) + { + if (serie.type != SerieType.Gantt) continue; + if (serie.xAxisIndex != axis.index) continue; + foreach (var serieData in serie.data) + { + if (serieData.data.Count >= 2) + { + var xData = serieData.data[0]; + var yData = serieData.data[1]; + if (xData < tempMinValue) tempMinValue = xData; + if (yData > tempMaxValue) tempMaxValue = yData; + } + } + } + if (tempMinValue == int.MaxValue) tempMinValue = 0; + if (tempMaxValue == int.MinValue) tempMaxValue = 0; + //AxisHelper.AdjustMinMaxValue(axis, ref tempMinValue, ref tempMaxValue, true, 60); + } + + protected override void OnRefreshLabel() + { + for (int i = 0; i < m_Series.Count; i++) + { + var serie = m_Series.GetSerie(i); + if (serie.IsPerformanceMode()) continue; + if (serie.type != SerieType.Gantt) continue; + foreach (var serieData in serie.data) + { + if (serieData.labelObject == null) continue; + var serieLabel = SerieHelper.GetSerieLabel(serie, serieData); + var labelShow = serie.show && serieLabel.show; + serieData.SetLabelActive(labelShow); + if (labelShow) + { + var labelColor = serieLabel.textStyle.GetColor(m_Theme.axis.textColor); + var labelPos = serieData.runtimePosition; + SerieLabelHelper.ResetLabel(serieData.labelObject.label, serieLabel, m_Theme, i); + serieData.labelObject.SetPosition(labelPos); + serieData.labelObject.SetLabelColor(labelColor); + serieData.labelObject.SetText(serieData.name); + } + } + } + } + + protected override void UpdateTooltipValue(Vector2 local) + { + var grid = GetGrid(tooltip.runtimeGridIndex); + if (grid == null) return; + tooltip.runtimeDataIndex.Clear(); + foreach (var serie in m_Series.list) + { + var serieGrid = GetSerieGridOrDefault(serie); + if (grid.index != serieGrid.index) continue; + for (int i = 0; i < serie.data.Count; i++) + { + var serieData = serie.GetSerieData(i); + var highlight = serieData.runtimeRect.Contains(local); + serieData.highlighted = highlight; + if (highlight) + { + + tooltip.runtimeDataIndex.Add(serie.index); + tooltip.runtimeDataIndex.Add(i); + return; + } + } + } + } + + protected override void UpdateTooltip() + { + if (tooltip.runtimeDataIndex.Count == 0) + { + if (tooltip.IsActive()) + { + tooltip.SetActive(false); + RefreshChart(); + } + return; + } + var serieIndex = tooltip.runtimeDataIndex[0]; + var dataIndex = tooltip.runtimeDataIndex[1]; + var serie = m_Series.GetSerie(serieIndex); + if (serie == null) return; + var serieData = serie.GetSerieData(dataIndex); + var category = serieData == null ? serie.name : serieData.name; + TooltipHelper.SetContentAndPosition(tooltip, category, chartRect); + tooltip.SetActive(true); + } + } +} diff --git a/Runtime/GanttChart.cs.meta b/Runtime/GanttChart.cs.meta new file mode 100644 index 00000000..2b699350 --- /dev/null +++ b/Runtime/GanttChart.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d20186b31c74d4711870603e97fd65bc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Helper/FormatterHelper.cs b/Runtime/Helper/FormatterHelper.cs index d3fef71b..55e5af88 100644 --- a/Runtime/Helper/FormatterHelper.cs +++ b/Runtime/Helper/FormatterHelper.cs @@ -198,6 +198,20 @@ namespace XCharts content = TrimAndReplaceLine(content); } + public static void ReplaceAxisLabelContent(ref string content, string value) + { + var mc = s_RegexForAxisLabel.Matches(content); + foreach (var m in mc) + { + var old = m.ToString(); + var args = s_RegexSubForAxisLabel.Matches(m.ToString()); + var argsCount = args.Count; + if (argsCount <= 0) continue; + content = content.Replace(old, value); + } + content = TrimAndReplaceLine(content); + } + public static void ReplaceSerieLabelContent(ref string content, string numericFormatter, float value, float total, string serieName, string dataName) { diff --git a/Runtime/Internal/BaseChart.cs b/Runtime/Internal/BaseChart.cs index 246b3986..41a26bed 100644 --- a/Runtime/Internal/BaseChart.cs +++ b/Runtime/Internal/BaseChart.cs @@ -284,6 +284,7 @@ namespace XCharts foreach (var component in m_Radars) component.SetAllDirty(); m_ReinitLabel = true; m_ReinitTitle = true; + m_RefreshChart = true; } protected override void OnDestroy() diff --git a/Runtime/Internal/CoordinateChart.cs b/Runtime/Internal/CoordinateChart.cs index 1eda45fc..2bf8017c 100644 --- a/Runtime/Internal/CoordinateChart.cs +++ b/Runtime/Internal/CoordinateChart.cs @@ -178,6 +178,9 @@ namespace XCharts case SerieType.Candlestick: DrawCandlestickSerie(vh, colorIndex, serie); break; + case SerieType.Gantt: + DrawGanttSerie(vh, colorIndex, serie); + break; } } @@ -240,7 +243,7 @@ namespace XCharts } } - protected void UpdateTooltipValue(Vector2 local) + protected virtual void UpdateTooltipValue(Vector2 local) { var isCartesian = IsValue(); var dataCount = m_Series.list.Count > 0 ? m_Series.list[0].GetDataList(dataZoom).Count : 0; @@ -495,6 +498,7 @@ namespace XCharts yAxis.painter = m_Painter; yAxis.refreshComponent = delegate () { + InitAxisRuntimeData(yAxis); string objName = ChartCached.GetYAxisName(yAxisIndex); var axisObj = ChartHelper.AddObject(objName, transform, graphAnchorMin, graphAnchorMax, chartPivot, new Vector2(chartWidth, chartHeight)); @@ -593,6 +597,26 @@ namespace XCharts yAxis.refreshComponent(); } + private void InitAxisRuntimeData(Axis axis) + { + if (axis.type != Axis.AxisType.Category) return; + if (axis.data.Count > 0) return; + var isYAxis = axis is YAxis; + if (this is GanttChart) + { + axis.runtimeData.Clear(); + for (int i = 0; i < m_Series.Count; i++) + { + var serie = m_Series.GetSerie(i); + if(serie.yAxisIndex != axis.index) continue; + for (int j = serie.data.Count - 1; j >= 0; j--) + { + axis.runtimeData.Add(serie.data[j].name); + } + } + } + } + private void InitAxisX() { for (int i = 0; i < m_XAxes.Count; i++) @@ -782,7 +806,7 @@ namespace XCharts return new Vector3(grid.runtimeX + scaleWid, posY); } - private void CheckMinMaxValue() + protected virtual void CheckMinMaxValue() { if (m_XAxes == null || m_YAxes == null) return; for (int i = 0; i < m_XAxes.Count; i++) @@ -806,23 +830,7 @@ namespace XCharts } float tempMinValue = 0; float tempMaxValue = 0; - - if (IsValue()) - { - if (axis is XAxis) - { - SeriesHelper.GetXMinMaxValue(m_Series, null, axisIndex, true, axis.inverse, out tempMinValue, out tempMaxValue); - } - else - { - SeriesHelper.GetYMinMaxValue(m_Series, null, axisIndex, true, axis.inverse, out tempMinValue, out tempMaxValue); - } - } - else - { - SeriesHelper.GetYMinMaxValue(m_Series, null, axisIndex, false, axis.inverse, out tempMinValue, out tempMaxValue); - } - AxisHelper.AdjustMinMaxValue(axis, ref tempMinValue, ref tempMaxValue, true); + GetSeriesMinMaxValue(axis, axisIndex, out tempMinValue, out tempMaxValue); if (tempMinValue != axis.runtimeMinValue || tempMaxValue != axis.runtimeMaxValue) { m_IsPlayingAnimation = true; @@ -866,6 +874,26 @@ namespace XCharts } } + protected virtual void GetSeriesMinMaxValue(Axis axis, int axisIndex, out float tempMinValue, out float tempMaxValue) + { + if (IsValue()) + { + if (axis is XAxis) + { + SeriesHelper.GetXMinMaxValue(m_Series, null, axisIndex, true, axis.inverse, out tempMinValue, out tempMaxValue); + } + else + { + SeriesHelper.GetYMinMaxValue(m_Series, null, axisIndex, true, axis.inverse, out tempMinValue, out tempMaxValue); + } + } + else + { + SeriesHelper.GetYMinMaxValue(m_Series, null, axisIndex, false, axis.inverse, out tempMinValue, out tempMaxValue); + } + AxisHelper.AdjustMinMaxValue(axis, ref tempMinValue, ref tempMaxValue, true); + } + protected void UpdateAxisLabelText(Axis axis) { var grid = GetAxisGridOrDefault(axis); @@ -1571,6 +1599,7 @@ namespace XCharts { base.OnRefreshLabel(); var anyPercentStack = SeriesHelper.IsPercentStack(m_Series, SerieType.Bar); + for (int i = 0; i < m_Series.Count; i++) { var serie = m_Series.GetSerie(i); @@ -1578,6 +1607,7 @@ namespace XCharts if (!serie.IsCoordinateSerie()) continue; var total = serie.yTotal; var isPercentStack = SeriesHelper.IsPercentStack(m_Series, serie.stack, SerieType.Bar); + for (int j = 0; j < serie.data.Count; j++) { var serieData = serie.data[j]; @@ -1602,7 +1632,7 @@ namespace XCharts dimension = VisualMapHelper.GetDimension(visualMap, serieData.data.Count); } - SerieLabelHelper.ResetLabel(serieData, serieLabel, theme, i); + SerieLabelHelper.ResetLabel(serieData.labelObject.label, serieLabel, theme, i); value = serieData.data[dimension]; var content = ""; diff --git a/Runtime/Internal/CoordinateChart_DrawGantt.cs b/Runtime/Internal/CoordinateChart_DrawGantt.cs new file mode 100644 index 00000000..0b885de9 --- /dev/null +++ b/Runtime/Internal/CoordinateChart_DrawGantt.cs @@ -0,0 +1,178 @@ +/************************************************/ +/* */ +/* Copyright (c) 2018 - 2021 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/************************************************/ + +using UnityEngine; +using UnityEngine.UI; +using XUGL; + +namespace XCharts +{ + public partial class CoordinateChart + { + protected void DrawGanttSerie(VertexHelper vh, int colorIndex, Serie serie) + { + if (!IsActive(serie.index)) return; + if (serie.animation.HasFadeOut()) return; + var showData = serie.GetDataList(null); + var yAxis = m_YAxes[serie.yAxisIndex]; + var xAxis = m_XAxes[serie.xAxisIndex]; + var grid = GetSerieGridOrDefault(serie); + var xCategoryWidth = AxisHelper.GetDataWidth(xAxis, grid.runtimeWidth, showData.Count, dataZoom); + var yCategoryWidth = AxisHelper.GetDataWidth(yAxis, grid.runtimeHeight, showData.Count, dataZoom); + var barGap = GetBarGap(); + var barWidth = serie.GetBarWidth(yCategoryWidth); + var space = (yCategoryWidth - barWidth) / 2; + var dataChanging = false; + var dataChangeDuration = serie.animation.GetUpdateAnimationDuration(); + var minValue = xAxis.GetCurrMinValue(dataChangeDuration); + var maxValue = xAxis.GetCurrMaxValue(dataChangeDuration); + var pX = grid.runtimeX + (xAxis.boundaryGap ? xCategoryWidth / 2 : 0); + var pY = 0f; + var startY = grid.runtimeY - (yAxis.boundaryGap ? 0 : yCategoryWidth / 2); + var isTime = xAxis.type == Axis.AxisType.Time; + + var categoryIndex = GetGanttSerieCategoryIndex(serie, grid.index); + var dataCount = serie.data.Count; + for (int i = 0; i < dataCount; i++) + { + var serieData = serie.data[i]; + pY = startY + (categoryIndex - 1 - i) * yCategoryWidth; + DrawSerieData(vh, grid, serie, serieData, colorIndex, pX, pY, space, barWidth, isTime, minValue, + maxValue, xCategoryWidth); + } + if (dataChanging) + { + RefreshPainter(serie); + } + } + + private void DrawSerieData(VertexHelper vh, Grid grid, Serie serie, SerieData serieData, int colorIndex, + float pX, float pY, float space, float barWidth, bool isTime, float minValue, float maxValue, + float xCategoryWidth) + { + var xStart = 0f; + var xEnd = 0f; + var xActualStart = 0f; + var xActualEnd = 0f; + var start = (int)serieData.GetData(0); + var end = (int)serieData.GetData(1); + var actualStart = (int)serieData.GetData(2); + var actualEnd = (int)serieData.GetData(3); + var enableActual = actualStart > 0 && actualEnd > 0; + if (isTime) + { + var valueTotal = maxValue - minValue; + xStart = pX + (start - minValue) / valueTotal * grid.runtimeWidth; + xEnd = pX + (end - minValue) / valueTotal * grid.runtimeWidth; + if (enableActual) + { + xActualStart = pX + (actualStart - minValue) / valueTotal * grid.runtimeWidth; + xActualEnd = pX + (actualEnd - minValue) / valueTotal * grid.runtimeWidth; + } + } + else + { + xStart = pX + start * xCategoryWidth; + xEnd = pX + end * xCategoryWidth; + if (enableActual) + { + xActualStart = pX + actualStart * xCategoryWidth; + xActualEnd = pX + actualEnd * xCategoryWidth; + } + } + var highlight = (serieData != null && serieData.highlighted) + || serie.highlighted; + var itemStyle = SerieHelper.GetItemStyle(serie, serieData, highlight); + var color = SerieHelper.GetItemColor(serie, serieData, m_Theme, colorIndex, highlight); + var borderWidth = itemStyle.borderWidth; + + var rect = DrawGanttBar(vh, grid, serie, serieData, itemStyle, color, pY, pY, space, barWidth, xStart, + xEnd); + if (enableActual) + { + var defaultActualColor = SerieHelper.GetItemColor(serie, serieData, m_Theme, colorIndex, true); + var actualColor = SerieHelper.GetItemColor0(serie, serieData, m_Theme, highlight, defaultActualColor); + var rect2 = DrawGanttBar(vh, grid, serie, serieData, itemStyle, actualColor, pY, pY, space, barWidth, + xActualStart, xActualEnd); + var rect3X = Mathf.Min(rect.x, rect2.x); + var rect3Width = Mathf.Max(rect.x + rect.width, rect2.x + rect2.width) - rect3X; + var rect3 = new Rect(rect3X, rect.y, rect3Width, rect.height); + serie.dataPoints.Add(rect3.center); + serieData.runtimePosition = rect3.center; + serieData.labelPosition = rect3.center; + serieData.runtimeRect = rect3; + } + else + { + serie.dataPoints.Add(rect.center); + serieData.runtimePosition = rect.center; + serieData.labelPosition = rect.center; + serieData.runtimeRect = rect; + } + } + + private Rect DrawGanttBar(VertexHelper vh, Grid grid, Serie serie, SerieData serieData, ItemStyle itemStyle, + Color32 color, float pX, float pY, float space, float barWidth, float xStart, float xEnd) + { + + var borderWidth = itemStyle.borderWidth; + var plb = new Vector3(xStart + borderWidth, pY + space + borderWidth); + var plt = new Vector3(xStart + borderWidth, pY + space + barWidth - borderWidth); + var prt = new Vector3(xEnd - borderWidth, pY + space + barWidth - borderWidth); + var prb = new Vector3(xEnd - borderWidth, pY + space + borderWidth); + var center = new Vector3((plb.x + prt.x) / 2, (plt.y + prb.y) / 2); + var itemWidth = Mathf.Abs(prt.x - plb.x); + var itemHeight = Mathf.Abs(plt.y - prb.y); + if (serie.clip) + { + plb = ClampInGrid(grid, plb); + plt = ClampInGrid(grid, plt); + prt = ClampInGrid(grid, prt); + prb = ClampInGrid(grid, prb); + center = ClampInGrid(grid, center); + } + if (ItemStyleHelper.IsNeedCorner(itemStyle)) + { + UGL.DrawRoundRectangle(vh, center, itemWidth, itemHeight, color, color, 0, + itemStyle.cornerRadius, true, 0.5f); + } + else + { + CheckClipAndDrawPolygon(vh, ref prb, ref plb, ref plt, ref prt, color, color, + serie.clip, grid); + } + if (borderWidth != 0) + { + UGL.DrawBorder(vh, center, itemWidth, itemHeight, borderWidth, itemStyle.borderColor, 0, + itemStyle.cornerRadius, true, 0.5f); + } + return new Rect(plb.x, plb.y, xEnd - xStart, barWidth); + } + + private int GetGanttSerieCategoryIndex(Serie currSerie, int gridIndex) + { + var count = m_Series.Count; + var index = 0; + for (int i = 0; i < count; i++) + { + var serie = m_Series.GetSerie(i); + if (serie.type != SerieType.Gantt) continue; + var grid = GetSerieGridOrDefault(serie); + if (grid.index != gridIndex) continue; + foreach (var serieData in serie.data) + { + index++; + } + if (serie.index == currSerie.index) + { + return index; + } + } + return index; + } + } +} \ No newline at end of file diff --git a/Runtime/Internal/CoordinateChart_DrawGantt.cs.meta b/Runtime/Internal/CoordinateChart_DrawGantt.cs.meta new file mode 100644 index 00000000..b779eed2 --- /dev/null +++ b/Runtime/Internal/CoordinateChart_DrawGantt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 81895b3c97e684e8090572c7e64b396e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/DrawSerieRadar.cs b/Runtime/Internal/DrawSerieRadar.cs index f40d06c7..75b534ff 100644 --- a/Runtime/Internal/DrawSerieRadar.cs +++ b/Runtime/Internal/DrawSerieRadar.cs @@ -99,7 +99,7 @@ namespace XCharts { var value = serieData.GetCurrData(1); var max = radar.GetIndicatorMax(n); - SerieLabelHelper.ResetLabel(serieData, serieLabel, chart.theme, i); + SerieLabelHelper.ResetLabel(serieData.labelObject.label, serieLabel, chart.theme, i); serieData.SetLabelActive(serieData.labelPosition != Vector3.zero); serieData.labelObject.SetLabelPosition(serieLabel.offset); var content = SerieLabelHelper.GetFormatterContent(serie, serieData, value, max, serieLabel); diff --git a/Runtime/Internal/Helper/AxisHelper.cs b/Runtime/Internal/Helper/AxisHelper.cs index 9544fc64..c7c258ba 100644 --- a/Runtime/Internal/Helper/AxisHelper.cs +++ b/Runtime/Internal/Helper/AxisHelper.cs @@ -53,6 +53,25 @@ namespace XCharts return axis.splitNumber > 0 ? axis.splitNumber : 4; } } + else if (axis.type == Axis.AxisType.Time) + { + if (axis.interval > 0) + { + if (coordinateWid <= 0) return 0; + int num = Mathf.CeilToInt(axis.runtimeMinMaxRange / axis.interval); + int maxNum = Mathf.CeilToInt(coordinateWid / 15); + if (num > maxNum) + { + axis.interval *= 2; + num = Mathf.CeilToInt(axis.runtimeMinMaxRange / axis.interval); + } + return num; + } + else + { + return axis.splitNumber > 0 ? axis.splitNumber : 4; + } + } else if (axis.type == Axis.AxisType.Log) { return axis.splitNumber > 0 ? axis.splitNumber : 4; @@ -146,6 +165,23 @@ namespace XCharts } return axis.axisLabel.GetFormatterContent(value, minValue, maxValue, true); } + else if (axis.type == Axis.AxisType.Time) + { + if (minValue == 0 && maxValue == 0) return string.Empty; + var value = 0f; + if (axis.interval > 0) + { + if (index == split) value = maxValue; + else value = minValue + index * axis.interval; + } + else + { + value = minValue + (maxValue - minValue) * index / split; + } + var timestamp = (int)value; + var dateTime = DateTimeUtil.GetDateTime(timestamp); + return axis.axisLabel.GetFormatterDateTime(dateTime); + } var showData = axis.GetDataList(dataZoom); int dataCount = showData.Count; if (dataCount <= 0) return ""; @@ -177,11 +213,12 @@ namespace XCharts int splitNum = GetSplitNumber(axis, coordinateWidth, dataZoom); if (axis.IsCategory()) { - int tick = Mathf.RoundToInt(axis.data.Count * 1f / splitNum); + var data = axis.GetDataList(); + int tick = Mathf.RoundToInt(data.Count * 1f / splitNum); if (axis.boundaryGap) - return Mathf.CeilToInt(axis.data.Count * 1.0f / tick) + 1; + return Mathf.CeilToInt(data.Count * 1.0f / tick) + 1; else - return Mathf.CeilToInt(axis.data.Count * 1.0f / tick); + return Mathf.CeilToInt(data.Count * 1.0f / tick); } else { @@ -209,10 +246,11 @@ namespace XCharts } else { - if (axis.IsCategory() && axis.data.Count > 0) + var data = axis.GetDataList(); + if (axis.IsCategory() && data.Count > 0) { - int tick = Mathf.RoundToInt(axis.data.Count * 1f / splitNum); - var count = axis.boundaryGap ? axis.data.Count : axis.data.Count - 1; + int tick = Mathf.RoundToInt(data.Count * 1f / splitNum); + var count = axis.boundaryGap ? data.Count : data.Count - 1; if (count <= 0) return 0; var each = coordinateWidth / count; if (index >= num - 1) @@ -232,9 +270,10 @@ namespace XCharts internal static float GetEachWidth(Axis axis, float coordinateWidth, DataZoom dataZoom = null) { - if (axis.data.Count > 0) + var data = axis.GetDataList(); + if (data.Count > 0) { - var count = axis.boundaryGap ? axis.data.Count : axis.data.Count - 1; + var count = axis.boundaryGap ? data.Count : data.Count - 1; return count > 0 ? coordinateWidth / count : coordinateWidth; } else @@ -249,7 +288,7 @@ namespace XCharts ///
/// /// - internal static void AdjustMinMaxValue(Axis axis, ref float minValue, ref float maxValue, bool needFormat) + internal static void AdjustMinMaxValue(Axis axis, ref float minValue, ref float maxValue, bool needFormat, int ceilRate = 0) { if (axis.type == Axis.AxisType.Log) { @@ -278,6 +317,7 @@ namespace XCharts } else { + if (ceilRate == 0) ceilRate = axis.ceilRate; switch (axis.minMaxType) { case Axis.AxisMinMaxType.Default: @@ -287,22 +327,22 @@ namespace XCharts else if (minValue > 0 && maxValue > 0) { minValue = 0; - maxValue = needFormat ? ChartHelper.GetMaxDivisibleValue(maxValue, axis.ceilRate) : maxValue; + maxValue = needFormat ? ChartHelper.GetMaxDivisibleValue(maxValue, ceilRate) : maxValue; } else if (minValue < 0 && maxValue < 0) { - minValue = needFormat ? ChartHelper.GetMinDivisibleValue(minValue, axis.ceilRate) : minValue; + minValue = needFormat ? ChartHelper.GetMinDivisibleValue(minValue, ceilRate) : minValue; maxValue = 0; } else { - minValue = needFormat ? ChartHelper.GetMinDivisibleValue(minValue, axis.ceilRate) : minValue; - maxValue = needFormat ? ChartHelper.GetMaxDivisibleValue(maxValue, axis.ceilRate) : maxValue; + minValue = needFormat ? ChartHelper.GetMinDivisibleValue(minValue, ceilRate) : minValue; + maxValue = needFormat ? ChartHelper.GetMaxDivisibleValue(maxValue, ceilRate) : maxValue; } break; case Axis.AxisMinMaxType.MinMax: - minValue = needFormat ? ChartHelper.GetMinDivisibleValue(minValue, axis.ceilRate) : minValue; - maxValue = needFormat ? ChartHelper.GetMaxDivisibleValue(maxValue, axis.ceilRate) : maxValue; + minValue = needFormat ? ChartHelper.GetMinDivisibleValue(minValue, ceilRate) : minValue; + maxValue = needFormat ? ChartHelper.GetMaxDivisibleValue(maxValue, ceilRate) : maxValue; break; } } @@ -320,7 +360,7 @@ namespace XCharts internal static bool NeedShowSplit(Axis axis) { if (!axis.show) return false; - if (axis.IsCategory() && axis.data.Count <= 0) return false; + if (axis.IsCategory() && axis.GetDataList().Count <= 0) return false; else if (axis.IsValue() && axis.runtimeMinValue == 0 && axis.runtimeMaxValue == 0) return false; else return true; } diff --git a/Runtime/Internal/Helper/SerieHelper.cs b/Runtime/Internal/Helper/SerieHelper.cs index 59eb1878..4433a1d2 100644 --- a/Runtime/Internal/Helper/SerieHelper.cs +++ b/Runtime/Internal/Helper/SerieHelper.cs @@ -70,6 +70,32 @@ namespace XCharts return color; } } + internal static Color32 GetItemColor0(Serie serie, SerieData serieData, ChartTheme theme, bool highlight, Color32 defaultColor) + { + if (serie == null) return ChartConst.clearColor32; + if (highlight) + { + var itemStyleEmphasis = GetItemStyleEmphasis(serie, serieData); + if (itemStyleEmphasis != null && !ChartHelper.IsClearColor(itemStyleEmphasis.color)) + { + var color = itemStyleEmphasis.color0; + ChartHelper.SetColorOpacity(ref color, itemStyleEmphasis.opacity); + return color; + } + } + var itemStyle = GetItemStyle(serie, serieData); + if (!ChartHelper.IsClearColor(itemStyle.color0)) + { + return itemStyle.GetColor0(); + } + else + { + var color = defaultColor; + if (highlight) color = ChartHelper.GetHighlightColor(color); + ChartHelper.SetColorOpacity(ref color, itemStyle.opacity); + return color; + } + } internal static Color32 GetItemToColor(Serie serie, SerieData serieData, ChartTheme theme, int index, bool highlight) { diff --git a/Runtime/Internal/Helper/SerieLabelHelper.cs b/Runtime/Internal/Helper/SerieLabelHelper.cs index ae11d392..9cc66317 100644 --- a/Runtime/Internal/Helper/SerieLabelHelper.cs +++ b/Runtime/Internal/Helper/SerieLabelHelper.cs @@ -75,14 +75,13 @@ namespace XCharts } } - public static void ResetLabel(SerieData serieData, SerieLabel label, ChartTheme theme, int colorIndex) + public static void ResetLabel(ChartText labelObject, SerieLabel label, ChartTheme theme, int colorIndex) { - if (serieData.labelObject == null) return; - if (serieData.labelObject.label == null) return; - serieData.labelObject.label.SetColor(!ChartHelper.IsClearColor(label.textStyle.color) ? label.textStyle.color : + if (labelObject == null) return; + labelObject.SetColor(!ChartHelper.IsClearColor(label.textStyle.color) ? label.textStyle.color : (Color)theme.GetColor(colorIndex)); - serieData.labelObject.label.SetFontSize(label.textStyle.GetFontSize(theme.common)); - serieData.labelObject.label.SetFontStyle(label.textStyle.fontStyle); + labelObject.SetFontSize(label.textStyle.GetFontSize(theme.common)); + labelObject.SetFontStyle(label.textStyle.fontStyle); } public static bool CanShowLabel(Serie serie, SerieData serieData, SerieLabel label, int dimesion) diff --git a/Runtime/Internal/Helper/TooltipHelper.cs b/Runtime/Internal/Helper/TooltipHelper.cs index a1090d3c..1101bcb5 100644 --- a/Runtime/Internal/Helper/TooltipHelper.cs +++ b/Runtime/Internal/Helper/TooltipHelper.cs @@ -232,6 +232,14 @@ namespace XCharts } } + private static void InitGanttTooltip(ref StringBuilder sb, Tooltip tooltip, Serie serie, int index, + ChartTheme theme, string category) + { + //if (tooltip.runtimeGridIndex >= 0) return; + //if (serie.index != index || serie.type != SerieType.Gantt) return; + sb.Append(serie.name); + } + private static void InitDefaultContent(ref StringBuilder sb, Tooltip tooltip, Serie serie, int index, string category, ChartTheme theme = null, DataZoom dataZoom = null, bool isCartesian = false, Radar radar = null) @@ -261,6 +269,9 @@ namespace XCharts case SerieType.Gauge: InitGaugeTooltip(ref sb, tooltip, serie, index, theme); break; + case SerieType.Gantt: + InitGanttTooltip(ref sb, tooltip, serie, index, theme, category); + break; } } diff --git a/Runtime/Template/SerieTemplate.cs b/Runtime/Template/SerieTemplate.cs index ada7f05c..3e2aeff4 100644 --- a/Runtime/Template/SerieTemplate.cs +++ b/Runtime/Template/SerieTemplate.cs @@ -28,6 +28,7 @@ namespace XCharts case SerieType.Gauge: AddDefaultGaugeSerie(chart, serieName); break; case SerieType.Ring: AddDefaultRingSerie(chart, serieName); break; case SerieType.Candlestick: AddDefaultCandlestickSerie(chart, serieName); break; + case SerieType.Gantt: AddDefaultCategoryGanttSerie(chart, serieName); break; default: Debug.LogError("AddDefaultSerie: not support serieType yet:" + serieType); break; } } @@ -183,5 +184,35 @@ namespace XCharts } return defaultDataCount; } + + public static Serie AddDefaultCategoryGanttSerie(BaseChart chart, string serieName, int dataCount = 0, int min = 0, int max = 0) + { + var serie = chart.AddSerie(SerieType.Gantt, serieName); + serie.showDataName = true; + serie.showDataDimension = 2; + for (int i = 0; i < dataCount; i++) + { + var start = Random.Range(min, max); + var end = Random.Range(start + 1, max); + serie.AddXYData(start, end, "task-" + (i + 1)); + } + return serie; + } + + public static Serie AddDefaultTimeGanttSerie(BaseChart chart, string serieName, int dataCount = 0) + { + var serie = chart.AddSerie(SerieType.Gantt, serieName); + serie.showDataName = true; + serie.showDataDimension = 2; + var timestamp = DateTimeUtil.GetTimestamp(); + var now = DateTimeUtil.GetDateTime(timestamp); + for (int i = 0; i < dataCount; i++) + { + var start = timestamp + Random.Range(1, 6) * 3600 * 24; + var end = start + Random.Range(1, 10) * 3600 * 24; + serie.AddXYData(start, end, "task-" + (i + 1)); + } + return serie; + } } } \ No newline at end of file diff --git a/Runtime/Utils/DateTimeUtil.cs b/Runtime/Utils/DateTimeUtil.cs new file mode 100644 index 00000000..674297f9 --- /dev/null +++ b/Runtime/Utils/DateTimeUtil.cs @@ -0,0 +1,33 @@ +/************************************************/ +/* */ +/* Copyright (c) 2018 - 2021 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/************************************************/ + +using System; +using UnityEngine; + +namespace XCharts +{ + public static class DateTimeUtil + { + private static readonly DateTime k_DateTime1970 = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); + + public static int GetTimestamp() + { + return (int)(DateTime.Now - k_DateTime1970).TotalSeconds; + } + + public static int GetTimestamp(DateTime time) + { + return (int)(time - k_DateTime1970).TotalSeconds; + } + + public static DateTime GetDateTime(int timestamp) + { + long span = ((long)timestamp) * 10000000; + return k_DateTime1970.Add(new TimeSpan(span)); + } + } +} \ No newline at end of file diff --git a/Runtime/Utils/DateTimeUtil.cs.meta b/Runtime/Utils/DateTimeUtil.cs.meta new file mode 100644 index 00000000..ac2244b3 --- /dev/null +++ b/Runtime/Utils/DateTimeUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f0ac80f189a04b5c826f40c8bc8af64 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: