bezel-charts 1.1.1 → 1.3.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.1.1 | 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.3.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,24 +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) {
184
- var x = x0, y = y0, s = "";
185
- names.forEach(function(nm, k) {
186
- var wd = 13 + tw(nm, 12) + 16;
187
- if (x > x0 && x + wd - 16 > x0 + w) {
188
- x = x0;
189
- y += 20;
190
- }
191
- s += '<circle cx="' + r2(x + 4) + '" cy="' + r2(y + 6) + '" r="4" fill="' + pal[k % pal.length] + '"/>' + text(x + 13, 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
- function axisWidth(ticks) {
197
- return Math.max.apply(null, ticks.map(function(t) {
198
- return tw(t, 10, true);
199
- })) + 10;
200
- }
201
142
 
202
143
  // src/core/tile.js
203
144
  function dims(o) {
@@ -295,65 +236,489 @@ function spark(o, th) {
295
236
  return s;
296
237
  }, { stat: st, summary: v.length ? "latest " + fmt(v[v.length - 1], o) : "no data" });
297
238
  }
239
+ spark.table = function(o) {
240
+ var labels = list(o.labels);
241
+ return { head: ["Label", "Value"], rows: nums(o.values).map(function(n, i) {
242
+ return [labels[i] || "#" + (i + 1), fmt(n, o)];
243
+ }) };
244
+ };
298
245
 
299
- // src/charts/line.js
300
- function line(o, th) {
301
- var S = series(o.values), names = list(o.series), labels = list(o.labels), pal = palette(o, th);
302
- var h = num(o.height, 170), n = Math.max.apply(null, [0].concat(S.map(function(s) {
303
- return s.length;
304
- })));
305
- var notes = parseNotes(o, labels), multi = S.length > 1 || names.length > 0;
306
- var st = statOf(S[0] || [], o, "");
307
- return tile(o, th, h, function(x0, y0, w, h2) {
308
- if (!n) return emptyState(th, x0, y0, w, h2);
309
- var s = "", top = y0;
310
- if (multi) {
311
- var lg = legend(S.map(function(_, k) {
312
- return names[k] || "Series " + (k + 1);
313
- }), pal, th, x0, top, w);
314
- s += lg.svg;
315
- top += lg.height;
316
- }
317
- var all = [].concat.apply([], S);
318
- var mn = Math.min.apply(null, all), mx = Math.max.apply(null, all);
319
- if (has(o.goal)) {
320
- mn = Math.min(mn, +o.goal);
321
- mx = Math.max(mx, +o.goal);
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
+ }
268
+ };
269
+
270
+ // src/render.js
271
+ var NAMES = ["spark", "line", "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(", ")}.`);
280
+ }
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());
322
390
  }
323
- if (bool(o.zero)) {
324
- mn = Math.min(0, mn);
325
- mx = Math.max(0, mx);
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();
326
402
  }
327
- var sc = niceScale(mn, mx, 4);
328
- var tl = sc.ticks.map(function(t) {
329
- return fmt(t, o, true);
330
- });
331
- var axisW = Math.max.apply(null, tl.map(function(t) {
332
- return tw(t, 10, true);
333
- })) + 10;
334
- var bottom = labels.length ? 20 : 4;
335
- var px = x0 + axisW, pw = w - axisW, ph = h2 - (top - y0) - bottom;
336
- var X = function(i2) {
337
- return n === 1 ? px + pw / 2 : px + i2 * pw / (n - 1);
338
- };
339
- var Y = function(v) {
340
- return top + ph - (v - sc.lo) / (sc.hi - sc.lo) * ph;
341
- };
342
- sc.ticks.forEach(function(t, i2) {
343
- var y = Y(t);
344
- 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"'));
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();
345
415
  });
346
- if (labels.length) {
347
- var every = Math.max(1, Math.ceil(n * (Math.max.apply(null, labels.map(function(l) {
348
- return tw(l, 10, true);
349
- })) + 10) / pw));
350
- for (var i = 0; i < n; i += every) {
351
- if (labels[i] == null) continue;
352
- var anchor = n > 1 && i === 0 ? "start" : n > 1 && i === n - 1 ? "end" : "middle";
353
- s += text(X(i), top + ph + 15, labels[i], small(th, 10, 'text-anchor="' + anchor + '"'));
354
- }
355
- }
356
- notes.forEach(function(nt) {
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/charts/line.js
659
+ function line(o, th) {
660
+ var S = series(o.values), names = list(o.series), labels = list(o.labels), pal = palette(o, th);
661
+ var h = num(o.height, 170), n = Math.max.apply(null, [0].concat(S.map(function(s) {
662
+ return s.length;
663
+ })));
664
+ var notes = parseNotes(o, labels), multi = S.length > 1 || names.length > 0;
665
+ var st = statOf(S[0] || [], o, "");
666
+ var dashOf = styleOf(o.dash, Object.keys(DASHES), "solid"), markOf = styleOf(o.markers, SHAPES, "none");
667
+ var bg = o.background || th.tile;
668
+ var key = (dashOf || markOf) && function(k, x, y) {
669
+ var c = pal[k % pal.length], dk = dashOf ? DASHES[dashOf(k)] : "", mk = markOf ? markOf(k) : "none";
670
+ 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"') : "");
671
+ };
672
+ return tile(o, th, h, function(x0, y0, w, h2) {
673
+ if (!n) return emptyState(th, x0, y0, w, h2);
674
+ var s = "", top = y0;
675
+ if (multi) {
676
+ var lg = legend(S.map(function(_, k) {
677
+ return names[k] || "Series " + (k + 1);
678
+ }), pal, th, x0, top, w, key, key && 24);
679
+ s += lg.svg;
680
+ top += lg.height;
681
+ }
682
+ var all = [].concat.apply([], S);
683
+ var mn = Math.min.apply(null, all), mx = Math.max.apply(null, all);
684
+ if (has(o.goal)) {
685
+ mn = Math.min(mn, +o.goal);
686
+ mx = Math.max(mx, +o.goal);
687
+ }
688
+ if (bool(o.zero)) {
689
+ mn = Math.min(0, mn);
690
+ mx = Math.max(0, mx);
691
+ }
692
+ var sc = niceScale(mn, mx, 4);
693
+ var tl = sc.ticks.map(function(t) {
694
+ return fmt(t, o, true);
695
+ });
696
+ var axisW = Math.max.apply(null, tl.map(function(t) {
697
+ return tw(t, 10, true);
698
+ })) + 10;
699
+ var bottom = labels.length ? 20 : 4;
700
+ var px = x0 + axisW, pw = w - axisW, ph = h2 - (top - y0) - bottom;
701
+ var X = function(i2) {
702
+ return n === 1 ? px + pw / 2 : px + i2 * pw / (n - 1);
703
+ };
704
+ var Y = function(v) {
705
+ return top + ph - (v - sc.lo) / (sc.hi - sc.lo) * ph;
706
+ };
707
+ sc.ticks.forEach(function(t, i2) {
708
+ var y = Y(t);
709
+ 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"'));
710
+ });
711
+ if (labels.length) {
712
+ var every = Math.max(1, Math.ceil(n * (Math.max.apply(null, labels.map(function(l) {
713
+ return tw(l, 10, true);
714
+ })) + 10) / pw));
715
+ for (var i = 0; i < n; i += every) {
716
+ if (labels[i] == null) continue;
717
+ var anchor = n > 1 && i === 0 ? "start" : n > 1 && i === n - 1 ? "end" : "middle";
718
+ s += text(X(i), top + ph + 15, labels[i], small(th, 10, 'text-anchor="' + anchor + '"'));
719
+ }
720
+ }
721
+ notes.forEach(function(nt) {
357
722
  if (nt.i >= n) return;
358
723
  var x = X(nt.i);
359
724
  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));
@@ -369,9 +734,17 @@ function line(o, th) {
369
734
  var g = newId("g"), last = P[P.length - 1];
370
735
  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") + "/>";
371
736
  }
372
- s += '<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"' : "") + "/>";
373
- var e = P[P.length - 1];
374
- s += '<circle cx="' + r2(e[0]) + '" cy="' + r2(e[1]) + '" r="4" fill="' + c + '" stroke="' + (o.background || th.tile) + '" stroke-width="2"/>';
737
+ var dk = dashOf ? DASHES[dashOf(k)] : "", mk = markOf ? markOf(k) : "none";
738
+ 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"' : "") + "/>";
739
+ if (mk !== "none") {
740
+ var every2 = Math.ceil(v.length / 40);
741
+ v.forEach(function(y, i2) {
742
+ 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));
743
+ });
744
+ } else {
745
+ var e = P[P.length - 1];
746
+ s += '<circle cx="' + r2(e[0]) + '" cy="' + r2(e[1]) + '" r="4" fill="' + c + '" stroke="' + bg + '" stroke-width="2"/>';
747
+ }
375
748
  if (bool(o.showValues)) v.forEach(function(y, i2) {
376
749
  s += text(X(i2), Y(y) - 8, fmt(y, o, true), small(th, 9, 'text-anchor="middle"'));
377
750
  });
@@ -388,6 +761,13 @@ function line(o, th) {
388
761
  return s;
389
762
  }, { stat: st, summary: S.length + " series over " + n + " points" });
390
763
  }
764
+ line.table = function(o) {
765
+ return seriesTable(o);
766
+ };
767
+
768
+ // src/entries/line.js
769
+ register("line", line);
770
+ define("line");
391
771
 
392
772
  // src/charts/bars.js
393
773
  var ANGLE = 40;
@@ -416,8 +796,18 @@ function bars(o, th) {
416
796
  var nameOf = function(j) {
417
797
  return names[j] || "Series " + (j + 1);
418
798
  };
799
+ var patOf = grouped ? styleOf(o.patterns, PATTERNS, "solid") : null, defs = "", fills = [];
800
+ for (var p = 0; patOf && p < k; p++) {
801
+ if (patOf(p) === "solid") continue;
802
+ var pid = newId("g");
803
+ defs += pattern(pid, patOf(p), pal[p % pal.length], o.background || th.tile);
804
+ fills[p] = "url(#" + pid + ")";
805
+ }
419
806
  var colourOf = function(j, i) {
420
- return grouped ? pal[j % pal.length] : many ? pal[i % pal.length] : pal[0];
807
+ return grouped ? fills[j] || pal[j % pal.length] : many ? pal[i % pal.length] : pal[0];
808
+ };
809
+ var key = patOf && function(j, x, y) {
810
+ return '<rect x="' + r2(x) + '" y="' + r2(y + 1) + '" width="10" height="10" rx="2" fill="' + colourOf(j) + '"/>';
421
811
  };
422
812
  var opac = function(val) {
423
813
  return grouped || many || o.highlight === "none" || val === top ? 1 : 0.5;
@@ -434,7 +824,7 @@ function bars(o, th) {
434
824
  var d0 = dims(o);
435
825
  var legendH = grouped || names.length ? legend(S.map(function(_, j) {
436
826
  return nameOf(j);
437
- }), pal, th, 0, 0, d0.w).height : 0;
827
+ }), pal, th, 0, 0, d0.w, key, key && 15).height : 0;
438
828
  var rows = stacked ? 1 : k, thin = grouped && !stacked;
439
829
  var subH = thin ? 10 : 14, subGap = 3;
440
830
  var rowH = horiz ? rows * subH + (rows - 1) * subGap + (thin ? 14 : 16) : 0;
@@ -442,11 +832,11 @@ function bars(o, th) {
442
832
  var best = v0.indexOf(Math.max.apply(null, v0.length ? v0 : [0]));
443
833
  return tile(o, th, h, function(x0, y0, w, h2) {
444
834
  if (!n) return emptyState(th, x0, y0, w, h2);
445
- var s = "", top0 = y0;
835
+ var s = defs ? "<defs>" + defs + "</defs>" : "", top0 = y0;
446
836
  if (legendH) {
447
837
  s += legend(S.map(function(_, j) {
448
838
  return nameOf(j);
449
- }), pal, th, x0, y0, w).svg;
839
+ }), pal, th, x0, y0, w, key, key && 15).svg;
450
840
  top0 += legendH;
451
841
  }
452
842
  var lo = Math.min(0, Math.min.apply(null, scale)), hi = Math.max(0, top);
@@ -597,33 +987,196 @@ function bars(o, th) {
597
987
  summary: !n ? "no data" : stacked ? k + " series stacked across " + n + " groups" : grouped ? k + " series across " + n + " groups" : "highest " + (labels[best] || "") + " " + fmt(v0[best], o)
598
988
  });
599
989
  }
990
+ bars.table = function(o) {
991
+ return seriesTable(o, series(o.values).length > 1 && bool(o.stacked));
992
+ };
600
993
 
601
- // src/charts/ring.js
602
- function ring(o, th) {
603
- var v = nums(o.values), labels = list(o.labels), pal = palette(o, th);
604
- var max = num(o.max, 100) || 100, h = num(o.height, 140);
605
- var auto = !has(o.prefix) && !has(o.suffix) && max === 100;
606
- var show = function(n) {
607
- return fmt(n, o) + (auto ? "%" : "");
994
+ // src/entries/bars.js
995
+ register("bars", bars);
996
+ define("bars");
997
+
998
+ // src/charts/scatter.js
999
+ function fit(xs, ys) {
1000
+ var n = Math.min(xs.length, ys.length);
1001
+ if (n < 2) return null;
1002
+ var mx = 0, my = 0, i;
1003
+ for (i = 0; i < n; i++) {
1004
+ mx += xs[i];
1005
+ my += ys[i];
1006
+ }
1007
+ mx /= n;
1008
+ my /= n;
1009
+ var sxx = 0, syy = 0, sxy = 0;
1010
+ for (i = 0; i < n; i++) {
1011
+ sxx += (xs[i] - mx) * (xs[i] - mx);
1012
+ syy += (ys[i] - my) * (ys[i] - my);
1013
+ sxy += (xs[i] - mx) * (ys[i] - my);
1014
+ }
1015
+ if (!sxx) return null;
1016
+ var slope = sxy / sxx;
1017
+ return { slope, intercept: my - slope * mx, r: syy ? sxy / Math.sqrt(sxx * syy) : 0 };
1018
+ }
1019
+ function scatter(o, th) {
1020
+ var X = series(o.x), Y = series(o.y), names = list(o.series), labels = list(o.labels), pal = palette(o, th);
1021
+ var groups = [];
1022
+ for (var g = 0; g < Math.max(X.length, Y.length); g++) {
1023
+ var xs = X[g] || [], ys = Y[g] || [], m = Math.min(xs.length, ys.length);
1024
+ groups.push({ xs: xs.slice(0, m), ys: ys.slice(0, m) });
1025
+ }
1026
+ groups = groups.filter(function(gr) {
1027
+ return gr.xs.length;
1028
+ });
1029
+ var count = groups.reduce(function(a, gr) {
1030
+ return a + gr.xs.length;
1031
+ }, 0);
1032
+ var h = num(o.height, 210), multi = groups.length > 1 || names.length > 0, trend = bool(o.trend);
1033
+ var nameOf = function(j) {
1034
+ return names[j] || "Group " + (j + 1);
1035
+ };
1036
+ var xo = { decimals: o.decimals, compact: o.compact, lang: o.lang };
1037
+ var notes = parseNotes(o, labels);
1038
+ var markOf = styleOf(o.markers, SHAPES, "circle"), bg = o.background || th.tile;
1039
+ var shapeOf = function(j) {
1040
+ var m2 = markOf ? markOf(j) : "circle";
1041
+ return m2 === "none" ? "circle" : m2;
1042
+ };
1043
+ var key = markOf && function(j, x, y) {
1044
+ return marker(shapeOf(j), x + 4, y + 6, 4, ' fill="' + pal[j % pal.length] + '"');
608
1045
  };
609
- var st = statOf(v, o, "");
610
1046
  return tile(o, th, h, function(x0, y0, w, h2) {
611
- if (!v.length) return emptyState(th, x0, y0, w, h2);
612
- var s = "", single = v.length === 1;
613
- var size = Math.min(h2, single ? w : w * 0.55);
614
- var cx = single ? x0 + w / 2 : x0 + size / 2, cy = y0 + h2 / 2;
615
- var sw = single ? size * 0.12 : clamp(size * 0.42 / (v.length * 1.35), 5, 16), gap = sw * 0.38;
616
- v.forEach(function(n, i) {
617
- var r = size / 2 - sw / 2 - i * (sw + gap);
618
- if (r <= sw / 2) return;
619
- var pct = clamp(n / max * 100, 0, 100), c = pal[i % pal.length];
620
- s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="' + c + '" stroke-opacity=".16" stroke-width="' + r2(sw) + '"/>';
621
- 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"' : "") + "/>";
622
- 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))) + '"/>';
623
- });
624
- if (single) {
625
- 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"');
626
- if (labels[0]) s += text(cx, cy + size * 0.08 + 16, labels[0], small(th, 10, 'text-anchor="middle"'));
1047
+ if (!count) return emptyState(th, x0, y0, w, h2);
1048
+ var s = "", top = y0;
1049
+ if (multi) {
1050
+ var lg = legend(groups.map(function(_, j) {
1051
+ return nameOf(j);
1052
+ }), pal, th, x0, top, w, key);
1053
+ s += lg.svg;
1054
+ top += lg.height;
1055
+ }
1056
+ var ax = [].concat.apply([], groups.map(function(gr) {
1057
+ return gr.xs;
1058
+ }));
1059
+ var ay = [].concat.apply([], groups.map(function(gr) {
1060
+ return gr.ys;
1061
+ }));
1062
+ var ymin = Math.min.apply(null, ay), ymax = Math.max.apply(null, ay);
1063
+ if (has(o.goal)) {
1064
+ ymin = Math.min(ymin, +o.goal);
1065
+ ymax = Math.max(ymax, +o.goal);
1066
+ }
1067
+ if (bool(o.zero)) {
1068
+ ymin = Math.min(0, ymin);
1069
+ ymax = Math.max(0, ymax);
1070
+ }
1071
+ var sy = niceScale(ymin, ymax, 4), sx = niceScale(Math.min.apply(null, ax), Math.max.apply(null, ax), 5);
1072
+ var tly = sy.ticks.map(function(t) {
1073
+ return fmt(t, o, true);
1074
+ });
1075
+ var axisW = axisWidth(tly) + (has(o.yLabel) ? 14 : 0);
1076
+ var bottom = 20 + (has(o.xLabel) ? 16 : 0);
1077
+ var px = x0 + axisW, pw = w - axisW, ph = h2 - (top - y0) - bottom;
1078
+ var PX = function(v) {
1079
+ return px + 6 + (v - sx.lo) / (sx.hi - sx.lo) * (pw - 12);
1080
+ };
1081
+ var PY = function(v) {
1082
+ return top + 6 + ph - 6 - (v - sy.lo) / (sy.hi - sy.lo) * (ph - 6);
1083
+ };
1084
+ sy.ticks.forEach(function(t, i) {
1085
+ var y = PY(t);
1086
+ s += '<line x1="' + r2(px) + '" x2="' + r2(px + pw) + '" y1="' + r2(y) + '" y2="' + r2(y) + '" stroke="' + th.grid + '" stroke-dasharray="2 4"/>' + text(px - 8, y + 3.5, tly[i], small(th, 10, 'text-anchor="end"'));
1087
+ });
1088
+ var every = Math.max(1, Math.ceil(sx.ticks.length * 44 / pw));
1089
+ sx.ticks.forEach(function(t, i) {
1090
+ if (i % every) return;
1091
+ var x = PX(t);
1092
+ s += '<line x1="' + r2(x) + '" x2="' + r2(x) + '" y1="' + r2(top) + '" y2="' + r2(top + ph) + '" stroke="' + th.grid + '" stroke-dasharray="2 4"/>' + text(x, top + ph + 15, fmt(t, xo, true), small(th, 10, 'text-anchor="middle"'));
1093
+ });
1094
+ if (has(o.xLabel)) s += text(px + pw / 2, top + ph + 32, o.xLabel, small(th, 10, 'text-anchor="middle" font-weight="600"'));
1095
+ if (has(o.yLabel)) {
1096
+ var cy = top + ph / 2;
1097
+ s += '<text x="' + r2(x0 + 4) + '" y="' + r2(cy) + '" transform="rotate(-90 ' + r2(x0 + 4) + " " + r2(cy) + ')" text-anchor="middle" ' + small(th, 10, 'font-weight="600"') + ' dominant-baseline="hanging">' + String(o.yLabel).replace(/[&<>"]/g, function(c) {
1098
+ return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c];
1099
+ }) + "</text>";
1100
+ }
1101
+ if (has(o.goal)) s += goalLine(o, th, px, px + pw, PY(+o.goal));
1102
+ if (trend) {
1103
+ groups.forEach(function(gr, j) {
1104
+ var f = fit(gr.xs, gr.ys);
1105
+ if (!f) return;
1106
+ var xa = Math.min.apply(null, gr.xs), xb = Math.max.apply(null, gr.xs);
1107
+ var ya = f.slope * xa + f.intercept, yb = f.slope * xb + f.intercept;
1108
+ var c = pal[j % pal.length];
1109
+ s += '<line x1="' + r2(PX(xa)) + '" y1="' + r2(clamp(PY(ya), top, top + ph)) + '" x2="' + r2(PX(xb)) + '" y2="' + r2(clamp(PY(yb), top, top + ph)) + '" stroke="' + c + '" stroke-width="2" stroke-dasharray="6 4" stroke-linecap="round" stroke-opacity=".9"/>';
1110
+ s += text(Math.min(PX(xb), px + pw) - 2, clamp(PY(yb), top + 10, top + ph) - 7, "r = " + f.r.toFixed(2), small(th, 10, 'text-anchor="end" font-weight="600"', c));
1111
+ });
1112
+ }
1113
+ var idx = 0, r = count > 150 ? 3 : 4.5;
1114
+ groups.forEach(function(gr, j) {
1115
+ var c = pal[j % pal.length];
1116
+ gr.xs.forEach(function(xv, i) {
1117
+ var cx = PX(xv), cy2 = PY(gr.ys[i]);
1118
+ var tip = (labels[idx] ? labels[idx] + "\n" : multi ? nameOf(j) + "\n" : "") + (has(o.xLabel) ? o.xLabel : "x") + ": " + fmt(xv, xo) + "\n" + (has(o.yLabel) ? o.yLabel : "y") + ": " + fmt(gr.ys[i], o);
1119
+ s += marker(shapeOf(j), cx, cy2, r, ' fill="' + c + '" fill-opacity=".85" stroke="' + bg + '" stroke-width="1.5"' + (o.animate ? ' class="bzc-a" style="animation-delay:' + r2(Math.min(0.8, idx * 0.012)) + 's"' : ""));
1120
+ s += hit(cx - 8, cy2 - 8, 16, 16, tip);
1121
+ idx++;
1122
+ });
1123
+ });
1124
+ var flat = [];
1125
+ groups.forEach(function(gr) {
1126
+ gr.xs.forEach(function(xv, i) {
1127
+ flat.push([PX(xv), PY(gr.ys[i])]);
1128
+ });
1129
+ });
1130
+ notes.forEach(function(nt) {
1131
+ var p = flat[nt.i];
1132
+ if (!p) return;
1133
+ 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));
1134
+ });
1135
+ return s;
1136
+ }, { summary: count + " points" + (multi ? " in " + groups.length + " groups" : "") });
1137
+ }
1138
+ scatter.table = function(o) {
1139
+ var X = series(o.x), Y = series(o.y), names = list(o.series), labels = list(o.labels), multi = X.length > 1 || names.length > 0;
1140
+ var xo = { decimals: o.decimals, compact: o.compact, lang: o.lang }, rows = [], idx = 0;
1141
+ X.forEach(function(xs, g) {
1142
+ var ys = Y[g] || [];
1143
+ for (var i = 0; i < Math.min(xs.length, ys.length); i++, idx++) {
1144
+ rows.push([labels[idx] || "#" + (idx + 1)].concat(multi ? [names[g] || "Group " + (g + 1)] : [], [fmt(xs[i], xo), fmt(ys[i], o)]));
1145
+ }
1146
+ });
1147
+ return { head: ["Point"].concat(multi ? ["Group"] : [], [has(o.xLabel) ? o.xLabel : "x", has(o.yLabel) ? o.yLabel : "y"]), rows };
1148
+ };
1149
+
1150
+ // src/entries/scatter.js
1151
+ register("scatter", scatter);
1152
+ define("scatter");
1153
+
1154
+ // src/charts/ring.js
1155
+ function ring(o, th) {
1156
+ var v = nums(o.values), labels = list(o.labels), pal = palette(o, th);
1157
+ var max = num(o.max, 100) || 100, h = num(o.height, 140);
1158
+ var auto = !has(o.prefix) && !has(o.suffix) && max === 100;
1159
+ var show = function(n) {
1160
+ return fmt(n, o) + (auto ? "%" : "");
1161
+ };
1162
+ var st = statOf(v, o, "");
1163
+ return tile(o, th, h, function(x0, y0, w, h2) {
1164
+ if (!v.length) return emptyState(th, x0, y0, w, h2);
1165
+ var s = "", single = v.length === 1;
1166
+ var size = Math.min(h2, single ? w : w * 0.55);
1167
+ var cx = single ? x0 + w / 2 : x0 + size / 2, cy = y0 + h2 / 2;
1168
+ var sw = single ? size * 0.12 : clamp(size * 0.42 / (v.length * 1.35), 5, 16), gap = sw * 0.38;
1169
+ v.forEach(function(n, i) {
1170
+ var r = size / 2 - sw / 2 - i * (sw + gap);
1171
+ if (r <= sw / 2) return;
1172
+ var pct = clamp(n / max * 100, 0, 100), c = pal[i % pal.length];
1173
+ s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="' + c + '" stroke-opacity=".16" stroke-width="' + r2(sw) + '"/>';
1174
+ 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"' : "") + "/>";
1175
+ 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))) + '"/>';
1176
+ });
1177
+ if (single) {
1178
+ 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"');
1179
+ if (labels[0]) s += text(cx, cy + size * 0.08 + 16, labels[0], small(th, 10, 'text-anchor="middle"'));
627
1180
  } else {
628
1181
  var lx = x0 + size + 22, rowH = Math.min(26, h2 / v.length), ly = cy - rowH * v.length / 2;
629
1182
  v.forEach(function(n, i) {
@@ -636,6 +1189,16 @@ function ring(o, th) {
636
1189
  return (labels[i] ? labels[i] + " " : "") + show(n);
637
1190
  }).join(", ") });
