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

評論