81501a5359c2a3a519873f250fe7f0e0ecb5b41dfa622f28bd4f2c96448d562035c67a53834c7caba365bda4df1820c6b8cb8851e1e45a5d3c02e52bf89cf3 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. import { doUpdate } from '../class/updater.class'
  2. import { pieConfig } from '../config/pie'
  3. import { getCircleRadianPoint } from '@jiaminghi/c-render/lib/plugin/util'
  4. import { deepMerge, initNeedSeries, mulAdd, getPolylineLength } from '../util'
  5. export function pie (chart, option = {}) {
  6. let { series } = option
  7. if (!series) series = []
  8. let pies = initNeedSeries(series, pieConfig, 'pie')
  9. pies = calcPiesCenter(pies, chart)
  10. pies = calcPiesRadius(pies, chart)
  11. pies = calcRosePiesRadius(pies, chart)
  12. pies = calcPiesPercent(pies)
  13. pies = calcPiesAngle(pies, chart)
  14. pies = calcPiesInsideLabelPos(pies)
  15. pies = calcPiesEdgeCenterPos(pies)
  16. pies = calcPiesOutSideLabelPos(pies)
  17. doUpdate({
  18. chart,
  19. series: pies,
  20. key: 'pie',
  21. getGraphConfig: getPieConfig,
  22. getStartGraphConfig: getStartPieConfig,
  23. beforeChange: beforeChangePie
  24. })
  25. doUpdate({
  26. chart,
  27. series: pies,
  28. key: 'pieInsideLabel',
  29. getGraphConfig: getInsideLabelConfig
  30. })
  31. doUpdate({
  32. chart,
  33. series: pies,
  34. key: 'pieOutsideLabelLine',
  35. getGraphConfig: getOutsideLabelLineConfig,
  36. getStartGraphConfig: getStartOutsideLabelLineConfig
  37. })
  38. doUpdate({
  39. chart,
  40. series: pies,
  41. key: 'pieOutsideLabel',
  42. getGraphConfig: getOutsideLabelConfig,
  43. getStartGraphConfig: getStartOutsideLabelConfig
  44. })
  45. }
  46. function calcPiesCenter(pies, chart) {
  47. const { area } = chart.render
  48. pies.forEach(pie => {
  49. let { center } = pie
  50. center = center.map((pos, i) => {
  51. if (typeof pos === 'number') return pos
  52. return parseInt(pos) / 100 * area[i]
  53. })
  54. pie.center = center
  55. })
  56. return pies
  57. }
  58. function calcPiesRadius (pies, chart) {
  59. const maxRadius = Math.min(...chart.render.area) / 2
  60. pies.forEach(pie => {
  61. let { radius, data } = pie
  62. radius = getNumberRadius(radius, maxRadius)
  63. data.forEach(item => {
  64. let { radius: itemRadius } = item
  65. if (!itemRadius) itemRadius = radius
  66. itemRadius = getNumberRadius(itemRadius, maxRadius)
  67. item.radius = itemRadius
  68. })
  69. pie.radius = radius
  70. })
  71. return pies
  72. }
  73. function getNumberRadius (radius, maxRadius) {
  74. if (!(radius instanceof Array)) radius = [0, radius]
  75. radius = radius.map(r => {
  76. if (typeof r === 'number') return r
  77. return parseInt(r) / 100 * maxRadius
  78. })
  79. return radius
  80. }
  81. function calcRosePiesRadius (pies, chart) {
  82. const rosePie = pies.filter(({ roseType }) => roseType)
  83. rosePie.forEach(pie => {
  84. let { radius, data, roseSort } = pie
  85. const roseIncrement = getRoseIncrement(pie)
  86. const dataCopy = [...data]
  87. data = sortData(data)
  88. data.forEach((item, i) => {
  89. item.radius[1] = radius[1] - roseIncrement * i
  90. })
  91. if (roseSort) {
  92. data.reverse()
  93. } else {
  94. pie.data = dataCopy
  95. }
  96. pie.roseIncrement = roseIncrement
  97. })
  98. return pies
  99. }
  100. function sortData (data) {
  101. return data.sort(({ value: a }, { value: b }) => {
  102. if (a === b) return 0
  103. if (a > b) return -1
  104. if (a < b) return 1
  105. })
  106. }
  107. function getRoseIncrement (pie) {
  108. const { radius, roseIncrement } = pie
  109. if (typeof roseIncrement === 'number') return roseIncrement
  110. if (roseIncrement === 'auto') {
  111. const { data } = pie
  112. const allRadius = data.reduce((all, { radius }) => [...all, ...radius], [])
  113. const minRadius = Math.min(...allRadius)
  114. const maxRadius = Math.max(...allRadius)
  115. return (maxRadius - minRadius) * 0.6 / (data.length - 1 || 1)
  116. }
  117. return parseInt(roseIncrement) / 100 * radius[1]
  118. }
  119. function calcPiesPercent (pies) {
  120. pies.forEach(pie => {
  121. const { data, percentToFixed } = pie
  122. const sum = getDataSum(data)
  123. data.forEach(item => {
  124. const { value } = item
  125. item.percent = value / sum * 100
  126. item.percentForLabel = toFixedNoCeil(value / sum * 100, percentToFixed)
  127. })
  128. const percentSumNoLast = mulAdd(data.slice(0, -1).map(({ percent }) => percent))
  129. data.slice(-1)[0].percent = 100 - percentSumNoLast
  130. data.slice(-1)[0].percentForLabel = toFixedNoCeil(100 - percentSumNoLast, percentToFixed)
  131. })
  132. return pies
  133. }
  134. function toFixedNoCeil (number, toFixed = 0) {
  135. const stringNumber = number.toString()
  136. const splitedNumber = stringNumber.split('.')
  137. const decimal = splitedNumber[1] || '0'
  138. const fixedDecimal = decimal.slice(0, toFixed)
  139. splitedNumber[1] = fixedDecimal
  140. return parseFloat(splitedNumber.join('.'))
  141. }
  142. function getDataSum (data) {
  143. return mulAdd(data.map(({ value }) => value))
  144. }
  145. function calcPiesAngle (pies) {
  146. pies.forEach(pie => {
  147. const { startAngle: start, data } = pie
  148. data.forEach((item, i) => {
  149. const [startAngle, endAngle] = getDataAngle(data, i)
  150. item.startAngle = start + startAngle
  151. item.endAngle = start + endAngle
  152. })
  153. })
  154. return pies
  155. }
  156. function getDataAngle (data, i) {
  157. const fullAngle = Math.PI * 2
  158. const needAddData = data.slice(0, i + 1)
  159. const percentSum = mulAdd(needAddData.map(({ percent }) => percent))
  160. const { percent } = data[i]
  161. const startPercent = percentSum - percent
  162. return [fullAngle * startPercent / 100, fullAngle * percentSum / 100]
  163. }
  164. function calcPiesInsideLabelPos (pies) {
  165. pies.forEach(pieItem => {
  166. const { data } = pieItem
  167. data.forEach(item => {
  168. item.insideLabelPos = getPieInsideLabelPos(pieItem, item)
  169. })
  170. })
  171. return pies
  172. }
  173. function getPieInsideLabelPos (pieItem, dataItem) {
  174. const { center } = pieItem
  175. const { startAngle, endAngle, radius: [ir, or] } = dataItem
  176. const radius = (ir + or) / 2
  177. const angle = (startAngle + endAngle) / 2
  178. return getCircleRadianPoint(...center, radius, angle)
  179. }
  180. function calcPiesEdgeCenterPos(pies) {
  181. pies.forEach(pie => {
  182. const { data, center } = pie
  183. data.forEach(item => {
  184. const { startAngle, endAngle, radius } = item
  185. const centerAngle = (startAngle + endAngle) / 2
  186. const pos = getCircleRadianPoint(...center, radius[1], centerAngle)
  187. item.edgeCenterPos = pos
  188. })
  189. })
  190. return pies
  191. }
  192. function calcPiesOutSideLabelPos (pies) {
  193. pies.forEach(pieItem => {
  194. let leftPieDataItems = getLeftOrRightPieDataItems(pieItem)
  195. let rightPieDataItems = getLeftOrRightPieDataItems(pieItem, false)
  196. leftPieDataItems = sortPiesFromTopToBottom(leftPieDataItems)
  197. rightPieDataItems = sortPiesFromTopToBottom(rightPieDataItems)
  198. addLabelLineAndAlign(leftPieDataItems, pieItem)
  199. addLabelLineAndAlign(rightPieDataItems, pieItem, false)
  200. })
  201. return pies
  202. }
  203. function getLabelLineBendRadius (pieItem) {
  204. let { labelLineBendGap } = pieItem.outsideLabel
  205. const maxRadius = getPieMaxRadius(pieItem)
  206. if (typeof labelLineBendGap !== 'number') {
  207. labelLineBendGap = parseInt(labelLineBendGap) / 100 * maxRadius
  208. }
  209. return labelLineBendGap + maxRadius
  210. }
  211. function getPieMaxRadius(pieItem) {
  212. const { data } = pieItem
  213. const radius = data.map(({ radius: [foo, r] }) => r)
  214. return Math.max(...radius)
  215. }
  216. function getLeftOrRightPieDataItems (pieItem, left = true) {
  217. const { data, center } = pieItem
  218. const centerXPos = center[0]
  219. return data.filter(({ edgeCenterPos }) => {
  220. const xPos = edgeCenterPos[0]
  221. if (left) return xPos <= centerXPos
  222. return xPos > centerXPos
  223. })
  224. }
  225. function sortPiesFromTopToBottom (dataItem) {
  226. dataItem.sort(({ edgeCenterPos: [t, ay] }, { edgeCenterPos: [tt, by] }) => {
  227. if (ay > by) return 1
  228. if (ay < by) return -1
  229. if (ay === by) return 0
  230. })
  231. return dataItem
  232. }
  233. function addLabelLineAndAlign (dataItem, pieItem, left = true) {
  234. const { center, outsideLabel } = pieItem
  235. const radius = getLabelLineBendRadius(pieItem)
  236. dataItem.forEach(item => {
  237. const { edgeCenterPos, startAngle, endAngle } = item
  238. const { labelLineEndLength } = outsideLabel
  239. const angle = (startAngle + endAngle) / 2
  240. const bendPoint = getCircleRadianPoint(...center, radius, angle)
  241. let endPoint = [...bendPoint]
  242. endPoint[0] += labelLineEndLength * (left ? -1 : 1)
  243. item.labelLine = [edgeCenterPos, bendPoint, endPoint]
  244. item.labelLineLength = getPolylineLength(item.labelLine)
  245. item.align = { textAlign: 'left', textBaseline: 'middle' }
  246. if (left) item.align.textAlign = 'right'
  247. })
  248. }
  249. function getPieConfig (pieItem) {
  250. const { data, animationCurve, animationFrame, rLevel } = pieItem
  251. return data.map((foo, i) => ({
  252. name: 'pie',
  253. index: rLevel,
  254. animationCurve,
  255. animationFrame,
  256. shape: getPieShape(pieItem, i),
  257. style: getPieStyle(pieItem, i)
  258. }))
  259. }
  260. function getStartPieConfig (pieItem) {
  261. const { animationDelayGap, startAnimationCurve } = pieItem
  262. const configs = getPieConfig(pieItem)
  263. configs.forEach((config, i) => {
  264. config.animationCurve = startAnimationCurve
  265. config.animationDelay = i * animationDelayGap
  266. config.shape.or = config.shape.ir
  267. })
  268. return configs
  269. }
  270. function beforeChangePie (graph) {
  271. graph.animationDelay = 0
  272. }
  273. function getPieShape (pieItem, i) {
  274. const { center, data } = pieItem
  275. const dataItem = data[i]
  276. const { radius, startAngle, endAngle } = dataItem
  277. return {
  278. startAngle,
  279. endAngle,
  280. ir: radius[0],
  281. or: radius[1],
  282. rx: center[0],
  283. ry: center[1]
  284. }
  285. }
  286. function getPieStyle (pieItem, i) {
  287. const { pieStyle, data } = pieItem
  288. const dataItem = data[i]
  289. const { color } = dataItem
  290. return deepMerge({ fill: color }, pieStyle)
  291. }
  292. function getInsideLabelConfig (pieItem) {
  293. const { animationCurve, animationFrame, data, rLevel } = pieItem
  294. return data.map((foo, i) => ({
  295. name: 'text',
  296. index: rLevel,
  297. visible: pieItem.insideLabel.show,
  298. animationCurve,
  299. animationFrame,
  300. shape: getInsideLabelShape(pieItem, i),
  301. style: getInsideLabelStyle(pieItem, i)
  302. }))
  303. }
  304. function getInsideLabelShape (pieItem, i) {
  305. const { insideLabel, data } = pieItem
  306. const { formatter } = insideLabel
  307. const dataItem = data[i]
  308. const formatterType = typeof formatter
  309. let label = ''
  310. if (formatterType === 'string') {
  311. label = formatter.replace('{name}', dataItem.name)
  312. label = label.replace('{percent}', dataItem.percentForLabel)
  313. label = label.replace('{value}', dataItem.value)
  314. }
  315. if (formatterType === 'function') {
  316. label = formatter(dataItem)
  317. }
  318. return {
  319. content: label,
  320. position: dataItem.insideLabelPos
  321. }
  322. }
  323. function getInsideLabelStyle (pieItem, i) {
  324. const { insideLabel: { style } } = pieItem
  325. return style
  326. }
  327. function getOutsideLabelLineConfig (pieItem) {
  328. const { animationCurve, animationFrame, data, rLevel } = pieItem
  329. return data.map((foo, i) => ({
  330. name: 'polyline',
  331. index: rLevel,
  332. visible: pieItem.outsideLabel.show,
  333. animationCurve,
  334. animationFrame,
  335. shape: getOutsideLabelLineShape(pieItem, i),
  336. style: getOutsideLabelLineStyle(pieItem, i)
  337. }))
  338. }
  339. function getStartOutsideLabelLineConfig (pieItem) {
  340. const { data } = pieItem
  341. const configs = getOutsideLabelLineConfig(pieItem)
  342. configs.forEach((config, i) => {
  343. config.style.lineDash = [0, data[i].labelLineLength]
  344. })
  345. return configs
  346. }
  347. function getOutsideLabelLineShape (pieItem, i) {
  348. const { data } = pieItem
  349. const dataItem = data[i]
  350. return {
  351. points: dataItem.labelLine
  352. }
  353. }
  354. function getOutsideLabelLineStyle (pieItem, i) {
  355. const { outsideLabel, data } = pieItem
  356. const { labelLineStyle } = outsideLabel
  357. const { color } = data[i]
  358. return deepMerge({ stroke: color, lineDash: [data[i].labelLineLength, 0] }, labelLineStyle)
  359. }
  360. function getOutsideLabelConfig (pieItem) {
  361. const { animationCurve, animationFrame, data, rLevel } = pieItem
  362. return data.map((foo, i) => ({
  363. name: 'text',
  364. index: rLevel,
  365. visible: pieItem.outsideLabel.show,
  366. animationCurve,
  367. animationFrame,
  368. shape: getOutsideLabelShape(pieItem, i),
  369. style: getOutsideLabelStyle(pieItem, i)
  370. }))
  371. }
  372. function getStartOutsideLabelConfig (pieItem) {
  373. const { data } = pieItem
  374. const configs = getOutsideLabelConfig(pieItem)
  375. configs.forEach((config, i) => {
  376. config.shape.position = data[i].labelLine[1]
  377. })
  378. return configs
  379. }
  380. function getOutsideLabelShape (pieItem, i) {
  381. const { outsideLabel, data } = pieItem
  382. const { formatter } = outsideLabel
  383. const { labelLine, name, percentForLabel, value } = data[i]
  384. const formatterType = typeof formatter
  385. let label = ''
  386. if (formatterType === 'string') {
  387. label = formatter.replace('{name}', name)
  388. label = label.replace('{percent}', percentForLabel)
  389. label = label.replace('{value}', value)
  390. }
  391. if (formatterType === 'function') {
  392. label = formatter(data[i])
  393. }
  394. return {
  395. content: label,
  396. position: labelLine[2],
  397. }
  398. }
  399. function getOutsideLabelStyle (pieItem, i) {
  400. const { outsideLabel, data } = pieItem
  401. const { color, align } = data[i]
  402. const { style } = outsideLabel
  403. return deepMerge({ fill: color, ...align }, style)
  404. }