目錄
- 前言
- 相關系列
- 代碼示例詳解(LineSeriesDemo3.qml)
- 功能概覽
- 運行效果
- 代碼說明
- 工程下載
- 參考
前言
接上文(QML Charts組件之折線圖的基礎屬性),本文將重點介紹LineSeries的鼠標交互,包括:鼠標拖拽平移、滾輪縮放等操作。
相關系列
- QML Charts組件之折線圖的基礎屬性
- QML Charts組件之LineSeries、SplineSeries與ScatterSeries
- QML Charts組件之坐標軸共有屬性
代碼示例詳解(LineSeriesDemo3.qml)
示例文件:Series/LineSeriesDemo3.qml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtChartsRectangle {Layout.fillWidth: trueLayout.fillHeight: trueColumnLayout {anchors.fill: parentChartView {id: chartViewtitle: "折線圖示例"titleFont.bold: truetitleFont.pointSize: 14Layout.fillWidth: trueLayout.fillHeight: trueantialiasing: trueValueAxis {id: axis_xmin: 0max: 10tickCount: 11}ValueAxis {id: axis_ymin: 0max: 10tickCount: 11}LineSeries {id: seriesname: "line"color: "#1296FF"width: 3bestFitLineColor : "#00FF00"bestFitLineVisible : true// 設置點是否可見pointsVisible : true// 設置點標簽是否可見pointLabelsVisible : true// 設置點標簽文本顏色pointLabelsColor: "#FF0000"// 設置點標簽字體pointLabelsFont.bold: truepointLabelsFont.pointSize: 12pointLabelsFont.family: "Courier"// 設置點標簽顯示格式// 具體格式可能受Qt版本或平臺影響// 如果數據太多,會影響性能// 格式標簽限制:不支持更復雜的運算或格式化指令,如果是小數,可能需要對點數據預處理pointLabelsFormat : "(@xPoint,@yPoint)"// 控制當點標簽超出繪圖區域時是否被裁剪,默認為true// 設為false可允許標簽顯示在繪圖區域之外,但需要注意可能布局重疊。pointLabelsClipping : trueaxisX: axis_xaxisY: axis_yonClicked: function(point){console.log("onClicked: " + Math.round(point.x) + ", " + Math.round(point.y));}}// 交互:拖拽平移與滾輪縮放property real __panStartX: 0property real __panStartY: 0property real __panStartMinX: 0property real __panStartMaxX: 0property real __panStartMinY: 0property real __panStartMaxY: 0MouseArea {anchors.fill: parentacceptedButtons: Qt.LeftButtonhoverEnabled: truepreventStealing: trueonPressed: function(mouse) {var pa = chartView.plotAreaif (!(mouse.x >= pa.x && mouse.x <= pa.x + pa.width &&mouse.y >= pa.y && mouse.y <= pa.y + pa.height)) {return}chartView.__panStartX = mouse.xchartView.__panStartY = mouse.ychartView.__panStartMinX = axis_x.minchartView.__panStartMaxX = axis_x.maxchartView.__panStartMinY = axis_y.minchartView.__panStartMaxY = axis_y.max}onPositionChanged: function(mouse) {if (!pressed) returnvar pa = chartView.plotAreaif (pa.width <= 0 || pa.height <= 0) returnvar dx = mouse.x - chartView.__panStartXvar dy = mouse.y - chartView.__panStartYvar rangeX = chartView.__panStartMaxX - chartView.__panStartMinXvar rangeY = chartView.__panStartMaxY - chartView.__panStartMinY// 像素 -> 數值映射(注意Y軸方向)var valueDeltaX = -dx * rangeX / pa.widthvar valueDeltaY = dy * rangeY / pa.heightaxis_x.min = chartView.__panStartMinX + valueDeltaXaxis_x.max = chartView.__panStartMaxX + valueDeltaXaxis_y.min = chartView.__panStartMinY + valueDeltaYaxis_y.max = chartView.__panStartMaxY + valueDeltaY}}WheelHandler {// 以光標為中心縮放,支持鼠標與觸控板acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPadtarget: chartViewonWheel: function(event) {var pa = chartView.plotArea//var pos = event.pointvar x = event.xvar y = event.yif (!(x >= pa.x && x <= pa.x + pa.width &&y >= pa.y && y <= pa.y + pa.height)) {return}// 計算縮放步數(15度/步),向上滾動放大var degrees = event.angleDelta.y / 8.0var steps = degrees / 15.0var factor = Math.pow(1.2, -steps)var minX = axis_x.minvar maxX = axis_x.maxvar minY = axis_y.minvar maxY = axis_y.maxvar rangeX = maxX - minXvar rangeY = maxY - minYif (rangeX <= 0 || rangeY <= 0) returnvar fx = (x - pa.x) / pa.widthvar fy = (y - pa.y) / pa.heightfx = Math.max(0, Math.min(1, fx))fy = Math.max(0, Math.min(1, fy))var newRangeX = rangeX * factorvar newRangeY = rangeY * factor// 以光標數值位置為錨點縮放var newMinX = minX + fx * (rangeX - newRangeX)var newMinY = minY + fy * (rangeY - newRangeY)axis_x.min = newMinXaxis_x.max = newMinX + newRangeXaxis_y.min = newMinYaxis_y.max = newMinY + newRangeYevent.accepted = true}}// Add data dynamically to the seriesComponent.onCompleted: {for (var i = 0; i <= 5; i++) {var num = Math.floor((Math.random()*10))series.append(i, num);}}}Row {Layout.minimumHeight: 50Button {text: "append"width: 100height: 25onClicked: {var num = Math.floor((Math.random()*10))series.append(series.count, num)axis_x.min++;axis_x.max++;}}Button {text: "remove"width: 100height: 25onClicked: {if (series.count > 0) {series.removePoints(series.count-1, 1)axis_x.min--;axis_x.max--;}}}}}
}
功能概覽
這段 QML 代碼實現了一個可交互的折線圖窗口,能夠進行鼠標拖拽平移和滾輪縮放操作。
功能 | 實現方式 |
---|---|
顯示折線圖 | 使用ChartView 和 LineSeries |
動態追加 / 刪除數據 | 使用 series.append 和 series.removePoints 方法。 |
鼠標拖拽平移 | 使用MouseArea ,記錄按下/移動坐標。 |
滾輪縮放 | 使用WheelHandler ,以光標為中心縮放,支持鼠標與觸控板。 |
point標簽顯示 | 使用pointLabelsFormat : "(@xPoint,@yPoint)", 顯示坐標。 |
點擊數據點 | 觸發onClicked 信號,打印坐標。 |
運行效果
代碼說明
折線屬性: 詳細的屬性描述見上文 — QML Charts組件之折線圖的基礎屬性
拖拽平移(鼠標左鍵按住拖圖表):
MouseArea {anchors.fill: parentonPressed: { /* 記錄初始狀態 */ }onPositionChanged: { /* 根據位移更新軸范圍 */ }
}
1. 按下瞬間干什么?
記住四件事:
- 鼠標當時在屏幕的 x、y 坐標。
- 當時 X 軸的最小值、最大值。
- 當時 Y 軸的最小值、最大值。
它們一起構成初始快照,后面所有計算都以這個快照為基準,不會累積誤差。
2. 拖動過程中干什么?
- 把鼠標當前位置跟按下時的位置做減法,得到像素位移 dx、dy。
- 用 dx 除以繪圖區寬度,得到橫向走了百分之幾;同樣處理 dy。
- 把百分比乘以當時的軸范圍,就換算成全局坐標該走多少。
- 右拖 → 畫面要向右,于是把軸整體向左平移相同的量;左拖相反。
- 下拖 → 畫面要向上,于是把軸整體向下平移;上拖相反。
- 每移動一次鼠標,就重新給軸的最小、最大值賦一次新結果,圖就實時跟過來了。
簡而言之 鼠標拖拽 是把鼠標像素差按寬高比例變成軸坐標差,然后整體平移軸范圍。
滾輪縮放(以光標為中心放大/縮小):
WheelHandler {onWheel: function(event) {// 計算縮放步數(15度/步),向上滾動放大...// 以光標數值位置為錨點縮放...}
}
1. 什么時候生效?
只在繪圖區內部滾輪才處理,滾到外邊就不管,避免整個窗口一起亂動。
2. 滾一次算幾步?
系統告訴你這次滾了多少度,15° 算一步。向上滾一步算 +1,向下滾一步算 ?1。
把步數代進公式 1.2^(-步數)
得到縮放因子:
- 向上滾一步 → 因子 ≈ 1.2,表示放大 20%。
- 向下滾一步 → 因子 ≈ 0.83,表示縮小 17%。
3. 怎樣以光標為中心?
- 先算出光標在繪圖區里的橫向百分比 fx、縱向百分比 fy。
- 縮放前后,光標對應的那個數據值必須保持不變。
- 于是用百分比做插值:
- 新范圍 = 舊范圍 × 因子。
- 新最小值 = 舊最小值 + fx × (舊范圍 ? 新范圍)。
- 這樣光標在舊矩形里占多少比例,在新矩形里還是同樣比例,視覺上就是以光標為錨點放大或縮小。
簡而言之 滾輪縮放 是把滾了幾步變成縮放因子,再用光標位置做插值,重新算軸邊界。
工程下載
Git Code 下載鏈接:QML Charts組件之折線圖的基礎屬性示例
參考
- Qt官方文檔 - LineSeries
- Qt官方文檔 - XYSeries