Files
2026-08-06 17:09:51 +08:00

86 KiB
Raw Permalink Blame History

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
  • 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: 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
  2. Inline properties set in <span style="...">
  3. Paragraph properties set in <p style="...">
  4. Style fields set directly on Text.content (distinct from the theme style referenced by style; including color, fontSize, fontFamily, bold, italic, backgroundColor, lineHeight, lineHeightPx, letterSpacing, marginTop)
  5. The TextStyleConfig theme style referenced by Text.content.style
  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
  2. Inline properties set in <span style="...">
  3. Paragraph properties set in <p style="...">
  4. Cell inline fields
  5. The TextStyleConfig referenced by Cell.textStyle (applies only to text fields; does not include fill / border / align)
  6. 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
  7. TableStyleConfig.bodyStyles: applies to data rows other than the first and last rows, cycled by data-row index
  8. TableStyleConfig.cellStyle: 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)
  2. The same-named field for the corresponding type in Chart.seriesDefaults
  3. 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:

  1. Sub-component fontFamily (TitleConfig / LegendConfig / DataLabelConfig / AxisConfig.label / SpokeAxisConfig.label)
  2. Chart.fontFamily
  3. Theme default (Theme or the PPTX master font)

3.3 dataLabels priority chain:

  1. series[i].dataLabels
  2. Chart.dataLabels (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; 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 / 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:

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

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 null inside the array means no border at the corresponding position; a top-level null clears 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 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

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

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.

  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.

  1. 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
  1. 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 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
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 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 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: 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:

# 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
}

ShapeDef fields 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 columnWidths and rowHeights is 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 / 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).

- 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 / 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 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 colsDuplicateColumnError
  • cols contains an empty string → EmptyColumnError
  • rows[i].length !== cols.lengthRowLengthError
  • A column name referenced by encode is not in colsUnknownColumnError
  • 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 (HEX8 or a $xxx theme reference), an object as GradientFill (with type: "gradient"); some types support the (Color | GradientFill)[] array form (cycled by slice/node). Series-level fill does not support ImageFill.
  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 for details).
  4. 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 (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

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 / gridLine of 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: 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

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 rect naming 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 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.

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.

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.

area

series constraint: may be freely mixed with bar / line / scatter / bubble; may also mix with candlestick.

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.

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

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.

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

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.

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.

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.

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.

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)

- 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: appear appears immediately, fade-in fades in, fly-in flies in, zoom-in zooms in, wipe-in wipes in, float-in fades in with a short drift (about 10% of the page height and gentler than fly-in), peek-in slides out from behind the element's own edge mask without fading in, and rise-in rises linearly from below the page without fading in
  • Emphasis: pulse pulses (scales to 110% and rebounds), grow-shrink scales to 150%, spin rotates 360°, teeter rocks from side to side, fill-color changes the fill color and preserves the result, transparency changes the opacity and preserves the result, and color-pulse changes the fill color and then restores the original color
  • Exit: disappear disappears immediately, fade-out fades out, fly-out flies out, zoom-out zooms out, wipe-out wipes out, and float-out fades out with a short drift
  • Path: motion-path moves 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

  • onClick starts a new click group and plays on click
  • withPrevious starts at the same time as the preceding animation
  • afterPrevious starts automatically after the preceding animation ends
  • If the first animation on a page uses withPrevious or afterPrevious, 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 onClick and the rest to withPrevious
  • Use afterPrevious for 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 13 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.