638
1191
  }
1192
+ ring.table = function(o) {
1193
+ var labels = list(o.labels), max = num(o.max, 100) || 100, auto = !has(o.prefix) && !has(o.suffix) && max === 100;
1194
+ return { head: ["Label", "Value"], rows: nums(o.values).map(function(n, i) {
1195
+ return [labels[i] || "#" + (i + 1), fmt(n, o) + (auto ? "%" : " of " + fmt(max, o))];
1196
+ }) };
1197
+ };
1198
+
1199
+ // src/entries/ring.js
1200
+ register("ring", ring);
1201
+ define("ring");
639
1202
 
640
1203
  // src/charts/donut.js
641
1204
  function donut(o, th) {
@@ -688,12 +1251,24 @@ function donut(o, th) {
688
1251
  v.forEach(function(n, i) {
689
1252
  var len = n / total * 100;
690
1253
  if (len <= 0) return;
691
- 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-tip="' + esc((labels[i] || "#" + (i + 1)) + ": " + fmt(n, o) + " (" + Math.round(len) + "%)") + '"/>';
1254
+ 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) + "%)") + '"/>';
692
1255
  start += len;
693
1256
  });
694
1257
  return s;
695
1258
  }, { stat: st, summary: "total " + fmt(total, o) + " across " + v.length + " parts" });
