bezel-charts 0.0.0-stage → 1.1.1
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/LICENSE +21 -0
- package/README.md +420 -2
- package/dist/bezel-charts.iife.js +9 -0
- package/dist/bezel-charts.js +1222 -0
- package/dist/bezel-charts.min.js +9 -0
- package/package.json +48 -4
- package/src/charts/bars.js +180 -0
- package/src/charts/donut.js +50 -0
- package/src/charts/gauge.js +41 -0
- package/src/charts/heatmap.js +53 -0
- package/src/charts/line.js +71 -0
- package/src/charts/progress.js +57 -0
- package/src/charts/ring.js +40 -0
- package/src/charts/scatter.js +115 -0
- package/src/charts/spark.js +41 -0
- package/src/core/themes.js +13 -0
- package/src/core/tile.js +56 -0
- package/src/core/util.js +142 -0
- package/src/element.js +169 -0
- package/src/index.js +26 -0
- package/src/render.js +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shaz Moghaddam, https://shazmoghaddam.github.io, https://linkedin.com/in/shazmoghaddam, London.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,421 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Bezel Charts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Charts that look finished the moment you add them.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/ShazMoghaddam/bezel-charts/releases)
|
|
6
|
+
[](https://www.npmjs.com/package/bezel-charts)
|
|
7
|
+
[](https://pypi.org/project/bezel-charts/)
|
|
8
|
+
[](https://github.com/ShazMoghaddam/bezel-charts/actions/workflows/ci.yml)
|
|
9
|
+
[](https://github.com/ShazMoghaddam/bezel-charts/actions/workflows/python.yml)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
|
|
12
|
+
Bezel Charts is a set of free, open-source web components: ten chart tiles drawn in pure SVG, so they stay sharp on a phone, a 4K screen or a printed report. Build one in the Studio, paste one line of HTML into any site, or save any chart as a static SVG for a README, Notion page or slide, where JavaScript can't run.
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
**[Open the Studio](https://shazmoghaddam.github.io/bezel-charts/site/#studio)** to build a chart and copy its code.
|
|
17
|
+
|
|
18
|
+
- **One tag per chart.** `<bezel-bars values="4,9,6">` is a finished, labelled, animated chart.
|
|
19
|
+
- **Yours to style.** Any colour, light and dark themes that follow the viewer's setting, rounder or squarer corners, with or without the bezel frame.
|
|
20
|
+
- **Made for READMEs.** Every chart exports as a clean static SVG, so a GitHub README can show a real chart.
|
|
21
|
+
- **Data science friendly.** Scatter plots with a least-squares trend line and Pearson's r, grouped series, goal lines and notes on key points.
|
|
22
|
+
- **Speaks your language.** Numbers and dates follow any locale, including Persian.
|
|
23
|
+
- **Light.** About 12 kB gzipped with no dependencies and no build step.
|
|
24
|
+
- **Accessible.** Every chart describes itself to screen readers, shows exact values on hover or tap, and respects reduced-motion settings.
|
|
25
|
+
- **Python too.** `pip install bezel-charts` draws the same charts straight from a pandas DataFrame, for notebooks, reports and READMEs.
|
|
26
|
+
|
|
27
|
+
A sibling of [Bezel](https://github.com/ShazMoghaddam/bezel), the clock, calendar and weather tiles.
|
|
28
|
+
|
|
29
|
+
## Contents
|
|
30
|
+
|
|
31
|
+
- [Quick start](#quick-start)
|
|
32
|
+
- [The Studio](#the-studio)
|
|
33
|
+
- [The charts](#the-charts)
|
|
34
|
+
- [Options for every chart](#options-for-every-chart)
|
|
35
|
+
- [Colours and dark mode](#colours-and-dark-mode)
|
|
36
|
+
- [Charts in a README](#charts-in-a-readme)
|
|
37
|
+
- [JavaScript API](#javascript-api)
|
|
38
|
+
- [Python](#python)
|
|
39
|
+
- [Frameworks](#frameworks)
|
|
40
|
+
- [Development](#development)
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<script type="module" src="https://cdn.jsdelivr.net/gh/ShazMoghaddam/bezel-charts@1/dist/bezel-charts.min.js"></script>
|
|
46
|
+
|
|
47
|
+
<bezel-bars heading="Commits this week" values="4,9,6,3,8,5,3" labels="M,T,W,T,F,S,S" stat="total"></bezel-bars>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`@1` always loads the latest 1.x release, so you get fixes without changing anything. To pin an exact version, use `@v1.1.1` instead.
|
|
51
|
+
|
|
52
|
+
That's all. Charts fill the width of whatever they sit in and redraw when it changes, so they work in a sidebar, a card grid or a full-width section without extra CSS.
|
|
53
|
+
|
|
54
|
+
Prefer a classic script tag? `dist/bezel-charts.iife.js` registers the same elements and exposes a `BezelCharts` global.
|
|
55
|
+
|
|
56
|
+
With npm:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
npm install bezel-charts
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import 'bezel-charts'; // registers every element
|
|
64
|
+
import { render, toSVG } from 'bezel-charts'; // draw charts as SVG strings, even in Node
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## The Studio
|
|
68
|
+
|
|
69
|
+
The [Studio](https://shazmoghaddam.github.io/bezel-charts/site/#studio) is the easiest way to make a chart. No data to hand? Press one of the three sample files under **Import a file** to see a finished chart straight away, or download them from [`site/samples`](site/samples). Then pick a chart type then work through the tabs while the preview updates beside them:
|
|
70
|
+
|
|
71
|
+
- **Data:** the title, your numbers, labels and series. **Import a file** (CSV, Excel .xlsx or JSON), drop one onto the Studio, or paste cells straight from Excel, Numbers or Google Sheets, and it fills the values, series names and labels in one go.
|
|
72
|
+
- **Style:** theme, frame, corners, height, colours and how numbers are written.
|
|
73
|
+
- **Extras:** headline number, goal line, notes on key points, and the options that suit that chart, such as a trend line on a scatter plot.
|
|
74
|
+
|
|
75
|
+
The Studio only offers settings that change the chart you're editing. When you're done, **Copy HTML** gives you the script line and your chart's tag, ready to paste into a page. Untick **Include the script line** when the page already has it. **Save SVG** gives you an image file, and **Copy link** opens your exact chart in the Studio for someone else. The **Customise** button under every example on the site opens that example in the Studio.
|
|
76
|
+
|
|
77
|
+
### Importing data
|
|
78
|
+
|
|
79
|
+
The Studio reads the first sheet of an Excel file, CSV (comma or semicolon separated, quoted cells are fine) and JSON. It looks at your columns and fills in the chart for you:
|
|
80
|
+
|
|
81
|
+
- A text column becomes the labels, and number columns become the values. Line and bar charts take up to six number columns as series; Bars switches to Grouped bars when there's more than one.
|
|
82
|
+
- A scatter plot uses the first two number columns for across and up. A text column with a few repeated values, like a team name, splits the points into groups.
|
|
83
|
+
- A heatmap uses a date column (2026-03-01, 01/03/2026 or Excel dates) and adds up the values for each day.
|
|
84
|
+
- Currency signs and thousands separators are understood, so `£1,200` reads as 1200.
|
|
85
|
+
|
|
86
|
+
Units are picked up too: a column called `Distance (km)` gets " km" after its numbers, and cells like `£1,200` or `45%` set the £ or %. Dates used as labels are shortened to "1 Jan".
|
|
87
|
+
|
|
88
|
+
The imported data stays when you switch chart type, so you can try it as a line, then bars, then a donut. **Start over with sample data** clears it. Files stay in your browser: nothing is uploaded.
|
|
89
|
+
|
|
90
|
+
No file to hand? The Studio has three to try, which you can also download from [`site/samples`](site/samples): monthly sales (grouped bars), study hours against exam scores (a scatter plot with trend lines) and a year of daily runs (a heatmap).
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
[
|
|
94
|
+
{ "month": "Jan", "sales": 1200, "costs": 800 },
|
|
95
|
+
{ "month": "Feb", "sales": 1500, "costs": 900 }
|
|
96
|
+
]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## The charts
|
|
100
|
+
|
|
101
|
+
Values can be separated with commas, spaces or new lines. Labels are separated with commas.
|
|
102
|
+
|
|
103
|
+
### `<bezel-spark>`
|
|
104
|
+
|
|
105
|
+
A sparkline with the latest value up front and its change since the start.
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<bezel-spark heading="London · 7 day high" values="17,16,15,15,13,12,14" labels="Mon,Tue,Wed,Thu,Fri,Sat,Sun" suffix="°C"></bezel-spark>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
| Attribute | Values | Default |
|
|
112
|
+
| ------------- | ------------------------------------------- | ------- |
|
|
113
|
+
| `values` | numbers | |
|
|
114
|
+
| `labels` | one per value, shown in tooltips | |
|
|
115
|
+
| `height` | chart height in px | `64` |
|
|
116
|
+
| `straight` | boolean, straight lines instead of curves | off |
|
|
117
|
+
|
|
118
|
+
Also takes `stat`, `goal`, `notes` and `show-values` from [the shared options](#options-for-every-chart). `stat` is `last` by default here.
|
|
119
|
+
|
|
120
|
+
### `<bezel-line>`
|
|
121
|
+
|
|
122
|
+
One or more lines over time, with an axis, a legend and notes on key moments. Separate series with `|`.
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<bezel-line heading="Site visitors"
|
|
126
|
+
values="420,460,510,580,620,900|380,400,430,450,470,490"
|
|
127
|
+
series="2026,2025" labels="Jan,Feb,Mar,Apr,May,Jun" notes="Jun:Launch day"></bezel-line>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
| Attribute | Values | Default |
|
|
131
|
+
| ------------- | ------------------------------------------- | ------- |
|
|
132
|
+
| `values` | numbers, series separated by `\|` | |
|
|
133
|
+
| `series` | series names for the legend | |
|
|
134
|
+
| `labels` | one per point, along the bottom | |
|
|
135
|
+
| `height` | chart height in px | `170` |
|
|
136
|
+
| `zero` | boolean, start the axis at zero | off |
|
|
137
|
+
| `straight` | boolean, straight lines instead of curves | off |
|
|
138
|
+
|
|
139
|
+
### `<bezel-bars>`
|
|
140
|
+
|
|
141
|
+
Bars, vertical or horizontal. Give it more than one series and you get **grouped bars**, side by side with a legend, or add `stacked` to pile each group's series into one bar.
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<bezel-bars heading="Revenue by quarter" values="182000,214000,236000,268000|151000,170000,198000,205000"
|
|
145
|
+
series="2026,2025" labels="Q1,Q2,Q3,Q4" prefix="£" compact></bezel-bars>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Attribute | Values | Default |
|
|
149
|
+
| ------------- | -------------------------------------------------------- | ------- |
|
|
150
|
+
| `values` | numbers, series separated by `\|` | |
|
|
151
|
+
| `series` | series names for the legend | |
|
|
152
|
+
| `labels` | one per bar or group | |
|
|
153
|
+
| `horizontal` | boolean, bars run left to right with labels beside them | off |
|
|
154
|
+
| `stacked` | boolean, with several series, piles them into one bar per group | off |
|
|
155
|
+
| `angle-labels` | boolean, slants labels under vertical bars when they don't fit side by side, so every one shows | off |
|
|
156
|
+
| `highlight` | `none` to stop the highest bar standing out | on |
|
|
157
|
+
| `height` | chart height in px (vertical bars) | `140` |
|
|
158
|
+
|
|
159
|
+
With a single series and several colours in `color`, each bar takes its own colour.
|
|
160
|
+
|
|
161
|
+
Stacked bars put positive parts above zero and negative parts below it. The tooltip lists every part and the total, and `show-values` writes the total at the end of each bar. Horizontal stacked bars always show the total.
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<bezel-bars heading="Land in a flood zone" values="18.4,16.6,0.1|6.6,24.8,33"
|
|
165
|
+
series="Low or higher,Very Low" labels="Barking and Dagenham,Newham,Southwark"
|
|
166
|
+
suffix="%" decimals="1" stacked angle-labels></bezel-bars>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Without `angle-labels`, labels that don't fit take turns, so only every few are shown. With it, they lean at 40 degrees and every one shows, shortened after 24 characters. The tooltip always has the full label.
|
|
170
|
+
|
|
171
|
+
### `<bezel-scatter>`
|
|
172
|
+
|
|
173
|
+
How two measures move together. Pass matching `x` and `y` lists; separate groups with `|`.
|
|
174
|
+
|
|
175
|
+
```html
|
|
176
|
+
<bezel-scatter heading="Study hours vs exam score"
|
|
177
|
+
x="2,3,4,5,6,7,8,9,10" y="48,52,55,63,70,72,75,82,88"
|
|
178
|
+
x-label="Hours studied" y-label="Score" suffix="%" trend></bezel-scatter>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| Attribute | Values | Default |
|
|
182
|
+
| ------------- | -------------------------------------------------------------- | ------- |
|
|
183
|
+
| `x`, `y` | numbers, one of each per point, groups separated by `\|` | |
|
|
184
|
+
| `series` | group names for the legend | |
|
|
185
|
+
| `labels` | point names, shown in tooltips | |
|
|
186
|
+
| `x-label`, `y-label` | axis titles | |
|
|
187
|
+
| `trend` | boolean, a least-squares line per group with Pearson's r | off |
|
|
188
|
+
| `zero` | boolean, start the y axis at zero | off |
|
|
189
|
+
| `height` | chart height in px | `210` |
|
|
190
|
+
|
|
191
|
+
`prefix`, `suffix` apply to y. Notes point at a point by its position, counting from 0 across all groups, or by its label.
|
|
192
|
+
|
|
193
|
+
### `<bezel-ring>`
|
|
194
|
+
|
|
195
|
+
A progress ring, or several nested like an activity tracker.
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<bezel-ring heading="Today" values="72,45,90" labels="Move,Focus,Sleep"></bezel-ring>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
| Attribute | Values | Default |
|
|
202
|
+
| ------------- | ------------------------------------------- | ------- |
|
|
203
|
+
| `values` | one number per ring | |
|
|
204
|
+
| `labels` | one per ring | |
|
|
205
|
+
| `max` | the value that fills a ring | `100` |
|
|
206
|
+
| `height` | chart height in px | `140` |
|
|
207
|
+
|
|
208
|
+
With `max` at 100 and no `prefix` or `suffix`, values show as percentages.
|
|
209
|
+
|
|
210
|
+
### `<bezel-donut>`
|
|
211
|
+
|
|
212
|
+
How a total splits into parts. More than six slices are grouped into "Other".
|
|
213
|
+
|
|
214
|
+
```html
|
|
215
|
+
<bezel-donut heading="Monthly budget" values="1200,420,210,160,140" labels="Rent,Food,Bills,Travel,Fun" prefix="£"></bezel-donut>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
| Attribute | Values | Default |
|
|
219
|
+
| -------------- | ---------------------------- | -------- |
|
|
220
|
+
| `values` | one number per slice | |
|
|
221
|
+
| `labels` | one per slice | |
|
|
222
|
+
| `center-label` | the word under the total | `Total` |
|
|
223
|
+
| `height` | chart height in px | `150` |
|
|
224
|
+
|
|
225
|
+
### `<bezel-gauge>`
|
|
226
|
+
|
|
227
|
+
A reading on a scale, optionally coloured by zone.
|
|
228
|
+
|
|
229
|
+
```html
|
|
230
|
+
<bezel-gauge heading="Server load" values="63" labels="CPU" zones="60,85" goal="80"></bezel-gauge>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
| Attribute | Values | Default |
|
|
234
|
+
| ------------- | --------------------------------------------------------------- | ------- |
|
|
235
|
+
| `values` | the reading | |
|
|
236
|
+
| `labels` | a caption under the reading | |
|
|
237
|
+
| `min`, `max` | the ends of the scale | `0`, `100` |
|
|
238
|
+
| `zones` | breakpoints, e.g. `60,85`: green, then amber, then red | |
|
|
239
|
+
| `goal` | a marker on the scale | |
|
|
240
|
+
| `height` | chart height in px | `130` |
|
|
241
|
+
|
|
242
|
+
Pass several colours in `color` to choose the zone colours yourself.
|
|
243
|
+
|
|
244
|
+
### `<bezel-progress>`
|
|
245
|
+
|
|
246
|
+
A progress bar, or stacked parts against a target.
|
|
247
|
+
|
|
248
|
+
```html
|
|
249
|
+
<bezel-progress heading="Sprint 14" values="18,6,3" labels="Done,In review,Blocked" max="34" goal="27"></bezel-progress>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
| Attribute | Values | Default |
|
|
253
|
+
| ------------- | -------------------------------------------- | ------- |
|
|
254
|
+
| `values` | one number, or one per segment | |
|
|
255
|
+
| `labels` | one per segment | |
|
|
256
|
+
| `max` | the full length of the bar | `100` for one value, otherwise the total |
|
|
257
|
+
| `goal` | a marker on the bar | |
|
|
258
|
+
|
|
259
|
+
### `<bezel-heatmap>`
|
|
260
|
+
|
|
261
|
+
Activity by day, GitHub style, with Monday at the top.
|
|
262
|
+
|
|
263
|
+
```html
|
|
264
|
+
<bezel-heatmap heading="Contributions" start="2026-01-01" values="0,3,5,1,0,2,8,4" suffix=" commits"></bezel-heatmap>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
| Attribute | Values | Default |
|
|
268
|
+
| ------------- | ------------------------------------------- | ------- |
|
|
269
|
+
| `values` | one number per day | |
|
|
270
|
+
| `start` | the first day, `YYYY-MM-DD` | so the last value is today |
|
|
271
|
+
|
|
272
|
+
On narrow screens the heatmap shows the most recent weeks that fit rather than shrinking the squares.
|
|
273
|
+
|
|
274
|
+
### `<bezel-chart>`
|
|
275
|
+
|
|
276
|
+
Any of the above, picked with `type`: `<bezel-chart type="donut" values="3,2,1">`. Handy when the chart type comes from data.
|
|
277
|
+
|
|
278
|
+
## Options for every chart
|
|
279
|
+
|
|
280
|
+
| Attribute | Values | Default |
|
|
281
|
+
| --------------- | --------------------------------------------------------------- | ------------- |
|
|
282
|
+
| `heading` | a title above the chart | |
|
|
283
|
+
| `prefix` | text before numbers, e.g. `£` | |
|
|
284
|
+
| `suffix` | text after numbers, e.g. `%` or ` kg` | |
|
|
285
|
+
| `decimals` | decimal places, `0` to `6` | automatic |
|
|
286
|
+
| `compact` | boolean, writes big numbers short, e.g. `12.5k` | off |
|
|
287
|
+
| `lang` | a locale for numbers and dates, e.g. `fr-FR`, `fa-IR` | `en-GB` |
|
|
288
|
+
| `theme` | `auto`, `dark`, `light` | `auto` |
|
|
289
|
+
| `frame` | `bezel`, `none` | `bezel` |
|
|
290
|
+
| `corners` | corner radius in px | `22` |
|
|
291
|
+
| `color` | one colour, or a comma list for series, slices or zones | theme palette |
|
|
292
|
+
| `background` | the tile colour | from theme |
|
|
293
|
+
| `stat` | a headline number: `last`, `total`, `average`, `max`, `min`, `none` | `none` |
|
|
294
|
+
| `delta` | with `stat="last"`: `inverse` when falling is good, `none` to hide the change | |
|
|
295
|
+
| `delta-label` | wording after the change | `since start` |
|
|
296
|
+
| `goal` | a dashed goal line, or a marker on gauges and progress bars | |
|
|
297
|
+
| `goal-label` | wording for the goal line | `Goal` |
|
|
298
|
+
| `notes` | `point:text` pairs separated by `;`, e.g. `Jun:Launch;9:Peak`. A point is a label or, if no label matches, a position counted from 0 | |
|
|
299
|
+
| `show-values` | boolean, writes each value on the chart | off |
|
|
300
|
+
| `width` | a fixed width in px instead of filling the container | |
|
|
301
|
+
| `no-tooltips` | boolean, turns off hover and tap values | off |
|
|
302
|
+
| `static` | boolean, skips the entrance animation | off |
|
|
303
|
+
|
|
304
|
+
Not every option suits every chart: `notes` works on spark, line and scatter; `show-values` on spark, line and bars; `goal-label` on spark, line, bars and scatter; `stat` on everything except the gauge and scatter. The Studio only shows what applies.
|
|
305
|
+
|
|
306
|
+
## Colours and dark mode
|
|
307
|
+
|
|
308
|
+
With `theme="auto"` (the default) a chart follows the viewer's light or dark setting, and redraws when it changes. A page can also force one by setting `data-theme="light"` or `data-theme="dark"` on `<html>`. Each theme has its own palette, picked to read well on its background:
|
|
309
|
+
|
|
310
|
+
| Theme | Palette |
|
|
311
|
+
| ----- | ------- |
|
|
312
|
+
| Dark | `#9FA1FF` `#F675A8` `#5EE6C8` `#FFC857` `#7CC4FF` `#FF8A5B` |
|
|
313
|
+
| Light | `#6E70F0` `#E2508C` `#16A884` `#D99A0E` `#2F8FE0` `#EE6431` |
|
|
314
|
+
|
|
315
|
+
Set `color` to use your own. Colours you set stay the same in both themes, so choose ones with enough contrast against both tile colours, or set `theme` to fix one.
|
|
316
|
+
|
|
317
|
+
## Charts in a README
|
|
318
|
+
|
|
319
|
+
GitHub doesn't run scripts in a README, but it shows SVG images. So:
|
|
320
|
+
|
|
321
|
+
1. Build your chart in the [Studio](https://shazmoghaddam.github.io/bezel-charts/site/#studio) and press **Save SVG**. The image is drawn at the preview width.
|
|
322
|
+
2. Commit the file to your repository, e.g. `docs/commits.svg`.
|
|
323
|
+
3. Add it to your README:
|
|
324
|
+
|
|
325
|
+
```md
|
|
326
|
+

|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
The SVG is self-contained: no scripts, no fonts to load, no tooltips. It uses the system font, so it looks at home on GitHub, in Notion and in slides.
|
|
330
|
+
|
|
331
|
+
## JavaScript API
|
|
332
|
+
|
|
333
|
+
Every chart element has:
|
|
334
|
+
|
|
335
|
+
- `toSVG()`: the chart as a static SVG string.
|
|
336
|
+
- `download(name)`: saves the chart as an `.svg` file.
|
|
337
|
+
- `options`: the current settings as a plain object.
|
|
338
|
+
- `refresh()`: redraws without replaying the animation.
|
|
339
|
+
|
|
340
|
+
It fires a `bezel-render` event each time it draws.
|
|
341
|
+
|
|
342
|
+
The module also exports `render(type, options)` and `toSVG(type, options)`, which return SVG strings without touching the DOM. They take the same option names as the attributes and run anywhere, including Node:
|
|
343
|
+
|
|
344
|
+
```js
|
|
345
|
+
import { toSVG } from 'bezel-charts';
|
|
346
|
+
import { writeFileSync } from 'node:fs';
|
|
347
|
+
|
|
348
|
+
writeFileSync('downloads.svg', toSVG('bars', { heading: 'Downloads', values: [120, 340, 410], labels: 'Jan,Feb,Mar', width: 480 }));
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Python
|
|
352
|
+
|
|
353
|
+
The [Python package](python/) draws the same charts from a pandas DataFrame, a NumPy array or a plain list, with no dependencies. Charts show up in Jupyter and save as static SVGs:
|
|
354
|
+
|
|
355
|
+
```sh
|
|
356
|
+
pip install bezel-charts
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
import bezel_charts as bc
|
|
361
|
+
|
|
362
|
+
bc.line(df, x="month", y=["sales", "costs"], heading="Sales", prefix="£")
|
|
363
|
+
bc.spark(df["signups"], heading="Signups").save("docs/signups.svg")
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
All ten charts are available, and the Python output matches the JavaScript byte for byte, checked in CI. See the [Python README](python/README.md).
|
|
367
|
+
|
|
368
|
+
## Frameworks
|
|
369
|
+
|
|
370
|
+
Bezel Charts are standard custom elements, so they work anywhere HTML does. Set attributes as strings.
|
|
371
|
+
|
|
372
|
+
**React 19+**
|
|
373
|
+
|
|
374
|
+
```jsx
|
|
375
|
+
<bezel-line values={data.join(',')} labels={months.join(',')} heading="Sales" />
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**Vue**
|
|
379
|
+
|
|
380
|
+
```vue
|
|
381
|
+
<bezel-donut :values="parts.join(',')" heading="Budget" />
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
In Vue, tell the compiler these are custom elements with `compilerOptions.isCustomElement: (tag) => tag.startsWith('bezel-')`.
|
|
385
|
+
|
|
386
|
+
## Development
|
|
387
|
+
|
|
388
|
+
```sh
|
|
389
|
+
git clone https://github.com/ShazMoghaddam/bezel-charts.git
|
|
390
|
+
cd bezel-charts
|
|
391
|
+
npm install
|
|
392
|
+
npm run serve # http://localhost:4173/site/
|
|
393
|
+
npm run check # build, run the tests, and check the size budget
|
|
394
|
+
npm run test:browser # click through every Studio control in a real browser (needs Python and Playwright)
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
```
|
|
398
|
+
src/
|
|
399
|
+
core/ shared helpers, the bezel frame and the colour themes
|
|
400
|
+
charts/ one file per chart type: options in, SVG string out
|
|
401
|
+
render.js picks the chart and returns its SVG
|
|
402
|
+
element.js the custom element: sizing, theme, tooltips and export
|
|
403
|
+
site/ the website and Studio, with sample files in site/samples
|
|
404
|
+
python/ the Python package, with parity tests against the JavaScript
|
|
405
|
+
docs/ the README image and social preview, with the pages that render them
|
|
406
|
+
test/ node:test suites, plus a Playwright browser test
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Each chart is a pure function that takes its options and a theme and returns an SVG string, which keeps them easy to test and lets the same code run in Node. The bundle has a 14 kB gzip budget, checked in CI. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
410
|
+
|
|
411
|
+
## Security
|
|
412
|
+
|
|
413
|
+
Found a security problem? Please report it privately through GitHub rather than in an issue. [SECURITY.md](SECURITY.md) explains how, and which versions get fixes.
|
|
414
|
+
|
|
415
|
+
## Credits
|
|
416
|
+
|
|
417
|
+
Typeface Geist (SIL Open Font License) on the website; the charts themselves use the system font. Designed and built by [Shaz Moghaddam](https://shazmoghaddam.github.io), London.
|
|
418
|
+
|
|
419
|
+
## Licence
|
|
420
|
+
|
|
421
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*! Bezel Charts v1.1.1 | MIT | github.com/ShazMoghaddam/bezel-charts */
|
|
2
|
+
var BezelCharts=(()=>{var le=Object.defineProperty;var De=Object.getOwnPropertyDescriptor;var Ge=Object.getOwnPropertyNames;var Pe=Object.prototype.hasOwnProperty;var Ue=(t,e)=>{for(var r in e)le(t,r,{get:e[r],enumerable:!0})},He=(t,e,r,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let l of Ge(e))!Pe.call(t,l)&&l!==r&&le(t,l,{get:()=>e[l],enumerable:!(i=De(e,l))||i.enumerable});return t};var Ie=t=>He(le({},"__esModule",{value:!0}),t);var Je={};Ue(Je,{BezelChart:()=>Vt,render:()=>Yt,themes:()=>Ht,toSVG:()=>Ae,types:()=>Fe,version:()=>Qe});var Ht={dark:{tile:"#15151d",bezel:"#000000",fg:"#f2f1f7",muted:"#8c8aa0",grid:"#2a2935",up:"#5EE6C8",down:"#F675A8",palette:["#9FA1FF","#F675A8","#5EE6C8","#FFC857","#7CC4FF","#FF8A5B"]},light:{tile:"#ffffff",bezel:"#000000",fg:"#17161f",muted:"#6b6980",grid:"#e8e6ef",up:"#12a383",down:"#d63f7c",palette:["#6E70F0","#E2508C","#16A884","#D99A0E","#2F8FE0","#EE6431"]}};var tt="ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif",lt="ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",$e=0;function It(t){return"bzc"+t+(++$e).toString(36)+Math.random().toString(36).slice(2,6)}function ut(t){return String(t??"").replace(/[&<>"']/g,function(e){return{"&":"&","<":"<",">":">",'"':""","'":"'"}[e]})}function a(t){return Math.round(t*100)/100}function X(t,e,r){return Math.max(e,Math.min(r,t))}function _(t,e){var r=parseFloat(t);return isFinite(r)?r:e}function O(t){return t!=null&&t!==""}function ct(t){return Array.isArray(t)?t.map(function(e){var r=+e;return isFinite(r)?r:0}):O(t)?String(t).split(/[\s,;]+/).filter(Boolean).map(function(e){var r=+e;return isFinite(r)?r:0}):[]}function Z(t){return Array.isArray(t)?t.map(String):O(t)?String(t).split(",").map(function(e){return e.trim()}):[]}function Ct(t){return Array.isArray(t)?Array.isArray(t[0])?t.map(ct):[ct(t)]:O(t)?String(t).split("|").map(ct).filter(function(e){return e.length}):[]}function Lt(t){return t.reduce(function(e,r){return e+r},0)}function vt(t,e,r){return String(t).length*e*(r?.6:.56)}function Nt(t,e){return t=String(t),t.length>e?t.slice(0,Math.max(1,e-1))+"\u2026":t}function J(t){return t===!0||t===""||t==="true"||t==="1"}function g(t,e,r){if(!isFinite(t))return"\u2013";var i=Math.abs(t),l;O(e.decimals)?l=X(parseInt(e.decimals,10)||0,0,6):l=t%1===0?0:i<10?2:i<100?1:0;var n={maximumFractionDigits:l,minimumFractionDigits:O(e.decimals)?l:0};(J(e.compact)||r)&&i>=1e4&&(n={notation:"compact",maximumFractionDigits:1});var s;try{s=new Intl.NumberFormat(e.lang||"en-GB",n).format(i)}catch{s=String(a(i))}return(t<0?"\u2212":"")+(e.prefix||"")+s+(e.suffix||"")}function Qt(t,e,r){if(t===e)if(t===0)e=1;else{var i=Math.abs(t)*.1;t-=i,e+=i}for(var l=(e-t)/(r||4),n=Math.pow(10,Math.floor(Math.log10(l))),s=l/n,o=(s<=1?1:s<=2?2:s<=5?5:10)*n,c=Math.floor(t/o)*o,A=Math.ceil(e/o)*o,x=[],y=c;y<=A+o/2;y+=o)x.push(+y.toFixed(10));return{lo:c,hi:A,ticks:x}}function jt(t,e){if(!t.length)return"";for(var r="M"+a(t[0][0])+","+a(t[0][1]),i=1;i<t.length;i++){var l=t[i-1],n=t[i];if(e)r+="L"+a(n[0])+","+a(n[1]);else{var s=(l[0]+n[0])/2;r+="C"+a(s)+","+a(l[1])+" "+a(s)+","+a(n[1])+" "+a(n[0])+","+a(n[1])}}return r}function M(t,e,r,i){return'<text x="'+a(t)+'" y="'+a(e)+'" '+(i||"")+">"+ut(r)+"</text>"}function H(t,e,r,i){return'fill="'+(i||t.muted)+'" font-family="'+lt+'" font-size="'+(e||10)+'"'+(r?" "+r:"")}function pt(t,e,r,i,l){return'<rect class="bzc-hit" x="'+a(t)+'" y="'+a(e)+'" width="'+a(Math.max(1,r))+'" height="'+a(Math.max(1,i))+'" fill="#000" fill-opacity="0" data-tip="'+ut(l)+'"/>'}function ot(t,e,r){return t.animate?' class="'+e+'"'+(r?' style="animation-delay:'+a(r)+'s"':""):""}function at(t,e){var r=Z(t.color).filter(Boolean);return r.length?r:e.palette}function nt(t,e,r,i,l){return M(e+i/2,r+l/2+4,"No data yet",'text-anchor="middle" fill="'+t.muted+'" font-family="'+tt+'" font-size="13"')}function $t(t,e){return O(t.notes)?String(t.notes).split(";").map(function(r){var i=r.indexOf(":");if(i<0)return null;var l=r.slice(0,i).trim(),n=r.slice(i+1).trim(),s=e.map(String).indexOf(l);return s<0&&String(parseInt(l,10))===l&&(s=parseInt(l,10)),s>=0&&n?{i:s,text:n}:null}).filter(Boolean):[]}function At(t,e,r,i,l){var n=(O(t.goalLabel)?t.goalLabel:"Goal")+" "+g(_(t.goal,0),t);return'<line x1="'+a(r)+'" x2="'+a(i)+'" y1="'+a(l)+'" y2="'+a(l)+'" stroke="'+e.fg+'" stroke-opacity=".55" stroke-width="1.25" stroke-dasharray="4 4"/>'+M(i,l-5,n,H(e,10,'text-anchor="end"'))}function xt(t,e,r){var i=String(O(e.stat)?e.stat:r||"").toLowerCase();if(!i||i==="none"||!t.length)return null;var l=Lt(t),n={last:t[t.length-1],total:l,sum:l,average:l/t.length,avg:l/t.length,max:Math.max.apply(null,t),min:Math.min.apply(null,t)};if(!(i in n))return null;var s=null;return i==="last"&&t.length>1&&e.delta!=="none"&&(s=t[t.length-1]-t[0]),{val:n[i],delta:s}}function Rt(t,e,r,i,l,n){var s=i,o=l,c="";return t.forEach(function(A,x){var y=13+vt(A,12)+16;s>i&&s+y-16>i+n&&(s=i,o+=20),c+='<circle cx="'+a(s+4)+'" cy="'+a(o+6)+'" r="4" fill="'+e[x%e.length]+'"/>'+M(s+13,o+10,A,'fill="'+r.fg+'" font-family="'+tt+'" font-size="12"'),s+=y}),{svg:c,height:o-l+24}}function Ee(t){return Math.max.apply(null,t.map(function(e){return vt(e,10,!0)}))+10}function Bt(t){var e=Math.round(X(_(t.width,360),160,2400)),r=t.frame==="none"?0:4,i=t.frame==="none"?8:20;return{W:e,b:r,P:i,w:e-2*(r+i)}}var Ye=".bzc-a{animation:bzc-in .6s ease both}.bzc-d{animation:bzc-draw 1.1s cubic-bezier(.3,.7,.2,1) both}.bzc-g{transform-box:fill-box;transform-origin:50% 100%;animation:bzc-grow .7s cubic-bezier(.2,.8,.2,1) both}.bzc-gn{transform-box:fill-box;transform-origin:50% 0;animation:bzc-grow .7s cubic-bezier(.2,.8,.2,1) both}.bzc-gx{transform-box:fill-box;transform-origin:0 50%;animation:bzc-growx .8s cubic-bezier(.2,.8,.2,1) both}.bzc-s{animation:bzc-sweep 1.1s cubic-bezier(.2,.8,.2,1) both}@keyframes bzc-in{from{opacity:0}}@keyframes bzc-draw{from{stroke-dashoffset:1}}@keyframes bzc-grow{from{transform:scaleY(0)}}@keyframes bzc-growx{from{transform:scaleX(0)}}@keyframes bzc-sweep{from{stroke-dasharray:0 100}}@media (prefers-reduced-motion:reduce){[class^=bzc-]{animation:none!important}}";function it(t,e,r,i,l){var n=Bt(t),s=n.W,o=n.b,c=n.P,A=n.w,x=X(_(t.corners,22),0,60),y=o+c,u="";O(t.heading)&&(u+=M(o+c,y+10,String(t.heading).toUpperCase(),H(e,11,'font-weight="600" letter-spacing="1.3"')),y+=24);var S=l&&l.stat;if(S){if(u+=M(o+c,y+30,g(S.val,t),'fill="'+e.fg+'" font-family="'+tt+'" font-size="32" font-weight="800" letter-spacing="-0.5"'),S.delta!=null){var F=S.delta>0,C=S.delta===0,T=t.delta==="inverse"?!F:F,k=C?e.muted:T?e.up:e.down,E=(C?"\u25CF ":F?"\u25B2 ":"\u25BC ")+g(Math.abs(S.delta),t)+" "+(O(t.deltaLabel)?t.deltaLabel:"since start");u+=M(o+c+A,y+28,E,'text-anchor="end" fill="'+k+'" font-family="'+lt+'" font-size="11" font-weight="600"')}y+=44}var f=i(o+c,y,A,r),R=Math.round(y+r+c+o),v="";t.frame!=="none"?v='<rect width="'+s+'" height="'+R+'" rx="'+x+'" fill="'+e.bezel+'"/><rect x="'+o+'" y="'+o+'" width="'+(s-2*o)+'" height="'+(R-2*o)+'" rx="'+Math.max(0,x-o)+'" fill="'+(t.background||e.tile)+'"/>':O(t.background)&&(v='<rect width="'+s+'" height="'+R+'" rx="'+x+'" fill="'+t.background+'"/>');var b=(O(t.heading)?t.heading+": ":"")+(l&&l.summary||"chart");return'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 '+s+" "+R+'" width="'+s+'" height="'+R+'" role="img" aria-label="'+ut(b)+'"><title>'+ut(b)+"</title>"+(t.animate?"<style>"+Ye+"</style>":"")+v+u+f+"</svg>"}function oe(t,e){var r=ct(t.values),i=at(t,e)[0],l=_(t.height,64),n=Z(t.labels),s=$t(t,n),o=xt(r,t,"last");return it(t,e,l,function(c,A,x,y){if(!r.length)return nt(e,c,A,x,y);var u=r.length===1?[r[0],r[0]]:r,S=Math.min.apply(null,u),F=Math.max.apply(null,u);O(t.goal)&&(S=Math.min(S,+t.goal),F=Math.max(F,+t.goal));var C=(F-S||Math.abs(F)||1)*.12;S-=C,F+=C;var T=function(m){return c+m*x/(u.length-1)},k=function(m){return A+y-(m-S)/(F-S)*y},E=u.map(function(m,h){return[T(h),k(m)]}),f=jt(E,J(t.straight)),R=It("g"),v=E[E.length-1],b='<defs><linearGradient id="'+R+'" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="'+i+'" stop-opacity=".32"/><stop offset="1" stop-color="'+i+'" stop-opacity="0"/></linearGradient></defs>';[1/3,2/3].forEach(function(m){b+='<line x1="'+c+'" x2="'+(c+x)+'" y1="'+a(A+y*m)+'" y2="'+a(A+y*m)+'" stroke="'+e.grid+'" stroke-dasharray="2 4"/>'}),b+='<path d="'+f+"L"+a(v[0])+","+(A+y)+"L"+c+","+(A+y)+'Z" fill="url(#'+R+')"'+ot(t,"bzc-a")+"/>",b+='<path d="'+f+'" fill="none" stroke="'+i+'" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" pathLength="1" style="stroke-dasharray:1"'+ot(t,"bzc-d")+"/>",O(t.goal)&&(b+=At(t,e,c,c+x,k(+t.goal))),s.forEach(function(m){if(!(m.i>=r.length)){var h=E[m.i];b+='<circle cx="'+a(h[0])+'" cy="'+a(h[1])+'" r="3.5" fill="'+e.fg+'"/>'+M(X(h[0],c+30,c+x-30),h[1]-9,m.text,H(e,10,'text-anchor="middle"',e.fg))}}),b+='<circle cx="'+a(v[0])+'" cy="'+a(v[1])+'" r="9" fill="'+i+'" fill-opacity=".25"/><circle cx="'+a(v[0])+'" cy="'+a(v[1])+'" r="4.5" fill="'+i+'"/>',J(t.showValues)&&E.forEach(function(m,h){h<r.length&&(b+=M(m[0],m[1]-8,g(r[h],t,!0),H(e,9,'text-anchor="middle"')))});var N=r.length>1?x/(r.length-1):x;return r.forEach(function(m,h){b+=pt(T(h)-N/2,A-6,N,y+12,(n[h]||"#"+(h+1))+": "+g(m,t))}),b},{stat:o,summary:r.length?"latest "+g(r[r.length-1],t):"no data"})}function se(t,e){var r=Ct(t.values),i=Z(t.series),l=Z(t.labels),n=at(t,e),s=_(t.height,170),o=Math.max.apply(null,[0].concat(r.map(function(y){return y.length}))),c=$t(t,l),A=r.length>1||i.length>0,x=xt(r[0]||[],t,"");return it(t,e,s,function(y,u,S,F){if(!o)return nt(e,y,u,S,F);var C="",T=u;if(A){var k=Rt(r.map(function(w,z){return i[z]||"Series "+(z+1)}),n,e,y,T,S);C+=k.svg,T+=k.height}var E=[].concat.apply([],r),f=Math.min.apply(null,E),R=Math.max.apply(null,E);O(t.goal)&&(f=Math.min(f,+t.goal),R=Math.max(R,+t.goal)),J(t.zero)&&(f=Math.min(0,f),R=Math.max(0,R));var v=Qt(f,R,4),b=v.ticks.map(function(w){return g(w,t,!0)}),N=Math.max.apply(null,b.map(function(w){return vt(w,10,!0)}))+10,m=l.length?20:4,h=y+N,I=S-N,p=F-(T-u)-m,D=function(w){return o===1?h+I/2:h+w*I/(o-1)},L=function(w){return T+p-(w-v.lo)/(v.hi-v.lo)*p};if(v.ticks.forEach(function(w,z){var $=L(w);C+='<line x1="'+a(h)+'" x2="'+a(h+I)+'" y1="'+a($)+'" y2="'+a($)+'" stroke="'+e.grid+'"'+(w===0?"":' stroke-dasharray="2 4"')+"/>"+M(h-8,$+3.5,b[z],H(e,10,'text-anchor="end"'))}),l.length){for(var U=Math.max(1,Math.ceil(o*(Math.max.apply(null,l.map(function(w){return vt(w,10,!0)}))+10)/I)),W=0;W<o;W+=U)if(l[W]!=null){var q=o>1&&W===0?"start":o>1&&W===o-1?"end":"middle";C+=M(D(W),T+p+15,l[W],H(e,10,'text-anchor="'+q+'"'))}}c.forEach(function(w){if(!(w.i>=o)){var z=D(w.i);C+='<line x1="'+a(z)+'" x2="'+a(z)+'" y1="'+a(T+12)+'" y2="'+a(T+p)+'" stroke="'+e.fg+'" stroke-opacity=".4" stroke-dasharray="3 3"/>'+M(X(z,h+30,h+I-30),T+8,w.text,H(e,10,'text-anchor="middle"',e.fg))}}),r.forEach(function(w,z){var $=n[z%n.length],G=w.map(function(mt,B){return[D(B),L(mt)]});G.length===1&&(G=[[h,G[0][1]],[h+I,G[0][1]]]);var dt=jt(G,J(t.straight));if(z===0){var ht=It("g"),et=G[G.length-1];C+='<defs><linearGradient id="'+ht+'" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="'+$+'" stop-opacity=".26"/><stop offset="1" stop-color="'+$+'" stop-opacity="0"/></linearGradient></defs><path d="'+dt+"L"+a(et[0])+","+a(T+p)+"L"+a(G[0][0])+","+a(T+p)+'Z" fill="url(#'+ht+')"'+ot(t,"bzc-a")+"/>"}C+='<path d="'+dt+'" fill="none" stroke="'+$+'" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" pathLength="1" style="stroke-dasharray:1'+(t.animate?";animation-delay:"+z*.15+"s":"")+'"'+(t.animate?' class="bzc-d"':"")+"/>";var ft=G[G.length-1];C+='<circle cx="'+a(ft[0])+'" cy="'+a(ft[1])+'" r="4" fill="'+$+'" stroke="'+(t.background||e.tile)+'" stroke-width="2"/>',J(t.showValues)&&w.forEach(function(mt,B){C+=M(D(B),L(mt)-8,g(mt,t,!0),H(e,9,'text-anchor="middle"'))})}),O(t.goal)&&(C+=At(t,e,h,h+I,L(+t.goal)));for(var st=o>1?I/(o-1):I,Q=0;Q<o;Q++){var d=l[Q]||"#"+(Q+1);r.forEach(function(w,z){Q<w.length&&(d+=`
|
|
3
|
+
`+(A?(i[z]||"Series "+(z+1))+": ":"")+g(w[Q],t))}),C+=pt(D(Q)-st/2,T,st,p,d)}return C},{stat:x,summary:r.length+" series over "+o+" points"})}var Le=40,Oe=24;function fe(t,e){for(var r=Ct(t.values),i=Z(t.series),l=Z(t.labels),n=at(t,e),s=r.length,o=Math.max.apply(null,[0].concat(r.map(function(d){return d.length}))),c=s>1,A=J(t.horizontal),x=c&&J(t.stacked),y=!c&&Z(t.color).filter(Boolean).length>1,u=r[0]||[],S=[].concat.apply([],r),F=function(d,w){return r.reduce(function(z,$){return d<$.length&&$[d]*w>0?z+$[d]:z},0)},C=[],T=[],k=[],E=0;x&&E<o;E++)T.push(F(E,1)),k.push(F(E,-1)),C.push(T[E]+k[E]);var f=x?T.concat(k):S,R=f.length?Math.max.apply(null,f):0,v=function(d){return i[d]||"Series "+(d+1)},b=function(d,w){return c?n[d%n.length]:y?n[w%n.length]:n[0]},N=function(d){return c||y||t.highlight==="none"||d===R?1:.5},m=function(d){return l[d]||"#"+(d+1)},h=function(d){return c?m(d)+r.map(function(w,z){return d<w.length?`
|
|
4
|
+
`+v(z)+": "+g(w[d],t):""}).join("")+(x?`
|
|
5
|
+
Total: `+g(C[d],t):""):m(d)+": "+g(u[d],t)},I=Bt(t),p=c||i.length?Rt(r.map(function(d,w){return v(w)}),n,e,0,0,I.w).height:0,D=x?1:s,L=c&&!x,U=L?10:14,W=3,q=A?D*U+(D-1)*W+(L?14:16):0,st=A?p+Math.max(1,o)*q-6:_(t.height,140)+p,Q=u.indexOf(Math.max.apply(null,u.length?u:[0]));return it(t,e,st,function(d,w,z,$){if(!o)return nt(e,d,w,z,$);var G="",dt=w;p&&(G+=Rt(r.map(function(P,j){return v(j)}),n,e,d,w,z).svg,dt+=p);var ht=Math.min(0,Math.min.apply(null,f)),et=Math.max(0,R);if(O(t.goal)&&(ht=Math.min(ht,+t.goal),et=Math.max(et,+t.goal)),et===ht&&(et=ht+1),A){for(var ft=l.length?Math.min(z*.4,Math.max.apply(null,l.map(function(P){return vt(P,12)}))+10):0,mt=x?[C.map(function(P){return g(P,t)})]:r.map(function(P){return P.map(function(j){return g(j,t)})}),B=Math.max.apply(null,[].concat.apply([],mt).map(function(P){return vt(P,L?10:11,!0)}))+10,Y=d+ft,V=z-ft-B,kt=Math.max(et,0)||1,K=0;K<o;K++){var bt=dt+K*q;l[K]!=null&&(G+=M(d,bt+(q-6)/2+4,Nt(l[K],Math.floor(ft/6.7)),'fill="'+e.fg+'" font-family="'+tt+'" font-size="12"')),x?(G+=Dt(K,Y,bt+5,V,kt),G+=M(d+z,bt+5+U/2+3.8,mt[0][K],'text-anchor="end" fill="'+e.fg+'" font-family="'+lt+'" font-size="11" font-weight="600"')):r.forEach(function(P,j){if(!(K>=P.length)){var rt=P[K],yt=bt+(c?2:5)+j*(U+W),wt=Math.max(0,rt)/kt*V;c||(G+='<rect x="'+a(Y)+'" y="'+a(yt)+'" width="'+a(V)+'" height="'+U+'" rx="'+U/2+'" fill="'+e.grid+'" fill-opacity=".6"/>'),wt>0&&(G+='<rect x="'+a(Y)+'" y="'+a(yt)+'" width="'+a(Math.max(U,wt))+'" height="'+U+'" rx="'+U/2+'" fill="'+b(j,K)+'" fill-opacity="'+N(rt)+'"'+ot(t,"bzc-gx",K*.06+j*.03)+"/>"),G+=M(d+z,yt+U/2+3.8,mt[j][K],'text-anchor="end" fill="'+e.fg+'" font-family="'+lt+'" font-size="'+(c?10:11)+'" font-weight="600"')}}),G+=pt(d,bt,z,q-6,h(K))}if(O(t.goal)&&+t.goal>0){var Ot=Y+ +t.goal/kt*V;G+='<line x1="'+a(Ot)+'" x2="'+a(Ot)+'" y1="'+a(dt-2)+'" y2="'+a(w+$+2)+'" stroke="'+e.fg+'" stroke-opacity=".55" stroke-dasharray="3 3"/>'}return G}function Dt(P,j,rt,yt,wt){var zt='<rect x="'+a(j)+'" y="'+a(rt)+'" width="'+a(yt)+'" height="'+U+'" rx="'+U/2+'" fill="'+e.grid+'" fill-opacity=".6"/>',Pt=0;return r.forEach(function(qt,St){var Ut=P<qt.length?qt[P]:0,Et=Math.max(0,Ut)/wt*yt;if(!(Et<=0)){var Ft=Pt>0?1.5:0;zt+='<rect x="'+a(j+Pt+Ft)+'" y="'+a(rt)+'" width="'+a(Math.max(2,Et-Ft))+'" height="'+U+'" rx="'+a(Math.min(3,Et/2))+'" fill="'+b(St,P)+'"'+ot(t,"bzc-gx",P*.06+St*.03)+"/>",Pt+=Et}}),zt}var xe=l.length?Math.max.apply(null,l.map(function(P){return vt(P,10,!0)})):0,te=l.length?Math.max(1,Math.ceil(o*(xe+8)/z)):1,Wt=J(t.angleLabels)&&te>1,ye=Le*Math.PI/180,be=function(P){return Wt?Nt(P,Oe):P};if(Wt){var Ce=z/o,Gt=0;l.forEach(function(P,j){Gt=Math.max(Gt,Math.cos(ye)*vt(be(P),10,!0)+4-(j+.5)*Ce)}),Gt=Math.min(Gt,z*.25),d+=Gt,z-=Gt,te=1}var Me=J(t.showValues)&&ht<0?14:0,Ne=Wt?Math.ceil(Math.sin(ye)*Math.min(xe,vt("x".repeat(Oe),10,!0)))+18:20,Re=(l.length?Ne:2)+Me,ke=J(t.showValues)?16:4,ee=$-(dt-w)-Re-ke,we=dt+ke,Mt=function(P){return we+ee-(P-ht)/(et-ht)*ee},Xt=z/o,_t=Math.min(Xt*(L?.78:.62),L?s*30:56),ze=L?Math.min(3,_t/s*.15):0,Tt=x?_t:(_t-ze*(s-1))/s,Kt=Mt(0);G+='<line x1="'+d+'" x2="'+(d+z)+'" y1="'+a(Kt)+'" y2="'+a(Kt)+'" stroke="'+e.grid+'"/>';for(var gt=0;gt<o;gt++){var re=d+gt*Xt+(Xt-_t)/2;if(x?G+=Be(gt,re):r.forEach(function(P,j){if(!(gt>=P.length)){var rt=P[gt],yt=re+j*(Tt+ze),wt=Math.min(Mt(rt),Kt),zt=Math.abs(Mt(rt)-Kt);zt>.5&&(G+='<rect x="'+a(yt)+'" y="'+a(wt)+'" width="'+a(Tt)+'" height="'+a(zt)+'" rx="'+a(Math.min(6,Tt/2,zt/2))+'" fill="'+b(j,gt)+'" fill-opacity="'+N(rt)+'"'+ot(t,rt<0?"bzc-gn":"bzc-g",gt*.05+j*.03)+"/>"),J(t.showValues)&&(G+=M(yt+Tt/2,rt<0?Mt(rt)+12:Mt(rt)-5,g(rt,t,!0),'text-anchor="middle" fill="'+e.fg+'" font-family="'+lt+'" font-size="'+(c?9:10)+'" font-weight="600"'))}}),l[gt]!=null&>%te===0){var ae=re+_t/2,ne=we+ee+Me+(Wt?12:15);G+=Wt?M(ae,ne,be(l[gt]),H(e,10,'text-anchor="end" transform="rotate(-'+Le+" "+a(ae)+" "+a(ne)+')"')):M(ae,ne,l[gt],H(e,10,'text-anchor="middle"'))}G+=pt(d+gt*Xt,dt,Xt,$-(dt-w),h(gt))}return O(t.goal)&&(G+=At(t,e,d,d+z,Mt(+t.goal))),G;function Be(P,j){var rt="",yt=0,wt=0;if(r.forEach(function(Pt,qt){var St=P<Pt.length?Pt[P]:0;if(St){var Ut=St>0?yt:wt,Et=Ut+St;St>0?yt=Et:wt=Et;var Ft=Mt(Math.max(Ut,Et)),ie=Mt(Math.min(Ut,Et)),Zt=ie-Ft,Se=Ut!==0?Math.min(1.5,Zt/2):0;St>0?ie-=Se:Ft+=Se,Zt=ie-Ft,Zt>.5&&(rt+='<rect x="'+a(j)+'" y="'+a(Ft)+'" width="'+a(Tt)+'" height="'+a(Zt)+'" rx="'+a(Math.min(3,Tt/2,Zt/2))+'" fill="'+b(qt,P)+'"'+ot(t,St<0?"bzc-gn":"bzc-g",P*.05+qt*.03)+"/>")}}),J(t.showValues)){var zt=C[P];rt+=M(j+Tt/2,zt<0?Mt(k[P])+12:Mt(T[P])-5,g(zt,t,!0),'text-anchor="middle" fill="'+e.fg+'" font-family="'+lt+'" font-size="10" font-weight="600"')}return rt}},{stat:xt(u,t,""),summary:o?x?s+" series stacked across "+o+" groups":c?s+" series across "+o+" groups":"highest "+(l[Q]||"")+" "+g(u[Q],t):"no data"})}function ce(t,e){var r=ct(t.values),i=Z(t.labels),l=at(t,e),n=_(t.max,100)||100,s=_(t.height,140),o=!O(t.prefix)&&!O(t.suffix)&&n===100,c=function(x){return g(x,t)+(o?"%":"")},A=xt(r,t,"");return it(t,e,s,function(x,y,u,S){if(!r.length)return nt(e,x,y,u,S);var F="",C=r.length===1,T=Math.min(S,C?u:u*.55),k=C?x+u/2:x+T/2,E=y+S/2,f=C?T*.12:X(T*.42/(r.length*1.35),5,16),R=f*.38;if(r.forEach(function(m,h){var I=T/2-f/2-h*(f+R);if(!(I<=f/2)){var p=X(m/n*100,0,100),D=l[h%l.length];F+='<circle cx="'+a(k)+'" cy="'+a(E)+'" r="'+a(I)+'" fill="none" stroke="'+D+'" stroke-opacity=".16" stroke-width="'+a(f)+'"/>',p>0&&(F+='<circle cx="'+a(k)+'" cy="'+a(E)+'" r="'+a(I)+'" fill="none" stroke="'+D+'" stroke-width="'+a(f)+'" stroke-linecap="round" pathLength="100" transform="rotate(-90 '+a(k)+" "+a(E)+')" style="stroke-dasharray:'+a(p)+" 100"+(t.animate?";animation-delay:"+h*.12+"s":"")+'"'+(t.animate?' class="bzc-s"':"")+"/>"),F+='<circle cx="'+a(k)+'" cy="'+a(E)+'" r="'+a(I+f/2)+'" fill="#000" fill-opacity="0" class="bzc-hit" data-tip="'+ut((i[h]||"#"+(h+1))+": "+c(m)+(o?"":" of "+g(n,t)))+'"/>'}}),C)F+=M(k,E+T*.08,c(r[0]),'text-anchor="middle" fill="'+e.fg+'" font-family="'+tt+'" font-size="'+a(T*.22)+'" font-weight="800"'),i[0]&&(F+=M(k,E+T*.08+16,i[0],H(e,10,'text-anchor="middle"')));else{var v=x+T+22,b=Math.min(26,S/r.length),N=E-b*r.length/2;r.forEach(function(m,h){var I=N+h*b+b/2;F+='<circle cx="'+a(v+4)+'" cy="'+a(I)+'" r="4" fill="'+l[h%l.length]+'"/>'+M(v+14,I+4,Nt(i[h]||"#"+(h+1),Math.floor((x+u-v-60)/6.7)),'fill="'+e.fg+'" font-family="'+tt+'" font-size="12"')+M(x+u,I+4,c(m),'text-anchor="end" fill="'+e.fg+'" font-family="'+lt+'" font-size="11" font-weight="600"')})}return F},{stat:A,summary:r.map(function(x,y){return(i[y]?i[y]+" ":"")+c(x)}).join(", ")})}function ue(t,e){var r=ct(t.values).map(function(u){return Math.max(0,u)}),i=Z(t.labels),l=at(t,e),n=r.slice(),s=i.slice();if(n.length>6){var o=n.map(function(u,S){return S}).sort(function(u,S){return r[S]-r[u]}),c=o.slice(0,5).sort(function(u,S){return u-S});n=c.map(function(u){return r[u]}),s=c.map(function(u){return i[u]||"#"+(u+1)}),n.push(Lt(o.slice(5).map(function(u){return r[u]}))),s.push("Other")}var A=Lt(n),x=_(t.height,150),y=xt(n,t,"");return it(t,e,x,function(u,S,F,C){if(!A)return nt(e,u,S,F,C);var T="",k=Math.min(C,F*.5),E=u+k/2,f=S+C/2,R=k*.16,v=(k-R)/2,b=0,N=n.filter(Boolean).length>1?.9:0;n.forEach(function(p,D){var L=p/A*100;if(!(L<=0)){var U=Math.max(.01,L-N);T+='<circle cx="'+a(E)+'" cy="'+a(f)+'" r="'+a(v)+'" fill="none" stroke="'+l[D%l.length]+'" stroke-width="'+a(R)+'" pathLength="100" stroke-dasharray="'+a(U)+" "+a(100-U)+'" stroke-dashoffset="'+a(-b)+'" transform="rotate(-90 '+a(E)+" "+a(f)+')"'+ot(t,"bzc-a",D*.08)+"/>",b+=L}}),T+=M(E,f+2,g(A,t,!0),'text-anchor="middle" fill="'+e.fg+'" font-family="'+tt+'" font-size="'+a(k*.17)+'" font-weight="800"'),T+=M(E,f+2+14,(O(t.centerLabel)?t.centerLabel:"Total").toUpperCase(),H(e,9,'text-anchor="middle" letter-spacing="1"'));var m=u+k+22,h=Math.min(24,C/n.length),I=f-h*n.length/2;return n.forEach(function(p,D){var L=I+D*h+h/2,U=Math.round(p/A*100),W=(s[D]||"#"+(D+1))+": "+g(p,t)+" ("+U+"%)";T+='<circle cx="'+a(m+4)+'" cy="'+a(L)+'" r="4" fill="'+l[D%l.length]+'"/>'+M(m+14,L+4,Nt(s[D]||"#"+(D+1),Math.floor((u+F-m-50)/6.7)),'fill="'+e.fg+'" font-family="'+tt+'" font-size="12"')+M(u+F,L+4,U+"%",'text-anchor="end" fill="'+e.muted+'" font-family="'+lt+'" font-size="11" font-weight="600"')+pt(m,L-h/2,u+F-m,h,W)}),b=0,n.forEach(function(p,D){var L=p/A*100;L<=0||(T+='<circle cx="'+a(E)+'" cy="'+a(f)+'" r="'+a(v)+'" fill="none" stroke="#000" stroke-opacity="0" stroke-width="'+a(R)+'" pathLength="100" stroke-dasharray="'+a(L)+" "+a(100-L)+'" stroke-dashoffset="'+a(-b)+'" transform="rotate(-90 '+a(E)+" "+a(f)+')" class="bzc-hit" data-tip="'+ut((s[D]||"#"+(D+1))+": "+g(p,t)+" ("+Math.round(L)+"%)")+'"/>',b+=L)}),T},{stat:y,summary:"total "+g(A,t)+" across "+n.length+" parts"})}function he(t,e){var r=ct(t.values),i=r.length?r[0]:_(t.value,NaN),l=Z(t.labels),n=_(t.min,0),s=_(t.max,100),o=_(t.height,130);s===n&&(s=n+1);var c=ct(t.zones),A=at(t,e),x=Z(t.color).filter(Boolean).length>1?A:[e.up,e.palette[3],e.down],y=A[0];if(c.length&&isFinite(i)){for(var u=0;u<c.length&&i>=c[u];)u++;y=x[Math.min(u,x.length-1)]}var S=!O(t.prefix)&&!O(t.suffix)&&n===0&&s===100,F=function(C){return g(C,t)+(S?"%":"")};return it(t,e,o,function(C,T,k,E){if(!isFinite(i))return nt(e,C,T,k,E);var f=Math.min(k/2,E-22),R=f*.16,v=f-R/2,b=C+k/2,N=T+f,m=X((i-n)/(s-n),0,1),h=function(q,st){var Q=Math.PI*(1-q);return[b+st*Math.cos(Q),N-st*Math.sin(Q)]},I="M"+a(b-v)+","+a(N)+"A"+a(v)+","+a(v)+" 0 0 1 "+a(b+v)+","+a(N),p='<path d="'+I+'" fill="none" stroke="'+y+'" stroke-opacity=".16" stroke-width="'+a(R)+'" stroke-linecap="round"/>';c.length&&c.forEach(function(q){var st=X((q-n)/(s-n),0,1),Q=h(st,v-R/2-3),d=h(st,v-R/2-8);p+='<line x1="'+a(Q[0])+'" y1="'+a(Q[1])+'" x2="'+a(d[0])+'" y2="'+a(d[1])+'" stroke="'+e.muted+'" stroke-width="1.5"/>'}),m>0&&(p+='<path d="'+I+'" fill="none" stroke="'+y+'" stroke-width="'+a(R)+'" stroke-linecap="round" pathLength="100" style="stroke-dasharray:'+a(m*100)+' 100"'+ot(t,"bzc-s")+"/>");var D=h(m,v);if(p+='<circle cx="'+a(D[0])+'" cy="'+a(D[1])+'" r="'+a(R*.26)+'" fill="'+(t.background||e.tile)+'"'+ot(t,"bzc-a",.9)+"/>",O(t.goal)){var L=X((+t.goal-n)/(s-n),0,1),U=h(L,v+R/2+4),W=h(L,v-R/2-4);p+='<line x1="'+a(U[0])+'" y1="'+a(U[1])+'" x2="'+a(W[0])+'" y2="'+a(W[1])+'" stroke="'+e.fg+'" stroke-width="2" stroke-linecap="round"/>'}return p+=M(b,N-f*.12,F(i),'text-anchor="middle" fill="'+e.fg+'" font-family="'+tt+'" font-size="'+a(f*.34)+'" font-weight="800"'),l[0]&&(p+=M(b,N-f*.12+16,l[0],H(e,10,'text-anchor="middle"'))),p+=M(b-v,N+18,g(n,t,!0),H(e,10,'text-anchor="middle"'))+M(b+v,N+18,g(s,t,!0),H(e,10,'text-anchor="middle"')),p+=pt(b-f,N-f,f*2,f,(l[0]?l[0]+": ":"")+F(i)+" (range "+g(n,t)+" to "+g(s,t)+")"+(O(t.goal)?`
|
|
6
|
+
Goal: `+g(+t.goal,t):"")),p},{summary:isFinite(i)?F(i)+" on a scale of "+g(n,t)+" to "+g(s,t):"no data"})}function me(t,e){var r=ct(t.values).map(function(k){return Math.max(0,k)}),i=Z(t.labels),l=at(t,e),n=r.length===1,s=Lt(r),o=O(t.max)?_(t.max,100):n?100:s;o>0||(o=1);var c=!O(t.prefix)&&!O(t.suffix)&&o===100&&n,A=function(k){return g(k,t)+(c?"%":"")},x=Bt(t),y=x.w,u=0,S=[];if(!n&&r.length){var F=0;u=1,r.forEach(function(k,E){var f=i[E]||"#"+(E+1),R=14+vt(f,12)+6+vt(g(k,t),11,!0)+18;F>0&&F+R>y&&(F=0,u++),S.push({x:F,row:u-1,l:f}),F+=R})}var C=36+(u?12+u*20:0),T=xt(r,t,"");return it(t,e,C,function(k,E,f,R){if(!r.length)return nt(e,k,E,f,R);var v="",b=E+22,N=It("c"),m=n?A(r[0])+(c?"":" of "+g(o,t)):g(s,t)+" of "+g(o,t);n&&i[0]&&(v+=M(k,E+11,i[0],'fill="'+e.fg+'" font-family="'+tt+'" font-size="12"')),v+=M(k+f,E+11,m,'text-anchor="end" fill="'+e.fg+'" font-family="'+lt+'" font-size="11" font-weight="600"'),v+='<rect x="'+k+'" y="'+b+'" width="'+f+'" height="14" rx="7" fill="'+e.grid+'" fill-opacity=".7"/>',v+='<clipPath id="'+N+'"><rect x="'+k+'" y="'+b+'" width="'+f+'" height="14" rx="7"/></clipPath><g clip-path="url(#'+N+')"><g'+ot(t,"bzc-gx")+">";var h=0;if(r.forEach(function(p,D){var L=Math.min(p,Math.max(0,o-h))/o*f;L>0&&(v+='<rect x="'+a(k+h/o*f)+'" y="'+b+'" width="'+a(L)+'" height="14" fill="'+l[D%l.length]+'"/>'),D>0&&L>0&&(v+='<rect x="'+a(k+h/o*f-1)+'" y="'+b+'" width="2" height="14" fill="'+(t.background||e.tile)+'"/>'),h+=p}),v+="</g></g>",O(t.goal)){var I=k+X(+t.goal/o,0,1)*f;v+='<line x1="'+a(I)+'" x2="'+a(I)+'" y1="'+(b-4)+'" y2="'+(b+18)+'" stroke="'+e.fg+'" stroke-width="2" stroke-linecap="round"/>'}return h=0,r.forEach(function(p,D){var L=Math.min(p,Math.max(0,o-h))/o*f;L>0&&(v+=pt(k+h/o*f,b-4,L,22,(i[D]||(n?"Progress":"#"+(D+1)))+": "+A(p)+(n?"":" ("+Math.round(p/o*100)+"%)"))),h+=p}),S.forEach(function(p,D){var L=k+p.x,U=b+14+12+p.row*20+8;v+='<circle cx="'+a(L+4)+'" cy="'+a(U)+'" r="4" fill="'+l[D%l.length]+'"/><text x="'+a(L+14)+'" y="'+a(U+4)+'" fill="'+e.fg+'" font-family="'+tt+'" font-size="12">'+ut(p.l)+'<tspan dx="6" fill="'+e.muted+'" font-family="'+lt+'" font-size="11">'+ut(g(r[D],t))+"</tspan></text>"}),v},{stat:T,summary:n?A(r[0])+(c?"":" of "+g(o,t)):g(s,t)+" of "+g(o,t)})}function pe(t,e){var r=ct(t.values),i=at(t,e)[0],l=t.lang||"en-GB",n;if(O(t.start)&&/^\d{4}-\d{2}-\d{2}$/.test(String(t.start)))n=new Date(String(t.start)+"T00:00:00Z");else{var s=new Date;n=new Date(Date.UTC(s.getFullYear(),s.getMonth(),s.getDate())-Math.max(0,r.length-1)*864e5)}var o=(n.getUTCDay()+6)%7,c=Math.max(1,Math.ceil((o+r.length)/7)),A=Bt(t),x=28,y=Math.max(1,Math.floor((A.w-x)/10));if(c>y){var u=(c-y)*7-o;r=r.slice(u),n=new Date(n.getTime()+u*864e5),o=0,c=y}var S=Math.min(18,(A.w-x)/c),F=S<8?1:3,C=S-F,T=16+7*S+26,k=r.length?Math.max.apply(null,r):0,E=function(N){try{return new Intl.DateTimeFormat(l,Object.assign({timeZone:"UTC"},N))}catch{return new Intl.DateTimeFormat("en-GB",Object.assign({timeZone:"UTC"},N))}},f=E({month:"short"}),R=E({weekday:"short"}),v=E({weekday:"short",day:"numeric",month:"short",year:"numeric"}),b=xt(r,t,"");return it(t,e,T,function(N,m,h,I){if(!r.length)return nt(e,N,m,h,I);var p="",D=N+x,L=m+16,U=-99,W=[.28,.5,.75,1];S>=9&&[0,2,4].forEach(function(Y){var V=new Date(Date.UTC(2024,0,1+Y));p+=M(N,L+Y*S+C/2+3.5,R.format(V),H(e,9))});for(var q=0;q<r.length;q++){var st=o+q,Q=Math.floor(st/7),d=st%7,w=new Date(n.getTime()+q*864e5),z=D+Q*S,$=L+d*S;(w.getUTCDate()===1||q===0)&&z-U>30&&(p+=M(z,m+9,f.format(w),H(e,9)),U=z);var G=r[q]<=0||!k?0:Math.ceil(r[q]/k*4),dt=G?i:e.grid,ht=G?W[G-1]:.7;p+='<rect x="'+a(z)+'" y="'+a($)+'" width="'+a(C)+'" height="'+a(C)+'" rx="'+a(C*.24)+'" fill="'+dt+'" fill-opacity="'+ht+'"'+(t.animate?' class="bzc-a" style="animation-delay:'+a(Q*.012)+'s"':"")+' data-tip="'+ut(v.format(w)+": "+g(r[q],t))+'"/>'}var et=L+7*S+14,ft=10,mt=N+h-5*(ft+3)-30;p+=M(N,et+8,g(Lt(r),t,!0)+" in "+r.length+" days",H(e,10)),p+=M(mt-6,et+8,"Less",H(e,9,'text-anchor="end"'));for(var B=0;B<5;B++)p+='<rect x="'+a(mt+B*(ft+3))+'" y="'+et+'" width="'+ft+'" height="'+ft+'" rx="2.5" fill="'+(B?i:e.grid)+'" fill-opacity="'+(B?W[B-1]:.7)+'"/>';return p+=M(mt+5*(ft+3)+3,et+8,"More",H(e,9)),p},{stat:b,summary:g(Lt(r),t)+" across "+r.length+" days"})}function Ve(t,e){var r=Math.min(t.length,e.length);if(r<2)return null;var i=0,l=0,n;for(n=0;n<r;n++)i+=t[n],l+=e[n];i/=r,l/=r;var s=0,o=0,c=0;for(n=0;n<r;n++)s+=(t[n]-i)*(t[n]-i),o+=(e[n]-l)*(e[n]-l),c+=(t[n]-i)*(e[n]-l);if(!s)return null;var A=c/s;return{slope:A,intercept:l-A*i,r:o?c/Math.sqrt(s*o):0}}function de(t,e){for(var r=Ct(t.x),i=Ct(t.y),l=Z(t.series),n=Z(t.labels),s=at(t,e),o=[],c=0;c<Math.max(r.length,i.length);c++){var A=r[c]||[],x=i[c]||[],y=Math.min(A.length,x.length);o.push({xs:A.slice(0,y),ys:x.slice(0,y)})}o=o.filter(function(f){return f.xs.length});var u=o.reduce(function(f,R){return f+R.xs.length},0),S=_(t.height,210),F=o.length>1||l.length>0,C=J(t.trend),T=function(f){return l[f]||"Group "+(f+1)},k={decimals:t.decimals,compact:t.compact,lang:t.lang},E=$t(t,n);return it(t,e,S,function(f,R,v,b){if(!u)return nt(e,f,R,v,b);var N="",m=R;if(F){var h=Rt(o.map(function(B,Y){return T(Y)}),s,e,f,m,v);N+=h.svg,m+=h.height}var I=[].concat.apply([],o.map(function(B){return B.xs})),p=[].concat.apply([],o.map(function(B){return B.ys})),D=Math.min.apply(null,p),L=Math.max.apply(null,p);O(t.goal)&&(D=Math.min(D,+t.goal),L=Math.max(L,+t.goal)),J(t.zero)&&(D=Math.min(0,D),L=Math.max(0,L));var U=Qt(D,L,4),W=Qt(Math.min.apply(null,I),Math.max.apply(null,I),5),q=U.ticks.map(function(B){return g(B,t,!0)}),st=Ee(q)+(O(t.yLabel)?14:0),Q=20+(O(t.xLabel)?16:0),d=f+st,w=v-st,z=b-(m-R)-Q,$=function(B){return d+6+(B-W.lo)/(W.hi-W.lo)*(w-12)},G=function(B){return m+6+z-6-(B-U.lo)/(U.hi-U.lo)*(z-6)};U.ticks.forEach(function(B,Y){var V=G(B);N+='<line x1="'+a(d)+'" x2="'+a(d+w)+'" y1="'+a(V)+'" y2="'+a(V)+'" stroke="'+e.grid+'" stroke-dasharray="2 4"/>'+M(d-8,V+3.5,q[Y],H(e,10,'text-anchor="end"'))});var dt=Math.max(1,Math.ceil(W.ticks.length*44/w));if(W.ticks.forEach(function(B,Y){if(!(Y%dt)){var V=$(B);N+='<line x1="'+a(V)+'" x2="'+a(V)+'" y1="'+a(m)+'" y2="'+a(m+z)+'" stroke="'+e.grid+'" stroke-dasharray="2 4"/>'+M(V,m+z+15,g(B,k,!0),H(e,10,'text-anchor="middle"'))}}),O(t.xLabel)&&(N+=M(d+w/2,m+z+32,t.xLabel,H(e,10,'text-anchor="middle" font-weight="600"'))),O(t.yLabel)){var ht=m+z/2;N+='<text x="'+a(f+4)+'" y="'+a(ht)+'" transform="rotate(-90 '+a(f+4)+" "+a(ht)+')" text-anchor="middle" '+H(e,10,'font-weight="600"')+' dominant-baseline="hanging">'+String(t.yLabel).replace(/[&<>"]/g,function(B){return{"&":"&","<":"<",">":">",'"':"""}[B]})+"</text>"}O(t.goal)&&(N+=At(t,e,d,d+w,G(+t.goal))),C&&o.forEach(function(B,Y){var V=Ve(B.xs,B.ys);if(V){var kt=Math.min.apply(null,B.xs),K=Math.max.apply(null,B.xs),bt=V.slope*kt+V.intercept,Ot=V.slope*K+V.intercept,Dt=s[Y%s.length];N+='<line x1="'+a($(kt))+'" y1="'+a(X(G(bt),m,m+z))+'" x2="'+a($(K))+'" y2="'+a(X(G(Ot),m,m+z))+'" stroke="'+Dt+'" stroke-width="2" stroke-dasharray="6 4" stroke-linecap="round" stroke-opacity=".9"/>',N+=M(Math.min($(K),d+w)-2,X(G(Ot),m+10,m+z)-7,"r = "+V.r.toFixed(2),H(e,10,'text-anchor="end" font-weight="600"',Dt))}});var et=0,ft=u>150?3:4.5;o.forEach(function(B,Y){var V=s[Y%s.length];B.xs.forEach(function(kt,K){var bt=$(kt),Ot=G(B.ys[K]),Dt=(n[et]?n[et]+`
|
|
7
|
+
`:F?T(Y)+`
|
|
8
|
+
`:"")+(O(t.xLabel)?t.xLabel:"x")+": "+g(kt,k)+`
|
|
9
|
+
`+(O(t.yLabel)?t.yLabel:"y")+": "+g(B.ys[K],t);N+='<circle cx="'+a(bt)+'" cy="'+a(Ot)+'" r="'+ft+'" fill="'+V+'" fill-opacity=".85" stroke="'+(t.background||e.tile)+'" stroke-width="1.5"'+(t.animate?' class="bzc-a" style="animation-delay:'+a(Math.min(.8,et*.012))+'s"':"")+"/>",N+=pt(bt-8,Ot-8,16,16,Dt),et++})});var mt=[];return o.forEach(function(B){B.xs.forEach(function(Y,V){mt.push([$(Y),G(B.ys[V])])})}),E.forEach(function(B){var Y=mt[B.i];Y&&(N+='<circle cx="'+a(Y[0])+'" cy="'+a(Y[1])+'" r="'+(ft+4)+'" fill="none" stroke="'+e.fg+'" stroke-width="1.5"/>'+M(X(Y[0],d+30,d+w-30),Y[1]-ft-9,B.text,H(e,10,'text-anchor="middle"',e.fg)))}),N},{summary:u+" points"+(F?" in "+o.length+" groups":"")})}var Jt={spark:oe,line:se,bars:fe,scatter:de,ring:ce,donut:ue,gauge:he,progress:me,heatmap:pe},We=t=>t.replace(/-([a-z])/g,(e,r)=>r.toUpperCase());function Yt(t,e={}){if(!Jt[t])throw new Error(`Bezel Charts: unknown chart type "${t}". Choose one of: ${Object.keys(Jt).join(", ")}.`);let r={};for(let[l,n]of Object.entries(e))r[We(l)]=n;r.animate=J(r.animate),r.frame=r.frame==="none"?"none":"bezel";let i=r.theme==="light"||r.theme==="dark"?r.theme:r.autoTheme||"dark";return Jt[t](r,Ht[i])}function ge(t){return t.replace(/<rect class="bzc-hit"[^>]*\/>/g,"").replace(/<circle[^>]* class="bzc-hit"[^>]*\/>/g,"").replace(/ data-tip="[^"]*"/g,"")}function Ae(t,e={}){return ge(Yt(t,{...e,animate:!1}))}var Te=["type","values","labels","series","heading","prefix","suffix","decimals","compact","color","theme","frame","width","height","goal","goal-label","notes","show-values","stat","delta","delta-label","max","min","value","zones","horizontal","straight","zero","start","lang","corners","background","center-label","highlight","x","y","x-label","y-label","trend","static","no-tooltips","stacked","angle-labels"],Xe=`: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 ${lt};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}`,_e=()=>typeof matchMedia=="function"?matchMedia("(prefers-color-scheme: dark)"):null,qe=()=>{let t=typeof document<"u"&&document.documentElement?.getAttribute?.("data-theme");return t==="light"||t==="dark"?t:null},ve=new Set;typeof MutationObserver=="function"&&typeof document<"u"&&document.documentElement&&new MutationObserver(()=>ve.forEach(t=>t.refresh())).observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]});var Ze=typeof HTMLElement>"u"?class{}:HTMLElement,Vt=class extends Ze{static get observedAttributes(){return Te}static get themes(){return Ht}get resolvedTheme(){let e=this.getAttribute("theme");return e==="light"||e==="dark"?e:this.options.autoTheme}get chartType(){return this.getAttribute("type")||"spark"}connectedCallback(){this.shadowRoot||(this.attachShadow({mode:"open"}),this.shadowRoot.addEventListener("pointermove",e=>this.#o(e)),this.shadowRoot.addEventListener("pointerdown",e=>this.#o(e)),this.addEventListener("pointerleave",()=>this.shadowRoot.querySelector(".tip")?.classList.remove("on"))),ve.add(this),this.#t=_e(),this.#t?.addEventListener?.("change",this.#l),typeof ResizeObserver=="function"?(this.#a=new ResizeObserver(([e])=>{let r=Math.round(e.contentRect.width);(!this.#e||!this.hasAttribute("width")&&r>0&&Math.abs(r-this.#n)>1)&&this.#r()}),this.#a.observe(this)):this.#r()}disconnectedCallback(){ve.delete(this),this.#a?.disconnect(),this.#t?.removeEventListener?.("change",this.#l)}attributeChangedCallback(){!this.#e||this.#i||(this.#i=!0,queueMicrotask(()=>{this.#i=!1,this.#r()}))}get options(){let e={};for(let r of Te)this.hasAttribute(r)&&(e[r]=this.getAttribute(r));return e.width||(e.width=this.#n||360),e.autoTheme=qe()||(this.#t?this.#t.matches?"dark":"light":"dark"),e}refresh(){this.#e&&this.#r()}toSVG(e={}){return ge(Yt(this.chartType,{...this.options,...e,animate:!1}))}download(e){let r=URL.createObjectURL(new Blob([this.toSVG()],{type:"image/svg+xml"})),i=Object.assign(document.createElement("a"),{href:r,download:`${(e||this.getAttribute("heading")||this.chartType).replace(/[^\w-]+/g,"-").toLowerCase()}.svg`});document.body.append(i),i.click(),i.remove(),setTimeout(()=>URL.revokeObjectURL(r),1e3)}#a=null;#t=null;#n=0;#e=!1;#i=!1;#l=()=>{let e=this.getAttribute("theme");(!e||e==="auto")&&this.refresh()};#r(){if(!this.shadowRoot)return;let e=Math.round(this.getBoundingClientRect?.().width||0);this.#n=e>0?e:360;let r=this.options;r.animate=!this.#e&&!this.hasAttribute("static");let i;try{i=Yt(this.chartType,r)}catch(l){i=`<p style="font:13px ${tt};color:#d63f7c">${ut(l.message)}</p>`}i=i.replace(/<title>[\s\S]*?<\/title>/,""),this.shadowRoot.innerHTML=`<style>${Xe}</style>${i}<div class="tip" role="status"></div>`,this.#e=!0,this.dispatchEvent(new CustomEvent("bezel-render",{bubbles:!0}))}#o(e){let r=this.shadowRoot.querySelector(".tip");if(!r||this.hasAttribute("no-tooltips"))return;let i=e.target?.closest?.("[data-tip]");if(!i){r.classList.remove("on");return}r.textContent=i.getAttribute("data-tip");let l=this.getBoundingClientRect(),n=r.offsetWidth/2;r.style.left=`${X(e.clientX-l.left,n,Math.max(n,l.width-n))}px`,r.style.top=`${e.clientY-l.top}px`,r.classList.add("on")}};var Qe="1.1.1",Fe=Object.keys(Jt);if(typeof customElements<"u"&&typeof HTMLElement<"u"){for(let t of Fe){let e=`bezel-${t}`;customElements.get(e)||customElements.define(e,class extends Vt{get chartType(){return t}})}customElements.get("bezel-chart")||customElements.define("bezel-chart",class extends Vt{})}return Ie(Je);})();
|