Skip to content

File PlotSeries.hpp

File List > QAccelPlot > src > QAccelPlot > series > PlotSeries.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/axis/Axis.hpp"
#include "QAccelPlot/inspection/SeriesInspection.hpp"
#include "QAccelPlot/series/DataPointStore.hpp"

#include <QList>
#include <QPointer>
#include <QQuickItem>
#include <QRectF>
#include <QString>
#if __has_include(<QtQmlIntegration/qqmlintegration.h>)
#include <QtQmlIntegration/qqmlintegration.h>
#else
#include <QtQml/qqmlregistration.h>
#endif

#include <limits>
#include <optional>
#include <string>
#include <vector>

namespace QAccelPlot {

struct LegendEntry;

class PlotSeries : public QQuickItem {
    Q_OBJECT
    QML_NAMED_ELEMENT(PlotSeries)
    QML_UNCREATABLE("PlotSeries is a base class for concrete plot series.")


    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(Axis* xAxis READ xAxis WRITE setXAxis NOTIFY xAxisChanged)
    Q_PROPERTY(Axis* yAxis READ yAxis WRITE setYAxis NOTIFY yAxisChanged)
    Q_PROPERTY(QRectF plotRect READ plotRect WRITE setPlotRect NOTIFY plotRectChanged)
    Q_PROPERTY(LegendSymbol legendSymbol READ legendSymbol WRITE setLegendSymbol NOTIFY legendSymbolChanged)
    Q_PROPERTY(bool hoverEnabled READ hoverEnabled WRITE setHoverEnabled NOTIFY hoverEnabledChanged)
    Q_PROPERTY(bool hovered READ hovered NOTIFY hoveredChanged)


    Q_PROPERTY(quint64 dataRevision READ dataRevision NOTIFY dataRevisionChanged)
    Q_PROPERTY(SeriesInspection* inspection READ inspection CONSTANT)
    Q_PROPERTY(int count READ count NOTIFY countChanged)

public:
    enum class LegendSymbol { Line, Fill, Marker };
    Q_ENUM(LegendSymbol)


    struct DataExtent {
        qreal min; 
        qreal max; 
    };

    struct DataBounds {
        qreal xMin; 
        qreal xMax; 
        qreal yMin; 
        qreal yMax; 
    };

    explicit PlotSeries(QQuickItem* parent = nullptr);
    ~PlotSeries() override;

    quint64 dataRevision() const;
    SeriesInspection* inspection() const;
    virtual int count() const;

    QString name() const;
    void setName(const QString& name);

    Axis* xAxis() const;
    void setXAxis(Axis* axis);

    Axis* yAxis() const;
    void setYAxis(Axis* axis);

    QRectF plotRect() const;
    void setPlotRect(const QRectF& rect);

    LegendSymbol legendSymbol() const;
    void setLegendSymbol(LegendSymbol symbol);

    bool hoverEnabled() const;
    void setHoverEnabled(bool enabled);
    bool hovered() const;

    virtual std::optional<LegendEntry> legendEntry() const;
    virtual QList<LegendEntry> legendEntries() const;

    void setData(const double* data, int count);
    void setData(std::vector<double>&& data, int count);
    void setDataF(const float* data, int count);
    void setDataF(std::vector<float>&& data, int count);
    void setData(const double* data, int count, const DataBounds& bounds);
    void setData(std::vector<double>&& data, int count, const DataBounds& bounds);
    void setDataF(const float* data, int count, const DataBounds& bounds);
    void setDataF(std::vector<float>&& data, int count, const DataBounds& bounds);
    void postData(std::vector<double>&& data, int count);
    void postData(std::vector<float>&& data, int count);
    virtual void clearData();

    std::optional<DataExtent> xDataRange() const;
    std::optional<DataExtent> yDataRange() const;

signals:
    void dataRevisionChanged();
    void countChanged();
    void nameChanged();
    void xAxisChanged();
    void yAxisChanged();
    void plotRectChanged();
    void legendSymbolChanged();
    void hoverEnabledChanged();
    void hoveredChanged();
    void legendEntryChanged();

protected:
    struct DataPointWording {
        std::string countName;  
        std::string plural;     
        std::string doubleUnit; 
        std::string floatUnit;  
    };

