@rowsncolumns/charts 28.1.14 → 28.1.16

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 (99) hide show
  1. package/dist/cjs/basic-chart/basic-chart.d.ts.map +1 -1
  2. package/dist/cjs/basic-chart/basic-chart.js +312 -271
  3. package/dist/cjs/components/editors/chart-editor-capabilities.d.ts +48 -0
  4. package/dist/cjs/components/editors/chart-editor-capabilities.d.ts.map +1 -0
  5. package/dist/cjs/components/editors/chart-editor-capabilities.js +62 -0
  6. package/dist/cjs/components/editors/chart-editor.d.ts.map +1 -1
  7. package/dist/cjs/components/editors/chart-editor.js +200 -534
  8. package/dist/cjs/components/editors/fields/axis-options-fields.d.ts +16 -0
  9. package/dist/cjs/components/editors/fields/axis-options-fields.d.ts.map +1 -0
  10. package/dist/cjs/components/editors/fields/axis-options-fields.js +31 -0
  11. package/dist/cjs/components/editors/fields/chart-type-options.d.ts +25 -0
  12. package/dist/cjs/components/editors/fields/chart-type-options.d.ts.map +1 -0
  13. package/dist/cjs/components/editors/fields/chart-type-options.js +170 -0
  14. package/dist/cjs/components/editors/fields/data-labels-fields.d.ts +15 -0
  15. package/dist/cjs/components/editors/fields/data-labels-fields.d.ts.map +1 -0
  16. package/dist/cjs/components/editors/fields/data-labels-fields.js +57 -0
  17. package/dist/cjs/components/editors/fields/primitives.d.ts +54 -0
  18. package/dist/cjs/components/editors/fields/primitives.d.ts.map +1 -0
  19. package/dist/cjs/components/editors/fields/primitives.js +52 -0
  20. package/dist/cjs/components/editors/fields/series-format-fields.d.ts +17 -0
  21. package/dist/cjs/components/editors/fields/series-format-fields.d.ts.map +1 -0
  22. package/dist/cjs/components/editors/fields/series-format-fields.js +165 -0
  23. package/dist/cjs/components/editors/fields/series-name-field.d.ts +16 -0
  24. package/dist/cjs/components/editors/fields/series-name-field.d.ts.map +1 -0
  25. package/dist/cjs/components/editors/fields/series-name-field.js +36 -0
  26. package/dist/cjs/components/editors/fields/text-format-fields.d.ts +20 -0
  27. package/dist/cjs/components/editors/fields/text-format-fields.d.ts.map +1 -0
  28. package/dist/cjs/components/editors/fields/text-format-fields.js +46 -0
  29. package/dist/cjs/config.d.ts.map +1 -1
  30. package/dist/cjs/config.js +3 -0
  31. package/dist/cjs/interface/create-chart.d.ts +3 -2
  32. package/dist/cjs/interface/create-chart.d.ts.map +1 -1
  33. package/dist/cjs/interface/create-chart.js +7 -0
  34. package/dist/cjs/plotly/basic-chart/basic-chart.d.ts.map +1 -1
  35. package/dist/cjs/plotly/basic-chart/basic-chart.js +255 -218
  36. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  37. package/dist/cjs/utils/chart-orientation.d.ts.map +1 -1
  38. package/dist/cjs/utils/chart-orientation.js +24 -12
  39. package/dist/cjs/utils/chart-spec-utils.d.ts.map +1 -1
  40. package/dist/cjs/utils/chart-spec-utils.js +120 -21
  41. package/dist/cjs/utils/echarts-overlays.d.ts +2 -0
  42. package/dist/cjs/utils/echarts-overlays.d.ts.map +1 -1
  43. package/dist/cjs/utils/echarts-overlays.js +25 -12
  44. package/dist/cjs/utils/series-name.d.ts +12 -0
  45. package/dist/cjs/utils/series-name.d.ts.map +1 -0
  46. package/dist/cjs/utils/series-name.js +26 -0
  47. package/dist/cjs/utils/trendline.d.ts +9 -0
  48. package/dist/cjs/utils/trendline.d.ts.map +1 -1
  49. package/dist/cjs/utils/trendline.js +16 -1
  50. package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
  51. package/dist/esm/basic-chart/basic-chart.js +80 -38
  52. package/dist/esm/components/editors/chart-editor-capabilities.d.ts +48 -0
  53. package/dist/esm/components/editors/chart-editor-capabilities.d.ts.map +1 -0
  54. package/dist/esm/components/editors/chart-editor-capabilities.js +58 -0
  55. package/dist/esm/components/editors/chart-editor.d.ts.map +1 -1
  56. package/dist/esm/components/editors/chart-editor.js +166 -448
  57. package/dist/esm/components/editors/fields/axis-options-fields.d.ts +16 -0
  58. package/dist/esm/components/editors/fields/axis-options-fields.d.ts.map +1 -0
  59. package/dist/esm/components/editors/fields/axis-options-fields.js +22 -0
  60. package/dist/esm/components/editors/fields/chart-type-options.d.ts +25 -0
  61. package/dist/esm/components/editors/fields/chart-type-options.d.ts.map +1 -0
  62. package/dist/esm/components/editors/fields/chart-type-options.js +151 -0
  63. package/dist/esm/components/editors/fields/data-labels-fields.d.ts +15 -0
  64. package/dist/esm/components/editors/fields/data-labels-fields.d.ts.map +1 -0
  65. package/dist/esm/components/editors/fields/data-labels-fields.js +47 -0
  66. package/dist/esm/components/editors/fields/primitives.d.ts +54 -0
  67. package/dist/esm/components/editors/fields/primitives.d.ts.map +1 -0
  68. package/dist/esm/components/editors/fields/primitives.js +26 -0
  69. package/dist/esm/components/editors/fields/series-format-fields.d.ts +17 -0
  70. package/dist/esm/components/editors/fields/series-format-fields.d.ts.map +1 -0
  71. package/dist/esm/components/editors/fields/series-format-fields.js +143 -0
  72. package/dist/esm/components/editors/fields/series-name-field.d.ts +16 -0
  73. package/dist/esm/components/editors/fields/series-name-field.d.ts.map +1 -0
  74. package/dist/esm/components/editors/fields/series-name-field.js +31 -0
  75. package/dist/esm/components/editors/fields/text-format-fields.d.ts +20 -0
  76. package/dist/esm/components/editors/fields/text-format-fields.d.ts.map +1 -0
  77. package/dist/esm/components/editors/fields/text-format-fields.js +34 -0
  78. package/dist/esm/config.d.ts.map +1 -1
  79. package/dist/esm/config.js +3 -0
  80. package/dist/esm/interface/create-chart.d.ts +3 -2
  81. package/dist/esm/interface/create-chart.d.ts.map +1 -1
  82. package/dist/esm/interface/create-chart.js +8 -1
  83. package/dist/esm/plotly/basic-chart/basic-chart.d.ts.map +1 -1
  84. package/dist/esm/plotly/basic-chart/basic-chart.js +52 -30
  85. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  86. package/dist/esm/utils/chart-orientation.d.ts.map +1 -1
  87. package/dist/esm/utils/chart-orientation.js +20 -0
  88. package/dist/esm/utils/chart-spec-utils.d.ts.map +1 -1
  89. package/dist/esm/utils/chart-spec-utils.js +95 -8
  90. package/dist/esm/utils/echarts-overlays.d.ts +2 -0
  91. package/dist/esm/utils/echarts-overlays.d.ts.map +1 -1
  92. package/dist/esm/utils/echarts-overlays.js +15 -1
  93. package/dist/esm/utils/series-name.d.ts +12 -0
  94. package/dist/esm/utils/series-name.d.ts.map +1 -0
  95. package/dist/esm/utils/series-name.js +24 -0
  96. package/dist/esm/utils/trendline.d.ts +9 -0
  97. package/dist/esm/utils/trendline.d.ts.map +1 -1
  98. package/dist/esm/utils/trendline.js +14 -0
  99. package/package.json +3 -3
@@ -1,17 +1,24 @@
1
1
  import { BarChartIcon, ChevronRightIcon, CrossCircledIcon, TriangleDownIcon, } from "@rowsncolumns/icons";
