Files
XCharts/Runtime/RingChart.cs
2020-07-23 12:53:10 +08:00

341 lines
15 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, m_LegendRealShowName);
}
}
#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 itemToColor = SerieHelper.GetItemToColor(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, itemToColor,
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(local + m_Tooltip.offset);
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);
}
}
}