47 雷达图绘制:标签自适应布局
2026/7/11 22:18:42 网站建设 项目流程

47 雷达图绘制:标签自适应布局

前言

图:47 雷达图绘制:标签自适应布局 运行效果截图(HarmonyOS NEXT)

雷达图的 6 个顶点各有一个维度标签(能量/思维/行动/情绪/社交/细节),这些标签需要刚好位于顶点外侧,并且在不同尺寸的雷达图上都能清晰展示。

本文以 [HandwritingRadar.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/common/components/HandwritingRadar.ets) 中的标签绘制代码为例,深入解析 Canvas 文字绘制的自适应布局方案。

鸿蒙官方·Canvas 文字 API:developer.huawei.com
项目源码:[HandwritingRadar.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/common/components/HandwritingRadar.ets)

图:雷达图6个顶点标签的自适应布局——根据角度位置动态调整对齐方式

左侧 180°-360°

右侧 0°-180°

顶部 270°附近

底部 90°附近

6个标签

计算标签中心坐标
顶点坐标 × labelOffset

角度区间

textAlign = right

textAlign = left

textAlign = center

fillText 绘制

一、标签绘制基础

1.1 核心代码

// HandwritingRadar.ets — 标签绘制部分constfontSize=Math.max(8,Math.round(11*this.radarSize/280))ctx.font=`${fontSize}px HarmonyOS Sans SC`ctx.fillStyle=AppColors.TEXT_2ctx.textAlign='center'ctx.textBaseline='middle'constlabelOffset=radius+Math.max(12,Math.round(18*this.radarSize/280))this.data.forEach((p,i)=>{constx=cx+labelOffset*Math.cos(angles[i])consty=cy+labelOffset*Math.sin(angles[i])ctx.fillText(p.label,x,y)})

1.2 视觉效果

标签①(能量) ↑ │ ←──○──→ 标签④(社交) │ ↓ 标签③(情绪) 每个标签位于顶点向外延伸的方向上,距离顶点约 12-30px

二、自适应字体大小

2.1 按比例缩放

constfontSize=Math.max(8,Math.round(11*this.radarSize/280))

计算逻辑:

radarSize计算过程fontSize
140(小)11 × 140 / 280 = 5.5Math.max(8, 6)8px(下限保护)
200(中)11 × 200 / 280 = 7.86Math.max(8, 8)8px
280(默认)11 × 280 / 280 = 11Math.max(8, 11)11px
360(大)11 × 360 / 280 = 14.14Math.max(8, 14)14px

设计要点:

  • 基准大小280px画布对应11px字号
  • 线性缩放:字号随画布大小线性变化
  • 下限保护Math.max(8, ...)确保最小 8px,避免太小无法阅读

2.2 字号下限保护的原因

// ❌ 如果没有 Math.max(8, ...)fontSize=11*100/2804// 4px 完全无法阅读// ✅ 有 Math.max(8, ...) 保护fontSize=Math.max(8,4)=8// 8px 勉强可读

三、自适应标签偏移

3.1 偏移距离

constlabelOffset=radius+Math.max(12,Math.round(18*this.radarSize/280))

标签位置 = 网格顶点 + 外推偏移,确保标签不盖住网格:

网格顶点(radius) ───┬─── │ ↓ ← labelOffset = 顶点 + 外推距离 文字标签

偏移计算:

radarSize外推距离公式labelOffset
14012px(下限)radius + 12radius + 12
20013pxradius + 13radius + 13
28018pxradius + 18radius + 18
36023pxradius + 23radius + 23

3.2 偏移方向

每个标签沿着该顶点方向的射线外推:

