QCustomPlot replot QCPLayer - c++

I'm trying to figure out how to use QCPLayer to only replot certain items in the plot.
The qcustomplot documentation states this:
If you often need to call a full QCustomPlot::replot only because a non-complex object (e.g. an item) has changed while having relatively static but complex graphs in the plot, consider placing the regularly changing objects onto an own layer and setting its mode (QCPLayer::setMode) to QCPLayer::lmBuffered. This makes QCustomPlot allocate a dedicated paint buffer for this layer, and allows it to be replotted individually with QCPLayer::replot, independent of the other layers containing the potentially complex and slow graphs. See the documentation of the respective methods for details.
Which is what I'm trying to do in the example below:
I am creating a custom qcustomplot by inheriting from QCustomPlot:
QCustomPlot_custom.h
#pragma once
#include "qcustomplot.h"
#define USING_LAYER false
struct QCPCursor{
QCPItemLine *hLine;
QCPItemLine *vLine;
QCPItemText* cursorText;
};
class QCustomPlot_custom :
public QCustomPlot
{
Q_OBJECT
private slots:
void mouseMove(QMouseEvent*);
public:
QCustomPlot_custom(QWidget* parent = NULL);
~QCustomPlot_custom(){}
private:
QCPLayer* cursorLayer;
QCPCursor cursor;
void manageCursor(double x, double y, QPen pen);
public:
void init(QVector<double> xdata, QVector<double> ydata);
};
This class initializes with some data to plot. It also overloads the mouseMove event to control a custom cursor. USING_LAYER set to true means that the custom cursor is added to it's own layer (cursorLayer).
By setting USING_LAYER to false I get the desired effect as seen below:
The cursor is displayed by a horizontal and vertical line and the coordinates.
If I have many graphs in the plot and/or a lot of point in each graph, I will see a delay when moving the cursor. (Which is the reason I want to be able to replot only the cursor by setting it in a layer.)
QCustomPlot_custom.cpp
QCustomPlot_custom::QCustomPlot_custom(QWidget* parent)
{
connect(this, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove(QMouseEvent*)));
QCustomPlot::setInteraction(QCP::iRangeDrag, true);
QCustomPlot::setInteraction(QCP::iRangeZoom, true);
if (USING_LAYER){
this->addLayer("cursorLayer", 0, QCustomPlot::limAbove);
cursorLayer = new QCPLayer(this, "cursorLayer");
cursorLayer->setMode(QCPLayer::lmBuffered);
}
}
void QCustomPlot_custom::init(QVector<double> xdata, QVector<double> ydata)
{
this->addGraph();
this->graph(0)->setData(xdata, ydata);
QColor colorPen(10, 25, 180, 255);
QPen pen;
pen.setWidth(50);
pen.setColor(colorPen);
this->graph()->setLineStyle(QCPGraph::lsLine);
this->graph()->setPen(QPen(colorPen));
this->xAxis->setLabel("X-axis");
this->yAxis->setLabel("Y-axis");
this->rescaleAxes();
this->replot();
}
void QCustomPlot_custom::mouseMove(QMouseEvent* event)
{
//Cursor coordinates:
double x = this->xAxis->pixelToCoord(event->pos().x());
double y = this->yAxis->pixelToCoord(event->pos().y());
manageCursor(x, y, QPen(Qt::DashDotLine));
if (USING_LAYER)
cursorLayer->replot();
else
this->replot();
}
void QCustomPlot_custom::manageCursor(double x, double y, QPen pen)
{
if (cursor.hLine)
this->removeItem(cursor.hLine);
cursor.hLine = new QCPItemLine(this);
cursor.hLine->setPen(pen);
cursor.hLine->start->setCoords(-QCPRange::maxRange, y);
cursor.hLine->end->setCoords(QCPRange::maxRange, y);
if (cursor.vLine)
this->removeItem(cursor.vLine);
cursor.vLine = new QCPItemLine(this);
cursor.vLine->setPen(pen);
cursor.vLine->start->setCoords(x, -QCPRange::maxRange);
cursor.vLine->end->setCoords(x, QCPRange::maxRange);
//Coordinates as text:
if (cursor.cursorText)
this->removeItem(cursor.cursorText);
cursor.cursorText = new QCPItemText(this);
cursor.cursorText->setText(QString("(%1, %2)").arg(x).arg(y));
cursor.cursorText->position->setCoords(QPointF(x, y));
QPointF pp = cursor.cursorText->position->pixelPosition() + QPointF(50.0, -15.0);
cursor.cursorText->position->setPixelPosition(pp);
cursor.cursorText->setFont(QFont(font().family(), 8));
//Add to layer:
if (USING_LAYER){
cursor.hLine->setLayer(cursorLayer);
cursor.vLine->setLayer(cursorLayer);
cursor.cursorText->setLayer(cursorLayer);
}
}
The function that initializes the class member:
void Qt_PlotTest::testPlot(){
//Create some data and initalize plot:
QVector<double> yData, xData;
int imax = 100000;
for (int i = 0; i < imax; i++){
double x = double(i) / imax;
xData.push_back(x);
yData.push_back(pow(x, 2)*( 1.0 + 0.5*cos(20*x) + 0.1*sin(500*x - 0.1)));
}
ui.custom_QWidgetPlot->init(xData, yData);
}
When using the layer method, the cursor doesn't render. I tried understanding the documentation, but it is not clear for me how to correctly use QCPLayers.
How should I do this?

