7
\$\begingroup\$

I have created a custom scatterplot in Qt and am wondering if there is a more efficient way to do it. Here is a sketch of the problem:

scatterplot sketch

The layout of the plotting area consists of a rectangular structure starting at [0,0] in the top left corner and expands by 250 pixels to the bottom right corner [250,250]. So the y-coordinate grows downwards and the x-coordinate to the right.

The task consists of mapping two vectors (A[i],B[i]) holding numerical data onto this rectangular structure such that the lowest x,y coordinates are in the bottom left corner and the highest x,y coordinates are in the top right corner.

To solve this problem I choose the following approach (pseudo code):

  1. calculate the min and max for (A and B);
  2. calculate the spread (max - min) for (A and B);
  3. transform A and B by:

     foreach (i in A) {
         (i - min(A) / spread(a))* 250
     } 
     foreach(j in B) {
         (250 - ((j - min(B) / spread(B))*250
     } 
    

and the implementation:

.h file

#include <QDialog>
#include <QtGui>
#include <QtCore>
#include <QGraphicsScene>
#include <cstdlib>
#include <cmath>
#include <QVector>
#include <QRectF>

namespace Ui {
class paintEr;
}

class paintEr : public QDialog
{
    Q_OBJECT

public:
    explicit paintEr(QWidget *parent = 0);
    ~paintEr();
private slots:
    void addPoint();

private:
    Ui::paintEr *ui;
    QGraphicsScene *scatter;

protected:
    void paintEvent(QPaintEvent *event);


};

.cpp

#include "painter.h"
#include "ui_painter.h"

paintEr::paintEr(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::paintEr)
{
    ui->setupUi(this);
}

void paintEr::paintEvent(QPaintEvent *event){

    scatter = new QGraphicsScene(this);
    ui->graphicsView->setSceneRect(0,0,250,250);
    ui->graphicsView->setScene(scatter);

}
void paintEr::addPoint(){
    QPen greenPen(Qt::green);   
    QBrush greenBrush(Qt::green);

    QVector<double> a,b,c,d;
    a << 2 << 5 << 7;
    b << 22 << 34 << 75;
    double min_a = *std::min_element(a.begin(), a.end());
    double max_a = *std::max_element(a.begin(), a.end());
    double min_b = *std::min_element(b.begin(), b.end());
    double max_b = *std::max_element(b.begin(), b.end());
    double spread_a = max_a - min_a;
    double spread_b = max_b - min_b;

    for(int i = 0; i < a.length(); ++i){
        c.push_back(((a[i]-min_a)/spread_a)*250);
        d.push_back(250-((b[i]-min_b)/spread_b)*250);        
        scatter->addEllipse(c[i], d[i], 5, 5, greenPen, greenBrush);
    }
}
paintEr::~paintEr()
{
    delete ui;
}

main.cpp

#include "painter.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    paintEr w;
    w.show();

    return a.exec();
}
\$\endgroup\$
3
\$\begingroup\$

You should separate the actual data from the logic. Make your function capable of working on any QVectorq of QPairs (or pair of QVectors but that's not as normalized).

Variable names a,b,c,d don't make sense. Especially since a and b really ought to be input parameters.

You should be able to get both the min and max with one pass over the list. You'll probably have to write a custom mapreduce-style function.

For the circles in the QGraphicsScene: Are they supposed to have their center at the point in question, or the topleft of their bounding rect?

\$\endgroup\$
  • \$\begingroup\$ Thanks for your reply. I guess I did not state the problem properly> My questions are: first does Qt provide any classes or methods that performs that task of scaling the data to their relative position in the scene rectangle or not. Second if there is a better algorithm to implement the mapping process. \$\endgroup\$ – Vincent Mar 20 '15 at 17:11
-1
\$\begingroup\$

Here is a more detailed explanation to why I think the second approach is more efficient:

Before I come the my second approach let me explain the basic idea behind the first:

1. Approach:

  • Basic idea: express data of arrays A, B in percentages, with the min of A, B = 0 % and the max of A, B = 100 %;

  • Once the percentages are calculated, multiply each value in A, B with the respective length of the axis in pixels

2. Approach:

  • find the max of A, B by sorting both arrays and taking the last element as the max

  • divide the length of each axis by the max of A, B respectively and obtain the "multiplier" A_multip, B_multip

  • multiply each value of A, B with their respective multiplier and plot

Pseudocode:

//initialise values
int lengthOfAxis_A;
int lengthofAxis_B;
double multiplier_A;
double multiplier_B;

//sort arrays
sort(A);
sort(B);

multiplier_A = lengthOfAxis_A / A.last;
multiplier_B = lengthofAxis_B / B.last;

foreach(i in A, B)
    plot(A[i]*multiplier_A, B[i]*multiplier_B)

Explanation:

I believe the second approach is faster because:

  • sorting once is less costly then finding the min and max respectivly (not 100% sure though)

  • no need for additional vectors (memory efficiency), instead two double values (multipliers)

  • (A[i] - min(A)) / spread(A) is replaced by: A[i] * A_multip (less computation)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.