// 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); } void JFJochChartView::setImage(int64_t val) { if (!currentSeries) return; curr_image = val; if (val < values.size() && val >= 0) { currentSeries->clear(); 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++) series->append(i, values[i]); } else { for (int i = 0; i < values.size() / binning; i++) { float tmp = 0; for (int b = 0; b < binning; b++) tmp += values[i * binning + b]; series->append((i + 0.5) * binning, tmp / binning); } } if (curr_image < values.size() && curr_image >= 0) 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); } void JFJochChartView::leaveEvent(QEvent *event) { QChartView::leaveEvent(event); if (m_hoverLine) { chart()->scene()->removeItem(m_hoverLine); delete m_hoverLine; m_hoverLine = nullptr; } emit writeStatusBar(QString(), 0); }