Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs-site/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# build output
dist/
.astro/

# dependencies
node_modules/

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# environment
.env
.env.production

# macOS
.DS_Store
22 changes: 22 additions & 0 deletions docs-site/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# GoldenChart docs site

Astro Starlight site. Independent of the library build; deployed under
`/docs/` of the GitHub Pages site alongside the existing playground.

## Local development

```bash
cd docs-site
npm install
npm run dev
```

Open <http://localhost:4321/GoldenChart/docs>.

## Status

Phase 1 scaffold — only seed pages are filled in (Quick start, Brand vs. vibe,
BarChart reference, Responsive / Dark mode / Export recipes, MCP overview,
Vibe gallery). Filling in the remaining 14 chart pages, auto-generating prop
tables off the TypeScript types, and wiring CI deploy are tracked under #128
follow-ups.
53 changes: 53 additions & 0 deletions docs-site/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';

// Goldchart docs site. Built independently of the library; deployed under
// `/docs/` of the GitHub Pages site alongside the existing playground.
export default defineConfig({
site: 'https://benseverndev-oss.github.io',
base: '/GoldenChart/docs',
integrations: [
starlight({
title: 'GoldenChart',
description:
'Hand-drawn, sketchy React charts and flowcharts. D3 does the math, Rough.js does the drawing, and a Vibe engine dials in the aesthetic.',
social: [
{
icon: 'github',
label: 'GitHub',
href: 'https://github.com/benseverndev-oss/GoldenChart',
},
],
sidebar: [
{
label: 'Start here',
items: [
{ label: 'Introduction', link: '/' },
{ label: 'Quick start', link: '/start/quick-start/' },
{ label: 'Brand vs. vibe', link: '/start/brand-vs-vibe/' },
],
},
{
label: 'Charts',
items: [{ label: 'BarChart', link: '/charts/bar-chart/' }],
},
{
label: 'Recipes',
items: [
{ label: 'Responsive sizing', link: '/recipes/responsive/' },
{ label: 'Dark mode', link: '/recipes/dark-mode/' },
{ label: 'Export to PNG/SVG', link: '/recipes/export/' },
],
},
{
label: 'MCP server',
items: [{ label: 'Overview', link: '/mcp/overview/' }],
},
{
label: 'Gallery',
items: [{ label: 'Vibes', link: '/gallery/vibes/' }],
},
],
}),
],
});
16 changes: 16 additions & 0 deletions docs-site/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "goldenchart-docs-site",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/starlight": "^0.36.0",
"astro": "^5.16.0"
}
}
7 changes: 7 additions & 0 deletions docs-site/src/content.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineCollection } from 'astro:content';
import { docsLoader } from '@astrojs/starlight/loaders';
import { docsSchema } from '@astrojs/starlight/schema';

export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
};
84 changes: 84 additions & 0 deletions docs-site/src/content/docs/charts/bar-chart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: BarChart
description: Single, grouped, or stacked bar chart. d3-scale computes the geometry; <RoughRectangle> sketches each bar.
---

The reference chart for the calc/render split. `bandScale` + `linearScale` lay out the geometry; `<RoughRectangle>` draws each bar. Supports single-series and multi-series (grouped or stacked) modes.

## Single series

```tsx
import { BarChart } from 'goldenchart';

<BarChart
width={480}
height={300}
data={[
{ label: 'Q1', value: 12 },
{ label: 'Q2', value: 19 },
{ label: 'Q3', value: 7 },
{ label: 'Q4', value: 24 },
]}
/>
```

## Grouped multi-series

```tsx
<BarChart
width={480}
height={300}
mode="grouped"
data={[
{ label: 'Q1', values: { team_a: 12, team_b: 8, team_c: 15 } },
{ label: 'Q2', values: { team_a: 19, team_b: 14, team_c: 22 } },
]}
/>
```

## Stacked

```tsx
<BarChart
width={480}
height={300}
mode="stacked"
data={[
{ label: 'Q1', values: { team_a: 12, team_b: 8, team_c: 15 } },
{ label: 'Q2', values: { team_a: 19, team_b: 14, team_c: 22 } },
]}
/>
```

## Props

