// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include #include #include "JFJochChartView.h" JFJochChartView::JFJochChartView(QWidget *parent) : QChartView(new QChart(), parent) { chart()->legend()->hide(); setRenderHint(QPainter::Antialiasing); setRubberBand(QChartView::RubberBand::RectangleRubberBand); setMouseTracking(true); m_hoverLoadTimer = new QTimer(this); m_hoverLoadTimer->setSingleShot(true); connect(m_hoverLoadTimer, &QTimer::timeout, this, &JFJochChartView::onHoverLoadTimeout); } void JFJochChartView::setImage(int64_t val) { if (!currentSeries) return; curr_image = val; if (val < values.size() && val >= 0) { currentSeries->clear(); if (std::isfinite(values[curr_image])) currentSeries->append(curr_image, values[curr_image]); } } void JFJochChartView::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { QPointF clickedPoint = event->pos(); QPointF chartCoord = chart()->mapToValue(clickedPoint); int64_t val = std::lround(chartCoord.x()); if (val >= 0 && val < values.size()) emit imageSelected(val); } QChartView::mousePressEvent(event); // Call the base implementation } void JFJochChartView::resetZoom() { chart()->zoomReset(); } void JFJochChartView::updateChart() { chart()->removeAllSeries(); if (m_hoverLine) { chart()->scene()->removeItem(m_hoverLine); delete m_hoverLine; m_hoverLine = nullptr; } if (values.size() >= binning) { // At least one full point series = new QLineSeries(this); currentSeries = new QScatterSeries(this); if (binning == 1) { for (int i = 0; i < values.size(); i++) if (std::isfinite(values[i])) series->append(i, values[i]); } else { for (int i = 0; i < values.size() / binning; i++) { float tmp = 0; int64_t count = 0; for (int b = 0; b < binning; b++) { if (std::isfinite(values[i * binning + b])) { tmp += values[i * binning + b]; count++; } } if (count > 0) series->append((i + 0.5) * binning, tmp / count); } } if (curr_image < values.size() && curr_image >= 0 && std::isfinite(values[curr_image])) currentSeries->append(curr_image, values[curr_image]); chart()->addSeries(series); chart()->addSeries(currentSeries); chart()->createDefaultAxes(); } } void JFJochChartView::setBinning(int64_t val) { if (val >= 1) { binning = val; updateChart(); } } void JFJochChartView::contextMenuEvent(QContextMenuEvent *event) { QMenu menu(this); QAction *copyXY = menu.addAction("Copy (x y) points"); copyXY->setEnabled(!values.empty()); QAction *chosen = menu.exec(event->globalPos()); if (chosen == copyXY) { QString out; out.reserve(static_cast(values.size() * 16)); // rough prealloc for (size_t i = 0; i < values.size(); ++i) { out.append(QString::number(i)); out.append(' '); out.append(QString::number(values[i], 'g', 10)); if (i + 1 < values.size()) out.append('\n'); } QClipboard *cb = QApplication::clipboard(); cb->setText(out); } } void JFJochChartView::mouseMoveEvent(QMouseEvent *event) { QChartView::mouseMoveEvent(event); if (!series || values.empty()) return; // Map mouse position to chart coordinates const QPointF chartPos = chart()->mapToValue(event->pos(), series); double xVal = chartPos.x(); // Convert x to closest image index int64_t idx = std::lround(xVal); if (idx < 0 || idx >= static_cast(values.size())) return; // Map that x position to scene coords for the vertical line const QRectF plotArea = chart()->plotArea(); const QPointF ptOnChart = chart()->mapToPosition(QPointF(static_cast(idx), 0.0), series); if (!m_hoverLine) { m_hoverLine = new QGraphicsLineItem; m_hoverLine->setPen(QPen(QColor(200, 0, 0, 150), 1.0)); chart()->scene()->addItem(m_hoverLine); } m_hoverLine->setLine(QLineF(ptOnChart.x(), plotArea.top(), ptOnChart.x(), plotArea.bottom())); // Status bar text QString text = QString("image = %1, value = %2") .arg(idx) .arg(values[static_cast(idx)], 0, 'g', 6); emit writeStatusBar(text, 6000); // Debounced image load on hover when Shift is pressed if (event->modifiers() & Qt::ShiftModifier) { if (!m_hoverLoadTimer->isActive()) { m_hoverPendingIdx = -1; if (idx != curr_image) emit imageSelected(idx); m_hoverLoadTimer->start(500); // debounce } else m_hoverPendingIdx = idx; } else { m_hoverLoadTimer->stop(); m_hoverPendingIdx = -1; } } void JFJochChartView::leaveEvent(QEvent *event) { QChartView::leaveEvent(event); if (m_hoverLine) { chart()->scene()->removeItem(m_hoverLine); delete m_hoverLine; m_hoverLine = nullptr; } m_hoverLoadTimer->stop(); m_hoverPendingIdx = -1; emit writeStatusBar(QString(), 0); } void JFJochChartView::onHoverLoadTimeout() { if (!(QApplication::keyboardModifiers() & Qt::ShiftModifier)) return; if (m_hoverPendingIdx >= 0 && m_hoverPendingIdx < static_cast(values.size())) { if (m_hoverPendingIdx != curr_image) { emit imageSelected(m_hoverPendingIdx); } } }