Files
Alexander cee285162a Improve bio card: profile button, touch support, root-click navigation
Replace the "View profile" link with icon action buttons in the bio card
top-right corner. On touch devices, single tap shows the bio card (with
focus + profile buttons), double tap speed-focuses in the diagram. On
desktop, click navigates the diagram and hover shows the bio card.

Clicking the root/focused person now navigates to their profile page.
Tapping the SVG background or the same card again dismisses the tooltip.
2026-03-16 22:54:43 +01:00

93 lines
2.8 KiB
JavaScript

/**
* Main chart orchestrator.
*
* Uses ELK for layout (Sugiyama / union-node pattern) and D3 for
* SVG rendering with clean orthogonal bus-line connectors.
*/
import { computeElkLayout } from "./layout/elk-layout.js";
import { createSvg, getCanvas } from "./chart/svg.js";
import { initZoom, createZoomControls } from "./chart/zoom.js";
import { renderPersonCard } from "./chart/box.js";
import { hideTooltip } from "./chart/overlay.js";
import { zoomIdentity } from "./d3.js";
export default class Chart {
constructor(containerSelector, data, baseUrl) {
this.containerSelector = containerSelector;
this.data = data;
this.config = {
cardWidth: 200,
cardHeight: 80,
horizontalSpacing: 30,
verticalSpacing: 60,
};
this.baseUrl = baseUrl;
}
async render() {
const ctr = this.containerSelector;
const chartSelector = `${ctr} .full-diagram-chart`;
const svg = createSvg(chartSelector);
this.svg = svg;
const zoomBehavior = initZoom(svg);
this.zoomBehavior = zoomBehavior;
svg.on("zoom.tooltip", () => hideTooltip());
svg.on("click.tooltip", () => hideTooltip());
createZoomControls(ctr, svg, zoomBehavior);
const canvas = getCanvas(svg);
// Compute layout using ELK
const layout = await computeElkLayout(
this.data.persons,
this.data.mainId,
this.config
);
// Click handler — root person goes to profile, others reload diagram
const baseUrl = this.baseUrl;
const mainId = this.data.mainId;
const onNodeClick = (data) => {
hideTooltip();
if (data.id === mainId && data.url) {
window.location.href = data.url;
} else {
window.location.href = baseUrl.replace("__XREF__", data.id);
}
};
// Draw connections first (behind cards)
this.renderConnections(canvas, layout);
// Draw person cards
for (const person of layout.persons) {
renderPersonCard(canvas, person, this.config, onNodeClick, ctr);
}
// Center on root
this.centerOnRoot();
}
renderConnections(canvas, layout) {
const linkGroup = canvas.append("g").attr("class", "edges");
for (const conn of layout.connections) {
linkGroup
.append("path")
.attr("class", conn.cssClass)
.attr("d", conn.path);
}
}
centerOnRoot() {
const { width, height } = this.svg.node().getBoundingClientRect();
this.svg.call(
this.zoomBehavior.transform,
zoomIdentity.translate(width / 2, height / 2)
);
}
}