bezel-charts 1.1.0 → 1.2.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.
- package/README.md +51 -9
- package/dist/bezel-charts.iife.js +9 -9
- package/dist/bezel-charts.js +272 -44
- package/dist/bezel-charts.min.js +9 -9
- package/package.json +4 -4
- package/src/charts/bars.js +16 -5
- package/src/charts/donut.js +9 -1
- package/src/charts/gauge.js +8 -0
- package/src/charts/heatmap.js +22 -5
- package/src/charts/line.js +27 -5
- package/src/charts/progress.js +9 -0
- package/src/charts/ring.js +7 -0
- package/src/charts/scatter.js +20 -4
- package/src/charts/spark.js +5 -0
- package/src/core/util.js +57 -6
- package/src/element.js +114 -16
- package/src/index.js +1 -1
- package/src/render.js +14 -1
package/dist/bezel-charts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Bezel Charts v1.
|
|
1
|
+
/*! Bezel Charts v1.2.0 | MIT | github.com/ShazMoghaddam/bezel-charts */
|
|
2
2
|
|
|
3
3
|
// src/core/themes.js
|
|
4
4
|
var THEMES = {
|
|
@@ -180,24 +180,67 @@ function statOf(v, o, fallback) {
|
|
|
180
180
|
if (k === "last" && v.length > 1 && o.delta !== "none") delta = v[v.length - 1] - v[0];
|
|
181
181
|
return { val: map[k], delta };
|
|
182
182
|
}
|
|
183
|
-
function legend(names, pal, th, x0, y0, w) {
|
|
184
|
-
var x = x0, y = y0, s = "";
|
|
183
|
+
function legend(names, pal, th, x0, y0, w, key, kw) {
|
|
184
|
+
var x = x0, y = y0, s = "", off = kw || 13;
|
|
185
185
|
names.forEach(function(nm, k) {
|
|
186
|
-
var wd =
|
|
186
|
+
var wd = off + tw(nm, 12) + 16;
|
|
187
187
|
if (x > x0 && x + wd - 16 > x0 + w) {
|
|
188
188
|
x = x0;
|
|
189
189
|
y += 20;
|
|
190
190
|
}
|
|
191
|
-
s += '<circle cx="' + r2(x + 4) + '" cy="' + r2(y + 6) + '" r="4" fill="' + pal[k % pal.length] + '"/>' + text(x +
|
|
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
192
|
x += wd;
|
|
193
193
|
});
|
|
194
194
|
return { svg: s, height: y - y0 + 24 };
|
|
195
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
|
+
}
|
|
196
220
|
function axisWidth(ticks) {
|
|
197
221
|
return Math.max.apply(null, ticks.map(function(t) {
|
|
198
222
|
return tw(t, 10, true);
|
|
199
223
|
})) + 10;
|
|
200
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
|
+
}
|
|
201
244
|
|
|
202
245
|
// src/core/tile.js
|
|
203
246
|
function dims(o) {
|
|
@@ -295,6 +338,12 @@ function spark(o, th) {
|
|
|
295
338
|
return s;
|
|
296
339
|
}, { stat: st, summary: v.length ? "latest " + fmt(v[v.length - 1], o) : "no data" });
|
|
297
340
|
}
|
|
341
|
+
spark.table = function(o) {
|
|
342
|
+
var labels = list(o.labels);
|
|
343
|
+
return { head: ["Label", "Value"], rows: nums(o.values).map(function(n, i) {
|
|
344
|
+
return [labels[i] || "#" + (i + 1), fmt(n, o)];
|
|
345
|
+
}) };
|
|
346
|
+
};
|
|
298
347
|
|
|
299
348
|
// src/charts/line.js
|
|
300
349
|
function line(o, th) {
|
|
@@ -304,13 +353,19 @@ function line(o, th) {
|
|
|
304
353
|
})));
|
|
305
354
|
var notes = parseNotes(o, labels), multi = S.length > 1 || names.length > 0;
|
|
306
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
|
+
};
|
|
307
362
|
return tile(o, th, h, function(x0, y0, w, h2) {
|
|
308
363
|
if (!n) return emptyState(th, x0, y0, w, h2);
|
|
309
364
|
var s = "", top = y0;
|
|
310
365
|
if (multi) {
|
|
311
366
|
var lg = legend(S.map(function(_, k) {
|
|
312
367
|
return names[k] || "Series " + (k + 1);
|
|
313
|
-
}), pal, th, x0, top, w);
|
|
368
|
+
}), pal, th, x0, top, w, key, key && 24);
|
|
314
369
|
s += lg.svg;
|
|
315
370
|
top += lg.height;
|
|
316
371
|
}
|
|
@@ -369,9 +424,17 @@ function line(o, th) {
|
|
|
369
424
|
var g = newId("g"), last = P[P.length - 1];
|
|
370
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") + "/>";
|
|
371
426
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
+
}
|
|
375
438
|
if (bool(o.showValues)) v.forEach(function(y, i2) {
|
|
376
439
|
s += text(X(i2), Y(y) - 8, fmt(y, o, true), small(th, 9, 'text-anchor="middle"'));
|
|
377
440
|
});
|
|
@@ -388,6 +451,9 @@ function line(o, th) {
|
|
|
388
451
|
return s;
|
|
389
452
|
}, { stat: st, summary: S.length + " series over " + n + " points" });
|
|
390
453
|
}
|
|
454
|
+
line.table = function(o) {
|
|
455
|
+
return seriesTable(o);
|
|
456
|
+
};
|
|
391
457
|
|
|
392
458
|
// src/charts/bars.js
|
|
393
459
|
var ANGLE = 40;
|
|
@@ -416,8 +482,18 @@ function bars(o, th) {
|
|
|
416
482
|
var nameOf = function(j) {
|
|
417
483
|
return names[j] || "Series " + (j + 1);
|
|
418
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 + ")";
|
|
491
|
+
}
|
|
419
492
|
var colourOf = function(j, i) {
|
|
420
|
-
return grouped ? pal[j % pal.length] : many ? pal[i % pal.length] : pal[0];
|
|
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) + '"/>';
|
|
421
497
|
};
|
|
422
498
|
var opac = function(val) {
|
|
423
499
|
return grouped || many || o.highlight === "none" || val === top ? 1 : 0.5;
|
|
@@ -434,7 +510,7 @@ function bars(o, th) {
|
|
|
434
510
|
var d0 = dims(o);
|
|
435
511
|
var legendH = grouped || names.length ? legend(S.map(function(_, j) {
|
|
436
512
|
return nameOf(j);
|
|
437
|
-
}), pal, th, 0, 0, d0.w).height : 0;
|
|
513
|
+
}), pal, th, 0, 0, d0.w, key, key && 15).height : 0;
|
|
438
514
|
var rows = stacked ? 1 : k, thin = grouped && !stacked;
|
|
439
515
|
var subH = thin ? 10 : 14, subGap = 3;
|
|
440
516
|
var rowH = horiz ? rows * subH + (rows - 1) * subGap + (thin ? 14 : 16) : 0;
|
|
@@ -442,11 +518,11 @@ function bars(o, th) {
|
|
|
442
518
|
var best = v0.indexOf(Math.max.apply(null, v0.length ? v0 : [0]));
|
|
443
519
|
return tile(o, th, h, function(x0, y0, w, h2) {
|
|
444
520
|
if (!n) return emptyState(th, x0, y0, w, h2);
|
|
445
|
-
var s = "", top0 = y0;
|
|
521
|
+
var s = defs ? "<defs>" + defs + "</defs>" : "", top0 = y0;
|
|
446
522
|
if (legendH) {
|
|
447
523
|
s += legend(S.map(function(_, j) {
|
|
448
524
|
return nameOf(j);
|
|
449
|
-
}), pal, th, x0, y0, w).svg;
|
|
525
|
+
}), pal, th, x0, y0, w, key, key && 15).svg;
|
|
450
526
|
top0 += legendH;
|
|
451
527
|
}
|
|
452
528
|
var lo = Math.min(0, Math.min.apply(null, scale)), hi = Math.max(0, top);
|
|
@@ -597,6 +673,9 @@ function bars(o, th) {
|
|
|
597
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)
|
|
598
674
|
});
|
|
599
675
|
}
|
|
676
|
+
bars.table = function(o) {
|
|
677
|
+
return seriesTable(o, series(o.values).length > 1 && bool(o.stacked));
|
|
678
|
+
};
|
|
600
679
|
|
|
601
680
|
// src/charts/ring.js
|
|
602
681
|
function ring(o, th) {
|
|
@@ -636,6 +715,12 @@ function ring(o, th) {
|
|
|
636
715
|
return (labels[i] ? labels[i] + " " : "") + show(n);
|
|
637
716
|
}).join(", ") });
|
|
638
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
|
+
};
|
|
639
724
|
|
|
640
725
|
// src/charts/donut.js
|
|
641
726
|
function donut(o, th) {
|
|
@@ -688,12 +773,20 @@ function donut(o, th) {
|
|
|
688
773
|
v.forEach(function(n, i) {
|
|
689
774
|
var len = n / total * 100;
|
|
690
775
|
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) + "%)") + '"/>';
|
|
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) + "%)") + '"/>';
|
|
692
777
|
start += len;
|
|
693
778
|
});
|
|
694
779
|
return s;
|
|
695
780
|
}, { stat: st, summary: "total " + fmt(total, o) + " across " + v.length + " parts" });
|
|
696
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
|
+
};
|
|
697
790
|
|
|
698
791
|
// src/charts/gauge.js
|
|
699
792
|
function gauge(o, th) {
|
|
@@ -740,6 +833,12 @@ function gauge(o, th) {
|
|
|
740
833
|
return s;
|
|
741
834
|
}, { summary: isFinite(val) ? show(val) + " on a scale of " + fmt(mn, o) + " to " + fmt(mx, o) : "no data" });
|
|
742
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
|
+
};
|
|
743
842
|
|
|
744
843
|
// src/charts/progress.js
|
|
745
844
|
function progress(o, th) {
|
|
@@ -802,16 +901,36 @@ function progress(o, th) {
|
|
|
802
901
|
return s;
|
|
803
902
|
}, { stat: st, summary: single ? show(v[0]) + (auto ? "" : " of " + fmt(max, o)) : fmt(total, o) + " of " + fmt(max, o) });
|
|
804
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
|
+
};
|
|
805
916
|
|
|
806
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
|
+
}
|
|
807
931
|
function heatmap(o, th) {
|
|
808
932
|
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
|
-
}
|
|
933
|
+
var start = startOf(o, v.length);
|
|
815
934
|
var offset = (start.getUTCDay() + 6) % 7, cols = Math.max(1, Math.ceil((offset + v.length) / 7));
|
|
816
935
|
var d0 = dims(o), leftW = 28, maxCols = Math.max(1, Math.floor((d0.w - leftW) / 10));
|
|
817
936
|
if (cols > maxCols) {
|
|
@@ -825,13 +944,9 @@ function heatmap(o, th) {
|
|
|
825
944
|
var h = 16 + 7 * pitch + 26;
|
|
826
945
|
var max = v.length ? Math.max.apply(null, v) : 0;
|
|
827
946
|
var dfm = function(opt) {
|
|
828
|
-
|
|
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
|
-
}
|
|
947
|
+
return dates(lang, opt);
|
|
833
948
|
};
|
|
834
|
-
var monthF = dfm({ month: "short" }), dayF = dfm({ weekday: "short" }), fullF = dfm(
|
|
949
|
+
var monthF = dfm({ month: "short" }), dayF = dfm({ weekday: "short" }), fullF = dfm(FULL);
|
|
835
950
|
var st = statOf(v, o, "");
|
|
836
951
|
return tile(o, th, h, function(x0, y0, w, h2) {
|
|
837
952
|
if (!v.length) return emptyState(th, x0, y0, w, h2);
|
|
@@ -861,6 +976,12 @@ function heatmap(o, th) {
|
|
|
861
976
|
return s;
|
|
862
977
|
}, { stat: st, summary: fmt(sum(v), o) + " across " + v.length + " days" });
|
|
863
978
|
}
|
|
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
|
+
}) };
|
|
984
|
+
};
|
|
864
985
|
|
|
865
986
|
// src/charts/scatter.js
|
|
866
987
|
function fit(xs, ys) {
|
|
@@ -902,13 +1023,21 @@ function scatter(o, th) {
|
|
|
902
1023
|
};
|
|
903
1024
|
var xo = { decimals: o.decimals, compact: o.compact, lang: o.lang };
|
|
904
1025
|
var notes = parseNotes(o, labels);
|
|
1026
|
+
var markOf = styleOf(o.markers, SHAPES, "circle"), bg = o.background || th.tile;
|
|
1027
|
+
var shapeOf = function(j) {
|
|
1028
|
+
var m2 = markOf ? markOf(j) : "circle";
|
|
1029
|
+
return m2 === "none" ? "circle" : m2;
|
|
1030
|
+
};
|
|
1031
|
+
var key = markOf && function(j, x, y) {
|
|
1032
|
+
return marker(shapeOf(j), x + 4, y + 6, 4, ' fill="' + pal[j % pal.length] + '"');
|
|
1033
|
+
};
|
|
905
1034
|
return tile(o, th, h, function(x0, y0, w, h2) {
|
|
906
1035
|
if (!count) return emptyState(th, x0, y0, w, h2);
|
|
907
1036
|
var s = "", top = y0;
|
|
908
1037
|
if (multi) {
|
|
909
1038
|
var lg = legend(groups.map(function(_, j) {
|
|
910
1039
|
return nameOf(j);
|
|
911
|
-
}), pal, th, x0, top, w);
|
|
1040
|
+
}), pal, th, x0, top, w, key);
|
|
912
1041
|
s += lg.svg;
|
|
913
1042
|
top += lg.height;
|
|
914
1043
|
}
|
|
@@ -975,7 +1104,7 @@ function scatter(o, th) {
|
|
|
975
1104
|
gr.xs.forEach(function(xv, i) {
|
|
976
1105
|
var cx = PX(xv), cy2 = PY(gr.ys[i]);
|
|
977
1106
|
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 +=
|
|
1107
|
+
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"' : ""));
|
|
979
1108
|
s += hit(cx - 8, cy2 - 8, 16, 16, tip);
|
|
980
1109
|
idx++;
|
|
981
1110
|
});
|
|
@@ -994,21 +1123,39 @@ function scatter(o, th) {
|
|
|
994
1123
|
return s;
|
|
995
1124
|
}, { summary: count + " points" + (multi ? " in " + groups.length + " groups" : "") });
|
|
996
1125
|
}
|
|
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 };
|
|
1136
|
+
};
|
|
997
1137
|
|
|
998
1138
|
// src/render.js
|
|
999
1139
|
var TYPES = { spark, line, bars, scatter, ring, donut, gauge, progress, heatmap };
|
|
1000
1140
|
var camel = (k) => k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
1001
|
-
function
|
|
1141
|
+
function prepare(type, options) {
|
|
1002
1142
|
if (!TYPES[type]) {
|
|
1003
1143
|
throw new Error(`Bezel Charts: unknown chart type "${type}". Choose one of: ${Object.keys(TYPES).join(", ")}.`);
|
|
1004
1144
|
}
|
|
1005
1145
|
const o = {};
|
|
1006
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);
|
|
1007
1151
|
o.animate = bool(o.animate);
|
|
1008
1152
|
o.frame = o.frame === "none" ? "none" : "bezel";
|
|
1009
1153
|
const mode = o.theme === "light" || o.theme === "dark" ? o.theme : o.autoTheme || "dark";
|
|
1010
1154
|
return TYPES[type](o, THEMES[mode]);
|
|
1011
1155
|
}
|
|
1156
|
+
function table(type, options = {}) {
|
|
1157
|
+
return TYPES[type].table(prepare(type, options));
|
|
1158
|
+
}
|
|
1012
1159
|
function toStatic(svg) {
|
|
1013
1160
|
return svg.replace(/<rect class="bzc-hit"[^>]*\/>/g, "").replace(/<circle[^>]* class="bzc-hit"[^>]*\/>/g, "").replace(/ data-tip="[^"]*"/g, "");
|
|
1014
1161
|
}
|
|
@@ -1060,9 +1207,13 @@ var ATTRIBUTES = [
|
|
|
1060
1207
|
"static",
|
|
1061
1208
|
"no-tooltips",
|
|
1062
1209
|
"stacked",
|
|
1063
|
-
"angle-labels"
|
|
1210
|
+
"angle-labels",
|
|
1211
|
+
"dash",
|
|
1212
|
+
"markers",
|
|
1213
|
+
"patterns"
|
|
1064
1214
|
];
|
|
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}`;
|
|
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 };
|
|
1066
1217
|
var darkQuery = () => typeof matchMedia === "function" ? matchMedia("(prefers-color-scheme: dark)") : null;
|
|
1067
1218
|
var pageTheme = () => {
|
|
1068
1219
|
const t = typeof document !== "undefined" && document.documentElement?.getAttribute?.("data-theme");
|
|
@@ -1093,10 +1244,13 @@ var BezelChart = class extends Base {
|
|
|
1093
1244
|
}
|
|
1094
1245
|
connectedCallback() {
|
|
1095
1246
|
if (!this.shadowRoot) {
|
|
1096
|
-
this.attachShadow({ mode: "open" })
|
|
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;
|
|
1097
1249
|
this.shadowRoot.addEventListener("pointermove", (e) => this.#tip(e));
|
|
1098
1250
|
this.shadowRoot.addEventListener("pointerdown", (e) => this.#tip(e));
|
|
1099
|
-
this.addEventListener("pointerleave", () => this.
|
|
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());
|
|
1100
1254
|
}
|
|
1101
1255
|
live.add(this);
|
|
1102
1256
|
this.#dark = darkQuery();
|
|
@@ -1153,6 +1307,11 @@ var BezelChart = class extends Base {
|
|
|
1153
1307
|
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
1154
1308
|
}
|
|
1155
1309
|
#ro = null;
|
|
1310
|
+
#box = null;
|
|
1311
|
+
#tipEl = null;
|
|
1312
|
+
#live = null;
|
|
1313
|
+
#table = null;
|
|
1314
|
+
#at = -1;
|
|
1156
1315
|
#dark = null;
|
|
1157
1316
|
#width = 0;
|
|
1158
1317
|
#rendered = false;
|
|
@@ -1167,36 +1326,105 @@ var BezelChart = class extends Base {
|
|
|
1167
1326
|
this.#width = w > 0 ? w : 360;
|
|
1168
1327
|
const o = this.options;
|
|
1169
1328
|
o.animate = !this.#rendered && !this.hasAttribute("static");
|
|
1170
|
-
let svg;
|
|
1329
|
+
let svg, data = null;
|
|
1171
1330
|
try {
|
|
1172
1331
|
svg = render(this.chartType, o);
|
|
1332
|
+
data = table(this.chartType, o);
|
|
1173
1333
|
} catch (err) {
|
|
1174
1334
|
svg = `<p style="font:13px ${SANS};color:#d63f7c">${esc(err.message)}</p>`;
|
|
1175
1335
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
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;
|
|
1178
1360
|
this.#rendered = true;
|
|
1179
1361
|
this.dispatchEvent(new CustomEvent("bezel-render", { bubbles: true }));
|
|
1180
1362
|
}
|
|
1181
1363
|
#tip(e) {
|
|
1182
|
-
|
|
1183
|
-
if (!tip || this.hasAttribute("no-tooltips")) return;
|
|
1364
|
+
if (this.hasAttribute("no-tooltips")) return;
|
|
1184
1365
|
const target = e.target?.closest?.("[data-tip]");
|
|
1185
1366
|
if (!target) {
|
|
1186
|
-
|
|
1367
|
+
this.#tipEl.classList.remove("on");
|
|
1187
1368
|
return;
|
|
1188
1369
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
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 };
|
|
1191
1377
|
const half = tip.offsetWidth / 2;
|
|
1192
|
-
tip.style.left = `${clamp(
|
|
1193
|
-
tip.style.top = `${
|
|
1378
|
+
tip.style.left = `${clamp(x - box.left, half, Math.max(half, box.width - half))}px`;
|
|
1379
|
+
tip.style.top = `${y - box.top}px`;
|
|
1194
1380
|
tip.classList.add("on");
|
|
1195
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;
|
|
1398
|
+
}
|
|
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
|
+
}
|
|
1196
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>`;
|
|
1424
|
+
}
|
|
1197
1425
|
|
|
1198
1426
|
// src/index.js
|
|
1199
|
-
var version = "1.
|
|
1427
|
+
var version = "1.2.0";
|
|
1200
1428
|
var types = Object.keys(TYPES);
|
|
1201
1429
|
if (typeof customElements !== "undefined" && typeof HTMLElement !== "undefined") {
|
|
1202
1430
|
for (const type of types) {
|