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.