Skip to content

Commit

Permalink
New time_pivot visualization (#3941)
Browse files Browse the repository at this point in the history
* New time_pivot visualization

* Minor tweaks

* Addressing comments
  • Loading branch information
mistercrunch authored Dec 7, 2017
1 parent 5ee70b2 commit 5bc581f
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 7 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions superset/assets/javascripts/explore/components/Control.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const propTypes = {
label: PropTypes.string.isRequired,
choices: PropTypes.arrayOf(PropTypes.array),
description: PropTypes.string,
tooltipOnClick: PropTypes.func,
places: PropTypes.number,
validators: PropTypes.array,
validationErrors: PropTypes.array,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const propTypes = {
leftNode: PropTypes.node,
onClick: PropTypes.func,
hovered: PropTypes.bool,
tooltipOnClick: PropTypes.func,
};

const defaultProps = {
Expand All @@ -32,6 +33,7 @@ export default class ControlHeader extends React.Component {
label={t('description')}
tooltip={this.props.description}
placement="top"
onClick={this.props.tooltipOnClick}
/>
{' '}
</span>
Expand Down
24 changes: 24 additions & 0 deletions superset/assets/javascripts/explore/stores/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,30 @@ export const controls = {
'to find in the [country] column'),
},

freq: {
type: 'SelectControl',
label: t('Frequency'),
default: 'W-MON',
freeForm: true,
clearable: false,
choices: [
['AS', 'Year (freq=AS)'],
['52W-MON', '52 weeks starting Monday (freq=52W-MON)'],
['W-SUN', '1 week starting Sunday (freq=W-SUN)'],
['W-MON', '1 week starting Monday (freq=W-MON)'],
['D', 'Day (freq=D)'],
['4W-MON', '4 weeks (freq=4W-MON)'],
],
description: t(
`The periodicity over which to pivot time. Users can provide
"Pandas" offset alias.
Click on the info bubble for more details on accepted "freq" expressions.`),
tooltipOnClick: () => {
window.open(
'https://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases');
},
},

groupby: groupByControl,
dimension: {
...groupByControl,
Expand Down
44 changes: 44 additions & 0 deletions superset/assets/javascripts/explore/stores/visTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,50 @@ export const visTypes = {
},
},

time_pivot: {
label: t('Time Series - Periodicity Pivot'),
showOnExplore: true,
requiresTime: true,
controlPanelSections: [
{
label: t('Query'),
expanded: true,
controlSetRows: [
['metric', 'freq'],
],
},
{
label: t('Chart Options'),
expanded: true,
controlSetRows: [
['show_legend', 'line_interpolation'],
['color_picker', null],
],
},
{
label: t('X Axis'),
controlSetRows: [
['x_axis_label', 'bottom_margin'],
['x_axis_showminmax', 'x_axis_format'],
],
},
{
label: t('Y Axis'),
controlSetRows: [
['y_axis_label', 'left_margin'],
['y_axis_showminmax', 'y_log_scale'],
['y_axis_format', 'y_axis_bounds'],
],
},
],
controlOverrides: {
x_axis_format: {
choices: D3_TIME_FORMAT_OPTIONS,
default: 'smart_date',
},
},
},

dual_line: {
label: t('Dual Axis Line Chart'),
requiresTime: true,
Expand Down
1 change: 1 addition & 0 deletions superset/assets/javascripts/modules/colors.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import d3 from 'd3';

export const brandColor = '#00A699';
export const colorPrimary = { r: 0, g: 122, b: 135, a: 1 };

// Color related utility functions go in this object
export const bnbColors = [
Expand Down
1 change: 1 addition & 0 deletions superset/assets/visualizations/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const vizMap = {
horizon: require('./horizon.js'),
iframe: require('./iframe.js'),
line: require('./nvd3_vis.js'),
time_pivot: require('./nvd3_vis.js'),
mapbox: require('./mapbox.jsx'),
markup: require('./markup.js'),
para: require('./parallel_coordinates.js'),
Expand Down
20 changes: 18 additions & 2 deletions superset/assets/visualizations/nvd3_vis.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ function nvd3Vis(slice, payload) {
chart.xAxis.staggerLabels(false);
break;

case 'time_pivot':
chart = nv.models.lineChart();
chart.xScale(d3.time.scale.utc());
chart.interpolate(fd.line_interpolation);
chart.xAxis.staggerLabels(false);
break;

case 'dual_line':
chart = nv.models.multiChart();
chart.interpolate('linear');
Expand Down Expand Up @@ -337,7 +344,7 @@ function nvd3Vis(slice, payload) {
chart.xScale(d3.scale.log());
}
const isTimeSeries = [
'line', 'dual_line', 'area', 'compare', 'bar'].indexOf(vizType) >= 0;
'line', 'dual_line', 'area', 'compare', 'bar', 'time_pivot'].indexOf(vizType) >= 0;
// if x axis format is a date format, rotate label 90 degrees
if (isTimeSeries) {
chart.xAxis.rotateLabels(45);
Expand Down Expand Up @@ -375,7 +382,16 @@ function nvd3Vis(slice, payload) {
setAxisShowMaxMin(chart.yAxis, fd.y_axis_showminmax);
setAxisShowMaxMin(chart.y2Axis, fd.y_axis_showminmax);

if (vizType !== 'bullet') {
if (vizType === 'time_pivot') {
chart.color((d) => {
const c = fd.color_picker;
let alpha = 1;
if (d.rank > 0) {
alpha = d.perc * 0.5;
}
return `rgba(${c.r}, ${c.g}, ${c.b}, ${alpha})`;
});
} else if (vizType !== 'bullet') {
chart.color(d => getColorFromScheme(d[colorKey], fd.color_scheme));
}
if ((vizType === 'line' || vizType === 'area') && fd.rich_tooltip) {
Expand Down
61 changes: 56 additions & 5 deletions superset/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from markdown import markdown
import numpy as np
import pandas as pd
from pandas.tseries.frequencies import to_offset
import simplejson as json
from six import PY3, string_types
from six.moves import reduce
Expand Down Expand Up @@ -947,14 +948,23 @@ def to_series(self, df, classed='', title_suffix=''):
elif title_suffix and isinstance(series_title, (list, tuple)):
series_title = series_title + (title_suffix,)

values = []
for ds in df.index:
if ds in ys:
d = {
'x': ds,
'y': ys[ds],
}
else:
d = {}
values.append(d)

d = {
'key': series_title,
'classed': classed,
'values': [
{'x': ds, 'y': ys[ds] if ds in ys else None}
for ds in df.index
],
'values': values,
}
if classed:
d['classed'] = classed
chart_data.append(d)
return chart_data

Expand Down Expand Up @@ -1136,6 +1146,47 @@ class NVD3TimeSeriesBarViz(NVD3TimeSeriesViz):
verbose_name = _('Time Series - Bar Chart')


class NVD3TimePivotViz(NVD3TimeSeriesViz):

"""Time Series - Periodicity Pivot"""

viz_type = 'time_pivot'
sort_series = True
verbose_name = _('Time Series - Period Pivot')

def query_obj(self):
d = super(NVD3TimePivotViz, self).query_obj()
d['metrics'] = [self.form_data.get('metric')]
return d

def get_data(self, df):
fd = self.form_data
df = self.process_data(df)
freq = to_offset(fd.get('freq'))
freq.normalize = True
df[DTTM_ALIAS] = df.index.map(freq.rollback)
df['ranked'] = df[DTTM_ALIAS].rank(method='dense', ascending=False) - 1
df.ranked = df.ranked.map(int)
df['series'] = '-' + df.ranked.map(str)
df['series'] = df['series'].str.replace('-0', 'current')
rank_lookup = {
row['series']: row['ranked']
for row in df.to_dict(orient='records')
}
max_ts = df[DTTM_ALIAS].max()
max_rank = df['ranked'].max()
df[DTTM_ALIAS] = df.index + (max_ts - df[DTTM_ALIAS])
df = df.pivot_table(
index=DTTM_ALIAS,
columns='series',
values=fd.get('metric'))
chart_data = self.to_series(df)
for serie in chart_data:
serie['rank'] = rank_lookup[serie['key']]
serie['perc'] = 1 - (serie['rank'] / (max_rank + 1))
return chart_data


class NVD3CompareTimeSeriesViz(NVD3TimeSeriesViz):

"""A line chart component where you can compare the % change over time"""
Expand Down

0 comments on commit 5bc581f

Please sign in to comment.