eleventy-plugin-uncharted 0.9.1 → 1.0.0-beta.2

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/css/uncharted.css CHANGED
@@ -313,6 +313,11 @@
313
313
  background-color: var(--color);
314
314
  }
315
315
 
316
+ .chart-stacked-bar .bar-fill[title]:hover {
317
+ filter: brightness(1.15);
318
+ cursor: default;
319
+ }
320
+
316
321
  .chart-stacked-bar .bar-value {
317
322
  display: flex;
318
323
  align-items: center;
@@ -374,6 +379,11 @@
374
379
  background-color: var(--color);
375
380
  }
376
381
 
382
+ .chart-stacked-column .column-segment[title]:hover {
383
+ filter: brightness(1.15);
384
+ cursor: default;
385
+ }
386
+
377
387
  .chart-stacked-column .column-labels {
378
388
  grid-row: 2;
379
389
  display: flex;
@@ -604,7 +614,7 @@
604
614
  }
605
615
 
606
616
  :is(.chart-dot, .chart-line) .dot[title]:hover {
607
- transform: translate(-50%, 50%) scale(1.3);
617
+ transform: translate(-50%, 50%) scale(1.3) !important;
608
618
  z-index: 1;
609
619
  }
610
620
 
@@ -742,7 +752,7 @@
742
752
  }
743
753
 
744
754
  .chart-bubble .dot[title]:hover {
745
- transform: translate(-50%, 50%) scale(1.3);
755
+ transform: translate(-50%, 50%) scale(1.3) !important;
746
756
  z-index: 1;
747
757
  }
748
758
 
@@ -932,7 +942,7 @@
932
942
  }
933
943
 
934
944
  .chart-scatter .dot[title]:hover {
935
- transform: translate(-50%, 50%) scale(1.3);
945
+ transform: translate(-50%, 50%) scale(1.3) !important;
936
946
  z-index: 1;
937
947
  }
938
948
 
@@ -1038,7 +1048,7 @@
1038
1048
  }
1039
1049
 
1040
1050
  .chart-timeseries .dot[title]:hover {
1041
- transform: translate(-50%, 50%) scale(1.3);
1051
+ transform: translate(-50%, 50%) scale(1.3) !important;
1042
1052
  z-index: 1;
1043
1053
  }
1044
1054
 
@@ -1051,7 +1061,7 @@
1051
1061
 
1052
1062
  :is(.chart-line, .chart-timeseries).no-dots .dot:hover {
1053
1063
  background-color: var(--color);
1054
- transform: translate(-50%, 50%) scale(1.3);
1064
+ transform: translate(-50%, 50%) scale(1.3) !important;
1055
1065
  z-index: 1;
1056
1066
  }
1057
1067
 
@@ -4,6 +4,16 @@ import { renderers } from './src/renderers/index.js';
4
4
  import { loadCSV } from './src/csv.js';
5
5
  import { normalizeConfig } from './src/config.js';
6
6
  import { resolveColumns } from './src/columns.js';
7
+ import { validateChartType, checkDeprecatedOptions } from './src/deprecation.js';
8
+ import {
9
+ normalizeImageOptions,
10
+ queueChartForImage,
11
+ processQueue,
12
+ getImageUrl,
13
+ getStoredImageUrl,
14
+ shouldSkipInDevMode,
15
+ clearImageUrls
16
+ } from './src/image/index.js';
7
17
 
8
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
19
 
@@ -18,6 +28,14 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
28
  * @param {boolean} [options.dataPassthrough] - Copy CSV files to public dataPath (default: false)
19
29
  * @param {string} [options.dataPath] - Public URL path for CSV files (default: '/data/')
20
30
  * @param {boolean|string} [options.downloadData] - Enable download links globally (individual charts can override)
