大家好,我是陳楊。在上一篇文章中,我簡(jiǎn)要介紹了折線圖的實(shí)現(xiàn)邏輯,并解釋了整體圖表的繪制規(guī)則。根據(jù)這些規(guī)則,我們還可以繪制更多種類的圖表組件。在本期中,我將講解如何實(shí)現(xiàn)柱狀圖,并引入了一個(gè)新的功能。鑒于柱狀圖跟折線圖可共用的基礎(chǔ)配置很多,我將不再重復(fù)介紹基礎(chǔ)知識(shí),如果你對(duì)此感興趣,可以翻閱上一篇文章了解更多內(nèi)容。
在開(kāi)始技術(shù)講解之前,我想插播一個(gè)消息。為了方便大家今后使用與圖表相關(guān)的組件,我已經(jīng)陸續(xù)封裝了相關(guān)系列的組件,使其可以開(kāi)箱即用。未來(lái),我還將采取開(kāi)源策略,希望大家共同分享創(chuàng)造更多實(shí)用的工具。目前,相關(guān)組件的使用文檔地址為:[http://meichuang.org.cn/McBarChart]
進(jìn)入正題,老規(guī)矩先把實(shí)現(xiàn)結(jié)構(gòu)整理出來(lái)。結(jié)構(gòu)中有一些我上篇文章已經(jīng)講過(guò),所以這里不再做過(guò)多的闡述。直講新內(nèi)容
- 公共屬性
- 畫(huà)布的寬高
- 畫(huà)布內(nèi)部間距
- ...
- 繪畫(huà)坐標(biāo)
- 繪畫(huà)x與y坐標(biāo)軸
- ...
- 繪畫(huà)柱狀區(qū)
- 繪畫(huà)柱子
- 繪畫(huà)特性功能(文本標(biāo)簽)
- Tooltip( 新 )
- 基本屬性
- 定位顯示
繪畫(huà)柱狀區(qū)
在上一篇文章中,已經(jīng)包含了定義公共屬性和繪制坐標(biāo)的代碼。如果您對(duì)此感興趣,可以去查看。本期的內(nèi)容主要涵蓋了柱狀圖區(qū)域的基本屬性、柱子的繪制以及特性功能的實(shí)現(xiàn)。
繪畫(huà)柱子
先繪畫(huà)出柱狀區(qū)的概覽圖:
通過(guò)概覽圖,我們可以得到以下步驟:首先計(jì)算出每個(gè)刻度的X坐標(biāo),然后將數(shù)值轉(zhuǎn)換為對(duì)應(yīng)的高度,并設(shè)置柱子的寬度,最后利用canvas的矩形繪制方法來(lái)繪制相應(yīng)的柱子。接下來(lái)我們將詳細(xì)介紹具體的算法:
每個(gè)刻度的X坐標(biāo)算法 :首先將實(shí)際數(shù)據(jù)長(zhǎng)度length
除以畫(huà)布的寬度width
,得到等分刻度。然后,將等分刻度乘以索引值即可得到每個(gè)刻度的X坐標(biāo)。
數(shù)據(jù)轉(zhuǎn)化高度算法 :首先將實(shí)際數(shù)據(jù)的最大值maxValue
除以畫(huà)布的高度height
,得到縮放倍數(shù)。然后,使用每個(gè)刻度的實(shí)際數(shù)值乘以縮放倍數(shù)即可得到對(duì)應(yīng)的高度值。
柱子寬度 :設(shè)置基礎(chǔ)值,可以動(dòng)態(tài)傳參。
了解大概算法,我們將算法轉(zhuǎn)換成代碼。代碼如下:
.onReady(() = > {
...
// 上面是繪制x軸跟y軸的代碼
// 繪畫(huà)折線
const ySacle = (this.context.height - cSpace *2) / maxValue // 計(jì)算出y軸與實(shí)際最大值的縮放倍數(shù)
//連線
for(var i=0; i< this.options.data.length; i++){
const dotVal = String(this.options.data[i].value)
const barW = 10
// 畫(huà)布的高度減去下邊內(nèi)部高度加x軸高度
const barH = parseInt(dotVal * ySacle)
// 計(jì)算每個(gè)數(shù)值的x坐標(biāo)值,減去barW/2是為了柱子能夠居中顯示
const x = xSplitSpacing * (i + 1) + cSpace + maxNameW - barW / 2
// 由于畫(huà)布的左邊是從左上角開(kāi)始計(jì)算的,用畫(huà)布高度減去縮放后的高度得到柱子頂部的坐標(biāo)
const y = this.context.height - cSpace - barH
ctx.beginPath()
ctx.rect( x, y, barW, barH)
ctx.fillStyle = "green"
ctx.fill()
ctx.closePath()
}
ctx.stroke();
})
繪畫(huà)特性功能(文本標(biāo)簽)
繪制文本標(biāo)簽其實(shí)也很簡(jiǎn)單。由于我們已經(jīng)計(jì)算出每根柱子的起點(diǎn)坐標(biāo),所以只需要將計(jì)算得到的坐標(biāo)減去文本的寬度和高度,就可以得到文本標(biāo)簽的位置。以下是具體的代碼示例:
.onReady(() = > {
...
// 上面是繪制x軸跟y軸的代碼
// 繪畫(huà)折線
const ySacle = (this.context.height - cSpace *2) / maxValue // 計(jì)算出y軸與實(shí)際最大值的縮放倍數(shù)
//連線
for(let i = 0; i < this.options.data.length; i++){
const dotVal = String(this.options.data[i].value)
const barW = 10
// 畫(huà)布的高度減去下邊內(nèi)部高度加x軸高度
const barH = parseInt(dotVal * ySacle)
// 計(jì)算每個(gè)數(shù)值的x坐標(biāo)值,減去barW/2是為了柱子能夠居中顯示
const x = xSplitSpacing * (i + 1) + cSpace + maxNameW - barW / 2
// 由于畫(huà)布的左邊是從左上角開(kāi)始計(jì)算的,用畫(huà)布高度減去縮放后的高度得到柱子頂部的坐標(biāo)
const y = this.context.height - cSpace - barH
... 繪畫(huà)柱子
// 繪制文本標(biāo)簽
const textWidth = this.context.measureText(dotVal).width; // 獲取文字的長(zhǎng)度
const textHeight = this.context.measureText(dotVal).height; // 獲取文字的長(zhǎng)度
this.context.fillText(dotVal, x - textWidth / 2, y - textHeight / 2); // 文字
}
ctx.stroke();
})
整個(gè)繪制基礎(chǔ)柱狀圖的功能已經(jīng)完成了。大家可以嘗試使用,并根據(jù)自己的業(yè)務(wù)需求來(lái)實(shí)現(xiàn)相應(yīng)的功能。希望這些代碼能夠?qū)δ兴鶐椭?/p>
Tooltip(提示層)
在講解完整個(gè)柱狀圖的繪畫(huà)之后,接下來(lái)我們將探討如何實(shí)現(xiàn)提示層功能。提示層功能在使用圖表呈現(xiàn)數(shù)據(jù)時(shí)是必不可少的,它可以讓數(shù)據(jù)更加直觀地展示,同時(shí)增加圖表的交互性,避免過(guò)于單調(diào)。
如果使用傳統(tǒng)的 JavaScript 開(kāi)發(fā)機(jī)制,實(shí)現(xiàn)提示層功能相對(duì)簡(jiǎn)單:點(diǎn)擊圖表內(nèi)容,判斷坐標(biāo)獲取對(duì)應(yīng)索引數(shù)據(jù),動(dòng)態(tài)創(chuàng)建
元素來(lái)展示數(shù)據(jù),計(jì)算畫(huà)布和提示層的寬高,并決定提示層的最佳位置。這是大致的實(shí)現(xiàn)思路。
然而,在 ArkTS 語(yǔ)言中,我們需要解決以下兩個(gè)問(wèn)題:
- 無(wú)法動(dòng)態(tài)創(chuàng)建
元素。 - 無(wú)法實(shí)時(shí)獲取元素的寬高。
- 無(wú)法觸發(fā)組件之外的內(nèi)容隱藏提示層。
針對(duì)這些問(wèn)題,我們可以按照以下步驟將思路轉(zhuǎn)化為代碼并解決相應(yīng)的問(wèn)題。
綁定事件/創(chuàng)建動(dòng)態(tài)組件
首先,對(duì)畫(huà)布進(jìn)行單擊事件的綁定,并獲取點(diǎn)擊位置的 x
和 y
值。然后循環(huán)遍歷數(shù)據(jù),對(duì)比判斷 x
值是否大于對(duì)應(yīng)索引的刻度值,如果大于,則記錄對(duì)應(yīng)的索引數(shù)據(jù),否則繼續(xù)判斷。代碼示例如下:
Canvas(this.context)
.width('100%')
.height('100%')
.onReady(() = > {
// 繪畫(huà)內(nèi)容區(qū)
})
.gesture(
TapGesture({ count: 1 })
.onAction((e: GestureEvent) = > {
const ctx = this.context
// 獲取點(diǎn)擊的x、y值
let pos = {
x: e.localX,
y: e.localY
}
// 獲取x軸的刻度等分
let xSplitSpacing = parseInt(String((ctx.width - cSpace * 2 - maxNameW) / this.options.data.length))
// 循環(huán)數(shù)據(jù)判斷
const activeObj = {}
const activeX = null
for(let i = 0; i < this.options.data.length; i++){
const item = this.options.data[i]
if(pos.x > i * xSplitSpacing) {
activeObj = item
activeX = i * xSplitSpacing
}
}
// 顯示提示層
if(this.activeX !== null) {
....
}
})
)
由于我們沒(méi)有辦法直接動(dòng)態(tài)添加元素,那我們要先定義好一個(gè)組件來(lái)呈現(xiàn)我們的數(shù)據(jù),動(dòng)態(tài)控制顯示跟隱藏。
@State tooltipInfo: InterfaceObj = {}
@State tooltipPos: InterfaceObj = {
x: -100000,
y: -100000
}
Column() {
Canvas(this.context)
.width('100%')
.height('100%')
.onReady(() = > {
// 繪畫(huà)內(nèi)容區(qū)
})
.gesture(
TapGesture({ count: 1 })
.onAction((e: GestureEvent) = > {
...判斷邏輯
// 顯示提示層
if(this.activeX !== null) {
this.tooltipInfo = activeObj
this.tooltipPos.x = activeX
}
})
)
if(Object.keys(this.tooltipInfo).length) {
Column () {
Text(this.tooltipInfo.title)
ForEach(this.tooltipInfo.data, (item, index) = > {
Text(item.name + ':' + item.num)
})
}
}
}
好的,利用ArkTS提供的渲染控制能力來(lái)動(dòng)態(tài)顯示元素節(jié)點(diǎn)是一個(gè)不錯(cuò)的解決方案。這樣我們就成功地解決了無(wú)法動(dòng)態(tài)添加節(jié)點(diǎn)的問(wèn)題,并順利完成了第一步。
定位適配顯示
接下來(lái),我們需要實(shí)現(xiàn)適配顯示的定位功能,使提示層能夠定位在鼠標(biāo)點(diǎn)擊的位置,并且不超出屏幕范圍。在上面顯示提示層時(shí),我記錄了點(diǎn)擊圖表時(shí)數(shù)據(jù)項(xiàng)對(duì)應(yīng)的 X 坐標(biāo),這樣就可以為提示層設(shè)置相對(duì)定位的 X 屬性。至于 Y 軸的定位,我選擇居中顯示,當(dāng)然你們也可以根據(jù)數(shù)據(jù)項(xiàng)的 Y 坐標(biāo)進(jìn)行定位。
在設(shè)置提示層的 X 坐標(biāo)時(shí),當(dāng)點(diǎn)擊右邊最后幾個(gè)數(shù)據(jù)項(xiàng)或者提示層內(nèi)容較大時(shí),可能會(huì)導(dǎo)致提示層超出畫(huà)布內(nèi)容,從而造成數(shù)據(jù)顯示不全。解決這個(gè)問(wèn)題的方法也比較簡(jiǎn)單:判斷獲取提示層自身的寬度加上 X 坐標(biāo)是否大于畫(huà)布寬度,如果大于,則證明超出了畫(huà)布的范圍。然后,將 X 坐標(biāo)減去畫(huà)布的寬度,就可以得到最終的 X 坐標(biāo)。
然而,問(wèn)題也隨之而來(lái)。由于 ArkTS 沒(méi)有提供直接獲取某些元素寬度和高度的功能,一開(kāi)始我以為無(wú)法繼續(xù)下去了。但是,在仔細(xì)閱讀官方文檔之后,終于發(fā)現(xiàn)了一點(diǎn)線索。這里我就不賣關(guān)子了,這個(gè) API 就是"組件區(qū)域變化事件"。你可以在官方文檔中找到相關(guān)的信息:
這個(gè)事件主要用于監(jiān)聽(tīng)某個(gè)元素位置或尺寸的變化,并在變化發(fā)生時(shí)回調(diào),提供最新的位置和尺寸信息。這正好符合我們的需求,因?yàn)槲覀兊?X 坐標(biāo)是不斷變化的,這樣我們就可以獲取到元素的尺寸了。下面是完整的代碼示例:
@State tooltipInfo: InterfaceObj = {}
@State tooltipPos: InterfaceObj = {
x: -100000,
y: -100000
}
Column() {
Canvas(this.context)
.width('100%')
.height('100%')
.onReady(() = > {
// 繪畫(huà)內(nèi)容區(qū)
})
.gesture(
// 點(diǎn)擊畫(huà)布相關(guān)事項(xiàng)
)
if(Object.keys(this.tooltipInfo).length) {
Column () {
Text(this.tooltipInfo.title)
ForEach(this.tooltipInfo.data, (item, index) = > {
Text(item.name + ':' + item.num)
})
}
.position({
x: this.tooltipPos.x,
y: this.tooltipPos.y
})
.onAreaChange((oldValue: Area, newValue: Area) = > {
const { x } = this.tooltipInfo.pos
const { width: W, height: H } = this.context
const { width, height } = newValue
if (x + 40 + width > W - 10) {
this.tooltipPos.x = x - width + 20
} else {
this.tooltipPos.x = x + 40
}
this.tooltipPos.y = H / 2 - height / 2
})
}
}
結(jié)束
講到這里,我們已經(jīng)完成了柱狀圖組件以及提示層功能的開(kāi)發(fā),并成功封裝成了組件,即將發(fā)布到相關(guān)的文檔上。希望大家在使用過(guò)程中能夠?qū)W到很多知識(shí)。如果你有任何需要交流的地方,請(qǐng)?jiān)谙旅媪粞栽u(píng)論,我會(huì)第一時(shí)間回復(fù)你。感謝大家的支持!
審核編輯 黃宇
-
鴻蒙
+關(guān)注
關(guān)注
60文章
2620瀏覽量
44058
發(fā)布評(píng)論請(qǐng)先 登錄
開(kāi)源啦!?。』?b class='flag-5'>鴻蒙ArkTS封裝的圖表組件《McCharts》,大家快來(lái)一起共創(chuàng)
《Visual C# 2008程序設(shè)計(jì)經(jīng)典案例設(shè)計(jì)與實(shí)現(xiàn)》---柱狀圖表分析圖
《Visual C# 2008程序設(shè)計(jì)經(jīng)典案例設(shè)計(jì)與實(shí)現(xiàn)》---柱狀圖表的升序和降序
《Visual C# 2008程序設(shè)計(jì)經(jīng)典案例設(shè)計(jì)與實(shí)現(xiàn)》---利用Windows組件打印數(shù)據(jù)庫(kù)數(shù)據(jù)柱狀圖表
請(qǐng)問(wèn)labview中使用net的chart控件如何實(shí)現(xiàn)柱狀圖?
Matplotlib繪制柱柱狀圖、直方圖、條形圖的使用語(yǔ)法
Labview 柱狀圖
LabVIEW強(qiáng)大的生產(chǎn)產(chǎn)量---柱狀圖表---嵌入MES系統(tǒng)
怎么將每小時(shí)得到的合格和不合格的結(jié)果用柱狀圖表示出來(lái)?
怎樣用MATLAB去給柱狀圖加數(shù)據(jù)標(biāo)簽?zāi)?/a>
HarmonyOS/OpenHarmony應(yīng)用開(kāi)發(fā)-ArkTS語(yǔ)言基本語(yǔ)法說(shuō)明
如何用seabron生成柱狀圖和散點(diǎn)圖

評(píng)論