v1.0.0-rc.102 #7
@@ -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<const JFJochReaderImage> in_image) {
|
||||
|
||||
@@ -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<const JFJochReaderImage> image;
|
||||
QComboBox *combo_box;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "JFJochOneOverResSqChartView.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QClipboard>
|
||||
#include <QApplication>
|
||||
#include <QCategoryAxis>
|
||||
|
||||
JFJochOneOverResSqChartView::JFJochOneOverResSqChartView (QWidget *parent)
|
||||
: JFJochSimpleChartView(parent) {}
|
||||
|
||||
void JFJochOneOverResSqChartView::UpdateData(const std::vector<float> &in_x, const std::vector<float> &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();
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#ifndef JFJOCH_JFJOCHONEOVERRESSQCHARTVIEW_H
|
||||
#define JFJOCH_JFJOCHONEOVERRESSQCHARTVIEW_H
|
||||
|
||||
#include "JFJochSimpleChartView.h"
|
||||
|
||||
#include <QChartView>
|
||||
#include <QLineSeries>
|
||||
#include <QValueAxis>
|
||||
|
||||
class JFJochOneOverResSqChartView : public JFJochSimpleChartView {
|
||||
Q_OBJECT
|
||||
|
||||
QString getText(size_t idx) override;
|
||||
public:
|
||||
JFJochOneOverResSqChartView(QWidget *parent = nullptr);
|
||||
|
||||
void UpdateData(const std::vector<float> &in_x, const std::vector<float> &in_y,
|
||||
QString legend_x, QString legend_y) override;
|
||||
};
|
||||
|
||||
|
||||
#endif //JFJOCH_JFJOCHONEOVERRESSQCHARTVIEW_H
|
||||
@@ -7,9 +7,10 @@
|
||||
#include <QClipboard>
|
||||
#include <QApplication>
|
||||
#include <QCategoryAxis>
|
||||
#include <QRegularExpression>
|
||||
|
||||
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 <iostream>
|
||||
|
||||
void JFJochSimpleChartView::UpdateData(const std::vector<float> &in_x, const std::vector<float> &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<float> &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<double>(*minYIt);
|
||||
ymax = static_cast<double>(*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<int>(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<float>(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
|
||||
}
|
||||
|
||||
@@ -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<float> &in_x, const std::vector<float> &in_y,
|
||||
QString legend_x, QString legend_y);
|
||||
QString legend_x, QString legend_y, bool one_over_d);
|
||||
void ClearData();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user