Compare commits

..

23 Commits

Author SHA1 Message Date
monitor1394
3deda8d9cc Merge branch 'master' into 3.0 2026-03-01 12:21:33 +08:00
monitor1394
b9c72c8cb9 Merge branch 'master' into 3.0 2025-01-01 23:13:07 +08:00
monitor1394
d76b474a61 Merge branch 'master' into 3.0 2024-12-01 22:20:39 +08:00
monitor1394
2acb841d2e Merge branch 'master' into 3.0 2024-09-30 18:35:24 +08:00
monitor1394
8af1796ff8 Merge branch 'master' into 3.0 2024-06-16 22:08:09 +08:00
monitor1394
78a07aa2ae 修复Pie在设置ItemStyleopacity时颜色不对的问题 (#309) 2024-03-19 22:33:19 +08:00
monitor1394
fe867d18e6 Merge branch 'master' into 3.0 2024-03-19 22:03:31 +08:00
monitor1394
757ccb04fb 修复Tooltip移出坐标系后还显示的问题 2024-03-11 08:00:40 +08:00
monitor1394
b4cb62241f 修复Legendformatter在设置{d}通配符时显示可能不匹配的问题 (#304) 2024-03-11 07:01:22 +08:00
monitor1394
0fb1cab302 修复Legendformatter在设置{d}通配符时显示可能不匹配的问题 2024-03-11 07:00:31 +08:00
monitor1394
ead9034870 修复Tooltiptitle从旧版本升级后可能不显示的问题 2024-03-08 08:39:06 +08:00
monitor1394
20c87265a5 Merge branch 'master' into 3.0 2024-02-23 08:34:58 +08:00
monitor1394
1e31cf3010 Merge branch 'master' into 3.0 2023-12-01 22:22:51 +08:00
monitor1394
d33e2f66ef Merge branch 'master' into 3.0 2023-10-02 12:12:58 +08:00
monitor1394
ff4fbb2176 Merge branch 'master' into 3.0 2023-09-04 22:43:48 +08:00
monitor1394
9fc3ff862f Merge branch 'master' into 3.0 2023-06-09 08:05:20 +08:00
monitor1394
bc31f86fcf Merge branch 'master' into 3.0 2023-04-01 23:16:34 +08:00
monitor1394
c6412f5d78 Merge branch 'master' into 3.0 2022-12-01 21:45:30 +08:00
monitor1394
0e60a26333 Merge branch 'master' into 3.0 2022-11-01 07:54:45 +08:00
monitor1394
59bb60950b Merge branch 'master' into 3.0 2022-09-28 08:25:06 +08:00
monitor1394
b3320bd2cd Merge branch 'master' into 3.0 2022-08-16 08:31:50 +08:00
monitor1394
71cfbc15f3 Merge branch 'master' into 3.0 2022-07-12 07:43:08 +08:00
monitor1394
8fbda1fa73 v3.0.1 2022-06-16 08:18:07 +08:00
50 changed files with 295 additions and 4119 deletions

View File

@@ -81,25 +81,6 @@ slug: /changelog
## master
* (2026.06.03) 修复`GetMinMaxData``showDataDimension`未初始化时返回异常的问题 (#365)
* (2026.06.03) 增加`Tooltip`通过`TitleLabelStyle``TextStyle``Alignment`设置标题的对齐方式 (#363)
* (2026.05.25) 增加`DataZoom``filterAxisRange`设置坐标轴的范围计算是否受`DataZoom`的影响
* (2026.05.23) 优化`DataZoom``Marquee`框选功能
* (2026.05.23) 修复`DataZoom`内绘制的折线图可能会超出范围的问题
* (2026.05.23) 修复`Axis``inverse`没能正确反转的问题
* (2026.05.23) 增加`LabelStyle``showCondition`,`showFilter`,`showThreshold`可控制`label`显示和隐藏
* (2026.05.22) 增加`LabelStyle``minGap`可避免`label`过于密集
* (2026.05.17) 修复`DataZoom`点击时指示区域不准的问题
* (2026.05.17) 增加`Legend``Width``Height`可设置固定宽高
* (2026.05.17) 修复`Serie``EndLabel``Y`轴是`MinMax`类型时显示的数值不对的问题
* (2026.05.16) 修复`Candlestick`按昨收判断涨跌颜色,一字涨停/跌停显示不对的问题 (#362)
* (2026.03.29) 修复`Legend``Background`区域在`Horizonal`模式下不对的问题
* (2026.03.25) 增加`Chart``Json`导出导入
* (2026.03.10) 增加`Sankey`的线条tooltip触发显示
* (2026.03.10) 增加`UITable`的排序功能支持
* (2026.03.07) 修复`UITable`在尺寸变化时背景没有实时刷新的问题
* (2026.03.06) 优化`UITable`支持万级以上数据量不卡顿
## v3.15.0
版本要点:

View File

@@ -19,8 +19,6 @@ namespace XCharts.Editor
public static readonly GUIContent btnSaveAsImage = new GUIContent("Save As Image", "");
public static readonly GUIContent btnCheckWarning = new GUIContent("Check Warning", "");
public static readonly GUIContent btnHideWarning = new GUIContent("Hide Warning", "");
public static readonly GUIContent btnImportJsonData = new GUIContent("Import Json", "");
public static readonly GUIContent btnExportJsonData = new GUIContent("Export Json", "");
}
protected BaseChart m_Chart;
protected SerializedProperty m_Script;
@@ -38,7 +36,6 @@ namespace XCharts.Editor
private bool m_BaseFoldout;
private bool m_CheckWarning = false;
private bool m_ExportPending = false;
private int m_LastComponentCount = 0;
private int m_LastSerieCount = 0;
private string m_VersionString = "";
@@ -303,14 +300,6 @@ namespace XCharts.Editor
m_CheckWarning = false;
}
EditorGUILayout.EndHorizontal();
if (GUILayout.Button(Styles.btnImportJsonData))
{
ChartJsonImportWindow.ShowWindow(m_Chart);
}
if (GUILayout.Button(Styles.btnExportJsonData))
{
RequestExportJsonData();
}
sb.Length = 0;
sb.AppendFormat("v{0}", XChartsMgr.fullVersion);
if (!string.IsNullOrEmpty(m_Chart.warningInfo))
@@ -332,47 +321,8 @@ namespace XCharts.Editor
m_CheckWarning = true;
m_Chart.CheckWarning();
}
if (GUILayout.Button(Styles.btnImportJsonData))
{
ChartJsonImportWindow.ShowWindow(m_Chart);
}
if (GUILayout.Button(Styles.btnExportJsonData))
{
RequestExportJsonData();
}
}
}
private void RequestExportJsonData()
{
if (m_ExportPending) return;
m_ExportPending = true;
var chart = m_Chart;
EditorApplication.delayCall += delegate()
{
m_ExportPending = false;
ExportJsonData(chart);
};
GUIUtility.ExitGUI();
}
private static void ExportJsonData(BaseChart chart)
{
if (chart == null) return;
var json = chart.ExportToJson(true);
var defaultName = chart.gameObject.name + ".json";
var path = EditorUtility.SaveFilePanel("Save Chart JSON", "", defaultName, "json");
if (string.IsNullOrEmpty(path)) return;
try
{
System.IO.File.WriteAllText(path, json);
Debug.Log("[XCharts] JSON exported to: " + path);
}
catch (Exception ex)
{
Debug.LogError("[XCharts] Failed to save JSON: " + ex.Message);
}
}
}
}

View File

@@ -113,15 +113,6 @@ namespace XCharts.Editor
}
}
protected void PropertyFlagsField(SerializedProperty prop, string relativePropName, System.Type enumType)
{
if (IngorePropertys.Contains(relativePropName)) return;
if (!ChartEditorHelper.PropertyFlagsField(ref m_DrawRect, m_Heights, m_KeyName, prop, relativePropName, enumType))
{
Debug.LogError("PropertyFlagsField ERROR:" + prop.displayName + ", " + relativePropName);
}
}
protected void PropertyFieldLimitMin(SerializedProperty prop, string relativePropName, float minValue)
{
if (IngorePropertys.Contains(relativePropName)) return;

View File

@@ -26,10 +26,6 @@ namespace XCharts.Editor
PropertyField(prop, "m_Height");
PropertyField(prop, "m_FixedX");
PropertyField(prop, "m_FixedY");
PropertyField(prop, "m_ShowCondition");
PropertyField(prop, "m_ShowFilter");
PropertyField(prop, "m_ShowThreshold");
PropertyField(prop, "m_ShowMinGap");
PropertyField(prop, "m_Icon");
PropertyField(prop, "m_Background");
PropertyField(prop, "m_TextStyle");

View File

@@ -28,11 +28,10 @@ namespace XCharts.Editor
PropertyField("m_ScrollSensitivity");
PropertyField("m_RangeMode");
PropertyField(m_Start);
PropertyField("m_StartLock");
PropertyField(m_End);
PropertyField("m_StartLock");
PropertyField("m_EndLock");
PropertyField(m_MinZoomRatio);
PropertyField("m_FilterAxisRange");
if (m_Start.floatValue < 0) m_Start.floatValue = 0;
if (m_End.floatValue > 100) m_End.floatValue = 100;
if (m_MinZoomRatio.floatValue < 0) m_MinZoomRatio.floatValue = 0;

View File

@@ -10,8 +10,6 @@ namespace XCharts.Editor
{
++EditorGUI.indentLevel;
PropertyField("m_IconType");
PropertyField("m_Width");
PropertyField("m_Height");
PropertyField("m_ItemWidth");
PropertyField("m_ItemHeight");
PropertyField("m_ItemGap");

View File

@@ -13,13 +13,10 @@ namespace XCharts.Editor
public static readonly GUIContent btnAddComponent = new GUIContent("Add Main Component", "");
public static readonly GUIContent btnRebuildChartObject = new GUIContent("Rebuild Object", "");
public static readonly GUIContent btnSaveAsImage = new GUIContent("Save As Image", "");
public static readonly GUIContent btnImportJsonData = new GUIContent("Import Json", "");
public static readonly GUIContent btnExportJsonData = new GUIContent("Export Json", "");
public static readonly GUIContent btnCheckWarning = new GUIContent("Check Warning", "");
public static readonly GUIContent btnHideWarning = new GUIContent("Hide Warning", "");
}
public UIComponent m_UIComponent;
private bool m_ExportPending;
public static T AddUIComponent<T>(string chartName) where T : UIComponent
{
@@ -59,14 +56,6 @@ namespace XCharts.Editor
{
m_UIComponent.SaveAsImage("png", "", 4f);
}
if (GUILayout.Button(Styles.btnImportJsonData))
{
UIComponentJsonImportWindow.ShowWindow(m_UIComponent);
}
if (GUILayout.Button(Styles.btnExportJsonData))
{
RequestExportJsonData();
}
OnDebugEndInspectorGUI();
}
@@ -100,37 +89,6 @@ namespace XCharts.Editor
EditorGUILayout.PropertyField(property, title);
}
private void RequestExportJsonData()
{
if (m_ExportPending) return;
m_ExportPending = true;
var target = m_UIComponent;
EditorApplication.delayCall += delegate ()
{
m_ExportPending = false;
ExportJsonData(target);
};
GUIUtility.ExitGUI();
}
private static void ExportJsonData(UIComponent target)
{
if (target == null) return;
var json = target.ExportToJson(true);
var defaultName = target.gameObject.name + ".json";
var path = EditorUtility.SaveFilePanel("Save UI Component JSON", "", defaultName, "json");
if (string.IsNullOrEmpty(path)) return;
try
{
System.IO.File.WriteAllText(path, json);
Debug.Log("[XCharts] UI JSON exported to: " + path);
}
catch (System.Exception ex)
{
Debug.LogError("[XCharts] Failed to save UI JSON: " + ex.Message);
}
}
protected void PropertyListField(string relativePropName, bool showOrder = true, params HeaderMenuInfo[] menus)
{
var m_DrawRect = GUILayoutUtility.GetRect(1f, 17f);

View File

@@ -507,28 +507,6 @@ namespace XCharts.Editor
{
return PropertyField(ref drawRect, heights, key, parentProp.FindPropertyRelative(relativeName));
}
public static bool PropertyFlagsField(ref Rect drawRect, Dictionary<string, float> heights, string key,
SerializedProperty parentProp, string relativeName, System.Type enumType)
{
return PropertyFlagsField(ref drawRect, heights, key, parentProp.FindPropertyRelative(relativeName), enumType);
}
public static bool PropertyFlagsField(ref Rect drawRect, Dictionary<string, float> heights, string key,
SerializedProperty prop, System.Type enumType)
{
if (prop == null) return false;
var label = GetContent(prop.displayName);
var enumValue = (System.Enum)System.Enum.ToObject(enumType, prop.intValue);
EditorGUI.BeginChangeCheck();
var newValue = EditorGUI.EnumFlagsField(drawRect, label, enumValue);
if (EditorGUI.EndChangeCheck())
prop.intValue = (int)(object)newValue;
var hig = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
drawRect.y += hig;
heights[key] += hig;
return true;
}
public static bool PropertyFieldWithMinValue(ref Rect drawRect, Dictionary<string, float> heights, string key,
SerializedProperty parentProp, string relativeName, float minValue)
{

View File

@@ -1,201 +0,0 @@
using UnityEditor;
using UnityEngine;
using XCharts.Runtime;
namespace XCharts.Editor
{
public class ChartJsonImportWindow : EditorWindow
{
private const int TEXTAREA_SAFE_CHAR_LIMIT = 8000;
private const int LARGE_JSON_PREVIEW_CHAR_LIMIT = 4000;
private static BaseChart s_TargetChart;
private string m_JsonInput = "";
private Vector2 m_ScrollPos;
private bool m_ShowPreview = false;
private string m_PreviewText = "";
private bool m_OpenFilePending = false;
private bool m_PreviewPending = false;
private bool m_ImportPending = false;
public static void ShowWindow(BaseChart targetChart)
{
s_TargetChart = targetChart;
var window = GetWindow<ChartJsonImportWindow>("Import Chart JSON");
window.minSize = new Vector2(600, 400);
window.Show();
}
private void OnGUI()
{
if (s_TargetChart == null)
{
EditorGUILayout.HelpBox("Target chart is null. Please select a chart first.", MessageType.Error);
if (GUILayout.Button("Close")) Close();
return;
}
if (m_JsonInput == null) m_JsonInput = "";
EditorGUILayout.LabelField("Target Chart: " + s_TargetChart.gameObject.name, EditorStyles.boldLabel);
GUILayout.Space(10);
EditorGUILayout.LabelField("Paste JSON Data:", EditorStyles.boldLabel);
using (var scroll = new EditorGUILayout.ScrollViewScope(m_ScrollPos, GUILayout.Height(250)))
{
m_ScrollPos = scroll.scrollPosition;
if (m_JsonInput.Length <= TEXTAREA_SAFE_CHAR_LIMIT)
{
m_JsonInput = EditorGUILayout.TextArea(m_JsonInput, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
}
else
{
var preview = m_JsonInput.Substring(0, LARGE_JSON_PREVIEW_CHAR_LIMIT);
EditorGUILayout.HelpBox("JSON content is very large. To avoid editor text rendering limits, only a preview is shown below. Import uses full content.", MessageType.Info);
EditorGUILayout.TextArea(preview, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
}
}
EditorGUILayout.HelpBox("Paste JSON directly, or click Open Json File.", MessageType.Info);
GUILayout.Space(10);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Open Json File", GUILayout.Width(120)))
{
RequestOpenJsonFile();
}
if (GUILayout.Button("Preview", GUILayout.Width(100)))
{
RequestPreviewJson();
}
}
if (m_ShowPreview && !string.IsNullOrEmpty(m_PreviewText))
{
EditorGUILayout.TextArea(m_PreviewText, GUILayout.Height(150));
}
GUILayout.Space(10);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Import", GUILayout.Height(40), GUILayout.Width(150)))
{
RequestImportJson();
}
if (GUILayout.Button("Cancel", GUILayout.Height(40), GUILayout.Width(150))) Close();
}
}
private void RequestOpenJsonFile()
{
if (m_OpenFilePending) return;
m_OpenFilePending = true;
EditorApplication.delayCall += delegate ()
{
m_OpenFilePending = false;
if (this == null) return;
OpenJsonFile();
Repaint();
};
GUIUtility.ExitGUI();
}
private void RequestPreviewJson()
{
if (m_PreviewPending) return;
m_PreviewPending = true;
EditorApplication.delayCall += delegate ()
{
m_PreviewPending = false;
if (this == null) return;
PreviewJson();
Repaint();
};
GUIUtility.ExitGUI();
}
private void RequestImportJson()
{
if (m_ImportPending) return;
m_ImportPending = true;
EditorApplication.delayCall += delegate ()
{
m_ImportPending = false;
if (this == null) return;
ImportJson();
};
GUIUtility.ExitGUI();
}
private void PreviewJson()
{
if (string.IsNullOrEmpty(m_JsonInput))
{
EditorUtility.DisplayDialog("Error", "JSON input is empty.", "OK");
return;
}
try
{
var json = JsonUtility.FromJson<XCharts.Runtime.ChartJson>(m_JsonInput);
if (json == null)
{
m_PreviewText = "Invalid JSON or unsupported schema.";
}
else
{
var componentCount = json.components != null ? json.components.Count : 0;
var seriesCount = json.series != null ? json.series.Count : 0;
m_PreviewText = "Chart Type: " + json.chartType + "\nComponents: " + componentCount + "\nSeries: " + seriesCount + "\n(Full validation on import)";
}
m_ShowPreview = true;
}
catch (System.Exception ex)
{
EditorUtility.DisplayDialog("Preview Error", "Invalid JSON: " + ex.Message, "OK");
}
}
private void ImportJson()
{
if (string.IsNullOrEmpty(m_JsonInput))
{
EditorUtility.DisplayDialog("Error", "JSON input is empty. Please paste JSON data.", "OK");
return;
}
try
{
Undo.RecordObject(s_TargetChart, "Import Chart JSON");
s_TargetChart.ImportFromJson(m_JsonInput);
s_TargetChart.RebuildChartObject();
s_TargetChart.RefreshAllComponent();
s_TargetChart.RefreshChart();
EditorUtility.SetDirty(s_TargetChart);
UnityEditor.SceneView.RepaintAll();
var chart = s_TargetChart;
EditorApplication.delayCall += delegate ()
{
if (chart == null) return;
chart.RefreshAllComponent();
chart.RefreshChart();
UnityEditor.SceneView.RepaintAll();
};
EditorUtility.DisplayDialog("Success", "Chart '" + s_TargetChart.gameObject.name + "' imported successfully!", "OK");
Close();
}
catch (System.Exception ex)
{
EditorUtility.DisplayDialog("Import Error", "Failed to import JSON:\n" + ex.Message + "\n\n" + ex.StackTrace, "OK");
}
}
private void OpenJsonFile()
{
var path = EditorUtility.OpenFilePanel("Open Chart JSON", "", "json");
if (string.IsNullOrEmpty(path)) return;
try
{
m_JsonInput = System.IO.File.ReadAllText(path);
m_ShowPreview = false;
m_PreviewText = "";
}
catch (System.Exception ex)
{
EditorUtility.DisplayDialog("Open File Error", "Failed to read JSON file:\n" + ex.Message, "OK");
}
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 21f2eafb07ab34d4abf575784acc56a3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,193 +0,0 @@
using UnityEditor;
using UnityEngine;
using XCharts.Runtime;
namespace XCharts.Editor
{
public class UIComponentJsonImportWindow : EditorWindow
{
private const int TEXTAREA_SAFE_CHAR_LIMIT = 8000;
private const int LARGE_JSON_PREVIEW_CHAR_LIMIT = 4000;
private static UIComponent s_TargetComponent;
private string m_JsonInput = "";
private Vector2 m_ScrollPos;
private bool m_ShowPreview = false;
private string m_PreviewText = "";
private bool m_OpenFilePending = false;
private bool m_PreviewPending = false;
private bool m_ImportPending = false;
public static void ShowWindow(UIComponent target)
{
s_TargetComponent = target;
var window = GetWindow<UIComponentJsonImportWindow>("Import UI JSON");
window.minSize = new Vector2(600, 400);
window.Show();
}
private void OnGUI()
{
if (s_TargetComponent == null)
{
EditorGUILayout.HelpBox("Target UI component is null. Please select a component first.", MessageType.Error);
if (GUILayout.Button("Close")) Close();
return;
}
if (m_JsonInput == null) m_JsonInput = "";
EditorGUILayout.LabelField("Target: " + s_TargetComponent.gameObject.name + " (" + s_TargetComponent.GetType().Name + ")", EditorStyles.boldLabel);
GUILayout.Space(10);
EditorGUILayout.LabelField("Paste JSON Data:", EditorStyles.boldLabel);
using (var scroll = new EditorGUILayout.ScrollViewScope(m_ScrollPos, GUILayout.Height(250)))
{
m_ScrollPos = scroll.scrollPosition;
if (m_JsonInput.Length <= TEXTAREA_SAFE_CHAR_LIMIT)
{
m_JsonInput = EditorGUILayout.TextArea(m_JsonInput, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
}
else
{
var preview = m_JsonInput.Substring(0, LARGE_JSON_PREVIEW_CHAR_LIMIT);
EditorGUILayout.HelpBox("JSON content is very large. Only a preview is shown below. Import uses full content.", MessageType.Info);
EditorGUILayout.TextArea(preview, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
}
}
EditorGUILayout.HelpBox("Paste JSON directly, or click Open Json File.", MessageType.Info);
GUILayout.Space(10);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Open Json File", GUILayout.Width(120))) RequestOpenJsonFile();
if (GUILayout.Button("Preview", GUILayout.Width(100))) RequestPreviewJson();
}
if (m_ShowPreview && !string.IsNullOrEmpty(m_PreviewText))
{
EditorGUILayout.TextArea(m_PreviewText, GUILayout.Height(120));
}
GUILayout.Space(10);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Import", GUILayout.Height(40), GUILayout.Width(150))) RequestImportJson();
if (GUILayout.Button("Cancel", GUILayout.Height(40), GUILayout.Width(150))) Close();
}
}
private void RequestOpenJsonFile()
{
if (m_OpenFilePending) return;
m_OpenFilePending = true;
EditorApplication.delayCall += delegate ()
{
m_OpenFilePending = false;
if (this == null) return;
OpenJsonFile();
Repaint();
};
GUIUtility.ExitGUI();
}
private void RequestPreviewJson()
{
if (m_PreviewPending) return;
m_PreviewPending = true;
EditorApplication.delayCall += delegate ()
{
m_PreviewPending = false;
if (this == null) return;
PreviewJson();
Repaint();
};
GUIUtility.ExitGUI();
}
private void RequestImportJson()
{
if (m_ImportPending) return;
m_ImportPending = true;
EditorApplication.delayCall += delegate ()
{
m_ImportPending = false;
if (this == null) return;
ImportJson();
};
GUIUtility.ExitGUI();
}
private void PreviewJson()
{
if (string.IsNullOrEmpty(m_JsonInput))
{
EditorUtility.DisplayDialog("Error", "JSON input is empty.", "OK");
return;
}
try
{
var json = JsonUtility.FromJson<UIComponentJson>(m_JsonInput);
if (json == null)
m_PreviewText = "Invalid JSON or unsupported schema.";
else
m_PreviewText = "Component Type: " + json.componentType + "\nSchema: " + json.schemaVersion + "\nVersion: " + json.componentVersion;
m_ShowPreview = true;
}
catch (System.Exception ex)
{
EditorUtility.DisplayDialog("Preview Error", "Invalid JSON: " + ex.Message, "OK");
}
}
private void ImportJson()
{
if (string.IsNullOrEmpty(m_JsonInput))
{
EditorUtility.DisplayDialog("Error", "JSON input is empty. Please paste JSON data.", "OK");
return;
}
try
{
Undo.RecordObject(s_TargetComponent, "Import UI Component JSON");
s_TargetComponent.ImportFromJson(m_JsonInput);
s_TargetComponent.RebuildChartObject();
s_TargetComponent.RefreshAllComponent();
s_TargetComponent.RefreshGraph();
EditorUtility.SetDirty(s_TargetComponent);
SceneView.RepaintAll();
var target = s_TargetComponent;
EditorApplication.delayCall += delegate ()
{
if (target == null) return;
target.RefreshAllComponent();
target.RefreshGraph();
SceneView.RepaintAll();
};
EditorUtility.DisplayDialog("Success", "UI component imported successfully!", "OK");
Close();
}
catch (System.Exception ex)
{
EditorUtility.DisplayDialog("Import Error", "Failed to import JSON:\n" + ex.Message + "\n\n" + ex.StackTrace, "OK");
}
}
private void OpenJsonFile()
{
var path = EditorUtility.OpenFilePanel("Open UI Component JSON", "", "json");
if (string.IsNullOrEmpty(path)) return;
try
{
m_JsonInput = System.IO.File.ReadAllText(path);
m_ShowPreview = false;
m_PreviewText = "";
}
catch (System.Exception ex)
{
EditorUtility.DisplayDialog("Open File Error", "Failed to read JSON file:\n" + ex.Message, "OK");
}
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 68157a6f7d4e94ccc8ccbb4913d187f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -103,35 +103,22 @@ namespace XCharts.Runtime
internal void UpdateFilterData(List<string> data, DataZoom dataZoom)
{
int start = 0, end = 0;
// Use (N-1) intervals to match shadow drawing (scaleWid = width/(N-1)).
// CeilToInt for start, FloorToInt for end, so filter aligns exactly with filler.
int n = data.Count - 1;
int startIndex, endIndex;
if (n > 0)
var range = Mathf.RoundToInt(data.Count * (dataZoom.end - dataZoom.start) / 100);
if (range <= 0)
range = 1;
if (dataZoom.context.invert)
{
if (dataZoom.context.invert)
{
startIndex = Mathf.CeilToInt((float)n * (100 - dataZoom.end) / 100);
endIndex = Mathf.FloorToInt((float)n * (100 - dataZoom.start) / 100);
}
else
{
startIndex = Mathf.CeilToInt((float)n * dataZoom.start / 100);
endIndex = Mathf.FloorToInt((float)n * dataZoom.end / 100);
}
end = Mathf.RoundToInt(data.Count * dataZoom.end / 100);
start = end - range;
if (start < 0) start = 0;
}
else
{
startIndex = 0;
endIndex = 0;
start = Mathf.RoundToInt(data.Count * dataZoom.start / 100);
end = start + range;
if (end > data.Count) end = data.Count;
}
var range = endIndex - startIndex + 1;
if (range <= 0)
range = 1;
start = startIndex;
if (start < 0) start = 0;
end = start + range;
if (end > data.Count) end = data.Count;
var minZoomRatio = (int)(data.Count * dataZoom.minZoomRatio);
if (start != filterStart ||

View File

@@ -122,9 +122,10 @@ namespace XCharts
{
if (axis is YAxis)
{
var yValue = axis.context.minValue + (chart.pointerPos.y - grid.context.y) / grid.context.height * axis.context.minMaxRange;
if (axis.inverse)
yValue = -yValue;
var yRate = axis.context.minMaxRange / grid.context.height;
var yValue = yRate * (chart.pointerPos.y - grid.context.y - axis.context.offset);
if (axis.context.minValue > 0)
yValue += axis.context.minValue;
var labelX = axis.GetLabelObjectPosition(0).x;
axis.context.pointerValue = yValue;
@@ -149,9 +150,10 @@ namespace XCharts
}
else
{
xValue = axis.context.minValue + (chart.pointerPos.x - grid.context.x) / grid.context.width * axis.context.minMaxRange;
if (axis.inverse)
xValue = -xValue;
var xRate = axis.context.minMaxRange / grid.context.width;
xValue = xRate * (chart.pointerPos.x - grid.context.x - axis.context.offset);
if (axis.context.minValue > 0)
xValue += axis.context.minValue;
}
var labelY = axis.GetLabelObjectPosition(0).y;
axis.context.pointerValue = xValue;
@@ -186,20 +188,15 @@ namespace XCharts
double tempMinValue;
double tempMaxValue;
axis.context.needAnimation = Application.isPlaying && axis.animation.show;
if (axis.inverse != axis.context.lastCheckInverse)
{
foreach (var serie in chart.series)
serie.context.InvalidateMinMaxCache();
}
chart.GetSeriesMinMaxValue(axis, axisIndex, out tempMinValue, out tempMaxValue);
var dataZoom = chart.GetDataZoomOfAxis(axis);
if (dataZoom != null && dataZoom.enable)
{
if (axis is XAxis)
dataZoom.SetXAxisIndexValueInfo(axisIndex, ref tempMinValue, ref tempMaxValue, axis.inverse);
dataZoom.SetXAxisIndexValueInfo(axisIndex, ref tempMinValue, ref tempMaxValue);
else
dataZoom.SetYAxisIndexValueInfo(axisIndex, ref tempMinValue, ref tempMaxValue, axis.inverse);
dataZoom.SetYAxisIndexValueInfo(axisIndex, ref tempMinValue, ref tempMaxValue);
}
if (tempMinValue != axis.context.destMinValue ||

View File

@@ -495,17 +495,17 @@ namespace XCharts.Runtime
public static double GetAxisPositionValue(GridCoord grid, Axis axis, Vector3 pos)
{
if (axis is YAxis)
return GetAxisPositionValue(pos.y, grid.context.height, axis.context.minMaxRange, grid.context.y, axis.context.offset, axis.context.minValue);
return GetAxisPositionValue(pos.y, grid.context.height, axis.context.minMaxRange, grid.context.y, axis.context.offset);
else if (axis is XAxis)
return GetAxisPositionValue(pos.x, grid.context.width, axis.context.minMaxRange, grid.context.x, axis.context.offset, axis.context.minValue);
return GetAxisPositionValue(pos.x, grid.context.width, axis.context.minMaxRange, grid.context.x, axis.context.offset);
else
return 0;
}
public static double GetAxisPositionValue(float xy, float axisLength, double axisRange, float axisStart, float axisOffset, double minValue = 0)
public static double GetAxisPositionValue(float xy, float axisLength, double axisRange, float axisStart, float axisOffset)
{
var yRate = axisRange / axisLength;
return minValue + yRate * (xy - axisStart - axisOffset);
return yRate * (xy - axisStart - axisOffset);
}
/// <summary>

View File

@@ -15,7 +15,7 @@ namespace XCharts.Runtime
[SerializeField] private float m_BorderWidth;
[SerializeField] private Color32 m_BorderColor;
[SerializeField] private bool m_RoundedCorner = true;
[SerializeField] private float[] m_CornerRadius = new float[] { 10, 10, 10, 10 };
[SerializeField] private float[] m_CornerRadius = new float[] { 0, 0, 0, 0 };
/// <summary>
/// whether the border is visible.

View File

@@ -11,8 +11,8 @@ namespace XCharts.Runtime
[Since("v3.5.0")]
[System.Serializable]
public class MarqueeStyle : ChildComponent
{
[SerializeField][Since("v3.5.0")] private bool m_Apply = true;
{
[SerializeField][Since("v3.5.0")] private bool m_Apply = false;
[SerializeField][Since("v3.5.0")] private bool m_RealRect = false;
[SerializeField][Since("v3.5.0")] private AreaStyle m_AreaStyle = new AreaStyle();
[SerializeField][Since("v3.5.0")] private LineStyle m_LineStyle = new LineStyle();
@@ -52,7 +52,7 @@ namespace XCharts.Runtime
/// Custom checkboxes select ongoing callbacks.
/// ||自定义选取框选取进行时的回调。
/// </summary>
public Action<DataZoom> onGoing { set { m_OnGoing = value; } get { return m_OnGoing; } }
public Action<DataZoom> onGoing { set { m_OnStart = value; } get { return m_OnStart; } }
/// <summary>
/// Customize the callback at the end of the selection.
/// ||自定义选取框结束选取时的回调。

View File

@@ -92,7 +92,6 @@ namespace XCharts.Runtime
[SerializeField][Since("v3.5.0")] private MarqueeStyle m_MarqueeStyle = new MarqueeStyle();
[SerializeField][Since("v3.6.0")] private bool m_StartLock;
[SerializeField][Since("v3.6.0")] private bool m_EndLock;
[SerializeField][Since("v3.12.0")] private bool m_FilterAxisRange = true;
public DataZoomContext context = new DataZoomContext();
private CustomDataZoomStartEndFunction m_StartEndFunction;
@@ -326,16 +325,6 @@ namespace XCharts.Runtime
set { if (PropertyUtil.SetStruct(ref m_EndLock, value)) SetVerticesDirty(); }
}
/// <summary>
/// Whether dataZoom filters the axis min/max range. When true, the axis scale adapts to the current zoom window.
/// When false, the axis always shows the full data range regardless of the zoom position.
/// ||是否根据DataZoom的缩放窗口过滤坐标轴的最大最小值范围。为true时坐标轴范围随缩放窗口变化为false时坐标轴始终显示全部数据范围。
/// </summary>
public bool filterAxisRange
{
get { return m_FilterAxisRange; }
set { if (PropertyUtil.SetStruct(ref m_FilterAxisRange, value)) SetVerticesDirty(); }
}
/// <summary>
/// The end percentage of the window out of the data extent, in the range of 0 ~ 100.
/// ||数据窗口范围的结束百分比。范围是0 ~ 100。
/// </summary>
@@ -426,7 +415,6 @@ namespace XCharts.Runtime
public double rawMax;
public double min;
public double max;
public bool isInverse;
}
private Dictionary<int, AxisIndexValueInfo> m_XAxisIndexInfos = new Dictionary<int, AxisIndexValueInfo>();
private Dictionary<int, AxisIndexValueInfo> m_YAxisIndexInfos = new Dictionary<int, AxisIndexValueInfo>();
@@ -700,7 +688,7 @@ namespace XCharts.Runtime
context.height = chartHeight - runtimeTop - runtimeBottom;
}
internal void SetXAxisIndexValueInfo(int xAxisIndex, ref double min, ref double max, bool isInverse = false)
internal void SetXAxisIndexValueInfo(int xAxisIndex, ref double min, ref double max)
{
AxisIndexValueInfo info;
if (!m_XAxisIndexInfos.TryGetValue(xAxisIndex, out info))
@@ -710,14 +698,13 @@ namespace XCharts.Runtime
}
info.rawMin = min;
info.rawMax = max;
info.isInverse = isInverse;
info.min = min + (max - min) * start / 100;
info.max = min + (max - min) * end / 100;
min = info.min;
max = info.max;
}
internal void SetYAxisIndexValueInfo(int yAxisIndex, ref double min, ref double max, bool isInverse = false)
internal void SetYAxisIndexValueInfo(int yAxisIndex, ref double min, ref double max)
{
AxisIndexValueInfo info;
if (!m_YAxisIndexInfos.TryGetValue(yAxisIndex, out info))
@@ -727,7 +714,6 @@ namespace XCharts.Runtime
}
info.rawMin = min;
info.rawMax = max;
info.isInverse = isInverse;
info.min = min + (max - min) * start / 100;
info.max = min + (max - min) * end / 100;
min = info.min;
@@ -752,14 +738,6 @@ namespace XCharts.Runtime
var range = info.rawMax - info.rawMin;
min = info.rawMin + range * m_Start / 100;
max = info.rawMin + range * m_End / 100;
if (info.isInverse)
{
// Internal values are negated; convert back to original for data comparison
var originalMin = -max;
var originalMax = -min;
min = originalMin;
max = originalMax;
}
}
else
{
@@ -775,14 +753,6 @@ namespace XCharts.Runtime
var range = info.rawMax - info.rawMin;
min = info.rawMin + range * m_Start / 100;
max = info.rawMin + range * m_End / 100;
if (info.isInverse)
{
// Internal values are negated; convert back to original for data comparison
var originalMin = -max;
var originalMax = -min;
min = originalMin;
max = originalMax;
}
}
else
{

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
@@ -20,9 +19,6 @@ namespace XCharts.Runtime
private float m_DataZoomLastEndIndex;
private float m_LastStart;
private float m_LastEnd;
private List<double> _sampleSumPrefixCache;
private int _sampleSumPrefixMaxCount = 0;
private bool _sampleSumPrefixInverse = false;
public override void InitComponent()
{
@@ -117,7 +113,7 @@ namespace XCharts.Runtime
dataZoom.context.isCoordinateDrag = true;
}
}
if (dataZoom.supportMarquee && grid.Contains(pos))
if (dataZoom.supportMarquee)
{
dataZoom.context.isMarqueeDrag = true;
dataZoom.context.marqueeStartPos = pos;
@@ -167,7 +163,7 @@ namespace XCharts.Runtime
var dataZoom = component;
var grid = chart.GetGridOfDataZoom(dataZoom);
if (dataZoom.supportMarquee && dataZoom.context.isMarqueeDrag)
if (dataZoom.supportMarquee)
{
Vector2 pos;
if (!chart.ScreenPointToChartPoint(eventData.position, out pos))
@@ -211,38 +207,16 @@ namespace XCharts.Runtime
var dataZoom = component;
if (dataZoom.supportMarquee && dataZoom.context.isMarqueeDrag)
if (dataZoom.supportMarquee)
{
dataZoom.context.isMarqueeDrag = false;
if (dataZoom.marqueeStyle.apply)
{
var grid = chart.GetGridOfDataZoom(dataZoom);
var currentRange = dataZoom.end - dataZoom.start;
var startRatio = (dataZoom.context.marqueeRect.x - grid.context.x) / grid.context.width;
var endRatio = (dataZoom.context.marqueeRect.x - grid.context.x + dataZoom.context.marqueeRect.width) / grid.context.width;
var start = dataZoom.start + startRatio * currentRange;
var end = dataZoom.start + endRatio * currentRange;
if (start > end)
{
var temp = start;
start = end;
end = temp;
}
if (start < 0) start = 0;
if (end > 100) end = 100;
if (end - start >= 1)
{
// Bypass minZoomRatio for marquee: the user explicitly selected this region.
if (!dataZoom.startLock) dataZoom.start = start;
if (!dataZoom.endLock) dataZoom.end = end;
m_LastStart = dataZoom.start;
m_LastEnd = dataZoom.end;
chart.OnDataZoomRangeChanged(dataZoom);
chart.RefreshChart();
}
var start = (dataZoom.context.marqueeRect.x - grid.context.x) / grid.context.width * 100;
var end = (dataZoom.context.marqueeRect.x - grid.context.x + dataZoom.context.marqueeRect.width) / grid.context.width * 100;
UpdateDataZoomRange(dataZoom, start, end, grid);
}
dataZoom.context.marqueeRect = Rect.zero;
dataZoom.SetVerticesDirty();
if (dataZoom.marqueeStyle.onEnd != null)
{
dataZoom.marqueeStyle.onEnd(dataZoom);
@@ -273,55 +247,30 @@ namespace XCharts.Runtime
var dataZoom = component;
var grid = chart.GetGridOfDataZoom(dataZoom);
if (dataZoom.IsInStartZoom(localPos) ||
dataZoom.IsInEndZoom(localPos) ||
dataZoom.IsInSelectedZoom(localPos))
dataZoom.IsInEndZoom(localPos))
{
return;
}
if (dataZoom.IsInZoom(localPos))
if (dataZoom.IsInZoom(localPos) &&
!dataZoom.IsInSelectedZoom(localPos))
{
var start = dataZoom.start;
var end = dataZoom.end;
switch (dataZoom.orient)
var pointerX = localPos.x;
var selectWidth = grid.context.width * (dataZoom.end - dataZoom.start) / 100;
var startX = pointerX - selectWidth / 2;
var endX = pointerX + selectWidth / 2;
if (startX < grid.context.x)
{
case Orient.Horizonal:
var pointerX = localPos.x;
var selectWidth = dataZoom.context.width * (dataZoom.end - dataZoom.start) / 100;
var startX = pointerX - selectWidth / 2;
var endX = pointerX + selectWidth / 2;
if (startX < dataZoom.context.x)
{
startX = dataZoom.context.x;
endX = dataZoom.context.x + selectWidth;
}
else if (endX > dataZoom.context.x + dataZoom.context.width)
{
endX = dataZoom.context.x + dataZoom.context.width;
startX = dataZoom.context.x + dataZoom.context.width - selectWidth;
}
start = (startX - dataZoom.context.x) / dataZoom.context.width * 100;
end = (endX - dataZoom.context.x) / dataZoom.context.width * 100;
break;
case Orient.Vertical:
var pointerY = localPos.y;
var selectHeight = dataZoom.context.height * (dataZoom.end - dataZoom.start) / 100;
var startY = pointerY - selectHeight / 2;
var endY = pointerY + selectHeight / 2;
if (startY < dataZoom.context.y)
{
startY = dataZoom.context.y;
endY = dataZoom.context.y + selectHeight;
}
else if (endY > dataZoom.context.y + dataZoom.context.height)
{
endY = dataZoom.context.y + dataZoom.context.height;
startY = dataZoom.context.y + dataZoom.context.height - selectHeight;
}
start = (startY - dataZoom.context.y) / dataZoom.context.height * 100;
end = (endY - dataZoom.context.y) / dataZoom.context.height * 100;
break;
startX = grid.context.x;
endX = grid.context.x + selectWidth;
}
else if (endX > grid.context.x + grid.context.width)
{
endX = grid.context.x + grid.context.width;
startX = grid.context.x + grid.context.width - selectWidth;
}
var start = (startX - grid.context.x) / grid.context.width * 100;
var end = (endX - grid.context.x) / grid.context.width * 100;
UpdateDataZoomRange(dataZoom, start, end, grid);
}
}
@@ -345,7 +294,7 @@ namespace XCharts.Runtime
if ((dataZoom.supportInside && dataZoom.supportInsideScroll && grid.Contains(pos)) ||
dataZoom.IsInZoom(pos))
{
ScaleDataZoom(dataZoom, eventData.scrollDelta.y * dataZoom.scrollSensitivity, grid, pos);
ScaleDataZoom(dataZoom, eventData.scrollDelta.y * dataZoom.scrollSensitivity, grid);
}
}
@@ -426,61 +375,23 @@ namespace XCharts.Runtime
}
}
private void ScaleDataZoom(DataZoom dataZoom, float delta, GridCoord grid = null, Vector2? mousePos = null)
private void ScaleDataZoom(DataZoom dataZoom, float delta, GridCoord grid = null)
{
if (grid == null) grid = chart.GetGridOfDataZoom(dataZoom);
var gridRange = dataZoom.orient == Orient.Horizonal ? grid.context.width : grid.context.height;
var currentRange = dataZoom.end - dataZoom.start;
if (delta > 0 && currentRange <= 0) return;
// Mouse position as a fraction [0,1] within the grid.
// The grid always maps the current [start,end] window onto its full pixel width,
// so this fraction directly equals the mouse's relative position inside the data window.
float fraction = 0.5f;
if (mousePos.HasValue && gridRange > 0)
var range = dataZoom.orient == Orient.Horizonal ? grid.context.width : grid.context.height;
var deltaPercent = Mathf.Abs(delta / range * 100);
float start, end;
if (delta > 0)
{
float raw = dataZoom.orient == Orient.Horizonal
? (mousePos.Value.x - grid.context.x) / gridRange
: (mousePos.Value.y - grid.context.y) / gridRange;
bool isInverse = false;
if (dataZoom.orient == Orient.Horizonal && dataZoom.xAxisIndexs.Count > 0)
{
var xAxis = chart.GetChartComponent<XAxis>(dataZoom.xAxisIndexs[0]);
isInverse = xAxis != null && xAxis.inverse;
}
else if (dataZoom.orient == Orient.Vertical && dataZoom.yAxisIndexs.Count > 0)
{
var yAxis = chart.GetChartComponent<YAxis>(dataZoom.yAxisIndexs[0]);
isInverse = yAxis != null && yAxis.inverse;
}
fraction = Mathf.Clamp01(isInverse ? 1f - raw : raw);
if (dataZoom.end <= dataZoom.start) return;
start = dataZoom.start + deltaPercent;
end = dataZoom.end - deltaPercent;
}
// The data-space anchor (0~100) under the mouse — must stay at the same fraction after zoom.
var anchorPercent = dataZoom.start + fraction * currentRange;
// New range: proportional to current range for consistent zoom feel at any zoom level.
var deltaPercent = Mathf.Abs(delta / gridRange * currentRange);
var newRange = delta > 0 ? currentRange - deltaPercent : currentRange + deltaPercent;
// Place the new window so that anchorPercent stays under the mouse.
var start = anchorPercent - fraction * newRange;
var end = anchorPercent + (1f - fraction) * newRange;
// If the window goes out of [0,100], slide it to the boundary while keeping its size,
// so the zoom always feels consistent and the window never shrinks on one side only.
if (start < 0f)
else
{
end = Mathf.Min(100f, newRange);
start = 0f;
start = dataZoom.start - deltaPercent;
end = dataZoom.end + deltaPercent;
}
else if (end > 100f)
{
start = Mathf.Max(0f, 100f - newRange);
end = 100f;
}
UpdateDataZoomRange(dataZoom, start, end, grid);
}
@@ -500,11 +411,7 @@ namespace XCharts.Runtime
if(grid == null) grid = chart.GetGridOfDataZoom(dataZoom);
var range = dataZoom.orient == Orient.Horizonal ? grid.context.width : grid.context.height;
var minRange = dataZoom.minZoomRatio * range;
var newRange = end - start;
var currentRange = dataZoom.end - dataZoom.start;
// Only block when shrinking the range; always allow expansion so the chart
// cannot get permanently locked after a marquee selects a narrow region.
if (newRange < minRange / range * 100 && newRange <= currentRange)
if (end - start < minRange / range * 100)
{
return;
}
@@ -639,26 +546,13 @@ namespace XCharts.Runtime
Serie serie = chart.series[0];
Axis axis = chart.GetChartComponent<YAxis>(0);
var showData = serie.GetDataList(null);
float scaleWid = showData.Count > 1 ? dataZoom.context.width / (showData.Count - 1) : dataZoom.context.width;
float scaleWid = dataZoom.context.width / (showData.Count - 1);
Vector3 lp = Vector3.zero;
Vector3 np = Vector3.zero;
// shadow always shows the full data range, independent of DataZoom window
double minValue = SerieHelper.GetMinData(serie, 1, null, axis.inverse);
double maxValue = SerieHelper.GetMaxData(serie, 1, null, axis.inverse);
minValue = ChartHelper.GetMinDivisibleValue(minValue, 0);
maxValue = ChartHelper.GetMaxDivisibleValue(maxValue, 0);
double xMinValue = 0;
double xMaxValue = 0;
bool useXValueForShadow = false;
var xAxisIndex = dataZoom.xAxisIndexs.Count > 0 ? dataZoom.xAxisIndexs[0] : 0;
var xAxis = chart.GetChartComponent<XAxis>(xAxisIndex);
if (xAxis != null && (xAxis.IsValue() || xAxis.IsTime()))
{
xMinValue = SerieHelper.GetMinData(serie, 0, null, xAxis.inverse);
xMaxValue = SerieHelper.GetMaxData(serie, 0, null, xAxis.inverse);
AxisHelper.AdjustMinMaxValue(xAxis, ref xMinValue, ref xMaxValue, true);
useXValueForShadow = (xMaxValue - xMinValue) > 0;
}
double minValue = 0;
double maxValue = 0;
SeriesHelper.GetYMinMaxValue(chart, 0, axis.inverse, out minValue, out maxValue, false, false);
AxisHelper.AdjustMinMaxValue(axis, ref minValue, ref maxValue, true);
int rate = 1;
var sampleDist = serie.sampleDist < 2 ? 2 : serie.sampleDist;
@@ -674,40 +568,12 @@ namespace XCharts.Runtime
var animationDuration = serie.animation.GetChangeDuration();
var dataAddDuration = serie.animation.GetAdditionDuration();
var unscaledTime = serie.animation.unscaledTime;
var useCurrentData = false;
List<double> sampleSumPrefix = null;
if (serie.animation.enable)
{
useCurrentData = DataHelper.IsAnyDataChanged(ref showData, serie.minShow, maxCount);
dataChanging = useCurrentData;
}
if (!useCurrentData && rate > 1 &&
(serie.sampleType == SampleType.Sum || serie.sampleType == SampleType.Average))
{
if (_sampleSumPrefixCache == null || _sampleSumPrefixMaxCount != maxCount || _sampleSumPrefixInverse != axis.inverse)
{
_sampleSumPrefixCache = DataHelper.BuildSampleSumPrefix(ref showData, maxCount, axis.inverse);
_sampleSumPrefixMaxCount = maxCount;
_sampleSumPrefixInverse = axis.inverse;
}
sampleSumPrefix = _sampleSumPrefixCache;
}
for (int i = serie.minShow; i < maxCount; i += rate)
for (int i = 0; i < maxCount; i += rate)
{
double value = DataHelper.SampleValue(ref showData, serie.sampleType, rate, serie.minShow, maxCount, totalAverage, i,
dataAddDuration, animationDuration, ref dataChanging, axis, unscaledTime,
useCurrentData, false, sampleSumPrefix);
float pX;
if (useXValueForShadow && i < showData.Count && showData[i].data.Count > 0)
{
var xVal = (xAxis != null && xAxis.inverse) ? -showData[i].data[0] : showData[i].data[0];
pX = dataZoom.context.x + (float)((xVal - xMinValue) / (xMaxValue - xMinValue)) * dataZoom.context.width;
}
else
{
pX = dataZoom.context.x + i * scaleWid;
}
dataAddDuration, animationDuration, ref dataChanging, axis, unscaledTime);
float pX = dataZoom.context.x + i * scaleWid;
float dataHig = (float)((maxValue - minValue) == 0 ? 0 :
(value - minValue) / (maxValue - minValue) * dataZoom.context.height);
np = new Vector3(pX, chart.chartY + dataZoom.bottom + dataHig);
@@ -775,11 +641,11 @@ namespace XCharts.Runtime
float scaleWid = dataZoom.context.height / (showData.Count - 1);
Vector3 lp = Vector3.zero;
Vector3 np = Vector3.zero;
double minValue;
double maxValue;
double minValue = 0;
double maxValue = 0;
SeriesHelper.GetYMinMaxValue(chart, 0, axis.inverse, out minValue, out maxValue);
minValue = ChartHelper.GetMinDivisibleValue(minValue, 0);
maxValue = ChartHelper.GetMaxDivisibleValue(maxValue, 0);
AxisHelper.AdjustMinMaxValue(axis, ref minValue, ref maxValue, true);
int rate = 1;
var sampleDist = serie.sampleDist < 2 ? 2 : serie.sampleDist;
var maxCount = showData.Count;
@@ -794,30 +660,11 @@ namespace XCharts.Runtime
var animationDuration = serie.animation.GetChangeDuration();
var dataAddDuration = serie.animation.GetAdditionDuration();
var unscaledTime = serie.animation.unscaledTime;
var useCurrentData = false;
List<double> sampleSumPrefix = null;
if (serie.animation.enable)
{
useCurrentData = DataHelper.IsAnyDataChanged(ref showData, serie.minShow, maxCount);
dataChanging = useCurrentData;
}
if (!useCurrentData && rate > 1 &&
(serie.sampleType == SampleType.Sum || serie.sampleType == SampleType.Average))
{
if (_sampleSumPrefixCache == null || _sampleSumPrefixMaxCount != maxCount || _sampleSumPrefixInverse != axis.inverse)
{
_sampleSumPrefixCache = DataHelper.BuildSampleSumPrefix(ref showData, maxCount, axis.inverse);
_sampleSumPrefixMaxCount = maxCount;
_sampleSumPrefixInverse = axis.inverse;
}
sampleSumPrefix = _sampleSumPrefixCache;
}
for (int i = serie.minShow; i < maxCount; i += rate)
for (int i = 0; i < maxCount; i += rate)
{
double value = DataHelper.SampleValue(ref showData, serie.sampleType, rate, serie.minShow, maxCount, totalAverage, i,
dataAddDuration, animationDuration, ref dataChanging, axis, unscaledTime,
useCurrentData, false, sampleSumPrefix);
dataAddDuration, animationDuration, ref dataChanging, axis, unscaledTime);
float pY = dataZoom.context.y + i * scaleWid;
float dataHig = (maxValue - minValue) == 0 ? 0 :
(float)((value - minValue) / (maxValue - minValue) * dataZoom.context.width);
@@ -859,7 +706,7 @@ namespace XCharts.Runtime
private void DrawMarquee(VertexHelper vh, DataZoom dataZoom)
{
if (!dataZoom.enable || !dataZoom.supportMarquee || !dataZoom.context.isMarqueeDrag)
if (!dataZoom.enable || !dataZoom.supportMarquee)
return;
var areaColor = dataZoom.marqueeStyle.areaStyle.GetColor(chart.theme.dataZoom.dataAreaColor);
UGL.DrawRectangle(vh, dataZoom.context.marqueeRect, areaColor);

View File

@@ -46,7 +46,7 @@ namespace XCharts.Runtime
{
double serieMinValue = 0;
double serieMaxValue = 0;
SerieHelper.GetMinMaxData(serie, 0, out serieMinValue, out serieMaxValue);
SerieHelper.GetMinMaxData(serie, out serieMinValue, out serieMaxValue, null, 2);
if (serieMinValue < min)
min = serieMinValue;
if (serieMaxValue > max)

View File

@@ -68,50 +68,6 @@ namespace XCharts.Runtime
/// </summary>
End
}
/// <summary>
/// The value-based condition for showing label. Controls visibility based on threshold comparison.
/// ||标签基于值的显示条件,通过与阈值比较来控制标签的显示。
/// </summary>
public enum ShowCondition
{
/// <summary>
/// Always show label.
/// ||总是显示标签。
/// </summary>
Always,
/// <summary>
/// Show label when value is greater than showThreshold.
/// ||大于showThreshold才显示标签。
/// </summary>
GreaterThan,
/// <summary>
/// Show label when value is less than showThreshold.
/// ||小于showThreshold才显示标签。
/// </summary>
LessThan,
}
/// <summary>
/// The data-pattern-based filter for showing label. Controls visibility based on data topology.
/// ||标签基于数据形态的显示筛选,通过数据的拓扑特征(波峰/波谷)来控制标签的显示。
/// </summary>
public enum ShowFilter
{
/// <summary>
/// All data points show label.
/// ||所有数据点都显示标签。
/// </summary>
All,
/// <summary>
/// Show label when value is at a peak.
/// ||波峰才显示标签。
/// </summary>
Peak,
/// <summary>
/// Show label when value is at a valley.
/// ||波谷才显示标签。
/// </summary>
Valley
}
[SerializeField] protected bool m_Show = true;
[SerializeField] Position m_Position = Position.Default;
@@ -126,10 +82,6 @@ namespace XCharts.Runtime
[SerializeField] protected float m_Height = 0;
[SerializeField][Since("v3.15.0")] protected float m_FixedX = 0;
[SerializeField][Since("v3.15.0")] protected float m_FixedY = 0;
[SerializeField][Since("v3.16.0")] protected ShowCondition m_ShowCondition = ShowCondition.Always;
[SerializeField][Since("v3.16.0")] protected ShowFilter m_ShowFilter = ShowFilter.All;
[SerializeField][Since("v3.16.0")] protected double m_ShowThreshold = 0;
[SerializeField][Since("v3.16.0")] protected float m_ShowMinGap = 0;
[SerializeField] protected IconStyle m_Icon = new IconStyle();
[SerializeField] protected ImageStyle m_Background = new ImageStyle();
@@ -148,9 +100,6 @@ namespace XCharts.Runtime
m_Height = 0;
m_NumericFormatter = "";
m_AutoOffset = false;
m_ShowCondition = ShowCondition.Always;
m_ShowFilter = ShowFilter.All;
m_ShowThreshold = 0;
}
/// <summary>
@@ -359,47 +308,6 @@ namespace XCharts.Runtime
set { if (PropertyUtil.SetStruct(ref m_FixedY, value)) SetComponentDirty(); }
}
/// <summary>
/// The value-based show condition of label. Default is ShowCondition.Always.
/// When set to GreaterThan or LessThan, the label is shown only when the value satisfies the threshold.
/// ||标签基于值的显示条件。默认为ShowCondition.Always总是显示。
/// 设为GreaterThan或LessThan时只有满足showThreshold阈值条件的数据点才显示标签。
/// </summary>
public ShowCondition showCondition
{
get { return m_ShowCondition; }
set { if (PropertyUtil.SetStruct(ref m_ShowCondition, value)) SetComponentDirty(); }
}
/// <summary>
/// The data-pattern-based show filter of label. Default is ShowFilter.All.
/// When set to Peak or Valley, the label is shown only at local maximum or minimum data points.
/// ||标签基于数据形态的显示筛选。默认为ShowFilter.All所有数据点都显示。
/// 设为Peak或Valley时只在局部波峰或波谷的数据点显示标签。
/// </summary>
public ShowFilter showFilter
{
get { return m_ShowFilter; }
set { if (PropertyUtil.SetStruct(ref m_ShowFilter, value)) SetComponentDirty(); }
}
/// <summary>
/// The threshold for showCondition. When showCondition is GreaterThan or LessThan, only values that satisfy the comparison will show label. Default is 0.
/// ||showCondition的阈值。当showCondition为GreaterThan或LessThan时生效只有满足比较条件的值才显示标签。默认值为0。
/// </summary>
public double showThreshold
{
get { return m_ShowThreshold; }
set { if (PropertyUtil.SetStruct(ref m_ShowThreshold, value)) SetComponentDirty(); }
}
/// <summary>
/// the gap between label and the previous label. When the distance to the previous label is less than this value,
/// the label with smaller y value will be hidden. Default is 0, which means this function is turned off.
/// 和上一个标签的最小间距。当和上一个标签的距离小于该值时隐藏对应y值较小的标签。默认为0不开启该功能。
/// </summary>
public float showMinGap
{
get { return m_ShowMinGap; }
set { if (PropertyUtil.SetStruct(ref m_ShowMinGap, value)) SetComponentDirty(); }
}
/// <summary>
/// the sytle of background.
/// ||背景图样式。
/// </summary>

View File

@@ -80,8 +80,6 @@ namespace XCharts.Runtime
[SerializeField] private bool m_ItemAutoColor = true;
[SerializeField] private float m_ItemOpacity = 1;
[SerializeField][Since("v3.15.0")] private float m_ItemInactiveOpacity = 1;
[SerializeField][Since("v3.16.0")] private float m_Width = 0;
[SerializeField][Since("v3.16.0")] private float m_Height = 0;
[SerializeField] private string m_Formatter;
[SerializeField] private LabelStyle m_LabelStyle = new LabelStyle();
[SerializeField][Since("v3.10.0")] private TextLimit m_TextLimit = new TextLimit();
@@ -204,25 +202,6 @@ namespace XCharts.Runtime
set { if (PropertyUtil.SetClass(ref m_Formatter, value)) SetComponentDirty(); }
}
/// <summary>
/// the width of legend component. Default is 0 for auto adapt. When set a value between 0 and 1, it means the percentage relative to chart width and height.
/// ||图例组件的宽。默认为0自适应。当设置0-1的值时表示相对于图表宽高的比例。
/// </summary>
public float width
{
get { return m_Width; }
set { if (PropertyUtil.SetStruct(ref m_Width, value)) SetComponentDirty(); }
}
/// <summary>
/// the height of legend component. Default is 0 for auto adapt. When set a value between 0 and 1, it means the percentage relative to chart width and height.
/// ||图例组件的高。默认为0自适应。当设置0-1的值时表示相对于图表宽高的比例。
/// </summary>
/// <value></value>
public float height
{
get { return m_Height; }
set { if (PropertyUtil.SetStruct(ref m_Height, value)) SetComponentDirty(); }
}
/// <summary>
/// the style of text.
/// ||文本样式。
/// </summary>

View File

@@ -93,23 +93,10 @@ namespace XCharts.Runtime
var startY = 0f;
var legendMaxWidth = chartWidth - legend.location.runtimeLeft - legend.location.runtimeRight;
var legendMaxHeight = chartHeight - legend.location.runtimeTop - legend.location.runtimeBottom;
var isVertical = legend.orient == Orient.Vertical;
var fixedWidth = legend.width <= 0 ? 0
: legend.width < 1 ? chartWidth * legend.width
: legend.width;
var fixedHeight = legend.height <= 0 ? 0
: legend.height < 1 ? chartHeight * legend.height
: legend.height;
// Horizonal: width constrains layout wrapping; Vertical: height constrains layout wrapping.
// The other axis only affects the background size, not the layout.
if (!isVertical && fixedWidth > 0) legendMaxWidth = fixedWidth;
if (isVertical && fixedHeight > 0) legendMaxHeight = fixedHeight;
UpdateLegendWidthAndHeight(legend, legendMaxWidth, legendMaxHeight);
// Override context size for fixed dimensions (controls background rect size).
if (fixedWidth > 0) legend.context.width = fixedWidth;
if (fixedHeight > 0) legend.context.height = fixedHeight;
var legendRuntimeWidth = legend.context.width;
var legendRuntimeHeight = legend.context.height;
var isVertical = legend.orient == Orient.Vertical;
switch (legend.location.align)
{
case Location.Align.TopCenter:
@@ -211,14 +198,11 @@ namespace XCharts.Runtime
legend.context.eachHeight = 0;
if (legend.orient == Orient.Horizonal)
{
var maxRowWidth = 0f;
foreach (var kv in legend.context.buttonList)
{
if (width + kv.Value.width > maxWidth)
{
realWidth = width - legend.itemGap;
if (realWidth > maxRowWidth)
maxRowWidth = realWidth;
realHeight += height + legend.itemGap;
if (legend.context.eachHeight < height + legend.itemGap)
{
@@ -232,10 +216,8 @@ namespace XCharts.Runtime
height = kv.Value.height;
}
width -= legend.itemGap;
if (width > maxRowWidth)
maxRowWidth = width;
legend.context.height = realHeight + height;
legend.context.width = maxRowWidth;
legend.context.width = realWidth > 0 ? realWidth : width;
}
else
{

View File

@@ -155,7 +155,7 @@ namespace XCharts.Runtime
[SerializeField]
private List<LabelStyle> m_ContentLabelStyles = new List<LabelStyle>()
{
new LabelStyle() { textPadding = new TextPadding(0, 5, 0, 0), textStyle = new TextStyle() { alignment = TextAnchor.MiddleLeft } },
new LabelStyle() { textPadding = new TextPadding(0, 5, 0, 0), textStyle = new TextStyle() { alignment = TextAnchor.MiddleCenter } },
new LabelStyle() { textPadding = new TextPadding(0, 20, 0, 0), textStyle = new TextStyle() { alignment = TextAnchor.MiddleLeft } },
new LabelStyle() { textPadding = new TextPadding(0, 0, 0, 0), textStyle = new TextStyle() { alignment = TextAnchor.MiddleRight } }
};

View File

@@ -11,8 +11,6 @@ namespace XCharts.Runtime
internal sealed class TooltipHandler : MainComponentHandler<Tooltip>
{
private Dictionary<string, ChartLabel> m_IndicatorLabels = new Dictionary<string, ChartLabel>();
private Dictionary<Serie, Dictionary<int, List<SerieData>>> m_SortedAxisDataCache =
new Dictionary<Serie, Dictionary<int, List<SerieData>>>();
private GameObject m_LabelRoot;
private ISerieContainer m_PointerContainer;
@@ -427,10 +425,8 @@ namespace XCharts.Runtime
private void GetSerieDataByXYAxis(Serie serie, Axis xAxis, Axis yAxis)
{
var xPointerInternal = xAxis.inverse ? -xAxis.context.pointerValue : xAxis.context.pointerValue;
var yPointerInternal = yAxis.inverse ? -yAxis.context.pointerValue : yAxis.context.pointerValue;
var xAxisIndex = AxisHelper.GetAxisValueSplitIndex(xAxis, xPointerInternal, false);
var yAxisIndex = AxisHelper.GetAxisValueSplitIndex(yAxis, yPointerInternal, false);
var xAxisIndex = AxisHelper.GetAxisValueSplitIndex(xAxis, xAxis.context.pointerValue, false);
var yAxisIndex = AxisHelper.GetAxisValueSplitIndex(yAxis, yAxis.context.pointerValue, false);
serie.context.pointerItemDataIndex = -1;
if (serie is Heatmap)
{
@@ -443,10 +439,8 @@ namespace XCharts.Runtime
}
foreach (var serieData in serie.data)
{
var xData = xAxis.inverse ? -serieData.GetData(0) : serieData.GetData(0);
var yData = yAxis.inverse ? -serieData.GetData(1) : serieData.GetData(1);
var x = AxisHelper.GetAxisValueSplitIndex(xAxis, xData, true);
var y = AxisHelper.GetAxisValueSplitIndex(yAxis, yData, true);
var x = AxisHelper.GetAxisValueSplitIndex(xAxis, serieData.GetData(0), true);
var y = AxisHelper.GetAxisValueSplitIndex(yAxis, serieData.GetData(1), true);
if (xAxisIndex == x && y == yAxisIndex)
{
serie.context.pointerItemDataIndex = serieData.index;
@@ -456,118 +450,30 @@ namespace XCharts.Runtime
}
private void GetSerieDataIndexByAxis(Serie serie, Axis axis, GridCoord grid, int dimension = 0)
{
var axisValue = axis.context.pointerValue;
serie.context.pointerAxisDataIndexs.Clear();
if (axis.IsTime())
{
FindSerieDataIndexByAxisLinear(serie, axis, axisValue, dimension);
}
else
{
var sortedData = GetSortedAxisData(serie, dimension);
var nearestIndex = GetNearestSerieDataIndex(sortedData, axisValue, dimension, axis.context.tickValue);
if (nearestIndex >= 0)
serie.context.pointerAxisDataIndexs.Add(nearestIndex);
}
if (serie.context.pointerAxisDataIndexs.Count > 0)
{
var index = serie.context.pointerAxisDataIndexs[0];
serie.context.pointerItemDataIndex = index;
var dataValue = serie.GetSerieData(index).GetData(dimension);
axis.context.axisTooltipValue = axis.inverse ? -dataValue : dataValue;
}
else
{
serie.context.pointerItemDataIndex = -1;
axis.context.axisTooltipValue = 0;
}
}
private List<SerieData> GetSortedAxisData(Serie serie, int dimension)
{
Dictionary<int, List<SerieData>> dimensionCache;
if (!m_SortedAxisDataCache.TryGetValue(serie, out dimensionCache))
{
dimensionCache = new Dictionary<int, List<SerieData>>();
m_SortedAxisDataCache[serie] = dimensionCache;
}
List<SerieData> sortedData;
if (!dimensionCache.TryGetValue(dimension, out sortedData))
{
sortedData = new List<SerieData>();
dimensionCache[dimension] = sortedData;
}
if (serie.dataDirty || sortedData.Count != serie.dataCount)
{
sortedData.Clear();
for (int i = 0; i < serie.dataCount; i++)
{
sortedData.Add(serie.data[i]);
}
sortedData.Sort(delegate (SerieData a, SerieData b)
{
return a.GetData(dimension).CompareTo(b.GetData(dimension));
});
}
return sortedData;
}
private int GetNearestSerieDataIndex(List<SerieData> sortedData, double axisValue, int dimension, double tickValue)
{
var dataCount = sortedData.Count;
if (dataCount <= 0) return -1;
if (dataCount == 1)
{
var currValue = sortedData[0].GetData(dimension);
var diff = tickValue * 0.5f;
return axisValue >= currValue - diff && axisValue <= currValue + diff
? sortedData[0].index
: -1;
}
var firstValue = sortedData[0].GetData(dimension);
var secondValue = sortedData[1].GetData(dimension);
if (axisValue <= firstValue + (secondValue - firstValue) / 2)
return sortedData[0].index;
var lastValue = sortedData[dataCount - 1].GetData(dimension);
var beforeLastValue = sortedData[dataCount - 2].GetData(dimension);
if (axisValue > beforeLastValue + (lastValue - beforeLastValue) / 2)
return sortedData[dataCount - 1].index;
var low = 1;
var high = dataCount - 2;
while (low <= high)
{
var mid = (low + high) / 2;
var prevValue = sortedData[mid - 1].GetData(dimension);
var currValue = sortedData[mid].GetData(dimension);
var nextValue = sortedData[mid + 1].GetData(dimension);
var leftBound = currValue - (currValue - prevValue) / 2;
var rightBound = currValue + (nextValue - currValue) / 2;
if (axisValue > leftBound && axisValue <= rightBound)
return sortedData[mid].index;
if (axisValue <= leftBound)
high = mid - 1;
else
low = mid + 1;
}
return -1;
}
private void FindSerieDataIndexByAxisLinear(Serie serie, Axis axis, double axisValue, int dimension)
{
var currValue = 0d;
var lastValue = 0d;
var nextValue = 0d;
var axisValue = axis.context.pointerValue;
var isTimeAxis = axis.IsTime();
var dataCount = serie.dataCount;
var themeSymbolSize = chart.theme.serie.scatterSymbolSize;
var data = serie.data;
if (!isTimeAxis)// || serie.useSortData)
{
serie.context.sortedData.Clear();
for (int i = 0; i < dataCount; i++)
{
var serieData = serie.data[i];
serie.context.sortedData.Add(serieData);
}
serie.context.sortedData.Sort(delegate (SerieData a, SerieData b)
{
return a.GetData(dimension).CompareTo(b.GetData(dimension));
});
data = serie.context.sortedData;
}
serie.context.pointerAxisDataIndexs.Clear();
for (int i = 0; i < dataCount; i++)
{
var serieData = data[i];
@@ -612,18 +518,28 @@ namespace XCharts.Runtime
}
lastValue = currValue;
}
if (serie.context.pointerAxisDataIndexs.Count > 0)
{
var index = serie.context.pointerAxisDataIndexs[0];
serie.context.pointerItemDataIndex = index;
axis.context.axisTooltipValue = serie.GetSerieData(index).GetData(dimension);
}
else
{
serie.context.pointerItemDataIndex = -1;
axis.context.axisTooltipValue = 0;
}
}
private void GetSerieDataIndexByItem(Serie serie, Axis axis, GridCoord grid, int dimension = 0)
{
if (serie.context.pointerItemDataIndex >= 0)
{
var dataValue = serie.GetSerieData(serie.context.pointerItemDataIndex).GetData(dimension);
axis.context.axisTooltipValue = axis.inverse ? -dataValue : dataValue;
axis.context.axisTooltipValue = serie.GetSerieData(serie.context.pointerItemDataIndex).GetData(dimension);
}
else if (component.type == Tooltip.Type.Cross)
{
axis.context.axisTooltipValue = axis.inverse ? -axis.context.pointerValue : axis.context.pointerValue;
axis.context.axisTooltipValue = axis.context.pointerValue;
}
else
{

View File

@@ -260,7 +260,6 @@ namespace XCharts.Runtime
view.layout.childControlWidth = false;
view.layout.childForceExpandHeight = false;
view.layout.childForceExpandWidth = false;
view.layout.childAlignment = tooltip.titleLabelStyle.textStyle.alignment;
view.layout.padding = new RectOffset(tooltip.paddingLeftRight,
tooltip.paddingLeftRight,
tooltip.paddingTopBottom,

View File

@@ -25,13 +25,6 @@ namespace XCharts.Runtime
return !string.IsNullOrEmpty(content) && content.IndexOf('{') >= 0;
}
public static bool NeedTotalContent(string content)
{
if (string.IsNullOrEmpty(content)) return false;
return content.IndexOf("{d", System.StringComparison.OrdinalIgnoreCase) >= 0 ||
content.IndexOf("{f", System.StringComparison.OrdinalIgnoreCase) >= 0;
}
/// <summary>
/// 替换字符串中的通配符,支持的通配符有{.}、{a}、{b}、{c}、{d}、{e}、{f}、{g}、{h}、{y}。
/// </summary>

View File

@@ -778,27 +778,5 @@ namespace XCharts.Runtime
foreach (var component in m_Components) component.ResetStatus();
foreach (var handler in m_SerieHandlers) handler.ForceUpdateSerieContext();
}
/// <summary>
/// Export chart configuration and data to JSON string.
/// ||导出图表配置和数据为JSON字符串。
/// </summary>
[Since("v3.16.0")]
public string ExportToJson(bool prettyPrint = true)
{
return XCharts.Runtime.ChartJsonSerializer.Serialize(this, prettyPrint);
}
/// <summary>
/// Import JSON and update current chart configuration.
/// ||导入JSON并更新当前图表配置。
/// </summary>
[Since("v3.16.0")]
public void ImportFromJson(string json)
{
XCharts.Runtime.ChartJsonDeserializer.Deserialize(json, this);
RefreshAllComponent();
RefreshChart();
}
}
}

View File

@@ -56,15 +56,15 @@ namespace XCharts.Runtime
}
if (isX)
{
SeriesHelper.GetXMinMaxValue(this, axisIndex, axis.inverse, out tempMinValue, out tempMaxValue, false, needAnimationData);
SeriesHelper.GetXMinMaxValue(this, axisIndex, axis.inverse, out tempMinValue, out tempMaxValue, false, false, needAnimationData);
}
else if (isY)
{
SeriesHelper.GetYMinMaxValue(this, axisIndex, axis.inverse, out tempMinValue, out tempMaxValue, false, needAnimationData);
SeriesHelper.GetYMinMaxValue(this, axisIndex, axis.inverse, out tempMinValue, out tempMaxValue, false, false, needAnimationData);
}
else if(isZ)
{
SeriesHelper.GetZMinMaxValue(this, axisIndex, axis.inverse, out tempMinValue, out tempMaxValue, false, needAnimationData);
SeriesHelper.GetZMinMaxValue(this, axisIndex, axis.inverse, out tempMinValue, out tempMaxValue, false, false, needAnimationData);
}
AxisHelper.AdjustMinMaxValue(axis, ref tempMinValue, ref tempMaxValue, true);
}

View File

@@ -154,25 +154,5 @@ namespace XCharts.Runtime
}
protected virtual void OnThemeChanged() { }
/// <summary>
/// Export UI component configuration to JSON string.
/// </summary>
[Since("v3.16.0")]
public string ExportToJson(bool prettyPrint = true)
{
return UIComponentJsonSerializer.Serialize(this, prettyPrint);
}
/// <summary>
/// Import JSON and update current UI component configuration.
/// </summary>
[Since("v3.16.0")]
public void ImportFromJson(string json)
{
UIComponentJsonDeserializer.Deserialize(json, this);
RefreshAllComponent();
RefreshGraph();
}
}
}

View File

@@ -1290,7 +1290,8 @@ namespace XCharts.Runtime
return null;
var clampedExportScale = Mathf.Max(1f, exportScale);
var outputScaleFactor = clampedExportScale;
var scaleFactor = canvas.scaleFactor <= 0 ? 1f : canvas.scaleFactor;
var outputScaleFactor = scaleFactor * clampedExportScale;
var width = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.width * outputScaleFactor));
var height = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.height * outputScaleFactor));
var chart = rectTransform.GetComponent<BaseChart>();
@@ -1403,8 +1404,8 @@ namespace XCharts.Runtime
// so the saved image has original width/height but higher quality.
if (clampedExportScale > 1f)
{
var targetWidth = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.width));
var targetHeight = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.height));
var targetWidth = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.width * scaleFactor));
var targetHeight = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.height * scaleFactor));
var smallRT = RenderTexture.GetTemporary(targetWidth, targetHeight, 0, rt.format);
Graphics.Blit(rt, smallRT);
@@ -1415,7 +1416,7 @@ namespace XCharts.Runtime
tex.Apply();
RenderTexture.ReleaseTemporary(smallRT);
var cornerRadiiFinal = GetChartCornerRadius(chart, rectTransform.rect.width, rectTransform.rect.height, 1f);
var cornerRadiiFinal = GetChartCornerRadius(chart, rectTransform.rect.width, rectTransform.rect.height, scaleFactor);
ApplyRoundedCornerClip(tex, cornerRadiiFinal);
}
else

