Files
Jungfraujoch/viewer/charts/JFJochDatasetInfoChartView.h
T
leonarski_fandClaude Opus 5 20977e89bc
Build Packages / Create release (push) Successful in 15s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m24s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m32s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m18s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m53s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m56s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m15s
Build Packages / build:windows:nocuda (push) Successful in 17m30s
Build Packages / build:windows:cuda (push) Successful in 20m7s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 15m54s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m40s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 18m54s
Build Packages / build:rugnux:windows (push) Successful in 11m4s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 18m49s
Build Packages / Generate python client (push) Successful in 46s
Build Packages / Build documentation (push) Successful in 1m25s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m40s
Build Packages / build:rpm (rocky8) (push) Successful in 17m4s
Build Packages / build:rpm (rocky9) (push) Successful in 16m57s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 14m32s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 13m1s
Build Packages / Unit tests (push) Successful in 1h53m43s
viewer: draw a live grid scan as a grid, and the compact axis names upright
Two fixes in the dataset-info panel.

The live (HTTP) reader took the goniometer axis and the grid scan as
alternatives, so a raster collected at a head position - both fields set,
the axis with step 0 - lost its grid and was drawn as a line plot, with
the Grid button disabled. The broker had the same bug and lost it in
rc.162 (OpenAPIConvert.cpp); the HDF5 read-back path already read both.
The reader now sets both, like the other two.

Qt Charts always draws a vertical axis title rotated, which reads
awkwardly beside the two numbers it names in a half-height plot and is
what forced that plot's minimum height (Qt elided "Spots" to "Sp..." once
the font outgrew it). The title is switched off and the view paints the
name itself: upright, one letter under the next, in the series' colour,
centred on the plot area, with the left margin reserving its width. It is
re-measured on every rebuild, so a font change still fixes itself, and
plotAreaChanged re-centres it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:34:57 +02:00

144 lines
5.7 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <QtCharts/QChartView>
#include <QtCharts/QValueAxis>
#include <QtCharts/QLineSeries>
#include <QtCharts/QScatterSeries>
#include <QMouseEvent>
#include <QGraphicsLineItem>
#include <QGraphicsSimpleTextItem>
#include <QTimer>
#include <QPointer>
#include <QColor>
#ifdef JFJOCH_USE_FFTW
#include <fftw3.h>
#endif
#include "../../common/GoniometerAxis.h"
#include "../../reader/JFJochReaderDataset.h"
class JFJochDatasetInfoChartView : public QChartView {
Q_OBJECT
QPointer<QLineSeries> series = nullptr;
QPointer<QScatterSeries> currentSeries = nullptr;
QGraphicsLineItem *m_hoverLine = nullptr; // vertical crosshair, at the hovered x
QGraphicsLineItem *m_hoverLineHorizontal = nullptr; // horizontal crosshair, at the hovered y
QTimer *m_hoverLoadTimer = nullptr;
int64_t m_hoverPendingIdx = -1;
std::vector<float> values;
QString primaryName_; // legend name of the active (primary) run
int64_t binning = 1;
int64_t curr_image = 0;
bool m_minYZeroEnabled = true;
bool m_yOneOverD = false;
bool m_xUseGoniometerAxis = true;
// Compact mode: this view is one of two stacked half-height plots. The y axis is reduced to
// its two range ends, named by a short bold coloured label; the upper plot hides its x labels
// (the lower one carries them for both).
QString compact_label_;
bool compact_hide_x_ = false;
int compact_y_label_px_ = 0; // width of this plot's own widest y label
int compact_y_pad_px_ = 0; // y label width to pad out to (0 = no padding)
int compact_min_h_ = 0; // height this plot needs for its own y-axis title
int compact_title_w_ = 0; // width of that title, reserved in the left margin
// The y-axis name, painted upright (one letter per line) in place of Qt's rotated axis title.
QGraphicsSimpleTextItem *compact_title_ = nullptr;
// The other plot of a stacked pair. Both are padded out to the wider of the two y labels, so
// both curves start at the same x.
QPointer<JFJochDatasetInfoChartView> compact_peer_;
void applyCompactMargins();
void pairCompactYLabels();
void updateCompactTitle(const QFont &title_font);
void placeCompactTitle();
// Place the horizontal crosshair at y (scene coords), snapping to snap_to_y when the cursor is
// already within a few pixels of the curve. A non-finite snap_to_y never snaps.
void setHoverLineY(double y, double snap_to_y);
std::optional<GoniometerAxis> goniometer_axis;
std::optional<float> image_time_us;
void updateChart();
// Build regular (index or goniometer) chart
void buildTimeDomainChart();
public:
// One overlay line (a non-active run), drawn alongside the primary series with a legend entry.
// x maps each value to an image number on the shared x-axis (empty => 0,1,2,... by index).
struct NamedSeries { QString name; std::vector<float> values; QColor color; std::vector<float> x; };
private:
std::vector<NamedSeries> overlays_;
QColor primary_color_; // colour of the active (primary) run, if assigned
std::vector<float> primary_x_; // x (image numbers) for the primary series; empty => index
int64_t full_range_ = 0; // total images in the dataset; fixes the x-axis to [0, n)
// Append (binned) finite points of vals to s at x positions (xs empty => index), extending [mn,mx].
void appendSeries(QLineSeries *s, const std::vector<float> &vals, const std::vector<float> &xs,
double &mn, double &mx) const;
// FFT view toggle
bool m_showFFT = false;
#ifdef JFJOCH_USE_FFTW
// Cached FFT data for hover/status bar etc.
void buildFFTChart();
std::vector<double> m_fftMagnitudes;
std::vector<double> m_fftFrequenciesHz;
#endif
signals:
void imageSelected(int64_t number);
void writeStatusBar(QString string, int timeout_ms = 0);
private slots:
void onHoverLoadTimeout();
void setBinning(int64_t val);
private:
void changeEvent(QEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void leaveEvent(QEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
public:
explicit JFJochDatasetInfoChartView(QWidget *parent = nullptr);
void setImage(int64_t val);
// An empty label switches compact mode off. Takes effect on the next loadValues().
void SetCompact(const QString &axis_label, bool hide_x_labels);
// The other plot of a stacked pair; set once on both, it keeps their y labels the same width.
void SetCompactPeer(JFJochDatasetInfoChartView *peer) { compact_peer_ = peer; }
// Whether the y axis starts at zero. On by default: a spot count is read against zero, while a
// background level is not, and a plot of it this short needs the whole height for its shape.
void SetMinYZero(bool val) { m_minYZeroEnabled = val; }
public slots:
void resetZoom();
public:
// Load the primary (active) run's values plus any overlay runs. one_over_d2 applies the same
// d -> 1/d^2 transform to every series; primaryName/overlay names drive the legend.
void loadValues(const std::vector<float> &input,
int64_t image,
bool one_over_d2,
const JFJochReaderDataset *dataset = nullptr,
const QString &primaryName = QString(),
std::vector<NamedSeries> overlays = {},
const QColor &primaryColor = QColor(),
std::vector<float> primaryX = {},
int64_t fullRange = 0);
};