glitch-charts 1.0.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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +170 -0
- package/dist/glitch-charts.css +160 -0
- package/dist/glitch-charts.css.d.ts +2 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +103 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +88 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
- `GlitchSparkline` component with scramble animation
|
|
8
|
+
- Configurable axes (`xAxis`, `yAxis`) with custom formatters and tick values
|
|
9
|
+
- `color` prop for single colors or gradient stops
|
|
10
|
+
- Data resampling (upsample/downsample)
|
|
11
|
+
- CSS custom properties theming with dark + light modes
|
|
12
|
+
- Exported utilities: `resample`, `formatCompact`, `formatDate`, `pickDateTicks`
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ivan Husarov
|
|
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,170 @@
|
|
|
1
|
+
# glitch-charts
|
|
2
|
+
|
|
3
|
+
React bar charts with a scramble entrance animation. Pure divs, dark-mode native, ~15 kB.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/glitch-charts)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- ASCII-art bar chart built with HTML divs (no SVG, no canvas)
|
|
11
|
+
- Glitch/scramble entrance animation with Unicode box-drawing glyphs
|
|
12
|
+
- Interactive hover with crosshair cursor and value label
|
|
13
|
+
- Configurable x/y axes with custom formatters, tick counts, and explicit tick values
|
|
14
|
+
- Responsive x-axis with auto-hiding overlapping tick labels
|
|
15
|
+
- Square-root scaling for better visual distribution
|
|
16
|
+
- Built-in data resampling (upsampling with zero-pad, downsampling with sum)
|
|
17
|
+
- Dark + light theme via CSS custom properties
|
|
18
|
+
- SSR-safe (Next.js App Router compatible)
|
|
19
|
+
- Tree-shakeable ESM + CJS dual build
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install glitch-charts
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { GlitchSparkline } from "glitch-charts";
|
|
31
|
+
import "glitch-charts/styles";
|
|
32
|
+
|
|
33
|
+
const data = [120, 450, 230, 890, 670, 340, 1200, 560, 980, 410];
|
|
34
|
+
|
|
35
|
+
export function Dashboard() {
|
|
36
|
+
return <GlitchSparkline data={data} />;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Props
|
|
41
|
+
|
|
42
|
+
| Prop | Type | Default | Description |
|
|
43
|
+
|------|------|---------|-------------|
|
|
44
|
+
| `data` | `number[]` | required | Numeric values (one per time bucket) |
|
|
45
|
+
| `labels` | `string[]` | — | ISO date strings for x-axis labels |
|
|
46
|
+
| `columns` | `number` | `48` | Target columns (data is resampled to fit) |
|
|
47
|
+
| `rows` | `number` | `16` | Vertical cells per column |
|
|
48
|
+
| `yAxis` | `AxisConfig \| false` | `{ show: true, tickCount: 3 }` | Y-axis config, or `false` to hide |
|
|
49
|
+
| `xAxis` | `AxisConfig \| false` | `{ show: true, tickCount: 10 }` | X-axis config, or `false` to hide |
|
|
50
|
+
| `formatValue` | `(n: number) => string` | compact formatter | Hover label formatter |
|
|
51
|
+
| `glyphs` | `string` | `"▒░▚▞#@%&?!~^*+"` | Characters used during scramble |
|
|
52
|
+
| `animationMs` | `number` | `400` | Scramble animation duration |
|
|
53
|
+
| `trigger` | `number` | — | Bump to re-trigger the scramble animation |
|
|
54
|
+
| `className` | `string` | — | Additional CSS class on the root wrapper |
|
|
55
|
+
|
|
56
|
+
### AxisConfig
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
interface AxisConfig {
|
|
60
|
+
show?: boolean; // render the axis (default true)
|
|
61
|
+
tickFormat?: (value: number, index: number) => string; // custom tick formatter
|
|
62
|
+
tickCount?: number; // approximate number of ticks
|
|
63
|
+
tickValues?: number[]; // explicit tick positions (y-axis only)
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Axes
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// Custom y-axis formatter, hide x-axis
|
|
71
|
+
<GlitchSparkline
|
|
72
|
+
data={cpuData}
|
|
73
|
+
yAxis={{ tickCount: 5, tickFormat: (v) => `${v}%` }}
|
|
74
|
+
xAxis={false}
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
// Explicit y-axis tick positions
|
|
78
|
+
<GlitchSparkline
|
|
79
|
+
data={data}
|
|
80
|
+
yAxis={{ tickValues: [0, 50, 100], tickFormat: (v) => `${v}%` }}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
// Bare sparkline — no axes
|
|
84
|
+
<GlitchSparkline data={data} yAxis={false} />
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Samples
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
// Minimal
|
|
91
|
+
<GlitchSparkline data={[1, 4, 2, 8, 5, 7, 3]} />
|
|
92
|
+
|
|
93
|
+
// With date labels
|
|
94
|
+
<GlitchSparkline
|
|
95
|
+
data={hourlyData}
|
|
96
|
+
labels={hourlyTimestamps}
|
|
97
|
+
columns={48}
|
|
98
|
+
xAxis={{ tickCount: 6 }}
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
// Custom glyphs and animation speed
|
|
102
|
+
<GlitchSparkline
|
|
103
|
+
data={metrics}
|
|
104
|
+
glyphs="01"
|
|
105
|
+
animationMs={800}
|
|
106
|
+
rows={12}
|
|
107
|
+
/>
|
|
108
|
+
|
|
109
|
+
// Custom formatter
|
|
110
|
+
<GlitchSparkline
|
|
111
|
+
data={revenue}
|
|
112
|
+
formatValue={(n) => `$${n.toFixed(2)}`}
|
|
113
|
+
/>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Utilities
|
|
117
|
+
|
|
118
|
+
Exported for advanced use cases:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { resample, formatCompact, formatDate, pickDateTicks } from "glitch-charts";
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- **`resample(values, labels, target)`** — Upsample (zero-pad) or downsample (sum) data to a target length
|
|
125
|
+
- **`formatCompact(n)`** — Format numbers as "1.2B", "3.4M", "5.6K"
|
|
126
|
+
- **`formatDate(iso)`** — Format ISO dates as "Mon Day HH:MM"
|
|
127
|
+
- **`pickDateTicks(labels, count)`** — Pick evenly spaced date ticks
|
|
128
|
+
|
|
129
|
+
## Theming
|
|
130
|
+
|
|
131
|
+
The stylesheet uses CSS custom properties prefixed with `--gc-`. Override them to match your design:
|
|
132
|
+
|
|
133
|
+
```css
|
|
134
|
+
:root {
|
|
135
|
+
--gc-text-primary: #e5e5e5;
|
|
136
|
+
--gc-text-secondary: #737373;
|
|
137
|
+
--gc-text-muted: #404040;
|
|
138
|
+
--gc-separator: #1a1a1a;
|
|
139
|
+
--gc-spacing: 0.75rem;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Set `data-theme="light"` on any ancestor for the built-in light theme.
|
|
144
|
+
|
|
145
|
+
## Companion Libraries
|
|
146
|
+
|
|
147
|
+
For scramble text effects (hero numbers, loading indicators), use [use-scramble](https://github.com/tol-is/use-scramble) — the same library that powers the hover label internally.
|
|
148
|
+
|
|
149
|
+
## Development
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
git clone https://github.com/Houstoten/glitch-charts
|
|
153
|
+
cd glitch-charts
|
|
154
|
+
npm install
|
|
155
|
+
npm run build # Build the library
|
|
156
|
+
npm test # Run tests
|
|
157
|
+
npm run dev # Watch mode
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Contributing
|
|
161
|
+
|
|
162
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
163
|
+
|
|
164
|
+
## Author
|
|
165
|
+
|
|
166
|
+
[Ivan Husarov](https://x.com/ivaaaa0n)
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* glitch-charts — default styles
|
|
3
|
+
*
|
|
4
|
+
* Import via: import "glitch-charts/styles";
|
|
5
|
+
*
|
|
6
|
+
* The library uses CSS custom properties for theming. Override these in your
|
|
7
|
+
* own stylesheet or set `data-theme="light"` on any ancestor.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* ---- Theme tokens ---- */
|
|
11
|
+
:root {
|
|
12
|
+
--gc-bg: #0a0a0a;
|
|
13
|
+
--gc-text-primary: #e5e5e5;
|
|
14
|
+
--gc-text-secondary: #737373;
|
|
15
|
+
--gc-text-muted: #404040;
|
|
16
|
+
--gc-separator: #1a1a1a;
|
|
17
|
+
--gc-spacing: 0.75rem;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
[data-theme="light"] {
|
|
21
|
+
--gc-bg: #fafafa;
|
|
22
|
+
--gc-text-primary: #171717;
|
|
23
|
+
--gc-text-secondary: #737373;
|
|
24
|
+
--gc-text-muted: #a3a3a3;
|
|
25
|
+
--gc-separator: #e5e5e5;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ---- Wrapper ---- */
|
|
29
|
+
.gc-wrap {
|
|
30
|
+
display: flex;
|
|
31
|
+
gap: var(--gc-spacing);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ---- Y-axis ---- */
|
|
35
|
+
.gc-y-axis {
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
justify-content: space-between;
|
|
39
|
+
align-items: flex-end;
|
|
40
|
+
padding-top: 2rem;
|
|
41
|
+
padding-bottom: 1.5rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.gc-no-x .gc-y-axis {
|
|
45
|
+
padding-bottom: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* ---- Chart area ---- */
|
|
49
|
+
.gc-chart {
|
|
50
|
+
flex: 1;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ---- Sparkline (bar area) ---- */
|
|
55
|
+
.gc-sparkline {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: flex-end;
|
|
58
|
+
gap: 1px;
|
|
59
|
+
width: 100%;
|
|
60
|
+
height: 12rem;
|
|
61
|
+
position: relative;
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ---- Grid lines ---- */
|
|
66
|
+
.gc-grid {
|
|
67
|
+
position: absolute;
|
|
68
|
+
inset: 0;
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-direction: column;
|
|
71
|
+
justify-content: space-between;
|
|
72
|
+
pointer-events: none;
|
|
73
|
+
z-index: 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.gc-grid-line {
|
|
77
|
+
width: 100%;
|
|
78
|
+
height: 1px;
|
|
79
|
+
background: var(--gc-separator);
|
|
80
|
+
opacity: 0.4;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ---- X-axis ---- */
|
|
84
|
+
.gc-x-axis {
|
|
85
|
+
position: relative;
|
|
86
|
+
height: 1.5rem;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.gc-tick {
|
|
90
|
+
font-size: 0.6875rem;
|
|
91
|
+
color: var(--gc-text-muted);
|
|
92
|
+
font-variant-numeric: tabular-nums;
|
|
93
|
+
white-space: nowrap;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.gc-x-axis .gc-tick {
|
|
97
|
+
position: absolute;
|
|
98
|
+
top: 0.25rem;
|
|
99
|
+
transform: translateX(-50%);
|
|
100
|
+
visibility: hidden;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ---- Bar columns ---- */
|
|
104
|
+
.gc-col {
|
|
105
|
+
flex: 1;
|
|
106
|
+
min-width: 0;
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: flex-end;
|
|
109
|
+
height: 100%;
|
|
110
|
+
cursor: crosshair;
|
|
111
|
+
container-type: inline-size;
|
|
112
|
+
position: relative;
|
|
113
|
+
z-index: 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.gc-bar {
|
|
117
|
+
width: 100%;
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: column-reverse;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* ---- Individual cells ---- */
|
|
123
|
+
.gc-cell {
|
|
124
|
+
width: 100%;
|
|
125
|
+
aspect-ratio: 1;
|
|
126
|
+
background: var(--gc-text-muted);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.gc-col.active .gc-cell {
|
|
130
|
+
background: var(--gc-text-secondary);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.gc-cell.scrambling,
|
|
134
|
+
.gc-col.active .gc-cell.scrambling {
|
|
135
|
+
background: transparent !important;
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
font-size: 100cqi;
|
|
140
|
+
line-height: 1;
|
|
141
|
+
color: var(--gc-col-color, var(--gc-text-secondary));
|
|
142
|
+
overflow: hidden;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* ---- Hover value label ---- */
|
|
146
|
+
.gc-value {
|
|
147
|
+
height: 2rem;
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: flex-end;
|
|
150
|
+
justify-content: flex-end;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.gc-label {
|
|
154
|
+
font-size: 1.5rem;
|
|
155
|
+
font-weight: 200;
|
|
156
|
+
letter-spacing: -0.04em;
|
|
157
|
+
font-variant-numeric: tabular-nums;
|
|
158
|
+
color: var(--gc-text-primary);
|
|
159
|
+
white-space: nowrap;
|
|
160
|
+
}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var H=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var W=Object.prototype.hasOwnProperty;var X=(t,e)=>{for(var n in e)H(t,n,{get:e[n],enumerable:!0})},Q=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of K(e))!W.call(t,r)&&r!==n&&H(t,r,{get:()=>e[r],enumerable:!(i=z(e,r))||i.enumerable});return t};var Z=t=>Q(H({},"__esModule",{value:!0}),t);var ft={};X(ft,{GlitchSparkline:()=>V,formatCompact:()=>w,formatDate:()=>F,pickDateTicks:()=>D,resample:()=>S});module.exports=Z(ft);var m=require("react"),P=require("use-scramble");function S(t,e,n){if(t.length===0)return{data:[],labels:void 0};if(t.length<=n){let o=n-t.length,l;if(e&&e.length>=2){let d=new Date(e[0]).getTime(),g=new Date(e[1]).getTime(),f=g-d;!isNaN(d)&&!isNaN(g)&&f>0?l=[...Array.from({length:o},(A,T)=>new Date(d-(o-T)*f).toISOString()),...e]:l=[...Array(o).fill(""),...e]}else l=e?[...Array(o).fill(""),...e]:void 0;return{data:[...Array(o).fill(0),...t],labels:l}}let i=t.length/n,r=[],s=[];for(let o=0;o<n;o++){let l=Math.floor(o*i),d=Math.floor((o+1)*i),g=0;for(let f=l;f<d;f++)g+=t[f];r.push(g),e&&s.push(e[l])}return{data:r,labels:e?s:void 0}}function w(t){return t>=1e9?`${(t/1e9).toFixed(1)}B`:t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}K`:t.toLocaleString()}var tt=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function F(t){let e=new Date(t);if(isNaN(e.getTime()))return t;let n=tt[e.getMonth()],i=e.getDate(),r=e.getHours();if(r!==0||t.includes("T")||t.includes(" ")){let s=String(r).padStart(2,"0"),o=String(e.getMinutes()).padStart(2,"0");return`${n} ${i} ${s}:${o}`}return`${n} ${i}`}function R(t){let e=t.querySelectorAll(".gc-tick"),n=12,i=-1/0;for(let r of e){r.style.visibility="visible";let s=r.getBoundingClientRect();s.left<i+n?r.style.visibility="hidden":i=s.right}}function D(t,e){let n=t.map((r,s)=>({label:r,index:s})).filter(r=>r.label);return n.length===0?[]:(n.length<=e?n:Array.from({length:e},(r,s)=>{let o=Math.round(s/(e-1)*(n.length-1));return n[o]})).map(r=>({index:r.index,label:F(r.label)}))}var c=require("react/jsx-runtime"),et=16,nt=48,rt="\u2592\u2591\u259A\u259E#@%&?!~^*+",it=400;function st(t,e){(0,m.useLayoutEffect)(()=>{t.current&&R(t.current)},[t,e]),(0,m.useEffect)(()=>{let n=t.current;if(!n)return;let i=new ResizeObserver(()=>R(n));return i.observe(n),()=>i.disconnect()},[t,e])}function ot(t){let e=t.children;for(let n=0;n<e.length;n++){let i=e[n];i.textContent="",i.classList.remove("scrambling")}}function ct({count:t,trigger:e,isActive:n,onEnter:i,onLeave:r,glyphs:s,animationMs:o,color:l}){let d=(0,m.useRef)(0),g=(0,m.useRef)(0),f=(0,m.useRef)(null),A=(0,m.useRef)([]),T=(0,m.useRef)(!1);return(0,m.useLayoutEffect)(()=>{let h=f.current;if(h)for(let v=0;v<h.children.length;v++){let k=h.children[v];k.textContent=s[Math.floor(Math.random()*s.length)],k.classList.add("scrambling")}},[t,e,s]),(0,m.useEffect)(()=>{let h=f.current;if(!h)return;let v=!n&&T.current;if(T.current=n,v){cancelAnimationFrame(d.current),ot(h);return}let k=Array.from({length:h.children.length},(p,u)=>u);for(let p=k.length-1;p>0;p--){let u=Math.floor(Math.random()*(p+1));[k[p],k[u]]=[k[u],k[p]]}A.current=k,g.current=0;let L=p=>{if(!f.current)return;g.current===0&&(g.current=p);let u=p-g.current,x=Math.min(u/o,1),C=f.current.children,E=Math.floor(x*C.length);for(let b=0;b<C.length;b++){let N=A.current[b];if(N==null||N>=C.length)continue;let y=C[N];b<E?(y.textContent="",y.classList.remove("scrambling")):(y.textContent=s[Math.floor(Math.random()*s.length)],y.classList.add("scrambling"))}x<1&&(d.current=requestAnimationFrame(L))};return d.current=requestAnimationFrame(L),()=>cancelAnimationFrame(d.current)},[n,t,e,s,o]),(0,c.jsx)("div",{className:`gc-col${n?" active":""}`,onMouseEnter:i,onMouseLeave:r,children:(0,c.jsx)("div",{className:"gc-bar",ref:f,children:Array.from({length:t},(h,v)=>(0,c.jsx)("div",{className:"gc-cell",style:l?{background:l,"--gc-col-color":l}:void 0},v))})})}function at({text:t}){let{ref:e}=(0,P.useScramble)({text:t,speed:.8,scramble:5,overdrive:!1});return(0,c.jsx)("span",{ref:e,className:"gc-label"})}function _(t){let e=t.replace("#",""),n=e.length===3?parseInt(e[0]+e[0]+e[1]+e[1]+e[2]+e[2],16):parseInt(e,16);return[n>>16&255,n>>8&255,n&255]}function lt(t,e,n){let[i,r,s]=_(t),[o,l,d]=_(e),g=Math.round(i+(o-i)*n),f=Math.round(r+(l-r)*n),A=Math.round(s+(d-s)*n);return`rgb(${g},${f},${A})`}function ut(t,e){return t?typeof t=="string"?new Array(e).fill(t):t.length===1?new Array(e).fill(t[0]):Array.from({length:e},(n,i)=>{let s=(e===1?0:i/(e-1))*(t.length-1),o=Math.floor(s),l=Math.min(o+1,t.length-1);return lt(t[o],t[l],s-o)}):new Array(e).fill(void 0)}function G(t,e){return t===!1?!1:{show:t?.show??e.show,tickCount:t?.tickCount??e.tickCount,tickFormat:t?.tickFormat,tickValues:t?.tickValues}}function mt(t,e){if(e.tickValues)return e.tickValues;let n=e.tickCount;if(n<=1)return[t];if(n===2)return[t,0];let i=[];for(let r=0;r<n;r++)i.push(Math.round(t*(1-r/(n-1))));return i}function V({data:t,labels:e,className:n,trigger:i,rows:r=et,columns:s=nt,glyphs:o=rt,animationMs:l=it,yAxis:d,xAxis:g,formatValue:f,color:A,tickCount:T}){let[h,v]=(0,m.useState)(-1),[k,L]=(0,m.useState)(!1),p=(0,m.useRef)(null);(0,m.useEffect)(()=>L(!0),[]);let u=G(d,{show:!0,tickCount:3}),x=G(g,{show:!0,tickCount:T??10}),C=f??(u&&u.tickFormat?a=>u.tickFormat(a,0):w),E=u&&u.tickFormat?u.tickFormat:a=>C(a),{data:b,labels:N}=t.length>0?S(t,e,s):{data:[],labels:void 0},y=Math.max(...b,0),j=x?x.tickCount:10,$=(N&&x?D(N,j):[]).map((a,M)=>({...a,label:x&&x.tickFormat?x.tickFormat(a.index,M):a.label})),I=$.map(a=>a.index).join(",");if(st(p,I),b.length===0)return null;let q=u?mt(y,u):[],B=u!==!1&&u.show,O=x!==!1&&x.show&&$.length>0,Y=ut(A,b.length),J=["gc-wrap",!O&&"gc-no-x",n].filter(Boolean).join(" ");return(0,c.jsxs)("div",{className:J,children:[B&&(0,c.jsx)("div",{className:"gc-y-axis",children:q.map((a,M)=>(0,c.jsx)("span",{className:"gc-tick",children:E(a,M)},`${a}-${M}`))}),(0,c.jsxs)("div",{className:"gc-chart",children:[(0,c.jsx)("div",{className:"gc-value",children:h>=0&&b[h]>0&&(0,c.jsx)(at,{text:C(b[h])})}),(0,c.jsxs)("div",{className:"gc-sparkline",children:[(0,c.jsxs)("div",{className:"gc-grid",children:[(0,c.jsx)("div",{className:"gc-grid-line"}),(0,c.jsx)("div",{className:"gc-grid-line"}),(0,c.jsx)("div",{className:"gc-grid-line"})]}),b.map((a,M)=>{if(!k||a===0)return(0,c.jsx)("div",{className:"gc-col"},M);let U=Math.max(1,Math.round(Math.sqrt(a/y)*r));return(0,c.jsx)(ct,{count:U,trigger:i,isActive:M===h,onEnter:()=>v(M),onLeave:()=>v(-1),glyphs:o,animationMs:l,color:Y[M]},M)})]}),O&&(0,c.jsx)("div",{className:"gc-x-axis",ref:p,children:$.map(a=>(0,c.jsx)("span",{className:"gc-tick",style:{left:`${(a.index+.5)/b.length*100}%`},children:a.label},a.index))})]})]})}0&&(module.exports={GlitchSparkline,formatCompact,formatDate,pickDateTicks,resample});
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/sparkline.tsx","../src/utils/resample.ts","../src/utils/format.ts","../src/utils/ticks.ts"],"sourcesContent":["// Components\nexport { GlitchSparkline } from \"./sparkline.js\";\nexport type { GlitchSparklineProps, AxisConfig } from \"./sparkline.js\";\n\n// Utilities (useful for custom integrations)\nexport { resample } from \"./utils/resample.js\";\nexport { formatCompact, formatDate } from \"./utils/format.js\";\nexport { pickDateTicks } from \"./utils/ticks.js\";\n","\"use client\";\n\nimport {\n useState,\n useEffect,\n useLayoutEffect,\n useRef,\n type RefObject,\n} from \"react\";\nimport { useScramble } from \"use-scramble\";\nimport { resample } from \"./utils/resample.js\";\nimport { formatCompact } from \"./utils/format.js\";\nimport { cullTicks, pickDateTicks } from \"./utils/ticks.js\";\n\n/** Default constants — can be overridden via props */\nconst DEFAULT_ROWS = 16;\nconst DEFAULT_COLS = 48;\nconst GLYPHS = \"▒░▚▞#@%&?!~^*+\";\nconst ANIM_MS = 400;\n\n/** Configuration for an axis. Pass `false` to hide the axis entirely. */\nexport interface AxisConfig {\n /** Whether to render this axis (default true). */\n show?: boolean;\n /** Custom tick formatter. Receives the tick value and its index. */\n tickFormat?: (value: number, index: number) => string;\n /** Approximate number of ticks to display. */\n tickCount?: number;\n /** Explicit tick positions (y-axis only). Overrides tickCount. */\n tickValues?: number[];\n}\n\nexport interface GlitchSparklineProps {\n /** Numeric data values (one per time bucket). */\n data: number[];\n /** ISO date strings matching each data point — used for x-axis labels. */\n labels?: string[];\n /** Additional CSS class name on the root wrapper. */\n className?: string;\n /** Bump this value to re-trigger the scramble entrance animation. */\n trigger?: number;\n /** Number of vertical cells per column (default 16). */\n rows?: number;\n /** Target number of columns — data is resampled to fit (default 48). */\n columns?: number;\n /** Custom glyph characters used during the scramble animation. */\n glyphs?: string;\n /** Duration of the scramble animation in ms (default 400). */\n animationMs?: number;\n\n /**\n * Y-axis configuration, or `false` to hide.\n *\n * Defaults: `{ show: true, tickCount: 3 }` with compact number formatting.\n *\n * ```tsx\n * yAxis={{ tickFormat: (v) => `${v}%`, tickCount: 5 }}\n * yAxis={{ tickValues: [0, 50, 100], tickFormat: (v) => `${v}%` }}\n * yAxis={false} // hide y-axis\n * ```\n */\n yAxis?: AxisConfig | false;\n\n /**\n * X-axis configuration, or `false` to hide.\n *\n * Shown only when `labels` are provided. Defaults: `{ show: true, tickCount: 10 }`\n * with built-in date formatting.\n *\n * ```tsx\n * xAxis={{ tickCount: 5, tickFormat: (v) => new Date(v).toLocaleDateString() }}\n * xAxis={false} // hide x-axis\n * ```\n */\n xAxis?: AxisConfig | false;\n\n /**\n * Custom value formatter for the hover label.\n * Falls back to y-axis tickFormat, then the built-in compact formatter.\n */\n formatValue?: (n: number) => string;\n\n /**\n * Bar color. A single CSS color string applies to all columns.\n * An array of colors is interpolated across columns (gradient / rainbow).\n *\n * ```tsx\n * color=\"#ec4899\" // single color\n * color={[\"#ec4899\", \"#8b5cf6\", \"#06b6d4\"]} // gradient\n * ```\n *\n * When omitted, bars use the default theme colors (--gc-text-muted / --gc-text-secondary).\n */\n color?: string | string[];\n\n /**\n * @deprecated Use `xAxis={{ tickCount }}` instead. Kept for backward compat.\n */\n tickCount?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Hooks\n// ---------------------------------------------------------------------------\n\nfunction useHideOverlappingTicks(\n ref: RefObject<HTMLDivElement | null>,\n tickKey: string,\n) {\n useLayoutEffect(() => {\n if (ref.current) cullTicks(ref.current);\n }, [ref, tickKey]);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const ro = new ResizeObserver(() => cullTicks(el));\n ro.observe(el);\n return () => ro.disconnect();\n }, [ref, tickKey]);\n}\n\n// ---------------------------------------------------------------------------\n// Internal sub-components\n// ---------------------------------------------------------------------------\n\nfunction resetCells(el: HTMLElement) {\n const children = el.children;\n for (let i = 0; i < children.length; i++) {\n const child = children[i] as HTMLElement;\n child.textContent = \"\";\n child.classList.remove(\"scrambling\");\n }\n}\n\nfunction BarCol({\n count,\n trigger,\n isActive,\n onEnter,\n onLeave,\n glyphs,\n animationMs,\n color,\n}: {\n count: number;\n trigger?: number;\n isActive: boolean;\n onEnter: () => void;\n onLeave: () => void;\n glyphs: string;\n animationMs: number;\n color?: string;\n}) {\n const rafRef = useRef(0);\n const startRef = useRef(0);\n const cellsRef = useRef<HTMLDivElement>(null);\n const orderRef = useRef<number[]>([]);\n const wasActiveRef = useRef(false);\n\n useLayoutEffect(() => {\n const el = cellsRef.current;\n if (!el) return;\n for (let i = 0; i < el.children.length; i++) {\n const child = el.children[i] as HTMLElement;\n child.textContent = glyphs[Math.floor(Math.random() * glyphs.length)];\n child.classList.add(\"scrambling\");\n }\n }, [count, trigger, glyphs]);\n\n useEffect(() => {\n const el = cellsRef.current;\n if (!el) return;\n\n const deactivating = !isActive && wasActiveRef.current;\n wasActiveRef.current = isActive;\n\n if (deactivating) {\n cancelAnimationFrame(rafRef.current);\n resetCells(el);\n return;\n }\n\n // Build a shuffled settle order\n const order = Array.from({ length: el.children.length }, (_, i) => i);\n for (let i = order.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [order[i], order[j]] = [order[j], order[i]];\n }\n orderRef.current = order;\n startRef.current = 0;\n\n const animate = (now: number) => {\n if (!cellsRef.current) return;\n if (startRef.current === 0) startRef.current = now;\n\n const elapsed = now - startRef.current;\n const progress = Math.min(elapsed / animationMs, 1);\n const children = cellsRef.current.children;\n const settled = Math.floor(progress * children.length);\n\n for (let k = 0; k < children.length; k++) {\n const idx = orderRef.current[k];\n if (idx == null || idx >= children.length) continue;\n const child = children[idx] as HTMLElement;\n if (k < settled) {\n child.textContent = \"\";\n child.classList.remove(\"scrambling\");\n } else {\n child.textContent =\n glyphs[Math.floor(Math.random() * glyphs.length)];\n child.classList.add(\"scrambling\");\n }\n }\n\n if (progress < 1) {\n rafRef.current = requestAnimationFrame(animate);\n }\n };\n\n rafRef.current = requestAnimationFrame(animate);\n return () => cancelAnimationFrame(rafRef.current);\n }, [isActive, count, trigger, glyphs, animationMs]);\n\n return (\n <div\n className={`gc-col${isActive ? \" active\" : \"\"}`}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n >\n <div className=\"gc-bar\" ref={cellsRef}>\n {Array.from({ length: count }, (_, j) => (\n <div\n key={j}\n className=\"gc-cell\"\n style={color ? { background: color, \"--gc-col-color\": color } as React.CSSProperties : undefined}\n />\n ))}\n </div>\n </div>\n );\n}\n\nfunction ScrambleLabel({ text }: { text: string }) {\n const { ref } = useScramble({\n text,\n speed: 0.8,\n scramble: 5,\n overdrive: false,\n });\n\n return <span ref={ref} className=\"gc-label\" />;\n}\n\n// ---------------------------------------------------------------------------\n// Color helpers\n// ---------------------------------------------------------------------------\n\nfunction parseHex(hex: string): [number, number, number] {\n const h = hex.replace(\"#\", \"\");\n const n = h.length === 3\n ? parseInt(h[0]+h[0]+h[1]+h[1]+h[2]+h[2], 16)\n : parseInt(h, 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nfunction lerpColor(a: string, b: string, t: number): string {\n const [r1, g1, b1] = parseHex(a);\n const [r2, g2, b2] = parseHex(b);\n const r = Math.round(r1 + (r2 - r1) * t);\n const g = Math.round(g1 + (g2 - g1) * t);\n const bl = Math.round(b1 + (b2 - b1) * t);\n return `rgb(${r},${g},${bl})`;\n}\n\nfunction resolveColors(\n color: string | string[] | undefined,\n count: number,\n): (string | undefined)[] {\n if (!color) return new Array(count).fill(undefined);\n if (typeof color === \"string\") return new Array(count).fill(color);\n if (color.length === 1) return new Array(count).fill(color[0]);\n // Interpolate across stops\n return Array.from({ length: count }, (_, i) => {\n const t = count === 1 ? 0 : i / (count - 1);\n const segment = t * (color.length - 1);\n const lo = Math.floor(segment);\n const hi = Math.min(lo + 1, color.length - 1);\n return lerpColor(color[lo], color[hi], segment - lo);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Axis resolution helpers\n// ---------------------------------------------------------------------------\n\nfunction resolveAxisConfig(\n cfg: AxisConfig | false | undefined,\n defaults: Required<Pick<AxisConfig, \"show\" | \"tickCount\">>,\n): (AxisConfig & { show: boolean; tickCount: number }) | false {\n if (cfg === false) return false;\n return {\n show: cfg?.show ?? defaults.show,\n tickCount: cfg?.tickCount ?? defaults.tickCount,\n tickFormat: cfg?.tickFormat,\n tickValues: cfg?.tickValues,\n };\n}\n\nfunction computeYTicks(\n max: number,\n cfg: AxisConfig & { tickCount: number },\n): number[] {\n // Explicit tick values override everything\n if (cfg.tickValues) return cfg.tickValues;\n\n const count = cfg.tickCount;\n if (count <= 1) return [max];\n if (count === 2) return [max, 0];\n\n // Evenly spaced ticks from max to 0\n const ticks: number[] = [];\n for (let i = 0; i < count; i++) {\n ticks.push(Math.round(max * (1 - i / (count - 1))));\n }\n return ticks;\n}\n\n// ---------------------------------------------------------------------------\n// Main export\n// ---------------------------------------------------------------------------\n\nexport function GlitchSparkline({\n data: rawData,\n labels: rawLabels,\n className,\n trigger,\n rows = DEFAULT_ROWS,\n columns = DEFAULT_COLS,\n glyphs = GLYPHS,\n animationMs = ANIM_MS,\n yAxis: yAxisProp,\n xAxis: xAxisProp,\n formatValue,\n color: colorProp,\n tickCount: tickCountLegacy,\n}: GlitchSparklineProps) {\n const [active, setActive] = useState(-1);\n const [hydrated, setHydrated] = useState(false);\n const xAxisRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => setHydrated(true), []);\n\n // Resolve axis configs\n const yAxis = resolveAxisConfig(yAxisProp, { show: true, tickCount: 3 });\n const xAxis = resolveAxisConfig(xAxisProp, {\n show: true,\n tickCount: tickCountLegacy ?? 10,\n });\n\n // Value formatter: explicit > yAxis.tickFormat > built-in compact\n const fmtValue =\n formatValue ??\n (yAxis && yAxis.tickFormat\n ? (n: number) => yAxis.tickFormat!(n, 0)\n : formatCompact);\n\n // Y-axis tick formatter\n const fmtYTick =\n yAxis && yAxis.tickFormat ? yAxis.tickFormat : (_v: number) => fmtValue(_v);\n\n const { data, labels } =\n rawData.length > 0\n ? resample(rawData, rawLabels, columns)\n : { data: [], labels: undefined };\n\n const max = Math.max(...data, 0);\n\n // X-axis ticks\n const xTickCount = xAxis ? xAxis.tickCount : 10;\n const dateTicks =\n labels && xAxis ? pickDateTicks(labels, xTickCount) : [];\n\n // Apply custom x-axis formatter if provided\n const formattedDateTicks = dateTicks.map((t, i) => ({\n ...t,\n label: xAxis && xAxis.tickFormat\n ? xAxis.tickFormat(t.index, i)\n : t.label,\n }));\n\n const tickKey = formattedDateTicks.map((t) => t.index).join(\",\");\n useHideOverlappingTicks(xAxisRef, tickKey);\n\n if (data.length === 0) return null;\n\n // Y-axis ticks\n const yTicks = yAxis ? computeYTicks(max, yAxis) : [];\n const showYAxis = yAxis !== false && yAxis.show;\n const showXAxis = xAxis !== false && xAxis.show && formattedDateTicks.length > 0;\n\n // Resolve per-column colors\n const colors = resolveColors(colorProp, data.length);\n\n const wrapClass = [\n \"gc-wrap\",\n !showXAxis && \"gc-no-x\",\n className,\n ].filter(Boolean).join(\" \");\n\n return (\n <div className={wrapClass}>\n {showYAxis && (\n <div className=\"gc-y-axis\">\n {yTicks.map((v, i) => (\n <span key={`${v}-${i}`} className=\"gc-tick\">\n {fmtYTick(v, i)}\n </span>\n ))}\n </div>\n )}\n <div className=\"gc-chart\">\n <div className=\"gc-value\">\n {active >= 0 && data[active] > 0 && (\n <ScrambleLabel text={fmtValue(data[active])} />\n )}\n </div>\n <div className=\"gc-sparkline\">\n <div className=\"gc-grid\">\n <div className=\"gc-grid-line\" />\n <div className=\"gc-grid-line\" />\n <div className=\"gc-grid-line\" />\n </div>\n {data.map((v, i) => {\n if (!hydrated || v === 0)\n return <div key={i} className=\"gc-col\" />;\n const count = Math.max(\n 1,\n Math.round(Math.sqrt(v / max) * rows),\n );\n return (\n <BarCol\n key={i}\n count={count}\n trigger={trigger}\n isActive={i === active}\n onEnter={() => setActive(i)}\n onLeave={() => setActive(-1)}\n glyphs={glyphs}\n animationMs={animationMs}\n color={colors[i]}\n />\n );\n })}\n </div>\n {showXAxis && (\n <div className=\"gc-x-axis\" ref={xAxisRef}>\n {formattedDateTicks.map((t) => (\n <span\n key={t.index}\n className=\"gc-tick\"\n style={{\n left: `${((t.index + 0.5) / data.length) * 100}%`,\n }}\n >\n {t.label}\n </span>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n","/**\n * Resample a data array (and optional labels) to a target number of columns.\n *\n * - If `values.length < target`, pads with leading zeros (and back-extrapolates\n * labels from the first two timestamps).\n * - If `values.length > target`, sums values into evenly sized buckets.\n * - If equal, returns as-is.\n */\nexport function resample(\n values: number[],\n labels: string[] | undefined,\n target: number,\n): { data: number[]; labels: string[] | undefined } {\n if (values.length === 0) return { data: [], labels: undefined };\n\n if (values.length <= target) {\n const pad = target - values.length;\n let paddedLabels: string[] | undefined;\n\n if (labels && labels.length >= 2) {\n const t0 = new Date(labels[0]).getTime();\n const t1 = new Date(labels[1]).getTime();\n const step = t1 - t0;\n\n if (!isNaN(t0) && !isNaN(t1) && step > 0) {\n paddedLabels = [\n ...Array.from({ length: pad }, (_, i) =>\n new Date(t0 - (pad - i) * step).toISOString(),\n ),\n ...labels,\n ];\n } else {\n paddedLabels = [...Array(pad).fill(\"\"), ...labels];\n }\n } else {\n paddedLabels = labels ? [...Array(pad).fill(\"\"), ...labels] : undefined;\n }\n\n return {\n data: [...Array(pad).fill(0), ...values],\n labels: paddedLabels,\n };\n }\n\n // Downsample: sum values into target buckets\n const ratio = values.length / target;\n const out: number[] = [];\n const outLabels: string[] = [];\n\n for (let i = 0; i < target; i++) {\n const start = Math.floor(i * ratio);\n const end = Math.floor((i + 1) * ratio);\n let sum = 0;\n for (let j = start; j < end; j++) sum += values[j];\n out.push(sum);\n if (labels) outLabels.push(labels[start]);\n }\n\n return { data: out, labels: labels ? outLabels : undefined };\n}\n","/** Format a number with compact suffixes: 1.2B, 3.4M, 5.6K */\nexport function formatCompact(n: number): string {\n if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;\n if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;\n if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;\n return n.toLocaleString();\n}\n\nconst MONTHS = [\n \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\",\n];\n\n/** Format an ISO date string as \"Mon Day HH:MM\" or \"Mon Day\" */\nexport function formatDate(iso: string): string {\n const d = new Date(iso);\n if (isNaN(d.getTime())) return iso;\n const mon = MONTHS[d.getMonth()];\n const day = d.getDate();\n const h = d.getHours();\n\n if (h !== 0 || iso.includes(\"T\") || iso.includes(\" \")) {\n const hh = String(h).padStart(2, \"0\");\n const mm = String(d.getMinutes()).padStart(2, \"0\");\n return `${mon} ${day} ${hh}:${mm}`;\n }\n return `${mon} ${day}`;\n}\n","import { formatDate } from \"./format.js\";\n\n/** Hide ticks that overlap horizontally within a container element. */\nexport function cullTicks(el: HTMLElement) {\n const ticks = el.querySelectorAll<HTMLElement>(\".gc-tick\");\n const MIN_GAP = 12;\n let lastRight = -Infinity;\n\n for (const tick of ticks) {\n tick.style.visibility = \"visible\";\n const rect = tick.getBoundingClientRect();\n if (rect.left < lastRight + MIN_GAP) {\n tick.style.visibility = \"hidden\";\n } else {\n lastRight = rect.right;\n }\n }\n}\n\n/** Pick evenly spaced date ticks from a label array. */\nexport function pickDateTicks(\n labels: string[],\n count: number,\n): { index: number; label: string }[] {\n const entries = labels\n .map((l, i) => ({ label: l, index: i }))\n .filter((e) => e.label);\n\n if (entries.length === 0) return [];\n\n const pick =\n entries.length <= count\n ? entries\n : Array.from({ length: count }, (_, i) => {\n const idx = Math.round((i / (count - 1)) * (entries.length - 1));\n return entries[idx];\n });\n\n return pick.map((e) => ({ index: e.index, label: formatDate(e.label) }));\n}\n"],"mappings":"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","names":["index_exports","__export","GlitchSparkline","formatCompact","formatDate","pickDateTicks","resample","__toCommonJS","import_react","import_use_scramble","resample","values","labels","target","pad","paddedLabels","t0","t1","step","_","i","ratio","out","outLabels","start","end","sum","j","formatCompact","n","MONTHS","formatDate","iso","d","mon","day","h","hh","mm","cullTicks","el","ticks","MIN_GAP","lastRight","tick","rect","pickDateTicks","labels","count","entries","l","i","e","_","idx","formatDate","import_jsx_runtime","DEFAULT_ROWS","DEFAULT_COLS","GLYPHS","ANIM_MS","useHideOverlappingTicks","ref","tickKey","cullTicks","el","ro","resetCells","children","i","child","BarCol","count","trigger","isActive","onEnter","onLeave","glyphs","animationMs","color","rafRef","startRef","cellsRef","orderRef","wasActiveRef","deactivating","order","_","j","animate","now","elapsed","progress","settled","k","idx","ScrambleLabel","text","parseHex","hex","h","lerpColor","a","b","t","r1","g1","b1","r2","g2","b2","r","g","bl","resolveColors","segment","lo","hi","resolveAxisConfig","cfg","defaults","computeYTicks","max","ticks","GlitchSparkline","rawData","rawLabels","className","rows","columns","yAxisProp","xAxisProp","formatValue","colorProp","tickCountLegacy","active","setActive","hydrated","setHydrated","xAxisRef","yAxis","xAxis","fmtValue","n","formatCompact","fmtYTick","_v","data","labels","resample","xTickCount","formattedDateTicks","pickDateTicks","yTicks","showYAxis","showXAxis","colors","wrapClass","v"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/** Configuration for an axis. Pass `false` to hide the axis entirely. */
|
|
4
|
+
interface AxisConfig {
|
|
5
|
+
/** Whether to render this axis (default true). */
|
|
6
|
+
show?: boolean;
|
|
7
|
+
/** Custom tick formatter. Receives the tick value and its index. */
|
|
8
|
+
tickFormat?: (value: number, index: number) => string;
|
|
9
|
+
/** Approximate number of ticks to display. */
|
|
10
|
+
tickCount?: number;
|
|
11
|
+
/** Explicit tick positions (y-axis only). Overrides tickCount. */
|
|
12
|
+
tickValues?: number[];
|
|
13
|
+
}
|
|
14
|
+
interface GlitchSparklineProps {
|
|
15
|
+
/** Numeric data values (one per time bucket). */
|
|
16
|
+
data: number[];
|
|
17
|
+
/** ISO date strings matching each data point — used for x-axis labels. */
|
|
18
|
+
labels?: string[];
|
|
19
|
+
/** Additional CSS class name on the root wrapper. */
|
|
20
|
+
className?: string;
|
|
21
|
+
/** Bump this value to re-trigger the scramble entrance animation. */
|
|
22
|
+
trigger?: number;
|
|
23
|
+
/** Number of vertical cells per column (default 16). */
|
|
24
|
+
rows?: number;
|
|
25
|
+
/** Target number of columns — data is resampled to fit (default 48). */
|
|
26
|
+
columns?: number;
|
|
27
|
+
/** Custom glyph characters used during the scramble animation. */
|
|
28
|
+
glyphs?: string;
|
|
29
|
+
/** Duration of the scramble animation in ms (default 400). */
|
|
30
|
+
animationMs?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Y-axis configuration, or `false` to hide.
|
|
33
|
+
*
|
|
34
|
+
* Defaults: `{ show: true, tickCount: 3 }` with compact number formatting.
|
|
35
|
+
*
|
|
36
|
+
* ```tsx
|
|
37
|
+
* yAxis={{ tickFormat: (v) => `${v}%`, tickCount: 5 }}
|
|
38
|
+
* yAxis={{ tickValues: [0, 50, 100], tickFormat: (v) => `${v}%` }}
|
|
39
|
+
* yAxis={false} // hide y-axis
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
yAxis?: AxisConfig | false;
|
|
43
|
+
/**
|
|
44
|
+
* X-axis configuration, or `false` to hide.
|
|
45
|
+
*
|
|
46
|
+
* Shown only when `labels` are provided. Defaults: `{ show: true, tickCount: 10 }`
|
|
47
|
+
* with built-in date formatting.
|
|
48
|
+
*
|
|
49
|
+
* ```tsx
|
|
50
|
+
* xAxis={{ tickCount: 5, tickFormat: (v) => new Date(v).toLocaleDateString() }}
|
|
51
|
+
* xAxis={false} // hide x-axis
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
xAxis?: AxisConfig | false;
|
|
55
|
+
/**
|
|
56
|
+
* Custom value formatter for the hover label.
|
|
57
|
+
* Falls back to y-axis tickFormat, then the built-in compact formatter.
|
|
58
|
+
*/
|
|
59
|
+
formatValue?: (n: number) => string;
|
|
60
|
+
/**
|
|
61
|
+
* Bar color. A single CSS color string applies to all columns.
|
|
62
|
+
* An array of colors is interpolated across columns (gradient / rainbow).
|
|
63
|
+
*
|
|
64
|
+
* ```tsx
|
|
65
|
+
* color="#ec4899" // single color
|
|
66
|
+
* color={["#ec4899", "#8b5cf6", "#06b6d4"]} // gradient
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* When omitted, bars use the default theme colors (--gc-text-muted / --gc-text-secondary).
|
|
70
|
+
*/
|
|
71
|
+
color?: string | string[];
|
|
72
|
+
/**
|
|
73
|
+
* @deprecated Use `xAxis={{ tickCount }}` instead. Kept for backward compat.
|
|
74
|
+
*/
|
|
75
|
+
tickCount?: number;
|
|
76
|
+
}
|
|
77
|
+
declare function GlitchSparkline({ data: rawData, labels: rawLabels, className, trigger, rows, columns, glyphs, animationMs, yAxis: yAxisProp, xAxis: xAxisProp, formatValue, color: colorProp, tickCount: tickCountLegacy, }: GlitchSparklineProps): react_jsx_runtime.JSX.Element | null;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resample a data array (and optional labels) to a target number of columns.
|
|
81
|
+
*
|
|
82
|
+
* - If `values.length < target`, pads with leading zeros (and back-extrapolates
|
|
83
|
+
* labels from the first two timestamps).
|
|
84
|
+
* - If `values.length > target`, sums values into evenly sized buckets.
|
|
85
|
+
* - If equal, returns as-is.
|
|
86
|
+
*/
|
|
87
|
+
declare function resample(values: number[], labels: string[] | undefined, target: number): {
|
|
88
|
+
data: number[];
|
|
89
|
+
labels: string[] | undefined;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** Format a number with compact suffixes: 1.2B, 3.4M, 5.6K */
|
|
93
|
+
declare function formatCompact(n: number): string;
|
|
94
|
+
/** Format an ISO date string as "Mon Day HH:MM" or "Mon Day" */
|
|
95
|
+
declare function formatDate(iso: string): string;
|
|
96
|
+
|
|
97
|
+
/** Pick evenly spaced date ticks from a label array. */
|
|
98
|
+
declare function pickDateTicks(labels: string[], count: number): {
|
|
99
|
+
index: number;
|
|
100
|
+
label: string;
|
|
101
|
+
}[];
|
|
102
|
+
|
|
103
|
+
export { type AxisConfig, GlitchSparkline, type GlitchSparklineProps, formatCompact, formatDate, pickDateTicks, resample };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/** Configuration for an axis. Pass `false` to hide the axis entirely. */
|
|
4
|
+
interface AxisConfig {
|
|
5
|
+
/** Whether to render this axis (default true). */
|
|
6
|
+
show?: boolean;
|
|
7
|
+
/** Custom tick formatter. Receives the tick value and its index. */
|
|
8
|
+
tickFormat?: (value: number, index: number) => string;
|
|
9
|
+
/** Approximate number of ticks to display. */
|
|
10
|
+
tickCount?: number;
|
|
11
|
+
/** Explicit tick positions (y-axis only). Overrides tickCount. */
|
|
12
|
+
tickValues?: number[];
|
|
13
|
+
}
|
|
14
|
+
interface GlitchSparklineProps {
|
|
15
|
+
/** Numeric data values (one per time bucket). */
|
|
16
|
+
data: number[];
|
|
17
|
+
/** ISO date strings matching each data point — used for x-axis labels. */
|
|
18
|
+
labels?: string[];
|
|
19
|
+
/** Additional CSS class name on the root wrapper. */
|
|
20
|
+
className?: string;
|
|
21
|
+
/** Bump this value to re-trigger the scramble entrance animation. */
|
|
22
|
+
trigger?: number;
|
|
23
|
+
/** Number of vertical cells per column (default 16). */
|
|
24
|
+
rows?: number;
|
|
25
|
+
/** Target number of columns — data is resampled to fit (default 48). */
|
|
26
|
+
columns?: number;
|
|
27
|
+
/** Custom glyph characters used during the scramble animation. */
|
|
28
|
+
glyphs?: string;
|
|
29
|
+
/** Duration of the scramble animation in ms (default 400). */
|
|
30
|
+
animationMs?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Y-axis configuration, or `false` to hide.
|
|
33
|
+
*
|
|
34
|
+
* Defaults: `{ show: true, tickCount: 3 }` with compact number formatting.
|
|
35
|
+
*
|
|
36
|
+
* ```tsx
|
|
37
|
+
* yAxis={{ tickFormat: (v) => `${v}%`, tickCount: 5 }}
|
|
38
|
+
* yAxis={{ tickValues: [0, 50, 100], tickFormat: (v) => `${v}%` }}
|
|
39
|
+
* yAxis={false} // hide y-axis
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
yAxis?: AxisConfig | false;
|
|
43
|
+
/**
|
|
44
|
+
* X-axis configuration, or `false` to hide.
|
|
45
|
+
*
|
|
46
|
+
* Shown only when `labels` are provided. Defaults: `{ show: true, tickCount: 10 }`
|
|
47
|
+
* with built-in date formatting.
|
|
48
|
+
*
|
|
49
|
+
* ```tsx
|
|
50
|
+
* xAxis={{ tickCount: 5, tickFormat: (v) => new Date(v).toLocaleDateString() }}
|
|
51
|
+
* xAxis={false} // hide x-axis
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
xAxis?: AxisConfig | false;
|
|
55
|
+
/**
|
|
56
|
+
* Custom value formatter for the hover label.
|
|
57
|
+
* Falls back to y-axis tickFormat, then the built-in compact formatter.
|
|
58
|
+
*/
|
|
59
|
+
formatValue?: (n: number) => string;
|
|
60
|
+
/**
|
|
61
|
+
* Bar color. A single CSS color string applies to all columns.
|
|
62
|
+
* An array of colors is interpolated across columns (gradient / rainbow).
|
|
63
|
+
*
|
|
64
|
+
* ```tsx
|
|
65
|
+
* color="#ec4899" // single color
|
|
66
|
+
* color={["#ec4899", "#8b5cf6", "#06b6d4"]} // gradient
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* When omitted, bars use the default theme colors (--gc-text-muted / --gc-text-secondary).
|
|
70
|
+
*/
|
|
71
|
+
color?: string | string[];
|
|
72
|
+
/**
|
|
73
|
+
* @deprecated Use `xAxis={{ tickCount }}` instead. Kept for backward compat.
|
|
74
|
+
*/
|
|
75
|
+
tickCount?: number;
|
|
76
|
+
}
|
|
77
|
+
declare function GlitchSparkline({ data: rawData, labels: rawLabels, className, trigger, rows, columns, glyphs, animationMs, yAxis: yAxisProp, xAxis: xAxisProp, formatValue, color: colorProp, tickCount: tickCountLegacy, }: GlitchSparklineProps): react_jsx_runtime.JSX.Element | null;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resample a data array (and optional labels) to a target number of columns.
|
|
81
|
+
*
|
|
82
|
+
* - If `values.length < target`, pads with leading zeros (and back-extrapolates
|
|
83
|
+
* labels from the first two timestamps).
|
|
84
|
+
* - If `values.length > target`, sums values into evenly sized buckets.
|
|
85
|
+
* - If equal, returns as-is.
|
|
86
|
+
*/
|
|
87
|
+
declare function resample(values: number[], labels: string[] | undefined, target: number): {
|
|
88
|
+
data: number[];
|
|
89
|
+
labels: string[] | undefined;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** Format a number with compact suffixes: 1.2B, 3.4M, 5.6K */
|
|
93
|
+
declare function formatCompact(n: number): string;
|
|
94
|
+
/** Format an ISO date string as "Mon Day HH:MM" or "Mon Day" */
|
|
95
|
+
declare function formatDate(iso: string): string;
|
|
96
|
+
|
|
97
|
+
/** Pick evenly spaced date ticks from a label array. */
|
|
98
|
+
declare function pickDateTicks(labels: string[], count: number): {
|
|
99
|
+
index: number;
|
|
100
|
+
label: string;
|
|
101
|
+
}[];
|
|
102
|
+
|
|
103
|
+
export { type AxisConfig, GlitchSparkline, type GlitchSparklineProps, formatCompact, formatDate, pickDateTicks, resample };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{useState as G,useEffect as O,useLayoutEffect as j,useRef as N}from"react";import{useScramble as W}from"use-scramble";function D(t,e,n){if(t.length===0)return{data:[],labels:void 0};if(t.length<=n){let o=n-t.length,a;if(e&&e.length>=2){let f=new Date(e[0]).getTime(),d=new Date(e[1]).getTime(),u=d-f;!isNaN(f)&&!isNaN(d)&&u>0?a=[...Array.from({length:o},(M,y)=>new Date(f-(o-y)*u).toISOString()),...e]:a=[...Array(o).fill(""),...e]}else a=e?[...Array(o).fill(""),...e]:void 0;return{data:[...Array(o).fill(0),...t],labels:a}}let i=t.length/n,r=[],s=[];for(let o=0;o<n;o++){let a=Math.floor(o*i),f=Math.floor((o+1)*i),d=0;for(let u=a;u<f;u++)d+=t[u];r.push(d),e&&s.push(e[a])}return{data:r,labels:e?s:void 0}}function E(t){return t>=1e9?`${(t/1e9).toFixed(1)}B`:t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}K`:t.toLocaleString()}var K=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function $(t){let e=new Date(t);if(isNaN(e.getTime()))return t;let n=K[e.getMonth()],i=e.getDate(),r=e.getHours();if(r!==0||t.includes("T")||t.includes(" ")){let s=String(r).padStart(2,"0"),o=String(e.getMinutes()).padStart(2,"0");return`${n} ${i} ${s}:${o}`}return`${n} ${i}`}function H(t){let e=t.querySelectorAll(".gc-tick"),n=12,i=-1/0;for(let r of e){r.style.visibility="visible";let s=r.getBoundingClientRect();s.left<i+n?r.style.visibility="hidden":i=s.right}}function R(t,e){let n=t.map((r,s)=>({label:r,index:s})).filter(r=>r.label);return n.length===0?[]:(n.length<=e?n:Array.from({length:e},(r,s)=>{let o=Math.round(s/(e-1)*(n.length-1));return n[o]})).map(r=>({index:r.index,label:$(r.label)}))}import{jsx as m,jsxs as S}from"react/jsx-runtime";var X=16,Q=48,Z="\u2592\u2591\u259A\u259E#@%&?!~^*+",tt=400;function et(t,e){j(()=>{t.current&&H(t.current)},[t,e]),O(()=>{let n=t.current;if(!n)return;let i=new ResizeObserver(()=>H(n));return i.observe(n),()=>i.disconnect()},[t,e])}function nt(t){let e=t.children;for(let n=0;n<e.length;n++){let i=e[n];i.textContent="",i.classList.remove("scrambling")}}function rt({count:t,trigger:e,isActive:n,onEnter:i,onLeave:r,glyphs:s,animationMs:o,color:a}){let f=N(0),d=N(0),u=N(null),M=N([]),y=N(!1);return j(()=>{let g=u.current;if(g)for(let x=0;x<g.children.length;x++){let b=g.children[x];b.textContent=s[Math.floor(Math.random()*s.length)],b.classList.add("scrambling")}},[t,e,s]),O(()=>{let g=u.current;if(!g)return;let x=!n&&y.current;if(y.current=n,x){cancelAnimationFrame(f.current),nt(g);return}let b=Array.from({length:g.children.length},(h,l)=>l);for(let h=b.length-1;h>0;h--){let l=Math.floor(Math.random()*(h+1));[b[h],b[l]]=[b[l],b[h]]}M.current=b,d.current=0;let L=h=>{if(!u.current)return;d.current===0&&(d.current=h);let l=h-d.current,k=Math.min(l/o,1),A=u.current.children,w=Math.floor(k*A.length);for(let p=0;p<A.length;p++){let T=M.current[p];if(T==null||T>=A.length)continue;let C=A[T];p<w?(C.textContent="",C.classList.remove("scrambling")):(C.textContent=s[Math.floor(Math.random()*s.length)],C.classList.add("scrambling"))}k<1&&(f.current=requestAnimationFrame(L))};return f.current=requestAnimationFrame(L),()=>cancelAnimationFrame(f.current)},[n,t,e,s,o]),m("div",{className:`gc-col${n?" active":""}`,onMouseEnter:i,onMouseLeave:r,children:m("div",{className:"gc-bar",ref:u,children:Array.from({length:t},(g,x)=>m("div",{className:"gc-cell",style:a?{background:a,"--gc-col-color":a}:void 0},x))})})}function it({text:t}){let{ref:e}=W({text:t,speed:.8,scramble:5,overdrive:!1});return m("span",{ref:e,className:"gc-label"})}function P(t){let e=t.replace("#",""),n=e.length===3?parseInt(e[0]+e[0]+e[1]+e[1]+e[2]+e[2],16):parseInt(e,16);return[n>>16&255,n>>8&255,n&255]}function st(t,e,n){let[i,r,s]=P(t),[o,a,f]=P(e),d=Math.round(i+(o-i)*n),u=Math.round(r+(a-r)*n),M=Math.round(s+(f-s)*n);return`rgb(${d},${u},${M})`}function ot(t,e){return t?typeof t=="string"?new Array(e).fill(t):t.length===1?new Array(e).fill(t[0]):Array.from({length:e},(n,i)=>{let s=(e===1?0:i/(e-1))*(t.length-1),o=Math.floor(s),a=Math.min(o+1,t.length-1);return st(t[o],t[a],s-o)}):new Array(e).fill(void 0)}function V(t,e){return t===!1?!1:{show:t?.show??e.show,tickCount:t?.tickCount??e.tickCount,tickFormat:t?.tickFormat,tickValues:t?.tickValues}}function ct(t,e){if(e.tickValues)return e.tickValues;let n=e.tickCount;if(n<=1)return[t];if(n===2)return[t,0];let i=[];for(let r=0;r<n;r++)i.push(Math.round(t*(1-r/(n-1))));return i}function at({data:t,labels:e,className:n,trigger:i,rows:r=X,columns:s=Q,glyphs:o=Z,animationMs:a=tt,yAxis:f,xAxis:d,formatValue:u,color:M,tickCount:y}){let[g,x]=G(-1),[b,L]=G(!1),h=N(null);O(()=>L(!0),[]);let l=V(f,{show:!0,tickCount:3}),k=V(d,{show:!0,tickCount:y??10}),A=u??(l&&l.tickFormat?c=>l.tickFormat(c,0):E),w=l&&l.tickFormat?l.tickFormat:c=>A(c),{data:p,labels:T}=t.length>0?D(t,e,s):{data:[],labels:void 0},C=Math.max(...p,0),I=k?k.tickCount:10,F=(T&&k?R(T,I):[]).map((c,v)=>({...c,label:k&&k.tickFormat?k.tickFormat(c.index,v):c.label})),q=F.map(c=>c.index).join(",");if(et(h,q),p.length===0)return null;let B=l?ct(C,l):[],Y=l!==!1&&l.show,_=k!==!1&&k.show&&F.length>0,J=ot(M,p.length),U=["gc-wrap",!_&&"gc-no-x",n].filter(Boolean).join(" ");return S("div",{className:U,children:[Y&&m("div",{className:"gc-y-axis",children:B.map((c,v)=>m("span",{className:"gc-tick",children:w(c,v)},`${c}-${v}`))}),S("div",{className:"gc-chart",children:[m("div",{className:"gc-value",children:g>=0&&p[g]>0&&m(it,{text:A(p[g])})}),S("div",{className:"gc-sparkline",children:[S("div",{className:"gc-grid",children:[m("div",{className:"gc-grid-line"}),m("div",{className:"gc-grid-line"}),m("div",{className:"gc-grid-line"})]}),p.map((c,v)=>{if(!b||c===0)return m("div",{className:"gc-col"},v);let z=Math.max(1,Math.round(Math.sqrt(c/C)*r));return m(rt,{count:z,trigger:i,isActive:v===g,onEnter:()=>x(v),onLeave:()=>x(-1),glyphs:o,animationMs:a,color:J[v]},v)})]}),_&&m("div",{className:"gc-x-axis",ref:h,children:F.map(c=>m("span",{className:"gc-tick",style:{left:`${(c.index+.5)/p.length*100}%`},children:c.label},c.index))})]})]})}export{at as GlitchSparkline,E as formatCompact,$ as formatDate,R as pickDateTicks,D as resample};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sparkline.tsx","../src/utils/resample.ts","../src/utils/format.ts","../src/utils/ticks.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useState,\n useEffect,\n useLayoutEffect,\n useRef,\n type RefObject,\n} from \"react\";\nimport { useScramble } from \"use-scramble\";\nimport { resample } from \"./utils/resample.js\";\nimport { formatCompact } from \"./utils/format.js\";\nimport { cullTicks, pickDateTicks } from \"./utils/ticks.js\";\n\n/** Default constants — can be overridden via props */\nconst DEFAULT_ROWS = 16;\nconst DEFAULT_COLS = 48;\nconst GLYPHS = \"▒░▚▞#@%&?!~^*+\";\nconst ANIM_MS = 400;\n\n/** Configuration for an axis. Pass `false` to hide the axis entirely. */\nexport interface AxisConfig {\n /** Whether to render this axis (default true). */\n show?: boolean;\n /** Custom tick formatter. Receives the tick value and its index. */\n tickFormat?: (value: number, index: number) => string;\n /** Approximate number of ticks to display. */\n tickCount?: number;\n /** Explicit tick positions (y-axis only). Overrides tickCount. */\n tickValues?: number[];\n}\n\nexport interface GlitchSparklineProps {\n /** Numeric data values (one per time bucket). */\n data: number[];\n /** ISO date strings matching each data point — used for x-axis labels. */\n labels?: string[];\n /** Additional CSS class name on the root wrapper. */\n className?: string;\n /** Bump this value to re-trigger the scramble entrance animation. */\n trigger?: number;\n /** Number of vertical cells per column (default 16). */\n rows?: number;\n /** Target number of columns — data is resampled to fit (default 48). */\n columns?: number;\n /** Custom glyph characters used during the scramble animation. */\n glyphs?: string;\n /** Duration of the scramble animation in ms (default 400). */\n animationMs?: number;\n\n /**\n * Y-axis configuration, or `false` to hide.\n *\n * Defaults: `{ show: true, tickCount: 3 }` with compact number formatting.\n *\n * ```tsx\n * yAxis={{ tickFormat: (v) => `${v}%`, tickCount: 5 }}\n * yAxis={{ tickValues: [0, 50, 100], tickFormat: (v) => `${v}%` }}\n * yAxis={false} // hide y-axis\n * ```\n */\n yAxis?: AxisConfig | false;\n\n /**\n * X-axis configuration, or `false` to hide.\n *\n * Shown only when `labels` are provided. Defaults: `{ show: true, tickCount: 10 }`\n * with built-in date formatting.\n *\n * ```tsx\n * xAxis={{ tickCount: 5, tickFormat: (v) => new Date(v).toLocaleDateString() }}\n * xAxis={false} // hide x-axis\n * ```\n */\n xAxis?: AxisConfig | false;\n\n /**\n * Custom value formatter for the hover label.\n * Falls back to y-axis tickFormat, then the built-in compact formatter.\n */\n formatValue?: (n: number) => string;\n\n /**\n * Bar color. A single CSS color string applies to all columns.\n * An array of colors is interpolated across columns (gradient / rainbow).\n *\n * ```tsx\n * color=\"#ec4899\" // single color\n * color={[\"#ec4899\", \"#8b5cf6\", \"#06b6d4\"]} // gradient\n * ```\n *\n * When omitted, bars use the default theme colors (--gc-text-muted / --gc-text-secondary).\n */\n color?: string | string[];\n\n /**\n * @deprecated Use `xAxis={{ tickCount }}` instead. Kept for backward compat.\n */\n tickCount?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Hooks\n// ---------------------------------------------------------------------------\n\nfunction useHideOverlappingTicks(\n ref: RefObject<HTMLDivElement | null>,\n tickKey: string,\n) {\n useLayoutEffect(() => {\n if (ref.current) cullTicks(ref.current);\n }, [ref, tickKey]);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const ro = new ResizeObserver(() => cullTicks(el));\n ro.observe(el);\n return () => ro.disconnect();\n }, [ref, tickKey]);\n}\n\n// ---------------------------------------------------------------------------\n// Internal sub-components\n// ---------------------------------------------------------------------------\n\nfunction resetCells(el: HTMLElement) {\n const children = el.children;\n for (let i = 0; i < children.length; i++) {\n const child = children[i] as HTMLElement;\n child.textContent = \"\";\n child.classList.remove(\"scrambling\");\n }\n}\n\nfunction BarCol({\n count,\n trigger,\n isActive,\n onEnter,\n onLeave,\n glyphs,\n animationMs,\n color,\n}: {\n count: number;\n trigger?: number;\n isActive: boolean;\n onEnter: () => void;\n onLeave: () => void;\n glyphs: string;\n animationMs: number;\n color?: string;\n}) {\n const rafRef = useRef(0);\n const startRef = useRef(0);\n const cellsRef = useRef<HTMLDivElement>(null);\n const orderRef = useRef<number[]>([]);\n const wasActiveRef = useRef(false);\n\n useLayoutEffect(() => {\n const el = cellsRef.current;\n if (!el) return;\n for (let i = 0; i < el.children.length; i++) {\n const child = el.children[i] as HTMLElement;\n child.textContent = glyphs[Math.floor(Math.random() * glyphs.length)];\n child.classList.add(\"scrambling\");\n }\n }, [count, trigger, glyphs]);\n\n useEffect(() => {\n const el = cellsRef.current;\n if (!el) return;\n\n const deactivating = !isActive && wasActiveRef.current;\n wasActiveRef.current = isActive;\n\n if (deactivating) {\n cancelAnimationFrame(rafRef.current);\n resetCells(el);\n return;\n }\n\n // Build a shuffled settle order\n const order = Array.from({ length: el.children.length }, (_, i) => i);\n for (let i = order.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [order[i], order[j]] = [order[j], order[i]];\n }\n orderRef.current = order;\n startRef.current = 0;\n\n const animate = (now: number) => {\n if (!cellsRef.current) return;\n if (startRef.current === 0) startRef.current = now;\n\n const elapsed = now - startRef.current;\n const progress = Math.min(elapsed / animationMs, 1);\n const children = cellsRef.current.children;\n const settled = Math.floor(progress * children.length);\n\n for (let k = 0; k < children.length; k++) {\n const idx = orderRef.current[k];\n if (idx == null || idx >= children.length) continue;\n const child = children[idx] as HTMLElement;\n if (k < settled) {\n child.textContent = \"\";\n child.classList.remove(\"scrambling\");\n } else {\n child.textContent =\n glyphs[Math.floor(Math.random() * glyphs.length)];\n child.classList.add(\"scrambling\");\n }\n }\n\n if (progress < 1) {\n rafRef.current = requestAnimationFrame(animate);\n }\n };\n\n rafRef.current = requestAnimationFrame(animate);\n return () => cancelAnimationFrame(rafRef.current);\n }, [isActive, count, trigger, glyphs, animationMs]);\n\n return (\n <div\n className={`gc-col${isActive ? \" active\" : \"\"}`}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n >\n <div className=\"gc-bar\" ref={cellsRef}>\n {Array.from({ length: count }, (_, j) => (\n <div\n key={j}\n className=\"gc-cell\"\n style={color ? { background: color, \"--gc-col-color\": color } as React.CSSProperties : undefined}\n />\n ))}\n </div>\n </div>\n );\n}\n\nfunction ScrambleLabel({ text }: { text: string }) {\n const { ref } = useScramble({\n text,\n speed: 0.8,\n scramble: 5,\n overdrive: false,\n });\n\n return <span ref={ref} className=\"gc-label\" />;\n}\n\n// ---------------------------------------------------------------------------\n// Color helpers\n// ---------------------------------------------------------------------------\n\nfunction parseHex(hex: string): [number, number, number] {\n const h = hex.replace(\"#\", \"\");\n const n = h.length === 3\n ? parseInt(h[0]+h[0]+h[1]+h[1]+h[2]+h[2], 16)\n : parseInt(h, 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nfunction lerpColor(a: string, b: string, t: number): string {\n const [r1, g1, b1] = parseHex(a);\n const [r2, g2, b2] = parseHex(b);\n const r = Math.round(r1 + (r2 - r1) * t);\n const g = Math.round(g1 + (g2 - g1) * t);\n const bl = Math.round(b1 + (b2 - b1) * t);\n return `rgb(${r},${g},${bl})`;\n}\n\nfunction resolveColors(\n color: string | string[] | undefined,\n count: number,\n): (string | undefined)[] {\n if (!color) return new Array(count).fill(undefined);\n if (typeof color === \"string\") return new Array(count).fill(color);\n if (color.length === 1) return new Array(count).fill(color[0]);\n // Interpolate across stops\n return Array.from({ length: count }, (_, i) => {\n const t = count === 1 ? 0 : i / (count - 1);\n const segment = t * (color.length - 1);\n const lo = Math.floor(segment);\n const hi = Math.min(lo + 1, color.length - 1);\n return lerpColor(color[lo], color[hi], segment - lo);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Axis resolution helpers\n// ---------------------------------------------------------------------------\n\nfunction resolveAxisConfig(\n cfg: AxisConfig | false | undefined,\n defaults: Required<Pick<AxisConfig, \"show\" | \"tickCount\">>,\n): (AxisConfig & { show: boolean; tickCount: number }) | false {\n if (cfg === false) return false;\n return {\n show: cfg?.show ?? defaults.show,\n tickCount: cfg?.tickCount ?? defaults.tickCount,\n tickFormat: cfg?.tickFormat,\n tickValues: cfg?.tickValues,\n };\n}\n\nfunction computeYTicks(\n max: number,\n cfg: AxisConfig & { tickCount: number },\n): number[] {\n // Explicit tick values override everything\n if (cfg.tickValues) return cfg.tickValues;\n\n const count = cfg.tickCount;\n if (count <= 1) return [max];\n if (count === 2) return [max, 0];\n\n // Evenly spaced ticks from max to 0\n const ticks: number[] = [];\n for (let i = 0; i < count; i++) {\n ticks.push(Math.round(max * (1 - i / (count - 1))));\n }\n return ticks;\n}\n\n// ---------------------------------------------------------------------------\n// Main export\n// ---------------------------------------------------------------------------\n\nexport function GlitchSparkline({\n data: rawData,\n labels: rawLabels,\n className,\n trigger,\n rows = DEFAULT_ROWS,\n columns = DEFAULT_COLS,\n glyphs = GLYPHS,\n animationMs = ANIM_MS,\n yAxis: yAxisProp,\n xAxis: xAxisProp,\n formatValue,\n color: colorProp,\n tickCount: tickCountLegacy,\n}: GlitchSparklineProps) {\n const [active, setActive] = useState(-1);\n const [hydrated, setHydrated] = useState(false);\n const xAxisRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => setHydrated(true), []);\n\n // Resolve axis configs\n const yAxis = resolveAxisConfig(yAxisProp, { show: true, tickCount: 3 });\n const xAxis = resolveAxisConfig(xAxisProp, {\n show: true,\n tickCount: tickCountLegacy ?? 10,\n });\n\n // Value formatter: explicit > yAxis.tickFormat > built-in compact\n const fmtValue =\n formatValue ??\n (yAxis && yAxis.tickFormat\n ? (n: number) => yAxis.tickFormat!(n, 0)\n : formatCompact);\n\n // Y-axis tick formatter\n const fmtYTick =\n yAxis && yAxis.tickFormat ? yAxis.tickFormat : (_v: number) => fmtValue(_v);\n\n const { data, labels } =\n rawData.length > 0\n ? resample(rawData, rawLabels, columns)\n : { data: [], labels: undefined };\n\n const max = Math.max(...data, 0);\n\n // X-axis ticks\n const xTickCount = xAxis ? xAxis.tickCount : 10;\n const dateTicks =\n labels && xAxis ? pickDateTicks(labels, xTickCount) : [];\n\n // Apply custom x-axis formatter if provided\n const formattedDateTicks = dateTicks.map((t, i) => ({\n ...t,\n label: xAxis && xAxis.tickFormat\n ? xAxis.tickFormat(t.index, i)\n : t.label,\n }));\n\n const tickKey = formattedDateTicks.map((t) => t.index).join(\",\");\n useHideOverlappingTicks(xAxisRef, tickKey);\n\n if (data.length === 0) return null;\n\n // Y-axis ticks\n const yTicks = yAxis ? computeYTicks(max, yAxis) : [];\n const showYAxis = yAxis !== false && yAxis.show;\n const showXAxis = xAxis !== false && xAxis.show && formattedDateTicks.length > 0;\n\n // Resolve per-column colors\n const colors = resolveColors(colorProp, data.length);\n\n const wrapClass = [\n \"gc-wrap\",\n !showXAxis && \"gc-no-x\",\n className,\n ].filter(Boolean).join(\" \");\n\n return (\n <div className={wrapClass}>\n {showYAxis && (\n <div className=\"gc-y-axis\">\n {yTicks.map((v, i) => (\n <span key={`${v}-${i}`} className=\"gc-tick\">\n {fmtYTick(v, i)}\n </span>\n ))}\n </div>\n )}\n <div className=\"gc-chart\">\n <div className=\"gc-value\">\n {active >= 0 && data[active] > 0 && (\n <ScrambleLabel text={fmtValue(data[active])} />\n )}\n </div>\n <div className=\"gc-sparkline\">\n <div className=\"gc-grid\">\n <div className=\"gc-grid-line\" />\n <div className=\"gc-grid-line\" />\n <div className=\"gc-grid-line\" />\n </div>\n {data.map((v, i) => {\n if (!hydrated || v === 0)\n return <div key={i} className=\"gc-col\" />;\n const count = Math.max(\n 1,\n Math.round(Math.sqrt(v / max) * rows),\n );\n return (\n <BarCol\n key={i}\n count={count}\n trigger={trigger}\n isActive={i === active}\n onEnter={() => setActive(i)}\n onLeave={() => setActive(-1)}\n glyphs={glyphs}\n animationMs={animationMs}\n color={colors[i]}\n />\n );\n })}\n </div>\n {showXAxis && (\n <div className=\"gc-x-axis\" ref={xAxisRef}>\n {formattedDateTicks.map((t) => (\n <span\n key={t.index}\n className=\"gc-tick\"\n style={{\n left: `${((t.index + 0.5) / data.length) * 100}%`,\n }}\n >\n {t.label}\n </span>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n","/**\n * Resample a data array (and optional labels) to a target number of columns.\n *\n * - If `values.length < target`, pads with leading zeros (and back-extrapolates\n * labels from the first two timestamps).\n * - If `values.length > target`, sums values into evenly sized buckets.\n * - If equal, returns as-is.\n */\nexport function resample(\n values: number[],\n labels: string[] | undefined,\n target: number,\n): { data: number[]; labels: string[] | undefined } {\n if (values.length === 0) return { data: [], labels: undefined };\n\n if (values.length <= target) {\n const pad = target - values.length;\n let paddedLabels: string[] | undefined;\n\n if (labels && labels.length >= 2) {\n const t0 = new Date(labels[0]).getTime();\n const t1 = new Date(labels[1]).getTime();\n const step = t1 - t0;\n\n if (!isNaN(t0) && !isNaN(t1) && step > 0) {\n paddedLabels = [\n ...Array.from({ length: pad }, (_, i) =>\n new Date(t0 - (pad - i) * step).toISOString(),\n ),\n ...labels,\n ];\n } else {\n paddedLabels = [...Array(pad).fill(\"\"), ...labels];\n }\n } else {\n paddedLabels = labels ? [...Array(pad).fill(\"\"), ...labels] : undefined;\n }\n\n return {\n data: [...Array(pad).fill(0), ...values],\n labels: paddedLabels,\n };\n }\n\n // Downsample: sum values into target buckets\n const ratio = values.length / target;\n const out: number[] = [];\n const outLabels: string[] = [];\n\n for (let i = 0; i < target; i++) {\n const start = Math.floor(i * ratio);\n const end = Math.floor((i + 1) * ratio);\n let sum = 0;\n for (let j = start; j < end; j++) sum += values[j];\n out.push(sum);\n if (labels) outLabels.push(labels[start]);\n }\n\n return { data: out, labels: labels ? outLabels : undefined };\n}\n","/** Format a number with compact suffixes: 1.2B, 3.4M, 5.6K */\nexport function formatCompact(n: number): string {\n if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;\n if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;\n if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;\n return n.toLocaleString();\n}\n\nconst MONTHS = [\n \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\",\n];\n\n/** Format an ISO date string as \"Mon Day HH:MM\" or \"Mon Day\" */\nexport function formatDate(iso: string): string {\n const d = new Date(iso);\n if (isNaN(d.getTime())) return iso;\n const mon = MONTHS[d.getMonth()];\n const day = d.getDate();\n const h = d.getHours();\n\n if (h !== 0 || iso.includes(\"T\") || iso.includes(\" \")) {\n const hh = String(h).padStart(2, \"0\");\n const mm = String(d.getMinutes()).padStart(2, \"0\");\n return `${mon} ${day} ${hh}:${mm}`;\n }\n return `${mon} ${day}`;\n}\n","import { formatDate } from \"./format.js\";\n\n/** Hide ticks that overlap horizontally within a container element. */\nexport function cullTicks(el: HTMLElement) {\n const ticks = el.querySelectorAll<HTMLElement>(\".gc-tick\");\n const MIN_GAP = 12;\n let lastRight = -Infinity;\n\n for (const tick of ticks) {\n tick.style.visibility = \"visible\";\n const rect = tick.getBoundingClientRect();\n if (rect.left < lastRight + MIN_GAP) {\n tick.style.visibility = \"hidden\";\n } else {\n lastRight = rect.right;\n }\n }\n}\n\n/** Pick evenly spaced date ticks from a label array. */\nexport function pickDateTicks(\n labels: string[],\n count: number,\n): { index: number; label: string }[] {\n const entries = labels\n .map((l, i) => ({ label: l, index: i }))\n .filter((e) => e.label);\n\n if (entries.length === 0) return [];\n\n const pick =\n entries.length <= count\n ? entries\n : Array.from({ length: count }, (_, i) => {\n const idx = Math.round((i / (count - 1)) * (entries.length - 1));\n return entries[idx];\n });\n\n return pick.map((e) => ({ index: e.index, label: formatDate(e.label) }));\n}\n"],"mappings":"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","names":["useState","useEffect","useLayoutEffect","useRef","useScramble","resample","values","labels","target","pad","paddedLabels","t0","t1","step","_","i","ratio","out","outLabels","start","end","sum","j","formatCompact","n","MONTHS","formatDate","iso","d","mon","day","h","hh","mm","cullTicks","el","ticks","MIN_GAP","lastRight","tick","rect","pickDateTicks","labels","count","entries","l","i","e","_","idx","formatDate","jsx","jsxs","DEFAULT_ROWS","DEFAULT_COLS","GLYPHS","ANIM_MS","useHideOverlappingTicks","ref","tickKey","useLayoutEffect","cullTicks","useEffect","el","ro","resetCells","children","i","child","BarCol","count","trigger","isActive","onEnter","onLeave","glyphs","animationMs","color","rafRef","useRef","startRef","cellsRef","orderRef","wasActiveRef","deactivating","order","_","j","animate","now","elapsed","progress","settled","k","idx","ScrambleLabel","text","useScramble","parseHex","hex","h","lerpColor","a","b","t","r1","g1","b1","r2","g2","b2","r","g","bl","resolveColors","segment","lo","hi","resolveAxisConfig","cfg","defaults","computeYTicks","max","ticks","GlitchSparkline","rawData","rawLabels","className","rows","columns","yAxisProp","xAxisProp","formatValue","colorProp","tickCountLegacy","active","setActive","useState","hydrated","setHydrated","xAxisRef","yAxis","xAxis","fmtValue","n","formatCompact","fmtYTick","_v","data","labels","resample","xTickCount","formattedDateTicks","pickDateTicks","yTicks","showYAxis","showXAxis","colors","wrapClass","v"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "glitch-charts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React bar charts with a scramble entrance animation. Pure divs, dark-mode native, ~15 kB",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"./styles": {
|
|
21
|
+
"types": "./dist/glitch-charts.css.d.ts",
|
|
22
|
+
"default": "./dist/glitch-charts.css"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"CHANGELOG.md"
|
|
30
|
+
],
|
|
31
|
+
"sideEffects": [
|
|
32
|
+
"*.css"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup && cp src/styles/glitch-charts.css dist/glitch-charts.css && cp src/styles.d.ts dist/glitch-charts.css.d.ts",
|
|
36
|
+
"dev": "tsup --watch",
|
|
37
|
+
"lint": "eslint src/",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": ">=18.0.0",
|
|
45
|
+
"react-dom": ">=18.0.0"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"use-scramble": "^2.2.15"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@testing-library/react": "^16.1.0",
|
|
52
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
53
|
+
"@types/react": "^19.0.0",
|
|
54
|
+
"@types/react-dom": "^19.0.0",
|
|
55
|
+
"eslint": "^9.0.0",
|
|
56
|
+
"jsdom": "^25.0.0",
|
|
57
|
+
"react": "^19.0.0",
|
|
58
|
+
"react-dom": "^19.0.0",
|
|
59
|
+
"tsup": "^8.0.0",
|
|
60
|
+
"typescript": "^5.7.0",
|
|
61
|
+
"vitest": "^3.0.0"
|
|
62
|
+
},
|
|
63
|
+
"keywords": [
|
|
64
|
+
"react",
|
|
65
|
+
"chart",
|
|
66
|
+
"sparkline",
|
|
67
|
+
"glitch",
|
|
68
|
+
"ascii",
|
|
69
|
+
|
|
70
|
+
"visualization",
|
|
71
|
+
"scramble",
|
|
72
|
+
"animation",
|
|
73
|
+
"bar-chart"
|
|
74
|
+
],
|
|
75
|
+
"license": "MIT",
|
|
76
|
+
"author": "Ivan Husarov",
|
|
77
|
+
"repository": {
|
|
78
|
+
"type": "git",
|
|
79
|
+
"url": "git+https://github.com/Houstoten/glitch-charts.git"
|
|
80
|
+
},
|
|
81
|
+
"homepage": "https://houstoten.github.io/glitch-charts/",
|
|
82
|
+
"bugs": {
|
|
83
|
+
"url": "https://github.com/Houstoten/glitch-charts/issues"
|
|
84
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=18"
|
|
87
|
+
}
|
|
88
|
+
}
|