    PlotSeries(DataPointStore::Layout layout, DataPointWording wording, QQuickItem* parent);

    struct DataPointInput {
        std::vector<double> doubles;        
        std::vector<float> floats;          
        const double* doubleArray{nullptr}; 
        const float* floatArray{nullptr};   
        int count{0};                       
        int stride{0};                      
        bool precise{true};                 

        std::vector<double> toDoubles() &&;
    };

    virtual void applyDataPoints(DataPointInput&& input);
    void storeDataPoints(DataPointInput&& input);
    virtual int dataPointValueCount() const;

    enum class DataChange {
        Replaced, 
        Appended, 
    };

    DataPointStore& dataPoints();
    const DataPointStore& dataPoints() const;
    bool logScaleX() const;
    bool logScaleY() const;
    bool validateRawDataArguments(const void* data, int count) const;
    bool validateDataSize(std::size_t valueCount, int count, bool floatData, int valuesPerDataPoint = 0) const;
    void appendDataPoint(const double* coordinates);
    void notifyDataPointsChanged(DataChange change, int previousCount);
    virtual void onDataPointsChanged(DataChange change, int previousCount);

    void inspectionDataChanged(DataChange change = DataChange::Replaced);
    void invalidateInspection();
    virtual Internal::InspectionSource inspectionSource() const;
    virtual bool inspectionAvailable() const;
    virtual bool recordInspectionSupported() const;
    virtual InspectionRecord inspectionRecord(int index) const;
    virtual InspectionRecord inspectionRecordAt(const QPointF& position) const;
    virtual bool positionInspectionSupported() const;
    virtual InspectionDataPoint inspectionDataPointAt(const QPointF& position) const;

    struct DataRanges {
        std::optional<DataExtent> x; 
        std::optional<DataExtent> y; 
    };

    virtual DataRanges computeDataRanges() const;
    void invalidateDataRanges();
    void extendXDataRange(qreal x);
    void extendYDataRange(qreal y);
    virtual void onAxisScaleChanged();
    virtual void onAxisRangeChanged();
    QRectF resolvePlotRect() const;
    enum class CapacityOverflow {
        DropsExcess, 
        DropsAll,    
    };
    void updateRenderCapacity(int capacity, int recordCount, CapacityOverflow overflow = CapacityOverflow::DropsExcess);
    void supportHover();
    void setHovered(bool hovered);
    bool event(QEvent* event) override;

    int renderCapacity_{std::numeric_limits<int>::max()};
    bool overRenderCapacity_{false};

private:
    friend class SeriesInspection;

    void handleAxisScaleChanged();
    void handleAxisRangeChanged();
    // Returns whether the scale of a dimension changed.
    bool syncDataPointDimensions();
    void ensureDataRanges() const;
    void reportDataRangesChanged() const;
    void deliverTopmostHover(QHoverEvent* event);
    bool coveredBySeriesAbove(const QPointF& position) const;
    void updateAcceptHoverEvents();

    bool hoverSupported_{false};
    bool hoverEnabled_{true};
    bool hovered_{false};
    bool hoverDelivered_{false};
    quint64 dataRevision_{0};
    mutable SeriesInspection* inspection_{nullptr};
    QString name_;
    QPointer<Axis> xAxis_;
    QPointer<Axis> yAxis_;
    QMetaObject::Connection xAxisDestroyed_;
    QMetaObject::Connection yAxisDestroyed_;
    QRectF plotRect_;
    LegendSymbol legendSymbol_{LegendSymbol::Line};
    // The extents are a cache of a scan over the records, refreshed by const readers.
    mutable std::optional<DataExtent> xDataExtent_;
    mutable std::optional<DataExtent> yDataExtent_;
    mutable bool dataRangesStale_{false};
    // Bounds given with the data update in progress; they replace its scan.
    std::optional<DataBounds> updateBounds_;
    DataPointStore dataPoints_;
    DataPointWording wording_;
};

} // namespace QAccelPlot