This is meant to be an interactive example of connecting nodes in a graph based off of metric distance. For any node, that node is connected to any other nodes that are within a radius R
of that node.
Use the inputs to the right to adjust variables:
[-M, M]
for both x and yTo see the neighborhood radius for an individual node, hover your mouse over that node. To see the radius for all nodes, use the Show Radius
and Hide Radius
buttons.
Use the New Points
to generate a new random set of points from the current parameters.
forked from lwthatcher's block: Random Points II
xxxxxxxxxx
<meta charset="utf-8">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<style> /* set the CSS */
.point {
fill: black;
}
.hover {
fill: darkblue;
}
.Radius {
fill: cornflowerblue;
}
.radiusHover {
fill: lightsalmon;
}
.counter {
width: 80px;
float: right;
margin-right: 40px;
}
.num {
margin-left: 40px;
}
.center {
float: left;
}
.value {
margin-left: 20px;
}
hr {
margin-top: 10px;
margin-bottom: 10px;
}
</style>
<!-- LOAD LIBRARIES -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://www.numericjs.com/lib/numeric-1.2.6.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js">
</script>
<!-- PAGE SETUP -->
<body>
<div class="container-fluid">
<div class="row">
<div class="col-sm-9" id="svg-bin"></div>
<div class="col-sm-3">
<div class="row"><button class="btn btn-info" onclick="showRadius()">Show Radius</button><button class="btn btn-warning" onclick="hideRadius()">Hide Radius</button></div>
<hr/>
<div class="row"><b class="num">M:</b> <input step=5 class="counter" type="number" onchange="updateM(this.value)" value=50></input></div>
<hr>
<div class="row"><b class="num">N:</b> <input class="counter" type="number" onchange="updateN(this.value)" value=10></input></div>
<hr>
<div class="row"><b class="num">R:</b><input class="counter" type="number" onchange="updateR(this.value)" value=10></input></div>
<hr>
<div class="row"><div class="num"><b>Proportion: </b> <span class="value" id="prop"></span> </div></div>
<div class="row"><div class="num"><b>Connections: </b> <span class="value" id="cons"></span> </div></div>
<!-- <div class="row"><div class="num"><b>Components: </b> <span class="value" id="comps"></span> </div></div> -->
</div>
</div>
<div class="row">
<button class="btn btn-danger" onclick="reset()">New Points</button>
</div>
</div>
<!-- SCRIPT TO RUN -->
<script>
// global vars
var M = 50;
var N = 10;
var R = 10;
//helper vars
var point_radius = 4;
var DATA;
var CONNECTIONS = [];
var redraws = 0;
//form updates
function updateM(value) {
M = value;
console.log("updated M", M);
redraw();
redraws = 0;
}
function updateN(value) {
N = value;
console.log("updated N", N);
redraw();
redraws = 0;
}
function updateR(value) {
R = value;
console.log("updated R", R);
updateRadius();
redraws = 0;
}
function reset() {
redraw();
redraws++;
console.info(redraws);
}
// transition function
var t = d3.transition()
.duration(450)
.ease(d3.easeCubicInOut);
// SETUP
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 450 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#svg-bin").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// DRAWING
// random points
function generate_random() {
var dataset = [];
for (var i = 0; i < N; i++) {
var x = d3.randomUniform(-M,M)();
var y = d3.randomUniform(-M,M)();
dataset.push({"x": x, "y": y});
}
return dataset;
}
function withinRadius(pt1, pt2) {
var dx = pt1.x-pt2.x;
var dy = pt1.y-pt2.y;
// console.log(pt1.idx, pt2.idx, dx, dy)
return dx*dx + dy*dy <= R*R;
}
function setProp() {
var prop = (Math.PI*R*R)/((2*M)*(2*M));
prop *= 100;
prop = prop.toFixed(3);
d3.select("#prop").text(prop + "%")
}
function setCons(num_cons) {
d3.select("#cons").text(num_cons);
}
function redraw() {
// clear the board
clear();
setProp();
// get the data
var data = generate_random();
var r = d3.scaleLinear().range([-M, M]);
r = d3.scaleLinear().range([0, M]);
// format the data
data.forEach(function(d,i) {
d.idx = i;
d.x = +d.x;
d.y = +d.y;
});
// scale the range of the data
x.domain([-M, M]);
y.domain([-M, M]);
// save data
DATA = data;
// add the dots
var points = svg.selectAll(".point")
.data(data)
.enter().append("g")
.attr("class", "point")
svg.selectAll(".point")
.append("circle")
.attr("class", (d,i) => {return "Radius pt-" + i;})
.style("opacity", 0)
.attr("r", 0)
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); });
svg.selectAll(".point")
.append("circle")
.attr("r", point_radius)
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
connectComponents();
// add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
}
function connectComponents() {
CONNECTIONS = [];
svg.selectAll(".connection").remove();
var count = 0;
for (var d of DATA) {
for (var d2 of DATA) {
if (d.idx != d2.idx && withinRadius(d, d2)) {
if (d2.idx < d.idx) {CONNECTIONS.push({"start": d, "end":d2})}
count++;
}
}
}
setCons(count/2);
drawLines(CONNECTIONS);
}
function drawLines(connections) {
svg.selectAll(".connection")
.data(connections)
.enter().append("line")
.style("stroke", "red") // <<<<< Add a color
.attr("class", "connection")
.attr("x1", function(d) { return x(d.start.x); })
.attr("y1", function(d) { return y(d.start.y); })
.attr("x2", function(d) { return x(d.end.x); })
.attr("y2", function(d) { return y(d.end.y); })
}
function clear() {
svg.selectAll(".connection").remove();
svg.selectAll(".point").remove();
svg.selectAll(".Radius").remove();
svg.selectAll("g").remove();
}
function updateRadius() {
svg.selectAll(".Radius")
.interrupt()
.transition(t)
.attr("r", () => { return ((R/2*x(M))/M);});
setProp();
connectComponents();
}
function showRadius() {
svg.selectAll(".Radius").transition(t)
.attr("r", () => { return ((R/2*x(M))/M);})
.style("opacity", .35);
}
function hideRadius() {
svg.selectAll(".Radius").transition(t)
.attr("r", 0)
.style("opacity", 0);
}
function showR(i) {
pt = ".pt-" + i
var g = svg.selectAll(pt)
g.interrupt()
.transition(t)
.attr("r", () => { return ((R/2*x(M))/M);})
.attr("fill", "lightsalmon")
.style("opacity", .35);
}
function hideR(i) {
pt = ".pt-" + i
var g = svg.selectAll(pt)
g.interrupt()
.transition(t)
.attr("r", 0)
.style("opacity", 0);
}
var mouseover = function(d, i) {
d3.select(this).attr("class", "hover");
showR(i);
}
function mouseout(d, i) {
d3.select(this).attr("class", null);
hideR(i);
}
redraw();
</script>
</body>
Modified http://www.numericjs.com/lib/numeric-1.2.6.js to a secure url
https://d3js.org/d3.v4.min.js
https://www.numericjs.com/lib/numeric-1.2.6.js
https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js
https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js