bezel-charts 1.7.0 → 1.8.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.7.0 | MIT | github.com/ShazMoghaddam/bezel-charts */
1
+ /*! Bezel Charts v1.8.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";
@@ -198,9 +198,9 @@ function spark(o, th) {
198
198
  mn = Math.min(mn, +o.goal);
199
199
  mx = Math.max(mx, +o.goal);
200
200
  }
201
- var pad = (mx - mn || Math.abs(mx) || 1) * 0.12;
202
- mn -= pad;
203
- mx += pad;
201
+ var pad2 = (mx - mn || Math.abs(mx) || 1) * 0.12;
202
+ mn -= pad2;
203
+ mx += pad2;
204
204
  var X = function(i) {
205
205
  return x0 + i * w / (pts.length - 1);
206
206
  };
@@ -358,7 +358,9 @@ var ATTRIBUTES = [
358
358
  "line-suffix",
359
359
  "line-decimals",
360
360
  "line-label",
361
- "line-zero"
361
+ "line-zero",
362
+ "dates",
363
+ "log"
362
364
  ];
363
365
  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}}`;
364
366
  var STEPS = { ArrowLeft: -1, ArrowUp: -1, ArrowRight: 1, ArrowDown: 1 };
@@ -667,9 +669,122 @@ function seriesTable(o, total2) {
667
669
  return { head: ["Label"].concat(head, total2 ? ["Total"] : []), rows };
668
670
  }
669
671
 
672
+ // src/core/time.js
673
+ var HOUR = 36e5;
674
+ var DAY = 864e5;
675
+ function parseDates(o, n) {
676
+ var d = list(o.dates);
677
+ if (!n || d.length < n) return null;
678
+ var out = [];
679
+ for (var i = 0; i < n; i++) {
680
+ var m = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?$/.exec(String(d[i]).trim());
681
+ if (!m) return null;
682
+ out.push(Date.UTC(+m[1], +m[2] - 1, +m[3], +(m[4] || 0), +(m[5] || 0), +(m[6] || 0)));
683
+ }
684
+ return out;
685
+ }
686
+ function fmt2(lang, opt) {
687
+ try {
688
+ return new Intl.DateTimeFormat(lang, Object.assign({ timeZone: "UTC" }, opt));
689
+ } catch (e) {
690
+ return new Intl.DateTimeFormat("en-GB", Object.assign({ timeZone: "UTC" }, opt));
691
+ }
692
+ }
693
+ var pad = function(n) {
694
+ return (n < 10 ? "0" : "") + n;
695
+ };
696
+ var clock = function(t) {
697
+ var d = new Date(t);
698
+ return pad(d.getUTCHours()) + ":" + pad(d.getUTCMinutes());
699
+ };
700
+ function dateText(t, lang, withTime) {
701
+ return fmt2(lang, { day: "numeric", month: "short", year: "numeric" }).format(new Date(t)) + (withTime ? " " + clock(t) : "");
702
+ }
703
+ function hasTimes(T) {
704
+ return T.some(function(t) {
705
+ return t % DAY !== 0;
706
+ });
707
+ }
708
+ var STEPS2 = [
709
+ ["h", 1],
710
+ ["h", 3],
711
+ ["h", 6],
712
+ ["h", 12],
713
+ ["d", 1],
714
+ ["d", 2],
715
+ ["w", 1],
716
+ ["w", 2],
717
+ ["m", 1],
718
+ ["m", 2],
719
+ ["m", 3],
720
+ ["m", 6],
721
+ ["y", 1],
722
+ ["y", 2],
723
+ ["y", 5],
724
+ ["y", 10],
725
+ ["y", 20],
726
+ ["y", 50],
727
+ ["y", 100]
728
+ ];
729
+ var ROUGH = { h: HOUR, d: DAY, w: 7 * DAY, m: 30.44 * DAY, y: 365.25 * DAY };
730
+ function ticksFor(t0, t1, unit, k) {
731
+ var out = [], t;
732
+ if (unit === "h" || unit === "d" || unit === "w") {
733
+ var size = unit === "h" ? HOUR * k : DAY * (unit === "w" ? 7 * k : k);
734
+ var from = unit === "w" ? 4 * DAY : 0;
735
+ for (t = Math.ceil((t0 - from) / size) * size + from; t <= t1; t += size) out.push(t);
736
+ return out;
737
+ }
738
+ var d = new Date(t0), y = d.getUTCFullYear(), mo = unit === "m" ? d.getUTCMonth() : 0;
739
+ for (; ; ) {
740
+ t = Date.UTC(y, mo, 1);
741
+ if (t > t1) return out;
742
+ if (t >= t0 && (unit === "m" ? mo % k === 0 : y % k === 0)) out.push(t);
743
+ if (unit === "m") {
744
+ mo++;
745
+ if (mo === 12) {
746
+ mo = 0;
747
+ y++;
748
+ }
749
+ } else y++;
750
+ }
751
+ }
752
+ function dateTicks(t0, t1, room, lang) {
753
+ var most = Math.max(2, Math.floor(room / 64)), span = t1 - t0;
754
+ for (var i = 0; i < STEPS2.length; i++) {
755
+ var unit = STEPS2[i][0], k = STEPS2[i][1];
756
+ if (span / (ROUGH[unit] * k) > most + 2) continue;
757
+ var ticks = ticksFor(t0, t1, unit, k);
758
+ if (ticks.length > most) continue;
759
+ var dm = fmt2(lang, { day: "numeric", month: "short" }), my = fmt2(lang, { month: "short", year: "numeric" }), mm = fmt2(lang, { month: "short" });
760
+ return ticks.map(function(t, j) {
761
+ var d = new Date(t), text2;
762
+ if (unit === "h") text2 = t % DAY === 0 ? dm.format(d) : clock(t);
763
+ else if (unit === "d" || unit === "w") text2 = dm.format(d);
764
+ else if (unit === "m") text2 = d.getUTCMonth() === 0 || j === 0 ? my.format(d) : mm.format(d);
765
+ else text2 = String(d.getUTCFullYear());
766
+ return { t, text: text2 };
767
+ });
768
+ }
769
+ return [];
770
+ }
771
+ function pointName(labels, T, i, lang, timed) {
772
+ return labels[i] || (T ? dateText(T[i], lang, timed) : "#" + (i + 1));
773
+ }
774
+ function datedTable(t, o, n) {
775
+ var T = parseDates(o, n), labels = list(o.labels);
776
+ if (!T) return t;
777
+ var timed = hasTimes(T);
778
+ t.head[0] = "Date";
779
+ t.rows.forEach(function(r, i) {
780
+ if (!labels[i]) r[0] = dateText(T[i], o.lang || "en-GB", timed);
781
+ });
782
+ return t;
783
+ }
784
+
670
785
  // src/core/xy.js
671
786
  function xyFrame(o, th, c, x0, y0, w, h) {
672
- var s = "", top = y0, n = c.n, labels = c.labels;
787
+ var s = "", top = y0, n = c.n, labels = c.labels, T = c.dates;
673
788
  if (c.names) {
674
789
  var lg = legend(c.names, c.pal, th, x0, top, w, c.key, c.kw);
675
790
  s += lg.svg;
@@ -684,24 +799,42 @@ function xyFrame(o, th, c, x0, y0, w, h) {
684
799
  mn = Math.min(0, mn);
685
800
  mx = Math.max(0, mx);
686
801
  }
687
- var sc = niceScale(mn, mx, 4);
802
+ var hasMin = isFinite(c.min), hasMax = isFinite(c.max);
803
+ if (hasMin && hasMax && c.min >= c.max) hasMin = hasMax = false;
804
+ var sc = c.log ? logScale(hasMin && c.min > 0 ? c.min : c.posLo, hasMax && c.max > 0 ? c.max : c.posHi) : hasMin || hasMax ? fixedScale(hasMin ? c.min : mn, hasMax ? c.max : mx, hasMin, hasMax) : niceScale(mn, mx, 4);
688
805
  var tl = sc.ticks.map(c.tick);
689
806
  var axisW = Math.max.apply(null, tl.map(function(t) {
690
807
  return tw(t, 10, true);
691
808
  })) + 10;
692
- var bottom = labels.length ? 20 : 4;
809
+ var bottom = labels.length || T ? 20 : 4;
693
810
  var px = x0 + axisW, pw = w - axisW, ph = h - (top - y0) - bottom;
694
- var X = function(i2) {
811
+ var t0 = T ? Math.min.apply(null, T) : 0, span = T ? Math.max.apply(null, T) - t0 : 0;
812
+ var X = T ? function(i2) {
813
+ return span ? px + (T[i2] - t0) / span * pw : px + pw / 2;
814
+ } : function(i2) {
695
815
  return n === 1 ? px + pw / 2 : px + i2 * pw / (n - 1);
696
816
  };
697
- var Y = function(v) {
817
+ var Y = sc.log ? function(v) {
818
+ return top + ph - (Math.log10(v) - sc.a) / (sc.b - sc.a) * ph;
819
+ } : function(v) {
698
820
  return top + ph - (v - sc.lo) / (sc.hi - sc.lo) * ph;
699
821
  };
822
+ var clip = "";
823
+ if (hasMin || hasMax) {
824
+ var cid = newId("c");
825
+ s += '<defs><clipPath id="' + cid + '"><rect x="' + r2(px - 4) + '" y="' + r2(top - 4) + '" width="' + r2(pw + 8) + '" height="' + r2(ph + 8) + '"/></clipPath></defs>';
826
+ clip = ' clip-path="url(#' + cid + ')"';
827
+ }
700
828
  sc.ticks.forEach(function(t, i2) {
701
829
  var y = Y(t);
702
830
  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"'));
703
831
  });
704
- if (labels.length) {
832
+ if (T) {
833
+ dateTicks(t0, t0 + span, pw, o.lang || "en-GB").forEach(function(d) {
834
+ var half = tw(d.text, 10, true) / 2, x = span ? px + (d.t - t0) / span * pw : px + pw / 2;
835
+ s += text(clamp(x, px + half, px + pw - half), top + ph + 15, d.text, small(th, 10, 'text-anchor="middle"'));
836
+ });
837
+ } else if (labels.length) {
705
838
  var every = Math.max(1, Math.ceil(n * (Math.max.apply(null, labels.map(function(l) {
706
839
  return tw(l, 10, true);
707
840
  })) + 10) / pw));
@@ -716,14 +849,44 @@ function xyFrame(o, th, c, x0, y0, w, h) {
716
849
  var x = X(nt.i);
717
850
  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));
718
851
  });
719
- return { svg: s, X, Y, px, pw, top, ph };
852
+ return { svg: s, X, Y, px, pw, top, ph, clip, uneven: !!T };
853
+ }
854
+ function fixedScale(lo, hi, hasMin, hasMax) {
855
+ var nice = niceScale(lo, hi, 4), a = hasMin ? lo : nice.lo, b = hasMax ? hi : nice.hi, step = nice.ticks[1] - nice.ticks[0];
856
+ var ticks = nice.ticks.filter(function(t) {
857
+ return t >= a - step * 1e-9 && t <= b + step * 1e-9;
858
+ });
859
+ if (hasMin && (!ticks.length || ticks[0] - a > step * 0.4)) ticks.unshift(a);
860
+ if (hasMax && (!ticks.length || b - ticks[ticks.length - 1] > step * 0.4)) ticks.push(b);
861
+ return { lo: a, hi: b, ticks };
862
+ }
863
+ function logScale(lo, hi) {
864
+ var a = Math.floor(Math.log10(lo)), b = Math.ceil(Math.log10(hi)), ticks = [];
865
+ if (b === a) b++;
866
+ for (var e = a; e <= b; e++) {
867
+ var p = Number("1e" + e);
868
+ ticks.push(p);
869
+ if (b - a <= 2 && e < b) ticks.push(2 * p, 5 * p);
870
+ }
871
+ return { log: true, a, b, lo: ticks[0], hi: ticks[ticks.length - 1], ticks };
720
872
  }
721
873
  function xyFinish(o, th, f, n, tip) {
722
- var s = has(o.goal) ? goalLine(o, th, f.px, f.px + f.pw, f.Y(+o.goal)) : "";
723
- var step = n > 1 ? f.pw / (n - 1) : f.pw;
724
- for (var j = 0; j < n; j++) s += hit(f.X(j) - step / 2, f.top, step, f.ph, tip(j));
874
+ var g = has(o.goal) ? f.Y(+o.goal) : NaN;
875
+ var s = isFinite(g) ? goalLine(o, th, f.px, f.px + f.pw, g) : "";
876
+ var step = n > 1 ? f.pw / (n - 1) : f.pw, X = f.X;
877
+ for (var j = 0; j < n; j++) {
878
+ if (!f.uneven) {
879
+ s += hit(X(j) - step / 2, f.top, step, f.ph, tip(j));
880
+ continue;
881
+ }
882
+ var l = j ? (X(j - 1) + X(j)) / 2 : X(0) - (n > 1 ? (X(1) - X(0)) / 2 : f.pw / 2);
883
+ var r = j < n - 1 ? (X(j) + X(j + 1)) / 2 : X(j) + (n > 1 ? (X(j) - X(j - 1)) / 2 : f.pw / 2);
884
+ s += hit(l, f.top, Math.max(1, r - l), f.ph, tip(j));
885
+ }
725
886
  return s;
726
887
  }
888
+
889
+ // src/core/lines.js
727
890
  function lineKey(pal, dashOf, markOf, bg) {
728
891
  return function(k, x, y) {
729
892
  var c = pal[k % pal.length], dk = dashOf ? DASHES[dashOf(k)] : "", mk = markOf ? markOf(k) : "none";
@@ -736,6 +899,7 @@ function seriesLine(o, d, c, k, dk) {
736
899
  function pointMarks(o, mk, ys, X, Y, c, bg, k) {
737
900
  var s = "", every = Math.ceil(ys.length / 40);
738
901
  ys.forEach(function(y, i) {
902
+ if (!isFinite(Y(y))) return;
739
903
  if (i % every === 0 || i === ys.length - 1) s += marker(mk, X(i), Y(y), 3.5, ' fill="' + c + '" stroke="' + bg + '" stroke-width="1.5"' + anim(o, "bzc-a", 0.6 + k * 0.15));
740
904
  });
741
905
  return s;
@@ -755,9 +919,14 @@ function line(o, th) {
755
919
  var nameOf = function(k) {
756
920
  return names[k] || "Series " + (k + 1);
757
921
  };
922
+ var T = parseDates(o, n), timed = T && hasTimes(T), lang = o.lang || "en-GB";
923
+ var all = [].concat.apply([], S), pos = all.filter(function(v) {
924
+ return v > 0;
925
+ });
926
+ if (has(o.goal) && +o.goal > 0) pos.push(+o.goal);
927
+ var log = bool(o.log) && pos.length > 0;
758
928
  return tile(o, th, h, function(x0, y0, w, h2) {
759
929
  if (!n) return emptyState(th, x0, y0, w, h2);
760
- var all = [].concat.apply([], S);
761
930
  var f = xyFrame(o, th, {
762
931
  n,
763
932
  names: multi ? S.map(function(_, k) {
@@ -773,17 +942,34 @@ function line(o, th) {
773
942
  notes,
774
943
  tick: function(t) {
775
944
  return fmt(t, o, true);
776
- }
945
+ },
946
+ dates: T,
947
+ min: has(o.min) ? +o.min : NaN,
948
+ max: has(o.max) ? +o.max : NaN,
949
+ log,
950
+ posLo: log ? Math.min.apply(null, pos) : 0,
951
+ posHi: log ? Math.max.apply(null, pos) : 0
777
952
  }, x0, y0, w, h2);
778
- var s = f.svg, X = f.X, Y = f.Y, px = f.px, pw = f.pw, top = f.top, ph = f.ph;
953
+ var s = f.svg + (f.clip ? "<g" + f.clip + ">" : ""), X = f.X, Y = f.Y, px = f.px, pw = f.pw, top = f.top, ph = f.ph;
779
954
  S.forEach(function(v, k) {
780
955
  var c = pal[k % pal.length];
781
956
  var P = v.map(function(y, i) {
782
957
  return [X(i), Y(y)];
783
958
  });
784
959
  if (P.length === 1) P = [[px, P[0][1]], [px + pw, P[0][1]]];
785
- var d = pathOf(P, bool(o.straight));
786
- if (k === 0) {
960
+ var runs = log ? P.reduce(function(r, p) {
961
+ if (isFinite(p[1])) r[r.length - 1].push(p);
962
+ else if (r[r.length - 1].length) r.push([]);
963
+ return r;
964
+ }, [[]]).filter(function(r) {
965
+ return r.length;
966
+ }) : [P];
967
+ if (!runs.length) return;
968
+ var d = runs.map(function(r) {
969
+ return pathOf(r, bool(o.straight));
970
+ }).join("");
971
+ P = [].concat.apply([], runs);
972
+ if (k === 0 && !log) {
787
973
  var g = newId("g"), last = P[P.length - 1];
788
974
  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") + "/>";
789
975
  }
@@ -796,11 +982,11 @@ function line(o, th) {
796
982
  s += '<circle cx="' + r2(e[0]) + '" cy="' + r2(e[1]) + '" r="4" fill="' + c + '" stroke="' + bg + '" stroke-width="2"/>';
797
983
  }
798
984
  if (bool(o.showValues)) v.forEach(function(y, i) {
799
- s += text(X(i), Y(y) - 8, fmt(y, o, true), small(th, 9, 'text-anchor="middle"'));
985
+ if (isFinite(Y(y))) s += text(X(i), Y(y) - 8, fmt(y, o, true), small(th, 9, 'text-anchor="middle"'));
800
986
  });
801
987
  });
802
- return s + xyFinish(o, th, f, n, function(j) {
803
- var tip = labels[j] || "#" + (j + 1);
988
+ return s + (f.clip ? "</g>" : "") + xyFinish(o, th, f, n, function(j) {
989
+ var tip = pointName(labels, T, j, lang, timed);
804
990
  S.forEach(function(v, k) {
805
991
  if (j < v.length) tip += "\n" + (multi ? nameOf(k) + ": " : "") + fmt(v[j], o);
806
992
  });
@@ -809,7 +995,8 @@ function line(o, th) {
809
995
  }, { stat: st, summary: S.length + " series over " + n + " points" });
810
996
  }
811
997
  line.table = function(o) {
812
- return seriesTable(o);
998
+ var t = seriesTable(o);
999
+ return datedTable(t, o, t.rows.length);
813
1000
  };
814
1001
 
815
1002
  // src/entries/line.js
@@ -871,6 +1058,7 @@ function area(o, th) {
871
1058
  var key = patOf ? function(j2, x, y) {
872
1059
  return '<rect x="' + r2(x) + '" y="' + r2(y + 1) + '" width="10" height="10" rx="2" fill="' + (fills[j2] || pal[j2 % pal.length]) + '"/>';
873
1060
  } : (dashOf || markOf) && lineKey(pal, dashOf, markOf, bg);
1061
+ var T = parseDates(o, n), timed = T && hasTimes(T), lang = o.lang || "en-GB";
874
1062
  var share = function(j2, i2) {
875
1063
  return pos[i2] ? Math.round(Math.max(0, val(j2, i2)) / pos[i2] * 100) : 0;
876
1064
  };
@@ -889,13 +1077,17 @@ function area(o, th) {
889
1077
  zero: true,
890
1078
  labels,
891
1079
  notes,
1080
+ dates: T,
1081
+ // Areas are measured from zero, so a min above zero would cut off their base: only max and a min at or below zero apply.
1082
+ min: has(o.min) && +o.min <= 0 ? +o.min : NaN,
1083
+ max: has(o.max) ? +o.max : NaN,
892
1084
  tick: mode === "percent" ? function(t2) {
893
1085
  return fmt(t2, { lang: o.lang }, true) + "%";
894
1086
  } : function(t2) {
895
1087
  return fmt(t2, o, true);
896
1088
  }
897
1089
  }, x0, y0, w, h2);
898
- var s = (defs ? "<defs>" + defs + "</defs>" : "") + f.svg, X = f.X, Y = f.Y;
1090
+ var s = (defs ? "<defs>" + defs + "</defs>" : "") + f.svg + (f.clip ? "<g" + f.clip + ">" : ""), X = f.X, Y = f.Y;
899
1091
  var pts = function(ys) {
900
1092
  var P = ys.map(function(y, i2) {
901
1093
  return [X(i2), Y(y)];
@@ -929,8 +1121,8 @@ function area(o, th) {
929
1121
  s += text(X(i2), Y(top) - 8, fmt(t2, o, true), small(th, 9, 'text-anchor="middle"'));
930
1122
  });
931
1123
  }
932
- return s + xyFinish(o, th, f, n, function(i2) {
933
- var tip = labels[i2] || "#" + (i2 + 1);
1124
+ return s + (f.clip ? "</g>" : "") + xyFinish(o, th, f, n, function(i2) {
1125
+ var tip = pointName(labels, T, i2, lang, timed);
934
1126
  S.forEach(function(ser, j2) {
935
1127
  if (i2 < ser.length) tip += "\n" + (multi ? nameOf(j2) + ": " : "") + fmt(ser[i2], o) + (mode === "percent" ? " (" + share(j2, i2) + "%)" : "");
936
1128
  });
@@ -942,8 +1134,8 @@ function area(o, th) {
942
1134
  });
943
1135
  }
944
1136
  area.table = function(o) {
945
- var S = series(o.values);
946
- return seriesTable(o, S.length > 1 && (o.stacked === "percent" || bool(o.stacked)));
1137
+ var S = series(o.values), t = seriesTable(o, S.length > 1 && (o.stacked === "percent" || bool(o.stacked)));
1138
+ return datedTable(t, o, t.rows.length);
947
1139
  };
948
1140
 
949
1141
  // src/entries/area.js
@@ -2217,7 +2409,7 @@ register("heatmap", heatmap);
2217
2409
  define("heatmap");
2218
2410
 
2219
2411
  // src/index.js
2220
- var version = "1.7.0";
2412
+ var version = "1.8.0";
2221
2413
  var types = NAMES.slice();
2222
2414
  export {
2223
2415
  BezelChart,