1
0
mirror of https://github.com/kalmarek/SmallHyperbolic synced 2024-11-27 16:35:26 +01:00

add SmallHyperbolic/morphisms page

This commit is contained in:
Marek Kaluba 2022-02-22 14:33:01 +01:00
parent f84aa07e9e
commit 0ae4f333ed
No known key found for this signature in database
GPG Key ID: 8BF1A3855328FC15
3 changed files with 393 additions and 0 deletions

89
docs/morphisms/index.html Normal file
View File

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
<!-- KaTeX -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.css"
integrity="sha384-MlJdn/WNKDGXveldHDdyRP1R4CTHr3FeuDNfhsLPYrq2t0UBkUdK2jyTnXPEK1NQ" crossorigin="anonymous">
<!-- The loading of KaTeX is deferred to speed up page rendering -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.js"
integrity="sha384-VQ8d8WVFw0yHhCk5E8I86oOhv48xLpnDZx5T9GogA/Y84DcCKWXDmSDfn13bzFZY"
crossorigin="anonymous"></script>
<!-- To automatically render math in text elements, include the auto-render extension: -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/contrib/auto-render.min.js"
integrity="sha384-+XBljXPPiv+OzfbB3cVmLHf4hdUFHlWNZN5spNQ7rmHTXpd7WvJum6fIACpNNfIR" crossorigin="anonymous"
onload="renderMathInElement(document.body);"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style type="text/css">
body {
background-color: #eee;
font-family: "Helvetica", sans-serif;
font-weight: 300;
}
.height {
height: 100vh
}
.canvas {
display: inline-block;
position: absolute;
width: 100%;
height: 100vh;
vertical-align: top;
overflow: hidden;
border: 1mm black;
}
.svg-content {
display: inline-block;
position: absolute;
top: 0;
left: 0;
}
.math {
position: absolute;
top: 2px;
}
.foreground {
opacity: 1;
filter: sepia(0.0);
}
.search-field {
position: relative;
/* width: 30%; */
z-index: 1;
}
</style>
</head>
<body>
<div class="canvas">
<!-- <div class="container">
<div class="search-field">
<label for="exampleDataList" class="form-label">Datalist example</label>
<input class="form-control" list="datalistOptions" id="exampleDataList" placeholder="Type to search...">
<datalist id="datalistOptions">
</datalist>
</div>
</div> -->
</div>
</body>
<script type="text/javascript" src="../math_render.js"></script>
<script type="text/javascript" src="js/d3_visualisation.js"></script>
<script type="text/javascript" src="js/morphisms.js"></script>
</html>

221
docs/morphisms/js/d3_visualisation.js vendored Normal file
View File

@ -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;
}

View File

@ -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)
;