Scenarios, Regions & Playback
Scenarios
Scenarios are named graph snapshots. Each captures the full graph state (nodes + edges), viewport, and region boundaries. Saved scenarios are deep-cloned — mutations to the live graph never leak into saved state.
sys.saveScenario('baseline');
sys.updateAsset('V1', { status: 'closed' });
sys.removeConnection('V1', 'H1', 'feeds');
sys.saveScenario('v1-shutdown');
sys.loadScenario('baseline');
// graph, viewport, and regions restored to baseline stateDiff
Compare two saved scenarios to see what changed:
const diff = sys.diffScenarios('baseline', 'v1-shutdown');
// → {
// assets: { added: [], removed: [], modified: [...] },
// connections: { added: [], removed: [...], modified: [] },
// regions: { added: [], removed: [], modified: [] },
// }Each category contains:
- added — items present in the second scenario but not the first
- removed — items present in the first but not the second
- modified — items in both but with different data. Returns
{ uid, before, after }
CRUD
sys.saveScenario('name');
sys.loadScenario('name');
sys.deleteScenario('name');
sys.getScenarioNames(); // → ['baseline', 'v1-shutdown']Regions
Regions are polygon-bounded spatial annotations that overlay the graph without participating in topology. They provide bounded-region semantics for any domain: distribution zones, coverage areas, administrative boundaries, operational sectors.
sys.addRegion('zone-north', {
label: 'North Service Zone',
polygon: [[12.95, 77.55], [12.95, 77.65], [13.05, 77.65], [13.05, 77.55]],
meta: { fill: '#3b82f620', stroke: '#3b82f6', population: 1200000 },
});Containment queries
Point-in-polygon queries determine which assets fall inside a region, and which regions contain a given asset:
sys.getAssetsInRegion('zone-north');
// → ['T1', 'P3', 'H12'] — assets whose coordinates fall inside the polygon
sys.getRegionsOfAsset('T1');
// → ['zone-north'] — which regions contain this assetContainment reads coordinate fields the same way as reproject() and computeExtent(): per-type coordFields if registered, otherwise the global spatial coordFields. Assets without coordinate fields are skipped.
CRUD
sys.addRegion('zone-south', {
label: 'South Service Zone',
polygon: [[12.85, 77.55], [12.85, 77.65], [12.95, 77.65], [12.95, 77.55]],
meta: { fill: '#10b98120', stroke: '#10b981' },
});
sys.updateRegion('zone-north', {
label: 'North Zone (Expanded)',
meta: { population: 1400000 },
});
sys.getRegion('zone-north'); // → { label, polygon, meta }
sys.getRegions(); // → [{ uid, label, polygon, meta }, ...]
sys.removeRegion('zone-south');
sys.regionCount; // → 1Regions are included in scenario snapshots. Different scenarios can have different region boundaries — saving a scenario captures the current region state, and loading one restores it.
The meta field is an open bag (same convention as type meta) — wity-system stores it, L3 interprets it for rendering.
Playback
Playback defines an ordered sequence of presentation steps for stepping through a system graph. Pure data — wity-system stores and serializes the sequence. L3 interprets step semantics (animation, pan/zoom, reveal timing).
sys.setPlayback([
{ type: 'focus', center: { a: 12.42, b: 76.57 }, zoom: 12 },
{ type: 'reveal-assets', uids: ['KRS', 'TKH-1'] },
{ type: 'reveal-connections', uids: ['trunk:KRS→TKH-1'] },
{ type: 'focus', uids: ['HRH', 'TAT'] },
{ type: 'reveal-regions', uids: ['zone-north'] },
{ type: 'annotate', text: 'Pipeline corridor', position: { a: 12.7, b: 77.1 } },
{ type: 'scenario', name: 'drought-2024' },
]);
sys.getPlayback();
// → [{ type: 'focus', ... }, { type: 'reveal-assets', ... }, ...]Step types are open-ended — L3 defines and interprets them. Common conventions:
| Step type | Fields | L3 interpretation |
|---|---|---|
focus | center, zoom or uids | Pan/zoom camera to area or to fit named assets |
reveal-assets | uids | Make named assets visible |
reveal-connections | uids | Make named connections visible |
reveal-regions | uids | Make named regions visible |
annotate | text, position | Show text overlay at position |
scenario | name | Switch to named scenario |
Selector resolution
For structured L2-level step resolution, SystemGraph provides resolveSelector and resolvePlaybackStep:
sys.resolveSelector({ type: 'region', uid: 'zone-north' });
// → ['T1', 'P3', 'H12'] — assets inside the region
sys.resolveSelector({ type: 'assetType', name: 'plant' });
// → ['WTP', 'WTP2'] — all assets of that type
sys.resolveSelector({ type: 'path', from: 'WTP', to: 'H1' });
// → ['WTP', 'V1', 'H1'] — shortest path
sys.resolveSelector({ type: 'uids', uids: ['WTP', 'V1'] });
// → ['WTP', 'V1'] — explicit list (validates existence)
sys.resolveSelector({ type: 'all' });
// → all asset UIDs
sys.resolveSelector({ type: 'union', selectors: [
{ type: 'region', uid: 'zone-north' },
{ type: 'assetType', name: 'plant' },
]});
// → deduplicated union of both queriesresolvePlaybackStep resolves a full step definition into concrete UIDs and viewport bounds:
const resolved = sys.resolvePlaybackStep({
label: 'Show plants in north zone',
select: [
{ type: 'region', uid: 'zone-north' },
{ type: 'assetType', name: 'plant' },
],
connections: 'related',
regions: ['zone-north'],
viewport: 'fit',
cumulative: true,
}, prevAssets);
// → { assets: [...], connections: [...], regions: [...], viewport: { bounds } }Step schema:
| Field | Type | Default | Description |
|---|---|---|---|
label | string | — | Human-readable step name |
select | Selector | Selector[] | — | What assets to show |
connections | 'related' | 'all' | 'none' | 'related' | Connection inclusion mode |
regions | string[] | [] | Region UIDs to highlight |
viewport | 'fit' | { center, zoom } | 'fit' | Camera positioning |
cumulative | boolean | true | Whether to include previous step's assets |
Spatial projection
SystemGraph supports optional spatial projection with pluggable coordinate adapters:
const sys = new SystemGraph({
GraphAbstract,
spatial: {
project: (lat, lon) => mapLibrary.project([lon, lat]),
unproject: (x, y) => mapLibrary.unproject([x, y]),
coordFields: ['lat', 'lon'],
},
});Per-type coordFields override the global default:
sys.registerAssetType('tower', {
coordFields: ['easting', 'northing'],
});
sys.addAsset('T1', 'tower', { easting: 500000, northing: 7800000 });
sys.reproject(); // writes x/y into asset data — reads correct fields per typeMethods
sys.isSpatial; // → boolean
sys.project(lat, lon); // → { x, y }
sys.unproject(x, y); // → { a, b }
sys.reproject(); // writes x/y into all assets from their coord fields
sys.computeExtent(); // → { sw: {a,b}, ne: {a,b} } from all asset positions
sys.getExtentOfAssets(uids); // → extent for a subset of assets
sys.setSpatial({ project, unproject, coordFields });
sys.clearSpatial();Serialization as <wity-knowledge>
System documents serialize to <wity-knowledge> XML via graph-headless. The graph provider must be injected once at startup:
import * as graph from '@wity/graph-headless';
import { setGraphProvider, serialize, parse } from 'wity-system';
setGraphProvider(graph);
const xml = serialize(sys.snapshot()); // → <wity-knowledge> XML string
const snapshot = parse(xml); // → SystemGraph-compatible snapshot
sys.restore(snapshot);The serializer maps the snapshot to a GraphAbstract:
- Root node (
$sys, typesystem-document) — carries version, viewport, spatial config, and all top-level fields - Type registry node (
$sys/types, typesystem-types) — serialized TypeRegistry - Scenario nodes (
$sys/scenario/<name>, typesystem-scenario) — each saved scenario - Asset nodes — directly from the system graph
- Connection edges — directly from the system graph
Metadata nodes use the $sys UID prefix to avoid collision with user-created asset UIDs.
