HarmonyOS 实战开发-使用canvas实现图表系列之折线图

一、功能结构

实现一个公共组件的时候,首先分析一下大概的实现结构以及开发思路,方便我们少走弯路,也可以使组件更加容易拓展,维护性更强。然后我会把功能逐个拆开来讲,这样大家才能学习到更详细的内容。下面简单阐述下折线图组件的功能结构:

以上是基础的功能结构框架,包含一些比较简单的基础功能,后续还有点击触发、动画等功能也会规划进去。这一期我们先实现上面这些基础的功能,后续再慢慢拓展。

二、公共属性

  1. 一个组件肯定会有一些公共的属性作为动态参数,便于组件之间的信息传递,我们分别讲解一下五个公共属性的作用:画布的宽度(cWidth)和高度(cHeight),这个是最基本的。但是我这里控制是非必传,默认值都是 100%就可以了。
  2. 画布的内部留白间距(cSpace)。主要是用来控制内容区与画布外框的距离,避免绘画的内容被截掉。
  3. 字体大小(fontSize)。主要是来控制整个绘画内容的字体大小,全局性,避免每个小功能都需要传字体大小。
  4. 字体颜色(color)。与字体大小的功能一致。
  5. 图表数据(data)。用来存储图表内容的数组,其中 name 与 value 是必传的。

以下是具体的代码:

// 图表数据的特征接口
interface interface_data {name: string | number;value: string | number;[key: string]: any;
}// 图表的特征接口
interface interface_option {cWidth?: string | number,cHeight?: string | number,fontSize?: string | number,color?: string,cSpace?: number,data?: interface_data[]
}// option 默认值
const def_option: interface_option = {cWidth: '100%',cHeight: '100%',fontSize: 10,color: '#333',cSpace: 20,data: []
}@Component
export struct McLineChart {private settings: RenderingContextSettings = new RenderingContextSettings(true)private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)@State options: interface_option = {}aboutToAppear() {this.options = Object.assign({}, def_option, this.options)}build() {Canvas(this.context).width(this.options.cWidth).height(this.options.cHeight).onReady(() => {})}
}

三、绘画坐标轴

绘画图表内容区部分,首先是绘画坐标轴,坐标轴分为 X 轴跟 Y 轴,我们要先开始画 Y 轴,原因是:y 轴上要显示文本标签,如果一开始没有得到文本标签对应的宽度最大值,那么 Y 轴跟 X 轴的起点坐标就会有偏差,会导致绘画全部错位,下图是完整的坐标轴的效果。

1.绘画 Y 轴

Y 轴整体是由轴线、分割线、刻度线、文本标签四个部分组成的,四个部分都有先后关系,而且包含一定的算法逻辑,下面简单用一个概念图进行讲解。

首先用 500*500 的矩形作为我们这次的画布,我们可以在图上看到 Y 轴整体包含了文本标签、Y 轴线、分割线、刻度线四个部分。而 canvas 绘画基本都是通过坐标来定位的,Y 轴整体的四个部分的起点与结束坐标都互相有关系,甚至需要把内部间距、分割间距、y 轴线高度、文本最大的宽度四个属性计算在内。以上是概念与思路,接下来我们逐一讲解代码:

1、计算得到文本最长宽度(maxNameW ),我们可以从图中看到,不论是 y 轴线、刻度线还是分割线的起点坐标都是需要内容间距、文本标签、文本标签与分割线间隔相加计算得到,而为了保持对齐,所以我们需要计算出文本最长宽度。而 y 轴的文本一般都是数据(data)对应的数值,所以我们需要得到传入数据(data)中的最大值。然后讲最大值分割成五等分。以下就是计算获取最大文本宽度的代码,部分逻辑我也会写在代码上:

build() {Canvas(this.context).width(this.options.cWidth).height(this.options.cHeight).backgroundColor(this.options.backgroundColor).onReady(() => {const values: number[] = this.options.data.map((item) => Number(item.value || 0))const maxValue = Math.max(...values)let maxNameW = 0let cSpiltNum = 5 // 分割等分let cSpiltVal = maxValue / cSpiltNum // 计算分割间距for(var i = 0; i <= this.options.data.length; i++){// 用最大值除于分割等分得到每一个文本的间隔值,而每一次遍历用间隔值乘于i就能得到每个刻度对应的数值了,计算得到得知需要保留整数且转成字符串const text = (cSpiltVal * i).toFixed(0)const textWidth = this.context.measureText(text).width; // 获取文字的长度maxNameW = textWidth > maxNameW ? textWidth : maxNameW // 每次进行最大值的匹配}})
}

2、绘画文本标签,我们可以从图中看到文本标签的 x 坐标只跟内部间距有关,而且我们从上面代码就已经得到每个刻度的分割间距了,从而可以得到每个文本的 y 轴。

.onReady(() => {....for(var i = 0; i <= this.options.data.length; i++){...// 绘画文本标签this.context.fillText(text, this.options.cSpace, cSpiltVal * (this.options.data.length - i) + this.options.cSpace , 0);}
})

3、绘画刻度线。我们可以从概念图得到,刻度线的起点 x 坐标算法是:内部间距(cSpace)加最长文本宽度(maxNameW )加上文本与刻度线的间距,起点 y 坐标则跟文本一样,通过分割间距与下角标的关系得到每个刻度的 y 坐标;而终点 x 坐标则是刻度线的长度,终点 y 坐标则跟起点的 y 坐标一样,我设置默认长度是 5,这样就能得到我们的刻度线了。代码如下:

.onReady(() => {....const length = this.options.data.lengthfor(var i = 0; i <= length; i++){...}// 上面是获取最长文本宽度的代码// 画线的方法function drawLine(x, y, X, Y){this.context.beginPath();this.context.moveTo(x, y);this.context.lineTo(X, Y);this.context.stroke();this.context.closePath();}for(var i = 0; i <= length; i++){const item = this.options.data[i]// 绘画文本标签ctx.fillText(text, this.options.cSpace,  cSpiltVal * (this.data.length - i) + this.options.cSpace, 0);// 内部间距+文本长度const scaleX = this.options.cSpace + maxNameW// 通过数据最大值算出等分间隔,从而计算出每一个的终点坐标const scaleY = cSpiltVal * (length - i) + this.options.cSpace// 这里的5就是我设置文本跟刻度线的间隔与刻度线的长度drawLine(scaleX, scaleY, scaleX + 5 + 5, scaleY);}
})

4、绘画 y 轴线。继续分析概览图,从图中我们可以得到:y 轴线的起点 x 坐标的算法是:内部间距(cSpace)加最长文本宽度(maxNameW )加上文本与刻度线的间距以及刻度线长度,起点 y 坐标则是内部上间距;而终点 x 坐标与起点 x 坐标相同,终点 y 坐标算法是:画布高度减去上下两边的内部间距。通过以上计算关系就能绘画出 y 轴线了。代码如下:

.onReady(() => {...// 上面是绘画其他组成部分代码const startX = this.options.cSpace + maxNameW + 5 + 5const startY = this.options.cSpaceconst endX = startXconst endY = this.context.height - (this.options.cSpace * 2)drawLine(startX, startY, endX, endY); // 绘画y轴
})

5、绘画分割线。其实从图中可以看出分割线与刻度线差不多,起点 x 坐标算法是:在刻度线起点 x 坐标基础上加刻度线长度;起点 y 轴与刻度线相同。而终点的 x 坐标算法:画布宽度减去起点 x 坐标;终点的 y 坐标与起点的 y 坐标相同。具体代码如下:

.onReady(() => {....// 上面是获取最长文本宽度的代码for(var i = 0; i <= length; i++){const item = this.options.data[i]// 绘画文本标签跟刻度...// 绘画分割线const splitX = scaleX + 5 + 5const splitY = scaleYdrawLine(splitX, splitY, this.context.width - splitX - this.options.cSpace, splitY);}
})

2.绘画 X 轴

绘画完 Y 轴之后,我们接着绘画 X 轴, X 轴与 Y 轴绘画逻辑一致,只是方向不同而已。具体的算法就不一一详解,可以参考一下概念图。

而与绘画 Y 轴不一致的在于:

  1. 最长对象不一样。Y 轴最长是文本宽度;而 X 轴需要获取的最长是文本高度。
  2. 间隔分割数不一样。Y 轴是自定义的分割数;而 X 轴分割线是实际数据的长度。
  3. 分割间距长度算法不一样。Y 轴算法是用数据最大值处于自定义的分割数;而 X 轴算法是用画布宽度减去(左右两边的内部间隙以及 Y 轴宽度(文本最长宽度加上刻度线宽度)),再除去数据的长度,得到每个间隔的长度。

除了上面三点需要注意的,其他的就是调换一下计算的位置。X 轴整体的代码如下:

.onReady(() => {const cSpace = this.options.cSpace// 上面是绘制y轴的代码....// 绘制x轴// 获取每个分割线的间距:this.context.width - 20为x轴的长度let xSplitSpacing = parseInt(String((this.context.width - cSpace * 2 - maxNameW) / this.options.data.length))let x = 0;for(var i = 0; i <= this.options.data.length; i++){// 绘画分割线x = xSplitSpacing * (i + 1) // 计算每个数值的x坐标值this.drawLine(x + cSpace + maxNameW, this.context.height - cSpace, x + cSpace + maxNameW, cSpace);// 绘制刻度this.drawLine(x + cSpace + maxNameW, this.context.height - cSpace, x + cSpace + maxNameW, this.context.height - cSpace);// 绘制文字刻度标签const text = this.options.data[i].nameconst textWidth = this.context.measureText(text).width; // 获取文字的长度// 这里文本的x坐标需要减去本身文本宽度的一半,这样才能居中显示, y坐标这是画布高度减去内部间距即可this.context.fillText(text, x + cSpace + maxNameW - textWidth / 2, this.context.height - cSpace, 0);}this.context.save();this.context.rotate(-Math.PI/2);this.context.restore();})

四、绘画折线区

绘画完坐标轴之后,就可以来绘画折线区的内容了。也是整个画布重点的部分。折线区分为三个部分:绘画折线、绘画标点、绘画文本。

1.绘画折线

从上面的图可以看出折线直接就是把实际数据的数值转成 x 跟 y 坐标,再通过连线连接起来。而每一个转折点的 x 坐标算法跟 x 轴的刻度或者文本是一样的,而 y 坐标是实际数值通过一定算法转成我们需要的高度。x 坐标我们已经获取了,只要是攻克我们的 y 坐标即可。可以通过图来观察一下在画布中与实际数据的关系:

首先 Y 轴的高度代表的是实际数据的最大值,这个我们绘画 Y 轴的时候就得到的结果,那我们则可以算出 Y 轴高度与实际数据的缩放倍数(scale),而折线的的每个 y 坐标对应的也是实际数值,需要把实际数值转换成画布中高度,那么就用实际数值乘与刚刚得到的缩放倍数(scale)就能得到转化后的高度了。

虽然我们已经得到每个转折点缩放后的高度,但是如果要跟 Y 轴坐标一一对应的 y 坐标的画,还需要用画布的高度减去下边内部高度加 x 轴高度,再减去缩放后的实际高度。这样算出来的才是我们想要的 y 坐标值,大概算法关系已经知道了,以下是最终代码:

.onReady(() => {...// 上面是绘制x轴跟y轴的代码// 绘画折线const ySacle = (this.context.height - cSpace *2) / maxValue // 计算出y轴与实际最大值的缩放倍数//连线this.context.beginPath();for(var i=0; i< this.options.data.length; i++){const dotVal = String(this.options.data[i].value);const x = xSplitSpacing * (i + 1) + cSpace + maxNameW // 计算每个数值的x坐标值const y = this.context.height - cSpace - parseInt(dotVal * ySacle); // 画布的高度减去下边内部高度加x轴高度,再减去缩放后的实际高度if(i==0){// 第一个作为起点this.context.moveTo( x, y );}else{this.context.lineTo( x, y );}}ctx.stroke();
})

2.绘画标点、文本标签

画完折线我们基本能得到很多东西,比如折线上每个转折点的 x 跟 y 坐标值。这样对我们绘画标点与文本标签就很方便了:

.onReady(() => {...// 上面是绘制x轴跟y轴的代码// 绘画折线const ySacle = (this.context.height - cSpace *2) / maxValue // 计算出y轴与实际最大值的缩放倍数this.context.beginPath();for(var i=0; i< this.options.data.length; i++){// 绘画折线代码...// 绘制标点drawArc(x, y);// 绘制文本标签const textWidth = this.context.measureText(dotVal).width; // 获取文字的长度const textHeight = this.context.measureText(dotVal).height; // 获取文字的长度this.context.fillText(dotVal, x - textWidth / 2, y - textHeight / 2); // 文字}function drawArc( x, y ){this.context.beginPath();this.context.arc( x, y, 3, 0, Math.PI*2 );this.context.fill();this.context.closePath();}this.context.stroke();
})

最终效果如下:

五、总结

以上是本次技术分析,希望能对大家有所启发

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线[包含了大APP实战项目开发]。

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:https://qr21.cn/Bm8gyp

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:https://qr21.cn/Bm8gyp

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:https://qr21.cn/Bm8gyp

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):https://qr21.cn/Bm8gyp

鸿蒙入门教学视频:

美团APP实战开发教学:https://qr21.cn/Bm8gyp

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:https://qr21.cn/FV7h05

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/3639.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux系统编程】基础指令(二)

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

架构师系列- 定时任务(二)- Quartz框架

quartz特点 Quartz是一个优秀的任务调度框架&#xff0c; 具有以下特点 强大的调度功能&#xff0c;例如支持丰富多样的调度方法&#xff0c;可以满足各种常规及特殊需求&#xff1b;负载均衡高可用 quartz 架构体系 Quartz 设计有四个核心类&#xff0c;分别是Scheduler(调度…

一个简单的java递归下降语法分析器例子

import parser.Parser; import parser.RecursiveDescentParser;import java.util.ArrayList; import java.util.Arrays; import java.util.List;public class Main {public static void main(String[] args) {// 关键词List<String> keyList new ArrayList<>(Arra…

NXP i.MX8系列平台开发讲解 - 3.10 Linux PCIe资源分配与访问(二)

目录 1. PCIe BFD 2. PCIe 配置空间 2.1 PCIe 配置空间访问 PCIe I/O访问方法 PCIe MMIO访问方法 3. PCIe BAR相关 4. PCIe Capbility 5. PCIe 操作 本文将重点讲解PCIe的资源访问相关内容&#xff0c;对于PCIe资源访问是从Host 端老看可以对PCIe进行配置与访问的资源主…

【opencv 加速推理】如何安装 支持cuda的opencv 包 用于截帧加速

要在支持CUDA的系统上安装OpenCV&#xff0c;您可以使用pip来安装支持CUDA的OpenCV版本。OpenCV支持CUDA加速&#xff0c;但需要安装额外的库&#xff0c;如cuDNN和NVIDIA CUDA Toolkit。以下是一般步骤&#xff1a; 安装NVIDIA CUDA Toolkit: 首先&#xff0c;您需要安装NVID…

深度学习基础之《TensorFlow框架(12)—图片数据》

一、图像基本知识 1、如何转换图片文件 回忆&#xff1a;之前我们在特征抽取中讲过如何将文本处理成数据 思考&#xff1a;如何将图片文件转换成机器学习算法能够处理的数据&#xff1f; 我们经常接触到的图片有两种&#xff0c;一种是黑白图片&#xff08;灰度图&#xff09;…

网站被SmartScreen标记为不安全怎么办?

在互联网时代&#xff0c;网站的安全性和可信度是用户选择是否继续访问的重要因素之一&#xff0c;然而&#xff0c;网站运营者偶尔会发现使用Edge浏览器访问网站时&#xff0c;会出现Microsoft Defender SmartScreen&#xff08;以下简称SmartScreen&#xff09;提示网站不安全…

Windows下搭建Flutter开发环境

IDE:VS code Flutter官网:Flutter: 为所有屏幕创造精彩 - Flutter 中文开发者网站 - Flutter 下载&安装 下载Flutter SDK,如图,建议自行下载安装: SDK还是挺大的,近1G,使用迅雷下载会快不少。 下载完成,解压缩到指定目录即可! 设置Local SDK,按下面步骤操作即…

【数据结构(邓俊辉)学习笔记】绪论05——动态规划

文章目录 0.前言1. Fibonacci数应用1.1 fib&#xff08;&#xff09;&#xff1a;递归1.1.1 问题与代码1.1.2 复杂度分析1.1.3 递归分析 1.2 fib&#xff08;&#xff09;&#xff1a;迭代 0.前言 make it work,make it right,make it fast. 让代码能够不仅正确而且足够高效地…

适合初学者的自然语言处理 (NLP) 综合指南

一、简述 自然语言处理 (NLP) 是人工智能 (AI) 最热门的领域之一&#xff0c;现在主要指大语言模型了。这要归功于人们热衷于能编写故事的文本生成器、欺骗人们的聊天机器人以及产生照片级真实感的文本到图像程序等应用程序。近年来&#xff0c;计算机理解人类语言、编程语言&a…

前端开发攻略---用原生JS在网页中也能实现文本转语音

1、原理 语音合成 (也被称作是文本转为语音&#xff0c;英语简写是 tts) 包括接收 app 中需要语音合成的文本&#xff0c;再在设备麦克风播放出来这两个过程。 Web API中对此有一个主要控制接口 SpeechSynthesis&#xff0c;外加一些处理如何表示要被合成的文本 (也被称为 utte…

Idea:通义千问插件

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、通义千问大模型 二、程序编写助手 三、Idea安装通义千问插件 总结 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、通义千问大模型…

数据结构——二叉树的操作 (层序遍历)(C++实现)

数据结构——二叉树的操作&#xff08;2&#xff09;&#xff08;C实现&#xff09; 统计叶子结点个数统计结点个数层序遍历非递归方式递归方式 我们今天接着来看二叉树的操作&#xff0c;如果还没有看过上一篇的可以点击这里&#xff1a; https://blog.csdn.net/qq_67693066/a…

来自异国的客人 - 华为OD统一考试(D卷)

OD统一考试(D卷) 分值: 100分 题解: Java / Python / C++ 题目描述 有位客人来自异国,在该国使用m进制计数。 该客人有个幸运数字n(n<m),每次购物时,其总是喜欢计算本次支付的花费(折算为异国的价格后)中存在多少幸运数字。 问: 当其购买一个在我国价值k的产品时,…

【Leetcode每日一题】 穷举vs暴搜vs深搜vs回溯vs剪枝_全排列 - 子集(难度⭐⭐)(65)

1. 题目解析 题目链接&#xff1a;78. 子集 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 算法思路详解&#xff1a; 为了生成数组 nums 的所有子集&#xff0c;我们需要对数组中的每个元素进行“选择”或“不选择…

JavaScript-Vue入门

本文主要测分享Vue的一些基础 Vue简介 Vue.js 是一个构建数据驱动的 web 界面的渐进式框架。它的主要目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。 下是一些 Vue 的主要特点和概念&#xff1a; 1. 响应式数据绑定&#xff1a;Vue 使用基于 HTML 的模板语法…

Visual Studio安装MFC开发组件

MFC由于比较古老了&#xff0c;Visual Studio默认没有这个开发组件。最近由于一些原因&#xff0c;需要使用这个库&#xff0c;这就需要另外安装。 参考了网上的一些资料&#xff0c;根据实际使用&#xff0c;其实很多步骤不是必须的。 https://zhuanlan.zhihu.com/p/68117276…

Neo-reGeorg(webshell代理)-php

https://github.com/L-codes/Neo-reGeorg python加载需要的库 pip install requests 生成webshell代理文件 python .\neoreg.py generate -k 123456 其中&#xff0c;123456为密码&#xff0c;可以修改 生成后的文件路径&#xff1a; > neoreg_servers/tunnel.ashx …

Java后台开发的前置说明

1.知识点逻辑 一个部分 都是先挑重点知识点讲解 然后根据这些重点知识点去完成一个项目的开发 然后在到返回来解决这个部分其他细枝末节的知识点 2.软件开发的分工 我们大致可以将软件开发分成四块&#xff1a; 1.前端开发(比如开发电脑中的京东 htmlcssjavascript) 2.移动开…

什么是主数据管理?合理管理主数据,奠定企业数据战略基石

主数据&#xff08;Master Data&#xff09;是指在企业多个业务系统中重复使用、共享的、具有高价值的核心业务实体数据&#xff0c;如客户、产品、供应商、员工等&#xff0c;它们是企业运营和决策的基础&#xff0c;需要保持高度的准确性、一致性和完整性&#xff0c;以确保业…