HarmonyOs - ArkUI(JS)画布组件Canvas之自定义柱状图

系统 OpenHarmony
最近项目中有柱状图的功能,看了下JS中的组件chart,发现并不适用要求,研究之后决定用canvas动手画一个。

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

前言

最近项目中有柱状图的功能,看了下JS中的组件chart,发现并不适用要求,研究之后决定用canvas动手画一个。

项目说明

本项目基于ArkUI中JS扩展的类Web开发范式,关于语法和概念直接看官网官方文档地址:​​基于JS扩展的类Web开发范式1​​​ ​​​基于JS扩展的类Web开发范式2​​。

  • 工具版本:DevEco Studio 3.0 Beta2。
  • SDK版本:3.0.0.1(API Version 7 Beta2)。

提供画布组件,用于自定义绘制图形:​​画布组件canvas​​。

效果演示

默认选中第一条,点击柱状图,切换选中效果。

但是发现绘制复杂一点的内容,调用清屏接口clearRect()后,重新绘制内容会闪烁,在官网论坛上面问了其他人,也都有遇到此问题,希望官方早点修复这个bug。

使用到的API

实现步骤

组件设置了上下左右间距,所以内容区域宽度 = 组件宽度 - 左间距 - 右间距,内容区域的高度 = 组件高度 - 上间距 - 下间距。

1、画横线(x轴)、右边的文字

// 获取画布组件
const element = this.$refs.canvas;
// 获取绘图上下文
const ctx = element.getContext('2d', {antialias: true});
// 获取组件大小和位置信息
const rect = element.getBoundingClientRect()
// 测量y轴最大数的文本长度
const yTextMaxWidth = ctx.measureText('' + this.yAxisMaxValue).width
// x轴起始坐标
const xAxisStart = this.paddingLeft
// x轴结束坐标 = 组件宽度 - 右间距 - y轴文字最大宽度
const xAxisEnd = rect.width - this.paddingRight - yTextMaxWidth
// y轴初始坐标
const yAxisStart = this.paddingTop
// y轴结束坐标 = 组件高度 - 底间距 - 底部文字高度 - 文字顶间距
const yAxisEnd = rect.height - this.paddingBottom - 10 - this.xAxisTextTopPadding
// y轴初始值-画文字
let yValue = this.yAxisMaxValue
// y轴平均值-画文字
let yAverageValue = (this.yAxisMaxValue - this.yAxisMinValue) / this.yAxisDivide
// y轴初始坐标
let yAxis = yAxisStart
// 画横线、画右边文字
while (yAxis <= yAxisEnd) {
// 画线
// 对当前的绘图上下文进行保存
ctx.save()
// 线颜色
ctx.strokeStyle = this.xAxisColor
// 创建一个新的绘制路径
ctx.beginPath()
// 线条的宽度
ctx.lineWidth = this.xAxisWidth
// 路径从当前点移动到指定点
ctx.moveTo(xAxisStart, yAxis)
// 从当前点到指定点进行路径连接
ctx.lineTo(xAxisEnd, yAxis)
// 进行边框绘制操作
ctx.stroke();
// 对保存的绘图上下文进行恢复
ctx.restore()
// 画文本
ctx.save()
// 指定绘制的填充色
ctx.fillStyle = this.yAxisTextColor
// 设置文本绘制中的字体样式
ctx.font = this.yAxisTextFont
// 设置文本绘制中的文本对齐方式:文本右对齐
ctx.textAlign = 'right'
// 绘制填充类文本
ctx.fillText('' + yValue, xAxisEnd + yTextMaxWidth, yAxis + 2.5)
ctx.restore()
// 右边文本
yValue -= yAverageValue
// 更新y轴坐标:每次加上y轴等分
yAxis += (yAxisEnd - yAxisStart) / this.yAxisDivide
}

2、画圆柱图、和底部文字

 // 画x轴的实际宽度,两边柱状图不靠边
