news 2026/8/8 11:16:46

HarmonyOS7 用 Canvas 画柱状图并不难:CanvasBarChartBasics 全流程拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS7 用 Canvas 画柱状图并不难:CanvasBarChartBasics 全流程拆解


文章目录

      • 前言
      • 这页案例的重点是什么
      • 完整代码
      • `data` 和 `labels` 是图表的数据骨架
      • 为什么一定要先留 `padding`
      • 坐标轴和基线怎么画
      • 网格线为什么很关键
      • 柱宽和间距怎么分配
      • 柱子的高度怎么从数据映射出来
      • 顶部数值和底部标签为什么值得保留
      • 颜色数组为什么也值得单独设计
      • 这个案例怎么改成业务图表
      • 常见坑提醒
      • 写在最后

前言

一提到数据可视化,很多人第一反应就是找图表库。这个思路没问题,但有些场景下,你未必需要一整套库。比如只是做一个轻量周报图、设备状态面板、运营数据卡片,自己用Canvas画一个简单柱状图,反而更灵活,依赖也更轻。

问题是,图表跟普通图形不一样。它不是“画几个柱子”就完了,还涉及坐标区域、留白、标签、刻度、数值位置。这也是为什么不少人一开始觉得图表很难。

这个案例正好给了一个很好的起点:手写一张最基础的柱状图。

这页案例的重点是什么

它不是要做一个专业图表库,而是把最基础的数据图表达清楚。里面已经包含了柱状图最关键的几块结构:

  • 数据数组和标签数组
  • 画布内边距
  • Y 轴和底部基线
  • 网格线与刻度文字
  • 柱子本身
  • 顶部数值和底部标签

你把这些关系弄明白,以后换成折线图、面积图、进度图,思路都能迁移。

完整代码

import{DEMO_BG_COLOR,DEMO_CARD_COLOR,DEMO_THEME_COLOR,DEMO_SUBTEXT_COLOR}from'./types'interfacePaddingConfig{top:numberbottom:numberleft:numberright:number}@Entry@Componentstruct CanvasBarChartBasics{@StateisShow:boolean=trueprivatesettings:RenderingContextSettings=newRenderingContextSettings(true)privatecontext:CanvasRenderingContext2D=newCanvasRenderingContext2D(this.settings)privatedata:number[]=[65,80,45,90,55,75,60]privatelabels:string[]=['周一','周二','周三','周四','周五','周六','周日']build(){Column(){if(this.isShow){Scroll(){Column({space:16}){Text('Canvas 柱状图').fontSize(16).fontColor(DEMO_THEME_COLOR).fontWeight(FontWeight.Bold)Column(){Text('每周数据统计').fontSize(13).fontColor('#4D96FF').margin({bottom:8})Column(){Canvas(this.context).onReady(()=>{constw=380consth=220constpadding:PaddingConfig={top:20,bottom:40,left:40,right:20}constchartW=w-padding.left-padding.rightconstchartH=h-padding.top-padding.bottomconstbarW=chartW/this.data.length*0.6constgap=chartW/this.data.length*0.4this.context.clearRect(0,0,w,h)this.context.strokeStyle='#E0E0E0'this.context.lineWidth=1this.context.beginPath()this.context.moveTo(padding.left,padding.top)this.context.lineTo(padding.left,padding.top+chartH)this.context.lineTo(w-padding.right,padding.top+chartH)this.context.stroke()for(leti=0;i<=4;i++){consty=padding.top+chartH*(1-i/4)this.context.strokeStyle='#F0F0F0'this.context.beginPath()this.context.moveTo(padding.left,y)this.context.lineTo(w-padding.right,y)this.context.stroke()this.context.font='10px sans-serif'this.context.fillStyle='#AAAAAA'this.context.fillText(''+25*i,padding.left-30,y+3)}constcolors=['#4D96FF','#6BCB77','#FFD93D','#FF6B6B','#9B59B6','#FFA500','#4ECDC4']for(leti=0;i<this.data.length;i++){constx=padding.left+(chartW/this.data.length)*i+gap/2constbarH=(this.data[i]/100)*chartHconsty=padding.top+chartH-barHthis.context.fillStyle=colors[i]this.context.fillRect(x,y,barW,barH)this.context.font='10px sans-serif'this.context.fillStyle='#666666'this.context.textAlign='center'this.context.fillText(''+this.data[i],x+barW/2,y-5)this.context.fillText(this.labels[i],x+barW/2,padding.top+chartH+18)}}).width('100%').height(240).backgroundColor('#FAFAFA').borderRadius(8)}}.backgroundColor(DEMO_CARD_COLOR).padding(12).borderRadius(12)Column({space:6}){Text('数据详情').fontSize(14).fontColor(DEMO_THEME_COLOR).fontWeight(FontWeight.Bold)Row(){ForEach(this.labels,(label:string,_:number)=>{Text(label).fontSize(11).fontColor('#666666').layoutWeight(1).textAlign(TextAlign.Center)})}.width('100%')Row(){ForEach(this.data,(val:number,_:number)=>{Text(''+val).fontSize(13).fontColor('#1A1A1A').fontWeight(FontWeight.Bold).layoutWeight(1).textAlign(TextAlign.Center)})}.width('100%')}.width('100%').backgroundColor('#E8F4FD').padding(14).borderRadius(12)}.width('100%')}}Text('CanvasBarChartBasics - 绘制每周柱状图').fontSize(12).fontColor(DEMO_SUBTEXT_COLOR).margin({top:12})}.width('100%').height('100%').backgroundColor(DEMO_BG_COLOR).padding(16)}}

