using System;
using System.Collections.Generic;
using UnityEngine;
namespace XericUI.XChart.Radar
{
///
/// 雷达图数据模型。
///
/// 管理多个数据系列(RadarSeries),每个系列对应一个雷达多边形。
/// 所有系列共享同一组维度名称(Dimensions)。
///
/// 使用示例:
/// var model = new XChartRadarDataModel();
/// model.SetDimensions("速度", "力量", "防御", "敏捷", "魔力");
/// model.AddSeries("战士", new float[] { 0.8f, 0.9f, 0.7f, 0.4f, 0.2f });
/// model.AddSeries("法师", new float[] { 0.2f, 0.3f, 0.4f, 0.6f, 0.9f });
///
public class XChartRadarDataModel
{
// ── 数据系列 ──────────────────────────────────────────────
///
/// 单个数据系列。
///
[Serializable]
public struct RadarSeries
{
/// 系列名称(用于图例)
public string name;
/// 各维度的值(长度需与 Dimensions 一致)
public float[] values;
/// 系列颜色
public Color color;
/// 线宽
public float lineWidth;
public RadarSeries(string name, float[] values, Color color, float lineWidth = 2f)
{
this.name = name;
this.values = values;
this.color = color;
this.lineWidth = lineWidth;
}
}
// ── 数据存储 ──────────────────────────────────────────────
/// 维度名称列表
public string[] Dimensions { get; private set; }
/// 维度数量
public int DimensionCount => Dimensions?.Length ?? 0;
/// 数据系列列表
public IReadOnlyList Series => m_Series;
/// 系列数量
public int SeriesCount => m_Series.Count;
/// 是否有数据系列
public bool HasSeries => m_Series.Count > 0;
private List m_Series = new List();
// ── 全局范围 ──────────────────────────────────────────────
/// 所有系列中的全局最大值(用于归一化)
public float GlobalMaxValue { get; private set; } = 1f;
/// 是否自动缩放(基于 GlobalMaxValue)
public bool AutoScale { get; set; } = true;
/// 自定义最大值(AutoScale=false 时使用)
public float CustomMaxValue { get; set; } = 1f;
/// 当前使用的最大值
public float MaxValue => AutoScale ? GlobalMaxValue : CustomMaxValue;
// ── 脏标记 ────────────────────────────────────────────────
[Flags]
public enum DirtyFlags
{
None = 0,
DataChanged = 1 << 0,
DimensionsChanged = 1 << 1,
All = DataChanged | DimensionsChanged,
}
public DirtyFlags CurrentDirty { get; private set; }
public void MarkDirty(DirtyFlags flag) { CurrentDirty |= flag; }
public void ClearDirty(DirtyFlags flag) { CurrentDirty &= ~flag; }
public bool IsDirty(DirtyFlags flag) => (CurrentDirty & flag) != 0;
// ── 维度操作 ──────────────────────────────────────────────
/// 设置维度名称
public void SetDimensions(params string[] dimensionNames)
{
Dimensions = dimensionNames ?? new string[0];
MarkDirty(DirtyFlags.DimensionsChanged);
}
/// 设置维度名称(从列表)
public void SetDimensions(IList dimensionNames)
{
if (dimensionNames == null)
{
Dimensions = new string[0];
}
else
{
Dimensions = new string[dimensionNames.Count];
for (int i = 0; i < dimensionNames.Count; i++)
Dimensions[i] = dimensionNames[i];
}
MarkDirty(DirtyFlags.DimensionsChanged);
}
// ── 系列操作 ──────────────────────────────────────────────
/// 添加一个数据系列
public void AddSeries(string name, float[] values, Color? color = null, float lineWidth = 2f)
{
if (DimensionCount == 0) return;
// 截断或补齐
int count = Math.Min(values.Length, DimensionCount);
var trimmed = new float[DimensionCount];
Array.Copy(values, trimmed, count);
m_Series.Add(new RadarSeries(name, trimmed, color ?? Color.white, lineWidth));
RecalculateRange();
MarkDirty(DirtyFlags.DataChanged);
}
/// 批量设置数据系列
public void SetSeries(IList series)
{
m_Series.Clear();
if (series != null)
{
foreach (var s in series)
m_Series.Add(s);
}
RecalculateRange();
MarkDirty(DirtyFlags.All);
}
/// 移除指定系列
public void RemoveSeries(int index)
{
if (index < 0 || index >= m_Series.Count) return;
m_Series.RemoveAt(index);
RecalculateRange();
MarkDirty(DirtyFlags.DataChanged);
}
/// 移除所有系列
public void ClearAll()
{
m_Series.Clear();
Dimensions = new string[0];
GlobalMaxValue = 1f;
MarkDirty(DirtyFlags.All);
}
/// 获取指定系列
public RadarSeries GetSeries(int index) => m_Series[index];
/// 更新指定系列的值
public void UpdateSeries(int index, float[] values)
{
if (index < 0 || index >= m_Series.Count) return;
var s = m_Series[index];
int count = Math.Min(values.Length, DimensionCount);
s.values = new float[DimensionCount];
Array.Copy(values, s.values, count);
m_Series[index] = s;
RecalculateRange();
MarkDirty(DirtyFlags.DataChanged);
}
// ── 范围计算 ──────────────────────────────────────────────
private void RecalculateRange()
{
float maxVal = 0f;
for (int si = 0; si < m_Series.Count; si++)
{
var vals = m_Series[si].values;
if (vals == null) continue;
for (int vi = 0; vi < vals.Length; vi++)
{
if (vals[vi] > maxVal) maxVal = vals[vi];
}
}
GlobalMaxValue = maxVal > 0.001f ? maxVal : 1f;
}
// ── 坐标计算 ──────────────────────────────────────────────
///
/// 计算指定维度在雷达图上的方向向量(从中心指向端点,单位向量)。
/// 角度从正上方(12点钟方向)开始,顺时针排列。
///
public static Vector2 GetDimensionDirection(int index, int total)
{
if (total <= 0) return Vector2.up;
float angle = 90f - index * (360f / total);
float rad = angle * Mathf.Deg2Rad;
return new Vector2(Mathf.Cos(rad), Mathf.Sin(rad));
}
///
/// 计算所有维度的方向向量。
///
public static Vector2[] GetDimensionDirections(int total)
{
var dirs = new Vector2[total];
for (int i = 0; i < total; i++)
dirs[i] = GetDimensionDirection(i, total);
return dirs;
}
///
/// 计算雷达图最大半径时的端点位置。
///
public static Vector2[] GetOuterPoints(Vector2 center, float radius, int dimensionCount)
{
var pts = new Vector2[dimensionCount];
for (int i = 0; i < dimensionCount; i++)
{
Vector2 dir = GetDimensionDirection(i, dimensionCount);
pts[i] = center + dir * radius;
}
return pts;
}
///
/// 计算某个系列的数据点位置(归一化后的值 × 半径)。
///
public static Vector2[] GetSeriesPoints(Vector2 center, float radius,
float[] values, float maxValue, int dimensionCount)
{
var pts = new Vector2[dimensionCount];
float invMax = maxValue > 0.001f ? 1f / maxValue : 1f;
for (int i = 0; i < dimensionCount; i++)
{
Vector2 dir = GetDimensionDirection(i, dimensionCount);
float val = (i < values.Length) ? values[i] : 0f;
float r = Mathf.Clamp01(val * invMax) * radius;
pts[i] = center + dir * r;
}
return pts;
}
}
}