@liquidcars/atlas-layout 0.1.7 → 0.1.9
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 +22 -0
- package/package.json +1 -1
- package/spec/ATLAS-LAYOUT-SPEC-v1.md +10 -2
- package/src/index.js +76 -0
- package/src/spec.js +22 -1
package/README.md
CHANGED
|
@@ -49,6 +49,28 @@ como alias de compatibilidad. Un contenedor puede no tener geometría o usar una
|
|
|
49
49
|
primitiva `geo.*`; las geometrías personalizadas de los packs, como `infra.cloud`,
|
|
50
50
|
sólo se permiten en entidades hoja.
|
|
51
51
|
|
|
52
|
+
La presentación puede conservar una vista inicial sin introducir datos de
|
|
53
|
+
cámara en el algoritmo de layout:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
render:
|
|
57
|
+
camera:
|
|
58
|
+
position: [12.4, 7.2, 18.6]
|
|
59
|
+
target: [0, 1.5, 0]
|
|
60
|
+
zoom: 1.35
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Filtered projections
|
|
64
|
+
|
|
65
|
+
`compileAtlasProjection(source, visibleIds, options)` compiles a temporary model
|
|
66
|
+
containing only the requested entities and their required ancestors. Relations,
|
|
67
|
+
constraints and virtual layout groups are pruned consistently. The source object is
|
|
68
|
+
never mutated, making the helper suitable for selection-driven `render.focus` reflow.
|
|
69
|
+
|
|
70
|
+
Las coordenadas son coordenadas del modelo. `zoom` es un multiplicador de
|
|
71
|
+
encuadre independiente del tamaño del viewport y se conserva al compilar YAML
|
|
72
|
+
o Markdown.
|
|
73
|
+
|
|
52
74
|
Para compilar una fuente YAML durante el build:
|
|
53
75
|
|
|
54
76
|
```js
|
package/package.json
CHANGED
|
@@ -150,8 +150,16 @@ Optional fields include id, label, fromAnchor, toAnchor, priority and layout.
|
|
|
150
150
|
## Rendering
|
|
151
151
|
|
|
152
152
|
palette maps tokens to CSS colours. style.color or c can reference a token or direct
|
|
153
|
-
CSS colour. render can additionally set relationMode, selectionMode, toolbar
|
|
154
|
-
shellLabels
|
|
153
|
+
CSS colour. render can additionally set relationMode, selectionMode, toolbar,
|
|
154
|
+
shellLabels (the text-label contrast outline), focus and camera. `render.focus`
|
|
155
|
+
accepts `mode: all|connected`, `effect: dim|hide`, `layout: preserve|reflow`
|
|
156
|
+
and an optional dim `opacity`. Connected focus retains the selection, its directly
|
|
157
|
+
related entities and the ancestor containers required to preserve context. Preserve
|
|
158
|
+
keeps spatial memory; reflow compiles a temporary projection and never mutates the
|
|
159
|
+
source model. An initial camera snapshot contains model-space
|
|
160
|
+
position and target vectors plus a positive, dimensionless zoom value. Position
|
|
161
|
+
and target determine the viewing direction; zoom preserves the intended framing
|
|
162
|
+
across viewport sizes. theme contains background, lighting and ui settings.
|
|
155
163
|
|
|
156
164
|
## Markdown blocks
|
|
157
165
|
|
package/src/index.js
CHANGED
|
@@ -964,6 +964,81 @@ function normalizeRoot(input) {
|
|
|
964
964
|
return input || {};
|
|
965
965
|
}
|
|
966
966
|
|
|
967
|
+
function projectedAtlasSource(input, visibleIds) {
|
|
968
|
+
const source = structuredClone(normalizeRoot(input));
|
|
969
|
+
const requested = new Set(Array.from(visibleIds || []).filter(Boolean));
|
|
970
|
+
const records = new Map();
|
|
971
|
+
|
|
972
|
+
function collect(items, nestedParent = null) {
|
|
973
|
+
for (const entity of Array.isArray(items) ? items : []) {
|
|
974
|
+
if (!entity?.id) continue;
|
|
975
|
+
const parent = nestedParent ?? entity.parent ?? null;
|
|
976
|
+
records.set(entity.id, { entity, parent });
|
|
977
|
+
collect(entity.children, entity.id);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
collect(source.entities);
|
|
981
|
+
for (const id of [...requested]) {
|
|
982
|
+
let parent = records.get(id)?.parent || null;
|
|
983
|
+
while (parent && !requested.has(parent)) {
|
|
984
|
+
requested.add(parent);
|
|
985
|
+
parent = records.get(parent)?.parent || null;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function filterEntities(items) {
|
|
990
|
+
return (Array.isArray(items) ? items : []).flatMap(entity => {
|
|
991
|
+
if (!entity?.id || !requested.has(entity.id)) return [];
|
|
992
|
+
const copy = { ...entity };
|
|
993
|
+
if (Array.isArray(entity.children)) copy.children = filterEntities(entity.children);
|
|
994
|
+
return [copy];
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
source.entities = filterEntities(source.entities);
|
|
998
|
+
source.relations = (source.relations || []).filter(relation => requested.has(relation.from) && requested.has(relation.to));
|
|
999
|
+
|
|
1000
|
+
const childrenByParent = new Map();
|
|
1001
|
+
function index(items, nestedParent = null) {
|
|
1002
|
+
for (const entity of items || []) {
|
|
1003
|
+
const parent = nestedParent ?? entity.parent ?? null;
|
|
1004
|
+
if (!childrenByParent.has(parent)) childrenByParent.set(parent, []);
|
|
1005
|
+
childrenByParent.get(parent).push(entity.id);
|
|
1006
|
+
index(entity.children, entity.id);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
index(source.entities);
|
|
1010
|
+
|
|
1011
|
+
function pruneOwner(owner, ownerId = null) {
|
|
1012
|
+
const siblingIds = new Set(childrenByParent.get(ownerId) || []);
|
|
1013
|
+
const layout = owner.layout ? { ...owner.layout } : null;
|
|
1014
|
+
if (layout?.groups) {
|
|
1015
|
+
layout.groups = layout.groups.flatMap(group => {
|
|
1016
|
+
const members = (group.members || []).filter(id => siblingIds.has(id));
|
|
1017
|
+
return members.length ? [{ ...group, members }] : [];
|
|
1018
|
+
});
|
|
1019
|
+
if (!layout.groups.length) delete layout.groups;
|
|
1020
|
+
owner.layout = layout;
|
|
1021
|
+
}
|
|
1022
|
+
const candidates = new Set([...siblingIds, ...(layout?.groups || []).map(group => group.id)]);
|
|
1023
|
+
if (Array.isArray(owner.constraints)) {
|
|
1024
|
+
owner.constraints = owner.constraints.flatMap(constraint => {
|
|
1025
|
+
const key = Array.isArray(constraint.align) ? "align" : Array.isArray(constraint.equal) ? "equal" : null;
|
|
1026
|
+
if (!key) return [constraint];
|
|
1027
|
+
const ids = constraint[key].filter(id => candidates.has(id));
|
|
1028
|
+
return ids.length >= 2 ? [{ ...constraint, [key]: ids }] : [];
|
|
1029
|
+
});
|
|
1030
|
+
if (!owner.constraints.length) delete owner.constraints;
|
|
1031
|
+
}
|
|
1032
|
+
for (const entity of ownerId === null ? source.entities : owner.children || []) pruneOwner(entity, entity.id);
|
|
1033
|
+
}
|
|
1034
|
+
pruneOwner(source, null);
|
|
1035
|
+
return source;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
export function compileAtlasProjection(input, visibleIds, options = {}) {
|
|
1039
|
+
return compileAtlasModel(projectedAtlasSource(input, visibleIds), options);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
967
1042
|
export function compileAtlasModel(input, options = {}) {
|
|
968
1043
|
const diagnostics = [];
|
|
969
1044
|
const source = normalizeRoot(input);
|
|
@@ -1050,6 +1125,7 @@ export function compileAtlasModel(input, options = {}) {
|
|
|
1050
1125
|
relations,
|
|
1051
1126
|
layout: source.layout || undefined,
|
|
1052
1127
|
constraints: source.constraints || undefined,
|
|
1128
|
+
render: source.render || undefined,
|
|
1053
1129
|
diagnostics
|
|
1054
1130
|
};
|
|
1055
1131
|
if (options.throwOnFatal && diagnostics.some(item => item.level === "fatal")) throw new Error(diagnostics.filter(item => item.level === "fatal").map(item => item.message).join("\n"));
|
package/src/spec.js
CHANGED
|
@@ -136,7 +136,26 @@ export const ATLAS_LAYOUT_SPEC_V1 = freeze({
|
|
|
136
136
|
relationMode: field("Visible relation set.", ["all", "selected", "none"]),
|
|
137
137
|
selectionMode: field("Selection ownership.", ["internal", "event"]),
|
|
138
138
|
toolbar: field("Built-in toolbar visibility.", [true, false]),
|
|
139
|
-
shellLabels: field("
|
|
139
|
+
shellLabels: field("Text-label contrast outline.", [true, false]),
|
|
140
|
+
focus: field("Selection-driven visual filtering.", null, { context: "focus" }),
|
|
141
|
+
camera: field("Initial camera snapshot in model-space coordinates.", null, { context: "camera" })
|
|
142
|
+
})
|
|
143
|
+
}),
|
|
144
|
+
focus: freeze({
|
|
145
|
+
label: "Visual focus",
|
|
146
|
+
properties: freeze({
|
|
147
|
+
mode: field("Entity set retained by the visual filter.", ["all", "connected"]),
|
|
148
|
+
effect: field("Treatment of entities outside the focused set.", ["dim", "hide"]),
|
|
149
|
+
layout: field("Keep spatial memory or reflow the focused projection.", ["preserve", "reflow"]),
|
|
150
|
+
opacity: field("Opacity applied to dimmed entities.", null, { type: "number" })
|
|
151
|
+
})
|
|
152
|
+
}),
|
|
153
|
+
camera: freeze({
|
|
154
|
+
label: "Initial camera",
|
|
155
|
+
properties: freeze({
|
|
156
|
+
position: field("Camera position in model-space coordinates.", null, { type: "vector", required: true }),
|
|
157
|
+
target: field("Model-space point observed by the camera.", null, { type: "vector", required: true }),
|
|
158
|
+
zoom: field("Dimensionless framing multiplier.", null, { type: "number", required: true })
|
|
140
159
|
})
|
|
141
160
|
}),
|
|
142
161
|
span: freeze({
|
|
@@ -177,6 +196,8 @@ export function atlasLayoutContextForPath(path = []) {
|
|
|
177
196
|
["palette", "palette"],
|
|
178
197
|
["theme", "theme"],
|
|
179
198
|
["render", "render"],
|
|
199
|
+
["focus", "focus"],
|
|
200
|
+
["camera", "camera"],
|
|
180
201
|
["span", "span"],
|
|
181
202
|
["stagger", "stagger"],
|
|
182
203
|
["label", "label"],
|