basic support for multiple charts
This commit is contained in:
parent
eb67713db7
commit
536ef57121
96
html/bootstrap-button.js
vendored
Normal file
96
html/bootstrap-button.js
vendored
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/* ============================================================
|
||||||
|
* bootstrap-button.js v2.0.4
|
||||||
|
* http://twitter.github.com/bootstrap/javascript.html#buttons
|
||||||
|
* ============================================================
|
||||||
|
* Copyright 2012 Twitter, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
* ============================================================ */
|
||||||
|
|
||||||
|
|
||||||
|
!function ($) {
|
||||||
|
|
||||||
|
"use strict"; // jshint ;_;
|
||||||
|
|
||||||
|
|
||||||
|
/* BUTTON PUBLIC CLASS DEFINITION
|
||||||
|
* ============================== */
|
||||||
|
|
||||||
|
var Button = function (element, options) {
|
||||||
|
this.$element = $(element)
|
||||||
|
this.options = $.extend({}, $.fn.button.defaults, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.prototype.setState = function (state) {
|
||||||
|
var d = 'disabled'
|
||||||
|
, $el = this.$element
|
||||||
|
, data = $el.data()
|
||||||
|
, val = $el.is('input') ? 'val' : 'html'
|
||||||
|
|
||||||
|
state = state + 'Text'
|
||||||
|
data.resetText || $el.data('resetText', $el[val]())
|
||||||
|
|
||||||
|
$el[val](data[state] || this.options[state])
|
||||||
|
|
||||||
|
// push to event loop to allow forms to submit
|
||||||
|
setTimeout(function () {
|
||||||
|
state == 'loadingText' ?
|
||||||
|
$el.addClass(d).attr(d, d) :
|
||||||
|
$el.removeClass(d).removeAttr(d)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.prototype.toggle = function () {
|
||||||
|
var $parent = this.$element.parent('[data-toggle="buttons-radio"]')
|
||||||
|
|
||||||
|
$parent && $parent
|
||||||
|
.find('.active')
|
||||||
|
.removeClass('active')
|
||||||
|
|
||||||
|
this.$element.toggleClass('active')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* BUTTON PLUGIN DEFINITION
|
||||||
|
* ======================== */
|
||||||
|
|
||||||
|
$.fn.button = function (option) {
|
||||||
|
return this.each(function () {
|
||||||
|
var $this = $(this)
|
||||||
|
, data = $this.data('button')
|
||||||
|
, options = typeof option == 'object' && option
|
||||||
|
if (!data) $this.data('button', (data = new Button(this, options)))
|
||||||
|
if (option == 'toggle') data.toggle()
|
||||||
|
else if (option) data.setState(option)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.button.defaults = {
|
||||||
|
loadingText: 'loading...'
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.button.Constructor = Button
|
||||||
|
|
||||||
|
|
||||||
|
/* BUTTON DATA-API
|
||||||
|
* =============== */
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
$('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) {
|
||||||
|
var $btn = $(e.target)
|
||||||
|
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
|
||||||
|
$btn.button('toggle')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}(window.jQuery);
|
9
html/bootstrap.min.css
vendored
Normal file
9
html/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -27,16 +27,24 @@ line.link {
|
||||||
fill: #FFF0B3;
|
fill: #FFF0B3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nodeinfo {
|
#nodeinfo, #controlpanel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
|
||||||
left: 10px;
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
font-family: arial, helvatica;
|
font-family: arial, helvatica;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nodeinfo {
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controlpanel {
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
#nodeinfo h1, p {
|
#nodeinfo h1, p {
|
||||||
color: #555;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
@ -50,3 +58,70 @@ line.link {
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
color: #6e6e6e;
|
||||||
|
font: bold 12px Helvetica, Arial, sans-serif;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 7px 12px;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
text-shadow: 0 1px 0 #fff;
|
||||||
|
-webkit-transition: border-color .218s;
|
||||||
|
-moz-transition: border .218s;
|
||||||
|
-o-transition: border-color .218s;
|
||||||
|
transition: border-color .218s;
|
||||||
|
background: #f3f3f3;
|
||||||
|
background: -webkit-gradient(linear,0% 40%,0% 70%,from(#F5F5F5),to(#F1F1F1));
|
||||||
|
background: -moz-linear-gradient(linear,0% 40%,0% 70%,from(#F5F5F5),to(#F1F1F1));
|
||||||
|
border: solid 1px #dcdcdc;
|
||||||
|
border-radius: 2px;
|
||||||
|
-webkit-border-radius: 2px;
|
||||||
|
-moz-border-radius: 2px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
color: #333;
|
||||||
|
border-color: #999;
|
||||||
|
-moz-box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2) -webkit-box-shadow:0 2px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
color: #000;
|
||||||
|
border-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.left {
|
||||||
|
-webkit-border-top-right-radius: 0;
|
||||||
|
-moz-border-radius-topright: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
-webkit-border-bottom-right-radius: 0;
|
||||||
|
-moz-border-radius-bottomright: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.middle {
|
||||||
|
border-left: solid 1px #f3f3f3;
|
||||||
|
margin: 0;
|
||||||
|
border-left: solid 1px rgba(255, 255, 255, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.right {
|
||||||
|
-webkit-border-top-left-radius: 0;
|
||||||
|
-moz-border-radius-topleft: 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
-webkit-border-bottom-left-radius: 0;
|
||||||
|
-moz-border-radius-bottomleft: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-left: solid 1px #f3f3f3;
|
||||||
|
border-left: solid 1px rgba(255, 255, 255, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.active {
|
||||||
|
background: #C83771;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
197
html/force.js
197
html/force.js
|
@ -1,12 +1,12 @@
|
||||||
function getOffset( el ) {
|
function getOffset( el ) {
|
||||||
var _x = 0;
|
var _x = 0;
|
||||||
var _y = 0;
|
var _y = 0;
|
||||||
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
|
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
|
||||||
_x += el.offsetLeft - el.scrollLeft;
|
_x += el.offsetLeft - el.scrollLeft;
|
||||||
_y += el.offsetTop - el.scrollTop;
|
_y += el.offsetTop - el.scrollTop;
|
||||||
el = el.offsetParent;
|
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'));
|
||||||
|
@ -15,71 +15,113 @@ var w = window.innerWidth - offset.left,
|
||||||
h = window.innerHeight - offset.top,
|
h = window.innerHeight - offset.top,
|
||||||
fill = d3.scale.category20();
|
fill = d3.scale.category20();
|
||||||
|
|
||||||
var vis = d3.select("#chart").append("svg")
|
var cp = d3.select("#chart").append("div")
|
||||||
|
.attr("id", "controlpanel");
|
||||||
|
|
||||||
|
var btns = cp.append("div")
|
||||||
|
.attr("class", "btn-group")
|
||||||
|
.attr("data-toggle", "buttons-radio");
|
||||||
|
|
||||||
|
btns.append("button")
|
||||||
|
.attr("class", "btn active left")
|
||||||
|
.attr("value", "all")
|
||||||
|
.text("Everything")
|
||||||
|
.on("click", update_graph);
|
||||||
|
|
||||||
|
btns.append("button")
|
||||||
|
.attr("class", "btn right")
|
||||||
|
.attr("value", "mesh")
|
||||||
|
.text("Mesh only")
|
||||||
|
.on("click", update_graph);
|
||||||
|
|
||||||
|
function update_graph() {
|
||||||
|
var value = jQuery(this).val()
|
||||||
|
|
||||||
|
render_graph(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
render_graph("all");
|
||||||
|
|
||||||
|
function render_graph(type) {
|
||||||
|
|
||||||
|
d3.select("#chart svg").remove();
|
||||||
|
|
||||||
|
var vis = d3.select("#chart").append("svg")
|
||||||
.attr("width", w)
|
.attr("width", w)
|
||||||
.attr("height", h);
|
.attr("height", 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();
|
|
||||||
|
|
||||||
var linkedByIndex = {};
|
|
||||||
|
|
||||||
json.links.forEach(function(d) {
|
d3.json("nodes.json", function(json) {
|
||||||
linkedByIndex[d.source.index + "," + d.target.index] = 1;
|
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();
|
||||||
|
|
||||||
|
var linkedByIndex = {};
|
||||||
|
|
||||||
|
json.links.forEach(function(d) {
|
||||||
|
linkedByIndex[d.source.index + "," + d.target.index] = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
var linkdata = json.links;
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
var link = vis.selectAll("line.link")
|
var link = vis.selectAll("line.link")
|
||||||
.data(json.links)
|
.data(linkdata)
|
||||||
.enter().append("line")
|
.enter().append("line")
|
||||||
.attr("class", "link")
|
.attr("class", "link")
|
||||||
.style("stroke-width", function(d) { return Math.min(1, d.strength * 2); })
|
.style("stroke-width", function(d) { return Math.min(1, d.strength * 2); })
|
||||||
.attr("x1", function(d) { return d.source.x; })
|
.attr("x1", function(d) { return d.source.x; })
|
||||||
.attr("y1", function(d) { return d.source.y; })
|
.attr("y1", function(d) { return d.source.y; })
|
||||||
.attr("x2", function(d) { return d.target.x; })
|
.attr("x2", function(d) { return d.target.x; })
|
||||||
.attr("y2", function(d) { return d.target.y; });
|
.attr("y2", function(d) { return d.target.y; });
|
||||||
|
|
||||||
function isConnected(a, b) {
|
function isConnected(a, b) {
|
||||||
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
|
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function fade(opacity) {
|
function fade(opacity) {
|
||||||
return function(d) {
|
return function(d) {
|
||||||
node.style("stroke-opacity", function(o) {
|
node.style("stroke-opacity", function(o) {
|
||||||
var connected = isConnected(d, o);
|
var connected = isConnected(d, o);
|
||||||
|
|
||||||
if (connected && opacity != 1)
|
if (connected && opacity != 1)
|
||||||
d3.select(this).classed("highlight", true);
|
d3.select(this).classed("highlight", true);
|
||||||
else
|
else
|
||||||
d3.select(this).classed("highlight", false);
|
d3.select(this).classed("highlight", false);
|
||||||
|
|
||||||
thisOpacity = connected ? 1 : opacity;
|
thisOpacity = connected ? 1 : opacity;
|
||||||
this.setAttribute('fill-opacity', thisOpacity);
|
this.setAttribute('fill-opacity', thisOpacity);
|
||||||
return thisOpacity;
|
return thisOpacity;
|
||||||
});
|
});
|
||||||
|
|
||||||
link.style("stroke-opacity", function(o) {
|
link.style("stroke-opacity", function(o) {
|
||||||
return o.source === d || o.target === d ? 1 : opacity;
|
return o.source === d || o.target === d ? 1 : opacity;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function show_node_info(d) {
|
function show_node_info(d) {
|
||||||
if (!(typeof nodeinfo === 'undefined'))
|
if (!(typeof nodeinfo === 'undefined'))
|
||||||
nodeinfo.remove();
|
nodeinfo.remove();
|
||||||
|
|
||||||
nodeinfo = d3.select("#chart").append("div")
|
nodeinfo = d3.select("#chart").append("div")
|
||||||
.attr("id", "nodeinfo");
|
.attr("id", "nodeinfo");
|
||||||
|
|
||||||
nodeinfo.append("h1")
|
nodeinfo.append("h1")
|
||||||
.text(d.name);
|
.text(d.name);
|
||||||
|
@ -92,34 +134,57 @@ d3.json("nodes.json", function(json) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var node = vis.selectAll("svg.node")
|
var node = vis.selectAll("svg.node")
|
||||||
.data(json.nodes)
|
.data(json.nodes.filter(function (d) {
|
||||||
.enter().append("g")
|
return type != "mesh" || (d.group != 2 && d.group != 3);
|
||||||
.attr("class", "node")
|
}))
|
||||||
.on("mouseover", fade(.2))
|
.enter().append("g")
|
||||||
.on("mouseout", fade(1))
|
.attr("class", "node")
|
||||||
.on("click", show_node_info)
|
.on("mouseover", fade(.2))
|
||||||
.call(force.drag);
|
.on("mouseout", fade(1))
|
||||||
|
.on("click", show_node_info)
|
||||||
|
.call(force.drag);
|
||||||
|
|
||||||
node.append("ellipse")
|
node.append("ellipse")
|
||||||
.attr("rx", function(d) { if (d.group == 3) return 4; else return Math.max(10, d.name.length * 5); })
|
.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; })
|
.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("fill", function(d) { if (d.group == 3) return fill(d.group); else return ""; })
|
||||||
.style("stroke", function(d) { return fill(d.group); });
|
.style("stroke", function(d) { return fill(d.group); });
|
||||||
|
|
||||||
node.append("text")
|
node.append("text")
|
||||||
.attr("text-anchor", "middle")
|
.attr("text-anchor", "middle")
|
||||||
.attr("y", "4px")
|
.attr("y", "4px")
|
||||||
.text(function(d) { if (d.group == 3) return ""; else return d.name; });
|
.text(function(d) { if (d.group == 3) return ""; else return d.name; });
|
||||||
|
|
||||||
node.append("title")
|
node.append("title")
|
||||||
.text(function(d) { return d.macs; });
|
.text(function(d) { return d.macs; });
|
||||||
|
|
||||||
|
var uplink_info = node.filter(function (d) {
|
||||||
|
return d.name == "krtek"
|
||||||
|
})
|
||||||
|
.append("g");
|
||||||
|
|
||||||
|
if (type == "mesh") {
|
||||||
|
uplink_info.append("rect")
|
||||||
|
.attr("width", 16)
|
||||||
|
.attr("height", 16)
|
||||||
|
.attr("x", -8)
|
||||||
|
.attr("y", -28)
|
||||||
|
.attr("fill", "#fff")
|
||||||
|
.attr("stroke", "#066");
|
||||||
|
|
||||||
|
uplink_info.append("text")
|
||||||
|
.attr("text-anchor", "middle")
|
||||||
|
.attr("y", 4 - 20)
|
||||||
|
.text("8");
|
||||||
|
}
|
||||||
|
|
||||||
force.on("tick", function() {
|
force.on("tick", function() {
|
||||||
link.attr("x1", function(d) { return d.source.x; })
|
link.attr("x1", function(d) { return d.source.x; })
|
||||||
.attr("y1", function(d) { return d.source.y; })
|
.attr("y1", function(d) { return d.source.y; })
|
||||||
.attr("x2", function(d) { return d.target.x; })
|
.attr("x2", function(d) { return d.target.x; })
|
||||||
.attr("y2", function(d) { return d.target.y; });
|
.attr("y2", function(d) { return d.target.y; });
|
||||||
|
|
||||||
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
|
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
4
html/jquery.min.js
vendored
Normal file
4
html/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,8 @@
|
||||||
<title>Freifunk Lübeck - Knotengraph</title>
|
<title>Freifunk Lübeck - Knotengraph</title>
|
||||||
<link href='style.css' rel='stylesheet' type='text/css' />
|
<link href='style.css' rel='stylesheet' type='text/css' />
|
||||||
<link href='force.css' rel='stylesheet' type='text/css' />
|
<link href='force.css' rel='stylesheet' type='text/css' />
|
||||||
|
<script type="text/javascript" src="jquery.min.js"></script>
|
||||||
|
<script type="text/javascript" src="bootstrap-button.js"></script>
|
||||||
<script type="text/javascript" src="d3.js"></script>
|
<script type="text/javascript" src="d3.js"></script>
|
||||||
<script src='d3.layout.js' type='text/javascript'> </script>
|
<script src='d3.layout.js' type='text/javascript'> </script>
|
||||||
<script src='d3.geom.js' type='text/javascript'> </script>
|
<script src='d3.geom.js' type='text/javascript'> </script>
|
||||||
|
|
Loading…
Reference in a new issue