tfrere HF Staff commited on
Commit
639d61f
·
verified ·
1 Parent(s): e730958

## Embed Chart Authoring Guidelines

Browse files

### Quickstart (TL;DR)
- Create a single self-contained HTML fragment: root div + scoped style + IIFE script.
- Draw marks/axes in SVG; render UI (legend and controls) in HTML.
- Place legend and controls BELOW the chart (header appended after the chart). Include a legend title "Legend" and a select labeled "Metric" when relevant.
- Load data from public `/data` first, then fall back to `assets/data`.
- Use `window.ColorPalettes` for colors; stick to CSS variables for theming.

Minimal header markup:
```html
<div class="legend">
<div class="legend-title">Legend</div>
<div class="items"></div>
<!-- items populated by JS: <span class="item"><span class="swatch"></span><span>Name</span></span> -->
</div>
<div class="controls">
<div class="control-group">
<label for="metric-select-<id>">Metric</label>
<select id="metric-select-<id>"></select>
</div>
<!-- optional: other controls -->
</div>
```

See also: `d3-line-simple.html`, `d3-line-quad.html`, `d3-benchmark.html`.

Authoring rules for creating a new interactive chart as a single self-contained `.html` file under `src/content/embeds/`. These conventions are derived from `d3-bar.html`, `d3-comparison.html`, `d3-neural.html`, `d3-line.html`, and `d3-pie.html`.

### A) Colors & palettes (MANDATORY)
- Always obtain color arrays from `window.ColorPalettes`; do not hardcode palettes.
- Use the categorical/sequential/diverging helpers and the current primary color.
- If you change `--primary-color` dynamically, call `window.ColorPalettes.refresh()` so listeners update.

Usage:
```js
// Usage (with explicit counts)
const cat = window.ColorPalettes.getColors('categorical', 8);
const seq = window.ColorPalettes.getColors('sequential', 8);
const div = window.ColorPalettes.getColors('diverging', 7);

// For current primary color string
const primaryHex = window.ColorPalettes.getPrimary();

// If you change --primary-color dynamically, call refresh to notify listeners
document.documentElement.style.setProperty('--primary-color', '#6D4AFF');
window.ColorPalettes.refresh();
```

Notes:
- Keep chart accents (lines, markers, selection) aligned with `--primary-color`.
- Prefer CSS variables for fills/strokes when possible; derive series colors via `ColorPalettes`.
- Provide a graceful fallback to CSS variables if `window.ColorPalettes` is unavailable.

### B) Layout & form elements (HTML-only)
- All UI controls (labels, selects, sliders, buttons, toggles) must be plain HTML inside the root container.
- Do not draw controls with SVG; style them consistently (rounded 8px, custom caret, focus ring).
- Use `<label>` wrapping inputs for accessibility and concise text (e.g., "Metric", "Model Size").
- Manage layout with CSS inside the scoped `<style>` for the root class; avoid global rules.

### C) SVG scope: charts only; UI in HTML
- SVG is for chart primitives (marks, axes, gridlines) only.
- Put legends and controls in HTML (adjacent DOM is preferred; `foreignObject` only if necessary).
- Tooltips are HTML positioned absolutely inside the root container.
- Details: see sections 4 (controls), 5 (tooltips), 8 (legends).

### 1) File, naming, and structure
- Name files with a clear prefix and purpose: `d3-<type>.html` (e.g., `d3-scatter.html`).
- Wrap everything in a single `<div class="<root-class>">`, a `<style>` block scoped to that root class, and a `<script>` IIFE.
- Do not leak globals; do not attach anything to `window`.
- Use a unique, descriptive root class (e.g., `.d3-scatter`).