2
2
  import { defaultCellCoords } from "@rowsncolumns/spreadsheet-state";
3
- import { Button, DropdownLeftSlot, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownRightSlot, IconButton, Input, Stack, Tabs, TabsList, TabsTrigger, TabsContent, } from "@rowsncolumns/ui";
3
+ import { Button, DropdownLeftSlot, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownRightSlot, IconButton, Input, Stack, Tabs, TabsContent, TabsList, TabsTrigger, } from "@rowsncolumns/ui";
4
4
  import { DropdownMenu } from "@rowsncolumns/ui";
5
5
  import { DropdownMenuTrigger } from "@rowsncolumns/ui";
6
+ import { uuid } from "@rowsncolumns/utils";
6
7
  import { useFormik } from "formik";
7
8
  import React, { useMemo } from "react";
8
9
  import { useSetChartEditorDialogState } from "../../atoms/editing-chart.atom";
9
- import { ChartRangeInput } from "./chart-range-input";
10
10
  import { CHART_TYPES, getChartPresetLabel } from "../../config";
11
- import { uuid } from "@rowsncolumns/utils";
12
11
  import { canSwitchRowColumn, inferSeriesOrientation, switchRowColumn, } from "../../utils/chart-orientation";
13
12
  import { isBasicChartSpec, isBubbleChartSpec, isHierarchyChartSpec, mergeChartSpec, } from "../../utils/chart-spec-utils";
14
13
  import { applyChartColorPalette, applyChartQuickLayout, applyChartStyle, CHART_COLOR_PALETTES, CHART_QUICK_LAYOUTS, CHART_STYLES, resetChartToMatchStyle, } from "../../utils/chart-styles";
14
+ import { getChartEditorCapabilities } from "./chart-editor-capabilities";
15
+ import { ChartRangeInput } from "./chart-range-input";
16
+ import { AxisOptionsFields } from "./fields/axis-options-fields";
17
+ import { chartTypeHasOptions, ChartTypeOptionsFields, } from "./fields/chart-type-options";
18
+ import { DataLabelsFields } from "./fields/data-labels-fields";
19
+ import { SeriesFormatFields } from "./fields/series-format-fields";
20
+ import { SeriesNameField } from "./fields/series-name-field";
21
+ import { TextFormatFields } from "./fields/text-format-fields";
15
22
  const AGGREGATE_OPTIONS = [
16
23
  { label: "Sum", value: "SUM" },
17
24
  { label: "Average", value: "AVERAGE" },
@@ -84,58 +91,118 @@ export const ChartEditor = ({ chart, sheetId, onSubmit, onRequestMoveChart, getF
84
91
  resetForm();
85
92
  closeDialog();
86
93
  };
87
- const basicSpec = isBasicChartSpec(values.spec) ? values.spec : undefined;
88
- const bubbleSpec = isBubbleChartSpec(values.spec) ? values.spec : undefined;
89
- const treemapSpec = isHierarchyChartSpec(values.spec)
90
- ? values.spec
94
+ const spec = values.spec;
95
+ const basicSpec = isBasicChartSpec(spec) ? spec : undefined;
96
+ const bubbleSpec = isBubbleChartSpec(spec) ? spec : undefined;
97
+ const treemapSpec = isHierarchyChartSpec(spec)
98
+ ? spec
91
99
  : undefined;
92
- // Cartesian (XY) chart types have value/category axes; circular and
93
- // polar/derived types (pie/doughnut/radar) do not, so the axis-scale
94
- // controls are gated to these.
95
- const isCartesianBasic = basicSpec
96
- ? basicSpec.chartType !== "pie" &&
97
- basicSpec.chartType !== "doughnut" &&
98
- basicSpec.chartType !== "radar"
99
- : false;
100
- // Bar/column-only features. gapWidth is consumed by both renderers
101
- // (echarts barCategoryGap / plotly bargap); overlap by echarts
102
- // (barGap).
103
- const isBarOrColumnBasic = basicSpec
104
- ? basicSpec.chartType === "bar" || basicSpec.chartType === "column"
105
- : false;
106
- // Customize → "Chart options": each control names the chart types it
107
- // applies to and the section renders when any of them does, so a chart type
108
- // is never left out by a separately maintained list (pie was, until #783).
109
- const chartOptionControls = {
110
- binCount: basicSpec?.chartType === "histogram",
111
- radarStyle: basicSpec?.chartType === "radar",
112
- stockVariant: basicSpec?.chartType === "stock",
113
- doughnutHoleSize: basicSpec?.chartType === "doughnut",
114
- explosion: basicSpec?.chartType === "pie" || basicSpec?.chartType === "doughnut",
115
- gapWidth: isBarOrColumnBasic,
116
- overlap: isBarOrColumnBasic,
117
- };
118
- const showChartOptions = Object.values(chartOptionControls).some(Boolean);
119
- // Check aggregate
120
- const isAggregateType = basicSpec
121
- ? basicSpec.series.some((serie) => serie.aggregateType)
122
- : false;
123
- const bubbleAggregateEnabled = bubbleSpec
124
- ? bubbleSpec.series.some((serie) => serie.aggregateType)
125
- : false;
126
- const renderChartDataSection = (label, field, data, helperText) => {
127
- const sections = data.length > 0 ? data : [{ sources: [] }];
100
+ // Which controls this chart type has — Excel's rule set, so an option is
101
+ // only offered where the chart (and the renderers) honour it (#1102).
102
+ const caps = getChartEditorCapabilities(spec);
103
+ // The series of a basic or bubble chart. Guarded: a spec converted from
104
+ // another category used to arrive without `series` and crash the editor
105
+ // on `series.some` (#1101).
106
+ const seriesList = basicSpec
107
+ ? basicSpec.series ?? []
108
+ : bubbleSpec
109
+ ? bubbleSpec.series ?? []
110
+ : [];
111
+ const domainsList = basicSpec
112
+ ? basicSpec.domains ?? []
113
+ : bubbleSpec
114
+ ? bubbleSpec.domains ?? []
115
+ : [];
116
+ const isAggregateType = seriesList.some((serie) => serie.aggregateType);
117
+ const setSeries = (index, patch) => setFieldValue(`spec.series[${index}]`, { ...seriesList[index], ...patch });
118
+ const hasSeriesFormat = caps.hasSecondaryAxis ||
119
+ caps.hasTrendlines ||
120
+ caps.hasErrorBars ||
121
+ caps.hasSeriesFill ||
122
+ caps.hasInvertIfNegative ||
123
+ caps.hasMarkers ||
124
+ caps.hasPointColors ||
125
+ caps.hasDataLabels;
126
+ const categoryLevels = basicSpec?.categoryLevels ?? [];
127
+ const hasTypeOptions = chartTypeHasOptions(spec, caps);
128
+ const renderChartDataSection = (label, field, data, helperText, levels) => {
129
+ // A levelled section (parent labels, category levels) starts empty and
130
+ // grows with "Add level"; a plain section always shows one input.
131
+ const sections = data.length > 0 ? data : levels ? [] : [{ sources: [] }];
128
132
  return (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-3" },
129
133
  React.createElement("label", { className: "font-system text-sm" }, label),
130
134
  helperText ? (React.createElement("p", { className: "m-0 text-xs text-rnc-text-weak" }, helperText)) : null,
131
135
  sections.map((domain, i) => {
132
136
  const sources = domain.sources.length > 0 ? domain.sources : [undefined];
133
- return sources.map((source, idx) => (React.createElement("div", { key: `${values.chartId}-${label}-${sections.length}-${i}-${idx}` },
137
+ return sources.map((source, idx) => (React.createElement("div", { key: `${values.chartId}-${label}-${sections.length}-${i}-${idx}`, className: "flex items-center gap-1" },
134
138
  React.createElement(ChartRangeInput, { sheetId: sheetId, range: source, onChangeRange: (range) => {
135
139
  setFieldValue(`spec.${field}[${i}].sources[${idx}]`, range);
136
- }, required: true, placeholder: "Select a range", className: "mb-0 flex-1" }))));
137
- })));
140
+ }, required: true, placeholder: "Select a range", className: "mb-0 flex-1" }),
141
+ levels && data.length > 1 ? (React.createElement(IconButton, { type: "button", "aria-label": `${levels.removeLabel} ${i + 1}`, title: levels.removeLabel, onClick: () => setFieldValue(`spec.${field}`, data.filter((_, levelIndex) => levelIndex !== i)) },
142
+ React.createElement(CrossCircledIcon, null))) : null)));
143
+ }),
144
+ levels ? (React.createElement("div", null,
145
+ React.createElement(Button, { variant: "link", type: "button", className: "cursor-pointer", onClick: () => setFieldValue(`spec.${field}`, [...data, { sources: [] }]) }, levels.addLabel))) : null));
138
146
  };