31
+ * @param {Object} [options.image] - Image generation options
32
+ * @param {boolean} [options.image.enabled=false] - Enable PNG image generation
33
+ * @param {string} [options.image.outputDir='/images/charts/'] - Output directory for images
34
+ * @param {number} [options.image.width=800] - Default image width in pixels
35
+ * @param {number} [options.image.height=400] - Default image height in pixels
36
+ * @param {number} [options.image.scale=2] - Device scale factor (2 for retina)
37
+ * @param {string} [options.image.background='#ffffff'] - Default background color
38
+ * @param {boolean} [options.image.skipDev=true] - Skip image generation during --serve/--watch
21
39
  */
22
40
  export default function(eleventyConfig, options = {}) {
23
41
  // Directory config from Eleventy (populated by eleventy.directories event)
@@ -49,6 +67,13 @@ export default function(eleventyConfig, options = {}) {
49
67
  const dataPath = options.dataPath || '/data/';
50
68
  const globalDownloadData = options.downloadData ?? false;
51
69
 
70
+ // Image generation options
71
+ const imageOptions = normalizeImageOptions(options.image);
72
+ const skipImageGeneration = shouldSkipInDevMode(imageOptions);
73
+
74
+ // Clear image URLs at start of each build
75
+ clearImageUrls();
76
+
52
77
  // Automatic CSS handling
53
78
  if (injectCss) {
54
79
  const cssSource = path.join(__dirname, 'css/uncharted.css');
@@ -121,11 +146,21 @@ export default function(eleventyConfig, options = {}) {
121
146
  return `<!-- Chart "${chartId}" has no type specified -->`;
122
147
  }
123
148
 
149
+ // Check for deprecated chart type (ERROR - prevents rendering)
150
+ const typeError = validateChartType(chartType, chartId);
151
+ if (typeError) {
152
+ console.error(`[uncharted] ${typeError}`);
153
+ return `<!-- ${typeError} -->`;
154
+ }
155
+
124
156
  const renderer = renderers[chartType];
125
157
  if (!renderer) {
126
158
  return `<!-- Unknown chart type "${chartType}" for chart "${chartId}" -->`;
127
159
  }
128
160
 
161
+ // Check for deprecated config options (WARNING - chart renders but option ignored)
162
+ checkDeprecatedOptions(chartConfig, chartId);
163
+
129
164
  // Load data from CSV file or use inline data
130
165
  let data = chartConfig.data;
131
166
  if (chartConfig.file && !data) {
@@ -153,7 +188,8 @@ export default function(eleventyConfig, options = {}) {
153
188
  // Resolve column mappings
154
189
  const columns = resolveColumns(normalizedConfig, data, chartType);
155
190
 
156
- return renderer({
191
+ // Render the chart HTML
192
+ let chartHtml = renderer({
157
193
  ...normalizedConfig,
158
194
  id: chartId,
159
195
  data,
@@ -162,5 +198,71 @@ export default function(eleventyConfig, options = {}) {
162
198
  downloadDataUrl,
163
199
  _columns: columns
164
200
  });
201
+
202
+ // Add aria-label for accessibility
203
+ const altText = chartConfig.alt || chartConfig.title || chartId;
204
+ chartHtml = chartHtml.replace(
205
+ /^<figure([^>]*class="chart[^"]*")/,
206
+ `<figure aria-label="${altText}"$1`
207
+ );
208
+
209
+ // Handle image generation
210
+ const chartImageEnabled = chartConfig.image?.enabled ?? imageOptions.enabled;
211
+ if (chartImageEnabled && !skipImageGeneration && eleventyDirs?.output) {
212
+ // Queue chart for image generation
213
+ queueChartForImage(chartId, chartHtml, chartConfig, imageOptions, eleventyDirs.output);
214
+
215
+ // Add data-chart-image and data-chart-alt attributes to the figure element
216
+ const imageUrl = getImageUrl(chartId, chartConfig, imageOptions);
217
+ chartHtml = chartHtml.replace(
218
+ /^<figure([^>]*aria-label="[^"]*"[^>]*class="chart[^"]*")/,
219
+ `<figure$1 data-chart-image="${imageUrl}" data-chart-alt="${altText}"`
220
+ );
221
+ }
222
+
223
+ return chartHtml;
224
+ });
225
+
226
+ // Shortcode to get chart image URL
227
+ eleventyConfig.addShortcode('chartImageUrl', function(chartId) {
228
+ // First check if we have a stored URL from queueing
229
+ const storedUrl = getStoredImageUrl(chartId);
230
+ if (storedUrl) return storedUrl;
231
+
232
+ // Otherwise, look up chart config and compute URL
233
+ const pageCharts = this.page?.charts;
234
+ const globalCharts = this.charts || this.ctx?.charts;
235
+ const chartConfig = pageCharts?.[chartId] || globalCharts?.[chartId];
236
+
237
+ if (!chartConfig) return '';
238
+
239
+ return getImageUrl(chartId, chartConfig, imageOptions);
240
+ });
241
+
242
+ // Filter to replace chart HTML with image tags (for RSS feeds)
243
+ eleventyConfig.addFilter('chartToImage', function(content, baseUrl = '') {
244
+ if (!content) return content;
245
+
246
+ // Find chart figures with data-chart-image and data-chart-alt attributes and replace with img tags
247
+ return content.replace(
248
+ /<figure[^>]*class="chart[^"]*"[^>]*data-chart-image="([^"]+)"[^>]*data-chart-alt="([^"]+)"[^>]*>[\s\S]*?<\/figure>/g,
249
+ (match, src, alt) => `<img src="${baseUrl}${src}" alt="${alt}">`
250
+ );
251
+ });
252
+
253
+ // Process image queue after build completes
254
+ eleventyConfig.on('eleventy.after', async () => {
255
+ if (!imageOptions.enabled || skipImageGeneration) return;
256
+
257
+ const results = await processQueue(imageOptions);
258
+
259
+ if (results.skipped) return;
260
+
261
+ if (results.success.length > 0) {
262
+ console.log(`[uncharted] Generated ${results.success.length} chart image(s)`);
263
+ }
264
+ if (results.failed.length > 0) {
265
+ console.warn(`[uncharted] Failed to generate ${results.failed.length} chart image(s)`);
266
+ }
165
267
  });
166
268
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eleventy-plugin-uncharted",
3
- "version": "0.9.1",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "An Eleventy plugin that renders CSS-based charts from CSV data using shortcodes",
5
5
  "main": "eleventy.config.js",
6
6
  "type": "module",
@@ -32,7 +32,13 @@
32
32
  "url": "https://github.com/slunsford/uncharted/issues"
33
33
  },
34
34
  "peerDependencies": {
35
- "@11ty/eleventy": ">=2.0.0 || >=4.0.0-0"
35
+ "@11ty/eleventy": ">=2.0.0 || >=4.0.0-0",
36
+ "puppeteer": ">=21.0.0"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "puppeteer": {
40
+ "optional": true
41
+ }
36
42
  },
37
43
  "devDependencies": {
38
44
  "@11ty/eleventy": "^3.0.0 || ^4.0.0-0"
package/src/columns.js CHANGED
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Column resolution for Uncharted
3
3
  * Handles explicit column mapping and implicit detection
4
- * Supports both new unified schema (x.column, y.columns) and deprecated columns key
5
4
  */
6
5
 
7
6
  import { parseAxisConfig } from './config.js';
@@ -9,13 +8,10 @@ import { parseAxisConfig } from './config.js';
9
8
  /**
10
9
  * Resolve column mappings for chart data
11
10
  *
12
- * New unified schema (takes precedence):
11
+ * New unified schema:
13
12
  * - x: { column: "month" } or x: "month"
14
13
  * - y: { columns: ["a", "b"] } or y: { columns: { a: "Label A" } }
15
14
  *
16
- * Deprecated schema (fallback):
17
- * - columns: { x: "month", y: ["a", "b"] }
18
- *
19
15
  * @param {Object} config - Chart configuration with optional columns mapping
20
16
  * @param {Object[]} data - Chart data array
21
17
  * @param {string} chartType - Chart type for context-aware defaults
@@ -42,7 +38,6 @@ export function resolveColumns(config, data, chartType) {
42
38
  }
43
39
 
44
40
  const keys = Object.keys(data[0]);
45
- const deprecatedColumns = config.columns || {};
46
41
 
47
42
  // Helper to find column by name (case-insensitive)
48
43
  const findKey = name => keys.find(k => k.toLowerCase() === name.toLowerCase()) || null;
@@ -100,40 +95,28 @@ export function resolveColumns(config, data, chartType) {
100
95
  // Chart-type specific resolution
101
96
  if (chartType === 'bubble') {
102
97
  // Bubble charts use x (categorical), y, series, size columns
103
- // Like scatter but with categorical X axis
104
- // New schema: x.column, y.column, series.column, size.column
105
- // Deprecated: columns.x, columns.y, columns.series, columns.size
98
+ // Schema: x.column, y.column, series.column, size.column
106
99
 
107
100
  // X column (categorical)
108
101
  if (xConfig?.columns?.length) {
109
102
  resolved.x = validateColumn('x.column', xConfig.columns[0]);
110
- } else if (deprecatedColumns.x) {
111
- resolved.x = validateColumn('columns.x', deprecatedColumns.x);
112
103
  }
113
104
 
114
105
  // Y column
115
106
  if (yConfig?.columns?.length) {
116
107
  resolved.y = validateColumn('y.column', yConfig.columns[0]);
117
- } else if (deprecatedColumns.y) {
118
- resolved.y = validateColumn('columns.y', deprecatedColumns.y);
119
108
  }
120
109
 
121
110
  // Series column (for coloring)
122
111
  if (seriesConfig?.columns?.length) {
123
112
  resolved.series = validateColumn('series.column', seriesConfig.columns[0]);
124
113
  resolved.seriesTitle = seriesConfig.title;
125
- } else if (deprecatedColumns.series) {
126
- resolved.series = validateColumn('columns.series', deprecatedColumns.series);
127
- resolved.seriesTitle = config.legendTitle; // deprecated
128
114
  }
129
115
 
130
116
  // Size column
131
117
  if (sizeConfig?.columns?.length) {
132
118
  resolved.size = validateColumn('size.column', sizeConfig.columns[0]);
133
119
  resolved.sizeTitle = sizeConfig.title;
134
- } else if (deprecatedColumns.size) {
135
- resolved.size = validateColumn('columns.size', deprecatedColumns.size);
136
- resolved.sizeTitle = config.sizeTitle; // deprecated
137
120
  }
138
121
 
139
122
  // Implicit detection for bubble if not explicitly specified
@@ -168,28 +151,21 @@ export function resolveColumns(config, data, chartType) {
168
151
 
169
152
  } else if (chartType === 'scatter') {
170
153
  // Scatter charts use x, y, label, series, size columns
171
- // New schema: x.column, y.column, label.column, series.column, size.column
172
- // Deprecated: columns.x, columns.y, columns.label, columns.series, columns.size
154
+ // Schema: x.column, y.column, label.column, series.column, size.column
173
155
 
174
156
  // X column
175
157
  if (xConfig?.columns?.length) {
176
158
  resolved.x = validateColumn('x.column', xConfig.columns[0]);
177
- } else if (deprecatedColumns.x) {
178
- resolved.x = validateColumn('columns.x', deprecatedColumns.x);
179
159
  }
180
160
 
181
161
  // Y column
182
162
  if (yConfig?.columns?.length) {
183
163
  resolved.y = validateColumn('y.column', yConfig.columns[0]);
184
- } else if (deprecatedColumns.y) {
185
- resolved.y = validateColumn('columns.y', deprecatedColumns.y);
186
164
  }
187
165
 
188
166
  // Label column (point identifier)
189
167
  if (labelConfig?.columns?.length) {
190
168
  resolved.label = validateColumn('label.column', labelConfig.columns[0]);
191
- } else if (deprecatedColumns.label) {
192
- resolved.label = validateColumn('columns.label', deprecatedColumns.label);
193
169
  } else {
194
170
  resolved.label = keys[0];
195
171
  }
@@ -198,18 +174,12 @@ export function resolveColumns(config, data, chartType) {
198
174
  if (seriesConfig?.columns?.length) {
199
175
  resolved.series = validateColumn('series.column', seriesConfig.columns[0]);
200
176
  resolved.seriesTitle = seriesConfig.title;
201
- } else if (deprecatedColumns.series) {
202
- resolved.series = validateColumn('columns.series', deprecatedColumns.series);
203
- resolved.seriesTitle = config.legendTitle; // deprecated
204
177
  }
205
178
 
206
179
  // Size column
207
180
  if (sizeConfig?.columns?.length) {
208
181
  resolved.size = validateColumn('size.column', sizeConfig.columns[0]);
209
182
  resolved.sizeTitle = sizeConfig.title;
210
- } else if (deprecatedColumns.size) {
211
- resolved.size = validateColumn('columns.size', deprecatedColumns.size);
212
- resolved.sizeTitle = config.sizeTitle; // deprecated
213
183
  }
214
184
 
215
185
  // Implicit detection for scatter if not explicitly specified
@@ -236,21 +206,16 @@ export function resolveColumns(config, data, chartType) {
236
206
 
237
207
  } else if (chartType === 'sankey') {
238
208
  // Sankey charts use source, target, value columns
239
- // New schema: source.column, target.column, value.column
240
- // Deprecated: columns.source, columns.target, columns.value
209
+ // Schema: source.column, target.column, value.column
241
210
 
242
211
  if (sourceConfig?.columns?.length) {
243
212
  resolved.source = validateColumn('source.column', sourceConfig.columns[0]);
244
- } else if (deprecatedColumns.source) {
245
- resolved.source = validateColumn('columns.source', deprecatedColumns.source);
246
213
  } else {
247
214
  resolved.source = keys[0];
248
215
  }
249
216
 
250
217
  if (targetConfig?.columns?.length) {
251
218
  resolved.target = validateColumn('target.column', targetConfig.columns[0]);
252
- } else if (deprecatedColumns.target) {
253
- resolved.target = validateColumn('columns.target', deprecatedColumns.target);
254
219
  } else {
255
220
  resolved.target = keys[1];
256
221
  }
@@ -258,22 +223,17 @@ export function resolveColumns(config, data, chartType) {
258
223
  if (valueConfig?.columns?.length) {
259
224
  resolved.value = validateColumn('value.column', valueConfig.columns[0]);
260
225
  resolved.valueFormat = valueConfig.format;
261
- } else if (deprecatedColumns.value) {
262
- resolved.value = validateColumn('columns.value', deprecatedColumns.value);
263
226
  } else {
264
227
  resolved.value = keys[2];
265
228
  }
266
229
 
267
230
  } else if (chartType === 'donut') {
268
231
  // Donut charts use label, value columns
269
- // New schema: label.column, value.column
270
- // Deprecated: columns.label, columns.value (or implicit first/second columns)
232
+ // Schema: label.column, value.column
271
233
 
272
234
  if (labelConfig?.columns?.length) {
273
235
  resolved.label = validateColumn('label.column', labelConfig.columns[0]);
274
236
  resolved.yLabels = labelConfig.labels || {};
275
- } else if (deprecatedColumns.label) {
276
- resolved.label = validateColumn('columns.label', deprecatedColumns.label);
277
237
  } else {
278
238
  resolved.label = keys[0];
279
239
  }
@@ -284,9 +244,6 @@ export function resolveColumns(config, data, chartType) {
284
244
  resolved.values = [resolved.values];
285
245
  }
286
246
  resolved.valueFormat = valueConfig.format;
287
- } else if (deprecatedColumns.value) {
288
- const val = validateColumn('columns.value', deprecatedColumns.value);
289
- resolved.values = val ? [val] : [];
290
247
  } else {
291
248
  // Default: all columns except label
292
249
  resolved.values = keys.filter(k => k !== resolved.label);
@@ -294,14 +251,11 @@ export function resolveColumns(config, data, chartType) {
294
251
 
295
252
  } else if (chartType === 'stacked-bar') {
296
253
  // Stacked bar: y = categories (left side), x = value series (bars extend right)
297
- // New schema: y.column (categories), x.columns (values with labels)
298
- // Deprecated: columns.y (or label), columns.x (or y as values)
254
+ // Schema: y.column (categories), x.columns (values with labels)
299
255
 
300
256
  // Category column (on Y axis for stacked-bar)
301
257
  if (yConfig?.columns?.length) {
302
258
  resolved.label = validateColumn('y.column', yConfig.columns[0]);
303
- } else if (deprecatedColumns.label) {
304
- resolved.label = validateColumn('columns.label', deprecatedColumns.label);
305
259
  } else {
306
260
  resolved.label = keys[0];
307
261
  }
@@ -310,25 +264,18 @@ export function resolveColumns(config, data, chartType) {
310
264
  if (xConfig?.columns?.length) {
311
265
  resolved.values = validateColumn('x.columns', xConfig.columns) || [];
312
266
  resolved.xLabels = xConfig.labels || {};
313
- } else if (deprecatedColumns.y) {
314
- // Deprecated: columns.y was used for value columns in stacked-bar
315
- const explicitY = validateColumn('columns.y', deprecatedColumns.y);
316
- resolved.values = Array.isArray(explicitY) ? explicitY : (explicitY ? [explicitY] : []);
317
267
  } else {
318
268
  // Implicit: all columns except label are values
319
269
  resolved.values = keys.filter(k => k !== resolved.label);
320
270
  }
321
271
 
322
272
  } else {
323
- // Standard charts (dot, line, stacked-column): x = categories, y = multi-series values
324
- // New schema: x.column (categories), y.columns (values with labels)
325
- // Deprecated: columns.label (or implicit first), columns.y
273
+ // Standard charts (line, stacked-column): x = categories, y = multi-series values
274
+ // Schema: x.column (categories), y.columns (values with labels)
326
275
 
327
276
  // Label/category column (X axis)
328
277
  if (xConfig?.columns?.length) {
329
278
  resolved.label = validateColumn('x.column', xConfig.columns[0]);
330
- } else if (deprecatedColumns.label) {
331
- resolved.label = validateColumn('columns.label', deprecatedColumns.label);
332
279
  } else {
333
280
  resolved.label = keys[0];
334
281
  }
@@ -337,9 +284,6 @@ export function resolveColumns(config, data, chartType) {
337
284
  if (yConfig?.columns?.length) {
338
285
  resolved.values = validateColumn('y.columns', yConfig.columns) || [];
339
286
  resolved.yLabels = yConfig.labels || {};
340
- } else if (deprecatedColumns.y) {
341
- const explicitY = validateColumn('columns.y', deprecatedColumns.y);
342
- resolved.values = Array.isArray(explicitY) ? explicitY : (explicitY ? [explicitY] : []);
343
287
  } else {
344
288
  // Implicit: all columns except label are values
345
289
  resolved.values = keys.filter(k => k !== resolved.label);
package/src/config.js CHANGED
@@ -3,58 +3,22 @@
3
3
  * Transforms various config formats into a standardized structure
4
4
  */
5
5
 
6
- // Deprecated keys mapping for deprecation warnings
7
- const DEPRECATED_KEYS = {
8
- maxX: 'x.max',
9
- minX: 'x.min',
10
- maxY: 'y.max',
11
- minY: 'y.min',
12
- titleX: 'x.title',
13
- titleY: 'y.title',
14
- legendTitle: 'series.title',
15
- sizeTitle: 'size.title',
16
- rotateLabels: 'x.rotateLabels'
17
- };
18
-
19
- // Track which deprecation warnings have been shown to avoid spam
20
- const warnedConfigs = new Set();
21
-
22
- /**
23
- * Log a deprecation warning (once per config/key combination)
24
- * @param {string} chartId - Chart identifier
25
- * @param {string} oldKey - Deprecated key name
26
- * @param {string} newKey - New key path
27
- */
28
- function warnDeprecation(chartId, oldKey, newKey) {
29
- const key = `${chartId}:${oldKey}`;
30
- if (warnedConfigs.has(key)) return;
31
- warnedConfigs.add(key);
32
- console.warn(`[uncharted] Chart "${chartId}": "${oldKey}" is deprecated, use "${newKey}" instead`);
33
- }
34
-
35
6
  /**
36
7
  * Normalize chart configuration to standardized structure
37
8
  *
38
- * Precedence for axis properties (highest to lowest):
39
- * 1. x.max / y.max (new nested format)
40
- * 2. maxX / maxY (deprecated suffixed format)
41
- * 3. max (global fallback)
42
- *
43
- * Format precedence:
44
- * 1. x.format / y.format (new nested format)
45
- * 2. format.x / format.y (deprecated nested format)
46
- * 3. format (global fallback)
9
+ * Axis properties:
10
+ * - x.max / y.max, x.min / y.min
11
+ * - x.title / y.title
12
+ * - x.format / y.format
47
13
  *
48
- * Column mapping precedence:
49
- * 1. x.column / x.columns / y.column / y.columns (new unified format)
50
- * 2. columns.x / columns.y (deprecated format)
14
+ * Column mapping:
15
+ * - x.column / x.columns / y.column / y.columns
51
16
  *
52
- * Legend precedence:
53
- * 1. y.columns: { key: "Label" } or x.columns (for stacked-bar)
54
- * 2. legend: ["Label1", "Label2"] (deprecated)
17
+ * Legend:
18
+ * - y.columns: { key: "Label" } or x.columns (for stacked-bar)
55
19
  *
56
20
  * @param {Object} config - Raw chart configuration
57
- * @param {string} [chartId] - Chart ID for deprecation warnings
21
+ * @param {string} [chartId] - Chart ID (unused, kept for API compatibility)
58
22
  * @returns {Object} - Normalized configuration
59
23
  */
60
24
  export function normalizeConfig(config, chartId = 'unknown') {
@@ -63,26 +27,10 @@ export function normalizeConfig(config, chartId = 'unknown') {
63
27
  const normalized = { ...config };
64
28
 
65
29
  // Build x axis config
66
- normalized.x = buildAxisConfig('x', config, chartId);
30
+ normalized.x = buildAxisConfig('x', config);
67
31
 
68
32
  // Build y axis config
69
- normalized.y = buildAxisConfig('y', config, chartId);
70
-
71
- // Warn for deprecated legend array (when used for labels, not boolean)
72
- if (Array.isArray(config.legend)) {
73
- warnDeprecation(chartId, 'legend (array)', 'y.columns: { key: "Label" }');
74
- }
75
-
76
- // Warn for deprecated scatter-specific keys
77
- if (config.legendTitle !== undefined) {
78
- warnDeprecation(chartId, 'legendTitle', 'series.title');
79
- }
80
- if (config.sizeTitle !== undefined) {
81
- warnDeprecation(chartId, 'sizeTitle', 'size.title');
82
- }
83
-
84
- // Clean up deprecated top-level keys (keep them for backwards compat but don't pass to renderers)
85
- // The renderers will use normalized.x and normalized.y instead
33
+ normalized.y = buildAxisConfig('y', config);
86
34
 
87
35
  return normalized;
88
36
  }
@@ -91,58 +39,22 @@ export function normalizeConfig(config, chartId = 'unknown') {
91
39
  * Build normalized axis configuration
92
40
  * @param {'x' | 'y'} axis - Axis name
93
41
  * @param {Object} config - Raw config
94
- * @param {string} chartId - Chart ID for warnings
95
42
  * @returns {Object} - Normalized axis config
96
43
  */
97
- function buildAxisConfig(axis, config, chartId) {
98
- const axisUpper = axis.toUpperCase();
44
+ function buildAxisConfig(axis, config) {
99
45
  const existingAxisConfig = config[axis] || {};
100
46
 
101
47
  // Start with existing axis config if present
102
48
  const axisConfig = { ...existingAxisConfig };
103
49
 
104
- // max: x.max > maxX > max
105
- if (axisConfig.max === undefined) {
106
- const deprecatedKey = `max${axisUpper}`;
107
- if (config[deprecatedKey] !== undefined) {
108
- warnDeprecation(chartId, deprecatedKey, `${axis}.max`);
109
- axisConfig.max = config[deprecatedKey];
110
- } else if (config.max !== undefined) {
111
- axisConfig.max = config.max;
112
- }
113
- }
114
-
115
- // min: x.min > minX > min
116
- if (axisConfig.min === undefined) {
117
- const deprecatedKey = `min${axisUpper}`;
118
- if (config[deprecatedKey] !== undefined) {
119
- warnDeprecation(chartId, deprecatedKey, `${axis}.min`);
120
- axisConfig.min = config[deprecatedKey];
121
- } else if (config.min !== undefined) {
122
- axisConfig.min = config.min;
123
- }
50
+ // max: x.max > max (global fallback)
51
+ if (axisConfig.max === undefined && config.max !== undefined) {
52
+ axisConfig.max = config.max;
124
53
  }
125
54
 
126
- // title: x.title > titleX
127
- if (axisConfig.title === undefined) {
128
- const deprecatedKey = `title${axisUpper}`;
129
- if (config[deprecatedKey] !== undefined) {
130
- warnDeprecation(chartId, deprecatedKey, `${axis}.title`);
131
- axisConfig.title = config[deprecatedKey];
132
- }
133
- }
134
-
135
- // format: x.format > format.x > format
136
- if (axisConfig.format === undefined) {
137
- const globalFormat = config.format || {};
138
- if (globalFormat[axis] !== undefined) {
139
- // format.x or format.y (deprecated nested format)
140
- warnDeprecation(chartId, `format.${axis}`, `${axis}.format`);
141
- axisConfig.format = globalFormat[axis];
142
- } else if (typeof globalFormat === 'object' && !globalFormat.x && !globalFormat.y) {
143
- // Global format object (no x/y nesting) - use as fallback
144
- axisConfig.format = globalFormat;
145
- }
55
+ // min: x.min > min (global fallback)
56
+ if (axisConfig.min === undefined && config.min !== undefined) {
57
+ axisConfig.min = config.min;
146
58
  }
147
59
 
148
60
  return axisConfig;
@@ -236,20 +148,10 @@ export function parseAxisConfig(axisConfig) {
236
148
  }
237
149
 
238
150
  /**
239
- * Get rotateLabels setting from axis config or deprecated top-level
151
+ * Get rotateLabels setting from axis config
240
152
  * @param {Object} config - Normalized config
241
- * @param {string} chartId - Chart ID for deprecation warnings
242
153
  * @returns {boolean} - Whether to rotate labels
243
154
  */
244
- export function getRotateLabels(config, chartId) {
245
- // New schema: x.rotateLabels
246
- if (config.x?.rotateLabels !== undefined) {
247
- return config.x.rotateLabels;
248
- }
249
- // Deprecated: top-level rotateLabels
250
- if (config.rotateLabels !== undefined) {
251
- warnDeprecation(chartId, 'rotateLabels', 'x.rotateLabels');
252
- return config.rotateLabels;
253
- }
254
- return false;
155
+ export function getRotateLabels(config) {
156
+ return config.x?.rotateLabels ?? false;
255
157
  }