View File

@@ -5,36 +5,6 @@ namespace XCharts.Runtime
{
public static class DataHelper
{
private static List<double> s_SampleSumPrefix = new List<double>();
public static bool IsAnyDataChanged(ref List<SerieData> showData, int minCount, int maxCount)
{
for (int i = minCount; i < maxCount; i++)
{
if (showData[i].IsDataChanged())
return true;
}
return false;
}
public static List<double> BuildSampleSumPrefix(ref List<SerieData> showData, int maxCount, bool inverse)
{
if (maxCount < 0) maxCount = 0;
var targetCount = maxCount + 1;
if (s_SampleSumPrefix.Count != targetCount)
{
s_SampleSumPrefix.Clear();
for (int i = 0; i < targetCount; i++)
s_SampleSumPrefix.Add(0);
}
s_SampleSumPrefix[0] = 0;
for (int i = 0; i < maxCount; i++)
{
s_SampleSumPrefix[i + 1] = s_SampleSumPrefix[i] + showData[i].GetData(1, inverse);
}
return s_SampleSumPrefix;
}
public static double DataAverage(ref List<SerieData> showData, SampleType sampleType,
int minCount, int maxCount, int rate)
{
@@ -53,84 +23,14 @@ namespace XCharts.Runtime
public static double SampleValue(ref List<SerieData> showData, SampleType sampleType, int rate,
int minCount, int maxCount, double totalAverage, int index, float dataAddDuration, float dataChangeDuration,
ref bool dataChanging, Axis axis, bool unscaledTime, bool useCurrentData = true,
bool checkDataChanging = true, List<double> sampleSumPrefix = null)
ref bool dataChanging, Axis axis, bool unscaledTime)
{
var inverse = axis.inverse;
var minValue = 0;
var maxValue = 0;
if (!useCurrentData)
{
if (rate <= 1 || index == minCount)
return showData[index].GetData(1, inverse);
switch (sampleType)
{
case SampleType.Sum:
case SampleType.Average:
if (sampleSumPrefix != null)
{
var totalByPrefix = sampleSumPrefix[index + 1] - sampleSumPrefix[index - rate + 1];
if (sampleType == SampleType.Average)
return totalByPrefix / rate;
else
return totalByPrefix;
}
double total = 0;
for (int i = index; i > index - rate; i--)
{
total += showData[i].GetData(1, inverse);
}
if (sampleType == SampleType.Average)
return total / rate;
else
return total;
case SampleType.Max:
double max = double.MinValue;
for (int i = index; i > index - rate; i--)
{
var value = showData[i].GetData(1, inverse);
if (value > max)
max = value;
}
return max;
case SampleType.Min:
double min = double.MaxValue;
for (int i = index; i > index - rate; i--)
{
var value = showData[i].GetData(1, inverse);
if (value < min)
min = value;
}
return min;
case SampleType.Peak:
max = double.MinValue;
min = double.MaxValue;
total = 0;
for (int i = index; i > index - rate; i--)
{
var value = showData[i].GetData(1, inverse);
total += value;
if (value < min)
min = value;
if (value > max)
max = value;
}
var average = total / rate;
if (average >= totalAverage)
return max;
else
return min;
}
return showData[index].GetData(1, inverse);
}
if (rate <= 1 || index == minCount)
{
if (checkDataChanging && showData[index].IsDataChanged())
if (showData[index].IsDataChanged())
dataChanging = true;
return showData[index].GetCurrData(1, dataAddDuration, dataChangeDuration, inverse, minValue, maxValue, unscaledTime);
@@ -145,7 +45,7 @@ namespace XCharts.Runtime
{
count++;
total += showData[i].GetCurrData(1, dataAddDuration, dataChangeDuration, inverse, minValue, maxValue, unscaledTime);
if (checkDataChanging && showData[i].IsDataChanged())
if (showData[i].IsDataChanged())
dataChanging = true;
}
if (sampleType == SampleType.Average)
@@ -161,7 +61,7 @@ namespace XCharts.Runtime
if (value > max)
max = value;
if (checkDataChanging && showData[i].IsDataChanged())
if (showData[i].IsDataChanged())
dataChanging = true;
}
return max;
@@ -174,7 +74,7 @@ namespace XCharts.Runtime
if (value < min)
min = value;
if (checkDataChanging && showData[i].IsDataChanged())
if (showData[i].IsDataChanged())
dataChanging = true;
}
return min;
@@ -192,7 +92,7 @@ namespace XCharts.Runtime
if (value > max)
max = value;
if (checkDataChanging && showData[i].IsDataChanged())
if (showData[i].IsDataChanged())
dataChanging = true;
}
var average = total / rate;
@@ -201,7 +101,7 @@ namespace XCharts.Runtime
else
return min;
}
if (checkDataChanging && showData[index].IsDataChanged())
if (showData[index].IsDataChanged())
dataChanging = true;
return showData[index].GetCurrData(1, dataAddDuration, dataChangeDuration, inverse, minValue, maxValue, unscaledTime);

