From bd316d8249f096c4d5616d682f142f49d4ca914f Mon Sep 17 00:00:00 2001 From: Aiden Bai Date: Fri, 27 May 2022 08:39:44 -0700 Subject: [PATCH 1/4] Bump million to 1.9.2 --- assets/js/router.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/assets/js/router.js b/assets/js/router.js index 81c25ac1..b70aac2d 100644 --- a/assets/js/router.js +++ b/assets/js/router.js @@ -1,12 +1,16 @@ -import { router, navigate } from "https://unpkg.com/million@1.8.9-0/dist/router.mjs" +import { router, navigate, reload, prefetch } from "https://unpkg.com/million@1.9.2/dist/router.mjs" export const attachSPARouting = (draw) => { - // SPA navigation for access later - window.navigate = navigate - // We only mutate document.title and content within .singlePage element + // Attach SPA functions to the global Million namespace + window.Million = { + router, + navigate, + reload, + prefetch, + }; router(".singlePage") // We need on initial load, then subsequent redirs // requestAnimationFrame() delays graph draw until SPA routing is finished - window.addEventListener("million:navigate", () => requestAnimationFrame(draw)) + reload(draw) window.addEventListener("DOMContentLoaded", () => requestAnimationFrame(draw)) } From efb6c7845f0a2743dd07b81f1d03b03d058ede0d Mon Sep 17 00:00:00 2001 From: Aiden Bai Date: Fri, 27 May 2022 08:40:00 -0700 Subject: [PATCH 2/4] Add prefetch to graph --- assets/js/graph.js | 205 +++++++++++++++---------------------- assets/js/search.js | 2 +- layouts/partials/head.html | 2 +- 3 files changed, 87 insertions(+), 122 deletions(-) diff --git a/assets/js/graph.js b/assets/js/graph.js index f71e44d3..c7634cf3 100644 --- a/assets/js/graph.js +++ b/assets/js/graph.js @@ -1,12 +1,5 @@ -async function drawGraph( - baseUrl, - pathColors, - depth, - enableDrag, - enableLegend, - enableZoom -) { - const container = document.getElementById('graph-container') +async function drawGraph(baseUrl, pathColors, depth, enableDrag, enableLegend, enableZoom) { + const container = document.getElementById("graph-container") const { index, links, content } = await fetchData // Use .pathname to remove hashes / searchParams / text fragments @@ -23,22 +16,19 @@ async function drawGraph( const copyLinks = JSON.parse(JSON.stringify(links)) const neighbours = new Set() - const wl = [curPage || '/', '__SENTINEL'] + const wl = [curPage || "/", "__SENTINEL"] if (depth >= 0) { while (depth >= 0 && wl.length > 0) { // compute neighbours const cur = wl.shift() - if (cur === '__SENTINEL') { + if (cur === "__SENTINEL") { depth-- - wl.push('__SENTINEL') + wl.push("__SENTINEL") } else { neighbours.add(cur) const outgoing = index.links[cur] || [] const incoming = index.backlinks[cur] || [] - wl.push( - ...outgoing.map((l) => l.target), - ...incoming.map((l) => l.source) - ) + wl.push(...outgoing.map((l) => l.target), ...incoming.map((l) => l.source)) } } } else { @@ -47,14 +37,12 @@ async function drawGraph( const data = { nodes: [...neighbours].map((id) => ({ id })), - links: copyLinks.filter( - (l) => neighbours.has(l.source) && neighbours.has(l.target) - ), + links: copyLinks.filter((l) => neighbours.has(l.source) && neighbours.has(l.target)), } const color = (d) => { - if (d.id === curPage || (d.id === '/' && curPage === '')) { - return 'var(--g-node-active)' + if (d.id === curPage || (d.id === "/" && curPage === "")) { + return "var(--g-node-active)" } for (const pathColor of pathColors) { @@ -65,7 +53,7 @@ async function drawGraph( } } - return 'var(--g-node)' + return "var(--g-node)" } const drag = (simulation) => { @@ -86,12 +74,12 @@ async function drawGraph( d.fy = null } - const noop = () => { } + const noop = () => {} return d3 .drag() - .on('start', enableDrag ? dragstarted : noop) - .on('drag', enableDrag ? dragged : noop) - .on('end', enableDrag ? dragended : noop) + .on("start", enableDrag ? dragstarted : noop) + .on("drag", enableDrag ? dragged : noop) + .on("end", enableDrag ? dragended : noop) } const height = Math.max(container.offsetHeight, 250) @@ -99,67 +87,58 @@ async function drawGraph( const simulation = d3 .forceSimulation(data.nodes) - .force('charge', d3.forceManyBody().strength(-30)) + .force("charge", d3.forceManyBody().strength(-30)) .force( - 'link', + "link", d3 .forceLink(data.links) .id((d) => d.id) - .distance(40) + .distance(40), ) - .force('center', d3.forceCenter()) + .force("center", d3.forceCenter()) const svg = d3 - .select('#graph-container') - .append('svg') - .attr('width', width) - .attr('height', height) - .attr('viewBox', [-width / 2, -height / 2, width, height]) + .select("#graph-container") + .append("svg") + .attr("width", width) + .attr("height", height) + .attr("viewBox", [-width / 2, -height / 2, width, height]) if (enableLegend) { - const legend = [ - { Current: 'var(--g-node-active)' }, - { Note: 'var(--g-node)' }, - ...pathColors, - ] + const legend = [{ Current: "var(--g-node-active)" }, { Note: "var(--g-node)" }, ...pathColors] legend.forEach((legendEntry, i) => { const key = Object.keys(legendEntry)[0] const colour = legendEntry[key] svg - .append('circle') - .attr('cx', -width / 2 + 20) - .attr('cy', height / 2 - 30 * (i + 1)) - .attr('r', 6) - .style('fill', colour) + .append("circle") + .attr("cx", -width / 2 + 20) + .attr("cy", height / 2 - 30 * (i + 1)) + .attr("r", 6) + .style("fill", colour) svg - .append('text') - .attr('x', -width / 2 + 40) - .attr('y', height / 2 - 30 * (i + 1)) + .append("text") + .attr("x", -width / 2 + 40) + .attr("y", height / 2 - 30 * (i + 1)) .text(key) - .style('font-size', '15px') - .attr('alignment-baseline', 'middle') + .style("font-size", "15px") + .attr("alignment-baseline", "middle") }) } // draw links between nodes const link = svg - .append('g') - .selectAll('line') + .append("g") + .selectAll("line") .data(data.links) - .join('line') - .attr('class', 'link') - .attr('stroke', 'var(--g-link)') - .attr('stroke-width', 2) - .attr('data-source', (d) => d.source.id) - .attr('data-target', (d) => d.target.id) + .join("line") + .attr("class", "link") + .attr("stroke", "var(--g-link)") + .attr("stroke-width", 2) + .attr("data-source", (d) => d.source.id) + .attr("data-target", (d) => d.target.id) // svg groups - const graphNode = svg - .append('g') - .selectAll('g') - .data(data.nodes) - .enter() - .append('g') + const graphNode = svg.append("g").selectAll("g").data(data.nodes).enter().append("g") // calculate radius const nodeRadius = (d) => { @@ -170,82 +149,68 @@ async function drawGraph( // draw individual nodes const node = graphNode - .append('circle') - .attr('class', 'node') - .attr('id', (d) => d.id) - .attr('r', nodeRadius) - .attr('fill', color) - .style('cursor', 'pointer') - .on('click', (_, d) => { + .append("circle") + .attr("class", "node") + .attr("id", (d) => d.id) + .attr("r", nodeRadius) + .attr("fill", color) + .style("cursor", "pointer") + .on("click", (_, d) => { // SPA navigation - window.navigate( - new URL(`${baseUrl}${decodeURI(d.id).replace(/\s+/g, '-')}/`), - '.singlePage' - ) + window.Million.navigate(new URL(`${baseUrl}${decodeURI(d.id).replace(/\s+/g, "-")}/`), ".singlePage") }) - .on('mouseover', function(_, d) { - d3.selectAll('.node') - .transition() - .duration(100) - .attr('fill', 'var(--g-node-inactive)') + .on("mouseover", function (_, d) { + d3.selectAll(".node").transition().duration(100).attr("fill", "var(--g-node-inactive)") const neighbours = parseIdsFromLinks([ ...(index.links[d.id] || []), ...(index.backlinks[d.id] || []), ]) - const neighbourNodes = d3 - .selectAll('.node') - .filter((d) => neighbours.includes(d.id)) + const neighbourNodes = d3.selectAll(".node").filter((d) => neighbours.includes(d.id)) const currentId = d.id + window.Million.prefetch(new URL(`${baseUrl}${decodeURI(d.id).replace(/\s+/g, "-")}/`)) const linkNodes = d3 - .selectAll('.link') + .selectAll(".link") .filter((d) => d.source.id === currentId || d.target.id === currentId) // highlight neighbour nodes - neighbourNodes.transition().duration(200).attr('fill', color) + neighbourNodes.transition().duration(200).attr("fill", color) // highlight links - linkNodes - .transition() - .duration(200) - .attr('stroke', 'var(--g-link-active)') + linkNodes.transition().duration(200).attr("stroke", "var(--g-link-active)") // show text for self d3.select(this.parentNode) .raise() - .select('text') + .select("text") .transition() .duration(200) - .style('opacity', 1) + .style("opacity", 1) }) - .on('mouseleave', function(_, d) { - d3.selectAll('.node').transition().duration(200).attr('fill', color) + .on("mouseleave", function (_, d) { + d3.selectAll(".node").transition().duration(200).attr("fill", color) const currentId = d.id const linkNodes = d3 - .selectAll('.link') + .selectAll(".link") .filter((d) => d.source.id === currentId || d.target.id === currentId) - linkNodes.transition().duration(200).attr('stroke', 'var(--g-link)') + linkNodes.transition().duration(200).attr("stroke", "var(--g-link)") - d3.select(this.parentNode) - .select('text') - .transition() - .duration(200) - .style('opacity', 0) + d3.select(this.parentNode).select("text").transition().duration(200).style("opacity", 0) }) .call(drag(simulation)) // draw labels const labels = graphNode - .append('text') - .attr('dx', 0) - .attr('dy', (d) => nodeRadius(d) + 8 + 'px') - .attr('text-anchor', 'middle') - .text((d) => content[d.id]?.title || d.id.replace('-', ' ')) - .style('opacity', 0) - .style('pointer-events', 'none') - .style('font-size', '0.4em') + .append("text") + .attr("dx", 0) + .attr("dy", (d) => nodeRadius(d) + 8 + "px") + .attr("text-anchor", "middle") + .text((d) => content[d.id]?.title || d.id.replace("-", " ")) + .style("opacity", 0) + .style("pointer-events", "none") + .style("font-size", "0.4em") .raise() .call(drag(simulation)) @@ -260,24 +225,24 @@ async function drawGraph( [width, height], ]) .scaleExtent([0.25, 4]) - .on('zoom', ({ transform }) => { - link.attr('transform', transform) - node.attr('transform', transform) + .on("zoom", ({ transform }) => { + link.attr("transform", transform) + node.attr("transform", transform) const scale = transform.k const scaledOpacity = Math.max((scale - 1) / 3.75, 0) - labels.attr('transform', transform).style('opacity', scaledOpacity) - }) + labels.attr("transform", transform).style("opacity", scaledOpacity) + }), ) } // progress the simulation - simulation.on('tick', () => { + simulation.on("tick", () => { 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) - node.attr('cx', (d) => d.x).attr('cy', (d) => d.y) - labels.attr('x', (d) => d.x).attr('y', (d) => d.y) + .attr("x1", (d) => d.source.x) + .attr("y1", (d) => d.source.y) + .attr("x2", (d) => d.target.x) + .attr("y2", (d) => d.target.y) + node.attr("cx", (d) => d.x).attr("cy", (d) => d.y) + labels.attr("x", (d) => d.x).attr("y", (d) => d.y) }) } diff --git a/assets/js/search.js b/assets/js/search.js index bb94cd30..ee006471 100644 --- a/assets/js/search.js +++ b/assets/js/search.js @@ -153,7 +153,7 @@ const highlight = (content, term) => { const redir = (id, term) => { // SPA navigation - window.navigate( + window.Million.navigate( new URL( `${BASE_URL.replace(/\/$/g, "")}${id}#:~:text=${encodeURIComponent(term)}/` ), diff --git a/layouts/partials/head.html b/layouts/partials/head.html index a49800be..b65bb843 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -107,7 +107,7 @@ {{else}} {{end}} From bc32bbeaed10eac3094fcaa899ca626dc8e56771 Mon Sep 17 00:00:00 2001 From: Aiden Bai Date: Fri, 27 May 2022 09:02:01 -0700 Subject: [PATCH 3/4] Bump milliomn to 1.9.3 --- assets/js/router.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/router.js b/assets/js/router.js index b70aac2d..dff17413 100644 --- a/assets/js/router.js +++ b/assets/js/router.js @@ -1,4 +1,4 @@ -import { router, navigate, reload, prefetch } from "https://unpkg.com/million@1.9.2/dist/router.mjs" +import { router, navigate, reload, prefetch } from "https://unpkg.com/million@1.9.3/dist/router.mjs" export const attachSPARouting = (draw) => { // Attach SPA functions to the global Million namespace From e0fd9570d746cb2aa270dbe64dae038fc02bdbac Mon Sep 17 00:00:00 2001 From: Aiden Bai Date: Fri, 27 May 2022 09:49:28 -0700 Subject: [PATCH 4/4] Bump million to 1.9.4 --- assets/js/router.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/router.js b/assets/js/router.js index dff17413..3cd33500 100644 --- a/assets/js/router.js +++ b/assets/js/router.js @@ -1,4 +1,4 @@ -import { router, navigate, reload, prefetch } from "https://unpkg.com/million@1.9.3/dist/router.mjs" +import { router, navigate, reload, prefetch } from "https://unpkg.com/million@1.9.4/dist/router.mjs" export const attachSPARouting = (draw) => { // Attach SPA functions to the global Million namespace