diff --git a/viewer/CMakeLists.txt b/viewer/CMakeLists.txt index 4c780983..14d8039f 100644 --- a/viewer/CMakeLists.txt +++ b/viewer/CMakeLists.txt @@ -24,8 +24,8 @@ ADD_EXECUTABLE(jfjoch_viewer jfjoch_viewer.cpp JFJochViewerWindow.cpp JFJochView JFJochViewerSidePanel.h widgets/TitleLabel.cpp widgets/TitleLabel.h - charts/JFJochChartView.cpp - charts/JFJochChartView.h + charts/JFJochDatasetInfoChartView.cpp + charts/JFJochDatasetInfoChartView.h widgets/JFJochViewerImageStatistics.cpp widgets/JFJochViewerImageStatistics.h charts/JFJochSimpleChartView.cpp diff --git a/viewer/JFJochViewerDatasetInfo.cpp b/viewer/JFJochViewerDatasetInfo.cpp index 8d0e78ec..37cd69c8 100644 --- a/viewer/JFJochViewerDatasetInfo.cpp +++ b/viewer/JFJochViewerDatasetInfo.cpp @@ -35,7 +35,7 @@ JFJochViewerDatasetInfo::JFJochViewerDatasetInfo(QWidget *parent) : QWidget(pare layout->addWidget(grid_button, 0, 3); stack = new QStackedWidget(this); - chart_view = new JFJochChartView(this); + chart_view = new JFJochDatasetInfoChartView(this); grid_scan_image = new JFJochGridScanImage(this); stack->addWidget(chart_view); @@ -43,7 +43,7 @@ JFJochViewerDatasetInfo::JFJochViewerDatasetInfo(QWidget *parent) : QWidget(pare layout->addWidget(stack, 1, 0, 1, 4); - connect(chart_view, &JFJochChartView::imageSelected, + connect(chart_view, &JFJochDatasetInfoChartView::imageSelected, this, &JFJochViewerDatasetInfo::imageSelectedInChart); connect(reset_button, &QPushButton::clicked, @@ -55,7 +55,7 @@ JFJochViewerDatasetInfo::JFJochViewerDatasetInfo(QWidget *parent) : QWidget(pare connect(grid_scan_image, &JFJochGridScanImage::imageSelected, this, &JFJochViewerDatasetInfo::imageSelectedInChart); - connect(chart_view, &JFJochChartView::writeStatusBar, + connect(chart_view, &JFJochDatasetInfoChartView::writeStatusBar, [this](QString string, int timeout_ms) { emit writeStatusBar(string, timeout_ms); }); diff --git a/viewer/JFJochViewerDatasetInfo.h b/viewer/JFJochViewerDatasetInfo.h index 788de1a4..544f8571 100644 --- a/viewer/JFJochViewerDatasetInfo.h +++ b/viewer/JFJochViewerDatasetInfo.h @@ -8,14 +8,14 @@ #include #include -#include "charts/JFJochChartView.h" +#include "charts/JFJochDatasetInfoChartView.h" #include "../reader/JFJochReader.h" #include "image_viewer/JFJochGridScanImage.h" class JFJochViewerDatasetInfo : public QWidget { Q_OBJECT QComboBox *combo_box; - JFJochChartView *chart_view; + JFJochDatasetInfoChartView *chart_view; const std::vector *GetDataset(); std::shared_ptr dataset; std::shared_ptr image; diff --git a/viewer/charts/JFJochChartView.cpp b/viewer/charts/JFJochDatasetInfoChartView.cpp similarity index 67% rename from viewer/charts/JFJochChartView.cpp rename to viewer/charts/JFJochDatasetInfoChartView.cpp index d80e14f5..755c9032 100644 --- a/viewer/charts/JFJochChartView.cpp +++ b/viewer/charts/JFJochDatasetInfoChartView.cpp @@ -6,21 +6,21 @@ #include #include -#include "JFJochChartView.h" +#include "JFJochDatasetInfoChartView.h" -JFJochChartView::JFJochChartView(QWidget *parent) +JFJochDatasetInfoChartView::JFJochDatasetInfoChartView(QWidget *parent) : QChartView(new QChart(), parent) { chart()->legend()->hide(); setRenderHint(QPainter::Antialiasing); - setRubberBand(QChartView::RubberBand::RectangleRubberBand); + // setRubberBand(QChartView::RubberBand::RectangleRubberBand); setMouseTracking(true); m_hoverLoadTimer = new QTimer(this); m_hoverLoadTimer->setSingleShot(true); - connect(m_hoverLoadTimer, &QTimer::timeout, this, &JFJochChartView::onHoverLoadTimeout); + connect(m_hoverLoadTimer, &QTimer::timeout, this, &JFJochDatasetInfoChartView::onHoverLoadTimeout); } -void JFJochChartView::setImage(int64_t val) { +void JFJochDatasetInfoChartView::setImage(int64_t val) { if (!currentSeries) return; @@ -36,7 +36,7 @@ void JFJochChartView::setImage(int64_t val) { } } -void JFJochChartView::mousePressEvent(QMouseEvent *event) { +void JFJochDatasetInfoChartView::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { QPointF clickedPoint = event->pos(); QPointF chartCoord = chart()->mapToValue(clickedPoint); @@ -47,11 +47,11 @@ void JFJochChartView::mousePressEvent(QMouseEvent *event) { QChartView::mousePressEvent(event); // Call the base implementation } -void JFJochChartView::resetZoom() { +void JFJochDatasetInfoChartView::resetZoom() { chart()->zoomReset(); } -void JFJochChartView::updateChart() { +void JFJochDatasetInfoChartView::updateChart() { chart()->removeAllSeries(); if (m_hoverLine) { chart()->scene()->removeItem(m_hoverLine); @@ -72,10 +72,7 @@ void JFJochChartView::updateChart() { if (!std::isfinite(values[i])) continue; const double disp = values[i]; if (!std::isfinite(disp)) continue; - if (goniometer_axis) - series->append(goniometer_axis->GetAngle_deg(i), disp); - else - series->append(i, disp); + series->append(i, disp); if (disp < dispMin) dispMin = disp; if (disp > dispMax) dispMax = disp; } @@ -94,11 +91,7 @@ void JFJochChartView::updateChart() { const double mean = tmp / static_cast(count); const double disp = mean; if (std::isfinite(disp)) { - if (goniometer_axis) { - series->append(goniometer_axis->GetAngle_deg((i + 0.5) * binning), disp); - } else { - series->append((i + 0.5) * binning, disp); - } + series->append((i + 0.5) * binning, disp); if (disp < dispMin) dispMin = disp; if (disp > dispMax) dispMax = disp; } @@ -107,22 +100,67 @@ void JFJochChartView::updateChart() { } if (curr_image < values.size() && curr_image >= 0 && std::isfinite(values[curr_image])) { - if (goniometer_axis) - currentSeries->append(goniometer_axis->GetAngle_deg(curr_image), values[curr_image]); - else - currentSeries->append(curr_image, values[curr_image]); + currentSeries->append(curr_image, values[curr_image]); } chart()->addSeries(series); chart()->addSeries(currentSeries); chart()->createDefaultAxes(); - if (goniometer_axis) { - chart()->axisX(series)->setTitleText("Rotation angle (deg.)"); - } else { - chart()->axisX(series)->setTitleText("Image number"); + + // ----- X axis handling ----- + QValueAxis *axisX = qobject_cast(chart()->axisX(series)); + if (axisX) { + if (goniometer_axis.has_value() && m_xUseGoniometerAxis) { + // Hide labels on numeric axis and move it to the top + + axisX->setTitleText(QString("")); + axisX->setLabelsVisible(false); + + // Re-attach numeric axis on top side (default axis on other side) + chart()->removeAxis(axisX); + chart()->addAxis(axisX, Qt::AlignTop); + series->attachAxis(axisX); + currentSeries->attachAxis(axisX); + + // Build a visible category axis on the bottom with goniometer angles + auto *axXcat = new QCategoryAxis(); + axXcat->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue); + axXcat->setGridLineVisible(false); + axXcat->setMinorGridLineVisible(false); + axXcat->setTitleText(QStringLiteral("Rotation angle (deg.)")); + + const int tickCountX = std::max(2, axisX->tickCount()); + const double xmin = axisX->min(); + const double xmax = axisX->max(); + const double xstep = (tickCountX > 1) ? (xmax - xmin) / (tickCountX - 1) : 0.0; + const int64_t lastIdx = static_cast(values.empty() ? 0 : values.size() - 1); + + for (int i = 0; i < tickCountX; ++i) { + const double xv = (i == tickCountX - 1) ? xmax : (xmin + i * xstep); + // Map tick position to closest image index + int64_t imgIdx = static_cast(std::llround(xv)); + if (imgIdx < 0) imgIdx = 0; + if (imgIdx > lastIdx) imgIdx = lastIdx; + + double angleDeg = 0.0; + if (lastIdx >= 0) { + angleDeg = goniometer_axis->GetAngle_deg(imgIdx); + } + + QString lab = QString::number(angleDeg, 'f', 2); + axXcat->append(lab, xv); + } + + chart()->addAxis(axXcat, Qt::AlignBottom); + series->attachAxis(axXcat); + currentSeries->attachAxis(axXcat); + } else { + axisX->setLabelsVisible(true); + axisX->setTitleText(QStringLiteral("Image number")); + } } - // Set Y-axis behavior according to options + // ----- Y-axis handling ----- QValueAxis *axisY = qobject_cast(chart()->axisY(series)); if (axisY) { if (std::isfinite(dispMin) && std::isfinite(dispMax)) { @@ -140,11 +178,15 @@ void JFJochChartView::updateChart() { } } - // If Y is in 1/d^2, hide numeric labels and add a category axis with d labels if (m_yOneOverD) { - axisY->setLabelsVisible(false); // keep grid/ticks from value axis + // Keep value axis for numeric range + grid, but move it to the RIGHT + axisY->setLabelsVisible(false); + chart()->removeAxis(axisY); + chart()->addAxis(axisY, Qt::AlignRight); + series->attachAxis(axisY); + currentSeries->attachAxis(axisY); - // Build a mirrored visible axis with labels in d (Å) + // Build a mirrored visible axis with labels in d (Å) on the LEFT auto *axYcat = new QCategoryAxis(); axYcat->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue); axYcat->setGridLineVisible(false); @@ -169,6 +211,7 @@ void JFJochChartView::updateChart() { } axYcat->append(lab, yv); } + chart()->addAxis(axYcat, Qt::AlignLeft); series->attachAxis(axYcat); currentSeries->attachAxis(axYcat); @@ -180,7 +223,12 @@ void JFJochChartView::updateChart() { chart()->setMargins(m); } } else { - // Normal numeric labels + // Normal numeric labels, axis on the LEFT + chart()->removeAxis(axisY); + chart()->addAxis(axisY, Qt::AlignLeft); + series->attachAxis(axisY); + currentSeries->attachAxis(axisY); + axisY->setTitleText(QString()); axisY->setLabelsVisible(true); } @@ -188,14 +236,14 @@ void JFJochChartView::updateChart() { } } -void JFJochChartView::setBinning(int64_t val) { +void JFJochDatasetInfoChartView::setBinning(int64_t val) { if (val >= 1) { binning = val; updateChart(); } } -void JFJochChartView::contextMenuEvent(QContextMenuEvent *event) { +void JFJochDatasetInfoChartView::contextMenuEvent(QContextMenuEvent *event) { QMenu menu(this); QAction *copyXY = menu.addAction("Copy (x y) points"); copyXY->setEnabled(!values.empty()); @@ -207,6 +255,11 @@ void JFJochChartView::contextMenuEvent(QContextMenuEvent *event) { actMinYZero->setCheckable(true); actMinYZero->setChecked(m_minYZeroEnabled); + QAction *actXGoniometer = menu.addAction("Use goniometer X-axis"); + actXGoniometer->setCheckable(true); + actXGoniometer->setChecked(m_xUseGoniometerAxis); + actXGoniometer->setEnabled(goniometer_axis.has_value()); + QAction *chosen = menu.exec(event->globalPos()); if (chosen == copyXY) { QString out; @@ -222,10 +275,13 @@ void JFJochChartView::contextMenuEvent(QContextMenuEvent *event) { } else if (chosen == actMinYZero) { m_minYZeroEnabled = !m_minYZeroEnabled; updateChart(); + } else if (chosen == actXGoniometer) { + m_xUseGoniometerAxis = !m_xUseGoniometerAxis; + updateChart(); } } -void JFJochChartView::mouseMoveEvent(QMouseEvent *event) { +void JFJochDatasetInfoChartView::mouseMoveEvent(QMouseEvent *event) { QChartView::mouseMoveEvent(event); if (!series || values.empty()) return; @@ -255,6 +311,7 @@ void JFJochChartView::mouseMoveEvent(QMouseEvent *event) { // Status bar text const double yv = values[static_cast(idx)]; QString text; + if (m_yOneOverD) { if (std::isfinite(yv) && yv > 0.0) { const double d = 1.0 / std::sqrt(yv); @@ -286,7 +343,7 @@ void JFJochChartView::mouseMoveEvent(QMouseEvent *event) { } } -void JFJochChartView::leaveEvent(QEvent *event) { +void JFJochDatasetInfoChartView::leaveEvent(QEvent *event) { QChartView::leaveEvent(event); if (m_hoverLine) { chart()->scene()->removeItem(m_hoverLine); @@ -298,7 +355,7 @@ void JFJochChartView::leaveEvent(QEvent *event) { emit writeStatusBar(QString(), 0); } -void JFJochChartView::onHoverLoadTimeout() { +void JFJochDatasetInfoChartView::onHoverLoadTimeout() { if (!(QApplication::keyboardModifiers() & Qt::ShiftModifier)) return; diff --git a/viewer/charts/JFJochChartView.h b/viewer/charts/JFJochDatasetInfoChartView.h similarity index 92% rename from viewer/charts/JFJochChartView.h rename to viewer/charts/JFJochDatasetInfoChartView.h index d9511e55..b5850a82 100644 --- a/viewer/charts/JFJochChartView.h +++ b/viewer/charts/JFJochDatasetInfoChartView.h @@ -12,7 +12,7 @@ #include #include "../../common/GoniometerAxis.h" -class JFJochChartView : public QChartView { +class JFJochDatasetInfoChartView : public QChartView { Q_OBJECT QLineSeries *series = nullptr; @@ -27,7 +27,7 @@ class JFJochChartView : public QChartView { bool m_minYZeroEnabled = true; bool m_yOneOverD = false; - + bool m_xUseGoniometerAxis = true; std::optional goniometer_axis; void updateChart(); @@ -46,7 +46,7 @@ private: void contextMenuEvent(QContextMenuEvent *event) override; public: - explicit JFJochChartView(QWidget *parent = nullptr); + explicit JFJochDatasetInfoChartView(QWidget *parent = nullptr); void setImage(int64_t val); public slots: