@konce-pt/react 0.18.0 → 0.18.1

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,12 +1,35 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useEffect, useId, useRef, useState } from 'react';
4
- import { KptChartMotion, areaPath, axisBase, bandPath, bandScale, barLayout, chartScale, chartAccessibleName, chartDataTable, dataScale, deltaKind, domainScale, formatNumber, linePath, linearScale, pieSlices, plotArea, polarSlices, polarToCartesian, polygonPath, radarGrid, radarPoints, radialArea, sameChartShape, scaleRows, seriesColor, significantDelta, stackSeries, } from '@konce-pt/chart';
4
+ import { KptChartMotion, areaPath, axisBase, axisLabelIndices, bandPath, bandScale, barLayout, chartScale, chartAccessibleName, chartDataTable, clampLabel, dataScale, deltaKind, domainScale, estimateLabelWidth, formatNumber, linePath, linearScale, pieSlices, plotArea, polarSlices, polarToCartesian, polygonPath, radarGrid, radarPoints, radialArea, sameChartShape, scaleRows, seriesColor, significantDelta, stackSeries, } from '@konce-pt/chart';
5
5
  import { cn } from "../internal/cn.js";
6
6
  import { useKptMotion } from "../motion/motion-context.js";
7
7
  import { useKptMessages } from "../i18n/i18n-context.js";
8
8
  import { KptChartLegend } from "./chart-legend.js";
9
9
  import { KptChartTooltip } from "./chart-tooltip.js";
10
+ /** Wysokość linii etykiety kategorii na osi pionowej (słupki poziome) - jednostki viewBoxa. */
11
+ const CATEGORY_LINE = 12;
12
+ /**
13
+ * Etykiety osi poziomej: wszystkie, jeśli się mieszczą, inaczej równy podział 9/5/3/2
14
+ * (`axisLabelIndices`). Pełna nazwa każdej kategorii zostaje w pigułce prowadnicy i w dymku.
15
+ */
16
+ function bottomLabels(labels, centers, y, width) {
17
+ const sizes = labels.map((l) => estimateLabelWidth(l));
18
+ const shown = new Set(axisLabelIndices(centers, sizes));
19
+ return labels.map((text, i) => ({
20
+ at: centers[i] ?? 0,
21
+ x: clampLabel(centers[i] ?? 0, sizes[i] ?? 0, 0, width),
22
+ y,
23
+ text,
24
+ anchor: 'middle',
25
+ visible: shown.has(i),
26
+ }));
27
+ }
28
+ /** Etykiety osi pionowej (słupki poziome): ten sam podział, rozmiarem jest wysokość linii. */
29
+ function sideLabels(labels, centers, x) {
30
+ const shown = new Set(axisLabelIndices(centers, centers.map(() => CATEGORY_LINE), 2));
31
+ return labels.map((text, i) => ({ at: centers[i] ?? 0, x, y: (centers[i] ?? 0) + 3, text, anchor: 'end', visible: shown.has(i) }));
32
+ }
10
33
  const VERTICAL_INSETS = { top: 12, right: 16, bottom: 28, left: 46 };
11
34
  const HORIZONTAL_INSETS = { top: 12, right: 16, bottom: 28, left: 76 };
12
35
  /** Szerokość pasma smugi światła (jednostki viewBoxa) - połowa po każdej stronie czoła fali. */
@@ -234,12 +257,8 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
234
257
  text: formatNumber(t),
235
258
  anchor: 'end',
236
259
  }));
