using System.Collections.Generic; using EasyChart; using EasyChart.UGUI; using UnityEngine; public class URadarChartController : MonoBehaviour { [Header("Chart")] public UGUIChartBridge chartBridge; public bool rebuildOnEnable = true; public bool showReferenceSeries = false; public int enforcedSortOrder = 0; [Header("Axis Count")] [Min(3)] public int axisCount = 6; [Header("Series Names")] public string mainSeriesName = "Player"; public string referenceSeriesName = "Reference"; [Header("Axes")] public List axes = new List { new URadarAxisEntry { label = "A", value = 72f, referenceValue = 60f }, new URadarAxisEntry { label = "B", value = 55f, referenceValue = 60f }, new URadarAxisEntry { label = "C", value = 68f, referenceValue = 60f }, new URadarAxisEntry { label = "D", value = 49f, referenceValue = 60f }, new URadarAxisEntry { label = "E", value = 80f, referenceValue = 60f }, new URadarAxisEntry { label = "F", value = 61f, referenceValue = 60f } }; [Header("Range")] public float minValue = 0f; public float maxValue = 100f; public int splitCount = 5; [Header("Grid")] public bool overrideGridStyle = false; public Color gridLineColor = new Color32(255, 255, 255, 180); public Color outerGridLineColor = Color.white; public float gridLineWidth = 2f; [Header("Labels")] public bool overrideLabelStyle = false; public bool overrideLabelFont = false; public Font axisLabelFont; public int axisLabelFontSize = 18; public Color axisLabelColor = Color.white; public Vector2 axisLabelOffset = Vector2.zero; [Header("Tooltip")] public bool overrideTooltipStyle = false; public bool overrideTooltipFont = false; public Font tooltipFont; public int tooltipFontSize = 18; public Color tooltipTextColor = Color.white; public Color tooltipBackgroundColor = new Color(0f, 0f, 0f, 0.85f); public Color tooltipBorderColor = new Color(1f, 1f, 1f, 0f); [Min(0f)] public float tooltipBorderWidth = 0f; [Min(0f)] public float tooltipCornerRadius = 4f; public Vector4 tooltipPadding = new Vector4(8f, 8f, 4f, 4f); [Header("Style")] public bool overrideSeriesStyle = true; public Color mainStrokeColor = new Color32(82, 126, 255, 255); public Color mainFillColor = new Color(82f / 255f, 126f / 255f, 255f / 255f, 0.28f); public Color mainPointColor = Color.white; public Color referenceStrokeColor = new Color32(72, 229, 229, 255); public Color referenceFillColor = new Color(72f / 255f, 229f / 255f, 229f / 255f, 0.22f); public Color referencePointColor = Color.white; public float radarPlotPadding = 8f; [Min(0f)] public float radarInnerRadius = 0f; [Min(0f)] public float radarLabelRadialOffset = 0f; private ChartProfile sourceProfile; private ChartProfile runtimeProfile; private ChartTheme runtimeTheme; private bool rebuildQueued; private void Awake() { showReferenceSeries = false; NormalizeAxes(); QueueRebuild(); } private void OnEnable() { showReferenceSeries = false; NormalizeAxes(); if (rebuildOnEnable) { QueueRebuild(); } } private void LateUpdate() { if (rebuildQueued) { TryRebuild(); } } private void OnValidate() { showReferenceSeries = false; NormalizeAxes(); axisLabelFontSize = Mathf.Max(1, axisLabelFontSize); tooltipFontSize = Mathf.Max(1, tooltipFontSize); gridLineWidth = Mathf.Max(0.1f, gridLineWidth); radarInnerRadius = Mathf.Max(0f, radarInnerRadius); radarLabelRadialOffset = Mathf.Max(0f, radarLabelRadialOffset); tooltipBorderWidth = Mathf.Max(0f, tooltipBorderWidth); tooltipCornerRadius = Mathf.Max(0f, tooltipCornerRadius); axisCount = Mathf.Max(3, axisCount); QueueRebuild(); } private void OnDestroy() { if (runtimeProfile != null) { DestroyImmediate(runtimeProfile); runtimeProfile = null; } if (runtimeTheme != null) { DestroyImmediate(runtimeTheme); runtimeTheme = null; } } public void QueueRebuild() { rebuildQueued = true; } [ContextMenu("Rebuild Radar Chart")] public void RebuildNow() { QueueRebuild(); TryRebuild(); } public void SetAxisLabel(int index, string label) { if (!TryGetAxis(index, out URadarAxisEntry axis)) { return; } axis.label = string.IsNullOrWhiteSpace(label) ? $"Axis {index + 1}" : label.Trim(); QueueRebuild(); } public void SetAxisValue(int index, float value) { if (!TryGetAxis(index, out URadarAxisEntry axis)) { return; } axis.value = Mathf.Clamp(value, minValue, maxValue); QueueRebuild(); } public void SetReferenceAxisValue(int index, float value) { if (!TryGetAxis(index, out URadarAxisEntry axis)) { return; } axis.referenceValue = Mathf.Clamp(value, minValue, maxValue); QueueRebuild(); } public void SetAxisData(int index, string label, float value, float referenceValueToSet) { NormalizeAxes(); if (index < 0 || index >= axes.Count) { return; } URadarAxisEntry axis = axes[index]; if (axis == null) { axis = new URadarAxisEntry(); axes[index] = axis; } axis.label = string.IsNullOrWhiteSpace(label) ? $"Axis {index + 1}" : label.Trim(); axis.value = Mathf.Clamp(value, minValue, maxValue); axis.referenceValue = Mathf.Clamp(referenceValueToSet, minValue, maxValue); QueueRebuild(); } public void NormalizeAxesForExternalUse() { NormalizeAxes(); } private bool TryGetAxis(int index, out URadarAxisEntry axis) { NormalizeAxes(); if (index < 0 || index >= axes.Count) { axis = null; return false; } axis = axes[index]; return axis != null; } private void TryRebuild() { if (chartBridge == null || chartBridge.Profile == null) { return; } if (sourceProfile == null) { sourceProfile = chartBridge.Profile; } if (sourceProfile == null) { return; } EnsureRuntimeProfile(); EnsureBridgePriority(); ConfigureRuntimeProfile(); chartBridge.Refresh(); ApplyRuntimeTheme(); rebuildQueued = false; } private void EnsureRuntimeProfile() { if (runtimeProfile != null) { if (!ReferenceEquals(chartBridge.Profile, runtimeProfile)) { chartBridge.Profile = runtimeProfile; } return; } runtimeProfile = Instantiate(sourceProfile); runtimeProfile.name = sourceProfile.name + "_URadar_Runtime"; runtimeProfile.hideFlags = HideFlags.DontSave; chartBridge.Profile = runtimeProfile; } private void EnsureBridgePriority() { if (chartBridge != null && chartBridge.SortOrder < enforcedSortOrder) { chartBridge.SortOrder = enforcedSortOrder; } } private void ConfigureRuntimeProfile() { NormalizeAxes(); runtimeProfile.coordinateSystem = CoordinateSystemType.Polar2D; runtimeProfile.EnsureRuntimeData(); runtimeProfile.polarAxes.angleAxis.labels = BuildLabels(); runtimeProfile.polarAxes.angleAxis.visible = true; runtimeProfile.polarAxes.angleAxis.showLabels = true; if (runtimeProfile.polarAxes.angleAxis.labelStyle == null) { runtimeProfile.polarAxes.angleAxis.labelStyle = new LabelStyleSettings(); } runtimeProfile.polarAxes.angleAxis.labelStyle.enabled = true; runtimeProfile.polarAxes.radiusAxis.visible = true; runtimeProfile.polarAxes.radiusAxis.autoRangeMin = false; runtimeProfile.polarAxes.radiusAxis.autoRangeMax = false; runtimeProfile.polarAxes.radiusAxis.minValue = minValue; runtimeProfile.polarAxes.radiusAxis.maxValue = Mathf.Max(minValue + 1f, maxValue); runtimeProfile.polarAxes.radiusAxis.splitCount = Mathf.Max(2, splitCount); if (runtimeProfile.polarAxes.radiusAxis.labelStyle == null) { runtimeProfile.polarAxes.radiusAxis.labelStyle = new LabelStyleSettings(); } runtimeProfile.polarAxes.radiusAxis.labelStyle.enabled = runtimeProfile.polarAxes.radiusAxis.showLabels; if (overrideGridStyle) { runtimeProfile.polarAxes.angleAxis.color = gridLineColor; runtimeProfile.polarAxes.angleAxis.width = gridLineWidth; runtimeProfile.polarAxes.radiusAxis.color = gridLineColor; runtimeProfile.polarAxes.radiusAxis.edgeColor = outerGridLineColor; runtimeProfile.polarAxes.radiusAxis.width = gridLineWidth; } if (overrideLabelStyle) { runtimeProfile.polarAxes.angleAxis.labelColor = axisLabelColor; runtimeProfile.polarAxes.angleAxis.fontSize = axisLabelFontSize; runtimeProfile.polarAxes.angleAxis.labelOffset = axisLabelOffset; runtimeProfile.polarAxes.angleAxis.labelStyle.fontSize = axisLabelFontSize; runtimeProfile.polarAxes.angleAxis.labelStyle.color = axisLabelColor; runtimeProfile.polarAxes.angleAxis.labelStyle.offset = axisLabelOffset; runtimeProfile.polarAxes.radiusAxis.labelColor = axisLabelColor; runtimeProfile.polarAxes.radiusAxis.fontSize = axisLabelFontSize; runtimeProfile.polarAxes.radiusAxis.labelStyle.fontSize = axisLabelFontSize; runtimeProfile.polarAxes.radiusAxis.labelStyle.color = axisLabelColor; } if (runtimeProfile.series == null) { runtimeProfile.series = new List(); } int targetSeriesCount = 1; while (runtimeProfile.series.Count < targetSeriesCount) { runtimeProfile.series.Add(new Serie()); } while (runtimeProfile.series.Count > targetSeriesCount) { runtimeProfile.series.RemoveAt(runtimeProfile.series.Count - 1); } ConfigureSeries(runtimeProfile.series[0], mainSeriesName, true); runtimeProfile.EnsureRuntimeData(); } private void ApplyRuntimeTheme() { if (chartBridge == null || chartBridge.ChartElement == null) { return; } bool useTooltipStyle = overrideTooltipStyle; bool useTooltipFont = overrideTooltipFont && tooltipFont != null; bool useLabelFont = overrideLabelFont && axisLabelFont != null; if (!useLabelFont && !useTooltipFont && !useTooltipStyle) { chartBridge.ChartElement.Theme = null; return; } if (runtimeTheme == null) { runtimeTheme = ScriptableObject.CreateInstance(); runtimeTheme.name = "URadar_RuntimeTheme"; runtimeTheme.hideFlags = HideFlags.DontSave; } runtimeTheme.primaryFont = useLabelFont ? axisLabelFont : null; runtimeTheme.axisFontSize = useLabelFont ? axisLabelFontSize : -1f; runtimeTheme.tooltipFont = useTooltipFont ? tooltipFont : null; runtimeTheme.tooltipFontSize = (useTooltipFont || useTooltipStyle) ? tooltipFontSize : -1f; runtimeTheme.tooltipTextColor = useTooltipStyle ? tooltipTextColor : Color.white; runtimeTheme.tooltipBackgroundColor = useTooltipStyle ? tooltipBackgroundColor : new Color(0f, 0f, 0f, 0.8f); runtimeTheme.tooltipBorderColor = useTooltipStyle ? tooltipBorderColor : new Color(0f, 0f, 0f, 0f); runtimeTheme.tooltipBorderWidth = useTooltipStyle ? tooltipBorderWidth : 0f; runtimeTheme.tooltipCornerRadius = useTooltipStyle ? tooltipCornerRadius : 4f; runtimeTheme.tooltipPadding = useTooltipStyle ? tooltipPadding : new Vector4(8f, 8f, 4f, 4f); // Force ChartElement.Theme setter to re-apply tooltip visuals even when reusing the same runtime theme instance. chartBridge.ChartElement.Theme = null; chartBridge.ChartElement.Theme = runtimeTheme; } private void ConfigureSeries(Serie serie, string serieName, bool isMainSeries) { if (serie == null) { return; } serie.name = string.IsNullOrWhiteSpace(serieName) ? (isMainSeries ? "Player" : "Reference") : serieName.Trim(); serie.visible = true; if (serie.type != SerieType.Radar) { serie.SetType(SerieType.Radar); } if (!(serie.settings is RadarSettings radarSettings)) { radarSettings = new RadarSettings(); serie.settings = radarSettings; } radarSettings.radar.innerRadius = radarInnerRadius; radarSettings.radar.outerRadius = 0f; radarSettings.radar.plot.padding = radarPlotPadding; radarSettings.radar.plot.labelRadialOffset = radarLabelRadialOffset; radarSettings.area.show = true; radarSettings.point.show = true; if (overrideSeriesStyle) { radarSettings.stroke.color = isMainSeries ? mainStrokeColor : referenceStrokeColor; radarSettings.stroke.width = isMainSeries ? 2.5f : 2f; radarSettings.area.textureFill.color = isMainSeries ? mainFillColor : referenceFillColor; radarSettings.point.textureFill.color = isMainSeries ? mainPointColor : referencePointColor; radarSettings.point.size = isMainSeries ? 8f : 7f; } if (serie.labelSettings != null) { serie.labelSettings.enabled = false; } if (serie.seriesData == null) { serie.seriesData = new List(); } else { serie.seriesData.Clear(); } for (int i = 0; i < axisCount; i++) { URadarAxisEntry axis = axes[i]; float value = isMainSeries ? axis.value : axis.referenceValue; serie.seriesData.Add(new SeriesData { id = (isMainSeries ? "main_" : "ref_") + i, name = axis.label, x = i, value = Mathf.Clamp(value, minValue, maxValue) }); } } private List BuildLabels() { List labels = new List(axisCount); for (int i = 0; i < axisCount; i++) { string label = axes[i] != null ? axes[i].label : null; labels.Add(string.IsNullOrWhiteSpace(label) ? $"Axis {i + 1}" : label.Trim()); } return labels; } private void NormalizeAxes() { if (axes == null) { axes = new List(); } axisCount = Mathf.Max(3, axisCount); while (axes.Count < axisCount) { axes.Add(new URadarAxisEntry { label = $"Axis {axes.Count + 1}", value = 50f, referenceValue = 60f }); } if (axes.Count > axisCount) { axes.RemoveRange(axisCount, axes.Count - axisCount); } for (int i = 0; i < axes.Count; i++) { if (axes[i] == null) { axes[i] = new URadarAxisEntry { label = $"Axis {i + 1}", value = 50f, referenceValue = 60f }; } } } }