147
+ const renderSeriesCard = (entry, i) => {
148
+ const sources = entry.sources?.length > 0 ? entry.sources : [undefined];
149
+ return sources.map((source, idx) => (React.createElement("div", { className: "border border-rnc-border border-solid p-2 relative", key: `${values.chartId}-series-${seriesList.length}-${i}-${idx}` },
150
+ React.createElement("div", { className: "flex gap-2 items-start flex-wrap" },
151
+ React.createElement(SeriesNameField, { sheetId: sheetId, series: entry, getFormattedValue: getFormattedValue, onChange: (patch) => setSeries(i, patch) }),
152
+ React.createElement(Stack, { direction: "vertical", className: "gap-1 flex-1" },
153
+ React.createElement("label", { className: "font-system text-sm" }, "Y values"),
154
+ React.createElement(ChartRangeInput, { sheetId: sheetId, range: source, onChangeRange: (range) => {
155
+ setFieldValue(`spec.series[${i}].sources[${idx}]`, range);
156
+ }, required: true, placeholder: "Select a range", className: "mb-0" })),
157
+ React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2 w-20" },
158
+ React.createElement("label", { className: "font-system text-sm" }, "Color"),
159
+ React.createElement("div", { className: "flex items-center gap-1" },
160
+ React.createElement("input", { type: "color", value: entry.color ?? "#4285F4", "aria-label": `Series ${i + 1} color`, onChange: (e) => setSeries(i, { color: e.target.value }), className: "h-6 w-8 p-0 border border-rnc-border rounded-sm bg-transparent cursor-pointer" }),
161
+ entry.color ? (React.createElement(IconButton, { type: "button", "aria-label": `Clear series ${i + 1} color`, title: "Automatic color", onClick: () => setSeries(i, { color: undefined }) },
162
+ React.createElement(CrossCircledIcon, null))) : null)),
163
+ isAggregateType ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2 w-32" },
164
+ React.createElement("label", { className: "font-system text-sm" }, "Aggregate type"),
165
+ React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent", value: entry.aggregateType ?? "", onChange: (event) => {
166
+ const aggregateType = event.target.value;
167
+ setSeries(i, { aggregateType: aggregateType || undefined });
168
+ } },
169
+ React.createElement("option", { value: "" }, "None"),
170
+ AGGREGATE_OPTIONS.map((option) => (React.createElement("option", { key: option.value, value: option.value }, option.label)))))) : null,
171
+ caps.hasSeriesChartType ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2 w-28" },
172
+ React.createElement("label", { className: "font-system text-sm" }, "Series type"),
173
+ React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent", value: entry.seriesChartType ?? "", "aria-label": `Series ${i + 1} type`, onChange: (event) => setSeries(i, {
174
+ seriesChartType: (event.target.value || undefined),
175
+ }) },
176
+ React.createElement("option", { value: "" }, "Default"),
177
+ React.createElement("option", { value: "column" }, "Column"),
178
+ React.createElement("option", { value: "bar" }, "Bar"),
179
+ React.createElement("option", { value: "line" }, "Line"),
180
+ React.createElement("option", { value: "area" }, "Area")))) : null),
181
+ hasSeriesFormat ? (React.createElement("details", { className: "mt-2 group/fmt" },
182
+ React.createElement("summary", { className: "cursor-pointer list-none py-1 font-system text-xs text-rnc-muted-foreground select-none [&::-webkit-details-marker]:hidden flex items-center gap-1" },
183
+ React.createElement(ChevronRightIcon, { className: "transition-transform group-open/fmt:rotate-90" }),
184
+ "Format"),
185
+ React.createElement(SeriesFormatFields, { sheetId: sheetId, series: entry, capabilities: caps, onChange: (patch) => setSeries(i, patch) }))) : null,
186
+ React.createElement(IconButton, { className: "absolute right-1 top-1", type: "button", "aria-label": `Remove series ${i + 1}`, onClick: () => setFieldValue("spec.series", seriesList.filter((_, seriesIndex) => seriesIndex !== i)) },
187
+ React.createElement(CrossCircledIcon, null)))));
188
+ };
189
+ const renderSeriesList = () => (React.createElement(Stack, { direction: "vertical", className: "gap-2 pb-2" },
190
+ React.createElement("label", { className: "font-system text-sm" }, "Legend entries (series)"),
191
+ seriesList.map(renderSeriesCard),
192
+ React.createElement("div", { className: "py-1" },
193
+ React.createElement(Button, { variant: "link", type: "button", className: "cursor-pointer", onClick: () => {
194
+ const lastSeries = seriesList[seriesList.length - 1];
195
+ setFieldValue("spec.series", [
196
+ ...seriesList,
197
+ {
198
+ sources: lastSeries
199
+ ? lastSeries.sources
200
+ : spec.dataRange
201
+ ? [spec.dataRange]
202
+ : [],
203
+ },
204
+ ]);
205
+ } }, "Add series"))));
139
206
  return (React.createElement("form", { onSubmit: handleSubmit, className: "flex flex-col" },
140
207
  React.createElement(Tabs, { defaultValue: "setup", className: "w-full" },
141
208
  React.createElement(TabsList, { className: "grid-cols-2" },
@@ -187,252 +254,48 @@ export const ChartEditor = ({ chart, sheetId, onSubmit, onRequestMoveChart, getF
187
254
  basicSpec ? (React.createElement(React.Fragment, null,
188
255
  React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
189
256
  React.createElement("label", { className: "font-system text-sm" }, "Horizontal (category) axis labels"),
190
- (basicSpec.domains ?? []).map((domain, i) => {
191
- return domain.sources.map((source, idx) => {
192
- return (React.createElement(ChartRangeInput, { key: `${values.chartId}-domains-${basicSpec.domains.length}-${i}-${idx}`, sheetId: sheetId, range: source, onChangeRange: (range) => {
257
+ domainsList.map((domain, i) => {
258
+ return (domain.sources ?? []).map((source, idx) => {
259
+ return (React.createElement(ChartRangeInput, { key: `${values.chartId}-domains-${domainsList.length}-${i}-${idx}`, sheetId: sheetId, range: source, onChangeRange: (range) => {
193
260
  setFieldValue(`spec.domains[${i}].sources[${idx}]`, range);
194
261
  }, required: true, placeholder: "Select a range", className: "mb-0" }));
195
262
  });
196
263
  })),
197
- React.createElement("label", { className: "font-system text-sm flex items-center gap-1 pb-2" },
198
- React.createElement("input", { type: "checkbox", checked: isAggregateType, name: "showColumnStripes", onChange: (e) => {
199
- const nextSeries = basicSpec.series.map((serie) => ({
200
- ...serie,
201
- aggregateType: e.target.checked ? "SUM" : undefined,
202
- }));
203
- setFieldValue("spec.series", nextSeries);
204
- } }),
205
- "Aggregate"),
206
- React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
207
- React.createElement("label", { className: "font-system text-sm" }, "Legend entries (series)"),
208
- basicSpec.series.map((domain, i) => {
209
- return domain.sources.map((source, idx) => {
210
- return (React.createElement("div", { className: "border border-rnc-border border-solid p-2 relative", key: `${values.chartId}-series-${basicSpec.series.length}-${i}-${idx}` },
211
- React.createElement("div", { className: "flex gap-2 " },
212
- React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
213
- React.createElement("label", { className: "font-system text-sm" }, "Name"),
214
- React.createElement(Input, { value: domain.dataLabel ?? "", type: "text", name: "fromValue", onChange: (e) => setFieldValue(`spec.series[${i}].dataLabel`, e.target.value), className: "text-xs", placeholder: "Enter value" })),
215
- React.createElement(Stack, { direction: "vertical", className: "gap-1 flex-1" },
216
- React.createElement("label", { className: "font-system text-sm" }, "Y values"),
217
- React.createElement(ChartRangeInput, { sheetId: sheetId, range: source, onChangeRange: (range) => {
218
- setFieldValue(`spec.series[${i}].sources[${idx}]`, range);
219
- }, required: true, placeholder: "Select a range", className: "mb-0" })),
220
- React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2 w-20" },
221
- React.createElement("label", { className: "font-system text-sm" }, "Color"),
222
- React.createElement("div", { className: "flex items-center gap-1" },
223
- React.createElement("input", { type: "color", value: domain.color ?? "#4285F4", onChange: (e) => setFieldValue(`spec.series[${i}].color`, e.target.value), className: "h-6 w-8 p-0 border border-rnc-border rounded-sm bg-transparent cursor-pointer", "aria-label": "Series color" }),
224
- domain.color ? (React.createElement(IconButton, { type: "button", onClick: () => setFieldValue(`spec.series[${i}].color`, undefined), "aria-label": "Clear series color" },
225
- React.createElement(CrossCircledIcon, null))) : null)),
226
- isAggregateType ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2 w-32" },
227
- React.createElement("label", { className: "font-system text-sm" }, "Aggregate type"),
228
- React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent", value: domain.aggregateType ?? "", onChange: (event) => {
229
- const aggregateType = event.target.value;
230
- setFieldValue(`spec.series[${i}].aggregateType`, aggregateType || undefined);
231
- } },
232
- React.createElement("option", { value: "" }, "None"),
233
- AGGREGATE_OPTIONS.map((option) => (React.createElement("option", { key: option.value, value: option.value }, option.label)))))) : null,
234
- basicSpec.chartType === "combo" ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2 w-32" },
235
- React.createElement("label", { className: "font-system text-sm" }, "Series type"),
236
- React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent", value: domain.seriesChartType ?? "bar", onChange: (event) => {
237
- setFieldValue(`spec.series[${i}].seriesChartType`, event.target.value || undefined);
238
- } },
239
- React.createElement("option", { value: "bar" }, "Bar"),
240
- React.createElement("option", { value: "column" }, "Column"),
241
- React.createElement("option", { value: "line" }, "Line"),
242
- React.createElement("option", { value: "area" }, "Area")))) : null),
243
- basicSpec.chartType !== "pie" &&
244
- basicSpec.chartType !== "doughnut" &&
245
- basicSpec.chartType !== "radar" &&
246
- basicSpec.chartType !== "stock" ? (React.createElement("details", { className: "mt-2 group/fmt" },
247
- React.createElement("summary", { className: "cursor-pointer list-none py-1 font-system text-xs text-rnc-muted-foreground select-none [&::-webkit-details-marker]:hidden flex items-center gap-1" },
248
- React.createElement(ChevronRightIcon, { className: "transition-transform group-open/fmt:rotate-90" }),
249
- "Format"),
250
- React.createElement("div", { className: "flex flex-col gap-2 pt-2 border border-rnc-border border-solid mt-2" },
251
- React.createElement("label", { className: "font-system text-sm flex items-center gap-1" },
252
- React.createElement("input", { type: "checkbox", checked: !!domain.useSecondaryAxis, onChange: (e) => setFieldValue(`spec.series[${i}].useSecondaryAxis`, e.target.checked || undefined) }),
253
- "Plot on secondary axis"),
254
- React.createElement("div", { className: "flex items-center gap-2" },
255
- React.createElement("label", { className: "font-system text-sm flex items-center gap-1" },
256
- React.createElement("input", { type: "checkbox", checked: (domain.trendlines?.length ?? 0) > 0, onChange: (e) => setFieldValue(`spec.series[${i}].trendlines`, e.target.checked
257
- ? [
258
- domain.trendlines?.[0] ?? {
259
- type: "linear",
260
- },
261
- ]
262
- : undefined) }),
263
- "Trendline"),
264
- (domain.trendlines?.length ?? 0) > 0 ? (React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent", value: domain.trendlines?.[0]?.type ?? "linear", onChange: (event) => setFieldValue(`spec.series[${i}].trendlines[0]`, {
265
- ...(domain.trendlines?.[0] ?? {}),
266
- type: event.target.value,
267
- ...(event.target.value === "poly"
268
- ? {
269
- order: domain.trendlines?.[0]
270
- ?.order ?? 2,
271
- }
272
- : {}),
273
- }) },
274
- React.createElement("option", { value: "linear" }, "Linear"),
275
- React.createElement("option", { value: "poly" }, "Polynomial"),
276
- React.createElement("option", { value: "exp" }, "Exponential"),
277
- React.createElement("option", { value: "log" }, "Logarithmic"))) : null),
278
- React.createElement("div", { className: "flex items-center gap-2 flex-wrap" },
279
- React.createElement("label", { className: "font-system text-sm flex items-center gap-1" },
280
- React.createElement("input", { type: "checkbox", checked: !!domain.errorBars, onChange: (e) => setFieldValue(`spec.series[${i}].errorBars`, e.target.checked
281
- ? domain.errorBars ?? {
282
- valueType: "percentage",
283
- value: 5,
284
- }
285
- : undefined) }),
286
- "Error bars"),
287
- domain.errorBars ? (React.createElement(React.Fragment, null,
288
- React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent", value: domain.errorBars?.valueType ??
289
- "percentage", onChange: (event) => setFieldValue(`spec.series[${i}].errorBars`, {
290
- ...(domain.errorBars ?? {}),
291
- valueType: event.target.value,
292
- }) },
293
- React.createElement("option", { value: "fixedVal" }, "Fixed"),
294
- React.createElement("option", { value: "percentage" }, "Percentage"),
295
- React.createElement("option", { value: "stdDev" }, "Std deviation"),
296
- React.createElement("option", { value: "stdErr" }, "Std error")),
297
- domain.errorBars?.valueType !==
298
- "stdErr" ? (React.createElement(Input, { type: "number", value: domain.errorBars?.value ?? "", onChange: (e) => setFieldValue(`spec.series[${i}].errorBars`, {
299
- ...(domain.errorBars ?? {}),
300
- value: e.target.value === ""
301
- ? undefined
302
- : Number(e.target.value),
303
- }), className: "text-xs w-20", placeholder: "Value" })) : null)) : null),
304
- React.createElement("label", { className: "font-system text-sm flex items-center gap-1" },
305
- React.createElement("input", { type: "checkbox", checked: !!domain.gradient, onChange: (e) => setFieldValue(`spec.series[${i}].gradient`, e.target.checked
306
- ? domain.gradient ?? {
307
- type: "linear",
308
- degree: 90,
309
- stops: [
310
- {
311
- position: 0,
312
- color: domain.color ?? "#4285F4",
313
- },
314
- {
315
- position: 1,
316
- color: "#FFFFFF",
317
- },
318
- ],
319
- }
320
- : undefined) }),
321
- "Gradient fill")))) : null,
322
- React.createElement(IconButton, { className: "absolute right-1 top-1", type: "button", onClick: () => {
323
- const currentSeries = basicSpec.series;
324
- setFieldValue("spec.series", currentSeries.filter((_, seriesIndex) => seriesIndex !== i));
325
- } },
326
- React.createElement(CrossCircledIcon, null))));
327
- });
328
- }),
329
- React.createElement("div", { className: "py-1" },
330
- React.createElement(Button, { variant: "link", onClick: () => {
331
- const nextSeries = basicSpec.series;
332
- const lastSeries = nextSeries[nextSeries.length - 1];
333
- setFieldValue("spec.series", [
334
- ...nextSeries,
335
- {
336
- sources: lastSeries
337
- ? lastSeries.sources
338
- : values.spec.dataRange
339
- ? [values.spec.dataRange]
340
- : [],
341
- },
342
- ]);
343
- }, type: "button", className: "cursor-pointer" }, "Add series"))))) : null,
264
+ caps.hasCategoryLevels ? (React.createElement("details", { className: "pb-2" },
265
+ React.createElement("summary", { className: "cursor-pointer list-none font-system text-xs text-rnc-muted-foreground select-none [&::-webkit-details-marker]:hidden" },
266
+ "Category axis levels",
267
+ categoryLevels.length ? ` (${categoryLevels.length})` : ""),
268
+ renderChartDataSection("Category axis levels", "categoryLevels", categoryLevels, "One range per level, outermost first; the last level is the plotted categories.", { addLabel: "Add level", removeLabel: "Remove level" }))) : null)) : null,
344
269
  bubbleSpec ? (React.createElement(React.Fragment, null,
345
270
  React.createElement("label", { className: "font-system text-sm flex items-center gap-1 pb-2" },
346
- React.createElement("input", { type: "checkbox", checked: bubbleAggregateEnabled, onChange: (e) => {
347
- const nextSeries = bubbleSpec.series.map((serie) => ({
271
+ React.createElement("input", { type: "checkbox", checked: bubbleSpec.useFirstRowAsHeaders !== false, onChange: (e) => setFieldValue("spec.useFirstRowAsHeaders", e.target.checked) }),
272
+ "Use first row as headers"),
273
+ bubbleSpec.chartType === "bubble"
274
+ ? renderChartDataSection("Bubble labels", "bubbleLabels", bubbleSpec.bubbleLabels ?? [], "Select the cells containing labels for each bubble.")
275
+ : null,
276
+ renderChartDataSection("X values", "domains", domainsList, "Choose the numeric values for the horizontal axis."),
277
+ bubbleSpec.chartType === "bubble"
278
+ ? renderChartDataSection("Bubble sizes", "bubbleSizes", bubbleSpec.bubbleSizes ?? [], "Choose the numeric values that size each bubble.")
279
+ : null)) : null,
280
+ basicSpec || bubbleSpec ? (React.createElement(React.Fragment, null,
281
+ React.createElement("label", { className: "font-system text-sm flex items-center gap-1 pb-2" },
282
+ React.createElement("input", { type: "checkbox", checked: isAggregateType, name: "showColumnStripes", onChange: (e) => {
283
+ const nextSeries = seriesList.map((serie) => ({
348
284
  ...serie,
349
285
  aggregateType: e.target.checked ? "SUM" : undefined,
350
286
  }));
351
287
  setFieldValue("spec.series", nextSeries);
352
288
  } }),
353
289
  "Aggregate"),
354
- React.createElement("label", { className: "font-system text-sm flex items-center gap-1 pb-2" },
355
- React.createElement("input", { type: "checkbox", checked: bubbleSpec.useFirstRowAsHeaders !== false, onChange: (e) => setFieldValue("spec.useFirstRowAsHeaders", e.target.checked) }),
356
- "Use first row as headers"),
357
- bubbleSpec.chartType === "bubble"
358
- ? renderChartDataSection("Bubble labels", "bubbleLabels", bubbleSpec.bubbleLabels ?? [], "Select the cells containing labels for each bubble.")
359
- : null,
360
- renderChartDataSection("X values", "domains", bubbleSpec.domains ?? [], "Choose the numeric values for the horizontal axis."),
361
- React.createElement(Stack, { direction: "vertical", className: "gap-2 pb-2" },
362
- React.createElement("label", { className: "font-system text-sm" }, "Legend entries (series)"),
363
- bubbleSpec.series.map((seriesEntry, i) => {
364
- const sources = seriesEntry.sources.length > 0
365
- ? seriesEntry.sources
366
- : [undefined];
367
- return sources.map((source, idx) => (React.createElement("div", { className: "border border-rnc-border border-solid p-2 relative", key: `${values.chartId}-bubble-series-${bubbleSpec.series.length}-${i}-${idx}` },
368
- React.createElement("div", { className: "flex gap-2 items-start" },
369
- React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
370
- React.createElement("label", { className: "font-system text-sm" }, "Name"),
371
- React.createElement(Input, { value: seriesEntry.dataLabel ?? "", type: "text", onChange: (e) => setFieldValue(`spec.series[${i}].dataLabel`, e.target.value), className: "text-xs", placeholder: "Enter label" })),
372
- React.createElement(Stack, { direction: "vertical", className: "gap-1 flex-1" },
373
- React.createElement("label", { className: "font-system text-sm" }, "Y values"),
374
- React.createElement(ChartRangeInput, { sheetId: sheetId, range: source, onChangeRange: (range) => {
375
- setFieldValue(`spec.series[${i}].sources[${idx}]`, range);
376
- }, required: true, placeholder: "Select a range", className: "mb-0 flex-1" })),
377
- bubbleSpec.chartType === "scatter" ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2 w-24" },
378
- React.createElement("label", { className: "font-system text-sm" }, "Marker"),
379
- React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent", value: seriesEntry.marker?.symbol ?? "", onChange: (event) => setFieldValue(`spec.series[${i}].marker`, {
380
- ...(seriesEntry.marker ?? {}),
381
- symbol: event.target.value || undefined,
382
- }) },
383
- React.createElement("option", { value: "" }, "Auto"),
384
- React.createElement("option", { value: "circle" }, "Circle"),
385
- React.createElement("option", { value: "square" }, "Square"),
386
- React.createElement("option", { value: "diamond" }, "Diamond"),
387
- React.createElement("option", { value: "triangle" }, "Triangle"),
388
- React.createElement("option", { value: "x" }, "X"),
389
- React.createElement("option", { value: "plus" }, "Plus"),
390
- React.createElement("option", { value: "star" }, "Star"),
391
- React.createElement("option", { value: "none" }, "None")),
392
- React.createElement("label", { className: "font-system text-xs" }, "Marker size"),
393
- React.createElement(Input, { type: "number", min: 1, value: seriesEntry.marker?.size ?? "", onChange: (e) => setFieldValue(`spec.series[${i}].marker`, {
394
- ...(seriesEntry.marker ?? {}),
395
- size: e.target.value === ""
396
- ? undefined
397
- : Number(e.target.value),
398
- }), className: "text-xs", placeholder: "6" }))) : null,
399
- bubbleAggregateEnabled ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2 w-32" },
400
- React.createElement("label", { className: "font-system text-sm" }, "Aggregate type"),
401
- React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent", value: seriesEntry.aggregateType ?? "", onChange: (event) => {
402
- const aggregateType = event.target.value;
403
- setFieldValue(`spec.series[${i}].aggregateType`, aggregateType || undefined);
404
- } },
405
- React.createElement("option", { value: "" }, "None"),
406
- AGGREGATE_OPTIONS.map((option) => (React.createElement("option", { key: option.value, value: option.value }, option.label)))))) : null),
407
- React.createElement(IconButton, { className: "absolute right-1 top-1", type: "button", onClick: () => {
408
- const currentSeries = bubbleSpec.series;
409
- setFieldValue("spec.series", currentSeries.filter((_, index) => index !== i));
410
- } },
411
- React.createElement(CrossCircledIcon, null)))));
412
- }),
413
- React.createElement("div", { className: "py-1" },
414
- React.createElement(Button, { variant: "link", type: "button", onClick: () => {
415
- const nextSeries = bubbleSpec.series;
416
- const lastSeries = nextSeries[nextSeries.length - 1];
417
- setFieldValue("spec.series", [
418
- ...nextSeries,
419
- {
420
- sources: lastSeries
421
- ? lastSeries.sources
422
- : values.spec.dataRange
423
- ? [values.spec.dataRange]
424
- : [],
425
- },
426
- ]);
427
- }, className: "cursor-pointer" }, "Add series"))))) : null,
290
+ renderSeriesList())) : null,
428
291
  treemapSpec ? (React.createElement(React.Fragment, null,
429
292
  React.createElement("label", { className: "font-system text-sm flex items-center gap-1 pb-2" },
430
293
  React.createElement("input", { type: "checkbox", checked: treemapSpec.useFirstRowAsHeaders !== false, onChange: (e) => setFieldValue("spec.useFirstRowAsHeaders", e.target.checked) }),
431
294
  "Use first row as headers"),
432
295
  renderChartDataSection("Labels", "labels", treemapSpec.labels ?? [], "Select the cells that contain node labels."),
433
- renderChartDataSection("Parent labels", "parentLabels", treemapSpec.parentLabels ?? [], "Choose the cells that define each node's parent label."),
296
+ renderChartDataSection("Parent labels", "parentLabels", treemapSpec.parentLabels ?? [], "One range per level above the labels, top level first.", { addLabel: "Add level", removeLabel: "Remove level" }),
434
297
  renderChartDataSection("Size data", "sizeData", treemapSpec.sizeData ?? [], "Provide numeric values to determine each node's size."))) : null,
