Adding Parallel coordinates viz

This commit is contained in:
Maxime Beauchemin 2016-01-21 08:20:53 -08:00
parent 2885227050
commit 73989f41ac
7 changed files with 2456 additions and 0 deletions

View File

@ -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

View File

@ -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=(

View File

@ -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

View File

@ -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;
};

View File

@ -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,
};
});

View File

@ -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])