HarmonyOS7 用 Canvas 画柱状图并不难:CanvasBarChartBasics 全流程拆解
文章目录前言这页案例的重点是什么完整代码data 和 labels 是图表的数据骨架为什么一定要先留 padding坐标轴和基线怎么画网格线为什么很关键柱宽和间距怎么分配柱子的高度怎么从数据映射出来顶部数值和底部标签为什么值得保留颜色数组为什么也值得单独设计这个案例怎么改成业务图表常见坑提醒写在最后前言一提到数据可视化很多人第一反应就是找图表库。这个思路没问题但有些场景下你未必需要一整套库。比如只是做一个轻量周报图、设备状态面板、运营数据卡片自己用Canvas画一个简单柱状图反而更灵活依赖也更轻。问题是图表跟普通图形不一样。它不是“画几个柱子”就完了还涉及坐标区域、留白、标签、刻度、数值位置。这也是为什么不少人一开始觉得图表很难。这个案例正好给了一个很好的起点手写一张最基础的柱状图。这页案例的重点是什么它不是要做一个专业图表库而是把最基础的数据图表达清楚。里面已经包含了柱状图最关键的几块结构数据数组和标签数组画布内边距Y 轴和底部基线网格线与刻度文字柱子本身顶部数值和底部标签你把这些关系弄明白以后换成折线图、面积图、进度图思路都能迁移。完整代码import{DEMO_BG_COLOR,DEMO_CARD_COLOR,DEMO_THEME_COLOR,DEMO_SUBTEXT_COLOR}from./typesinterfacePaddingConfig{top:numberbottom:numberleft:numberright:number}EntryComponentstruct CanvasBarChartBasics{StateisShow:booleantrueprivatesettings:RenderingContextSettingsnewRenderingContextSettings(true)privatecontext:CanvasRenderingContext2DnewCanvasRenderingContext2D(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((){constw380consth220constpadding:PaddingConfig{top:20,bottom:40,left:40,right:20}constchartWw-padding.left-padding.rightconstchartHh-padding.top-padding.bottomconstbarWchartW/this.data.length*0.6constgapchartW/this.data.length*0.4this.context.clearRect(0,0,w,h)this.context.strokeStyle#E0E0E0this.context.lineWidth1this.context.beginPath()this.context.moveTo(padding.left,padding.top)this.context.lineTo(padding.left,padding.topchartH)this.context.lineTo(w-padding.right,padding.topchartH)this.context.stroke()for(leti0;i4;i){constypadding.topchartH*(1-i/4)this.context.strokeStyle#F0F0F0this.context.beginPath()this.context.moveTo(padding.left,y)this.context.lineTo(w-padding.right,y)this.context.stroke()this.context.font10px sans-serifthis.context.fillStyle#AAAAAAthis.context.fillText(25*i,padding.left-30,y3)}constcolors[#4D96FF,#6BCB77,#FFD93D,#FF6B6B,#9B59B6,#FFA500,#4ECDC4]for(leti0;ithis.data.length;i){constxpadding.left(chartW/this.data.length)*igap/2constbarH(this.data[i]/100)*chartHconstypadding.topchartH-barHthis.context.fillStylecolors[i]this.context.fillRect(x,y,barW,barH)this.context.font10px sans-serifthis.context.fillStyle#666666this.context.textAligncenterthis.context.fillText(this.data[i],xbarW/2,y-5)this.context.fillText(this.labels[i],xbarW/2,padding.topchartH18)}}).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)}}data和labels是图表的数据骨架案例先定义了两组数组privatedata:number[][65,80,45,90,55,75,60]privatelabels:string[][周一,周二,周三,周四,周五,周六,周日]这就是图表最基本的输入。data决定柱子的高度labels决定底部每根柱子对应的说明。很多人一开始就急着画其实真正应该先想清楚的是数据结构长什么样。如果输入结构乱后面坐标、标签、颜色都会跟着乱。为什么一定要先留padding这句是全篇里最容易被低估的代码constpadding:PaddingConfig{top:20,bottom:40,left:40,right:20}图表不是填满整块画布就完事。你必须给坐标轴、刻度、标签、顶部数值留空间。所以这里把画布总尺寸w、h再减去内边距得到真正的图表可用区域constchartWw-padding.left-padding.rightconstchartHh-padding.top-padding.bottom这个思路特别重要。以后你做任何可视化几乎都要先从“总空间”和“绘图区”拆开。坐标轴和基线怎么画案例用路径画了左侧轴线和底部基线this.context.beginPath()this.context.moveTo(padding.left,padding.top)this.context.lineTo(padding.left,padding.topchartH)this.context.lineTo(w-padding.right,padding.topchartH)this.context.stroke()这里其实把图表的边界感一下子立起来了。即使你还没画柱子用户也能感觉到“这是个数据区域”。很多轻量图表不一定要完整包围框但底线和一条纵向参考轴通常很有必要。网格线为什么很关键案例里用循环画了五条水平网格线for(leti0;i4;i){constypadding.topchartH*(1-i/4)this.context.moveTo(padding.left,y)this.context.lineTo(w-padding.right,y)}它的作用不是装饰而是帮助读数。没有网格线时用户只能看出哪根柱子高、哪根柱子低有了刻度线和左侧数值他才更容易估出这是 50、75 还是接近 100。数据图的本质不是“好看”而是“能读”。柱宽和间距怎么分配案例把每一列的总空间拆成两部分constbarWchartW/this.data.length*0.6constgapchartW/this.data.length*0.4这是一种非常实用的比例分配法。每组位置 60% 给柱子40% 给空隙。这样柱子不会挤在一起也不会显得太散。这类写法的好处是数据项数量变了整体仍然能保持相对平衡。柱子的高度怎么从数据映射出来真正的数据映射就在这两句constbarH(this.data[i]/100)*chartHconstypadding.topchartH-barH第一句把数据值映射成柱高。第二句决定柱子从哪里开始画。因为Canvas的y轴是向下增长的所以柱子要想“从底部往上长”就必须先算出顶部坐标。这一步是很多人最容易算反的地方。顶部数值和底部标签为什么值得保留案例里每根柱子都画了两个文字this.context.fillText(this.data[i],xbarW/2,y-5)this.context.fillText(this.labels[i],xbarW/2,padding.topchartH18)这两个信息点其实很关键。顶部数值让用户不必用眼睛估计底部标签让用户知道这一列到底代表哪一天。它们一上图表就从“可视块”变成了“可阅读的数据表达”。颜色数组为什么也值得单独设计案例给每根柱子配了不同颜色constcolors[#4D96FF,#6BCB77,#FFD93D,#FF6B6B,#9B59B6,#FFA500,#4ECDC4]这适合演示效果但真实业务里要看场景。如果强调每一天是独立分类多色可以如果强调同一维度的连续数据统一色系往往更专业。比如一组销售额柱子通常不必七种颜色全上。这个案例怎么改成业务图表稍微改一下这页就能变成很多业务组件。比如把labels换成设备编号做设备监测面板把labels换成月份做月度趋势卡片把data接成接口返回值做真实报表模块把颜色策略改成阈值色做异常预警图也就是说这个案例已经不是玩具了它其实是个可复用的最小图表骨架。常见坑提醒第一个坑是没有先减掉padding结果标签和柱子全贴边。第二个坑是textAlign改成center后忘了恢复不过这个案例后续没有再写别的内容所以暂时没出问题。第三个坑是数据最大值写死为 100。如果真实数据不是这个量级需要先做动态最大值计算。第四个坑是直接把太多标签塞到底部文字会重叠。数据一多布局策略要变。写在最后柱状图这件事看起来像是图形问题实际上更像空间和数据关系问题。只要你把绘图区、比例映射、标签位置和信息层级想清楚用Canvas画一张清爽的图并不难。这一页特别值得反复练因为它已经把Canvas从“画图”推进到了“表达数据”。这一步走通后面的应用场景会一下子宽很多。

相关新闻

AI智能体记忆系统:Cognee开源平台架构与LangChain集成实战

AI智能体记忆系统:Cognee开源平台架构与LangChain集成实战

1. 项目概述:当AI智能体开始拥有“记忆”最近在AI智能体(Agent)的圈子里,一个词被频繁提起:记忆。无论是构建一个能持续对话的客服助手,还是一个能根据历史交互调整策略的自动化流程,智能体如何…

2026/10/13 17:07:57 阅读更多 →
HarmonyOS7 Canvas 怎么动起来:CanvasAnimationLoop 动画循环详解

HarmonyOS7 Canvas 怎么动起来:CanvasAnimationLoop 动画循环详解

文章目录前言这页案例到底讲了什么完整代码angle 是这段动画的发动机startAnimation() 做的其实是“开循环”动画帧开始前为什么一定要 clearRect()旋转为什么要配合 translate() 使用save() 和 restore() 为什么不能少颜色跟着角度变化,是个很巧的示范stopAnimatio…

2026/10/8 21:06:38 阅读更多 →
Java 反射全套常用方法汇总

Java 反射全套常用方法汇总

反射核心围绕 Class、Constructor、Method、Field 四大API,下面分类整理,附带作用与区分要点。前提:获取类字节码对象 1.Class clazz 目标类.class;2. Class clazz Class.forName("全类名");3.对象.getClass()一、Class 类&#…

2026/10/12 21:20:57 阅读更多 →

最新新闻

八字排盘四千年:节气计算与儒略日工程实践

八字排盘四千年:节气计算与儒略日工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:09:04 阅读更多 →
ESMM如何解决推荐系统中的CVR预估偏差问题

ESMM如何解决推荐系统中的CVR预估偏差问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:09:04 阅读更多 →
jQuery UI Layout插件:五区模型实现可拖拽后台布局

jQuery UI Layout插件:五区模型实现可拖拽后台布局

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:09:04 阅读更多 →
硬件板上调试顺序:电源、时钟、复位与通信信号抓取指南

硬件板上调试顺序:电源、时钟、复位与通信信号抓取指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:09:04 阅读更多 →
Zabbix 4.0 数据收集全链路解析:从 Agent 配置到大规模优化实践

Zabbix 4.0 数据收集全链路解析:从 Agent 配置到大规模优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:09:04 阅读更多 →
算法概览图:用拓扑建模实现原理级动画可视化

算法概览图:用拓扑建模实现原理级动画可视化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:08:04 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →