|
<html> |
|
<head> |
|
<style> |
|
/* HTML styles */ |
|
html{ width: 100%; } |
|
body{ |
|
width: 100%; |
|
margin: 0; padding: 0; |
|
display: flex; |
|
font-family: sans-serif; font-size: 75%; } |
|
.controls { |
|
flex-basis: 200px; |
|
padding: 0 5px; |
|
overflow-y: scroll; |
|
} |
|
.controls .force { |
|
background-color:#eee; |
|
border-radius: 3px; |
|
padding: 5px; |
|
margin: 5px 0; |
|
} |
|
.controls .force p label { margin-right: .5em; font-size: 120%; font-weight: bold;} |
|
.controls .force p { margin-top: 0;} |
|
.controls .force label { display: inline-block; } |
|
.controls input[type="checkbox"] { transform: scale(1.2, 1.2); } |
|
.controls input[type="range"] { margin: 0 5% 0.5em 5%; width: 90%; } |
|
/* alpha viewer */ |
|
.controls .alpha p { margin-bottom: .25em; } |
|
.controls .alpha .alpha_bar { height: .5em; border: 1px #777 solid; border-radius: 2px; padding: 1px; display: flex; } |
|
.controls .alpha .alpha_bar #alpha_value { background-color: #555; border-radius: 1px; flex-basis: 100% } |
|
.controls .alpha .alpha_bar:hover { border-width: 2px; margin:-1px; } |
|
.controls .alpha .alpha_bar:active #alpha_value { background-color: #222 } |
|
|
|
/* SVG styles */ |
|
svg { |
|
flex-basis: 100%; |
|
min-width: 200px; |
|
} |
|
body { |
|
margin:0; |
|
position:fixed; |
|
top:0; |
|
right:0; |
|
bottom:0; |
|
left:0; |
|
} |
|
.links line { |
|
stroke: #aaa; |
|
} |
|
.nodes circle { |
|
pointer-events: all; |
|
} |
|
|
|
line { |
|
stroke: rgb(212, 212, 212); |
|
stroke-width: 1px; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
|
|
path { |
|
fill-opacity: .1; |
|
stroke-opacity: 1; |
|
} |
|
|
|
.polygons :first-child { |
|
/*fill: #f00;*/ |
|
} |
|
|
|
.sites { |
|
fill: #000; |
|
stroke: #fff; |
|
} |
|
|
|
.sites :first-child { |
|
fill: #fff; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="controls"> |
|
<div class="force alpha"> |
|
<p><label>alpha</label> Simulation activity</p> |
|
<div class="alpha_bar" onclick="updateAll();"><div id="alpha_value"></div></div> |
|
</div> |
|
<div class="force"> |
|
<p><label>Voronoi</label> </p> |
|
|
|
<div id="scaleFactorSettings"> |
|
<input id="checkVoronoi" type="checkbox">Show Voronoi</input> |
|
|
|
|
|
</div> |
|
|
|
</div> |
|
<div class="force"> |
|
<p><label>Delaunay links</label> </p> |
|
|
|
<div id="scaleFactorSettings"> |
|
<input id="checkDelaunay" type="checkbox">Show Delaunay</input> |
|
|
|
|
|
</div> |
|
|
|
</div> |
|
<div class="force"> |
|
<p><label><input type="checkbox" checked onchange="roadProperties.enabled = this.checked; updateAll();"> Roads</label> shows roads</p> |
|
<label title="Show roads"> |
|
<output id="road_ResolutionSliderOutput">5</output> |
|
<input type="range" min="0" max="125" value="25" step="0.01" oninput="d3.select('#road_ResolutionSliderOutput').text(Number(value)); roadProperties.Resolution=Number(value); updateAll();"> |
|
</label> |
|
|
|
</div> |
|
|
|
|
|
<div class="force"> |
|
<p><label>Settings</label></p> |
|
<input id="checkGroupInABox" type="checkbox">Group in a Box</input> |
|
<input id="checkshowGrid" type="checkbox">Show Grid</input> |
|
<input id="snapGrid" type="checkbox">Snap to Grid</input> |
|
<input id="checkShowTreemap" type="checkbox">Show Template</input> |
|
<select id="selectTemplate" type="select"> |
|
<option value="treemap">Treemap</option> |
|
<option value="force">Force</option> |
|
</select> |
|
</div> |
|
<div class="force"> |
|
<p><label>Grid Settings</label> </p> |
|
<label> |
|
R |
|
<output id="Resolution_RSliderOutput">25</output> |
|
<input type="range" min="0" max="125" value="25" step="0.01" oninput="d3.select('#Resolution_RSliderOutput').text(Number(value)); gridProperties.r=Number(value); updateAll();"> |
|
Resolution |
|
<output id="Resolution_ReSliderOutput">25</output> |
|
<input type="range" min="1" max="125" value="25" step="0.01" oninput="d3.select('#Resolution_ReSliderOutput').text(Number(value)); gridProperties.resolution=Number(value); updateAll();"> |
|
</label> |
|
|
|
</div> |
|
<div class="force"> |
|
<p><label>center</label> Shifts the view, so the graph is centered at this location.</p> |
|
<label> |
|
x |
|
<output id="center_XSliderOutput">.5</output> |
|
<input type="range" min="0" max="1" value=".5" step="0.01" oninput="d3.select('#center_XSliderOutput').text(Number(value)); forceProperties.center.x=Number(value); updateAll();"> |
|
</label> |
|
<label> |
|
y |
|
<output id="center_YSliderOutput">.5</output> |
|
<input type="range" min="0" max="1" value=".5" step="0.01" oninput="d3.select('#center_YSliderOutput').text(Number(value)); forceProperties.center.y=Number(value); updateAll();"> |
|
</label> |
|
</div> |
|
|
|
<div class="force"> |
|
<p><label><input type="checkbox" checked onchange="forceProperties.charge.enabled = this.checked; updateAll();"> charge</label> Attracts (+) or repels (-) nodes to/from each other.</p> |
|
<label title="Negative strength repels nodes. Positive strength attracts nodes."> |
|
strength |
|
<output id="charge_StrengthSliderOutput">-30</output> |
|
<input type="range" min="-6000" max="50" value="-30" step=".1" oninput="d3.select('#charge_StrengthSliderOutput').text(Number(value)); forceProperties.charge.strength=Number(value); updateAll();"> |
|
</label> |
|
<label title="Minimum distance where force is applied"> |
|
distanceMin |
|
<output id="charge_distanceMinSliderOutput">1</output> |
|
<input type="range" min="-500" max="500" value="1" step=".1" oninput="d3.select('#charge_distanceMinSliderOutput').text(Number(value)); forceProperties.charge.distanceMin=Number(value); updateAll();"> |
|
</label> |
|
<label title="Maximum distance where force is applied"> |
|
distanceMax |
|
<output id="charge_distanceMaxSliderOutput">2000</output> |
|
<input type="range" min="-2000" max="2000" value="2000" step=".1" oninput="d3.select('#charge_distanceMaxSliderOutput').text(Number(value)); forceProperties.charge.distanceMax=Number(value); updateAll();"> |
|
</label> |
|
</div> |
|
|
|
<div class="force"> |
|
<p><label><input type="checkbox" checked onchange="forceProperties.collide.enabled = this.checked; updateAll();"> collide</label> Prevents nodes from overlapping</p> |
|
<label> |
|
strength |
|
<output id="collide_StrengthSliderOutput">.7</output> |
|
<input type="range" min="-50" max="50" value=".7" step=".1" oninput="d3.select('#collide_StrengthSliderOutput').text(Number(value)); forceProperties.collide.strength=Number(value); updateAll();"> |
|
</label> |
|
<label title="Size of nodes"> |
|
radius |
|
<output id="collide_radiusSliderOutput">5</output> |
|
<input type="range" min="0" max="100" value="5" step="1" oninput="d3.select('#collide_radiusSliderOutput').text(Number(value)); forceProperties.collide.radius=Number(value); updateAll();"> |
|
</label> |
|
<label title="Higher values increase rigidity of the nodes (WARNING: high values are computationally expensive)"> |
|
iterations |
|
<output id="collide_iterationsSliderOutput">1</output> |
|
<input type="range" min="1" max="10" value="1" step="1" oninput="d3.select('#collide_iterationsSliderOutput').text(Number(value)); forceProperties.collide.iterations=Number(value); updateAll();"> |
|
</label> |
|
</div> |
|
|
|
<div class="force"> |
|
<p><label><input type="checkbox" onchange="forceProperties.forceX.enabled = this.checked; updateAll();"> forceX</label> Acts like gravity. Pulls all points towards an X location.</p> |
|
<label> |
|
strength |
|
<output id="forceX_StrengthSliderOutput">.1</output> |
|
<input type="range" min="0" max="1" value=".1" step="0.01" oninput="d3.select('#forceX_StrengthSliderOutput').text(Number(value)); forceProperties.forceX.strength=Number(value); updateAll();"> |
|
</label> |
|
<label title="The X location that the force will push the nodes to (NOTE: This demo multiplies by the svg width)"> |
|
x |
|
<output id="forceX_XSliderOutput">.5</output> |
|
<input type="range" min="-10" max="10" value=".5" step="0.01" oninput="d3.select('#forceX_XSliderOutput').text(Number(value)); forceProperties.forceX.x=Number(value); updateAll();"> |
|
</label> |
|
</div> |
|
|
|
<div class="force"> |
|
<p><label><input type="checkbox" onchange="forceProperties.forceY.enabled = this.checked; updateAll();"> forceY</label> Acts like gravity. Pulls all points towards a Y location.</p> |
|
<label> |
|
strength |
|
<output id="forceY_StrengthSliderOutput">.1</output> |
|
<input type="range" min="0" max="1" value=".1" step="0.01" oninput="d3.select('#forceY_StrengthSliderOutput').text(Number(value)); forceProperties.forceY.strength=Number(value); updateAll();"> |
|
</label> |
|
<label title="The Y location that the force will push the nodes to (NOTE: This demo multiplies by the svg height)"> |
|
y |
|
<output id="forceY_YSliderOutput">.5</output> |
|
<input type="range" min="0" max="1" value=".5" step="0.01" oninput="d3.select('#forceY_YSliderOutput').text(Number(value)); forceProperties.forceY.y=Number(value); updateAll();"> |
|
</label> |
|
</div> |
|
|
|
<div class="force"> |
|
<p><label><input type="checkbox" checked onchange="forceProperties.link.enabled = this.checked; updateAll();"> link</label> Sets link length</p> |
|
<label title="The force will push/pull nodes to make links this long"> |
|
distance |
|
<output id="link_DistanceSliderOutput">30</output> |
|
<input type="range" min="0" max="100" value="30" step="1" oninput="d3.select('#link_DistanceSliderOutput').text(Number(value)); forceProperties.link.distance=Number(value); updateAll();"> |
|
</label> |
|
<label title="Higher values increase rigidity of the links (WARNING: high values are computationally expensive)"> |
|
iterations |
|
<output id="link_IterationsSliderOutput">1</output> |
|
<input type="range" min="1" max="10" value="1" step="1" oninput="d3.select('#link_IterationsSliderOutput').text(Number(value)); forceProperties.link.iterations=Number(value); updateAll();"> |
|
</label> |
|
</div> |
|
|
|
|
|
</div> |
|
<svg></svg> |
|
|
|
<script src="//d3js.org/d3.v4.js"></script> |
|
<script type="text/javascript" src="./forceInABox.js"></script> |
|
|
|
|
|
<script> |
|
|
|
|
|
gridProperties = { |
|
r:1, |
|
resolution : 25 |
|
}; |
|
// Road Properties |
|
roadProperties = { |
|
enabled: true, |
|
Resolution : 25 |
|
|
|
}; |
|
// values for all forces |
|
forceProperties = { |
|
center: { |
|
x: 0.5, |
|
y: 0.5 |
|
}, |
|
charge: { |
|
enabled: true, |
|
strength: -30, |
|
distanceMin: 1, |
|
distanceMax: 2000 |
|
}, |
|
collide: { |
|
enabled: true, |
|
strength: .7, |
|
iterations: 1, |
|
radius: 5 |
|
}, |
|
forceX: { |
|
enabled: false, |
|
strength: .1, |
|
x: .5 |
|
}, |
|
forceY: { |
|
enabled: false, |
|
strength: .1, |
|
y: .5 |
|
}, |
|
link: { |
|
enabled: true, |
|
distance: 30, |
|
iterations: 1 |
|
} |
|
}; |
|
|
|
var graph; |
|
var svg = d3.select("svg"), |
|
width = +svg.node().getBoundingClientRect().width, |
|
height = +svg.node().getBoundingClientRect().height; |
|
var radius = 50; |
|
var svgbgGrid = svg.append("g"); |
|
var svgVoronoi = svg.append("g"); |
|
var svgRoads = svg.append("g"); |
|
var svgForce = svg.append("g"); |
|
var svgDelaunay = svg.append("g"); |
|
var svgm = svg.append("g"); |
|
var road, |
|
roads, |
|
node, |
|
link, |
|
|
|
links, |
|
text, |
|
polygons, |
|
polygon, |
|
site, |
|
sites, |
|
path_end; |
|
var useGroupInABox = true, |
|
drawTemplate = true, |
|
checkGroupBubbles = true, |
|
checkVoronoi = true, |
|
checkDelaunay = true, |
|
drawGrid = false, |
|
snapGrid = false, |
|
template = "force"; |
|
d3.select("#checkGroupInABox").property("checked", useGroupInABox); |
|
d3.select("#checkshowGrid").property("checked", drawGrid); |
|
d3.select("#checkDelaunay").property("checked", checkDelaunay); |
|
d3.select("#checkVoronoi").property("checked", checkVoronoi); |
|
d3.select("#checkGroupBubbles").property("checked", checkGroupBubbles); |
|
d3.select("#snapGrid").property("checked", snapGrid); |
|
d3.select("#checkShowTreemap").property("checked", drawTemplate); |
|
d3.select("#selectTemplate").property("value", template); |
|
//add drag capabilities |
|
//add zoom capabilities |
|
var zoom_handler = d3.zoom() |
|
.on("zoom", zoom_actions); |
|
//Zoom functions |
|
function zoom_actions(){ |
|
svg.attr("transform", d3.event.transform) |
|
} |
|
|
|
|
|
var voronoi = d3.voronoi() |
|
.x(function(d) { |
|
return d[0]; |
|
}) |
|
.y(function(d) { |
|
return d[1]; |
|
}).extent([[-1,-1], [width, height]]); |
|
var nodes; |
|
var force = d3.forceSimulation() |
|
.force("charge", d3.forceManyBody()) |
|
.force("link", d3.forceLink().id(function(d) { return d.id; })) |
|
.force("collide",d3.forceCollide( function(d){return d.r + 8 }).iterations(16) ) |
|
.force("x", d3.forceX(width/2).strength(0.05)) |
|
.force("y", d3.forceY(height/2).strength(0.05)).force("center", d3.forceCenter(width / 2, height / 2)); |
|
|
|
var groupingForce = forceInABox(); |
|
var color = d3.scaleOrdinal(d3.schemeCategory10); |
|
var nodeById ; |
|
// set up the simulation and event to update locations after each tick |
|
function initializeSimulation() { |
|
|
|
nodeById = d3.map(graph.nodes, function(d) { return d.id; }); |
|
|
|
groupingForce |
|
.strength(0.1) // Strength to foci |
|
.template(template) // Either treemap or force |
|
.groupBy("group") // Node attribute to group |
|
.links(graph.links) // The graph links. Must be called after setting the grouping attribute |
|
.enableGrouping(useGroupInABox) |
|
.nodeSize(5) // How big are the nodes to compute the force template |
|
.forceCharge(-20) // Separation between nodes on the force template |
|
.size([width, height]); // Size of the chart |
|
|
|
force |
|
.nodes(graph.nodes) |
|
.force("group", groupingForce) |
|
.force("link", d3.forceLink(graph.links) |
|
// .distance(5) |
|
.strength(groupingForce.getLinkStrength) |
|
) ; |
|
|
|
|
|
// simulation; |
|
initializeForces(); |
|
|
|
force.on("tick", ticked); |
|
} |
|
|
|
|
|
function round(p, n) { |
|
return p % n < n / 2 ? p - (p % n) : p + n - (p % n); |
|
} |
|
// add forces to the simulation |
|
function initializeForces() { |
|
// add forces and associate each with a name |
|
force |
|
.force("link", d3.forceLink()) |
|
.force("charge", d3.forceManyBody()) |
|
.force("collide", d3.forceCollide()) |
|
.force("center", d3.forceCenter()) |
|
.force("forceX", d3.forceX()) |
|
.force("forceY", d3.forceY()); |
|
|
|
// apply properties to each of the forces |
|
updateForces(); |
|
} |
|
// apply new force properties |
|
function updateForces() { |
|
// get each force by name and update the properties |
|
force.force("center") |
|
.x(width * forceProperties.center.x) |
|
.y(height * forceProperties.center.y); |
|
force.force("charge") |
|
.strength(forceProperties.charge.strength * forceProperties.charge.enabled) |
|
.distanceMin(forceProperties.charge.distanceMin) |
|
.distanceMax(forceProperties.charge.distanceMax); |
|
force.force("collide") |
|
.strength(forceProperties.collide.strength * forceProperties.collide.enabled) |
|
.radius(forceProperties.collide.radius) |
|
.iterations(forceProperties.collide.iterations); |
|
force.force("forceX") |
|
.strength(forceProperties.forceX.strength * forceProperties.forceX.enabled) |
|
.x(width * forceProperties.forceX.x); |
|
force.force("forceY") |
|
.strength(forceProperties.forceY.strength * forceProperties.forceY.enabled) |
|
.y(height * forceProperties.forceY.y); |
|
force.force("link") |
|
.id(function(d) {return d.id;}) |
|
.distance(forceProperties.link.distance) |
|
.iterations(forceProperties.link.iterations) |
|
.links(forceProperties.link.enabled ? graph.links : []); |
|
|
|
|
|
// updates ignored until this is run |
|
// restarts the simulation (important if simulation has already slowed down) |
|
force.alpha(1).restart(); |
|
} |
|
|
|
// generate the svg objects and force simulation |
|
function initializeDisplay() { |
|
if(drawGrid) { |
|
svgbgGrid.selectAll('.vertical') |
|
.data(d3.range(1, width / gridProperties.resolution)) |
|
.enter().append('line') |
|
.attr('class', 'vertical') |
|
.attr('x1', function (d) { |
|
return d * gridProperties.resolution; |
|
}) |
|
.attr('y1', 0) |
|
.attr('x2', function (d) { |
|
return d * gridProperties.resolution; |
|
}) |
|
.attr('y2', height); |
|
|
|
svgbgGrid.selectAll('.horizontal') |
|
.data(d3.range(1, height / gridProperties.resolution)) |
|
.enter().append('line') |
|
.attr('class', 'horizontal') |
|
.attr('x1', 0) |
|
.attr('y1', function (d) { |
|
return d * gridProperties.resolution; |
|
}) |
|
.attr('x2', width) |
|
.attr('y2', function (d) { |
|
return d * gridProperties.resolution; |
|
}); |
|
} |
|
|
|
polygons = svgVoronoi.append("g") |
|
.attr("class", "polygons") |
|
.selectAll("polygon") |
|
.data(graph.nodes) |
|
.enter().append("polygon") |
|
.style("fill", function(d) { return color(d.group); }) |
|
.style("fill-opacity", .2) |
|
.style("stroke", "black") |
|
.call(d3.drag() |
|
.on("start", dragstarted) |
|
.on("drag", dragged) |
|
.on("end", dragended)); |
|
|
|
node = svgForce.append("g") |
|
.attr("class", "nodes") |
|
.selectAll("circle") |
|
.data(graph.nodes) |
|
.enter().append("circle") |
|
.style("fill", function(d) { return color(d.group); }) |
|
.attr("class", "node") |
|
.call(d3.drag() |
|
.on("start", dragstarted) |
|
.on("drag", dragged) |
|
.on("end", dragended)); |
|
|
|
|
|
zoom_handler(node); |
|
|
|
path_end = svgRoads.append("g") |
|
.attr("class", "nodes") |
|
.attr("class", "path-ends") |
|
.selectAll("circle") |
|
.data(graph.nodes) |
|
.enter().append("circle") |
|
.style("fill", function(d) { return color(d.group); }) |
|
.attr("class", "path-end") |
|
.call(d3.drag() |
|
.on("start", dragstarted) |
|
.on("drag", dragged) |
|
.on("end", dragended)); |
|
zoom_handler(path_end); |
|
|
|
site = svgDelaunay.append("g") |
|
.attr("class", "sites") |
|
.selectAll("circle") |
|
.data(graph.nodes) |
|
.enter().append("circle") |
|
.attr("class", "site") |
|
.attr("r", 0.1) ; |
|
|
|
zoom_handler(site); |
|
links = voronoi(force.nodes().map(function(d) {return [d.x, d.y]})).links(); |
|
|
|
link = svgDelaunay.append("g") |
|
.attr("class", "links") |
|
.selectAll("line") |
|
.data(links) |
|
.enter().append("line"); |
|
|
|
roads = links; |
|
|
|
|
|
road = svgRoads.append("g") |
|
.attr("class", "links") |
|
.attr("class", "roads") |
|
.selectAll("line") |
|
.data(roads) |
|
.enter() |
|
.append("line"); |
|
|
|
updateDisplay(); |
|
} |
|
|
|
|
|
|
|
// update the display based on the forces (but not positions) |
|
function updateDisplay() { |
|
// set the data and properties of link lines |
|
link = link.enter().append("line").merge(link).call(redrawLink); |
|
|
|
road = road.enter().append("line").merge(road).call(redrawRoad); |
|
|
|
svgbgGrid.selectAll('.vertical').remove(); |
|
svgbgGrid.selectAll('.horizontal').remove(); |
|
if(drawGrid) { |
|
svgbgGrid.selectAll('.vertical') |
|
.data(d3.range(1, width / gridProperties.resolution)) |
|
.enter().append('line') |
|
.attr('class', 'vertical') |
|
.attr('x1', function (d) { |
|
return d * gridProperties.resolution; |
|
}) |
|
.attr('y1', 0) |
|
.attr('x2', function (d) { |
|
return d * gridProperties.resolution; |
|
}) |
|
.attr('y2', height); |
|
|
|
svgbgGrid.selectAll('.horizontal') |
|
.data(d3.range(1, height / gridProperties.resolution)) |
|
.enter().append('line') |
|
.attr('class', 'horizontal') |
|
.attr('x1', 0) |
|
.attr('y1', function (d) { |
|
return d * gridProperties.resolution; |
|
}) |
|
.attr('x2', width) |
|
.attr('y2', function (d) { |
|
return d * gridProperties.resolution; |
|
}); |
|
} |
|
node |
|
.attr("r", forceProperties.collide.radius) |
|
.attr("stroke-width", forceProperties.charge.enabled==false ? 0 : Math.abs(forceProperties.charge.strength)/15); |
|
|
|
site |
|
// .attr("r", forceProperties.collide.radius) |
|
.attr("stroke-width", forceProperties.charge.enabled==false ? 0 : 0.01); |
|
|
|
path_end |
|
.attr("r", forceProperties.collide.radius) |
|
.attr("stroke-width", forceProperties.charge.enabled==false ? 0 : 0.01); |
|
|
|
link |
|
.attr("stroke-width", forceProperties.link.enabled ? 1 : .5) |
|
.attr("opacity", forceProperties.link.enabled ? 1 : 0); |
|
|
|
|
|
|
|
road |
|
.attr("opacity", roadProperties.enabled ? 1 : 0); |
|
|
|
zoom_handler(node); |
|
zoom_handler(site); |
|
zoom_handler(path_end); |
|
} |
|
|
|
function redrawLink(link) { |
|
|
|
link |
|
.attr("x1", function(d) { return d.source[0]; }) |
|
.attr("y1", function(d) { return d.source[1]; }) |
|
.attr("x2", function(d) { return d.target[0]; }) |
|
.attr("y2", function(d) { return d.target[1]; }); |
|
|
|
} |
|
function redrawRoad(link) { |
|
|
|
link |
|
.attr("x1", function(d) { return d.source[0]; }) |
|
.attr("y1", function(d) { return d.source[1]; }) |
|
.attr("x2", function(d) { return d.target[0]; }) |
|
.attr("y2", function(d) { return d.target[1]; }); |
|
|
|
} |
|
|
|
/* function redrawLink(link) { |
|
console.log("here?"); |
|
link |
|
.classed("primary", function(d) { return d.source === sites[0] || d.target === sites[0]; }) |
|
.attr("x1", function(d) { return d.source[0]; }) |
|
.attr("y1", function(d) { return d.source[1]; }) |
|
.attr("x2", function(d) { return d.target[0]; }) |
|
.attr("y2", function(d) { return d.target[1]; }); |
|
}*/ |
|
// update the display positions after each simulation tick |
|
function ticked() { |
|
|
|
if(checkVoronoi){ |
|
if (graph.nodes[0].x) { |
|
var polygonShapes = voronoi(graph.nodes.map(function(d) {return [d.x, d.y]})).polygons(); |
|
polygons.attr("points", function(d, i) {return polygonShapes[i]}) |
|
} |
|
} |
|
|
|
if (snapGrid) { |
|
node.attr("cx", function (d) { |
|
return d.x = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.x)), gridProperties.resolution); |
|
}) |
|
.attr("cy", function (d) { |
|
return d.y = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.y)), gridProperties.resolution); |
|
}); |
|
site.attr("cx", function (d) { |
|
return d.x = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.x)), gridProperties.resolution); |
|
}) |
|
.attr("cy", function (d) { |
|
return d.y = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.y)), gridProperties.resolution); |
|
}); |
|
|
|
path_end.attr("cx", function (d) { |
|
return d.x = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.x)), gridProperties.resolution); |
|
}) |
|
.attr("cy", function (d) { |
|
return d.y = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.y)), gridProperties.resolution); |
|
}); |
|
d3.select("#alpha_value").style("flex-basis", (force.alpha()*100) + "%"); |
|
} else { |
|
|
|
node |
|
.attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); }) |
|
.attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); }); |
|
site |
|
.attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); }) |
|
.attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); }); |
|
path_end |
|
.attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); }) |
|
.attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); }); |
|
node.append("text") |
|
.attr("dx", ".10em") |
|
.attr("dy", ".10em") |
|
.text(function(d) { return d.name; }); |
|
d3.select("#alpha_value").style("flex-basis", (force.alpha()*100) + "%"); |
|
} |
|
|
|
links = voronoi(force.nodes().map(function(d) {return [d.x, d.y]})).links(); |
|
link = link.data(links), link.exit().remove(); |
|
link = link.enter().append("line").merge(link).call(redrawLink); |
|
|
|
if(roadProperties.enabled){ |
|
roads = links; |
|
road = road.data(roads), road.exit().remove(); |
|
|
|
road = road.enter().append("line").merge(road).call(redrawRoad); |
|
} |
|
|
|
} |
|
// update size-related forces |
|
d3.select(window).on("resize", function(){ |
|
width = +svg.node().getBoundingClientRect().width; |
|
height = +svg.node().getBoundingClientRect().height; |
|
updateForces(); |
|
}); |
|
|
|
// convenience function to update everything (run after UI input) |
|
function updateAll() { |
|
|
|
updateForces(); |
|
|
|
updateDisplay(); |
|
|
|
} |
|
function dragstarted(d) { |
|
d3.select(this).raise().classed("active", true); |
|
if (!d3.event.active) force.alphaTarget(0.3).restart(); |
|
d.fx = d.x; |
|
d.fy = d.y; |
|
} |
|
|
|
function dragged(d) { |
|
/* d3.select(this).select("text") |
|
.attr("x", d.x = d3.event.x) |
|
.attr("y", d.y = d3.event.y); |
|
d3.select(this).select("rect") |
|
.attr("x", d.x = d3.event.x) |
|
.attr("y", d.y = d3.event.y);*/ |
|
d.fx = d3.event.x; |
|
d.fy = d3.event.y; |
|
} |
|
|
|
function dragended(d) { |
|
d3.select(this).classed("active", false); |
|
if (!d3.event.active) force.alphaTarget(0); |
|
d.fx = null; |
|
d.fy = null; |
|
} |
|
|
|
(function() { |
|
|
|
d3.json("data.json", function(error, _graph) { |
|
if (error) throw error; graph = _graph; |
|
initializeDisplay(); |
|
initializeSimulation(); |
|
}); |
|
|
|
d3.select("#checkGroupInABox").on("change", function () { |
|
force.stop(); |
|
useGroupInABox = d3.select("#checkGroupInABox").property("checked"); |
|
force |
|
.force("group").enableGrouping(useGroupInABox) |
|
|
|
force.alphaTarget(0.5).restart(); |
|
}); |
|
|
|
d3.select("#snapGrid").on("change", function () { |
|
|
|
force.stop(); |
|
snapGrid = d3.select("#snapGrid").property("checked"); |
|
force |
|
.force("group").enableGrouping(useGroupInABox) |
|
|
|
force.alphaTarget(0.5).restart(); |
|
}); |
|
|
|
d3.select("#checkVoronoi").on("change", function () { |
|
|
|
checkVoronoi = d3.select("#checkVoronoi").property("checked"); |
|
checkVoronoi ? svgVoronoi.style("display","") : svgVoronoi.style("display","none"); |
|
//checkVoronoi ? null : polygons.remove(); |
|
//checkVoronoi ? null : links2.remove(); |
|
|
|
updateAll(); |
|
|
|
}); |
|
|
|
|
|
d3.select("#checkDelaunay").on("change", function () { |
|
|
|
checkDelaunay = d3.select("#checkDelaunay").property("checked"); |
|
checkDelaunay ? svgDelaunay.style("display","") : svgDelaunay.style("display","none"); |
|
//checkVoronoi ? null : polygons.remove(); |
|
//checkVoronoi ? null : links2.remove(); |
|
|
|
updateAll(); |
|
|
|
}); |
|
|
|
d3.select("#checkshowGrid").on("change", function () { |
|
drawGrid = d3.select("#checkshowGrid").property("checked"); |
|
if(drawGrid) { |
|
svgbgGrid.selectAll('.vertical') |
|
.data(d3.range(1, width / gridProperties.resolution)) |
|
.enter().append('line') |
|
.attr('class', 'vertical') |
|
.attr('x1', function (d) { |
|
return d * gridProperties.resolution; |
|
}) |
|
.attr('y1', 0) |
|
.attr('x2', function (d) { |
|
return d * gridProperties.resolution; |
|
}) |
|
.attr('y2', height); |
|
|
|
svgbgGrid.selectAll('.horizontal') |
|
.data(d3.range(1, height / gridProperties.resolution)) |
|
.enter().append('line') |
|
.attr('class', 'horizontal') |
|
.attr('x1', 0) |
|
.attr('y1', function (d) { |
|
return d * gridProperties.resolution; |
|
}) |
|
.attr('x2', width) |
|
.attr('y2', function (d) { |
|
return d * gridProperties.resolution; |
|
}); |
|
} |
|
else { |
|
svgbgGrid.selectAll('.vertical').remove() |
|
svgbgGrid.selectAll('.horizontal').remove() |
|
} |
|
}); |
|
|
|
d3.select("#selectTemplate").on("change", function () { |
|
template = d3.select("#selectTemplate").property("value"); |
|
force.stop(); |
|
force.force("group").template(template); |
|
force.alphaTarget(0.5).restart(); |
|
}); |
|
|
|
d3.select("#checkShowTreemap").on("change", function () { |
|
drawTemplate = d3.select("#checkShowTreemap").property("checked"); |
|
if (drawTemplate) { |
|
force.force("group").drawTemplate(svg); |
|
} else { |
|
force.force("group").deleteTemplate(svg); |
|
} |
|
}); |
|
})(); |
|
</script> |