How do you plot points in QT? - c++

I am writing an application in C++ with QT where you have n points and compute the convex hull of this. However, once this is computed I have no idea how to plot the points and draw the border of the hull. Making menu buttons and such is simple enough, but I'm not sure I know the tools to do this.
How do you do this?

Graphics View, addEllipse
QGraphicsView does 2D plotting very well and gives you many options for how to display it. It isn't as tailored for plotting scientific data as much as qwt, but just for showing a bunch of points, or geometry or animations and lots of other things it works very well. See Qt's Graphics View Framework documentation and examples.
Here is how you plot a bunch of points in a QGraphicsScene and show it in a QGraphicsView.
#include <QtGui/QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QPointF>
#include <QVector>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QVector <QPointF> points;
// Fill in points with n number of points
for(int i = 0; i< 100; i++)
points.append(QPointF(i*5, i*5));
// Create a view, put a scene in it and add tiny circles
// in the scene
QGraphicsView * view = new QGraphicsView();
QGraphicsScene * scene = new QGraphicsScene();
view->setScene(scene);
for(int i = 0; i< points.size(); i++)
scene->addEllipse(points[i].x(), points[i].y(), 1, 1);
// Show the view
view->show();
// or add the view to the layout inside another widget
return a.exec();
}
Note: You will probably want to call setSceneRect on your view, otherwise the scene will just auto-center it. Read the descriptions for QGraphicsScene and QGraphicsView in the Qt Documentation. You can scale the view to show more or less of the scene and it can put scroll bars in. I answered a related question where I show more about what you can do with a QGraphicsView that you may want to look at also.

You can just create a custom class deriving from QWidget where you override the void paintEvent(QPaintEvent* event) method. In that you put the points into some sort of point list, either std::vector<QPoint> or QList<QPoint> and then paint it with a Polyline method. For instance:
void Foo::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
std::vector<QPoint> points;
// Fill points with the points
painter.drawPolyLine(points.data(), static_cast<int>(points.size()));
}

There is a charting library, qwt, that provides Qt widgets for - erm - charting purposes.

Qt Charts, QML or GraphicsView
This was going to be an update to my QGraphics View example, but it got kind of long, and it really is a completely different method to answer the question.
Qt Charts (LGPL available since 2016) is a great way to do this without needing a third party library.
https://doc.qt.io/qt-5/qtcharts-linechart-example.html
http://blog.qt.io/blog/2016/01/18/qt-charts-2-1-0-release/
https://doc.qt.io/qt-5/qtcharts-overview.html
https://doc.qt.io/qt-5/qlineseries.html#QLineSeries
QLineSeries* series = new QLineSeries();
series->append(0, 6);
series->append(2, 4);
...
chart->addSeries(series);
For the convex hull example specifically, you probably want the QAreaSeries chart.
https://doc.qt.io/qt-5/qtcharts-areachart-example.html
https://doc.qt.io/qt-5/qareaseries.html
QLineSeries *series0 = new QLineSeries();
QLineSeries *series1 = new QLineSeries();
*series0 << QPointF(1, 5) << QPointF(3, 7) << QPointF(7, 6) << QPointF(9, 7) << QPointF(12, 6)
<< QPointF(16, 7) << QPointF(18, 5);
*series1 << QPointF(1, 3) << QPointF(3, 4) << QPointF(7, 3) << QPointF(8, 2) << QPointF(12, 3)
<< QPointF(16, 4) << QPointF(18, 3);
QAreaSeries *series = new QAreaSeries(series0, series1);
Hope that helps.

Related

How to make only one point label visible for QLineSeries/QXYSeries

