c++qtqcustomplot

Sorted Float Vectors for Scatter Plotting (C++ / QCustomPlot)


Questions:

1 - Sort multiple float vectors in the same order (keeping correspondance)

2 - QCustomPlot (QCP) plots ONLY outer boundary of a scatter plot.

(answering either of these 2 questions would solve my problem)

Situation:

I have 3 vectors for plotting:

std::vector<float> x, y;
std::vector<int> hits;

The resulting plot is a hit or miss scatter plot. The resulting plot is used by QCustomPlot's curve, which ends up as a circular "scribble." It just needs to look similar to a circle with no "scribbling" inside. I need this plot to overlay over another plot.

I don't have much control over the initial order of x, y, or hits.

x and y are sorted in a traditional grid indexing:

x = -8, -8, -8, -8, -8,  -4, -4, -4, -4, -4, ... 8
y = -8, -4,  0,  4,  8,  -8, -4,  0,  4,  8, ... 8

The hits are based on wether (let's just say archer's arrow) a hit was successful based on a range and speed of a fast target (let's just say bird).

The resulting plot is the outer boudary of hits based on the bird as a center reference.

The data vectors can be very large.

Method 1: I can calculate range and angle. Then doing a sort on float vectors: Sort the angles in order, so that when QCustomPlot plots the outer boundary without 'scribbles' inside. However, I need to know how to keep the corresponding x & y values together based on sorting the angle.

// Make range and angle vectors for sorting
std::vector<float> range, angle;
for(int i = 0; i < x.size(); ++i {

    float r = sqrt(x[i]*x[i] + y[i]*y[i]);
    range.push_back(r);

    float a = 0;
    if(y < 0)
        a = -acos(x[i]/r);
    else
        a = acos(x[i]/r);
    angle.push_back(a);
}

// Sort all vectors by ascending angle vector.
/* Do stuff here! */

// Set up boundary plot data
QVector<float> plot_x, plot_y;
for(int i = 0; i < x.size(); ++i {
    if(hits[i]) {
        plot_x.push_back(x[i]);
        plot_y.push_back(y[i]);
    }
}
// curve is a QCPCurve object already existing.
curve->addData(plot_x, plot_y); // Already sorted QVectors

Method 2: Get QCustomPlot curve->addData(x, y) member to only plot an "perimeter line" of the scatter plot's hits. I have tried using QCPScatterStyle, .setCustomPath, but have not been successful.

Thank you in advance! -John


Solution

  • If you want to order several vectors using some criteria and all the indices correspond create a new vector that are the indices, and order it, then use those indices to create the new vectors:

    #include <cmath>
    #include <QDebug>
    
    static float calc_angle(float x, float y){
        float r = sqrt(x*x + y*y);
        float angle = acos(x/r);
        return  y<0 ? -angle : angle;
    }
    
    int main(int argc, char *argv[])
    {
        std::vector<int> hits{0, 1, 2, 1, 0, 1, 2, 1, 0, 1};
        std::vector<float> x{-8, -8, -8, -8, -8,  -4, -4, -4, -4, -4};
        std::vector<float> y{-8, -4,  0,  4,  8,  -8, -4,  0,  4,  8};
    
        Q_ASSERT(x.size() == y.size() && y.size() == hits.size());
        std::vector<int> indexes(x.size());
        std::iota(indexes.begin(), indexes.end(), 0);
    
        std::sort(indexes.begin(), indexes.end(), [&](const int & i, const int & j) -> bool{
            return calc_angle(x[i], y[i]) < calc_angle(x[j], y[i]);
        });
        QVector<float> plot_x, plot_y;
        QVector<int> new_hits;
        for(const int & index : indexes){
            plot_x<<x[index];
            plot_y<<y[index];
            new_hits<<hits[index];
        }
    
        qDebug()<<indexes;
        qDebug()<< plot_x;
        qDebug()<<plot_y;
        qDebug()<<new_hits;
    
        return 0;//a.exec();
    }
    

    Output:

    std::vector(8, 0, 1, 2, 3, 4, 5, 6, 7, 9)
    QVector(-4, -8, -8, -8, -8, -8, -4, -4, -4, -4)
    QVector(4, -8, -4, 0, 4, 8, -8, -4, 0, 8)
    QVector(0, 0, 1, 2, 1, 0, 1, 2, 1, 1)