Pond graph: size the canvas to the viewport, not the width ratio (#131)
The SVG scaled to container width with height following the fixed 800×560 viewBox ratio — on wide windows the graph grew taller than the viewport, the page got a scrollbar, and wheel-zoom scrolled along. The graph page is now a flex column filling the main column; the canvas takes the remaining height (flex: 1, min-height: 0), a ResizeObserver feeds its measured size to ForceGraph as width/height, and the SVG fills it exactly. LocalGraphPanel keeps its fixed defaults. Scope deliberately layout-only (issue comment 1187): with no scrollbar there is nothing for the wheel to scroll, so no non-passive listener needed. The graph pack now asserts the main column does not overflow vertically on the graph route. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn
This commit is contained in:
parent
31557f09e5
commit
d23e5dc10c
@ -72,6 +72,15 @@ test('pond graph renders the link structure and creates phantom pages', async ({
|
|||||||
expect(await page.locator('.force-graph__edge').count()).toBeGreaterThanOrEqual(2);
|
expect(await page.locator('.force-graph__edge').count()).toBeGreaterThanOrEqual(2);
|
||||||
await expect(page.locator('.graph-legend')).toBeVisible();
|
await expect(page.locator('.graph-legend')).toBeVisible();
|
||||||
|
|
||||||
|
// #131: the page fits the viewport — wheel-zoom must never scroll along,
|
||||||
|
// so the main column may not grow a vertical scrollbar on this route.
|
||||||
|
expect(
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const main = document.querySelector('.main')!;
|
||||||
|
return main.scrollHeight - main.clientHeight;
|
||||||
|
}),
|
||||||
|
).toBeLessThanOrEqual(0);
|
||||||
|
|
||||||
// #123: the physics/rendering sliders are there and take effect — the
|
// #123: the physics/rendering sliders are there and take effect — the
|
||||||
// font-size slider writes straight into the SVG labels.
|
// font-size slider writes straight into the SVG labels.
|
||||||
const controls = page.locator('.graph-controls');
|
const controls = page.locator('.graph-controls');
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { PageView, PondGraphView, PondView } from '@dorfteich/shared';
|
import type { PageView, PondGraphView, PondView } from '@dorfteich/shared';
|
||||||
import { DEFAULT_LABEL_COLOR } from '@dorfteich/shared';
|
import { DEFAULT_LABEL_COLOR } from '@dorfteich/shared';
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
@ -125,11 +125,37 @@ function GraphView({
|
|||||||
graphSettingsKey(pondId),
|
graphSettingsKey(pondId),
|
||||||
GRAPH_SETTINGS_DEFAULTS,
|
GRAPH_SETTINGS_DEFAULTS,
|
||||||
);
|
);
|
||||||
|
// #131: the canvas flexes to the remaining viewport height; its measured
|
||||||
|
// size becomes the SVG viewBox, so the graph fills it exactly instead of
|
||||||
|
// deriving its height from the width (which used to overflow the page).
|
||||||
|
const canvasRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [size, setSize] = useState<{ width: number; height: number } | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
const observer = new ResizeObserver((entries) => {
|
||||||
|
const rect = entries[0]?.contentRect;
|
||||||
|
if (rect && rect.width > 0 && rect.height > 0) {
|
||||||
|
setSize({ width: Math.round(rect.width), height: Math.round(rect.height) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(canvas);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GraphControls settings={settings} setSettings={setSettings} />
|
<GraphControls settings={settings} setSettings={setSettings} />
|
||||||
<div className="graph-page__canvas">
|
<div className="graph-page__canvas" ref={canvasRef}>
|
||||||
<ForceGraph nodes={nodes} edges={edges} settings={settings} onNodeClick={onNodeClick} />
|
{size !== null && (
|
||||||
|
<ForceGraph
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
width={size.width}
|
||||||
|
height={size.height}
|
||||||
|
settings={settings}
|
||||||
|
onNodeClick={onNodeClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1776,6 +1776,16 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Knowledge graph (issue #112) + local graph panel (#113). */
|
/* Knowledge graph (issue #112) + local graph panel (#113). */
|
||||||
|
|
||||||
|
/* #131: the page fills the main column as a flex column, so the canvas can
|
||||||
|
take exactly the remaining viewport height — the page itself must never
|
||||||
|
grow a scrollbar (wheel-zoom would scroll along with it). */
|
||||||
|
.graph-page {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.graph-page__header {
|
.graph-page__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
@ -1799,6 +1809,14 @@ button {
|
|||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The pond graph SVG gets its measured container size as viewBox (#131), so
|
||||||
|
it may fill the canvas exactly; the local panel keeps width-scaled sizing. */
|
||||||
|
.graph-page__canvas .force-graph {
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Physics/rendering sliders (#123). */
|
/* Physics/rendering sliders (#123). */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user