I am using the qtcharts module of qt.
I am using c++ but it does not matter if the solution comes for another language (I will translate it afterwards).
Problem: I plot a bunch of QLineSeries in a QChart and I want to display the point labels only when hovering them.
I planned to use the signal QXYSeries::hovered() to detect when the mouse moves over a point (the same when the mouse moves away the point).
I know that there exists a member function QXYSeries::setPointLabelsVisible() but it makes visible all the points of the series.
I want to be able to display only one point at a time because the series are relatively large and displaying all the labels would degrade the readability.
Question: Is it possible to display only one point label for a QLineSeries ? If yes, how ?
I could not find such a feature anywhere in the Qt documentation.
Here is a baseline code sample to start with (for convenience):
Declaration:
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
};
Definition:
#include <QApplication>
#include <QLineSeries>
#include <QDateTimeAxis>
#include <QValueAxis>
#include <QChartView>
#include <QDateTime>
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
MainWindow w;
w.show();
return app.exec();
}
MainWindow::MainWindow()
{
setWindowTitle("QtCharts baseline");
resize(800, 500);
QtCharts::QChart * chart = new QtCharts::QChart;
chart->setTitle("Baseline sample");
chart->legend()->setAlignment(Qt::AlignRight);
QtCharts::QDateTimeAxis * time_axis = new QtCharts::QDateTimeAxis;
time_axis->setFormat("hh:mm:ss");
time_axis->setTitleText("Time");
time_axis->setTickCount(5);
QtCharts::QValueAxis * value_axis = new QtCharts::QValueAxis;
value_axis->setTitleText("Value (unit)");
value_axis->setTickCount(6);
chart->addAxis(time_axis, Qt::AlignBottom);
chart->addAxis(value_axis, Qt::AlignLeft);
QtCharts::QLineSeries * ls = new QtCharts::QLineSeries;
ls->setName("Test series");
ls->setPointsVisible(true);
//ls->setPointLabelsVisible(true);
QDateTime dt = QDateTime::currentDateTime();
ls->append(dt.toMSecsSinceEpoch(), -10);
ls->append(dt.addSecs(1).toMSecsSinceEpoch(), 8);
ls->append(dt.addSecs(2).toMSecsSinceEpoch(), 27);
ls->append(dt.addSecs(3).toMSecsSinceEpoch(), 12);
ls->append(dt.addSecs(4).toMSecsSinceEpoch(), 42);
chart->addSeries(ls);
ls->attachAxis(time_axis);
ls->attachAxis(value_axis);
QtCharts::QChartView * view = new QtCharts::QChartView;
view->setChart(chart);
this->setCentralWidget(view);
}
One technique you could try is making a "shadow" copy of the line series with just the one or few points you need, sitting on top of the actual data line. Draw with a transparent pen so the line doesn't show up but set the labels to be visible. You can add/remove/change points to the shadow copy of the line series, and only the labels you want drawn can be added to the series. The link shows one
example of the technique.

Qt Adjusting axes does not adjust the chart itself

I am currently trying to get my head around Line Charts in Qt. For some reason, my charts seem to show really weird behavior. When I am using the createDefaultAxis it will set the minimum and maximum values according to the minimum and maximum values of the Series behind the chart. While this seems alright at first it's already different from the behavior in this example. The minimum Y-Value there is 1, the minimum value on the axis, however, is 0.
At first, I thought this might just be due to a change to the QChart class, so I created my own Axis and tried again. This time I made the y-Axis range from 0 to 100 and the x-Axis range from 1 to 52. Also, I changed the tick counts to 4 and 52. However, the chart still looked like before and didn't seem to be affected by the changes to the axis.
I included a screenshot of that program here
I hope you can help me fix that. The goal would be that the values of the series match the values on the axes.
Edit: Here is the main.cpp:
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
QT_CHARTS_USE_NAMESPACE
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QLineSeries *series = new QLineSeries();
series->append(0, 6);
series->append(2, 4);
series->append(3, 8);
series->append(7, 4);
series->append(10, 5);
*series << QPointF(11, 1) << QPointF(13, 3) << QPointF(17, 6) << QPointF(18, 3) << QPointF(20, 2);
QChart *chart = new QChart();
chart->legend()->hide();
chart->addSeries(series);
QValueAxis *axisX = new QValueAxis();
axisX->setRange(1, 52);
axisX->setMin(1);
axisX->setMax(52);
axisX->setTickCount(52);
QValueAxis *axisY = new QValueAxis();
axisY->setRange(0, 100);
axisY->setMin(0);
axisY->setMax(100);
axisY->setTickCount(4);
chart->setAxisX(axisX);
chart->setAxisY(axisY);
chart->setTitle("Simple line chart example");
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
QMainWindow window;
window.setCentralWidget(chartView);
window.resize(400, 300);
window.show();
return a.exec();
}
The series isn't attached to any axis, it will by default scale to utilize the entire plot area of the chart.
You should attach the series to axis created as :
//... After setting up your axis X and Y
chart->setAxisX(axisX);
chart->setAxisY(axisY);
//attach the series to the specific axis.
series->attachAxis(axisX);
series->attachAxis(axisY);
to add axis on chart use following technique.
addAxis(xAxis,postion);
addAxis(yAxis,postion);
and then attached series to it

