314 lines
11 KiB
C#
314 lines
11 KiB
C#
using UnityEngine;
|
|
using XericLibrary.Runtime.UIGraph;
|
|
using TMPro;
|
|
|
|
namespace XericUI.XChart.Radar
|
|
{
|
|
/// <summary>
|
|
/// 雷达图网格背景工具。
|
|
///
|
|
/// 使用 UILineRendererV2 绘制:
|
|
/// 1. 同心多边形网格(外圈 + 内圈百分比环)
|
|
/// 2. 从中心到各维度的轴线
|
|
/// 3. 每个维度端点的 TMP 标签
|
|
///
|
|
/// 子节点结构:
|
|
/// [ChartTool_RadarBg]
|
|
/// ├─ GridRenderer (UILineRendererV2) — 网格线
|
|
/// ├─ AxisRenderer (UILineRendererV2) — 轴线
|
|
/// └─ [Labels] (transform)
|
|
/// ├─ Label_0 (TMP_Text)
|
|
/// ├─ Label_1 (TMP_Text)
|
|
/// └─ ...
|
|
/// </summary>
|
|
[AddComponentMenu("Xeric/XChart/Radar/Radar Background Tool")]
|
|
public class ChartToolRadarBg : XChartToolBase
|
|
{
|
|
// ── 配置 ──────────────────────────────────────────────────
|
|
|
|
/// <summary>雷达图半径(像素)</summary>
|
|
public float Radius { get; set; } = 120f;
|
|
|
|
/// <summary>中心位置</summary>
|
|
public Vector2 Center { get; set; } = Vector2.zero;
|
|
|
|
/// <summary>维度数量</summary>
|
|
public int DimensionCount { get; set; } = 5;
|
|
|
|
/// <summary>维度名称列表</summary>
|
|
public string[] DimensionNames { get; set; } = new string[0];
|
|
|
|
/// <summary>网格环数量(含外圈)</summary>
|
|
public int RingCount { get; set; } = 4;
|
|
|
|
/// <summary>网格颜色</summary>
|
|
public Color GridColor { get; set; } = new Color(0.5f, 0.5f, 0.5f, 0.3f);
|
|
|
|
/// <summary>轴线颜色</summary>
|
|
public Color AxisColor { get; set; } = new Color(0.5f, 0.5f, 0.5f, 0.2f);
|
|
|
|
/// <summary>网格线宽</summary>
|
|
public float GridThickness { get; set; } = 1f;
|
|
|
|
/// <summary>轴线宽</summary>
|
|
public float AxisThickness { get; set; } = 1f;
|
|
|
|
/// <summary>是否显示网格</summary>
|
|
public bool ShowGrid { get; set; } = true;
|
|
|
|
/// <summary>是否显示轴线</summary>
|
|
public bool ShowAxis { get; set; } = true;
|
|
|
|
/// <summary>是否显示标签</summary>
|
|
public bool ShowLabels { get; set; } = true;
|
|
|
|
/// <summary>标签字体大小</summary>
|
|
public float LabelFontSize { get; set; } = 14f;
|
|
|
|
/// <summary>标签颜色</summary>
|
|
public Color LabelColor { get; set; } = Color.white;
|
|
|
|
/// <summary>标签相对端点的偏移量(像素)</summary>
|
|
public float LabelOffset { get; set; } = 10f;
|
|
|
|
// ── 内部组件 ──────────────────────────────────────────────
|
|
|
|
/// <summary>网格线渲染器</summary>
|
|
public UILineRendererV2 GridRenderer { get; private set; }
|
|
|
|
/// <summary>轴线渲染器</summary>
|
|
public UILineRendererV2 AxisRenderer { get; private set; }
|
|
|
|
/// <summary>标签的父变换</summary>
|
|
public Transform LabelsRoot { get; private set; }
|
|
|
|
/// <summary>所有标签文本组件</summary>
|
|
public TMP_Text[] LabelTexts { get; private set; }
|
|
|
|
// ── 内部状态 ──────────────────────────────────────────────
|
|
|
|
private bool m_DataDirty = true;
|
|
|
|
// ── 初始化 ────────────────────────────────────────────────
|
|
|
|
protected override void OnInitialize()
|
|
{
|
|
// 创建网格线渲染器
|
|
GridRenderer = CreateChildComponent<UILineRendererV2>("GridRenderer");
|
|
if (GridRenderer != null)
|
|
{
|
|
var rt = GridRenderer.rectTransform;
|
|
rt.anchorMin = Vector2.zero;
|
|
rt.anchorMax = Vector2.one;
|
|
rt.offsetMin = Vector2.zero;
|
|
rt.offsetMax = Vector2.zero;
|
|
}
|
|
|
|
// 创建轴线渲染器
|
|
AxisRenderer = CreateChildComponent<UILineRendererV2>("AxisRenderer");
|
|
if (AxisRenderer != null)
|
|
{
|
|
var rt = AxisRenderer.rectTransform;
|
|
rt.anchorMin = Vector2.zero;
|
|
rt.anchorMax = Vector2.one;
|
|
rt.offsetMin = Vector2.zero;
|
|
rt.offsetMax = Vector2.zero;
|
|
}
|
|
|
|
// 创建标签父节点
|
|
var labelsObj = CreateChild("Labels");
|
|
LabelsRoot = labelsObj;
|
|
|
|
}
|
|
|
|
// ── 标记脏 ────────────────────────────────────────────────
|
|
|
|
public void MarkDirty()
|
|
{
|
|
m_DataDirty = true;
|
|
}
|
|
|
|
// ── 渲染 ──────────────────────────────────────────────────
|
|
|
|
public override void OnRender()
|
|
{
|
|
base.OnRender();
|
|
if (!m_DataDirty) return;
|
|
m_DataDirty = false;
|
|
|
|
DrawGrid();
|
|
DrawAxis();
|
|
UpdateLabels();
|
|
}
|
|
|
|
// ── 网格绘制 ──────────────────────────────────────────────
|
|
|
|
private void DrawGrid()
|
|
{
|
|
if (GridRenderer == null) return;
|
|
GridRenderer.ClearAll();
|
|
|
|
if (!ShowGrid || DimensionCount < 3) return;
|
|
|
|
// 预计算所有维度的方向
|
|
int dims = DimensionCount;
|
|
var dirs = XChartRadarDataModel.GetDimensionDirections(dims);
|
|
|
|
// 绘制同心多边形环
|
|
for (int ring = 1; ring <= RingCount; ring++)
|
|
{
|
|
float r = Radius * ring / RingCount;
|
|
|
|
// 构建多边形顶点
|
|
var pts = new Vector2[dims + 1];
|
|
for (int i = 0; i < dims; i++)
|
|
pts[i] = Center + dirs[i] * r;
|
|
pts[dims] = pts[0]; // 闭合
|
|
|
|
// 绘制每个线段
|
|
for (int i = 0; i < dims; i++)
|
|
{
|
|
GridRenderer.DrawLine(pts[i], pts[i + 1],
|
|
GridColor, GridThickness);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── 轴线绘制 ──────────────────────────────────────────────
|
|
|
|
private void DrawAxis()
|
|
{
|
|
if (AxisRenderer == null) return;
|
|
AxisRenderer.ClearAll();
|
|
|
|
if (!ShowAxis || DimensionCount < 3) return;
|
|
|
|
int dims = DimensionCount;
|
|
var dirs = XChartRadarDataModel.GetDimensionDirections(dims);
|
|
|
|
for (int i = 0; i < dims; i++)
|
|
{
|
|
Vector2 end = Center + dirs[i] * Radius;
|
|
AxisRenderer.DrawLine(Center, end, AxisColor, AxisThickness);
|
|
}
|
|
}
|
|
|
|
// ── 标签管理 ──────────────────────────────────────────────
|
|
|
|
private void UpdateLabels()
|
|
{
|
|
if (LabelsRoot == null) return;
|
|
|
|
int dims = DimensionCount;
|
|
var dirs = XChartRadarDataModel.GetDimensionDirections(dims);
|
|
|
|
// 确保标签数量匹配
|
|
EnsureLabelCount(dims);
|
|
|
|
bool show = ShowLabels && dims >= 3;
|
|
|
|
for (int i = 0; i < dims; i++)
|
|
{
|
|
var label = LabelTexts[i];
|
|
if (label == null) continue;
|
|
|
|
label.gameObject.SetActive(show);
|
|
|
|
if (!show) continue;
|
|
|
|
// 设置文本
|
|
string name = (i < DimensionNames.Length && DimensionNames[i] != null)
|
|
? DimensionNames[i]
|
|
: $"Dim {i}";
|
|
label.text = name;
|
|
label.fontSize = LabelFontSize;
|
|
label.color = LabelColor;
|
|
|
|
// 计算标签位置:端点向外偏移
|
|
Vector2 endPos = Center + dirs[i] * Radius;
|
|
Vector2 labelPos = endPos + dirs[i] * LabelOffset;
|
|
|
|
// 对齐方式:根据方向决定
|
|
float angle = Mathf.Atan2(dirs[i].y, dirs[i].x) * Mathf.Rad2Deg;
|
|
if (angle > -45f && angle <= 45f)
|
|
label.alignment = TextAlignmentOptions.Left;
|
|
else if (angle > 45f && angle <= 135f)
|
|
label.alignment = TextAlignmentOptions.Bottom;
|
|
else if (angle > 135f || angle <= -135f)
|
|
label.alignment = TextAlignmentOptions.Right;
|
|
else
|
|
label.alignment = TextAlignmentOptions.Top;
|
|
|
|
// 应用位置
|
|
var rt = label.rectTransform;
|
|
rt.anchoredPosition = labelPos;
|
|
rt.anchorMin = new Vector2(0.5f, 0.5f);
|
|
rt.anchorMax = new Vector2(0.5f, 0.5f);
|
|
rt.pivot = new Vector2(0.5f, 0.5f);
|
|
}
|
|
|
|
// 隐藏多余的标签
|
|
for (int i = dims; i < LabelTexts.Length; i++)
|
|
{
|
|
if (LabelTexts[i] != null)
|
|
LabelTexts[i].gameObject.SetActive(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>确保标签数组长度匹配维度数量</summary>
|
|
private void EnsureLabelCount(int count)
|
|
{
|
|
if (LabelTexts != null && LabelTexts.Length == count)
|
|
return;
|
|
|
|
// 清理旧的标签
|
|
if (LabelTexts != null)
|
|
{
|
|
foreach (var label in LabelTexts)
|
|
{
|
|
if (label != null && label.gameObject != null)
|
|
{
|
|
if (Application.isPlaying)
|
|
Object.Destroy(label.gameObject);
|
|
else
|
|
Object.DestroyImmediate(label.gameObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
LabelTexts = new TMP_Text[count];
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var labelObj = new GameObject($"Label_{i}", typeof(RectTransform));
|
|
labelObj.transform.SetParent(LabelsRoot, false);
|
|
labelObj.hideFlags = CurrentFlags;
|
|
|
|
var tmp = labelObj.AddComponent<TextMeshProUGUI>();
|
|
tmp.text = "";
|
|
tmp.fontSize = LabelFontSize;
|
|
tmp.color = LabelColor;
|
|
tmp.alignment = TextAlignmentOptions.Center;
|
|
|
|
var rt = tmp.rectTransform;
|
|
rt.anchorMin = new Vector2(0.5f, 0.5f);
|
|
rt.anchorMax = new Vector2(0.5f, 0.5f);
|
|
rt.pivot = new Vector2(0.5f, 0.5f);
|
|
rt.sizeDelta = new Vector2(100f, 24f);
|
|
|
|
LabelTexts[i] = tmp;
|
|
}
|
|
}
|
|
|
|
// ── 清理 ──────────────────────────────────────────────────
|
|
|
|
protected override void OnDispose()
|
|
{
|
|
GridRenderer = null;
|
|
AxisRenderer = null;
|
|
LabelsRoot = null;
|
|
LabelTexts = null;
|
|
}
|
|
}
|
|
}
|