@chartbuddy.io/embed

中文文档
1.9.6发布于 2026/10/28SEE LICENSE IN LICENSETypeScriptES Module零依赖近 30 天下载 5221

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

原始使用文档
文档目录26 节
  1. Claude / strict CSP (recommended)
  2. Install / multi-file (normal websites)
  3. With React
  4. With Vue 3
  5. With Angular (and Svelte / Solid / plain HTML)
  6. Usage (script tag / global)
  7. API
  8. Engine flags ( engineFlags )
  9. View mode (default)
  10. Display-only ( allowEdit: false )
  11. Edit mode ( editable: true )
  12. Locked edit ( editSession: 'locked' )
  13. chartData ( cd ) schema
  14. Required
  15. Recommended
  16. Allowed chartType values
  17. Validation
  18. Partial updates
  19. Events
  20. seriesData layout (recommended)
  21. Minimal example
  22. Full config
  23. How it works
  24. Security: where chartData comes from
  25. Notes
  26. License

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.

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;
  };
  engineFlags?: {       // engine features still being built; all off by default
    arrowGeometry?: boolean; // arrows clear bars and total labels (see below)
  };
}): {
  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;

Engine flags (engineFlags)

Features of the chart engine that are still being built can be turned on per page before they are on for everyone. A flag applies to every chart on the page from its next draw, and goes away once the feature is on by default.

  • arrowGeometry: difference arrows (and later CAGR and level arrows) are positioned by one shared calculation, so they clear the bars and total labels they span and the chart keeps room for them instead of clipping them. Arrows in existing charts may move when it is on.
new Insight('#chart', { chartData, engineFlags: { arrowGeometry: true } });

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

FieldTypeNotes
chartTypestringSee allowed values below
seriesDataarray[]2D grid (see layout)
FieldTypeNotes
isDataTransposedbooleanPrefer true with the grid layout below
titletext block{ visible, text } — text may be plain or HTML
subtitletext blockSame shape; set visible: false to hide the default subtitle
footnotetext blockSame shape
legendobject{ visible, colors?: string[], … } — series colors live in legend.colors
backgroundColorstringDefault "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 passResolves toSeeded
'donut'piepie.innerRadiusRatio: 0.5 (a real hole)
'pie'piepie.innerRadiusRatio: 0 (a full pie)
'bubble'scattera 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

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.

Security: where chartData comes from

chartData is rendered as given. The engine treats it as a document you authored, not as untrusted input, so if the chart data on your page can be influenced by your own end users, treat it the way you would treat any user-supplied content.

The concrete case worth knowing about: font family names in chart data are used to request web fonts. ChartBuddy validates them and refuses names that could escape a stylesheet, but a Content-Security-Policy on your page is the control that does not depend on us getting that right. If you render chart data that users can influence, set at minimum:

style-src  'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src   'self' data: https://fonts.gstatic.com;
object-src 'none';

style-src and font-src bound where a stylesheet or font may be fetched from, so a malicious font name cannot turn into a request to a host you did not choose. 'unsafe-inline' is required for style-src because the engine sets inline styles while rendering; it does not weaken the origin allowlist above.

If your page's chart data only ever comes from your own backend or from charts your own team authored, this is defence in depth rather than a requirement.

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.

版本与发布记录

@chartbuddy.io/embed 共发布 26 个版本,首次发布于 2026/08/05;过去 12 个月发布了 26 个版本。

版本发布日期距今
1.9.6latest2026/10/2812 天前
1.9.52026/10/2812 天前
1.8.132026/08/262 个月前
1.8.52026/08/142 个月前
1.8.42026/08/142 个月前
1.8.12026/08/102 个月前

关于 @chartbuddy.io/embed 的常见问题

@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

如何安装 @chartbuddy.io/embed?

在项目目录运行 npm install @chartbuddy.io/embed,或使用 pnpm add / yarn add / bun add。

@chartbuddy.io/embed 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@chartbuddy.io/embed 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@chartbuddy.io/embed 可以商用吗?

@chartbuddy.io/embed 使用 SEE LICENSE IN LICENSE 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@chartbuddy.io/embed 的下载量有多少?

根据 npm 官方下载统计,@chartbuddy.io/embed 在 2026/10/10 – 2026/11/08 这 30 天内被下载 5,221 次,日均约 174 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@chartbuddy.io/embed 还在维护吗?

最新版本 1.9.6 发布于 2026/10/28(12 天前);至今共发布 26 个版本,其中过去 12 个月发布了 26 个;首次发布于 2026/08/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。