周趋势分析-Cordova&openharmony折线视图设计
本文介绍了如何在Cordova Web层和OpenHarmony ArkTS插件中实现周趋势分析功能。主要内容包括: 通过HTML/CSS构建周趋势分析页面框架,包含周导航按钮、Canvas图表区和统计摘要区 使用JavaScript实现数据加载和聚合: 按周计算日期范围 将每日喝水记录聚合为7天数据 处理无数据日期(默认为0) Canvas图表绘制技术: 坐标轴绘制 折线图绘制算法 数据点标记

一、功能概述
喝水习惯的形成往往需要观察一周内的变化趋势。"周趋势分析"模块让用户能够直观看到过去 7 天的喝水量变化,从而发现自己的规律、调整计划。本篇文章围绕"周趋势分析"展开,介绍如何在 Cordova Web 层 通过 Canvas 或简单的 SVG 绘制折线图,并通过 OpenHarmony ArkTS 插件 提供原生图表渲染能力。
我们继续采用"一段代码一段说明"的结构,通过 HTML/JavaScript 和 ArkTS 示例,构建一条完整的数据可视化动线。
二、Web 端周趋势分析界面
<div id="weekly-trend-page" class="page page-weekly-trend">
<h1>周趋势分析</h1>
<div class="trend-controls">
<button id="btn-prev-week" class="btn-secondary">上一周</button>
<span id="week-label" class="text-label">本周</span>
<button id="btn-next-week" class="btn-secondary">下一周</button>
</div>
<canvas id="trend-canvas" width="800" height="400"></canvas>
<div id="trend-summary" class="summary-box"></div>
</div>
这段 HTML 定义了周趋势分析页面的基本结构。顶部的导航按钮允许用户切换不同的周,trend-canvas 用于绘制折线图,trend-summary 展示该周的统计摘要(如平均喝水量、最高值等)。
.page-weekly-trend {
padding: 16px 24px;
}
.trend-controls {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.text-label {
flex: 1;
text-align: center;
font-weight: bold;
}
#trend-canvas {
border: 1px solid #555;
margin-bottom: 16px;
background: #1f2937;
}
.summary-box {
background: #374151;
padding: 12px;
border-radius: 4px;
font-size: 14px;
}
CSS 为页面添加布局和样式。Canvas 元素设置了固定的宽高,背景色与深色主题一致,便于绘制图表。
三、加载周数据并绘制折线图
async function loadWeeklyTrend(weekOffset = 0) {
const today = new Date();
const startDate = new Date(today);
startDate.setDate(today.getDate() - today.getDay() + weekOffset * 7);
startDate.setHours(0, 0, 0, 0);
const endDate = new Date(startDate);
endDate.setDate(startDate.getDate() + 6);
endDate.setHours(23, 59, 59, 999);
const records = await db.getRecordsByDateRange(startDate, endDate);
const dailyData = aggregateDailyData(records, startDate);
renderTrendChart(dailyData);
renderTrendSummary(dailyData);
updateWeekLabel(startDate);
}
loadWeeklyTrend 函数是周趋势分析的核心数据加载函数。它首先获取当前日期,然后根据 weekOffset 参数(周偏移量)计算目标周的起始日期。通过 setDate(today.getDate() - today.getDay() + weekOffset * 7) 这行代码,我们计算出该周的周一日期。例如,如果 weekOffset 为 0,表示当前周;为 -1 表示上一周;为 1 表示下一周。
接着,我们设置起始日期的时间为 00:00:00,确保从该天的最开始开始计算。然后计算结束日期为起始日期加 6 天,时间设为 23:59:59,这样就覆盖了整个周的所有时间。
从 IndexedDB 中查询该日期范围内的所有喝水记录后,通过 aggregateDailyData 函数将这些记录按日期分组,得到每天的总喝水量。最后调用 renderTrendChart 绘制折线图,renderTrendSummary 展示统计摘要,updateWeekLabel 更新页面上显示的周日期范围。
function aggregateDailyData(records, startDate) {
const dailyMap = new Map();
for (let i = 0; i < 7; i++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i);
const key = date.toISOString().split('T')[0];
dailyMap.set(key, 0);
}
records.forEach((r) => {
const key = new Date(r.timestamp).toISOString().split('T')[0];
if (dailyMap.has(key)) {
dailyMap.set(key, dailyMap.get(key) + r.amount);
}
});
return Array.from(dailyMap.values());
}
aggregateDailyData 函数的作用是将原始的喝水记录数据按日期进行聚合。首先,我们创建一个 Map 对象 dailyMap,用于存储每天的喝水总量。接着,通过一个循环为该周的每一天(共 7 天)初始化一个 0 值。这一步很重要,因为即使某天没有喝水记录,我们也需要在图表上显示为 0,这样才能保证图表的完整性和连续性。
然后,我们遍历所有的喝水记录。对于每条记录,我们提取其时间戳中的日期部分(格式为 YYYY-MM-DD),然后检查这个日期是否在我们的 dailyMap 中。如果存在,我们就将该记录的喝水量累加到对应日期的总量上。这样,如果某天有多条记录,它们的喝水量会被累加在一起。
最后,我们使用 Array.from(dailyMap.values()) 将 Map 中的所有值转换为一个数组,这个数组就是按顺序排列的 7 天的喝水量数据,可以直接用于绘制图表。
function renderTrendChart(dailyData) {
const canvas = document.getElementById('trend-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
const padding = 40;
// 清空画布
ctx.fillStyle = '#1f2937';
ctx.fillRect(0, 0, width, height);
// 绘制坐标轴
ctx.strokeStyle = '#555';
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, height - padding);
ctx.lineTo(width - padding, height - padding);
ctx.stroke();
// 绘制折线
const maxValue = Math.max(...dailyData, 1);
const pointSpacing = (width - 2 * padding) / (dailyData.length - 1 || 1);
ctx.strokeStyle = '#60a5fa';
ctx.lineWidth = 2;
ctx.beginPath();
dailyData.forEach((value, index) => {
const x = padding + index * pointSpacing;
const y = height - padding - (value / maxValue) * (height - 2 * padding);
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
// 绘制数据点
ctx.fillStyle = '#60a5fa';
dailyData.forEach((value, index) => {
const x = padding + index * pointSpacing;
const y = height - padding - (value / maxValue) * (height - 2 * padding);
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.fill();
});
}
renderTrendChart 函数使用 Canvas 2D API 绘制一条完整的折线图。首先,我们获取 Canvas 元素和其 2D 绘图上下文,设置画布的宽高和内边距(padding)。内边距用于在画布边缘留出空间,用于绘制坐标轴和标签。
接着,我们清空整个画布,填充深色背景色(#1f2937),这样可以清除之前的绘图内容。然后绘制坐标轴:从左上角(padding, padding)向下绘制一条竖线到左下角(padding, height - padding),再向右绘制一条横线到右下角(width - padding, height - padding),形成一个 L 形的坐标轴。
数据的关键处理在于归一化。我们找出数据中的最大值,然后将所有数据点的 y 坐标按照 (value / maxValue) * (height - 2 * padding) 的公式计算。这样做的好处是,无论数据的绝对值是多少,都能自动适应画布的高度,充分利用画布空间。
计算点的水平间距:pointSpacing = (width - 2 * padding) / (dailyData.length - 1),这样 7 个数据点会均匀分布在画布的宽度上。
然后我们绘制折线。对于第一个点,使用 moveTo 移动到该点;对于后续的点,使用 lineTo 连接到该点。这样就形成了一条连贯的折线。
最后,我们在每个数据点上绘制一个小圆点(半径为 4),这样用户可以清楚地看到每个数据点的位置。整个图表就完成了,用户可以直观地看到一周内的喝水量变化趋势。
function renderTrendSummary(dailyData) {
const sum = dailyData.reduce((a, b) => a + b, 0);
const avg = Math.round(sum / dailyData.length);
const max = Math.max(...dailyData);
const min = Math.min(...dailyData);
const summaryDiv = document.getElementById('trend-summary');
if (!summaryDiv) return;
summaryDiv.innerHTML = `
<div>本周总量: ${sum} ml | 平均: ${avg} ml | 最高: ${max} ml | 最低: ${min} ml</div>
`;
}
renderTrendSummary 函数用于计算和展示该周的统计摘要信息。首先,我们使用 reduce 方法计算所有 7 天喝水量的总和。然后计算平均值,通过将总和除以天数,并使用 Math.round 四舍五入到整数。接着,我们使用 Math.max 和 Math.min 分别找出该周的最高喝水量和最低喝水量。
这些统计数据能够帮助用户快速了解自己这一周的喝水情况:总量反映了整周的喝水努力程度,平均值显示了日均喝水量,最高值和最低值则反映了喝水量的波动范围。最后,我们将这些数据格式化为易读的字符串,并将其插入到摘要框的 HTML 中。
四、周导航与事件绑定
let currentWeekOffset = 0;
function updateWeekLabel(startDate) {
const endDate = new Date(startDate);
endDate.setDate(startDate.getDate() + 6);
const label = document.getElementById('week-label');
if (label) {
label.textContent = `${startDate.toLocaleDateString()} - ${endDate.toLocaleDateString()}`;
}
}
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('btn-prev-week')?.addEventListener('click', () => {
currentWeekOffset--;
loadWeeklyTrend(currentWeekOffset);
});
document.getElementById('btn-next-week')?.addEventListener('click', () => {
currentWeekOffset++;
loadWeeklyTrend(currentWeekOffset);
});
loadWeeklyTrend(0);
});
这段代码实现了周导航的核心逻辑。我们使用全局变量 currentWeekOffset 来跟踪当前显示的是哪一周。初始值为 0,表示当前周。
updateWeekLabel 函数用于更新页面上显示的周日期范围。它接收起始日期作为参数,然后计算该周的结束日期(起始日期加 6 天),最后将起始日期和结束日期格式化为本地日期字符串,并更新页面上的标签。
在 DOMContentLoaded 事件中,我们为"上一周"和"下一周"按钮绑定点击事件。当用户点击"上一周"按钮时,currentWeekOffset 减 1,然后调用 loadWeeklyTrend 重新加载数据;当点击"下一周"按钮时,currentWeekOffset 加 1。这样用户就可以方便地在不同的周之间切换,查看历史数据或未来的计划。
最后,在页面加载完成时,我们调用 loadWeeklyTrend(0) 加载当前周的数据,这样用户打开页面时就能立即看到当前周的趋势分析。
五、通过 Cordova 同步周趋势数据到原生层
function syncWeeklyTrendToNative(dailyData) {
if (!window.cordova) {
console.warn('[WeeklyTrend] cordova not ready, skip native sync');
return;
}
cordova.exec(
() => {
console.info('[WeeklyTrend] sync success');
},
(err) => {
console.error('[WeeklyTrend] sync failed', err);
},
'WaterTrackerWeeklyTrend',
'setWeeklyData',
[{ dailyData }]
);
}
syncWeeklyTrendToNative 函数是 Web 层和原生层通信的桥梁。首先,我们检查 window.cordova 是否存在,这是判断 Cordova 环境是否已准备好的标准方法。如果 Cordova 还没有加载,我们就打印一个警告日志并返回,避免调用不存在的方法导致错误。
如果 Cordova 已准备好,我们使用 cordova.exec 方法调用原生插件。这个方法有 5 个参数:
- 成功回调函数:当原生侧成功处理请求时调用
- 失败回调函数:当原生侧处理失败时调用
- 插件名称:‘WaterTrackerWeeklyTrend’
- 方法名称:‘setWeeklyData’
- 参数数组:包含要传递给原生侧的数据
在这个例子中,我们将 dailyData(7 天的喝水量数据)打包成一个对象并传递给原生侧。原生侧可以接收这些数据,用于绘制原生图表、进行数据分析或其他处理。
六、OpenHarmony ArkTS 插件与周趋势存储
// entry/src/main/ets/plugins/WaterTrackerWeeklyTrendPlugin.ets
import common from '@ohos.app.ability.common';
export interface WeeklyTrendData {
dailyData: number[];
}
export class WeeklyTrendStore {
private static _weeklyData: WeeklyTrendData | null = null;
static setWeeklyData(data: WeeklyTrendData) {
this._weeklyData = data;
}
static get weeklyData() {
return this._weeklyData;
}
}
export default class WaterTrackerWeeklyTrendPlugin {
context: common.UIAbilityContext;
constructor(ctx: common.UIAbilityContext) {
this.context = ctx;
}
setWeeklyData(args: Array<Object>, callbackId: number) {
const data = args[0] as WeeklyTrendData;
WeeklyTrendStore.setWeeklyData(data);
console.info('[WeeklyTrendPlugin] weekly data set');
}
}
ArkTS 侧的 WaterTrackerWeeklyTrendPlugin 插件接收周趋势数据,并通过 WeeklyTrendStore 缓存。
七、ArkUI 中展示周趋势图表
// entry/src/main/ets/pages/WeeklyTrendPage.ets
import { WeeklyTrendStore } from '../plugins/WaterTrackerWeeklyTrendPlugin';
@Component
struct WeeklyTrendView {
build() {
const data = WeeklyTrendStore.weeklyData;
Column() {
Text('周趋势分析')
.fontSize(18)
.margin({ bottom: 8 });
if (data && data.dailyData.length > 0) {
Text(`本周数据: ${data.dailyData.join(', ')} ml`)
.fontSize(14);
} else {
Text('暂无数据')
.fontSize(14);
}
}
.padding(16)
}
}
WeeklyTrendView 组件在原生界面中展示周趋势数据。
八、小结
本篇文章从周数据加载、日期聚合、Canvas 折线图绘制、周导航到 Cordova 桥接和 ArkTS 插件,完整演示了"周趋势分析"在 Cordova&openharmony 混合应用中的实现路径。Web 层通过 loadWeeklyTrend 和 aggregateDailyData 实现了周数据聚合,通过 renderTrendChart 实现了 Canvas 图表绘制;syncWeeklyTrendToNative 将数据推送给原生侧,ArkTS 侧通过 WeeklyTrendStore 和 WaterTrackerWeeklyTrendPlugin 缓存数据,ArkUI 组件 WeeklyTrendView 则提供原生展示入口。
通过"一段代码一段说明"的方式,我们把整个周趋势分析流程拆解得足够细致。你可以在此基础上进一步扩展,例如添加更多的图表类型(柱状图、饼图)、数据导出、趋势预测等功能,让"周趋势分析"真正成为用户了解自己喝水习惯的有力工具.
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)