一、功能概述
喝水习惯的形成往往需要观察一周内的变化趋势。"周趋势分析"模块让用户能够直观看到过去 7 天的喝水量变化,从而发现自己的规律、调整计划。本篇文章围绕"周趋势分析"展开,介绍如何在Cordova Web 层通过 Canvas 或简单的 SVG 绘制折线图,并通过OpenHarmony ArkTS 插件提供原生图表渲染能力。
我们继续采用"一段代码一段说明"的结构,通过 HTML/JavaScript 和 ArkTS 示例,构建一条完整的数据可视化动线。
二、Web 端周趋势分析界面
<divid="weekly-trend-page"class="page page-weekly-trend"><h1>周趋势分析</h1><divclass="trend-controls"><buttonid="btn-prev-week"class="btn-secondary">上一周</button><spanid="week-label"class="text-label">本周</span><buttonid="btn-next-week"class="btn-secondary">下一周</button></div><canvasid="trend-canvas"width="800"height="400"></canvas><divid="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 元素设置了固定的宽高,背景色与深色主题一致,便于绘制图表。
三、加载周数据并绘制折线图
asyncfunctionloadWeeklyTrend(weekOffset=0){consttoday=newDate();conststartDate=newDate(today);startDate.setDate(today.getDate()-today.getDay()+weekOffset*7);startDate.setHours(0,0,0,0);constendDate=newDate(startDate);endDate.setDate(startDate.getDate()+6);endDate.setHours(23,59,59,999);constrecords=awaitdb.getRecordsByDateRange(startDate,endDate);constdailyData=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更新页面上显示的周日期范围。
functionaggregateDailyData(records,startDate){constdailyMap=newMap();for(leti=0;i<7;i++){constdate=newDate(startDate);date.setDate(startDate.getDate()+i);constkey=date.toISOString().split('T')[0];dailyMap.set(key,0);}records.forEach((r)=>{constkey=newDate(r.timestamp).toISOString().split('T')[0];if(dailyMap.has(key)){dailyMap.set(key,dailyMap.get(key)+r.amount);}});returnArray.from(dailyMap.values());}aggregateDailyData函数的作用是将原始的喝水记录数据按日期进行聚合。首先,我们创建一个 Map 对象dailyMap,用于存储每天的喝水总量。接着,通过一个循环为该周的每一天(共 7 天)初始化一个 0 值。这一步很重要,因为即使某天没有喝水记录,我们也需要在图表上显示为 0,这样才能保证图表的完整性和连续性。
然后,我们遍历所有的喝水记录。对于每条记录,我们提取其时间戳中的日期部分(格式为 YYYY-MM-DD),然后检查这个日期是否在我们的dailyMap中。如果存在,我们就将该记录的喝水量累加到对应日期的总量上。这样,如果某天有多条记录,它们的喝水量会被累加在一起。
最后,我们使用Array.from(dailyMap.values())将 Map 中的所有值转换为一个数组,这个数组就是按顺序排列的 7 天的喝水量数据,可以直接用于绘制图表。
functionrenderTrendChart(dailyData){constcanvas=document.getElementById('trend-canvas');if(!canvas)return;constctx=canvas.getContext('2d');constwidth=canvas.width;constheight=canvas.height;constpadding=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();// 绘制折线constmaxValue=Math.max(...dailyData,1);constpointSpacing=(width-2*padding)/(dailyData.length-1||1);ctx.strokeStyle='#60a5fa';ctx.lineWidth=2;ctx.beginPath();dailyData.forEach((value,index)=>{constx=padding+index*pointSpacing;consty=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)=>{constx=padding+index*pointSpacing;consty=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),这样用户可以清楚地看到每个数据点的位置。整个图表就完成了,用户可以直观地看到一周内的喝水量变化趋势。
functionrenderTrendSummary(dailyData){constsum=dailyData.reduce((a,b)=>a+b,0);constavg=Math.round(sum/dailyData.length);constmax=Math.max(...dailyData);constmin=Math.min(...dailyData);constsummaryDiv=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 中。
四、周导航与事件绑定
letcurrentWeekOffset=0;functionupdateWeekLabel(startDate){constendDate=newDate(startDate);endDate.setDate(startDate.getDate()+6);constlabel=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 同步周趋势数据到原生层
functionsyncWeeklyTrendToNative(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.etsimportcommonfrom'@ohos.app.ability.common';exportinterfaceWeeklyTrendData{dailyData:number[];}exportclassWeeklyTrendStore{privatestatic_weeklyData:WeeklyTrendData|null=null;staticsetWeeklyData(data:WeeklyTrendData){this._weeklyData=data;}staticgetweeklyData(){returnthis._weeklyData;}}exportdefaultclassWaterTrackerWeeklyTrendPlugin{context:common.UIAbilityContext;constructor(ctx:common.UIAbilityContext){this.context=ctx;}setWeeklyData(args:Array<Object>,callbackId:number){constdata=args[0]asWeeklyTrendData;WeeklyTrendStore.setWeeklyData(data);console.info('[WeeklyTrendPlugin] weekly data set');}}ArkTS 侧的WaterTrackerWeeklyTrendPlugin插件接收周趋势数据,并通过WeeklyTrendStore缓存。
七、ArkUI 中展示周趋势图表
// entry/src/main/ets/pages/WeeklyTrendPage.etsimport{WeeklyTrendStore}from'../plugins/WaterTrackerWeeklyTrendPlugin';@Componentstruct WeeklyTrendView{build(){constdata=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则提供原生展示入口。
通过"一段代码一段说明"的方式,我们把整个周趋势分析流程拆解得足够细致。你可以在此基础上进一步扩展,例如添加更多的图表类型(柱状图、饼图)、数据导出、趋势预测等功能,让"周趋势分析"真正成为用户了解自己喝水习惯的有力工具.