bezel-charts 1.4.0 → 1.5.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,4 +1,4 @@
1
- /*! Bezel Charts v1.4.0 | MIT | github.com/ShazMoghaddam/bezel-charts */
1
+ /*! Bezel Charts v1.5.0 | MIT | github.com/ShazMoghaddam/bezel-charts */
2
2
 
3
3
  // src/core/util.js
4
4
  var SANS = "ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif";
@@ -268,7 +268,7 @@ var THEMES = {
268
268
  };
269
269
 
270
270
  // src/render.js
271
- var NAMES = ["spark", "line", "area", "bars", "scatter", "ring", "donut", "gauge", "progress", "heatmap"];
271
+ var NAMES = ["spark", "line", "area", "bars", "histogram", "scatter", "ring", "donut", "gauge", "progress", "heatmap"];
272
272
  var TYPES = {};
273
273
  function register(type, fn) {
274
274
  TYPES[type] = fn;
@@ -346,7 +346,11 @@ var ATTRIBUTES = [
346
346
  "angle-labels",
347
347
  "dash",
348
348
  "markers",
349
- "patterns"
349
+ "patterns",
350
+ "bins",
351
+ "bin-width",
352
+ "mark",
353
+ "percent"
350
354
  ];
351
355
  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
356
  var STEPS = { ArrowLeft: -1, ArrowUp: -1, ArrowRight: 1, ArrowDown: 1 };
@@ -635,7 +639,7 @@ function axisWidth(ticks) {
635
639
  return tw(t, 10, true);
636
640
  })) + 10;
637
641
  }
