5169749e04d7ce9f9825c2f7e2c4ca3a7c390a3d05f5a30f8367e9be96263514e0bf691c8eb9a4596e990c6f1217a6825118b277924ac6a1fcd2c73263d934 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. import { doUpdate } from '../class/updater.class'
  2. import { xAxisConfig, yAxisConfig } from '../config'
  3. import { filterNonNumber, deepMerge, mergeSameStackData } from '../util'
  4. import { deepClone } from '@jiaminghi/c-render/lib/plugin/util'
  5. const axisConfig = { xAxisConfig, yAxisConfig }
  6. const { min, max, abs, pow } = Math
  7. export function axis (chart, option = {}) {
  8. let { xAxis, yAxis, series } = option
  9. let allAxis = []
  10. if (xAxis && yAxis && series) {
  11. allAxis = getAllAxis(xAxis, yAxis)
  12. allAxis = mergeDefaultAxisConfig(allAxis)
  13. allAxis = allAxis.filter(({ show }) => show)
  14. allAxis = mergeDefaultBoundaryGap(allAxis)
  15. allAxis = calcAxisLabelData(allAxis, series)
  16. allAxis = setAxisPosition(allAxis)
  17. allAxis = calcAxisLinePosition(allAxis, chart)
  18. allAxis = calcAxisTickPosition(allAxis, chart)
  19. allAxis = calcAxisNamePosition(allAxis, chart)
  20. allAxis = calcSplitLinePosition(allAxis, chart)
  21. }
  22. doUpdate({
  23. chart,
  24. series: allAxis,
  25. key: 'axisLine',
  26. getGraphConfig: getLineConfig
  27. })
  28. doUpdate({
  29. chart,
  30. series: allAxis,
  31. key: 'axisTick',
  32. getGraphConfig: getTickConfig
  33. })
  34. doUpdate({
  35. chart,
  36. series: allAxis,
  37. key: 'axisLabel',
  38. getGraphConfig: getLabelConfig
  39. })
  40. doUpdate({
  41. chart,
  42. series: allAxis,
  43. key: 'axisName',
  44. getGraphConfig: getNameConfig
  45. })
  46. doUpdate({
  47. chart,
  48. series: allAxis,
  49. key: 'splitLine',
  50. getGraphConfig: getSplitLineConfig
  51. })
  52. chart.axisData = allAxis
  53. }
  54. function getAllAxis (xAxis, yAxis) {
  55. let [allXAxis, allYAxis] = [[], []]
  56. if (xAxis instanceof Array) {
  57. allXAxis.push(...xAxis)
  58. } else {
  59. allXAxis.push(xAxis)
  60. }
  61. if (yAxis instanceof Array) {
  62. allYAxis.push(...yAxis)
  63. } else {
  64. allYAxis.push(yAxis)
  65. }
  66. allXAxis.splice(2)
  67. allYAxis.splice(2)
  68. allXAxis = allXAxis.map((axis, i) => ({ ...axis, index: i, axis: 'x' }))
  69. allYAxis = allYAxis.map((axis, i) => ({ ...axis, index: i, axis: 'y' }))
  70. return [...allXAxis, ...allYAxis]
  71. }
  72. function mergeDefaultAxisConfig (allAxis) {
  73. let xAxis = allAxis.filter(({ axis }) => axis === 'x')
  74. let yAxis = allAxis.filter(({ axis }) => axis === 'y')
  75. xAxis = xAxis.map(axis => deepMerge(deepClone(xAxisConfig), axis))
  76. yAxis = yAxis.map(axis => deepMerge(deepClone(yAxisConfig), axis))
  77. return [...xAxis, ...yAxis]
  78. }
  79. function mergeDefaultBoundaryGap (allAxis) {
  80. const valueAxis = allAxis.filter(({ data }) => data === 'value')
  81. const labelAxis = allAxis.filter(({ data }) => data !== 'value')
  82. valueAxis.forEach(axis => {
  83. if (typeof axis.boundaryGap === 'boolean') return
  84. axis.boundaryGap = false
  85. })
  86. labelAxis.forEach(axis => {
  87. if (typeof axis.boundaryGap === 'boolean') return
  88. axis.boundaryGap = true
  89. })
  90. return [...valueAxis, ...labelAxis]
  91. }
  92. function calcAxisLabelData (allAxis, series) {
  93. let valueAxis = allAxis.filter(({ data }) => data === 'value')
  94. let labelAxis = allAxis.filter(({ data }) => data instanceof Array)
  95. valueAxis = calcValueAxisLabelData(valueAxis, series)
  96. labelAxis = calcLabelAxisLabelData(labelAxis)
  97. return [...valueAxis, ...labelAxis]
  98. }
  99. function calcValueAxisLabelData (valueAxis, series) {
  100. return valueAxis.map(axis => {
  101. const minMaxValue = getValueAxisMaxMinValue(axis, series)
  102. const [min, max] = getTrueMinMax(axis, minMaxValue)
  103. const interval = getValueInterval(min, max, axis)
  104. const { axisLabel: { formatter } } = axis
  105. let label = []
  106. if (min < 0 && max > 0) {
  107. label = getValueAxisLabelFromZero(min, max, interval)
  108. } else {
  109. label = getValueAxisLabelFromMin(min, max, interval)
  110. }
  111. label = label.map(l => parseFloat(l.toFixed(2)))
  112. return {
  113. ...axis,
  114. maxValue: label.slice(-1)[0],
  115. minValue: label[0],
  116. label: getAfterFormatterLabel(label, formatter)
  117. }
  118. })
  119. }
  120. function getValueAxisMaxMinValue (axis, series) {
  121. series = series.filter(({ show, type }) => {
  122. if (show === false) return false
  123. if (type === 'pie') return false
  124. return true
  125. })
  126. if (series.length === 0) return [0, 0]
  127. const { index, axis: axisType } = axis
  128. series = mergeStackData(series)
  129. const axisName = axisType + 'Axis'
  130. let valueSeries = series.filter(s => s[axisName] === index)
  131. if (!valueSeries.length) valueSeries = series
  132. return getSeriesMinMaxValue(valueSeries)
  133. }
  134. function getSeriesMinMaxValue (series) {
  135. if (!series) return
  136. const minValue = Math.min(
  137. ...series
  138. .map(({ data }) => Math.min(...filterNonNumber(data)))
  139. )
  140. const maxValue = Math.max(
  141. ...series
  142. .map(({ data }) => Math.max(...filterNonNumber(data)))
  143. )
  144. return [minValue, maxValue]
  145. }
  146. function mergeStackData (series) {
  147. const seriesCloned = deepClone(series, true)
  148. series.forEach((item, i) => {
  149. const data = mergeSameStackData(item, series)
  150. seriesCloned[i].data = data
  151. })
  152. return seriesCloned
  153. }
  154. function getTrueMinMax ({ min, max, axis }, [minValue, maxValue]) {
  155. let [minType, maxType] = [typeof min, typeof max]
  156. if (!testMinMaxType(min)) {
  157. min = axisConfig[axis + 'AxisConfig'].min
  158. minType = 'string'
  159. }
  160. if (!testMinMaxType(max)) {
  161. max = axisConfig[axis + 'AxisConfig'].max
  162. maxType = 'string'
  163. }
  164. if (minType === 'string') {
  165. min = parseInt(minValue - abs(minValue * parseFloat(min) / 100))
  166. const lever = getValueLever(min)
  167. min = parseFloat((min / lever - 0.1).toFixed(1)) * lever
  168. }
  169. if (maxType === 'string') {
  170. max = parseInt(maxValue + abs(maxValue * parseFloat(max) / 100))
  171. const lever = getValueLever(max)
  172. max = parseFloat((max / lever + 0.1).toFixed(1)) * lever
  173. }
  174. return [min, max]
  175. }
  176. function getValueLever (value) {
  177. const valueString = abs(value).toString()
  178. const valueLength = valueString.length
  179. const firstZeroIndex = valueString.replace(/0*$/g, '').indexOf('0')
  180. let pow10Num = valueLength - 1
  181. if (firstZeroIndex !== -1) pow10Num -= firstZeroIndex
  182. return pow(10, pow10Num)
  183. }
  184. function testMinMaxType (val) {
  185. const valType = typeof val
  186. const isValidString = (valType === 'string' && /^\d+%$/.test(val))
  187. const isValidNumber = valType === 'number'
  188. return isValidString || isValidNumber
  189. }
  190. function getValueAxisLabelFromZero (min, max, interval) {
  191. let [negative, positive] = [[], []]
  192. let [currentNegative, currentPositive] = [0, 0]
  193. do {
  194. negative.push(currentNegative -= interval)
  195. } while (currentNegative > min)
  196. do {
  197. positive.push(currentPositive += interval)
  198. } while (currentPositive < max)
  199. return [...negative.reverse(), 0, ...positive]
  200. }
  201. function getValueAxisLabelFromMin (min, max, interval) {
  202. let [label, currentValue] = [[min], min]
  203. do {
  204. label.push(currentValue += interval)
  205. } while (currentValue < max)
  206. return label
  207. }
  208. function getAfterFormatterLabel (label, formatter) {
  209. if (!formatter) return label
  210. if (typeof formatter === 'string') label = label.map(l => formatter.replace('{value}', l))
  211. if (typeof formatter === 'function') label = label.map((value, index) => formatter({ value, index }))
  212. return label
  213. }
  214. function calcLabelAxisLabelData (labelAxis) {
  215. return labelAxis.map(axis => {
  216. const { data, axisLabel: { formatter } } = axis
  217. return { ...axis, label: getAfterFormatterLabel(data, formatter) }
  218. })
  219. }
  220. function getValueInterval (min, max, axis) {
  221. let { interval, minInterval, maxInterval, splitNumber, axis: axisType } = axis
  222. const config = axisConfig[axisType + 'AxisConfig']
  223. if (typeof interval !== 'number') interval = config.interval
  224. if (typeof minInterval !== 'number') minInterval = config.minInterval
  225. if (typeof maxInterval !== 'number') maxInterval = config.maxInterval
  226. if (typeof splitNumber !== 'number') splitNumber = config.splitNumber
  227. if (typeof interval === 'number') return interval
  228. let valueInterval = parseInt((max - min) / (splitNumber - 1))
  229. if (valueInterval.toString().length > 1) valueInterval = parseInt(valueInterval.toString().replace(/\d$/, '0'))
  230. if (valueInterval === 0) valueInterval = 1
  231. if (typeof minInterval === 'number' && valueInterval < minInterval) return minInterval
  232. if (typeof maxInterval === 'number' && valueInterval > maxInterval) return maxInterval
  233. return valueInterval
  234. }
  235. function setAxisPosition (allAxis) {
  236. const xAxis = allAxis.filter(({ axis }) => axis === 'x')
  237. const yAxis = allAxis.filter(({ axis }) => axis === 'y')
  238. if (xAxis[0] && !xAxis[0].position) xAxis[0].position = xAxisConfig.position
  239. if (xAxis[1] && !xAxis[1].position) {
  240. xAxis[1].position = xAxis[0].position === 'bottom' ? 'top' : 'bottom'
  241. }
  242. if (yAxis[0] && !yAxis[0].position) yAxis[0].position = yAxisConfig.position
  243. if (yAxis[1] && !yAxis[1].position) {
  244. yAxis[1].position = yAxis[0].position === 'left' ? 'right' : 'left'
  245. }
  246. return [...xAxis, ...yAxis]
  247. }
  248. function calcAxisLinePosition (allAxis, chart) {
  249. const { x, y, w, h } = chart.gridArea
  250. allAxis = allAxis.map(axis => {
  251. const { position } = axis
  252. let linePosition = []
  253. if (position === 'left') {
  254. linePosition = [[x, y], [x, y + h]].reverse()
  255. } else if (position === 'right') {
  256. linePosition = [[x + w, y], [x + w, y + h]].reverse()
  257. } else if (position === 'top') {
  258. linePosition = [[x, y], [x + w, y]]
  259. } else if (position === 'bottom') {
  260. linePosition = [[x, y + h], [x + w, y + h]]
  261. }
  262. return {
  263. ...axis,
  264. linePosition
  265. }
  266. })
  267. return allAxis
  268. }
  269. function calcAxisTickPosition (allAxis, chart) {
  270. return allAxis.map(axisItem => {
  271. let { axis, linePosition, position, label, boundaryGap } = axisItem
  272. if (typeof boundaryGap !== 'boolean') boundaryGap = axisConfig[axis + 'AxisConfig'].boundaryGap
  273. const labelNum = label.length
  274. const [[startX, startY], [endX, endY]] = linePosition
  275. const gapLength = axis === 'x' ? endX - startX : endY - startY
  276. const gap = gapLength / (boundaryGap ? labelNum : labelNum - 1)
  277. const tickPosition = new Array(labelNum)
  278. .fill(0)
  279. .map((foo, i) => {
  280. if (axis === 'x') {
  281. return [startX + gap * (boundaryGap ? (i + 0.5) : i), startY]
  282. }
  283. return [startX, startY + gap * (boundaryGap ? (i + 0.5) : i)]
  284. })
  285. const tickLinePosition = getTickLinePosition(axis, boundaryGap, position, tickPosition, gap)
  286. return {
  287. ...axisItem,
  288. tickPosition,
  289. tickLinePosition,
  290. tickGap: gap
  291. }
  292. })
  293. }
  294. function getTickLinePosition (axisType, boundaryGap, position, tickPosition, gap) {
  295. let index = axisType === 'x' ? 1 : 0
  296. let plus = 5
  297. if (axisType === 'x' && position === 'top') plus = -5
  298. if (axisType === 'y' && position === 'left') plus = -5
  299. let tickLinePosition = tickPosition.map(lineStart => {
  300. const lineEnd = deepClone(lineStart)
  301. lineEnd[index] += plus
  302. return [deepClone(lineStart), lineEnd]
  303. })
  304. if (!boundaryGap) return tickLinePosition
  305. index = axisType === 'x' ? 0 : 1
  306. plus = gap / 2
  307. tickLinePosition.forEach(([lineStart, lineEnd]) => {
  308. lineStart[index] += plus
  309. lineEnd[index] += plus
  310. })
  311. return tickLinePosition
  312. }
  313. function calcAxisNamePosition (allAxis, chart) {
  314. return allAxis.map(axisItem => {
  315. let { nameGap, nameLocation, position, linePosition } = axisItem
  316. const [lineStart, lineEnd] = linePosition
  317. let namePosition = [...lineStart]
  318. if (nameLocation === 'end') namePosition = [...lineEnd]
  319. if (nameLocation === 'center') {
  320. namePosition[0] = (lineStart[0] + lineEnd[0]) / 2
  321. namePosition[1] = (lineStart[1] + lineEnd[1]) / 2
  322. }
  323. let index = 0
  324. if (position === 'top' && nameLocation === 'center') index = 1
  325. if (position === 'bottom' && nameLocation === 'center') index = 1
  326. if (position === 'left' && nameLocation !== 'center') index = 1
  327. if (position === 'right' && nameLocation !== 'center') index = 1
  328. let plus = nameGap
  329. if (position === 'top' && nameLocation !== 'end') plus *= -1
  330. if (position === 'left' && nameLocation !== 'start') plus *= -1
  331. if (position === 'bottom' && nameLocation === 'start') plus *= -1
  332. if (position === 'right' && nameLocation === 'end') plus *= -1
  333. namePosition[index] += plus
  334. return {
  335. ...axisItem,
  336. namePosition
  337. }
  338. })
  339. }
  340. function calcSplitLinePosition (allAxis, chart) {
  341. const { w, h } = chart.gridArea
  342. return allAxis.map(axisItem => {
  343. const { tickLinePosition, position, boundaryGap } = axisItem
  344. let [index, plus] = [0, w]
  345. if (position === 'top' || position === 'bottom') index = 1
  346. if (position === 'top' || position === 'bottom') plus = h
  347. if (position === 'right' || position === 'bottom') plus *= -1
  348. const splitLinePosition = tickLinePosition.map(([startPoint]) => {
  349. const endPoint = [...startPoint]
  350. endPoint[index] += plus
  351. return [[...startPoint], endPoint]
  352. })
  353. if (!boundaryGap) splitLinePosition.shift()
  354. return {
  355. ...axisItem,
  356. splitLinePosition
  357. }
  358. })
  359. }
  360. function getLineConfig (axisItem) {
  361. const { animationCurve, animationFrame, rLevel } = axisItem
  362. return [{
  363. name: 'polyline',
  364. index: rLevel,
  365. visible: axisItem.axisLine.show,
  366. animationCurve,
  367. animationFrame,
  368. shape: getLineShape(axisItem),
  369. style: getLineStyle(axisItem)
  370. }]
  371. }
  372. function getLineShape (axisItem) {
  373. const { linePosition } = axisItem
  374. return {
  375. points: linePosition
  376. }
  377. }
  378. function getLineStyle (axisItem) {
  379. return axisItem.axisLine.style
  380. }
  381. function getTickConfig (axisItem) {
  382. const { animationCurve, animationFrame, rLevel } = axisItem
  383. const shapes = getTickShapes(axisItem)
  384. const style = getTickStyle(axisItem)
  385. return shapes.map(shape => ({
  386. name: 'polyline',
  387. index: rLevel,
  388. visible: axisItem.axisTick.show,
  389. animationCurve,
  390. animationFrame,
  391. shape,
  392. style
  393. }))
  394. }
  395. function getTickShapes (axisItem) {
  396. const { tickLinePosition } = axisItem
  397. return tickLinePosition.map(points => ({ points }))
  398. }
  399. function getTickStyle (axisItem) {
  400. return axisItem.axisTick.style
  401. }
  402. function getLabelConfig (axisItem) {
  403. const { animationCurve, animationFrame, rLevel } = axisItem
  404. const shapes = getLabelShapes(axisItem)
  405. const styles = getLabelStyle(axisItem, shapes)
  406. return shapes.map((shape, i) => ({
  407. name: 'text',
  408. index: rLevel,
  409. visible: axisItem.axisLabel.show,
  410. animationCurve,
  411. animationFrame,
  412. shape,
  413. style: styles[i],
  414. setGraphCenter: () => (void 0)
  415. }))
  416. }
  417. function getLabelShapes (axisItem) {
  418. const { label, tickPosition, position } = axisItem
  419. return tickPosition.map((point, i) => ({
  420. position: getLabelRealPosition(point, position),
  421. content: label[i].toString(),
  422. }))
  423. }
  424. function getLabelRealPosition (points, position) {
  425. let [index, plus] = [0, 10]
  426. if (position === 'top' || position === 'bottom') index = 1
  427. if (position === 'top' || position === 'left') plus = -10
  428. points = deepClone(points)
  429. points[index] += plus
  430. return points
  431. }
  432. function getLabelStyle (axisItem, shapes) {
  433. const { position } = axisItem
  434. let { style } = axisItem.axisLabel
  435. const align = getAxisLabelRealAlign(position)
  436. style = deepMerge(align, style)
  437. const styles = shapes.map(({ position }) => ({
  438. ...style,
  439. graphCenter: position
  440. }))
  441. return styles
  442. }
  443. function getAxisLabelRealAlign (position) {
  444. if (position === 'left') return {
  445. textAlign: 'right',
  446. textBaseline: 'middle'
  447. }
  448. if (position === 'right') return {
  449. textAlign: 'left',
  450. textBaseline: 'middle'
  451. }
  452. if (position === 'top') return {
  453. textAlign: 'center',
  454. textBaseline: 'bottom'
  455. }
  456. if (position === 'bottom') return {
  457. textAlign: 'center',
  458. textBaseline: 'top'
  459. }
  460. }
  461. function getNameConfig (axisItem) {
  462. const { animationCurve, animationFrame, rLevel } = axisItem
  463. return [{
  464. name: 'text',
  465. index: rLevel,
  466. animationCurve,
  467. animationFrame,
  468. shape: getNameShape(axisItem),
  469. style: getNameStyle(axisItem)
  470. }]
  471. }
  472. function getNameShape (axisItem) {
  473. const { name, namePosition } = axisItem
  474. return {
  475. content: name,
  476. position: namePosition
  477. }
  478. }
  479. function getNameStyle (axisItem) {
  480. const { nameLocation, position, nameTextStyle: style } = axisItem
  481. const align = getNameRealAlign(position, nameLocation)
  482. return deepMerge(align, style)
  483. }
  484. function getNameRealAlign (position, location) {
  485. if (
  486. (position === 'top' && location === 'start') ||
  487. (position === 'bottom' && location === 'start') ||
  488. (position === 'left' && location === 'center')
  489. ) return {
  490. textAlign: 'right',
  491. textBaseline: 'middle'
  492. }
  493. if (
  494. (position === 'top' && location === 'end') ||
  495. (position === 'bottom' && location === 'end') ||
  496. (position === 'right' && location === 'center')
  497. ) return {
  498. textAlign: 'left',
  499. textBaseline: 'middle'
  500. }
  501. if (
  502. (position === 'top' && location === 'center') ||
  503. (position === 'left' && location === 'end') ||
  504. (position === 'right' && location === 'end')
  505. ) return {
  506. textAlign: 'center',
  507. textBaseline: 'bottom'
  508. }
  509. if (
  510. (position === 'bottom' && location === 'center') ||
  511. (position === 'left' && location === 'start') ||
  512. (position === 'right' && location === 'start')
  513. ) return {
  514. textAlign: 'center',
  515. textBaseline: 'top'
  516. }
  517. }
  518. function getSplitLineConfig (axisItem) {
  519. const { animationCurve, animationFrame, rLevel } = axisItem
  520. const shapes = getSplitLineShapes(axisItem)
  521. const style = getSplitLineStyle(axisItem)
  522. return shapes.map(shape => ({
  523. name: 'polyline',
  524. index: rLevel,
  525. visible: axisItem.splitLine.show,
  526. animationCurve,
  527. animationFrame,
  528. shape,
  529. style
  530. }))
  531. }
  532. function getSplitLineShapes (axisItem) {
  533. const { splitLinePosition } = axisItem
  534. return splitLinePosition.map(points => ({ points }))
  535. }
  536. function getSplitLineStyle (axisItem) {
  537. return axisItem.splitLine.style
  538. }