View File

@@ -217,8 +217,7 @@ namespace XCharts.Runtime
var close = serieData.GetCurrData(startDataIndex + 1, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
var lowest = serieData.GetCurrData(startDataIndex + 2, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
var heighest = serieData.GetCurrData(startDataIndex + 3, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
var isBodyRise = yAxis.inverse ? close <= open : close >= open;
var isColorRise = IsColorRise(showData, i, startDataIndex, open, close);
var isRise = yAxis.inverse ? close < open : close > open;
var borderWidth = open == 0 ? 0f :
(itemStyle.borderWidth == 0 ? theme.serie.candlestickBorderWidth :
itemStyle.borderWidth);
@@ -240,7 +239,7 @@ namespace XCharts.Runtime
Vector3 plb, plt, prt, prb, top;
var offset = 2 * borderWidth;
if (isBodyRise)
if (isRise)
{
plb = new Vector3(pX + gap + offset, pY + offset);
plt = new Vector3(pX + gap + offset, pY + currHig - offset);
@@ -266,10 +265,10 @@ namespace XCharts.Runtime
}
serie.context.dataPoints.Add(top);
serie.context.dataIndexs.Add(serieData.index);
var areaColor = isColorRise ?
var areaColor = isRise ?
itemStyle.GetColor(theme.serie.candlestickColor) :
itemStyle.GetColor0(theme.serie.candlestickColor0);
var borderColor = isColorRise ?
var borderColor = isRise ?
itemStyle.GetBorderColor(theme.serie.candlestickBorderColor) :
itemStyle.GetBorderColor0(theme.serie.candlestickBorderColor0);
var itemWidth = Mathf.Abs(prt.x - plb.x);
@@ -313,7 +312,7 @@ namespace XCharts.Runtime
{
UGL.DrawLine(vh, openCenterPos, closeCenterPos, Mathf.Max(borderWidth, barWidth / 2), borderColor);
}
if (isBodyRise)
if (isRise)
{
UGL.DrawLine(vh, openCenterPos, lowPos, borderWidth, borderColor);
UGL.DrawLine(vh, closeCenterPos, heighPos, borderWidth, borderColor);
@@ -334,17 +333,5 @@ namespace XCharts.Runtime
chart.RefreshPainter(serie);
}
}
private static bool IsColorRise(List<SerieData> showData, int dataIndex, int startDataIndex, double open, double close)
{
if (dataIndex > 0)
{
var prevSerieData = showData[dataIndex - 1];
var prevStartDataIndex = prevSerieData.data.Count > 4 ? 1 : 0;
var prevClose = prevSerieData.GetData(prevStartDataIndex + 1);
return close >= prevClose;
}
return close >= open;
}
}
}

