feat(plugin-chart-echarts): subject Update echarts to v5.0.1 (#928)

* feat(plugin-chart-echarts): subject Update echarts to v5.0.1

* fix lint

* fix lint

* wip

* wip

* wip

* Fix comments

* Add back missed properties

* Fix lint

* Fix
This commit is contained in:
Victor Malai 2021-02-04 11:34:51 +02:00 committed by Yongjie Zhao
parent 2a4cc71870
commit d3d343d6c4
11 changed files with 163 additions and 142 deletions

View File

@ -28,9 +28,8 @@
"dependencies": {
"@superset-ui/chart-controls": "0.17.5",
"@superset-ui/core": "0.17.5",
"@types/echarts": "^4.9.3",
"@types/mathjs": "^6.0.7",
"echarts": "^5.0.0",
"echarts": "^5.0.1",
"mathjs": "^8.0.1"
},
"peerDependencies": {

View File

@ -23,6 +23,8 @@ import {
getMetricLabel,
getNumberFormatter,
} from '@superset-ui/core';
import { EChartsOption, BoxplotSeriesOption } from 'echarts';
import { CallbackDataParams } from 'echarts/types/src/util/types';
import { BoxPlotQueryFormData } from './types';
import { EchartsProps } from '../types';
import { extractGroupbyLabel } from '../utils/series';
@ -43,7 +45,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
const metricLabels = formdataMetrics.map(getMetricLabel);
const transformedData = data
.map(datum => {
.map((datum: any) => {
const groupbyLabel = extractGroupbyLabel({ datum, groupby });
return metricLabels.map(metric => {
const name = metricLabels.length === 1 ? groupbyLabel : `${groupbyLabel}, ${metric}`;
@ -102,8 +104,43 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
else if (xTicksLayout === 'staggered') axisLabel = { rotate: -45 };
else axisLabel = { show: true };
// @ts-ignore
const echartOptions: echarts.EChartOption<echarts.EChartOption.SeriesBoxplot> = {
const series: BoxplotSeriesOption[] = [
{
name: 'boxplot',
type: 'boxplot',
data: transformedData,
tooltip: {
formatter: (param: CallbackDataParams) => {
// @ts-ignore
const {
value,
name,
}: {
value: [number, number, number, number, number, number, number, number, number[]];
name: string;
} = param;
const headline = name ? `<p><strong>${name}</strong></p>` : '';
const stats = [
`Max: ${numberFormatter(value[5])}`,
`3rd Quartile: ${numberFormatter(value[4])}`,
`Mean: ${numberFormatter(value[6])}`,
`Median: ${numberFormatter(value[3])}`,
`1st Quartile: ${numberFormatter(value[2])}`,
`Min: ${numberFormatter(value[1])}`,
`# Observations: ${numberFormatter(value[7])}`,
];
if (value[8].length > 0) {
stats.push(`# Outliers: ${numberFormatter(value[8].length)}`);
}
return headline + stats.join('<br/>');
},
},
},
// @ts-ignore
...outlierData,
];
const echartOptions: EChartsOption = {
grid: {
...defaultGrid,
top: 30,
@ -128,43 +165,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
type: 'shadow',
},
},
series: [
{
name: 'boxplot',
type: 'boxplot',
avoidLabelOverlap: true,
// @ts-ignore
data: transformedData,
tooltip: {
formatter: param => {
// @ts-ignore
const {
value,
name,
}: {
value: [number, number, number, number, number, number, number, number, number[]];
name: string;
} = param;
const headline = name ? `<p><strong>${name}</strong></p>` : '';
const stats = [
`Max: ${numberFormatter(value[5])}`,
`3rd Quartile: ${numberFormatter(value[4])}`,
`Mean: ${numberFormatter(value[6])}`,
`Median: ${numberFormatter(value[3])}`,
`1st Quartile: ${numberFormatter(value[2])}`,
`Min: ${numberFormatter(value[1])}`,
`# Observations: ${numberFormatter(value[7])}`,
];
if (value[8].length > 0) {
stats.push(`# Outliers: ${numberFormatter(value[8].length)}`);
}
return headline + stats.join('<br/>');
},
},
},
// @ts-ignore
...outlierData,
],
series,
};
return {

View File

@ -17,7 +17,7 @@
* under the License.
*/
import { QueryFormData } from '@superset-ui/core';
import { PostProcessingBoxplot } from '@superset-ui/core/src/query/types/PostProcessing';
import { PostProcessingBoxplot } from '@superset-ui/core/lib/query/types/PostProcessing';
export type BoxPlotQueryFormData = QueryFormData & {
numberFormat?: string;

View File

@ -25,6 +25,8 @@ import {
NumberFormats,
NumberFormatter,
} from '@superset-ui/core';
import { CallbackDataParams } from 'echarts/types/src/util/types';
import { EChartsOption, PieSeriesOption } from 'echarts';
import {
DEFAULT_FORM_DATA as DEFAULT_PIE_FORM_DATA,
EchartsPieFormData,
@ -41,7 +43,7 @@ export function formatPieLabel({
labelType,
numberFormatter,
}: {
params: echarts.EChartOption.Tooltip.Format;
params: CallbackDataParams;
labelType: EchartsPieLabelType;
numberFormatter: NumberFormatter;
}): string {
@ -93,7 +95,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
const numberFormatter = getNumberFormatter(numberFormat);
const transformedData = data.map(datum => {
const transformedData: PieSeriesOption[] = data.map(datum => {
const name = extractGroupbyLabel({ datum, groupby });
return {
value: datum[metricLabel],
@ -104,7 +106,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
};
});
const formatter = (params: { name: string; value: number; percent: number }) =>
const formatter = (params: CallbackDataParams) =>
formatPieLabel({ params, numberFormatter, labelType });
const defaultLabel = {
@ -113,16 +115,47 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
color: '#000000',
};
const echartOptions: echarts.EChartOption<echarts.EChartOption.SeriesPie> = {
const series: PieSeriesOption[] = [
{
type: 'pie',
...getChartPadding(showLegend, legendOrientation, legendMargin),
animation: false,
radius: [`${donut ? innerRadius : 0}%`, `${outerRadius}%`],
center: ['50%', '50%'],
avoidLabelOverlap: true,
labelLine: labelsOutside && labelLine ? { show: true } : { show: false },
label: labelsOutside
? {
...defaultLabel,
position: 'outer',
alignTo: 'none',
bleedMargin: 5,
}
: {
...defaultLabel,
position: 'inner',
},
emphasis: {
label: {
show: true,
fontWeight: 'bold',
backgroundColor: 'white',
},
},
data: transformedData,
},
];
const echartOptions: EChartsOption = {
grid: {
...defaultGrid,
},
tooltip: {
...defaultTooltip,
trigger: 'item',
formatter: params =>
formatter: (params: any) =>
formatPieLabel({
params: params as echarts.EChartOption.Tooltip.Format,
params,
numberFormatter,
labelType: EchartsPieLabelType.KeyValuePercent,
}),
@ -131,37 +164,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
...getLegendProps(legendType, legendOrientation, showLegend),
data: keys,
},
series: [
{
type: 'pie',
...getChartPadding(showLegend, legendOrientation, legendMargin),
animation: false,
radius: [`${donut ? innerRadius : 0}%`, `${outerRadius}%`],
center: ['50%', '50%'],
avoidLabelOverlap: true,
labelLine: labelsOutside && labelLine ? { show: true } : { show: false },
label: labelsOutside
? {
...defaultLabel,
position: 'outer',
alignTo: 'none',
bleedMargin: 5,
}
: {
...defaultLabel,
position: 'inner',
},
emphasis: {
label: {
show: true,
fontWeight: 'bold',
backgroundColor: 'white',
},
},
// @ts-ignore
data: transformedData,
},
],
series,
};
return {

View File

@ -32,6 +32,7 @@ import {
TimeseriesChartDataResponseResult,
TimeFormatter,
} from '@superset-ui/core';
import { EChartsOption, SeriesOption } from 'echarts';
import { DEFAULT_FORM_DATA, EchartsTimeseriesFormData } from './types';
import { EchartsProps, ForecastSeriesEnum, ProphetValue } from '../types';
import { parseYAxisBound } from '../utils/controls';
@ -85,7 +86,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
const rebasedData = rebaseTimeseriesDatum(data);
const rawSeries = extractTimeseriesSeries(rebasedData);
const series: echarts.EChartOption.Series[] = [];
const series: SeriesOption[] = [];
const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat);
rawSeries.forEach(entry => {
@ -136,7 +137,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
xAxisFormatter = String;
}
const echartOptions: echarts.EChartOption = {
const echartOptions: EChartsOption = {
useUTC: true,
grid: {
...defaultGrid,
@ -168,7 +169,6 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
tooltip: {
...defaultTooltip,
trigger: richTooltip ? 'axis' : 'item',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
formatter: (params: any) => {
const value: number = !richTooltip ? params.value : params[0].value[0];
const prophetValue = !richTooltip ? [params] : params;
@ -193,10 +193,12 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
},
legend: {
...getLegendProps(legendType, legendOrientation, showLegend),
// @ts-ignore
data: rawSeries
.filter(
entry =>
extractForecastSeriesContext(entry.name || '').type === ForecastSeriesEnum.Observation,
extractForecastSeriesContext((entry.name || '') as string).type ===
ForecastSeriesEnum.Observation,
)
.map(entry => entry.name || '')
.concat(extractAnnotationLabels(annotationLayers, annotationData)),
@ -227,7 +229,6 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
};
return {
// @ts-ignore
echartOptions,
width,
height,

View File

@ -18,15 +18,28 @@
*/
import {
AnnotationData,
AnnotationLayer,
AnnotationOpacity,
CategoricalColorScale,
EventAnnotationLayer,
FormulaAnnotationLayer,
IntervalAnnotationLayer,
isTimeseriesAnnotationResult,
TimeseriesAnnotationLayer,
TimeseriesDataRecord,
} from '@superset-ui/core';
import { SeriesOption } from 'echarts';
import {
CallbackDataParams,
DefaultExtraStateOpts,
ItemStyleOption,
LineStyleOption,
OptionName,
ZRLineType,
} from 'echarts/types/src/util/types';
import {
MarkArea1DDataItemOption,
MarkArea2DDataItemOption,
} from 'echarts/types/src/component/marker/MarkAreaModel';
import { extractForecastSeriesContext } from '../utils/prophet';
import { ForecastSeriesEnum } from '../types';
import { DEFAULT_FORM_DATA, EchartsTimeseriesFormData } from './types';
@ -38,10 +51,10 @@ import {
} from '../utils/annotation';
export function transformSeries(
series: echarts.EChartOption.Series,
series: SeriesOption,
formData: EchartsTimeseriesFormData,
colorScale: CategoricalColorScale,
): echarts.EChartOption.Series | undefined {
): SeriesOption | undefined {
const { name } = series;
const {
area,
@ -52,7 +65,7 @@ export function transformSeries(
seriesType,
stack,
richTooltip,
} = {
}: EchartsTimeseriesFormData = {
...DEFAULT_FORM_DATA,
...formData,
};
@ -93,9 +106,10 @@ export function transformSeries(
itemStyle: {
color: colorScale(forecastSeries.name),
},
type: plotType,
// @ts-ignore
type: plotType,
smooth: seriesType === 'smooth',
// @ts-ignore
step: ['start', 'middle', 'end'].includes(seriesType as string) ? seriesType : undefined,
stack: stackId,
lineStyle,
@ -113,10 +127,10 @@ export function transformSeries(
}
export function transformFormulaAnnotation(
layer: FormulaAnnotationLayer,
layer: AnnotationLayer,
data: TimeseriesDataRecord[],
colorScale: CategoricalColorScale,
): echarts.EChartOption.Series {
): SeriesOption {
const { name, color, opacity, width, style } = layer;
return {
name,
@ -126,12 +140,11 @@ export function transformFormulaAnnotation(
},
lineStyle: {
opacity: parseAnnotationOpacity(opacity),
type: style,
type: style as ZRLineType,
width,
},
type: 'line',
smooth: true,
// @ts-ignore
data: evalFormula(layer, data),
symbolSize: 0,
z: 0,
@ -143,14 +156,14 @@ export function transformIntervalAnnotation(
data: TimeseriesDataRecord[],
annotationData: AnnotationData,
colorScale: CategoricalColorScale,
): echarts.EChartOption.Series[] {
const series: echarts.EChartOption.Series[] = [];
): SeriesOption[] {
const series: SeriesOption[] = [];
const annotations = extractRecordAnnotations(layer, annotationData);
annotations.forEach(annotation => {
const { name, color, opacity } = layer;
const { descriptions, intervalEnd, time, title } = annotation;
const label = formatAnnotationLabel(name, title, descriptions);
const intervalData = [
const intervalData: (MarkArea1DDataItemOption | MarkArea2DDataItemOption)[] = [
[
{
name: label,
@ -173,10 +186,11 @@ export function transformIntervalAnnotation(
emphasis: {
opacity: 0.8,
},
},
} as ItemStyleOption,
label: {
show: false,
color: '#000000',
// @ts-ignore
emphasis: {
fontWeight: 'bold',
show: true,
@ -185,7 +199,6 @@ export function transformIntervalAnnotation(
backgroundColor: '#ffffff',
},
},
// @ts-ignore
data: intervalData,
},
});
@ -198,8 +211,8 @@ export function transformEventAnnotation(
data: TimeseriesDataRecord[],
annotationData: AnnotationData,
colorScale: CategoricalColorScale,
): echarts.EChartOption.Series[] {
const series: echarts.EChartOption.Series[] = [];
): SeriesOption[] {
const series: SeriesOption[] = [];
const annotations = extractRecordAnnotations(layer, annotationData);
annotations.forEach(annotation => {
const { name, color, opacity, style, width } = layer;
@ -208,9 +221,21 @@ export function transformEventAnnotation(
const eventData = [
{
name: label,
xAxis: time,
xAxis: (time as unknown) as number,
},
];
const lineStyle: LineStyleOption & DefaultExtraStateOpts['emphasis'] = {
width,
type: style as ZRLineType,
color: color || colorScale(name),
opacity: parseAnnotationOpacity(opacity),
emphasis: {
width: width ? width + 1 : width,
opacity: 1,
},
};
series.push({
id: `Event - ${label}`,
type: 'line',
@ -218,32 +243,19 @@ export function transformEventAnnotation(
markLine: {
silent: false,
symbol: 'none',
lineStyle: {
width,
type: style,
color: color || colorScale(name),
opacity: parseAnnotationOpacity(opacity),
emphasis: {
width: width ? width + 1 : width,
opacity: 1,
},
},
lineStyle,
label: {
show: false,
color: '#000000',
position: 'insideEndTop',
// @ts-ignore
emphasis: {
// @ts-ignore
formatter: params =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
params.name,
// @ts-ignore
formatter: (params: CallbackDataParams) => params.name,
fontWeight: 'bold',
show: true,
backgroundColor: '#ffffff',
},
},
// @ts-ignore
data: eventData,
},
});
@ -256,8 +268,8 @@ export function transformTimeseriesAnnotation(
formData: EchartsTimeseriesFormData,
data: TimeseriesDataRecord[],
annotationData: AnnotationData,
): echarts.EChartOption.Series[] {
const series: echarts.EChartOption.Series[] = [];
): SeriesOption[] {
const series: SeriesOption[] = [];
const { markerSize } = formData;
const { hideLine, name, opacity, showMarkers, style, width } = layer;
const result = annotationData[name];
@ -268,11 +280,11 @@ export function transformTimeseriesAnnotation(
type: 'line',
id: key,
name: key,
data: values.map(row => [row.x, row.y] as [number | string, number]),
data: values.map(row => [row.x, row.y] as [OptionName, number]),
symbolSize: showMarkers ? markerSize : 0,
lineStyle: {
opacity: parseAnnotationOpacity(opacity),
type: style,
type: style as ZRLineType,
width: hideLine ? 0 : width,
},
});

View File

@ -18,7 +18,7 @@
*/
import React, { useRef, useEffect } from 'react';
import { styled } from '@superset-ui/core';
import { init } from 'echarts';
import { ECharts, init } from 'echarts';
import { EchartsProps, EchartsStylesProps } from '../types';
const Styles = styled.div<EchartsStylesProps>`
@ -28,7 +28,7 @@ const Styles = styled.div<EchartsStylesProps>`
export default function Echart({ width, height, echartOptions }: EchartsProps) {
const divRef = useRef<HTMLDivElement>(null);
const chartRef = useRef<echarts.ECharts>();
const chartRef = useRef<ECharts>();
useEffect(() => {
if (!divRef.current) return;

View File

@ -16,6 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
import { EChartsOption } from 'echarts';
import { TooltipMarker } from 'echarts/types/src/util/format';
export type EchartsStylesProps = {
height: number;
width: number;
@ -24,7 +27,7 @@ export type EchartsStylesProps = {
export interface EchartsProps {
height: number;
width: number;
echartOptions: echarts.EChartOption;
echartOptions: EChartsOption;
}
export enum ForecastSeriesEnum {
@ -52,7 +55,7 @@ export enum LegendType {
}
export type ProphetValue = {
marker: string;
marker: TooltipMarker;
observation?: number;
forecastTrend?: number;
forecastLower?: number;

View File

@ -23,7 +23,6 @@ import {
AnnotationLayer,
AnnotationOpacity,
AnnotationType,
FormulaAnnotationLayer,
isRecordAnnotationResult,
isTableAnnotationLayer,
isTimeseriesAnnotationResult,
@ -32,11 +31,11 @@ import {
import { parse as mathjsParse } from 'mathjs';
export function evalFormula(
formula: FormulaAnnotationLayer,
formula: AnnotationLayer,
data: TimeseriesDataRecord[],
): [Date, number][] {
const { value } = formula;
const node = mathjsParse(value);
const node = mathjsParse(value as string);
const func = node.compile();
return data.map(row => [
new Date(Number(row.__timestamp)),

View File

@ -17,14 +17,17 @@
* under the License.
*/
import { TimeseriesDataRecord, NumberFormatter } from '@superset-ui/core';
import { CallbackDataParams, OptionName } from 'echarts/types/src/util/types';
import { TooltipMarker } from 'echarts/types/src/util/format';
import { ForecastSeriesContext, ForecastSeriesEnum, ProphetValue } from '../types';
const seriesTypeRegex = new RegExp(
`(.+)(${ForecastSeriesEnum.ForecastLower}|${ForecastSeriesEnum.ForecastTrend}|${ForecastSeriesEnum.ForecastUpper})$`,
);
export const extractForecastSeriesContext = (seriesName: string): ForecastSeriesContext => {
const regexMatch = seriesTypeRegex.exec(seriesName);
if (!regexMatch) return { name: seriesName, type: ForecastSeriesEnum.Observation };
export const extractForecastSeriesContext = (seriesName: OptionName): ForecastSeriesContext => {
const name = seriesName as string;
const regexMatch = seriesTypeRegex.exec(name);
if (!regexMatch) return { name, type: ForecastSeriesEnum.Observation };
return {
name: regexMatch[1],
type: regexMatch[2] as ForecastSeriesEnum,
@ -32,7 +35,7 @@ export const extractForecastSeriesContext = (seriesName: string): ForecastSeries
};
export const extractProphetValuesFromTooltipParams = (
params: (echarts.EChartOption.Tooltip.Format & { seriesId: string })[],
params: (CallbackDataParams & { seriesId: string })[],
): Record<string, ProphetValue> => {
const values: Record<string, ProphetValue> = {};
params.forEach(param => {
@ -67,7 +70,7 @@ export const formatProphetTooltipSeries = ({
formatter,
}: ProphetValue & {
seriesName: string;
marker: string;
marker: TooltipMarker;
formatter: NumberFormatter;
}): string => {
let row = `${marker}${seriesName}: `;

View File

@ -24,13 +24,12 @@ import {
TimeFormatter,
TimeseriesDataRecord,
} from '@superset-ui/core';
import { LegendComponentOption, SeriesOption } from 'echarts';
import { NULL_STRING } from '../constants';
import { LegendOrientation, LegendType } from '../types';
import { defaultLegendPadding } from '../defaults';
export function extractTimeseriesSeries(
data: TimeseriesDataRecord[],
): echarts.EChartOption.Series[] {
export function extractTimeseriesSeries(data: TimeseriesDataRecord[]): SeriesOption[] {
if (data.length === 0) return [];
const rows = data.map(datum => ({
...datum,
@ -42,9 +41,10 @@ export function extractTimeseriesSeries(
.map(key => ({
id: key,
name: key,
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
data: rows.map(datum => [datum.__timestamp, datum[key]]),
data: rows.map((datum: { [p: string]: DataRecordValue; __timestamp: Date | null }) => [
datum.__timestamp,
datum[key],
]),
}));
}
@ -93,8 +93,8 @@ export function getLegendProps(
type: LegendType,
orientation: LegendOrientation,
show: boolean,
): echarts.EChartOption.Legend {
const legend: echarts.EChartOption.Legend = {
): LegendComponentOption | LegendComponentOption[] {
const legend: LegendComponentOption | LegendComponentOption[] = {
orient: [LegendOrientation.Top, LegendOrientation.Bottom].includes(orientation)
? 'horizontal'
: 'vertical',