638
- function seriesTable(o, total) {
642
+ function seriesTable(o, total2) {
639
643
  var S = series(o.values), names = list(o.series), labels = list(o.labels), rows = [];
640
644
  var n = Math.max.apply(null, [0].concat(S.map(function(s) {
641
645
  return s.length;
@@ -646,13 +650,13 @@ function seriesTable(o, total) {
646
650
  row.push(i < s.length ? fmt(s[i], o) : "");
647
651
  t += i < s.length ? s[i] : 0;
648
652
  });
649
- if (total) row.push(fmt(t, o));
653
+ if (total2) row.push(fmt(t, o));
650
654
  rows.push(row);
651
655
  }
652
656
  var head = S.length > 1 || names.length ? S.map(function(_, k) {
653
657
  return names[k] || "Series " + (k + 1);
654
658
  }) : ["Value"];
655
- return { head: ["Label"].concat(head, total ? ["Total"] : []), rows };
659
+ return { head: ["Label"].concat(head, total2 ? ["Total"] : []), rows };
656
660
  }
657
661
 
658
662
  // src/core/xy.js
@@ -1164,6 +1168,200 @@ bars.table = function(o) {
1164
1168
  register("bars", bars);
1165
1169
  define("bars");
1166
1170
 
1171
+ // src/charts/histogram.js
1172
+ function histogram(o, th) {
1173
+ var b = binned(o), names = list(o.series), pal = palette(o, th), h = num(o.height, 190);
1174
+ var S = b ? b.S : [], k = S.length, multi = k > 1 || names.length > 0, pct = bool(o.percent);
1175
+ var nameOf = function(j) {
1176
+ return names[j] || "Series " + (j + 1);
1177
+ };
1178
+ var eo = Object.assign({}, o, { decimals: has(o.decimals) ? o.decimals : b ? b.dp : 0 });
1179
+ var range = function(i2) {
1180
+ return fmt(b.edge(i2), eo) + " to " + fmt(b.edge(i2 + 1), eo);
1181
+ };
1182
+ var share = function(j, i2) {
1183
+ return Math.round(b.counts[j][i2] / S[j].length * 100);
1184
+ };
1185
+ var height = function(j, i2) {
1186
+ return pct ? b.counts[j][i2] / S[j].length * 100 : b.counts[j][i2];
1187
+ };
1188
+ var marks = list(o.mark).filter(function(m) {
1189
+ return m === "mean" || m === "median";
1190
+ });
1191
+ var patOf = k > 1 ? styleOf(o.patterns, PATTERNS, "solid") : null, defs = "", fills = [];
1192
+ for (var q = 0; patOf && q < k; q++) {
1193
+ if (patOf(q) === "solid") continue;
1194
+ var pid = newId("g");
1195
+ defs += pattern(pid, patOf(q), pal[q % pal.length], o.background || th.tile);
1196
+ fills[q] = "url(#" + pid + ")";
1197
+ }
1198
+ var key = patOf && function(j, x, y) {
1199
+ return '<rect x="' + r2(x) + '" y="' + r2(y + 1) + '" width="10" height="10" rx="2" fill="' + (fills[j] || pal[j % pal.length]) + '"/>';
1200
+ };
1201
+ var peak = 0, n = 0;
1202
+ if (b) {
1203
+ for (var i = 0; i < b.count; i++) if (total(b, i) > total(b, peak)) peak = i;
1204
+ S.forEach(function(s) {
1205
+ n += s.length;
1206
+ });
1207
+ }
1208
+ return tile(o, th, h, function(x0, y0, w, h2) {
1209
+ if (!b) return emptyState(th, x0, y0, w, h2);
1210
+ var s = defs ? "<defs>" + defs + "</defs>" : "", top = y0;
1211
+ if (multi) {
1212
+ var lg = legend(S.map(function(_, j) {
1213
+ return nameOf(j);
1214
+ }), pal, th, x0, top, w, key, key && 15);
1215
+ s += lg.svg;
1216
+ top += lg.height;
1217
+ }
1218
+ var most = 0;
1219
+ S.forEach(function(_, j) {
1220
+ for (var i2 = 0; i2 < b.count; i2++) most = Math.max(most, height(j, i2));
1221
+ });
1222
+ var sc = niceScale(0, most || 1, 4);
1223
+ var tl = sc.ticks.map(function(t) {
1224
+ return fmt(t, { lang: o.lang }, true) + (pct ? "%" : "");
1225
+ });
1226
+ var axisW = Math.max.apply(null, tl.map(function(t) {
1227
+ return tw(t, 10, true);
1228
+ })) + 10;
1229
+ var px = x0 + axisW, pw = w - axisW, ph = h2 - (top - y0) - 20, cw = pw / b.count;
1230
+ var X = function(i2) {
1231
+ return px + i2 * cw;
1232
+ };
1233
+ var Y = function(v) {
1234
+ return top + ph - (v - sc.lo) / (sc.hi - sc.lo) * ph;
1235
+ };
1236
+ sc.ticks.forEach(function(t, i2) {
1237
+ var y = Y(t);
1238
+ 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"'));
1239
+ });
1240
+ var el = [];
1241
+ for (var e = 0; e <= b.count; e++) el.push(fmt(b.edge(e), eo, true));
1242
+ var every = Math.max(1, Math.ceil((Math.max.apply(null, el.map(function(l) {
1243
+ return tw(l, 10, true);
1244
+ })) * 1.5 + 8) / cw));
1245
+ for (e = 0; e <= b.count; e += every) {
1246
+ s += text(X(e), top + ph + 15, el[e], small(th, 10, 'text-anchor="' + (e === 0 ? "start" : e === b.count ? "end" : "middle") + '"'));
1247
+ }
1248
+ S.forEach(function(_, j) {
1249
+ for (var i2 = 0; i2 < b.count; i2++) {
1250
+ var v = height(j, i2);
1251
+ if (!v) continue;
1252
+ var y = Y(v);
1253
+ s += '<rect x="' + r2(X(i2) + 0.5) + '" y="' + r2(y) + '" width="' + r2(Math.max(1, cw - 1)) + '" height="' + r2(top + ph - y) + '" fill="' + (fills[j] || pal[j % pal.length]) + '"' + (multi ? ' fill-opacity=".55"' : "") + anim(o, "bzc-g", Math.min(0.6, i2 * 0.03)) + "/>";
1254
+ }
1255
+ });
1256
+ var lo = b.edge(0), span = b.edge(b.count) - lo, lines = [];
1257
+ S.forEach(function(ser, j) {
1258
+ marks.forEach(function(m) {
1259
+ var v = m === "mean" ? mean(ser) : median(ser);
1260
+ lines.push({ x: px + (v - lo) / span * pw, c: multi ? pal[j % pal.length] : th.fg, t: (multi ? nameOf(j) + " " : "") + m + " " + fmt(v, o) });
1261
+ });
1262
+ });
1263
+ if (lines.length) {
1264
+ var xs = lines.map(function(l) {
1265
+ return l.x;
1266
+ }), left = (Math.min.apply(null, xs) + Math.max.apply(null, xs)) / 2 > px + pw / 2;
1267
+ var lx = left ? Math.min.apply(null, xs) - 5 : Math.max.apply(null, xs) + 5;
1268
+ lines.forEach(function(l, r) {
1269
+ s += '<line x1="' + r2(l.x) + '" x2="' + r2(l.x) + '" y1="' + r2(top) + '" y2="' + r2(top + ph) + '" stroke="' + l.c + '" stroke-width="1.5" stroke-dasharray="4 3"/>' + text(lx, top + 9 + r * 12, l.t, small(th, 10, 'text-anchor="' + (left ? "end" : "start") + '"', multi ? l.c : th.fg));
1270
+ });
1271
+ }
1272
+ for (i = 0; i < b.count; i++) {
1273
+ var tip = range(i);
1274
+ S.forEach(function(_, j) {
1275
+ tip += "\n" + (multi ? nameOf(j) + ": " : "") + b.counts[j][i] + " (" + share(j, i) + "%)";
1276
+ });
1277
+ s += hit(X(i), top, cw, ph, tip);
1278
+ }
1279
+ return s;
1280
+ }, {
1281
+ summary: b ? (multi ? k + " series, " : "") + n + " values in " + b.count + " ranges from " + fmt(b.edge(0), eo) + " to " + fmt(b.edge(b.count), eo) + ", most in " + range(peak) : "no data"
1282
+ });
1283
+ }
1284
+ histogram.table = function(o) {
1285
+ var b = binned(o), names = list(o.series);
1286
+ if (!b) return { head: ["Range", "Count"], rows: [] };
1287
+ var multi = b.S.length > 1 || names.length > 0, eo = Object.assign({}, o, { decimals: has(o.decimals) ? o.decimals : b.dp }), rows = [];
1288
+ for (var i = 0; i < b.count; i++) {
1289
+ var row = [fmt(b.edge(i), eo) + " to " + fmt(b.edge(i + 1), eo)];
1290
+ if (multi) b.S.forEach(function(_, j) {
1291
+ row.push(String(b.counts[j][i]));
1292
+ });
1293
+ else row.push(String(b.counts[0][i]), Math.round(b.counts[0][i] / b.S[0].length * 100) + "%");
1294
+ rows.push(row);
1295
+ }
1296
+ return { head: ["Range"].concat(multi ? b.S.map(function(_, j) {
1297
+ return names[j] || "Series " + (j + 1);
1298
+ }) : ["Count", "Share"]), rows };
1299
+ };
1300
+ function niceWidth(raw) {
1301
+ var e = Math.floor(Math.log10(raw)), p = Number("1e" + e), f = raw / p;
1302
+ var m = f < 1.4142 ? 1 : f < 2.2361 ? 2 : f < 3.5355 ? 2.5 : f < 7.0711 ? 5 : 10;
1303
+ return { w: m * p, dp: Math.max(0, m === 10 ? -e - 1 : -e) + (m === 2.5 ? 1 : 0) };
1304
+ }
1305
+ function binned(o) {
1306
+ var S = series(o.values), all = [].concat.apply([], S);
1307
+ if (!all.length) return null;
1308
+ var lo = Math.min.apply(null, all), hi = Math.max.apply(null, all), w = num(o.binWidth, 0), dp, nice;
1309
+ if (w > 0 && (hi - lo) / w <= 200) {
1310
+ var str = String(o.binWidth), dot = str.indexOf(".");
1311
+ dp = dot < 0 ? 0 : Math.min(6, str.length - dot - 1);
1312
+ } else {
1313
+ var k = num(o.bins, 0) > 0 ? Math.round(num(o.bins, 0)) : Math.ceil(Math.log2(all.length) + 1);
1314
+ k = Math.min(Math.max(k, 1), 50);
1315
+ nice = niceWidth(hi > lo ? (hi - lo) / k : Math.abs(lo) || 1);
1316
+ w = nice.w;
1317
+ dp = nice.dp;
1318
+ }
1319
+ var s0 = Math.floor(lo / w);
1320
+ var edge = function(i) {
1321
+ return Number(((s0 + i) * w).toFixed(dp + 3));
1322
+ };
1323
+ while (edge(0) > lo) s0--;
1324
+ var count = Math.max(1, Math.ceil((hi - edge(0)) / w));
1325
+ while (count > 1 && edge(count - 1) >= hi) count--;
1326
+ while (edge(count) < hi) count++;
1327
+ var counts = S.map(function(ser) {
1328
+ var c = [];
1329
+ for (var i = 0; i < count; i++) c.push(0);
1330
+ ser.forEach(function(x) {
1331
+ var i2 = Math.min(count - 1, Math.max(0, Math.floor((x - edge(0)) / w)));
1332
+ while (i2 > 0 && x < edge(i2)) i2--;
1333
+ while (i2 < count - 1 && x >= edge(i2 + 1)) i2++;
1334
+ c[i2]++;
1335
+ });
1336
+ return c;
1337
+ });
1338
+ return { S, edge, count, counts, dp };
1339
+ }
1340
+ function total(b, i) {
1341
+ var t = 0;
1342
+ b.counts.forEach(function(c) {
1343
+ t += c[i];
1344
+ });
1345
+ return t;
1346
+ }
1347
+ function mean(v) {
1348
+ var t = 0;
1349
+ v.forEach(function(x) {
1350
+ t += x;
1351
+ });
1352
+ return t / v.length;
1353
+ }
1354
+ function median(v) {
1355
+ var s = v.slice().sort(function(a, b) {
1356
+ return a - b;
1357
+ }), m = Math.floor(s.length / 2);
1358
+ return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
1359
+ }
1360
+
1361
+ // src/entries/histogram.js
1362
+ register("histogram", histogram);
1363
+ define("histogram");
1364
+
1167
1365
  // src/charts/scatter.js
1168
1366
  function fit(xs, ys) {
1169
1367
  var n = Math.min(xs.length, ys.length);
@@ -1395,43 +1593,43 @@ function donut(o, th) {
1395
1593
  })));
1396
1594
  labels.push("Other");
1397
1595
  }
1398
- var total = sum(v), h = num(o.height, 150);
1596
+ var total2 = sum(v), h = num(o.height, 150);
1399
1597
  var st = statOf(v, o, "");
1400
1598
  return tile(o, th, h, function(x0, y0, w, h2) {
1401
- if (!total) return emptyState(th, x0, y0, w, h2);
1599
+ if (!total2) return emptyState(th, x0, y0, w, h2);
1402
1600
  var s = "", size = Math.min(h2, w * 0.5), cx = x0 + size / 2, cy = y0 + h2 / 2;
1403
1601
  var sw = size * 0.16, r = (size - sw) / 2, start = 0, gap = v.filter(Boolean).length > 1 ? 0.9 : 0;
1404
1602
  v.forEach(function(n, i) {
1405
- var len = n / total * 100;
1603
+ var len = n / total2 * 100;
1406
1604
  if (len <= 0) return;
1407
1605
  var seg = Math.max(0.01, len - gap);
1408
1606
  s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="' + pal[i % pal.length] + '" stroke-width="' + r2(sw) + '" pathLength="100" stroke-dasharray="' + r2(seg) + " " + r2(100 - seg) + '" stroke-dashoffset="' + r2(-start) + '" transform="rotate(-90 ' + r2(cx) + " " + r2(cy) + ')"' + anim(o, "bzc-a", i * 0.08) + "/>";
1409
1607
  start += len;
1410
1608
  });
1411
- s += text(cx, cy + 2, fmt(total, o, true), 'text-anchor="middle" fill="' + th.fg + '" font-family="' + SANS + '" font-size="' + r2(size * 0.17) + '" font-weight="800"');
1609
+ s += text(cx, cy + 2, fmt(total2, o, true), 'text-anchor="middle" fill="' + th.fg + '" font-family="' + SANS + '" font-size="' + r2(size * 0.17) + '" font-weight="800"');
1412
1610
  s += text(cx, cy + 2 + 14, (has(o.centerLabel) ? o.centerLabel : "Total").toUpperCase(), small(th, 9, 'text-anchor="middle" letter-spacing="1"'));
1413
1611
  var lx = x0 + size + 22, rowH = Math.min(24, h2 / v.length), ly = cy - rowH * v.length / 2;
1414
1612
  v.forEach(function(n, i) {
1415
- var y = ly + i * rowH + rowH / 2, pct = Math.round(n / total * 100);
1613
+ var y = ly + i * rowH + rowH / 2, pct = Math.round(n / total2 * 100);
1416
1614
  var tip = (labels[i] || "#" + (i + 1)) + ": " + fmt(n, o) + " (" + pct + "%)";
1417
1615
  s += '<circle cx="' + r2(lx + 4) + '" cy="' + r2(y) + '" r="4" fill="' + pal[i % pal.length] + '"/>' + text(lx + 14, y + 4, trunc(labels[i] || "#" + (i + 1), Math.floor((x0 + w - lx - 50) / 6.7)), 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"') + text(x0 + w, y + 4, pct + "%", 'text-anchor="end" fill="' + th.muted + '" font-family="' + MONO + '" font-size="11" font-weight="600"') + hit(lx, y - rowH / 2, x0 + w - lx, rowH, tip);
1418
1616
  });
1419
1617
  start = 0;
1420
1618
  v.forEach(function(n, i) {
1421
- var len = n / total * 100;
1619
+ var len = n / total2 * 100;
1422
1620
  if (len <= 0) return;
1423
1621
  s += '<circle cx="' + r2(cx) + '" cy="' + r2(cy) + '" r="' + r2(r) + '" fill="none" stroke="#000" stroke-opacity="0" stroke-width="' + r2(sw) + '" pathLength="100" stroke-dasharray="' + r2(len) + " " + r2(100 - len) + '" stroke-dashoffset="' + r2(-start) + '" transform="rotate(-90 ' + r2(cx) + " " + r2(cy) + ')" class="bzc-hit" data-skip="" data-tip="' + esc((labels[i] || "#" + (i + 1)) + ": " + fmt(n, o) + " (" + Math.round(len) + "%)") + '"/>';
1424
1622
  start += len;
1425
1623
  });
1426
1624
  return s;
1427
- }, { stat: st, summary: "total " + fmt(total, o) + " across " + v.length + " parts" });
1625
+ }, { stat: st, summary: "total " + fmt(total2, o) + " across " + v.length + " parts" });
1428
1626
  }
