Skip to content

Commit 0cd0b83

Browse files
authored
Merge pull request #617 from RookieLinux/main
更新qmlcustomplot目录,主要是更新baseplot,增加固定刷新时间间隔、绘制最大点数等属性,预留添加数据接口与更新刷图接口,…
2 parents f2647e1 + 00c7c4e commit 0cd0b83

File tree

16 files changed

+827
-189
lines changed

16 files changed

+827
-189
lines changed

example/example.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@
211211
<file>qml/page/T_Icons.qml</file>
212212
<file>qml/window/HotkeyWindow.qml</file>
213213
<file>qml/page/T_CustomPlot.qml</file>
214+
<file>qml/page/T_CustomPlot2.qml</file>
214215
<file>res/image/logo_pro.png</file>
215216
<file>qml/page/T_FluentPro.qml</file>
216217
<file>qml/page/T_BubbleBox.qml</file>

example/qml/global/ItemsOriginal.qml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,12 @@ FluObject{
450450
url: "qrc:/example/qml/page/T_CustomPlot.qml"
451451
onTap: { navigationView.push(url) }
452452
}
453+
FluPaneItem{
454+
title: qsTr("QCustomPlot2")
455+
menuDelegate: paneItemMenu
456+
url: "qrc:/example/qml/page/T_CustomPlot2.qml"
457+
onTap: { navigationView.push(url) }
458+
}
453459
FluPaneItem{
454460
title: qsTr("QRCode")
455461
menuDelegate: paneItemMenu

example/qml/page/T_CustomPlot2.qml

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import QtQuick 2.15
2+
import QtQuick.Layouts 1.15
3+
import QtQuick.Window 2.15
4+
import QtQuick.Controls 2.15
5+
import FluentUI 1.0
6+
import example 1.0
7+
import "../component"
8+
9+
FluPage{
10+
title: qsTr("QCustomPlot2")
11+
RealTimePlot {
12+
anchors.fill: parent // 关键:给容器确定尺寸
13+
14+
id: realtimePlot
15+
xAxis.visible: true
16+
yAxis.visible: true
17+
18+
xAxis.ticker.tickCount: 2
19+
xAxis.ticker.ticks: true
20+
xAxis.ticker.subTicks: false
21+
yAxis.ticker.tickCount: 10
22+
yAxis.ticker.ticks: true
23+
yAxis.ticker.subTicks: false
24+
25+
backgroundColor: FluTheme.dark ? Qt.rgba(39/255, 39/255, 39/255, 1) : "white"
26+
baseColor: FluTheme.dark ? "white" : Qt.rgba(39/255, 39/255, 39/255, 1)
27+
labelColor: FluTheme.dark ? "white" : Qt.rgba(39/255, 39/255, 39/255, 1)
28+
maxBufferPoints: 20000
29+
refreshMs: 30
30+
initialXRange : ({"lower":0, "upper":1.0})
31+
initialYRange : ({"lower":-2, "upper":2})
32+
Component.onCompleted: {
33+
realtimePlot.addGraph("main");
34+
}
35+
36+
Component.onDestruction: {
37+
38+
}
39+
// 鼠标滚轮缩放
40+
MouseArea {
41+
id: rtMouse
42+
anchors.fill: parent
43+
hoverEnabled: true
44+
property double posX : 0
45+
property double posY : 0
46+
// 框选缩放相关
47+
property bool dragging: false
48+
property real startX: 0
49+
property real startY: 0
50+
property real curX: 0
51+
property real curY: 0
52+
53+
onPositionChanged: function(mouse) {
54+
// console.log("mouse:", mouse.x, mouse.y)
55+
posX = mouse.x
56+
posY = mouse.y
57+
if (dragging) {
58+
curX = mouse.x
59+
curY = mouse.y
60+
}
61+
}
62+
onPressed: function(mouse) {
63+
if (mouse.button === Qt.LeftButton) {
64+
dragging = true
65+
startX = mouse.x
66+
startY = mouse.y
67+
curX = mouse.x
68+
curY = mouse.y
69+
}
70+
}
71+
onReleased: function(mouse) {
72+
if (dragging && mouse.button === Qt.LeftButton) {
73+
dragging = false
74+
// 计算选择框
75+
var x1 = Math.min(startX, curX)
76+
var y1 = Math.min(startY, curY)
77+
var x2 = Math.max(startX, curX)
78+
var y2 = Math.max(startY, curY)
79+
var selW = Math.abs(x2 - x1)
80+
var selH = Math.abs(y2 - y1)
81+
// 阈值,避免误触
82+
if (selW > 8 && selH > 8) {
83+
// 将像素坐标转换为数据坐标
84+
realtimePlot.setRangeByPixels(x1, y1, x2, y2)
85+
}
86+
}
87+
}
88+
onWheel: function(wheel){
89+
let delta = wheel.angleDelta.y;
90+
if (wheel.modifiers & Qt.ControlModifier) {
91+
realtimePlot.zoomXY(posX, posY, delta > 0 ? 0.9 : 1.1, true);
92+
} else {
93+
realtimePlot.moveY( delta > 0 ? -0.05 : 0.05);
94+
}
95+
wheel.accepted = true;
96+
}
97+
onDoubleClicked: function(mouse) {
98+
if (mouse.button === Qt.LeftButton) {
99+
realtimePlot.resetRange();
100+
}
101+
}
102+
}
103+
// 框选可视化选区
104+
Rectangle {
105+
id: realtimeSelection
106+
x: Math.min(rtMouse.startX, rtMouse.curX)
107+
y: Math.min(rtMouse.startY, rtMouse.curY)
108+
width: Math.abs(rtMouse.curX - rtMouse.startX)
109+
height: Math.abs(rtMouse.curY - rtMouse.startY)
110+
visible: rtMouse.dragging && width > 4 && height > 4
111+
color: Qt.rgba(0, 120/255, 215/255, 0.15) // Windows 风格选区蓝
112+
border.color: Qt.rgba(0, 120/255, 215/255, 0.8)
113+
border.width: 1
114+
z: 10
115+
}
116+
}
117+
118+
DataGenerator {
119+
id: dataGenerator
120+
Component.onCompleted: {
121+
dataGenerator.start();
122+
}
123+
}
124+
125+
// 使用数据的示例
126+
Timer {
127+
interval: 50
128+
running: true
129+
repeat: true
130+
onTriggered: {
131+
// 获取最新数据
132+
var xData = dataGenerator.xData
133+
var yData = dataGenerator.yData
134+
realtimePlot.appendBatch(xData, yData);
135+
}
136+
}
137+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//
2+
// Created by rookie on 2025/12/3.
3+
//
4+
5+
#include "DataGenerator.h"
6+
7+
8+
QVector<double> DataGenerator::xData() const {
9+
QReadLocker locker(&m_lock);
10+
return m_xData;
11+
}
12+
13+
QVector<double> DataGenerator::yData() const {
14+
QReadLocker locker(&m_lock);
15+
return m_yData;
16+
}
17+
18+
bool DataGenerator::isRunning() const {
19+
return m_timer->isActive();
20+
}
21+
22+
Q_INVOKABLE void DataGenerator::start() {
23+
if (!m_timer->isActive()) {
24+
m_timer->start();
25+
emit runningChanged(true);
26+
generateData();
27+
}
28+
}
29+
30+
Q_INVOKABLE void DataGenerator::stop() {
31+
if (m_timer->isActive()) {
32+
m_timer->stop();
33+
emit runningChanged(false);
34+
}
35+
}
36+
37+
Q_INVOKABLE void DataGenerator::setParameters(double sineFreq, double sineAmp, double noiseAmp) {
38+
QWriteLocker locker(&m_lock);
39+
m_sineFrequency = sineFreq;
40+
m_sineAmplitude = sineAmp;
41+
m_noiseAmplitude = noiseAmp;
42+
}
43+
44+
void DataGenerator::generateData() {
45+
QWriteLocker locker(&m_lock);
46+
47+
m_xData.clear();
48+
m_yData.clear();
49+
50+
double dt = 1.0 / m_sampleRate;
51+
52+
for (int i = 0; i < m_sampleRate; ++i) {
53+
double t = i * dt;
54+
55+
double sineValue = m_sineAmplitude * std::sin(2 * M_PI * m_sineFrequency * t);
56+
57+
double randomValue = (QRandomGenerator::global()->generateDouble() - 0.5) * 2.0;
58+
double noiseValue = m_noiseAmplitude * randomValue;//-0.03~0.03
59+
60+
m_xData.append(t);
61+
m_yData.append(sineValue + noiseValue);
62+
}
63+
64+
emit dataUpdated();
65+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// Created by rookie on 2025/12/3.
3+
//
4+
5+
#ifndef DATAGENERATOR_H
6+
#define DATAGENERATOR_H
7+
8+
9+
#include <QObject>
10+
#include <QVector>
11+
#include <QTimer>
12+
#include <QRandomGenerator>
13+
#include <QReadWriteLock>
14+
#include <cmath>
15+
#include <QDateTime>
16+
17+
class DataGenerator : public QObject {
18+
Q_OBJECT
19+
Q_PROPERTY(QVector<double> xData READ xData NOTIFY dataUpdated)
20+
Q_PROPERTY(QVector<double> yData READ yData NOTIFY dataUpdated)
21+
Q_PROPERTY(bool running READ isRunning NOTIFY runningChanged)
22+
23+
public:
24+
explicit DataGenerator(QObject *parent = nullptr)
25+
: QObject(parent), m_timer(new QTimer(this)) {
26+
27+
m_timer->setInterval(50);
28+
connect(m_timer, &QTimer::timeout, this, &DataGenerator::generateData);
29+
30+
m_sampleRate = 20000.0;
31+
m_sineFrequency = 2.0; //Hz
32+
m_sineAmplitude = 1.5;
33+
m_noiseAmplitude = 0.03;
34+
35+
m_xData.reserve(m_sampleRate);
36+
m_yData.reserve(m_sampleRate);
37+
}
38+
39+
~DataGenerator() override {
40+
stop();
41+
}
42+
QVector<double> xData() const;
43+
QVector<double> yData() const;
44+
bool isRunning() const;
45+
46+
47+
public slots:
48+
Q_INVOKABLE void start();
49+
Q_INVOKABLE void stop();
50+
Q_INVOKABLE void setParameters(double sineFreq, double sineAmp, double noiseAmp);
51+
52+
signals:
53+
void dataUpdated();
54+
void runningChanged(bool running);
55+
56+
private slots:
57+
void generateData();
58+
59+
private:
60+
double m_sampleRate;
61+
double m_sineFrequency;
62+
double m_sineAmplitude;
63+
double m_noiseAmplitude;
64+
65+
QVector<double> m_xData;
66+
QVector<double> m_yData;
67+
68+
QTimer *m_timer;
69+
mutable QReadWriteLock m_lock;
70+
};
71+
72+
73+
#endif //DATAGENERATOR_H

example/src/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "src/helper/Network.h"
2222
#include "src/helper/SettingsHelper.h"
2323
#include "src/helper/TranslateHelper.h"
24+
#include "src/component/DataGenerator.h"
2425

2526
#ifdef FLUENTUI_BUILD_STATIC_LIB
2627
# if (QT_VERSION > QT_VERSION_CHECK(6, 2, 0))
@@ -81,6 +82,7 @@ int main(int argc, char *argv[]) {
8182
qmlRegisterType<OpenGLItem>(uri, major, minor, "OpenGLItem");
8283
qmlRegisterUncreatableMetaObject(NetworkType::staticMetaObject, uri, major, minor,
8384
"NetworkType", "Access to enums & flags only");
85+
qmlRegisterType<DataGenerator>(uri, major, minor, "DataGenerator");
8486

8587
QQmlApplicationEngine engine;
8688
TranslateHelper::getInstance()->init(&engine);

src/FluentUI.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ void FluentUI::registerTypes(const char *uri) {
5151

5252
qmlRegisterType<QmlQCustomPlot::TimePlot>(uri, major, minor, "TimePlot");
5353
qmlRegisterType<QmlQCustomPlot::BasePlot>(uri, major, minor, "BasePlot");
54+
qmlRegisterType<QmlQCustomPlot::RealTimePlot>(uri, major, minor, "RealTimePlot");
5455

5556
qmlRegisterUncreatableType<QmlQCustomPlot::Axis>(uri, major, minor, "Axis", "");
5657
qmlRegisterUncreatableType<QmlQCustomPlot::Ticker>(uri, major, minor, "Ticker", "");

src/qmlcustomplot/RealTimePlot.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//
2+
// Created by rookie on 2025/11/8.
3+
//
4+
5+
#include "RealTimePlot.h"
6+
#include "qcustomplot.h"
7+
#include <algorithm>
8+
#include <limits>
9+
10+
namespace QmlQCustomPlot
11+
{
12+
RealTimePlot::RealTimePlot(QQuickItem *parent): BasePlot(parent)
13+
{
14+
connect(this, &BasePlot::maxBufferChanged, this, [this](){
15+
this->recvMaxBufferPoints(this->maxBufferPoints());
16+
});
17+
}
18+
RealTimePlot::~RealTimePlot()
19+
{
20+
21+
}
22+
void RealTimePlot::recvMaxBufferPoints(int n)
23+
{
24+
m_frontX.reserve(n);
25+
m_frontY.reserve(n);
26+
m_backX.reserve(n);
27+
m_backY.reserve(n);
28+
}
29+
30+
Q_INVOKABLE void RealTimePlot::appendBatch(const QVector<double> &x, const QVector<double> &y)
31+
{
32+
if (paused()) return;
33+
if (width() <= 0 || height() <= 0) return;
34+
if (x.isEmpty() || y.isEmpty()) return;
35+
36+
QMutexLocker locker(&m_bufLock);
37+
m_backX = x;
38+
m_backY = y;
39+
m_hasNewBatch.store(true, std::memory_order_release);
40+
}
41+
42+
void RealTimePlot::updatePlot()
43+
{
44+
QCustomPlot * m_customPlot = customPlot();
45+
if (!m_customPlot) return;
46+
if (!m_hasNewBatch.load(std::memory_order_acquire)) {
47+
return;
48+
}
49+
50+
{
51+
QMutexLocker locker(&m_bufLock);
52+
if (m_hasNewBatch.load(std::memory_order_relaxed)) {
53+
m_frontX.swap(m_backX);
54+
m_frontY.swap(m_backY);
55+
m_hasNewBatch.store(false, std::memory_order_release);
56+
}
57+
}
58+
59+
const auto &gmap = graphs();
60+
for (auto it = gmap.constBegin(); it != gmap.constEnd(); ++it) {
61+
it->value<Graph*>()->setData(m_frontX, m_frontY, true);
62+
}
63+
m_customPlot->replot(QCustomPlot::rpQueuedReplot);
64+
}
65+
}

0 commit comments

Comments
 (0)