After adding layer
this->addLayer("cursorLayer", 0, QCustomPlot::limAbove);
don't call QCPLayer constructor to get layer pointer. Use provided getters with name of the layer or index:
QCPLayer * QCustomPlot::layer ( const QString & name) const
QCPLayer * QCustomPlot::layer ( int index) const
cursorLayer = this->layer("cursorLayer");
Also every Graph and Item is added to currentLayer and in your case it's not cursorLayer it's the main. You need to change current layer
bool QCustomPlot::setCurrentLayer ( const QString & name)
bool QCustomPlot::setCurrentLayer ( QCPLayer * layer)
I.e.:
this->setCurrentLayer("cursorLayer");
this->addGraph();
...
this->setCurrentLayer("main");
Or you can specify layer for each QCPLayerable
bool QCPLayerable::setLayer ( QCPLayer * layer)
bool QCPLayerable::setLayer ( const QString & layerName)
someGraph->setLayer("cursorLayer);

As #EligijusPupeikis reminded me, I am deleting and re-creating the cursor every time I move it.
I didn't think this would have any effect to my issue, but apparently it is, because reploting a layer which has new items in it requires that the plot has be reploted first (source: will check the qcustomplot doc and add link).
So my code now looks like this:
QCustomPlot_custom::QCustomPlot_custom(QWidget* parent)
{
connect(this, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove(QMouseEvent*)));
QCustomPlot::setInteraction(QCP::iRangeDrag, true);
QCustomPlot::setInteraction(QCP::iRangeZoom, true);
}
void QCustomPlot_custom::init(QVector<double> xdata, QVector<double> ydata)
{
this->addGraph();
this->graph(0)->setData(xdata, ydata);
QColor colorPen(10, 25, 180, 255);
QPen pen;
pen.setWidth(50);
pen.setColor(colorPen);
this->graph()->setLineStyle(QCPGraph::lsLine);
this->graph()->setPen(QPen(colorPen));
this->xAxis->setLabel("X-axis");
this->yAxis->setLabel("Y-axis");
this->rescaleAxes();
this->replot();
if (USING_LAYER){
this->addLayer("cursorLayer", 0, QCustomPlot::limAbove);
cursorLayer = this->layer("cursorLayer");
//cursorLayer = new QCPLayer(this, "cursorLayer");
cursorLayer->setMode(QCPLayer::lmBuffered);
}
//Cursor:
QPen qpen = QPen(Qt::DashDotLine);
cursor.hLine = new QCPItemLine(this);
cursor.hLine->setPen(qpen);
cursor.vLine = new QCPItemLine(this);
cursor.vLine->setPen(qpen);
cursor.cursorText = new QCPItemText(this);
cursor.cursorText->setFont(QFont(font().family(), 8));
//Add to layer:
if (USING_LAYER){
cursor.hLine->setLayer("cursorLayer"); //"cursorLayer"
cursor.vLine->setLayer("cursorLayer");
cursor.cursorText->setLayer("cursorLayer");
}
}
void QCustomPlot_custom::mouseMove(QMouseEvent* event)
{
//Cursor coordinates:
double x = this->xAxis->pixelToCoord(event->pos().x());
double y = this->yAxis->pixelToCoord(event->pos().y());
manageCursor(x, y);
if (USING_LAYER)
this->layer("cursorLayer")->replot();
else
this->replot();
}
void QCustomPlot_custom::manageCursor(double x, double y)
{
cursor.hLine->start->setCoords(-QCPRange::maxRange, y);
cursor.hLine->end->setCoords(QCPRange::maxRange, y);
cursor.vLine->start->setCoords(x, -QCPRange::maxRange);
cursor.vLine->end->setCoords(x, QCPRange::maxRange);
cursor.cursorText->setText(QString("(%1, %2)").arg(x).arg(y));
cursor.cursorText->position->setCoords(QPointF(x, y));
QPointF pp = cursor.cursorText->position->pixelPosition() + QPointF(50.0, -15.0);
cursor.cursorText->position->setPixelPosition(pp);
}
As a test, if I plot 10 000 000 points, and set USING_LAYER to false, I will notice a clear lag on the cursor when moving the mouse. While setting it to true, will result in a smooth cursor movement.

