// 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(200); setRenderHint(QPainter::Antialiasing); //setRubberBand(QChartView::RubberBand::HorizontalRubberBand); } void JFJochSimpleChartView::UpdateData(const std::vector &in_x, const std::vector &in_y, bool one_over_x) { x = in_x; y = in_y; 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); auto* axY = new QValueAxis(); 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; if (one_over_x) { // Hidden X value axis on top for grid/range auto* axXTop = new QValueAxis(); axXTop->setRange(xmin, xmax); axXTop->setTickCount(5); axXTop->setLabelsVisible(false); chart()->addAxis(axXTop, Qt::AlignTop); series->attachAxis(axXTop); // Visible category axis at bottom for 1/x labels auto* axXcat = new QCategoryAxis(); axXcat->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue); axXcat->setGridLineVisible(false); axXcat->setMinorGridLineVisible(false); const int tickCount = axXTop->tickCount(); const double step = (tickCount > 1) ? (xmax - xmin) / (tickCount - 1) : 0.0; for (int i = 0; i < tickCount; ++i) { const double xv = xmin + i * step; const QString lab = (std::abs(xv) < 1e-12) ? QStringLiteral("∞") : QString::number(1.0 / xv, 'g', 4); axXcat->append(lab, xv); } chart()->addAxis(axXcat, Qt::AlignBottom); series->attachAxis(axXcat); } else { // Normal value axis at bottom auto* axX = new QValueAxis(); axX->setRange(xmin, xmax); chart()->addAxis(axX, Qt::AlignBottom); series->attachAxis(axX); } } void JFJochSimpleChartView::ClearData() { 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::applyInverseXLabels(QValueAxis* axX, QLineSeries* s) { axX->setTickCount(5); // adjust as needed axX->setLabelsVisible(false); // hide default numeric labels auto* axXcat = new QCategoryAxis(); axXcat->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue); axXcat->setGridLineVisible(false); axXcat->setMinorGridLineVisible(false); const int tickCount = axX->tickCount(); const double min = axX->min(); const double max = axX->max(); const double step = (tickCount > 1) ? (max - min) / (tickCount - 1) : 0.0; for (int i = 0; i < tickCount; ++i) { const double xv = min + i * step; if (std::abs(xv) < 1e-12) { axXcat->append("∞", xv); } else { const double inv = 1.0 / xv; axXcat->append(QString::number(inv, 'g', 4), xv); } } chart()->addAxis(axXcat, Qt::AlignBottom); s->attachAxis(axXcat); }