PythonIDE Docs
中文
简体中文

图表与画布

Chart、Canvas、DrawingContext 和 Path 的使用模式。

Chart 提供系统图表视图,用字典列表作为数据源,指定 x / y 键与 typeCanvas 用绘制命令渲染自定义图形,可用 DrawingContext 链式生成命令,或直接传入 commands 字典列表。Path 则用于矢量路径(move / line / close 等)。


#预期效果

示例会展示柱状/折线图、Canvas 绘制和 Path 图形在页面中的结果。

#概念速览

能力API
统计图Chart(data, x, y, type='bar', color=None, series=None)
画布绘制DrawingContext().fill_rect(...)...Canvas(width, height, context=ctx)
原始命令Canvas(width, height, commands=[{'op': 'fill_rect', ...}, ...])
矢量路径Path(commands=[...], fill=..., stroke=..., line_width=...)

#五种 Chart 类型的直观含义

  • bar:离散类别对比,适合少量枚举横轴(如月份、地区)。
  • line:连续或有序横轴上的趋势;与 series 组合可绘制多条线。
  • area:与折线类似,但填充下方区域,强调「累积感」或占比。
  • point:强调散点分布,适合样本量不大时的离群观察。
  • rule:水平或垂直参考线,适合阈值、平均线等「标线」场景(数据需自行构造为常数 yx)。

#Canvas 两条路径

  1. 声明式DrawingContext 链式调用,最后交给 Canvas(..., context=ctx);可读性高,适合中等数量图元。
  2. 命令列表:直接维护 commands 列表,适合波形、频谱等高频追加场景。

#基础示例

同一组销售数据,切换 Charttype

python
import appui

state = appui.State(chart_kind="bar")

SALES = [
    {"m": "1月", "v": 12},
    {"m": "2月", "v": 19},
    {"m": "3月", "v": 15},
    {"m": "4月", "v": 22},
]


def set_chart_kind(value):
    state.chart_kind = value


def body():
    return appui.NavigationStack(
        appui.VStack(
            [
                appui.Picker(
                    label="图表类型",
                    selection=state.chart_kind,
                    options=["bar", "line", "area", "point", "rule"],
                    on_change=set_chart_kind,
                ).picker_style("segmented"),
                appui.Chart(
                    data=SALES,
                    x="m",
                    y="v",
                    type=state.chart_kind,
                    color="systemBlue",
                ).frame(height=220),
            ],
            spacing=16,
        )
        .padding()
        .navigation_title("Chart 类型")
    )


appui.run(body, state=state)

#进阶示例

多系列折线(series 键)、完整 DrawingContext 演示、Canvas 原始 commandsPath 三角形。

python
import appui

state = appui.State()

MULTI = [
    {"day": "周一", "amount": 3, "team": "A"},
    {"day": "周二", "amount": 5, "team": "A"},
    {"day": "周一", "amount": 4, "team": "B"},
    {"day": "周二", "amount": 2, "team": "B"},
]


