DrawSerieRing.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. /************************************************/
  2. /* */
  3. /* Copyright (c) 2018 - 2021 monitor1394 */
  4. /* https://github.com/monitor1394 */
  5. /* */
  6. /************************************************/
  7. using UnityEngine;
  8. using UnityEngine.UI;
  9. using UnityEngine.EventSystems;
  10. using XUGL;
  11. namespace XCharts
  12. {
  13. internal class DrawSerieRing : IDrawSerie
  14. {
  15. public BaseChart chart;
  16. private bool m_UpdateTitleText = false;
  17. private bool m_UpdateLabelText = false;
  18. private bool m_IsEnterLegendButtom;
  19. public DrawSerieRing(BaseChart chart)
  20. {
  21. this.chart = chart;
  22. }
  23. public void InitComponent()
  24. {
  25. }
  26. public void CheckComponent()
  27. {
  28. }
  29. public void Update()
  30. {
  31. if (m_UpdateTitleText)
  32. {
  33. m_UpdateTitleText = false;
  34. foreach (var serie in chart.series.list)
  35. {
  36. if (serie.type == SerieType.Ring)
  37. {
  38. TitleStyleHelper.UpdateTitleText(serie);
  39. }
  40. }
  41. }
  42. if (m_UpdateLabelText)
  43. {
  44. m_UpdateLabelText = false;
  45. foreach (var serie in chart.series.list)
  46. {
  47. if (serie.type == SerieType.Ring)
  48. {
  49. SerieLabelHelper.SetRingLabelText(serie, chart.theme);
  50. }
  51. }
  52. }
  53. }
  54. public void DrawBase(VertexHelper vh)
  55. {
  56. }
  57. public void DrawSerie(VertexHelper vh, Serie serie)
  58. {
  59. if (serie.type != SerieType.Ring) return;
  60. if (!serie.show || serie.animation.HasFadeOut()) return;
  61. var data = serie.data;
  62. serie.animation.InitProgress(data.Count, serie.startAngle, serie.startAngle + 360);
  63. SerieHelper.UpdateCenter(serie, chart.chartPosition, chart.chartWidth, chart.chartHeight);
  64. TitleStyleHelper.CheckTitle(serie, ref chart.m_ReinitTitle, ref m_UpdateTitleText);
  65. SerieLabelHelper.CheckLabel(serie, ref chart.m_ReinitLabel, ref m_UpdateLabelText);
  66. var dataChangeDuration = serie.animation.GetUpdateAnimationDuration();
  67. var ringWidth = serie.runtimeOutsideRadius - serie.runtimeInsideRadius;
  68. var dataChanging = false;
  69. for (int j = 0; j < data.Count; j++)
  70. {
  71. var serieData = data[j];
  72. if (!serieData.show) continue;
  73. if (serieData.IsDataChanged()) dataChanging = true;
  74. var value = serieData.GetFirstData(dataChangeDuration);
  75. var max = serieData.GetLastData();
  76. var degree = (float)(360 * value / max);
  77. var startDegree = GetStartAngle(serie);
  78. var toDegree = GetToAngle(serie, degree);
  79. var itemStyle = SerieHelper.GetItemStyle(serie, serieData, serieData.highlighted);
  80. var itemColor = SerieHelper.GetItemColor(serie, serieData, chart.theme, j, serieData.highlighted);
  81. var itemToColor = SerieHelper.GetItemToColor(serie, serieData, chart.theme, j, serieData.highlighted);
  82. var outsideRadius = serie.runtimeOutsideRadius - j * (ringWidth + serie.ringGap);
  83. var insideRadius = outsideRadius - ringWidth;
  84. var centerRadius = (outsideRadius + insideRadius) / 2;
  85. var borderWidth = itemStyle.borderWidth;
  86. var borderColor = itemStyle.borderColor;
  87. var roundCap = serie.roundCap && insideRadius > 0;
  88. serieData.runtimePieStartAngle = serie.clockwise ? startDegree : toDegree;
  89. serieData.runtimePieToAngle = serie.clockwise ? toDegree : startDegree;
  90. serieData.runtimePieInsideRadius = insideRadius;
  91. serieData.runtimePieOutsideRadius = outsideRadius;
  92. if (itemStyle.backgroundColor.a != 0)
  93. {
  94. UGL.DrawDoughnut(vh, serie.runtimeCenterPos, insideRadius, outsideRadius, itemStyle.backgroundColor,
  95. itemStyle.backgroundColor, Color.clear, 0, 360, borderWidth, borderColor, 0,
  96. chart.settings.cicleSmoothness, false, serie.clockwise);
  97. }
  98. var isGradient = !UGLHelper.IsValueEqualsColor(itemColor, itemToColor);
  99. if (isGradient)
  100. {
  101. if (serie.clockwise)
  102. itemToColor = Color.Lerp(itemColor, itemToColor, toDegree / (startDegree + 360));
  103. else
  104. itemToColor = Color.Lerp(itemToColor, itemColor, toDegree / (startDegree + 360));
  105. }
  106. UGL.DrawDoughnut(vh, serie.runtimeCenterPos, insideRadius, outsideRadius, itemColor, itemToColor,
  107. Color.clear, startDegree, toDegree, borderWidth, borderColor, 0, chart.settings.cicleSmoothness,
  108. roundCap, serie.clockwise);
  109. DrawCenter(vh, serie, serieData, insideRadius, j == data.Count - 1);
  110. UpateLabelPosition(serie, serieData, j, startDegree, toDegree, centerRadius);
  111. }
  112. if (!serie.animation.IsFinish())
  113. {
  114. serie.animation.CheckProgress(360);
  115. chart.RefreshChart();
  116. }
  117. if (dataChanging)
  118. {
  119. chart.RefreshChart();
  120. }
  121. }
  122. public void RefreshLabel()
  123. {
  124. }
  125. public bool CheckTootipArea(Vector2 local)
  126. {
  127. if (!chart.series.Contains(SerieType.Ring)) return false;
  128. if (!PointerIsInRingSerie(chart.series, local)) return false;
  129. if (m_IsEnterLegendButtom) return false;
  130. bool selected = false;
  131. chart.tooltip.runtimeDataIndex.Clear();
  132. foreach (var serie in chart.series.list)
  133. {
  134. int index = GetRingIndex(serie, local);
  135. chart.tooltip.runtimeDataIndex.Add(index);
  136. if (serie.type != SerieType.Ring) continue;
  137. bool refresh = false;
  138. for (int j = 0; j < serie.data.Count; j++)
  139. {
  140. var serieData = serie.data[j];
  141. if (serieData.highlighted != (j == index)) refresh = true;
  142. serieData.highlighted = j == index;
  143. }
  144. if (index >= 0) selected = true;
  145. if (refresh) chart.RefreshChart();
  146. }
  147. if (selected)
  148. {
  149. chart.tooltip.UpdateContentPos(local + chart.tooltip.offset);
  150. UpdateTooltip();
  151. }
  152. else if (chart.tooltip.IsActive())
  153. {
  154. chart.tooltip.SetActive(false);
  155. chart.RefreshChart();
  156. }
  157. return true;
  158. }
  159. public bool OnLegendButtonClick(int index, string legendName, bool show)
  160. {
  161. if (!SeriesHelper.ContainsSerie(chart.series, SerieType.Ring)) return false;
  162. if (!LegendHelper.IsSerieLegend(chart, legendName, SerieType.Ring)) return false;
  163. LegendHelper.CheckDataShow(chart.series, legendName, show);
  164. chart.UpdateLegendColor(legendName, show);
  165. chart.RefreshChart();
  166. return true;
  167. }
  168. public bool OnLegendButtonEnter(int index, string legendName)
  169. {
  170. if (!SeriesHelper.ContainsSerie(chart.series, SerieType.Ring)) return false;
  171. if (!LegendHelper.IsSerieLegend(chart, legendName, SerieType.Ring)) return false;
  172. m_IsEnterLegendButtom = true;
  173. LegendHelper.CheckDataHighlighted(chart.series, legendName, true);
  174. chart.RefreshChart();
  175. return true;
  176. }
  177. public bool OnLegendButtonExit(int index, string legendName)
  178. {
  179. if (!SeriesHelper.ContainsSerie(chart.series, SerieType.Ring)) return false;
  180. if (!LegendHelper.IsSerieLegend(chart, legendName, SerieType.Ring)) return false;
  181. m_IsEnterLegendButtom = false;
  182. LegendHelper.CheckDataHighlighted(chart.series, legendName, false);
  183. chart.RefreshChart();
  184. return true;
  185. }
  186. public void OnPointerDown(PointerEventData eventData)
  187. {
  188. }
  189. private float GetStartAngle(Serie serie)
  190. {
  191. return serie.clockwise ? serie.startAngle : 360 - serie.startAngle;
  192. }
  193. private float GetToAngle(Serie serie, float angle)
  194. {
  195. var toAngle = angle + serie.startAngle;
  196. if (!serie.clockwise)
  197. {
  198. toAngle = 360 - angle - serie.startAngle;
  199. }
  200. if (!serie.animation.IsFinish())
  201. {
  202. var currAngle = serie.animation.GetCurrDetail();
  203. if (serie.clockwise)
  204. {
  205. toAngle = toAngle > currAngle ? currAngle : toAngle;
  206. }
  207. else
  208. {
  209. toAngle = toAngle < 360 - currAngle ? 360 - currAngle : toAngle;
  210. }
  211. }
  212. return toAngle;
  213. }
  214. private void DrawCenter(VertexHelper vh, Serie serie, SerieData serieData, float insideRadius, bool last)
  215. {
  216. var itemStyle = SerieHelper.GetItemStyle(serie, serieData);
  217. if (!ChartHelper.IsClearColor(itemStyle.centerColor) && last)
  218. {
  219. var radius = insideRadius - itemStyle.centerGap;
  220. var smoothness = chart.settings.cicleSmoothness;
  221. UGL.DrawCricle(vh, serie.runtimeCenterPos, radius, itemStyle.centerColor, smoothness);
  222. }
  223. }
  224. private void UpateLabelPosition(Serie serie, SerieData serieData, int index, float startAngle,
  225. float toAngle, float centerRadius)
  226. {
  227. if (!serie.label.show) return;
  228. if (serieData.labelObject == null) return;
  229. switch (serie.label.position)
  230. {
  231. case SerieLabel.Position.Center:
  232. serieData.labelPosition = serie.runtimeCenterPos + serie.label.offset;
  233. break;
  234. case SerieLabel.Position.Bottom:
  235. var px1 = Mathf.Sin(startAngle * Mathf.Deg2Rad) * centerRadius;
  236. var py1 = Mathf.Cos(startAngle * Mathf.Deg2Rad) * centerRadius;
  237. var xDiff = serie.clockwise ? -serie.label.margin : serie.label.margin;
  238. serieData.labelPosition = serie.runtimeCenterPos + new Vector3(px1 + xDiff, py1);
  239. break;
  240. case SerieLabel.Position.Top:
  241. startAngle += serie.clockwise ? -serie.label.margin : serie.label.margin;
  242. toAngle += serie.clockwise ? serie.label.margin : -serie.label.margin;
  243. var px2 = Mathf.Sin(toAngle * Mathf.Deg2Rad) * centerRadius;
  244. var py2 = Mathf.Cos(toAngle * Mathf.Deg2Rad) * centerRadius;
  245. serieData.labelPosition = serie.runtimeCenterPos + new Vector3(px2, py2);
  246. break;
  247. }
  248. serieData.labelObject.SetLabelPosition(serieData.labelPosition);
  249. }
  250. private void DrawBackground(VertexHelper vh, Serie serie, SerieData serieData, int index, float insideRadius, float outsideRadius)
  251. {
  252. var itemStyle = SerieHelper.GetItemStyle(serie, serieData);
  253. var backgroundColor = SerieHelper.GetItemBackgroundColor(serie, serieData, chart.theme, index, false);
  254. if (itemStyle.backgroundWidth != 0)
  255. {
  256. var centerRadius = (outsideRadius + insideRadius) / 2;
  257. var inradius = centerRadius - itemStyle.backgroundWidth / 2;
  258. var outradius = centerRadius + itemStyle.backgroundWidth / 2;
  259. UGL.DrawDoughnut(vh, serie.runtimeCenterPos, inradius,
  260. outradius, backgroundColor, Color.clear, chart.settings.cicleSmoothness);
  261. }
  262. else
  263. {
  264. UGL.DrawDoughnut(vh, serie.runtimeCenterPos, insideRadius,
  265. outsideRadius, backgroundColor, Color.clear, chart.settings.cicleSmoothness);
  266. }
  267. }
  268. private void DrawBorder(VertexHelper vh, Serie serie, SerieData serieData, float insideRadius, float outsideRadius)
  269. {
  270. var itemStyle = SerieHelper.GetItemStyle(serie, serieData);
  271. if (itemStyle.show && itemStyle.borderWidth > 0 && !ChartHelper.IsClearColor(itemStyle.borderColor))
  272. {
  273. UGL.DrawDoughnut(vh, serie.runtimeCenterPos, outsideRadius,
  274. outsideRadius + itemStyle.borderWidth, itemStyle.borderColor,
  275. Color.clear, chart.settings.cicleSmoothness);
  276. UGL.DrawDoughnut(vh, serie.runtimeCenterPos, insideRadius,
  277. insideRadius + itemStyle.borderWidth, itemStyle.borderColor,
  278. Color.clear, chart.settings.cicleSmoothness);
  279. }
  280. }
  281. private void DrawRoundCap(VertexHelper vh, Serie serie, Vector3 centerPos, Color color,
  282. float insideRadius, float outsideRadius, ref float drawStartDegree, ref float drawEndDegree)
  283. {
  284. if (serie.roundCap && insideRadius > 0 && drawStartDegree != drawEndDegree)
  285. {
  286. var width = (outsideRadius - insideRadius) / 2;
  287. var radius = insideRadius + width;
  288. var diffDegree = Mathf.Asin(width / radius) * Mathf.Rad2Deg;
  289. drawStartDegree += serie.clockwise ? diffDegree : -diffDegree;
  290. drawEndDegree -= serie.clockwise ? diffDegree : -diffDegree;
  291. UGL.DrawRoundCap(vh, centerPos, width, radius, drawStartDegree, serie.clockwise, color, false);
  292. UGL.DrawRoundCap(vh, centerPos, width, radius, drawEndDegree, serie.clockwise, color, true);
  293. }
  294. }
  295. private int GetRingIndex(Serie serie, Vector2 local)
  296. {
  297. if (serie.type != SerieType.Ring) return -1;
  298. var dist = Vector2.Distance(local, serie.runtimeCenterPos);
  299. if (dist > serie.runtimeOutsideRadius) return -1;
  300. Vector2 dir = local - new Vector2(serie.runtimeCenterPos.x, serie.runtimeCenterPos.y);
  301. float angle = VectorAngle(Vector2.up, dir);
  302. for (int i = 0; i < serie.data.Count; i++)
  303. {
  304. var serieData = serie.data[i];
  305. if (dist >= serieData.runtimePieInsideRadius &&
  306. dist <= serieData.runtimePieOutsideRadius &&
  307. angle >= serieData.runtimePieStartAngle &&
  308. angle <= serieData.runtimePieToAngle)
  309. {
  310. return i;
  311. }
  312. }
  313. return -1;
  314. }
  315. private bool PointerIsInRingSerie(Series series, Vector2 local)
  316. {
  317. foreach (var serie in series.list)
  318. {
  319. if (serie.type != SerieType.Ring) continue;
  320. if (GetRingIndex(serie, local) >= 0) return true;
  321. }
  322. return false;
  323. }
  324. private float VectorAngle(Vector2 from, Vector2 to)
  325. {
  326. float angle;
  327. Vector3 cross = Vector3.Cross(from, to);
  328. angle = Vector2.Angle(from, to);
  329. angle = cross.z > 0 ? -angle : angle;
  330. angle = (angle + 360) % 360;
  331. return angle;
  332. }
  333. private void UpdateTooltip()
  334. {
  335. bool showTooltip = false;
  336. foreach (var serie in chart.series.list)
  337. {
  338. int index = chart.tooltip.runtimeDataIndex[serie.index];
  339. if (index < 0) continue;
  340. showTooltip = true;
  341. var content = TooltipHelper.GetFormatterContent(chart.tooltip, index, chart);
  342. TooltipHelper.SetContentAndPosition(chart.tooltip, content.TrimStart(), chart.chartRect);
  343. }
  344. chart.tooltip.SetActive(showTooltip);
  345. }
  346. }
  347. }