// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include "JFJochSimpleChartView.h" #include #include #include #include JFJochSimpleChartView::JFJochSimpleChartView(QWidget *parent) : QChartView(new QChart(), parent) { chart()->legend()->hide(); setFixedHeight(300); setRenderHint(QPainter::Antialiasing); setMouseTracking(true); //setRubberBand(QChartView::RubberBand::HorizontalRubberBand); } void JFJochSimpleChartView::UpdateData(const std::vector &in_x, const std::vector &in_y, QString legend_x, QString legend_y) { x = in_x; y = in_y; // Remove hover line if any if (m_hoverLine) { chart()->scene()->removeItem(m_hoverLine); delete m_hoverLine; m_hoverLine = nullptr; } m_series = nullptr; chart()->removeAllSeries(); // Remove all axes to avoid duplicates for (auto ax: chart()->axes()) chart()->removeAxis(ax); if (x.empty() || x.size() != y.size()) return; auto *series = new QLineSeries(this); for (size_t i = 0; i < x.size(); ++i) series->append(x[i], y[i]); chart()->addSeries(series); m_series = series; auto *axY = new QValueAxis(); axY->setTitleText(legend_y); chart()->addAxis(axY, Qt::AlignLeft); series->attachAxis(axY); // Build X range auto [minXIt, maxXIt] = std::minmax_element(x.begin(), x.end()); const double xmin = *minXIt, xmax = *maxXIt; // Normal value axis at bottom auto *axX = new QValueAxis(); axX->setRange(xmin, xmax); axX->setTitleText(legend_x); chart()->addAxis(axX, Qt::AlignBottom); series->attachAxis(axX); } void JFJochSimpleChartView::ClearData() { x.clear(); y.clear(); m_series = nullptr; if (m_hoverLine) { chart()->scene()->removeItem(m_hoverLine); delete m_hoverLine; m_hoverLine = nullptr; } chart()->removeAllSeries(); } void JFJochSimpleChartView::contextMenuEvent(QContextMenuEvent *event) { QMenu menu(this); QAction *copyXY = menu.addAction("Copy (x y) points"); copyXY->setEnabled(!x.empty() && x.size() == y.size()); QAction *chosen = menu.exec(event->globalPos()); if (chosen == copyXY) { QString out; out.reserve(static_cast(x.size() * 16)); // rough prealloc for (size_t i = 0; i < x.size() && i < y.size(); ++i) { out.append(QString::number(x[i], 'g', 10)); out.append(' '); out.append(QString::number(y[i], 'g', 10)); if (i + 1 < x.size()) out.append('\n'); } QClipboard *cb = QApplication::clipboard(); cb->setText(out); } } void JFJochSimpleChartView::mouseMoveEvent(QMouseEvent *event) { QChartView::mouseMoveEvent(event); if (!m_series || x.empty() || x.size() != y.size()) return; // Map mouse position to chart coordinates const QPointF chartPos = chart()->mapToValue(event->pos(), m_series); const double xVal = chartPos.x(); // Find closest index in x[] auto it = std::lower_bound(x.begin(), x.end(), static_cast(xVal)); if (it == x.end() && !x.empty()) it = std::prev(x.end()); if (it == x.end()) return; size_t idx = static_cast(std::distance(x.begin(), it)); if (idx > 0) { const float xPrev = x[idx - 1]; if (std::abs(xPrev - xVal) < std::abs(x[idx] - xVal)) --idx; } const float xNearest = x[idx]; const float yNearest = y[idx]; // Map that data point to scene coords to get the x position const QPointF ptOnSeries = chart()->mapToPosition(QPointF(xNearest, yNearest), m_series); const QRectF plotArea = chart()->plotArea(); 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(ptOnSeries.x(), plotArea.top(), ptOnSeries.x(), plotArea.bottom())); // Send to status bar emit writeStatusBar(getText(idx),6000); } QString JFJochSimpleChartView::getText(size_t idx) { if (idx < x.size()) return QString("x = %1, y = %2") .arg(x[idx], 0, 'g', 6) .arg(y[idx], 0, 'g', 6); return QString(); } void JFJochSimpleChartView::leaveEvent(QEvent *event) { QChartView::leaveEvent(event); if (m_hoverLine) { chart()->scene()->removeItem(m_hoverLine); delete m_hoverLine; m_hoverLine = nullptr; } emit writeStatusBar(QString(), 0); // clear status bar when leaving }