Created
May 25, 2020 21:12
-
-
Save bhpayne/c0fc326b974f19f10f6e616caaceb70e to your computer and use it in GitHub Desktop.
d3js graph with hyperlinked images for nodes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"nodes": [ | |
{"id": "9040079362", "group": 0, "img": "https://derivationmap.net/static/9040079362.png", "url": "https://derivationmap.net/list_all_expressions?referrer=d3js#9040079362", "width": 18, "height": 30, "linear index": 0}, | |
{"id": "3131111133", "group": 0, "img": "https://derivationmap.net/static/3131111133.png", "url": "https://derivationmap.net/list_all_expressions?referrer=d3js#3131111133", "width": 121, "height": 34, "linear index": 0}, | |
{"id": "4718871", "group": 2, "img": "https://derivationmap.net/static/multiplybothsidesby.png", "url": "https://derivationmap.net/list_all_inference_rules?referrer=d3js#multiply both sides by", "width": 309, "height": 32, "linear index": 2}, | |
{"id": "2113211456", "group": 0, "img": "https://derivationmap.net/static/2113211456.png", "url": "https://derivationmap.net/list_all_expressions?referrer=d3js#2113211456", "width": 121, "height": 34, "linear index": 0}, | |
{"id": "2131616531", "group": 0, "img": "https://derivationmap.net/static/2131616531.png", "url": "https://derivationmap.net/list_all_expressions?referrer=d3js#2131616531", "width": 103, "height": 30, "linear index": 0}, | |
{"id": "2169431", "group": 3, "img": "https://derivationmap.net/static/dividebothsidesby.png", "url": "https://derivationmap.net/list_all_inference_rules?referrer=d3js#divide both sides by", "width": 276, "height": 31, "linear index": 3}, | |
{"id": "9278347", "group": 1, "img": "https://derivationmap.net/static/declareinitialexpr.png", "url": "https://derivationmap.net/list_all_inference_rules?referrer=d3js#declare initial expr", "width": 257, "height": 32, "linear index": 1}, | |
{"id": "6286448", "group": 4, "img": "https://derivationmap.net/static/declarefinalexpr.png", "url": "https://derivationmap.net/list_all_inference_rules?referrer=d3js#declare final expr", "width": 236, "height": 32, "linear index": 4}, | |
{"id": "9565166889", "group": 0, "img": "https://derivationmap.net/static/9565166889.png", "url": "https://derivationmap.net/list_all_expressions?referrer=d3js#9565166889", "width": 24, "height": 23, "linear index": 0} | |
], | |
"links": [ | |
{"source": "9565166889", "target": "2169431", "value": 1}, | |
{"source": "2169431", "target": "2113211456", "value": 1}, | |
{"source": "9278347", "target": "3131111133", "value": 1}, | |
{"source": "9040079362", "target": "4718871", "value": 1}, | |
{"source": "4718871", "target": "2131616531", "value": 1}, | |
{"source": "2113211456", "target": "6286448", "value": 1}, | |
{"source": "3131111133", "target": "4718871", "value": 1}, | |
{"source": "2131616531", "target": "2169431", "value": 1} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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); | |
// https://chartio.com/resources/tutorials/how-to-resize-an-svg-when-the-window-is-resized-in-d3-js/ | |
//var svg = d3.select("div#container") | |
// .append("svg") | |
// .attr("preserveAspectRatio", "xMinYMin meet") | |
// .attr("viewBox", "0 0 300 300") | |
// .attr("border",border) | |
// .classed("svg-content", true); | |
// 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 5 10") // min-x, min-y, width, height; https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox | |
.attr("refX", 8) // x coordinate of an element's reference point; https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/refX | |
.attr("markerWidth", 20) // width of the viewport; https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerWidth | |
.attr("markerHeight", 20) | |
.attr("orient", "auto") | |
.append("svg:path") | |
.attr("d", "M0,-3L6,0L0,3") // draw line starting at 0,-5; connect to 8,0, connect to 0,5 | |
.attr("fill", "gray"); | |
// 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", "2px") | |
.attr("marker-end","url(#end-arrow)"); | |
var node = container.append("g").attr("class", "nodes") | |
.selectAll("g") | |
.data(graph.nodes) | |
.enter() // https://www.d3indepth.com/enterexit/ | |
// to hyperlink the node circle, uncomment the next three lines | |
// .append("a") | |
// .attr('href', function(d) { return d.url }) | |
// .attr('target', '_blank') | |
.append("circle") | |
.attr("r", 10) | |
.attr("fill", function(d) { return color(d.group); }); | |
// written by https://www.freelancer.com/u/Wawa27 for $20 on Freelancer | |
var labelNode = container.append("g").attr("class", "labelNodes") | |
.selectAll("text") | |
.data(label.nodes) | |
.enter() // https://www.d3indepth.com/enterexit/ | |
// the following three lines hyperlink the image | |
.append("a") | |
.attr('href', function(d, i) { return d.node.url }) | |
.attr('target', '_blank') | |
.append("image") | |
// alternative option, unverified: https://stackoverflow.com/questions/39908583/d3-js-labeling-nodes-with-image-in-force-layout | |
// BHP does not know why the i%2 is needed; without it the graph gets two images per node | |
// see "label.links.push" above which uses this even/odd identifier | |
// 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", 4) | |
.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"); | |
node.on("mouseover", focus).on("mouseout", unfocus); | |
node.call( | |
d3.drag() | |
.on("start", dragstarted) | |
.on("drag", dragged) | |
.on("end", dragended) | |
); | |
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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment