mirror of https://github.com/apache/superset.git
Adding Parallel coordinates viz
This commit is contained in:
parent
2885227050
commit
73989f41ac
1
TODO.md
1
TODO.md
|
@ -2,6 +2,7 @@
|
|||
List of TODO items for Panoramix
|
||||
|
||||
## Features
|
||||
* Slider form element
|
||||
* **Dashboard URL filters:** `{dash_url}#fltin__fieldname__value1,value2`
|
||||
* **Default slice:** choose a default slice for the dataset instead of default endpoint
|
||||
* **refresh freq**: specifying the refresh frequency of a dashboard and specific slices within it, some randomization would be nice
|
||||
|
|
|
@ -371,6 +371,9 @@ class FormFactory(object):
|
|||
"Range Filter", default=False,
|
||||
description=(
|
||||
"Whether to display the time range interactive selector")),
|
||||
'show_datatable': BetterBooleanField(
|
||||
"Data Table", default=False,
|
||||
description="Whether to display the interactive data table"),
|
||||
'include_search': BetterBooleanField(
|
||||
"Search Box", default=False,
|
||||
description=(
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
.parcoords svg, .parcoords canvas {
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
}
|
||||
.parcoords > canvas {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.parcoords text.label {
|
||||
font: 100%;
|
||||
font-size: 12px;
|
||||
cursor: drag;
|
||||
}
|
||||
|
||||
.parcoords rect.background {
|
||||
fill: transparent;
|
||||
}
|
||||
.parcoords rect.background:hover {
|
||||
fill: rgba(120,120,120,0.2);
|
||||
}
|
||||
.parcoords .resize rect {
|
||||
fill: rgba(0,0,0,0.1);
|
||||
}
|
||||
.parcoords rect.extent {
|
||||
fill: rgba(255,255,255,0.25);
|
||||
stroke: rgba(0,0,0,0.6);
|
||||
}
|
||||
.parcoords .axis line, .parcoords .axis path {
|
||||
fill: none;
|
||||
stroke: #222;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
.parcoords canvas {
|
||||
opacity: 1;
|
||||
-moz-transition: opacity 0.3s;
|
||||
-webkit-transition: opacity 0.3s;
|
||||
-o-transition: opacity 0.3s;
|
||||
}
|
||||
.parcoords canvas.faded {
|
||||
opacity: 0.25;
|
||||
}
|
||||
.parcoords {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* data table styles */
|
||||
.parcoords .row, .parcoords .header {
|
||||
clear: left; font-size: 12px; line-height: 18px; height: 18px;
|
||||
margin: 0px;
|
||||
}
|
||||
.parcoords .row:nth-child(odd) { background: rgba(0,0,0,0.05); }
|
||||
.parcoords .header { font-weight: bold; }
|
||||
.parcoords .cell { float: left; overflow: hidden; white-space: nowrap; width: 100px; height: 18px; }
|
||||
.parcoords .col-0 { width: 180px; }
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,59 @@
|
|||
// http://bl.ocks.org/3687826
|
||||
d3.divgrid = function(config) {
|
||||
var columns = [];
|
||||
|
||||
var dg = function(selection) {
|
||||
if (columns.length == 0) columns = d3.keys(selection.data()[0][0]);
|
||||
|
||||
// header
|
||||
selection.selectAll(".header")
|
||||
.data([true])
|
||||
.enter().append("div")
|
||||
.attr("class", "header")
|
||||
|
||||
var header = selection.select(".header")
|
||||
.selectAll(".cell")
|
||||
.data(columns);
|
||||
|
||||
header.enter().append("div")
|
||||
.attr("class", function(d,i) { return "col-" + i; })
|
||||
.classed("cell", true)
|
||||
|
||||
selection.selectAll(".header .cell")
|
||||
.text(function(d) { return d; });
|
||||
|
||||
header.exit().remove();
|
||||
|
||||
// rows
|
||||
var rows = selection.selectAll(".row")
|
||||
.data(function(d) { return d; })
|
||||
|
||||
rows.enter().append("div")
|
||||
.attr("class", "row")
|
||||
|
||||
rows.exit().remove();
|
||||
|
||||
var cells = selection.selectAll(".row").selectAll(".cell")
|
||||
.data(function(d) { return columns.map(function(col){return d[col];}) })
|
||||
|
||||
// cells
|
||||
cells.enter().append("div")
|
||||
.attr("class", function(d,i) { return "col-" + i; })
|
||||
.classed("cell", true)
|
||||
|
||||
cells.exit().remove();
|
||||
|
||||
selection.selectAll(".cell")
|
||||
.text(function(d) { return d; });
|
||||
|
||||
return dg;
|
||||
};
|
||||
|
||||
dg.columns = function(_) {
|
||||
if (!arguments.length) return columns;
|
||||
columns = _;
|
||||
return this;
|
||||
};
|
||||
|
||||
return dg;
|
||||
};
|
|
@ -0,0 +1,75 @@
|
|||
px.registerViz('para', function(slice) {
|
||||
|
||||
function refresh() {
|
||||
$('#code').attr('rows', '15')
|
||||
$.getJSON(slice.jsonEndpoint(), function(payload) {
|
||||
var data = payload.data;
|
||||
var fd = payload.form_data;
|
||||
ext = d3.extent(data, function(d){
|
||||
return d[fd.secondary_metric];
|
||||
});
|
||||
ext = [ext[0], (ext[1]-ext[0])/2,ext[1]];
|
||||
var cScale = d3.scale.linear()
|
||||
.domain(ext)
|
||||
.range(['red', 'grey', 'blue'])
|
||||
.interpolate(d3.interpolateLab);
|
||||
var color = function(d){return cScale(d[fd.secondary_metric])};
|
||||
var container = d3.select(slice.selector);
|
||||
if (fd.show_datatable)
|
||||
var eff_height = slice.height() / 2;
|
||||
else
|
||||
var eff_height = slice.height();
|
||||
|
||||
var div = container.append('div')
|
||||
.attr('id', 'parcoords_' + slice.container_id)
|
||||
.style('height', eff_height + 'px')
|
||||
.classed("parcoords", true);
|
||||
var parcoords = d3.parcoords()('#parcoords_' + slice.container_id)
|
||||
.width(slice.width())
|
||||
.color(color)
|
||||
.alpha(0.5)
|
||||
.composite("darken")
|
||||
.height(eff_height)
|
||||
.data(payload.data)
|
||||
.render()
|
||||
.createAxes()
|
||||
.shadows()
|
||||
.reorderable()
|
||||
.brushMode("1D-axes");
|
||||
|
||||
if (fd.show_datatable) {
|
||||
// create data table, row hover highlighting
|
||||
var grid = d3.divgrid();
|
||||
container.append("div")
|
||||
.datum(data.slice(0,10))
|
||||
.attr('id', "grid")
|
||||
.call(grid)
|
||||
.classed("parcoords", true)
|
||||
.selectAll(".row")
|
||||
.on({
|
||||
"mouseover": function(d) { parcoords.highlight([d]) },
|
||||
"mouseout": parcoords.unhighlight
|
||||
});
|
||||
// update data table on brush event
|
||||
parcoords.on("brush", function(d) {
|
||||
d3.select("#grid")
|
||||
.datum(d.slice(0,10))
|
||||
.call(grid)
|
||||
.selectAll(".row")
|
||||
.on({
|
||||
"mouseover": function(d) { parcoords.highlight([d]) },
|
||||
"mouseout": parcoords.unhighlight
|
||||
});
|
||||
});
|
||||
}
|
||||
slice.done();
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
slice.error(xhr.responseText);
|
||||
});
|
||||
};
|
||||
return {
|
||||
render: refresh,
|
||||
resize: refresh,
|
||||
};
|
||||
});
|
|
@ -1158,6 +1158,44 @@ class IFrameViz(BaseViz):
|
|||
},)
|
||||
|
||||
|
||||
class ParallelCoordinatesViz(BaseViz):
|
||||
viz_type = "para"
|
||||
verbose_name = "Parallel Coordinates"
|
||||
is_timeseries = False
|
||||
js_files = [
|
||||
'lib/para/d3.parcoords.js',
|
||||
'lib/para/divgrid.js',
|
||||
'widgets/viz_para.js']
|
||||
css_files = ['lib/para/d3.parcoords.css']
|
||||
fieldsets = (
|
||||
{
|
||||
'label': None,
|
||||
'fields': (
|
||||
'granularity',
|
||||
('since', 'until'),
|
||||
'series',
|
||||
'metrics',
|
||||
'secondary_metric',
|
||||
'limit',
|
||||
('show_datatable', None),
|
||||
)
|
||||
},)
|
||||
def query_obj(self):
|
||||
d = super(ParallelCoordinatesViz, self).query_obj()
|
||||
fd = self.form_data
|
||||
d['metrics'] = fd.get('metrics')
|
||||
second = fd.get('secondary_metric')
|
||||
if second not in d['metrics']:
|
||||
d['metrics'] += [second]
|
||||
d['groupby'] = [fd.get('series')]
|
||||
return d
|
||||
|
||||
def get_json_data(self):
|
||||
df = self.get_df()
|
||||
df = df[[self.form_data.get('series')] + self.form_data.get('metrics')]
|
||||
return df.to_json(orient="records")
|
||||
|
||||
|
||||
viz_types_list = [
|
||||
TableViz,
|
||||
PivotTableViz,
|
||||
|
@ -1177,6 +1215,7 @@ viz_types_list = [
|
|||
WorldMapViz,
|
||||
FilterBoxViz,
|
||||
IFrameViz,
|
||||
ParallelCoordinatesViz,
|
||||
]
|
||||
|
||||
viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list])
|
||||
|
|
Loading…
Reference in New Issue