86 KiB
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 likestyle="..."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 - Default presentation sizes: 16:9 →
[960, 540]; 4:3 →[720, 540]. Default poster sizes: 16:9 →[1280, 720]; 9:16 →[720, 1280]; 4:3 →[1280, 960]; 3:4 →[960, 1280]; 1:1 →[1080, 1080] - This specification defines 1px = 1pt (i.e.,
fontSize: 18is 18pt in PPTX) - Element stacking order is determined by the order of the
Page.elementsarray; 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) andlineHeightPx(fixed px) are mutually exclusive; when both are set,lineHeightPxtakes precedence.
1. Text Styles Inside a Text Box
Priority chain:
- Rich-text semantic tags such as
<u>,<sup>,<strong>in Text.content.text - Inline properties set in
<span style="..."> - Paragraph properties set in
<p style="..."> - Style fields set directly on Text.content (distinct from the theme style referenced by
style; includingcolor,fontSize,fontFamily,bold,italic,backgroundColor,lineHeight,lineHeightPx,letterSpacing,marginTop) - The TextStyleConfig theme style referenced by Text.content.style
- 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:
- Rich-text semantic tags such as
<u>,<sup>,<strong>in Cell.text - Inline properties set in
<span style="..."> - Paragraph properties set in
<p style="..."> - Cell inline fields
- The TextStyleConfig referenced by Cell.textStyle (applies only to text fields; does not include
fill/border/align) - Position-category styles of TableStyleConfig
- On row vs column conflicts, TableStyleConfig.rowOverColumn decides the winner; default
true= row wins - Row categories:
TableStyleConfig.firstRowStyle/TableStyleConfig.lastRowStyle - Column categories:
TableStyleConfig.firstColumnStyle/TableStyleConfig.lastColumnStyle
- On row vs column conflicts, TableStyleConfig.rowOverColumn decides the winner; default
- TableStyleConfig.bodyStyles: applies to data rows other than the first and last rows, cycled by data-row index
- TableStyleConfig.cellStyle: the baseline cell style for the whole table
- 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:
- A series' explicit
fill/lineColor/areaColor(field names differ per type; see Color Mechanism) - The same-named field for the corresponding type in Chart.seriesDefaults
- The Theme.colors theme color cycle (colors are picked in the order the series appear in the array)
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.
3.2 Font priority chain:
- Sub-component
fontFamily(TitleConfig / LegendConfig / DataLabelConfig / AxisConfig.label / SpokeAxisConfig.label) - Chart.fontFamily
- Theme default (Theme or the PPTX master font)
3.3 dataLabels priority chain:
series[i].dataLabels- Chart.dataLabels (global default)
- Not shown (equivalent to
show: false)
Sub-fields follow a one-level shallow merge:
series.dataLabelsonly overrides the sub-fields it explicitly provides; unprovided ones fall back from Chart.dataLabels; if neither provides them, the per-type default applies (see dataLabels.content value quick reference).
3.4 seriesDefaults merge rules:
Chart.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/ gradientfillobjects, 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 typeandencodeare not allowed inside seriesDefaults- Only multi-series types support seriesDefaults:
bar / line / area / scatter / bubble / candlestick / radar
Counterintuitive example:
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= offtrue= on with default configuration- Object
{...}= on + custom configuration
The only exception: scatter.marker 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:
- Fully self-contained: all referenced files must be located inside the folder containing the
.pptdfile; referencing files outside the directory is not allowed - Only relative paths are supported (relative to the directory containing the
.pptdfile):- The
pageslist in.pptd:pages/1_cover.page - Image paths in
.page:media/image1.jpg
- The
- Media supports URLs:
Image.src, and the ImageFill.src ofbackground/fill, may behttps://...(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
type Color = string;
Supports opaque HEX6 (
#RRGGBB), alpha HEX8 (#RRGGBBAA), and Theme.colors theme color references (e.g.$primary)
FontFamily
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 for the list of available fonts
Alignment
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
type LineStyle = "solid" | "dash" | "dot";
Border
interface Border {
style?: LineStyle; // default: "solid"
width?: number; // default: 1
color?: Color; // default: "#000000"
}
BorderSpec
Cell and CellStyle support an array form of Border to set the four side borders separately
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
nullinside the array means no border at the corresponding position; a top-levelnullclears everything.
Shadow
interface Shadow {
blur: number; // blur radius
color: Color;
offset?: [number, number]; // default: [0, 0]; [x, y] offset
}
ColorStop
interface ColorStop {
position: number; // constraint: [0, 1]
color: Color;
}
ImageFit / ImageCrop
interface ImageFit {
mode: "fill" | "contain" | "cover";
}
interface ImageCrop {
left?: number;
top?: number;
right?: number;
bottom?: number;
}
Constraint: the four fields of
ImageCropare 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 ensureleft + right < 1andtop + 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
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.angletakes values in[0, 360);0means 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 eachfit.modevalue are consistent with the "rendering logic" discussion in the Image section.
Examples:
# 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
interface Presentation {
version: "v2"; // required, fixed to "v2" (version identifier)
title?: string; // default: no title
customFonts?: CustomFont[]; // default: no custom fonts are loaded
size: [number, number]; // [width, height]; see "Coordinate System and Units" for PPT and poster recommendations
theme?: Theme;
pages: string[]; // list of relative paths to page files, e.g. "pages/cover.page"
}
Example:
version: v2
title: Annual Work Summary
size: [960, 540]
customFonts:
- family: Noto Serif SC
src: "https://fonts.googleapis.com/css2?family=Noto+Serif+SC"
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 field | $primary |
textStyles |
TextContent.style / Cell.textStyle | $title |
tableStyles |
Table.style | $default |
interface Theme {
colors?: Record<string, Color>;
textStyles?: Record<string, TextStyleConfig>;
tableStyles?: Record<string, TableStyleConfig>;
}
TextStyleConfig
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 for details)
CellStyle
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 for details)
TableStyleConfig
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/lastColumnStylemean 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
CustomFont
interface CustomFont {
family: string; // font family name for use in fontFamily fields
src: string; // Google Fonts CSS URL; format: https://fonts.googleapis.com/css2?family=Noto+Serif+SC; only the default weight is supported — weight selection is not supported
}
4. Page Files (.page)
Page
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
animations?: Animation[]; // default: none; orchestrated in array order; see [Animations](#6-animations)
}
Example:
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.
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)
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
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:
# 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 rules.
- 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-spacingon<p>; to set letter spacing uniformly, usecontent.letterSpacingorCell.letterSpacing.
- 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 |
- Inline styles (only
<span>supports them)
Styles apply only to the text inside that <span>.
| Property | Description | Values | Example |
|---|---|---|---|
color |
Text 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 (HEX6 / HEX8 / theme reference) | <span style="background-color:$accent">…</span> |
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):
≡
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
colorandfont-sizestyles from its context; other text styles are not passed through. - A
<p>tag can wrap a LaTeX formula to control the alignment
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)
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 useviewBoxandpathto define a custom shape; these two parameters have no effect whenshapeNameis notcustom
The
adjustmentsparameters: reuse the parameter order and quantity defined by OOXML; see ./shapes.md for value constraints.
Note
:
shapedoes 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 theM / L / H / V / C / S / Q / A / Zcommands.- 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
boundsresizes 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, requireviewBoxW : viewBoxH = bounds.w : bounds.h.
Common shapes
See 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:
# 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)
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:
pointsneeds 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 withinviewBox. 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, requireviewBoxW : viewBoxH = bounds.w : bounds.h.
Examples:
# 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)
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
}
ShapeDeffields correspond one-to-one with the shape fields of the Shape element; for detailed conventions (adjustments values and angle conversion, custom path rules, hollow rules, common shape table), see the 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:
- 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)
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:
- elementId: icon-1
elementType: icon
bounds: [100, 100, 48, 48]
iconName: "fas:lightbulb"
fill: {type: solid, color: "$primary"}
Table (table)
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 element.
Constraint: each item of
columnWidthsandrowHeightsis within[0, 1], and the elements of each sum to 1.
Cell
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):
- 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/colSpandeclare the merge range; cells covered by the merged region are omitted from therowsarray, with nonullplaceholder 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).
- 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
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/shadowcontrol 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 element.
ChartData
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→DuplicateColumnErrorcolscontains an empty string →EmptyColumnErrorrows[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,NonNumericValueErroris raisedHow 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
- The
filltype of series:Color | GradientFill; a string is treated as a solid Color (HEX8 or a$xxxtheme reference), an object as GradientFill (withtype: "gradient"); some types support the(Color | GradientFill)[]array form (cycled by slice/node). Series-level fill does not support ImageFill. - 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. - Conditionally effective top-level fields:
xAxis/yAxis/barWidth/barGap/categoryGap/spokeAxisare coordinate-system-level configurations, conditionally effective based on theseries[].typeset (see 5.3 Applicability of chart top-level fields for details). - Color theme reference scope: all fields of type
Color(including every Color position inside nested arrays and objects) support$xxxtheme references — e.g.upBars: {fill: "$success"},colorScheme: ["$bg", "$primary"],fill: ["$primary", "$accent"]. - 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 fromfill/border/shadowof ElementBase (where omission means not applied).
bar / waterfall direction: determined by the axis type — vertical (default) when
xAxis.type === "category", horizontal whenyAxis.type === "category".axis.typeis 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 withaxis.type: "category". In the horizontal case,encode.xreferences the numeric column andencode.ythe category column, andnumberFormatis 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
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 / LegendConfig / DataLabelConfig / AxisConfig.label / SpokeAxisConfig.label; for the font priority chain, see §3.2.
LineStyleConfig
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/gridLineof AxisConfig / SpokeAxisConfig.
TitleConfig
interface TitleConfig extends TextStyle {
text: string; // required
// fontSize auto-adapts to chart size by default
}
LegendConfig
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) |
legend: falseorlegend: {show: false}turns it off, effective for all 13 types;legend: trueor the object form only has a visual effect for the types marked applicable in the table above.
DataLabelConfig
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
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
rectnaming is consistent with the shapes.md shape library.
AxisConfig
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
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.
SpokeAxisConfig
Used only by radar series.
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 / area / radar.
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
nullHandlingvalues, 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 withcandlestick; no limit on the number of bar series in the same chart.
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 withcandlestick.
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
fillis not provided; for the other curve-class fields, see LinearSeriesBase.
area
series constraint: may be freely mixed with
bar / line / scatter / bubble; may also mix withcandlestick.
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
stackare 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 setstackmust use the same value (all"value"/ all"percent"/ all"stream"); mixing raisesStackModeMismatchError; series withoutstackdisplay independently. If multiple independent stack groups are needed, split them into multiple chart elements.
streamapplies only to area:valuenormalization + 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.
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.
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;linearmakes the radius proportional to size;logsuits 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 ownx/y/sizecolumn triple.
candlestick
series constraint: may only mix with
bar / line / area(common usage: candlestick body + a line overlaying the MA moving average).
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 inrows, 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
seriesarray may only have 1 element, and may not coexist with other types.
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.
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, shared by multiple series.
Dimension-column sharing constraint: all radar series within the same chart must reference the same
categorycolumn (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: theencode.categoryof all radar series must be identical.
waterfall
series constraint: the
seriesarray may only have 1 element, and may not coexist with other types.
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 categoriestotalBars/increaseBars/decreaseBarsby isTotal and the sign of y; all total columns (opening/subtotal/closing) sharetotalBars.isTotal semantics: it may appear at any position (first row / middle subtotal / last row are all legal); every
isTotal=trueis 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 outputsWaterfallTotalMismatchWarning(the first-row total column's y is defined directly). TheisTotalcolumn only accepts bool or null; the string"true"or the number1both raiseInvalidValueError.
heatmap
series constraint: the
seriesarray may only have 1 element, and may not coexist with other types.
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:
colorSchemeserves as gradient endpoints, interpolated percolorScale.type:
linear:colorSchemelength ≥ 2, interpolated between the endpoints;diverging:colorSchemelength = 3 (low / mid / high); the midpoint is determined by the middle ofdomain, 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 inrowsare 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 bycolorbar;colorbar: falseturns off the color-scale bar.
treemap
series constraint: the
seriesarray may only have 1 element, and may not coexist with other types.
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
seriesarray may only have 1 element, and may not coexist with other types.
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
seriesarray may only have 1 element, and may not coexist with other types.
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
sourceandtargetcolumns are deduplicated and arranged in topological order. Whenfillis 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/yfor the horizontal/vertical axes; non-Cartesian category fields are uniformlycategory(pie/radar/treemap/sunburst); sankey graph edge endpoints usesource/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
yAxisarray +yAxisIndex, horizontal charts use anxAxisarray +xAxisIndex. When any series usesxAxisIndex > 0/yAxisIndex > 0, the correspondingxAxis/yAxismust 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)
- 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)
- 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)
- 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)
- 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)
- 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
- 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
- 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)
- 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
- 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
- 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)
- 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}
6. Animations
The optional animations array can be used to orchestrate animations for elements on the current page. animations is a page-level field and references elements in the page's elements array through elementId. Array order determines the sequence relationships among animations, while trigger determines when each animation starts.
type AnimationEffect =
| "appear" | "fade-in" | "fly-in" | "zoom-in" | "wipe-in" | "float-in" | "peek-in" | "rise-in"
| "pulse" | "grow-shrink" | "spin" | "teeter" | "fill-color" | "transparency" | "color-pulse"
| "disappear" | "fade-out" | "fly-out" | "zoom-out" | "wipe-out" | "float-out"
| "motion-path";
type AnimationTrigger = "onClick" | "withPrevious" | "afterPrevious";
type AnimationDirection = "up" | "down" | "left" | "right";
type AnimationEasing = "linear" | "ease-in" | "ease-out" | "ease-in-out";
interface Animation {
elementId: string; // required; must reference an existing element on this page
effect: AnimationEffect; // required; animation effect
trigger?: AnimationTrigger; // default: "onClick"
direction?: AnimationDirection; // default: "up"; only used by fly/wipe/peek/float effects
durationMs?: number; // default: the default duration of the corresponding effect; constraint: > 0
delayMs?: number; // default: 0; constraint: >= 0
easing?: AnimationEasing; // default: "linear"
repeat?: number; // default: 1; constraint: positive integer
path?: string; // required when using motion-path
color?: string; // required for fill-color / color-pulse; 6-digit HEX, with optional #
amount?: number; // required for transparency; target opacity; constraint: [0, 1]
}
Example:
elements:
- elementId: title
elementType: text
bounds: [60, 40, 600, 60]
content: {fontSize: 40, text: Title}
- elementId: photo
elementType: image
bounds: [60, 140, 400, 300]
src: media/pic.png
animations:
- elementId: title
effect: fade-in
trigger: onClick
- elementId: photo
effect: fly-in
direction: up
trigger: withPrevious
- elementId: photo
effect: pulse
trigger: afterPrevious
Effects
- Entrance:
appearappears immediately,fade-infades in,fly-inflies in,zoom-inzooms in,wipe-inwipes in,float-infades in with a short drift (about 10% of the page height and gentler thanfly-in),peek-inslides out from behind the element's own edge mask without fading in, andrise-inrises linearly from below the page without fading in - Emphasis:
pulsepulses (scales to 110% and rebounds),grow-shrinkscales to 150%,spinrotates 360°,teeterrocks from side to side,fill-colorchanges the fill color and preserves the result,transparencychanges the opacity and preserves the result, andcolor-pulsechanges the fill color and then restores the original color - Exit:
disappeardisappears immediately,fade-outfades out,fly-outflies out,zoom-outzooms out,wipe-outwipes out, andfloat-outfades out with a short drift - Path:
motion-pathmoves along a path
| Effect | Default duration |
|---|---|
fade-in, fade-out, fly-in, fly-out, zoom-in, zoom-out, wipe-in, wipe-out, float-in, float-out, peek-in |
500ms |
pulse |
600ms |
grow-shrink, spin, fill-color, transparency, color-pulse |
2000ms |
teeter, rise-in |
1000ms |
motion-path |
2000ms |
appear, disappear |
Instantaneous; ignores durationMs |
Parameterized Emphasis Effects
| Effect | Parameter | Visual behavior |
|---|---|---|
fill-color |
color, required |
Transitions the fill from its current color to color and preserves the target color after the animation ends |
transparency |
amount, required |
Transitions opacity from its current value to amount and preserves the target opacity after the animation ends; 0 is fully transparent and 1 is fully opaque |
color-pulse |
color, required |
Transitions the fill color to color, then restores the original color without preserving the intermediate state |
animations:
# Highlight a process step: change the fill to amber and preserve it
- elementId: step-2
effect: fill-color
color: "#F59E0B"
trigger: onClick
# Dim a secondary element: reduce its opacity to 30%
- elementId: bg-decoration
effect: transparency
amount: 0.3
trigger: withPrevious
# Emphasize the current card: pulse red once, then restore the original color
- elementId: card-current
effect: color-pulse
color: "#EF4444"
trigger: afterPrevious
# Bring body text in gently
- elementId: body-text
effect: float-in
direction: up
easing: ease-out
Triggers and Groups
onClickstarts a new click group and plays on clickwithPreviousstarts at the same time as the preceding animationafterPreviousstarts automatically after the preceding animation ends- If the first animation on a page uses
withPreviousorafterPrevious, the group beginning with it plays automatically when the page is entered - To make multiple elements enter together after a click, set the first animation to
onClickand the rest towithPrevious - Use
afterPreviousfor animations that should play sequentially
direction
direction indicates the travel direction or wipe progression direction. For entrance effects, up means entering upward from below the page; for exit effects, up means leaving the page upward. This field only affects fly/wipe/peek/float effects and is ignored by other effects. Float effects support only up and down; rise-in does not use this field and always rises from below the page.
motion-path
path is an SVG path string. Path coordinates are offsets relative to the element's current position, measured in page px. The path must start with M 0 0, contain only one subpath, and may use L (line), Q / C (curves), and Z (close). For example, M 0 0 L 200 -100 moves the element 200px to the right and 100px upward.
The same element may define multiple animations in array order. Keep each page to 1–3 animation groups when possible, prefer simple effects such as fade, fly, and zoom, and avoid applying multiple emphasis effects to the same element in succession.