@thilakbhat/heatmap-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +471 -0
- package/dist/calendar.d.ts +40 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +136 -0
- package/dist/calendar.js.map +1 -0
- package/dist/colors.d.ts +112 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +186 -0
- package/dist/colors.js.map +1 -0
- package/dist/grid.d.ts +26 -0
- package/dist/grid.d.ts.map +1 -0
- package/dist/grid.js +102 -0
- package/dist/grid.js.map +1 -0
- package/dist/heatmap.d.ts +4 -0
- package/dist/heatmap.d.ts.map +1 -0
- package/dist/heatmap.js +134 -0
- package/dist/heatmap.js.map +1 -0
- package/dist/heatmap3d-patterns.d.ts +19 -0
- package/dist/heatmap3d-patterns.d.ts.map +1 -0
- package/dist/heatmap3d-patterns.js +57 -0
- package/dist/heatmap3d-patterns.js.map +1 -0
- package/dist/heatmap3d-scene.d.ts +49 -0
- package/dist/heatmap3d-scene.d.ts.map +1 -0
- package/dist/heatmap3d-scene.js +104 -0
- package/dist/heatmap3d-scene.js.map +1 -0
- package/dist/heatmap3d-visuals.d.ts +30 -0
- package/dist/heatmap3d-visuals.d.ts.map +1 -0
- package/dist/heatmap3d-visuals.js +50 -0
- package/dist/heatmap3d-visuals.js.map +1 -0
- package/dist/heatmap3d.d.ts +6 -0
- package/dist/heatmap3d.d.ts.map +1 -0
- package/dist/heatmap3d.js +268 -0
- package/dist/heatmap3d.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/projection3d.d.ts +29 -0
- package/dist/projection3d.d.ts.map +1 -0
- package/dist/projection3d.js +55 -0
- package/dist/projection3d.js.map +1 -0
- package/dist/scale.d.ts +21 -0
- package/dist/scale.d.ts.map +1 -0
- package/dist/scale.js +61 -0
- package/dist/scale.js.map +1 -0
- package/dist/shapes.d.ts +13 -0
- package/dist/shapes.d.ts.map +1 -0
- package/dist/shapes.js +63 -0
- package/dist/shapes.js.map +1 -0
- package/dist/static-svg.d.ts +45 -0
- package/dist/static-svg.d.ts.map +1 -0
- package/dist/static-svg.js +177 -0
- package/dist/static-svg.js.map +1 -0
- package/dist/types.d.ts +151 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/cover.png +0 -0
- package/docs/playground-2d.png +0 -0
- package/docs/playground-3d.png +0 -0
- package/package.json +61 -0
- package/src/styles.css +385 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thilak Bhat
|
|
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
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
# heatmap-ui
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[](https://github.com/tb962/heatmap-ui/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/@thilakbhat/heatmap-ui)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
A headless heatmap toolkit for React. Eight cell shapes, an interactive 3D mode
|
|
10
|
+
that runs on plain SVG, and control over every colour, size and label on the
|
|
11
|
+
grid.
|
|
12
|
+
|
|
13
|
+
**[Try it in the playground →](https://tb962.github.io/heatmap-ui/)**
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @thilakbhat/heatmap-ui
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { CalendarHeatmap } from "@thilakbhat/heatmap-ui";
|
|
21
|
+
import "@thilakbhat/heatmap-ui/styles.css";
|
|
22
|
+
|
|
23
|
+
<CalendarHeatmap values={days} weeks={20} showLegend />;
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## What you get
|
|
27
|
+
|
|
28
|
+
Five entry points: `Heatmap` for any grid, `CalendarHeatmap` for dates, their
|
|
29
|
+
`Heatmap3D` and `CalendarHeatmap3D` counterparts, and `renderHeatmap3DSvg`,
|
|
30
|
+
which returns an SVG string without React or a DOM.
|
|
31
|
+
|
|
32
|
+
Everything below is a prop. None of it needs a wrapper, a fork, or a stylesheet
|
|
33
|
+
override.
|
|
34
|
+
|
|
35
|
+
| | |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| Cell shapes | `rounded`, `square`, `circle`, `diamond`, `hexagon`, `plus`, `bar`, `ring` |
|
|
38
|
+
| 3D forms | `rectangle`, `circle` (cylinder), `bar` (slender column) |
|
|
39
|
+
| 3D block styles | `solid`, `lego` (studs), `building` (windowed facades) |
|
|
40
|
+
| 3D themes | `color` (your ramp), `night`, `seasonal`, `rainbow` |
|
|
41
|
+
| Surfaces | flat fills, or SVG bitmap patterns you define per colour level |
|
|
42
|
+
| Intensity | colour, cell size, or both |
|
|
43
|
+
| Shading | `linear`, `quantile`, `log`, or a function you supply |
|
|
44
|
+
| Colour | any ramp, palest first, with separate colours for zero and for no data |
|
|
45
|
+
| Labels | row and column labels, tooltips, and arbitrary content inside cells |
|
|
46
|
+
| Layout | cell size, gap, corner radius, and a predicate for hiding slots |
|
|
47
|
+
|
|
48
|
+
### The 3D mode
|
|
49
|
+
|
|
50
|
+
`Heatmap3D` draws an isometric scene in SVG. No WebGL, no canvas, no 3D library,
|
|
51
|
+
and no second bundle: it ships in the same package as the flat grid and takes
|
|
52
|
+
the same data, labels, tooltips, click handlers and legend props.
|
|
53
|
+
|
|
54
|
+
Height comes from the value itself rather than from its colour band, so a 40
|
|
55
|
+
is twice as tall as a 20. Blocks can be plain solids, LEGO bricks with
|
|
56
|
+
studs sized to fit, or buildings with lit windows. You can drive the camera
|
|
57
|
+
yourself or let the reader drag it. If you would rather ship a picture,
|
|
58
|
+
`renderHeatmap3DSvg` renders the identical scene to a string for an email or a
|
|
59
|
+
README.
|
|
60
|
+
|
|
61
|
+
### Customisation
|
|
62
|
+
|
|
63
|
+
Colour is a prop, not a theme file. Pass any ramp and the bands follow it. Pass
|
|
64
|
+
`faceColor` and you control the top, left and right fills of every 3D block
|
|
65
|
+
independently. Pass `patterns` and each colour level gets its own SVG bitmap,
|
|
66
|
+
written as binary or hexadecimal rows.
|
|
67
|
+
|
|
68
|
+
The stylesheet only handles structure and four CSS variables, so you are not
|
|
69
|
+
fighting a design system to make the chart look like yours.
|
|
70
|
+
|
|
71
|
+
### Data that stays honest
|
|
72
|
+
|
|
73
|
+
A slot with no observation is not a slot with a zero. Pass `known: false` and it
|
|
74
|
+
renders at reduced opacity and announces "No data" to a screen reader, instead
|
|
75
|
+
of quietly reading as a day off. Ragged grids are supported through
|
|
76
|
+
`isSlotHidden`, so a cohort table missing its future quarters does not have to
|
|
77
|
+
invent them.
|
|
78
|
+
|
|
79
|
+
Colour alone is invisible to roughly one reader in twelve, so `encode="size"`
|
|
80
|
+
and `encode="both"` carry intensity redundantly.
|
|
81
|
+
|
|
82
|
+
## The core
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { Heatmap } from "@thilakbhat/heatmap-ui";
|
|
86
|
+
|
|
87
|
+
<Heatmap rows={24} columns={7} values={matrix} />;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
You can pass `values` in either form:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// Dense. null means no data; 0 means measured and empty.
|
|
94
|
+
values={[[3, 0, null], [1, 5, 2]]}
|
|
95
|
+
|
|
96
|
+
// Sparse. Anything not listed has no data.
|
|
97
|
+
values={[{ row: 0, column: 0, value: 3, known: true, meta: anything }]}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
| Prop | Default | |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| `rows`, `columns` | required | Grid size. |
|
|
103
|
+
| `values` | `[]` | Matrix or sparse cells. |
|
|
104
|
+
| `scale` | `"linear"` | `"linear"`, `"quantile"`, `"log"`, or `(value, all) => 0..1`. |
|
|
105
|
+
| `levels` | `colors.length` | Number of shade bands. |
|
|
106
|
+
| `thresholds` | none | Explicit band ceilings; skips `scale`. |
|
|
107
|
+
| `shape` | `"rounded"` | See below. |
|
|
108
|
+
| `encode` | `"color"` | `"color"`, `"size"`, or `"both"`. |
|
|
109
|
+
| `cellSize` | `13` | Pixels. |
|
|
110
|
+
| `gap` | `3` | Pixels. |
|
|
111
|
+
| `radius` | none | Overrides the shape's corner rounding. |
|
|
112
|
+
| `colors` | GitHub green | The ramp, palest first. |
|
|
113
|
+
| `emptyColor` | `#ebedf0` | A known value of zero. |
|
|
114
|
+
| `unknownOpacity` | `0.5` | Applied to slots with no data. |
|
|
115
|
+
| `isSlotHidden` | none | `(row, column) => boolean` for ragged grids. |
|
|
116
|
+
| `rowLabels`, `columnLabels` | none | Positioned against the grid. |
|
|
117
|
+
| `tooltip` | none | `(cell) => ReactNode`; opens on hover and focus. |
|
|
118
|
+
| `cellContent` | none | Optional visual React node centred inside each cell; values are hidden unless provided. |
|
|
119
|
+
| `cellLabel` | none | `(cell) => string`; the accessible name for a cell. |
|
|
120
|
+
| `onCellClick` | none | Makes cells buttons. |
|
|
121
|
+
| `showLegend` | `false` | less/more key, plus "no data" when relevant. |
|
|
122
|
+
|
|
123
|
+
### Shading
|
|
124
|
+
|
|
125
|
+
`scale="linear"` is the default. Bands are cut at even fractions of the largest
|
|
126
|
+
value, which is what GitHub and most other heatmaps do, so a reader who knows
|
|
127
|
+
one chart can read yours without relearning it.
|
|
128
|
+
|
|
129
|
+
Switch to `quantile` when the data has a long tail. Ranking the active values
|
|
130
|
+
keeps every band populated whatever the unit. On one real dataset, linear
|
|
131
|
+
banding put 58% of active days in the first band while quantile banding put 25%
|
|
132
|
+
in each:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
linear ████████████████████████████ ▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒ ░░░░░
|
|
136
|
+
quantile ████████████ ▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒ ░░░░░░░░░░░░
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`log` suits values spanning orders of magnitude. A function gets the value and
|
|
140
|
+
the full set and returns 0..1, and `thresholds` skips the whole question by
|
|
141
|
+
naming the band ceilings yourself.
|
|
142
|
+
|
|
143
|
+
## 3D heatmaps
|
|
144
|
+
|
|
145
|
+
Use `Heatmap3D` for any grid, or `CalendarHeatmap3D` for dates. Both render a
|
|
146
|
+
shaded, interactive SVG scene without WebGL.
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { Heatmap3D, CalendarHeatmap3D } from "@thilakbhat/heatmap-ui";
|
|
150
|
+
import "@thilakbhat/heatmap-ui/styles.css";
|
|
151
|
+
|
|
152
|
+
<Heatmap3D
|
|
153
|
+
rows={2}
|
|
154
|
+
columns={3}
|
|
155
|
+
values={[[20, 40, 80], [0, null, 60]]}
|
|
156
|
+
shape="rectangle"
|
|
157
|
+
blockStyle="lego"
|
|
158
|
+
maxHeight={75}
|
|
159
|
+
showLegend
|
|
160
|
+
tooltip={cell => cell.known ? `${cell.value} events` : "No data"}
|
|
161
|
+
/>;
|
|
162
|
+
|
|
163
|
+
<CalendarHeatmap3D values={days} weeks={20} blockStyle="building" />;
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Playground previews
|
|
167
|
+
|
|
168
|
+
**2D calendar view**
|
|
169
|
+
|
|
170
|
+

|
|
171
|
+
|
|
172
|
+
**3D calendar view**
|
|
173
|
+
|
|
174
|
+

|
|
175
|
+
|
|
176
|
+
Height is proportional to the actual value: with the default domain, 40 is
|
|
177
|
+
twice as tall as 20. The `scale`, `levels`, and `thresholds` props only affect
|
|
178
|
+
colour bands. LEGO studs fit inside that height. Measured zeroes are flat
|
|
179
|
+
tiles. Missing slots get an outline, and hidden slots are omitted.
|
|
180
|
+
|
|
181
|
+
| Prop | Default | Behaviour |
|
|
182
|
+
| --- | --- | --- |
|
|
183
|
+
| `shape` | `"rectangle"` | `"rectangle"`, `"circle"` (cylinder), or `"bar"` (slender column). |
|
|
184
|
+
| `blockStyle` | `"solid"` | `"solid"`, `"lego"` (studs), or `"building"` (windowed facades). |
|
|
185
|
+
| `theme` | `"color"` | `"color"` paints with `colors` and sets only the face-aware neutrals around it. `"night"`, `"seasonal"`, and `"rainbow"` bring their own ramp, so leave `colors` unset with those. |
|
|
186
|
+
| `material` | `"solid"` | `"solid"` or `"pattern"`; pattern material uses SVG bitmap marks over each face. |
|
|
187
|
+
| `patterns` | built-in | Optional `{ top, side }` arrays of `{ width, bitmap, background, foreground }`, indexed by colour level. |
|
|
188
|
+
| `faceColor` | preset shading | `(args) => string` resolver for different top, left, and right fills. |
|
|
189
|
+
| `animation` | `"none"` | `"grow"` adds a staggered entrance; reduced motion uses a fade instead. |
|
|
190
|
+
| `maxHeight` | `100` | Maximum elevation in the same grid units as cell size and gap. |
|
|
191
|
+
| `heightDomain` | `[0, largest positive value]` | Fixed numeric domain for comparisons across charts. Out-of-range values clamp. |
|
|
192
|
+
| `yaw`, `pitch`, `zoom` | `-35`, `38`, `1` | Camera orbit, elevation, and magnification. |
|
|
193
|
+
| `onCameraChange` | none | Receives `{ yaw, pitch, zoom }` after camera interactions. |
|
|
194
|
+
| `interactive` | `true` | Enables pointer dragging and keyboard camera control. |
|
|
195
|
+
| `showControls` | `true` | Shows zoom and camera-reset buttons. |
|
|
196
|
+
|
|
197
|
+
3D accepts the same data, labels, tooltips, click callbacks, and legend props.
|
|
198
|
+
`encode`, `radius`, and `minScale` belong to the 2D component. Negative values
|
|
199
|
+
keep their labels but render at the zero plane. The 3D component is intended
|
|
200
|
+
for nonnegative activity and magnitude data.
|
|
201
|
+
|
|
202
|
+
Drag to orbit the scene. Focus the chart to use the arrow keys, `+` / `-`, or
|
|
203
|
+
`Home` to rotate, zoom, or reset it. Cells expose their values to assistive
|
|
204
|
+
technology. Click handlers also respond to Enter and Space.
|
|
205
|
+
|
|
206
|
+
### Patterns, themes, and static SVG
|
|
207
|
+
|
|
208
|
+
The built-in patterns are small and repeatable. A row can be a number, a
|
|
209
|
+
hexadecimal string, or a binary string. The most-significant bit is drawn on
|
|
210
|
+
the left:
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
<Heatmap3D
|
|
214
|
+
rows={1}
|
|
215
|
+
columns={3}
|
|
216
|
+
values={[[2, 8, 20]]}
|
|
217
|
+
theme="night"
|
|
218
|
+
material="pattern"
|
|
219
|
+
animation="grow"
|
|
220
|
+
patterns={{
|
|
221
|
+
top: [{ width: 4, bitmap: ["1000", "0100", "0010", "0001"] }],
|
|
222
|
+
side: [{ width: 2, bitmap: ["10", "01"] }],
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
For an email, README image, or scheduled asset, use the same scene without React
|
|
228
|
+
or a DOM:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { renderHeatmap3DSvg } from "@thilakbhat/heatmap-ui";
|
|
232
|
+
|
|
233
|
+
const svg = renderHeatmap3DSvg({
|
|
234
|
+
rows: 1,
|
|
235
|
+
columns: 3,
|
|
236
|
+
values: [[2, 8, 20]],
|
|
237
|
+
theme: "seasonal",
|
|
238
|
+
material: "pattern",
|
|
239
|
+
showLegend: true,
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`renderHeatmap3DSvg` uses the same height domain, geometry, face visibility,
|
|
244
|
+
paint order, themes, and pattern definitions as `Heatmap3D`. It returns an
|
|
245
|
+
accessible, non-interactive SVG snapshot.
|
|
246
|
+
|
|
247
|
+
## Shapes
|
|
248
|
+
|
|
249
|
+
| | |
|
|
250
|
+
| --- | --- |
|
|
251
|
+
| `rounded` `square` `circle` | The basic shapes. |
|
|
252
|
+
| `diamond` | A 45° square. Reads denser over long ranges. |
|
|
253
|
+
| `hexagon` | Offset rows, honeycomb. The standard form for hex-binned data. |
|
|
254
|
+
| `plus` | Stays legible at sizes where circles turn to mush. |
|
|
255
|
+
| `bar` | Height tracks the value; colour stays flat. |
|
|
256
|
+
| `ring` | Stroke thickness tracks the value. Works on any ground. |
|
|
257
|
+
|
|
258
|
+
## Encoding without colour
|
|
259
|
+
|
|
260
|
+
Colour alone is not enough for roughly one reader in twelve.
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
<Heatmap encode="both" ... />
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
With `"size"`, each cell scales within its slot, so intensity still reads in
|
|
267
|
+
greyscale. `"both"` uses colour and size. `minScale` sets the smallest size for
|
|
268
|
+
the weakest cell.
|
|
269
|
+
|
|
270
|
+
### Values inside cells
|
|
271
|
+
|
|
272
|
+
Use `cellContent` for short values, counts, or other compact content. It is
|
|
273
|
+
separate from `cellLabel`, so a dense visual can still have a complete
|
|
274
|
+
accessible description:
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
<Heatmap
|
|
278
|
+
rows={7}
|
|
279
|
+
columns={20}
|
|
280
|
+
values={activity}
|
|
281
|
+
cellContent={(cell) => cell.known && cell.value > 0 ? cell.value : null}
|
|
282
|
+
cellLabel={(cell) => cell.known ? `${cell.value} events` : "No data"}
|
|
283
|
+
/>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The content is decorative to assistive technology; use `cellLabel` for its
|
|
287
|
+
meaning. This option belongs to the flat grid. The 3D component keeps values in
|
|
288
|
+
labels and tooltips, so it stays an SVG renderer without an HTML overlay.
|
|
289
|
+
|
|
290
|
+
## Calendar
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
<CalendarHeatmap
|
|
294
|
+
values={[{ date: "2026-09-07", value: 12 }]}
|
|
295
|
+
to="2026-09-07"
|
|
296
|
+
weeks={20}
|
|
297
|
+
weekStart={1}
|
|
298
|
+
showMonthLabels
|
|
299
|
+
showWeekdayLabels
|
|
300
|
+
unitLabel="commits"
|
|
301
|
+
tooltip={(day) => `${day.date}: ${day.value}`}
|
|
302
|
+
/>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`weeks` defaults to 53 when omitted.
|
|
306
|
+
|
|
307
|
+
The calendar adapter handles date mapping, month labels, weekday labels, and a
|
|
308
|
+
default accessible name for each day. It hides days after `to` instead of
|
|
309
|
+
drawing them as missing, so a final partial column stays partial. Every
|
|
310
|
+
`Heatmap` prop passes through.
|
|
311
|
+
|
|
312
|
+
## Recipes
|
|
313
|
+
|
|
314
|
+
Most heatmap variants are just different grid dimensions. Each example below
|
|
315
|
+
uses `Heatmap`; the playground can generate the same code from its current
|
|
316
|
+
settings.
|
|
317
|
+
|
|
318
|
+
### Punchcard: hours down, weekdays across
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
const hourLabels = Array.from({ length: 24 }, (_, i) => (i % 6 === 0 ? `${i}:00` : ""));
|
|
322
|
+
const dayLabels = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
|
|
323
|
+
.map((text, column) => ({ column, text }));
|
|
324
|
+
|
|
325
|
+
<Heatmap
|
|
326
|
+
rows={24}
|
|
327
|
+
columns={7}
|
|
328
|
+
values={commits} // number[24][7]
|
|
329
|
+
rowLabels={hourLabels}
|
|
330
|
+
columnLabels={dayLabels}
|
|
331
|
+
ariaLabel="Commits by hour and weekday"
|
|
332
|
+
tooltip={(cell) => `${cell.row}:00 — ${cell.value}`}
|
|
333
|
+
/>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Cohort retention: a ragged grid
|
|
337
|
+
|
|
338
|
+
A cohort that signed up last week has no week-10 number until ten weeks pass.
|
|
339
|
+
That slot does not exist, so `isSlotHidden` removes it from the grid. This
|
|
340
|
+
differs from `known: false`, which represents an existing slot that was not
|
|
341
|
+
measured.
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
<Heatmap
|
|
345
|
+
rows={cohorts.length}
|
|
346
|
+
columns={12}
|
|
347
|
+
values={retention} // { row, column, value }[]
|
|
348
|
+
isSlotHidden={(row, column) => column >= cohorts.length - row}
|
|
349
|
+
rowLabels={cohorts} // ["Jul 6", "Jul 13", …]
|
|
350
|
+
columnLabels={Array.from({ length: 12 }, (_, column) => ({ column, text: `W${column}` }))}
|
|
351
|
+
ariaLabel="Retention by signup cohort"
|
|
352
|
+
tooltip={(cell) => `Week ${cell.column} — ${cell.value}% retained`}
|
|
353
|
+
/>
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Uptime: wide and short, with days that were never collected
|
|
357
|
+
|
|
358
|
+
This is the other kind of gap: the slots exist, but nobody was watching them.
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
<Heatmap
|
|
362
|
+
rows={services.length}
|
|
363
|
+
columns={30}
|
|
364
|
+
values={uptime} // { row, column, value, known }[]
|
|
365
|
+
rowLabels={services} // ["api", "web", …]
|
|
366
|
+
columnLabels={dayLabels}
|
|
367
|
+
ariaLabel="Service uptime by day"
|
|
368
|
+
tooltip={(cell) =>
|
|
369
|
+
cell.known ? `${cell.value}% up` : "not monitored"}
|
|
370
|
+
/>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Co-occurrence: symmetric, diagonal removed
|
|
374
|
+
|
|
375
|
+
The diagonal compares each label with itself, so it adds noise at full strength.
|
|
376
|
+
Hide it instead of letting it dominate the scale.
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
<Heatmap
|
|
380
|
+
rows={topics.length}
|
|
381
|
+
columns={topics.length}
|
|
382
|
+
values={overlap} // symmetric: value(i, j) === value(j, i)
|
|
383
|
+
isSlotHidden={(row, column) => row === column}
|
|
384
|
+
rowLabels={topics}
|
|
385
|
+
columnLabels={topics.map((text, column) => ({ column, text }))}
|
|
386
|
+
ariaLabel="Label co-occurrence"
|
|
387
|
+
/>
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### A note on label density
|
|
391
|
+
|
|
392
|
+
`Heatmap` positions the labels you provide, but it cannot measure their width.
|
|
393
|
+
A label such as `"Wednesday"` can run into a neighbouring 16px column. Decide
|
|
394
|
+
how many labels fit in the calling code. Use shorter labels or thin them:
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
// Keep only as many labels as the column stride can hold.
|
|
398
|
+
function thinLabels(texts, stride) {
|
|
399
|
+
const widest = Math.max(...texts.map((t) => t.length)) * 6 + 8; // ~6px/char at 10px
|
|
400
|
+
const step = Math.max(1, Math.ceil(widest / stride));
|
|
401
|
+
return texts
|
|
402
|
+
.map((text, column) => ({ column, text }))
|
|
403
|
+
.filter((_, i) => i % step === 0);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
columnLabels={thinLabels(dates, cellSize + gap)}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
`CalendarHeatmap` already thins month labels when one would land too close to
|
|
410
|
+
the previous label.
|
|
411
|
+
|
|
412
|
+
## Accessibility
|
|
413
|
+
|
|
414
|
+
- The grid uses a labelled `group`, not `role="img"`, so the cells inside it can
|
|
415
|
+
still receive focus.
|
|
416
|
+
- Cells receive focus only when they carry information, such as a `cellLabel`,
|
|
417
|
+
`tooltip`, or `onCellClick`.
|
|
418
|
+
- Tooltips open on focus as well as hover, after a 300ms delay, and are wired
|
|
419
|
+
with `aria-describedby`.
|
|
420
|
+
- `encode` lets you carry intensity without relying on colour alone.
|
|
421
|
+
- Hover and focus scaling is dropped under `prefers-reduced-motion`.
|
|
422
|
+
|
|
423
|
+
## Styling
|
|
424
|
+
|
|
425
|
+
The stylesheet handles structure. Props provide the colour, and the stylesheet
|
|
426
|
+
defines four variables you can override:
|
|
427
|
+
|
|
428
|
+
```css
|
|
429
|
+
.heatmap {
|
|
430
|
+
--heatmap-tooltip-bg: …;
|
|
431
|
+
--heatmap-tooltip-text: …;
|
|
432
|
+
--heatmap-label: …;
|
|
433
|
+
--heatmap-cell-content-color: …;
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Tooltip colours follow `prefers-color-scheme`. Set
|
|
438
|
+
`data-heatmap-theme="light" | "dark"` to pin them.
|
|
439
|
+
|
|
440
|
+
## Playground
|
|
441
|
+
|
|
442
|
+
**<https://tb962.github.io/heatmap-ui/>**. No install required.
|
|
443
|
+
|
|
444
|
+
To run it from your working copy:
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
npm run build
|
|
448
|
+
npx serve .
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Open `examples/playground.html`. You can switch between 2D and 3D, choose Solid,
|
|
452
|
+
LEGO, or Skyline cells, and orbit the scene. The graph choices include calendar,
|
|
453
|
+
punchcard, cohort retention, co-occurrence, and uptime. Adjust a prop and the
|
|
454
|
+
panel below the chart shows the exact code for the current view, including its
|
|
455
|
+
imports.
|
|
456
|
+
|
|
457
|
+
## Development
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
npm install
|
|
461
|
+
npm run typecheck
|
|
462
|
+
npm test
|
|
463
|
+
npm run build
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
For contribution details, see [CONTRIBUTING.md](CONTRIBUTING.md). To report a
|
|
467
|
+
vulnerability, see [SECURITY.md](SECURITY.md).
|
|
468
|
+
|
|
469
|
+
## License
|
|
470
|
+
|
|
471
|
+
MIT.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { Heatmap3DProps, HeatmapProps, ResolvedCell } from "./types.js";
|
|
3
|
+
export type CalendarDay = {
|
|
4
|
+
/** ISO date, YYYY-MM-DD. */
|
|
5
|
+
date: string;
|
|
6
|
+
value: number;
|
|
7
|
+
/** false when the day is outside what the source could observe. */
|
|
8
|
+
known?: boolean;
|
|
9
|
+
meta?: unknown;
|
|
10
|
+
};
|
|
11
|
+
type CalendarOptions = {
|
|
12
|
+
values?: ReadonlyArray<CalendarDay>;
|
|
13
|
+
/** Last day shown. Defaults to today. */
|
|
14
|
+
to?: string | Date;
|
|
15
|
+
/** Columns to render. Defaults to 53. */
|
|
16
|
+
weeks?: number;
|
|
17
|
+
/** 0 = Sunday (default), 1 = Monday. */
|
|
18
|
+
weekStart?: 0 | 1;
|
|
19
|
+
showMonthLabels?: boolean;
|
|
20
|
+
showWeekdayLabels?: boolean;
|
|
21
|
+
/** Which weekday rows to label. Defaults to every other one. */
|
|
22
|
+
weekdayLabelRows?: readonly number[];
|
|
23
|
+
unitLabel?: string;
|
|
24
|
+
tooltip?: (day: CalendarCell) => ReactNode;
|
|
25
|
+
cellLabel?: (day: CalendarCell) => string;
|
|
26
|
+
};
|
|
27
|
+
type CalendarManagedProps = "rows" | "columns" | "values" | "rowLabels" | "columnLabels" | "tooltip" | "cellLabel";
|
|
28
|
+
export type CalendarHeatmapProps = Omit<HeatmapProps, CalendarManagedProps> & CalendarOptions;
|
|
29
|
+
/** A calendar whose values rise into 3D columns, bricks or buildings. */
|
|
30
|
+
export type CalendarHeatmap3DProps = Omit<Heatmap3DProps, CalendarManagedProps> & CalendarOptions;
|
|
31
|
+
/** A resolved cell with its calendar date attached. */
|
|
32
|
+
export type CalendarCell = ResolvedCell & {
|
|
33
|
+
date: string;
|
|
34
|
+
};
|
|
35
|
+
/** Lays days onto a flat grid, with one column per week. */
|
|
36
|
+
export declare function CalendarHeatmap(props: CalendarHeatmapProps): import("react").JSX.Element;
|
|
37
|
+
/** Lays the same calendar onto a rotatable 3D grid, with height encoding value. */
|
|
38
|
+
export declare function CalendarHeatmap3D(props: CalendarHeatmap3DProps): import("react").JSX.Element;
|
|
39
|
+
export default CalendarHeatmap;
|
|
40
|
+
//# sourceMappingURL=calendar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../src/calendar.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIhD,OAAO,KAAK,EAAE,cAAc,EAAe,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAS1F,MAAM,MAAM,WAAW,GAAG;IACxB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,MAAM,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IACpC,yCAAyC;IACzC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,SAAS,CAAC;IAC3C,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,MAAM,CAAC;CAC3C,CAAC;AAEF,KAAK,oBAAoB,GACrB,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,YAAY,EAAE,oBAAoB,CAAC,GAAG,eAAe,CAAC;AAE9F,yEAAyE;AACzE,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,cAAc,EAAE,oBAAoB,CAAC,GAAG,eAAe,CAAC;AAElG,uDAAuD;AACvD,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AA8I3D,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAG1D;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BAG9D;AAED,eAAe,eAAe,CAAC"}
|
package/dist/calendar.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { Heatmap } from "./heatmap.js";
|
|
5
|
+
import { Heatmap3D } from "./heatmap3d.js";
|
|
6
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
const MONTHS = [
|
|
8
|
+
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
9
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
|
10
|
+
];
|
|
11
|
+
const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
12
|
+
function toUtcMidnight(value) {
|
|
13
|
+
const date = typeof value === "string" ? parseIsoDate(value) : value;
|
|
14
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
|
15
|
+
}
|
|
16
|
+
function parseIsoDate(value) {
|
|
17
|
+
const [year, month, day] = value.split("-").map(Number);
|
|
18
|
+
return new Date(Date.UTC(year, (month ?? 1) - 1, day ?? 1));
|
|
19
|
+
}
|
|
20
|
+
function isoKey(date) {
|
|
21
|
+
return date.toISOString().slice(0, 10);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Lays days onto the grid: one column per week, one row per weekday. This is
|
|
25
|
+
* the only place in the package that knows what a date is.
|
|
26
|
+
*/
|
|
27
|
+
function useCalendarProps({ values = [], to, weeks = 53, weekStart = 0, showMonthLabels = true, showWeekdayLabels = false, weekdayLabelRows = [1, 3, 5], unitLabel = "", tooltip, cellLabel, ...heatmapProps }) {
|
|
28
|
+
const end = useMemo(() => toUtcMidnight(to ?? new Date()), [to]);
|
|
29
|
+
const { cells, columnLabels, dates, hidden } = useMemo(() => {
|
|
30
|
+
const byDate = new Map(values.map((day) => [day.date, day]));
|
|
31
|
+
// The grid ends on the week containing `end`, so the last column is
|
|
32
|
+
// partial whenever today is not the last day of the week.
|
|
33
|
+
const endWeekday = (end.getUTCDay() - weekStart + 7) % 7;
|
|
34
|
+
const lastColumnStart = new Date(end.getTime() - endWeekday * DAY_MS);
|
|
35
|
+
const firstColumnStart = new Date(lastColumnStart.getTime() - (weeks - 1) * 7 * DAY_MS);
|
|
36
|
+
const resolved = [];
|
|
37
|
+
const dateByKey = new Map();
|
|
38
|
+
const hiddenSlots = new Set();
|
|
39
|
+
const labels = [];
|
|
40
|
+
let previousMonth = -1;
|
|
41
|
+
for (let column = 0; column < weeks; column += 1) {
|
|
42
|
+
// Which month a column belongs to is a property of the calendar, not of
|
|
43
|
+
// the data, so the label is derived from the date before any value is
|
|
44
|
+
// looked up. Deriving it inside the cell loop lost the label whenever
|
|
45
|
+
// the caller had supplied nothing for that column's first day.
|
|
46
|
+
const columnMonth = new Date(firstColumnStart.getTime() + column * 7 * DAY_MS).getUTCMonth();
|
|
47
|
+
if (columnMonth !== previousMonth) {
|
|
48
|
+
labels.push({ column, text: MONTHS[columnMonth] });
|
|
49
|
+
previousMonth = columnMonth;
|
|
50
|
+
}
|
|
51
|
+
for (let row = 0; row < 7; row += 1) {
|
|
52
|
+
const date = new Date(firstColumnStart.getTime() + (column * 7 + row) * DAY_MS);
|
|
53
|
+
// Days after `end` are not "no data", they simply do not exist yet.
|
|
54
|
+
if (date.getTime() > end.getTime()) {
|
|
55
|
+
hiddenSlots.add(row + ":" + column);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const key = isoKey(date);
|
|
59
|
+
dateByKey.set(row + ":" + column, key);
|
|
60
|
+
const day = byDate.get(key);
|
|
61
|
+
if (!day)
|
|
62
|
+
continue;
|
|
63
|
+
resolved.push({
|
|
64
|
+
row,
|
|
65
|
+
column,
|
|
66
|
+
value: day.value,
|
|
67
|
+
known: day.known !== false,
|
|
68
|
+
meta: day.meta,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { cells: resolved, columnLabels: labels, dates: dateByKey, hidden: hiddenSlots };
|
|
73
|
+
}, [values, end, weeks, weekStart]);
|
|
74
|
+
// A three-letter month is about 24px at the label's 10px font. When a month
|
|
75
|
+
// starts too few columns after the previous label there is no room for both,
|
|
76
|
+
// so the later one is dropped rather than drawn on top of its neighbour.
|
|
77
|
+
const monthLabels = useMemo(() => {
|
|
78
|
+
const stride = (heatmapProps.cellSize ?? 13) + (heatmapProps.gap ?? 3);
|
|
79
|
+
const minColumns = Math.ceil(30 / stride);
|
|
80
|
+
const kept = [];
|
|
81
|
+
let lastColumn = Number.NEGATIVE_INFINITY;
|
|
82
|
+
for (const label of columnLabels) {
|
|
83
|
+
if (label.column - lastColumn < minColumns)
|
|
84
|
+
continue;
|
|
85
|
+
kept.push(label);
|
|
86
|
+
lastColumn = label.column;
|
|
87
|
+
}
|
|
88
|
+
return kept;
|
|
89
|
+
}, [columnLabels, heatmapProps.cellSize, heatmapProps.gap]);
|
|
90
|
+
const withDate = (cell) => ({
|
|
91
|
+
...cell,
|
|
92
|
+
date: dates.get(cell.row + ":" + cell.column) ?? "",
|
|
93
|
+
});
|
|
94
|
+
const rowLabels = showWeekdayLabels
|
|
95
|
+
? Array.from({ length: 7 }, (_, row) => weekdayLabelRows.includes(row) ? WEEKDAYS[(row + weekStart) % 7] : "")
|
|
96
|
+
: undefined;
|
|
97
|
+
return {
|
|
98
|
+
rows: 7,
|
|
99
|
+
columns: weeks,
|
|
100
|
+
values: cells,
|
|
101
|
+
isSlotHidden: (row, column) => hidden.has(row + ":" + column),
|
|
102
|
+
rowLabels,
|
|
103
|
+
columnLabels: showMonthLabels ? monthLabels : undefined,
|
|
104
|
+
tooltip: tooltip ? (cell) => tooltip(withDate(cell)) : undefined,
|
|
105
|
+
cellLabel: (cell) => {
|
|
106
|
+
const resolved = withDate(cell);
|
|
107
|
+
if (cellLabel)
|
|
108
|
+
return cellLabel(resolved);
|
|
109
|
+
if (!resolved.date)
|
|
110
|
+
return "";
|
|
111
|
+
const readable = parseIsoDate(resolved.date).toLocaleDateString("en-US", {
|
|
112
|
+
weekday: "long",
|
|
113
|
+
day: "numeric",
|
|
114
|
+
month: "long",
|
|
115
|
+
year: "numeric",
|
|
116
|
+
timeZone: "UTC",
|
|
117
|
+
});
|
|
118
|
+
if (!resolved.known)
|
|
119
|
+
return readable + ". No data.";
|
|
120
|
+
return readable + ". " + resolved.value + (unitLabel ? " " + unitLabel : "");
|
|
121
|
+
},
|
|
122
|
+
...heatmapProps,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Lays days onto a flat grid, with one column per week. */
|
|
126
|
+
export function CalendarHeatmap(props) {
|
|
127
|
+
const heatmapProps = useCalendarProps(props);
|
|
128
|
+
return _jsx(Heatmap, { ...heatmapProps });
|
|
129
|
+
}
|
|
130
|
+
/** Lays the same calendar onto a rotatable 3D grid, with height encoding value. */
|
|
131
|
+
export function CalendarHeatmap3D(props) {
|
|
132
|
+
const heatmapProps = useCalendarProps(props);
|
|
133
|
+
return _jsx(Heatmap3D, { ...heatmapProps });
|
|
134
|
+
}
|
|
135
|
+
export default CalendarHeatmap;
|
|
136
|
+
//# sourceMappingURL=calendar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar.js","sourceRoot":"","sources":["../src/calendar.tsx"],"names":[],"mappings":"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"}
|