Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,146 @@
* specific language governing permissions and limitations
* under the License.
*/
import { useMemo } from 'react';
import {
ContextMenuFilters,
getColumnLabel,
TimeGranularity,
} from '@superset-ui/core';
import Echart from '../components/Echart';
import { WaterfallChartTransformedProps } from './types';
import { EventHandlers } from '../types';
import { useLegendEventHandlers } from '../utils/legendEventHandlers';
import {
getSelectedWaterfallKeys,
getWaterfallCrossFilterDataMask,
getWaterfallDrillFilters,
} from './crossFilter';

const noop = () => {};

type WaterfallBarEvent = {
dataIndex?: number;
event?: { stop: () => void; event: PointerEvent };
};

export default function EchartsWaterfall(
props: WaterfallChartTransformedProps,
) {
const { height, width, echartOptions, refs, onLegendStateChanged, formData } =
props;
const {
categories,
coltypeMapping,
echartOptions,
emitCrossFilters,
filterState,
formData,
height,
onContextMenu,
onLegendStateChanged,
refs,
setDataMask = noop,
width,
} = props;
const { xAxisTimeFormat } = formData;
const timeGrain: TimeGranularity | undefined =
formData.extraFormData?.time_grain_sqla ?? formData.timeGrainSqla;

// The waterfall legend has a fixed set of three entries, so it never
// paginates and has no scroll position to preserve.
const eventHandlers: EventHandlers =
useLegendEventHandlers(onLegendStateChanged);
const legendEventHandlers = useLegendEventHandlers(onLegendStateChanged);

const crossFilterHandlers: EventHandlers = useMemo(() => {
const selectedKeys = getSelectedWaterfallKeys(filterState?.selectedValues);
const categoryAt = (event: WaterfallBarEvent) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

categoryAt maps every series hit to a category by dataIndex, including the transparent ASSIST_MARK spacer series. Those spacers are drawn with a transparent fill but are still hit-testable and carry the pointer cursor, so with values 10 then 3 the second visible bar spans 10–13, and clicking the empty area under it (0–10) sets or clears that category's cross-filter; right-clicking it opens the drill menu. Should the handlers ignore events from the assist series (e.g. check event.seriesName) while still accepting the colored sign-crossing segments?

if (event.dataIndex == null) {
return undefined;
}
const category = categories[event.dataIndex];
if (!category || category.isGrandTotal) {
return undefined;
}
return category;
};

return {
click: (event: WaterfallBarEvent) => {
if (!emitCrossFilters) {
return;
}
const category = categoryAt(event);
if (!category) {
return;
}
setDataMask(
getWaterfallCrossFilterDataMask({
category,
selectedKeys,
coltypeMapping,
timeFormat: xAxisTimeFormat,
timeGrain,
}).dataMask,
);
},
contextmenu: (event: WaterfallBarEvent) => {
if (!onContextMenu) {
return;
}
const category = categoryAt(event);
if (!category) {
return;
}
event.event?.stop();
const pointerEvent = event.event?.event;
if (!pointerEvent) {
return;
}
// IS NULL is unary. The menu types only binary operators, and both
// drill paths forward `op` unchanged.
const drillFilters = getWaterfallDrillFilters({
category,
coltypeMapping,
timeFormat: xAxisTimeFormat,
timeGrain,
}) as NonNullable<ContextMenuFilters['drillToDetail']>;
const xColumnLabel = getColumnLabel(category.xColumn);
Comment thread
EnxDev marked this conversation as resolved.
Resolved
const xAxisFilters = drillFilters.filter(
filter => getColumnLabel(filter.col) === xColumnLabel,
);
const breakdownFilters = drillFilters.filter(
filter => getColumnLabel(filter.col) !== xColumnLabel,
);
onContextMenu(pointerEvent.clientX, pointerEvent.clientY, {
drillToDetail: drillFilters,
crossFilter: getWaterfallCrossFilterDataMask({
category,
selectedKeys,
coltypeMapping,
timeFormat: xAxisTimeFormat,
timeGrain,
}),
drillBy: {
filters: breakdownFilters,
groupbyFieldName: 'groupby',
xAxisFilters,
},
});
},
};
}, [
categories,
coltypeMapping,
emitCrossFilters,
filterState?.selectedValues,
onContextMenu,
setDataMask,
timeGrain,
xAxisTimeFormat,
]);

const eventHandlers: EventHandlers = {
...legendEventHandlers,
...crossFilterHandlers,
};

return (
<Echart
Expand Down
Loading
Loading