概述
在App Inventor 2中实现动态折线图/曲线图有两种主要方案:
| 方案 | 灵活度 | 性能 | 难度 |
|---|---|---|---|
| Chart组件(内置) | ⭐⭐ | 高 | ⭐ |
| ECharts + WebView | ⭐⭐⭐⭐⭐ | 中 | ⭐⭐⭐ |
方案一:内置Chart组件
组件位置
组件面板 → Charts → Chart + LineChart
基本使用
Chart组件需要配合数据源组件使用:
- ChartData — 静态数据
- FileData — 从文件读取数据
- WebData — 从网络API获取数据
- BluetoothData — 从蓝牙获取数据
添加Chart组件
- 在Designer中拖入 Chart 组件
- 设置Chart类型为 Line(折线图)
- 添加 ChartData 组件作为数据源
动态添加数据点
when 按钮1.Click() {
global 值 = TextBox1.Text
ChartData2D1.AddEntry("1", 值)
}
// 带标签的数据点
when 按钮2.Click() {
global 标签 = join("第", 数据计数, "个")
global 值 = TextBox1.Text
ChartData2D1.AddEntry(标签, 值)
}
清除图表数据
when 按钮3.Click() {
ChartData2D1.RemoveDataSource()
}
实时更新图表(配合Clock)
global 数据计数 = 0
when Screen1.Initialize() {
Clock1.TimerInterval = 1000 // 每秒更新
Clock1.TimerEnabled = true
}
when Clock1.Timer() {
global 数据计数 = 数据计数 + 1
global 随机值 = Math.random() * 100
ChartData2D1.AddEntry(join("T", 数据计数), 随机值)
// 限制显示最近20个点
if (数据计数 > 20) {
ChartData2D1.RemoveEntry("0", "0") // dummy
}
}
Chart组件属性
| 属性 | 说明 |
|---|---|
| Type | 图表类型(Line/Bar/Scatter/Area) |
| Description | 图表描述 |
| LabelsFromX | 是否从X轴数据生成标签 |
| GridEnabled | 是否显示网格线 |
| LegendEnabled | 是否显示图例 |
| XFromZero | X轴是否从0开始 |
多条折线
添加多个ChartData组件,每个对应一条线:
when Clock1.Timer() {
global 值1 = 传感器值1
global 值2 = 传感器值2
ChartData2D1.AddEntry("1", 值1) // 线1
ChartData2D2.AddEntry("2", 值2) // 线2
}
方案二:ECharts + WebView
ECharts是百度开源的强大图表库,通过WebView可以在App Inventor中使用。
HTML文件(echarts_dynamic.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<style>
body { margin: 0; background: #fff; }
#chart { width: 100vw; height: 100vh; }
</style>
</head>
<body>
<div id="chart"></div>
<script>
var myChart = echarts.init(document.getElementById('chart'));
var data = [];
var labels = [];
var maxPoints = 30;
var option = {
title: { text: '实时数据监控', left: 'center', textStyle: { fontSize: 16 } },
tooltip: { trigger: 'axis' },
grid: { left: '10%', right: '5%', bottom: '15%', top: '20%' },
xAxis: { type: 'category', data: labels, axisLabel: { fontSize: 10 } },
yAxis: { type: 'value', axisLabel: { fontSize: 10 } },
series: [{
type: 'line',
data: data,
smooth: true,
lineStyle: { color: '#4CAF50', width: 2 },
areaStyle: { color: 'rgba(76,175,80,0.2)' },
itemStyle: { color: '#4CAF50' }
}],
dataZoom: [{ type: 'inside' }]
};
myChart.setOption(option);
setInterval(function() {
var msg = window.AppInventor.getWebViewString();
if (msg && msg !== "") {
try {
var parsed = JSON.parse(msg);
if (parsed.type === "add") {
data.push(parsed.value);
labels.push(parsed.label || "");
if (data.length > maxPoints) {
data.shift();
labels.shift();
}
myChart.setOption({ xAxis: { data: labels }, series: [{ data: data }] });
} else if (parsed.type === "multi") {
// 多条线
option.series = parsed.series;
option.xAxis.data = parsed.labels;
myChart.setOption(option, true);
} else if (parsed.type === "clear") {
data = [];
labels = [];
myChart.setOption({ xAxis: { data: [] }, series: [{ data: [] }] });
}
} catch(e) {}
window.AppInventor.setWebViewString("");
}
}, 100);
</script>
</body>
</html>
App Inventor积木块
global 数据计数 = 0
when Screen1.Initialize() {
WebViewer1.HomeUrl = "file:///android_asset/echarts_dynamic.html"
Clock1.TimerInterval = 500
Clock1.TimerEnabled = true
}
when Clock1.Timer() {
global 数据计数 = 数据计数 + 1
global 值 = Math.random() * 100
global 消息 = join("{\"type\":\"add\",\"label\":\"", 数据计数, "\",\"value\":", 值, "}")
WebViewer1.WebViewString = 消息
}
when 按钮_清除图表.Click() {
WebViewer1.WebViewString = "{\"type\":\"clear\"}"
global 数据计数 = 0
}
多条折线(ECharts)
when 按钮_显示多条线.Click() {
global JSON数据 = "{\"type\":\"multi\",\"labels\":[\"1月\",\"2月\",\"3月\",\"4月\",\"5月\"],\"series\":[{\"type\":\"line\",\"name\":\"温度\",\"data\":[5,8,15,22,28],\"smooth\":true},{\"type\":\"line\",\"name\":\"湿度\",\"data\":[80,70,60,55,50],\"smooth\":true}]}"
WebViewer1.WebViewString = JSON数据
}
实战案例:温度监测折线图
功能需求
- 每2秒采集一次温度(模拟)
- 实时更新折线图
- 显示最高/最低/平均温度
- 可以暂停/继续
积木块代码
global 温度列表 = []
global 时间列表 = []
global 是否运行 = false
global 采集计数 = 0
when Screen1.Initialize() {
WebViewer1.HomeUrl = "file:///android_asset/echarts_dynamic.html"
}
when 按钮_开始.Click() {
global 是否运行 = true
Clock1.TimerInterval = 2000
Clock1.TimerEnabled = true
按钮_开始.Text = "暂停"
}
when Clock1.Timer() {
if (是否运行) {
global 采集计数 = 采集计数 + 1
// 模拟温度数据(20-30度随机波动)
global 温度 = 25 + (Math.random() - 0.5) * 10
global 温度 = formatAsDecimal(温度, 1)
global 时间标签 = Clock1.FormatDateTime(Clock1.Now(), "HH:mm:ss")
addItemsTo(温度列表, 温度)
addItemsTo(时间列表, 时间标签)
// 更新图表
global 消息 = join("{\"type\":\"add\",\"label\":\"", 时间标签, "\",\"value\":", 温度, "}")
WebViewer1.WebViewString = 消息
// 更新统计
global 最高 = math.max(温度列表)
global 最低 = math.min(温度列表)
// AI2没有直接的列表求和积木,这里假设存在相关方法,或者用其他方式展示
标签_统计.Text = join("最高:", 最高, "°C | 最低:", 最低, "°C")
}
}
常见问题
Q1: Chart组件数据多了会卡顿?
内置Chart组件在100+数据点时可能变慢。解决方案:
- 定期清除旧数据
- 使用ECharts方案(性能更好)
- 限制显示的数据点数量
Q2: ECharts图表在WebView中显示不全?
确保HTML中容器尺寸正确:
#chart { width: 100vw; height: 100vh; }
WebView的宽高也需要正确设置。
Q3: 如何实现饼图/柱状图?
ECharts方案中修改series的type:
- 折线图:
type: 'line' - 柱状图:
type: 'bar' - 饼图:
type: 'pie' - 散点图:
type: 'scatter'
Q4: 图表可以导出为图片吗?
ECharts支持导出图片:
var imgData = myChart.getDataURL({ type: 'png' });
window.AppInventor.setWebViewString("IMG:" + imgData);
总结
| 需求 | 推荐方案 |
|---|---|
| 简单折线图 | 内置Chart组件 |
| 实时动态图 | ECharts + WebView |
| 多种图表类型 | ECharts |
| 自定义样式 | ECharts |
| 离线使用 | 内置Chart组件 |
扫码添加客服咨询