const xDrawWidth = xAxisEnd - xAxisStart - this.xAxisPadding * 2
// 起始点
let xAxis = this.paddingLeft + this.xAxisPadding
// x轴平均值
let xAverageWidth = (xDrawWidth) / (this.chartData.length - 1)
// 画x轴上的文字和数据
for (let i = 0; i < this.chartData.length; i++) {
const item = this.chartData[i]
// 画文本
ctx.save()
ctx.fillStyle = this.xAxisTextColor
ctx.font = this.xAxisTextFont
ctx.textAlign = 'center'
ctx.fillText(item.xData, xAxis, rect.height - this.paddingBottom)
ctx.restore()
// 画柱图
ctx.save()
// 创建一个新的绘制路径
ctx.beginPath()
// 线条的宽度,设置到最小,因为我们需要填充圆柱
ctx.lineWidth = 0.1
// 路径从当前点移动到指定点
ctx.moveTo(xAxis - this.columnarWidth / 2, yAxisEnd - this.xAxisWidth)
// 根据数据比例得出 画的高度
const yDrawHeight = (yAxisEnd - yAxisStart) * item.yData / this.yAxisMaxValue
// 从y轴结束开始画,值结束的坐标:yAxisValueEnd 为了显示圆角:+ 柱图宽度/2(圆的半径)
const yAxisValueEnd = yAxisEnd - yDrawHeight + this.xAxisWidth + this.columnarWidth / 2
// 从当前点到指定点进行路径连接
ctx.lineTo(xAxis - this.columnarWidth / 2, yAxisValueEnd)
// 画圆角
ctx.arc(xAxis, yAxisValueEnd, this.columnarWidth / 2, 3.14, 0)
// 从当前点到指定点进行路径连接
ctx.lineTo(xAxis + this.columnarWidth / 2, yAxisEnd - this.xAxisWidth)
// 进行边框绘制操作
ctx.stroke()
// 填充颜色
ctx.fillStyle = this.columnarColor
// 填充
ctx.fill()
ctx.restore()
// 更新x轴坐标:每次加上x轴等分
xAxis += xAverageWidth
}

3、点击选中效果:画柱图时,记录x轴的位置

// 存入索引和对应的坐标
this.columnarXArray = []
// 画x轴上的文字和数据
for (let i = 0; i < this.chartData.length; i++) {
const item = this.chartData[i]
const columnarX = {
index: i, columnarX: xAxis
}
// 记录x轴,索引和对应的坐标
this.columnarXArray.push(columnarX)
// 画文本
......
// 文本颜色
ctx.fillStyle = this.selectIndex === i ? this.selectXAxisTextColor : this.xAxisTextColor
......
// 画选中的线
if(this.selectIndex === i){
// 画线
ctx.save()
ctx.strokeStyle = this.yAxisColor
ctx.lineCap = 'butt'
ctx.lineWidth = this.yAxisWidth
ctx.beginPath()
ctx.moveTo(xAxis, yAxisEnd - this.xAxisWidth)
ctx.lineTo(xAxis, this.paddingTop)
ctx.stroke();
ctx.restore()
}
// 画柱图
......
// 填充颜色
ctx.fillStyle = this.selectIndex === i ? this.selectColumnarColor : this.columnarColor
......
}

根据触摸事件的坐标,判断是否在范围内,更新选中的索引。

// 触摸按下
onTouchStart(event) {
console.log(event.touches[0].localX)
// 点击x坐标,相对于组件
const clickX = event.touches[0].localX
let lastSelectIndex = this.selectIndex
// 筛选出点击的柱图索引
for (let i = 0; i < this.columnarXArray.length; i++) {
let item = this.columnarXArray[i]
if (Math.abs(item.columnarX - clickX) <= this.columnarWidth + 5) {
this.selectIndex = item.index
break
}
}
// 重新绘制
if (this.selectIndex !== lastSelectIndex) {
this.draw()
}
}

项目地址:

完整代码:https://gitee.com/liangdidi/HistogramDemo。

总结

此项目并没有特别复杂的地方,注释也很详细,主要是xy轴的起始、结束位置的计算,屏幕的原点坐标(0,0)是在左上角,最后根据系统提供的api画出想要的效果。有些效果看起来很复杂,但是一步一步的拆解,懂得其原理之后,多练多用,也能做出炫酷的效果。

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​​。

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2022-05-20 14:34:20

list组件鸿蒙操作系统

2021-11-24 10:02:53

鸿蒙HarmonyOS应用

2022-07-06 20:24:08

ArkUI计时组件

2021-11-01 10:21:36

鸿蒙HarmonyOS应用

2022-02-21 15:16:30

HarmonyOS鸿蒙操作系统

2021-01-06 10:05:09

鸿蒙HarmonyOSCanvas

2021-09-15 10:19:15

鸿蒙HarmonyOS应用

2022-06-20 15:43:45

switch开关鸿蒙

2022-06-30 14:02:07

鸿蒙开发消息弹窗组件

2022-07-15 16:45:35

slider滑块组件鸿蒙

2022-02-16 15:25:31

JS代码Canvas鸿蒙

2022-02-16 16:09:12

鸿蒙游戏操作系统

2022-04-24 15:17:56

鸿蒙操作系统

2022-07-12 16:56:48

自定义组件鸿蒙

2022-09-09 14:47:50

CircleArkUI

2021-12-24 15:46:23

鸿蒙HarmonyOS应用

2022-10-10 14:51:51

ArkUI eTSPieChart组件

2022-10-09 15:13:18

TextPickerArkUI eTS

2023-02-20 15:20:43

启动页组件鸿蒙

2022-05-26 14:50:15

ArkUITS扩展
点赞
收藏

51CTO技术栈公众号