@siyahi/ui 0.1.9-next.24 → 0.1.9-next.26
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/agents/examples/change-grid-money.tsx +36 -0
- package/agents/examples/change-grid.tsx +42 -0
- package/agents/examples/drawdown-chart.tsx +35 -0
- package/agents/examples/heatmap-calendar-signed.tsx +67 -0
- package/agents/examples/heatmap-status.tsx +6 -4
- package/agents/examples/histogram-chart.tsx +28 -0
- package/agents/examples/scatter-chart-quadrants.tsx +63 -0
- package/agents/examples/scatter-chart.tsx +75 -0
- package/agents/examples/slope-chart-market.tsx +33 -0
- package/agents/examples/slope-chart.tsx +26 -0
- package/agents/examples/waterfall-chart-stores.tsx +27 -0
- package/agents/examples/waterfall-chart.tsx +37 -0
- package/agents/manifest.json +160 -0
- package/dist/components/registry/heatmap/calendar-heatmap.d.ts +8 -2
- package/dist/components/registry/heatmap/calendar-heatmap.js +96 -18
- package/dist/components/registry/heatmap/status-heatmap.js +86 -47
- package/dist/components/registry/heatmap/weekday-heatmap.js +5 -10
- package/dist/components/siyahi/change-grid.d.ts +31 -0
- package/dist/components/siyahi/change-grid.js +74 -0
- package/dist/components/siyahi/drawdown-chart.d.ts +34 -0
- package/dist/components/siyahi/drawdown-chart.js +102 -0
- package/dist/components/siyahi/histogram-chart.d.ts +57 -0
- package/dist/components/siyahi/histogram-chart.js +134 -0
- package/dist/components/siyahi/scatter-chart.d.ts +59 -0
- package/dist/components/siyahi/scatter-chart.js +367 -0
- package/dist/components/siyahi/slope-chart.d.ts +35 -0
- package/dist/components/siyahi/slope-chart.js +159 -0
- package/dist/components/siyahi/treemap.js +3 -7
- package/dist/components/siyahi/waterfall-chart.d.ts +51 -0
- package/dist/components/siyahi/waterfall-chart.js +147 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +7 -1
- package/dist/lib/chart-frame.d.ts +7 -2
- package/dist/lib/chart-frame.js +44 -10
- package/dist/lib/chart-series.d.ts +2 -3
- package/dist/lib/fit-text.d.ts +6 -0
- package/dist/lib/fit-text.js +11 -0
- package/dist/lib/heat.d.ts +7 -0
- package/dist/lib/heat.js +14 -0
- package/dist/lib/scatter.d.ts +98 -0
- package/dist/lib/scatter.js +218 -0
- package/dist/lib/treemap.d.ts +0 -5
- package/dist/lib/treemap.js +1 -16
- package/package.json +1 -1
- package/report.css +9 -0
- package/styles/siyahi.css +16 -0
package/agents/manifest.json
CHANGED
|
@@ -64,6 +64,29 @@
|
|
|
64
64
|
"guidance": "a series with `color` and `negativeColor`",
|
|
65
65
|
"components": []
|
|
66
66
|
},
|
|
67
|
+
{
|
|
68
|
+
"need": "A total built up or bridged step by step, such as a year's profit month by month or revenue from last year to this by store",
|
|
69
|
+
"guidance": "`WaterfallChartCard`; `horizontal` for long names",
|
|
70
|
+
"components": [
|
|
71
|
+
"WaterfallChartCard"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"need": "Signed changes by two categories, such as returns or profit by month and year, or by store and quarter",
|
|
76
|
+
"guidance": "`ChangeGrid` in a `Card`, with each row's `total` worked out by the page",
|
|
77
|
+
"components": [
|
|
78
|
+
"ChangeGrid",
|
|
79
|
+
"Card"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"need": "Each item's value at two points or against a benchmark, such as share of sales last year and this, or our mix against the market's",
|
|
84
|
+
"guidance": "`SlopeChart` in a `Card`, with the few lines that matter `highlighted`",
|
|
85
|
+
"components": [
|
|
86
|
+
"SlopeChart",
|
|
87
|
+
"Card"
|
|
88
|
+
]
|
|
89
|
+
},
|
|
67
90
|
{
|
|
68
91
|
"need": "Ranked counts or amounts by category (a name, a bar and a figure)",
|
|
69
92
|
"guidance": "`BarList`; give two lists one `max` to share a scale",
|
|
@@ -131,6 +154,20 @@
|
|
|
131
154
|
"RangePosition"
|
|
132
155
|
]
|
|
133
156
|
},
|
|
157
|
+
{
|
|
158
|
+
"need": "How far a level fell below its highest so far, over time, such as a fund's or a cash balance's drawdown",
|
|
159
|
+
"guidance": "`DrawdownChartCard` with the levels in `levelKey`",
|
|
160
|
+
"components": [
|
|
161
|
+
"DrawdownChartCard"
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"need": "How many values fall in each range, such as response times or each trade's return, with the median or p95 marked",
|
|
166
|
+
"guidance": "`HistogramChartCard` with the raw `values` and `marks`",
|
|
167
|
+
"components": [
|
|
168
|
+
"HistogramChartCard"
|
|
169
|
+
]
|
|
170
|
+
},
|
|
134
171
|
{
|
|
135
172
|
"need": "A long daily series, such as a price or an order value",
|
|
136
173
|
"guidance": "`PriceChart`",
|
|
@@ -145,6 +182,27 @@
|
|
|
145
182
|
"Treemap"
|
|
146
183
|
]
|
|
147
184
|
},
|
|
185
|
+
{
|
|
186
|
+
"need": "How two measures relate across many items, such as stores' size against growth",
|
|
187
|
+
"guidance": "`ScatterChartCard`",
|
|
188
|
+
"components": [
|
|
189
|
+
"ScatterChartCard"
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"need": "Items sorted into four groups by two signed changes, weighted by size, such as products by price change and sales change, sized by revenue",
|
|
194
|
+
"guidance": "`ScatterChartCard` with `quadrants` and each point's `size`",
|
|
195
|
+
"components": [
|
|
196
|
+
"ScatterChartCard"
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"need": "A gain or loss for every day, such as daily profit or a portfolio's daily change",
|
|
201
|
+
"guidance": "`CalendarHeatmap` with `signed`",
|
|
202
|
+
"components": [
|
|
203
|
+
"CalendarHeatmap"
|
|
204
|
+
]
|
|
205
|
+
},
|
|
148
206
|
{
|
|
149
207
|
"need": "Activity by day, by weekday and hour, or a status over time",
|
|
150
208
|
"guidance": "`CalendarHeatmap`, `WeekdayHeatmap`, `StatusHeatmap`",
|
|
@@ -442,6 +500,28 @@
|
|
|
442
500
|
"useFormat"
|
|
443
501
|
]
|
|
444
502
|
},
|
|
503
|
+
{
|
|
504
|
+
"file": "agents/examples/change-grid-money.tsx",
|
|
505
|
+
"components": [
|
|
506
|
+
"Card",
|
|
507
|
+
"CardContent",
|
|
508
|
+
"CardDescription",
|
|
509
|
+
"CardHeader",
|
|
510
|
+
"CardTitle",
|
|
511
|
+
"ChangeGrid"
|
|
512
|
+
]
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
"file": "agents/examples/change-grid.tsx",
|
|
516
|
+
"components": [
|
|
517
|
+
"Card",
|
|
518
|
+
"CardContent",
|
|
519
|
+
"CardDescription",
|
|
520
|
+
"CardHeader",
|
|
521
|
+
"CardTitle",
|
|
522
|
+
"ChangeGrid"
|
|
523
|
+
]
|
|
524
|
+
},
|
|
445
525
|
{
|
|
446
526
|
"file": "agents/examples/chart-area.tsx",
|
|
447
527
|
"components": [
|
|
@@ -704,6 +784,13 @@
|
|
|
704
784
|
"DistributionBar"
|
|
705
785
|
]
|
|
706
786
|
},
|
|
787
|
+
{
|
|
788
|
+
"file": "agents/examples/drawdown-chart.tsx",
|
|
789
|
+
"components": [
|
|
790
|
+
"DrawdownChartCard",
|
|
791
|
+
"useFormat"
|
|
792
|
+
]
|
|
793
|
+
},
|
|
707
794
|
{
|
|
708
795
|
"file": "agents/examples/dropdown-menu.tsx",
|
|
709
796
|
"components": [
|
|
@@ -730,6 +817,22 @@
|
|
|
730
817
|
"useFormat"
|
|
731
818
|
]
|
|
732
819
|
},
|
|
820
|
+
{
|
|
821
|
+
"file": "agents/examples/heatmap-calendar-signed.tsx",
|
|
822
|
+
"components": [
|
|
823
|
+
"CalendarHeatmap",
|
|
824
|
+
"CalendarHeatmapBlock",
|
|
825
|
+
"CalendarHeatmapBody",
|
|
826
|
+
"CalendarHeatmapFooter",
|
|
827
|
+
"CalendarHeatmapLegend",
|
|
828
|
+
"Card",
|
|
829
|
+
"CardContent",
|
|
830
|
+
"CardDescription",
|
|
831
|
+
"CardHeader",
|
|
832
|
+
"CardTitle",
|
|
833
|
+
"useFormat"
|
|
834
|
+
]
|
|
835
|
+
},
|
|
733
836
|
{
|
|
734
837
|
"file": "agents/examples/heatmap-calendar.tsx",
|
|
735
838
|
"components": [
|
|
@@ -776,6 +879,13 @@
|
|
|
776
879
|
"WeekdayHeatmapLegend"
|
|
777
880
|
]
|
|
778
881
|
},
|
|
882
|
+
{
|
|
883
|
+
"file": "agents/examples/histogram-chart.tsx",
|
|
884
|
+
"components": [
|
|
885
|
+
"HistogramChartCard",
|
|
886
|
+
"useFormat"
|
|
887
|
+
]
|
|
888
|
+
},
|
|
779
889
|
{
|
|
780
890
|
"file": "agents/examples/input-states.tsx",
|
|
781
891
|
"components": [
|
|
@@ -905,6 +1015,20 @@
|
|
|
905
1015
|
"useFormat"
|
|
906
1016
|
]
|
|
907
1017
|
},
|
|
1018
|
+
{
|
|
1019
|
+
"file": "agents/examples/scatter-chart-quadrants.tsx",
|
|
1020
|
+
"components": [
|
|
1021
|
+
"ScatterChartCard",
|
|
1022
|
+
"useFormat"
|
|
1023
|
+
]
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
"file": "agents/examples/scatter-chart.tsx",
|
|
1027
|
+
"components": [
|
|
1028
|
+
"ScatterChartCard",
|
|
1029
|
+
"useFormat"
|
|
1030
|
+
]
|
|
1031
|
+
},
|
|
908
1032
|
{
|
|
909
1033
|
"file": "agents/examples/scroll-area.tsx",
|
|
910
1034
|
"components": [
|
|
@@ -979,6 +1103,28 @@
|
|
|
979
1103
|
"Slider"
|
|
980
1104
|
]
|
|
981
1105
|
},
|
|
1106
|
+
{
|
|
1107
|
+
"file": "agents/examples/slope-chart-market.tsx",
|
|
1108
|
+
"components": [
|
|
1109
|
+
"Card",
|
|
1110
|
+
"CardContent",
|
|
1111
|
+
"CardDescription",
|
|
1112
|
+
"CardHeader",
|
|
1113
|
+
"CardTitle",
|
|
1114
|
+
"SlopeChart"
|
|
1115
|
+
]
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
"file": "agents/examples/slope-chart.tsx",
|
|
1119
|
+
"components": [
|
|
1120
|
+
"Card",
|
|
1121
|
+
"CardContent",
|
|
1122
|
+
"CardDescription",
|
|
1123
|
+
"CardHeader",
|
|
1124
|
+
"CardTitle",
|
|
1125
|
+
"SlopeChart"
|
|
1126
|
+
]
|
|
1127
|
+
},
|
|
982
1128
|
{
|
|
983
1129
|
"file": "agents/examples/surfaces.tsx",
|
|
984
1130
|
"components": [
|
|
@@ -1076,6 +1222,20 @@
|
|
|
1076
1222
|
"TreemapLegend",
|
|
1077
1223
|
"useFormat"
|
|
1078
1224
|
]
|
|
1225
|
+
},
|
|
1226
|
+
{
|
|
1227
|
+
"file": "agents/examples/waterfall-chart-stores.tsx",
|
|
1228
|
+
"components": [
|
|
1229
|
+
"WaterfallChartCard",
|
|
1230
|
+
"useFormat"
|
|
1231
|
+
]
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
"file": "agents/examples/waterfall-chart.tsx",
|
|
1235
|
+
"components": [
|
|
1236
|
+
"WaterfallChartCard",
|
|
1237
|
+
"useFormat"
|
|
1238
|
+
]
|
|
1079
1239
|
}
|
|
1080
1240
|
]
|
|
1081
1241
|
}
|
|
@@ -32,6 +32,10 @@ export type CalendarHeatmapProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
32
32
|
blockAspectRatio?: number;
|
|
33
33
|
levels?: number;
|
|
34
34
|
isNormalized?: boolean;
|
|
35
|
+
/** Colour each day green or red by its sign, deeper the larger it is, for a daily gain or loss. */
|
|
36
|
+
signed?: boolean;
|
|
37
|
+
/** With signed: the size that takes the deepest step. Defaults to the largest day's. */
|
|
38
|
+
range?: number;
|
|
35
39
|
colors?: ColorConfig;
|
|
36
40
|
locale?: Locale;
|
|
37
41
|
labels?: Labels;
|
|
@@ -75,7 +79,7 @@ export type CalendarHeatmapProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
75
79
|
* @param levels - Total number of legend cells (including empty when not normalized). Default: 5
|
|
76
80
|
* @param isNormalized - When true, uses min-max normalization across the dataset (suitable for signed values). When false (default), treats 0 as empty and scales from 0 to max.
|
|
77
81
|
*/
|
|
78
|
-
export declare const CalendarHeatmap: ({ data, weekStart, continuousMonths, hasEmptyColumn, blockSize: blockSizeProp, blockMargin, blockRadius, blockAspectRatio, levels: levelsProp, isNormalized, colors, locale, labels: labelsProp, fontSize, emptyState, totalCount: totalCountProp, style, className, children, ...props }: CalendarHeatmapProps) => string | number | bigint | true | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null;
|
|
82
|
+
export declare const CalendarHeatmap: ({ data, weekStart, continuousMonths, hasEmptyColumn, blockSize: blockSizeProp, blockMargin, blockRadius, blockAspectRatio, levels: levelsProp, isNormalized, signed, range, colors, locale, labels: labelsProp, fontSize, emptyState, totalCount: totalCountProp, style, className, children, ...props }: CalendarHeatmapProps) => string | number | bigint | true | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null;
|
|
79
83
|
export type CalendarHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
|
|
80
84
|
/** The cell the page has picked: drawn with a ring and announced as pressed. */
|
|
81
85
|
selected?: boolean;
|
|
@@ -127,10 +131,12 @@ export type CalendarHeatmapLegendProps = Omit<HTMLAttributes<HTMLFieldSetElement
|
|
|
127
131
|
less?: string;
|
|
128
132
|
more?: string;
|
|
129
133
|
};
|
|
134
|
+
/** A signed heatmap's figures in the legend's scale line, such as useFormat().money. */
|
|
135
|
+
valueFormatter?: (value: number) => string;
|
|
130
136
|
children?: (props: {
|
|
131
137
|
level: number;
|
|
132
138
|
}) => ReactNode;
|
|
133
139
|
};
|
|
134
|
-
export declare const CalendarHeatmapLegend: ({ labels: labelsProp, className, children, ...props }: CalendarHeatmapLegendProps) => import("react").JSX.Element;
|
|
140
|
+
export declare const CalendarHeatmapLegend: ({ labels: labelsProp, valueFormatter, className, children, ...props }: CalendarHeatmapLegendProps) => import("react").JSX.Element;
|
|
135
141
|
//#endregion
|
|
136
142
|
export {};
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.js";
|
|
2
2
|
import { useHeatmapNavigation } from "../../../lib/heatmap-navigation.js";
|
|
3
3
|
import { useElementWidth } from "../../../lib/use-element-width.js";
|
|
4
|
+
import { heat, heatFill } from "../../../lib/heat.js";
|
|
4
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
import { Fragment as Fragment$1, createContext, use, useLayoutEffect, useMemo } from "react";
|
|
6
7
|
import { differenceInCalendarDays, eachDayOfInterval, format, formatISO, getDay, getMonth, getYear, nextDay, parseISO, subWeeks } from "date-fns";
|
|
7
8
|
//#region src/components/registry/heatmap/calendar-heatmap.tsx
|
|
8
9
|
const colorStepCount = (levels, isNormalized) => Math.max(1, isNormalized ? levels : levels - 1);
|
|
9
|
-
const getLevelFill = (level, levels, isNormalized,
|
|
10
|
+
const getLevelFill = (level, levels, isNormalized, colors) => {
|
|
10
11
|
const emptyColor = colors?.empty ?? "var(--color-secondary)";
|
|
11
12
|
const scaleColor = colors?.scale ?? "var(--color-chart-1)";
|
|
12
13
|
if (level === 0) return emptyColor;
|
|
13
14
|
const steps = colorStepCount(levels, isNormalized);
|
|
14
|
-
|
|
15
|
-
return `color-mix(in oklch, ${scaleColor} ${highlighted ? Math.round(opacity * .6) : opacity}%, transparent)`;
|
|
15
|
+
return `color-mix(in oklch, ${scaleColor} ${steps === 1 ? 100 : Math.round(20 + (level - 1) * 80 / (steps - 1))}%, transparent)`;
|
|
16
16
|
};
|
|
17
17
|
const calculateLevel = (value, minValue, maxValue, levels, isNormalized) => {
|
|
18
18
|
const steps = colorStepCount(levels, isNormalized);
|
|
@@ -40,6 +40,14 @@ const generateWeekdayLabels = (locale) => {
|
|
|
40
40
|
return date.toLocaleString("en-US", { weekday: "short" });
|
|
41
41
|
});
|
|
42
42
|
};
|
|
43
|
+
/** How a signed day is drawn: its heat tint, the edge that carries its sign's colour, and whether it is round. */
|
|
44
|
+
const signedMark = (value) => {
|
|
45
|
+
const known = Number.isFinite(value) ? value : 0;
|
|
46
|
+
return {
|
|
47
|
+
stroke: known > 0 ? "var(--positive)" : known < 0 ? "var(--negative)" : "var(--input)",
|
|
48
|
+
round: known < 0
|
|
49
|
+
};
|
|
50
|
+
};
|
|
43
51
|
const EMPTY_STYLE = {};
|
|
44
52
|
const LABEL_MARGIN = 8;
|
|
45
53
|
const WEEKDAY_LABEL_WIDTH = 40;
|
|
@@ -218,7 +226,7 @@ const getMonthLabels = (weeks, monthNames = generateMonthLabels(), yearTicks = f
|
|
|
218
226
|
* @param levels - Total number of legend cells (including empty when not normalized). Default: 5
|
|
219
227
|
* @param isNormalized - When true, uses min-max normalization across the dataset (suitable for signed values). When false (default), treats 0 as empty and scales from 0 to max.
|
|
220
228
|
*/
|
|
221
|
-
const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmptyColumn = false, blockSize: blockSizeProp, blockMargin = 4, blockRadius = 2, blockAspectRatio = 1, levels: levelsProp = 5, isNormalized = false, colors, locale, labels: labelsProp, fontSize = 14, emptyState, totalCount: totalCountProp, style = EMPTY_STYLE, className, children, ...props }) => {
|
|
229
|
+
const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmptyColumn = false, blockSize: blockSizeProp, blockMargin = 4, blockRadius = 2, blockAspectRatio = 1, levels: levelsProp = 5, isNormalized = false, signed = false, range, colors, locale, labels: labelsProp, fontSize = 14, emptyState, totalCount: totalCountProp, style = EMPTY_STYLE, className, children, ...props }) => {
|
|
222
230
|
const levels = Math.max(1, levelsProp);
|
|
223
231
|
const [rootRef, rootWidth] = useElementWidth();
|
|
224
232
|
const dataWithLevels = useMemo(() => {
|
|
@@ -241,6 +249,15 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
241
249
|
continuousMonths
|
|
242
250
|
]);
|
|
243
251
|
const weeks = useMemo(() => yearRows.flatMap((r) => r.weeks), [yearRows]);
|
|
252
|
+
const signedRange = useMemo(() => {
|
|
253
|
+
if (!signed) return null;
|
|
254
|
+
if (range !== void 0 && Number.isFinite(range) && range > 0) return range;
|
|
255
|
+
return Math.max(0, ...data.map((d) => Number.isFinite(d.value) ? Math.abs(d.value) : 0));
|
|
256
|
+
}, [
|
|
257
|
+
signed,
|
|
258
|
+
range,
|
|
259
|
+
data
|
|
260
|
+
]);
|
|
244
261
|
const labels = useMemo(() => ({
|
|
245
262
|
months: generateMonthLabels(locale),
|
|
246
263
|
weekdays: generateWeekdayLabels(locale),
|
|
@@ -280,7 +297,8 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
280
297
|
hasEmptyColumn,
|
|
281
298
|
continuousMonths,
|
|
282
299
|
locale,
|
|
283
|
-
colors
|
|
300
|
+
colors,
|
|
301
|
+
signedRange
|
|
284
302
|
}), [
|
|
285
303
|
dataWithLevels,
|
|
286
304
|
weeks,
|
|
@@ -303,7 +321,8 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
303
321
|
hasEmptyColumn,
|
|
304
322
|
continuousMonths,
|
|
305
323
|
locale,
|
|
306
|
-
colors
|
|
324
|
+
colors,
|
|
325
|
+
signedRange
|
|
307
326
|
]);
|
|
308
327
|
if (data.length === 0) return emptyState ? emptyState : null;
|
|
309
328
|
return /* @__PURE__ */ jsx(CalendarHeatmapContext, {
|
|
@@ -322,8 +341,9 @@ const CalendarHeatmap = ({ data, weekStart = 0, continuousMonths = true, hasEmpt
|
|
|
322
341
|
});
|
|
323
342
|
};
|
|
324
343
|
const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted = false, selected = false, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }) => {
|
|
325
|
-
const { blockSize, blockWidth, blockMargin, blockRadius, labelHeight, labels, levels, isNormalized, colors } = useCalendarHeatmap();
|
|
344
|
+
const { blockSize, blockWidth, blockMargin, blockRadius, labelHeight, labels, levels, isNormalized, colors, signedRange } = useCalendarHeatmap();
|
|
326
345
|
const level = Math.max(0, Math.min(colorStepCount(levels, isNormalized), activity.level));
|
|
346
|
+
const mark = signedRange === null ? null : signedMark(activity.value);
|
|
327
347
|
const ariaLabel = (labels.cellLabel ?? "{{date}}: {{value}} contributions").replace("{{date}}", activity.date).replace("{{value}}", String(activity.value));
|
|
328
348
|
return /* @__PURE__ */ jsx("rect", {
|
|
329
349
|
ref,
|
|
@@ -332,24 +352,22 @@ const CalendarHeatmapBlock = ({ ref, activity, dayIndex, weekIndex, highlighted
|
|
|
332
352
|
"aria-pressed": onCellClick ? selected : void 0,
|
|
333
353
|
tabIndex: onCellClick ? 0 : -1,
|
|
334
354
|
"aria-label": ariaLabel,
|
|
335
|
-
className: cn("
|
|
355
|
+
className: cn("hover:stroke-foreground hover:stroke-1", highlighted && "stroke-foreground stroke-1", selected && "stroke-foreground stroke-2 hover:stroke-2", onCellClick && "cursor-pointer focus-visible:outline-none focus-visible:stroke-ring focus-visible:stroke-2", className),
|
|
336
356
|
"data-value": activity.value,
|
|
337
357
|
"data-date": activity.date,
|
|
338
358
|
"data-level": level,
|
|
339
359
|
"data-highlighted": highlighted || void 0,
|
|
340
360
|
"data-selected": selected || void 0,
|
|
341
361
|
height: blockSize,
|
|
342
|
-
rx: blockRadius,
|
|
343
|
-
ry: blockRadius,
|
|
362
|
+
rx: mark?.round ? blockSize / 2 : blockRadius,
|
|
363
|
+
ry: mark?.round ? blockSize / 2 : blockRadius,
|
|
344
364
|
width: blockWidth,
|
|
365
|
+
stroke: mark?.stroke,
|
|
366
|
+
strokeWidth: mark ? 1 : void 0,
|
|
345
367
|
x: (blockWidth + blockMargin) * weekIndex,
|
|
346
368
|
y: labelHeight + (blockSize + blockMargin) * dayIndex,
|
|
347
369
|
style: {
|
|
348
|
-
fill: getLevelFill(level, levels, isNormalized,
|
|
349
|
-
...selected ? {
|
|
350
|
-
stroke: "var(--foreground)",
|
|
351
|
-
strokeWidth: 2
|
|
352
|
-
} : {},
|
|
370
|
+
fill: signedRange === null ? getLevelFill(level, levels, isNormalized, colors) : heat(activity.value, signedRange),
|
|
353
371
|
...styleProp
|
|
354
372
|
},
|
|
355
373
|
onClick: (event) => {
|
|
@@ -498,8 +516,14 @@ const CalendarHeatmapStat = ({ compute, label, className, children, ...props })
|
|
|
498
516
|
children: template.replace("{{value}}", String(value)).replace("{{year}}", String(year))
|
|
499
517
|
});
|
|
500
518
|
};
|
|
501
|
-
const CalendarHeatmapLegend = ({ labels: labelsProp, className, children, ...props }) => {
|
|
502
|
-
const { levels, isNormalized, blockRadius, colors, labels } = useCalendarHeatmap();
|
|
519
|
+
const CalendarHeatmapLegend = ({ labels: labelsProp, valueFormatter, className, children, ...props }) => {
|
|
520
|
+
const { levels, isNormalized, blockRadius, colors, labels, signedRange } = useCalendarHeatmap();
|
|
521
|
+
if (signedRange !== null) return /* @__PURE__ */ jsx(SignedLegend, {
|
|
522
|
+
labels: labelsProp,
|
|
523
|
+
valueFormatter,
|
|
524
|
+
className,
|
|
525
|
+
...props
|
|
526
|
+
});
|
|
503
527
|
const lessLabel = labelsProp?.less ?? "Less";
|
|
504
528
|
const moreLabel = labelsProp?.more ?? "More";
|
|
505
529
|
const legendLevels = Array.from({ length: levels }, (_, i) => isNormalized ? i + 1 : i);
|
|
@@ -524,7 +548,7 @@ const CalendarHeatmapLegend = ({ labels: labelsProp, className, children, ...pro
|
|
|
524
548
|
rx: blockRadius,
|
|
525
549
|
ry: blockRadius,
|
|
526
550
|
width: LEGEND_SWATCH,
|
|
527
|
-
style: { fill: getLevelFill(level, levels, isNormalized,
|
|
551
|
+
style: { fill: getLevelFill(level, levels, isNormalized, colors) }
|
|
528
552
|
})
|
|
529
553
|
}, `legend-level-${level}`)),
|
|
530
554
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -534,5 +558,59 @@ const CalendarHeatmapLegend = ({ labels: labelsProp, className, children, ...pro
|
|
|
534
558
|
]
|
|
535
559
|
});
|
|
536
560
|
};
|
|
561
|
+
/** A signed heatmap's scale: round loss steps deepest first, no change, square gain steps, and what they span. */
|
|
562
|
+
const SignedLegend = ({ labels: labelsProp, valueFormatter = String, className, ...props }) => {
|
|
563
|
+
const { blockRadius, labels, signedRange } = useCalendarHeatmap();
|
|
564
|
+
const steps = Array.from({ length: 5 }, (_, i) => i + 1);
|
|
565
|
+
const swatch = (key, fill, value) => {
|
|
566
|
+
const { stroke, round } = signedMark(value);
|
|
567
|
+
const corner = round ? 11 / 2 : blockRadius;
|
|
568
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
569
|
+
"aria-hidden": "true",
|
|
570
|
+
height: LEGEND_SWATCH,
|
|
571
|
+
width: LEGEND_SWATCH,
|
|
572
|
+
children: /* @__PURE__ */ jsx("rect", {
|
|
573
|
+
x: .5,
|
|
574
|
+
y: .5,
|
|
575
|
+
height: 11,
|
|
576
|
+
width: 11,
|
|
577
|
+
rx: corner,
|
|
578
|
+
ry: corner,
|
|
579
|
+
stroke,
|
|
580
|
+
style: { fill }
|
|
581
|
+
})
|
|
582
|
+
}, key);
|
|
583
|
+
};
|
|
584
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
585
|
+
"data-slot": "calendar-heatmap-legend",
|
|
586
|
+
"aria-label": labels.legendLabel ?? "Activity intensity legend",
|
|
587
|
+
className: cn("ml-auto flex flex-col items-end gap-1 text-muted-foreground", className),
|
|
588
|
+
...props,
|
|
589
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
590
|
+
className: "flex items-center gap-1",
|
|
591
|
+
children: [
|
|
592
|
+
/* @__PURE__ */ jsx("span", {
|
|
593
|
+
className: "mr-1 font-medium text-xs",
|
|
594
|
+
children: labelsProp?.less ?? "Loss"
|
|
595
|
+
}),
|
|
596
|
+
steps.toReversed().map((step) => swatch(`loss-${step}`, heatFill("negative", step), -1)),
|
|
597
|
+
swatch("none", heat(null), 0),
|
|
598
|
+
steps.map((step) => swatch(`gain-${step}`, heatFill("positive", step), 1)),
|
|
599
|
+
/* @__PURE__ */ jsx("span", {
|
|
600
|
+
className: "ml-1 font-medium text-xs",
|
|
601
|
+
children: labelsProp?.more ?? "Gain"
|
|
602
|
+
})
|
|
603
|
+
]
|
|
604
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
605
|
+
"data-slot": "calendar-heatmap-legend-scale",
|
|
606
|
+
className: "text-xs",
|
|
607
|
+
children: [
|
|
608
|
+
"Deepest at ±",
|
|
609
|
+
valueFormatter(signedRange ?? 0),
|
|
610
|
+
". Round days lost, square days gained; grey days had no change."
|
|
611
|
+
]
|
|
612
|
+
})]
|
|
613
|
+
});
|
|
614
|
+
};
|
|
537
615
|
//#endregion
|
|
538
616
|
export { CalendarHeatmap, CalendarHeatmapBlock, CalendarHeatmapBody, CalendarHeatmapFooter, CalendarHeatmapLegend, CalendarHeatmapStat };
|