Minimal skeleton:
```html
<div class="d3-yourchart"></div>
<style>
.d3-yourchart {/* all styles scoped to the root */}
</style>
<script>
(() => {
// Optional dependency loader (e.g., D3)
const ensureD3 = (cb) => {
if (window.d3 && typeof window.d3.select === 'function') return cb();
let s = document.getElementById('d3-cdn-script');
if (!s) { s = document.createElement('script'); s.id = 'd3-cdn-script'; s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js'; document.head.appendChild(s); }
const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };
s.addEventListener('load', onReady, { once: true });
if (window.d3) onReady();
};

const bootstrap = () => {
const scriptEl = document.currentScript;
// Prefer the closest previous sibling with the root class
let container = scriptEl ? scriptEl.previousElementSibling : null;
if (!(container && container.classList && container.classList.contains('d3-yourchart'))) {
// Fallback: pick the last unmounted instance in the page
const candidates = Array.from(document.querySelectorAll('.d3-yourchart'))
.filter((el) => !(el.dataset && el.dataset.mounted === 'true'));
container = candidates[candidates.length - 1] || null;
}
if (!container) return;
if (container.dataset) {
if (container.dataset.mounted === 'true') return;
container.dataset.mounted = 'true';
}

// Tooltip (optional)
container.style.position = container.style.position || 'relative';
let tip = container.querySelector('.d3-tooltip'); let tipInner;
if (!tip) {
tip = document.createElement('div'); tip.className = 'd3-tooltip';
Object.assign(tip.style, { position:'absolute', top:'0px', left:'0px', transform:'translate(-9999px, -9999px)', pointerEvents:'none', padding:'8px 10px', borderRadius:'8px', fontSize:'12px', lineHeight:'1.35', border:'1px solid var(--border-color)', background:'var(--surface-bg)', color:'var(--text-color)', boxShadow:'0 4px 24px rgba(0,0,0,.18)', opacity:'0', transition:'opacity .12s ease' });
tipInner = document.createElement('div'); tipInner.className = 'd3-tooltip__inner'; tipInner.style.textAlign='left'; tip.appendChild(tipInner); container.appendChild(tip);
} else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; }

// SVG scaffolding (if using D3)
const svg = d3.select(container).append('svg').attr('width','100%').style('display','block');
const gRoot = svg.append('g');

// State & layout
let width = 800, height = 360; const margin = { top: 16, right: 28, bottom: 56, left: 64 };
function updateSize(){
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
width = container.clientWidth || 800;
height = Math.max(260, Math.round(width / 3));
svg.attr('width', width).attr('height', height);
gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
return { innerWidth: width - margin.left - margin.right, innerHeight: height - margin.top - margin.bottom, isDark };
}

function render(){
const { innerWidth, innerHeight } = updateSize();
// ... draw/update your chart here using data joins
}

// Initial render + resize handling
render();
const rerender = () => render();
if (window.ResizeObserver) { const ro = new ResizeObserver(() => rerender()); ro.observe(container); }
else { window.addEventListener('resize', rerender); }
};

if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); }
else { ensureD3(bootstrap); }
})();
</script>
```

### 2) Mounting and re-entrancy
- Select the closest previous sibling with the root class; fallback to the last unmounted matching element in the document.
- Gate with `data-mounted` to avoid double-initialization when the fragment re-runs.
- Assume the chart can appear multiple times on the same page.

### 3) Styling and theming
- Scope all rules under the root class; do not style `body`, `svg` globally.
- Use CSS variables for theme alignment: `--primary-color`, `--text-color`, `--muted-color`, `--surface-bg`, `--border-color`.
- Derive palette colors from `window.ColorPalettes` (categorical, sequential, diverging); do not hardcode arrays.
- For dark mode–aware strokes/ticks, either:
- Read `document.documentElement.getAttribute('data-theme') === 'dark'`, or
- Prefer CSS-only where possible.
- Keep backgrounds light and borders subtle; the outer card frame is handled by `HtmlEmbed.astro`.

Standard axis/tick/grid colors (global variables from `_variables.css`):

