Skip to content

File PlotInspector.hpp

File List > inspection > PlotInspector.hpp

Go to the documentation of this file

//
// SPDX-FileCopyrightText: 2026 Michal Grabarczyk
// SPDX-License-Identifier: GPL-3.0-only WITH Universal-FOSS-exception-1.0
//
// This file is also available under a separate commercial license.
// See COMMERCIAL-LICENSING.md for contact information.
//
#pragma once

#include "QAccelPlot/PlotView.hpp"
#include "QAccelPlot/inspection/InspectionRowModel.hpp"

#include <QQmlListProperty>

#include <memory>
#include <optional>

namespace QAccelPlot {

namespace Internal {
class OverlayChildren;
} // namespace Internal

class PlotInspector : public QObject {
    Q_OBJECT
    QML_NAMED_ELEMENT(PlotInspector)
    Q_CLASSINFO("DefaultProperty", "data")
    Q_PROPERTY(QQmlListProperty<QObject> data READ data)
    Q_PROPERTY(PlotView* plot READ plot WRITE setPlot NOTIFY plotChanged)
    Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
    Q_PROPERTY(Mode mode READ mode WRITE setMode NOTIFY modeChanged)
    Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
    Q_PROPERTY(bool snapToDataPoint READ snapToDataPoint WRITE setSnapToDataPoint NOTIFY snapToDataPointChanged)
    Q_PROPERTY(bool interpolate READ interpolate WRITE setInterpolate NOTIFY interpolateChanged)
    Q_PROPERTY(bool summaries READ summaries WRITE setSummaries NOTIFY summariesChanged)
    Q_PROPERTY(qreal summaryRadius READ summaryRadius WRITE setSummaryRadius NOTIFY summaryRadiusChanged)
    Q_PROPERTY(QList<PlotSeries*> includedSeries READ includedSeries WRITE setIncludedSeries NOTIFY includedSeriesChanged)
    Q_PROPERTY(QList<PlotSeries*> excludedSeries READ excludedSeries WRITE setExcludedSeries NOTIFY excludedSeriesChanged)
    Q_PROPERTY(bool followPointer READ followPointer WRITE setFollowPointer NOTIFY followPointerChanged)
    Q_PROPERTY(qreal cursorX READ cursorX WRITE setCursorX NOTIFY cursorChanged)
    Q_PROPERTY(qreal cursorY READ cursorY WRITE setCursorY NOTIFY cursorChanged)
    Q_PROPERTY(qreal cursorPixelX READ cursorPixelX WRITE setCursorPixelX NOTIFY cursorChanged)
    Q_PROPERTY(qreal cursorPixelY READ cursorPixelY WRITE setCursorPixelY NOTIFY cursorChanged)
    Q_PROPERTY(QString cursorXText READ cursorXText NOTIFY cursorChanged)
    Q_PROPERTY(QString cursorYText READ cursorYText NOTIFY cursorChanged)
    Q_PROPERTY(bool active READ active NOTIFY activeChanged)
    Q_PROPERTY(QPointF crosshairPosition READ crosshairPosition NOTIFY crosshairPositionChanged)
    Q_PROPERTY(InspectionRowModel* model READ model CONSTANT)
    Q_PROPERTY(int validCount READ validCount NOTIFY validCountChanged)

public:
    enum Mode {
        NearestX, 
        NearestY, 
        NearestXY 
    };
    Q_ENUM(Mode)


    explicit PlotInspector(QObject* parent = nullptr);
    ~PlotInspector() override;

    QQmlListProperty<QObject> data();

    PlotView* plot() const;
    void setPlot(PlotView* plot);
    bool enabled() const;
    void setEnabled(bool enabled);
    Mode mode() const;
    void setMode(Mode mode);
    qreal radius() const;
    void setRadius(qreal radius);
    bool snapToDataPoint() const;
    void setSnapToDataPoint(bool enabled);
    bool interpolate() const;
    void setInterpolate(bool enabled);
    bool summaries() const;
    void setSummaries(bool enabled);
    qreal summaryRadius() const;
    void setSummaryRadius(qreal radius);
    QList<PlotSeries*> includedSeries() const;
    void setIncludedSeries(const QList<PlotSeries*>& series);
    QList<PlotSeries*> excludedSeries() const;
    void setExcludedSeries(const QList<PlotSeries*>& series);
    bool followPointer() const;
    void setFollowPointer(bool follow);
    qreal cursorX() const;
    void setCursorX(qreal x);
    qreal cursorY() const;
    void setCursorY(qreal y);
    qreal cursorPixelX() const;
    void setCursorPixelX(qreal x);
    qreal cursorPixelY() const;
    void setCursorPixelY(qreal y);
    QString cursorXText() const;
    QString cursorYText() const;
    bool active() const;
    QPointF crosshairPosition() const;
    InspectionRowModel* model() const;
    int validCount() const;

