@lolmath/ui 9.5.0 → 9.6.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.
@@ -0,0 +1,942 @@
1
+ /* empty css */
2
+ import { cx } from "cva";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { useMemo } from "react";
5
+ import { areaY, areaY as areaY$1, barX, barX as barX$1, barY, barY as barY$1, crosshair, crosshair as crosshair$1, d3Curve, defineChart, dot, dot as dot$1, group, group as group$1, lineX, lineY, lineY as lineY$1, rect, ruleX, ruleY, stack, stack as stack$1, text, text as text$1, tickX, tickY } from "@tanstack/charts";
6
+ import { scaleBand, scaleBand as scaleBand$1 } from "@tanstack/charts/scales/band";
7
+ import { scaleLinear, scaleLinear as scaleLinear$1 } from "@tanstack/charts/scales/linear";
8
+ import { scaleOrdinal } from "@tanstack/charts/scales/ordinal";
9
+ import { scalePoint, scalePoint as scalePoint$1 } from "@tanstack/charts/scales/point";
10
+ import { tooltip, tooltip as tooltip$1 } from "@tanstack/charts/tooltip";
11
+ import { Chart } from "@tanstack/charts/react";
12
+ //#region src/charts/curves.ts
13
+ /** Straight segments, like d3's `curveLinear`. */
14
+ const linearCurve = makeCurve({ segments: linearSegments });
15
+ /**
16
+ * Monotone cubic interpolation over x — d3's `curveMonotoneX`, by way of
17
+ * Steffen's method. Smooth, but it never overshoots a reading, so a rounded
18
+ * corner cannot invent a value the data never had.
19
+ */
20
+ const smoothCurve = makeCurve({
21
+ segments: monotoneSegments,
22
+ dedupe: true
23
+ });
24
+ /** Holds each value until the next one, like d3's `curveStepAfter`. */
25
+ const stepCurve = makeCurve({
26
+ segments: stepAfterSegments,
27
+ back: stepBeforeSegments
28
+ });
29
+ const CURVES = {
30
+ linear: linearCurve,
31
+ smooth: smoothCurve,
32
+ step: stepCurve
33
+ };
34
+ function resolveCurve(name) {
35
+ return CURVES[name];
36
+ }
37
+ function makeCurve(kernel) {
38
+ const back = kernel.back ?? kernel.segments;
39
+ const prepare = (points) => kernel.dedupe ? dedupe(points) : points;
40
+ return {
41
+ line: (points) => {
42
+ const run = prepare(points);
43
+ if (run.length === 0) return "";
44
+ const tail = run.length === 1 ? "Z" : kernel.segments(run);
45
+ return `M${pair(run[0])}${tail}`;
46
+ },
47
+ area: (top, bottom) => {
48
+ const upper = prepare(top);
49
+ const lower = prepare([...bottom].reverse());
50
+ if (upper.length === 0 || lower.length === 0) return "";
51
+ return `M${pair(upper[0])}${upper.length === 1 ? "" : kernel.segments(upper)}L${pair(lower[0])}${lower.length === 1 ? "" : back(lower)}Z`;
52
+ }
53
+ };
54
+ }
55
+ function dedupe(points) {
56
+ const run = [];
57
+ for (const point of points) {
58
+ const last = run[run.length - 1];
59
+ if (last && last[0] === point[0] && last[1] === point[1]) continue;
60
+ run.push(point);
61
+ }
62
+ return run;
63
+ }
64
+ function linearSegments(points) {
65
+ let path = "";
66
+ for (let index = 1; index < points.length; index += 1) path += `L${pair(points[index])}`;
67
+ return path;
68
+ }
69
+ /** Across at the height being held, then up to the new one. */
70
+ function stepAfterSegments(points) {
71
+ let path = "";
72
+ for (let index = 1; index < points.length; index += 1) {
73
+ const [x, y] = points[index];
74
+ path += `L${number(x)},${number(points[index - 1][1])}L${number(x)},${number(y)}`;
75
+ }
76
+ return path;
77
+ }
78
+ /** Up to the new height first, then across — the mirror of the above. */
79
+ function stepBeforeSegments(points) {
80
+ let path = "";
81
+ for (let index = 1; index < points.length; index += 1) {
82
+ const [x, y] = points[index];
83
+ path += `L${number(points[index - 1][0])},${number(y)}L${number(x)},${number(y)}`;
84
+ }
85
+ return path;
86
+ }
87
+ function monotoneSegments(points) {
88
+ if (points.length < 3) return linearSegments(points);
89
+ const tangents = monotoneTangents(points);
90
+ let path = "";
91
+ for (let index = 1; index < points.length; index += 1) {
92
+ const [x0, y0] = points[index - 1];
93
+ const [x1, y1] = points[index];
94
+ const third = (x1 - x0) / 3;
95
+ path += `C${number(x0 + third)},${number(y0 + third * tangents[index - 1])},${number(x1 - third)},${number(y1 - third * tangents[index])},${number(x1)},${number(y1)}`;
96
+ }
97
+ return path;
98
+ }
99
+ /**
100
+ * The tangent at every point. Interior tangents come from Steffen's rule,
101
+ * which clamps each one to the smaller neighbouring slope and zeroes it at a
102
+ * turning point — that clamp is what keeps the curve monotone. The two ends
103
+ * take d3's one-sided estimate off their single neighbour.
104
+ */
105
+ function monotoneTangents(points) {
106
+ const count = points.length;
107
+ const tangents = new Array(count);
108
+ for (let index = 1; index < count - 1; index += 1) tangents[index] = steffen(points[index - 1], points[index], points[index + 1]);
109
+ tangents[0] = oneSided(points[0], points[1], tangents[1]);
110
+ tangents[count - 1] = oneSided(points[count - 2], points[count - 1], tangents[count - 2]);
111
+ return tangents;
112
+ }
113
+ function steffen(before, at, after) {
114
+ const runBefore = at[0] - before[0];
115
+ const runAfter = after[0] - at[0];
116
+ const slopeBefore = (at[1] - before[1]) / (runBefore || (runAfter < 0 ? -0 : 0));
117
+ const slopeAfter = (after[1] - at[1]) / (runAfter || (runBefore < 0 ? -0 : 0));
118
+ const parabolic = (slopeBefore * runAfter + slopeAfter * runBefore) / (runBefore + runAfter);
119
+ return (sign(slopeBefore) + sign(slopeAfter)) * Math.min(Math.abs(slopeBefore), Math.abs(slopeAfter), .5 * Math.abs(parabolic)) || 0;
120
+ }
121
+ function oneSided(from, to, neighbour) {
122
+ const run = to[0] - from[0];
123
+ return run ? (3 * ((to[1] - from[1]) / run) - neighbour) / 2 : neighbour;
124
+ }
125
+ function sign(value) {
126
+ return value < 0 ? -1 : 1;
127
+ }
128
+ function pair(point) {
129
+ return `${number(point[0])},${number(point[1])}`;
130
+ }
131
+ /** Trims float noise, so an unchanged scene serialises to an unchanged path. */
132
+ function number(value) {
133
+ return Number.isFinite(value) ? String(Math.round(value * 1e6) / 1e6) : "0";
134
+ }
135
+ //#endregion
136
+ //#region src/charts/theme/theme.ts
137
+ /**
138
+ * The Hextech palette, in slot order. Every entry is a CSS custom property, so
139
+ * a host page retunes the whole library by redefining `--lol-chart-series-N`
140
+ * rather than by threading colours through props.
141
+ *
142
+ * Assign these in sequence and never cycle them: a seventh series is not a
143
+ * seventh colour, it is a sign that the chart should fold its tail into
144
+ * "Other" or split into small multiples.
145
+ */
146
+ const hextechPalette = [
147
+ "var(--lol-chart-series-1)",
148
+ "var(--lol-chart-series-2)",
149
+ "var(--lol-chart-series-3)",
150
+ "var(--lol-chart-series-4)",
151
+ "var(--lol-chart-series-5)",
152
+ "var(--lol-chart-series-6)"
153
+ ];
154
+ /** How many categorical series the palette can carry. */
155
+ const hextechPaletteSize = hextechPalette.length;
156
+ /**
157
+ * Dot, bubble and scatter forms are held to a harder test than bars and lines:
158
+ * any two marks can end up touching, not just neighbouring slots. The palette
159
+ * clears that test for its first three slots, so those forms cap at three
160
+ * series — past that, facet rather than reach for a fourth colour.
161
+ */
162
+ const hextechScatterPaletteSize = 3;
163
+ /**
164
+ * Reserved status colours. A series that *means* good or bad — a win rate, a
165
+ * gold lead, a delta — wears these; a series that is merely "the third one"
166
+ * wears the categorical palette. Never both in one chart.
167
+ */
168
+ const hextechStatusColors = {
169
+ positive: "var(--lol-chart-positive)",
170
+ negative: "var(--lol-chart-negative)"
171
+ };
172
+ /**
173
+ * The chart theme: gold ink and a near-black plot, matching the League client.
174
+ *
175
+ * `background` stays transparent so the frame behind the chart — or whatever
176
+ * the host puts there — shows through.
177
+ */
178
+ const hextechChartTheme = {
179
+ foreground: "var(--lol-chart-foreground)",
180
+ muted: "var(--lol-chart-muted)",
181
+ grid: "var(--lol-chart-grid)",
182
+ background: "transparent",
183
+ palette: hextechPalette
184
+ };
185
+ /** The colour for a categorical slot, counted from zero and never wrapped. */
186
+ function hextechSeriesColor(index) {
187
+ return hextechPalette[index] ?? hextechPalette[hextechPalette.length - 1];
188
+ }
189
+ /**
190
+ * Applies the Hextech theme to a chart definition you wrote by hand, keeping
191
+ * any theme fields the definition sets for itself.
192
+ *
193
+ * ```ts
194
+ * const chart = withHextechTheme(defineChart({ marks: [...], x, y }));
195
+ * ```
196
+ */
197
+ function withHextechTheme(definition) {
198
+ return {
199
+ ...definition,
200
+ theme: {
201
+ ...hextechChartTheme,
202
+ ...definition.theme
203
+ }
204
+ };
205
+ }
206
+ //#endregion
207
+ //#region src/charts/components/chart-common.ts
208
+ /** The colour a series is drawn in: its own, or its slot in the palette. */
209
+ function seriesColor(series, index) {
210
+ return series.color ?? hextechSeriesColor(index);
211
+ }
212
+ function seriesLabel(series) {
213
+ return series.label ?? series.key;
214
+ }
215
+ function legendItems(series) {
216
+ return series.map((entry, index) => ({
217
+ key: entry.key,
218
+ label: seriesLabel(entry),
219
+ color: seriesColor(entry, index)
220
+ }));
221
+ }
222
+ /**
223
+ * Whether to draw the legend. One series does not get one: there is a single
224
+ * colour, and the title already says what it is.
225
+ */
226
+ function showLegend(series, legend) {
227
+ return legend ?? series.length > 1;
228
+ }
229
+ /**
230
+ * The chart needs a label whatever the caller passed. A string title is the
231
+ * obvious one; past that we fall back to naming the series, so the chart is
232
+ * never announced as an unlabelled graphic.
233
+ */
234
+ function resolveAriaLabel(ariaLabel, title, series) {
235
+ if (ariaLabel) return ariaLabel;
236
+ if (typeof title === "string") return title;
237
+ if (typeof title === "number") return String(title);
238
+ const names = series.map(seriesLabel);
239
+ return names.length ? `Chart of ${names.join(", ")}` : "Chart";
240
+ }
241
+ /**
242
+ * Folds wide rows into one row per (row, series) pair.
243
+ *
244
+ * Grouping and stacking are computed *within* a mark, off its series channel —
245
+ * so a grouped or stacked chart is a single mark over folded data, not one mark
246
+ * per series. Rows come out x-major, which keeps a stack in the order the
247
+ * `series` array declares.
248
+ */
249
+ function foldSeries(data, series, x) {
250
+ const rows = [];
251
+ for (const datum of data) {
252
+ const xValue = x(datum);
253
+ for (const entry of series) rows.push({
254
+ datum,
255
+ seriesKey: entry.key,
256
+ x: xValue,
257
+ value: entry.value(datum)
258
+ });
259
+ }
260
+ return rows;
261
+ }
262
+ /** Looks up each series' colour by key, for a folded mark's `fill`. */
263
+ function seriesColorLookup(series) {
264
+ const colors = new Map(series.map((entry, index) => [entry.key, seriesColor(entry, index)]));
265
+ return (key) => colors.get(key) ?? seriesColor(series[0] ?? {
266
+ key,
267
+ value: () => 0
268
+ }, 0);
269
+ }
270
+ /** Every distinct x value, in the order the data first mentions it. */
271
+ function xDomain(data, x) {
272
+ const seen = /* @__PURE__ */ new Set();
273
+ const domain = [];
274
+ for (const datum of data) {
275
+ const value = x(datum);
276
+ if (seen.has(value)) continue;
277
+ seen.add(value);
278
+ domain.push(value);
279
+ }
280
+ return domain;
281
+ }
282
+ /** Categorical unless every x is a number. */
283
+ function isCategoricalX(data, x) {
284
+ return data.some((datum) => typeof x(datum) !== "number");
285
+ }
286
+ /**
287
+ * The x axis for a chart whose marks sit *between* positions — lines, areas,
288
+ * dots. Categorical data gets a point scale; numbers get a linear one.
289
+ */
290
+ function pointXAxis(data, x, options) {
291
+ const categorical = isCategoricalX(data, x);
292
+ const domain = xDomain(data, x);
293
+ return {
294
+ scale: categorical ? () => scalePoint$1().domain(domain).padding(.2) : scaleLinear$1,
295
+ nice: !categorical,
296
+ axis: axisPresentation(options)
297
+ };
298
+ }
299
+ /** The x axis for marks that occupy a slot: bars and their labels. */
300
+ function bandXAxis(data, x, options) {
301
+ const domain = xDomain(data, x);
302
+ return {
303
+ scale: () => scaleBand$1().domain(domain).padding(options.padding ?? .24),
304
+ axis: axisPresentation(options)
305
+ };
306
+ }
307
+ /** A linear measure axis, niced and — usually — gridded. */
308
+ function valueAxis(options) {
309
+ return {
310
+ scale: scaleLinear$1,
311
+ nice: true,
312
+ grid: options.grid ?? true,
313
+ axis: axisPresentation(options)
314
+ };
315
+ }
316
+ function axisPresentation(options) {
317
+ return {
318
+ label: options.label,
319
+ ticks: options.format ? { format: options.format } : void 0
320
+ };
321
+ }
322
+ /**
323
+ * The tooltip, wired so it speaks the caller's labels and formats rather than
324
+ * the raw channel values. The `group` item is what turns "line-0" into
325
+ * "Blue side" on a shared-x tooltip.
326
+ */
327
+ function hextechTooltip(args) {
328
+ const labels = new Map(args.series.map((entry) => [entry.key, seriesLabel(entry)]));
329
+ const items = [{
330
+ channel: "x",
331
+ label: args.xLabel,
332
+ text: (point) => formatChartValue(point.xValue, args.formatX)
333
+ }, {
334
+ channel: "y",
335
+ label: args.yLabel,
336
+ text: (point) => formatChartValue(point.yValue, args.formatY)
337
+ }];
338
+ if (args.series.length > 1) items.push({
339
+ channel: "group",
340
+ text: (point) => labels.get(String(point.group ?? point.markId)) ?? point.groupLabel
341
+ });
342
+ return {
343
+ use: tooltip$1,
344
+ items
345
+ };
346
+ }
347
+ /** Row-level accessor for a tooltip item, tolerant of the widened value type. */
348
+ function formatChartValue(value, format) {
349
+ if (format) return format(value);
350
+ if (typeof value === "number") return numberFormat.format(value);
351
+ return String(value);
352
+ }
353
+ const numberFormat = new Intl.NumberFormat();
354
+ //#endregion
355
+ //#region src/charts/components/chart-frame/chart-frame.module.css
356
+ var chart_frame_module_default = {
357
+ "frame": "_frame_7dkb0_8",
358
+ "bare": "_bare_7dkb0_26",
359
+ "corner": "_corner_7dkb0_38",
360
+ "cornerTopLeft": "_cornerTopLeft_7dkb0_51",
361
+ "cornerTopRight": "_cornerTopRight_7dkb0_56",
362
+ "cornerBottomLeft": "_cornerBottomLeft_7dkb0_61",
363
+ "cornerBottomRight": "_cornerBottomRight_7dkb0_66",
364
+ "header": "_header_7dkb0_71",
365
+ "headings": "_headings_7dkb0_78",
366
+ "title": "_title_7dkb0_87",
367
+ "subtitle": "_subtitle_7dkb0_98",
368
+ "actions": "_actions_7dkb0_106",
369
+ "rule": "_rule_7dkb0_115",
370
+ "ruleLine": "_ruleLine_7dkb0_121",
371
+ "ruleDiamond": "_ruleDiamond_7dkb0_135",
372
+ "body": "_body_7dkb0_143",
373
+ "footer": "_footer_7dkb0_149"
374
+ };
375
+ //#endregion
376
+ //#region src/charts/components/chart-frame/chart-frame.tsx
377
+ /**
378
+ * The panel a Hextech chart sits in: a gold hairline, diamond corners, a title
379
+ * in Beaufort and a rule under it.
380
+ *
381
+ * Every chart in this package renders one of these. Reach for it directly when
382
+ * you are building a chart of your own and want it to sit alongside them.
383
+ */
384
+ function ChartFrame({ title, subtitle, actions, footer, preset = "framed", className, children, ...rest }) {
385
+ const hasHeader = title !== void 0 || subtitle !== void 0 || actions;
386
+ return /* @__PURE__ */ jsxs("figure", {
387
+ className: cx(chart_frame_module_default.frame, preset === "bare" && chart_frame_module_default.bare, className),
388
+ ...rest,
389
+ children: [
390
+ hasHeader && /* @__PURE__ */ jsxs("figcaption", {
391
+ className: chart_frame_module_default.header,
392
+ children: [/* @__PURE__ */ jsxs("div", {
393
+ className: chart_frame_module_default.headings,
394
+ children: [title !== void 0 && /* @__PURE__ */ jsx("p", {
395
+ className: chart_frame_module_default.title,
396
+ children: title
397
+ }), subtitle !== void 0 && /* @__PURE__ */ jsx("p", {
398
+ className: chart_frame_module_default.subtitle,
399
+ children: subtitle
400
+ })]
401
+ }), actions && /* @__PURE__ */ jsx("div", {
402
+ className: chart_frame_module_default.actions,
403
+ children: actions
404
+ })]
405
+ }),
406
+ hasHeader && /* @__PURE__ */ jsxs("div", {
407
+ "aria-hidden": true,
408
+ className: chart_frame_module_default.rule,
409
+ children: [
410
+ /* @__PURE__ */ jsx("span", { className: chart_frame_module_default.ruleDiamond }),
411
+ /* @__PURE__ */ jsx("hr", { className: chart_frame_module_default.ruleLine }),
412
+ /* @__PURE__ */ jsx("span", { className: chart_frame_module_default.ruleDiamond })
413
+ ]
414
+ }),
415
+ /* @__PURE__ */ jsx("div", {
416
+ className: chart_frame_module_default.body,
417
+ children
418
+ }),
419
+ footer && /* @__PURE__ */ jsx("div", {
420
+ className: chart_frame_module_default.footer,
421
+ children: footer
422
+ }),
423
+ /* @__PURE__ */ jsx("span", {
424
+ "aria-hidden": true,
425
+ className: cx(chart_frame_module_default.corner, chart_frame_module_default.cornerTopLeft)
426
+ }),
427
+ /* @__PURE__ */ jsx("span", {
428
+ "aria-hidden": true,
429
+ className: cx(chart_frame_module_default.corner, chart_frame_module_default.cornerTopRight)
430
+ }),
431
+ /* @__PURE__ */ jsx("span", {
432
+ "aria-hidden": true,
433
+ className: cx(chart_frame_module_default.corner, chart_frame_module_default.cornerBottomLeft)
434
+ }),
435
+ /* @__PURE__ */ jsx("span", {
436
+ "aria-hidden": true,
437
+ className: cx(chart_frame_module_default.corner, chart_frame_module_default.cornerBottomRight)
438
+ })
439
+ ]
440
+ });
441
+ }
442
+ //#endregion
443
+ //#region src/charts/components/chart-legend/chart-legend.module.css
444
+ var chart_legend_module_default = {
445
+ "legend": "_legend_1rk8r_2",
446
+ "item": "_item_1rk8r_13",
447
+ "swatch": "_swatch_1rk8r_26",
448
+ "square": "_square_1rk8r_35",
449
+ "line": "_line_1rk8r_39"
450
+ };
451
+ //#endregion
452
+ //#region src/charts/components/chart-legend/chart-legend.tsx
453
+ /**
454
+ * The legend every multi-series chart carries.
455
+ *
456
+ * Colour alone is never allowed to be the only way to tell two series apart,
457
+ * so this is not optional decoration — a chart with two or more series renders
458
+ * one. A single-series chart does not: its title already says what is plotted,
459
+ * and a lone swatch would only restate it.
460
+ */
461
+ function ChartLegend({ items, swatch = "diamond", className, ...rest }) {
462
+ return /* @__PURE__ */ jsx("ul", {
463
+ className: cx(chart_legend_module_default.legend, className),
464
+ ...rest,
465
+ children: items.map((item) => /* @__PURE__ */ jsxs("li", {
466
+ className: chart_legend_module_default.item,
467
+ children: [/* @__PURE__ */ jsx("span", {
468
+ "aria-hidden": true,
469
+ className: cx(chart_legend_module_default.swatch, swatch === "square" && chart_legend_module_default.square, swatch === "line" && chart_legend_module_default.line),
470
+ style: { "--lol-chart-legend-color": item.color }
471
+ }), item.label ?? item.key]
472
+ }, item.key))
473
+ });
474
+ }
475
+ //#endregion
476
+ //#region src/charts/components/hextech-chart/hextech-chart.module.css
477
+ var hextech_chart_module_default = { "chart": "_chart_1mnqy_2" };
478
+ //#endregion
479
+ //#region src/charts/components/hextech-chart/hextech-chart.tsx
480
+ /**
481
+ * A TanStack chart wearing the Hextech theme, with no frame around it.
482
+ *
483
+ * Use it when you have written a `defineChart` definition of your own and want
484
+ * it to look like the rest of the library. The theme is merged into the
485
+ * definition, so anything the definition sets for itself still wins.
486
+ */
487
+ function HextechChart({ definition, glow = true, className, wrapperProps, ...rest }) {
488
+ const themed = useMemo(() => applyHextechTheme(definition), [definition]);
489
+ const { className: wrapperClassName, ...restWrapperProps } = wrapperProps ?? {};
490
+ return /* @__PURE__ */ jsx("div", {
491
+ "data-lol-chart": "",
492
+ "data-lol-chart-glow": glow,
493
+ className: cx(hextech_chart_module_default.chart, wrapperClassName),
494
+ ...restWrapperProps,
495
+ children: /* @__PURE__ */ jsx(Chart, {
496
+ definition: themed,
497
+ className,
498
+ ...rest
499
+ })
500
+ });
501
+ }
502
+ /**
503
+ * Merges the theme in. A responsive definition builds its spec per size, so the
504
+ * theme has to go on what the builder returns rather than on the definition.
505
+ */
506
+ function applyHextechTheme(definition) {
507
+ if ("chart" in definition) {
508
+ const build = definition.chart;
509
+ return {
510
+ ...definition,
511
+ chart: (context) => withHextechTheme(build(context))
512
+ };
513
+ }
514
+ return {
515
+ ...definition,
516
+ theme: {
517
+ ...hextechChartTheme,
518
+ ...definition.theme
519
+ }
520
+ };
521
+ }
522
+ //#endregion
523
+ //#region src/charts/components/area-chart/area-chart.tsx
524
+ /**
525
+ * Filled areas — a composition over time, or a single magnitude you want to
526
+ * read as volume rather than as a trace.
527
+ *
528
+ * Overlaid areas (the default) are only honest for two or three series; past
529
+ * that the ones behind disappear. Stack them, or split into small multiples.
530
+ */
531
+ function AreaChart({ data, series, x, curve = "linear", stacked = false, normalize = false, stroke = true, crosshair: withCrosshair = true, height = 280, title, subtitle, actions, xLabel, yLabel, formatX, formatY, grid = true, legend, frame = true, glow = true, ariaLabel, ariaDescription, className, frameProps }) {
532
+ const definition = useMemo(() => {
533
+ const resolvedCurve = resolveCurve(curve);
534
+ const marks = [];
535
+ if (stacked) {
536
+ const rows = foldSeries(data, series, x);
537
+ const colorOf = seriesColorLookup(series);
538
+ marks.push(areaY$1(rows, {
539
+ id: "areas",
540
+ x: (row) => row.x,
541
+ y: (row) => row.value,
542
+ z: (row) => row.seriesKey,
543
+ fill: (row) => colorOf(row.seriesKey),
544
+ fillOpacity: .62,
545
+ curve: resolvedCurve,
546
+ layout: stack$1(normalize ? { offset: "normalize" } : void 0),
547
+ ...stroke ? {
548
+ stroke: "var(--lol-chart-surface)",
549
+ strokeWidth: 1
550
+ } : {}
551
+ }));
552
+ } else for (const [index, entry] of series.entries()) {
553
+ const color = seriesColor(entry, index);
554
+ marks.push(areaY$1(data, {
555
+ id: entry.key,
556
+ x,
557
+ y: entry.value,
558
+ fill: color,
559
+ fillOpacity: .16,
560
+ curve: resolvedCurve
561
+ }));
562
+ if (stroke) marks.push(lineY$1(data, {
563
+ id: `${entry.key}-line`,
564
+ x,
565
+ y: entry.value,
566
+ stroke: color,
567
+ strokeWidth: 2,
568
+ curve: resolvedCurve
569
+ }));
570
+ }
571
+ if (withCrosshair) marks.push(crosshair$1({
572
+ x: true,
573
+ y: false,
574
+ stroke: "var(--lol-chart-frame-accent)",
575
+ strokeOpacity: .45
576
+ }));
577
+ return {
578
+ marks,
579
+ x: pointXAxis(data, x, {
580
+ label: xLabel,
581
+ format: formatX
582
+ }),
583
+ y: valueAxis({
584
+ label: yLabel,
585
+ format: formatY,
586
+ grid
587
+ }),
588
+ focus: "group-x",
589
+ tooltip: hextechTooltip({
590
+ series,
591
+ xLabel,
592
+ yLabel,
593
+ formatX,
594
+ formatY
595
+ })
596
+ };
597
+ }, [
598
+ curve,
599
+ data,
600
+ formatX,
601
+ formatY,
602
+ grid,
603
+ normalize,
604
+ series,
605
+ stacked,
606
+ stroke,
607
+ withCrosshair,
608
+ x,
609
+ xLabel,
610
+ yLabel
611
+ ]);
612
+ return /* @__PURE__ */ jsx(ChartFrame, {
613
+ title,
614
+ subtitle,
615
+ actions,
616
+ preset: frame ? "framed" : "bare",
617
+ className,
618
+ footer: showLegend(series, legend) ? /* @__PURE__ */ jsx(ChartLegend, { items: legendItems(series) }) : void 0,
619
+ ...frameProps,
620
+ children: /* @__PURE__ */ jsx(HextechChart, {
621
+ definition,
622
+ height,
623
+ glow,
624
+ ariaLabel: resolveAriaLabel(ariaLabel, title, series),
625
+ ariaDescription
626
+ })
627
+ });
628
+ }
629
+ //#endregion
630
+ //#region src/charts/components/bar-chart/bar-chart.tsx
631
+ /**
632
+ * Columns over categories — per-champion damage, per-role gold share, counts
633
+ * by patch.
634
+ *
635
+ * Bars are separated by a gap in the surface rather than by an outline: a
636
+ * stroke around a bar is ink that carries no data.
637
+ */
638
+ function BarChart({ data, series, x, layout = "grouped", normalize = false, radius = 0, maxThickness = 24, height = 280, title, subtitle, actions, xLabel, yLabel, formatX, formatY, grid = true, legend, frame = true, glow = false, ariaLabel, ariaDescription, className, frameProps }) {
639
+ const definition = useMemo(() => {
640
+ const stacked = layout === "stacked";
641
+ const rows = foldSeries(data, series, x);
642
+ const colorOf = seriesColorLookup(series);
643
+ return {
644
+ marks: [barY$1(rows, {
645
+ id: "bars",
646
+ x: (row) => row.x,
647
+ y: (row) => row.value,
648
+ z: (row) => row.seriesKey,
649
+ fill: (row) => colorOf(row.seriesKey),
650
+ inset: 1,
651
+ maxThickness,
652
+ radius,
653
+ layout: stacked ? stack$1(normalize ? { offset: "normalize" } : void 0) : group$1({ padding: .16 })
654
+ })],
655
+ x: bandXAxis(data, x, {
656
+ label: xLabel,
657
+ format: formatX
658
+ }),
659
+ y: valueAxis({
660
+ label: yLabel,
661
+ format: formatY,
662
+ grid
663
+ }),
664
+ focus: series.length > 1 ? "group-x" : "nearest",
665
+ tooltip: hextechTooltip({
666
+ series,
667
+ xLabel,
668
+ yLabel,
669
+ formatX,
670
+ formatY
671
+ })
672
+ };
673
+ }, [
674
+ data,
675
+ formatX,
676
+ formatY,
677
+ grid,
678
+ layout,
679
+ maxThickness,
680
+ normalize,
681
+ radius,
682
+ series,
683
+ x,
684
+ xLabel,
685
+ yLabel
686
+ ]);
687
+ return /* @__PURE__ */ jsx(ChartFrame, {
688
+ title,
689
+ subtitle,
690
+ actions,
691
+ preset: frame ? "framed" : "bare",
692
+ className,
693
+ footer: showLegend(series, legend) ? /* @__PURE__ */ jsx(ChartLegend, {
694
+ items: legendItems(series),
695
+ swatch: "square"
696
+ }) : void 0,
697
+ ...frameProps,
698
+ children: /* @__PURE__ */ jsx(HextechChart, {
699
+ definition,
700
+ height,
701
+ glow,
702
+ ariaLabel: resolveAriaLabel(ariaLabel, title, series),
703
+ ariaDescription
704
+ })
705
+ });
706
+ }
707
+ //#endregion
708
+ //#region src/charts/components/line-chart/line-chart.tsx
709
+ /**
710
+ * Lines over time — gold curves, damage curves, anything that only makes sense
711
+ * read left to right.
712
+ *
713
+ * ```tsx
714
+ * <LineChart
715
+ * title="Team gold"
716
+ * data={timeline}
717
+ * x={(row) => row.minute}
718
+ * series={[
719
+ * { key: "blue", label: "Blue side", value: (row) => row.blueGold },
720
+ * { key: "red", label: "Red side", value: (row) => row.redGold },
721
+ * ]}
722
+ * xLabel="Minute"
723
+ * yLabel="Gold"
724
+ * />
725
+ * ```
726
+ */
727
+ function LineChart({ data, series, x, curve = "linear", points = false, area = false, crosshair: withCrosshair = true, height = 280, title, subtitle, actions, xLabel, yLabel, formatX, formatY, grid = true, legend, frame = true, glow = true, ariaLabel, ariaDescription, className, frameProps }) {
728
+ const definition = useMemo(() => {
729
+ const resolvedCurve = resolveCurve(curve);
730
+ const marks = [];
731
+ for (const [index, entry] of series.entries()) {
732
+ const color = seriesColor(entry, index);
733
+ if (area) marks.push(areaY$1(data, {
734
+ id: `${entry.key}-area`,
735
+ x,
736
+ y: entry.value,
737
+ fill: color,
738
+ fillOpacity: .12,
739
+ curve: resolvedCurve
740
+ }));
741
+ marks.push(lineY$1(data, {
742
+ id: entry.key,
743
+ x,
744
+ y: entry.value,
745
+ stroke: color,
746
+ strokeWidth: 2,
747
+ curve: resolvedCurve
748
+ }));
749
+ if (points) marks.push(dot$1(data, {
750
+ id: `${entry.key}-points`,
751
+ x,
752
+ y: entry.value,
753
+ r: 4,
754
+ fill: color,
755
+ stroke: "var(--lol-chart-surface)",
756
+ strokeWidth: 2
757
+ }));
758
+ }
759
+ if (withCrosshair) marks.push(crosshair$1({
760
+ x: true,
761
+ y: false,
762
+ stroke: "var(--lol-chart-frame-accent)",
763
+ strokeOpacity: .45
764
+ }));
765
+ return {
766
+ marks,
767
+ x: pointXAxis(data, x, {
768
+ label: xLabel,
769
+ format: formatX
770
+ }),
771
+ y: valueAxis({
772
+ label: yLabel,
773
+ format: formatY,
774
+ grid
775
+ }),
776
+ focus: "group-x",
777
+ tooltip: hextechTooltip({
778
+ series,
779
+ xLabel,
780
+ yLabel,
781
+ formatX,
782
+ formatY
783
+ })
784
+ };
785
+ }, [
786
+ area,
787
+ curve,
788
+ data,
789
+ formatX,
790
+ formatY,
791
+ grid,
792
+ points,
793
+ series,
794
+ withCrosshair,
795
+ x,
796
+ xLabel,
797
+ yLabel
798
+ ]);
799
+ return /* @__PURE__ */ jsx(ChartFrame, {
800
+ title,
801
+ subtitle,
802
+ actions,
803
+ preset: frame ? "framed" : "bare",
804
+ className,
805
+ footer: showLegend(series, legend) ? /* @__PURE__ */ jsx(ChartLegend, {
806
+ items: legendItems(series),
807
+ swatch: "line"
808
+ }) : void 0,
809
+ ...frameProps,
810
+ children: /* @__PURE__ */ jsx(HextechChart, {
811
+ definition,
812
+ height,
813
+ glow,
814
+ ariaLabel: resolveAriaLabel(ariaLabel, title, series),
815
+ ariaDescription
816
+ })
817
+ });
818
+ }
819
+ //#endregion
820
+ //#region src/charts/components/ranking-chart/ranking-chart.tsx
821
+ const defaultFormat = new Intl.NumberFormat();
822
+ /**
823
+ * A ranked horizontal bar chart — the leaderboard shape. Longest bar on top,
824
+ * every value written at the tip.
825
+ *
826
+ * ```tsx
827
+ * <RankingChart
828
+ * title="Damage to champions"
829
+ * data={scoreboard}
830
+ * label={(row) => row.champion}
831
+ * value={(row) => row.damage}
832
+ * formatValue={(value) => `${Math.round(value / 1000)}k`}
833
+ * />
834
+ * ```
835
+ */
836
+ function RankingChart({ data, label, value, color, order = "descending", limit, showValues = true, formatValue, valueLabel, axis = false, height, title, subtitle, actions, frame = true, glow = false, ariaLabel, ariaDescription, className, frameProps }) {
837
+ const rows = useMemo(() => {
838
+ const mapped = data.map((datum) => ({
839
+ datum,
840
+ label: label(datum),
841
+ value: value(datum)
842
+ }));
843
+ if (order === "descending") mapped.sort((a, b) => b.value - a.value);
844
+ if (order === "ascending") mapped.sort((a, b) => a.value - b.value);
845
+ return (limit === void 0 ? mapped : mapped.slice(0, limit)).map((row, rank) => ({
846
+ ...row,
847
+ rank
848
+ }));
849
+ }, [
850
+ data,
851
+ label,
852
+ limit,
853
+ order,
854
+ value
855
+ ]);
856
+ const format = formatValue ?? ((input) => defaultFormat.format(input));
857
+ const definition = useMemo(() => {
858
+ const fill = (row) => typeof color === "function" ? color(row.datum, row.rank) : color ?? hextechSeriesColor(0);
859
+ const domain = rows.map((row) => row.label);
860
+ const marks = [barX$1(rows, {
861
+ id: "value",
862
+ x: (row) => row.value,
863
+ y: (row) => row.label,
864
+ fill,
865
+ inset: 1,
866
+ maxThickness: 24
867
+ })];
868
+ if (showValues) marks.push(text$1(rows, {
869
+ id: "value-label",
870
+ x: (row) => row.value,
871
+ y: (row) => row.label,
872
+ text: (row) => format(row.value),
873
+ fill: "var(--lol-chart-foreground)",
874
+ anchor: "start",
875
+ dx: 8,
876
+ fontSize: 12
877
+ }));
878
+ return {
879
+ marks,
880
+ x: {
881
+ scale: scaleLinear$1,
882
+ nice: true,
883
+ grid: axis,
884
+ axis: axis ? {
885
+ label: valueLabel,
886
+ ticks: { format }
887
+ } : false
888
+ },
889
+ y: {
890
+ scale: () => scaleBand$1().domain(domain).padding(.28),
891
+ axis: {
892
+ line: false,
893
+ ticks: {
894
+ size: 0,
895
+ padding: 8
896
+ }
897
+ }
898
+ },
899
+ ...showValues ? { margin: { right: 56 } } : {},
900
+ clip: false,
901
+ focus: "nearest",
902
+ tooltip: {
903
+ use: tooltip$1,
904
+ items: [{
905
+ channel: "y",
906
+ label: "Name",
907
+ text: (point) => point.datum.label
908
+ }, {
909
+ channel: "x",
910
+ label: valueLabel,
911
+ text: (point) => format(point.datum.value)
912
+ }]
913
+ }
914
+ };
915
+ }, [
916
+ axis,
917
+ color,
918
+ format,
919
+ rows,
920
+ showValues,
921
+ valueLabel
922
+ ]);
923
+ return /* @__PURE__ */ jsx(ChartFrame, {
924
+ title,
925
+ subtitle,
926
+ actions,
927
+ preset: frame ? "framed" : "bare",
928
+ className,
929
+ ...frameProps,
930
+ children: /* @__PURE__ */ jsx(HextechChart, {
931
+ definition,
932
+ height: height ?? Math.max(120, rows.length * 34 + 24),
933
+ glow,
934
+ ariaLabel: ariaLabel ?? (typeof title === "string" ? title : "Ranking"),
935
+ ariaDescription
936
+ })
937
+ });
938
+ }
939
+ //#endregion
940
+ export { AreaChart, BarChart, ChartFrame, ChartLegend, HextechChart, LineChart, RankingChart, areaY, barX, barY, crosshair, d3Curve, defineChart, dot, group, hextechChartTheme, hextechPalette, hextechPaletteSize, hextechScatterPaletteSize, hextechSeriesColor, hextechStatusColors, lineX, lineY, linearCurve, rect, ruleX, ruleY, scaleBand, scaleLinear, scaleOrdinal, scalePoint, smoothCurve, stack, stepCurve, text, tickX, tickY, tooltip, withHextechTheme };
941
+
942
+ //# sourceMappingURL=charts.mjs.map