Related

Why when drawBackground is called does it override the setBackgroundBrush colour?

I'm following this series on youtube Node Editor. I'm attempting to learn both Qt and C++ at the same time, possibly a stupid idea, but "hello world" tutorials don't do much for me.
Episode 1 We are to create a grid using the drawBackground method, however before this in the class constructor we have set the background to a light grey colour. When I run the code without the drawBackground the scene shows the light grey background. However when I run it with the drawBackground method uncommented the grid is drawn but the background is now white.
Why does the drawBackground() method override the QDMGraphicsScene() setBackgroundBrush?
qdmgraphicsscene.h
#ifndef QDMGRAPHICSSCENE_H
#define QDMGRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QtGui>
#include <QtCore>
#include "math.h"
class QDMGraphicsScene : public QGraphicsScene
{
public:
QDMGraphicsScene();
//Settings
QColor _colour_background, _colour_light, _colour_dark;
QPen _pen_light, _pen_dark;
int scene_width, scene_height, gridSize, gridSquares;
QVector<QLine> lines_light, lines_dark;
void drawBackground(QPainter *painter, const QRectF &rect);
};
#endif // QDMGRAPHICSSCENE_H
qdmgraphicsscene.cpp
#include "qdmgraphicsscene.h"
QDMGraphicsScene::QDMGraphicsScene()
{
this->gridSize = 60;
this->gridSquares = 3;
this->_colour_background = QColor(57,57,57);
this->_colour_light = QColor(47,47,47);
this->_colour_dark = QColor(41, 41, 41);
this->_pen_light = QPen(this->_colour_light);
this->_pen_light.setWidth(1);
this->_pen_dark = QPen(this->_colour_dark);
this->_pen_dark.setWidth(2);
this->scene_width = 64000;
this->scene_height = 64000;
this->setSceneRect(-this->scene_width/2, -this->scene_height/2, this->scene_width, this->scene_height);
this->setBackgroundBrush(this->_colour_background);
}
void QDMGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect)
{
//create grid
int left = (int)floor(rect.left());
int right = (int)ceil(rect.right());
int top = (int)floor(rect.top());
int bottom = (int)ceil(rect.bottom());
int first_left = left - (left % this->gridSize);
int first_top = top - (top % this->gridSize);
//compute lines
for(int x = first_left; x < right; x=x+this->gridSize){
if(x % (this->gridSize * this->gridSquares) != 0){
this->lines_light.append(QLine(x,top,x,bottom));
} else {
this->lines_dark.append(QLine(x,top,x,bottom));
}
}
for(int y = first_top; y < bottom; y=y+this->gridSize){
if(y % (this->gridSize * this->gridSquares) != 0){
this->lines_light.append(QLine(left,y,right,y));
} else {
this->lines_dark.append(QLine(left,y,right,y));
}
}
//draw lines
painter->setPen(this->_pen_light);
painter->drawLines(this->lines_light);
painter->setPen(this->_pen_dark);
painter->drawLines(this->lines_dark);
}
I suspect the problem is simply that your drawBackground override doesn't use the background brush -- you just set the QPainter pen and use it to draw lines.
I think the simplest solution would be to call the base class implementation of drawBackground at the top of your own, so...
void QDMGraphicsScene::drawBackground (QPainter *painter, const QRectF &rect)
{
/*
* Call base class drawBackground implementation to draw background.
*/
QGraphicsScene::drawBackground(painter, rect);
//create grid
.
.
.

QScrollArea - Resize content widgets by keeping the aspect ratio

I have a layout that looks like this.
Where:
Blue: rectangle it's a ScrollArea
Orange: rectangles are the widgets from that ScrollArea
My code:
#include <QtWidgets>
///////////////////////////////////////////////////////////////////////////////////////
class RoundedPolygon : public QPolygon {
public:
RoundedPolygon() { SetRadius(10); }
void SetRadius(unsigned int iRadius) { m_iRadius = iRadius; }
const QPainterPath &GetPath() {
m_path = QPainterPath();
if (count() < 3) {
qDebug() << "!! Polygon should have at least 3 points !!";
return m_path;
}
QPointF pt1;
QPointF pt2;
for (int i = 0; i < count(); i++) {
pt1 = GetLineStart(i);
if (i == 0)
m_path.moveTo(pt1);
else
m_path.quadTo(at(i), pt1);
pt2 = GetLineEnd(i);
m_path.lineTo(pt2);
}
// close the last corner
pt1 = GetLineStart(0);
m_path.quadTo(at(0), pt1);
return m_path;
}
private:
QPointF GetLineStart(int i) const {
QPointF pt;
QPoint pt1 = at(i);
QPoint pt2 = at((i + 1) % count());
float fRat = m_iRadius / GetDistance(pt1, pt2);
if (fRat > 0.5f)
fRat = 0.5f;
pt.setX((1.0f - fRat) * pt1.x() + fRat * pt2.x());
pt.setY((1.0f - fRat) * pt1.y() + fRat * pt2.y());
return pt;
}
QPointF GetLineEnd(int i) const {
QPointF pt;
QPoint pt1 = at(i);
QPoint pt2 = at((i + 1) % count());
float fRat = m_iRadius / GetDistance(pt1, pt2);
if (fRat > 0.5f)
fRat = 0.5f;
pt.setX(fRat * pt1.x() + (1.0f - fRat) * pt2.x());
pt.setY(fRat * pt1.y() + (1.0f - fRat) * pt2.y());
return pt;
}
float GetDistance(QPoint pt1, QPoint pt2) const {
int fD = (pt1.x() - pt2.x()) * (pt1.x() - pt2.x()) + (pt1.y() - pt2.y()) * (pt1.y() - pt2.y());
return sqrtf(fD);
}
private:
QPainterPath m_path;
unsigned int m_iRadius{};
};
class PolygonButtonWidget : public QWidget {
Q_OBJECT
public:
explicit PolygonButtonWidget(QWidget *parent = nullptr) : QWidget(parent) {}
~PolygonButtonWidget() override = default;
protected:
void resizeEvent(QResizeEvent *event) override {
float ratioW = 8;
float ratioH = 3;
// ui->scrollAreaWidgetContents->setFixedSize(5000, h);
float thisAspectRatio = (float) event->size().width() / event->size().height();
if (thisAspectRatio < ratioW / ratioH) {
float w = event->size().height() * ratioW / ratioH;
float h = event->size().height();
qDebug() << hasHeightForWidth() << " " << w << " " << h;
this->resize(w, h);
if (m_nrButtons != 0) {
this->move((w + 20) * m_nrButtons, this->y());
}
}
QWidget::resizeEvent(event);
}
int m_nrButtons{};
public:
void setMNrButtons(int mNrButtons) {
m_nrButtons = mNrButtons;
}
protected:
void paintEvent(QPaintEvent *event) override {
int offset = 50;
m_polygon.clear();
m_polygon.emplace_back(0, height()); //DOWN-LEFT
m_polygon.emplace_back(width() - offset, height()); //DOWN-RIGHT
m_polygon.emplace_back(width(), 0); //TOP-RIGHT
m_polygon.emplace_back(0 + offset, 0);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
RoundedPolygon poly;
poly.SetRadius(15);
for (QPoint point: m_polygon) {
poly << point;
}
QBrush fillBrush;
fillBrush.setColor(Qt::darkBlue);
fillBrush.setStyle(Qt::SolidPattern);
QPainterPath path;
path.addPath(poly.GetPath());
painter.fillPath(path, fillBrush);
}
void mousePressEvent(QMouseEvent *event) override {
auto cursorPos = mapFromGlobal(QCursor::pos());
qDebug() << "X: " << cursorPos.x() << " Y: " << cursorPos.y();
inside(cursorPos, m_polygon);
qDebug() << "Pressed";
}
private:
std::vector<QPoint> m_polygon;
bool inside(QPoint point, std::vector<QPoint> polygon) {
auto x = point.x();
auto y = point.y();
auto inside = false;
auto i = 0;
auto j = polygon.size() - 1;
while (i < polygon.size()) {
auto xi = polygon[i].x();
auto yi = polygon[i].y();
auto xj = polygon[j].x();
auto yj = polygon[j].y();
auto intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
j = i++;
}
qDebug() << inside;
return inside;
}
};
///////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget root;
QHBoxLayout layout{&root};
for (int i = 0; i < 10; ++i) {
auto p = new PolygonButtonWidget();
p->setMinimumSize(100, 100);
p->setMNrButtons(i);
layout.addWidget(p);
}
root.setStyleSheet("background-color: rgb(19,19,19);");
QScrollArea view;
view.setWidget(&root);
view.show();
app.exec();
}
#include "main.moc"
The problem arises when I'm trying to resize the window. In the moment of resizing, I want my widgets to keep their aspect ratio. But that's not going to happen.
I have scroll list of widgets which is looking like this (if it's expended on X way too much)
If I will scale it on Y-axis it's going to look like this.
After I've changed the resizeEvent now it's going to look something like this
or like this
How can I fix this? For some reason, some of my widgets are going to disappear, what should be my approach in order to fix this issue?
The problem is caused by the assumption that there's any mechanism that will automatically resize the widgets for you. There isn't. A QScrollArea acts as a layout barrier and any layouts inside of it are isolated from its size, and thus from any resize events.
You must resize the container widget (the one with blue outline on your diagram) yourself anytime the scroll area changes size, and you need first to prepare a test case for the widgets such that their size changes are properly managed when placed in the layout of your choice, and said layout is resized.
Finally, the pet peeve of mine: It's unlikely that you actually need the QMainWindow for anything. It's just a silly Qt Creator template. But unless you want an MDI interface and docking, you shouldn't be using the QMainWindow - and especially not when making a self-contained example. All you need here is QScrollArea as a top-level widget. That's literally all. Any QWidget can be a top-level window!
For future submissions, please provide all the code needed in a single main.cpp file that begins with #include <QtWidgets> and ends with #include "main.moc". You won't need any other includes for Qt classes, and you can write class definitions Java-style, with all the methods defined within the class declaration itself. This provides for short code - after all, a SO question isn't an Enterprise project. It's supposed to be minimal, and that really means that anything not necessary must be removed. No need for header files, multiple includes, nor other fluff - i.e. use Qt containers instead of C++ STL so that you don't need more includes etc.
Your example should look roughly as follows:
#include <QtWidgets>
class PolygonButtonWidget : public QAbstractButton {
Q_OBJECT
/* without seeing the code here, your question is unanswerable */
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QWidget root;
QHBoxLayout layout{&root};
PolygonButtonWidget buttons[10];
for (auto &button : buttons)
layout.addWidget(&button);
QScrollArea view;
view.setWidget(&root);
view.show();
app.exec();
view.takeWidget();
}
#include "main.moc"
Without such an example, your question is hard to answer, since:
How can we debug it? Debugging means using a debugger. If your code cannot be immediately compiled, then it's quite unlikely that someone will bother debugging it, and debugging by inspection is often error-prone.
How can we provide a tested answer if we'd have to first write the entire "test case" for it?
How can we know what's inside your button widget? The behavior of that widget does affect the ultimate solution.
It'd also help if you described a few use cases that you'd expect to work. That is, mock up (with a drawing) the state of the widgets before and after the view is resized, so that we can easily see what it is that you expect to happen. A lot of it is very easy to miss when explaining your needs in words. Use cases are a lingua franca of software specifications. If you don't use them, it's highly likely that you yourself don't know what behavior you expect in all cases.

Calculating view offset for zooming in at the position of the mouse cursor

I've got a "canvas" that the user can draw pixels, etc. onto. It works well, but my zoom functionality currently uses the same origin regardless of the position of the mouse. I'd like to implement functionality like that of Google Maps' zoom behaviour:
That is, the zoom's origin should always be the position of the mouse cursor.
What I currently have is not exactly right...
My attempts have mostly been stabs in the dark, but I've also tried using the code from this answer without success.
main.cpp:
#include <QGuiApplication>
#include <QtQuick>
class Canvas : public QQuickPaintedItem
{
Q_OBJECT
public:
Canvas() :
mTileWidth(25),
mTileHeight(25),
mTilesAcross(10),
mTilesDown(10),
mOffset(QPoint(400, 400)),
mZoomLevel(1)
{
}
void paint(QPainter *painter) override {
painter->translate(mOffset);
const int zoomedTileWidth = mTilesAcross * mZoomLevel;
const int zoomedTileHeight = mTilesDown * mZoomLevel;
const int zoomedMapWidth = qMin(mTilesAcross * zoomedTileWidth, qFloor(width()));
const int zoomedMapHeight = qMin(mTilesDown * zoomedTileHeight, qFloor(height()));
painter->fillRect(0, 0, zoomedMapWidth, zoomedMapHeight, QColor(Qt::gray));
for (int y = 0; y < mTilesDown; ++y) {
for (int x = 0; x < mTilesAcross; ++x) {
const QRect rect(x * zoomedTileWidth, y * zoomedTileHeight, zoomedTileWidth, zoomedTileHeight);
painter->drawText(rect, QString::fromLatin1("%1, %2").arg(x).arg(y));
}
}
}
protected:
void wheelEvent(QWheelEvent *event) override {
const int oldZoomLevel = mZoomLevel;
mZoomLevel = qMax(1, qMin(mZoomLevel + (event->angleDelta().y() > 0 ? 1 : -1), 30));
const QPoint cursorPosRelativeToOffset = event->pos() - mOffset;
if (mZoomLevel != oldZoomLevel) {
mOffset.rx() -= cursorPosRelativeToOffset.x();
mOffset.ry() -= cursorPosRelativeToOffset.y();
// Attempts based on https://stackoverflow.com/a/14085161/904422
// mOffset.setX((event->pos().x() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.x()));
// mOffset.setY((event->pos().y() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.y()));
// mOffset.setX((cursorPosRelativeToOffset.x() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.x()));
// mOffset.setY((cursorPosRelativeToOffset.y() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.y()));
update();
}
}
void keyReleaseEvent(QKeyEvent *event) override {
static const int panDistance = 50;
switch (event->key()) {
case Qt::Key_Left:
mOffset.rx() -= panDistance;
update();
break;
case Qt::Key_Right:
mOffset.rx() += panDistance;
update();
break;
case Qt::Key_Up:
mOffset.ry() -= panDistance;
update();
break;
case Qt::Key_Down:
mOffset.ry() += panDistance;
update();
break;
}
}
private:
const int mTileWidth;
const int mTileHeight;
const int mTilesAcross;
const int mTilesDown;
QPoint mOffset;
int mZoomLevel;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<Canvas>("App", 1, 0, "Canvas");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.5
import QtQuick.Window 2.2
import App 1.0 as App
Window {
visible: true
width: 1200
height: 900
title: qsTr("Hello World")
Shortcut {
sequence: "Ctrl+Q"
onActivated: Qt.quit()
}
App.Canvas {
focus: true
anchors.fill: parent
}
}
What am I doing wrong in the wheelEvent() function?
You have a rectangle R = [x_0, x_0 + w] x [y_0, y_0 + h] with absolute coordinates. When you map it to a widget (another rectangle), you apply some transformation T to an area W of R. This transformation is linear with offset:
Values of a_x, b_x, a_y, b_y are calculated to satisfy some simple conditions, you have already done it.
You also have a cursor (x_c, y_c) in R. It's coordinates in W are T(x_c, y_c). Now you want to apply another transformation ,
changing scale coefficients a_x, a_y to known a_x', a_y' with following condition: you want your cursor to point at the same coordinates (x_c, y_c) in R. I.e. T'(x_c, y_c) = T(x_c, y_c) — the same point in relative coordinates points to the same position in absolute coordinates. We derive a system for unknown offsets b_x', b_y' with known rest values. It gives
Last work is to find (x_c, y_c) from widget cursor position (x_p, y_p) = T(x_c, y_c):
and to substitute it:
In your terms it is
mOffset = event->pos() - float(mZoomLevel) / float(oldZoomLevel) *
(event->pos() - mOffset);
If you want to zoom like Google maps then your origin must be at top-left corner of the image(lets say (x,y) = (0,0) and (width, height) = (100,100)) with initial zoomLevel 100.
If you want to zoom at point(40,20) with zoom Factor of 5% then,
the displacement can be calculated as-
newX = 40 - 40*(100.0/105)
newY = 20 - 20*(100.0/105)
newWidth = width - (100.0/105)
newHeight = height - (100.0/105)
then set newX, newY as your origin and change width, height to newWidth and newHeight.
By this implementation you'll be able to zoom at a particular point where the cursor is. But this implementation will not work when you move the cursor at some other positions.
I am also looking for that implementation.

Get intermediate color from a gradient

Say I have a liner gradient as shown:
QLinearGradient linearGrad(QPointF(0, 0), QPointF(0, 100));
linearGrad.setColorAt(1, Qt::red);
linearGrad.setColorAt(0.5, Qt::yellow);
linearGrad.setColorAt(0, Qt::green);
How to get the color of the point QPointF(0, 28.5) in this gradient?
Indeed I want to have this kind of color distribution to be able to choose intermediate colors. I don't care if it is done by using QLinearGradient or something else.
I store the colors of gradient in one QList and then compute with color interpolation.
QColor ColorGradient::getColor(double value)
{
qDebug()<< "ColorGradient::getColor:";
//Asume mGradientColors.count()>1 and value=[0,1]
double stepbase = 1.0/(mGradientColors.count()-1);
int interval=mGradientColors.count()-1; //to fix 1<=0.99999999;
for (int i=1; i<mGradientColors.count();i++)//remove begin and end
{
if(value<=i*stepbase ){interval=i;break;}
}
double percentage = (value-stepbase*(interval-1))/stepbase;
QColor color(interpolate(mGradientColors[interval],mGradientColors[interval-1],percentage));
return color;
}
QColor ColorGradient::interpolate(QColor start,QColor end,double ratio)
{
int r = (int)(ratio*start.red() + (1-ratio)*end.red());
int g = (int)(ratio*start.green() + (1-ratio)*end.green());
int b = (int)(ratio*start.blue() + (1-ratio)*end.blue());
return QColor::fromRgb(r,g,b);
}
Mason Zhang answer does work, and very well !
Let controlPoints() return a QMap<qreal,QColor>, with a key between 0.0 and 1.0.
Here is how i did (thanks to Mason Zhang)
QColor getColor(qreal key) const
{
// key must belong to [0,1]
key = Clip(key, 0.0, 1.0) ;
// directly get color if known
if(controlPoints().contains(key))
{
return controlPoints().value(key) ;
}
// else, emulate a linear gradient
QPropertyAnimation interpolator ;
const qreal granularite = 100.0 ;
interpolator.setEasingCurve(QEasingCurve::Linear) ;
interpolator.setDuration(granularite) ;
foreach( qreal key, controlPoints().keys() )
{
interpolator.setKeyValueAt(key, controlPoints().value(key)) ;
}
interpolator.setCurrentTime(key*granularite) ;
return interpolator.currentValue().value<QColor>() ;
}
There is only way to make it:
There is a static member in QPixmap class
QPixmap QPixmap::grabWindow( WId window, int x = 0, int y = 0, int width = -1, int height = -1 )
1) draw your gradient on your widget;
2) grab you widget's surface into pixmap using that function; WId can be received from QWidget::effectiveWinId ();
3) convert token pixmap into QImage (there is an constructor available);
4) int QImage::pixelIndex( int x, int y ) returns the pixel index at (x, y) in QImage's color table. In your case you must calculate percentage value from widget's height ( pWidget->height() / 100 * 28.5 ).
5) QRgb QImage::color( int i ) returns the color in the color table at index i.
So returned Color is the color you were seeking.
QVariantAnimation has the similar functionality, and QVariantAnimation::keyValueAt can return the value you need. You may step into the code of QVariantAnimation and see how keyValueAt works.

