bezel-charts 1.2.0 → 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,4 +1,5 @@
1
- import { r2, clamp, num, has, list, series, bool, fmt, niceScale, text, small, hit, palette, emptyState, parseNotes, goalLine, legend, axisWidth, styleOf, marker, SHAPES } from '../core/util.js';
1
+ import { r2, clamp, num, has, list, series, bool, fmt, text, small, hit, palette, emptyState, parseNotes, goalLine } from '../core/util.js';
2
+ import { niceScale, legend, axisWidth, styleOf, marker, SHAPES } from '../core/series.js';
2
3
  import { tile } from '../core/tile.js';
3
4
 
4
5
  /** Least-squares fit. Returns slope, intercept and Pearson's r, or null when x has no spread. */
@@ -0,0 +1,85 @@
1
+ /** Helpers for the charts with axes and several series: line, bars and scatter. Kept apart from util.js so the other charts don't load them. */
2
+ import { r2, list, series, fmt, tw, text, SANS } from './util.js';
3
+
4
+ export function niceScale(min, max, count) {
5
+ if (min === max) {
6
+ if (min === 0) max = 1;
7
+ else { var p = Math.abs(min) * 0.1; min -= p; max += p; }
8
+ }
9
+ var step0 = (max - min) / (count || 4);
10
+ var mag = Math.pow(10, Math.floor(Math.log10(step0)));
11
+ var err = step0 / mag;
12
+ var step = (err <= 1 ? 1 : err <= 2 ? 2 : err <= 5 ? 5 : 10) * mag;
13
+ var lo = Math.floor(min / step) * step, hi = Math.ceil(max / step) * step, ticks = [];
14
+ for (var t = lo; t <= hi + step / 2; t += step) ticks.push(+t.toFixed(10));
15
+ return { lo: lo, hi: hi, ticks: ticks };
16
+ }
17
+
18
+ /**
19
+ * A wrapping row of coloured dots and series names. Returns the SVG and the height it used.
20
+ * key(k, x, y) draws a different swatch, such as a dashed line, and kw is the room it takes before the name.
21
+ */
22
+ export function legend(names, pal, th, x0, y0, w, key, kw) {
23
+ var x = x0, y = y0, s = '', off = kw || 13;
24
+ names.forEach(function (nm, k) {
25
+ var wd = off + tw(nm, 12) + 16;
26
+ if (x > x0 && x + wd - 16 > x0 + w) { x = x0; y += 20; }
27
+ s += (key ? key(k, x, y) : '<circle cx="' + r2(x + 4) + '" cy="' + r2(y + 6) + '" r="4" fill="' + pal[k % pal.length] + '"/>') +
28
+ text(x + off, y + 10, nm, 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"');
29
+ x += wd;
30
+ });
31
+ return { svg: s, height: y - y0 + 24 };
32
+ }
33
+
34
+ /** Line dashes, marker shapes and bar patterns, so series differ by more than colour. "auto" hands them out in this order. */
35
+ export var DASHES = { solid: '', dashed: '7 5', dotted: '1 5', dashdot: '9 4 1 4' };
36
+ export var SHAPES = ['circle', 'square', 'triangle', 'diamond'];
37
+ export var PATTERNS = ['solid', 'stripes', 'dots', 'grid'];
38
+
39
+ /**
40
+ * Reads dash, markers or patterns. The bare attribute or "auto" hands out every style in turn; a list sets one per series,
41
+ * repeating like colours do. Returns a function from series index to style name, or null when the option is off.
42
+ */
43
+ export function styleOf(v, names, off) {
44
+ if (v == null || v === false || v === 'none' || v === 'false') return null;
45
+ var l = v === true || v === '' || v === 'auto' ? names : list(v).map(function (s) { return names.indexOf(s) < 0 ? off : s; });
46
+ if (!l.length) return null;
47
+ return function (k) { return l[k % l.length]; };
48
+ }
49
+
50
+ /** A point marker centred on x, y. attrs carries the fill and stroke. */
51
+ export function marker(kind, x, y, r, attrs) {
52
+ var a = r * 1.15, b = r * 0.85;
53
+ if (kind === 'square') return '<rect x="' + r2(x - b) + '" y="' + r2(y - b) + '" width="' + r2(2 * b) + '" height="' + r2(2 * b) + '" rx="1"' + attrs + '/>';
54
+ 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 + '/>';
55
+ 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 + '/>';
56
+ return '<circle cx="' + r2(x) + '" cy="' + r2(y) + '" r="' + r2(r) + '"' + attrs + '/>';
57
+ }
58
+
59
+ /** A fill pattern in colour c, with stripes, dots or a grid drawn in the tile colour so they show on any colour. */
60
+ export function pattern(id, kind, c, bg) {
61
+ var ink = ' stroke="' + bg + '" stroke-opacity=".55" stroke-width="1.5"';
62
+ return '<pattern id="' + id + '" width="6" height="6" patternUnits="userSpaceOnUse"' + (kind === 'dots' ? '' : ' patternTransform="rotate(45)"') +
63
+ '><rect width="6" height="6" fill="' + c + '"/>' +
64
+ (kind === 'dots' ? '<circle cx="3" cy="3" r="1.3" fill="' + bg + '" fill-opacity=".55"/>' : '<path d="M3,0V6' + (kind === 'grid' ? 'M0,3H6' : '') + '"' + ink + '/>') +
65
+ '</pattern>';
66
+ }
67
+
68
+ /** Value axis: gridlines plus tick labels on the left. Returns the axis width it reserved. */
69
+ export function axisWidth(ticks) {
70
+ return Math.max.apply(null, ticks.map(function (t) { return tw(t, 10, true); })) + 10;
71
+ }
72
+
73
+ /** The data table for charts with labels and one or more series. total adds a column adding each row up. */
74
+ export function seriesTable(o, total) {
75
+ var S = series(o.values), names = list(o.series), labels = list(o.labels), rows = [];
76
+ var n = Math.max.apply(null, [0].concat(S.map(function (s) { return s.length; })));
77
+ for (var i = 0; i < n; i++) {
78
+ var row = [labels[i] || '#' + (i + 1)], t = 0;
79
+ S.forEach(function (s) { row.push(i < s.length ? fmt(s[i], o) : ''); t += i < s.length ? s[i] : 0; });
80
+ if (total) row.push(fmt(t, o));
81
+ rows.push(row);
82
+ }
83
+ var head = S.length > 1 || names.length ? S.map(function (_, k) { return names[k] || 'Series ' + (k + 1); }) : ['Value'];
84
+ return { head: ['Label'].concat(head, total ? ['Total'] : []), rows: rows };
85
+ }
package/src/core/util.js CHANGED
@@ -46,20 +46,6 @@ export function fmt(n, o, short) {
46
46
  return (n < 0 ? '−' : '') + (o.prefix || '') + s + (o.suffix || '');
47
47
  }
48
48
 
49
- export function niceScale(min, max, count) {
50
- if (min === max) {
51
- if (min === 0) max = 1;
52
- else { var p = Math.abs(min) * 0.1; min -= p; max += p; }
53
- }
54
- var step0 = (max - min) / (count || 4);
55
- var mag = Math.pow(10, Math.floor(Math.log10(step0)));
56
- var err = step0 / mag;
57
- var step = (err <= 1 ? 1 : err <= 2 ? 2 : err <= 5 ? 5 : 10) * mag;
58
- var lo = Math.floor(min / step) * step, hi = Math.ceil(max / step) * step, ticks = [];
59
- for (var t = lo; t <= hi + step / 2; t += step) ticks.push(+t.toFixed(10));
60
- return { lo: lo, hi: hi, ticks: ticks };
61
- }
62
-
63
49
  export function pathOf(pts, straight) {
64
50
  if (!pts.length) return '';
65
51
  var d = 'M' + r2(pts[0][0]) + ',' + r2(pts[0][1]);
@@ -122,72 +108,3 @@ export function statOf(v, o, fallback) {
122
108
  if (k === 'last' && v.length > 1 && o.delta !== 'none') delta = v[v.length - 1] - v[0];
123
109
  return { val: map[k], delta: delta };
124
110
  }
125
-
126
- /**
127
- * A wrapping row of coloured dots and series names. Returns the SVG and the height it used.
128
- * key(k, x, y) draws a different swatch, such as a dashed line, and kw is the room it takes before the name.
129
- */
130
- export function legend(names, pal, th, x0, y0, w, key, kw) {
131
- var x = x0, y = y0, s = '', off = kw || 13;
132
- names.forEach(function (nm, k) {
133
- var wd = off + tw(nm, 12) + 16;
134
- if (x > x0 && x + wd - 16 > x0 + w) { x = x0; y += 20; }
135
- s += (key ? key(k, x, y) : '<circle cx="' + r2(x + 4) + '" cy="' + r2(y + 6) + '" r="4" fill="' + pal[k % pal.length] + '"/>') +
136
- text(x + off, y + 10, nm, 'fill="' + th.fg + '" font-family="' + SANS + '" font-size="12"');
137
- x += wd;
138
- });
139
- return { svg: s, height: y - y0 + 24 };
140
- }
141
-
142
- /** Line dashes, marker shapes and bar patterns, so series differ by more than colour. "auto" hands them out in this order. */
143
- export var DASHES = { solid: '', dashed: '7 5', dotted: '1 5', dashdot: '9 4 1 4' };
144
- export var SHAPES = ['circle', 'square', 'triangle', 'diamond'];
145
- export var PATTERNS = ['solid', 'stripes', 'dots', 'grid'];
146
-
147
- /**
148
- * Reads dash, markers or patterns. The bare attribute or "auto" hands out every style in turn; a list sets one per series,
149
- * repeating like colours do. Returns a function from series index to style name, or null when the option is off.
150
- */
151
- export function styleOf(v, names, off) {
152
- if (v == null || v === false || v === 'none' || v === 'false') return null;
153
- var l = v === true || v === '' || v === 'auto' ? names : list(v).map(function (s) { return names.indexOf(s) < 0 ? off : s; });
154
- if (!l.length) return null;
155
- return function (k) { return l[k % l.length]; };
156
- }
157
-
158
- /** A point marker centred on x, y. attrs carries the fill and stroke. */
159
- export function marker(kind, x, y, r, attrs) {
160
- var a = r * 1.15, b = r * 0.85;
161
- if (kind === 'square') return '<rect x="' + r2(x - b) + '" y="' + r2(y - b) + '" width="' + r2(2 * b) + '" height="' + r2(2 * b) + '" rx="1"' + attrs + '/>';
162
- 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 + '/>';
163
- 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 + '/>';
164
- return '<circle cx="' + r2(x) + '" cy="' + r2(y) + '" r="' + r2(r) + '"' + attrs + '/>';
165
- }
166
-
167
- /** A fill pattern in colour c, with stripes, dots or a grid drawn in the tile colour so they show on any colour. */
168
- export function pattern(id, kind, c, bg) {
169
- var ink = ' stroke="' + bg + '" stroke-opacity=".55" stroke-width="1.5"';
170
- return '<pattern id="' + id + '" width="6" height="6" patternUnits="userSpaceOnUse"' + (kind === 'dots' ? '' : ' patternTransform="rotate(45)"') +
171
- '><rect width="6" height="6" fill="' + c + '"/>' +
172
- (kind === 'dots' ? '<circle cx="3" cy="3" r="1.3" fill="' + bg + '" fill-opacity=".55"/>' : '<path d="M3,0V6' + (kind === 'grid' ? 'M0,3H6' : '') + '"' + ink + '/>') +
173
- '</pattern>';
174
- }
175
-
176
- /** Value axis: gridlines plus tick labels on the left. Returns the axis width it reserved. */
177
- export function axisWidth(ticks) {
178
- return Math.max.apply(null, ticks.map(function (t) { return tw(t, 10, true); })) + 10;
179
- }
180
-
181
- /** The data table for charts with labels and one or more series. total adds a column adding each row up. */
182
- export function seriesTable(o, total) {
183
- var S = series(o.values), names = list(o.series), labels = list(o.labels), rows = [];
184
- var n = Math.max.apply(null, [0].concat(S.map(function (s) { return s.length; })));
185
- for (var i = 0; i < n; i++) {
186
- var row = [labels[i] || '#' + (i + 1)], t = 0;
187
- S.forEach(function (s) { row.push(i < s.length ? fmt(s[i], o) : ''); t += i < s.length ? s[i] : 0; });
188
- if (total) row.push(fmt(t, o));
189
- rows.push(row);
190
- }
191
- var head = S.length > 1 || names.length ? S.map(function (_, k) { return names[k] || 'Series ' + (k + 1); }) : ['Value'];
192
- return { head: ['Label'].concat(head, total ? ['Total'] : []), rows: rows };
193
- }
package/src/element.js CHANGED
@@ -265,3 +265,16 @@ function tableHTML(caption, { head, rows }) {
265
265
  return `<table><caption>${esc(caption || 'Chart data')}</caption><thead><tr>${head.map((h) => `<th scope="col">${esc(h)}</th>`).join('')}</tr></thead>` +
266
266
  `<tbody>${rows.map((r) => `<tr>${cells(r)}</tr>`).join('')}</tbody></table>`;
267
267
  }
268
+
269
+ /** Defines <bezel-type>, plus <bezel-chart> if no chart has yet. Safe to call twice, and does nothing outside a browser. */
270
+ export function define(type) {
271
+ if (typeof customElements === 'undefined' || typeof HTMLElement === 'undefined') return;
272
+ if (!customElements.get(`bezel-${type}`)) {
273
+ customElements.define(`bezel-${type}`, class extends BezelChart {
274
+ get chartType() {
275
+ return type;
276
+ }
277
+ });
278
+ }
279
+ if (!customElements.get('bezel-chart')) customElements.define('bezel-chart', class extends BezelChart {});
280
+ }
@@ -0,0 +1,10 @@
1
+ /** <bezel-bars> on its own: import 'bezel-charts/bars', or load dist/charts/bars.js. */
2
+ import bars from '../charts/bars.js';
3
+ import { register } from '../render.js';
4
+ import { define } from '../element.js';
5
+
6
+ register('bars', bars);
7
+ define('bars');
8
+
9
+ export { render, toSVG } from '../render.js';
10
+ export { BezelChart } from '../element.js';
@@ -0,0 +1,10 @@
1
+ /** <bezel-donut> on its own: import 'bezel-charts/donut', or load dist/charts/donut.js. */
2
+ import donut from '../charts/donut.js';
3
+ import { register } from '../render.js';
4
+ import { define } from '../element.js';
5
+
6
+ register('donut', donut);
7
+ define('donut');
8
+
9
+ export { render, toSVG } from '../render.js';
10
+ export { BezelChart } from '../element.js';
@@ -0,0 +1,10 @@
1
+ /** <bezel-gauge> on its own: import 'bezel-charts/gauge', or load dist/charts/gauge.js. */
2
+ import gauge from '../charts/gauge.js';
3
+ import { register } from '../render.js';
4
+ import { define } from '../element.js';
5
+
6
+ register('gauge', gauge);
7
+ define('gauge');
8
+
9
+ export { render, toSVG } from '../render.js';
10
+ export { BezelChart } from '../element.js';
@@ -0,0 +1,10 @@
1
+ /** <bezel-heatmap> on its own: import 'bezel-charts/heatmap', or load dist/charts/heatmap.js. */
2
+ import heatmap from '../charts/heatmap.js';
3
+ import { register } from '../render.js';
4
+ import { define } from '../element.js';
5
+
6
+ register('heatmap', heatmap);
7
+ define('heatmap');
8
+
9
+ export { render, toSVG } from '../render.js';
10
+ export { BezelChart } from '../element.js';
@@ -0,0 +1,10 @@
1
+ /** <bezel-line> on its own: import 'bezel-charts/line', or load dist/charts/line.js. */
2
+ import line from '../charts/line.js';
3
+ import { register } from '../render.js';
4
+ import { define } from '../element.js';
5
+
6
+ register('line', line);
7
+ define('line');
8
+
9
+ export { render, toSVG } from '../render.js';
10
+ export { BezelChart } from '../element.js';
@@ -0,0 +1,10 @@
1
+ /** <bezel-progress> on its own: import 'bezel-charts/progress', or load dist/charts/progress.js. */
2
+ import progress from '../charts/progress.js';
3
+ import { register } from '../render.js';
4
+ import { define } from '../element.js';
5
+
6
+ register('progress', progress);
7
+ define('progress');
8
+
9
+ export { render, toSVG } from '../render.js';
10
+ export { BezelChart } from '../element.js';
@@ -0,0 +1,10 @@
1
+ /** <bezel-ring> on its own: import 'bezel-charts/ring', or load dist/charts/ring.js. */
2
+ import ring from '../charts/ring.js';
3
+ import { register } from '../render.js';
4
+ import { define } from '../element.js';
5
+
6
+ register('ring', ring);
7
+ define('ring');
8
+
9
+ export { render, toSVG } from '../render.js';
10
+ export { BezelChart } from '../element.js';
@@ -0,0 +1,10 @@
1
+ /** <bezel-scatter> on its own: import 'bezel-charts/scatter', or load dist/charts/scatter.js. */
2
+ import scatter from '../charts/scatter.js';
3
+ import { register } from '../render.js';
4
+ import { define } from '../element.js';
5
+
6
+ register('scatter', scatter);
7
+ define('scatter');
8
+
9
+ export { render, toSVG } from '../render.js';
10
+ export { BezelChart } from '../element.js';
@@ -0,0 +1,10 @@
1
+ /** <bezel-spark> on its own: import 'bezel-charts/spark', or load dist/charts/spark.js. */
2
+ import spark from '../charts/spark.js';
3
+ import { register } from '../render.js';
4
+ import { define } from '../element.js';
5
+
6
+ register('spark', spark);
7
+ define('spark');
8
+
9
+ export { render, toSVG } from '../render.js';
10
+ export { BezelChart } from '../element.js';
package/src/index.js CHANGED
@@ -3,24 +3,20 @@
3
3
  * Small, polished charts as web components. Zero dependencies, pure SVG.
4
4
  * Created by Shaz Moghaddam, London · https://shazmoghaddam.github.io
5
5
  */
6
- import { TYPES, render, toSVG } from './render.js';
6
+ // Each entry registers its chart and defines its tag. Pages that only need a few charts can import those entries instead.
7
+ import './entries/spark.js';
8
+ import './entries/line.js';
9
+ import './entries/bars.js';
10
+ import './entries/scatter.js';
11
+ import './entries/ring.js';
12
+ import './entries/donut.js';
13
+ import './entries/gauge.js';
14
+ import './entries/progress.js';
15
+ import './entries/heatmap.js';
16
+ import { NAMES, render, toSVG } from './render.js';
7
17
  import { THEMES } from './core/themes.js';
8
18
  import { BezelChart } from './element.js';
9
19
 
10
- export const version = '1.2.0';
11
- export const types = Object.keys(TYPES);
20
+ export const version = '1.3.0';
21
+ export const types = NAMES.slice();
12
22
  export { render, toSVG, BezelChart, THEMES as themes };
13
-
14
- if (typeof customElements !== 'undefined' && typeof HTMLElement !== 'undefined') {
15
- for (const type of types) {
16
- const tag = `bezel-${type}`;
17
- if (!customElements.get(tag)) {
18
- customElements.define(tag, class extends BezelChart {
19
- get chartType() {
20
- return type;
21
- }
22
- });
23
- }
24
- }
25
- if (!customElements.get('bezel-chart')) customElements.define('bezel-chart', class extends BezelChart {});
26
- }
package/src/render.js CHANGED
@@ -1,17 +1,16 @@
1
1
  /** Pure rendering: options in, SVG string out. No DOM needed, so it runs in browsers, Node and build scripts. */
2
2
  import { THEMES } from './core/themes.js';
3
3
  import { bool } from './core/util.js';
4
- import spark from './charts/spark.js';
5
- import line from './charts/line.js';
6
- import bars from './charts/bars.js';
7
- import ring from './charts/ring.js';
8
- import donut from './charts/donut.js';
9
- import gauge from './charts/gauge.js';
10
- import progress from './charts/progress.js';
11
- import heatmap from './charts/heatmap.js';
12
- import scatter from './charts/scatter.js';
13
-
14
- export const TYPES = { spark, line, bars, scatter, ring, donut, gauge, progress, heatmap };
4
+ /** Every chart type, in the order the full bundle loads them. */
5
+ export const NAMES = ['spark', 'line', 'bars', 'scatter', 'ring', 'donut', 'gauge', 'progress', 'heatmap'];
6
+
7
+ /** The renderers loaded so far. Each chart's entry file adds its own, so a page only loads the charts it uses. */
8
+ export const TYPES = {};
9
+
10
+ /** Makes a chart type available to render(), toSVG() and the elements. */
11
+ export function register(type, fn) {
12
+ TYPES[type] = fn;
13
+ }
15
14
 
16
15
  const camel = (k) => k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
17
16
 
@@ -22,7 +21,9 @@ const camel = (k) => k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
22
21
  */
23
22
  function prepare(type, options) {
24
23
  if (!TYPES[type]) {
25
- throw new Error(`Bezel Charts: unknown chart type "${type}". Choose one of: ${Object.keys(TYPES).join(', ')}.`);
24
+ throw new Error(NAMES.includes(type)
25
+ ? `Bezel Charts: the ${type} chart isn't loaded. Add import 'bezel-charts/${type}', or load dist/charts/${type}.js.`
26
+ : `Bezel Charts: unknown chart type "${type}". Choose one of: ${NAMES.join(', ')}.`);
26
27
  }
27
28
  const o = {};
28
29
  for (const [k, v] of Object.entries(options)) o[camel(k)] = v;