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 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
+ [![npm version](https://img.shields.io/npm/v/glitch-charts.svg)](https://www.npmjs.com/package/glitch-charts)
6
+ [![license](https://img.shields.io/npm/l/glitch-charts.svg)](./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
+ }
@@ -0,0 +1,2 @@
1
+ declare const styles: string;
2
+ export default styles;
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":"yaAAA,IAAAA,GAAA,GAAAC,EAAAD,GAAA,qBAAAE,EAAA,kBAAAC,EAAA,eAAAC,EAAA,kBAAAC,EAAA,aAAAC,IAAA,eAAAC,EAAAP,ICEA,IAAAQ,EAMO,iBACPC,EAA4B,wBCDrB,SAASC,EACdC,EACAC,EACAC,EACkD,CAClD,GAAIF,EAAO,SAAW,EAAG,MAAO,CAAE,KAAM,CAAC,EAAG,OAAQ,MAAU,EAE9D,GAAIA,EAAO,QAAUE,EAAQ,CAC3B,IAAMC,EAAMD,EAASF,EAAO,OACxBI,EAEJ,GAAIH,GAAUA,EAAO,QAAU,EAAG,CAChC,IAAMI,EAAK,IAAI,KAAKJ,EAAO,CAAC,CAAC,EAAE,QAAQ,EACjCK,EAAK,IAAI,KAAKL,EAAO,CAAC,CAAC,EAAE,QAAQ,EACjCM,EAAOD,EAAKD,EAEd,CAAC,MAAMA,CAAE,GAAK,CAAC,MAAMC,CAAE,GAAKC,EAAO,EACrCH,EAAe,CACb,GAAG,MAAM,KAAK,CAAE,OAAQD,CAAI,EAAG,CAACK,EAAGC,IACjC,IAAI,KAAKJ,GAAMF,EAAMM,GAAKF,CAAI,EAAE,YAAY,CAC9C,EACA,GAAGN,CACL,EAEAG,EAAe,CAAC,GAAG,MAAMD,CAAG,EAAE,KAAK,EAAE,EAAG,GAAGF,CAAM,CAErD,MACEG,EAAeH,EAAS,CAAC,GAAG,MAAME,CAAG,EAAE,KAAK,EAAE,EAAG,GAAGF,CAAM,EAAI,OAGhE,MAAO,CACL,KAAM,CAAC,GAAG,MAAME,CAAG,EAAE,KAAK,CAAC,EAAG,GAAGH,CAAM,EACvC,OAAQI,CACV,CACF,CAGA,IAAMM,EAAQV,EAAO,OAASE,EACxBS,EAAgB,CAAC,EACjBC,EAAsB,CAAC,EAE7B,QAASH,EAAI,EAAGA,EAAIP,EAAQO,IAAK,CAC/B,IAAMI,EAAQ,KAAK,MAAMJ,EAAIC,CAAK,EAC5BI,EAAM,KAAK,OAAOL,EAAI,GAAKC,CAAK,EAClCK,EAAM,EACV,QAASC,EAAIH,EAAOG,EAAIF,EAAKE,IAAKD,GAAOf,EAAOgB,CAAC,EACjDL,EAAI,KAAKI,CAAG,EACRd,GAAQW,EAAU,KAAKX,EAAOY,CAAK,CAAC,CAC1C,CAEA,MAAO,CAAE,KAAMF,EAAK,OAAQV,EAASW,EAAY,MAAU,CAC7D,CC1DO,SAASK,EAAcC,EAAmB,CAC/C,OAAIA,GAAK,IAAY,IAAIA,EAAI,KAAK,QAAQ,CAAC,CAAC,IACxCA,GAAK,IAAY,IAAIA,EAAI,KAAK,QAAQ,CAAC,CAAC,IACxCA,GAAK,IAAY,IAAIA,EAAI,KAAK,QAAQ,CAAC,CAAC,IACrCA,EAAE,eAAe,CAC1B,CAEA,IAAMC,GAAS,CACb,MAAO,MAAO,MAAO,MAAO,MAAO,MACnC,MAAO,MAAO,MAAO,MAAO,MAAO,KACrC,EAGO,SAASC,EAAWC,EAAqB,CAC9C,IAAMC,EAAI,IAAI,KAAKD,CAAG,EACtB,GAAI,MAAMC,EAAE,QAAQ,CAAC,EAAG,OAAOD,EAC/B,IAAME,EAAMJ,GAAOG,EAAE,SAAS,CAAC,EACzBE,EAAMF,EAAE,QAAQ,EAChBG,EAAIH,EAAE,SAAS,EAErB,GAAIG,IAAM,GAAKJ,EAAI,SAAS,GAAG,GAAKA,EAAI,SAAS,GAAG,EAAG,CACrD,IAAMK,EAAK,OAAOD,CAAC,EAAE,SAAS,EAAG,GAAG,EAC9BE,EAAK,OAAOL,EAAE,WAAW,CAAC,EAAE,SAAS,EAAG,GAAG,EACjD,MAAO,GAAGC,CAAG,IAAIC,CAAG,IAAIE,CAAE,IAAIC,CAAE,EAClC,CACA,MAAO,GAAGJ,CAAG,IAAIC,CAAG,EACtB,CCxBO,SAASI,EAAUC,EAAiB,CACzC,IAAMC,EAAQD,EAAG,iBAA8B,UAAU,EACnDE,EAAU,GACZC,EAAY,KAEhB,QAAWC,KAAQH,EAAO,CACxBG,EAAK,MAAM,WAAa,UACxB,IAAMC,EAAOD,EAAK,sBAAsB,EACpCC,EAAK,KAAOF,EAAYD,EAC1BE,EAAK,MAAM,WAAa,SAExBD,EAAYE,EAAK,KAErB,CACF,CAGO,SAASC,EACdC,EACAC,EACoC,CACpC,IAAMC,EAAUF,EACb,IAAI,CAACG,EAAGC,KAAO,CAAE,MAAOD,EAAG,MAAOC,CAAE,EAAE,EACtC,OAAQC,GAAMA,EAAE,KAAK,EAExB,OAAIH,EAAQ,SAAW,EAAU,CAAC,GAGhCA,EAAQ,QAAUD,EACdC,EACA,MAAM,KAAK,CAAE,OAAQD,CAAM,EAAG,CAACK,EAAGF,IAAM,CACtC,IAAMG,EAAM,KAAK,MAAOH,GAAKH,EAAQ,IAAOC,EAAQ,OAAS,EAAE,EAC/D,OAAOA,EAAQK,CAAG,CACpB,CAAC,GAEK,IAAKF,IAAO,CAAE,MAAOA,EAAE,MAAO,MAAOG,EAAWH,EAAE,KAAK,CAAE,EAAE,CACzE,CHiMU,IAAAI,EAAA,6BAzNJC,GAAe,GACfC,GAAe,GACfC,GAAS,qCACTC,GAAU,IAuFhB,SAASC,GACPC,EACAC,EACA,IACA,mBAAgB,IAAM,CAChBD,EAAI,SAASE,EAAUF,EAAI,OAAO,CACxC,EAAG,CAACA,EAAKC,CAAO,CAAC,KAEjB,aAAU,IAAM,CACd,IAAME,EAAKH,EAAI,QACf,GAAI,CAACG,EAAI,OACT,IAAMC,EAAK,IAAI,eAAe,IAAMF,EAAUC,CAAE,CAAC,EACjD,OAAAC,EAAG,QAAQD,CAAE,EACN,IAAMC,EAAG,WAAW,CAC7B,EAAG,CAACJ,EAAKC,CAAO,CAAC,CACnB,CAMA,SAASI,GAAWF,EAAiB,CACnC,IAAMG,EAAWH,EAAG,SACpB,QAASI,EAAI,EAAGA,EAAID,EAAS,OAAQC,IAAK,CACxC,IAAMC,EAAQF,EAASC,CAAC,EACxBC,EAAM,YAAc,GACpBA,EAAM,UAAU,OAAO,YAAY,CACrC,CACF,CAEA,SAASC,GAAO,CACd,MAAAC,EACA,QAAAC,EACA,SAAAC,EACA,QAAAC,EACA,QAAAC,EACA,OAAAC,EACA,YAAAC,EACA,MAAAC,CACF,EASG,CACD,IAAMC,KAAS,UAAO,CAAC,EACjBC,KAAW,UAAO,CAAC,EACnBC,KAAW,UAAuB,IAAI,EACtCC,KAAW,UAAiB,CAAC,CAAC,EAC9BC,KAAe,UAAO,EAAK,EAEjC,4BAAgB,IAAM,CACpB,IAAMnB,EAAKiB,EAAS,QACpB,GAAKjB,EACL,QAASI,EAAI,EAAGA,EAAIJ,EAAG,SAAS,OAAQI,IAAK,CAC3C,IAAMC,EAAQL,EAAG,SAASI,CAAC,EAC3BC,EAAM,YAAcO,EAAO,KAAK,MAAM,KAAK,OAAO,EAAIA,EAAO,MAAM,CAAC,EACpEP,EAAM,UAAU,IAAI,YAAY,CAClC,CACF,EAAG,CAACE,EAAOC,EAASI,CAAM,CAAC,KAE3B,aAAU,IAAM,CACd,IAAMZ,EAAKiB,EAAS,QACpB,GAAI,CAACjB,EAAI,OAET,IAAMoB,EAAe,CAACX,GAAYU,EAAa,QAG/C,GAFAA,EAAa,QAAUV,EAEnBW,EAAc,CAChB,qBAAqBL,EAAO,OAAO,EACnCb,GAAWF,CAAE,EACb,MACF,CAGA,IAAMqB,EAAQ,MAAM,KAAK,CAAE,OAAQrB,EAAG,SAAS,MAAO,EAAG,CAACsB,EAAGlB,IAAMA,CAAC,EACpE,QAASA,EAAIiB,EAAM,OAAS,EAAGjB,EAAI,EAAGA,IAAK,CACzC,IAAMmB,EAAI,KAAK,MAAM,KAAK,OAAO,GAAKnB,EAAI,EAAE,EAC5C,CAACiB,EAAMjB,CAAC,EAAGiB,EAAME,CAAC,CAAC,EAAI,CAACF,EAAME,CAAC,EAAGF,EAAMjB,CAAC,CAAC,CAC5C,CACAc,EAAS,QAAUG,EACnBL,EAAS,QAAU,EAEnB,IAAMQ,EAAWC,GAAgB,CAC/B,GAAI,CAACR,EAAS,QAAS,OACnBD,EAAS,UAAY,IAAGA,EAAS,QAAUS,GAE/C,IAAMC,EAAUD,EAAMT,EAAS,QACzBW,EAAW,KAAK,IAAID,EAAUb,EAAa,CAAC,EAC5CV,EAAWc,EAAS,QAAQ,SAC5BW,EAAU,KAAK,MAAMD,EAAWxB,EAAS,MAAM,EAErD,QAAS0B,EAAI,EAAGA,EAAI1B,EAAS,OAAQ0B,IAAK,CACxC,IAAMC,EAAMZ,EAAS,QAAQW,CAAC,EAC9B,GAAIC,GAAO,MAAQA,GAAO3B,EAAS,OAAQ,SAC3C,IAAME,EAAQF,EAAS2B,CAAG,EACtBD,EAAID,GACNvB,EAAM,YAAc,GACpBA,EAAM,UAAU,OAAO,YAAY,IAEnCA,EAAM,YACJO,EAAO,KAAK,MAAM,KAAK,OAAO,EAAIA,EAAO,MAAM,CAAC,EAClDP,EAAM,UAAU,IAAI,YAAY,EAEpC,CAEIsB,EAAW,IACbZ,EAAO,QAAU,sBAAsBS,CAAO,EAElD,EAEA,OAAAT,EAAO,QAAU,sBAAsBS,CAAO,EACvC,IAAM,qBAAqBT,EAAO,OAAO,CAClD,EAAG,CAACN,EAAUF,EAAOC,EAASI,EAAQC,CAAW,CAAC,KAGhD,OAAC,OACC,UAAW,SAASJ,EAAW,UAAY,EAAE,GAC7C,aAAcC,EACd,aAAcC,EAEd,mBAAC,OAAI,UAAU,SAAS,IAAKM,EAC1B,eAAM,KAAK,CAAE,OAAQV,CAAM,EAAG,CAACe,EAAGC,OACjC,OAAC,OAEC,UAAU,UACV,MAAOT,EAAQ,CAAE,WAAYA,EAAO,iBAAkBA,CAAM,EAA2B,QAFlFS,CAGP,CACD,EACH,EACF,CAEJ,CAEA,SAASQ,GAAc,CAAE,KAAAC,CAAK,EAAqB,CACjD,GAAM,CAAE,IAAAnC,CAAI,KAAI,eAAY,CAC1B,KAAAmC,EACA,MAAO,GACP,SAAU,EACV,UAAW,EACb,CAAC,EAED,SAAO,OAAC,QAAK,IAAKnC,EAAK,UAAU,WAAW,CAC9C,CAMA,SAASoC,EAASC,EAAuC,CACvD,IAAMC,EAAID,EAAI,QAAQ,IAAK,EAAE,EACvB,EAAIC,EAAE,SAAW,EACnB,SAASA,EAAE,CAAC,EAAEA,EAAE,CAAC,EAAEA,EAAE,CAAC,EAAEA,EAAE,CAAC,EAAEA,EAAE,CAAC,EAAEA,EAAE,CAAC,EAAG,EAAE,EAC1C,SAASA,EAAG,EAAE,EAClB,MAAO,CAAE,GAAK,GAAM,IAAM,GAAK,EAAK,IAAK,EAAI,GAAG,CAClD,CAEA,SAASC,GAAUC,EAAWC,EAAWC,EAAmB,CAC1D,GAAM,CAACC,EAAIC,EAAIC,CAAE,EAAIT,EAASI,CAAC,EACzB,CAACM,EAAIC,EAAIC,CAAE,EAAIZ,EAASK,CAAC,EACzBQ,EAAI,KAAK,MAAMN,GAAMG,EAAKH,GAAMD,CAAC,EACjCQ,EAAI,KAAK,MAAMN,GAAMG,EAAKH,GAAMF,CAAC,EACjCS,EAAK,KAAK,MAAMN,GAAMG,EAAKH,GAAMH,CAAC,EACxC,MAAO,OAAOO,CAAC,IAAIC,CAAC,IAAIC,CAAE,GAC5B,CAEA,SAASC,GACPnC,EACAP,EACwB,CACxB,OAAKO,EACD,OAAOA,GAAU,SAAiB,IAAI,MAAMP,CAAK,EAAE,KAAKO,CAAK,EAC7DA,EAAM,SAAW,EAAU,IAAI,MAAMP,CAAK,EAAE,KAAKO,EAAM,CAAC,CAAC,EAEtD,MAAM,KAAK,CAAE,OAAQP,CAAM,EAAG,CAACe,EAAG,IAAM,CAE7C,IAAM4B,GADI3C,IAAU,EAAI,EAAI,GAAKA,EAAQ,KACpBO,EAAM,OAAS,GAC9BqC,EAAK,KAAK,MAAMD,CAAO,EACvBE,EAAK,KAAK,IAAID,EAAK,EAAGrC,EAAM,OAAS,CAAC,EAC5C,OAAOsB,GAAUtB,EAAMqC,CAAE,EAAGrC,EAAMsC,CAAE,EAAGF,EAAUC,CAAE,CACrD,CAAC,EAVkB,IAAI,MAAM5C,CAAK,EAAE,KAAK,MAAS,CAWpD,CAMA,SAAS8C,EACPC,EACAC,EAC6D,CAC7D,OAAID,IAAQ,GAAc,GACnB,CACL,KAAMA,GAAK,MAAQC,EAAS,KAC5B,UAAWD,GAAK,WAAaC,EAAS,UACtC,WAAYD,GAAK,WACjB,WAAYA,GAAK,UACnB,CACF,CAEA,SAASE,GACPC,EACAH,EACU,CAEV,GAAIA,EAAI,WAAY,OAAOA,EAAI,WAE/B,IAAM/C,EAAQ+C,EAAI,UAClB,GAAI/C,GAAS,EAAG,MAAO,CAACkD,CAAG,EAC3B,GAAIlD,IAAU,EAAG,MAAO,CAACkD,EAAK,CAAC,EAG/B,IAAMC,EAAkB,CAAC,EACzB,QAAStD,EAAI,EAAGA,EAAIG,EAAOH,IACzBsD,EAAM,KAAK,KAAK,MAAMD,GAAO,EAAIrD,GAAKG,EAAQ,GAAG,CAAC,EAEpD,OAAOmD,CACT,CAMO,SAASC,EAAgB,CAC9B,KAAMC,EACN,OAAQC,EACR,UAAAC,EACA,QAAAtD,EACA,KAAAuD,EAAOvE,GACP,QAAAwE,EAAUvE,GACV,OAAAmB,EAASlB,GACT,YAAAmB,EAAclB,GACd,MAAOsE,EACP,MAAOC,EACP,YAAAC,EACA,MAAOC,EACP,UAAWC,CACb,EAAyB,CACvB,GAAM,CAACC,EAAQC,CAAS,KAAI,YAAS,EAAE,EACjC,CAACC,EAAUC,CAAW,KAAI,YAAS,EAAK,EACxCC,KAAW,UAAuB,IAAI,KAE5C,aAAU,IAAMD,EAAY,EAAI,EAAG,CAAC,CAAC,EAGrC,IAAME,EAAQtB,EAAkBY,EAAW,CAAE,KAAM,GAAM,UAAW,CAAE,CAAC,EACjEW,EAAQvB,EAAkBa,EAAW,CACzC,KAAM,GACN,UAAWG,GAAmB,EAChC,CAAC,EAGKQ,EACJV,IACCQ,GAASA,EAAM,WACXG,GAAcH,EAAM,WAAYG,EAAG,CAAC,EACrCC,GAGAC,EACJL,GAASA,EAAM,WAAaA,EAAM,WAAcM,GAAeJ,EAASI,CAAE,EAEtE,CAAE,KAAAC,EAAM,OAAAC,CAAO,EACnBvB,EAAQ,OAAS,EACbwB,EAASxB,EAASC,EAAWG,CAAO,EACpC,CAAE,KAAM,CAAC,EAAG,OAAQ,MAAU,EAE9BP,EAAM,KAAK,IAAI,GAAGyB,EAAM,CAAC,EAGzBG,EAAaT,EAAQA,EAAM,UAAY,GAKvCU,GAHJH,GAAUP,EAAQW,EAAcJ,EAAQE,CAAU,EAAI,CAAC,GAGpB,IAAI,CAAC9C,EAAGnC,KAAO,CAClD,GAAGmC,EACH,MAAOqC,GAASA,EAAM,WAClBA,EAAM,WAAWrC,EAAE,MAAOnC,CAAC,EAC3BmC,EAAE,KACR,EAAE,EAEIzC,EAAUwF,EAAmB,IAAK/C,GAAMA,EAAE,KAAK,EAAE,KAAK,GAAG,EAG/D,GAFA3C,GAAwB8E,EAAU5E,CAAO,EAErCoF,EAAK,SAAW,EAAG,OAAO,KAG9B,IAAMM,EAASb,EAAQnB,GAAcC,EAAKkB,CAAK,EAAI,CAAC,EAC9Cc,EAAYd,IAAU,IAASA,EAAM,KACrCe,EAAYd,IAAU,IAASA,EAAM,MAAQU,EAAmB,OAAS,EAGzEK,EAAS1C,GAAcmB,EAAWc,EAAK,MAAM,EAE7CU,EAAY,CAChB,UACA,CAACF,GAAa,UACd5B,CACF,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,EAE1B,SACE,QAAC,OAAI,UAAW8B,EACb,UAAAH,MACC,OAAC,OAAI,UAAU,YACZ,SAAAD,EAAO,IAAI,CAACK,EAAGzF,OACd,OAAC,QAAuB,UAAU,UAC/B,SAAA4E,EAASa,EAAGzF,CAAC,GADL,GAAGyF,CAAC,IAAIzF,CAAC,EAEpB,CACD,EACH,KAEF,QAAC,OAAI,UAAU,WACb,oBAAC,OAAI,UAAU,WACZ,SAAAkE,GAAU,GAAKY,EAAKZ,CAAM,EAAI,MAC7B,OAACvC,GAAA,CAAc,KAAM8C,EAASK,EAAKZ,CAAM,CAAC,EAAG,EAEjD,KACA,QAAC,OAAI,UAAU,eACb,qBAAC,OAAI,UAAU,UACb,oBAAC,OAAI,UAAU,eAAe,KAC9B,OAAC,OAAI,UAAU,eAAe,KAC9B,OAAC,OAAI,UAAU,eAAe,GAChC,EACCY,EAAK,IAAI,CAACW,EAAGzF,IAAM,CAClB,GAAI,CAACoE,GAAYqB,IAAM,EACrB,SAAO,OAAC,OAAY,UAAU,UAAbzF,CAAsB,EACzC,IAAMG,EAAQ,KAAK,IACjB,EACA,KAAK,MAAM,KAAK,KAAKsF,EAAIpC,CAAG,EAAIM,CAAI,CACtC,EACA,SACE,OAACzD,GAAA,CAEC,MAAOC,EACP,QAASC,EACT,SAAUJ,IAAMkE,EAChB,QAAS,IAAMC,EAAUnE,CAAC,EAC1B,QAAS,IAAMmE,EAAU,EAAE,EAC3B,OAAQ3D,EACR,YAAaC,EACb,MAAO8E,EAAOvF,CAAC,GARVA,CASP,CAEJ,CAAC,GACH,EACCsF,MACC,OAAC,OAAI,UAAU,YAAY,IAAKhB,EAC7B,SAAAY,EAAmB,IAAK/C,MACvB,OAAC,QAEC,UAAU,UACV,MAAO,CACL,KAAM,IAAKA,EAAE,MAAQ,IAAO2C,EAAK,OAAU,GAAG,GAChD,EAEC,SAAA3C,EAAE,OANEA,EAAE,KAOT,CACD,EACH,GAEJ,GACF,CAEJ","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"]}
@@ -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 };
@@ -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
+ }