Commit e9ab7c48 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Merge branch...

Merge branch 'feat/355-cttc-enhance-network-topology-depicting-with-pan-tilt-zoom-capacities' into 'develop'

Resolve "(CTTC) Enhance Network Topology depicting with Pan/Tilt/Zoom capacities"

See merge request !410
parents 0e3e7470 3a687d23
Loading
Loading
Loading
Loading
+88 −58
Original line number Diff line number Diff line
@@ -31,24 +31,39 @@ const margin = {top: 5, right: 5, bottom: 5, left: 5};
const icon_width  = 40;
const icon_height = 40;

width = 1400 - margin.left - margin.right;
height = 800 - 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')
const topologyContainer = d3.select('#topology');
const svg = topologyContainer.append('svg');
const zoomLayer = svg.append('g');
const graphLayer = zoomLayer.append('g')
    .attr('transform', `translate(${margin.left}, ${margin.top})`);

let width = 0;
let height = 0;
let graphInitialized = false;

function updateCanvasSize() {
    const bounds = topologyContainer.node().getBoundingClientRect();
    width = Math.max(bounds.width - margin.left - margin.right, 200);
    height = Math.max(bounds.height - margin.top - margin.bottom, 200);
    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})`)
        ;
        .attr('height', height + margin.top + margin.bottom);
}
updateCanvasSize();

function handleZoom() {
    zoomLayer.attr('transform', d3.event.transform);
}

const zoom = d3.zoom()
    .scaleExtent([0.2, 8])
    .on('zoom', handleZoom);

svg.call(zoom);

const ZOOM_DURATION_MS = 250;
const ZOOM_SCALE_STEP = 1.2;
const PAN_OFFSET = 80;

// svg objects
var link, node, optical_link, labels;
@@ -67,10 +82,21 @@ forceProperties = {

var simulation = d3.forceSimulation();

function applyDirectionalForces() {
    simulation
        .force("center", d3.forceCenter(width * forceProperties.center.x, 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));
}

// 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')
    link = graphLayer.append("g").attr("class", "links")//.style('stroke', '#aaa')
        .selectAll("line")
        .data(data.links)
        .enter()
@@ -86,7 +112,7 @@ d3.json("{{ url_for('main.topology') }}", function(data) {
            return l.name.toLowerCase().includes('mgmt') ? "5,5" : "0";
        });

    optical_link = svg.append("g").attr("class", "links")//.style('stroke', '#aaa')
    optical_link = graphLayer.append("g").attr("class", "links")//.style('stroke', '#aaa')
        .selectAll("line")
        .data(data.optical_links)
        .enter()
@@ -102,7 +128,7 @@ d3.json("{{ url_for('main.topology') }}", function(data) {
            return l.name.toLowerCase().includes('mgmt') ? "5,5" : "0";
        });

    node = svg.append("g").attr("class", "devices").attr('r', 20).style('fill', '#69b3a2')
    node = graphLayer.append("g").attr("class", "devices").attr('r', 20).style('fill', '#69b3a2')
        .selectAll("circle")
        .data(data.devices)
        .enter()
@@ -117,7 +143,7 @@ d3.json("{{ url_for('main.topology') }}", function(data) {
    // node tooltip
    //node.append("title").text(function(n) { return n.name; });
    // persistent node labels
    labels = svg.append("g").attr("class", "labels")
    labels = graphLayer.append("g").attr("class", "labels")
        .selectAll("text")
        .data(data.devices)
        .enter()
@@ -142,7 +168,7 @@ d3.json("{{ url_for('main.topology') }}", function(data) {

    // add forces, associate each with a name, and set their properties
    // Experimental : Optical link part 
    all_links = data.links.concat(data.optical_links)
    const all_links = data.links.concat(data.optical_links);
    simulation
        .force("link", d3.forceLink()
            .id(function(d) {return d.id;})
@@ -162,19 +188,13 @@ d3.json("{{ url_for('main.topology') }}", function(data) {
        .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));
            .iterations(forceProperties.collide.iterations));

    applyDirectionalForces();
    
    // after each simulation tick, update the display positions
    simulation.on("tick", ticked);
    graphInitialized = true;
});

// update the display positions
@@ -221,30 +241,40 @@ function dragended(d) {
}

// update size-related forces
d3.select(window).on("resize", function(){
    width = +svg.node().getBoundingClientRect().width;
    height = +svg.node().getBoundingClientRect().height;
    simulation.alpha(1).restart();
});
function handleResize() {
    updateCanvasSize();
    if (graphInitialized) {
        applyDirectionalForces();
        simulation.alpha(0.3).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);
//}
d3.select(window).on("resize", handleResize);

/******************** UI ACTIONS *******************/

function resetZoom() {
    svg.transition().duration(ZOOM_DURATION_MS).call(zoom.transform, d3.zoomIdentity);
}

function zoomIn() {
    svg.transition().duration(ZOOM_DURATION_MS).call(zoom.scaleBy, ZOOM_SCALE_STEP);
}

function zoomOut() {
    svg.transition().duration(ZOOM_DURATION_MS).call(zoom.scaleBy, 1 / ZOOM_SCALE_STEP);
}

function center() {
    const centerX = margin.left + width / 2;
    const centerY = margin.top + height / 2;
    svg.transition().duration(ZOOM_DURATION_MS).call(zoom.translateTo, centerX, centerY);
}

function panLeft() {
    svg.transition().duration(ZOOM_DURATION_MS).call(zoom.translateBy, -PAN_OFFSET, 0);
}

function panRight() {
    svg.transition().duration(ZOOM_DURATION_MS).call(zoom.translateBy, PAN_OFFSET, 0);
}
+26 −1
Original line number Diff line number Diff line
@@ -18,6 +18,17 @@

{% block content %}
    <h2>ETSI TeraFlowSDN Controller</h2>
    <style>
        #topology {
            border: 1px solid #b5b5b5;
            border-radius: 0.25rem;
            background-color: #fff;
            width: 100%;
            min-height: 400px;
            height: min(75vh, 850px);
            overflow: hidden;
        }
    </style>

    {% for field, message in context_topology_form.errors.items() %}
        <div class="alert alert-dismissible fade show" role="alert">
@@ -78,7 +89,21 @@
    </form>

    <script src="https://d3js.org/d3.v4.min.js"></script>
    <div id="topology"></div>
    <div class="btn-toolbar mb-3 flex-wrap" role="toolbar" aria-label="Topology controls">
        <div class="btn-group btn-group-sm me-2 mb-2" role="group" aria-label="Zoom controls">
            <button type="button" class="btn btn-outline-secondary" onclick="zoomIn()">Zoom in</button>
            <button type="button" class="btn btn-outline-secondary" onclick="zoomOut()">Zoom out</button>
            <button type="button" class="btn btn-outline-secondary" onclick="resetZoom()">Reset zoom</button>
        </div>
        <div class="btn-group btn-group-sm me-2 mb-2" role="group" aria-label="Pan controls">
            <button type="button" class="btn btn-outline-secondary" onclick="panLeft()">Pan left</button>
            <button type="button" class="btn btn-outline-secondary" onclick="panRight()">Pan right</button>
        </div>
        <div class="btn-group btn-group-sm mb-2" role="group" aria-label="Center control">
            <button type="button" class="btn btn-outline-secondary" onclick="center()">Center</button>
        </div>
    </div>
    <div id="topology" class="mb-4"></div>
    <script src="{{ url_for('js.topology_js') }}"></script>

{% endblock %}