QT QGraphicsView rotation

Disclaimer: I am pretty much a beginner with QT.
I've been struggling for some time to rotate a QGraphicsView (no 3D rotation) but, despite what i do, it doesn't work. I have tried:
QTransform transform;
transform.rotate(45);
ui->graphicsView->setTransform(transform);
or more simply:
ui->graphicsView->rotate(45);
These seem like very straightforward ways to do it that should work, but for some reason, whenever i run it, the QGraphicsView doesn't rotate at all. If possible, i'd like some direct and easy to understand code snippets, and/or what i'm doing wrong.
EDIT: This is the code in the widget cpp file i have problems with. It should be a simple timer with an animated hourglass icon. It gets repeated every .5 seconds.
void Widget::timerEvent(QTimerEvent *event)
{
++timeFlag;
++timerFlag;
if (timerFlag < 115){
animateTimer = QString("\":/new/100/timerFrames/timerIconFrame%1.png\"").arg(timerFlag);
QPixmap pix(animateTimer);
pixmapitem.setPixmap(pix);
scene.addItem(&pixmapitem);
ui->graphicsView_2->setScene(&scene);
}
if (timerFlag >= 115 && timerFlag < 119){
//
}
if(timerFlag == 119){
ui->graphicsView_2->setStyleSheet("border-image:url(:/new/100/timerIconPix.PNG);border:0px;}");
}
if(timerFlag == 120){
timerFlag = 0;
}
if (timeFlag==2){
timeFlag = 0;
if(sec>=10){
ui->label_2->setText(QString("%1:%2").arg(min).arg(sec));
} else {
ui->label_2->setText(QString("%1:0%2").arg(min).arg(sec));
}
++sec;
if (sec == 60) {
sec = 0;
++min;
}
}
}
You're merely decorating the QGraphicsView using the style mechanism. You could have used a plain QWidget instead, since you don't use any graphics view functionality. None of the images in the stylesheet are what the view actually displays. The image must be on the scene displayed by the view.
Set the image on a QGraphicsPixmapItem, add that item to a scene, set the scene on the view, and then the transformations will work. You can then keep replacing the pixmap in the timer handler.
Finally, you must also check the timer id in the timerEvent. I assume that you're using a QBasicTimer, say called m_timer, you'd then check as follows:
void Widget::timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_timer.timerId()) return;
... // rest of the code
}
As you can see, the code that you've not included in the original question was absolutely essential! Without it, the question was wholly off-topic.
You need to implement a QGraphicsView, a QGraphicsScene and then add something that inherits from QGraphicsItem to that scene to rotate.
Here is an example that rotates a QWidget in a QGraphicsView:
QGraphicsView* view = new QGraphicsView(parent);
QGraphicsScene* scene = new QGraphicsScene(view);
view->setScene(scene);
// Widget to rotate - important to not parent it
QWidget* widget = new QWidget();
QProxyWidget proxy_widget = scene_->addWidget(widget);
QPropertyAnimation* animation = new QPropertyAnimation(proxy_widget, "rotation");
animation->setDuration(5000);
animation->setStartValue(0);
animation->setEndValue(360);
animation->setEasingCurve(QEasingCurve::Linear);
animation->start(QAbstractAnimation::DeleteWhenStopped);

Qt - Adding items to QGraphicsScene with absolute position