1429
1627
  donut.table = function(o) {
1430
1628
  var v = nums(o.values).map(function(n) {
1431
1629
  return Math.max(0, n);
1432
- }), labels = list(o.labels), total = sum(v);
1630
+ }), labels = list(o.labels), total2 = sum(v);
1433
1631
  return { head: ["Label", "Value", "Share"], rows: v.map(function(n, i) {
1434
- return [labels[i] || "#" + (i + 1), fmt(n, o), (total ? Math.round(n / total * 100) : 0) + "%"];
1632
+ return [labels[i] || "#" + (i + 1), fmt(n, o), (total2 ? Math.round(n / total2 * 100) : 0) + "%"];
1435
1633
  }) };
1436
1634
  };
1437
1635
 
@@ -1500,8 +1698,8 @@ function progress(o, th) {
1500
1698
  var v = nums(o.values).map(function(n) {
1501
1699
  return Math.max(0, n);
1502
1700
  }), labels = list(o.labels), pal = palette(o, th);
1503
- var single = v.length === 1, total = sum(v);
1504
- var max = has(o.max) ? num(o.max, 100) : single ? 100 : total;
1701
+ var single = v.length === 1, total2 = sum(v);
1702
+ var max = has(o.max) ? num(o.max, 100) : single ? 100 : total2;
1505
1703
  if (!(max > 0)) max = 1;
1506
1704
  var auto = !has(o.prefix) && !has(o.suffix) && max === 100 && single;
1507
1705
  var show = function(n) {
@@ -1526,7 +1724,7 @@ function progress(o, th) {
1526
1724
  return tile(o, th, h, function(x0, y0, w, h2) {
1527
1725
  if (!v.length) return emptyState(th, x0, y0, w, h2);
1528
1726
  var s = "", by = y0 + 22, cid = newId("c");
1529
- var head = single ? show(v[0]) + (auto ? "" : " of " + fmt(max, o)) : fmt(total, o) + " of " + fmt(max, o);
1727
+ var head = single ? show(v[0]) + (auto ? "" : " of " + fmt(max, o)) : fmt(total2, o) + " of " + fmt(max, o);
1530
1728
  if (single && labels[0]) s += text(x0, y0 + 11, labels[0], 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"');
1531
1729
  s += text(x0 + w, y0 + 11, head, 'text-anchor="end" fill="' + th.fg + '" font-family="' + MONO + '" font-size="11" font-weight="600"');
1532
1730
  s += '<rect x="' + x0 + '" y="' + by + '" width="' + w + '" height="14" rx="7" fill="' + th.grid + '" fill-opacity=".7"/>';
@@ -1554,7 +1752,7 @@ function progress(o, th) {
1554
1752
  s += '<circle cx="' + r2(lx + 4) + '" cy="' + r2(ly) + '" r="4" fill="' + pal[i % pal.length] + '"/><text x="' + r2(lx + 14) + '" y="' + r2(ly + 4) + '" fill="' + th.fg + '" font-family="' + SANS + '" font-size="12">' + esc(it.l) + '<tspan dx="6" fill="' + th.muted + '" font-family="' + MONO + '" font-size="11">' + esc(fmt(v[i], o)) + "</tspan></text>";
1555
1753
  });
1556
1754
  return s;
1557
- }, { stat: st, summary: single ? show(v[0]) + (auto ? "" : " of " + fmt(max, o)) : fmt(total, o) + " of " + fmt(max, o) });
1755
+ }, { stat: st, summary: single ? show(v[0]) + (auto ? "" : " of " + fmt(max, o)) : fmt(total2, o) + " of " + fmt(max, o) });
1558
1756
  }
1559
1757
  progress.table = function(o) {
1560
1758
  var v = nums(o.values).map(function(n) {
@@ -1647,7 +1845,7 @@ register("heatmap", heatmap);
1647
1845
  define("heatmap");
1648
1846
 
1649
1847
  // src/index.js
1650
- var version = "1.4.0";
1848
+ var version = "1.5.0";
1651
1849
  var types = NAMES.slice();
1652
1850
  export {
1653
1851
  BezelChart,