graphein-mcp 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  validateSpec,
4
4
  repairSpec,
5
+ recommendChart,
5
6
  summarize
6
7
  } from "graphein";
7
8
  import { renderChart } from "@graphein/node";
@@ -14,6 +15,9 @@ function json(value) {
14
15
  function specType(spec) {
15
16
  return typeof spec === "object" && spec !== null && "type" in spec ? String(spec.type) : "(missing)";
16
17
  }
18
+ function isRecommendIntent(value) {
19
+ return value === void 0 || value === "trend" || value === "comparison" || value === "distribution" || value === "relationship" || value === "composition";
20
+ }
17
21
  function tidyError(e) {
18
22
  const out = { path: e.path, message: e.message };
19
23
  if (e.rule) out.rule = e.rule;
@@ -109,6 +113,28 @@ function validateChartHandler(args) {
109
113
  ]
110
114
  };
111
115
  }
116
+ function recommendChartHandler(args) {
117
+ if (!isRecommendIntent(args.intent)) {
118
+ return {
119
+ isError: true,
120
+ content: [
121
+ json({
122
+ ok: false,
123
+ message: "Unsupported intent. Expected trend, comparison, distribution, relationship, or composition."
124
+ })
125
+ ]
126
+ };
127
+ }
128
+ return {
129
+ isError: false,
130
+ content: [
131
+ json({
132
+ ok: true,
133
+ recommendations: recommendChart(args.data, { intent: args.intent, maxResults: args.maxResults })
134
+ })
135
+ ]
136
+ };
137
+ }
112
138
  function repairChartHandler(args) {
113
139
  const { spec, applied, remaining } = repairSpec(args.spec);
114
140
  return {
@@ -232,6 +258,19 @@ function createServer() {
232
258
  },
233
259
  async (args) => validateChartHandler(args)
234
260
  );
261
+ server.registerTool(
262
+ "recommend_chart",
263
+ {
264
+ title: "Recommend Graphein chart specs",
265
+ description: "Profile tidy rows and return ranked, ready-to-render ChartSpecs with rationale. Use this before guessing a chart type or encoding.",
266
+ inputSchema: {
267
+ data: z.array(z.record(z.string(), z.any())),
268
+ intent: z.string().optional(),
269
+ maxResults: z.number().int().positive().optional()
270
+ }
271
+ },
272
+ async (args) => recommendChartHandler(args)
273
+ );
235
274
  server.registerTool(
236
275
  "repair_chart",
237
276
  {
@@ -308,4 +347,4 @@ export {
308
347
  VERSION,
309
348
  createServer
310
349
  };
311
- //# sourceMappingURL=chunk-LBB3C2XH.js.map
350
+ //# sourceMappingURL=chunk-PHH2HU3O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/handlers.ts","../src/resources.ts","../src/create-server.ts"],"sourcesContent":["/**\n * Pure tool handlers — the generate → validate → repair → render → critique loop\n * exposed as plain functions so they can be unit-tested without a transport. Each\n * returns MCP-shaped content (`text` and/or `image` blocks). `createServer` wires\n * these into the `McpServer`.\n *\n * The handlers wrap `graphein`'s self-validating / self-repairing / self-explaining\n * core (`validateSpec`, `repairSpec`, `summarize`) and `@graphein/node`'s headless\n * `renderChart`, so an agent gets the chart **plus** a vision-free critique in one\n * call — and a one-step repair when its spec is slightly wrong.\n */\nimport {\n validateSpec,\n repairSpec,\n recommendChart,\n summarize,\n type ChartSpec,\n type RecommendOptions,\n type ValidationError,\n} from 'graphein';\nimport { renderChart } from '@graphein/node';\nimport type { JsonPatchOp } from './types.js';\n\n/** A subset of MCP content blocks the handlers emit. */\nexport type McpContent =\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string };\n\n/**\n * The shape every handler returns — assignable to the SDK's `CallToolResult`\n * (which is an open/passthrough type, hence the index signature).\n */\nexport interface ToolResult {\n content: McpContent[];\n isError?: boolean;\n [key: string]: unknown;\n}\n\n/** Options accepted by {@link renderChartHandler}. */\nexport interface RenderArgs {\n spec: unknown;\n width?: number;\n height?: number;\n dpr?: number;\n /** Auto-apply safe repairs before rendering when the spec is invalid. Default true. */\n repair?: boolean;\n}\n\nexport interface RecommendChartArgs {\n data: Record<string, unknown>[];\n intent?: string;\n maxResults?: number;\n}\n\nfunction text(value: string): McpContent {\n return { type: 'text', text: value };\n}\n\nfunction json(value: unknown): McpContent {\n return text(JSON.stringify(value, null, 2));\n}\n\nfunction specType(spec: unknown): string {\n return typeof spec === 'object' && spec !== null && 'type' in spec\n ? String((spec as { type: unknown }).type)\n : '(missing)';\n}\n\nfunction isRecommendIntent(value: string | undefined): value is RecommendOptions['intent'] {\n return (\n value === undefined ||\n value === 'trend' ||\n value === 'comparison' ||\n value === 'distribution' ||\n value === 'relationship' ||\n value === 'composition'\n );\n}\n\n/** Slim a validation error for an agent payload (drops nothing useful). */\nfunction tidyError(e: ValidationError) {\n const out: Record<string, unknown> = { path: e.path, message: e.message };\n if (e.rule) out.rule = e.rule;\n if (e.severity) out.severity = e.severity;\n if (e.fix) out.fix = e.fix;\n if (e.suggestion) out.suggestion = e.suggestion;\n return out;\n}\n\n/**\n * **The flagship.** Validate a spec, auto-repair it if it's safely fixable, render\n * it to a PNG headless, and return the image alongside a machine-readable critique\n * (the render report + lint warnings + any repairs applied). When the spec can't be\n * made valid, returns the structured errors and JSON-Patch fixes instead of an image\n * so the agent corrects in one step rather than regenerating.\n */\nexport function renderChartHandler(args: RenderArgs): ToolResult {\n const { spec, width, height, dpr, repair = true } = args;\n\n let working: unknown = spec;\n let validation = validateSpec(working);\n let repairs: JsonPatchOp[] = [];\n\n if (!validation.valid && repair) {\n const repaired = repairSpec(working);\n if (repaired.applied.length > 0) {\n working = repaired.spec;\n repairs = repaired.applied;\n validation = validateSpec(working);\n }\n }\n\n if (!validation.valid) {\n return {\n isError: true,\n content: [\n json({\n ok: false,\n rendered: false,\n stage: 'validate',\n type: specType(working),\n errors: validation.errors.map(tidyError),\n lint: validation.warnings.map(tidyError),\n repairsApplied: repairs,\n hint: 'Apply each error.fix JSON Patch (or the repair_chart tool), then call render_chart again. See the graphein://agent-guide and graphein://schema resources.',\n }),\n ],\n };\n }\n\n const type = specType(working);\n\n try {\n const { png, report, width: pxW, height: pxH } = renderChart(working as ChartSpec, {\n width,\n height,\n dpr,\n });\n return {\n isError: false,\n content: [\n { type: 'image', data: png.toString('base64'), mimeType: 'image/png' },\n json({\n ok: report.ok,\n rendered: true,\n type,\n pixelSize: { width: pxW, height: pxH },\n summary: report.summary,\n marks: report.markCount,\n series: report.seriesCount,\n colors: report.colorCount,\n diagnostics: report.diagnostics,\n lint: validation.warnings.map(tidyError),\n repairsApplied: repairs,\n }),\n ],\n };\n } catch (e) {\n return {\n isError: true,\n content: [\n json({\n ok: false,\n rendered: false,\n stage: 'render',\n type,\n message: e instanceof Error ? e.message : String(e),\n summary: summarize(working as ChartSpec) || undefined,\n repairsApplied: repairs,\n }),\n ],\n };\n }\n}\n\n/**\n * Validate a spec without rendering — fast structural + best-practice feedback.\n * Returns errors (each with a JSON-Patch `fix` when unambiguous and \"did you mean\"\n * suggestions) and lint `warnings`.\n */\nexport function validateChartHandler(args: { spec: unknown }): ToolResult {\n const result = validateSpec(args.spec);\n return {\n isError: false,\n content: [\n json({\n valid: result.valid,\n type: specType(args.spec),\n errors: result.errors.map(tidyError),\n warnings: result.warnings.map(tidyError),\n }),\n ],\n };\n}\n\n/** Recommend ready-to-render ChartSpecs from tidy rows and an optional intent. */\nexport function recommendChartHandler(args: RecommendChartArgs): ToolResult {\n if (!isRecommendIntent(args.intent)) {\n return {\n isError: true,\n content: [\n json({\n ok: false,\n message: 'Unsupported intent. Expected trend, comparison, distribution, relationship, or composition.',\n }),\n ],\n };\n }\n return {\n isError: false,\n content: [\n json({\n ok: true,\n recommendations: recommendChart(args.data, { intent: args.intent, maxResults: args.maxResults }),\n }),\n ],\n };\n}\n\n/**\n * Apply every safe, unambiguous repair Graphein proposes and return the corrected\n * spec, the JSON Patch operations applied, and whether it is now valid.\n */\nexport function repairChartHandler(args: { spec: unknown }): ToolResult {\n const { spec, applied, remaining } = repairSpec(args.spec);\n return {\n isError: false,\n content: [\n json({\n valid: remaining.length === 0,\n applied,\n remaining: remaining.map(tidyError),\n spec,\n }),\n ],\n };\n}\n\n/**\n * Return a deterministic, plain-English summary of what the chart's data shows —\n * doubles as alt-text, needs no LLM.\n */\nexport function summarizeChartHandler(args: { spec: unknown }): ToolResult {\n const result = validateSpec(args.spec);\n if (!result.valid) {\n return {\n isError: true,\n content: [\n json({\n summary: null,\n reason: 'Spec is invalid; fix it first (validate_chart / repair_chart).',\n errors: result.errors.map(tidyError),\n }),\n ],\n };\n }\n const summary = summarize(args.spec as ChartSpec);\n return {\n isError: false,\n content: [\n text(summary || `(No narrative summary is available for a '${specType(args.spec)}' chart.)`),\n ],\n };\n}\n","/**\n * The agent-facing knowledge Graphein serves as MCP **resources** — the schema\n * and the prose guides — so a model that has never seen Graphein's API can read\n * the contract at runtime instead of relying on training data. This is the core\n * of the MCP server's \"neutralize the training-data gap\" purpose.\n *\n * The files live in `../resources/` (committed copies of the repo's `docs/`, kept\n * in sync by `scripts/sync-resources.mjs`). They are read lazily and cached so the\n * server pays nothing until an agent actually asks for one.\n */\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\n\n/** Metadata describing one served resource. */\nexport interface GrapheinResource {\n /** Short registration name. */\n name: string;\n /** Stable `graphein://…` URI an agent reads. */\n uri: string;\n /** Human title. */\n title: string;\n /** What the resource is and when to read it. */\n description: string;\n /** IANA media type. */\n mimeType: string;\n /** File under `resources/`. */\n file: string;\n}\n\n/** Every resource the server exposes, in the order they should be advertised. */\nexport const RESOURCES: GrapheinResource[] = [\n {\n name: 'schema',\n uri: 'graphein://schema',\n title: 'Graphein ChartSpec JSON Schema',\n description:\n 'The machine-readable JSON Schema for every Graphein ChartSpec and DashboardSpec field — chart types, channels, transforms, annotations, and required properties. Generate or check a spec against this.',\n mimeType: 'application/json',\n file: 'chart-spec.schema.json',\n },\n {\n name: 'agent-guide',\n uri: 'graphein://agent-guide',\n title: 'Graphein Agent Guide',\n description:\n 'A task-oriented guide for producing correct Graphein charts: the one-rule workflow, choosing a chart type, encodings, transforms, the validate → repair → render → critique loop, and worked recipes. Read this first.',\n mimeType: 'text/markdown',\n file: 'agent-guide.md',\n },\n {\n name: 'spec-reference',\n uri: 'graphein://spec-reference',\n title: 'Graphein Spec Reference',\n description:\n 'The exhaustive field-by-field reference for every chart type, channel, transform, annotation, and modifier. Consult this for the precise shape of a specific field.',\n mimeType: 'text/markdown',\n file: 'spec-reference.md',\n },\n];\n\nconst cache = new Map<string, string>();\n\n/**\n * Read a bundled resource file's text, cached. Resolved relative to this module\n * so it works both from `dist/` (published) and `src/` (tests) — the committed\n * `resources/` dir always sits one level up from either.\n */\nexport function readResourceFile(file: string): string {\n const hit = cache.get(file);\n if (hit !== undefined) return hit;\n const url = new URL(`../resources/${file}`, import.meta.url);\n const text = readFileSync(fileURLToPath(url), 'utf8');\n cache.set(file, text);\n return text;\n}\n\n/** Look up a resource by its `graphein://…` URI. */\nexport function resourceByUri(uri: string): GrapheinResource | undefined {\n return RESOURCES.find((r) => r.uri === uri);\n}\n","/**\n * `createServer()` — builds the Graphein MCP server: the generate → validate →\n * repair → render → critique loop as four tools, the schema + guides as resources,\n * and a `create_chart` prompt that teaches the workflow. Split from the stdio\n * entry point (`server.ts`) so it can be driven over any transport — including the\n * in-memory transport in tests.\n */\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport {\n renderChartHandler,\n validateChartHandler,\n repairChartHandler,\n recommendChartHandler,\n summarizeChartHandler,\n} from './handlers.js';\nimport { RESOURCES, readResourceFile } from './resources.js';\n\n/** Package version, surfaced as the MCP server version. */\nexport const VERSION = '0.3.0';\n\nconst SERVER_INSTRUCTIONS = `Graphein is an agent-first data-visualization library: you describe a chart as one JSON ChartSpec ({ type, data, encoding, ... }) and it renders. This server lets you build correct charts without prior knowledge of the API.\n\nWorkflow:\n1. Read the graphein://agent-guide resource (and graphein://schema for exact fields) if you are unsure of the API.\n2. Shape your data as a tidy array — one row per observation, one column per variable.\n3. Emit a ChartSpec and call render_chart: it validates, auto-repairs safe mistakes, renders a PNG, and returns a vision-free critique (the render report + lint warnings). Read the critique to verify the chart looks right.\n4. If a spec is invalid, render_chart (and validate_chart) return structured errors each with a JSON-Patch 'fix' — apply it (or call repair_chart) and retry instead of regenerating.\nUse summarize_chart for deterministic alt-text. Every type rasterizes headlessly, including kpi, table, matrix, slicers and dashboard (static canvas snapshots).`;\n\n/** A permissive object schema for a Graphein spec — validateSpec does the real checking. */\nconst specSchema = z\n .record(z.string(), z.unknown())\n .describe(\n 'A Graphein ChartSpec or DashboardSpec object, e.g. { \"type\": \"line\", \"data\": [...], \"encoding\": {...} }. See the graphein://schema and graphein://agent-guide resources.',\n );\n\n/**\n * Build a fully-configured Graphein MCP server. The caller connects it to a\n * transport (`server.connect(transport)`).\n */\nexport function createServer(): McpServer {\n const server = new McpServer(\n { name: 'graphein-mcp', version: VERSION },\n { instructions: SERVER_INSTRUCTIONS },\n );\n\n // --- Tools: the runtime loop -------------------------------------------------\n\n server.registerTool(\n 'render_chart',\n {\n title: 'Render a Graphein chart',\n description:\n 'The one-call loop: validate a ChartSpec, auto-repair safe mistakes, render it to a PNG, and return the image plus a vision-free critique (render report, lint warnings, repairs applied). If the spec cannot be made valid, returns structured errors with JSON-Patch fixes instead of an image. Every type rasterizes headlessly — kpi, table, matrix, slicers and dashboard render static canvas snapshots.',\n inputSchema: {\n spec: specSchema,\n width: z.number().int().positive().optional().describe('Logical width in CSS px (default 800).'),\n height: z.number().int().positive().optional().describe('Logical height in CSS px (default 500).'),\n dpr: z.number().positive().optional().describe('Device pixel ratio for crisp output (default 2).'),\n repair: z\n .boolean()\n .optional()\n .describe('Auto-apply safe repairs before rendering when the spec is invalid (default true).'),\n },\n },\n async (args) => renderChartHandler(args),\n );\n\n server.registerTool(\n 'validate_chart',\n {\n title: 'Validate a Graphein chart spec',\n description:\n 'Validate a ChartSpec without rendering. Returns structural errors (each with a JSON-Patch `fix` when unambiguous, plus \"did you mean\" suggestions) and best-practice lint warnings. Fast feedback before rendering.',\n inputSchema: { spec: specSchema },\n },\n async (args) => validateChartHandler(args),\n );\n\n server.registerTool(\n 'recommend_chart',\n {\n title: 'Recommend Graphein chart specs',\n description:\n 'Profile tidy rows and return ranked, ready-to-render ChartSpecs with rationale. Use this before guessing a chart type or encoding.',\n inputSchema: {\n data: z.array(z.record(z.string(), z.any())),\n intent: z.string().optional(),\n maxResults: z.number().int().positive().optional(),\n },\n },\n async (args) => recommendChartHandler(args),\n );\n\n server.registerTool(\n 'repair_chart',\n {\n title: 'Repair a Graphein chart spec',\n description:\n 'Apply every safe, unambiguous fix Graphein proposes (misspelled chart type or enum, a temporal field typed as a category, …) and return the corrected spec, the JSON Patch ops applied, and whether it is now valid. Turns a near-miss into a one-step correction.',\n inputSchema: { spec: specSchema },\n },\n async (args) => repairChartHandler(args),\n );\n\n server.registerTool(\n 'summarize_chart',\n {\n title: 'Summarize a Graphein chart',\n description:\n 'Return a deterministic, plain-English description of what the chart\\'s data shows (e.g. \"Users grew 46% over six months, peaking in June\"). Doubles as alt-text; needs no LLM.',\n inputSchema: { spec: specSchema },\n },\n async (args) => summarizeChartHandler(args),\n );\n\n // --- Resources: deliver the API knowledge at runtime -------------------------\n\n for (const r of RESOURCES) {\n server.registerResource(\n r.name,\n r.uri,\n { title: r.title, description: r.description, mimeType: r.mimeType },\n async (uri) => ({\n contents: [{ uri: uri.href, mimeType: r.mimeType, text: readResourceFile(r.file) }],\n }),\n );\n }\n\n // --- Prompt: teach the workflow ---------------------------------------------\n\n server.registerPrompt(\n 'create_chart',\n {\n title: 'Create a Graphein chart',\n description:\n 'Scaffold the workflow for building a validated Graphein chart from a goal (and optional data).',\n argsSchema: {\n goal: z.string().describe('What the chart should show, e.g. \"monthly active users over the last year\".'),\n data: z\n .string()\n .optional()\n .describe('Optional: the data as a JSON array, or a description of the columns available.'),\n },\n },\n ({ goal, data }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Build a Graphein chart for this goal:\\n\\n${goal}\\n${\n data ? `\\nData:\\n${data}\\n` : ''\n }\\nSteps:\\n1. If unsure of the API, read the graphein://agent-guide resource (and graphein://schema for exact fields).\\n2. Shape the data as a tidy array — one row per observation, one column per variable.\\n3. Choose a chart type and write a single ChartSpec ({ type, data, encoding, title }).\\n4. Call render_chart with the spec. Read the returned critique (render report + lint) to confirm it looks right.\\n5. If it reports errors, apply each error.fix patch (or call repair_chart) and render again — do not regenerate from scratch.`,\n },\n },\n ],\n }),\n );\n\n return server;\n}\n"],"mappings":"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","names":["text"]}
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  resourceByUri,
9
9
  summarizeChartHandler,
10
10
  validateChartHandler
11
- } from "./chunk-LBB3C2XH.js";
11
+ } from "./chunk-PHH2HU3O.js";
12
12
  export {
13
13
  RESOURCES,
14
14
  VERSION,
package/dist/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createServer
4
- } from "./chunk-LBB3C2XH.js";
4
+ } from "./chunk-PHH2HU3O.js";
5
5
 
