123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798 |
- /*
- * <<
- * Davinci
- * ==
- * Copyright (C) 2016 - 2017 EDP
- * ==
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * >>
- */
- import mean from 'lodash/mean'
- import { IAxisConfig } from '../../components/Workbench/ConfigSections/AxisSection'
- import { ILabelConfig } from '../../components/Workbench/ConfigSections/LabelSection'
- import { ILegendConfig } from '../../components/Workbench/ConfigSections/LegendSection'
- import { getFormattedValue } from '../../components/Config/Format'
- import { CHART_LEGEND_POSITIONS, DEFAULT_SPLITER } from 'app/globalConstants'
- import { EChartOption } from 'echarts'
- import { IWidgetMetric } from '../../components/Widget'
- import {
- metricAxisLabelFormatter,
- decodeMetricName,
- getTextWidth,
- getAggregatorLocale
- } from '../../components/util'
- import { FieldSortTypes } from '../../components/Config/Sort'
- import { getFieldAlias } from '../../components/Config/Field'
- import {
- IReference,
- IReferenceLineData,
- IReferenceBandData
- } from '../../components/Workbench/Reference/types'
- import {
- ReferenceType,
- ReferenceValueType
- } from '../../components/Workbench/Reference/constants'
- import ChartTypes from '../../config/chart/ChartTypes'
- interface ISplitLineConfig {
- showLine: boolean
- lineStyle: 'solid' | 'dashed' | 'dotted'
- lineSize: string
- lineColor: string
- }
- export function getDimetionAxisOption(
- dimetionAxisConfig: IAxisConfig,
- splitLineConfig: ISplitLineConfig,
- data: string[]
- ): EChartOption.XAxis {
- const {
- inverse,
- showLine: showLineX,
- lineStyle: lineStyleX,
- lineSize: lineSizeX,
- lineColor: lineColorX,
- showLabel: showLabelX,
- labelFontFamily: labelFontFamilyX,
- labelFontSize: labelFontSizeX,
- labelColor: labelColorX,
- nameLocation,
- nameGap,
- nameRotate,
- showInterval,
- xAxisInterval,
- xAxisRotate
- } = dimetionAxisConfig
- const { showLine, lineStyle, lineSize, lineColor } = splitLineConfig
- const intervalOption = showInterval ? { interval: xAxisInterval } : null
- return {
- data,
- inverse,
- axisLabel: {
- show: showLabelX,
- color: labelColorX,
- fontFamily: labelFontFamilyX,
- fontSize: Number(labelFontSizeX),
- rotate: xAxisRotate,
- ...intervalOption
- },
- axisLine: {
- show: showLineX,
- lineStyle: {
- color: lineColorX,
- width: Number(lineSizeX),
- type: lineStyleX
- }
- },
- axisTick: {
- show: showLabelX,
- lineStyle: {
- color: lineColorX
- }
- },
- splitLine: {
- show: showLine,
- lineStyle: {
- color: lineColor,
- width: Number(lineSize),
- type: lineStyle
- }
- },
- nameLocation,
- nameRotate,
- nameGap
- }
- }
- export function getMetricAxisOption(
- metricAxisConfig: IAxisConfig,
- splitLineConfig: ISplitLineConfig,
- title: string,
- axis: 'x' | 'y' = 'y',
- percentage?: boolean
- ): EChartOption.YAxis {
- const {
- inverse,
- showLine: showLineY,
- lineStyle: lineStyleY,
- lineSize: lineSizeY,
- lineColor: lineColorY,
- showLabel: showLabelY,
- labelFontFamily: labelFontFamilyY,
- labelFontSize: labelFontSizeY,
- labelColor: labelColorY,
- showTitleAndUnit,
- titleFontFamily,
- titleFontSize,
- titleColor,
- nameLocation,
- nameRotate,
- nameGap,
- min,
- max
- } = metricAxisConfig
- const { showLine, lineStyle, lineSize, lineColor } = splitLineConfig
- return {
- type: 'value',
- inverse,
- min: percentage ? 0 : min,
- max: percentage ? 100 : max,
- axisLabel: {
- show: showLabelY,
- color: labelColorY,
- fontFamily: labelFontFamilyY,
- fontSize: Number(labelFontSizeY),
- formatter: percentage ? '{value}%' : metricAxisLabelFormatter
- },
- axisLine: {
- show: showLineY,
- lineStyle: {
- color: lineColorY,
- width: Number(lineSizeY),
- type: lineStyleY
- }
- },
- axisTick: {
- show: showLabelY,
- lineStyle: {
- color: lineColorY
- }
- },
- name: showTitleAndUnit ? title : '',
- nameLocation,
- nameGap,
- nameRotate,
- nameTextStyle: {
- color: titleColor,
- fontFamily: titleFontFamily,
- fontSize: Number(titleFontSize)
- },
- splitLine: {
- show: showLine,
- lineStyle: {
- color: lineColor,
- width: Number(lineSize),
- type: lineStyle
- }
- }
- }
- }
- export function getLabelOption(
- type: string,
- labelConfig: ILabelConfig,
- metrics,
- emphasis?: boolean,
- options?: object
- ) {
- const {
- showLabel,
- labelPosition,
- labelFontFamily,
- labelFontSize,
- labelColor,
- pieLabelPosition,
- funnelLabelPosition
- } = labelConfig
- let position
- switch (type) {
- case 'pie':
- position = pieLabelPosition
- break
- case 'funnel':
- position = funnelLabelPosition
- break
- default:
- position = labelPosition
- break
- }
- let formatter
- switch (type) {
- case 'line':
- formatter = (params) => {
- const { value, seriesId } = params
- const m = metrics.find(
- (m) =>
- m.name === seriesId.split(`${DEFAULT_SPLITER}${DEFAULT_SPLITER}`)[0]
- )
- const formattedValue = getFormattedValue(value, m.format)
- return formattedValue
- }
- break
- case 'waterfall':
- formatter = (params) => {
- const { value } = params
- const formattedValue = getFormattedValue(value, metrics[0].format)
- return formattedValue
- }
- break
- case 'scatter':
- formatter = (params) => {
- const { value } = params
- const formattedValue = getFormattedValue(value[0], metrics[0].format)
- return formattedValue
- }
- break
- case 'pie':
- case 'funnel':
- formatter = (params) => {
- const { name, value, percent, dataIndex, data } = params
- const formattedValue = getFormattedValue(
- value,
- metrics[metrics.length > 1 ? dataIndex : 0].format
- )
- const { labelParts } = labelConfig
- if (!labelParts) {
- return `${name}\n${formattedValue}(${percent}%)`
- }
- const labels: string[] = []
- const multiRate =
- labelParts.filter((label) =>
- ['percentage', 'conversion', 'arrival'].includes(label)
- ).length > 1
- if (labelParts.includes('dimensionValue')) {
- labels.push(name)
- }
- if (labelParts.includes('indicatorValue')) {
- labels.push(formattedValue)
- }
- if (labelParts.includes('conversion') && data.conversion) {
- labels.push(`${multiRate ? '转化率:' : ''}${data.conversion}%`)
- }
- if (labelParts.includes('arrival') && data.arrival) {
- labels.push(`${multiRate ? '到达率:' : ''}${data.arrival}%`)
- }
- if (labelParts.includes('percentage')) {
- labels.push(`${multiRate ? '百分比:' : ''}${percent}%`)
- }
- return labels.join('\n')
- }
- break
- case 'radar':
- formatter = (params) => {
- const { name, value, dataIndex, data } = params
- const metricIdx = data.name ? dataIndex : data.value.indexOf(value)
- const formattedValue = getFormattedValue(
- value,
- metrics[metricIdx].format
- )
- const labelName =
- name ||
- getFieldAlias(metrics[metricIdx].field, {}) ||
- decodeMetricName(metrics[metricIdx].name)
- const { labelParts } = labelConfig
- if (!labelParts) {
- return `${labelName}\n${formattedValue}`
- }
- const labels: string[] = []
- if (labelParts.includes('indicatorName')) {
- labels.push(labelName)
- }
- if (labelParts.includes('indicatorValue')) {
- labels.push(formattedValue)
- }
- if (labels.length > 1) {
- labels.splice(1, 0, '\n')
- }
- return labels.join('')
- }
- break
- case 'lines':
- formatter = (param) => {
- const { name, data } = param
- return `${name}(${data.value[2]})`
- }
- break
- }
- const labelOption = {
- normal: {
- show: type === 'pie' && pieLabelPosition === 'center' ? false : showLabel,
- position,
- distance: 15,
- color: labelColor,
- fontFamily: labelFontFamily,
- fontSize: labelFontSize,
- formatter,
- ...options
- },
- ...(emphasis && {
- emphasis: {
- show: showLabel,
- position,
- distance: 15,
- color: labelColor,
- fontFamily: labelFontFamily,
- fontSize: labelFontSize,
- formatter,
- ...options
- }
- })
- }
- return labelOption
- }
- export function getLegendOption(
- legendConfig: ILegendConfig,
- seriesNames: string[]
- ) {
- const {
- showLegend,
- legendPosition,
- legendType,
- selectAll,
- fontFamily,
- fontSize,
- color
- } = legendConfig
- let orient
- let positions
- let type
- switch (legendType) {
- case 'plain':
- type = 'plain'
- break
- case 'scroll':
- type = 'scroll'
- break
- default:
- break
- }
- switch (legendPosition) {
- case 'top':
- orient = { orient: 'horizontal' }
- positions = { top: 8, left: 8, right: 8, height: 32 }
- break
- case 'bottom':
- orient = { orient: 'horizontal' }
- positions = { bottom: 8, left: 8, right: 8, height: 32 }
- break
- case 'left':
- orient = { orient: 'vertical' }
- positions = { left: 8, top: 16, bottom: 24, width: 96 }
- break
- default:
- orient = { orient: 'vertical' }
- positions = { right: 8, top: 16, bottom: 24, width: 96 }
- break
- }
- const selected = {
- selected: seriesNames.reduce(
- (obj, name) => ({
- ...obj,
- [name]: selectAll
- }),
- {}
- )
- }
- return {
- show: showLegend && seriesNames.length > 1,
- data: seriesNames,
- type,
- textStyle: {
- fontFamily,
- fontSize,
- color
- },
- ...orient,
- ...positions,
- ...selected
- }
- }
- export function getGridPositions(
- legendConfig: Partial<ILegendConfig>,
- seriesNames,
- chartName?: string,
- isHorizontalBar?: boolean,
- yAxisConfig?: IAxisConfig,
- dimetionAxisConfig?: IAxisConfig,
- xAxisData?: string[]
- ) {
- const { showLegend, legendPosition, fontSize } = legendConfig
- return CHART_LEGEND_POSITIONS.reduce((grid, pos) => {
- const val = pos.value
- grid[val] = getGridBase(
- val,
- chartName,
- dimetionAxisConfig,
- xAxisData,
- isHorizontalBar,
- yAxisConfig
- )
- if (showLegend && seriesNames.length > 1) {
- grid[val] +=
- legendPosition === val
- ? ['top', 'bottom'].includes(val)
- ? 64
- : 64 +
- Math.max(
- ...seriesNames.map((s) => getTextWidth(s, '', `${fontSize}px`))
- )
- : 0
- }
- return grid
- }, {})
- }
- function getGridBase(
- pos,
- chartName,
- dimetionAxisConfig?: IAxisConfig,
- xAxisData?: string[],
- isHorizontalBar?: boolean,
- yAxisConfig?: IAxisConfig
- ) {
- const labelFontSize = dimetionAxisConfig
- ? dimetionAxisConfig.labelFontSize
- : 12
- const xAxisRotate = dimetionAxisConfig ? dimetionAxisConfig.xAxisRotate : 0
- const maxWidth =
- xAxisData && xAxisData.length
- ? Math.max(
- ...xAxisData.map((s) => getTextWidth(s, '', `${labelFontSize}px`))
- )
- : 0
- const bottomDistance =
- dimetionAxisConfig && dimetionAxisConfig.showLabel
- ? isHorizontalBar
- ? 50
- : xAxisRotate
- ? 50 + Math.sin((xAxisRotate * Math.PI) / 180) * maxWidth
- : 50
- : 50
- const yAxisConfigLeft =
- yAxisConfig && !yAxisConfig.showLabel && !yAxisConfig.showTitleAndUnit
- ? 24
- : 64
- const leftDistance =
- dimetionAxisConfig && dimetionAxisConfig.showLabel
- ? isHorizontalBar
- ? xAxisRotate === void 0
- ? 64
- : 24 + Math.cos((xAxisRotate * Math.PI) / 180) * maxWidth
- : yAxisConfigLeft
- : isHorizontalBar
- ? 24
- : yAxisConfigLeft
- switch (pos) {
- case 'top':
- return 24
- case 'left':
- return leftDistance
- case 'right':
- return chartName === 'doubleYAxis' ? 64 : 24
- case 'bottom':
- return bottomDistance
- }
- }
- export function makeGrouped(
- data: object[],
- groupColumns: string[],
- xAxisColumn: string,
- metrics: IWidgetMetric[],
- xAxisData: string[]
- ) {
- const grouped = {}
- data.forEach((d) => {
- const groupingKey = groupColumns.map((col) => d[col]).join(' ')
- const colKey = d[xAxisColumn] || 'default'
- if (!grouped[groupingKey]) {
- grouped[groupingKey] = {}
- }
- if (!grouped[groupingKey][colKey]) {
- grouped[groupingKey][colKey] = []
- }
- grouped[groupingKey][colKey].push(d)
- })
- Object.keys(grouped).forEach((groupingKey) => {
- const currentGroupValues = grouped[groupingKey]
- grouped[groupingKey] = xAxisData.length
- ? xAxisData.map((xd) => {
- if (currentGroupValues[xd]) {
- return currentGroupValues[xd][0]
- } else {
- return metrics.reduce(
- (obj, m) => ({
- ...obj,
- [`${m.agg}(${decodeMetricName(m.name)})`]: 0
- }),
- {
- [xAxisColumn]: xd
- // []: groupingKey
- }
- )
- }
- })
- : [currentGroupValues['default'][0]]
- })
- return grouped
- }
- // TODO: function explanation
- export function getGroupedXaxis(data, xAxisColumn, metrics) {
- if (xAxisColumn) {
- const metricsInSorting = metrics.filter(
- ({ sort }) => sort && sort.sortType !== FieldSortTypes.Default
- )
- const appliedMetric = metricsInSorting.length ? metricsInSorting[0] : void 0
- const dataGroupByXaxis = data.reduce((grouped, d) => {
- const colKey = d[xAxisColumn]
- if (grouped[colKey] === void 0) {
- grouped[colKey] = 0
- }
- if (appliedMetric) {
- const { agg, name } = appliedMetric
- grouped[colKey] += d[`${agg}(${decodeMetricName(name)})`]
- }
- return grouped
- }, {})
- if (appliedMetric) {
- return Object.entries(dataGroupByXaxis)
- .sort((p1: [string, number], p2: [string, number]) => {
- return appliedMetric.sort.sortType === FieldSortTypes.Asc
- ? p1[1] - p2[1]
- : appliedMetric.sort.sortType === FieldSortTypes.Desc
- ? p2[1] - p1[1]
- : 0
- })
- .map(([key, value]) => key)
- } else {
- return Object.keys(dataGroupByXaxis)
- }
- }
- return []
- }
- export function getSymbolSize(sizeRate, size) {
- return sizeRate ? Math.ceil(size / sizeRate) : size
- }
- export function getCartesianChartMetrics(metrics: IWidgetMetric[]) {
- return metrics.map((metric) => {
- const { name, agg } = metric
- const decodedMetricName = decodeMetricName(name)
- const duplicates = metrics.filter(
- (m) => decodeMetricName(m.name) === decodedMetricName && m.agg === agg
- )
- const prefix = agg !== 'sum' ? `[${getAggregatorLocale(agg)}] ` : ''
- const suffix =
- duplicates.length > 1
- ? duplicates.indexOf(metric)
- ? duplicates.indexOf(metric) + 1
- : ''
- : ''
- return {
- ...metric,
- displayName: `${prefix}${decodedMetricName}${suffix}`
- }
- })
- }
- export function getCartesianChartReferenceOptions(
- references: IReference[],
- chartType: ChartTypes,
- metrics: IWidgetMetric[],
- sourcedata: any[],
- barChart?: boolean
- ) {
- if (references) {
- const markLines = []
- const markAreas = []
- references.forEach((ref) => {
- const { name, type, data } = ref
- if (type === ReferenceType.Line) {
- const {
- metric,
- type: valueType,
- value,
- label,
- line
- } = data as IReferenceLineData
- const axis = getReferenceDataMetricAxis(chartType, {
- barChart,
- metrics,
- metric
- })
- if (axis) {
- const metricData = sourcedata.map((d) => {
- const metricObject = metrics.find((m) => m.name === metric)
- return (
- metricObject &&
- d[`${metricObject.agg}(${decodeMetricName(metric)})`]
- )
- })
- markLines.push({
- ...getReferenceDataOptions(metricData, valueType, value, axis),
- name,
- label: {
- show: label.visible,
- position: label.position,
- color: label.font.color,
- fontSize: label.font.size,
- fontFamily: label.font.family
- },
- lineStyle: {
- color: line.color,
- width: line.width,
- type: line.type
- }
- })
- }
- } else {
- const areaData = (data as [IReferenceBandData, IReferenceBandData]).map(
- (d, index) => {
- const { metric, type: valueType, value, label, band } = d
- const axis = getReferenceDataMetricAxis(chartType, {
- barChart,
- metrics,
- metric
- })
- if (axis) {
- const metricData = sourcedata.map((d) => {
- const metricObject = metrics.find((m) => m.name === metric)
- return (
- metricObject &&
- d[`${metricObject.agg}(${decodeMetricName(metric)})`]
- )
- })
- const dataOptions = getReferenceDataOptions(
- metricData,
- valueType,
- value,
- axis
- )
- return !index
- ? dataOptions
- : {
- ...dataOptions,
- name,
- label: {
- show: label.visible,
- position: label.position,
- color: label.font.color,
- fontSize: label.font.size,
- fontFamily: label.font.family
- },
- emphasis: {
- label: {
- position: label.position
- }
- },
- itemStyle: {
- color: band.color,
- borderColor: band.border.color,
- borderWidth: band.border.width,
- borderType: band.border.type
- }
- }
- } else {
- return void 0
- }
- }
- )
- if (areaData.every((d) => !!d)) {
- markAreas.push(areaData)
- }
- }
- })
- return {
- ...(markLines.length && { markLine: { data: markLines } }),
- ...(markAreas.length && { markArea: { data: markAreas } })
- }
- }
- }
- function getReferenceDataOptions(
- metricData: number[],
- valueType: ReferenceValueType,
- value: any,
- axis: string
- ) {
- const option: any = {}
- if (valueType === ReferenceValueType.Constant) {
- option[axis] = value
- } else {
- option[axis] = calcAggregateReferenceData(valueType, metricData)
- }
- return option
- }
- function getReferenceDataMetricAxis(
- chartType: ChartTypes,
- options?: {
- barChart?: boolean
- metrics?: IWidgetMetric[]
- metric?: string
- }
- ) {
- switch (chartType) {
- case ChartTypes.Bar:
- return options.barChart ? 'xAxis' : 'yAxis'
- case ChartTypes.Scatter:
- const axisIndexMapping = ['xAxis', 'yAxis']
- const metricIndex = options.metrics.findIndex(
- (m) => m.name === options.metric
- )
- return axisIndexMapping[metricIndex]
- default:
- return 'yAxis'
- }
- }
- function calcAggregateReferenceData(
- valueType: ReferenceValueType,
- metricData: number[]
- ) {
- switch (valueType) {
- case ReferenceValueType.Max:
- return Math.max(...metricData)
- case ReferenceValueType.Min:
- return Math.min(...metricData)
- case ReferenceValueType.Average:
- return mean(metricData)
- }
- }
|