Artifacts when trying to draw background grid without anti-aliasing in a QGraphicsScene

I'm trying to draw a background grid in the drawBackground() function of my QGraphicsScene subclass:
void Scene::drawBackground(QPainter *painter, const QRectF &rect)
{
const int gridSize = 50;
const int realLeft = static_cast<int>(std::floor(rect.left()));
const int realRight = static_cast<int>(std::ceil(rect.right()));
const int realTop = static_cast<int>(std::floor(rect.top()));
const int realBottom = static_cast<int>(std::ceil(rect.bottom()));
// Draw grid.
const int firstLeftGridLine = realLeft - (realLeft % gridSize);
const int firstTopGridLine = realTop - (realTop % gridSize);
QVarLengthArray<QLine, 100> lines;
for (qreal x = firstLeftGridLine; x <= realRight; x += gridSize)
lines.append(QLine(x, realTop, x, realBottom));
for (qreal y = firstTopGridLine; y <= realBottom; y += gridSize)
lines.append(QLine(realLeft, y, realRight, y));
//painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(QPen(QColor(220, 220, 220), 0.0));
painter->drawLines(lines.data(), lines.size());
// Draw axes.
painter->setPen(QPen(Qt::lightGray, 0.0));
painter->drawLine(0, realTop, 0, realBottom);
painter->drawLine(realLeft, 0, realRight, 0);
}
However, unless I turn on anti-aliasing, moving items around will sometimes leave artifacts in the grid (areas where it's not drawn). It seems it mostly happens at low zoom levels, when the view is zoomed out a bit. Any ideas what I might be doing wrong here?
I'd really don't want to turn anti-aliasing on since the lines are strictly horizontal and vertical, and I'd like them to be as crisp as possible.
Any help is much appriciated,
Regards, Elvis
Sounds like you need to use a different viewport update method:
http://doc.qt.io/qt-5/qgraphicsview.html#viewportUpdateMode-prop
and perhaps the background caching:
http://doc.qt.io/qt-5/qgraphicsview.html#cacheMode-prop