@icen.ai/ui 0.8.0 → 0.8.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/AGENTS.md +15 -9
- package/README.md +8 -3
- package/dist/base.css +4 -3
- package/dist/behaviors/accordion.d.mts +5 -2
- package/dist/behaviors/accordion.mjs +36 -11
- package/dist/behaviors/ai-chat.d.mts +3 -1
- package/dist/behaviors/ai-chat.mjs +66 -43
- package/dist/behaviors/ai-composer.d.mts +28 -3
- package/dist/behaviors/ai-composer.mjs +291 -209
- package/dist/behaviors/ai-core.d.mts +9 -4
- package/dist/behaviors/ai-core.mjs +21 -8
- package/dist/behaviors/ai-diff.d.mts +2 -2
- package/dist/behaviors/ai-diff.mjs +172 -41
- package/dist/behaviors/ai-panel.d.mts +11 -8
- package/dist/behaviors/ai-panel.mjs +185 -110
- package/dist/behaviors/ai-provider.mjs +54 -48
- package/dist/behaviors/ai-tool.d.mts +12 -4
- package/dist/behaviors/ai-tool.mjs +311 -263
- package/dist/behaviors/ai-tools.d.mts +9 -6
- package/dist/behaviors/ai-tools.mjs +259 -155
- package/dist/behaviors/back-top.mjs +16 -1
- package/dist/behaviors/carousel.d.mts +5 -2
- package/dist/behaviors/carousel.mjs +53 -26
- package/dist/behaviors/charts.d.mts +68 -22
- package/dist/behaviors/charts.mjs +221 -150
- package/dist/behaviors/command-palette.d.mts +40 -6
- package/dist/behaviors/command-palette.mjs +143 -15
- package/dist/behaviors/context-menu.d.mts +21 -4
- package/dist/behaviors/context-menu.mjs +122 -38
- package/dist/behaviors/controls.d.mts +59 -0
- package/dist/behaviors/controls.mjs +601 -0
- package/dist/behaviors/copy.d.mts +5 -2
- package/dist/behaviors/copy.mjs +37 -12
- package/dist/behaviors/datatable.d.mts +12 -0
- package/dist/behaviors/datatable.mjs +226 -43
- package/dist/behaviors/date-picker.d.mts +2 -2
- package/dist/behaviors/date-picker.mjs +171 -32
- package/dist/behaviors/dropdown.d.mts +6 -2
- package/dist/behaviors/dropdown.mjs +164 -25
- package/dist/behaviors/events.d.mts +330 -0
- package/dist/behaviors/events.mjs +208 -0
- package/dist/behaviors/input.mjs +25 -12
- package/dist/behaviors/modal.d.mts +24 -5
- package/dist/behaviors/modal.mjs +76 -10
- package/dist/behaviors/nav.d.mts +1 -1
- package/dist/behaviors/nav.mjs +33 -7
- package/dist/behaviors/notification.d.mts +20 -4
- package/dist/behaviors/notification.mjs +53 -73
- package/dist/behaviors/popover.d.mts +39 -3
- package/dist/behaviors/popover.mjs +90 -4
- package/dist/behaviors/select.d.mts +4 -2
- package/dist/behaviors/select.mjs +161 -24
- package/dist/behaviors/sidebar.d.mts +5 -2
- package/dist/behaviors/sidebar.mjs +43 -13
- package/dist/behaviors/slider.d.mts +5 -2
- package/dist/behaviors/slider.mjs +54 -43
- package/dist/behaviors/split-pane.d.mts +6 -2
- package/dist/behaviors/split-pane.mjs +62 -21
- package/dist/behaviors/tabs.d.mts +3 -2
- package/dist/behaviors/tabs.mjs +65 -14
- package/dist/behaviors/tag-input.d.mts +5 -2
- package/dist/behaviors/tag-input.mjs +61 -21
- package/dist/behaviors/theme.d.mts +13 -2
- package/dist/behaviors/theme.mjs +48 -4
- package/dist/behaviors/toast.d.mts +30 -8
- package/dist/behaviors/toast.mjs +74 -7
- package/dist/behaviors/tree.d.mts +5 -2
- package/dist/behaviors/tree.mjs +38 -15
- package/dist/behaviors/upload.mjs +21 -23
- package/dist/cli.mjs +1 -1
- package/dist/components/ai-panel.css +1 -1
- package/dist/components/ai-tool-call.mjs +1 -1
- package/dist/components/badge.css +1 -1
- package/dist/components/breadcrumb.css +0 -32
- package/dist/components/chart-calendar.mjs +1 -2
- package/dist/components/chart-pie.css +0 -6
- package/dist/components/charts.css +1 -1
- package/dist/components/content.css +3 -16
- package/dist/components/datatable.css +1 -1
- package/dist/components/date-picker.css +1 -1
- package/dist/components/notification.css +2 -3
- package/dist/components/pagination.css +3 -1
- package/dist/components/pagination.mjs +1 -0
- package/dist/components/rating.mjs +1 -0
- package/dist/components/segmented.css +9 -1
- package/dist/components/segmented.mjs +1 -1
- package/dist/components/select.css +67 -4
- package/dist/components/steps.css +13 -3
- package/dist/components/steps.mjs +1 -0
- package/dist/components/switch.css +3 -1
- package/dist/components/switch.mjs +1 -0
- package/dist/components/table.mjs +1 -0
- package/dist/components/tabs.css +23 -0
- package/dist/components/tag-input.css +1 -6
- package/dist/components/upload.css +0 -15
- package/dist/index.d.mts +12 -10
- package/dist/index.mjs +2700 -1460
- package/dist/registry.json +42 -13
- package/dist/retro-effects.css +3 -16
- package/dist/tokens/retro-effects.css +3 -16
- package/dist/tokens/style-profiles.css +3 -2
- package/dist/tokens/typography.css +2 -0
- package/dist/tokens.css +5 -2
- package/dist/ui.css +138 -98
- package/docs/spec/ai-native.md +7 -4
- package/package.json +1 -1
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
// src/behaviors/events.ts
|
|
2
|
+
function emitIcen(source, type, detail) {
|
|
3
|
+
return source.dispatchEvent(
|
|
4
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
5
|
+
);
|
|
6
|
+
}
|
|
7
|
+
|
|
1
8
|
// src/behaviors/charts.ts
|
|
2
9
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
10
|
+
var REGISTERED_TONES = /* @__PURE__ */ new Map();
|
|
11
|
+
function registerChartTone(name, cssVar) {
|
|
12
|
+
if (!name || !cssVar) return;
|
|
13
|
+
REGISTERED_TONES.set(name, cssVar.startsWith("var(") ? cssVar : `var(${cssVar})`);
|
|
14
|
+
}
|
|
3
15
|
function toneVar(tone = "accent") {
|
|
16
|
+
const registered = REGISTERED_TONES.get(tone);
|
|
17
|
+
if (registered) return registered;
|
|
4
18
|
switch (tone) {
|
|
5
19
|
case "success":
|
|
6
20
|
return "var(--token-success)";
|
|
@@ -154,17 +168,14 @@ function withChrome(el, opts, hasLegend, paint, state) {
|
|
|
154
168
|
if (state) state.hidden = hidden;
|
|
155
169
|
el.setAttribute("data-legend-hidden", String(hidden));
|
|
156
170
|
syncEye();
|
|
157
|
-
el
|
|
158
|
-
bubbles: true,
|
|
159
|
-
detail: { visible: hidden ? false : true }
|
|
160
|
-
}));
|
|
171
|
+
emitIcen(el, "icen:chart-legend-visibility", { visible: hidden ? false : true });
|
|
161
172
|
});
|
|
162
173
|
syncEye();
|
|
163
174
|
head.append(title, eye);
|
|
164
175
|
el.append(head, body);
|
|
165
176
|
paint(body);
|
|
166
177
|
}
|
|
167
|
-
function toggleLegend(items, scope
|
|
178
|
+
function toggleLegend(items, scope) {
|
|
168
179
|
const legend = document.createElement("div");
|
|
169
180
|
legend.className = "chart-legend chart-legend--toggle";
|
|
170
181
|
for (const item of items) {
|
|
@@ -183,10 +194,7 @@ function toggleLegend(items, scope, _opts) {
|
|
|
183
194
|
scope.querySelectorAll(`[data-chart-key="${CSS.escape(item.key)}"]`).forEach((m) => {
|
|
184
195
|
m.classList.toggle("is-off", off);
|
|
185
196
|
});
|
|
186
|
-
legend
|
|
187
|
-
bubbles: true,
|
|
188
|
-
detail: { key: item.key, seriesIndex: item.si, hidden: off }
|
|
189
|
-
}));
|
|
197
|
+
emitIcen(legend, "icen:chart-legend-toggle", { key: item.key, seriesIndex: item.si, hidden: off });
|
|
190
198
|
};
|
|
191
199
|
row.addEventListener("click", toggle);
|
|
192
200
|
row.addEventListener("keydown", (e) => {
|
|
@@ -205,7 +213,7 @@ function toggleLegend(items, scope, _opts) {
|
|
|
205
213
|
return legend;
|
|
206
214
|
}
|
|
207
215
|
function renderVBar(el, opts) {
|
|
208
|
-
if (typeof document === "undefined") return;
|
|
216
|
+
if (typeof document === "undefined") return el;
|
|
209
217
|
const seriesList = normalizeSeriesInput(opts);
|
|
210
218
|
const labels = opts.labels ?? [];
|
|
211
219
|
const n = Math.min(labels.length, ...seriesList.map((s) => s.values.length));
|
|
@@ -256,11 +264,13 @@ function renderVBar(el, opts) {
|
|
|
256
264
|
wrap.appendChild(track);
|
|
257
265
|
}
|
|
258
266
|
body.append(wrap, labelsRow(labelsUsed));
|
|
259
|
-
if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone, si })), wrap
|
|
267
|
+
if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone, si })), wrap));
|
|
260
268
|
});
|
|
269
|
+
attachChartInteraction(el, "vbar");
|
|
270
|
+
return el;
|
|
261
271
|
}
|
|
262
272
|
function renderHBar(el, opts) {
|
|
263
|
-
if (typeof document === "undefined") return;
|
|
273
|
+
if (typeof document === "undefined") return el;
|
|
264
274
|
withChrome(el, opts, false, (body) => {
|
|
265
275
|
const n = Math.min(opts.labels.length, opts.values.length);
|
|
266
276
|
if (n === 0) {
|
|
@@ -294,9 +304,11 @@ function renderHBar(el, opts) {
|
|
|
294
304
|
});
|
|
295
305
|
body.appendChild(wrap);
|
|
296
306
|
});
|
|
307
|
+
attachChartInteraction(el, "hbar");
|
|
308
|
+
return el;
|
|
297
309
|
}
|
|
298
310
|
function renderStack(el, opts) {
|
|
299
|
-
if (typeof document === "undefined") return;
|
|
311
|
+
if (typeof document === "undefined") return el;
|
|
300
312
|
withChrome(el, opts, opts.segments.length > 0, (body) => {
|
|
301
313
|
const total = opts.segments.reduce((sum, s) => sum + s.value, 0);
|
|
302
314
|
if (opts.segments.length === 0 || total <= 0) {
|
|
@@ -305,13 +317,13 @@ function renderStack(el, opts) {
|
|
|
305
317
|
}
|
|
306
318
|
const bar = document.createElement("div");
|
|
307
319
|
bar.className = "chart-stack";
|
|
308
|
-
for (const seg of opts.segments) {
|
|
320
|
+
for (const [segIndex, seg] of opts.segments.entries()) {
|
|
309
321
|
const s = document.createElement("div");
|
|
310
322
|
s.className = "chart-stack-seg";
|
|
311
323
|
s.style.width = `${seg.value / total * 100}%`;
|
|
312
324
|
if (seg.tone) s.style.setProperty("--chart-tone", toneVar(seg.tone));
|
|
313
325
|
s.setAttribute("title", `${seg.label}: ${fmt(opts, seg.value)}`);
|
|
314
|
-
setMarkData(s, { index:
|
|
326
|
+
setMarkData(s, { index: segIndex, label: seg.label, value: seg.value });
|
|
315
327
|
bar.appendChild(s);
|
|
316
328
|
}
|
|
317
329
|
const legend = document.createElement("div");
|
|
@@ -321,9 +333,11 @@ function renderStack(el, opts) {
|
|
|
321
333
|
}
|
|
322
334
|
body.append(bar, legend);
|
|
323
335
|
});
|
|
336
|
+
attachChartInteraction(el, "stack");
|
|
337
|
+
return el;
|
|
324
338
|
}
|
|
325
339
|
function renderDonut(el, opts) {
|
|
326
|
-
if (typeof document === "undefined") return;
|
|
340
|
+
if (typeof document === "undefined") return el;
|
|
327
341
|
withChrome(el, opts, opts.segments.length > 0, (body) => {
|
|
328
342
|
const total = opts.segments.reduce((sum, s) => sum + s.value, 0);
|
|
329
343
|
if (opts.segments.length === 0 || total <= 0) {
|
|
@@ -370,10 +384,12 @@ function renderDonut(el, opts) {
|
|
|
370
384
|
wrap.append(svg, legend);
|
|
371
385
|
body.appendChild(wrap);
|
|
372
386
|
});
|
|
387
|
+
attachChartInteraction(el, "donut");
|
|
388
|
+
return el;
|
|
373
389
|
}
|
|
374
390
|
var lineGradientSeq = 0;
|
|
375
391
|
function renderLine(el, opts) {
|
|
376
|
-
if (typeof document === "undefined") return;
|
|
392
|
+
if (typeof document === "undefined") return el;
|
|
377
393
|
const seriesList = normalizeSeriesInput(opts);
|
|
378
394
|
const labels = opts.labels ?? [];
|
|
379
395
|
const n = Math.min(labels.length, ...seriesList.map((s) => s.values.length));
|
|
@@ -478,11 +494,13 @@ function renderLine(el, opts) {
|
|
|
478
494
|
if (defs.firstChild) svg.insertBefore(defs, svg.firstChild);
|
|
479
495
|
wrap.append(svg, labelsRow(sampleLabels(labelsUsed)));
|
|
480
496
|
body.appendChild(wrap);
|
|
481
|
-
if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone, si })), wrap
|
|
497
|
+
if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone, si })), wrap));
|
|
482
498
|
});
|
|
499
|
+
attachChartInteraction(el, "line");
|
|
500
|
+
return el;
|
|
483
501
|
}
|
|
484
502
|
function renderArea(el, opts) {
|
|
485
|
-
if (typeof document === "undefined") return;
|
|
503
|
+
if (typeof document === "undefined") return el;
|
|
486
504
|
withChrome(el, opts, false, (body) => {
|
|
487
505
|
const n = Math.min(opts.labels.length, opts.values.length);
|
|
488
506
|
if (n === 0) {
|
|
@@ -551,9 +569,11 @@ function renderArea(el, opts) {
|
|
|
551
569
|
wrap.append(svg, labelsRow(sampleLabels(labels)));
|
|
552
570
|
body.appendChild(wrap);
|
|
553
571
|
});
|
|
572
|
+
attachChartInteraction(el, "area");
|
|
573
|
+
return el;
|
|
554
574
|
}
|
|
555
575
|
function renderRadar(el, opts) {
|
|
556
|
-
if (typeof document === "undefined") return;
|
|
576
|
+
if (typeof document === "undefined") return el;
|
|
557
577
|
withChrome(el, opts, opts.series.length > 1, (body) => {
|
|
558
578
|
const axes = opts.axes;
|
|
559
579
|
const n = axes.length;
|
|
@@ -655,13 +675,12 @@ function renderRadar(el, opts) {
|
|
|
655
675
|
}
|
|
656
676
|
legendItems.push({ key: s.name || `\u7CFB\u5217${si + 1}`, label: s.name || `\u7CFB\u5217${si + 1}`, tone: s.tone, si });
|
|
657
677
|
});
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
l.className = "chart-legend";
|
|
661
|
-
return l;
|
|
662
|
-
})());
|
|
678
|
+
const legend = opts.series.length > 1 ? toggleLegend(legendItems, wrap) : null;
|
|
679
|
+
wrap.append(svg, ...legend ? [legend] : []);
|
|
663
680
|
body.appendChild(wrap);
|
|
664
681
|
});
|
|
682
|
+
attachChartInteraction(el, "radar");
|
|
683
|
+
return el;
|
|
665
684
|
}
|
|
666
685
|
function niceCeil(v) {
|
|
667
686
|
if (v <= 0) return 1;
|
|
@@ -680,8 +699,41 @@ function isoDate(d) {
|
|
|
680
699
|
const day = String(d.getDate()).padStart(2, "0");
|
|
681
700
|
return `${d.getFullYear()}-${m}-${day}`;
|
|
682
701
|
}
|
|
702
|
+
function weekdayLabelAt(defaults, day) {
|
|
703
|
+
return defaults[(day + 6) % 7] ?? "";
|
|
704
|
+
}
|
|
705
|
+
function levelOf(v, max) {
|
|
706
|
+
return v <= 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
|
|
707
|
+
}
|
|
708
|
+
function padFirstWeek(grid, firstDate, weekStart) {
|
|
709
|
+
const first = /* @__PURE__ */ new Date(`${firstDate}T00:00:00`);
|
|
710
|
+
const pad = (first.getDay() + 7 - weekStart) % 7;
|
|
711
|
+
for (let i = 0; i < pad; i++) {
|
|
712
|
+
const blank = document.createElement("span");
|
|
713
|
+
blank.className = "chart-heatmap-cell is-blank";
|
|
714
|
+
grid.appendChild(blank);
|
|
715
|
+
}
|
|
716
|
+
return pad;
|
|
717
|
+
}
|
|
718
|
+
function heatmapLegend(labels) {
|
|
719
|
+
const legend = document.createElement("div");
|
|
720
|
+
legend.className = "chart-heatmap-legend";
|
|
721
|
+
const less = document.createElement("span");
|
|
722
|
+
less.textContent = labels?.less ?? "\u5C11";
|
|
723
|
+
const more = document.createElement("span");
|
|
724
|
+
more.textContent = labels?.more ?? "\u591A";
|
|
725
|
+
legend.appendChild(less);
|
|
726
|
+
for (let lv = 0; lv <= 4; lv++) {
|
|
727
|
+
const cell = document.createElement("span");
|
|
728
|
+
cell.className = "chart-heatmap-cell";
|
|
729
|
+
cell.dataset.level = String(lv);
|
|
730
|
+
legend.appendChild(cell);
|
|
731
|
+
}
|
|
732
|
+
legend.appendChild(more);
|
|
733
|
+
return legend;
|
|
734
|
+
}
|
|
683
735
|
function renderHeatmap(el, opts) {
|
|
684
|
-
if (typeof document === "undefined") return;
|
|
736
|
+
if (typeof document === "undefined") return el;
|
|
685
737
|
withChrome(el, opts, true, (body) => {
|
|
686
738
|
let entries;
|
|
687
739
|
if (opts.data && opts.data.length > 0) {
|
|
@@ -700,48 +752,29 @@ function renderHeatmap(el, opts) {
|
|
|
700
752
|
const weeks = opts.weeks ?? Math.ceil(entries.length / 7);
|
|
701
753
|
if (entries.length > weeks * 7) entries = entries.slice(entries.length - weeks * 7);
|
|
702
754
|
const max = maxOf(entries.map((e) => e.value));
|
|
703
|
-
const levelOf = (v) => v <= 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
|
|
704
755
|
const wrap = document.createElement("div");
|
|
705
756
|
wrap.className = "chart-heatmap";
|
|
706
757
|
if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
|
|
707
758
|
const grid = document.createElement("div");
|
|
708
759
|
grid.className = "chart-heatmap-grid";
|
|
709
760
|
grid.setAttribute("role", "img");
|
|
710
|
-
|
|
711
|
-
const
|
|
712
|
-
for (let i = 0; i < pad; i++) {
|
|
713
|
-
const blank = document.createElement("span");
|
|
714
|
-
blank.className = "chart-heatmap-cell is-blank";
|
|
715
|
-
grid.appendChild(blank);
|
|
716
|
-
}
|
|
717
|
-
for (const entry of entries) {
|
|
761
|
+
padFirstWeek(grid, entries[0].date, opts.weekStart === 0 ? 0 : 1);
|
|
762
|
+
for (const [i, entry] of entries.entries()) {
|
|
718
763
|
const cell = document.createElement("span");
|
|
719
764
|
cell.className = "chart-heatmap-cell";
|
|
720
|
-
cell.dataset.level = String(levelOf(entry.value));
|
|
765
|
+
cell.dataset.level = String(levelOf(entry.value, max));
|
|
721
766
|
cell.setAttribute("title", `${entry.date}\uFF1A${fmt(opts, entry.value)}`);
|
|
722
|
-
setMarkData(cell, { index:
|
|
767
|
+
setMarkData(cell, { index: i, label: entry.date, value: entry.value });
|
|
723
768
|
grid.appendChild(cell);
|
|
724
769
|
}
|
|
725
|
-
|
|
726
|
-
legend.className = "chart-heatmap-legend";
|
|
727
|
-
const less = document.createElement("span");
|
|
728
|
-
less.textContent = "\u5C11";
|
|
729
|
-
const more = document.createElement("span");
|
|
730
|
-
more.textContent = "\u591A";
|
|
731
|
-
legend.appendChild(less);
|
|
732
|
-
for (let lv = 0; lv <= 4; lv++) {
|
|
733
|
-
const cell = document.createElement("span");
|
|
734
|
-
cell.className = "chart-heatmap-cell";
|
|
735
|
-
cell.dataset.level = String(lv);
|
|
736
|
-
legend.appendChild(cell);
|
|
737
|
-
}
|
|
738
|
-
legend.appendChild(more);
|
|
739
|
-
wrap.append(grid, legend);
|
|
770
|
+
wrap.append(grid, heatmapLegend(opts.labels));
|
|
740
771
|
body.appendChild(wrap);
|
|
741
772
|
});
|
|
773
|
+
attachChartInteraction(el, "heatmap");
|
|
774
|
+
return el;
|
|
742
775
|
}
|
|
743
776
|
function renderSparkline(el, opts) {
|
|
744
|
-
if (typeof document === "undefined") return;
|
|
777
|
+
if (typeof document === "undefined") return el;
|
|
745
778
|
withChrome(el, opts, false, (body) => {
|
|
746
779
|
const values = opts.values;
|
|
747
780
|
if (values.length === 0) {
|
|
@@ -796,9 +829,11 @@ function renderSparkline(el, opts) {
|
|
|
796
829
|
wrap.appendChild(svg);
|
|
797
830
|
body.appendChild(wrap);
|
|
798
831
|
});
|
|
832
|
+
attachChartInteraction(el, "sparkline");
|
|
833
|
+
return el;
|
|
799
834
|
}
|
|
800
835
|
function renderGauge(el, opts) {
|
|
801
|
-
if (typeof document === "undefined") return;
|
|
836
|
+
if (typeof document === "undefined") return el;
|
|
802
837
|
el.textContent = "";
|
|
803
838
|
const max = opts.max ?? 100;
|
|
804
839
|
const ratio = max > 0 ? Math.min(1, Math.max(0, opts.value / max)) : 0;
|
|
@@ -842,9 +877,11 @@ function renderGauge(el, opts) {
|
|
|
842
877
|
}
|
|
843
878
|
wrap.append(svg, center);
|
|
844
879
|
el.appendChild(wrap);
|
|
880
|
+
attachChartInteraction(el, "gauge");
|
|
881
|
+
return el;
|
|
845
882
|
}
|
|
846
883
|
function renderCalendar(el, opts) {
|
|
847
|
-
if (typeof document === "undefined") return;
|
|
884
|
+
if (typeof document === "undefined") return el;
|
|
848
885
|
withChrome(el, opts, true, (body) => {
|
|
849
886
|
let entries;
|
|
850
887
|
if (opts.data && opts.data.length > 0) {
|
|
@@ -858,6 +895,10 @@ function renderCalendar(el, opts) {
|
|
|
858
895
|
const wrap = document.createElement("div");
|
|
859
896
|
wrap.className = "chart-calendar";
|
|
860
897
|
if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
|
|
898
|
+
const weekStart = opts.weekStart === 0 ? 0 : 1;
|
|
899
|
+
const weekdayDefaults = ["\u4E00", "", "\u4E09", "", "\u4E94", "", "\u65E5"];
|
|
900
|
+
const weekdayLabels = opts.labels?.weekdays ?? weekdayDefaults.map((_, i) => weekdayLabelAt(weekdayDefaults, (weekStart + i) % 7));
|
|
901
|
+
const monthLabel = (m) => opts.labels?.months?.[m] ?? `${m + 1}\u6708`;
|
|
861
902
|
const grid = document.createElement("div");
|
|
862
903
|
grid.className = "chart-heatmap-grid";
|
|
863
904
|
grid.setAttribute("role", "img");
|
|
@@ -865,20 +906,13 @@ function renderCalendar(el, opts) {
|
|
|
865
906
|
months.className = "chart-calendar-months";
|
|
866
907
|
const weekdayRow = document.createElement("div");
|
|
867
908
|
weekdayRow.className = "chart-calendar-weekdays";
|
|
868
|
-
|
|
909
|
+
weekdayLabels.forEach((w) => {
|
|
869
910
|
const s = document.createElement("span");
|
|
870
911
|
s.textContent = w;
|
|
871
912
|
weekdayRow.appendChild(s);
|
|
872
913
|
});
|
|
873
914
|
const max = maxOf(entries.map((e) => e.value));
|
|
874
|
-
const
|
|
875
|
-
const first = /* @__PURE__ */ new Date(`${entries[0].date}T00:00:00`);
|
|
876
|
-
const pad = (first.getDay() + 6) % 7;
|
|
877
|
-
for (let i = 0; i < pad; i++) {
|
|
878
|
-
const blank = document.createElement("span");
|
|
879
|
-
blank.className = "chart-heatmap-cell is-blank";
|
|
880
|
-
grid.appendChild(blank);
|
|
881
|
-
}
|
|
915
|
+
const pad = padFirstWeek(grid, entries[0].date, weekStart);
|
|
882
916
|
let lastMonth = -1;
|
|
883
917
|
entries.forEach((entry, i) => {
|
|
884
918
|
const d = /* @__PURE__ */ new Date(`${entry.date}T00:00:00`);
|
|
@@ -887,39 +921,28 @@ function renderCalendar(el, opts) {
|
|
|
887
921
|
lastMonth = d.getMonth();
|
|
888
922
|
const label = document.createElement("span");
|
|
889
923
|
label.className = "chart-calendar-month";
|
|
890
|
-
label.textContent =
|
|
924
|
+
label.textContent = monthLabel(d.getMonth());
|
|
891
925
|
label.style.setProperty("--col", String(col));
|
|
892
926
|
months.appendChild(label);
|
|
893
927
|
}
|
|
894
928
|
const cell = document.createElement("span");
|
|
895
929
|
cell.className = "chart-heatmap-cell";
|
|
896
|
-
cell.dataset.level = String(levelOf(entry.value));
|
|
930
|
+
cell.dataset.level = String(levelOf(entry.value, max));
|
|
897
931
|
cell.setAttribute("title", `${entry.date}\uFF1A${fmt(opts, entry.value)}`);
|
|
898
932
|
setMarkData(cell, { index: i, label: entry.date, value: entry.value });
|
|
899
933
|
grid.appendChild(cell);
|
|
900
934
|
});
|
|
901
|
-
const legend =
|
|
902
|
-
legend.className = "chart-heatmap-legend";
|
|
903
|
-
const less = document.createElement("span");
|
|
904
|
-
less.textContent = "\u5C11";
|
|
905
|
-
const more = document.createElement("span");
|
|
906
|
-
more.textContent = "\u591A";
|
|
907
|
-
legend.appendChild(less);
|
|
908
|
-
for (let lv = 0; lv <= 4; lv++) {
|
|
909
|
-
const cell = document.createElement("span");
|
|
910
|
-
cell.className = "chart-heatmap-cell";
|
|
911
|
-
cell.dataset.level = String(lv);
|
|
912
|
-
legend.appendChild(cell);
|
|
913
|
-
}
|
|
914
|
-
legend.appendChild(more);
|
|
935
|
+
const legend = heatmapLegend(opts.labels);
|
|
915
936
|
const columns = Math.max(1, Math.ceil((pad + entries.length) / 7));
|
|
916
937
|
months.style.setProperty("--columns", String(columns));
|
|
917
938
|
wrap.append(months, weekdayRow, grid, legend);
|
|
918
939
|
body.appendChild(wrap);
|
|
919
940
|
});
|
|
941
|
+
attachChartInteraction(el, "calendar");
|
|
942
|
+
return el;
|
|
920
943
|
}
|
|
921
944
|
function renderScatter(el, opts) {
|
|
922
|
-
if (typeof document === "undefined") return;
|
|
945
|
+
if (typeof document === "undefined") return el;
|
|
923
946
|
withChrome(el, opts, false, (body) => {
|
|
924
947
|
const pts = opts.points.filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y));
|
|
925
948
|
if (pts.length === 0) {
|
|
@@ -964,7 +987,7 @@ function renderScatter(el, opts) {
|
|
|
964
987
|
"font-size": "8",
|
|
965
988
|
fill: "var(--token-text-faint)"
|
|
966
989
|
});
|
|
967
|
-
txt.textContent =
|
|
990
|
+
txt.textContent = fmt(opts, Math.round(tv * 10) / 10);
|
|
968
991
|
svg.appendChild(txt);
|
|
969
992
|
}
|
|
970
993
|
for (let i = 0; i <= 4; i++) {
|
|
@@ -986,7 +1009,7 @@ function renderScatter(el, opts) {
|
|
|
986
1009
|
"font-size": "8",
|
|
987
1010
|
fill: "var(--token-text-faint)"
|
|
988
1011
|
});
|
|
989
|
-
txt.textContent =
|
|
1012
|
+
txt.textContent = fmt(opts, Math.round(tv * 10) / 10);
|
|
990
1013
|
svg.appendChild(txt);
|
|
991
1014
|
}
|
|
992
1015
|
if (opts.yLabel) {
|
|
@@ -1010,7 +1033,7 @@ function renderScatter(el, opts) {
|
|
|
1010
1033
|
"stroke-width": "1"
|
|
1011
1034
|
});
|
|
1012
1035
|
const title = svgEl("title", {});
|
|
1013
|
-
title.textContent = `${p.label ?? `#${i + 1}`}: (${p.x}, ${p.y}${p.size != null ? `, size ${p.size}` : ""})`;
|
|
1036
|
+
title.textContent = `${p.label ?? `#${i + 1}`}: (${fmt(opts, p.x)}, ${fmt(opts, p.y)}${p.size != null ? `, size ${fmt(opts, p.size)}` : ""})`;
|
|
1014
1037
|
c.appendChild(title);
|
|
1015
1038
|
setMarkData(c, { index: i, label: p.label ?? `#${i + 1}`, value: p.y });
|
|
1016
1039
|
c.dataset.chartX = String(p.x);
|
|
@@ -1019,6 +1042,8 @@ function renderScatter(el, opts) {
|
|
|
1019
1042
|
wrap.appendChild(svg);
|
|
1020
1043
|
body.appendChild(wrap);
|
|
1021
1044
|
});
|
|
1045
|
+
attachChartInteraction(el, "scatter");
|
|
1046
|
+
return el;
|
|
1022
1047
|
}
|
|
1023
1048
|
function chartFormatValue(format) {
|
|
1024
1049
|
if (!format) return (v) => String(v);
|
|
@@ -1149,7 +1174,7 @@ function normalizeChartSpec(raw) {
|
|
|
1149
1174
|
}
|
|
1150
1175
|
}
|
|
1151
1176
|
if (!src || typeof src !== "object" || Array.isArray(src)) {
|
|
1152
|
-
if (Array.isArray(
|
|
1177
|
+
if (Array.isArray(src)) src = { data: src };
|
|
1153
1178
|
else return {};
|
|
1154
1179
|
}
|
|
1155
1180
|
const s = src;
|
|
@@ -1219,6 +1244,7 @@ function normalizeChartSpec(raw) {
|
|
|
1219
1244
|
}
|
|
1220
1245
|
return spec;
|
|
1221
1246
|
}
|
|
1247
|
+
var TEMPORAL_HIT_RATIO = 0.6;
|
|
1222
1248
|
function looksTemporal(labels) {
|
|
1223
1249
|
if (labels.length === 0) return false;
|
|
1224
1250
|
let hits = 0;
|
|
@@ -1232,7 +1258,7 @@ function looksTemporal(labels) {
|
|
|
1232
1258
|
else if (/^(v|ver\.?|version[ .]?)?\d+(\.\d+)+$/i.test(l)) hits++;
|
|
1233
1259
|
else if (/^\d{4}$/.test(l)) hits++;
|
|
1234
1260
|
}
|
|
1235
|
-
return hits / labels.length >=
|
|
1261
|
+
return hits / labels.length >= TEMPORAL_HIT_RATIO;
|
|
1236
1262
|
}
|
|
1237
1263
|
function inferChartType(spec) {
|
|
1238
1264
|
if (spec.points && spec.points.length > 0) return "scatter";
|
|
@@ -1267,7 +1293,7 @@ function dispatchChartEvent(root, type, mark, chart, phase, pointer) {
|
|
|
1267
1293
|
pointerX: pointer?.x,
|
|
1268
1294
|
pointerY: pointer?.y
|
|
1269
1295
|
};
|
|
1270
|
-
root
|
|
1296
|
+
emitIcen(root, `icen:chart-${type}`, detail);
|
|
1271
1297
|
}
|
|
1272
1298
|
var tooltipEl = null;
|
|
1273
1299
|
function chartTooltip() {
|
|
@@ -1280,6 +1306,12 @@ function chartTooltip() {
|
|
|
1280
1306
|
}
|
|
1281
1307
|
return tooltipEl;
|
|
1282
1308
|
}
|
|
1309
|
+
function applyTooltipSizing(host) {
|
|
1310
|
+
const tip = chartTooltip();
|
|
1311
|
+
const raw = host?.closest(".chart, [data-chart-root]")?.getAttribute("data-panel-max") ?? null;
|
|
1312
|
+
const n = raw != null ? Number(raw) : NaN;
|
|
1313
|
+
tip.style.maxWidth = Number.isFinite(n) && n > 0 ? `${n}px` : "";
|
|
1314
|
+
}
|
|
1283
1315
|
function tooltipText(mark) {
|
|
1284
1316
|
const label = mark.getAttribute("data-chart-label") ?? "";
|
|
1285
1317
|
const series = mark.getAttribute("data-chart-series-name");
|
|
@@ -1299,6 +1331,101 @@ function moveTooltip(x, y) {
|
|
|
1299
1331
|
tip.style.left = `${Math.round(left)}px`;
|
|
1300
1332
|
tip.style.top = `${Math.round(top)}px`;
|
|
1301
1333
|
}
|
|
1334
|
+
var chartDelegateMeta = /* @__PURE__ */ new WeakMap();
|
|
1335
|
+
var chartDelegateUnbind = /* @__PURE__ */ new WeakMap();
|
|
1336
|
+
function bindChartEvents(root) {
|
|
1337
|
+
const noop = () => void 0;
|
|
1338
|
+
if (typeof document === "undefined") return noop;
|
|
1339
|
+
const attached = root;
|
|
1340
|
+
if (attached.__icenChartBound) {
|
|
1341
|
+
const prev = chartDelegateUnbind.get(root);
|
|
1342
|
+
if (prev) return prev;
|
|
1343
|
+
}
|
|
1344
|
+
for (let p = root.parentElement; p; p = p.parentElement) {
|
|
1345
|
+
if (p.__icenChartBound) return noop;
|
|
1346
|
+
}
|
|
1347
|
+
const typeNow = () => chartDelegateMeta.get(root)?.type() ?? "vbar";
|
|
1348
|
+
const tipEnabled = () => chartDelegateMeta.get(root)?.tooltip() ?? true;
|
|
1349
|
+
const markOf = (e) => {
|
|
1350
|
+
const t = e.target;
|
|
1351
|
+
return t instanceof Element ? t.closest("[data-chart-mark]") : null;
|
|
1352
|
+
};
|
|
1353
|
+
let hoverMark = null;
|
|
1354
|
+
const enterMark = (mark, x, y) => {
|
|
1355
|
+
hoverMark = mark;
|
|
1356
|
+
mark.classList.add("is-hover");
|
|
1357
|
+
dispatchChartEvent(root, "hover", mark, typeNow(), "enter", { x, y });
|
|
1358
|
+
if (tipEnabled()) {
|
|
1359
|
+
const tip = chartTooltip();
|
|
1360
|
+
tip.textContent = tooltipText(mark);
|
|
1361
|
+
applyTooltipSizing(mark);
|
|
1362
|
+
tip.hidden = false;
|
|
1363
|
+
moveTooltip(x, y);
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
const leaveMark = (pointer) => {
|
|
1367
|
+
if (!hoverMark) return;
|
|
1368
|
+
hoverMark.classList.remove("is-hover");
|
|
1369
|
+
dispatchChartEvent(root, "hover", hoverMark, typeNow(), "leave", pointer);
|
|
1370
|
+
hoverMark = null;
|
|
1371
|
+
chartTooltip().hidden = true;
|
|
1372
|
+
};
|
|
1373
|
+
const bindings = [];
|
|
1374
|
+
const on = (type, fn) => {
|
|
1375
|
+
const listener = (evt) => fn(evt);
|
|
1376
|
+
root.addEventListener(type, listener);
|
|
1377
|
+
bindings.push([type, listener]);
|
|
1378
|
+
};
|
|
1379
|
+
on("pointerover", (e) => {
|
|
1380
|
+
const mark = markOf(e);
|
|
1381
|
+
if (!mark || mark === hoverMark) return;
|
|
1382
|
+
leaveMark({ x: e.clientX, y: e.clientY });
|
|
1383
|
+
enterMark(mark, e.clientX, e.clientY);
|
|
1384
|
+
});
|
|
1385
|
+
on("pointermove", (e) => {
|
|
1386
|
+
if (hoverMark && tipEnabled()) {
|
|
1387
|
+
dispatchChartEvent(root, "hover", hoverMark, typeNow(), "move", { x: e.clientX, y: e.clientY });
|
|
1388
|
+
moveTooltip(e.clientX, e.clientY);
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
on("pointerout", (e) => {
|
|
1392
|
+
const mark = markOf(e);
|
|
1393
|
+
if (mark && mark === hoverMark) leaveMark({ x: e.clientX, y: e.clientY });
|
|
1394
|
+
});
|
|
1395
|
+
on("pointerleave", () => leaveMark());
|
|
1396
|
+
on("click", (e) => {
|
|
1397
|
+
const mark = markOf(e);
|
|
1398
|
+
if (mark) dispatchChartEvent(root, "click", mark, typeNow(), void 0, { x: e.clientX, y: e.clientY });
|
|
1399
|
+
});
|
|
1400
|
+
on("dblclick", (e) => {
|
|
1401
|
+
const mark = markOf(e);
|
|
1402
|
+
if (mark) dispatchChartEvent(root, "dblclick", mark, typeNow(), void 0, { x: e.clientX, y: e.clientY });
|
|
1403
|
+
});
|
|
1404
|
+
on("contextmenu", (e) => {
|
|
1405
|
+
const mark = markOf(e);
|
|
1406
|
+
if (!mark) return;
|
|
1407
|
+
e.preventDefault();
|
|
1408
|
+
dispatchChartEvent(root, "contextmenu", mark, typeNow(), void 0, { x: e.clientX, y: e.clientY });
|
|
1409
|
+
});
|
|
1410
|
+
const unbind = () => {
|
|
1411
|
+
for (const [type, listener] of bindings) root.removeEventListener(type, listener);
|
|
1412
|
+
bindings.length = 0;
|
|
1413
|
+
attached.__icenChartBound = false;
|
|
1414
|
+
chartDelegateUnbind.delete(root);
|
|
1415
|
+
if (hoverMark) {
|
|
1416
|
+
hoverMark.classList.remove("is-hover");
|
|
1417
|
+
hoverMark = null;
|
|
1418
|
+
}
|
|
1419
|
+
chartTooltip().hidden = true;
|
|
1420
|
+
};
|
|
1421
|
+
attached.__icenChartBound = true;
|
|
1422
|
+
chartDelegateUnbind.set(root, unbind);
|
|
1423
|
+
return unbind;
|
|
1424
|
+
}
|
|
1425
|
+
function attachChartInteraction(el, type) {
|
|
1426
|
+
chartDelegateMeta.set(el, { type: () => type, tooltip: () => true });
|
|
1427
|
+
bindChartEvents(el);
|
|
1428
|
+
}
|
|
1302
1429
|
function hasLegendOf(spec) {
|
|
1303
1430
|
switch (spec.type) {
|
|
1304
1431
|
case "donut":
|
|
@@ -1321,9 +1448,6 @@ function renderChart(el, raw) {
|
|
|
1321
1448
|
return { el, spec: {}, update: noop, on: () => noop, destroy: noop };
|
|
1322
1449
|
}
|
|
1323
1450
|
const root = el;
|
|
1324
|
-
const attached = root;
|
|
1325
|
-
const wasBound = attached.__icenChartBound === true;
|
|
1326
|
-
attached.__icenChartBound = true;
|
|
1327
1451
|
let current = normalizeChartSpec(raw);
|
|
1328
1452
|
const typeOf = () => current.type ?? "vbar";
|
|
1329
1453
|
const legendState = { hidden: null };
|
|
@@ -1358,10 +1482,10 @@ function renderChart(el, raw) {
|
|
|
1358
1482
|
break;
|
|
1359
1483
|
}
|
|
1360
1484
|
case "heatmap":
|
|
1361
|
-
renderHeatmap(body, { values: spec.values, data: spec.data, weeks: spec.weeks, tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
|
|
1485
|
+
renderHeatmap(body, { values: spec.values, data: spec.data, weeks: spec.weeks, tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend, weekStart: spec.i18n?.weekStart, labels: spec.i18n });
|
|
1362
1486
|
break;
|
|
1363
1487
|
case "calendar":
|
|
1364
|
-
renderCalendar(body, { dates: spec.dates, values: spec.values, data: spec.data, tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
|
|
1488
|
+
renderCalendar(body, { dates: spec.dates, values: spec.values, data: spec.data, tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend, weekStart: spec.i18n?.weekStart, labels: spec.i18n });
|
|
1365
1489
|
break;
|
|
1366
1490
|
case "sparkline":
|
|
1367
1491
|
renderSparkline(body, { labels: spec.labels ?? [], values: spec.values ?? [], tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
|
|
@@ -1377,63 +1501,8 @@ function renderChart(el, raw) {
|
|
|
1377
1501
|
}
|
|
1378
1502
|
});
|
|
1379
1503
|
};
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
const t = e.target;
|
|
1383
|
-
return t instanceof Element ? t.closest("[data-chart-mark]") : null;
|
|
1384
|
-
};
|
|
1385
|
-
const tipEnabled = () => current.tooltip !== false;
|
|
1386
|
-
let hoverMark = null;
|
|
1387
|
-
const enterMark = (mark, x, y) => {
|
|
1388
|
-
hoverMark = mark;
|
|
1389
|
-
mark.classList.add("is-hover");
|
|
1390
|
-
dispatchChartEvent(root, "hover", mark, typeOf(), "enter", { x, y });
|
|
1391
|
-
if (tipEnabled()) {
|
|
1392
|
-
const tip = chartTooltip();
|
|
1393
|
-
tip.textContent = tooltipText(mark);
|
|
1394
|
-
tip.hidden = false;
|
|
1395
|
-
moveTooltip(x, y);
|
|
1396
|
-
}
|
|
1397
|
-
};
|
|
1398
|
-
const leaveMark = (pointer) => {
|
|
1399
|
-
if (!hoverMark) return;
|
|
1400
|
-
hoverMark.classList.remove("is-hover");
|
|
1401
|
-
dispatchChartEvent(root, "hover", hoverMark, typeOf(), "leave", pointer);
|
|
1402
|
-
hoverMark = null;
|
|
1403
|
-
chartTooltip().hidden = true;
|
|
1404
|
-
};
|
|
1405
|
-
root.addEventListener("pointerover", (e) => {
|
|
1406
|
-
const mark = markOf(e);
|
|
1407
|
-
if (!mark || mark === hoverMark) return;
|
|
1408
|
-
leaveMark({ x: e.clientX, y: e.clientY });
|
|
1409
|
-
enterMark(mark, e.clientX, e.clientY);
|
|
1410
|
-
});
|
|
1411
|
-
root.addEventListener("pointermove", (e) => {
|
|
1412
|
-
if (hoverMark && tipEnabled()) {
|
|
1413
|
-
dispatchChartEvent(root, "hover", hoverMark, typeOf(), "move", { x: e.clientX, y: e.clientY });
|
|
1414
|
-
moveTooltip(e.clientX, e.clientY);
|
|
1415
|
-
}
|
|
1416
|
-
});
|
|
1417
|
-
root.addEventListener("pointerout", (e) => {
|
|
1418
|
-
const mark = markOf(e);
|
|
1419
|
-
if (mark && mark === hoverMark) leaveMark({ x: e.clientX, y: e.clientY });
|
|
1420
|
-
});
|
|
1421
|
-
root.addEventListener("pointerleave", () => leaveMark());
|
|
1422
|
-
root.addEventListener("click", (e) => {
|
|
1423
|
-
const mark = markOf(e);
|
|
1424
|
-
if (mark) dispatchChartEvent(root, "click", mark, typeOf(), void 0, { x: e.clientX, y: e.clientY });
|
|
1425
|
-
});
|
|
1426
|
-
root.addEventListener("dblclick", (e) => {
|
|
1427
|
-
const mark = markOf(e);
|
|
1428
|
-
if (mark) dispatchChartEvent(root, "dblclick", mark, typeOf(), void 0, { x: e.clientX, y: e.clientY });
|
|
1429
|
-
});
|
|
1430
|
-
root.addEventListener("contextmenu", (e) => {
|
|
1431
|
-
const mark = markOf(e);
|
|
1432
|
-
if (!mark) return;
|
|
1433
|
-
e.preventDefault();
|
|
1434
|
-
dispatchChartEvent(root, "contextmenu", mark, typeOf(), void 0, { x: e.clientX, y: e.clientY });
|
|
1435
|
-
});
|
|
1436
|
-
}
|
|
1504
|
+
chartDelegateMeta.set(root, { type: typeOf, tooltip: () => current.tooltip !== false });
|
|
1505
|
+
const unbindEvents = bindChartEvents(root);
|
|
1437
1506
|
render();
|
|
1438
1507
|
return {
|
|
1439
1508
|
el: root,
|
|
@@ -1451,15 +1520,17 @@ function renderChart(el, raw) {
|
|
|
1451
1520
|
},
|
|
1452
1521
|
destroy() {
|
|
1453
1522
|
root.textContent = "";
|
|
1454
|
-
|
|
1523
|
+
unbindEvents();
|
|
1455
1524
|
chartTooltip().hidden = true;
|
|
1456
1525
|
}
|
|
1457
1526
|
};
|
|
1458
1527
|
}
|
|
1459
1528
|
export {
|
|
1529
|
+
bindChartEvents,
|
|
1460
1530
|
chartFormatValue,
|
|
1461
1531
|
inferChartType,
|
|
1462
1532
|
normalizeChartSpec,
|
|
1533
|
+
registerChartTone,
|
|
1463
1534
|
renderArea,
|
|
1464
1535
|
renderCalendar,
|
|
1465
1536
|
renderChart,
|