mirror of
https://github.com/XCharts-Team/XCharts.git
synced 2026-05-16 13:30:10 +00:00
340 lines
14 KiB
C#
340 lines
14 KiB
C#
|
|
/******************************************/
|
|
/* */
|
|
/* Copyright (c) 2018 monitor1394 */
|
|
/* https://github.com/monitor1394 */
|
|
/* */
|
|
/******************************************/
|
|
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace XCharts
|
|
{
|
|
[AddComponentMenu("XCharts/RingChart", 20)]
|
|
[ExecuteInEditMode]
|
|
[RequireComponent(typeof(RectTransform))]
|
|
[DisallowMultipleComponent]
|
|
public partial class RingChart : BaseChart
|
|
{
|
|
private bool m_UpdateTitleText = false;
|
|
private bool m_UpdateLabelText = false;
|
|
private bool m_IsEnterLegendButtom;
|
|
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
if (m_UpdateTitleText)
|
|
{
|
|
m_UpdateTitleText = false;
|
|
TitleStyleHelper.UpdateTitleText(m_Series);
|
|
}
|
|
if (m_UpdateLabelText)
|
|
{
|
|
m_UpdateLabelText = false;
|
|
SerieLabelHelper.UpdateLabelText(m_Series, m_ThemeInfo);
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
protected override void Reset()
|
|
{
|
|
base.Reset();
|
|
m_Title.text = "RingChart";
|
|
m_Tooltip.type = Tooltip.Type.Line;
|
|
RemoveData();
|
|
var serie = AddSerie(SerieType.Ring, "serie1");
|
|
serie.roundCap = true;
|
|
serie.radius = new float[] { 0.3f, 0.35f };
|
|
serie.titleStyle.show = false;
|
|
serie.titleStyle.textStyle.offset = new Vector2(0, 30);
|
|
serie.label.show = true;
|
|
serie.label.position = SerieLabel.Position.Center;
|
|
serie.label.formatter = "{d:f0}%";
|
|
serie.label.fontSize = 28;
|
|
var value = Random.Range(30, 90);
|
|
var max = 100;
|
|
AddData(0, value, max, "data1");
|
|
}
|
|
#endif
|
|
|
|
protected override void DrawChart(VertexHelper vh)
|
|
{
|
|
base.DrawChart(vh);
|
|
for (int i = 0; i < m_Series.list.Count; i++)
|
|
{
|
|
var serie = m_Series.list[i];
|
|
var data = serie.data;
|
|
serie.index = i;
|
|
if (!serie.show || serie.type != SerieType.Ring || serie.animation.HasFadeOut())
|
|
{
|
|
continue;
|
|
}
|
|
serie.animation.InitProgress(data.Count, serie.startAngle, serie.startAngle + 360);
|
|
SerieHelper.UpdateCenter(serie, chartPosition, chartWidth, chartHeight);
|
|
TitleStyleHelper.CheckTitle(serie, ref m_ReinitTitle, ref m_UpdateTitleText);
|
|
SerieLabelHelper.CheckLabel(serie, ref m_ReinitLabel, ref m_UpdateLabelText);
|
|
var dataChangeDuration = serie.animation.GetUpdateAnimationDuration();
|
|
var ringWidth = serie.runtimeOutsideRadius - serie.runtimeInsideRadius;
|
|
var dataChanging = false;
|
|
for (int j = 0; j < data.Count; j++)
|
|
{
|
|
var serieData = data[j];
|
|
if (!serieData.show) continue;
|
|
if (serieData.IsDataChanged()) dataChanging = true;
|
|
var value = serieData.GetFirstData(dataChangeDuration);
|
|
var max = serieData.GetLastData();
|
|
var degree = 360 * value / max;
|
|
var startDegree = GetStartAngle(serie);
|
|
var toDegree = GetToAngle(serie, degree);
|
|
var itemStyle = SerieHelper.GetItemStyle(serie, serieData, serieData.highlighted);
|
|
var itemColor = SerieHelper.GetItemColor(serie, serieData, m_ThemeInfo, j, serieData.highlighted);
|
|
var outsideRadius = serie.runtimeOutsideRadius - j * (ringWidth + serie.ringGap);
|
|
var insideRadius = outsideRadius - ringWidth;
|
|
var centerRadius = (outsideRadius + insideRadius) / 2;
|
|
var borderWidth = itemStyle.borderWidth;
|
|
var borderColor = itemStyle.borderColor;
|
|
var roundCap = serie.roundCap && insideRadius > 0;
|
|
|
|
serieData.runtimePieStartAngle = serie.clockwise ? startDegree : toDegree;
|
|
serieData.runtimePieToAngle = serie.clockwise ? toDegree : startDegree;
|
|
serieData.runtimePieInsideRadius = insideRadius;
|
|
serieData.runtimePieOutsideRadius = outsideRadius;
|
|
ChartDrawer.DrawDoughnut(vh, serie.runtimeCenterPos, insideRadius, outsideRadius, itemColor, itemColor,
|
|
Color.clear, startDegree, toDegree, borderWidth, borderColor, 0, m_Settings.cicleSmoothness,
|
|
roundCap, serie.clockwise);
|
|
DrawCenter(vh, serie, serieData, insideRadius, j == data.Count - 1);
|
|
UpateLabelPosition(serie, serieData, j, startDegree, toDegree, centerRadius);
|
|
}
|
|
if (!serie.animation.IsFinish())
|
|
{
|
|
serie.animation.CheckProgress(360);
|
|
serie.animation.CheckSymbol(serie.symbol.size);
|
|
RefreshChart();
|
|
}
|
|
if (dataChanging)
|
|
{
|
|
RefreshChart();
|
|
}
|
|
}
|
|
}
|
|
|
|
private float GetStartAngle(Serie serie)
|
|
{
|
|
return serie.clockwise ? serie.startAngle : 360 - serie.startAngle;
|
|
}
|
|
|
|
private float GetToAngle(Serie serie, float angle)
|
|
{
|
|
var toAngle = angle + serie.startAngle;
|
|
if (!serie.clockwise)
|
|
{
|
|
toAngle = 360 - angle - serie.startAngle;
|
|
}
|
|
if (!serie.animation.IsFinish())
|
|
{
|
|
var currAngle = serie.animation.GetCurrDetail();
|
|
if (serie.clockwise)
|
|
{
|
|
toAngle = toAngle > currAngle ? currAngle : toAngle;
|
|
}
|
|
else
|
|
{
|
|
toAngle = toAngle < 360 - currAngle ? 360 - currAngle : toAngle;
|
|
}
|
|
}
|
|
return toAngle;
|
|
}
|
|
|
|
private void DrawCenter(VertexHelper vh, Serie serie, SerieData serieData, float insideRadius, bool last)
|
|
{
|
|
var itemStyle = SerieHelper.GetItemStyle(serie, serieData);
|
|
if (!ChartHelper.IsClearColor(itemStyle.centerColor) && last)
|
|
{
|
|
var radius = insideRadius - itemStyle.centerGap;
|
|
var smoothness = m_Settings.cicleSmoothness;
|
|
ChartDrawer.DrawCricle(vh, serie.runtimeCenterPos, radius, itemStyle.centerColor, smoothness);
|
|
}
|
|
}
|
|
|
|
private void UpateLabelPosition(Serie serie, SerieData serieData, int index, float startAngle,
|
|
float toAngle, float centerRadius)
|
|
{
|
|
if (!serie.label.show) return;
|
|
if (serieData.labelObject == null) return;
|
|
switch (serie.label.position)
|
|
{
|
|
case SerieLabel.Position.Center:
|
|
serieData.labelPosition = serie.runtimeCenterPos + serie.label.offset;
|
|
break;
|
|
case SerieLabel.Position.Bottom:
|
|
var px1 = Mathf.Sin(startAngle * Mathf.Deg2Rad) * centerRadius;
|
|
var py1 = Mathf.Cos(startAngle * Mathf.Deg2Rad) * centerRadius;
|
|
var xDiff = serie.clockwise ? -serie.label.margin : serie.label.margin;
|
|
serieData.labelPosition = serie.runtimeCenterPos + new Vector3(px1 + xDiff, py1);
|
|
break;
|
|
case SerieLabel.Position.Top:
|
|
startAngle += serie.clockwise ? -serie.label.margin : serie.label.margin;
|
|
toAngle += serie.clockwise ? serie.label.margin : -serie.label.margin;
|
|
var px2 = Mathf.Sin(toAngle * Mathf.Deg2Rad) * centerRadius;
|
|
var py2 = Mathf.Cos(toAngle * Mathf.Deg2Rad) * centerRadius;
|
|
serieData.labelPosition = serie.runtimeCenterPos + new Vector3(px2, py2);
|
|
break;
|
|
}
|
|
serieData.labelObject.SetLabelPosition(serieData.labelPosition);
|
|
}
|
|
|
|
private void DrawBackground(VertexHelper vh, Serie serie, SerieData serieData, int index, float insideRadius, float outsideRadius)
|
|
{
|
|
var itemStyle = SerieHelper.GetItemStyle(serie, serieData);
|
|
var backgroundColor = SerieHelper.GetItemBackgroundColor(serie, serieData, m_ThemeInfo, index, false);
|
|
if (itemStyle.backgroundWidth != 0)
|
|
{
|
|
var centerRadius = (outsideRadius + insideRadius) / 2;
|
|
var inradius = centerRadius - itemStyle.backgroundWidth / 2;
|
|
var outradius = centerRadius + itemStyle.backgroundWidth / 2;
|
|
ChartDrawer.DrawDoughnut(vh, serie.runtimeCenterPos, inradius,
|
|
outradius, backgroundColor, Color.clear, m_Settings.cicleSmoothness);
|
|
}
|
|
else
|
|
{
|
|
ChartDrawer.DrawDoughnut(vh, serie.runtimeCenterPos, insideRadius,
|
|
outsideRadius, backgroundColor, Color.clear, m_Settings.cicleSmoothness);
|
|
}
|
|
}
|
|
|
|
private void DrawBorder(VertexHelper vh, Serie serie, SerieData serieData, float insideRadius, float outsideRadius)
|
|
{
|
|
var itemStyle = SerieHelper.GetItemStyle(serie, serieData);
|
|
if (itemStyle.show && itemStyle.borderWidth > 0 && !ChartHelper.IsClearColor(itemStyle.borderColor))
|
|
{
|
|
ChartDrawer.DrawDoughnut(vh, serie.runtimeCenterPos, outsideRadius,
|
|
outsideRadius + itemStyle.borderWidth, itemStyle.borderColor,
|
|
Color.clear, m_Settings.cicleSmoothness);
|
|
ChartDrawer.DrawDoughnut(vh, serie.runtimeCenterPos, insideRadius,
|
|
insideRadius + itemStyle.borderWidth, itemStyle.borderColor,
|
|
Color.clear, m_Settings.cicleSmoothness);
|
|
}
|
|
}
|
|
|
|
|
|
private void DrawRoundCap(VertexHelper vh, Serie serie, Vector3 centerPos, Color color,
|
|
float insideRadius, float outsideRadius, ref float drawStartDegree, ref float drawEndDegree)
|
|
{
|
|
if (serie.roundCap && insideRadius > 0 && drawStartDegree != drawEndDegree)
|
|
{
|
|
var width = (outsideRadius - insideRadius) / 2;
|
|
var radius = insideRadius + width;
|
|
|
|
var diffDegree = Mathf.Asin(width / radius) * Mathf.Rad2Deg;
|
|
drawStartDegree += serie.clockwise ? diffDegree : -diffDegree;
|
|
drawEndDegree -= serie.clockwise ? diffDegree : -diffDegree;
|
|
ChartDrawer.DrawRoundCap(vh, centerPos, width, radius, drawStartDegree, serie.clockwise, color, false);
|
|
ChartDrawer.DrawRoundCap(vh, centerPos, width, radius, drawEndDegree, serie.clockwise, color, true);
|
|
}
|
|
}
|
|
|
|
protected override void OnLegendButtonClick(int index, string legendName, bool show)
|
|
{
|
|
LegendHelper.CheckDataShow(m_Series, legendName, show);
|
|
UpdateLegendColor(legendName, show);
|
|
RefreshChart();
|
|
}
|
|
|
|
protected override void OnLegendButtonEnter(int index, string legendName)
|
|
{
|
|
m_IsEnterLegendButtom = true;
|
|
LegendHelper.CheckDataHighlighted(m_Series, legendName, true);
|
|
RefreshChart();
|
|
}
|
|
|
|
protected override void OnLegendButtonExit(int index, string legendName)
|
|
{
|
|
m_IsEnterLegendButtom = false;
|
|
LegendHelper.CheckDataHighlighted(m_Series, legendName, false);
|
|
RefreshChart();
|
|
}
|
|
|
|
protected override void CheckTootipArea(Vector2 local)
|
|
{
|
|
if (m_IsEnterLegendButtom) return;
|
|
m_Tooltip.runtimeDataIndex.Clear();
|
|
bool selected = false;
|
|
foreach (var serie in m_Series.list)
|
|
{
|
|
int index = GetRingIndex(serie, local);
|
|
m_Tooltip.runtimeDataIndex.Add(index);
|
|
if (serie.type != SerieType.Ring) continue;
|
|
bool refresh = false;
|
|
for (int j = 0; j < serie.data.Count; j++)
|
|
{
|
|
var serieData = serie.data[j];
|
|
if (serieData.highlighted != (j == index)) refresh = true;
|
|
serieData.highlighted = j == index;
|
|
}
|
|
if (index >= 0) selected = true;
|
|
if (refresh) RefreshChart();
|
|
}
|
|
if (selected)
|
|
{
|
|
m_Tooltip.UpdateContentPos(new Vector2(local.x + 18, local.y - 25));
|
|
UpdateTooltip();
|
|
}
|
|
else if (m_Tooltip.IsActive())
|
|
{
|
|
m_Tooltip.SetActive(false);
|
|
RefreshChart();
|
|
}
|
|
}
|
|
|
|
private int GetRingIndex(Serie serie, Vector2 local)
|
|
{
|
|
if (serie.type != SerieType.Ring) return -1;
|
|
var dist = Vector2.Distance(local, serie.runtimeCenterPos);
|
|
if (dist > serie.runtimeOutsideRadius) return -1;
|
|
Vector2 dir = local - new Vector2(serie.runtimeCenterPos.x, serie.runtimeCenterPos.y);
|
|
float angle = VectorAngle(Vector2.up, dir);
|
|
for (int i = 0; i < serie.data.Count; i++)
|
|
{
|
|
var serieData = serie.data[i];
|
|
if (dist >= serieData.runtimePieInsideRadius &&
|
|
dist <= serieData.runtimePieOutsideRadius &&
|
|
angle >= serieData.runtimePieStartAngle &&
|
|
angle <= serieData.runtimePieToAngle)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
float VectorAngle(Vector2 from, Vector2 to)
|
|
{
|
|
float angle;
|
|
|
|
Vector3 cross = Vector3.Cross(from, to);
|
|
angle = Vector2.Angle(from, to);
|
|
angle = cross.z > 0 ? -angle : angle;
|
|
angle = (angle + 360) % 360;
|
|
return angle;
|
|
}
|
|
|
|
protected override void UpdateTooltip()
|
|
{
|
|
base.UpdateTooltip();
|
|
bool showTooltip = false;
|
|
foreach (var serie in m_Series.list)
|
|
{
|
|
int index = m_Tooltip.runtimeDataIndex[serie.index];
|
|
if (index < 0) continue;
|
|
showTooltip = true;
|
|
var content = TooltipHelper.GetFormatterContent(m_Tooltip, index, m_Series, m_ThemeInfo);
|
|
TooltipHelper.SetContentAndPosition(tooltip,content,chartRect);
|
|
}
|
|
m_Tooltip.SetActive(showTooltip);
|
|
}
|
|
}
|
|
}
|