Skip to content

Commit

Permalink
shadow interactivity
Browse files Browse the repository at this point in the history
  • Loading branch information
stereobooster committed Nov 18, 2024
1 parent 98a9fb0 commit 3711e12
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 103 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ I have implemented core packages and added some examples. However, I still need
| Mermaid | [@beoe/rehype-mermaid](/packages/rehype-mermaid/) | |
| Gnuplot | [@beoe/rehype-gnuplot](/packages/rehype-gnuplot/) | |
| Vizdom | [@beoe/rehype-vizdom](/packages/rehype-vizdom/) | |
| D2 | | |
| Penrose | | |
| ... | | |

Expand Down
130 changes: 130 additions & 0 deletions packages/demo/src/components/graphviz.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { json, alg, type Path } from "@dagrejs/graphlib";

type D = { [node: string]: Path };

// interactivity for graphviz diagrams
document.querySelectorAll(".graphviz").forEach((container) => {
const data = container.getAttribute("data-graph")
? JSON.parse(container.getAttribute("data-graph")!)
: null;

if (!data) return;
const graph = json.read(data);

function clear() {
container.querySelectorAll(".node").forEach((node) => {
node.classList.remove("active");
node.classList.remove("selected");
});
container
.querySelectorAll(".edge")
.forEach((node) => node.classList.remove("active"));
}

function highlight(id: string) {
alg.postorder(graph, [id]).forEach((node) => {
container.querySelector(`#node${node}`)?.classList.add("active");
graph.outEdges(node)?.forEach(({ name }) => {
container.querySelector(`#edge${name}`)?.classList.add("active");
});
});
}

function walkPath(d: D, node: string) {
container.querySelector(`#node${node}`)?.classList.add("active");
if (d[node].distance === 0 || d[node].distance === Infinity) return;
graph.outEdges(d[node].predecessor, node)?.forEach(({ name }) => {
container.querySelector(`#edge${name}`)?.classList.add("active");
});
walkPath(d, d[node].predecessor);
}

function drawShortestpath(a: string, b: string) {
const first = alg.dijkstra(graph, a);
if (first[b].distance != Infinity) {
walkPath(first, b);
return;
}
const second = alg.dijkstra(graph, b);
if (second[a].distance != Infinity) {
walkPath(second, a);
return;
}
}

let selected = new Set<string>();
// highlight selected
// if two nodes selected will show shortest path
container.addEventListener("click", (e) => {
// @ts-expect-error
const node = e.target?.closest(".node");
if (!node) return;
const id = node.getAttribute("id").replace("node", "");
console.log(id);
clear();

if (selected.has(id)) {
selected.delete(id);
} else {
if (selected.size < 2) {
selected.add(id);
} else {
selected.delete([...selected][0]);
selected.add(id);
}
}
if (selected.size === 0) return;
if (selected.size === 1) {
const id = [...selected][0];
container.querySelector(`#node${id}`)?.classList.add("selected");
highlight(id);
return;
}

const [a, b] = [...selected];
container.querySelector(`#node${a}`)?.classList.add("selected");
container.querySelector(`#node${b}`)?.classList.add("selected");
drawShortestpath(a, b);
});

// highlight on hover
let currentHover: string | null = null;
container.addEventListener("mouseover", (e) => {
if (selected.size > 1) return;
// @ts-expect-error
const node = e.target?.closest(".node");
if (selected.size === 0) {
if (node) {
const id = node.getAttribute("id").replace("node", "");
if (currentHover == id) return;
clear();
highlight(id);
currentHover = id;
} else {
if (currentHover == null) return;
clear();
currentHover = null;
}
} else {
const selectedId = [...selected][0];
if (node) {
const id = node.getAttribute("id").replace("node", "");
if (currentHover == id) return;
clear();
container
.querySelector(`#node${selectedId}`)
?.classList.add("selected");
drawShortestpath(selectedId, id);
currentHover = id;
} else {
if (currentHover == null) return;
clear();
container
.querySelector(`#node${selectedId}`)
?.classList.add("selected");
highlight(selectedId);
currentHover = null;
}
}
});
});
122 changes: 21 additions & 101 deletions packages/demo/src/components/vizdom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { json, alg, type Path } from "@dagrejs/graphlib";
type D = { [node: string]: Path };

