RingHandler.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. using System.Collections.Generic;
  2. using System.Text;
  3. using UnityEngine;
  4. using UnityEngine.EventSystems;
  5. using UnityEngine.UI;
  6. using XUGL;
  7. namespace XCharts.Runtime
  8. {
  9. [UnityEngine.Scripting.Preserve]
  10. internal sealed class RingHandler : SerieHandler<Ring>
  11. {
  12. public override int defaultDimension { get { return 0; } }
  13. public override void Update()
  14. {
  15. base.Update();
  16. }
  17. public override void UpdateSerieContext()
  18. {
  19. var needCheck = chart.isPointerInChart || m_LegendEnter;
  20. var needInteract = false;
  21. if (!needCheck)
  22. {
  23. if (m_LastCheckContextFlag != needCheck)
  24. {
  25. m_LastCheckContextFlag = needCheck;
  26. serie.context.pointerItemDataIndex = -1;
  27. serie.context.pointerEnter = false;
  28. foreach (var serieData in serie.data)
  29. {
  30. serieData.context.highlight = false;
  31. }
  32. chart.RefreshPainter(serie);
  33. }
  34. return;
  35. }
  36. m_LastCheckContextFlag = needCheck;
  37. if (m_LegendEnter)
  38. {
  39. serie.context.pointerEnter = true;
  40. foreach (var serieData in serie.data)
  41. {
  42. serieData.context.highlight = true;
  43. }
  44. }
  45. else
  46. {
  47. serie.context.pointerEnter = false;
  48. serie.context.pointerItemDataIndex = -1;
  49. var ringIndex = GetRingIndex(chart.pointerPos);
  50. foreach (var serieData in serie.data)
  51. {
  52. if (!needInteract && ringIndex == serieData.index)
  53. {
  54. serie.context.pointerEnter = true;
  55. serie.context.pointerItemDataIndex = ringIndex;
  56. serieData.context.highlight = true;
  57. needInteract = true;
  58. }
  59. else
  60. {
  61. serieData.context.highlight = false;
  62. }
  63. }
  64. }
  65. if (needInteract)
  66. {
  67. chart.RefreshPainter(serie);
  68. }
  69. }
  70. public override void UpdateTooltipSerieParams(int dataIndex, bool showCategory, string category,
  71. string marker, string itemFormatter, string numericFormatter, string ignoreDataDefaultContent,
  72. ref List<SerieParams> paramList, ref string title)
  73. {
  74. if (dataIndex < 0)
  75. dataIndex = serie.context.pointerItemDataIndex;
  76. if (dataIndex < 0)
  77. return;
  78. var serieData = serie.GetSerieData(dataIndex);
  79. if (serieData == null)
  80. return;
  81. Color32 color, toColor;
  82. SerieHelper.GetItemColor(out color, out toColor, serie, serieData, chart.theme, dataIndex);
  83. var param = serie.context.param;
  84. param.serieName = serie.serieName;
  85. param.serieIndex = serie.index;
  86. param.category = category;
  87. param.dimension = defaultDimension;
  88. param.serieData = serieData;
  89. param.dataCount = serie.dataCount;
  90. param.value = serieData.GetData(0);
  91. param.total = serieData.GetData(1);
  92. param.color = color;
  93. param.marker = SerieHelper.GetItemMarker(serie, serieData, marker);
  94. param.itemFormatter = SerieHelper.GetItemFormatter(serie, serieData, itemFormatter);
  95. param.numericFormatter = SerieHelper.GetNumericFormatter(serie, serieData, numericFormatter);
  96. param.columns.Clear();
  97. param.columns.Add(param.marker);
  98. param.columns.Add(serieData.name);
  99. param.columns.Add(ChartCached.NumberToStr(param.value, param.numericFormatter));
  100. paramList.Add(param);
  101. }
  102. private Vector3 GetLabelLineEndPosition(Serie serie, SerieData serieData, LabelLine labelLine)
  103. {
  104. if (labelLine == null || !labelLine.show)
  105. return serieData.context.labelLinePosition;
  106. var isRight = !serie.clockwise;
  107. var dire = isRight ? Vector3.right : Vector3.left;
  108. var rad = Mathf.Deg2Rad * (isRight ? labelLine.lineAngle : 180 - labelLine.lineAngle);
  109. var lineLength1 = ChartHelper.GetActualValue(labelLine.lineLength1, serie.context.outsideRadius);
  110. var lineLength2 = ChartHelper.GetActualValue(labelLine.lineLength2, serie.context.outsideRadius);
  111. var pos1 = serieData.context.labelLinePosition;
  112. var pos2 = pos1 + new Vector3(Mathf.Cos(rad) * lineLength1, Mathf.Sin(rad) * lineLength1);
  113. var pos5 = labelLine.lineType == LabelLine.LineType.HorizontalLine
  114. ? pos1 + dire * (lineLength1 + lineLength2) + labelLine.GetEndSymbolOffset()
  115. : pos2 + dire * lineLength2 + labelLine.GetEndSymbolOffset();
  116. if (labelLine.lineEndX != 0)
  117. {
  118. pos5.x = labelLine.lineEndX;
  119. }
  120. return pos5;
  121. }
  122. public override void DrawSerie(VertexHelper vh)
  123. {
  124. if (!serie.show || serie.animation.HasFadeOut()) return;
  125. UpdateRuntimeData();
  126. var data = serie.data;
  127. serie.animation.InitProgress(serie.startAngle, serie.startAngle + 360);
  128. var ringWidth = serie.context.outsideRadius - serie.context.insideRadius;
  129. var dataChanging = false;
  130. for (int j = 0; j < data.Count; j++)
  131. {
  132. var serieData = data[j];
  133. if (!serieData.show) continue;
  134. if (serieData.IsDataChanged()) dataChanging = true;
  135. var outsideRadius = serie.context.outsideRadius - j * (ringWidth + serie.gap);
  136. if (outsideRadius < 0) continue;
  137. var value = serieData.GetCurrData(0, serie.animation, false, false);
  138. var max = serieData.GetLastData();
  139. var degree = (float)(360 * value / max);
  140. var startDegree = GetStartAngle(serie);
  141. var toDegree = GetToAngle(serie, degree);
  142. var itemStyle = SerieHelper.GetItemStyle(serie, serieData);
  143. var colorIndex = chart.GetLegendRealShowNameIndex(serieData.legendName);
  144. Color32 itemColor, itemToColor;
  145. SerieHelper.GetItemColor(out itemColor, out itemToColor, serie, serieData, chart.theme, colorIndex);
  146. var insideRadius = outsideRadius - ringWidth;
  147. var borderWidth = itemStyle.borderWidth;
  148. var borderColor = itemStyle.borderColor;
  149. var roundCap = serie.roundCap && insideRadius > 0;
  150. DrawBackground(vh, serie, serieData, j, insideRadius, outsideRadius);
  151. UGL.DrawDoughnut(vh, serie.context.center, insideRadius, outsideRadius, itemColor, itemToColor,
  152. Color.clear, startDegree, toDegree, borderWidth, borderColor, 0, chart.settings.cicleSmoothness,
  153. roundCap, serie.clockwise);
  154. DrawCenter(vh, serie, serieData, insideRadius, j == data.Count - 1);
  155. }
  156. for (int j = 0; j < data.Count; j++)
  157. {
  158. var serieData = data[j];
  159. if (!serieData.show) continue;
  160. var serieLabel = SerieHelper.GetSerieLabel(serie, serieData);
  161. var colorIndex = chart.GetLegendRealShowNameIndex(serieData.legendName);
  162. Color32 itemColor, itemToColor;
  163. SerieHelper.GetItemColor(out itemColor, out itemToColor, serie, serieData, chart.theme, colorIndex);
  164. if (SerieLabelHelper.CanShowLabel(serie, serieData, serieLabel, 0))
  165. {
  166. DrawRingLabelLine(vh, serie, serieData, itemColor);
  167. }
  168. }
  169. if (!serie.animation.IsFinish())
  170. {
  171. serie.animation.CheckProgress(360);
  172. chart.RefreshChart();
  173. }
  174. if (dataChanging)
  175. {
  176. chart.RefreshChart();
  177. }
  178. }
  179. private void UpdateRuntimeData()
  180. {
  181. var data = serie.data;
  182. SerieHelper.UpdateCenter(serie, chart);
  183. var ringWidth = serie.context.outsideRadius - serie.context.insideRadius;
  184. for (int j = 0; j < data.Count; j++)
  185. {
  186. var serieData = data[j];
  187. if (!serieData.show) continue;
  188. var outsideRadius = serie.context.outsideRadius - j * (ringWidth + serie.gap);
  189. if (outsideRadius < 0) continue;
  190. var value = serieData.GetCurrData(0, serie.animation, false, false);
  191. var max = serieData.GetLastData();
  192. var degree = (float)(360 * value / max);
  193. var startDegree = GetStartAngle(serie);
  194. var toDegree = GetToAngle(serie, degree);
  195. var insideRadius = outsideRadius - ringWidth;
  196. var halfAngle = startDegree + (toDegree - startDegree) / 2;
  197. var halfRadius = (outsideRadius + insideRadius) / 2;
  198. serieData.context.startAngle = startDegree;
  199. serieData.context.toAngle = toDegree;
  200. serieData.context.insideRadius = insideRadius;
  201. serieData.context.outsideRadius = serieData.radius > 0 ? serieData.radius : outsideRadius;
  202. serieData.context.position = ChartHelper.GetPosition(serie.context.center, halfAngle, halfRadius);
  203. UpdateLabelPosition(serieData);
  204. }
  205. AvoidLabelOverlap();
  206. }
  207. public override void OnLegendButtonClick(int index, string legendName, bool show)
  208. {
  209. if (!serie.IsLegendName(legendName))
  210. return;
  211. LegendHelper.CheckDataShow(serie, legendName, show);
  212. chart.UpdateLegendColor(legendName, show);
  213. chart.RefreshPainter(serie);
  214. }
  215. public override void OnLegendButtonEnter(int index, string legendName)
  216. {
  217. if (!serie.IsLegendName(legendName))
  218. return;
  219. LegendHelper.CheckDataHighlighted(serie, legendName, true);
  220. chart.RefreshPainter(serie);
  221. }
  222. public override void OnLegendButtonExit(int index, string legendName)
  223. {
  224. if (!serie.IsLegendName(legendName))
  225. return;
  226. LegendHelper.CheckDataHighlighted(serie, legendName, false);
  227. chart.RefreshPainter(serie);
  228. }
  229. public override void OnPointerDown(PointerEventData eventData) { }
  230. private float GetStartAngle(Serie serie)
  231. {
  232. return serie.clockwise ? serie.startAngle : 360 - serie.startAngle;
  233. }
  234. private float GetToAngle(Serie serie, float angle)
  235. {
  236. var toAngle = angle + serie.startAngle;
  237. if (!serie.clockwise)
  238. {
  239. toAngle = 360 - angle - serie.startAngle;
  240. }
  241. if (!serie.animation.IsFinish())
  242. {
  243. var currAngle = serie.animation.GetCurrDetail();
  244. if (serie.clockwise)
  245. {
  246. toAngle = toAngle > currAngle ? currAngle : toAngle;
  247. }
  248. else
  249. {
  250. toAngle = toAngle < 360 - currAngle ? 360 - currAngle : toAngle;
  251. }
  252. }
  253. return toAngle;
  254. }
  255. private void DrawCenter(VertexHelper vh, Serie serie, SerieData serieData, float insideRadius, bool last)
  256. {
  257. var itemStyle = SerieHelper.GetItemStyle(serie, serieData);
  258. if (!ChartHelper.IsClearColor(itemStyle.centerColor) && last)
  259. {
  260. var radius = insideRadius - itemStyle.centerGap;
  261. var smoothness = chart.settings.cicleSmoothness;
  262. UGL.DrawCricle(vh, serie.context.center, radius, itemStyle.centerColor, smoothness);
  263. }
  264. }
  265. private void DrawBackground(VertexHelper vh, Serie serie, SerieData serieData, int index, float insideRadius, float outsideRadius)
  266. {
  267. var itemStyle = SerieHelper.GetItemStyle(serie, serieData);
  268. var backgroundColor = itemStyle.backgroundColor;
  269. if (ChartHelper.IsClearColor(backgroundColor))
  270. {
  271. backgroundColor = chart.theme.GetColor(index);
  272. backgroundColor.a = 50;
  273. }
  274. if (itemStyle.backgroundWidth != 0)
  275. {
  276. var centerRadius = (outsideRadius + insideRadius) / 2;
  277. var inradius = centerRadius - itemStyle.backgroundWidth / 2;
  278. var outradius = centerRadius + itemStyle.backgroundWidth / 2;
  279. UGL.DrawDoughnut(vh, serie.context.center, inradius,
  280. outradius, backgroundColor, Color.clear, chart.settings.cicleSmoothness);
  281. }
  282. else
  283. {
  284. UGL.DrawDoughnut(vh, serie.context.center, insideRadius,
  285. outsideRadius, backgroundColor, Color.clear, chart.settings.cicleSmoothness);
  286. }
  287. }
  288. private void DrawBorder(VertexHelper vh, Serie serie, SerieData serieData, float insideRadius, float outsideRadius)
  289. {
  290. var itemStyle = SerieHelper.GetItemStyle(serie, serieData);
  291. if (itemStyle.show && itemStyle.borderWidth > 0 && !ChartHelper.IsClearColor(itemStyle.borderColor))
  292. {
  293. UGL.DrawDoughnut(vh, serie.context.center, outsideRadius,
  294. outsideRadius + itemStyle.borderWidth, itemStyle.borderColor,
  295. Color.clear, chart.settings.cicleSmoothness);
  296. UGL.DrawDoughnut(vh, serie.context.center, insideRadius,
  297. insideRadius + itemStyle.borderWidth, itemStyle.borderColor,
  298. Color.clear, chart.settings.cicleSmoothness);
  299. }
  300. }
  301. private int GetRingIndex(Vector2 local)
  302. {
  303. var dist = Vector2.Distance(local, serie.context.center);
  304. if (dist > serie.context.outsideRadius) return -1;
  305. Vector2 dir = local - new Vector2(serie.context.center.x, serie.context.center.y);
  306. float angle = VectorAngle(Vector2.up, dir);
  307. for (int i = 0; i < serie.data.Count; i++)
  308. {
  309. var serieData = serie.data[i];
  310. if (dist >= serieData.context.insideRadius &&
  311. dist <= serieData.context.outsideRadius &&
  312. IsInAngle(serieData, angle, serie.clockwise))
  313. {
  314. return i;
  315. }
  316. }
  317. return -1;
  318. }
  319. private bool IsInAngle(SerieData serieData, float angle, bool clockwise)
  320. {
  321. if (clockwise)
  322. return angle >= serieData.context.startAngle && angle <= serieData.context.toAngle;
  323. else
  324. return angle >= serieData.context.toAngle && angle <= serieData.context.startAngle;
  325. }
  326. private float VectorAngle(Vector2 from, Vector2 to)
  327. {
  328. float angle;
  329. Vector3 cross = Vector3.Cross(from, to);
  330. angle = Vector2.Angle(from, to);
  331. angle = cross.z > 0 ? -angle : angle;
  332. angle = (angle + 360) % 360;
  333. return angle;
  334. }
  335. private void UpdateLabelPosition(SerieData serieData)
  336. {
  337. if (serieData.labelObject == null) return;
  338. var label = SerieHelper.GetSerieLabel(serie, serieData);
  339. var labelLine = SerieHelper.GetSerieLabelLine(serie, serieData);
  340. var centerRadius = (serieData.context.outsideRadius + serieData.context.insideRadius) / 2;
  341. var startAngle = serieData.context.startAngle;
  342. var toAngle = serieData.context.toAngle;
  343. switch (label.position)
  344. {
  345. case LabelStyle.Position.Bottom:
  346. case LabelStyle.Position.Start:
  347. var px1 = Mathf.Sin(startAngle * Mathf.Deg2Rad) * centerRadius;
  348. var py1 = Mathf.Cos(startAngle * Mathf.Deg2Rad) * centerRadius;
  349. var xDiff = serie.clockwise ? -label.distance : label.distance;
  350. if (labelLine != null && labelLine.show)
  351. {
  352. serieData.context.labelLinePosition = serie.context.center + new Vector3(px1, py1) + labelLine.GetStartSymbolOffset();
  353. serieData.context.labelPosition = GetLabelLineEndPosition(serie, serieData, labelLine) + new Vector3(xDiff, 0);
  354. }
  355. else
  356. {
  357. serieData.context.labelLinePosition = serie.context.center + new Vector3(px1 + xDiff, py1);
  358. serieData.context.labelPosition = serieData.context.labelLinePosition;
  359. }
  360. break;
  361. case LabelStyle.Position.Top:
  362. case LabelStyle.Position.End:
  363. case LabelStyle.Position.Outside:
  364. startAngle += serie.clockwise ? -label.distance : label.distance;
  365. toAngle += serie.clockwise ? label.distance : -label.distance;
  366. var px2 = Mathf.Sin(toAngle * Mathf.Deg2Rad) * centerRadius;
  367. var py2 = Mathf.Cos(toAngle * Mathf.Deg2Rad) * centerRadius;
  368. if (labelLine != null && labelLine.show)
  369. {
  370. serieData.context.labelLinePosition = serie.context.center + new Vector3(px2, py2) + labelLine.GetStartSymbolOffset();
  371. serieData.context.labelPosition = GetLabelLineEndPosition(serie, serieData, labelLine);
  372. }
  373. else
  374. {
  375. serieData.context.labelLinePosition = serie.context.center + new Vector3(px2, py2);
  376. serieData.context.labelPosition = serieData.context.labelLinePosition;
  377. }
  378. break;
  379. default: //LabelStyle.Position.Center
  380. serieData.context.labelLinePosition = serie.context.center + label.offset;
  381. serieData.context.labelPosition = serieData.context.labelLinePosition;
  382. break;
  383. }
  384. }
  385. private void AvoidLabelOverlap()
  386. {
  387. if (!serie.avoidLabelOverlap) return;
  388. serie.context.sortedData.Clear();
  389. foreach (var serieData in serie.data)
  390. {
  391. serie.context.sortedData.Add(serieData);
  392. }
  393. serie.context.sortedData.Sort(delegate (SerieData a, SerieData b)
  394. {
  395. if (a == null || b == null) return 0;
  396. return a.context.labelPosition.y.CompareTo(b.context.labelPosition.y);
  397. });
  398. var startY = serie.context.sortedData[0].context.labelPosition.y;
  399. for (int i = 1; i < serie.context.sortedData.Count; i++)
  400. {
  401. var serieData = serie.context.sortedData[i];
  402. var fontSize = serieData.labelObject.GetHeight();
  403. if (serieData.context.labelPosition.y - startY < fontSize)
  404. {
  405. serieData.context.labelPosition.y = startY + fontSize;
  406. }
  407. startY = serieData.context.labelPosition.y;
  408. }
  409. }
  410. private void DrawRingLabelLine(VertexHelper vh, Serie serie, SerieData serieData, Color32 defaltColor)
  411. {
  412. var serieLabel = SerieHelper.GetSerieLabel(serie, serieData);
  413. var labelLine = SerieHelper.GetSerieLabelLine(serie, serieData);
  414. if (serieLabel != null && serieLabel.show &&
  415. labelLine != null && labelLine.show)
  416. {
  417. var color = ChartHelper.IsClearColor(labelLine.lineColor) ?
  418. ChartHelper.GetHighlightColor(defaltColor, 0.9f) :
  419. labelLine.lineColor;
  420. var isRight = !serie.clockwise;
  421. var rad = Mathf.Deg2Rad * (isRight ? labelLine.lineAngle : 180 - labelLine.lineAngle);
  422. var lineLength1 = ChartHelper.GetActualValue(labelLine.lineLength1, serie.context.outsideRadius);
  423. var pos1 = serieData.context.labelLinePosition;
  424. var pos2 = pos1 + new Vector3(Mathf.Cos(rad) * lineLength1, Mathf.Sin(rad) * lineLength1);
  425. var pos5 = serieData.context.labelPosition;
  426. switch (labelLine.lineType)
  427. {
  428. case LabelLine.LineType.BrokenLine:
  429. UGL.DrawLine(vh, pos1, pos2, pos5, labelLine.lineWidth, color);
  430. break;
  431. case LabelLine.LineType.Curves:
  432. UGL.DrawCurves(vh, pos1, pos5, pos1, pos2, labelLine.lineWidth, color,
  433. chart.settings.lineSmoothness, UGL.Direction.XAxis);
  434. break;
  435. case LabelLine.LineType.HorizontalLine:
  436. UGL.DrawLine(vh, pos1, pos5, labelLine.lineWidth, color);
  437. break;
  438. }
  439. DrawLabelLineSymbol(vh, labelLine, pos1, pos5, color);
  440. }
  441. }
  442. }
  443. }