bezel-charts 1.2.0 → 1.4.0

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.
@@ -1,28 +1,4 @@
1
- /*! Bezel Charts v1.2.0 | MIT | github.com/ShazMoghaddam/bezel-charts */
2
-
3
- // src/core/themes.js
4
- var THEMES = {
5
- dark: {
6
- tile: "#15151d",
7
- bezel: "#000000",
8
- fg: "#f2f1f7",
9
- muted: "#8c8aa0",
10
- grid: "#2a2935",
11
- up: "#5EE6C8",
12
- down: "#F675A8",
13
- palette: ["#9FA1FF", "#F675A8", "#5EE6C8", "#FFC857", "#7CC4FF", "#FF8A5B"]
14
- },
15
- light: {
16
- tile: "#ffffff",
17
- bezel: "#000000",
18
- fg: "#17161f",
19
- muted: "#6b6980",
20
- grid: "#e8e6ef",
21
- up: "#12a383",
22
- down: "#d63f7c",
23
- palette: ["#6E70F0", "#E2508C", "#16A884", "#D99A0E", "#2F8FE0", "#EE6431"]
24
- }
25
- };
1
+ /*! Bezel Charts v1.4.0 | MIT | github.com/ShazMoghaddam/bezel-charts */
26
2
 
27
3
  // src/core/util.js
28
4
  var SANS = "ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif";
@@ -106,23 +82,6 @@ function fmt(n, o, short) {
106
82
  }
107
83
  return (n < 0 ? "\u2212" : "") + (o.prefix || "") + s + (o.suffix || "");
108
84
  }
109
- function niceScale(min, max, count) {
110
- if (min === max) {
111
- if (min === 0) max = 1;
112
- else {
113
- var p = Math.abs(min) * 0.1;
114
- min -= p;
115
- max += p;
116
- }
117
- }
118
- var step0 = (max - min) / (count || 4);
119
- var mag = Math.pow(10, Math.floor(Math.log10(step0)));
120
- var err = step0 / mag;
121
- var step = (err <= 1 ? 1 : err <= 2 ? 2 : err <= 5 ? 5 : 10) * mag;
122
- var lo = Math.floor(min / step) * step, hi = Math.ceil(max / step) * step, ticks = [];
123
- for (var t = lo; t <= hi + step / 2; t += step) ticks.push(+t.toFixed(10));
124
- return { lo, hi, ticks };
125
- }
126
85
  function pathOf(pts, straight) {
127
86
  if (!pts.length) return "";
128
87
  var d = "M" + r2(pts[0][0]) + "," + r2(pts[0][1]);
@@ -180,67 +139,6 @@ function statOf(v, o, fallback) {
180
139
  if (k === "last" && v.length > 1 && o.delta !== "none") delta = v[v.length - 1] - v[0];
181
140
  return { val: map[k], delta };
182
141
  }
183
- function legend(names, pal, th, x0, y0, w, key, kw) {
184
- var x = x0, y = y0, s = "", off = kw || 13;
185
- names.forEach(function(nm, k) {
186
- var wd = off + tw(nm, 12) + 16;
187
- if (x > x0 && x + wd - 16 > x0 + w) {
188
- x = x0;
189
- y += 20;
190
- }
191
- s += (key ? key(k, x, y) : '<circle cx="' + r2(x + 4) + '" cy="' + r2(y + 6) + '" r="4" fill="' + pal[k % pal.length] + '"/>') + text(x + off, y + 10, nm, 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"');
192
- x += wd;
193
- });
194
- return { svg: s, height: y - y0 + 24 };
195
- }
196
- var DASHES = { solid: "", dashed: "7 5", dotted: "1 5", dashdot: "9 4 1 4" };
197
- var SHAPES = ["circle", "square", "triangle", "diamond"];
198
- var PATTERNS = ["solid", "stripes", "dots", "grid"];
199
- function styleOf(v, names, off) {
200
- if (v == null || v === false || v === "none" || v === "false") return null;
201
- var l = v === true || v === "" || v === "auto" ? names : list(v).map(function(s) {
202
- return names.indexOf(s) < 0 ? off : s;
203
- });
204
- if (!l.length) return null;
205
- return function(k) {
206
- return l[k % l.length];
207
- };
208
- }
209
- function marker(kind, x, y, r, attrs) {
210
- var a = r * 1.15, b = r * 0.85;
211
- if (kind === "square") return '<rect x="' + r2(x - b) + '" y="' + r2(y - b) + '" width="' + r2(2 * b) + '" height="' + r2(2 * b) + '" rx="1"' + attrs + "/>";
212
- if (kind === "triangle") return '<path d="M' + r2(x) + "," + r2(y - a) + "L" + r2(x + a) + "," + r2(y + b) + "L" + r2(x - a) + "," + r2(y + b) + 'Z"' + attrs + "/>";
213
- if (kind === "diamond") return '<path d="M' + r2(x) + "," + r2(y - a * 1.1) + "L" + r2(x + a) + "," + r2(y) + "L" + r2(x) + "," + r2(y + a * 1.1) + "L" + r2(x - a) + "," + r2(y) + 'Z"' + attrs + "/>";
214
- return '<circle cx="' + r2(x) + '" cy="' + r2(y) + '" r="' + r2(r) + '"' + attrs + "/>";
215
- }
216
- function pattern(id, kind, c, bg) {
217
- var ink = ' stroke="' + bg + '" stroke-opacity=".55" stroke-width="1.5"';
218
- return '<pattern id="' + id + '" width="6" height="6" patternUnits="userSpaceOnUse"' + (kind === "dots" ? "" : ' patternTransform="rotate(45)"') + '><rect width="6" height="6" fill="' + c + '"/>' + (kind === "dots" ? '<circle cx="3" cy="3" r="1.3" fill="' + bg + '" fill-opacity=".55"/>' : '<path d="M3,0V6' + (kind === "grid" ? "M0,3H6" : "") + '"' + ink + "/>") + "</pattern>";
219
- }
220
- function axisWidth(ticks) {
221
- return Math.max.apply(null, ticks.map(function(t) {
222
- return tw(t, 10, true);
223
- })) + 10;
224
- }
225
- function seriesTable(o, total) {
226
- var S = series(o.values), names = list(o.series), labels = list(o.labels), rows = [];
227
- var n = Math.max.apply(null, [0].concat(S.map(function(s) {
228
- return s.length;
229
- })));
230
- for (var i = 0; i < n; i++) {
231
- var row = [labels[i] || "#" + (i + 1)], t = 0;
232
- S.forEach(function(s) {
233
- row.push(i < s.length ? fmt(s[i], o) : "");
234
- t += i < s.length ? s[i] : 0;
235
- });
236
- if (total) row.push(fmt(t, o));
237
- rows.push(row);
238
- }
239
- var head = S.length > 1 || names.length ? S.map(function(_, k) {
240
- return names[k] || "Series " + (k + 1);
241
- }) : ["Value"];
242
- return { head: ["Label"].concat(head, total ? ["Total"] : []), rows };
243
- }
244
142
 
245
143
  // src/core/tile.js
246
144
  function dims(o) {
@@ -345,179 +243,764 @@ spark.table = function(o) {
345
243
  }) };
346
244
  };
347
245
 
