@usetheo/ui 0.34.0 → 0.35.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.
|
@@ -50,6 +50,17 @@ export declare function niceMax(values: number[]): number;
|
|
|
50
50
|
* marker is what makes it visible.
|
|
51
51
|
*/
|
|
52
52
|
export declare function seriesPath(points: TrendPoint[], xScale: (x: number) => number, yScale: (y: number) => number): string;
|
|
53
|
+
/**
|
|
54
|
+
* Quais posicoes do eixo recebem rotulo.
|
|
55
|
+
*
|
|
56
|
+
* Rotular 31 pontos em ~450px e ilegivel; rotular so as pontas nao responde "quando foi o pico".
|
|
57
|
+
* A densidade sai da largura REAL (`viewW`, que o ResizeObserver ja rastreia), com o primeiro e o
|
|
58
|
+
* ultimo SEMPRE presentes: sao as bordas da janela, e sem eles o leitor nao sabe de quando ate
|
|
59
|
+
* quando o grafico fala.
|
|
60
|
+
*
|
|
61
|
+
* Fora do JSX porque e aritmetica, e aritmetica isolada e aritmetica que a mutacao alcanca.
|
|
62
|
+
*/
|
|
63
|
+
export declare function xTicks(axisXs: number[], viewW: number): number[];
|
|
53
64
|
/**
|
|
54
65
|
* Quais pontos recebem marcador.
|
|
55
66
|
*
|
|
@@ -86,6 +97,20 @@ export interface TrendChartProps extends HTMLAttributes<HTMLElement> {
|
|
|
86
97
|
* Ignored when ≤ 0. Default: auto (`niceMax`).
|
|
87
98
|
*/
|
|
88
99
|
yMax?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Formata o `x` para o eixo e para a tabela acessivel. **Opcional de proposito.**
|
|
102
|
+
*
|
|
103
|
+
* O contrato do `x` e ambiguo — "bucket index or epoch-ms" — e na pratica quase unanime: das seis
|
|
104
|
+
* fabricas de serie do consumidor, cinco passam INDICE e uma passa epoch-ms. Inferir por magnitude
|
|
105
|
+
* funcionaria e seria a forma exata do defeito que este componente ja pagou (a regra de marcador
|
|
106
|
+
* do M76 chaveava em `points.length` em vez de perguntar o que queria saber, e a densificacao do
|
|
107
|
+
* M144 a desligou em silencio). Quem sabe o que o `x` significa e o chamador.
|
|
108
|
+
*
|
|
109
|
+
* Sem esta prop, nada muda: nenhum rotulo de eixo X e desenhado e a tabela segue numerando.
|
|
110
|
+
*/
|
|
111
|
+
xFormatter?: (x: number) => string;
|
|
112
|
+
/** Cabecalho da primeira coluna da tabela acessivel. "Point" sobre datas seria desonesto. */
|
|
113
|
+
xLabel?: string;
|
|
89
114
|
}
|
|
90
115
|
declare const TrendChart: import("react").ForwardRefExoticComponent<TrendChartProps & import("react").RefAttributes<HTMLElement>>;
|
|
91
116
|
export { TrendChart };
|
package/dist/index.js
CHANGED
|
@@ -5109,6 +5109,17 @@ function seriesPath(points, xScale, yScale) {
|
|
|
5109
5109
|
}
|
|
5110
5110
|
var VIEW_W = 600;
|
|
5111
5111
|
var PAD = { top: 8, right: 8, bottom: 4, left: 40 };
|
|
5112
|
+
var X_AXIS_PX = 14;
|
|
5113
|
+
var MIN_TICK_PX2 = 70;
|
|
5114
|
+
function xTicks(axisXs, viewW) {
|
|
5115
|
+
if (axisXs.length <= 2) return [...axisXs];
|
|
5116
|
+
const maxTicks = Math.max(2, Math.floor((viewW - PAD.left - PAD.right) / MIN_TICK_PX2));
|
|
5117
|
+
if (axisXs.length <= maxTicks) return [...axisXs];
|
|
5118
|
+
const passo = (axisXs.length - 1) / (maxTicks - 1);
|
|
5119
|
+
const escolhidos = /* @__PURE__ */ new Set();
|
|
5120
|
+
for (let i = 0; i < maxTicks; i++) escolhidos.add(Math.round(i * passo));
|
|
5121
|
+
return [...escolhidos].sort((a, b) => a - b).map((i) => axisXs[i]);
|
|
5122
|
+
}
|
|
5112
5123
|
var SPARSE_MARKER_MAX = 5;
|
|
5113
5124
|
function markedPoints(points) {
|
|
5114
5125
|
const finito = (p) => Number.isFinite(p?.y);
|
|
@@ -5123,10 +5134,13 @@ var TrendChart = forwardRef(
|
|
|
5123
5134
|
height = 180,
|
|
5124
5135
|
valueFormatter = (v) => String(v),
|
|
5125
5136
|
yMax: yMaxProp,
|
|
5137
|
+
xFormatter,
|
|
5138
|
+
xLabel,
|
|
5126
5139
|
className,
|
|
5127
5140
|
...props
|
|
5128
5141
|
}, ref) => {
|
|
5129
5142
|
const [viewW, setViewW] = useState(VIEW_W);
|
|
5143
|
+
const [hoverIdx, setHoverIdx] = useState(null);
|
|
5130
5144
|
const roRef = useRef(null);
|
|
5131
5145
|
const setSvgNode = useCallback((node) => {
|
|
5132
5146
|
roRef.current?.disconnect();
|
|
@@ -5172,72 +5186,149 @@ var TrendChart = forwardRef(
|
|
|
5172
5186
|
const yMax = yMaxProp != null && yMaxProp > 0 ? yMaxProp : niceMax(allPoints.map((p) => p.y)) || 1;
|
|
5173
5187
|
const axisXs = Array.from(new Set(allPoints.map((p) => p.x))).sort((a, b) => a - b);
|
|
5174
5188
|
const yByX = series.map((s) => new Map(s.points.map((p) => [p.x, p.y])));
|
|
5189
|
+
const padBottom = PAD.bottom + (xFormatter ? X_AXIS_PX : 0);
|
|
5175
5190
|
const xScale = linScale([xMin, xMax], [PAD.left, viewW - PAD.right]);
|
|
5176
|
-
const yScale = linScale([0, yMax], [height -
|
|
5191
|
+
const yScale = linScale([0, yMax], [height - padBottom, PAD.top]);
|
|
5177
5192
|
return /* @__PURE__ */ jsxs("figure", { "data-slot": "trend-chart", ref, className: cn("space-y-1", className), ...props, children: [
|
|
5178
5193
|
/* @__PURE__ */ jsx("figcaption", { className: "font-semibold text-label-caps text-muted-foreground uppercase", children: title }),
|
|
5179
5194
|
/* @__PURE__ */ jsxs(
|
|
5180
|
-
"
|
|
5195
|
+
"div",
|
|
5181
5196
|
{
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5197
|
+
"data-slot": "trend-chart-surface",
|
|
5198
|
+
className: "relative",
|
|
5199
|
+
onMouseMove: (e) => {
|
|
5200
|
+
const caixa = e.currentTarget.getBoundingClientRect();
|
|
5201
|
+
if (caixa.width === 0 || axisXs.length === 0) return;
|
|
5202
|
+
const frac = (e.clientX - caixa.left) / caixa.width;
|
|
5203
|
+
const alvo = xMin + frac * (xMax - xMin);
|
|
5204
|
+
let melhor = 0;
|
|
5205
|
+
for (let i = 1; i < axisXs.length; i++) {
|
|
5206
|
+
if (Math.abs(axisXs[i] - alvo) < Math.abs(axisXs[melhor] - alvo))
|
|
5207
|
+
melhor = i;
|
|
5208
|
+
}
|
|
5209
|
+
setHoverIdx(melhor);
|
|
5210
|
+
},
|
|
5211
|
+
onMouseLeave: () => setHoverIdx(null),
|
|
5189
5212
|
children: [
|
|
5190
|
-
/* @__PURE__ */
|
|
5191
|
-
"
|
|
5213
|
+
/* @__PURE__ */ jsxs(
|
|
5214
|
+
"svg",
|
|
5192
5215
|
{
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5216
|
+
ref: setSvgNode,
|
|
5217
|
+
viewBox: `0 0 ${viewW} ${height}`,
|
|
5218
|
+
preserveAspectRatio: "none",
|
|
5219
|
+
role: "img",
|
|
5220
|
+
"aria-label": `${title} trend`,
|
|
5221
|
+
className: "w-full",
|
|
5222
|
+
style: { height },
|
|
5223
|
+
children: [
|
|
5224
|
+
/* @__PURE__ */ jsx(
|
|
5225
|
+
"line",
|
|
5226
|
+
{
|
|
5227
|
+
x1: PAD.left,
|
|
5228
|
+
y1: height - padBottom,
|
|
5229
|
+
x2: viewW - PAD.right,
|
|
5230
|
+
y2: height - padBottom,
|
|
5231
|
+
className: "stroke-border",
|
|
5232
|
+
strokeWidth: 1
|
|
5233
|
+
}
|
|
5234
|
+
),
|
|
5235
|
+
/* @__PURE__ */ jsx(
|
|
5236
|
+
"line",
|
|
5237
|
+
{
|
|
5238
|
+
x1: PAD.left,
|
|
5239
|
+
y1: PAD.top,
|
|
5240
|
+
x2: viewW - PAD.right,
|
|
5241
|
+
y2: PAD.top,
|
|
5242
|
+
className: "stroke-border/50",
|
|
5243
|
+
strokeWidth: 1,
|
|
5244
|
+
strokeDasharray: "2 3"
|
|
5245
|
+
}
|
|
5246
|
+
),
|
|
5247
|
+
/* @__PURE__ */ jsx("text", { x: 4, y: PAD.top + 8, className: "fill-muted-foreground text-[10px]", children: valueFormatter(yMax) }),
|
|
5248
|
+
/* @__PURE__ */ jsx("text", { x: 4, y: height - padBottom, className: "fill-muted-foreground text-[10px]", children: "0" }),
|
|
5249
|
+
xFormatter ? xTicks(axisXs, viewW).map((x, i, todos) => /* @__PURE__ */ jsx(
|
|
5250
|
+
"text",
|
|
5251
|
+
{
|
|
5252
|
+
"data-slot": "trend-chart-xtick",
|
|
5253
|
+
x: xScale(x),
|
|
5254
|
+
y: height - 2,
|
|
5255
|
+
textAnchor: i === 0 ? "start" : i === todos.length - 1 ? "end" : "middle",
|
|
5256
|
+
className: "fill-muted-foreground text-[10px]",
|
|
5257
|
+
children: xFormatter(x)
|
|
5258
|
+
},
|
|
5259
|
+
x
|
|
5260
|
+
)) : null,
|
|
5261
|
+
series.map((s) => /* @__PURE__ */ jsxs("g", { children: [
|
|
5262
|
+
/* @__PURE__ */ jsx(
|
|
5263
|
+
"path",
|
|
5264
|
+
{
|
|
5265
|
+
"data-slot": "trend-chart-line",
|
|
5266
|
+
"data-series": s.name,
|
|
5267
|
+
d: seriesPath(s.points, xScale, yScale),
|
|
5268
|
+
fill: "none",
|
|
5269
|
+
stroke: s.color,
|
|
5270
|
+
strokeWidth: 1.5,
|
|
5271
|
+
children: /* @__PURE__ */ jsx("title", { children: s.name })
|
|
5272
|
+
}
|
|
5273
|
+
),
|
|
5274
|
+
markedPoints(s.points).map((p, i) => /* @__PURE__ */ jsx(
|
|
5275
|
+
"circle",
|
|
5276
|
+
{
|
|
5277
|
+
"data-slot": "trend-chart-dot",
|
|
5278
|
+
cx: xScale(p.x),
|
|
5279
|
+
cy: yScale(p.y),
|
|
5280
|
+
r: 4,
|
|
5281
|
+
fill: s.color,
|
|
5282
|
+
children: /* @__PURE__ */ jsx("title", { children: s.name })
|
|
5283
|
+
},
|
|
5284
|
+
`${s.name}-${p.x}-${i}`
|
|
5285
|
+
))
|
|
5286
|
+
] }, s.name)),
|
|
5287
|
+
hoverIdx !== null && axisXs[hoverIdx] !== void 0 ? /* @__PURE__ */ jsx(
|
|
5288
|
+
"line",
|
|
5289
|
+
{
|
|
5290
|
+
"data-slot": "trend-chart-crosshair",
|
|
5291
|
+
x1: xScale(axisXs[hoverIdx]),
|
|
5292
|
+
y1: PAD.top,
|
|
5293
|
+
x2: xScale(axisXs[hoverIdx]),
|
|
5294
|
+
y2: height - padBottom,
|
|
5295
|
+
className: "stroke-muted-foreground/40",
|
|
5296
|
+
strokeWidth: 1
|
|
5297
|
+
}
|
|
5298
|
+
) : null
|
|
5299
|
+
]
|
|
5199
5300
|
}
|
|
5200
5301
|
),
|
|
5201
|
-
/* @__PURE__ */
|
|
5202
|
-
"
|
|
5302
|
+
hoverIdx !== null && axisXs[hoverIdx] !== void 0 ? /* @__PURE__ */ jsxs(
|
|
5303
|
+
"div",
|
|
5203
5304
|
{
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
strokeDasharray: "2 3"
|
|
5211
|
-
}
|
|
5212
|
-
),
|
|
5213
|
-
/* @__PURE__ */ jsx("text", { x: 4, y: PAD.top + 8, className: "fill-muted-foreground text-[10px]", children: valueFormatter(yMax) }),
|
|
5214
|
-
/* @__PURE__ */ jsx("text", { x: 4, y: height - PAD.bottom, className: "fill-muted-foreground text-[10px]", children: "0" }),
|
|
5215
|
-
series.map((s) => /* @__PURE__ */ jsxs("g", { children: [
|
|
5216
|
-
/* @__PURE__ */ jsx(
|
|
5217
|
-
"path",
|
|
5218
|
-
{
|
|
5219
|
-
"data-slot": "trend-chart-line",
|
|
5220
|
-
"data-series": s.name,
|
|
5221
|
-
d: seriesPath(s.points, xScale, yScale),
|
|
5222
|
-
fill: "none",
|
|
5223
|
-
stroke: s.color,
|
|
5224
|
-
strokeWidth: 1.5,
|
|
5225
|
-
children: /* @__PURE__ */ jsx("title", { children: s.name })
|
|
5226
|
-
}
|
|
5227
|
-
),
|
|
5228
|
-
markedPoints(s.points).map((p, i) => /* @__PURE__ */ jsx(
|
|
5229
|
-
"circle",
|
|
5230
|
-
{
|
|
5231
|
-
"data-slot": "trend-chart-dot",
|
|
5232
|
-
cx: xScale(p.x),
|
|
5233
|
-
cy: yScale(p.y),
|
|
5234
|
-
r: 4,
|
|
5235
|
-
fill: s.color,
|
|
5236
|
-
children: /* @__PURE__ */ jsx("title", { children: s.name })
|
|
5305
|
+
"data-slot": "trend-chart-tooltip",
|
|
5306
|
+
"aria-hidden": "true",
|
|
5307
|
+
className: "pointer-events-none absolute top-0 z-10 rounded border bg-popover px-2 py-1 text-[10px] text-popover-foreground shadow",
|
|
5308
|
+
style: {
|
|
5309
|
+
left: `${xScale(axisXs[hoverIdx]) / viewW * 100}%`,
|
|
5310
|
+
transform: "translateX(-50%)"
|
|
5237
5311
|
},
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5312
|
+
children: [
|
|
5313
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium", children: xFormatter ? xFormatter(axisXs[hoverIdx]) : `#${hoverIdx + 1}` }),
|
|
5314
|
+
series.map((s, si) => {
|
|
5315
|
+
const y = yByX[si]?.get(axisXs[hoverIdx]);
|
|
5316
|
+
return /* @__PURE__ */ jsxs("div", { className: "whitespace-nowrap", children: [
|
|
5317
|
+
/* @__PURE__ */ jsx(
|
|
5318
|
+
"span",
|
|
5319
|
+
{
|
|
5320
|
+
className: "mr-1 inline-block size-2 rounded-full align-middle",
|
|
5321
|
+
style: { background: s.color }
|
|
5322
|
+
}
|
|
5323
|
+
),
|
|
5324
|
+
s.name,
|
|
5325
|
+
": ",
|
|
5326
|
+
y !== void 0 && Number.isFinite(y) ? valueFormatter(y) : "\u2014"
|
|
5327
|
+
] }, s.name);
|
|
5328
|
+
})
|
|
5329
|
+
]
|
|
5330
|
+
}
|
|
5331
|
+
) : null
|
|
5241
5332
|
]
|
|
5242
5333
|
}
|
|
5243
5334
|
),
|
|
@@ -5265,11 +5356,11 @@ var TrendChart = forwardRef(
|
|
|
5265
5356
|
" \u2014 data table"
|
|
5266
5357
|
] }),
|
|
5267
5358
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
5268
|
-
/* @__PURE__ */ jsx("th", { children: "Point" }),
|
|
5359
|
+
/* @__PURE__ */ jsx("th", { children: xLabel ?? "Point" }),
|
|
5269
5360
|
series.map((s) => /* @__PURE__ */ jsx("th", { children: s.name }, s.name))
|
|
5270
5361
|
] }) }),
|
|
5271
5362
|
/* @__PURE__ */ jsx("tbody", { children: axisXs.map((x, i) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
5272
|
-
/* @__PURE__ */ jsx("td", { children: i + 1 }),
|
|
5363
|
+
/* @__PURE__ */ jsx("td", { children: xFormatter ? xFormatter(x) : i + 1 }),
|
|
5273
5364
|
series.map((s, si) => {
|
|
5274
5365
|
const y = yByX[si]?.get(x);
|
|
5275
5366
|
return /* @__PURE__ */ jsx("td", { children: y !== void 0 && Number.isFinite(y) ? valueFormatter(y) : "\u2014" }, s.name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usetheo/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|