bar.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. /*
  2. * <<
  3. * Davinci
  4. * ==
  5. * Copyright (C) 2016 - 2017 EDP
  6. * ==
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. * >>
  19. */
  20. import { IChartProps } from '../../components/Chart'
  21. import { DEFAULT_SPLITER } from 'app/globalConstants'
  22. import {
  23. IFieldFormatConfig,
  24. getFormattedValue,
  25. FieldFormatTypes
  26. } from 'containers/Widget/components/Config/Format'
  27. import { decodeMetricName, getChartTooltipLabel } from '../../components/util'
  28. import {
  29. getDimetionAxisOption,
  30. getMetricAxisOption,
  31. getLabelOption,
  32. getLegendOption,
  33. getGridPositions,
  34. makeGrouped,
  35. getGroupedXaxis,
  36. getCartesianChartMetrics,
  37. getCartesianChartReferenceOptions
  38. } from './util'
  39. import {
  40. getStackName,
  41. EmptyStack
  42. } from 'containers/Widget/components/Config/Stack'
  43. const defaultTheme = require('assets/json/echartsThemes/default.project.json')
  44. const defaultThemeColors = defaultTheme.theme.color
  45. import { inGroupColorSort } from '../../components/Config/Sort/util'
  46. import { FieldSortTypes } from '../../components/Config/Sort'
  47. import ChartTypes from '../../config/chart/ChartTypes'
  48. export default function (chartProps: IChartProps, drillOptions) {
  49. const { data, cols, chartStyles, color, tip, references } = chartProps
  50. const {
  51. isDrilling,
  52. getDataDrillDetail,
  53. instance,
  54. selectedItems,
  55. callback
  56. } = drillOptions
  57. const metrics = getCartesianChartMetrics(chartProps.metrics)
  58. const { bar, label, legend, xAxis, yAxis, splitLine } = chartStyles
  59. const {
  60. barChart,
  61. border: barBorder,
  62. gap: barGap,
  63. width: barWidth,
  64. stack: stackConfig
  65. } = bar
  66. const {
  67. color: borderColor,
  68. width: borderWidth,
  69. type: borderType,
  70. radius: barBorderRadius
  71. } = barBorder
  72. const { on: turnOnStack, percentage } = stackConfig || EmptyStack
  73. const {
  74. showVerticalLine,
  75. verticalLineColor,
  76. verticalLineSize,
  77. verticalLineStyle,
  78. showHorizontalLine,
  79. horizontalLineColor,
  80. horizontalLineSize,
  81. horizontalLineStyle
  82. } = splitLine
  83. const labelOption = {
  84. label: {
  85. ...getLabelOption('bar', label, metrics, false, {
  86. formatter: (params) => {
  87. const { value, seriesId } = params
  88. const m = metrics.find(
  89. (m) =>
  90. m.name ===
  91. seriesId.split(`${DEFAULT_SPLITER}${DEFAULT_SPLITER}`)[0]
  92. )
  93. let format: IFieldFormatConfig = m.format
  94. let formattedValue = value
  95. if (percentage) {
  96. format = {
  97. formatType: FieldFormatTypes.Percentage,
  98. [FieldFormatTypes.Percentage]: {
  99. decimalPlaces: 0
  100. }
  101. }
  102. formattedValue /= 100
  103. }
  104. const formatted = getFormattedValue(formattedValue, format)
  105. return formatted
  106. }
  107. })
  108. }
  109. }
  110. const referenceOptions = getCartesianChartReferenceOptions(
  111. references,
  112. ChartTypes.Bar,
  113. metrics,
  114. data,
  115. barChart
  116. )
  117. const xAxisColumnName = cols.length ? cols[0].name : ''
  118. let xAxisData = []
  119. let grouped = {}
  120. let percentGrouped = {}
  121. if (color.items.length) {
  122. xAxisData = getGroupedXaxis(data, xAxisColumnName, metrics)
  123. grouped = makeGrouped(
  124. data,
  125. color.items.map((c) => c.name),
  126. xAxisColumnName,
  127. metrics,
  128. xAxisData
  129. )
  130. const configValue = color.items[0].config.values
  131. const configKeys = []
  132. Object.entries(configValue).forEach(([k, v]: [string, string]) => {
  133. configKeys.push(k)
  134. })
  135. percentGrouped = makeGrouped(
  136. data,
  137. cols.map((c) => c.name),
  138. color.items[0].name,
  139. metrics,
  140. configKeys
  141. )
  142. } else {
  143. xAxisData = data.map((d) => d[xAxisColumnName] || '')
  144. }
  145. const series = []
  146. const seriesData = []
  147. metrics.forEach((m, i) => {
  148. const decodedMetricName = decodeMetricName(m.name)
  149. const stackOption = turnOnStack
  150. ? { stack: getStackName(m.name, stackConfig) }
  151. : null
  152. if (color.items.length) {
  153. const sumArr = []
  154. Object.entries(percentGrouped).forEach(([k, v]: [string, any[]]) => {
  155. sumArr.push(getColorDataSum(v, metrics))
  156. })
  157. const groupEntries = Object.entries(grouped)
  158. const customColorSort = color.items
  159. .filter(({ sort }) => sort && sort.sortType === FieldSortTypes.Custom)
  160. .map(({ name, sort }) => ({
  161. name,
  162. list: sort[FieldSortTypes.Custom].sortList
  163. }))
  164. if (customColorSort.length) {
  165. inGroupColorSort(groupEntries, customColorSort[0])
  166. }
  167. groupEntries.forEach(([k, v]: [string, any[]], gIndex) => {
  168. const serieObj = {
  169. id: `${m.name}${DEFAULT_SPLITER}${DEFAULT_SPLITER}${k}`,
  170. name: `${k}${metrics.length > 1 ? ` ${m.displayName}` : ''}`,
  171. type: 'bar',
  172. ...stackOption,
  173. sampling: 'average',
  174. data: v.map((g, index) => {
  175. if (
  176. selectedItems &&
  177. selectedItems.length &&
  178. selectedItems.some((item) => item === index)
  179. ) {
  180. return {
  181. value: percentage
  182. ? (g[`${m.agg}(${decodedMetricName})`] / sumArr[index]) * 100
  183. : g[`${m.agg}(${decodedMetricName})`],
  184. itemStyle: {
  185. normal: {
  186. opacity: 1
  187. }
  188. }
  189. }
  190. } else {
  191. if (percentage) {
  192. return (
  193. (g[`${m.agg}(${decodedMetricName})`] / sumArr[index]) * 100
  194. )
  195. } else {
  196. return g[`${m.agg}(${decodedMetricName})`]
  197. }
  198. }
  199. }),
  200. itemStyle: {
  201. normal: {
  202. opacity: selectedItems && selectedItems.length > 0 ? 0.25 : 1,
  203. color: color.items[0].config.values[k]
  204. }
  205. },
  206. ...labelOption,
  207. ...(gIndex === groupEntries.length - 1 &&
  208. i === metrics.length - 1 &&
  209. referenceOptions)
  210. }
  211. series.push(serieObj)
  212. seriesData.push(grouped[k])
  213. })
  214. } else {
  215. const serieObj = {
  216. id: m.name,
  217. name: m.displayName,
  218. type: 'bar',
  219. ...stackOption,
  220. sampling: 'average',
  221. data: data.map((d, index) => {
  222. if (
  223. selectedItems &&
  224. selectedItems.length &&
  225. selectedItems.some((item) => item === index)
  226. ) {
  227. return {
  228. value: percentage
  229. ? (d[`${m.agg}(${decodedMetricName})`] /
  230. getDataSum(data, metrics)[index]) *
  231. 100
  232. : d[`${m.agg}(${decodedMetricName})`],
  233. itemStyle: {
  234. normal: {
  235. opacity: 1
  236. }
  237. }
  238. }
  239. } else {
  240. if (percentage) {
  241. return (
  242. (d[`${m.agg}(${decodedMetricName})`] /
  243. getDataSum(data, metrics)[index]) *
  244. 100
  245. )
  246. } else {
  247. return d[`${m.agg}(${decodedMetricName})`]
  248. }
  249. }
  250. }),
  251. itemStyle: {
  252. normal: {
  253. opacity: selectedItems && selectedItems.length > 0 ? 0.25 : 1,
  254. borderColor,
  255. borderWidth,
  256. borderType,
  257. barBorderRadius,
  258. color:
  259. color.value[m.name] ||
  260. defaultThemeColors[i % defaultThemeColors.length]
  261. }
  262. },
  263. barGap: `${barGap}%`,
  264. barWidth: barWidth ? `${barWidth}%` : undefined,
  265. // lineStyle: {
  266. // normal: {
  267. // opacity: interactIndex === undefined ? 1 : 0.25
  268. // }
  269. // },
  270. // itemStyle: {
  271. // normal: {
  272. // opacity: interactIndex === undefined ? 1 : 0.25
  273. // color: color.value[m.name] || defaultThemeColors[i]
  274. // }
  275. // },
  276. ...labelOption,
  277. ...(i === metrics.length - 1 && referenceOptions)
  278. }
  279. series.push(serieObj)
  280. seriesData.push([...data])
  281. }
  282. })
  283. const seriesNames = series.map((s) => s.name)
  284. if (turnOnStack && stackConfig.sum.show) {
  285. const {
  286. fontFamily,
  287. fontStyle,
  288. fontColor,
  289. fontSize,
  290. fontWeight
  291. } = stackConfig.sum.font
  292. const sumSeries = series.reduce((acc, serie, serieIdx) => {
  293. const stackName = serie.stack
  294. if (acc[stackName]) {
  295. return acc
  296. }
  297. acc[stackName] = {
  298. name: stackName,
  299. type: 'bar',
  300. stack: stackName,
  301. label: {
  302. normal: {
  303. show: true,
  304. color: fontColor,
  305. fontStyle,
  306. fontWeight,
  307. fontFamily,
  308. fontSize,
  309. position: barChart ? 'right' : 'top',
  310. formatter: (params) => {
  311. let val = series
  312. .filter((s) => s.stack === stackName)
  313. .reduce((acc, s) => {
  314. const dataIndex = params.dataIndex
  315. if (typeof s.data[dataIndex] === 'number') {
  316. return acc + s.data[params.dataIndex]
  317. } else {
  318. const { value } = s.data[dataIndex]
  319. return acc + value
  320. }
  321. }, 0)
  322. let format = metrics[serieIdx].format
  323. if (percentage) {
  324. format = {
  325. formatType: FieldFormatTypes.Percentage,
  326. [FieldFormatTypes.Percentage]: {
  327. decimalPlaces: 0
  328. }
  329. }
  330. val /= 100
  331. }
  332. const formattedValue = getFormattedValue(val, format)
  333. return formattedValue
  334. }
  335. }
  336. },
  337. data: Array.from(xAxisData).fill(0)
  338. }
  339. return acc
  340. }, {})
  341. series.push(...Object.values(sumSeries))
  342. }
  343. const brushedOptions =
  344. isDrilling === true
  345. ? {
  346. brush: {
  347. toolbox: ['rect', 'polygon', 'keep', 'clear'],
  348. throttleType: 'debounce',
  349. throttleDelay: 300,
  350. brushStyle: {
  351. borderWidth: 1,
  352. color: 'rgba(255,255,255,0.2)',
  353. borderColor: 'rgba(120,140,180,0.6)'
  354. }
  355. }
  356. }
  357. : null
  358. // if (isDrilling) {
  359. // // instance.off('brushselected')
  360. // instance.on('brushselected', brushselected)
  361. // setTimeout(() => {
  362. // instance.dispatchAction({
  363. // type: 'takeGlobalCursor',
  364. // key: 'brush',
  365. // brushOption: {
  366. // brushType: 'rect',
  367. // brushMode: 'multiple'
  368. // }
  369. // })
  370. // }, 0)
  371. // }
  372. if (callback) {
  373. callback.call(null, seriesData)
  374. }
  375. function brushselected(params) {
  376. const brushComponent = params.batch[0]
  377. const brushed = []
  378. const sourceData = seriesData[0]
  379. let range: any[] = []
  380. if (brushComponent && brushComponent.areas && brushComponent.areas.length) {
  381. brushComponent.areas.forEach((area) => {
  382. range = range.concat(area.range)
  383. })
  384. }
  385. if (
  386. brushComponent &&
  387. brushComponent.selected &&
  388. brushComponent.selected.length
  389. ) {
  390. for (let i = 0; i < brushComponent.selected.length; i++) {
  391. const rawIndices = brushComponent.selected[i].dataIndex
  392. const seriesIndex = brushComponent.selected[i].seriesIndex
  393. brushed.push({ [i]: rawIndices })
  394. }
  395. }
  396. if (getDataDrillDetail) {
  397. getDataDrillDetail(JSON.stringify({ range, brushed, sourceData }))
  398. }
  399. }
  400. const xAxisSplitLineConfig = {
  401. showLine: showVerticalLine,
  402. lineColor: verticalLineColor,
  403. lineSize: verticalLineSize,
  404. lineStyle: verticalLineStyle
  405. }
  406. const yAxisSplitLineConfig = {
  407. showLine: showHorizontalLine,
  408. lineColor: horizontalLineColor,
  409. lineSize: horizontalLineSize,
  410. lineStyle: horizontalLineStyle
  411. }
  412. const dimetionAxisOption = getDimetionAxisOption(
  413. xAxis,
  414. xAxisSplitLineConfig,
  415. xAxisData
  416. )
  417. const metricAxisOption = getMetricAxisOption(
  418. yAxis,
  419. yAxisSplitLineConfig,
  420. metrics.map((m) => decodeMetricName(m.name)).join(` / `),
  421. 'x',
  422. percentage
  423. )
  424. return {
  425. xAxis: barChart ? metricAxisOption : dimetionAxisOption,
  426. yAxis: barChart ? dimetionAxisOption : metricAxisOption,
  427. series,
  428. tooltip: {
  429. formatter: getChartTooltipLabel('bar', seriesData, {
  430. cols,
  431. metrics,
  432. color,
  433. tip
  434. })
  435. },
  436. legend: getLegendOption(legend, seriesNames),
  437. grid: getGridPositions(
  438. legend,
  439. seriesNames,
  440. '',
  441. barChart,
  442. yAxis,
  443. xAxis,
  444. xAxisData
  445. )
  446. // ...brushedOptions
  447. }
  448. }
  449. export function getDataSum(data, metrics) {
  450. const dataSum = data.map((d, index) => {
  451. const metricArr = []
  452. let maSum = 0
  453. metrics.forEach((m, i) => {
  454. const decodedMetricName = decodeMetricName(m.name)
  455. const metricName = d[`${m.agg}(${decodedMetricName})`]
  456. metricArr.push(metricName)
  457. if (metricArr.length === metrics.length) {
  458. metricArr.forEach((mr) => {
  459. maSum += mr
  460. })
  461. }
  462. })
  463. return maSum
  464. })
  465. return dataSum
  466. }
  467. export function getColorDataSum(data, metrics) {
  468. let maSum = 0
  469. const dataSum = data.map((d, index) => {
  470. let metricArr = 0
  471. metrics.forEach((m, i) => {
  472. const decodedMetricName = decodeMetricName(m.name)
  473. const metricName = d[`${m.agg}(${decodedMetricName})`]
  474. metricArr += metricName
  475. })
  476. return metricArr
  477. })
  478. dataSum.forEach((mr) => {
  479. maSum += mr
  480. })
  481. return maSum
  482. }