diff --git a/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/constants.ts b/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/constants.ts
new file mode 100644
index 0000000000..7e3c8404c6
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/constants.ts
@@ -0,0 +1,2 @@
+export const BOX_PLOT_PLUGIN_TYPE = 'v2-box-plot';
+export const BOX_PLOT_PLUGIN_LEGACY_TYPE = 'v2-box-plot/legacy';
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/data.js b/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/data.js
new file mode 100644
index 0000000000..fc1ee8f5d7
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/data.js
@@ -0,0 +1,80 @@
+/* eslint-disable sort-keys, no-magic-numbers */
+export default [
+ {
+ label: 'East Asia & Pacific',
+ values: {
+ Q1: 1384725172.5,
+ Q2: 1717904169.0,
+ Q3: 2032724922.5,
+ whisker_high: 2240687901.0,
+ whisker_low: 1031863394.0,
+ outliers: [],
+ },
+ },
+ {
+ label: 'Europe & Central Asia',
+ values: {
+ Q1: 751386460.5,
+ Q2: 820716895.0,
+ Q3: 862814192.5,
+ whisker_high: 903095786.0,
+ whisker_low: 660881033.0,
+ outliers: [],
+ },
+ },
+ {
+ label: 'Latin America & Caribbean',
+ values: {
+ Q1: 313690832.5,
+ Q2: 421490233.0,
+ Q3: 529668114.5,
+ whisker_high: 626270167.0,
+ whisker_low: 220564224.0,
+ outliers: [],
+ },
+ },
+ {
+ label: 'Middle East & North Africa',
+ values: {
+ Q1: 152382756.5,
+ Q2: 232066828.0,
+ Q3: 318191071.5,
+ whisker_high: 417451428.0,
+ whisker_low: 105512645.0,
+ outliers: [],
+ },
+ },
+ {
+ label: 'North America',
+ values: {
+ Q1: 235506847.5,
+ Q2: 268896849.0,
+ Q3: 314553651.5,
+ whisker_high: 354462656.0,
+ whisker_low: 198624409.0,
+ outliers: [],
+ },
+ },
+ {
+ label: 'South Asia',
+ values: {
+ Q1: 772373036.5,
+ Q2: 1059570231.0,
+ Q3: 1398841234.0,
+ whisker_high: 1720976995.0,
+ whisker_low: 572036107.0,
+ outliers: [],
+ },
+ },
+ {
+ label: 'Sub-Saharan Africa',
+ values: {
+ Q1: 320037758.0,
+ Q2: 467337821.0,
+ Q3: 676768689.0,
+ whisker_high: 974315323.0,
+ whisker_low: 228268752.0,
+ outliers: [],
+ },
+ },
+];
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/index.js b/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/index.js
new file mode 100644
index 0000000000..5ce989c0fa
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/index.js
@@ -0,0 +1,12 @@
+import { BoxPlotChartPlugin as LegacyBoxPlotChartPlugin } from '../../../../../superset-ui-preset-chart-xy/src/legacy';
+import { BoxPlotChartPlugin } from '../../../../../superset-ui-preset-chart-xy/src';
+import Stories from './stories/Basic';
+import LegacyStories from './stories/Legacy';
+import { BOX_PLOT_PLUGIN_LEGACY_TYPE, BOX_PLOT_PLUGIN_TYPE } from './constants';
+
+new LegacyBoxPlotChartPlugin().configure({ key: BOX_PLOT_PLUGIN_LEGACY_TYPE }).register();
+new BoxPlotChartPlugin().configure({ key: BOX_PLOT_PLUGIN_TYPE }).register();
+
+export default {
+ examples: [...Stories, ...LegacyStories],
+};
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/stories/Basic.tsx b/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/stories/Basic.tsx
new file mode 100644
index 0000000000..55776a9374
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/stories/Basic.tsx
@@ -0,0 +1,109 @@
+/* eslint-disable no-magic-numbers, sort-keys */
+import React from 'react';
+import { SuperChart, ChartProps } from '@superset-ui/chart';
+import data from '../data';
+
+export default [
+ {
+ renderStory: () => (
+
+ ),
+ storyName: 'Basic',
+ storyPath: 'preset-chart-xy|BoxPlotChartPlugin',
+ },
+ {
+ renderStory: () => (
+
+ ),
+ storyName: 'Horizontal',
+ storyPath: 'preset-chart-xy|BoxPlotChartPlugin',
+ },
+];
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/stories/Legacy.tsx b/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/stories/Legacy.tsx
new file mode 100644
index 0000000000..59fbb55c33
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-plugins-demo/storybook/stories/preset-chart-xy/BoxPlot/stories/Legacy.tsx
@@ -0,0 +1,31 @@
+/* eslint-disable no-magic-numbers, sort-keys */
+import React from 'react';
+import { SuperChart, ChartProps } from '@superset-ui/chart';
+import data from '../data';
+
+export default [
+ {
+ renderStory: () => (
+
+ ),
+ storyName: 'Use Legacy API shim',
+ storyPath: 'preset-chart-xy|BoxPlotChartPlugin',
+ },
+];
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/BoxPlot.tsx b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/BoxPlot.tsx
new file mode 100644
index 0000000000..a46036e4a2
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/BoxPlot.tsx
@@ -0,0 +1,148 @@
+/**
+ * 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.
+ */
+/* eslint-disable sort-keys, no-magic-numbers, complexity */
+import React from 'react';
+import { BoxPlotSeries, XYChart } from '@data-ui/xy-chart';
+import { chartTheme, ChartTheme } from '@data-ui/theme';
+import { Margin, Dimension } from '@superset-ui/dimension';
+import { createSelector } from 'reselect';
+import createTooltip from './createTooltip';
+import XYChartLayout from '../utils/XYChartLayout';
+import WithLegend from '../components/WithLegend';
+import ChartLegend from '../components/ChartLegend';
+import Encoder, { ChannelTypes, Encoding, Outputs } from './Encoder';
+import { Dataset, PlainObject } from '../encodeable/types/Data';
+
+chartTheme.gridStyles.stroke = '#f1f3f5';
+
+const DEFAULT_MARGIN = { top: 20, right: 20, left: 20, bottom: 20 };
+
+const defaultProps = {
+ className: '',
+ margin: DEFAULT_MARGIN,
+ theme: chartTheme,
+} as const;
+
+type Props = {
+ className?: string;
+ width: string | number;
+ height: string | number;
+ margin?: Margin;
+ encoding: Encoding;
+ data: Dataset;
+ theme?: ChartTheme;
+} & Readonly;
+
+export default class BoxPlot extends React.PureComponent {
+ static defaultProps = defaultProps;
+
+ constructor(props: Props) {
+ super(props);
+
+ const createEncoder = createSelector(
+ (enc: Encoding) => enc,
+ (enc: Encoding) => new Encoder({ encoding: enc }),
+ );
+
+ this.createEncoder = () => {
+ this.encoder = createEncoder(this.props.encoding);
+ };
+
+ this.encoder = createEncoder(this.props.encoding);
+ this.renderChart = this.renderChart.bind(this);
+ }
+
+ encoder: Encoder;
+ private createEncoder: () => void;
+
+ renderChart(dim: Dimension) {
+ const { width, height } = dim;
+ const { data, encoding, margin, theme } = this.props;
+ const { channels } = this.encoder;
+
+ const isHorizontal = encoding.y.type === 'nominal';
+
+ const children = [
+ ({ ...row, y: channels.y.get(row) }))
+ : data.map(row => ({ ...row, x: channels.x.get(row) }))
+ }
+ fill={(datum: PlainObject) => channels.color.encode(datum, '#55acee')}
+ fillOpacity={0.4}
+ stroke={(datum: PlainObject) => channels.color.encode(datum)}
+ strokeWidth={1}
+ widthRatio={0.6}
+ horizontal={encoding.y.type === 'nominal'}
+ />,
+ ];
+
+ const layout = new XYChartLayout({
+ width,
+ height,
+ margin: { ...DEFAULT_MARGIN, ...margin },
+ theme,
+ xEncoder: channels.x,
+ yEncoder: channels.y,
+ children,
+ });
+
+ return layout.renderChartWithFrame((chartDim: Dimension) => (
+
+ {children}
+ {layout.renderXAxis()}
+ {layout.renderYAxis()}
+
+ ));
+ }
+
+ render() {
+ const { className, data, width, height } = this.props;
+
+ this.createEncoder();
+ const renderLegend = this.encoder.hasLegend()
+ ? // eslint-disable-next-line react/jsx-props-no-multi-spaces
+ () => data={data} encoder={this.encoder} />
+ : undefined;
+
+ return (
+
+ );
+ }
+}
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/Encoder.ts b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/Encoder.ts
new file mode 100644
index 0000000000..74c5342220
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/Encoder.ts
@@ -0,0 +1,42 @@
+import AbstractEncoder from '../encodeable/AbstractEncoder';
+import { PartialSpec } from '../encodeable/types/Specification';
+import { EncodingFromChannelsAndOutputs } from '../encodeable/types/Channel';
+
+/**
+ * Define channel types
+ */
+const channelTypes = {
+ color: 'Color',
+ x: 'XBand',
+ y: 'YBand',
+} as const;
+
+export type ChannelTypes = typeof channelTypes;
+
+/**
+ * Define output type for each channel
+ */
+export interface Outputs {
+ x: number | null;
+ y: number | null;
+ color: string;
+}
+
+/**
+ * Derive encoding config
+ */
+export type Encoding = EncodingFromChannelsAndOutputs;
+
+export default class Encoder extends AbstractEncoder {
+ static readonly DEFAULT_ENCODINGS: Encoding = {
+ color: { value: '#222' },
+ x: { field: 'x', type: 'nominal' },
+ y: { field: 'y', type: 'quantitative' },
+ };
+
+ static readonly CHANNEL_OPTIONS = {};
+
+ constructor(spec: PartialSpec) {
+ super(channelTypes, spec, Encoder.DEFAULT_ENCODINGS, Encoder.CHANNEL_OPTIONS);
+ }
+}
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/createMetadata.ts b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/createMetadata.ts
new file mode 100644
index 0000000000..52fd28fc88
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/createMetadata.ts
@@ -0,0 +1,12 @@
+import { t } from '@superset-ui/translation';
+import { ChartMetadata } from '@superset-ui/chart';
+import thumbnail from './images/thumbnail.png';
+
+export default function createMetadata(useLegacyApi = false) {
+ return new ChartMetadata({
+ description: '',
+ name: t('Box Plot'),
+ thumbnail,
+ useLegacyApi,
+ });
+}
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/createTooltip.tsx b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/createTooltip.tsx
new file mode 100644
index 0000000000..b3efd5070f
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/createTooltip.tsx
@@ -0,0 +1,44 @@
+import { isDefined } from '@superset-ui/core';
+import React from 'react';
+import TooltipFrame from '../components/tooltip/TooltipFrame';
+import TooltipTable from '../components/tooltip/TooltipTable';
+import Encoder from './Encoder';
+import { BoxPlotDataRow } from './types';
+
+export default function createBoxPlotTooltip(encoder: Encoder) {
+ const { channels } = encoder;
+
+ return function BoxPlotTooltip({ datum, color }: { datum: BoxPlotDataRow; color: string }) {
+ const { label, min, max, median, firstQuartile, thirdQuartile, outliers } = datum;
+
+ const data = [];
+ if (isDefined(min)) {
+ data.push({ key: 'Min', valueColumn: channels.y.formatValue(min) });
+ }
+ if (isDefined(max)) {
+ data.push({ key: 'Max', valueColumn: channels.y.formatValue(max) });
+ }
+ if (isDefined(median)) {
+ data.push({ key: 'Median', valueColumn: channels.y.formatValue(median) });
+ }
+ if (isDefined(firstQuartile)) {
+ data.push({ key: '1st Quartile', valueColumn: channels.y.formatValue(firstQuartile) });
+ }
+ if (isDefined(thirdQuartile)) {
+ data.push({ key: '3rd Quartile', valueColumn: channels.y.formatValue(thirdQuartile) });
+ }
+ if (isDefined(outliers) && outliers.length > 0) {
+ data.push({ key: '# Outliers', valueColumn: outliers.length });
+ }
+
+ return (
+
+
+ {label}
+
+ {data.length > 0 &&
}
+
+
+ );
+ };
+}
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/images/thumbnail.png b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/images/thumbnail.png
new file mode 100644
index 0000000000..c6f8fdcfd6
Binary files /dev/null and b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/images/thumbnail.png differ
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/images/thumbnailLarge.png b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/images/thumbnailLarge.png
new file mode 100644
index 0000000000..f7bbe62407
Binary files /dev/null and b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/images/thumbnailLarge.png differ
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/index.ts b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/index.ts
new file mode 100644
index 0000000000..ee02dbbbe4
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/index.ts
@@ -0,0 +1,31 @@
+/**
+ * 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 { ChartPlugin } from '@superset-ui/chart';
+import createMetadata from './createMetadata';
+import transformProps from './transformProps';
+
+export default class BoxPlotChartPlugin extends ChartPlugin {
+ constructor() {
+ super({
+ loadChart: () => import('./BoxPlot'),
+ metadata: createMetadata(),
+ transformProps,
+ });
+ }
+}
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/legacy/index.ts b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/legacy/index.ts
new file mode 100644
index 0000000000..6667d5ccff
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/legacy/index.ts
@@ -0,0 +1,31 @@
+/**
+ * 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 { ChartPlugin } from '@superset-ui/chart';
+import createMetadata from '../createMetadata';
+import transformProps from './transformProps';
+
+export default class BoxPlotChartPlugin extends ChartPlugin {
+ constructor() {
+ super({
+ loadChart: () => import('../BoxPlot'),
+ metadata: createMetadata(true),
+ transformProps,
+ });
+ }
+}
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/legacy/transformProps.ts b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/legacy/transformProps.ts
new file mode 100644
index 0000000000..183230c946
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/legacy/transformProps.ts
@@ -0,0 +1,94 @@
+/**
+ * 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.
+ */
+/* eslint-disable sort-keys, no-magic-numbers */
+import { ChartProps } from '@superset-ui/chart';
+import { RawBoxPlotDataRow, BoxPlotDataRow } from '../types';
+
+export default function transformProps(chartProps: ChartProps) {
+ const { width, height, datasource = {}, formData, payload } = chartProps;
+ const { verboseMap = {} } = datasource;
+ const { colorScheme, groupby, metrics } = formData;
+
+ const data = (payload.data as RawBoxPlotDataRow[]).map(({ label, values }) => ({
+ label,
+ min: values.whisker_low,
+ max: values.whisker_high,
+ firstQuartile: values.Q1,
+ median: values.Q2,
+ thirdQuartile: values.Q3,
+ outliers: values.outliers,
+ }));
+
+ const xAxisLabel = groupby.join('/');
+ const yAxisLabel = metrics.length > 0 ? verboseMap[metrics[0]] || metrics[0] : '';
+
+ const boxPlotValues = data.reduce((r: number[], e: BoxPlotDataRow) => {
+ r.push(e.min, e.max, ...e.outliers);
+
+ return r;
+ }, []);
+
+ const minBoxPlotValue = Math.min(...boxPlotValues);
+ const maxBoxPlotValue = Math.max(...boxPlotValues);
+ const valueDomain = [
+ minBoxPlotValue - 0.1 * Math.abs(minBoxPlotValue),
+ maxBoxPlotValue + 0.1 * Math.abs(maxBoxPlotValue),
+ ];
+
+ return {
+ data,
+ width,
+ height,
+ encoding: {
+ x: {
+ field: 'label',
+ type: 'nominal',
+ scale: {
+ type: 'band',
+ paddingInner: 0.15,
+ paddingOuter: 0.3,
+ },
+ axis: {
+ title: xAxisLabel,
+ },
+ },
+ y: {
+ field: 'value',
+ type: 'quantitative',
+ scale: {
+ type: 'linear',
+ domain: valueDomain,
+ },
+ axis: {
+ title: yAxisLabel,
+ numTicks: 5,
+ format: 'SMART_NUMBER',
+ },
+ },
+ color: {
+ field: 'label',
+ type: 'nominal',
+ scale: {
+ scheme: colorScheme,
+ },
+ legend: false,
+ },
+ },
+ };
+}
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/transformProps.ts b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/transformProps.ts
new file mode 100644
index 0000000000..959ca29b61
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/transformProps.ts
@@ -0,0 +1,68 @@
+import { ChartProps } from '@superset-ui/chart';
+import { BoxPlotDataRow, RawBoxPlotDataRow } from './types';
+
+/**
+ * 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.
+ */
+
+/* eslint-disable sort-keys, no-magic-numbers */
+
+export default function transformProps(chartProps: ChartProps) {
+ const { width, height, formData, payload } = chartProps;
+ const { encoding, margin, theme } = formData;
+
+ const data = (payload.data as RawBoxPlotDataRow[]).map(({ label, values }) => ({
+ label,
+ min: values.whisker_low,
+ max: values.whisker_high,
+ firstQuartile: values.Q1,
+ median: values.Q2,
+ thirdQuartile: values.Q3,
+ outliers: values.outliers,
+ }));
+
+ const isHorizontal = encoding.y.type === 'nominal';
+
+ const boxPlotValues = data.reduce((r: number[], e: BoxPlotDataRow) => {
+ r.push(e.min, e.max, ...e.outliers);
+
+ return r;
+ }, []);
+
+ const minBoxPlotValue = Math.min(...boxPlotValues);
+ const maxBoxPlotValue = Math.max(...boxPlotValues);
+ const valueDomain = [
+ minBoxPlotValue - 0.1 * Math.abs(minBoxPlotValue),
+ maxBoxPlotValue + 0.1 * Math.abs(maxBoxPlotValue),
+ ];
+
+ if (isHorizontal) {
+ encoding.x.scale.domain = valueDomain;
+ } else {
+ encoding.y.scale.domain = valueDomain;
+ }
+
+ return {
+ data,
+ width,
+ height,
+ margin,
+ theme,
+ encoding,
+ };
+}
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/types.ts b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/types.ts
new file mode 100644
index 0000000000..8b1839ee3c
--- /dev/null
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/BoxPlot/types.ts
@@ -0,0 +1,23 @@
+export interface RawBoxPlotDataRow {
+ label: string;
+ values: {
+ Q1: number;
+ Q2: number;
+ Q3: number;
+ outliers: number[];
+ // eslint-disable-next-line camelcase
+ whisker_high: number;
+ // eslint-disable-next-line camelcase
+ whisker_low: number;
+ };
+}
+
+export interface BoxPlotDataRow {
+ label: string;
+ min: number;
+ max: number;
+ firstQuartile: number;
+ median: number;
+ thirdQuartile: number;
+ outliers: number[];
+}
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/createTooltip.tsx b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/createTooltip.tsx
index d1de33e0d9..a373a021c9 100644
--- a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/createTooltip.tsx
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/createTooltip.tsx
@@ -9,7 +9,7 @@ import Encoder from './Encoder';
const MARK_STYLE = { marginRight: 4 };
export default function createTooltip(encoder: Encoder, allSeries: Series[]) {
- function LineTooltip({
+ return function LineTooltip({
datum,
series = {},
}: {
@@ -57,7 +57,5 @@ export default function createTooltip(encoder: Encoder, allSeries: Series[]) {
>
);
- }
-
- return LineTooltip;
+ };
}
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipTable.tsx b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipTable.tsx
index b106a876b5..e0fc6c4d63 100644
--- a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipTable.tsx
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipTable.tsx
@@ -4,7 +4,7 @@ type Props = {
className?: string;
data: {
key: string | number;
- keyColumn: ReactNode;
+ keyColumn?: ReactNode;
keyStyle?: CSSProperties;
valueColumn: ReactNode;
valueStyle?: CSSProperties;
@@ -27,7 +27,7 @@ export default class TooltipTable extends PureComponent {
{data.map(({ key, keyColumn, keyStyle, valueColumn, valueStyle }, i) => (
- {keyColumn} |
+ {keyColumn || key} |
{valueColumn}
|
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/index.ts b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/index.ts
index ce786e6928..498637196f 100644
--- a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/index.ts
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/index.ts
@@ -1,2 +1,2 @@
-/* eslint-disable import/prefer-default-export */
+export { default as BoxPlotChartPlugin } from './BoxPlot';
export { default as LineChartPlugin } from './Line';
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/legacy.ts b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/legacy.ts
index 6a9010bda4..c0403f479c 100644
--- a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/legacy.ts
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/legacy.ts
@@ -1,2 +1,2 @@
-/* eslint-disable import/prefer-default-export */
+export { default as BoxPlotChartPlugin } from './BoxPlot/legacy';
export { default as LineChartPlugin } from './Line/legacy';
diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/types/@data-ui/xy-chart/index.d.ts b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/types/@data-ui/xy-chart/index.d.ts
index 3e48cb665a..504be9e516 100644
--- a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/types/@data-ui/xy-chart/index.d.ts
+++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/types/@data-ui/xy-chart/index.d.ts
@@ -20,15 +20,16 @@ declare module '@data-ui/xy-chart' {
onMouseMove?: (...args: any[]) => void;
onMouseLeave?: (...args: any[]) => void;
renderTooltip: any;
- showYGrid: boolean;
- snapTooltipToDataX: boolean;
- theme: any;
- tooltipData: any;
+ showYGrid?: boolean;
+ snapTooltipToDataX?: boolean;
+ theme?: any;
+ tooltipData?: any;
xScale: any;
yScale: any;
}
export class AreaSeries extends React.PureComponent {}
+ export class BoxPlotSeries extends React.PureComponent {}
export class CrossHair extends React.PureComponent {}
export class LinearGradient extends React.PureComponent {}
export class LineSeries extends React.PureComponent {}