# Agent chart PNG guide

Dealroom pages can expose chart screenshots as polished PNG banners for agents, social posts, and QA. The contract is intentionally page-local: load the public page, wait for readiness, call the browser API, upload the returned PNG.

## Public browser contract

Every chart-enabled page should expose:

```js
await window.__chartReadyPromise;     // resolves when data + charts are rendered
window.__chartReady === true;         // boolean convenience flag
window.__chartList();                 // discover available chart IDs

const png = await window.__chartPng({
  chart: 'chart-id',                  // required unless the page has exactly one chart
  scale: 2,                           // 1 = 1500px wide, 2 = 3000px wide
  theme: 'light'                      // default; use light for social unless there is a reason not to
});

const snapshot = await window.__chartSnapshot({
  chart: 'chart-id',
  filters: { region: 'Europe' },      // optional; page-specific filter adapter applies these first
  scale: 2,
  theme: 'light'
});
```

Return shape:

```js
{
  dataUrl: 'data:image/png;base64,...',
  blob: Blob,
  filename: 'dealroom-...png',
  width: 3000,
  height: 1800,
  chart: 'chart-id',
  theme: 'light',
  filters: { ... }
}
```

## Zero-automation iframe shape

For runtimes that cannot run Playwright/Puppeteer but can load an iframe:

```js
window.addEventListener('message', (event) => {
  if (event.data?.type === 'chartPng') {
    // { chart, dataUrl, filename, filters, width, height, theme }
  }
  if (event.data?.type === 'chartPngDone') {
    // all requested charts finished
  }
});

const iframe = document.createElement('iframe');
iframe.src = 'https://dealroom.co/multiples/?autoPng=1&chart=revenue-growth-vc-rounds&scale=2&theme=light';
iframe.style.cssText = 'position:fixed;left:-10000px;width:1280px;height:900px';
document.body.appendChild(iframe);
```

Omit `chart=` to export every chart from the page.

## Discovery

Read the manifest and list IDs instead of hard-coding:

```js
const manifest = JSON.parse(document.querySelector('#chart-export-manifest').textContent);
const charts = window.__chartList().map(c => ({ id: c.id, title: c.title, subtitle: c.subtitle, filters: c.filters }));
```

Current high-value pages:

- `/vc-investment-dashboard/`
  - Custom API: `treemap`, `stack`, `loc`.
  - Filters: `region`, `minAmount`, `roundType`, `sector`.
  - Example: `window.__chartSnapshot({ chart: 'treemap', filters: { region: 'Europe', minAmount: '15' }, scale: 2 })`.
- `/power-law-investor-ranking/`
  - Use `window.__chartList()`; examples include `power-law-curve`, `power-law-ranking`, `investor-outcome-compare`.
- `/multiples/`
  - Use `window.__chartList()`; examples include `revenue-growth-vc-rounds`, `deal-size-vc-rounds`, `location-exit-rounds`.

## Agent tweet workflow

1. Start from the point of view, not the chart. Decide the claim you want to make in one sentence.
2. Pick the chart whose title directly supports that claim. If no chart supports it, do not force it.
3. Use filters that make the claim standalone in the PNG title/subtitle/filename. Prefer `region`, `sector`, `roundType`, and amount thresholds over UI-only context.
4. Fetch at `scale: 2` for final posting. Use `scale: 1` only for smoke tests.
5. Visually QA before posting:
   - headline includes geography / sector / window;
   - bars or marks fill the plot area sensibly;
   - legend is present when color encodes meaning;
   - footer/source/logo are visible;
   - no filters, hover controls, tables, or page chrome are captured;
   - no critical label is clipped.
6. Draft the tweet around the chart, not as a caption dump:
   - first line: the punchline;
   - second line: the surprising number or comparison;
   - final line: what it implies for founders/investors.

## Implementation checklist for new pages

1. Add `/js/agent-charts.js` after the page defines `window.AgentChartsConfig`.
2. Mark each exportable chart/card with `data-agent-chart`, `data-agent-title`, `data-agent-kicker`, and `data-agent-subtitle`.
3. Hide UI-only chrome in PNGs with existing ignore classes (`png-btn`, `agent-only-link`, `agent-hide`, `chart-filters`, `chart-controls`, etc.).
4. If data loads asynchronously, set `AgentChartsConfig.waitForReady` or call `window.AgentCharts.markReady()` after the first full render.
5. If agents should control filters, register a filter adapter:

```js
window.AgentCharts.registerFilterAdapter('chart-id', {
  get() { return { region: currentRegion, sector: currentSector }; },
  async set(filters) {
    applyFilters(filters);
    await renderDonePromise;
  },
  async refresh() {
    await renderDonePromise;
  }
});
```

6. For Chart.js/canvas charts, force final pixels before html2canvas captures: disable animations where possible, or call `chart.update('none')` inside the page's render/adapter path. Avoid relying on `requestAnimationFrame` as the only settle mechanism; background tabs throttle/pause it.
7. Add a manual smoke test before shipping:

```js
await window.__chartReadyPromise;
for (const c of window.__chartList().slice(0, 3)) {
  const r = await window.__chartPng({ chart: c.id, scale: 1 });
  console.log(c.id, r.filename, r.width, r.height, r.dataUrl.length);
}
```

## Production smoke results, 2026-05-24

- `dealroom.co/vc-investment-dashboard/`: `treemap`, `stack`, and `loc` exported at 1500×1216 with Europe filters; filenames include region.
- `dealroom.co/power-law-investor-ranking/`: `power-law-curve` exported at 1500×1264.
- `dealroom.co/multiples/`: `revenue-growth-vc-rounds` exported at 1500×933.
