@siyahi/ui 0.1.9-next.26 → 0.1.9-next.27

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,45 @@
1
+ import { AreaChartCard, ToggleGroup, ToggleGroupItem, useFormat, type ChartSeries } from "@siyahi/ui";
2
+
3
+ // Money a new fund manages, month by month from launch, made up for the catalogue: ₹2L to ₹3.1Cr.
4
+ const data = [
5
+ { month: "Nov", managed: 200000 },
6
+ { month: "Dec", managed: 450000 },
7
+ { month: "Jan", managed: 900000 },
8
+ { month: "Feb", managed: 1600000 },
9
+ { month: "Mar", managed: 2400000 },
10
+ { month: "Apr", managed: 3900000 },
11
+ { month: "May", managed: 5800000 },
12
+ { month: "Jun", managed: 8200000 },
13
+ { month: "Jul", managed: 12500000 },
14
+ { month: "Aug", managed: 17000000 },
15
+ { month: "Sep", managed: 23500000 },
16
+ { month: "Oct", managed: 31000000 },
17
+ ];
18
+
19
+ const series: ChartSeries<(typeof data)[number]> = [
20
+ { key: "managed", label: "Money managed", color: "var(--chart-1)" },
21
+ ];
22
+
23
+ export default function ChartAreaLogExample() {
24
+ const { money, moneyCompact } = useFormat();
25
+ return (
26
+ <AreaChartCard
27
+ title="₹3.1Cr managed a year after launch"
28
+ description="Linear shows the size today; Log shows the early months, when it grew fastest."
29
+ action={
30
+ <ToggleGroup defaultValue={["1Y"]} variant="outline" size="sm" aria-label="Range">
31
+ <ToggleGroupItem value="6M">6M</ToggleGroupItem>
32
+ <ToggleGroupItem value="1Y">1Y</ToggleGroupItem>
33
+ </ToggleGroup>
34
+ }
35
+ data={data}
36
+ xKey="month"
37
+ series={series}
38
+ // Linear is selected first; the buttons after the range toggle let the reader switch to log.
39
+ yScaleToggle
40
+ valueFormatter={money}
41
+ yTickFormatter={moneyCompact}
42
+ height={256}
43
+ />
44
+ );
45
+ }
@@ -0,0 +1,39 @@
1
+ import { Badge, LineChartCard, useFormat, type ChartSeries } from "@siyahi/ui";
2
+
3
+ // Monthly active users in the app's first year, made up for the catalogue: from a few hundred to tens of thousands.
4
+ const data = [
5
+ { month: "Nov", users: 240 },
6
+ { month: "Dec", users: 410 },
7
+ { month: "Jan", users: 780 },
8
+ { month: "Feb", users: 1350 },
9
+ { month: "Mar", users: 2100 },
10
+ { month: "Apr", users: 3900 },
11
+ { month: "May", users: 6200 },
12
+ { month: "Jun", users: 9800 },
13
+ { month: "Jul", users: 14100 },
14
+ { month: "Aug", users: 21500 },
15
+ { month: "Sep", users: 30200 },
16
+ { month: "Oct", users: 46800 },
17
+ ];
18
+
19
+ const series: ChartSeries<(typeof data)[number]> = [{ key: "users", label: "Active users", color: "var(--chart-1)" }];
20
+
21
+ export default function ChartLineLogExample() {
22
+ const { count } = useFormat();
23
+ return (
24
+ <LineChartCard
25
+ title="Active users grew about 1.5× a month all year"
26
+ description="On log, steady growth is a straight line; Linear shows how much of it came in the last few months."
27
+ action={<Badge variant="outline">First year</Badge>}
28
+ data={data}
29
+ xKey="month"
30
+ series={series}
31
+ // Log is selected first; the buttons in the header let the reader switch.
32
+ yScale="log"
33
+ yScaleToggle
34
+ yLabel="Active users"
35
+ valueFormatter={count}
36
+ height={256}
37
+ />
38
+ );
39
+ }
@@ -52,10 +52,10 @@ export default function ScatterChartQuadrantsExample() {
52
52
  x: 0,
53
53
  y: 0,
54
54
  labels: {
55
- topLeft: "Cheaper, selling more",
56
- topRight: "Dearer, still selling more",
57
- bottomLeft: "Cheaper, selling less",
58
- bottomRight: "Dearer, selling less",
55
+ topLeft: "Cheaper, sells more",
56
+ topRight: "Dearer, still sells more",
57
+ bottomLeft: "Cheaper, sells less",
58
+ bottomRight: "Dearer, sells less",
59
59
  },
60
60
  }}
61
61
  />
@@ -0,0 +1,46 @@
1
+ import { ScatterChartCard, useFormat, type ScatterPoint } from "@siyahi/ui";
2
+
3
+ // A shop's products this month: x is units sold, y the shelf price; made-up figures. Both run from a few to
4
+ // thousands, so both axes open on log. Largest sellers first, so they take the labels.
5
+ const products: ScatterPoint[] = [
6
+ { name: "Milk 500 ml", x: 38000, y: 32 },
7
+ { name: "Bread", x: 21000, y: 45 },
8
+ { name: "Eggs, 6", x: 14500, y: 60 },
9
+ { name: "Tea 250 g", x: 6200, y: 140 },
10
+ { name: "Atta 5 kg", x: 4800, y: 260 },
11
+ { name: "Rice 5 kg", x: 3900, y: 420 },
12
+ { name: "Oil 1 L", x: 3100, y: 180 },
13
+ { name: "Ghee 500 ml", x: 1400, y: 340 },
14
+ { name: "Coffee 200 g", x: 900, y: 480 },
15
+ { name: "Dry fruit box", x: 260, y: 1200 },
16
+ { name: "Saffron 1 g", x: 120, y: 350 },
17
+ { name: "Olive oil 1 L", x: 95, y: 980 },
18
+ { name: "Gift hamper", x: 40, y: 3500 },
19
+ { name: "Mixer grinder", x: 18, y: 4200 },
20
+ { name: "Pressure cooker", x: 25, y: 2100 },
21
+ ];
22
+
23
+ export default function ScatterChartScalesExample() {
24
+ const { count, money } = useFormat();
25
+ return (
26
+ <ScatterChartCard
27
+ title="A few cheap products sell most of the units"
28
+ description="Units sold this month against shelf price. Both axes switch: each pair of buttons is named for its axis."
29
+ points={products}
30
+ height={360}
31
+ xLabel="Units sold"
32
+ yLabel="Price"
33
+ xScale="log"
34
+ xScaleToggle
35
+ yScale="log"
36
+ yScaleToggle
37
+ xFormatter={count}
38
+ yFormatter={(value) => money(value)}
39
+ quadrants={{
40
+ x: 1000,
41
+ y: 500,
42
+ labels: { topLeft: "Dear, few sold", bottomRight: "Cheap, many sold" },
43
+ }}
44
+ />
45
+ );
46
+ }
@@ -1,4 +1,4 @@
1
- import { ScatterChartCard, useFormat, type ScatterPoint } from "@siyahi/ui";
1
+ import { Badge, ScatterChartCard, useFormat, type ScatterPoint } from "@siyahi/ui";
2
2
  import { useState } from "react";
3
3
 
