@scalewing/react 1.38.0 → 1.40.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.
Files changed (162) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +155 -1
  3. package/dist/calendar-month.d.ts +6 -0
  4. package/dist/calendar-month.js +15 -0
  5. package/dist/chart-axis.d.ts +61 -0
  6. package/dist/chart-axis.js +83 -0
  7. package/dist/chart-dates.d.ts +6 -0
  8. package/dist/chart-dates.js +15 -0
  9. package/dist/chart-metrics.d.ts +40 -0
  10. package/dist/chart-metrics.js +48 -0
  11. package/dist/chart-scale.d.ts +48 -0
  12. package/dist/chart-scale.js +116 -0
  13. package/dist/chart-table-rows.d.ts +8 -0
  14. package/dist/chart-table-rows.js +1 -0
  15. package/dist/column-chart-bars.d.ts +38 -0
  16. package/dist/column-chart-bars.js +82 -0
  17. package/dist/column-chart-callouts.d.ts +48 -0
  18. package/dist/column-chart-callouts.js +54 -0
  19. package/dist/column-chart-input.d.ts +40 -0
  20. package/dist/column-chart-input.js +60 -0
  21. package/dist/column-chart-layout.d.ts +63 -0
  22. package/dist/column-chart-layout.js +97 -0
  23. package/dist/components/BarChart.d.ts +1 -1
  24. package/dist/components/BarChart.js +1 -1
  25. package/dist/components/ChartLegend.d.ts +38 -0
  26. package/dist/components/ChartLegend.js +32 -0
  27. package/dist/components/ColumnChart.d.ts +72 -0
  28. package/dist/components/ColumnChart.js +71 -0
  29. package/dist/components/DataBar.d.ts +6 -6
  30. package/dist/components/DateRangeButton.d.ts +65 -0
  31. package/dist/components/DateRangeButton.js +54 -0
  32. package/dist/components/LineChart.d.ts +83 -0
  33. package/dist/components/LineChart.js +77 -0
  34. package/dist/components/ListGroup.d.ts +33 -0
  35. package/dist/components/ListGroup.js +19 -0
  36. package/dist/components/ListRow.d.ts +51 -0
  37. package/dist/components/ListRow.js +59 -0
  38. package/dist/components/NavCount.d.ts +21 -0
  39. package/dist/components/NavCount.js +23 -0
  40. package/dist/components/SectionNav.js +1 -8
  41. package/dist/components/StatTile.d.ts +11 -4
  42. package/dist/components/StatTile.js +11 -4
  43. package/dist/components/{date-field → calendar}/CalendarGrid.d.ts +17 -2
  44. package/dist/components/{date-field → calendar}/CalendarGrid.js +11 -3
  45. package/dist/components/calendar/CalendarPopover.d.ts +25 -0
  46. package/dist/components/calendar/CalendarPopover.js +22 -0
  47. package/dist/components/calendar/MonthStep.d.ts +16 -0
  48. package/dist/components/calendar/MonthStep.js +17 -0
  49. package/dist/components/calendar/use-grid-focus.d.ts +18 -0
  50. package/dist/components/calendar/use-grid-focus.js +35 -0
  51. package/dist/components/calendar/wrap-tab.d.ts +7 -0
  52. package/dist/components/calendar/wrap-tab.js +29 -0
  53. package/dist/components/chart/ChartTable.d.ts +16 -0
  54. package/dist/components/chart/ChartTable.js +12 -0
  55. package/dist/components/chart/ChartValueAxis.d.ts +14 -0
  56. package/dist/components/chart/ChartValueAxis.js +11 -0
  57. package/dist/components/chart/use-chart-width.d.ts +8 -0
  58. package/dist/components/chart/use-chart-width.js +34 -0
  59. package/dist/components/column-chart/ChartCalloutList.d.ts +16 -0
  60. package/dist/components/column-chart/ChartCalloutList.js +13 -0
  61. package/dist/components/column-chart/ColumnChartDrawing.d.ts +17 -0
  62. package/dist/components/column-chart/ColumnChartDrawing.js +18 -0
  63. package/dist/components/date-field/CalendarDialog.js +9 -67
  64. package/dist/components/date-field/CalendarHeader.js +2 -11
  65. package/dist/components/date-range/DateRangePopover.d.ts +7 -0
  66. package/dist/components/date-range/DateRangePopover.js +17 -0
  67. package/dist/components/date-range/DateRangeSheet.d.ts +7 -0
  68. package/dist/components/date-range/DateRangeSheet.js +15 -0
  69. package/dist/components/date-range/RangeMonths.d.ts +28 -0
  70. package/dist/components/date-range/RangeMonths.js +50 -0
  71. package/dist/components/date-range/RangePresets.d.ts +16 -0
  72. package/dist/components/date-range/RangePresets.js +10 -0
  73. package/dist/components/date-range/RangeSummary.d.ts +15 -0
  74. package/dist/components/date-range/RangeSummary.js +11 -0
  75. package/dist/components/date-range/range-panel-props.d.ts +31 -0
  76. package/dist/components/date-range/range-panel-props.js +1 -0
  77. package/dist/components/date-range/use-range-popup.d.ts +21 -0
  78. package/dist/components/date-range/use-range-popup.js +53 -0
  79. package/dist/components/line-chart/LineChartDrawing.d.ts +17 -0
  80. package/dist/components/line-chart/LineChartDrawing.js +16 -0
  81. package/dist/components/link-press.d.ts +7 -0
  82. package/dist/components/link-press.js +13 -0
  83. package/dist/components/list/ListRowAccessoryMark.d.ts +5 -0
  84. package/dist/components/list/ListRowAccessoryMark.js +7 -0
  85. package/dist/components/list/ListRowContent.d.ts +20 -0
  86. package/dist/components/list/ListRowContent.js +10 -0
  87. package/dist/components/list/list-row-container.d.ts +5 -0
  88. package/dist/components/list/list-row-container.js +6 -0
  89. package/dist/components/use-media-query.d.ts +5 -0
  90. package/dist/components/use-media-query.js +18 -0
  91. package/dist/css/chart-marks.d.ts +10 -0
  92. package/dist/css/chart-marks.js +15 -0
  93. package/dist/css/chart-tones.d.ts +68 -0
  94. package/dist/css/chart-tones.js +130 -0
  95. package/dist/css/chevron.d.ts +6 -0
  96. package/dist/css/chevron.js +14 -0
  97. package/dist/css/{css-chart.d.ts → css-bar-chart.d.ts} +2 -2
  98. package/dist/css/{css-chart.js → css-bar-chart.js} +2 -2
  99. package/dist/css/css-button-group-attached.js +8 -4
  100. package/dist/css/css-button-group-track.js +1 -1
  101. package/dist/css/css-chart-legend.d.ts +5 -0
  102. package/dist/css/css-chart-legend.js +102 -0
  103. package/dist/css/css-chart-plot.d.ts +2 -0
  104. package/dist/css/css-chart-plot.js +170 -0
  105. package/dist/css/css-checkbox.js +2 -2
  106. package/dist/css/css-components.js +23 -2
  107. package/dist/css/css-count-pill.d.ts +2 -0
  108. package/dist/css/css-count-pill.js +54 -0
  109. package/dist/css/css-data-bar.d.ts +4 -9
  110. package/dist/css/css-data-bar.js +15 -31
  111. package/dist/css/css-date-field.js +3 -2
  112. package/dist/css/css-date-range-presets.d.ts +2 -0
  113. package/dist/css/css-date-range-presets.js +109 -0
  114. package/dist/css/css-date-range.d.ts +2 -0
  115. package/dist/css/css-date-range.js +197 -0
  116. package/dist/css/css-line-chart.d.ts +2 -0
  117. package/dist/css/css-line-chart.js +31 -0
  118. package/dist/css/css-list-row.d.ts +2 -0
  119. package/dist/css/css-list-row.js +193 -0
  120. package/dist/css/css-list.d.ts +3 -0
  121. package/dist/css/css-list.js +85 -0
  122. package/dist/css/css-scroll-region.d.ts +18 -0
  123. package/dist/css/css-scroll-region.js +41 -0
  124. package/dist/css/css-section-nav-filled.js +2 -25
  125. package/dist/css/css-stat-tile.d.ts +7 -0
  126. package/dist/css/css-stat-tile.js +16 -0
  127. package/dist/css/css-steps.js +2 -2
  128. package/dist/css/css-table-bounded-edges.js +4 -27
  129. package/dist/css/range-band.d.ts +2 -0
  130. package/dist/css/range-band.js +7 -0
  131. package/dist/css/stacking.js +2 -2
  132. package/dist/css/stylesheet.js +16 -2
  133. package/dist/date-range-draft.d.ts +26 -0
  134. package/dist/date-range-draft.js +36 -0
  135. package/dist/date-range-input.d.ts +26 -0
  136. package/dist/date-range-input.js +58 -0
  137. package/dist/date-range-labels.d.ts +22 -0
  138. package/dist/date-range-labels.js +26 -0
  139. package/dist/date-range-view.d.ts +12 -0
  140. package/dist/date-range-view.js +29 -0
  141. package/dist/date-range-words.d.ts +11 -0
  142. package/dist/date-range-words.js +40 -0
  143. package/dist/index.d.ts +9 -1
  144. package/dist/index.js +9 -1
  145. package/dist/is-one-of.d.ts +5 -0
  146. package/dist/is-one-of.js +7 -0
  147. package/dist/line-chart-input.d.ts +39 -0
  148. package/dist/line-chart-input.js +70 -0
  149. package/dist/line-chart-layout.d.ts +68 -0
  150. package/dist/line-chart-layout.js +99 -0
  151. package/dist/line-chart-markers.d.ts +29 -0
  152. package/dist/line-chart-markers.js +57 -0
  153. package/dist/line-chart-path.d.ts +18 -0
  154. package/dist/line-chart-path.js +33 -0
  155. package/dist/line-chart-points.d.ts +6 -0
  156. package/dist/line-chart-points.js +8 -0
  157. package/dist/line-chart-table.d.ts +15 -0
  158. package/dist/line-chart-table.js +29 -0
  159. package/dist/list-options.d.ts +70 -0
  160. package/dist/list-options.js +77 -0
  161. package/dist/styles.css +726 -26
  162. package/package.json +1 -1
