Skip to content

/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.

From plugin
portaljs
2.4k25 skills25 commands
> /plugin marketplace add datopian/portaljs
> /plugin install portaljs@datopian-portaljs

Command definition

portaljs-add-chart.md
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 { t } from '../lib/i18n'
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) => {
Read more
@datopian
Ships withportaljs

🌀 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).

Stars on GitHub

Momentum

stars in total
2.4k
Star history of datopian/portaljs on GitHub, a chart by star-history.com

Other commands on portaljs.

add-chart
Auto-invoked

add-chart

Command

Renamed → /portaljs-add-chart. This alias will be removed next minor release.

@datopian@datopianInstall Command
add-dataset
Auto-invoked

add-dataset

Command

Renamed → /portaljs-add-dataset. This alias will be removed next minor release.

@datopian@datopianInstall Command
add-map
Auto-invoked

add-map

Command

Renamed → /portaljs-add-map. This alias will be removed next minor release.

@datopian@datopianInstall Command
add-resource
Auto-invoked

add-resource

Command

Renamed → /portaljs-add-resource. This alias will be removed next minor release.

@datopian@datopianInstall Command
arcgis-to-portaljs
Auto-invoked

arcgis-to-portaljs

Command

Migrate a whole ArcGIS Hub site (opendata.arcgis.com or a Hub Premium custom domain) into a PortalJS Arc portal end-to-end.…

@datopian@datopianInstall Command
architect
Auto-invoked

architect

Command

Renamed → /portaljs-architect. This alias will be removed next minor release.

@datopian@datopianInstall Command

More commands from other plugins.

build
Auto-invoked

build

Command

Implement tasks incrementally — build, test, verify, commit. Add "auto" to run the whole plan in one approved pass.

@addyosmani@addyosmaniInstall Command