6
6
  // src/server.ts
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphein-mcp",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "description": "Model Context Protocol server for Graphein — wraps generate → validate → repair → render → critique into one tool call, and serves Graphein's schema + agent guide as resources so a model that never saw the API can still build correct charts.",
6
6
  "license": "MIT",
@@ -61,9 +61,9 @@
61
61
  "prepack": "tsup"
62
62
  },
63
63
  "dependencies": {
64
- "@graphein/node": "^0.9.1",
64
+ "@graphein/node": "^0.11.0",
65
65
  "@modelcontextprotocol/sdk": "^1.20.0",
66
- "graphein": "^0.9.1",
66
+ "graphein": "^0.11.0",
67
67
  "zod": "^3.23.0"
68
68
  }
69
69
  }
@@ -119,9 +119,11 @@ reference: [spec-reference → Transforms](./spec-reference.md#transforms).
119
119
 
120
120
  Rules of thumb: prefer `bar` over `pie` beyond ~6 slices; use `stack` for
121
121
  part‑to‑whole and grouped bars for direct comparison; reserve `pie` for a small
122
- number of shares. For a donut with several small slices, set `labels` to a
123
- `PieLabels` object — `placement:'auto'` keeps tight labels readable by moving
124
- them outside onto leader lines.
122
+ number of shares. For long category names or ranked lists, set `orientation:'horizontal'`
123
+ on a `bar` to lay categories down the left and grow bars rightward (works with
124
+ `stack`/`group`, `insights`, and annotations). For a donut with several small slices,
125
+ set `labels` to a `PieLabels` object — `placement:'auto'` keeps tight labels readable
126
+ by moving them outside onto leader lines.
125
127
 
126
128
  ## Recipes
127
129
 
@@ -475,6 +477,8 @@ specs still round‑trip through `JSON.stringify`. Three optional fields on any
475
477
  - `filter` clauses are a `{ param }` (cross‑filter) or a literal predicate:
476
478
  `{ field, equals }`, `{ field, oneOf }`, `{ field, range:[min,max] }`,
477
479
  `{ field, contains }`. An empty/absent selection matches everything (`empty:'all'`).
480
+ - Set `legend:{ "interactive": true }` on multi-series cartesian charts to let swatch
481
+ clicks publish the **visible** series as a set selection (shift/alt-click isolates).
478
482
 
479
483
  **Slicers** are first‑class visuals that publish a selection from a control:
480
484
 
@@ -586,6 +590,8 @@ unless `layout.navigators:'inline'`. Theme cascades to every view.
586
590
 
587
591
  The default look is **flat and modern** (solid fills, minimal shadows). The built‑in
588
592
  palette is accessible on both light and dark backgrounds.
593
+ Use top-level `"palette": "colorblind"` for Okabe–Ito categorical colors, or
594
+ `"bright"`, `"muted"`, or a custom string array when a chart needs different series colors.
589
595
 
590
596
  ## Hand-drawn ("sketch") mode
591
597
 
@@ -326,6 +326,20 @@
326
326
  "$ref": "#/$defs/ThemeInput",
327
327
  "description": "Theme name ('light' | 'dark') or a partial override object."
328
328
  },
