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 +15 -5
- package/eleventy.config.js +103 -1
- package/package.json +8 -2
- package/src/columns.js +8 -64
- package/src/config.js +21 -119
- package/src/deprecation.js +111 -0
- package/src/image/index.js +205 -0
- package/src/image/queue.js +62 -0
- package/src/image/renderer.js +277 -0
- package/src/renderers/bubble.js +12 -12
- package/src/renderers/donut.js +3 -4
- package/src/renderers/index.js +1 -3
- package/src/renderers/line.js +215 -3
- package/src/renderers/scatter.js +19 -8
- package/src/renderers/stacked-bar.js +4 -5
- package/src/renderers/stacked-column.js +5 -6
- package/src/renderers/timeseries.js +8 -6
- package/src/renderers/dot.js +0 -222
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
|
|
package/eleventy.config.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 (
|
|
324
|
-
//
|
|
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
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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
|
|
49
|
-
*
|
|
50
|
-
* 2. columns.x / columns.y (deprecated format)
|
|
14
|
+
* Column mapping:
|
|
15
|
+
* - x.column / x.columns / y.column / y.columns
|
|
51
16
|
*
|
|
52
|
-
* Legend
|
|
53
|
-
*
|
|
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
|
|
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
|
|
30
|
+
normalized.x = buildAxisConfig('x', config);
|
|
67
31
|
|
|
68
32
|
// Build y axis config
|
|
69
|
-
normalized.y = buildAxisConfig('y', config
|
|
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
|
|
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 >
|
|
105
|
-
if (axisConfig.max === undefined) {
|
|
106
|
-
|
|
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
|
-
//
|
|
127
|
-
if (axisConfig.
|
|
128
|
-
|
|
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
|
|
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
|
|
245
|
-
|
|
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
|
}
|