@assistant-ui/react-generative-ui 0.0.21 → 0.0.23
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.
- package/LICENSE +1 -1
- package/dist/JSONGenerativeUI.client.d.ts.map +1 -1
- package/dist/JSONGenerativeUI.client.js +30 -13
- package/dist/JSONGenerativeUI.client.js.map +1 -1
- package/dist/RadioGroupScope.d.ts +6 -0
- package/dist/RadioGroupScope.d.ts.map +1 -0
- package/dist/RadioGroupScope.js +25 -0
- package/dist/RadioGroupScope.js.map +1 -0
- package/dist/a2ui/BindingContext.d.ts +11 -0
- package/dist/a2ui/BindingContext.d.ts.map +1 -0
- package/dist/a2ui/BindingContext.js +12 -0
- package/dist/a2ui/BindingContext.js.map +1 -0
- package/dist/a2ui/PresentRenderer.d.ts +11 -0
- package/dist/a2ui/PresentRenderer.d.ts.map +1 -0
- package/dist/a2ui/PresentRenderer.js +39 -0
- package/dist/a2ui/PresentRenderer.js.map +1 -0
- package/dist/a2ui/convert.d.ts +6 -0
- package/dist/a2ui/convert.d.ts.map +1 -1
- package/dist/a2ui/convert.js +363 -85
- package/dist/a2ui/convert.js.map +1 -1
- package/dist/a2ui/dataModel.d.ts +9 -0
- package/dist/a2ui/dataModel.d.ts.map +1 -0
- package/dist/a2ui/dataModel.js +44 -0
- package/dist/a2ui/dataModel.js.map +1 -0
- package/dist/a2ui/reducer.d.ts +5 -0
- package/dist/a2ui/reducer.d.ts.map +1 -1
- package/dist/a2ui/reducer.js +3 -3
- package/dist/a2ui/reducer.js.map +1 -1
- package/dist/a2ui/types.d.ts +8 -2
- package/dist/a2ui/types.d.ts.map +1 -1
- package/dist/a2ui/types.js.map +1 -1
- package/dist/a2ui/valueFunctions.d.ts +16 -0
- package/dist/a2ui/valueFunctions.d.ts.map +1 -0
- package/dist/a2ui/valueFunctions.js +332 -0
- package/dist/a2ui/valueFunctions.js.map +1 -0
- package/dist/actionRegistry.d.ts +1 -1
- package/dist/actionRegistry.js.map +1 -1
- package/dist/answeredValues.d.ts +16 -0
- package/dist/answeredValues.d.ts.map +1 -0
- package/dist/answeredValues.js +37 -0
- package/dist/answeredValues.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +4 -1
- package/dist/constants.js.map +1 -1
- package/dist/fieldReferences.d.ts +9 -0
- package/dist/fieldReferences.d.ts.map +1 -0
- package/dist/fieldReferences.js +53 -0
- package/dist/fieldReferences.js.map +1 -0
- package/dist/generativeUIToJSX.js +3 -3
- package/dist/generativeUIToJSX.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.server.js +2 -2
- package/dist/index.shared.js +2 -2
- package/dist/ir.d.ts +1 -1
- package/dist/ir.js.map +1 -1
- package/dist/renderGenerativeUI.d.ts.map +1 -1
- package/dist/renderGenerativeUI.js +12 -1
- package/dist/renderGenerativeUI.js.map +1 -1
- package/dist/slack/constants.d.ts +2 -0
- package/dist/slack/constants.d.ts.map +1 -1
- package/dist/slack/constants.js +3 -1
- package/dist/slack/constants.js.map +1 -1
- package/dist/slack/decodeBlockAction.d.ts +3 -1
- package/dist/slack/decodeBlockAction.d.ts.map +1 -1
- package/dist/slack/decodeBlockAction.js +96 -7
- package/dist/slack/decodeBlockAction.js.map +1 -1
- package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
- package/dist/slack/fromSlackBlocks.js +43 -3
- package/dist/slack/fromSlackBlocks.js.map +1 -1
- package/dist/slack/toSlackBlocks.d.ts.map +1 -1
- package/dist/slack/toSlackBlocks.js +244 -20
- package/dist/slack/toSlackBlocks.js.map +1 -1
- package/dist/slack/types.d.ts +24 -1
- package/dist/slack/types.d.ts.map +1 -1
- package/dist/slack.d.ts +1 -1
- package/dist/slack.d.ts.map +1 -1
- package/dist/teams/constants.d.ts +1 -1
- package/dist/teams/constants.d.ts.map +1 -1
- package/dist/teams/constants.js +1 -1
- package/dist/teams/constants.js.map +1 -1
- package/dist/teams/decodeSubmitData.d.ts +4 -0
- package/dist/teams/decodeSubmitData.d.ts.map +1 -1
- package/dist/teams/decodeSubmitData.js +29 -3
- package/dist/teams/decodeSubmitData.js.map +1 -1
- package/dist/teams/temporalId.d.ts +5 -0
- package/dist/teams/temporalId.d.ts.map +1 -0
- package/dist/teams/temporalId.js +28 -0
- package/dist/teams/temporalId.js.map +1 -0
- package/dist/teams/toAdaptiveCard.d.ts +5 -4
- package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
- package/dist/teams/toAdaptiveCard.js +136 -18
- package/dist/teams/toAdaptiveCard.js.map +1 -1
- package/dist/teams/toTeamsAttachments.d.ts.map +1 -1
- package/dist/teams/toTeamsAttachments.js.map +1 -1
- package/dist/teams/types.d.ts +23 -2
- package/dist/teams/types.d.ts.map +1 -1
- package/dist/teams.d.ts +1 -1
- package/dist/teams.d.ts.map +1 -1
- package/dist/temporal.d.ts +33 -0
- package/dist/temporal.d.ts.map +1 -0
- package/dist/temporal.js +152 -0
- package/dist/temporal.js.map +1 -0
- package/dist/vocabulary/alert.d.ts +9 -2
- package/dist/vocabulary/alert.d.ts.map +1 -1
- package/dist/vocabulary/alert.js +116 -19
- package/dist/vocabulary/alert.js.map +1 -1
- package/dist/vocabulary/collectFormValues.d.ts +2 -1
- package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
- package/dist/vocabulary/collectFormValues.js +15 -4
- package/dist/vocabulary/collectFormValues.js.map +1 -1
- package/dist/vocabulary/data.d.ts +22 -5
- package/dist/vocabulary/data.d.ts.map +1 -1
- package/dist/vocabulary/data.js +278 -83
- package/dist/vocabulary/data.js.map +1 -1
- package/dist/vocabulary/dispatch.d.ts +3 -2
- package/dist/vocabulary/dispatch.d.ts.map +1 -1
- package/dist/vocabulary/dispatch.js +33 -4
- package/dist/vocabulary/dispatch.js.map +1 -1
- package/dist/vocabulary/fact.d.ts +8 -1
- package/dist/vocabulary/fact.d.ts.map +1 -1
- package/dist/vocabulary/fact.js +37 -11
- package/dist/vocabulary/fact.js.map +1 -1
- package/dist/vocabulary/form.d.ts.map +1 -1
- package/dist/vocabulary/form.js +3 -3
- package/dist/vocabulary/form.js.map +1 -1
- package/dist/vocabulary/formatValue.d.ts +7 -0
- package/dist/vocabulary/formatValue.d.ts.map +1 -0
- package/dist/vocabulary/formatValue.js +76 -0
- package/dist/vocabulary/formatValue.js.map +1 -0
- package/dist/vocabulary/icon.d.ts +1 -1
- package/dist/vocabulary/interactive.d.ts +143 -8
- package/dist/vocabulary/interactive.d.ts.map +1 -1
- package/dist/vocabulary/interactive.js +458 -107
- package/dist/vocabulary/interactive.js.map +1 -1
- package/dist/vocabulary/layout.d.ts.map +1 -1
- package/dist/vocabulary/layout.js +4 -4
- package/dist/vocabulary/layout.js.map +1 -1
- package/dist/vocabulary/list.js +2 -2
- package/dist/vocabulary/list.js.map +1 -1
- package/dist/vocabulary/text.d.ts +1 -1
- package/package.json +8 -5
- package/src/JSONGenerativeUI.client.tsx +84 -40
- package/src/JSONGenerativeUI.test.tsx +964 -68
- package/src/RadioGroupScope.server.test.tsx +24 -0
- package/src/RadioGroupScope.tsx +34 -0
- package/src/a2ui/BindingContext.ts +26 -0
- package/src/a2ui/PresentRenderer.tsx +83 -0
- package/src/a2ui/convert.test.ts +2054 -229
- package/src/a2ui/convert.ts +665 -134
- package/src/a2ui/dataModel.ts +69 -0
- package/src/a2ui/reducer.test.ts +35 -0
- package/src/a2ui/reducer.ts +5 -4
- package/src/a2ui/types.ts +9 -2
- package/src/a2ui/valueFunctions.test.ts +271 -0
- package/src/a2ui/valueFunctions.ts +523 -0
- package/src/actionRegistry.ts +1 -1
- package/src/answeredValues.test.ts +33 -0
- package/src/answeredValues.tsx +70 -0
- package/src/constants.ts +5 -0
- package/src/fieldReferences.test.ts +66 -0
- package/src/fieldReferences.ts +83 -0
- package/src/generativeUIToJSX.test.ts +18 -0
- package/src/generativeUIToJSX.ts +4 -6
- package/src/index.server.test.ts +106 -0
- package/src/ir.ts +1 -1
- package/src/renderGenerativeUI.test.tsx +14 -5
- package/src/renderGenerativeUI.tsx +30 -1
- package/src/slack/a2uiSlack.test.ts +198 -0
- package/src/slack/constants.ts +3 -0
- package/src/slack/datePicker.test.ts +612 -0
- package/src/slack/decodeBlockAction.test.ts +98 -0
- package/src/slack/decodeBlockAction.ts +182 -16
- package/src/slack/fromSlackBlocks.test.ts +222 -3
- package/src/slack/fromSlackBlocks.ts +82 -2
- package/src/slack/toSlackBlocks.test.ts +682 -3
- package/src/slack/toSlackBlocks.ts +463 -25
- package/src/slack/types.ts +28 -0
- package/src/slack.ts +2 -0
- package/src/teams/constants.ts +1 -1
- package/src/teams/decodeSubmitData.test.ts +249 -0
- package/src/teams/decodeSubmitData.ts +57 -4
- package/src/teams/temporal.test.ts +766 -0
- package/src/teams/temporalId.ts +56 -0
- package/src/teams/toAdaptiveCard.test.ts +219 -0
- package/src/teams/toAdaptiveCard.ts +259 -21
- package/src/teams/toTeamsAttachments.ts +4 -1
- package/src/teams/types.ts +21 -1
- package/src/teams.ts +1 -0
- package/src/temporal.test.ts +319 -0
- package/src/temporal.ts +259 -0
- package/src/vocabulary/alert.test.tsx +288 -7
- package/src/vocabulary/alert.tsx +168 -26
- package/src/vocabulary/collectFormValues.test.ts +225 -1
- package/src/vocabulary/collectFormValues.ts +54 -5
- package/src/vocabulary/data.test.tsx +248 -6
- package/src/vocabulary/data.tsx +398 -100
- package/src/vocabulary/datepicker.dom.test.tsx +1148 -0
- package/src/vocabulary/dispatch.ts +63 -2
- package/src/vocabulary/fact.test.tsx +67 -0
- package/src/vocabulary/fact.tsx +40 -9
- package/src/vocabulary/form.tsx +7 -2
- package/src/vocabulary/formatValue.test.ts +72 -0
- package/src/vocabulary/formatValue.ts +127 -0
- package/src/vocabulary/index.test.ts +2 -0
- package/src/vocabulary/interactive.dispatch.test.tsx +167 -79
- package/src/vocabulary/interactive.dom.test.tsx +1747 -0
- package/src/vocabulary/interactive.test.tsx +60 -4
- package/src/vocabulary/interactive.tsx +910 -155
- package/src/vocabulary/layout.test.tsx +6 -4
- package/src/vocabulary/layout.tsx +13 -2
- package/src/vocabulary/list.tsx +2 -2
package/src/vocabulary/data.tsx
CHANGED
|
@@ -1,18 +1,41 @@
|
|
|
1
|
+
import * as React from "react";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import type { GenerativeUILibrary } from "../types";
|
|
4
|
+
import { formatValue, isNumericTableFormat } from "./formatValue";
|
|
3
5
|
import { toTextContent } from "./toTextContent";
|
|
4
6
|
|
|
5
7
|
const columnSchema = z.object({
|
|
6
8
|
label: z.string().describe("Column header label."),
|
|
9
|
+
align: z.enum(["start", "end"]).optional().describe("Cell alignment."),
|
|
10
|
+
format: z
|
|
11
|
+
.object({
|
|
12
|
+
kind: z
|
|
13
|
+
.enum(["number", "currency", "percent", "date"])
|
|
14
|
+
.describe(
|
|
15
|
+
"Format kind. `percent` takes a fraction, so 0.25 renders as 25%.",
|
|
16
|
+
),
|
|
17
|
+
currency: z.string().optional().describe("Currency code."),
|
|
18
|
+
decimals: z.number().optional().describe("Decimal places."),
|
|
19
|
+
})
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Cell value format."),
|
|
7
22
|
});
|
|
8
23
|
|
|
9
24
|
const cellSchema = z
|
|
10
25
|
.union([z.string(), z.number(), z.boolean()])
|
|
11
26
|
.describe("A cell value.");
|
|
12
27
|
|
|
13
|
-
type TableColumn = {
|
|
28
|
+
type TableColumn = {
|
|
29
|
+
label: string;
|
|
30
|
+
align?: "start" | "end";
|
|
31
|
+
format?: unknown;
|
|
32
|
+
};
|
|
14
33
|
type TableCell = string | number | boolean;
|
|
15
34
|
|
|
35
|
+
type TableSort = { column: number; direction: "ascending" | "descending" };
|
|
36
|
+
|
|
37
|
+
const tableCollator = new Intl.Collator("en-US", { numeric: true });
|
|
38
|
+
|
|
16
39
|
const isTableColumn = (column: unknown): column is TableColumn =>
|
|
17
40
|
column !== null &&
|
|
18
41
|
typeof column === "object" &&
|
|
@@ -24,14 +47,155 @@ const isTableCell = (cell: unknown): cell is TableCell =>
|
|
|
24
47
|
typeof cell === "number" ||
|
|
25
48
|
typeof cell === "boolean";
|
|
26
49
|
|
|
50
|
+
const tableAlignment = (column: unknown): "start" | "end" | undefined => {
|
|
51
|
+
if (!isTableColumn(column)) return undefined;
|
|
52
|
+
if (column.align === "start" || column.align === "end") return column.align;
|
|
53
|
+
return isNumericTableFormat(column.format) ? "end" : undefined;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const compareTableCells = (left: unknown, right: unknown): number => {
|
|
57
|
+
if (
|
|
58
|
+
typeof left === "number" &&
|
|
59
|
+
Number.isFinite(left) &&
|
|
60
|
+
typeof right === "number" &&
|
|
61
|
+
Number.isFinite(right)
|
|
62
|
+
) {
|
|
63
|
+
return left - right;
|
|
64
|
+
}
|
|
65
|
+
if (typeof left === "number" && Number.isFinite(left)) return -1;
|
|
66
|
+
if (typeof right === "number" && Number.isFinite(right)) return 1;
|
|
67
|
+
return tableCollator.compare(
|
|
68
|
+
isTableCell(left) ? String(left) : "",
|
|
69
|
+
isTableCell(right) ? String(right) : "",
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type TableViewProps = {
|
|
74
|
+
columns: unknown;
|
|
75
|
+
rows: unknown;
|
|
76
|
+
children: React.ReactNode;
|
|
77
|
+
sort?: TableSort | undefined;
|
|
78
|
+
onSort?: ((column: number) => void) | undefined;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function TableView({ columns, rows, children, sort, onSort }: TableViewProps) {
|
|
82
|
+
const safeColumns = Array.isArray(columns) ? columns : [];
|
|
83
|
+
const hasColumns = safeColumns.some(isTableColumn);
|
|
84
|
+
const safeRows = Array.isArray(rows) ? rows.filter(Array.isArray) : [];
|
|
85
|
+
const sortedRows =
|
|
86
|
+
sort === undefined
|
|
87
|
+
? safeRows
|
|
88
|
+
: safeRows
|
|
89
|
+
.map((row, index) => ({ row, index }))
|
|
90
|
+
.sort((left, right) => {
|
|
91
|
+
const comparison = compareTableCells(
|
|
92
|
+
left.row[sort.column],
|
|
93
|
+
right.row[sort.column],
|
|
94
|
+
);
|
|
95
|
+
return comparison === 0
|
|
96
|
+
? left.index - right.index
|
|
97
|
+
: sort.direction === "ascending"
|
|
98
|
+
? comparison
|
|
99
|
+
: -comparison;
|
|
100
|
+
})
|
|
101
|
+
.map(({ row }) => row);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<table data-aui="table">
|
|
105
|
+
{hasColumns ? (
|
|
106
|
+
<thead>
|
|
107
|
+
<tr>
|
|
108
|
+
{safeColumns.map((column, i) => {
|
|
109
|
+
const label = isTableColumn(column) ? column.label : "";
|
|
110
|
+
const direction = sort?.column === i ? sort.direction : undefined;
|
|
111
|
+
return (
|
|
112
|
+
<th
|
|
113
|
+
key={i}
|
|
114
|
+
data-aui="table-col"
|
|
115
|
+
data-aui-align={tableAlignment(column)}
|
|
116
|
+
aria-sort={direction}
|
|
117
|
+
>
|
|
118
|
+
{onSort ? (
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
data-aui="table-sort"
|
|
122
|
+
onClick={() => onSort(i)}
|
|
123
|
+
>
|
|
124
|
+
{label}
|
|
125
|
+
{direction ? (
|
|
126
|
+
<span
|
|
127
|
+
aria-hidden="true"
|
|
128
|
+
data-aui="table-sort-direction"
|
|
129
|
+
>
|
|
130
|
+
{direction === "ascending" ? "↑" : "↓"}
|
|
131
|
+
</span>
|
|
132
|
+
) : null}
|
|
133
|
+
</button>
|
|
134
|
+
) : (
|
|
135
|
+
label
|
|
136
|
+
)}
|
|
137
|
+
</th>
|
|
138
|
+
);
|
|
139
|
+
})}
|
|
140
|
+
</tr>
|
|
141
|
+
</thead>
|
|
142
|
+
) : null}
|
|
143
|
+
{safeRows.length ? (
|
|
144
|
+
<tbody>
|
|
145
|
+
{sortedRows.map((row, r) => (
|
|
146
|
+
<tr key={r}>
|
|
147
|
+
{row.map((cell, c) => (
|
|
148
|
+
<td key={c} data-aui-align={tableAlignment(safeColumns[c])}>
|
|
149
|
+
{formatValue(
|
|
150
|
+
cell,
|
|
151
|
+
isTableColumn(safeColumns[c])
|
|
152
|
+
? safeColumns[c].format
|
|
153
|
+
: undefined,
|
|
154
|
+
)}
|
|
155
|
+
</td>
|
|
156
|
+
))}
|
|
157
|
+
</tr>
|
|
158
|
+
))}
|
|
159
|
+
</tbody>
|
|
160
|
+
) : null}
|
|
161
|
+
{children}
|
|
162
|
+
</table>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function SortableTable(props: Omit<TableViewProps, "sort" | "onSort">) {
|
|
167
|
+
const [sort, setSort] = React.useState<TableSort | undefined>();
|
|
168
|
+
return (
|
|
169
|
+
<TableView
|
|
170
|
+
{...props}
|
|
171
|
+
sort={sort}
|
|
172
|
+
onSort={(column) =>
|
|
173
|
+
setSort((current) =>
|
|
174
|
+
current?.column !== column
|
|
175
|
+
? { column, direction: "ascending" }
|
|
176
|
+
: current.direction === "ascending"
|
|
177
|
+
? { column, direction: "descending" }
|
|
178
|
+
: undefined,
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
/>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
27
185
|
const CHART_HEIGHT = 40;
|
|
28
186
|
const CHART_WIDTH = 100;
|
|
29
187
|
|
|
30
188
|
const clampValue = (value: unknown): number =>
|
|
31
|
-
typeof value === "number" && Number.isFinite(value) ?
|
|
189
|
+
typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
190
|
+
|
|
191
|
+
type ChartScale = { min: number; max: number };
|
|
32
192
|
|
|
33
|
-
const yFor = (value: number,
|
|
34
|
-
|
|
193
|
+
const yFor = (value: number, scale: ChartScale): number => {
|
|
194
|
+
const span = scale.max - scale.min;
|
|
195
|
+
return span > 0
|
|
196
|
+
? CHART_HEIGHT - ((value - scale.min) / span) * CHART_HEIGHT
|
|
197
|
+
: CHART_HEIGHT;
|
|
198
|
+
};
|
|
35
199
|
|
|
36
200
|
const pointSchema = z.object({
|
|
37
201
|
label: z.string().optional().describe("Point label."),
|
|
@@ -40,13 +204,24 @@ const pointSchema = z.object({
|
|
|
40
204
|
|
|
41
205
|
const seriesSchema = z.object({
|
|
42
206
|
label: z.string().optional().describe("Series label, shown in the legend."),
|
|
207
|
+
color: z
|
|
208
|
+
.string()
|
|
209
|
+
.optional()
|
|
210
|
+
.describe(
|
|
211
|
+
"Series color. One of `emphasis`, `secondary`, `alpha-70`, `white`, `white-70`, or `white-50`, matching Text's `color` tokens; other values have no visual effect.",
|
|
212
|
+
),
|
|
43
213
|
data: z.array(pointSchema).describe("Data points for this series."),
|
|
44
214
|
});
|
|
45
215
|
|
|
46
|
-
type ChartPoint = { label?: string; value: number };
|
|
47
|
-
type ChartSeriesInput = {
|
|
216
|
+
type ChartPoint = { label?: string | undefined; value: number };
|
|
217
|
+
type ChartSeriesInput = {
|
|
218
|
+
label?: string | undefined;
|
|
219
|
+
color?: string | undefined;
|
|
220
|
+
data?: ChartPoint[] | undefined;
|
|
221
|
+
};
|
|
48
222
|
type NormalizedChartSeries = {
|
|
49
223
|
label: string | undefined;
|
|
224
|
+
color: string | undefined;
|
|
50
225
|
values: number[];
|
|
51
226
|
labels: (string | undefined)[];
|
|
52
227
|
};
|
|
@@ -77,6 +252,7 @@ function normalizeSeries(
|
|
|
77
252
|
}
|
|
78
253
|
return {
|
|
79
254
|
label: typeof s?.label === "string" ? s.label : undefined,
|
|
255
|
+
color: typeof s?.color === "string" ? s.color : undefined,
|
|
80
256
|
values,
|
|
81
257
|
labels,
|
|
82
258
|
};
|
|
@@ -85,23 +261,38 @@ function normalizeSeries(
|
|
|
85
261
|
return { count, series };
|
|
86
262
|
}
|
|
87
263
|
|
|
88
|
-
function
|
|
264
|
+
function valueScale(values: number[]): ChartScale {
|
|
265
|
+
let min = 0;
|
|
266
|
+
let max = 0;
|
|
267
|
+
for (const value of values) {
|
|
268
|
+
min = Math.min(min, value);
|
|
269
|
+
max = Math.max(max, value);
|
|
270
|
+
}
|
|
271
|
+
return { min, max };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function computeScale(
|
|
89
275
|
series: NormalizedChartSeries[],
|
|
90
276
|
count: number,
|
|
91
277
|
stacked: boolean,
|
|
92
|
-
):
|
|
278
|
+
): ChartScale {
|
|
93
279
|
if (stacked) {
|
|
280
|
+
let min = 0;
|
|
94
281
|
let max = 0;
|
|
95
282
|
for (let i = 0; i < count; i++) {
|
|
96
|
-
let
|
|
97
|
-
|
|
98
|
-
|
|
283
|
+
let negative = 0;
|
|
284
|
+
let positive = 0;
|
|
285
|
+
for (const s of series) {
|
|
286
|
+
const value = s.values[i] ?? 0;
|
|
287
|
+
if (value < 0) negative += value;
|
|
288
|
+
else positive += value;
|
|
289
|
+
}
|
|
290
|
+
min = Math.min(min, negative);
|
|
291
|
+
max = Math.max(max, positive);
|
|
99
292
|
}
|
|
100
|
-
return max;
|
|
293
|
+
return { min, max };
|
|
101
294
|
}
|
|
102
|
-
|
|
103
|
-
for (const s of series) for (const v of s.values) max = Math.max(max, v);
|
|
104
|
-
return max;
|
|
295
|
+
return valueScale(series.flatMap((s) => s.values));
|
|
105
296
|
}
|
|
106
297
|
|
|
107
298
|
const NICE_MULTIPLES = [1, 2, 5];
|
|
@@ -119,11 +310,27 @@ function niceMax(max: number): number {
|
|
|
119
310
|
|
|
120
311
|
const TICK_COUNT = 5;
|
|
121
312
|
|
|
122
|
-
|
|
123
|
-
|
|
313
|
+
function axisScale(scale: ChartScale): ChartScale {
|
|
314
|
+
if (scale.min === 0) return { min: 0, max: niceMax(scale.max) };
|
|
315
|
+
if (scale.max === 0) return { min: -niceMax(-scale.min), max: 0 };
|
|
316
|
+
const step = niceMax(Math.max(-scale.min, scale.max) / 2);
|
|
317
|
+
return { min: -2 * step, max: 2 * step };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** `TICK_COUNT` evenly spaced ticks from the top of the scale down to its bottom, for a top-to-bottom y-axis column. */
|
|
321
|
+
function tickValues(scale: ChartScale): number[] {
|
|
124
322
|
const ticks: number[] = [];
|
|
323
|
+
if (scale.min === 0) {
|
|
324
|
+
for (let i = 0; i < TICK_COUNT; i++) {
|
|
325
|
+
ticks.push((scale.max * (TICK_COUNT - 1 - i)) / (TICK_COUNT - 1));
|
|
326
|
+
}
|
|
327
|
+
return ticks;
|
|
328
|
+
}
|
|
125
329
|
for (let i = 0; i < TICK_COUNT; i++) {
|
|
126
|
-
ticks.push(
|
|
330
|
+
ticks.push(
|
|
331
|
+
scale.min +
|
|
332
|
+
((scale.max - scale.min) * (TICK_COUNT - 1 - i)) / (TICK_COUNT - 1),
|
|
333
|
+
);
|
|
127
334
|
}
|
|
128
335
|
return ticks;
|
|
129
336
|
}
|
|
@@ -136,29 +343,84 @@ type ChartVariant = "bar" | "line" | "sparkline" | "area";
|
|
|
136
343
|
|
|
137
344
|
const SERIES_PALETTE_SIZE = 5;
|
|
138
345
|
|
|
346
|
+
const tooltipText = (
|
|
347
|
+
seriesLabel: string | undefined,
|
|
348
|
+
pointLabel: string | undefined,
|
|
349
|
+
index: number,
|
|
350
|
+
value: number,
|
|
351
|
+
) =>
|
|
352
|
+
`${seriesLabel ? `${seriesLabel}, ` : ""}${pointLabel || index + 1}: ${formatTick(value)}`;
|
|
353
|
+
|
|
354
|
+
const seriesColor = (
|
|
355
|
+
series: NormalizedChartSeries,
|
|
356
|
+
seriesCount: number,
|
|
357
|
+
color: unknown,
|
|
358
|
+
) =>
|
|
359
|
+
series.color ??
|
|
360
|
+
(seriesCount === 1 && typeof color === "string" ? color : undefined);
|
|
361
|
+
|
|
362
|
+
const xFor = (index: number, count: number): number =>
|
|
363
|
+
count === 1 ? CHART_WIDTH / 2 : (index / (count - 1)) * CHART_WIDTH;
|
|
364
|
+
|
|
365
|
+
function pointTargets(
|
|
366
|
+
series: NormalizedChartSeries,
|
|
367
|
+
positions: number[],
|
|
368
|
+
count: number,
|
|
369
|
+
scale: ChartScale,
|
|
370
|
+
) {
|
|
371
|
+
return series.values.map((value, i) => (
|
|
372
|
+
<circle
|
|
373
|
+
key={i}
|
|
374
|
+
data-aui="chart-point"
|
|
375
|
+
cx={xFor(i, count)}
|
|
376
|
+
cy={yFor(positions[i] ?? 0, scale)}
|
|
377
|
+
r={4}
|
|
378
|
+
fill="transparent"
|
|
379
|
+
>
|
|
380
|
+
<title>{tooltipText(series.label, series.labels[i], i, value)}</title>
|
|
381
|
+
</circle>
|
|
382
|
+
));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function barGeometry(value: number, start: number, scale: ChartScale) {
|
|
386
|
+
const end = start + value;
|
|
387
|
+
if (scale.min === 0 && start >= 0 && end >= 0) {
|
|
388
|
+
const height = scale.max > 0 ? (value / scale.max) * CHART_HEIGHT : 0;
|
|
389
|
+
const belowHeight = scale.max > 0 ? (start / scale.max) * CHART_HEIGHT : 0;
|
|
390
|
+
return { y: CHART_HEIGHT - belowHeight - height, height };
|
|
391
|
+
}
|
|
392
|
+
const startY = yFor(start, scale);
|
|
393
|
+
const endY = yFor(end, scale);
|
|
394
|
+
return { y: Math.min(startY, endY), height: Math.abs(endY - startY) };
|
|
395
|
+
}
|
|
396
|
+
|
|
139
397
|
function renderSeriesMarks(
|
|
140
398
|
variant: ChartVariant,
|
|
141
399
|
series: NormalizedChartSeries[],
|
|
142
400
|
count: number,
|
|
143
|
-
|
|
401
|
+
scale: ChartScale,
|
|
144
402
|
stacked: boolean,
|
|
403
|
+
color: unknown,
|
|
145
404
|
) {
|
|
405
|
+
const seriesCount = series.length;
|
|
406
|
+
|
|
146
407
|
if (variant === "bar") {
|
|
147
408
|
const slot = count > 0 ? CHART_WIDTH / count : 0;
|
|
148
409
|
const gap = slot * 0.2;
|
|
149
410
|
const groupWidth = slot - gap;
|
|
150
|
-
const seriesCount = series.length;
|
|
151
411
|
const perSeriesWidth =
|
|
152
412
|
stacked || seriesCount <= 1 ? groupWidth : groupWidth / seriesCount;
|
|
153
|
-
const
|
|
413
|
+
const negatives = new Array(count).fill(0) as number[];
|
|
414
|
+
const positives = new Array(count).fill(0) as number[];
|
|
154
415
|
|
|
155
416
|
return series.map((s, seriesIndex) => {
|
|
156
417
|
const marks = s.values.map((value, i) => {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
418
|
+
const start = stacked
|
|
419
|
+
? value < 0
|
|
420
|
+
? (negatives[i] ?? 0)
|
|
421
|
+
: (positives[i] ?? 0)
|
|
422
|
+
: 0;
|
|
423
|
+
const { y, height } = barGeometry(value, start, scale);
|
|
162
424
|
const x =
|
|
163
425
|
stacked || seriesCount <= 1
|
|
164
426
|
? i * slot + gap / 2
|
|
@@ -167,16 +429,20 @@ function renderSeriesMarks(
|
|
|
167
429
|
<rect
|
|
168
430
|
key={i}
|
|
169
431
|
x={x}
|
|
170
|
-
y={
|
|
432
|
+
y={y}
|
|
171
433
|
width={perSeriesWidth}
|
|
172
434
|
height={height}
|
|
173
435
|
fill="currentColor"
|
|
174
|
-
|
|
436
|
+
>
|
|
437
|
+
<title>{tooltipText(s.label, s.labels[i], i, value)}</title>
|
|
438
|
+
</rect>
|
|
175
439
|
);
|
|
176
440
|
});
|
|
177
441
|
if (stacked) {
|
|
178
442
|
for (let i = 0; i < count; i++) {
|
|
179
|
-
|
|
443
|
+
const value = s.values[i] ?? 0;
|
|
444
|
+
if (value < 0) negatives[i] = (negatives[i] ?? 0) + value;
|
|
445
|
+
else positives[i] = (positives[i] ?? 0) + value;
|
|
180
446
|
}
|
|
181
447
|
}
|
|
182
448
|
return (
|
|
@@ -184,6 +450,7 @@ function renderSeriesMarks(
|
|
|
184
450
|
key={seriesIndex}
|
|
185
451
|
data-aui="chart-series"
|
|
186
452
|
data-aui-series={seriesIndex % SERIES_PALETTE_SIZE}
|
|
453
|
+
data-aui-color={seriesColor(s, seriesCount, color)}
|
|
187
454
|
>
|
|
188
455
|
{marks}
|
|
189
456
|
</g>
|
|
@@ -192,28 +459,35 @@ function renderSeriesMarks(
|
|
|
192
459
|
}
|
|
193
460
|
|
|
194
461
|
if (variant === "area") {
|
|
195
|
-
const
|
|
462
|
+
const negatives = new Array(count).fill(0) as number[];
|
|
463
|
+
const positives = new Array(count).fill(0) as number[];
|
|
196
464
|
|
|
197
465
|
return series.map((s, seriesIndex) => {
|
|
198
|
-
const bottoms =
|
|
199
|
-
?
|
|
200
|
-
|
|
466
|
+
const bottoms = s.values.map((value, i) =>
|
|
467
|
+
stacked ? (value < 0 ? (negatives[i] ?? 0) : (positives[i] ?? 0)) : 0,
|
|
468
|
+
);
|
|
469
|
+
const tops = s.values.map((value, i) =>
|
|
470
|
+
stacked ? (bottoms[i] ?? 0) + value : value,
|
|
471
|
+
);
|
|
201
472
|
if (stacked) {
|
|
202
473
|
for (let i = 0; i < count; i++) {
|
|
203
|
-
|
|
474
|
+
const value = s.values[i] ?? 0;
|
|
475
|
+
if (value < 0) negatives[i] = (negatives[i] ?? 0) + value;
|
|
476
|
+
else positives[i] = (positives[i] ?? 0) + value;
|
|
204
477
|
}
|
|
205
478
|
}
|
|
206
|
-
const tops = stacked ? cumulative.slice() : s.values.slice();
|
|
207
479
|
|
|
208
480
|
if (count === 1) {
|
|
209
|
-
const y = yFor(tops[0] ?? 0,
|
|
481
|
+
const y = yFor(tops[0] ?? 0, scale);
|
|
210
482
|
return (
|
|
211
483
|
<g
|
|
212
484
|
key={seriesIndex}
|
|
213
485
|
data-aui="chart-series"
|
|
214
486
|
data-aui-series={seriesIndex % SERIES_PALETTE_SIZE}
|
|
487
|
+
data-aui-color={seriesColor(s, seriesCount, color)}
|
|
215
488
|
>
|
|
216
489
|
<circle cx={CHART_WIDTH / 2} cy={y} r={2} fill="currentColor" />
|
|
490
|
+
{pointTargets(s, tops, count, scale)}
|
|
217
491
|
</g>
|
|
218
492
|
);
|
|
219
493
|
}
|
|
@@ -223,17 +497,16 @@ function renderSeriesMarks(
|
|
|
223
497
|
key={seriesIndex}
|
|
224
498
|
data-aui="chart-series"
|
|
225
499
|
data-aui-series={seriesIndex % SERIES_PALETTE_SIZE}
|
|
500
|
+
data-aui-color={seriesColor(s, seriesCount, color)}
|
|
226
501
|
/>
|
|
227
502
|
);
|
|
228
503
|
}
|
|
229
504
|
|
|
230
505
|
const topPoints = tops.map(
|
|
231
|
-
(v, i) => `${(i
|
|
506
|
+
(v, i) => `${xFor(i, count)},${yFor(v, scale)}`,
|
|
232
507
|
);
|
|
233
508
|
const bottomPoints = bottoms
|
|
234
|
-
.map(
|
|
235
|
-
(v, i) => `${(i / (count - 1)) * CHART_WIDTH},${yFor(v, scaleMax)}`,
|
|
236
|
-
)
|
|
509
|
+
.map((v, i) => `${xFor(i, count)},${yFor(v, scale)}`)
|
|
237
510
|
.reverse();
|
|
238
511
|
|
|
239
512
|
return (
|
|
@@ -241,6 +514,7 @@ function renderSeriesMarks(
|
|
|
241
514
|
key={seriesIndex}
|
|
242
515
|
data-aui="chart-series"
|
|
243
516
|
data-aui-series={seriesIndex % SERIES_PALETTE_SIZE}
|
|
517
|
+
data-aui-color={seriesColor(s, seriesCount, color)}
|
|
244
518
|
>
|
|
245
519
|
<polygon
|
|
246
520
|
points={[...topPoints, ...bottomPoints].join(" ")}
|
|
@@ -253,6 +527,7 @@ function renderSeriesMarks(
|
|
|
253
527
|
stroke="currentColor"
|
|
254
528
|
vectorEffect="non-scaling-stroke"
|
|
255
529
|
/>
|
|
530
|
+
{pointTargets(s, tops, count, scale)}
|
|
256
531
|
</g>
|
|
257
532
|
);
|
|
258
533
|
});
|
|
@@ -264,27 +539,26 @@ function renderSeriesMarks(
|
|
|
264
539
|
key={seriesIndex}
|
|
265
540
|
data-aui="chart-series"
|
|
266
541
|
data-aui-series={seriesIndex % SERIES_PALETTE_SIZE}
|
|
542
|
+
data-aui-color={seriesColor(s, series.length, color)}
|
|
267
543
|
>
|
|
268
544
|
{count === 1 ? (
|
|
269
545
|
<circle
|
|
270
546
|
cx={CHART_WIDTH / 2}
|
|
271
|
-
cy={yFor(s.values[0] ?? 0,
|
|
547
|
+
cy={yFor(s.values[0] ?? 0, scale)}
|
|
272
548
|
r={2}
|
|
273
549
|
fill="currentColor"
|
|
274
550
|
/>
|
|
275
551
|
) : count > 1 ? (
|
|
276
552
|
<polyline
|
|
277
553
|
points={s.values
|
|
278
|
-
.map(
|
|
279
|
-
(v, i) =>
|
|
280
|
-
`${(i / (count - 1)) * CHART_WIDTH},${yFor(v, scaleMax)}`,
|
|
281
|
-
)
|
|
554
|
+
.map((v, i) => `${xFor(i, count)},${yFor(v, scale)}`)
|
|
282
555
|
.join(" ")}
|
|
283
556
|
fill="none"
|
|
284
557
|
stroke="currentColor"
|
|
285
558
|
vectorEffect="non-scaling-stroke"
|
|
286
559
|
/>
|
|
287
560
|
) : null}
|
|
561
|
+
{variant === "line" ? pointTargets(s, s.values, count, scale) : null}
|
|
288
562
|
</g>
|
|
289
563
|
));
|
|
290
564
|
}
|
|
@@ -296,40 +570,18 @@ export const dataVocabulary = {
|
|
|
296
570
|
properties: z.object({
|
|
297
571
|
columns: z.array(columnSchema).optional().describe("Column definitions."),
|
|
298
572
|
rows: z.array(z.array(cellSchema)).optional().describe("Rows of cells."),
|
|
573
|
+
sortable: z.boolean().optional().describe("Allow header sorting."),
|
|
299
574
|
}),
|
|
300
|
-
render: ({ columns, rows, children }) =>
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const safeRows = Array.isArray(rows) ? rows.filter(Array.isArray) : [];
|
|
304
|
-
|
|
305
|
-
return (
|
|
306
|
-
<table data-aui="table">
|
|
307
|
-
{hasColumns ? (
|
|
308
|
-
<thead>
|
|
309
|
-
<tr>
|
|
310
|
-
{safeColumns.map((column, i) => (
|
|
311
|
-
<th key={i} data-aui="table-col">
|
|
312
|
-
{isTableColumn(column) ? column.label : ""}
|
|
313
|
-
</th>
|
|
314
|
-
))}
|
|
315
|
-
</tr>
|
|
316
|
-
</thead>
|
|
317
|
-
) : null}
|
|
318
|
-
{safeRows.length ? (
|
|
319
|
-
<tbody>
|
|
320
|
-
{safeRows.map((row, r) => (
|
|
321
|
-
<tr key={r}>
|
|
322
|
-
{row.map((cell, c) => (
|
|
323
|
-
<td key={c}>{isTableCell(cell) ? String(cell) : ""}</td>
|
|
324
|
-
))}
|
|
325
|
-
</tr>
|
|
326
|
-
))}
|
|
327
|
-
</tbody>
|
|
328
|
-
) : null}
|
|
575
|
+
render: ({ columns, rows, sortable, children }) =>
|
|
576
|
+
sortable === true ? (
|
|
577
|
+
<SortableTable columns={columns} rows={rows}>
|
|
329
578
|
{children}
|
|
330
|
-
</
|
|
331
|
-
)
|
|
332
|
-
|
|
579
|
+
</SortableTable>
|
|
580
|
+
) : (
|
|
581
|
+
<TableView columns={columns} rows={rows}>
|
|
582
|
+
{children}
|
|
583
|
+
</TableView>
|
|
584
|
+
),
|
|
333
585
|
},
|
|
334
586
|
Markdown: {
|
|
335
587
|
description:
|
|
@@ -403,10 +655,19 @@ export const dataVocabulary = {
|
|
|
403
655
|
const points = Array.isArray(data) ? data : [];
|
|
404
656
|
const n = points.length;
|
|
405
657
|
const values = points.map((d) => clampValue(d?.value));
|
|
406
|
-
const
|
|
658
|
+
const scale = valueScale(values);
|
|
659
|
+
const chartSeries: NormalizedChartSeries = {
|
|
660
|
+
label: undefined,
|
|
661
|
+
color: undefined,
|
|
662
|
+
values,
|
|
663
|
+
labels: points.map((point) =>
|
|
664
|
+
typeof point?.label === "string" ? point.label : undefined,
|
|
665
|
+
),
|
|
666
|
+
};
|
|
407
667
|
const slot = n > 0 ? CHART_WIDTH / n : 0;
|
|
408
668
|
const gap = slot * 0.2;
|
|
409
669
|
const barWidth = slot - gap;
|
|
670
|
+
const crossesZero = scale.min < 0 && scale.max > 0;
|
|
410
671
|
|
|
411
672
|
return (
|
|
412
673
|
<svg
|
|
@@ -418,37 +679,60 @@ export const dataVocabulary = {
|
|
|
418
679
|
viewBox={`0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`}
|
|
419
680
|
preserveAspectRatio="none"
|
|
420
681
|
>
|
|
682
|
+
{crossesZero ? (
|
|
683
|
+
<line
|
|
684
|
+
data-aui="chart-zero"
|
|
685
|
+
x1={0}
|
|
686
|
+
x2={CHART_WIDTH}
|
|
687
|
+
y1={yFor(0, scale)}
|
|
688
|
+
y2={yFor(0, scale)}
|
|
689
|
+
stroke="currentColor"
|
|
690
|
+
vectorEffect="non-scaling-stroke"
|
|
691
|
+
/>
|
|
692
|
+
) : null}
|
|
421
693
|
{variant === "bar" ? (
|
|
422
694
|
values.map((v, i) => {
|
|
423
|
-
const
|
|
695
|
+
const { y, height } = barGeometry(v, 0, scale);
|
|
424
696
|
return (
|
|
425
697
|
<rect
|
|
426
698
|
key={i}
|
|
427
699
|
x={i * slot + gap / 2}
|
|
428
|
-
y={
|
|
700
|
+
y={y}
|
|
429
701
|
width={barWidth}
|
|
430
702
|
height={height}
|
|
431
703
|
fill="currentColor"
|
|
432
|
-
|
|
704
|
+
>
|
|
705
|
+
<title>
|
|
706
|
+
{tooltipText(undefined, chartSeries.labels[i], i, v)}
|
|
707
|
+
</title>
|
|
708
|
+
</rect>
|
|
433
709
|
);
|
|
434
710
|
})
|
|
435
711
|
) : n === 1 ? (
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
712
|
+
<>
|
|
713
|
+
<circle
|
|
714
|
+
cx={CHART_WIDTH / 2}
|
|
715
|
+
cy={yFor(values[0] ?? 0, scale)}
|
|
716
|
+
r={2}
|
|
717
|
+
fill="currentColor"
|
|
718
|
+
/>
|
|
719
|
+
{variant === "line"
|
|
720
|
+
? pointTargets(chartSeries, values, n, scale)
|
|
721
|
+
: null}
|
|
722
|
+
</>
|
|
442
723
|
) : n > 1 ? (
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
(v, i) => `${(i
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
724
|
+
<>
|
|
725
|
+
<polyline
|
|
726
|
+
points={values
|
|
727
|
+
.map((v, i) => `${xFor(i, n)},${yFor(v, scale)}`)
|
|
728
|
+
.join(" ")}
|
|
729
|
+
fill="none"
|
|
730
|
+
stroke="currentColor"
|
|
731
|
+
/>
|
|
732
|
+
{variant === "line"
|
|
733
|
+
? pointTargets(chartSeries, values, n, scale)
|
|
734
|
+
: null}
|
|
735
|
+
</>
|
|
452
736
|
) : null}
|
|
453
737
|
</svg>
|
|
454
738
|
);
|
|
@@ -457,15 +741,17 @@ export const dataVocabulary = {
|
|
|
457
741
|
const { count, series: normalized } = normalizeSeries(series, data);
|
|
458
742
|
const stackedScale =
|
|
459
743
|
!!stacked && (variant === "bar" || variant === "area");
|
|
460
|
-
const
|
|
461
|
-
const
|
|
744
|
+
const rawScale = computeScale(normalized, count, stackedScale);
|
|
745
|
+
const scale = showAxis ? axisScale(rawScale) : rawScale;
|
|
462
746
|
const marks = renderSeriesMarks(
|
|
463
747
|
variant,
|
|
464
748
|
normalized,
|
|
465
749
|
count,
|
|
466
|
-
|
|
750
|
+
scale,
|
|
467
751
|
stackedScale,
|
|
752
|
+
color,
|
|
468
753
|
);
|
|
754
|
+
const crossesZero = rawScale.min < 0 && rawScale.max > 0;
|
|
469
755
|
|
|
470
756
|
const svg = (
|
|
471
757
|
<svg
|
|
@@ -477,6 +763,17 @@ export const dataVocabulary = {
|
|
|
477
763
|
viewBox={`0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`}
|
|
478
764
|
preserveAspectRatio="none"
|
|
479
765
|
>
|
|
766
|
+
{crossesZero ? (
|
|
767
|
+
<line
|
|
768
|
+
data-aui="chart-zero"
|
|
769
|
+
x1={0}
|
|
770
|
+
x2={CHART_WIDTH}
|
|
771
|
+
y1={yFor(0, scale)}
|
|
772
|
+
y2={yFor(0, scale)}
|
|
773
|
+
stroke="currentColor"
|
|
774
|
+
vectorEffect="non-scaling-stroke"
|
|
775
|
+
/>
|
|
776
|
+
) : null}
|
|
480
777
|
{marks}
|
|
481
778
|
</svg>
|
|
482
779
|
);
|
|
@@ -487,7 +784,7 @@ export const dataVocabulary = {
|
|
|
487
784
|
<div data-aui="chart-frame">
|
|
488
785
|
{showAxis ? (
|
|
489
786
|
<div data-aui="chart-ticks">
|
|
490
|
-
{tickValues(
|
|
787
|
+
{tickValues(scale).map((tick, i) => (
|
|
491
788
|
<div key={i}>{formatTick(tick)}</div>
|
|
492
789
|
))}
|
|
493
790
|
</div>
|
|
@@ -507,6 +804,7 @@ export const dataVocabulary = {
|
|
|
507
804
|
key={i}
|
|
508
805
|
data-aui="chart-legend-item"
|
|
509
806
|
data-aui-series={i % SERIES_PALETTE_SIZE}
|
|
807
|
+
data-aui-color={seriesColor(s, normalized.length, color)}
|
|
510
808
|
>
|
|
511
809
|
<span data-aui="chart-legend-swatch" />
|
|
512
810
|
{s.label ?? `Series ${i + 1}`}
|