working rendering using d3-force plugin

This commit is contained in:
Oliver Gorwits
2017-12-11 00:09:08 +00:00
parent 187265fc03
commit 77ca8f96e1
6 changed files with 4400 additions and 181 deletions

View File

@@ -14,7 +14,7 @@ ajax '/ajax/content/device/netmap' => require_login sub {
template 'ajax/device/netmap.tt', {}, { layout => undef };
};
ajax '/ajax/data/device/alldevicelinks' => require_login sub {
ajax '/ajax/data/device/netmap' => require_login sub {
my $q = param('q');
my %data = ( nodes => [], links => [] );

View File

@@ -0,0 +1,197 @@
.net_gobrechts_d3_force,
.net_gobrechts_d3_force_customize,
.net_gobrechts_d3_force_customize td,
.net_gobrechts_d3_force_tooltip {
box-sizing: content-box;
font-family: Arial, Helvetica, Sans Serif;
font-size: 10px;
background-color: #fff
}
.net_gobrechts_d3_force.border {
border: 1px solid silver;
border-radius: 5px;
}
.net_gobrechts_d3_force circle.highlighted {
stroke: #555;
stroke-width: 2px;
stroke-opacity: 1.0;
}
.net_gobrechts_d3_force circle.selected {
stroke: #555;
stroke-width: 4px;
stroke-dasharray: 4 2;
stroke-opacity: 1.0;
}
.net_gobrechts_d3_force text.label,
.net_gobrechts_d3_force text.labelCircular {
fill: black;
font-size: 10px;
letter-spacing: 0;
pointer-events: none;
}
.net_gobrechts_d3_force text.label{
text-anchor: middle;
}
.net_gobrechts_d3_force text.highlighted {
font-size: 12px;
font-weight: bold;
}
.net_gobrechts_d3_force text.link {
font-size: 12px;
fill: blue;
cursor: pointer;
}
.net_gobrechts_d3_force line.link,
.net_gobrechts_d3_force path.link {
fill: none;
stroke: #bbb;
stroke-width: 1.5px;
stroke-opacity: 0.8;
}
.net_gobrechts_d3_force line.dotted,
.net_gobrechts_d3_force path.dotted {
stroke-dasharray: .01 3;
stroke-linecap: round;
}
.net_gobrechts_d3_force line.dashed,
.net_gobrechts_d3_force path.dashed {
stroke-dasharray: 4 2;
}
.net_gobrechts_d3_force line.highlighted,
.net_gobrechts_d3_force path.highlighted {
stroke: #555 !important;
stroke-opacity: 1.0;
}
.net_gobrechts_d3_force marker.normal {
stroke: none;
fill: #bbb;
}
.net_gobrechts_d3_force marker.highlighted {
stroke: none;
fill: #555;
}
.net_gobrechts_d3_force .graphOverlay,
.net_gobrechts_d3_force .graphOverlaySizeHelper {
fill: none;
pointer-events: all;
}
.net_gobrechts_d3_force .lasso path {
stroke: #505050;
stroke-width: 2px;
}
.net_gobrechts_d3_force .lasso .drawn {
fill-opacity: 0.05 ;
}
.net_gobrechts_d3_force .lasso .loop_close {
fill: none;
stroke-dasharray: 4,4;
}
.net_gobrechts_d3_force .lasso .origin {
fill: #3399FF;
fill-opacity: 0.5;
}
.net_gobrechts_d3_force .loading rect {
fill: black;
fill-opacity: 0.2;
}
.net_gobrechts_d3_force .loading text {
fill: white;
font-size: 36px;
text-anchor: middle;
}
.net_gobrechts_d3_force_tooltip {
position: absolute;
border-radius: 5px;
padding: 5px;
background-color: silver;
opacity: 0.9;
width: 150px;
overflow: auto;
font-size: 12px;
z-index: 100000;
pointer-events: none;
display: none;
}
.net_gobrechts_d3_force_customize {
border: 1px solid silver;
border-radius: 5px;
font-size: 12px;
position: absolute;
padding: 5px;
background-color:white;
box-shadow: 1px 1px 6px #666;
z-index: 200000;
}
.net_gobrechts_d3_force_customize .drag {
border: 1px dashed silver;
border-radius: 3px;
display: block;
cursor: move;
font-weight: bold;
height: 24px;
margin-bottom: 5px;
}
.net_gobrechts_d3_force_customize .title {
position: absolute;
top: 10px;
left: 10px;
}
.net_gobrechts_d3_force_customize .close {
position: absolute;
top: 10px;
right: 10px;
}
.net_gobrechts_d3_force_customize table {
border-collapse: collapse;
border-spacing: 0;
border: none;
margin:0;
padding:0;
}
.net_gobrechts_d3_force_customize tr.hidden {
display: none;
}
.net_gobrechts_d3_force_customize td {
padding: 1px;
font-size: 12px;
vertical-align: middle;
border: none;
}
.net_gobrechts_d3_force_customize .label {
text-align: right;
}
.net_gobrechts_d3_force_customize .warning {
background-color: orange;
}
.net_gobrechts_d3_force_customize input,
.net_gobrechts_d3_force_customize select,
.net_gobrechts_d3_force_customize textarea,
.net_gobrechts_d3_force_customize a {
border: 1px solid silver;
margin: 0;
padding: 0;
height: auto;
}
.net_gobrechts_d3_force_customize a {
border: 1px solid transparent;
color: blue;
text-decoration: none;
cursor: pointer;
}
.net_gobrechts_d3_force_customize input:focus,
.net_gobrechts_d3_force_customize select:focus,
.net_gobrechts_d3_force_customize textarea:focus,
.net_gobrechts_d3_force_customize a:focus {
outline: none !important;
border: 1px solid blue !important;
background-color: yellow !important;
box-shadow: none !important;
}
.net_gobrechts_d3_force_customize textarea {
font-size: 10px !important;
padding: 2px;
width: 160px;
height: 85px;
background-color: white;
color: black;
}

4175
share/public/javascripts/d3-force.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,184 +1,28 @@
<script>
var winHeight = window.innerHeight;
var winWidth = window.innerWidth;
// links in the initial tree drawing use this generator
var treeLink = d3.svg.diagonal.radial()
.projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });
// actual device neighbor links use this generator
var neighLink = d3.svg.diagonal.radial();
// store x,y for all circles on the map
var loc = {};
// store actual links between all nodes
var neighbors_data = {};
// main SVG background, with support for pan/zoom
var svg = d3.select("#netmap_pane").append("svg")
.attr("width", winWidth - 50)
.attr("height", winHeight - 100)
.attr("pointer-events", "all")
.append('g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append("g")
.attr("transform", "translate(" + winHeight / 2 + "," + winHeight / 2 + ")");
// this is the image background
// XXX there must be a way to discover the radial tree's size?
svg.append('rect')
.attr("x", (0 - (winHeight * 2)))
.attr('width', "400%")
.attr("y", (0 - (winHeight * 2)))
.attr('height', "400%")
.attr('fill', 'white');
// handle pan and zoom
function redraw() {
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ "scale(" + d3.event.scale + ")"
+ "translate(" + (winHeight / 2) + "," + (winHeight / 2) + ")");
}
// save the x,y of an element into the loc dictionary
function recordLocation(d,i) {
var rect = this.getBoundingClientRect();
loc[d.ip] = {
'x': (rect.left + ((rect.right - rect.left) / 2))
,'y': (rect.top + ((rect.bottom - rect.top) / 2))
};
}
// convert a device ip to a valid CSS class name
function to_class(ip) { return 'nd_' + ip.replace(/[:.]/g, "_") }
// handler for clicking on a circle - redirect to that device's netmap
function circleClick(d) {
window.location = '[% uri_for('/device') %]?tab=netmap'
+ '&q=' + d.ip
+ '&depth=[% params.depth | uri %]'
+ '&vlan=[% params.vlan | uri %]';
}
// handler for mouseover on a circle - show that device's real neighbors
function circleOver(d) {
$('.link').hide();
$('path.' + to_class(d.ip)).show();
$(this).css('cursor', 'pointer');
$.each(neighbors_data[d.ip], function(idx, target) {
if (! (target in loc)) { return true }
$('circle.' + to_class(target)).css('fill', '#e96cfa');
});
}
// handler for mouseout on a circle - hide real neighbours and show treeLinks
function circleOut(d) {
$.each(neighbors_data[d.ip], function(idx, target) {
if (! (target in loc)) { return true }
$('circle.' + to_class(target)).css('fill', '#fff');
});
$(this).css('cursor', 'auto');
$('path.' + to_class(d.ip)).hide();
$('.link').show();
}
// load all device connections into neighbors_data dictionary
$.getJSON('[% uri_for('/ajax/data/device/alldevicelinks') %]', function(data) {
neighbors_data = data;
$.getJSON('[% uri_for('/ajax/data/device/netmap') %]', function(data) {
function resizeGraphContainer() {
setTimeout(function(){
var graph = jQuery('#netmap_pane');
var toc = jQuery('#dw__toc');
netmap_pane.width(
parseInt(graph.parent().css('width')) -
( toc.css('float') === 'right' && parseInt(toc.css('height')) > 30 ? parseInt(toc.css('width')) : 0 )
).resume();
}, 500)
}
// draw the tree
d3.json("[% uri_for('/ajax/data/device/netmap') %]?"
+ '&q=[% params.q | uri %]'
+ '&depth=[% params.depth | uri %]'
+ '&vlan=[% params.vlan | uri %]', function(error, root) {
var tree = d3.layout.tree()
// magic number "8" for scaling (network depth). seems to make things look right for me.
.size([360, (winHeight / 8 * (root['scale'] || 0))])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
var nodes = tree.nodes(root),
links = tree.links(nodes);
var link = svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", treeLink);
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; });
// begin to draw...
d3.select("#nd_waiting").remove();
node.append("circle")
.attr("r", 4.5)
// circle has class name of its device, so we can show/hide it
.attr("class", function(d) { return to_class(d.ip) })
// store the x,y of every circle we've just drawn
.each(recordLocation)
// handlers for mouse interaction with the circles
.on("click", circleClick)
.on("mouseover", circleOver)
.on("mouseout", circleOut);
node.append("text")
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; })
.text(function(d) { return d.name; });
// reorient text on the root node
svg.select(".node")
.attr("transform", function(d) {
return d.x < 180 ? "rotate(0)translate(0)" : "rotate(180)translate(0)"; });
// key (ip) of the root node in our locations store
var rootname = svg.select(".node").data()[0].ip;
// reformatted neighbors_data for the real neighbor links
var neighbors = [];
// need to build neighbors array only after we have built loc dictionary,
// after drawing the circles and storing their x,y
$.each(neighbors_data, function(key, val) {
if (! (key in loc)) { return true }
$.each(val, function(idx, ip) {
if (! (ip in loc)) { return true }
neighbors.push({
'source': {
'ip': key
,'x': loc[key]['x']
,'y': loc[key]['y']
}
,'target': {
'ip': ip
,'x': loc[ip]['x']
,'y': loc[ip]['y']
}
});
});
});
// insert Netdisco neighbor links below circles but above tree links
svg.selectAll(".neighbor")
.data(neighbors)
.enter().insert("path", ".node")
// add class name of source device, so we can show/hide the link
// (also "neighbor" class)
.attr("class", function(d) { return ("neighbor " + to_class( d.source.ip )) })
.attr("d", neighLink)
.attr("transform", "translate(-" + loc[rootname]['x'] + ",-" + loc[rootname]['y'] + ")");
jQuery(document).ready(function() {
window.netmap_pane = netGobrechtsD3Force('netmap_pane')
.debug(true)
// .lassoMode(true)
.start(data);
jQuery(window).on("resize", resizeGraphContainer);
// jQuery('#dw__toc h3').on("click", resizeGraphContainer);
$('#nd_waiting').hide();
resizeGraphContainer();
});
}); // jquery getJSON for all connections
// vim: ft=javascript

View File

@@ -21,6 +21,7 @@
<script type="text/javascript" src="[% uri_base %]/javascripts/underscore.min.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/jquery.qtip.min.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/d3.min.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/d3-force.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/toastr.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/jquery.floatThead.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/daterangepicker.js"></script>
@@ -52,6 +53,7 @@
<link rel="stylesheet" href="[% uri_base %]/css/smoothness/jquery-ui.custom.min.css"/>
<link rel="stylesheet" href="[% uri_base %]/css/font-awesome.min.css"/>
<link rel="stylesheet" href="[% uri_base %]/css/toastr.css"/>
<link rel="stylesheet" href="[% uri_base %]/css/d3-force.css"/>
<link rel="stylesheet" href="[% uri_base %]/css/netdisco.css"/>
<link rel="stylesheet" href="[% uri_base %]/css/bootstrap-tree.css"/>
<link rel="stylesheet" href="[% uri_base %]/css/daterangepicker-bs2.css"/>