@jmbarzee/force-graph 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -28
- package/dist-lib/components/FilterBar.d.ts +12 -0
- package/dist-lib/components/graph-view/useTheme.d.ts +6 -3
- package/dist-lib/filter/types.d.ts +16 -14
- package/dist-lib/graph/axis.d.ts +28 -10
- package/dist-lib/graph/model.d.ts +7 -2
- package/dist-lib/graph/taxonomy.d.ts +6 -11
- package/dist-lib/graph/validate.d.ts +6 -5
- package/dist-lib/index.d.ts +1 -1
- package/dist-lib/index.js +2423 -2336
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/styles.css +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ their values on those axes, and mounts `GraphView`. The view lays the graph out
|
|
|
9
9
|
pull on any axis the nodes carry, two layout axes keyed on an axis or a
|
|
10
10
|
computed scalar), filters it on any declared axis, highlights it, and lets the
|
|
11
11
|
user tune the forces — without knowing what any node or edge means. The host's
|
|
12
|
-
vocabulary never enters this package.
|
|
12
|
+
vocabulary never enters this package. What the user sees and does is
|
|
13
|
+
[SPEC.md](SPEC.md).
|
|
13
14
|
|
|
14
15
|
## Install
|
|
15
16
|
|
|
@@ -143,7 +144,7 @@ never by spreading the built object.
|
|
|
143
144
|
and no icon elsewhere, the abbreviation from the label, no tooltip),
|
|
144
145
|
`initialSelection(values)` is the first open's selection (default every
|
|
145
146
|
value on an allow-list, none on a narrowing axis), `presentation` is
|
|
146
|
-
`chip` or `tag
|
|
147
|
+
`chip` or `tag`, and `reads: 'tags'` makes it a tag axis (see The graph).
|
|
147
148
|
- **Scalars and hooks.** `scalars` adds computed axes (`id`, `label` and
|
|
148
149
|
`tooltip` — defaults the id and the label — and `compute(graph)` answering a
|
|
149
150
|
number per node; `normalize` maps
|
|
@@ -179,7 +180,14 @@ engine compares them and never parses one.
|
|
|
179
180
|
the parent, and
|
|
180
181
|
`validateGraph` names one that contradicts `parentId`. A host adds axes of
|
|
181
182
|
its own to edges and nodes alike through `values`; any axis the nodes
|
|
182
|
-
carry can be pushed, pulled and laid out on.
|
|
183
|
+
carry a value on can be pushed, pulled and laid out on.
|
|
184
|
+
- **Tags.** A node's `tags` map an axis to a list — its assignees, its
|
|
185
|
+
labels — read by a filter axis declared `reads: 'tags'`: a node passes it
|
|
186
|
+
when any of its tags does, and each tag's chip counts the node once. A tag
|
|
187
|
+
axis only filters — push, pull, a layout axis and colour never key on it —
|
|
188
|
+
and `validateGraph` warns of tags on any other axis. `valuesOn` and
|
|
189
|
+
`countsOn` read a tag axis when handed its descriptor
|
|
190
|
+
(`taxonomy.axisFor(id)`).
|
|
183
191
|
- **Adornments.** A node may carry `badges` (short texts drawn as chips above
|
|
184
192
|
it) and a `ring` (`dashed`, for a node set apart).
|
|
185
193
|
- **Identity.** Keep one `graph` object until something in it changes: a new
|
|
@@ -295,15 +303,14 @@ the console. Everything else is optional.
|
|
|
295
303
|
refused; with `classifyEdge` given, its answer stands. Derived containment
|
|
296
304
|
edges are `in:<id>` (numbered on a collision) and reach `highlight` and
|
|
297
305
|
`collapse`. An error diagnostic drops the edge, or the later node of a
|
|
298
|
-
duplicated id, or clears the `parentId`, it names. The
|
|
299
|
-
|
|
300
|
-
|
|
306
|
+
duplicated id, or clears the `parentId`, it names. The pane's theme
|
|
307
|
+
observer watches every `data-theme` in the document unless `systemTheme` is
|
|
308
|
+
given.
|
|
301
309
|
- **Keyboard.** `keyboard` scopes the shortcuts: `pane` (the default — the
|
|
302
310
|
focusable root handles the keys that reach it, so a page keeps Tab and
|
|
303
311
|
Space for its own controls), `global` (the window — a page that is the
|
|
304
|
-
pane, or a tab shell's active tab) or `none` (a hidden tab).
|
|
305
|
-
|
|
306
|
-
opens search (while the search is offered), Space pauses, `?` lists them.
|
|
312
|
+
pane, or a tab shell's active tab) or `none` (a hidden tab). The keys are
|
|
313
|
+
[SPEC § Keyboard](SPEC.md#keyboard).
|
|
307
314
|
|
|
308
315
|
## Two views sharing a bar
|
|
309
316
|
|
|
@@ -342,9 +349,8 @@ last `linked.focus` asked of it with `onFocusResult` (a `FilterBar` ignores
|
|
|
342
349
|
those two); it throws for a view the hook was not given (`views` is read
|
|
343
350
|
once). `linked.active` is the view shown.
|
|
344
351
|
`linked.switchTo(to, transition?)` switches from the active view and makes
|
|
345
|
-
`to` active
|
|
346
|
-
|
|
347
|
-
target. `linked.focus(view, node)` is the focus switch and the request in one.
|
|
352
|
+
`to` active, reconciling as [SPEC § Linked views](SPEC.md#linked-views) says
|
|
353
|
+
(a manual switch is the default). `linked.focus(view, node)` is the focus switch and the request in one.
|
|
348
354
|
`linked.updateFilter(view, fn)` rewrites one view's filter and
|
|
349
355
|
`linked.updateFilters(fn)` every view's (a stale value pruned when the data
|
|
350
356
|
changes — `reseedFilter` is the rule the hook itself applies to every view
|
|
@@ -353,17 +359,19 @@ when `graph` changes, on the axes the user has not touched); `linked.state`,
|
|
|
353
359
|
for a host that persists them. The bar carries its own theme and scheme
|
|
354
360
|
(`theme`, default `system`; `systemTheme` says what that means), so it needs
|
|
355
361
|
no wrapper; give it the `graph` (or the `values` per axis — `valuesOn(records,
|
|
356
|
-
axis)` reads them off any subjects carrying `values
|
|
362
|
+
axis)` reads them off any subjects carrying `values`, or their `tags` given the
|
|
363
|
+
axis's descriptor) and, if the host reads
|
|
357
364
|
its own data, the `counts`. The lock toggles appear only when `locks` is
|
|
358
365
|
controlled with its callback — `useLinkedViews` does — or `chrome.locks` asks.
|
|
359
366
|
The bar tracks the values that just turned on for its chip flash
|
|
360
367
|
(`RecentlyChanged`, a set per axis); a host with its own rule passes
|
|
361
|
-
`recentlyChanged`.
|
|
368
|
+
`recentlyChanged`. `refreshFlash` flashes the bar each time the host bumps
|
|
369
|
+
it.
|
|
362
370
|
|
|
363
371
|
What the pane computes for its own bar is exported for the second view:
|
|
364
372
|
`passesSelection({ filter, taxonomy, axes, node })` decides visibility on the
|
|
365
373
|
axes the view shows (`valueOn(node, axis)` for a record that carries its
|
|
366
|
-
values under other names), `countsOn(nodes, axes)` the badges,
|
|
374
|
+
values or tags under other names), `countsOn(nodes, axes)` the badges,
|
|
367
375
|
`hiddenMatchesOf({ nodes, filter, taxonomy, axes, visibleIds, matches })` the
|
|
368
376
|
search's hidden-match badges. `reconcileFilter({ destination, source, locks,
|
|
369
377
|
transition, taxonomy, axes, graph })` is the switch the hook makes; `axes`
|
|
@@ -381,14 +389,17 @@ The pane resolves its theme from its own choice (Auto / Light / Dark) or, for
|
|
|
381
389
|
`system`, from `systemTheme` when the host gives it, else the nearest
|
|
382
390
|
ancestor's `data-theme` and then the OS — `useHostTheme(ref)` is that same
|
|
383
391
|
read, for a host's own chrome, and `themeChoiceOf(value)` reads a stored
|
|
384
|
-
choice back (`system` for anything else).
|
|
392
|
+
choice back (`system` for anything else). A server render knows neither
|
|
393
|
+
ancestors nor OS, so a server-rendered pane paints light until it hydrates; a
|
|
394
|
+
host that wants no flash resolves the theme on the server (a cookie, say) and
|
|
395
|
+
passes `systemTheme`. Every
|
|
385
396
|
token the components read is a `--gv-*` custom property declared in the
|
|
386
397
|
stylesheet per theme (`--gv-color-bg`, `--gv-color-text`, `--gv-accent`,
|
|
387
398
|
`--gv-font-sans`, …), and every class is `gv-*`; a host overrides a token by
|
|
388
399
|
redeclaring it on an ancestor. Each style value's fill and border are
|
|
389
400
|
published on the pane under the name `taxonomy.colorVar(value)` answers —
|
|
390
|
-
`--gv-node-` and the value id in kebab case (
|
|
391
|
-
`--gv-node-
|
|
401
|
+
`--gv-node-` and the value id in kebab case (a value id such as
|
|
402
|
+
`teachingLab` → `--gv-node-teaching-lab`), or a numbered name when the id kebab-cases to
|
|
392
403
|
nothing or to another value's — and `-border` for the border. A host's own
|
|
393
404
|
chrome beside the pane reads the same tints through `schemeVars(taxonomy,
|
|
394
405
|
theme)` inline, or `schemeStylesheet(taxonomy, darkSelector?)` mounted once
|
|
@@ -410,15 +421,46 @@ draw in lanes.
|
|
|
410
421
|
## Limits
|
|
411
422
|
|
|
412
423
|
The charge force is O(n²) over the visible nodes: a few hundred draw
|
|
413
|
-
smoothly, a few thousand do not
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
([#
|
|
421
|
-
|
|
422
|
-
|
|
424
|
+
smoothly, a few thousand do not (Barnes-Hut,
|
|
425
|
+
[#30](https://github.com/jmbarzee/force-graph/issues/30)). The push/pull and
|
|
426
|
+
layout controls bound their tokens by a ceiling
|
|
427
|
+
([SPEC § The control panel](SPEC.md#the-control-panel)).
|
|
428
|
+
What is not built is filed as issues:
|
|
429
|
+
|
|
430
|
+
- **The graph and taxonomy** — colour by another axis
|
|
431
|
+
([#9](https://github.com/jmbarzee/force-graph/issues/9)), an opaque
|
|
432
|
+
`Taxonomy` ([#10](https://github.com/jmbarzee/force-graph/issues/10)),
|
|
433
|
+
undirected edges ([#11](https://github.com/jmbarzee/force-graph/issues/11)),
|
|
434
|
+
per-edge weight ([#12](https://github.com/jmbarzee/force-graph/issues/12)),
|
|
435
|
+
an edge time series played back
|
|
436
|
+
([#33](https://github.com/jmbarzee/force-graph/issues/33)), host-declared
|
|
437
|
+
pair-axis mappings ([#32](https://github.com/jmbarzee/force-graph/issues/32)),
|
|
438
|
+
a node drawn as a stack of ghosts
|
|
439
|
+
([#19](https://github.com/jmbarzee/force-graph/issues/19)).
|
|
440
|
+
- **Groups** — structural colouring, a metric channel and a cohesion pull
|
|
441
|
+
([#27](https://github.com/jmbarzee/force-graph/issues/27)); hiding through
|
|
442
|
+
the legend, one emphasis path, colours validated on arrival
|
|
443
|
+
([#17](https://github.com/jmbarzee/force-graph/issues/17)).
|
|
444
|
+
- **Interaction** — selecting several nodes
|
|
445
|
+
([#28](https://github.com/jmbarzee/force-graph/issues/28)), edge hover and
|
|
446
|
+
its outputs ([#16](https://github.com/jmbarzee/force-graph/issues/16)), a
|
|
447
|
+
focus that frames the neighbourhood
|
|
448
|
+
([#21](https://github.com/jmbarzee/force-graph/issues/21)), a neighbourhood
|
|
449
|
+
frame that leaves hidden neighbours out
|
|
450
|
+
([#26](https://github.com/jmbarzee/force-graph/issues/26)), the keyboard and
|
|
451
|
+
accessibility model ([#7](https://github.com/jmbarzee/force-graph/issues/7)),
|
|
452
|
+
positions, viewport and selection kept across a live reload
|
|
453
|
+
([#5](https://github.com/jmbarzee/force-graph/issues/5)), animated reveals
|
|
454
|
+
([#8](https://github.com/jmbarzee/force-graph/issues/8)), an empty state
|
|
455
|
+
when the filter hides every node
|
|
456
|
+
([#6](https://github.com/jmbarzee/force-graph/issues/6)).
|
|
457
|
+
- **The control panel** — named presets
|
|
458
|
+
([#29](https://github.com/jmbarzee/force-graph/issues/29)), grouping a ladder
|
|
459
|
+
of values and the Misc tab's read-outs
|
|
460
|
+
([#31](https://github.com/jmbarzee/force-graph/issues/31)), a force tab with
|
|
461
|
+
nothing visible ([#4](https://github.com/jmbarzee/force-graph/issues/4)).
|
|
462
|
+
- **The package** — the pane's strings in other languages
|
|
463
|
+
([#13](https://github.com/jmbarzee/force-graph/issues/13)), the React 19
|
|
464
|
+
types gate ([#14](https://github.com/jmbarzee/force-graph/issues/14)).
|
|
423
465
|
|
|
424
466
|
`src/index.ts` names the whole surface.
|
|
@@ -80,6 +80,18 @@ interface FilterBarBaseProps {
|
|
|
80
80
|
/** Flash the whole bar once whenever this value changes to something truthy — a counter a host bumps when its data reloads. */
|
|
81
81
|
refreshFlash?: boolean | number;
|
|
82
82
|
}
|
|
83
|
+
/** The first line of a row's items, laid out at these offsets from its top: its height, and how many items wrap past it. */
|
|
84
|
+
export declare function firstLine(items: readonly {
|
|
85
|
+
top: number;
|
|
86
|
+
bottom: number;
|
|
87
|
+
}[]): {
|
|
88
|
+
height: number;
|
|
89
|
+
rest: number;
|
|
90
|
+
};
|
|
91
|
+
/** The axes a change of filter opens: those whose selection changed, but for the ones it leaves as they are. */
|
|
92
|
+
export declare function axesOpenedBy(prev: FilterState, next: FilterState, leftAsIs: readonly AxisId[]): AxisId[];
|
|
93
|
+
/** The axes a filter re-seeded: their presented values changed between two readings, and the filter selects their seed. */
|
|
94
|
+
export declare function axesReseeded(taxonomy: Taxonomy, prev: ReadonlyMap<string, readonly string[]>, next: ReadonlyMap<string, readonly string[]>, filter: FilterState): AxisId[];
|
|
83
95
|
/**
|
|
84
96
|
* The shared filter bar: a section of chips per axis shown with its lock and
|
|
85
97
|
* remove controls, the add-axis control, the search control, and the host's
|
|
@@ -18,9 +18,12 @@ export declare function themeOfMarker(marker: ThemeMarker | null): Theme | undef
|
|
|
18
18
|
export declare function hostTheme(el: Element | null): Theme;
|
|
19
19
|
/**
|
|
20
20
|
* The host's theme for the element at `ref`, re-read when any ancestor's
|
|
21
|
-
* `data-theme` changes and when the OS preference changes.
|
|
22
|
-
*
|
|
21
|
+
* `data-theme` changes and when the OS preference changes. Light until read,
|
|
22
|
+
* the server's answer, so a hydrating render matches it; with `enabled`
|
|
23
|
+
* false nothing is read or observed, and the answer stays light.
|
|
23
24
|
*/
|
|
24
25
|
export declare function useHostTheme(ref: React.RefObject<HTMLElement | null>, enabled?: boolean): Theme;
|
|
25
|
-
/** The
|
|
26
|
+
/** The pane's resolved theme, undefined until the host is read. */
|
|
27
|
+
export declare function useResolvedTheme(ref: React.RefObject<HTMLElement | null>, choice: ThemeChoice, systemTheme?: Theme): Theme | undefined;
|
|
28
|
+
/** The theme the pane rooted at `ref` draws in: the resolved theme, light until read. */
|
|
26
29
|
export declare function useTheme(ref: React.RefObject<HTMLElement | null>, choice: ThemeChoice, systemTheme?: Theme): Theme;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AxisId,
|
|
1
|
+
import { AxisId, AxisValue, ResolvedAxisDescriptor } from '../graph/axis';
|
|
2
2
|
import { Graph } from '../graph/model';
|
|
3
3
|
import { Taxonomy } from '../graph/taxonomy';
|
|
4
4
|
/** The chosen values on one axis. Empty means what the axis's descriptor says. */
|
|
@@ -45,13 +45,14 @@ export declare function withLock(locks: AxisLocks, axis: AxisId, locked: boolean
|
|
|
45
45
|
/** Whether an axis is locked against adoption. */
|
|
46
46
|
export declare function lockedFor(locks: AxisLocks, axis: AxisId): boolean;
|
|
47
47
|
/**
|
|
48
|
-
* Where a focus transition wants to land: the target's value per axis
|
|
49
|
-
* axis the target says nothing about is absent,
|
|
50
|
-
* as "no expansion needed". A host that also needs
|
|
51
|
-
* view of its own carries it in a type of its own.
|
|
48
|
+
* Where a focus transition wants to land: the target's value per axis, and
|
|
49
|
+
* its tags per tag axis. An axis the target says nothing about is absent,
|
|
50
|
+
* which the reconciler reads as "no expansion needed". A host that also needs
|
|
51
|
+
* the target's name for a view of its own carries it in a type of its own.
|
|
52
52
|
*/
|
|
53
53
|
export type FocusTarget = {
|
|
54
54
|
values: Readonly<Record<AxisId, string | undefined>>;
|
|
55
|
+
tags?: Readonly<Record<AxisId, readonly string[] | undefined>>;
|
|
55
56
|
};
|
|
56
57
|
/** How a view is being switched to: by the user (`manual`: adopt the source's selections) or to show a node (`focus`: widen for its values). */
|
|
57
58
|
export type ViewTransition = {
|
|
@@ -75,6 +76,8 @@ export type ReconcileResult = {
|
|
|
75
76
|
* choice.
|
|
76
77
|
*/
|
|
77
78
|
export declare function initialFilter(taxonomy: Pick<Taxonomy, 'filterAxes'>, graph: Pick<Graph, 'nodes'>): FilterState;
|
|
79
|
+
/** One axis's opening selection on the values the graph presents there: `initialFilter`'s, axis by axis. */
|
|
80
|
+
export declare function seedSelection(taxonomy: Pick<Taxonomy, 'filterAxes'>, d: ResolvedAxisDescriptor, values: readonly string[]): AxisSelection;
|
|
78
81
|
/**
|
|
79
82
|
* A filter re-seeded for a graph that changed: every filter axis the user has
|
|
80
83
|
* not touched takes its opening selection on `graph` (`initialFilter`'s), so
|
|
@@ -113,8 +116,8 @@ export interface PassesSelectionInput<N> {
|
|
|
113
116
|
axes: readonly AxisId[];
|
|
114
117
|
/** The record judged: a graph node, or any record of the host's. */
|
|
115
118
|
node: N;
|
|
116
|
-
/** The record's value on an axis, for a record that carries
|
|
117
|
-
valueOn?: (node: N, axis: AxisId) =>
|
|
119
|
+
/** The record's value on an axis — its tags, on a tag axis — for a record that carries them under other names. Default: its `values` and `tags`, when it has them. */
|
|
120
|
+
valueOn?: (node: N, axis: AxisId) => AxisValue | readonly AxisValue[] | undefined;
|
|
118
121
|
}
|
|
119
122
|
/**
|
|
120
123
|
* Does a record pass the selection on the axes a view shows? The policy —
|
|
@@ -124,20 +127,19 @@ export interface PassesSelectionInput<N> {
|
|
|
124
127
|
*/
|
|
125
128
|
export declare function passesSelection<N>({ filter, taxonomy, axes, node, valueOn }: PassesSelectionInput<N>): boolean;
|
|
126
129
|
/** `passesSelection` over descriptors already resolved — the view's hot path, which resolves them once per render. */
|
|
127
|
-
export declare function passesDescriptors(filter: FilterState, axes: readonly ResolvedAxisDescriptor[],
|
|
130
|
+
export declare function passesDescriptors(filter: FilterState, axes: readonly ResolvedAxisDescriptor[], valuesOf: (axis: ResolvedAxisDescriptor) => readonly AxisValue[]): boolean;
|
|
128
131
|
/** Search matches split by the filter: the visible ones, and the hidden ones tallied on the chip hiding each. */
|
|
129
132
|
export interface HiddenMatches {
|
|
130
133
|
/** The ids of the matching nodes the filter shows. */
|
|
131
134
|
visible: ReadonlySet<string>;
|
|
132
135
|
/** How many matching nodes the filter hides. */
|
|
133
136
|
hiddenCount: number;
|
|
134
|
-
/** The hidden matches per axis and value — on the first of the axes shown that each node fails, under its value there. */
|
|
137
|
+
/** The hidden matches per axis and value — on the first of the axes shown that each node fails, under its value there (on a tag axis, its first tag whose chip is off). */
|
|
135
138
|
hiddenByAxis: ReadonlyMap<AxisId, ReadonlyMap<string, number>>;
|
|
136
139
|
}
|
|
137
140
|
/** What `hiddenMatchesOf` reads. */
|
|
138
141
|
export interface HiddenMatchesInput<N extends {
|
|
139
142
|
id: string;
|
|
140
|
-
values?: AxisValues;
|
|
141
143
|
}> {
|
|
142
144
|
/** Every node the search ran over, visible or not. */
|
|
143
145
|
nodes: Iterable<N>;
|
|
@@ -147,16 +149,16 @@ export interface HiddenMatchesInput<N extends {
|
|
|
147
149
|
axes: readonly AxisId[];
|
|
148
150
|
visibleIds: ReadonlySet<string>;
|
|
149
151
|
matches: (node: N) => boolean;
|
|
150
|
-
/** A node's value on an axis. Default: its `values`. */
|
|
151
|
-
valueOn?: (node: N, axis: AxisId) =>
|
|
152
|
+
/** A node's value on an axis — its tags, on a tag axis. Default: its `values` and `tags`. */
|
|
153
|
+
valueOn?: (node: N, axis: AxisId) => AxisValue | readonly AxisValue[] | undefined;
|
|
152
154
|
}
|
|
153
155
|
/**
|
|
154
156
|
* Split a search's matches by the filter, the way the pane does under its
|
|
155
157
|
* own bar: a match the filter shows lights up, a match it hides is counted
|
|
156
158
|
* on the chip hiding it — the first axis in `axes` the node fails, under the
|
|
157
|
-
* node's value there — so a host's second
|
|
159
|
+
* node's value there, or its first tag whose chip is off — so a host's second
|
|
160
|
+
* bar can wear the same badges.
|
|
158
161
|
*/
|
|
159
162
|
export declare function hiddenMatchesOf<N extends {
|
|
160
163
|
id: string;
|
|
161
|
-
values?: AxisValues;
|
|
162
164
|
}>({ nodes, filter, taxonomy, axes, visibleIds, matches, valueOn }: HiddenMatchesInput<N>): HiddenMatches;
|
package/dist-lib/graph/axis.d.ts
CHANGED
|
@@ -7,6 +7,17 @@ export type AxisId = string;
|
|
|
7
7
|
export type AxisValue = string;
|
|
8
8
|
/** Where a node sits on every axis it has a value for. */
|
|
9
9
|
export type AxisValues = Readonly<Record<AxisId, AxisValue>>;
|
|
10
|
+
/** The tags a node carries, on each tag axis it has any on. */
|
|
11
|
+
export type AxisTags = Readonly<Record<AxisId, readonly AxisValue[]>>;
|
|
12
|
+
/** How an axis is read off a node: its one value, or its tags. */
|
|
13
|
+
export type AxisReading = Pick<ResolvedAxisDescriptor, 'id' | 'reads'>;
|
|
14
|
+
/** What a node, a focus target or a host's record carries: its values and its tags, either possibly absent. */
|
|
15
|
+
export interface AxisSubject {
|
|
16
|
+
values?: Readonly<Record<AxisId, AxisValue | undefined>>;
|
|
17
|
+
tags?: Readonly<Record<AxisId, readonly AxisValue[] | undefined>>;
|
|
18
|
+
}
|
|
19
|
+
/** What a subject carries on an axis: its tags on a tag axis, else its one value; none when it carries nothing there. */
|
|
20
|
+
export declare function readAxis(subject: AxisSubject, axis: AxisReading): readonly AxisValue[];
|
|
10
21
|
/**
|
|
11
22
|
* Fold anything into a stable value key.
|
|
12
23
|
*
|
|
@@ -136,6 +147,13 @@ export interface AxisDescriptor {
|
|
|
136
147
|
* `emptyMeans: 'all'` axis.
|
|
137
148
|
*/
|
|
138
149
|
presentation?: 'chip' | 'tag';
|
|
150
|
+
/**
|
|
151
|
+
* What a node carries on this axis: its one value in `values` (the
|
|
152
|
+
* default), or a list in `tags` — its assignees, its labels. A node passes
|
|
153
|
+
* a tag axis when any of its tags does. A tag axis only filters: push,
|
|
154
|
+
* pull, a layout axis and colour never key on it.
|
|
155
|
+
*/
|
|
156
|
+
reads?: 'values' | 'tags';
|
|
139
157
|
}
|
|
140
158
|
/** A filter axis with every policy filled in — what `Taxonomy.filterAxes` carries. */
|
|
141
159
|
export interface ResolvedAxisDescriptor extends AxisNamer {
|
|
@@ -151,6 +169,7 @@ export interface ResolvedAxisDescriptor extends AxisNamer {
|
|
|
151
169
|
chipsFor(values: readonly AxisValue[]): readonly FilterChip[];
|
|
152
170
|
initialSelection(values: readonly AxisValue[]): readonly AxisValue[];
|
|
153
171
|
presentation: 'chip' | 'tag';
|
|
172
|
+
reads: 'values' | 'tags';
|
|
154
173
|
}
|
|
155
174
|
/** How the style axis names and adorns its values, for a descriptor's defaults. */
|
|
156
175
|
export interface StyleNamer {
|
|
@@ -231,23 +250,22 @@ export declare function isIdentityMapping(mapping: AxisMapping): boolean;
|
|
|
231
250
|
* The distinct values a set of subjects carries on one axis, in first-encounter
|
|
232
251
|
* order. The domain of an axis is whatever the graph presents on it — the
|
|
233
252
|
* engine declares no value vocabulary of its own — so this is how a control
|
|
234
|
-
* surface or a filter learns what an axis can take.
|
|
253
|
+
* surface or a filter learns what an axis can take. An axis given by id reads
|
|
254
|
+
* values; given by its descriptor, a tag axis reads tags.
|
|
235
255
|
*/
|
|
236
|
-
export declare function valuesOn(subjects: Iterable<
|
|
237
|
-
values: AxisValues;
|
|
238
|
-
}>, axis: AxisId): AxisValue[];
|
|
256
|
+
export declare function valuesOn(subjects: Iterable<AxisSubject>, axis: AxisId | AxisReading): AxisValue[];
|
|
239
257
|
/**
|
|
240
258
|
* How many entities carry each value on each of the given axes — the chip
|
|
241
259
|
* badges' numbers when a host does not supply its own reading. The copies of
|
|
242
|
-
* one entity (`copyKey`) count once
|
|
243
|
-
*
|
|
244
|
-
*
|
|
260
|
+
* one entity (`copyKey`) count once, and so does each of an entity's tags; a
|
|
261
|
+
* subject with neither key nor id counts as itself. An axis is read as
|
|
262
|
+
* `valuesOn` reads it, and one no subject carries anything on gets an empty
|
|
263
|
+
* map, never a missing one.
|
|
245
264
|
*/
|
|
246
|
-
export declare function countsOn(subjects: Iterable<{
|
|
247
|
-
values: AxisValues;
|
|
265
|
+
export declare function countsOn(subjects: Iterable<AxisSubject & {
|
|
248
266
|
id?: string;
|
|
249
267
|
copyKey?: string;
|
|
250
|
-
}>, axes: readonly AxisId[]): ReadonlyMap<AxisId, ReadonlyMap<AxisValue, number>>;
|
|
268
|
+
}>, axes: readonly (AxisId | AxisReading)[]): ReadonlyMap<AxisId, ReadonlyMap<AxisValue, number>>;
|
|
251
269
|
/**
|
|
252
270
|
* Does this table declare `key` itself, rather than inheriting it?
|
|
253
271
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AxisValues, Relation } from './axis';
|
|
2
|
-
/** A node of the host's graph: an opaque id, a name, its values on the host's axes, and an optional parent, copy key, ring, badges and payload. */
|
|
1
|
+
import { AxisTags, AxisValues, Relation } from './axis';
|
|
2
|
+
/** A node of the host's graph: an opaque id, a name, its values on the host's axes, and an optional parent, copy key, ring, badges, tags and payload. */
|
|
3
3
|
export interface GraphNode {
|
|
4
4
|
/**
|
|
5
5
|
* Stable identity, owned by whoever produced the graph.
|
|
@@ -16,6 +16,11 @@ export interface GraphNode {
|
|
|
16
16
|
* one axis each and read it from here; nothing composes across axes.
|
|
17
17
|
*/
|
|
18
18
|
values: AxisValues;
|
|
19
|
+
/**
|
|
20
|
+
* Several values on one axis — its assignees, its labels — on each axis
|
|
21
|
+
* the taxonomy declares to read tags. Read by the filter only.
|
|
22
|
+
*/
|
|
23
|
+
tags?: AxisTags;
|
|
19
24
|
name: string;
|
|
20
25
|
/**
|
|
21
26
|
* Metadata the host owns and the library does not interpret.
|
|
@@ -29,10 +29,10 @@ export type ContextLine = ReactNode | readonly ReactNode[];
|
|
|
29
29
|
* policy and host hook through. Built once by `createTaxonomy`; derive a
|
|
30
30
|
* variant from the spec, never by spreading this object.
|
|
31
31
|
*
|
|
32
|
-
* What a host reads: `filterAxes
|
|
33
|
-
*
|
|
34
|
-
* chrome of its own in the scheme's colours; `edgeTypes` for a
|
|
35
|
-
* `searchText` to match as the view matches. The rest is the engine's
|
|
32
|
+
* What a host reads: `filterAxes` and `axisFor` to filter its own records the
|
|
33
|
+
* way the view does, through `passesSelection`; `styleOf`, `styleValues` and
|
|
34
|
+
* `colorVar` for chrome of its own in the scheme's colours; `edgeTypes` for a
|
|
35
|
+
* legend; `searchText` to match as the view matches. The rest is the engine's
|
|
36
36
|
* interface — resolution for the draw loop — and a host has no call to read
|
|
37
37
|
* it.
|
|
38
38
|
*/
|
|
@@ -63,13 +63,6 @@ export interface Taxonomy {
|
|
|
63
63
|
readonly filterAxes: readonly ResolvedAxisDescriptor[];
|
|
64
64
|
/** One axis's policies, or undefined if the host does not declare it. */
|
|
65
65
|
axisFor(axis: AxisId): ResolvedAxisDescriptor | undefined;
|
|
66
|
-
/**
|
|
67
|
-
* The value a subject presents on one axis — read off its values, for
|
|
68
|
-
* every axis alike. An axis that is a projection of another is materialized
|
|
69
|
-
* by the host when it builds the node, so nothing here knows which axes are
|
|
70
|
-
* derived.
|
|
71
|
-
*/
|
|
72
|
-
valueOn(subject: StyleSubject, axis: AxisId): AxisValue | undefined;
|
|
73
66
|
/** Every style value, in declaration order (top of the hierarchy first). */
|
|
74
67
|
readonly styleValues: readonly AxisValue[];
|
|
75
68
|
/** Every edge type, in control-panel order, its defaults filled in. */
|
|
@@ -205,3 +198,5 @@ export interface TaxonomySpec {
|
|
|
205
198
|
* fields.
|
|
206
199
|
*/
|
|
207
200
|
export declare function createTaxonomy(spec: TaxonomySpec): Taxonomy;
|
|
201
|
+
/** The axes a taxonomy declares to read tags, which only filter. */
|
|
202
|
+
export declare function tagAxesOf(taxonomy: Pick<Taxonomy, 'filterAxes'>): AxisId[];
|
|
@@ -5,11 +5,12 @@ import { Taxonomy } from './taxonomy';
|
|
|
5
5
|
* an edge whose endpoint is not in the graph, an unknown relation, a type
|
|
6
6
|
* whose relation is not the edge's, a rule that threw, a parent not in the
|
|
7
7
|
* graph. Warnings: an edge no type claims, a style value the taxonomy does
|
|
8
|
-
* not declare,
|
|
8
|
+
* not declare, tags on an axis the taxonomy does not declare to read them,
|
|
9
|
+
* containment edges that contradict `parentId`, derived
|
|
9
10
|
* containment edges no type claims (`containment-untyped`, from
|
|
10
11
|
* `derivedContainmentDiagnostics`).
|
|
11
12
|
*/
|
|
12
|
-
export type GraphDiagnosticCode = 'edge-id-duplicate' | 'edge-endpoint-missing' | 'edge-relation-unknown' | 'edge-type-rule-threw' | 'edge-type-unclassified' | 'edge-type-relation-mismatch' | 'parent-missing' | 'containment-parent-mismatch' | 'node-value-undeclared' | 'node-id-duplicate' | 'containment-untyped';
|
|
13
|
+
export type GraphDiagnosticCode = 'edge-id-duplicate' | 'edge-endpoint-missing' | 'edge-relation-unknown' | 'edge-type-rule-threw' | 'edge-type-unclassified' | 'edge-type-relation-mismatch' | 'parent-missing' | 'containment-parent-mismatch' | 'node-value-undeclared' | 'node-tags-undeclared' | 'node-id-duplicate' | 'containment-untyped';
|
|
13
14
|
/** One defect in a host's graph, as data: its severity, its code, a message, and the node or edge it names. */
|
|
14
15
|
export interface GraphDiagnostic {
|
|
15
16
|
/** An error names something the view cannot draw as given; a warning, something it draws but the host should know. */
|
|
@@ -24,9 +25,9 @@ export interface GraphDiagnostic {
|
|
|
24
25
|
* draw as given (a duplicated node id, a dangling endpoint, an unknown
|
|
25
26
|
* relation, a type whose relation is not the edge's, a parent not in the
|
|
26
27
|
* graph) and warnings for what it draws but the host should know (an
|
|
27
|
-
* unclassified edge, an undeclared style value,
|
|
28
|
-
* contradict `parentId`). Pure; the same graph and
|
|
29
|
-
* list, in graph order.
|
|
28
|
+
* unclassified edge, an undeclared style value, tags no axis reads,
|
|
29
|
+
* containment edges that contradict `parentId`). Pure; the same graph and
|
|
30
|
+
* taxonomy give the same list, in graph order.
|
|
30
31
|
*/
|
|
31
32
|
export declare function validateGraph(graph: Graph, taxonomy: Taxonomy): GraphDiagnostic[];
|
|
32
33
|
/**
|
package/dist-lib/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export { toggleGroup, toggleValue } from './filter/toggle';
|
|
|
18
18
|
export { initialFilter, filterFromJSON, filterStatesEqual, filterToJSON, hiddenMatchesOf, locksFromJSON, locksToJSON, matchesFocus, passesSelection, reseedFilter, lockedFor, selectionFor, withLock, withSelection } from './filter/types';
|
|
19
19
|
export type { FilterState, FocusTarget, HiddenMatches, HiddenMatchesInput, AxisLocks, PassesSelectionInput, ReconcileResult, AxisSelection, ViewTransition } from './filter/types';
|
|
20
20
|
export { countsOn, valuesOn } from './graph/axis';
|
|
21
|
-
export type { AxisDescriptor, AxisId, AxisNamer, AxisValues, AxisValue, FilterChip, Relation, ResolvedAxisDescriptor } from './graph/axis';
|
|
21
|
+
export type { AxisDescriptor, AxisId, AxisNamer, AxisTags, AxisValues, AxisValue, FilterChip, Relation, ResolvedAxisDescriptor } from './graph/axis';
|
|
22
22
|
export type { ActiveGroup, NodeGroup } from './graph/groups';
|
|
23
23
|
export { copyGroups, isContainment, nodesById, payloadNumber, payloadString } from './graph/model';
|
|
24
24
|
export type { Graph, GraphEdge, GraphNode, ReadableGraph } from './graph/model';
|