PythonIDE Docs
中文
简体中文

图表与画布 API

Chart、Canvas、DrawingContext 和 Path。

本页覆盖 ChartCanvasDrawingContextPathChart 用系统图表展示结构化数据;CanvasDrawingContext 用命令列表画 2D 图形;Path 用矢量路径命令画自定义形状。

#什么时候用

目标首选 API说明
单一柱状、折线、面积、散点图Chart(type=..., x=..., y=...)旧写法继续支持,适合一种图形。
组合图、区间图、饼图、阈值和逐 Mark 样式Chart(marks=[...])一份数据可组合多种系统原生 Mark。
简单 2D 绘图Canvas + DrawingContext矩形、圆、线、文本、渐变、路径等命令。
自定义矢量形状Path三角形、曲线、弧线、可填充或描边的路径。
实时高频绘制Canvas + 稳定命令列表避免每次 body() 重建大量命令。

#Chart

Chart(data=None, x='x', y='y', type='bar', color=None, series=None, *, ...)

API签名分类
ChartChart(data: Optional[Sequence[Dict[str, Any]]] = None, x: str = 'x', y: str = 'y', type: str = 'bar', color: Optional[ColorLike] = None, series: Optional[str] = None, *, x_type: str = 'auto', interpolation: str = 'linear', stacking: str = 'standard', symbol: Optional[str] = None, symbol_size: Optional[float] = None, show_legend: bool = True, show_x_axis: bool = True, show_y_axis: bool = True, y_domain: Optional[Sequence[float]] = None, selection: Any = None, on_selection_change: Optional[Callable[[Any], Any]] = None, on_select: Optional[Callable[[Optional[Dict[str, Any]]], Any]] = None, scrollable_axes: Optional[str] = None, visible_domain: Optional[float] = None, annotation_key: Optional[str] = None, area_opacity: float = 0.3, marks: Optional[Sequence[ChartMark]] = None, selection_x: Optional[str] = None)media

Chart 直接由系统原生图表框架渲染,坐标计算、滚动、选择命中、插值和动画都留在原生端执行;Python 只在数据或用户选择真正变化时收到事件。

参数取值与语义
x_type'auto''category''number''date';日期接受 ISO-8601 字符串或 Unix 时间戳。
interpolation'linear''monotone''cardinal''catmull_rom''step_start''step_center''step_end'
stacking'standard''normalized''center''unstacked'
symbol / symbol_size折线或散点的原生符号及面积;可用 circle、square、triangle、diamond、pentagon、plus、cross、asterisk。
show_legend / show_x_axis / show_y_axis控制系统图例和坐标轴可见性。
y_domain两个数值组成的 [minimum, maximum]
selection标量或 State.bind.<field>;用户在图表中选择 x 值后双向同步。iOS 17+ 支持触摸选择。
on_select接收最接近所选 x 值的原始数据字典。
scrollable_axes / visible_domainiOS 17+ 的原生图表滚动方向和可见 x 域长度。
annotation_key数据字典中用作原生标注文字的字段名。
marks1–32 个 BarMark / LineMark / AreaMark / PointMark / RuleMark / RectangleMark / SectorMark;传入后启用组合 Mark。
selection_x组合 Mark 用于触摸选择、最近行回调和横向可见域的字段名;省略时继续使用 x

旧的 Chart(data, x, y, type, ...) 没有废弃,也不会被偷偷改写成新的用户 API。它仍是单一图形的最短写法;需要组合多个 Mark、区间坐标、环形扇区或每个 Mark 独立样式时,再使用 marks=。两条写法共享同一套系统原生图表渲染、选择和数据更新路径。

python
import appui


def body():
    rows = [
        {"day": "Mon", "value": 3},
        {"day": "Tue", "value": 7},
        {"day": "Wed", "value": 5},
        {"day": "Thu", "value": 9},
    ]

    return appui.NavigationStack(
        appui.Chart(
            data=rows,
            x="day",
            y="value",
            type="bar",
            color="systemBlue",
        )
        .frame(height=240)
        .padding()
        .navigation_title("Chart")
    )