| Prop | Type | Default | Notes |
|---|---|---|---|
| `data` | `ChartDatum[] \| MultiSeriesDatum[]` | required | `{label, value}` for single; `{label, values}` for grouped/stacked. |
| `width` | `number` | required | Pixel width of the surface. |
| `height` | `number` | required | Pixel height of the surface. |
| `mode` | `'single' \| 'grouped' \| 'stacked'` | `'single'` | Layout for multi-series data. |
| `seriesKeys` | `string[]` | union of value keys | Order/filter the series in multi-series modes. |
| `margin` | `Partial<Margin>` | per-chart defaults | Plot inset. |
| `vibe` | `VibeConfig` | inherited | Aesthetic. See [Brand vs. vibe](/start/brand-vs-vibe/). |
| `brand` | `BrandConfig` | inherited | Identity (palette / ink / page / font / logo). |
| `title` | `string` | — | Rendered as `<title>` / `aria-label`. |
| `description` | `string` | auto from data | Rendered as `<desc>`. Falls back to a generated summary. |
| `ariaLabel` | `string` | falls back to `title` | Explicit accessible label. |
| `dataTable` | `boolean` | — | Emit a visually-hidden data table for screen readers. |
| `showAxes` | `boolean` | `true` | |
| `showGrid` | `boolean` | `true` | |
| `showLegend` | `boolean` | `true` | Multi-series only. |
| `annotations` | `Annotation[]` | — | Lines, bands, callouts. |
| `xAxis` / `yAxis` | `AxisFormat` | — | Tick/format overrides. |
| `transitions` | `{ enabled?, durationMs? }` | `{ enabled: false }` | Opt-in enter/update animation on data change. Honours `prefers-reduced-motion`. |

## Accessibility

`BarChart` sets `role="img"` on the SVG, emits a `<title>` from the `title` prop, and a `<desc>` from the `description` prop (or an auto-generated summary like "Bar chart with 4 categories, values from 7 to 24."). Pair with `dataTable={true}` for full screen-reader parity.

## See also

- [Stacked vs. grouped semantics](/start/brand-vs-vibe/)
- [Responsive sizing](/recipes/responsive/)
- [Export to PNG/SVG](/recipes/export/)
44 changes: 44 additions & 0 deletions docs-site/src/content/docs/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: GoldenChart
description: Hand-drawn, sketchy React charts and flowcharts.
template: splash
hero:
tagline: Hand-drawn, sketchy React charts and flowcharts. D3 does the math, Rough.js does the drawing, and a Vibe engine dials in the aesthetic.
actions:
- text: Quick start
link: /start/quick-start/
icon: right-arrow
variant: primary
- text: Live demo
link: https://benseverndev-oss.github.io/GoldenChart/
icon: external
- text: GitHub
link: https://github.com/benseverndev-oss/GoldenChart
icon: external
---

import { Card, CardGrid } from '@astrojs/starlight/components';

<CardGrid stagger>
<Card title="Calc / render / vibe split" icon="puzzle">
Calculation layer (`d3-scale`, `d3-shape`, `d3-hierarchy`) computes coordinates
and never touches the DOM. The render layer (`roughjs`) turns those coordinates
into hand-drawn SVG. The Vibe engine translates `messy_sketch` into concrete
Rough.js parameters.
</Card>
<Card title="27 vibes, layered with brand" icon="seti:image">
Every chart can take a vibe preset (`chaotic_notebook`, `chalkboard`, `neon`, …)
plus a brand identity (palette, primary, ink, page, font, logo). Swap either
independently.
</Card>
<Card title="Headless + MCP first-class" icon="seti:python">
`goldenchart/server`'s `renderToSVGString` is the seam every server-side and
LLM-driven path builds on. The `goldenchart-mcp` package exposes the whole
library to agents level by level.
</Card>
<Card title="Interactive, accessible, exportable" icon="seti:react">
Hover/focus tooltips, brush-zoom, linked selection, screen-reader-friendly
`<title>` / `<desc>` / SR-only data tables, PNG/SVG client-side export — all
opt-in, all under a 75 KB gzipped budget.
</Card>
</CardGrid>
47 changes: 47 additions & 0 deletions docs-site/src/content/docs/mcp/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
title: MCP server overview
description: goldenchart-mcp exposes the whole library to LLM agents — render tools at every level, plus a critique-and-revise loop.
---