329
+ "palette": {
330
+ "anyOf": [
331
+ {
332
+ "type": "string"
333
+ },
334
+ {
335
+ "type": "array",
336
+ "items": {
337
+ "type": "string"
338
+ }
339
+ }
340
+ ],
341
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
342
+ },
329
343
  "dimensions": {
330
344
  "$ref": "#/$defs/Dimensions"
331
345
  },
@@ -604,6 +618,20 @@
604
618
  "$ref": "#/$defs/ThemeInput",
605
619
  "description": "Theme name ('light' | 'dark') or a partial override object."
606
620
  },
621
+ "palette": {
622
+ "anyOf": [
623
+ {
624
+ "type": "string"
625
+ },
626
+ {
627
+ "type": "array",
628
+ "items": {
629
+ "type": "string"
630
+ }
631
+ }
632
+ ],
633
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
634
+ },
607
635
  "dimensions": {
608
636
  "$ref": "#/$defs/Dimensions"
609
637
  },
@@ -770,7 +798,8 @@
770
798
  "enum": [
771
799
  "vertical",
772
800
  "horizontal"
773
- ]
801
+ ],
802
+ "description": "Bar direction. `'vertical'` (default) draws columns growing up from the x-axis; `'horizontal'` draws bars growing rightward from the y-axis, with the `x` categories listed down the left — ideal when category names are long. The `encoding` is unchanged either way (`x` = category, `y` = value)."
774
803
  },
