Skip to content

Commit

Permalink
feat(plugin-chart-echarts): add support for generic axis to mixed chart
Browse files Browse the repository at this point in the history
  • Loading branch information
villebro committed May 17, 2022
1 parent 55aef4d commit b6e13af
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
*/
import {
buildQueryContext,
QueryFormData,
QueryObject,
DTTM_ALIAS,
ensureIsArray,
normalizeOrderBy,
PostProcessingPivot,
QueryFormData,
QueryObject,
} from '@superset-ui/core';
import {
pivotOperator,
Expand All @@ -39,28 +41,36 @@ import {
} from '../utils/formDataSuffix';

export default function buildQuery(formData: QueryFormData) {
const { x_axis: index } = formData;
const is_timeseries = index === DTTM_ALIAS || !index;
const baseFormData = {
...formData,
is_timeseries: true,
columns: formData.groupby,
columns_b: formData.groupby_b,
is_timeseries,
};

const formData1 = removeFormDataSuffix(baseFormData, '_b');
const formData2 = retainFormDataSuffix(baseFormData, '_b');

const queryContexts = [formData1, formData2].map(fd =>
buildQueryContext(fd, baseQueryObject => {
const queryObject = {
...baseQueryObject,
is_timeseries: true,
columns: [...ensureIsArray(index), ...ensureIsArray(fd.groupby)],
series_columns: fd.groupby,
is_timeseries,
};

const pivotOperatorInRuntime: PostProcessingPivot = isTimeComparison(
fd,
queryObject,
)
? timeComparePivotOperator(fd, queryObject)
: pivotOperator(fd, queryObject);
: pivotOperator(fd, {
...queryObject,
columns: fd.groupby,
index,
is_timeseries,
});

const tmpQueryObject = {
...queryObject,
Expand All @@ -70,9 +80,13 @@ export default function buildQuery(formData: QueryFormData) {
rollingWindowOperator(fd, queryObject),
timeCompareOperator(fd, queryObject),
resampleOperator(fd, queryObject),
renameOperator(fd, queryObject),
renameOperator(fd, {
...queryObject,
columns: fd.groupby,
...{ is_timeseries },
}),
flattenOperator(fd, queryObject),
],
].filter(op => op),
} as QueryObject;
return [normalizeOrderBy(tmpQueryObject)];
}),
Expand Down
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 } from '@superset-ui/core';
import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
import { cloneDeep } from 'lodash';
import {
ControlPanelConfig,
Expand All @@ -31,7 +31,7 @@ import {

import { DEFAULT_FORM_DATA } from './types';
import { EchartsTimeseriesSeriesType } from '../Timeseries/types';
import { legendSection, richTooltipSection } from '../controls';
import { legendSection, richTooltipSection, xAxisControl } from '../controls';

const {
area,
Expand Down Expand Up @@ -447,4 +447,12 @@ const config: ControlPanelConfig = {
],
};

if (isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)) {
config.controlPanelSections.splice(1, 0, {
label: t('Shared query fields'),
expanded: true,
controlSetRows: [[xAxisControl]],
});
}

export default config;
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,21 @@
* under the License.
*/
import {
t,
ChartMetadata,
ChartPlugin,
AnnotationType,
Behavior,
ChartMetadata,
ChartPlugin,
FeatureFlag,
isFeatureEnabled,
t,
} from '@superset-ui/core';
import buildQuery from './buildQuery';
import controlPanel from './controlPanel';
import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png';
import {
EchartsMixedTimeseriesProps,
EchartsMixedTimeseriesFormData,
EchartsMixedTimeseriesProps,
} from './types';

export default class EchartsTimeseriesChartPlugin extends ChartPlugin<
Expand All @@ -55,16 +57,22 @@ export default class EchartsTimeseriesChartPlugin extends ChartPlugin<
behaviors: [Behavior.INTERACTIVE_CHART],
category: t('Evolution'),
credits: ['https://echarts.apache.org'],
description: t(
'Visualize two different time series using the same x-axis time range. Note that each time series can be visualized differently (e.g. 1 using bars and 1 using a line).',
),
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t(
'Visualize two different series using the same x-axis. Note that both series can be visualized with a different chart type (e.g. 1 using bars and 1 using a line).',
)
: t(
'Visualize two different time series using the same x-axis. Note that each time series can be visualized differently (e.g. 1 using bars and 1 using a line).',
),
supportedAnnotationTypes: [
AnnotationType.Event,
AnnotationType.Formula,
AnnotationType.Interval,
AnnotationType.Timeseries,
],
name: t('Mixed Time-Series'),
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t('Mixed Chart')
: t('Mixed Time-Series'),
thumbnail,
tags: [
t('Advanced-Analytics'),
Expand All @@ -73,7 +81,6 @@ export default class EchartsTimeseriesChartPlugin extends ChartPlugin<
t('Experimental'),
t('Line'),
t('Multi-Variables'),
t('Predictive'),
t('Time'),
t('Transformable'),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ import {
AnnotationLayer,
CategoricalColorNamespace,
DataRecordValue,
TimeseriesDataRecord,
DTTM_ALIAS,
GenericDataType,
getColumnLabel,
getNumberFormatter,
isEventAnnotationLayer,
isFormulaAnnotationLayer,
isIntervalAnnotationLayer,
isTimeseriesAnnotationLayer,
TimeseriesDataRecord,
} from '@superset-ui/core';
import { EChartsCoreOption, SeriesOption } from 'echarts';
import {
Expand All @@ -41,6 +44,8 @@ import {
currentSeries,
dedupSeries,
extractSeries,
getAxisType,
getColtypesMapping,
getLegendProps,
} from '../utils/series';
import { extractAnnotationLabels } from '../utils/annotation';
Expand All @@ -62,7 +67,7 @@ import {
transformSeries,
transformTimeseriesAnnotation,
} from '../Timeseries/transformers';
import { TIMESERIES_CONSTANTS } from '../constants';
import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants';

export default function transformProps(
chartProps: EchartsMixedTimeseriesProps,
Expand Down Expand Up @@ -124,24 +129,35 @@ export default function transformProps(
groupbyB,
emitFilter,
emitFilterB,
xAxis: xAxisOrig,
xAxisTitle,
yAxisTitle,
xAxisTitleMargin,
yAxisTitleMargin,
yAxisTitlePosition,
sliceId,
timeGrainSqla,
}: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };

const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);

const xAxisCol =
verboseMap[xAxisOrig] || getColumnLabel(xAxisOrig || DTTM_ALIAS);

const rebasedDataA = rebaseForecastDatum(data1, verboseMap);
const rawSeriesA = extractSeries(rebasedDataA, {
fillNeighborValue: stack ? 0 : undefined,
xAxis: xAxisCol,
});
const rebasedDataB = rebaseForecastDatum(data2, verboseMap);
const rawSeriesB = extractSeries(rebasedDataB, {
fillNeighborValue: stackB ? 0 : undefined,
xAxis: xAxisCol,
});

const dataTypes = getColtypesMapping(queriesData[0]);
const xAxisDataType = dataTypes?.[xAxisCol];
const xAxisType = getAxisType(xAxisDataType);
const series: SeriesOption[] = [];
const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat);
const formatterSecondary = getNumberFormatter(
Expand Down Expand Up @@ -255,8 +271,14 @@ export default function transformProps(
if (max === undefined) max = 1;
}

const tooltipTimeFormatter = getTooltipTimeFormatter(tooltipTimeFormat);
const xAxisFormatter = getXAxisFormatter(xAxisTimeFormat);
const tooltipFormatter =
xAxisDataType === GenericDataType.TEMPORAL
? getTooltipTimeFormatter(tooltipTimeFormat)
: String;
const xAxisFormatter =
xAxisDataType === GenericDataType.TEMPORAL
? getXAxisFormatter(xAxisTimeFormat)
: String;

const addYAxisTitleOffset = !!(yAxisTitle || yAxisTitleSecondary);
const addXAxisTitleOffset = !!xAxisTitle;
Expand Down Expand Up @@ -298,14 +320,18 @@ export default function transformProps(
...chartPadding,
},
xAxis: {
type: 'time',
type: xAxisType,
name: xAxisTitle,
nameGap: convertInteger(xAxisTitleMargin),
nameLocation: 'middle',
axisLabel: {
formatter: xAxisFormatter,
rotate: xAxisLabelRotation,
},
minInterval:
xAxisType === 'time' && timeGrainSqla
? TIMEGRAIN_TO_TIMESTAMP[timeGrainSqla]
: 0,
},
yAxis: [
{
Expand Down Expand Up @@ -350,7 +376,7 @@ export default function transformProps(
forecastValue.sort((a, b) => b.data[1] - a.data[1]);
}

const rows: Array<string> = [`${tooltipTimeFormatter(xValue)}`];
const rows: Array<string> = [`${tooltipFormatter(xValue)}`];
const forecastValues =
extractForecastValuesFromTooltipParams(forecastValue);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import {
buildQueryContext,
DTTM_ALIAS,
ensureIsArray,
QueryFormData,
normalizeOrderBy,
PostProcessingPivot,
QueryFormData,
} from '@superset-ui/core';
import {
rollingWindowOperator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
isFormulaAnnotationLayer,
isIntervalAnnotationLayer,
isTimeseriesAnnotationLayer,
TimeGranularity,
TimeseriesChartDataResponseResult,
} from '@superset-ui/core';
import { EChartsCoreOption, SeriesOption } from 'echarts';
Expand All @@ -47,6 +46,7 @@ import {
currentSeries,
dedupSeries,
extractSeries,
getAxisType,
getColtypesMapping,
getLegendProps,
} from '../utils/series';
Expand All @@ -70,15 +70,7 @@ import {
transformSeries,
transformTimeseriesAnnotation,
} from './transformers';
import { TIMESERIES_CONSTANTS } from '../constants';

const TimeGrainToTimestamp = {
[TimeGranularity.HOUR]: 3600 * 1000,
[TimeGranularity.DAY]: 3600 * 1000 * 24,
[TimeGranularity.MONTH]: 3600 * 1000 * 24 * 31,
[TimeGranularity.QUARTER]: 3600 * 1000 * 24 * 31 * 3,
[TimeGranularity.YEAR]: 3600 * 1000 * 24 * 31 * 12,
};
import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants';

export default function transformProps(
chartProps: EchartsTimeseriesChartProps,
Expand Down Expand Up @@ -157,18 +149,7 @@ export default function transformProps(
Object.values(rawSeries).map(series => series.name as string),
);
const xAxisDataType = dataTypes?.[xAxisCol];
let xAxisType: 'time' | 'value' | 'category';
switch (xAxisDataType) {
case GenericDataType.TEMPORAL:
xAxisType = 'time';
break;
case GenericDataType.NUMERIC:
xAxisType = 'value';
break;
default:
xAxisType = 'category';
break;
}
const xAxisType = getAxisType(xAxisDataType);
const series: SeriesOption[] = [];
const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat);

Expand Down Expand Up @@ -342,7 +323,7 @@ export default function transformProps(
},
minInterval:
xAxisType === 'time' && timeGrainSqla
? TimeGrainToTimestamp[timeGrainSqla]
? TIMEGRAIN_TO_TIMESTAMP[timeGrainSqla]
: 0,
};
let yAxis: any = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/

import { TimeGranularity } from '@superset-ui/core';
import { LabelPositionEnum } from './types';

// eslint-disable-next-line import/prefer-default-export
Expand Down Expand Up @@ -59,3 +60,11 @@ export enum OpacityEnum {
SemiTransparent = 0.3,
NonTransparent = 1,
}

export const TIMEGRAIN_TO_TIMESTAMP = {
[TimeGranularity.HOUR]: 3600 * 1000,
[TimeGranularity.DAY]: 3600 * 1000 * 24,
[TimeGranularity.MONTH]: 3600 * 1000 * 24 * 31,
[TimeGranularity.QUARTER]: 3600 * 1000 * 24 * 31 * 3,
[TimeGranularity.YEAR]: 3600 * 1000 * 24 * 31 * 12,
};
12 changes: 12 additions & 0 deletions superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,15 @@ export const currentSeries = {
name: '',
legend: '',
};

export function getAxisType(
dataType?: GenericDataType,
): 'time' | 'value' | 'category' {
if (dataType === GenericDataType.TEMPORAL) {
return 'time';
}
if (dataType === GenericDataType.NUMERIC) {
return 'value';
}
return 'category';
}

0 comments on commit b6e13af

Please sign in to comment.