Fixed layouts and regions
Some data has places of its own: countries on a map, rooms on a floor plan, panels on a board. A fixed scene draws each item at the rectangle you give it, at the size you give it, and a region fills that rectangle, or the outline you give it, edge to edge. Zoom into an island below: its districts appear exactly where it was drawn, and the other island stays where it was. Point at the lake on South Isle: it is not part of the island.
fixed-layout.tsximport { createSceneSource, DataExplorer, type ExplorerNode, type ExplorerRect } from "@data-xplorer/core";
// Two made-up islands, each split into districts that share their borders exactly. Every shape is// in one set of map units, as projected map data would be; nothing here is laid out for you.type Ring = number[][];interface Place { name: string; outline: Ring[]; // its edge, then any holes districts?: Place[];}
const islands: Place[] = [ { name: "North Isle", outline: [[[30, 40], [90, 20], [160, 45], [150, 100], [60, 110], [20, 80]]], districts: [ { name: "Westfold", outline: [[[30, 40], [90, 20], [100, 105.6], [60, 110], [20, 80]]] }, { name: "Eastmarch", outline: [[[90, 20], [160, 45], [150, 100], [100, 105.6]]] }, ], }, { name: "South Isle", outline: [ [[160, 130], [230, 110], [280, 150], [250, 190], [170, 180], [150, 155]], [[230, 150], [245, 150], [245, 162], [230, 162]], // a lake: not part of the island ], districts: [ { name: "Lowmoor", outline: [[[160, 130], [205, 117.1], [205, 184.4], [170, 180], [150, 155]]] }, { name: "Lakeside", outline: [ [[205, 117.1], [230, 110], [280, 150], [250, 190], [205, 184.4]], [[230, 150], [245, 150], [245, 162], [230, 162]], ], }, ], },];
// The smallest rectangle round a place's edge: where it sits, and the bounds of its own scene.function boxOf(place: Place): ExplorerRect { const xs = place.outline[0]!.map(([x]) => x!); const ys = place.outline[0]!.map(([, y]) => y!); const x = Math.min(...xs); const y = Math.min(...ys); return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };}
const region = (place: Place, i: number): ExplorerNode<string> => ({ id: place.name, label: place.name, weight: 1, // ignored: a fixed scene draws each item at its own box href: `#${place.name}`, drillsInto: place.districts ? place.name : null, shape: "region", box: boxOf(place), outline: [place.outline], // one polygon: its edge, then its holes ramp: i / 3,});
const source = createSceneSource<string>({ load(focus) { const island = islands.find((i) => i.name === focus); const places = island ? island.districts! : islands; return { id: focus ?? "map", heading: focus ?? "The islands", layout: "fixed", // An island's scene is bounded by that island's own box, so its districts land exactly on it. bounds: island ? boxOf(island) : { x: 0, y: 0, width: 300, height: 200 }, clusters: island ? [] : places.map(region), nodes: island ? places.map(region) : [], edges: [], }; }, parentOf: (focus) => ({ kind: "anchored", focus: null, anchorId: focus }),});
export default function FixedLayout() { return <DataExplorer source={source} focus={null} />;}layout: "fixed"on a scene, and abox({ x, y, width, height }) on each item, in units of your choosing. An item without a box is not drawn.weightis not used for size.boundsis the scene’s extent in the same units; without it, the smallest rectangle round its items. A fixed scene streamed in pages should set it, or the extent grows with each page and everything already drawn moves.- Children land on their parent when a fixed scene is entered from an item of a fixed scene:
the child scene’s
boundsis fitted exactly onto the item’s box. So keep one set of units all the way down, and give each scene its parent item’s box asbounds. shape: "region"fills its box. With anoutline, it is that shape instead: a list of polygons, each a list of rings (its edge, then its holes), each a list of[x, y]points, in the same units as the box. That is a GeoJSON MultiPolygon’scoordinatesonce projected, so aPolygon’s coordinates go in as[coordinates]. Keep the outline inside the box.- Hit-testing follows the outline: a hole is not part of the region, and nor is the box around
it. Hover, selection and the
ringemphasis are drawn along the outline. - Labels go inside a region, in the middle of its box, or at
labelAt(a point in the same units) when the middle of the box falls outside the shape. - The other shapes work in a fixed scene too: a
squarefills its box, and adiscorcloudis the largest circle that fits it. A custom renderer gets the box asframe.widthandframe.height. - Your units do not matter. Each fixed scene is scaled so its longer side is 1,000 units of its own world, the size of any other scene’s, so a small country deep in a map zooms in like anything else.
- The accessible outline lists a fixed scene’s items in the order you give them, clusters first: for a map, alphabetical is usually right.