appui.run(body)

#组合 Mark 构造器

API签名分类
ChartField__init__( self, key: str, label: Optional[str] = ..., value_type: str = ..., )公开类型
ChartValue__init__( self, value: Any, label: str = ..., value_type: str = ..., )公开类型
BarMark__init__( self, x: Optional[ChartCoordinate] = ..., y: Optional[ChartCoordinate] = ..., *, x_start: Optional[ChartCoordinate] = ..., x_end: Optional[ChartCoordinate] = ..., y_start: Optional[ChartCoordinate] = ..., y_end: Optional[ChartCoordinate] = ..., series: Optional[ChartCoordinate] = ..., stacking: str = ..., width: Optional[float] = ..., corner_radius: Optional[float] = ..., foreground_style: Optional[ColorLike] = ..., opacity: float = ..., annotation: Optional[ChartCoordinate] = ..., annotation_position: str = ..., )公开类型
LineMark__init__( self, x: ChartCoordinate, y: ChartCoordinate, *, series: Optional[ChartCoordinate] = ..., interpolation: str = ..., line_width: float = ..., dash: Optional[Sequence[float]] = ..., symbol: Optional[str] = ..., symbol_size: Optional[float] = ..., foreground_style: Optional[ColorLike] = ..., opacity: float = ..., annotation: Optional[ChartCoordinate] = ..., annotation_position: str = ..., )公开类型
AreaMark__init__( self, x: ChartCoordinate, y: Optional[ChartCoordinate] = ..., *, y_start: Optional[ChartCoordinate] = ..., y_end: Optional[ChartCoordinate] = ..., series: Optional[ChartCoordinate] = ..., interpolation: str = ..., stacking: str = ..., foreground_style: Optional[ColorLike] = ..., opacity: float = ..., annotation: Optional[ChartCoordinate] = ..., annotation_position: str = ..., )公开类型
PointMark__init__( self, x: ChartCoordinate, y: ChartCoordinate, *, series: Optional[ChartCoordinate] = ..., symbol: str = ..., symbol_size: float = ..., foreground_style: Optional[ColorLike] = ..., opacity: float = ..., annotation: Optional[ChartCoordinate] = ..., annotation_position: str = ..., )公开类型
RuleMark__init__( self, *, x: Optional[ChartCoordinate] = ..., y: Optional[ChartCoordinate] = ..., series: Optional[ChartCoordinate] = ..., line_width: float = ..., dash: Optional[Sequence[float]] = ..., foreground_style: Optional[ColorLike] = ..., opacity: float = ..., annotation: Optional[ChartCoordinate] = ..., annotation_position: str = ..., )公开类型
RectangleMark__init__( self, x_start: ChartCoordinate, x_end: ChartCoordinate, y_start: ChartCoordinate, y_end: ChartCoordinate, *, series: Optional[ChartCoordinate] = ..., corner_radius: Optional[float] = ..., foreground_style: Optional[ColorLike] = ..., opacity: float = ..., annotation: Optional[ChartCoordinate] = ..., annotation_position: str = ..., )公开类型
SectorMark__init__( self, angle: ChartCoordinate, *, series: Optional[ChartCoordinate] = ..., inner_radius: float = ..., outer_radius: float = ..., angular_inset: float = ..., foreground_style: Optional[ColorLike] = ..., opacity: float = ..., annotation: Optional[ChartCoordinate] = ..., annotation_position: str = ..., )公开类型

#坐标值

Mark 的 xyseries、区间端点和 annotation 使用同一套 ChartCoordinate 规则:

写法含义
"sales"当前数据行中的 sales 字段;字符串默认是字段名。
ChartField("sales", label="销售额", value_type="number")带轴标题与类型的字段引用。value_typeautocategorynumberdate
ChartValue(100, label="目标")常量坐标,适合阈值或固定区间。常量字符串必须显式包成 ChartValue,否则会被当成字段名。