View File

@@ -151,8 +151,7 @@ namespace XCharts.Runtime
var close = serieData.GetCurrData(startDataIndex + 1, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
var lowest = serieData.GetCurrData(startDataIndex + 2, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
var heighest = serieData.GetCurrData(startDataIndex + 3, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
var isBodyRise = yAxis.inverse ? close <= open : close >= open;
var isColorRise = IsColorRise(showData, i, startDataIndex, open, close);
var isRise = yAxis.inverse ? close<open : close> open;
var borderWidth = open == 0 ? 0f :
(itemStyle.borderWidth == 0 ? theme.serie.candlestickBorderWidth :
itemStyle.borderWidth);
@@ -174,7 +173,7 @@ namespace XCharts.Runtime
Vector3 plb, plt, prt, prb, top;
var offset = 2 * borderWidth;
if (isBodyRise)
if (isRise)
{
plb = new Vector3(pX + gap + offset, pY + offset);
plt = new Vector3(pX + gap + offset, pY + currHig - offset);
@@ -200,10 +199,10 @@ namespace XCharts.Runtime
// }
serie.context.dataPoints.Add(top);
serie.context.dataIndexs.Add(serieData.index);
var areaColor = isColorRise ?
var areaColor = isRise ?
itemStyle.GetColor(theme.serie.candlestickColor) :
itemStyle.GetColor0(theme.serie.candlestickColor0);
var borderColor = isColorRise ?
var borderColor = isRise ?
itemStyle.GetBorderColor(theme.serie.candlestickBorderColor) :
itemStyle.GetBorderColor0(theme.serie.candlestickBorderColor0);
var itemWidth = Mathf.Abs(prt.x - plb.x);
@@ -236,7 +235,7 @@ namespace XCharts.Runtime
UGL.DrawBorder(vh, center, itemWidth, itemHeight, 2 * borderWidth, borderColor, 0,
itemStyle.cornerRadius, isYAxis, 0.5f);
}
if (isBodyRise)
if (isRise)
{
UGL.DrawLine(vh, openCenterPos, lowPos, borderWidth, borderColor);
UGL.DrawLine(vh, closeCenterPos, heighPos, borderWidth, borderColor);
@@ -262,17 +261,5 @@ namespace XCharts.Runtime
chart.RefreshPainter(serie);
}
}
private static bool IsColorRise(List<SerieData> showData, int dataIndex, int startDataIndex, double open, double close)
{
if (dataIndex > 0)
{
var prevSerieData = showData[dataIndex - 1];
var prevStartDataIndex = prevSerieData.data.Count > 4 ? 1 : 0;
var prevClose = prevSerieData.GetData(prevStartDataIndex + 1);
return close >= prevClose;
}
return close >= open;
}
}
}

View File

@@ -56,7 +56,6 @@ namespace XCharts.Runtime
m_LastCheckContextFlag = needCheck;
var lineWidth = serie.lineStyle.GetWidth(chart.theme.serie.lineWidth);
var themeSymbolSize = chart.theme.serie.lineSymbolSize;
var symbolVisible = serie.symbol != null && serie.symbol.show && serie.symbol.type != SymbolType.None;
var needInteract = false;
serie.ResetDataIndex();
if (m_LegendEnter)
@@ -66,12 +65,9 @@ namespace XCharts.Runtime
for (int i = 0; i < serie.dataCount; i++)
{
var serieData = serie.data[i];
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, SerieState.Emphasis);
serieData.context.highlight = true;
if (symbolVisible)
{
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, SerieState.Emphasis);
serieData.interact.SetValue(ref needInteract, size);
}
serieData.interact.SetValue(ref needInteract, size);
}
}
else if (serie.context.isTriggerByAxis)
@@ -83,12 +79,9 @@ namespace XCharts.Runtime
var serieData = serie.data[i];
var highlight = i == serie.context.pointerItemDataIndex;
serieData.context.highlight = highlight;
if (symbolVisible)
{
var state = SerieHelper.GetSerieState(serie, serieData, true);
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, state);
serieData.interact.SetValue(ref needInteract, size);
}
var state = SerieHelper.GetSerieState(serie, serieData, true);
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, state);
serieData.interact.SetValue(ref needInteract, size);
if (highlight)
{
serie.context.pointerEnter = true;
@@ -105,23 +98,13 @@ namespace XCharts.Runtime
for (int i = 0; i < serie.dataCount; i++)
{
var serieData = serie.data[i];
var pointerOffset = (Vector2)chart.pointerPos - (Vector2)serieData.context.position;
bool highlight;
if (symbolVisible)
{
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize);
var radius = size * 2.5f;
highlight = pointerOffset.sqrMagnitude <= radius * radius;
var state = SerieHelper.GetSerieState(serie, serieData, true);
size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, state);
serieData.interact.SetValue(ref needInteract, size);
}
else
{
var radius = themeSymbolSize * 2.5f;
highlight = pointerOffset.sqrMagnitude <= radius * radius;
}
var dist = Vector3.Distance(chart.pointerPos, serieData.context.position);
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize);
var highlight = dist <= size * 2.5f;
serieData.context.highlight = highlight;
var state = SerieHelper.GetSerieState(serie, serieData, true);
size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, state);
serieData.interact.SetValue(ref needInteract, size);
if (highlight)
{
serie.context.pointerEnter = true;
@@ -309,20 +292,6 @@ namespace XCharts.Runtime
var dataChanging = false;
var dataChangeDuration = serie.animation.GetChangeDuration();
var unscaledTime = serie.animation.unscaledTime;
var dataAddDuration = 0f;
var useCurrentData = false;
List<double> sampleSumPrefix = null;
if (serie.animation.enable)
{
dataAddDuration = serie.animation.GetAdditionDuration();
useCurrentData = DataHelper.IsAnyDataChanged(ref showData, serie.minShow, showData.Count);
dataChanging = useCurrentData;
}
if (!useCurrentData && rate > 1 &&
(serie.sampleType == SampleType.Sum || serie.sampleType == SampleType.Average))
{
sampleSumPrefix = DataHelper.BuildSampleSumPrefix(ref showData, showData.Count, relativedAxis.inverse);
}
var interacting = false;
var lineWidth = LineHelper.GetLineWidth(ref interacting, serie, chart.theme.serie.lineWidth);
@@ -359,8 +328,7 @@ namespace XCharts.Runtime
var np = Vector3.zero;
var xValue = axis.IsCategory() ? realIndex : serieData.GetData(0, axis.inverse);
var relativedValue = DataHelper.SampleValue(ref showData, serie.sampleType, rate, serie.minShow,
maxCount, totalAverage, i, dataAddDuration, dataChangeDuration, ref dataChanging, relativedAxis,
unscaledTime, useCurrentData, false, sampleSumPrefix);
maxCount, totalAverage, i, 0, dataChangeDuration, ref dataChanging, relativedAxis, unscaledTime);
serieData.context.stackHeight = GetDataPoint(isY, axis, relativedAxis, m_SerieGrid, xValue, relativedValue,
i, scaleWid, scaleRelativedWid, isStack, ref np);

View File

