util.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. import {
  2. DEFAULT_SPLITER,
  3. DEFAULT_FONT_SIZE,
  4. DEFAULT_FONT_WEIGHT,
  5. DEFAULT_FONT_FAMILY,
  6. PIVOT_CELL_PADDING,
  7. PIVOT_CELL_BORDER,
  8. PIVOT_LINE_HEIGHT,
  9. PIVOT_MAX_CONTENT_WIDTH,
  10. PIVOT_CHART_ELEMENT_MIN_WIDTH,
  11. PIVOT_CHART_ELEMENT_MAX_WIDTH,
  12. PIVOT_CHART_METRIC_AXIS_MIN_SIZE,
  13. PIVOT_CHART_POINT_LIMIT,
  14. PIVOT_BORDER,
  15. PIVOT_XAXIS_SIZE,
  16. PIVOT_YAXIS_SIZE,
  17. PIVOT_TITLE_SIZE,
  18. PIVOT_XAXIS_ROTATE_LIMIT,
  19. PIVOT_XAXIS_TICK_SIZE,
  20. PIVOT_CANVAS_AXIS_SIZE_LIMIT,
  21. PIVOT_DEFAULT_SCATTER_SIZE_TIMES
  22. } from 'app/globalConstants'
  23. import { DimetionType, IChartStyles, IChartInfo } from './Widget'
  24. import { IChartLine, IChartUnit } from './Pivot/Chart'
  25. import { IDataParamSource } from './Workbench/Dropbox'
  26. import { getFieldAlias } from '../components/Config/Field'
  27. import { getFormattedValue } from '../components/Config/Format'
  28. import widgetlibs from '../config'
  29. import PivotTypes from '../config/pivot/PivotTypes'
  30. import ChartTypes from '../config/chart/ChartTypes'
  31. const pivotlibs = widgetlibs['pivot']
  32. const chartlibs = widgetlibs['chart']
  33. import { uuid } from 'utils/util'
  34. export function getAggregatorLocale(agg) {
  35. switch (agg) {
  36. case 'sum':
  37. return '总计'
  38. case 'avg':
  39. return '平均数'
  40. case 'count':
  41. return '计数'
  42. case 'COUNTDISTINCT':
  43. return '去重计数'
  44. case 'max':
  45. return '最大值'
  46. case 'min':
  47. return '最小值'
  48. case 'median':
  49. return '中位数'
  50. case 'percentile':
  51. return '百分位'
  52. case 'stddev':
  53. return '标准偏差'
  54. case 'var':
  55. return '方差'
  56. }
  57. }
  58. export function encodeMetricName(name) {
  59. return `${name}${DEFAULT_SPLITER}${uuid(8, 16)}`
  60. }
  61. export function decodeMetricName(encodedName) {
  62. return encodedName.split(DEFAULT_SPLITER)[0]
  63. }
  64. export function spanSize(arr, i, j) {
  65. if (i !== 0) {
  66. let noDraw = true
  67. for (let x = 0; x <= j; x += 1) {
  68. if (arr[i - 1][x] !== arr[i][x]) {
  69. noDraw = false
  70. }
  71. }
  72. if (noDraw) {
  73. return -1
  74. }
  75. }
  76. let len = 0
  77. while (i + len < arr.length) {
  78. let stop = false
  79. for (let x = 0; x <= j; x += 1) {
  80. if (arr[i][x] !== arr[i + len][x]) {
  81. stop = true
  82. }
  83. }
  84. if (stop) {
  85. break
  86. }
  87. len++
  88. }
  89. return len
  90. }
  91. export function naturalSort(a, b): number {
  92. const rx = /(\d+)|(\D+)/g
  93. const rd = /\d/
  94. const rz = /^0/
  95. if (b != null && a == null) {
  96. return -1
  97. }
  98. if (a != null && b == null) {
  99. return 1
  100. }
  101. if (typeof a === 'number' && isNaN(a)) {
  102. return -1
  103. }
  104. if (typeof b === 'number' && isNaN(b)) {
  105. return 1
  106. }
  107. const na = +a
  108. const nb = +b
  109. if (na < nb) {
  110. return -1
  111. }
  112. if (na > nb) {
  113. return 1
  114. }
  115. if (typeof a === 'number' && typeof b !== 'number') {
  116. return -1
  117. }
  118. if (typeof b === 'number' && typeof a !== 'number') {
  119. return 1
  120. }
  121. if (typeof a === 'number' && typeof b === 'number') {
  122. return 0
  123. }
  124. if (isNaN(nb) && !isNaN(na)) {
  125. return -1
  126. }
  127. if (isNaN(na) && !isNaN(nb)) {
  128. return 1
  129. }
  130. const sa = String(a)
  131. const sb = String(b)
  132. if (sa === sb) {
  133. return 0
  134. }
  135. if (!(rd.test(sa) && rd.test(sb))) {
  136. return sa > sb ? 1 : -1
  137. }
  138. const ra = sa.match(rx)
  139. const rb = sb.match(rx)
  140. while (ra.length && rb.length) {
  141. const a1 = ra.shift()
  142. const b1 = rb.shift()
  143. if (a1 !== b1) {
  144. if (rd.test(a1) && rd.test(b1)) {
  145. return Number(a1.replace(rz, '.0')) - Number(b1.replace(rz, '.0'))
  146. } else {
  147. return a1 > b1 ? 1 : -1
  148. }
  149. }
  150. }
  151. return ra.length - rb.length
  152. }
  153. let utilCanvas = null
  154. export const getTextWidth = (
  155. text: string,
  156. fontWeight: string = DEFAULT_FONT_WEIGHT,
  157. fontSize: string = DEFAULT_FONT_SIZE,
  158. fontFamily: string = DEFAULT_FONT_FAMILY
  159. ): number => {
  160. const canvas = utilCanvas || (utilCanvas = document.createElement('canvas'))
  161. const context = canvas.getContext('2d')
  162. context.font = `${fontWeight} ${fontSize} ${fontFamily}`
  163. const metrics = context.measureText(text)
  164. return Math.ceil(metrics.width)
  165. }
  166. export const getPivotContentTextWidth = (
  167. text: string,
  168. fontWeight: string = DEFAULT_FONT_WEIGHT,
  169. fontSize: string = DEFAULT_FONT_SIZE,
  170. fontFamily: string = DEFAULT_FONT_FAMILY
  171. ): number => {
  172. return Math.min(
  173. getTextWidth(text, fontWeight, fontSize, fontFamily),
  174. PIVOT_MAX_CONTENT_WIDTH
  175. )
  176. }
  177. export function getPivotCellWidth(width: number): number {
  178. return width + PIVOT_CELL_PADDING * 2 + PIVOT_CELL_BORDER * 2
  179. }
  180. export function getPivotCellHeight(height?: number): number {
  181. return (
  182. (height || PIVOT_LINE_HEIGHT) + PIVOT_CELL_PADDING * 2 + PIVOT_CELL_BORDER
  183. )
  184. }
  185. export const getTableBodyWidth = (
  186. direction: DimetionType,
  187. containerWidth,
  188. rowHeaderWidths
  189. ) => {
  190. const title = rowHeaderWidths.length && PIVOT_TITLE_SIZE
  191. const rowHeaderWidthSum =
  192. direction === 'row'
  193. ? rowHeaderWidths
  194. .slice(0, rowHeaderWidths.length - 1)
  195. .reduce((sum, r) => sum + getPivotCellWidth(r), 0)
  196. : rowHeaderWidths.reduce((sum, r) => sum + getPivotCellWidth(r), 0)
  197. return (
  198. containerWidth -
  199. PIVOT_BORDER * 2 -
  200. rowHeaderWidthSum -
  201. PIVOT_YAXIS_SIZE -
  202. title
  203. )
  204. }
  205. export const getTableBodyHeight = (
  206. direction: DimetionType,
  207. containerHeight,
  208. columnHeaderCount
  209. ) => {
  210. const title = columnHeaderCount && PIVOT_TITLE_SIZE
  211. const realColumnHeaderCount =
  212. direction === 'col' ? Math.max(columnHeaderCount - 1, 0) : columnHeaderCount
  213. return (
  214. containerHeight -
  215. PIVOT_BORDER * 2 -
  216. realColumnHeaderCount * getPivotCellHeight() -
  217. PIVOT_XAXIS_SIZE -
  218. title
  219. )
  220. }
  221. export function getChartElementSize(
  222. direction: DimetionType,
  223. tableBodySideLength: number[],
  224. chartElementCountArr: number[],
  225. multiCoordinate: boolean
  226. ): number {
  227. let chartElementCount
  228. let side
  229. if (direction === 'col') {
  230. chartElementCount = Math.max(1, chartElementCountArr[0])
  231. side = tableBodySideLength[0]
  232. } else {
  233. chartElementCount = Math.max(1, chartElementCountArr[1])
  234. side = tableBodySideLength[1]
  235. }
  236. const sizePerElement = side / chartElementCount
  237. const limit = multiCoordinate
  238. ? PIVOT_CHART_METRIC_AXIS_MIN_SIZE
  239. : PIVOT_CHART_ELEMENT_MIN_WIDTH
  240. return Math.max(Math.floor(sizePerElement), limit)
  241. }
  242. export function shouldTableBodyCollapsed(
  243. direction: DimetionType,
  244. elementSize: number,
  245. tableBodyHeight: number,
  246. rowKeyLength: number
  247. ): boolean {
  248. return direction === 'row' && tableBodyHeight > rowKeyLength * elementSize
  249. }
  250. export function getChartUnitMetricWidth(
  251. tableBodyWidth,
  252. colKeyCount: number,
  253. metricCount: number
  254. ): number {
  255. const realContainerWidth = Math.max(
  256. tableBodyWidth,
  257. colKeyCount * metricCount * PIVOT_CHART_METRIC_AXIS_MIN_SIZE
  258. )
  259. return realContainerWidth / colKeyCount / metricCount
  260. }
  261. export function getChartUnitMetricHeight(
  262. tableBodyHeight,
  263. rowKeyCount: number,
  264. metricCount: number
  265. ): number {
  266. const realContainerHeight = Math.max(
  267. tableBodyHeight,
  268. rowKeyCount * metricCount * PIVOT_CHART_METRIC_AXIS_MIN_SIZE
  269. )
  270. return realContainerHeight / rowKeyCount / metricCount
  271. }
  272. export function checkChartEnable(
  273. dimensionCount: number,
  274. metricCount: number,
  275. charts: IChartInfo | IChartInfo[]
  276. ): boolean {
  277. const chartArr = Array.isArray(charts) ? charts : [charts]
  278. const enabled = chartArr.every(({ rules }) => {
  279. const currentRulesChecked = rules.some(({ dimension, metric }) => {
  280. if (Array.isArray(dimension)) {
  281. if (dimensionCount < dimension[0] || dimensionCount > dimension[1]) {
  282. return false
  283. }
  284. } else if (dimensionCount !== dimension) {
  285. return false
  286. }
  287. if (Array.isArray(metric)) {
  288. if (metricCount < metric[0] || metricCount > metric[1]) {
  289. return false
  290. }
  291. } else if (metricCount !== metric) {
  292. return false
  293. }
  294. return true
  295. })
  296. return currentRulesChecked
  297. })
  298. return enabled
  299. }
  300. export function getAxisInterval(max, splitNumber) {
  301. const roughInterval = Math.floor(max / splitNumber)
  302. const divisor = Math.pow(10, `${roughInterval}`.length - 1)
  303. return (Math.floor(roughInterval / divisor) + 1) * divisor
  304. }
  305. export function getChartPieces(total, lines) {
  306. if (lines === 1) {
  307. return lines
  308. }
  309. const eachLine = total / lines
  310. const pct =
  311. Math.abs(eachLine - PIVOT_CHART_POINT_LIMIT) / PIVOT_CHART_POINT_LIMIT
  312. return pct < 0.2
  313. ? lines
  314. : eachLine > PIVOT_CHART_POINT_LIMIT
  315. ? lines
  316. : getChartPieces(total, Math.round(lines / 2))
  317. }
  318. export function metricAxisLabelFormatter(value) {
  319. const positive = value >= 0
  320. if (!positive) {
  321. value = -value
  322. }
  323. let endValue
  324. const orderLessKilo = (value) => {
  325. if (value < Math.pow(10, 3)) {
  326. endValue = value
  327. }
  328. }
  329. const orderKilo = (value) => {
  330. if (value >= Math.pow(10, 3) && value < Math.pow(10, 6)) {
  331. endValue = `${precision(value / Math.pow(10, 3))}K`
  332. }
  333. }
  334. const orderMillion = (value) => {
  335. if (value >= Math.pow(10, 6) && value < Math.pow(10, 9)) {
  336. endValue = `${precision(value / Math.pow(10, 6))}M`
  337. }
  338. }
  339. const orderBillion = (value) => {
  340. if (value >= Math.pow(10, 9) && value < Math.pow(10, 12)) {
  341. endValue = `${precision(value / Math.pow(10, 9))}B`
  342. }
  343. }
  344. const orderTrillion = (value) => {
  345. if (value >= Math.pow(10, 12)) {
  346. endValue = `${precision(value / Math.pow(10, 12))}T`
  347. }
  348. }
  349. const orderFn = (...fns) => (value) =>
  350. fns.reduce((pre, fn) => fn.call(this, value), 0)
  351. orderFn(
  352. orderLessKilo,
  353. orderKilo,
  354. orderMillion,
  355. orderBillion,
  356. orderTrillion
  357. )(value)
  358. return positive ? endValue : `-${endValue}`
  359. function precision(num) {
  360. return num >= 10 ? Math.floor(num) : num.toFixed(1)
  361. }
  362. }
  363. export function getPivot(): IChartInfo {
  364. return pivotlibs.find((p) => p.id === PivotTypes.PivotTable)
  365. }
  366. export function getTable(): IChartInfo {
  367. return chartlibs.find((c) => c.id === ChartTypes.Table)
  368. }
  369. export function getPivotModeSelectedCharts(
  370. items: IDataParamSource[]
  371. ): IChartInfo[] {
  372. return items.length ? items.map((i) => i.chart) : [getPivot()]
  373. }
  374. export function getStyleConfig(chartStyles: IChartStyles): IChartStyles {
  375. return {
  376. ...chartStyles,
  377. pivot: chartStyles.pivot || { ...getPivot().style['pivot'] } // FIXME 兼容0.3.0-beta 数据库
  378. }
  379. }
  380. export function getAxisData(
  381. type: 'x' | 'y',
  382. rowKeys,
  383. colKeys,
  384. rowTree,
  385. colTree,
  386. tree,
  387. metrics,
  388. drawingData,
  389. dimetionAxis
  390. ) {
  391. const { elementSize, unitMetricWidth, unitMetricHeight } = drawingData
  392. const data: IChartLine[] = []
  393. const chartLine: IChartUnit[] = []
  394. let axisLength = 0
  395. let renderKeys
  396. let renderTree
  397. let sndKeys
  398. let sndTree
  399. let renderDimetionAxis
  400. let unitMetricSide
  401. if (type === 'x') {
  402. renderKeys = colKeys
  403. renderTree = colTree
  404. sndKeys = rowKeys
  405. sndTree = rowTree
  406. renderDimetionAxis = 'col'
  407. unitMetricSide = unitMetricWidth
  408. } else {
  409. renderKeys = rowKeys
  410. renderTree = rowTree
  411. sndKeys = colKeys
  412. sndTree = colTree
  413. renderDimetionAxis = 'row'
  414. unitMetricSide = unitMetricHeight
  415. }
  416. if (renderKeys.length) {
  417. renderKeys.forEach((keys, i) => {
  418. const flatKey = keys.join(String.fromCharCode(0))
  419. const { records } = renderTree[flatKey]
  420. if (dimetionAxis === renderDimetionAxis) {
  421. const nextKeys = renderKeys[i + 1] || []
  422. let lastUnit = chartLine[chartLine.length - 1]
  423. if (!lastUnit || lastUnit.ended) {
  424. lastUnit = {
  425. width: 0,
  426. records: [],
  427. ended: false
  428. }
  429. chartLine.push(lastUnit)
  430. }
  431. lastUnit.records.push({
  432. key: keys[keys.length - 1],
  433. value: records[0]
  434. })
  435. if (
  436. (keys.length === 1 && i === renderKeys.length - 1) ||
  437. keys[keys.length - 2] !== nextKeys[nextKeys.length - 2]
  438. ) {
  439. const unitLength = lastUnit.records.length * elementSize
  440. axisLength += unitLength
  441. lastUnit.width = unitLength
  442. lastUnit.ended = true
  443. }
  444. if (!nextKeys.length) {
  445. data.push({
  446. key: flatKey,
  447. data: chartLine.slice()
  448. })
  449. }
  450. } else {
  451. axisLength += unitMetricSide
  452. chartLine.push({
  453. width: unitMetricSide,
  454. records: [
  455. {
  456. key: keys[keys.length - 1],
  457. value: records[0]
  458. }
  459. ],
  460. ended: true
  461. })
  462. if (i === renderKeys.length - 1) {
  463. data.push({
  464. key: flatKey,
  465. data: chartLine.slice()
  466. })
  467. }
  468. }
  469. })
  470. } else {
  471. if (dimetionAxis !== renderDimetionAxis) {
  472. data.push({
  473. key: uuid(8, 16),
  474. data: [
  475. {
  476. width: unitMetricSide,
  477. records: [
  478. {
  479. key: '',
  480. value: sndKeys.length
  481. ? Object.values(sndTree)[0]
  482. : tree[0]
  483. ? tree[0][0]
  484. : []
  485. }
  486. ],
  487. ended: true
  488. }
  489. ]
  490. })
  491. axisLength = unitMetricSide
  492. }
  493. }
  494. axisLength =
  495. dimetionAxis === renderDimetionAxis
  496. ? axisLength
  497. : axisLength * metrics.length
  498. return {
  499. data: axisDataCutting(type, dimetionAxis, metrics, axisLength, data),
  500. length: axisLength
  501. }
  502. }
  503. export function axisDataCutting(
  504. type: 'x' | 'y',
  505. dimetionAxis,
  506. metrics,
  507. axisLength,
  508. data
  509. ) {
  510. if (axisLength > PIVOT_CANVAS_AXIS_SIZE_LIMIT) {
  511. const result = []
  512. data.forEach((line) => {
  513. let blockLine = {
  514. key: `${uuid(8, 16)}${line.key}`,
  515. data: []
  516. }
  517. let block = {
  518. key: '',
  519. length: 0,
  520. data: [blockLine]
  521. }
  522. line.data.forEach((unit, index) => {
  523. const unitWidth =
  524. (type === 'x' && dimetionAxis === 'row') ||
  525. (type === 'y' && dimetionAxis === 'col')
  526. ? unit.width * metrics.length
  527. : unit.width
  528. if (block.length + unitWidth > PIVOT_CANVAS_AXIS_SIZE_LIMIT) {
  529. block.key = `${index}${block.data.map((d) => d.key).join(',')}`
  530. result.push(block)
  531. blockLine = {
  532. key: `${uuid(8, 16)}${line.key}`,
  533. data: []
  534. }
  535. block = {
  536. key: '',
  537. length: 0,
  538. data: [blockLine]
  539. }
  540. }
  541. block.length += unitWidth
  542. blockLine.data.push(unit)
  543. if (index === line.data.length - 1) {
  544. block.key = `${index}${block.data.map((d) => d.key).join(',')}`
  545. result.push(block)
  546. }
  547. })
  548. })
  549. return result
  550. } else {
  551. return [
  552. {
  553. key: 'block',
  554. data,
  555. length: axisLength
  556. }
  557. ]
  558. }
  559. }
  560. export function getXaxisLabel(elementSize) {
  561. return function (label) {
  562. const originLabel = label
  563. const ellipsis = '…'
  564. const limit =
  565. elementSize > PIVOT_XAXIS_ROTATE_LIMIT
  566. ? elementSize
  567. : PIVOT_XAXIS_SIZE - PIVOT_XAXIS_TICK_SIZE
  568. while (getTextWidth(label) > limit) {
  569. label = label.substring(0, label.length - 1)
  570. }
  571. return label === originLabel
  572. ? label
  573. : `${label.substring(0, label.length - 1)}${ellipsis}`
  574. }
  575. }
  576. export function getTooltipPosition(point, params, dom, rect, size) {
  577. const [x, y] = point
  578. const { contentSize, viewSize } = size
  579. const [cx, cy] = contentSize
  580. const [vx, vy] = viewSize
  581. const distanceXToMouse = 10
  582. return [
  583. x + cx + distanceXToMouse > vx
  584. ? x - distanceXToMouse - cx
  585. : x + distanceXToMouse,
  586. // Math.min(x, vx - cx),
  587. Math.min(y, vy - cy)
  588. ]
  589. }
  590. export function getPivotTooltipLabel(
  591. seriesData,
  592. cols,
  593. rows,
  594. metrics,
  595. color,
  596. label,
  597. size,
  598. scatterXAxis,
  599. tip
  600. ) {
  601. let dimetionColumns = cols.concat(rows)
  602. let metricColumns = [...metrics]
  603. if (color) {
  604. dimetionColumns = dimetionColumns.concat(color.items.map((i) => i.name))
  605. }
  606. if (label) {
  607. dimetionColumns = dimetionColumns.concat(
  608. label.items.filter((i) => i.type === 'category').map((i) => i.name)
  609. )
  610. metricColumns = metricColumns.concat(
  611. label.items.filter((i) => i.type === 'value')
  612. )
  613. }
  614. if (size) {
  615. metricColumns = metricColumns.concat(size.items)
  616. }
  617. if (scatterXAxis) {
  618. metricColumns = metricColumns.concat(scatterXAxis.items)
  619. }
  620. if (tip) {
  621. metricColumns = metricColumns.concat(tip.items)
  622. }
  623. dimetionColumns = dimetionColumns.reduce((arr, dc) => {
  624. if (!arr.includes(dc)) {
  625. arr.push(dc)
  626. }
  627. return arr
  628. }, [])
  629. metricColumns = metricColumns.reduce((arr, mc) => {
  630. const decodedName = decodeMetricName(mc.name)
  631. if (
  632. !arr.find(
  633. (m) => decodeMetricName(m.name) === decodedName && m.agg === mc.agg
  634. )
  635. ) {
  636. arr.push(mc)
  637. }
  638. return arr
  639. }, [])
  640. return function (params) {
  641. const record = getTriggeringRecord(params, seriesData)
  642. return metricColumns
  643. .map((mc) => {
  644. const decodedName = decodeMetricName(mc.name)
  645. const value = record
  646. ? Array.isArray(record)
  647. ? record.reduce((sum, r) => sum + r[`${mc.agg}(${decodedName})`], 0)
  648. : record[`${mc.agg}(${decodedName})`]
  649. : 0
  650. return `${decodedName}: ${getFormattedValue(value, mc.format)}`
  651. })
  652. .concat(
  653. dimetionColumns.map((dc) => {
  654. const value = record
  655. ? Array.isArray(record)
  656. ? record[0][dc]
  657. : record[dc]
  658. : ''
  659. return `${dc}: ${getFormattedValue(value, dc.format)}`
  660. })
  661. )
  662. .join('<br/>')
  663. }
  664. }
  665. export function getChartTooltipLabel(type, seriesData, options) {
  666. const { cols, metrics, color, size, scatterXAxis, tip } = options
  667. let dimentionColumns: any[] = cols
  668. let metricColumns = [...metrics]
  669. if (color) {
  670. dimentionColumns = dimentionColumns.concat(color.items)
  671. }
  672. if (size) {
  673. metricColumns = metricColumns.concat(size.items)
  674. }
  675. if (scatterXAxis) {
  676. metricColumns = metricColumns.concat(scatterXAxis.items)
  677. }
  678. if (tip) {
  679. metricColumns = metricColumns.concat(tip.items)
  680. }
  681. dimentionColumns = dimentionColumns.filter(
  682. (dc, idx) => dimentionColumns.findIndex((c) => c.name === dc.name) === idx
  683. )
  684. metricColumns = metricColumns.reduce((arr, mc) => {
  685. const decodedName = decodeMetricName(mc.name)
  686. if (
  687. !arr.find(
  688. (m) => decodeMetricName(m.name) === decodedName && m.agg === mc.agg
  689. )
  690. ) {
  691. arr.push(mc)
  692. }
  693. return arr
  694. }, [])
  695. return function (params) {
  696. const { componentType } = params
  697. if (componentType === 'markLine') {
  698. const { name, value } = params
  699. return `参考线<br/>${name}: ${value}`
  700. } else if (componentType === 'markArea') {
  701. const {
  702. name,
  703. data: { coord }
  704. } = params
  705. const valueIndex = coord[0].findIndex(
  706. (c) => c !== Infinity && c !== -Infinity
  707. )
  708. return `参考区间<br/>${name}: ${coord[0][valueIndex]} - ${coord[1][valueIndex]}`
  709. } else {
  710. const { seriesIndex, dataIndex, color } = params
  711. const record =
  712. type === 'funnel' || type === 'map'
  713. ? seriesData[dataIndex]
  714. : seriesData[seriesIndex][dataIndex]
  715. let tooltipLabels = []
  716. tooltipLabels = tooltipLabels.concat(
  717. dimentionColumns.map((dc) => {
  718. let value = record
  719. ? Array.isArray(record)
  720. ? record[0][dc.name]
  721. : record[dc.name]
  722. : ''
  723. value = getFormattedValue(value, dc.format)
  724. return `${getFieldAlias(dc.field, {}) || dc.name}: ${value}` // @FIXME dynamic field alias by queryVariable in dashboard
  725. })
  726. )
  727. tooltipLabels = tooltipLabels.concat(
  728. metricColumns.map((mc) => {
  729. const decodedName = decodeMetricName(mc.name)
  730. let value = record
  731. ? Array.isArray(record)
  732. ? record.reduce(
  733. (sum, r) => sum + r[`${mc.agg}(${decodedName})`],
  734. 0
  735. )
  736. : record[`${mc.agg}(${decodedName})`]
  737. : 0
  738. value = getFormattedValue(value, mc.format)
  739. return `${getFieldAlias(mc.field, {}) || decodedName}: ${value}`
  740. })
  741. )
  742. if (color) {
  743. const circle = `<span class="widget-tooltip-circle" style="background: ${color}"></span>`
  744. if (!dimentionColumns.length) {
  745. tooltipLabels.unshift(circle)
  746. } else {
  747. tooltipLabels[0] = circle + tooltipLabels[0]
  748. }
  749. }
  750. return tooltipLabels.join('<br/>')
  751. }
  752. }
  753. }
  754. export function getChartLabel(seriesData, labelItem) {
  755. return function (params) {
  756. const record = getTriggeringRecord(params, seriesData) || {}
  757. return labelItem.type === 'category'
  758. ? Array.isArray(record)
  759. ? record[0][labelItem.name]
  760. : record[labelItem.name] || ''
  761. : Array.isArray(record)
  762. ? record.reduce(
  763. (sum, r) =>
  764. sum + r[`${labelItem.agg}(${decodeMetricName(labelItem.name)})`],
  765. 0
  766. )
  767. : record[`${labelItem.agg}(${decodeMetricName(labelItem.name)})`] || 0
  768. }
  769. }
  770. export function getTriggeringRecord(params, seriesData) {
  771. const { seriesIndex, dataIndex } = params
  772. const { type, grouped, records } = seriesData[seriesIndex]
  773. let record
  774. if (type === 'cartesian') {
  775. record = grouped ? records[dataIndex] : records[dataIndex].value
  776. } else if (type === 'polar') {
  777. record = records[dataIndex]
  778. } else {
  779. record = records ? records[0] : {}
  780. }
  781. return record
  782. }
  783. export function getSizeRate(min, max) {
  784. return Math.max(min / 10, max / 100)
  785. }
  786. export function getSizeValue(value) {
  787. return value >= PIVOT_DEFAULT_SCATTER_SIZE_TIMES
  788. ? value - PIVOT_DEFAULT_SCATTER_SIZE_TIMES + 1
  789. : 1 / Math.pow(2, PIVOT_DEFAULT_SCATTER_SIZE_TIMES - value)
  790. }
  791. export const iconMapping = {
  792. line: 'icon-chart-line',
  793. bar: 'icon-chart-bar',
  794. scatter: 'icon-scatter-chart',
  795. pie: 'icon-chartpie',
  796. area: 'icon-area-chart',
  797. sankey: 'icon-kongjiansangjitu',
  798. funnel: 'icon-iconloudoutu',
  799. treemap: 'icon-chart-treemap',
  800. wordCloud: 'icon-chartwordcloud',
  801. table: 'icon-table',
  802. scorecard: 'icon-calendar1',
  803. text: 'icon-text',
  804. map: 'icon-china',
  805. doubleYAxis: 'icon-duplex',
  806. boxplot: 'icon-508tongji_xiangxiantu',
  807. markBoxplot: 'icon-508tongji_xiangxiantu',
  808. graph: 'icon-510tongji_guanxitu',
  809. waterfall: 'icon-waterfall',
  810. gauge: 'icon-gauge',
  811. radar: 'icon-radarchart',
  812. parallel: 'icon-parallel',
  813. confidenceBand: 'icon-confidence-band'
  814. }
  815. export function getCorrectInputNumber(num: any): number {
  816. switch (typeof num) {
  817. case 'string':
  818. if (!num.trim()) {
  819. return null
  820. } else {
  821. return Number(num) || null
  822. }
  823. return
  824. case 'number':
  825. return num
  826. default:
  827. return null
  828. }
  829. }