Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .opencode/skills/data-viz/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ A single insight might just be one chart with a headline and annotation. Scale c
- **Responsive**: `min-h-[VALUE]` on all charts. Grid stacks on mobile
- **Animation**: Entry transitions only, `duration-300` to `duration-500`. Never continuous
- **Accessibility**: `aria-label` on charts, WCAG AA contrast, don't rely on color alone
- **Dynamic color safety**: When colors come from external sources (brand palettes, category maps, API data, user config), never apply them directly as text color without a contrast check. Dark colors are invisible on dark card backgrounds. Safe pattern: use the external color only for non-text elements (left border, dot, underline); always use the standard text color (white / `var(--text)`) for the label itself. If color-coded text is required, apply a minimum lightness floor: `color: hsl(from brandColor h s max(l, 60%))`
- **Icon semantics**: Verify every icon matches its label's actual meaning, not just its visual shape. Common traps: using a rising-trend icon (📈) for metrics where lower is better (latency, error rate, cost); using achievement icons (🏆) for plain counts. When in doubt, use a neutral descriptive icon over a thematic one that could mislead

### Step 5: Interactivity & Annotations

Expand Down Expand Up @@ -133,3 +135,16 @@ A single insight might just be one chart with a headline and annotation. Scale c
- Pie charts > 5 slices — use horizontal bar
- Unlabeled dual y-axes — use two separate charts
- Truncated bar axes — always start at zero
- Filtering or mapping over a field not confirmed to exist in the data export — an undefined field in `.filter()` or `.map()` produces empty arrays or NaN silently, not an error; always validate the exported schema matches what the chart code consumes

## Pre-Delivery Checklist

Before marking a dashboard complete:

- [ ] Every tab / view activated — all charts render (no blank canvases, no unexpected 0–1 axes)
- [ ] Every field referenced in chart/filter code confirmed present in the data export
- [ ] All text readable on its background — check explicitly when colors come from external data
- [ ] All icons match their label's meaning
- [ ] Tooltips appear on hover for every chart
- [ ] No chart silently receives an empty dataset — add a visible empty state or console warning
- [ ] Mobile: grid stacks correctly, no body-level horizontal overflow
67 changes: 67 additions & 0 deletions .opencode/skills/data-viz/references/component-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,70 @@ const CalloutLabel = ({ viewBox, label, color = "#1e293b" }: { viewBox?: { x: nu
```

**Rules:** Never overlap data. Use `position: "insideTopRight"/"insideTopLeft"` on labels. Pair annotations with tooltips — annotation names the event, tooltip shows the value.

---

## Multi-Tab Dashboard — Lazy Chart Initialization

Charts initialized inside a hidden container (`display:none`) render blank. Chart.js, Recharts, and Nivo all read container dimensions at mount time — a hidden container measures as `0×0`.

**Rule: never initialize a chart until its container is visible.**

```js
// Vanilla JS pattern
var _inited = {};

function activateTab(name) {
// 1. make the tab visible first
document.querySelectorAll('.tab').forEach(el => el.classList.remove('active'));
document.getElementById('tab-' + name).classList.add('active');
// 2. then initialize charts — only on first visit
if (!_inited[name]) {
_inited[name] = true;
initChartsFor(name);
}
}

activateTab('overview'); // init the default visible tab on page load
```

Library-specific notes:
- **Chart.js**: canvas reads as `0×0` inside `display:none` — bars/lines never appear
- **Recharts `ResponsiveContainer`**: reads `clientWidth = 0` — chart collapses to nothing
- **Nivo `Responsive*`**: uses `ResizeObserver` — fires once at `0×0`, never re-fires on show
- **React conditional rendering**: prefer `visibility:hidden` + `position:absolute` over toggling `display:none` if you want charts to stay mounted and pre-rendered

---

## Programmatic Dashboard Generation — Data-Code Separation

When generating a standalone HTML dashboard from a script (Python, shell, etc.), never embed JSON data inside a template string that also contains JavaScript. Curly-brace collisions in f-strings / template literals cause silent JS parse failures that are hard to debug.

**Wrong** — data and JS logic share one f-string, every `{` in JS must be escaped as `{{`:

```python
html = f"""
<script>
const data = {json.dumps(data)}; // fine
const fn = () => {{ return x; }} // must escape — easy to miss
const obj = {{ key: getValue() }}; // one missed escape = blank page
</script>
"""
```

**Right** — separate data from logic entirely:

```python
# Step 1: write data to its own file — no template string needed
with open('data.js', 'w') as f:
f.write('const DATA = ' + json.dumps(data) + ';')

# Step 2: HTML loads both files; app.js is static and never needs escaping
```

```html
<script src="data.js"></script> <!-- generated, data only -->
<script src="app.js"></script> <!-- static, logic only -->
```

Benefits: `app.js` is static and independently testable; `data.js` is regenerated without touching logic; no escaping required in either file.
Loading