Skip to content
This repository has been archived by the owner on Dec 10, 2021. It is now read-only.

feat(plugin-chart-echarts): Emit cross filters for pie and boxplot #1010

Merged
merged 12 commits into from
Mar 19, 2021
77 changes: 73 additions & 4 deletions plugins/plugin-chart-echarts/src/BoxPlot/EchartsBoxPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,79 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { EchartsProps } from '../types';
import React, { useCallback } from 'react';
import Echart from '../components/Echart';
import { EventHandlers } from '../types';
import { BoxPlotChartTransformedProps } from './types';

export default function EchartsBoxPlot({ height, width, echartOptions }: EchartsProps) {
return <Echart height={height} width={width} echartOptions={echartOptions} />;
export default function EchartsBoxPlot({
height,
width,
echartOptions,
setDataMask,
labelMap,
groupby,
selectedValues,
}: BoxPlotChartTransformedProps) {
const handleChange = useCallback(
(values: string[]) => {
const groupbyValues = values.map(value => labelMap[value]);

setDataMask({
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to check if emitFilter is enabled before calling the hook.

crossFilters: {
extraFormData: {
append_form_data: {
filters:
values.length === 0
? []
: groupby.map((col, idx) => {
const val = groupbyValues.map(v => v[idx]);
if (val === null || val === undefined)
return {
col,
op: 'IS NULL',
};
return {
col,
op: 'IN',
val: val as (string | number | boolean)[],
};
}),
},
},
currentState: {
value: groupbyValues ?? null,
},
},
ownFilters: {
currentState: {
selectedValues: values,
},
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
currentState: {
value: groupbyValues ?? null,
},
},
ownFilters: {
currentState: {
selectedValues: values,
},
},
currentState: {
value: groupbyValues.length ? groupbyValues : null,
},
},
ownFilters: {
currentState: {
selectedValues: values.length ? values : null,
},
},

});
},
[groupby, labelMap, setDataMask, selectedValues],
);

const eventHandlers: EventHandlers = {
click: props => {
const { name } = props;
const values = Object.values(selectedValues);
if (values.includes(name)) {
handleChange(values.filter(v => v !== name));
} else {
handleChange([...values, name]);
}
},
};

return (
<Echart
height={height}
width={width}
echartOptions={echartOptions}
eventHandlers={eventHandlers}
selectedValues={selectedValues}
/>
);
}
16 changes: 15 additions & 1 deletion plugins/plugin-chart-echarts/src/BoxPlot/controlPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@superset-ui/core';
import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
import {
D3_FORMAT_DOCS,
D3_FORMAT_OPTIONS,
Expand Down Expand Up @@ -62,6 +62,20 @@ export default {
expanded: true,
controlSetRows: [
['color_scheme'],
isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)
? [
{
name: 'emit_filter',
config: {
type: 'CheckboxControl',
label: t('Enable emitting filters'),
default: false,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we pick the default value from DEFAULT_FORM_DATA?

renderTrigger: true,
description: t('Enable emmiting filters.'),
},
},
]
: [],
[
{
name: 'x_ticks_layout',
Expand Down
3 changes: 2 additions & 1 deletion plugins/plugin-chart-echarts/src/BoxPlot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
import { t, ChartMetadata, ChartPlugin, Behavior } from '@superset-ui/core';
import buildQuery from './buildQuery';
import controlPanel from './controlPanel';
import transformProps from './transformProps';
Expand All @@ -43,6 +43,7 @@ export default class EchartsBoxPlotChartPlugin extends ChartPlugin<
controlPanel,
loadChart: () => import('./EchartsBoxPlot'),
metadata: new ChartMetadata({
behaviors: [Behavior.CROSS_FILTER],
credits: ['https://echarts.apache.org'],
description: 'Box Plot (Apache ECharts)',
name: t('Box Plot'),
Expand Down
47 changes: 43 additions & 4 deletions plugins/plugin-chart-echarts/src/BoxPlot/transformProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,27 @@
*/
import {
CategoricalColorNamespace,
DataRecordValue,
getMetricLabel,
getNumberFormatter,
getTimeFormatter,
} from '@superset-ui/core';
import { EChartsOption, BoxplotSeriesOption } from 'echarts';
import { CallbackDataParams } from 'echarts/types/src/util/types';
import { BoxPlotQueryFormData, EchartsBoxPlotChartProps } from './types';
import { EchartsProps } from '../types';
import {
BoxPlotChartTransformedProps,
BoxPlotQueryFormData,
EchartsBoxPlotChartProps,
} from './types';
import { extractGroupbyLabel, getColtypesMapping } from '../utils/series';
import { defaultGrid, defaultTooltip, defaultYAxis } from '../defaults';

export default function transformProps(chartProps: EchartsBoxPlotChartProps): EchartsProps {
const { width, height, formData, queriesData } = chartProps;
export default function transformProps(
chartProps: EchartsBoxPlotChartProps,
): BoxPlotChartTransformedProps {
const { width, height, formData, hooks, ownCurrentState, queriesData } = chartProps;
const { data = [] } = queriesData[0];
const { setDataMask = () => {} } = hooks;
const coltypeMapping = getColtypesMapping(queriesData[0]);
const {
colorScheme,
Expand All @@ -40,6 +47,7 @@ export default function transformProps(chartProps: EchartsBoxPlotChartProps): Ec
numberFormat,
dateFormat,
xTicksLayout,
emitFilter,
} = formData as BoxPlotQueryFormData;
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
const numberFormatter = getNumberFormatter(numberFormat);
Expand Down Expand Up @@ -106,6 +114,31 @@ export default function transformProps(chartProps: EchartsBoxPlotChartProps): Ec
}),
)
.flat(2);

const labelMap = data.reduce((acc: Record<string, DataRecordValue[]>, datum) => {
const label = extractGroupbyLabel({
datum,
groupby,
coltypeMapping,
timeFormatter: getTimeFormatter(dateFormat),
});
return {
...acc,
[label]: groupby.map(col => datum[col]),
};
}, {});

const selectedValues = (ownCurrentState.selectedValues || []).reduce(
(acc: Record<string, number>, selectedValue: string) => {
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 };
Expand Down Expand Up @@ -178,8 +211,14 @@ export default function transformProps(chartProps: EchartsBoxPlotChartProps): Ec
};

return {
formData,
width,
height,
echartOptions,
setDataMask,
emitFilter,
labelMap,
groupby,
selectedValues,
};
}
21 changes: 20 additions & 1 deletion plugins/plugin-chart-echarts/src/BoxPlot/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ChartDataResponseResult, ChartProps, QueryFormData } from '@superset-ui/core';
import {
ChartDataResponseResult,
ChartProps,
DataRecordValue,
QueryFormData,
SetDataMaskHook,
} from '@superset-ui/core';
import { PostProcessingBoxplot } from '@superset-ui/core/lib/query/types/PostProcessing';
import { EChartsOption } from 'echarts';

export type BoxPlotQueryFormData = QueryFormData & {
numberFormat?: string;
Expand All @@ -39,3 +46,15 @@ export interface EchartsBoxPlotChartProps extends ChartProps {
}

export type BoxPlotQueryObjectWhiskerType = PostProcessingBoxplot['options']['whisker_type'];

export interface BoxPlotChartTransformedProps {
formData: BoxPlotQueryFormData;
height: number;
width: number;
echartOptions: EChartsOption;
emitFilter: boolean;
setDataMask: SetDataMaskHook;
labelMap: Record<string, DataRecordValue[]>;
groupby: string[];
selectedValues: Record<number, string>;
}
77 changes: 73 additions & 4 deletions plugins/plugin-chart-echarts/src/Pie/EchartsPie.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,79 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { EchartsProps } from '../types';
import React, { useCallback } from 'react';
import { PieChartTransformedProps } from './types';
import Echart from '../components/Echart';
import { EventHandlers } from '../types';

export default function EchartsPie({ height, width, echartOptions }: EchartsProps) {
return <Echart height={height} width={width} echartOptions={echartOptions} />;
export default function EchartsPie({
height,
width,
echartOptions,
setDataMask,
labelMap,
groupby,
selectedValues,
}: PieChartTransformedProps) {
const handleChange = useCallback(
(values: string[]) => {
const groupbyValues = values.map(value => labelMap[value]);

setDataMask({
Copy link
Contributor

Choose a reason for hiding this comment

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

Here we also need to check if emitFilter is enabled.

crossFilters: {
extraFormData: {
append_form_data: {
filters:
values.length === 0
? []
: groupby.map((col, idx) => {
const val = groupbyValues.map(v => v[idx]);
if (val === null || val === undefined)
return {
col,
op: 'IS NULL',
};
return {
col,
op: 'IN',
val: val as (string | number | boolean)[],
};
}),
},
},
currentState: {
value: groupbyValues ?? null,
},
},
ownFilters: {
currentState: {
selectedValues: values,
},
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
currentState: {
value: groupbyValues ?? null,
},
},
ownFilters: {
currentState: {
selectedValues: values,
},
},
currentState: {
value: groupbyValues.length ? groupbyValues : null,
},
},
ownFilters: {
currentState: {
selectedValues: values.length ? values : null,
},
},

});
},
[groupby, labelMap, setDataMask, selectedValues],
);

const eventHandlers: EventHandlers = {
click: props => {
const { name } = props;
const values = Object.values(selectedValues);
if (values.includes(name)) {
handleChange(values.filter(v => v !== name));
} else {
handleChange([...values, name]);
}
},
};

return (
<Echart
height={height}
width={width}
echartOptions={echartOptions}
eventHandlers={eventHandlers}
selectedValues={selectedValues}
/>
);
}
19 changes: 18 additions & 1 deletion plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
import { t, validateNonEmpty } from '@superset-ui/core';
import { FeatureFlag, isFeatureEnabled, t, validateNonEmpty } from '@superset-ui/core';
import {
ControlPanelConfig,
D3_FORMAT_DOCS,
Expand All @@ -42,6 +42,7 @@ const {
outerRadius,
numberFormat,
showLabels,
emitFilter,
} = DEFAULT_FORM_DATA;

const config: ControlPanelConfig = {
Expand Down Expand Up @@ -85,6 +86,22 @@ const config: ControlPanelConfig = {
},
},
],

isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)
? [
{
name: 'emit_filter',
config: {
type: 'CheckboxControl',
label: t('Enable emitting filters'),
default: emitFilter,
renderTrigger: true,
description: t('Enable emmiting filters.'),
},
},
]
: [],

// eslint-disable-next-line react/jsx-key
[<h1 className="section-header">{t('Legend')}</h1>],
[showLegendControl],
Expand Down
3 changes: 2 additions & 1 deletion plugins/plugin-chart-echarts/src/Pie/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
import { Behavior, ChartMetadata, ChartPlugin, t } from '@superset-ui/core';
import buildQuery from './buildQuery';
import controlPanel from './controlPanel';
import transformProps from './transformProps';
Expand All @@ -43,6 +43,7 @@ export default class EchartsPieChartPlugin extends ChartPlugin<
controlPanel,
loadChart: () => import('./EchartsPie'),
metadata: new ChartMetadata({
behaviors: [Behavior.CROSS_FILTER],
credits: ['https://echarts.apache.org'],
description: 'Pie Chart (Apache ECharts)',
name: t('Pie Chart'),
Expand Down
Loading