From ecfd9c578949eb06c9037d931a299f06e1d607c9 Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Mon, 30 Apr 2018 09:36:24 -0700 Subject: [PATCH] Heatmap improvements (#4897) * allow option to normalize the color distribution * make bounds work client side (instantaneous) * make more controls instantaneous (cherry picked from commit 3c7feb770af4f7941b3c69f8a03d09c28ab4dc6d) --- superset/assets/src/explore/controls.jsx | 8 +++++++- superset/assets/src/explore/visTypes.js | 12 ++++++------ superset/assets/src/visualizations/heatmap.js | 14 +++++++------- superset/viz.py | 7 ++----- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/superset/assets/src/explore/controls.jsx b/superset/assets/src/explore/controls.jsx index ae964ef03c695..ffe264b547936 100644 --- a/superset/assets/src/explore/controls.jsx +++ b/superset/assets/src/explore/controls.jsx @@ -387,8 +387,10 @@ export const controls = { xscale_interval: { type: 'SelectControl', label: t('XScale Interval'), + renderTrigger: true, choices: formatSelectOptionsForRange(1, 50), default: '1', + clearable: false, description: t('Number of steps to take between ticks when ' + 'displaying the X scale'), }, @@ -397,7 +399,9 @@ export const controls = { type: 'SelectControl', label: t('YScale Interval'), choices: formatSelectOptionsForRange(1, 50), - default: null, + default: '1', + clearable: false, + renderTrigger: true, description: t('Number of steps to take between ticks when ' + 'displaying the Y scale'), }, @@ -726,6 +730,7 @@ export const controls = { bottom_margin: { type: 'SelectControl', + clearable: false, freeForm: true, label: t('Bottom Margin'), choices: formatSelectOptions(['auto', 50, 75, 100, 125, 150, 200]), @@ -747,6 +752,7 @@ export const controls = { left_margin: { type: 'SelectControl', freeForm: true, + clearable: false, label: t('Left Margin'), choices: formatSelectOptions(['auto', 50, 75, 100, 125, 150, 200]), default: 'auto', diff --git a/superset/assets/src/explore/visTypes.js b/superset/assets/src/explore/visTypes.js index 3af1f7eeb8203..d05064a6ad0eb 100644 --- a/superset/assets/src/explore/visTypes.js +++ b/superset/assets/src/explore/visTypes.js @@ -1488,6 +1488,7 @@ export const visTypes = { }, { label: t('Heatmap Options'), + expanded: true, controlSetRows: [ ['linear_color_scheme'], ['xscale_interval', 'yscale_interval'], @@ -1495,7 +1496,7 @@ export const visTypes = { ['left_margin', 'bottom_margin'], ['y_axis_bounds', 'y_axis_format'], ['show_legend', 'show_perc'], - ['show_values'], + ['show_values', 'normalized'], ['sort_x_axis', 'sort_y_axis'], ], }, @@ -1507,14 +1508,13 @@ export const visTypes = { all_columns_y: { validators: [v.nonEmpty], }, + normalized: t('Whether to apply a normal distribution based on rank on the color scale'), y_axis_bounds: { label: t('Value bounds'), - renderTrigger: false, - description: ( + renderTrigger: true, + description: t( 'Hard value bounds applied for color coding. Is only relevant ' + - 'and applied when the normalization is applied against the whole ' + - 'heatmap.' - ), + 'and applied when the normalization is applied against the whole heatmap.'), }, y_axis_format: { label: t('Value Format'), diff --git a/superset/assets/src/visualizations/heatmap.js b/superset/assets/src/visualizations/heatmap.js index a8d3ccf694b27..7d471d5d809d3 100644 --- a/superset/assets/src/visualizations/heatmap.js +++ b/superset/assets/src/visualizations/heatmap.js @@ -92,7 +92,7 @@ function heatmapVis(slice, payload) { const height = slice.height(); const hmWidth = width - (margin.left + margin.right); const hmHeight = height - (margin.bottom + margin.top); - const fp = d3.format('.3p'); + const fp = d3.format('.2%'); const xScale = ordScale('x', null, fd.sort_x_axis); const yScale = ordScale('y', null, fd.sort_y_axis); @@ -102,7 +102,9 @@ function heatmapVis(slice, payload) { const Y = 1; const heatmapDim = [xRbScale.domain().length, yRbScale.domain().length]; - const color = colorScalerFactory(fd.linear_color_scheme); + const minBound = fd.y_axis_bounds[0] || 0; + const maxBound = fd.y_axis_bounds[1] || 1; + const colorScaler = colorScalerFactory(fd.linear_color_scheme, null, null, [minBound, maxBound]); const scale = [ d3.scale.linear() @@ -149,11 +151,9 @@ function heatmapVis(slice, payload) { } if (fd.show_legend) { - const legendScaler = colorScalerFactory( - fd.linear_color_scheme, null, null, payload.data.extents); const colorLegend = d3.legend.color() .labelFormat(valueFormatter) - .scale(legendScaler) + .scale(colorScaler) .shapePadding(0) .cells(50) .shapeWidth(10) @@ -183,7 +183,7 @@ function heatmapVis(slice, payload) { s += '
' + fd.all_columns_y + ': ' + obj.y + '
'; s += '
' + fd.metric + ': ' + valueFormatter(obj.v) + '
'; if (fd.show_perc) { - s += '
%: ' + fp(obj.perc) + '
'; + s += '
%: ' + fp(fd.normalized ? obj.rank : obj.perc) + '
'; } tip.style('display', null); } else { @@ -246,7 +246,7 @@ function heatmapVis(slice, payload) { const image = context.createImageData(heatmapDim[0], heatmapDim[1]); const pixs = {}; data.forEach((d) => { - const c = d3.rgb(color(d.perc)); + const c = d3.rgb(colorScaler(fd.normalized ? d.rank : d.perc)); const x = xScale(d.x); const y = yScale(d.y); pixs[x + (y * xScale.domain().length)] = c; diff --git a/superset/viz.py b/superset/viz.py index 5e6042dc46730..6c5a2e8757313 100644 --- a/superset/viz.py +++ b/superset/viz.py @@ -1803,11 +1803,6 @@ def get_data(self, df): overall = False max_ = df.v.max() min_ = df.v.min() - bounds = fd.get('y_axis_bounds') - if bounds and bounds[0] is not None: - min_ = bounds[0] - if bounds and bounds[1] is not None: - max_ = bounds[1] if norm == 'heatmap': overall = True else: @@ -1819,8 +1814,10 @@ def get_data(self, df): gb.apply( lambda x: (x.v - x.v.min()) / (x.v.max() - x.v.min())) ) + df['rank'] = gb.apply(lambda x: x.v.rank(pct=True)) if overall: df['perc'] = (df.v - min_) / (max_ - min_) + df['rank'] = df.v.rank(pct=True) return { 'records': df.to_dict(orient='records'), 'extents': [min_, max_],