@@ -0,0 +1,116 @@
1
+ /** How many steps a value axis aims for between its ends. */
2
+ export const chartScaleSteps = 4;
3
+ /** The multipliers a step takes within each power of ten. */
4
+ const niceMultipliers = [1, 2, 2.5, 5, 10];
5
+ /**
6
+ * The smallest round step (1, 2, 2.5 or 5 times a power of ten) that
7
+ * covers `span` in at most `steps` steps.
8
+ */
9
+ export function niceStep(span, steps) {
10
+ if (!(span > 0))
11
+ return 1;
12
+ const raw = span / steps;
13
+ const power = 10 ** Math.floor(Math.log10(raw));
14
+ for (const multiplier of niceMultipliers) {
15
+ const step = multiplier * power;
16
+ if (step >= raw * (1 - 1e-9))
17
+ return step;
18
+ }
19
+ return 10 * power;
20
+ }
21
+ /** The least and greatest of some values, both stretched to reach zero. */
22
+ export function extentThroughZero(values) {
23
+ let min = 0;
24
+ let max = 0;
25
+ for (const value of values) {
26
+ if (value < min)
27
+ min = value;
28
+ if (value > max)
29
+ max = value;
30
+ }
31
+ return { min, max };
32
+ }
33
+ /** Rounds away the floating-point dust of a step multiple. */
34
+ function tidy(value, step) {
35
+ // toFixed takes at most 100 digits.
36
+ const decimals = Math.min(100, Math.max(0, -Math.floor(Math.log10(step)) + 2));
37
+ return Number(value.toFixed(decimals)) + 0;
38
+ }
39
+ function scaleFrom(step, below, above) {
40
+ const ticks = [];
41
+ for (let index = -below; index <= above; index += 1)
42
+ ticks.push(tidy(index * step, step));
43
+ return {
44
+ min: tidy(-below * step, step),
45
+ max: tidy(above * step, step),
46
+ step,
47
+ ticks,
48
+ };
49
+ }
50
+ /** Whole steps below and above zero for an extent at one step. */
51
+ function stepsAround(extent, step) {
52
+ return {
53
+ below: Math.ceil(-extent.min / step - 1e-9),
54
+ above: Math.ceil(extent.max / step - 1e-9),
55
+ };
56
+ }
57
+ /** A scale for an extent aiming at a number of steps. */
58
+ function scaleAiming(extent, steps) {
59
+ const span = extent.max - extent.min;
60
+ if (span === 0)
61
+ return scaleFrom(1, 0, 1);
62
+ let step = niceStep(span, steps);
63
+ let { below, above } = stepsAround(extent, step);
64
+ // Rounding each side up can add a step; widen the step once if so.
65
+ if (below + above > steps + 1 && steps > 1) {
66
+ step = niceStep(span, steps - 1);
67
+ ({ below, above } = stepsAround(extent, step));
68
+ }
69
+ return scaleFrom(step, Math.max(below, 0), Math.max(above, 0));
70
+ }
71
+ /**
72
+ * A value axis for an extent through zero: a round step, and as many whole
73
+ * steps either side of zero as the extent needs. An extent of only zero
74
+ * reads 0 to 1.
75
+ */
76
+ export function chartScale(extent) {
77
+ return scaleAiming(extent, chartScaleSteps);
78
+ }
79
+ /** Whole steps a scale has below and above zero. */
80
+ function sides(scale) {
81
+ return {
82
+ below: Math.round(-scale.min / scale.step),
83
+ above: Math.round(scale.max / scale.step),
84
+ };
85
+ }
86
+ /**
87
+ * Two value axes drawn on one plot, such as columns on the left and a
88
+ * running total on the right: each keeps its own round step, and both have
89
+ * the same number of steps below and above zero, so zero and every grid
90
+ * line fall at the same height on both. When the two lean different ways
91
+ * (one mostly above zero, the other below), both take fewer, longer steps
92
+ * so the plot keeps about as many grid lines as one scale would.
93
+ */
94
+ export function alignedChartScales(first, second) {
95
+ let aligned = [
96
+ chartScale(first),
97
+ chartScale(second),
98
+ ];
99
+ for (let steps = chartScaleSteps; steps >= 1; steps -= 1) {
100
+ const a = scaleAiming(first, steps);
101
+ const b = scaleAiming(second, steps);
102
+ const below = Math.max(sides(a).below, sides(b).below);
103
+ const above = Math.max(sides(a).above, sides(b).above);
104
+ aligned = [
105
+ scaleFrom(a.step, below, above),
106
+ scaleFrom(b.step, below, above),
107
+ ];
108
+ if (below + above <= chartScaleSteps + 1)
109
+ break;
110
+ }
111
+ return aligned;
112
+ }
113
+ /** Where a value falls on a scale, as a share from its bottom (0) to top (1). */
114
+ export function scaleShare(value, scale) {
115
+ return (value - scale.min) / (scale.max - scale.min);
116
+ }
@@ -0,0 +1,8 @@
1
+ /** One row of a chart's hidden table: what it is, and each column's words. */
2
+ export type ChartTableRow = {
3
+ key: string;
4
+ header: string;
5
+ cells: readonly string[];
6
+ /** Marks the boxed current period. */
7
+ current?: boolean;
8
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import { type ChartPlot } from './chart-axis.js';
2
+ import { type ChartScale } from './chart-scale.js';
3
+ /** How a category's columns sit: one on another, or side by side. */
4
+ export declare const columnChartModes: readonly ["stacked", "grouped"];
5
+ export type ColumnChartMode = (typeof columnChartModes)[number];
6
+ /**
7
+ * One series' column in one category, in values: from `low` to `high`,
8
+ * and which of the category's slots it takes (always 0 when stacked).
9
+ */
10
+ export type ColumnSpan = {
11
+ series: number;
12
+ category: number;
13
+ slot: number;
14
+ low: number;
15
+ high: number;
16
+ };
17
+ /**
18
+ * Each series' column in each category, in values. Stacked, the positive
19
+ * values stack up from zero and the negative ones down from it, each in
20
+ * series order; grouped, every value runs from zero in its own slot. A
21
+ * zero value has no column.
22
+ */
23
+ export declare function columnSpans(series: readonly (readonly number[])[], categories: number, mode: ColumnChartMode): ColumnSpan[];
24
+ /** A drawn column: which series and category, and its box. */
25
+ export type PlacedColumn = {
26
+ series: number;
27
+ category: number;
28
+ x: number;
29
+ y: number;
30
+ width: number;
31
+ height: number;
32
+ };
33
+ /** Each span's box: a column, or its slot in the category's group. */
34
+ export declare function placeColumns(input: {
35
+ series: readonly (readonly number[])[];
36
+ categories: number;
37
+ mode: ColumnChartMode;
38
+ }, scale: ChartScale, plot: ChartPlot, band: number): PlacedColumn[];
@@ -0,0 +1,82 @@
1
+ import { valueY } from './chart-axis.js';
2
+ import { chartColumnGap } from './css/chart-marks.js';
3
+ /** How a category's columns sit: one on another, or side by side. */
4
+ export const columnChartModes = ['stacked', 'grouped'];
5
+ /**
6
+ * Each series' column in each category, in values. Stacked, the positive
7
+ * values stack up from zero and the negative ones down from it, each in
8
+ * series order; grouped, every value runs from zero in its own slot. A
9
+ * zero value has no column.
10
+ */
11
+ export function columnSpans(series, categories, mode) {
12
+ const spans = [];
13
+ for (let category = 0; category < categories; category += 1) {
14
+ let up = 0;
15
+ let down = 0;
16
+ series.forEach((values, index) => {
17
+ const value = values[category];
18
+ if (value === 0)
19
+ return;
20
+ if (mode === 'grouped') {
21
+ spans.push({
22
+ series: index,
23
+ category,
24
+ slot: index,
25
+ low: Math.min(0, value),
26
+ high: Math.max(0, value),
27
+ });
28
+ }
29
+ else if (value > 0) {
30
+ spans.push({
31
+ series: index,
32
+ category,
33
+ slot: 0,
34
+ low: up,
35
+ high: up + value,
36
+ });
37
+ up += value;
38
+ }
39
+ else {
40
+ spans.push({
41
+ series: index,
42
+ category,
43
+ slot: 0,
44
+ low: down + value,
45
+ high: down,
46
+ });
47
+ down += value;
48
+ }
49
+ });
50
+ }
51
+ return spans;
52
+ }
53
+ /** How much of a category's width its stacked column, or its group, takes. */
54
+ const stackedShare = 0.6;
55
+ const groupedShare = 0.72;
56
+ /** Each span's box: a column, or its slot in the category's group. */
57
+ export function placeColumns(input, scale, plot, band) {
58
+ const slots = input.mode === 'grouped' ? input.series.length : 1;
59
+ const group = band * (input.mode === 'grouped' ? groupedShare : stackedShare);
60
+ const slot = group / slots;
61
+ const gap = slots > 1 ? Math.min(chartColumnGap, slot / 4) : 0;
62
+ return columnSpans(input.series, input.categories, input.mode).map((span) => {
63
+ const top = valueY(span.high, scale, plot);
64
+ const bottom = valueY(span.low, scale, plot);
65
+ // A value too small to see still shows: a column at least 1 px tall,
66
+ // standing on zero (or hanging from it).
67
+ const height = Math.max(bottom - top, 1);
68
+ const y = span.low < 0 && span.high <= 0 ? top : bottom - height;
69
+ return {
70
+ series: span.series,
71
+ category: span.category,
72
+ x: plot.left +
73
+ band * span.category +
74
+ (band - group) / 2 +
75
+ slot * span.slot +
76
+ gap / 2,
77
+ y,
78
+ width: slot - gap,
79
+ height,
80
+ };
81
+ });
82
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A callout pinned above the plot: its words in a row at the top, beside a
3
+ * dashed leader that runs down from the row to the column it is about.
4
+ */
5
+ export type PlacedCallout = {
6
+ /** Its place in the chart's `callouts`. */
7
+ order: number;
8
+ /** The category it is about. */
9
+ index: number;
10
+ text: string;
11
+ /** The leader's x, the category's centre. */
12
+ x: number;
13
+ /** The words' start (or end) and baseline. */
14
+ textX: number;
15
+ textY: number;
16
+ anchor: 'start' | 'end';
17
+ /** The leader runs from the row (`leaderStart`) down to `leaderEnd`. */
18
+ leaderStart: number;
19
+ leaderEnd: number;
20
+ };
21
+ /**
22
+ * Where a callout's words sit: after its leader in the first half of the
23
+ * chart and before it in the second, the side with more room. Words
24
+ * longer than that room are moved in so they stay inside the chart,
25
+ * crossing their leader.
26
+ */
27
+ export declare function besideLeader(x: number, width: number, bounds: {
28
+ left: number;
29
+ right: number;
30
+ }): {
31
+ anchor: 'start' | 'end';
32
+ textX: number;
33
+ };
34
+ /**
35
+ * Sets each callout's words beside its leader (`besideLeader`) and gives it the first row from
36
+ * the top where it meets no other callout's words. Returns how many rows
37
+ * the callouts take.
38
+ */
39
+ export declare function calloutRows(callouts: readonly {
40
+ index: number;
41
+ text: string;
42
+ }[], centre: (index: number) => number, bounds: {
43
+ left: number;
44
+ right: number;
45
+ }): {
46
+ rows: number;
47
+ callouts: PlacedCallout[];
48
+ };
@@ -0,0 +1,54 @@
1
+ import { chartGap, chartInset, chartTextWidth, chartType, } from './chart-metrics.js';
2
+ function overlaps(a, b) {
3
+ return a.from < b.to + chartGap && b.from < a.to + chartGap;
4
+ }
5
+ /**
6
+ * Where a callout's words sit: after its leader in the first half of the
7
+ * chart and before it in the second, the side with more room. Words
8
+ * longer than that room are moved in so they stay inside the chart,
9
+ * crossing their leader.
10
+ */
11
+ export function besideLeader(x, width, bounds) {
12
+ if (x <= (bounds.left + bounds.right) / 2)
13
+ return {
14
+ anchor: 'start',
15
+ textX: Math.max(bounds.left, Math.min(x + chartInset, bounds.right - width)),
16
+ };
17
+ return {
18
+ anchor: 'end',
19
+ textX: Math.min(bounds.right, Math.max(x - chartInset, bounds.left + width)),
20
+ };
21
+ }
22
+ /**
23
+ * Sets each callout's words beside its leader (`besideLeader`) and gives it the first row from
24
+ * the top where it meets no other callout's words. Returns how many rows
25
+ * the callouts take.
26
+ */
27
+ export function calloutRows(callouts, centre, bounds) {
28
+ const rows = [];
29
+ const placed = callouts.map((callout, order) => {
30
+ const x = centre(callout.index);
31
+ const width = chartTextWidth(callout.text);
32
+ const { anchor, textX } = besideLeader(x, width, bounds);
33
+ const span = anchor === 'start'
34
+ ? { from: Math.min(x, textX), to: textX + width }
35
+ : { from: textX - width, to: Math.max(x, textX) };
36
+ let row = rows.findIndex((taken) => !taken.some((t) => overlaps(t, span)));
37
+ if (row === -1)
38
+ row = rows.push([]) - 1;
39
+ rows[row].push(span);
40
+ const rowTop = row * chartType.lineHeight;
41
+ return {
42
+ order,
43
+ index: callout.index,
44
+ text: callout.text,
45
+ x,
46
+ textX,
47
+ textY: rowTop + chartType.fontSize,
48
+ anchor,
49
+ leaderStart: rowTop + chartType.lineHeight / 2,
50
+ leaderEnd: rowTop + chartType.lineHeight / 2,
51
+ };
52
+ });
53
+ return { rows: rows.length, callouts: placed };
54
+ }
@@ -0,0 +1,40 @@
1
+ import { type CategoricalColor } from '@scalewing/tokens';
2
+ import { type ColumnChartMode } from './column-chart-bars.js';
3
+ /** One series of a column chart: a value per category, in one hue. */
4
+ export type ColumnChartSeries = {
5
+ /** Stable and unique within the chart. */
6
+ id: string;
7
+ label: string;
8
+ values: readonly number[];
9
+ tone: CategoricalColor;
10
+ };
11
+ /** A line over the columns, on its own scale: a value per category. */
12
+ export type ColumnChartLine = {
13
+ label: string;
14
+ values: readonly number[];
15
+ /** Writes the line's last value beside its axis (default `true`). */
16
+ endLabel?: boolean;
17
+ };
18
+ /** Words pinned above one category's column, such as why it stands out. */
19
+ export type ColumnChartCallout = {
20
+ index: number;
21
+ text: string;
22
+ /** The hue of its words and dot; the text colour when left out. */
23
+ tone?: CategoricalColor;
24
+ };
25
+ export type ColumnChartInput = {
26
+ label: string;
27
+ categories: readonly string[];
28
+ series: readonly ColumnChartSeries[];
29
+ mode: ColumnChartMode;
30
+ line?: ColumnChartLine;
31
+ highlight?: number;
32
+ callouts: readonly ColumnChartCallout[];
33
+ };
34
+ /**
35
+ * Refuses what a column chart cannot draw, with a `RangeError`: empty
36
+ * words, a series or line without one finite value per category, a
37
+ * repeated series id, a tone off the categorical scale, an unknown mode,
38
+ * and a highlight or callout that is not a category's index.
39
+ */
40
+ export declare function assertColumnChartInput(input: ColumnChartInput): void;
@@ -0,0 +1,60 @@
1
+ import { isCategoricalColor } from '@scalewing/tokens';
2
+ import { columnChartModes } from './column-chart-bars.js';
3
+ import { isOneOf } from './is-one-of.js';
4
+ function refuse(message) {
5
+ throw new RangeError(`ColumnChart ${message}`);
6
+ }
7
+ function assertWords(what, value) {
8
+ if (typeof value !== 'string' || value.trim() === '')
9
+ refuse(`${what} must not be empty`);
10
+ }
11
+ function assertValues(what, values, count) {
12
+ if (values.length !== count)
13
+ refuse(`${what} must have one value per category (${count}); received ${values.length}`);
14
+ for (const value of values)
15
+ if (typeof value !== 'number' || !Number.isFinite(value))
16
+ refuse(`${what} values must be finite numbers: ${String(value)}`);
17
+ }
18
+ function assertIndex(what, index, count) {
19
+ if (!Number.isInteger(index) ||
20
+ index < 0 ||
21
+ index >= count)
22
+ refuse(`${what} must be a category's index, 0 to ${count - 1}: ${String(index)}`);
23
+ }
24
+ /**
25
+ * Refuses what a column chart cannot draw, with a `RangeError`: empty
26
+ * words, a series or line without one finite value per category, a
27
+ * repeated series id, a tone off the categorical scale, an unknown mode,
28
+ * and a highlight or callout that is not a category's index.
29
+ */
30
+ export function assertColumnChartInput(input) {
31
+ const count = input.categories.length;
32
+ assertWords('label', input.label);
33
+ for (const category of input.categories)
34
+ assertWords('category', category);
35
+ if (!isOneOf(columnChartModes, input.mode))
36
+ refuse(`mode must be one of ${columnChartModes.join(', ')}; received ${String(input.mode)}`);
37
+ const ids = new Set();
38
+ for (const series of input.series) {
39
+ assertWords('series id', series.id);
40
+ if (ids.has(series.id))
41
+ refuse(`series id must be unique: ${series.id}`);
42
+ ids.add(series.id);
43
+ assertWords('series label', series.label);
44
+ assertValues(`series ${series.id}`, series.values, count);
45
+ if (!isCategoricalColor(series.tone))
46
+ refuse(`series tone must be a categorical colour: ${String(series.tone)}`);
47
+ }
48
+ if (input.line) {
49
+ assertWords('line label', input.line.label);
50
+ assertValues('line', input.line.values, count);
51
+ }
52
+ if (input.highlight !== undefined)
53
+ assertIndex('highlight', input.highlight, count);
54
+ for (const callout of input.callouts) {
55
+ assertIndex('callout index', callout.index, count);
56
+ assertWords('callout text', callout.text);
57
+ if (callout.tone !== undefined && !isCategoricalColor(callout.tone))
58
+ refuse(`callout tone must be a categorical colour: ${String(callout.tone)}`);
59
+ }
60
+ }
@@ -0,0 +1,63 @@
1
+ import { type ChartPlot, type ChartTick } from './chart-axis.js';
2
+ import { type ColumnChartMode, type PlacedColumn } from './column-chart-bars.js';
3
+ import { type PlacedCallout } from './column-chart-callouts.js';
4
+ export type ColumnChartLayoutInput = {
5
+ width: number;
6
+ categories: readonly string[];
7
+ series: readonly (readonly number[])[];
8
+ mode: ColumnChartMode;
9
+ /** The line's values, and the words at its last value (or none). */
10
+ line: {
11
+ values: readonly number[];
12
+ endLabel: string | null;
13
+ } | null;
14
+ highlight: number | null;
15
+ callouts: readonly {
16
+ index: number;
17
+ text: string;
18
+ }[];
19
+ formatTick: (value: number) => string;
20
+ };
21
+ export type ColumnChartLayout = {
22
+ width: number;
23
+ height: number;
24
+ compact: boolean;
25
+ plot: ChartPlot;
26
+ ticks: ChartTick[];
27
+ lineTicks: ChartTick[];
28
+ columns: PlacedColumn[];
29
+ categoryLabels: {
30
+ index: number;
31
+ x: number;
32
+ y: number;
33
+ text: string;
34
+ }[];
35
+ highlight: {
36
+ x: number;
37
+ y: number;
38
+ width: number;
39
+ height: number;
40
+ } | null;
41
+ line: {
42
+ points: {
43
+ x: number;
44
+ y: number;
45
+ }[];
46
+ end: {
47
+ x: number;
48
+ y: number;
49
+ text: string;
50
+ } | null;
51
+ } | null;
52
+ callouts: PlacedCallout[];
53
+ };
54
+ /**
55
+ * Lays a column chart out at a width: its value axis on the left (and a
56
+ * line's own scale on the right, its zero and grid lines level with the
57
+ * columns'), the columns, the category labels (fewer when they would
58
+ * crowd), the boxed period, the line with its last value beside its axis,
59
+ * and the callouts pinned in rows above the plot. A compact chart (under
60
+ * `chartCompactWidth`) has a shorter plot and draws no callouts: they are
61
+ * listed under it.
62
+ */
63
+ export declare function columnChartLayout(input: ColumnChartLayoutInput): ColumnChartLayout;
@@ -0,0 +1,97 @@
1
+ import { endGutter, labelGutter, labelInside, periodBox, labelledPlaces, ticksClearOf, valueTicks, valueY, } from './chart-axis.js';
2
+ import { chartCompactWidth, chartGap, chartInset, chartPlotHeight, chartType, } from './chart-metrics.js';
3
+ import { alignedChartScales, chartScale, extentThroughZero, } from './chart-scale.js';
4
+ import { columnSpans, placeColumns, } from './column-chart-bars.js';
5
+ import { calloutRows } from './column-chart-callouts.js';
6
+ function scalesFor(input) {
7
+ const spans = columnSpans(input.series, input.categories.length, input.mode);
8
+ const columns = extentThroughZero(spans.flatMap((s) => [s.low, s.high]));
9
+ if (!input.line)
10
+ return { values: chartScale(columns), line: null };
11
+ const [values, line] = alignedChartScales(columns, extentThroughZero(input.line.values));
12
+ return { values, line };
13
+ }
14
+ /**
15
+ * Lays a column chart out at a width: its value axis on the left (and a
16
+ * line's own scale on the right, its zero and grid lines level with the
17
+ * columns'), the columns, the category labels (fewer when they would
18
+ * crowd), the boxed period, the line with its last value beside its axis,
19
+ * and the callouts pinned in rows above the plot. A compact chart (under
20
+ * `chartCompactWidth`) has a shorter plot and draws no callouts: they are
21
+ * listed under it.
22
+ */
23
+ export function columnChartLayout(input) {
24
+ const { width, categories } = input;
25
+ const compact = width < chartCompactWidth;
26
+ const scales = scalesFor(input);
27
+ const tickWords = scales.values.ticks.map(input.formatTick);
28
+ const lineWords = scales.line ? scales.line.ticks.map(input.formatTick) : [];
29
+ const endLabel = input.line?.endLabel ?? null;
30
+ const left = labelGutter(tickWords);
31
+ const end = endGutter({
32
+ width,
33
+ left,
34
+ axisWords: scales.line ? lineWords : [],
35
+ endLabel: scales.line ? endLabel : null,
36
+ });
37
+ const lineWordsShown = end.axisWordsShown;
38
+ const rightGutter = end.gutter;
39
+ const right = Math.max(left + 1, width - rightGutter);
40
+ const band = categories.length > 0 ? (right - left) / categories.length : 0;
41
+ const centre = (index) => left + band * (index + 0.5);
42
+ const placed = compact
43
+ ? { rows: 0, callouts: [] }
44
+ : calloutRows(input.callouts, centre, { left: 0, right: width });
45
+ const top = placed.rows * chartType.lineHeight + chartGap + chartType.lineHeight / 2;
46
+ const plot = {
47
+ left,
48
+ right,
49
+ top,
50
+ bottom: top + chartPlotHeight(compact),
51
+ };
52
+ const columns = placeColumns({ series: input.series, categories: categories.length, mode: input.mode }, scales.values, plot, band);
53
+ const line = input.line && scales.line
54
+ ? input.line.values.map((value, index) => ({
55
+ x: centre(index),
56
+ y: valueY(value, scales.line, plot),
57
+ }))
58
+ : null;
59
+ const last = line?.at(-1);
60
+ const endMark = last && endLabel
61
+ ? { x: right + chartGap, y: last.y, text: endLabel }
62
+ : null;
63
+ const lineTicks = scales.line && lineWordsShown
64
+ ? valueTicks(scales.line, plot, input.formatTick)
65
+ : [];
66
+ return {
67
+ width,
68
+ height: plot.bottom + chartGap + chartType.lineHeight,
69
+ compact,
70
+ plot,
71
+ ticks: valueTicks(scales.values, plot, input.formatTick),
72
+ lineTicks: endMark ? ticksClearOf(lineTicks, endMark.y) : lineTicks,
73
+ columns,
74
+ categoryLabels: labelledPlaces(categories, band).map((index) => ({
75
+ index,
76
+ x: labelInside(centre(index), categories[index], width),
77
+ y: plot.bottom + chartGap + chartType.fontSize,
78
+ text: categories[index],
79
+ })),
80
+ highlight: input.highlight === null || categories.length === 0
81
+ ? null
82
+ : periodBox(left + band * input.highlight, band, plot),
83
+ line: line ? { points: line, end: endMark } : null,
84
+ callouts: placed.callouts.map((callout) => leaderFrom(callout, columns, plot, scales.values)),
85
+ };
86
+ }
87
+ /** A callout's leader starts just above its category's tallest column. */
88
+ function leaderFrom(callout, columns, plot, scale) {
89
+ let top = valueY(0, scale, plot);
90
+ for (const column of columns)
91
+ if (column.category === callout.index)
92
+ top = Math.min(top, column.y);
93
+ return {
94
+ ...callout,
95
+ leaderEnd: Math.max(top - chartInset, callout.leaderStart),
96
+ };
97
+ }
@@ -1,5 +1,5 @@
1
1
  import { type HTMLAttributes } from 'react';
2
- import { type BarChartTone, barChartTones } from '../css/css-chart.js';
2
+ import { type BarChartTone, barChartTones } from '../css/css-bar-chart.js';
3
3
  export type { BarChartTone };
4
4
  export { barChartTones };
5
5
  export type BarChartItem = {
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { barChartFillRatio, barChartScaleMax, formatBarChartValue, } from '@scalewing/tokens';
3
3
  import { forwardRef } from 'react';
4
4
  import { cx } from '../class-names.js';
5
- import { barChartTones } from '../css/css-chart.js';
5
+ import { barChartTones } from '../css/css-bar-chart.js';
6
6
  export { barChartTones };
7
7
  /** The fill's classes: the sign's (which also shapes a diverging bar) and the tone's. */
8
8
  function barChartFillClassName(item) {
@@ -0,0 +1,38 @@
1
+ import { type HTMLAttributes } from 'react';
2
+ import { type ChartFillTone, chartFillTones, type ChartStrokeTone, chartStrokeTones, type ChartTone } from '../css/chart-tones.js';
3
+ import { type ChartLegendSwatch, chartLegendSwatches } from '../css/css-chart-legend.js';
4
+ export type { ChartFillTone, ChartLegendSwatch, ChartStrokeTone, ChartTone };
5
+ export { chartFillTones, chartLegendSwatches, chartStrokeTones };
6
+ /**
7
+ * One item of a legend: its words and the mark they name. A `bar` is a
8
+ * filled mark (a column, a range bar) and takes a fill tone: a categorical
9
+ * colour for a chart's series, or a range bar's statement tone (`danger`
10
+ * is DataBar's light shortage fill: for a shortage only, never for cash
11
+ * leaving). A `line`,
12
+ * a `marker` (a downward triangle) and a `box` (the boxed current period)
13
+ * are drawn strokes and take a stroke tone. Each swatch is the colour of
14
+ * the mark it names in the same tone.
15
+ */
16
+ export type ChartLegendItem = {
17
+ label: string;
18
+ swatch: 'bar';
19
+ tone: ChartFillTone;
20
+ } | {
21
+ label: string;
22
+ swatch: 'line' | 'marker' | 'box';
23
+ tone: ChartStrokeTone;
24
+ };
25
+ export type ChartLegendProps = Omit<HTMLAttributes<HTMLUListElement>, 'children'> & {
26
+ items: readonly ChartLegendItem[];
27
+ };
28
+ /**
29
+ * A chart's key: a list of its marks, each a swatch and its words, in a
30
+ * wrapping row in the caption type. Put it above the chart it names, or
31
+ * above a statement's range bars. The swatches are decorative
32
+ * (`aria-hidden`), so each item reads as its words; the list keeps its
33
+ * list role where a style would drop it. Empty words, or a swatch or tone the
34
+ * item cannot draw, throw a `RangeError`.
35
+ */
36
+ export declare const ChartLegend: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLUListElement>, "children"> & {
37
+ items: readonly ChartLegendItem[];
38
+ } & import("react").RefAttributes<HTMLUListElement>>;