696
1259
  }
1260
+ donut.table = function(o) {
1261
+ var v = nums(o.values).map(function(n) {
1262
+ return Math.max(0, n);
1263
+ }), labels = list(o.labels), total = sum(v);
1264
+ return { head: ["Label", "Value", "Share"], rows: v.map(function(n, i) {
1265
+ return [labels[i] || "#" + (i + 1), fmt(n, o), (total ? Math.round(n / total * 100) : 0) + "%"];
1266
+ }) };
1267
+ };
1268
+
1269
+ // src/entries/donut.js
1270
+ register("donut", donut);
1271
+ define("donut");
697
1272
 
698
1273
  // src/charts/gauge.js
699
1274
  function gauge(o, th) {
@@ -740,6 +1315,16 @@ function gauge(o, th) {
740
1315
  return s;
741
1316
  }, { summary: isFinite(val) ? show(val) + " on a scale of " + fmt(mn, o) + " to " + fmt(mx, o) : "no data" });
742
1317
  }
1318
+ gauge.table = function(o) {
1319
+ var v = nums(o.values), val = v.length ? v[0] : num(o.value, NaN), labels = list(o.labels);
1320
+ var mn = num(o.min, 0), mx = num(o.max, 100), auto = !has(o.prefix) && !has(o.suffix) && mn === 0 && mx === 100;
1321
+ if (!isFinite(val)) return { head: ["Label", "Value"], rows: [] };
1322
+ 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)]] : []) };
1323
+ };
1324
+
1325
+ // src/entries/gauge.js
1326
+ register("gauge", gauge);
1327
+ define("gauge");
743
1328
 