775
804
  "stack": {
776
805
  "type": "boolean",
@@ -885,6 +914,20 @@
885
914
  "$ref": "#/$defs/ThemeInput",
886
915
  "description": "Theme name ('light' | 'dark') or a partial override object."
887
916
  },
917
+ "palette": {
918
+ "anyOf": [
919
+ {
920
+ "type": "string"
921
+ },
922
+ {
923
+ "type": "array",
924
+ "items": {
925
+ "type": "string"
926
+ }
927
+ }
928
+ ],
929
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
930
+ },
888
931
  "dimensions": {
889
932
  "$ref": "#/$defs/Dimensions"
890
933
  },
@@ -1094,6 +1137,20 @@
1094
1137
  "$ref": "#/$defs/ThemeInput",
1095
1138
  "description": "Theme name ('light' | 'dark') or a partial override object."
1096
1139
  },
1140
+ "palette": {
1141
+ "anyOf": [
1142
+ {
1143
+ "type": "string"
1144
+ },
1145
+ {
1146
+ "type": "array",
1147
+ "items": {
1148
+ "type": "string"
1149
+ }
1150
+ }
1151
+ ],
1152
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
1153
+ },
1097
1154
  "dimensions": {
1098
1155
  "$ref": "#/$defs/Dimensions"
1099
1156
  },
@@ -1272,6 +1329,20 @@
1272
1329
  "$ref": "#/$defs/ThemeInput",
1273
1330
  "description": "Theme name ('light' | 'dark') or a partial override object."
1274
1331
  },
1332
+ "palette": {
1333
+ "anyOf": [
1334
+ {
1335
+ "type": "string"
1336
+ },
1337
+ {
1338
+ "type": "array",
1339
+ "items": {
1340
+ "type": "string"
1341
+ }
1342
+ }
1343
+ ],
1344
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
1345
+ },
1275
1346
  "dimensions": {
1276
1347
  "$ref": "#/$defs/Dimensions"
1277
1348
  },
@@ -1555,6 +1626,20 @@
1555
1626
  "$ref": "#/$defs/ThemeInput",
1556
1627
  "description": "Theme name ('light' | 'dark') or a partial override object."
1557
1628
  },
1629
+ "palette": {
1630
+ "anyOf": [
1631
+ {
1632
+ "type": "string"
1633
+ },
1634
+ {
1635
+ "type": "array",
1636
+ "items": {
1637
+ "type": "string"
1638
+ }
1639
+ }
1640
+ ],
1641
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
1642
+ },
1558
1643
  "dimensions": {
1559
1644
  "$ref": "#/$defs/Dimensions"
1560
1645
  },
@@ -1832,6 +1917,20 @@
1832
1917
  "$ref": "#/$defs/ThemeInput",
1833
1918
  "description": "Theme name ('light' | 'dark') or a partial override object."
1834
1919
  },
1920
+ "palette": {
1921
+ "anyOf": [
1922
+ {
1923
+ "type": "string"
1924
+ },
1925
+ {
1926
+ "type": "array",
1927
+ "items": {
1928
+ "type": "string"
1929
+ }
1930
+ }
1931
+ ],
1932
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
1933
+ },
1835
1934
  "dimensions": {
1836
1935
  "$ref": "#/$defs/Dimensions"
1837
1936
  },
@@ -2423,6 +2522,20 @@
2423
2522
  "$ref": "#/$defs/ThemeInput",
2424
2523
  "description": "Theme name ('light' | 'dark') or a partial override object."
2425
2524
  },
2525
+ "palette": {
2526
+ "anyOf": [
2527
+ {
2528
+ "type": "string"
2529
+ },
2530
+ {
2531
+ "type": "array",
2532
+ "items": {
2533
+ "type": "string"
2534
+ }
2535
+ }
2536
+ ],
2537
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
2538
+ },
2426
2539
  "dimensions": {
2427
2540
  "$ref": "#/$defs/Dimensions"
2428
2541
  },
@@ -2604,6 +2717,20 @@
2604
2717
  "$ref": "#/$defs/ThemeInput",
2605
2718
  "description": "Theme name ('light' | 'dark') or a partial override object."
2606
2719
  },
2720
+ "palette": {
2721
+ "anyOf": [
2722
+ {
2723
+ "type": "string"
2724
+ },
2725
+ {
2726
+ "type": "array",
2727
+ "items": {
2728
+ "type": "string"
2729
+ }
2730
+ }
2731
+ ],
2732
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
2733
+ },
2607
2734
  "dimensions": {
2608
2735
  "$ref": "#/$defs/Dimensions"
2609
2736
  },
@@ -2762,6 +2889,20 @@
2762
2889
  "$ref": "#/$defs/ThemeInput",
2763
2890
  "description": "Theme name ('light' | 'dark') or a partial override object."
2764
2891
  },
2892
+ "palette": {
2893
+ "anyOf": [
2894
+ {
2895
+ "type": "string"
2896
+ },
2897
+ {
2898
+ "type": "array",
2899
+ "items": {
2900
+ "type": "string"
2901
+ }
2902
+ }
2903
+ ],
2904
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
2905
+ },
2765
2906
  "dimensions": {
2766
2907
  "$ref": "#/$defs/Dimensions"
2767
2908
  },
@@ -3340,6 +3481,20 @@
3340
3481
  "$ref": "#/$defs/ThemeInput",
3341
3482
  "description": "Theme name ('light' | 'dark') or a partial override object."
3342
3483
  },
3484
+ "palette": {
3485
+ "anyOf": [
3486
+ {
3487
+ "type": "string"
3488
+ },
3489
+ {
3490
+ "type": "array",
3491
+ "items": {
3492
+ "type": "string"
3493
+ }
3494
+ }
3495
+ ],
3496
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
3497
+ },
3343
3498
  "dimensions": {
3344
3499
  "$ref": "#/$defs/Dimensions"
3345
3500
  },
@@ -3542,6 +3697,20 @@
3542
3697
  "$ref": "#/$defs/ThemeInput",
3543
3698
  "description": "Theme name ('light' | 'dark') or a partial override object."
3544
3699
  },
