图表与画布实验室
Chart、Canvas、DrawingContext 和阈值可视化。
演示 Chart 与 Canvas/DrawingContext:统计图优先 Chart,自定义示意图再用 Canvas。
#预期效果
运行后会出现图表与画布实验室,图表类型、强调色和阈值会联动更新。
#完整示例
已复制
import appui
state = appui.State(
chart_type="bar",
threshold=55.0,
accent="systemBlue",
rows=[
{"month": "Jan", "value": 42},
{"month": "Feb", "value": 64},
{"month": "Mar", "value": 53},
{"month": "Apr", "value": 78},
{"month": "May", "value": 69},
],
)
def set_chart_type(value):
state.chart_type = value
def set_threshold(value):
state.threshold = float(value)
def set_accent(value):
state.accent = value
def drawing_context():
ctx = appui.DrawingContext()
ctx.rounded_rect(
10, 10, 260, 140,
corner_radius=14,
color="secondarySystemBackground",
fill=True,
)
ctx.line(24, 118, 250, 118, color="separator", line_width=1)
ctx.line(24, 28, 24, 118, color="separator", line_width=1)
max_value = max(row["value"] for row in state.rows)
for index, row in enumerate(state.rows):
x = 42 + index * 42
height = 80 * row["value"] / max_value
color = "systemRed" if row["value"] >= state.threshold else state.accent
ctx.rounded_rect(x, 118 - height, 24, height, corner_radius=5, color=color, fill=True)
ctx.fill_text(row["month"], x - 3, 132, color="secondaryLabel", font_size=10)
y = 118 - 80 * state.threshold / max_value
ctx.line(24, y, 250, y, color="systemOrange", line_width=2)
ctx.fill_text("阈值", 212, y - 6, color="systemOrange", font_size=11)
return ctx
def body():
return appui.NavigationStack(
appui.ScrollView(
appui.VStack(
[
appui.Form(
[
appui.Section(
[
appui.Picker(
"图表类型",
selection=state.chart_type,
options=["bar", "line", "area", "point"],
on_change=set_chart_type,
),
appui.ColorPicker(
"强调色",
selection=state.accent,
on_change=set_accent,
),
appui.Slider(
value=state.threshold,
minimum=30,
maximum=90,
step=1,
label="阈值",
on_change=set_threshold,
),
appui.LabeledContent("当前阈值", value=f"{state.threshold:.0f}"),
],
header="配置",
)
]
),
appui.VStack(
[
appui.Text("系统图表").font("headline"),
appui.Chart(
state.rows,
x="month",
y="value",
type=state.chart_type,
color=state.accent,
).frame(height=220),
],
spacing=8,
).padding(horizontal=16),
appui.VStack(
[
appui.Text("Canvas 自定义阈值图").font("headline"),
appui.Canvas(width=280, height=160, context=drawing_context()),
],
spacing=8,
).padding(horizontal=16),
],
spacing=14,
)
).navigation_title("可视化")
)
appui.run(body, state=state, presentation="sheet")
#关键技巧
Chart数据为字典列表,x/y与字段名一致。DrawingContext在渲染函数里按需构造,不要整段上下文存进State;命令需可 JSON 序列化。- 常规统计用
Canvas手绘可维护性差,优先Chart。