744
1329
  // src/charts/progress.js
745
1330
  function progress(o, th) {
@@ -802,16 +1387,40 @@ function progress(o, th) {
802
1387
  return s;
803
1388
  }, { stat: st, summary: single ? show(v[0]) + (auto ? "" : " of " + fmt(max, o)) : fmt(total, o) + " of " + fmt(max, o) });
804
1389
  }
1390
+ progress.table = function(o) {
1391
+ var v = nums(o.values).map(function(n) {
1392
+ return Math.max(0, n);
1393
+ }), labels = list(o.labels), single = v.length === 1;
1394
+ var max = has(o.max) ? num(o.max, 100) : single ? 100 : sum(v);
1395
+ if (!(max > 0)) max = 1;
1396
+ var auto = !has(o.prefix) && !has(o.suffix) && max === 100 && single;
1397
+ if (single) return { head: ["Label", "Value"], rows: [[labels[0] || "Progress", fmt(v[0], o) + (auto ? "%" : " of " + fmt(max, o))]] };
1398
+ return { head: ["Label", "Value", "Share"], rows: v.map(function(n, i) {
1399
+ return [labels[i] || "#" + (i + 1), fmt(n, o), Math.round(n / max * 100) + "%"];
1400
+ }) };
1401
+ };
1402
+
1403
+ // src/entries/progress.js
1404
+ register("progress", progress);
1405
+ define("progress");
805
1406
 
