eleventy-plugin-uncharted 1.0.0-beta.3 → 1.0.0-beta.4
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 +5 -2
- package/eleventy.config.js +13 -3
- package/package.json +1 -1
- package/src/renderers/bubble.js +3 -3
- package/src/renderers/donut.js +3 -3
- package/src/renderers/line.js +3 -3
- package/src/renderers/sankey.js +3 -3
- package/src/renderers/scatter.js +3 -3
- package/src/renderers/stacked-bar.js +3 -3
- package/src/renderers/stacked-column.js +3 -3
- package/src/renderers/timeseries.js +3 -3
- package/src/utils.js +28 -0
package/css/uncharted.css
CHANGED
|
@@ -1674,8 +1674,11 @@
|
|
|
1674
1674
|
Download Link
|
|
1675
1675
|
========================================================================== */
|
|
1676
1676
|
|
|
1677
|
-
.chart-
|
|
1678
|
-
display: block;
|
|
1677
|
+
.chart-downloads {
|
|
1679
1678
|
font-size: 0.75em;
|
|
1680
1679
|
margin-block-start: 0.5em;
|
|
1681
1680
|
}
|
|
1681
|
+
|
|
1682
|
+
.chart-download-sep {
|
|
1683
|
+
opacity: 0.5;
|
|
1684
|
+
}
|
package/eleventy.config.js
CHANGED
|
@@ -28,6 +28,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
28
28
|
* @param {boolean} [options.dataPassthrough] - Copy CSV files to public dataPath (default: false)
|
|
29
29
|
* @param {string} [options.dataPath] - Public URL path for CSV files (default: '/data/')
|
|
30
30
|
* @param {boolean|string} [options.downloadData] - Enable download links globally (individual charts can override)
|
|
31
|
+
* @param {boolean|string} [options.downloadImage] - Enable image download links globally (requires image generation)
|
|
31
32
|
* @param {Object} [options.image] - Image generation options
|
|
32
33
|
* @param {boolean} [options.image.enabled=false] - Enable PNG image generation
|
|
33
34
|
* @param {string} [options.image.outputDir='/images/charts/'] - Output directory for images (URL path)
|
|
@@ -67,11 +68,11 @@ export default function(eleventyConfig, options = {}) {
|
|
|
67
68
|
const dataPassthrough = options.dataPassthrough ?? false;
|
|
68
69
|
const dataPath = options.dataPath || '/data/';
|
|
69
70
|
const globalDownloadData = options.downloadData ?? false;
|
|
71
|
+
const globalDownloadImage = options.downloadImage ?? false;
|
|
70
72
|
|
|
71
73
|
// Image generation options
|
|
72
74
|
const imageOptions = normalizeImageOptions(options.image);
|
|
73
|
-
const skipImageGeneration = shouldSkipInDevMode(imageOptions)
|
|
74
|
-
process.argv.includes('--skip-images');
|
|
75
|
+
const skipImageGeneration = shouldSkipInDevMode(imageOptions);
|
|
75
76
|
|
|
76
77
|
// Clear image URLs at start of each build
|
|
77
78
|
clearImageUrls();
|
|
@@ -185,6 +186,7 @@ export default function(eleventyConfig, options = {}) {
|
|
|
185
186
|
// Render the chart (chart-specific settings override global)
|
|
186
187
|
const animate = chartConfig.animate ?? globalAnimate;
|
|
187
188
|
const downloadData = chartConfig.downloadData ?? globalDownloadData;
|
|
189
|
+
const downloadImage = chartConfig.downloadImage ?? globalDownloadImage;
|
|
188
190
|
|
|
189
191
|
// Calculate download URL if download is enabled and file is specified
|
|
190
192
|
let downloadDataUrl = null;
|
|
@@ -193,6 +195,13 @@ export default function(eleventyConfig, options = {}) {
|
|
|
193
195
|
downloadDataUrl = normalizedDataPath + chartConfig.file;
|
|
194
196
|
}
|
|
195
197
|
|
|
198
|
+
// Calculate image download URL if enabled and image generation is active
|
|
199
|
+
const chartImageEnabled = chartConfig.image?.enabled ?? imageOptions.enabled;
|
|
200
|
+
let downloadImageUrl = null;
|
|
201
|
+
if (downloadImage && chartImageEnabled) {
|
|
202
|
+
downloadImageUrl = getImageUrl(chartId, chartConfig, imageOptions);
|
|
203
|
+
}
|
|
204
|
+
|
|
196
205
|
// Normalize configuration (handles deprecated keys, axis config)
|
|
197
206
|
const normalizedConfig = normalizeConfig(chartConfig, chartId);
|
|
198
207
|
|
|
@@ -207,6 +216,8 @@ export default function(eleventyConfig, options = {}) {
|
|
|
207
216
|
animate,
|
|
208
217
|
downloadData,
|
|
209
218
|
downloadDataUrl,
|
|
219
|
+
downloadImage,
|
|
220
|
+
downloadImageUrl,
|
|
210
221
|
_columns: columns
|
|
211
222
|
});
|
|
212
223
|
|
|
@@ -218,7 +229,6 @@ export default function(eleventyConfig, options = {}) {
|
|
|
218
229
|
);
|
|
219
230
|
|
|
220
231
|
// Handle image generation
|
|
221
|
-
const chartImageEnabled = chartConfig.image?.enabled ?? imageOptions.enabled;
|
|
222
232
|
if (chartImageEnabled && !skipImageGeneration && eleventyDirs?.output) {
|
|
223
233
|
// Queue chart for image generation
|
|
224
234
|
queueChartForImage(chartId, chartHtml, chartConfig, imageOptions, eleventyDirs.output);
|
package/package.json
CHANGED
package/src/renderers/bubble.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { slugify, escapeHtml,
|
|
1
|
+
import { slugify, escapeHtml, renderDownloadLinks } from '../utils.js';
|
|
2
2
|
import { formatNumber } from '../formatters.js';
|
|
3
3
|
import { getAxisMax, getAxisMin, getAxisTitle, getAxisFormat, getRotateLabels } from '../config.js';
|
|
4
4
|
|
|
@@ -17,7 +17,7 @@ import { getAxisMax, getAxisMin, getAxisTitle, getAxisFormat, getRotateLabels }
|
|
|
17
17
|
* @returns {string} - HTML string
|
|
18
18
|
*/
|
|
19
19
|
export function renderBubble(config) {
|
|
20
|
-
const { title, subtitle, data, legend, animate, format, id, downloadData, downloadDataUrl, icons, _columns } = config;
|
|
20
|
+
const { title, subtitle, data, legend, animate, format, id, downloadData, downloadDataUrl, downloadImage, downloadImageUrl, icons, _columns } = config;
|
|
21
21
|
|
|
22
22
|
// Get axis-specific format configs
|
|
23
23
|
const fmtY = getAxisFormat(config, 'y');
|
|
@@ -262,7 +262,7 @@ export function renderBubble(config) {
|
|
|
262
262
|
html += `</div>`;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
html +=
|
|
265
|
+
html += renderDownloadLinks(downloadDataUrl, downloadData, downloadImageUrl, downloadImage);
|
|
266
266
|
html += `</figure>`;
|
|
267
267
|
|
|
268
268
|
return html;
|
package/src/renderers/donut.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { slugify, escapeHtml, getLabelKey, getValueKey, getSeriesNames,
|
|
1
|
+
import { slugify, escapeHtml, getLabelKey, getValueKey, getSeriesNames, renderDownloadLinks } from '../utils.js';
|
|
2
2
|
import { formatNumber } from '../formatters.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -17,7 +17,7 @@ import { formatNumber } from '../formatters.js';
|
|
|
17
17
|
* @returns {string} - HTML string
|
|
18
18
|
*/
|
|
19
19
|
export function renderDonut(config) {
|
|
20
|
-
const { title, subtitle, data, legend, center, animate, format, id, showPercentages, downloadData, downloadDataUrl, _columns } = config;
|
|
20
|
+
const { title, subtitle, data, legend, center, animate, format, id, showPercentages, downloadData, downloadDataUrl, downloadImage, downloadImageUrl, _columns } = config;
|
|
21
21
|
|
|
22
22
|
if (!data || data.length === 0) {
|
|
23
23
|
return `<!-- Donut chart: no data provided -->`;
|
|
@@ -136,7 +136,7 @@ export function renderDonut(config) {
|
|
|
136
136
|
html += `</div>`;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
html +=
|
|
139
|
+
html += renderDownloadLinks(downloadDataUrl, downloadData, downloadImageUrl, downloadImage);
|
|
140
140
|
html += `</figure>`;
|
|
141
141
|
|
|
142
142
|
return html;
|
package/src/renderers/line.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { slugify, escapeHtml, getLabelKey, getSeriesNames,
|
|
1
|
+
import { slugify, escapeHtml, getLabelKey, getSeriesNames, renderDownloadLinks } from '../utils.js';
|
|
2
2
|
import { formatNumber } from '../formatters.js';
|
|
3
3
|
import { getAxisMax, getAxisMin, getAxisFormat, getAxisTitle, getRotateLabels } from '../config.js';
|
|
4
4
|
|
|
@@ -17,7 +17,7 @@ import { getAxisMax, getAxisMin, getAxisFormat, getAxisTitle, getRotateLabels }
|
|
|
17
17
|
* @returns {string} - HTML string
|
|
18
18
|
*/
|
|
19
19
|
export function renderLine(config) {
|
|
20
|
-
const { title, subtitle, data, max, min, legend, legendTitle, animate, format, id, downloadData, downloadDataUrl, icons, _columns } = config;
|
|
20
|
+
const { title, subtitle, data, max, min, legend, legendTitle, animate, format, id, downloadData, downloadDataUrl, downloadImage, downloadImageUrl, icons, _columns } = config;
|
|
21
21
|
|
|
22
22
|
// Line-specific options
|
|
23
23
|
const showLines = config.lines !== false; // default true
|
|
@@ -211,7 +211,7 @@ export function renderLine(config) {
|
|
|
211
211
|
html += `</div>`;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
html +=
|
|
214
|
+
html += renderDownloadLinks(downloadDataUrl, downloadData, downloadImageUrl, downloadImage);
|
|
215
215
|
html += `</figure>`;
|
|
216
216
|
|
|
217
217
|
return html;
|
package/src/renderers/sankey.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { slugify, escapeHtml,
|
|
1
|
+
import { slugify, escapeHtml, renderDownloadLinks } from '../utils.js';
|
|
2
2
|
import { formatNumber } from '../formatters.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -17,7 +17,7 @@ import { formatNumber } from '../formatters.js';
|
|
|
17
17
|
* @returns {string} - HTML string
|
|
18
18
|
*/
|
|
19
19
|
export function renderSankey(config) {
|
|
20
|
-
const { title, subtitle, data, legend, animate, format, id, downloadData, downloadDataUrl, nodeWidth = 20, nodePadding = 10, endLabelsOutside = false, proportional = false, _columns } = config;
|
|
20
|
+
const { title, subtitle, data, legend, animate, format, id, downloadData, downloadDataUrl, downloadImage, downloadImageUrl, nodeWidth = 20, nodePadding = 10, endLabelsOutside = false, proportional = false, _columns } = config;
|
|
21
21
|
|
|
22
22
|
if (!data || data.length === 0) {
|
|
23
23
|
return `<!-- Sankey chart: no data provided -->`;
|
|
@@ -526,7 +526,7 @@ export function renderSankey(config) {
|
|
|
526
526
|
html += `</div>`;
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
-
html +=
|
|
529
|
+
html += renderDownloadLinks(downloadDataUrl, downloadData, downloadImageUrl, downloadImage);
|
|
530
530
|
html += `</figure>`;
|
|
531
531
|
|
|
532
532
|
return html;
|
package/src/renderers/scatter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { slugify, escapeHtml,
|
|
1
|
+
import { slugify, escapeHtml, renderDownloadLinks } from '../utils.js';
|
|
2
2
|
import { formatNumber } from '../formatters.js';
|
|
3
3
|
import { getAxisMax, getAxisMin, getAxisTitle, getAxisFormat } from '../config.js';
|
|
4
4
|
|
|
@@ -18,7 +18,7 @@ import { getAxisMax, getAxisMin, getAxisTitle, getAxisFormat } from '../config.j
|
|
|
18
18
|
* @returns {string} - HTML string
|
|
19
19
|
*/
|
|
20
20
|
export function renderScatter(config) {
|
|
21
|
-
const { title, subtitle, data, legend, animate, format, id, downloadData, downloadDataUrl, proportional, icons, _columns } = config;
|
|
21
|
+
const { title, subtitle, data, legend, animate, format, id, downloadData, downloadDataUrl, downloadImage, downloadImageUrl, proportional, icons, _columns } = config;
|
|
22
22
|
|
|
23
23
|
// Get axis-specific format configs (normalized config provides x.format/y.format)
|
|
24
24
|
const fmtX = getAxisFormat(config, 'x');
|
|
@@ -259,7 +259,7 @@ export function renderScatter(config) {
|
|
|
259
259
|
html += `</div>`;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
html +=
|
|
262
|
+
html += renderDownloadLinks(downloadDataUrl, downloadData, downloadImageUrl, downloadImage);
|
|
263
263
|
html += `</figure>`;
|
|
264
264
|
|
|
265
265
|
return html;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { slugify, calculatePercentages, getLabelKey, getSeriesNames, escapeHtml,
|
|
1
|
+
import { slugify, calculatePercentages, getLabelKey, getSeriesNames, escapeHtml, renderDownloadLinks } from '../utils.js';
|
|
2
2
|
import { formatNumber } from '../formatters.js';
|
|
3
3
|
import { getAxisMax, getAxisFormat } from '../config.js';
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ import { getAxisMax, getAxisFormat } from '../config.js';
|
|
|
15
15
|
* @returns {string} - HTML string
|
|
16
16
|
*/
|
|
17
17
|
export function renderStackedBar(config) {
|
|
18
|
-
const { title, subtitle, data, max, legend, legendTitle, animate, format, id, downloadData, downloadDataUrl, _columns } = config;
|
|
18
|
+
const { title, subtitle, data, max, legend, legendTitle, animate, format, id, downloadData, downloadDataUrl, downloadImage, downloadImageUrl, _columns } = config;
|
|
19
19
|
|
|
20
20
|
if (!data || data.length === 0) {
|
|
21
21
|
return `<!-- Stacked bar chart: no data provided -->`;
|
|
@@ -114,7 +114,7 @@ export function renderStackedBar(config) {
|
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
html += `</div>`;
|
|
117
|
-
html +=
|
|
117
|
+
html += renderDownloadLinks(downloadDataUrl, downloadData, downloadImageUrl, downloadImage);
|
|
118
118
|
html += `</figure>`;
|
|
119
119
|
|
|
120
120
|
return html;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { slugify, getLabelKey, getSeriesNames, escapeHtml,
|
|
1
|
+
import { slugify, getLabelKey, getSeriesNames, escapeHtml, renderDownloadLinks } from '../utils.js';
|
|
2
2
|
import { formatNumber } from '../formatters.js';
|
|
3
3
|
import { getAxisMax, getAxisMin, getAxisFormat, getRotateLabels } from '../config.js';
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ import { getAxisMax, getAxisMin, getAxisFormat, getRotateLabels } from '../confi
|
|
|
15
15
|
* @returns {string} - HTML string
|
|
16
16
|
*/
|
|
17
17
|
export function renderStackedColumn(config) {
|
|
18
|
-
const { title, subtitle, data, max, min, legend, animate, format, id, downloadData, downloadDataUrl, _columns } = config;
|
|
18
|
+
const { title, subtitle, data, max, min, legend, animate, format, id, downloadData, downloadDataUrl, downloadImage, downloadImageUrl, _columns } = config;
|
|
19
19
|
|
|
20
20
|
if (!data || data.length === 0) {
|
|
21
21
|
return `<!-- Stacked column chart: no data provided -->`;
|
|
@@ -208,7 +208,7 @@ export function renderStackedColumn(config) {
|
|
|
208
208
|
html += `</div>`;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
html +=
|
|
211
|
+
html += renderDownloadLinks(downloadDataUrl, downloadData, downloadImageUrl, downloadImage);
|
|
212
212
|
html += `</figure>`;
|
|
213
213
|
|
|
214
214
|
return html;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { slugify, escapeHtml, getLabelKey, getSeriesNames,
|
|
1
|
+
import { slugify, escapeHtml, getLabelKey, getSeriesNames, renderDownloadLinks } from '../utils.js';
|
|
2
2
|
import { formatNumber } from '../formatters.js';
|
|
3
3
|
import { getAxisMax, getAxisMin, getAxisTitle, getAxisFormat, getRotateLabels } from '../config.js';
|
|
4
4
|
|
|
@@ -237,7 +237,7 @@ function getAxisTicks(min, max, isDate) {
|
|
|
237
237
|
* @returns {string} - HTML string
|
|
238
238
|
*/
|
|
239
239
|
export function renderTimeseries(config) {
|
|
240
|
-
const { title, subtitle, data, legend, legendTitle, animate, format, id, downloadData, downloadDataUrl, dots: showDots = false, icons, _columns } = config;
|
|
240
|
+
const { title, subtitle, data, legend, legendTitle, animate, format, id, downloadData, downloadDataUrl, downloadImage, downloadImageUrl, dots: showDots = false, icons, _columns } = config;
|
|
241
241
|
|
|
242
242
|
if (!data || data.length === 0) {
|
|
243
243
|
return `<!-- Timeseries chart: no data provided -->`;
|
|
@@ -498,7 +498,7 @@ export function renderTimeseries(config) {
|
|
|
498
498
|
html += `</div>`;
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
-
html +=
|
|
501
|
+
html += renderDownloadLinks(downloadDataUrl, downloadData, downloadImageUrl, downloadImage);
|
|
502
502
|
html += `</figure>`;
|
|
503
503
|
|
|
504
504
|
return html;
|
package/src/utils.js
CHANGED
|
@@ -82,3 +82,31 @@ export function renderDownloadLink(url, label) {
|
|
|
82
82
|
return `<a href="${escapeHtml(url)}" class="chart-download" download>${escapeHtml(text)}</a>`;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Render a download link for chart image
|
|
87
|
+
* @param {string} url - URL to the image file
|
|
88
|
+
* @param {boolean|string} label - true for default label, or custom string
|
|
89
|
+
* @returns {string} - HTML string for the download link, or empty string if no URL
|
|
90
|
+
*/
|
|
91
|
+
export function renderDownloadImageLink(url, label) {
|
|
92
|
+
if (!url) return '';
|
|
93
|
+
const text = typeof label === 'string' ? label : '↓ Download image';
|
|
94
|
+
return `<a href="${escapeHtml(url)}" class="chart-download" download>${escapeHtml(text)}</a>`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Render download links container (wraps data and image download links)
|
|
99
|
+
* @param {string} dataUrl - URL to the CSV file
|
|
100
|
+
* @param {boolean|string} dataLabel - true for default label, or custom string
|
|
101
|
+
* @param {string} imageUrl - URL to the image file
|
|
102
|
+
* @param {boolean|string} imageLabel - true for default label, or custom string
|
|
103
|
+
* @returns {string} - HTML string for the download links container, or empty string if no URLs
|
|
104
|
+
*/
|
|
105
|
+
export function renderDownloadLinks(dataUrl, dataLabel, imageUrl, imageLabel) {
|
|
106
|
+
const dataLink = renderDownloadLink(dataUrl, dataLabel);
|
|
107
|
+
const imageLink = renderDownloadImageLink(imageUrl, imageLabel);
|
|
108
|
+
if (!dataLink && !imageLink) return '';
|
|
109
|
+
const separator = dataLink && imageLink ? ' <span class="chart-download-sep">•</span> ' : '';
|
|
110
|
+
return `<div class="chart-downloads">${dataLink}${separator}${imageLink}</div>`;
|
|
111
|
+
}
|
|
112
|
+
|