@chartbuddy.io/embed

Embed ChartBuddy Insights via new Insight() — default view-only with hover ball (Download/Drag to slide/Edit; Done exits edit); allowEdit:false for display-only exports; editable:true for the full editor; editSession:'locked' for always-on edit without mo
2,678
Install
npm install @chartbuddy.io/embed
Latest Version:1.8.13
Vue:3
License:Custom
Last Updated:Aug 26, 2026
Links: npm
Maintainer: kobloid321

ChartBuddy Embed API (@chartbuddy.io/embed)

Create ChartBuddy Insights in any web page. Default is view-only (headless draw): hover the top-left ChartBuddy ball for Download (PNG), Export config (JSON), and Edit (full interactive editor). Pass editable: true to open the editor immediately. Spreadsheet is not included. No account or chart ID required: pass data via chartData.

Public API: Insight (via new Insight()), getInsights, snapshotInsights, version. Global registry: window.__CHARTBUDDY_INSIGHTS__.

Every JS entry (package root, .single.mjs, multi loader) opens with a comment header naming the exports and a minimal example. Agents: start at llms.txt, then fetch linked docs .md pages as needed. Also: https://unpkg.com/@chartbuddy.io/embed/llms.txt · https://chartbuddy.io/embed/docs/llms.txt. Package root on unpkg resolves to a readable stub that re-exports the single-file build.

Claude / strict CSP (recommended)

import { Insight } from 'https://unpkg.com/@chartbuddy.io/embed';
// equivalent:
// import { Insight } from 'https://unpkg.com/@chartbuddy.io/embed/chartbuddy-embed.single.mjs';
<div id="chart" style="width:960px;height:560px"></div>
<script type="module">
  import { Insight } from 'https://unpkg.com/@chartbuddy.io/embed';

  // Default: view-only + ChartBuddy hover ball
  new Insight('#chart', {
    instanceId: 'revenue', // stable id for multi-mount
    chartData: {
      chartType: 'clusteredBar',
      isDataTransposed: true,
      seriesData: [
        ['', 'Q1', 'Q2', 'Q3', 'Q4'],
        ['Revenue', 100, 112, 125, 140],
        ['Costs', 60, 66, 70, 78],
      ],
      title: { visible: true, text: 'Revenue vs Costs' },
      subtitle: { visible: false, text: '' },
    },
  });

  // Full editor: new Insight('#chart', { editable: true, chartData: … })
</script>

Do not pass assetBase on this path. Bundler: import { Insight } from '@chartbuddy.io/embed'.

Install / multi-file (normal websites)

npm install @chartbuddy.io/embed

Cache-friendly multi-file loader (siblings OK when CSP allows):

https://unpkg.com/@chartbuddy.io/embed/chartbuddy-embed.mjs

or import { Insight } from '@chartbuddy.io/embed/multi'.

With React

Use the bundled bindings — they handle StrictMode's double mount, patch the live chart on data changes instead of remounting, and destroy on unmount.

import { InsightChart } from '@chartbuddy.io/embed/react';

export function ChartEmbed({ chartData }) {
  return <InsightChart chartData={chartData} style={{ width: '100%', height: 560 }} />;
}

react is an optional peer dependency (React 18 or 19). For imperative access to the instance:

import { useInsight } from '@chartbuddy.io/embed/react';

const { ref, insight, ready, error } = useInsight({ chartData, editable: true });
// <div ref={ref} style={{ height: 400 }} />

chartData is compared by identity — memoize it if you build it inline.

Without a bundler, a plain HTML page needs an import map mapping both react and the subpath (which needs the explicit .mjs file):

<script type="importmap">{"imports":{
  "react": "https://esm.sh/react@19",
  "react-dom/client": "https://esm.sh/react-dom@19/client",
  "@chartbuddy.io/embed/react": "https://unpkg.com/@chartbuddy.io/embed/react.mjs"
}}</script>

Use React.createElement there — no build step means no JSX. For a standalone artifact, plain new Insight() or the custom element is simpler than either.

With Vue 3

<script setup>
import { InsightChart } from '@chartbuddy.io/embed/vue';
</script>

<template>
  <InsightChart :chart-data="chartData" style="height: 400px" @change="onChange" />
</template>

vue is an optional peer dependency. useInsight() is also exported for imperative access. Assign a new object to chartData to patch the chart — mutating in place is not detected.

With Angular (and Svelte / Solid / plain HTML)

Angular uses the <chartbuddy-insight> custom element rather than a compiled Angular library, so ChartBuddy upgrades are not tied to your Angular major. No peer dependency.

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@chartbuddy.io/embed/element';

// template:
// <chartbuddy-insight [chartData]="cd" instance-id="revenue"
//                     style="display:block; height:400px"
//                     (change)="onChange($event)"></chartbuddy-insight>

chartData must be set as a property (it is an object). Events are CustomEvents — read event.detail. Full guide: https://chartbuddy.io/embed/docs/guides/angular

Usage (script tag / global)

<script src="https://unpkg.com/@chartbuddy.io/embed/chartbuddy-embed.global.js"></script>
<script>new ChartBuddyEmbed.Insight('#chart');</script>

