Select Git revision
-
Selby, Nicholas Stearns authoredSelby, Nicholas Stearns authored
index.html 15.39 KiB
<!-- simbit visualizer frontend for in-browser simulation -->
<!doctype html>
<html>
<head>
<title>simbit</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
body {
font: 10px sans-serif;
}
.network {
border: 1px solid #DEDEDE;
display:block;
margin:auto;
}
svg {
margin: auto;
display:block;
}
rect {
fill: none;
pointer-events: all;
}
.node {
fill: #000;
}
.link {
stroke: #999;
}
input[type="number"] {
width: 50px;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
path.diff {
stroke: red;
}
path.avShort {
stroke: green;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<script type="text/javascript" src="d3.v3.min.js"></script>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="require.js"></script>
<script src="graph.js"></script>
<script src="goog/base.js"></script>
<script>
goog.require("goog.structs.PriorityQueue")
</script>
</head>
<body>
<table>
<tr>
<td id="buttonparent">
<div style="float:left">
<input id="disable" type="button" value="Disable Visualizer">
</div>
</td>
<td id="optionparent">
<div id="graphwindows" style="display:none">
Short window:
<input id="shortwindow" type="number" min=1 value=10>
Long window:
<input id="longwindow" type="number" min=10 step=10 value=100>
</div>
</td>
</tr>
<tr>
<td id="networkparent">
<div class="network" style="width: 700px; height: 500px"></div>
</td>
<td id="graphparent" style="display:none">
<div class="graph" style="width: 700px; height: 500px"></div>
</td>
<td id="logs" valign="top">
</td>
</tr>
<tr>
<td valign="top">
<div style="float:right">
Elapsed: <span id="elapsed">0 seconds</span>
<input id="play" type="button" value="Play">
<input id="pause" type="button" value="Pause">
</div>
<div style="float:left">
Speed: <input id="slower" type="button" value="⇠"><input id="defaultspeed" type="button" value="Default"><input id="faster" type="button" value="⇢">
<span id="relspeed">(1.00x)</span>
</div>
</td>
<td id="timeBetweenBlocks" style="display:none" valign="top">
<center>
<h1>time between each block</h1>
</center>
<div class='scatter'>
<!-- /the chart goes here -->
</div>
</td>
</tr>
</table>
<script type="text/javascript">
// make it so that net.run() doesn't actually run
var DELAY_RUN = {net:false};
function Visualizer(div) {
this.nindex = 0;
this.svg = null;
this.divname = div;
this.force = null;
this.nodes = null;
this.links = null;
this.slink = null;
this.snode = null;
this.edges = {};
this.inodes = [];
this.lastmsgs = [];
this.updated = false;
this.colormap = {};
this.colormap_u = false;
this.link_colormap = {};
this.link_colormap_last = 0;
}
Visualizer.prototype = {
width: 700,
height: 500,
linkDistance: 30,
charge: -150,
gravity: .5,
drawGraph: function(data) {
var maxx = d3.max(data, function(d) { return d[0]; })
var maxdiff = d3.max(data, function(d) { return d[1]; })
var mintime = d3.min(data, function(d) { return d[2]; })
var maxtime = d3.max(data, function(d) { return d[2]; })
var margin = {top: 20, right: 60, bottom: 60, left: 60},
width = 700 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, maxx])
.range([ 0, width]);
var diff = d3.scale.linear()
.domain([0, maxdiff])
.range([ height, 0 ]);
var time = d3.scale.linear()
.domain([mintime, maxtime])
.range([ height, 0 ]);
$("#graphparent").css('display', 'block')
$("#graphwindows").css('display', 'inline')
$(".graph").html("")
var chart = d3.select('.graph')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis)
.append("text")
.attr("dx", ".71em")
.attr("x", 420)
.attr("y", -6)
.style("text-anchor", "end")
.text("Block");
// draw the d axis
var diffAxis = d3.svg.axis()
.scale(diff)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(diffAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Difficulty");
// draw the t axis
var timeAxis = d3.svg.axis()
.scale(time)
.orient('right');
main.append('g')
.attr('transform', 'translate(' + width + ',0)')
.attr('class', 'main axis date')
.call(timeAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -10)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Block time");
var dline = d3.svg.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return diff(d[1]); });
var tsline = d3.svg.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return time(d[2]); });
var tlline = d3.svg.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return time(d[3]); });
var g = main.append("svg:g");
g.append("path")
.attr("class", "line diff" )
.attr("d", dline(data) );
g.append("path")
.attr("class", "line avShort" )
.attr("d", tsline(data) );
g.append("path")
.attr("class", "line" )
.attr("d", tlline(data) );
},
drawScatter: function(data) {
var maxy = d3.max(data, function(d) { return d[1]; })
var maxx = d3.max(data, function(d) { return d[0]; })
var margin = {top: 20, right: 15, bottom: 60, left: 60}
, width = 500 - margin.left - margin.right
, height = 300 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, 1200])
.range([ 0, width]);
var y = d3.scale.linear()
.domain([0, maxy])
.range([ height, 0 ]);
$("#timeBetweenBlocks").css('display', 'block')
$(".scatter").html("")
var chart = d3.select('.scatter')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis)
.append("text")
.attr("dx", ".71em")
.attr("x", 420)
.attr("y", -6)
.style("text-anchor", "end")
.text("Time between blocks (in seconds)");
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Blocks");
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(data)
.enter().append("svg:circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 2);
},
init: function() {
// init the network layout/svg
$(this.divname).css('width', this.width);
$(this.divname).css('height', this.height);
this.force = d3.layout.force()
.size([this.width,this.height])
.nodes([]) // no nodes
.friction(0.5)
.linkDistance(function(link) {
return link.linkDistance;
})
.charge(this.charge)
.gravity(this.gravity);
this.svg = d3.select(this.divname).append("svg")
.attr("width", this.width)
.attr("height", this.height);
this.svg.append("rect")
.attr("width", this.width)
.attr("height", this.height);
this.nodes = this.force.nodes();
this.links = this.force.links();
this.slink = this.svg.selectAll(".link");
this.snode = this.svg.selectAll(".node");
this.force = this.force.on("tick", this.tick());
this.updated = true;
this.rehash(0);
},
log: function(msg) {
this.lastmsgs.push(msg);
if (this.lastmsgs.length > 35)
this.lastmsgs.shift();
var newlogs = "";
this.lastmsgs.forEach(function(e) {
newlogs += e + "<br />";
})
$("#logs").html(newlogs);
},
setColor: function(p, color) {
this.colormap_u = true;
this.colormap[p] = color;
},
setLinkActivity: function(p, now) {
this.link_colormap[p] = now;
this.link_colormap_last = 0;
},
getRandomLink: function() {
var result;
var count=1;
for (var prop in this.edges) {
if (Math.random() < 1/++count)
result = prop;
}
if (!result)
return -1;
var e = result.split("-");
return [parseInt(e[0]), parseInt(e[1])];
},
getRandomNode: function() {
return this.inodes[Math.floor(Math.random()*this.inodes.length)];
},
getKeyForID: function(id) {
return this.inodes.indexOf(id);
},
incCharge: function(amt) {
this.force.charge(this.force.charge() - amt);
this.updated = true;
///////////this.rehash();
},
addNode: function() {
// add a node, return the index
this.nodes.push({id:"n"+this.nindex});
this.inodes.push(this.nindex);
this.updated = true;
/////////////this.rehash();
this.nindex++;
return this.nindex-1;
},
connect: function(a, b, latency) {
if (this.edges.hasOwnProperty(a + '-' + b) || this.edges.hasOwnProperty(b + '-' + a))
return false; // we're already connected
if (a==b)
return false; // can't connect to ourself silly!
this.edges[a + '-' + b] = {source:this.nodes[this.getKeyForID(a)],target:this.nodes[this.getKeyForID(b)],linkDistance:latency};
this.links.push(this.edges[a + '-' + b]);
this.updated = true;
//////this.rehash();
},
disconnect: function(a, b) {
if (!this.edges.hasOwnProperty(a + '-' + b) && !this.edges.hasOwnProperty(b + '-' + a))
return false; // we're already disconnected
var i = this.links.indexOf(this.edges[a + '-' + b]);
if (i<0)
i = this.links.indexOf(this.edges[b + '-' + a]);
delete this.edges[a + '-' + b];
delete this.edges[b + '-' + a];
this.links.splice(i, 1); // remove the link
this.updated = true;
//////this.rehash();
},
removeNode: function(index) {
// remove a node at index
var i = this.getKeyForID(index);
if (i < 0)
return false; // this one has already been removed
this.nodes.splice(i, 1);
this.inodes.splice(i, 1);
this.updated = true;
///////////////////this.rehash();
},
tick: function() {
var svg = this.svg;
return function() {
svg.selectAll(".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("id", function(d) {return "l-" + d.source.id + "-" + d.target.id;});
svg.selectAll(".node").attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
},
rehash: function(now) {
/***** COLORMAP *****/
if (this.colormap_u) {
for (var p in this.colormap) {
$(".n" + p).css('fill', this.colormap[p]);
}
this.colormap_u = false;
}
if (this.link_colormap_last < (now-100)) {
this.link_colormap_last = now;
for (var p in this.link_colormap) {
if (this.link_colormap[p] + 100 > now) {
$("#l-" + p).css('stroke', "black")
} else {
$("#l-" + p).css('stroke', "#999")
delete this.link_colormap[p];
}
}
}
if (!this.updated)
return;
this.slink = this.slink.data(this.force.links(), function(d) { return d.source.id + "-" + d.target.id; });
this.slink.enter().insert("line", ".node")
.attr("class", "link");
this.slink.exit().remove();
this.snode = this.snode.data(this.force.nodes(), function(d) {return d.id;});
this.snode.enter().append("circle").attr("class", function (d) {return "node " + d.id;})
.attr("r", 3)
// .call(this.force.drag);
this.snode.exit().remove();
this.force.start();
this.updated = false;
}
};
$("#network").html("");
var VISUALIZER = new Visualizer(".network");
VISUALIZER.init();
</script>
<script type="text/javascript" src="sim.js"></script>
<script type="text/javascript">
var amt = 10;
var play = false;
$("#play").click(function() {
play = true;
})
$("#pause").click(function() {
play = false;
})
$("#slower").click(function() {
amt = amt / 2;
$("#relspeed").html("(" + (amt / 10).toFixed(2) + "x)");
})
$("#faster").click(function() {
amt = amt * 2;
$("#relspeed").html("(" + (amt / 10).toFixed(2) + "x)");
})
$("#defaultspeed").click(function() {
amt = 10;
$("#relspeed").html("(" + (amt / 10).toFixed(2) + "x)");
})
$("#disable").click(function() {
VISUALIZER.rehash = function() {return;}
VISUALIZER.force.stop();
$("#disable").prop("disabled", true);
})
$("#disable").prop("disabled", false);
setInterval(function() {
if (play && DELAY_RUN.net) {
DELAY_RUN.net._run(amt);
VISUALIZER.rehash(DELAY_RUN.net.now);
$("#elapsed").html((DELAY_RUN.net.now / 1000).toFixed(2) + " seconds")
}
}, 10);
</script>
</body>
</html>