/** * 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 { getColumnLabel, getNumberFormatter, getTimeFormatter, } from '@superset-ui/core'; import { EventHandlers } from '../types'; import Echart from '../components/Echart'; import { GraphChartTransformedProps } from './types'; import { formatSeriesName } from '../utils/series'; type DataRow = { source?: string; target?: string; id?: string; col: string; name: string; }; type Data = DataRow[]; type Event = { name: string; event: { stop: () => void; event: PointerEvent }; data: DataRow; dataType: 'node' | 'edge'; }; export default function EchartsGraph({ height, width, echartOptions, formData, onContextMenu, setDataMask, filterState, emitCrossFilters, refs, coltypeMapping, }: GraphChartTransformedProps) { const getCrossFilterDataMask = (node: DataRow | undefined) => { if (!node?.name || !node?.col) { return undefined; } const { name, col } = node; const selected = Object.values( filterState?.selectedValues || {}, ) as string[]; let values: string[]; if (selected.includes(name)) { values = selected.filter(v => v !== name); } else { values = [name]; } return { dataMask: { extraFormData: { filters: values.length ? [ { col, op: 'IN' as const, val: values, }, ] : [], }, filterState: { value: values.length ? values : null, selectedValues: values.length ? values : null, }, }, isCurrentValueSelected: selected.includes(name), }; }; const eventHandlers: EventHandlers = { click: (e: Event) => { if (!emitCrossFilters || !setDataMask) { return; } e.event.stop(); const data = (echartOptions as any).series[0].data as Data; const node = data.find(item => item.id === e.data.id); const dataMask = getCrossFilterDataMask(node)?.dataMask; if (dataMask) { setDataMask(dataMask); } }, contextmenu: (e: Event) => { const handleNodeClick = (data: Data) => { const node = data.find(item => item.id === e.data.id); if (node?.name) { return [ { col: node.col, op: '==' as const, val: node.name, formattedVal: node.name, }, ]; } return undefined; }; const handleEdgeClick = (data: Data) => { const sourceValue = data.find(item => item.id === e.data.source)?.name; const targetValue = data.find(item => item.id === e.data.target)?.name; if (sourceValue && targetValue) { return [ { col: formData.source, op: '==' as const, val: sourceValue, formattedVal: sourceValue, }, { col: formData.target, op: '==' as const, val: targetValue, formattedVal: targetValue, }, ]; } return undefined; }; if (onContextMenu) { e.event.stop(); const pointerEvent = e.event.event; const data = (echartOptions as any).series[0].data as Data; const drillToDetailFilters = e.dataType === 'node' ? handleNodeClick(data) : handleEdgeClick(data); const node = data.find(item => item.id === e.data.id); onContextMenu(pointerEvent.clientX, pointerEvent.clientY, { drillToDetail: drillToDetailFilters, crossFilter: getCrossFilterDataMask(node), drillBy: node && { filters: [ { col: node.col, op: '==', val: node.name, formattedVal: formatSeriesName(node.name, { timeFormatter: getTimeFormatter(formData.dateFormat), numberFormatter: getNumberFormatter(formData.numberFormat), coltype: coltypeMapping?.[getColumnLabel(node.col)], }), }, ], groupbyFieldName: node.col === formData.source ? 'source' : 'target', }, }); } }, }; return ( ); }