From 0ae4f333ed5d58385eaabab88d3813229f24a29a Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Tue, 22 Feb 2022 14:33:01 +0100 Subject: [PATCH] add SmallHyperbolic/morphisms page --- docs/morphisms/index.html | 89 +++++++++++ docs/morphisms/js/d3_visualisation.js | 221 ++++++++++++++++++++++++++ docs/morphisms/js/morphisms.js | 83 ++++++++++ 3 files changed, 393 insertions(+) create mode 100644 docs/morphisms/index.html create mode 100644 docs/morphisms/js/d3_visualisation.js create mode 100644 docs/morphisms/js/morphisms.js diff --git a/docs/morphisms/index.html b/docs/morphisms/index.html new file mode 100644 index 0000000..ff87ecb --- /dev/null +++ b/docs/morphisms/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + diff --git a/docs/morphisms/js/d3_visualisation.js b/docs/morphisms/js/d3_visualisation.js new file mode 100644 index 0000000..5543cc5 --- /dev/null +++ b/docs/morphisms/js/d3_visualisation.js @@ -0,0 +1,221 @@ +function drag(simulation) { + + function dragstarted(event, d) { + if (!event.active) simulation.alphaTarget(0.3).restart(); + d.fx = d.x; + d.fy = d.y; + } + + function dragged(event, d) { + d.fx = event.x; + d.fy = event.y; + } + + function dragended(event, d) { + if (!event.active) simulation.alphaTarget(0); + d.fx = null; + d.fy = null; + } + + return d3.drag() + .on("start", dragstarted) + .on("drag", dragged) + .on("end", dragended); +} + +function linkArc(d) { + const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y); + return ` + M${d.source.x},${d.source.y} + A${r},${r} 0 0,1 ${d.target.x},${d.target.y} + `; +} + +function highlight(node) { + return node.transition() + .duration('400') + .attr('opacity', 1) + .attr('filter', 'sepia(0.0)') + ; +} + +function dehighlight(node) { + return node.transition() + .duration('400') + .attr('opacity', 0.3) + .attr('filter', 'sepia(0.8)') + ; +} + +function _union(...arr) { + return arr.reduce((first, second) => [...new Set(first.concat(second))]); +} + +async function create_svg( + data, + width, + height, + ) { + let links = data.links; + let nodes = data.nodes; + let types = Array.from(new Set(nodes.map(d => d.level))); + let color = d3.scaleOrdinal(types, d3.schemeSet1); + + d3.select("datalist") + .selectAll("option") + .data(nodes) + .join("option") + .attr("value", n=>n.id) + .text(n=>n.id) + + + + const simulation = d3.forceSimulation(nodes) + .force("link", d3.forceLink(links).id(d => d.id)) + .force("charge", d3.forceManyBody().strength(-400)) + .force("center", d3.forceCenter(width/2, height/2)) + .force("x", d3.forceX()) + .force("y", d3.forceY()); + + const svg = d3.create("svg") + .attr("preserveAspectRatio", "xMinYMin meet") + .attr("viewBox", [0, 0, width, height]) + .classed("svg-content", true) + .style("font", "12px sans-serif"); + + // Per-type markers, as they don't inherit styles. + svg.append("defs").selectAll("marker") + .data(types) + .join("marker") + .attr("id", d => `arrow-${d}`) + .attr("viewBox", "0 -5 10 10") + .attr("refX", 15) + .attr("refY", 0) + .attr("markerWidth", 6) + .attr("markerHeight", 6) + .attr("orient", "auto") + .append("path") + .attr("fill", d=>color(d)) + .attr("d", "M0,-5 L10,0 L0,5 z") + ; + + const svg_content = svg.append("g") + + const link = svg_content.append("g") + .attr("fill", "none") + .attr("stroke-width", 1.5) + .selectAll("path") + .data(links) + .join("path") + .attr("stroke", d => color(d.source.level)) + .attr("marker-end", d => `url(${new URL(`#arrow-${d.source.level}`, location)})`) + .attr("opacity", 0.3) + .attr("filter", "sepia(0.8)") + ; + + function find_descendants(node) { + let desc = links.filter(l => l.source.id == node.id ); + if (desc.length == 0) { + return [] + } else { + let all_desc = desc + .map(d => find_descendants(d.target)) + .reduce( + (total, item) => Array.from(new Set(total.concat(item))), + desc.map(l=>l.target) + ) + ; + return all_desc; + // return _union(desc, _union(...desc.map(l=>find_descendants(l.target)))); + } + }; + + const descendants = {}; + + nodes.forEach((node) => { + let desc = find_descendants(node); + desc.push(node) + descendants[node.id] = desc; + }); + + console.log(descendants) + + function foreground_descendants(id) { + node.classed("foreground", (n) => { + return (descendants[id].find(v => v.id == n.id)) ? true : false; + }); + + link.classed("foreground", (n) => { + let verts = descendants[id] + return (verts.includes(n.source) && verts.includes(n.target)) ? true : false; + }); + } + + d3.select("input").on("input", function () { + let n = nodes.find(n => n.id == this.value) + if (n) { + foreground_descendants(n.id) + // let transform = {k:1, x:n.x, y:n.y} + console.log(n) + + d3.zoom().translateTo(svg_content,n.x, n.y) + // console.log(transform) + // svg_content.attr("transform", `translate(${n.x/2},${n.y/2})`) + } + }); + + const node = svg_content.append("g") + .attr("stroke-linecap", "round") + .attr("stroke-linejoin", "round") + .selectAll("g") + .data(nodes) + .join("g") + .attr("class", d=>d.id) + .attr("opacity", 0.3) + .attr("filter", "sepia(0.8)") + .on("mouseover", function (d, i) { + highlight(d3.select(this)); + }) + .on("mouseout", function (d, i) { + dehighlight(d3.select(this)) + }) + .on("click", function (d, i) { + console.log(this) + let id = this.classList[0]; + foreground_descendants(id); + }) + .call(drag(simulation)); + + // circles for nodes: + node.append("circle") + .attr("stroke", "white") + .attr("stroke-width", 1.5) + .attr("r", 5) + .attr("fill", d => color(d.level)); + + node.append("foreignObject") + .attr("x", 10) + .attr("y", "0.31em") + .clone(true).lower() + .attr("fill", "none") + .attr("stroke", "white") + .attr("stroke-width", 5) + .append(d => createMathSpan(d.id)); + + const zoom = d3.zoom() + .scaleExtent([0.2, 5]) + // .translateExtent([[0, 0], [width, height]]) + .on("zoom", (e) => { + console.log(e.transform) + svg_content.attr("transform", e.transform) + }); + + svg.call(zoom) + + simulation.on("tick", () => { + link.attr("d", linkArc); + node.attr("transform", d => `translate(${d.x},${d.y})`); + }); + + return svg; +} diff --git a/docs/morphisms/js/morphisms.js b/docs/morphisms/js/morphisms.js new file mode 100644 index 0000000..d8807e7 --- /dev/null +++ b/docs/morphisms/js/morphisms.js @@ -0,0 +1,83 @@ +const morphisms_url = new URL("https://raw.githubusercontent.com/kalmarek/SmallHyperbolic/mk/morphisms/data/triangle_groups_morphisms.json") + +async function fetch_json(url) { + try { + let response = await fetch(url); + let json = await response.json(); + return json; + } catch (err) { + console.log("Error while fetching json:" + err); + } +} + + + ; + +async function place_svg(svg) { + d3.select("div.canvas") + .append("div") + .attr("class", "container-fluid") + .attr("class", "svg-container") + .node() + .appendChild(svg.node()); +}; + +async function add_search() { + let input_grp = d3.select("div.canvas") + .append("div") + .classed("search-field", true) + .append("div") + .classed("container", true) + // .append("div") + // .classed("input-group", true) + ; + // let floating = input_grp.insert("div") + // .attr("class", "form-floating") + + let input = input_grp.insert("input") + .attr("class", "form-control") + .attr("list", "datalistOptions") + .attr("id", "groupSearch") + .attr("placeholder", "Type to search..."); + + // input_grp.insert("label") + // .attr("for", "groupSearch") + // .text("Type to search...") + + input_grp.insert("datalist") + .attr("id", "datalistOptions") + + // input_grp.append("button") + // .classed("btn btn-primary", true) + // .attr("type", "button") + // .attr("id", "searchBtn") + // .append("i") + // .classed("bi-search", true) + // ; +} + +async function show_katex() { + let math_objects = document.getElementsByClassName("math"); + let toggle = true; + for (let elt of math_objects) { + toggleKaTeX(elt, toggle); + let fObj = elt.parentElement; + let rect = elt.getElementsByClassName("math-tex")[0].getBoundingClientRect(); + fObj.setAttribute("width", rect.width+4); + fObj.setAttribute("height", rect.height+4); + } +}; + +add_search() + +fetch_json(morphisms_url) + // .then(async (data) => { console.log(data); return data;}) + .then(async (data) => { + return create_svg(data, window.innerWidth, window.innerHeight); + }) + // .then(async (data) => { console.log(data); return data; }) + .then(place_svg) + .then(show_katex) + // .then(add_search) +; +