348
- // src/charts/line.js
349
- function line(o, th) {
350
- var S = series(o.values), names = list(o.series), labels = list(o.labels), pal = palette(o, th);
351
- var h = num(o.height, 170), n = Math.max.apply(null, [0].concat(S.map(function(s) {
352
- return s.length;
353
- })));
354
- var notes = parseNotes(o, labels), multi = S.length > 1 || names.length > 0;
355
- var st = statOf(S[0] || [], o, "");
356
- var dashOf = styleOf(o.dash, Object.keys(DASHES), "solid"), markOf = styleOf(o.markers, SHAPES, "none");
357
- var bg = o.background || th.tile;
358
- var key = (dashOf || markOf) && function(k, x, y) {
359
- var c = pal[k % pal.length], dk = dashOf ? DASHES[dashOf(k)] : "", mk = markOf ? markOf(k) : "none";
360
- return '<line x1="' + r2(x) + '" x2="' + r2(x + 18) + '" y1="' + r2(y + 6) + '" y2="' + r2(y + 6) + '" stroke="' + c + '" stroke-width="2.5" stroke-linecap="round"' + (dk ? ' stroke-dasharray="' + dk + '"' : "") + "/>" + (mk !== "none" ? marker(mk, x + 9, y + 6, 3.5, ' fill="' + c + '" stroke="' + bg + '" stroke-width="1.5"') : "");
361
- };
362
- return tile(o, th, h, function(x0, y0, w, h2) {
363
- if (!n) return emptyState(th, x0, y0, w, h2);
364
- var s = "", top = y0;
365
- if (multi) {
366
- var lg = legend(S.map(function(_, k) {
367
- return names[k] || "Series " + (k + 1);
368
- }), pal, th, x0, top, w, key, key && 24);
369
- s += lg.svg;
370
- top += lg.height;
371
- }
372
- var all = [].concat.apply([], S);
373
- var mn = Math.min.apply(null, all), mx = Math.max.apply(null, all);
374
- if (has(o.goal)) {
375
- mn = Math.min(mn, +o.goal);
376
- mx = Math.max(mx, +o.goal);
377
- }
378
- if (bool(o.zero)) {
379
- mn = Math.min(0, mn);
380
- mx = Math.max(0, mx);
381
- }
382
- var sc = niceScale(mn, mx, 4);
383
- var tl = sc.ticks.map(function(t) {
384
- return fmt(t, o, true);
385
- });
386
- var axisW = Math.max.apply(null, tl.map(function(t) {
387
- return tw(t, 10, true);
388
- })) + 10;
389
- var bottom = labels.length ? 20 : 4;
390
- var px = x0 + axisW, pw = w - axisW, ph = h2 - (top - y0) - bottom;
391
- var X = function(i2) {
392
- return n === 1 ? px + pw / 2 : px + i2 * pw / (n - 1);
393
- };
394
- var Y = function(v) {
395
- return top + ph - (v - sc.lo) / (sc.hi - sc.lo) * ph;
396
- };
397
- sc.ticks.forEach(function(t, i2) {
398
- var y = Y(t);
399
- s += '<line x1="' + r2(px) + '" x2="' + r2(px + pw) + '" y1="' + r2(y) + '" y2="' + r2(y) + '" stroke="' + th.grid + '"' + (t === 0 ? "" : ' stroke-dasharray="2 4"') + "/>" + text(px - 8, y + 3.5, tl[i2], small(th, 10, 'text-anchor="end"'));
400
- });
401
- if (labels.length) {
402
- var every = Math.max(1, Math.ceil(n * (Math.max.apply(null, labels.map(function(l) {
403
- return tw(l, 10, true);
404
- })) + 10) / pw));
405
- for (var i = 0; i < n; i += every) {
406
- if (labels[i] == null) continue;
407
- var anchor = n > 1 && i === 0 ? "start" : n > 1 && i === n - 1 ? "end" : "middle";
408
- s += text(X(i), top + ph + 15, labels[i], small(th, 10, 'text-anchor="' + anchor + '"'));
409
- }
410
- }
411
- notes.forEach(function(nt) {
412
- if (nt.i >= n) return;
413
- var x = X(nt.i);
414
- s += '<line x1="' + r2(x) + '" x2="' + r2(x) + '" y1="' + r2(top + 12) + '" y2="' + r2(top + ph) + '" stroke="' + th.fg + '" stroke-opacity=".4" stroke-dasharray="3 3"/>' + text(clamp(x, px + 30, px + pw - 30), top + 8, nt.text, small(th, 10, 'text-anchor="middle"', th.fg));
415
- });
416
- S.forEach(function(v, k) {
417
- var c = pal[k % pal.length];
418
- var P = v.map(function(y, i2) {
419
- return [X(i2), Y(y)];
420
- });
421
- if (P.length === 1) P = [[px, P[0][1]], [px + pw, P[0][1]]];
422
- var d = pathOf(P, bool(o.straight));
423
- if (k === 0) {
424
- var g = newId("g"), last = P[P.length - 1];
425
- s += '<defs><linearGradient id="' + g + '" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="' + c + '" stop-opacity=".26"/><stop offset="1" stop-color="' + c + '" stop-opacity="0"/></linearGradient></defs><path d="' + d + "L" + r2(last[0]) + "," + r2(top + ph) + "L" + r2(P[0][0]) + "," + r2(top + ph) + 'Z" fill="url(#' + g + ')"' + anim(o, "bzc-a") + "/>";
426
- }
427
- var dk = dashOf ? DASHES[dashOf(k)] : "", mk = markOf ? markOf(k) : "none";
428
- s += dk ? '<path d="' + d + '" fill="none" stroke="' + c + '" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="' + dk + '"' + anim(o, "bzc-a", k * 0.15) + "/>" : '<path d="' + d + '" fill="none" stroke="' + c + '" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" pathLength="1" style="stroke-dasharray:1' + (o.animate ? ";animation-delay:" + k * 0.15 + "s" : "") + '"' + (o.animate ? ' class="bzc-d"' : "") + "/>";
429
- if (mk !== "none") {
430
- var every2 = Math.ceil(v.length / 40);
431
- v.forEach(function(y, i2) {
432
- if (i2 % every2 === 0 || i2 === v.length - 1) s += marker(mk, X(i2), Y(y), 3.5, ' fill="' + c + '" stroke="' + bg + '" stroke-width="1.5"' + anim(o, "bzc-a", 0.6 + k * 0.15));
433
- });
434
- } else {
435
- var e = P[P.length - 1];
436
- s += '<circle cx="' + r2(e[0]) + '" cy="' + r2(e[1]) + '" r="4" fill="' + c + '" stroke="' + bg + '" stroke-width="2"/>';
437
- }
438
- if (bool(o.showValues)) v.forEach(function(y, i2) {
439
- s += text(X(i2), Y(y) - 8, fmt(y, o, true), small(th, 9, 'text-anchor="middle"'));
440
- });
441
- });
442
- if (has(o.goal)) s += goalLine(o, th, px, px + pw, Y(+o.goal));
443
- var step = n > 1 ? pw / (n - 1) : pw;
444
- for (var j = 0; j < n; j++) {
445
- var tip = labels[j] || "#" + (j + 1);
446
- S.forEach(function(v, k) {
447
- if (j < v.length) tip += "\n" + (multi ? (names[k] || "Series " + (k + 1)) + ": " : "") + fmt(v[j], o);
448
- });
449
- s += hit(X(j) - step / 2, top, step, ph, tip);
450
- }
451
- return s;
452
- }, { stat: st, summary: S.length + " series over " + n + " points" });
453
- }
454
- line.table = function(o) {
455
- return seriesTable(o);
246
+ // src/core/themes.js
247
+ var THEMES = {
248
+ dark: {
249
+ tile: "#15151d",
250
+ bezel: "#000000",
251
+ fg: "#f2f1f7",
252
+ muted: "#8c8aa0",
253
+ grid: "#2a2935",
254
+ up: "#5EE6C8",
255
+ down: "#F675A8",
256
+ palette: ["#9FA1FF", "#F675A8", "#5EE6C8", "#FFC857", "#7CC4FF", "#FF8A5B"]
257
+ },
258
+ light: {
259
+ tile: "#ffffff",
260
+ bezel: "#000000",
261
+ fg: "#17161f",
262
+ muted: "#6b6980",
263
+ grid: "#e8e6ef",
264
+ up: "#12a383",
265
+ down: "#d63f7c",
266
+ palette: ["#6E70F0", "#E2508C", "#16A884", "#D99A0E", "#2F8FE0", "#EE6431"]
267
+ }
456
268
  };
457
269
 
458
- // src/charts/bars.js
459
- var ANGLE = 40;
460
- var ANGLE_MAX_CHARS = 24;
461
- function bars(o, th) {
462
- var S = series(o.values), names = list(o.series), labels = list(o.labels), pal = palette(o, th);
463
- var k = S.length, n = Math.max.apply(null, [0].concat(S.map(function(s) {
464
- return s.length;
465
- })));
466
- var grouped = k > 1, horiz = bool(o.horizontal), stacked = grouped && bool(o.stacked);
467
- var many = !grouped && list(o.color).filter(Boolean).length > 1;
468
- var v0 = S[0] || [], all = [].concat.apply([], S);
469
- var sumOf = function(i, sign) {
470
- return S.reduce(function(t2, s) {
471
- return i < s.length && s[i] * sign > 0 ? t2 + s[i] : t2;
472
- }, 0);
473
- };
474
- var totals = [], ups = [], downs = [];
475
- for (var t = 0; stacked && t < n; t++) {
476
- ups.push(sumOf(t, 1));
477
- downs.push(sumOf(t, -1));
478
- totals.push(ups[t] + downs[t]);
479
- }
480
- var scale = stacked ? ups.concat(downs) : all;
481
- var top = scale.length ? Math.max.apply(null, scale) : 0;
482
- var nameOf = function(j) {
483
- return names[j] || "Series " + (j + 1);
484
- };
485
- var patOf = grouped ? styleOf(o.patterns, PATTERNS, "solid") : null, defs = "", fills = [];
486
- for (var p = 0; patOf && p < k; p++) {
487
- if (patOf(p) === "solid") continue;
488
- var pid = newId("g");
489
- defs += pattern(pid, patOf(p), pal[p % pal.length], o.background || th.tile);
490
- fills[p] = "url(#" + pid + ")";
270
+ // src/render.js
271
+ var NAMES = ["spark", "line", "area", "bars", "scatter", "ring", "donut", "gauge", "progress", "heatmap"];
272
+ var TYPES = {};
273
+ function register(type, fn) {
274
+ TYPES[type] = fn;
275
+ }
276
+ var camel = (k) => k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
277
+ function prepare(type, options) {
278
+ if (!TYPES[type]) {
279
+ throw new Error(NAMES.includes(type) ? `Bezel Charts: the ${type} chart isn't loaded. Add import 'bezel-charts/${type}', or load dist/charts/${type}.js.` : `Bezel Charts: unknown chart type "${type}". Choose one of: ${NAMES.join(", ")}.`);
491
280
  }
492
- var colourOf = function(j, i) {
493
- return grouped ? fills[j] || pal[j % pal.length] : many ? pal[i % pal.length] : pal[0];
494
- };
495
- var key = patOf && function(j, x, y) {
496
- return '<rect x="' + r2(x) + '" y="' + r2(y + 1) + '" width="10" height="10" rx="2" fill="' + colourOf(j) + '"/>';
497
- };
498
- var opac = function(val) {
499
- return grouped || many || o.highlight === "none" || val === top ? 1 : 0.5;
500
- };
501
- var labelOf = function(i) {
502
- return labels[i] || "#" + (i + 1);
503
- };
504
- var tipOf = function(i) {
505
- if (!grouped) return labelOf(i) + ": " + fmt(v0[i], o);
506
- return labelOf(i) + S.map(function(s, j) {
507
- return i < s.length ? "\n" + nameOf(j) + ": " + fmt(s[i], o) : "";
508
- }).join("") + (stacked ? "\nTotal: " + fmt(totals[i], o) : "");
509
- };
510
- var d0 = dims(o);
511
- var legendH = grouped || names.length ? legend(S.map(function(_, j) {
512
- return nameOf(j);
513
- }), pal, th, 0, 0, d0.w, key, key && 15).height : 0;
514
- var rows = stacked ? 1 : k, thin = grouped && !stacked;
515
- var subH = thin ? 10 : 14, subGap = 3;
516
- var rowH = horiz ? rows * subH + (rows - 1) * subGap + (thin ? 14 : 16) : 0;
517
- var h = horiz ? legendH + Math.max(1, n) * rowH - 6 : num(o.height, 140) + legendH;
518
- var best = v0.indexOf(Math.max.apply(null, v0.length ? v0 : [0]));
519
- return tile(o, th, h, function(x0, y0, w, h2) {
520
- if (!n) return emptyState(th, x0, y0, w, h2);
281
+ const o = {};
282
+ for (const [k, v] of Object.entries(options)) o[camel(k)] = v;
283
+ return o;
284
+ }
285
+ function render(type, options = {}) {
286
+ const o = prepare(type, options);
287
+ o.animate = bool(o.animate);
288
+ o.frame = o.frame === "none" ? "none" : "bezel";
289
+ const mode = o.theme === "light" || o.theme === "dark" ? o.theme : o.autoTheme || "dark";
290
+ return TYPES[type](o, THEMES[mode]);
291
+ }
292
+ function table(type, options = {}) {
293
+ return TYPES[type].table(prepare(type, options));
294
+ }
295
+ function toStatic(svg) {
296
+ return svg.replace(/<rect class="bzc-hit"[^>]*\/>/g, "").replace(/<circle[^>]* class="bzc-hit"[^>]*\/>/g, "").replace(/ data-tip="[^"]*"/g, "");
297
+ }
298
+ function toSVG(type, options = {}) {
299
+ return toStatic(render(type, { ...options, animate: false }));
300
+ }
301
+
302
+ // src/element.js
303
+ var ATTRIBUTES = [
304
+ "type",
305
+ "values",
306
+ "labels",
307
+ "series",
308
+ "heading",
309
+ "prefix",
310
+ "suffix",
311
+ "decimals",
312
+ "compact",
313
+ "color",
314
+ "theme",
315
+ "frame",
316
+ "width",
317
+ "height",
318
+ "goal",
319
+ "goal-label",
320
+ "notes",
321
+ "show-values",
322
+ "stat",
323
+ "delta",
324
+ "delta-label",
325
+ "max",
326
+ "min",
327
+ "value",
328
+ "zones",
329
+ "horizontal",
330
+ "straight",
331
+ "zero",
332
+ "start",
333
+ "lang",
334
+ "corners",
335
+ "background",
336
+ "center-label",
337
+ "highlight",
338
+ "x",
339
+ "y",
340
+ "x-label",
341
+ "y-label",
342
+ "trend",
343
+ "static",
344
+ "no-tooltips",
345
+ "stacked",
346
+ "angle-labels",
347
+ "dash",
348
+ "markers",
349
+ "patterns"
350
+ ];
351
+ var HOST_CSS = `:host{display:block;position:relative;max-width:100%}:host([hidden]){display:none}svg{display:block;max-width:100%;height:auto;overflow:visible}.tip{position:absolute;left:0;top:0;pointer-events:none;background:#0b0b10;color:#f2f1f7;font:600 12px/1.5 ${MONO};padding:7px 10px;border-radius:10px;white-space:pre;opacity:0;transform:translate(-50%,calc(-100% - 12px));transition:opacity .12s;z-index:3;border:1px solid #2a2935;box-shadow:0 8px 24px rgba(0,0,0,.35)}.tip.on{opacity:1}.c{outline:none}.c:focus-visible{outline:2px solid #fff;outline-offset:3px;box-shadow:0 0 0 7px #000}.k{stroke:currentColor;stroke-width:2;stroke-opacity:1}.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}@media (forced-colors:active){.c:focus-visible{outline-color:Highlight}}`;
352
+ var STEPS = { ArrowLeft: -1, ArrowUp: -1, ArrowRight: 1, ArrowDown: 1 };
353
+ var darkQuery = () => typeof matchMedia === "function" ? matchMedia("(prefers-color-scheme: dark)") : null;
354
+ var pageTheme = () => {
355
+ const t = typeof document !== "undefined" && document.documentElement?.getAttribute?.("data-theme");
356
+ return t === "light" || t === "dark" ? t : null;
357
+ };
358
+ var live = /* @__PURE__ */ new Set();
359
+ if (typeof MutationObserver === "function" && typeof document !== "undefined" && document.documentElement) {
360
+ new MutationObserver(() => live.forEach((el) => el.refresh())).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
361
+ }
362
+ var Base = typeof HTMLElement === "undefined" ? class {
363
+ } : HTMLElement;
364
+ var BezelChart = class extends Base {
365
+ static get observedAttributes() {
366
+ return ATTRIBUTES;
367
+ }
368
+ /** The default colour sets, so tools like the Studio can show them. */
369
+ static get themes() {
370
+ return THEMES;
371
+ }
372
+ /** Which theme the chart is drawing in right now: "dark" or "light". */
373
+ get resolvedTheme() {
374
+ const t = this.getAttribute("theme");
375
+ return t === "light" || t === "dark" ? t : this.options.autoTheme;
376
+ }
377
+ /** The chart type this tag draws. Subclasses fix it; <bezel-chart> reads its type attribute. */
378
+ get chartType() {
379
+ return this.getAttribute("type") || "spark";
380
+ }
381
+ connectedCallback() {
382
+ if (!this.shadowRoot) {
383
+ this.attachShadow({ mode: "open" }).innerHTML = `<style>${HOST_CSS}</style><div class="c" role="group" aria-roledescription="chart"></div><p class="sr" id="h">Use the arrow keys to step through the values.</p><div class="tip" aria-hidden="true"></div><div class="sr" aria-live="polite"></div><div class="sr"></div>`;
384
+ [, this.#box, , this.#tipEl, this.#live, this.#table] = this.shadowRoot.children;
385
+ this.shadowRoot.addEventListener("pointermove", (e) => this.#tip(e));
386
+ this.shadowRoot.addEventListener("pointerdown", (e) => this.#tip(e));
387
+ this.addEventListener("pointerleave", () => this.#tipEl.classList.remove("on"));
388
+ this.#box.addEventListener("keydown", (e) => this.#key(e));
389
+ this.#box.addEventListener("blur", () => this.#clear());
390
+ }
391
+ live.add(this);
392
+ this.#dark = darkQuery();
393
+ this.#dark?.addEventListener?.("change", this.#onScheme);
394
+ if (typeof ResizeObserver === "function") {
395
+ this.#ro = new ResizeObserver(([entry]) => {
396
+ const w = Math.round(entry.contentRect.width);
397
+ if (!this.#rendered || !this.hasAttribute("width") && w > 0 && Math.abs(w - this.#width) > 1) this.#render();
398
+ });
399
+ this.#ro.observe(this);
400
+ } else {
401
+ this.#render();
402
+ }
403
+ }
404
+ disconnectedCallback() {
405
+ live.delete(this);
406
+ this.#ro?.disconnect();
407
+ this.#dark?.removeEventListener?.("change", this.#onScheme);
408
+ }
409
+ attributeChangedCallback() {
410
+ if (!this.#rendered || this.#queued) return;
411
+ this.#queued = true;
412
+ queueMicrotask(() => {
413
+ this.#queued = false;
414
+ this.#render();
415
+ });
416
+ }
417
+ /** Current settings as a plain object, the same shape BezelCharts.render() takes. */
418
+ get options() {
419
+ const o = {};
420
+ for (const name of ATTRIBUTES) if (this.hasAttribute(name)) o[name] = this.getAttribute(name);
421
+ if (!o.width) o.width = this.#width || 360;
422
+ o.autoTheme = pageTheme() || (this.#dark ? this.#dark.matches ? "dark" : "light" : "dark");
423
+ return o;
424
+ }
425
+ /** Redraws the chart without replaying its entrance animation. */
426
+ refresh() {
427
+ if (this.#rendered) this.#render();
428
+ }
429
+ /** The chart as a standalone static SVG string. */
430
+ toSVG(extra = {}) {
431
+ return toStatic(render(this.chartType, { ...this.options, ...extra, animate: false }));
432
+ }
433
+ /** Saves the chart as an .svg file. */
434
+ download(name) {
435
+ const url = URL.createObjectURL(new Blob([this.toSVG()], { type: "image/svg+xml" }));
436
+ const a = Object.assign(document.createElement("a"), {
437
+ href: url,
438
+ download: `${(name || this.getAttribute("heading") || this.chartType).replace(/[^\w-]+/g, "-").toLowerCase()}.svg`
439
+ });
440
+ document.body.append(a);
441
+ a.click();
442
+ a.remove();
443
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
444
+ }
445
+ #ro = null;
446
+ #box = null;
447
+ #tipEl = null;
448
+ #live = null;
449
+ #table = null;
450
+ #at = -1;
451
+ #dark = null;
452
+ #width = 0;
453
+ #rendered = false;
454
+ #queued = false;
455
+ #onScheme = () => {
456
+ const t = this.getAttribute("theme");
457
+ if (!t || t === "auto") this.refresh();
458
+ };
459
+ #render() {
460
+ if (!this.shadowRoot) return;
461
+ const w = Math.round(this.getBoundingClientRect?.().width || 0);
462
+ this.#width = w > 0 ? w : 360;
463
+ const o = this.options;
464
+ o.animate = !this.#rendered && !this.hasAttribute("static");
465
+ let svg, data = null;
466
+ try {
467
+ svg = render(this.chartType, o);
468
+ data = table(this.chartType, o);
469
+ } catch (err) {
470
+ svg = `<p style="font:13px ${SANS};color:#d63f7c">${esc(err.message)}</p>`;
471
+ }
472
+ const box = this.#box;
473
+ box.innerHTML = svg.replace(/<title>[\s\S]*?<\/title>/, "");
474
+ const img = box.querySelector("svg");
475
+ if (img) {
476
+ box.setAttribute("aria-label", img.getAttribute("aria-label"));
477
+ img.removeAttribute("role");
478
+ img.removeAttribute("aria-label");
479
+ img.setAttribute("aria-hidden", "true");
480
+ } else {
481
+ box.removeAttribute("aria-label");
482
+ }
483
+ const keys = !!img && !this.hasAttribute("no-tooltips");
484
+ if (keys) {
485
+ box.setAttribute("tabindex", "0");
486
+ box.setAttribute("aria-describedby", "h");
487
+ } else {
488
+ box.removeAttribute("tabindex");
489
+ box.removeAttribute("aria-describedby");
490
+ }
491
+ box.style.color = THEMES[o.theme === "light" || o.theme === "dark" ? o.theme : o.autoTheme].fg;
492
+ box.style.borderRadius = `${o.frame === "none" ? 4 : clamp(+o.corners || 22, 0, 60)}px`;
493
+ this.#table.innerHTML = data && data.rows.length ? tableHTML(o.heading, data) : "";
494
+ if (this.#at >= 0 && keys && this.shadowRoot.activeElement === box) this.#show(this.#at);
495
+ else this.#at = -1;
496
+ this.#rendered = true;
497
+ this.dispatchEvent(new CustomEvent("bezel-render", { bubbles: true }));
498
+ }
499
+ #tip(e) {
500
+ if (this.hasAttribute("no-tooltips")) return;
501
+ const target = e.target?.closest?.("[data-tip]");
502
+ if (!target) {
503
+ this.#tipEl.classList.remove("on");
504
+ return;
505
+ }
506
+ this.#place(target, e.clientX, e.clientY);
507
+ }
508
+ /** Shows the tooltip for a mark at a point on screen. */
509
+ #place(mark, x, y) {
510
+ const tip = this.#tipEl;
511
+ tip.textContent = mark.getAttribute("data-tip");
512
+ const box = this.getBoundingClientRect?.() || { left: 0, top: 0, width: 0 };
513
+ const half = tip.offsetWidth / 2;
514
+ tip.style.left = `${clamp(x - box.left, half, Math.max(half, box.width - half))}px`;
515
+ tip.style.top = `${y - box.top}px`;
516
+ tip.classList.add("on");
517
+ }
518
+ /** Every value the arrow keys visit, in order. Donuts mark their slices data-skip, as the legend rows repeat them. */
519
+ #marks() {
520
+ return [...this.#box.querySelectorAll("[data-tip]:not([data-skip])")];
521
+ }
522
+ #key(e) {
523
+ const marks = this.#marks(), n = marks.length;
524
+ if (!n) return;
525
+ let i = this.#at, step = STEPS[e.key];
526
+ if (step) {
527
+ if (this.chartType === "heatmap" && /Left|Right/.test(e.key)) step *= 7;
528
+ i = i < 0 ? step > 0 ? 0 : n - 1 : clamp(i + step, 0, n - 1);
529
+ } else if (e.key === "Home" || e.key === "End") {
530
+ i = e.key === "Home" ? 0 : n - 1;
531
+ } else {
532
+ if (e.key === "Escape") this.#clear();
533
+ return;
534
+ }
535
+ e.preventDefault();
536
+ this.#show(i, true);
537
+ }
538
+ /** Outlines value i, shows its tooltip and, when say is set, reads it out. */
539
+ #show(i, say) {
540
+ const marks = this.#marks();
541
+ if (!marks.length) return;
542
+ i = clamp(i, 0, marks.length - 1);
543
+ this.#box.querySelector(".k")?.classList.remove("k");
544
+ const mark = marks[i], r = mark.getBoundingClientRect?.() || { left: 0, top: 0, width: 0 };
545
+ mark.classList.add("k");
546
+ this.#at = i;
547
+ this.#place(mark, r.left + r.width / 2, r.top);
548
+ if (say) this.#live.textContent = `${mark.getAttribute("data-tip").replace(/\n/g, ", ")}. ${i + 1} of ${marks.length}`;
549
+ }
550
+ #clear() {
551
+ this.#box.querySelector(".k")?.classList.remove("k");
552
+ this.#tipEl.classList.remove("on");
553
+ this.#live.textContent = "";
554
+ this.#at = -1;
555
+ }
556
+ };
557
+ function tableHTML(caption, { head, rows }) {
558
+ const cells = (row) => row.map((c, i) => i ? `<td>${esc(c)}</td>` : `<th scope="row">${esc(c)}</th>`).join("");
559
+ return `<table><caption>${esc(caption || "Chart data")}</caption><thead><tr>${head.map((h) => `<th scope="col">${esc(h)}</th>`).join("")}</tr></thead><tbody>${rows.map((r) => `<tr>${cells(r)}</tr>`).join("")}</tbody></table>`;
560
+ }
561
+ function define(type) {
562
+ if (typeof customElements === "undefined" || typeof HTMLElement === "undefined") return;
563
+ if (!customElements.get(`bezel-${type}`)) {
564
+ customElements.define(`bezel-${type}`, class extends BezelChart {
565
+ get chartType() {
566
+ return type;
567
+ }
568
+ });
569
+ }
570
+ if (!customElements.get("bezel-chart")) customElements.define("bezel-chart", class extends BezelChart {
571
+ });
572
+ }
573
+
574
+ // src/entries/spark.js
575
+ register("spark", spark);
576
+ define("spark");
577
+
578
+ // src/core/series.js
579
+ function niceScale(min, max, count) {
580
+ if (min === max) {
581
+ if (min === 0) max = 1;
582
+ else {
583
+ var p = Math.abs(min) * 0.1;
584
+ min -= p;
585
+ max += p;
586
+ }
587
+ }
588
+ var step0 = (max - min) / (count || 4);
589
+ var mag = Math.pow(10, Math.floor(Math.log10(step0)));
590
+ var err = step0 / mag;
591
+ var step = (err <= 1 ? 1 : err <= 2 ? 2 : err <= 5 ? 5 : 10) * mag;
592
+ var lo = Math.floor(min / step) * step, hi = Math.ceil(max / step) * step, ticks = [];
593
+ for (var t = lo; t <= hi + step / 2; t += step) ticks.push(+t.toFixed(10));
594
+ return { lo, hi, ticks };
595
+ }
596
+ function legend(names, pal, th, x0, y0, w, key, kw) {
597
+ var x = x0, y = y0, s = "", off = kw || 13;
598
+ names.forEach(function(nm, k) {
599
+ var wd = off + tw(nm, 12) + 16;
600
+ if (x > x0 && x + wd - 16 > x0 + w) {
601
+ x = x0;
602
+ y += 20;
603
+ }
604
+ s += (key ? key(k, x, y) : '<circle cx="' + r2(x + 4) + '" cy="' + r2(y + 6) + '" r="4" fill="' + pal[k % pal.length] + '"/>') + text(x + off, y + 10, nm, 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"');
605
+ x += wd;
606
+ });
607
+ return { svg: s, height: y - y0 + 24 };
608
+ }
609
+ var DASHES = { solid: "", dashed: "7 5", dotted: "1 5", dashdot: "9 4 1 4" };
610
+ var SHAPES = ["circle", "square", "triangle", "diamond"];
611
+ var PATTERNS = ["solid", "stripes", "dots", "grid"];
612
+ function styleOf(v, names, off) {
613
+ if (v == null || v === false || v === "none" || v === "false") return null;
614
+ var l = v === true || v === "" || v === "auto" ? names : list(v).map(function(s) {
615
+ return names.indexOf(s) < 0 ? off : s;
616
+ });
617
+ if (!l.length) return null;
618
+ return function(k) {
619
+ return l[k % l.length];
620
+ };
621
+ }
622
+ function marker(kind, x, y, r, attrs) {
623
+ var a = r * 1.15, b = r * 0.85;
624
+ if (kind === "square") return '<rect x="' + r2(x - b) + '" y="' + r2(y - b) + '" width="' + r2(2 * b) + '" height="' + r2(2 * b) + '" rx="1"' + attrs + "/>";
625
+ if (kind === "triangle") return '<path d="M' + r2(x) + "," + r2(y - a) + "L" + r2(x + a) + "," + r2(y + b) + "L" + r2(x - a) + "," + r2(y + b) + 'Z"' + attrs + "/>";
626
+ if (kind === "diamond") return '<path d="M' + r2(x) + "," + r2(y - a * 1.1) + "L" + r2(x + a) + "," + r2(y) + "L" + r2(x) + "," + r2(y + a * 1.1) + "L" + r2(x - a) + "," + r2(y) + 'Z"' + attrs + "/>";
627
+ return '<circle cx="' + r2(x) + '" cy="' + r2(y) + '" r="' + r2(r) + '"' + attrs + "/>";
628
+ }
629
+ function pattern(id, kind, c, bg) {
630
+ var ink = ' stroke="' + bg + '" stroke-opacity=".55" stroke-width="1.5"';
631
+ return '<pattern id="' + id + '" width="6" height="6" patternUnits="userSpaceOnUse"' + (kind === "dots" ? "" : ' patternTransform="rotate(45)"') + '><rect width="6" height="6" fill="' + c + '"/>' + (kind === "dots" ? '<circle cx="3" cy="3" r="1.3" fill="' + bg + '" fill-opacity=".55"/>' : '<path d="M3,0V6' + (kind === "grid" ? "M0,3H6" : "") + '"' + ink + "/>") + "</pattern>";
632
+ }
633
+ function axisWidth(ticks) {
634
+ return Math.max.apply(null, ticks.map(function(t) {
635
+ return tw(t, 10, true);
636
+ })) + 10;
637
+ }
638
+ function seriesTable(o, total) {
639
+ var S = series(o.values), names = list(o.series), labels = list(o.labels), rows = [];
640
+ var n = Math.max.apply(null, [0].concat(S.map(function(s) {
641
+ return s.length;
642
+ })));
643
+ for (var i = 0; i < n; i++) {
644
+ var row = [labels[i] || "#" + (i + 1)], t = 0;
645
+ S.forEach(function(s) {
646
+ row.push(i < s.length ? fmt(s[i], o) : "");
647
+ t += i < s.length ? s[i] : 0;
648
+ });
649
+ if (total) row.push(fmt(t, o));
650
+ rows.push(row);
651
+ }
652
+ var head = S.length > 1 || names.length ? S.map(function(_, k) {
653
+ return names[k] || "Series " + (k + 1);
654
+ }) : ["Value"];
655
+ return { head: ["Label"].concat(head, total ? ["Total"] : []), rows };
656
+ }
657
+
658
+ // src/core/xy.js
659
+ function xyFrame(o, th, c, x0, y0, w, h) {
660
+ var s = "", top = y0, n = c.n, labels = c.labels;
661
+ if (c.names) {
662
+ var lg = legend(c.names, c.pal, th, x0, top, w, c.key, c.kw);
663
+ s += lg.svg;
664
+ top += lg.height;
665
+ }
666
+ var mn = c.lo, mx = c.hi;
667
+ if (has(o.goal)) {
668
+ mn = Math.min(mn, +o.goal);
669
+ mx = Math.max(mx, +o.goal);
670
+ }
671
+ if (c.zero) {
672
+ mn = Math.min(0, mn);
673
+ mx = Math.max(0, mx);
674
+ }
675
+ var sc = niceScale(mn, mx, 4);
676
+ var tl = sc.ticks.map(c.tick);
677
+ var axisW = Math.max.apply(null, tl.map(function(t) {
678
+ return tw(t, 10, true);
679
+ })) + 10;
680
+ var bottom = labels.length ? 20 : 4;
681
+ var px = x0 + axisW, pw = w - axisW, ph = h - (top - y0) - bottom;
682
+ var X = function(i2) {
683
+ return n === 1 ? px + pw / 2 : px + i2 * pw / (n - 1);
684
+ };
685
+ var Y = function(v) {
686
+ return top + ph - (v - sc.lo) / (sc.hi - sc.lo) * ph;
687
+ };
688
+ sc.ticks.forEach(function(t, i2) {
689
+ var y = Y(t);
690
+ s += '<line x1="' + r2(px) + '" x2="' + r2(px + pw) + '" y1="' + r2(y) + '" y2="' + r2(y) + '" stroke="' + th.grid + '"' + (t === 0 ? "" : ' stroke-dasharray="2 4"') + "/>" + text(px - 8, y + 3.5, tl[i2], small(th, 10, 'text-anchor="end"'));
691
+ });
692
+ if (labels.length) {
693
+ var every = Math.max(1, Math.ceil(n * (Math.max.apply(null, labels.map(function(l) {
694
+ return tw(l, 10, true);
695
+ })) + 10) / pw));
696
+ for (var i = 0; i < n; i += every) {
697
+ if (labels[i] == null) continue;
698
+ var anchor = n > 1 && i === 0 ? "start" : n > 1 && i === n - 1 ? "end" : "middle";
699
+ s += text(X(i), top + ph + 15, labels[i], small(th, 10, 'text-anchor="' + anchor + '"'));
700
+ }
701
+ }
702
+ c.notes.forEach(function(nt) {
703
+ if (nt.i >= n) return;
704
+ var x = X(nt.i);
705
+ s += '<line x1="' + r2(x) + '" x2="' + r2(x) + '" y1="' + r2(top + 12) + '" y2="' + r2(top + ph) + '" stroke="' + th.fg + '" stroke-opacity=".4" stroke-dasharray="3 3"/>' + text(clamp(x, px + 30, px + pw - 30), top + 8, nt.text, small(th, 10, 'text-anchor="middle"', th.fg));
706
+ });
707
+ return { svg: s, X, Y, px, pw, top, ph };
708
+ }
709
+ function xyFinish(o, th, f, n, tip) {
710
+ var s = has(o.goal) ? goalLine(o, th, f.px, f.px + f.pw, f.Y(+o.goal)) : "";
711
+ var step = n > 1 ? f.pw / (n - 1) : f.pw;
712
+ for (var j = 0; j < n; j++) s += hit(f.X(j) - step / 2, f.top, step, f.ph, tip(j));
713
+ return s;
714
+ }
715
+ function lineKey(pal, dashOf, markOf, bg) {
716
+ return function(k, x, y) {
717
+ var c = pal[k % pal.length], dk = dashOf ? DASHES[dashOf(k)] : "", mk = markOf ? markOf(k) : "none";
718
+ return '<line x1="' + r2(x) + '" x2="' + r2(x + 18) + '" y1="' + r2(y + 6) + '" y2="' + r2(y + 6) + '" stroke="' + c + '" stroke-width="2.5" stroke-linecap="round"' + (dk ? ' stroke-dasharray="' + dk + '"' : "") + "/>" + (mk !== "none" ? marker(mk, x + 9, y + 6, 3.5, ' fill="' + c + '" stroke="' + bg + '" stroke-width="1.5"') : "");
719
+ };
720
+ }
721
+ function seriesLine(o, d, c, k, dk) {
722
+ return dk ? '<path d="' + d + '" fill="none" stroke="' + c + '" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="' + dk + '"' + anim(o, "bzc-a", k * 0.15) + "/>" : '<path d="' + d + '" fill="none" stroke="' + c + '" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" pathLength="1" style="stroke-dasharray:1' + (o.animate ? ";animation-delay:" + k * 0.15 + "s" : "") + '"' + (o.animate ? ' class="bzc-d"' : "") + "/>";
723
+ }
724
+ function pointMarks(o, mk, ys, X, Y, c, bg, k) {
725
+ var s = "", every = Math.ceil(ys.length / 40);
726
+ ys.forEach(function(y, i) {
727
+ if (i % every === 0 || i === ys.length - 1) s += marker(mk, X(i), Y(y), 3.5, ' fill="' + c + '" stroke="' + bg + '" stroke-width="1.5"' + anim(o, "bzc-a", 0.6 + k * 0.15));
728
+ });
729
+ return s;
730
+ }
731
+
732
+ // src/charts/line.js
733
+ function line(o, th) {
734
+ var S = series(o.values), names = list(o.series), labels = list(o.labels), pal = palette(o, th);
735
+ var h = num(o.height, 170), n = Math.max.apply(null, [0].concat(S.map(function(s) {
736
+ return s.length;
737
+ })));
738
+ var notes = parseNotes(o, labels), multi = S.length > 1 || names.length > 0;
739
+ var st = statOf(S[0] || [], o, "");
740
+ var dashOf = styleOf(o.dash, Object.keys(DASHES), "solid"), markOf = styleOf(o.markers, SHAPES, "none");
741
+ var bg = o.background || th.tile;
742
+ var key = (dashOf || markOf) && lineKey(pal, dashOf, markOf, bg);
743
+ var nameOf = function(k) {
744
+ return names[k] || "Series " + (k + 1);
745
+ };
746
+ return tile(o, th, h, function(x0, y0, w, h2) {
747
+ if (!n) return emptyState(th, x0, y0, w, h2);
748
+ var all = [].concat.apply([], S);
749
+ var f = xyFrame(o, th, {
750
+ n,
751
+ names: multi ? S.map(function(_, k) {
752
+ return nameOf(k);
753
+ }) : null,
754
+ pal,
755
+ key,
756
+ kw: key && 24,
757
+ lo: Math.min.apply(null, all),
758
+ hi: Math.max.apply(null, all),
759
+ zero: bool(o.zero),
760
+ labels,
761
+ notes,
762
+ tick: function(t) {
763
+ return fmt(t, o, true);
764
+ }
765
+ }, x0, y0, w, h2);
766
+ var s = f.svg, X = f.X, Y = f.Y, px = f.px, pw = f.pw, top = f.top, ph = f.ph;
767
+ S.forEach(function(v, k) {
768
+ var c = pal[k % pal.length];
769
+ var P = v.map(function(y, i) {
770
+ return [X(i), Y(y)];
771
+ });
772
+ if (P.length === 1) P = [[px, P[0][1]], [px + pw, P[0][1]]];
773
+ var d = pathOf(P, bool(o.straight));
774
+ if (k === 0) {
775
+ var g = newId("g"), last = P[P.length - 1];
776
+ s += '<defs><linearGradient id="' + g + '" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="' + c + '" stop-opacity=".26"/><stop offset="1" stop-color="' + c + '" stop-opacity="0"/></linearGradient></defs><path d="' + d + "L" + r2(last[0]) + "," + r2(top + ph) + "L" + r2(P[0][0]) + "," + r2(top + ph) + 'Z" fill="url(#' + g + ')"' + anim(o, "bzc-a") + "/>";
777
+ }
778
+ var mk = markOf ? markOf(k) : "none";
779
+ s += seriesLine(o, d, c, k, dashOf ? DASHES[dashOf(k)] : "");
780
+ if (mk !== "none") {
781
+ s += pointMarks(o, mk, v, X, Y, c, bg, k);
782
+ } else {
783
+ var e = P[P.length - 1];
784
+ s += '<circle cx="' + r2(e[0]) + '" cy="' + r2(e[1]) + '" r="4" fill="' + c + '" stroke="' + bg + '" stroke-width="2"/>';
785
+ }
786
+ if (bool(o.showValues)) v.forEach(function(y, i) {
787
+ s += text(X(i), Y(y) - 8, fmt(y, o, true), small(th, 9, 'text-anchor="middle"'));
788
+ });
789
+ });
790
+ return s + xyFinish(o, th, f, n, function(j) {
791
+ var tip = labels[j] || "#" + (j + 1);
792
+ S.forEach(function(v, k) {
793
+ if (j < v.length) tip += "\n" + (multi ? nameOf(k) + ": " : "") + fmt(v[j], o);
794
+ });
795
+ return tip;
796
+ });
797
+ }, { stat: st, summary: S.length + " series over " + n + " points" });
798
+ }
799
+ line.table = function(o) {
800
+ return seriesTable(o);
801
+ };
802
+
803
+ // src/entries/line.js
804
+ register("line", line);
805
+ define("line");
806
+
807
+ // src/charts/area.js
808
+ function area(o, th) {
809
+ var S = series(o.values), names = list(o.series), labels = list(o.labels), pal = palette(o, th);
810
+ var h = num(o.height, 170), k = S.length, n = Math.max.apply(null, [0].concat(S.map(function(s) {
811
+ return s.length;
812
+ })));
813
+ var mode = k > 1 ? o.stacked === "percent" ? "percent" : bool(o.stacked) ? "stack" : "" : "";
814
+ var notes = parseNotes(o, labels), multi = k > 1 || names.length > 0;
815
+ var nameOf = function(j2) {
816
+ return names[j2] || "Series " + (j2 + 1);
817
+ };
818
+ var val = function(j2, i2) {
819
+ return i2 < S[j2].length ? S[j2][i2] : 0;
820
+ };
821
+ var lows = S.map(function() {
822
+ return [];
823
+ }), highs = S.map(function() {
824
+ return [];
825
+ }), totals = [], pos = [];
826
+ for (var i = 0; i < n; i++) {
827
+ var up = 0, down = 0, t = 0, p = 0;
828
+ for (var j = 0; j < k; j++) {
829
+ t += val(j, i);
830
+ p += Math.max(0, val(j, i));
831
+ }
832
+ totals.push(t);
833
+ pos.push(p);
834
+ for (j = 0; j < k; j++) {
835
+ var v = mode === "percent" ? p ? Math.max(0, val(j, i)) / p * 100 : 0 : val(j, i);
836
+ if (!mode) {
837
+ lows[j].push(0);
838
+ highs[j].push(v);
839
+ } else if (v >= 0) {
840
+ lows[j].push(up);
841
+ up += v;
842
+ highs[j].push(up);
843
+ } else {
844
+ lows[j].push(down);
845
+ down += v;
846
+ highs[j].push(down);
847
+ }
848
+ }
849
+ }
850
+ var edges = [].concat.apply([], lows.concat(highs));
851
+ var dashOf = styleOf(o.dash, Object.keys(DASHES), "solid"), markOf = styleOf(o.markers, SHAPES, "none");
852
+ var patOf = k > 1 ? styleOf(o.patterns, PATTERNS, "solid") : null, bg = o.background || th.tile, defs = "", fills = [];
853
+ for (var q = 0; patOf && q < k; q++) {
854
+ if (patOf(q) === "solid") continue;
855
+ var pid = newId("g");
856
+ defs += pattern(pid, patOf(q), pal[q % pal.length], bg);
857
+ fills[q] = "url(#" + pid + ")";
858
+ }
859
+ var key = patOf ? function(j2, x, y) {
860
+ return '<rect x="' + r2(x) + '" y="' + r2(y + 1) + '" width="10" height="10" rx="2" fill="' + (fills[j2] || pal[j2 % pal.length]) + '"/>';
861
+ } : (dashOf || markOf) && lineKey(pal, dashOf, markOf, bg);
862
+ var share = function(j2, i2) {
863
+ return pos[i2] ? Math.round(Math.max(0, val(j2, i2)) / pos[i2] * 100) : 0;
864
+ };
865
+ return tile(o, th, h, function(x0, y0, w, h2) {
866
+ if (!n) return emptyState(th, x0, y0, w, h2);
867
+ var f = xyFrame(o, th, {
868
+ n,
869
+ names: multi ? S.map(function(_, j2) {
870
+ return nameOf(j2);
871
+ }) : null,
872
+ pal,
873
+ key,
874
+ kw: key && (patOf ? 15 : 24),
875
+ lo: Math.min.apply(null, edges),
876
+ hi: Math.max.apply(null, edges),
877
+ zero: true,
878
+ labels,
879
+ notes,
880
+ tick: mode === "percent" ? function(t2) {
881
+ return fmt(t2, { lang: o.lang }, true) + "%";
882
+ } : function(t2) {
883
+ return fmt(t2, o, true);
884
+ }
885
+ }, x0, y0, w, h2);
886
+ var s = (defs ? "<defs>" + defs + "</defs>" : "") + f.svg, X = f.X, Y = f.Y;
887
+ var pts = function(ys) {
888
+ var P = ys.map(function(y, i2) {
889
+ return [X(i2), Y(y)];
890
+ });
891
+ return P.length === 1 ? [[f.px, P[0][1]], [f.px + f.pw, P[0][1]]] : P;
892
+ };
893
+ var straight = bool(o.straight), alpha = mode ? ".8" : ".22";
894
+ var shown = function(j2) {
895
+ return mode ? highs[j2] : S[j2];
896
+ };
897
+ S.forEach(function(_, j2) {
898
+ var top = pts(shown(j2)), base = pts(mode ? lows[j2] : shown(j2).map(function() {
899
+ return 0;
900
+ })).reverse();
901
+ s += '<path d="' + pathOf(top, straight) + pathOf(base, straight).replace(/^M/, "L") + 'Z" fill="' + (fills[j2] || pal[j2 % pal.length]) + '" fill-opacity="' + alpha + '"' + (o.animate ? ' class="bzc-a" style="animation-delay:' + r2(j2 * 0.1) + 's"' : "") + "/>";
902
+ });
903
+ S.forEach(function(_, j2) {
904
+ var c = pal[j2 % pal.length], ys = shown(j2), P = pts(ys), mk = markOf ? markOf(j2) : "none";
905
+ s += seriesLine(o, pathOf(P, straight), c, j2, dashOf ? DASHES[dashOf(j2)] : "");
906
+ if (mk !== "none") s += pointMarks(o, mk, ys, X, Y, c, bg, j2);
907
+ else if (!mode) s += '<circle cx="' + r2(P[P.length - 1][0]) + '" cy="' + r2(P[P.length - 1][1]) + '" r="4" fill="' + c + '" stroke="' + bg + '" stroke-width="2"/>';
908
+ if (bool(o.showValues) && !mode) ys.forEach(function(y, i2) {
909
+ s += text(X(i2), Y(y) - 8, fmt(y, o, true), small(th, 9, 'text-anchor="middle"'));
910
+ });
911
+ });
912
+ if (bool(o.showValues) && mode === "stack") {
913
+ totals.forEach(function(t2, i2) {
914
+ var top = Math.max.apply(null, highs.map(function(hs) {
915
+ return hs[i2];
916
+ }).concat([0]));
917
+ s += text(X(i2), Y(top) - 8, fmt(t2, o, true), small(th, 9, 'text-anchor="middle"'));
918
+ });
919
+ }
920
+ return s + xyFinish(o, th, f, n, function(i2) {
921
+ var tip = labels[i2] || "#" + (i2 + 1);
922
+ S.forEach(function(ser, j2) {
923
+ if (i2 < ser.length) tip += "\n" + (multi ? nameOf(j2) + ": " : "") + fmt(ser[i2], o) + (mode === "percent" ? " (" + share(j2, i2) + "%)" : "");
924
+ });
925
+ return tip + (mode ? "\nTotal: " + fmt(totals[i2], o) : "");
926
+ });
927
+ }, {
928
+ stat: statOf(mode ? totals : S[0] || [], o, ""),
929
+ summary: k + " series " + (mode === "percent" ? "as shares of each total" : mode ? "stacked" : "filled") + " over " + n + " points"
930
+ });
931
+ }
932
+ area.table = function(o) {
933
+ var S = series(o.values);
934
+ return seriesTable(o, S.length > 1 && (o.stacked === "percent" || bool(o.stacked)));
935
+ };
936
+
937
+ // src/entries/area.js
938
+ register("area", area);
939
+ define("area");
940
+
941
+ // src/charts/bars.js
942
+ var ANGLE = 40;
943
+ var ANGLE_MAX_CHARS = 24;
944
+ function bars(o, th) {
945
+ var S = series(o.values), names = list(o.series), labels = list(o.labels), pal = palette(o, th);
946
+ var k = S.length, n = Math.max.apply(null, [0].concat(S.map(function(s) {
947
+ return s.length;
948
+ })));
949
+ var grouped = k > 1, horiz = bool(o.horizontal), stacked = grouped && bool(o.stacked);
950
+ var many = !grouped && list(o.color).filter(Boolean).length > 1;
951
+ var v0 = S[0] || [], all = [].concat.apply([], S);
952
+ var sumOf = function(i, sign) {
953
+ return S.reduce(function(t2, s) {
954
+ return i < s.length && s[i] * sign > 0 ? t2 + s[i] : t2;
955
+ }, 0);
956
+ };
957
+ var totals = [], ups = [], downs = [];
958
+ for (var t = 0; stacked && t < n; t++) {
959
+ ups.push(sumOf(t, 1));
960
+ downs.push(sumOf(t, -1));
961
+ totals.push(ups[t] + downs[t]);
962
+ }
963
+ var scale = stacked ? ups.concat(downs) : all;
964
+ var top = scale.length ? Math.max.apply(null, scale) : 0;
965
+ var nameOf = function(j) {
966
+ return names[j] || "Series " + (j + 1);
967
+ };
968
+ var patOf = grouped ? styleOf(o.patterns, PATTERNS, "solid") : null, defs = "", fills = [];
969
+ for (var p = 0; patOf && p < k; p++) {
970
+ if (patOf(p) === "solid") continue;
971
+ var pid = newId("g");
972
+ defs += pattern(pid, patOf(p), pal[p % pal.length], o.background || th.tile);
973
+ fills[p] = "url(#" + pid + ")";
974
+ }
975
+ var colourOf = function(j, i) {
976
+ return grouped ? fills[j] || pal[j % pal.length] : many ? pal[i % pal.length] : pal[0];
977
+ };
978
+ var key = patOf && function(j, x, y) {
979
+ return '<rect x="' + r2(x) + '" y="' + r2(y + 1) + '" width="10" height="10" rx="2" fill="' + colourOf(j) + '"/>';
980
+ };
981
+ var opac = function(val) {
982
+ return grouped || many || o.highlight === "none" || val === top ? 1 : 0.5;
983
+ };
984
+ var labelOf = function(i) {
985
+ return labels[i] || "#" + (i + 1);
986
+ };
987
+ var tipOf = function(i) {
988
+ if (!grouped) return labelOf(i) + ": " + fmt(v0[i], o);
989
+ return labelOf(i) + S.map(function(s, j) {
990
+ return i < s.length ? "\n" + nameOf(j) + ": " + fmt(s[i], o) : "";
991
+ }).join("") + (stacked ? "\nTotal: " + fmt(totals[i], o) : "");
992
+ };
993
+ var d0 = dims(o);
994
+ var legendH = grouped || names.length ? legend(S.map(function(_, j) {
995
+ return nameOf(j);
996
+ }), pal, th, 0, 0, d0.w, key, key && 15).height : 0;
997
+ var rows = stacked ? 1 : k, thin = grouped && !stacked;
998
+ var subH = thin ? 10 : 14, subGap = 3;
999
+ var rowH = horiz ? rows * subH + (rows - 1) * subGap + (thin ? 14 : 16) : 0;
1000
+ var h = horiz ? legendH + Math.max(1, n) * rowH - 6 : num(o.height, 140) + legendH;
1001
+ var best = v0.indexOf(Math.max.apply(null, v0.length ? v0 : [0]));
1002
+ return tile(o, th, h, function(x0, y0, w, h2) {
1003
+ if (!n) return emptyState(th, x0, y0, w, h2);
521
1004
  var s = defs ? "<defs>" + defs + "</defs>" : "", top0 = y0;
522
1005
  if (legendH) {
523
1006
  s += legend(S.map(function(_, j) {
@@ -636,353 +1119,51 @@ function bars(o, th) {
636
1119
  var lx = gx0 + groupW / 2, ly = py + ph + negLab + (angled ? 12 : 15);
637
1120
  s += angled ? text(lx, ly, labText(labels[c]), small(th, 10, 'text-anchor="end" transform="rotate(-' + ANGLE + " " + r2(lx) + " " + r2(ly) + ')"')) : text(lx, ly, labels[c], small(th, 10, 'text-anchor="middle"'));
638
1121
  }
639
- s += hit(x0 + c * slot, top0, slot, h2 - (top0 - y0), tipOf(c));
640
- }
641
- if (has(o.goal)) s += goalLine(o, th, x0, x0 + w, Y(+o.goal));
642
- return s;
643
- function stackColumn(c2, x) {
644
- var out = "", up = 0, down = 0;
645
- S.forEach(function(ser, j) {
646
- var val = c2 < ser.length ? ser[c2] : 0;
647
- if (!val) return;
648
- var from = val > 0 ? up : down, to = from + val;
649
- if (val > 0) up = to;
650
- else down = to;
651
- var yA = Y(Math.max(from, to)), yB = Y(Math.min(from, to)), bh = yB - yA;
652
- var gapZ = from !== 0 ? Math.min(1.5, bh / 2) : 0;
653
- if (val > 0) yB -= gapZ;
654
- else yA += gapZ;
655
- bh = yB - yA;
656
- if (bh > 0.5) {
657
- out += '<rect x="' + r2(x) + '" y="' + r2(yA) + '" width="' + r2(bwid) + '" height="' + r2(bh) + '" rx="' + r2(Math.min(3, bwid / 2, bh / 2)) + '" fill="' + colourOf(j, c2) + '"' + anim(o, val < 0 ? "bzc-gn" : "bzc-g", c2 * 0.05 + j * 0.03) + "/>";
658
- }
659
- });
660
- if (bool(o.showValues)) {
661
- var tot = totals[c2];
662
- out += text(
663
- x + bwid / 2,
664
- tot < 0 ? Y(downs[c2]) + 12 : Y(ups[c2]) - 5,
665
- fmt(tot, o, true),
666
- 'text-anchor="middle" fill="' + th.fg + '" font-family="' + MONO + '" font-size="10" font-weight="600"'
667
- );
668
- }
669
- return out;
670
- }
671
- }, {
672
- stat: statOf(v0, o, ""),
673
- summary: !n ? "no data" : stacked ? k + " series stacked across " + n + " groups" : grouped ? k + " series across " + n + " groups" : "highest " + (labels[best] || "") + " " + fmt(v0[best], o)
674
- });
675
- }
676
- bars.table = function(o) {
677
- return seriesTable(o, series(o.values).length > 1 && bool(o.stacked));
678
- };
679
-
680
- // src/charts/ring.js
681
- function ring(o, th) {
682
- var v = nums(o.values), labels = list(o.labels), pal = palette(o, th);
683
- var max = num(o.max, 100) || 100, h = num(o.height, 140);
684
- var auto = !has(o.prefix) && !has(o.suffix) && max === 100;
685
- var show = function(n) {
686
- return fmt(n, o) + (auto ? "%" : "");
687
- };
688
- var st = statOf(v, o, "");
689
- return tile(o, th, h, function(x0, y0, w, h2) {
690
- if (!v.length) return emptyState(th, x0, y0, w, h2);
691
- var s = "", single = v.length === 1;
692
- var size = Math.min(h2, single ? w : w * 0.55);
693
- var cx = single ? x0 + w / 2 : x0 + size / 2, cy = y0 + h2 / 2;
694
- var sw = single ? size * 0.12 : clamp(size * 0.42 / (v.length * 1.35), 5, 16), gap = sw * 0.38;
695
- v.forEach(function(n, i) {
696
- var r = size / 2 - sw / 2 - i * (sw + gap);
697
- if (r <= sw / 2) return;
698
- var pct = clamp(n / max * 100, 0, 100), c = pal[i % pal.length];
699
- s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="' + c + '" stroke-opacity=".16" stroke-width="' + r2(sw) + '"/>';
700
- if (pct > 0) s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="' + c + '" stroke-width="' + r2(sw) + '" stroke-linecap="round" pathLength="100" transform="rotate(-90 ' + r2(cx) + " " + r2(cy) + ')" style="stroke-dasharray:' + r2(pct) + " 100" + (o.animate ? ";animation-delay:" + i * 0.12 + "s" : "") + '"' + (o.animate ? ' class="bzc-s"' : "") + "/>";
701
- s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r + sw / 2) + '" fill="#000" fill-opacity="0" class="bzc-hit" data-tip="' + esc((labels[i] || "#" + (i + 1)) + ": " + show(n) + (auto ? "" : " of " + fmt(max, o))) + '"/>';
702
- });
703
- if (single) {
704
- s += text(cx, cy + size * 0.08, show(v[0]), 'text-anchor="middle" fill="' + th.fg + '" font-family="' + SANS + '" font-size="' + r2(size * 0.22) + '" font-weight="800"');
705
- if (labels[0]) s += text(cx, cy + size * 0.08 + 16, labels[0], small(th, 10, 'text-anchor="middle"'));
706
- } else {
707
- var lx = x0 + size + 22, rowH = Math.min(26, h2 / v.length), ly = cy - rowH * v.length / 2;
708
- v.forEach(function(n, i) {
709
- var y = ly + i * rowH + rowH / 2;
710
- s += '<circle cx="' + r2(lx + 4) + '" cy="' + r2(y) + '" r="4" fill="' + pal[i % pal.length] + '"/>' + text(lx + 14, y + 4, trunc(labels[i] || "#" + (i + 1), Math.floor((x0 + w - lx - 60) / 6.7)), 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"') + text(x0 + w, y + 4, show(n), 'text-anchor="end" fill="' + th.fg + '" font-family="' + MONO + '" font-size="11" font-weight="600"');
711
- });
712
- }
713
- return s;
714
- }, { stat: st, summary: v.map(function(n, i) {
715
- return (labels[i] ? labels[i] + " " : "") + show(n);
716
- }).join(", ") });
717
- }
718
- ring.table = function(o) {
719
- var labels = list(o.labels), max = num(o.max, 100) || 100, auto = !has(o.prefix) && !has(o.suffix) && max === 100;
720
- return { head: ["Label", "Value"], rows: nums(o.values).map(function(n, i) {
721
- return [labels[i] || "#" + (i + 1), fmt(n, o) + (auto ? "%" : " of " + fmt(max, o))];
722
- }) };
723
- };
724
-
725
- // src/charts/donut.js
726
- function donut(o, th) {
727
- var raw = nums(o.values).map(function(n) {
728
- return Math.max(0, n);
729
- }), lab = list(o.labels), pal = palette(o, th);
730
- var v = raw.slice(), labels = lab.slice();
731
- if (v.length > 6) {
732
- var idx = v.map(function(n, i) {
733
- return i;
734
- }).sort(function(a, b) {
735
- return raw[b] - raw[a];
736
- });
737
- var keep = idx.slice(0, 5).sort(function(a, b) {
738
- return a - b;
739
- });
740
- v = keep.map(function(i) {
741
- return raw[i];
742
- });
743
- labels = keep.map(function(i) {
744
- return lab[i] || "#" + (i + 1);
745
- });
746
- v.push(sum(idx.slice(5).map(function(i) {
747
- return raw[i];
748
- })));
749
- labels.push("Other");
750
- }
751
- var total = sum(v), h = num(o.height, 150);
752
- var st = statOf(v, o, "");
753
- return tile(o, th, h, function(x0, y0, w, h2) {
754
- if (!total) return emptyState(th, x0, y0, w, h2);
755
- var s = "", size = Math.min(h2, w * 0.5), cx = x0 + size / 2, cy = y0 + h2 / 2;
756
- var sw = size * 0.16, r = (size - sw) / 2, start = 0, gap = v.filter(Boolean).length > 1 ? 0.9 : 0;
757
- v.forEach(function(n, i) {
758
- var len = n / total * 100;
759
- if (len <= 0) return;
760
- var seg = Math.max(0.01, len - gap);
761
- s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="' + pal[i % pal.length] + '" stroke-width="' + r2(sw) + '" pathLength="100" stroke-dasharray="' + r2(seg) + " " + r2(100 - seg) + '" stroke-dashoffset="' + r2(-start) + '" transform="rotate(-90 ' + r2(cx) + " " + r2(cy) + ')"' + anim(o, "bzc-a", i * 0.08) + "/>";
762
- start += len;
763
- });
764
- s += text(cx, cy + 2, fmt(total, o, true), 'text-anchor="middle" fill="' + th.fg + '" font-family="' + SANS + '" font-size="' + r2(size * 0.17) + '" font-weight="800"');
765
- s += text(cx, cy + 2 + 14, (has(o.centerLabel) ? o.centerLabel : "Total").toUpperCase(), small(th, 9, 'text-anchor="middle" letter-spacing="1"'));
766
- var lx = x0 + size + 22, rowH = Math.min(24, h2 / v.length), ly = cy - rowH * v.length / 2;
767
- v.forEach(function(n, i) {
768
- var y = ly + i * rowH + rowH / 2, pct = Math.round(n / total * 100);
769
- var tip = (labels[i] || "#" + (i + 1)) + ": " + fmt(n, o) + " (" + pct + "%)";
770
- s += '<circle cx="' + r2(lx + 4) + '" cy="' + r2(y) + '" r="4" fill="' + pal[i % pal.length] + '"/>' + text(lx + 14, y + 4, trunc(labels[i] || "#" + (i + 1), Math.floor((x0 + w - lx - 50) / 6.7)), 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"') + text(x0 + w, y + 4, pct + "%", 'text-anchor="end" fill="' + th.muted + '" font-family="' + MONO + '" font-size="11" font-weight="600"') + hit(lx, y - rowH / 2, x0 + w - lx, rowH, tip);
771
- });
772
- start = 0;
773
- v.forEach(function(n, i) {
774
- var len = n / total * 100;
775
- if (len <= 0) return;
776
- s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="#000" stroke-opacity="0" stroke-width="' + r2(sw) + '" pathLength="100" stroke-dasharray="' + r2(len) + " " + r2(100 - len) + '" stroke-dashoffset="' + r2(-start) + '" transform="rotate(-90 ' + r2(cx) + " " + r2(cy) + ')" class="bzc-hit" data-skip="" data-tip="' + esc((labels[i] || "#" + (i + 1)) + ": " + fmt(n, o) + " (" + Math.round(len) + "%)") + '"/>';
777
- start += len;
778
- });
779
- return s;
780
- }, { stat: st, summary: "total " + fmt(total, o) + " across " + v.length + " parts" });
781
- }
782
- donut.table = function(o) {
783
- var v = nums(o.values).map(function(n) {
784
- return Math.max(0, n);
785
- }), labels = list(o.labels), total = sum(v);
786
- return { head: ["Label", "Value", "Share"], rows: v.map(function(n, i) {
787
- return [labels[i] || "#" + (i + 1), fmt(n, o), (total ? Math.round(n / total * 100) : 0) + "%"];
788
- }) };
789
- };
790
-
791
- // src/charts/gauge.js
792
- function gauge(o, th) {
793
- var v = nums(o.values), val = v.length ? v[0] : num(o.value, NaN), labels = list(o.labels);
794
- var mn = num(o.min, 0), mx = num(o.max, 100), h = num(o.height, 130);
795
- if (mx === mn) mx = mn + 1;
796
- var zones = nums(o.zones), pal = palette(o, th);
797
- var zc = list(o.color).filter(Boolean).length > 1 ? pal : [th.up, th.palette[3], th.down];
798
- var colour = pal[0];
799
- if (zones.length && isFinite(val)) {
800
- var z = 0;
801
- while (z < zones.length && val >= zones[z]) z++;
802
- colour = zc[Math.min(z, zc.length - 1)];
803
- }
804
- var auto = !has(o.prefix) && !has(o.suffix) && mn === 0 && mx === 100;
805
- var show = function(n) {
806
- return fmt(n, o) + (auto ? "%" : "");
807
- };
808
- return tile(o, th, h, function(x0, y0, w, h2) {
809
- if (!isFinite(val)) return emptyState(th, x0, y0, w, h2);
810
- var R = Math.min(w / 2, h2 - 22), sw = R * 0.16, r = R - sw / 2, cx = x0 + w / 2, cy = y0 + R;
811
- var t = clamp((val - mn) / (mx - mn), 0, 1);
812
- var pt = function(f, rr) {
813
- var a = Math.PI * (1 - f);
814
- return [cx + rr * Math.cos(a), cy - rr * Math.sin(a)];
815
- };
816
- var arc = "M" + r2(cx - r) + "," + r2(cy) + "A" + r2(r) + "," + r2(r) + " 0 0 1 " + r2(cx + r) + "," + r2(cy);
817
- var s = '<path d="' + arc + '" fill="none" stroke="' + colour + '" stroke-opacity=".16" stroke-width="' + r2(sw) + '" stroke-linecap="round"/>';
818
- if (zones.length) zones.forEach(function(zv) {
819
- var f = clamp((zv - mn) / (mx - mn), 0, 1), a = pt(f, r - sw / 2 - 3), b = pt(f, r - sw / 2 - 8);
820
- s += '<line x1="' + r2(a[0]) + '" y1="' + r2(a[1]) + '" x2="' + r2(b[0]) + '" y2="' + r2(b[1]) + '" stroke="' + th.muted + '" stroke-width="1.5"/>';
821
- });
822
- if (t > 0) s += '<path d="' + arc + '" fill="none" stroke="' + colour + '" stroke-width="' + r2(sw) + '" stroke-linecap="round" pathLength="100" style="stroke-dasharray:' + r2(t * 100) + ' 100"' + anim(o, "bzc-s") + "/>";
823
- var e = pt(t, r);
824
- s += '<circle cx="' + r2(e[0]) + '" cy="' + r2(e[1]) + '" r="' + r2(sw * 0.26) + '" fill="' + (o.background || th.tile) + '"' + anim(o, "bzc-a", 0.9) + "/>";
825
- if (has(o.goal)) {
826
- var gf = clamp((+o.goal - mn) / (mx - mn), 0, 1), g1 = pt(gf, r + sw / 2 + 4), g2 = pt(gf, r - sw / 2 - 4);
827
- s += '<line x1="' + r2(g1[0]) + '" y1="' + r2(g1[1]) + '" x2="' + r2(g2[0]) + '" y2="' + r2(g2[1]) + '" stroke="' + th.fg + '" stroke-width="2" stroke-linecap="round"/>';
828
- }
829
- s += text(cx, cy - R * 0.12, show(val), 'text-anchor="middle" fill="' + th.fg + '" font-family="' + SANS + '" font-size="' + r2(R * 0.34) + '" font-weight="800"');
830
- if (labels[0]) s += text(cx, cy - R * 0.12 + 16, labels[0], small(th, 10, 'text-anchor="middle"'));
831
- s += text(cx - r, cy + 18, fmt(mn, o, true), small(th, 10, 'text-anchor="middle"')) + text(cx + r, cy + 18, fmt(mx, o, true), small(th, 10, 'text-anchor="middle"'));
832
- s += hit(cx - R, cy - R, R * 2, R, (labels[0] ? labels[0] + ": " : "") + show(val) + " (range " + fmt(mn, o) + " to " + fmt(mx, o) + ")" + (has(o.goal) ? "\nGoal: " + fmt(+o.goal, o) : ""));
833
- return s;
834
- }, { summary: isFinite(val) ? show(val) + " on a scale of " + fmt(mn, o) + " to " + fmt(mx, o) : "no data" });
835
- }
836
- gauge.table = function(o) {
837
- var v = nums(o.values), val = v.length ? v[0] : num(o.value, NaN), labels = list(o.labels);
838
- var mn = num(o.min, 0), mx = num(o.max, 100), auto = !has(o.prefix) && !has(o.suffix) && mn === 0 && mx === 100;
839
- if (!isFinite(val)) return { head: ["Label", "Value"], rows: [] };
840
- return { head: ["Label", "Value"], rows: [[labels[0] || "Reading", fmt(val, o) + (auto ? "%" : "")], ["Lowest", fmt(mn, o)], ["Highest", fmt(mx, o)]].concat(has(o.goal) ? [["Goal", fmt(+o.goal, o)]] : []) };
841
- };
842
-
843
- // src/charts/progress.js
844
- function progress(o, th) {
845
- var v = nums(o.values).map(function(n) {
846
- return Math.max(0, n);
847
- }), labels = list(o.labels), pal = palette(o, th);
848
- var single = v.length === 1, total = sum(v);
849
- var max = has(o.max) ? num(o.max, 100) : single ? 100 : total;
850
- if (!(max > 0)) max = 1;
851
- var auto = !has(o.prefix) && !has(o.suffix) && max === 100 && single;
852
- var show = function(n) {
853
- return fmt(n, o) + (auto ? "%" : "");
854
- };
855
- var d0 = dims(o), w0 = d0.w, rows = 0, layout = [];
856
- if (!single && v.length) {
857
- var x = 0;
858
- rows = 1;
859
- v.forEach(function(n, i) {
860
- var l = labels[i] || "#" + (i + 1), wd = 14 + tw(l, 12) + 6 + tw(fmt(n, o), 11, true) + 18;
861
- if (x > 0 && x + wd > w0) {
862
- x = 0;
863
- rows++;
864
- }
865
- layout.push({ x, row: rows - 1, l });
866
- x += wd;
867
- });
868
- }
869
- var h = 22 + 14 + (rows ? 12 + rows * 20 : 0);
870
- var st = statOf(v, o, "");
871
- return tile(o, th, h, function(x0, y0, w, h2) {
872
- if (!v.length) return emptyState(th, x0, y0, w, h2);
873
- var s = "", by = y0 + 22, cid = newId("c");
874
- var head = single ? show(v[0]) + (auto ? "" : " of " + fmt(max, o)) : fmt(total, o) + " of " + fmt(max, o);
875
- if (single && labels[0]) s += text(x0, y0 + 11, labels[0], 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"');
876
- s += text(x0 + w, y0 + 11, head, 'text-anchor="end" fill="' + th.fg + '" font-family="' + MONO + '" font-size="11" font-weight="600"');
877
- s += '<rect x="' + x0 + '" y="' + by + '" width="' + w + '" height="14" rx="7" fill="' + th.grid + '" fill-opacity=".7"/>';
878
- s += '<clipPath id="' + cid + '"><rect x="' + x0 + '" y="' + by + '" width="' + w + '" height="14" rx="7"/></clipPath><g clip-path="url(#' + cid + ')"><g' + anim(o, "bzc-gx") + ">";
879
- var acc = 0;
880
- v.forEach(function(n, i) {
881
- var len = Math.min(n, Math.max(0, max - acc)) / max * w;
882
- if (len > 0) s += '<rect x="' + r2(x0 + acc / max * w) + '" y="' + by + '" width="' + r2(len) + '" height="14" fill="' + pal[i % pal.length] + '"/>';
883
- if (i > 0 && len > 0) s += '<rect x="' + r2(x0 + acc / max * w - 1) + '" y="' + by + '" width="2" height="14" fill="' + (o.background || th.tile) + '"/>';
884
- acc += n;
885
- });
886
- s += "</g></g>";
887
- if (has(o.goal)) {
888
- var gx = x0 + clamp(+o.goal / max, 0, 1) * w;
889
- s += '<line x1="' + r2(gx) + '" x2="' + r2(gx) + '" y1="' + (by - 4) + '" y2="' + (by + 18) + '" stroke="' + th.fg + '" stroke-width="2" stroke-linecap="round"/>';
1122
+ s += hit(x0 + c * slot, top0, slot, h2 - (top0 - y0), tipOf(c));
890
1123
  }
891
- acc = 0;
892
- v.forEach(function(n, i) {
893
- var len = Math.min(n, Math.max(0, max - acc)) / max * w;
894
- if (len > 0) s += hit(x0 + acc / max * w, by - 4, len, 22, (labels[i] || (single ? "Progress" : "#" + (i + 1))) + ": " + show(n) + (single ? "" : " (" + Math.round(n / max * 100) + "%)"));
895
- acc += n;
896
- });
897
- layout.forEach(function(it, i) {
898
- var lx = x0 + it.x, ly = by + 14 + 12 + it.row * 20 + 8;
899
- s += '<circle cx="' + r2(lx + 4) + '" cy="' + r2(ly) + '" r="4" fill="' + pal[i % pal.length] + '"/><text x="' + r2(lx + 14) + '" y="' + r2(ly + 4) + '" fill="' + th.fg + '" font-family="' + SANS + '" font-size="12">' + esc(it.l) + '<tspan dx="6" fill="' + th.muted + '" font-family="' + MONO + '" font-size="11">' + esc(fmt(v[i], o)) + "</tspan></text>";
900
- });
1124
+ if (has(o.goal)) s += goalLine(o, th, x0, x0 + w, Y(+o.goal));
901
1125
  return s;
902
- }, { stat: st, summary: single ? show(v[0]) + (auto ? "" : " of " + fmt(max, o)) : fmt(total, o) + " of " + fmt(max, o) });
903
- }
904
- progress.table = function(o) {
905
- var v = nums(o.values).map(function(n) {
906
- return Math.max(0, n);
907
- }), labels = list(o.labels), single = v.length === 1;
908
- var max = has(o.max) ? num(o.max, 100) : single ? 100 : sum(v);
909
- if (!(max > 0)) max = 1;
910
- var auto = !has(o.prefix) && !has(o.suffix) && max === 100 && single;
911
- if (single) return { head: ["Label", "Value"], rows: [[labels[0] || "Progress", fmt(v[0], o) + (auto ? "%" : " of " + fmt(max, o))]] };
912
- return { head: ["Label", "Value", "Share"], rows: v.map(function(n, i) {
913
- return [labels[i] || "#" + (i + 1), fmt(n, o), Math.round(n / max * 100) + "%"];
914
- }) };
915
- };
916
-
917
- // src/charts/heatmap.js
918
- var FULL = { weekday: "short", day: "numeric", month: "short", year: "numeric" };
919
- function startOf(o, len) {
920
- if (has(o.start) && /^\d{4}-\d{2}-\d{2}$/.test(String(o.start))) return /* @__PURE__ */ new Date(String(o.start) + "T00:00:00Z");
921
- var t = /* @__PURE__ */ new Date();
922
- return new Date(Date.UTC(t.getFullYear(), t.getMonth(), t.getDate()) - Math.max(0, len - 1) * 864e5);
923
- }
924
- function dates(lang, opt) {
925
- try {
926
- return new Intl.DateTimeFormat(lang, Object.assign({ timeZone: "UTC" }, opt));
927
- } catch (e) {
928
- return new Intl.DateTimeFormat("en-GB", Object.assign({ timeZone: "UTC" }, opt));
929
- }
930
- }
931
- function heatmap(o, th) {
932
- var v = nums(o.values), c = palette(o, th)[0], lang = o.lang || "en-GB";
933
- var start = startOf(o, v.length);
934
- var offset = (start.getUTCDay() + 6) % 7, cols = Math.max(1, Math.ceil((offset + v.length) / 7));
935
- var d0 = dims(o), leftW = 28, maxCols = Math.max(1, Math.floor((d0.w - leftW) / 10));
936
- if (cols > maxCols) {
937
- var drop = (cols - maxCols) * 7 - offset;
938
- v = v.slice(drop);
939
- start = new Date(start.getTime() + drop * 864e5);
940
- offset = 0;
941
- cols = maxCols;
942
- }
943
- var pitch = Math.min(18, (d0.w - leftW) / cols), gap = pitch < 8 ? 1 : 3, cell = pitch - gap;
944
- var h = 16 + 7 * pitch + 26;
945
- var max = v.length ? Math.max.apply(null, v) : 0;
946
- var dfm = function(opt) {
947
- return dates(lang, opt);
948
- };
949
- var monthF = dfm({ month: "short" }), dayF = dfm({ weekday: "short" }), fullF = dfm(FULL);
950
- var st = statOf(v, o, "");
951
- return tile(o, th, h, function(x0, y0, w, h2) {
952
- if (!v.length) return emptyState(th, x0, y0, w, h2);
953
- var s = "", gx = x0 + leftW, gy = y0 + 16, lastMonthX = -99, op = [0.28, 0.5, 0.75, 1];
954
- if (pitch >= 9) [0, 2, 4].forEach(function(r) {
955
- var d = new Date(Date.UTC(2024, 0, 1 + r));
956
- s += text(x0, gy + r * pitch + cell / 2 + 3.5, dayF.format(d), small(th, 9));
957
- });
958
- for (var i = 0; i < v.length; i++) {
959
- var k = offset + i, col = Math.floor(k / 7), row = k % 7;
960
- var day = new Date(start.getTime() + i * 864e5), x = gx + col * pitch, y = gy + row * pitch;
961
- if ((day.getUTCDate() === 1 || i === 0) && x - lastMonthX > 30) {
962
- s += text(x, y0 + 9, monthF.format(day), small(th, 9));
963
- lastMonthX = x;
1126
+ function stackColumn(c2, x) {
1127
+ var out = "", up = 0, down = 0;
1128
+ S.forEach(function(ser, j) {
1129
+ var val = c2 < ser.length ? ser[c2] : 0;
1130
+ if (!val) return;
1131
+ var from = val > 0 ? up : down, to = from + val;
1132
+ if (val > 0) up = to;
1133
+ else down = to;
1134
+ var yA = Y(Math.max(from, to)), yB = Y(Math.min(from, to)), bh = yB - yA;
1135
+ var gapZ = from !== 0 ? Math.min(1.5, bh / 2) : 0;
1136
+ if (val > 0) yB -= gapZ;
1137
+ else yA += gapZ;
1138
+ bh = yB - yA;
1139
+ if (bh > 0.5) {
1140
+ out += '<rect x="' + r2(x) + '" y="' + r2(yA) + '" width="' + r2(bwid) + '" height="' + r2(bh) + '" rx="' + r2(Math.min(3, bwid / 2, bh / 2)) + '" fill="' + colourOf(j, c2) + '"' + anim(o, val < 0 ? "bzc-gn" : "bzc-g", c2 * 0.05 + j * 0.03) + "/>";
1141
+ }
1142
+ });
1143
+ if (bool(o.showValues)) {
1144
+ var tot = totals[c2];
1145
+ out += text(
1146
+ x + bwid / 2,
1147
+ tot < 0 ? Y(downs[c2]) + 12 : Y(ups[c2]) - 5,
1148
+ fmt(tot, o, true),
1149
+ 'text-anchor="middle" fill="' + th.fg + '" font-family="' + MONO + '" font-size="10" font-weight="600"'
1150
+ );
964
1151
  }
965
- var lvl = v[i] <= 0 || !max ? 0 : Math.ceil(v[i] / max * 4);
966
- var fill = lvl ? c : th.grid, fo = lvl ? op[lvl - 1] : 0.7;
967
- s += '<rect x="' + r2(x) + '" y="' + r2(y) + '" width="' + r2(cell) + '" height="' + r2(cell) + '" rx="' + r2(cell * 0.24) + '" fill="' + fill + '" fill-opacity="' + fo + '"' + (o.animate ? ' class="bzc-a" style="animation-delay:' + r2(col * 0.012) + 's"' : "") + ' data-tip="' + esc(fullF.format(day) + ": " + fmt(v[i], o)) + '"/>';
968
- }
969
- var ly = gy + 7 * pitch + 14, lc = 10, lx = x0 + w - 5 * (lc + 3) - 30;
970
- s += text(x0, ly + 8, fmt(sum(v), o, true) + " in " + v.length + " days", small(th, 10));
971
- s += text(lx - 6, ly + 8, "Less", small(th, 9, 'text-anchor="end"'));
972
- for (var j = 0; j < 5; j++) {
973
- s += '<rect x="' + r2(lx + j * (lc + 3)) + '" y="' + ly + '" width="' + lc + '" height="' + lc + '" rx="2.5" fill="' + (j ? c : th.grid) + '" fill-opacity="' + (j ? op[j - 1] : 0.7) + '"/>';
1152
+ return out;
974
1153
  }
975
- s += text(lx + 5 * (lc + 3) + 3, ly + 8, "More", small(th, 9));
976
- return s;
977
- }, { stat: st, summary: fmt(sum(v), o) + " across " + v.length + " days" });
1154
+ }, {
1155
+ stat: statOf(v0, o, ""),
1156
+ summary: !n ? "no data" : stacked ? k + " series stacked across " + n + " groups" : grouped ? k + " series across " + n + " groups" : "highest " + (labels[best] || "") + " " + fmt(v0[best], o)
1157
+ });
978
1158
  }
979
- heatmap.table = function(o) {
980
- var v = nums(o.values), start = startOf(o, v.length), f = dates(o.lang || "en-GB", FULL);
981
- return { head: ["Date", "Value"], rows: v.map(function(n, i) {
982
- return [f.format(new Date(start.getTime() + i * 864e5)), fmt(n, o)];
983
- }) };
1159
+ bars.table = function(o) {
1160
+ return seriesTable(o, series(o.values).length > 1 && bool(o.stacked));
984
1161
  };
985
1162
 
1163
+ // src/entries/bars.js
1164
+ register("bars", bars);
1165
+ define("bars");
1166
+
986
1167
  // src/charts/scatter.js
987
1168
  function fit(xs, ys) {
988
1169
  var n = Math.min(xs.length, ys.length);
@@ -1109,337 +1290,365 @@ function scatter(o, th) {
1109
1290
  idx++;
1110
1291
  });
1111
1292
  });
1112
- var flat = [];
1113
- groups.forEach(function(gr) {
1114
- gr.xs.forEach(function(xv, i) {
1115
- flat.push([PX(xv), PY(gr.ys[i])]);
1116
- });
1293
+ var flat = [];
1294
+ groups.forEach(function(gr) {
1295
+ gr.xs.forEach(function(xv, i) {
1296
+ flat.push([PX(xv), PY(gr.ys[i])]);
1297
+ });
1298
+ });
1299
+ notes.forEach(function(nt) {
1300
+ var p = flat[nt.i];
1301
+ if (!p) return;
1302
+ s += '<circle cx="' + r2(p[0]) + '" cy="' + r2(p[1]) + '" r="' + (r + 4) + '" fill="none" stroke="' + th.fg + '" stroke-width="1.5"/>' + text(clamp(p[0], px + 30, px + pw - 30), p[1] - r - 9, nt.text, small(th, 10, 'text-anchor="middle"', th.fg));
1303
+ });
1304
+ return s;
1305
+ }, { summary: count + " points" + (multi ? " in " + groups.length + " groups" : "") });
1306
+ }
1307
+ scatter.table = function(o) {
1308
+ var X = series(o.x), Y = series(o.y), names = list(o.series), labels = list(o.labels), multi = X.length > 1 || names.length > 0;
1309
+ var xo = { decimals: o.decimals, compact: o.compact, lang: o.lang }, rows = [], idx = 0;
1310
+ X.forEach(function(xs, g) {
1311
+ var ys = Y[g] || [];
1312
+ for (var i = 0; i < Math.min(xs.length, ys.length); i++, idx++) {
1313
+ rows.push([labels[idx] || "#" + (idx + 1)].concat(multi ? [names[g] || "Group " + (g + 1)] : [], [fmt(xs[i], xo), fmt(ys[i], o)]));
1314
+ }
1315
+ });
1316
+ return { head: ["Point"].concat(multi ? ["Group"] : [], [has(o.xLabel) ? o.xLabel : "x", has(o.yLabel) ? o.yLabel : "y"]), rows };
1317
+ };
1318
+
1319
+ // src/entries/scatter.js
1320
+ register("scatter", scatter);
1321
+ define("scatter");
1322
+
1323
+ // src/charts/ring.js
1324
+ function ring(o, th) {
1325
+ var v = nums(o.values), labels = list(o.labels), pal = palette(o, th);
1326
+ var max = num(o.max, 100) || 100, h = num(o.height, 140);
1327
+ var auto = !has(o.prefix) && !has(o.suffix) && max === 100;
1328
+ var show = function(n) {
1329
+ return fmt(n, o) + (auto ? "%" : "");
1330
+ };
1331
+ var st = statOf(v, o, "");
1332
+ return tile(o, th, h, function(x0, y0, w, h2) {
1333
+ if (!v.length) return emptyState(th, x0, y0, w, h2);
1334
+ var s = "", single = v.length === 1;
1335
+ var size = Math.min(h2, single ? w : w * 0.55);
1336
+ var cx = single ? x0 + w / 2 : x0 + size / 2, cy = y0 + h2 / 2;
1337
+ var sw = single ? size * 0.12 : clamp(size * 0.42 / (v.length * 1.35), 5, 16), gap = sw * 0.38;
1338
+ v.forEach(function(n, i) {
1339
+ var r = size / 2 - sw / 2 - i * (sw + gap);
1340
+ if (r <= sw / 2) return;
1341
+ var pct = clamp(n / max * 100, 0, 100), c = pal[i % pal.length];
1342
+ s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="' + c + '" stroke-opacity=".16" stroke-width="' + r2(sw) + '"/>';
1343
+ if (pct > 0) s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="' + c + '" stroke-width="' + r2(sw) + '" stroke-linecap="round" pathLength="100" transform="rotate(-90 ' + r2(cx) + " " + r2(cy) + ')" style="stroke-dasharray:' + r2(pct) + " 100" + (o.animate ? ";animation-delay:" + i * 0.12 + "s" : "") + '"' + (o.animate ? ' class="bzc-s"' : "") + "/>";
1344
+ s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r + sw / 2) + '" fill="#000" fill-opacity="0" class="bzc-hit" data-tip="' + esc((labels[i] || "#" + (i + 1)) + ": " + show(n) + (auto ? "" : " of " + fmt(max, o))) + '"/>';
1345
+ });
1346
+ if (single) {
1347
+ s += text(cx, cy + size * 0.08, show(v[0]), 'text-anchor="middle" fill="' + th.fg + '" font-family="' + SANS + '" font-size="' + r2(size * 0.22) + '" font-weight="800"');
1348
+ if (labels[0]) s += text(cx, cy + size * 0.08 + 16, labels[0], small(th, 10, 'text-anchor="middle"'));
1349
+ } else {
1350
+ var lx = x0 + size + 22, rowH = Math.min(26, h2 / v.length), ly = cy - rowH * v.length / 2;
1351
+ v.forEach(function(n, i) {
1352
+ var y = ly + i * rowH + rowH / 2;
1353
+ s += '<circle cx="' + r2(lx + 4) + '" cy="' + r2(y) + '" r="4" fill="' + pal[i % pal.length] + '"/>' + text(lx + 14, y + 4, trunc(labels[i] || "#" + (i + 1), Math.floor((x0 + w - lx - 60) / 6.7)), 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"') + text(x0 + w, y + 4, show(n), 'text-anchor="end" fill="' + th.fg + '" font-family="' + MONO + '" font-size="11" font-weight="600"');
1354
+ });
1355
+ }
1356
+ return s;
1357
+ }, { stat: st, summary: v.map(function(n, i) {
1358
+ return (labels[i] ? labels[i] + " " : "") + show(n);
1359
+ }).join(", ") });
1360
+ }
1361
+ ring.table = function(o) {
1362
+ var labels = list(o.labels), max = num(o.max, 100) || 100, auto = !has(o.prefix) && !has(o.suffix) && max === 100;
1363
+ return { head: ["Label", "Value"], rows: nums(o.values).map(function(n, i) {
1364
+ return [labels[i] || "#" + (i + 1), fmt(n, o) + (auto ? "%" : " of " + fmt(max, o))];
1365
+ }) };
1366
+ };
1367
+
1368
+ // src/entries/ring.js
1369
+ register("ring", ring);
1370
+ define("ring");
1371
+
1372
+ // src/charts/donut.js
1373
+ function donut(o, th) {
1374
+ var raw = nums(o.values).map(function(n) {
1375
+ return Math.max(0, n);
1376
+ }), lab = list(o.labels), pal = palette(o, th);
1377
+ var v = raw.slice(), labels = lab.slice();
1378
+ if (v.length > 6) {
1379
+ var idx = v.map(function(n, i) {
1380
+ return i;
1381
+ }).sort(function(a, b) {
1382
+ return raw[b] - raw[a];
1383
+ });
1384
+ var keep = idx.slice(0, 5).sort(function(a, b) {
1385
+ return a - b;
1386
+ });
1387
+ v = keep.map(function(i) {
1388
+ return raw[i];
1389
+ });
1390
+ labels = keep.map(function(i) {
1391
+ return lab[i] || "#" + (i + 1);
1392
+ });
1393
+ v.push(sum(idx.slice(5).map(function(i) {
1394
+ return raw[i];
1395
+ })));
1396
+ labels.push("Other");
1397
+ }
1398
+ var total = sum(v), h = num(o.height, 150);
1399
+ var st = statOf(v, o, "");
1400
+ return tile(o, th, h, function(x0, y0, w, h2) {
1401
+ if (!total) return emptyState(th, x0, y0, w, h2);
1402
+ var s = "", size = Math.min(h2, w * 0.5), cx = x0 + size / 2, cy = y0 + h2 / 2;
1403
+ var sw = size * 0.16, r = (size - sw) / 2, start = 0, gap = v.filter(Boolean).length > 1 ? 0.9 : 0;
1404
+ v.forEach(function(n, i) {
1405
+ var len = n / total * 100;
1406
+ if (len <= 0) return;
1407
+ var seg = Math.max(0.01, len - gap);
1408
+ s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="' + pal[i % pal.length] + '" stroke-width="' + r2(sw) + '" pathLength="100" stroke-dasharray="' + r2(seg) + " " + r2(100 - seg) + '" stroke-dashoffset="' + r2(-start) + '" transform="rotate(-90 ' + r2(cx) + " " + r2(cy) + ')"' + anim(o, "bzc-a", i * 0.08) + "/>";
1409
+ start += len;
1410
+ });
1411
+ s += text(cx, cy + 2, fmt(total, o, true), 'text-anchor="middle" fill="' + th.fg + '" font-family="' + SANS + '" font-size="' + r2(size * 0.17) + '" font-weight="800"');
1412
+ s += text(cx, cy + 2 + 14, (has(o.centerLabel) ? o.centerLabel : "Total").toUpperCase(), small(th, 9, 'text-anchor="middle" letter-spacing="1"'));
1413
+ var lx = x0 + size + 22, rowH = Math.min(24, h2 / v.length), ly = cy - rowH * v.length / 2;
1414
+ v.forEach(function(n, i) {
1415
+ var y = ly + i * rowH + rowH / 2, pct = Math.round(n / total * 100);
1416
+ var tip = (labels[i] || "#" + (i + 1)) + ": " + fmt(n, o) + " (" + pct + "%)";
1417
+ s += '<circle cx="' + r2(lx + 4) + '" cy="' + r2(y) + '" r="4" fill="' + pal[i % pal.length] + '"/>' + text(lx + 14, y + 4, trunc(labels[i] || "#" + (i + 1), Math.floor((x0 + w - lx - 50) / 6.7)), 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"') + text(x0 + w, y + 4, pct + "%", 'text-anchor="end" fill="' + th.muted + '" font-family="' + MONO + '" font-size="11" font-weight="600"') + hit(lx, y - rowH / 2, x0 + w - lx, rowH, tip);
1117
1418
  });
1118
- notes.forEach(function(nt) {
1119
- var p = flat[nt.i];
1120
- if (!p) return;
1121
- s += '<circle cx="' + r2(p[0]) + '" cy="' + r2(p[1]) + '" r="' + (r + 4) + '" fill="none" stroke="' + th.fg + '" stroke-width="1.5"/>' + text(clamp(p[0], px + 30, px + pw - 30), p[1] - r - 9, nt.text, small(th, 10, 'text-anchor="middle"', th.fg));
1419
+ start = 0;
1420
+ v.forEach(function(n, i) {
1421
+ var len = n / total * 100;
1422
+ if (len <= 0) return;
1423
+ s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="#000" stroke-opacity="0" stroke-width="' + r2(sw) + '" pathLength="100" stroke-dasharray="' + r2(len) + " " + r2(100 - len) + '" stroke-dashoffset="' + r2(-start) + '" transform="rotate(-90 ' + r2(cx) + " " + r2(cy) + ')" class="bzc-hit" data-skip="" data-tip="' + esc((labels[i] || "#" + (i + 1)) + ": " + fmt(n, o) + " (" + Math.round(len) + "%)") + '"/>';
1424
+ start += len;
1122
1425
  });
1123
1426
  return s;
1124
- }, { summary: count + " points" + (multi ? " in " + groups.length + " groups" : "") });
1427
+ }, { stat: st, summary: "total " + fmt(total, o) + " across " + v.length + " parts" });
1125
1428
  }
1126
- scatter.table = function(o) {
1127
- var X = series(o.x), Y = series(o.y), names = list(o.series), labels = list(o.labels), multi = X.length > 1 || names.length > 0;
1128
- var xo = { decimals: o.decimals, compact: o.compact, lang: o.lang }, rows = [], idx = 0;
1129
- X.forEach(function(xs, g) {
1130
- var ys = Y[g] || [];
1131
- for (var i = 0; i < Math.min(xs.length, ys.length); i++, idx++) {
1132
- rows.push([labels[idx] || "#" + (idx + 1)].concat(multi ? [names[g] || "Group " + (g + 1)] : [], [fmt(xs[i], xo), fmt(ys[i], o)]));
1133
- }
1134
- });
1135
- return { head: ["Point"].concat(multi ? ["Group"] : [], [has(o.xLabel) ? o.xLabel : "x", has(o.yLabel) ? o.yLabel : "y"]), rows };
1429
+ donut.table = function(o) {
1430
+ var v = nums(o.values).map(function(n) {
1431
+ return Math.max(0, n);
1432
+ }), labels = list(o.labels), total = sum(v);
1433
+ return { head: ["Label", "Value", "Share"], rows: v.map(function(n, i) {
1434
+ return [labels[i] || "#" + (i + 1), fmt(n, o), (total ? Math.round(n / total * 100) : 0) + "%"];
1435
+ }) };
1136
1436
  };
1137
1437
 
1138
- // src/render.js
1139
- var TYPES = { spark, line, bars, scatter, ring, donut, gauge, progress, heatmap };
1140
- var camel = (k) => k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1141
- function prepare(type, options) {
1142
- if (!TYPES[type]) {
1143
- throw new Error(`Bezel Charts: unknown chart type "${type}". Choose one of: ${Object.keys(TYPES).join(", ")}.`);
1438
+ // src/entries/donut.js
1439
+ register("donut", donut);
1440
+ define("donut");
1441
+
1442
+ // src/charts/gauge.js
1443
+ function gauge(o, th) {
1444
+ var v = nums(o.values), val = v.length ? v[0] : num(o.value, NaN), labels = list(o.labels);
1445
+ var mn = num(o.min, 0), mx = num(o.max, 100), h = num(o.height, 130);
1446
+ if (mx === mn) mx = mn + 1;
1447
+ var zones = nums(o.zones), pal = palette(o, th);
1448
+ var zc = list(o.color).filter(Boolean).length > 1 ? pal : [th.up, th.palette[3], th.down];
1449
+ var colour = pal[0];
1450
+ if (zones.length && isFinite(val)) {
1451
+ var z = 0;
1452
+ while (z < zones.length && val >= zones[z]) z++;
1453
+ colour = zc[Math.min(z, zc.length - 1)];
1144
1454
  }
1145
- const o = {};
1146
- for (const [k, v] of Object.entries(options)) o[camel(k)] = v;
1147
- return o;
1148
- }
1149
- function render(type, options = {}) {
1150
- const o = prepare(type, options);
1151
- o.animate = bool(o.animate);
1152
- o.frame = o.frame === "none" ? "none" : "bezel";
1153
- const mode = o.theme === "light" || o.theme === "dark" ? o.theme : o.autoTheme || "dark";
1154
- return TYPES[type](o, THEMES[mode]);
1155
- }
1156
- function table(type, options = {}) {
1157
- return TYPES[type].table(prepare(type, options));
1158
- }
1159
- function toStatic(svg) {
1160
- return svg.replace(/<rect class="bzc-hit"[^>]*\/>/g, "").replace(/<circle[^>]* class="bzc-hit"[^>]*\/>/g, "").replace(/ data-tip="[^"]*"/g, "");
1161
- }
1162
- function toSVG(type, options = {}) {
1163
- return toStatic(render(type, { ...options, animate: false }));
1455
+ var auto = !has(o.prefix) && !has(o.suffix) && mn === 0 && mx === 100;
1456
+ var show = function(n) {
1457
+ return fmt(n, o) + (auto ? "%" : "");
1458
+ };
1459
+ return tile(o, th, h, function(x0, y0, w, h2) {
1460
+ if (!isFinite(val)) return emptyState(th, x0, y0, w, h2);
1461
+ var R = Math.min(w / 2, h2 - 22), sw = R * 0.16, r = R - sw / 2, cx = x0 + w / 2, cy = y0 + R;
1462
+ var t = clamp((val - mn) / (mx - mn), 0, 1);
1463
+ var pt = function(f, rr) {
1464
+ var a = Math.PI * (1 - f);
1465
+ return [cx + rr * Math.cos(a), cy - rr * Math.sin(a)];
1466
+ };
1467
+ var arc = "M" + r2(cx - r) + "," + r2(cy) + "A" + r2(r) + "," + r2(r) + " 0 0 1 " + r2(cx + r) + "," + r2(cy);
1468
+ var s = '<path d="' + arc + '" fill="none" stroke="' + colour + '" stroke-opacity=".16" stroke-width="' + r2(sw) + '" stroke-linecap="round"/>';
1469
+ if (zones.length) zones.forEach(function(zv) {
1470
+ var f = clamp((zv - mn) / (mx - mn), 0, 1), a = pt(f, r - sw / 2 - 3), b = pt(f, r - sw / 2 - 8);
1471
+ s += '<line x1="' + r2(a[0]) + '" y1="' + r2(a[1]) + '" x2="' + r2(b[0]) + '" y2="' + r2(b[1]) + '" stroke="' + th.muted + '" stroke-width="1.5"/>';
1472
+ });
1473
+ if (t > 0) s += '<path d="' + arc + '" fill="none" stroke="' + colour + '" stroke-width="' + r2(sw) + '" stroke-linecap="round" pathLength="100" style="stroke-dasharray:' + r2(t * 100) + ' 100"' + anim(o, "bzc-s") + "/>";
1474
+ var e = pt(t, r);
1475
+ s += '<circle cx="' + r2(e[0]) + '" cy="' + r2(e[1]) + '" r="' + r2(sw * 0.26) + '" fill="' + (o.background || th.tile) + '"' + anim(o, "bzc-a", 0.9) + "/>";
1476
+ if (has(o.goal)) {
1477
+ var gf = clamp((+o.goal - mn) / (mx - mn), 0, 1), g1 = pt(gf, r + sw / 2 + 4), g2 = pt(gf, r - sw / 2 - 4);
1478
+ s += '<line x1="' + r2(g1[0]) + '" y1="' + r2(g1[1]) + '" x2="' + r2(g2[0]) + '" y2="' + r2(g2[1]) + '" stroke="' + th.fg + '" stroke-width="2" stroke-linecap="round"/>';
1479
+ }
1480
+ s += text(cx, cy - R * 0.12, show(val), 'text-anchor="middle" fill="' + th.fg + '" font-family="' + SANS + '" font-size="' + r2(R * 0.34) + '" font-weight="800"');
1481
+ if (labels[0]) s += text(cx, cy - R * 0.12 + 16, labels[0], small(th, 10, 'text-anchor="middle"'));
1482
+ s += text(cx - r, cy + 18, fmt(mn, o, true), small(th, 10, 'text-anchor="middle"')) + text(cx + r, cy + 18, fmt(mx, o, true), small(th, 10, 'text-anchor="middle"'));
1483
+ s += hit(cx - R, cy - R, R * 2, R, (labels[0] ? labels[0] + ": " : "") + show(val) + " (range " + fmt(mn, o) + " to " + fmt(mx, o) + ")" + (has(o.goal) ? "\nGoal: " + fmt(+o.goal, o) : ""));
1484
+ return s;
1485
+ }, { summary: isFinite(val) ? show(val) + " on a scale of " + fmt(mn, o) + " to " + fmt(mx, o) : "no data" });
1164
1486
  }
1165
-
1166
- // src/element.js
1167
- var ATTRIBUTES = [
1168
- "type",
1169
- "values",
1170
- "labels",
1171
- "series",
1172
- "heading",
1173
- "prefix",
1174
- "suffix",
1175
- "decimals",
1176
- "compact",
1177
- "color",
1178
- "theme",
1179
- "frame",
1180
- "width",
1181
- "height",
1182
- "goal",
1183
- "goal-label",
1184
- "notes",
1185
- "show-values",
1186
- "stat",
1187
- "delta",
1188
- "delta-label",
1189
- "max",
1190
- "min",
1191
- "value",
1192
- "zones",
1193
- "horizontal",
1194
- "straight",
1195
- "zero",
1196
- "start",
1197
- "lang",
1198
- "corners",
1199
- "background",
1200
- "center-label",
1201
- "highlight",
1202
- "x",
1203
- "y",
1204
- "x-label",
1205
- "y-label",
1206
- "trend",
1207
- "static",
1208
- "no-tooltips",
1209
- "stacked",
1210
- "angle-labels",
1211
- "dash",
1212
- "markers",
1213
- "patterns"
1214
- ];
1215
- var HOST_CSS = `:host{display:block;position:relative;max-width:100%}:host([hidden]){display:none}svg{display:block;max-width:100%;height:auto;overflow:visible}.tip{position:absolute;left:0;top:0;pointer-events:none;background:#0b0b10;color:#f2f1f7;font:600 12px/1.5 ${MONO};padding:7px 10px;border-radius:10px;white-space:pre;opacity:0;transform:translate(-50%,calc(-100% - 12px));transition:opacity .12s;z-index:3;border:1px solid #2a2935;box-shadow:0 8px 24px rgba(0,0,0,.35)}.tip.on{opacity:1}.c{outline:none}.c:focus-visible{outline:2px solid #fff;outline-offset:3px;box-shadow:0 0 0 7px #000}.k{stroke:currentColor;stroke-width:2;stroke-opacity:1}.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}@media (forced-colors:active){.c:focus-visible{outline-color:Highlight}}`;
1216
- var STEPS = { ArrowLeft: -1, ArrowUp: -1, ArrowRight: 1, ArrowDown: 1 };
1217
- var darkQuery = () => typeof matchMedia === "function" ? matchMedia("(prefers-color-scheme: dark)") : null;
1218
- var pageTheme = () => {
1219
- const t = typeof document !== "undefined" && document.documentElement?.getAttribute?.("data-theme");
1220
- return t === "light" || t === "dark" ? t : null;
1487
+ gauge.table = function(o) {
1488
+ var v = nums(o.values), val = v.length ? v[0] : num(o.value, NaN), labels = list(o.labels);
1489
+ var mn = num(o.min, 0), mx = num(o.max, 100), auto = !has(o.prefix) && !has(o.suffix) && mn === 0 && mx === 100;
1490
+ if (!isFinite(val)) return { head: ["Label", "Value"], rows: [] };
1491
+ return { head: ["Label", "Value"], rows: [[labels[0] || "Reading", fmt(val, o) + (auto ? "%" : "")], ["Lowest", fmt(mn, o)], ["Highest", fmt(mx, o)]].concat(has(o.goal) ? [["Goal", fmt(+o.goal, o)]] : []) };
1221
1492
  };
1222
- var live = /* @__PURE__ */ new Set();
1223
- if (typeof MutationObserver === "function" && typeof document !== "undefined" && document.documentElement) {
1224
- new MutationObserver(() => live.forEach((el) => el.refresh())).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
1225
- }
1226
- var Base = typeof HTMLElement === "undefined" ? class {
1227
- } : HTMLElement;
1228
- var BezelChart = class extends Base {
1229
- static get observedAttributes() {
1230
- return ATTRIBUTES;
1231
- }
1232
- /** The default colour sets, so tools like the Studio can show them. */
1233
- static get themes() {
1234
- return THEMES;
1235
- }
1236
- /** Which theme the chart is drawing in right now: "dark" or "light". */
1237
- get resolvedTheme() {
1238
- const t = this.getAttribute("theme");
1239
- return t === "light" || t === "dark" ? t : this.options.autoTheme;
1240
- }
1241
- /** The chart type this tag draws. Subclasses fix it; <bezel-chart> reads its type attribute. */
1242
- get chartType() {
1243
- return this.getAttribute("type") || "spark";
1493
+
1494
+ // src/entries/gauge.js
1495
+ register("gauge", gauge);
1496
+ define("gauge");
1497
+
1498
+ // src/charts/progress.js
1499
+ function progress(o, th) {
1500
+ var v = nums(o.values).map(function(n) {
1501
+ return Math.max(0, n);
1502
+ }), labels = list(o.labels), pal = palette(o, th);
1503
+ var single = v.length === 1, total = sum(v);
1504
+ var max = has(o.max) ? num(o.max, 100) : single ? 100 : total;
1505
+ if (!(max > 0)) max = 1;
1506
+ var auto = !has(o.prefix) && !has(o.suffix) && max === 100 && single;
1507
+ var show = function(n) {
1508
+ return fmt(n, o) + (auto ? "%" : "");
1509
+ };
1510
+ var d0 = dims(o), w0 = d0.w, rows = 0, layout = [];
1511
+ if (!single && v.length) {
1512
+ var x = 0;
1513
+ rows = 1;
1514
+ v.forEach(function(n, i) {
1515
+ var l = labels[i] || "#" + (i + 1), wd = 14 + tw(l, 12) + 6 + tw(fmt(n, o), 11, true) + 18;
1516
+ if (x > 0 && x + wd > w0) {
1517
+ x = 0;
1518
+ rows++;
1519
+ }
1520
+ layout.push({ x, row: rows - 1, l });
1521
+ x += wd;
1522
+ });
1244
1523
  }
1245
- connectedCallback() {
1246
- if (!this.shadowRoot) {
1247
- this.attachShadow({ mode: "open" }).innerHTML = `<style>${HOST_CSS}</style><div class="c" role="group" aria-roledescription="chart"></div><p class="sr" id="h">Use the arrow keys to step through the values.</p><div class="tip" aria-hidden="true"></div><div class="sr" aria-live="polite"></div><div class="sr"></div>`;
1248
- [, this.#box, , this.#tipEl, this.#live, this.#table] = this.shadowRoot.children;
1249
- this.shadowRoot.addEventListener("pointermove", (e) => this.#tip(e));
1250
- this.shadowRoot.addEventListener("pointerdown", (e) => this.#tip(e));
1251
- this.addEventListener("pointerleave", () => this.#tipEl.classList.remove("on"));
1252
- this.#box.addEventListener("keydown", (e) => this.#key(e));
1253
- this.#box.addEventListener("blur", () => this.#clear());
1254
- }
1255
- live.add(this);
1256
- this.#dark = darkQuery();
1257
- this.#dark?.addEventListener?.("change", this.#onScheme);
1258
- if (typeof ResizeObserver === "function") {
1259
- this.#ro = new ResizeObserver(([entry]) => {
1260
- const w = Math.round(entry.contentRect.width);
1261
- if (!this.#rendered || !this.hasAttribute("width") && w > 0 && Math.abs(w - this.#width) > 1) this.#render();
1262
- });
1263
- this.#ro.observe(this);
1264
- } else {
1265
- this.#render();
1524
+ var h = 22 + 14 + (rows ? 12 + rows * 20 : 0);
1525
+ var st = statOf(v, o, "");
1526
+ return tile(o, th, h, function(x0, y0, w, h2) {
1527
+ if (!v.length) return emptyState(th, x0, y0, w, h2);
1528
+ var s = "", by = y0 + 22, cid = newId("c");
1529
+ var head = single ? show(v[0]) + (auto ? "" : " of " + fmt(max, o)) : fmt(total, o) + " of " + fmt(max, o);
1530
+ if (single && labels[0]) s += text(x0, y0 + 11, labels[0], 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"');
1531
+ s += text(x0 + w, y0 + 11, head, 'text-anchor="end" fill="' + th.fg + '" font-family="' + MONO + '" font-size="11" font-weight="600"');
1532
+ s += '<rect x="' + x0 + '" y="' + by + '" width="' + w + '" height="14" rx="7" fill="' + th.grid + '" fill-opacity=".7"/>';
1533
+ s += '<clipPath id="' + cid + '"><rect x="' + x0 + '" y="' + by + '" width="' + w + '" height="14" rx="7"/></clipPath><g clip-path="url(#' + cid + ')"><g' + anim(o, "bzc-gx") + ">";
1534
+ var acc = 0;
1535
+ v.forEach(function(n, i) {
1536
+ var len = Math.min(n, Math.max(0, max - acc)) / max * w;
1537
+ if (len > 0) s += '<rect x="' + r2(x0 + acc / max * w) + '" y="' + by + '" width="' + r2(len) + '" height="14" fill="' + pal[i % pal.length] + '"/>';
1538
+ if (i > 0 && len > 0) s += '<rect x="' + r2(x0 + acc / max * w - 1) + '" y="' + by + '" width="2" height="14" fill="' + (o.background || th.tile) + '"/>';
1539
+ acc += n;
1540
+ });
1541
+ s += "</g></g>";
1542
+ if (has(o.goal)) {
1543
+ var gx = x0 + clamp(+o.goal / max, 0, 1) * w;
1544
+ s += '<line x1="' + r2(gx) + '" x2="' + r2(gx) + '" y1="' + (by - 4) + '" y2="' + (by + 18) + '" stroke="' + th.fg + '" stroke-width="2" stroke-linecap="round"/>';
1266
1545
  }
1267
- }
1268
- disconnectedCallback() {
1269
- live.delete(this);
1270
- this.#ro?.disconnect();
1271
- this.#dark?.removeEventListener?.("change", this.#onScheme);
1272
- }
1273
- attributeChangedCallback() {
1274
- if (!this.#rendered || this.#queued) return;
1275
- this.#queued = true;
1276
- queueMicrotask(() => {
1277
- this.#queued = false;
1278
- this.#render();
1546
+ acc = 0;
1547
+ v.forEach(function(n, i) {
1548
+ var len = Math.min(n, Math.max(0, max - acc)) / max * w;
1549
+ if (len > 0) s += hit(x0 + acc / max * w, by - 4, len, 22, (labels[i] || (single ? "Progress" : "#" + (i + 1))) + ": " + show(n) + (single ? "" : " (" + Math.round(n / max * 100) + "%)"));
1550
+ acc += n;
1279
1551
  });
1280
- }
1281
- /** Current settings as a plain object, the same shape BezelCharts.render() takes. */
1282
- get options() {
1283
- const o = {};
1284
- for (const name of ATTRIBUTES) if (this.hasAttribute(name)) o[name] = this.getAttribute(name);
1285
- if (!o.width) o.width = this.#width || 360;
1286
- o.autoTheme = pageTheme() || (this.#dark ? this.#dark.matches ? "dark" : "light" : "dark");
1287
- return o;
1288
- }
1289
- /** Redraws the chart without replaying its entrance animation. */
1290
- refresh() {
1291
- if (this.#rendered) this.#render();
1292
- }
1293
- /** The chart as a standalone static SVG string. */
1294
- toSVG(extra = {}) {
1295
- return toStatic(render(this.chartType, { ...this.options, ...extra, animate: false }));
1296
- }
1297
- /** Saves the chart as an .svg file. */
1298
- download(name) {
1299
- const url = URL.createObjectURL(new Blob([this.toSVG()], { type: "image/svg+xml" }));
1300
- const a = Object.assign(document.createElement("a"), {
1301
- href: url,
1302
- download: `${(name || this.getAttribute("heading") || this.chartType).replace(/[^\w-]+/g, "-").toLowerCase()}.svg`
1552
+ layout.forEach(function(it, i) {
1553
+ var lx = x0 + it.x, ly = by + 14 + 12 + it.row * 20 + 8;
1554
+ s += '<circle cx="' + r2(lx + 4) + '" cy="' + r2(ly) + '" r="4" fill="' + pal[i % pal.length] + '"/><text x="' + r2(lx + 14) + '" y="' + r2(ly + 4) + '" fill="' + th.fg + '" font-family="' + SANS + '" font-size="12">' + esc(it.l) + '<tspan dx="6" fill="' + th.muted + '" font-family="' + MONO + '" font-size="11">' + esc(fmt(v[i], o)) + "</tspan></text>";
1303
1555
  });
1304
- document.body.append(a);
1305
- a.click();
1306
- a.remove();
1307
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
1556
+ return s;
1557
+ }, { stat: st, summary: single ? show(v[0]) + (auto ? "" : " of " + fmt(max, o)) : fmt(total, o) + " of " + fmt(max, o) });
1558
+ }
1559
+ progress.table = function(o) {
1560
+ var v = nums(o.values).map(function(n) {
1561
+ return Math.max(0, n);
1562
+ }), labels = list(o.labels), single = v.length === 1;
1563
+ var max = has(o.max) ? num(o.max, 100) : single ? 100 : sum(v);
1564
+ if (!(max > 0)) max = 1;
1565
+ var auto = !has(o.prefix) && !has(o.suffix) && max === 100 && single;
1566
+ if (single) return { head: ["Label", "Value"], rows: [[labels[0] || "Progress", fmt(v[0], o) + (auto ? "%" : " of " + fmt(max, o))]] };
1567
+ return { head: ["Label", "Value", "Share"], rows: v.map(function(n, i) {
1568
+ return [labels[i] || "#" + (i + 1), fmt(n, o), Math.round(n / max * 100) + "%"];
1569
+ }) };
1570
+ };
1571
+
1572
+ // src/entries/progress.js
1573
+ register("progress", progress);
1574
+ define("progress");
1575
+
1576
+ // src/charts/heatmap.js
1577
+ var FULL = { weekday: "short", day: "numeric", month: "short", year: "numeric" };
1578
+ function startOf(o, len) {
1579
+ if (has(o.start) && /^\d{4}-\d{2}-\d{2}$/.test(String(o.start))) return /* @__PURE__ */ new Date(String(o.start) + "T00:00:00Z");
1580
+ var t = /* @__PURE__ */ new Date();
1581
+ return new Date(Date.UTC(t.getFullYear(), t.getMonth(), t.getDate()) - Math.max(0, len - 1) * 864e5);
1582
+ }
1583
+ function dates(lang, opt) {
1584
+ try {
1585
+ return new Intl.DateTimeFormat(lang, Object.assign({ timeZone: "UTC" }, opt));
1586
+ } catch (e) {
1587
+ return new Intl.DateTimeFormat("en-GB", Object.assign({ timeZone: "UTC" }, opt));
1308
1588
  }
1309
- #ro = null;
1310
- #box = null;
1311
- #tipEl = null;
1312
- #live = null;
1313
- #table = null;
1314
- #at = -1;
1315
- #dark = null;
1316
- #width = 0;
1317
- #rendered = false;
1318
- #queued = false;
1319
- #onScheme = () => {
1320
- const t = this.getAttribute("theme");
1321
- if (!t || t === "auto") this.refresh();
1322
- };
1323
- #render() {
1324
- if (!this.shadowRoot) return;
1325
- const w = Math.round(this.getBoundingClientRect?.().width || 0);
1326
- this.#width = w > 0 ? w : 360;
1327
- const o = this.options;
1328
- o.animate = !this.#rendered && !this.hasAttribute("static");
1329
- let svg, data = null;
1330
- try {
1331
- svg = render(this.chartType, o);
1332
- data = table(this.chartType, o);
1333
- } catch (err) {
1334
- svg = `<p style="font:13px ${SANS};color:#d63f7c">${esc(err.message)}</p>`;
1335
- }
1336
- const box = this.#box;
1337
- box.innerHTML = svg.replace(/<title>[\s\S]*?<\/title>/, "");
1338
- const img = box.querySelector("svg");
1339
- if (img) {
1340
- box.setAttribute("aria-label", img.getAttribute("aria-label"));
1341
- img.removeAttribute("role");
1342
- img.removeAttribute("aria-label");
1343
- img.setAttribute("aria-hidden", "true");
1344
- } else {
1345
- box.removeAttribute("aria-label");
1346
- }
1347
- const keys = !!img && !this.hasAttribute("no-tooltips");
1348
- if (keys) {
1349
- box.setAttribute("tabindex", "0");
1350
- box.setAttribute("aria-describedby", "h");
1351
- } else {
1352
- box.removeAttribute("tabindex");
1353
- box.removeAttribute("aria-describedby");
1354
- }
1355
- box.style.color = THEMES[o.theme === "light" || o.theme === "dark" ? o.theme : o.autoTheme].fg;
1356
- box.style.borderRadius = `${o.frame === "none" ? 4 : clamp(+o.corners || 22, 0, 60)}px`;
1357
- this.#table.innerHTML = data && data.rows.length ? tableHTML(o.heading, data) : "";
1358
- if (this.#at >= 0 && keys && this.shadowRoot.activeElement === box) this.#show(this.#at);
1359
- else this.#at = -1;
1360
- this.#rendered = true;
1361
- this.dispatchEvent(new CustomEvent("bezel-render", { bubbles: true }));
1589
+ }
1590
+ function heatmap(o, th) {
1591
+ var v = nums(o.values), c = palette(o, th)[0], lang = o.lang || "en-GB";
1592
+ var start = startOf(o, v.length);
1593
+ var offset = (start.getUTCDay() + 6) % 7, cols = Math.max(1, Math.ceil((offset + v.length) / 7));
1594
+ var d0 = dims(o), leftW = 28, maxCols = Math.max(1, Math.floor((d0.w - leftW) / 10));
1595
+ if (cols > maxCols) {
1596
+ var drop = (cols - maxCols) * 7 - offset;
1597
+ v = v.slice(drop);
1598
+ start = new Date(start.getTime() + drop * 864e5);
1599
+ offset = 0;
1600
+ cols = maxCols;
1362
1601
  }
1363
- #tip(e) {
1364
- if (this.hasAttribute("no-tooltips")) return;
1365
- const target = e.target?.closest?.("[data-tip]");
1366
- if (!target) {
1367
- this.#tipEl.classList.remove("on");
1368
- return;
1602
+ var pitch = Math.min(18, (d0.w - leftW) / cols), gap = pitch < 8 ? 1 : 3, cell = pitch - gap;
1603
+ var h = 16 + 7 * pitch + 26;
1604
+ var max = v.length ? Math.max.apply(null, v) : 0;
1605
+ var dfm = function(opt) {
1606
+ return dates(lang, opt);
1607
+ };
1608
+ var monthF = dfm({ month: "short" }), dayF = dfm({ weekday: "short" }), fullF = dfm(FULL);
1609
+ var st = statOf(v, o, "");
1610
+ return tile(o, th, h, function(x0, y0, w, h2) {
1611
+ if (!v.length) return emptyState(th, x0, y0, w, h2);
1612
+ var s = "", gx = x0 + leftW, gy = y0 + 16, lastMonthX = -99, op = [0.28, 0.5, 0.75, 1];
1613
+ if (pitch >= 9) [0, 2, 4].forEach(function(r) {
1614
+ var d = new Date(Date.UTC(2024, 0, 1 + r));
1615
+ s += text(x0, gy + r * pitch + cell / 2 + 3.5, dayF.format(d), small(th, 9));
1616
+ });
1617
+ for (var i = 0; i < v.length; i++) {
1618
+ var k = offset + i, col = Math.floor(k / 7), row = k % 7;
1619
+ var day = new Date(start.getTime() + i * 864e5), x = gx + col * pitch, y = gy + row * pitch;
1620
+ if ((day.getUTCDate() === 1 || i === 0) && x - lastMonthX > 30) {
1621
+ s += text(x, y0 + 9, monthF.format(day), small(th, 9));
1622
+ lastMonthX = x;
1623
+ }
1624
+ var lvl = v[i] <= 0 || !max ? 0 : Math.ceil(v[i] / max * 4);
1625
+ var fill = lvl ? c : th.grid, fo = lvl ? op[lvl - 1] : 0.7;
1626
+ s += '<rect x="' + r2(x) + '" y="' + r2(y) + '" width="' + r2(cell) + '" height="' + r2(cell) + '" rx="' + r2(cell * 0.24) + '" fill="' + fill + '" fill-opacity="' + fo + '"' + (o.animate ? ' class="bzc-a" style="animation-delay:' + r2(col * 0.012) + 's"' : "") + ' data-tip="' + esc(fullF.format(day) + ": " + fmt(v[i], o)) + '"/>';
1369
1627
  }
1370
- this.#place(target, e.clientX, e.clientY);
1371
- }
1372
- /** Shows the tooltip for a mark at a point on screen. */
1373
- #place(mark, x, y) {
1374
- const tip = this.#tipEl;
1375
- tip.textContent = mark.getAttribute("data-tip");
1376
- const box = this.getBoundingClientRect?.() || { left: 0, top: 0, width: 0 };
1377
- const half = tip.offsetWidth / 2;
1378
- tip.style.left = `${clamp(x - box.left, half, Math.max(half, box.width - half))}px`;
1379
- tip.style.top = `${y - box.top}px`;
1380
- tip.classList.add("on");
1381
- }
1382
- /** Every value the arrow keys visit, in order. Donuts mark their slices data-skip, as the legend rows repeat them. */
1383
- #marks() {
1384
- return [...this.#box.querySelectorAll("[data-tip]:not([data-skip])")];
1385
- }
1386
- #key(e) {
1387
- const marks = this.#marks(), n = marks.length;
1388
- if (!n) return;
1389
- let i = this.#at, step = STEPS[e.key];
1390
- if (step) {
1391
- if (this.chartType === "heatmap" && /Left|Right/.test(e.key)) step *= 7;
1392
- i = i < 0 ? step > 0 ? 0 : n - 1 : clamp(i + step, 0, n - 1);
1393
- } else if (e.key === "Home" || e.key === "End") {
1394
- i = e.key === "Home" ? 0 : n - 1;
1395
- } else {
1396
- if (e.key === "Escape") this.#clear();
1397
- return;
1628
+ var ly = gy + 7 * pitch + 14, lc = 10, lx = x0 + w - 5 * (lc + 3) - 30;
1629
+ s += text(x0, ly + 8, fmt(sum(v), o, true) + " in " + v.length + " days", small(th, 10));
1630
+ s += text(lx - 6, ly + 8, "Less", small(th, 9, 'text-anchor="end"'));
1631
+ for (var j = 0; j < 5; j++) {
1632
+ s += '<rect x="' + r2(lx + j * (lc + 3)) + '" y="' + ly + '" width="' + lc + '" height="' + lc + '" rx="2.5" fill="' + (j ? c : th.grid) + '" fill-opacity="' + (j ? op[j - 1] : 0.7) + '"/>';
1398
1633
  }
1399
- e.preventDefault();
1400
- this.#show(i, true);
1401
- }
1402
- /** Outlines value i, shows its tooltip and, when say is set, reads it out. */
1403
- #show(i, say) {
1404
- const marks = this.#marks();
1405
- if (!marks.length) return;
1406
- i = clamp(i, 0, marks.length - 1);
1407
- this.#box.querySelector(".k")?.classList.remove("k");
1408
- const mark = marks[i], r = mark.getBoundingClientRect?.() || { left: 0, top: 0, width: 0 };
1409
- mark.classList.add("k");
1410
- this.#at = i;
1411
- this.#place(mark, r.left + r.width / 2, r.top);
1412
- if (say) this.#live.textContent = `${mark.getAttribute("data-tip").replace(/\n/g, ", ")}. ${i + 1} of ${marks.length}`;
1413
- }
1414
- #clear() {
1415
- this.#box.querySelector(".k")?.classList.remove("k");
1416
- this.#tipEl.classList.remove("on");
1417
- this.#live.textContent = "";
1418
- this.#at = -1;
1419
- }
1420
- };
1421
- function tableHTML(caption, { head, rows }) {
1422
- const cells = (row) => row.map((c, i) => i ? `<td>${esc(c)}</td>` : `<th scope="row">${esc(c)}</th>`).join("");
1423
- return `<table><caption>${esc(caption || "Chart data")}</caption><thead><tr>${head.map((h) => `<th scope="col">${esc(h)}</th>`).join("")}</tr></thead><tbody>${rows.map((r) => `<tr>${cells(r)}</tr>`).join("")}</tbody></table>`;
1634
+ s += text(lx + 5 * (lc + 3) + 3, ly + 8, "More", small(th, 9));
1635
+ return s;
1636
+ }, { stat: st, summary: fmt(sum(v), o) + " across " + v.length + " days" });
1424
1637
  }
1638
+ heatmap.table = function(o) {
1639
+ var v = nums(o.values), start = startOf(o, v.length), f = dates(o.lang || "en-GB", FULL);
1640
+ return { head: ["Date", "Value"], rows: v.map(function(n, i) {
1641
+ return [f.format(new Date(start.getTime() + i * 864e5)), fmt(n, o)];
1642
+ }) };
1643
+ };
1644
+
1645
+ // src/entries/heatmap.js
1646
+ register("heatmap", heatmap);
1647
+ define("heatmap");
1425
1648
 
1426
1649
  // src/index.js
1427
- var version = "1.2.0";
1428
- var types = Object.keys(TYPES);
1429
- if (typeof customElements !== "undefined" && typeof HTMLElement !== "undefined") {
1430
- for (const type of types) {
1431
- const tag = `bezel-${type}`;
1432
- if (!customElements.get(tag)) {
1433
- customElements.define(tag, class extends BezelChart {
1434
- get chartType() {
1435
- return type;
1436
- }
1437
- });
1438
- }
1439
- }
1440
- if (!customElements.get("bezel-chart")) customElements.define("bezel-chart", class extends BezelChart {
1441
- });
1442
- }
1650
+ var version = "1.4.0";
1651
+ var types = NAMES.slice();
1443
1652
  export {
1444
1653
  BezelChart,
1445
1654
  render,