pandorafms/pandora_console/include/graphs/pandora.d3.js

857 lines
22 KiB
JavaScript

// Pandora FMS - http://pandorafms.com
// ==================================================
// Copyright (c) 2005-2011 Artica Soluciones Tecnologicas
// Please see http://pandorafms.org for full contribution list
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; version 2
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// The recipient is the selector of the html element
// The elements is an array with the names of the wheel elements
// The matrix must be a 2 dimensional array with a row and a column for each element
// Ex:
// elements = ["a", "b", "c"];
// matrix = [[0, 0, 2], // a[a => a, a => b, a => c]
// [5, 0, 1], // b[b => a, b => b, b => c]
// [2, 3, 0]]; // c[c => a, c => b, c => c]
function chordDiagram (recipient, elements, matrix, unit, width) {
d3.chart = d3.chart || {};
d3.chart.chordWheel = function(options) {
// Default values
var width = 700;
var margin = 150;
var padding = 0.02;
function chart(selection) {
selection.each(function(data) {
var matrix = data.matrix;
var elements = data.elements;
var radius = width / 2 - margin;
// create the layout
var chord = d3.layout.chord()
.padding(padding)
.sortSubgroups(d3.descending);
// Select the svg element, if it exists.
var svg = d3.select(this).selectAll("svg").data([data]);
// Otherwise, create the skeletal chart.
var gEnter = svg.enter().append("svg:svg")
.attr("width", width)
.attr("height", width)
.attr("class", "dependencyWheel")
.append("g")
.attr("transform", "translate(" + (width / 2) + "," + (width / 2) + ")");
var arc = d3.svg.arc()
.innerRadius(radius)
.outerRadius(radius + 20);
var fill = function(d) {
return "hsl(" + parseInt((d.index / 26) * 360, 10) + ",80%,70%)";
};
// Returns an event handler for fading a given chord group.
var fade = function(opacity) {
return function(g, i) {
svg.selectAll(".chord")
.filter(function(d) {
return d.source.index != i && d.target.index != i;
})
.transition()
.style("opacity", opacity);
var groups = [];
svg.selectAll(".chord")
.filter(function(d) {
if (d.source.index == i) {
groups.push(d.target.index);
}
if (d.target.index == i) {
groups.push(d.source.index);
}
});
groups.push(i);
var length = groups.length;
svg.selectAll('.group')
.filter(function(d) {
for (var i = 0; i < length; i++) {
if(groups[i] == d.index) return false;
}
return true;
})
.transition()
.style("opacity", opacity);
};
};
chord.matrix(matrix);
var rootGroup = chord.groups()[0];
var rotation = - (rootGroup.endAngle - rootGroup.startAngle) / 2 * (180 / Math.PI);
var g = gEnter.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", "group")
.attr("transform", function(d) {
return "rotate(" + rotation + ")";
});
g.append("svg:path")
.style("fill", fill)
.style("stroke", fill)
.attr("d", arc)
.on("mouseover", fade(0.1))
.on("mouseout", fade(1));
g.append("svg:text")
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" +
"translate(" + (radius + 26) + ")" +
(d.angle > Math.PI ? "rotate(180)" : "");
})
.text(function(d) { return elements[d.index]; });
gEnter.selectAll("path.chord")
.data(chord.chords)
.enter().append("svg:path")
.attr("class", "chord")
.style("stroke", function(d) { return d3.rgb(fill(d.source)).darker(); })
.style("fill", function(d) { return fill(d.source); })
.attr("d", d3.svg.chord().radius(radius))
.attr("transform", function(d) {
return "rotate(" + rotation + ")";
})
.style("opacity", 1);
// Add an elaborate mouseover title for each chord.
gEnter.selectAll("path.chord")
.on("mouseover", over_user)
.on("mouseout", out_user)
.on("mousemove", move_tooltip);
function move_tooltip(d) {
x = d3.event.pageX + 10;
y = d3.event.pageY + 10;
$("#tooltip").css('left', x + 'px');
$("#tooltip").css('top', y + 'px');
}
function over_user(d) {
id = d.id;
$("#" + id).css('border', '1px solid black');
$("#" + id).css('z-index', '1');
show_tooltip(d);
}
function out_user(d) {
id = d.id;
$("#" + id).css('border', '');
$("#" + id).css('z-index', '');
hide_tooltip();
}
function create_tooltip(d, x, y) {
if ($("#tooltip").length == 0) {
$(recipient)
.append($("<div></div>")
.attr('id', 'tooltip')
.html(
elements[d.source.index]
+ " → "
+ elements[d.target.index]
+ ": <b>" + d.source.value.toFixed(2) + " " + unit + "</b>"
+ "<br>"
+ elements[d.target.index]
+ " → "
+ elements[d.source.index]
+ ": <b>" + d.target.value.toFixed(2) + " " + unit + "</b>"
));
}
else {
$("#tooltip").html(
elements[d.source.index]
+ " → "
+ elements[d.target.index]
+ ": <b>" + d.source.value.toFixed(2) + " " + unit + "</b>"
+ "<br>"
+ elements[d.target.index]
+ " → "
+ elements[d.source.index]
+ ": <b>" + d.target.value.toFixed(2) + " " + unit + "</b>"
);
}
$("#tooltip").attr('style', 'background: #fff;' +
'position: absolute;' +
'display: inline-block;' +
'width: auto;' +
'max-width: 500px;' +
'text-align: left;' +
'padding: 10px 10px 10px 10px;' +
'z-index: 2;' +
"-webkit-box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75);" +
"-moz-box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75);" +
"box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75);" +
'left: ' + x + 'px;' +
'top: ' + y + 'px;');
}
function show_tooltip(d) {
x = d3.event.pageX + 10;
y = d3.event.pageY + 10;
create_tooltip(d, x, y);
}
function hide_tooltip() {
$("#tooltip").hide();
}
});
}
chart.width = function(value) {
if (!arguments.length) return width;
width = value;
return chart;
};
chart.margin = function(value) {
if (!arguments.length) return margin;
margin = value;
return chart;
};
chart.padding = function(value) {
if (!arguments.length) return padding;
padding = value;
return chart;
};
return chart;
};
var chart = d3.chart.chordWheel()
.width(width)
.margin(150)
.padding(.02);
d3.select(recipient)
.datum({
elements: elements,
matrix: matrix
})
.call(chart);
}
// The recipient is the selector of the html element
// The data must be a bunch of associative arrays like this
// data = {
// "name": "IP Traffic",
// "id": 0,
// "children": [
// {
// "name": "192.168.1.1",
// "id": 1,
// "children": [
// {
// "name": "HTTP",
// "id": 2,
// "value": 33938
// }
// ]
// },
// {
// "name": "192.168.1.2",
// "id": 3,
// "children": [
// {
// "name": "HTTP",
// "id": 4,
// "value": 3938
// },
// {
// "name": "FTP",
// "id": 5,
// "value": 1312
// }
// ]
// }
// ]
// };
function treeMap(recipient, data, width, height) {
//var isIE = BrowserDetect.browser == 'Explorer';
var isIE = true;
var chartWidth = width;
var chartHeight = height;
if (width === 'auto') {
chartWidth = $(recipient).innerWidth();
}
if (height === 'auto') {
chartHeight = $(recipient).innerHeight();
}
var xscale = d3.scale.linear().range([0, chartWidth]);
var yscale = d3.scale.linear().range([0, chartHeight]);
var color = d3.scale.category10();
var headerHeight = 20;
var headerColor = "#555555";
var transitionDuration = 500;
var root;
var node;
var treemap = d3.layout.treemap()
.round(false)
.size([chartWidth, chartHeight])
.sticky(true)
.value(function(d) {
return d.value;
});
var chart = d3.select(recipient)
.append("svg:svg")
.attr("width", chartWidth)
.attr("height", chartHeight)
.append("svg:g");
node = root = data;
var nodes = treemap.nodes(root);
var children = nodes.filter(function(d) {
return !d.children;
});
var parents = nodes.filter(function(d) {
return d.children;
});
// create parent cells
var parentCells = chart.selectAll("g.cell.parent")
.data(parents, function(d) {
return d.id;
});
var parentEnterTransition = parentCells.enter()
.append("g")
.attr("class", "cell parent")
.on("click", function(d) {
zoom(d);
})
.append("svg")
.attr("class", "clip")
.attr("width", function(d) {
return Math.max(0.01, d.dx);
})
.attr("height", headerHeight);
parentEnterTransition.append("rect")
.attr("width", function(d) {
return Math.max(0.01, d.dx);
})
.attr("height", headerHeight)
.style("fill", headerColor);
parentEnterTransition.append('text')
.attr("class", "label")
.attr("fill", "white")
.attr("transform", "translate(3, 13)")
.attr("width", function(d) {
return Math.max(0.01, d.dx);
})
.attr("height", headerHeight)
.text(function(d) {
return d.name;
});
// update transition
var parentUpdateTransition = parentCells.transition().duration(transitionDuration);
parentUpdateTransition.select(".cell")
.attr("transform", function(d) {
return "translate(" + d.dx + "," + d.y + ")";
});
parentUpdateTransition.select("rect")
.attr("width", function(d) {
return Math.max(0.01, d.dx);
})
.attr("height", headerHeight)
.style("fill", headerColor);
parentUpdateTransition.select(".label")
.attr("transform", "translate(3, 13)")
.attr("width", function(d) {
return Math.max(0.01, d.dx);
})
.attr("height", headerHeight)
.text(function(d) {
return d.name;
});
// remove transition
parentCells.exit()
.remove();
// create children cells
var childrenCells = chart.selectAll("g.cell.child")
.data(children, function(d) {
return d.id;
});
// enter transition
var childEnterTransition = childrenCells.enter()
.append("g")
.attr("class", "cell child")
.on("click", function(d) {
zoom(node === d.parent ? root : d.parent);
})
.on("mouseover", over_user)
.on("mouseout", out_user)
.on("mousemove", move_tooltip)
.append("svg")
.attr("class", "clip");
childEnterTransition.append("rect")
.classed("background", true)
.style("fill", function(d) {
return color(d.name);
});
childEnterTransition.append('text')
.attr("class", "label")
.attr('x', function(d) {
return d.dx / 2;
})
.attr('y', function(d) {
return d.dy / 2;
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("display", "none")
.text(function(d) {
return d.name;
});
// update transition
var childUpdateTransition = childrenCells.transition().duration(transitionDuration);
childUpdateTransition.select(".cell")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
childUpdateTransition.select("rect")
.attr("width", function(d) {
return Math.max(0.01, d.dx);
})
.attr("height", function(d) {
return d.dy;
});
childUpdateTransition.select(".label")
.attr('x', function(d) {
return d.dx / 2;
})
.attr('y', function(d) {
return d.dy / 2;
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("display", "none")
.text(function(d) {
return d.name;
});
// exit transition
childrenCells.exit()
.remove();
d3.select("select").on("change", function() {
treemap.value(this.value == "size" ? size : count)
.nodes(root);
zoom(node);
});
zoom(node);
function size(d) {
return d.size;
}
function count(d) {
return 1;
}
//and another one
function textHeight(d) {
var ky = chartHeight / d.dy;
yscale.domain([d.y, d.y + d.dy]);
return (ky * d.dy) / headerHeight;
}
function getRGBComponents (color) {
var r = color.substring(1, 3);
var g = color.substring(3, 5);
var b = color.substring(5, 7);
return {
R: parseInt(r, 16),
G: parseInt(g, 16),
B: parseInt(b, 16)
};
}
function idealTextColor (bgColor) {
var nThreshold = 105;
var components = getRGBComponents(bgColor);
var bgDelta = (components.R * 0.299) + (components.G * 0.587) + (components.B * 0.114);
return ((255 - bgDelta) < nThreshold) ? "#000000" : "#ffffff";
}
function zoom(d) {
treemap
.padding([headerHeight / (chartHeight / d.dy), 0, 0, 0])
.nodes(d);
// moving the next two lines above treemap layout messes up padding of zoom result
var kx = chartWidth / d.dx;
var ky = chartHeight / d.dy;
var level = d;
xscale.domain([d.x, d.x + d.dx]);
yscale.domain([d.y, d.y + d.dy]);
if (node != level) {
chart.selectAll(".cell.child .label")
.style("display", "none");
}
var zoomTransition = chart.selectAll("g.cell").transition().duration(transitionDuration)
.attr("transform", function(d) {
return "translate(" + xscale(d.x) + "," + yscale(d.y) + ")";
})
.each("start", function() {
d3.select(this).select("label")
.style("display", "none");
})
.each("end", function(d, i) {
if (!i && (level !== self.root)) {
chart.selectAll(".cell.child")
.filter(function(d) {
return d.parent === self.node; // only get the children for selected group
})
.select(".label")
.style("display", "")
.style("color", function(d) {
return idealTextColor(color(d.parent.name));
});
}
});
zoomTransition.select(".clip")
.attr("width", function(d) {
return Math.max(0.01, (kx * d.dx));
})
.attr("height", function(d) {
return d.children ? headerHeight : Math.max(0.01, (ky * d.dy));
});
zoomTransition.select(".label")
.attr("width", function(d) {
return Math.max(0.01, (kx * d.dx));
})
.attr("height", function(d) {
return d.children ? headerHeight : Math.max(0.01, (ky * d.dy));
})
.text(function(d) {
return d.name;
});
zoomTransition.select(".child .label")
.attr("x", function(d) {
return kx * d.dx / 2;
})
.attr("y", function(d) {
return ky * d.dy / 2;
});
zoomTransition.select("rect")
.attr("width", function(d) {
return Math.max(0.01, (kx * d.dx));
})
.attr("height", function(d) {
return d.children ? headerHeight : Math.max(0.01, (ky * d.dy));
});
node = d;
if (d3.event) {
d3.event.stopPropagation();
}
}
function position() {
this.style("left", function(d) { return d.x + "px"; })
.style("top", function(d) { return d.y + "px"; })
.style("width", function(d) { return Math.max(0, d.dx - 1) + "px"; })
.style("height", function(d) { return Math.max(0, d.dy - 1) + "px"; });
}
function move_tooltip(d) {
x = d3.event.pageX + 10;
y = d3.event.pageY + 10;
$("#tooltip").css('left', x + 'px');
$("#tooltip").css('top', y + 'px');
}
function over_user(d) {
id = d.id;
$("#" + id).css('border', '1px solid black');
$("#" + id).css('z-index', '1');
show_tooltip(d);
}
function out_user(d) {
id = d.id;
$("#" + id).css('border', '');
$("#" + id).css('z-index', '');
hide_tooltip();
}
function create_tooltip(d, x, y) {
if ($("#tooltip").length == 0) {
$(recipient)
.append($("<div></div>")
.attr('id', 'tooltip')
.html(d.tooltip_content));
}
else {
$("#tooltip").html(d.tooltip_content);
}
$("#tooltip").attr('style', 'background: #fff;' +
'position: absolute;' +
'display: block;' +
'width: 200px;' +
'text-align: left;' +
'padding: 10px 10px 10px 10px;' +
'z-index: 2;' +
"-webkit-box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75);" +
"-moz-box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75);" +
"box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75);" +
'left: ' + x + 'px;' +
'top: ' + y + 'px;');
}
function show_tooltip(d) {
x = d3.event.pageX + 10;
y = d3.event.pageY + 10;
create_tooltip(d, x, y);
}
function hide_tooltip() {
$("#tooltip").hide();
}
}
// A sunburst is similar to a treemap, except it uses a radial layout.
// The root node of the tree is at the center, with leaves on the circumference.
// The area (or angle, depending on implementation) of each arc corresponds to its value.
// Sunburst design by John Stasko. Data courtesy Jeff Heer.
// http://bl.ocks.org/mbostock/4348373
function sunburst (recipient, data, width, height) {
if (width === 'auto') {
width = $(recipient).innerWidth();
}
if (height === 'auto') {
height = width;
}
// var width = 960,
// height = 700;
var radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var color = d3.scale.category20c();
var svg = d3.select(recipient).append("svg")
.attr("width", width)
.attr("height", height);
var partition = d3.layout.partition()
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
var g = svg.selectAll("g")
.data(partition.nodes(data))
.enter().append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var path = g.append("path")
.attr("d", arc)
.style("fill", function(d) { return d.color ? d3.rgb(d.color) : color((d.children ? d : d.parent).name); })
.style("cursor", "pointer")
.on("click", click)
.on("mouseover", over_user)
.on("mouseout", out_user)
.on("mousemove", move_tooltip);
function computeTextRotation(d) {
var angle = x(d.x + d.dx / 2) - Math.PI / 2;
return angle / Math.PI * 180;
}
var text = g.append("text")
.attr("x", function(d) { return y(d.y); })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("opacity", function(d) {
if (typeof d.show_name != "undefined" && d.show_name)
return 1;
else
return 0;
})
.text(function(d) {
return d.name;
})
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.style("font-size", "10px")
// Makes svg elements invisible to events
.style("pointer-events", "none");
function click(d) {
if (typeof d.link != "undefined") {
window.location.href = d.link;
}
else {
// fade out all text elements
text.transition().attr("opacity", 0);
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if ((typeof e.type != 'undefined'
&& (e.type == "group"
|| ( e.type == "agent" && (d.type == "group" || d.type == "agent" || d.type == "module_group" || d.type == "module") )
|| ( (e.type == "module_group" || e.type == "module") && (d.type == "agent" || d.type == "module_group") ) ))
&& e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText
.attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
.attr("x", function(d) { return y(d.y); })
.transition().duration(250)
.attr("opacity", 1);
}
});
}
}
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
function move_tooltip(d) {
var x = d3.event.pageX + 10;
var y = d3.event.pageY + 10;
$("#tooltip").css('left', x + 'px');
$("#tooltip").css('top', y + 'px');
}
function over_user(d) {
id = d.id;
$("#" + id).css('border', '1px solid black');
$("#" + id).css('z-index', '1');
show_tooltip(d);
}
function out_user(d) {
id = d.id;
$("#" + id).css('border', '');
$("#" + id).css('z-index', '');
hide_tooltip();
}
function create_tooltip(d, x, y) {
var tooltip = (typeof d.tooltip_content != 'undefined') ? d.tooltip_content : d.name;
if ($("#tooltip").length == 0) {
$(recipient)
.append($("<div></div>")
.attr('id', 'tooltip')
.html(tooltip));
}
else {
$("#tooltip").html(tooltip);
}
$("#tooltip").attr('style', 'background: #fff;' +
'position: absolute;' +
'display: block;' +
'width: 200px;' +
'text-align: left;' +
'padding: 10px 10px 10px 10px;' +
'z-index: 2;' +
"-webkit-box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75);" +
"-moz-box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75);" +
"box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75);" +
'left: ' + x + 'px;' +
'top: ' + y + 'px;');
}
function show_tooltip(d) {
var x = d3.event.pageX + 10;
var y = d3.event.pageY + 10;
create_tooltip(d, x, y);
}
function hide_tooltip() {
$("#tooltip").hide();
}
}