// interactivity for vizdom diagrams
document.querySelectorAll(".vizdom").forEach((container) => {
document.querySelectorAll(".vizdom.ants").forEach((container) => {
const data = container.getAttribute("data-graph")
? JSON.parse(container.getAttribute("data-graph")!)
: null;
Expand Down Expand Up @@ -128,8 +128,8 @@ document.querySelectorAll(".vizdom").forEach((container) => {
});
});

// interactivity for graphviz diagrams
document.querySelectorAll(".graphviz").forEach((container) => {
// interactivity for vizdom diagrams
document.querySelectorAll(".vizdom.shadow").forEach((container) => {
const data = container.getAttribute("data-graph")
? JSON.parse(container.getAttribute("data-graph")!)
: null;
Expand All @@ -138,119 +138,39 @@ document.querySelectorAll(".graphviz").forEach((container) => {
const graph = json.read(data);

function clear() {
container.querySelectorAll(".node").forEach((node) => {
node.classList.remove("active");
node.classList.remove("selected");
});
container
.querySelectorAll(".edge")
.forEach((node) => node.classList.remove("active"));
.querySelectorAll(".node,.edge,.cluster")
.forEach((node) => node.classList.remove("shadow"));
}

function highlight(id: string) {
container
.querySelectorAll(".node,.edge,.cluster")
.forEach((node) => node.classList.add("shadow"));
alg.postorder(graph, [id]).forEach((node) => {
container.querySelector(`#node${node}`)?.classList.add("active");
container.querySelector(`#node-${node}`)?.classList.remove("shadow");
graph.outEdges(node)?.forEach(({ name }) => {
container.querySelector(`#edge${name}`)?.classList.add("active");
container.querySelector(`#edge-${name}`)?.classList.remove("shadow");
});
});
}

function walkPath(d: D, node: string) {
container.querySelector(`#node${node}`)?.classList.add("active");
if (d[node].distance === 0 || d[node].distance === Infinity) return;
graph.outEdges(d[node].predecessor, node)?.forEach(({ name }) => {
container.querySelector(`#edge${name}`)?.classList.add("active");
});
walkPath(d, d[node].predecessor);
}

function drawShortestpath(a: string, b: string) {
const first = alg.dijkstra(graph, a);
if (first[b].distance != Infinity) {
walkPath(first, b);
return;
}
const second = alg.dijkstra(graph, b);
if (second[a].distance != Infinity) {
walkPath(second, a);
return;
}
}

let selected = new Set<string>();
// highlight selected
// if two nodes selected will show shortest path
container.addEventListener("click", (e) => {
// @ts-expect-error
const node = e.target?.closest(".node");
if (!node) return;
const id = node.getAttribute("id").replace("node", "");
console.log(id);
clear();

if (selected.has(id)) {
selected.delete(id);
} else {
if (selected.size < 2) {
selected.add(id);
} else {
selected.delete([...selected][0]);
selected.add(id);
}
}
if (selected.size === 0) return;
if (selected.size === 1) {
const id = [...selected][0];
container.querySelector(`#node${id}`)?.classList.add("selected");
highlight(id);
return;
}

const [a, b] = [...selected];
container.querySelector(`#node${a}`)?.classList.add("selected");
container.querySelector(`#node${b}`)?.classList.add("selected");
drawShortestpath(a, b);
});

// highlight on hover
let currentHover: string | null = null;
container.addEventListener("mouseover", (e) => {
if (selected.size > 1) return;
// @ts-expect-error
const node = e.target?.closest(".node");
if (selected.size === 0) {
if (node) {
const id = node.getAttribute("id").replace("node", "");
if (currentHover == id) return;
clear();
highlight(id);
currentHover = id;
} else {
if (currentHover == null) return;
clear();
currentHover = null;
}

if (node) {
const id = node.getAttribute("id").replace("node-", "");
if (currentHover == id) return;
clear();
highlight(id);
currentHover = id;
} else {
const selectedId = [...selected][0];
if (node) {
const id = node.getAttribute("id").replace("node", "");
if (currentHover == id) return;
clear();
container
.querySelector(`#node${selectedId}`)
?.classList.add("selected");
drawShortestpath(selectedId, id);
currentHover = id;
} else {
if (currentHover == null) return;
clear();
container
.querySelector(`#node${selectedId}`)
?.classList.add("selected");
highlight(selectedId);
currentHover = null;
}
if (currentHover == null) return;
clear();
currentHover = null;
}
});
});
});
8 changes: 6 additions & 2 deletions packages/demo/src/content/docs/examples/vizdom.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ title: vizdom

## Simple

```vizdom dataGraph=dagre svgo=false
**Interactivity**: shadow out. Try 👇 hover nodes.

```vizdom dataGraph=dagre svgo=false class=shadow
digraph TD {
cluster=true
node [shape=box]
Expand Down Expand Up @@ -58,7 +60,9 @@ digraph TD {

## Same example as Graphviz

```vizdom dataGraph=dagre
**Interactivity**: ants. Try 👇 hover or click nodes.

```vizdom dataGraph=dagre class=ants
digraph finite_state_machine {
bgcolor="transparent";
fontname="Helvetica,Arial,sans-serif";
Expand Down
4 changes: 4 additions & 0 deletions packages/demo/src/styles/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@
animation-timing-function: linear;
}

.shadow {
opacity: 0.4;
}

.node {
cursor: pointer;
}
Expand Down

0 comments on commit 3711e12

Please sign in to comment.