ChartValue 接受字符串、有限数字、datedatetime,不接受 bool。日期会以 ISO-8601 传给原生端。

#Mark 能力

Mark必要坐标专属能力
BarMarkx + y,或成对的 x_start/x_endy_start/y_endstacking、宽度、圆角、区间柱。
LineMarkx + y插值、线宽、虚线、系统 symbol。
AreaMarkx + y,或 x + y_start/y_end插值、stacking、范围面积。
PointMarkx + ysymbol 与 symbol_size。
RuleMarkxy,必须且只能选一个阈值线、虚线、标注。
RectangleMarkx_start/x_end/y_start/y_end热力区间、时间窗口、圆角矩形。
SectorMarkangleiOS 17+ 扇区/饼图/环形图,支持内外半径与间隔。

所有 Mark 都支持 seriesforeground_styleopacityannotationannotation_position。标注位置可用 topbottomleadingtrailingoverlay

#组合面积、折线、点和阈值

python
import appui

state = appui.State(selected_day="Wed", detail="")

ROWS = [
    {"day": "Mon", "actual": 7},
    {"day": "Tue", "actual": 11},
    {"day": "Wed", "actual": 9},
    {"day": "Thu", "actual": 14},
]


def selected_row(row):
    state.detail = "未选择" if row is None else f"{row['day']}: {row['actual']}"


def body():
    day = appui.ChartField("day", label="日期", value_type="category")
    actual = appui.ChartField("actual", label="实际值", value_type="number")

    marks = [
        appui.AreaMark(
            day,
            actual,
            interpolation="monotone",
            foreground_style="systemBlue",
            opacity=0.16,
        ),
        appui.LineMark(
            day,
            actual,
            interpolation="monotone",
            line_width=3,
            foreground_style="systemBlue",
        ),
        appui.PointMark(
            day,
            actual,
            symbol="circle",
            symbol_size=34,
            foreground_style="systemBlue",
        ),
        appui.RuleMark(
            y=appui.ChartValue(10, label="目标"),
            dash=[5, 3],
            foreground_style="systemOrange",
            annotation=appui.ChartValue("目标", label="目标"),
            annotation_position="top",
        ),
    ]

    return appui.VStack([
        appui.Chart(
            ROWS,
            marks=marks,
            selection_x="day",
            selection=state.bind.selected_day,
            on_select=selected_row,
            y_domain=[0, 16],
        ).frame(height=260),
        appui.Text(state.detail or "触摸图表查看数据"),
    ], spacing=12).padding()


appui.run(body, state=state)

逐帧拖动、命中测试、插值与过渡动画留在原生端;Python 只接收离散选择结果。marks 是结构描述,不要在手势过程中反复重建它。

#环形图

python
import appui

ROWS = [
    {"name": "照片", "size": 42},
    {"name": "视频", "size": 31},
    {"name": "其他", "size": 27},
]


def body():
    return appui.Chart(
        ROWS,
        marks=[
            appui.SectorMark(
                "size",
                series="name",
                inner_radius=0.55,
                outer_radius=1.0,
                angular_inset=1.5,
            )
        ],
        selection_x="name",
    ).frame(height=260)


appui.run(body)

SectorMark 在 iOS 17+ 使用系统原生扇区;应用仍需为更低系统版本准备可用的替代页面或不显示该图表。

#可滚动、可选择的数值轴

python
import appui

state = appui.State(selected=2.0, detail="")
rows = [
    {"id": "m1", "month": 1.0, "value": 8, "label": "Jan"},
    {"id": "m2", "month": 2.0, "value": 13, "label": "Feb"},
    {"id": "m3", "month": 3.0, "value": 9, "label": "Mar"},
    {"id": "m4", "month": 4.0, "value": 16, "label": "Apr"},
]


def selected_row(row):
    state.detail = row["label"] if row else ""


