From 21824c24e34d8fdc9ad91b386a67e55fa11aa22b Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Tue, 18 Nov 2025 08:39:05 +0100 Subject: [PATCH] jfjoch_viewer: Minor adjustments --- viewer/JFJochViewerSidePanelChart.cpp | 34 ++--- viewer/JFJochViewerSidePanelChart.h | 2 - viewer/JFJochViewerWindow.cpp | 6 +- .../widgets/JFJochOneOverResSqChartView.cpp | 87 ----------- viewer/widgets/JFJochOneOverResSqChartView.h | 25 ---- viewer/widgets/JFJochSimpleChartView.cpp | 141 +++++++++++++++--- viewer/widgets/JFJochSimpleChartView.h | 4 +- 7 files changed, 136 insertions(+), 163 deletions(-) delete mode 100644 viewer/widgets/JFJochOneOverResSqChartView.cpp delete mode 100644 viewer/widgets/JFJochOneOverResSqChartView.h diff --git a/viewer/JFJochViewerSidePanelChart.cpp b/viewer/JFJochViewerSidePanelChart.cpp index 24db9517..6e1ccb36 100644 --- a/viewer/JFJochViewerSidePanelChart.cpp +++ b/viewer/JFJochViewerSidePanelChart.cpp @@ -21,16 +21,9 @@ JFJochViewerSidePanelChart::JFJochViewerSidePanelChart(QWidget *parent) : QWidge this, &JFJochViewerSidePanelChart::comboBoxSelected); azint_plot = new JFJochSimpleChartView(this); - one_over_d_sq_plot = new JFJochOneOverResSqChartView(this); - - stack = new QStackedWidget(this); - stack->addWidget(azint_plot); // index 0 - stack->addWidget(one_over_d_sq_plot); // index 2 - - layout->addWidget(stack); + layout->addWidget(azint_plot); // index 0 setLayout(layout); - } void JFJochViewerSidePanelChart::comboBoxSelected(int val) { @@ -46,40 +39,37 @@ void JFJochViewerSidePanelChart::redrawPlot() { case 0: x = image->GetAzInt1D_BinToQ(); y = image->GetAzInt1D(); - azint_plot->UpdateData(x, y, "Q [Å^-1]", ""); - stack->setCurrentWidget(azint_plot); + azint_plot->UpdateData(x, y, "Q [Å^-1]", "", false); break; case 1: x = image->ImageData().integration_B_one_over_d_square; y = image->ImageData().integration_B_logI; - one_over_d_sq_plot->UpdateData(x, y, "d [Å]", "ln(<I>)"); - stack->setCurrentWidget(one_over_d_sq_plot); + azint_plot->UpdateData(x, y, "d [Å]", "ln(<I>)", true); + break; case 2: x = image->ImageData().integration_Isigma_one_over_d_square; y = image->ImageData().integration_Isigma; - one_over_d_sq_plot->UpdateData(x, y, "d [Å]", "I/sigma"); - stack->setCurrentWidget(one_over_d_sq_plot); + azint_plot->UpdateData(x, y, "d [Å]", "I/sigma", true); + break; case 4: x = image->Dataset().experiment.GetFluorescenceSpectrum().GetEnergy_eV(); y = image->Dataset().experiment.GetFluorescenceSpectrum().GetData(); for (auto &iter: x) iter /= 1000.0; // Convert eV to keV - azint_plot->UpdateData(x, y, "E [keV]", ""); - stack->setCurrentWidget(azint_plot); + azint_plot->UpdateData(x, y, "E [keV]", "", false); break; case 5: x = image->ImageData().spot_plot_one_over_d_square; y = image->ImageData().spot_plot_count; - one_over_d_sq_plot->UpdateData(x, y, "d [Å]", "Count"); - stack->setCurrentWidget(one_over_d_sq_plot); + azint_plot->UpdateData(x, y, "d [Å]", "Count", true); + break; case 6: x = image->ImageData().spot_plot_one_over_d_square; y = image->ImageData().spot_plot_intensity; - one_over_d_sq_plot->UpdateData(x, y, "d [Å]", "Count"); - stack->setCurrentWidget(one_over_d_sq_plot); + azint_plot->UpdateData(x, y, "d [Å]", "Count", true); break; } } @@ -87,10 +77,6 @@ void JFJochViewerSidePanelChart::redrawPlot() { [&](QString string, int timeout_ms) { emit writeStatusBar(string, timeout_ms); }); - connect(one_over_d_sq_plot, &JFJochSimpleChartView::writeStatusBar, - [&](QString string, int timeout_ms) { - emit writeStatusBar(string, timeout_ms); - }); } void JFJochViewerSidePanelChart::loadImage(std::shared_ptr in_image) { diff --git a/viewer/JFJochViewerSidePanelChart.h b/viewer/JFJochViewerSidePanelChart.h index 84741359..7963a705 100644 --- a/viewer/JFJochViewerSidePanelChart.h +++ b/viewer/JFJochViewerSidePanelChart.h @@ -10,7 +10,6 @@ #include "../reader/JFJochReaderImage.h" #include "widgets/JFJochSimpleChartView.h" -#include "widgets/JFJochOneOverResSqChartView.h" #include "widgets/JFJochAzIntImage.h" class JFJochViewerSidePanelChart : public QWidget { @@ -18,7 +17,6 @@ class JFJochViewerSidePanelChart : public QWidget { QStackedWidget *stack = nullptr; JFJochSimpleChartView *azint_plot = nullptr; - JFJochOneOverResSqChartView *one_over_d_sq_plot = nullptr; std::shared_ptr image; QComboBox *combo_box; diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index f3a4d954..b76d1b6e 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -77,9 +77,9 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString side_panel_scroll->setWidgetResizable(true); side_panel_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); side_panel_scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - side_panel_scroll->setMinimumWidth(200); + side_panel_scroll->setMinimumWidth(450); // side_panel_scroll->setMaximumWidth(700); - side_panel_scroll->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + side_panel_scroll->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); h_splitter->addWidget(side_panel_scroll); @@ -99,7 +99,7 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString auto processingWindow = new JFJochViewerProcessingWindow(spot_finding_settings, indexing_settings, this); auto calibrationWindow = new JFJochCalibrationWindow(this); auto azintWindow = new JFJochAzIntWindow(experiment.GetAzimuthalIntegrationSettings(), this); - auto azintImageWindow = new JFJoch2DAzintImageWindow(this); + auto azintImageWindow = new JFJoch2DAzintImageWindow(this); menuBar->AddWindowEntry(tableWindow, "Image list"); menuBar->AddWindowEntry(spotWindow, "Spot list"); diff --git a/viewer/widgets/JFJochOneOverResSqChartView.cpp b/viewer/widgets/JFJochOneOverResSqChartView.cpp deleted file mode 100644 index d1309384..00000000 --- a/viewer/widgets/JFJochOneOverResSqChartView.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute -// SPDX-License-Identifier: GPL-3.0-only - -#include "JFJochOneOverResSqChartView.h" - -#include -#include -#include -#include - -JFJochOneOverResSqChartView::JFJochOneOverResSqChartView (QWidget *parent) - : JFJochSimpleChartView(parent) {} - -void JFJochOneOverResSqChartView::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; - - float max_y = *std::max_element(y.begin(), y.end()); - if (max_y <= 0) 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); - - axY->setRange(0, 1.05 * max_y); - chart()->addAxis(axY, Qt::AlignLeft); - series->attachAxis(axY); - - // Build X range - const float xmin = x[0], xmax = x[x.size() - 1]; - - // 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); - - axXcat->setTitleText(legend_x); - - 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 / sqrtf(xv), 'f', 2); - axXcat->append(lab, xv); - } - chart()->addAxis(axXcat, Qt::AlignBottom); - series->attachAxis(axXcat); -} - -QString JFJochOneOverResSqChartView::getText(size_t idx) { - if (idx < x.size() && x[idx] > 0) - return QString("d = %1 Å, y = %2") - .arg(1 / sqrt(x[idx]), 0, 'g', 4) - .arg(y[idx], 0, 'g', 6); - return QString(); -} - diff --git a/viewer/widgets/JFJochOneOverResSqChartView.h b/viewer/widgets/JFJochOneOverResSqChartView.h deleted file mode 100644 index c418ac2c..00000000 --- a/viewer/widgets/JFJochOneOverResSqChartView.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute -// SPDX-License-Identifier: GPL-3.0-only - -#ifndef JFJOCH_JFJOCHONEOVERRESSQCHARTVIEW_H -#define JFJOCH_JFJOCHONEOVERRESSQCHARTVIEW_H - -#include "JFJochSimpleChartView.h" - -#include -#include -#include - -class JFJochOneOverResSqChartView : public JFJochSimpleChartView { - Q_OBJECT - - QString getText(size_t idx) override; -public: - JFJochOneOverResSqChartView(QWidget *parent = nullptr); - - void UpdateData(const std::vector &in_x, const std::vector &in_y, - QString legend_x, QString legend_y) override; -}; - - -#endif //JFJOCH_JFJOCHONEOVERRESSQCHARTVIEW_H \ No newline at end of file diff --git a/viewer/widgets/JFJochSimpleChartView.cpp b/viewer/widgets/JFJochSimpleChartView.cpp index 0a9303f3..16d07ec8 100644 --- a/viewer/widgets/JFJochSimpleChartView.cpp +++ b/viewer/widgets/JFJochSimpleChartView.cpp @@ -7,9 +7,10 @@ #include #include #include +#include JFJochSimpleChartView::JFJochSimpleChartView(QWidget *parent) - : QChartView(new QChart(), parent) { + : QChartView(new QChart(), parent) { chart()->legend()->hide(); setFixedHeight(300); setRenderHint(QPainter::Antialiasing); @@ -17,8 +18,11 @@ JFJochSimpleChartView::JFJochSimpleChartView(QWidget *parent) //setRubberBand(QChartView::RubberBand::HorizontalRubberBand); } +#include + void JFJochSimpleChartView::UpdateData(const std::vector &in_x, const std::vector &in_y, - QString legend_x, QString legend_y) { + QString legend_x, QString legend_y, bool in_one_over_d) { + one_over_d = in_one_over_d; x = in_x; y = in_y; @@ -41,23 +45,114 @@ void JFJochSimpleChartView::UpdateData(const std::vector &in_x, const std chart()->addSeries(series); m_series = series; - auto *axY = new QValueAxis(); - axY->setTitleText(legend_y); - chart()->addAxis(axY, Qt::AlignLeft); - series->attachAxis(axY); + // Compute Y range + double ymin = 0.0, ymax = 0.0; + { + auto [minYIt, maxYIt] = std::minmax_element(y.begin(), y.end()); + ymin = static_cast(*minYIt); + ymax = static_cast(*maxYIt); + if (ymin == ymax) { + const double eps = (std::abs(ymax) > 0.0) ? std::abs(ymax) * 1e-6 : 1.0; + ymin -= eps; + ymax += eps; + } + } + + // Hidden value axis (left): range + grid + auto *axYvalue = new QValueAxis(); + axYvalue->setTitleText(legend_y); + axYvalue->setRange(ymin, ymax); + axYvalue->setTickCount(5); // ensure ticks exist now (avoid 0 before layout) + axYvalue->setLabelsVisible(false); // hide numeric labels (grid only) + chart()->addAxis(axYvalue, Qt::AlignRight); + series->attachAxis(axYvalue); + + // SI formatter (Plotly/D3-like ".3~s") + int precision = 3; + const auto formatSI = [precision](double v) -> QString { + if (v == 0.0 || std::isnan(v)) return QStringLiteral("0"); + static const struct { int exp; const char* pre; } si[] = { + {24,"Y"},{21,"Z"},{18,"E"},{15,"P"},{12,"T"},{9,"G"},{6,"M"},{3,"k"}, + {0,""},{-3,"m"},{-6,"µ"},{-9,"n"},{-12,"p"},{-15,"f"},{-18,"a"},{-21,"z"},{-24,"y"} + }; + double av = std::fabs(v); + int chosenExp = 0; const char* chosenPre = ""; + if (av > 0.0) { + int exp10 = static_cast(std::floor(std::log10(av))); + int exp3 = (exp10 >= 0) ? (exp10 / 3) * 3 : -(((-exp10 + 2) / 3) * 3); + if (exp3 > 24) exp3 = 24; + if (exp3 < -24) exp3 = -24; + for (auto &e : si) if (e.exp == exp3) { chosenExp = e.exp; chosenPre = e.pre; break; } + } + const double scaled = v / std::pow(10.0, chosenExp); + QString num = QString::number(scaled, 'g', std::max(1, precision)); // ~ trimming effect + std::cout << v << " " << num.toStdString() << std::endl; + return (chosenPre && *chosenPre) ? num + QStringLiteral(" ") + QString::fromUtf8(chosenPre) : num; + }; + + // Visible category axis (right) with formatted labels + auto *axYcat = new QCategoryAxis(); + axYcat->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue); + axYcat->setGridLineVisible(false); + axYcat->setMinorGridLineVisible(false); + axYcat->setTitleText(legend_y); + axYcat->setLabelsVisible(true); // make sure labels are actually shown + + // Build ticks from hidden axis tick count immediately (stable even before layout) + const int tickCountY = std::max(2, axYvalue->tickCount()); + const double ystep = (ymax - ymin) / (tickCountY - 1); + for (int i = 0; i < tickCountY; ++i) { + const double yv = (i == tickCountY - 1) ? ymax : (ymin + i * ystep); + axYcat->append(formatSI(yv), yv); + } + chart()->addAxis(axYcat, Qt::AlignLeft); + series->attachAxis(axYcat); + + // Give a bit more room on the right so labels are not clipped + QMargins m = chart()->margins(); + if (m.right() < 12) { + m.setRight(12); + chart()->setMargins(m); + } // Build X range - auto [minXIt, maxXIt] = std::minmax_element(x.begin(), x.end()); - const double xmin = *minXIt, xmax = *maxXIt; + const float xmin = x[0], xmax = x[x.size() - 1]; - // 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); + if (one_over_d) { + auto *axXTop = new QValueAxis(); + axXTop->setRange(xmin, xmax); + axXTop->setTickCount(5); + axXTop->setLabelsVisible(false); + chart()->addAxis(axXTop, Qt::AlignTop); + series->attachAxis(axXTop); + + auto *axXcat = new QCategoryAxis(); + axXcat->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue); + axXcat->setGridLineVisible(false); + axXcat->setMinorGridLineVisible(false); + axXcat->setTitleText(legend_x); + + 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 / sqrtf(xv), 'f', 2); + axXcat->append(lab, xv); + } + chart()->addAxis(axXcat, Qt::AlignBottom); + series->attachAxis(axXcat); + } else { + 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(); @@ -97,7 +192,7 @@ void JFJochSimpleChartView::mouseMoveEvent(QMouseEvent *event) { // Map mouse position to chart coordinates const QPointF chartPos = chart()->mapToValue(event->pos(), m_series); - const double xVal = chartPos.x(); + const double xVal = chartPos.x(); // Find closest index in x[] auto it = std::lower_bound(x.begin(), x.end(), static_cast(xVal)); @@ -118,7 +213,7 @@ void JFJochSimpleChartView::mouseMoveEvent(QMouseEvent *event) { // 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(); + const QRectF plotArea = chart()->plotArea(); if (!m_hoverLine) { m_hoverLine = new QGraphicsLineItem; @@ -130,15 +225,21 @@ void JFJochSimpleChartView::mouseMoveEvent(QMouseEvent *event) { ptOnSeries.x(), plotArea.bottom())); // Send to status bar - emit writeStatusBar(getText(idx),6000); + emit writeStatusBar(getText(idx), 6000); } QString JFJochSimpleChartView::getText(size_t idx) { - if (idx < x.size()) + if (idx > x.size()) + return {}; + + if (one_over_d) + return QString("d = %1 Å, y = %2") + .arg(1 / sqrt(x[idx]), 0, 'g', 4) + .arg(y[idx], 0, 'g', 6); + else 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) { @@ -148,5 +249,5 @@ void JFJochSimpleChartView::leaveEvent(QEvent *event) { delete m_hoverLine; m_hoverLine = nullptr; } - emit writeStatusBar(QString(), 0); // clear status bar when leaving + emit writeStatusBar(QString(), 6000); // clear status bar when leaving } diff --git a/viewer/widgets/JFJochSimpleChartView.h b/viewer/widgets/JFJochSimpleChartView.h index 86e680f1..e6cdf64f 100644 --- a/viewer/widgets/JFJochSimpleChartView.h +++ b/viewer/widgets/JFJochSimpleChartView.h @@ -10,7 +10,7 @@ class JFJochSimpleChartView : public QChartView { Q_OBJECT - + bool one_over_d = false; signals: void writeStatusBar(QString string, int timeout_ms = 0); @@ -26,7 +26,7 @@ protected: public: JFJochSimpleChartView(QWidget *parent = nullptr); virtual void UpdateData(const std::vector &in_x, const std::vector &in_y, - QString legend_x, QString legend_y); + QString legend_x, QString legend_y, bool one_over_d); void ClearData(); };