Skip to content

Colours and backgrounds

Anywhere a colour is accepted (an item’s color, the palette, a background, an edge style) you can pass a 0xRRGGBB number or a CSS string: #rrggbb, #rgb, rgb()/rgba() or hsl()/hsla(), with commas or spaces. Alpha is ignored, because the explorer sets transparency per layer. Named colours ("tomato") are not supported, and a value that does not parse is ignored as if it had not been set (parseColour, in ./headless, checks one yourself).

An item’s own color wins over its ramp position, which wins over the stable per-id colour. A hollow aggregate keeps the palette’s aggregate colour, so it never looks like an item.

background is drawn in layers, bottom to top: color, then gradient ({ from, to, angle? }, with angle in degrees as in CSS), then image, then the dot grid. Leave it out and you get the palette’s background with the grid. Like every option, it is live: pass a new value and the canvas redraws, with no new scene and no camera reset, and values are compared by content, so an inline object costs nothing on re-render.

Running the code below: colours-and-backgrounds.tsx
colours-and-backgrounds.tsx
import { useState } from "react";
import { createSceneSource, DataExplorer, type ExplorerBackground } from "@data-xplorer/core";
// An item's own `color` wins over the ramp. Any CSS colour string (or a 0xRRGGBB number) works.
const languages: [string, number, string][] = [
["TypeScript", 48, "#3178c6"],
["Rust", 21, "rgb(222 165 132)"],
["Python", 30, "hsl(50 90% 55%)"],
["Go", 12, "#00add8"],
["Kotlin", 9, "#a97bff"],
["Elixir", 5, "#6e4a7e"],
];
const source = createSceneSource<null>({
load: () => ({
id: "languages",
heading: "Languages by repositories",
clusters: [],
nodes: languages.map(([name, repos, color]) => ({
id: name,
label: name,
sublabel: `${repos} repositories`,
weight: repos,
href: `#${name}`,
drillsInto: null,
shape: "disc" as const,
color,
})),
edges: [],
}),
});
// Backgrounds are live: change one and the canvas redraws, with no new scene and no camera reset.
const backgrounds: Record<string, ExplorerBackground> = {
Plain: {},
Gradient: { gradient: { from: "#0f172a", to: "#3b0764", angle: 135 } },
"No grid": { color: "#111827", grid: false },
"Bright grid": { grid: { alpha: 0.6, color: "#38bdf8" } },
};
export default function ColoursAndBackgrounds() {
const [name, setName] = useState("Gradient");
return (
<>
<div className="demo-toolbar">
{Object.keys(backgrounds).map((key) => (
<button key={key} aria-pressed={name === key} onClick={() => setName(key)}>
{key}
</button>
))}
</div>
<DataExplorer source={source} focus={null} background={backgrounds[name]} />
</>
);
}
Option Default Effect
background.color palette.background A solid colour behind everything.
background.gradient none { from, to, angle? }: 180 (the default) runs top to bottom, 90 left to right.
background.image none A URL, or { url, fit?: "cover" | "tile", alpha? }, fixed to the view. It needs CORS.
background.grid on (alpha 0.22, palette.grid) false hides it; { alpha, color, step, zoom, minScreenStep, tile } restyles it. See below.

The grid is anchored to the world, so it pans and zooms with the scene. step is the world distance between repeats (160 by default). zoom: "snap" (the default) keeps the spacing between 36 and 190 px by stepping it by 4× as you zoom; zoom: "continuous" keeps step fixed, so the pattern scales smoothly like a baseplate, and fades out once it is under minScreenStep px (8 by default).

To replace the dots, give tile a lazy import, the way you give renderers. The module draws one repeating tile from the PixiJS classes in kit, in white or greys: color tints it and alpha fades it. It is drawn once, at 2× resolution.

Running the code below: custom-grid.tsx
custom-grid.tsx
import { createSceneSource, DataExplorer } from "@data-xplorer/core";
const source = createSceneSource<null>({
load: () => ({
id: "plate",
heading: "Plate",
clusters: [],
nodes: ["Red", "Blue", "Green", "Yellow"].map((name, i) => ({
id: name,
label: name,
weight: 3 + i,
href: `#${name}`,
drillsInto: null,
shape: "square" as const,
ramp: i / 3,
})),
edges: [],
}),
});
// A module-level constant: a new function on each render would reload the tile.
const studs = () => import("./custom-grid.tile");
export default function CustomGrid() {
// `step` is the world distance one tile covers; `continuous` scales it smoothly as you zoom.
return <DataExplorer source={source} focus={null} background={{ grid: { tile: studs, step: 80, zoom: "continuous", alpha: 0.5 } }} />;
}

The module it loads:

custom-grid.tile.ts
// Import this module only through the dynamic import in custom-grid.tsx, so it stays out of the
// page's first load. It imports PixiJS only as types: its classes come from `draw(kit)`.
import { defineGridTile } from "@data-xplorer/core/pixi";
// One baseplate stud, drawn in white and greys: the grid's `color` tints it, its `alpha` fades it.
export default defineGridTile({
size: 64,
draw(kit) {
const tile = new kit.Container();
tile.addChild(
new kit.Graphics().circle(34, 35, 17).fill({ color: 0x000000, alpha: 0.35 }),
new kit.Graphics().circle(32, 32, 16).fill(0xffffff).stroke({ width: 2, color: 0x7a7a7a }),
);
return tile;
},
});

Pass the same function each render (a module-level constant, as above), because a new function reloads the tile. If it fails to load or draw, the dots are used.