Skip to content

Commit

Permalink
Visualization for multiple line charts (#4819)
Browse files Browse the repository at this point in the history
* Initial test

* Save

* Working version

* Use since/until from payload

* Option to prefix metric name

* Rename LineMultiLayer to MultiLineViz

* Add more styles

* Explicit nulls

* Add more x controls

* Refactor to reuse nvd3_vis

* Fix x ticks

* Fix spacing

* Fix for druid datasource

* Rename file

* Small fixes and cleanup

* Fix margins

* Add proper thumbnails

* Align yaxis1 and yaxis2 ticks

* Improve code

* Trigger tests

* Move file

* Small fixes plus example

* Fix unit test

* Remove SQL and Filter sections
  • Loading branch information
betodealmeida authored and mistercrunch committed May 22, 2018
1 parent a746fce commit 459cb70
Show file tree
Hide file tree
Showing 13 changed files with 342 additions and 18 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@ class ExploreViewContainer extends React.Component {
this.props.actions.resetControls();
this.props.actions.triggerQuery(true, this.props.chart.chartKey);
}
if (np.controls.datasource.value !== this.props.controls.datasource.value) {
if (
np.controls.datasource && (
this.props.controls.datasource == null ||
np.controls.datasource.value !== this.props.controls.datasource.value
)
) {
this.props.actions.fetchDatasourceMetadata(np.form_data.datasource, true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const propTypes = {
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
value: PropTypes.string.isRequired,
datasource: PropTypes.object.isRequired,
datasource: PropTypes.object,
};

const defaultProps = {
Expand Down
43 changes: 43 additions & 0 deletions superset/assets/src/explore/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2068,6 +2068,49 @@ export const controls = {
description: t('The width of the lines'),
},

line_charts: {
type: 'SelectAsyncControl',
multi: true,
label: t('Line charts'),
validators: [v.nonEmpty],
default: [],
description: t('Pick a set of line charts to layer on top of one another'),
dataEndpoint: '/sliceasync/api/read?_flt_0_viz_type=line&_flt_7_viz_type=line_multi',
placeholder: t('Select charts'),
onAsyncErrorMessage: t('Error while fetching charts'),
mutator: (data) => {
if (!data || !data.result) {
return [];
}
return data.result.map(o => ({ value: o.id, label: o.slice_name }));
},
},

line_charts_2: {
type: 'SelectAsyncControl',
multi: true,
label: t('Right Axis chart(s)'),
validators: [],
default: [],
description: t('Choose one or more charts for right axis'),
dataEndpoint: '/sliceasync/api/read?_flt_0_viz_type=line&_flt_7_viz_type=line_multi',
placeholder: t('Select charts'),
onAsyncErrorMessage: t('Error while fetching charts'),
mutator: (data) => {
if (!data || !data.result) {
return [];
}
return data.result.map(o => ({ value: o.id, label: o.slice_name }));
},
},

prefix_metric_with_slice_name: {
type: 'CheckboxControl',
label: t('Prefix metric name with slice name'),
default: false,
renderTrigger: true,
},

reverse_long_lat: {
type: 'CheckboxControl',
label: t('Reverse Lat & Long'),
Expand Down
99 changes: 95 additions & 4 deletions superset/assets/src/explore/visTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,82 @@ export const visTypes = {
},
},

line_multi: {
label: t('Time Series - Multiple Line Charts'),
showOnExplore: true,
requiresTime: true,
controlPanelSections: [
{
label: t('Chart Options'),
expanded: true,
controlSetRows: [
['color_scheme'],
['prefix_metric_with_slice_name', null],
['show_legend', 'show_markers'],
['line_interpolation', null],
],
},
{
label: t('X Axis'),
expanded: true,
controlSetRows: [
['x_axis_label', 'bottom_margin'],
['x_ticks_layout', 'x_axis_format'],
['x_axis_showminmax', null],
],
},
{
label: t('Y Axis 1'),
expanded: true,
controlSetRows: [
['line_charts', 'y_axis_format'],
],
},
{
label: t('Y Axis 2'),
expanded: false,
controlSetRows: [
['line_charts_2', 'y_axis_2_format'],
],
},
sections.annotations,
],
controlOverrides: {
line_charts: {
label: t('Left Axis chart(s)'),
description: t('Choose one or more charts for left axis'),
},
y_axis_format: {
label: t('Left Axis Format'),
},
x_axis_format: {
choices: D3_TIME_FORMAT_OPTIONS,
default: 'smart_date',
},
},
sectionOverrides: {
sqlClause: [],
filters: [[]],
datasourceAndVizType: {
label: t('Chart Type'),
controlSetRows: [
['viz_type'],
['slice_id', 'cache_timeout'],
],
},
sqlaTimeSeries: {
controlSetRows: [
['since', 'until'],
],
},
druidTimeSeries: {
controlSetRows: [
['since', 'until'],
],
},
},
},

time_pivot: {
label: t('Time Series - Periodicity Pivot'),
showOnExplore: true,
Expand Down Expand Up @@ -1731,11 +1807,26 @@ function adhocFilterEnabled(viz) {

export function sectionsToRender(vizType, datasourceType) {
const viz = visTypes[vizType];

const sectionsCopy = { ...sections };
if (viz.sectionOverrides) {
Object.entries(viz.sectionOverrides).forEach(([section, overrides]) => {
if (typeof overrides === 'object' && overrides.constructor === Object) {
sectionsCopy[section] = {
...sectionsCopy[section],
...overrides,
};
} else {
sectionsCopy[section] = overrides;
}
});
}

return [].concat(
sections.datasourceAndVizType,
datasourceType === 'table' ? sections.sqlaTimeSeries : sections.druidTimeSeries,
sectionsCopy.datasourceAndVizType,
datasourceType === 'table' ? sectionsCopy.sqlaTimeSeries : sectionsCopy.druidTimeSeries,
viz.controlPanelSections,
!adhocFilterEnabled(viz) && (datasourceType === 'table' ? sections.sqlClause : []),
!adhocFilterEnabled(viz) && (datasourceType === 'table' ? sections.filters[0] : sections.filters),
!adhocFilterEnabled(viz) && (datasourceType === 'table' ? sectionsCopy.sqlClause : []),
!adhocFilterEnabled(viz) && (datasourceType === 'table' ? sectionsCopy.filters[0] : sectionsCopy.filters),
).filter(section => section);
}
3 changes: 3 additions & 0 deletions superset/assets/src/visualizations/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable global-require */
import nvd3Vis from './nvd3_vis';
import lineMulti from './line_multi';

// You ***should*** use these to reference viz_types in code
export const VIZ_TYPES = {
Expand All @@ -21,6 +22,7 @@ export const VIZ_TYPES = {
horizon: 'horizon',
iframe: 'iframe',
line: 'line',
line_multi: 'line_multi',
mapbox: 'mapbox',
markup: 'markup',
para: 'para',
Expand Down Expand Up @@ -71,6 +73,7 @@ const vizMap = {
[VIZ_TYPES.horizon]: require('./horizon.js'),
[VIZ_TYPES.iframe]: require('./iframe.js'),
[VIZ_TYPES.line]: nvd3Vis,
[VIZ_TYPES.line_multi]: lineMulti,
[VIZ_TYPES.time_pivot]: nvd3Vis,
[VIZ_TYPES.mapbox]: require('./mapbox.jsx'),
[VIZ_TYPES.markup]: require('./markup.js'),
Expand Down
74 changes: 74 additions & 0 deletions superset/assets/src/visualizations/line_multi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import nvd3Vis from './nvd3_vis';
import { getExploreLongUrl } from '../explore/exploreUtils';


export default function lineMulti(slice, payload) {
/*
* Show multiple line charts
*
* This visualization works by fetching the data from each of the saved
* charts, building the payload data and passing it along to nvd3Vis.
*/
const fd = slice.formData;

// fetch data from all the charts
const promises = [];
const subslices = [
...payload.data.slices.axis1.map(subslice => [1, subslice]),
...payload.data.slices.axis2.map(subslice => [2, subslice]),
];
subslices.forEach(([yAxis, subslice]) => {
let filters = subslice.form_data.filters || [];
filters.concat(fd.filters);
if (fd.extra_filters) {
filters = filters.concat(fd.extra_filters);
}
const fdCopy = {
...subslice.form_data,
filters,
since: fd.since,
until: fd.until,
};
const url = getExploreLongUrl(fdCopy, 'json');
promises.push(new Promise((resolve, reject) => {
d3.json(url, (error, response) => {
if (error) {
reject(error);
} else {
const data = [];
response.data.forEach((datum) => {
let key = datum.key;
if (fd.prefix_metric_with_slice_name) {
key = subslice.slice_name + ': ' + key;
}
data.push({ key, values: datum.values, type: fdCopy.viz_type, yAxis });
});
resolve(data);
}
});
}));
});

Promise.all(promises).then((data) => {
const payloadCopy = { ...payload };
payloadCopy.data = [].concat(...data);

// add null values at the edges to fix multiChart bug when series with
// different x values use different y axes
if (fd.line_charts.length && fd.line_charts_2.length) {
let minx = Infinity;
let maxx = -Infinity;
payloadCopy.data.forEach((datum) => {
minx = Math.min(minx, ...datum.values.map(v => v.x));
maxx = Math.max(maxx, ...datum.values.map(v => v.x));
});
// add null values at the edges
payloadCopy.data.forEach((datum) => {
datum.values.push({ x: minx, y: null });
datum.values.push({ x: maxx, y: null });
});
}

nvd3Vis(slice, payloadCopy);
});
}
Loading

0 comments on commit 459cb70

Please sign in to comment.