# Chart views

Each view, in a fixed order, with whether the dataset allows it and, when not, why. A line needs an ordered axis and two points; a donut needs parts of one whole, one series and no negatives; stacking needs more than one series; bars and areas start at zero, so not below it; a heatmap needs series for its rows. The table is always allowed. Pass the words from `useAllowedViews`, or leave them out for English.

- Registry name: `chart-views` (`@candor/chart-views`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/chart-views

## Install

```bash
npx candor-ui add chart-views
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/chart-views`.
It brings in: locale, tokens.

## Example

```tsx
"use client"

import * as React from "react"
import { BarChart } from "@/components/candor/bar-chart"
import type { ChartDatum } from "@/components/candor/chart-focus"
import { useAllowedViews } from "@/components/candor/chart-views"
import type { Series } from "@/lib/candor/data"

const repositories = ["api", "web", "billing", "workers"]
const runs: Series[] = [
  { id: "passed", label: "Passed", values: [212, 148, 96, 74] },
  { id: "failed", label: "Failed", values: [3, 0, 5, null] },
]

export default function Example() {
  const [filter, setFilter] = React.useState<ChartDatum | null>(null)
  // Repositories have no order of their own, and pass and fail counts aren't parts of one whole.
  const views = useAllowedViews({ categories: repositories.length, series: runs.length, ordered: false, partsOfWhole: false, negative: false })
  return (
    <div className="flex max-w-xl flex-col gap-3">
      <BarChart
        title="Tests by repository"
        categories={repositories}
        series={runs}
        stacked
        onSelect={setFilter}
        selected={filter}
        selectLabel={(d) => `Show the ${d.series.toLowerCase()} tests in ${d.category}`}
      />
      <p className="text-sm text-muted-foreground">{filter ? `Filtered to ${filter.series.toLowerCase()} tests in ${filter.category}.` : "Press a bar to filter."}</p>
      <ul className="flex flex-col gap-1 text-sm">
        {views.map((v) => (
          <li key={v.view}>
            <span className="font-medium">{v.label}</span>: {v.allowed ? "allowed" : v.reason}
          </li>
        ))}
      </ul>
    </div>
  )
}
```

## Functions

### allowedViews

```ts
allowedViews(shape: DatasetShape, words: (key: keyof ViewStrings) => string = (key) => chartViewsStrings[key])
```

### useAllowedViews

```ts
useAllowedViews(shape: DatasetShape)
```

`allowedViews` in LocaleProvider's words.

## Strings (`chartViews`)

The fixed words, English by default. Override any of them under `chartViews` in `<LocaleProvider strings={{ chartViews: { … } }}>`; the table is exported as `chartViewsStrings`.

- `bar`: Bars
- `stacked-bar`: Stacked bars
- `line`: Line
- `area`: Area
- `donut`: Donut
- `heatmap`: Heatmap
- `table`: Table
- `nothing`: There is nothing to draw.
- `barsNegative`: These bars start at zero and can't show values below it.
- `stackNeedsSeries`: Stacking needs more than one series.
- `lineNeedsOrder`: A line needs an ordered axis, such as time.
- `lineNeedsPoints`: A line needs at least two points.
- `areaNegative`: An area fills from zero and can't show values below it.
- `donutNeedsWhole`: A donut needs parts of one whole.
- `donutNegative`: A donut can't show values below zero.
- `donutOneSeries`: A donut shows one series.
- `heatmapNeedsSeries`: A heatmap needs more than one series, as its rows.

## Types

### ChartView

The views a dataset can be drawn in.

```ts
"bar" | "stacked-bar" | "line" | "area" | "donut" | "heatmap" | "table"
```

### DatasetShape

What a dataset is like, which decides the views that tell the truth about it.

```ts
{
  /** How many categories (x positions, columns) there are. */
  categories: number;
  /** How many series there are. */
  series: number;
  /** Whether the categories have an order of their own, such as time; a line between unordered ones means nothing. */
  ordered: boolean;
  /** Whether the values are parts of one whole, so a share of it means something. */
  partsOfWhole: boolean;
  /** Whether any value is below zero. */
  negative: boolean;
}
```

### ViewAllowance

One view and whether the dataset allows it; when it doesn't, `reason` says why.

```ts
{ view: ChartView; label: string; allowed: boolean; reason?: string | undefined }
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
