#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> mTexts = new Dictionary>(); protected HashSet mActiveTexts = new HashSet(); HashSet mOccupiedCateogies = new HashSet(); 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 addTo; if (mTexts.TryGetValue(cat, out addTo) == false) { addTo = new List(); 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 { } /// /// occures when a point is clicked /// public RadarEvent PointClicked = new RadarEvent(); /// /// occurs when a point is hovered /// public RadarEvent PointHovered = new RadarEvent(); /// /// occurs when no point is hovered any longer /// public UnityEvent NonHovered = new UnityEvent(); List mAxisObjects = new List(); /// /// the radar data /// [HideInInspector] [SerializeField] private RadarChartData Data = new RadarChartData(); /// /// Holds the radar chart data. including values, categories and groups. /// 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(); } /// /// used internally , do not call this method /// 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()) { 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 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()) 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()) 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; } } }