vlandham closed pull request #3864: WIP: Start of Categorical Scatterplot
URL: https://github.com/apache/incubator-superset/pull/3864
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/superset/assets/javascripts/explore/stores/controls.jsx
b/superset/assets/javascripts/explore/stores/controls.jsx
index e3da06b621..2ce6c29078 100644
--- a/superset/assets/javascripts/explore/stores/controls.jsx
+++ b/superset/assets/javascripts/explore/stores/controls.jsx
@@ -813,6 +813,19 @@ export const controls = {
}),
},
+ shape: {
+ type: 'SelectControl',
+ label: t('Bubble Shape'),
+ default: null,
+ validators: [v.nonEmpty],
+ optionRenderer: m => <MetricOption metric={m} />,
+ valueRenderer: m => <MetricOption metric={m} />,
+ valueKey: 'metric_name',
+ mapStateToProps: state => ({
+ options: (state.datasource) ? state.datasource.metrics : [],
+ }),
+ },
+
url: {
type: 'TextControl',
label: t('URL'),
diff --git a/superset/assets/javascripts/explore/stores/visTypes.js
b/superset/assets/javascripts/explore/stores/visTypes.js
index 94115bb076..1749dba12d 100644
--- a/superset/assets/javascripts/explore/stores/visTypes.js
+++ b/superset/assets/javascripts/explore/stores/visTypes.js
@@ -597,6 +597,50 @@ export const visTypes = {
},
},
+ catscat: {
+ label: t('Categorical Scatterplot'),
+ controlPanelSections: [
+ {
+ label: t('Query'),
+ expanded: true,
+ controlSetRows: [
+ ['series', 'entity'],
+ ],
+ },
+ {
+ label: t('Y Axis'),
+ controlSetRows: [
+ ['marker_lines'],
+ ['y_axis_label', 'bottom_margin'],
+ ['y', 'y_axis_format'],
+ ['y_log_scale', 'y_axis_showminmax'],
+ ],
+ },
+ {
+ label: t('Points'),
+ controlSetRows: [
+ ['shape'],
+ ],
+ },
+ {
+ label: t('Chart Options'),
+ controlSetRows: [
+ ['color_scheme'],
+ ['show_legend', null],
+ ],
+ },
+ ],
+ controlOverrides: {
+ x_axis_format: {
+ default: '.3s',
+ },
+ color_scheme: {
+ renderTrigger: false,
+ },
+ },
+ },
+
+
bullet: {
label: t('Bullet Chart'),
requiresTime: false,
diff --git a/superset/assets/visualizations/catscat.css
b/superset/assets/visualizations/catscat.css
new file mode 100644
index 0000000000..6120433c38
--- /dev/null
+++ b/superset/assets/visualizations/catscat.css
@@ -0,0 +1,3 @@
+.scatcat {
+
+}
diff --git a/superset/assets/visualizations/catscat.js
b/superset/assets/visualizations/catscat.js
new file mode 100644
index 0000000000..0688b80b87
--- /dev/null
+++ b/superset/assets/visualizations/catscat.js
@@ -0,0 +1,111 @@
+import d3 from 'd3';
+import { getColorFromScheme } from '../javascripts/modules/colors';
+import './catscat.css';
+
+function computedProps(props) {
+ const { fd, data, width, height } = props;
+
+ const padding = {
+ top: 20,
+ left: 50,
+ right: 20,
+ bottom: 50,
+ };
+
+ const plotWidth = width - (padding.right + padding.left);
+ const plotHeight = height - (padding.top + padding.bottom);
+
+ const yMin = d3.min(data, d => d3.min(d.values, v => v.y));
+ const yMax = d3.max(data, d => d3.max(d.values, v => v.y));
+ const yScale = d3.scale.linear()
+ .domain([yMin, yMax])
+ .range([plotHeight, 0]);
+
+ const xScale = d3.scale.ordinal()
+ .domain(d3.range(data.length))
+ .rangePoints([0, plotWidth]);
+
+ const shapeValuesAll = data.map(d => d.values.map(v => v.shape));
+ const shapeValues = d3.set([].concat(...shapeValuesAll)).values();
+ const shapeScale = d3.scale.ordinal()
+ .domain(shapeValues)
+ .range(d3.svg.symbolTypes);
+
+ const yFormat = d3.format(fd.y_axis_format);
+
+ return {
+ padding,
+ plotWidth,
+ plotHeight,
+ yScale,
+ xScale,
+ shapeScale,
+ yFormat,
+ };
+}
+
+
+function scatCatViz(slice, json) {
+ const div = d3.select(slice.selector);
+ const fd = slice.formData;
+
+ const width = slice.width();
+ const height = slice.height();
+
+ const data = json.data.data;
+ const yLines = json.data.yLines;
+
+ const { padding, plotHeight, plotWidth, yScale, xScale, shapeScale } =
computedProps({ fd, data, width, height });
+
+ const svg = div.append('svg')
+ .attr('width', width)
+ .attr('height', height);
+
+ const g = svg.append('g')
+ .attr('transform', `translate(${padding.left}, ${padding.top})`);
+
+ const yAxis = g.append('g')
+ .attr('class', 'y axis');
+
+ const xAxis = g.append('g')
+ .attr('transform', `translate(${0},${plotHeight + (padding.top / 2)})`)
+ .attr('class', 'x axis');
+
+ yAxis
+ .call(d3.svg.axis().scale(yScale).orient('left'));
+
+ xAxis
+ .call(d3.svg.axis().scale(xScale).orient('bottom'));
+
+
+ const band = g.selectAll('.band')
+ .data(data)
+ .enter()
+ .append('g')
+ .classed('band', true)
+ .attr('transform', (d, i) => `translate(${xScale(i)}, ${0})`);
+
+ band.selectAll('.point')
+ .data(d => d.values)
+ .enter()
+ .append('path')
+ .classed('point', true)
+ .attr('d', d3.svg.symbol().type(d => shapeScale(d.shape)))
+ .attr('transform', d => `translate(${0}, ${yScale(d.y)})`);
+
+ if (yLines) {
+ g.selectAll('.line')
+ .data(yLines)
+ .enter()
+ .append('line')
+ .classed('line', true)
+ .attr('stroke', 'red')
+ .attr('stroke-width', 1.5)
+ .attr('x1', 0)
+ .attr('x2', plotWidth)
+ .attr('y1', d => yScale(d))
+ .attr('y2', d => yScale(d))
+ }
+}
+
+module.exports = scatCatViz;
diff --git a/superset/assets/visualizations/main.js
b/superset/assets/visualizations/main.js
index 78e81ab6d7..ccd6d1212d 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'),
+ catscat: require('./catscat.js'),
bullet: require('./nvd3_vis.js'),
cal_heatmap: require('./cal_heatmap.js'),
compare: require('./nvd3_vis.js'),
diff --git a/superset/viz.py b/superset/viz.py
index f53ccbbc2f..6d94c58c2c 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -803,6 +803,60 @@ def get_data(self, df):
'values': v})
return chart_data
+class CatScatViz(BaseViz):
+ """Cat Scat Viz"""
+
+ viz_type = "catscat"
+ verbose_name = _("Categorical Scatterplot")
+ is_timeseries = False
+
+ def query_obj(self):
+ 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)]
+
+ form_data = self.form_data
+ d = super(CatScatViz, self).query_obj()
+ d['groupby'] = [
+ form_data.get('entity'),
+ ]
+ if form_data.get('series'):
+ d['groupby'].append(form_data.get('series'))
+ self.y_metric = form_data.get('y')
+ self.z_metric = form_data.get('shape')
+ self.entity = form_data.get('entity')
+ self.series = form_data.get('series') or self.entity
+ self.y_lines = as_floats('marker_lines')
+
+
+ d['metrics'] = [
+ self.y_metric,
+ ]
+
+ if not all(d['metrics'] + [self.entity]):
+ raise Exception(_("Pick a metric for y"))
+ return d
+
+ def get_data(self, df):
+ df['y'] = df[[self.y_metric]]
+ df['shape'] = df[[self.z_metric]]
+ df['group'] = df[[self.series]]
+
+ series = defaultdict(list)
+ for row in df.to_dict(orient='records'):
+ series[row['group']].append(row)
+ chart_data = []
+ for k, v in series.items():
+ chart_data.append({
+ 'key': k,
+ 'values': v})
+ return {
+ 'data':chart_data,
+ 'yLines': self.y_lines or None,
+ }
class BulletViz(NVD3Viz):
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
With regards,
Apache Git Services