图表与画布
Chart、Canvas、DrawingContext 和 Path 的使用模式。
Chart 提供系统图表视图,用字典列表作为数据源,指定 x / y 键与 type。Canvas 用绘制命令渲染自定义图形,可用 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:水平或垂直参考线,适合阈值、平均线等「标线」场景(数据需自行构造为常数y或x)。
#Canvas 两条路径
- 声明式:
DrawingContext链式调用,最后交给Canvas(..., context=ctx);可读性高,适合中等数量图元。 - 命令列表:直接维护
commands列表,适合波形、频谱等高频追加场景。
#基础示例
同一组销售数据,切换 Chart 的 type。
已复制
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 原始 commands、Path 三角形。
已复制
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)
#常见误区
Chart使用参数名type=:不是mark;x/y为数据字典中的键名字符串。Canvas命令字典的字段:手写commands时使用'op','x','y','w','h','c'等键。Path命令格式:appui的Path使用{'move': [x, y]}、{'line': [x, y]}、{'close': True}等形式,与DrawingContext的op字典不同。- 性能:高频更新(如波形)可优先
Canvas+commands列表局部重建;Chart更适合中等规模静态或慢变数据。 - 系统版本:
Chart依赖 iOS 16+,Canvas依赖 iOS 15+;在过低版本设备上可能空白或降级。 series与颜色:多系列时color仍可作为默认着色提示,具体调色策略由系统图表渲染决定;若发现图例颜色异常,应先检查数据中series字段是否稳定存在。
#练习题
- 将
Chart的type改为'rule',用水平参考线标注「目标值」常量(在数据中复制同一y)。 - 用
DrawingContext绘制一条正弦折线(多点line或stroke_path)。 - 把
Path的commands改为带curve/arc的复杂轮廓(若当前环境支持对应序列化键)。
#附录:最小 Canvas 与 Chart
已复制
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 期望 data 为 list[dict],且每个字典至少包含你在 x=、y= 上指定的键;若使用 series=,则每个字典还应包含该键以便分组。键名建议使用 ASCII 标识符(如 month、sales),可减少跨端序列化问题。
#DrawingContext 生成的 op 一览
下列为 DrawingContext 常用命令,便于手写 commands 数组时对照:
| 链式方法 | op 字段 | 说明 |
|---|---|---|
fill_rect | fill_rect | 填充轴对齐矩形 |
stroke_rect | stroke_rect | 描边矩形 |
fill_circle | fill_circle | 填充圆 |
stroke_circle | stroke_circle | 描边圆 |
fill_ellipse | fill_ellipse | 填充椭圆外接矩形 |
stroke_ellipse | stroke_ellipse | 描边椭圆 |
line | line | 线段 |
fill_text | fill_text | 文本 |
fill_path | fill_path | 多边形填充 |
stroke_path | stroke_path | 折线描边 |
arc | arc | 弧或扇形 |
rounded_rect | rounded_rect | 圆角矩形 |
gradient_rect | gradient_rect | 线性渐变矩形 |