datalabels是图表的数据骨架

案例先定义了两组数组:

privatedata:number[]=[65,80,45,90,55,75,60]privatelabels:string[]=['周一','周二','周三','周四','周五','周六','周日']

这就是图表最基本的输入。

data决定柱子的高度,labels决定底部每根柱子对应的说明。很多人一开始就急着画,其实真正应该先想清楚的是:数据结构长什么样。

如果输入结构乱,后面坐标、标签、颜色都会跟着乱。

为什么一定要先留padding

这句是全篇里最容易被低估的代码:

constpadding:PaddingConfig={top:20,bottom:40,left:40,right:20}

图表不是填满整块画布就完事。你必须给坐标轴、刻度、标签、顶部数值留空间。

所以这里把画布总尺寸wh再减去内边距,得到真正的图表可用区域:

constchartW=w-padding.left-padding.rightconstchartH=h-padding.top-padding.bottom

这个思路特别重要。以后你做任何可视化,几乎都要先从“总空间”和“绘图区”拆开。

坐标轴和基线怎么画

案例用路径画了左侧轴线和底部基线:

this.context.beginPath()this.context.moveTo(padding.left,padding.top)this.context.lineTo(padding.left,padding.top+chartH)this.context.lineTo(w-padding.right,padding.top+chartH)this.context.stroke()

这里其实把图表的边界感一下子立起来了。即使你还没画柱子,用户也能感觉到“这是个数据区域”。

很多轻量图表不一定要完整包围框,但底线和一条纵向参考轴通常很有必要。

网格线为什么很关键

案例里用循环画了五条水平网格线:

for(leti=0;i<=4;i++){consty=padding.top+chartH*(1-i/4)this.context.moveTo(padding.left,y)this.context.lineTo(w-padding.right,y)}

它的作用不是装饰,而是帮助读数。

没有网格线时,用户只能看出哪根柱子高、哪根柱子低;有了刻度线和左侧数值,他才更容易估出这是 50、75 还是接近 100。

数据图的本质不是“好看”,而是“能读”。

柱宽和间距怎么分配

案例把每一列的总空间拆成两部分:

constbarW=chartW/this.data.length*0.6constgap=chartW/this.data.length*0.4

这是一种非常实用的比例分配法。每组位置 60% 给柱子,40% 给空隙。这样柱子不会挤在一起,也不会显得太散。

这类写法的好处是,数据项数量变了,整体仍然能保持相对平衡。

柱子的高度怎么从数据映射出来

真正的数据映射就在这两句:

constbarH=(this.data[i]/100)*chartHconsty=padding.top+chartH-barH

第一句把数据值映射成柱高。

第二句决定柱子从哪里开始画。因为Canvasy轴是向下增长的,所以柱子要想“从底部往上长”,就必须先算出顶部坐标。

这一步是很多人最容易算反的地方。

顶部数值和底部标签为什么值得保留

案例里每根柱子都画了两个文字:

this.context.fillText(''+this.data[i],x+barW/2,y-5)this.context.fillText(this.labels[i],x+barW/2,padding.top+chartH+18)

这两个信息点其实很关键。

顶部数值让用户不必用眼睛估计;底部标签让用户知道这一列到底代表哪一天。它们一上,图表就从“可视块”变成了“可阅读的数据表达”。

