pele-animate 0.15.0 → 0.16.1
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 +1 -1
- package/docs/diagram-types.md +229 -5
- package/docs/faq.md +1 -1
- package/docs/step-order.md +6 -0
- package/package.json +1 -1
- package/pele-animate.css +8 -3
- package/pele-animate.js +104 -8
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Unofficial add-on; not affiliated with or endorsed by the Pele or Obsidian teams
|
|
|
16
16
|
- **You set the order.** Nodes appear in the order of a `%% pa:` comment, or by default along the edges; edges draw in and labels fade in.
|
|
17
17
|
- **Readers step through it.** Click (or Enter) starts step mode: previous / play-pause / next buttons, Space, ←/→ and Esc. Hover or focus sends tokens along the edges.
|
|
18
18
|
- **Icons.** `@{ icon: "name" }` nodes use any of 4,368 [Tabler](https://github.com/tabler/tabler-icons) icons (loaded per category) or your own. Unknown names get a neutral rounded square.
|
|
19
|
-
- Flowcharts and
|
|
19
|
+
- Flowcharts, sequence diagrams, architecture diagrams, state diagrams, C4 context, block, class and ER diagrams, timelines, journeys, git graphs and Gantt charts. The finished picture is the resting state; `prefers-reduced-motion` turns the motion off. Light and dark themes.
|
|
20
20
|
- The source stays valid Mermaid. One catch: GitHub's own Mermaid renderer does not know the icon names and draws "?" boxes, so the icon examples in this repository are shown as source text.
|
|
21
21
|
|
|
22
22
|
## Install
|
package/docs/diagram-types.md
CHANGED
|
@@ -7,16 +7,38 @@ section: Start
|
|
|
7
7
|
|
|
8
8
|
# Diagram types
|
|
9
9
|
|
|
10
|
-
pele-animate
|
|
11
|
-
|
|
10
|
+
pele-animate animates these Mermaid diagram types. Each one plays an intro when it scrolls into view and, on click, steps through its
|
|
11
|
+
items with previous / play / next controls.
|
|
12
|
+
|
|
13
|
+
| Type | Each step reveals | Pins, icons | Remarks |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| [Flowchart](#flowcharts)<br>`flowchart` | Nodes level by level, edges from their source | Pins and icons | The fullest support: groups, edge labels, large icon nodes. |
|
|
16
|
+
| [Sequence diagram](#sequence-diagrams)<br>`sequenceDiagram` | Actors, then one message at a time | Neither | The order is the source order; reordering messages changes what the diagram says. |
|
|
17
|
+
| [Architecture](#architecture-diagrams)<br>`architecture-beta` | Services level by level, edges from their source | Pins and icons | Icons are written `(name)` and are small and fixed in size. |
|
|
18
|
+
| [State diagram](#state-diagrams)<br>`stateDiagram-v2` | States and transitions from the start marker | Pins | Markers are `root_start` and `root_end`. Composite states are untested. |
|
|
19
|
+
| [C4 context](#c4-context-diagrams)<br>`C4Context` | People and systems, then relationships | Pins | Only `C4Context` is checked. Keep `Rel` labels short. |
|
|
20
|
+
| [Block diagram](#block-diagrams)<br>`block-beta` | Blocks and arrows from their source | Pins | Put linked blocks in the same row or column. |
|
|
21
|
+
| [Class diagram](#class-diagrams)<br>`classDiagram` | Classes and relationships, from the class written first | Pins | `A <\|-- B` reveals `A`, then `B`. |
|
|
22
|
+
| [ER diagram](#er-diagrams)<br>`erDiagram` | Entities and relationships, from the entity written first | Pins | Same written-order rule as class diagrams. |
|
|
23
|
+
| [Timeline](#timelines-journeys-git-graphs-and-gantt-charts)<br>`timeline` | One time period with its events | Neither | Section headers stay visible. |
|
|
24
|
+
| [User journey](#timelines-journeys-git-graphs-and-gantt-charts)<br>`journey` | One task, its face and the line joining it to the last | Neither | |
|
|
25
|
+
| [Git graph](#timelines-journeys-git-graphs-and-gantt-charts)<br>`gitGraph` | One commit with its edge; a branch with its first commit | Neither | Cherry-picks and tags are untested. |
|
|
26
|
+
| [Gantt chart](#timelines-journeys-git-graphs-and-gantt-charts)<br>`gantt` | One task, top to bottom | Neither | `todayMarker` and parallel tasks are untested. |
|
|
27
|
+
|
|
28
|
+
Pins are `%% pa:` comments; see [step order](./step-order.md). Types without pins use their source order. Icons are set up in
|
|
29
|
+
[icons](./icons.md). Pele renders the other Mermaid types too (pie, quadrant, radar and the rest), but pele-animate is not built
|
|
30
|
+
for them; see [Everything else](#everything-else).
|
|
12
31
|
|
|
13
32
|
## Flowcharts
|
|
14
33
|
|
|
15
34
|
Nodes appear in step order, edges draw from their source node, and edge labels fade in. Groups (`subgraph`) and their titles
|
|
16
35
|
are kept. Icon nodes (`@{ icon }`) work here; see [icons](./icons.md).
|
|
17
36
|
|
|
37
|
+
The pins below put "Serve" (the cache hit) before "Fetch", then draw the `Fetch` to `Serve` edge as its own last step. Without them, `Serve` and `Fetch` would appear together.
|
|
38
|
+
|
|
18
39
|
```mermaid live
|
|
19
40
|
flowchart LR
|
|
41
|
+
%% pa: 1=A 2=B 3=C 4=D 5=D>C
|
|
20
42
|
A[Client] --> B{Cached?}
|
|
21
43
|
B -->|yes| C[Serve]
|
|
22
44
|
B -->|no| D[Fetch] --> C
|
|
@@ -24,11 +46,13 @@ flowchart LR
|
|
|
24
46
|
|
|
25
47
|
## Sequence diagrams
|
|
26
48
|
|
|
27
|
-
Actors appear first, then
|
|
49
|
+
Actors appear first, then one step per message in source order, so a cache miss reads as five short steps. Participant boxes in a row get the same height, and group titles are bold.
|
|
28
50
|
|
|
29
51
|
```mermaid live
|
|
30
52
|
sequenceDiagram
|
|
31
53
|
Client->>API: request
|
|
54
|
+
API->>Cache: lookup
|
|
55
|
+
Cache-->>API: miss
|
|
32
56
|
API->>Database: query
|
|
33
57
|
Database-->>API: rows
|
|
34
58
|
API-->>Client: response
|
|
@@ -36,8 +60,208 @@ sequenceDiagram
|
|
|
36
60
|
|
|
37
61
|
**Limit:** Pele ignores `@{ icon }` on sequence participants (the box shows its id), so icons apply to flowcharts only.
|
|
38
62
|
|
|
63
|
+
## Architecture diagrams
|
|
64
|
+
|
|
65
|
+
`architecture-beta`: services in groups, joined by edges. Services appear in step order like flowchart nodes, groups stay visible,
|
|
66
|
+
and `%% pa:` pins work the same way. The icon in parentheses is looked up in your icon packs, so Tabler names and `doverunner-*`
|
|
67
|
+
names work (import the packs as for [icons](./icons.md)). The pins here follow a request: the user, the CDN, the API, the cache and database together, then the queue and worker.
|
|
68
|
+
|
|
69
|
+
```mermaid-icons live
|
|
70
|
+
architecture-beta
|
|
71
|
+
%% pa: 1=user 2=cdn 3=api 4=cache,db 5=queue 6=worker
|
|
72
|
+
group edge(cloud)[Edge]
|
|
73
|
+
group core(cloud)[Core]
|
|
74
|
+
service user(device-desktop)[User]
|
|
75
|
+
service cdn(cloud-computing)[CDN] in edge
|
|
76
|
+
service api(server)[API] in core
|
|
77
|
+
service cache(bolt)[Cache] in core
|
|
78
|
+
service db(database)[Database] in core
|
|
79
|
+
service queue(mail)[Queue] in core
|
|
80
|
+
service worker(settings)[Worker] in core
|
|
81
|
+
user:R --> L:cdn
|
|
82
|
+
cdn:R --> L:api
|
|
83
|
+
api:T --> B:cache
|
|
84
|
+
api:B --> T:db
|
|
85
|
+
api:R --> L:queue
|
|
86
|
+
queue:R --> L:worker
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Icons here are small and fixed in size; `form` and `h` apply to flowcharts only. Edges are read from their written direction
|
|
90
|
+
(`user:R --> L:cdn` is user to cdn). A node id containing `-` can be mistaken for two ids on an edge, so use plain ids.
|
|
91
|
+
|
|
92
|
+
## State diagrams
|
|
93
|
+
|
|
94
|
+
`stateDiagram-v2`: states appear in step order, transitions draw from their source state, and transition labels fade in. The
|
|
95
|
+
start and end markers are nodes too, with the ids `root_start` and `root_end`, so pins can name them.
|
|
96
|
+
|
|
97
|
+
```mermaid live
|
|
98
|
+
stateDiagram-v2
|
|
99
|
+
%% pa: 1=root_start 2=Idle 3=Loading 4=Playing 5=Paused 6=Error 7=root_end
|
|
100
|
+
[*] --> Idle
|
|
101
|
+
Idle --> Loading: play
|
|
102
|
+
Loading --> Playing: ready
|
|
103
|
+
Playing --> Paused: pause
|
|
104
|
+
Paused --> Playing: resume
|
|
105
|
+
Loading --> Error: fail
|
|
106
|
+
Error --> Idle: retry
|
|
107
|
+
Playing --> [*]: stop
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The pins give each state its own step, in the order a viewer meets them. Without pins the order is breadth-first from the start marker, which shows states at the same distance together (here `Playing` and `Error`). Pele gives
|
|
111
|
+
state edges no readable ids, so pele-animate finds each transition's ends from where its path starts and stops. Composite
|
|
112
|
+
states and forks are untested. State diagrams take no icons.
|
|
113
|
+
|
|
114
|
+
## C4 context diagrams
|
|
115
|
+
|
|
116
|
+
`C4Context`: people and systems appear in step order and each `Rel` draws from its source, with its label. Pins use the ids you
|
|
117
|
+
give `Person(...)` and `System(...)`; an edge pin is `3=app>drm`. The example adds the DRM service, CDN and analytics one at a time, then the operator who reads the analytics.
|
|
118
|
+
|
|
119
|
+
```mermaid live
|
|
120
|
+
C4Context
|
|
121
|
+
%% pa: 1=viewer 2=app 3=drm 4=cdn 5=analytics 6=admin
|
|
122
|
+
Person(viewer, "Viewer", "Watches video")
|
|
123
|
+
System(app, "Player app", "Plays protected video")
|
|
124
|
+
System_Ext(drm, "DRM service", "Issues licenses")
|
|
125
|
+
System_Ext(cdn, "CDN", "Delivers segments")
|
|
126
|
+
System(analytics, "Analytics", "Tracks playback")
|
|
127
|
+
Person(admin, "Operator", "Reviews usage")
|
|
128
|
+
Rel(viewer, app, "watches")
|
|
129
|
+
Rel(app, drm, "license")
|
|
130
|
+
Rel(app, cdn, "segments")
|
|
131
|
+
Rel(app, analytics, "events")
|
|
132
|
+
Rel(admin, analytics, "reviews")
|
|
133
|
+
UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Pele lays C4 diagrams out in rows, so a long label between two boxes in the same row can leave no room for the arrowhead:
|
|
137
|
+
keep `Rel` labels short. Only `C4Context` has been checked; the other C4 types and boundaries are untested. C4 shapes take no icons.
|
|
138
|
+
|
|
139
|
+
## Block diagrams
|
|
140
|
+
|
|
141
|
+
`block-beta`: blocks appear in step order and each arrow draws from its source block. Pins use the block ids.
|
|
142
|
+
|
|
143
|
+
```mermaid live
|
|
144
|
+
block-beta
|
|
145
|
+
columns 4
|
|
146
|
+
%% pa: 1=src 2=enc 3=pkg 4=cdn 5=store
|
|
147
|
+
src["Source"] enc["Encode"] pkg["Package"] cdn["CDN"]
|
|
148
|
+
space space store[("Storage")] space
|
|
149
|
+
src --> enc
|
|
150
|
+
enc --> pkg
|
|
151
|
+
pkg --> cdn
|
|
152
|
+
pkg --> store
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Pele routes arrows between blocks in straight lines or short bends, so place linked blocks in the same row or column; a diagonal
|
|
156
|
+
neighbour can give a bent arrow that runs along another one. Block diagrams take no icons.
|
|
157
|
+
|
|
158
|
+
## Class diagrams
|
|
159
|
+
|
|
160
|
+
`classDiagram`: classes appear in step order, relationships draw from the class written first. `Media <|-- Video` reveals `Media`
|
|
161
|
+
and then `Video`, whichever way the arrowhead points. Pins use the class names; the example shows the base class, its subtypes, then the classes that use them.
|
|
162
|
+
|
|
163
|
+
```mermaid live
|
|
164
|
+
classDiagram
|
|
165
|
+
%% pa: 1=Media 2=Video,Audio 3=Player 4=License 5=Session
|
|
166
|
+
Media <|-- Video
|
|
167
|
+
Media <|-- Audio
|
|
168
|
+
Player --> Media : plays
|
|
169
|
+
Player --> License : needs
|
|
170
|
+
License --> Session : opens
|
|
171
|
+
Media : +id
|
|
172
|
+
Media : +title
|
|
173
|
+
Player : +play()
|
|
174
|
+
License : +expiry
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## ER diagrams
|
|
178
|
+
|
|
179
|
+
`erDiagram`: entities appear in step order, relationships draw from the entity written first (`CUSTOMER ||--o{ ORDER` reveals
|
|
180
|
+
`CUSTOMER`, then `ORDER`). The example reads from the customer down to the supplier.
|
|
181
|
+
|
|
182
|
+
```mermaid live
|
|
183
|
+
erDiagram
|
|
184
|
+
%% pa: 1=CUSTOMER 2=ORDER 3=LINE 4=PRODUCT 5=SUPPLIER
|
|
185
|
+
CUSTOMER ||--o{ ORDER : places
|
|
186
|
+
ORDER ||--|{ LINE : contains
|
|
187
|
+
LINE }o--|| PRODUCT : lists
|
|
188
|
+
PRODUCT }o--|| SUPPLIER : "supplied by"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Neither takes icons.
|
|
192
|
+
|
|
193
|
+
## Timelines, journeys, git graphs and Gantt charts
|
|
194
|
+
|
|
195
|
+
These have no edges to follow, so each steps through its items in reading order and `%% pa:` pins do nothing (as for sequence
|
|
196
|
+
diagrams). Section headers, axes, grids and legends stay visible.
|
|
197
|
+
|
|
198
|
+
- **`timeline`:** one step per time period, with its events. List periods in time order.
|
|
199
|
+
|
|
200
|
+
```mermaid live
|
|
201
|
+
timeline
|
|
202
|
+
title Product history
|
|
203
|
+
section Build
|
|
204
|
+
2019 : Prototype
|
|
205
|
+
2020 : Private beta : First customers
|
|
206
|
+
section Grow
|
|
207
|
+
2021 : Public launch
|
|
208
|
+
2023 : Mobile apps : Offline mode
|
|
209
|
+
2025 : Global rollout
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
- **`journey`:** one step per task: the task, its face, and the line segment that joins it to the task before.
|
|
213
|
+
|
|
214
|
+
```mermaid live
|
|
215
|
+
journey
|
|
216
|
+
title Watching a protected video
|
|
217
|
+
section Find
|
|
218
|
+
Open the app: 5: Viewer
|
|
219
|
+
Pick a title: 4: Viewer
|
|
220
|
+
section Play
|
|
221
|
+
Wait for the license: 2: Viewer, App
|
|
222
|
+
Playback starts: 5: Viewer
|
|
223
|
+
section Finish
|
|
224
|
+
Resume later: 4: Viewer
|
|
225
|
+
Rate the title: 3: Viewer
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- **`gitGraph`:** one step per commit in the order written, with the edge that ends on it. A branch's label and lane appear with
|
|
229
|
+
its first commit.
|
|
230
|
+
|
|
231
|
+
```mermaid live
|
|
232
|
+
gitGraph
|
|
233
|
+
commit id: "init"
|
|
234
|
+
branch feature
|
|
235
|
+
commit id: "api"
|
|
236
|
+
commit id: "ui"
|
|
237
|
+
checkout main
|
|
238
|
+
commit id: "fix"
|
|
239
|
+
merge feature id: "m"
|
|
240
|
+
branch release
|
|
241
|
+
commit id: "v1"
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
- **`gantt`:** one step per task, top to bottom: write tasks in the order you want them to appear.
|
|
245
|
+
|
|
246
|
+
```mermaid live
|
|
247
|
+
gantt
|
|
248
|
+
dateFormat YYYY-MM-DD
|
|
249
|
+
section Plan
|
|
250
|
+
Scope :a1, 2024-01-01, 3d
|
|
251
|
+
Design :after a1, 4d
|
|
252
|
+
section Build
|
|
253
|
+
Code :b1, 2024-01-08, 8d
|
|
254
|
+
Review :after b1, 2d
|
|
255
|
+
section Ship
|
|
256
|
+
Test :2024-01-18, 4d
|
|
257
|
+
Release :2024-01-22, 1d
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
None of these take icons. Only these small examples have been checked in a browser; long diagrams, `todayMarker`, tags, cherry-picks and
|
|
261
|
+
parallel tasks are untested.
|
|
262
|
+
|
|
39
263
|
## Everything else
|
|
40
264
|
|
|
41
|
-
Other Mermaid types (
|
|
265
|
+
Other Mermaid types (pie, quadrant, radar and the rest) render through Pele as usual. pele-animate is not designed for
|
|
42
266
|
them and its behavior on them is untested, so use plain Pele for those. Step mode has been checked in a browser on
|
|
43
|
-
|
|
267
|
+
the types above only; sequence diagrams have not been checked the same way yet.
|
package/docs/faq.md
CHANGED
|
@@ -22,7 +22,7 @@ you want to explain in order.
|
|
|
22
22
|
**How big is it?** The core (`pele-animate.js` plus CSS) is about 20 KB unminified. Icon packs are separate modules, so you only
|
|
23
23
|
ship the categories you import (the largest, `system`, is about 199 KB unminified).
|
|
24
24
|
|
|
25
|
-
**Which diagrams does it support?** Flowcharts and
|
|
25
|
+
**Which diagrams does it support?** Flowcharts, sequence diagrams, architecture diagrams, state diagrams, C4 context, block, class and ER diagrams, timelines, journeys, git graphs and Gantt charts ([diagram types](./diagram-types.md)).
|
|
26
26
|
|
|
27
27
|
**Can I use my own icons?** Yes ([custom icons](./custom-icons.md)).
|
|
28
28
|
|
package/docs/step-order.md
CHANGED
|
@@ -14,6 +14,12 @@ them. By default, the order follows the edges. A `%% pa:` comment lets you set i
|
|
|
14
14
|
|
|
15
15
|
- **Flowchart:** nodes appear by level, counting from the nodes nothing points to. Each edge draws from its source once the source
|
|
16
16
|
is on screen, and its target appears as the edge lands. Branches of a decision appear together.
|
|
17
|
+
- **Architecture diagram:** like a flowchart: services appear by level, edges follow their written direction, pins work the same way.
|
|
18
|
+
- **Timeline, journey, git graph, Gantt chart:** one step per item in reading order (period, task, commit, task); pins do nothing.
|
|
19
|
+
- **Class and ER diagrams:** like a flowchart, but a relationship runs from the class or entity written first.
|
|
20
|
+
- **Block diagram:** like a flowchart, from the blocks nothing points to.
|
|
21
|
+
- **C4 context diagram:** like a flowchart, from the people and systems nothing points to.
|
|
22
|
+
- **State diagram:** like a flowchart, starting from the start marker (`root_start`); the end marker is `root_end`.
|
|
17
23
|
- **Sequence diagram:** actors first, then the messages in order.
|
|
18
24
|
|
|
19
25
|
## Pins
|
package/package.json
CHANGED
package/pele-animate.css
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
.pa-ready:not(.pa-in):not(.pa-step) .pele-node,
|
|
3
3
|
.pa-ready:not(.pa-in):not(.pa-step) .pele-edge-label,
|
|
4
4
|
.pa-ready:not(.pa-in):not(.pa-step) .pele-lifeline,
|
|
5
|
-
.pa-ready:not(.pa-in):not(.pa-step) .pele-marker
|
|
5
|
+
.pa-ready:not(.pa-in):not(.pa-step) .pele-marker,
|
|
6
|
+
.pa-ready:not(.pa-in):not(.pa-step) .pa-item { opacity: 0; }
|
|
6
7
|
.pa-ready:not(.pa-in):not(.pa-step) .pele-edge > path:not(.pele-marker) { stroke-dasharray: var(--len); stroke-dashoffset: var(--len); }
|
|
7
8
|
|
|
8
9
|
/* Keyframes carry the dash, so edges that are dashed in the diagram go back to their own dash pattern afterwards. */
|
|
9
10
|
.pa-ready.pa-in:not(.pa-step) .pele-node,
|
|
10
11
|
.pa-ready.pa-in:not(.pa-step) .pele-edge-label,
|
|
11
12
|
.pa-ready.pa-in:not(.pa-step) .pele-lifeline,
|
|
12
|
-
.pa-ready.pa-in:not(.pa-step) .pele-marker
|
|
13
|
+
.pa-ready.pa-in:not(.pa-step) .pele-marker,
|
|
14
|
+
.pa-ready.pa-in:not(.pa-step) .pa-item { animation: pa-fade .4s ease-out backwards; animation-delay: var(--d, 0s); }
|
|
13
15
|
.pa-ready.pa-in:not(.pa-step) .pele-edge > path:not(.pele-marker) { animation: pa-draw .4s ease-out backwards; animation-delay: var(--d, 0s); }
|
|
14
16
|
.pa-ready.pa-done:not(.pa-step) * { animation: none !important; }
|
|
15
17
|
@keyframes pa-fade { from { opacity: 0; } }
|
|
@@ -19,7 +21,7 @@
|
|
|
19
21
|
|
|
20
22
|
@media (prefers-reduced-motion: reduce) {
|
|
21
23
|
.pa-ready * { animation: none !important; }
|
|
22
|
-
.pa-ready:not(.pa-step) .pele-node, .pa-ready:not(.pa-step) .pele-edge-label, .pa-ready:not(.pa-step) .pele-lifeline, .pa-ready:not(.pa-step) .pele-marker { opacity: 1 !important; }
|
|
24
|
+
.pa-ready:not(.pa-step) .pele-node, .pa-ready:not(.pa-step) .pele-edge-label, .pa-ready:not(.pa-step) .pele-lifeline, .pa-ready:not(.pa-step) .pele-marker, .pa-ready:not(.pa-step) .pa-item { opacity: 1 !important; }
|
|
23
25
|
.pa-ready .pele-edge > path { stroke-dasharray: none; stroke-dashoffset: 0 !important; }
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -51,6 +53,9 @@
|
|
|
51
53
|
/* Edge labels sit on the line; give them the panel color so the line doesn't strike through the text. */
|
|
52
54
|
.pa-scene .pele-edge-label rect { fill: var(--pa-bg); }
|
|
53
55
|
|
|
56
|
+
/* Pele colours text on solid fills (timeline periods, gantt bars, git branch tags) with --_bg, which the scene makes transparent: use the panel surface instead. */
|
|
57
|
+
.pa-scene text[fill="var(--_bg)"] { fill: var(--pele-surface); }
|
|
58
|
+
|
|
54
59
|
/* Group and box titles read as headings. */
|
|
55
60
|
:where(.pa-scene) .pele-cluster-label { font-weight: 700; }
|
|
56
61
|
|
package/pele-animate.js
CHANGED
|
@@ -16,11 +16,33 @@ export function parseOrder(source = '') {
|
|
|
16
16
|
return levels;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Edge ids by diagram type: flowchart `L_A_B_0`, architecture and C4 `A-B` (repeat edges get `-N`), block `1-A-B`, class `id_A_B_1`,
|
|
20
|
+
// ER `id_entity-A-0_entity-B-1_0`. Ends follow the written order (`A <|-- B` is A, then B).
|
|
21
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
19
22
|
function endpoints(edgeId, ids) {
|
|
20
|
-
for (const s of ids)
|
|
23
|
+
for (const s of ids)
|
|
24
|
+
for (const d of ids)
|
|
25
|
+
if (new RegExp(`^(?:L_${esc(s)}_${esc(d)}_\\d+|(?:\\d+-)?${esc(s)}-${esc(d)}(?:-\\d+)?|id_${esc(s)}_${esc(d)}_\\d+|id_entity-${esc(s)}-\\d+_entity-${esc(d)}-\\d+_\\d+)$`).test(edgeId)) return [s, d];
|
|
21
26
|
return null;
|
|
22
27
|
}
|
|
23
28
|
|
|
29
|
+
// State diagrams name edges `edge0`, `edge1`, ... with no ends in the id, so read the ends off the geometry:
|
|
30
|
+
// the node nearest to where the path starts (source) and ends (target).
|
|
31
|
+
function geoEndpoints(edge, boxes) {
|
|
32
|
+
const path = edge.querySelector('path:not(.pele-marker)');
|
|
33
|
+
if (!path) return null;
|
|
34
|
+
const near = (pt) => {
|
|
35
|
+
let best = null, bd = 12; // an end more than 12 px from every node is not an end
|
|
36
|
+
for (const [id, b] of boxes) {
|
|
37
|
+
const d = Math.hypot(Math.max(b.x - pt.x, 0, pt.x - b.x - b.width), Math.max(b.y - pt.y, 0, pt.y - b.y - b.height));
|
|
38
|
+
if (d < bd) { bd = d; best = id; }
|
|
39
|
+
}
|
|
40
|
+
return best;
|
|
41
|
+
};
|
|
42
|
+
const s = near(path.getPointAtLength(0)), d = near(path.getPointAtLength(path.getTotalLength()));
|
|
43
|
+
return s && d ? [s, d] : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
24
46
|
function bfs(ids, pairs, pinned) {
|
|
25
47
|
const level = new Map(pinned);
|
|
26
48
|
const hasIn = new Set(pairs.map(([, d]) => d));
|
|
@@ -97,11 +119,80 @@ export function prepare(source = '') {
|
|
|
97
119
|
}
|
|
98
120
|
|
|
99
121
|
|
|
122
|
+
// Diagrams with no edges to follow (timeline, journey, gitGraph, gantt) step through their items in reading order.
|
|
123
|
+
// Returns [[element, ...], ...], one array per step, or null for any other diagram.
|
|
124
|
+
const pts = (path) => path.getAttribute('d').match(/-?[\d.]+/g)?.map(Number) ?? [];
|
|
125
|
+
function sequentialSteps(svg) {
|
|
126
|
+
const all = (sel) => [...svg.querySelectorAll(sel)];
|
|
127
|
+
if (svg.classList.contains('pele-timeline')) return all('.pele-period').map((p) => [p]);
|
|
128
|
+
if (svg.classList.contains('pele-gantt')) return all('.pele-task').map((t) => [t]);
|
|
129
|
+
if (svg.classList.contains('pele-journey')) {
|
|
130
|
+
// Wide: tasks, faces and one polyline are separate. Narrow (vertical list): each step holds its face, edges join the steps.
|
|
131
|
+
const tasks = all('.pele-task, .pele-journey-step');
|
|
132
|
+
const faces = all('.pele-face').filter((f) => !f.closest('.pele-node'));
|
|
133
|
+
// The satisfaction line is one polyline `M x,yL x,y...`: split it so each segment arrives with its task.
|
|
134
|
+
const segs = [];
|
|
135
|
+
for (const line of all('path.pele-edge')) {
|
|
136
|
+
const d = line.getAttribute('d');
|
|
137
|
+
if (!/^M[-\d.,]+(L[-\d.,]+)+$/.test(d)) { segs.push(line); continue; }
|
|
138
|
+
const p = d.slice(1).split('L');
|
|
139
|
+
for (let i = 1; i < p.length; i++) {
|
|
140
|
+
const seg = line.cloneNode();
|
|
141
|
+
seg.setAttribute('d', `M${p[i - 1]}L${p[i]}`);
|
|
142
|
+
line.before(seg);
|
|
143
|
+
segs.push(seg);
|
|
144
|
+
}
|
|
145
|
+
line.remove();
|
|
146
|
+
}
|
|
147
|
+
return tasks.map((t, i) => [t, faces[i], segs[i - 1]].filter(Boolean));
|
|
148
|
+
}
|
|
149
|
+
if (svg.classList.contains('pele-gitGraph')) {
|
|
150
|
+
const commits = all('.pele-commit');
|
|
151
|
+
const at = (c) => { const e = c.querySelector('circle'); return [+e.getAttribute('cx'), +e.getAttribute('cy')]; };
|
|
152
|
+
const steps = commits.map((c) => [c]);
|
|
153
|
+
// an edge arrives with the commit it ends on
|
|
154
|
+
for (const e of all('.pele-edge')) {
|
|
155
|
+
const path = e.matches('path') ? e : e.querySelector('path');
|
|
156
|
+
const end = path.getPointAtLength(path.getTotalLength());
|
|
157
|
+
let best = steps.length - 1, bd = Infinity;
|
|
158
|
+
commits.forEach((c, i) => { const [x, y] = at(c), d = Math.hypot(x - end.x, y - end.y); if (d < bd) { bd = d; best = i; } });
|
|
159
|
+
steps[best]?.push(e);
|
|
160
|
+
}
|
|
161
|
+
// a branch (label and lane) appears with its first commit: the commit whose y is on the lane
|
|
162
|
+
for (const b of all('.pele-branch')) {
|
|
163
|
+
const y = pts(b.querySelector('.pele-lane'))[1];
|
|
164
|
+
const first = commits.findIndex((c) => Math.abs(at(c)[1] - y) < 1);
|
|
165
|
+
steps[first < 0 ? 0 : first].push(b);
|
|
166
|
+
}
|
|
167
|
+
return steps;
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Intro for sequentialSteps: each step fades in after the one before; the same steps drive step mode.
|
|
173
|
+
function introSteps(svg, steps) {
|
|
174
|
+
const u = Math.min(UNIT, MAX_INTRO / Math.max(1, steps.length));
|
|
175
|
+
steps.forEach((els, i) => els.forEach((el) => { el.classList.add('pa-item'); el.style.setProperty('--d', `${i * u}s`); }));
|
|
176
|
+
svg.paSteps = steps.map((els) => els.map((el) => [el, [el]]));
|
|
177
|
+
reveal(svg);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function reveal(svg) {
|
|
181
|
+
if (!('IntersectionObserver' in window)) return svg.classList.add('pa-in');
|
|
182
|
+
svg.classList.remove('pa-in');
|
|
183
|
+
const io = new IntersectionObserver(([en]) => {
|
|
184
|
+
if (en.isIntersecting) { svg.classList.add('pa-in'); io.disconnect(); }
|
|
185
|
+
}, { threshold: 0.3 });
|
|
186
|
+
io.observe(svg);
|
|
187
|
+
}
|
|
188
|
+
|
|
100
189
|
export function animate(svg, { hover = svg, source = '' } = {}) {
|
|
101
190
|
fitGlyphs(svg);
|
|
102
191
|
equalizeActors(svg);
|
|
103
192
|
svg.querySelectorAll('.pa-dot').forEach((d) => d.remove());
|
|
104
193
|
svg.classList.add('pa-ready');
|
|
194
|
+
const seq = sequentialSteps(svg);
|
|
195
|
+
if (seq) return introSteps(svg, seq);
|
|
105
196
|
const nodes = [...svg.querySelectorAll('.pele-node')];
|
|
106
197
|
const edges = [...svg.querySelectorAll('.pele-edge')];
|
|
107
198
|
const ids = [...new Set(nodes.map((n) => n.dataset.id))];
|
|
@@ -116,7 +207,17 @@ export function animate(svg, { hover = svg, source = '' } = {}) {
|
|
|
116
207
|
edges.forEach((e, i) => { times.set(e, 0.5 + i * u); step.set(e, i + 1); });
|
|
117
208
|
nodes.forEach((n) => step.set(n, 0));
|
|
118
209
|
} else {
|
|
119
|
-
const
|
|
210
|
+
const boxes = new Map();
|
|
211
|
+
const pairs = edges.map((e) => {
|
|
212
|
+
const found = endpoints(e.dataset.id, ids);
|
|
213
|
+
if (found) return found;
|
|
214
|
+
if (!boxes.size)
|
|
215
|
+
for (const n of nodes) {
|
|
216
|
+
const b = n.getBBox(), t = n.transform.baseVal.consolidate()?.matrix;
|
|
217
|
+
boxes.set(n.dataset.id, { x: b.x + (t?.e ?? 0), y: b.y + (t?.f ?? 0), width: b.width, height: b.height });
|
|
218
|
+
}
|
|
219
|
+
return geoEndpoints(e, boxes);
|
|
220
|
+
});
|
|
120
221
|
const level = bfs(ids, pairs.filter(Boolean), parseOrder(source));
|
|
121
222
|
const u = Math.min(UNIT, MAX_INTRO / (Math.max(0, ...level.values()) + 1));
|
|
122
223
|
const k = u / UNIT;
|
|
@@ -184,12 +285,7 @@ export function animate(svg, { hover = svg, source = '' } = {}) {
|
|
|
184
285
|
hover.addEventListener('focusin', play);
|
|
185
286
|
hover.addEventListener('focusout', stop);
|
|
186
287
|
|
|
187
|
-
|
|
188
|
-
svg.classList.remove('pa-in');
|
|
189
|
-
const io = new IntersectionObserver(([en]) => {
|
|
190
|
-
if (en.isIntersecting) { svg.classList.add('pa-in'); io.disconnect(); }
|
|
191
|
-
}, { threshold: 0.3 });
|
|
192
|
-
io.observe(svg);
|
|
288
|
+
reveal(svg);
|
|
193
289
|
}
|
|
194
290
|
|
|
195
291
|
const STEP_MS = 1200; // autoplay pace in step mode
|