435
- basicSpec ? (React.createElement(React.Fragment, null,
298
+ caps.hasEmptyCellOptions ? (React.createElement(React.Fragment, null,
436
299
  React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2 w-56" },
437
300
  React.createElement("label", { className: "font-system text-sm" }, "Show empty cells as"),
438
301
  React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent", "aria-label": "Show empty cells as", value: values.spec.emptyCells ?? "gaps", onChange: (event) => {
@@ -459,6 +322,9 @@ export const ChartEditor = ({ chart, sheetId, onSubmit, onRequestMoveChart, getF
459
322
  React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
460
323
  React.createElement("label", { className: "font-system text-sm" }, "Chart title"),
461
324
  React.createElement(Input, { value: values?.spec.title ?? "", type: "text", onChange: (e) => setFieldValue(`spec.title`, e.target.value), className: "text-xs", placeholder: "Enter chart title" })),
325
+ React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
326
+ React.createElement("label", { className: "font-system text-sm" }, "Subtitle"),
327
+ React.createElement(Input, { value: values.spec.subtitle ?? "", type: "text", onChange: (e) => setFieldValue("spec.subtitle", e.target.value || undefined), className: "text-xs", placeholder: "Enter chart subtitle", "aria-label": "Chart subtitle" })),
462
328
  React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
463
329
  React.createElement("label", { className: "font-system text-sm" }, "Style"),
464
330
  React.createElement("div", { className: "grid grid-cols-4 gap-1", role: "radiogroup" }, CHART_STYLES.map((style) => {
@@ -492,18 +358,7 @@ export const ChartEditor = ({ chart, sheetId, onSubmit, onRequestMoveChart, getF
492
358
  CHART_QUICK_LAYOUTS.map((layout) => (React.createElement("option", { key: layout.id, value: layout.id }, layout.label))))),
493
359
  React.createElement("div", null,
494
360
  React.createElement(Button, { type: "button", size: "xs", onClick: () => setFieldValue("spec", resetChartToMatchStyle(values.spec)) }, "Reset to match style")),
495
- React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
496
- React.createElement("label", { className: "font-system text-sm" }, "Title font"),
497
- React.createElement("div", { className: "flex items-center gap-1 flex-wrap" },
498
- React.createElement(Input, { value: values.spec.titleTextFormat?.fontFamily ?? "", type: "text", onChange: (e) => setFieldValue("spec.titleTextFormat.fontFamily", e.target.value || undefined), className: "text-xs w-32", placeholder: "Font family", "aria-label": "Title font family" }),
499
- React.createElement(Input, { value: values.spec.titleTextFormat?.fontSize ?? "", type: "number", min: 1, onChange: (e) => setFieldValue("spec.titleTextFormat.fontSize", e.target.value === "" ? undefined : Number(e.target.value)), className: "text-xs w-16", placeholder: "Size", "aria-label": "Title font size" }),
500
- React.createElement("input", { type: "color", value: values.spec.titleTextFormat?.color ?? "#000000", onChange: (e) => setFieldValue("spec.titleTextFormat.color", e.target.value), className: "h-6 w-8 p-0 border border-rnc-border rounded-sm bg-transparent cursor-pointer", "aria-label": "Title color" }),
501
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
502
- React.createElement("input", { type: "checkbox", checked: Boolean(values.spec.titleTextFormat?.bold), onChange: (e) => setFieldValue("spec.titleTextFormat.bold", e.target.checked) }),
503
- "Bold"),
504
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
505
- React.createElement("input", { type: "checkbox", checked: Boolean(values.spec.titleTextFormat?.italic), onChange: (e) => setFieldValue("spec.titleTextFormat.italic", e.target.checked) }),
506
- "Italic")))),
361
+ React.createElement(TextFormatFields, { label: "Title font", value: values.spec.titleTextFormat, onChange: (titleTextFormat) => setFieldValue("spec.titleTextFormat", titleTextFormat), ariaLabels: { color: "Title color" } })),
507
362
  React.createElement(Section, { title: "Chart area" },
508
363
  React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
509
364
  React.createElement("label", { className: "font-system text-sm" }, "Fill"),
@@ -520,171 +375,34 @@ export const ChartEditor = ({ chart, sheetId, onSubmit, onRequestMoveChart, getF
520
375
  React.createElement("label", { className: "font-system text-sm flex items-center gap-1" },
521
376
  React.createElement("input", { type: "checkbox", checked: Boolean(values.spec.roundedCorners), onChange: (e) => setFieldValue("spec.roundedCorners", e.target.checked) }),
522
377
  "Rounded corners")),
523
- basicSpec ? (React.createElement(React.Fragment, null,
524
- React.createElement(Section, { title: "Legend" },
525
- React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
526
- React.createElement("label", { className: "font-system text-sm" }, "Legend"),
527
- React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent w-32", value: values?.spec.legend?.position ?? "", onChange: (e) => setFieldValue("spec.legend.position", e.target.value || undefined) },
528
- React.createElement("option", { value: "" }, "Auto"),
529
- React.createElement("option", { value: "top" }, "Top"),
530
- React.createElement("option", { value: "bottom" }, "Bottom"),
531
- React.createElement("option", { value: "left" }, "Left"),
532
- React.createElement("option", { value: "right" }, "Right"),
533
- React.createElement("option", { value: "none" }, "None")))),
534
- React.createElement(Section, { title: "Vertical axis" },
535
- React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
536
- React.createElement("label", { className: "font-system text-sm" }, "Vertical axis title"),
537
- React.createElement(Input, { value: values?.spec.verticalAxisTitle ?? "", type: "text", onChange: (e) => setFieldValue(`spec.verticalAxisTitle`, e.target.value), className: "text-xs", placeholder: "Vertical axis title" })),
538
- isCartesianBasic ? (React.createElement(Stack, { direction: "vertical", className: "gap-2 pb-2" },
539
- React.createElement("label", { className: "font-system text-sm" }, "Value axis"),
540
- React.createElement("div", { className: "flex flex-wrap gap-2 items-end" },
541
- React.createElement(Stack, { direction: "vertical", className: "gap-1 w-20" },
542
- React.createElement("label", { className: "font-system text-xs" }, "Min"),
543
- React.createElement(Input, { type: "number", value: values?.spec.valueAxisOptions?.min ?? "", onChange: (e) => setFieldValue("spec.valueAxisOptions.min", e.target.value === ""
544
- ? undefined
545
- : Number(e.target.value)), className: "text-xs", placeholder: "Auto" })),
546
- React.createElement(Stack, { direction: "vertical", className: "gap-1 w-20" },
547
- React.createElement("label", { className: "font-system text-xs" }, "Max"),
548
- React.createElement(Input, { type: "number", value: values?.spec.valueAxisOptions?.max ?? "", onChange: (e) => setFieldValue("spec.valueAxisOptions.max", e.target.value === ""
549
- ? undefined
550
- : Number(e.target.value)), className: "text-xs", placeholder: "Auto" }))),
551
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
552
- React.createElement("input", { type: "checkbox", checked: Boolean(values?.spec.valueAxisOptions?.logarithmic), onChange: (e) => setFieldValue("spec.valueAxisOptions.logarithmic", e.target.checked || undefined) }),
553
- "Logarithmic scale"),
554
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
555
- React.createElement("input", { type: "checkbox", checked: Boolean(values?.spec.valueAxisOptions?.reverse), onChange: (e) => setFieldValue("spec.valueAxisOptions.reverse", e.target.checked || undefined) }),
556
- "Reverse direction"),
557
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
558
- React.createElement("input", { type: "checkbox", checked: values?.spec.valueAxisOptions?.showMajorGridlines !==
559
- false, onChange: (e) => setFieldValue("spec.valueAxisOptions.showMajorGridlines", e.target.checked) }),
560
- "Gridlines"),
561
- React.createElement("label", { className: "font-system text-xs" }, "Number format"),
562
- React.createElement(Input, { value: values?.spec.valueAxisOptions?.numberFormatPattern ??
563
- "", type: "text", onChange: (e) => setFieldValue("spec.valueAxisOptions.numberFormatPattern", e.target.value || undefined), className: "text-xs w-32", placeholder: "Linked to source" }),
564
- basicSpec.series.some((s) => s.useSecondaryAxis) ? (React.createElement(React.Fragment, null,
565
- React.createElement("label", { className: "font-system text-sm pt-1" }, "Secondary value axis"),
566
- React.createElement("div", { className: "flex flex-wrap gap-2 items-end" },
567
- React.createElement(Stack, { direction: "vertical", className: "gap-1 w-20" },
568
- React.createElement("label", { className: "font-system text-xs" }, "Min"),
569
- React.createElement(Input, { type: "number", value: values?.spec.secondaryValueAxisOptions?.min ??
570
- "", onChange: (e) => setFieldValue("spec.secondaryValueAxisOptions.min", e.target.value === ""
571
- ? undefined
572
- : Number(e.target.value)), className: "text-xs", placeholder: "Auto" })),
573
- React.createElement(Stack, { direction: "vertical", className: "gap-1 w-20" },
574
- React.createElement("label", { className: "font-system text-xs" }, "Max"),
575
- React.createElement(Input, { type: "number", value: values?.spec.secondaryValueAxisOptions?.max ??
576
- "", onChange: (e) => setFieldValue("spec.secondaryValueAxisOptions.max", e.target.value === ""
577
- ? undefined
578
- : Number(e.target.value)), className: "text-xs", placeholder: "Auto" }))),
579
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
580
- React.createElement("input", { type: "checkbox", checked: Boolean(values?.spec.secondaryValueAxisOptions
581
- ?.logarithmic), onChange: (e) => setFieldValue("spec.secondaryValueAxisOptions.logarithmic", e.target.checked || undefined) }),
582
- "Logarithmic scale"),
583
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
584
- React.createElement("input", { type: "checkbox", checked: values?.spec.secondaryValueAxisOptions
585
- ?.showMajorGridlines !== false, onChange: (e) => setFieldValue("spec.secondaryValueAxisOptions.showMajorGridlines", e.target.checked) }),
586
- "Gridlines"),
587
- React.createElement("label", { className: "font-system text-xs" }, "Number format"),
588
- React.createElement(Input, { value: values?.spec.secondaryValueAxisOptions
589
- ?.numberFormatPattern ?? "", type: "text", onChange: (e) => setFieldValue("spec.secondaryValueAxisOptions.numberFormatPattern", e.target.value || undefined), className: "text-xs w-32", placeholder: "Linked to source" }))) : null)) : null),
590
- React.createElement(Section, { title: "Horizontal axis" },
591
- React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
592
- React.createElement("label", { className: "font-system text-sm" }, "Horizontal axis title"),
593
- React.createElement(Input, { value: values?.spec.horizontalAxisTitle ?? "", type: "text", onChange: (e) => setFieldValue(`spec.horizontalAxisTitle`, e.target.value), className: "text-xs", placeholder: "Horizontal axis title" })),
594
- isCartesianBasic ? (React.createElement(React.Fragment, null,
595
- React.createElement("label", { className: "font-system text-sm pt-1" }, "Category axis"),
596
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
597
- React.createElement("input", { type: "checkbox", checked: values?.spec.categoryAxisOptions
598
- ?.showMajorGridlines === true, onChange: (e) => setFieldValue("spec.categoryAxisOptions.showMajorGridlines", e.target.checked) }),
599
- "Gridlines"))) : null),
600
- React.createElement(Section, { title: "Data labels" },
601
- React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
602
- React.createElement("label", { className: "font-system text-sm flex items-center gap-1" },
603
- React.createElement("input", { type: "checkbox", checked: Boolean(values?.spec.dataLabels?.show), onChange: (e) => setFieldValue("spec.dataLabels.show", e.target.checked) }),
604
- "Show data labels"),
605
- values?.spec.dataLabels?.show ? (React.createElement("div", { className: "pl-4 flex flex-col gap-1" },
606
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
607
- React.createElement("input", { type: "checkbox", checked: values?.spec.dataLabels?.showValue !== false, onChange: (e) => setFieldValue("spec.dataLabels.showValue", e.target.checked) }),
608
- "Value"),
609
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
610
- React.createElement("input", { type: "checkbox", checked: Boolean(values?.spec.dataLabels?.showCategoryName), onChange: (e) => setFieldValue("spec.dataLabels.showCategoryName", e.target.checked) }),
611
- "Category name"),
612
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
613
- React.createElement("input", { type: "checkbox", checked: Boolean(values?.spec.dataLabels?.showSeriesName), onChange: (e) => setFieldValue("spec.dataLabels.showSeriesName", e.target.checked) }),
614
- "Series name"),
615
- React.createElement("label", { className: "font-system text-xs flex items-center gap-1" },
616
- React.createElement("input", { type: "checkbox", checked: Boolean(values?.spec.dataLabels?.showPercent), onChange: (e) => setFieldValue("spec.dataLabels.showPercent", e.target.checked) }),
617
- "Percentage"),
618
- React.createElement("label", { className: "font-system text-xs" }, "Label position"),
619
- React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent w-32", value: values?.spec.dataLabels?.position ?? "outside", onChange: (e) => setFieldValue("spec.dataLabels.position", e.target.value) },
620
- React.createElement("option", { value: "center" }, "Center"),
621
- React.createElement("option", { value: "inside" }, "Inside"),
622
- React.createElement("option", { value: "outside" }, "Outside"),
623
- React.createElement("option", { value: "insideEnd" }, "Inside end"),
624
- React.createElement("option", { value: "outsideEnd" }, "Outside end"),
625
- React.createElement("option", { value: "bestFit" }, "Best fit"),
626
- React.createElement("option", { value: "above" }, "Above"),
627
- React.createElement("option", { value: "below" }, "Below"),
628
- React.createElement("option", { value: "left" }, "Left"),
629
- React.createElement("option", { value: "right" }, "Right")),
630
- React.createElement("label", { className: "font-system text-xs" }, "Number format pattern"),
631
- React.createElement(Input, { value: values?.spec.dataLabels?.numberFormatPattern ?? "", type: "text", onChange: (e) => setFieldValue("spec.dataLabels.numberFormatPattern", e.target.value || undefined), className: "text-xs w-32", placeholder: "#,##0.00" }))) : null)),
632
- basicSpec && showChartOptions ? (React.createElement(Section, { title: "Chart options" },
633
- chartOptionControls.binCount ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
634
- React.createElement("label", { className: "font-system text-sm" }, "Bins (leave blank for auto)"),
635
- React.createElement(Input, { value: basicSpec.binCount ?? "", type: "number", min: 1, onChange: (e) => {
636
- const raw = e.target.value;
637
- setFieldValue("spec.binCount", raw === "" ? undefined : Number(raw));
638
- }, className: "text-xs", placeholder: "Auto (Sturges)" }))) : null,
639
- chartOptionControls.radarStyle ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
640
- React.createElement("label", { className: "font-system text-sm" }, "Radar style"),
641
- React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent", value: basicSpec.radarStyle ?? "standard", onChange: (e) => setFieldValue("spec.radarStyle", e.target.value) },
642
- React.createElement("option", { value: "standard" }, "Standard"),
643
- React.createElement("option", { value: "marker" }, "With markers"),
644
- React.createElement("option", { value: "filled" }, "Filled")))) : null,
645
- chartOptionControls.stockVariant ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
646
- React.createElement("label", { className: "font-system text-sm" }, "Stock variant"),
647
- React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent", value: basicSpec.stockVariant ?? "HLC", onChange: (e) => setFieldValue("spec.stockVariant", e.target.value) },
648
- React.createElement("option", { value: "HLC" }, "High-Low-Close"),
649
- React.createElement("option", { value: "OHLC" }, "Open-High-Low-Close"),
650
- React.createElement("option", { value: "VHLC" }, "Volume-High-Low-Close"),
651
- React.createElement("option", { value: "VOHLC" }, "Volume-Open-High-Low-Close")))) : null,
652
- chartOptionControls.doughnutHoleSize ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
653
- React.createElement("label", { className: "font-system text-sm" }, "Doughnut hole size (0-0.9)"),
654
- React.createElement(Input, { type: "number", min: 0, max: 0.9, step: 0.05, value: basicSpec.doughnutHoleSize ?? "", onChange: (e) => setFieldValue("spec.doughnutHoleSize", e.target.value === ""
655
- ? undefined
656
- : Number(e.target.value)), className: "text-xs w-32", placeholder: "0.5" }))) : null,
657
- chartOptionControls.explosion ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
658
- React.createElement("label", { className: "font-system text-sm" }, "Explosion (% of radius, 0-400)"),
659
- React.createElement(Input, { type: "number", min: 0, max: 400, step: 5, "aria-label": "Explosion", value: basicSpec.explosion ?? "", onChange: (e) => setFieldValue("spec.explosion", e.target.value === ""
660
- ? undefined
661
- : Number(e.target.value)), className: "text-xs w-32", placeholder: "0" }))) : null,
662
- chartOptionControls.gapWidth ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
663
- React.createElement("label", { className: "font-system text-sm" }, "Gap width (0-500)"),
664
- React.createElement(Input, { type: "number", min: 0, max: 500, value: basicSpec.gapWidth ?? "", onChange: (e) => setFieldValue("spec.gapWidth", e.target.value === ""
665
- ? undefined
666
- : Number(e.target.value)), className: "text-xs w-32", placeholder: "150" }))) : null,
667
- chartOptionControls.overlap ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
668
- React.createElement("label", { className: "font-system text-sm" }, "Series overlap (-100 to 100)"),
669
- React.createElement(Input, { type: "number", min: -100, max: 100, value: basicSpec.overlap ?? "", onChange: (e) => setFieldValue("spec.overlap", e.target.value === ""
670
- ? undefined
671
- : Number(e.target.value)), className: "text-xs w-32", placeholder: "0" }))) : null)) : null)) : null,
672
- bubbleSpec && bubbleSpec.chartType === "scatter" ? (React.createElement(Section, { title: "Chart options" },
673
- React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
674
- React.createElement("label", { className: "font-system text-sm" }, "Scatter style"),
675
- React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent w-56", value: bubbleSpec.scatterStyle ?? "lineMarker", onChange: (e) => setFieldValue("spec.scatterStyle", e.target.value) },
676
- React.createElement("option", { value: "lineMarker" }, "Straight lines with markers"),
677
- React.createElement("option", { value: "line" }, "Straight lines"),
678
- React.createElement("option", { value: "marker" }, "Markers only"),
679
- React.createElement("option", { value: "smoothMarker" }, "Smooth lines with markers"),
680
- React.createElement("option", { value: "smooth" }, "Smooth lines"))))) : null,
681
- bubbleSpec ? (React.createElement(Section, { title: "Axis titles" },
378
+ caps.hasLegend ? (React.createElement(Section, { title: "Legend" },
682
379
  React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
380
+ React.createElement("label", { className: "font-system text-sm" }, "Legend"),
381
+ React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent w-32", "aria-label": "Legend position", value: values?.spec.legend?.position ?? "", onChange: (e) => setFieldValue("spec.legend.position", e.target.value || undefined) },
382
+ React.createElement("option", { value: "" }, "Auto"),
383
+ React.createElement("option", { value: "top" }, "Top"),
384
+ React.createElement("option", { value: "bottom" }, "Bottom"),
385
+ React.createElement("option", { value: "left" }, "Left"),
386
+ React.createElement("option", { value: "right" }, "Right"),
387
+ React.createElement("option", { value: "topRight" }, "Top right"),
388
+ React.createElement("option", { value: "none" }, "None"))),
389
+ React.createElement(TextFormatFields, { label: "Legend font", collapsible: true, value: values.spec.legend?.textFormat, onChange: (textFormat) => setFieldValue("spec.legend.textFormat", textFormat) }))) : null,
390
+ caps.hasAxisTitles || caps.hasValueAxis ? (React.createElement(Section, { title: "Vertical axis" },
391
+ caps.hasAxisTitles ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
683
392
  React.createElement("label", { className: "font-system text-sm" }, "Vertical axis title"),
684
- React.createElement(Input, { value: values?.spec.verticalAxisTitle ?? "", type: "text", onChange: (e) => setFieldValue(`spec.verticalAxisTitle`, e.target.value), className: "text-xs", placeholder: "Vertical axis title" })),
685
- React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
393
+ React.createElement(Input, { value: values?.spec.verticalAxisTitle ?? "", type: "text", onChange: (e) => setFieldValue(`spec.verticalAxisTitle`, e.target.value), className: "text-xs", placeholder: "Vertical axis title" }))) : null,
394
+ caps.hasValueAxis ? (React.createElement(AxisOptionsFields, { label: "Value axis", kind: "value", value: values.spec.valueAxisOptions, onChange: (next) => setFieldValue("spec.valueAxisOptions", next) })) : null,
395
+ caps.hasSecondaryAxis &&
396
+ seriesList.some((s) => s.useSecondaryAxis) ? (React.createElement(AxisOptionsFields, { label: "Secondary value axis", kind: "value", value: values.spec.secondaryValueAxisOptions, onChange: (next) => setFieldValue("spec.secondaryValueAxisOptions", next) })) : null)) : null,
397
+ caps.hasAxisTitles || caps.hasCategoryAxis ? (React.createElement(Section, { title: "Horizontal axis" },
398
+ caps.hasAxisTitles ? (React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
686
399
  React.createElement("label", { className: "font-system text-sm" }, "Horizontal axis title"),
687
- React.createElement(Input, { value: values?.spec.horizontalAxisTitle ?? "", type: "text", onChange: (e) => setFieldValue(`spec.horizontalAxisTitle`, e.target.value), className: "text-xs", placeholder: "Horizontal axis title" })))) : null,
400
+ React.createElement(Input, { value: values?.spec.horizontalAxisTitle ?? "", type: "text", onChange: (e) => setFieldValue(`spec.horizontalAxisTitle`, e.target.value), className: "text-xs", placeholder: "Horizontal axis title" }))) : null,
401
+ caps.hasCategoryAxis ? (React.createElement(AxisOptionsFields, { label: "Category axis", kind: caps.category === "bubble" ? "value" : "category", value: values.spec.categoryAxisOptions, gridlinesDefault: false, onChange: (next) => setFieldValue("spec.categoryAxisOptions", next) })) : null)) : null,
402
+ caps.hasDataLabels ? (React.createElement(Section, { title: "Data labels" },
403
+ React.createElement(DataLabelsFields, { value: values.spec.dataLabels, capabilities: caps, onChange: (dataLabels) => setFieldValue("spec.dataLabels", dataLabels) }))) : null,
404
+ hasTypeOptions ? (React.createElement(Section, { title: "Chart options" },
405
+ React.createElement(ChartTypeOptionsFields, { spec: values.spec, capabilities: caps, setFieldValue: setFieldValue }))) : null,
688
406
  React.createElement(Section, { title: "Properties" },
689
407
  React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2", role: "radiogroup" }, CHART_PLACEMENT_OPTIONS.map(({ label, value }) => (React.createElement("label", { key: value, className: "font-system text-sm flex items-center gap-1" },
690
408
  React.createElement("input", { type: "radio", name: "placement", value: value, checked: (values.position.overlayPosition.placement ??