610 lines
19 KiB
C#

#define Graph_And_Chart_PRO
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace ChartAndGraph
{
[ExecuteInEditMode]
public abstract class RadarChart : AnyChart
{
protected Dictionary<string, List<BillboardText>> mTexts = new Dictionary<string, List<BillboardText>>();
protected HashSet<BillboardText> mActiveTexts = new HashSet<BillboardText>();
HashSet<string> mOccupiedCateogies = new HashSet<string>();
Vector3[] mDirections;
public class RadarEventArgs
{
public RadarEventArgs(string category,string group,double value,Vector3 position, int index)
{
Position = position;
Category = category;
Group = group;
Value = value;
Index = index;
}
public int Index { get; private set; }
public string Category { get; private set; }
public string Group { get; private set; }
public double Value { get; private set; }
public Vector3 Position { get; private set; }
}
[SerializeField]
private float radius = 3f;
public float Radius
{
get { return radius; }
set
{
radius = value;
GenerateChart();
}
}
[SerializeField]
private float angle = 0f;
public float Angle
{
get { return angle; }
set
{
angle = value;
GenerateChart();
}
}
[SerializeField]
private Material axisPointMaterial;
protected void AddBillboardText(string cat, BillboardText text)
{
List<BillboardText> addTo;
if (mTexts.TryGetValue(cat, out addTo) == false)
{
addTo = new List<BillboardText>();
mTexts.Add(cat, addTo);
}
addTo.Add(text);
}
public Material AxisPointMaterial
{
get
{
return axisPointMaterial;
}
set
{
axisPointMaterial = value;
GenerateChart();
}
}
[SerializeField]
private Material axisLineMaterial;
public Material AxisLineMaterial
{
get
{
return axisLineMaterial;
}
set
{
axisLineMaterial = value;
GenerateChart();
}
}
protected override void Start()
{
base.Start();
if (ChartCommon.IsInEditMode == false)
{
HookEvents();
}
Invalidate();
}
[SerializeField]
private float axisThickness = 0.1f;
public float AxisThickness
{
get
{
return axisThickness;
}
set
{
axisThickness = value;
GenerateChart();
}
}
[SerializeField]
private float axisPointSize = 1f;
public float AxisPointSize
{
get
{
return axisPointSize;
}
set
{
axisPointSize = value;
GenerateChart();
}
}
[SerializeField]
private float axisAdd = 0f;
public float AxisAdd
{
get
{
return axisAdd;
}
set
{
axisAdd = value;
GenerateChart();
}
}
[SerializeField]
private int totalAxisDevisions = 5;
public int TotalAxisDevisions
{
get
{
return totalAxisDevisions;
}
set
{
totalAxisDevisions = value;
GenerateChart();
}
}
[Serializable]
public class RadarEvent : UnityEvent<RadarEventArgs>
{
}
/// <summary>
/// occures when a point is clicked
/// </summary>
public RadarEvent PointClicked = new RadarEvent();
/// <summary>
/// occurs when a point is hovered
/// </summary>
public RadarEvent PointHovered = new RadarEvent();
/// <summary>
/// occurs when no point is hovered any longer
/// </summary>
public UnityEvent NonHovered = new UnityEvent();
List<GameObject> mAxisObjects = new List<GameObject>();
/// <summary>
/// the radar data
/// </summary>
[HideInInspector]
[SerializeField]
private RadarChartData Data = new RadarChartData();
/// <summary>
/// Holds the radar chart data. including values, categories and groups.
/// </summary>
public RadarChartData DataSource { get { return Data; } }
void HookEvents()
{
Data.ProperyUpdated -= DataUpdated;
Data.ProperyUpdated += DataUpdated;
((IInternalRadarData)Data).InternalDataSource.DataStructureChanged -= StructureUpdated;
((IInternalRadarData)Data).InternalDataSource.DataStructureChanged += StructureUpdated;
((IInternalRadarData)Data).InternalDataSource.DataValueChanged -= ValueChanged; ;
((IInternalRadarData)Data).InternalDataSource.DataValueChanged += ValueChanged;
}
private void ValueChanged(object sender, DataSource.ChartDataSourceBase.DataValueChangedEventArgs e)
{
Invalidate();
}
private void StructureUpdated(object sender, EventArgs e)
{
Invalidate();
}
private void DataUpdated()
{
Invalidate();
}
public override bool SupportRealtimeGeneration
{
get
{
return false;
}
}
protected override LegenedData LegendInfo
{
get
{
LegenedData legend = new LegenedData();
if (Data == null)
return legend;
foreach (var column in ((IInternalRadarData)Data).InternalDataSource.Columns)
{
var item = new LegenedData.LegenedItem();
var catData = column.UserData as RadarChartData.CategoryData;
item.Name = column.Name;
if (catData.FillMaterial != null)
item.Material = catData.FillMaterial;
else
{
if (catData.LineMaterial != null)
{
item.Material = catData.LineMaterial;
}
else
item.Material = null;
}
legend.AddLegenedItem(item);
}
return legend;
}
}
protected override IChartData DataLink
{
get
{
return Data;
}
}
protected override bool SupportsCategoryLabels
{
get
{
return true;
}
}
protected override bool SupportsGroupLables
{
get
{
return true;
}
}
protected override bool SupportsItemLabels
{
get
{
return true;
}
}
protected override float TotalDepthLink
{
get
{
return 0f;
}
}
protected override float TotalHeightLink
{
get
{
return 0f;
}
}
protected override float TotalWidthLink
{
get
{
return 0f;
}
}
protected override void Update()
{
base.Update();
}
/// <summary>
/// used internally , do not call this method
/// </summary>
protected override void OnValidate()
{
base.OnValidate();
if (Application.isPlaying)
{
HookEvents();
}
Invalidate();
}
protected override void ClearChart()
{
base.ClearChart();
mActiveTexts.Clear();
mTexts.Clear();
}
protected override void LateUpdate()
{
base.LateUpdate();
}
public bool ItemToWorldPosition(string group, double amount, out Vector3 worldposition)
{
worldposition = Vector3.zero;
if (mDirections == null || mDirections.Length == 0)
return false;
int index = Data.GetGroupIndex(group);
Vector3 dir = mDirections[index];
double max = Data.GetMaxValue();
worldposition = ((float)(amount / max) * Radius) * dir;
worldposition = transform.TransformPoint(worldposition);
return true;
}
public bool SnapWorldPointToPosition(Vector3 worldSpace,out string group,out double amount)
{
group = null;
amount = 0f;
if (mDirections == null || mDirections.Length == 0)
return false;
Vector3 pos = transform.InverseTransformPoint(worldSpace);
pos.z = 0;
// Vector3 dir = mDirections[0];
group = Data.GetGroupName(0);
if (Math.Abs(pos.x) < 0.001f && Math.Abs(pos.y) < 0.001f)
{
//zero vector do nothing we are taking the first direction in the array
}
else
{
float dot = float.MinValue;
for (int i = 0; i < mDirections.Length; i++)
{
float newDot = Vector3.Dot(mDirections[i], pos);
if(newDot > dot)
{
dot = newDot;
// dir = mDirections[i];
group = Data.GetGroupName(i);
}
}
}
float mag = pos.magnitude;
double max = Data.GetMaxValue();
amount = ((mag/Radius) * max);
amount = Math.Max(0, Math.Min(max, amount));
return true;
}
public override void InternalGenerateChart()
{
if (gameObject.activeInHierarchy == false)
return;
ClearChart();
base.InternalGenerateChart();
if (((IInternalRadarData)Data).InternalDataSource == null)
return;
double[,] data = ((IInternalRadarData)Data).InternalDataSource.getRawData();
int rowCount = data.GetLength(0);
int columnCount = data.GetLength(1);
//restrict to 3 groups
if (rowCount <3)
return;
mDirections = new Vector3[rowCount];
float[] angles = new float[rowCount];
for (int i = 0; i < rowCount; i++)
{
float angle = (float)((((float)i) / (float)rowCount) * Math.PI * 2f) + Angle * Mathf.Deg2Rad;
angles[i] = angle;
mDirections[i] = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0f);
}
Vector3[] path = new Vector3[rowCount];
Vector3 zAdd = Vector3.zero;
for (int i = 0; i < TotalAxisDevisions; i++)
{
float rad = Radius * ((float)(i + 1) / (float)TotalAxisDevisions);
for (int j = 0; j < rowCount; j++)
path[j] = (mDirections[j] * rad) + zAdd;
// path[rowCount] = path[0];
zAdd.z += AxisAdd;
GameObject axisObject = CreateAxisObject(AxisThickness, path);
mAxisObjects.Add(axisObject);
axisObject.transform.SetParent(transform, false);
}
if (mGroupLabels != null && mGroupLabels.isActiveAndEnabled)
{
for (int i = 0; i < rowCount; i++)
{
string group = Data.GetGroupName(i);
Vector3 basePosition = mDirections[i] * Radius;
Vector3 breadthAxis = Vector3.Cross(mDirections[i], Vector3.forward);
Vector3 position = basePosition + mDirections[i] * mGroupLabels.Seperation;
position += breadthAxis * mGroupLabels.Location.Breadth;
position += new Vector3(0f , 0f, mGroupLabels.Location.Depth);
string toSet = mGroupLabels.TextFormat.Format(group, "", "");
BillboardText billboard = ChartCommon.CreateBillboardText(null,mGroupLabels.TextPrefab, transform, toSet, position.x, position.y, position.z, angles[i],transform,hideHierarchy, mGroupLabels.FontSize, mGroupLabels.FontSharpness);
billboard.UserData = group;
TextController.AddText(billboard);
}
}
double maxValue = Data.GetMaxValue();
double minValue = Data.GetMinValue();
if (maxValue > 0.000001f)
{
for (int i = 0; i < columnCount; i++)
{
double finalMaxValue = DataSource.GetCategoryMaxValue(i);
if (finalMaxValue < 0f)
finalMaxValue = maxValue;
for (int j = 0; j < rowCount; j++)
{
float rad = ((float)((data[j, i]- minValue) / (finalMaxValue - minValue))) * Radius;
path[j] = mDirections[j] * rad;
}
// path[rowCount] = path[0];
GameObject category = CreateCategoryObject(path, i);
category.transform.SetParent(transform, false);
}
if (mItemLabels != null && mItemLabels.isActiveAndEnabled)
{
float angle = mItemLabels.Location.Breadth;
float blend = (angle / 360f);
blend -= Mathf.Floor(blend);
blend *= rowCount;
int index = (int)blend;
int nextIndex = (index + 1) % rowCount;
blend = blend - Mathf.Floor(blend);
for (int i = 0; i < TotalAxisDevisions; i++)
{
float factor = ((float)(i + 1) / (float)TotalAxisDevisions);
float rad = Radius * factor + mItemLabels.Seperation;
string value = ChartAdancedSettings.Instance.FormatFractionDigits(mItemLabels.FractionDigits,(float)(Mathf.Lerp((float)minValue,(float)maxValue,factor)), CustomNumberFormat);
Vector3 position = Vector3.Lerp(mDirections[index] , mDirections[nextIndex] , blend) * rad;
position.z = mItemLabels.Location.Depth;
string toSet = mItemLabels.TextFormat.Format(value, "", "");
BillboardText billboard = ChartCommon.CreateBillboardText(null,mItemLabels.TextPrefab, transform, toSet, position.x, position.y, position.z,0f, transform,hideHierarchy, mItemLabels.FontSize, mItemLabels.FontSharpness);
billboard.UserData = (float)(maxValue * factor);
TextController.AddText(billboard);
}
}
}
}
protected override void OnItemSelected(object userData)
{
base.OnItemSelected(userData);
RadarEventArgs args = userData as RadarEventArgs;
if (args == null)
return;
mOccupiedCateogies.Add(args.Category);
if(PointClicked != null)
PointClicked.Invoke(args);
}
protected override void OnItemLeave(object userData,string type)
{
base.OnItemLeave(userData, type);
RadarEventArgs args = userData as RadarEventArgs;
foreach (BillboardText t in mActiveTexts)
{
foreach (ChartItemEffect effect in t.UIText.GetComponents<ChartItemEffect>())
{
if (t.UIText == null)
continue;
effect.TriggerOut(false);
}
}
mActiveTexts.Clear();
string category = args.Category;
mOccupiedCateogies.Remove(category);
mOccupiedCateogies.RemoveWhere(x => !Data.HasCategory(x));
if (mOccupiedCateogies.Count == 0)
{
if (NonHovered != null)
NonHovered.Invoke();
}
}
protected override void OnItemHoverted(object userData)
{
base.OnItemHoverted(userData);
List<BillboardText> catgoryTexts;
RadarEventArgs args = userData as RadarEventArgs;
if (args == null)
return;
foreach (BillboardText t in mActiveTexts)
{
if (t.UIText == null)
continue;
foreach (ChartItemEffect effect in t.UIText.GetComponents<ChartItemEffect>())
effect.TriggerOut(false);
}
mActiveTexts.Clear();
if (mTexts.TryGetValue(args.Category, out catgoryTexts))
{
if (args.Index < catgoryTexts.Count)
{
BillboardText b = catgoryTexts[args.Index];
mActiveTexts.Add(b);
GameObject t = b.UIText;
if (t != null)
{
foreach (ChartItemEffect effect in t.GetComponents<ChartItemEffect>())
effect.TriggerIn(false);
}
}
}
mOccupiedCateogies.Add(args.Category);
if(PointHovered != null)
PointHovered.Invoke(args);
}
protected abstract GameObject CreateCategoryObject(Vector3[] path, int category);
protected abstract GameObject CreateAxisObject(float thickness, Vector3[] path);
protected override bool HasValues(AxisBase axis)
{
return false;
}
protected override double MaxValue(AxisBase axis)
{
return 0.0;
}
protected override double MinValue(AxisBase axis)
{
return 0.0;
}
}
}