.csv`, etc.
- Implement `fetchFirstAvailable(paths)`; try in order with `cache:'no-cache'`; handle errors gracefully with a red `` message.
- For images or JSON models, mirror the same approach (see `d3-comparison.html`, `d3-neural.html`).
#### 6.1) Data props (HtmlEmbed → embed)
- HtmlEmbed accepts an optional `data` prop that can be a string (single file) or an array of strings (multiple files).
- This prop is passed to the fragment via the `data-datafiles` HTML attribute.
- In the embed script, read this attribute from the closest ancestor that carries it (the `HtmlEmbed` wrapper), not necessarily the chart’s direct container.
- Recommended normalization: if a value contains no slash, automatically prefix it with `/data/` to target the public data folder.
- If `data` is not provided, keep the usual fallback (public, then `assets/data`).
Optional configuration (e.g., default metric):
```js
// In HtmlEmbed usage (MDX):
// In the embed script (read from closest ancestor):
let mountEl = container;
while (mountEl && !mountEl.getAttribute?.('data-datafiles') && !mountEl.getAttribute?.('data-config')) {
mountEl = mountEl.parentElement;
}
let providedConfig = null;
try {
const cfg = mountEl && mountEl.getAttribute ? mountEl.getAttribute('data-config') : null;
if (cfg && cfg.trim()) providedConfig = cfg.trim().startsWith('{') ? JSON.parse(cfg) : cfg;
} catch(_) {}
// Example: selecting initial metric if present
const desired = providedConfig && providedConfig.defaultMetric ? String(providedConfig.defaultMetric) : null;
```
Examples (MDX):
```mdx
```
Reading on the embed side (JS):
```js
// Find the closest ancestor that carries the attribute
let mountEl = container;
while (mountEl && !mountEl.getAttribute?.('data-datafiles')) {
mountEl = mountEl.parentElement;
}
let providedData = null;
try {
const attr = mountEl && mountEl.getAttribute ? mountEl.getAttribute('data-datafiles') : null;
if (attr && attr.trim()) {
providedData = attr.trim().startsWith('[') ? JSON.parse(attr) : attr.trim();
}
} catch(_) {}
const DEFAULT_CSV = '/data/formatting_filters.csv';
const ensureDataPrefix = (p) => (typeof p === 'string' && p && !p.includes('/')) ? `/data/${p}` : p;
const normalizeInput = (inp) => Array.isArray(inp)
? inp.map(ensureDataPrefix)
: (typeof inp === 'string' ? [ ensureDataPrefix(inp) ] : null);
const CSV_PATHS = Array.isArray(providedData)
? normalizeInput(providedData)
: (typeof providedData === 'string' ? normalizeInput(providedData) || [DEFAULT_CSV] : [
DEFAULT_CSV,
'./assets/data/formatting_filters.csv',
'../assets/data/formatting_filters.csv',
'../../assets/data/formatting_filters.csv'
]);
const fetchFirstAvailable = async (paths) => {
for (const p of paths) {
try {
const r = await fetch(p, { cache: 'no-cache' });
if (r.ok) return await r.text();
} catch(_){}
}
throw new Error('CSV not found');
};
```
### 7) Responsiveness and layout
- Compute `width = container.clientWidth`, and a height derived from width (e.g., `width / 3`), with a sensible minimum height.
- Maintain a `margin` object and derive `innerWidth/innerHeight` for plots.
- Use a `ResizeObserver` on the container; fallback to `window.resize`.
- Recompute scales/axes/grid on every render.
### 8) Legends and labels
- Prefer HTML for legends for wrapping and accessibility; avoid SVG-based legends.
- Always add axis labels when applicable (e.g., `Step`, `Value`).
- Standardize legend swatch size: 14×14px, border-radius 3px, 1px border `var(--border-color)`.
#### 8.1) Required legend title: "Legend"
- Always render a visible title above legend items with the exact text "Legend".
- Canonical markup:
```html
```
Minimal CSS (match project styles):
```css
.legend { display:flex; flex-direction:column; align-items:flex-start; gap:6px; }
.legend-title { font-size:12px; font-weight:700; color: var(--text-color); }
.legend .items { display:flex; flex-wrap:wrap; gap:8px 14px; }
.legend .item { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; font-size:12px; color: var(--text-color); }
.legend .swatch { width:14px; height:14px; border-radius:3px; border:1px solid var(--border-color); }
```
Recommended JS pattern to (re)build the legend:
```js
function makeLegend(seriesNames, colorFor) {
let legend = container.querySelector('.legend');
if (!legend) { legend = document.createElement('div'); legend.className = 'legend'; container.appendChild(legend); }
let title = legend.querySelector('.legend-title'); if (!title) { title = document.createElement('div'); title.className = 'legend-title'; title.textContent = 'Legend'; legend.appendChild(title); }
let items = legend.querySelector('.items'); if (!items) { items = document.createElement('div'); items.className = 'items'; legend.appendChild(items); }
items.innerHTML = '';
seriesNames.forEach(name => {
const el = document.createElement('span'); el.className = 'item';
const sw = document.createElement('span'); sw.className = 'swatch'; sw.style.background = colorFor(name);
const txt = document.createElement('span'); txt.textContent = name;
el.appendChild(sw); el.appendChild(txt); items.appendChild(el);
});
}
```
### 9) Accessibility
- Provide `alt` attributes on `
` (see `d3-comparison.html`).
- Provide `aria-label` on interactive buttons (e.g., the erase button in `d3-neural.html`).
- Ensure focus-visible styles for interactive controls; avoid relying on color alone to encode meaning.
### 10) Performance and updates
- Use D3 data joins (`.data().join()` or explicit enter/merge/exit) and keep transitions short (≤200ms).
- Recompute only what is necessary on each render; avoid repeated DOM clears if not needed.
- Debounce or gate expensive computations, especially on `mousemove`.
### 11) External dependencies
- Load D3 (and optional TFJS) via CDN only once using an element id (e.g., `d3-cdn-script`, `tfjs-cdn-script`).
- After `.load`, verify the expected API (e.g., `window.d3.select`).
- Prefer pure D3 and built-ins; do not introduce new runtime dependencies unless necessary.
### 12) Error handling and fallbacks
- Fail gracefully: append a small `` with a readable message inside the container.
- For optional models (e.g., TFJS), attempt multiple URLs and fall back to a heuristic if load fails.
### 13) Printing
- Favor vector (`svg`) or simple shapes; avoid large bitmap backgrounds.
- Let `HtmlEmbed.astro` handle most print constraints; ensure the chart scales with width 100% and auto height.
### 14) Conventions checklist (before committing)
- Root class is unique and matches file name (`d3-`).
- No globals added; script wrapped in an IIFE.
- `data-mounted` guard is present to avoid double-mount.
- Colors come from `window.ColorPalettes` (no hardcoded arrays); `--primary-color` respected.
- Uses CSS variables for colors; dark-mode friendly.
- Responsive: recomputes layout on resize; uses `ResizeObserver`.
- Controls are HTML-only, accessible, and consistently styled.
- Legends and tooltips are HTML, not SVG.
- Data loading includes public-path-first strategy and graceful error.
- Axes/labels are legible at small widths.
- Code is easy to skim: clear naming, early returns, short functions.
### 14.1) Agent Checklist (operational)
- Ensure root: one `` + scoped `
```