feat(plugin-chart-echarts): support non-timeseries x-axis (#17917)

* feat(plugin-chart-echarts): support non-timeseries x-axis

* fix tests

* change formula return type from Date to number

* add x_axis test coverage

* rename func and improve coverage

* add x-axis control to bar chart

* remove redundant console.log

* fix description

* make x-axis control mandatory

* 🙃

* fix x-axis formatter

* fix showValues

* fix implicit rDTTM_ALIAS references in postProcessing

* replace TIME_COLUMN with DTTM_ALIAS

* fix remaining implicit indexes

* fix: Disable filtering on wide result sets (#18021)

* fix: handle null values in time-series table (#18039)

* cleanup column_type_mappings (#17569)

Signed-off-by: Đặng Minh Dũng <dungdm93@live.com>

* important change to MakeFile (#18037)

* add missing is_timeseries to pivot op

Co-authored-by: Erik Ritter <erik.ritter@airbnb.com>
Co-authored-by: Grace Guo <grace.guo@airbnb.com>
Co-authored-by: Đặng Minh Dũng <dungdm93@live.com>
Co-authored-by: AAfghahi <48933336+AAfghahi@users.noreply.github.com>
This commit is contained in:
Ville Brofeldt
2022-01-21 21:23:23 +02:00
committed by GitHub
parent b083b3421f
commit e9651ea52f
42 changed files with 489 additions and 201 deletions

View File

@@ -22,10 +22,7 @@ import {
PostProcessingResample,
QueryFormData,
} from '@superset-ui/core';
import {
rollingWindowOperator,
TIME_COLUMN,
} from '@superset-ui/chart-controls';
import { rollingWindowOperator } from '@superset-ui/chart-controls';
const TIME_GRAIN_MAP: Record<string, string> = {
PT1S: 'S',
@@ -65,7 +62,7 @@ export default function buildQuery(formData: QueryFormData) {
method: 'asfreq',
rule,
fill_value: null,
time_column: TIME_COLUMN,
time_column: DTTM_ALIAS,
},
};
}

View File

@@ -17,6 +17,7 @@
* under the License.
*/
import {
DTTM_ALIAS,
extractTimegrain,
getNumberFormatter,
NumberFormats,
@@ -54,7 +55,6 @@ export function renderTooltipFactory(
};
}
const TIME_COLUMN = '__timestamp';
const formatPercentChange = getNumberFormatter(
NumberFormats.PERCENT_SIGNED_1_POINT,
);
@@ -97,7 +97,7 @@ export default function transformProps(
let trendLineData;
let percentChange = 0;
let bigNumber = data.length === 0 ? null : data[0][metricName];
let timestamp = data.length === 0 ? null : data[0][TIME_COLUMN];
let timestamp = data.length === 0 ? null : data[0][DTTM_ALIAS];
let bigNumberFallback;
const metricColtypeIndex = colnames.findIndex(name => name === metricName);
@@ -106,7 +106,7 @@ export default function transformProps(
if (data.length > 0) {
const sortedData = (data as BigNumberDatum[])
.map(d => [d[TIME_COLUMN], parseMetricValue(d[metricName])])
.map(d => [d[DTTM_ALIAS], parseMetricValue(d[metricName])])
// sort in time descending order
.sort((a, b) => (a[0] !== null && b[0] !== null ? b[0] - a[0] : 0));

View File

@@ -40,7 +40,7 @@ import { parseYAxisBound } from '../utils/controls';
import {
currentSeries,
dedupSeries,
extractTimeseriesSeries,
extractSeries,
getLegendProps,
} from '../utils/series';
import { extractAnnotationLabels } from '../utils/annotation';
@@ -131,11 +131,11 @@ export default function transformProps(
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
const rebasedDataA = rebaseTimeseriesDatum(data1, verboseMap);
const rawSeriesA = extractTimeseriesSeries(rebasedDataA, {
const rawSeriesA = extractSeries(rebasedDataA, {
fillNeighborValue: stack ? 0 : undefined,
});
const rebasedDataB = rebaseTimeseriesDatum(data2, verboseMap);
const rawSeriesB = extractTimeseriesSeries(rebasedDataB, {
const rawSeriesB = extractSeries(rebasedDataB, {
fillNeighborValue: stackB ? 0 : undefined,
});

View File

@@ -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 {
ControlPanelConfig,
ControlPanelsContainerProps,
@@ -36,6 +36,7 @@ import {
legendSection,
richTooltipSection,
showValueSection,
xAxisControl,
} from '../../controls';
const {
@@ -59,6 +60,7 @@ const config: ControlPanelConfig = {
label: t('Query'),
expanded: true,
controlSetRows: [
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) ? [xAxisControl] : [],
['metrics'],
['groupby'],
[

View File

@@ -22,6 +22,8 @@ import {
ChartPlugin,
AnnotationType,
Behavior,
isFeatureEnabled,
FeatureFlag,
} from '@superset-ui/core';
import buildQuery from '../buildQuery';
import controlPanel from './controlPanel';
@@ -43,16 +45,6 @@ export default class EchartsAreaChartPlugin extends ChartPlugin<
EchartsTimeseriesFormData,
EchartsTimeseriesChartProps
> {
/**
* The constructor is used to pass relevant metadata and callbacks that get
* registered in respective registries that are used throughout the library
* and application. A more thorough description of each property is given in
* the respective imported file.
*
* It is worth noting that `buildQuery` and is optional, and only needed for
* advanced visualizations that require either post processing operations
* (pivoting, rolling aggregations, sorting etc) or submitting multiple queries.
*/
constructor() {
super({
buildQuery,
@@ -62,9 +54,13 @@ export default class EchartsAreaChartPlugin extends ChartPlugin<
behaviors: [Behavior.INTERACTIVE_CHART],
category: t('Evolution'),
credits: ['https://echarts.apache.org'],
description: t(
'Time-series Area chart are similar to line chart in that they represent variables with the same scale, but area charts stack the metrics on top of each other. An area chart in Superset can be stream, stack, or expand.',
),
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t(
'Area charts are similar to line charts in that they represent variables with the same scale, but area charts stack the metrics on top of each other.',
)
: t(
'Time-series Area chart are similar to line chart in that they represent variables with the same scale, but area charts stack the metrics on top of each other. An area chart in Superset can be stream, stack, or expand.',
),
exampleGallery: [{ url: example1 }],
supportedAnnotationTypes: [
AnnotationType.Event,
@@ -72,7 +68,9 @@ export default class EchartsAreaChartPlugin extends ChartPlugin<
AnnotationType.Interval,
AnnotationType.Timeseries,
],
name: t('Time-series Area Chart'),
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t('Area Chart v2')
: t('Time-series Area Chart'),
tags: [
t('ECharts'),
t('Predictive'),

View File

@@ -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 {
ControlPanelConfig,
ControlPanelsContainerProps,
@@ -35,6 +35,7 @@ import {
legendSection,
richTooltipSection,
showValueSection,
xAxisControl,
} from '../../../controls';
const {
@@ -56,6 +57,7 @@ const config: ControlPanelConfig = {
label: t('Query'),
expanded: true,
controlSetRows: [
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) ? [xAxisControl] : [],
['metrics'],
['groupby'],
[

View File

@@ -17,11 +17,13 @@
* 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';
@@ -58,9 +60,11 @@ export default class EchartsTimeseriesBarChartPlugin extends ChartPlugin<
behaviors: [Behavior.INTERACTIVE_CHART],
category: t('Evolution'),
credits: ['https://echarts.apache.org'],
description: t(
'Time-series Bar Charts are used to show the changes in a metric over time as a series of bars.',
),
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t('Bar Charts are used to show metrics as a series of bars.')
: t(
'Time-series Bar Charts are used to show the changes in a metric over time as a series of bars.',
),
exampleGallery: [
{ url: example1 },
{ url: example2 },
@@ -72,7 +76,9 @@ export default class EchartsTimeseriesBarChartPlugin extends ChartPlugin<
AnnotationType.Interval,
AnnotationType.Timeseries,
],
name: t('Time-series Bar Chart v2'),
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t('Bar Chart v2')
: t('Time-series Bar Chart v2'),
tags: [
t('ECharts'),
t('Predictive'),

View File

@@ -17,11 +17,13 @@
* 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';
@@ -57,9 +59,13 @@ export default class EchartsTimeseriesLineChartPlugin extends ChartPlugin<
behaviors: [Behavior.INTERACTIVE_CHART],
category: t('Evolution'),
credits: ['https://echarts.apache.org'],
description: t(
'Time-series line chart is used to visualize repeated measurements taken over regular time intervals. Line chart is a type of chart which displays information as a series of data points connected by straight line segments. It is a basic type of chart common in many fields.',
),
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t(
'Line chart is used to visualize measurements taken over a given category. Line chart is a type of chart which displays information as a series of data points connected by straight line segments. It is a basic type of chart common in many fields.',
)
: t(
'Time-series line chart is used to visualize repeated measurements taken over regular time intervals. Line chart is a type of chart which displays information as a series of data points connected by straight line segments. It is a basic type of chart common in many fields.',
),
exampleGallery: [{ url: example1 }, { url: example2 }],
supportedAnnotationTypes: [
AnnotationType.Event,
@@ -67,7 +73,9 @@ export default class EchartsTimeseriesLineChartPlugin extends ChartPlugin<
AnnotationType.Interval,
AnnotationType.Timeseries,
],
name: t('Time-series Line Chart'),
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t('Line Chart')
: t('Time-series Line Chart'),
tags: [
t('ECharts'),
t('Predictive'),

View File

@@ -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 {
ControlPanelConfig,
ControlPanelsContainerProps,
@@ -32,6 +32,7 @@ import {
legendSection,
richTooltipSection,
showValueSection,
xAxisControl,
} from '../../../controls';
const {
@@ -52,6 +53,7 @@ const config: ControlPanelConfig = {
label: t('Query'),
expanded: true,
controlSetRows: [
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) ? [xAxisControl] : [],
['metrics'],
['groupby'],
['adhoc_filters'],

View File

@@ -17,11 +17,13 @@
* 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';
@@ -56,9 +58,13 @@ export default class EchartsTimeseriesScatterChartPlugin extends ChartPlugin<
behaviors: [Behavior.INTERACTIVE_CHART],
category: t('Evolution'),
credits: ['https://echarts.apache.org'],
description: t(
'Time-series Scatter Plot has time on the horizontal axis in linear units, and the points are connected in order. It shows a statistical relationship between two variables.',
),
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t(
'Scatter Plot has the horizontal axis in linear units, and the points are connected in order. It shows a statistical relationship between two variables.',
)
: t(
'Time-series Scatter Plot has time on the horizontal axis in linear units, and the points are connected in order. It shows a statistical relationship between two variables.',
),
exampleGallery: [{ url: example1 }],
supportedAnnotationTypes: [
AnnotationType.Event,
@@ -66,7 +72,9 @@ export default class EchartsTimeseriesScatterChartPlugin extends ChartPlugin<
AnnotationType.Interval,
AnnotationType.Timeseries,
],
name: t('Time-series Scatter Plot'),
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t('Scatter Plot')
: t('Time-series Scatter Plot'),
tags: [
t('ECharts'),
t('Predictive'),

View File

@@ -17,11 +17,13 @@
* 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';
@@ -56,9 +58,13 @@ export default class EchartsTimeseriesSmoothLineChartPlugin extends ChartPlugin<
behaviors: [Behavior.INTERACTIVE_CHART],
category: t('Evolution'),
credits: ['https://echarts.apache.org'],
description: t(
'Time-series Smooth-line is a variation of line chart. Without angles and hard edges, Smooth-line looks more smarter and more professional.',
),
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t(
'Smooth-line is a variation of the line chart. Without angles and hard edges, Smooth-line sometimes looks smarter and more professional.',
)
: t(
'Time-series Smooth-line is a variation of the line chart. Without angles and hard edges, Smooth-line sometimes looks smarter and more professional.',
),
exampleGallery: [{ url: example1 }],
supportedAnnotationTypes: [
AnnotationType.Event,
@@ -66,7 +72,9 @@ export default class EchartsTimeseriesSmoothLineChartPlugin extends ChartPlugin<
AnnotationType.Interval,
AnnotationType.Timeseries,
],
name: t('Time-series Smooth Line'),
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t('Smooth Line')
: t('Time-series Smooth Line'),
tags: [
t('ECharts'),
t('Predictive'),

View File

@@ -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 {
ControlPanelConfig,
ControlPanelsContainerProps,
@@ -31,7 +31,8 @@ import { DEFAULT_FORM_DATA, EchartsTimeseriesContributionType } from '../types';
import {
legendSection,
richTooltipSection,
showValueSection,
showValueSectionWithoutStack,
xAxisControl,
} from '../../controls';
const {
@@ -53,6 +54,7 @@ const config: ControlPanelConfig = {
label: t('Query'),
expanded: true,
controlSetRows: [
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) ? [xAxisControl] : [],
['metrics'],
['groupby'],
[
@@ -100,7 +102,7 @@ const config: ControlPanelConfig = {
expanded: true,
controlSetRows: [
['color_scheme'],
...showValueSection,
...showValueSectionWithoutStack,
[
{
name: 'markerEnabled',

View File

@@ -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 {
ControlPanelConfig,
ControlPanelsContainerProps,
@@ -36,6 +36,7 @@ import {
legendSection,
richTooltipSection,
showValueSection,
xAxisControl,
} from '../../controls';
const {
@@ -59,6 +60,7 @@ const config: ControlPanelConfig = {
label: t('Query'),
expanded: true,
controlSetRows: [
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) ? [xAxisControl] : [],
['metrics'],
['groupby'],
[

View File

@@ -17,11 +17,13 @@
* 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';
@@ -47,9 +49,13 @@ export default class EchartsTimeseriesStepChartPlugin extends ChartPlugin<
behaviors: [Behavior.INTERACTIVE_CHART],
category: t('Evolution'),
credits: ['https://echarts.apache.org'],
description: t(
'Time-series Stepped-line graph (also called step chart) is a variation of line chart but with the line forming a series of steps between data points. A step chart can be useful when you want to show the changes that occur at irregular intervals.',
),
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t(
'Stepped-line graph (also called step chart) is a variation of line chart but with the line forming a series of steps between data points. A step chart can be useful when you want to show the changes that occur at irregular intervals.',
)
: t(
'Time-series Stepped-line graph (also called step chart) is a variation of line chart but with the line forming a series of steps between data points. A step chart can be useful when you want to show the changes that occur at irregular intervals.',
),
exampleGallery: [{ url: example1 }, { url: example2 }],
supportedAnnotationTypes: [
AnnotationType.Event,
@@ -57,7 +63,9 @@ export default class EchartsTimeseriesStepChartPlugin extends ChartPlugin<
AnnotationType.Interval,
AnnotationType.Timeseries,
],
name: t('Time-series Stepped Line'),
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t('Stepped Line')
: t('Time-series Stepped Line'),
tags: [
t('ECharts'),
t('Predictive'),

View File

@@ -18,6 +18,8 @@
*/
import {
buildQueryContext,
DTTM_ALIAS,
ensureIsArray,
QueryFormData,
normalizeOrderBy,
RollingType,
@@ -35,11 +37,14 @@ import {
} from '@superset-ui/chart-controls';
export default function buildQuery(formData: QueryFormData) {
const { x_axis, groupby } = formData;
const is_timeseries = x_axis === DTTM_ALIAS || !x_axis;
return buildQueryContext(formData, baseQueryObject => {
const pivotOperatorInRuntime: PostProcessingPivot | undefined =
pivotOperator(formData, {
...baseQueryObject,
is_timeseries: true,
index: x_axis,
is_timeseries,
});
if (
pivotOperatorInRuntime &&
@@ -57,7 +62,9 @@ export default function buildQuery(formData: QueryFormData) {
return [
{
...baseQueryObject,
is_timeseries: true,
columns: [...ensureIsArray(x_axis), ...ensureIsArray(groupby)],
series_columns: groupby,
is_timeseries,
// todo: move `normalizeOrderBy to extractQueryFields`
orderby: normalizeOrderBy(baseQueryObject).orderby,
time_offsets: isValidTimeCompare(formData, baseQueryObject)

View File

@@ -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 {
ControlPanelConfig,
ControlPanelsContainerProps,
@@ -36,6 +36,7 @@ import {
legendSection,
richTooltipSection,
showValueSection,
xAxisControl,
} from '../controls';
const {
@@ -60,6 +61,7 @@ const config: ControlPanelConfig = {
label: t('Query'),
expanded: true,
controlSetRows: [
isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) ? [xAxisControl] : [],
['metrics'],
['groupby'],
[

View File

@@ -17,11 +17,13 @@
* 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';
@@ -37,16 +39,6 @@ export default class EchartsTimeseriesChartPlugin extends ChartPlugin<
EchartsTimeseriesFormData,
EchartsTimeseriesChartProps
> {
/**
* The constructor is used to pass relevant metadata and callbacks that get
* registered in respective registries that are used throughout the library
* and application. A more thorough description of each property is given in
* the respective imported file.
*
* It is worth noting that `buildQuery` and is optional, and only needed for
* advanced visualizations that require either post processing operations
* (pivoting, rolling aggregations, sorting etc) or submitting multiple queries.
*/
constructor() {
super({
buildQuery,
@@ -56,9 +48,13 @@ export default class EchartsTimeseriesChartPlugin extends ChartPlugin<
behaviors: [Behavior.INTERACTIVE_CHART],
category: t('Evolution'),
credits: ['https://echarts.apache.org'],
description: t(
'Swiss army knife for visualizing time series data. Choose between step, line, scatter, and bar charts. This viz type has many customization options as well.',
),
description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t(
'Swiss army knife for visualizing data. Choose between step, line, scatter, and bar charts. This viz type has many customization options as well.',
)
: t(
'Swiss army knife for visualizing time series data. Choose between step, line, scatter, and bar charts. This viz type has many customization options as well.',
),
exampleGallery: [{ url: example }],
supportedAnnotationTypes: [
AnnotationType.Event,
@@ -66,7 +62,9 @@ export default class EchartsTimeseriesChartPlugin extends ChartPlugin<
AnnotationType.Interval,
AnnotationType.Timeseries,
],
name: t('Time-series Chart'),
name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)
? t('Generic Chart')
: t('Time-series Chart'),
tags: [
t('Advanced-Analytics'),
t('Aesthetic'),

View File

@@ -20,28 +20,32 @@
import {
AnnotationLayer,
CategoricalColorNamespace,
DataRecordValue,
DTTM_ALIAS,
GenericDataType,
getNumberFormatter,
isEventAnnotationLayer,
isFormulaAnnotationLayer,
isIntervalAnnotationLayer,
isTimeseriesAnnotationLayer,
TimeseriesChartDataResponseResult,
DataRecordValue,
} from '@superset-ui/core';
import { EChartsCoreOption, SeriesOption } from 'echarts';
import {
DEFAULT_FORM_DATA,
EchartsTimeseriesChartProps,
EchartsTimeseriesFormData,
EchartsTimeseriesSeriesType,
TimeseriesChartTransformedProps,
} from './types';
import { ForecastSeriesEnum, ProphetValue } from '../types';
import { parseYAxisBound } from '../utils/controls';
import {
dedupSeries,
extractTimeseriesSeries,
getLegendProps,
currentSeries,
dedupSeries,
extractSeries,
getColtypesMapping,
getLegendProps,
} from '../utils/series';
import { extractAnnotationLabels } from '../utils/annotation';
import {
@@ -77,8 +81,10 @@ export default function transformProps(
datasource,
} = chartProps;
const { verboseMap = {} } = datasource;
const [queryData] = queriesData;
const { annotation_data: annotationData_, data = [] } =
queriesData[0] as TimeseriesChartDataResponseResult;
queryData as TimeseriesChartDataResponseResult;
const dataTypes = getColtypesMapping(queryData);
const annotationData = annotationData_ || {};
const {
@@ -106,6 +112,7 @@ export default function transformProps(
tooltipSortByMetric,
zoomable,
richTooltip,
xAxis: xAxisOrig,
xAxisLabelRotation,
emitFilter,
groupby,
@@ -120,12 +127,28 @@ export default function transformProps(
}: EchartsTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
const rebasedData = rebaseTimeseriesDatum(data, verboseMap);
const rawSeries = extractTimeseriesSeries(rebasedData, {
const xAxisCol = xAxisOrig || DTTM_ALIAS;
const rawSeries = extractSeries(rebasedData, {
fillNeighborValue: stack && !forecastEnabled ? 0 : undefined,
xAxis: xAxisCol,
removeNulls: seriesType === EchartsTimeseriesSeriesType.Scatter,
});
const seriesContexts = extractForecastSeriesContexts(
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 series: SeriesOption[] = [];
const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat);
@@ -135,7 +158,7 @@ export default function transformProps(
rebasedData.forEach(data => {
const values = Object.keys(data).reduce((prev, curr) => {
if (curr === '__timestamp') {
if (curr === xAxisCol) {
return prev;
}
const value = data[curr] || 0;
@@ -227,8 +250,14 @@ export default function transformProps(
if (max === undefined) max = 1;
}
const tooltipFormatter = getTooltipTimeFormatter(tooltipTimeFormat);
const xAxisFormatter = getXAxisFormatter(xAxisTimeFormat);
const tooltipFormatter =
xAxisDataType === GenericDataType.TEMPORAL
? getTooltipTimeFormatter(tooltipTimeFormat)
: String;
const xAxisFormatter =
xAxisDataType === GenericDataType.TEMPORAL
? getXAxisFormatter(xAxisTimeFormat)
: String;
const labelMap = series.reduce(
(acc: Record<string, DataRecordValue[]>, datum) => {
@@ -273,7 +302,7 @@ export default function transformProps(
...padding,
},
xAxis: {
type: 'time',
type: xAxisType,
name: xAxisTitle,
nameGap: xAxisTitleMargin,
nameLocation: 'middle',

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
import { t } from '@superset-ui/core';
import { t, validateNonEmpty } from '@superset-ui/core';
import {
ControlPanelsContainerProps,
ControlSetItem,
@@ -138,6 +138,18 @@ const onlyTotalControl: ControlSetItem = {
},
};
export const xAxisControl: ControlSetItem = {
name: 'x_axis',
config: {
...sharedControls.groupby,
label: t('X-axis'),
default: null,
multi: false,
description: t('Dimension to use on x-axis.'),
validators: [validateNonEmpty],
},
};
const percentageThresholdControl: ControlSetItem = {
name: 'percentage_threshold',
config: {
@@ -163,6 +175,11 @@ export const showValueSection: ControlSetRow[] = [
[percentageThresholdControl],
];
export const showValueSectionWithoutStack: ControlSetRow[] = [
[showValueControl],
[onlyTotalControl],
];
const richTooltipControl: ControlSetItem = {
name: 'rich_tooltip',
config: {

View File

@@ -34,11 +34,11 @@ import {
export function evalFormula(
formula: FormulaAnnotationLayer,
data: TimeseriesDataRecord[],
): [Date, number][] {
): [number, number][] {
const { value: expression } = formula;
return data.map(row => [
new Date(Number(row.__timestamp)),
Number(row.__timestamp),
evalExpression(expression, row.__timestamp as number),
]);
}

View File

@@ -16,7 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import { TimeseriesDataRecord, NumberFormatter } from '@superset-ui/core';
import {
TimeseriesDataRecord,
NumberFormatter,
DTTM_ALIAS,
} from '@superset-ui/core';
import { CallbackDataParams, OptionName } from 'echarts/types/src/util/types';
import { TooltipMarker } from 'echarts/types/src/util/format';
import {
@@ -117,7 +121,7 @@ export function rebaseTimeseriesDatum(
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return data.map(row => {
const newRow: TimeseriesDataRecord = { __timestamp: '' };
const newRow: TimeseriesDataRecord = { [DTTM_ALIAS]: '' };
keys.forEach(key => {
const forecastContext = extractForecastSeriesContext(key);
const lowerKey = `${forecastContext.name}${ForecastSeriesEnum.ForecastLower}`;
@@ -131,7 +135,7 @@ export function rebaseTimeseriesDatum(
value -= row[lowerKey] as number;
}
const newKey =
key !== '__timestamp' && verboseMap[key] ? verboseMap[key] : key;
key !== DTTM_ALIAS && verboseMap[key] ? verboseMap[key] : key;
newRow[newKey] = value;
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-return

View File

@@ -21,11 +21,11 @@ import {
ChartDataResponseResult,
DataRecord,
DataRecordValue,
DTTM_ALIAS,
ensureIsArray,
GenericDataType,
NumberFormatter,
TimeFormatter,
TimeseriesDataRecord,
} from '@superset-ui/core';
import { format, LegendComponentOption, SeriesOption } from 'echarts';
import { NULL_STRING, TIMESERIES_CONSTANTS } from '../constants';
@@ -36,37 +36,40 @@ function isDefined<T>(value: T | undefined | null): boolean {
return value !== undefined && value !== null;
}
export function extractTimeseriesSeries(
data: TimeseriesDataRecord[],
opts: { fillNeighborValue?: number } = {},
export function extractSeries(
data: DataRecord[],
opts: {
fillNeighborValue?: number;
xAxis?: string;
removeNulls?: boolean;
} = {},
): SeriesOption[] {
const { fillNeighborValue } = opts;
const { fillNeighborValue, xAxis = DTTM_ALIAS, removeNulls = false } = opts;
if (data.length === 0) return [];
const rows: TimeseriesDataRecord[] = data.map(datum => ({
const rows: DataRecord[] = data.map(datum => ({
...datum,
__timestamp:
datum.__timestamp || datum.__timestamp === 0
? new Date(datum.__timestamp)
: null,
[xAxis]: datum[xAxis],
}));
return Object.keys(rows[0])
.filter(key => key !== '__timestamp')
.filter(key => key !== xAxis && key !== DTTM_ALIAS)
.map(key => ({
id: key,
name: key,
data: rows.map((row, idx) => {
const isNextToDefinedValue =
isDefined(rows[idx - 1]?.[key]) || isDefined(rows[idx + 1]?.[key]);
return [
row.__timestamp,
!isDefined(row[key]) &&
isNextToDefinedValue &&
fillNeighborValue !== undefined
? fillNeighborValue
: row[key],
];
}),
data: rows
.map((row, idx) => {
const isNextToDefinedValue =
isDefined(rows[idx - 1]?.[key]) || isDefined(rows[idx + 1]?.[key]);
return [
row[xAxis],
!isDefined(row[key]) &&
isNextToDefinedValue &&
fillNeighborValue !== undefined
? fillNeighborValue
: row[key],
];
})
.filter(obs => !removeNulls || (obs[0] !== null && obs[1] !== null)),
}));
}
@@ -102,7 +105,10 @@ export function formatSeriesName(
export const getColtypesMapping = ({
coltypes = [],
colnames = [],
}: ChartDataResponseResult): Record<string, GenericDataType> =>
}: Pick<ChartDataResponseResult, 'coltypes' | 'colnames'>): Record<
string,
GenericDataType
> =>
colnames.reduce(
(accumulator, item, index) => ({ ...accumulator, [item]: coltypes[index] }),
{},
@@ -119,7 +125,7 @@ export function extractGroupbyLabel({
groupby?: string[] | null;
numberFormatter?: NumberFormatter;
timeFormatter?: TimeFormatter;
coltypeMapping: Record<string, GenericDataType>;
coltypeMapping?: Record<string, GenericDataType>;
}): string {
return ensureIsArray(groupby)
.map(val =>