颜色数组为什么也值得单独设计

案例给每根柱子配了不同颜色:

constcolors=['#4D96FF','#6BCB77','#FFD93D','#FF6B6B','#9B59B6','#FFA500','#4ECDC4']

这适合演示效果,但真实业务里要看场景。

如果强调每一天是独立分类,多色可以;如果强调同一维度的连续数据,统一色系往往更专业。比如一组销售额柱子通常不必七种颜色全上。

这个案例怎么改成业务图表

稍微改一下,这页就能变成很多业务组件。

比如:

  • labels换成设备编号,做设备监测面板
  • labels换成月份,做月度趋势卡片
  • data接成接口返回值,做真实报表模块
  • 把颜色策略改成阈值色,做异常预警图

也就是说,这个案例已经不是玩具了,它其实是个可复用的最小图表骨架。

常见坑提醒

第一个坑,是没有先减掉padding,结果标签和柱子全贴边。

第二个坑,是textAlign改成center后忘了恢复,不过这个案例后续没有再写别的内容,所以暂时没出问题。

第三个坑,是数据最大值写死为 100。如果真实数据不是这个量级,需要先做动态最大值计算。

第四个坑,是直接把太多标签塞到底部,文字会重叠。数据一多,布局策略要变。

写在最后

柱状图这件事,看起来像是图形问题,实际上更像空间和数据关系问题。只要你把绘图区、比例映射、标签位置和信息层级想清楚,用Canvas画一张清爽的图并不难。

这一页特别值得反复练,因为它已经把Canvas从“画图”推进到了“表达数据”。这一步走通,后面的应用场景会一下子宽很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/8 11:15:56

MediaCrawler:一站式社交媒体数据采集终极指南

MediaCrawler&#xff1a;一站式社交媒体数据采集终极指南 【免费下载链接】MediaCrawler-new 项目地址: https://gitcode.com/GitHub_Trending/me/MediaCrawler-new 还在为获取小红书、抖音、快手、B站、微博等主流社交平台的数据而烦恼吗&#xff1f;MediaCrawler正是…

作者头像 李华
网站建设 2026/8/8 11:15:32

终极免费激活指南:如何一键解决Windows和Office激活难题

终极免费激活指南&#xff1a;如何一键解决Windows和Office激活难题 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 还在为Windows系统频繁弹出激活提示而烦恼&#xff1f;Office突然变成只读模…

作者头像 李华
网站建设 2026/8/8 11:15:17

嵌入式软件开发——可重入代码详解

引言在嵌入式系统开发中&#xff0c;代码不仅要实现功能正确&#xff0c;更要在复杂的并发和中断环境下保持行为确定。随着嵌入式系统从简单的裸机循环演变为多任务实时操作系统&#xff08;RTOS&#xff09;和复杂的中断驱动架构&#xff0c;代码的可重入性&#xff08;Reentr…

作者头像 李华
网站建设 2026/8/8 11:14:59

Sketchfab免费下载终极指南:Firefox用户脚本轻松获取3D模型

Sketchfab免费下载终极指南&#xff1a;Firefox用户脚本轻松获取3D模型 【免费下载链接】sketchfab sketchfab download userscipt for Tampermonkey by firefox only 项目地址: https://gitcode.com/gh_mirrors/sk/sketchfab 还在为Sketchfab上的精美3D模型无法下载而烦…

作者头像 李华
网站建设 2026/8/8 11:13:34

Deepin Linux下配置富士施乐M115b打印机驱动:CUPS与foo2zjs实战指南

1. 项目缘起&#xff1a;当国产Linux遇上老牌打印机最近在把家里的老笔记本换成Deepin Linux&#xff0c;图的就是它界面漂亮、对中文友好&#xff0c;用起来跟Windows差不多顺手。一切都挺顺利&#xff0c;直到我需要打印一份文件&#xff0c;麻烦就来了。我手头这台富士施乐D…

作者头像 李华
网站建设 2026/8/8 11:13:03

文件批量重命名实战:从编码冲突到工程化解决方案

最近在整理本地文件时&#xff0c;遇到一个挺有意思的“小麻烦”。我有一套从不同渠道收集来的同人作品合集&#xff0c;文件命名五花八门&#xff0c;有中文的、英文的、带日文假名的&#xff0c;甚至还有一堆乱码和特殊符号。我的目标很简单&#xff1a;把它们批量重命名&…

作者头像 李华