@@ -97,25 +97,6 @@ namespace XCharts.Runtime
new Vector3(zero, points[count - 1].position.y) :
new Vector3(points[count - 1].position.x, zero);
// ===== 优化:缓存动画检查结果 =====
bool needAnimationCheck = serie.animation.IsSerieAnimation() && !serie.animation.IsFinish();
float animationCurrDetail = serie.animation.GetCurrDetail();
// ===== 优化:预计算颜色 =====
Color32[] gradientColors1 = null, gradientColors2 = null;
if (isVisualMapGradient)
{
gradientColors1 = new Color32[count];
gradientColors2 = new Color32[count];
for (int i = 0; i < count; i++)
{
var tp = points[i].position;
var zp = isY ? new Vector3(zero, tp.y) : new Vector3(tp.x, zero);
gradientColors1[i] = VisualMapHelper.GetLineGradientColor(visualMap, zp, grid, axis, relativedAxis, areaColor);
gradientColors2[i] = VisualMapHelper.GetLineGradientColor(visualMap, tp, grid, axis, relativedAxis, areaToColor);
}
}
var lastDataIsIgnore = false;
for (int i = 0; i < points.Count; i++)
{
@@ -130,26 +111,23 @@ namespace XCharts.Runtime
var toColor = areaToColor;
var lerp = areaLerp;
// ===== 优化:使用缓存的动画状态 =====
if (needAnimationCheck)
if (serie.animation.CheckDetailBreak(tp, isY))
{
if (isY && tp.y > animationCurrDetail || !isY && tp.x > animationCurrDetail)
{
isBreak = true;
var ip = Vector3.zero;
var axisStartPos = isY ? new Vector3(-10000, animationCurrDetail) : new Vector3(animationCurrDetail, -10000);
var axisEndPos = isY ? new Vector3(10000, animationCurrDetail) : new Vector3(animationCurrDetail, 10000);
isBreak = true;
if (UGLHelper.GetIntersection(lp, tp, axisStartPos, axisEndPos, ref ip))
tp = ip;
}
var progress = serie.animation.GetCurrDetail();
var ip = Vector3.zero;
var axisStartPos = isY ? new Vector3(-10000, progress) : new Vector3(progress, -10000);
var axisEndPos = isY ? new Vector3(10000, progress) : new Vector3(progress, 10000);
if (UGLHelper.GetIntersection(lp, tp, axisStartPos, axisEndPos, ref ip))
tp = ip;
}
var zp = isY ? new Vector3(zero, tp.y) : new Vector3(tp.x, zero);
if (isVisualMapGradient)
{
color = gradientColors1[i];
toColor = gradientColors2[i];
color = VisualMapHelper.GetLineGradientColor(visualMap, zp, grid, axis, relativedAxis, areaColor);
toColor = VisualMapHelper.GetLineGradientColor(visualMap, tp, grid, axis, relativedAxis, areaToColor);
lerp = true;
}
if (i > 0)
@@ -293,13 +271,6 @@ namespace XCharts.Runtime
}
}
/// <summary>
/// 【优化版本】关键性能优化:
/// 1. 颜色预计算 (50-70% 性能提升)
/// 2. 缓存动画检查结果 (30-40% 性能提升)
/// 3. 线段样式预处理 (10-20% 性能提升)
/// 总体预期提升50-70%(当启用渐变时)
/// </summary>
internal static void DrawSerieLine(VertexHelper vh, ThemeStyle theme, Serie serie, VisualMap visualMap,
GridCoord grid, Axis axis, Axis relativedAxis, float lineWidth)
{
@@ -333,40 +304,6 @@ namespace XCharts.Runtime
var dashLength = serie.lineStyle.dashLength;
var gapLength = serie.lineStyle.gapLength;
var dotLength = serie.lineStyle.dotLength;
// ===== 优化 1: 预计算颜色数组 (如果启用 VisualMap 渐变) =====
Color32[] pointColors1 = null, pointColors2 = null;
if (isVisualMapGradient)
{
pointColors1 = new Color32[dataCount];
pointColors2 = new Color32[dataCount];
for (int i = 0; i < dataCount; i++)
{
pointColors1[i] = VisualMapHelper.GetLineGradientColor(visualMap, datas[i].position, grid, axis, relativedAxis, lineColor);
pointColors2[i] = pointColors1[i];
}
}
// 如果启用线段样式渐变,也预计算
Color32[] styleColors1 = null, styleColors2 = null;
if (isLineStyleGradient && !isVisualMapGradient)
{
styleColors1 = new Color32[dataCount];
styleColors2 = new Color32[dataCount];
for (int i = 0; i < dataCount; i++)
{
styleColors1[i] = VisualMapHelper.GetLineStyleGradientColor(serie.lineStyle, datas[i].position, grid, axis, lineColor);
styleColors2[i] = styleColors1[i];
}
}
// ===== 优化 2: 缓存动画检查结果 =====
bool needAnimationCheck = serie.animation.IsSerieAnimation() && !serie.animation.IsFinish();
float animationCurrDetail = serie.animation.GetCurrDetail();
// ===== 优化 3: 线段样式预处理 (避免循环内 switch) =====
System.Func<int, bool> isSegmentIgnored = BuildSegmentIgnoreFunc(serie.lineStyle.type,
dashLength, gapLength, dotLength);
for (int i = 1; i < dataCount; i++)
{
var cdata = datas[i];
@@ -375,20 +312,15 @@ namespace XCharts.Runtime
var lp = datas[i - 1].position;
var np = i == dataCount - 1 ? cp : datas[i + 1].position;
// ===== 优化:使用缓存的动画状态 =====
if (needAnimationCheck)
if (serie.animation.CheckDetailBreak(cp, isY))
{
if (isY && cp.y > animationCurrDetail || !isY && cp.x > animationCurrDetail)
{
isBreak = true;
var ip = Vector3.zero;
var rate = 0f;
if (AnimationStyleHelper.GetAnimationPosition(serie.animation, isY, lp, cp, animationCurrDetail, ref ip, ref rate))
cp = np = ip;
}
isBreak = true;
var ip = Vector3.zero;
var progress = serie.animation.GetCurrDetail();
var rate = 0f;
if (AnimationStyleHelper.GetAnimationPosition(serie.animation, isY, lp, cp, progress, ref ip, ref rate))
cp = np = ip;
}
serie.context.lineEndPostion = cp;
serie.context.lineEndValueY = AxisHelper.GetAxisPositionValue(grid, relativedAxis, cp);
var handled = false;
@@ -406,11 +338,39 @@ namespace XCharts.Runtime
handled = true;
break;
}
// ===== 优化:使用预处理的线段样式函数 =====
segmentCount++;
if (isSegmentIgnored(segmentCount))
isIgnore = true;
{
segmentCount++;
var index = 0f;
switch (serie.lineStyle.type)
{
case LineStyle.Type.Dashed:
index = segmentCount % (dashLength + gapLength);
if (index >= dashLength)
isIgnore = true;
break;
case LineStyle.Type.Dotted:
index = segmentCount % (dotLength + gapLength);
if (index >= dotLength)
isIgnore = true;
break;
case LineStyle.Type.DashDot:
index = segmentCount % (dashLength + dotLength + 2 * gapLength);
if (index >= dashLength && index < dashLength + gapLength)
isIgnore = true;
else if (index >= dashLength + gapLength + dotLength)
isIgnore = true;
break;
case LineStyle.Type.DashDotDot:
index = segmentCount % (dashLength + 2 * dotLength + 3 * gapLength);
if (index >= dashLength && index < dashLength + gapLength)
isIgnore = true;
else if (index >= dashLength + gapLength + dotLength && index < dashLength + dotLength + 2 * gapLength)
isIgnore = true;
else if (index >= dashLength + 2 * gapLength + 2 * dotLength)
isIgnore = true;
break;
}
}
if (handled)
{
@@ -431,35 +391,12 @@ namespace XCharts.Runtime
if (i == 1)
{
if (isClip) lastDataIsIgnore = true;
if (isVisualMapGradient)
{
AddLineVertToVertexHelperFast(vh, ltp, lbp, pointColors1[0], pointColors1[0], false, lastDataIsIgnore, isIgnore);
}
else if (isLineStyleGradient)
{
AddLineVertToVertexHelperFast(vh, ltp, lbp, styleColors1[0], styleColors1[0], false, lastDataIsIgnore, isIgnore);
}
else
{
AddLineVertToVertexHelper(vh, ltp, lbp, lineColor, false, false,
visualMap, serie.lineStyle, grid, axis, relativedAxis, false, lastDataIsIgnore, isIgnore);
}
AddLineVertToVertexHelper(vh, ltp, lbp, lineColor, isVisualMapGradient, isLineStyleGradient,
visualMap, serie.lineStyle, grid, axis, relativedAxis, false, lastDataIsIgnore, isIgnore);
if (dataCount == 2 || isBreak)
{
if (isVisualMapGradient)
{
AddLineVertToVertexHelperFast(vh, clp, crp, pointColors1[i], pointColors1[i], true, lastDataIsIgnore, isIgnore);
}
else if (isLineStyleGradient)
{
AddLineVertToVertexHelperFast(vh, clp, crp, styleColors1[i], styleColors1[i], true, lastDataIsIgnore, isIgnore);
}
else
{
AddLineVertToVertexHelper(vh, clp, crp, lineColor, false, false,
visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
}
AddLineVertToVertexHelper(vh, clp, crp, lineColor, isVisualMapGradient, isLineStyleGradient,
visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
serie.context.lineEndPostion = cp;
serie.context.lineEndValueY = AxisHelper.GetAxisPositionValue(grid, relativedAxis, cp);
break;
@@ -469,70 +406,31 @@ namespace XCharts.Runtime
if (bitp == bibp)
{
if (bitp)
{
if (isVisualMapGradient)
AddLineVertToVertexHelperFast(vh, itp, ibp, pointColors1[i], pointColors1[i], true, lastDataIsIgnore, isIgnore);
else if (isLineStyleGradient)
AddLineVertToVertexHelperFast(vh, itp, ibp, styleColors1[i], styleColors1[i], true, lastDataIsIgnore, isIgnore);
else
AddLineVertToVertexHelper(vh, itp, ibp, lineColor, false, false, visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
}
AddLineVertToVertexHelper(vh, itp, ibp, lineColor, isVisualMapGradient, isLineStyleGradient,
visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
else
{
if (isVisualMapGradient)
{
AddLineVertToVertexHelperFast(vh, ltp, clp, pointColors1[i-1], pointColors1[i], true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelperFast(vh, ltp, crp, pointColors1[i-1], pointColors1[i], true, lastDataIsIgnore, isIgnore);
}
else if (isLineStyleGradient)
{
AddLineVertToVertexHelperFast(vh, ltp, clp, styleColors1[i-1], styleColors1[i], true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelperFast(vh, ltp, crp, styleColors1[i-1], styleColors1[i], true, lastDataIsIgnore, isIgnore);
}
else
{
AddLineVertToVertexHelper(vh, ltp, clp, lineColor, false, false, visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelper(vh, ltp, crp, lineColor, false, false, visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
}
AddLineVertToVertexHelper(vh, ltp, clp, lineColor, isVisualMapGradient, isLineStyleGradient,
visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelper(vh, ltp, crp, lineColor, isVisualMapGradient, isLineStyleGradient,
visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
}
}
else
{
if (bitp)
{
if (isVisualMapGradient)
{
AddLineVertToVertexHelperFast(vh, itp, clp, pointColors1[i], pointColors1[i], true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelperFast(vh, itp, crp, pointColors1[i], pointColors1[i], true, lastDataIsIgnore, isIgnore);
}
else if (isLineStyleGradient)
{
AddLineVertToVertexHelperFast(vh, itp, clp, styleColors1[i], styleColors1[i], true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelperFast(vh, itp, crp, styleColors1[i], styleColors1[i], true, lastDataIsIgnore, isIgnore);
}
else
{
AddLineVertToVertexHelper(vh, itp, clp, lineColor, false, false, visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelper(vh, itp, crp, lineColor, false, false, visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
}
AddLineVertToVertexHelper(vh, itp, clp, lineColor, isVisualMapGradient, isLineStyleGradient,
visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelper(vh, itp, crp, lineColor, isVisualMapGradient, isLineStyleGradient,
visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
}
else if (bibp)
{
if (isVisualMapGradient)
{
AddLineVertToVertexHelperFast(vh, clp, ibp, pointColors1[i], pointColors1[i], true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelperFast(vh, crp, ibp, pointColors1[i], pointColors1[i], true, lastDataIsIgnore, isIgnore);
}
else if (isLineStyleGradient)
{
AddLineVertToVertexHelperFast(vh, clp, ibp, styleColors1[i], styleColors1[i], true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelperFast(vh, crp, ibp, styleColors1[i], styleColors1[i], true, lastDataIsIgnore, isIgnore);
}
else
{
AddLineVertToVertexHelper(vh, clp, ibp, lineColor, false, false, visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelper(vh, crp, ibp, lineColor, false, false, visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
}
AddLineVertToVertexHelper(vh, clp, ibp, lineColor, isVisualMapGradient, isLineStyleGradient,
visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
AddLineVertToVertexHelper(vh, crp, ibp, lineColor, isVisualMapGradient, isLineStyleGradient,
visualMap, serie.lineStyle, grid, axis, relativedAxis, true, lastDataIsIgnore, isIgnore);
}
}
lastDataIsIgnore = isIgnore;
@@ -541,47 +439,6 @@ namespace XCharts.Runtime
}
}
/// <summary>
/// 【优化】预处理线段样式,避免循环内重复的 switch 判断
/// 返回一个委托,用于快速判断某个段是否应该被忽略
/// </summary>
private static System.Func<int, bool> BuildSegmentIgnoreFunc(LineStyle.Type lineType,
float dashLength, float gapLength, float dotLength)
{
switch (lineType)
{
case LineStyle.Type.Dashed:
return (segmentCount) =>
{
var index = segmentCount % (dashLength + gapLength);
return index >= dashLength;
};
case LineStyle.Type.Dotted:
return (segmentCount) =>
{
var index = segmentCount % (dotLength + gapLength);
return index >= dotLength;
};
case LineStyle.Type.DashDot:
return (segmentCount) =>
{
var index = segmentCount % (dashLength + dotLength + 2 * gapLength);
return (index >= dashLength && index < dashLength + gapLength) ||
(index >= dashLength + gapLength + dotLength);
};
case LineStyle.Type.DashDotDot:
return (segmentCount) =>
{
var index = segmentCount % (dashLength + 2 * dotLength + 3 * gapLength);
return (index >= dashLength && index < dashLength + gapLength) ||
(index >= dashLength + gapLength + dotLength && index < dashLength + dotLength + 2 * gapLength) ||
(index >= dashLength + 2 * gapLength + 2 * dotLength);
};
default:
return (_) => false;
}
}
public static float GetLineWidth(ref bool interacting, Serie serie, float defaultWidth)
{
var lineWidth = 0f;
@@ -593,27 +450,6 @@ namespace XCharts.Runtime
return lineWidth;
}
/// <summary>
/// 快速路径版本 - 用于颜色已预计算的情况,避免条件判断和重复计算
/// </summary>
private static void AddLineVertToVertexHelperFast(VertexHelper vh, Vector3 tp, Vector3 bp,
Color32 color1, Color32 color2, bool needTriangle, bool lastIgnore, bool ignore)
{
if (lastIgnore && needTriangle)
UGL.AddVertToVertexHelper(vh, tp, bp, ColorUtil.clearColor32, true);
UGL.AddVertToVertexHelper(vh, tp, bp, color1, color2, needTriangle);
if (lastIgnore && !needTriangle)
{
UGL.AddVertToVertexHelper(vh, tp, bp, ColorUtil.clearColor32, false);
}
if (ignore && needTriangle)
{
UGL.AddVertToVertexHelper(vh, tp, bp, ColorUtil.clearColor32, false);
}
}
private static void AddLineVertToVertexHelper(VertexHelper vh, Vector3 tp, Vector3 bp,
Color32 lineColor, bool visualMapGradient, bool lineStyleGradient, VisualMap visualMap,
LineStyle lineStyle, GridCoord grid, Axis axis, Axis relativedAxis, bool needTriangle,

View File

@@ -67,7 +67,6 @@ namespace XCharts.Runtime
m_LastCheckContextFlag = needCheck;
var themeSymbolSize = chart.theme.serie.lineSymbolSize;
lineWidth = serie.lineStyle.GetWidth(chart.theme.serie.lineWidth);
var symbolVisible = serie.symbol != null && serie.symbol.show && serie.symbol.type != SymbolType.None;
var needInteract = false;
if (m_LegendEnter)
@@ -77,12 +76,9 @@ namespace XCharts.Runtime
for (int i = 0; i < serie.dataCount; i++)
{
var serieData = serie.data[i];
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, SerieState.Emphasis);
serieData.context.highlight = true;
if (symbolVisible)
{
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, SerieState.Emphasis);
serieData.interact.SetValue(ref needInteract, size);
}
serieData.interact.SetValue(ref needInteract, size);
}
}
else if (serie.context.isTriggerByAxis)
@@ -94,17 +90,13 @@ namespace XCharts.Runtime
var serieData = serie.data[i];
var highlight = i == serie.context.pointerItemDataIndex;
serieData.context.highlight = highlight;
if (symbolVisible)
{
var state = SerieHelper.GetSerieState(serie, serieData, true);
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, state);
serieData.interact.SetValue(ref needInteract, size);
}
var state = SerieHelper.GetSerieState(serie, serieData, true);
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, state);
serieData.interact.SetValue(ref needInteract, size);
if (highlight)
{
serie.context.pointerEnter = true;
serie.context.pointerItemDataIndex = i;
needInteract = true;
}
}
}
@@ -116,21 +108,13 @@ namespace XCharts.Runtime
for (int i = 0; i < serie.dataCount; i++)
{
var serieData = serie.data[i];
var pointerOffset = (Vector2)chart.pointerPos - (Vector2)serieData.context.position;
bool highlight;
if (symbolVisible)
{
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize);
highlight = pointerOffset.sqrMagnitude <= size * size;
var state = SerieHelper.GetSerieState(serie, serieData, true);
size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, state);
serieData.interact.SetValue(ref needInteract, size);
}
else
{
highlight = pointerOffset.sqrMagnitude <= themeSymbolSize * themeSymbolSize;
}
var dist = Vector3.Distance(chart.pointerPos, serieData.context.position);
var size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize);
var highlight = dist <= size;
serieData.context.highlight = highlight;
var state = SerieHelper.GetSerieState(serie, serieData, true);
size = SerieHelper.GetSysmbolSize(serie, serieData, themeSymbolSize, state);
serieData.interact.SetValue(ref needInteract, size);
if (highlight)
{
serie.context.pointerEnter = true;
@@ -193,18 +177,6 @@ namespace XCharts.Runtime
var dataChangeDuration = serie.animation.GetChangeDuration();
var dataAddDuration = serie.animation.GetAdditionDuration();
var unscaledTime = serie.animation.unscaledTime;
var useCurrentData = false;
List<double> sampleSumPrefix = null;
if (serie.animation.enable)
{
useCurrentData = DataHelper.IsAnyDataChanged(ref showData, serie.minShow, maxCount);
dataChanging = useCurrentData;
}
if (!useCurrentData && rate > 1 &&
(serie.sampleType == SampleType.Sum || serie.sampleType == SampleType.Average))
{
sampleSumPrefix = DataHelper.BuildSampleSumPrefix(ref showData, maxCount, relativedAxis.inverse);
}
var interacting = false;
var lineWidth = LineHelper.GetLineWidth(ref interacting, serie, chart.theme.serie.lineWidth);
@@ -232,8 +204,7 @@ namespace XCharts.Runtime
var np = Vector3.zero;
var xValue = axis.IsCategory() ? i : serieData.GetData(0, axis.inverse);
var relativedValue = DataHelper.SampleValue(ref showData, serie.sampleType, rate, serie.minShow,
maxCount, totalAverage, i, dataAddDuration, dataChangeDuration, ref dataChanging, relativedAxis,
unscaledTime, useCurrentData, false, sampleSumPrefix);
maxCount, totalAverage, i, dataAddDuration, dataChangeDuration, ref dataChanging, relativedAxis, unscaledTime);
serieData.context.stackHeight = GetDataPoint(isY, axis, relativedAxis, m_SerieGrid, xValue, relativedValue,
i, scaleWid, scaleRelativedWid, false, ref np);

View File

@@ -95,9 +95,6 @@ namespace XCharts.Runtime
else
{
itemFormatter = itemFormatter.Replace("\\n", "\n");
var needTotal = itemFormatter.IndexOf("{d", System.StringComparison.OrdinalIgnoreCase) >= 0 ||
itemFormatter.IndexOf("{f", System.StringComparison.OrdinalIgnoreCase) >= 0;
var total = needTotal ? serie.yTotal : 0;
var temp = itemFormatter.Split('\n');
for (int i = 0; i < temp.Length; i++)
{
@@ -109,7 +106,7 @@ namespace XCharts.Runtime
param.serieData = serieData;
param.dataCount = serie.dataCount;
param.value = serieData.GetData(i);
param.total = total;
param.total = serie.yTotal;
param.color = color;
param.category = radar.GetIndicatorName(i);
param.marker = marker;

View File

@@ -323,9 +323,6 @@ namespace XCharts.Runtime
[NonSerialized] internal bool m_NeedUpdateFilterData;
[NonSerialized] public List<SerieData> m_FilterData = new List<SerieData>();
[NonSerialized] private bool m_NameDirty;
[NonSerialized] private int m_YTotalCacheFrame = -1;
[NonSerialized] private double m_YTotalCacheValue = 0;
/// <summary>
/// event callback when click serie.
@@ -1242,9 +1239,6 @@ namespace XCharts.Runtime
{
get
{
if (m_YTotalCacheFrame == Time.frameCount)
return m_YTotalCacheValue;
double total = 0;
if (IsPerformanceMode())
{
@@ -1265,8 +1259,6 @@ namespace XCharts.Runtime
total += sdata.GetCurrData(1, dataAddDuration, duration, unscaledTime);
}
}
m_YTotalCacheFrame = Time.frameCount;
m_YTotalCacheValue = total;
return total;
}
}
@@ -1317,7 +1309,6 @@ namespace XCharts.Runtime
/// </summary>
public override void ClearData()
{
InvalidateTotalCache();
while (m_Data.Count > 0)
{
RemoveData(0);
@@ -1345,7 +1336,6 @@ namespace XCharts.Runtime
{
if (index >= 0 && index < m_Data.Count)
{
InvalidateTotalCache();
if (!string.IsNullOrEmpty(m_Data[index].name))
{
SetSerieNameDirty();
@@ -1394,7 +1384,6 @@ namespace XCharts.Runtime
public virtual void AddSerieData(SerieData serieData)
{
InvalidateTotalCache();
if (m_InsertDataToHead)
m_Data.Insert(0, serieData);
else
@@ -1835,7 +1824,6 @@ namespace XCharts.Runtime
var flag = m_Data[index].UpdateData(dimension, value, animationOpen, unscaledTime, animationDuration);
if (flag)
{
InvalidateTotalCache();
SetVerticesDirty();
dataDirty = true;
titleDirty = true;
@@ -1857,7 +1845,6 @@ namespace XCharts.Runtime
{
if (index >= 0 && index < m_Data.Count && values != null)
{
InvalidateTotalCache();
var serieData = m_Data[index];
var animationOpen = animation.enable;
var animationDuration = animation.GetChangeDuration();
@@ -1871,18 +1858,6 @@ namespace XCharts.Runtime
return false;
}
private void InvalidateTotalCache()
{
m_YTotalCacheFrame = -1;
m_YTotalCacheValue = 0;
InvalidateMinMaxCache();
}
private void InvalidateMinMaxCache()
{
context.InvalidateMinMaxCache();
}
public bool UpdateDataName(int index, string name)
{
if (index >= 0 && index < m_Data.Count)

View File

@@ -29,40 +29,6 @@ namespace XCharts.Runtime
public class SerieContext
{
[System.NonSerialized] internal double[] cachedMin = new double[3] { double.MaxValue, double.MaxValue, double.MaxValue };
[System.NonSerialized] internal double[] cachedMax = new double[3] { double.MinValue, double.MinValue, double.MinValue };
[System.NonSerialized] internal bool[] cacheValid = new bool[3] { false, false, false };
internal void InvalidateMinMaxCache()
{
for (int i = 0; i < cacheValid.Length; i++)
cacheValid[i] = false;
cachedMin[0] = cachedMin[1] = cachedMin[2] = double.MaxValue;
cachedMax[0] = cachedMax[1] = cachedMax[2] = double.MinValue;
}
internal bool TryGetCachedMinMax(int dimension, out double minValue, out double maxValue)
{
minValue = 0; maxValue = 0;
if (dimension < 0 || dimension > 2) return false;
if (cacheValid[dimension])
{
minValue = cachedMin[dimension];
maxValue = cachedMax[dimension];
return true;
}
return false;
}
internal void SetCachedMinMax(int dimension, double minValue, double maxValue)
{
if (dimension < 0 || dimension > 2) return;
cachedMin[dimension] = minValue;
cachedMax[dimension] = maxValue;
cacheValid[dimension] = true;
}
/// <summary>
/// 鼠标是否进入serie
/// </summary>

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
@@ -69,7 +68,6 @@ namespace XCharts.Runtime
protected bool m_ForceUpdateSerieContext = false;
protected int m_LegendEnterIndex;
protected ChartLabel m_EndLabel;
private HashSet<int> m_DataIndexsSet = new HashSet<int>();
private float[] m_LastRadius = new float[2] { 0, 0 };
private float[] m_LastCenter = new float[2] { 0, 0 };
@@ -507,24 +505,15 @@ namespace XCharts.Runtime
var dataAddDuration = serie.animation.GetAdditionDuration();
var unscaledTime = serie.animation.unscaledTime;
var needCheck = serie.context.dataIndexs.Count > 0;
if (needCheck)
{
m_DataIndexsSet.Clear();
foreach (var idx in serie.context.dataIndexs)
m_DataIndexsSet.Add(idx);
}
var allLabelZeroPosition = true;
var anyLabelActive = false;
SerieData lastActiveLabelSerieData = null;
var lastActiveLabelPos = Vector3.zero;
double lastActiveLabelValue = 0;
foreach (var serieData in serie.data)
{
if (serieData.labelObject == null && serieData.context.dataLabels.Count <= 0)
{
continue;
}
if (needCheck && !m_DataIndexsSet.Contains(serieData.index))
if (needCheck && !serie.context.dataIndexs.Contains(serieData.index))
{
serieData.SetLabelActive(false);
continue;
@@ -540,7 +529,7 @@ namespace XCharts.Runtime
{
if (serie.multiDimensionLabel)
{
var total = FormatterHelper.NeedTotalContent(currLabel.formatter) ? serieData.GetTotalData() : 0;
var total = serieData.GetTotalData();
var color = chart.GetItemColor(serie, serieData);
for (int i = 0; i < serieData.context.dataLabels.Count; i++)
{
@@ -553,7 +542,6 @@ namespace XCharts.Runtime
currLabel, color, chart);
var offset = GetSerieDataLabelOffset(serieData, currLabel);
var active = currLabel.show && !isIgnore && !serie.IsMinShowLabelValue(value);
if (active) active = CheckLabelVisible(currLabel, serieData.index, value, i);
if (active)
{
anyLabelActive = true;
@@ -577,7 +565,7 @@ namespace XCharts.Runtime
else
{
var value = serieData.GetCurrData(defaultDimension, dataAddDuration, dataChangeDuration, unscaledTime);
var total = FormatterHelper.NeedTotalContent(currLabel.formatter) ? serie.GetDataTotal(defaultDimension, serieData) : 0;
var total = serie.GetDataTotal(defaultDimension, serieData);
var color = chart.GetItemColor(serie, serieData);
var content = string.IsNullOrEmpty(currLabel.formatter) ?
ChartCached.NumberToStr(value, currLabel.numericFormatter) :
@@ -585,38 +573,6 @@ namespace XCharts.Runtime
currLabel, color, chart);
var labelPos = UpdateLabelPosition(serieData, currLabel);
var active = currLabel.show && !isIgnore && !serie.IsMinShowLabelValue(value);
if (active) active = CheckLabelVisible(currLabel, serieData.index, value, defaultDimension);
if (active && currLabel.showMinGap > 0 && lastActiveLabelSerieData != null)
{
var dist = Mathf.Abs(labelPos.x - lastActiveLabelPos.x);
if (dist < currLabel.showMinGap)
{
var currValue = serieData.GetData(1);
if (Math.Abs(currValue) >= Math.Abs(lastActiveLabelValue))
{
lastActiveLabelSerieData.SetLabelActive(false);
lastActiveLabelSerieData = serieData;
lastActiveLabelPos = labelPos;
lastActiveLabelValue = currValue;
}
else
{
active = false;
}
}
else
{
lastActiveLabelSerieData = serieData;
lastActiveLabelPos = labelPos;
lastActiveLabelValue = serieData.GetData(1);
}
}
else if (active)
{
lastActiveLabelSerieData = serieData;
lastActiveLabelPos = labelPos;
lastActiveLabelValue = serieData.GetData(1);
}
if (active)
{
anyLabelActive = true;
@@ -650,66 +606,6 @@ namespace XCharts.Runtime
}
}
private bool CheckLabelVisible(LabelStyle label, int dataIndex, double value, int dimension)
{
// showCondition: 基于阈值的条件检查AND showFilter
bool conditionResult;
switch (label.showCondition)
{
case LabelStyle.ShowCondition.GreaterThan:
conditionResult = value > label.showThreshold;
break;
case LabelStyle.ShowCondition.LessThan:
conditionResult = value < label.showThreshold;
break;
default: // Always
conditionResult = true;
break;
}
if (!conditionResult)
return false;
// showFilter: 基于数据形态的过滤检查
switch (label.showFilter)
{
case LabelStyle.ShowFilter.Peak:
{
bool isPeak = true;
bool hasNeighbor = false;
if (dataIndex > 0)
{
hasNeighbor = true;
isPeak &= value > serie.data[dataIndex - 1].GetData(dimension);
}
if (dataIndex < serie.dataCount - 1)
{
hasNeighbor = true;
isPeak &= value > serie.data[dataIndex + 1].GetData(dimension);
}
return isPeak && hasNeighbor;
}
case LabelStyle.ShowFilter.Valley:
{
bool isValley = true;
bool hasNeighbor = false;
if (dataIndex > 0)
{
hasNeighbor = true;
isValley &= value < serie.data[dataIndex - 1].GetData(dimension);
}
if (dataIndex < serie.dataCount - 1)
{
hasNeighbor = true;
isValley &= value < serie.data[dataIndex + 1].GetData(dimension);
}
return isValley && hasNeighbor;
}
default: // All
return true;
}
}
public virtual void RefreshEndLabelInternal()
{
if (m_EndLabel == null)
@@ -798,14 +694,11 @@ namespace XCharts.Runtime
if (itemFormatter == null) itemFormatter = "";
var newItemFormatter = itemFormatter.Replace("\\n", "\n");
var newNumericFormatter = SerieHelper.GetNumericFormatter(serie, serieData, numericFormatter);
var needTotal = newItemFormatter.IndexOf("{d", System.StringComparison.OrdinalIgnoreCase) >= 0 ||
newItemFormatter.IndexOf("{f", System.StringComparison.OrdinalIgnoreCase) >= 0;
var total = needTotal ? serie.yTotal : 0;
int newLinePos = newItemFormatter.IndexOf('\n');
if (newLinePos < 0)
var temp = newItemFormatter.Split('\n');
for (int i = 0; i < temp.Length; i++)
{
var formatter = newItemFormatter;
var param = serie.context.param;
var formatter = temp[i];
var param = i == 0 ? serie.context.param : new SerieParams();
param.serieName = serie.serieName;
param.serieIndex = serie.index;
param.category = category;
@@ -814,7 +707,7 @@ namespace XCharts.Runtime
param.dataCount = serie.dataCount;
param.value = serieData.GetData(dimension);
param.ignore = ignore;
param.total = total;
param.total = serie.yTotal;
param.color = chart.GetMarkColor(serie, serieData);
param.marker = SerieHelper.GetItemMarker(serie, serieData, marker);
param.itemFormatter = formatter;
@@ -827,35 +720,6 @@ namespace XCharts.Runtime
paramList.Add(param);
}
else
{
var temp = newItemFormatter.Split('\n');
for (int i = 0; i < temp.Length; i++)
{
var formatter = temp[i];
var param = i == 0 ? serie.context.param : new SerieParams();
param.serieName = serie.serieName;
param.serieIndex = serie.index;
param.category = category;
param.dimension = dimension;
param.serieData = serieData;
param.dataCount = serie.dataCount;
param.value = serieData.GetData(dimension);
param.ignore = ignore;
param.total = total;
param.color = chart.GetMarkColor(serie, serieData);
param.marker = SerieHelper.GetItemMarker(serie, serieData, marker);
param.itemFormatter = formatter;
param.numericFormatter = newNumericFormatter;
param.columns.Clear();
param.columns.Add(param.marker);
param.columns.Add(showCategory ? category : serie.serieName);
param.columns.Add(ignore ? ignoreDataDefaultContent : ChartCached.NumberToStr(param.value, param.numericFormatter));
paramList.Add(param);
}
}
}
protected void UpdateItemSerieParams(ref List<SerieParams> paramList, ref string title,

View File

@@ -7,7 +7,7 @@ namespace XCharts.Runtime
{
public static partial class SerieHelper
{
public static double GetMinData(Serie serie, int dimension = 1, DataZoom dataZoom = null, bool inverse = false)
public static double GetMinData(Serie serie, int dimension = 1, DataZoom dataZoom = null)
{
double min = double.MaxValue;
var dataList = serie.GetDataList(dataZoom);
@@ -16,7 +16,7 @@ namespace XCharts.Runtime
var serieData = dataList[i];
if (serieData.show && serieData.data.Count > dimension)
{
var value = serieData.GetData(dimension, inverse);
var value = serieData.data[dimension];
if (value < min && !serie.IsIgnoreValue(serieData, value)) min = value;
}
}
@@ -42,7 +42,7 @@ namespace XCharts.Runtime
}
return minData;
}
public static double GetMaxData(Serie serie, int dimension = 1, DataZoom dataZoom = null, bool inverse = false)
public static double GetMaxData(Serie serie, int dimension = 1, DataZoom dataZoom = null)
{
double max = double.MinValue;
var dataList = serie.GetDataList(dataZoom);
@@ -51,7 +51,7 @@ namespace XCharts.Runtime
var serieData = dataList[i];
if (serieData.show && serieData.data.Count > dimension)
{
var value = serieData.GetData(dimension, inverse);
var value = serieData.data[dimension];
if (value > max && !serie.IsIgnoreValue(serieData, value)) max = value;
}
}
@@ -159,7 +159,7 @@ namespace XCharts.Runtime
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="dataZoom"></param>
public static void GetMinMaxData(Serie serie, out double min, out double max, DataZoom dataZoom = null)
public static void GetMinMaxData(Serie serie, out double min, out double max, DataZoom dataZoom = null, int dimension = 0)
{
max = double.MinValue;
min = double.MaxValue;
@@ -169,9 +169,11 @@ namespace XCharts.Runtime
var serieData = dataList[i];
if (serieData.show)
{
var count = serie.showDataDimension > 0 && serie.showDataDimension < serieData.data.Count ?
serie.showDataDimension :
serieData.data.Count;
var count = 0;
if (dimension > 0) count = dimension;
else count = serie.showDataDimension > serieData.data.Count ?
serieData.data.Count :
serie.showDataDimension;
for (int j = 0; j < count; j++)
{
var value = serieData.data[j];
@@ -867,36 +869,21 @@ namespace XCharts.Runtime
private static void UpdateFilterData_Category(Serie serie, DataZoom dataZoom)
{
var data = serie.data;
// Use (N-1) intervals so that data point i maps to exactly i/(N-1)*100% of the
// DataZoom width — matching how the shadow draws data via scaleWid = width/(N-1).
// CeilToInt for start ensures we never include a point that lies before the filler.
// FloorToInt for end ensures we never include a point that lies after the filler.
int n = data.Count - 1;
int startIndex, endIndex;
if (n > 0)
var range = Mathf.RoundToInt(data.Count * (dataZoom.end - dataZoom.start) / 100);
if (range <= 0) range = 1;
int start = 0, end = 0;
if (dataZoom.context.invert)
{
if (dataZoom.context.invert)
{
startIndex = Mathf.CeilToInt((float)n * (100 - dataZoom.end) / 100);
endIndex = Mathf.FloorToInt((float)n * (100 - dataZoom.start) / 100);
}
else
{
startIndex = Mathf.CeilToInt((float)n * dataZoom.start / 100);
endIndex = Mathf.FloorToInt((float)n * dataZoom.end / 100);
}
end = Mathf.RoundToInt(data.Count * dataZoom.end / 100);
start = end - range;
if (start < 0) start = 0;
}
else
{
startIndex = 0;
endIndex = 0;
start = Mathf.RoundToInt(data.Count * dataZoom.start / 100);
end = start + range;
if (end > data.Count) end = data.Count;
}
var range = endIndex - startIndex + 1;
if (range <= 0) range = 1;
int start = startIndex;
if (start < 0) start = 0;
int end = start + range;
if (end > data.Count) end = data.Count;
var minZoomRatio = (int)(data.Count * dataZoom.minZoomRatio);
if (start != serie.m_FilterStart || end != serie.m_FilterEnd ||
minZoomRatio != serie.m_FilterMinShow || serie.m_NeedUpdateFilterData)

View File

@@ -324,9 +324,9 @@ namespace XCharts.Runtime
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
public static void GetXMinMaxValue(BaseChart chart, int axisIndex, bool inverse, out double minValue,
out double maxValue, bool isPolar = false, bool needAnimation = false)
out double maxValue, bool isPolar = false, bool filterByDataZoom = true, bool needAnimation = false)
{
GetMinMaxValue(chart, axisIndex, inverse, 0, out minValue, out maxValue, isPolar, needAnimation);
GetMinMaxValue(chart, axisIndex, inverse, 0, out minValue, out maxValue, isPolar, filterByDataZoom, needAnimation);
}
/// <summary>
@@ -337,9 +337,9 @@ namespace XCharts.Runtime
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
public static void GetYMinMaxValue(BaseChart chart, int axisIndex, bool inverse, out double minValue,
out double maxValue, bool isPolar = false, bool needAnimation = false)
out double maxValue, bool isPolar = false, bool filterByDataZoom = true, bool needAnimation = false)
{
GetMinMaxValue(chart, axisIndex, inverse, 1, out minValue, out maxValue, isPolar, needAnimation);
GetMinMaxValue(chart, axisIndex, inverse, 1, out minValue, out maxValue, isPolar, filterByDataZoom, needAnimation);
}
/// <summary>
@@ -350,16 +350,16 @@ namespace XCharts.Runtime
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
public static void GetZMinMaxValue(BaseChart chart, int axisIndex, bool inverse, out double minValue,
out double maxValue, bool isPolar = false, bool needAnimation = false)
out double maxValue, bool isPolar = false, bool filterByDataZoom = true, bool needAnimation = false)
{
GetMinMaxValue(chart, axisIndex, inverse, 2, out minValue, out maxValue, isPolar, needAnimation);
GetMinMaxValue(chart, axisIndex, inverse, 2, out minValue, out maxValue, isPolar, filterByDataZoom, needAnimation);
}
private static Dictionary<int, List<Serie>> _stackSeriesForMinMax = new Dictionary<int, List<Serie>>();
private static Dictionary<int, double> _serieTotalValueForMinMax = new Dictionary<int, double>();
public static void GetMinMaxValue(BaseChart chart, int axisIndex,
bool inverse, int dimension, out double minValue, out double maxValue, bool isPolar = false,
bool needAnimation = false)
bool filterByDataZoom = true, bool needAnimation = false)
{
double min = double.MaxValue;
double max = double.MinValue;
@@ -376,48 +376,22 @@ namespace XCharts.Runtime
var updateDuration = needAnimation ? serie.animation.GetChangeDuration() : 0;
var dataAddDuration = needAnimation ? serie.animation.GetAdditionDuration() : 0;
var unscaledTime = serie.animation.unscaledTime;
// determine whether DataZoom filtering applies for this serie
var dz = chart.GetXDataZoomOfSerie(serie);
// Only apply DataZoom filter for non-X dimensions (dimension > 0, e.g. Y axis
// scaling to visible data). For dimension=0 (X axis whose range is controlled
// by DataZoom), using filtered X data would create a circular dependency:
// rawMin/rawMax would be set from filtered data, making the filter boundary
// relative to an already-filtered range instead of the full data range.
bool useDataZoomFilter = dimension > 0 && dz != null && dz.enable && dz.filterAxisRange;
// try per-serie cache when not filtering by dataZoom and not in animation mode
if (!useDataZoomFilter && !needAnimation)
{
double cmin, cmax;
if (serie.context.TryGetCachedMinMax(dimension, out cmin, out cmax))
{
if (cmax > max) max = cmax;
if (cmin < min) min = cmin;
continue;
}
}
double smin = double.MaxValue;
double smax = double.MinValue;
if (isPercentStack && SeriesHelper.IsPercentStack<Bar>(series, serie.serieName))
{
// percent stack per-serie considered as full range
smin = 0;
smax = 100;
if (100 > max) max = 100;
if (0 < min) min = 0;
}
else
{
var showData = serie.GetDataList(useDataZoomFilter ? dz : null);
var showData = serie.GetDataList(filterByDataZoom ? chart.GetXDataZoomOfSerie(serie) : null);
if (dimension > 0 && (serie is Candlestick || serie is SimplifiedCandlestick))
{
foreach (var data in showData)
{
double dataMin, dataMax;
data.GetMinMaxData(1, inverse, out dataMin, out dataMax);
if (dataMax > smax) smax = dataMax;
if (dataMin < smin) smin = dataMin;
if (dataMax > max) max = dataMax;
if (dataMin < min) min = dataMin;
}
}
else
@@ -429,25 +403,12 @@ namespace XCharts.Runtime
data.GetCurrData(dimension, dataAddDuration, updateDuration, unscaledTime, inverse);
if (!serie.IsIgnoreValue(data, currData))
{
if (currData > smax) smax = currData;
if (currData < smin) smin = currData;
if (currData > max) max = currData;
if (currData < min) min = currData;
}
}
}
}
// if no data found for this serie, skip
if (smax == double.MinValue && smin == double.MaxValue)
continue;
// cache per-serie result for future calls
if (!needAnimation && !useDataZoomFilter)
{
serie.context.SetCachedMinMax(dimension, smin == double.MaxValue ? 0 : smin, smax == double.MinValue ? 0 : smax);
}
if (smax > max) max = smax;
if (smin < min) min = smin;
}
}
else
@@ -462,10 +423,7 @@ namespace XCharts.Runtime
if ((isPolar && serie.polarIndex != axisIndex) ||
(!isPolar && serie.yAxisIndex != axisIndex) ||
!serie.show) continue;
var stackDz = chart.GetXDataZoomOfSerie(serie);
// Same rule as non-stack: don't use filtered data for dimension=0 (X axis).
if (stackDz != null && (dimension == 0 || !stackDz.filterAxisRange || !stackDz.enable)) stackDz = null;
var showData = serie.GetDataList(stackDz);
var showData = serie.GetDataList(filterByDataZoom ? chart.GetXDataZoomOfSerie(serie) : null);
if (SeriesHelper.IsPercentStack<Bar>(series, serie.stack))
{
for (int j = 0; j < showData.Count; j++)

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 529ec33cd4fb6466784b3a682204428c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,291 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if dUI_TextMeshPro
using TMPro;
#endif
namespace XCharts.Runtime
{
/// <summary>
/// Portable resource reference that supports a multi-level lookup strategy:
/// GUID → path → name → fallbackName → null/default
/// </summary>
[Serializable]
public class ResourceRef
{
/// <summary>Unity AssetDatabase GUID (editor-only, same project).</summary>
public string guid;
/// <summary>Asset path relative to project root e.g. "Assets/Fonts/Arial.ttf".</summary>
public string path;
/// <summary>Asset.name used for cross-project name search.</summary>
public string name;
/// <summary>Optional secondary name (system font alias, built-in default, etc.).</summary>
public string fallbackName;
/// <summary>Optional Base64-encoded asset bytes for full portability (< 100 KB recommended).</summary>
public string base64;
public bool IsEmpty()
{
return string.IsNullOrEmpty(guid)
&& string.IsNullOrEmpty(path)
&& string.IsNullOrEmpty(name)
&& string.IsNullOrEmpty(fallbackName)
&& string.IsNullOrEmpty(base64);
}
public override string ToString()
{
return string.Format("ResourceRef{{name={0}, path={1}, guid={2}}}", name, path, guid);
}
}
/// <summary>
/// Handles serialization and resolution of Unity asset references for chart JSON export/import.
/// Supports Font, TMP_FontAsset, Sprite, Material, Texture2D.
/// </summary>
public static class ResourceRefHandler
{
// ─── Serialize ─────────────────────────────────────────────────────────────
/// <summary>
/// Serializes a Unity Object into a portable ResourceRef.
/// </summary>
public static ResourceRef Serialize(UnityEngine.Object asset, bool includeBase64 = false)
{
if (asset == null) return null;
var refData = new ResourceRef
{
name = asset.name
};
#if UNITY_EDITOR
var assetPath = AssetDatabase.GetAssetPath(asset);
if (!string.IsNullOrEmpty(assetPath))
{
refData.path = assetPath;
refData.guid = AssetDatabase.AssetPathToGUID(assetPath);
}
#endif
// Optional base64 embedding for portability
if (includeBase64)
{
var b64 = TryEncodeBase64(asset);
if (!string.IsNullOrEmpty(b64))
refData.base64 = b64;
}
return refData;
}
/// <summary>
/// Serializes a Font with a fallback name hint.
/// </summary>
public static ResourceRef SerializeFont(Font font, string fallbackName = null)
{
if (font == null) return null;
var refData = Serialize(font) ?? new ResourceRef();
refData.fallbackName = fallbackName ?? "Arial";
return refData;
}
#if dUI_TextMeshPro
/// <summary>
/// Serializes a TMP_FontAsset with a fallback name hint.
/// </summary>
public static ResourceRef SerializeTMPFont(TMP_FontAsset font, string fallbackName = null)
{
if (font == null) return null;
var refData = Serialize(font) ?? new ResourceRef();
refData.fallbackName = fallbackName ?? "LiberationSans SDF";
return refData;
}
#endif
// ─── Resolve ────────────────────────────────────────────────────────────────
/// <summary>
/// Attempts to resolve a ResourceRef back to a Unity asset using the fallback chain:<br/>
/// GUID → path → name → fallbackName → null
/// </summary>
public static T TryResolve<T>(ResourceRef refData) where T : UnityEngine.Object
{
if (refData == null || refData.IsEmpty()) return null;
#if UNITY_EDITOR
// 1. GUID lookup (same project, exact)
if (!string.IsNullOrEmpty(refData.guid))
{
var guidPath = AssetDatabase.GUIDToAssetPath(refData.guid);
if (!string.IsNullOrEmpty(guidPath))
{
var asset = AssetDatabase.LoadAssetAtPath<T>(guidPath);
if (asset != null) return asset;
}
}
// 2. Path-based lookup
if (!string.IsNullOrEmpty(refData.path))
{
var asset = AssetDatabase.LoadAssetAtPath<T>(refData.path);
if (asset != null) return asset;
}
// 3. Name-based search across project
if (!string.IsNullOrEmpty(refData.name))
{
var found = FindAssetByName<T>(refData.name);
if (found != null) return found;
}
#endif
// 4. Resources.Load by name
if (!string.IsNullOrEmpty(refData.name))
{
var asset = Resources.Load<T>(refData.name);
if (asset != null) return asset;
}
// 5. Fallback name
if (!string.IsNullOrEmpty(refData.fallbackName))
{
#if UNITY_EDITOR
var found = FindAssetByName<T>(refData.fallbackName);
if (found != null) return found;
#endif
var asset = Resources.Load<T>(refData.fallbackName);
if (asset != null) return asset;
}
// 6. Base64 decode
if (!string.IsNullOrEmpty(refData.base64))
{
var decoded = TryDecodeBase64<T>(refData.base64, refData.name ?? "imported_asset");
if (decoded != null) return decoded;
}
if (!IsUnityBuiltinDefaultResourceRef(refData))
Debug.LogWarning(string.Format("[XCharts] ResourceRefHandler: Could not resolve asset '{0}'. Using default.", refData));
return null;
}
private static bool IsUnityBuiltinDefaultResourceRef(ResourceRef refData)
{
if (refData == null) return false;
bool pathIsBuiltin = !string.IsNullOrEmpty(refData.path)
&& refData.path.IndexOf("Library/unity default resources", StringComparison.OrdinalIgnoreCase) >= 0;
bool guidIsBuiltin = !string.IsNullOrEmpty(refData.guid)
&& string.Equals(refData.guid, "0000000000000000e000000000000000", StringComparison.OrdinalIgnoreCase);
bool nameIsBuiltinFont = string.Equals(refData.name, "Arial", StringComparison.OrdinalIgnoreCase)
|| string.Equals(refData.fallbackName, "Arial", StringComparison.OrdinalIgnoreCase)
#if dUI_TextMeshPro
|| string.Equals(refData.name, "LiberationSans SDF", StringComparison.OrdinalIgnoreCase)
|| string.Equals(refData.fallbackName, "LiberationSans SDF", StringComparison.OrdinalIgnoreCase)
#endif
;
return pathIsBuiltin || guidIsBuiltin || nameIsBuiltinFont;
}
#if UNITY_EDITOR
private static T FindAssetByName<T>(string assetName) where T : UnityEngine.Object
{
string typeName = typeof(T).Name;
var guids = AssetDatabase.FindAssets(string.Format("{0} t:{1}", assetName, typeName));
foreach (var g in guids)
{
var p = AssetDatabase.GUIDToAssetPath(g);
var asset = AssetDatabase.LoadAssetAtPath<T>(p);
if (asset != null && asset.name == assetName)
return asset;
}
// Looser match (name contains)
foreach (var g in guids)
{
var p = AssetDatabase.GUIDToAssetPath(g);
var asset = AssetDatabase.LoadAssetAtPath<T>(p);
if (asset != null) return asset;
}
return null;
}
#endif
// ─── Base64 helpers ──────────────────────────────────────────────────────────
private static string TryEncodeBase64(UnityEngine.Object asset)
{
var texture = asset as Texture2D;
if (texture != null)
return Convert.ToBase64String(texture.EncodeToPNG());
// Font/Material: not trivially serializable as bytes at runtime; skip.
return null;
}
private static T TryDecodeBase64<T>(string base64, string assetName) where T : UnityEngine.Object
{
try
{
if (typeof(T) == typeof(Texture2D) || typeof(T) == typeof(Sprite))
{
var bytes = Convert.FromBase64String(base64);
var tex = new Texture2D(2, 2);
if (tex.LoadImage(bytes))
{
tex.name = assetName;
if (typeof(T) == typeof(Sprite))
{
var sprite = Sprite.Create(tex,
new Rect(0, 0, tex.width, tex.height),
new Vector2(0.5f, 0.5f));
sprite.name = assetName;
return sprite as T;
}
return tex as T;
}
}
}
catch (Exception ex)
{
Debug.LogWarning(string.Format("[XCharts] ResourceRefHandler: Base64 decode failed: {0}", ex.Message));
}
return null;
}
// ─── Convenience overloads ───────────────────────────────────────────────────
public static Font TryResolveFont(ResourceRef refData)
{
return TryResolve<Font>(refData);
}
public static Sprite TryResolveSprite(ResourceRef refData)
{
return TryResolve<Sprite>(refData);
}
public static Material TryResolveMaterial(ResourceRef refData)
{
return TryResolve<Material>(refData);
}
public static Texture2D TryResolveTexture(ResourceRef refData)
{
return TryResolve<Texture2D>(refData);
}
#if dUI_TextMeshPro
public static TMP_FontAsset TryResolveTMPFont(ResourceRef refData)
{
return TryResolve<TMP_FontAsset>(refData);
}
#endif
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 9c3e7340177cb43e488f9f9547ceea7b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,299 +0,0 @@
using System;
using System.Reflection;
using UnityEngine;
namespace XCharts.Runtime
{
[Serializable]
public class UIComponentJson
{
public string schemaVersion = "1.0";
public string componentType;
public string componentVersion;
public string exportedAt;
public string data;
}
public static class UIComponentJsonSerializer
{
public static string Serialize(UIComponent component, bool prettyPrint = true)
{
if (component == null) throw new ArgumentNullException("component");
var currentJson = JsonUtility.ToJson(component);
var defaultJson = GetDefaultInstanceJson(component.GetType());
var dataJson = BuildDataJson(component.GetType(), currentJson, defaultJson);
var dto = new UIComponentJson
{
schemaVersion = "1.0",
componentType = component.GetType().Name,
componentVersion = XChartsMgr.version,
exportedAt = DateTime.UtcNow.ToString("o"),
data = dataJson
};
var json = JsonUtility.ToJson(dto, prettyPrint);
json = ChartJsonDataFieldCodec.ConvertEscapedDataStringToRawObject(json);
if (prettyPrint)
{
object parsedJson;
if (SimpleJson.TryParse(json, out parsedJson))
json = SimpleJson.Stringify(parsedJson, true);
}
return json;
}
private static string BuildDataJson(Type componentType, string currentJson, string defaultJson)
{
if (componentType == null)
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
if (string.Equals(componentType.Name, "UITable", StringComparison.Ordinal))
return PruneUITableData(componentType, currentJson, defaultJson);
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
}
private static string PruneUITableData(Type tableType, string currentJson, string defaultJson)
{
object currentParsed;
object defaultParsed;
if (!SimpleJson.TryParse(currentJson, out currentParsed))
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
if (!SimpleJson.TryParse(defaultJson, out defaultParsed))
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
var currentRoot = currentParsed as System.Collections.Generic.Dictionary<string, object>;
var defaultRoot = defaultParsed as System.Collections.Generic.Dictionary<string, object>;
if (currentRoot == null)
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
var prunedRootObj = JsonDiffPruner.PruneParsedDefaults(currentRoot, defaultRoot);
var prunedRoot = prunedRootObj as System.Collections.Generic.Dictionary<string, object>;
if (prunedRoot == null)
prunedRoot = new System.Collections.Generic.Dictionary<string, object>();
object currentRowsObj;
if (!currentRoot.TryGetValue("m_Data", out currentRowsObj))
return SimpleJson.Stringify(prunedRoot);
var currentRows = currentRowsObj as System.Collections.Generic.List<object>;
if (currentRows == null)
return SimpleJson.Stringify(prunedRoot);
var rowDefaultObj = CreateDefaultParsedObject(tableType.Assembly, "XCharts.Runtime.UI.TableRow");
if (rowDefaultObj == null)
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
var cellDefaultObj = CreateDefaultParsedObject(tableType.Assembly, "XCharts.Runtime.UI.TableCell");
var prunedRows = new System.Collections.Generic.List<object>(currentRows.Count);
for (int i = 0; i < currentRows.Count; i++)
{
var prunedRow = PruneTableRowKeepCellShape(currentRows[i], rowDefaultObj, cellDefaultObj);
prunedRows.Add(prunedRow ?? new System.Collections.Generic.Dictionary<string, object>());
}
prunedRoot["m_Data"] = prunedRows;
return SimpleJson.Stringify(prunedRoot);
}
private static object PruneTableRowKeepCellShape(object rowObj, object rowDefaultObj, object cellDefaultObj)
{
var rowDict = rowObj as System.Collections.Generic.Dictionary<string, object>;
if (rowDict == null)
return JsonDiffPruner.PruneParsedDefaults(rowObj, rowDefaultObj);
object rawCells;
rowDict.TryGetValue("m_Data", out rawCells);
var currentCells = rawCells as System.Collections.Generic.List<object>;
var prunedRowObj = JsonDiffPruner.PruneParsedDefaults(rowObj, rowDefaultObj);
var prunedRowDict = prunedRowObj as System.Collections.Generic.Dictionary<string, object>;
if (prunedRowDict == null)
prunedRowDict = new System.Collections.Generic.Dictionary<string, object>();
if (currentCells != null)
{
var prunedCells = new System.Collections.Generic.List<object>(currentCells.Count);
for (int i = 0; i < currentCells.Count; i++)
{
var prunedCell = cellDefaultObj != null
? JsonDiffPruner.PruneParsedDefaults(currentCells[i], cellDefaultObj)
: JsonDiffPruner.PruneParsedDefaults(currentCells[i], null);
prunedCells.Add(prunedCell ?? new System.Collections.Generic.Dictionary<string, object>());
}
prunedRowDict["m_Data"] = prunedCells;
}
if (prunedRowDict.Count == 0)
return null;
return prunedRowDict;
}
private static object CreateDefaultParsedObject(Assembly assembly, string fullTypeName)
{
if (assembly == null || string.IsNullOrEmpty(fullTypeName))
return null;
try
{
var type = assembly.GetType(fullTypeName);
if (type == null)
return null;
var instance = Activator.CreateInstance(type);
if (instance == null)
return null;
var json = JsonUtility.ToJson(instance);
object parsed;
if (!SimpleJson.TryParse(json, out parsed))
return null;
return parsed;
}
catch
{
return null;
}
}
private static string GetDefaultInstanceJson(Type type)
{
if (type == null) return "{}";
GameObject tempObject = null;
try
{
if (typeof(MonoBehaviour).IsAssignableFrom(type))
{
tempObject = new GameObject("__XCharts_UIJson_Default__");
tempObject.hideFlags = HideFlags.HideAndDontSave;
var defaultComponent = tempObject.AddComponent(type) as UIComponent;
if (defaultComponent != null)
return JsonUtility.ToJson(defaultComponent);
return "{}";
}
var instance = Activator.CreateInstance(type);
if (instance == null)
return "{}";
return JsonUtility.ToJson(instance);
}
catch
{
return "{}";
}
finally
{
if (tempObject != null)
{
#if UNITY_EDITOR
UnityEngine.Object.DestroyImmediate(tempObject);
#else
UnityEngine.Object.Destroy(tempObject);
#endif
}
}
}
}
public static class UIComponentJsonDeserializer
{
private const string LOG_TAG = "[XCharts] UIComponentJsonDeserializer";
public static void Deserialize(string json, UIComponent target)
{
if (string.IsNullOrEmpty(json)) throw new ArgumentNullException("json");
if (target == null) throw new ArgumentNullException("target");
json = ChartJsonDataFieldCodec.ConvertRawObjectDataToEscapedString(json);
UIComponentJson dto;
try
{
dto = JsonUtility.FromJson<UIComponentJson>(json);
}
catch (Exception ex)
{
throw new ArgumentException(string.Format("Invalid JSON format: {0}", ex.Message), ex);
}
if (dto == null || string.IsNullOrEmpty(dto.schemaVersion))
throw new ArgumentException("JSON does not appear to be a valid XCharts UI component export (missing schemaVersion).");
if (!dto.schemaVersion.StartsWith("1."))
throw new ArgumentException(string.Format("Unsupported schema version '{0}'. This version only supports '1.x'.", dto.schemaVersion));
ValidateComponentType(dto.componentType, target);
if (!string.IsNullOrEmpty(dto.data))
JsonUtility.FromJsonOverwrite(dto.data, target);
target.RefreshAllComponent();
target.RefreshGraph();
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(target);
#endif
Debug.Log(string.Format("{0}: Import complete for '{1}'.", LOG_TAG, target.GetType().Name));
}
private static void ValidateComponentType(string typeName, UIComponent target)
{
if (string.IsNullOrEmpty(typeName) || target == null) return;
var resolved = ResolveType(typeName);
if (resolved == null) return;
if (!resolved.IsAssignableFrom(target.GetType()))
throw new ArgumentException(string.Format("JSON is for UI component '{0}', target is '{1}'.", resolved.Name, target.GetType().Name));
}
private static Type ResolveType(string typeName)
{
if (string.IsNullOrEmpty(typeName)) return null;
var type = Type.GetType(typeName);
if (type != null) return type;
var shortName = typeName.Split(',')[0].Trim();
var simpleName = shortName.Contains(".") ? shortName.Substring(shortName.LastIndexOf(".") + 1) : shortName;
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
type = asm.GetType(shortName);
if (type != null) return type;
var types = asm.GetTypes();
for (int i = 0; i < types.Length; i++)
{
var candidate = types[i];
if (candidate == null) continue;
if (string.Equals(candidate.Name, simpleName, StringComparison.Ordinal))
return candidate;
}
}
catch (ReflectionTypeLoadException rtle)
{
var partialTypes = rtle.Types;
if (partialTypes == null) continue;
for (int i = 0; i < partialTypes.Length; i++)
{
var candidate = partialTypes[i];
if (candidate == null) continue;
if (string.Equals(candidate.Name, simpleName, StringComparison.Ordinal))
return candidate;
}
}
catch
{
}
}
return null;
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 20febe04d2ef346e196ab69da9c1d7d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -140,26 +140,19 @@ namespace XUGL
public static void DrawLine(VertexHelper vh, List<Vector3> points, float width, Color32 color, bool smooth, bool closepath = false)
{
if (points == null || points.Count < 2) return;
// Compact duplicate consecutive points into a reusable buffer to avoid repeated RemoveAt (O(n^2)).
s_CurvesPosList.Clear();
s_CurvesPosList.Add(points[0]);
for (int i = 1; i < points.Count; i++)
for (int i = points.Count - 1; i >= 1; i--)
{
if (!UGLHelper.IsValueEqualsVector3(points[i], points[i - 1]))
s_CurvesPosList.Add(points[i]);
if (UGLHelper.IsValueEqualsVector3(points[i], points[i - 1]))
points.RemoveAt(i);
}
var pts = s_CurvesPosList;
if (pts.Count < 2) return;
else if (pts.Count == 2)
if (points.Count < 2) return;
else if (points.Count <= 2)
{
DrawLine(vh, pts[0], pts[1], width, color);
DrawLine(vh, points[0], points[1], width, color);
}
else if (smooth)
{
DrawCurves(vh, pts, width, color, 2, 2, Direction.XAxis, float.NaN, closepath);
DrawCurves(vh, points, width, color, 2, 2, Direction.XAxis, float.NaN, closepath);
}
else
{
@@ -171,14 +164,14 @@ namespace XUGL
var ibp = Vector3.zero;
var ctp = Vector3.zero;
var cbp = Vector3.zero;
if (closepath && !UGLHelper.IsValueEqualsVector3(pts[pts.Count - 1], pts[0]))
if (closepath && !UGLHelper.IsValueEqualsVector3(points[points.Count - 1], points[0]))
{
pts.Add(pts[0]);
points.Add(points[0]);
}
for (int i = 1; i < pts.Count - 1; i++)
for (int i = 1; i < points.Count - 1; i++)
{
bool bitp = true, bibp = true;
UGLHelper.GetLinePoints(pts[i - 1], pts[i], pts[i + 1], width,
UGLHelper.GetLinePoints(points[i - 1], points[i], points[i + 1], width,
ref ltp, ref lbp,
ref ntp, ref nbp,
ref itp, ref ibp,