
双色叠加雷达图DuetReport 双人对比前言图51 双色叠加雷达图DuetReport 双人对比 运行效果截图HarmonyOS NEXT双人合盘是鹿鹿·笔迹心理分析App 最具特色的功能。当用户与伴侣绑定后DuetReportPage 会展示一个双色叠加雷达图——用两种颜色WARM 暖色 vs ROSE 粉色分别绘制自己和伴侣的 6 维人格数据让双方特征一目了然地对比。本文以 [DuetReportPage.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/DuetReportPage.ets) 中的drawDuet()方法为例解析双人雷达图的实现原理。鸿蒙官方·Canvas 虚线 APIdeveloper.huawei.com项目源码[DuetReportPage.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/DuetReportPage.ets)图双色叠加雷达图——WARM 暖色自己与 ROSE 粉色伴侣半透明叠加用户A的6维分值绘制雷达图1WARM 暖色系用户B的6维分值绘制雷达图2ROSE 粉色系半透明叠加globalAlpha 0.7网格背景setLineDash 虚线图例说明两种颜色标注一、数据准备1.1 DuetDimension 数据结构interfaceDuetDimension{label:string// 维度名称如 能量场myValue:number// 我的数值 0-100partnerValue:number// 伴侣的数值 0-100}1.2 数据来源双方报告均值双人雷达图的数据来自双方所有历史报告的平均值// 从双方各自的报告列表计算均值functionavgScore(reports:ReportEntity[]):RadarScore{constnreports.lengthif(n0)return{energy:0,thinking:0,action:0,emotion:0,social:0,detail:0}constsum{energy:0,thinking:0,action:0,emotion:0,social:0,detail:0}for(constrofreports){constscoresReportDao.parseRadar(r)// 解析 JSON → RadarScoresum.energyscores.energy sum.thinkingscores.thinking sum.actionscores.action sum.emotionscores.emotion sum.socialscores.social sum.detailscores.detail}return{energy:sum.energy/n,thinking:sum.thinking/n,action:sum.action/n,emotion:sum.emotion/n,social:sum.social/n,detail:sum.detail/n}}// 构建双人雷达数据this.dimensions[{label:能量场,myValue:Math.round(myAvg.energy),partnerValue:Math.round(partnerAvg.energy)},{label:思维,myValue:Math.round(myAvg.thinking),partnerValue:Math.round(partnerAvg.thinking)},{label:行动力,myValue:Math.round(myAvg.action),partnerValue:Math.round(partnerAvg.action)},{label:情绪,myValue:Math.round(myAvg.emotion),partnerValue:Math.round(partnerAvg.emotion)},{label:社交,myValue:Math.round(myAvg.social),partnerValue:Math.round(partnerAvg.social)},{label:细节,myValue:Math.round(myAvg.detail),partnerValue:Math.round(partnerAvg.detail)}]二、双色雷达绘制2.1 两套颜色系统// 我的数据WARM 暖色实线描边ctx.strokeStyleAppColors.WARM// #D9A689ctx.fillStylergba(217,166,137,0.32)// 暖色半透明// 对方数据ROSE 粉色虚线描边ctx.strokeStyleAppColors.ROSE// #C9A0A0ctx.fillStylergba(201,160,160,0.28)// 粉色半透明2.2 绘制我的多边形// 我的多边形WARM 暖色ctx.beginPath()this.dimensions.forEach((p,i){constangle(Math.PI*2*i)/sides-Math.PI/2constrradius*(p.myValue/100)*this.drawProgressconstxcxr*Math.cos(angle)constycyr*Math.sin(angle)if(i0)ctx.moveTo(x,y)elsectx.lineTo(x,y)})ctx.closePath()ctx.fillStylergba(217,166,137,0.32)// 暖色填充ctx.fill()ctx.strokeStyleAppColors.WARM// 暖色实线ctx.lineWidth2ctx.stroke()2.3 绘制对方多边形// TA 的多边形ROSE 粉色虚线描边ctx.beginPath()this.dimensions.forEach((p,i){constangle(Math.PI*2*i)/sides-Math.PI/2constrradius*(p.partnerValue/100)*this.drawProgressconstxcxr*Math.cos(angle)constycyr*Math.sin(angle)if(i0)ctx.moveTo(x,y)elsectx.lineTo(x,y)})ctx.closePath()ctx.fillStylergba(201,160,160,0.28)// 粉色填充ctx.fill()ctx.strokeStyleAppColors.ROSE// 玫瑰色虚线ctx.lineWidth2ctx.setLineDash([3,2])// 3px 实线 2px 间隔ctx.stroke()ctx.setLineDash([])// 恢复实线2.4 样式对比属性我的多边形对方多边形描边颜色WARM #D9A689ROSE #C9A0A0描边线型实线虚线setLineDash([3,2])描边宽度2px2px填充颜色rgba(217,166,137,0.32)rgba(201,160,160,0.28)填充透明度32%28%三、完整的 drawDuet 方法3.1 绘制顺序privatedrawDuet(){constctxthis.contextconstW240constcxW/2constcyW/2constradiusW*0.35constsidesthis.dimensions.length ctx.clearRect(0,0,W,W)// ① 三层网格与单色雷达图相同for(letlayer1;layer3;layer){// ... 绘制同心正六边形}// ② 我的数据多边形WARMctx.beginPath()this.dimensions.forEach(...)ctx.closePath()ctx.fillStylergba(217,166,137,0.32)ctx.fill()ctx.strokeStyleAppColors.WARMctx.lineWidth2ctx.stroke()// ③ TA 的数据多边形ROSE 虚线ctx.beginPath()this.dimensions.forEach(...)ctx.closePath()ctx.fillStylergba(201,160,160,0.28)ctx.fill()ctx.strokeStyleAppColors.ROSEctx.lineWidth2ctx.setLineDash([3,2])ctx.stroke()ctx.setLineDash([])// ④ 标签同单色雷达图ctx.font11px HarmonyOS Sans SCctx.textAligncenterctx.textBaselinemiddlethis.dimensions.forEach((p,i){constangle(Math.PI*2*i)/sides-Math.PI/2constrradius18ctx.fillText(p.label,cxr*Math.cos(angle),cyr*Math.sin(angle))})}3.2 绘制层级标签最上层不遮挡任何图形 ────────────────────────────── TA 多边形描边实线/虚线重叠在最后 TA 多边形填充半透明 ────────────────────────────── 我的多边形描边 我的多边形填充半透明 ────────────────────────────── 三层网格背景四、视觉区别策略4.1 两种区分方式双色雷达图通过两种维度区分双方数据维度我的数据对方数据颜色WARM(暖棕 #D9A689)ROSE(玫瑰粉 #C9A0A0)线型实线虚线[3px, 2px]为什么同时使用颜色 线型双重区分颜色远距离快速识别“暖色我粉色TA”线型色盲友好打印机黑白输出也能区分双重保障任何场景下都不会混淆4.2 重叠区域的视觉效果当双方的多边形重叠时半透明填充的叠加会产生自然的颜色混合我的区域暖色 32%透明度 ┌─────────────────────┐ │ ┌──────────┐ │ │ │ 重叠区 │ │ ← 暖色 粉色混合 │ │ (混合色) │ │ │ └──────────┘ │ └─────────────────────┘ TA 区域粉色 28%透明度五、标签处理5.1 双色雷达的标签双色雷达图中的标签与单色雷达图相同只显示维度名称// 标签位置顶点外推 18pxconstlabelOffsetradius18this.dimensions.forEach((p,i){constangle(Math.PI*2*i)/sides-Math.PI/2ctx.fillText(p.label,cxlabelOffset*Math.cos(angle),cylabelOffset*Math.sin(angle))})5.2 详情的获取具体的我和TA的数值不在 Canvas 上显示而是通过 UI 层展示// DuetReportPage 的 UI 层 — 在 Canvas 下方展示详细数值Column(){// 图例Row(){Circle().width(10).height(10).fill(AppColors.WARM)Text(我).fontSize(12).fontColor(AppColors.TEXT)Circle().width(10).height(10).fill(AppColors.ROSE)Text(TA).fontSize(12).fontColor(AppColors.TEXT)}// 数值对比需要滚动查看ForEach(this.dimensions,(dim:DuetDimension){Row(){Text(dim.label).fontSize(13)Text(我:${dim.myValue}).fontSize(12).fontColor(AppColors.WARM)Text(TA:${dim.partnerValue}).fontSize(12).fontColor(AppColors.ROSE)}})}六、差异可视化6.1 差异分析双色雷达图除了展示两个多边形外还可以在页面其他部分展示差异分析// 计算每个维度的差异functioncomputeDifferences(dimensions:DuetDimension[]):{label:string,diff:number,who:string}[]{returndimensions.map(d{constdiffMath.abs(d.myValue-d.partnerValue)constwhod.myValued.partnerValue?我:TAreturn{label:d.label,diff,who}}).sort((a,b)b.diff-a.diff)// 按差异从大到小排序}// 示例输出// [// { label: 能量场, diff: 15, who: 我 }, // 最大差异// { label: 社交, diff: 12, who: TA },// { label: 情绪, diff: 8, who: 我 },// { label: 思维, diff: 5, who: TA },// { label: 行动力, diff: 3, who: 我 },// { label: 细节, diff: 2, who: TA } // 最小差异// ]6.2 在 DuetReportPage 中的优势挑战// 依据差异数据生成优势·挑战文本constadvantagesdimensions.filter(dd.myValued.partnerValue5).map(d你在「${d.label}」上领先 TA${d.myValue-d.partnerValue}分)constchallengesdimensions.filter(dd.partnerValued.myValue5).map(dTA 在「${d.label}」上领先你${d.partnerValue-d.myValue}分)七、DuetReportPage 的完整动画7.1 三段并行动画privatestartEntranceAnimation():void{// ① 内容入场0→1 透明度 上移animateTo({duration:800,curve:Curve.FastOutSlowIn},(){this.contentOpacity1this.contentOffset0})// ② 双色雷达图动画1 秒展开animateTo({duration:1000,curve:Curve.FastOutSlowIn},(){this.drawProgress1})// ③ 3 维卡片动画底部进度条animateTo({duration:800,curve:Curve.FastOutSlowIn},(){this.barProgress1})}7.2 协同时序T0ms 页面加载 → loadData T100ms startEntranceAnimation ├── contentOpacity 0→1 (800ms) ├── drawProgress 0→1 (1000ms) → Watch → drawDuet └── barProgress 0→1 (800ms) T900ms content 动画完成 T1100ms Canvas 双雷达 卡片柱条动画完成八、复用与扩展8.1 双色雷达的通用性虽然双色雷达目前只在 DuetReportPage 中使用但它可以轻松扩展为通用组件Componentexportstruct DuetRadar{PropmyData:RadarPoint[]ProppartnerData:RadarPoint[]PropradarSize:number240// ... 将 drawDuet 逻辑封装在此组件中}8.2 扩展应用场景场景A 数据B 数据对比目的 本项目我的 6 维伴侣的 6 维性格匹配游戏角色当前属性装备后属性装备效果健身月初体能月末体能进步追踪职场自评结果上级评价差距分析九、实现要点对比9.1 单色雷达 vs 双色雷达特性HandwritingRadar单色DuetReport drawDuet双色数据输入一组RadarPoint[]一组DuetDimension[]含双方值颜色1 套PRIMARY 品牌棕2 套WARM 我 ROSE TA线型实线我实线 / TA虚线填充1 层半透明2 层半透明叠加标签自适应 fontSize固定 11px尺寸Prop radarSize可调固定 240px9.2 三个 Canvas 组件最终对比组件类型尺寸动画时长颜色数HandwritingRadar雷达图可调默认2801000ms1 套EmotionTrendChart折线图可调默认320×1801200ms1 套MatchRing圆环可调默认2001200ms渐变DuetReport drawDuet双色雷达固定 2401000ms2 套总结本文完整解析了 DuetReportPage 中双色叠加雷达图的实现数据准备双方历史报告均值 → 6 维对比数据双色绘制WARM 暖色我实线 ROSE 粉色TA虚线样式对比颜色 线型双重区分色盲友好绘制顺序网格 → 我的多边形 → TA 多边形 → 标签差异分析计算各维度差异生成优势·挑战建议本篇文章是第七模块Canvas 高级绘图的收官之作。下一模块将进入AI 端侧推理涵盖 OCR 识别、特征提取、情绪分类等 AI 流水线技术。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力参考资源Canvas setLineDash 虚线[DuetReportPage 双色雷达源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/DuetReportPage.ets)[HandwritingRadar 单色雷达组件](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/common/components/HandwritingRadar.ets)[AppColors 颜色常量](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/common/theme/AppColors.ets)Canvas 绘图指南HarmonyOS 开发文档