Fix hover pill geometry; fade labels in/out over a zoom span
- roundedRect typo: the first arcTo's second control point used y+width as the x-coordinate (y + width instead of x + width). Since arcTo tangent math is in absolute coordinates, the pill outline sprouted a stray wedge/notch whose size scaled with the pill's on-screen position (diagnosed live by the tester subagent: ~1000px left spike on a top-right node, right-side bites near the left edge). - Labels now fade instead of popping: fully visible at fit zoom or closer, then the labels canvas layer's opacity is interpolated across a 0.5 camera-ratio zoom span before renderLabels switches off, with the setSetting call guarded so pans don't spam refreshes.
This commit is contained in:
@@ -15,10 +15,11 @@
|
|||||||
* - defaultDrawNodeHover: the same wrapped name on a dark pill (the app's
|
* - defaultDrawNodeHover: the same wrapped name on a dark pill (the app's
|
||||||
* popover tokens) instead of sigma's hardcoded white box. Hover always
|
* popover tokens) instead of sigma's hardcoded white box. Hover always
|
||||||
* shows the name, even when labels are zoomed out.
|
* shows the name, even when labels are zoomed out.
|
||||||
* Labels are zoom-gated GLOBALLY: all names appear at once when the camera
|
* Labels are zoom-gated GLOBALLY with a fade: all names appear at once
|
||||||
* is at fit zoom or closer (ratio <= 1) and all disappear at once when
|
* when the camera is at fit zoom or closer (ratio <= 1), then fade out
|
||||||
* zoomed further out — no per-node size gating, so labels never pop in one
|
* together across a short zoom span before label rendering switches off —
|
||||||
* by one. (The Obsidian text-fade feel, applied to the whole graph.)
|
* no per-node size gating, so labels never pop in one by one. (The Obsidian
|
||||||
|
* text-fade feel, applied to the whole graph.)
|
||||||
*
|
*
|
||||||
* Clicking a node opens the detail card anchored just below the node's
|
* Clicking a node opens the detail card anchored just below the node's
|
||||||
* viewport position (flipping above near the bottom edge); any camera move
|
* viewport position (flipping above near the bottom edge); any camera move
|
||||||
@@ -64,6 +65,8 @@ const PILL_PAD_X = 8;
|
|||||||
const PILL_PAD_Y = 5;
|
const PILL_PAD_Y = 5;
|
||||||
/** Camera ratio at/below which ALL labels appear at once (1 = fit zoom). */
|
/** Camera ratio at/below which ALL labels appear at once (1 = fit zoom). */
|
||||||
const LABELS_CAMERA_RATIO = 1;
|
const LABELS_CAMERA_RATIO = 1;
|
||||||
|
/** Zoom span (camera-ratio units) over which labels fade out together. */
|
||||||
|
const LABELS_FADE_SPAN = 0.5;
|
||||||
|
|
||||||
/** Word-wrap for canvas text. Never truncates: overlong single words are
|
/** Word-wrap for canvas text. Never truncates: overlong single words are
|
||||||
* hard-broken by character so no line ever exceeds maxWidth. */
|
* hard-broken by character so no line ever exceeds maxWidth. */
|
||||||
@@ -104,7 +107,7 @@ function roundedRect(
|
|||||||
const r = Math.min(radius, width / 2, height / 2);
|
const r = Math.min(radius, width / 2, height / 2);
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.moveTo(x + r, y);
|
context.moveTo(x + r, y);
|
||||||
context.arcTo(x + width, y, y + width, y + height, r);
|
context.arcTo(x + width, y, x + width, y + height, r);
|
||||||
context.arcTo(x + width, y + height, x, y + height, r);
|
context.arcTo(x + width, y + height, x, y + height, r);
|
||||||
context.arcTo(x, y + height, x, y, r);
|
context.arcTo(x, y + height, x, y, r);
|
||||||
context.arcTo(x, y, x + width, y, r);
|
context.arcTo(x, y, x + width, y, r);
|
||||||
@@ -306,11 +309,23 @@ export function GraphView() {
|
|||||||
});
|
});
|
||||||
// Clicking the empty stage closes the node card.
|
// Clicking the empty stage closes the node card.
|
||||||
renderer.on("clickStage", () => setSelected(null));
|
renderer.on("clickStage", () => setSelected(null));
|
||||||
// One global label gate: names show at fit zoom or closer and all
|
// One global label gate with a fade: names are fully visible at fit
|
||||||
// vanish together when zoomed further out. Any pan/zoom also
|
// zoom or closer, then fade out together across LABELS_FADE_SPAN of
|
||||||
// detaches the card from its node — close it.
|
// zoom (the labels canvas layer's opacity), and only once fully
|
||||||
|
// faded does label rendering switch off. Any pan/zoom also detaches
|
||||||
|
// the card from its node — close it.
|
||||||
|
const labelsCanvas = renderer.getCanvases().labels;
|
||||||
|
let labelsRendering: boolean | null = null;
|
||||||
const syncRenderLabels = (ratio: number) => {
|
const syncRenderLabels = (ratio: number) => {
|
||||||
renderer?.setSetting("renderLabels", ratio <= LABELS_CAMERA_RATIO);
|
// 0 at fit zoom → 1 at the end of the fade span
|
||||||
|
const fade = (ratio - LABELS_CAMERA_RATIO) / LABELS_FADE_SPAN;
|
||||||
|
const alpha = Math.min(Math.max(1 - fade, 0), 1);
|
||||||
|
const shouldRender = alpha > 0;
|
||||||
|
if (shouldRender !== labelsRendering) {
|
||||||
|
labelsRendering = shouldRender;
|
||||||
|
renderer?.setSetting("renderLabels", shouldRender);
|
||||||
|
}
|
||||||
|
if (labelsCanvas) labelsCanvas.style.opacity = alpha.toFixed(3);
|
||||||
};
|
};
|
||||||
syncRenderLabels(renderer.getCamera().getState().ratio);
|
syncRenderLabels(renderer.getCamera().getState().ratio);
|
||||||
renderer.getCamera().on("updated", (state) => {
|
renderer.getCamera().on("updated", (state) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user