contrib-graphs 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1124 @@
1
+ 'use client';
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_data = require("./data-w1Mb5e5D.cjs");
4
+ let react = require("react");
5
+ let react_jsx_runtime = require("react/jsx-runtime");
6
+ // src/core.tsx
7
+ /**
8
+ * useLayoutEffect en el navegador (mide antes de pintar) y useEffect en el
9
+ * servidor, donde los efectos no corren y React avisaría por cada componente.
10
+ */
11
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
12
+ /** `:focus-visible` no existe en navegadores viejos (Safari < 15.4) ni en algunos entornos de test. */
13
+ function isFocusVisible(el) {
14
+ try {
15
+ return el.matches(":focus-visible");
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+ const indexOf = (target) => {
21
+ const el = target?.closest?.("[data-cg-i]");
22
+ return el ? Number(el.getAttribute("data-cg-i")) : null;
23
+ };
24
+ /**
25
+ * Hover + navegación con teclado usando delegación de eventos: las celdas solo
26
+ * llevan `data-cg-i`, así que no se crean cientos de handlers. Flechas izquierda/derecha
27
+ * avanzan `stepX` elementos y arriba/abajo `stepY`.
28
+ */
29
+ function useGraphInteraction(opts) {
30
+ const { rootRef, count, stepX, stepY, onActivate } = opts;
31
+ const [state, setState] = (0, react.useState)(null);
32
+ const [pos, setPos] = (0, react.useState)(null);
33
+ const index = state && state.index < count ? state.index : null;
34
+ useIsomorphicLayoutEffect(() => {
35
+ const root = rootRef.current;
36
+ if (!root) return;
37
+ const el = index == null ? null : root.querySelector(`[data-cg-i="${index}"]`);
38
+ root.querySelectorAll("[data-active]").forEach((n) => n !== el && n.removeAttribute("data-active"));
39
+ el?.setAttribute("data-active", "");
40
+ });
41
+ useIsomorphicLayoutEffect(() => {
42
+ const root = rootRef.current;
43
+ const el = index == null || !root ? null : root.querySelector(`[data-cg-i="${index}"]`);
44
+ if (!root || !el) {
45
+ setPos(null);
46
+ return;
47
+ }
48
+ const r = root.getBoundingClientRect();
49
+ const e = el.getBoundingClientRect();
50
+ setPos({
51
+ x: e.left + e.width / 2 - r.left,
52
+ y: e.top - r.top
53
+ });
54
+ }, [
55
+ rootRef,
56
+ index,
57
+ count
58
+ ]);
59
+ const onKeyDown = (0, react.useCallback)((e) => {
60
+ const cur = index ?? count - 1;
61
+ let next = null;
62
+ switch (e.key) {
63
+ case "ArrowRight":
64
+ next = cur + stepX;
65
+ break;
66
+ case "ArrowLeft":
67
+ next = cur - stepX;
68
+ break;
69
+ case "ArrowDown":
70
+ next = stepY ? cur + stepY : null;
71
+ break;
72
+ case "ArrowUp":
73
+ next = stepY ? cur - stepY : null;
74
+ break;
75
+ case "Home":
76
+ next = 0;
77
+ break;
78
+ case "End":
79
+ next = count - 1;
80
+ break;
81
+ case "Escape":
82
+ setState(null);
83
+ return;
84
+ case "Enter":
85
+ case " ":
86
+ if (index != null) {
87
+ e.preventDefault();
88
+ onActivate?.(index);
89
+ }
90
+ return;
91
+ default: return;
92
+ }
93
+ if (next == null || count === 0) return;
94
+ e.preventDefault();
95
+ setState({
96
+ index: Math.max(0, Math.min(count - 1, next)),
97
+ via: "keyboard"
98
+ });
99
+ }, [
100
+ index,
101
+ count,
102
+ stepX,
103
+ stepY,
104
+ onActivate
105
+ ]);
106
+ return {
107
+ rootRef,
108
+ active: index,
109
+ via: state?.via ?? null,
110
+ pos,
111
+ handlers: {
112
+ tabIndex: 0,
113
+ onKeyDown,
114
+ onPointerOver: (e) => {
115
+ const i = indexOf(e.target);
116
+ if (i != null && i !== index) setState({
117
+ index: i,
118
+ via: "pointer"
119
+ });
120
+ },
121
+ onPointerLeave: () => setState((s) => s?.via === "pointer" ? null : s),
122
+ onClick: (e) => {
123
+ const i = indexOf(e.target);
124
+ if (i != null) onActivate?.(i);
125
+ },
126
+ onFocus: (e) => {
127
+ if (e.target === e.currentTarget && isFocusVisible(e.currentTarget)) setState((s) => s ?? {
128
+ index: count - 1,
129
+ via: "keyboard"
130
+ });
131
+ },
132
+ onBlur: () => setState((s) => s?.via === "keyboard" ? null : s)
133
+ }
134
+ };
135
+ }
136
+ /** Reduce las semanas visibles al ancho del contenedor (en vez de hacer scroll). */
137
+ function useFitWeeks(ref, requested, pitch, reserved, enabled) {
138
+ const [fit, setFit] = (0, react.useState)(requested);
139
+ useIsomorphicLayoutEffect(() => {
140
+ const el = ref.current;
141
+ if (!enabled || !el || typeof ResizeObserver === "undefined") {
142
+ setFit(requested);
143
+ return;
144
+ }
145
+ const update = () => {
146
+ const w = el.clientWidth;
147
+ if (w > 0) setFit(Math.max(4, Math.min(requested, Math.floor((w - reserved) / pitch))));
148
+ };
149
+ update();
150
+ const ro = new ResizeObserver(update);
151
+ ro.observe(el);
152
+ return () => ro.disconnect();
153
+ }, [
154
+ ref,
155
+ requested,
156
+ pitch,
157
+ reserved,
158
+ enabled
159
+ ]);
160
+ return enabled ? fit : requested;
161
+ }
162
+ /** Monta calendario + interacción. Es la base de todas las variantes por día. */
163
+ function useCalendarGraph(props, opts = {}) {
164
+ const rootRef = (0, react.useRef)(null);
165
+ const requested = props.weeks ?? 53;
166
+ const weeks = useFitWeeks(rootRef, requested, opts.fit?.pitch ?? 1, opts.fit?.reserved ?? 0, opts.fit?.enabled ?? false);
167
+ const endKey = props.endDate instanceof Date ? props.endDate.getTime() : props.endDate;
168
+ const scaleKey = typeof props.scale === "string" ? props.scale : props.scale?.join(",");
169
+ const full = (0, react.useMemo)(() => require_data.buildCalendar(props.data, {
170
+ weeks: requested,
171
+ endDate: props.endDate,
172
+ weekStart: props.weekStart,
173
+ scale: props.scale,
174
+ locale: props.locale
175
+ }), [
176
+ props.data,
177
+ requested,
178
+ endKey,
179
+ props.weekStart,
180
+ scaleKey,
181
+ props.locale
182
+ ]);
183
+ const calendar = (0, react.useMemo)(() => require_data.cropCalendar(full, weeks, props.locale), [
184
+ full,
185
+ weeks,
186
+ props.locale
187
+ ]);
188
+ const { onDayClick } = props;
189
+ const onActivate = (0, react.useCallback)((i) => onDayClick?.(calendar.days[i]), [onDayClick, calendar]);
190
+ const interaction = useGraphInteraction({
191
+ rootRef,
192
+ count: calendar.days.length,
193
+ stepX: opts.stepX ?? 7,
194
+ stepY: opts.stepY ?? 1,
195
+ onActivate
196
+ });
197
+ /** `periodTotal` es el total del periodo pedido aunque se muestren menos semanas. */
198
+ return {
199
+ calendar,
200
+ periodTotal: full.total,
201
+ interaction,
202
+ theme: require_data.resolveTheme(props.theme)
203
+ };
204
+ }
205
+ function describeDay(day, props) {
206
+ const n = day.count;
207
+ return {
208
+ title: `${require_data.formatNumber(n, props.locale)} ${require_data.pluralize(n, props.unit)}`,
209
+ subtitle: require_data.formatDate(day.date, props.locale)
210
+ };
211
+ }
212
+ /** Texto del periodo según las semanas pedidas (no las recortadas por ancho). */
213
+ function periodText(props) {
214
+ const weeks = props.weeks ?? 53;
215
+ const p = props.periodLabel;
216
+ if (typeof p === "function") return p(weeks);
217
+ if (typeof p === "string") return p;
218
+ return weeks >= 52 ? "in the last year" : `in the last ${weeks} weeks`;
219
+ }
220
+ function defaultTitle(total, props) {
221
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
222
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: require_data.formatNumber(total, props.locale) }),
223
+ " ",
224
+ require_data.pluralize(total, props.unit),
225
+ " ",
226
+ periodText(props)
227
+ ] });
228
+ }
229
+ const cellDelay = (w) => ({ "--cg-w": w });
230
+ /** Id estable y válido para usar en `url(#...)` dentro de SVG. */
231
+ function useSvgId(prefix) {
232
+ return `${prefix}-${(0, react.useId)().replace(/[^a-zA-Z0-9_-]/g, "")}`;
233
+ }
234
+ function Legend({ labels = ["Less", "More"], swatch }) {
235
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
236
+ className: "cg-legend",
237
+ "aria-hidden": "true",
238
+ children: [
239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: labels[0] }),
240
+ [
241
+ 0,
242
+ 1,
243
+ 2,
244
+ 3,
245
+ 4
246
+ ].map((l) => swatch ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: swatch(l) }, l) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
247
+ className: "cg-swatch",
248
+ style: { background: `var(--cg-l${l})` }
249
+ }, l)),
250
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: labels[1] })
251
+ ]
252
+ });
253
+ }
254
+ /**
255
+ * Contenedor común: tema, modo claro/oscuro, encabezado, leyenda, tooltip
256
+ * y región aria-live para anunciar el día activo al navegar con teclado.
257
+ */
258
+ function GraphShell({ props, interaction, variant, describe, summary, title, headerEnd, footerStart, legendSwatch, glow, surface, children }) {
259
+ const theme = require_data.resolveTheme(props.theme);
260
+ const { active, pos, via } = interaction;
261
+ const desc = active != null ? describe(active) : null;
262
+ const tipRef = (0, react.useRef)(null);
263
+ const [shift, setShift] = (0, react.useState)(0);
264
+ useIsomorphicLayoutEffect(() => {
265
+ const tip = tipRef.current;
266
+ const root = interaction.rootRef.current;
267
+ if (!tip || !root || !pos) return;
268
+ const half = tip.offsetWidth / 2;
269
+ const w = root.clientWidth;
270
+ setShift(pos.x - half < 0 ? half - pos.x : pos.x + half > w ? w - half - pos.x : 0);
271
+ }, [
272
+ pos,
273
+ desc?.title,
274
+ desc?.subtitle,
275
+ interaction.rootRef
276
+ ]);
277
+ const showHeader = props.showTotal !== false || headerEnd;
278
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
279
+ ref: interaction.rootRef,
280
+ className: [
281
+ "cg-root",
282
+ `cg-${variant}`,
283
+ props.className
284
+ ].filter(Boolean).join(" "),
285
+ style: {
286
+ ...require_data.themeVars(theme),
287
+ ...props.style
288
+ },
289
+ "data-mode": props.colorMode ?? "auto",
290
+ "data-animate": props.animate ? "" : void 0,
291
+ "data-glow": glow ? "" : void 0,
292
+ "data-surface": surface ? "" : void 0,
293
+ "data-has-active": active != null ? "" : void 0,
294
+ role: "group",
295
+ "aria-label": props["aria-label"] ?? `${summary}. Use arrow keys to explore.`,
296
+ ...interaction.handlers,
297
+ children: [
298
+ showHeader && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
299
+ className: "cg-header",
300
+ children: [props.showTotal !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
301
+ className: "cg-title",
302
+ children: props.title ?? title
303
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), headerEnd]
304
+ }),
305
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
306
+ className: "cg-body",
307
+ children
308
+ }),
309
+ (props.showLegend !== false || footerStart) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
310
+ className: "cg-footer",
311
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: footerStart }), props.showLegend !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Legend, {
312
+ labels: props.legendLabels,
313
+ swatch: legendSwatch
314
+ })]
315
+ }),
316
+ desc && pos && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
317
+ ref: tipRef,
318
+ className: "cg-tooltip",
319
+ style: {
320
+ left: pos.x + shift,
321
+ top: pos.y,
322
+ "--cg-arrow": `${-shift}px`
323
+ },
324
+ "aria-hidden": "true",
325
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: desc.title }), desc.subtitle && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [" · ", desc.subtitle] })]
326
+ }),
327
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
328
+ className: "cg-sr-only",
329
+ "aria-live": "polite",
330
+ children: via === "keyboard" && desc ? `${desc.title}, ${desc.subtitle ?? ""}` : ""
331
+ })
332
+ ]
333
+ });
334
+ }
335
+ function summaryOf(calendar, props) {
336
+ return `${require_data.formatNumber(calendar.total, props.locale)} ${require_data.pluralize(calendar.total, props.unit)} from ${require_data.formatDate(calendar.start, props.locale)} to ${require_data.formatDate(calendar.end, props.locale)}`;
337
+ }
338
+ // src/variants/GridGraph.tsx
339
+ function useGrid(props, shape) {
340
+ const { cellSize = 11, gap = 3, radius = 2, showWeekdays = true, showMonths = true, fit = "crop" } = props;
341
+ const pitch = cellSize + gap;
342
+ const left = showWeekdays ? 30 : 0;
343
+ const top = showMonths ? 15 : 0;
344
+ const { calendar, periodTotal, interaction } = useCalendarGraph(props, { fit: {
345
+ pitch,
346
+ reserved: left - gap,
347
+ enabled: fit === "crop"
348
+ } });
349
+ const glow = props.glow ?? false;
350
+ const filterId = useSvgId("cg-glow");
351
+ const weekdays = (0, react.useMemo)(() => require_data.weekdayLabels(props.locale, props.weekStart ?? 0), [props.locale, props.weekStart]);
352
+ const width = left + calendar.weeks.length * pitch - gap;
353
+ const height = top + 7 * pitch - gap;
354
+ const svg = (0, react.useMemo)(() => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
355
+ width: fit === "scale" ? "100%" : width,
356
+ height: fit === "scale" ? void 0 : height,
357
+ viewBox: `0 0 ${width} ${height}`,
358
+ "aria-hidden": "true",
359
+ children: [
360
+ glow && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("filter", {
361
+ id: filterId,
362
+ x: "-50%",
363
+ y: "-50%",
364
+ width: "200%",
365
+ height: "200%",
366
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("feGaussianBlur", {
367
+ stdDeviation: cellSize / 4,
368
+ result: "b"
369
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("feMerge", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("feMergeNode", { in: "b" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("feMergeNode", { in: "SourceGraphic" })] })]
370
+ }) }),
371
+ showMonths && calendar.months.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
372
+ className: "cg-label",
373
+ x: left + m.weekIndex * pitch,
374
+ y: 9,
375
+ children: m.label
376
+ }, `${m.year}-${m.month}`)),
377
+ showWeekdays && [
378
+ 1,
379
+ 3,
380
+ 5
381
+ ].map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
382
+ className: "cg-label",
383
+ x: 0,
384
+ y: top + r * pitch + cellSize - 2,
385
+ children: weekdays[r]
386
+ }, r)),
387
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", { children: calendar.days.map((d) => {
388
+ const { kind, attrs } = shape.cell({
389
+ x: left + d.weekIndex * pitch,
390
+ y: top + d.dayIndex * pitch,
391
+ size: cellSize,
392
+ level: d.level,
393
+ radius
394
+ });
395
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(kind, {
396
+ ...attrs,
397
+ className: `cg-cell${glow && d.level >= 3 ? " cg-glow" : ""}`,
398
+ "data-cg-i": d.index,
399
+ "data-level": d.level,
400
+ fill: `var(--cg-l${d.level})`,
401
+ style: cellDelay(d.weekIndex)
402
+ }, d.date);
403
+ }) })
404
+ ]
405
+ }), [
406
+ calendar,
407
+ width,
408
+ height,
409
+ fit,
410
+ glow,
411
+ filterId,
412
+ cellSize,
413
+ showMonths,
414
+ showWeekdays,
415
+ left,
416
+ top,
417
+ pitch,
418
+ radius,
419
+ weekdays,
420
+ shape
421
+ ]);
422
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GraphShell, {
423
+ props,
424
+ interaction,
425
+ variant: shape.variant,
426
+ describe: (i) => describeDay(calendar.days[i], props),
427
+ summary: summaryOf(calendar, props),
428
+ title: defaultTitle(periodTotal, props),
429
+ glow,
430
+ legendSwatch: shape.variant === "dots" ? (l) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
431
+ className: "cg-swatch",
432
+ style: {
433
+ background: `var(--cg-l${l})`,
434
+ borderRadius: "50%",
435
+ transform: `scale(${.55 + l * .11})`
436
+ }
437
+ }) : void 0,
438
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
439
+ style: glow ? { "--cg-glow-filter": `url(#${filterId})` } : void 0,
440
+ children: svg
441
+ })
442
+ });
443
+ }
444
+ const classicShape = {
445
+ variant: "classic",
446
+ cell: ({ x, y, size, radius }) => ({
447
+ kind: "rect",
448
+ attrs: {
449
+ x,
450
+ y,
451
+ width: size,
452
+ height: size,
453
+ rx: radius,
454
+ ry: radius
455
+ }
456
+ })
457
+ };
458
+ const dotsShape = {
459
+ variant: "dots",
460
+ cell: ({ x, y, size, level }) => ({
461
+ kind: "circle",
462
+ attrs: {
463
+ cx: x + size / 2,
464
+ cy: y + size / 2,
465
+ r: size / 2 * (level === 0 ? .42 : .55 + level * .115)
466
+ }
467
+ })
468
+ };
469
+ /** The classic weeks × days grid. */
470
+ function ClassicGraph(props) {
471
+ return useGrid(props, classicShape);
472
+ }
473
+ /** Circles whose size also encodes intensity, so it still reads in monochrome. */
474
+ function DotsGraph(props) {
475
+ return useGrid(props, dotsShape);
476
+ }
477
+ // src/isometric.ts
478
+ const r = (v) => Math.round(v * 100) / 100;
479
+ /**
480
+ * Geometría de la vista isométrica, compartida por `IsometricGraph` y por
481
+ * `renderIsometricSvg`: así el componente y el SVG estático se ven igual.
482
+ * Las baldosas vienen ordenadas de atrás hacia adelante (orden de pintado).
483
+ */
484
+ function isometricLayout(calendar, { tileSize = 12, maxHeight = 48, heightBy = "count" } = {}) {
485
+ const tw = tileSize;
486
+ const th = tileSize / 2;
487
+ const W = calendar.weeks.length;
488
+ const width = (W + 7) * (tw / 2);
489
+ const height = (W + 7) * (th / 2) + maxHeight + 2;
490
+ const ox = 7 * (tw / 2);
491
+ const max = Math.max(1, calendar.max);
492
+ return {
493
+ width,
494
+ height,
495
+ tiles: [...calendar.days].sort((a, b) => a.weekIndex + a.dayIndex - (b.weekIndex + b.dayIndex) || a.weekIndex - b.weekIndex).map((day) => {
496
+ const X = ox + (day.weekIndex - day.dayIndex) * (tw / 2);
497
+ const Y = maxHeight + (day.weekIndex + day.dayIndex) * (th / 2);
498
+ const h = day.count === 0 ? 0 : heightBy === "level" ? day.level / 4 * maxHeight : Math.max(3, day.count / max * maxHeight);
499
+ const p = (x, y) => `${r(x)},${r(y)}`;
500
+ return {
501
+ day,
502
+ height: h,
503
+ top: [
504
+ p(X, Y - h),
505
+ p(X + tw / 2, Y + th / 2 - h),
506
+ p(X, Y + th - h),
507
+ p(X - tw / 2, Y + th / 2 - h)
508
+ ].join(" "),
509
+ left: [
510
+ p(X - tw / 2, Y + th / 2 - h),
511
+ p(X, Y + th - h),
512
+ p(X, Y + th),
513
+ p(X - tw / 2, Y + th / 2)
514
+ ].join(" "),
515
+ right: [
516
+ p(X, Y + th - h),
517
+ p(X + tw / 2, Y + th / 2 - h),
518
+ p(X + tw / 2, Y + th / 2),
519
+ p(X, Y + th)
520
+ ].join(" ")
521
+ };
522
+ })
523
+ };
524
+ }
525
+ /** Porcentaje de color de la cara izquierda y derecha (el resto se mezcla con negro). */
526
+ const ISO_SHADES = {
527
+ left: 78,
528
+ right: 60
529
+ };
530
+ // src/variants/IsometricGraph.tsx
531
+ const shade = (level, pct) => `color-mix(in oklab, var(--cg-l${level}) ${pct}%, var(--cg-mix))`;
532
+ /** Isometric columns whose height shows each day's activity. */
533
+ function IsometricGraph(props) {
534
+ const { tileSize = 12, maxHeight = 48, heightBy = "count", showStats = true, statLabels = [
535
+ "longest streak",
536
+ "current streak",
537
+ "best day"
538
+ ] } = props;
539
+ const { calendar, periodTotal, interaction } = useCalendarGraph(props);
540
+ const stats = (0, react.useMemo)(() => require_data.computeStats(calendar.days), [calendar]);
541
+ const svg = (0, react.useMemo)(() => {
542
+ const { width, height, tiles } = isometricLayout(calendar, {
543
+ tileSize,
544
+ maxHeight,
545
+ heightBy
546
+ });
547
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
548
+ width,
549
+ height,
550
+ viewBox: `0 0 ${width} ${height}`,
551
+ "aria-hidden": "true",
552
+ children: tiles.map(({ day: d, height: h, top, left, right }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
553
+ className: "cg-cell cg-rise",
554
+ "data-cg-i": d.index,
555
+ style: cellDelay(d.weekIndex),
556
+ children: [
557
+ h > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polygon", {
558
+ points: left,
559
+ style: { fill: shade(d.level, ISO_SHADES.left) }
560
+ }),
561
+ h > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polygon", {
562
+ points: right,
563
+ style: { fill: shade(d.level, ISO_SHADES.right) }
564
+ }),
565
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polygon", {
566
+ points: top,
567
+ style: { fill: `var(--cg-l${d.level})` }
568
+ })
569
+ ]
570
+ }, d.date))
571
+ });
572
+ }, [
573
+ calendar,
574
+ tileSize,
575
+ maxHeight,
576
+ heightBy
577
+ ]);
578
+ const n = (v) => require_data.formatNumber(v, props.locale);
579
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GraphShell, {
580
+ props,
581
+ interaction,
582
+ variant: "isometric",
583
+ describe: (i) => describeDay(calendar.days[i], props),
584
+ summary: summaryOf(calendar, props),
585
+ title: defaultTitle(periodTotal, props),
586
+ headerEnd: showStats && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
587
+ className: "cg-stats",
588
+ children: [
589
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: n(stats.longestStreak) }), statLabels[0]] }),
590
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: n(stats.currentStreak) }), statLabels[1]] }),
591
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
592
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: n(stats.best?.count ?? 0) }),
593
+ statLabels[2],
594
+ stats.best && ` · ${require_data.formatDate(stats.best.date, props.locale, {
595
+ month: "short",
596
+ day: "numeric"
597
+ })}`
598
+ ] })
599
+ ]
600
+ }),
601
+ children: svg
602
+ });
603
+ }
604
+ // src/variants/RadialGraph.tsx
605
+ function sector(cx, cy, r0, r1, a0, a1) {
606
+ const p = (r, a) => `${(cx + r * Math.cos(a)).toFixed(2)} ${(cy + r * Math.sin(a)).toFixed(2)}`;
607
+ return `M${p(r1, a0)}A${r1} ${r1} 0 0 1 ${p(r1, a1)}L${p(r0, a1)}A${r0} ${r0} 0 0 0 ${p(r0, a0)}Z`;
608
+ }
609
+ /** The year as a ring: each week is a slice and each weekday a band. */
610
+ function RadialGraph(props) {
611
+ const { size = 320, innerRadius = .38, showMonths = true } = props;
612
+ const { calendar, interaction } = useCalendarGraph(props);
613
+ const pad = showMonths ? 22 : 4;
614
+ const R = size / 2;
615
+ const outer = R - pad;
616
+ const inner = outer * innerRadius;
617
+ const ring = (outer - inner) / 7;
618
+ const W = calendar.weeks.length;
619
+ const slice = Math.PI * 2 / W;
620
+ const start = -Math.PI / 2;
621
+ const gapA = Math.min(.012, slice * .12);
622
+ const svg = (0, react.useMemo)(() => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
623
+ width: size,
624
+ height: size,
625
+ viewBox: `0 0 ${size} ${size}`,
626
+ "aria-hidden": "true",
627
+ children: [
628
+ calendar.days.map((d) => {
629
+ const a0 = start + d.weekIndex * slice + gapA;
630
+ const a1 = start + (d.weekIndex + 1) * slice - gapA;
631
+ const r0 = inner + d.dayIndex * ring + .6;
632
+ const r1 = inner + (d.dayIndex + 1) * ring - .6;
633
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
634
+ d: sector(R, R, r0, r1, a0, a1),
635
+ className: "cg-cell",
636
+ "data-cg-i": d.index,
637
+ fill: `var(--cg-l${d.level})`,
638
+ style: cellDelay(d.weekIndex)
639
+ }, d.date);
640
+ }),
641
+ showMonths && calendar.months.map((m) => {
642
+ const a = start + (m.weekIndex + .5) * slice;
643
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
644
+ className: "cg-label",
645
+ x: R + (outer + 11) * Math.cos(a),
646
+ y: R + (outer + 11) * Math.sin(a),
647
+ textAnchor: "middle",
648
+ dominantBaseline: "middle",
649
+ children: m.label
650
+ }, `${m.year}-${m.month}`);
651
+ }),
652
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
653
+ x: R,
654
+ y: R - 4,
655
+ textAnchor: "middle",
656
+ style: {
657
+ fill: "var(--cg-text)",
658
+ fontSize: size / 11,
659
+ fontWeight: 650
660
+ },
661
+ children: require_data.formatNumber(calendar.total, props.locale)
662
+ }),
663
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
664
+ x: R,
665
+ y: R + size / 18,
666
+ textAnchor: "middle",
667
+ className: "cg-label",
668
+ style: { fontSize: 11 },
669
+ children: require_data.pluralize(calendar.total, props.unit)
670
+ })
671
+ ]
672
+ }), [
673
+ calendar,
674
+ size,
675
+ R,
676
+ inner,
677
+ ring,
678
+ outer,
679
+ slice,
680
+ start,
681
+ gapA,
682
+ showMonths,
683
+ props.locale,
684
+ props.unit
685
+ ]);
686
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GraphShell, {
687
+ props: {
688
+ ...props,
689
+ showTotal: props.showTotal ?? false
690
+ },
691
+ interaction,
692
+ variant: "radial",
693
+ describe: (i) => describeDay(calendar.days[i], props),
694
+ summary: summaryOf(calendar, props),
695
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
696
+ style: {
697
+ display: "flex",
698
+ justifyContent: "center"
699
+ },
700
+ children: svg
701
+ })
702
+ });
703
+ }
704
+ // src/variants/SkylineGraph.tsx
705
+ /** One building per week: your year as a city skyline. */
706
+ function SkylineGraph(props) {
707
+ const { height = 110, barWidth = 9, gap = 3, windows = true, fit = "crop" } = props;
708
+ const pitch = barWidth + gap;
709
+ const rootRef = (0, react.useRef)(null);
710
+ const weeksN = useFitWeeks(rootRef, props.weeks ?? 53, pitch, -gap, fit === "crop");
711
+ const endKey = props.endDate instanceof Date ? props.endDate.getTime() : props.endDate;
712
+ const requested = props.weeks ?? 53;
713
+ const full = (0, react.useMemo)(() => require_data.buildCalendar(props.data, {
714
+ weeks: requested,
715
+ endDate: props.endDate,
716
+ weekStart: props.weekStart,
717
+ locale: props.locale
718
+ }), [
719
+ props.data,
720
+ requested,
721
+ endKey,
722
+ props.weekStart,
723
+ props.locale
724
+ ]);
725
+ const calendar = (0, react.useMemo)(() => require_data.cropCalendar(full, weeksN, props.locale), [
726
+ full,
727
+ weeksN,
728
+ props.locale
729
+ ]);
730
+ const allWeeks = (0, react.useMemo)(() => {
731
+ const raw = full.weeks.map((w, index) => {
732
+ const days = w.filter((d) => d !== null);
733
+ return {
734
+ index,
735
+ days,
736
+ total: days.reduce((s, d) => s + d.count, 0),
737
+ activeDays: days.filter((d) => d.count > 0).length,
738
+ level: 0
739
+ };
740
+ });
741
+ const levelOf = require_data.makeLevelFn(raw.map((w) => w.total), props.scale);
742
+ raw.forEach((w) => w.level = levelOf(w.total));
743
+ return raw;
744
+ }, [full, typeof props.scale === "string" ? props.scale : props.scale?.join(",")]);
745
+ const maxWeek = Math.max(1, ...allWeeks.map((w) => w.total));
746
+ const weeks = (0, react.useMemo)(() => allWeeks.slice(allWeeks.length - calendar.weeks.length).map((w, index) => ({
747
+ ...w,
748
+ index,
749
+ days: calendar.weeks[index].filter((d) => d !== null)
750
+ })), [allWeeks, calendar]);
751
+ const { onWeekClick } = props;
752
+ const onActivate = (0, react.useCallback)((i) => onWeekClick?.(weeks[i]), [onWeekClick, weeks]);
753
+ const interaction = useGraphInteraction({
754
+ rootRef,
755
+ count: weeks.length,
756
+ stepX: 1,
757
+ stepY: 0,
758
+ onActivate
759
+ });
760
+ const labelH = 14;
761
+ const minH = windows ? 30 : 3;
762
+ const width = weeks.length * pitch - gap;
763
+ const svgH = height + labelH;
764
+ const svg = (0, react.useMemo)(() => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
765
+ width: fit === "scale" ? "100%" : width,
766
+ height: fit === "scale" ? void 0 : svgH,
767
+ viewBox: `0 0 ${width} ${svgH}`,
768
+ "aria-hidden": "true",
769
+ children: [
770
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
771
+ x1: 0,
772
+ x2: width,
773
+ y1: height + .5,
774
+ y2: height + .5,
775
+ stroke: "var(--cg-border)"
776
+ }),
777
+ weeks.map((w) => {
778
+ const h = w.total === 0 ? minH * .8 : minH + w.total / maxWeek * (height - minH);
779
+ const x = w.index * pitch;
780
+ const y = height - h;
781
+ const ww = Math.max(2, barWidth - 4);
782
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
783
+ className: "cg-cell cg-rise",
784
+ "data-cg-i": w.index,
785
+ style: cellDelay(w.index),
786
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
787
+ x,
788
+ y,
789
+ width: barWidth,
790
+ height: h,
791
+ rx: 1.5,
792
+ fill: `var(--cg-l${w.level})`
793
+ }), windows && w.days.map((d) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
794
+ x: x + (barWidth - ww) / 2,
795
+ y: y + 3 + d.dayIndex * 3.6,
796
+ width: ww,
797
+ height: 2.2,
798
+ rx: .5,
799
+ fill: d.count > 0 ? "#fff6c8" : "currentColor",
800
+ opacity: d.count > 0 ? .92 : .12,
801
+ style: { color: "var(--cg-mix)" }
802
+ }, d.date))]
803
+ }, w.index);
804
+ }),
805
+ calendar.months.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
806
+ className: "cg-label",
807
+ x: m.weekIndex * pitch,
808
+ y: svgH - 2,
809
+ children: m.label
810
+ }, `${m.year}-${m.month}`))
811
+ ]
812
+ }), [
813
+ weeks,
814
+ calendar.months,
815
+ width,
816
+ svgH,
817
+ height,
818
+ pitch,
819
+ barWidth,
820
+ maxWeek,
821
+ minH,
822
+ windows,
823
+ fit
824
+ ]);
825
+ const describe = (i) => {
826
+ const w = weeks[i];
827
+ return {
828
+ title: `${require_data.formatNumber(w.total, props.locale)} ${require_data.pluralize(w.total, props.unit)}`,
829
+ subtitle: w.days[0] ? `${require_data.formatDate(w.days[0].date, props.locale, {
830
+ month: "short",
831
+ day: "numeric",
832
+ year: "numeric"
833
+ })} · ${w.activeDays}/7` : void 0
834
+ };
835
+ };
836
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GraphShell, {
837
+ props,
838
+ interaction,
839
+ variant: "skyline",
840
+ describe,
841
+ summary: summaryOf(calendar, props),
842
+ title: defaultTitle(full.total, props),
843
+ children: svg
844
+ });
845
+ }
846
+ // src/variants/TerminalGraph.tsx
847
+ const terminalCharsets = {
848
+ blocks: [
849
+ "··",
850
+ "░░",
851
+ "▒▒",
852
+ "▓▓",
853
+ "██"
854
+ ],
855
+ squares: [
856
+ "□ ",
857
+ "▪ ",
858
+ "■ ",
859
+ "■ ",
860
+ "■ "
861
+ ],
862
+ ascii: [
863
+ ". ",
864
+ ": ",
865
+ "+ ",
866
+ "# ",
867
+ "@ "
868
+ ],
869
+ dots: [
870
+ "· ",
871
+ "• ",
872
+ "• ",
873
+ "● ",
874
+ "● "
875
+ ]
876
+ };
877
+ const CHAR_W = 7.22;
878
+ const MIN_FONT = 6;
879
+ /** Real monospace text, for portfolios with a terminal look. */
880
+ function TerminalGraph(props) {
881
+ const { charset = "blocks", command = "git log --since='1 year ago' | heatmap", prompt = "~ $", colored = true, fit = "crop" } = props;
882
+ const chars = terminalCharsets[charset];
883
+ const { calendar, periodTotal, interaction } = useCalendarGraph(props, { fit: (() => {
884
+ const k = fit === "scale" ? MIN_FONT / 12 : 1;
885
+ return {
886
+ pitch: CHAR_W * 2 * k,
887
+ reserved: CHAR_W * 5 * k + 34,
888
+ enabled: true
889
+ };
890
+ })() });
891
+ const weekdays = (0, react.useMemo)(() => require_data.weekdayLabels(props.locale, props.weekStart ?? 0), [props.locale, props.weekStart]);
892
+ const [fontSize, setFontSize] = (0, react.useState)(12);
893
+ const { rootRef } = interaction;
894
+ const cols = 5 + calendar.weeks.length * 2;
895
+ useIsomorphicLayoutEffect(() => {
896
+ const el = rootRef.current;
897
+ if (fit !== "scale" || !el || typeof ResizeObserver === "undefined") {
898
+ setFontSize(12);
899
+ return;
900
+ }
901
+ const update = () => {
902
+ const avail = el.clientWidth - 34;
903
+ if (avail > 0) setFontSize(Math.max(MIN_FONT, Math.min(12, avail / cols * (12 / CHAR_W))));
904
+ };
905
+ update();
906
+ const ro = new ResizeObserver(update);
907
+ ro.observe(el);
908
+ return () => ro.disconnect();
909
+ }, [
910
+ fit,
911
+ cols,
912
+ rootRef
913
+ ]);
914
+ const color = (l) => colored ? `var(--cg-l${l === 0 ? 2 : l})` : "var(--cg-text)";
915
+ const op = (l) => l === 0 ? .35 : colored ? 1 : .4 + l * .15;
916
+ const body = (0, react.useMemo)(() => {
917
+ const cols = calendar.weeks.length * 2;
918
+ const header = Array.from({ length: cols }, () => " ");
919
+ for (const m of calendar.months) {
920
+ const lbl = m.label.replace(".", "").slice(0, 3);
921
+ for (let i = 0; i < lbl.length && m.weekIndex * 2 + i < cols; i++) header[m.weekIndex * 2 + i] = lbl[i];
922
+ }
923
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
924
+ " ",
925
+ header.join(""),
926
+ "\n",
927
+ Array.from({ length: 7 }, (_, r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [
928
+ (r % 2 === 1 ? weekdays[r].replace(".", "").slice(0, 3) : "").padEnd(5),
929
+ calendar.weeks.map((w, wi) => {
930
+ const d = w[r];
931
+ if (!d) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: " " }, wi);
932
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
933
+ className: "cg-cell",
934
+ "data-cg-i": d.index,
935
+ style: {
936
+ ...cellDelay(wi),
937
+ color: color(d.level),
938
+ opacity: op(d.level)
939
+ },
940
+ children: chars[d.level]
941
+ }, d.date);
942
+ }),
943
+ "\n"
944
+ ] }, r))
945
+ ] });
946
+ }, [
947
+ calendar,
948
+ weekdays,
949
+ chars,
950
+ colored
951
+ ]);
952
+ const [less, more] = props.legendLabels ?? ["less", "more"];
953
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GraphShell, {
954
+ props: {
955
+ ...props,
956
+ showTotal: false,
957
+ showLegend: false
958
+ },
959
+ interaction,
960
+ variant: "terminal",
961
+ surface: true,
962
+ describe: (i) => describeDay(calendar.days[i], props),
963
+ summary: summaryOf(calendar, props),
964
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("pre", {
965
+ className: "cg-term",
966
+ style: fontSize !== 12 ? { fontSize } : void 0,
967
+ children: [
968
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
969
+ className: "cg-prompt cg-wrap",
970
+ children: [
971
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: prompt }),
972
+ " ",
973
+ command
974
+ ]
975
+ }),
976
+ "\n\n",
977
+ body,
978
+ "\n",
979
+ (props.showTotal !== false || props.showLegend !== false) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
980
+ className: "cg-foot",
981
+ children: [props.showTotal !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
982
+ className: "cg-prompt",
983
+ children: props.title ?? `${require_data.formatNumber(periodTotal, props.locale)} ${require_data.pluralize(periodTotal, props.unit)} ${periodText(props)}`
984
+ }), props.showLegend !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
985
+ less,
986
+ " ",
987
+ [
988
+ 0,
989
+ 1,
990
+ 2,
991
+ 3,
992
+ 4
993
+ ].map((l) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
994
+ style: {
995
+ color: color(l),
996
+ opacity: op(l)
997
+ },
998
+ children: chars[l]
999
+ }, l)),
1000
+ more
1001
+ ] })]
1002
+ }),
1003
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1004
+ className: "cg-prompt",
1005
+ children: [
1006
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: prompt }),
1007
+ " ",
1008
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cg-caret" })
1009
+ ]
1010
+ })
1011
+ ]
1012
+ })
1013
+ });
1014
+ }
1015
+ // src/variants/CompactGraph.tsx
1016
+ /** A single row with the last N days, for headers, cards and footers. */
1017
+ function CompactGraph(props) {
1018
+ const { days: n = 30, cellSize = 10, gap = 3, radius = 2, showStreak = true } = props;
1019
+ const rootRef = (0, react.useRef)(null);
1020
+ const endKey = props.endDate instanceof Date ? props.endDate.getTime() : props.endDate;
1021
+ const scaleKey = typeof props.scale === "string" ? props.scale : props.scale?.join(",");
1022
+ const days = (0, react.useMemo)(() => {
1023
+ const slice = require_data.buildCalendar(props.data, {
1024
+ weeks: Math.ceil(n / 7) + 1,
1025
+ endDate: props.endDate
1026
+ }).days.slice(-n);
1027
+ const levelOf = require_data.makeLevelFn(slice.map((d) => d.count), props.scale);
1028
+ return slice.map((d, i) => ({
1029
+ ...d,
1030
+ index: i,
1031
+ weekIndex: i,
1032
+ level: levelOf(d.count)
1033
+ }));
1034
+ }, [
1035
+ props.data,
1036
+ n,
1037
+ endKey,
1038
+ scaleKey
1039
+ ]);
1040
+ const stats = (0, react.useMemo)(() => require_data.computeStats(days), [days]);
1041
+ const { onDayClick } = props;
1042
+ const onActivate = (0, react.useCallback)((i) => onDayClick?.(days[i]), [onDayClick, days]);
1043
+ const interaction = useGraphInteraction({
1044
+ rootRef,
1045
+ count: days.length,
1046
+ stepX: 1,
1047
+ stepY: 0,
1048
+ onActivate
1049
+ });
1050
+ const pitch = cellSize + gap;
1051
+ const width = days.length * pitch - gap;
1052
+ const svg = (0, react.useMemo)(() => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1053
+ width,
1054
+ height: cellSize,
1055
+ viewBox: `0 0 ${width} ${cellSize}`,
1056
+ "aria-hidden": "true",
1057
+ children: days.map((d) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1058
+ className: "cg-cell",
1059
+ "data-cg-i": d.index,
1060
+ x: d.index * pitch,
1061
+ y: 0,
1062
+ width: cellSize,
1063
+ height: cellSize,
1064
+ rx: radius,
1065
+ fill: `var(--cg-l${d.level})`,
1066
+ style: cellDelay(d.index / 3)
1067
+ }, d.date))
1068
+ }), [
1069
+ days,
1070
+ width,
1071
+ cellSize,
1072
+ pitch,
1073
+ radius
1074
+ ]);
1075
+ const streak = props.streakLabel ?? ((s) => `${s} day streak`);
1076
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GraphShell, {
1077
+ props: {
1078
+ ...props,
1079
+ showLegend: props.showLegend ?? false
1080
+ },
1081
+ interaction,
1082
+ variant: "compact",
1083
+ describe: (i) => describeDay(days[i], props),
1084
+ summary: `${stats.total} ${require_data.pluralize(stats.total, props.unit)} in the last ${n} days`,
1085
+ title: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1086
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: require_data.formatNumber(stats.total, props.locale) }),
1087
+ " ",
1088
+ require_data.pluralize(stats.total, props.unit),
1089
+ " ",
1090
+ (props.rangeLabel ?? ((d) => `in the last ${d} days`))(n)
1091
+ ] }),
1092
+ headerEnd: showStreak && stats.currentStreak > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1093
+ style: { color: "var(--cg-muted)" },
1094
+ children: streak(stats.currentStreak)
1095
+ }),
1096
+ children: svg
1097
+ });
1098
+ }
1099
+ exports.ClassicGraph = ClassicGraph;
1100
+ exports.CompactGraph = CompactGraph;
1101
+ exports.DotsGraph = DotsGraph;
1102
+ exports.GITHUB_CONTRIBUTIONS_QUERY = require_data.GITHUB_CONTRIBUTIONS_QUERY;
1103
+ exports.IsometricGraph = IsometricGraph;
1104
+ exports.RadialGraph = RadialGraph;
1105
+ exports.SkylineGraph = SkylineGraph;
1106
+ exports.TerminalGraph = TerminalGraph;
1107
+ exports.buildCalendar = require_data.buildCalendar;
1108
+ exports.computeStats = require_data.computeStats;
1109
+ exports.cropCalendar = require_data.cropCalendar;
1110
+ exports.fetchGitHubContributions = require_data.fetchGitHubContributions;
1111
+ exports.fetchGitLabContributions = require_data.fetchGitLabContributions;
1112
+ exports.formatDate = require_data.formatDate;
1113
+ exports.fromDates = require_data.fromDates;
1114
+ exports.fromGitHub = require_data.fromGitHub;
1115
+ exports.fromGitLab = require_data.fromGitLab;
1116
+ exports.generateSampleData = require_data.generateSampleData;
1117
+ exports.makeLevelFn = require_data.makeLevelFn;
1118
+ exports.mergeSources = require_data.mergeSources;
1119
+ exports.resolveTheme = require_data.resolveTheme;
1120
+ exports.sampleProfiles = require_data.sampleProfiles;
1121
+ exports.terminalCharsets = terminalCharsets;
1122
+ exports.themeNames = require_data.themeNames;
1123
+ exports.themeVars = require_data.themeVars;
1124
+ exports.themes = require_data.themes;