/**
 * 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 , DataRecordValue , getColumnLabel , getMetricLabel , getNumberFormatter , getTimeFormatter , } from '@superset-ui/core' ; import { EChartsCoreOption , BoxplotSeriesOption } from 'echarts' ; import { CallbackDataParams } from 'echarts/types/src/util/types' ; import { BoxPlotChartTransformedProps , BoxPlotQueryFormData , EchartsBoxPlotChartProps , } from './types' ; import { extractGroupbyLabel , getColtypesMapping , sanitizeHtml , } from '../utils/series' ; import { defaultGrid , defaultTooltip , defaultYAxis } from '../defaults' ; import { getPadding } from '../Timeseries/transformers' ; import { OpacityEnum } from '../constants' ; export default function transformProps ( chartProps ) { const { width , height , formData , hooks , filterState , queriesData } = chartProps ; const { data = [ ] } = queriesData [ 0 ] ; const { setDataMask = ( ) => { } } = hooks ; const coltypeMapping = getColtypesMapping ( queriesData [ 0 ] ) ; const { colorScheme , groupby = [ ] , metrics = [ ] , numberFormat , dateFormat , xTicksLayout , emitFilter , legendOrientation = 'top' , xAxisTitle , yAxisTitle , xAxisTitleMargin , yAxisTitleMargin , yAxisTitlePosition , } = formData as BoxPlotQueryFormData ; 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 ) => { 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 ) , opacity : isFiltered ? OpacityEnum . SemiTransparent : 0.6 , borderColor : colorFn ( groupbyLabel ) , } , } ; } ) ; } ) . 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 }` ; 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 : { formatter : ( param ) => { const [ outlierName , stats ] = param . data ; const headline = groupbyLabels . length ? `<p><strong>${ sanitizeHtml ( outlierName ) }</strong></p>` : '' ; return `${ headline }${ numberFormatter ( stats ) }` ; } , } , itemStyle : { color : colorFn ( groupbyLabel ) , opacity : isFiltered ? OpacityEnum . SemiTransparent : OpacityEnum . NonTransparent , } , } ; } ) ) . flat ( 2 ) ; const labelMap = data . reduce ( ( acc , datum ) => { const label = extractGroupbyLabel ( { datum , groupby : groupbyLabels , coltypeMapping , timeFormatter : getTimeFormatter ( dateFormat ) , } ) ; return { ... acc , [ label ] : groupbyLabels . map ( col => datum [ col ] ) , } ; } , { } ) ; const selectedValues = ( filterState . selectedValues || [ ] ) . reduce ( ( acc , selectedValue ) => { const index = transformedData . findIndex ( ( { name } ) => name === selectedValue ) ; return { ... acc , [ index ] : selectedValue , } ; } , { } ) ; 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 = [ { name : 'boxplot' , type : 'boxplot' , data : transformedData , tooltip : { formatter : ( param ) => { const { value , name , } = param ; const headline = name ? `<p><strong>${ sanitizeHtml ( 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/>' ) ; } , } , } , ... outlierData , ] ; const addYAxisTitleOffset = ! ! yAxisTitle ; const addXAxisTitleOffset = ! ! xAxisTitle ; const chartPadding = getPadding ( true , legendOrientation , addYAxisTitleOffset , false , null , addXAxisTitleOffset , yAxisTitlePosition , yAxisTitleMargin , xAxisTitleMargin ) ; const echartOptions = { grid : { ... defaultGrid , ... chartPadding , } , xAxis : { type : 'category' , data : transformedData . map ( row => row . name ) , axisLabel , name : xAxisTitle , nameGap : xAxisTitleMargin , nameLocation : 'middle' , } , yAxis : { ... defaultYAxis , type : 'value' , axisLabel : { formatter : numberFormatter } , name : yAxisTitle , nameGap : yAxisTitleMargin , nameLocation : yAxisTitlePosition === 'Left' ? 'middle' : 'end' , } , tooltip : { ... defaultTooltip , trigger : 'item' , axisPointer : { type : 'shadow' , } , } , series , } ; return { formData , width , height , echartOptions , setDataMask , emitFilter , labelMap , groupby , selectedValues , } ; }