kcl-digital-humanities-garden/hypertext.html
2021-03-11 03:38:02 +00:00

403 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hypertext 🌱 Digital Garden</title>
<meta name="description" content="&lt;p&gt;Hypertext Markup Language&lt;/p&gt;
">
<link rel="stylesheet" href="/assets/css/style.css"><link type="application/atom+xml" rel="alternate" href="/feed.xml" title="Digital Garden" /></head><body class="dark-green garamond links-dark-green bg-washed-green"><header class="f-6 flex"><a class="no-underline-hover" rel="author" href="/">Digital Garden</a><nav>, <a class="no-underline-hover" href="/about/">About</a></nav></header><main aria-label="Content">
<div class="flex">
<article class="w-50">
<div>
<h1>Hypertext</h1>
<time datetime="2021-03-11T03:37:19+00:00">
Last updated on March 11, 2021
</time>
</div>
<div id="notes-entry-container">
<content>
<p>Hypertext Markup Language</p>
</content>
<side style="font-size: 0.9em">
<h3 style="margin-bottom: 1em">Notes mentioning this note</h3>
<div style="display: grid; grid-gap: 1em; grid-template-columns: repeat(1fr);">
<div class="backlink-box">
<a class="internal-link" href="/statement-of-intent">Statement of Intent</a><br>
<div style="font-size: 0.9em">Hyphas practice is situated across many topics that are present in the theme of Adaptive Reuse &amp; Creative Misuse. Drawing...</div>
</div>
</div>
</side>
</div>
</article>
<div class="w-50">
<p>Here are all the notes in this garden, along with their links, visualized as a graph.</p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js"
integrity="sha512-FHsFVKQ/T1KWJDGSbrUhTJyS1ph3eRrxI228ND0EGaEp6v4a/vGwPWd3Dtd/+9cI7ccofZvl/wulICEurHN1pg=="
crossorigin="anonymous"></script>
<div id="zoom"></div>
<div id="graph-wrapper">
<script>
const MINIMAL_NODE_SIZE = 8;
const MAX_NODE_SIZE = 12;
const ACTIVE_RADIUS_FACTOR = 1.5;
const STROKE = 1;
const FONT_SIZE = 16;
const TICKS = 200;
const FONT_BASELINE = 40;
const MAX_LABEL_LENGTH = 50;
const graphData = {"edges":[{"source":"229320080329284392969231993","target":"50678562812897"},{"source":"229320080329284392969231993","target":"72697617314704"},{"source":"229320080329284392969231993","target":"36028"}],"nodes":[{"id":"50678562812897","path":"/hypertext","label":"Hypertext"},{"id":"72697617314704","path":"/protocols","label":"Protocols"},{"id":"36028","path":"/rss","label":"RSS"},{"id":"229320080329284392969231993","path":"/statement-of-intent","label":"Statement of Intent"}]}
let nodesData = graphData.nodes;
let linksData = graphData.edges;
const nodeSize = {};
const updateNodeSize = () => {
nodesData.forEach((el) => {
let weight =
3 *
Math.sqrt(
linksData.filter((l) => l.source === el.id || l.target === el.id)
.length + 1
);
if (weight < MINIMAL_NODE_SIZE) {
weight = MINIMAL_NODE_SIZE;
} else if (weight > MAX_NODE_SIZE) {
weight = MAX_NODE_SIZE;
}
nodeSize[el.id] = weight;
});
};
const onClick = (d) => {
window.location = d.path
};
const onMouseover = function (d) {
const relatedNodesSet = new Set();
linksData
.filter((n) => n.target.id == d.id || n.source.id == d.id)
.forEach((n) => {
relatedNodesSet.add(n.target.id);
relatedNodesSet.add(n.source.id);
});
node.attr("class", (node_d) => {
if (node_d.id !== d.id && !relatedNodesSet.has(node_d.id)) {
return "inactive";
}
return "";
});
link.attr("class", (link_d) => {
if (link_d.source.id !== d.id && link_d.target.id !== d.id) {
return "inactive";
}
return "";
});
link.attr("stroke-width", (link_d) => {
if (link_d.source.id === d.id || link_d.target.id === d.id) {
return STROKE * 4;
}
return STROKE;
});
text.attr("class", (text_d) => {
if (text_d.id !== d.id && !relatedNodesSet.has(text_d.id)) {
return "inactive";
}
return "";
});
};
const onMouseout = function (d) {
node.attr("class", "");
link.attr("class", "");
text.attr("class", "");
link.attr("stroke-width", STROKE);
};
const sameNodes = (previous, next) => {
if (next.length !== previous.length) {
return false;
}
const map = new Map();
for (const node of previous) {
map.set(node.id, node.label);
}
for (const node of next) {
const found = map.get(node.id);
if (!found || found !== node.title) {
return false;
}
}
return true;
};
const sameEdges = (previous, next) => {
if (next.length !== previous.length) {
return false;
}
const set = new Set();
for (const edge of previous) {
set.add(`${edge.source.id}-${edge.target.id}`);
}
for (const edge of next) {
if (!set.has(`${edge.source}-${edge.target}`)) {
return false;
}
}
return true;
};
const graphWrapper = document.getElementById('graph-wrapper')
const element = document.createElementNS("http://www.w3.org/2000/svg", "svg");
element.setAttribute("width", graphWrapper.getBoundingClientRect().width);
element.setAttribute("height", window.innerHeight * 0.8);
graphWrapper.appendChild(element);
const reportWindowSize = () => {
element.setAttribute("width", window.innerWidth);
element.setAttribute("height", window.innerHeight);
};
window.onresize = reportWindowSize;
const svg = d3.select("svg");
const width = Number(svg.attr("width"));
const height = Number(svg.attr("height"));
let zoomLevel = 1;
const simulation = d3
.forceSimulation(nodesData)
.force("forceX", d3.forceX().x(width / 2))
.force("forceY", d3.forceY().y(height / 2))
.force("charge", d3.forceManyBody())
.force(
"link",
d3
.forceLink(linksData)
.id((d) => d.id)
.distance(70)
)
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(80))
.stop();
const g = svg.append("g");
let link = g.append("g").attr("class", "links").selectAll(".link");
let node = g.append("g").attr("class", "nodes").selectAll(".node");
let text = g.append("g").attr("class", "text").selectAll(".text");
const resize = () => {
if (d3.event) {
const scale = d3.event.transform;
zoomLevel = scale.k;
g.attr("transform", scale);
}
const zoomOrKeep = (value) => (zoomLevel >= 1 ? value / zoomLevel : value);
const font = Math.max(Math.round(zoomOrKeep(FONT_SIZE)), 1);
text.attr("font-size", (d) => font);
text.attr("y", (d) => d.y - zoomOrKeep(FONT_BASELINE) + 8);
link.attr("stroke-width", zoomOrKeep(STROKE));
node.attr("r", (d) => {
return zoomOrKeep(nodeSize[d.id]);
});
svg
.selectAll("circle")
.filter((_d, i, nodes) => d3.select(nodes[i]).attr("active"))
.attr("r", (d) => zoomOrKeep(ACTIVE_RADIUS_FACTOR * nodeSize[d.id]));
document.getElementById("zoom").innerHTML = zoomLevel.toFixed(2);
};
const ticked = () => {
node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
text
.attr("x", (d) => d.x)
.attr("y", (d) => d.y - (FONT_BASELINE - nodeSize[d.id]) / zoomLevel);
link
.attr("x1", (d) => d.source.x)
.attr("y1", (d) => d.source.y)
.attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y);
};
const restart = () => {
updateNodeSize();
node = node.data(nodesData, (d) => d.id);
node.exit().remove();
node = node
.enter()
.append("circle")
.attr("r", (d) => {
return nodeSize[d.id];
})
.on("click", onClick)
.on("mouseover", onMouseover)
.on("mouseout", onMouseout)
.merge(node);
link = link.data(linksData, (d) => `${d.source.id}-${d.target.id}`);
link.exit().remove();
link = link.enter().append("line").attr("stroke-width", STROKE).merge(link);
text = text.data(nodesData, (d) => d.label);
text.exit().remove();
text = text
.enter()
.append("text")
.text((d) => shorten(d.label.replace(/_*/g, ""), MAX_LABEL_LENGTH))
.attr("font-size", `${FONT_SIZE}px`)
.attr("text-anchor", "middle")
.attr("alignment-baseline", "central")
.on("click", onClick)
.on("mouseover", onMouseover)
.on("mouseout", onMouseout)
.merge(text);
node.attr("active", (d) => isCurrentPath(d.path) ? true : null);
text.attr("active", (d) => isCurrentPath(d.path) ? true : null);
simulation.nodes(nodesData);
simulation.force("link").links(linksData);
simulation.alpha(1).restart();
simulation.stop();
for (let i = 0; i < TICKS; i++) {
simulation.tick();
}
ticked();
};
const zoomHandler = d3.zoom().scaleExtent([0.2, 3]).on("zoom", resize);
zoomHandler(svg);
restart();
function isCurrentPath(notePath) {
return window.location.pathname.includes(notePath)
}
function shorten(str, maxLen, separator = ' ') {
if (str.length <= maxLen) return str;
return str.substr(0, str.lastIndexOf(separator, maxLen)) + '...';
}
</script>
</div>
</div>
</div>
</main><footer>
</footer><!-- That file is not particularly elegant. This will need a refactor at some point. -->
<div style="opacity: 0; display: none;" id='tooltip-wrapper'>
<div id='tooltip-content'>
</div>
</div>
<iframe style="display: none; height: 0; width: 0;" id='link-preview-iframe' src="">
</iframe>
<script>
var opacityTimeout;
var contentTimeout;
var transitionDurationMs = 100;
var iframe = document.getElementById('link-preview-iframe')
var tooltipWrapper = document.getElementById('tooltip-wrapper')
var tooltipContent = document.getElementById('tooltip-content')
function hideTooltip() {
opacityTimeout = setTimeout(function() {
tooltipWrapper.style.opacity = 0;
contentTimeout = setTimeout(function() {
tooltipContent.innerHTML = '';
tooltipWrapper.style.display = 'none';
}, transitionDurationMs + 1);
}, transitionDurationMs)
}
function showTooltip(event) {
var elem = event.target;
var elem_props = elem.getClientRects()[elem.getClientRects().length - 1];
var top = window.pageYOffset || document.documentElement.scrollTop
if (event.target.host === window.location.host) {
iframe.src = event.target.href
iframe.onload = function() {
tooltipContentHtml = ''
tooltipContentHtml += '<div style="font-weight: bold;">' + iframe.contentWindow.document.querySelector('h1').innerHTML + '</div>'
tooltipContentHtml += iframe.contentWindow.document.querySelector('content').innerHTML
tooltipContent.innerHTML = tooltipContentHtml
tooltipWrapper.style.display = 'block';
setTimeout(function() {
tooltipWrapper.style.opacity = 1;
}, 1)
}
tooltipWrapper.style.left = elem_props.left - (tooltipWrapper.offsetWidth / 2) + (elem_props.width / 2) + "px";
if ((window.innerHeight - elem_props.top) < (tooltipWrapper.offsetHeight)) {
tooltipWrapper.style.top = elem_props.top + top - tooltipWrapper.offsetHeight - 10 + "px";
} else if ((window.innerHeight - elem_props.top) > (tooltipWrapper.offsetHeight)) {
tooltipWrapper.style.top = elem_props.top + top + 35 + "px";
}
if ((elem_props.left + (elem_props.width / 2)) < (tooltipWrapper.offsetWidth / 2)) {
tooltipWrapper.style.left = "10px";
} else if ((document.body.clientWidth - elem_props.left - (elem_props.width / 2)) < (tooltipWrapper.offsetWidth / 2)) {
tooltipWrapper.style.left = document.body.clientWidth - tooltipWrapper.offsetWidth - 20 + "px";
}
}
}
function setupListeners(linkElement) {
linkElement.addEventListener('mouseleave', function(_event) {
hideTooltip();
});
tooltipWrapper.addEventListener('mouseleave', function(_event) {
hideTooltip();
});
linkElement.addEventListener('mouseenter', function(event) {
clearTimeout(opacityTimeout);
clearTimeout(contentTimeout);
showTooltip(event);
});
tooltipWrapper.addEventListener('mouseenter', function(event) {
clearTimeout(opacityTimeout);
clearTimeout(contentTimeout);
});
}
document.querySelectorAll('content a').forEach(setupListeners);
</script>
<script src="/assets/js/scripts.js" async></script>
</body>
</html>