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 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;
@@ -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] - @deprecated Data directory path (now auto-detected from Eleventy's dir.data)
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 (deprecated)
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 (set up after directories are known)
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
- eleventyConfig.on('eleventy.directories', () => {
99
- const resolvedDataDir = getDataDir();
100
- eleventyConfig.addPassthroughCopy({
101
- [resolvedDataDir]: dataPath.replace(/^\//, '').replace(/\/$/, '')
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eleventy-plugin-uncharted",
3
- "version": "0.7.4",
3
+ "version": "0.8.0",
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",
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" }');
@@ -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
- const dotsClass = !showDots ? ' no-dots' : '';
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 (showDots) {
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 += `></div>`;
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
- html += `<span class="chart-legend-item ${colorClass} ${seriesClass}">${escapeHtml(label)}</span>`;
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
  }
@@ -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 += `></div>`;
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
- html += `<span class="chart-legend-item ${colorClass} ${seriesClass}">${escapeHtml(label)}</span>`;
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
  }