806
1407
  // src/charts/heatmap.js
1408
+ var FULL = { weekday: "short", day: "numeric", month: "short", year: "numeric" };
1409
+ function startOf(o, len) {
1410
+ if (has(o.start) && /^\d{4}-\d{2}-\d{2}$/.test(String(o.start))) return /* @__PURE__ */ new Date(String(o.start) + "T00:00:00Z");
1411
+ var t = /* @__PURE__ */ new Date();
1412
+ return new Date(Date.UTC(t.getFullYear(), t.getMonth(), t.getDate()) - Math.max(0, len - 1) * 864e5);
1413
+ }
1414
+ function dates(lang, opt) {
1415
+ try {
1416
+ return new Intl.DateTimeFormat(lang, Object.assign({ timeZone: "UTC" }, opt));
1417
+ } catch (e) {
1418
+ return new Intl.DateTimeFormat("en-GB", Object.assign({ timeZone: "UTC" }, opt));
1419
+ }
1420
+ }
807
1421
  function heatmap(o, th) {
808
1422
  var v = nums(o.values), c = palette(o, th)[0], lang = o.lang || "en-GB";
809
- var start;
810
- if (has(o.start) && /^\d{4}-\d{2}-\d{2}$/.test(String(o.start))) start = /* @__PURE__ */ new Date(String(o.start) + "T00:00:00Z");
811
- else {
812
- var t = /* @__PURE__ */ new Date();
813
- start = new Date(Date.UTC(t.getFullYear(), t.getMonth(), t.getDate()) - Math.max(0, v.length - 1) * 864e5);
814
- }
1423
+ var start = startOf(o, v.length);
815
1424
  var offset = (start.getUTCDay() + 6) % 7, cols = Math.max(1, Math.ceil((offset + v.length) / 7));
816
1425
  var d0 = dims(o), leftW = 28, maxCols = Math.max(1, Math.floor((d0.w - leftW) / 10));
817
1426
  if (cols > maxCols) {
@@ -825,13 +1434,9 @@ function heatmap(o, th) {
825
1434
  var h = 16 + 7 * pitch + 26;
826
1435
  var max = v.length ? Math.max.apply(null, v) : 0;
827
1436
  var dfm = function(opt) {
828
- try {
829
- return new Intl.DateTimeFormat(lang, Object.assign({ timeZone: "UTC" }, opt));
830
- } catch (e) {
831
- return new Intl.DateTimeFormat("en-GB", Object.assign({ timeZone: "UTC" }, opt));
832
- }
1437
+ return dates(lang, opt);
833
1438
  };
834
- var monthF = dfm({ month: "short" }), dayF = dfm({ weekday: "short" }), fullF = dfm({ weekday: "short", day: "numeric", month: "short", year: "numeric" });
1439
+ var monthF = dfm({ month: "short" }), dayF = dfm({ weekday: "short" }), fullF = dfm(FULL);
835
1440
  var st = statOf(v, o, "");
836
1441
  return tile(o, th, h, function(x0, y0, w, h2) {
837
1442
  if (!v.length) return emptyState(th, x0, y0, w, h2);
@@ -861,357 +1466,20 @@ function heatmap(o, th) {
861
1466
  return s;
862
1467
  }, { stat: st, summary: fmt(sum(v), o) + " across " + v.length + " days" });
863
1468
  }
864
-
865
- // src/charts/scatter.js
866
- function fit(xs, ys) {
867
- var n = Math.min(xs.length, ys.length);
868
- if (n < 2) return null;
869
- var mx = 0, my = 0, i;
870
- for (i = 0; i < n; i++) {
871
- mx += xs[i];
872
- my += ys[i];
873
- }
874
- mx /= n;
875
- my /= n;
876
- var sxx = 0, syy = 0, sxy = 0;
877
- for (i = 0; i < n; i++) {
878
- sxx += (xs[i] - mx) * (xs[i] - mx);
879
- syy += (ys[i] - my) * (ys[i] - my);
880
- sxy += (xs[i] - mx) * (ys[i] - my);
881
- }
882
- if (!sxx) return null;
883
- var slope = sxy / sxx;
884
- return { slope, intercept: my - slope * mx, r: syy ? sxy / Math.sqrt(sxx * syy) : 0 };
885
- }
886
- function scatter(o, th) {
887
- var X = series(o.x), Y = series(o.y), names = list(o.series), labels = list(o.labels), pal = palette(o, th);
888
- var groups = [];
889
- for (var g = 0; g < Math.max(X.length, Y.length); g++) {
890
- var xs = X[g] || [], ys = Y[g] || [], m = Math.min(xs.length, ys.length);
891
- groups.push({ xs: xs.slice(0, m), ys: ys.slice(0, m) });
892
- }
893
- groups = groups.filter(function(gr) {
894
- return gr.xs.length;
895
- });
896
- var count = groups.reduce(function(a, gr) {
897
- return a + gr.xs.length;
898
- }, 0);
899
- var h = num(o.height, 210), multi = groups.length > 1 || names.length > 0, trend = bool(o.trend);
900
- var nameOf = function(j) {
901
- return names[j] || "Group " + (j + 1);
902
- };
903
- var xo = { decimals: o.decimals, compact: o.compact, lang: o.lang };
904
- var notes = parseNotes(o, labels);
905
- return tile(o, th, h, function(x0, y0, w, h2) {
906
- if (!count) return emptyState(th, x0, y0, w, h2);
907
- var s = "", top = y0;
908
- if (multi) {
909
- var lg = legend(groups.map(function(_, j) {
910
- return nameOf(j);
911
- }), pal, th, x0, top, w);
912
- s += lg.svg;
913
- top += lg.height;
914
- }
915
- var ax = [].concat.apply([], groups.map(function(gr) {
916
- return gr.xs;
917
- }));
918
- var ay = [].concat.apply([], groups.map(function(gr) {
919
- return gr.ys;
920
- }));
921
- var ymin = Math.min.apply(null, ay), ymax = Math.max.apply(null, ay);
922
- if (has(o.goal)) {
923
- ymin = Math.min(ymin, +o.goal);
924
- ymax = Math.max(ymax, +o.goal);
925
- }
926
- if (bool(o.zero)) {
927
- ymin = Math.min(0, ymin);
928
- ymax = Math.max(0, ymax);
929
- }
930
- var sy = niceScale(ymin, ymax, 4), sx = niceScale(Math.min.apply(null, ax), Math.max.apply(null, ax), 5);
931
- var tly = sy.ticks.map(function(t) {
932
- return fmt(t, o, true);
933
- });
934
- var axisW = axisWidth(tly) + (has(o.yLabel) ? 14 : 0);
935
- var bottom = 20 + (has(o.xLabel) ? 16 : 0);
936
- var px = x0 + axisW, pw = w - axisW, ph = h2 - (top - y0) - bottom;
937
- var PX = function(v) {
938
- return px + 6 + (v - sx.lo) / (sx.hi - sx.lo) * (pw - 12);
939
- };
940
- var PY = function(v) {
941
- return top + 6 + ph - 6 - (v - sy.lo) / (sy.hi - sy.lo) * (ph - 6);
942
- };
943
- sy.ticks.forEach(function(t, i) {
944
- var y = PY(t);
945
- s += '<line x1="' + r2(px) + '" x2="' + r2(px + pw) + '" y1="' + r2(y) + '" y2="' + r2(y) + '" stroke="' + th.grid + '" stroke-dasharray="2 4"/>' + text(px - 8, y + 3.5, tly[i], small(th, 10, 'text-anchor="end"'));
946
- });
947
- var every = Math.max(1, Math.ceil(sx.ticks.length * 44 / pw));
948
- sx.ticks.forEach(function(t, i) {
949
- if (i % every) return;
950
- var x = PX(t);
951
- s += '<line x1="' + r2(x) + '" x2="' + r2(x) + '" y1="' + r2(top) + '" y2="' + r2(top + ph) + '" stroke="' + th.grid + '" stroke-dasharray="2 4"/>' + text(x, top + ph + 15, fmt(t, xo, true), small(th, 10, 'text-anchor="middle"'));
952
- });
953
- if (has(o.xLabel)) s += text(px + pw / 2, top + ph + 32, o.xLabel, small(th, 10, 'text-anchor="middle" font-weight="600"'));
954
- if (has(o.yLabel)) {
955
- var cy = top + ph / 2;
956
- s += '<text x="' + r2(x0 + 4) + '" y="' + r2(cy) + '" transform="rotate(-90 ' + r2(x0 + 4) + " " + r2(cy) + ')" text-anchor="middle" ' + small(th, 10, 'font-weight="600"') + ' dominant-baseline="hanging">' + String(o.yLabel).replace(/[&<>"]/g, function(c) {
957
- return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c];
958
- }) + "</text>";
959
- }
960
- if (has(o.goal)) s += goalLine(o, th, px, px + pw, PY(+o.goal));
961
- if (trend) {
962
- groups.forEach(function(gr, j) {
963
- var f = fit(gr.xs, gr.ys);
964
- if (!f) return;
965
- var xa = Math.min.apply(null, gr.xs), xb = Math.max.apply(null, gr.xs);
966
- var ya = f.slope * xa + f.intercept, yb = f.slope * xb + f.intercept;
967
- var c = pal[j % pal.length];
968
- s += '<line x1="' + r2(PX(xa)) + '" y1="' + r2(clamp(PY(ya), top, top + ph)) + '" x2="' + r2(PX(xb)) + '" y2="' + r2(clamp(PY(yb), top, top + ph)) + '" stroke="' + c + '" stroke-width="2" stroke-dasharray="6 4" stroke-linecap="round" stroke-opacity=".9"/>';
969
- s += text(Math.min(PX(xb), px + pw) - 2, clamp(PY(yb), top + 10, top + ph) - 7, "r = " + f.r.toFixed(2), small(th, 10, 'text-anchor="end" font-weight="600"', c));
970
- });
971
- }
972
- var idx = 0, r = count > 150 ? 3 : 4.5;
973
- groups.forEach(function(gr, j) {
974
- var c = pal[j % pal.length];
975
- gr.xs.forEach(function(xv, i) {
976
- var cx = PX(xv), cy2 = PY(gr.ys[i]);
977
- var tip = (labels[idx] ? labels[idx] + "\n" : multi ? nameOf(j) + "\n" : "") + (has(o.xLabel) ? o.xLabel : "x") + ": " + fmt(xv, xo) + "\n" + (has(o.yLabel) ? o.yLabel : "y") + ": " + fmt(gr.ys[i], o);
978
- s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy2) + '" r="' + r + '" fill="' + c + '" fill-opacity=".85" stroke="' + (o.background || th.tile) + '" stroke-width="1.5"' + (o.animate ? ' class="bzc-a" style="animation-delay:' + r2(Math.min(0.8, idx * 0.012)) + 's"' : "") + "/>";
979
- s += hit(cx - 8, cy2 - 8, 16, 16, tip);
980
- idx++;
981
- });
982
- });
983
- var flat = [];
984
- groups.forEach(function(gr) {
985
- gr.xs.forEach(function(xv, i) {
986
- flat.push([PX(xv), PY(gr.ys[i])]);
987
- });
988
- });
989
- notes.forEach(function(nt) {
990
- var p = flat[nt.i];
991
- if (!p) return;
992
- 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));
993
- });
994
- return s;
995
- }, { summary: count + " points" + (multi ? " in " + groups.length + " groups" : "") });
996
- }
997
-
998
- // src/render.js
999
- var TYPES = { spark, line, bars, scatter, ring, donut, gauge, progress, heatmap };
1000
- var camel = (k) => k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1001
- function render(type, options = {}) {
1002
- if (!TYPES[type]) {
1003
- throw new Error(`Bezel Charts: unknown chart type "${type}". Choose one of: ${Object.keys(TYPES).join(", ")}.`);
1004
- }
1005
- const o = {};
1006
- for (const [k, v] of Object.entries(options)) o[camel(k)] = v;
1007
- o.animate = bool(o.animate);
1008
- o.frame = o.frame === "none" ? "none" : "bezel";
1009
- const mode = o.theme === "light" || o.theme === "dark" ? o.theme : o.autoTheme || "dark";
1010
- return TYPES[type](o, THEMES[mode]);
1011
- }
1012
- function toStatic(svg) {
1013
- return svg.replace(/<rect class="bzc-hit"[^>]*\/>/g, "").replace(/<circle[^>]* class="bzc-hit"[^>]*\/>/g, "").replace(/ data-tip="[^"]*"/g, "");
1014
- }
1015
- function toSVG(type, options = {}) {
1016
- return toStatic(render(type, { ...options, animate: false }));
1017
- }
1018
-
1019
- // src/element.js
1020
- var ATTRIBUTES = [
1021
- "type",
1022
- "values",
1023
- "labels",
1024
- "series",
1025
- "heading",
1026
- "prefix",
1027
- "suffix",
1028
- "decimals",
1029
- "compact",
1030
- "color",
1031
- "theme",
1032
- "frame",
1033
- "width",
1034
- "height",
1035
- "goal",
1036
- "goal-label",
1037
- "notes",
1038
- "show-values",
1039
- "stat",
1040
- "delta",
1041
- "delta-label",
1042
- "max",
1043
- "min",
1044
- "value",
1045
- "zones",
1046
- "horizontal",
1047
- "straight",
1048
- "zero",
1049
- "start",
1050
- "lang",
1051
- "corners",
1052
- "background",
1053
- "center-label",
1054
- "highlight",
1055
- "x",
1056
- "y",
1057
- "x-label",
1058
- "y-label",
1059
- "trend",
1060
- "static",
1061
- "no-tooltips",
1062
- "stacked",
1063
- "angle-labels"
1064
- ];
1065
- 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}`;
1066
- var darkQuery = () => typeof matchMedia === "function" ? matchMedia("(prefers-color-scheme: dark)") : null;
1067
- var pageTheme = () => {
1068
- const t = typeof document !== "undefined" && document.documentElement?.getAttribute?.("data-theme");
1069
- return t === "light" || t === "dark" ? t : null;
1070
- };
1071
- var live = /* @__PURE__ */ new Set();
1072
- if (typeof MutationObserver === "function" && typeof document !== "undefined" && document.documentElement) {
1073
- new MutationObserver(() => live.forEach((el) => el.refresh())).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
1074
- }
1075
- var Base = typeof HTMLElement === "undefined" ? class {
1076
- } : HTMLElement;
1077
- var BezelChart = class extends Base {
1078
- static get observedAttributes() {
1079
- return ATTRIBUTES;
1080
- }
1081
- /** The default colour sets, so tools like the Studio can show them. */
1082
- static get themes() {
1083
- return THEMES;
1084
- }
1085
- /** Which theme the chart is drawing in right now: "dark" or "light". */
1086
- get resolvedTheme() {
1087
- const t = this.getAttribute("theme");
1088
- return t === "light" || t === "dark" ? t : this.options.autoTheme;
1089
- }
1090
- /** The chart type this tag draws. Subclasses fix it; <bezel-chart> reads its type attribute. */
1091
- get chartType() {
1092
- return this.getAttribute("type") || "spark";
1093
- }
1094
- connectedCallback() {
1095
- if (!this.shadowRoot) {
1096
- this.attachShadow({ mode: "open" });
1097
- this.shadowRoot.addEventListener("pointermove", (e) => this.#tip(e));
1098
- this.shadowRoot.addEventListener("pointerdown", (e) => this.#tip(e));
1099
- this.addEventListener("pointerleave", () => this.shadowRoot.querySelector(".tip")?.classList.remove("on"));
1100
- }
1101
- live.add(this);
1102
- this.#dark = darkQuery();
1103
- this.#dark?.addEventListener?.("change", this.#onScheme);
1104
- if (typeof ResizeObserver === "function") {
1105
- this.#ro = new ResizeObserver(([entry]) => {
1106
- const w = Math.round(entry.contentRect.width);
1107
- if (!this.#rendered || !this.hasAttribute("width") && w > 0 && Math.abs(w - this.#width) > 1) this.#render();
1108
- });
1109
- this.#ro.observe(this);
1110
- } else {
1111
- this.#render();
1112
- }
1113
- }
1114
- disconnectedCallback() {
1115
- live.delete(this);
1116
- this.#ro?.disconnect();
1117
- this.#dark?.removeEventListener?.("change", this.#onScheme);
1118
- }
1119
- attributeChangedCallback() {
1120
- if (!this.#rendered || this.#queued) return;
1121
- this.#queued = true;
1122
- queueMicrotask(() => {
1123
- this.#queued = false;
1124
- this.#render();
1125
- });
1126
- }
1127
- /** Current settings as a plain object, the same shape BezelCharts.render() takes. */
1128
- get options() {
1129
- const o = {};
1130
- for (const name of ATTRIBUTES) if (this.hasAttribute(name)) o[name] = this.getAttribute(name);
1131
- if (!o.width) o.width = this.#width || 360;
1132
- o.autoTheme = pageTheme() || (this.#dark ? this.#dark.matches ? "dark" : "light" : "dark");
1133
- return o;
1134
- }
1135
- /** Redraws the chart without replaying its entrance animation. */
1136
- refresh() {
1137
- if (this.#rendered) this.#render();
1138
- }
1139
- /** The chart as a standalone static SVG string. */
1140
- toSVG(extra = {}) {
1141
- return toStatic(render(this.chartType, { ...this.options, ...extra, animate: false }));
1142
- }
1143
- /** Saves the chart as an .svg file. */
1144
- download(name) {
1145
- const url = URL.createObjectURL(new Blob([this.toSVG()], { type: "image/svg+xml" }));
1146
- const a = Object.assign(document.createElement("a"), {
1147
- href: url,
1148
- download: `${(name || this.getAttribute("heading") || this.chartType).replace(/[^\w-]+/g, "-").toLowerCase()}.svg`
1149
- });
1150
- document.body.append(a);
1151
- a.click();
1152
- a.remove();
1153
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
1154
- }
1155
- #ro = null;
1156
- #dark = null;
1157
- #width = 0;
1158
- #rendered = false;
1159
- #queued = false;
1160
- #onScheme = () => {
1161
- const t = this.getAttribute("theme");
1162
- if (!t || t === "auto") this.refresh();
1163
- };
1164
- #render() {
1165
- if (!this.shadowRoot) return;
1166
- const w = Math.round(this.getBoundingClientRect?.().width || 0);
1167
- this.#width = w > 0 ? w : 360;
1168
- const o = this.options;
1169
- o.animate = !this.#rendered && !this.hasAttribute("static");
1170
- let svg;
1171
- try {
1172
- svg = render(this.chartType, o);
1173
- } catch (err) {
1174
- svg = `<p style="font:13px ${SANS};color:#d63f7c">${esc(err.message)}</p>`;
1175
- }
1176
- svg = svg.replace(/<title>[\s\S]*?<\/title>/, "");
1177
- this.shadowRoot.innerHTML = `<style>${HOST_CSS}</style>${svg}<div class="tip" role="status"></div>`;
1178
- this.#rendered = true;
1179
- this.dispatchEvent(new CustomEvent("bezel-render", { bubbles: true }));
1180
- }
1181
- #tip(e) {
1182
- const tip = this.shadowRoot.querySelector(".tip");
1183
- if (!tip || this.hasAttribute("no-tooltips")) return;
1184
- const target = e.target?.closest?.("[data-tip]");
1185
- if (!target) {
1186
- tip.classList.remove("on");
1187
- return;
1188
- }
1189
- tip.textContent = target.getAttribute("data-tip");
1190
- const box = this.getBoundingClientRect();
1191
- const half = tip.offsetWidth / 2;
1192
- tip.style.left = `${clamp(e.clientX - box.left, half, Math.max(half, box.width - half))}px`;
1193
- tip.style.top = `${e.clientY - box.top}px`;
1194
- tip.classList.add("on");
1195
- }
1469
+ heatmap.table = function(o) {
1470
+ var v = nums(o.values), start = startOf(o, v.length), f = dates(o.lang || "en-GB", FULL);
1471
+ return { head: ["Date", "Value"], rows: v.map(function(n, i) {
1472
+ return [f.format(new Date(start.getTime() + i * 864e5)), fmt(n, o)];
1473
+ }) };
1196
1474
  };
1197
1475
 
1476
+ // src/entries/heatmap.js
1477
+ register("heatmap", heatmap);
1478
+ define("heatmap");
1479
+
1198
1480
  // src/index.js
1199
- var version = "1.1.1";
1200
- var types = Object.keys(TYPES);
1201
- if (typeof customElements !== "undefined" && typeof HTMLElement !== "undefined") {
1202
- for (const type of types) {
1203
- const tag = `bezel-${type}`;
1204
- if (!customElements.get(tag)) {
1205
- customElements.define(tag, class extends BezelChart {
1206
- get chartType() {
1207
- return type;
1208
- }
1209
- });
1210
- }
1211
- }
1212
- if (!customElements.get("bezel-chart")) customElements.define("bezel-chart", class extends BezelChart {
1213
- });
1214
- }
1481
+ var version = "1.3.0";
1482
+ var types = NAMES.slice();
1215
1483
  export {
1216
1484
  BezelChart,
1217
1485
  render,