|
|
|
@ -15,7 +15,7 @@ angular.module('kibana.parallelcoordinates', []) |
|
|
|
|
style : {'font-size': '9pt'}, |
|
|
|
|
fields : [], |
|
|
|
|
sortable: true, |
|
|
|
|
spyable: true, |
|
|
|
|
spyable: true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_.defaults($scope.panel, _d) |
|
|
|
@ -47,6 +47,7 @@ angular.module('kibana.parallelcoordinates', []) |
|
|
|
|
eventBus.register($scope,'selected_fields', function(event, fields) { |
|
|
|
|
console.log("selected_fields", fields); |
|
|
|
|
$scope.panel.fields = _.clone(fields) |
|
|
|
|
$scope.$emit('render'); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -194,14 +195,6 @@ angular.module('kibana.parallelcoordinates', []) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var species = ["setosa", "versicolor", "virginica"], |
|
|
|
|
traits = ["sepal length", "petal length", "sepal width", "petal width"]; |
|
|
|
|
|
|
|
|
|
var m = [80, 160, 200, 160], |
|
|
|
|
w = 1280 - m[1] - m[3], |
|
|
|
|
h = 800 - m[0] - m[2]; |
|
|
|
|
|
|
|
|
|
var x, y,line,axis,foreground,svg; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -211,95 +204,43 @@ angular.module('kibana.parallelcoordinates', []) |
|
|
|
|
*/ |
|
|
|
|
function init_panel() { |
|
|
|
|
|
|
|
|
|
scope.m = [80, 160, 200, 160]; |
|
|
|
|
scope.w = $(elem[0]).width() - scope.m[1] - scope.m[3], |
|
|
|
|
scope.h = $(elem[0]).height() - scope.m[0] - scope.m[2]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log("init"); |
|
|
|
|
console.log("fields", scope.panel.fields); |
|
|
|
|
|
|
|
|
|
scope.initializing = true; |
|
|
|
|
// Using LABjs, wait until all scripts are loaded before rendering panel
|
|
|
|
|
var scripts = $LAB.script("common/lib/d3.v3.min.js?rand="+Math.floor(Math.random()*10000)) |
|
|
|
|
.script("panels/parallelcoordinates/lib/d3.csv.js?rand="+Math.floor(Math.random()*10000)); |
|
|
|
|
var scripts = $LAB.script("common/lib/d3.v3.min.js?rand="+Math.floor(Math.random()*10000)); |
|
|
|
|
|
|
|
|
|
scripts.wait(function () { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log("scripts loaded"); |
|
|
|
|
|
|
|
|
|
x = d3.scale.ordinal().domain(traits).rangePoints([0, w]); |
|
|
|
|
y = {}; |
|
|
|
|
scope.x = d3.scale.ordinal().domain(scope.panel.fields).rangePoints([0, scope.w]); |
|
|
|
|
scope.y = {}; |
|
|
|
|
|
|
|
|
|
line = d3.svg.line(); |
|
|
|
|
axis = d3.svg.axis().orient("left"); |
|
|
|
|
scope.line = d3.svg.line().interpolate('cardinal'); |
|
|
|
|
scope.axis = d3.svg.axis().orient("left"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
svg = d3.select(elem[0]).append("svg") |
|
|
|
|
scope.svg = d3.select(elem[0]).append("svg") |
|
|
|
|
.attr("width", "100%") |
|
|
|
|
.attr("height", "100%") |
|
|
|
|
.attr("viewbox", "0 0 " + (w + m[1] + m[3]) + " " + (h + m[0] + m[2])) |
|
|
|
|
.attr("viewbox", "0 0 " + (scope.w + scope.m[1] + scope.m[3]) + " " + (scope.h + scope.m[0] + scope.m[2])) |
|
|
|
|
.append("svg:g") |
|
|
|
|
.attr("transform", "translate(" + m[3] + "," + m[0] + ")"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log("loaded"); |
|
|
|
|
.attr("transform", "translate(" + scope.m[3] + "," + scope.m[0] + ")"); |
|
|
|
|
|
|
|
|
|
//console.log(flowers);
|
|
|
|
|
// Create a scale and brush for each trait.
|
|
|
|
|
scope.panel.fields.forEach(function(d) { |
|
|
|
|
console.log("extent", d3.extent(scope.data, function(p) { return +p[d]; })); |
|
|
|
|
y[d] = d3.scale.linear() |
|
|
|
|
.domain(d3.extent(scope.data, function(p) { return +p[d]; })) |
|
|
|
|
.range([h, 0]); |
|
|
|
|
// Add foreground lines.
|
|
|
|
|
scope.foreground = scope.svg.append("svg:g") |
|
|
|
|
.attr("class", "foreground"); |
|
|
|
|
|
|
|
|
|
y[d].brush = d3.svg.brush() |
|
|
|
|
.y(y[d]) |
|
|
|
|
.on("brush", brush); |
|
|
|
|
}); |
|
|
|
|
console.log("y", y); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add foreground lines.
|
|
|
|
|
foreground = svg.append("svg:g") |
|
|
|
|
.attr("class", "foreground") |
|
|
|
|
.selectAll("path") |
|
|
|
|
.data(scope.data) |
|
|
|
|
.enter().append("svg:path") |
|
|
|
|
.attr("d", path) |
|
|
|
|
.attr("class", 'setosa'); |
|
|
|
|
|
|
|
|
|
// Add a group element for each trait.
|
|
|
|
|
scope.g = svg.selectAll(".trait") |
|
|
|
|
.data(scope.panel.fields) |
|
|
|
|
.enter().append("svg:g") |
|
|
|
|
.attr("class", "trait") |
|
|
|
|
.attr("transform", function(d) { return "translate(" + x(d) + ")"; }) |
|
|
|
|
.call(d3.behavior.drag() |
|
|
|
|
.origin(function(d) { return {x: x(d)}; }) |
|
|
|
|
.on("dragstart", dragstart) |
|
|
|
|
.on("drag", drag) |
|
|
|
|
.on("dragend", dragend)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add a brush for each axis.
|
|
|
|
|
scope.g.append("svg:g") |
|
|
|
|
.attr("class", "brush") |
|
|
|
|
.each(function(d) { d3.select(this).call(y[d].brush); }) |
|
|
|
|
.selectAll("rect") |
|
|
|
|
.attr("x", -8) |
|
|
|
|
.attr("width", 16); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add an axis and title.
|
|
|
|
|
scope.g.append("svg:g") |
|
|
|
|
.attr("class", "axis") |
|
|
|
|
.each(function(d) { d3.select(this).call(axis.scale(y[d])); }) |
|
|
|
|
.append("svg:text") |
|
|
|
|
.attr("text-anchor", "middle") |
|
|
|
|
.attr("y", -9) |
|
|
|
|
.text(String); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scope.initializing = false; |
|
|
|
|
console.log("init done"); |
|
|
|
|
render_panel(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
@ -308,16 +249,16 @@ angular.module('kibana.parallelcoordinates', []) |
|
|
|
|
|
|
|
|
|
// Returns the path for a given data point.
|
|
|
|
|
function path(d) { |
|
|
|
|
return line(scope.panel.fields.map(function(p) { return [x(p), y[p](d[p])]; })); |
|
|
|
|
return scope.line(scope.panel.fields.map(function(p) { return [scope.x(p), scope.y[p](d[p])]; })); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Handles a brush event, toggling the display of foreground lines.
|
|
|
|
|
function brush() { |
|
|
|
|
var actives = scope.panel.fields.filter(function(p) { return !y[p].brush.empty(); }), |
|
|
|
|
extents = actives.map(function(p) { return y[p].brush.extent(); }); |
|
|
|
|
var actives = scope.panel.fields.filter(function(p) { return !scope.y[p].brush.empty(); }), |
|
|
|
|
extents = actives.map(function(p) { return scope.y[p].brush.extent(); }); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreground.classed("fade", function(d) { |
|
|
|
|
scope.foregroundLines.classed("fade", function(d) { |
|
|
|
|
return !actives.every(function(p, i) { |
|
|
|
|
return extents[i][0] <= d[p] && d[p] <= extents[i][1]; |
|
|
|
|
}); |
|
|
|
@ -325,21 +266,29 @@ angular.module('kibana.parallelcoordinates', []) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function dragstart(d) { |
|
|
|
|
i = scope.panel.fields.indexOf(d); |
|
|
|
|
scope.i = scope.panel.fields.indexOf(d); |
|
|
|
|
console.log("dragstart", d, scope.i) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function drag(d) { |
|
|
|
|
x.range()[i] = d3.event.x; |
|
|
|
|
scope.panel.fields.sort(function(a, b) { return x(a) - x(b); }); |
|
|
|
|
scope.g.attr("transform", function(d) { return "translate(" + x(d) + ")"; }); |
|
|
|
|
foreground.attr("d", path); |
|
|
|
|
console.log("drag", d, scope.i) |
|
|
|
|
scope.x.range()[scope.i] = d3.event.x; |
|
|
|
|
scope.panel.fields.sort(function(a, b) { return scope.x(a) - scope.x(b); }); |
|
|
|
|
scope.foregroundLines.attr("transform", function(d) { return "translate(" + scope.x(d) + ")"; }); |
|
|
|
|
scope.traits.attr("transform", function(d) { return "translate(" + scope.x(d) + ")"; }); |
|
|
|
|
scope.brushes.attr("transform", function(d) { return "translate(" + scope.x(d) + ")"; }); |
|
|
|
|
scope.axisLines.attr("transform", function(d) { return "translate(" + scope.x(d) + ")"; }); |
|
|
|
|
scope.foregroundLines.attr("d", path); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function dragend(d) { |
|
|
|
|
x.domain(scope.panel.fields).rangePoints([0, w]); |
|
|
|
|
console.log("dragend", d) |
|
|
|
|
scope.x.domain(scope.panel.fields).rangePoints([0, scope.w]); |
|
|
|
|
var t = d3.transition().duration(500); |
|
|
|
|
t.selectAll(".trait").attr("transform", function(d) { return "translate(" + x(d) + ")"; }); |
|
|
|
|
t.selectAll(".foreground path").attr("d", path); |
|
|
|
|
t.selectAll(".trait").attr("transform", function(d) { return "translate(" + scope.x(d) + ")"; }); |
|
|
|
|
t.selectAll(".axis").attr("transform", function(d) { return "translate(" + scope.x(d) + ")"; }); |
|
|
|
|
t.selectAll(".brush").attr("transform", function(d) { return "translate(" + scope.x(d) + ")"; }); |
|
|
|
|
t.selectAll(".foregroundlines").attr("d", path); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -351,8 +300,195 @@ angular.module('kibana.parallelcoordinates', []) |
|
|
|
|
*/ |
|
|
|
|
function render_panel() { |
|
|
|
|
|
|
|
|
|
var width = $(elem[0]).width(), |
|
|
|
|
height = $(elem[0]).height(); |
|
|
|
|
console.log("render_panel"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scope.x = d3.scale.ordinal().domain(scope.panel.fields).rangePoints([0, scope.w]); |
|
|
|
|
scope.y = {}; |
|
|
|
|
|
|
|
|
|
scope.line = d3.svg.line().interpolate('cardinal'); |
|
|
|
|
scope.axis = d3.svg.axis().orient("left"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var colorExtent = d3.extent(scope.data, function(p) { return +p['phpmemory']; }); |
|
|
|
|
|
|
|
|
|
scope.colors = d3.scale.linear() |
|
|
|
|
.domain([colorExtent[0],colorExtent[1]]) |
|
|
|
|
.range(["#4580FF", "#FF9245"]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scope.panel.fields.forEach(function(d) { |
|
|
|
|
scope.y[d] = d3.scale.linear() |
|
|
|
|
.domain(d3.extent(scope.data, function(p) { return +p[d]; })) |
|
|
|
|
.range([scope.h, 0]); |
|
|
|
|
|
|
|
|
|
scope.y[d].brush = d3.svg.brush() |
|
|
|
|
.y(scope.y[d]) |
|
|
|
|
.on("brush", brush); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
console.log("render y", scope.y); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var activeData = _.map(scope.data, function(d) { |
|
|
|
|
var t = {}; |
|
|
|
|
_.each(scope.panel.fields, function(f) { |
|
|
|
|
t[f] = d[f]; |
|
|
|
|
}); |
|
|
|
|
return t; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scope.foregroundLines = scope.foreground |
|
|
|
|
.selectAll(".foregroundlines") |
|
|
|
|
.data(activeData, function(d, i){ |
|
|
|
|
var id = ""; |
|
|
|
|
_.each(d, function(v) { |
|
|
|
|
id += i + "_" + v; |
|
|
|
|
}); |
|
|
|
|
return id; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
scope.foregroundLines |
|
|
|
|
.enter().append("svg:path") |
|
|
|
|
.attr("d", path) |
|
|
|
|
.attr("class", "foregroundlines") |
|
|
|
|
.attr("style", function(d) { |
|
|
|
|
return "stroke:" + scope.colors(d.phpmemory) + ";"; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
scope.foregroundLines.exit().remove(); |
|
|
|
|
|
|
|
|
|
console.log("Render Fields",scope.panel.fields); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scope.traits = scope.svg.selectAll(".trait") |
|
|
|
|
.data(scope.panel.fields, String); |
|
|
|
|
|
|
|
|
|
scope.traits |
|
|
|
|
.enter().append("svg:g") |
|
|
|
|
.attr("class", "trait") |
|
|
|
|
.attr("transform", function(d) { return "translate(" + scope.x(d) + ")"; }); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scope.brushes = scope.svg.selectAll(".brush") |
|
|
|
|
.data(scope.panel.fields, String); |
|
|
|
|
|
|
|
|
|
scope.brushes |
|
|
|
|
.enter() |
|
|
|
|
.append("svg:g") |
|
|
|
|
.attr("class", "brush") |
|
|
|
|
.each(function(d) { |
|
|
|
|
d3.select(this) |
|
|
|
|
.call(scope.y[d].brush) |
|
|
|
|
.attr("transform", function(d) { return "translate(" + scope.x(d) + ")"; }); |
|
|
|
|
}) |
|
|
|
|
.selectAll("rect") |
|
|
|
|
.attr("x", -8) |
|
|
|
|
.attr("width", 16); |
|
|
|
|
|
|
|
|
|
scope.brushes |
|
|
|
|
.each(function(d) { |
|
|
|
|
d3.select(this) |
|
|
|
|
.call(scope.y[d].brush) |
|
|
|
|
.attr("transform", function(d) { return "translate(" + scope.x(d) + ")"; }); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
scope.axisLines = scope.svg.selectAll(".axis") |
|
|
|
|
.data(scope.panel.fields, String); |
|
|
|
|
|
|
|
|
|
scope.axisLines |
|
|
|
|
.enter() |
|
|
|
|
.append("svg:g") |
|
|
|
|
.attr("class", "axis") |
|
|
|
|
.each(function(d) { |
|
|
|
|
console.log("axis",d) |
|
|
|
|
d3.select(this) |
|
|
|
|
.call(scope.axis.scale(scope.y[d])) |
|
|
|
|
.attr("transform", function(d) { return "translate(" + scope.x(d) + ")"; }); |
|
|
|
|
}).call(d3.behavior.drag() |
|
|
|
|
.origin(function(d) { return {x: scope.x(d)}; }) |
|
|
|
|
.on("dragstart", dragstart) |
|
|
|
|
.on("drag", drag) |
|
|
|
|
.on("dragend", dragend)) |
|
|
|
|
|
|
|
|
|
.append("svg:text") |
|
|
|
|
.attr("text-anchor", "middle") |
|
|
|
|
.attr("y", -9) |
|
|
|
|
.text(String); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scope.brushes |
|
|
|
|
.exit().remove(); |
|
|
|
|
|
|
|
|
|
scope.axisLines |
|
|
|
|
.exit().remove(); |
|
|
|
|
|
|
|
|
|
scope.traits |
|
|
|
|
.exit().remove(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dragend(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add a brush for each axis.
|
|
|
|
|
scope.brushes = scope.g.append("svg:g") |
|
|
|
|
.attr("class", "brush"); |
|
|
|
|
|
|
|
|
|
scope.axisLines = scope.g.append("svg:g") |
|
|
|
|
.attr("class", "axis"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Draw the brushes
|
|
|
|
|
//If the field is no longer in the list of actives,
|
|
|
|
|
//remove the element. Sorta like a poor-man's enter() / exit()
|
|
|
|
|
scope.brushes |
|
|
|
|
.each(function(d) { |
|
|
|
|
if (typeof scope.y[d] !== 'undefined') { |
|
|
|
|
console.log("brushes.each", d); |
|
|
|
|
d3.select(this).attr("style", "display").call(scope.y[d].brush); |
|
|
|
|
} else { |
|
|
|
|
console.log("none"); |
|
|
|
|
d3.select(this).attr("style", "display:none"); |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
.selectAll("rect") |
|
|
|
|
.attr("x", -8) |
|
|
|
|
.attr("width", 16); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Draw the axis lines
|
|
|
|
|
//If the field is no longer in the list of actives,
|
|
|
|
|
//remove the element. Sorta like a poor-man's enter() / exit()
|
|
|
|
|
scope.axisLines |
|
|
|
|
.each(function(d) { |
|
|
|
|
if (typeof scope.y[d] !== 'undefined') { |
|
|
|
|
d3.select(this).attr("style", "display").call(scope.axis.scale(scope.y[d])); |
|
|
|
|
} else { |
|
|
|
|
d3.select(this).attr("style", "display:none"); |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
.append("svg:text") |
|
|
|
|
.attr("text-anchor", "middle") |
|
|
|
|
.attr("y", -9) |
|
|
|
|
.text(String) |
|
|
|
|
.call(dragend); //call dragend so that the axis reshuffle.
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|