```css
/* Provided globally */
:root {
--axis-color: var(--text-color);
--tick-color: var(--muted-color);
--grid-color: rgba(0,0,0,.08);
}
[data-theme="dark"] {
--axis-color: var(--text-color);
--tick-color: var(--muted-color);
--grid-color: rgba(255,255,255,.10);
}
/* Apply inside charts */
.your-root-class .axes path,
.your-root-class .axes line { stroke: var(--axis-color); }
.your-root-class .axes text { fill: var(--tick-color); }
.your-root-class .grid line { stroke: var(--grid-color); }
```

#### 3.1) Text on fixed-colored backgrounds

- When rendering text over cells/areas with fixed background colors (that do not change with theme), compute a readable text style once from the actual background color.
- Use `window.ColorPalettes.getTextStyleForBackground(bgCss, { blend: 0.6 })` when available; avoid tying text color to dark mode toggles since the background is constant.
- Do not re-evaluate on theme toggle unless the background color itself changes.

Example:
```js
const bg = getComputedStyle(cellRect).fill; // e.g., 'rgb(12, 34, 56)'
const style = window.ColorPalettes?.getTextStyleForBackground
? window.ColorPalettes.getTextStyleForBackground(bg, { blend: 0.6 })
: { fill: 'var(--text-color)' };
textSel.style('fill', style.fill);
```

### 4) Controls (labels, selects, sliders)
- Compose controls as plain HTML elements appended inside the root container (no SVG UI).
- Style selects like in `d3-line.html`/`d3-bar.html` for consistency (rounded 8px, custom caret via data-URI, focus ring).
- Use `<label>` wrapping the input for accessibility; set concise text (e.g., "Metric", "Model Size").

#### 4.1) Required select label: "Metric"
- When a select is used to switch metrics, include a visible label above the select with the exact text "Metric".
- Preferred markup (grouped for easy vertical stacking):

