|
<html> |
|
<head> |
|
|
|
<title>Welcome to the Physics Derivation Graph</title> |
|
<!-- https://www.w3schools.com/html/html_css.asp --> |
|
<style> |
|
/* SVG region |
|
https://chartio.com/resources/tutorials/how-to-resize-an-svg-when-the-window-is-resized-in-d3-js/ |
|
*/ |
|
|
|
.svg-container { |
|
display: inline-block; |
|
position: relative; |
|
width: 100%; |
|
padding-bottom: 100%; |
|
vertical-align: top; |
|
overflow: hidden; |
|
} |
|
.svg-content { |
|
display: inline-block; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
} |
|
/* end SVG region */ |
|
|
|
/* a bit of space around the edges looks cleaner */ |
|
html, body { |
|
height: 100%; |
|
margin: 5; |
|
padding: 5; |
|
} |
|
|
|
</style> |
|
|
|
</head> |
|
<body> |
|
|
|
<center> |
|
<H2>Physics Derivation Graph</H2> |
|
</center> |
|
|
|
<P> |
|
<center> |
|
<svg id="viz"></svg> |
|
</center> |
|
</P> |
|
|
|
<script src="https://d3js.org/d3.v5.min.js"></script> |
|
|
|
<!-- code below is based on https://bl.ocks.org/mapio/53fed7d84cd1812d6a6639ed7aa83868 |
|
and is better than https://bl.ocks.org/mbostock/950642 |
|
because mapio's implementation features zoom and node coloring --> |
|
|
|
<script> |
|
var border = 1; |
|
var bordercolor="black"; |
|
var color = d3.scaleOrdinal(d3.schemeCategory10); // coloring of nodes |
|
|
|
//d3.json("https://derivationmap.net/static/884319.json").then(function(graph) { |
|
d3.json("884319.json").then(function(graph) { |
|
|
|
var node_count = graph["nodes"].length; |
|
|
|
// the following does not automatically resize as the window changes size |
|
// see https://bl.ocks.org/rudedogg187/8c86bae1f1eb0ac4008c5f7ff5f172a6 |
|
// and https://bl.ocks.org/curran/3a68b0c81991e2e94b19 |
|
if (node_count <= 10) { |
|
// https://github.com/allofphysicsgraph/proofofconcept/issues/163 |
|
console.log("inside if") |
|
var width = 600; |
|
var height = 350; |
|
} else { |
|
console.log("inside else") |
|
var width = window.innerWidth*0.9; |
|
var height = window.innerHeight*0.8; |
|
} |
|
|
|
|
|
var label = { |
|
"nodes": [], |
|
"links": [] |
|
}; |
|
|
|
graph.nodes.forEach(function(d, i) { |
|
label.nodes.push({node: d}); |
|
label.nodes.push({node: d}); |
|
label.links.push({ |
|
source: i * 2, |
|
target: i * 2 + 1 |
|
}); |
|
}); |
|
|
|
var labelLayout = d3.forceSimulation(label.nodes) |
|
.force("charge", d3.forceManyBody().strength(-50)) |
|
.force("link", d3.forceLink(label.links).distance(0).strength(2)); |
|
|
|
var graphLayout = d3.forceSimulation(graph.nodes) |
|
.force("charge", d3.forceManyBody().strength(-3000)) |
|
.force("center", d3.forceCenter(width / 2, height / 2)) |
|
.force("x", d3.forceX(width / 2).strength(1)) |
|
.force("y", d3.forceY(height / 2).strength(1)) |
|
.force("link", d3.forceLink(graph.links).id(function(d) {return d.id; }).distance(50).strength(1)) |
|
.on("tick", ticked); |
|
|
|
var adjlist = []; |
|
|
|
graph.links.forEach(function(d) { |
|
adjlist[d.source.index + "-" + d.target.index] = true; |
|
adjlist[d.target.index + "-" + d.source.index] = true; |
|
}); |
|
|
|
function neigh(a, b) { |
|
return a == b || adjlist[a + "-" + b]; |
|
} |
|
|
|
|
|
var svg = d3.select("#viz").attr("width", width).attr("height", height); |
|
|
|
// from http://bl.ocks.org/rkirsling/5001347 |
|
// define arrow markers for graph links |
|
svg.append("svg:defs").append("svg:marker") |
|
.attr("id", "end-arrow") |
|
.attr("viewBox", "0 -5 10 10") |
|
.attr("refX", 10) |
|
.attr("markerWidth", 20) |
|
.attr("markerHeight", 20) |
|
.attr("orient", "auto") |
|
.append("svg:path") |
|
.attr("d", "M0,-5L20,0L0,5") |
|
.attr("fill", "#000"); |
|
|
|
// http://bl.ocks.org/AndrewStaroscik/5222370 |
|
var borderPath = svg.append("rect") |
|
.attr("x", 0) |
|
.attr("y", 0) |
|
.attr("height", height) |
|
.attr("width", width) |
|
.style("stroke", bordercolor) |
|
.style("fill", "none") |
|
.style("stroke-width", border); |
|
|
|
var container = svg.append("g"); |
|
|
|
svg.call( |
|
d3.zoom() |
|
.scaleExtent([.1, 4]) |
|
.on("zoom", function() { container.attr("transform", d3.event.transform); }) |
|
); |
|
|
|
var link = container.append("g").attr("class", "links") |
|
.selectAll("line") |
|
.data(graph.links) |
|
.enter() |
|
.append("line") |
|
.attr("stroke", "#aaa") |
|
.attr("stroke-width", "1px") |
|
.attr("marker-end","url(#end-arrow)"); |
|
|
|
var node = container.append("g").attr("class", "nodes") |
|
.selectAll("g") |
|
.data(graph.nodes) |
|
.enter() |
|
.append("circle") |
|
.attr("r", 5) |
|
.attr("fill", function(d) { return color(d.group); }) |
|
|
|
node.on("mouseover", focus).on("mouseout", unfocus); |
|
|
|
node.call( |
|
d3.drag() |
|
.on("start", dragstarted) |
|
.on("drag", dragged) |
|
.on("end", dragended) |
|
); |
|
|
|
var labelNode = container.append("g").attr("class", "labelNodes") |
|
.selectAll("text") |
|
.data(label.nodes) |
|
.enter() |
|
.append("image") |
|
// alternative option, unverified: https://stackoverflow.com/questions/39908583/d3-js-labeling-nodes-with-image-in-force-layout |
|
// I have no idea why the i%2 is needed; without it I get two images per node |
|
// switching between i%2==1 and i%2==0 produces different image locations (?) |
|
.attr("xlink:href", function(d, i) { return i % 2 == 1 ? "" : d.node.img; } ) |
|
.attr("x", 0) |
|
.attr("y", 0) |
|
// the following alter the image size |
|
.attr("width", function(d, i) { return d.node.width/2; }) |
|
.attr("height", function(d, i) { return d.node.height/2; }) |
|
// .append("text") |
|
// .text(function(d, i) { return i % 2 == 0 ? "" : d.node.id; }) |
|
// .style("fill", "#555") |
|
// .style("font-family", "Arial") |
|
// .style("font-size", 12) |
|
.style("pointer-events", "none"); // to prevent mouseover/drag capture |
|
|
|
node.on("mouseover", focus).on("mouseout", unfocus); |
|
|
|
function ticked() { |
|
|
|
node.call(updateNode); |
|
link.call(updateLink); |
|
|
|
labelLayout.alphaTarget(0.3).restart(); |
|
labelNode.each(function(d, i) { |
|
if(i % 2 == 0) { |
|
d.x = d.node.x; |
|
d.y = d.node.y; |
|
} else { |
|
var b = this.getBBox(); |
|
|
|
var diffX = d.x - d.node.x; |
|
var diffY = d.y - d.node.y; |
|
|
|
var dist = Math.sqrt(diffX * diffX + diffY * diffY); |
|
|
|
var shiftX = b.width * (diffX - dist) / (dist * 2); |
|
shiftX = Math.max(-b.width, Math.min(0, shiftX)); |
|
var shiftY = 16; |
|
this.setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")"); |
|
} |
|
}); |
|
labelNode.call(updateNode); |
|
|
|
} |
|
|
|
function fixna(x) { |
|
if (isFinite(x)) return x; |
|
return 0; |
|
} |
|
|
|
function focus(d) { |
|
var index = d3.select(d3.event.target).datum().index; |
|
node.style("opacity", function(o) { |
|
return neigh(index, o.index) ? 1 : 0.1; |
|
}); |
|
labelNode.attr("display", function(o) { |
|
return neigh(index, o.node.index) ? "block": "none"; |
|
}); |
|
link.style("opacity", function(o) { |
|
return o.source.index == index || o.target.index == index ? 1 : 0.1; |
|
}); |
|
} |
|
|
|
function unfocus() { |
|
labelNode.attr("display", "block"); |
|
node.style("opacity", 1); |
|
link.style("opacity", 1); |
|
} |
|
|
|
function updateLink(link) { |
|
link.attr("x1", function(d) { return fixna(d.source.x); }) |
|
.attr("y1", function(d) { return fixna(d.source.y); }) |
|
.attr("x2", function(d) { return fixna(d.target.x); }) |
|
.attr("y2", function(d) { return fixna(d.target.y); }); |
|
} |
|
|
|
function updateNode(node) { |
|
node.attr("transform", function(d) { |
|
return "translate(" + fixna(d.x) + "," + fixna(d.y) + ")"; |
|
}); |
|
} |
|
|
|
function dragstarted(d) { |
|
d3.event.sourceEvent.stopPropagation(); |
|
if (!d3.event.active) graphLayout.alphaTarget(0.3).restart(); |
|
d.fx = d.x; |
|
d.fy = d.y; |
|
} |
|
|
|
function dragged(d) { |
|
d.fx = d3.event.x; |
|
d.fy = d3.event.y; |
|
} |
|
|
|
function dragended(d) { |
|
if (!d3.event.active) graphLayout.alphaTarget(0); |
|
d.fx = null; |
|
d.fy = null; |
|
} |
|
|
|
}); // d3.json |
|
</script> |
|
|
|
|
|
</body> |
|
</html> |