| /** |
| * 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 { useCallback } from 'react'; |
| import { |
| AxisType, |
| BinaryQueryObjectFilterClause, |
| DTTM_ALIAS, |
| DataRecordValue, |
| getColumnLabel, |
| getNumberFormatter, |
| getTimeFormatter, |
| } from '@superset-ui/core'; |
| import { EchartsMixedTimeseriesChartTransformedProps } from './types'; |
| import Echart from '../components/Echart'; |
| import { EventHandlers } from '../types'; |
| import { formatSeriesName } from '../utils/series'; |
| |
| export default function EchartsMixedTimeseries({ |
| height, |
| width, |
| echartOptions, |
| setDataMask, |
| labelMap, |
| labelMapB, |
| groupby, |
| groupbyB, |
| selectedValues, |
| formData, |
| emitCrossFilters, |
| seriesBreakdown, |
| onContextMenu, |
| onFocusedSeries, |
| xValueFormatter, |
| xAxis, |
| refs, |
| coltypeMapping, |
| }: EchartsMixedTimeseriesChartTransformedProps) { |
| const isFirstQuery = useCallback( |
| (seriesIndex: number) => seriesIndex < seriesBreakdown, |
| [seriesBreakdown], |
| ); |
| |
| const getCrossFilterDataMask = useCallback( |
| (seriesName, seriesIndex) => { |
| const selected: string[] = Object.values(selectedValues || {}); |
| let values: string[]; |
| if (selected.includes(seriesName)) { |
| values = selected.filter(v => v !== seriesName); |
| } else { |
| values = [seriesName]; |
| } |
| |
| const currentGroupBy = isFirstQuery(seriesIndex) ? groupby : groupbyB; |
| const currentLabelMap = isFirstQuery(seriesIndex) ? labelMap : labelMapB; |
| const groupbyValues = values |
| .map(value => currentLabelMap?.[value]) |
| .filter(value => !!value); |
| |
| return { |
| dataMask: { |
| extraFormData: { |
| // @ts-ignore |
| filters: |
| values.length === 0 |
| ? [] |
| : [ |
| ...currentGroupBy.map((col, idx) => { |
| const val: DataRecordValue[] = groupbyValues.map( |
| v => v[idx], |
| ); |
| if (val === null || val === undefined) |
| return { |
| col, |
| op: 'IS NULL' as const, |
| }; |
| return { |
| col, |
| op: 'IN' as const, |
| val: val as (string | number | boolean)[], |
| }; |
| }), |
| ], |
| }, |
| filterState: { |
| value: !groupbyValues.length ? null : groupbyValues, |
| selectedValues: values.length ? values : null, |
| }, |
| }, |
| isCurrentValueSelected: selected.includes(seriesName), |
| }; |
| }, |
| [groupby, groupbyB, isFirstQuery, labelMap, labelMapB, selectedValues], |
| ); |
| |
| const handleChange = useCallback( |
| (seriesName: string, seriesIndex: number) => { |
| const isFirst = isFirstQuery(seriesIndex); |
| if ( |
| !emitCrossFilters || |
| (isFirst && groupby.length === 0) || |
| (!isFirst && groupbyB.length === 0) |
| ) { |
| return; |
| } |
| |
| setDataMask(getCrossFilterDataMask(seriesName, seriesIndex).dataMask); |
| }, |
| [ |
| isFirstQuery, |
| emitCrossFilters, |
| groupby.length, |
| groupbyB.length, |
| setDataMask, |
| getCrossFilterDataMask, |
| ], |
| ); |
| |
| const eventHandlers: EventHandlers = { |
| click: props => { |
| const { seriesName, seriesIndex } = props; |
| handleChange(seriesName, seriesIndex); |
| }, |
| mouseout: () => { |
| onFocusedSeries(null); |
| }, |
| mouseover: params => { |
| onFocusedSeries(params.seriesName); |
| }, |
| contextmenu: async eventParams => { |
| if (onContextMenu) { |
| eventParams.event.stop(); |
| const { data, seriesName, seriesIndex } = eventParams; |
| const pointerEvent = eventParams.event.event; |
| const drillToDetailFilters: BinaryQueryObjectFilterClause[] = []; |
| const drillByFilters: BinaryQueryObjectFilterClause[] = []; |
| const isFirst = isFirstQuery(seriesIndex); |
| const values = [ |
| ...(eventParams.name ? [eventParams.name] : []), |
| ...((isFirst ? labelMap : labelMapB)[eventParams.seriesName] || []), |
| ]; |
| if (data && xAxis.type === AxisType.Time) { |
| drillToDetailFilters.push({ |
| col: |
| xAxis.label === DTTM_ALIAS |
| ? formData.granularitySqla |
| : xAxis.label, |
| grain: formData.timeGrainSqla, |
| op: '==', |
| val: data[0], |
| formattedVal: xValueFormatter(data[0]), |
| }); |
| } |
| [ |
| ...(data && xAxis.type === AxisType.Category ? [xAxis.label] : []), |
| ...(isFirst ? formData.groupby : formData.groupbyB), |
| ].forEach((dimension, i) => |
| drillToDetailFilters.push({ |
| col: dimension, |
| op: '==', |
| val: values[i], |
| formattedVal: String(values[i]), |
| }), |
| ); |
| |
| [...(isFirst ? formData.groupby : formData.groupbyB)].forEach( |
| (dimension, i) => |
| drillByFilters.push({ |
| col: dimension, |
| op: '==', |
| val: values[i], |
| formattedVal: formatSeriesName(values[i], { |
| timeFormatter: getTimeFormatter(formData.dateFormat), |
| numberFormatter: getNumberFormatter(formData.numberFormat), |
| coltype: coltypeMapping?.[getColumnLabel(dimension)], |
| }), |
| }), |
| ); |
| const hasCrossFilter = |
| (isFirst && groupby.length > 0) || (!isFirst && groupbyB.length > 0); |
| |
| onContextMenu(pointerEvent.clientX, pointerEvent.clientY, { |
| drillToDetail: drillToDetailFilters, |
| crossFilter: hasCrossFilter |
| ? getCrossFilterDataMask(seriesName, seriesIndex) |
| : undefined, |
| drillBy: { |
| filters: drillByFilters, |
| groupbyFieldName: isFirst ? 'groupby' : 'groupby_b', |
| adhocFilterFieldName: isFirst ? 'adhoc_filters' : 'adhoc_filters_b', |
| }, |
| }); |
| } |
| }, |
| }; |
| |
| return ( |
| <Echart |
| refs={refs} |
| height={height} |
| width={width} |
| echartOptions={echartOptions} |
| eventHandlers={eventHandlers} |
| selectedValues={selectedValues} |
| /> |
| ); |
| } |