```html
<div class="controls">
<div

Files changed (2) hide show
  1. directive.md +10 -0
  2. style.css +116 -10
directive.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="legend">
2
+ <div class="legend-title">Legend</div>
3
+ <div class="items"></div>
4
+ </div>
5
+ <div class="controls">
6
+ <div class="control-group">
7
+ <label for="metric-select-<id>">Metric</label>
8
+ <select id="metric-select-<id>"></select>
9
+ </div>
10
+ </div>
style.css CHANGED
@@ -1,12 +1,118 @@
1
- ```css
2
- /* Main styles for Design Tokens Playground */
3
- body {
4
- font-family: 'Inter', system-ui, -apple-system, sans-serif;
5
- margin: 0;
6
- padding: 0;
7
- background-color: #f8fafc;
8
- color: #1e293b;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  }
10
 
11
- /* Add your custom styles here */
12
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================================================ */
2
+ /* Design Tokens */
3
+ /* ============================================================================ */
4
+ :root {
5
+ /* Neutrals */
6
+ --neutral-600: rgb(107, 114, 128);
7
+ --neutral-400: rgb(185, 185, 185);
8
+ --neutral-300: rgb(228, 228, 228);
9
+ --neutral-200: rgb(245, 245, 245);
10
+
11
+ --default-font-family: Source Sans Pro, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
12
+
13
+ /* Brand (OKLCH base + derived states) */
14
+ --primary-base: oklch(0.75 0.12 47);
15
+ --primary-color: var(--primary-base);
16
+ --primary-color-hover: oklch(from var(--primary-color) calc(l - 0.05) c h);
17
+ --primary-color-active: oklch(from var(--primary-color) calc(l - 0.10) c h);
18
+ --on-primary: #ffffff;
19
+
20
+ /* Text & Surfaces */
21
+ --page-bg: #ffffff;
22
+ --text-color: rgba(0, 0, 0, .85);
23
+ --transparent-page-contrast: rgba(255, 255, 255, .85);
24
+ --muted-color: rgba(0, 0, 0, .6);
25
+ --border-color: rgba(0, 0, 0, .1);
26
+ --surface-bg: #fafafa;
27
+ --code-bg: #f6f8fa;
28
+
29
+ /* Links */
30
+ --link-underline: var(--primary-color);
31
+ --link-underline-hover: var(--primary-color-hover);
32
+
33
+ /* Spacing scale */
34
+ --spacing-1: 8px;
35
+ --spacing-2: 12px;
36
+ --spacing-3: 16px;
37
+ --spacing-4: 24px;
38
+ --spacing-5: 32px;
39
+ --spacing-6: 40px;
40
+ --spacing-7: 48px;
41
+ --spacing-8: 56px;
42
+ --spacing-9: 64px;
43
+ --spacing-10: 72px;
44
+
45
+ /* Custom Media aliases compiled by PostCSS */
46
+ @custom-media --bp-xxs (max-width: 320px);
47
+ @custom-media --bp-xs (max-width: 480px);
48
+ @custom-media --bp-sm (max-width: 640px);
49
+ @custom-media --bp-md (max-width: 768px);
50
+ @custom-media --bp-lg (max-width: 1024px);
51
+ @custom-media --bp-xl (max-width: 1280px);
52
+ @custom-media --bp-content-collapse (max-width: 1100px);
53
+
54
+ /* Layout */
55
+ --content-padding-x: 16px;
56
+ /* default page gutter */
57
+ --block-spacing-y: var(--spacing-4);
58
+ /* default vertical spacing between block components */
59
+
60
+ /* Config */
61
+ --palette-count: 8;
62
+
63
+ /* Button tokens */
64
+ --button-radius: 6px;
65
+ --button-padding-x: 12px;
66
+ --button-padding-y: 8px;
67
+ --button-font-size: 14px;
68
+ --button-icon-padding: 8px;
69
+ /* Big button */
70
+ --button-big-padding-x: 16px;
71
+ --button-big-padding-y: 12px;
72
+ --button-big-font-size: 16px;
73
+ --button-big-icon-padding: 12px;
74
+
75
+ /* Table tokens */
76
+ --table-border-radius: 8px;
77
+ --table-header-bg: oklch(from var(--surface-bg) calc(l - 0.02) c h);
78
+ --table-row-odd-bg: oklch(from var(--surface-bg) calc(l - 0.01) c h);
79
+
80
+ /* Z-index */
81
+ --z-base: 0;
82
+ --z-content: 1;
83
+ --z-elevated: 10;
84
+ --z-overlay: 1000;
85
+ --z-modal: 1100;
86
+ --z-tooltip: 1200;
87
+
88
+ /* Charts (global) */
89
+ --axis-color: var(--muted-color);
90
+ --tick-color: var(--text-color);
91
+ --grid-color: rgba(0, 0, 0, .08);
92
  }
93
 
94
+ /* ============================================================================ */
95
+ /* Dark Theme Overrides */
96
+ /* ============================================================================ */
97
+ [data-theme="dark"] {
98
+ --page-bg: #0f1115;
99
+ --text-color: rgba(255, 255, 255, .9);
100
+ --muted-color: rgba(255, 255, 255, .7);
101
+ --border-color: rgba(255, 255, 255, .15);
102
+ --surface-bg: #12151b;
103
+ --code-bg: #12151b;
104
+ --transparent-page-contrast: rgba(0, 0, 0, .85);
105
+
106
+ /* Charts (global) */
107
+ --axis-color: var(--muted-color);
108
+ --tick-color: var(--muted-color);
109
+ --grid-color: rgba(255, 255, 255, .10);
110
+
111
+ /* Primary (lower L in dark) */
112
+ --primary-color-hover: oklch(from var(--primary-color) calc(l - 0.05) c h);
113
+ --primary-color-active: oklch(from var(--primary-color) calc(l - 0.10) c h);
114
+ --on-primary: #0f1115;
115
+
116
+ color-scheme: dark;
117
+ background: #0f1115;
118
+ }