From 413585448e660cd5421d52e4f510d5de57e92d42 Mon Sep 17 00:00:00 2001 From: Ariel Shemtov Date: Fri, 2 Mar 2018 13:45:46 -0800 Subject: [PATCH] Superset issue #4512: fixing histogram (#4513) * fixing histogram axes and colors, adding normalized and x-axis label options * adding y-axis label option --- .../javascripts/explore/stores/controls.jsx | 8 ++ .../javascripts/explore/stores/visTypes.js | 2 + superset/assets/visualizations/histogram.js | 91 ++++++++++--------- 3 files changed, 57 insertions(+), 44 deletions(-) diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx index 5e2c44e839d7a..1a5b554297627 100644 --- a/superset/assets/javascripts/explore/stores/controls.jsx +++ b/superset/assets/javascripts/explore/stores/controls.jsx @@ -1985,5 +1985,13 @@ export const controls = { description: t('Whether to fill the objects'), default: false, }, + + normalized: { + type: 'CheckboxControl', + label: t('Normalized'), + renderTrigger: true, + description: t('Whether to normalize the histogram'), + default: false, + }, }; export default controls; diff --git a/superset/assets/javascripts/explore/stores/visTypes.js b/superset/assets/javascripts/explore/stores/visTypes.js index df00bc5da5a79..a4ffe4d22e97b 100644 --- a/superset/assets/javascripts/explore/stores/visTypes.js +++ b/superset/assets/javascripts/explore/stores/visTypes.js @@ -1110,6 +1110,8 @@ export const visTypes = { controlSetRows: [ ['color_scheme'], ['link_length'], + ['x_axis_label', 'y_axis_label'], + ['normalized'], ], }, ], diff --git a/superset/assets/visualizations/histogram.js b/superset/assets/visualizations/histogram.js index b5bbf0951946b..b4bf6fcc746dd 100644 --- a/superset/assets/visualizations/histogram.js +++ b/superset/assets/visualizations/histogram.js @@ -4,40 +4,54 @@ import { getColorFromScheme } from '../javascripts/modules/colors'; require('./histogram.css'); function histogram(slice, payload) { + const data = payload.data; const div = d3.select(slice.selector); - const draw = function (data, numBins) { + const numBins = Number(slice.formData.link_length) || 10; + const normalized = slice.formData.normalized; + const xAxisLabel = slice.formData.x_axis_label; + const yAxisLabel = slice.formData.y_axis_label; + + const draw = function () { // Set Margins + const left = yAxisLabel ? 70 : 50; const margin = { top: 50, right: 10, bottom: 20, - left: 50, + left, }; const navBarHeight = 36; const navBarBuffer = 10; const width = slice.width() - margin.left - margin.right; const height = slice.height() - margin.top - margin.bottom - navBarHeight - navBarBuffer; + // set number of ticks + const maxTicks = 20; + const numTicks = d3.min([maxTicks, numBins]); + // Set Histogram objects - const formatNumber = d3.format(',.0f'); - const formatTicks = d3.format(',.00f'); - const x = d3.scale.ordinal(); + const x = d3.scale.linear(); const y = d3.scale.linear(); const xAxis = d3.svg.axis() .scale(x) .orient('bottom') - .ticks(numBins) - .tickFormat(formatTicks); + .ticks(numTicks, 's'); const yAxis = d3.svg.axis() .scale(y) .orient('left') - .ticks(numBins); + .ticks(numTicks, 's'); // Calculate bins for the data - const bins = d3.layout.histogram().bins(numBins)(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 - x.domain(bins.map(d => d.x)) - .rangeRoundBands([0, width], 0.1); + const max = d3.max(data); + const min = d3.min(data); + x.domain([min, max]) + .range([0, width], 0.1); // Set the y-values y.domain([0, d3.max(bins, d => d.y)]) .range([height, 0]); @@ -72,42 +86,13 @@ function histogram(slice, payload) { bar.enter().append('rect'); bar.exit().remove(); // Set the Height and Width for each bar - bar.attr('width', x.rangeBand()) + 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', d => getColorFromScheme(d.length, slice.formData.color_scheme)) + .style('fill', getColorFromScheme(1, slice.formData.color_scheme)) .order(); - // Find maximum length to position the ticks on top of the bar correctly - const maxLength = d3.max(bins, d => d.length); - function textAboveBar(d) { - return d.length / maxLength < 0.1; - } - - // Add a bar text to each bar in the histogram - svg.selectAll('.bartext') - .data(bins) - .enter() - .append('text') - .attr('dy', '.75em') - .attr('y', function (d) { - let padding = 0.0; - if (textAboveBar(d)) { - padding = 12.0; - } else { - padding = -8.0; - } - return y(d.y) - padding; - }) - .attr('x', d => x(d.x) + (x.rangeBand() / 2)) - .attr('text-anchor', 'middle') - .attr('font-weight', 'bold') - .attr('font-size', '15px') - .text(d => formatNumber(d.y)) - .attr('fill', d => textAboveBar(d) ? 'black' : 'white') - .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - // Update the x-axis svg.append('g') .attr('class', 'axis') @@ -124,11 +109,29 @@ function histogram(slice, payload) { .selectAll('g') .filter(function (d) { return d; }) .classed('minor', true); + + // add axis labels if passed + if (xAxisLabel) { + svg.append('text') + .attr('transform', + 'translate(' + ((width + margin.left) / 2) + ' ,' + + (height + margin.top + 50) + ')') + .style('text-anchor', 'middle') + .text(xAxisLabel); + } + if (yAxisLabel) { + svg.append('text') + .attr('transform', 'rotate(-90)') + .attr('y', '1em') + .attr('x', 0 - (height / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(yAxisLabel); + } }; - const numBins = Number(slice.formData.link_length) || 10; div.selectAll('*').remove(); - draw(payload.data, numBins); + draw(); } module.exports = histogram;