From ed6939148e58fe17678b2fc97a9cb25f56f459ab Mon Sep 17 00:00:00 2001 From: monitor1394 Date: Wed, 1 Jul 2020 09:38:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0`PolarChart`=E6=9E=81?= =?UTF-8?q?=E5=9D=90=E6=A0=87=E5=9B=BE=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + Editor/PolarChartEditor.cs | 40 ++ Editor/PolarChartEditor.cs.meta | 11 + Editor/PropertyDrawers/AngleAxisDrawer.cs | 40 ++ .../PropertyDrawers/AngleAxisDrawer.cs.meta | 11 + Editor/PropertyDrawers/AxisDrawer.cs | 12 + Editor/PropertyDrawers/PolarDrawer.cs | 49 ++ Editor/PropertyDrawers/PolarDrawer.cs.meta | 11 + Editor/PropertyDrawers/RadiusAxisDrawer.cs | 24 + .../PropertyDrawers/RadiusAxisDrawer.cs.meta | 11 + Editor/XChartEditor.cs | 7 + Examples/Runtime/Example80_Polar.cs | 57 ++ Examples/Runtime/Example80_Polar.cs.meta | 11 + Runtime/API/PolarChart_API.cs | 30 + Runtime/API/PolarChart_API.cs.meta | 11 + Runtime/Component/Main/Axis.cs | 82 +++ Runtime/Component/Main/Polar.cs | 92 +++ Runtime/Component/Main/Polar.cs.meta | 11 + Runtime/Component/Main/Tooltip.cs | 6 +- Runtime/Component/Sub/SerieData.cs | 1 + Runtime/Internal/Helper/AxisHelper.cs | 49 +- Runtime/Internal/Helper/PolarHelper.cs | 34 + Runtime/Internal/Helper/PolarHelper.cs.meta | 11 + Runtime/Internal/Helper/SerieHelper.cs | 12 +- Runtime/Internal/Helper/TooltipHelper.cs | 42 ++ Runtime/PolarChart.cs | 592 ++++++++++++++++++ Runtime/PolarChart.cs.meta | 11 + Runtime/RadarChart.cs | 20 +- Runtime/Utility/ChartDrawer.cs | 70 +-- Runtime/Utility/ChartHelper.cs | 28 + 30 files changed, 1324 insertions(+), 63 deletions(-) create mode 100644 Editor/PolarChartEditor.cs create mode 100644 Editor/PolarChartEditor.cs.meta create mode 100644 Editor/PropertyDrawers/AngleAxisDrawer.cs create mode 100644 Editor/PropertyDrawers/AngleAxisDrawer.cs.meta create mode 100644 Editor/PropertyDrawers/PolarDrawer.cs create mode 100644 Editor/PropertyDrawers/PolarDrawer.cs.meta create mode 100644 Editor/PropertyDrawers/RadiusAxisDrawer.cs create mode 100644 Editor/PropertyDrawers/RadiusAxisDrawer.cs.meta create mode 100644 Examples/Runtime/Example80_Polar.cs create mode 100644 Examples/Runtime/Example80_Polar.cs.meta create mode 100644 Runtime/API/PolarChart_API.cs create mode 100644 Runtime/API/PolarChart_API.cs.meta create mode 100644 Runtime/Component/Main/Polar.cs create mode 100644 Runtime/Component/Main/Polar.cs.meta create mode 100644 Runtime/Internal/Helper/PolarHelper.cs create mode 100644 Runtime/Internal/Helper/PolarHelper.cs.meta create mode 100644 Runtime/PolarChart.cs create mode 100644 Runtime/PolarChart.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index 86fd8680..6eb1b21a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 更新日志 +* (2020.07.01) 增加`PolarChart`极坐标图表 * (2020.06.25) 发布`v1.5.2`版本 * (2020.06.25) 修复`BarChart`在数值为`0`时还会绘制一小部分柱条的问题 * (2020.06.24) 修复`PieChart`在设置`clockwise`后绘制异常的问题#65 diff --git a/Editor/PolarChartEditor.cs b/Editor/PolarChartEditor.cs new file mode 100644 index 00000000..3c8e4d00 --- /dev/null +++ b/Editor/PolarChartEditor.cs @@ -0,0 +1,40 @@ +/******************************************/ +/* */ +/* Copyright (c) 2018 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/******************************************/ + +using UnityEditor; + +namespace XCharts +{ + /// + /// Editor class used to edit UI PolarChart. + /// + + [CustomEditor(typeof(PolarChart), false)] + public class PolarChartEditor : BaseChartEditor + { + protected SerializedProperty m_Polar; + protected SerializedProperty m_RadiusAxis; + protected SerializedProperty m_AngleAxis; + + protected override void OnEnable() + { + base.OnEnable(); + m_Target = (PolarChart)target; + m_Polar = serializedObject.FindProperty("m_Polar"); + m_RadiusAxis = serializedObject.FindProperty("m_RadiusAxis"); + m_AngleAxis = serializedObject.FindProperty("m_AngleAxis"); + } + + protected override void OnStartInspectorGUI() + { + base.OnStartInspectorGUI(); + EditorGUILayout.PropertyField(m_Polar, true); + EditorGUILayout.PropertyField(m_RadiusAxis, true); + EditorGUILayout.PropertyField(m_AngleAxis, true); + } + } +} \ No newline at end of file diff --git a/Editor/PolarChartEditor.cs.meta b/Editor/PolarChartEditor.cs.meta new file mode 100644 index 00000000..1957252b --- /dev/null +++ b/Editor/PolarChartEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 157ef5f1d75e04aa1814e0b188591912 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PropertyDrawers/AngleAxisDrawer.cs b/Editor/PropertyDrawers/AngleAxisDrawer.cs new file mode 100644 index 00000000..76c1e8ab --- /dev/null +++ b/Editor/PropertyDrawers/AngleAxisDrawer.cs @@ -0,0 +1,40 @@ +/******************************************/ +/* */ +/* Copyright (c) 2018 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/******************************************/ + +using UnityEditor; +using UnityEngine; + +namespace XCharts +{ + [CustomPropertyDrawer(typeof(AngleAxis), true)] + public class AngleAxisDrawer : AxisDrawer + { + protected override void DrawExtended(ref Rect drawRect, SerializedProperty prop) + { + SerializedProperty m_StartAngle = prop.FindPropertyRelative("m_StartAngle"); + //SerializedProperty m_Clockwise = prop.FindPropertyRelative("m_Clockwise"); + EditorGUI.PropertyField(drawRect, m_StartAngle); + drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + //EditorGUI.PropertyField(drawRect, m_Clockwise); + //drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + } + + protected override string GetDisplayName(string displayName) + { + if (displayName.StartsWith("Element")) + { + displayName = displayName.Replace("Element", "Angle Axis"); + } + return displayName; + } + + protected override float GetExtendedHeight() + { + return 1 * EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + } + } +} \ No newline at end of file diff --git a/Editor/PropertyDrawers/AngleAxisDrawer.cs.meta b/Editor/PropertyDrawers/AngleAxisDrawer.cs.meta new file mode 100644 index 00000000..993985d5 --- /dev/null +++ b/Editor/PropertyDrawers/AngleAxisDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 521ea44136ea74a2f82a4c0c46edfd32 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PropertyDrawers/AxisDrawer.cs b/Editor/PropertyDrawers/AxisDrawer.cs index ae1209ed..6a244a62 100644 --- a/Editor/PropertyDrawers/AxisDrawer.cs +++ b/Editor/PropertyDrawers/AxisDrawer.cs @@ -100,6 +100,7 @@ namespace XCharts drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; EditorGUI.PropertyField(drawRect, m_BoundaryGap); drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + DrawExtended(ref drawRect, prop); EditorGUI.PropertyField(drawRect, m_AxisLine); drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; drawRect.y += EditorGUI.GetPropertyHeight(m_AxisLine); @@ -133,6 +134,11 @@ namespace XCharts } } + protected virtual void DrawExtended(ref Rect drawRect, SerializedProperty prop) + { + + } + public override float GetPropertyHeight(SerializedProperty prop, GUIContent label) { int index = InitToggle(prop); @@ -195,10 +201,16 @@ namespace XCharts height += EditorGUI.GetPropertyHeight(m_AxisLabel); height += EditorGUI.GetPropertyHeight(m_SplitArea); height += EditorGUI.GetPropertyHeight(m_SplitLine); + height += GetExtendedHeight(); return height; } } + protected virtual float GetExtendedHeight() + { + return 0; + } + private int InitToggle(SerializedProperty prop) { int index = 0; diff --git a/Editor/PropertyDrawers/PolarDrawer.cs b/Editor/PropertyDrawers/PolarDrawer.cs new file mode 100644 index 00000000..3be62a3c --- /dev/null +++ b/Editor/PropertyDrawers/PolarDrawer.cs @@ -0,0 +1,49 @@ +/******************************************/ +/* */ +/* Copyright (c) 2018 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/******************************************/ + +using UnityEditor; +using UnityEngine; + +namespace XCharts +{ + [CustomPropertyDrawer(typeof(Polar), true)] + public class PolarDrawer : PropertyDrawer + { + private bool m_PolarModuleToggle = false; + + public override void OnGUI(Rect pos, SerializedProperty prop, GUIContent label) + { + Rect drawRect = pos; + drawRect.height = EditorGUIUtility.singleLineHeight; + SerializedProperty show = prop.FindPropertyRelative("m_Show"); + SerializedProperty m_Center = prop.FindPropertyRelative("m_Center"); + SerializedProperty m_Radius = prop.FindPropertyRelative("m_Radius"); + SerializedProperty m_BackgroundColor = prop.FindPropertyRelative("m_BackgroundColor"); + + ChartEditorHelper.MakeFoldout(ref drawRect, ref m_PolarModuleToggle, "Polar", show); + drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + if (m_PolarModuleToggle) + { + EditorGUI.indentLevel++; + ChartEditorHelper.MakeTwoField(ref drawRect, pos.width, m_Center, "Center"); + EditorGUI.PropertyField(drawRect, m_Radius); + drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + EditorGUI.PropertyField(drawRect, m_BackgroundColor); + drawRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + EditorGUI.indentLevel--; + } + } + + public override float GetPropertyHeight(SerializedProperty prop, GUIContent label) + { + if (m_PolarModuleToggle) + return 4 * EditorGUIUtility.singleLineHeight + 3 * EditorGUIUtility.standardVerticalSpacing; + else + return EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + } + } +} \ No newline at end of file diff --git a/Editor/PropertyDrawers/PolarDrawer.cs.meta b/Editor/PropertyDrawers/PolarDrawer.cs.meta new file mode 100644 index 00000000..35d024ba --- /dev/null +++ b/Editor/PropertyDrawers/PolarDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 48ff9465776e54e749f9ff8c424bafe2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PropertyDrawers/RadiusAxisDrawer.cs b/Editor/PropertyDrawers/RadiusAxisDrawer.cs new file mode 100644 index 00000000..3b86e7f6 --- /dev/null +++ b/Editor/PropertyDrawers/RadiusAxisDrawer.cs @@ -0,0 +1,24 @@ +/******************************************/ +/* */ +/* Copyright (c) 2018 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/******************************************/ + +using UnityEditor; + +namespace XCharts +{ + [CustomPropertyDrawer(typeof(RadiusAxis), true)] + public class RadiusAxisDrawer : AxisDrawer + { + protected override string GetDisplayName(string displayName) + { + if (displayName.StartsWith("Element")) + { + displayName = displayName.Replace("Element", "Radius Axis"); + } + return displayName; + } + } +} \ No newline at end of file diff --git a/Editor/PropertyDrawers/RadiusAxisDrawer.cs.meta b/Editor/PropertyDrawers/RadiusAxisDrawer.cs.meta new file mode 100644 index 00000000..9d0b08e7 --- /dev/null +++ b/Editor/PropertyDrawers/RadiusAxisDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 433e6c679c39c4bf988a0447fd2e3775 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/XChartEditor.cs b/Editor/XChartEditor.cs index ee22523e..3aa23e35 100644 --- a/Editor/XChartEditor.cs +++ b/Editor/XChartEditor.cs @@ -127,5 +127,12 @@ namespace XCharts { AddChart("RingChart"); } + + [MenuItem("XCharts/PolarChart", priority = 52)] + [MenuItem("GameObject/XCharts/PolarChart", priority = 52)] + public static void AddPolarChart() + { + AddChart("PolarChart"); + } } } \ No newline at end of file diff --git a/Examples/Runtime/Example80_Polar.cs b/Examples/Runtime/Example80_Polar.cs new file mode 100644 index 00000000..de71f0d2 --- /dev/null +++ b/Examples/Runtime/Example80_Polar.cs @@ -0,0 +1,57 @@ +using System.Runtime.InteropServices; +/******************************************/ +/* */ +/* Copyright (c) 2018 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/******************************************/ + + +using UnityEngine; + +namespace XCharts.Examples +{ + [DisallowMultipleComponent] + [ExecuteInEditMode] + public class Example80_Polar : MonoBehaviour + { + private PolarChart chart; + private float updateTime; + + void Awake() + { + chart = gameObject.GetComponent(); + if (chart == null) + { + chart = gameObject.AddComponent(); + } + } + + void Update() + { + if (Input.GetKeyDown(KeyCode.Space)) + { + AddData(); + } + } + + void AddData() + { + chart.RemoveData(); + chart.angleAxis.type = Axis.AxisType.Value; + chart.angleAxis.minMaxType = Axis.AxisMinMaxType.Custom; + chart.angleAxis.min = 0; + chart.angleAxis.max = 360; + chart.angleAxis.startAngle = Random.Range(0,90); + chart.AddSerie(SerieType.Line, "line1"); + + var rate = Random.Range(1, 4); + for (int i = 0; i <= 360; i++) + { + var t = i / 180f * Mathf.PI; + var r = Mathf.Sin(2 * t) * Mathf.Cos(2 * t) * rate; + chart.AddData(0, Mathf.Abs(r), i); + } + } + } +} \ No newline at end of file diff --git a/Examples/Runtime/Example80_Polar.cs.meta b/Examples/Runtime/Example80_Polar.cs.meta new file mode 100644 index 00000000..ea734cf4 --- /dev/null +++ b/Examples/Runtime/Example80_Polar.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca29783da761a4e0e9c5204d5b24b610 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/API/PolarChart_API.cs b/Runtime/API/PolarChart_API.cs new file mode 100644 index 00000000..18347905 --- /dev/null +++ b/Runtime/API/PolarChart_API.cs @@ -0,0 +1,30 @@ +/******************************************/ +/* */ +/* Copyright (c) 2018 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/******************************************/ + +using System.Collections.Generic; +using UnityEngine; + +namespace XCharts +{ + public partial class PolarChart + { + /// + /// 极坐标。 + /// + public Polar polar { get { return m_Polar; } } + /// + /// Angle axis of Polar Coordinate. + /// 极坐标系的角度轴。 + /// + public AngleAxis angleAxis { get { return m_AngleAxis; } } + /// + /// Radial axis of polar coordinate. + /// 极坐标系的径向轴。 + /// + public RadiusAxis radiusAxis { get { return m_RadiusAxis; } } + } +} \ No newline at end of file diff --git a/Runtime/API/PolarChart_API.cs.meta b/Runtime/API/PolarChart_API.cs.meta new file mode 100644 index 00000000..352ff64a --- /dev/null +++ b/Runtime/API/PolarChart_API.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 42960f04fcc2442baa061d32386aaaa8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Component/Main/Axis.cs b/Runtime/Component/Main/Axis.cs index 26b9eeaa..0b63f5e4 100644 --- a/Runtime/Component/Main/Axis.cs +++ b/Runtime/Component/Main/Axis.cs @@ -805,4 +805,86 @@ namespace XCharts } } } + + /// + /// Radial axis of polar coordinate. + /// 极坐标系的径向轴。 + /// + [System.Serializable] + public class RadiusAxis : Axis + { + public static RadiusAxis defaultRadiusAxis + { + get + { + var axis = new RadiusAxis + { + m_Show = true, + m_Type = AxisType.Value, + m_Min = 0, + m_Max = 0, + m_SplitNumber = 5, + m_BoundaryGap = false, + m_Data = new List(5), + }; + axis.splitLine.show = true; + axis.splitLine.lineStyle.type = LineStyle.Type.Solid; + axis.axisLabel.textLimit.enable = false; + return axis; + } + } + } + + /// + /// Angle axis of Polar Coordinate. + /// 极坐标系的角度轴。 + /// + [System.Serializable] + public class AngleAxis : Axis + { + [SerializeField] private float m_StartAngle = 90; + [SerializeField] private bool m_Clockwise = true; + /// + /// Starting angle of axis. 90 degrees by default, standing for top position of center. 0 degree stands for right position of center. + /// 起始刻度的角度,默认为 90 度,即圆心的正上方。0 度为圆心的正右方。 + /// + public float startAngle + { + get { return m_StartAngle; } + set { if (PropertyUtility.SetStruct(ref m_StartAngle, value)) SetAllDirty(); } + } + /// + /// Whether the positive position of axis is in clockwise. True for clockwise by default. + /// 刻度增长是否按顺时针,默认顺时针。 + /// + public bool clockWise + { + get { return m_Clockwise; } + set { if (PropertyUtility.SetStruct(ref m_Clockwise, value)) SetAllDirty(); } + } + + public float runtimeStartAngle { get; set; } + + public static AngleAxis defaultAngleAxis + { + get + { + var axis = new AngleAxis + { + m_Show = true, + m_Type = AxisType.Value, + m_SplitNumber = 13, + m_BoundaryGap = false, + m_Data = new List(13), + }; + axis.splitLine.show = true; + axis.splitLine.lineStyle.type = LineStyle.Type.Solid; + axis.axisLabel.textLimit.enable = false; + axis.minMaxType = AxisMinMaxType.Custom; + axis.min = 0; + axis.max = 360; + return axis; + } + } + } } \ No newline at end of file diff --git a/Runtime/Component/Main/Polar.cs b/Runtime/Component/Main/Polar.cs new file mode 100644 index 00000000..fc42d5d4 --- /dev/null +++ b/Runtime/Component/Main/Polar.cs @@ -0,0 +1,92 @@ +/******************************************/ +/* */ +/* Copyright (c) 2018 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/******************************************/ + +using System; +using UnityEngine; + +namespace XCharts +{ + /// + /// Polar coordinate can be used in scatter and line chart. Every polar coordinate has an angleAxis and a radiusAxis. + /// + /// 极坐标系组件。 + /// 极坐标系,可以用于散点图和折线图。每个极坐标系拥有一个角度轴和一个半径轴。 + /// + /// + [Serializable] + public class Polar : MainComponent + { + [SerializeField] private bool m_Show = true; + [SerializeField] private float[] m_Center = new float[2] { 0.5f, 0.5f }; + [SerializeField] private float m_Radius = 100; + [SerializeField] private Color m_BackgroundColor; + + + /// + /// Whether to show the grid in rectangular coordinate. + /// 是否显示直角坐标系网格。 + /// + public bool show + { + get { return m_Show; } + set { if (PropertyUtility.SetStruct(ref m_Show, value)) SetVerticesDirty(); } + } + /// + /// the center of ploar. + /// 极坐标的中心点。数组的第一项是横坐标,第二项是纵坐标。 + /// 当值为0-1之间时表示百分比,设置成百分比时第一项是相对于容器宽度,第二项是相对于容器高度。 + /// + public float[] center + { + get { return m_Center; } + set { if (value != null) { m_Center = value; SetAllDirty(); } } + } + /// + /// the radius of polar. + /// 极坐标的半径。 + /// + public float radius + { + get { return m_Radius; } + set { if (PropertyUtility.SetStruct(ref m_Radius, value)) SetAllDirty(); } + } + /// + /// Background color of polar, which is transparent by default. + /// 极坐标的背景色,默认透明。 + /// + public Color backgroundColor + { + get { return m_BackgroundColor; } + set { if (PropertyUtility.SetColor(ref m_BackgroundColor, value)) SetVerticesDirty(); } + } + + /// + /// the center position of polar in container. + /// 极坐标在容器中的具体中心点。 + /// + public Vector3 runtimeCenterPos { get; internal set; } + /// + /// the true radius of polar. + /// 极坐标的运行时实际半径。 + /// + public float runtimeRadius { get; internal set; } + public static Polar defaultPolar + { + get + { + var polar = new Polar + { + m_Show = true, + m_Radius = 0.35f, + }; + polar.center[0] = 0.5f; + polar.center[1] = 0.45f; + return polar; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Component/Main/Polar.cs.meta b/Runtime/Component/Main/Polar.cs.meta new file mode 100644 index 00000000..54369aec --- /dev/null +++ b/Runtime/Component/Main/Polar.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9eb9ba0a1d154f11ba169fc07ad7a91 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Component/Main/Tooltip.cs b/Runtime/Component/Main/Tooltip.cs index e7396170..1600e566 100644 --- a/Runtime/Component/Main/Tooltip.cs +++ b/Runtime/Component/Main/Tooltip.cs @@ -249,6 +249,10 @@ namespace XCharts /// 提示框的gameObject。 /// public GameObject runtimeGameObject { get { return m_GameObject; } } + /// + /// 当前指示的角度。 + /// + public float runtimeAngle { get; internal set; } public static Tooltip defaultTooltip { @@ -389,7 +393,7 @@ namespace XCharts /// public void SetActive(bool flag) { - if(!flag && m_AlwayShow) return; + if (!flag && m_AlwayShow) return; lastDataIndex[0] = lastDataIndex[1] = -1; if (m_GameObject && m_GameObject.activeInHierarchy != flag) m_GameObject.SetActive(flag); diff --git a/Runtime/Component/Sub/SerieData.cs b/Runtime/Component/Sub/SerieData.cs index 2457d5ac..52f90eaa 100644 --- a/Runtime/Component/Sub/SerieData.cs +++ b/Runtime/Component/Sub/SerieData.cs @@ -167,6 +167,7 @@ namespace XCharts /// public float runtimePieOffsetRadius { get; internal set; } public Vector3 runtimePosition { get; internal set; } + public float runtimeAngle { get; internal set; } public Vector3 runtiemPieOffsetCenter { get; internal set; } private List m_PreviousData = new List(); private List m_DataUpdateTime = new List(); diff --git a/Runtime/Internal/Helper/AxisHelper.cs b/Runtime/Internal/Helper/AxisHelper.cs index 75d23db1..a1b720c9 100644 --- a/Runtime/Internal/Helper/AxisHelper.cs +++ b/Runtime/Internal/Helper/AxisHelper.cs @@ -6,6 +6,7 @@ /******************************************/ using System.Text; using UnityEngine; +using UnityEngine.UI; namespace XCharts { @@ -165,7 +166,7 @@ namespace XCharts /// /// /// - internal static int GetScaleNumber(Axis axis, float coordinateWidth, DataZoom dataZoom) + internal static int GetScaleNumber(Axis axis, float coordinateWidth, DataZoom dataZoom = null) { if (axis.type == Axis.AxisType.Value || axis.type == Axis.AxisType.Log) { @@ -190,7 +191,7 @@ namespace XCharts /// /// /// - internal static float GetScaleWidth(Axis axis, float coordinateWidth, int index, DataZoom dataZoom) + internal static float GetScaleWidth(Axis axis, float coordinateWidth, int index, DataZoom dataZoom = null) { int num = GetScaleNumber(axis, coordinateWidth, dataZoom) - 1; if (num <= 0) num = 1; @@ -285,5 +286,49 @@ namespace XCharts else if (axis.IsValue() && axis.runtimeMinValue == 0 && axis.runtimeMaxValue == 0) return false; else return true; } + + internal static void AdjustCircleLabelPos(Text txt, Vector3 pos, Vector3 cenPos, float txtHig, Vector3 offset) + { + var txtWidth = txt.preferredWidth; + var sizeDelta = new Vector2(txtWidth, txt.preferredHeight); + txt.GetComponent().sizeDelta = sizeDelta; + var diff = pos.x - cenPos.x; + if (diff < -1f) //left + { + pos = new Vector3(pos.x - txtWidth / 2, pos.y); + } + else if (diff > 1f) //right + { + pos = new Vector3(pos.x + txtWidth / 2, pos.y); + } + else + { + float y = pos.y > cenPos.y ? pos.y + txtHig / 2 : pos.y - txtHig / 2; + pos = new Vector3(pos.x, y); + } + txt.transform.localPosition = pos + offset; + } + + internal static void AdjustRadiusAxisLabelPos(Text txt, Vector3 pos, Vector3 cenPos, float txtHig, Vector3 offset) + { + var txtWidth = txt.preferredWidth; + var sizeDelta = new Vector2(txtWidth, txt.preferredHeight); + txt.GetComponent().sizeDelta = sizeDelta; + var diff = pos.y - cenPos.y; + if (diff > 20f) //left + { + pos = new Vector3(pos.x - txtWidth / 2, pos.y); + } + else if (diff < -20f) //right + { + pos = new Vector3(pos.x + txtWidth / 2, pos.y); + } + else + { + float y = pos.y > cenPos.y ? pos.y + txtHig / 2 : pos.y - txtHig / 2; + pos = new Vector3(pos.x, y); + } + txt.transform.localPosition = pos; + } } } \ No newline at end of file diff --git a/Runtime/Internal/Helper/PolarHelper.cs b/Runtime/Internal/Helper/PolarHelper.cs new file mode 100644 index 00000000..589e6b72 --- /dev/null +++ b/Runtime/Internal/Helper/PolarHelper.cs @@ -0,0 +1,34 @@ +/******************************************/ +/* */ +/* Copyright (c) 2018 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/******************************************/ + +using UnityEngine; + +namespace XCharts +{ + internal static class PolarHelper + { + internal static void UpdatePolarCenter(Polar polar, Vector3 chartPosition, float chartWidth, float chartHeight) + { + if (polar.center.Length < 2) return; + var centerX = polar.center[0] <= 1 ? chartWidth * polar.center[0] : polar.center[0]; + var centerY = polar.center[1] <= 1 ? chartHeight * polar.center[1] : polar.center[1]; + polar.runtimeCenterPos = chartPosition + new Vector3(centerX, centerY); + if (polar.radius <= 0) + { + polar.runtimeRadius = 0; + } + else if (polar.radius <= 1) + { + polar.runtimeRadius = Mathf.Min(chartWidth, chartHeight) * polar.radius; + } + else + { + polar.runtimeRadius = polar.radius; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Internal/Helper/PolarHelper.cs.meta b/Runtime/Internal/Helper/PolarHelper.cs.meta new file mode 100644 index 00000000..6833e7a8 --- /dev/null +++ b/Runtime/Internal/Helper/PolarHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 64e494d13836f430ea7f4fe3e2a716b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Helper/SerieHelper.cs b/Runtime/Internal/Helper/SerieHelper.cs index 601d0abc..97dd2d49 100644 --- a/Runtime/Internal/Helper/SerieHelper.cs +++ b/Runtime/Internal/Helper/SerieHelper.cs @@ -286,8 +286,16 @@ namespace XCharts if (value < min) min = value; } } - serie.runtimeDataMin = ChartHelper.GetMinDivisibleValue(min, ceilRate); - serie.runtimeDataMax = ChartHelper.GetMaxDivisibleValue(max, ceilRate); + if (ceilRate < 0) + { + serie.runtimeDataMin = min; + serie.runtimeDataMax = max; + } + else + { + serie.runtimeDataMin = ChartHelper.GetMinDivisibleValue(min, ceilRate); + serie.runtimeDataMax = ChartHelper.GetMaxDivisibleValue(max, ceilRate); + } } public static void GetAllMinMaxData(Serie serie, int ceilRate = 0, DataZoom dataZoom = null) diff --git a/Runtime/Internal/Helper/TooltipHelper.cs b/Runtime/Internal/Helper/TooltipHelper.cs index 8e1ba3d1..80136452 100644 --- a/Runtime/Internal/Helper/TooltipHelper.cs +++ b/Runtime/Internal/Helper/TooltipHelper.cs @@ -181,6 +181,48 @@ namespace XCharts tooltip.UpdateContentPos(pos); } + public static string GetPolarFormatterContent(Tooltip tooltip, Series series, ThemeInfo themeInfo) + { + if (string.IsNullOrEmpty(tooltip.formatter)) + { + var sb = ChartHelper.sb; + sb.Length = 0; + sb.Append(tooltip.runtimeAngle).Append("\n"); + foreach (var serie in series.list) + { + if (serie.show && IsSelectedSerie(tooltip, serie.index)) + { + var dataIndexList = tooltip.runtimeSerieIndex[serie.index]; + for (int i = 0; i < dataIndexList.Count; i++) + { + var dataIndex = dataIndexList[i]; + var serieData = serie.GetSerieData(dataIndex); + var numericFormatter = GetItemNumericFormatter(tooltip, serie, serieData); + float xValue, yValue; + serie.GetXYData(dataIndex, null, out xValue, out yValue); + + sb.Append("● "); + if (!string.IsNullOrEmpty(serie.name)) + sb.Append(serie.name).Append(": "); + sb.AppendFormat("{0}", ChartCached.FloatToStr(xValue, numericFormatter)); + if (i != dataIndexList.Count - 1) + { + sb.Append("\n"); + } + } + sb.Append("\n"); + } + } + return sb.ToString().Trim(); + } + else + { + string content = tooltip.formatter; + FormatterHelper.ReplaceContent(ref content, 0, tooltip.numericFormatter, null, series, themeInfo, null, null); + return content; + } + } + public static string GetFormatterContent(Tooltip tooltip, int dataIndex, Series series, ThemeInfo themeInfo, string category = null, DataZoom dataZoom = null, bool isCartesian = false) { diff --git a/Runtime/PolarChart.cs b/Runtime/PolarChart.cs new file mode 100644 index 00000000..0a925bf0 --- /dev/null +++ b/Runtime/PolarChart.cs @@ -0,0 +1,592 @@ +/******************************************/ +/* */ +/* Copyright (c) 2018 monitor1394 */ +/* https://github.com/monitor1394 */ +/* */ +/******************************************/ + +using UnityEngine; +using UnityEngine.UI; + +namespace XCharts +{ + [AddComponentMenu("XCharts/PolarChart", 21)] + [ExecuteInEditMode] + [RequireComponent(typeof(RectTransform))] + [DisallowMultipleComponent] + public partial class PolarChart : BaseChart + { + [SerializeField] Polar m_Polar = Polar.defaultPolar; + [SerializeField] private RadiusAxis m_RadiusAxis = RadiusAxis.defaultRadiusAxis; + [SerializeField] private AngleAxis m_AngleAxis = AngleAxis.defaultAngleAxis; + + private bool m_CheckMinMaxValue = false; + + protected override void Awake() + { + base.Awake(); + m_CheckMinMaxValue = false; + CheckMinMaxValue(); + UpdateRuntimeValue(); + InitRadiusAxis(m_RadiusAxis); + InitAngleAxis(m_AngleAxis); + m_Tooltip.UpdateToTop(); + } + + +#if UNITY_EDITOR + protected override void Reset() + { + base.Reset(); + m_Title.text = "PolarChart"; + m_Tooltip.type = Tooltip.Type.Line; + RemoveData(); + ResetValuePolar(); + Awake(); + } + + private void ResetValuePolar() + { + m_AngleAxis.type = Axis.AxisType.Value; + m_AngleAxis.minMaxType = Axis.AxisMinMaxType.Custom; + m_AngleAxis.min = 0; + m_AngleAxis.max = 360; + AddSerie(SerieType.Line, "line1"); + for (int i = 0; i <= 360; i++) + { + var t = i / 180f * Mathf.PI; + var r = Mathf.Sin(2 * t) * Mathf.Cos(2 * t) * 2; + AddData(0, Mathf.Abs(r), i); + } + } + + private void ResetCategoryPolar() + { + m_AngleAxis.type = Axis.AxisType.Category; + AddSerie(SerieType.Bar, "line1"); + for (int i = 0; i <= 13; i++) + { + m_AngleAxis.AddData("bar" + i); + AddData(0, Random.Range(0, 10)); + } + } + + protected override void OnValidate() + { + base.OnValidate(); + m_RadiusAxis.SetAllDirty(); + m_AngleAxis.SetAllDirty(); + CheckMinMaxValue(); + } +#endif + + protected override void CheckComponent() + { + if (m_Polar.anyDirty) + { + if (m_Polar.componentDirty) + { + m_AngleAxis.SetComponentDirty(); + m_RadiusAxis.SetComponentDirty(); + } + if (m_Polar.vertsDirty) RefreshChart(); + m_Polar.ClearDirty(); + } + if (m_AngleAxis.anyDirty || m_RadiusAxis.anyDirty) + { + if (m_AngleAxis.componentDirty || m_RadiusAxis.componentDirty) + { + UpdateRuntimeValue(); + InitAngleAxis(m_AngleAxis); + InitRadiusAxis(m_RadiusAxis); + } + if (m_AngleAxis.vertsDirty || m_RadiusAxis.vertsDirty) RefreshChart(); + m_AngleAxis.ClearDirty(); + m_RadiusAxis.ClearDirty(); + } + base.CheckComponent(); + } + + protected override void OnSizeChanged() + { + base.OnSizeChanged(); + m_RadiusAxis.SetAllDirty(); + m_AngleAxis.SetAllDirty(); + UpdateRuntimeValue(); + } + + private void InitRadiusAxis(RadiusAxis axis) + { + PolarHelper.UpdatePolarCenter(m_Polar, m_ChartPosition, m_ChartWidth, m_ChartHeight); + var radius = m_Polar.runtimeRadius; + axis.axisLabelTextList.Clear(); + string objName = "axis_radius"; + var axisObj = ChartHelper.AddObject(objName, transform, chartAnchorMin, + chartAnchorMax, chartPivot, new Vector2(chartWidth, chartHeight)); + axisObj.transform.localPosition = Vector3.zero; + axisObj.SetActive(axis.show && axis.axisLabel.show); + axisObj.hideFlags = chartHideFlags; + ChartHelper.HideAllObject(axisObj); + var labelColor = ChartHelper.IsClearColor(axis.axisLabel.color) ? + (Color)m_ThemeInfo.axisTextColor : + axis.axisLabel.color; + int splitNumber = AxisHelper.GetSplitNumber(axis, radius, null); + float totalWidth = 0; + var startAngle = m_AngleAxis.runtimeStartAngle; + var cenPos = m_Polar.runtimeCenterPos; + var txtHig = axis.axisLabel.fontSize + 2; + var dire = ChartHelper.GetDire(startAngle, true).normalized; + var tickVetor = ChartHelper.GetVertialDire(dire) * (m_RadiusAxis.axisTick.length + m_RadiusAxis.axisLabel.margin); + for (int i = 0; i < splitNumber; i++) + { + float labelWidth = AxisHelper.GetScaleWidth(axis, radius, i, null); + bool inside = axis.axisLabel.inside; + Text txt = ChartHelper.AddTextObject(objName + i, axisObj.transform, + m_ThemeInfo.font, labelColor, TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), + new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(labelWidth, txtHig), + axis.axisLabel.fontSize, axis.axisLabel.rotate, axis.axisLabel.fontStyle); + if (i == 0) axis.axisLabel.SetRelatedText(txt, labelWidth); + var isPercentStack = SeriesHelper.IsPercentStack(m_Series, SerieType.Bar); + txt.text = AxisHelper.GetLabelName(axis, radius, i, axis.runtimeMinValue, axis.runtimeMaxValue, null, + isPercentStack); + txt.gameObject.SetActive(axis.show && + (axis.axisLabel.interval == 0 || i % (axis.axisLabel.interval + 1) == 0)); + var pos = ChartHelper.GetPos(cenPos, totalWidth, startAngle, true) + tickVetor; + txt.transform.localPosition = pos; + AxisHelper.AdjustRadiusAxisLabelPos(txt, pos, cenPos, txtHig, Vector3.zero); + axis.axisLabelTextList.Add(txt); + + totalWidth += labelWidth; + } + if (m_Tooltip.runtimeGameObject) + { + Vector2 privot = new Vector2(0.5f, 1); + var labelParent = m_Tooltip.runtimeGameObject.transform; + GameObject labelObj = ChartHelper.AddTooltipLabel(ChartCached.GetAxisTooltipLabel(objName), labelParent, m_ThemeInfo.font, privot); + axis.SetTooltipLabel(labelObj); + axis.SetTooltipLabelColor(m_ThemeInfo.tooltipBackgroundColor, m_ThemeInfo.tooltipTextColor); + axis.SetTooltipLabelActive(axis.show && m_Tooltip.show && m_Tooltip.type == Tooltip.Type.Corss); + } + } + + private void InitAngleAxis(AngleAxis axis) + { + PolarHelper.UpdatePolarCenter(m_Polar, m_ChartPosition, m_ChartWidth, m_ChartHeight); + var radius = m_Polar.runtimeRadius; + axis.axisLabelTextList.Clear(); + + string objName = "axis_angle"; + var axisObj = ChartHelper.AddObject(objName, transform, chartAnchorMin, + chartAnchorMax, chartPivot, new Vector2(chartWidth, chartHeight)); + axisObj.transform.localPosition = Vector3.zero; + axisObj.SetActive(axis.show && axis.axisLabel.show); + axisObj.hideFlags = chartHideFlags; + ChartHelper.HideAllObject(axisObj); + var labelColor = ChartHelper.IsClearColor(axis.axisLabel.color) ? + (Color)m_ThemeInfo.axisTextColor : + axis.axisLabel.color; + int splitNumber = AxisHelper.GetSplitNumber(axis, radius, null); + float totalAngle = m_AngleAxis.runtimeStartAngle; + var total = 360; + var cenPos = m_Polar.runtimeCenterPos; + var txtHig = m_AngleAxis.axisLabel.fontSize + 2; + var margin = m_AngleAxis.axisLabel.margin; + var isCategory = m_AngleAxis.IsCategory(); + for (int i = 0; i < splitNumber - 1; i++) + { + float scaleAngle = AxisHelper.GetScaleWidth(axis, total, i, null); + bool inside = axis.axisLabel.inside; + Text txt = ChartHelper.AddTextObject(objName + i, axisObj.transform, + m_ThemeInfo.font, labelColor, TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), + new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(scaleAngle, txtHig), + axis.axisLabel.fontSize, axis.axisLabel.rotate, axis.axisLabel.fontStyle); + if (i == 0) axis.axisLabel.SetRelatedText(txt, scaleAngle); + var isPercentStack = SeriesHelper.IsPercentStack(m_Series, SerieType.Bar); + txt.text = AxisHelper.GetLabelName(axis, total, i, axis.runtimeMinValue, axis.runtimeMaxValue, null, + isPercentStack); + txt.gameObject.SetActive(axis.show && + (axis.axisLabel.interval == 0 || i % (axis.axisLabel.interval + 1) == 0)); + var pos = ChartHelper.GetPos(cenPos, radius + margin, isCategory ? (totalAngle + scaleAngle / 2) : totalAngle, true); + AxisHelper.AdjustCircleLabelPos(txt, pos, cenPos, txtHig, Vector3.zero); + axis.axisLabelTextList.Add(txt); + + totalAngle += scaleAngle; + } + if (m_Tooltip.runtimeGameObject) + { + Vector2 privot = new Vector2(0.5f, 1); + var labelParent = m_Tooltip.runtimeGameObject.transform; + GameObject labelObj = ChartHelper.AddTooltipLabel(ChartCached.GetAxisTooltipLabel(objName), labelParent, m_ThemeInfo.font, privot); + axis.SetTooltipLabel(labelObj); + axis.SetTooltipLabelColor(m_ThemeInfo.tooltipBackgroundColor, m_ThemeInfo.tooltipTextColor); + axis.SetTooltipLabelActive(axis.show && m_Tooltip.show && m_Tooltip.type == Tooltip.Type.Corss); + } + } + + protected override void Update() + { + base.Update(); + CheckMinMaxValue(); + } + + private void CheckMinMaxValue() + { + + if (m_RadiusAxis.IsCategory() && m_AngleAxis.IsCategory()) + { + m_CheckMinMaxValue = true; + return; + } + UpdateAxisMinMaxValue(0, m_RadiusAxis); + UpdateAxisMinMaxValue(0, m_AngleAxis); + } + + private void UpdateAxisMinMaxValue(int axisIndex, Axis axis, bool updateChart = true) + { + if (axis.IsCategory() || !axis.show) return; + float tempMinValue = 0; + float tempMaxValue = 0; + if (axis is RadiusAxis) + { + 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); + } + AxisHelper.AdjustMinMaxValue(axis, ref tempMinValue, ref tempMaxValue, true); + if (tempMinValue != axis.runtimeMinValue || tempMaxValue != axis.runtimeMaxValue) + { + m_CheckMinMaxValue = true; + m_IsPlayingAnimation = true; + var needCheck = !m_IsPlayingAnimation && axis.runtimeLastCheckInverse == axis.inverse; + axis.UpdateMinValue(tempMinValue, needCheck); + axis.UpdateMaxValue(tempMaxValue, needCheck); + axis.runtimeZeroXOffset = 0; + axis.runtimeZeroYOffset = 0; + axis.runtimeLastCheckInverse = axis.inverse; + if (updateChart) + { + UpdateAxisLabelText(axis); + RefreshChart(); + } + } + if (axis.IsValueChanging(500) && !m_IsPlayingAnimation) + { + UpdateAxisLabelText(axis); + RefreshChart(); + } + } + + protected void UpdateAxisLabelText(Axis axis) + { + float m_CoordinateWidth = axis is RadiusAxis ? m_Polar.runtimeRadius : 360; + var isPercentStack = SeriesHelper.IsPercentStack(m_Series, SerieType.Bar); + axis.UpdateLabelText(m_CoordinateWidth, null, isPercentStack, 500); + } + + protected override void DrawChart(VertexHelper vh) + { + base.DrawChart(vh); + DrawPolar(vh); + DrawAngleAxis(vh); + DrawRadiusAxis(vh); + DrawSerie(vh); + } + + private void UpdateRuntimeValue() + { + PolarHelper.UpdatePolarCenter(m_Polar, m_ChartPosition, m_ChartWidth, m_ChartHeight); + m_AngleAxis.runtimeStartAngle = 90 - m_AngleAxis.startAngle; + } + + private void DrawPolar(VertexHelper vh) + { + UpdateRuntimeValue(); + if (!ChartHelper.IsClearColor(m_Polar.backgroundColor)) + { + ChartDrawer.DrawCricle(vh, m_Polar.runtimeCenterPos, m_Polar.runtimeRadius, m_Polar.backgroundColor); + } + } + + private void DrawRadiusAxis(VertexHelper vh) + { + var startAngle = m_AngleAxis.runtimeStartAngle; + var radius = m_Polar.runtimeRadius; + var cenPos = m_Polar.runtimeCenterPos; + var size = AxisHelper.GetScaleNumber(m_RadiusAxis, radius, null); + var totalWidth = 0f; + var dire = ChartHelper.GetDire(startAngle, true).normalized; + var tickVetor = ChartHelper.GetVertialDire(dire) * m_RadiusAxis.axisTick.length; + var tickWidth = AxisHelper.GetTickWidth(m_RadiusAxis); + for (int i = 0; i < size; i++) + { + var scaleWidth = AxisHelper.GetScaleWidth(m_RadiusAxis, radius, i); + var pos = ChartHelper.GetPos(cenPos, totalWidth, startAngle, true); + if (m_RadiusAxis.show && m_RadiusAxis.splitLine.show) + { + var outsideRaidus = totalWidth + m_RadiusAxis.splitLine.lineStyle.width * 2; + var splitLineColor = m_RadiusAxis.splitLine.GetColor(m_ThemeInfo); + ChartDrawer.DrawDoughnut(vh, cenPos, totalWidth, outsideRaidus, splitLineColor, Color.clear); + } + if (m_RadiusAxis.show && m_RadiusAxis.axisTick.show) + { + ChartDrawer.DrawLine(vh, pos, pos + tickVetor, tickWidth, m_ThemeInfo.axisLineColor); + } + totalWidth += scaleWidth; + } + if (m_RadiusAxis.show && m_RadiusAxis.axisLine.show) + { + var lineStartPos = m_Polar.runtimeCenterPos - dire * m_RadiusAxis.axisTick.width; + var lineEndPos = m_Polar.runtimeCenterPos + dire * (radius + m_RadiusAxis.axisTick.width); + ChartDrawer.DrawLine(vh, lineStartPos, lineEndPos, m_RadiusAxis.axisLine.width, m_ThemeInfo.axisLineColor); + } + } + + private void DrawAngleAxis(VertexHelper vh) + { + var radius = m_Polar.runtimeRadius; + var cenPos = m_Polar.runtimeCenterPos; + var total = 360; + var size = AxisHelper.GetScaleNumber(m_AngleAxis, total, null); + var currAngle = m_AngleAxis.runtimeStartAngle; + var tickWidth = AxisHelper.GetTickWidth(m_AngleAxis); + for (int i = 0; i < size; i++) + { + var scaleWidth = AxisHelper.GetScaleWidth(m_AngleAxis, total, i); + var pos = ChartHelper.GetPos(cenPos, radius, currAngle, true); + if (m_AngleAxis.show && m_AngleAxis.splitLine.show) + { + var splitLineColor = m_AngleAxis.splitLine.GetColor(m_ThemeInfo); + ChartDrawer.DrawLine(vh, cenPos, pos, m_AngleAxis.splitLine.lineStyle.width, splitLineColor); + } + if (m_AngleAxis.show && m_AngleAxis.axisTick.show) + { + var tickPos = ChartHelper.GetPos(cenPos, radius + m_AngleAxis.axisTick.length, currAngle, true); + ChartDrawer.DrawLine(vh, pos, tickPos, tickWidth, m_ThemeInfo.axisLineColor); + } + currAngle += scaleWidth; + } + if (m_AngleAxis.show && m_AngleAxis.axisLine.show) + { + var outsideRaidus = radius + m_AngleAxis.axisLine.width * 2; + ChartDrawer.DrawDoughnut(vh, cenPos, radius, outsideRaidus, m_ThemeInfo.axisLineColor, Color.clear); + } + } + + private void DrawSerie(VertexHelper vh) + { + for (int i = 0; i < m_Series.Count; i++) + { + var serie = m_Series.GetSerie(i); + serie.index = i; + if (!serie.show) continue; + switch (serie.type) + { + case SerieType.Line: + DrawPolarLine(vh, serie); + break; + case SerieType.Bar: + break; + case SerieType.Scatter: + case SerieType.EffectScatter: + break; + } + + } + DrawPolarLineSymbol(vh); + + } + + private void DrawPolarLine(VertexHelper vh, Serie serie) + { + var startAngle = m_AngleAxis.runtimeStartAngle; + var radius = m_Polar.runtimeRadius; + var datas = serie.data; + if (datas.Count <= 0) return; + float dataChangeDuration = serie.animation.GetUpdateAnimationDuration(); + float min = m_RadiusAxis.GetCurrMinValue(dataChangeDuration); + float max = m_RadiusAxis.GetCurrMaxValue(dataChangeDuration); + + var firstSerieData = datas[0]; + var startPos = GetPolarPos(firstSerieData, min, max, radius); + var nextPos = Vector3.zero; + var lineColor = SerieHelper.GetLineColor(serie, m_ThemeInfo, serie.index, serie.highlighted); + var lineWidth = serie.lineStyle.width; + float currDetailProgress = 0; + float totalDetailProgress = datas.Count; + serie.animation.InitProgress(serie.dataPoints.Count, currDetailProgress, totalDetailProgress); + serie.animation.SetDataFinish(0); + for (int i = 1; i < datas.Count; i++) + { + if (serie.animation.CheckDetailBreak(i)) break; + var serieData = datas[i]; + nextPos = GetPolarPos(datas[i], min, max, radius); + ChartDrawer.DrawLine(vh, startPos, nextPos, lineWidth, lineColor); + startPos = nextPos; + } + if (!serie.animation.IsFinish()) + { + serie.animation.CheckProgress(totalDetailProgress); + serie.animation.CheckSymbol(serie.symbol.size); + m_IsPlayingAnimation = true; + RefreshChart(); + } + } + + private void DrawPolarBar(VertexHelper vh, Serie serie) + { + + } + + private void DrawPolarLineSymbol(VertexHelper vh) + { + for (int n = 0; n < m_Series.Count; n++) + { + var serie = m_Series.GetSerie(n); + if (!serie.show) continue; + if (serie.type != SerieType.Line) continue; + var count = serie.dataCount; + for (int i = 0; i < count; i++) + { + var serieData = serie.GetSerieData(i); + var symbol = SerieHelper.GetSerieSymbol(serie, serieData); + if (ChartHelper.IsIngore(serieData.runtimePosition)) continue; + bool highlight = serieData.highlighted || serie.highlighted; + if ((!symbol.show || !symbol.ShowSymbol(i, count) || serie.IsPerformanceMode()) && !serieData.highlighted) continue; + float symbolSize = highlight ? symbol.selectedSize : symbol.size; + var symbolColor = SerieHelper.GetItemColor(serie, serieData, m_ThemeInfo, n, highlight); + var symbolToColor = SerieHelper.GetItemToColor(serie, serieData, m_ThemeInfo, n, highlight); + var symbolBorder = SerieHelper.GetSymbolBorder(serie, serieData, highlight); + var cornerRadius = SerieHelper.GetSymbolCornerRadius(serie, serieData, highlight); + symbolSize = serie.animation.GetSysmbolSize(symbolSize); + DrawSymbol(vh, symbol.type, symbolSize, symbolBorder, serieData.runtimePosition, symbolColor, + symbolToColor, symbol.gap, cornerRadius); + } + } + } + + protected override void DrawTooltip(VertexHelper vh) + { + if (m_Tooltip.runtimeAngle < 0) return; + var lineColor = TooltipHelper.GetLineColor(tooltip, m_ThemeInfo); + var cenPos = m_Polar.runtimeCenterPos; + var sp = m_Polar.runtimeCenterPos; + var tooltipAngle = m_Tooltip.runtimeAngle + m_AngleAxis.runtimeStartAngle; + var ep = ChartHelper.GetPos(sp, m_Polar.runtimeRadius, tooltipAngle, true); + ChartDrawer.DrawLineStyle(vh, m_Tooltip.lineStyle, sp, ep, lineColor); + if (m_Tooltip.type == Tooltip.Type.Corss) + { + var dist = Vector2.Distance(pointerPos, cenPos); + if (dist > m_Polar.runtimeRadius) dist = m_Polar.runtimeRadius; + var outsideRaidus = dist + m_Tooltip.lineStyle.width * 2; + ChartDrawer.DrawDoughnut(vh, cenPos, dist, outsideRaidus, lineColor, Color.clear); + } + } + + private Vector3 GetPolarPos(SerieData serieData, float min, float max, float polarRadius) + { + var angle = m_AngleAxis.runtimeStartAngle + serieData.GetData(1); + var value = serieData.GetData(0); + var radius = (value - min) / (max - min) * polarRadius; + serieData.runtimeAngle = (angle + 360) % 360; + serieData.runtimePosition = ChartHelper.GetPos(m_Polar.runtimeCenterPos, radius, angle, true); + return serieData.runtimePosition; + } + + protected override void CheckTootipArea(Vector2 local) + { + var dist = Vector2.Distance(local, m_Polar.runtimeCenterPos); + if (dist > m_Polar.runtimeRadius) + { + m_Tooltip.runtimeAngle = -1; + if (m_Tooltip.IsActive()) + { + foreach (var kv in m_Tooltip.runtimeSerieIndex) + { + var serie = m_Series.GetSerie(kv.Key); + foreach (var dataIndex in kv.Value) + { + serie.GetSerieData(dataIndex).highlighted = false; + } + } + m_Tooltip.ClearSerieDataIndex(); + m_Tooltip.SetActive(false); + RefreshChart(); + } + return; + } + m_Tooltip.ClearSerieDataIndex(); + Vector2 dir = local - new Vector2(m_Polar.runtimeCenterPos.x, m_Polar.runtimeCenterPos.y); + float angle = ChartHelper.GetAngle360(Vector2.up, dir); + + foreach (var serie in m_Series.list) + { + switch (serie.type) + { + case SerieType.Line: + bool refresh = false; + var count = serie.data.Count; + SerieHelper.GetDimensionMinMaxData(serie, 1, -1); + var diff = (serie.runtimeDataMax - serie.runtimeDataMin) / (count - 1); + for (int j = 0; j < count; j++) + { + var serieData = serie.data[j]; + var flag = Mathf.Abs(serieData.runtimeAngle - angle) < Mathf.Abs(diff / 2); + if (serieData.highlighted != flag) + { + refresh = true; + } + serieData.highlighted = flag; + if (flag) + { + m_Tooltip.runtimeAngle = (serieData.runtimeAngle - m_AngleAxis.runtimeStartAngle + 360) % 360; + m_Tooltip.AddSerieDataIndex(serie.index, j); + } + } + if (refresh) RefreshChart(); + break; + case SerieType.Bar: + break; + case SerieType.Scatter: + case SerieType.EffectScatter: + break; + } + } + m_Tooltip.UpdateContentPos(new Vector2(local.x + 18, local.y - 25)); + UpdateTooltip(); + if (m_Tooltip.type == Tooltip.Type.Corss) + { + RefreshChart(); + } + } + + private float GetAngleDiff(SerieData nextData, SerieData serieData, float angle) + { + var nextAngle = nextData.runtimeAngle; + var lastAngle = serieData.runtimeAngle; + var diff = 0f; + if (nextAngle > 270 && lastAngle < 90) + { + diff = 360 - nextAngle + lastAngle; + } + else + { + diff = nextAngle - lastAngle; + } + return Mathf.Abs(diff); + } + + protected override void UpdateTooltip() + { + base.UpdateTooltip(); + var showTooltip = m_Tooltip.isAnySerieDataIndex(); + if (showTooltip) + { + var content = TooltipHelper.GetPolarFormatterContent(m_Tooltip, m_Series, m_ThemeInfo); + TooltipHelper.SetContentAndPosition(tooltip, content, chartRect); + } + m_Tooltip.SetActive(showTooltip); + } + } +} diff --git a/Runtime/PolarChart.cs.meta b/Runtime/PolarChart.cs.meta new file mode 100644 index 00000000..c6712164 --- /dev/null +++ b/Runtime/PolarChart.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 52fd81110b7774a4096479f4cd777579 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/RadarChart.cs b/Runtime/RadarChart.cs index b8a53141..7c19f51e 100644 --- a/Runtime/RadarChart.cs +++ b/Runtime/RadarChart.cs @@ -133,24 +133,8 @@ namespace XCharts txt.gameObject.hideFlags = chartHideFlags; txt.text = radar.indicatorList[i].name; txt.gameObject.SetActive(radar.indicator); - var txtWidth = txt.preferredWidth; - var sizeDelta = new Vector2(txt.preferredWidth, txt.preferredHeight); - txt.GetComponent().sizeDelta = sizeDelta; - var diff = pos.x - radar.runtimeCenterPos.x; - if (diff < -1f) //left - { - pos = new Vector3(pos.x - txtWidth / 2, pos.y); - } - else if (diff > 1f) //right - { - pos = new Vector3(pos.x + txtWidth / 2, pos.y); - } - else - { - float y = pos.y > radar.runtimeCenterPos.y ? pos.y + txtHig / 2 : pos.y - txtHig / 2; - pos = new Vector3(pos.x, y); - } - txt.transform.localPosition = pos + new Vector3(textStyle.offset.x, textStyle.offset.y); + var offset = new Vector3(textStyle.offset.x, textStyle.offset.y); + AxisHelper.AdjustCircleLabelPos(txt, pos, radar.runtimeCenterPos, txtHig, offset); } } } diff --git a/Runtime/Utility/ChartDrawer.cs b/Runtime/Utility/ChartDrawer.cs index d81dc610..c30d5f03 100644 --- a/Runtime/Utility/ChartDrawer.cs +++ b/Runtime/Utility/ChartDrawer.cs @@ -824,7 +824,7 @@ namespace XCharts float borderAngle = 0; float spaceAngle = 0; - var p2 = p + radius * GetDire(startAngle); + var p2 = p + radius * ChartHelper.GetDire(startAngle); var p3 = Vector3.zero; var p4 = Vector3.zero; var spaceCenter = p; @@ -834,7 +834,7 @@ namespace XCharts var needBorder = borderWidth != 0; var needSpace = space != 0; var lastPos = Vector3.zero; - var middleDire = GetDire(startAngle + halfAngle); + var middleDire = ChartHelper.GetDire(startAngle + halfAngle); if (needBorder || needSpace) { float spaceDiff = 0f; @@ -848,7 +848,7 @@ namespace XCharts realStartAngle = startAngle + spaceAngle; realToAngle = toAngle - spaceAngle; if (realToAngle < realStartAngle) realToAngle = realStartAngle; - p2 = GetPos(p, radius, realStartAngle); + p2 = ChartHelper.GetPos(p, radius, realStartAngle); } if (needBorder) { @@ -860,16 +860,16 @@ namespace XCharts if (realToAngle < realStartAngle) { realToAngle = realStartAngle; - p2 = GetPos(p, radius, realStartAngle); + p2 = ChartHelper.GetPos(p, radius, realStartAngle); } else { - var borderX1 = GetPos(p, radius, realStartAngle); + var borderX1 = ChartHelper.GetPos(p, radius, realStartAngle); DrawPolygon(vh, realCenter, spaceCenter, p2, borderX1, borderColor); p2 = borderX1; - var borderX2 = GetPos(p, radius, realToAngle); - var pEnd = GetPos(p, radius, toAngle - spaceAngle); + var borderX2 = ChartHelper.GetPos(p, radius, realToAngle); + var pEnd = ChartHelper.GetPos(p, radius, toAngle - spaceAngle); DrawPolygon(vh, realCenter, borderX2, pEnd, spaceCenter, borderColor); } } @@ -879,7 +879,7 @@ namespace XCharts for (int i = 0; i <= segments; i++) { float currAngle = realStartAngle + i * segmentAngle; - p3 = p + radius * GetDire(currAngle); + p3 = p + radius * ChartHelper.GetDire(currAngle); if (gradientType == 1) { if (isYAxis) @@ -918,7 +918,7 @@ namespace XCharts { if (realToAngle > realStartAngle) { - var borderX2 = p + radius * GetDire(realToAngle); + var borderX2 = p + radius * ChartHelper.GetDire(realToAngle); DrawTriangle(vh, realCenter, p2, borderX2, toColor, color, color); if (needBorder) { @@ -931,17 +931,7 @@ namespace XCharts } } - private static Vector3 GetPos(Vector3 center, float radius, float angle, bool isDegree = false) - { - angle = isDegree ? angle * Mathf.Deg2Rad : angle; - return new Vector3(center.x + radius * Mathf.Sin(angle), center.y + radius * Mathf.Cos(angle)); - } - - private static Vector3 GetDire(float angle, bool isDegree = false) - { - angle = isDegree ? angle * Mathf.Deg2Rad : angle; - return new Vector3(Mathf.Sin(angle), Mathf.Cos(angle)); - } + public static void DrawRoundCap(VertexHelper vh, Vector3 center, float width, float radius, float angle, bool clockwise, Color color, bool end) @@ -1059,8 +1049,8 @@ namespace XCharts spaceHalfAngle = 2 * Mathf.Asin(space / (2 * (insideRadius + (outsideRadius - insideRadius) / 2))); if (clockwise) { - p1 = GetPos(p, insideRadius, startAngle + spaceInAngle, false); - e1 = GetPos(p, insideRadius, toAngle - spaceInAngle, false); + p1 = ChartHelper.GetPos(p, insideRadius, startAngle + spaceInAngle, false); + e1 = ChartHelper.GetPos(p, insideRadius, toAngle - spaceInAngle, false); realStartOutAngle = startAngle + spaceAngle; realToOutAngle = toAngle - spaceAngle; realStartInAngle = startAngle + spaceInAngle; @@ -1068,15 +1058,15 @@ namespace XCharts } else { - p1 = GetPos(p, insideRadius, startAngle - spaceInAngle, false); - e1 = GetPos(p, insideRadius, toAngle + spaceInAngle, false); + p1 = ChartHelper.GetPos(p, insideRadius, startAngle - spaceInAngle, false); + e1 = ChartHelper.GetPos(p, insideRadius, toAngle + spaceInAngle, false); realStartOutAngle = startAngle - spaceAngle; realToOutAngle = toAngle + spaceAngle; realStartInAngle = startAngle - spaceInAngle; realToOutAngle = toAngle + spaceInAngle; } - p2 = GetPos(p, outsideRadius, realStartOutAngle, false); - e2 = GetPos(p, outsideRadius, realToOutAngle, false); + p2 = ChartHelper.GetPos(p, outsideRadius, realStartOutAngle, false); + e2 = ChartHelper.GetPos(p, outsideRadius, realToOutAngle, false); } if (needBorder) { @@ -1091,15 +1081,15 @@ namespace XCharts realToOutAngle = realToOutAngle - borderAngle; realStartInAngle = startAngle + spaceInAngle + borderInAngle; realToInAngle = toAngle - spaceInAngle - borderInAngle; - var newp1 = GetPos(p, insideRadius, startAngle + spaceInAngle + borderInAngle, false); - var newp2 = GetPos(p, outsideRadius, realStartOutAngle, false); + var newp1 = ChartHelper.GetPos(p, insideRadius, startAngle + spaceInAngle + borderInAngle, false); + var newp2 = ChartHelper.GetPos(p, outsideRadius, realStartOutAngle, false); if (!roundCap) DrawPolygon(vh, newp2, newp1, p1, p2, borderColor); p1 = newp1; p2 = newp2; if (toAngle - spaceInAngle - 2 * borderInAngle > realStartOutAngle) { - var newe1 = GetPos(p, insideRadius, toAngle - spaceInAngle - borderInAngle, false); - var newe2 = GetPos(p, outsideRadius, realToOutAngle, false); + var newe1 = ChartHelper.GetPos(p, insideRadius, toAngle - spaceInAngle - borderInAngle, false); + var newe2 = ChartHelper.GetPos(p, outsideRadius, realToOutAngle, false); if (!roundCap) DrawPolygon(vh, newe2, e2, e1, newe1, borderColor); e1 = newe1; e2 = newe2; @@ -1111,15 +1101,15 @@ namespace XCharts realToOutAngle = realToOutAngle + borderAngle; realStartInAngle = startAngle - spaceInAngle - borderInAngle; realToInAngle = toAngle + spaceInAngle + borderInAngle; - var newp1 = GetPos(p, insideRadius, startAngle - spaceInAngle - borderInAngle, false); - var newp2 = GetPos(p, outsideRadius, realStartOutAngle, false); + var newp1 = ChartHelper.GetPos(p, insideRadius, startAngle - spaceInAngle - borderInAngle, false); + var newp2 = ChartHelper.GetPos(p, outsideRadius, realStartOutAngle, false); if (!roundCap) DrawPolygon(vh, newp2, newp1, p1, p2, borderColor); p1 = newp1; p2 = newp2; if (toAngle + spaceInAngle + 2 * borderInAngle < realStartOutAngle) { - var newe1 = GetPos(p, insideRadius, toAngle + spaceInAngle + borderInAngle, false); - var newe2 = GetPos(p, outsideRadius, realToOutAngle, false); + var newe1 = ChartHelper.GetPos(p, insideRadius, toAngle + spaceInAngle + borderInAngle, false); + var newe2 = ChartHelper.GetPos(p, outsideRadius, realToOutAngle, false); if (!roundCap) DrawPolygon(vh, newe2, e2, e1, newe1, borderColor); e1 = newe1; e2 = newe2; @@ -1143,7 +1133,7 @@ namespace XCharts realStartInAngle = startAngle - 2 * spaceHalfAngle - borderHalfAngle - roundAngle; } var roundTotalDegree = realStartOutAngle * Mathf.Rad2Deg; - var roundCenter = p + roundAngleRadius * GetDire(realStartOutAngle); + var roundCenter = p + roundAngleRadius * ChartHelper.GetDire(realStartOutAngle); var sectorStartDegree = clockwise ? roundTotalDegree + 180 : roundTotalDegree; var sectorToDegree = clockwise ? roundTotalDegree + 360 : roundTotalDegree + 180; DrawSector(vh, roundCenter, roundRadius, color, sectorStartDegree, sectorToDegree, smoothness / 2); @@ -1152,8 +1142,8 @@ namespace XCharts DrawDoughnut(vh, roundCenter, roundRadius, roundRadius + borderWidth, borderColor, Color.clear, sectorStartDegree, sectorToDegree, smoothness / 2); } - p1 = GetPos(p, insideRadius, realStartOutAngle); - p2 = GetPos(p, outsideRadius, realStartOutAngle); + p1 = ChartHelper.GetPos(p, insideRadius, realStartOutAngle); + p2 = ChartHelper.GetPos(p, outsideRadius, realStartOutAngle); if (clockwise) { @@ -1168,7 +1158,7 @@ namespace XCharts if (realToOutAngle > realStartOutAngle) realToOutAngle = realStartOutAngle; } roundTotalDegree = realToOutAngle * Mathf.Rad2Deg; - roundCenter = p + roundAngleRadius * GetDire(realToOutAngle); + roundCenter = p + roundAngleRadius * ChartHelper.GetDire(realToOutAngle); sectorStartDegree = clockwise ? roundTotalDegree : roundTotalDegree + 180; sectorToDegree = clockwise ? roundTotalDegree + 180 : roundTotalDegree + 360; DrawSector(vh, roundCenter, roundRadius, color, sectorStartDegree, sectorToDegree, smoothness / 2); @@ -1177,8 +1167,8 @@ namespace XCharts DrawDoughnut(vh, roundCenter, roundRadius, roundRadius + borderWidth, borderColor, Color.clear, sectorStartDegree, sectorToDegree, smoothness / 2); } - e1 = GetPos(p, insideRadius, realToOutAngle); - e2 = GetPos(p, outsideRadius, realToOutAngle); + e1 = ChartHelper.GetPos(p, insideRadius, realToOutAngle); + e2 = ChartHelper.GetPos(p, outsideRadius, realToOutAngle); } float segmentAngle = (realToInAngle - realStartInAngle) / segments; for (int i = 0; i <= segments; i++) diff --git a/Runtime/Utility/ChartHelper.cs b/Runtime/Utility/ChartHelper.cs index 601ef77b..1f7cc387 100644 --- a/Runtime/Utility/ChartHelper.cs +++ b/Runtime/Utility/ChartHelper.cs @@ -759,5 +759,33 @@ namespace XCharts angle = (angle + 360) % 360; return angle; } + + public static Vector3 GetPos(Vector3 center, float radius, float angle, bool isDegree = false) + { + angle = isDegree ? angle * Mathf.Deg2Rad : angle; + return new Vector3(center.x + radius * Mathf.Sin(angle), center.y + radius * Mathf.Cos(angle)); + } + + public static Vector3 GetDire(float angle, bool isDegree = false) + { + angle = isDegree ? angle * Mathf.Deg2Rad : angle; + return new Vector3(Mathf.Sin(angle), Mathf.Cos(angle)); + } + + public static Vector3 GetVertialDire(Vector3 dire) + { + if (dire.x == 0) + { + return new Vector3(-1, 0, 0); + } + if (dire.y == 0) + { + return new Vector3(0, -1, 0); + } + else + { + return new Vector3(-dire.y / dire.x, 1, 0).normalized; + } + } } } \ No newline at end of file