1887 lines
79 KiB
Markdown
1887 lines
79 KiB
Markdown
# PPTD Format Specification
|
||
|
||
PPTD (PPT-DSL) is a YAML abstraction layer for PowerPoint presentations, used to describe, generate, and edit slides in an AI-friendly way, with lossless bidirectional conversion to and from PPTX
|
||
|
||
---
|
||
|
||
## Conventions in This Document
|
||
- Uses **TS interfaces** to describe structures, with **field tables** and **minimal YAML examples** to aid understanding
|
||
- **Default values** are annotated in TS end-of-line comments as `// default: X`. X may be a literal (`1` / `"top"` / `[0, 0]`) or a descriptive phrase (`not applied` / `not shown` / `falls back along the inheritance chain` / `auto-adapts to chart size`, etc.)
|
||
- **Constraints** are annotated in TS end-of-line comments or below the TS block as `// constraint: ...`, uniformly using interval or inequality notation (`[0, 1]` / `> 0`) or textual descriptions
|
||
---
|
||
|
||
## 1. Global Conventions
|
||
|
||
### Syntax
|
||
- Uses **YAML 1.2** syntax
|
||
- For special characters such as `:`, `#`, `{`, `}`, the value must be wrapped in quotes or written with a block scalar instead
|
||
- For fields with many special characters such as `content.text`, a block scalar (`|`) should be used as its own block, to prevent content like `style="..."` from being parsed incorrectly
|
||
|
||
### Coordinate System and Units
|
||
- All geometry and size units are **px**; the origin `(0, 0)` is the top-left corner of the page
|
||
- Recommended sizes: 16:9 → `[960, 540]`; 4:3 → `[720, 540]`
|
||
- This specification defines 1px = 1pt (i.e., `fontSize: 18` is 18pt in PPTX)
|
||
- Element stacking order is determined by the order of the `Page.elements` array; the later an element, the higher its layer
|
||
|
||
### Style Priority and Default Values
|
||
|
||
For property values that conflict, the first source with a value is found by searching the following priorities from top to bottom; when none of the levels is set, fall back to the default values at the end of that section.
|
||
|
||
> The following rule applies to all subsections of this section: `lineHeight` (a multiple) and `lineHeightPx` (fixed px) are mutually exclusive; when both are set, `lineHeightPx` takes precedence.
|
||
|
||
#### 1. Text Styles Inside a Text Box
|
||
|
||
**Priority chain:**
|
||
1. Rich-text semantic tags such as `<u>`, `<sup>`, `<strong>` in [Text.content.text](#textcontent)
|
||
2. Inline properties set in `<span style="...">`
|
||
3. Paragraph properties set in `<p style="...">`
|
||
4. **Style fields set directly on [Text.content](#textcontent)** (distinct from the theme style referenced by `style`; including `color`, `fontSize`, `fontFamily`, `bold`, `italic`, `backgroundColor`, `lineHeight`, `lineHeightPx`, `letterSpacing`, `marginTop`)
|
||
5. The [TextStyleConfig](#textstyleconfig) theme style referenced by [Text.content.style](#textcontent)
|
||
6. Default values:
|
||
|
||
| Property | Default value |
|
||
|---|---|
|
||
| color | `#000000` |
|
||
| backgroundColor | Not applied |
|
||
| fontSize | `18` |
|
||
| fontFamily | `"MiSans"` |
|
||
| bold | `false` |
|
||
| italic | `false` |
|
||
| lineHeight | `1` |
|
||
| lineHeightPx | Not applied |
|
||
| letterSpacing | `0` |
|
||
| marginTop | `0` |
|
||
|
||
#### 2. Table Cell Styles
|
||
|
||
**Priority chain:**
|
||
1. Rich-text semantic tags such as `<u>`, `<sup>`, `<strong>` in [Cell.text](#cell)
|
||
2. Inline properties set in `<span style="...">`
|
||
3. Paragraph properties set in `<p style="...">`
|
||
4. [Cell](#cell) inline fields
|
||
5. The [TextStyleConfig](#textstyleconfig) referenced by [Cell.textStyle](#cell) (**applies only to text fields**; does not include `fill` / `border` / `align`)
|
||
6. Position-category styles of [TableStyleConfig](#tablestyleconfig)
|
||
- On row vs column conflicts, [TableStyleConfig.rowOverColumn](#tablestyleconfig) decides the winner; default `true` = row wins
|
||
- Row categories: `TableStyleConfig.firstRowStyle` / `TableStyleConfig.lastRowStyle`
|
||
- Column categories: `TableStyleConfig.firstColumnStyle` / `TableStyleConfig.lastColumnStyle`
|
||
7. [TableStyleConfig.bodyStyles](#tablestyleconfig): applies to data rows other than the first and last rows, cycled by data-row index
|
||
8. [TableStyleConfig.cellStyle](#tablestyleconfig): the baseline cell style for the whole table
|
||
9. Default values
|
||
|
||
| Property | Default value |
|
||
|---|---|
|
||
| color | `#000000` |
|
||
| backgroundColor | Not applied |
|
||
| fontSize | Auto-adapts based on cell height |
|
||
| fontFamily | `"MiSans"` |
|
||
| bold | `false` |
|
||
| italic | `false` |
|
||
| lineHeight | `1` |
|
||
| lineHeightPx | Not applied |
|
||
| letterSpacing | `0` |
|
||
| marginTop | `0` |
|
||
| fill | Not applied (transparent) |
|
||
| border | `{style: solid, width: 1, color: "#000000"}` |
|
||
| align | `["center", "middle"]` |
|
||
|
||
#### 3. Chart Styles
|
||
|
||
Charts involve multiple kinds of styles (series body colors, fonts, data labels, axis/legend visibility, etc.), each with its own independent priority chain, explained below.
|
||
|
||
**3.1 Series body color priority chain:**
|
||
1. A series' explicit `fill` / `lineColor` / `areaColor` (field names differ per type; see [Color Mechanism](#52-color-mechanism))
|
||
2. The same-named field for the corresponding type in [Chart.seriesDefaults](#seriesdefaults)
|
||
3. The [Theme.colors](#theme) theme color cycle (colors are picked in the order the series appear in the array)
|
||
|
||
> [scatter](#scatter) is an exception: marker color resolves as `marker.fill > series.fill > theme color cycle`; marker.border likewise takes precedence over series.border.
|
||
>
|
||
> For each type's specific color fields, derivation rules, and role mappings, see [§5.2 Color Mechanism](#52-color-mechanism).
|
||
|
||
**3.2 Font priority chain:**
|
||
1. Sub-component `fontFamily` ([TitleConfig](#titleconfig) / [LegendConfig](#legendconfig) / [DataLabelConfig](#datalabelconfig) / [AxisConfig.label](#axisconfig) / [SpokeAxisConfig.label](#spokeaxisconfig))
|
||
2. [Chart.fontFamily](#chart)
|
||
3. Theme default ([Theme](#theme) or the PPTX master font)
|
||
|
||
**3.3 dataLabels priority chain:**
|
||
1. `series[i].dataLabels`
|
||
2. [Chart.dataLabels](#chart) (global default)
|
||
3. Not shown (equivalent to `show: false`)
|
||
|
||
> Sub-fields follow a **one-level shallow merge**: `series.dataLabels` only overrides the sub-fields it explicitly provides; unprovided ones fall back from [Chart.dataLabels](#chart); if neither provides them, the per-type default applies (see [dataLabels.content value quick reference](#55-datalabelscontent-value-quick-reference)).
|
||
|
||
**3.4 `seriesDefaults` merge rules:**
|
||
|
||
[Chart.seriesDefaults](#seriesdefaults)`[type]` provides common defaults for all series of that type, merged with each series via a **one-level deep merge**:
|
||
- **Scalar fields** (string / number / boolean): the series' explicit value overrides defaults
|
||
- **Object fields** (`marker` / `dataLabels` / `border` / `upBars` / `downBars` / `totalBars` / gradient `fill` objects, etc.): recursive one-level shallow merge — same-named fields of defaults and series are spread respectively, with sub-fields overridden by the nearest source
|
||
- **Array fields** (`fill: []` / `colorScheme: []`): the series replaces defaults as a whole, with no element-level merging
|
||
- `type` and `encode` are not allowed inside seriesDefaults
|
||
- Only multi-series types support seriesDefaults: `bar / line / area / scatter / bubble / candlestick / radar`
|
||
|
||
Counterintuitive example:
|
||
```yaml
|
||
seriesDefaults:
|
||
bar: {marker: {shape: circle, size: 8}}
|
||
series:
|
||
- type: bar
|
||
marker: {size: 12} # after merge: {shape: circle, size: 12}, not {size: 12}
|
||
```
|
||
|
||
**3.5 `boolean | Config` field convention:**
|
||
|
||
Fields of the form `boolean | XxxConfig` (`marker` / `legend` / `AxisConfig.label / axisLine / gridLine` / `SpokeAxisConfig.label / axisLine / gridLine` / `colorbar`) uniformly follow:
|
||
- `false` = off
|
||
- `true` = on with default configuration
|
||
- Object `{...}` = on + custom configuration
|
||
|
||
> **The only exception**: [scatter.marker](#scatter) cannot be `false` (a scatter plot without a marker has nothing to render).
|
||
|
||
---
|
||
|
||
### Multi-File Structure
|
||
A PPTD project consists of a main entry file and individual page files:
|
||
```
|
||
project/
|
||
slides_name.pptd # main entry (size/theme/title + page reference list)
|
||
media/ # media resources such as images and videos
|
||
pages/ # page file directory
|
||
1_cover.page # one .page file per page
|
||
2_intro.page
|
||
```
|
||
**Path rules:**
|
||
1. **Fully self-contained**: all referenced files must be located inside the folder containing the `.pptd` file; **referencing files outside the directory is not allowed**
|
||
2. **Only relative paths are supported** (relative to the directory containing the `.pptd` file):
|
||
- The `pages` list in `.pptd`: `pages/1_cover.page`
|
||
- Image paths in `.page`: `media/image1.jpg`
|
||
3. **Media supports URLs**: `Image.src`, and the [ImageFill](#fill).src of `background` / `fill`, may be `https://...` (only jpg/jpeg/png/gif supported)
|
||
|
||
**Main entry is required:** everything must be loaded through the `.pptd` main entry file; a `.page` cannot be passed alone to the `convert`/`check` commands
|
||
|
||
---
|
||
|
||
## 2. Shared Types
|
||
The following types are reused in multiple places and are defined together up front. Element sections reference them by type name without repeating the expansion
|
||
|
||
### Color
|
||
```ts
|
||
type Color = string;
|
||
```
|
||
> Supports opaque **HEX6** (`#RRGGBB`), alpha **HEX8** (`#RRGGBBAA`), and [Theme.colors](#theme) theme color references (e.g. `$primary`)
|
||
|
||
### FontFamily
|
||
```ts
|
||
type FontFamily = string | { latin: string; ea: string };
|
||
```
|
||
| Form | Example | Description |
|
||
|------|------|------|
|
||
| String | `"MiSans"` | Chinese and English use the same font uniformly |
|
||
| Object | `{latin: "Arial", ea: "MiSans"}` | Explicitly specify Latin (latin) and East Asian (ea) fonts separately |
|
||
|
||
See [fonts.md](./fonts.md) for the list of available fonts
|
||
|
||
### Alignment
|
||
```ts
|
||
type HorizontalAlign = "left" | "center" | "right" | "justify" | "distributed";
|
||
type VerticalAlign = "top" | "middle" | "bottom";
|
||
type Alignment = [HorizontalAlign, VerticalAlign];
|
||
```
|
||
| Value | Description |
|
||
|----|------|
|
||
| `left` / `center` / `right` | Horizontal left / center / right alignment |
|
||
| `justify` | Justified (last line not stretched) |
|
||
| `distributed` | Distributed (last line stretched) |
|
||
| `top` / `middle` / `bottom` | Vertical top / middle / bottom alignment |
|
||
|
||
### LineStyle
|
||
```ts
|
||
type LineStyle = "solid" | "dash" | "dot";
|
||
```
|
||
|
||
### Border
|
||
```ts
|
||
interface Border {
|
||
style?: LineStyle; // default: "solid"
|
||
width?: number; // default: 1
|
||
color?: Color; // default: "#000000"
|
||
}
|
||
```
|
||
|
||
#### BorderSpec
|
||
[Cell](#cell) and [CellStyle](#cellstyle) support an array form of `Border` to set the four side borders separately
|
||
|
||
```ts
|
||
type BorderSpec = null
|
||
| Border
|
||
| [Border | null, Border | null]
|
||
| [Border | null, Border | null, Border | null, Border | null];
|
||
```
|
||
|
||
| Form | Meaning |
|
||
|------|------|
|
||
| `null` | Explicit clear: no border on any of the four sides (used to override a border set higher up the inheritance chain)|
|
||
| `Border` | Same on all four sides |
|
||
| Two-element array `[Border\|null, Border\|null]` | `[top-bottom, left-right]` |
|
||
| Four-element array `[Border\|null, Border\|null, Border\|null, Border\|null]` | `[top, right, bottom, left]` (clockwise) |
|
||
|
||
> A `null` inside the array means no border at the corresponding position; a top-level `null` clears everything.
|
||
|
||
### Shadow
|
||
|
||
```ts
|
||
interface Shadow {
|
||
blur: number; // blur radius
|
||
color: Color;
|
||
offset?: [number, number]; // default: [0, 0]; [x, y] offset
|
||
}
|
||
```
|
||
|
||
### ColorStop
|
||
|
||
```ts
|
||
interface ColorStop {
|
||
position: number; // constraint: [0, 1]
|
||
color: Color;
|
||
}
|
||
```
|
||
|
||
### ImageFit / ImageCrop
|
||
|
||
```ts
|
||
interface ImageFit {
|
||
mode: "fill" | "contain" | "cover";
|
||
}
|
||
|
||
interface ImageCrop {
|
||
left?: number;
|
||
top?: number;
|
||
right?: number;
|
||
bottom?: number;
|
||
}
|
||
```
|
||
|
||
> **Constraint:** the four fields of `ImageCrop` are analogous, default 0. A positive value crops inward from the corresponding edge proportionally (inset); a negative value expands outward toward the corresponding edge proportionally and pads with transparent pixels (outset). Must ensure `left + right < 1` and `top + bottom < 1`, otherwise the source rectangle degenerates.
|
||
|
||
| ImageFit.mode | Description |
|
||
|---|---|
|
||
| `cover` | Fills the container, keeps aspect ratio, may crop |
|
||
| `contain` | Shows the image completely, keeps aspect ratio, may leave blank space |
|
||
| `fill` | Stretches to fill, may distort |
|
||
|
||
### Fill
|
||
|
||
```ts
|
||
type Fill = SolidFill | GradientFill | ImageFill;
|
||
|
||
interface SolidFill {
|
||
type: "solid";
|
||
color: Color;
|
||
}
|
||
|
||
interface GradientFill {
|
||
type: "gradient";
|
||
gradientType: "linear" | "radial";
|
||
stops: ColorStop[]; // constraint: at least 2
|
||
angle?: number; // default: 0; only effective for linear
|
||
}
|
||
|
||
interface ImageFill {
|
||
type: "image";
|
||
src: string; // URL or relative path
|
||
fit?: ImageFit; // default: {mode: "cover"}
|
||
crop?: ImageCrop; // always applied; see the rendering order with fit below
|
||
opacity?: number; // default: 1; constraint: [0, 1]
|
||
}
|
||
```
|
||
|
||
> `GradientFill.angle` takes values in `[0, 360)`; `0` means left to right, increasing clockwise. Examples: `90` = top→bottom, `180` = right→left.
|
||
|
||
> **ImageFill rendering order:** `crop` (adjust the source rectangle proportionally: positive values crop inward, negative values expand outward and pad with transparent pixels) → `fit` (adapt to the fill container per mode). The specific semantics of each `fit.mode` value are consistent with the "rendering logic" discussion in the [Image](#image-image) section.
|
||
|
||
**Examples:**
|
||
|
||
```yaml
|
||
# Solid
|
||
fill:
|
||
type: solid
|
||
color: "$primary"
|
||
|
||
# Gradient
|
||
fill:
|
||
type: gradient
|
||
gradientType: linear
|
||
angle: 90
|
||
stops:
|
||
- {position: 0, color: "$primary"}
|
||
- {position: 1, color: "$accent"}
|
||
|
||
# Image
|
||
fill:
|
||
type: image
|
||
src: "media/bg.jpg"
|
||
fit: {mode: cover}
|
||
opacity: 0.9
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Main Entry File (.pptd)
|
||
|
||
### Presentation
|
||
|
||
```ts
|
||
interface Presentation {
|
||
version: "v2"; // required, fixed to "v2" (version identifier)
|
||
title?: string; // default: no title
|
||
size: [number, number]; // [width, height]; 16:9 recommended [960, 540], 4:3 recommended [720, 540]
|
||
theme?: Theme;
|
||
pages: string[]; // list of relative paths to page files, e.g. "pages/cover.page"
|
||
}
|
||
```
|
||
|
||
**Example:**
|
||
|
||
```yaml
|
||
version: v2
|
||
title: Annual Work Summary
|
||
size: [960, 540]
|
||
theme:
|
||
colors:
|
||
primary: "#2563EB"
|
||
accent: "#F59E0B"
|
||
text: "#1F2937"
|
||
textStyles:
|
||
title:
|
||
fontSize: 40
|
||
color: "$primary"
|
||
body:
|
||
fontSize: 18
|
||
color: "$text"
|
||
lineHeight: 1.6
|
||
tableStyles:
|
||
default:
|
||
firstRowStyle:
|
||
fill: {type: solid, color: "$primary"}
|
||
color: "#ffffff"
|
||
bold: true
|
||
bodyStyles:
|
||
- {fill: {type: solid, color: "#f8fafc"}}
|
||
- {fill: {type: solid, color: "#ffffff"}}
|
||
pages:
|
||
- pages/1_cover.page
|
||
- pages/2_content.page
|
||
```
|
||
|
||
### Theme
|
||
|
||
The theme centrally manages colors, text styles, and table styles. Use `$<key>` in relevant fields to reference the theme:
|
||
|
||
| Theme type | Referencing field | Example |
|
||
|---|---|---|
|
||
| `colors` | Any [Color](#color) field | `$primary` |
|
||
| `textStyles` | [TextContent.style](#textcontent) / [Cell.textStyle](#cell) | `$title` |
|
||
| `tableStyles` | [Table.style](#table-table) | `$default` |
|
||
|
||
```ts
|
||
interface Theme {
|
||
colors?: Record<string, Color>;
|
||
textStyles?: Record<string, TextStyleConfig>;
|
||
tableStyles?: Record<string, TableStyleConfig>;
|
||
}
|
||
```
|
||
|
||
#### TextStyleConfig
|
||
|
||
```ts
|
||
interface TextStyleConfig {
|
||
color?: Color;
|
||
fontSize?: number;
|
||
fontFamily?: FontFamily;
|
||
bold?: boolean; // bold
|
||
italic?: boolean; // italic
|
||
backgroundColor?: Color; // text background color (e.g., text highlight)
|
||
lineHeight?: number; // line-height multiple
|
||
lineHeightPx?: number; // fixed line height (px); when it conflicts with lineHeight, lineHeightPx prevails
|
||
letterSpacing?: number;
|
||
marginTop?: number;
|
||
}
|
||
```
|
||
|
||
> Unset fields fall back along the inheritance chain (see [Style Priority and Default Values](#style-priority-and-default-values) for details)
|
||
|
||
#### CellStyle
|
||
|
||
```ts
|
||
interface CellStyle extends TextStyleConfig {
|
||
// —— Inherits all properties of TextStyleConfig ——
|
||
// color / fontSize / fontFamily / bold / italic / backgroundColor / lineHeight / lineHeightPx / letterSpacing / marginTop
|
||
|
||
// —— CellStyle-specific ——
|
||
fill?: Fill; // background fill
|
||
border?: BorderSpec; // border
|
||
align?: Alignment; // text alignment
|
||
}
|
||
```
|
||
|
||
> Unset fields fall back along the inheritance chain (see [Style Priority and Default Values](#style-priority-and-default-values) for details)
|
||
|
||
#### TableStyleConfig
|
||
|
||
```ts
|
||
interface TableStyleConfig {
|
||
// —— Cell style: applied to every cell ——
|
||
cellStyle?: CellStyle;
|
||
|
||
// —— Row category overrides ——
|
||
firstRowStyle?: CellStyle; // first-row style
|
||
lastRowStyle?: CellStyle; // last-row style
|
||
|
||
// —— Column category overrides ——
|
||
firstColumnStyle?: CellStyle;
|
||
lastColumnStyle?: CellStyle;
|
||
|
||
// —— Alternating row styles ——
|
||
bodyStyles?: CellStyle[]; // data rows other than the first/last row apply these cyclically by data-row index
|
||
|
||
// —— Cross-category rule ——
|
||
rowOverColumn?: boolean; // default: true; whether the row style wins when a cell is covered by both row and column rules
|
||
}
|
||
```
|
||
> **Row/column style rules**: category styles such as `firstRowStyle` / `lastRowStyle` / `firstColumnStyle` / `lastColumnStyle` mean **apply the style independently to every matching cell**, not apply the style to the first row/last column as a whole
|
||
> - Writing `firstRowStyle.border: {style: solid, width: 2}` → **every cell of the first row** gets a border on all four sides
|
||
> - To add an outer frame to the first row as a whole, use per-side BorderSpec: `border: [<top line>, null, <bottom line>, null]`, then set borders separately on the first-column and last-column cells of the first row
|
||
|
||
|
||
> For fallback rules, see [Style Priority and Default Values](#style-priority-and-default-values)
|
||
|
||
## 4. Page Files (.page)
|
||
|
||
### Page
|
||
|
||
```ts
|
||
interface Page {
|
||
pageType?: "cover" | "table_of_contents" | "chapter" | "content" | "final" | string; // default: none; category label (does not affect rendering); preset values are recognized as the corresponding page type, arbitrary custom strings are also allowed
|
||
background?: Fill; // default: {type: solid, color: "#FFFFFF"} (white solid fill)
|
||
notes?: string; // default: none; speaker notes; plain text
|
||
elements: Element[]; // the later an element, the higher its layer
|
||
}
|
||
```
|
||
|
||
**Example:**
|
||
|
||
```yaml
|
||
pageType: cover
|
||
background:
|
||
type: solid
|
||
color: "$primary"
|
||
notes: Speaker notes
|
||
elements:
|
||
- elementId: title1
|
||
elementType: text
|
||
bounds: [100, 200, 760, 80]
|
||
content:
|
||
style: "$title"
|
||
align: [center, middle]
|
||
text: Hello World
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Elements
|
||
|
||
### ElementBase
|
||
|
||
Common properties of all elements.
|
||
|
||
```ts
|
||
interface ElementBase {
|
||
elementId: string; // constraint: unique within the same page; unique element ID
|
||
elementType: "text" | "shape" | "line" | "image" | "icon" | "table" | "chart"; // element type
|
||
bounds: [number, number, number, number]; // element size and position, [x, y, width, height]
|
||
}
|
||
|
||
type Element = Text | Shape | Line | Image | Icon | Table | Chart;
|
||
```
|
||
|
||
---
|
||
|
||
### Text (text box)
|
||
|
||
```ts
|
||
interface Text extends ElementBase {
|
||
elementType: "text";
|
||
rotation?: number; // default: 0; degrees, clockwise rotation
|
||
opacity?: number; // default: 1; constraint: [0, 1]
|
||
flip?: [boolean, boolean]; // default: [false, false]; [horizontal flip, vertical flip]
|
||
content: TextContent;
|
||
}
|
||
```
|
||
|
||
#### TextContent
|
||
|
||
```ts
|
||
interface TextContent {
|
||
text: string; // rich text string (block scalar)
|
||
style?: string; // references theme.textStyles, written as "$key" (e.g. "$title")
|
||
|
||
// —— Style fields (when unset, fall back along the inheritance chain) ——
|
||
color?: Color;
|
||
fontSize?: number;
|
||
fontFamily?: FontFamily;
|
||
bold?: boolean; // bold: true=on, false/unset=off
|
||
italic?: boolean; // italic: true=on, false/unset=off
|
||
backgroundColor?: Color; // text background color (e.g., text highlight)
|
||
lineHeight?: number; // line-height multiple
|
||
lineHeightPx?: number; // fixed line height (px)
|
||
letterSpacing?: number;
|
||
marginTop?: number;
|
||
|
||
// —— Layout fields ——
|
||
textDirection?: "horizontal" | "vertical"; // default: "horizontal"
|
||
wrap?: boolean; // default: true; when false, no wrapping, and the part beyond bounds.width overflows the element boundary; explicitly setting false is recommended for single-line text
|
||
align?: Alignment; // default: ["left", "top"]
|
||
|
||
// —— Visual decoration (unset = not applied) ——
|
||
gradient?: GradientFill; // text gradient (applied to the text itself)
|
||
shadow?: Shadow; // text shadow
|
||
}
|
||
```
|
||
|
||
**Examples:**
|
||
|
||
```yaml
|
||
# Basic: theme style + plain text
|
||
- elementId: title-1
|
||
elementType: text
|
||
bounds: [100, 50, 760, 80]
|
||
content:
|
||
style: "$title"
|
||
align: [center, middle]
|
||
text: Annual Work Summary
|
||
|
||
# Rich text + inline property overrides
|
||
- elementId: body-1
|
||
elementType: text
|
||
bounds: [100, 200, 600, 200]
|
||
content:
|
||
fontSize: 20
|
||
color: "$text"
|
||
lineHeight: 1.6
|
||
align: [left, top]
|
||
text: |
|
||
<p><strong>Key achievement</strong>: completed <span style="color:$primary;">3</span> key projects</p>
|
||
<p style="text-align:right"><span style="font-size:14px; color:#6b7280;">—— FY2024</span></p>
|
||
|
||
# Text gradient + shadow
|
||
- elementId: hero-text
|
||
elementType: text
|
||
bounds: [100, 100, 760, 120]
|
||
content:
|
||
align: [center, middle]
|
||
gradient:
|
||
type: gradient
|
||
gradientType: linear
|
||
angle: 90
|
||
stops:
|
||
- {position: 0, color: "$primary"}
|
||
- {position: 1, color: "$accent"}
|
||
shadow:
|
||
blur: 6
|
||
color: "#00000040"
|
||
offset: [0, 3]
|
||
text: |
|
||
<p><span style="font-size:64px;">FUTURE</span></p>
|
||
```
|
||
|
||
#### Rich Text Rules
|
||
|
||
`TextContent.text` and `Cell.text` follow the rich text rules below for paragraph splitting and for setting paragraph or inline styles.
|
||
|
||
**Supported tags**
|
||
|
||
| Tag | Description | Example |
|
||
|------|------|------|
|
||
| `<p>` | Paragraph; may carry paragraph styles | `<p>paragraph</p>` |
|
||
| `<span>` | Inline style; use this tag to set inline styles | `<span style="color:#f00">red</span>` |
|
||
| `<strong>` | Bold | `<strong>important</strong>` |
|
||
| `<em>` | Italic | `<em>emphasis</em>` |
|
||
| `<u>` | Underline | `<u>underline</u>` |
|
||
| `<s>` | Strikethrough | `<s>deleted</s>` |
|
||
| `<sup>` | Superscript | `E=mc<sup>2</sup>` |
|
||
| `<sub>` | Subscript | `H<sub>2</sub>O` |
|
||
| `<a>` | Hyperlink; supports `https://`, `http://`, `mailto:`; once set, the hyperlink text style (blue with underline) is applied automatically | `<a href="https://x.com">link</a>` |
|
||
| `<ul>` | Unordered list | `<ul><li>item</li></ul>` |
|
||
| `<ol>` | Ordered list | `<ol><li>first item</li></ol>` |
|
||
| `<li>` | List item; must be used together with `<ul>` or `<ol>` | — |
|
||
|
||
**style attribute mapping**
|
||
|
||
`<p>`, `<li>`, and `<span>` may use `style="..."`. Color-type values may all use theme references (e.g. `$primary`), resolved per the [Color](#color) rules.
|
||
|
||
1. **Paragraph styles (only `<p>` supports them)**
|
||
|
||
| Property | Description | Values | Example |
|
||
| --- | --- | --- | --- |
|
||
| `text-align` | Paragraph horizontal alignment | `left` / `center` / `right` / `justify` / `distributed` | `<p style="text-align:center">…</p>` |
|
||
| `line-height` | Line height; **unitless** is treated as a `lineHeight` multiple, **with `px`** as a `lineHeightPx` fixed value | number (e.g. `1.5`) or px string (e.g. `24px`) | `<p style="line-height:1.6">…</p>` |
|
||
| `margin-top` | Spacing before the paragraph | px string (e.g. `8px`) | `<p style="margin-top:8px">…</p>` |
|
||
| `margin-left` | Left margin | px string (e.g. `12px`) | `<p style="margin-left:12px">…</p>` |
|
||
| `margin-right` | Right margin | px string (e.g. `12px`) | `<p style="margin-right:12px">…</p>` |
|
||
|
||
> Do not set `letter-spacing` on `<p>`; to set letter spacing uniformly, use `content.letterSpacing` or `Cell.letterSpacing`.
|
||
|
||
2. **List-item styles (only `<li>` supports them)**
|
||
|
||
| Property | Description |
|
||
| --- | --- |
|
||
| `text-align` | List-item horizontal alignment |
|
||
| `line-height` | Line height; value rules same as `<p>` |
|
||
| `letter-spacing` | Letter spacing |
|
||
| `margin-top` | Spacing before the paragraph |
|
||
| `margin-left` | Left margin |
|
||
| `list-style` | List style shorthand |
|
||
| `list-style-type` | List marker type |
|
||
| `list-style-position` | List marker position |
|
||
| `list-style-image` | List marker image |
|
||
|
||
3. **Inline styles (only `<span>` supports them)**
|
||
|
||
Styles apply only to the text inside that `<span>`.
|
||
|
||
| Property | Description | Values | Example |
|
||
| --- | --- | --- | --- |
|
||
| `color` | Text color | [Color](#color) (HEX6 / HEX8 / theme reference) | `<span style="color:$primary">…</span>` |
|
||
| `font-size` | Font size | px string (e.g. `24px`) | `<span style="font-size:24px">…</span>` |
|
||
| `font-family` | Font family | Font name (e.g. `Arial`, `"Arial, 微软雅黑"`) | `<span style="font-family:Arial">…</span>` |
|
||
| `background-color` | Text background color | [Color](#color) (HEX6 / HEX8 / theme reference) | `<span style="background-color:$accent">…</span>` |
|
||
|
||
|
||
```yaml
|
||
content:
|
||
align: [left, top]
|
||
lineHeight: 1.2
|
||
text: |
|
||
<p><span style="font-size:32px; color:$primary;">Main Title</span><span style="font-size:18px; color:$secondary;">Subtitle</span></p>
|
||
<p style="text-align:center; line-height:1.8">This paragraph is center-aligned with 1.8x line height</p>
|
||
<p style="text-align:right">This paragraph is right-aligned; line height inherits the default 1.2</p>
|
||
```
|
||
**Plain-text shorthand**
|
||
|
||
`content.text` may use plain text directly:
|
||
- Single line: `text: "Hello"` ≡ `text: "<p>Hello</p>"`
|
||
- Multi-line (block scalar):
|
||
```yaml
|
||
text: |
|
||
First line
|
||
Second line
|
||
```
|
||
≡ `<p>First line</p><p>Second line</p>`
|
||
- `<br/>` may be used for a line break within a paragraph, but it is not guaranteed to be preserved on re-conversion after editing. When stable line breaks are needed, use multiple `<p>`.
|
||
|
||
**LaTeX formulas**
|
||
|
||
Rich text supports embedding LaTeX formulas with the `\(...\)` delimiters:
|
||
- May form their own paragraph, or be mixed with other text inside a `<p>`.
|
||
- Rich text tags are **not allowed** inside a formula.
|
||
- A formula **only inherits** the `color` and `font-size` styles from its context; other text styles are not passed through.
|
||
- A `<p>` tag can wrap a LaTeX formula to control the alignment
|
||
|
||
```yaml
|
||
content:
|
||
text: |
|
||
<p>Pythagorean theorem: \(a^2 + b^2 = c^2\)</p>
|
||
<p>\(\int_0^1 x^2 \mathrm{d}x = \frac{1}{3}\)</p>
|
||
```
|
||
|
||
---
|
||
|
||
### Shape (shape)
|
||
|
||
```ts
|
||
interface Shape extends ElementBase {
|
||
elementType: "shape";
|
||
rotation?: number; // default: 0; degrees, clockwise rotation
|
||
opacity?: number; // default: 1; constraint: [0, 1]
|
||
flip?: [boolean, boolean]; // default: [false, false]; [horizontal flip, vertical flip]
|
||
shapeName: string; // see ./shapes.md
|
||
adjustments?: number[]; // see ./shapes.md; geometry parameters; default: the default parameter values
|
||
viewBox?: [number, number]; // view box; used only when shapeName="custom", required in that case
|
||
path?: string; // SVG shape path; used only when shapeName="custom", required in that case
|
||
fill?: Fill; // default: not applied
|
||
border?: Border; // default: not applied
|
||
shadow?: Shadow; // default: not applied
|
||
}
|
||
```
|
||
|
||
> Custom shapes: you may specify `shapeName: "custom"` and use `viewBox` and `path` to define a custom shape; these two parameters have no effect when `shapeName` is not `custom`
|
||
|
||
> The `adjustments` parameters: reuse the parameter order and quantity defined by OOXML; see ./shapes.md for value constraints.
|
||
|
||
> **Note**: `shape` does not support embedded text! Add an extra text box to achieve that.
|
||
|
||
**custom path conventions:**
|
||
- `viewBox`: view box, the path coordinate system `[w, h]`
|
||
- `path`: SVG path string, supporting the `M / L / H / V / C / S / Q / A / Z` commands.
|
||
- Multi-segment paths are supported for shapes such as hollow-outs: make the outer contour **clockwise** (`sweep=1`) and the inner contour **counterclockwise** (`sweep=0`) to achieve a hollow cutout
|
||
- **Scaling and aspect ratio**: changing `bounds` resizes the shape (the path needs no rewriting); but the viewBox is stretched independently to bounds — when the ratios differ, the shape distorts. To keep the ratio, require `viewBoxW : viewBoxH = bounds.w : bounds.h`.
|
||
|
||
**Common shapes**
|
||
|
||
> See [shapes.md](./shapes.md) for the full 177 shapes.
|
||
|
||
| shapeName | Description | adjustments default values |
|
||
|-----------|------|-------------------|
|
||
| `rect` | Rectangle | — |
|
||
| `roundRect` | Rounded rectangle | `[16667]` (corner radius) |
|
||
| `ellipse` | Ellipse | — |
|
||
| `triangle` | Triangle | `[50000]` (horizontal position of apex) |
|
||
| `diamond` | Diamond | — |
|
||
| `homePlate` | Five-sided arrow | `[50000]` |
|
||
| `chevron` | V-shaped arrow | `[50000]` |
|
||
| `donut` | Ring | `[25000]` (ring width ratio) |
|
||
| `star5` | 5-point star | `[19098, 105146, 110557]` |
|
||
| `rightArrow` | Right arrow | `[50000, 50000]` (shaft width, arrowhead length) |
|
||
| `wedgeRectCallout` | Rectangle callout | `[-20833, 62500]` |
|
||
| `bracePair` | Brace pair | `[8333]` |
|
||
|
||
**Examples:**
|
||
|
||
```yaml
|
||
# Built-in shape
|
||
- elementId: shape-1
|
||
elementType: shape
|
||
bounds: [200, 200, 300, 150]
|
||
shapeName: roundRect
|
||
adjustments: [20000]
|
||
fill: {type: solid, color: "$primary"}
|
||
border: {style: solid, width: 2, color: "$accent"}
|
||
|
||
# Custom hollow ring (outer contour clockwise + inner contour counterclockwise)
|
||
- elementId: shape-2
|
||
elementType: shape
|
||
bounds: [400, 200, 150, 150]
|
||
shapeName: custom
|
||
viewBox: [1000, 1000]
|
||
path: "M500,0 A500,500 0 1 1 499,0 Z M500,200 A300,300 0 1 0 499,200 Z"
|
||
fill: {type: solid, color: "$accent"}
|
||
```
|
||
|
||
---
|
||
|
||
### Line (line)
|
||
|
||
```ts
|
||
type ArrowType = "arrow" | "stealth" | "diamond" | "oval";
|
||
|
||
interface Line extends ElementBase {
|
||
elementType: "line";
|
||
rotation?: number; // default: 0; degrees, clockwise rotation
|
||
opacity?: number; // default: 1; constraint: [0, 1]
|
||
flip?: [boolean, boolean]; // default: [false, false]; [horizontal flip, vertical flip]
|
||
viewBox: [number, number]; // path coordinate system [w, h]; points live in this coordinate system, so changing bounds does not require changing points
|
||
points: string; // bezier path points "x1,y1 x2,y2 ..."; the first/last points are the start/end the curve passes through, the middle points are control points
|
||
curve?: "sharp" | "round" | "smooth"; // default: "round"; sharp joins / rounded joins / bezier smooth curve
|
||
arrow?: [ArrowType | null, ArrowType | null]; // start arrow, end arrow; default: [null, null] (no arrows at either end)
|
||
border?: Border; // default: not applied
|
||
shadow?: Shadow; // default: not applied
|
||
}
|
||
```
|
||
|
||
> **Constraint:** `points` needs at least 2 points; the first point and the last point are points the curve passes through, the rest are bezier control points; all coordinates must be within `viewBox`.
|
||
> **viewBox vs bounds:** at render time, the viewBox is stretched independently to the bounds size; to keep the line from being stretched out of shape, require `viewBoxW : viewBoxH = bounds.w : bounds.h`.
|
||
|
||
**Examples:**
|
||
|
||
```yaml
|
||
# Normalized coordinates: from top-left to bottom-right, the two middle points are control points
|
||
- elementId: l4
|
||
elementType: line
|
||
bounds: [100, 100, 500, 300]
|
||
viewBox: [1, 1]
|
||
points: "0,0 0.2,0 0.8,1 1,1"
|
||
curve: smooth
|
||
border: {style: solid, width: 2, color: "$primary"}
|
||
|
||
# Bezier arc: passes through the start and end points; the two middle points control the bend direction
|
||
- elementId: bezier-arc
|
||
elementType: line
|
||
bounds: [50, 200, 860, 100]
|
||
viewBox: [360, 100]
|
||
points: "0,80 120,0 240,100 360,20"
|
||
curve: smooth
|
||
border: {style: solid, width: 2, color: "$primary"}
|
||
```
|
||
|
||
---
|
||
|
||
### Image (image)
|
||
|
||
```ts
|
||
interface Image extends ElementBase {
|
||
elementType: "image";
|
||
rotation?: number; // default: 0; degrees, clockwise rotation
|
||
opacity?: number; // default: 1; constraint: [0, 1]
|
||
flip?: [boolean, boolean]; // default: [false, false]; [horizontal flip, vertical flip]
|
||
src: string; // URL or local relative path
|
||
cropShape?: ShapeDef; // default: rectangle (i.e., no shape cropping)
|
||
fit?: ImageFit; // default: {mode: "cover"}
|
||
crop?: ImageCrop; // always applied; see the rendering order with fit/cropShape below
|
||
border?: Border; // default: not applied
|
||
shadow?: Shadow; // default: not applied
|
||
}
|
||
|
||
interface ShapeDef {
|
||
shapeName: string; // see ./shapes.md; use "custom" for a custom path
|
||
adjustments?: number[]; // default: use the shape's built-in defaults (see ./shapes.md)
|
||
viewBox?: [number, number]; // used only when shapeName="custom", required in that case
|
||
path?: string; // used only when shapeName="custom", required in that case
|
||
}
|
||
```
|
||
|
||
> `ShapeDef` fields correspond one-to-one with the shape fields of the [Shape](#shape-shape) element; for detailed conventions (adjustments values and angle conversion, custom path rules, hollow rules, common shape table), see the [Shape](#shape-shape) section.
|
||
|
||
**Rendering logic:** `crop` (proportionally adjust the source rectangle to get a sub-image: positive values crop inward, negative values expand outward and pad with transparent pixels) → `fit` (adapt the sub-image to the bounds container per mode) → `cropShape` (clip the final display area to the shape outline). All three can be set independently and are applied in the fixed order above.
|
||
|
||
- `fit.mode="cover"`: scale the sub-image proportionally to fill bounds; the overflow is cropped.
|
||
- `fit.mode="contain"`: scale the sub-image proportionally to display it completely; the shortfall is left blank.
|
||
- `fit.mode="fill"`: **the sub-image is stretched directly to fill bounds** — although no cropped blank edges are visible in this case, the picture content is still only the sub-region after crop, not the full original image.
|
||
|
||
**Examples:**
|
||
|
||
```yaml
|
||
- elementId: img-1
|
||
elementType: image
|
||
bounds: [50, 50, 400, 300]
|
||
src: "media/cover.jpg"
|
||
cropShape: {shapeName: roundRect, adjustments: [15000]}
|
||
fit: {mode: cover}
|
||
crop: {top: 0.1, bottom: 0.1, left: 0.05, right: 0.05} # crop the surrounding proportions first, then apply cover fitting
|
||
shadow:
|
||
blur: 10
|
||
color: "#00000033"
|
||
offset: [0, 4]
|
||
|
||
# Custom clip outline
|
||
- elementId: img-2
|
||
elementType: image
|
||
bounds: [200, 200, 200, 200]
|
||
src: "media/avatar.jpg"
|
||
cropShape:
|
||
shapeName: custom
|
||
viewBox: [1000, 1000]
|
||
path: "M500,0 A500,500 0 1 1 499,0 Z"
|
||
fit: {mode: cover}
|
||
```
|
||
|
||
---
|
||
|
||
### Icon (icon)
|
||
|
||
```ts
|
||
interface Icon extends ElementBase {
|
||
elementType: "icon";
|
||
rotation?: number; // default: 0; degrees, clockwise rotation
|
||
opacity?: number; // default: 1; constraint: [0, 1]
|
||
flip?: [boolean, boolean]; // default: [false, false]; [horizontal flip, vertical flip]
|
||
iconName: string; // format "style:name"
|
||
fill?: Fill; // default: black solid fill
|
||
border?: Border; // default: not applied
|
||
shadow?: Shadow; // default: not applied
|
||
}
|
||
```
|
||
|
||
**iconName format:** `style:name`, using the Font Awesome 7.x free icon library.
|
||
|
||
| Prefix | Style | Example |
|
||
|------|------|------|
|
||
| `fas` | Solid (most common) | `fas:house` |
|
||
| `far` | Regular | `far:heart` |
|
||
| `fab` | Brands | `fab:github` |
|
||
|
||
Icon search: https://fontawesome.com/search?ic=free-collection
|
||
|
||
**Example:**
|
||
|
||
```yaml
|
||
- elementId: icon-1
|
||
elementType: icon
|
||
bounds: [100, 100, 48, 48]
|
||
iconName: "fas:lightbulb"
|
||
fill: {type: solid, color: "$primary"}
|
||
```
|
||
|
||
---
|
||
|
||
### Table (table)
|
||
|
||
```ts
|
||
interface Table extends ElementBase {
|
||
elementType: "table";
|
||
columnWidths: number[]; // array of column-width ratios (not px; relative to the bounds width)
|
||
rowHeights: number[]; // array of row-height ratios (not px; relative to the bounds height)
|
||
rows: Cell[][]; // 2-D array; merged regions are declared with rowSpan/colSpan, occupied positions are skipped in the array
|
||
style?: string | TableStyleConfig; // references theme.tableStyles, written as "$key" (e.g. "$default"), or an inline TableStyleConfig object
|
||
fill?: Fill; // default: not applied; table-level fill (applied to the whole table, can be overridden by cell fill)
|
||
shadow?: Shadow; // default: not applied
|
||
}
|
||
```
|
||
|
||
> **PowerPoint limitation:** native tables cannot be rotated/flipped as a whole; whole-table global opacity including text and borders is also not supported. When whole rotation/flip/opacity is needed, render as an image first and treat it as an [Image](#image-image) element.
|
||
|
||
> **Constraint:** each item of `columnWidths` and `rowHeights` is within `[0, 1]`, and the elements of each sum to 1.
|
||
|
||
#### Cell
|
||
|
||
```ts
|
||
interface Cell {
|
||
// —— Content ——
|
||
text?: string; // default: empty cell; rich text string (written as a block scalar), rules same as TextContent.text
|
||
textStyle?: string; // references theme.textStyles, written as "$key" (e.g. "$body")
|
||
|
||
// —— Text styles (when unset, fall back along the inheritance chain) ——
|
||
color?: Color;
|
||
fontSize?: number;
|
||
fontFamily?: FontFamily;
|
||
bold?: boolean;
|
||
italic?: boolean;
|
||
backgroundColor?: Color; // text background color (e.g., text highlight)
|
||
lineHeight?: number; // line-height multiple
|
||
lineHeightPx?: number; // fixed line height (px)
|
||
letterSpacing?: number;
|
||
marginTop?: number;
|
||
|
||
// —— Cell styles (when unset, fall back along the inheritance chain) ——
|
||
fill?: Fill; // background fill; supports solid / gradient / image
|
||
border?: BorderSpec;
|
||
align?: Alignment;
|
||
|
||
// —— Merging ——
|
||
rowSpan?: number; // default: 1
|
||
colSpan?: number; // default: 1
|
||
}
|
||
```
|
||
|
||
**Basic example (using theme styles):**
|
||
|
||
```yaml
|
||
- elementId: table-basic
|
||
elementType: table
|
||
bounds: [80, 120, 800, 280]
|
||
columnWidths: [0.3, 0.35, 0.35]
|
||
rowHeights: [0.33, 0.33, 0.34]
|
||
style: "$default"
|
||
rows:
|
||
- - text: "Metric"
|
||
- text: "2023"
|
||
- text: "2024"
|
||
- - text: "Revenue (100M CNY)"
|
||
- text: "82.5"
|
||
- text: "96.3"
|
||
- - text: "Net profit (100M CNY)"
|
||
- text: "12.1"
|
||
- text: "15.8"
|
||
```
|
||
|
||
> **Merged-cell rules:** `rowSpan` / `colSpan` declare the merge range; **cells covered by the merged region are omitted from the `rows` array, with no `null` placeholder needed**. For example, after a top-left 2×2 merge, row 0's colSpan=2 covers (0,1), so that row only has two items ((0,0) merged cell + (0,2)); row 1 has (1,0) and (1,1) occupied by the merge, so it only has one item, (1,2).
|
||
|
||
```yaml
|
||
- elementId: table-merged
|
||
elementType: table
|
||
bounds: [100, 100, 600, 400]
|
||
columnWidths: [0.33, 0.33, 0.34]
|
||
rowHeights: [0.33, 0.33, 0.34]
|
||
rows:
|
||
# Row 0: top-left 2×2 merge + C1. The merged (0,1) is omitted
|
||
- - text: "Merged cell"
|
||
fill: {type: solid, color: "$accent"}
|
||
rowSpan: 2
|
||
colSpan: 2
|
||
- text: "C1"
|
||
# Row 1: (1,0) and (1,1) are occupied by the merge → only C2 remains
|
||
- - text: "C2"
|
||
# Row 2: full three columns
|
||
- - text: "A3"
|
||
- text: "B3"
|
||
- text: "C3"
|
||
```
|
||
|
||
---
|
||
|
||
### Chart (charts)
|
||
|
||
PPTD v2's chart element follows the ECharts philosophy: **the chart top level carries no `type` field**; each `series[i].type` determines its own form. 13 series types are supported in total, laid out flat by type name, all equal in status:
|
||
|
||
`bar` / `line` / `area` / `scatter` / `bubble` / `candlestick` / `pie` / `radar` / `waterfall` / `heatmap` / `treemap` / `sunburst` / `sankey`
|
||
|
||
Each type declares its **series constraint** on the first line of its own subsection: the maximum count allowed within the same chart + which other types it may coexist with.
|
||
|
||
#### Chart
|
||
|
||
```ts
|
||
interface Chart extends ElementBase {
|
||
elementType: "chart";
|
||
|
||
data: ChartData; // required
|
||
series: SeriesConfig[]; // required; constraint: length ≥ 1
|
||
seriesDefaults?: SeriesDefaults; // default: not applied; common defaults grouped by series.type, merged with each series
|
||
|
||
// —— Cartesian coordinate system (conditionally effective by series.type, see §5.3) ——
|
||
xAxis?: AxisConfig | AxisConfig[]; // default: auto-adapt to data; in array form, referenced via series[i].xAxisIndex
|
||
yAxis?: AxisConfig | AxisConfig[]; // default: auto-adapt to data; in array form, referenced via series[i].yAxisIndex
|
||
barWidth?: number; // default: adaptive; constraint: (0, 1]; bar width / category slot width ratio
|
||
barGap?: number; // default: 0 (flush); constraint: [0, 1); gap between bars when multiple bar series are grouped
|
||
categoryGap?: number; // default: 0.2; constraint: [0, 1); blank ratio between category slots
|
||
|
||
// —— Radar coordinate system (radar series only) ——
|
||
spokeAxis?: SpokeAxisConfig; // default: auto-adapt to data; spoke axes + spider grid
|
||
|
||
// —— Global components ——
|
||
title?: string | TitleConfig; // default: no title
|
||
legend?: boolean | LegendConfig; // default: varies by type (see the default-value table in [LegendConfig](#legendconfig))
|
||
dataLabels?: DataLabelConfig; // default: not applied; global default, can be overridden by series.dataLabels
|
||
fontFamily?: FontFamily; // default: falls back along the theme/master fonts
|
||
|
||
// —— Chart frame (controls the rectangular container of the whole chart element, independent of series colors) ——
|
||
fill?: Fill; // default: not applied
|
||
border?: Border; // default: not applied
|
||
shadow?: Shadow; // default: not applied
|
||
}
|
||
|
||
type SeriesConfig =
|
||
| BarSeries | LineSeries | AreaSeries | ScatterSeries | BubbleSeries
|
||
| CandlestickSeries | PieSeries | RadarSeries | WaterfallSeries
|
||
| HeatmapSeries | TreemapSeries | SunburstSeries | SankeySeries;
|
||
```
|
||
|
||
> `fill` / `border` / `shadow` control the **chart element's rectangular frame** (acting on the whole chart container), independent of the series body colors.
|
||
>
|
||
> **PowerPoint limitation:** native charts cannot be rotated/flipped as a whole; there is also no single global opacity property covering "the whole chart including title, axes, legend, labels, and series". When whole rotation/flip/opacity is needed, render as an image first and treat it as an [Image](#image-image) element.
|
||
|
||
#### ChartData
|
||
|
||
```ts
|
||
interface ChartData {
|
||
cols: string[]; // column names; constraint: unique, non-empty strings
|
||
rows: (number | string | null)[][]; // constraint: each row's length = cols.length
|
||
}
|
||
```
|
||
|
||
> **Data integrity constraints** (validated by the checker):
|
||
> - Duplicate column names in `cols` → `DuplicateColumnError`
|
||
> - `cols` contains an empty string → `EmptyColumnError`
|
||
> - `rows[i].length !== cols.length` → `RowLengthError`
|
||
> - A column name referenced by encode is not in `cols` → `UnknownColumnError`
|
||
> - It is legal for the same column to be referenced by multiple series (e.g., the same y column drawn once by bar and once by line)
|
||
> - When a column value of a numeric channel (`y` / `value` / `open` / `high` / `low` / `close` / `size` / `flow`) is a string, it is parsed as a number; on failure, `NonNumericValueError` is raised
|
||
>
|
||
> **How to write missing cells**: fill with `null`, e.g. `[null, null, 2, 3]`. Consecutive commas `[, , 2, 3]` are **not recommended** — strict YAML parsers will error.
|
||
|
||
#### General Rules
|
||
|
||
1. **The `fill` type of series**: `Color | GradientFill`; a string is treated as a solid [Color](#color) (HEX8 or a `$xxx` theme reference), an object as [GradientFill](#fill) (with `type: "gradient"`); some types support the `(Color | GradientFill)[]` array form (cycled by slice/node). **Series-level fill does not support [ImageFill](#fill)**.
|
||
2. **Type mixing**: which types a chart's `series[]` may contain is determined by the "series constraint" on the first line of each type's section; the checker validates accordingly.
|
||
3. **Conditionally effective top-level fields**: `xAxis` / `yAxis` / `barWidth` / `barGap` / `categoryGap` / `spokeAxis` are **coordinate-system-level** configurations, conditionally effective based on the `series[].type` set (see [5.3 Applicability of chart top-level fields](#53-applicability-of-chart-top-level-fields) for details).
|
||
4. **[Color](#color) theme reference scope**: all fields of type `Color` (including every Color position inside nested arrays and objects) support `$xxx` theme references — e.g. `upBars: {fill: "$success"}`, `colorScheme: ["$bg", "$primary"]`, `fill: ["$primary", "$accent"]`.
|
||
5. **Omission semantics of optional object-type fields**: all **object-type** fields marked with `?` (`xAxis` / `yAxis` / `spokeAxis` / `colorScale` / `marker` / `dataLabels`, etc.): when omitted, they are equivalent to an empty configuration `{}` of that object, and all sub-fields take their own default values — i.e., "axes/grids/labels etc. still render by default, just with automatically inferred parameters". This differs from `fill` / `border` / `shadow` of [ElementBase](#elementbase) (where omission means **not applied**).
|
||
|
||
> **bar / waterfall direction**: determined by the axis type — vertical (default) when `xAxis.type === "category"`, horizontal when `yAxis.type === "category"`. `axis.type` is inferred from the data column by default (string → category, number → value); when a numeric column needs to be used as categories (e.g. years), override explicitly with `axis.type: "category"`. In the horizontal case, `encode.x` references the numeric column and `encode.y` the category column, and `numberFormat` is written on the side where the value axis is. For scatter / bubble, both x and y are numeric channels, with no notion of direction.
|
||
|
||
---
|
||
|
||
#### TextStyle
|
||
|
||
```ts
|
||
interface TextStyle {
|
||
color?: Color; // default: falls back along the inheritance chain (theme text color / PPTX master)
|
||
fontSize?: number; // default: auto-adapts to chart size
|
||
fontFamily?: FontFamily; // default: falls back along the inheritance chain to Chart.fontFamily or the theme font
|
||
}
|
||
```
|
||
|
||
> The common trio of text styles, inherited and reused by [TitleConfig](#titleconfig) / [LegendConfig](#legendconfig) / [DataLabelConfig](#datalabelconfig) / [AxisConfig](#axisconfig).label / [SpokeAxisConfig](#spokeaxisconfig).label; for the font priority chain, see [§3.2](#3-chart-styles).
|
||
|
||
#### LineStyleConfig
|
||
|
||
```ts
|
||
interface LineStyleConfig {
|
||
style?: "solid" | "dash" | "dot"; // default: "solid"
|
||
color?: Color; // default: falls back to the theme
|
||
width?: number; // default: 1
|
||
}
|
||
```
|
||
|
||
> Generic line style, reused by the `axisLine` / `gridLine` of [AxisConfig](#axisconfig) / [SpokeAxisConfig](#spokeaxisconfig).
|
||
|
||
#### TitleConfig
|
||
|
||
```ts
|
||
interface TitleConfig extends TextStyle {
|
||
text: string; // required
|
||
// fontSize auto-adapts to chart size by default
|
||
}
|
||
```
|
||
|
||
#### LegendConfig
|
||
|
||
```ts
|
||
interface LegendConfig extends TextStyle {
|
||
show?: boolean; // default: varies by type (see the table below)
|
||
position?: "top" | "bottom" | "left" | "right"; // default: "bottom"
|
||
}
|
||
```
|
||
|
||
`show` defaults by type:
|
||
|
||
| type | Default |
|
||
|---|---|
|
||
| bar / line / area / scatter / bubble / candlestick / pie / radar | `true` |
|
||
| waterfall | `false` |
|
||
| treemap / sunburst / sankey | `false` (names and values are already shown on the chart) |
|
||
| heatmap | Does not use `chart.legend` (controlled by [series.colorbar](#heatmap)) |
|
||
|
||
> `legend: false` or `legend: {show: false}` turns it off, **effective for all 13 types**; `legend: true` or the object form only has a visual effect for the types marked applicable in the table above.
|
||
|
||
#### DataLabelConfig
|
||
|
||
```ts
|
||
interface DataLabelConfig extends TextStyle {
|
||
show?: boolean; // default: false
|
||
content?: "value" | "percentage" | "category"; // default: varies by type (see [5.5 value quick reference](#55-datalabelscontent-value-quick-reference))
|
||
numberFormat?: string; // default: no formatting; Excel number-format string (see below)
|
||
}
|
||
```
|
||
|
||
> **numberFormat standard**: takes a subset of Excel number-format strings — `0` (integer) / `0.0` (one decimal) / `0%` (percentage) / `0.0%` (percentage with decimals) / `#,##0` (thousands separator) / `0.0E+00` (scientific notation). Advanced syntax such as `[Red]` color sections, negative sections, and conditional formatting is **not supported**.
|
||
|
||
#### MarkerConfig
|
||
|
||
```ts
|
||
interface MarkerConfig {
|
||
shape?: "circle" | "rect" | "diamond" | "triangle"; // default: "circle"
|
||
fill?: Color | GradientFill; // default: follows the series body color
|
||
border?: Border; // default: not applied
|
||
size?: number; // default: auto-adapts to chart size; unit px
|
||
}
|
||
```
|
||
|
||
> The `rect` naming is consistent with the [shapes.md](./shapes.md) shape library.
|
||
|
||
#### AxisConfig
|
||
|
||
```ts
|
||
interface AxisConfig {
|
||
show?: boolean; // default: true
|
||
type?: "category" | "value"; // default: inferred from the data column (string → category, number → value)
|
||
min?: number; // default: auto-adapt to data; only effective for value axes
|
||
max?: number; // default: auto-adapt to data; only effective for value axes
|
||
reverse?: boolean; // default: false; true = reverse the axis direction (maximum at the origin side)
|
||
title?: string | TitleConfig; // default: no title; the string form is recommended, use the object only for special styling
|
||
label?: boolean | (TextStyle & { // default: true; tick labels
|
||
numberFormat?: string; // default: no formatting; only effective for value axes
|
||
});
|
||
axisLine?: boolean | (LineStyleConfig & { // default: true
|
||
arrow?: boolean | "start" | "end" | "both"; // default: false; true is equivalent to "end"
|
||
});
|
||
gridLine?: boolean | LineStyleConfig; // default: true
|
||
}
|
||
```
|
||
|
||
#### SeriesDefaults
|
||
|
||
```ts
|
||
interface SeriesDefaults {
|
||
bar?: Partial<Omit<BarSeries, "type" | "encode">>;
|
||
line?: Partial<Omit<LineSeries, "type" | "encode">>;
|
||
area?: Partial<Omit<AreaSeries, "type" | "encode">>;
|
||
scatter?: Partial<Omit<ScatterSeries, "type" | "encode">>;
|
||
bubble?: Partial<Omit<BubbleSeries, "type" | "encode">>;
|
||
candlestick?: Partial<Omit<CandlestickSeries, "type" | "encode">>;
|
||
radar?: Partial<Omit<RadarSeries, "type" | "encode">>;
|
||
}
|
||
```
|
||
|
||
> Provides common default values for all series of that type, avoiding repetition across multiple series. For the merge algorithm and the range of usable types, see [§3.4](#3-chart-styles).
|
||
|
||
#### SpokeAxisConfig
|
||
|
||
Used only by [radar](#radar) series.
|
||
|
||
```ts
|
||
interface SpokeAxisConfig {
|
||
show?: boolean; // default: true
|
||
min?: number; // default: 0; minimum of the value axis shared by all dimensions
|
||
max?: number; // default: auto-adapt to data; maximum of the value axis shared by all dimensions
|
||
label?: boolean | (TextStyle & { // default: true; tick labels
|
||
numberFormat?: string; // default: no formatting
|
||
});
|
||
axisLine?: boolean | LineStyleConfig; // default: true; spoke lines from the center to the outer ring
|
||
gridLine?: boolean | LineStyleConfig; // default: true; spider grid lines (concentric polygons connecting the spoke endpoints)
|
||
}
|
||
```
|
||
|
||
#### LinearSeriesBase
|
||
|
||
Curve-class common fields shared by [line](#line) / [area](#area) / [radar](#radar).
|
||
|
||
```ts
|
||
interface LinearSeriesBase {
|
||
smooth?: boolean; // default: false
|
||
lineStyle?: "solid" | "dash" | "dot"; // default: "solid"
|
||
width?: number; // default: 2
|
||
marker?: false | MarkerConfig; // default: not applied
|
||
nullHandling?: "zero" | "gap" | "connect"; // default: "gap" for line/area, "connect" for radar
|
||
lineColor?: Color | GradientFill; // default: follows the theme color cycle; line color of line / polygon stroke color of area+radar
|
||
}
|
||
```
|
||
|
||
> If multiple line/area/radar series within the same chart set different `nullHandling` values, only the **first non-empty value** takes effect and the other series follow; multiple null-handling methods are not supported.
|
||
|
||
---
|
||
|
||
#### bar
|
||
|
||
> **series constraint**: may be freely mixed with `line / area / scatter / bubble`; may also mix with `candlestick`; no limit on the number of bar series in the same chart.
|
||
|
||
```ts
|
||
interface BarSeries {
|
||
type: "bar";
|
||
encode: { x: string; y: string }; // required
|
||
name?: string; // default: the encode.y column name; for legend display only
|
||
xAxisIndex?: number; // default: 0; meaningful only when chart.xAxis is an array
|
||
yAxisIndex?: number; // default: 0; meaningful only when chart.yAxis is an array
|
||
stack?: "value" | "percent"; // default: no stacking; "value" sums directly, "percent" normalizes to 100%
|
||
symbol?: ShapeDef; // default: normal rectangular bar; pictographic bar shape definition (see [ShapeDef](#image-image))
|
||
fill?: Color | GradientFill; // default: follows the theme color cycle
|
||
border?: Border; // default: not applied
|
||
dataLabels?: DataLabelConfig; // default: not shown; content only takes "value"
|
||
}
|
||
```
|
||
|
||
#### line
|
||
|
||
> **series constraint**: may be freely mixed with `bar / area / scatter / bubble`; may also mix with `candlestick`.
|
||
|
||
```ts
|
||
interface LineSeries extends LinearSeriesBase {
|
||
type: "line";
|
||
encode: { x: string; y: string }; // required
|
||
name?: string; // default: the encode.y column name
|
||
xAxisIndex?: number; // default: 0
|
||
yAxisIndex?: number; // default: 0
|
||
dataLabels?: DataLabelConfig; // default: not shown; content only takes "value"
|
||
}
|
||
```
|
||
|
||
> line has no area, so `fill` is not provided; for the other curve-class fields, see [LinearSeriesBase](#linearseriesbase).
|
||
|
||
#### area
|
||
|
||
> **series constraint**: may be freely mixed with `bar / line / scatter / bubble`; may also mix with `candlestick`.
|
||
|
||
```ts
|
||
interface AreaSeries extends LinearSeriesBase {
|
||
type: "area";
|
||
encode: { x: string; y: string }; // required
|
||
name?: string; // default: the encode.y column name
|
||
xAxisIndex?: number; // default: 0
|
||
yAxisIndex?: number; // default: 0
|
||
stack?: "value" | "percent" | "stream"; // default: no stacking; "stream" = streamgraph (area only)
|
||
areaColor?: Color | GradientFill; // default: derived from lineColor as semi-transparent
|
||
dataLabels?: DataLabelConfig; // default: not shown
|
||
}
|
||
```
|
||
|
||
> **Stacking group rules**: within the same chart, **all series of the same type that set `stack` are automatically grouped into one stack**, with no explicit group identifier needed. At most one stack group of the same type is supported within the same chart — all series that set `stack` must use the same value (all `"value"` / all `"percent"` / all `"stream"`); mixing raises `StackModeMismatchError`; series without `stack` display independently. If multiple independent stack groups are needed, split them into multiple chart elements.
|
||
>
|
||
> **`stream` applies only to area**: `value` normalization + central baseline offset; the stacked region is symmetric above and below y=0, taking a "streamgraph" shape.
|
||
|
||
#### scatter
|
||
|
||
> **series constraint**: may be freely mixed with `bar / line / area / bubble`.
|
||
|
||
```ts
|
||
interface ScatterSeries {
|
||
type: "scatter";
|
||
encode: { x: string; y: string }; // required; each series references its own x/y column pair
|
||
name?: string; // default: the encode.y column name
|
||
yAxisIndex?: number; // default: 0
|
||
dataFilter?: { col: string; value: string | number }; // default: no filtering; optional: group with a long table
|
||
marker?: MarkerConfig; // default: {shape: "circle"}; constraint: cannot be false
|
||
fill?: Color | GradientFill; // default: follows the theme color cycle; serves as the marker's default fill color (marker.fill takes precedence)
|
||
border?: Border; // default: not applied; serves as the marker's default border (marker.border takes precedence)
|
||
dataLabels?: DataLabelConfig; // default: not shown; content only takes "value"
|
||
}
|
||
```
|
||
|
||
#### bubble
|
||
|
||
> **series constraint**: may be freely mixed with `bar / line / area / scatter`.
|
||
|
||
```ts
|
||
interface BubbleSeries {
|
||
type: "bubble";
|
||
encode: { x: string; y: string; size: string }; // required
|
||
name?: string; // default: the encode.y column name
|
||
yAxisIndex?: number; // default: 0
|
||
dataFilter?: { col: string; value: string | number }; // default: no filtering; rows where the col column equals value are used as this series' data
|
||
sizeScale?: "linear" | "sqrt" | "log"; // default: "sqrt"
|
||
sizeRange?: [number, number]; // default: auto-adapts to chart size; bubble radius range in px
|
||
fill?: Color | GradientFill; // default: follows the theme color cycle; bubble fill color
|
||
border?: Border; // default: not applied
|
||
dataLabels?: DataLabelConfig; // default: not shown; content only takes "value"
|
||
}
|
||
```
|
||
|
||
> **sizeScale**: `sqrt` (default) makes the area proportional to size; `linear` makes the radius proportional to size; `log` suits scenarios with order-of-magnitude differences. Negative size is treated as 0. For multiple groups, use a wide table + null padding, with each series referencing its own `x/y/size` column triple.
|
||
|
||
#### candlestick
|
||
|
||
> **series constraint**: may only mix with `bar / line / area` (common usage: candlestick body + a line overlaying the MA moving average).
|
||
|
||
```ts
|
||
interface CandlestickSeries {
|
||
type: "candlestick";
|
||
/**
|
||
* encode.open is optional → determines the rendering mode
|
||
* open provided → OHLC candlestick (rendered with 4 series; a solid body expresses the open-close direction)
|
||
* open omitted → HLC high-low-close (rendered with 3 series; a vertical line + dot marker at close, no body)
|
||
*/
|
||
encode: { x: string; high: string; low: string; close: string; open?: string };
|
||
xAxisIndex?: number; // default: 0
|
||
yAxisIndex?: number; // default: 0
|
||
upBars?: { fill?: Color; border?: Border }; // rising bar (close > open) style; only effective in OHLC mode (HLC has no body)
|
||
downBars?: { fill?: Color; border?: Border }; // falling bar (close ≤ open) style; only effective in OHLC mode
|
||
wickStyle?: Border; // wick (high-low vertical line) style; common to HLC / OHLC
|
||
}
|
||
```
|
||
>
|
||
> **Date column handling**: a date column (e.g. `"2024-01-01"`) is treated as string categories, laid out at equal intervals on the x-axis in the order they appear in `rows`, naturally skipping non-trading days. If precise layout by real date intervals is needed, manually padding empty trading days with null rows is recommended.
|
||
|
||
#### pie
|
||
|
||
> **series constraint**: the `series` array may only have 1 element, and may not coexist with other types.
|
||
|
||
```ts
|
||
interface PieSeries {
|
||
type: "pie";
|
||
encode: { category: string; value: string }; // required
|
||
innerRadius?: number; // default: 0; constraint: [0, 1]; > 0 = donut
|
||
startAngle?: number; // default: 0 (12 o'clock direction)
|
||
fill?: Color | GradientFill | (Color | GradientFill)[]; // default: follows the theme color cycle; an array cycles by slice
|
||
border?: Border; // default: not applied
|
||
dataLabels?: DataLabelConfig; // default: not shown; content takes "value" | "percentage" | "category", default "value"
|
||
}
|
||
```
|
||
|
||
> **Angle direction**: fixed **clockwise** as positive; 0° = 12 o'clock position, 90° = 3 o'clock, 180° = 6 o'clock, 270° = 9 o'clock.
|
||
|
||
#### radar
|
||
|
||
> **series constraint**: multiple radar series are allowed in the same chart (sharing one set of spokes), but the type of all series must be `radar`; it may not coexist with other types.
|
||
|
||
```ts
|
||
interface RadarSeries extends LinearSeriesBase {
|
||
type: "radar";
|
||
encode: { category: string; y: string }; // required; the category column holds the spoke labels
|
||
name?: string; // default: the encode.y column name
|
||
areaColor?: Color | GradientFill; // default: derived from lineColor as semi-transparent; polygon fill color
|
||
dataLabels?: DataLabelConfig; // default: not shown; content only takes "value"
|
||
}
|
||
```
|
||
|
||
> The radar chart's spoke axis lines, spider grid, and value range (min/max) are uniformly configured via the chart top-level [spokeAxis](#spokeaxisconfig), shared by multiple series.
|
||
>
|
||
> **Dimension-column sharing constraint**: all radar series within the same chart must reference the same `category` column (i.e., all polygons share the same set of spoke labels). To display radar charts with different spoke labels, use multiple chart elements. Checker validation: the `encode.category` of all radar series must be identical.
|
||
|
||
#### waterfall
|
||
|
||
> **series constraint**: the `series` array may only have 1 element, and may not coexist with other types.
|
||
|
||
```ts
|
||
interface WaterfallSeries {
|
||
type: "waterfall";
|
||
encode: {
|
||
x: string; // category column
|
||
y: string; // value column (floating bars hold the increase/decrease amounts; total columns hold the absolute value of the cumulative total)
|
||
isTotal?: string; // default: omitted = all floating bars; after specifying a bool column, true = total column (opening/subtotal/closing), false/null = floating bar
|
||
};
|
||
totalBars?: { fill?: Color; border?: Border }; // total column (opening/subtotal/closing, isTotal=true) style
|
||
increaseBars?: { fill?: Color; border?: Border }; // floating increase bar (y > 0) style
|
||
decreaseBars?: { fill?: Color; border?: Border }; // floating decrease bar (y < 0) style
|
||
dataLabels?: DataLabelConfig; // default: not shown; content takes "value" | "category", default "value"
|
||
}
|
||
```
|
||
|
||
> **Colors**: waterfall does not use `fill`; colors are mapped through the three categories `totalBars` / `increaseBars` / `decreaseBars` by isTotal and the sign of y; all total columns (opening/subtotal/closing) share `totalBars`.
|
||
>
|
||
> **isTotal semantics**: it may appear at any position (first row / middle subtotal / last row are all legal); every `isTotal=true` is an independent total column whose y value should equal "previous total column's y + the sum of all intermediate floating bars' y" — on mismatch, the checker outputs `WaterfallTotalMismatchWarning` (the first-row total column's y is defined directly). The `isTotal` column only accepts bool or null; the string `"true"` or the number `1` both raise `InvalidValueError`.
|
||
|
||
#### heatmap
|
||
|
||
> **series constraint**: the `series` array may only have 1 element, and may not coexist with other types.
|
||
|
||
```ts
|
||
interface HeatmapSeries {
|
||
type: "heatmap";
|
||
encode: { x: string; y: string; value: string }; // required; the x and y columns must be categories (string), value is numeric
|
||
colorScheme?: Color[]; // default: falls back to the theme; gradient color-scale endpoints
|
||
colorScale?: {
|
||
type?: "linear" | "diverging"; // default: "linear"
|
||
domain?: [number, number]; // default: data range; for type=diverging, default [-max(|v|), +max(|v|)], 0 centered
|
||
};
|
||
colorbar?: boolean | LegendConfig; // default: true; color-scale bar legend; position default "right"
|
||
dataLabels?: DataLabelConfig; // default: not shown; content only takes "value"
|
||
}
|
||
```
|
||
|
||
> **Colors**: `colorScheme` serves as gradient endpoints, interpolated per `colorScale.type`:
|
||
> - `linear`: `colorScheme` length ≥ 2, interpolated between the endpoints;
|
||
> - `diverging`: `colorScheme` length = 3 (low / mid / high); the midpoint is determined by the middle of `domain`, often used in "negative-neutral-positive" scenarios.
|
||
>
|
||
> **Data layout**: the x / y columns are fixed as categories (string); category order on the axes follows first-appearance order in `rows`; (x, y) combinations that do not appear in `rows` are treated as missing cells, rendered transparent (the background color in PPTX). For a complete matrix, explicitly listing all (x, y) combinations and using null for missing values is recommended.
|
||
>
|
||
> heatmap does not use `chart.legend`; it is replaced by `colorbar`; `colorbar: false` turns off the color-scale bar.
|
||
|
||
#### treemap
|
||
|
||
> **series constraint**: the `series` array may only have 1 element, and may not coexist with other types.
|
||
|
||
```ts
|
||
interface TreemapSeries {
|
||
type: "treemap";
|
||
encode: {
|
||
category: string; // node-name column
|
||
value: string; // value column
|
||
parent?: string; // parent-node column; null/missing/empty = root node (multiple roots allowed)
|
||
};
|
||
levels?: number; // default: show all levels
|
||
fill?: Color | GradientFill
|
||
| (Color | GradientFill)[]
|
||
| (Color | GradientFill)[][]; // default: follows the theme color cycle; see "color derivation rules" below
|
||
border?: Border; // default: not applied
|
||
dataLabels?: DataLabelConfig; // default: not shown; content takes "value" | "category", default "category"
|
||
}
|
||
```
|
||
|
||
> **Color derivation rules**:
|
||
> - `fill: Color | GradientFill` (single value): all root nodes share this color; child nodes are derived by decreasing lightness by 10% per level (along the HSL.L dimension).
|
||
> - `fill: (Color | GradientFill)[]` (1-D array): cycles in the order the root nodes appear; each root node's child nodes are derived by decreasing lightness by 10% per level.
|
||
> - `fill: (Color | GradientFill)[][]` (2-D array): the outer dimension cycles by root node, the inner dimension specifies levels directly (bypassing automatic derivation); if an inner array is not long enough to cover all levels, the remaining levels are still derived by decreasing lightness by 10%.
|
||
|
||
#### sunburst
|
||
|
||
> **series constraint**: the `series` array may only have 1 element, and may not coexist with other types.
|
||
|
||
```ts
|
||
interface SunburstSeries {
|
||
type: "sunburst";
|
||
encode: { category: string; value: string; parent?: string }; // required
|
||
levels?: number; // default: show all levels
|
||
fill?: Color | GradientFill | (Color | GradientFill)[]; // default: follows the theme color cycle; an array cycles by top-level node
|
||
border?: Border; // default: not applied
|
||
dataLabels?: DataLabelConfig; // default: not shown; content takes "value" | "category", default "category"
|
||
}
|
||
```
|
||
|
||
#### sankey
|
||
|
||
> **series constraint**: the `series` array may only have 1 element, and may not coexist with other types.
|
||
|
||
```ts
|
||
interface SankeySeries {
|
||
type: "sankey";
|
||
encode: {
|
||
source: string; // source-node column
|
||
target: string; // target-node column
|
||
flow: string; // flow column
|
||
};
|
||
nodeAlign?: "left" | "right" | "justify"; // default: "justify"
|
||
fill?: Color | GradientFill // default: follows the theme color cycle (colors picked in node topological order)
|
||
| (Color | GradientFill)[] // an array cycles by node
|
||
| Record<string, Color | GradientFill>; // mapped by node name; unspecified nodes fall back to the theme color cycle
|
||
border?: Border; // default: not applied
|
||
dataLabels?: DataLabelConfig; // default: not shown; content takes "value" | "category", default "value"
|
||
}
|
||
```
|
||
|
||
> **Graph constraint**: sankey is restricted to a **directed acyclic graph (DAG)**; when the source/target columns form a cycle, the checker raises `CyclicGraphError`.
|
||
>
|
||
> **Node order**: the `source` and `target` columns are deduplicated and arranged in topological order. When `fill` is an array, it cycles in this order; when the array length < node count, it wraps around and reuses; when > node count, it truncates. The object form matches by node name exactly.
|
||
|
||
---
|
||
|
||
#### Field Quick Reference
|
||
|
||
##### 5.1 Data encode Channels
|
||
|
||
| type | encode channels |
|
||
|---|---|
|
||
| bar / line / area | `x` + `y` |
|
||
| scatter | `x` + `y` (multi-series use a wide table + null padding) |
|
||
| bubble | `x` + `y` + `size` (multi-series use a wide table + null padding) |
|
||
| candlestick | Candlestick: `x` + `open` + `close` + `low` + `high`; overlay: `x` + `y` |
|
||
| pie | `category` + `value` |
|
||
| radar | `category` + `y` |
|
||
| waterfall | `x` + `y` + optional `isTotal` (bool column) |
|
||
| heatmap | `x` + `y` + `value` |
|
||
| treemap / sunburst | `category` + `value` + optional `parent` |
|
||
| sankey | `source` + `target` + `flow` |
|
||
|
||
> **Naming convention**: Cartesian coordinate systems (bar/line/area/scatter/bubble/candlestick/waterfall/heatmap) use `x` / `y` for the horizontal/vertical axes; non-Cartesian category fields are uniformly `category` (pie/radar/treemap/sunburst); sankey graph edge endpoints use `source` / `target`.
|
||
|
||
##### 5.2 Color Mechanism
|
||
|
||
| type | Color fields | Description |
|
||
|---|---|---|
|
||
| bar | `series[].fill` | Bar body fill color |
|
||
| line | `series[].lineColor` | Line color (no area) |
|
||
| area | `series[].lineColor` + `series[].areaColor` | Stroke color + area color (when the area color is omitted, it is derived from lineColor as semi-transparent) |
|
||
| radar | `series[].lineColor` + `series[].areaColor` | Polygon stroke + fill (same as area) |
|
||
| scatter | `series[].fill` / `marker.fill` | Series level is the marker default color; `marker.fill` takes precedence |
|
||
| bubble | `series[].fill` | Bubble fill color |
|
||
| candlestick | `upBars` / `downBars` (body fill+border) + each overlay series' own `lineColor`/`fill` | The candlestick body maps by up/down; overlay line/bar use their own colors |
|
||
| pie / sunburst / sankey | The single series' `fill` array, cycled by data point/node | Array length cycles and reuses |
|
||
| treemap | The single series' `fill` (single value / 1-D array / 2-D array) | Same as pie etc.; child nodes decrease from the parent along the HSL.L dimension (`L_new = max(0, L_old - 10)`) |
|
||
| heatmap | `series[].colorScheme` + `series[].colorScale` | The gradient color scale maps by value; `linear` interpolates between the endpoints, `diverging` aligns three colors at the midpoint |
|
||
| waterfall | `series[].totalBars` / `increaseBars` / `decreaseBars` | Mapped into three classes — total (total columns) / increase / decrease; does not participate in the theme color cycle |
|
||
|
||
##### 5.3 Applicability of Chart Top-Level Fields
|
||
|
||
| Top-level field | Applicable series types |
|
||
|---|---|
|
||
| `xAxis` | bar / line / area / scatter / bubble / candlestick / waterfall / heatmap |
|
||
| `yAxis` | bar / line / area / scatter / bubble / candlestick / waterfall / heatmap |
|
||
| `barWidth` / `barGap` | bar / waterfall (bar layout parameters) |
|
||
| `categoryGap` | bar / candlestick / waterfall (category spacing parameter) |
|
||
| `spokeAxis` | radar (includes spoke axis lines + spider grid + min/max) |
|
||
| `legend` | bar / line / area / scatter / bubble / candlestick / pie / radar / waterfall |
|
||
| `title` | All |
|
||
| `dataLabels` | All (candlestick: only effective for overlay series; the candlestick body itself expresses the up/down roles via upBars/downBars) |
|
||
| `fontFamily` | All |
|
||
|
||
> **Axis single-value vs array rules**: a secondary axis is always placed on the **side where the value axis is** — vertical charts use a `yAxis` array + `yAxisIndex`, horizontal charts use an `xAxis` array + `xAxisIndex`. When any series uses `xAxisIndex > 0` / `yAxisIndex > 0`, the corresponding `xAxis` / `yAxis` must be an array (length ≥ max(index) + 1).
|
||
|
||
##### 5.4 Type-Mixing Compatibility Quick Reference
|
||
|
||
```
|
||
bar / line / area / scatter / bubble may coexist with each other freely; candlestick may only coexist with bar / line / area
|
||
```
|
||
|
||
> The other 7 types each exclusively own the series array; see the first line of the corresponding section for detailed constraints.
|
||
|
||
##### 5.5 dataLabels.content Value Quick Reference
|
||
|
||
| type | Allowed values | Default |
|
||
|---|---|---|
|
||
| bar / line / area / scatter / bubble / radar / heatmap | `value` | `value` |
|
||
| candlestick (overlay series only) | `value` | `value` |
|
||
| pie | `value` / `percentage` / `category` | `value` |
|
||
| waterfall | `value` / `category` | `value` |
|
||
| treemap / sunburst | `value` / `category` | `category` |
|
||
| sankey | `value` / `category` | `value` |
|
||
|
||
> Writing a value outside this table → the checker raises `InvalidValueError`. The candlestick body itself does not support dataLabels.
|
||
|
||
---
|
||
|
||
#### Examples
|
||
|
||
**Bar chart (stacked)**
|
||
|
||
```yaml
|
||
- elementId: c1
|
||
elementType: chart
|
||
bounds: [50, 100, 600, 400]
|
||
data:
|
||
cols: [quarter, revenue, cost]
|
||
rows:
|
||
- [Q1, 120, 220]
|
||
- [Q2, 132, 182]
|
||
- [Q3, 101, 191]
|
||
- [Q4, 134, 234]
|
||
seriesDefaults:
|
||
bar: {stack: value}
|
||
series:
|
||
- type: bar
|
||
encode: {x: quarter, y: revenue}
|
||
name: Revenue
|
||
fill: "$primary"
|
||
- type: bar
|
||
encode: {x: quarter, y: cost}
|
||
name: Expenses
|
||
fill: "$accent"
|
||
```
|
||
|
||
**Line chart (multi-series differentiation)**
|
||
|
||
```yaml
|
||
- elementId: c2
|
||
elementType: chart
|
||
bounds: [50, 100, 600, 400]
|
||
data:
|
||
cols: [month, actual, target, baseline]
|
||
rows:
|
||
- [Jan, 72, 65, 50]
|
||
- [Feb, 85, 70, 50]
|
||
- [Mar, null, 78, 50]
|
||
- [Apr, 90, 82, 50]
|
||
yAxis: {min: 0, max: 100, gridLine: {color: "#f0f0f0"}}
|
||
series:
|
||
- type: line
|
||
encode: {x: month, y: actual}
|
||
name: Actual
|
||
lineColor: "#5470c6"
|
||
lineStyle: solid
|
||
width: 3
|
||
smooth: true
|
||
- type: line
|
||
encode: {x: month, y: target}
|
||
name: Target
|
||
lineColor: "#ee6666"
|
||
lineStyle: dash
|
||
smooth: true
|
||
- type: line
|
||
encode: {x: month, y: baseline}
|
||
name: Baseline
|
||
lineColor: "#999999"
|
||
lineStyle: dot
|
||
width: 1
|
||
marker: false
|
||
```
|
||
|
||
**Area chart (stream stacking)**
|
||
|
||
```yaml
|
||
- elementId: c3
|
||
elementType: chart
|
||
bounds: [50, 80, 700, 400]
|
||
title: Traffic Evolution by Channel
|
||
data:
|
||
cols: [week, web, app, partner]
|
||
rows:
|
||
- [W1, 200, 120, 80]
|
||
- [W2, 240, 160, 90]
|
||
- [W3, 260, 200, 110]
|
||
- [W4, 280, 240, 130]
|
||
seriesDefaults:
|
||
area: {stack: stream}
|
||
series:
|
||
- type: area
|
||
encode: {x: week, y: web}
|
||
name: Web
|
||
areaColor: "#5470c6"
|
||
- type: area
|
||
encode: {x: week, y: app}
|
||
name: App
|
||
areaColor: "#91cc75"
|
||
- type: area
|
||
encode: {x: week, y: partner}
|
||
name: Partner
|
||
areaColor: "#fac858"
|
||
```
|
||
|
||
**Bubble chart (multi-series grouping)**
|
||
|
||
```yaml
|
||
- elementId: c5
|
||
elementType: chart
|
||
bounds: [50, 80, 700, 420]
|
||
title: User Distribution
|
||
xAxis: {title: Age}
|
||
yAxis: {title: "Annual income (10K)"}
|
||
data:
|
||
cols: [age_s, inc_s, pop_s, age_w, inc_w, pop_w, age_m, inc_m, pop_m]
|
||
rows:
|
||
- [22, 5, 120, 28, 12, 380, 45, 40, 180]
|
||
- [null, null, null, 35, 25, 260, 52, 60, 90]
|
||
seriesDefaults:
|
||
bubble:
|
||
sizeScale: sqrt
|
||
sizeRange: [8, 48]
|
||
series:
|
||
- type: bubble
|
||
encode: {x: age_s, y: inc_s, size: pop_s}
|
||
name: Students
|
||
fill: "#5470c6"
|
||
- type: bubble
|
||
encode: {x: age_w, y: inc_w, size: pop_w}
|
||
name: White-collar
|
||
fill: "#91cc75"
|
||
- type: bubble
|
||
encode: {x: age_m, y: inc_m, size: pop_m}
|
||
name: Management
|
||
fill: "#ee6666"
|
||
```
|
||
|
||
**Candlestick chart (with MA5 overlay line)**
|
||
|
||
```yaml
|
||
- elementId: c6
|
||
elementType: chart
|
||
bounds: [50, 80, 700, 420]
|
||
title: Stock Price Trend
|
||
data:
|
||
cols: [date, open, high, low, close, ma5]
|
||
rows:
|
||
- ["2024-01-01", 100, 110, 95, 108, null]
|
||
- ["2024-01-02", 108, 115, 105, 112, null]
|
||
- ["2024-01-03", 112, 118, 109, 116, null]
|
||
- ["2024-01-04", 116, 120, 110, 113, null]
|
||
- ["2024-01-05", 113, 117, 108, 115, 112.8]
|
||
yAxis: {title: Price}
|
||
series:
|
||
- type: candlestick
|
||
encode: {x: date, open: open, close: close, low: low, high: high}
|
||
upBars: {fill: "#ee6666"}
|
||
downBars: {fill: "#5470c6"}
|
||
- type: line
|
||
encode: {x: date, y: ma5}
|
||
name: MA5
|
||
smooth: true
|
||
width: 2
|
||
lineColor: "#fac858"
|
||
```
|
||
|
||
**Waterfall chart**
|
||
|
||
```yaml
|
||
- elementId: c9
|
||
elementType: chart
|
||
bounds: [50, 80, 700, 380]
|
||
title: Cash Flow Waterfall
|
||
data:
|
||
cols: [phase, amount, total]
|
||
rows:
|
||
- [Opening balance, 500, true]
|
||
- [Sales revenue, 300, null]
|
||
- [Operating expenses, -180, null]
|
||
- [Taxes, -60, null]
|
||
- [Closing balance, 560, true]
|
||
series:
|
||
- type: waterfall
|
||
encode: {x: phase, y: amount, isTotal: total}
|
||
totalBars: {fill: "#5470c6"}
|
||
increaseBars: {fill: "#91cc75"}
|
||
decreaseBars: {fill: "#ee6666"}
|
||
dataLabels: {show: true}
|
||
```
|
||
|
||
**Heatmap**
|
||
|
||
```yaml
|
||
- elementId: c10
|
||
elementType: chart
|
||
bounds: [50, 80, 700, 420]
|
||
title: User Activity Heatmap
|
||
data:
|
||
cols: [hour, day, count]
|
||
rows:
|
||
- ["00:00", Mon, 5]
|
||
- ["00:00", Tue, 8]
|
||
- ["06:00", Mon, 22]
|
||
- ["12:00", Mon, 45]
|
||
series:
|
||
- type: heatmap
|
||
encode: {x: hour, y: day, value: count}
|
||
colorScheme: ["#ffffff", "#5470c6"]
|
||
colorScale: {domain: [0, 50]}
|
||
```
|
||
|
||
**Treemap (with hierarchy)**
|
||
|
||
```yaml
|
||
- elementId: c11
|
||
elementType: chart
|
||
bounds: [50, 80, 700, 420]
|
||
title: Budget Allocation
|
||
data:
|
||
cols: [dept, parentDept, budget]
|
||
rows:
|
||
- [Engineering, null, 1000]
|
||
- [Frontend, Engineering, 400]
|
||
- [Backend, Engineering, 600]
|
||
- [Sales, null, 800]
|
||
series:
|
||
- type: treemap
|
||
encode: {category: dept, value: budget, parent: parentDept}
|
||
fill: ["#5470c6", "#91cc75"]
|
||
```
|
||
|
||
**Sankey diagram**
|
||
|
||
```yaml
|
||
- elementId: c13
|
||
elementType: chart
|
||
bounds: [50, 80, 800, 420]
|
||
title: User Conversion Funnel
|
||
data:
|
||
cols: [from, to, users]
|
||
rows:
|
||
- [Ad campaign, Landing page, 10000]
|
||
- [Ad campaign, Direct search, 4000]
|
||
- [Landing page, Sign-up, 3000]
|
||
- [Landing page, Drop-off, 7000]
|
||
- [Sign-up, First order, 1200]
|
||
- [Direct search, First order, 2000]
|
||
series:
|
||
- type: sankey
|
||
encode: {source: from, target: to, flow: users}
|
||
nodeAlign: justify
|
||
fill: ["#5470c6", "#91cc75", "#fac858", "#ee6666", "#73c0de"]
|
||
```
|
||
|
||
**seriesDefaults + mixing: bar default width + line default smoothing**
|
||
|
||
```yaml
|
||
- elementId: c15
|
||
elementType: chart
|
||
bounds: [50, 80, 700, 420]
|
||
data:
|
||
cols: [month, sales, growth]
|
||
rows:
|
||
- [Jan, 120, 0.10]
|
||
- [Feb, 150, 0.25]
|
||
- [Mar, 180, 0.20]
|
||
yAxis:
|
||
- {title: Sales}
|
||
- {title: Growth rate, label: {numberFormat: "0%"}}
|
||
seriesDefaults:
|
||
bar:
|
||
fill: "#5470c6"
|
||
line:
|
||
smooth: true
|
||
width: 2
|
||
lineColor: "#ee6666"
|
||
barWidth: 0.6
|
||
series:
|
||
- type: bar
|
||
encode: {x: month, y: sales}
|
||
name: Sales
|
||
- type: line
|
||
encode: {x: month, y: growth}
|
||
name: Growth rate
|
||
yAxisIndex: 1
|
||
```
|
||
|
||
**Horizontal bar chart (category axis on the y side)**
|
||
|
||
```yaml
|
||
- elementId: c16
|
||
elementType: chart
|
||
bounds: [50, 80, 600, 360]
|
||
title: Headcount by Department
|
||
data:
|
||
cols: [dept, headcount]
|
||
rows:
|
||
- [Engineering, 120]
|
||
- [Product, 45]
|
||
- [Design, 30]
|
||
- [Operations, 60]
|
||
xAxis: {label: {numberFormat: "#,##0"}}
|
||
yAxis: {label: {fontSize: 12}}
|
||
series:
|
||
- type: bar
|
||
encode: {x: headcount, y: dept}
|
||
fill: "$primary"
|
||
dataLabels: {show: true}
|
||
```
|