using System; using System.Collections.Generic; using UnityEngine; namespace EasyChart { public enum CoordinateSystemType { Cartesian2D, Polar2D, None } public enum AxisId { XBottom, XTop, YLeft, YRight } public enum AxisType { Category, Value } public enum CategoryLabelPlacement { Tick, CellCenter } [System.Serializable] public class CartesianMapping { public AxisId xAxisId = AxisId.XBottom; public AxisId yAxisId = AxisId.YLeft; } [System.Serializable] public class PlotLayoutSettings { public Vector2 centerOffset = Vector2.zero; public float padding = 10f; public float labelRadialOffset = 0f; } [System.Serializable] public class LabelStyleSettings { public bool enabled = true; public int fontSize = 10; public Color color = Color.white; public LabelPosition position = LabelPosition.Outside; public Vector2 offset = Vector2.zero; } [System.Serializable] public class AxisConfig { public AxisId id; public AxisType axisType = AxisType.Value; public List labels = new List(); public bool visible = true; public Color color = Color.white; public float width = 2.0f; public LabelStyleSettings labelStyle; public bool showLabels = true; public int fontSize = 10; public Color labelColor = Color.white; public LabelPosition labelPosition = LabelPosition.Outside; [InspectorName("LabelPlacement")] public CategoryLabelPlacement labelPlacement = CategoryLabelPlacement.Tick; public Vector2 labelOffset = Vector2.zero; public bool autoRangeMin = true; public bool autoRangeMax = true; public AutoRangeRoundingMode autoRangeRounding = AutoRangeRoundingMode.Tens; public float autoRangeUnit = 1f; public float minValue = 0f; public float maxValue = 1f; public bool autoTicks = true; public int splitCount = 5; public string labelFormat; public bool categoryAutoScroll = false; public bool categorySmoothScroll = true; public float categoryScrollInterval = 1.0f; public int categoryScrollStep = 1; public bool showUnit = false; public string unitText; public LabelStyleSettings unitLabelStyle; } public enum AutoRangeRoundingMode { None, Integer, Tens, Hundreds, Custom } public enum SerieType { // 折线图:适合展示随时间/类别变化的趋势(连续变化)。常用度:★★★★★ 难度:★★☆☆☆ Line = 0, // 柱状图:适合展示分类对比(离散类别),强调数量大小。常用度:★★★★★ 难度:★★☆☆☆ Bar = 1, // 饼图:适合展示占比构成(总量=100%),不适合类别过多。常用度:★★★★☆ 难度:★★★☆☆ Pie = 2, // 散点图:适合展示二维数据分布、相关性(x-y 点)。常用度:★★★★☆ 难度:★★★☆☆ Scatter = 4, // 雷达图:适合展示多维指标对比(同一维度尺度下的多项指标)。常用度:★★★☆☆ 难度:★★★★☆ Radar = 5, // 环形图:饼图的变体,可在中心留空用于显示总量/标题等。常用度:★★★☆☆ 难度:★★★☆☆ RingChart = 6, // 热力图:适合展示二维矩阵强度/密度(例如日历热力、相关矩阵等)。常用度:★★★☆☆ 难度:★★★★☆ Heatmap = 7, // 仪表盘:展示单一指标当前值/区间(类似速度表)。常用度:★★☆☆☆ 难度:★★★☆☆ Gauge = 8, // 横向柱状图:与柱状图类似,但横向布局更适合长标签类别。常用度:★★★★☆ 难度:★★☆☆☆ HorizontalBar = 9, // K 线图:金融蜡烛图(开/高/低/收),用于行情走势。常用度:★★☆☆☆ 难度:★★★★★ Candlestick = 21, // OHLC 图:与 K 线类似,用线段表示开高低收(更简洁)。常用度:★★☆☆☆ 难度:★★★★★ OHLC = 22, // 箱线图:展示分布统计(四分位数、中位数、异常值)。常用度:★★☆☆☆ 难度:★★★★★ BoxPlot = 23, // 直方图:展示单变量分布(分箱计数)。常用度:★★★☆☆ 难度:★★★☆☆ Histogram = 24, // 瀑布图:展示从起点到终点的增减构成(正负贡献)。常用度:★★☆☆☆ 难度:★★★★☆ Waterfall = 25, // 漏斗图:展示流程转化/阶段漏损(例如注册→下单→支付)。常用度:★★☆☆☆ 难度:★★★☆☆ Funnel = 26, // 桑基图:展示流量/能量/资金流向与分配(有向流图)。常用度:★☆☆☆☆ 难度:★★★★★(不适合用当前 SeriesData,需图结构/边数据,先注释) // Sankey = 30, // 矩形树图:用矩形面积展示层级与占比(适合层级占比对比)。常用度:★☆☆☆☆ 难度:★★★★★(不适合用当前 SeriesData,需层级节点数据,先注释) // TreeMap = 31, // 旭日图:环形层级占比图(层级结构的构成展示)。常用度:★☆☆☆☆ 难度:★★★★★(不适合用当前 SeriesData,需层级节点数据,先注释) // Sunburst = 32, // 弦图:展示类别之间的关联/流量(成对关系强度)。常用度:★☆☆☆☆ 难度:★★★★★(不适合用当前 SeriesData,需矩阵/边数据,先注释) // Chord = 33, // 3D 饼图:饼图的三维表现形式(通常属于 Pro 扩展)。常用度:★☆☆☆☆ 难度:★★★★★ Pie3D = 100, // 3D 柱状图:三维柱形对比(可能用于二维分类 + 数值)。常用度:★☆☆☆☆ 难度:★★★★★ Bar3D = 101, // 3D 折线图:三维折线/轨迹展示(z 维度叠加)。常用度:★☆☆☆☆ 难度:★★★★★ Line3D = 102, // 3D 散点图:三维坐标点分布(x,y,z)。常用度:★☆☆☆☆ 难度:★★★★★ Scatter3D = 103, } [System.Serializable] public abstract class BaseSerieSettings { } [System.Serializable] public class PointSettings : ISerializationCallbackReceiver { public bool show = true; public float size = 4.0f; [InspectorName("Texture")] public TextureFillSettings textureFill = new TextureFillSettings(); public void OnBeforeSerialize() { if (textureFill == null) textureFill = new TextureFillSettings(); } public void OnAfterDeserialize() { if (textureFill == null) textureFill = new TextureFillSettings(); } } [System.Serializable] public class TextureMappingSettings { public Texture2D texture; public Vector2 tiling = Vector2.one; public Vector2 offset = Vector2.zero; } public enum TextureFillAnimationType { None = 0, TextureUvMove = 1, TextureScale = 2, TextureFade = 3 } public enum TextureFillScaleType { ZoomIn = 0, ZoomOut = 1, Sin = 2, PingPong = 3 } public enum TextureFillColorOverLifeWrap { Loop = 0, PingPong = 1, Clamp = 2 } [System.Serializable] public class TextureFillSettings : ISerializationCallbackReceiver { public Texture2D texture; public Vector2 tiling = Vector2.one; public Vector2 offset = Vector2.zero; public Color color = Color.white; public TextureFillAnimationType animationType = TextureFillAnimationType.None; public Vector2 uvMoveSpeed = Vector2.zero; public TextureFillScaleType scaleType = TextureFillScaleType.Sin; public float scaleSpeed = 1f; public Vector2 scaleFrom = Vector2.one; public Vector2 scaleTo = new Vector2(1.2f, 1.2f); public Color colorFadeStart = Color.white; public Color colorFadeEnd = new Color(1f, 1f, 1f, 0f); public float colorFadeSpeed = 1f; public TextureFillColorOverLifeWrap colorFadeWrap = TextureFillColorOverLifeWrap.PingPong; public Gradient colorFadeGradient; public void OnBeforeSerialize() { } public void OnAfterDeserialize() { if (colorFadeGradient == null) { colorFadeGradient = new Gradient(); colorFadeGradient.SetKeys( new[] { new GradientColorKey(colorFadeStart, 0f), new GradientColorKey(colorFadeEnd, 1f) }, new[] { new GradientAlphaKey(colorFadeStart.a, 0f), new GradientAlphaKey(colorFadeEnd.a, 1f) } ); } } } [System.Serializable] public class SeriesData { public string id; public float x; public float value; public float y; public float z; public string name; public bool useColor = false; public Color color = Color.white; } [System.Serializable] public class SizeMappingSettings { public bool enabled = false; public float minValue = 0f; public float maxValue = 1f; public float minSize = 4f; public float maxSize = 12f; public bool clamp = true; public AnimationCurve curve = AnimationCurve.Linear(0, 0, 1, 1); } [System.Serializable] public class HoverHighlightSettings : ISerializationCallbackReceiver { public bool enabled = false; [SerializeField, HideInInspector] private float _scale = 1.5f; [SerializeField, HideInInspector] private TextureFillSettings _textureFill = new TextureFillSettings { color = new Color(1f, 1f, 1f, 0.35f) }; public float scale { get => _scale; set => _scale = value; } public TextureFillSettings textureFill { get => _textureFill; set => _textureFill = value; } public float pickRadius = 8f; public PointSettings point = new PointSettings(); public Color lineColor = new Color(1f, 1f, 1f, 0.35f); public void OnBeforeSerialize() { if (_textureFill == null) _textureFill = new TextureFillSettings { color = new Color(1f, 1f, 1f, 0.35f) }; if (point == null) point = new PointSettings(); } public void OnAfterDeserialize() { if (_textureFill == null) _textureFill = new TextureFillSettings { color = new Color(1f, 1f, 1f, 0.35f) }; if (point == null) point = new PointSettings(); } } [System.Serializable] public class BarHoverSettings : ISerializationCallbackReceiver { public bool enabled = false; public float pickRadius = 8f; public float scale = 1.5f; public TextureFillSettings textureFill = new TextureFillSettings { color = new Color(1f, 1f, 1f, 0.35f) }; public void OnBeforeSerialize() { if (textureFill == null) textureFill = new TextureFillSettings { color = new Color(1f, 1f, 1f, 0.35f) }; } public void OnAfterDeserialize() { if (textureFill == null) textureFill = new TextureFillSettings { color = new Color(1f, 1f, 1f, 0.35f) }; } } [System.Serializable] public class LineStrokeSettings { public LineType lineType = LineType.Straight; public float width = 2.0f; public Color color = Color.white; [InspectorName("Texture")] public TextureFillSettings textureFill = new TextureFillSettings(); } [System.Serializable] public class AreaFillSettings : ISerializationCallbackReceiver { public bool show = false; [InspectorName("Texture")] public TextureFillSettings textureFill = new TextureFillSettings { color = new Color(1, 1, 1, 0.2f) }; public void OnBeforeSerialize() { if (textureFill == null) textureFill = new TextureFillSettings { color = new Color(1, 1, 1, 0.2f) }; } public void OnAfterDeserialize() { if (textureFill == null) textureFill = new TextureFillSettings { color = new Color(1, 1, 1, 0.2f) }; } } [System.Serializable] public class BorderSettings { public float width = 0f; public Color color = new Color(0, 0, 0, 0); } [System.Serializable] public class BackgroundSettings : ISerializationCallbackReceiver { public bool show = false; [InspectorName("Texture")] public TextureFillSettings textureFill = new TextureFillSettings { color = new Color(1, 1, 1, 0.08f) }; public void OnBeforeSerialize() { if (textureFill == null) textureFill = new TextureFillSettings { color = new Color(1, 1, 1, 0.08f) }; } public void OnAfterDeserialize() { if (textureFill == null) textureFill = new TextureFillSettings { color = new Color(1, 1, 1, 0.08f) }; } } [System.Serializable] public class LineSettings : BaseSerieSettings { [Header("Stroke")] [InspectorName("Stroke")] public LineStrokeSettings stroke = new LineStrokeSettings(); [Header("Point")] public PointSettings point = new PointSettings(); [Header("Hover")] public HoverHighlightSettings hover = new HoverHighlightSettings(); [Header("Area")] public AreaFillSettings area = new AreaFillSettings(); } [System.Serializable] public class ScatterSettings : BaseSerieSettings { [Header("Point")] public PointSettings point = new PointSettings(); public SizeMappingSettings sizeMapping = new SizeMappingSettings(); public HoverHighlightSettings hover = new HoverHighlightSettings(); } [System.Serializable] public class HeatmapSettings : BaseSerieSettings { public enum RenderMode { [Tooltip("Discrete cell-based rendering with sharp boundaries")] Grid, [Tooltip("Smooth pixel-based rendering using Gaussian interpolation")] Gradient, [Tooltip("Contour lines with optional filled regions")] Contour } public enum InfluenceMode { [Tooltip("No influence spreading between cells")] None, [Tooltip("Values bleed into neighboring cells with falloff")] Bleed, [Tooltip("Gaussian smoothing applied to the grid")] Smooth } [System.Serializable] public class BleedSettings { [Tooltip("Number of cells the influence spreads to")] public int radiusCells = 1; [Tooltip("Number of discrete falloff steps")] public int steps = 6; [Tooltip("Strength of the bleed effect (0-1)")] [Range(0f, 1f)] public float strength = 0.35f; } [System.Serializable] public class SmoothSettings { [Tooltip("Radius of the smoothing kernel in cells")] public int radiusCells = 1; [Tooltip("Number of smoothing passes to apply")] public int passes = 1; [Tooltip("Standard deviation of the Gaussian kernel")] public float sigma = 1f; } [System.Serializable] public class GradientSettings { [Tooltip("Texture resolution for gradient rendering (higher = smoother but slower)")] public int resolution = 256; [Tooltip("Gaussian kernel sigma for density estimation (larger = smoother blending)")] public float sigma = 30f; [Tooltip("Contrast adjustment: higher values increase contrast (sharper peaks), lower values spread colors more evenly")] public float intensity = 1f; } [System.Serializable] public class ContourSettings { [Tooltip("Number of contour levels to generate")] public int levels = 10; [Tooltip("Width of contour lines in pixels")] public float lineWidth = 1f; [Tooltip("Show filled regions between contour lines")] public bool showFill = true; [Tooltip("Show contour lines")] public bool showLines = true; [Tooltip("Color of the contour lines")] public Color lineColor = new Color(0.2f, 0.2f, 0.2f, 0.8f); } [Tooltip("Rendering mode: Grid (discrete cells), Gradient (smooth interpolation), or Contour (isolines)")] public RenderMode renderMode = RenderMode.Grid; [Tooltip("Gap between cells in pixels (Grid mode only)")] public float cellGapPx = 1f; [Tooltip("Number of horizontal cells when using Value axis type")] public int xSplitCount = 10; [Tooltip("Number of vertical cells when using Value axis type")] public int ySplitCount = 10; [Tooltip("Automatically calculate value range from data")] public bool autoRange = true; [Tooltip("Minimum value for color mapping (when autoRange is off)")] public float minValue = 0f; [Tooltip("Maximum value for color mapping (when autoRange is off)")] public float maxValue = 1f; [Tooltip("How cell values influence neighboring cells (Grid mode only)")] public InfluenceMode influenceMode = InfluenceMode.None; [Tooltip("Settings for Bleed influence mode")] public BleedSettings bleed = new BleedSettings(); [Tooltip("Settings for Smooth influence mode")] public SmoothSettings smooth = new SmoothSettings(); [Tooltip("Settings for Gradient render mode")] public GradientSettings gradient = new GradientSettings(); [Tooltip("Settings for Contour render mode")] public ContourSettings contour = new ContourSettings(); [Tooltip("Clamp values to min/max range before color mapping")] public bool clamp = true; [Tooltip("Color for minimum values")] public Color lowColor = new Color(0f, 0.55f, 1f, 1f); [Tooltip("Color for middle values (used for interpolation)")] public Color midColor = Color.white; [Tooltip("Color for maximum values")] public Color highColor = new Color(1f, 0.25f, 0.25f, 1f); } [System.Serializable] public class RadarSettings : BaseSerieSettings { [System.Serializable] public class RadarLayoutSettings { public float startAngleDeg = -90f; public bool clockwise = true; public float innerRadius = 0f; public float outerRadius = 0f; public PlotLayoutSettings plot = new PlotLayoutSettings(); public TextureFillSettings background = new TextureFillSettings { color = new Color(0.2f, 0.2f, 0.2f, 0.3f) }; } [Header("Radar")] public RadarLayoutSettings radar = new RadarLayoutSettings(); [Header("LineStroke")] [InspectorName("LineStroke")] public LineStrokeSettings stroke = new LineStrokeSettings(); [Header("Area")] public AreaFillSettings area = new AreaFillSettings { show = true, textureFill = new TextureFillSettings { color = new Color(1f, 1f, 1f, 0.2f) } }; [Header("Point")] public PointSettings point = new PointSettings(); } [System.Serializable] public class BarSettings : BaseSerieSettings, ISerializationCallbackReceiver { public float barWidth = 10f; [InspectorName("Texture")] public TextureFillSettings textureFill = new TextureFillSettings(); public bool stacked = false; public float cornerRadius = 0f; public int cornerSegments = 4; public string stackGroup = ""; public float barGap = 2f; public float categoryGap = 0f; public BorderSettings border = new BorderSettings(); public BackgroundSettings background = new BackgroundSettings(); public BarHoverSettings hover = new BarHoverSettings(); public void OnBeforeSerialize() { if (textureFill == null) textureFill = new TextureFillSettings(); } public void OnAfterDeserialize() { if (textureFill == null) textureFill = new TextureFillSettings(); if (hover == null) hover = new BarHoverSettings(); hover.OnAfterDeserialize(); } } public enum SliceGapType { Radial, Translate, Uniform } [System.Serializable] public class PieLayoutSettings { public float startAngleDeg = -90f; public bool clockwise = true; public float angleRangeDeg = 360f; public float innerRadius = 0f; public Color innerRadiusColor = new Color(0, 0, 0, 0); public float cornerRadius = 0f; public float sliceGapPx = 0f; public SliceGapType sliceGapType = SliceGapType.Radial; public float outerRadius = 0f; public PlotLayoutSettings plot = new PlotLayoutSettings(); } [System.Serializable] public class RingSettings { public bool showBackground = false; public float backgroundAlpha = 0.25f; public Color backgroundColor = new Color(0, 0, 0, 0); public float cornerRadius = 0f; public float ringGapPx = 0f; } public enum PieLegendSource { Slice, RingSlice, Series } public enum ValueDisplayStyle { None, Parentheses, AlignedColumns } [System.Serializable] public class PieLegendSettings { [HideInInspector] public bool overrideEnabled = false; public PieLegendSource source = PieLegendSource.Slice; public bool enabled = true; [HideInInspector] public bool showValue = false; public ValueDisplayStyle valueDisplayStyle = ValueDisplayStyle.None; public LegendPosition position = LegendPosition.Bottom; public int fontSize = 12; public Color color = Color.white; public Color backgroundColor = new Color(0, 0, 0, 0); public float itemSpacing = 10f; public Vector2 offset = Vector2.zero; } [System.Serializable] public class PieSettings : BaseSerieSettings { public bool sortByValue = false; public PieLayoutSettings layout = new PieLayoutSettings(); public PieHoverSettings hover = new PieHoverSettings(); public PieAggregationSettings aggregation = new PieAggregationSettings(); public RingSettings ring = new RingSettings(); public PieLegendSettings legend = new PieLegendSettings(); } [System.Serializable] public class RingChartLayoutSettings { public float startAngleDeg = -90f; public bool clockwise = true; public float angleRangeDeg = 360f; public float innerRadius = 0.3f; public float outerRadius = 0f; public PlotLayoutSettings plot = new PlotLayoutSettings(); } public enum RingValueMappingMode { Percent, Range } [System.Serializable] public class RingValueMappingSettings { public RingValueMappingMode mode = RingValueMappingMode.Percent; public bool autoRange = true; public float minValue = 0f; public float maxValue = 1f; } [System.Serializable] public class RingChartSettings : BaseSerieSettings { public RingChartLayoutSettings layout = new RingChartLayoutSettings(); public RingValueMappingSettings valueMapping = new RingValueMappingSettings(); public PieHoverSettings hover = new PieHoverSettings(); public PieLegendSettings legend = new PieLegendSettings(); public bool showBackground = false; public float backgroundAlpha = 0.25f; public Color backgroundColor = new Color(0, 0, 0, 0); public float cornerRadius = 0f; public float ringGapPx = 0f; } public enum PieExplodeType { Translate, Pull, Color, Stroke } [System.Serializable] public class PieHoverSettings { public bool enabled = false; public float explodeDistance = 10f; public PieExplodeType explodeType = PieExplodeType.Translate; } [System.Serializable] public class PieAggregationSettings { public bool enabled = false; public int keepTopN = 0; public string othersName = "Others"; public bool useOthersColor = false; public Color othersColor = new Color(0.6f, 0.6f, 0.6f, 1f); } [System.Serializable] public class SerieLabelSettings { public bool enabled = true; public Color color = Color.white; public int fontSize = 10; public int decimalPlaces = 2; public bool showName = false; // For Pie: show category name public Vector2 offset = Vector2.zero; public LabelPosition position = LabelPosition.Outside; public TextureFillSettings background = new TextureFillSettings { color = new Color(0f, 0f, 0f, 0f) }; } public enum LegendPosition { Top, Bottom, Left, Right } [System.Serializable] public class LegendSettings : ISerializationCallbackReceiver { public bool enabled = true; public LegendPosition position = LegendPosition.Bottom; public int fontSize = 12; public Color color = Color.white; public Color backgroundColor = new Color(0, 0, 0, 0); public float itemSpacing = 10f; [Tooltip("Offset from edge. Auto-set based on position when zero.")] public Vector2 offset = new Vector2(0, -30); [System.NonSerialized] private LegendPosition _lastPosition = LegendPosition.Bottom; [System.NonSerialized] private bool _initialized; public void OnBeforeSerialize() { } public void OnAfterDeserialize() { if (!_initialized) { _lastPosition = position; _initialized = true; } } public void SyncOffsetToPosition() { if (_lastPosition != position) { offset = GetDefaultOffsetForPosition(position); _lastPosition = position; } } public static Vector2 GetDefaultOffsetForPosition(LegendPosition pos) { switch (pos) { case LegendPosition.Top: return new Vector2(0, 30); case LegendPosition.Bottom: return new Vector2(0, -30); case LegendPosition.Right: return new Vector2(-30, 0); case LegendPosition.Left: return new Vector2(30, 0); default: return Vector2.zero; } } } [System.Serializable] public class PlotSettings { public bool overrideTheme = false; public Color backgroundColor = new Color(0f, 0f, 0f, 0f); public Color borderColor = new Color(0f, 0f, 0f, 0f); public float borderWidth = 0f; [Tooltip("Edge fade distance in pixels for series labels. Labels outside PlotArea will fade to transparent over this distance.")] public float labelEdgeFadePx = 20f; } [System.Serializable] public class CartesianGridSettings { public Color xGridColor = new Color(0.5f, 0.5f, 0.5f, 0.2f); public float xGridLineWidth = 1.0f; public bool xGridDashed = false; public float xGridDashLength = 4f; public float xGridDashGap = 2f; public float xGridDashOffset = 0f; public Color yGridColor = new Color(0.5f, 0.5f, 0.5f, 0.2f); public float yGridLineWidth = 1.0f; public bool yGridDashed = false; public float yGridDashLength = 4f; public float yGridDashGap = 2f; public float yGridDashOffset = 0f; } [System.Serializable] public class HoverSettings { public bool cursorLineDashed = false; public float cursorLineWidth = 1f; public Color cursorLineColor = Color.yellow; public float cursorLineDashLength = 4f; public float cursorLineDashGap = 2f; public float cursorLineDashOffset = 0f; } [System.Serializable] public class PolarAxisStyle { public List labels = new List(); public bool visible = true; public Color color = Color.white; public Color edgeColor = Color.white; public float width = 2.0f; public LabelStyleSettings labelStyle; public bool showLabels = true; public int fontSize = 10; public Color labelColor = Color.white; public LabelPosition labelPosition = LabelPosition.Outside; public Vector2 labelOffset = Vector2.zero; public bool autoRangeMin = true; public bool autoRangeMax = true; public AutoRangeRoundingMode autoRangeRounding = AutoRangeRoundingMode.Tens; public float autoRangeUnit = 1f; public float minValue = 0f; public float maxValue = 1f; public bool autoTicks = true; public int splitCount = 5; public string labelFormat; } [System.Serializable] public class PolarAxesSettings { public PolarAxisStyle angleAxis = new PolarAxisStyle(); public PolarAxisStyle radiusAxis = new PolarAxisStyle(); } [System.Serializable] public class ChartData { public CoordinateSystemType CoordinateSystem = CoordinateSystemType.Cartesian2D; public CartesianMapping Cartesian = new CartesianMapping(); public List Axes = new List(); // New axis fields (X/Y based) public AxisId XAxisId = AxisId.XBottom; public AxisId YAxisId = AxisId.YLeft; public CartesianGridSettings CartesianGrid = new CartesianGridSettings(); public HoverSettings Hover = new HoverSettings(); public PolarAxesSettings PolarAxes = new PolarAxesSettings(); public PlotSettings Plot = new PlotSettings(); public List Series = new List(); public LegendSettings legend = new LegendSettings(); public float animationDuration = 1.0f; } public enum LineType { Straight, Smooth, Step } public enum LabelPosition { Outside, Inside, Center } [System.Serializable] public class Serie : ISerializationCallbackReceiver { public string name; public string id; public SerieType type = SerieType.Line; public bool visible = true; [SerializeReference] public BaseSerieSettings settings; [SerializeField, SerializeReference] private BaseSerieSettings _cachedLineSettings; [SerializeField, SerializeReference] private BaseSerieSettings _cachedBarSettings; [SerializeField, SerializeReference] private BaseSerieSettings _cachedPieSettings; [SerializeField, SerializeReference] private BaseSerieSettings _cachedRingChartSettings; [SerializeField, SerializeReference] private BaseSerieSettings _cachedScatterSettings; [SerializeField, SerializeReference] private BaseSerieSettings _cachedRadarSettings; [SerializeField, SerializeReference] private BaseSerieSettings _cachedHeatmapSettings; public SerieLabelSettings labelSettings = new SerieLabelSettings(); // Actual Data public List seriesData = new List(); public void OnBeforeSerialize() { } public void OnAfterDeserialize() { } public bool SetType(SerieType newType) { bool changed = false; // Preserve current settings into a per-type cache before switching. // IMPORTANT: in editor workflows the enum `type` might already be changed // (via SerializedObject.ApplyModifiedProperties) before SetType is called, // so we cache based on the runtime type of `settings`, not the current `type`. if (settings != null) { if (settings is LineSettings) _cachedLineSettings = settings; else if (settings is BarSettings) _cachedBarSettings = settings; else if (settings is PieSettings) _cachedPieSettings = settings; else if (settings is ScatterSettings) _cachedScatterSettings = settings; else if (settings is RadarSettings) _cachedRadarSettings = settings; else if (settings is HeatmapSettings) _cachedHeatmapSettings = settings; } if (type != newType) { type = newType; changed = true; } // Minimal migration / preservation for common edits. // Data loss is acceptable per design, but keeping point settings improves UX. if (newType == SerieType.Scatter) { if (settings is ScatterSettings) { // ok } else if (_cachedScatterSettings is ScatterSettings cached) { settings = cached; changed = true; } else if (settings is LineSettings ls) { if (ls.point == null) ls.point = new PointSettings(); settings = new ScatterSettings { point = ls.point, sizeMapping = new SizeMappingSettings(), hover = new HoverHighlightSettings() }; changed = true; } else { settings = new ScatterSettings(); changed = true; } } else if (newType == SerieType.Line) { if (settings is LineSettings) { // ok } else if (_cachedLineSettings is LineSettings cached) { settings = cached; changed = true; } else if (settings is ScatterSettings ss) { if (ss.point == null) ss.point = new PointSettings(); settings = new LineSettings { point = ss.point, stroke = new LineStrokeSettings(), area = new AreaFillSettings() }; changed = true; } else { settings = new LineSettings(); changed = true; } } else if (newType == SerieType.Bar || newType == SerieType.HorizontalBar) { if (settings is BarSettings) { // ok } else if (_cachedBarSettings is BarSettings cached) { settings = cached; changed = true; } else { settings = new BarSettings(); changed = true; } } else if (newType == SerieType.Pie) { if (settings is PieSettings) { // ok } else if (_cachedPieSettings is PieSettings cached) { settings = cached; changed = true; } else { settings = new PieSettings(); changed = true; } } else if (newType == SerieType.RingChart) { if (settings is RingChartSettings) { // ok } else if (_cachedRingChartSettings is RingChartSettings cachedRing) { settings = cachedRing; changed = true; } else { settings = new RingChartSettings(); changed = true; } if (settings is RingChartSettings ringSettings) { if (ringSettings.layout == null) { ringSettings.layout = new RingChartLayoutSettings(); changed = true; } if (ringSettings.layout != null && ringSettings.layout.innerRadius <= 0f) { ringSettings.layout.innerRadius = 0.3f; changed = true; } } } else if (newType == SerieType.Pie3D) { if (SerieSettingsRegistry.TryCreate(newType, out var created)) { if (settings is PieSettings oldPs && created is PieSettings newPs && created.GetType() != settings.GetType()) { if (oldPs.layout != null) newPs.layout = oldPs.layout; if (oldPs.hover != null) newPs.hover = oldPs.hover; if (oldPs.aggregation != null) newPs.aggregation = oldPs.aggregation; if (oldPs.ring != null) newPs.ring = oldPs.ring; if (oldPs.legend != null) newPs.legend = oldPs.legend; settings = newPs; changed = true; } else if (!(settings is PieSettings)) { settings = created; changed = true; } } else { settings = null; changed = true; } } else if (newType == SerieType.Radar) { if (settings is RadarSettings) { // ok } else if (_cachedRadarSettings is RadarSettings cached) { settings = cached; changed = true; } else { settings = new RadarSettings(); changed = true; } } else if (newType == SerieType.Heatmap) { if (settings is HeatmapSettings) { // ok } else if (_cachedHeatmapSettings is HeatmapSettings cached) { settings = cached; changed = true; } else { settings = new HeatmapSettings(); changed = true; } } else { if (SerieSettingsRegistry.TryCreate(newType, out var created)) { settings = created; changed = true; } else { settings = null; changed = true; } } if (EnsureIntegrity()) changed = true; return changed; } public bool EnsureIntegrity() { bool changed = false; if (string.IsNullOrEmpty(id)) { id = Guid.NewGuid().ToString("N"); changed = true; } var before = settings; EnsureSettings(); if (!ReferenceEquals(before, settings)) changed = true; if (labelSettings == null) { labelSettings = new SerieLabelSettings(); changed = true; } if (labelSettings != null) { if (labelSettings.fontSize <= 0) { labelSettings.fontSize = 10; changed = true; } if (labelSettings.decimalPlaces < 0 || labelSettings.decimalPlaces > 8) { labelSettings.decimalPlaces = Mathf.Clamp(labelSettings.decimalPlaces, 0, 8); changed = true; } var c = labelSettings.color; bool looksUninitialized = Mathf.Approximately(c.r, 0f) && Mathf.Approximately(c.g, 0f) && Mathf.Approximately(c.b, 0f) && Mathf.Approximately(c.a, 0f); if (looksUninitialized) { labelSettings.color = Color.white; changed = true; } } if (seriesData == null) { seriesData = new List(); changed = true; } for (int i = 0; i < seriesData.Count; i++) { var dp = seriesData[i]; if (dp == null) continue; if (string.IsNullOrEmpty(dp.id)) { dp.id = Guid.NewGuid().ToString("N"); changed = true; } } if (settings is LineSettings ls) { if (ls.stroke == null) { ls.stroke = new LineStrokeSettings(); changed = true; } if (ls.point == null) { ls.point = new PointSettings(); changed = true; } if (ls.area == null) { ls.area = new AreaFillSettings(); changed = true; } } else if (settings is ScatterSettings ss) { if (ss.point == null) { ss.point = new PointSettings(); changed = true; } if (ss.sizeMapping == null) { ss.sizeMapping = new SizeMappingSettings(); changed = true; } if (ss.hover == null) { ss.hover = new HoverHighlightSettings(); changed = true; } } else if (settings is BarSettings bs) { if (bs.border == null) { bs.border = new BorderSettings(); changed = true; } if (bs.background == null) { bs.background = new BackgroundSettings(); changed = true; } if (bs.hover == null) { bs.hover = new BarHoverSettings(); changed = true; } } else if (settings is PieSettings ps) { if (ps.layout == null) { ps.layout = new PieLayoutSettings(); changed = true; } if (ps.hover == null) { ps.hover = new PieHoverSettings(); changed = true; } if (ps.aggregation == null) { ps.aggregation = new PieAggregationSettings(); changed = true; } if (ps.ring == null) { ps.ring = new RingSettings(); changed = true; } if (ps.legend == null) { ps.legend = new PieLegendSettings(); changed = true; } if (ps.legend != null && ps.legend.valueDisplayStyle == ValueDisplayStyle.None && ps.legend.showValue) { ps.legend.valueDisplayStyle = ValueDisplayStyle.Parentheses; ps.legend.showValue = false; changed = true; } } else if (settings is RadarSettings rs) { if (rs.radar == null) { rs.radar = new RadarSettings.RadarLayoutSettings(); changed = true; } if (rs.radar != null && rs.radar.plot == null) { rs.radar.plot = new PlotLayoutSettings(); changed = true; } if (rs.stroke == null) { rs.stroke = new LineStrokeSettings(); changed = true; } if (rs.area == null) { rs.area = new AreaFillSettings(); changed = true; } if (rs.point == null) { rs.point = new PointSettings(); changed = true; } } else if (settings is HeatmapSettings hs) { if (hs.cellGapPx < 0f) { hs.cellGapPx = 0f; changed = true; } } return changed; } public void EnsureSettings() { bool needCreate = settings == null; if (!needCreate) { switch (type) { case SerieType.Line: needCreate = !(settings is LineSettings); break; case SerieType.Scatter: needCreate = !(settings is ScatterSettings); break; case SerieType.Bar: case SerieType.HorizontalBar: needCreate = !(settings is BarSettings); break; case SerieType.Pie: needCreate = !(settings is PieSettings); break; case SerieType.RingChart: needCreate = !(settings is RingChartSettings); break; case SerieType.Pie3D: if (SerieSettingsRegistry.HasFactory(SerieType.Pie3D)) { if (!(settings is PieSettings)) { needCreate = true; } else if (SerieSettingsRegistry.TryCreate(SerieType.Pie3D, out var created3D)) { needCreate = created3D != null && created3D.GetType() != settings.GetType(); } else { needCreate = false; } } else needCreate = false; break; case SerieType.Radar: needCreate = !(settings is RadarSettings); break; case SerieType.Heatmap: needCreate = !(settings is HeatmapSettings); break; default: if (SerieSettingsRegistry.HasFactory(type)) { if (settings == null) { needCreate = true; } else if (SerieSettingsRegistry.TryCreate(type, out var createdAny)) { needCreate = createdAny != null && createdAny.GetType() != settings.GetType(); } else { needCreate = false; } } break; } } // If the type enum changed (or data was migrated) before we got a chance to cache, // avoid losing the previous settings instance by stashing it into the matching cache. if (needCreate && settings != null) { if (settings is LineSettings) _cachedLineSettings = settings; else if (settings is BarSettings) _cachedBarSettings = settings; else if (settings is RingChartSettings) _cachedRingChartSettings = settings; else if (settings is PieSettings) _cachedPieSettings = settings; else if (settings is ScatterSettings) _cachedScatterSettings = settings; else if (settings is RadarSettings) _cachedRadarSettings = settings; else if (settings is HeatmapSettings) _cachedHeatmapSettings = settings; } if (needCreate) { switch (type) { case SerieType.Line: settings = _cachedLineSettings is LineSettings cachedLine ? cachedLine : new LineSettings(); break; case SerieType.Scatter: settings = _cachedScatterSettings is ScatterSettings cachedScatter ? cachedScatter : new ScatterSettings(); break; case SerieType.Bar: case SerieType.HorizontalBar: settings = _cachedBarSettings is BarSettings cachedBar ? cachedBar : new BarSettings(); break; case SerieType.Pie: settings = _cachedPieSettings is PieSettings cachedPie ? cachedPie : new PieSettings(); break; case SerieType.RingChart: settings = _cachedRingChartSettings is RingChartSettings cachedRing ? cachedRing : new RingChartSettings(); break; case SerieType.Pie3D: if (SerieSettingsRegistry.TryCreate(type, out var created3D)) { if (created3D is PieSettings createdPie && _cachedPieSettings is PieSettings cachedPieFor3D) { if (cachedPieFor3D.layout != null) createdPie.layout = cachedPieFor3D.layout; if (cachedPieFor3D.hover != null) createdPie.hover = cachedPieFor3D.hover; if (cachedPieFor3D.aggregation != null) createdPie.aggregation = cachedPieFor3D.aggregation; if (cachedPieFor3D.ring != null) createdPie.ring = cachedPieFor3D.ring; } settings = created3D; } else settings = null; break; case SerieType.Radar: settings = _cachedRadarSettings is RadarSettings cachedRadar ? cachedRadar : new RadarSettings(); break; case SerieType.Heatmap: settings = _cachedHeatmapSettings is HeatmapSettings cachedHeatmap ? cachedHeatmap : new HeatmapSettings(); break; default: if (SerieSettingsRegistry.TryCreate(type, out var createdAny)) { settings = createdAny; } else { settings = null; } break; } } if (type == SerieType.Scatter && settings is ScatterSettings ss) { if (ss.point == null) ss.point = new PointSettings(); if (ss.sizeMapping == null) ss.sizeMapping = new SizeMappingSettings(); if (ss.hover == null) ss.hover = new HoverHighlightSettings(); } if (type == SerieType.Line && settings is LineSettings ls) { if (ls.stroke == null) ls.stroke = new LineStrokeSettings(); if (ls.point == null) ls.point = new PointSettings(); if (ls.area == null) ls.area = new AreaFillSettings(); } if (type == SerieType.Bar && settings is BarSettings bs) { if (bs.border == null) bs.border = new BorderSettings(); if (bs.background == null) bs.background = new BackgroundSettings(); } if (type == SerieType.Pie && settings is PieSettings ps) { if (ps.layout == null) ps.layout = new PieLayoutSettings(); if (ps.hover == null) ps.hover = new PieHoverSettings(); if (ps.aggregation == null) ps.aggregation = new PieAggregationSettings(); if (ps.ring == null) ps.ring = new RingSettings(); } if (type == SerieType.RingChart && settings is RingChartSettings rcs) { if (rcs.layout == null) rcs.layout = new RingChartLayoutSettings(); if (rcs.hover == null) rcs.hover = new PieHoverSettings(); if (rcs.legend == null) rcs.legend = new PieLegendSettings(); } if (type == SerieType.Pie3D && settings is PieSettings ps3d) { if (ps3d.layout == null) ps3d.layout = new PieLayoutSettings(); if (ps3d.hover == null) ps3d.hover = new PieHoverSettings(); if (ps3d.aggregation == null) ps3d.aggregation = new PieAggregationSettings(); if (ps3d.ring == null) ps3d.ring = new RingSettings(); } if (type == SerieType.Radar && settings is RadarSettings rs) { if (rs.stroke == null) rs.stroke = new LineStrokeSettings(); if (rs.point == null) rs.point = new PointSettings(); } if (type == SerieType.Heatmap && settings is HeatmapSettings hs) { if (hs.cellGapPx < 0f) hs.cellGapPx = 0f; } if (seriesData == null) seriesData = new List(); } } }