This is probably a dump question but I'm really stuck with it right now. I'm trying to draw some squares in a QGraphicsScene and I want them to be aligned from position x = 0 towards the positive position of x coordinates. However they are aligned according to the alignment configuration of the QGraphicsView and the setting of position is only effective for the second item and upwards relative to the first item! This means that if I have a single item, then setting of it's position has no effect. Mainly this line seems not to be working:
graphicsView->setAlignment(Qt::AlignAbsolute);
This is my code:
QGraphicsScene *scene = new QGraphicsScene();
QGraphicsView *graphicsView;
graphicsView->setScene(scene);
graphicsView->setAlignment(Qt::AlignAbsolute);
for(int i = 0; i < 500; i+= 50)
{
QGraphicsPolygonItem *item = new QGraphicsPolygonItem();
item->setPen(QPen(QColor(qrand()%255,qrand()%255,qrand()%255)));
item->setBrush(QBrush(QColor(255,251,253)));
item->setPolygon(*myPolygon);
graphicsView->scene()->addItem(item);
item->setPos(i , 40);
item->setFlag(QGraphicsItem::ItemIsMovable, true);
item->setFlag(QGraphicsItem::ItemIsSelectable, true);
graphicsView->show();
}
I do not know what the problem might be, so I tried the following code
const QRectF rect = QRectF(0, 0, ui->graphicsView->width(), ui->graphicsView->height());
ui->graphicsView->setScene(&_scene);
ui->graphicsView->fitInView(rect, Qt::KeepAspectRatio);
ui->graphicsView->setSceneRect(rect);
With respect to the previous four lines, the following output does not produce sizes even close to each other:
qDebug() << "scene =>" << _scene.width() << _scene.height();
qDebug() << "graphicview =>" << ui->graphicsView->width() << ui->graphicsView->height();
I highly appreciate your help.
It seems that Qt::AlignAbsolute does not do what you assume it does. What you actually need is Qt::AlignLeft | Qt::AlignTop.
It is explained here:
http://qt-project.org/doc/qt-4.8/qgraphicsview.html#alignment-prop
http://qt-project.org/doc/qt-4.8/qt.html#AlignmentFlag-enum
In my code, I only use graphicsView->setSceneRect(rect); and not fitInView(). The latter introduces a scaling and may hinder your understanding on what is going wrong.
Basically, I overwrite the QGraphicsview to re-implement resizeEvent():
void AutohideView::resizeEvent(QResizeEvent *event)
{
/* always resize the scene accordingly */
if (scene())
scene()->setSceneRect(QRect(QPoint(0, 0), event->size()));
QGraphicsView::resizeEvent(event);
}
I do not alter alignment or any other options and use absolute coordinates in the range [0,0 .. scene()->width(),scene()->height()] when positioning my items.

How do I controll clipping with non-opaque graphics-item's in Qt?

I have a bunch of QGraphicsSvgItem's in a QGraphicsScene that are drawn connected by QGraphicsLineItem's. This show's a graph of a tree-structure.
What I want to do is provide a feature where everything but a selected sub-tree becomes transparent. A kind of "highlight this sub-tree" feature. That part was easy, but the results are ugly because now the lines can be seen through the semi-transparent svg's.
I am looking for some way to still clip other QGraphicsItem's in the scene to the svg item's, giving the effect that the svg's are semi-transparent windows to the background.
I know this code does not use svg's but I figure you can replace that yourself if you are so inclined.
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QGraphicsScene scene;
for( int i = 0; i < 10; ++i ) {
QGraphicsLineItem* line = new QGraphicsLineItem;
line->setLine( i * 25.0 + 1.0, 0, i * 25.0 + 23.0, 0 );
scene.addItem( line );
}
for( int i = 0; i < 11; ++i ) {
QGraphicsEllipseItem* ellipse = new QGraphicsEllipseItem;
ellipse->setRect( (i * 25.0) - 9.0, -9.0, 18.0, 18.0f );
ellipse->setBrush( QBrush( Qt::green, Qt::SolidPattern ) );
ellipse->setOpacity( 0.5 );
scene.addItem( ellipse );
}
QGraphicsView view( &scene );
view.show();
return app.exec();
}
I would like the line's to not be seen behind the circle's. I have tried fiddling with the depth-buffer and the stencil buffer using opengl rendering to no avail.
How do I get the QGraphicsSvgItem's (or QGraphicsEllipseItem's in the example code) to still clip the lines even though they are semi-transparent?
The best solution here is to subclass QGraphicsScene and your graphics items.
Create other class for scene and several classes for different graphics items.
Then, you will have "paint" method for each item, where you can draw with the opacity you require.
In that case you will be able to solve clipping problem also because you will have control over the shape and bounding rectangle for each item.
Another nice feature would be ability to link items together in your implementation so that when you click somewhere, you can set the visibility settings for several items at once.
In other words, you will have more control over your entire scene and thus learning and writing these subclasses is a good time investment.
For example, you can see Colliding Mice Example where custom graphics items are painted.