Skip to content

File RectangleSeries.hpp

File List > QAccelPlot > src > QAccelPlot > series > RectangleSeries.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/Border.hpp"
#include "QAccelPlot/series/Category.hpp"
#include "QAccelPlot/series/PlotSeries.hpp"
#include "QAccelPlot/series/SpatialGrid.hpp"
#include "QAccelPlot/theme/ColorPalette.hpp"

#if __has_include(<QtQmlIntegration/qqmlintegration.h>)
#include <QtQmlIntegration/qqmlintegration.h>
#else
#include <QtQml/qqmlregistration.h>
#endif

#include <QList>
#include <QPointF>
#include <QQmlListProperty>
#include <QSizeF>

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

namespace QAccelPlot {

namespace Internal {
class CategoryList;
class HoverIndexBudget;
class RectVertexCache;
}
class RectMaterial;

class RectangleSeries : public PlotSeries {
    Q_OBJECT
    QML_NAMED_ELEMENT(RectangleSeries)


    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
    Q_PROPERTY(QQmlListProperty<Category> categories READ categories NOTIFY categoriesChanged)
    Q_PROPERTY(Border* border READ border CONSTANT)
    Q_PROPERTY(QColor hoverColor READ hoverColor WRITE setHoverColor NOTIFY hoverColorChanged)
    Q_PROPERTY(qreal minimumWidth READ minimumWidth WRITE setMinimumWidth NOTIFY minimumWidthChanged)
    Q_PROPERTY(qreal minimumHeight READ minimumHeight WRITE setMinimumHeight NOTIFY minimumHeightChanged)
    Q_PROPERTY(int hoveredIndex READ hoveredIndex NOTIFY hoveredIndexChanged)

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

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

    QQmlListProperty<Category> categories();
    void setCategories(const QList<Category*>& categories);

    Border* border() const;

    QColor hoverColor() const;
    void setHoverColor(const QColor& color);

    qreal minimumWidth() const;
    void setMinimumWidth(qreal width);

    qreal minimumHeight() const;
    void setMinimumHeight(qreal height);

    int hoveredIndex() const;

    Q_INVOKABLE void setData(const QVariantList& rects);

    // The PlotSeries overloads take interleaved (x1, y1, x2, y2) rectangles, as doubles or floats,
    // and clear the rectangles' categories.
    using PlotSeries::postData;
    using PlotSeries::setData;
    using PlotSeries::setDataF;

    void setData(std::vector<double>&& data, std::vector<int>&& categories, int rectCount);

    void setDataF(std::vector<float>&& data, std::vector<int>&& categories, int rectCount);

    void postData(std::vector<double>&& data, std::vector<int>&& categories, int rectCount);

    void postData(std::vector<float>&& data, std::vector<int>&& categories, int rectCount);

    Q_INVOKABLE void clearData() override;
    std::optional<LegendEntry> legendEntry() const override;
    QList<LegendEntry> legendEntries() const override;

    Q_INVOKABLE void setDataPointCategories(const QVariantList& categories);

    Q_INVOKABLE QVariantMap rectangleAt(int index) const;

    int rectangleIndexAt(const QPointF& position) const;
    bool contains(const QPointF& point) const override;

signals:
    void colorChanged();
    void categoriesChanged();
    void hoverColorChanged();
    void minimumWidthChanged();
    void minimumHeightChanged();
    void hoveredIndexChanged();

protected:
    QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
    bool recordInspectionSupported() const override;
    InspectionRecord inspectionRecord(int index) const override;
    InspectionRecord inspectionRecordAt(const QPointF& position) const override;
    void hoverEnterEvent(QHoverEvent* event) override;
    void hoverMoveEvent(QHoverEvent* event) override;
    void hoverLeaveEvent(QHoverEvent* event) override;
    void onAxisScaleChanged() override;
    void onDataPointsChanged(DataChange change, int previousCount) override;

private:
    void onCategoriesChanged();
    bool validateCategories(std::size_t categoryCount, int rectCount) const;
    void applyData(std::vector<double>&& data, std::vector<int>&& categories, int rectCount);
    void applyFloatData(std::vector<float>&& data, std::vector<int>&& categories, int rectCount);
    // Returns edge \a component (0 = x1, 1 = y1, 2 = x2, 3 = y2) of rectangle \a index.
    double coordinate(int index, int component) const;
    bool hasCategories() const;
    QColor rectangleColor(int index) const;
    // Tests rectangle \a index against item position \a position in pixels, widened like the shader draws it.
    bool containsInPixels(int index, const QPointF& position) const;
    void setHoveredIndex(int index);
    // Sets the uniforms for drawing the first \a drawnCount rectangles.
    void updateMaterial(RectMaterial& material, int drawnCount) const;
    // Returns true when the spatial grid answers the next query, building it once scanning has cost as much.
    bool spatialGridReady() const;

    // Everything a hit test reads besides the rectangles, so equal inputs give an equal answer.
    struct HitTestInputs {
        QPointF position;
        QSizeF itemSize;
        QSizeF minimumSize;
        // The axis mappings, pinned down by each axis' scale and the coordinates at its two ends.
        double xAtLeft;
        double xAtRight;
        double yAtTop;
        double yAtBottom;
        bool logScaleX;
        bool logScaleY;

        bool operator==(const HitTestInputs& other) const;
    };

    HitTestInputs hitTestInputs(const QPointF& position) const;
    int topmostRectangleAt(const QPointF& position) const;
    // Builds the vertices of the first \a rectCount rectangles.
    void buildVertexCache(int rectCount);

    QColor color_;
    std::unique_ptr<Internal::CategoryList> categories_;
    Border* border_{new Border{Qt::black, 0.0, this}};
    QColor hoverColor_;
    qreal minimumWidth_{1.0};
    qreal minimumHeight_{1.0};
    int hoveredIndex_{-1};
    // Render revision of the rectangles last uploaded to the GPU.
    std::uint64_t uploadedRevision_{0};
    // Vertex colors are category colors when categories are set; otherwise \c color is a uniform.
    std::unique_ptr<Internal::RectVertexCache> vertexCache_;
    // Whether the vertex cache holds category colors.
    bool vertexCacheHasCategories_{false};
    // Built once scanning the same data has cost as much as the build, so streaming skips it.
    mutable SpatialGrid spatialGrid_;
    mutable bool spatialGridValid_{false};
    std::unique_ptr<Internal::HoverIndexBudget> spatialGridBudget_;
    // The last hit test and its answer. Qt tests contains() several times per hover event before
    // the handler asks again, each time at the same position.
    mutable std::optional<HitTestInputs> lastHitTest_;
    mutable int lastHitIndex_{-1};
};

} // namespace QAccelPlot