eleventy-plugin-uncharted 0.7.4 → 0.8.0
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 +61 -1
- package/eleventy.config.js +8 -13
- package/package.json +1 -1
- package/src/config.js +0 -5
- package/src/renderers/dot.js +27 -6
- package/src/renderers/scatter.js +23 -4
package/css/uncharted.css
CHANGED
|
@@ -160,6 +160,20 @@
|
|
|
160
160
|
margin-bottom: 0.375rem;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/* ==========================================================================
|
|
164
|
+
Icons (Font Awesome support for dots and legends)
|
|
165
|
+
========================================================================== */
|
|
166
|
+
|
|
167
|
+
/* Legend items with icons - hide the default ::before marker */
|
|
168
|
+
.chart-legend-item.has-icon::before {
|
|
169
|
+
display: none;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.chart-legend-item.has-icon i {
|
|
173
|
+
color: var(--color);
|
|
174
|
+
margin-inline-end: 0.35em;
|
|
175
|
+
}
|
|
176
|
+
|
|
163
177
|
/* ==========================================================================
|
|
164
178
|
Axes
|
|
165
179
|
========================================================================== */
|
|
@@ -560,7 +574,6 @@
|
|
|
560
574
|
left: 0.5rem;
|
|
561
575
|
display: flex;
|
|
562
576
|
align-items: stretch;
|
|
563
|
-
gap: 6px;
|
|
564
577
|
}
|
|
565
578
|
|
|
566
579
|
:is(.chart-dot, .chart-line) .dot-col {
|
|
@@ -586,6 +599,34 @@
|
|
|
586
599
|
z-index: 1;
|
|
587
600
|
}
|
|
588
601
|
|
|
602
|
+
/* Dots with icons */
|
|
603
|
+
:is(.chart-dot, .chart-line) .dot.has-icon {
|
|
604
|
+
background: transparent;
|
|
605
|
+
display: flex;
|
|
606
|
+
align-items: center;
|
|
607
|
+
justify-content: center;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
:is(.chart-dot, .chart-line) .dot.has-icon i {
|
|
611
|
+
color: var(--color);
|
|
612
|
+
font-size: var(--chart-dot-size);
|
|
613
|
+
line-height: 1;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* Line chart icons: clip the line ends around the icons */
|
|
617
|
+
.chart-line:has(.dot.has-icon) .chart-line-segment {
|
|
618
|
+
--clip-size: calc(var(--chart-dot-size) * 0.75);
|
|
619
|
+
mask-image: linear-gradient(
|
|
620
|
+
to right,
|
|
621
|
+
transparent 0,
|
|
622
|
+
transparent var(--clip-size),
|
|
623
|
+
black var(--clip-size),
|
|
624
|
+
black calc(100% - var(--clip-size)),
|
|
625
|
+
transparent calc(100% - var(--clip-size)),
|
|
626
|
+
transparent 100%
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
|
|
589
630
|
:is(.chart-dot, .chart-line) .dot-labels {
|
|
590
631
|
grid-row: 2;
|
|
591
632
|
display: flex;
|
|
@@ -702,6 +743,20 @@
|
|
|
702
743
|
z-index: 1;
|
|
703
744
|
}
|
|
704
745
|
|
|
746
|
+
/* Scatter dots with icons */
|
|
747
|
+
.chart-scatter .dot.has-icon {
|
|
748
|
+
background: transparent;
|
|
749
|
+
display: flex;
|
|
750
|
+
align-items: center;
|
|
751
|
+
justify-content: center;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.chart-scatter .dot.has-icon i {
|
|
755
|
+
color: var(--color);
|
|
756
|
+
font-size: var(--chart-dot-size);
|
|
757
|
+
line-height: 1;
|
|
758
|
+
}
|
|
759
|
+
|
|
705
760
|
/* Variable-sized dots (size column) */
|
|
706
761
|
.chart-scatter .dot[style*="--size-scale"] {
|
|
707
762
|
--computed-size: calc(
|
|
@@ -712,6 +767,11 @@
|
|
|
712
767
|
height: var(--computed-size);
|
|
713
768
|
}
|
|
714
769
|
|
|
770
|
+
/* Variable-sized dots with icons */
|
|
771
|
+
.chart-scatter .dot.has-icon[style*="--size-scale"] i {
|
|
772
|
+
font-size: var(--computed-size);
|
|
773
|
+
}
|
|
774
|
+
|
|
715
775
|
/* Size legend */
|
|
716
776
|
.chart-size-legend {
|
|
717
777
|
display: flex;
|
package/eleventy.config.js
CHANGED
|
@@ -11,7 +11,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
11
11
|
* Uncharted - Eleventy CSS Charts Plugin
|
|
12
12
|
* @param {Object} eleventyConfig - Eleventy configuration object
|
|
13
13
|
* @param {Object} [options] - Plugin options
|
|
14
|
-
* @param {string} [options.dataDir] -
|
|
14
|
+
* @param {string} [options.dataDir] - Data directory path relative to root (e.g., '_data/charts'). Defaults to Eleventy's dir.data config.
|
|
15
15
|
* @param {boolean} [options.animate] - Enable animations globally (individual charts can override)
|
|
16
16
|
* @param {string} [options.cssPath] - Output path for stylesheet (default: '/css/uncharted.css')
|
|
17
17
|
* @param {boolean} [options.injectCss] - Automatically copy and inject CSS (default: true)
|
|
@@ -28,14 +28,9 @@ export default function(eleventyConfig, options = {}) {
|
|
|
28
28
|
eleventyDirs = dirs;
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
// Warn if deprecated dataDir option is used
|
|
32
|
-
if (options.dataDir) {
|
|
33
|
-
console.warn('[uncharted] "dataDir" option is deprecated; the plugin now auto-detects from Eleventy\'s dir.data config');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
31
|
// Helper to resolve data directory
|
|
37
32
|
function getDataDir() {
|
|
38
|
-
// Plugin option takes precedence if explicitly set
|
|
33
|
+
// Plugin option takes precedence if explicitly set
|
|
39
34
|
if (options.dataDir) {
|
|
40
35
|
return path.resolve(process.cwd(), options.dataDir);
|
|
41
36
|
}
|
|
@@ -93,13 +88,13 @@ export default function(eleventyConfig, options = {}) {
|
|
|
93
88
|
});
|
|
94
89
|
}
|
|
95
90
|
|
|
96
|
-
// CSV data passthrough for download links
|
|
91
|
+
// CSV data passthrough for download links
|
|
92
|
+
// When dataDir is set explicitly, use it; otherwise use Eleventy's default _data
|
|
97
93
|
if (dataPassthrough) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
});
|
|
94
|
+
const dataDirForPassthrough = options.dataDir || '_data';
|
|
95
|
+
const destPath = dataPath.replace(/^\//, '').replace(/\/$/, '');
|
|
96
|
+
eleventyConfig.addPassthroughCopy({
|
|
97
|
+
[dataDirForPassthrough]: destPath
|
|
103
98
|
});
|
|
104
99
|
}
|
|
105
100
|
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -68,11 +68,6 @@ export function normalizeConfig(config, chartId = 'unknown') {
|
|
|
68
68
|
// Build y axis config
|
|
69
69
|
normalized.y = buildAxisConfig('y', config, chartId);
|
|
70
70
|
|
|
71
|
-
// Warn for deprecated columns key
|
|
72
|
-
if (config.columns) {
|
|
73
|
-
warnDeprecation(chartId, 'columns', 'x.column / y.columns');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
71
|
// Warn for deprecated legend array (when used for labels, not boolean)
|
|
77
72
|
if (Array.isArray(config.legend)) {
|
|
78
73
|
warnDeprecation(chartId, 'legend (array)', 'y.columns: { key: "Label" }');
|
package/src/renderers/dot.js
CHANGED
|
@@ -16,7 +16,7 @@ import { getAxisMax, getAxisMin, getAxisFormat, getRotateLabels } from '../confi
|
|
|
16
16
|
* @returns {string} - HTML string
|
|
17
17
|
*/
|
|
18
18
|
export function renderDot(config) {
|
|
19
|
-
const { title, subtitle, data, max, min, legend, legendTitle, animate, format, id, downloadData, downloadDataUrl, connectDots, dots: showDots = true, chartType = 'dot', _columns } = config;
|
|
19
|
+
const { title, subtitle, data, max, min, legend, legendTitle, animate, format, id, downloadData, downloadDataUrl, connectDots, dots: showDots = true, icons, chartType = 'dot', _columns } = config;
|
|
20
20
|
|
|
21
21
|
if (!data || data.length === 0) {
|
|
22
22
|
return `<!-- Dot chart: no data provided -->`;
|
|
@@ -34,6 +34,13 @@ export function renderDot(config) {
|
|
|
34
34
|
return key;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
// Helper to get icon for a series
|
|
38
|
+
const getSeriesIcon = (key) => {
|
|
39
|
+
if (!icons) return null;
|
|
40
|
+
if (typeof icons === 'string') return icons;
|
|
41
|
+
return icons[key] ?? null;
|
|
42
|
+
};
|
|
43
|
+
|
|
37
44
|
const animateClass = animate ? ' chart-animate' : '';
|
|
38
45
|
const rotateLabels = getRotateLabels(config, config.id);
|
|
39
46
|
|
|
@@ -62,7 +69,8 @@ export function renderDot(config) {
|
|
|
62
69
|
const negativeClass = hasNegativeY ? ' has-negative-y' : '';
|
|
63
70
|
const idClass = id ? ` chart-${id}` : '';
|
|
64
71
|
const rotateClass = rotateLabels ? ' rotate-labels' : '';
|
|
65
|
-
|
|
72
|
+
// Only add no-dots class if dots are disabled AND no icons are set
|
|
73
|
+
const dotsClass = (!showDots && !icons) ? ' no-dots' : '';
|
|
66
74
|
let html = `<figure class="chart chart-${chartType}${animateClass}${negativeClass}${idClass}${rotateClass}${dotsClass}">`;
|
|
67
75
|
|
|
68
76
|
if (title) {
|
|
@@ -120,7 +128,8 @@ export function renderDot(config) {
|
|
|
120
128
|
}
|
|
121
129
|
|
|
122
130
|
// Each row becomes a column with dots for each series
|
|
123
|
-
if (
|
|
131
|
+
// Show dots if explicitly enabled OR if icons are set (icons override dots: false)
|
|
132
|
+
if (showDots || icons) {
|
|
124
133
|
data.forEach((row, colIndex) => {
|
|
125
134
|
const label = row[labelKey] ?? '';
|
|
126
135
|
|
|
@@ -133,11 +142,17 @@ export function renderDot(config) {
|
|
|
133
142
|
const colorClass = `chart-color-${i + 1}`;
|
|
134
143
|
const seriesClass = `chart-series-${slugify(key)}`;
|
|
135
144
|
const tooltipLabel = getSeriesLabel(key, i);
|
|
145
|
+
const icon = getSeriesIcon(key);
|
|
146
|
+
const iconClass = icon ? ' has-icon' : '';
|
|
136
147
|
|
|
137
|
-
html += `<div class="dot ${colorClass} ${seriesClass}" `;
|
|
148
|
+
html += `<div class="dot ${colorClass} ${seriesClass}${iconClass}" `;
|
|
138
149
|
html += `style="--value: ${yPct.toFixed(2)}%" `;
|
|
139
150
|
html += `title="${escapeHtml(tooltipLabel)}: ${formatNumber(value, yFormat) || value}"`;
|
|
140
|
-
html +=
|
|
151
|
+
html += `>`;
|
|
152
|
+
if (icon) {
|
|
153
|
+
html += `<i class="${escapeHtml(icon)}"></i>`;
|
|
154
|
+
}
|
|
155
|
+
html += `</div>`;
|
|
141
156
|
});
|
|
142
157
|
|
|
143
158
|
html += `</div>`;
|
|
@@ -169,7 +184,13 @@ export function renderDot(config) {
|
|
|
169
184
|
const label = getSeriesLabel(key, i);
|
|
170
185
|
const colorClass = `chart-color-${i + 1}`;
|
|
171
186
|
const seriesClass = `chart-series-${slugify(key)}`;
|
|
172
|
-
|
|
187
|
+
const icon = getSeriesIcon(key);
|
|
188
|
+
const iconClass = icon ? ' has-icon' : '';
|
|
189
|
+
html += `<span class="chart-legend-item ${colorClass} ${seriesClass}${iconClass}">`;
|
|
190
|
+
if (icon) {
|
|
191
|
+
html += `<i class="${escapeHtml(icon)}"></i>`;
|
|
192
|
+
}
|
|
193
|
+
html += `${escapeHtml(label)}</span>`;
|
|
173
194
|
});
|
|
174
195
|
html += `</div>`;
|
|
175
196
|
}
|
package/src/renderers/scatter.js
CHANGED
|
@@ -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, _columns } = config;
|
|
21
|
+
const { title, subtitle, data, legend, animate, format, id, downloadData, downloadDataUrl, 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');
|
|
@@ -115,6 +115,13 @@ export function renderScatter(config) {
|
|
|
115
115
|
const seriesList = Array.from(seriesSet);
|
|
116
116
|
const seriesIndex = new Map(seriesList.map((s, i) => [s, i]));
|
|
117
117
|
|
|
118
|
+
// Helper to get icon for a series
|
|
119
|
+
const getSeriesIcon = (seriesName) => {
|
|
120
|
+
if (!icons) return null;
|
|
121
|
+
if (typeof icons === 'string') return icons;
|
|
122
|
+
return icons[seriesName] ?? null;
|
|
123
|
+
};
|
|
124
|
+
|
|
118
125
|
const negativeClasses = (hasNegativeX ? ' has-negative-x' : '') + (hasNegativeY ? ' has-negative-y' : '');
|
|
119
126
|
const proportionalClass = proportional ? ' chart-proportional' : '';
|
|
120
127
|
const idClass = id ? ` chart-${id}` : '';
|
|
@@ -159,6 +166,8 @@ export function renderScatter(config) {
|
|
|
159
166
|
const seriesClass = `chart-series-${slugify(dot.series)}`;
|
|
160
167
|
const fmtXVal = formatNumber(dot.x, fmtX) || dot.x;
|
|
161
168
|
const fmtYVal = formatNumber(dot.y, fmtY) || dot.y;
|
|
169
|
+
const icon = getSeriesIcon(dot.series);
|
|
170
|
+
const iconClass = icon ? ' has-icon' : '';
|
|
162
171
|
|
|
163
172
|
// Build tooltip with optional size value
|
|
164
173
|
let tooltipText = dot.label ? `${dot.label}: (${fmtXVal}, ${fmtYVal})` : `(${fmtXVal}, ${fmtYVal})`;
|
|
@@ -173,10 +182,14 @@ export function renderScatter(config) {
|
|
|
173
182
|
styleStr += `; --size-scale: ${dot.sizeScale.toFixed(4)}`;
|
|
174
183
|
}
|
|
175
184
|
|
|
176
|
-
html += `<div class="dot ${colorClass} ${seriesClass}" `;
|
|
185
|
+
html += `<div class="dot ${colorClass} ${seriesClass}${iconClass}" `;
|
|
177
186
|
html += `style="${styleStr}" `;
|
|
178
187
|
html += `title="${escapeHtml(tooltipText)}"`;
|
|
179
|
-
html +=
|
|
188
|
+
html += `>`;
|
|
189
|
+
if (icon) {
|
|
190
|
+
html += `<i class="${escapeHtml(icon)}"></i>`;
|
|
191
|
+
}
|
|
192
|
+
html += `</div>`;
|
|
180
193
|
});
|
|
181
194
|
|
|
182
195
|
html += `</div>`;
|
|
@@ -206,7 +219,13 @@ export function renderScatter(config) {
|
|
|
206
219
|
const label = legendLabels[i] ?? series;
|
|
207
220
|
const colorClass = `chart-color-${i + 1}`;
|
|
208
221
|
const seriesClass = `chart-series-${slugify(series)}`;
|
|
209
|
-
|
|
222
|
+
const icon = getSeriesIcon(series);
|
|
223
|
+
const iconClass = icon ? ' has-icon' : '';
|
|
224
|
+
html += `<span class="chart-legend-item ${colorClass} ${seriesClass}${iconClass}">`;
|
|
225
|
+
if (icon) {
|
|
226
|
+
html += `<i class="${escapeHtml(icon)}"></i>`;
|
|
227
|
+
}
|
|
228
|
+
html += `${escapeHtml(label)}</span>`;
|
|
210
229
|
});
|
|
211
230
|
html += `</div>`;
|
|
212
231
|
}
|