| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 { |
| CategoricalColorNamespace, |
| ChartProps, |
| DataRecord, |
| getMetricLabel, |
| getNumberFormatter, |
| } from '@superset-ui/core'; |
| import { EChartsOption, BoxplotSeriesOption } from 'echarts'; |
| import { CallbackDataParams } from 'echarts/types/src/util/types'; |
| import { BoxPlotQueryFormData } from './types'; |
| import { EchartsProps } from '../types'; |
| import { extractGroupbyLabel } from '../utils/series'; |
| import { defaultGrid, defaultTooltip, defaultYAxis } from '../defaults'; |
| |
| export default function transformProps(chartProps: ChartProps): EchartsProps { |
| const { width, height, formData, queriesData } = chartProps; |
| const data: DataRecord[] = queriesData[0].data || []; |
| const { |
| colorScheme, |
| groupby = [], |
| metrics: formdataMetrics = [], |
| numberFormat, |
| xTicksLayout, |
| } = formData as BoxPlotQueryFormData; |
| const colorFn = CategoricalColorNamespace.getScale(colorScheme as string); |
| const numberFormatter = getNumberFormatter(numberFormat); |
| const metricLabels = formdataMetrics.map(getMetricLabel); |
| |
| const transformedData = data |
| .map((datum: any) => { |
| const groupbyLabel = extractGroupbyLabel({ datum, groupby }); |
| return metricLabels.map(metric => { |
| const name = metricLabels.length === 1 ? groupbyLabel : `${groupbyLabel}, ${metric}`; |
| return { |
| name, |
| value: [ |
| datum[`${metric}__min`], |
| datum[`${metric}__q1`], |
| datum[`${metric}__median`], |
| datum[`${metric}__q3`], |
| datum[`${metric}__max`], |
| datum[`${metric}__mean`], |
| datum[`${metric}__count`], |
| datum[`${metric}__outliers`], |
| ], |
| itemStyle: { |
| color: colorFn(groupbyLabel), |
| opacity: 0.6, |
| borderColor: colorFn(groupbyLabel), |
| }, |
| }; |
| }); |
| }) |
| .flatMap(row => row); |
| |
| const outlierData = data |
| .map(datum => |
| metricLabels.map(metric => { |
| const groupbyLabel = extractGroupbyLabel({ datum, groupby }); |
| const name = metricLabels.length === 1 ? groupbyLabel : `${groupbyLabel}, ${metric}`; |
| // Outlier data is a nested array of numbers (uncommon, therefore no need to add to DataRecordValue) |
| const outlierDatum = (datum[`${metric}__outliers`] || []) as number[]; |
| return { |
| name: 'outlier', |
| type: 'scatter', |
| data: outlierDatum.map(val => [name, val]), |
| tooltip: { |
| formatter: (param: { data: [string, number] }) => { |
| const [outlierName, stats] = param.data; |
| const headline = groupby ? `<p><strong>${outlierName}</strong></p>` : ''; |
| return `${headline}${numberFormatter(stats)}`; |
| }, |
| }, |
| itemStyle: { |
| color: colorFn(groupbyLabel), |
| }, |
| }; |
| }), |
| ) |
| .flat(2); |
| |
| let axisLabel; |
| if (xTicksLayout === '45°') axisLabel = { rotate: -45 }; |
| else if (xTicksLayout === '90°') axisLabel = { rotate: -90 }; |
| else if (xTicksLayout === 'flat') axisLabel = { rotate: 0 }; |
| else if (xTicksLayout === 'staggered') axisLabel = { rotate: -45 }; |
| else axisLabel = { show: true }; |
| |
| const series: BoxplotSeriesOption[] = [ |
| { |
| name: 'boxplot', |
| type: 'boxplot', |
| data: transformedData, |
| tooltip: { |
| formatter: (param: CallbackDataParams) => { |
| // @ts-ignore |
| const { |
| value, |
| name, |
| }: { |
| value: [number, number, number, number, number, number, number, number, number[]]; |
| name: string; |
| } = param; |
| const headline = name ? `<p><strong>${name}</strong></p>` : ''; |
| const stats = [ |
| `Max: ${numberFormatter(value[5])}`, |
| `3rd Quartile: ${numberFormatter(value[4])}`, |
| `Mean: ${numberFormatter(value[6])}`, |
| `Median: ${numberFormatter(value[3])}`, |
| `1st Quartile: ${numberFormatter(value[2])}`, |
| `Min: ${numberFormatter(value[1])}`, |
| `# Observations: ${numberFormatter(value[7])}`, |
| ]; |
| if (value[8].length > 0) { |
| stats.push(`# Outliers: ${numberFormatter(value[8].length)}`); |
| } |
| return headline + stats.join('<br/>'); |
| }, |
| }, |
| }, |
| // @ts-ignore |
| ...outlierData, |
| ]; |
| |
| const echartOptions: EChartsOption = { |
| grid: { |
| ...defaultGrid, |
| top: 30, |
| bottom: 30, |
| left: 20, |
| right: 20, |
| }, |
| xAxis: { |
| type: 'category', |
| data: transformedData.map(row => row.name), |
| axisLabel, |
| }, |
| yAxis: { |
| ...defaultYAxis, |
| type: 'value', |
| axisLabel: { formatter: numberFormatter }, |
| }, |
| tooltip: { |
| ...defaultTooltip, |
| trigger: 'item', |
| axisPointer: { |
| type: 'shadow', |
| }, |
| }, |
| series, |
| }; |
| |
| return { |
| width, |
| height, |
| echartOptions, |
| }; |
| } |