Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Superset issue #4512: multiple metrics, group by, opacity, legends for histogram #4525

Merged
merged 1 commit into from
Apr 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions superset/assets/javascripts/explore/stores/visTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,7 @@ export const visTypes = {
controlSetRows: [
['all_columns_x'],
['row_limit'],
['groupby'],
],
},
{
Expand All @@ -1111,20 +1112,26 @@ export const visTypes = {
['color_scheme'],
['link_length'],
['x_axis_label', 'y_axis_label'],
['global_opacity'],
['normalized'],
],
},
],
controlOverrides: {
all_columns_x: {
label: t('Numeric Column'),
description: t('Select the numeric column to draw the histogram'),
label: t('Numeric Columns'),
description: t('Select the numeric columns to draw the histogram'),
multi: true,
},
link_length: {
label: t('No of Bins'),
description: t('Select number of bins for the histogram'),
default: 5,
},
global_opacity: {
description: t('Opacity of the bars. Between 0 and 1'),
renderTrigger: true,
},
},
},

Expand Down
79 changes: 60 additions & 19 deletions superset/assets/visualizations/histogram.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import d3 from 'd3';
import nv from 'nvd3';
import { getColorFromScheme } from '../javascripts/modules/colors';

require('./histogram.css');
Expand All @@ -10,6 +11,7 @@ function histogram(slice, payload) {
const normalized = slice.formData.normalized;
const xAxisLabel = slice.formData.x_axis_label;
const yAxisLabel = slice.formData.y_axis_label;
const opacity = slice.formData.global_opacity;

const draw = function () {
// Set Margins
Expand Down Expand Up @@ -40,18 +42,28 @@ function histogram(slice, payload) {
.scale(y)
.orient('left')
.ticks(numTicks, 's');
// Calculate bins for the data
let bins = d3.layout.histogram().bins(numBins)(data);
if (normalized) {
const total = data.length;
bins = bins.map(d => ({ ...d, y: d.y / total }));
}

// Set the x-values
const max = d3.max(data);
const min = d3.min(data);
const max = d3.max(data, d => d3.max(d.values));
const min = d3.min(data, d => d3.min(d.values));
x.domain([min, max])
.range([0, width], 0.1);

// Calculate bins for the data
let bins = [];
data.forEach((d) => {
let b = d3.layout.histogram().bins(numBins)(d.values);
const color = getColorFromScheme(d.key, slice.formData.color_scheme);
const w = d3.max([(x(b[0].dx) - x(0)) - 1, 0]);
const key = d.key;
// normalize if necessary
if (normalized) {
const total = d.values.length;
b = b.map(v => ({ ...v, y: v.y / total }));
}
bins = bins.concat(b.map(v => ({ ...v, color, width: w, key, opacity })));
});

// Set the y-values
y.domain([0, d3.max(bins, d => d.y)])
.range([height, 0]);
Expand Down Expand Up @@ -81,17 +93,38 @@ function histogram(slice, payload) {
svg.attr('width', slice.width())
.attr('height', slice.height());

// Create the bars in the svg
const bar = svg.select('.bars').selectAll('.bar').data(bins);
bar.enter().append('rect');
bar.exit().remove();
// Set the Height and Width for each bar
bar.attr('width', (x(bins[0].dx) - x(0)) - 1)
.attr('x', d => x(d.x))
.attr('y', d => y(d.y))
.attr('height', d => y.range()[0] - y(d.y))
.style('fill', getColorFromScheme(1, slice.formData.color_scheme))
.order();
// make legend
const legend = nv.models.legend()
.color(d => getColorFromScheme(d.key, slice.formData.color_scheme))
.width(width);
const gLegend = gEnter.append('g').attr('class', 'nv-legendWrap')
.attr('transform', 'translate(0,' + (-margin.top) + ')')
.datum(data.map(d => ({ ...d, disabled: false })));

// function to draw bars and legends
function update(selectedBins) {
// Create the bars in the svg
const bar = svg.select('.bars')
.selectAll('rect')
.data(selectedBins, d => d.key + d.x);
// Set the Height and Width for each bar
bar.enter()
.append('rect')
.attr('width', d => d.width)
.attr('x', d => x(d.x))
.style('fill', d => d.color)
.style('fill-opacity', d => d.opacity)
.attr('y', d => y(d.y))
.attr('height', d => y.range()[0] - y(d.y));
bar.exit()
.attr('y', y(0))
.attr('height', 0)
.remove();
// apply legend
gLegend.call(legend);
}

update(bins);

// Update the x-axis
svg.append('g')
Expand All @@ -110,6 +143,14 @@ function histogram(slice, payload) {
.filter(function (d) { return d; })
.classed('minor', true);

// set callback on legend toggle
legend.dispatch.on('stateChange', function (newState) {
const activeKeys = data
.filter((d, i) => !newState.disabled[i])
.map(d => d.key);
update(bins.filter(d => activeKeys.indexOf(d.key) >= 0));
});

// add axis labels if passed
if (xAxisLabel) {
svg.append('text')
Expand Down
27 changes: 22 additions & 5 deletions superset/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from itertools import product
import logging
import math
import re
import traceback
import uuid

Expand Down Expand Up @@ -1322,15 +1323,31 @@ def query_obj(self):
d = super(HistogramViz, self).query_obj()
d['row_limit'] = self.form_data.get(
'row_limit', int(config.get('VIZ_ROW_LIMIT')))
numeric_column = self.form_data.get('all_columns_x')
if numeric_column is None:
raise Exception(_('Must have one numeric column specified'))
d['columns'] = [numeric_column]
numeric_columns = self.form_data.get('all_columns_x')
if numeric_columns is None:
raise Exception(_('Must have at least one numeric column specified'))
self.columns = numeric_columns
d['columns'] = numeric_columns + self.groupby
# override groupby entry to avoid aggregation
d['groupby'] = []
return d

def get_data(self, df):
"""Returns the chart data"""
chart_data = df[df.columns[0]].values.tolist()
chart_data = []
if len(self.groupby) > 0:
groups = df.groupby(self.groupby)
else:
groups = [((), df)]
for keys, data in groups:
if isinstance(keys, str):
keys = (keys,)
# removing undesirable characters
keys = [re.sub(r'\W+', r'_', k) for k in keys]
chart_data.extend([{
'key': '__'.join([c] + keys),
'values': data[c].tolist()}
for c in self.columns])
return chart_data


Expand Down