plotly.js
Version:
The open source javascript graphing library that powers plotly
253 lines (217 loc) • 12.6 kB
Markdown
# Type System Architecture
How TypeScript types are organized in plotly.js.
## Three layers
```
┌──────────────────────────────────────────────────────────────┐
│ Consumer surface (what `npm install plotly.js` exposes) │
│ lib/index.d.ts — wired via package.json#types │
│ `export type *` for generated schema types + explicit │
│ re-exports of hand-written types + `export as namespace │
│ Plotly` for global/namespace usage. │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Internal authoring surface │
│ src/types/index.d.ts — re-exports everything (internal) │
└──────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────────────┐ ┌────────────────────────────────┐
│ Hand-written types │ │ Generated types │
│ src/types/core/*.d.ts │ │ src/types/generated/... │
│ src/types/lib/*.d.ts │ │ │
│ │ │ schema.d.ts — common enums, │
│ │ │ traces, layout, animation, │
│ │ │ config, _internal namespace │
└──────────────────────────┘ └────────────────────────────────┘
```
`lib/index.d.ts` deliberately omits internal types (`FullLayout`,
`FullData`, `GraphDiv`, the `AttributeMap` machinery, etc.) so consumers
see a clean public API. Internal types live in `.internal.d.ts` files and
remain accessible to plotly.js's own code through the `src/types/`
re-exports.
The split:
- **Generated types** (`src/types/generated`) are the authoritative TypeScript representation of
the runtime schema. The schema itself is produced from Plotly's JS
attribute files (`src/.../attributes.js`), which remain the source of
truth: chain is **attribute files → `plot-schema.json` → generated
types**.
- **`src/types/generated/schema.d.ts`** contains:
- **Common enum aliases** discovered from the schema (Calendar, Dash,
AxisType, PatternShape, XRef, YRef, TransitionEasing, TraceType — plus
a deprecated `PlotType` alias for back-compat).
- **Shared sub-interfaces** extracted from repeated subtrees (Font,
FontArray, ColorBar, HoverLabel, Domain, Pattern, TickFormatStops,
LegendGroupTitle).
- **Per-trace data interfaces** for all trace types (BarData, ScatterData,
IndicatorData, etc.), plus the **`Data` discriminated union** over all
of them (`Partial<BarData> | Partial<ScatterData> | …`) — narrowed via
the `type` field.
- **Layout component interfaces** (LayoutAxis, Legend, Scene,
Annotation, Shape, Slider, UpdateMenu, etc.) and the Layout
interface itself.
- **Animation, frames, and config interfaces** (Transition,
AnimationFrameOpts, AnimationOpts, Frame, Edits) generated from
`schema.animation`, `schema.frames`, and `schema.config.edits`.
- **`_internal` namespace** wrapping types whose direct names would
mislead consumers (Marker is scatter-only, Line is the marker
outline) or are schema-internal helpers (AutoRangeOptions,
Lighting, ErrorY). Reachable as `_internal.Marker` etc.
but not at the top level.
Generated from `plot-schema.json` by `tasks/generate_schema_types.mjs`.
Run `npm run schema` to regenerate.
- **Hand-written types** (everything in `src/types/` besides the `generated/` directory)
cover everything the schema doesn't describe:
events, internal runtime state, public API function signatures,
utility types (Color, Datum, MarkerSymbol, ErrorBar), behavioral types
(ModeBarButton, Icon, etc.).
## Public vs. private (the underscore convention)
Plotly's runtime stores two kinds of state on graph elements:
- **Public** — user-supplied, what `Plotly.newPlot(gd, data, layout)` accepts
- **Private** (`_` prefix) — fully-resolved versions Plotly computes after
applying defaults, defined modules, and so on
This split is reflected in the types:
| User-facing | Internal | Where defined |
|---|---|---|
| `Layout` | `FullLayout` | `Layout` in `generated/schema.d.ts`; `FullLayout` in `core/layout.internal.d.ts` |
| `Data` (union over `type`) | `FullData` | `Data` in `generated/schema.d.ts` (union of schema `*Data` interfaces); `FullData = Data & FullDataInternals` in `core/data.internal.d.ts` |
| (n/a) | `GraphDiv` (the `gd` param) | `core/graph-div.internal.d.ts` — DOM element with `_fullLayout`, `_fullData`, `calcdata`, etc. |
`FullData` is the discriminated union of schema trace types intersected with
the internal `_`-prefixed fields. Internal code that narrows on `trace.type`
gets trace-specific fields plus the internal state in the same expression.
Internal types use index signatures (`[key: string]: any`) liberally to
allow incremental migration without blocking. As `_` properties get
discovered during JS→TS conversion, add them to `FullLayout` or
`FullDataInternals` (the file-local intersection target inside
`data.internal.d.ts`).
## Directory layout
```
src/types/
├── index.d.ts # main re-export hub (public + internal)
├── core/ # hand-written types for the core API
│ ├── api.d.ts # public API function signatures (newPlot, etc.)
│ ├── config.d.ts # Config, ToImgopts (Edits re-exported from generated)
│ ├── data.internal.d.ts # CalcData, FullData
│ ├── events.d.ts # PlotMouseEvent, PlotlyHTMLElement, etc.
│ ├── graph-div.internal.d.ts # GraphDiv, GraphContext
│ ├── layout.d.ts # AxisName, ModeBar behavioral types, Template
│ └── layout.internal.d.ts # FullLayout, LayoutSize, SubplotInfo
│
├── lib/ # primitives + the schema-extraction machinery
│ ├── common.d.ts # Color, Datum, TypedArray, MarkerSymbol, ...
│ └── attributes.d.ts # AttributeMap, AttrInfo (compile-time validation)
│
└── generated/ # machine-generated types
└── schema.d.ts # all traces + layout + shared types (from plot-schema.json)
```
### The `.internal.d.ts` convention
Files with the `.internal.d.ts` suffix contain types that are **not** part of the
public API (not re-exported in `lib/index.d.ts`). These are internal runtime types
used only within plotly.js itself — `FullLayout`, `FullData`, `GraphDiv`, etc.
If a file has no `.internal` suffix, all its exports are public.
## How schema type generation works
```
test/plot-schema.json (runtime schema: traces + layout + animation + config)
│
▼
[ tasks/generate_schema_types.mjs ]
│
│ 0. Discover common enum aliases via COMMON_TYPE_ANCHORS
│ (Calendar/Dash/AxisType/PatternShape/XRef/YRef/TransitionEasing,
│ plus TraceType derived from the trace-names list, plus a deprecated
│ `PlotType` alias)
│ 1. Fingerprint every container subtree across traces and layout
│ 2. Extract shared interfaces (Font, ColorBar, HoverLabel, etc.).
│ Inject Transition and AnimationFrameOpts as shared types
│ (animation has < MIN_OCCURRENCES sites otherwise)
│ 3. Emit per-trace interfaces referencing shared types
│ 4. Emit layout component interfaces (LayoutAxis, Legend, Scene, etc.)
│ and the Layout interface with subplot index signatures
│ 5. Emit AnimationOpts (from schema.animation), Frame (from
│ schema.frames with field overrides for the recursive data/layout
│ fields), and Edits (from schema.config.edits)
│ 6. Wrap names in INTERNAL_INTERFACES inside `export namespace _internal`
│ and rewrite outside-namespace references to `_internal.X`
│
▼
src/types/generated/schema.d.ts
│ // Common enum aliases
│ export type Calendar = 'chinese' | 'coptic' | ...;
│ export type TraceType = 'bar' | 'scatter' | ...;
│ /** @deprecated Renamed to TraceType. */
│ export type PlotType = TraceType;
│ // Shared interfaces (public)
│ export interface Font { ... }
│ export interface ColorBar { ... }
│ // Internal namespace
│ export namespace _internal {
│ export interface Marker { ... }
│ export interface Line { ... }
│ // ...
│ }
│ // Trace interfaces
│ export interface ScatterData { marker?: _internal.Marker; ... }
│ export interface BarData { ... }
│ // Discriminated union over all traces
│ export type Data = Partial<BarData> | Partial<ScatterData> | ...;
│ // Layout
│ export interface LayoutAxis { autorangeoptions?: _internal.AutoRangeOptions; ... }
│ export interface Layout { ... }
│ // Animation / config
│ export interface AnimationOpts { transition?: Transition; ... }
│ export interface Frame { data?: any[]; layout?: Partial<Layout>; ... }
│ export interface Edits { ... }
│
▼
src/types/index.d.ts re-exports all types (internal authoring index).
lib/index.d.ts uses `export type * from '.../generated/schema'` so every
public schema-derived type is automatically re-exported to consumers.
```
Regenerate with `npm run schema` (which rebuilds plot-schema.json
and then runs the schema type generator).
## What's hand-written and stays that way
The schema doesn't describe:
- **Events** — `PlotMouseEvent`, `PlotHoverEvent`, `LegendClickEvent`,
`PlotlyHTMLElement` and its `on()` overloads. These are runtime contracts.
- **Public API function signatures** — `Plotly.newPlot`, `relayout`,
`restyle`, etc. Live in `src/types/core/api.d.ts`.
- **Internal runtime state** — `FullLayout._modules`, `GraphDiv._fullData`,
`_calcInverseTransform`, etc. Live in `.internal.d.ts` files in `src/types/core/`.
- **Behavioral types** — `ModeBarButton`, `ModeBarDefaultButtons`, `Icon`,
`ButtonClickEvent`, `Template`. These describe runtime behavior patterns
not captured in the attribute schema.
- **Utility types** — `Color`, `Datum`, `TypedArray`, `MarkerSymbol`, etc.
Live in `src/types/lib/common.d.ts`. The generator references these types.
## Adding internal properties
When converting a JS file to TS and discovering an internal property like
`fullLayout._someFlag`, add it to the corresponding `Full*` interface:
```ts
// src/types/core/layout.internal.d.ts
export interface FullLayout extends Layout {
_modules?: any[];
_someFlag?: boolean; // add new ones here
[key: string]: any;
}
```
The `[key: string]: any` index signature is intentional — it absorbs
unknown internal properties so JS code can be migrated piecewise without
type errors.
## Coding conventions
### JSDoc on TypeScript functions
For exported functions in `.ts` source files, use a top-level JSDoc block
with `@param name - description` lines. Omit the type from `@param`
(the TS signature already has it) and use a hyphen separator:
```ts
/**
* make a regex for matching counter ids/names ie xaxis, xaxis2, xaxis10...
*
* @param head - the head of the pattern, eg 'x' matches 'x', 'x2', 'x10' etc.
* @param tail - a fixed piece after the id
*/
export function counter(head: string, tail: string = '') { ... }
```
## See also
- [SETUP.md](SETUP.md) — toolchain and npm scripts
- [CONVERTING_ATTRIBUTES.md](CONVERTING_ATTRIBUTES.md) — the conversion recipe
- [GENERATOR.md](GENERATOR.md) — internals of the type generators