4
4
  interface Store extends ScatterPoint {
@@ -40,12 +40,15 @@ export default function ScatterChartExample() {
40
40
  return (
41
41
  <ScatterChartCard
42
42
  title="Stores by size and growth"
43
- description="Across is the store's share of sales (log scale), up its growth on last year. Hover or tap a dot."
43
+ description="Across is the store's share of sales, up its growth on last year. Log spreads out the small stores; Linear shows how far ahead the largest are."
44
+ action={<Badge variant="outline">This month</Badge>}
44
45
  points={stores}
45
46
  height={360}
46
47
  xLabel="Share of sales"
47
48
  yLabel="Growth"
49
+ // Log is selected first; the buttons in the header let the reader switch.
48
50
  xScale="log"
51
+ xScaleToggle
49
52
  xFormatter={(x) => pct(x)}
50
53
  yFormatter={(y) => growth(y)}
51
54
  details={(s) => [
@@ -522,6 +522,15 @@
522
522
  "ChangeGrid"
523
523
  ]
524
524
  },
525
+ {
526
+ "file": "agents/examples/chart-area-log.tsx",
527
+ "components": [
528
+ "AreaChartCard",
529
+ "ToggleGroup",
530
+ "ToggleGroupItem",
531
+ "useFormat"
532
+ ]
533
+ },
525
534
  {
526
535
  "file": "agents/examples/chart-area.tsx",
527
536
  "components": [
@@ -590,6 +599,14 @@
590
599
  "LineChartCard"
591
600
  ]
592
601
  },
602
+ {
603
+ "file": "agents/examples/chart-line-log.tsx",
604
+ "components": [
605
+ "Badge",
606
+ "LineChartCard",
607
+ "useFormat"
608
+ ]
609
+ },
593
610
  {
594
611
  "file": "agents/examples/chart-line-timeline.tsx",
595
612
  "components": [
@@ -1022,9 +1039,17 @@
1022
1039
  "useFormat"
1023
1040
  ]
1024
1041
  },
1042
+ {
1043
+ "file": "agents/examples/scatter-chart-scales.tsx",
1044
+ "components": [
1045
+ "ScatterChartCard",
1046
+ "useFormat"
1047
+ ]
1048
+ },
1025
1049
  {
1026
1050
  "file": "agents/examples/scatter-chart.tsx",
1027
1051
  "components": [
1052
+ "Badge",
1028
1053
  "ScatterChartCard",
1029
1054
  "useFormat"
1030
1055
  ]
@@ -1,6 +1,6 @@
1
- import { ChartCurve, NumericXProps, SeriesChartProps } from "../../../lib/chart-series.js";
1
+ import { ChartCurve, NumericXProps, SeriesChartProps, ValueScaleProps } from "../../../lib/chart-series.js";
2
2
  //#region src/components/registry/charts/area-chart-card.d.ts
3
- export interface AreaChartCardProps<T extends object, K extends keyof T> extends SeriesChartProps<T, K>, NumericXProps {
3
+ export interface AreaChartCardProps<T extends object, K extends keyof T> extends SeriesChartProps<T, K>, NumericXProps, ValueScaleProps {
4
4
  /** Stack the series into a total instead of overlapping them. */
5
5
  stacked?: boolean;
6
6
  curve?: ChartCurve;
@@ -9,6 +9,7 @@ function AreaChartCard({ stacked = false, curve = "monotone", ...props }) {
9
9
  const id = useId();
10
10
  return /* @__PURE__ */ jsxs(ChartFrame, {
11
11
  chart: AreaChart,
12
+ logBlocked: stacked ? "Log cannot show stacked areas" : void 0,
12
13
  ...props,
13
14
  children: [/* @__PURE__ */ jsx("defs", { children: props.series.map(({ key, color }, index) => /* @__PURE__ */ jsxs("linearGradient", {
14
15
  id: `${id}-${index}`,
@@ -1,6 +1,6 @@
1
- import { ChartCurve, NumericXProps, SeriesChartProps } from "../../../lib/chart-series.js";
1
+ import { ChartCurve, NumericXProps, SeriesChartProps, ValueScaleProps } from "../../../lib/chart-series.js";
2
2
  //#region src/components/registry/charts/line-chart-card.d.ts
3
- export interface LineChartCardProps<T extends object, K extends keyof T> extends SeriesChartProps<T, K>, NumericXProps {
3
+ export interface LineChartCardProps<T extends object, K extends keyof T> extends SeriesChartProps<T, K>, NumericXProps, ValueScaleProps {
4
4
  curve?: ChartCurve;
5
5
  }
6
6
  /** Values over an ordered axis as lines: one series, or several to compare, such as sales against a target. */
@@ -1,6 +1,6 @@
1
+ import { linearTicks } from "../../lib/ticks.js";
1
2
  import { ChartFrame } from "../../lib/chart-frame.js";
2
3
  import { useFormat } from "../../lib/format.js";
3
- import { linearTicks } from "../../lib/scatter.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { useMemo } from "react";
6
6
  import { Area, AreaChart } from "recharts";
@@ -1,6 +1,6 @@
1
1
  import { useLocale } from "./siyahi-provider.js";
2
+ import { linearTicks } from "../../lib/ticks.js";
2
3
  import { ChartFrame } from "../../lib/chart-frame.js";
3
- import { linearTicks } from "../../lib/scatter.js";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { useMemo } from "react";
6
6
  import { Bar, BarChart } from "recharts";
@@ -0,0 +1,32 @@
1
+ import { AxisScale } from "../../lib/ticks.js";
2
+ import { ReactNode } from "react";
3
+ //#region src/components/siyahi/scale-toggle.d.ts
4
+ /** Why Log is disabled when a value is at or below zero. */
5
+ export declare const LOG_BLOCKED = "Log needs every value above zero";
6
+ export interface ScaleToggleProps {
7
+ /** The group's accessible name, such as "Share of book scale". */
8
+ label: string;
9
+ /** The axis' name shown before the buttons, for a card with a toggle on each axis. */
10
+ caption?: string;
11
+ value: AxisScale;
12
+ onValueChange: (scale: AxisScale) => void;
13
+ /** Why log cannot draw this data, such as a value at or below zero: Log is disabled and this shows beside it. */
14
+ logBlocked?: string;
15
+ }
16
+ /** Linear and Log buttons for one chart axis, in the header's toggle style. */
17
+ export declare function ScaleToggle({ label, caption, value, onValueChange, logBlocked }: ScaleToggleProps): import("react").JSX.Element;
18
+ export interface AxisScaleOptions extends Omit<ScaleToggleProps, "value" | "onValueChange"> {
19
+ /** The page's scale: the first selection. */
20
+ initial: AxisScale;
21
+ /** Show the buttons. */
22
+ toggle: boolean;
23
+ }
24
+ /**
25
+ * The scale an axis draws on, and its buttons when `toggle` is set. The page's scale is the first selection; after
26
+ * that the reader's pick holds. Log falls back to linear while `logBlocked` says why it cannot draw the data.
27
+ */
28
+ export declare function useAxisScale({ initial, toggle, ...buttons }: AxisScaleOptions): {
29
+ scale: AxisScale;
30
+ buttons: ReactNode;
31
+ };
32
+ //#endregion
@@ -0,0 +1,61 @@
1
+ import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useId, useState } from "react";
4
+ //#region src/components/siyahi/scale-toggle.tsx
5
+ /** Why Log is disabled when a value is at or below zero. */
6
+ const LOG_BLOCKED = "Log needs every value above zero";
7
+ /** Linear and Log buttons for one chart axis, in the header's toggle style. */
8
+ function ScaleToggle({ label, caption, value, onValueChange, logBlocked }) {
9
+ const reason = useId();
10
+ return /* @__PURE__ */ jsxs("div", {
11
+ "data-slot": "scale-toggle",
12
+ className: "flex flex-wrap items-center gap-x-2 gap-y-1",
13
+ children: [
14
+ caption && /* @__PURE__ */ jsx("span", {
15
+ className: "text-xs text-muted-foreground",
16
+ children: caption
17
+ }),
18
+ /* @__PURE__ */ jsxs(ToggleGroup, {
19
+ value: [value],
20
+ onValueChange: ([next]) => next && onValueChange(next === "log" ? "log" : "linear"),
21
+ variant: "outline",
22
+ size: "sm",
23
+ spacing: 0,
24
+ "aria-label": label,
25
+ "aria-describedby": logBlocked ? reason : void 0,
26
+ children: [/* @__PURE__ */ jsx(ToggleGroupItem, {
27
+ value: "linear",
28
+ children: "Linear"
29
+ }), /* @__PURE__ */ jsx(ToggleGroupItem, {
30
+ value: "log",
31
+ disabled: logBlocked !== void 0,
32
+ children: "Log"
33
+ })]
34
+ }),
35
+ logBlocked && /* @__PURE__ */ jsx("span", {
36
+ id: reason,
37
+ className: "text-xs text-muted-foreground",
38
+ children: logBlocked
39
+ })
40
+ ]
41
+ });
42
+ }
43
+ /**
44
+ * The scale an axis draws on, and its buttons when `toggle` is set. The page's scale is the first selection; after
45
+ * that the reader's pick holds. Log falls back to linear while `logBlocked` says why it cannot draw the data.
46
+ */
47
+ function useAxisScale({ initial, toggle, ...buttons }) {
48
+ const [picked, setPicked] = useState(initial);
49
+ const wanted = toggle ? picked : initial;
50
+ const scale = buttons.logBlocked ? "linear" : wanted;
51
+ return {
52
+ scale,
53
+ buttons: toggle ? /* @__PURE__ */ jsx(ScaleToggle, {
54
+ ...buttons,
55
+ value: scale,
56
+ onValueChange: setPicked
57
+ }) : null
58
+ };
59
+ }
60
+ //#endregion
61
+ export { LOG_BLOCKED, ScaleToggle, useAxisScale };
@@ -1,9 +1,10 @@
1
+ import { AxisScale } from "../../lib/ticks.js";
1
2
  import { ScatterQuadrants } from "../../lib/scatter.js";
2
3
  import { ReactNode } from "react";
3
4
  //#region src/components/siyahi/scatter-chart.d.ts
4
5
  export interface ScatterPoint {
5
6
  name: string;
6
- /** Across. On a log axis a point at or under zero is not drawn. */
7
+ /** Across. */
7
8
  x: number;
8
9
  /** Up. `null` when unknown: the point is not drawn, so say on the page how many are missing. */
9
10
  y: number | null;
@@ -20,7 +21,7 @@ export type ScatterDetail = readonly [label: string, value: string];
20
21
  export interface ScatterChartCardProps<T extends ScatterPoint> {
21
22
  title: ReactNode;
22
23
  description?: ReactNode;
23
- /** A scope badge or a toggle, top right of the card. */
24
+ /** A scope badge or a toggle, top right of the card, before any scale buttons. */
24
25
  action?: ReactNode;
25
26
  /** Most important first: earlier points take the labels. */
26
27
  points: T[];
@@ -30,8 +31,17 @@ export interface ScatterChartCardProps<T extends ScatterPoint> {
30
31
  xLabel: string;
31
32
  /** What the y axis measures, beside it, such as "Growth on last year". */
32
33
  yLabel: string;
33
- /** "log" spreads values across powers of ten, for sizes that run from tiny to large. */
34
- xScale?: "linear" | "log";
34
+ /**
35
+ * "log" spreads values across powers of ten, for sizes that run from tiny to large. With xScaleToggle it is the
36
+ * first selection. Log draws only when every x is above zero; otherwise the axis is linear.
37
+ */
38
+ xScale?: AxisScale;
39
+ /** Linear and Log buttons for the x axis in the header, so a reader can switch. */
40
+ xScaleToggle?: boolean;
41
+ /** As xScale, for the y axis: log draws only when every y is above zero. */
42
+ yScale?: AxisScale;
43
+ /** Linear and Log buttons for the y axis in the header. */
44
+ yScaleToggle?: boolean;
35
45
  /** The x ticks and the tooltip's x figure. Defaults to the locale's numbers. */
36
46
  xFormatter?: (value: number) => string;
37
47
  /** The y ticks and the tooltip's y figure. Defaults to the locale's numbers. */
@@ -54,6 +64,6 @@ export interface ScatterChartCardProps<T extends ScatterPoint> {
54
64
  sizeFormatter?: (value: number) => string;
55
65
  }
56
66
  /** Two measures per item as dots, such as size against growth, with the leading names beside their dots. */
57
- export declare function ScatterChartCard<T extends ScatterPoint>({ title, description, action, points, height, xLabel, yLabel, xScale, xFormatter, yFormatter, labels, details, selected, onSelect, footer, quadrants, sizeLabel, sizeFormatter }: ScatterChartCardProps<T>): import("react").JSX.Element;
67
+ export declare function ScatterChartCard<T extends ScatterPoint>({ title, description, action, points, height, xLabel, yLabel, xScale, xScaleToggle, yScale, yScaleToggle, xFormatter, yFormatter, labels, details, selected, onSelect, footer, quadrants, sizeLabel, sizeFormatter }: ScatterChartCardProps<T>): import("react").JSX.Element;
58
68
  //#endregion
59
69
  export type { ScatterQuadrants };
@@ -1,7 +1,9 @@
1
1
  import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../ui/card.js";
2
2
  import { ChartContainer } from "../ui/chart.js";
3
+ import { LOG_BLOCKED, useAxisScale } from "./scale-toggle.js";
3
4
  import { useLocale } from "./siyahi-provider.js";
4
- import { corners, dotSizes, linearTicks, logTicks, placeLabels, quadrantSplit } from "../../lib/scatter.js";
5
+ import { fitsLog, ticksFor } from "../../lib/ticks.js";
6
+ import { corners, dotSizes, placeLabels, quadrantSplit } from "../../lib/scatter.js";
5
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
8
  import { useEffect, useMemo, useRef, useState } from "react";
7
9
  import { CartesianGrid, DefaultZIndexes, ReferenceLine, Scatter, ScatterChart, Tooltip, XAxis, YAxis, ZIndexLayer, usePlotArea, useXAxisScale, useYAxisScale } from "recharts";
@@ -14,8 +16,9 @@ const NAME_BAND = 42;
14
16
  const TICK = { className: "fill-muted-foreground font-mono" };
15
17
  const AXIS_TITLE = "fill-muted-foreground text-xs";
16
18
  const keyOf = (point) => point.key ?? point.name;
19
+ const MONO_WIDTH = 7.2;
17
20
  /** Two measures per item as dots, such as size against growth, with the leading names beside their dots. */
18
- function ScatterChartCard({ title, description, action, points, height, xLabel, yLabel, xScale = "linear", xFormatter, yFormatter, labels = 12, details, selected = null, onSelect, footer, quadrants, sizeLabel = "Size", sizeFormatter }) {
21
+ function ScatterChartCard({ title, description, action, points, height, xLabel, yLabel, xScale = "linear", xScaleToggle = false, yScale = "linear", yScaleToggle = false, xFormatter, yFormatter, labels = 12, details, selected = null, onSelect, footer, quadrants, sizeLabel = "Size", sizeFormatter }) {
19
22
  const { numberLocale } = useLocale();
20
23
  const plotRef = useRef(null);
21
24
  const [focusedKey, setFocusedKey] = useState(null);
@@ -39,18 +42,35 @@ function ScatterChartCard({ title, description, action, points, height, xLabel,
39
42
  const formatX = useMemo(() => xFormatter ?? new Intl.NumberFormat(numberLocale).format, [xFormatter, numberLocale]);
40
43
  const formatY = useMemo(() => yFormatter ?? new Intl.NumberFormat(numberLocale).format, [yFormatter, numberLocale]);
41
44
  const formatSize = useMemo(() => sizeFormatter ?? new Intl.NumberFormat(numberLocale).format, [sizeFormatter, numberLocale]);
42
- const drawn = useMemo(() => points.filter((p) => p.y !== null && Number.isFinite(p.y) && Number.isFinite(p.x) && (xScale === "linear" || p.x > 0)), [points, xScale]);
45
+ const drawn = useMemo(() => points.filter((p) => p.y !== null && Number.isFinite(p.y) && Number.isFinite(p.x)), [points]);
46
+ const both = xScaleToggle && yScaleToggle;
47
+ const across = useAxisScale({
48
+ initial: xScale,
49
+ toggle: xScaleToggle,
50
+ label: `${xLabel} scale`,
51
+ caption: both ? xLabel : void 0,
52
+ logBlocked: fitsLog(drawn.map((p) => p.x)) ? void 0 : LOG_BLOCKED
53
+ });
54
+ const up = useAxisScale({
55
+ initial: yScale,
56
+ toggle: yScaleToggle,
57
+ label: `${yLabel} scale`,
58
+ caption: both ? yLabel : void 0,
59
+ logBlocked: fitsLog(drawn.map((p) => p.y ?? 0)) ? void 0 : LOG_BLOCKED
60
+ });
43
61
  const order = useMemo(() => drawn.toSorted((a, b) => a.x - b.x || (a.y ?? 0) - (b.y ?? 0)), [drawn]);
44
62
  const { radiusOf, sized } = dotSizes(drawn, RADIUS);
45
63
  const painted = sized ? drawn.toSorted((a, b) => radiusOf(b) - radiusOf(a)) : drawn;
46
- const split = quadrantSplit(quadrants, xScale);
64
+ const split = quadrantSplit(quadrants, across.scale, up.scale);
47
65
  const xs = [...drawn.map((p) => p.x), ...split ? [split.x] : []];
48
66
  const ys = [...drawn.map((p) => p.y ?? 0), ...split ? [split.y] : []];
49
67
  const reach = sized ? 23 : 0;
50
68
  const names = split?.labels;
51
69
  const band = (named) => named ? NAME_BAND + Math.max(reach, 6.5) : reach;
52
- const xTicks = xScale === "log" ? logTicks(Math.min(...xs), Math.max(...xs)) : void 0;
53
- const yTicks = linearTicks(Math.min(...ys), Math.max(...ys));
70
+ const xTicks = ticksFor(across.scale, Math.min(...xs), Math.max(...xs));
71
+ const yTicks = ticksFor(up.scale, Math.min(...ys), Math.max(...ys));
72
+ const lastX = xTicks.at(-1);
73
+ const endRoom = Math.max(16, lastX === void 0 ? 0 : Math.ceil(formatX(lastX).length * MONO_WIDTH / 2) + 4 - reach);
54
74
  const crossesZero = ys.some((y) => y < 0) && ys.some((y) => y > 0);
55
75
  const selectedKey = selected ? keyOf(selected) : null;
56
76
  const rows = details ?? ((p) => [
@@ -89,7 +109,14 @@ function ScatterChartCard({ title, description, action, points, height, xLabel,
89
109
  /* @__PURE__ */ jsxs(CardHeader, { children: [
90
110
  /* @__PURE__ */ jsx(CardTitle, { children: title }),
91
111
  description && /* @__PURE__ */ jsx(CardDescription, { children: description }),
92
- action && /* @__PURE__ */ jsx(CardAction, { children: action })
112
+ (action || across.buttons || up.buttons) && /* @__PURE__ */ jsxs(CardAction, {
113
+ className: "flex flex-wrap items-center gap-x-3 gap-y-2",
114
+ children: [
115
+ action,
116
+ across.buttons,
117
+ up.buttons
118
+ ]
119
+ })
93
120
  ] }),
94
121
  /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("div", {
95
122
  ref: plotRef,
@@ -105,7 +132,7 @@ function ScatterChartCard({ title, description, action, points, height, xLabel,
105
132
  children: /* @__PURE__ */ jsxs(ScatterChart, {
106
133
  margin: {
107
134
  top: 12,
108
- right: 16,
135
+ right: endRoom,
109
136
  left: 0
110
137
  },
111
138
  accessibilityLayer: false,
@@ -114,10 +141,10 @@ function ScatterChartCard({ title, description, action, points, height, xLabel,
114
141
  /* @__PURE__ */ jsx(XAxis, {
115
142
  type: "number",
116
143
  dataKey: "x",
117
- scale: xScale,
118
- domain: xTicks ? [xTicks[0] ?? "auto", xTicks.at(-1) ?? "auto"] : [(low) => Math.min(low, ...xs), (high) => Math.max(high, ...xs)],
119
- ticks: xTicks,
120
- niceTicks: xTicks ? void 0 : "snap125",
144
+ scale: across.scale,
145
+ domain: xTicks.length > 0 ? [xTicks[0] ?? "auto", xTicks.at(-1) ?? "auto"] : [(low) => Math.min(low, ...xs), (high) => Math.max(high, ...xs)],
146
+ ticks: xTicks.length > 0 ? xTicks : void 0,
147
+ niceTicks: xTicks.length > 0 ? void 0 : "snap125",
121
148
  padding: reach > 0 ? {
122
149
  left: reach,
123
150
  right: reach
@@ -137,6 +164,7 @@ function ScatterChartCard({ title, description, action, points, height, xLabel,
137
164
  /* @__PURE__ */ jsx(YAxis, {
138
165
  type: "number",
139
166
  dataKey: "y",
167
+ scale: up.scale,
140
168
  domain: [yTicks[0] ?? "auto", yTicks.at(-1) ?? "auto"],
141
169
  ticks: yTicks,
142
170
  padding: {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ChartColor, ChartCurve, ChartReferenceLine, ChartSeries, ChartSeriesItem, NumericKey, NumericXProps, SeriesChartProps } from "./lib/chart-series.js";
1
+ import { ChartColor, ChartCurve, ChartReferenceLine, ChartSeries, ChartSeriesItem, NumericKey, NumericXProps, SeriesChartProps, ValueScaleProps } from "./lib/chart-series.js";
2
2
  import { AreaChartCard, AreaChartCardProps } from "./components/registry/charts/area-chart-card.js";
3
3
  import { BarChartCard, BarChartCardProps } from "./components/registry/charts/bar-chart-card.js";
4
4
  import { LineChartCard, LineChartCardProps } from "./components/registry/charts/line-chart-card.js";
@@ -71,6 +71,7 @@ import { PanelGrid, PanelGridProps } from "./components/siyahi/panel-grid.js";
71
71
  import { PriceChart, PriceChartProps } from "./components/siyahi/price-chart.js";
72
72
  import { Quote, QuoteProps } from "./components/siyahi/quote.js";
73
73
  import { RangePosition, RangePositionProps } from "./components/siyahi/range-position.js";
74
+ import { AxisScaleOptions, LOG_BLOCKED, ScaleToggle, ScaleToggleProps, useAxisScale } from "./components/siyahi/scale-toggle.js";
74
75
  import { ScatterQuadrants } from "./lib/scatter.js";
75
76
  import { ScatterChartCard, ScatterChartCardProps, ScatterDetail, ScatterPoint } from "./components/siyahi/scatter-chart.js";
76
77
  import { SiyahiLocale, defaultLocale } from "./lib/locale.js";
@@ -108,4 +109,4 @@ import { Textarea } from "./components/ui/textarea.js";
108
109
  import { Toggle, toggleVariants } from "./components/ui/toggle.js";
109
110
  import { ToggleGroup, ToggleGroupItem } from "./components/ui/toggle-group.js";
110
111
  import { Formatters, TimeOptions, count, money, moneyCompact, pct, time, useFormat } from "./lib/format.js";
111
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActiveCell, ActiveSelection, Activity, Alert, AlertAction, AlertDescription, AlertTitle, AppSidebar, AppSidebarProps, AreaChartCard, AreaChartCardProps, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge, BarChartCard, BarChartCardProps, BarList, BarListItem, BarListProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Calendar, CalendarDayButton, CalendarHeatmap, CalendarHeatmapBlock, CalendarHeatmapBlockProps, CalendarHeatmapBody, CalendarHeatmapBodyProps, CalendarHeatmapFooter, CalendarHeatmapFooterProps, CalendarHeatmapLegend, CalendarHeatmapLegendProps, CalendarHeatmapProps, CalendarHeatmapStat, CalendarHeatmapStatProps, Callout, CalloutProps, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChangeBar, ChangeBarProps, ChangeGrid, ChangeGridProps, ChangeGridRow, ChartColor, ChartConfig, ChartContainer, ChartCurve, ChartLegend, ChartLegendContent, ChartReferenceLine, ChartSeries, ChartSeriesItem, ChartStyle, ChartTooltip, ChartTooltipContent, CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton, CodeBlockFooter, CodeBlockHeader, CodeBlockLine, CodeBlockTitle, Collapsible, CollapsibleContent, CollapsibleTrigger, ColorConfig, ColumnResizeInfo, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, CompactColumns, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DataBar, DataBarProps, DataList, DataListItem, DataListItemProps, DataTable, DataTableActions, DataTableBody, DataTableBodyProps, type DataTableCell, type DataTableColumn, DataTableColumnActions, type DataTableColumnDef, DataTableColumnFilter, DataTableColumnHeader, DataTableColumnHeaderProps, DataTableColumnHeaderRoot, DataTableColumnSortMenu, DataTableColumnSortOptions, DataTableColumnTitle, type DataTableColumns, DataTableCompactColumnsToggle, DataTableConfig, DataTableContainerProps, DataTableContext, DataTableContextState, DataTableEmptyActions, DataTableEmptyActionsProps, DataTableEmptyBody, DataTableEmptyBodyProps, DataTableEmptyDescription, DataTableEmptyDescriptionProps, DataTableEmptyFilteredMessage, DataTableEmptyFilteredMessageProps, DataTableEmptyIcon, DataTableEmptyIconProps, DataTableEmptyMessage, DataTableEmptyMessageProps, DataTableEmptyState, DataTableEmptyStateProps, DataTableEmptyTitle, DataTableEmptyTitleProps, DataTableErrorBoundary, DataTableErrorBoundaryProps, DataTableFacetedFilter, DataTableFacetedFilterContent, DataTableFootnote, DataTableFootnoteProps, DataTableGroupRowContextValue, DataTableGroupRowProvider, DataTableGroupedRowCells, DataTableGroupedRows, DataTableGroupedRowsProps, DataTableHeader, type DataTableHeader$1 as DataTableHeaderCell, DataTableHeaderProps, type DataTableInstance, DataTableLoading, DataTableLoadingMore, DataTableLoadingMoreProps, DataTableLoadingProps, DataTableProvider, DataTableRoot, type DataTableRow, DataTableRowContextMenu, DataTableRowContextMenuProps, DataTableRowContextMenuSlot, DataTableRowContextMenuSlotProps, DataTableRowCount, DataTableRowCountProps, DataTableRowMenuScope, DataTableSearchFilter, DataTableSkeleton, DataTableSkeletonProps, DataTableSortHeader, DataTableToolbarSection, DataTableToolbarSectionProps, Delta, DeltaProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DistributionBar, DistributionBarProps, DistributionMarker, DistributionSegment, DrawdownChartCard, DrawdownChartCardProps, DrawdownPoint, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type ExtendedColumnFilter, type ExtendedColumnSort, type FilterOperator, type FilterOperatorValues, type FilterVariant, type FilterVariantValues, FlashCellRef, FlashCells, FlashRows, FlashRowsOptions, Formatters, type GlobalFilter, GroupedBodyRow, GroupedBodyRowProps, HistogramBin, HistogramChartCard, HistogramChartCardProps, HistogramMark, InlineCode, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, type JoinOperator, type JoinOperatorValues, Labels, LineChartCard, LineChartCardProps, LocalTime, LocalTimeProps, MetricStrip, MetricStripItem, MetricStripProps, NavMain, NavProjects, NavUser, NumericKey, NumericXProps, type Option, PageHero, PageHeroProps, PageSection, PageSectionProps, PageSubheading, PageToc, PageTocItem, PageTocProps, PanelGrid, PanelGridProps, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, PriceChart, PriceChartProps, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, type QueryKeys, Quote, QuoteProps, RangePosition, RangePositionProps, RowMenuGroup, RowMenuItem, RowMenuLabel, RowMenuSeparator, RowMenuSub, RowMenuSubContent, RowMenuSubTrigger, ScatterChartCard, ScatterChartCardProps, ScatterDetail, ScatterPoint, type ScatterQuadrants, ScrollAlign, ScrollArea, ScrollBar, type ScrollEvent, ScrollRowIntoView, SectionCard, SectionCards, SectionCardsProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeriesChartProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SiyahiLocale, SiyahiProvider, SiyahiProviderProps, Skeleton, Slider, SlopeChart, SlopeChartProps, SlopeItem, Sparkline, SparklineProps, StatusActivity, StatusAlert, StatusAlertProps, StatusBadge, StatusBadgeProps, StatusColorConfig, StatusHeatmap, StatusHeatmapBlock, StatusHeatmapBlockProps, StatusHeatmapBody, StatusHeatmapBodyProps, StatusHeatmapFooter, StatusHeatmapFooterProps, StatusHeatmapLabels, StatusHeatmapLegend, StatusHeatmapLegendProps, StatusHeatmapProps, StatusHeatmapStat, StatusHeatmapStatProps, StatusValue, Surface, Table, TableBody, TableCaption, TableCell, TableColumnActions, TableColumnActionsProps, TableColumnSortMenu, TableColumnSortOptions, TableColumnTitle, TableFacetedFilter, TableFacetedFilterContent, TableFacetedFilterProps, TableFooter, TableHead, TableHeader, TableRow, TableSearchFilter, TableSearchFilterProps, Tabs, TabsContent, TabsList, TabsTrigger, TargetRow, TargetRows, TargetRowsProps, TargetStatus, TeamSwitcher, Textarea, TimeOptions, Toggle, ToggleGroup, ToggleGroupItem, ToneProgress, ToneProgressProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Treemap, TreemapItem, TreemapLegend, TreemapLegendProps, TreemapProps, type ValueOf, WaterfallBar, WaterfallChartCard, WaterfallChartCardProps, WaterfallStep, type ColorConfig$1 as WeekdayColorConfig, type WeekdayExtraColumnSpec, type WeekdayExtraRowSpec, WeekdayHeatmap, WeekdayHeatmapBlock, type WeekdayHeatmapBlockProps, WeekdayHeatmapBody, type WeekdayHeatmapBodyProps, WeekdayHeatmapFooter, type WeekdayHeatmapFooterProps, type WeekdayHeatmapLabels, WeekdayHeatmapLegend, type WeekdayHeatmapLegendProps, type WeekdayHeatmapProps, WeekdayHeatmapStat, type WeekdayHeatmapStatProps, type WeekdayHour, type WeekdayHourlyActivity, type WeekdayIndex, badgeVariants, bucketOf, buttonVariants, count, defaultLocale, defaultSidebarOpen, drawdowns, flashCellKey, histogram, markLines, money, moneyCompact, pct, percentile, resolveGroupedRowsSlot, resolveRowContextMenuRenderer, spread, tabsListVariants, time, toggleVariants, useColumnHeaderContext, useColumnResizeInfo, useDataTable, useDataTableActiveCell, useDataTableActiveCellSetter, useDataTableGroupRow, useDataTableRow, useFormat, useLocale, useResolvedRowContextMenuRenderer, useSidebar, useSurface, useTableFacetedFilter, waterfallBars };
112
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActiveCell, ActiveSelection, Activity, Alert, AlertAction, AlertDescription, AlertTitle, AppSidebar, AppSidebarProps, AreaChartCard, AreaChartCardProps, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, AxisScaleOptions, Badge, BarChartCard, BarChartCardProps, BarList, BarListItem, BarListProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Calendar, CalendarDayButton, CalendarHeatmap, CalendarHeatmapBlock, CalendarHeatmapBlockProps, CalendarHeatmapBody, CalendarHeatmapBodyProps, CalendarHeatmapFooter, CalendarHeatmapFooterProps, CalendarHeatmapLegend, CalendarHeatmapLegendProps, CalendarHeatmapProps, CalendarHeatmapStat, CalendarHeatmapStatProps, Callout, CalloutProps, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChangeBar, ChangeBarProps, ChangeGrid, ChangeGridProps, ChangeGridRow, ChartColor, ChartConfig, ChartContainer, ChartCurve, ChartLegend, ChartLegendContent, ChartReferenceLine, ChartSeries, ChartSeriesItem, ChartStyle, ChartTooltip, ChartTooltipContent, CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton, CodeBlockFooter, CodeBlockHeader, CodeBlockLine, CodeBlockTitle, Collapsible, CollapsibleContent, CollapsibleTrigger, ColorConfig, ColumnResizeInfo, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, CompactColumns, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DataBar, DataBarProps, DataList, DataListItem, DataListItemProps, DataTable, DataTableActions, DataTableBody, DataTableBodyProps, type DataTableCell, type DataTableColumn, DataTableColumnActions, type DataTableColumnDef, DataTableColumnFilter, DataTableColumnHeader, DataTableColumnHeaderProps, DataTableColumnHeaderRoot, DataTableColumnSortMenu, DataTableColumnSortOptions, DataTableColumnTitle, type DataTableColumns, DataTableCompactColumnsToggle, DataTableConfig, DataTableContainerProps, DataTableContext, DataTableContextState, DataTableEmptyActions, DataTableEmptyActionsProps, DataTableEmptyBody, DataTableEmptyBodyProps, DataTableEmptyDescription, DataTableEmptyDescriptionProps, DataTableEmptyFilteredMessage, DataTableEmptyFilteredMessageProps, DataTableEmptyIcon, DataTableEmptyIconProps, DataTableEmptyMessage, DataTableEmptyMessageProps, DataTableEmptyState, DataTableEmptyStateProps, DataTableEmptyTitle, DataTableEmptyTitleProps, DataTableErrorBoundary, DataTableErrorBoundaryProps, DataTableFacetedFilter, DataTableFacetedFilterContent, DataTableFootnote, DataTableFootnoteProps, DataTableGroupRowContextValue, DataTableGroupRowProvider, DataTableGroupedRowCells, DataTableGroupedRows, DataTableGroupedRowsProps, DataTableHeader, type DataTableHeader$1 as DataTableHeaderCell, DataTableHeaderProps, type DataTableInstance, DataTableLoading, DataTableLoadingMore, DataTableLoadingMoreProps, DataTableLoadingProps, DataTableProvider, DataTableRoot, type DataTableRow, DataTableRowContextMenu, DataTableRowContextMenuProps, DataTableRowContextMenuSlot, DataTableRowContextMenuSlotProps, DataTableRowCount, DataTableRowCountProps, DataTableRowMenuScope, DataTableSearchFilter, DataTableSkeleton, DataTableSkeletonProps, DataTableSortHeader, DataTableToolbarSection, DataTableToolbarSectionProps, Delta, DeltaProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DistributionBar, DistributionBarProps, DistributionMarker, DistributionSegment, DrawdownChartCard, DrawdownChartCardProps, DrawdownPoint, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type ExtendedColumnFilter, type ExtendedColumnSort, type FilterOperator, type FilterOperatorValues, type FilterVariant, type FilterVariantValues, FlashCellRef, FlashCells, FlashRows, FlashRowsOptions, Formatters, type GlobalFilter, GroupedBodyRow, GroupedBodyRowProps, HistogramBin, HistogramChartCard, HistogramChartCardProps, HistogramMark, InlineCode, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, type JoinOperator, type JoinOperatorValues, LOG_BLOCKED, Labels, LineChartCard, LineChartCardProps, LocalTime, LocalTimeProps, MetricStrip, MetricStripItem, MetricStripProps, NavMain, NavProjects, NavUser, NumericKey, NumericXProps, type Option, PageHero, PageHeroProps, PageSection, PageSectionProps, PageSubheading, PageToc, PageTocItem, PageTocProps, PanelGrid, PanelGridProps, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, PriceChart, PriceChartProps, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, type QueryKeys, Quote, QuoteProps, RangePosition, RangePositionProps, RowMenuGroup, RowMenuItem, RowMenuLabel, RowMenuSeparator, RowMenuSub, RowMenuSubContent, RowMenuSubTrigger, ScaleToggle, ScaleToggleProps, ScatterChartCard, ScatterChartCardProps, ScatterDetail, ScatterPoint, type ScatterQuadrants, ScrollAlign, ScrollArea, ScrollBar, type ScrollEvent, ScrollRowIntoView, SectionCard, SectionCards, SectionCardsProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeriesChartProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SiyahiLocale, SiyahiProvider, SiyahiProviderProps, Skeleton, Slider, SlopeChart, SlopeChartProps, SlopeItem, Sparkline, SparklineProps, StatusActivity, StatusAlert, StatusAlertProps, StatusBadge, StatusBadgeProps, StatusColorConfig, StatusHeatmap, StatusHeatmapBlock, StatusHeatmapBlockProps, StatusHeatmapBody, StatusHeatmapBodyProps, StatusHeatmapFooter, StatusHeatmapFooterProps, StatusHeatmapLabels, StatusHeatmapLegend, StatusHeatmapLegendProps, StatusHeatmapProps, StatusHeatmapStat, StatusHeatmapStatProps, StatusValue, Surface, Table, TableBody, TableCaption, TableCell, TableColumnActions, TableColumnActionsProps, TableColumnSortMenu, TableColumnSortOptions, TableColumnTitle, TableFacetedFilter, TableFacetedFilterContent, TableFacetedFilterProps, TableFooter, TableHead, TableHeader, TableRow, TableSearchFilter, TableSearchFilterProps, Tabs, TabsContent, TabsList, TabsTrigger, TargetRow, TargetRows, TargetRowsProps, TargetStatus, TeamSwitcher, Textarea, TimeOptions, Toggle, ToggleGroup, ToggleGroupItem, ToneProgress, ToneProgressProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Treemap, TreemapItem, TreemapLegend, TreemapLegendProps, TreemapProps, type ValueOf, ValueScaleProps, WaterfallBar, WaterfallChartCard, WaterfallChartCardProps, WaterfallStep, type ColorConfig$1 as WeekdayColorConfig, type WeekdayExtraColumnSpec, type WeekdayExtraRowSpec, WeekdayHeatmap, WeekdayHeatmapBlock, type WeekdayHeatmapBlockProps, WeekdayHeatmapBody, type WeekdayHeatmapBodyProps, WeekdayHeatmapFooter, type WeekdayHeatmapFooterProps, type WeekdayHeatmapLabels, WeekdayHeatmapLegend, type WeekdayHeatmapLegendProps, type WeekdayHeatmapProps, WeekdayHeatmapStat, type WeekdayHeatmapStatProps, type WeekdayHour, type WeekdayHourlyActivity, type WeekdayIndex, badgeVariants, bucketOf, buttonVariants, count, defaultLocale, defaultSidebarOpen, drawdowns, flashCellKey, histogram, markLines, money, moneyCompact, pct, percentile, resolveGroupedRowsSlot, resolveRowContextMenuRenderer, spread, tabsListVariants, time, toggleVariants, useAxisScale, useColumnHeaderContext, useColumnResizeInfo, useDataTable, useDataTableActiveCell, useDataTableActiveCellSetter, useDataTableGroupRow, useDataTableRow, useFormat, useLocale, useResolvedRowContextMenuRenderer, useSidebar, useSurface, useTableFacetedFilter, waterfallBars };
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, Tabl
30
30
  import { Tabs, TabsContent, TabsList, TabsTrigger, tabsListVariants } from "./components/ui/tabs.js";
31
31
  import { Toggle, toggleVariants } from "./components/ui/toggle.js";
32
32
  import { ToggleGroup, ToggleGroupItem } from "./components/ui/toggle-group.js";
33
+ import { LOG_BLOCKED, ScaleToggle, useAxisScale } from "./components/siyahi/scale-toggle.js";
33
34
  import { defaultLocale } from "./lib/locale.js";
34
35
  import { SiyahiProvider, useLocale, useSurface } from "./components/siyahi/siyahi-provider.js";
35
36
  import { AreaChartCard } from "./components/registry/charts/area-chart-card.js";
@@ -104,4 +105,4 @@ import { TargetRows } from "./components/siyahi/target-rows.js";
104
105
  import { ToneProgress } from "./components/siyahi/tone-progress.js";
105
106
  import { Treemap, TreemapLegend } from "./components/siyahi/treemap.js";
106
107
  import { WaterfallChartCard, waterfallBars } from "./components/siyahi/waterfall-chart.js";
107
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertAction, AlertDescription, AlertTitle, AppSidebar, AreaChartCard, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge, BarChartCard, BarList, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Calendar, CalendarDayButton, CalendarHeatmap, CalendarHeatmapBlock, CalendarHeatmapBody, CalendarHeatmapFooter, CalendarHeatmapLegend, CalendarHeatmapStat, Callout, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChangeBar, ChangeGrid, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton, CodeBlockFooter, CodeBlockHeader, CodeBlockLine, CodeBlockTitle, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DataBar, DataList, DataListItem, DataTable, DataTableActions, DataTableBody, DataTableColumnActions, DataTableColumnFilter, DataTableColumnHeader, DataTableColumnHeaderRoot, DataTableColumnSortMenu, DataTableColumnSortOptions, DataTableColumnTitle, DataTableCompactColumnsToggle, DataTableContext, DataTableEmptyActions, DataTableEmptyBody, DataTableEmptyDescription, DataTableEmptyFilteredMessage, DataTableEmptyIcon, DataTableEmptyMessage, DataTableEmptyState, DataTableEmptyTitle, DataTableErrorBoundary, DataTableFacetedFilter, DataTableFacetedFilterContent, DataTableFootnote, DataTableGroupRowProvider, DataTableGroupedRowCells, DataTableGroupedRows, DataTableHeader, DataTableLoading, DataTableLoadingMore, DataTableProvider, DataTableRoot, DataTableRowContextMenu, DataTableRowContextMenuSlot, DataTableRowCount, DataTableRowMenuScope, DataTableSearchFilter, DataTableSkeleton, DataTableSortHeader, DataTableToolbarSection, Delta, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DistributionBar, DrawdownChartCard, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, GroupedBodyRow, HistogramChartCard, InlineCode, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, LineChartCard, LocalTime, MetricStrip, NavMain, NavProjects, NavUser, PageHero, PageSection, PageSubheading, PageToc, PanelGrid, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, PriceChart, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, Quote, RangePosition, RowMenuGroup, RowMenuItem, RowMenuLabel, RowMenuSeparator, RowMenuSub, RowMenuSubContent, RowMenuSubTrigger, ScatterChartCard, ScrollArea, ScrollBar, SectionCards, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SiyahiProvider, Skeleton, Slider, SlopeChart, Sparkline, StatusAlert, StatusBadge, StatusHeatmap, StatusHeatmapBlock, StatusHeatmapBody, StatusHeatmapFooter, StatusHeatmapLegend, StatusHeatmapStat, Table, TableBody, TableCaption, TableCell, TableColumnActions, TableColumnSortMenu, TableColumnSortOptions, TableColumnTitle, TableFacetedFilter, TableFacetedFilterContent, TableFooter, TableHead, TableHeader, TableRow, TableSearchFilter, Tabs, TabsContent, TabsList, TabsTrigger, TargetRows, TeamSwitcher, Textarea, Toggle, ToggleGroup, ToggleGroupItem, ToneProgress, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Treemap, TreemapLegend, WaterfallChartCard, WeekdayHeatmap, WeekdayHeatmapBlock, WeekdayHeatmapBody, WeekdayHeatmapFooter, WeekdayHeatmapLegend, WeekdayHeatmapStat, badgeVariants, bucketOf, buttonVariants, count, defaultLocale, defaultSidebarOpen, drawdowns, flashCellKey, histogram, markLines, money, moneyCompact, pct, percentile, resolveGroupedRowsSlot, resolveRowContextMenuRenderer, spread, tabsListVariants, time, toggleVariants, useColumnHeaderContext, useColumnResizeInfo, useDataTable, useDataTableActiveCell, useDataTableActiveCellSetter, useDataTableGroupRow, useDataTableRow, useFormat, useLocale, useResolvedRowContextMenuRenderer, useSidebar, useSurface, useTableFacetedFilter, waterfallBars };
108
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertAction, AlertDescription, AlertTitle, AppSidebar, AreaChartCard, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge, BarChartCard, BarList, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Calendar, CalendarDayButton, CalendarHeatmap, CalendarHeatmapBlock, CalendarHeatmapBody, CalendarHeatmapFooter, CalendarHeatmapLegend, CalendarHeatmapStat, Callout, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChangeBar, ChangeGrid, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton, CodeBlockFooter, CodeBlockHeader, CodeBlockLine, CodeBlockTitle, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DataBar, DataList, DataListItem, DataTable, DataTableActions, DataTableBody, DataTableColumnActions, DataTableColumnFilter, DataTableColumnHeader, DataTableColumnHeaderRoot, DataTableColumnSortMenu, DataTableColumnSortOptions, DataTableColumnTitle, DataTableCompactColumnsToggle, DataTableContext, DataTableEmptyActions, DataTableEmptyBody, DataTableEmptyDescription, DataTableEmptyFilteredMessage, DataTableEmptyIcon, DataTableEmptyMessage, DataTableEmptyState, DataTableEmptyTitle, DataTableErrorBoundary, DataTableFacetedFilter, DataTableFacetedFilterContent, DataTableFootnote, DataTableGroupRowProvider, DataTableGroupedRowCells, DataTableGroupedRows, DataTableHeader, DataTableLoading, DataTableLoadingMore, DataTableProvider, DataTableRoot, DataTableRowContextMenu, DataTableRowContextMenuSlot, DataTableRowCount, DataTableRowMenuScope, DataTableSearchFilter, DataTableSkeleton, DataTableSortHeader, DataTableToolbarSection, Delta, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DistributionBar, DrawdownChartCard, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, GroupedBodyRow, HistogramChartCard, InlineCode, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, LOG_BLOCKED, LineChartCard, LocalTime, MetricStrip, NavMain, NavProjects, NavUser, PageHero, PageSection, PageSubheading, PageToc, PanelGrid, Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, PriceChart, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, Quote, RangePosition, RowMenuGroup, RowMenuItem, RowMenuLabel, RowMenuSeparator, RowMenuSub, RowMenuSubContent, RowMenuSubTrigger, ScaleToggle, ScatterChartCard, ScrollArea, ScrollBar, SectionCards, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SiyahiProvider, Skeleton, Slider, SlopeChart, Sparkline, StatusAlert, StatusBadge, StatusHeatmap, StatusHeatmapBlock, StatusHeatmapBody, StatusHeatmapFooter, StatusHeatmapLegend, StatusHeatmapStat, Table, TableBody, TableCaption, TableCell, TableColumnActions, TableColumnSortMenu, TableColumnSortOptions, TableColumnTitle, TableFacetedFilter, TableFacetedFilterContent, TableFooter, TableHead, TableHeader, TableRow, TableSearchFilter, Tabs, TabsContent, TabsList, TabsTrigger, TargetRows, TeamSwitcher, Textarea, Toggle, ToggleGroup, ToggleGroupItem, ToneProgress, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Treemap, TreemapLegend, WaterfallChartCard, WeekdayHeatmap, WeekdayHeatmapBlock, WeekdayHeatmapBody, WeekdayHeatmapFooter, WeekdayHeatmapLegend, WeekdayHeatmapStat, badgeVariants, bucketOf, buttonVariants, count, defaultLocale, defaultSidebarOpen, drawdowns, flashCellKey, histogram, markLines, money, moneyCompact, pct, percentile, resolveGroupedRowsSlot, resolveRowContextMenuRenderer, spread, tabsListVariants, time, toggleVariants, useAxisScale, useColumnHeaderContext, useColumnResizeInfo, useDataTable, useDataTableActiveCell, useDataTableActiveCellSetter, useDataTableGroupRow, useDataTableRow, useFormat, useLocale, useResolvedRowContextMenuRenderer, useSidebar, useSurface, useTableFacetedFilter, waterfallBars };
@@ -1,9 +1,9 @@
1
- import { ChartColor, ChartSeries, ChartSeriesItem, NumericXProps, SeriesChartProps } from "./chart-series.js";
1
+ import { ChartColor, ChartSeries, ChartSeriesItem, NumericXProps, SeriesChartProps, ValueScaleProps } from "./chart-series.js";
2
2
  import { ChartTooltipContent } from "../components/ui/chart.js";
3
3
  import { ComponentProps, ReactElement, ReactNode } from "react";
4
4
  import { AreaChart, BarChart, LineChart, YAxisProps } from "recharts";
5
5
  //#region src/lib/chart-frame.d.ts
6
- interface ChartFrameProps<T extends object, K extends keyof T> extends Omit<SeriesChartProps<T, K>, "yDomain">, NumericXProps {
6
+ interface ChartFrameProps<T extends object, K extends keyof T> extends Omit<SeriesChartProps<T, K>, "yDomain">, NumericXProps, ValueScaleProps {
7
7
  chart: typeof AreaChart | typeof BarChart | typeof LineChart;
8
8
  /** The value axis' range: a page's fixed one, or a card's rule such as bars reaching zero. */
9
9
  yDomain?: YAxisProps["domain"];
@@ -22,6 +22,8 @@ interface ChartFrameProps<T extends object, K extends keyof T> extends Omit<Seri
22
22
  tooltipRow?: ComponentProps<typeof ChartTooltipContent>["formatter"];
23
23
  /** The legend under the plot; by default one chip per series. */
24
24
  legend?: ReactElement;
25
+ /** Why this chart cannot go on a log scale whatever its values, such as stacked areas adding up from zero. */
26
+ logBlocked?: string;
25
27
  }
26
28
  /**
27
29
  * The colour a series' Bar takes, which its legend chip shows: the negative colour when every value drawn is below
@@ -47,6 +49,6 @@ export declare function seriesRow<T>(series: ChartSeries<T>, formatValue: (value
47
49
  * that gave its category axis its own id (dash#88) got a plot split in two
48
50
  * and every tooltip headed with the series name instead of the category.
49
51
  */
50
- export declare function ChartFrame<T extends object, K extends keyof T>({ chart: Chart, children, horizontal, stackOffset, onClick, tooltipRow, legend, title, description, action, data, xKey, series, xFormatter, xTooltipFormatter, valueFormatter, yTickFormatter, yDomain, yTicks, height, xTickDetail, xLabel, yLabel, referenceLines, placeholder, footer, xDomain, xTicks }: ChartFrameProps<T, K>): import("react").JSX.Element;
52
+ export declare function ChartFrame<T extends object, K extends keyof T>({ chart: Chart, children, horizontal, stackOffset, onClick, tooltipRow, legend, title, description, action, data, xKey, series, xFormatter, xTooltipFormatter, valueFormatter, yTickFormatter, yDomain, yTicks, height, xTickDetail, xLabel, yLabel, referenceLines, placeholder, footer, xDomain, xTicks, yScale, yScaleToggle, logBlocked }: ChartFrameProps<T, K>): import("react").JSX.Element;
51
53
  //#endregion
52
54
  export {};
@@ -1,7 +1,9 @@
1
1
  import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../components/ui/card.js";
2
2
  import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from "../components/ui/chart.js";
3
3
  import { cn } from "./utils.js";
4
+ import { useAxisScale } from "../components/siyahi/scale-toggle.js";
4
5
  import { useLocale } from "../components/siyahi/siyahi-provider.js";
6
+ import { fitsLog, logAxis } from "./ticks.js";
5
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
8
  import { useMemo } from "react";
7
9
  import { CartesianGrid, ReferenceLine, Text, XAxis, YAxis, useChartWidth, useXAxisTicks, useYAxisTicks } from "recharts";
@@ -82,11 +84,11 @@ function seriesRow(series, formatValue) {
82
84
  * never ₹80,000 halfway up. Recharts' tickFormatter sees one value at a time; the axis' ticks come from its hooks.
83
85
  * The rest of Recharts' tick props pass on as its own tick does, including the class it measures an auto width by.
84
86
  */
85
- function ValueTick({ payload, axis, formatter, ...tick }) {
87
+ function ValueTick({ payload, axis, formatter, ownUnit = false, ...tick }) {
86
88
  const xTicks = useXAxisTicks();
87
89
  const yTicks = useYAxisTicks();
88
90
  const value = Number(payload?.value);
89
- const scale = Math.max(Math.abs(value), ...((axis === "x" ? xTicks : yTicks) ?? []).map((t) => Math.abs(Number(t.value))));
91
+ const scale = ownUnit ? Math.abs(value) : Math.max(Math.abs(value), ...((axis === "x" ? xTicks : yTicks) ?? []).map((t) => Math.abs(Number(t.value))));
90
92
  return /* @__PURE__ */ jsx(Text, {
91
93
  ...tick,
92
94
  children: formatter(value, scale)
@@ -186,7 +188,7 @@ function HorizontalAxis({ data, xKey, xFormatter, xTickDetail, xTicks }) {
186
188
  * that gave its category axis its own id (dash#88) got a plot split in two
187
189
  * and every tooltip headed with the series name instead of the category.
188
190
  */
189
- function ChartFrame({ chart: Chart, children, horizontal = false, stackOffset, onClick, tooltipRow, legend = SERIES_LEGEND, title, description, action, data, xKey, series, xFormatter = String, xTooltipFormatter = xFormatter, valueFormatter, yTickFormatter, yDomain, yTicks, height, xTickDetail, xLabel, yLabel, referenceLines = NO_LINES, placeholder, footer, xDomain, xTicks }) {
191
+ function ChartFrame({ chart: Chart, children, horizontal = false, stackOffset, onClick, tooltipRow, legend = SERIES_LEGEND, title, description, action, data, xKey, series, xFormatter = String, xTooltipFormatter = xFormatter, valueFormatter, yTickFormatter, yDomain, yTicks, height, xTickDetail, xLabel, yLabel, referenceLines = NO_LINES, placeholder, footer, xDomain, xTicks, yScale = "linear", yScaleToggle = false, logBlocked }) {
190
192
  const { numberLocale } = useLocale();
191
193
  const formatValue = useMemo(() => valueFormatter ?? new Intl.NumberFormat(numberLocale).format, [valueFormatter, numberLocale]);
192
194
  const config = Object.fromEntries(series.map(({ key, label, color }) => [key, {
@@ -194,6 +196,16 @@ function ChartFrame({ chart: Chart, children, horizontal = false, stackOffset, o
194
196
  color
195
197
  }]));
196
198
  const crossesZero = data.some((row) => series.some(({ key }) => Number(row[key]) < 0));
199
+ const [low, high] = Array.isArray(yDomain) ? yDomain : [];
200
+ const range = typeof low === "number" && typeof high === "number" ? [low, high] : void 0;
201
+ const values = [...data.flatMap((row) => series.map(({ key }) => row[key])), ...range ?? []].filter((value) => typeof value === "number" && Number.isFinite(value));
202
+ const valueAxis = useAxisScale({
203
+ initial: yScale,
204
+ toggle: yScaleToggle,
205
+ label: yLabel ? `${yLabel} scale` : "Value axis scale",
206
+ logBlocked: logBlocked ?? (fitsLog(values) ? void 0 : "Log needs every value above zero")
207
+ });
208
+ const log = valueAxis.scale === "log" ? logAxis(values, range) : void 0;
197
209
  const detailedRows = horizontal && xTickDetail !== void 0;
198
210
  const rowCount = (xTicks ?? data).length;
199
211
  const plotClass = detailedRows ? "min-h-(--wrapped-chart-height) @min-[24rem]/chart:min-h-0" : void 0;
@@ -214,7 +226,10 @@ function ChartFrame({ chart: Chart, children, horizontal = false, stackOffset, o
214
226
  /* @__PURE__ */ jsxs(CardHeader, { children: [
215
227
  /* @__PURE__ */ jsx(CardTitle, { children: title }),
216
228
  description && /* @__PURE__ */ jsx(CardDescription, { children: description }),
217
- action && /* @__PURE__ */ jsx(CardAction, { children: action })
229
+ (action || valueAxis.buttons) && /* @__PURE__ */ jsxs(CardAction, {
230
+ className: "flex flex-wrap items-center gap-x-3 gap-y-2",
231
+ children: [action, valueAxis.buttons]
232
+ })
218
233
  ] }),
219
234
  /* @__PURE__ */ jsx(CardContent, {
220
235
  className: "@container/chart overflow-x-auto",
@@ -285,12 +300,13 @@ function ChartFrame({ chart: Chart, children, horizontal = false, stackOffset, o
285
300
  tick: xTickDetail || xTicks ? fittedTick(data, xKey, xFormatter, xTickDetail, false) : TICK,
286
301
  interval: xTickDetail || xTicks ? 0 : void 0
287
302
  }), /* @__PURE__ */ jsx(YAxis, {
288
- niceTicks: "snap125",
303
+ scale: valueAxis.scale,
304
+ niceTicks: log ? void 0 : "snap125",
289
305
  tickLine: false,
290
306
  axisLine: false,
291
307
  width: "auto",
292
- domain: yDomain,
293
- ticks: yTicks,
308
+ domain: log?.domain ?? yDomain,
309
+ ticks: log?.ticks ?? yTicks,
294
310
  label: yLabel ? {
295
311
  value: yLabel,
296
312
  angle: -90,
@@ -301,6 +317,7 @@ function ChartFrame({ chart: Chart, children, horizontal = false, stackOffset, o
301
317
  tick: yTickFormatter ? /* @__PURE__ */ jsx(ValueTick, {
302
318
  axis: "y",
303
319
  formatter: yTickFormatter,
320
+ ownUnit: log !== void 0,
304
321
  ...TICK
305
322
  }) : TICK,
306
323
  tickFormatter: yTickFormatter ? void 0 : (value) => formatValue(value)
@@ -1,3 +1,4 @@
1
+ import { AxisScale } from "./ticks.js";
1
2
  import { ReactNode } from "react";
2
3
  //#region src/lib/chart-series.d.ts
3
4
  /**
@@ -89,6 +90,17 @@ export interface NumericXProps {
89
90
  /** The x axis runs over this range of the xKey field, which must hold numbers; without it the axis is one slot per row. */
90
91
  xDomain?: [number, number];
91
92
  }
93
+ /** A value axis that may run on a log scale: lines and areas, which need not start at zero as bars must. */
94
+ export interface ValueScaleProps {
95
+ /**
96
+ * "log" spreads values across powers of ten, for values that run from tiny to large; the first selection when
97
+ * yScaleToggle is set. Log draws only when every value, and yDomain, is above zero; otherwise the axis is linear.
98
+ * On log the axis ticks at 1, 2 and 5 inside yDomain, if given, and yTicks is not used.
99
+ */
100
+ yScale?: AxisScale;
101
+ /** Linear and Log buttons for the value axis in the header, after the action, so a reader can switch. */
102
+ yScaleToggle?: boolean;
103
+ }
92
104
  /**
93
105
  * How a line or an area's edge joins its points: "linear" draws straight segments, as measured samples should
94
106
  * be drawn; "monotone", the default, smooths between them without overshooting a point.
@@ -1,15 +1,5 @@
1
+ import { AxisScale } from "./ticks.js";
1
2
  //#region src/lib/scatter.d.ts
2
- /**
3
- * Ticks for a log axis at 1, 2 and 5 times a power of ten, from the largest at or under min to the smallest at or
4
- * over max, so the axis ends on a tick and every label reads as a round number.
5
- */
6
- export declare function logTicks(min: number, max: number): number[];
7
- /**
8
- * Ticks for a linear axis that just covers min to max, about `count` of them, a step of 1, 2, 2.5 or 5 times a power
9
- * of ten: −50% to +75% by 25s, never −50% to +150% because the step had to be 50. Values that differ only by float
10
- * noise, such as 0.3 and 0.30000000000000004, count as one value and get that value as their only tick.
11
- */
12
- export declare function linearTicks(min: number, max: number, count?: number): number[];
13
3
  export interface Box {
14
4
  x: number;
15
5
  y: number;
@@ -46,7 +36,7 @@ export interface LabelType {
46
36
  export declare function placeLabels(candidates: readonly LabelCandidate[], plot: Box, limit: number, { size, measure }: LabelType, reserved?: readonly Box[]): PlacedLabel[];
47
37
  /**
48
38
  * Splits the plot into four at a value on each axis, such as zero, with an optional name in each corner. On a log
49
- * axis x must be above zero, or no split is drawn. A name that does not fit its quadrant in two lines is left out.
39
+ * axis that value must be above zero, or no split is drawn. A name that does not fit its quadrant in two lines is left out.
50
40
  */
51
41
  export interface ScatterQuadrants {
52
42
  x: number;
@@ -60,7 +50,7 @@ export interface ScatterQuadrants {
60
50
  }
61
51
  export type QuadrantLabels = ScatterQuadrants["labels"];
62
52
  /** A split a log axis cannot place, at or below zero, or one that is not a number, is no split at all. */
63
- export declare function quadrantSplit(quadrants: ScatterQuadrants | undefined, xScale: "linear" | "log"): ScatterQuadrants | undefined;
53
+ export declare function quadrantSplit(quadrants: ScatterQuadrants | undefined, xScale: AxisScale, yScale: AxisScale): ScatterQuadrants | undefined;
64
54
  /** The largest sized dot; the smallest stays big enough to see and to tap. */
65
55
  export declare const MAX_RADIUS = 20;
66
56
  /**
@@ -1,44 +1,4 @@
1
1
  //#region src/lib/scatter.ts
2
- /**
3
- * Ticks for a log axis at 1, 2 and 5 times a power of ten, from the largest at or under min to the smallest at or
4
- * over max, so the axis ends on a tick and every label reads as a round number.
5
- */
6
- function logTicks(min, max) {
7
- if (!(min > 0) || !Number.isFinite(max) || !(max >= min)) return [];
8
- const ticks = [];
9
- for (let decade = Math.floor(Math.log10(min));; decade++) for (const step of [
10
- 1,
11
- 2,
12
- 5
13
- ]) {
14
- const tick = Number((step * 10 ** decade).toPrecision(12));
15
- if (Number(((step === 5 ? 10 : step === 2 ? 5 : 2) * 10 ** decade).toPrecision(12)) <= min) continue;
16
- ticks.push(tick);
17
- if (tick >= max) return ticks;
18
- }
19
- }
20
- const clean = (value) => Number(value.toPrecision(12)) || 0;
21
- /**
22
- * Ticks for a linear axis that just covers min to max, about `count` of them, a step of 1, 2, 2.5 or 5 times a power
23
- * of ten: −50% to +75% by 25s, never −50% to +150% because the step had to be 50. Values that differ only by float
24
- * noise, such as 0.3 and 0.30000000000000004, count as one value and get that value as their only tick.
25
- */
26
- function linearTicks(min, max, count = 5) {
27
- if (!Number.isFinite(min) || !Number.isFinite(max) || max < min) return [];
28
- if (max - min <= Math.max(Math.abs(min), Math.abs(max)) * 1e-9) return [clean(min)];
29
- const rough = (max - min) / (count - 1);
30
- const power = 10 ** Math.floor(Math.log10(rough));
31
- const step = ([
32
- 1,
33
- 2,
34
- 2.5,
35
- 5,
36
- 10
37
- ].find((m) => m * power >= rough * (1 - 1e-9)) ?? 10) * power;
38
- const first = Math.floor(min / step + 1e-9);
39
- const last = Math.ceil(max / step - 1e-9);
40
- return Array.from({ length: last - first + 1 }, (_, i) => clean((first + i) * step));
41
- }
42
2
  const HALO = 2;
43
3
  const GAP = 4;
44
4
  const overlaps = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
@@ -125,10 +85,11 @@ function placeLabels(candidates, plot, limit, { size, measure }, reserved = [])
125
85
  }
126
86
  return placed;
127
87
  }
88
+ const fits = (scale, at) => scale === "linear" || at > 0;
128
89
  /** A split a log axis cannot place, at or below zero, or one that is not a number, is no split at all. */
129
- function quadrantSplit(quadrants, xScale) {
90
+ function quadrantSplit(quadrants, xScale, yScale) {
130
91
  if (!quadrants || !Number.isFinite(quadrants.x) || !Number.isFinite(quadrants.y)) return void 0;
131
- return xScale === "linear" || quadrants.x > 0 ? quadrants : void 0;
92
+ return fits(xScale, quadrants.x) && fits(yScale, quadrants.y) ? quadrants : void 0;
132
93
  }
133
94
  /** The largest sized dot; the smallest stays big enough to see and to tap. */
134
95
  const MAX_RADIUS = 20;
@@ -215,4 +176,4 @@ function corners(plot, names, split, type) {
215
176
  });
216
177
  }
217
178
  //#endregion
218
- export { MAX_RADIUS, corners, dotSizes, linearTicks, logTicks, placeLabels, quadrantSplit };
179
+ export { MAX_RADIUS, corners, dotSizes, placeLabels, quadrantSplit };
@@ -0,0 +1,27 @@
1
+ //#region src/lib/ticks.d.ts
2
+ /**
3
+ * Ticks for a log axis at 1, 2 and 5 times a power of ten, from the largest at or under min to the smallest at or
4
+ * over max, so the axis ends on a tick and every label reads as a round number.
5
+ */
6
+ export declare function logTicks(min: number, max: number): number[];
7
+ /**
8
+ * Ticks for a linear axis that just covers min to max, about `count` of them, a step of 1, 2, 2.5 or 5 times a power
9
+ * of ten: −50% to +75% by 25s, never −50% to +150% because the step had to be 50. Values that differ only by float
10
+ * noise, such as 0.3 and 0.30000000000000004, count as one value and get that value as their only tick.
11
+ */
12
+ export declare function linearTicks(min: number, max: number, count?: number): number[];
13
+ /** How an axis spaces its values: evenly, or by powers of ten for values that run from tiny to large. */
14
+ export type AxisScale = "linear" | "log";
15
+ /** An axis' ticks for its scale: round steps either way, so a linear axis starting at 0.4% reads 0%, 5%, 10%. */
16
+ export declare const ticksFor: (scale: AxisScale, min: number, max: number) => number[];
17
+ /** A log axis has no place for zero or below, so it can draw a set of values only when every one is above zero. */
18
+ export declare const fitsLog: (values: readonly number[]) => boolean;
19
+ /**
20
+ * A log axis' ends and ticks: a page's fixed range when it gives one, with the 1, 2 and 5 ticks inside it, or else the
21
+ * ticks that just cover the values and their outer two as the ends.
22
+ */
23
+ export declare function logAxis(values: readonly number[], range?: readonly [number, number]): {
24
+ domain: [number, number];
25
+ ticks: number[];
26
+ };
27
+ //#endregion
@@ -0,0 +1,67 @@
1
+ //#region src/lib/ticks.ts
2
+ /**
3
+ * Ticks for a log axis at 1, 2 and 5 times a power of ten, from the largest at or under min to the smallest at or
4
+ * over max, so the axis ends on a tick and every label reads as a round number.
5
+ */
6
+ function logTicks(min, max) {
7
+ if (!(min > 0) || !Number.isFinite(max) || !(max >= min)) return [];
8
+ const ticks = [];
9
+ for (let decade = Math.floor(Math.log10(min));; decade++) for (const step of [
10
+ 1,
11
+ 2,
12
+ 5
13
+ ]) {
14
+ const tick = Number((step * 10 ** decade).toPrecision(12));
15
+ if (Number(((step === 5 ? 10 : step === 2 ? 5 : 2) * 10 ** decade).toPrecision(12)) <= min) continue;
16
+ ticks.push(tick);
17
+ if (tick >= max) return ticks;
18
+ }
19
+ }
20
+ const clean = (value) => Number(value.toPrecision(12)) || 0;
21
+ /**
22
+ * Ticks for a linear axis that just covers min to max, about `count` of them, a step of 1, 2, 2.5 or 5 times a power
23
+ * of ten: −50% to +75% by 25s, never −50% to +150% because the step had to be 50. Values that differ only by float
24
+ * noise, such as 0.3 and 0.30000000000000004, count as one value and get that value as their only tick.
25
+ */
26
+ function linearTicks(min, max, count = 5) {
27
+ if (!Number.isFinite(min) || !Number.isFinite(max) || max < min) return [];
28
+ if (max - min <= Math.max(Math.abs(min), Math.abs(max)) * 1e-9) return [clean(min)];
29
+ const rough = (max - min) / (count - 1);
30
+ const power = 10 ** Math.floor(Math.log10(rough));
31
+ const step = ([
32
+ 1,
33
+ 2,
34
+ 2.5,
35
+ 5,
36
+ 10
37
+ ].find((m) => m * power >= rough * (1 - 1e-9)) ?? 10) * power;
38
+ const first = Math.floor(min / step + 1e-9);
39
+ const last = Math.ceil(max / step - 1e-9);
40
+ return Array.from({ length: last - first + 1 }, (_, i) => clean((first + i) * step));
41
+ }
42
+ /** An axis' ticks for its scale: round steps either way, so a linear axis starting at 0.4% reads 0%, 5%, 10%. */
43
+ const ticksFor = (scale, min, max) => scale === "log" ? logTicks(min, max) : linearTicks(min, max);
44
+ /** A log axis has no place for zero or below, so it can draw a set of values only when every one is above zero. */
45
+ const fitsLog = (values) => values.length > 0 && values.every((value) => value > 0);
46
+ /**
47
+ * A log axis' ends and ticks: a page's fixed range when it gives one, with the 1, 2 and 5 ticks inside it, or else the
48
+ * ticks that just cover the values and their outer two as the ends.
49
+ */
50
+ function logAxis(values, range) {
51
+ if (range) {
52
+ const [low, high] = range;
53
+ return {
54
+ domain: [low, high],
55
+ ticks: logTicks(low, high).filter((tick) => tick >= low && tick <= high)
56
+ };
57
+ }
58
+ const low = Math.min(...values);
59
+ const high = Math.max(...values);
60
+ const ticks = logTicks(low, high);
61
+ return {
62
+ domain: [ticks[0] ?? low, ticks.at(-1) ?? high],
63
+ ticks
64
+ };
65
+ }
66
+ //#endregion
67
+ export { fitsLog, linearTicks, logAxis, logTicks, ticksFor };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siyahi/ui",
3
- "version": "0.1.9-next.26",
3
+ "version": "0.1.9-next.27",
4
4
  "description": "Stock shadcn/ui on Base UI and Tailwind v4, a finance theme, a few compositions and formatters.",
5
5
  "keywords": [
6
6
  "base-ui",