using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; using EasyChart; namespace EasyChart.Editor { public partial class EasyChartLibraryWindow { private VisualElement _profilePropertyTracker; private VisualElement _legendSettingsBox; private void UpdateLegendSettingsVisibility() { if (_legendSettingsBox == null) return; bool hasPie = false; bool hasNonPie = false; if (_selectedProfile != null && _selectedProfile.series != null) { for (int i = 0; i < _selectedProfile.series.Count; i++) { var s = _selectedProfile.series[i]; if (s == null) continue; if (s.type == SerieType.Pie || s.type == SerieType.RingChart || s.type == SerieType.Pie3D) { hasPie = true; } else { hasNonPie = true; } } } bool isPurePie = hasPie && !hasNonPie; _legendSettingsBox.style.display = isPurePie ? DisplayStyle.None : DisplayStyle.Flex; } private void OnAnySerializedPropertyChanged(SerializedPropertyChangeEvent evt) { if (_serializedProfile == null) return; if (_isUpdatingPreview) return; if (this == null || rootVisualElement == null) return; if (evt == null) return; if (evt.target == null) return; UpdateLegendSettingsVisibility(); ScheduleUpdatePreview(); } private void OnTrackedProfilePropertyChanged(SerializedProperty _) { if (_serializedProfile == null) return; if (_isUpdatingPreview) return; if (this == null || rootVisualElement == null) return; ScheduleUpdatePreview(); } private void OnTreeSelectionChanged(IEnumerable selectedItems) { var previousProfile = _selectedProfile; var path = selectedItems.FirstOrDefault() as string; if (string.IsNullOrEmpty(path) || AssetDatabase.IsValidFolder(path)) { _selectedFolderPath = string.IsNullOrEmpty(path) ? null : path; _selectedProfile = null; _inspectorContainer.Clear(); _seriesContainer.Clear(); _jsonExampleDirtyByUser = false; UpdateInjectionJsonExample(forceOverwrite: true); return; } var profile = AssetDatabase.LoadAssetAtPath(path); if (profile == null) return; _selectedFolderPath = System.IO.Path.GetDirectoryName(path)?.Replace("\\", "/"); _selectedProfile = profile; _serializedProfile = new SerializedObject(_selectedProfile); _serializedProfile.Update(); if (!ReferenceEquals(previousProfile, _selectedProfile)) { _jsonExampleDirtyByUser = false; UpdateInjectionJsonExample(forceOverwrite: true); } if (_inspectorContainer != null) { _inspectorContainer.UnregisterCallback(OnAnySerializedPropertyChanged, TrickleDown.TrickleDown); _inspectorContainer.Unbind(); _inspectorContainer.Clear(); } if (_seriesContainer != null) { _seriesContainer.UnregisterCallback(OnAnySerializedPropertyChanged, TrickleDown.TrickleDown); _seriesContainer.Unbind(); _seriesContainer.Clear(); } if (_profilePropertyTracker != null) { _profilePropertyTracker.RemoveFromHierarchy(); _profilePropertyTracker = null; } if (_selectedProfile != null) { if (_selectedProfile.EnsureRuntimeData()) { EditorUtility.SetDirty(_selectedProfile); _serializedProfile.Update(); } } Foldout CreateFoldout(string title, string prefsKey, bool defaultValue) { var foldout = new Foldout { text = title }; foldout.value = EditorPrefs.GetBool(prefsKey, defaultValue); foldout.RegisterValueChangedCallback(evt => EditorPrefs.SetBool(prefsKey, evt.newValue)); return foldout; } const string foldoutKeyPrefix = "EasyChart.EasyChartLibraryWindow.GeneralProperties."; var chartSettingsFoldout = CreateFoldout("Chart Settings", foldoutKeyPrefix + "ChartSettings", true); var coordinateSystemFoldout = CreateFoldout("Coordinate System", foldoutKeyPrefix + "CoordinateSystem", true); var axesFoldout = CreateFoldout("Axis Settings", foldoutKeyPrefix + "Axes", true); var gridSettingsFoldout = CreateFoldout("Grid Settings", foldoutKeyPrefix + "GridSettings", true); var hoverSettingsFoldout = CreateFoldout("Hover Settings", foldoutKeyPrefix + "HoverSettings", true); var legendSettingsFoldout = CreateFoldout("Legend Settings", foldoutKeyPrefix + "LegendSettings", true); VisualElement WrapFoldout(Foldout foldout) { var box = CreateGroupBox(); box.Add(foldout); return box; } var chartSettingsBox = WrapFoldout(chartSettingsFoldout); var coordinateSystemBox = WrapFoldout(coordinateSystemFoldout); var axesBox = WrapFoldout(axesFoldout); var gridSettingsBox = WrapFoldout(gridSettingsFoldout); var hoverSettingsBox = WrapFoldout(hoverSettingsFoldout); var legendSettingsBox = WrapFoldout(legendSettingsFoldout); _legendSettingsBox = legendSettingsBox; _inspectorContainer.Add(chartSettingsBox); _inspectorContainer.Add(coordinateSystemBox); _inspectorContainer.Add(axesBox); _inspectorContainer.Add(gridSettingsBox); _inspectorContainer.Add(hoverSettingsBox); _inspectorContainer.Add(legendSettingsBox); _profilePropertyTracker = new VisualElement(); _profilePropertyTracker.style.display = DisplayStyle.None; _inspectorContainer.Add(_profilePropertyTracker); _inspectorContainer.RegisterCallback(OnAnySerializedPropertyChanged, TrickleDown.TrickleDown); _seriesContainer.RegisterCallback(OnAnySerializedPropertyChanged, TrickleDown.TrickleDown); UpdateLegendSettingsVisibility(); var chartWidthProp = _serializedProfile.FindProperty("chartWidth"); if (chartWidthProp != null) _profilePropertyTracker.TrackPropertyValue(chartWidthProp, OnTrackedProfilePropertyChanged); var chartHeightProp = _serializedProfile.FindProperty("chartHeight"); if (chartHeightProp != null) _profilePropertyTracker.TrackPropertyValue(chartHeightProp, OnTrackedProfilePropertyChanged); var coordinateSystemTrackProp = _serializedProfile.FindProperty("coordinateSystem"); if (coordinateSystemTrackProp != null) _profilePropertyTracker.TrackPropertyValue(coordinateSystemTrackProp, OnTrackedProfilePropertyChanged); var chartBackgroundProp = _serializedProfile.FindProperty("background"); if (chartBackgroundProp != null) { var bgFoldout = CreateFoldout("Background", foldoutKeyPrefix + "Background", true); var bgDepth = chartBackgroundProp.depth; var bgChild = chartBackgroundProp.Copy(); if (bgChild.NextVisible(true)) { while (bgChild.depth > bgDepth) { AddBoundPropertyField(bgFoldout, bgChild.Copy()); if (!bgChild.NextVisible(false)) break; } } var bgBox = CreateGroupBox(); bgBox.Add(bgFoldout); chartSettingsFoldout.Add(bgBox); } var chartNameProp = _serializedProfile.FindProperty("chartName"); if (chartNameProp != null) { var chartNameField = new TextField(chartNameProp.displayName) { bindingPath = chartNameProp.propertyPath }; chartSettingsFoldout.Add(chartNameField); chartNameField.Bind(_serializedProfile); void CommitChartNameAndRenameAsset() { if (_selectedProfile == null) return; _serializedProfile.Update(); var desiredName = SanitizeFileName(chartNameProp.stringValue); if (string.IsNullOrWhiteSpace(desiredName)) { var currentPath = AssetDatabase.GetAssetPath(_selectedProfile); var currentFileName = System.IO.Path.GetFileNameWithoutExtension(currentPath); chartNameProp.stringValue = currentFileName; _serializedProfile.ApplyModifiedProperties(); _serializedProfile.Update(); chartNameField.SetValueWithoutNotify(currentFileName); return; } var oldPath = AssetDatabase.GetAssetPath(_selectedProfile); if (string.IsNullOrEmpty(oldPath) || !oldPath.EndsWith(".asset")) return; var currentName = System.IO.Path.GetFileNameWithoutExtension(oldPath); if (string.Equals(currentName, desiredName, System.StringComparison.Ordinal)) { bool anyDirty = false; if (!string.Equals(_selectedProfile.name, desiredName, System.StringComparison.Ordinal)) { _selectedProfile.name = desiredName; anyDirty = true; } if (!string.Equals(_selectedProfile.chartName, desiredName, System.StringComparison.Ordinal)) { _selectedProfile.chartName = desiredName; anyDirty = true; } if (anyDirty) { EditorUtility.SetDirty(_selectedProfile); AssetDatabase.SaveAssets(); _serializedProfile.Update(); chartNameField.SetValueWithoutNotify(desiredName); } return; } string err = AssetDatabase.RenameAsset(oldPath, desiredName); if (!string.IsNullOrEmpty(err)) { EditorUtility.DisplayDialog("Error", err, "OK"); chartNameProp.stringValue = currentName; _serializedProfile.ApplyModifiedProperties(); _serializedProfile.Update(); chartNameField.SetValueWithoutNotify(currentName); return; } var parent = System.IO.Path.GetDirectoryName(oldPath)?.Replace('\\', '/'); var ext = System.IO.Path.GetExtension(oldPath); var newPath = string.IsNullOrEmpty(parent) ? null : $"{parent}/{desiredName}{ext}"; if (!string.Equals(_selectedProfile.name, desiredName, System.StringComparison.Ordinal)) { _selectedProfile.name = desiredName; EditorUtility.SetDirty(_selectedProfile); } if (!string.Equals(_selectedProfile.chartName, desiredName, System.StringComparison.Ordinal)) { _selectedProfile.chartName = desiredName; EditorUtility.SetDirty(_selectedProfile); } AssetDatabase.SaveAssets(); _serializedProfile.Update(); chartNameField.SetValueWithoutNotify(desiredName); if (_folderTree != null) { _folderTree.selectionChanged -= OnTreeSelectionChanged; RefreshTree(); if (!string.IsNullOrEmpty(newPath)) { bool TryFindId(IEnumerable> items, string targetPath, out int foundId) { if (items != null) { foreach (var it in items) { if (string.Equals(it.data, targetPath, System.StringComparison.OrdinalIgnoreCase)) { foundId = it.id; return true; } if (it.children != null && TryFindId(it.children, targetPath, out foundId)) { return true; } } } foundId = -1; return false; } if (TryFindId(_treeRoots, newPath, out var id) && id >= 0) { _folderTree.SetSelection(new[] { id }); } } _folderTree.selectionChanged += OnTreeSelectionChanged; } } chartNameField.RegisterCallback(_ => CommitChartNameAndRenameAsset()); chartNameField.RegisterCallback(evt => { if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter) { CommitChartNameAndRenameAsset(); evt.StopPropagation(); } }); } var coordinateSystemProp = _serializedProfile.FindProperty("coordinateSystem"); PropertyField coordinateSystemField = null; if (coordinateSystemProp != null) { coordinateSystemField = AddBoundPropertyField(coordinateSystemFoldout, coordinateSystemProp); } var cartesianProp = _serializedProfile.FindProperty("cartesian"); var axesProp = _serializedProfile.FindProperty("axes"); var cartesianGridProp = _serializedProfile.FindProperty("cartesianGrid"); var hoverProp = _serializedProfile.FindProperty("hover"); var polarAxesProp = _serializedProfile.FindProperty("polarAxes"); var xAxisIdProp = _serializedProfile.FindProperty("xAxisId"); var yAxisIdProp = _serializedProfile.FindProperty("yAxisId"); var cartesianAxisSelectionContainer = CreateGroupBox(); coordinateSystemFoldout.Add(cartesianAxisSelectionContainer); var cartesianAxesContainer = new VisualElement(); var polarAxesContainer = new VisualElement(); var categoryAxisFoldout = new Foldout { text = "X Axis Setting", value = true }; var valueAxisFoldout = new Foldout { text = "Y Axis Setting", value = true }; var categoryAxisFields = new VisualElement(); var valueAxisFields = new VisualElement(); categoryAxisFields.style.marginLeft = 8; valueAxisFields.style.marginLeft = 8; categoryAxisFoldout.Add(categoryAxisFields); valueAxisFoldout.Add(valueAxisFields); var categoryAxisBox = CreateGroupBox(); categoryAxisBox.Add(categoryAxisFoldout); cartesianAxesContainer.Add(categoryAxisBox); var valueAxisBox = CreateGroupBox(); valueAxisBox.Add(valueAxisFoldout); cartesianAxesContainer.Add(valueAxisBox); var angleAxisFoldout = new Foldout { text = "Angle Axis Setting", value = true }; var radiusAxisFoldout = new Foldout { text = "Radius Axis Setting", value = true }; var angleAxisFields = new VisualElement(); var radiusAxisFields = new VisualElement(); angleAxisFields.style.marginLeft = 8; radiusAxisFields.style.marginLeft = 8; angleAxisFoldout.Add(angleAxisFields); radiusAxisFoldout.Add(radiusAxisFields); var angleAxisBox = CreateGroupBox(); angleAxisBox.Add(angleAxisFoldout); polarAxesContainer.Add(angleAxisBox); var radiusAxisBox = CreateGroupBox(); radiusAxisBox.Add(radiusAxisFoldout); polarAxesContainer.Add(radiusAxisBox); axesFoldout.Add(cartesianAxesContainer); axesFoldout.Add(polarAxesContainer); AxisId GetXAxisId() { if (xAxisIdProp == null) return AxisId.XBottom; return (AxisId)xAxisIdProp.enumValueIndex; } AxisId GetYAxisId() { if (yAxisIdProp == null) return AxisId.YLeft; return (AxisId)yAxisIdProp.enumValueIndex; } var xAxisChoices = new List { AxisId.XBottom, AxisId.XTop }; var yAxisChoices = new List { AxisId.YLeft, AxisId.YRight }; var xAxisPopup = new PopupField("X Axis", xAxisChoices, GetXAxisId()); var yAxisPopup = new PopupField("Y Axis", yAxisChoices, GetYAxisId()); cartesianAxisSelectionContainer.Add(xAxisPopup); cartesianAxisSelectionContainer.Add(yAxisPopup); void ApplyAxisSelection(AxisId newX, AxisId newY) { if (_serializedProfile == null) return; if (cartesianAxisSelectionContainer.panel == null) return; if (xAxisIdProp != null) xAxisIdProp.enumValueIndex = (int)newX; if (yAxisIdProp != null) yAxisIdProp.enumValueIndex = (int)newY; if (_serializedProfile != null && _serializedProfile.hasModifiedProperties) _serializedProfile.ApplyModifiedProperties(); if (_selectedProfile != null) { if (_selectedProfile.EnsureAxesIntegrity()) { EditorUtility.SetDirty(_selectedProfile); } } _serializedProfile.Update(); RefreshActiveAxesUI(); ScheduleUpdatePreview(); } xAxisPopup.RegisterValueChangedCallback(evt => { if (_serializedProfile == null) return; if (cartesianAxisSelectionContainer.panel == null) return; ApplyAxisSelection(evt.newValue, yAxisPopup.value); }); yAxisPopup.RegisterValueChangedCallback(evt => { if (_serializedProfile == null) return; if (cartesianAxisSelectionContainer.panel == null) return; ApplyAxisSelection(xAxisPopup.value, evt.newValue); }); SerializedProperty FindAxisElement(SerializedProperty listProp, AxisId id) { if (listProp == null || !listProp.isArray) return null; for (int i = 0; i < listProp.arraySize; i++) { var el = listProp.GetArrayElementAtIndex(i); var idProp = el.FindPropertyRelative("id"); if (idProp != null && idProp.enumValueIndex == (int)id) return el; } return null; } void RefreshActiveAxesUI() { categoryAxisFields.Unbind(); valueAxisFields.Unbind(); categoryAxisFields.Clear(); valueAxisFields.Clear(); if (axesProp == null) return; SerializedProperty catEl = FindAxisElement(axesProp, GetXAxisId()); SerializedProperty valEl = FindAxisElement(axesProp, GetYAxisId()); if ((catEl == null || valEl == null) && _selectedProfile != null) { if (_serializedProfile.hasModifiedProperties) _serializedProfile.ApplyModifiedProperties(); if (_selectedProfile.EnsureAxesIntegrity()) { EditorUtility.SetDirty(_selectedProfile); } _serializedProfile.Update(); catEl = FindAxisElement(axesProp, GetXAxisId()); valEl = FindAxisElement(axesProp, GetYAxisId()); } SerializedProperty EnsureAxisElement(SerializedProperty listProp, AxisId id, AxisType axisType) { var el = FindAxisElement(listProp, id); if (el != null) return el; if (listProp == null || !listProp.isArray) return null; int idx = listProp.arraySize; listProp.InsertArrayElementAtIndex(idx); var created = listProp.GetArrayElementAtIndex(idx); if (created == null) return null; var idProp = created.FindPropertyRelative("id"); if (idProp != null) idProp.enumValueIndex = (int)id; var dimProp = created.FindPropertyRelative("axisType"); if (dimProp != null) dimProp.enumValueIndex = (int)axisType; return created; } if (catEl == null) { catEl = EnsureAxisElement(axesProp, GetXAxisId(), AxisType.Category); if (_serializedProfile != null && _serializedProfile.hasModifiedProperties) _serializedProfile.ApplyModifiedProperties(); } if (valEl == null) { valEl = EnsureAxisElement(axesProp, GetYAxisId(), AxisType.Value); if (_serializedProfile != null && _serializedProfile.hasModifiedProperties) _serializedProfile.ApplyModifiedProperties(); } void BuildAxisConfigUI(VisualElement parent, SerializedProperty axisEl) { if (axisEl == null) return; var labelsProp = axisEl.FindPropertyRelative("labels"); SerializedProperty Prop(string name) => axisEl.FindPropertyRelative(name); void AddProp(string name) { var p = Prop(name); if (p == null) return; AddBoundPropertyField(parent, p); } AddProp("axisType"); AddProp("visible"); AddProp("color"); AddProp("width"); var labelBox = CreateGroupBox(); if (labelsProp != null) { var pf = new PropertyField(labelsProp, "LabelTexts"); if (_serializedProfile != null) pf.Bind(_serializedProfile); pf.RegisterCallback(evt => { if (_serializedProfile == null) return; if (pf.panel == null) return; ScheduleUpdatePreview(); }); labelBox.Add(pf); } var labelStyleProp = Prop("labelStyle"); if (labelStyleProp != null) AddBoundPropertyField(labelBox, labelStyleProp); var labelPlacementProp = Prop("labelPlacement"); if (labelPlacementProp != null) AddBoundPropertyField(labelBox, labelPlacementProp); AddLabelFormatDropdown(labelBox, Prop("labelFormat")); parent.Add(labelBox); { var rangeContainer = new VisualElement(); parent.Add(rangeContainer); var autoRangeMinProp = Prop("autoRangeMin"); var autoRangeMaxProp = Prop("autoRangeMax"); var autoRangeRoundingProp = Prop("autoRangeRounding"); var autoRangeUnitProp = Prop("autoRangeUnit"); var minValueProp = Prop("minValue"); var maxValueProp = Prop("maxValue"); if (autoRangeMinProp != null) { var minContainer = new VisualElement(); minContainer.style.marginLeft = 8; AddToggleContainer(rangeContainer, autoRangeMinProp, minContainer, false); if (minValueProp != null) AddBoundPropertyField(minContainer, minValueProp); } else { if (minValueProp != null) AddBoundPropertyField(rangeContainer, minValueProp); } if (autoRangeMaxProp != null) { var maxContainer = new VisualElement(); maxContainer.style.marginLeft = 8; AddToggleContainer(rangeContainer, autoRangeMaxProp, maxContainer, false); if (maxValueProp != null) AddBoundPropertyField(maxContainer, maxValueProp); } else { if (maxValueProp != null) AddBoundPropertyField(rangeContainer, maxValueProp); } if (autoRangeRoundingProp != null) { AddBoundPropertyField(rangeContainer, autoRangeRoundingProp); } if (autoRangeRoundingProp != null && autoRangeUnitProp != null) { var unitField = AddBoundPropertyField(rangeContainer, autoRangeUnitProp); if (unitField != null) { unitField.style.marginLeft = 8; void UpdateAutoRangeUnitVisibility() { if (_serializedProfile == null) return; if (rangeContainer.panel == null) return; if (_serializedProfile.hasModifiedProperties) _serializedProfile.ApplyModifiedProperties(); _serializedProfile.Update(); bool show = autoRangeRoundingProp.enumValueIndex == 4; unitField.style.display = show ? DisplayStyle.Flex : DisplayStyle.None; } UpdateAutoRangeUnitVisibility(); unitField.RegisterCallback(evt => { UpdateAutoRangeUnitVisibility(); ScheduleUpdatePreview(); }); } } } var axisTypeProp = Prop("axisType"); var ticksRoot = new VisualElement(); parent.Add(ticksRoot); var categoryRoot = new VisualElement(); parent.Add(categoryRoot); var autoTicksProp = Prop("autoTicks"); if (autoTicksProp != null) { var ticksContainer = new VisualElement(); AddToggleContainer(ticksRoot, autoTicksProp, ticksContainer, false); var splitProp = Prop("splitCount"); if (splitProp != null) { // Label changes based on axis type var splitField = new PropertyField(splitProp); if (_serializedProfile != null) splitField.Bind(_serializedProfile); splitField.RegisterCallback(evt => { if (_serializedProfile == null) return; if (splitField.panel == null) return; ScheduleUpdatePreview(); }); ticksContainer.Add(splitField); // Update label based on axis type void UpdateSplitLabel() { if (axisTypeProp == null) return; bool isCategory = axisTypeProp.enumValueIndex == (int)AxisType.Category; splitField.label = isCategory ? "VisibleCount" : "Split Count"; } UpdateSplitLabel(); splitField.RegisterCallback(_ => UpdateSplitLabel()); parent.RegisterCallback(evt => UpdateSplitLabel()); } } var categoryAutoScrollProp = Prop("categoryAutoScroll"); var categorySmoothScrollProp = Prop("categorySmoothScroll"); var categoryScrollIntervalProp = Prop("categoryScrollInterval"); var categoryScrollStepProp = Prop("categoryScrollStep"); if (categoryAutoScrollProp != null) { var scrollDetails = new VisualElement(); scrollDetails.style.marginLeft = 8; AddToggleContainer(categoryRoot, categoryAutoScrollProp, scrollDetails, true); if (categorySmoothScrollProp != null) AddBoundPropertyField(scrollDetails, categorySmoothScrollProp); if (categoryScrollIntervalProp != null) AddBoundPropertyField(scrollDetails, categoryScrollIntervalProp); if (categoryScrollStepProp != null) AddBoundPropertyField(scrollDetails, categoryScrollStepProp); } var showUnitProp = Prop("showUnit"); var unitTextProp = Prop("unitText"); var unitLabelStyleProp = Prop("unitLabelStyle"); var unitRoot = new VisualElement(); parent.Add(unitRoot); var unitDetails = new VisualElement(); if (showUnitProp != null) { AddToggleContainer(unitRoot, showUnitProp, unitDetails, true); } else { unitRoot.Add(unitDetails); } if (unitTextProp != null) AddBoundPropertyField(unitDetails, unitTextProp); if (unitLabelStyleProp != null) AddBoundPropertyField(unitDetails, unitLabelStyleProp); void UpdateUnitVisibility() { if (_serializedProfile == null) return; if (unitRoot.panel == null) return; if (_serializedProfile.hasModifiedProperties) _serializedProfile.ApplyModifiedProperties(); _serializedProfile.Update(); bool isValue = axisTypeProp == null || axisTypeProp.enumValueIndex == (int)AxisType.Value; unitRoot.style.display = isValue ? DisplayStyle.Flex : DisplayStyle.None; } void UpdateAxisTypeVisibility() { if (_serializedProfile == null) return; if (parent.panel == null) return; if (_serializedProfile.hasModifiedProperties) _serializedProfile.ApplyModifiedProperties(); _serializedProfile.Update(); bool isCategory = axisTypeProp != null && axisTypeProp.enumValueIndex == (int)AxisType.Category; categoryRoot.style.display = isCategory ? DisplayStyle.Flex : DisplayStyle.None; // ticksRoot (autoTicks) is now visible for both Category and Value axis types ticksRoot.style.display = DisplayStyle.Flex; } UpdateUnitVisibility(); unitRoot.RegisterCallback(_ => UpdateUnitVisibility()); unitRoot.RegisterCallback(evt => { UpdateUnitVisibility(); ScheduleUpdatePreview(); }); UpdateAxisTypeVisibility(); parent.RegisterCallback(_ => UpdateAxisTypeVisibility()); parent.RegisterCallback(evt => { UpdateAxisTypeVisibility(); }); parent.RegisterCallback(evt => { UpdateUnitVisibility(); }); } if (catEl != null) { BuildAxisConfigUI(categoryAxisFields, catEl); } if (valEl != null) { BuildAxisConfigUI(valueAxisFields, valEl); } } void RefreshPolarAxesUI() { angleAxisFields.Unbind(); radiusAxisFields.Unbind(); angleAxisFields.Clear(); radiusAxisFields.Clear(); if (polarAxesProp == null) return; SerializedProperty angleAxisProp = polarAxesProp.FindPropertyRelative("angleAxis"); SerializedProperty radiusAxisProp = polarAxesProp.FindPropertyRelative("radiusAxis"); void BuildPolarAxisUI(VisualElement parent, SerializedProperty axisStyle) { if (axisStyle == null) return; SerializedProperty Prop(string name) => axisStyle.FindPropertyRelative(name); void AddProp(string name) { var p = Prop(name); if (p == null) return; AddBoundPropertyField(parent, p); } AddProp("labels"); AddProp("visible"); AddProp("color"); AddProp("width"); AddProp("labelStyle"); AddLabelFormatDropdown(parent, Prop("labelFormat")); { var rangeContainer = new VisualElement(); parent.Add(rangeContainer); var autoRangeMinProp = Prop("autoRangeMin"); var autoRangeMaxProp = Prop("autoRangeMax"); var autoRangeRoundingProp = Prop("autoRangeRounding"); var autoRangeUnitProp = Prop("autoRangeUnit"); var minValueProp = Prop("minValue"); var maxValueProp = Prop("maxValue"); if (autoRangeMinProp != null) { var minContainer = new VisualElement(); minContainer.style.marginLeft = 8; AddToggleContainer(rangeContainer, autoRangeMinProp, minContainer, false); if (minValueProp != null) AddBoundPropertyField(minContainer, minValueProp); } else { if (minValueProp != null) AddBoundPropertyField(rangeContainer, minValueProp); } if (autoRangeMaxProp != null) { var maxContainer = new VisualElement(); maxContainer.style.marginLeft = 8; AddToggleContainer(rangeContainer, autoRangeMaxProp, maxContainer, false); if (maxValueProp != null) AddBoundPropertyField(maxContainer, maxValueProp); } else { if (maxValueProp != null) AddBoundPropertyField(rangeContainer, maxValueProp); } if (autoRangeRoundingProp != null) { AddBoundPropertyField(rangeContainer, autoRangeRoundingProp); } if (autoRangeRoundingProp != null && autoRangeUnitProp != null) { var unitField = AddBoundPropertyField(rangeContainer, autoRangeUnitProp); if (unitField != null) { unitField.style.marginLeft = 8; void UpdateAutoRangeUnitVisibility() { if (_serializedProfile == null) return; if (rangeContainer.panel == null) return; if (_serializedProfile.hasModifiedProperties) _serializedProfile.ApplyModifiedProperties(); _serializedProfile.Update(); bool show = autoRangeRoundingProp.enumValueIndex == 4; unitField.style.display = show ? DisplayStyle.Flex : DisplayStyle.None; } UpdateAutoRangeUnitVisibility(); unitField.RegisterCallback(evt => { UpdateAutoRangeUnitVisibility(); ScheduleUpdatePreview(); }); } } } var autoTicksProp = Prop("autoTicks"); if (autoTicksProp != null) { var ticksContainer = new VisualElement(); var autoTicksField = AddToggleContainer(parent, autoTicksProp, ticksContainer, false); var splitProp = Prop("splitCount"); if (splitProp != null) { AddBoundPropertyField(ticksContainer, splitProp); } } } BuildPolarAxisUI(angleAxisFields, angleAxisProp); BuildPolarAxisUI(radiusAxisFields, radiusAxisProp); } var cartesianGridContainer = new VisualElement(); gridSettingsFoldout.Add(cartesianGridContainer); if (cartesianGridProp != null) { SerializedProperty Rel(string name) => cartesianGridProp.FindPropertyRelative(name); var xGridColor = Rel("xGridColor"); var xGridWidth = Rel("xGridLineWidth"); var yGridColor = Rel("yGridColor"); var yGridWidth = Rel("yGridLineWidth"); if (xGridColor != null) AddBoundPropertyField(cartesianGridContainer, xGridColor); if (xGridWidth != null) AddBoundPropertyField(cartesianGridContainer, xGridWidth); if (yGridColor != null) AddBoundPropertyField(cartesianGridContainer, yGridColor); if (yGridWidth != null) AddBoundPropertyField(cartesianGridContainer, yGridWidth); var xDashBox = new Box(); xDashBox.style.marginTop = 4; cartesianGridContainer.Add(xDashBox); xDashBox.Add(new Label("X Grid Dashed")); var xDashed = Rel("xGridDashed"); var xDashLen = Rel("xGridDashLength"); var xDashGap = Rel("xGridDashGap"); var xDashOff = Rel("xGridDashOffset"); if (xDashed != null) AddBoundPropertyField(xDashBox, xDashed); if (xDashLen != null) AddBoundPropertyField(xDashBox, xDashLen); if (xDashGap != null) AddBoundPropertyField(xDashBox, xDashGap); if (xDashOff != null) AddBoundPropertyField(xDashBox, xDashOff); var yDashBox = new Box(); yDashBox.style.marginTop = 4; cartesianGridContainer.Add(yDashBox); yDashBox.Add(new Label("Y Grid Dashed")); var yDashed = Rel("yGridDashed"); var yDashLen = Rel("yGridDashLength"); var yDashGap = Rel("yGridDashGap"); var yDashOff = Rel("yGridDashOffset"); if (yDashed != null) AddBoundPropertyField(yDashBox, yDashed); if (yDashLen != null) AddBoundPropertyField(yDashBox, yDashLen); if (yDashGap != null) AddBoundPropertyField(yDashBox, yDashGap); if (yDashOff != null) AddBoundPropertyField(yDashBox, yDashOff); } var hoverContainer = new VisualElement(); hoverSettingsFoldout.Add(hoverContainer); if (hoverProp != null) { SerializedProperty RelHover(string name) => hoverProp.FindPropertyRelative(name); var hoverBox = new Box(); hoverContainer.Add(hoverBox); hoverBox.Add(new Label("Cursor Line")); var cColor = RelHover("cursorLineColor"); var cWidth = RelHover("cursorLineWidth"); var cDashed = RelHover("cursorLineDashed"); var cDashLen = RelHover("cursorLineDashLength"); var cDashGap = RelHover("cursorLineDashGap"); var cDashOff = RelHover("cursorLineDashOffset"); if (cColor != null) AddBoundPropertyField(hoverBox, cColor); if (cWidth != null) AddBoundPropertyField(hoverBox, cWidth); if (cDashed != null) AddBoundPropertyField(hoverBox, cDashed); if (cDashLen != null) AddBoundPropertyField(hoverBox, cDashLen); if (cDashGap != null) AddBoundPropertyField(hoverBox, cDashGap); if (cDashOff != null) AddBoundPropertyField(hoverBox, cDashOff); } void UpdateCoordinateSpecificVisibility() { if (coordinateSystemProp == null) return; // 0 = Cartesian2D, 1 = Polar2D, 2 = None bool isCartesian = coordinateSystemProp.enumValueIndex == 0; bool isPolar = coordinateSystemProp.enumValueIndex == 1; bool isNone = coordinateSystemProp.enumValueIndex == 2; // Cartesian axis selection only for Cartesian2D cartesianAxisSelectionContainer.style.display = isCartesian ? DisplayStyle.Flex : DisplayStyle.None; // Cartesian axes only for Cartesian2D cartesianAxesContainer.style.display = isCartesian ? DisplayStyle.Flex : DisplayStyle.None; // Polar axes only for Polar2D polarAxesContainer.style.display = isPolar ? DisplayStyle.Flex : DisplayStyle.None; // Axis Settings box: hide for None coordinate system (no axes needed) axesBox.style.display = isNone ? DisplayStyle.None : DisplayStyle.Flex; // Grid settings only for Cartesian2D cartesianGridContainer.style.display = isCartesian ? DisplayStyle.Flex : DisplayStyle.None; gridSettingsBox.style.display = isCartesian ? DisplayStyle.Flex : DisplayStyle.None; // Hover settings (cursor line) only for Cartesian2D hoverSettingsBox.style.display = isCartesian ? DisplayStyle.Flex : DisplayStyle.None; } UpdateCoordinateSpecificVisibility(); if (coordinateSystemField != null) { coordinateSystemField.RegisterCallback(evt => { if (_serializedProfile == null) return; if (coordinateSystemField.panel == null) return; UpdateCoordinateSpecificVisibility(); ScheduleRefreshSeriesList(); }); } RefreshActiveAxesUI(); RefreshPolarAxesUI(); var iterator = _serializedProfile.GetIterator(); bool enterChildren = true; while (iterator.NextVisible(enterChildren)) { if (iterator.name == "m_Script" || iterator.name == "series") { enterChildren = false; continue; } if (iterator.name == "chartName" || iterator.name == "chartId" || iterator.name == "coordinateSystem" || iterator.name == "cartesian" || iterator.name == "axes" || iterator.name == "axisSelectionInitialized" || iterator.name == "xAxisId" || iterator.name == "yAxisId" || iterator.name == "xGridColor" || iterator.name == "xGridLineWidth" || iterator.name == "yGridColor" || iterator.name == "yGridLineWidth" || iterator.name == "cartesianGrid" || iterator.name == "hover" || iterator.name == "polarAxes" || iterator.name == "gridSettingsInitialized" || iterator.name == "hoverSettingsInitialized") { enterChildren = false; continue; } if (_legendSettingsBox != null && _legendSettingsBox.style.display == DisplayStyle.None && iterator.name == "legendSettings") { enterChildren = false; continue; } if (iterator.name == "background") { enterChildren = false; continue; } VisualElement parent; switch (iterator.name) { case "animationDuration": case "padding": parent = chartSettingsFoldout; break; case "xGridColor": case "xGridLineWidth": case "yGridColor": case "yGridLineWidth": parent = gridSettingsFoldout; break; case "legendSettings": parent = legendSettingsFoldout; break; default: parent = chartSettingsFoldout; break; } AddBoundPropertyField(parent, iterator.Copy()); enterChildren = false; } ScheduleRefreshSeriesList(); ScheduleUpdatePreview(); } } }