/portaljs-add-chart
Add a chart (line, bar, area, pie, or scatter) to a dataset's showcase in a PortalJS portal. Installs recharts, writes a reusable Chart component, and renders it in the showcase Views section.
> /plugin marketplace add datopian/portaljs > /plugin install portaljs@datopian-portaljs
How it fires
How this command gets triggered: by you, by Claude, or both.
- Fires itselfClaude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/portaljs-add-chart
Context preview
What this command does when you run it.
Add a chart (line, bar, area, pie, or scatter) to a dataset's showcase in a PortalJS portal. Installs recharts, writes a reusable Chart component, and renders it in the showcase Views section.
Command definition
portaljs-add-chart.mddescription: Add a chart (line, bar, area, pie, or scatter) to a dataset's showcase in a PortalJS portal. Installs recharts, writes a reusable Chart component, and renders it in the showcase Views section.
allowed-tools: Read, Write, Edit, Bash
/portaljs-add-chart
Add a visualization to a dataset's **showcase** in a `portaljs-catalog` portal. Installs `recharts` (added directly — **not** `@portaljs/components`), writes a reusable client-side `Chart` component into the portal's `components/`, and renders a `<Chart />` into the **Views** section of the showcase route `pages/[owner]/[slug].tsx` for the chosen dataset.
Use this after the dataset is registered in `datasets.json` (e.g. via `/portaljs-add-dataset`). The chart reads the same `/public/data/<file>` the showcase's `<Table />` already uses — no data is duplicated.
Required input — ask, don't error
- **Dataset** — which dataset to chart, by **slug** (e.g. `co2-emissions`) or `slug`
within a namespace. It must already be an entry in `datasets.json`.
- **X axis column** — the column name for the category/X axis (e.g. `year`).
- **Y axis column(s)** — one or more numeric column names to plot (e.g. `population`
or `imports,exports`).
- **Portal directory** — path to the portal project (defaults to current directory).
- **Chart type** — `line` (default), `bar`, `area`, `pie`, or `scatter`.
**If the target dataset isn't specified, ask which one (by name/slug) — never dead-end with a missing-input error.**
Steps
1. Gather input from `$ARGUMENTS` (interview if thin)
Extract:
- `DATASET` — dataset slug (required)
- `X` — x-axis column name (required)
- `Y` — comma-separated y-axis column name(s) (required)
- `TYPE` — chart type, one of `line|bar|area|pie|scatter` (default: `line`)
- `PORTAL_DIR` — portal directory (default: `.`)
- `TITLE` — chart heading (default: derived from Y columns, e.g. "Population over Year")
If the dataset (or X/Y) is missing, **ask** and wait. When the user doesn't know the slug, read `PORTAL_DIR/datasets.json` and list the available datasets (`name` → `slug`) so they can pick one:
To add a chart I need:
1. Which dataset? (slug — your catalog has: <name (slug)>, …)
2. X axis column (e.g. year)
3. Y axis column(s), comma-separated (e.g. population or imports,exports)
4. Chart type [line] (line|bar|area|pie|scatter)
5. Portal directory (Enter for current directory)
Validate `TYPE` is one of the five supported values. If not, tell the user and ask them to pick line, bar, area, pie, or scatter.
2. Resolve the dataset from the manifest and its data source
- Read `PORTAL_DIR/datasets.json` and find the entry whose `slug` matches `DATASET`
(if multiple namespaces share the slug, ask which `namespace`). Capture its `namespace`, `file`, and `format`.
- If no entry matches, tell the user and list the available slugs (don't error out) — they
may have meant a different one or need to run `/portaljs-add-dataset` first.
- The data source is the bare file served statically: `DATA_URL = /data/<file>`. The
showcase route is `pages/[owner]/[slug].tsx`; the page rendered for this dataset is `/@<namespace>/<slug>`.
3. Validate the requested columns exist
- For CSV/TSV: read `PORTAL_DIR/public/data/<file>` first line for headers.
- For JSON: read the first object's keys from `PORTAL_DIR/public/data/<file>`.
- Confirm `X` and every `Y` column is present. If any is missing, tell the user which
column wasn't found and list the available headers so they can correct it.
- Warn (do not fail) if a `Y` column's first non-empty value is non-numeric:
Note: column "COL" looks non-numeric — chart values are coerced with Number(); non-numeric cells render as gaps.
4. Install recharts
cd PORTAL_DIR && npm install recharts@^2.15.0
Do **not** install `@portaljs/components`. If the install fails, tell the user (check network and `package.json`) and retry.
5. Write the reusable Chart component
Write `PORTAL_DIR/components/Chart.tsx` **only if it does not already exist** (idempotent — do not overwrite a customized component):
import React, { useEffect, useMemo, useState } from 'react'
import {
ResponsiveContainer,
LineChart, Line,
BarChart, Bar,
AreaChart, Area,
PieChart, Pie, Cell,
ScatterChart, Scatter,
XAxis, YAxis, ZAxis,
CartesianGrid, Tooltip, Legend,
} from 'recharts'
import { parseCsv } from './ui/parseCsv'
type Row = Record<string, string | number>
export interface ChartProps {
/** CSV file URL under /public, e.g. "/data/file.csv" */
url?: string
/** Pre-parsed rows (e.g. from an imported JSON array) */
data?: Row[]
type?: 'line' | 'bar' | 'area' | 'pie' | 'scatter'
/** X-axis / category column key */
x: string
/** One or more numeric column keys to plot */
y: string | string[]
height?: number
}
const PALETTE = ['#2563eb', '#16a34a', '#dc2626', '#d97706', '#7c3aed', '#0891b2', '#db2777', '#65a30d']
export function Chart({ url = '', data: initialData = [], type = 'line', x, y, height = 320 }: ChartProps) {
const yKeys = useMemo(() => (Array.isArray(y) ? y : [y]), [y])
const [raw, setRaw] = useState<Row[]>(initialData)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (!url) {
setRaw(initialData)
return
}
setIsLoading(true)
setError(null)
fetch(url)
.then((r) => {
if (!r.ok) throw new Error(`HTTP ${r.status} — ${r.statusText}`)
return r.text()
})
.then((text) => setRaw(parseCsv(text).rows))
.catch((err: Error) => setError(err.message))
.finally(() => setIsLoading(false))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url, JSON.stringify(initialData)])
// Coerce y values to numbers; leave x untouched (categorical or numeric)
const rows = useMemo(
() =>
raw.map((row) => {
const out: Row = { [x]: roRead more
description: Add a chart (line, bar, area, pie, or scatter) to a dataset's showcase in a PortalJS portal. Installs recharts, writes a reusable Chart component, and renders it in the showcase Views section. allowed-tools: Read, Write, Edit, Bash
/portaljs-add-chart
Add a visualization to a dataset's **showcase** in a `portaljs-catalog` portal. Installs `recharts` (added directly — **not** `@portaljs/components`), writes a reusable client-side `Chart` component into the portal's `components/`, and renders a `<Chart />` into the **Views** section of the showcase route `pages/[owner]/[slug].tsx` for the chosen dataset.
Use this after the dataset is registered in `datasets.json` (e.g. via `/portaljs-add-dataset`). The chart reads the same `/public/data/<file>` the showcase's `<Table />` already uses — no data is duplicated.
Required input — ask, don't error
- **Dataset** — which dataset to chart, by **slug** (e.g. `co2-emissions`) or `slug`
within a namespace. It must already be an entry in `datasets.json`.
- **X axis column** — the column name for the category/X axis (e.g. `year`).
- **Y axis column(s)** — one or more numeric column names to plot (e.g. `population`
or `imports,exports`).
- **Portal directory** — path to the portal project (defaults to current directory).
- **Chart type** — `line` (default), `bar`, `area`, `pie`, or `scatter`.
**If the target dataset isn't specified, ask which one (by name/slug) — never dead-end with a missing-input error.**
Steps
1. Gather input from `$ARGUMENTS` (interview if thin)
Extract:
- `DATASET` — dataset slug (required)
- `X` — x-axis column name (required)
- `Y` — comma-separated y-axis column name(s) (required)
- `TYPE` — chart type, one of `line|bar|area|pie|scatter` (default: `line`)
- `PORTAL_DIR` — portal directory (default: `.`)
- `TITLE` — chart heading (default: derived from Y columns, e.g. "Population over Year")
If the dataset (or X/Y) is missing, **ask** and wait. When the user doesn't know the slug, read `PORTAL_DIR/datasets.json` and list the available datasets (`name` → `slug`) so they can pick one:
To add a chart I need: 1. Which dataset? (slug — your catalog has: <name (slug)>, …) 2. X axis column (e.g. year) 3. Y axis column(s), comma-separated (e.g. population or imports,exports) 4. Chart type [line] (line|bar|area|pie|scatter) 5. Portal directory (Enter for current directory)
Validate `TYPE` is one of the five supported values. If not, tell the user and ask them to pick line, bar, area, pie, or scatter.
2. Resolve the dataset from the manifest and its data source
- Read `PORTAL_DIR/datasets.json` and find the entry whose `slug` matches `DATASET`
(if multiple namespaces share the slug, ask which `namespace`). Capture its `namespace`, `file`, and `format`.
- If no entry matches, tell the user and list the available slugs (don't error out) — they
may have meant a different one or need to run `/portaljs-add-dataset` first.
- The data source is the bare file served statically: `DATA_URL = /data/<file>`. The
showcase route is `pages/[owner]/[slug].tsx`; the page rendered for this dataset is `/@<namespace>/<slug>`.
3. Validate the requested columns exist
- For CSV/TSV: read `PORTAL_DIR/public/data/<file>` first line for headers.
- For JSON: read the first object's keys from `PORTAL_DIR/public/data/<file>`.
- Confirm `X` and every `Y` column is present. If any is missing, tell the user which
column wasn't found and list the available headers so they can correct it.
- Warn (do not fail) if a `Y` column's first non-empty value is non-numeric:
Note: column "COL" looks non-numeric — chart values are coerced with Number(); non-numeric cells render as gaps.
4. Install recharts
cd PORTAL_DIR && npm install recharts@^2.15.0
Do **not** install `@portaljs/components`. If the install fails, tell the user (check network and `package.json`) and retry.
5. Write the reusable Chart component
Write `PORTAL_DIR/components/Chart.tsx` **only if it does not already exist** (idempotent — do not overwrite a customized component):
import React, { useEffect, useMemo, useState } from 'react'
import {
ResponsiveContainer,
LineChart, Line,
BarChart, Bar,
AreaChart, Area,
PieChart, Pie, Cell,
ScatterChart, Scatter,
XAxis, YAxis, ZAxis,
CartesianGrid, Tooltip, Legend,
} from 'recharts'
import { parseCsv } from './ui/parseCsv'
type Row = Record<string, string | number>
export interface ChartProps {
/** CSV file URL under /public, e.g. "/data/file.csv" */
url?: string
/** Pre-parsed rows (e.g. from an imported JSON array) */
data?: Row[]
type?: 'line' | 'bar' | 'area' | 'pie' | 'scatter'
/** X-axis / category column key */
x: string
/** One or more numeric column keys to plot */
y: string | string[]
height?: number
}
const PALETTE = ['#2563eb', '#16a34a', '#dc2626', '#d97706', '#7c3aed', '#0891b2', '#db2777', '#65a30d']
export function Chart({ url = '', data: initialData = [], type = 'line', x, y, height = 320 }: ChartProps) {
const yKeys = useMemo(() => (Array.isArray(y) ? y : [y]), [y])
const [raw, setRaw] = useState<Row[]>(initialData)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (!url) {
setRaw(initialData)
return
}
setIsLoading(true)
setError(null)
fetch(url)
.then((r) => {
if (!r.ok) throw new Error(`HTTP ${r.status} — ${r.statusText}`)
return r.text()
})
.then((text) => setRaw(parseCsv(text).rows))
.catch((err: Error) => setError(err.message))
.finally(() => setIsLoading(false))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url, JSON.stringify(initialData)])
// Coerce y values to numbers; leave x untouched (categorical or numeric)
const rows = useMemo(
() =>
raw.map((row) => {
const out: Row = { [x]: ro🌀 AI-native framework for building data portals. Scaffold a full portal from a brief and load datasets in minutes with agentic skills — any backend (CKAN, GitHub, Frictionless).
Repo: datopian/portaljs
Other commands on portaljs.
- /add-chart
Renamed → /portaljs-add-chart. This alias will be removed next minor release.
Open command - /add-dataset
Renamed → /portaljs-add-dataset. This alias will be removed next minor release.
Open command - /add-map
Renamed → /portaljs-add-map. This alias will be removed next minor release.
Open command - /add-resource
Renamed → /portaljs-add-resource. This alias will be removed next minor release.
Open command - /arcgis-to-portaljs
Migrate a whole ArcGIS Hub site (opendata.arcgis.com or a Hub Premium custom domain) into a PortalJS Arc portal end-to-end. Harvests the Hub /data.json (DCAT-US) inventory, exports every FeatureService layer through the ArcGIS REST query API (resultOffset paging), converts each
Open command - /architect
Renamed → /portaljs-architect. This alias will be removed next minor release.
Open command

