@usetheo/ui 0.33.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
export { linScale,
|
|
1
|
+
export { linScale, markedPoints, niceMax, seriesPath, TrendChart, type TrendChartProps, type TrendPoint, type TrendSeries, } from "./trend-chart.js";
|
|
@@ -50,14 +50,26 @@ 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
|
*
|
|
56
67
|
* Duas razões distintas para marcar, e a segunda só apareceu quando séries com lacuna passaram a
|
|
57
68
|
* existir (M144):
|
|
58
69
|
*
|
|
59
|
-
* 1. **Série esparsa** (< `SPARSE_MARKER_MAX`
|
|
60
|
-
* o marcador ancora o leitor no dado real. Regra do M76,
|
|
70
|
+
* 1. **Série esparsa** (< `SPARSE_MARKER_MAX` pontos FINITOS) — 1-4 pontos leem como um penhasco
|
|
71
|
+
* dramático; o marcador ancora o leitor no dado real. Regra do M76 — que passou a contar dado,
|
|
72
|
+
* não slot, porque a densificação do M144 desfez a equivalência entre os dois.
|
|
61
73
|
* 2. **Ponto isolado** — um ponto finito cercado de lacunas dos dois lados vira um `M` sozinho no
|
|
62
74
|
* `path`, e um `M` sozinho **desenha nada**: bounding-box zero, invisível. O dado existe, está
|
|
63
75
|
* correto, e o operador não vê. Medido no e2e do M144 (`d="M419.86,92.00 M448.00,36.00"`).
|
|
@@ -71,7 +83,7 @@ export declare function seriesPath(points: TrendPoint[], xScale: (x: number) =>
|
|
|
71
83
|
* Fora do JSX de propósito: é a aritmética da decisão, e aritmética isolada é aritmética que a
|
|
72
84
|
* mutação alcança.
|
|
73
85
|
*/
|
|
74
|
-
export declare function
|
|
86
|
+
export declare function markedPoints(points: TrendPoint[]): TrendPoint[];
|
|
75
87
|
export interface TrendChartProps extends HTMLAttributes<HTMLElement> {
|
|
76
88
|
title: string;
|
|
77
89
|
series: TrendSeries[];
|
|
@@ -85,6 +97,20 @@ export interface TrendChartProps extends HTMLAttributes<HTMLElement> {
|
|
|
85
97
|
* Ignored when ≤ 0. Default: auto (`niceMax`).
|
|
86
98
|
*/
|
|
87
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;
|
|
88
114
|
}
|
|
89
115
|
declare const TrendChart: import("react").ForwardRefExoticComponent<TrendChartProps & import("react").RefAttributes<HTMLElement>>;
|
|
90
116
|
export { TrendChart };
|
package/dist/index.js
CHANGED
|
@@ -5109,10 +5109,22 @@ 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
|
-
function
|
|
5124
|
+
function markedPoints(points) {
|
|
5114
5125
|
const finito = (p) => Number.isFinite(p?.y);
|
|
5115
|
-
|
|
5126
|
+
const finitos = points.filter(finito);
|
|
5127
|
+
if (finitos.length < SPARSE_MARKER_MAX) return finitos;
|
|
5116
5128
|
return points.filter((p, i) => finito(p) && !finito(points[i - 1]) && !finito(points[i + 1]));
|
|
5117
5129
|
}
|
|
5118
5130
|
var TrendChart = forwardRef(
|
|
@@ -5122,10 +5134,13 @@ var TrendChart = forwardRef(
|
|
|
5122
5134
|
height = 180,
|
|
5123
5135
|
valueFormatter = (v) => String(v),
|
|
5124
5136
|
yMax: yMaxProp,
|
|
5137
|
+
xFormatter,
|
|
5138
|
+
xLabel,
|
|
5125
5139
|
className,
|
|
5126
5140
|
...props
|
|
5127
5141
|
}, ref) => {
|
|
5128
5142
|
const [viewW, setViewW] = useState(VIEW_W);
|
|
5143
|
+
const [hoverIdx, setHoverIdx] = useState(null);
|
|
5129
5144
|
const roRef = useRef(null);
|
|
5130
5145
|
const setSvgNode = useCallback((node) => {
|
|
5131
5146
|
roRef.current?.disconnect();
|
|
@@ -5171,72 +5186,149 @@ var TrendChart = forwardRef(
|
|
|
5171
5186
|
const yMax = yMaxProp != null && yMaxProp > 0 ? yMaxProp : niceMax(allPoints.map((p) => p.y)) || 1;
|
|
5172
5187
|
const axisXs = Array.from(new Set(allPoints.map((p) => p.x))).sort((a, b) => a - b);
|
|
5173
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);
|
|
5174
5190
|
const xScale = linScale([xMin, xMax], [PAD.left, viewW - PAD.right]);
|
|
5175
|
-
const yScale = linScale([0, yMax], [height -
|
|
5191
|
+
const yScale = linScale([0, yMax], [height - padBottom, PAD.top]);
|
|
5176
5192
|
return /* @__PURE__ */ jsxs("figure", { "data-slot": "trend-chart", ref, className: cn("space-y-1", className), ...props, children: [
|
|
5177
5193
|
/* @__PURE__ */ jsx("figcaption", { className: "font-semibold text-label-caps text-muted-foreground uppercase", children: title }),
|
|
5178
5194
|
/* @__PURE__ */ jsxs(
|
|
5179
|
-
"
|
|
5195
|
+
"div",
|
|
5180
5196
|
{
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
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),
|
|
5188
5212
|
children: [
|
|
5189
|
-
/* @__PURE__ */
|
|
5190
|
-
"
|
|
5213
|
+
/* @__PURE__ */ jsxs(
|
|
5214
|
+
"svg",
|
|
5191
5215
|
{
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
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
|
+
]
|
|
5198
5300
|
}
|
|
5199
5301
|
),
|
|
5200
|
-
/* @__PURE__ */
|
|
5201
|
-
"
|
|
5302
|
+
hoverIdx !== null && axisXs[hoverIdx] !== void 0 ? /* @__PURE__ */ jsxs(
|
|
5303
|
+
"div",
|
|
5202
5304
|
{
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
strokeDasharray: "2 3"
|
|
5210
|
-
}
|
|
5211
|
-
),
|
|
5212
|
-
/* @__PURE__ */ jsx("text", { x: 4, y: PAD.top + 8, className: "fill-muted-foreground text-[10px]", children: valueFormatter(yMax) }),
|
|
5213
|
-
/* @__PURE__ */ jsx("text", { x: 4, y: height - PAD.bottom, className: "fill-muted-foreground text-[10px]", children: "0" }),
|
|
5214
|
-
series.map((s) => /* @__PURE__ */ jsxs("g", { children: [
|
|
5215
|
-
/* @__PURE__ */ jsx(
|
|
5216
|
-
"path",
|
|
5217
|
-
{
|
|
5218
|
-
"data-slot": "trend-chart-line",
|
|
5219
|
-
"data-series": s.name,
|
|
5220
|
-
d: seriesPath(s.points, xScale, yScale),
|
|
5221
|
-
fill: "none",
|
|
5222
|
-
stroke: s.color,
|
|
5223
|
-
strokeWidth: 1.5,
|
|
5224
|
-
children: /* @__PURE__ */ jsx("title", { children: s.name })
|
|
5225
|
-
}
|
|
5226
|
-
),
|
|
5227
|
-
pontosComMarcador(s.points).map((p, i) => /* @__PURE__ */ jsx(
|
|
5228
|
-
"circle",
|
|
5229
|
-
{
|
|
5230
|
-
"data-slot": "trend-chart-dot",
|
|
5231
|
-
cx: xScale(p.x),
|
|
5232
|
-
cy: yScale(p.y),
|
|
5233
|
-
r: 2.5,
|
|
5234
|
-
fill: s.color,
|
|
5235
|
-
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%)"
|
|
5236
5311
|
},
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
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
|
|
5240
5332
|
]
|
|
5241
5333
|
}
|
|
5242
5334
|
),
|
|
@@ -5264,11 +5356,11 @@ var TrendChart = forwardRef(
|
|
|
5264
5356
|
" \u2014 data table"
|
|
5265
5357
|
] }),
|
|
5266
5358
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
5267
|
-
/* @__PURE__ */ jsx("th", { children: "Point" }),
|
|
5359
|
+
/* @__PURE__ */ jsx("th", { children: xLabel ?? "Point" }),
|
|
5268
5360
|
series.map((s) => /* @__PURE__ */ jsx("th", { children: s.name }, s.name))
|
|
5269
5361
|
] }) }),
|
|
5270
5362
|
/* @__PURE__ */ jsx("tbody", { children: axisXs.map((x, i) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
5271
|
-
/* @__PURE__ */ jsx("td", { children: i + 1 }),
|
|
5363
|
+
/* @__PURE__ */ jsx("td", { children: xFormatter ? xFormatter(x) : i + 1 }),
|
|
5272
5364
|
series.map((s, si) => {
|
|
5273
5365
|
const y = yByX[si]?.get(x);
|
|
5274
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",
|