using System.Collections.Generic; using EasyChart.UGUI; using UnityEngine; using UnityEngine.UI; using UnityEngine.UIElements; using XCharts.Runtime; public class URadarChartController : MonoBehaviour { [Header("Legacy Compatibility")] public UGUIChartBridge chartBridge; [Header("Chart")] 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 bool overrideSymbolStyle = true; public float mainSymbolSize = 8f; public float referenceSymbolSize = 7f; public SymbolType mainSymbolType = SymbolType.Circle; public SymbolType referenceSymbolType = SymbolType.Circle; 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; [Header("Legacy Serialized Fields")] [SerializeField] private UIRadarGraphic radarGraphic; [SerializeField] private RectTransform labelRoot; [SerializeField] private bool autoCreateLabelRoot = true; [SerializeField] private string labelRootName = "RadarLabels"; [Header("XCharts Runtime")] [SerializeField] private RectTransform radarChartHost; [SerializeField] private string radarChartHostName = "RadarChartHost"; [SerializeField] private RadarChart radarChart; private bool rebuildQueued; private bool chartInitialized; public bool IsRendererReady { get { EnsureRenderer(); return radarChart != null; } } private void Awake() { showReferenceSeries = false; NormalizeAxes(); EnsureRenderer(); QueueRebuild(); } private void OnEnable() { showReferenceSeries = false; NormalizeAxes(); EnsureRenderer(); if (rebuildOnEnable) { QueueRebuild(); } } private void LateUpdate() { if (!Application.isPlaying) { return; } 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); mainSymbolSize = Mathf.Max(0f, mainSymbolSize); referenceSymbolSize = Mathf.Max(0f, referenceSymbolSize); tooltipBorderWidth = Mathf.Max(0f, tooltipBorderWidth); tooltipCornerRadius = Mathf.Max(0f, tooltipCornerRadius); axisCount = Mathf.Max(3, axisCount); EnsureRenderer(false); QueueRebuild(); } 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(); } public void EnsureRenderer(bool initializeChart = true) { DisableLegacyRenderer(); bool allowRuntimeChartMutation = Application.isPlaying; EnsureChartHost(allowRuntimeChartMutation); if (radarChart == null) { radarChart = radarChartHost != null ? radarChartHost.GetComponent() : null; } if (allowRuntimeChartMutation && radarChart == null && radarChartHost != null) { radarChart = radarChartHost.gameObject.AddComponent(); } if (radarChart != null) { radarChart.enabled = true; radarChart.gameObject.SetActive(true); } if (allowRuntimeChartMutation && initializeChart) { EnsureChartInitialized(); } EnsureRuntimeChartStyle(); } 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 (!Application.isPlaying) { rebuildQueued = false; return; } EnsureRenderer(); if (radarChart == null) { return; } ApplyRendererState(); rebuildQueued = false; } private void ApplyRendererState() { NormalizeAxes(); EnsureChartInitialized(); radarChart.RemoveChartComponents(); radarChart.RemoveData(); RadarCoord radarCoord = radarChart.AddChartComponent(); if (radarCoord == null) { return; } radarCoord.indicatorList.Clear(); float clampedMin = Mathf.Min(minValue, maxValue - 0.0001f); float clampedMax = Mathf.Max(minValue + 0.0001f, maxValue); for (int i = 0; i < axes.Count; i++) { string label = string.IsNullOrWhiteSpace(axes[i].label) ? $"Axis {i + 1}" : axes[i].label.Trim(); radarCoord.AddIndicator(label, clampedMin, clampedMax); } ConfigureRadarCoord(radarCoord); Radar mainSerie = radarChart.AddSerie(mainSeriesName); if (mainSerie != null) { mainSerie.radarIndex = radarCoord.index; } ConfigureSerie(mainSerie, true); if (mainSerie != null) { radarChart.AddData(mainSerie.index, BuildDataValues(false), mainSeriesName); } if (showReferenceSeries) { Radar referenceSerie = radarChart.AddSerie(referenceSeriesName); if (referenceSerie != null) { referenceSerie.radarIndex = radarCoord.index; } ConfigureSerie(referenceSerie, false); if (referenceSerie != null) { radarChart.AddData(referenceSerie.index, BuildDataValues(true), referenceSeriesName); } } RefreshLegendAndTooltip(); radarChart.RefreshChart(); } private void ConfigureRadarCoord(RadarCoord radarCoord) { RectTransform rectTransform = transform as RectTransform; float minRectSize = rectTransform == null ? 0f : Mathf.Min(rectTransform.rect.width, rectTransform.rect.height); float reservedRadius = Mathf.Max(0f, radarPlotPadding); float computedRadius = minRectSize > 0f ? Mathf.Max(0f, (minRectSize * 0.5f) - reservedRadius - Mathf.Max(0f, radarLabelRadialOffset)) : 0f; float xchartsRadius = minRectSize > 0f ? Mathf.Clamp01(computedRadius / minRectSize) : 0.35f; if (xchartsRadius <= 0f) { xchartsRadius = 0.35f; } radarCoord.show = true; radarCoord.shape = RadarCoord.Shape.Polygon; radarCoord.splitNumber = Mathf.Max(2, splitCount); radarCoord.center[0] = 0.5f; radarCoord.center[1] = 0.5f; radarCoord.radius = xchartsRadius; radarCoord.indicator = true; radarCoord.indicatorGap = radarLabelRadialOffset; radarCoord.positionType = RadarCoord.PositionType.Vertice; radarCoord.isAxisTooltip = false; radarCoord.startAngle = 0f; Color innerGrid = overrideGridStyle ? gridLineColor : new Color32(255, 255, 255, 180); Color outerGrid = overrideGridStyle ? outerGridLineColor : Color.white; Color effectiveLabelColor = overrideLabelStyle ? axisLabelColor : Color.white; Vector2 effectiveLabelOffset = overrideLabelStyle ? axisLabelOffset : Vector2.zero; Font effectiveLabelFont = overrideLabelFont && axisLabelFont != null ? axisLabelFont : axisLabelFont; radarCoord.axisLine.show = true; radarCoord.axisLine.lineStyle.show = true; radarCoord.axisLine.lineStyle.type = LineStyle.Type.Solid; radarCoord.axisLine.lineStyle.width = gridLineWidth; radarCoord.axisLine.lineStyle.color = innerGrid; radarCoord.splitLine.show = true; radarCoord.splitLine.lineStyle.show = true; radarCoord.splitLine.lineStyle.type = LineStyle.Type.Solid; radarCoord.splitLine.lineStyle.width = gridLineWidth; radarCoord.splitLine.lineStyle.color = outerGrid; radarCoord.splitArea.show = false; radarCoord.axisName.show = true; radarCoord.axisName.name = null; radarCoord.axisName.labelStyle.show = true; radarCoord.axisName.labelStyle.textStyle.autoColor = false; radarCoord.axisName.labelStyle.textStyle.color = effectiveLabelColor; radarCoord.axisName.labelStyle.textStyle.fontSize = axisLabelFontSize; radarCoord.axisName.labelStyle.textStyle.font = effectiveLabelFont; radarCoord.axisName.labelStyle.textStyle.autoAlign = false; radarCoord.axisName.labelStyle.textStyle.alignment = TextAnchor.MiddleCenter; radarCoord.axisName.labelStyle.offset = new Vector3(effectiveLabelOffset.x, effectiveLabelOffset.y, 0f); radarCoord.axisName.labelStyle.width = 140f; radarCoord.axisName.labelStyle.height = Mathf.Max(24f, axisLabelFontSize + 10f); radarCoord.SetVerticesDirty(); } private void ConfigureSerie(Radar serie, bool isMainSerie) { if (serie == null) { return; } serie.radarType = RadarType.Multiple; serie.showDataName = false; serie.symbol.show = true; if (overrideSymbolStyle) { serie.symbol.type = isMainSerie ? mainSymbolType : referenceSymbolType; serie.symbol.size = isMainSerie ? mainSymbolSize : referenceSymbolSize; serie.symbol.color = isMainSerie ? mainPointColor : referencePointColor; } else { serie.symbol.type = SymbolType.Circle; serie.symbol.size = isMainSerie ? 8f : 7f; serie.symbol.color = Color.white; } serie.lineStyle.show = true; serie.lineStyle.type = LineStyle.Type.Solid; serie.lineStyle.width = isMainSerie ? 2.5f : 2f; serie.lineStyle.color = isMainSerie ? (overrideSeriesStyle ? mainStrokeColor : new Color32(82, 126, 255, 255)) : (overrideSeriesStyle ? referenceStrokeColor : new Color32(72, 229, 229, 255)); AreaStyle areaStyle = serie.EnsureComponent(); areaStyle.show = true; areaStyle.color = isMainSerie ? (overrideSeriesStyle ? mainFillColor : new Color(82f / 255f, 126f / 255f, 1f, 0.28f)) : (overrideSeriesStyle ? referenceFillColor : new Color(72f / 255f, 229f / 255f, 229f / 255f, 0.22f)); areaStyle.toColor = Color.clear; areaStyle.opacity = 1f; LabelStyle labelStyle = serie.EnsureComponent(); labelStyle.show = false; } private List BuildDataValues(bool useReferenceValue) { List values = new List(axes.Count); for (int i = 0; i < axes.Count; i++) { float raw = useReferenceValue ? axes[i].referenceValue : axes[i].value; values.Add(Mathf.Clamp(raw, minValue, maxValue)); } return values; } private void RefreshLegendAndTooltip() { Legend legend = radarChart.GetChartComponent(); if (legend != null) { legend.show = false; legend.data.Clear(); } Title title = radarChart.GetChartComponent(); if (title != null) { title.show = false; title.text = string.Empty; title.subText = string.Empty; } XCharts.Runtime.Background background = radarChart.GetChartComponent<XCharts.Runtime.Background>(); if (background != null) { background.show = false; } Tooltip tooltip = radarChart.GetChartComponent<Tooltip>(); if (tooltip == null) { return; } tooltip.show = false; tooltip.showContent = false; tooltip.type = Tooltip.Type.None; tooltip.trigger = Tooltip.Trigger.None; if (overrideTooltipStyle) { tooltip.backgroundColor = tooltipBackgroundColor; tooltip.borderColor = tooltipBorderColor; tooltip.borderWidth = tooltipBorderWidth; tooltip.paddingLeftRight = Mathf.RoundToInt(Mathf.Max(0f, tooltipPadding.x)); tooltip.paddingTopBottom = Mathf.RoundToInt(Mathf.Max(0f, tooltipPadding.z)); } if (overrideTooltipFont) { tooltip.titleLabelStyle.textStyle.font = tooltipFont; tooltip.titleLabelStyle.textStyle.fontSize = tooltipFontSize; tooltip.titleLabelStyle.textStyle.color = tooltipTextColor; for (int i = 0; i < tooltip.contentLabelStyles.Count; i++) { tooltip.contentLabelStyles[i].textStyle.font = tooltipFont; tooltip.contentLabelStyles[i].textStyle.fontSize = tooltipFontSize; tooltip.contentLabelStyles[i].textStyle.color = tooltipTextColor; } } } private void EnsureChartInitialized() { if (radarChart == null) { return; } if (!chartInitialized) { radarChart.Init(); chartInitialized = true; } else { radarChart.EnsureChartComponent<Title>(); radarChart.EnsureChartComponent<Tooltip>(); radarChart.EnsureChartComponent<XCharts.Runtime.Background>(); radarChart.EnsureChartComponent<RadarCoord>(); } } private void EnsureRuntimeChartStyle() { if (radarChart == null) { return; } radarChart.raycastTarget = false; CanvasRenderer canvasRenderer = radarChart.GetComponent<CanvasRenderer>(); if (canvasRenderer != null) { canvasRenderer.SetAlpha(1f); } } private void EnsureChartHost(bool createIfMissing) { if (radarChartHost == null) { Transform existing = transform.Find(radarChartHostName); if (existing != null) { radarChartHost = existing as RectTransform; } } if (radarChartHost == null && createIfMissing) { GameObject host = new GameObject(radarChartHostName, typeof(RectTransform)); radarChartHost = host.GetComponent<RectTransform>(); radarChartHost.SetParent(transform, false); } if (radarChartHost == null) { return; } radarChartHost.anchorMin = Vector2.zero; radarChartHost.anchorMax = Vector2.one; radarChartHost.pivot = new Vector2(0.5f, 0.5f); radarChartHost.anchoredPosition = Vector2.zero; radarChartHost.sizeDelta = Vector2.zero; radarChartHost.offsetMin = Vector2.zero; radarChartHost.offsetMax = Vector2.zero; radarChartHost.localScale = Vector3.one; radarChartHost.localRotation = Quaternion.identity; radarChartHost.SetAsLastSibling(); radarChartHost.gameObject.SetActive(true); } private void DisableLegacyRenderer() { if (chartBridge == null) { chartBridge = GetComponent<UGUIChartBridge>(); } if (chartBridge != null) { chartBridge.enabled = false; } UIDocument uiDocument = GetComponent<UIDocument>(); if (uiDocument != null) { uiDocument.enabled = false; } RawImage rawImage = GetComponent<RawImage>(); if (rawImage != null) { rawImage.enabled = false; rawImage.texture = null; rawImage.raycastTarget = false; Color color = rawImage.color; color.a = 0f; rawImage.color = color; } if (radarGraphic == null) { radarGraphic = GetComponent<UIRadarGraphic>(); } if (radarGraphic != null) { radarGraphic.enabled = false; radarGraphic.raycastTarget = false; Color color = radarGraphic.color; color.a = 0f; radarGraphic.color = color; radarGraphic.SetVerticesDirty(); } if (labelRoot == null && autoCreateLabelRoot) { Transform existing = transform.Find(labelRootName); if (existing != null) { labelRoot = existing as RectTransform; } } if (labelRoot != null) { labelRoot.gameObject.SetActive(false); } } private void NormalizeAxes() { if (axes == null) { axes = new List<URadarAxisEntry>(); } 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 }; } } } }