237
- const catLabels = view.labels.map((label, i) => ({
238
- x: xAt(i),
239
- y: plot.y + plot.height + 18,
240
- text: label,
241
- anchor: 'middle',
242
- }));
260
+ const catLabels = bottomLabels(view.labels, view.labels.map((_, i) => xAt(i)), plot.y + plot.height + 18, width);
261
+ const catTicks = view.labels.map((_, i) => ({ x: xAt(i), y: plot.y + plot.height }));
243
262
  const series = active.map(({ s, i }, k) => ({
244
263
  key: `ser:${s.name}`,
245
264
  name: s.name,
@@ -257,14 +276,16 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
257
276
  : [];
258
277
  const crosshairCat = config.crosshair && activeIndex !== null ? catLabels[activeIndex] : undefined;
259
278
  // Prowadnica i pigułka jadą razem z dymkiem.
260
- const crossX = crosshairCat ? (frame.tip?.x ?? crosshairCat.x) : 0;
279
+ const crossX = crosshairCat ? (frame.tip?.x ?? crosshairCat.at) : 0;
261
280
  const pillWidth = crosshairCat ? Math.max(28, crosshairCat.text.length * 7 + 16) : 0;
281
+ // Pigułka przy skrajnej kategorii przesuwa się do środka zamiast wyjść poza viewBox.
282
+ const pillX = crosshairCat ? clampLabel(crossX, pillWidth, 0, width) : 0;
262
283
  /** Najbliższa kategoria w całym obszarze wykresu: wiersze wszystkich serii, dymek nad najwyższym punktem. */
263
284
  const onMove = (event) => {
264
285
  const p = svgPoint(event);
265
286
  if (!p || !series.length)
266
287
  return;
267
- const idx = nearestIndex(catLabels.map((l) => l.x), p.x);
288
+ const idx = nearestIndex(catLabels.map((l) => l.at), p.x);
268
289
  if (keepTip(idx))
269
290
  return;
270
291
  const hits = series.map((s) => ({ s, pt: s.points[idx] })).filter((e) => !!e.pt);
@@ -278,7 +299,7 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
278
299
  if (moved && tipMotion())
279
300
  setRipple({ id: ++rippleId.current, x: top.pt.x, y: top.pt.y, color: top.s.color });
280
301
  };
281
- return (_jsxs(_Fragment, { children: [_jsx("g", { className: "kpt-chart__grid", children: grid.map((g, i) => (_jsx("line", { className: "kpt-chart__gridline", x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 }, i))) }), crosshairCat ? (_jsx("line", { className: "kpt-chart__crosshair", x1: crossX, y1: plot.y, x2: crossX, y2: plot.y + plot.height })) : null, _jsx("g", { className: "kpt-chart__axis", children: [...valueLabels, ...catLabels].map((l, i) => (_jsx("text", { x: l.x, y: l.y, textAnchor: l.anchor, children: l.text }, i))) }), crosshairCat ? (_jsxs("g", { className: "kpt-chart__cat-active", children: [_jsx("rect", { x: crossX - pillWidth / 2, y: crosshairCat.y - 13, width: pillWidth, height: 18, rx: 9 }), _jsx("text", { x: crossX, y: crosshairCat.y, textAnchor: "middle", children: crosshairCat.text })] })) : null, ghostState
302
+ return (_jsxs(_Fragment, { children: [_jsx("g", { className: "kpt-chart__grid", children: grid.map((g, i) => (_jsx("line", { className: "kpt-chart__gridline", x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 }, i))) }), crosshairCat ? (_jsx("line", { className: "kpt-chart__crosshair", x1: crossX, y1: plot.y, x2: crossX, y2: plot.y + plot.height })) : null, _jsx("g", { className: "kpt-chart__axis", children: [...valueLabels, ...catLabels.filter((l) => l.visible)].map((l, i) => (_jsx("text", { x: l.x, y: l.y, textAnchor: l.anchor, children: l.text }, i))) }), _jsx("g", { className: "kpt-chart__cat-ticks", "aria-hidden": "true", children: catTicks.map((tk, i) => (_jsx("circle", { className: cn('kpt-chart__cat-tick', activeIndex === i && 'kpt-chart__cat-tick--active'), r: 2, cx: tk.x, cy: tk.y }, i))) }), crosshairCat ? (_jsxs("g", { className: "kpt-chart__cat-active", children: [_jsx("rect", { x: pillX - pillWidth / 2, y: crosshairCat.y - 13, width: pillWidth, height: 18, rx: 9 }), _jsx("text", { x: pillX, y: crosshairCat.y, textAnchor: "middle", children: crosshairCat.text })] })) : null, ghostState
282
303
  ? series.map((s) => {
283
304
  const prev = ghostState.prev[s.i] ?? [];
284
305
  const next = ghostState.next[s.i] ?? [];
@@ -332,9 +353,10 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
332
353
  text: formatNumber(t.value),
333
354
  anchor: 'middle',
334
355
  }));
356
+ const centers = view.labels.map((_, i) => cat.center(i));
335
357
  const catLabels = vertical
336
- ? view.labels.map((l, i) => ({ x: cat.center(i), y: plot.y + plot.height + 18, text: l, anchor: 'middle' }))
337
- : view.labels.map((l, i) => ({ x: plot.x - 8, y: cat.center(i) + 3, text: l, anchor: 'end' }));
358
+ ? bottomLabels(view.labels, centers, plot.y + plot.height + 18, width)
359
+ : sideLabels(view.labels, centers, plot.x - 8);
338
360
  const bars = layout.bars.map((b, order) => ({
339
361
  b,
340
362
  order,
@@ -371,7 +393,7 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
371
393
  const p = svgPoint(event);
372
394
  if (!p)
373
395
  return;
374
- const idx = nearestIndex(catLabels.map((l) => (vertical ? l.x : l.y - 3)), vertical ? p.x : p.y);
396
+ const idx = nearestIndex(catLabels.map((l) => l.at), vertical ? p.x : p.y);
375
397
  if (keepTip(idx))
376
398
  return;
377
399
  const group = bars.filter((e) => e.b.categoryIndex === idx);
@@ -379,11 +401,11 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
379
401
  if (!group.length || !center)
380
402
  return;
381
403
  const label = config.labels[idx] ?? '';
382
- const x = vertical ? center.x : Math.max(...group.map((e) => e.tipX));
383
- const y = vertical ? Math.min(...group.map((e) => e.tipY)) : center.y - 3;
404
+ const x = vertical ? center.at : Math.max(...group.map((e) => e.tipX));
405
+ const y = vertical ? Math.min(...group.map((e) => e.tipY)) : center.at;
384
406
  openTip(x, y, label, group.map((e) => row(e.name, label, e.b.value, e.color)), idx);
385
407
  };
386
- return (_jsxs(_Fragment, { children: [_jsx("g", { className: "kpt-chart__grid", children: grid.map((g, i) => (_jsx("line", { className: "kpt-chart__gridline", x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 }, i))) }), _jsx("g", { className: "kpt-chart__axis", children: [...valueLabels, ...catLabels].map((l, i) => (_jsx("text", { x: l.x, y: l.y, textAnchor: l.anchor, children: l.text }, i))) }), ghostBars.length ? (_jsx("g", { className: "kpt-chart__ghost", "aria-hidden": "true", children: ghostBars.map((g) => (_jsx("path", { className: "kpt-chart__ghost-bar", d: g.path }, g.key))) })) : null, _jsx("g", { className: "kpt-chart__bars", "data-orientation": orientation, children: bars.map((e) => (_jsx("path", { className: cn('kpt-chart__bar', dim(e.key) && 'kpt-chart__series--dim'), d: e.b.path, style: { fill: e.color, ['--kpt-chart-i']: e.order } }, `${e.key}#${e.order}`))) }), ghostBars.length ? (_jsx("g", { className: "kpt-chart__ghost", "aria-hidden": "true", children: ghostBars.map((g) => (_jsx("rect", { className: "kpt-chart__delta-bar", "data-kind": g.kind, rx: 2, ...g.delta }, g.key))) })) : null, sheen
408
+ return (_jsxs(_Fragment, { children: [_jsx("g", { className: "kpt-chart__grid", children: grid.map((g, i) => (_jsx("line", { className: "kpt-chart__gridline", x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 }, i))) }), _jsx("g", { className: "kpt-chart__axis", children: [...valueLabels, ...catLabels.filter((l) => l.visible)].map((l, i) => (_jsx("text", { x: l.x, y: l.y, textAnchor: l.anchor, children: l.text }, i))) }), ghostBars.length ? (_jsx("g", { className: "kpt-chart__ghost", "aria-hidden": "true", children: ghostBars.map((g) => (_jsx("path", { className: "kpt-chart__ghost-bar", d: g.path }, g.key))) })) : null, _jsx("g", { className: "kpt-chart__bars", "data-orientation": orientation, children: bars.map((e) => (_jsx("path", { className: cn('kpt-chart__bar', dim(e.key) && 'kpt-chart__series--dim'), d: e.b.path, style: { fill: e.color, ['--kpt-chart-i']: e.order } }, `${e.key}#${e.order}`))) }), ghostBars.length ? (_jsx("g", { className: "kpt-chart__ghost", "aria-hidden": "true", children: ghostBars.map((g) => (_jsx("rect", { className: "kpt-chart__delta-bar", "data-kind": g.kind, rx: 2, ...g.delta }, g.key))) })) : null, sheen
387
409
  ? bars.map((e) => (_jsx("path", { className: "kpt-chart__sheen-bar", d: e.b.path, fill: `url(#${sheenId})`, style: { opacity: sheen.opacity * 0.7 }, "aria-hidden": "true" }, `${e.key}#${e.order}`)))
388
410
  : null, _jsx("rect", { className: "kpt-chart__hover", x: plot.x, y: plot.y, width: plot.width, height: plot.height, onPointerMove: onMove, onPointerLeave: hideTip })] }));
389
411
  })();
@@ -32,6 +32,16 @@ takes, because both read it from `@konce-pt/chart`.
32
32
  `area`). The axis covers the totals; the tooltip shows the values of the series, not the totals
33
33
  - The rules live in `@konce-pt/chart`: `axisScale`, `chartScale`, `stackSeries`, `bandPath` (tested)
34
34
 
35
+ ## Category axis (line, bar)
36
+ - When the category labels do not fit, the axis shows an even subset instead of overlapping text: all of
37
+ them if they fit, otherwise the densest split that fits - 9 labels (eighths of the axis), 5 (first, 1/4,
38
+ middle, 3/4, last), 3, and finally the two ends only. Horizontal bars apply the same rule to the rows
39
+ - The choice is made in viewBox units, so it follows `width`, not the on-screen size; the outer labels and the
40
+ crosshair pill are shifted inward so they never leave the chart
41
+ - A line chart marks every category with a small dot on the axis; the active one grows on hover. The full
42
+ name of any category stays in the crosshair pill and the tooltip
43
+ - The rules live in `@konce-pt/chart`: `axisLabelIndices`, `estimateLabelWidth`, `clampLabel` (tested)
44
+
35
45
  ## The geometry lives in a package
36
46
  Scales, paths, bar layout and arcs come from `@konce-pt/chart` - pure functions with `node --test`
37
47
  coverage, shared with the Angular port. The component only turns them into SVG elements; there is
@@ -1,4 +1,4 @@
1
1
  export { KptChart } from '../charts/chart.tsx';
2
2
  export type { KptChartProps } from '../charts/chart.tsx';
3
- export type { KptChartConfig, KptChartSeries, KptChartType, KptLineInterpolation, KptBarOrientation, KptLegendOptions, KptLegendPosition, KptLegendTitle, KptAlign, KptPointStyle, KptTitleOptions, KptTooltipOptions, KptTooltipContext, KptAnimationOptions, KptFont, KptPadding, } from '@konce-pt/chart';
3
+ export type { KptChartConfig, KptChartSeries, KptChartType, KptLineInterpolation, KptBarOrientation, KptLegendOptions, KptLegendPosition, KptLegendTitle, KptAlign, KptPointStyle, KptTitleOptions, KptTooltipOptions, KptTooltipContext, KptAnimationOptions, KptValueAxisOptions, KptFont, KptPadding, } from '@konce-pt/chart';
4
4
  export { KPT_CHART_PALETTE, seriesColor, linePath, areaPath, pieSlices, polarSlices, barLayout, radarGrid, radarPoints, niceScale, } from '@konce-pt/chart';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konce-pt/react",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "Open-source React 19 component library on the Koncept UI design system - the same kpt-* CSS layer and --kpt-* tokens as the Angular port. Zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "konce.pt",
@@ -65,19 +65,19 @@
65
65
  "leaflet": "^1.9.0"
66
66
  },
67
67
  "dependencies": {
68
- "@konce-pt/backdrop": "0.18.0",
69
- "@konce-pt/mention": "0.18.0",
70
- "@konce-pt/chat": "0.18.0",
71
- "@konce-pt/chart": "0.18.0",
72
- "@konce-pt/speech": "0.18.0",
73
- "@konce-pt/i18n": "0.18.0",
74
- "@konce-pt/icons": "0.18.0",
75
- "@konce-pt/motion": "0.18.0",
76
- "@konce-pt/roadmap": "0.18.0",
77
- "@konce-pt/datetime": "0.18.0",
78
- "@konce-pt/table": "0.18.0",
79
- "@konce-pt/tokens": "0.18.0",
80
- "@konce-pt/map": "0.18.0"
68
+ "@konce-pt/backdrop": "0.18.1",
69
+ "@konce-pt/icons": "0.18.1",
70
+ "@konce-pt/chart": "0.18.1",
71
+ "@konce-pt/roadmap": "0.18.1",
72
+ "@konce-pt/mention": "0.18.1",
73
+ "@konce-pt/speech": "0.18.1",
74
+ "@konce-pt/motion": "0.18.1",
75
+ "@konce-pt/i18n": "0.18.1",
76
+ "@konce-pt/map": "0.18.1",
77
+ "@konce-pt/chat": "0.18.1",
78
+ "@konce-pt/tokens": "0.18.1",
79
+ "@konce-pt/datetime": "0.18.1",
80
+ "@konce-pt/table": "0.18.1"
81
81
  },
82
82
  "devDependencies": {
83
83
  "@types/react": "^19.2.0",