Skip to content

File LineCurve.hpp

File List > QAccelPlot > src > QAccelPlot > series > LineCurve.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/effects/GradientColorTypes.hpp"
#include "QAccelPlot/effects/LineCurveEffect.hpp"
#include "QAccelPlot/linestyles/SolidLine.hpp"
#include "QAccelPlot/renderers/LineCurveLineRenderer.hpp"
#include "QAccelPlot/renderers/LineCurvePointRenderer.hpp"
#include "QAccelPlot/series/LineCurveGaps.hpp"
#include "QAccelPlot/series/LineCurveVertexCache.hpp"
#include "QAccelPlot/series/PlotSeries.hpp"
#include "QAccelPlot/series/SeriesMarker.hpp"
#include "QAccelPlot/series/StepMode.hpp"
#include "QAccelPlot/theme/ColorPalette.hpp"
#include "QAccelPlot/transitions/DataTransition.hpp"

#include <QPointF>
#include <QPointer>
#include <QQmlListProperty>
#include <QQuickItem>

#include <memory>
#include <optional>
#include <utility>
#include <vector>

namespace QAccelPlot {

class TransitionRunner;

class LineCurve : public PlotSeries {
    Q_OBJECT
    QML_NAMED_ELEMENT(LineCurve)


    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
    Q_PROPERTY(qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged)
    Q_PROPERTY(qreal hoverRadius READ hoverRadius WRITE setHoverRadius NOTIFY hoverRadiusChanged)
    Q_PROPERTY(DataTransition* transition READ transition WRITE setTransition NOTIFY transitionChanged)
    Q_PROPERTY(LineStyle* lineStyle READ lineStyle WRITE setLineStyle NOTIFY lineStyleChanged)
    Q_PROPERTY(qreal antialiasingFeather READ antialiasingFeather WRITE setAntialiasingFeather NOTIFY antialiasingFeatherChanged)
    Q_PROPERTY(QQmlListProperty<LineCurveEffect> effects READ effects)
    Q_PROPERTY(LineCurveGaps* gaps READ gaps CONSTANT)
    Q_PROPERTY(SeriesMarker* marker READ marker CONSTANT)
    Q_PROPERTY(StepMode stepMode READ stepMode WRITE setStepMode NOTIFY stepModeChanged)

public:
    explicit LineCurve(QQuickItem* parent = nullptr);
    ~LineCurve() override;

    QColor color() const;
    void setColor(const QColor& c);

    qreal lineWidth() const;
    void setLineWidth(qreal w);

    qreal hoverRadius() const;
    void setHoverRadius(qreal radius);

    DataTransition* transition() const;
    void setTransition(DataTransition* transition);

    LineStyle* lineStyle() const;
    void setLineStyle(LineStyle* style);

    qreal antialiasingFeather() const;
    void setAntialiasingFeather(qreal feather);

    QQmlListProperty<LineCurveEffect> effects();

    LineCurveGaps* gaps() const;

    SeriesMarker* marker() const;

    StepMode stepMode() const;
    void setStepMode(StepMode mode);

    Q_INVOKABLE void appendData(qreal x, qreal y);
    Q_INVOKABLE void setData(const QList<QPointF>& data);
    void setData(const std::vector<double>& xs, const std::vector<double>& ys);
    using PlotSeries::postData;
    using PlotSeries::setData;
    using PlotSeries::setDataF;
    Q_INVOKABLE void clearData() override;
    std::optional<LegendEntry> legendEntry() const override;

protected:
    Internal::InspectionSource inspectionSource() const override;
    bool inspectionAvailable() const override;
    QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData* updatePaintNodeData) override;
    void itemChange(ItemChange change, const ItemChangeData& value) override;
    void hoverEnterEvent(QHoverEvent* event) override;
    void hoverLeaveEvent(QHoverEvent* event) override;
    bool contains(const QPointF& point) const override;
    void applyDataPoints(DataPointInput&& input) override;
    void onAxisScaleChanged() override;
    void onDataPointsChanged(DataChange change, int previousCount) override;

signals:
    void colorChanged();
    void lineWidthChanged();
    void hoverRadiusChanged();
    void transitionChanged();
    void lineStyleChanged();
    void antialiasingFeatherChanged();
    void stepModeChanged();

private:
    void onTransitionDestroyed();
    void onLineStyleChanged();
    void onLineStyleDestroyed();
    void onNanGapModeChanged();
    void onMarkerShapeChanged();

