From 9b35c9938128d6d399ad9dfd89a2f0107ab9e448 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Wed, 6 Jun 2012 01:19:31 +0200 Subject: [PATCH] lots of refactoring, prepare for dynamic updates --- html/force.css | 8 +- html/force.js | 484 +++++++++++++++++++++++------------------------- html/nodes.html | 1 + html/zigzag.js | 57 ++++++ 4 files changed, 301 insertions(+), 249 deletions(-) create mode 100644 html/zigzag.js diff --git a/html/force.css b/html/force.css index f79152f..b4141ac 100644 --- a/html/force.css +++ b/html/force.css @@ -8,11 +8,17 @@ color: #333; } -line.link { +.link line { stroke: #333; stroke-opacity: 1; } +.link .label { + fill: transparent; + stroke: #C83771; + stroke-width: 1px; +} + .strength { font-size: 10px; fill: #C83771; diff --git a/html/force.js b/html/force.js index 4cb07cf..2df8ffd 100644 --- a/html/force.js +++ b/html/force.js @@ -1,300 +1,304 @@ -function zigzag_amplitude(d) { - return d.amplitude; -} - -function zigzag_len(d) { - return d.len; -} - -function zigzag_angularFrequency(d) { - return d.angularFrequency; -} - -d3.svg.zigzag = function() { - var amplitude = zigzag_amplitude, - len = zigzag_len, - angularFrequency = zigzag_angularFrequency; - - function zigzag() { - var A = amplitude.apply(this, arguments), - l = len.apply(this, arguments), - ω = angularFrequency.apply(this, arguments) + 1; - - start = -l/2; - end = l/2; - - step = l/ω; - - var s = "M" + start + ",0" - - for (var i = 1; i<ω; i++) - s += "L" + (start + i*step) + "," + ((i%2)?A:-A); - - s += "L" + end + ",0" - - return s; - } - - zigzag.amplitude = function(v) { - if (!arguments.length) return amplitude; - amplitude = d3.functor(v); - return zigzag; - }; - - zigzag.len = function(v) { - if (!arguments.length) return len; - len = d3.functor(v); - return zigzag; - }; - - zigzag.angularFrequency = function(v) { - if (!arguments.length) return angularFrequency; - angularFrequency = d3.functor(v); - return zigzag; - }; - - return zigzag; -}; - - function getOffset( el ) { - var _x = 0; - var _y = 0; + var _x = 0, _y = 0 + while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) { - _x += el.offsetLeft - el.scrollLeft; - _y += el.offsetTop - el.scrollTop; - el = el.offsetParent; + _x += el.offsetLeft - el.scrollLeft + _y += el.offsetTop - el.scrollTop + el = el.offsetParent } - return { top: _y, left: _x }; + return { top: _y, left: _x } } -var offset = getOffset(document.getElementById('chart')); +var offset = getOffset(document.getElementById('chart')) var w = window.innerWidth - offset.left, h = window.innerHeight - offset.top, - fill = d3.scale.category20(); + fill = d3.scale.category20() var cp = d3.select("#chart").append("div") -.attr("id", "controlpanel"); + .attr("id", "controlpanel") var btns = cp.append("div") -.attr("class", "btn-group") -.attr("data-toggle", "buttons-radio"); + .attr("class", "btn-group") + .attr("data-toggle", "buttons-radio") btns.append("button") -.attr("class", "btn active left") -.attr("value", "all") -.text("Alles") -.on("click", update_graph); + .attr("class", "btn active left") + .attr("value", "all") + .text("Alles") + .on("click", update_graph) btns.append("button") -.attr("class", "btn right") -.attr("value", "mesh") -.text("nur Mesh") -.on("click", update_graph); + .attr("class", "btn right") + .attr("value", "mesh") + .text("nur Mesh") + .on("click", update_graph) cp.append("label") - .text("Knoten hervorheben:"); + .text("Knoten hervorheben:") -cp.append("br"); +cp.append("br") cp.append("input") .on("keyup", function(){show_node(this.value)}) - .on("change", function(){show_node(this.value)}); + .on("change", function(){show_node(this.value)}) function show_node(mac) { - d3.selectAll("#chart .node").classed("marked", false); - if (mac.length == 0) - return; + d3.selectAll("#chart .node") + .classed("marked", false) - d3.selectAll("#chart .node").each( function(d) { - if (d.id == mac) - d3.select(this).classed("marked", true); - }) + if (mac.length == 0) + return + + d3.selectAll("#chart .node") + .each( function(d) { + if (d.id == mac) + d3.select(this) + .classed("marked", true) + }) } -var hashstr = window.location.hash.substring(1); +var hashstr = window.location.hash.substring(1) + +function isConnected(a, b) { + return linkedByIndex[a.index + "," + b.index] || + linkedByIndex[b.index + "," + a.index] || + a.index == b.index +} + +function fade(opacity) { + return function(d) { + vis.selectAll("g.node") +   .style("stroke-opacity", function(o) { + var connected = isConnected(d, o) + + if (connected && opacity != 1) + d3.select(this) + .classed("highlight", true) + else + d3.select(this) + .classed("highlight", false) + +         thisOpacity = connected?1:opacity +    this.setAttribute('fill-opacity', thisOpacity) +         return thisOpacity +  }) + + vis.selectAll(".link *") +   .style("stroke-opacity", function(o) { +  return o.source === d || o.target === d ? 1 : opacity +  }) +  } +} + +function show_node_info(d) { + if (typeof nodeinfo !== 'undefined') + nodeinfo.remove(); + + nodeinfo = d3.select("#chart") + .append("div") + .attr("id", "nodeinfo") + nodeinfo.append("h1") + .text(d.name) + nodeinfo.append("p") + .text("primary: " + d.id) + nodeinfo.append("p") + .text("macs: " + d.macs) + nodeinfo.append("p") + .text(d.gps) +} function update_graph() { var value = jQuery(this).val() - - render_graph(value); + update(data, value) } -render_graph("all"); +var i, j -function render_graph(type) { +var vis = d3.select("#chart").append("svg") + .attr("width", w) + .attr("height", h) - d3.select("#chart svg").remove(); +vis.append("g").attr("class", "links") - var vis = d3.select("#chart").append("svg") - .attr("width", w) - .attr("height", h); +vis.append("g").attr("class", "nodes") +var linkedByIndex +var force = d3.layout.force() + .charge(-100) + .gravity(0.02) + .friction(0.75) + .theta(0.1) + .size([w, h]) - d3.json("nodes.json", function(json) { - var force = d3.layout.force() - .charge(-100) - .gravity(0.02) - .friction(0.75) - .theta(0.1) - .linkDistance(function (d) { return d.distance; }) - .linkStrength(function (d) { return d.strength; }) - .nodes(json.nodes) - .links(json.links) - .size([w, h]) - .start(); +force.on("tick", function() { + var link = vis.selectAll(".link") -   var linkedByIndex = {}; + link.selectAll("line") + .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 }) -   json.links.forEach(function(d) { -       linkedByIndex[d.source.index + "," + d.target.index] = 1; -   }); + link.selectAll(".label") + .attr("transform", function(d) { + π = Math.PI + Δx = d.source.x - d.target.x + Δy = d.source.y - d.target.y + m = Δy/Δx + α = Math.atan(m) + α += Δx<0?π:0 + sin = Math.sin(α) + cos = Math.cos(α) + x = (Math.min(d.source.x, d.target.x) + Math.abs(Δx) / 2) + y = (Math.min(d.source.y, d.target.y) + Math.abs(Δy) / 2) + return "matrix(" + [cos, sin, -sin, cos, x, y].join(",") + ")" + }) -   json.links.forEach(function(d) { - var node, other; + vis.selectAll(".node").attr("transform", function(d) { + return "translate(" + d.x + "," + d.y + ")"; + }) +}) - if (d.source.group == 2) { - node = d.target; - other = d.source; - } +var data - if (d.target.group == 2) { - node = d.source; - other = d.target; - } - - if (node) { - if (node.uplinks === undefined) - node.uplinks = new Array(); +d3.json("nodes.json", function(json) { + force.linkDistance(function (d) { return d.distance; }) + .linkStrength(function (d) { return d.strength; }) + .start() - node.uplinks.push(other); - } -   }); + json.links.forEach(function(d) { + var node, other - var linkdata = json.links; + if (d.source.group == 2) { + node = d.target; + other = d.source; + } - if (type == "mesh") - linkdata = json.links.filter( function (d) { - return d.source.group != 2 && d.target.group != 2 && - d.source.group != 3 && d.target.group != 3; - }); + if (d.target.group == 2) { + node = d.source; + other = d.target; + } + if (node) { + if (node.uplinks === undefined) + node.uplinks = new Array(); - var link = vis.selectAll("line.link") - .data(linkdata) - .enter().append("line") - .attr("class", "link") - .style("stroke-width", function(d) { return Math.min(1, d.strength * 2); }); + node.uplinks.push(other); + } + }) - var linklabel = vis.selectAll("g") - .data(linkdata.filter(function (d) {return d.quality != "TT" && d.quality != "1.000"})) - .enter() - .append("g") - .append("path") - .attr("d", d3.svg.zigzag().amplitude(function (d) { - return Math.pow((1 - 1/d.quality), 0.5) * 8; - }).len(30).angularFrequency(4) -/* - d3.svg.arc().outerRadius(5).innerRadius(0) - .startAngle( function (d) { - return Math.PI/d.quality - Math.PI/2; - }) - .endAngle( function (d) { - return -Math.PI/d.quality - Math.PI/2; - }) - */ - ) - .attr("fill", "transparent") - .attr("stroke", "#C83771") - .attr("stroke-width", "1px") + data = json; -     function isConnected(a, b) { -         return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index; -     } + update(data, "all") +}) +function update(src, type) { + var linkdata = data.links; -  function fade(opacity) { -         return function(d) { -             node.style("stroke-opacity", function(o) { - var connected = isConnected(d, o); + var links = force.links(data.links + .filter(function (d) { + return type != "mesh" || + d.source.group != 2 && + d.source.group != 3 && + d.target.group != 2 && + d.target.group != 3 + }) + ).links() - if (connected && opacity != 1) - d3.select(this).classed("highlight", true); - else - d3.select(this).classed("highlight", false); + var link = vis.select("g.links") + .selectAll("g.link") + .data(links, function(d) { + return d.id || (d.id = ++j) + }) -                 thisOpacity = connected ? 1 : opacity; -                 this.setAttribute('fill-opacity', thisOpacity); -                 return thisOpacity; -             }); + var linkEnter = link.enter().append("g") + .attr("class", "link") -             link.style("stroke-opacity", function(o) { -                 return o.source === d || o.target === d ? 1 : opacity; -             }); -             linklabel.style("opacity", function(o) { -                 return o.source === d || o.target === d ? 1 : opacity; -             }); -         }; -     } + linkEnter.append("line") + .style("stroke-width", function(d) { + return Math.min(1, d.strength * 2) + }) - function show_node_info(d) { - if (!(typeof nodeinfo === 'undefined')) - nodeinfo.remove(); + linkEnter.filter(function (d) { + return d.quality != "TT" && d.quality != "1.000" + }) + .append("path") + .attr("class", "label") + .attr("d", d3.svg.zigzag() + .amplitude(function (d) { + return Math.pow((1 - 1/d.quality), 0.5) * 8; + }) + .len(30) + .angularFrequency(4) + ) - nodeinfo = d3.select("#chart").append("div") - .attr("id", "nodeinfo"); + link.exit().remove() - nodeinfo.append("h1") - .text(d.name); + var nodes = force.nodes(data.nodes + .filter(function (d) { + return type != "mesh" || + (d.group != 2 && d.group != 3) + }) + ).nodes() - nodeinfo.append("p") - .text("primary: " + d.id); + var node = vis.select("g.nodes") + .selectAll("g.node") + .data(nodes, + function(d) { + return d.id || (d.id = ++i) + } + ) + var nodeEnter = node.enter().append("g") + .attr("class", "node") + .on("mouseover", fade(.2)) + .on("mouseout", fade(1)) + .on("click", show_node_info) + .call(force.drag) - nodeinfo.append("p") - .text("macs: " + d.macs); + nodeEnter.append("ellipse") + .attr("rx", function(d) { + if (d.group == 3) return 4 + else return Math.max(10, d.name.length * 5) + }) + .attr("ry", function(d) { + if (d.group == 3) return 4 + else return 10 + }) + .style("fill", function(d) { + if (d.group == 3) return fill(d.group) + else return "" + }) + .style("stroke", function(d) { + return fill(d.group) + }) - nodeinfo.append("p") - .text(d.gps); - } - - var node = vis.selectAll("svg.node") - .data(json.nodes.filter(function (d) { - return type != "mesh" || (d.group != 2 && d.group != 3); - })) - .enter().append("g") - .attr("class", "node") - .on("mouseover", fade(.2)) - .on("mouseout", fade(1)) - .on("click", show_node_info) - .call(force.drag); - - node.append("ellipse") - .attr("rx", function(d) { if (d.group == 3) return 4; else return Math.max(10, d.name.length * 5); }) - .attr("ry", function(d) { if (d.group == 3) return 4; else return 10; }) - .style("fill", function(d) { if (d.group == 3) return fill(d.group); else return ""; }) - .style("stroke", function(d) { return fill(d.group); }); - - node.append("text") + nodeEnter.filter(function(d) { + return d.group != 3 + }) + .append("text") .attr("text-anchor", "middle") .attr("y", "4px") - .text(function(d) { if (d.group == 3) return ""; else return d.name; }); + .text(function(d) { return d.name }) - node.append("title") - .text(function(d) { return d.macs; }); + nodeEnter.append("title") + .text(function(d) { return d.macs }) if (type == "mesh") { var uplink_info = node.filter(function (d) { if (d.uplinks !== undefined) - return d.uplinks.length > 0; + return d.uplinks.length > 0; else - return false; + return false; }) .append("g"); uplink_info.append("path") - .attr("d","m -2.8850049,-13.182327 c 7.5369165,0.200772 12.1529864,-1.294922 12.3338513,-10.639456 l 2.2140476,1.018191 -3.3137621,-5.293097 -3.2945999,5.20893 2.4339957,-0.995747 c -0.4041883,5.76426 -1.1549641,10.561363 -10.3735326,10.701179 z") + .attr("d","m -2.8850049,-13.182327" + + "c 7.5369165,0.200772 12.1529864,-1.294922 12.3338513,-10.639456" + + "l 2.2140476,1.018191 -3.3137621,-5.293097 -3.2945999,5.20893 2.4339957,-0.995747" + + "c -0.4041883,5.76426 -1.1549641,10.561363 -10.3735326,10.701179 z") .style("fill", "#333"); uplink_info.append("text") @@ -303,32 +307,16 @@ function render_graph(type) { .text(function (d) {return d.uplinks.length}); } - force.on("tick", function() { - 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.exit().remove() - linklabel - .attr("transform", function(d) { - π = Math.PI; - Δx = d.source.x - d.target.x; - Δy = d.source.y - d.target.y; - m = Δy/Δx; - α = Math.atan(m); - α += Δx<0?π:0; - sin = Math.sin(α); - cos = Math.cos(α); - x = (Math.min(d.source.x, d.target.x) + Math.abs(Δx) / 2) - y = (Math.min(d.source.y, d.target.y) + Math.abs(Δy) / 2) - return "matrix(" + [cos, sin, -sin, cos, x, y].join(",") + ")"; - }); + force.start() - node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); - }); + linkedByIndex = {} + + links.forEach(function(d) { + linkedByIndex[d.source.index + "," + d.target.index] = 1 + }) if (hashstr.length != 0) - show_node(hashstr); - }); - + show_node(hashstr) } diff --git a/html/nodes.html b/html/nodes.html index 3786d5a..30fb894 100644 --- a/html/nodes.html +++ b/html/nodes.html @@ -10,6 +10,7 @@ +
diff --git a/html/zigzag.js b/html/zigzag.js new file mode 100644 index 0000000..66a8803 --- /dev/null +++ b/html/zigzag.js @@ -0,0 +1,57 @@ +function zigzag_amplitude(d) { + return d.amplitude; +} + +function zigzag_len(d) { + return d.len; +} + +function zigzag_angularFrequency(d) { + return d.angularFrequency; +} + +d3.svg.zigzag = function() { + var amplitude = zigzag_amplitude, + len = zigzag_len, + angularFrequency = zigzag_angularFrequency; + + function zigzag() { + var A = amplitude.apply(this, arguments), + l = len.apply(this, arguments), + ω = angularFrequency.apply(this, arguments) + 1; + + start = -l/2; + end = l/2; + + step = l/ω; + + var s = "M" + start + ",0"; + + for (var i = 1; i<ω; i++) + s += "L" + (start + i*step) + "," + ((i%2)?A:-A); + + s += "L" + end + ",0"; + + return s; + } + + zigzag.amplitude = function(v) { + if (!arguments.length) return amplitude; + amplitude = d3.functor(v); + return zigzag; + }; + + zigzag.len = function(v) { + if (!arguments.length) return len; + len = d3.functor(v); + return zigzag; + }; + + zigzag.angularFrequency = function(v) { + if (!arguments.length) return angularFrequency; + angularFrequency = d3.functor(v); + return zigzag; + }; + + return zigzag; +};