# @chartbuddy.io/embed — LLM / Claude usage guide Package: @chartbuddy.io/embed Public API: Insight (new Insight()), getInsights, snapshotInsights, clearPersistedChart, version Purpose: Mount a ChartBuddy chart in HTML. Default is view-only (hover ball: Download PNG / Drag to slide / Edit). Pass editable:true for the full interactive editor (canvas + formatting toolbar). NOT Chart.js. Spreadsheet (Handsontable) is NOT included. No ChartBuddy account / chart ID required — pass data via options.chartData. Stable docs URLs (bookmark / fetch these): - https://unpkg.com/@chartbuddy.io/embed/llms.txt - https://cb-acc.xyz/docs/ - https://unpkg.com/@chartbuddy.io/embed/README.md Every JS artifact (package root stub, .single.mjs, multi loader) opens with a comment header naming the exports and a minimal Insight example. ## Claude Visualizer / inline artifacts (REQUIRED path) Import the PACKAGE ROOT (readable API stub → self-contained single-file build): ```js import { Insight } from 'https://unpkg.com/@chartbuddy.io/embed'; ``` Equivalents (also fine — each file self-describes in its first lines): - https://unpkg.com/@chartbuddy.io/embed/chartbuddy-embed.api.mjs - https://unpkg.com/@chartbuddy.io/embed/chartbuddy-embed.single.mjs - https://cdn.jsdelivr.net/npm/@chartbuddy.io/embed - https://cb-acc.xyz/api/embed/chartbuddy-embed.single.mjs Rules: - Create with **new Insight()** (not createChart / renderChart / mountEditor / mountChart). - Do NOT pass assetBase on this path. - Do NOT import chartbuddy-embed.mjs (multi-file) inside Claude Visualizer. - Do NOT expect a spreadsheet UI. - Default is view-only; pass editable: true for editor chrome / formatting UI. - Full chartData schema is below; also in README.md on the same package. ### Copy-paste HTML artifact ```html ChartBuddy
``` ### API ```js import { Insight, getInsights, snapshotInsights, clearPersistedChart, version, } from 'https://unpkg.com/@chartbuddy.io/embed'; const insight = new Insight(target, options?); // target: CSS selector string OR HTMLElement // options.chartData?: ChartBuddy cd object (schema below) // options.instanceId?: string // STABLE id for agents + persist (recommended) // options.editable?: boolean (default false → view mode) // options.toolbar?: boolean (edit mode only; default false) // options.persist?: boolean (default false; needs stable instanceId to reload) // options.assetBase?: IGNORE for single-file await insight.ready; insight.instanceId insight.mode // 'view' | 'edit' insight.chart insight.setChartData(cd) // partial OK — chartType optional insight.setData(seriesData) // data-only refresh insight.update(patch?) // partial patch; no arg = redraw insight.getChartData() // full cd snapshot — best source for a complete schema example insight.on('ready' | 'mode' | 'change', handler) // returns unsubscribe insight.off(event, handler) insight.isDirty() insight.getRevision() insight.toPngBase64() // PNG base64 (NO download); default background #ffffff insight.toPngBase64({ background: '#0f172a' }) // custom opaque underlay insight.toPngBase64({ background: null }) // transparent (Slides-style) insight.toPngBlob() // PNG as Blob (NO download); same background defaults insight.downloadPng() // human Save dialog; default transparent insight.downloadPng({ background: '#ffffff' }) // opaque human download insight.exportConfig() // human JSON download insight.enterEditMode() // view → full editor insight.exitEditMode() // edit → view (also ball → Done); checkpoints if persist insight.focus() insight.destroy() getInsights() // { [instanceId]: Insight } await snapshotInsights() // config-only for all mounts await snapshotInsights({ png: true }) // + pngBase64 (default white bg) await snapshotInsights({ png: true, background: '#111827' }) clearPersistedChart(instanceId?) version validateChartData(cd, options?) // → { valid, issues, errors, warnings }; NEVER throws assertValidChartData(cd, context?, options?) // throws ChartDataValidationError formatValidationIssues(issues, context?) // human-readable string // Also: window.__CHARTBUDDY_INSIGHTS__ === getInsights() ``` Bundler: `import { Insight } from '@chartbuddy.io/embed';` React: `import { InsightChart, useInsight } from '@chartbuddy.io/embed/react';` Vue 3: `import { InsightChart, useInsight } from '@chartbuddy.io/embed/vue';` Angular / Svelte / Solid / plain HTML — custom element, no peer dependency: `import '@chartbuddy.io/embed/element';` → `` set `.chartData` as a PROPERTY; events are CustomEvents (ready/change/mode/error) All bindings mount once, patch on chartData change, and destroy on unmount. NO BUNDLER? The react/vue subpaths import `react` / `vue` as bare specifiers, so a plain HTML page needs an import map mapping BOTH the framework AND the subpath (subpaths need the explicit file, e.g. `.../@chartbuddy.io/embed/react.mjs`): ```html ``` Then use React.createElement (no JSX — there is no build step). For a standalone artifact, prefer plain `new Insight()` or the custom element: neither needs an import map or a framework. ## Agent observation (round-trip / visual QA) Browsers cannot rewrite the HTML file. Agents (Cursor, Claude, CI) can — ChartBuddy returns structured data; the host agent writes files. ### Recommended loop 1. Author HTML with **stable `instanceId`** per mount. 2. Human edits in-browser (ball → Edit → Done). 3. Agent observes: ```js // CDP / Runtime.evaluate — after page has mounted Insights: const insights = window.__CHARTBUDDY_INSIGHTS__; const insight = insights['revenue']; const cd = insight.getChartData(); // Default opaque white underlay — pass background for other colors: const png = await insight.toPngBase64(); // raw base64, no data: prefix // const png = await insight.toPngBase64({ background: '#0f172a' }); ``` 4. Agent writes `deck.charts.json` / `exports/revenue.png` into the workspace. 5. LLM diffs JSON vs prior seed; updates narrative or re-seeds HTML. ### PNG background note Export strips the live SVG `#chartBackground` so Slides overlays stay transparent. `chartData.backgroundColor` alone does **not** bake into PNGs. Use `toPngBase64({ background })` / `toPngBlob({ background })` instead. Agent helpers default to `#ffffff`. Pass any CSS color, or `null`/`'transparent'` for a transparent PNG. ### Rules for agents - Prefer **`toPngBase64` / `getChartData`** over screenshots or `downloadPng`. - Use **`background`** on PNG helpers for visual QA (do not rely on `cd.backgroundColor`). - Always pass **`instanceId`** on multi-chart pages (and for `persist: true`). - Snapshot **config-only** by default; request PNG one chart at a time. - Do **not** expect HTML to rewrite itself after edits. - Do **not** expect `downloadPng` / `exportConfig` downloads to reach the agent. ### Optional sidecar convention (agent-written, not ChartBuddy) ```text deck.html deck.charts.json // all chartData from getInsights / snapshotInsights exports/ revenue.png ``` ## chartData (`cd`) schema Partial `chartData` is merged over defaults. Nested keys `title`, `subtitle`, `legend`, `axes`, `canvas`, `footnote` are deep-merged. ### Required - `chartType` (string) - `seriesData` (2D array) ### Recommended - `isDataTransposed: true` - `title: { visible: true, text: '…' }` - `subtitle: { visible: false, text: '' }` // hide default placeholder subtitle - `legend: { visible: true, colors?: string[] }` // palette is legend.colors - `backgroundColor`, `orientation` (`"vertical"` | `"horizontal"`) ### chartType values CHARTBUDDY:GENERATED-CHART-TYPES:BEGIN `area` | `area100` | `barMekko` | `clusteredBar` | `combo` | `line` `mekko` | `pie` | `scatter` | `stackedBar` | `stackedBar100` | `waterfall` Aliases: `bubble`→`scatter`, `donut`→`pie`. CHARTBUDDY:GENERATED-CHART-TYPES:END Aliases are input-only and SEED DEFAULTS — they are not just renames: chartType: 'donut' → pie with pie.innerRadiusRatio 0.5 (a real hole) chartType: 'pie' → pie with pie.innerRadiusRatio 0 (a full pie) chartType: 'bubble' → scatter with a larger point diameter For a donut just pass `chartType: 'donut'`; do NOT also set innerRadiusRatio unless you want a different hole size. getChartData() reports the CANONICAL type (a donut round-trips as `pie` + innerRadiusRatio 0.5 — that is correct, not loss). ### Validation (read this before generating a config) `new Insight({ chartData })` and `setChartData()` / `setData()` / `update()` validate input and THROW on invalid data, listing the path of every problem. Checked: chartType (with a did-you-mean hint), field types, enums, numeric ranges, the seriesData grid and its per-type row/column minimums, and option-bag/chartType agreement — `bar` options on a `pie` chart are rejected. Ragged seriesData (unequal row lengths) WARNS but does not throw. Duplicate instanceId on the same page THROWS. Unknown keys are never an error. Partial updates are first-class: `setData(seriesData)`, `update(patch)`, and `setChartData(partial)` all keep the current chart type when chartType is omitted. Events: `on('ready'|'mode'|'change', handler)`, `isDirty()`, `getRevision()`. Example failure: `pie.innerRadiusRatio: 5 is above the maximum of 1` BEST PRACTICE — check before you mount, instead of using a blank chart as the error signal. `validateChartData(cd)` never throws and returns every problem: ```js const { valid, errors, warnings } = validateChartData(cd); // each issue: { path, code, message, severity, expected?, received?, allowed?, suggestion? } ``` Branch on `code`, NEVER on `message` (messages may be reworded in any release): unknown-chart-type → use issue.suggestion, or pick from issue.allowed not-in-enum → use issue.suggestion, or pick from issue.allowed wrong-type → coerce to issue.expected (also catches NaN / Infinity) out-of-range → clamp to the bound in issue.expected series-data-shape → reshape; expected names the row/column minimum ragged-series-data → WARNING; pad the short rows foreign-option-bag → move options into the bag named in issue.suggestion empty-patch → include chartType and/or seriesData not-an-object → pass an object Repair loop (no second model call needed for typos): ```js let cd = generated; for (const issue of validateChartData(cd).errors) { if (issue.suggestion) cd = setAtPath(cd, issue.path, issue.suggestion); } ``` If you mount without checking, the throw is a `ChartDataValidationError` carrying the same objects: `err.errors`, `err.warnings`, `err.issues`, `JSON.stringify(err)`. The machine-readable form of these rules is chart-schema.json, which can be used to constrain structured output directly. ### seriesData layouts Bar / line / area / stacked (rows = series): ```js [ ['', 'Q1', 'Q2', 'Q3'], ['Revenue', 100, 112, 125], ['Costs', 60, 66, 70], ] ``` Pie: ```js [ ['Category', 'Value'], ['North', 45], ['South', 30], ] ``` Scatter (no transpose): ```js [ ['', 'Metric X', 'Metric Y', 'Size', 'Group'], ['Point 1', 10, 15, 8, 'A'], ] ``` ### Waterfall (DO NOT compute your own totals) Total columns are computed. Mark the column and leave its cell empty — a value typed into an `isTotal` column is ignored, and a closing figure typed WITHOUT `isTotal` becomes another contribution bar (the bridge then ends at ~2x). ```js { chartType: 'waterfall', isDataTransposed: true, seriesData: [ ['', 'Start', 'Price', 'Volume', 'Mix', 'End'], // ^0 ^1 ^2 ^3 ^4 <- waterfall.columns keys ['Bridge', 100, 18, -8, 5, null], // End carries NO value ], waterfall: { columns: { 4: { isTotal: true } } }, // -> closing bar = 115 } ``` - `waterfall.columns` keys are 0-based **data-column** indices: index into the header row AFTER the row-label cell. In the row above `'End'` is header position 5 but data column 4. Getting this wrong fails silently. - `isTotal: true` — show the running total, ignore this column's own value. - `startBar: true` — reset to zero and drop the connector (new sequence). A mid-chart opening bar needs BOTH `isTotal: true` and `startBar: true`; `startBar` alone still renders as a floating contribution. - `showSegments: true` — draw a total as per-series segments instead of a solid bar. - Column 0 is always forced to `isTotal` + `startBar`, and unlike other totals it DOES use its own value (the opening balance). ### Full cd Prefer `insight.getChartData()` / `insight.exportConfig()`, or author in the editor then snapshot. Advanced fields on full exports: `canvas`, `axes`, `annotations`, `multilines`, type-specific blocks (`bar`, `line`, `pie`, …), `seriesLabels`, `chartPositionPercentages`. ## Multi-mount dashboard Construct `new Insight()` once per container in the same document. Each instance is isolated. Pass a stable `instanceId` per mount so agents can address charts by name. ```js new Insight('#a', { instanceId: 'revenue', chartData: … }); new Insight('#b', { instanceId: 'bridge', chartData: … }); await snapshotInsights(); // { charts: { revenue: { chartData }, bridge: { … } } } ``` ## Fallbacks | Situation | Action | |-----------|--------| | Claude Visualizer / strict script-src | single-file unpkg/jsDelivr | | Host blocks large ESM | iframe https://cb-acc.xyz/embed/ | | You control page + siblings OK | multi-file chartbuddy-embed.mjs | ## Do not - Pass assetBase with single-file - Import sibling d3 / webapp-entry / wasm / worker yourself - Expect Handsontable / spreadsheet windows - Expect `downloadPng` to feed agents (use `toPngBase64`) - Expect the browser to rewrite HTML / sidecar files - Use for production client-facing apps under the current evaluation LICENSE ## Verification checklist - `document.querySelectorAll('svg[id^="chart-svg-"]').length >= 1` - `document.querySelectorAll('.spreadsheet-window').length === 0` - Default view: no `.webapp-toolbar`; hover → `[data-cb-embed-chrome]` - Network: one ChartBuddy JS request (`.single.mjs`) plus maybe fonts - Observation: `Object.keys(window.__CHARTBUDDY_INSIGHTS__ || {}).length >= 1` - Observation: `typeof insight.toPngBase64 === 'function'` and `getChartData()` returns `chartType`