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;
|
||||
}
|
||||
|
||||
#nodeinfo {
|
||||
#nodeinfo, #controlpanel {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
padding: 0.5em;
|
||||
font-family: arial, helvatica;
|
||||
}
|
||||
|
||||
#nodeinfo {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
#controlpanel {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
#nodeinfo h1, p {
|
||||
color: #555;
|
||||
}
|
||||
|
@ -50,3 +58,70 @@ line.link {
|
|||
font-size: 10pt;
|
||||
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;
|
||||
}
|
||||
|
|
195
html/force.js
195
html/force.js
|
@ -1,12 +1,12 @@
|
|||
function getOffset( el ) {
|
||||
var _x = 0;
|
||||
var _y = 0;
|
||||
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
|
||||
_x += el.offsetLeft - el.scrollLeft;
|
||||
_y += el.offsetTop - el.scrollTop;
|
||||
el = el.offsetParent;
|
||||
}
|
||||
return { top: _y, left: _x };
|
||||
var _x = 0;
|
||||
var _y = 0;
|
||||
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
|
||||
_x += el.offsetLeft - el.scrollLeft;
|
||||
_y += el.offsetTop - el.scrollTop;
|
||||
el = el.offsetParent;
|
||||
}
|
||||
return { top: _y, left: _x };
|
||||
}
|
||||
|
||||
var offset = getOffset(document.getElementById('chart'));
|
||||
|
@ -15,58 +15,100 @@ var w = window.innerWidth - offset.left,
|
|||
h = window.innerHeight - offset.top,
|
||||
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("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) {
|
||||
linkedByIndex[d.source.index + "," + d.target.index] = 1;
|
||||
});
|
||||
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) {
|
||||
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")
|
||||
.data(json.links)
|
||||
.data(linkdata)
|
||||
.enter().append("line")
|
||||
.attr("class", "link")
|
||||
.style("stroke-width", function(d) { return Math.min(1, d.strength * 2); })
|
||||
.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; });
|
||||
.attr("class", "link")
|
||||
.style("stroke-width", function(d) { return Math.min(1, d.strength * 2); })
|
||||
.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; });
|
||||
|
||||
function isConnected(a, b) {
|
||||
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
|
||||
}
|
||||
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) {
|
||||
node.style("stroke-opacity", function(o) {
|
||||
var connected = isConnected(d, o);
|
||||
var connected = isConnected(d, o);
|
||||
|
||||
if (connected && opacity != 1)
|
||||
d3.select(this).classed("highlight", true);
|
||||
else
|
||||
d3.select(this).classed("highlight", false);
|
||||
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;
|
||||
});
|
||||
thisOpacity = connected ? 1 : opacity;
|
||||
this.setAttribute('fill-opacity', thisOpacity);
|
||||
return thisOpacity;
|
||||
});
|
||||
|
||||
link.style("stroke-opacity", function(o) {
|
||||
return o.source === d || o.target === d ? 1 : opacity;
|
||||
|
@ -79,7 +121,7 @@ d3.json("nodes.json", function(json) {
|
|||
nodeinfo.remove();
|
||||
|
||||
nodeinfo = d3.select("#chart").append("div")
|
||||
.attr("id", "nodeinfo");
|
||||
.attr("id", "nodeinfo");
|
||||
|
||||
nodeinfo.append("h1")
|
||||
.text(d.name);
|
||||
|
@ -92,34 +134,57 @@ d3.json("nodes.json", function(json) {
|
|||
}
|
||||
|
||||
var node = vis.selectAll("svg.node")
|
||||
.data(json.nodes)
|
||||
.enter().append("g")
|
||||
.attr("class", "node")
|
||||
.on("mouseover", fade(.2))
|
||||
.on("mouseout", fade(1))
|
||||
.on("click", show_node_info)
|
||||
.call(force.drag);
|
||||
.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); });
|
||||
.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")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("y", "4px")
|
||||
.text(function(d) { if (d.group == 3) return ""; else return d.name; });
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("y", "4px")
|
||||
.text(function(d) { if (d.group == 3) return ""; else return d.name; });
|
||||
|
||||
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() {
|
||||
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; });
|
||||
.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("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>
|
||||
<link href='style.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 src='d3.layout.js' type='text/javascript'> </script>
|
||||
<script src='d3.geom.js' type='text/javascript'> </script>
|
||||
|
|
Loading…
Reference in a new issue