3700
+ "palette": {
3701
+ "anyOf": [
3702
+ {
3703
+ "type": "string"
3704
+ },
3705
+ {
3706
+ "type": "array",
3707
+ "items": {
3708
+ "type": "string"
3709
+ }
3710
+ }
3711
+ ],
3712
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
3713
+ },
3545
3714
  "dimensions": {
3546
3715
  "$ref": "#/$defs/Dimensions"
3547
3716
  },
@@ -3846,6 +4015,20 @@
3846
4015
  "$ref": "#/$defs/ThemeInput",
3847
4016
  "description": "Theme name ('light' | 'dark') or a partial override object."
3848
4017
  },
4018
+ "palette": {
4019
+ "anyOf": [
4020
+ {
4021
+ "type": "string"
4022
+ },
4023
+ {
4024
+ "type": "array",
4025
+ "items": {
4026
+ "type": "string"
4027
+ }
4028
+ }
4029
+ ],
4030
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
4031
+ },
3849
4032
  "dimensions": {
3850
4033
  "$ref": "#/$defs/Dimensions"
3851
4034
  },
@@ -4082,6 +4265,20 @@
4082
4265
  "$ref": "#/$defs/ThemeInput",
4083
4266
  "description": "Theme name ('light' | 'dark') or a partial override object."
4084
4267
  },
4268
+ "palette": {
4269
+ "anyOf": [
4270
+ {
4271
+ "type": "string"
4272
+ },
4273
+ {
4274
+ "type": "array",
4275
+ "items": {
4276
+ "type": "string"
4277
+ }
4278
+ }
4279
+ ],
4280
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
4281
+ },
4085
4282
  "dimensions": {
4086
4283
  "$ref": "#/$defs/Dimensions"
4087
4284
  },
@@ -4361,6 +4558,20 @@
4361
4558
  "$ref": "#/$defs/ThemeInput",
4362
4559
  "description": "Theme name ('light' | 'dark') or a partial override object."
4363
4560
  },
4561
+ "palette": {
4562
+ "anyOf": [
4563
+ {
4564
+ "type": "string"
4565
+ },
4566
+ {
4567
+ "type": "array",
4568
+ "items": {
4569
+ "type": "string"
4570
+ }
4571
+ }
4572
+ ],
4573
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
4574
+ },
4364
4575
  "dimensions": {
4365
4576
  "$ref": "#/$defs/Dimensions"
4366
4577
  },
@@ -4514,6 +4725,14 @@
4514
4725
  },
4515
4726
  "title": {
4516
4727
  "type": "string"
4728
+ },
4729
+ "interactive": {
4730
+ "type": "boolean",
4731
+ "description": "Allow clicking legend items to toggle/isolate series; publishes a selection."
4732
+ },
4733
+ "param": {
4734
+ "type": "string",
4735
+ "description": "Selection param to publish legend visibility to; defaults to the series field name."
4517
4736
  }
4518
4737
  },
4519
4738
  "additionalProperties": false
@@ -4548,6 +4767,20 @@
4548
4767
  "$ref": "#/$defs/ThemeInput",
4549
4768
  "description": "Theme name ('light' | 'dark') or a partial override object."
4550
4769
  },
4770
+ "palette": {
4771
+ "anyOf": [
4772
+ {
4773
+ "type": "string"
4774
+ },
4775
+ {
4776
+ "type": "array",
4777
+ "items": {
4778
+ "type": "string"
4779
+ }
4780
+ }
4781
+ ],
4782
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
4783
+ },
4551
4784
  "dimensions": {
4552
4785
  "$ref": "#/$defs/Dimensions"
4553
4786
  },
@@ -4781,6 +5014,20 @@
4781
5014
  "$ref": "#/$defs/ThemeInput",
4782
5015
  "description": "Theme name ('light' | 'dark') or a partial override object."
4783
5016
  },
5017
+ "palette": {
5018
+ "anyOf": [
5019
+ {
5020
+ "type": "string"
5021
+ },
5022
+ {
5023
+ "type": "array",
5024
+ "items": {
5025
+ "type": "string"
5026
+ }
5027
+ }
5028
+ ],
5029
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
5030
+ },
4784
5031
  "dimensions": {
4785
5032
  "$ref": "#/$defs/Dimensions"
4786
5033
  },
@@ -5024,6 +5271,20 @@
5024
5271
  "$ref": "#/$defs/ThemeInput",
5025
5272
  "description": "Theme name ('light' | 'dark') or a partial override object."
5026
5273
  },
5274
+ "palette": {
5275
+ "anyOf": [
5276
+ {
5277
+ "type": "string"
5278
+ },
5279
+ {
5280
+ "type": "array",
5281
+ "items": {
5282
+ "type": "string"
5283
+ }
5284
+ }
5285
+ ],
5286
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
5287
+ },
5027
5288
  "dimensions": {
5028
5289
  "$ref": "#/$defs/Dimensions"
5029
5290
  },
@@ -5306,6 +5567,20 @@
5306
5567
  "$ref": "#/$defs/ThemeInput",
5307
5568
  "description": "Theme name ('light' | 'dark') or a partial override object."
5308
5569
  },
5570
+ "palette": {
5571
+ "anyOf": [
5572
+ {
5573
+ "type": "string"
5574
+ },
5575
+ {
5576
+ "type": "array",
5577
+ "items": {
5578
+ "type": "string"
5579
+ }
5580
+ }
5581
+ ],
5582
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
5583
+ },
5309
5584
  "dimensions": {
5310
5585
  "$ref": "#/$defs/Dimensions"
5311
5586
  },
@@ -5573,6 +5848,20 @@
5573
5848
  "$ref": "#/$defs/ThemeInput",
5574
5849
  "description": "Theme name ('light' | 'dark') or a partial override object."
5575
5850
  },
5851
+ "palette": {
5852
+ "anyOf": [
5853
+ {
5854
+ "type": "string"
5855
+ },
5856
+ {
5857
+ "type": "array",
5858
+ "items": {
5859
+ "type": "string"
5860
+ }
5861
+ }
5862
+ ],
5863
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
5864
+ },
5576
5865
  "dimensions": {
5577
5866
  "$ref": "#/$defs/Dimensions"
5578
5867
  },
@@ -5739,6 +6028,20 @@
5739
6028
  "$ref": "#/$defs/ThemeInput",
5740
6029
  "description": "Theme name ('light' | 'dark') or a partial override object."
5741
6030
  },
6031
+ "palette": {
6032
+ "anyOf": [
6033
+ {
6034
+ "type": "string"
6035
+ },
6036
+ {
6037
+ "type": "array",
6038
+ "items": {
6039
+ "type": "string"
6040
+ }
6041
+ }
6042
+ ],
6043
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
6044
+ },
5742
6045
  "dimensions": {
5743
6046
  "$ref": "#/$defs/Dimensions"
5744
6047
  },
@@ -6004,6 +6307,20 @@
6004
6307
  "$ref": "#/$defs/ThemeInput",
6005
6308
  "description": "Theme name ('light' | 'dark') or a partial override object."
6006
6309
  },
6310
+ "palette": {
6311
+ "anyOf": [
6312
+ {
6313
+ "type": "string"
6314
+ },
6315
+ {
6316
+ "type": "array",
6317
+ "items": {
6318
+ "type": "string"
6319
+ }
6320
+ }
6321
+ ],
6322
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
6323
+ },
6007
6324
  "dimensions": {
6008
6325
  "$ref": "#/$defs/Dimensions"
6009
6326
  },
@@ -6215,6 +6532,20 @@
6215
6532
  "$ref": "#/$defs/ThemeInput",
6216
6533
  "description": "Theme name ('light' | 'dark') or a partial override object."
6217
6534
  },
6535
+ "palette": {
6536
+ "anyOf": [
6537
+ {
6538
+ "type": "string"
6539
+ },
6540
+ {
6541
+ "type": "array",
6542
+ "items": {
6543
+ "type": "string"
6544
+ }
6545
+ }
6546
+ ],
6547
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
6548
+ },
6218
6549
  "dimensions": {
6219
6550
  "$ref": "#/$defs/Dimensions"
6220
6551
  },
