deploy: 2082617721
This commit is contained in:
parent
5ca5507ff9
commit
59c7305b3e
@ -63,84 +63,7 @@ Economy as a form a expression</p>
|
||||
<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>
|
||||
<script src="/assets/js/d3.v6.min.js" defer></script>
|
||||
<script src="/assets/js/scripts.js" defer></script>
|
||||
</body>
|
||||
</html>
|
81
404.html
81
404.html
@ -37,84 +37,7 @@
|
||||
<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>
|
||||
<script src="/assets/js/d3.v6.min.js" defer></script>
|
||||
<script src="/assets/js/scripts.js" defer></script>
|
||||
</body>
|
||||
</html>
|
@ -27,84 +27,7 @@
|
||||
<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>
|
||||
<script src="/assets/js/d3.v6.min.js" defer></script>
|
||||
<script src="/assets/js/scripts.js" defer></script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
assets/js/d3.v6.min.js
vendored
Normal file
2
assets/js/d3.v6.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,302 @@
|
||||
// Link preview
|
||||
|
||||
let opacityTimeout
|
||||
let contentTimeout
|
||||
const transitionDurationMs = 100
|
||||
|
||||
const iframe = document.getElementById('link-preview-iframe')
|
||||
const tooltipWrapper = document.getElementById('tooltip-wrapper')
|
||||
const 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) {
|
||||
const elem = event.target
|
||||
const elemProps = elem.getClientRects()[elem.getClientRects().length - 1]
|
||||
const top = window.pageYOffset || document.documentElement.scrollTop
|
||||
|
||||
if (event.target.host === window.location.host) {
|
||||
iframe.src = event.target.href
|
||||
iframe.onload = function () {
|
||||
let tooltipContentHtml = ''
|
||||
tooltipContentHtml += '<div class="b">' + iframe.contentWindow.document.querySelector('h1').innerHTML + '</div>'
|
||||
tooltipContentHtml += iframe.contentWindow.document.querySelector('.note-contents').innerHTML
|
||||
|
||||
tooltipContent.innerHTML = tooltipContentHtml
|
||||
|
||||
tooltipWrapper.style.display = 'block'
|
||||
setTimeout(function () {
|
||||
tooltipWrapper.style.opacity = 1
|
||||
}, 1)
|
||||
}
|
||||
|
||||
tooltipWrapper.style.left = elemProps.left - (tooltipWrapper.offsetWidth / 2) + (elemProps.width / 2) + 'px'
|
||||
if ((window.innerHeight - elemProps.top) < (tooltipWrapper.offsetHeight)) {
|
||||
tooltipWrapper.style.top = elemProps.top + top - tooltipWrapper.offsetHeight - 10 + 'px'
|
||||
} else if ((window.innerHeight - elemProps.top) > (tooltipWrapper.offsetHeight)) {
|
||||
tooltipWrapper.style.top = elemProps.top + top + 35 + 'px'
|
||||
}
|
||||
|
||||
if ((elemProps.left + (elemProps.width / 2)) < (tooltipWrapper.offsetWidth / 2)) {
|
||||
tooltipWrapper.style.left = '10px'
|
||||
} else if ((document.body.clientWidth - elemProps.left - (elemProps.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('.note-contents a').forEach(setupListeners)
|
||||
|
||||
// Notes graph
|
||||
|
||||
const d3 = window.d3
|
||||
|
||||
if (typeof graphData !== 'undefined') {
|
||||
const MINIMAL_NODE_SIZE = 10
|
||||
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
|
||||
|
||||
/* global graphData */
|
||||
|
||||
const nodesData = graphData.nodes
|
||||
const 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 = d3.select(d.target).data()[0].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', (nodeD) => {
|
||||
if (nodeD.id !== d.id && !relatedNodesSet.has(nodeD.id)) {
|
||||
return 'inactive'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
link.attr('class', (linkD) => {
|
||||
if (linkD.source.id !== d.id && linkD.target.id !== d.id) {
|
||||
return 'inactive'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
link.attr('stroke-width', (linkD) => {
|
||||
if (linkD.source.id === d.id || linkD.target.id === d.id) {
|
||||
return STROKE * 4
|
||||
}
|
||||
return STROKE
|
||||
})
|
||||
text.attr('class', (textD) => {
|
||||
if (textD.id !== d.id && !relatedNodesSet.has(textD.id)) {
|
||||
return 'inactive'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
}
|
||||
|
||||
const onMouseout = function (d) {
|
||||
node.attr('class', '')
|
||||
link.attr('class', '')
|
||||
text.attr('class', '')
|
||||
link.attr('stroke-width', STROKE)
|
||||
}
|
||||
|
||||
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 = (event) => {
|
||||
if (event) {
|
||||
const scale = 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)) + '...'
|
||||
}
|
||||
}
|
2
feed.xml
2
feed.xml
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2021-03-11T04:13:05+00:00</updated><id>/feed.xml</id><title type="html">Digital Garden</title><subtitle>A digital garden or public notebook for The Bentway’s Digital and/as Public Space Micro-Residency.</subtitle><entry><title type="html">Initial Seeds</title><link href="/2021/03/01/initial-seeds/" rel="alternate" type="text/html" title="Initial Seeds" /><published>2021-03-01T00:00:00+00:00</published><updated>2021-03-11T04:12:44+00:00</updated><id>/2021/03/01/initial-seeds</id><content type="html" xml:base="/2021/03/01/initial-seeds/"><p>Set of areas that guide our reveries?</p>
|
||||
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2021-03-12T03:08:04+00:00</updated><id>/feed.xml</id><title type="html">Digital Garden</title><subtitle>A digital garden or public notebook for The Bentway’s Digital and/as Public Space Micro-Residency.</subtitle><entry><title type="html">Initial Seeds</title><link href="/2021/03/01/initial-seeds/" rel="alternate" type="text/html" title="Initial Seeds" /><published>2021-03-01T00:00:00+00:00</published><updated>2021-03-12T03:07:34+00:00</updated><id>/2021/03/01/initial-seeds</id><content type="html" xml:base="/2021/03/01/initial-seeds/"><p>Set of areas that guide our reveries?</p>
|
||||
|
||||
<ol>
|
||||
<li>The history of <a href="/hypertext">hypertext</a>, rss+adjacent protocols and standards.</li>
|
||||
|
350
hypertext.html
350
hypertext.html
@ -10,17 +10,17 @@
|
||||
<article class="w-50">
|
||||
<div>
|
||||
<h1>Hypertext</h1>
|
||||
<time datetime="2021-03-11T04:12:44+00:00">
|
||||
Last updated on March 11, 2021
|
||||
<time datetime="2021-03-12T03:07:34+00:00">
|
||||
Last updated on March 12, 2021
|
||||
|
||||
</time>
|
||||
</div>
|
||||
|
||||
<div id="notes-entry-container">
|
||||
<content>
|
||||
<div class="note-contents">
|
||||
<p>Hypertext Markup Language</p>
|
||||
|
||||
</content>
|
||||
</div>
|
||||
|
||||
<side style="font-size: 0.9em">
|
||||
<h3 style="margin-bottom: 1em">Notes mentioning this note</h3>
|
||||
@ -42,269 +42,10 @@
|
||||
|
||||
<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="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>
|
||||
@ -320,84 +61,7 @@
|
||||
<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>
|
||||
<script src="/assets/js/d3.v6.min.js" defer></script>
|
||||
<script src="/assets/js/scripts.js" defer></script>
|
||||
</body>
|
||||
</html>
|
81
index.html
81
index.html
@ -47,84 +47,7 @@
|
||||
<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>
|
||||
<script src="/assets/js/d3.v6.min.js" defer></script>
|
||||
<script src="/assets/js/scripts.js" defer></script>
|
||||
</body>
|
||||
</html>
|
350
protocols.html
350
protocols.html
@ -10,17 +10,17 @@
|
||||
<article class="w-50">
|
||||
<div>
|
||||
<h1>Protocols</h1>
|
||||
<time datetime="2021-03-11T04:12:44+00:00">
|
||||
Last updated on March 11, 2021
|
||||
<time datetime="2021-03-12T03:07:34+00:00">
|
||||
Last updated on March 12, 2021
|
||||
|
||||
</time>
|
||||
</div>
|
||||
|
||||
<div id="notes-entry-container">
|
||||
<content>
|
||||
<div class="note-contents">
|
||||
|
||||
|
||||
</content>
|
||||
</div>
|
||||
|
||||
<side style="font-size: 0.9em">
|
||||
<h3 style="margin-bottom: 1em">Notes mentioning this note</h3>
|
||||
@ -42,269 +42,10 @@
|
||||
|
||||
<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="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>
|
||||
@ -320,84 +61,7 @@
|
||||
<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>
|
||||
<script src="/assets/js/d3.v6.min.js" defer></script>
|
||||
<script src="/assets/js/scripts.js" defer></script>
|
||||
</body>
|
||||
</html>
|
350
rss.html
350
rss.html
@ -10,17 +10,17 @@
|
||||
<article class="w-50">
|
||||
<div>
|
||||
<h1>RSS</h1>
|
||||
<time datetime="2021-03-11T04:12:44+00:00">
|
||||
Last updated on March 11, 2021
|
||||
<time datetime="2021-03-12T03:07:34+00:00">
|
||||
Last updated on March 12, 2021
|
||||
|
||||
</time>
|
||||
</div>
|
||||
|
||||
<div id="notes-entry-container">
|
||||
<content>
|
||||
<div class="note-contents">
|
||||
<p>Really Simple Syndication</p>
|
||||
|
||||
</content>
|
||||
</div>
|
||||
|
||||
<side style="font-size: 0.9em">
|
||||
<h3 style="margin-bottom: 1em">Notes mentioning this note</h3>
|
||||
@ -42,269 +42,10 @@
|
||||
|
||||
<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="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>
|
||||
@ -320,84 +61,7 @@
|
||||
<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>
|
||||
<script src="/assets/js/d3.v6.min.js" defer></script>
|
||||
<script src="/assets/js/scripts.js" defer></script>
|
||||
</body>
|
||||
</html>
|
@ -10,17 +10,17 @@
|
||||
<article class="w-50">
|
||||
<div>
|
||||
<h1>Statement of Intent</h1>
|
||||
<time datetime="2021-03-11T04:12:44+00:00">
|
||||
Last updated on March 11, 2021
|
||||
<time datetime="2021-03-12T03:07:34+00:00">
|
||||
Last updated on March 12, 2021
|
||||
|
||||
</time>
|
||||
</div>
|
||||
|
||||
<div id="notes-entry-container">
|
||||
<content>
|
||||
<div class="note-contents">
|
||||
<p>Hypha’s practice is situated across many topics that are present in the theme of <em>Adaptive Reuse & Creative Misuse</em>. Drawing from our collective experiences, histories, and methodologies, our goal for the micro-residency to investigate how notions of digital infrastructure can be reused, reinterpreted, and reconfigured, to realize a kind of public space. Our approach to this theme will be composed of a few, very preliminary, subjects that will ground the residency: the situated histories of digital infrastructure, the implications of <a class="internal-link" href="./protocols">protocols</a> for publishing (<a class="internal-link" href="./hypertext">Hypertext</a>, <a class="internal-link" href="./rss">RSS</a>, Peer-to-peer) in defining public spaces, and the possibilities of cooperative approaches to maintenance and repair. Our intent is to make the process of this investigation public through online tools mapping our thinking about the theme (Open channels in Are.na as one example) and cultivating a Digital Public Garden as part of Hypha’s contributions to the initiative (a resyndicatable adaptive online notebook). The outputs from the micro-residency will be a written contribution to the <a href="https://www.are.na/from-later/field-guide-to-the-digital-real"><em>Field Guide to the Digital Real</em></a> and a micro-website containing the synthesis of our investigations and our evolving practice. The outputs will be textual and visual, and draw from our collaborative practices as a cooperative. They will explore ways to represent relationships with existing and emergent technologies within our communities. Through our micro-residency we will capture a poetic interpretation of the theme and provide prompts for institutions in the city on how they could reconfigure technology to create radically creative platforms.</p>
|
||||
|
||||
</content>
|
||||
</div>
|
||||
|
||||
<side style="font-size: 0.9em">
|
||||
<h3 style="margin-bottom: 1em">Notes mentioning this note</h3>
|
||||
@ -40,269 +40,10 @@
|
||||
|
||||
<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="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>
|
||||
@ -318,84 +59,7 @@
|
||||
<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>
|
||||
<script src="/assets/js/d3.v6.min.js" defer></script>
|
||||
<script src="/assets/js/scripts.js" defer></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user