    Q_INVOKABLE void refresh();
    Q_INVOKABLE void stepCursor(int steps);

signals:
    void plotChanged();
    void enabledChanged();
    void modeChanged();
    void radiusChanged();
    void snapToDataPointChanged();
    void interpolateChanged();
    void summariesChanged();
    void summaryRadiusChanged();
    void includedSeriesChanged();
    void excludedSeriesChanged();
    void followPointerChanged();
    void cursorChanged();
    void activeChanged();
    void crosshairPositionChanged();
    void validCountChanged();
    void refreshed();

private:
    // One series' query result together with what is needed to format it and to detect that it went stale.
    struct Query;
    struct State;

    void schedule();
    void flush();
    void reconnect();
    void connectSeries(PlotSeries* series);
    bool inspects(PlotSeries* series) const;
    QList<PlotSeries*> inspectedSeries() const;
    // Returns the cursor set from code in data units and in plot-local pixels; a coordinate it lacks, or that cannot be mapped, is NaN.
    QPointF pinnedData() const;
    QPointF pinnedPixel() const;
    // Returns the cursor in plot-local pixels, or nothing while the inspector is inactive. A coordinate that a cursor set from code lacks is NaN.
    std::optional<QPointF> activeCursor() const;
    // Returns the mode the cursor allows: without one of its coordinates, series are matched along the other axis.
    Mode effectiveMode(const QPointF& cursor) const;
    // Returns the source index of the data point at or before an interpolated dataPoint. On a step line its index can be the one after.
    int lowerNeighborIndex(SeriesInspection& inspection, const InspectionDataPoint& dataPoint) const;
    // Fills state with the results at cursor. Returns false when user code run by a label formatter invalidated them.
    bool inspect(const QPointF& cursor, State& state) const;
    QList<Query> queryRows(const QPointF& cursor) const;
    // Series matched by position report the record under a cursor with both coordinates, in every mode.
    InspectionDataPoint matchDataPoint(PlotSeries& series, Mode mode, const QPointF& cursor, const QPointF& local) const;
    // Between two data points a series always matches; the radius only limits how far past its ends it still does.
    InspectionDataPoint dataPointAlong(SeriesInspection& inspection, Mode mode, const QPointF& local) const;
    InspectionSummary summarize(PlotSeries& series, const QPointF& local, Mode mode) const;
    void formatRows(QList<Query>& queries) const;
    bool current(const QList<Query>& queries) const;
    QPointF snappedPosition(const QList<Query>& queries, const QPointF& cursor) const;
    void publish(const State& state);

    QPointer<PlotView> plot_;
    bool enabled_{true};
    Mode mode_{NearestX};
    qreal radius_{16};
    bool snapToDataPoint_{false};
    bool interpolate_{false};
    bool summaries_{false};
    qreal summaryRadius_{0.5};
    QList<QPointer<PlotSeries>> included_;
    QList<QPointer<PlotSeries>> excluded_;
    bool followPointer_{true};
    QList<QMetaObject::Connection> connections_;
    bool pending_{false};
    bool refreshing_{false};
    bool active_{false};
    QPointF crosshairPosition_;
    qreal cursorX_;
    qreal cursorY_;
    QPointF cursorPixel_;
    // Cursor set from code; used while followPointer_ is false. Each coordinate is in data units, or in plot-local pixels when its flag is set.
    qreal pinnedX_;
    qreal pinnedY_;
    bool pinnedXInPixels_{false};
    bool pinnedYInPixels_{false};
    QString cursorXText_;
    QString cursorYText_;
    int validCount_{0};
    InspectionRowModel* model_;
    std::unique_ptr<Internal::OverlayChildren> children_;
};

} // namespace QAccelPlot