def body():
    ctx = appui.DrawingContext()
    ctx.fill_rect(0, 0, 300, 120, color="secondarySystemBackground")
    ctx.stroke_rect(8, 8, 100, 60, color="label", line_width=1)
    ctx.fill_circle(150, 40, 22, color="systemRed")
    ctx.stroke_circle(150, 40, 28, color="systemOrange", line_width=2)
    ctx.fill_ellipse(190, 15, 50, 50, color="systemPurple")
    ctx.stroke_ellipse(190, 15, 50, 50, color="systemYellow", line_width=2)
    ctx.line(10, 100, 290, 100, color="separator", line_width=1)
    ctx.fill_text("DrawingContext", 12, 88, color="label", font_size=14)
    ctx.fill_path([(200, 75), (260, 110), (170, 110)], color="systemGreen", close=True)
    ctx.stroke_path([(20, 70), (60, 95), (40, 50)], color="systemBlue", line_width=2, close=True)
    ctx.arc(80, 95, 18, start_angle=0, end_angle=270, color="systemTeal", line_width=2, fill=False)
    ctx.rounded_rect(
        230, 70, 60, 40,
        corner_radius=10,
        color="systemIndigo",
        line_width=2,
        fill=False,
    )
    ctx.gradient_rect(120, 72, 90, 36, colors=["systemPink", "systemMint"], vertical=True)

    wave_commands = [
        {"op": "fill_rect", "x": 0, "y": 0, "w": 300, "h": 80, "c": "systemGray6"},
    ]
    for i in range(40):
        wave_commands.append(
            {
                "op": "fill_rect",
                "x": i * 7.5,
                "y": 40 + (i % 5) * 6,
                "w": 5,
                "h": 12 + (i % 7) * 2,
                "c": "systemCyan",
            }
        )

    triangle = appui.Path(
        commands=[
            {"move": [40, 20]},
            {"line": [80, 20]},
            {"line": [60, 60]},
            {"close": True},
        ],
        fill="systemYellow",
        stroke="systemOrange",
        line_width=2,
    )

    return appui.NavigationStack(
        appui.ScrollView(
            [
                appui.VStack(
                    [
                        appui.Text("多系列折线 (series)").font("headline"),
                        appui.Chart(
                            data=MULTI,
                            x="day",
                            y="amount",
                            type="line",
                            series="team",
                            color="systemBlue",
                        ).frame(height=200),
                        appui.Text("DrawingContext → Canvas").font("headline"),
                        appui.Canvas(width=300, height=120, context=ctx).corner_radius(12),
                        appui.Text("commands 列表 → Canvas").font("headline"),
                        appui.Canvas(width=300, height=80, commands=wave_commands).corner_radius(8),
                        appui.Text("Path 矢量").font("headline"),
                        triangle.frame(width=120, height=80),
                    ],
                    spacing=20,
                )
                .padding()
            ]
        )
        .navigation_title("图表与画布")
    )


appui.run(body, state=state)

#常见误区

  1. Chart 使用参数名 type=:不是 markx / y 为数据字典中的键名字符串。
  2. Canvas 命令字典的字段:手写 commands 时使用 'op', 'x', 'y', 'w', 'h', 'c' 等键。
  3. Path 命令格式appuiPath 使用 {'move': [x, y]}{'line': [x, y]}{'close': True} 等形式,与 DrawingContextop 字典不同。
  4. 性能:高频更新(如波形)可优先 Canvas + commands 列表局部重建;Chart 更适合中等规模静态或慢变数据。
  5. 系统版本Chart 依赖 iOS 16+,Canvas 依赖 iOS 15+;在过低版本设备上可能空白或降级。
  6. series 与颜色:多系列时 color 仍可作为默认着色提示,具体调色策略由系统图表渲染决定;若发现图例颜色异常,应先检查数据中 series 字段是否稳定存在。

#练习题

  1. Charttype 改为 'rule',用水平参考线标注「目标值」常量(在数据中复制同一 y)。
  2. DrawingContext 绘制一条正弦折线(多点 linestroke_path)。
  3. Pathcommands 改为带 curve / arc 的复杂轮廓(若当前环境支持对应序列化键)。

#附录:最小 CanvasChart

python
import appui

c = appui.Canvas(
    width=120,
    height=40,
    commands=[{"op": "fill_rect", "x": 0, "y": 0, "w": 120, "h": 40, "c": "systemBlue"}],
)
g = appui.Chart(
    data=[{"x": 1, "y": 2}],
    x="x",
    y="y",
    type="point",
    color="systemRed",
)
print(c, g)

#数据字典约定(给 Chart

Chart 期望 datalist[dict],且每个字典至少包含你在 x=y= 上指定的键;若使用 series=,则每个字典还应包含该键以便分组。键名建议使用 ASCII 标识符(如 monthsales),可减少跨端序列化问题。


#DrawingContext 生成的 op 一览

下列为 DrawingContext 常用命令,便于手写 commands 数组时对照:

链式方法op 字段说明
fill_rectfill_rect填充轴对齐矩形
stroke_rectstroke_rect描边矩形
fill_circlefill_circle填充圆
stroke_circlestroke_circle描边圆
fill_ellipsefill_ellipse填充椭圆外接矩形
stroke_ellipsestroke_ellipse描边椭圆
lineline线段
fill_textfill_text文本
fill_pathfill_path多边形填充
stroke_pathstroke_path折线描边
arcarc弧或扇形
rounded_rectrounded_rect圆角矩形
gradient_rectgradient_rect线性渐变矩形