def body():
    return appui.VStack([
        appui.Chart(
            rows,
            x="month",
            y="value",
            type="line",
            x_type="number",
            interpolation="monotone",
            symbol="circle",
            y_domain=[0, 20],
            selection=state.bind.selected,
            on_select=selected_row,
            scrollable_axes="horizontal",
            visible_domain=3,
            annotation_key="label",
        ).frame(height=240),
        appui.Text(state.detail or "Touch a point"),
    ], spacing=12).padding()


appui.run(body, state=state)

#多序列示例

python
import appui


def body():
    data = [
        {"month": "Jan", "value": 10, "category": "A"},
        {"month": "Jan", "value": 20, "category": "B"},
        {"month": "Feb", "value": 14, "category": "A"},
        {"month": "Feb", "value": 18, "category": "B"},
        {"month": "Mar", "value": 22, "category": "A"},
        {"month": "Mar", "value": 16, "category": "B"},
    ]

    return appui.NavigationStack(
        appui.Chart(
            data=data,
            x="month",
            y="value",
            series="category",
            type="line",
        )
        .frame(height=240)
        .padding()
        .navigation_title("Series")
    )


appui.run(body)

#Canvas

Canvas(width=300, height=300, commands=None, context=None)

API签名分类
CanvasCanvas(width: float = 300, height: float = 300, commands: Optional[Sequence[Dict[str, Any]]] = None, context: Optional[DrawingContext] = None)drawing
python
import appui


def make_context():
    ctx = appui.DrawingContext()
    ctx.gradient_rect(0, 0, 240, 140, colors=["systemBlue", "systemTeal"])
    ctx.rounded_rect(20, 20, 200, 100, corner_radius=16, color="systemBackground", fill=True)
    ctx.fill_text("Canvas", 72, 76, color="label", font_size=22)
    ctx.line(40, 98, 200, 98, color="separator", line_width=2)
    return ctx


def body():
    return appui.NavigationStack(
        appui.Canvas(width=240, height=140, context=make_context())
        .frame(width=240, height=140)
        .padding()
        .navigation_title("Canvas")
    )


appui.run(body)

#DrawingContext 方法

DrawingContext 的每个方法都会向 commands 追加一个公开命令字典,并返回自身,方便链式调用。

API签名所属类型
fill_rectfill_rect(x: float, y: float, width: float, height: float, color: ColorLike = 'black') -> SelfDrawingContext
stroke_rectstroke_rect(x: float, y: float, width: float, height: float, color: ColorLike = 'black', line_width: float = 1, **kwargs: Any) -> SelfDrawingContext
fill_circlefill_circle(cx: float, cy: float, radius: float, color: ColorLike = 'black') -> SelfDrawingContext
stroke_circlestroke_circle(cx: float, cy: float, radius: float, color: ColorLike = 'black', line_width: float = 1, **kwargs: Any) -> SelfDrawingContext
fill_ellipsefill_ellipse(x: float, y: float, width: float, height: float, color: ColorLike = 'black') -> SelfDrawingContext
stroke_ellipsestroke_ellipse(x: float, y: float, width: float, height: float, color: ColorLike = 'black', line_width: float = 1, **kwargs: Any) -> SelfDrawingContext
lineline(x1: float, y1: float, x2: float, y2: float, color: ColorLike = 'black', line_width: float = 1, **kwargs: Any) -> SelfDrawingContext
fill_textfill_text(text: str, x: float, y: float, color: ColorLike = 'black', font_size: float = 16, **kwargs: Any) -> SelfDrawingContext
fill_pathfill_path(points: Sequence[Tuple[float, float]], color: ColorLike = 'black', close: bool = True) -> SelfDrawingContext
stroke_pathstroke_path(points: Sequence[Tuple[float, float]], color: ColorLike = 'black', line_width: float = 1, close: bool = False, **kwargs: Any) -> SelfDrawingContext
arcarc(cx: float, cy: float, radius: float, start_angle: float = 0, end_angle: float = 360, color: ColorLike = 'black', line_width: float = 1, fill: bool = False, **kwargs: Any) -> SelfDrawingContext
rounded_rectrounded_rect(x: float, y: float, width: float, height: float, corner_radius: float = 8, color: ColorLike = 'black', line_width: float = 1, fill: bool = True, **kwargs: Any) -> SelfDrawingContext
gradient_rectgradient_rect(x: float, y: float, width: float, height: float, colors: Optional[Sequence[ColorLike]] = None, vertical: bool = True) -> SelfDrawingContext

