Skip to content

Commit

Permalink
feat(plugin-chart-echarts): supports sunburst chart v2 [WIP] (#21625)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael S. Molina <michael.s.molina@gmail.com>
  • Loading branch information
stephenLYZ and michael-s-molina committed Jan 16, 2023
1 parent d2a355b commit b53941f
Show file tree
Hide file tree
Showing 14 changed files with 1,266 additions and 100 deletions.
@@ -0,0 +1,126 @@
/**
* 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 React, { useCallback } from 'react';
import { BinaryQueryObjectFilterClause } from '@superset-ui/core';
import { SunburstTransformedProps } from './types';
import Echart from '../components/Echart';
import { EventHandlers, TreePathInfo } from '../types';

export const extractTreePathInfo = (treePathInfo: TreePathInfo[] | undefined) =>
(treePathInfo ?? [])
.map(pathInfo => pathInfo?.name || '')
.filter(path => path !== '');

export default function EchartsSunburst(props: SunburstTransformedProps) {
const {
height,
width,
echartOptions,
setDataMask,
labelMap,
selectedValues,
formData,
onContextMenu,
refs,
} = props;

const { emitFilter, columns } = formData;

const handleChange = useCallback(
(values: string[]) => {
if (!emitFilter) {
return;
}

const labels = values.map(value => labelMap[value]);

setDataMask({
extraFormData: {
filters:
values.length === 0 || !columns
? []
: columns.map((col, idx) => {
const val = labels.map(v => v[idx]);
if (val === null || val === undefined)
return {
col,
op: 'IS NULL',
};
return {
col,
op: 'IN',
val: val as (string | number | boolean)[],
};
}),
},
filterState: {
value: labels.length ? labels : null,
selectedValues: values.length ? values : null,
},
});
},
[emitFilter, setDataMask, columns, labelMap],
);

const eventHandlers: EventHandlers = {
click: props => {
const { treePathInfo } = props;
const treePath = extractTreePathInfo(treePathInfo);
const name = treePath.join(',');
const values = Object.values(selectedValues);
if (values.includes(name)) {
handleChange(values.filter(v => v !== name));
} else {
handleChange([name]);
}
},
contextmenu: eventParams => {
if (onContextMenu) {
eventParams.event.stop();
const treePath = extractTreePathInfo(eventParams.treePathInfo);
if (treePath.length > 0) {
const pointerEvent = eventParams.event.event;
const filters: BinaryQueryObjectFilterClause[] = [];
if (columns) {
treePath.forEach((path, i) =>
filters.push({
col: columns[i],
op: '==',
val: path,
formattedVal: path,
}),
);
}
onContextMenu(pointerEvent.clientX, pointerEvent.clientY, filters);
}
}
},
};

return (
<Echart
refs={refs}
height={height}
width={width}
echartOptions={echartOptions}
eventHandlers={eventHandlers}
selectedValues={selectedValues}
/>
);
}
@@ -0,0 +1,29 @@
/**
* 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 { buildQueryContext, QueryFormData } from '@superset-ui/core';

export default function buildQuery(formData: QueryFormData) {
const { metric, sort_by_metric } = formData;
return buildQueryContext(formData, baseQueryObject => [
{
...baseQueryObject,
...(sort_by_metric && { orderby: [[metric, false]] }),
},
]);
}
@@ -0,0 +1,207 @@
/**
* 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 React from 'react';
import { t } from '@superset-ui/core';
import {
ControlPanelConfig,
ControlPanelsContainerProps,
D3_FORMAT_DOCS,
D3_FORMAT_OPTIONS,
D3_TIME_FORMAT_OPTIONS,
emitFilterControl,
getStandardizedControls,
sections,
} from '@superset-ui/chart-controls';
import { DEFAULT_FORM_DATA } from './types';

const { labelType, numberFormat, showLabels } = DEFAULT_FORM_DATA;

const config: ControlPanelConfig = {
controlPanelSections: [
sections.legacyRegularTime,
{
label: t('Query'),
expanded: true,
controlSetRows: [
['columns'],
['metric'],
['secondary_metric'],
['adhoc_filters'],
emitFilterControl,
['row_limit'],
[
{
name: 'sort_by_metric',
config: {
type: 'CheckboxControl',
label: t('Sort by metric'),
description: t(
'Whether to sort results by the selected metric in descending order.',
),
},
},
],
],
},
{
label: t('Chart Options'),
expanded: true,
controlSetRows: [
['color_scheme'],
['linear_color_scheme'],
[<div className="section-header">{t('Labels')}</div>],
[
{
name: 'show_labels',
config: {
type: 'CheckboxControl',
label: t('Show Labels'),
renderTrigger: true,
default: showLabels,
description: t('Whether to display the labels.'),
},
},
],
[
{
name: 'show_labels_threshold',
config: {
type: 'TextControl',
label: t('Percentage threshold'),
renderTrigger: true,
isFloat: true,
default: 5,
description: t(
'Minimum threshold in percentage points for showing labels.',
),
},
},
],
[
{
name: 'show_total',
config: {
type: 'CheckboxControl',
label: t('Show Total'),
default: false,
renderTrigger: true,
description: t('Whether to display the aggregate count'),
},
},
],
[
{
name: 'label_type',
config: {
type: 'SelectControl',
label: t('Label Type'),
default: labelType,
renderTrigger: true,
choices: [
['key', t('Category Name')],
['value', t('Value')],
['key_value', t('Category and Value')],
],
description: t('What should be shown on the label?'),
},
},
],
[
{
name: 'number_format',
config: {
type: 'SelectControl',
freeForm: true,
label: t('Number format'),
renderTrigger: true,
default: numberFormat,
choices: D3_FORMAT_OPTIONS,
description: `${t(
'D3 format syntax: https://github.com/d3/d3-format',
)} ${t('Only applies when "Label Type" is set to show values.')}`,
},
},
],
[
{
name: 'date_format',
config: {
type: 'SelectControl',
freeForm: true,
label: t('Date format'),
renderTrigger: true,
choices: D3_TIME_FORMAT_OPTIONS,
default: 'smart_date',
description: D3_FORMAT_DOCS,
},
},
],
],
},
],
controlOverrides: {
metric: {
label: t('Primary Metric'),
description: t(
'The primary metric is used to define the arc segment sizes',
),
},
secondary_metric: {
label: t('Secondary Metric'),
default: null,
description: t(
'[optional] this secondary metric is used to ' +
'define the color as a ratio against the primary metric. ' +
'When omitted, the color is categorical and based on labels',
),
},
color_scheme: {
description: t(
'When only a primary metric is provided, a categorical color scale is used.',
),
visibility: ({ controls }: ControlPanelsContainerProps) =>
Boolean(
!controls?.secondary_metric?.value ||
controls?.secondary_metric?.value === controls?.metric.value,
),
},
linear_color_scheme: {
description: t(
'When a secondary metric is provided, a linear color scale is used.',
),
visibility: ({ controls }: ControlPanelsContainerProps) =>
Boolean(
controls?.secondary_metric?.value &&
controls?.secondary_metric?.value !== controls?.metric.value,
),
},
groupby: {
label: t('Hierarchy'),
description: t('This defines the level of the hierarchy'),
},
},
formDataOverrides: formData => ({
...formData,
groupby: getStandardizedControls().popAllColumns(),
metric: getStandardizedControls().shiftMetric(),
secondary_metric: getStandardizedControls().shiftMetric(),
}),
};

export default config;
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit b53941f

Please sign in to comment.