/** * 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, getColumnLabel, getMetricLabel, getNumberFormatter, getTimeFormatter, } from '@superset-ui/core'; import type { EChartsCoreOption } from 'echarts/core'; import type { BoxplotSeriesOption } from 'echarts/charts'; import type { CallbackDataParams } from 'echarts/types/src/util/types'; import { BoxPlotChartTransformedProps, BoxPlotQueryFormData, EchartsBoxPlotChartProps, } from './types'; import { extractGroupbyLabel, getColtypesMapping, sanitizeHtml, } from '../utils/series'; import { convertInteger } from '../utils/convertInteger'; import { defaultGrid, defaultYAxis } from '../defaults'; import { getPadding } from '../Timeseries/transformers'; import { OpacityEnum } from '../constants'; import { getDefaultTooltip } from '../utils/tooltip'; import { Refs } from '../types'; export default function transformProps( chartProps: EchartsBoxPlotChartProps, ): BoxPlotChartTransformedProps { const { width, height, formData, hooks, filterState, queriesData, inContextMenu, emitCrossFilters, } = chartProps; const { data = [] } = queriesData[0]; const { setDataMask = () => {}, onContextMenu } = hooks; const coltypeMapping = getColtypesMapping(queriesData[0]); const { colorScheme, groupby = [], metrics = [], numberFormat, dateFormat, xTicksLayout, legendOrientation = 'top', xAxisTitle, yAxisTitle, xAxisTitleMargin, yAxisTitleMargin, yAxisTitlePosition, sliceId, } = formData as BoxPlotQueryFormData; const refs: Refs = {}; const colorFn = CategoricalColorNamespace.getScale(colorScheme as string); const numberFormatter = getNumberFormatter(numberFormat); const metricLabels = metrics.map(getMetricLabel); const groupbyLabels = groupby.map(getColumnLabel); const transformedData = data .map((datum: any) => { const groupbyLabel = extractGroupbyLabel({ datum, groupby: groupbyLabels, coltypeMapping, timeFormatter: getTimeFormatter(dateFormat), }); return metricLabels.map(metric => { const name = metricLabels.length === 1 ? groupbyLabel : `${groupbyLabel}, ${metric}`; const isFiltered = filterState.selectedValues && !filterState.selectedValues.includes(name); 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, sliceId, colorScheme), opacity: isFiltered ? OpacityEnum.SemiTransparent : 0.6, borderColor: colorFn(groupbyLabel, sliceId, colorScheme), }, }; }); }) .flatMap(row => row); const outlierData = data .map(datum => metricLabels.map(metric => { const groupbyLabel = extractGroupbyLabel({ datum, groupby: groupbyLabels, coltypeMapping, timeFormatter: getTimeFormatter(dateFormat), }); 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[]; const isFiltered = filterState.selectedValues && !filterState.selectedValues.includes(name); return { name: 'outlier', type: 'scatter', data: outlierDatum.map(val => [name, val]), tooltip: { ...getDefaultTooltip(refs), formatter: (param: { data: [string, number] }) => { const [outlierName, stats] = param.data; const headline = groupbyLabels.length ? `
${sanitizeHtml(outlierName)}
` : ''; return `${headline}${numberFormatter(stats)}`; }, }, itemStyle: { color: colorFn(groupbyLabel, sliceId, colorScheme), opacity: isFiltered ? OpacityEnum.SemiTransparent : OpacityEnum.NonTransparent, }, }; }), ) .flat(2); const labelMap = data.reduce((acc: Record${sanitizeHtml(name)}
` : ''; 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: ${value[7]}`, ]; if (value[8].length > 0) { stats.push(`# Outliers: ${value[8].length}`); } return headline + stats.join('