#命令字段

如果直接传 commands,使用下面的公开字段:

op主要字段
fill_rect / stroke_rectxywhclw
fill_circle / stroke_circlecxcyrclw
fill_ellipse / stroke_ellipsexywhclw
linex1y1x2y2clw
fill_texttxycfs
fill_path / stroke_pathptsccloselw
arccxcyrsaeaclwfill
rounded_rectxywhcrclwfill
gradient_rectxywhcolorsvertical
python
import appui

ctx = appui.DrawingContext()
ctx.fill_rect(0, 0, 10, 10, color="systemRed")
ctx.stroke_rect(10, 0, 10, 10, color="systemBlue", line_width=2)
ctx.fill_circle(50, 50, 20, color="systemGreen")
ctx.line(0, 100, 100, 100, color="systemOrange", line_width=3)
ctx.fill_text("Hi", 5, 105, color="label", font_size=14)
assert len(ctx.commands) == 5

#Path

Path(commands=None, fill=None, stroke=None, line_width=None)

API签名分类
PathPath(commands: Optional[Sequence[Dict[str, Any]]] = None, fill: Optional[ColorLike] = None, stroke: Optional[ColorLike] = None, line_width: Optional[float] = None)drawing

Path 命令结构:

命令键结构行为
move[x, y]移动当前点。
line[x, y]添加直线。
curve{"to": [x, y], "control1": [x, y], "control2": [x, y]?}二次或三次贝塞尔曲线。
arc{"cx": x, "cy": y, "r": r, "start": deg, "end": deg, "clockwise": bool}弧线。
close任意真值闭合路径。
python
import appui


def body():
    commands = [
        {"move": [40, 10]},
        {"line": [80, 70]},
        {"line": [0, 70]},
        {"close": True},
    ]

    return appui.NavigationStack(
        appui.Path(
            commands=commands,
            fill="systemOrange",
            stroke="label",
            line_width=2,
        )
        .frame(width=100, height=90)
        .padding()
        .navigation_title("Path")
    )


appui.run(body)

#与相邻 API 的区别

API不同点
Chart vs CanvasChart 负责数据可视化和坐标轴;Canvas 只画你给的图形命令。
Canvas vs PathCanvas 可以混合矩形、圆、文字、渐变;Path 专注一个可填充/描边的矢量形状。
Path vs Rectangle / Circle常见形状用形状 API;不规则图形才用 Path
Canvas 命令 vs DrawingContext直接命令适合从数据生成;DrawingContext 适合手写绘图逻辑。

#常见错误

错误正确做法
每次 body() 都重新生成大量静态命令。静态命令放到模块级常量或函数缓存,状态变化只更新必要数据。
Chartx / y 字段名和数据字典不匹配。统一字段名,缺失值先清洗再传入。
把常量字符串直接传给 Mark。字符串表示字段名;常量字符串用 ChartValue("文字")
同时给 AreaMarkyy_start/y_end标量与范围二选一,范围端点必须成对。
RuleMark 同时传 xy只传一个坐标,表示垂直线或水平线。
动态创建超过 32 个 Mark。Mark 描述图层而不是数据点;把大量点放进 data 行。
Chart 展示非数值 y 值。y 对应字段应是数值。
Canvas(width,height) 和外层 .frame(...) 尺寸冲突。两者保持一致,或明确只让外层控制显示尺寸。
Canvas 手写原生控件。表单、按钮、列表、进度优先用 AppUI 原生控件。

#相关文档

文档用途
形状 APIRectangle、RoundedRectangle、Circle、Capsule、Ellipse、Color。
性能指南大数据图表和高频绘制的刷新边界。
UI 模式图表和媒体在 MiniApp 页面中的布局选择。