Glow, pulse and emphasis
An item asks for an effect; the view’s effects switch each one globally, so one request can be
shown several ways.
effects: ["glow"]draws a soft light in the item’s colour behind it.effects: ["pulse"]makes that glow breathe, each item on its own phase.emphasis: ["ring"]draws an outline and a soft halo in the palette’sownershipcolour, for a state worth seeing at a glance (such as “yours”). The ring always draws, since it carries meaning;effects.haloswitches off only the halo.
effects.tsximport { useState } from "react";import { createSceneSource, DataExplorer, type ExplorerEffects, type ExplorerNode } from "@data-xplorer/core";
// Services in a status board. Items ask for effects; the view's `effects` switch them globally.const services: [string, Partial<ExplorerNode<null>>][] = [ ["api", { effects: ["pulse"], color: "#f87171" }], // failing: it breathes ["auth", { effects: ["glow"], color: "#fbbf24" }], // degraded: a steady glow ["search", { emphasis: ["ring"] }], // yours: a ring and halo ["billing", {}], ["mail", {}], ["cdn", { emphasis: ["ring"] }],];const source = createSceneSource<null>({ load: () => ({ id: "status", heading: "Service status", clusters: [], nodes: services.map(([name, extra], i) => ({ id: name, label: name, weight: 4 + (i % 3) * 3, href: `#${name}`, drillsInto: null, shape: "disc" as const, ...extra, })), edges: [], }),});
const switches: (keyof ExplorerEffects)[] = ["glow", "pulse", "halo", "growIn", "hoverRing", "selectionRing"];
export default function Effects() { const [effects, setEffects] = useState<ExplorerEffects>({}); return ( <> <div className="demo-toolbar"> {switches.map((name) => ( <label key={name}> <input type="checkbox" checked={effects[name] !== false} onChange={(e) => setEffects({ ...effects, [name]: e.target.checked })} />{" "} {name} </label> ))} </div> <DataExplorer source={source} focus={null} effects={effects} /> </> );}| Switch | Default | Effect |
|---|---|---|
glow |
on | Glows on items that ask for glow or pulse. Off, none glow. |
pulse |
on | Whether a pulse breathes. Off, it holds as a still glow. |
halo |
on | The halo behind a ring. |
growIn |
on | An item grows from a dot to its size the first time it is drawn. |
hoverRing |
on | The ring round the hovered item. Its label still shows. |
selectionRing |
on | The ring round the selected item. onNodeSelect still fires. |
Nothing animates on a timer. A pulse asks for the next frame only while one is on screen; with
none in view, the canvas draws nothing at rest. Under prefers-reduced-motion, a pulse holds still
and items appear at their size without growing in. A hollow aggregate never glows.
Presence
Section titled “Presence”presence says how much of each item is there, from 0 to 1, by id. It scales and fades an item
where it stands, and never moves it: the layout comes from weight once per scene, so presence
can change on every frame (a time slider, a filter) while the map stays still.
presence.tsximport { useCallback, useState } from "react";import { createSceneSource, DataExplorer } from "@data-xplorer/core";
// Programming languages, sized by how widely they are taught, placed once. Presence fades in each// language from the year it appeared, so the map grows as the slider moves and nothing is relaid out.const languages: [name: string, year: number, weight: number][] = [ ["Fortran", 1957, 6], ["Lisp", 1958, 5], ["COBOL", 1959, 5], ["BASIC", 1964, 5], ["C", 1972, 10], ["SQL", 1974, 9], ["C++", 1985, 9], ["Perl", 1987, 4], ["Haskell", 1990, 4], ["Python", 1991, 12], ["Java", 1995, 11], ["JavaScript", 1995, 12], ["PHP", 1995, 6], ["Ruby", 1995, 5], ["C#", 2000, 8], ["Go", 2009, 6], ["Rust", 2015, 6], ["TypeScript", 2012, 9], ["Swift", 2014, 6], ["Kotlin", 2011, 5],];const released = new Map(languages.map(([name, year]) => [name, year]));
const source = createSceneSource<null>({ load: () => ({ id: "languages", heading: "Programming languages", clusters: [], nodes: languages.map(([name, , weight]) => ({ id: name, label: name, weight, href: `#${name}`, drillsInto: null, shape: "disc" as const, })), edges: [], }),});
export default function Presence() { const [year, setYear] = useState(1975); // A new function when the year changes is what tells the explorer to redraw; items ease to it. const presence = useCallback((id: string) => ((released.get(id) ?? 0) <= year ? 1 : 0), [year]); return ( <> <div className="demo-toolbar"> <label> Year{" "} <input type="range" min={1955} max={2020} value={year} onChange={(e) => setYear(Number(e.target.value))} /> {year} </label> </div> <DataExplorer source={source} focus={null} presence={presence} /> </> );}- Size follows the square root, so an item’s area is proportional to its presence, the same
rule
weightuses. - At 0 an item is a ghost: small, faint and unlabelled, but still drawn, selectable and enterable, so the viewer can see what is not there yet.
undefinedis 1. Pass a new function when the answer changes; items ease to it, and settle at once underprefers-reduced-motion. The accessible outline lists every item whatever its presence; say what presence means in your ownmarksor labels if it matters.