@@ -6518,6 +6849,20 @@
6518
6849
  "$ref": "#/$defs/ThemeInput",
6519
6850
  "description": "Theme name ('light' | 'dark') or a partial override object."
6520
6851
  },
6852
+ "palette": {
6853
+ "anyOf": [
6854
+ {
6855
+ "type": "string"
6856
+ },
6857
+ {
6858
+ "type": "array",
6859
+ "items": {
6860
+ "type": "string"
6861
+ }
6862
+ }
6863
+ ],
6864
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
6865
+ },
6521
6866
  "dimensions": {
6522
6867
  "$ref": "#/$defs/Dimensions"
6523
6868
  },
@@ -6794,6 +7139,20 @@
6794
7139
  "$ref": "#/$defs/ThemeInput",
6795
7140
  "description": "Theme name ('light' | 'dark') or a partial override object."
6796
7141
  },
7142
+ "palette": {
7143
+ "anyOf": [
7144
+ {
7145
+ "type": "string"
7146
+ },
7147
+ {
7148
+ "type": "array",
7149
+ "items": {
7150
+ "type": "string"
7151
+ }
7152
+ }
7153
+ ],
7154
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
7155
+ },
6797
7156
  "dimensions": {
6798
7157
  "$ref": "#/$defs/Dimensions"
6799
7158
  },
@@ -7045,8 +7404,7 @@
7045
7404
  "positive",
7046
7405
  "negative"
7047
7406
  ],
7048
- "additionalProperties": false,
7049
- "description": "Graphein design tokens. Flat, modern aesthetic: solid fills, minimal shadows, restrained radii, and a teal accent. Two built-in themes (light/dark). Token values are literals so this module stays dependency-free; charts combine these with the color module for derived ramps and contrast."
7407
+ "additionalProperties": false
7050
7408
  },
7051
7409
  "ThemeFont": {
7052
7410
  "type": "object",
@@ -7314,6 +7672,20 @@
7314
7672
  "$ref": "#/$defs/ThemeInput",
7315
7673
  "description": "Theme name ('light' | 'dark') or a partial override object."
7316
7674
  },
7675
+ "palette": {
7676
+ "anyOf": [
7677
+ {
7678
+ "type": "string"
7679
+ },
7680
+ {
7681
+ "type": "array",
7682
+ "items": {
7683
+ "type": "string"
7684
+ }
7685
+ }
7686
+ ],
7687
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
7688
+ },
7317
7689
  "dimensions": {
7318
7690
  "$ref": "#/$defs/Dimensions"
7319
7691
  },
@@ -7624,6 +7996,20 @@
7624
7996
  "$ref": "#/$defs/ThemeInput",
7625
7997
  "description": "Theme name ('light' | 'dark') or a partial override object."
7626
7998
  },
7999
+ "palette": {
8000
+ "anyOf": [
8001
+ {
8002
+ "type": "string"
8003
+ },
8004
+ {
8005
+ "type": "array",
8006
+ "items": {
8007
+ "type": "string"
8008
+ }
8009
+ }
8010
+ ],
8011
+ "description": "Named categorical palette ('graphein'|'colorblind'|'bright'|'muted') or an explicit array of series colors."
8012
+ },
7627
8013
  "dimensions": {
7628
8014
  "$ref": "#/$defs/Dimensions"
7629
8015
  },
@@ -67,10 +67,11 @@ Shared by **all** chart types.
67
67
  | `data` | `Datum[]` | — | Row‑oriented records. Required for every chart/table. |
