// Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Based on: // https://www.d3-graph-gallery.com/graph/network_basic.html // https://bl.ocks.org/steveharoz/8c3e2524079a8c440df60c1ab72b5d03 // https://www.d3indepth.com/zoom-and-pan/ // Pan & Zoom does not work; to be reviewed // // // // // // // set the dimensions and margins of the graph const margin = {top: 5, right: 5, bottom: 5, left: 5}; const icon_width = 40; const icon_height = 40; width = 1000 - margin.left - margin.right; height = 600 - margin.top - margin.bottom; //function handleZoom(e) { // console.dir(e); // d3.select('svg g').attr('transform', e.transform); //} //let zoom = d3.zoom().scaleExtent([0.01, 10]).translateExtent([[0, 0], [width, height]]).on('zoom', handleZoom); // append the svg object to the body of the page const svg = d3.select('#topology') .append('svg') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom) //.call(zoom) .append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`) ; // svg objects var link, node; // values for all forces forceProperties = { center: {x: 0.5, y: 0.5}, charge: {enabled: true, strength: -500, distanceMin: 10, distanceMax: 2000}, collide: {enabled: true, strength: 0.7, iterations: 1, radius: 5}, forceX: {enabled: false, strength: 0.1, x: 0.5}, forceY: {enabled: false, strength: 0.1, y: 0.5}, link: {enabled: true, distance: 100, iterations: 1} } /**************** FORCE SIMULATION *****************/ var simulation = d3.forceSimulation(); // load the data d3.json("{{ url_for('main.topology') }}", function(data) { // set the data and properties of link lines and node circles link = svg.append("g").attr("class", "links")//.style('stroke', '#aaa') .selectAll("line") .data(data.links) .enter() .append("line") .attr("opacity", 1) .attr("stroke", function(l) { return l.name.toLowerCase().includes('mgmt') ? '#AAAAAA' : '#555555'; }) .attr("stroke-width", function(l) { return l.name.toLowerCase().includes('mgmt') ? 1 : 2; }) .attr("stroke-dasharray", function(l) { return l.name.toLowerCase().includes('mgmt') ? "5,5" : "0"; }); node = svg.append("g").attr("class", "devices").attr('r', 20).style('fill', '#69b3a2') .selectAll("circle") .data(data.devices) .enter() .append("image") .attr('xlink:href', function(d) { return "{{ url_for('static', filename='/topology_icons/') }}" + d.type + ".png"; }) .attr('width', icon_width) .attr('height', icon_height) .call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended)); // node tooltip node.append("title").text(function(n) { return n.name; }); // link tooltip link.append("title").text(function(l) { return l.name; }); // link style //link // .attr("stroke-width", forceProperties.link.enabled ? 2 : 1) // .attr("opacity", forceProperties.link.enabled ? 1 : 0); // set up the simulation and event to update locations after each tick simulation.nodes(data.devices); // add forces, associate each with a name, and set their properties simulation .force("link", d3.forceLink() .id(function(d) {return d.id;}) .distance(forceProperties.link.distance) .iterations(forceProperties.link.iterations) .links(forceProperties.link.enabled ? data.links : [])) .force("charge", d3.forceManyBody() .strength(forceProperties.charge.strength * forceProperties.charge.enabled) .distanceMin(forceProperties.charge.distanceMin) .distanceMax(forceProperties.charge.distanceMax)) .force("collide", d3.forceCollide() .strength(forceProperties.collide.strength * forceProperties.collide.enabled) .radius(forceProperties.collide.radius) .iterations(forceProperties.collide.iterations)) .force("center", d3.forceCenter() .x(width * forceProperties.center.x) .y(height * forceProperties.center.y)) .force("forceX", d3.forceX() .strength(forceProperties.forceX.strength * forceProperties.forceX.enabled) .x(width * forceProperties.forceX.x)) .force("forceY", d3.forceY() .strength(forceProperties.forceY.strength * forceProperties.forceY.enabled) .y(height * forceProperties.forceY.y)); // after each simulation tick, update the display positions simulation.on("tick", ticked); }); // update the display positions function ticked() { link .attr('x1', function(d) { return d.source.x; }) .attr('y1', function(d) { return d.source.y; }) .attr('x2', function(d) { return d.target.x; }) .attr('y2', function(d) { return d.target.y; }); node .attr('x', function(d) { return d.x-icon_width/2; }) .attr('y', function(d) { return d.y-icon_height/2; }); } /******************** UI EVENTS ********************/ function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0.0001); d.fx = null; d.fy = null; } // update size-related forces d3.select(window).on("resize", function(){ width = +svg.node().getBoundingClientRect().width; height = +svg.node().getBoundingClientRect().height; simulation.alpha(1).restart(); }); ///******************** UI ACTIONS *******************/ // //function resetZoom() { // d3.select('svg').transition().call(zoom.scaleTo, 1.0); //} //function zoomIn() { // d3.select('svg').transition().call(zoom.scaleBy, 2.0); //} //function zoomOut() { // d3.select('svg').transition().call(zoom.scaleBy, 0.5); //} // //function center() { // d3.select('svg').transition().call(zoom.translateTo, 0.5 * width, 0.5 * height); //} //function panLeft() { // d3.select('svg').transition().call(zoom.translateBy, -50, 0); //} //function panRight() { // d3.select('svg').transition().call(zoom.translateBy, 50, 0); //}