@jarenjs/mermaid 0.46.4 → 0.49.2
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 +63 -3
- package/dist/types/gantt-calendar.d.ts +99 -0
- package/dist/types/index.d.ts +7 -1
- package/dist/types/layout/gantt.d.ts +31 -0
- package/dist/types/parser/gantt-grammar.d.ts +174 -0
- package/dist/types/parser/gantt.d.ts +35 -5
- package/dist/types/parser/index.d.ts +5 -1
- package/dist/types/render/gantt.d.ts +26 -0
- package/dist/types/render/misc.d.ts +1 -1
- package/docs/MERMAID-FORMAT.md +106 -4
- package/package.json +4 -4
- package/schemas/jaren-mermaid-ast.schema.json +379 -37
- package/src/gantt-calendar.js +152 -0
- package/src/index.js +5 -1
- package/src/layout/gantt.js +269 -0
- package/src/parser/gantt-grammar.js +298 -0
- package/src/parser/gantt.js +456 -8
- package/src/parser/index.js +5 -2
- package/src/render/gantt.js +169 -0
- package/src/render/index.js +6 -2
- package/src/render/misc.js +5 -13
- package/styles/mermaid.css +21 -0
package/README.md
CHANGED
|
@@ -158,13 +158,73 @@ flowchart LR
|
|
|
158
158
|
Fully laid out: **flowchart**, **sequence**, **state** (through the
|
|
159
159
|
flowchart engine via an adapter — states as rounded nodes, `[*]` as a
|
|
160
160
|
filled start dot and an end ring, verbatim transition labels on the
|
|
161
|
-
edges; composite states stay flattened in v1)
|
|
162
|
-
chart: **class**, **ER**,
|
|
161
|
+
edges; composite states stay flattened in v1) and **gantt** (a real time
|
|
162
|
+
axis — see below). Structured panels / chart: **class**, **ER**,
|
|
163
|
+
**pie**. Parse-accepted with an
|
|
163
164
|
honest "not yet laid out" placeholder: mindmap, gitGraph, journey,
|
|
164
165
|
timeline, quadrantChart, requirement. The benchmark's coverage scorecard reports this
|
|
165
166
|
without hiding gaps.
|
|
166
167
|
|
|
167
168
|
|
|
169
|
+
## Gantt: the date directives are interpreted
|
|
170
|
+
|
|
171
|
+
A Gantt diagram is parsed into a **resolved schedule** and rendered as a
|
|
172
|
+
timeline — an aligned time axis, section bands, bars, milestone
|
|
173
|
+
diamonds, dependency connectors and shaded excluded days:
|
|
174
|
+
|
|
175
|
+
```mermaid
|
|
176
|
+
gantt
|
|
177
|
+
title Release plan
|
|
178
|
+
dateFormat YYYY-MM-DD
|
|
179
|
+
excludes weekends
|
|
180
|
+
tickInterval 1week
|
|
181
|
+
section Build
|
|
182
|
+
Design : done, a1, 2024-01-04, 3d
|
|
183
|
+
Implement : active, a2, after a1, 5d
|
|
184
|
+
section Ship
|
|
185
|
+
Review : crit, a3, after a2, 2d
|
|
186
|
+
Launch : milestone, m1, after a3, 0d
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Every directive means something. `dateFormat` compiles to a strict date
|
|
190
|
+
parser, `axisFormat` to the axis labels, `tickInterval` and `weekday` to
|
|
191
|
+
the tick boundaries, and `excludes`/`weekend` to a working calendar that
|
|
192
|
+
moves a duration-derived end past the days it skips. Tick positions come
|
|
193
|
+
from `@jarenjs/core/dates` — the same ladder the chart component's time
|
|
194
|
+
axis reads — so a timeline and a chart cannot disagree about where a
|
|
195
|
+
week begins.
|
|
196
|
+
|
|
197
|
+
Each task carries `{ id, flags, start, end, duration, after, line }`
|
|
198
|
+
beside its raw `info`, with `start`/`end` as epoch milliseconds and the
|
|
199
|
+
interval half-open. The grammar, the vendored conformance table and the
|
|
200
|
+
three deliberate divergences from Mermaid are in
|
|
201
|
+
[docs/MERMAID-FORMAT.md §4.4](./docs/MERMAID-FORMAT.md). The load-bearing
|
|
202
|
+
one: **there is no clock here**, so a schedule with no dated anchor is a
|
|
203
|
+
line-aware error rather than a diagram whose meaning changes daily.
|
|
204
|
+
|
|
205
|
+
Mermaid's `dateFormat` is moment's grammar and its `axisFormat` is
|
|
206
|
+
strftime; neither is LDML, so each has its own tokenizer and each token
|
|
207
|
+
is mapped individually onto the core date kernel. An unsupported token
|
|
208
|
+
is refused by name rather than passed through as text.
|
|
209
|
+
|
|
210
|
+
**Localized labels take an injected provider.** This engine ships no
|
|
211
|
+
month or weekday names, so `axisFormat %b %Y` (or a `dateFormat` with
|
|
212
|
+
`MMMM`) needs one — the same `DateNames` record
|
|
213
|
+
`@jarenjs/locales`' `compileDateLocale(pack).names` produces:
|
|
214
|
+
|
|
215
|
+
```js
|
|
216
|
+
import { compileDateLocale, nl } from '@jarenjs/locales';
|
|
217
|
+
renderMermaid(source, { dateNames: compileDateLocale(nl).names });
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Without a provider a name token is a compile error, not a silent English
|
|
221
|
+
fallback. Task ids and dates stay ASCII data either way — an RTL name is
|
|
222
|
+
text, not syntax.
|
|
223
|
+
|
|
224
|
+
At scale, parse (which is also schedule resolution), layout and render
|
|
225
|
+
are reported separately in the benchmark: <!--bm:mermaid.ganttScale-->at 10,000 tasks, ~34 ms to parse and resolve, ~3.8 ms to lay out and ~7.7 ms to render<!--/bm-->.
|
|
226
|
+
|
|
227
|
+
|
|
168
228
|
## Styling, notes and interaction
|
|
169
229
|
|
|
170
230
|
**`classDef` / `class` / `style` now paint.** The parser always recorded them;
|
|
@@ -261,7 +321,7 @@ detection + Jison + validation — so it is heavy and noisy; these are
|
|
|
261
321
|
representative, not a bare-grammar microbenchmark.)
|
|
262
322
|
|
|
263
323
|
The headless **parse → layout → SVG string** rows are jaren-only
|
|
264
|
-
(<!--bm:mermaid.svgMs-->~0.
|
|
324
|
+
(<!--bm:mermaid.svgMs-->~0.68 ms for a 25-node flowchart, ~1 ms at 100 nodes<!--/bm-->): mermaid.js
|
|
265
325
|
needs a browser DOM (`getBBox`) to render, so there is no fair
|
|
266
326
|
full-render head-to-head — producing a complete standalone SVG in pure
|
|
267
327
|
Node is a capability it lacks.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The working calendar behind `excludes`: which whole days a
|
|
3
|
+
* schedule skips, and what a duration therefore means.
|
|
4
|
+
*
|
|
5
|
+
* Two implementations of one answer live here on purpose. The exported
|
|
6
|
+
* one expresses an excluded stretch as half-open day intervals and
|
|
7
|
+
* measures it with `@jarenjs/core/series` (`mergeIntervals`,
|
|
8
|
+
* `coverageOf`) — the suite's one interval algebra, so a weekend here
|
|
9
|
+
* and an unavailability there are the same kind of thing. The other,
|
|
10
|
+
* {@link pushEndDayByDay}, is the naive day-stepping loop the shipped
|
|
11
|
+
* Mermaid renderer runs; it exists so the fast answer has an
|
|
12
|
+
* independent oracle to be differential-tested against, and it is the
|
|
13
|
+
* definition when the two disagree.
|
|
14
|
+
*
|
|
15
|
+
* The rule is Mermaid's, not an invention: an excluded day pushes a
|
|
16
|
+
* task's END out by a whole day, the task's own START is left where the
|
|
17
|
+
* document put it, and an end that was written as an explicit DATE is
|
|
18
|
+
* never pushed at all — only one derived from a duration is. Days are
|
|
19
|
+
* UTC days, because this engine has no local zone and no clock.
|
|
20
|
+
*
|
|
21
|
+
* This is not a holiday service. A schedule skips exactly the weekends,
|
|
22
|
+
* weekday names and dates its own `excludes` line lists.
|
|
23
|
+
*/
|
|
24
|
+
/** Milliseconds in a UTC day. */
|
|
25
|
+
export declare const DAY_MS = 86400000;
|
|
26
|
+
/**
|
|
27
|
+
* Mermaid's own guard: a schedule whose exclusions swallow every day
|
|
28
|
+
* would push an end forever, so the walk stops and the caller reports a
|
|
29
|
+
* `JM` error instead of hanging.
|
|
30
|
+
*/
|
|
31
|
+
export declare const MAX_PUSH_DAYS = 10000;
|
|
32
|
+
/**
|
|
33
|
+
* The index of the UTC day holding `ms`, counting from 1970-01-01.
|
|
34
|
+
* @param {number} ms
|
|
35
|
+
* @returns {number}
|
|
36
|
+
*/
|
|
37
|
+
export declare function dayIndexOf(ms: number): number;
|
|
38
|
+
export type Excluder = {
|
|
39
|
+
/**
|
|
40
|
+
* whether the document excludes anything at all
|
|
41
|
+
*/
|
|
42
|
+
any: boolean;
|
|
43
|
+
isExcludedDay: (dayIndex: number) => boolean;
|
|
44
|
+
/**
|
|
45
|
+
* merged half-open day spans over `[fromDay, toDay)`
|
|
46
|
+
*/
|
|
47
|
+
intervalsIn: (fromDay: number, toDay: number) => {
|
|
48
|
+
start: number;
|
|
49
|
+
end: number;
|
|
50
|
+
}[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {object} Excluder
|
|
54
|
+
* @property {boolean} any whether the document excludes anything at all
|
|
55
|
+
* @property {(dayIndex: number) => boolean} isExcludedDay
|
|
56
|
+
* @property {(fromDay: number, toDay: number) => { start: number, end: number }[]}
|
|
57
|
+
* intervalsIn merged half-open day spans over `[fromDay, toDay)`
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Build the working calendar a document's `excludes`, `weekend` and
|
|
61
|
+
* `dateFormat` describe.
|
|
62
|
+
*
|
|
63
|
+
* @param {{ weekends: boolean, weekdays: number[], days: number[] }} rules -
|
|
64
|
+
* `weekdays` are ISO weekday numbers, `days` are day indexes
|
|
65
|
+
* @param {number} weekendStart - ISO weekday the weekend starts on
|
|
66
|
+
* @returns {Excluder}
|
|
67
|
+
*/
|
|
68
|
+
export declare function createExcluder(rules: {
|
|
69
|
+
weekends: boolean;
|
|
70
|
+
weekdays: number[];
|
|
71
|
+
days: number[];
|
|
72
|
+
}, weekendStart: number): Excluder;
|
|
73
|
+
/**
|
|
74
|
+
* Push a task's end past the excluded days its span crosses.
|
|
75
|
+
*
|
|
76
|
+
* The probes are the instants `start + k days` for `k` from 1, which is
|
|
77
|
+
* what makes this a *day* rule rather than a millisecond one: a task
|
|
78
|
+
* that starts at noon on Friday and lasts a day ends at noon on Monday
|
|
79
|
+
* when the weekend is excluded, not at midnight.
|
|
80
|
+
*
|
|
81
|
+
* @param {number} start - epoch milliseconds
|
|
82
|
+
* @param {number} end - epoch milliseconds, at or after `start`
|
|
83
|
+
* @param {Excluder} excluder
|
|
84
|
+
* @returns {number} the pushed end, or `NaN` when the exclusions never
|
|
85
|
+
* let the task finish
|
|
86
|
+
*/
|
|
87
|
+
export declare function pushEndPastExclusions(start: number, end: number, excluder: Excluder): number;
|
|
88
|
+
/**
|
|
89
|
+
* The oracle: Mermaid's own day-stepping loop, written plainly. Kept as
|
|
90
|
+
* a test double rather than as the implementation because it walks
|
|
91
|
+
* every day of a span one at a time, but it is the definition of the
|
|
92
|
+
* answer {@link pushEndPastExclusions} computes.
|
|
93
|
+
*
|
|
94
|
+
* @param {number} start - epoch milliseconds
|
|
95
|
+
* @param {number} end - epoch milliseconds
|
|
96
|
+
* @param {Excluder} excluder
|
|
97
|
+
* @returns {number} the pushed end, or `NaN` when it never finishes
|
|
98
|
+
*/
|
|
99
|
+
export declare function pushEndDayByDay(start: number, end: number, excluder: Excluder): number;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -28,11 +28,17 @@ export { MERMAID_VERSION, diagramDocument, walkSequence, flowNode, flowEdge, seq
|
|
|
28
28
|
/**
|
|
29
29
|
* Convenience: parse → layout → render, error-safe, in one call.
|
|
30
30
|
* @param {string} source
|
|
31
|
-
* @param {{ theme?: any
|
|
31
|
+
* @param {{ theme?: any, dateNames?: import('@jarenjs/core/dates').DateNames,
|
|
32
|
+
* parseFrontmatter?: (text: string) => any }} [options] - `dateNames`
|
|
33
|
+
* is the locale-name record a Gantt's `dateFormat`/`axisFormat` needs
|
|
34
|
+
* for a month or weekday token (`compileDateLocale(pack).names`);
|
|
35
|
+
* this engine ships none of its own
|
|
32
36
|
* @returns {any} an SVG vnode
|
|
33
37
|
*/
|
|
34
38
|
export declare function renderMermaid(source: string, options?: {
|
|
35
39
|
theme?: any;
|
|
40
|
+
dateNames?: import('@jarenjs/core/dates').DateNames;
|
|
41
|
+
parseFrontmatter?: (text: string) => any;
|
|
36
42
|
}): any;
|
|
37
43
|
export type CompiledMermaid = {
|
|
38
44
|
/**
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Gantt layout: a resolved schedule → a deterministic
|
|
3
|
+
* `PositionedDiagram` scene. Pure, host-free and vnode-free, so the
|
|
4
|
+
* geometry can be asserted without rendering anything.
|
|
5
|
+
*
|
|
6
|
+
* The time axis is not this file's invention: the domain comes from the
|
|
7
|
+
* AST, the tick positions come from `@jarenjs/core/dates` — either the
|
|
8
|
+
* document's own `tickInterval` through `timeTicksEvery`, or the shared
|
|
9
|
+
* step ladder through `axisTicksTime`, the same one the chart component
|
|
10
|
+
* draws its time axis with — and the tick labels come from the
|
|
11
|
+
* document's `axisFormat` compiled once here. A second tick ladder in a
|
|
12
|
+
* renderer would be a second set of positions.
|
|
13
|
+
*
|
|
14
|
+
* Two decisions are made here rather than in the renderer, because both
|
|
15
|
+
* are geometry:
|
|
16
|
+
*
|
|
17
|
+
* - **Tick labels thin out.** `tickInterval 1day` over two years is 730
|
|
18
|
+
* ticks; the marks all stay, and labels are kept at the largest
|
|
19
|
+
* stride that leaves them from overlapping. The scene says which.
|
|
20
|
+
* - **Row names elide.** A name wider than the label column is cut with
|
|
21
|
+
* an ellipsis at layout time, so the measured width and the drawn text
|
|
22
|
+
* are the same string.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* @param {any} ast gantt AST (`parser/gantt.js`)
|
|
26
|
+
* @param {{ dateNames?: import('@jarenjs/core/dates').DateNames }} [options]
|
|
27
|
+
* @returns {any} PositionedDiagram
|
|
28
|
+
*/
|
|
29
|
+
export declare function layoutGantt(ast: any, options?: {
|
|
30
|
+
dateNames?: import('@jarenjs/core/dates').DateNames;
|
|
31
|
+
}): any;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The three Gantt token grammars, adapted to the core date
|
|
3
|
+
* kernel. Mermaid's Gantt header speaks **three different pattern
|
|
4
|
+
* languages** and none of them is Unicode LDML:
|
|
5
|
+
*
|
|
6
|
+
* - `dateFormat` is dayjs' `customParseFormat` vocabulary (moment's
|
|
7
|
+
* spelling): `YYYY-MM-DD`, where `YYYY` is the calendar year;
|
|
8
|
+
* - `axisFormat` is d3-time-format's strftime vocabulary: `%Y-%m-%d`;
|
|
9
|
+
* - `tickInterval` and a task's duration are two small regular
|
|
10
|
+
* grammars of their own (`1week`, `3d`).
|
|
11
|
+
*
|
|
12
|
+
* Each gets its own tokenizer here, and each token is mapped
|
|
13
|
+
* individually onto the LDML token `compileDateFormat`/`compileDateParser`
|
|
14
|
+
* understand. Handing `YYYY` straight to the core compiler would be a
|
|
15
|
+
* silent lie — LDML's `YYYY` is the *week-numbering* year, which is
|
|
16
|
+
* moment's most reported footgun and the reason `@jarenjs/core/dates`
|
|
17
|
+
* refuses that spelling in the first place.
|
|
18
|
+
*
|
|
19
|
+
* Every function here returns `{ value, error }` rather than throwing:
|
|
20
|
+
* the caller owns the source line, and a `JM` error without one is not
|
|
21
|
+
* worth much.
|
|
22
|
+
*
|
|
23
|
+
* The vendored conformance table for all of this — which tokens the
|
|
24
|
+
* shipped Mermaid version documents, which of them this engine
|
|
25
|
+
* implements and why the rest are refused — is
|
|
26
|
+
* `test/mermaid/fixtures/gantt-grammar.json`, pinned against this file
|
|
27
|
+
* by `test/mermaid/gantt.test.js`.
|
|
28
|
+
*/
|
|
29
|
+
/** Mermaid's own default when a diagram declares no `dateFormat`. */
|
|
30
|
+
export declare const DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
|
|
31
|
+
/** Mermaid's own default when a diagram declares no `axisFormat`. */
|
|
32
|
+
export declare const DEFAULT_AXIS_FORMAT = "%Y-%m-%d";
|
|
33
|
+
/** dayjs token → the LDML token that reads and writes the same field. */
|
|
34
|
+
declare const MOMENT_TO_LDML: Readonly<{
|
|
35
|
+
YYYY: "yyyy";
|
|
36
|
+
YY: "yy";
|
|
37
|
+
Y: "y";
|
|
38
|
+
MMMM: "MMMM";
|
|
39
|
+
MMM: "MMM";
|
|
40
|
+
MM: "MM";
|
|
41
|
+
M: "M";
|
|
42
|
+
DD: "dd";
|
|
43
|
+
D: "d";
|
|
44
|
+
HH: "HH";
|
|
45
|
+
H: "H";
|
|
46
|
+
hh: "hh";
|
|
47
|
+
h: "h";
|
|
48
|
+
mm: "mm";
|
|
49
|
+
m: "m";
|
|
50
|
+
ss: "ss";
|
|
51
|
+
s: "s";
|
|
52
|
+
SSS: "SSS";
|
|
53
|
+
S: "S";
|
|
54
|
+
A: "a";
|
|
55
|
+
a: "a";
|
|
56
|
+
ZZ: "XX";
|
|
57
|
+
Z: "XXX";
|
|
58
|
+
}>;
|
|
59
|
+
/** Documented dayjs tokens this engine will not read, and why. */
|
|
60
|
+
declare const MOMENT_REFUSED: Readonly<{
|
|
61
|
+
SS: "the fraction tokens are tenths (S) and milliseconds (SSS); there is no hundredths field";
|
|
62
|
+
Q: "a quarter is derived from a month, not a field of a date";
|
|
63
|
+
Do: "the ordinal suffix is locale text and this engine ships no ordinal data";
|
|
64
|
+
DDDD: "the day of the year is derived from a date, not a field of one";
|
|
65
|
+
DDD: "the day of the year is derived from a date, not a field of one";
|
|
66
|
+
X: "an epoch second is a whole value, not a calendar field";
|
|
67
|
+
x: "an epoch millisecond is a whole value, not a calendar field";
|
|
68
|
+
ww: "the ISO week number is derived from a date, not a field of one";
|
|
69
|
+
w: "the ISO week number is derived from a date, not a field of one";
|
|
70
|
+
}>;
|
|
71
|
+
/** strftime specifier → the LDML fragment that writes the same field. */
|
|
72
|
+
declare const STRFTIME_TO_LDML: Readonly<{
|
|
73
|
+
Y: "yyyy";
|
|
74
|
+
y: "yy";
|
|
75
|
+
m: "MM";
|
|
76
|
+
d: "dd";
|
|
77
|
+
H: "HH";
|
|
78
|
+
I: "hh";
|
|
79
|
+
M: "mm";
|
|
80
|
+
S: "ss";
|
|
81
|
+
L: "SSS";
|
|
82
|
+
j: "DDD";
|
|
83
|
+
a: "EEE";
|
|
84
|
+
A: "EEEE";
|
|
85
|
+
b: "MMM";
|
|
86
|
+
B: "MMMM";
|
|
87
|
+
p: "a";
|
|
88
|
+
Z: "XX";
|
|
89
|
+
x: "MM'/'dd'/'yyyy";
|
|
90
|
+
X: "HH':'mm':'ss";
|
|
91
|
+
'%': "'%'";
|
|
92
|
+
}>;
|
|
93
|
+
/** Documented strftime specifiers this engine will not write, and why. */
|
|
94
|
+
declare const STRFTIME_REFUSED: Readonly<{
|
|
95
|
+
e: "a space-padded day is a token the formatter does not have";
|
|
96
|
+
c: "it expands to %e, which is unsupported";
|
|
97
|
+
U: "d3 counts weeks from the first Sunday; the core week number is ISO, so the two disagree";
|
|
98
|
+
W: "d3 counts weeks from the first Monday; the core week number is ISO, so the two disagree";
|
|
99
|
+
w: "d3 numbers Sunday 0; the core weekday number is ISO, where 1 is Monday";
|
|
100
|
+
f: "microseconds are below the millisecond this suite measures time in";
|
|
101
|
+
g: "the week-based year is a second year field, and the core parts record has one";
|
|
102
|
+
G: "the week-based year is a second year field, and the core parts record has one";
|
|
103
|
+
q: "a quarter on a time axis is a label, not a tick this engine plans";
|
|
104
|
+
Q: "an epoch is a whole value, not a calendar field";
|
|
105
|
+
s: "an epoch is a whole value, not a calendar field";
|
|
106
|
+
u: "the ISO weekday number is a tick label this engine does not plan";
|
|
107
|
+
V: "the ISO week number is a tick label this engine does not plan";
|
|
108
|
+
}>;
|
|
109
|
+
export type Adapted = {
|
|
110
|
+
value: any;
|
|
111
|
+
error: string | null;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Adapt a Mermaid `dateFormat` to an LDML pattern.
|
|
115
|
+
* @param {string} pattern - a dayjs `customParseFormat` pattern
|
|
116
|
+
* @returns {Adapted} `value` is the LDML pattern
|
|
117
|
+
*/
|
|
118
|
+
export declare function momentToLdml(pattern: string): Adapted;
|
|
119
|
+
/**
|
|
120
|
+
* Adapt a Mermaid `axisFormat` to an LDML pattern. This is a separate
|
|
121
|
+
* grammar from `dateFormat` on purpose: `%m` is a month and `m` is a
|
|
122
|
+
* minute, so one table serving both would silently mis-read half of
|
|
123
|
+
* every axis.
|
|
124
|
+
* @param {string} pattern - a d3-time-format (strftime) pattern
|
|
125
|
+
* @returns {Adapted} `value` is the LDML pattern
|
|
126
|
+
*/
|
|
127
|
+
export declare function strftimeToLdml(pattern: string): Adapted;
|
|
128
|
+
/**
|
|
129
|
+
* Whether an LDML pattern uses a token that needs locale names, and
|
|
130
|
+
* which token that is. Used to turn `compileDateFormat`'s TypeError into
|
|
131
|
+
* a `JM` error that names the Mermaid spelling and the way out.
|
|
132
|
+
* @param {string} ldml
|
|
133
|
+
* @returns {string | null}
|
|
134
|
+
*/
|
|
135
|
+
export declare function ldmlNeedsNames(ldml: string): string | null;
|
|
136
|
+
/**
|
|
137
|
+
* Parse a `tickInterval` directive.
|
|
138
|
+
* @param {string} text
|
|
139
|
+
* @returns {Adapted} `value` is `{ amount, unit }`, the unit a core
|
|
140
|
+
* `DATE_UNITS` member
|
|
141
|
+
*/
|
|
142
|
+
export declare function parseTickInterval(text: string): Adapted;
|
|
143
|
+
/**
|
|
144
|
+
* Parse a task duration (`3d`, `1.5w`, `2M`).
|
|
145
|
+
* @param {string} text
|
|
146
|
+
* @returns {Adapted} `value` is `{ amount, unit }`, or `null` with no
|
|
147
|
+
* error when the text is simply not a duration (the caller then tries
|
|
148
|
+
* to read it as a date)
|
|
149
|
+
*/
|
|
150
|
+
export declare function parseTaskDuration(text: string): Adapted;
|
|
151
|
+
/** ISO weekday numbers, 1 is Monday — the numbering `isoWeekdayFromDays` uses. */
|
|
152
|
+
export declare const ISO_WEEKDAYS: Readonly<{
|
|
153
|
+
monday: 1;
|
|
154
|
+
tuesday: 2;
|
|
155
|
+
wednesday: 3;
|
|
156
|
+
thursday: 4;
|
|
157
|
+
friday: 5;
|
|
158
|
+
saturday: 6;
|
|
159
|
+
sunday: 7;
|
|
160
|
+
}>;
|
|
161
|
+
/**
|
|
162
|
+
* Parse a `weekday` directive: which day a whole-week tick starts on.
|
|
163
|
+
* @param {string} text
|
|
164
|
+
* @returns {Adapted} `value` is an ISO weekday, 1 to 7
|
|
165
|
+
*/
|
|
166
|
+
export declare function parseWeekday(text: string): Adapted;
|
|
167
|
+
/**
|
|
168
|
+
* Parse a `weekend` directive: the first of the two days
|
|
169
|
+
* `excludes weekends` removes.
|
|
170
|
+
* @param {string} text
|
|
171
|
+
* @returns {Adapted} `value` is an ISO weekday, 5 (friday) or 6 (saturday)
|
|
172
|
+
*/
|
|
173
|
+
export declare function parseWeekend(text: string): Adapted;
|
|
174
|
+
export { MOMENT_TO_LDML, MOMENT_REFUSED, STRFTIME_TO_LDML, STRFTIME_REFUSED };
|
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file Gantt grammar → gantt AST: a schedule
|
|
3
|
-
*
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
2
|
+
* @file Gantt grammar → gantt AST: a schedule, resolved.
|
|
3
|
+
*
|
|
4
|
+
* Header directives (`title`, `dateFormat`, `axisFormat`, `excludes`,
|
|
5
|
+
* `tickInterval`, `weekday`, `weekend`, `todayMarker`) are kept verbatim
|
|
6
|
+
* in `meta` so the printer stays a fixed point, and are *also*
|
|
7
|
+
* interpreted into `rules` — a compiled date parser, an axis pattern, a
|
|
8
|
+
* tick step, a working calendar. `section` groups tasks; a task row
|
|
9
|
+
* keeps its raw metadata string (`:done, id, 2014-01-06, 3d`) in `info`
|
|
10
|
+
* for the same printing reason and gains the semantic members a
|
|
11
|
+
* timeline needs: `{ id, flags, start, end, duration, after, line }`,
|
|
12
|
+
* with `start`/`end` epoch milliseconds and the interval half-open.
|
|
13
|
+
*
|
|
14
|
+
* The AST stays geometry-free and plain JSON: no compiled closure and
|
|
15
|
+
* no coordinate crosses this boundary. Layout re-compiles the axis
|
|
16
|
+
* pattern from `rules`, once per diagram.
|
|
17
|
+
*
|
|
18
|
+
* Three refusals are the whole difference from a permissive reader, and
|
|
19
|
+
* each one is a `JM` error carrying its source line:
|
|
20
|
+
*
|
|
21
|
+
* 1. **No clock.** Mermaid starts an undated first task *today*. This
|
|
22
|
+
* engine has no clock (`docs/ROADMAP.md`, "there is no now"), so a
|
|
23
|
+
* schedule with no dated anchor is an error rather than a diagram
|
|
24
|
+
* that means something different tomorrow.
|
|
25
|
+
* 2. **No silent literals.** An unsupported `dateFormat` or
|
|
26
|
+
* `axisFormat` token is refused by name; Mermaid passes it through as
|
|
27
|
+
* literal text, which turns a typo into a date that reads as
|
|
28
|
+
* something else.
|
|
29
|
+
* 3. **No guessed dependencies.** A missing id, a duplicate id, a cycle
|
|
30
|
+
* and a reversed or empty span are all errors.
|
|
6
31
|
*/
|
|
7
32
|
/**
|
|
8
33
|
* @param {string[]} lines
|
|
34
|
+
* @param {number} [offset] - 0-based index of `lines[0]` in the source
|
|
35
|
+
* @param {string} [_header]
|
|
36
|
+
* @param {{ dateNames?: import('@jarenjs/core/dates').DateNames }} [options]
|
|
9
37
|
* @returns {object}
|
|
10
38
|
*/
|
|
11
|
-
export declare function parseGantt(lines: string[]
|
|
39
|
+
export declare function parseGantt(lines: string[], offset?: number, _header?: string, options?: {
|
|
40
|
+
dateNames?: import('@jarenjs/core/dates').DateNames;
|
|
41
|
+
}): object;
|
|
@@ -14,9 +14,13 @@ export declare const SECONDARY_TYPES: Set<string>;
|
|
|
14
14
|
/**
|
|
15
15
|
* Parse Mermaid source into a `DiagramDocument`.
|
|
16
16
|
* @param {string} source
|
|
17
|
-
* @param {{ parseFrontmatter?: (text: string) => any
|
|
17
|
+
* @param {{ parseFrontmatter?: (text: string) => any,
|
|
18
|
+
* dateNames?: import('@jarenjs/core/dates').DateNames }} [options] -
|
|
19
|
+
* `dateNames` is the locale-name record a Gantt's `dateFormat` needs
|
|
20
|
+
* for a month name token; this engine ships none of its own
|
|
18
21
|
* @returns {import('../ast.js').DiagramDocument}
|
|
19
22
|
*/
|
|
20
23
|
export declare function parseMermaid(source: string, options?: {
|
|
21
24
|
parseFrontmatter?: (text: string) => any;
|
|
25
|
+
dateNames?: import('@jarenjs/core/dates').DateNames;
|
|
22
26
|
}): import('../ast.js').DiagramDocument;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Gantt renderer: a positioned schedule → pure-vnode SVG. A real
|
|
3
|
+
* timeline — an aligned time axis, section bands, task bars, milestone
|
|
4
|
+
* diamonds, dependency connectors and shaded excluded days — where the
|
|
5
|
+
* engine used to draw a list of strings in a box.
|
|
6
|
+
*
|
|
7
|
+
* It imports `@jarenjs/core` and `@jarenjs/view` and nothing else: the
|
|
8
|
+
* tick ladder is core's, so this file acquires no dependency on the
|
|
9
|
+
* chart component to draw a time axis.
|
|
10
|
+
*
|
|
11
|
+
* Accessibility: the root carries a `<title>` (the diagram's own title,
|
|
12
|
+
* or a generic one) and a `<desc>` naming the span and the task count,
|
|
13
|
+
* so a screen reader gets the shape of the schedule rather than a
|
|
14
|
+
* hundred unlabelled rectangles. Bars carry `data-id` and a status
|
|
15
|
+
* class, which is also what an interactive host hit-tests on.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @param {any} scene PositionedDiagram (gantt)
|
|
19
|
+
* @param {{ tokens: Record<string,string>, cssVars: Record<string,string> }} theme
|
|
20
|
+
* @param {string} hash
|
|
21
|
+
* @returns {any}
|
|
22
|
+
*/
|
|
23
|
+
export declare function renderGantt(scene: any, theme: {
|
|
24
|
+
tokens: Record<string, string>;
|
|
25
|
+
cssVars: Record<string, string>;
|
|
26
|
+
}, hash: string): any;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @file Renderers for the first-class types beyond flowchart and
|
|
3
3
|
* sequence, and an honest placeholder for the deferred secondary
|
|
4
4
|
* types. Pie is
|
|
5
|
-
* a real chart; class
|
|
5
|
+
* a real chart; class and ER render as structured panels — a
|
|
6
6
|
* readable, geometry-light view that renders without error and so counts
|
|
7
7
|
* honestly in the coverage scorecard. Secondary types (mindmap,
|
|
8
8
|
* gitGraph, journey, timeline) render a labeled "not yet laid out"
|
package/docs/MERMAID-FORMAT.md
CHANGED
|
@@ -105,7 +105,7 @@ whose grammar permits those characters in ids.
|
|
|
105
105
|
(`{ kind:'block', blockType, branches:[{ label, statements[] }] }`) for
|
|
106
106
|
loop/opt/alt/par/critical/break.
|
|
107
107
|
|
|
108
|
-
### 4.3 class / er / state /
|
|
108
|
+
### 4.3 class / er / state / pie
|
|
109
109
|
|
|
110
110
|
Class: `{ classes:[{ name, label, members[] }], relations[] }`. ER:
|
|
111
111
|
`{ entities:[{ name, attributes[] }], relationships[] }`. State:
|
|
@@ -118,10 +118,112 @@ the guard (nesting counted), the effect starts at the first `/` after
|
|
|
118
118
|
it (or the first `/` at all when there is no guard), and a label that
|
|
119
119
|
fits no pattern — an unmatched `[`, or text between `]` and `/` — reads
|
|
120
120
|
whole as the event, which keeps plain labels meaning what they always
|
|
121
|
-
meant.
|
|
122
|
-
`{ title, showData, slices:[{ label, value }] }`.
|
|
121
|
+
meant. Pie: `{ title, showData, slices:[{ label, value }] }`.
|
|
123
122
|
|
|
124
|
-
|
|
123
|
+
Gantt is `{ meta, rules, sections:[{ name, tasks[] }], domain }` and is
|
|
124
|
+
described in §4.4, because it is the one AST that carries a *resolved*
|
|
125
|
+
answer rather than only what the source said.
|
|
126
|
+
|
|
127
|
+
### 4.4 gantt — a resolved schedule
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
{ meta, rules, sections:[{ name, tasks[] }], domain:{ start, end } }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`meta` holds the header lines **verbatim** (`title`, `dateFormat`,
|
|
134
|
+
`axisFormat`, `excludes`, `tickInterval`, `weekday`, `weekend`,
|
|
135
|
+
`todayMarker`), which is what keeps `printGantt` a fixed point. `rules`
|
|
136
|
+
holds the same directives *interpreted*:
|
|
137
|
+
`{ dateFormat, axisFormat, tick, weekStart, weekendStart, excludes,
|
|
138
|
+
todayMarker }`, where `tick` is `{ amount, unit }` or `null`,
|
|
139
|
+
`weekStart`/`weekendStart` are ISO weekdays (1 is Monday), and
|
|
140
|
+
`excludes` is `{ weekends, weekdays[], days[] }` with `days` as day
|
|
141
|
+
indexes from 1970-01-01.
|
|
142
|
+
|
|
143
|
+
A task is
|
|
144
|
+
`{ name, info, id, flags, start, end, duration, after, line }`. `info`
|
|
145
|
+
is the raw metadata string the printer emits; the rest is the schedule.
|
|
146
|
+
`start`/`end` are epoch milliseconds and the interval is **half-open**,
|
|
147
|
+
so `end` is the first instant the task no longer occupies — except a
|
|
148
|
+
milestone, which is the instant `start === end`. `flags` is the subset
|
|
149
|
+
of `done`, `active`, `crit`, `milestone` in that order. `domain` is the
|
|
150
|
+
half-open span every task falls inside, and it is what layout scales
|
|
151
|
+
against.
|
|
152
|
+
|
|
153
|
+
The AST stays geometry-free and plain JSON: no compiled closure and no
|
|
154
|
+
coordinate crosses this boundary. `layout/gantt.js` re-compiles the axis
|
|
155
|
+
pattern from `rules`, once per diagram.
|
|
156
|
+
|
|
157
|
+
#### The three grammars
|
|
158
|
+
|
|
159
|
+
A Gantt header speaks three pattern languages and **none of them is
|
|
160
|
+
Unicode LDML**, so each gets its own tokenizer and each token is mapped
|
|
161
|
+
individually onto the core vocabulary (`src/parser/gantt-grammar.js`):
|
|
162
|
+
|
|
163
|
+
| Directive | Grammar | Example |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| `dateFormat` | dayjs `customParseFormat` (moment's spelling) | `DD-MM-YYYY` |
|
|
166
|
+
| `axisFormat` | d3-time-format (strftime) | `%Y-%m-%d` |
|
|
167
|
+
| `tickInterval` | `^([1-9]\d*)(millisecond\|second\|minute\|hour\|day\|week\|month)$` | `1week` |
|
|
168
|
+
| a task duration | `^(\d+(?:\.\d+)?)([Mdhmswy]\|ms)$` | `3d`, `1.5w`, `2M` |
|
|
169
|
+
|
|
170
|
+
Handing `YYYY` straight to the core compiler would be a silent lie —
|
|
171
|
+
LDML's `YYYY` is the *week-numbering* year, so `YYYY-MM-DD` read as LDML
|
|
172
|
+
answers 2019 for `2018-12-31`. Note also that moment's `m` is a minute
|
|
173
|
+
while strftime's `%m` is a month: one shared table would mis-read half
|
|
174
|
+
of every diagram.
|
|
175
|
+
|
|
176
|
+
An **unsupported** token is a `JM` error naming the token and the
|
|
177
|
+
reason, not a literal passed through. The vendored conformance table —
|
|
178
|
+
every documented token of the Mermaid version this engine tracks, marked
|
|
179
|
+
supported or refused with its reason, plus the provenance of where it
|
|
180
|
+
was read — is `test/mermaid/fixtures/gantt-grammar.json`, and
|
|
181
|
+
`test/mermaid/gantt.test.js` pins the code against it in both
|
|
182
|
+
directions.
|
|
183
|
+
|
|
184
|
+
Both directions is a closed loop, so the table is also checked against
|
|
185
|
+
the shipped chunk it was read from: dayjs' parse table is inlined there,
|
|
186
|
+
and every token Mermaid really accepts must appear. A token in neither
|
|
187
|
+
list is not refused — it becomes literal text, and then the diagram's
|
|
188
|
+
own dates are what look wrong. `w`, `ww` and `Y` reached the tree that
|
|
189
|
+
way; the week numbers are now refused by name (a week number is derived
|
|
190
|
+
from a date rather than a field of one, which is why `compileDateParser`
|
|
191
|
+
will not read one either) and `Y` is the signed variable-width year
|
|
192
|
+
core's `y` already spells.
|
|
193
|
+
|
|
194
|
+
#### Task forms and working days
|
|
195
|
+
|
|
196
|
+
The field-count rule is Mermaid's own: one field is an end, two are a
|
|
197
|
+
start and an end, three add an explicit id in front, and the four flags
|
|
198
|
+
lead. `after <id …>` starts a task at the latest end of those ids;
|
|
199
|
+
`until <id …>` ends it at the earliest start of those. Ids may be
|
|
200
|
+
forward references — resolution is a topological pass — and a cycle, a
|
|
201
|
+
missing id, a duplicate id, a reversed span and an empty non-milestone
|
|
202
|
+
span are all refused with the offending line.
|
|
203
|
+
|
|
204
|
+
`excludes` removes whole days: `weekends` (the `weekend` day and the one
|
|
205
|
+
after it, saturday by default), weekday names, and dates written in the
|
|
206
|
+
document's own `dateFormat`. An end derived from a *duration* is pushed
|
|
207
|
+
out one day per excluded day it crosses; an end written as an explicit
|
|
208
|
+
*date* is the author's answer and is never pushed. The probes are the
|
|
209
|
+
instants `start + k days`, so a task starting Friday noon and lasting a
|
|
210
|
+
day ends Monday noon. Days are UTC days. This is not a holiday service:
|
|
211
|
+
a schedule skips exactly what its own `excludes` line lists.
|
|
212
|
+
|
|
213
|
+
#### Three deliberate divergences from Mermaid
|
|
214
|
+
|
|
215
|
+
1. **No clock.** Mermaid starts an undated first task *today*. This
|
|
216
|
+
engine has no clock anywhere (`docs/ROADMAP.md`, "there is no now"),
|
|
217
|
+
so a schedule with no dated anchor is a `JM` error rather than a
|
|
218
|
+
diagram that means something different tomorrow. In practice only the
|
|
219
|
+
first task needs a date. For the same reason `todayMarker` is kept
|
|
220
|
+
in `meta` and never drawn.
|
|
221
|
+
2. **No silent literals** — an unsupported pattern token is refused by
|
|
222
|
+
name, where Mermaid passes it through as text.
|
|
223
|
+
3. **No guessed dependencies** — Mermaid falls back to today for an
|
|
224
|
+
`after` naming an unknown id; this engine refuses.
|
|
225
|
+
|
|
226
|
+
### 4.5 Secondary types
|
|
125
227
|
|
|
126
228
|
mindmap, gitGraph, journey, timeline, quadrantChart, requirement
|
|
127
229
|
parse-accept into `{ diagram, lines[] }` and render an honest "not yet
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jarenjs/mermaid",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.49.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./dist/types/index.d.ts",
|
|
@@ -77,8 +77,8 @@
|
|
|
77
77
|
"prepack": "npm run build:types"
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
|
-
"@jarenjs/charts": "^0.
|
|
81
|
-
"@jarenjs/core": "^0.
|
|
82
|
-
"@jarenjs/view": "^0.
|
|
80
|
+
"@jarenjs/charts": "^0.49.2",
|
|
81
|
+
"@jarenjs/core": "^0.49.2",
|
|
82
|
+
"@jarenjs/view": "^0.49.2"
|
|
83
83
|
}
|
|
84
84
|
}
|