68
68
  | `transform` | `Transform[]` | — | Declarative pipeline that reshapes `data` **before** charting (aggregate, bin, filter, fold, timeUnit). See [Transforms](#transforms). |
69
69
  | `theme` | `'light' \| 'dark' \| ThemeOverride` | `'light'` | Theme name or a partial override (see [Themes](#themes)). |
70
+ | `palette` | `'graphein' \| 'colorblind' \| 'bright' \| 'muted' \| string[]` | theme palette | Categorical series palette selector, or explicit colors cycled across series. |
70
71
  | `dimensions` | `{ width?, height?, autoResize? }` | responsive | Omit `width`/`height` to fill the container and track resizes. |
71
72
  | `title` | `string \| TitleConfig` | — | `string`, or `{ text, subtitle?, align? }`. |
72
73
  | `description` | `string` | auto | Accessible alt text. Used verbatim as the chart's `aria-label`; auto‑synthesized from type/title/data when omitted (see [Accessibility](#accessibility)). |
73
- | `legend` | `LegendConfig \| boolean` | auto | `false` hides it; `{ show?, position?, title? }`. `position`: `top \| right \| bottom \| left`. |
74
+ | `legend` | `LegendConfig \| boolean` | auto | `false` hides it; `{ show?, position?, title?, interactive?, param? }`. `interactive:true` lets legend swatch clicks publish visible series as `{ kind:'set', field:<series field>, values:[...] }` (shift/alt-click isolates); `param` defaults to the series field. `position`: `top \| right \| bottom \| left`. |
74
75
  | `tooltip` | `TooltipConfig \| boolean` | `true` | `false` (or `{ show: false }`) disables hover tooltips. |
75
76
  | `axes` | `{ x?: AxisConfig, y?: AxisConfig }` | auto | Per‑axis overrides (cartesian charts). |
76
77
  | `animation` | `AnimationConfig \| boolean` | on | Brief entrance on first render. `false` disables; `{ enabled?, duration?, easing? }`. Honors `prefers-reduced-motion` (see [Animation](#animation)). |
@@ -480,13 +481,13 @@ Columns/bars with grouped or stacked series and rounded corners.
480
481
  | Field | Type | Notes |
481
482
  | --- | --- | --- |
482
483
  | `encoding` | requires `x`, `y`; optional `series` | — |
483
- | `orientation` | `'vertical' \| 'horizontal'` | Default `vertical`. |
484
+ | `orientation` | `'vertical' \| 'horizontal'` | Default `vertical`. `'horizontal'` lists categories down the left gutter and grows value bars rightward from a left baseline (value axis along the bottom) — ideal for long category names or ranked lists. Works with `stack`/`group`, `cornerRadius`, `insights`, and annotations. |
484
485
  | `stack` | `boolean` | Stack series. |
485
486
  | `group` | `boolean` | Side‑by‑side groups. Default when `series` is present and not stacked. |
486
487
  | `cornerRadius` | `number` | Bar corner radius in px. |
487
488
  | `facet` | `FacetConfig` | Split into a [trellis grid of small multiples](#faceting-small-multiples). |
488
489
 
489
- → [`examples/bar-grouped.json`](./examples/bar-grouped.json)
490
+ → [`examples/bar-grouped.json`](./examples/bar-grouped.json) · [`examples/bar-horizontal.json`](./examples/bar-horizontal.json)
490
491
 
491
492
  ### scatter
492
493
 
@@ -1120,7 +1121,9 @@ with an optional `base`:
1120
1121
  Overridable token groups: `color` (`background`, `surface`, `text`, `textMuted`,
1121
1122
  `axis`, `grid`, `border`, `accent`, `palette[]`, `positive`, `negative`),
1122
1123
  `font`, `spacing`, `radius`, `stroke`. The default categorical `palette` is a
1123
- vibrant, accessibility‑tuned 10‑color set that reads well on light and dark.
1124
+ vibrant, accessibility‑tuned 10‑color set that reads well on light and dark. For
1125
+ one chart, prefer top-level `palette: 'colorblind'` (or `'bright'`, `'muted'`, or
1126
+ an explicit `string[]`) instead of overriding theme tokens.
1124
1127
 
1125
1128
  ---
1126
1129
 
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/handlers.ts","../src/resources.ts","../src/create-server.ts"],"sourcesContent":["/**\n * Pure tool handlers — the generate → validate → repair → render → critique loop\n * exposed as plain functions so they can be unit-tested without a transport. Each\n * returns MCP-shaped content (`text` and/or `image` blocks). `createServer` wires\n * these into the `McpServer`.\n *\n * The handlers wrap `graphein`'s self-validating / self-repairing / self-explaining\n * core (`validateSpec`, `repairSpec`, `summarize`) and `@graphein/node`'s headless\n * `renderChart`, so an agent gets the chart **plus** a vision-free critique in one\n * call — and a one-step repair when its spec is slightly wrong.\n */\nimport {\n validateSpec,\n repairSpec,\n summarize,\n type ChartSpec,\n type ValidationError,\n} from 'graphein';\nimport { renderChart } from '@graphein/node';\nimport type { JsonPatchOp } from './types.js';\n\n/** A subset of MCP content blocks the handlers emit. */\nexport type McpContent =\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string };\n\n/**\n * The shape every handler returns — assignable to the SDK's `CallToolResult`\n * (which is an open/passthrough type, hence the index signature).\n */\nexport interface ToolResult {\n content: McpContent[];\n isError?: boolean;\n [key: string]: unknown;\n}\n\n/** Options accepted by {@link renderChartHandler}. */\nexport interface RenderArgs {\n spec: unknown;\n width?: number;\n height?: number;\n dpr?: number;\n /** Auto-apply safe repairs before rendering when the spec is invalid. Default true. */\n repair?: boolean;\n}\n\nfunction text(value: string): McpContent {\n return { type: 'text', text: value };\n}\n\nfunction json(value: unknown): McpContent {\n return text(JSON.stringify(value, null, 2));\n}\n\nfunction specType(spec: unknown): string {\n return typeof spec === 'object' && spec !== null && 'type' in spec\n ? String((spec as { type: unknown }).type)\n : '(missing)';\n}\n\n/** Slim a validation error for an agent payload (drops nothing useful). */\nfunction tidyError(e: ValidationError) {\n const out: Record<string, unknown> = { path: e.path, message: e.message };\n if (e.rule) out.rule = e.rule;\n if (e.severity) out.severity = e.severity;\n if (e.fix) out.fix = e.fix;\n if (e.suggestion) out.suggestion = e.suggestion;\n return out;\n}\n\n/**\n * **The flagship.** Validate a spec, auto-repair it if it's safely fixable, render\n * it to a PNG headless, and return the image alongside a machine-readable critique\n * (the render report + lint warnings + any repairs applied). When the spec can't be\n * made valid, returns the structured errors and JSON-Patch fixes instead of an image\n * so the agent corrects in one step rather than regenerating.\n */\nexport function renderChartHandler(args: RenderArgs): ToolResult {\n const { spec, width, height, dpr, repair = true } = args;\n\n let working: unknown = spec;\n let validation = validateSpec(working);\n let repairs: JsonPatchOp[] = [];\n\n if (!validation.valid && repair) {\n const repaired = repairSpec(working);\n if (repaired.applied.length > 0) {\n working = repaired.spec;\n repairs = repaired.applied;\n validation = validateSpec(working);\n }\n }\n\n if (!validation.valid) {\n return {\n isError: true,\n content: [\n json({\n ok: false,\n rendered: false,\n stage: 'validate',\n type: specType(working),\n errors: validation.errors.map(tidyError),\n lint: validation.warnings.map(tidyError),\n repairsApplied: repairs,\n hint: 'Apply each error.fix JSON Patch (or the repair_chart tool), then call render_chart again. See the graphein://agent-guide and graphein://schema resources.',\n }),\n ],\n };\n }\n\n const type = specType(working);\n\n try {\n const { png, report, width: pxW, height: pxH } = renderChart(working as ChartSpec, {\n width,\n height,\n dpr,\n });\n return {\n isError: false,\n content: [\n { type: 'image', data: png.toString('base64'), mimeType: 'image/png' },\n json({\n ok: report.ok,\n rendered: true,\n type,\n pixelSize: { width: pxW, height: pxH },\n summary: report.summary,\n marks: report.markCount,\n series: report.seriesCount,\n colors: report.colorCount,\n diagnostics: report.diagnostics,\n lint: validation.warnings.map(tidyError),\n repairsApplied: repairs,\n }),\n ],\n };\n } catch (e) {\n return {\n isError: true,\n content: [\n json({\n ok: false,\n rendered: false,\n stage: 'render',\n type,\n message: e instanceof Error ? e.message : String(e),\n summary: summarize(working as ChartSpec) || undefined,\n repairsApplied: repairs,\n }),\n ],\n };\n }\n}\n\n/**\n * Validate a spec without rendering — fast structural + best-practice feedback.\n * Returns errors (each with a JSON-Patch `fix` when unambiguous and \"did you mean\"\n * suggestions) and lint `warnings`.\n */\nexport function validateChartHandler(args: { spec: unknown }): ToolResult {\n const result = validateSpec(args.spec);\n return {\n isError: false,\n content: [\n json({\n valid: result.valid,\n type: specType(args.spec),\n errors: result.errors.map(tidyError),\n warnings: result.warnings.map(tidyError),\n }),\n ],\n };\n}\n\n/**\n * Apply every safe, unambiguous repair Graphein proposes and return the corrected\n * spec, the JSON Patch operations applied, and whether it is now valid.\n */\nexport function repairChartHandler(args: { spec: unknown }): ToolResult {\n const { spec, applied, remaining } = repairSpec(args.spec);\n return {\n isError: false,\n content: [\n json({\n valid: remaining.length === 0,\n applied,\n remaining: remaining.map(tidyError),\n spec,\n }),\n ],\n };\n}\n\n/**\n * Return a deterministic, plain-English summary of what the chart's data shows —\n * doubles as alt-text, needs no LLM.\n */\nexport function summarizeChartHandler(args: { spec: unknown }): ToolResult {\n const result = validateSpec(args.spec);\n if (!result.valid) {\n return {\n isError: true,\n content: [\n json({\n summary: null,\n reason: 'Spec is invalid; fix it first (validate_chart / repair_chart).',\n errors: result.errors.map(tidyError),\n }),\n ],\n };\n }\n const summary = summarize(args.spec as ChartSpec);\n return {\n isError: false,\n content: [\n text(summary || `(No narrative summary is available for a '${specType(args.spec)}' chart.)`),\n ],\n };\n}\n","/**\n * The agent-facing knowledge Graphein serves as MCP **resources** — the schema\n * and the prose guides — so a model that has never seen Graphein's API can read\n * the contract at runtime instead of relying on training data. This is the core\n * of the MCP server's \"neutralize the training-data gap\" purpose.\n *\n * The files live in `../resources/` (committed copies of the repo's `docs/`, kept\n * in sync by `scripts/sync-resources.mjs`). They are read lazily and cached so the\n * server pays nothing until an agent actually asks for one.\n */\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\n\n/** Metadata describing one served resource. */\nexport interface GrapheinResource {\n /** Short registration name. */\n name: string;\n /** Stable `graphein://…` URI an agent reads. */\n uri: string;\n /** Human title. */\n title: string;\n /** What the resource is and when to read it. */\n description: string;\n /** IANA media type. */\n mimeType: string;\n /** File under `resources/`. */\n file: string;\n}\n\n/** Every resource the server exposes, in the order they should be advertised. */\nexport const RESOURCES: GrapheinResource[] = [\n {\n name: 'schema',\n uri: 'graphein://schema',\n title: 'Graphein ChartSpec JSON Schema',\n description:\n 'The machine-readable JSON Schema for every Graphein ChartSpec and DashboardSpec field — chart types, channels, transforms, annotations, and required properties. Generate or check a spec against this.',\n mimeType: 'application/json',\n file: 'chart-spec.schema.json',\n },\n {\n name: 'agent-guide',\n uri: 'graphein://agent-guide',\n title: 'Graphein Agent Guide',\n description:\n 'A task-oriented guide for producing correct Graphein charts: the one-rule workflow, choosing a chart type, encodings, transforms, the validate → repair → render → critique loop, and worked recipes. Read this first.',\n mimeType: 'text/markdown',\n file: 'agent-guide.md',\n },\n {\n name: 'spec-reference',\n uri: 'graphein://spec-reference',\n title: 'Graphein Spec Reference',\n description:\n 'The exhaustive field-by-field reference for every chart type, channel, transform, annotation, and modifier. Consult this for the precise shape of a specific field.',\n mimeType: 'text/markdown',\n file: 'spec-reference.md',\n },\n];\n\nconst cache = new Map<string, string>();\n\n/**\n * Read a bundled resource file's text, cached. Resolved relative to this module\n * so it works both from `dist/` (published) and `src/` (tests) — the committed\n * `resources/` dir always sits one level up from either.\n */\nexport function readResourceFile(file: string): string {\n const hit = cache.get(file);\n if (hit !== undefined) return hit;\n const url = new URL(`../resources/${file}`, import.meta.url);\n const text = readFileSync(fileURLToPath(url), 'utf8');\n cache.set(file, text);\n return text;\n}\n\n/** Look up a resource by its `graphein://…` URI. */\nexport function resourceByUri(uri: string): GrapheinResource | undefined {\n return RESOURCES.find((r) => r.uri === uri);\n}\n","/**\n * `createServer()` — builds the Graphein MCP server: the generate → validate →\n * repair → render → critique loop as four tools, the schema + guides as resources,\n * and a `create_chart` prompt that teaches the workflow. Split from the stdio\n * entry point (`server.ts`) so it can be driven over any transport — including the\n * in-memory transport in tests.\n */\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport {\n renderChartHandler,\n validateChartHandler,\n repairChartHandler,\n summarizeChartHandler,\n} from './handlers.js';\nimport { RESOURCES, readResourceFile } from './resources.js';\n\n/** Package version, surfaced as the MCP server version. */\nexport const VERSION = '0.3.0';\n\nconst SERVER_INSTRUCTIONS = `Graphein is an agent-first data-visualization library: you describe a chart as one JSON ChartSpec ({ type, data, encoding, ... }) and it renders. This server lets you build correct charts without prior knowledge of the API.\n\nWorkflow:\n1. Read the graphein://agent-guide resource (and graphein://schema for exact fields) if you are unsure of the API.\n2. Shape your data as a tidy array — one row per observation, one column per variable.\n3. Emit a ChartSpec and call render_chart: it validates, auto-repairs safe mistakes, renders a PNG, and returns a vision-free critique (the render report + lint warnings). Read the critique to verify the chart looks right.\n4. If a spec is invalid, render_chart (and validate_chart) return structured errors each with a JSON-Patch 'fix' — apply it (or call repair_chart) and retry instead of regenerating.\nUse summarize_chart for deterministic alt-text. Every type rasterizes headlessly, including kpi, table, matrix, slicers and dashboard (static canvas snapshots).`;\n\n/** A permissive object schema for a Graphein spec — validateSpec does the real checking. */\nconst specSchema = z\n .record(z.string(), z.unknown())\n .describe(\n 'A Graphein ChartSpec or DashboardSpec object, e.g. { \"type\": \"line\", \"data\": [...], \"encoding\": {...} }. See the graphein://schema and graphein://agent-guide resources.',\n );\n\n/**\n * Build a fully-configured Graphein MCP server. The caller connects it to a\n * transport (`server.connect(transport)`).\n */\nexport function createServer(): McpServer {\n const server = new McpServer(\n { name: 'graphein-mcp', version: VERSION },\n { instructions: SERVER_INSTRUCTIONS },\n );\n\n // --- Tools: the runtime loop -------------------------------------------------\n\n server.registerTool(\n 'render_chart',\n {\n title: 'Render a Graphein chart',\n description:\n 'The one-call loop: validate a ChartSpec, auto-repair safe mistakes, render it to a PNG, and return the image plus a vision-free critique (render report, lint warnings, repairs applied). If the spec cannot be made valid, returns structured errors with JSON-Patch fixes instead of an image. Every type rasterizes headlessly — kpi, table, matrix, slicers and dashboard render static canvas snapshots.',\n inputSchema: {\n spec: specSchema,\n width: z.number().int().positive().optional().describe('Logical width in CSS px (default 800).'),\n height: z.number().int().positive().optional().describe('Logical height in CSS px (default 500).'),\n dpr: z.number().positive().optional().describe('Device pixel ratio for crisp output (default 2).'),\n repair: z\n .boolean()\n .optional()\n .describe('Auto-apply safe repairs before rendering when the spec is invalid (default true).'),\n },\n },\n async (args) => renderChartHandler(args),\n );\n\n server.registerTool(\n 'validate_chart',\n {\n title: 'Validate a Graphein chart spec',\n description:\n 'Validate a ChartSpec without rendering. Returns structural errors (each with a JSON-Patch `fix` when unambiguous, plus \"did you mean\" suggestions) and best-practice lint warnings. Fast feedback before rendering.',\n inputSchema: { spec: specSchema },\n },\n async (args) => validateChartHandler(args),\n );\n\n server.registerTool(\n 'repair_chart',\n {\n title: 'Repair a Graphein chart spec',\n description:\n 'Apply every safe, unambiguous fix Graphein proposes (misspelled chart type or enum, a temporal field typed as a category, …) and return the corrected spec, the JSON Patch ops applied, and whether it is now valid. Turns a near-miss into a one-step correction.',\n inputSchema: { spec: specSchema },\n },\n async (args) => repairChartHandler(args),\n );\n\n server.registerTool(\n 'summarize_chart',\n {\n title: 'Summarize a Graphein chart',\n description:\n 'Return a deterministic, plain-English description of what the chart\\'s data shows (e.g. \"Users grew 46% over six months, peaking in June\"). Doubles as alt-text; needs no LLM.',\n inputSchema: { spec: specSchema },\n },\n async (args) => summarizeChartHandler(args),\n );\n\n // --- Resources: deliver the API knowledge at runtime -------------------------\n\n for (const r of RESOURCES) {\n server.registerResource(\n r.name,\n r.uri,\n { title: r.title, description: r.description, mimeType: r.mimeType },\n async (uri) => ({\n contents: [{ uri: uri.href, mimeType: r.mimeType, text: readResourceFile(r.file) }],\n }),\n );\n }\n\n // --- Prompt: teach the workflow ---------------------------------------------\n\n server.registerPrompt(\n 'create_chart',\n {\n title: 'Create a Graphein chart',\n description:\n 'Scaffold the workflow for building a validated Graphein chart from a goal (and optional data).',\n argsSchema: {\n goal: z.string().describe('What the chart should show, e.g. \"monthly active users over the last year\".'),\n data: z\n .string()\n .optional()\n .describe('Optional: the data as a JSON array, or a description of the columns available.'),\n },\n },\n ({ goal, data }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Build a Graphein chart for this goal:\\n\\n${goal}\\n${\n data ? `\\nData:\\n${data}\\n` : ''\n }\\nSteps:\\n1. If unsure of the API, read the graphein://agent-guide resource (and graphein://schema for exact fields).\\n2. Shape the data as a tidy array — one row per observation, one column per variable.\\n3. Choose a chart type and write a single ChartSpec ({ type, data, encoding, title }).\\n4. Call render_chart with the spec. Read the returned critique (render report + lint) to confirm it looks right.\\n5. If it reports errors, apply each error.fix patch (or call repair_chart) and render again — do not regenerate from scratch.`,\n },\n },\n ],\n }),\n );\n\n return server;\n}\n"],"mappings":"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","names":["text"]}