this.data.forEach((p,i)=>{constx=cx+labelOffset*Math.cos(angles[i])consty=cy+labelOffset*Math.sin(angles[i])// angles[i] 与网格顶点计算使用的角度相同// 所以标签位于顶点正外方})

方向对照:

标签角度方向标签位置
能量-90°正上方
思维-30°右上
行动30°右下
情绪90°正下方
社交150°左下
细节-150°左上

四、文字对齐设置

4.1 textAlign 与 textBaseline

ctx.textAlign='center'// 水平居中ctx.textBaseline='middle'// 垂直居中

textAlign 可选值:

效果适用场景
left从左向右排列正左标签(如 9 点钟方向)
center以(x,y)为中心✅ 本项目:所有标签居中于顶点方向
right从右向左排列正右标签(如 3 点钟方向)

textBaseline 可选值:

效果适用场景
top顶部对齐标签在点下方
middle垂直居中✅ 本项目:所有标签居中
bottom底部对齐标签在点上方
alphabetic字母基线英文文本

4.2 为什么统一 center + middle?

对于分布在 6 个方向的标签,如果使用left/right/top/bottom组合,需要为每个顶点单独计算对齐方式:

// ❌ 如果每个方向单独处理,代码会变得复杂constalignMap=[{align:'center',baseline:'bottom'},// 12点方向:文字在点下方{align:'left',baseline:'middle'},// 2点方向:文字在点右侧{align:'left',baseline:'top'},// 4点方向:...{align:'center',baseline:'top'},// 6点方向{align:'right',baseline:'top'},// 8点方向{align:'right',baseline:'middle'},// 10点方向]// ✅ 本项目:统一 center + middle + labelOffset 外推// 所有标签从顶点向外推,自然分布在顶点外侧// 代码简洁,效果一致

五、标签碰撞处理

5.1 当前方案 vs 碰撞检测

当前方案将所有标签沿射线外推相同距离,在标准雷达图(6 个维度)下不会出现重叠,因为:

  • 6 个顶点间隔 60°,间距足够
  • 标签文字较短(2 个汉字)
  • 外推距离(18px)保证标签在网格外部

可能重叠的极端情况:

// 场景:大量维度(如 12 维雷达图)// 顶点间距只有 30°,标签可能互相覆盖// 方案:动态调整外推距离或缩小字号constminGap=Math.sin(Math.PI/sides)*labelOffset*2if(minGap<fontSize*2){// 空间不足,缩小字号或增大偏移}

5.2 基于方向的偏移微调(扩展方案)

如果需要更精细的标签布局,可以按方向微调:

functiongetLabelOffset(i:number,sides:number,radius:number,fontSize:number):number{constbase=radius+18// 水平方向(左右)需要更多空间,因为文字是横向排列的constisHorizontal=i===1||i===4// 2 点和 8 点方向if(isHorizontal)returnbase+fontSize*0.8returnbase}

六、字体选择

6.1 字体族

ctx.font=`${fontSize}px HarmonyOS Sans SC`

项目使用系统字体HarmonyOS Sans SC,这是鸿蒙系统的默认无衬线字体。

可用的系统字体:

字体风格用途
HarmonyOS Sans SC无衬线UI 文本、标签(✅ 本项目使用)
HarmonyOS Sans SC Medium中等字重标题、强调
HarmonyOS Sans SC Bold粗体数字、数值

6.2 完整字体设置

// Canvas 中设置字体比 ArkUI Text 组件更灵活ctx.font=`${fontWeight}${fontSize}px${fontFamily}`// 参数:字重 + 字号 + 字体族(字符串拼接)// 示例ctx.font='11px HarmonyOS Sans SC'// 普通ctx.font='bold 36px HarmonyOS Sans SC'// 粗体大号(MatchRing 中心数值)ctx.font='12px HarmonyOS Sans SC'// 中等(MatchRing 副标题)

七、不同场景的标签配置

7.1 标签与数据显示

项目中的标签有 6 个固定维度,每个维度垂直对应的数据值也在标签附近展示(在页面 UI 层而非 Canvas 层实现):

Canvas 层: 能量 ↑ │ 细节 ↖ ○ ↗ 思维 社交 ← ○ → 行动 ↓ 情绪 页面 UI 层: [能量 62] [思维 65] [行动 55] [情绪 72] [社交 60] [细节 70]

7.2 小尺寸雷达图的标签处理

当雷达图用作小尺寸预览时(如 SharePage 中radarSize=200),标签字号自动缩小:

// SharePage 中使用小雷达HandwritingRadar({data:this.radarData,radarSize:200// 比默认 280 小 29%})// 字号:Math.max(8, 11*200/280) = Math.max(8, 8) = 8px// 偏移:radius + Math.max(12, 18*200/280) = radius + 13

八、标签布局检查清单

检查项项目中当前值是否满足
字号自适应11 × size/280最小 8px
标签位置顶点外推Math.max(12, 18×size/280)
文字对齐center + middle
抗锯齿RenderingContextSettings antialias=true
标签不重叠6 维度 60° 间隔 + 短文字
颜色可见fillStyle = TEXT_2(深灰色)
字体加载使用系统内置 HarmonyOS Sans SC

总结

本文详细解析了雷达图标签的自适应布局方案:

  1. 自适应字号Math.max(8, 11 × radarSize / 280)线性缩放 + 下限保护
  2. 自适应偏移radius + Math.max(12, 18 × radarSize / 280)外推顶点
  3. 对齐方式:统一center+middle简化代码
  4. 方向计算:复用网格顶点的angles[],标签位于顶点正外方
  5. 极端处理:小尺寸下字号保护 + 外推距离缩小

下一篇文章将介绍EmotionTrendChart 情绪折线图——30天情绪趋势的 Canvas 绘制。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


参考资源:

  • Canvas fillText / textAlign / textBaseline
  • [HandwritingRadar 标签源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/common/components/HandwritingRadar.ets)
  • HarmonyOS Sans SC 字体
  • Canvas font 属性
  • [SharePage 小雷达图使用](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/SharePage.ets)
  • [AppFonts 字体常量](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/common/theme/AppFonts.ets)
  • HarmonyOS 开发文档

七、标签绘制完整实现

7.1 自适应对齐算法

privatedrawLabels(ctx:CanvasRenderingContext2D,cx:number,cy:number,r:number):void{constlabels=['能量','思维','行动','情绪','社交','细节']constlabelOffset=1.25// 标签距中心的倍数ctx.save()ctx.translate(cx,cy)ctx.font='12px HarmonyOS Sans SC'ctx.fillStyle='rgba(255, 255, 255, 0.85)'labels.forEach((label,i)=>{constangle=(Math.PI/3)*i-Math.PI/2constx=r*labelOffset*Math.cos(angle)consty=r*labelOffset*Math.sin(angle)// 根据角度动态调整文字对齐constcosVal=Math.cos(angle)if(Math.abs(cosVal)<0.1){ctx.textAlign='center'// 顶部/底部标签居中}elseif(cosVal>0){ctx.textAlign='left'// 右侧标签左对齐}else{ctx.textAlign='right'// 左侧标签右对齐}// 根据 sin 值调整垂直基线constsinVal=Math.sin(angle)ctx.textBaseline=sinVal>0.5?'top':(sinVal<-0.5?'bottom':'middle')ctx.fillText(label,x,y)})ctx.restore()}

7.2 标签样式参数对照表

参数项目值效果
font'12px HarmonyOS Sans SC'小字体,与图表协调
fillStylergba(255,255,255,0.85)白色微透明,图表背景对比
textAlign动态(left/center/right)自适应位置
textBaseline动态(top/middle/bottom)垂直方向居中
labelOffset1.25标签在顶点外侧 25% 处

7.3 多语言适配注意事项

当标签文本较长(如英文标签)时,需要用measureText检查文字宽度并自适应字号:

// 自适应字号:超出宽度则缩小constmaxLabelWidth=r*0.4// 允许标签最大宽度letfontSize=12ctx.font=`${fontSize}px HarmonyOS Sans SC`consttextWidth=ctx.measureText(label).widthif(textWidth>maxLabelWidth){fontSize=Math.floor(fontSize*maxLabelWidth/textWidth)ctx.font=`${fontSize}px HarmonyOS Sans SC`}ctx.fillText(label,x,y)

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询