    static void appendEffect(QQmlListProperty<LineCurveEffect>* list, LineCurveEffect* effect);
    static qsizetype effectCount(QQmlListProperty<LineCurveEffect>* list);
    static LineCurveEffect* effectAt(QQmlListProperty<LineCurveEffect>* list, qsizetype index);
    static void clearEffects(QQmlListProperty<LineCurveEffect>* list);

    bool hasGradientEffect() const;
    GradientColorPayload resolveGradientColorPayload() const;
    GradientFillPayload resolveGradientFillPayload() const;
    std::pair<qreal, qreal> gradientDataRange(Qt::Orientation direction) const;

    void applyNewData(std::vector<float>&& newData, int newPointCount);
    void applyNewData(std::vector<double>&& newData, int newPointCount);
    // Replaces the points with float data copied from \a xyInterleaved, without a transition.
    void applyFloatArray(const float* xyInterleaved, int pointCount);
    const std::vector<float>& renderData() const;
    CurveDataView sourceDataView() const;
    // Number of data points in renderData() / sourceDataView(); smaller than the point count
    // when NanGapMode::Connect has removed invalid data points.
    int renderPointCount() const;
    void refreshVertexCacheForDataChange();
    // Sizes vertexCache_ for the render points, writing only the points it does not hold yet.
    void updateVertexCache();
    void rebuildChunks() const;
    void invalidateVertices();
    void invalidateData();
    void cancelRunningTransition();
    // Shows the target data of a transition that stopped before its end.
    void finishTransition();
    // Starts a transition from the points drawn now to the stored points, the new data.
    void startTransition(std::vector<double>&& from, int fromCount);
    // Effects refresh on the GUI thread once per frame, from QQuickWindow::afterAnimating.
    void connectAnimationTicks(QQuickWindow* window);
    void refreshEffects();
    void advanceTransition();

    QColor color_{ColorPalette::dark().seriesPrimary};
    qreal lineWidth_{1.0};
    qreal hoverRadius_{10.0};
    // While its run is pending, the frames are the store's drawn points; the stored points are its target.
    std::unique_ptr<TransitionRunner> transition_;
    // The previous transition frame, reused for the next one.
    std::vector<double> frameBuffer_;
    QPointer<QQuickWindow> animationTickWindow_;
    QPointer<LineStyle> lineStyle_{new SolidLine{this}};
    qreal antialiasingFeather_{1.0};
    bool styleChanged_{false};
    LineCurveGaps* gaps_{new LineCurveGaps{this}};
    SeriesMarker* marker_{new SeriesMarker{MarkerShape::None, 4.0, SeriesMarker::NoneShape::Accepted, this}};
    StepMode stepMode_{StepMode::None};
    QList<LineCurveEffect*> effects_;
    LineCurveLineRenderer lineRenderer_;
    LineCurvePointRenderer pointRenderer_;

    // True when vertex buffer needs to be rebuilt (style or point-count changed). A change of the
    // render data is told by its revision. Does NOT include axis-range, color, lineWidth, or
    // markerSize changes — those only require a material-uniform update, which happens every frame in paint().
    bool dataChanged_{true};
    // Render revision of the points last uploaded to the GPU.
    std::uint64_t uploadedRevision_{0};
    // Render revision of the points the vertex cache was built from.
    std::uint64_t vertexCacheRevision_{0};

    // Pre-built vertex cache. The render thread consumes it via a single memcpy, which is
    // significantly faster than writing to GPU-mapped memory struct-by-struct.
    LineCurveVertexCache vertexCache_;

    // Data-space bounding boxes per chunk of kChunkSize points, extended to
    // adjacent boundary points to cover bridging segments between chunks.
    // Rebuilt lazily when chunksValid_ is false. Marked mutable for lazy init
    // from the const contains() path.
    mutable std::vector<CurveChunk> chunks_;
    mutable bool chunksValid_{false};
};

} // namespace QAccelPlot