Implementing Multi-Functional Cursors in QCustomPlot Charts

This article extends previous implementations by enhancing cursor functionality in QCustomPlot visualizations. It introduces advanced cursor features for improved data analysis capabilities.

Cursor Functionality Overview

The enhanced cursor system supports:

  • Single-line cursors with drag capabilities
  • Independent dual-cursor pairs with constrained movement
  • Locked dual-cursor pairs that move synchronously
  • Cursor visibility toggling and color customization
  • Data extraction between cursor positions

Implementation Approach

The cursor system uses a structured approach:

Class Structure

class PlotController {
public:
    void ClearCache();
    void SetGraphCount(int);
    void SetGraphKey(const QVector<double>&);
    void SetGraphKeyRange(double, double);
    void SetGraphScatterStyle(int, int);
    
    void SetGraphValue(int, const QString&, const QString&, const QVector<double>&);
    void AppendGraphValue(int, double, double);
    void AppendGraphValue(int, const QVector<double>&, const QVector<double>&);
    
    QVector<double> GetGraphValues(int, int);
    QString GetGraphName(int) const;
    void SetGraphColor(int, const QColor&);
    QColor GetGraphColor(int);
    
    void SetSingleCursor(bool);
    bool IsSingleCursor(int) const;
    void ShowCursor(bool = true);
    void AppendCursor(const QColor&);
    void LockedCursor(int, bool);
    int CursorCount() const;
    bool CursorVisible() const;
    void SetCursorColor(int, const QColor&);
    double GetCursorKey(bool);
    double GetCursorKey(int, bool);
    
    void ResizeKeyRange(bool, int = 0);
    void ResizeValueRange();
    void ConfigureGraph();
    void ConfigureGraphAmplitude(int);
    void SavePng(const QString& = "");
};

Cursor Creation

Different cursor types can be created programmatically:

void PlotWidget::createCursor(int typeIndex) {
    QColor colors[] = {Qt::red, Qt::green, Qt::blue, Qt::gray, 
                      Qt::cyan, Qt::yellow, Qt::magenta};
    QColor cursorColor = colors[rand() % 7];
    
    if (typeIndex % 3 == 0) {
        controller->SetSingleCursor(true);
        controller->AppendCursor(cursorColor);
    } 
    else if (typeIndex % 3 == 1) {
        controller->SetSingleCursor(false);
        controller->AppendCursor(cursorColor);
        controller->LockedCursor(typeIndex, false);
    } 
    else {
        controller->SetSingleCursor(false);
        controller->AppendCursor(cursorColor);
        controller->LockedCursor(typeIndex, true);
    }
}

Cursor Enteraction

Mouse events handle cursor movement detection:

void PlotWidget::mousePressEvent(QMouseEvent* event) {
    if (cursorEnabled) {
        dragActive = true;
        for (int i = 0; i < cursors.size(); ++i) {
            bool pressed = false;
            double leftDistance = cursors[i].left->selectTest(event->pos(), false);
            
            if (leftDistance <= 5 && plotArea.contains(event->pos())) {
                dragType = CURSOR_DRAG;
                isLeftCursor = true;
                pressed = true;
                isLocked = cursors[i].locked;
                isSingle = cursors[i].single;
                currentCursor = i;
            }
            
            double rightDistance = cursors[i].right->selectTest(event->pos(), false);
            if (rightDistance <= 5 && plotArea.contains(event->pos())) {
                dragType = CURSOR_DRAG;
                isLeftCursor = false;
                pressed = true;
                isLocked = cursors[i].locked;
                isSingle = cursors[i].single;
                currentCursor = i;
            }
            if (pressed) break;
        }
    }
    QCustomPlot::mousePressEvent(event);
}

Movement Constraints

Cursor movement follows specific boundary rules:

void PlotWidget::mouseMoveEvent(QMouseEvent* event) {
    if (dragType == CURSOR_DRAG && dragActive) {
        double mouseX = event->pos().x();
        QCPRange xRange = xAxis->range();
        double plotMin = xAxis->coordToPixel(xRange.lower);
        double plotMax = xAxis->coordToPixel(xRange.upper);
        
        // Boundary enforcement
        mouseX = qBound(plotMin + 1, mouseX, plotMax - 1);
        
        // Movement logic implementation
        double movementDelta = 0;
        double currentPos = isLeftCursor ? 
            xAxis->coordToPixel(cursors[currentCursor].left->point1->key()) :
            xAxis->coordToPixel(cursors[currentCursor].right->point1->key());
        
        // Calculate valid movement range based on:
        // - Adjacent cursors
        // - Locked state
        // - Single/double configuration
        
        // Apply calculated movement
        double newKey = xAxis->pixelToCoord(currentPos + movementDelta);
        if (isLeftCursor) {
            cursors[currentCursor].left->point1->setCoords(newKey, 0);
            cursors[currentCursor].left->point2->setCoords(newKey, 1);
        } else {
            cursors[currentCursor].right->point1->setCoords(newKey, 0);
            cursors[currentCursor].right->point2->setCoords(newKey, 1);
        }
        
        if (isLocked) {
            // Apply synchronized movement to paired cursor
        }
        
        replot();
        emit cursorPositionChanged();
        return;
    }
    QCustomPlot::mouseMoveEvent(event);
}

Data Integration

Data can be loaded from CSV files for visualization:

void DataLoader::loadDataset() {
    CSVHandler handler;
    handler.loadFile(qApp->applicationDirPath() + "/data/sample.csv");
    QStringList columns = handler.getColumnNames();
    
    plot->SetGraphCount(columns.size() - 1);
    for (int i = 0; i < columns.size(); ++i) {
        auto dataCallback = [this, columns](const QString& name, const QVector<double>& values) {
            int index = columns.indexOf(name);
            if (index == 0) {
                plot->SetGraphKey(values);
            } else {
                QString cleanName = name.section('(', 0, 0).trimmed();
                plot->SetGraphValue(index - 1, cleanName, "", values);
            }
        };
        handler.processColumn(columns[i], dataCallback);
    }
    
    plot->SetGraphKeyRange(handler.getMinTime(), handler.getMaxTime());
}

Related Topics

  • QCustomPlot: Core Functionality Overview
  • QCustomPlot: Source Code Structure Analysis
  • QCustomPlot: Graphical Elements Implementation
  • QCustomPlot: Layout Management Techniques
  • QCustomPlot: Axis and Grid Configuration

Conclusion

QCustomPlot provides efficient and flexible solutions for data visualization needs. The enhanced cursor system demonstrates how to extend its capabilities for complex analytical interactions while maintaining rendering performence.

Tags: qcustomplot Qt Cursors DataVisualization C++

Posted on Thu, 27 Aug 2026 16:54:17 +0000 by optikalefx