Skip to content

Getting started

@data-xplorer/core draws tree- and graph-shaped data as a space you zoom through. This page goes from install to a working explorer. Concepts explains the ideas behind it.

Terminal window
npm install @data-xplorer/core pixi.js react react-dom

react (18 or 19) and pixi.js (8.13.2 or later 8.x) are peer dependencies: your app provides them. PixiJS is loaded lazily, the first time an explorer mounts, so it stays out of your page’s first load. The package is ESM only.

Everything below is one file. It describes a folder tree as a scene source: given a focus (a folder path, or null for the top), it returns what to draw there. Zoom into a folder to enter it; zoom out to leave.

Running the code below: first-render.tsx
first-render.tsx
import { useState } from "react";
import { createSceneSource, DataExplorer } from "@data-xplorer/core";
// Your data: here, a small folder tree. Folders hold folders and files (with sizes in bytes).
const tree: Record<string, { folders: string[]; files: [string, number][] }> = {
"/": { folders: ["/src", "/docs", "/assets"], files: [["README.md", 4_200], ["package.json", 900]] },
"/src": { folders: ["/src/views"], files: [["index.ts", 1_500], ["camera.ts", 9_800], ["layout.ts", 12_400]] },
"/src/views": { folders: [], files: [["Outline.tsx", 6_100], ["Explorer.tsx", 21_000]] },
"/docs": { folders: [], files: [["concepts.md", 7_700], ["guide.md", 5_300]] },
"/assets": { folders: [], files: [["logo.svg", 2_100], ["hero.png", 48_000], ["icon.png", 3_000]] },
};
const name = (path: string) => path.split("/").pop() || "/";
const size = (path: string): number =>
tree[path]!.files.reduce((sum, [, bytes]) => sum + bytes, 0) + tree[path]!.folders.reduce((sum, f) => sum + size(f), 0);
// A scene source: the scene at each focus (a folder path; `null` is the top), cached for you.
const source = createSceneSource<string>({
load(focus) {
const path = focus ?? "/";
return {
id: path,
heading: name(path),
clusters: tree[path]!.folders.map((folder) => ({
id: folder,
label: name(folder),
weight: size(folder), // sizes are relative to the scene's other folders
href: `#${folder}`, // every item is also a link in the accessible outline
drillsInto: folder, // zooming into it opens this focus
shape: "cloud" as const,
})),
nodes: tree[path]!.files.map(([file, bytes]) => ({
id: file,
label: file,
sublabel: `${bytes.toLocaleString()} bytes`,
weight: bytes,
href: `#${path}/${file}`,
drillsInto: null, // a file has nothing inside
shape: "disc" as const,
})),
edges: [],
};
},
// Zooming out of a folder lands on its cloud in the parent folder.
parentOf(path) {
const parent = path.slice(0, path.lastIndexOf("/")) || "/";
return { kind: "anchored", focus: parent === "/" ? null : parent, anchorId: path };
},
});
export default function FirstRender() {
const [focus, setFocus] = useState<string | null>(null);
return <DataExplorer source={source} focus={focus} onSettle={setFocus} />;
}

Three things in it are worth knowing now:

  • Clusters and nodes. Folders are clusters, drawn as soft clouds you can enter; files are nodes, drawn as discs. weight sets each one’s size against its neighbours.
  • drillsInto and parentOf. An item’s drillsInto is the focus it opens. parentOf says where zooming out of a focus leads: here, back to the folder’s own cloud in its parent.
  • focus and onSettle. The explorer moves the camera itself as the viewer zooms. onSettle(focus) tells you where they came to rest, once per burst of motion. Store it (in state, or in your URL) and pass it back as focus; passing a different focus (Back, a deep link) makes the explorer jump there.

The explorer needs no stylesheet. Its canvas fills its container’s width; if nothing gives it a height, it takes 480 px. Give its container, or .data-explorer__surface, a height to change that.

  • Concepts: focus, scenes, packed, radial and fixed layouts, drilling and zooming out, loading, and the accessible outline.
  • Events: selection, hover, activation, and getting your own data back.
  • Styling and theming: colours, ramps and sizing.