Improve graph display, options and ability to have a global graph on the home page, local graphs on subpage.
1 files added
4 files modified
| | |
| | | async function drawGraph(baseUrl, pathColors, depth, enableDrag, enableLegend, enableZoom) { |
| | | async function drawGraph( |
| | | baseUrl, |
| | | pathColors, |
| | | depth, |
| | | enableDrag, |
| | | enableLegend, |
| | | enableZoom, |
| | | isHome, |
| | | opacityScale, |
| | | scale, |
| | | repelForce, |
| | | fontSize |
| | | ) { |
| | | const container = document.getElementById("graph-container") |
| | | const { index, links, content } = await fetchData |
| | | |
| | |
| | | .on("end", enableDrag ? dragended : noop) |
| | | } |
| | | |
| | | const height = Math.max(container.offsetHeight, 250) |
| | | const height = Math.max(container.offsetHeight, isHome ? 500 : 250) |
| | | const width = container.offsetWidth |
| | | |
| | | const simulation = d3 |
| | | .forceSimulation(data.nodes) |
| | | .force("charge", d3.forceManyBody().strength(-30)) |
| | | .force("charge", d3.forceManyBody().strength(-100 * repelForce)) |
| | | .force( |
| | | "link", |
| | | d3 |
| | |
| | | .append("svg") |
| | | .attr("width", width) |
| | | .attr("height", height) |
| | | .attr("viewBox", [-width / 2, -height / 2, width, height]) |
| | | .attr('viewBox', [-width / 2 * 1 / scale, -height / 2 * 1 / scale, width * 1 / scale, height * 1 / scale]) |
| | | |
| | | if (enableLegend) { |
| | | const legend = [{ Current: "var(--g-node-active)" }, { Note: "var(--g-node)" }, ...pathColors] |
| | |
| | | ]) |
| | | 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, "-")}/`)) |
| | | // window.Million.prefetch(new URL(`${baseUrl}${decodeURI(d.id).replace(/\s+/g, "-")}/`)) |
| | | const linkNodes = d3 |
| | | .selectAll(".link") |
| | | .filter((d) => d.source.id === currentId || d.target.id === currentId) |
| | |
| | | // highlight links |
| | | linkNodes.transition().duration(200).attr("stroke", "var(--g-link-active)") |
| | | |
| | | const bigFont = fontSize+0.5 |
| | | |
| | | // show text for self |
| | | d3.select(this.parentNode) |
| | | .raise() |
| | | .select("text") |
| | | .transition() |
| | | .duration(200) |
| | | .style("opacity", 1) |
| | | .attr('opacityOld', d3.select(this.parentNode).select('text').style("opacity")) |
| | | .style('opacity', 1) |
| | | .style('font-size', bigFont+'em') |
| | | .attr('dy', d => nodeRadius(d) + 20 + 'px') // radius is in px |
| | | }) |
| | | .on("mouseleave", function (_, d) { |
| | | d3.selectAll(".node").transition().duration(200).attr("fill", color) |
| | |
| | | |
| | | 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', d3.select(this.parentNode).select('text').attr("opacityOld")) |
| | | .style('font-size', fontSize+'em') |
| | | .attr('dy', d => nodeRadius(d) + 8 + 'px') // radius is in px |
| | | }) |
| | | .call(drag(simulation)) |
| | | |
| | |
| | | .attr("dy", (d) => nodeRadius(d) + 8 + "px") |
| | | .attr("text-anchor", "middle") |
| | | .text((d) => content[d.id]?.title || d.id.replace("-", " ")) |
| | | .style("opacity", 0) |
| | | .style('opacity', (opacityScale - 1) / 3.75) |
| | | .style("pointer-events", "none") |
| | | .style("font-size", "0.4em") |
| | | .style('font-size', fontSize+'em') |
| | | .raise() |
| | | .call(drag(simulation)) |
| | | |
| | |
| | | .on("zoom", ({ transform }) => { |
| | | link.attr("transform", transform) |
| | | node.attr("transform", transform) |
| | | const scale = transform.k |
| | | const scale = transform.k * opacityScale; |
| | | const scaledOpacity = Math.max((scale - 1) / 3.75, 0) |
| | | labels.attr("transform", transform).style("opacity", scaledOpacity) |
| | | }), |
| | |
| | | # if true, a Global Graph will be shown on home page with full width, no backlink. |
| | | # A different set of Local Graphs will be shown on sub pages. |
| | | # if false, Local Graph will be default on every page as usual |
| | | enableGlobalGraph: true |
| | | |
| | | ### Local Graph ### |
| | | |
| | | enableLegend: false |
| | | enableDrag: true |
| | | enableZoom: true |
| | | depth: -1 # set to -1 to show full graph |
| | | depth: 1 # set to -1 to show full graph |
| | | scale: 1 |
| | | repelForce: 2 |
| | | centerForce: 1 |
| | | linkDistance: 1 |
| | | fontSize: 0.6 |
| | | opacityScale: 3 |
| | | |
| | | ### Global Graph ### |
| | | |
| | | enableLegendGG: false |
| | | enableDragGG: true |
| | | enableZoomGG: true |
| | | depthGG: -1 # set to -1 to show full graph |
| | | scaleGG: 1.2 |
| | | repelForceGG: 1 |
| | | centerForceGG: 1 |
| | | linkDistanceGG: 1 |
| | | fontSizeGG: 0.5 |
| | | opacityScaleGG: 3 |
| | | |
| | | ### Graphs ### |
| | | |
| | | paths: |
| | | - /moc: "#4388cc" |
| | |
| | | {{partial "recent.html" . }} |
| | | {{end}} |
| | | </article> |
| | | {{partial "footer.html" .}} |
| | | {{partial "footerIndex.html" .}} |
| | | </div> |
| | | </body> |
| | | </html> |
| New file |
| | |
| | | |
| | | |
| | | <hr/> |
| | | |
| | | {{if $.Site.Data.config.enableFooter}} |
| | | {{if $.Site.Data.graphConfig.enableGlobalGraph}} |
| | | <div class="page-end"> |
| | | |
| | | <div> |
| | | {{partial "graph.html" .}} |
| | | </div> |
| | | |
| | | </div> |
| | | {{else}} |
| | | <hr/> |
| | | <div class="page-end"> |
| | | <div class="backlinks-container"> |
| | | {{partial "backlinks.html" .}} |
| | | </div> |
| | | <div> |
| | | {{partial "graph.html" .}} |
| | | </div> |
| | | |
| | | </div> |
| | | {{end}} |
| | | {{end}} |
| | | |
| | | {{partial "contact.html" .}} |
| | |
| | | })) |
| | | |
| | | const draw = () => { |
| | | |
| | | const siteBaseURL = new URL({{$.Site.BaseURL}}); |
| | | const pathBase = siteBaseURL.pathname; |
| | | const pathWindow = window.location.pathname; |
| | | const isHome = pathBase == pathWindow ? true : false; |
| | | |
| | | // NOTE: everything within this callback will be executed for every page navigation. This is a good place to put JavaScript that loads or modifies data on the page. |
| | | {{if $.Site.Data.config.enableFooter}} |
| | | const container = document.getElementById("graph-container") |
| | |
| | | // clear the graph in case there is anything within it |
| | | container.textContent = "" |
| | | |
| | | if (isHome && {{$.Site.Data.graphConfig.enableGlobalGraph}}) { |
| | | drawGraph( |
| | | {{strings.TrimRight "/" .Site.BaseURL}}, |
| | | {{$.Site.Data.graphConfig.paths}}, |
| | | {{$.Site.Data.graphConfig.depthGG}}, |
| | | {{$.Site.Data.graphConfig.enableDragGG}}, |
| | | {{$.Site.Data.graphConfig.enableLegendGG}}, |
| | | {{$.Site.Data.graphConfig.enableZoomGG}}, |
| | | true, |
| | | {{$.Site.Data.graphConfig.opacityScaleGG}}, |
| | | {{$.Site.Data.graphConfig.scaleGG}}, |
| | | {{$.Site.Data.graphConfig.repelForceGG}}, |
| | | {{$.Site.Data.graphConfig.fontSizeGG}} |
| | | ); |
| | | } else { |
| | | drawGraph( |
| | | {{strings.TrimRight "/" .Site.BaseURL}}, |
| | | {{$.Site.Data.graphConfig.paths}}, |
| | | {{$.Site.Data.graphConfig.depth}}, |
| | | {{$.Site.Data.graphConfig.enableDrag}}, |
| | | {{$.Site.Data.graphConfig.enableLegend}}, |
| | | {{$.Site.Data.graphConfig.enableZoom}} |
| | | {{$.Site.Data.graphConfig.enableZoom}}, |
| | | false, |
| | | {{$.Site.Data.graphConfig.opacityScale}}, |
| | | {{$.Site.Data.graphConfig.scale}}, |
| | | {{$.Site.Data.graphConfig.repelForce}}, |
| | | {{$.Site.Data.graphConfig.fontSize}} |
| | | ); |
| | | } |
| | | |
| | | {{end}} |
| | | {{if $.Site.Data.config.enableLinkPreview}} |
| | | initPopover( |