图表与画布 API
Chart、Canvas、DrawingContext 和 Path。
本页覆盖 Chart、Canvas、DrawingContext 和 Path。Chart 用系统图表展示结构化数据;Canvas 和 DrawingContext 用命令列表画 2D 图形;Path 用矢量路径命令画自定义形状。
#什么时候用
| 目标 | 首选 API | 说明 |
|---|---|---|
| 柱状、折线、面积、散点图 | Chart | 数据是字典列表,字段由 x、y、series 指定。 |
| 简单 2D 绘图 | Canvas + DrawingContext | 矩形、圆、线、文本、渐变、路径等命令。 |
| 自定义矢量形状 | Path | 三角形、曲线、弧线、可填充或描边的路径。 |
| 实时高频绘制 | Canvas + 稳定命令列表 | 避免每次 body() 重建大量命令。 |
#Chart
Chart(data=None, x='x', y='y', type='bar', color=None, series=None)
| API | 签名 | 分类 |
|---|---|---|
Chart | Chart(data: Optional[Sequence[Dict[str, Any]]] = None, x: str = 'x', y: str = 'y', type: str = 'bar', color: Optional[ColorLike] = None, series: Optional[str] = None) | media |
已复制
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)
#多序列示例
已复制
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 | 签名 | 分类 |
|---|---|---|
Canvas | Canvas(width: float = 300, height: float = 300, commands: Optional[Sequence[Dict[str, Any]]] = None, context: Optional[DrawingContext] = None) | drawing |
已复制
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_rect | fill_rect(x: float, y: float, width: float, height: float, color: ColorLike = 'black') -> Self | DrawingContext |
stroke_rect | stroke_rect(x: float, y: float, width: float, height: float, color: ColorLike = 'black', line_width: float = 1, **kwargs: Any) -> Self | DrawingContext |
fill_circle | fill_circle(cx: float, cy: float, radius: float, color: ColorLike = 'black') -> Self | DrawingContext |
stroke_circle | stroke_circle(cx: float, cy: float, radius: float, color: ColorLike = 'black', line_width: float = 1, **kwargs: Any) -> Self | DrawingContext |
fill_ellipse | fill_ellipse(x: float, y: float, width: float, height: float, color: ColorLike = 'black') -> Self | DrawingContext |
stroke_ellipse | stroke_ellipse(x: float, y: float, width: float, height: float, color: ColorLike = 'black', line_width: float = 1, **kwargs: Any) -> Self | DrawingContext |
line | line(x1: float, y1: float, x2: float, y2: float, color: ColorLike = 'black', line_width: float = 1, **kwargs: Any) -> Self | DrawingContext |
fill_text | fill_text(text: str, x: float, y: float, color: ColorLike = 'black', font_size: float = 16, **kwargs: Any) -> Self | DrawingContext |
fill_path | fill_path(points: Sequence[Tuple[float, float]], color: ColorLike = 'black', close: bool = True) -> Self | DrawingContext |
stroke_path | stroke_path(points: Sequence[Tuple[float, float]], color: ColorLike = 'black', line_width: float = 1, close: bool = False, **kwargs: Any) -> Self | DrawingContext |
arc | arc(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) -> Self | DrawingContext |
rounded_rect | rounded_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) -> Self | DrawingContext |
gradient_rect | gradient_rect(x: float, y: float, width: float, height: float, colors: Optional[Sequence[ColorLike]] = None, vertical: bool = True) -> Self | DrawingContext |
#命令字段
如果直接传 commands,使用下面的公开字段:
op | 主要字段 |
|---|---|
fill_rect / stroke_rect | x、y、w、h、c、lw |
fill_circle / stroke_circle | cx、cy、r、c、lw |
fill_ellipse / stroke_ellipse | x、y、w、h、c、lw |
line | x1、y1、x2、y2、c、lw |
fill_text | t、x、y、c、fs |
fill_path / stroke_path | pts、c、close、lw |
arc | cx、cy、r、sa、ea、c、lw、fill |
rounded_rect | x、y、w、h、cr、c、lw、fill |
gradient_rect | x、y、w、h、colors、vertical |
已复制
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 | 签名 | 分类 |
|---|---|---|
Path | Path(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 | 任意真值 | 闭合路径。 |
已复制
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 Canvas | Chart 负责数据可视化和坐标轴;Canvas 只画你给的图形命令。 |
Canvas vs Path | Canvas 可以混合矩形、圆、文字、渐变;Path 专注一个可填充/描边的矢量形状。 |
Path vs Rectangle / Circle | 常见形状用形状 API;不规则图形才用 Path。 |
Canvas 命令 vs DrawingContext | 直接命令适合从数据生成;DrawingContext 适合手写绘图逻辑。 |
#常见错误
| 错误 | 正确做法 |
|---|---|
每次 body() 都重新生成大量静态命令。 | 静态命令放到模块级常量或函数缓存,状态变化只更新必要数据。 |
Chart 的 x / y 字段名和数据字典不匹配。 | 统一字段名,缺失值先清洗再传入。 |
用 Chart 展示非数值 y 值。 | y 对应字段应是数值。 |
Canvas(width,height) 和外层 .frame(...) 尺寸冲突。 | 两者保持一致,或明确只让外层控制显示尺寸。 |
用 Canvas 手写原生控件。 | 表单、按钮、列表、进度优先用 AppUI 原生控件。 |