diff --git a/docs/gallery.rst b/docs/gallery.rst index e25cb19287a84..f0c7dfaec5908 100644 --- a/docs/gallery.rst +++ b/docs/gallery.rst @@ -49,6 +49,9 @@ Gallery .. image:: _static/img/viz_thumbnails/big_number_total.png :scale: 25 % +.. image:: _static/img/viz_thumbnails/bullet.png + :scale: 25 % + .. image:: _static/img/viz_thumbnails/dist_bar.png :scale: 25 % diff --git a/superset/assets/images/viz_thumbnails/bullet.png b/superset/assets/images/viz_thumbnails/bullet.png new file mode 100644 index 0000000000000..7a66a41c7af6b Binary files /dev/null and b/superset/assets/images/viz_thumbnails/bullet.png differ diff --git a/superset/assets/javascripts/explorev2/stores/fields.js b/superset/assets/javascripts/explorev2/stores/fields.js index af02d5b40c15f..77d2250351a45 100644 --- a/superset/assets/javascripts/explorev2/stores/fields.js +++ b/superset/assets/javascripts/explorev2/stores/fields.js @@ -983,6 +983,48 @@ export const fields = { ], description: 'The color for points and clusters in RGB', }, + + ranges: { + type: 'TextField', + label: 'Ranges', + default: '', + description: 'Ranges to highlight with shading', + }, + + range_labels: { + type: 'TextField', + label: 'Range labels', + default: '', + description: 'Labels for the ranges', + }, + + markers: { + type: 'TextField', + label: 'Markers', + default: '', + description: 'List of values to mark with triangles', + }, + + marker_labels: { + type: 'TextField', + label: 'Marker labels', + default: '', + description: 'Labels for the markers', + }, + + marker_lines: { + type: 'TextField', + label: 'Marker lines', + default: '', + description: 'List of values to mark with lines', + }, + + marker_line_labels: { + type: 'TextField', + label: 'Marker line labels', + default: '', + description: 'Labels for the marker lines', + }, }; export default fields; diff --git a/superset/assets/javascripts/explorev2/stores/store.js b/superset/assets/javascripts/explorev2/stores/store.js index 70a82b89a15cc..240ec1df880c2 100644 --- a/superset/assets/javascripts/explorev2/stores/store.js +++ b/superset/assets/javascripts/explorev2/stores/store.js @@ -49,3 +49,8 @@ export function initialState(vizType = 'table', datasourceType = 'table') { }; } +// Control Panel fields that re-render chart without need for 'Query button' +export const autoQueryFields = [ + 'datasource', + 'viz_type', +]; diff --git a/superset/assets/javascripts/explorev2/stores/visTypes.js b/superset/assets/javascripts/explorev2/stores/visTypes.js index 88130c4a2ab6d..75ce9b2fb03c9 100644 --- a/superset/assets/javascripts/explorev2/stores/visTypes.js +++ b/superset/assets/javascripts/explorev2/stores/visTypes.js @@ -354,6 +354,22 @@ const visTypes = { ], }, + bullet: { + label: 'Bullet Chart', + requiresTime: false, + controlPanelSections: [ + { + label: null, + fieldSetRows: [ + ['metric'], + ['ranges', 'range_labels'], + ['markers', 'marker_labels'], + ['marker_lines', 'marker_line_labels'], + ], + }, + ], + }, + big_number: { label: 'Big Number with Trendline', controlPanelSections: [ diff --git a/superset/assets/visualizations/main.js b/superset/assets/visualizations/main.js index e94046ce3ec07..ab2e265662300 100644 --- a/superset/assets/visualizations/main.js +++ b/superset/assets/visualizations/main.js @@ -6,6 +6,7 @@ const vizMap = { big_number_total: require('./big_number.js'), box_plot: require('./nvd3_vis.js'), bubble: require('./nvd3_vis.js'), + bullet: require('./nvd3_vis.js'), cal_heatmap: require('./cal_heatmap.js'), compare: require('./nvd3_vis.js'), directed_force: require('./directed_force.js'), diff --git a/superset/assets/visualizations/nvd3_vis.js b/superset/assets/visualizations/nvd3_vis.js index dfa1abcdb812c..ba7bbfcab0a67 100644 --- a/superset/assets/visualizations/nvd3_vis.js +++ b/superset/assets/visualizations/nvd3_vis.js @@ -252,6 +252,10 @@ function nvd3Vis(slice) { chart.maxBoxWidth(75); // prevent boxes from being incredibly wide break; + case 'bullet': + chart = nv.models.bulletChart(); + break; + default: throw new Error('Unrecognized visualization for nvd3' + vizType); } @@ -261,6 +265,9 @@ function nvd3Vis(slice) { } let height = slice.height() - 15; + if (vizType === 'bullet') { + height = Math.min(height, 50); + } chart.height(height); slice.container.css('height', height + 'px'); @@ -309,7 +316,9 @@ function nvd3Vis(slice) { chart.y2Axis.tickFormat(d3.format(fd.y_axis_format)); } } - chart.color((d) => category21(d[colorKey])); + if (vizType !== 'bullet') { + chart.color((d) => category21(d[colorKey])); + } if (fd.x_axis_label && fd.x_axis_label !== '' && chart.xAxis) { let distance = 0; diff --git a/superset/forms.py b/superset/forms.py index aa9ac038d0e08..805cb47b727da 100755 --- a/superset/forms.py +++ b/superset/forms.py @@ -987,6 +987,36 @@ def __init__(self, viz): ], "description": _("The color for points and clusters in RGB") }), + 'ranges': (TextField, { + "label": _("Ranges"), + "default": "", + "description": _("Ranges to highlight with shading") + }), + 'range_labels': (TextField, { + "label": _("Range labels"), + "default": "", + "description": _("Labels for the ranges") + }), + 'markers': (TextField, { + "label": _("Markers"), + "default": "", + "description": _("List of values to mark with triangles") + }), + 'marker_labels': (TextField, { + "label": _("Marker labels"), + "default": "", + "description": _("Labels for the markers") + }), + 'marker_lines': (TextField, { + "label": _("Marker lines"), + "default": "", + "description": _("List of values to mark with lines") + }), + 'marker_line_labels': (TextField, { + "label": _("Marker line labels"), + "default": "", + "description": _("Labels for the marker lines") + }), } # Override default arguments with form overrides diff --git a/superset/viz.py b/superset/viz.py index 6f0a4ba1a9826..2c142c443df12 100755 --- a/superset/viz.py +++ b/superset/viz.py @@ -896,6 +896,69 @@ def get_data(self): return chart_data +class BulletViz(NVD3Viz): + + """Based on the NVD3 bullet chart""" + + viz_type = "bullet" + verbose_name = _("Bullet Chart") + is_timeseries = False + fieldsets = ({ + 'label': None, + 'fields': ( + 'metric', + 'ranges', 'range_labels', + 'markers', 'marker_labels', + 'marker_lines', 'marker_line_labels', + ) + },) + + def query_obj(self): + form_data = self.form_data + d = super(BulletViz, self).query_obj() + self.metric = form_data.get('metric') + + def as_strings(field): + value = form_data.get(field) + return value.split(',') if value else [] + + def as_floats(field): + return [float(x) for x in as_strings(field)] + + self.ranges = as_floats('ranges') + self.range_labels = as_strings('range_labels') + self.markers = as_floats('markers') + self.marker_labels = as_strings('marker_labels') + self.marker_lines = as_floats('marker_lines') + self.marker_line_labels = as_strings('marker_line_labels') + + d['metrics'] = [ + self.metric, + ] + if not self.metric: + raise Exception("Pick a metric to display") + return d + + def get_df(self, query_obj=None): + df = super(BulletViz, self).get_df(query_obj) + df = df.fillna(0) + df['metric'] = df[[self.metric]] + return df + + def get_data(self): + df = self.get_df() + values = df['metric'].values + return { + 'measures': values.tolist(), + 'ranges': self.ranges or [0, values.max() * 1.1], + 'rangeLabels': self.range_labels or None, + 'markers': self.markers or None, + 'markerLabels': self.marker_labels or None, + 'markerLines': self.marker_lines or None, + 'markerLineLabels': self.marker_line_labels or None, + } + + class BigNumberViz(BaseViz): """Put emphasis on a single metric with this big number viz""" @@ -2008,6 +2071,7 @@ def get_data(self): DistributionBarViz, DistributionPieViz, BubbleViz, + BulletViz, MarkupViz, WordCloudViz, BigNumberViz,