The `goldenchart-mcp` package ([npm](https://www.npmjs.com/package/goldenchart-mcp)) exposes GoldenChart over the Model Context Protocol so an agent can render hand-drawn charts and diagrams by calling tools. The defining idea: **a tool at every level of the library's architecture**, so an agent can work top-down (`render a bar chart`) or drop to the lowest level (`draw this rough path with this vibe`) when it needs full control.

```
Level 4 Orchestration / export ── compose_surface, export_*
Level 3 Charts ── render_bar_chart, render_line_chart, …
Level 2 Primitives ── render_rough_path, render_rough_rect, …
Level 1 Calculation (D3, pure) ── compute_scale, compute_pie, layout_tree, …
Level 0 Vibe (configuration) ── list_vibe_presets, resolve_vibe, preview_vibe
```

## Smart entry: `visualize_data`

Feed raw records and an optional plain-English query:

```jsonc
{
"data": [
{ "month": "Jan", "revenue": 120 },
{ "month": "Feb", "revenue": 180 },
{ "month": "Mar", "revenue": 240 }
],
"query": "revenue by month as a line in pencil",
"width": 480,
"height": 300
}
```

The server profiles the data, picks the best-fit chart, applies any parsed vibe from the query, and returns the SVG plus a rationale and ranked alternatives.

## Critique-and-revise loop

`suggest_improvements` profiles + renders + critiques. Each critique carries a machine-readable `fix` patch. `render_with_revision` accepts the original data plus a `Revisions` patch (`keepTopCategories`, `groupRemainderAs`, `maxSeries`, `chartType`), re-renders, and returns the next critique pass.

The `iterate-until-good` prompt wires these together: an agent calls `suggest_improvements`, picks the highest-severity fix, applies it via `render_with_revision`, and repeats until critiques empty (capped at 3 iterations).

## Install

```bash
npm install goldenchart-mcp
```

See the [`mcp/README.md`](https://github.com/benseverndev-oss/GoldenChart/tree/main/mcp) on GitHub for the full tool list and stdio configuration.
49 changes: 49 additions & 0 deletions docs-site/src/content/docs/recipes/dark-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
title: Dark mode
description: Pass a ThemedBrand to follow prefers-color-scheme automatically. SSR-safe.
---

A `ThemedBrand` carries a `light` and a `dark` `Brand`; `BrandProvider` (or `useResolvedBrand`) picks the right side based on the OS preference.

```tsx
import { BrandProvider, BarChart } from 'goldenchart';

<BrandProvider
brand={{
// `mode` defaults to `'auto'` — follows the OS.
light: {
palette: ['#0ea5e9', '#8b5cf6'],
ink: '#0f172a',
page: '#ffffff',
},
dark: {
palette: ['#38bdf8', '#a78bfa'],
ink: '#e2e8f0',
page: '#0f172a',
},
}}
>
<BarChart width={400} height={300} data={data} />
</BrandProvider>
```

## Pinning a side

```tsx
brand={{ mode: 'light', light: {...}, dark: {...} }}
brand={{ mode: 'dark', light: {...}, dark: {...} }}
```

## SSR

`useColorScheme()` defaults to `'light'` until a browser is available, then upgrades to the live `prefers-color-scheme` value on hydration. No layout shift if your initial server render uses the light palette.

## Charts that don't wrap in BrandProvider

Charts read `resolveBrand(brand).palette` directly inside their own body (their layout runs above `Surface`'s providers), so a chart that *doesn't* sit under a `BrandProvider` will default a `ThemedBrand` to the light side. To get auto-theming inside a chart's body, swap `resolveBrand(brand)` for `useResolvedBrand(brand)`:

```tsx
import { useResolvedBrand } from 'goldenchart';

const { palette } = useResolvedBrand(brand);
```
45 changes: 45 additions & 0 deletions docs-site/src/content/docs/recipes/export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: Export to PNG / SVG
description: Client-side rasterisation, downloads, and clipboard copy via toPng / toSvgString / downloadChart.
---

`goldenchart` ships browser-side export helpers that work against a live `<svg>`. Attach a ref to the chart's `<Surface>` (or its outer container) and pass the `<svg>` to one of the helpers.

## Grabbing the SVG ref

```tsx
import { useRef } from 'react';
import { BarChart, downloadChart } from 'goldenchart';

function ExportableChart({ data }) {
const svgRef = useRef<SVGSVGElement>(null);
return (
<>
<BarChart svgRef={svgRef} width={480} height={300} data={data} />
<button
onClick={() =>
downloadChart(svgRef.current!, { filename: 'sales', format: 'png', scale: 2 })
}
>
Download PNG
</button>
</>
);
}
```

(`svgRef` is available on `Surface` and forwards through every chart that wraps it.)

## API

| Function | Signature | Notes |
|---|---|---|
| `toSvgString(svg)` | `(SVGSVGElement) => string` | XML-serialises the live SVG, ensures `xmlns` is set. No font embedding. |
| `toPng(svg, opts?)` | `(SVGSVGElement, {scale?, width?, height?, background?}) => Promise<Blob>` | Off-DOM `<canvas>` rasterisation. `scale: 2` by default. |
| `downloadChart(svg, opts)` | `(SVGSVGElement, {filename, format, ...}) => Promise<void>` | Triggers an `<a download>` click. |
| `copyToClipboard(svg, format?)` | `(SVGSVGElement, 'svg' \| 'png') => Promise<void>` | Requires HTTPS + user gesture. Uses `ClipboardItem` for PNG. |
| `chartSvgFrom(container)` | `(Element \| null) => SVGSVGElement \| null` | Find the inner `<svg>` from a container ref. |

## Font handling

These helpers don't embed `@font-face`. The browser is already painting the chart, so the rasterised PNG matches what the user sees. For a *standalone* SVG that renders identically without the consumer's CSS-loaded fonts, use `goldenchart/server`'s `renderToSVGString` instead — that path embeds the font bytes.
Loading
Loading