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

評論