Enrich Arrows SVG

We have enjoyed constructing graphs with Neo4J's Arrows app. Here we offer a workflow to enrich the SVG export from Arrows with wiki internal links. The combination makes for a fairly nice concept mapping tool. app

Draw the diagram in Arrows. Click the "Download/Export" button. Choose the SVG format. Copy the download link.

//wiki.dbbs.co/assets/pages/js-snippet-template/esm.html HEIGHT 55

Arrows encodes the SVG into a data:image/svg+xml,base64 link. We can use that link directly in this form. The form also works fine if you copy and paste the URL from an Arrows SVG uploaded as a wiki asset.

ITEM html TEXT .*<svg.*

.

We annotate the code which enriches the Arrows SVG.

Import Frame Integration Promises and setup DOM helpers.

import * as frame from "https://wiki.dbbs.co/assets/v1/frame.js" const $ = (s, el=document) => el.querySelector(s) const $$ = (s, el=document) => Array.from(el.querySelectorAll(s))

Given an Arrows SVG export URL, get the SVG DOM. We also remove the height and width attributes so browsers will scale the image to fit.

async function getSvg(url) { let res = await fetch(url) let string = await res.text() let dom = new DOMParser() .parseFromString(string, "image/svg+xml") let svg = dom.documentElement svg.removeAttribute("width") svg.removeAttribute("height") return svg }

Here is where we encode our understanding of the DOM structure of Arrows SVG export diagrams. We wrap specific elements in the document with annotated anchor tags that our HTML plugin interprets as internal links.

function enrich(svg) { $$('g > rect:not([stroke="none"])+text', svg) .forEach(wrapLabel) $$('.relationship g > rect+text', svg) .forEach(wrapRelationship) $$('.node', svg) .forEach(wrapNode) }

function wrapLabel(text) { let title = text.textContent.trim() let anchor = anchorFor(`${title}`) anchor.dataset.kind = 'label' let g = text.closest("g") g.parentElement.appendChild(anchor) g.parentElement.removeChild(g) anchor.appendChild(g) }

function wrapRelationship(text) { let title = text.textContent.trim() let anchor = anchorFor(title) let g = text.closest(".relationship") g.parentElement.appendChild(anchor) g.parentElement.removeChild(g) anchor.appendChild(g) }

function wrapNode(node) { let title = $$("g > text", node).filter(text => !$("rect", text.parentElement.parentElement)) .map(text => text.textContent.trim()) .join(" ") let anchor = anchorFor(title) node.parentElement.appendChild(anchor) node.parentElement.removeChild(node) anchor.appendChild(node) }

function anchorFor(title) { let anchor = document.createElementNS( "http://www.w3.org/2000/svg", "a") anchor.setAttribute("class", "internal") anchor.setAttribute("data-title", title) anchor.setAttribute("href", `/${asSlug(title)}.html`) return anchor }

const asSlug = title => title .replace(/\s/g, '-') .replace(/[^A-Za-z0-9-]/g, '') .toLowerCase()

Emit the HTML form.

export async function emit(el) { el.innerHTML = ` <style>input {width: 100%; display: block;}</style> <input name="title" type="text" placeholder="page title"> <input name="source" type="text" placeholder="URL to svg file"> <button>Create</button> ` }

Bind a click handler to the form button.

export async function bind(el) { el.querySelector('button').onclick = async e => { let title = $("[name=title]").value.trim() || "Enriched Arrows SVG" let url = $("input[name=source]").value.trim() let svg = await getSvg(url) enrich(svg) frame.open({ title, story: [ {type:"paragraph", text: "Describe this graph."}, {type:"html", text: svg.outerHTML} ] }, e.shiftKey) } }