@cahyo-dimas/freeday 1.50.0 → 1.51.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/CHANGELOG.md +72 -0
- package/COMPONENTS.md +31 -1
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/dist/freeday-chart.js +62 -28
- package/dist/freeday.bundle.css +4 -4
- package/dist/freeday.css +4 -4
- package/dist/freeday.js +62 -28
- package/docs/reference-screen.html +0 -5
- package/package.json +2 -2
- package/src/components/chart.css +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,78 @@
|
|
|
3
3
|
Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); tiap versi = git tag.
|
|
5
5
|
|
|
6
|
+
## [1.51.1] — 2026-08-24
|
|
7
|
+
### Docs
|
|
8
|
+
- **`COMPONENTS.md` never said whether two overlays may be open at once** (#046). Modal and Drawer
|
|
9
|
+
were documented in isolation, so "can a confirm modal open over an open drawer?" had no answer in
|
|
10
|
+
the file — and the built CSS does not answer it either: the only overlay `z-index` in the bundle
|
|
11
|
+
belongs to the toast region. Both entries now state the rule, because both components are
|
|
12
|
+
`<dialog>` opened with `showModal()` and therefore enter the **top layer**, where paint order is
|
|
13
|
+
the order they were opened: the later modal is above the drawer with no `z-index` from either,
|
|
14
|
+
Escape closes the topmost first (so cancelling returns to the still-open drawer), and closing them
|
|
15
|
+
out of order leaves the survivor painted and dismissible.
|
|
16
|
+
- **The one caveat is the browser's, and it is now written down**: open each overlay from the
|
|
17
|
+
gesture that asked for it. A `showModal()` with no user activation — from a timer, or after an
|
|
18
|
+
`await` that outlived the click — has its close watcher GROUPED with the dialog below it, and a
|
|
19
|
+
single Escape then closes both. Measured, not assumed: two untrusted `.click()` opens, one Escape,
|
|
20
|
+
both dialogs shut.
|
|
21
|
+
### Added — guards
|
|
22
|
+
- `browser/overlay-stack.mjs` (3 tests) proves that paragraph instead of restating it — the #041
|
|
23
|
+
lesson applied to a documentation change. Paint order is asserted in **pixels** at the overlap of
|
|
24
|
+
the two, because a modal dialog makes the rest of the document inert and `elementFromPoint` will
|
|
25
|
+
name the dialog whatever is really on top; the overlays are opened with **trusted** clicks, or the
|
|
26
|
+
close-watcher grouping above would fail the Escape assertion for a reason that is not the kit's.
|
|
27
|
+
- **`pressKey` in the browser harness sent every key but Tab with virtual-key code 0** — delivered
|
|
28
|
+
to the page, but not acted on by the browser: Escape reached a `keydown` listener yet never closed
|
|
29
|
+
a native `<dialog>`. A dismissal guard written with it would have passed for the wrong reason and
|
|
30
|
+
reported a broken kit as fine. It now carries the real `windowsVirtualKeyCode` /
|
|
31
|
+
`nativeVirtualKeyCode` for the keys the suite presses.
|
|
32
|
+
|
|
33
|
+
## [1.51.0] — 2026-08-21
|
|
34
|
+
### Fixed
|
|
35
|
+
- **A cartesian chart's axis text and dots grew with the container** (#042). `renderCartesian` built
|
|
36
|
+
a fixed `viewBox="0 0 320 180"` and let `.fdy-chart-xy__plot` stretch it to `width:100%`, so every
|
|
37
|
+
size the renderer expressed in user units was multiplied by `plotWidth / 320`. `font-size:9px` on
|
|
38
|
+
a tick is not nine screen pixels at any width but 320px: measured on a real console it rendered
|
|
39
|
+
13px at a 350px plot and **81px at a 2232px** one, making the chart's smallest, most secondary
|
|
40
|
+
text the largest type on the page. `r="2.2"` did the same to the dots — 4.8px across at 350px,
|
|
41
|
+
30.7px at 2232px. The chart now measures `__plot` and sizes the `viewBox` to it in CSS pixels, so
|
|
42
|
+
one user unit is one pixel and every declared size means what it says at every width; a
|
|
43
|
+
`ResizeObserver` repaints it when the box changes (a collapsing sidebar moves a plot ~300px at a
|
|
44
|
+
fixed viewport, so this is the common case, not an edge case). A chart that measures 0 — detached,
|
|
45
|
+
or inside `display:none` — falls back to the old box and repaints when it is laid out.
|
|
46
|
+
- **A currency y-axis was clipped by a gutter sized for a percent one** (#042). `PL` was a constant
|
|
47
|
+
38 user units, which was ~4 characters at one scale and something else at every other. It is now
|
|
48
|
+
derived from the widest formatted tick the axis will actually draw, capped at 35% of the plot.
|
|
49
|
+
### Changed
|
|
50
|
+
- **Axis type is a real CSS size**, set by `--fdy-chart-tick-size` on `.fdy-chart-xy` (default
|
|
51
|
+
`var(--text-xs)`). `font-size` moved off `.fdy-chart-xy__tick` / `__xlabel` onto
|
|
52
|
+
`.fdy-chart-xy__plot`, which the SVG text inherits — one computed read gives the renderer the real
|
|
53
|
+
pixel size it needs for the y-gutter and for the x-label autoskip, so an override flows to the
|
|
54
|
+
text *and* to the geometry around it. Before this, chart text could not be restyled at all: any
|
|
55
|
+
value set was multiplied by a scale the consumer could not see or compute.
|
|
56
|
+
The dot radius went 2.2 → 3 and the bar corner 1.5 → 2, both retuned against a 1:1 scale.
|
|
57
|
+
### Docs
|
|
58
|
+
- **`COMPONENTS.md` claimed a `<table>` fallback that no rendered chart has** (#041). It said every
|
|
59
|
+
chart is `role="img"` + `aria-label` "with a `<table>` fallback inside the element"; the renderer
|
|
60
|
+
builds no table and wipes the element on every render path. Worse, `role="img"` makes descendants
|
|
61
|
+
presentational, so the bar values, ticks and legend the renderer *does* draw are not exposed
|
|
62
|
+
either — the author's `aria-label` is the entire text alternative, and the docs now say so and ask
|
|
63
|
+
for one that carries the numbers. The four dead fallback tables in `docs/index.html` and
|
|
64
|
+
`docs/reference-screen.html`, which no reader has ever reached, are gone with it.
|
|
65
|
+
- **The kit's own chart sizing is written down** (#041): `.fdy-chart-xy__plot` is
|
|
66
|
+
`aspect-ratio:16/9` + `min-height:8rem`, `.fdy-bars` a fixed `9rem`, `.fdy-sparkline` an
|
|
67
|
+
inline-block `8rem × 2.25rem` — with the note to override those rather than the chart root, since
|
|
68
|
+
a root `height` fights `aspect-ratio` instead of setting it.
|
|
69
|
+
### Added — guards
|
|
70
|
+
- **`browser/chart-scale.mjs`** — the axis label box and dot diameter are measured at plot widths of
|
|
71
|
+
350 / 696 / 1400 / 2232px and must not vary by more than a pixel, must stay smaller than body
|
|
72
|
+
text, and the `viewBox` must equal the measured plot. A second spec widens a container and asserts
|
|
73
|
+
the repaint. This class of bug is invisible to a source read: `font-size:9px` and `r="2.2"` look
|
|
74
|
+
like correct sizes, and the one thing in the plot that *was* protected —
|
|
75
|
+
`vector-effect:non-scaling-stroke` on the line — is what made the other three look protected too.
|
|
76
|
+
Only a real layout tells them apart.
|
|
77
|
+
|
|
6
78
|
## [1.50.0] — 2026-08-20
|
|
7
79
|
### Fixed
|
|
8
80
|
- **A toast raised from inside a modal was painted behind it** (#027). `.fdy-toast-region` carried
|
package/COMPONENTS.md
CHANGED
|
@@ -858,7 +858,20 @@ Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`
|
|
|
858
858
|
validated categorical palette `--chart-1`…`--chart-8`.
|
|
859
859
|
- Format: `data-fdy-format="number|percent|currency"`; legend `data-fdy-legend` (`none` to drop);
|
|
860
860
|
axes `data-fdy-axes`
|
|
861
|
-
-
|
|
861
|
+
- Sizing is already set by the kit — override **these**, never a `height` on the chart root (a
|
|
862
|
+
root height fights `aspect-ratio` instead of setting it): `.fdy-chart-xy__plot` is `width:100%`
|
|
863
|
+
+ `aspect-ratio:16/9` + `min-height:8rem`; `.fdy-bars` is a fixed `height:9rem`; `.fdy-sparkline`
|
|
864
|
+
is an inline-block `8rem × 2.25rem`.
|
|
865
|
+
- Axis type: `--fdy-chart-tick-size` (default `var(--text-xs)`) sets the y-tick and x-label size on
|
|
866
|
+
`.fdy-chart-xy`. It is a real CSS size — a cartesian chart's `viewBox` is measured to its plot, so
|
|
867
|
+
one user unit is one pixel — and the renderer reads it back to size the y-gutter and to decide how
|
|
868
|
+
many x-labels fit. Set the token rather than `font-size` on `__tick`/`__xlabel`, or the gutter and
|
|
869
|
+
the autoskip will still be computed from the old size.
|
|
870
|
+
- A11y: every chart is `role="img"` + an **author-supplied** `aria-label`, and that label is the
|
|
871
|
+
*entire* text alternative — write one that carries the numbers, not just the shape. `role="img"`
|
|
872
|
+
makes descendants presentational, so rendered bar values, axis ticks and legends are not exposed
|
|
873
|
+
either; any `<table>`/text placed inside the element is **pre-render only** and is replaced on
|
|
874
|
+
render. Good: `aria-label="Monthly posting trend: Posted 62% → 88%, Draft 24% → 16%"`.
|
|
862
875
|
- Internal parts (set by JS, useful for overrides): `.fdy-chart__legend` (+`--row`) `__swatch`
|
|
863
876
|
`__tip`; `.fdy-bars__col` `__bar` `__track` `__label` `__val`; `.fdy-chart-xy__plot` `__grid`
|
|
864
877
|
`__tick` `__xlabel` `__line` `__area` `__bar` `__dot` `__band`; `.fdy-donut__ring` `__seg`
|
|
@@ -1036,6 +1049,18 @@ painted on top of a modal, because it will tell you the dialog is, even when it
|
|
|
1036
1049
|
`__close`. The body scrolls; the footer never clips. `aria-labelledby` → the title's `id`;
|
|
1037
1050
|
`data-close` on any button that should close it.
|
|
1038
1051
|
|
|
1052
|
+
**Stacking two overlays is supported.** Modal and drawer are both `<dialog>` opened with
|
|
1053
|
+
`showModal()`, so both enter the top layer and paint in the order they were opened — a confirm
|
|
1054
|
+
modal opened from inside an open drawer paints **above** it, and neither needs a `z-index` for
|
|
1055
|
+
that. Escape closes the topmost first, so cancelling returns to the still-open drawer; a second
|
|
1056
|
+
Escape then closes the drawer. Closing them out of order is fine too: closing the drawer while the
|
|
1057
|
+
modal is up leaves the modal open, painted and dismissible.
|
|
1058
|
+
|
|
1059
|
+
One caveat, and it is the browser's rather than the kit's: **open each overlay from the gesture
|
|
1060
|
+
that asked for it.** A `showModal()` that runs with no user activation — from a timer, or after an
|
|
1061
|
+
`await` that outlived the click — has its close watcher grouped with the dialog below it, and then
|
|
1062
|
+
a single Escape closes both at once instead of the topmost only.
|
|
1063
|
+
|
|
1039
1064
|
## Drawer — `.fdy-drawer`
|
|
1040
1065
|
> **Typed wrapper: `<FdyDrawer>`** — Vue (`:open` + `@close`) · React (`open` + `onClose`) · Blazor (`@bind-Open`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
1041
1066
|
|
|
@@ -1043,6 +1068,11 @@ Temporary side panel on native `<dialog>` — left by default, `--right` to flip
|
|
|
1043
1068
|
`__title` `__body` `__footer` `__close`. Open it from any
|
|
1044
1069
|
`<button data-fdy-drawer="<dialog id>">`. Needs `freeday-drawer.js`.
|
|
1045
1070
|
|
|
1071
|
+
Opened with `showModal()`, so it is in the **top layer** on the same terms as the modal — including
|
|
1072
|
+
opening a modal over an open drawer, which is supported and described under
|
|
1073
|
+
[Modal → *Stacking two overlays*](#modal--fdy-modal). It slides in on `transform` and never animates
|
|
1074
|
+
opacity: wait on its position, not on `opacity`, if you need to know when it has arrived.
|
|
1075
|
+
|
|
1046
1076
|
## Accordion — `.fdy-accordion`
|
|
1047
1077
|
Native `<details>`, zero JS: `.fdy-accordion__item` on each `<details>`, content in
|
|
1048
1078
|
`.fdy-accordion__panel`, the trigger is the `<summary>`.
|
package/README.id.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **Lebih banyak _free day_ buat dev — UI kit-nya sudah siap pakai.**
|
|
6
6
|
|
|
7
7
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.51.1)
|
|
9
9
|
|
|
10
10
|
UI KIT yang token-driven & framework-agnostic — satu sumber kebenaran untuk warna, tipografi,
|
|
11
11
|
spasi, dan komponen. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **More free days for devs — the UI kit is ready to use.**
|
|
6
6
|
|
|
7
7
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.51.1)
|
|
9
9
|
|
|
10
10
|
A token-driven, framework-agnostic UI kit — one source of truth for color, typography,
|
|
11
11
|
spacing, and components. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
|
package/dist/freeday-chart.js
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* (var(--color-<name>)) OR a slot "chart-1".."chart-8" (var(--chart-N)) to pin a series to the
|
|
11
11
|
* validated palette. Both data-fdy-color and data-fdy-colors accept the chart-N form.
|
|
12
12
|
* Cartesian charts (line/area, multi-series/stacked bar) draw themed axes from --chart-grid /
|
|
13
|
-
* --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency).
|
|
13
|
+
* --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency). Their
|
|
14
|
+
* viewBox is sized to the measured plot (1 user unit = 1 CSS pixel) and repainted from a
|
|
15
|
+
* ResizeObserver, so axis type stays in real pixels and is set in CSS via --fdy-chart-tick-size.
|
|
14
16
|
* Charts are an enhancement — put a table/text fallback inside the element and it will be
|
|
15
17
|
* replaced on render; give the element role="img" + aria-label.
|
|
16
18
|
*
|
|
@@ -50,6 +52,18 @@
|
|
|
50
52
|
function ensureImg(el) { if (!el.hasAttribute('role')) el.setAttribute('role', 'img'); }
|
|
51
53
|
function svgEl(name) { return document.createElementNS(NS, name); }
|
|
52
54
|
|
|
55
|
+
// Cartesian charts size their viewBox to the measured plot, so a width change needs a repaint.
|
|
56
|
+
// Guarded on width alone: a render can change the element's own height (the legend wrapping to a
|
|
57
|
+
// second row) but never its own width, so this settles instead of looping.
|
|
58
|
+
var cartesianRO = typeof ResizeObserver === 'function' ? new ResizeObserver(function (entries) {
|
|
59
|
+
entries.forEach(function (entry) {
|
|
60
|
+
var target = entry.target;
|
|
61
|
+
if (!target.isConnected) { cartesianRO.unobserve(target); return; }
|
|
62
|
+
var w = Math.round(entry.contentRect.width);
|
|
63
|
+
if (w && w !== target.__fdyChartW) renderChart(target);
|
|
64
|
+
});
|
|
65
|
+
}) : null;
|
|
66
|
+
|
|
53
67
|
// Multi-series model: data-series (JSON) wins; else the data-values single-series shortcut.
|
|
54
68
|
function getSeries(el) {
|
|
55
69
|
var raw = el.getAttribute('data-series');
|
|
@@ -194,7 +208,46 @@
|
|
|
194
208
|
var top = niceCeil(dataMax > base ? dataMax : base + 1);
|
|
195
209
|
if (top <= base) top = base + 1;
|
|
196
210
|
|
|
197
|
-
|
|
211
|
+
// Lay the chrome out first so the plot can be measured, then size the viewBox to it in CSS
|
|
212
|
+
// pixels: one user unit is one pixel, so font-size / r / stroke-width mean what they say at
|
|
213
|
+
// every container width. (A fixed 320x180 viewBox stretched by CSS multiplied every declared
|
|
214
|
+
// size by plotWidth/320 instead — a 9px axis label rendered ~39px on a 1728px viewport.)
|
|
215
|
+
var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
|
|
216
|
+
var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
|
|
217
|
+
el.classList.add('fdy-chart-xy');
|
|
218
|
+
el.innerHTML = '';
|
|
219
|
+
if (showLegend) {
|
|
220
|
+
var legend = document.createElement('ul');
|
|
221
|
+
legend.className = 'fdy-chart__legend fdy-chart__legend--row';
|
|
222
|
+
series.forEach(function (s, si) {
|
|
223
|
+
var li = document.createElement('li');
|
|
224
|
+
var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorFor(si);
|
|
225
|
+
li.appendChild(sw);
|
|
226
|
+
li.appendChild(document.createTextNode(s.label || ('Seri ' + (si + 1))));
|
|
227
|
+
legend.appendChild(li);
|
|
228
|
+
});
|
|
229
|
+
el.appendChild(legend);
|
|
230
|
+
}
|
|
231
|
+
var plot = document.createElement('div'); plot.className = 'fdy-chart-xy__plot';
|
|
232
|
+
el.appendChild(plot);
|
|
233
|
+
|
|
234
|
+
// Measured while empty: .fdy-chart-xy__plot carries its own width / aspect-ratio / min-height,
|
|
235
|
+
// so the box is already final. A detached or display:none chart measures 0 — fall back to the
|
|
236
|
+
// legacy box and let the ResizeObserver repaint it once it is actually laid out.
|
|
237
|
+
var box = plot.getBoundingClientRect();
|
|
238
|
+
var W = Math.round(box.width) || 320, H = Math.round(box.height) || 180;
|
|
239
|
+
var FS = parseFloat(getComputedStyle(plot).fontSize) || 12; // axis type size, owned by CSS
|
|
240
|
+
var charW = FS * 0.6; // mono/body advance is ~0.6em
|
|
241
|
+
|
|
242
|
+
// Tick values are known before the gutter is, so size PL to the widest one: a currency axis
|
|
243
|
+
// needs more room than a percent axis, and a fixed gutter only ever suited one of them.
|
|
244
|
+
var TICKS = 4;
|
|
245
|
+
var tickText = [];
|
|
246
|
+
for (var t0 = 0; t0 <= TICKS; t0++) tickText.push(fmt(base + (top - base) * t0 / TICKS));
|
|
247
|
+
var widestTick = 0;
|
|
248
|
+
tickText.forEach(function (t) { widestTick = Math.max(widestTick, t.length * charW); });
|
|
249
|
+
var PL = Math.min(Math.ceil(widestTick) + 10, Math.round(W * 0.35));
|
|
250
|
+
var PR = 10, PT = 10, PB = Math.round(FS * 2.2);
|
|
198
251
|
var plotW = W - PL - PR, plotH = H - PT - PB;
|
|
199
252
|
function mapY(v) { return PT + plotH * (1 - (v - base) / (top - base)); }
|
|
200
253
|
var slot = plotW / n;
|
|
@@ -206,7 +259,6 @@
|
|
|
206
259
|
svg.setAttribute('aria-hidden', 'true');
|
|
207
260
|
|
|
208
261
|
// y gridlines + formatted ticks (bottom line at `base` doubles as the zero baseline).
|
|
209
|
-
var TICKS = 4;
|
|
210
262
|
for (var k = 0; k <= TICKS; k++) {
|
|
211
263
|
var val = base + (top - base) * k / TICKS;
|
|
212
264
|
var gy = mapY(val);
|
|
@@ -216,9 +268,9 @@
|
|
|
216
268
|
gl.setAttribute('class', 'fdy-chart-xy__grid');
|
|
217
269
|
svg.appendChild(gl);
|
|
218
270
|
var tk = svgEl('text');
|
|
219
|
-
tk.setAttribute('x', PL - 5); tk.setAttribute('y', (gy +
|
|
271
|
+
tk.setAttribute('x', PL - 5); tk.setAttribute('y', (gy + FS * 0.35).toFixed(1));
|
|
220
272
|
tk.setAttribute('text-anchor', 'end'); tk.setAttribute('class', 'fdy-chart-xy__tick');
|
|
221
|
-
tk.textContent =
|
|
273
|
+
tk.textContent = tickText[k];
|
|
222
274
|
svg.appendChild(tk);
|
|
223
275
|
}
|
|
224
276
|
|
|
@@ -226,7 +278,6 @@
|
|
|
226
278
|
// of assuming a fixed 40px slot; keep every step-th plus the last, then drop any kept label that
|
|
227
279
|
// would collide with the one before it — including the forced last one (a fraction-of-a-slot
|
|
228
280
|
// overlap happens when n-1 lands next to the last kept multiple of step, e.g. n=24 at most widths).
|
|
229
|
-
var charW = 5.4; // ~9px font advance (.fdy-chart-xy__xlabel)
|
|
230
281
|
var widest = 0;
|
|
231
282
|
for (var wi = 0; wi < n; wi++) widest = Math.max(widest, String(labels[wi] || ('#' + (wi + 1))).length * charW);
|
|
232
283
|
var minGap = widest + 8;
|
|
@@ -242,7 +293,7 @@
|
|
|
242
293
|
}
|
|
243
294
|
kept.forEach(function (xk) {
|
|
244
295
|
var xt = svgEl('text');
|
|
245
|
-
xt.setAttribute('x', xPos(xk).toFixed(1)); xt.setAttribute('y', H -
|
|
296
|
+
xt.setAttribute('x', xPos(xk).toFixed(1)); xt.setAttribute('y', H - Math.round(FS * 0.7));
|
|
246
297
|
xt.setAttribute('text-anchor', 'middle'); xt.setAttribute('class', 'fdy-chart-xy__xlabel');
|
|
247
298
|
xt.textContent = labels[xk] || ('#' + (xk + 1));
|
|
248
299
|
svg.appendChild(xt);
|
|
@@ -268,7 +319,7 @@
|
|
|
268
319
|
var rect = svgEl('rect');
|
|
269
320
|
rect.setAttribute('x', x.toFixed(1)); rect.setAttribute('width', Math.max(1, barW - 1).toFixed(1));
|
|
270
321
|
rect.setAttribute('y', Math.min(y0, y1).toFixed(1)); rect.setAttribute('height', Math.max(0.5, Math.abs(y1 - y0)).toFixed(1));
|
|
271
|
-
rect.setAttribute('rx', '
|
|
322
|
+
rect.setAttribute('rx', '2'); rect.setAttribute('class', 'fdy-chart-xy__bar');
|
|
272
323
|
rect.style.fill = colorFor(si);
|
|
273
324
|
svg.appendChild(rect);
|
|
274
325
|
});
|
|
@@ -296,33 +347,14 @@
|
|
|
296
347
|
}
|
|
297
348
|
pts.forEach(function (p) {
|
|
298
349
|
var dot = svgEl('circle');
|
|
299
|
-
dot.setAttribute('cx', p[0].toFixed(1)); dot.setAttribute('cy', p[1].toFixed(1)); dot.setAttribute('r', '
|
|
350
|
+
dot.setAttribute('cx', p[0].toFixed(1)); dot.setAttribute('cy', p[1].toFixed(1)); dot.setAttribute('r', '3');
|
|
300
351
|
dot.setAttribute('class', 'fdy-chart-xy__dot'); dot.style.fill = colorFor(si);
|
|
301
352
|
svg.appendChild(dot);
|
|
302
353
|
});
|
|
303
354
|
});
|
|
304
355
|
}
|
|
305
356
|
|
|
306
|
-
// Assemble: optional legend on top, plot below, tooltip layer last.
|
|
307
|
-
var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
|
|
308
|
-
var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
|
|
309
|
-
el.classList.add('fdy-chart-xy');
|
|
310
|
-
el.innerHTML = '';
|
|
311
|
-
if (showLegend) {
|
|
312
|
-
var legend = document.createElement('ul');
|
|
313
|
-
legend.className = 'fdy-chart__legend fdy-chart__legend--row';
|
|
314
|
-
series.forEach(function (s, si) {
|
|
315
|
-
var li = document.createElement('li');
|
|
316
|
-
var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorFor(si);
|
|
317
|
-
li.appendChild(sw);
|
|
318
|
-
li.appendChild(document.createTextNode(s.label || ('Seri ' + (si + 1))));
|
|
319
|
-
legend.appendChild(li);
|
|
320
|
-
});
|
|
321
|
-
el.appendChild(legend);
|
|
322
|
-
}
|
|
323
|
-
var plot = document.createElement('div'); plot.className = 'fdy-chart-xy__plot';
|
|
324
357
|
plot.appendChild(svg);
|
|
325
|
-
el.appendChild(plot);
|
|
326
358
|
|
|
327
359
|
// Hover: one transparent band per category → readout of every series at that x.
|
|
328
360
|
var tip = makeTip(el);
|
|
@@ -342,6 +374,8 @@
|
|
|
342
374
|
band.addEventListener('mouseleave', function () { hideTip(tip); });
|
|
343
375
|
svg.appendChild(band);
|
|
344
376
|
}
|
|
377
|
+
el.__fdyChartW = W;
|
|
378
|
+
if (cartesianRO) cartesianRO.observe(el);
|
|
345
379
|
ensureImg(el);
|
|
346
380
|
}
|
|
347
381
|
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -972,12 +972,12 @@ a { color: var(--color-primary); }
|
|
|
972
972
|
/* Cartesian charts (line / area / multi-series & stacked bar) — SVG with themed axes.
|
|
973
973
|
* Axis colour comes from --chart-grid / --chart-tick (both theme-aware); series colours are
|
|
974
974
|
* set inline by the renderer from --chart-1..8, so the whole chart recolours on data-theme. */
|
|
975
|
-
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100
|
|
976
|
-
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;}
|
|
975
|
+
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100%;--fdy-chart-tick-size:var(--text-xs);}
|
|
976
|
+
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;font-size:var(--fdy-chart-tick-size);}
|
|
977
977
|
.fdy-chart-xy__plot svg{display:block;width:100%;height:100%;overflow:visible;}
|
|
978
978
|
.fdy-chart-xy__grid{stroke:var(--chart-grid);stroke-width:1;shape-rendering:crispEdges;}
|
|
979
|
-
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);
|
|
980
|
-
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);
|
|
979
|
+
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);}
|
|
980
|
+
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);}
|
|
981
981
|
.fdy-chart-xy__line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;vector-effect:non-scaling-stroke;}
|
|
982
982
|
.fdy-chart-xy__area{stroke:none;opacity:.14;}
|
|
983
983
|
.fdy-chart-xy__dot{stroke:var(--color-surface);stroke-width:1;}
|
package/dist/freeday.css
CHANGED
|
@@ -589,12 +589,12 @@ a { color: var(--color-primary); }
|
|
|
589
589
|
/* Cartesian charts (line / area / multi-series & stacked bar) — SVG with themed axes.
|
|
590
590
|
* Axis colour comes from --chart-grid / --chart-tick (both theme-aware); series colours are
|
|
591
591
|
* set inline by the renderer from --chart-1..8, so the whole chart recolours on data-theme. */
|
|
592
|
-
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100
|
|
593
|
-
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;}
|
|
592
|
+
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100%;--fdy-chart-tick-size:var(--text-xs);}
|
|
593
|
+
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;font-size:var(--fdy-chart-tick-size);}
|
|
594
594
|
.fdy-chart-xy__plot svg{display:block;width:100%;height:100%;overflow:visible;}
|
|
595
595
|
.fdy-chart-xy__grid{stroke:var(--chart-grid);stroke-width:1;shape-rendering:crispEdges;}
|
|
596
|
-
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);
|
|
597
|
-
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);
|
|
596
|
+
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);}
|
|
597
|
+
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);}
|
|
598
598
|
.fdy-chart-xy__line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;vector-effect:non-scaling-stroke;}
|
|
599
599
|
.fdy-chart-xy__area{stroke:none;opacity:.14;}
|
|
600
600
|
.fdy-chart-xy__dot{stroke:var(--color-surface);stroke-width:1;}
|
package/dist/freeday.js
CHANGED
|
@@ -874,7 +874,9 @@
|
|
|
874
874
|
* (var(--color-<name>)) OR a slot "chart-1".."chart-8" (var(--chart-N)) to pin a series to the
|
|
875
875
|
* validated palette. Both data-fdy-color and data-fdy-colors accept the chart-N form.
|
|
876
876
|
* Cartesian charts (line/area, multi-series/stacked bar) draw themed axes from --chart-grid /
|
|
877
|
-
* --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency).
|
|
877
|
+
* --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency). Their
|
|
878
|
+
* viewBox is sized to the measured plot (1 user unit = 1 CSS pixel) and repainted from a
|
|
879
|
+
* ResizeObserver, so axis type stays in real pixels and is set in CSS via --fdy-chart-tick-size.
|
|
878
880
|
* Charts are an enhancement — put a table/text fallback inside the element and it will be
|
|
879
881
|
* replaced on render; give the element role="img" + aria-label.
|
|
880
882
|
*
|
|
@@ -914,6 +916,18 @@
|
|
|
914
916
|
function ensureImg(el) { if (!el.hasAttribute('role')) el.setAttribute('role', 'img'); }
|
|
915
917
|
function svgEl(name) { return document.createElementNS(NS, name); }
|
|
916
918
|
|
|
919
|
+
// Cartesian charts size their viewBox to the measured plot, so a width change needs a repaint.
|
|
920
|
+
// Guarded on width alone: a render can change the element's own height (the legend wrapping to a
|
|
921
|
+
// second row) but never its own width, so this settles instead of looping.
|
|
922
|
+
var cartesianRO = typeof ResizeObserver === 'function' ? new ResizeObserver(function (entries) {
|
|
923
|
+
entries.forEach(function (entry) {
|
|
924
|
+
var target = entry.target;
|
|
925
|
+
if (!target.isConnected) { cartesianRO.unobserve(target); return; }
|
|
926
|
+
var w = Math.round(entry.contentRect.width);
|
|
927
|
+
if (w && w !== target.__fdyChartW) renderChart(target);
|
|
928
|
+
});
|
|
929
|
+
}) : null;
|
|
930
|
+
|
|
917
931
|
// Multi-series model: data-series (JSON) wins; else the data-values single-series shortcut.
|
|
918
932
|
function getSeries(el) {
|
|
919
933
|
var raw = el.getAttribute('data-series');
|
|
@@ -1058,7 +1072,46 @@
|
|
|
1058
1072
|
var top = niceCeil(dataMax > base ? dataMax : base + 1);
|
|
1059
1073
|
if (top <= base) top = base + 1;
|
|
1060
1074
|
|
|
1061
|
-
|
|
1075
|
+
// Lay the chrome out first so the plot can be measured, then size the viewBox to it in CSS
|
|
1076
|
+
// pixels: one user unit is one pixel, so font-size / r / stroke-width mean what they say at
|
|
1077
|
+
// every container width. (A fixed 320x180 viewBox stretched by CSS multiplied every declared
|
|
1078
|
+
// size by plotWidth/320 instead — a 9px axis label rendered ~39px on a 1728px viewport.)
|
|
1079
|
+
var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
|
|
1080
|
+
var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
|
|
1081
|
+
el.classList.add('fdy-chart-xy');
|
|
1082
|
+
el.innerHTML = '';
|
|
1083
|
+
if (showLegend) {
|
|
1084
|
+
var legend = document.createElement('ul');
|
|
1085
|
+
legend.className = 'fdy-chart__legend fdy-chart__legend--row';
|
|
1086
|
+
series.forEach(function (s, si) {
|
|
1087
|
+
var li = document.createElement('li');
|
|
1088
|
+
var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorFor(si);
|
|
1089
|
+
li.appendChild(sw);
|
|
1090
|
+
li.appendChild(document.createTextNode(s.label || ('Seri ' + (si + 1))));
|
|
1091
|
+
legend.appendChild(li);
|
|
1092
|
+
});
|
|
1093
|
+
el.appendChild(legend);
|
|
1094
|
+
}
|
|
1095
|
+
var plot = document.createElement('div'); plot.className = 'fdy-chart-xy__plot';
|
|
1096
|
+
el.appendChild(plot);
|
|
1097
|
+
|
|
1098
|
+
// Measured while empty: .fdy-chart-xy__plot carries its own width / aspect-ratio / min-height,
|
|
1099
|
+
// so the box is already final. A detached or display:none chart measures 0 — fall back to the
|
|
1100
|
+
// legacy box and let the ResizeObserver repaint it once it is actually laid out.
|
|
1101
|
+
var box = plot.getBoundingClientRect();
|
|
1102
|
+
var W = Math.round(box.width) || 320, H = Math.round(box.height) || 180;
|
|
1103
|
+
var FS = parseFloat(getComputedStyle(plot).fontSize) || 12; // axis type size, owned by CSS
|
|
1104
|
+
var charW = FS * 0.6; // mono/body advance is ~0.6em
|
|
1105
|
+
|
|
1106
|
+
// Tick values are known before the gutter is, so size PL to the widest one: a currency axis
|
|
1107
|
+
// needs more room than a percent axis, and a fixed gutter only ever suited one of them.
|
|
1108
|
+
var TICKS = 4;
|
|
1109
|
+
var tickText = [];
|
|
1110
|
+
for (var t0 = 0; t0 <= TICKS; t0++) tickText.push(fmt(base + (top - base) * t0 / TICKS));
|
|
1111
|
+
var widestTick = 0;
|
|
1112
|
+
tickText.forEach(function (t) { widestTick = Math.max(widestTick, t.length * charW); });
|
|
1113
|
+
var PL = Math.min(Math.ceil(widestTick) + 10, Math.round(W * 0.35));
|
|
1114
|
+
var PR = 10, PT = 10, PB = Math.round(FS * 2.2);
|
|
1062
1115
|
var plotW = W - PL - PR, plotH = H - PT - PB;
|
|
1063
1116
|
function mapY(v) { return PT + plotH * (1 - (v - base) / (top - base)); }
|
|
1064
1117
|
var slot = plotW / n;
|
|
@@ -1070,7 +1123,6 @@
|
|
|
1070
1123
|
svg.setAttribute('aria-hidden', 'true');
|
|
1071
1124
|
|
|
1072
1125
|
// y gridlines + formatted ticks (bottom line at `base` doubles as the zero baseline).
|
|
1073
|
-
var TICKS = 4;
|
|
1074
1126
|
for (var k = 0; k <= TICKS; k++) {
|
|
1075
1127
|
var val = base + (top - base) * k / TICKS;
|
|
1076
1128
|
var gy = mapY(val);
|
|
@@ -1080,9 +1132,9 @@
|
|
|
1080
1132
|
gl.setAttribute('class', 'fdy-chart-xy__grid');
|
|
1081
1133
|
svg.appendChild(gl);
|
|
1082
1134
|
var tk = svgEl('text');
|
|
1083
|
-
tk.setAttribute('x', PL - 5); tk.setAttribute('y', (gy +
|
|
1135
|
+
tk.setAttribute('x', PL - 5); tk.setAttribute('y', (gy + FS * 0.35).toFixed(1));
|
|
1084
1136
|
tk.setAttribute('text-anchor', 'end'); tk.setAttribute('class', 'fdy-chart-xy__tick');
|
|
1085
|
-
tk.textContent =
|
|
1137
|
+
tk.textContent = tickText[k];
|
|
1086
1138
|
svg.appendChild(tk);
|
|
1087
1139
|
}
|
|
1088
1140
|
|
|
@@ -1090,7 +1142,6 @@
|
|
|
1090
1142
|
// of assuming a fixed 40px slot; keep every step-th plus the last, then drop any kept label that
|
|
1091
1143
|
// would collide with the one before it — including the forced last one (a fraction-of-a-slot
|
|
1092
1144
|
// overlap happens when n-1 lands next to the last kept multiple of step, e.g. n=24 at most widths).
|
|
1093
|
-
var charW = 5.4; // ~9px font advance (.fdy-chart-xy__xlabel)
|
|
1094
1145
|
var widest = 0;
|
|
1095
1146
|
for (var wi = 0; wi < n; wi++) widest = Math.max(widest, String(labels[wi] || ('#' + (wi + 1))).length * charW);
|
|
1096
1147
|
var minGap = widest + 8;
|
|
@@ -1106,7 +1157,7 @@
|
|
|
1106
1157
|
}
|
|
1107
1158
|
kept.forEach(function (xk) {
|
|
1108
1159
|
var xt = svgEl('text');
|
|
1109
|
-
xt.setAttribute('x', xPos(xk).toFixed(1)); xt.setAttribute('y', H -
|
|
1160
|
+
xt.setAttribute('x', xPos(xk).toFixed(1)); xt.setAttribute('y', H - Math.round(FS * 0.7));
|
|
1110
1161
|
xt.setAttribute('text-anchor', 'middle'); xt.setAttribute('class', 'fdy-chart-xy__xlabel');
|
|
1111
1162
|
xt.textContent = labels[xk] || ('#' + (xk + 1));
|
|
1112
1163
|
svg.appendChild(xt);
|
|
@@ -1132,7 +1183,7 @@
|
|
|
1132
1183
|
var rect = svgEl('rect');
|
|
1133
1184
|
rect.setAttribute('x', x.toFixed(1)); rect.setAttribute('width', Math.max(1, barW - 1).toFixed(1));
|
|
1134
1185
|
rect.setAttribute('y', Math.min(y0, y1).toFixed(1)); rect.setAttribute('height', Math.max(0.5, Math.abs(y1 - y0)).toFixed(1));
|
|
1135
|
-
rect.setAttribute('rx', '
|
|
1186
|
+
rect.setAttribute('rx', '2'); rect.setAttribute('class', 'fdy-chart-xy__bar');
|
|
1136
1187
|
rect.style.fill = colorFor(si);
|
|
1137
1188
|
svg.appendChild(rect);
|
|
1138
1189
|
});
|
|
@@ -1160,33 +1211,14 @@
|
|
|
1160
1211
|
}
|
|
1161
1212
|
pts.forEach(function (p) {
|
|
1162
1213
|
var dot = svgEl('circle');
|
|
1163
|
-
dot.setAttribute('cx', p[0].toFixed(1)); dot.setAttribute('cy', p[1].toFixed(1)); dot.setAttribute('r', '
|
|
1214
|
+
dot.setAttribute('cx', p[0].toFixed(1)); dot.setAttribute('cy', p[1].toFixed(1)); dot.setAttribute('r', '3');
|
|
1164
1215
|
dot.setAttribute('class', 'fdy-chart-xy__dot'); dot.style.fill = colorFor(si);
|
|
1165
1216
|
svg.appendChild(dot);
|
|
1166
1217
|
});
|
|
1167
1218
|
});
|
|
1168
1219
|
}
|
|
1169
1220
|
|
|
1170
|
-
// Assemble: optional legend on top, plot below, tooltip layer last.
|
|
1171
|
-
var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
|
|
1172
|
-
var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
|
|
1173
|
-
el.classList.add('fdy-chart-xy');
|
|
1174
|
-
el.innerHTML = '';
|
|
1175
|
-
if (showLegend) {
|
|
1176
|
-
var legend = document.createElement('ul');
|
|
1177
|
-
legend.className = 'fdy-chart__legend fdy-chart__legend--row';
|
|
1178
|
-
series.forEach(function (s, si) {
|
|
1179
|
-
var li = document.createElement('li');
|
|
1180
|
-
var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorFor(si);
|
|
1181
|
-
li.appendChild(sw);
|
|
1182
|
-
li.appendChild(document.createTextNode(s.label || ('Seri ' + (si + 1))));
|
|
1183
|
-
legend.appendChild(li);
|
|
1184
|
-
});
|
|
1185
|
-
el.appendChild(legend);
|
|
1186
|
-
}
|
|
1187
|
-
var plot = document.createElement('div'); plot.className = 'fdy-chart-xy__plot';
|
|
1188
1221
|
plot.appendChild(svg);
|
|
1189
|
-
el.appendChild(plot);
|
|
1190
1222
|
|
|
1191
1223
|
// Hover: one transparent band per category → readout of every series at that x.
|
|
1192
1224
|
var tip = makeTip(el);
|
|
@@ -1206,6 +1238,8 @@
|
|
|
1206
1238
|
band.addEventListener('mouseleave', function () { hideTip(tip); });
|
|
1207
1239
|
svg.appendChild(band);
|
|
1208
1240
|
}
|
|
1241
|
+
el.__fdyChartW = W;
|
|
1242
|
+
if (cartesianRO) cartesianRO.observe(el);
|
|
1209
1243
|
ensureImg(el);
|
|
1210
1244
|
}
|
|
1211
1245
|
|
|
@@ -171,11 +171,6 @@
|
|
|
171
171
|
data-fdy-format="number"
|
|
172
172
|
role="img"
|
|
173
173
|
aria-label="Invoiced versus collected, in millions: invoiced rose from 62 to 88, collected from 54 to 81">
|
|
174
|
-
<table>
|
|
175
|
-
<caption>Invoiced vs collected (millions)</caption>
|
|
176
|
-
<tr><th scope="col">Month</th><th scope="col">Invoiced</th><th scope="col">Collected</th></tr>
|
|
177
|
-
<tr><td>Jul</td><td>88</td><td>81</td></tr>
|
|
178
|
-
</table>
|
|
179
174
|
</div>
|
|
180
175
|
</div>
|
|
181
176
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cahyo-dimas/freeday",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.51.1",
|
|
4
4
|
"description": "Freeday — token-driven, framework-agnostic UI KIT (design source-of-truth).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "node tokens/build.mjs",
|
|
94
94
|
"test": "node --test",
|
|
95
|
-
"test:browser": "node --test browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/cfl-multi.mjs browser/crowding.mjs browser/over-dialog.mjs",
|
|
95
|
+
"test:browser": "node --test browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/cfl-multi.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs",
|
|
96
96
|
"prepack": "node tokens/build.mjs",
|
|
97
97
|
"version": "node tokens/build.mjs && git add dist",
|
|
98
98
|
"typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit"
|
package/src/components/chart.css
CHANGED
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
/* Cartesian charts (line / area / multi-series & stacked bar) — SVG with themed axes.
|
|
43
43
|
* Axis colour comes from --chart-grid / --chart-tick (both theme-aware); series colours are
|
|
44
44
|
* set inline by the renderer from --chart-1..8, so the whole chart recolours on data-theme. */
|
|
45
|
-
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100
|
|
46
|
-
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;}
|
|
45
|
+
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100%;--fdy-chart-tick-size:var(--text-xs);}
|
|
46
|
+
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;font-size:var(--fdy-chart-tick-size);}
|
|
47
47
|
.fdy-chart-xy__plot svg{display:block;width:100%;height:100%;overflow:visible;}
|
|
48
48
|
.fdy-chart-xy__grid{stroke:var(--chart-grid);stroke-width:1;shape-rendering:crispEdges;}
|
|
49
|
-
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);
|
|
50
|
-
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);
|
|
49
|
+
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);}
|
|
50
|
+
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);}
|
|
51
51
|
.fdy-chart-xy__line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;vector-effect:non-scaling-stroke;}
|
|
52
52
|
.fdy-chart-xy__area{stroke:none;opacity:.14;}
|
|
53
53
|
.fdy-chart-xy__dot{stroke:var(--color-surface);stroke-width:1;}
|