diff --git a/CHANGELOG-EN.md b/CHANGELOG-EN.md index 93be7516..27f01f9d 100644 --- a/CHANGELOG-EN.md +++ b/CHANGELOG-EN.md @@ -32,6 +32,7 @@ ## Latest +* (2021.03.10) Added `CandlestickChart` #124 * (2021.03.06) Added `PieChart`'s `minAngle` parameter to support setting minimum sector angle #117 * (2021.03.05) Added support for `Legend` for several built-in ICONS #90 * (2021.03.02) Added `DataRoom` support for value axes #71 diff --git a/CHANGELOG.md b/CHANGELOG.md index 908b5446..252979e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ ## Latest +* (2021.03.10) 增加`CandlestickChart`K线图 #124 * (2021.03.06) 增加`PieChart`的`minAngle`参数支持设置最小扇区角度 #117 * (2021.03.05) 增加`Legend`几种内置图标的支持 #90 * (2021.03.02) 增加`DataZoom`对数值轴的支持 #71 diff --git a/Documentation/XCharts配置项手册.md b/Documentation/XCharts配置项手册.md index 64550219..70cfeb9f 100644 --- a/Documentation/XCharts配置项手册.md +++ b/Documentation/XCharts配置项手册.md @@ -23,6 +23,7 @@ * [Serie-Gauge 仪表盘](#Serie-Gauge) * [Serie-Ring 环形图](#Serie-Ring) * [Serie-Liquid 水位图](#Serie-Liquid) +* [Serie-Candlestick K线图](#Serie-Candlestick) * [Settings 设置](#Settings) * [Theme 主题](#Theme) * [Tooltip 提示框](#Tooltip) @@ -750,6 +751,29 @@ * `animation`:起始动画 [SerieAnimation](#SerieAnimation)。 * `data`:系列中的数据项 [SerieData](#SerieData) 数组,可以设置`1`到`n`维数据。水位图的数据一般只有一个,表示当前水位值,用`max`设置最大水位值。 +## `Serie-Candlestick` + +K线图系列。 + +* `show`:系列是否显示在图表上。 +* `type`:`Candlestick`。 +* `name`:系列名称。用于 `tooltip` 的显示,`legend` 的图例筛选。 +* `xAxisIndex`:使用的坐标轴X轴的 `index`,在单个图表实例中存在多个坐标轴的时候有用。 +* `yAxisIndex`:使用的坐标轴Y轴的 `index`,在单个图表实例中存在多个坐标轴的时候有用。 +* `minShow`:系列显示数据的最小索引。 +* `maxShow`:系列显示数据的最大索引。 +* `maxCache`:系列中可缓存的最大数据量。默认为`0`没有限制,大于0时超过指定值会移除旧数据再插入新数据。 +* `clip`:是否裁剪超出坐标系部分的图形。 +* `ignore`:是否开启忽略数据。当为 `true` 时,数据值为 `ignoreValue` 时不进行绘制。 +* `ignoreValue`:忽略数据的默认值。默认值默认为0,当 `ignore` 为 `true` 才有效。 +* `showAsPositiveNumber`:将负数数值显示为正数。一般和`AxisLabel`的`showAsPositiveNumber`配合使用。仅在折线图和柱状图中有效。 +* `large`:是否开启大数据量优化,在数据图形特别多而出现卡顿时候可以开启。开启后配合 largeThreshold 在数据量大于指定阈值的时候对绘制进行优化。缺点:优化后不能自定义设置单个数据项的样式,不能显示Label,折线图不绘制Symbol。 +* `largeThreshold`:开启大数量优化的阈值。只有当开启了large并且数据量大于该阀值时才进入性能模式。 +* `itemStyle`:环形图的圆环样式,包括设置背景颜色和边框等 [ItemStyle](#ItemStyle)。 +* `emphasis`:高亮样式 [Emphasis](#Emphasis)。 +* `animation`:起始动画 [SerieAnimation](#SerieAnimation)。 +* `data`:系列中的数据项 [SerieData](#SerieData) 数组,K线图至少需要4个维度的数组`[open, close, lowest, highest]`。 + ## `Settings` 全局参数设置组件。一般情况下可使用默认值,当有需要时可进行调整。 @@ -846,7 +870,8 @@ ## `ItemStyle` * `show`:是否启用。 -* `color`:颜色。 +* `color`:颜色。对于K线图,对应阳线的颜色。 +* `color0`:颜色。对于K线图,对应阴线的颜色。 * `toColor`:渐变颜色1。 * `toColor2`:渐变颜色2。只在折线图中有效。 * `backgroundColor`:背景颜色。 @@ -854,7 +879,8 @@ * `centerColor`:中心区域的颜色。如环形图的中心区域。 * `centerGap`:中心区域的间隙。如环形图的中心区域于最内环的间隙。 * `borderType`:边框的类型。 -* `borderColor`:边框的颜色。 +* `borderColor`:边框的颜色。对于K线图,对应阳线的边框颜色。 +* `borderColor0`:边框的颜色。对于K线图,对应阴线的边框颜色。 * `borderWidth`:边框宽。 * `opacity`:透明度。 * `tooltipFormatter`:提示框单项的字符串模版格式器。具体配置参考`Tooltip`的`formatter`。 diff --git a/Documentation/xcharts-configuration-EN.md b/Documentation/xcharts-configuration-EN.md index 3dd77cdc..2d89fc0e 100644 --- a/Documentation/xcharts-configuration-EN.md +++ b/Documentation/xcharts-configuration-EN.md @@ -25,6 +25,7 @@ __Main component:__ * [Serie-Gauge](#Serie-Gauge) * [Serie-Ring](#Serie-Ring) * [Serie-Liquid](#Serie-Liquid) +* [Serie-Candlestick](#Serie-Candlestick) * [Settings](#Settings) * [Theme](#Theme) * [Title](#Title) @@ -643,6 +644,29 @@ Line chart serie. * `animation`: 起始动画 [SerieAnimation](#SerieAnimation)。 * `data`: 系列中的数据项 [SerieData](#SerieData) 数组,可以设置`1`到`n`维数据。水位图的数据一般只有一个,表示当前水位值,用`max`设置最大水位值。 +## `Serie-Candlestick` + +K线图系列。 + +* `show`:系列是否显示在图表上。 +* `type`:`Candlestick`。 +* `name`:系列名称。用于 `tooltip` 的显示,`legend` 的图例筛选。 +* `xAxisIndex`:使用的坐标轴X轴的 `index`,在单个图表实例中存在多个坐标轴的时候有用。 +* `yAxisIndex`:使用的坐标轴Y轴的 `index`,在单个图表实例中存在多个坐标轴的时候有用。 +* `minShow`:系列显示数据的最小索引。 +* `maxShow`:系列显示数据的最大索引。 +* `maxCache`:系列中可缓存的最大数据量。默认为`0`没有限制,大于0时超过指定值会移除旧数据再插入新数据。 +* `clip`:是否裁剪超出坐标系部分的图形。 +* `ignore`:是否开启忽略数据。当为 `true` 时,数据值为 `ignoreValue` 时不进行绘制。 +* `ignoreValue`:忽略数据的默认值。默认值默认为0,当 `ignore` 为 `true` 才有效。 +* `showAsPositiveNumber`:将负数数值显示为正数。一般和`AxisLabel`的`showAsPositiveNumber`配合使用。仅在折线图和柱状图中有效。 +* `large`:是否开启大数据量优化,在数据图形特别多而出现卡顿时候可以开启。开启后配合 largeThreshold 在数据量大于指定阈值的时候对绘制进行优化。缺点:优化后不能自定义设置单个数据项的样式,不能显示Label,折线图不绘制Symbol。 +* `largeThreshold`:开启大数量优化的阈值。只有当开启了large并且数据量大于该阀值时才进入性能模式。 +* `itemStyle`:环形图的圆环样式,包括设置背景颜色和边框等 [ItemStyle](#ItemStyle)。 +* `emphasis`:高亮样式 [Emphasis](#Emphasis)。 +* `animation`:起始动画 [SerieAnimation](#SerieAnimation)。 +* `data`:系列中的数据项 [SerieData](#SerieData) 数组,K线图至少需要4个维度的数组`[open, close, lowest, highest]`。 + ## `Settings` 全局参数设置组件。一般情况下可使用默认值,当有需要时可进行调整。 @@ -739,15 +763,17 @@ Line chart serie. ## `ItemStyle` * `show`: 是否启用。 -* `color`: 颜色。 -* `toColor`:gradient color1. -* `toColor2`:gradient color2. +* `color`: 颜色。对于K线图,对应阳线的颜色。 +* `color0`: 颜色。对于K线图,对应阴线的颜色。 +* `toColor`: 渐变颜色1。 +* `toColor2`: 渐变颜色2。只在折线图中有效。 * `backgroundColor`: 背景颜色。 * `backgroundWidth`: 背景的宽。 * `centerColor`: 中心区域的颜色。如环形图的中心区域。 * `centerGap`: 中心区域的间隙。如环形图的中心区域于最内环的间隙。 * `borderType`: 边框的类型。 -* `borderColor`: 边框的颜色。 +* `borderColor`: 边框的颜色。对于K线图,对应阳线的边框颜色。 +* `borderColor0`: 边框的颜色。对于K线图,对应阴线的边框颜色。 * `borderWidth`: 边框宽。 * `opacity`: 透明度。 * `tooltipFormatter`: 提示框单项的字符串模版格式器。具体配置参考`Tooltip`的`formatter`。 @@ -776,8 +802,8 @@ Line chart serie. * `DashDot`: 点划线。 * `DashDotDot`: 双点划线。 * `color`: 线条颜色。默认和 `serie` 一致。 -* `toColor`:线的渐变颜色(需要水平方向渐变时)。 -* `toColor2`:线的渐变颜色2(需要水平方向三个渐变色的渐变时)。 +* `toColor`: 线的渐变颜色(需要水平方向渐变时)。 +* `toColor2`: 线的渐变颜色2(需要水平方向三个渐变色的渐变时)。 * `width`: 线条宽。 * `opacity`: 线条的透明度。支持从 `0` 到 `1` 的数字,为 `0` 时不绘制该图形。 diff --git a/Editor/CandlestickChartEditor.cs b/Editor/CandlestickChartEditor.cs new file mode 100644 index 00000000..6d3c047f --- /dev/null +++ b/Editor/CandlestickChartEditor.cs @@ -0,0 +1,24 @@ +/************************************************/ +/* */ +/* Copyright (c) 2018 - 2021 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/************************************************/ + +using UnityEditor; + +namespace XCharts +{ + /// + /// Editor class used to edit UI CandlestickChart. + /// + [CustomEditor(typeof(CandlestickChart), false)] + public class CandlestickChartEditor : CoordinateChartEditor + { + protected override void OnEnable() + { + base.OnEnable(); + m_Chart = (CandlestickChart)target; + } + } +} \ No newline at end of file diff --git a/Editor/CandlestickChartEditor.cs.meta b/Editor/CandlestickChartEditor.cs.meta new file mode 100644 index 00000000..d28d3852 --- /dev/null +++ b/Editor/CandlestickChartEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1bc2ab7ace40d480e839052673b65a5d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PropertyDrawers/ComponentThemeDrawer.cs b/Editor/PropertyDrawers/ComponentThemeDrawer.cs index 57e1bda4..e2eab097 100644 --- a/Editor/PropertyDrawers/ComponentThemeDrawer.cs +++ b/Editor/PropertyDrawers/ComponentThemeDrawer.cs @@ -179,6 +179,11 @@ namespace XCharts PropertyField(prop, "m_ScatterSymbolSelectedSize"); PropertyField(prop, "m_PieTooltipExtraRadius"); PropertyField(prop, "m_PieSelectedOffset"); + PropertyField(prop, "m_CandlestickColor"); + PropertyField(prop, "m_CandlestickColor0"); + PropertyField(prop, "m_CandlestickBorderColor"); + PropertyField(prop, "m_CandlestickBorderColor0"); + PropertyField(prop, "m_CandlestickBorderWidth"); --EditorGUI.indentLevel; } } diff --git a/Editor/PropertyDrawers/ItemStyleDrawer.cs b/Editor/PropertyDrawers/ItemStyleDrawer.cs index 5f6cc7c0..b1211965 100644 --- a/Editor/PropertyDrawers/ItemStyleDrawer.cs +++ b/Editor/PropertyDrawers/ItemStyleDrawer.cs @@ -22,6 +22,7 @@ namespace XCharts { ++EditorGUI.indentLevel; PropertyField(prop, "m_Color"); + PropertyField(prop, "m_Color0"); PropertyField(prop, "m_ToColor"); PropertyField(prop, "m_ToColor2"); PropertyField(prop, "m_BackgroundColor"); @@ -31,6 +32,7 @@ namespace XCharts PropertyField(prop, "m_BorderType"); PropertyField(prop, "m_BorderWidth"); PropertyField(prop, "m_BorderColor"); + PropertyField(prop, "m_BorderColor0"); PropertyField(prop, "m_Opacity"); PropertyField(prop, "m_TooltipFormatter"); PropertyField(prop, "m_NumericFormatter"); diff --git a/Editor/PropertyDrawers/SerieDrawer.cs b/Editor/PropertyDrawers/SerieDrawer.cs index 40e7d8a2..f2cbed49 100644 --- a/Editor/PropertyDrawers/SerieDrawer.cs +++ b/Editor/PropertyDrawers/SerieDrawer.cs @@ -182,9 +182,23 @@ namespace XCharts PropertyField(prop, "m_ItemStyle"); PropertyField(prop, "m_Label"); break; + case SerieType.Candlestick: + PropertyField(prop, "m_XAxisIndex"); + PropertyField(prop, "m_YAxisIndex"); + PropertyFieldLimitMin(prop, "m_MinShow", 0); + PropertyFieldLimitMin(prop, "m_MaxShow", 0); + PropertyFieldLimitMin(prop, "m_MaxCache", 0); + PropertyField(prop, "m_BarWidth"); + PropertyField(prop, "m_Clip"); + PropertyField(prop, "m_ShowAsPositiveNumber"); + 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"); - //PropertyListField(prop, "m_Data"); DrawData(pos, prop, serieType, ref m_DrawRect); --EditorGUI.indentLevel; } diff --git a/Editor/XChartEditor.cs b/Editor/XChartEditor.cs index 9568423a..e7364e1a 100644 --- a/Editor/XChartEditor.cs +++ b/Editor/XChartEditor.cs @@ -146,6 +146,13 @@ namespace XCharts AddChart("LiquidChart"); } + [MenuItem("XCharts/CandlestickChart", priority = 54)] + [MenuItem("GameObject/XCharts/CandlestickChart", priority = 54)] + public static void AddCandlestickChart() + { + AddChart("CandlestickChart"); + } + [MenuItem("XCharts/Themes Reload")] public static void ReloadTheme() { diff --git a/Examples/Runtime/Example90_Candlestick.cs b/Examples/Runtime/Example90_Candlestick.cs new file mode 100644 index 00000000..ed58a618 --- /dev/null +++ b/Examples/Runtime/Example90_Candlestick.cs @@ -0,0 +1,75 @@ +/************************************************/ +/* */ +/* Copyright (c) 2018 - 2021 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/************************************************/ + + +using UnityEngine; + +namespace XCharts.Examples +{ + [DisallowMultipleComponent] + [ExecuteInEditMode] + public class Example90_Candlestick : MonoBehaviour + { + private CandlestickChart chart; + private float updateTime; + + void Awake() + { + chart = gameObject.GetComponent(); + if (chart == null) + { + chart = gameObject.AddComponent(); + } + GenerateOHLC(1000); + } + + void Update() + { + if (Input.GetKeyDown(KeyCode.Space)) + { + AddData(); + } + } + + void AddData() + { + } + + void GenerateOHLC(int count) + { + 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; + + for (int i = 0; i < count; i++) + { + baseValue = baseValue + Random.Range(0f,1f) * 30 - 10; + for(int j=0;j< 4;j++){ + boxVals[j] = (Random.Range(0f,1f) - 0.5f) * dayRange + baseValue; + } + System.Array.Sort(boxVals); + var openIdx = Mathf.RoundToInt(Random.Range(0f,1f) * 3); + var closeIdx = Mathf.RoundToInt(Random.Range(0f,1f) * 2); + if(openIdx == closeIdx){ + closeIdx ++; + } + var volumn = boxVals[3]*(1000+Random.Range(0f,1f) * 500); + var open = boxVals[openIdx]; + var close = boxVals[closeIdx]; + var lowest = boxVals[0]; + var heighest = boxVals[3]; + + chart.AddXAxisData(i.ToString()); + chart.AddData(0,open,close,lowest,heighest); + } + } + } +} \ No newline at end of file diff --git a/Examples/Runtime/Example90_Candlestick.cs.meta b/Examples/Runtime/Example90_Candlestick.cs.meta new file mode 100644 index 00000000..189a3727 --- /dev/null +++ b/Examples/Runtime/Example90_Candlestick.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 69c7f3bf337c843888b4a7031eef1027 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/API/BaseChart_API.cs b/Runtime/API/BaseChart_API.cs index 8f31e40e..924cb98d 100644 --- a/Runtime/API/BaseChart_API.cs +++ b/Runtime/API/BaseChart_API.cs @@ -317,6 +317,20 @@ namespace XCharts } return serieData; } + public virtual SerieData AddData(int serieIndex, float open, float close, float lowest, float heighest, string dataName = null) + { + var serieData = m_Series.AddData(serieIndex, open, close, lowest,heighest, dataName); + if (serieData != null) + { + var serie = m_Series.GetSerie(serieIndex); + if (SerieHelper.GetSerieLabel(serie, serieData).show) + { + RefreshLabel(); + } + RefreshPainter(serie); + } + return serieData; + } /// /// Update serie data by serie name. diff --git a/Runtime/CandlestickChart.cs b/Runtime/CandlestickChart.cs new file mode 100644 index 00000000..975762d5 --- /dev/null +++ b/Runtime/CandlestickChart.cs @@ -0,0 +1,36 @@ + +/************************************************/ +/* */ +/* Copyright (c) 2018 - 2021 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/************************************************/ + +using UnityEngine; + +namespace XCharts +{ + [AddComponentMenu("XCharts/CandlestickChart", 21)] + [ExecuteInEditMode] + [RequireComponent(typeof(RectTransform))] + [DisallowMultipleComponent] + public class CandlestickChart : CoordinateChart + { + +#if UNITY_EDITOR + protected override void Reset() + { + base.Reset(); + title.text = "CandlestickChart"; + tooltip.type = Tooltip.Type.Corss; + + RemoveData(); + var defaultDataCount = SerieTemplate.AddDefaultCandlestickSerie(this, "serie1"); + for (int i = 0; i < defaultDataCount; i++) + { + AddXAxisData("x" + (i + 1)); + } + } +#endif + } +} diff --git a/Runtime/CandlestickChart.cs.meta b/Runtime/CandlestickChart.cs.meta new file mode 100644 index 00000000..359211cf --- /dev/null +++ b/Runtime/CandlestickChart.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b64f0bb738cc4acfa72fff2c30212b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Component/Main/Serie.cs b/Runtime/Component/Main/Serie.cs index 71c4f1bb..a89a273e 100644 --- a/Runtime/Component/Main/Serie.cs +++ b/Runtime/Component/Main/Serie.cs @@ -60,6 +60,10 @@ namespace XCharts /// 水位图。 /// Liquid, + /// + /// K线图。K线图的data至少包含四个数据:[open, close, lowest, highest] + /// + Candlestick, } /// @@ -1241,6 +1245,23 @@ namespace XCharts return serieData; } + public SerieData AddData(float open, float close, float lowest, float heighest, string dataName = null) + { + CheckMaxCache(); + var serieData = SerieDataPool.Get(); + serieData.data.Add(open); + serieData.data.Add(close); + serieData.data.Add(lowest); + serieData.data.Add(heighest); + serieData.name = dataName; + serieData.index = m_Data.Count; + m_Data.Add(serieData); + m_ShowDataDimension = 4; + SetVerticesDirty(); + CheckDataName(dataName); + return serieData; + } + /// /// 将一组数据添加到系列中。 /// 如果数据只有一个,默认添加到维度Y中。 @@ -1277,6 +1298,7 @@ namespace XCharts } } + private void CheckMaxCache() { if (m_MaxCache <= 0) return; diff --git a/Runtime/Component/Main/Series.cs b/Runtime/Component/Main/Series.cs index 256c6684..bd7cb1fe 100644 --- a/Runtime/Component/Main/Series.cs +++ b/Runtime/Component/Main/Series.cs @@ -307,6 +307,36 @@ namespace XCharts return null; } + /// + /// 添加[open, close, lowest, highest]数据 + /// + /// + /// + /// + /// + /// + /// + /// + public SerieData AddData(int index, float open, float close, float lowest, float heighest, string dataName = null) + { + var serie = GetSerie(index); + if (serie != null) + { + return serie.AddData(open, close, lowest, heighest, dataName); + } + return null; + } + + public SerieData AddData(string serieName, float open, float close, float lowest, float heighest, string dataName = null) + { + var serie = GetSerie(serieName); + if (serie != null) + { + return serie.AddData(open, close, lowest, heighest, dataName); + } + return null; + } + /// /// 添加一组数据到指定的系列中 /// diff --git a/Runtime/Component/Sub/ItemStyle.cs b/Runtime/Component/Sub/ItemStyle.cs index 13beb697..df3e5c76 100644 --- a/Runtime/Component/Sub/ItemStyle.cs +++ b/Runtime/Component/Sub/ItemStyle.cs @@ -37,6 +37,7 @@ namespace XCharts } [SerializeField] private bool m_Show = false; [SerializeField] private Color32 m_Color; + [SerializeField] private Color32 m_Color0; [SerializeField] private Color32 m_ToColor; [SerializeField] private Color32 m_ToColor2; [SerializeField] private Color32 m_BackgroundColor; @@ -46,6 +47,7 @@ namespace XCharts [SerializeField] private Type m_BorderType = Type.Solid; [SerializeField] private float m_BorderWidth = 0; [SerializeField] private Color32 m_BorderColor; + [SerializeField] private Color32 m_BorderColor0; [SerializeField] [Range(0, 1)] private float m_Opacity = 1; [SerializeField] private string m_TooltipFormatter; [SerializeField] private string m_NumericFormatter = ""; @@ -55,6 +57,7 @@ namespace XCharts { m_Show = false; m_Color = Color.clear; + m_Color0 = Color.clear; m_ToColor = Color.clear; m_ToColor2 = Color.clear; m_BackgroundColor = Color.clear; @@ -64,6 +67,7 @@ namespace XCharts m_BorderType = Type.Solid; m_BorderWidth = 0; m_BorderColor = Color.clear; + m_BorderColor0 = Color.clear; m_Opacity = 1; m_TooltipFormatter = null; m_NumericFormatter = ""; @@ -95,6 +99,14 @@ namespace XCharts set { if (PropertyUtil.SetColor(ref m_Color, value)) SetVerticesDirty(); } } /// + /// 数据项颜色。 + /// + public Color32 color0 + { + get { return m_Color0; } + set { if (PropertyUtil.SetColor(ref m_Color0, value)) SetVerticesDirty(); } + } + /// /// Gradient color1. /// 渐变色的颜色1。 /// @@ -161,6 +173,14 @@ namespace XCharts set { if (PropertyUtil.SetColor(ref m_BorderColor, value)) SetVerticesDirty(); } } /// + /// 边框的颜色。 + /// + public Color32 borderColor0 + { + get { return m_BorderColor0; } + set { if (PropertyUtil.SetColor(ref m_BorderColor0, value)) SetVerticesDirty(); } + } + /// /// 边框宽。 /// public float borderWidth @@ -225,6 +245,41 @@ namespace XCharts color.a = (byte)(color.a * m_Opacity); return color; } + public Color32 GetColor0() + { + if (m_Opacity == 1 || m_Color0.a == 0) return m_Color0; + var color = m_Color0; + color.a = (byte)(color.a * m_Opacity); + return color; + } + public Color32 GetColor(Color32 defaultColor) + { + var color = ChartHelper.IsClearColor(m_Color) ? defaultColor : m_Color; + if (m_Opacity == 1 || color.a == 0) return color; + color.a = (byte)(color.a * m_Opacity); + return color; + } + public Color32 GetColor0(Color32 defaultColor) + { + var color = ChartHelper.IsClearColor(m_Color0) ? defaultColor : m_Color0; + if (m_Opacity == 1 || color.a == 0) return color; + color.a = (byte)(color.a * m_Opacity); + return color; + } + public Color32 GetBorderColor(Color32 defaultColor) + { + var color = ChartHelper.IsClearColor(m_BorderColor) ? defaultColor : m_BorderColor; + if (m_Opacity == 1 || color.a == 0) return color; + color.a = (byte)(color.a * m_Opacity); + return color; + } + public Color32 GetBorderColor0(Color32 defaultColor) + { + var color = ChartHelper.IsClearColor(m_BorderColor0) ? defaultColor : m_BorderColor0; + if (m_Opacity == 1 || color.a == 0) return color; + color.a = (byte)(color.a * m_Opacity); + return color; + } public bool IsNeedGradient() { diff --git a/Runtime/Component/Sub/SerieData.cs b/Runtime/Component/Sub/SerieData.cs index 6a641e80..6966a7f5 100644 --- a/Runtime/Component/Sub/SerieData.cs +++ b/Runtime/Component/Sub/SerieData.cs @@ -121,12 +121,36 @@ namespace XCharts /// the maxinum value. /// 最大值。 /// - public float max { get { return m_Data.Max(); } } + public float max + { + get + { + if (m_Data.Count == 0) return 0; + float temp = float.MinValue; + for (int i = 0; i < m_Data.Count; i++) + { + if (m_Data[i] > temp) temp = m_Data[i]; + } + return temp; + } + } /// /// the mininum value. /// 最小值。 /// - public float min { get { return m_Data.Min(); } } + public float min + { + get + { + if (m_Data.Count == 0) return 0; + float temp = float.MaxValue; + for (int i = 0; i < m_Data.Count; i++) + { + if (m_Data[i] < temp) temp = m_Data[i]; + } + return temp; + } + } /// /// 饼图数据项的开始角度(运行时自动计算) /// diff --git a/Runtime/Component/Theme/SerieTheme.cs b/Runtime/Component/Theme/SerieTheme.cs index 5a30899b..2db1b42d 100644 --- a/Runtime/Component/Theme/SerieTheme.cs +++ b/Runtime/Component/Theme/SerieTheme.cs @@ -20,6 +20,11 @@ namespace XCharts [SerializeField] protected float m_ScatterSymbolSelectedSize; [SerializeField] protected float m_PieTooltipExtraRadius; [SerializeField] protected float m_PieSelectedOffset; + [SerializeField] protected Color32 m_CandlestickColor = new Color32(194, 53, 49, 255); + [SerializeField] protected Color32 m_CandlestickColor0 = new Color32(49, 70, 86, 255); + [SerializeField] protected float m_CandlestickBorderWidth = 1; + [SerializeField] protected Color32 m_CandlestickBorderColor = new Color32(194, 53, 49, 255); + [SerializeField] protected Color32 m_CandlestickBorderColor0 = new Color32(49, 70, 86, 255); /// /// the color of text. @@ -67,6 +72,47 @@ namespace XCharts get { return m_PieSelectedOffset; } set { if (PropertyUtil.SetStruct(ref m_PieSelectedOffset, value < 0 ? 0f : value)) SetVerticesDirty(); } } + /// + /// K线图阳线(涨)填充色 + /// + public Color32 candlestickColor + { + get { return m_CandlestickColor; } + set { if (PropertyUtil.SetColor(ref m_CandlestickColor, value)) SetVerticesDirty(); } + } + /// + /// K线图阴线(跌)填充色 + /// + public Color32 candlestickColor0 + { + get { return m_CandlestickColor0; } + set { if (PropertyUtil.SetColor(ref m_CandlestickColor0, value)) SetVerticesDirty(); } + } + /// + /// K线图阳线(跌)边框色 + /// + public Color32 candlestickBorderColor + { + get { return m_CandlestickBorderColor; } + set { if (PropertyUtil.SetColor(ref m_CandlestickBorderColor, value)) SetVerticesDirty(); } + } + /// + /// K线图阴线(跌)边框色 + /// + public Color32 candlestickBorderColor0 + { + get { return m_CandlestickBorderColor0; } + set { if (PropertyUtil.SetColor(ref m_CandlestickBorderColor0, value)) SetVerticesDirty(); } + } + + /// + /// K线图边框宽度 + /// + public float candlestickBorderWidth + { + get { return m_CandlestickBorderWidth; } + set { if (PropertyUtil.SetStruct(ref m_CandlestickBorderWidth, value < 0 ? 0f : value)) SetVerticesDirty(); } + } public void Copy(SerieTheme theme) { @@ -77,6 +123,11 @@ namespace XCharts m_ScatterSymbolSelectedSize = theme.scatterSymbolSelectedSize; m_PieTooltipExtraRadius = theme.pieTooltipExtraRadius; m_PieSelectedOffset = theme.pieSelectedOffset; + m_CandlestickColor = theme.candlestickColor; + m_CandlestickColor0 = theme.candlestickColor0; + m_CandlestickBorderColor = theme.candlestickBorderColor; + m_CandlestickBorderColor0 = theme.candlestickBorderColor0; + m_CandlestickBorderWidth = theme.candlestickBorderWidth; } public SerieTheme(Theme theme) @@ -88,6 +139,28 @@ namespace XCharts m_ScatterSymbolSelectedSize = XChartsSettings.serieScatterSymbolSelectedSize; m_PieTooltipExtraRadius = XChartsSettings.pieTooltipExtraRadius; m_PieSelectedOffset = XChartsSettings.pieSelectedOffset; + m_CandlestickBorderWidth = XChartsSettings.serieCandlestickBorderWidth; + switch (theme) + { + case Theme.Default: + m_CandlestickColor = ColorUtil.GetColor("#c23531"); + m_CandlestickColor0 = ColorUtil.GetColor("#314656"); + m_CandlestickBorderColor = ColorUtil.GetColor("#c23531"); + m_CandlestickBorderColor0 = ColorUtil.GetColor("#314656"); + break; + case Theme.Light: + m_CandlestickColor = ColorUtil.GetColor("#c23531"); + m_CandlestickColor0 = ColorUtil.GetColor("#314656"); + m_CandlestickBorderColor = ColorUtil.GetColor("#c23531"); + m_CandlestickBorderColor0 = ColorUtil.GetColor("#314656"); + break; + case Theme.Dark: + m_CandlestickColor = ColorUtil.GetColor("#c23531"); + m_CandlestickColor0 = ColorUtil.GetColor("#314656"); + m_CandlestickBorderColor = ColorUtil.GetColor("#c23531"); + m_CandlestickBorderColor0 = ColorUtil.GetColor("#314656"); + break; + } } } } \ No newline at end of file diff --git a/Runtime/Internal/CoordinateChart.cs b/Runtime/Internal/CoordinateChart.cs index 246d7d99..8f2e9e2b 100644 --- a/Runtime/Internal/CoordinateChart.cs +++ b/Runtime/Internal/CoordinateChart.cs @@ -175,6 +175,9 @@ namespace XCharts case SerieType.Heatmap: DrawHeatmapSerie(vh, colorIndex, serie); break; + case SerieType.Candlestick: + DrawCandlestickSerie(vh, colorIndex, serie); + break; } } @@ -846,7 +849,7 @@ namespace XCharts if (dataZoom != null && dataZoom.enable) { if (axis is XAxis) dataZoom.SetXAxisIndexValueInfo(axisIndex, tempMinValue, tempMaxValue); - else dataZoom.SetXAxisIndexValueInfo(axisIndex, tempMinValue, tempMaxValue); + else dataZoom.SetYAxisIndexValueInfo(axisIndex, tempMinValue, tempMaxValue); } if (updateChart) { diff --git a/Runtime/Internal/CoordinateChart_DrawBar.cs b/Runtime/Internal/CoordinateChart_DrawBar.cs index 5defaeb0..ce16369f 100644 --- a/Runtime/Internal/CoordinateChart_DrawBar.cs +++ b/Runtime/Internal/CoordinateChart_DrawBar.cs @@ -587,7 +587,7 @@ namespace XCharts for (int i = 0; i < m_Series.Count; i++) { var serie = m_Series.list[i]; - if (serie.type == SerieType.Bar) + if (serie.type == SerieType.Bar || serie.type == SerieType.Candlestick) { if (serie.barGap != 0) { @@ -625,7 +625,8 @@ namespace XCharts for (int i = 0; i < m_Series.Count; i++) { var serie = m_Series.list[i]; - if (serie.type == SerieType.Bar && serie.show) + if (!serie.show) continue; + if (serie.type == SerieType.Bar || serie.type == SerieType.Candlestick) { if (!string.IsNullOrEmpty(serie.stack)) { @@ -656,7 +657,8 @@ namespace XCharts for (int i = 0; i < m_Series.Count; i++) { var serie = m_Series.list[i]; - if (serie.type == SerieType.Bar && serie.show && now.stack.Equals(serie.stack)) + if ((serie.type == SerieType.Bar && serie.type == SerieType.Candlestick) + && serie.show && now.stack.Equals(serie.stack)) { if (serie.barWidth > barWidth) barWidth = serie.barWidth; } diff --git a/Runtime/Internal/CoordinateChart_DrawCandlestick.cs b/Runtime/Internal/CoordinateChart_DrawCandlestick.cs new file mode 100644 index 00000000..b00afe68 --- /dev/null +++ b/Runtime/Internal/CoordinateChart_DrawCandlestick.cs @@ -0,0 +1,150 @@ +/************************************************/ +/* */ +/* Copyright (c) 2018 - 2021 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/************************************************/ + +using UnityEngine; +using UnityEngine.UI; +using XUGL; + +namespace XCharts +{ + public partial class CoordinateChart + { + protected void DrawCandlestickSerie(VertexHelper vh, int colorIndex, Serie serie) + { + if (!IsActive(serie.index)) return; + if (serie.animation.HasFadeOut()) return; + var showData = serie.GetDataList(dataZoom); + var yAxis = m_YAxes[serie.yAxisIndex]; + var xAxis = m_XAxes[serie.xAxisIndex]; + var grid = GetSerieGridOrDefault(serie); + float categoryWidth = AxisHelper.GetDataWidth(xAxis, grid.runtimeWidth, showData.Count, dataZoom); + float barGap = GetBarGap(); + float totalBarWidth = GetBarTotalWidth(categoryWidth, barGap); + float barWidth = serie.GetBarWidth(categoryWidth); + float offset = (categoryWidth - totalBarWidth) / 2; + float barGapWidth = barWidth + barWidth * barGap; + float space = serie.barGap == -1 ? offset : offset + GetBarIndex(serie) * barGapWidth; + int maxCount = serie.maxShow > 0 + ? (serie.maxShow > showData.Count ? showData.Count : serie.maxShow) + : showData.Count; + + bool dataChanging = false; + float dataChangeDuration = serie.animation.GetUpdateAnimationDuration(); + float yMinValue = yAxis.GetCurrMinValue(dataChangeDuration); + float yMaxValue = yAxis.GetCurrMaxValue(dataChangeDuration); + var isAllBarEnd = true; + var isYAxis = false; + for (int i = serie.minShow; i < maxCount; i++) + { + var serieData = showData[i]; + if (serie.IsIgnoreValue(serieData.GetData(1))) + { + serie.dataPoints.Add(Vector3.zero); + continue; + } + var highlight = (tooltip.show && tooltip.IsSelected(i)) + || serie.data[i].highlighted + || serie.highlighted; + var itemStyle = SerieHelper.GetItemStyle(serie, serieData, highlight); + var open = serieData.GetCurrData(0, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue); + var close = serieData.GetCurrData(1, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue); + var lowest = serieData.GetCurrData(2, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue); + var heighest = serieData.GetCurrData(3, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue); + var isRise = close > open; + var borderWidth = open == 0 ? 0f + : (itemStyle.runtimeBorderWidth == 0 ? m_Theme.serie.candlestickBorderWidth + : itemStyle.runtimeBorderWidth); + if (serieData.IsDataChanged()) dataChanging = true; + float pX = grid.runtimeX + i * categoryWidth; + float zeroY = grid.runtimeY + yAxis.runtimeZeroYOffset; + if (!xAxis.boundaryGap) pX -= categoryWidth / 2; + float pY = zeroY; + var barHig = 0f; + var valueTotal = yMaxValue - yMinValue; + var minCut = (yMinValue > 0 ? yMinValue : 0); + if (valueTotal != 0) + { + barHig = (close - open) / valueTotal * grid.runtimeHeight; + pY += (open - minCut) / valueTotal * grid.runtimeHeight; + } + serieData.runtimeStackHig = barHig; + var isBarEnd = false; + float currHig = CheckAnimation(serie, i, barHig, out isBarEnd); + if (!isBarEnd) isAllBarEnd = false; + Vector3 plb, plt, prt, prb, top; + + plb = new Vector3(pX + space + borderWidth, pY + borderWidth); + plt = new Vector3(pX + space + borderWidth, pY + currHig - borderWidth); + prt = new Vector3(pX + space + barWidth - borderWidth, pY + currHig - borderWidth); + prb = new Vector3(pX + space + barWidth - borderWidth, pY + borderWidth); + top = new Vector3(pX + space + barWidth / 2, pY + currHig - borderWidth); + if (serie.clip) + { + plb = ClampInGrid(grid, plb); + plt = ClampInGrid(grid, plt); + prt = ClampInGrid(grid, prt); + prb = ClampInGrid(grid, prb); + top = ClampInGrid(grid, top); + } + serie.dataPoints.Add(top); + var areaColor = isRise + ? itemStyle.GetColor(m_Theme.serie.candlestickColor) + : itemStyle.GetColor0(m_Theme.serie.candlestickColor0); + var borderColor = isRise + ? itemStyle.GetBorderColor(m_Theme.serie.candlestickBorderColor) + : itemStyle.GetBorderColor0(m_Theme.serie.candlestickBorderColor0); + var itemWidth = Mathf.Abs(prt.x - plb.x); + var itemHeight = Mathf.Abs(plt.y - prb.y); + var center = new Vector3((plb.x + prt.x) / 2, (plt.y + prb.y) / 2); + var lowPos = new Vector3(center.x, zeroY + (lowest - minCut) / valueTotal * grid.runtimeHeight); + var heighPos = new Vector3(center.x, zeroY + (heighest - minCut) / valueTotal * grid.runtimeHeight); + var openCenterPos = new Vector3(center.x, prb.y); + var closeCenterPos = new Vector3(center.x, prt.y); + if (barWidth > 2f * borderWidth) + { + if (itemWidth > 0 && itemHeight > 0) + { + if (ItemStyleHelper.IsNeedCorner(itemStyle)) + { + UGL.DrawRoundRectangle(vh, center, itemWidth, itemHeight, areaColor, areaColor, 0, + itemStyle.cornerRadius, isYAxis, 0.5f); + } + else + { + CheckClipAndDrawPolygon(vh, ref prb, ref plb, ref plt, ref prt, areaColor, areaColor, + serie.clip, grid); + } + UGL.DrawBorder(vh, center, itemWidth, itemHeight, 2 * borderWidth, borderColor, 0, + itemStyle.cornerRadius, isYAxis, 0.5f); + } + } + else + { + UGL.DrawLine(vh, openCenterPos, closeCenterPos, Mathf.Max(borderWidth, barWidth / 2), borderColor); + } + if (isRise) + { + UGL.DrawLine(vh, openCenterPos, lowPos, borderWidth, borderColor); + UGL.DrawLine(vh, closeCenterPos, heighPos, borderWidth, borderColor); + } + else + { + UGL.DrawLine(vh, closeCenterPos, lowPos, borderWidth, borderColor); + UGL.DrawLine(vh, openCenterPos, heighPos, borderWidth, borderColor); + } + } + if (isAllBarEnd) + { + serie.animation.AllBarEnd(); + } + if (dataChanging) + { + RefreshPainter(serie); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Internal/CoordinateChart_DrawCandlestick.cs.meta b/Runtime/Internal/CoordinateChart_DrawCandlestick.cs.meta new file mode 100644 index 00000000..5e60337f --- /dev/null +++ b/Runtime/Internal/CoordinateChart_DrawCandlestick.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c404a9bbfdf51409e808fb354c438ca2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Helper/SeriesHelper.cs b/Runtime/Internal/Helper/SeriesHelper.cs index aeca863f..50f88a3c 100644 --- a/Runtime/Internal/Helper/SeriesHelper.cs +++ b/Runtime/Internal/Helper/SeriesHelper.cs @@ -454,9 +454,19 @@ namespace XCharts var showData = serie.GetDataList(dataZoom); foreach (var data in showData) { - var currData = data.GetData(yValue ? 1 : 0, inverse); - if (currData > max) max = currData; - if (currData < min) min = currData; + if (serie.type == SerieType.Candlestick) + { + var dataMin = data.min; + var dataMax = data.max; + if (dataMax > max) max = dataMax; + if (dataMin < min) min = dataMin; + } + else + { + var currData = data.GetData(yValue ? 1 : 0, inverse); + if (currData > max) max = currData; + if (currData < min) min = currData; + } } } } @@ -488,7 +498,15 @@ namespace XCharts { if (!_serieTotalValueForMinMax.ContainsKey(j)) _serieTotalValueForMinMax[j] = 0; - var currData = (yValue ? showData[j].GetData(1) : showData[j].GetData(0)); + var currData = 0f; + if (serie.type == SerieType.Candlestick) + { + currData = showData[j].max; + } + else + { + currData = yValue ? showData[j].GetData(1) : showData[j].GetData(0); + } if (inverse) currData = -currData; _serieTotalValueForMinMax[j] = _serieTotalValueForMinMax[j] + currData; } diff --git a/Runtime/Internal/Helper/TooltipHelper.cs b/Runtime/Internal/Helper/TooltipHelper.cs index 75ebaa4f..d0f8d0ab 100644 --- a/Runtime/Internal/Helper/TooltipHelper.cs +++ b/Runtime/Internal/Helper/TooltipHelper.cs @@ -210,9 +210,25 @@ namespace XCharts { var valueTxt = isIngore ? tooltip.ignoreDataDefaultContent : ChartCached.FloatToStr(yValue, numericFormatter); - sb.Append("● ") - .Append(key).Append(!string.IsNullOrEmpty(key) ? " : " : "") - .Append(valueTxt); + sb.Append("● "); + if (serie.type == SerieType.Candlestick) + { + sb.Append(key).Append(FormatterHelper.PH_NN); + var data = serieData.data; + var open = ChartCached.FloatToStr(data[0], numericFormatter); + var close = ChartCached.FloatToStr(data[1], numericFormatter); + var lowest = ChartCached.FloatToStr(data[2], numericFormatter); + var heighest = ChartCached.FloatToStr(data[3], numericFormatter); + sb.Append(" open: ").Append(open).Append(FormatterHelper.PH_NN); + sb.Append(" close: ").Append(close).Append(FormatterHelper.PH_NN); + sb.Append(" lowest: ").Append(lowest).Append(FormatterHelper.PH_NN); + sb.Append(" heighest: ").Append(heighest).Append(FormatterHelper.PH_NN); + } + else + { + sb.Append(key).Append(!string.IsNullOrEmpty(key) ? " : " : ""); + sb.Append(valueTxt); + } } } @@ -224,6 +240,7 @@ namespace XCharts { case SerieType.Line: case SerieType.Bar: + case SerieType.Candlestick: InitCoordinateTooltip(ref sb, tooltip, serie, index, theme, isCartesian, dataZoom); break; case SerieType.Scatter: diff --git a/Runtime/Template/SerieTemplate.cs b/Runtime/Template/SerieTemplate.cs index 8b3f4ebc..ada7f05c 100644 --- a/Runtime/Template/SerieTemplate.cs +++ b/Runtime/Template/SerieTemplate.cs @@ -27,6 +27,7 @@ namespace XCharts case SerieType.Liquid: AddDefaultLiquidSerie(chart, serieName); break; case SerieType.Gauge: AddDefaultGaugeSerie(chart, serieName); break; case SerieType.Ring: AddDefaultRingSerie(chart, serieName); break; + case SerieType.Candlestick: AddDefaultCandlestickSerie(chart, serieName); break; default: Debug.LogError("AddDefaultSerie: not support serieType yet:" + serieType); break; } } @@ -168,5 +169,19 @@ namespace XCharts var max = 100; chart.AddData(serie.index, value, max, "data1"); } + public static int AddDefaultCandlestickSerie(BaseChart chart, string serieName) + { + var serie = chart.AddSerie(SerieType.Candlestick, serieName); + var defaultDataCount = 5; + for (int i = 0; i < defaultDataCount; i++) + { + var open = Random.Range(20, 60); + var close = Random.Range(40, 90); + var lowest = Random.Range(0, 50); + var heighest = Random.Range(50, 100); + chart.AddData(serie.index, open, close, lowest, heighest); + } + return defaultDataCount; + } } } \ No newline at end of file diff --git a/Runtime/XChartsSettings.cs b/Runtime/XChartsSettings.cs index c2fb5c41..85812dc6 100644 --- a/Runtime/XChartsSettings.cs +++ b/Runtime/XChartsSettings.cs @@ -50,6 +50,7 @@ namespace XCharts [SerializeField] [Range(0, 200)] private float m_SerieLineSymbolSelectedSize = 8f; [SerializeField] [Range(0, 200)] private float m_SerieScatterSymbolSize = 20f; [SerializeField] [Range(0, 200)] private float m_SerieScatterSymbolSelectedSize = 30f; + [SerializeField] [Range(0, 10)] private float m_SerieCandlestickBorderWidth = 1f; [SerializeField] private bool m_EditorBlockEnable = true; [SerializeField] private bool m_EditorShowAllListData = false; @@ -97,6 +98,7 @@ namespace XCharts public static float serieLineSymbolSelectedSize { get { return Instance.m_SerieLineSymbolSelectedSize; } } public static float serieScatterSymbolSize { get { return Instance.m_SerieScatterSymbolSize; } } public static float serieScatterSymbolSelectedSize { get { return Instance.m_SerieScatterSymbolSelectedSize; } } + public static float serieCandlestickBorderWidth { get { return Instance.m_SerieCandlestickBorderWidth; } } #endregion #region editor