npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@chartbuddy.io/embed

v1.8.13

Published

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

Readme

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.