Single-file IIFE: chartbuddy-embed.single.global.js.

API

new Insight(target: string | HTMLElement, options?: {
  chartData?: object;   // ChartBuddy chart-data (cd) — see below
  assetBase?: string;   // multi-file only; ignored by single-file
  editable?: boolean;   // default false → view mode; true → full editor
  allowEdit?: boolean;  // default true; false = Download/Drag only (no Edit)
  editSession?: 'toggle' | 'locked'; // locked = always edit, no mode chrome
  host?: {              // optional chrome hooks
    getToolbarPlacement?: () => object; // widget (default) | float | dock
    getToolbarContainer?: () => string | Element | null;
    positionToolbar?: (toolbar, target) => void; // float only
    getPopupContainer?: () => string | Element | null; // menus; default body
    startDragging?: (event) => void;
  };
}): {
  container: HTMLElement;
  assetBase: string;
  instanceId: string;
  chartContainerId: string;
  mode: 'view' | 'edit';
  editSession: 'toggle' | 'locked';
  allowEdit: boolean;
  ready: Promise<void>;
  chart: any | null;
  focus(): void;
  setChartData(cd: object): void;   // partial OK — chartType optional
  setData(seriesData: any[][]): void;
  update(patch?: object): void;     // no arg = redraw only
  getChartData(): object | null;
  on(event: 'ready' | 'mode' | 'change', handler: Function): () => void;
  off(event: 'ready' | 'mode' | 'change', handler: Function): void;
  isDirty(): boolean;
  getRevision(): number;
  toPngBlob(options?: { scale?: number; background?: string | null }): Promise<Blob>;
  toPngBase64(options?: { scale?: number; background?: string | null }): Promise<string>;
  // PNG helpers default background to #ffffff; downloadPng defaults transparent
  downloadPng(options?: { scale?: number; background?: string | null }): Promise<void>;
  dragExport(): Promise<void>;
  exportConfig(): void;
  enterEditMode(): Promise<void>;  // view → edit (no-op if allowEdit: false)
  exitEditMode(): Promise<void>;   // edit → view (also ball → Done)
  destroy(): void;
};

getInsights(): Record<string, Insight>;
snapshotInsights(options?: {
  png?: boolean;
  scale?: number;
  background?: string | null;
}): Promise<{ schemaVersion: 1; version: string; charts: Record<string, any> }>;
version: string;

View mode (default)

  • Headless chart draw (no formatting toolbar / selection chrome).
  • On hover: ChartBuddy ball (top-left) → Download PNG, Drag to slide, Edit.
  • enterEditMode() / ball Edit remounts the full editor with the current chartData.

Display-only (allowEdit: false)

  • Same view ball and exports, but Edit is omitted.
  • enterEditMode() is a no-op.

Edit mode (editable: true)

  • Interactive ChartBuddy editor with formatting UI.
  • Default toolbar placement is widget (modules in the morph rail). Override with host.getToolbarPlacement.
  • Leave via ball Done or exitEditMode() (returns to view with current config).

Locked edit (editSession: 'locked')

  • Always-on editor: no Done / morph.
  • enterEditMode() / exitEditMode() are no-ops.
  • Use when the host page owns surrounding chrome (demos, dialogs).

chartData (cd) schema

options.chartData is a ChartBuddy chart-data object. Partial objects are merged over defaults (nested title / subtitle / legend / axes / canvas / footnote are deep-merged).

Required

Field Type Notes
chartType string See allowed values below
seriesData array[] 2D grid (see layout)

Recommended

Field Type Notes
isDataTransposed boolean Prefer true with the grid layout below
title text block { visible, text } — text may be plain or HTML
subtitle text block Same shape; set visible: false to hide the default subtitle
footnote text block Same shape
legend object { visible, colors?: string[], … } — series colors live in legend.colors
backgroundColor string Default "transparent"
orientation "vertical" | "horizontal" Default "vertical"

Allowed chartType values

CHARTBUDDY:GENERATED-CHART-TYPES:BEGIN barMekko · clusteredBar · combo · line · mekko · pie · scatter stackedArea · stackedArea100 · stackedBar · stackedBar100 waterfall

Also accepted: bubble → scatter, donut → pie. CHARTBUDDY:GENERATED-CHART-TYPES:END

Aliases are shortcuts that seed defaults — they are not just renames:

You pass Resolves to Seeded
'donut' pie pie.innerRadiusRatio: 0.5 (a real hole)
'pie' pie pie.innerRadiusRatio: 0 (a full pie)
'bubble' scatter a larger point diameter

For a donut, just pass chartType: 'donut' — setting innerRadiusRatio yourself is only needed to override the hole size. getChartData() returns pie with innerRadiusRatio: 0.5, which round-trips correctly.

Validation

Chart data is validated at the API boundary — both new Insight({ chartData }) and setChartData() / setData() / update(). Invalid input throws with the path of every problem, so you get one actionable error instead of a blank chart:

insight.setChartData({ chartType: 'pie', pie: { innerRadiusRatio: 5 } });
// Error: [chartbuddy/embed] setChartData: invalid chart data — 1 problem:
//   • pie.innerRadiusRatio: 5 is above the maximum of 1

What is checked: chartType (with a did-you-mean hint), field types, enums, numeric ranges, the seriesData grid (including the per-type row/column minimums listed above), and whether an option bag matches the chart type — bar options on a pie chart are rejected. Ragged seriesData (unequal row lengths) warns but does not throw. Duplicate instanceId values on the same page throw.

Unknown keys are never an error, so a config written for a newer version still loads. chart-schema.json is the machine-readable form of the same rules, usable for your own validation or to constrain generated or structured chartData.

Checking without mounting

The same checks are exported. validateChartData() never throws and reports everything it found, which is what you want when a model authored the config:

import { validateChartData } from '@chartbuddy.io/embed';

const { valid, errors, warnings } = validateChartData(candidate);
// errors[0] → {
//   path: 'chartType',
//   code: 'unknown-chart-type',
//   severity: 'error',
//   message: '"clusterdBar" is not a known chart type. Did you mean "clusteredBar"?',
//   expected: 'a ChartType or alias',
//   received: '"clusterdBar"',
//   allowed: ['barMekko', 'clusteredBar', 'stackedArea', …],
//   suggestion: 'clusteredBar',
// }

code and path are the stable contract — branch on code, repair the value at path, and never parse message. Codes: not-an-object, unknown-chart-type, empty-patch, wrong-type, out-of-range, not-in-enum, series-data-shape, ragged-series-data (warning), foreign-option-bag.

Throws carry the same objects:

import { ChartDataValidationError } from '@chartbuddy.io/embed';

try {
  insight.setChartData(candidate);
} catch (err) {
  if (err instanceof ChartDataValidationError) console.log(err.errors);
}

Partial updates

await insight.ready;
insight.setData([['', 'Q1'], ['Revenue', 120]]);  // data only
insight.update({ title: { text: 'FY26' } });       // any partial patch
insight.update();                                  // redraw only

setChartData also accepts partials — chartType is not required.

Events

insight.on('ready', () => { … });
insight.on('mode', (mode) => { … });          // 'view' | 'edit'
insight.on('change', (cd) => { … });          // after meaningful edits
insight.isDirty();                            // true since boot / last Done
insight.getRevision();                        // monotonic edit counter

seriesData layout (recommended)

With isDataTransposed: true, use chart-native rows-as-series:

[
  ['',        'Q1', 'Q2', 'Q3'],  // row 0: categories ( [0][0] usually "" )
  ['Revenue', 100,  112,  125],  // each later row = one series
  ['Costs',    60,   66,   70],
]

Pie (category / value pairs):

[
  ['Category', 'Value'],
  ['North', 45],
  ['South', 30],
  ['East', 25],
]

Scatter (transpose not supported):

[
  ['', 'Metric X', 'Metric Y', 'Size', 'Group'],
  ['Point 1', 10, 15, 8, 'A'],
  ['Point 2', 20, 12, 5, 'B'],
]

Minimal example

{
  chartType: 'clusteredBar',
  isDataTransposed: true,
  seriesData: [
    ['', 'Q1', 'Q2', 'Q3'],
    ['Revenue', 100, 112, 125],
  ],
  title: { visible: true, text: 'Revenue' },
  subtitle: { visible: false, text: '' },
}

Full config

The richest, safest cd is a snapshot from a live editor via insight.getChartData() (or insight.exportConfig()) after ready. Pass that JSON back as chartData.

Optional advanced fields (present on full exports): canvas, axes, chartPositionPercentages, annotations, multilines, type-specific blocks (bar, line, area, pie, waterfall, mekko, combo), seriesLabels.

How it works

Single-file: d3 + DOMPurify + engine + CSS in one module — use for Claude.

Multi-file: small loader pulls siblings (webapp-entry.js, vendors, CSS, worker). Use on sites you control.

Notes

  • Multi-mount supported. Construct new Insight() multiple times in one document.
  • No spreadsheet in this package.
  • Default view mode has no formatting toolbar; use Edit or editable: true.
  • Single-file uses main-thread label placement (no worker/wasm fetch).
  • Requires a browser (document).

License

Proprietary evaluation license — © ChartBuddy. See the bundled LICENSE file.

Related Packages

@chialab/plasma

Generate Custom Elements wrappers for Angular, React, Svelte and Vue.

3,951 2
@syidax/advanced-pro

Interactive charts and data grids for React, Angular, Vue and the web. Pro visua...

262
@syidax/advanced

Interactive charts and data grids for React, Angular, Vue and the web. Community...

255
@dlhsoft/ganttcharthyperlibrary

Gantt Chart library for JavaScript and TypeScript with Angular, React, and Vue e...

2,650 2
@alexstrnik/claude-devtools

Host server for Claude DevTools Chrome extension

885 11

Version History

Version Released License
1.8.13 Custom
1.8.5 Custom
1.8.4 Custom
1.8.1 Custom
1.8.0 Custom