Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ForceGraph/fix selection #140

Merged
merged 17 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions lib/graphHighlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { select } from "d3";

type Node = { id: string | number; [s: symbol]: Edge[] };

type Edge = {
id: string | number;
[s: symbol]: Node;
};

type Symbols = {
sourceNodeSym: symbol;

targetNodeSym: symbol;

edgeSym: symbol;
};

export function addHighlightOnHover(
symbols: Symbols,
nodes: Node[],
nodesSelection: d3.Selection<SVGGElement, Node, SVGGElement, unknown>,
linksSelection: d3.Selection<SVGPathElement, Edge, SVGGElement, unknown>
) {
nodesSelection.on("mouseover", function (e, d) {
select(this).classed("-active", true);
const node = nodes.find((n) => n.id === d.id);
const connectedEdges = node[symbols.edgeSym];

const connectedNodesIds = new Set(
connectedEdges
.map((edge) => [
edge[symbols.sourceNodeSym].id,
edge[symbols.targetNodeSym].id,
])
.flat()
);

nodesSelection
.classed("-fadeout", (p) => d !== p && !connectedNodesIds.has(p.id))
.classed("-half-active", (p) => {
return p !== d && connectedNodesIds.has(p.id);
});
linksSelection
.classed(
"-fadeout",
(p) =>
p[symbols.sourceNodeSym].id !== d.id &&
p[symbols.targetNodeSym].id !== d.id
)
.classed("-active", (p) => {
return (
p[symbols.sourceNodeSym].id === d.id ||
p[symbols.targetNodeSym].id === d.id
);
});
});

nodesSelection.on("mouseleave", function () {
linksSelection
.classed("-active", false)
.classed("-fadeout", false)
.classed("-half-active", false);
nodesSelection
.classed("-active", false)
.classed("-fadeout", false)
.classed("-half-active", false);
});
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import * as d3 from "d3";
import { addHighlightOnHover } from "../../lib/graphHighlight";

interface ExtendedChords extends d3.Chords {
groups: (d3.ChordGroup & {
color: string;
tooltip: string;
label: string;
id: string;
})[];
}

export function drawChordDiagram(svg, nodes, edges, { symbols, ...params }) {
const names = nodes.map((node) => node[params.nodeLabelParams.dataKey]);

const matrix = (() => {
const index = new Map(names.map((name, i) => [name, i]));
const index = new Map<string, string>(names.map((name, i) => [name, i]));
const matrix = Array.from(index, () => new Array(names.length).fill(0));
for (const edge of edges) {
matrix[index.get(edge.source)][index.get(edge.target)] +=
Expand Down Expand Up @@ -39,15 +49,25 @@ export function drawChordDiagram(svg, nodes, edges, { symbols, ...params }) {
.sortSubgroups(d3.descending)
.sortChords(d3.descending);

const chords = chord(matrix);
const chords = chord(matrix) as ExtendedChords;

const edgeColorScale = params.color();

chords.forEach((chord) => {
chord[symbols.sourceNodeSym] = nodes[chord.source.index];
chord[symbols.targetNodeSym] = nodes[chord.target.index];
});

// Nodes (arcs)
chords.groups.forEach((node) => {
node.color = edgeColorScale("" + node.index);
node.id = nodes[node.index][params.nodeLabelParams.dataKey];

node[symbols.nodeColorSym] = edgeColorScale("" + node.index);
node[symbols.edgeSym] = edges.filter(
(edge) => edge.source === node.id || edge.target === node.id
);
node.tooltip = nodes[node.index][params.tooltipParams.dataKey];
node.label = nodes[node.index][params.nodeLabelParams.dataKey];
node.id = nodes[node.index][params.nodeLabelParams.dataKey];
});

const rootGroup = svg
Expand All @@ -66,7 +86,7 @@ export function drawChordDiagram(svg, nodes, edges, { symbols, ...params }) {
.attr("d", ribbon)
.classed("link", true)
.classed("chord", true)
.style("fill", (d) => chords.groups[d.source.index].color);
.style("fill", (d) => chords.groups[d.source.index][symbols.nodeColorSym]);

const arcsG = rootGroup
.append("g")
Expand All @@ -79,7 +99,7 @@ export function drawChordDiagram(svg, nodes, edges, { symbols, ...params }) {
const arcs = arcsG
.append("path")
.attr("d", arc)
.attr("fill", (d) => d.color);
.attr("fill", (d) => d[symbols.nodeColorSym]);

arcsG.call((g) =>
g
Expand Down Expand Up @@ -125,47 +145,6 @@ export function drawChordDiagram(svg, nodes, edges, { symbols, ...params }) {
}

if (params.highlightAdjEdges) {
arcsG.on("mouseenter", onHighlight);
arcsG.on("mouseleave", onHighlightOff);
}

function onHighlight(e, d) {
const node = nodes[d.index];
const connectedEdges = node[symbols.edgeSym];
const connectedNodesIds = connectedEdges
.map((edge) => [
edge[symbols.sourceNodeSym].id,
edge[symbols.targetNodeSym].id,
])
.flat();

d3.select(this).classed("active", true);
arcsG.classed("fadeout", (p) => {
return d.index !== p.index;
});
arcsG.classed("half-active", (p) => {
return d.index !== p.index && connectedNodesIds.includes(p.id);
});
ribbons.classed(
"fadeout",
(p) => p.source.index !== d.index && p.target.index !== d.index
);
ribbons.classed("active", (p) => {
return p.source.index === d.index || p.target.index === d.index;
});
// ribbons.each(function (p) {
// const isActive = p.source.index === d.index || p.target.index === d.index
// if (isActive) {
// }
// nodes[p.source.index]
// });
}
function onHighlightOff() {
arcsG.classed("active", false);
arcsG.classed("fadeout", false);
arcsG.classed("half-active", false);

ribbons.classed("active", false);
ribbons.classed("fadeout", false);
addHighlightOnHover(symbols, nodes, arcsG, ribbons);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as d3 from "d3";

import { addHighlightOnHover } from "../../lib/graphHighlight";
export default function (
svg,
nodes,
Expand Down Expand Up @@ -113,7 +113,7 @@ export default function (
.data(nodes)
.enter()
.append("g")
.attr("class", "node-group")
.attr("class", "node")
.attr(
"transform",
(d) =>
Expand All @@ -124,7 +124,6 @@ export default function (

const nodeCircles = nodeGroups
.append("circle")
.attr("class", "node")
.style("fill", (d) => d[symbols.nodeColorSym])
.attr("r", (d) => {
return d[symbols.nodeSizeSym]; //OK
Expand Down Expand Up @@ -161,43 +160,13 @@ export default function (
}
}

// comment

if (tooltipParams.show) {
nodeCircles.attr("data-tooltip", (d) => d[tooltipParams.dataKey]);
}

if (highlightAdjEdges) {
nodeGroups.on("mouseover", function (e, d) {
// highlight current node
d3.select(this).classed("active", true);
// fade out all other nodes, highlight a little connected ones
nodeGroups
.classed("fadeout", (p) => d !== p)
.classed("half-active", (p) => {
return (
p !== d &&
d[symbols.edgeSym].some(
(edge) =>
edge[symbols.sourceNodeSym] === p ||
edge[symbols.targetNodeSym] === p
)
);
});

// fadeout not connected edges, highlight connected ones
links
.classed("fadeout", (p) => !d[symbols.edgeSym].includes(p))
.classed("active", (p) => d[symbols.edgeSym].includes(p));
});

nodeGroups.on("mouseleave", function () {
links
.classed("active", false)
.classed("fadeout", false)
.classed("half-active", false);
nodeGroups
.classed("active", false)
.classed("fadeout", false)
.classed("half-active", false);
});
addHighlightOnHover(symbols, nodes, nodeGroups, links);
}
}
2 changes: 2 additions & 0 deletions stanzas/chord-diagram/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ interface Datum {
id: string;
}

//log

export default class ChordDiagram extends MetaStanza {
tooltip: ToolTip;
_chartArea: d3.Selection<SVGGElement, any, SVGElement, any>;
Expand Down
Loading
Loading