| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| |
| // Inspired by |
| // https://github.com/samizdatco/arbor/blob/master/docs/sample-project/main.js |
| |
| function renderGraph(elem) { |
| |
| var canvas = $(elem).get(0); |
| canvas.width = $(window).width(); |
| canvas.height = $(window).height(); |
| var ctx = canvas.getContext("2d"); |
| var gfx = arbor.Graphics(canvas); |
| var psys; |
| |
| var totaltrans = 0; |
| var weights = {}; |
| var texts = {}; |
| var update = false; |
| |
| var myRenderer = { |
| init: function(system){ |
| psys = system; |
| psys.screenSize(canvas.width, canvas.height) |
| psys.screenPadding(20); |
| myRenderer.initMouseHandling(); |
| }, |
| |
| signal_update: function() { |
| update = true; |
| }, |
| |
| redraw: function() { |
| |
| if(!psys) |
| return; |
| |
| if(update) { |
| totaltrans = calculate_total_transmitted(psys); |
| weights = calculate_weights(psys, totaltrans); |
| texts = calculate_texts(psys, totaltrans); |
| update = false; |
| } |
| |
| |
| |
| ctx.fillStyle = "white"; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| var x = 0; |
| |
| |
| psys.eachEdge(function(edge, pt1, pt2) { |
| |
| var len = Math.sqrt(Math.pow(pt2.x - pt1.x,2) + Math.pow(pt2.y - pt1.y,2)); |
| var sublen = len - (Math.max(50, 20 + gfx.textWidth(edge.target.name)) / 2); |
| var thirdlen = len/3; |
| var theta = Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x); |
| |
| var newpt2 = { |
| x : pt1.x + (Math.cos(theta) * sublen), |
| y : pt1.y + (Math.sin(theta) * sublen) |
| }; |
| |
| var thirdpt = { |
| x: pt1.x + (Math.cos(theta) * thirdlen), |
| y: pt1.y + (Math.sin(theta) * thirdlen) |
| } |
| |
| weight = weights[edge.source.name + edge.target.name]; |
| |
| if(!weights[edge.source.name + edge.target.name]) |
| { |
| totaltrans = calculate_total_transmitted(psys); |
| weights = calculate_weights(psys, totaltrans); |
| } |
| |
| ctx.strokeStyle = "rgba(0,0,0, .333)"; |
| ctx.lineWidth = 25 * weight + 5; |
| ctx.beginPath(); |
| |
| var arrlen = 15; |
| ctx.moveTo(pt1.x, pt1.y); |
| ctx.lineTo(newpt2.x, newpt2.y); |
| ctx.lineTo(newpt2.x - arrlen * Math.cos(theta-Math.PI/6), newpt2.y - arrlen * Math.sin(theta - Math.PI/6)); |
| ctx.moveTo(newpt2.x, newpt2.y); |
| ctx.lineTo(newpt2.x - arrlen * Math.cos(theta+Math.PI/6), newpt2.y - arrlen * Math.sin(theta + Math.PI/6)); |
| |
| |
| if (texts[edge.source.name + edge.target.name] == null) |
| { |
| totaltrans = calculate_total_transmitted(psys); |
| texts = calculate_texts(psys, totaltrans); |
| } |
| |
| gfx.text(texts[edge.source.name + edge.target.name], thirdpt.x, thirdpt.y + 10, {color:"black", align:"center", font:"Arial", size:10}) |
| ctx.stroke(); |
| }); |
| |
| psys.eachNode(function(node, pt) { |
| var col; |
| |
| var real_trans = gather_stream_count(node.data[":stats"], "default", "600"); |
| |
| if(node.data[":type"] === "bolt") { |
| var cap = Math.min(node.data[":capacity"], 1); |
| var red = Math.floor(cap * 225) + 30; |
| var green = Math.floor(255 - red); |
| var blue = Math.floor(green/5); |
| col = arbor.colors.encode({r:red,g:green,b:blue,a:1}); |
| } else { |
| col = "#0000FF"; |
| } |
| |
| var w = Math.max(55, 25 + gfx.textWidth(node.name)); |
| |
| gfx.oval(pt.x - w/2, pt.y - w/2, w, w, {fill: col}); |
| gfx.text(node.name, pt.x, pt.y+3, {color:"white", align:"center", font:"Arial", size:12}); |
| gfx.text(node.name, pt.x, pt.y+3, {color:"white", align:"center", font:"Arial", size:12}); |
| |
| gfx.text(parseFloat(node.data[":latency"]).toFixed(2) + " ms", pt.x, pt.y + 17, {color:"white", align:"center", font:"Arial", size:12}); |
| |
| }); |
| |
| // Draw gradient sidebar |
| ctx.rect(0,0,50,canvas.height); |
| var grd = ctx.createLinearGradient(0,0,50,canvas.height); |
| grd.addColorStop(0, '#1ee12d'); |
| grd.addColorStop(1, '#ff0000'); |
| ctx.fillStyle=grd; |
| ctx.fillRect(0,0,50,canvas.height); |
| |
| |
| }, |
| |
| initMouseHandling:function() { |
| var dragged = null; |
| |
| var clicked = false; |
| |
| var handler = { |
| clicked:function(e){ |
| var pos = $(canvas).offset(); |
| _mouseP = arbor.Point(e.pageX-pos.left, e.pageY - pos.top); |
| dragged = psys.nearest(_mouseP); |
| |
| if(dragged && dragged.node !== null) { |
| dragged.node.fixed = true; |
| } |
| |
| clicked = true; |
| setTimeout(function(){clicked = false;}, 50); |
| |
| $(canvas).bind('mousemove', handler.dragged); |
| $(window).bind('mouseup', handler.dropped); |
| |
| return false; |
| }, |
| |
| dragged:function(e) { |
| |
| var pos = $(canvas).offset(); |
| var s = arbor.Point(e.pageX-pos.left, e.pageY-pos.top); |
| |
| if(dragged && dragged.node != null) { |
| var p = psys.fromScreen(s); |
| dragged.node.p = p; |
| } |
| |
| return false; |
| |
| }, |
| |
| dropped:function(e) { |
| if(clicked) { |
| if(dragged.distance < 50) { |
| if(dragged && dragged.node != null) { |
| window.location = dragged.node.data[":link"]; |
| } |
| } |
| } |
| |
| if(dragged === null || dragged.node === undefined) return; |
| if(dragged.node !== null) dragged.node.fixed = false; |
| dragged.node.tempMass = 1000; |
| dragged = null; |
| $(canvas).unbind('mousemove', handler.dragged); |
| $(window).unbind('mouseup', handler.dropped); |
| _mouseP = null; |
| return false; |
| } |
| |
| } |
| |
| $(canvas).mousedown(handler.clicked); |
| } |
| } |
| |
| return myRenderer; |
| } |
| |
| function calculate_texts(psys, totaltrans) { |
| var texts = {}; |
| psys.eachEdge(function(edge, pt1, pt2) { |
| var text = ""; |
| for(var i = 0; i < edge.target.data[":inputs"].length; i++) { |
| var stream = edge.target.data[":inputs"][i][":stream"]; |
| var sani_stream = edge.target.data[":inputs"][i][":sani-stream"]; |
| if(stream_checked(sani_stream) |
| && edge.target.data[":inputs"][i][":component"] === edge.source.name) { |
| stream_transfered = gather_stream_count(edge.source.data[":stats"], sani_stream, "600"); |
| text += stream + ": " |
| + stream_transfered + ": " |
| + (totaltrans > 0 ? Math.round((stream_transfered/totaltrans) * 100) : 0) + "%\n"; |
| |
| } |
| } |
| |
| texts[edge.source.name + edge.target.name] = text; |
| }); |
| |
| return texts; |
| } |
| |
| function calculate_weights(psys, totaltrans) { |
| var weights = {}; |
| |
| psys.eachEdge(function(edge, pt1, pt2) { |
| var trans = 0; |
| for(var i = 0; i < edge.target.data[":inputs"].length; i++) { |
| var stream = edge.target.data[":inputs"][i][":sani-stream"]; |
| if(stream_checked(stream) && edge.target.data[":inputs"][i][":component"] === edge.source.name) |
| trans += gather_stream_count(edge.source.data[":stats"], stream, "600"); |
| } |
| weights[edge.source.name + edge.target.name] = (totaltrans > 0 ? trans/totaltrans : 0); |
| }); |
| return weights; |
| } |
| |
| function calculate_total_transmitted(psys) { |
| var totaltrans = 0; |
| var countedmap = {} |
| psys.eachEdge(function(node, pt, pt2) { |
| if(!countedmap[node.source.name]) |
| countedmap[node.source.name] = {}; |
| |
| for(var i = 0; i < node.target.data[":inputs"].length; i++) { |
| var stream = node.target.data[":inputs"][i][":stream"]; |
| if(stream_checked(node.target.data[":inputs"][i][":sani-stream"])) |
| { |
| if(!countedmap[node.source.name][stream]) { |
| if(node.source.data[":stats"]) |
| { |
| var toadd = gather_stream_count(node.source.data[":stats"], node.target.data[":inputs"][i][":sani-stream"], "600"); |
| totaltrans += toadd; |
| } |
| countedmap[node.source.name][stream] = true; |
| } |
| } |
| } |
| |
| }); |
| |
| return totaltrans; |
| } |
| |
| function has_checked_stream_input(inputs) { |
| |
| for(var i = 0; i < inputs.length; i++) { |
| var x = stream_checked(inputs[i][":sani-stream"]); |
| if(x) |
| return true; |
| } |
| return false; |
| } |
| |
| function stream_checked(stream) { |
| var checked = $("#" + stream).is(":checked"); |
| return checked; |
| } |
| |
| function has_checked_stream_output(jdat, component) { |
| var ret = false; |
| $.each(jdat, function(k, v) { |
| for(var i = 0; i < v[":inputs"].length; i++) { |
| if(stream_checked(v[":inputs"][i][":sani-stream"]) |
| && v[":inputs"][i][":component"] == component) |
| ret = true; |
| } |
| }); |
| return ret; |
| } |
| |
| function gather_stream_count(stats, stream, time) { |
| var transferred = 0; |
| if(stats) |
| for(var i = 0; i < stats.length; i++) { |
| if(stats[i][":transferred"] != null) |
| { |
| var stream_trans = stats[i][":transferred"][time][stream]; |
| if(stream_trans != null) |
| transferred += stream_trans; |
| } |
| } |
| return transferred; |
| } |
| |
| |
| function rechoose(jdat, sys, box) { |
| var id = box.id; |
| if($(box).is(':checked')) |
| { |
| //Check each node in our json data to see if it has inputs from or outputs to selected streams. If it does, add a node for it. |
| $.each(jdat,function(k,v) { |
| if( has_checked_stream_input(v[":inputs"]) || has_checked_stream_output(jdat, k)) |
| sys.addNode(k,v); |
| }); |
| |
| //Check each node in our json data and add necessary edges based on selected components. |
| $.each(jdat, function(k, v) { |
| for(var i = 0; i < v[":inputs"].length; i++) |
| if(v[":inputs"][i][":sani-stream"] === id) { |
| |
| sys.addEdge(v[":inputs"][i][":component"], k, v); |
| } |
| }); |
| } |
| else { |
| //Check each node to see if it should be pruned. |
| sys.prune(function(node, from, to) { |
| return !has_checked_stream_input(node.data[":inputs"]) && !has_checked_stream_output(jdat, node.name); |
| }); |
| |
| //Check each edge to see if it represents any selected streams. If not, prune it. |
| sys.eachEdge(function(edge, pt1, pt2) { |
| var inputs = edge.target.data[":inputs"]; |
| |
| if($.grep(inputs, function(input) { |
| |
| return input[":component"] === edge.source.name |
| && stream_checked(input[":sani-stream"]); |
| |
| }).length == 0) |
| { |
| sys.pruneEdge(edge); |
| } |
| }); |
| } |
| |
| //Tell the particle system's renderer that it needs to update its labels, colors, widths, etc. |
| sys.renderer.signal_update(); |
| sys.renderer.redraw(); |
| |
| } |
| |
| var topology_data; |
| function update_data(jdat, sys) { |
| $.each(jdat, function(k,v) { |
| if(sys.getNode(k)) |
| sys.getNode(k).data = v; |
| }); |
| } |
| |
| function jsError(other) { |
| try { |
| other(); |
| } catch (err) { |
| $.get("/templates/json-error-template.html", function(template) { |
| $("#json-response-error").append(Mustache.render($(template).filter("#json-error-template").html(),{error: "JS Error", errorMessage: err})); |
| }); |
| } |
| } |
| |
| var should_update; |
| function show_visualization(sys) { |
| $.getJSON("/api/v1/topology/"+$.url("?id")+"/visualization-init",function(response,status,jqXHR) { |
| $.get("/templates/topology-page-template.html", function(template) { |
| jsError(function() { |
| var topologyVisualization = $("#visualization-container"); |
| topologyVisualization.append( |
| Mustache.render($(template) |
| .filter("#topology-visualization-container-template") |
| .html(), |
| response)); |
| }); |
| |
| if(sys == null) |
| { |
| sys = arbor.ParticleSystem(20, 1000, 0.15, true, 55, 0.02, 0.6); |
| sys.renderer = renderGraph("#topoGraph"); |
| sys.stop(); |
| |
| $(".stream-box").click(function () { rechoose(topology_data, sys, this) }); |
| } |
| |
| should_update = true; |
| var update_freq_ms = 10000; |
| var update = function(should_rechoose){ |
| if(should_update) { |
| $.ajax({ |
| url: "/api/v1/topology/"+$.url("?id")+"/visualization", |
| success: function(data, status, jqXHR) { |
| topology_data = data; |
| update_data(topology_data, sys); |
| sys.renderer.signal_update(); |
| sys.renderer.redraw(); |
| if(should_update) |
| setTimeout(update, update_freq_ms); |
| if(should_rechoose) |
| $(".stream-box").each(function () { |
| rechoose(topology_data, sys, this) |
| }); |
| } |
| }); |
| } |
| }; |
| |
| update(true); |
| $("#visualization-container").show(500); |
| $("#show-hide-visualization").attr('value', 'Hide Visualization'); |
| $("#show-hide-visualization").unbind("click"); |
| $("#show-hide-visualization").click(function () { hide_visualization(sys) }); |
| }) |
| }); |
| } |
| |
| function hide_visualization(sys) { |
| should_update = false; |
| $("#visualization-container").hide(500); |
| $("#show-hide-visualization").attr('value', 'Show Visualization'); |
| $("#show-hide-visualization").unbind("click"); |
| $("#show-hide-visualization").click(function () { show_visualization(sys) }); |
| } |