Skip to content

File BarSeries.hpp

File List > QAccelPlot > src > QAccelPlot > series > BarSeries.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/ColormappedSeries.hpp"
#include "QAccelPlot/series/SpatialGrid.hpp"
#include "QAccelPlot/transitions/DataTransition.hpp"

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

#include <QList>
#include <QQmlListProperty>

#include <array>
#include <memory>
#include <vector>

namespace QAccelPlot {

namespace Internal {
class CategoryList;
class RectVertexCache;
}
class BarMaterial;
class TransitionRunner;

class BarSeries : public ColormappedSeries {
    Q_OBJECT
    QML_NAMED_ELEMENT(BarSeries)


    Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged)
    Q_PROPERTY(qreal barWidth READ barWidth WRITE setBarWidth NOTIFY barWidthChanged)
    Q_PROPERTY(qreal barOffset READ barOffset WRITE setBarOffset NOTIFY barOffsetChanged)
    Q_PROPERTY(qreal baselineValue READ baselineValue WRITE setBaselineValue NOTIFY baselineValueChanged)
    Q_PROPERTY(qreal minimumWidth READ minimumWidth WRITE setMinimumWidth NOTIFY minimumWidthChanged)
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
    Q_PROPERTY(QQmlListProperty<Category> categories READ categories NOTIFY categoriesChanged)
    Q_PROPERTY(ColorSource colorSource READ colorSource WRITE setColorSource NOTIFY colorSourceChanged)
    Q_PROPERTY(Border* border READ border CONSTANT)
    Q_PROPERTY(QColor hoverColor READ hoverColor WRITE setHoverColor NOTIFY hoverColorChanged)
    Q_PROPERTY(DataTransition* transition READ transition WRITE setTransition NOTIFY transitionChanged)
    Q_PROPERTY(int hoveredIndex READ hoveredIndex NOTIFY hoveredIndexChanged)

public:
    enum class ColorSource {
        Value,   
        Position 
    };
    Q_ENUM(ColorSource)


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

    Qt::Orientation orientation() const;
    void setOrientation(Qt::Orientation orientation);

    qreal barWidth() const;
    void setBarWidth(qreal width);

    qreal barOffset() const;
    void setBarOffset(qreal offset);

    qreal baselineValue() const;
    void setBaselineValue(qreal baselineValue);

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

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

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

    ColorSource colorSource() const;
    void setColorSource(ColorSource source);

    Border* border() const;

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

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

    int hoveredIndex() const;

    Q_INVOKABLE void setData(const QVariantList& bars);

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

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

    using PlotSeries::postData;
    using PlotSeries::setData;
    using PlotSeries::setDataF;

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

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

    void setRangedData(std::vector<double>&& data, int barCount);
    void setRangedData(std::vector<double>&& data, std::vector<int>&& categories, int barCount);
    void postRangedData(std::vector<double>&& data, int barCount);

    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 barAt(int index) const;

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

signals:
    void orientationChanged();
    void barWidthChanged();
    void barOffsetChanged();
    void baselineValueChanged();
    void minimumWidthChanged();
    void colorChanged();
    void categoriesChanged();
    void colorSourceChanged();
    void hoverColorChanged();
    void transitionChanged();
    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 applyDataPoints(DataPointInput&& input) override;
    int dataPointValueCount() const override;
    void onAxisScaleChanged() override;
    DataRanges computeDataRanges() const override;
    void onDataPointsChanged(DataChange change, int previousCount) override;
    std::optional<ValueSpan> scanValueRange() const override;

private:
    // A set of bars: count bars of stride values each, with one category per bar or none.
    struct Bars {
        std::vector<double> values;
        std::vector<int> categories;
        int count{0};
        int stride{2};
    };

    void onCategoriesChanged();
    bool validateDataArguments(std::size_t valueCount, std::size_t categoryCount, int barCount, int barValueCount = 2) const;
    // Switches between (position, value) and (from, to, value) bars; dropping the bars when it changes.
    void setRanged(bool ranged);
    void applyData(std::vector<double>&& data, std::vector<int>&& categories, int barCount);
    void applyRangedData(std::vector<double>&& data, std::vector<int>&& categories, int barCount);
    // Replaces the bars, animating from the drawn ones when \a animate is set.
    void applyBars(std::vector<double>&& data, bool ranged, std::vector<int>&& categories, int barCount, bool animate);
    void applyFloatData(std::vector<float>&& data, std::vector<int>&& categories, int barCount);
    // Replaces the bars with float data, without a transition.
    void applyFloatBars(std::vector<float>&& data, std::vector<int>&& categories, int barCount);
    void setDataFFromArray(const float* data, int barCount);
    // True while transition bars are drawn instead of the data.
    bool animating() const;
    // Returns the stored bars as doubles.
    std::vector<double> storedBars() const;
    // Returns the bars drawn now: the transition bars, or the data.
    Bars drawnBars() const;
    int drawnBarCount() const;
    const std::vector<int>& drawnCategories() const;
    // Returns one category per transition bar: those of the data, then those of \a previous for the bars
    // that shrink away. Empty when no bar has a category.
    std::vector<int> transitionCategories(const std::vector<int>& previous, int transitionBarCount) const;
    // Animates from \a drawn to the data. A bar without a counterpart grows from, or shrinks to, the baseline.
    void startTransition(Bars&& drawn);
    void advanceTransition();
    // Drops the transition bars, so the data is drawn.
    void endTransition();
    // Invalidates what depends on the bar geometry after a barWidth, barOffset, baselineValue, or orientation change.
    void onGeometryChanged();
    bool isHorizontal() const;
    // Number of values per bar: (position, value), or (from, to, value) for ranged bars.
    int valuesPerBar() const;
    // Returns value \a offset of bar \a index, counted within the bar.
    double component(int index, int offset) const;
    double value(int index) const;
    // Returns the extent of bar \a index along the position axis, both NaN when the bar has none.
    std::array<double, 2> positionSpan(int index) const;
    // Returns bar \a index as data-space edges (x1, y1, x2, y2), all NaN when the bar is not drawn.
    std::array<double, 4> barRect(int index) const;
    bool hasCategories() const;
    QColor barColor(int index) const;
    // Tests bar \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);
    QSGNode* releaseNode(QSGNode* oldNode) const;
    // Sets the uniforms for drawing the first \a renderCount of the drawn bars.
    void updateMaterial(BarMaterial& material, int renderCount) const;
    // Uploads the colormap ramp and sets the uniforms that place each bar on it.
    void updateColormapUniforms(BarMaterial& material, QQuickWindow* window) const;
    // True when the colormap colors the bars instead of color and the category colors.
    bool colormapped() const;
    void ensureSpatialGrid() const;
    // Builds the vertices of the first \a barCount of the drawn bars.
    void buildVertexCache(int barCount);

    Qt::Orientation orientation_{Qt::Vertical};
    qreal barWidth_{0.8};
    qreal barOffset_{0.0};
    qreal baselineValue_{0.0};
    qreal minimumWidth_{1.0};
    QColor color_;
    std::unique_ptr<Internal::CategoryList> categories_;
    ColorSource colorSource_{ColorSource::Value};
    Border* border_{new Border{Qt::black, 0.0, this}};
    QColor hoverColor_;
    int hoveredIndex_{-1};
    std::unique_ptr<TransitionRunner> transition_;
    // Categories of the bars drawn while a transition runs: the new bars, then the removed ones
    // that shrink away. Empty when no bar has one.
    std::vector<int> transitionCategories_;
    // The previous transition frame, reused for the next one.
    std::vector<double> frameBuffer_;
    // Render revision of the bars 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 on the first hit test after a data or geometry change, so streaming without hover skips it.
    mutable std::vector<double> hitRects_;
    mutable SpatialGrid spatialGrid_;
    mutable bool spatialGridValid_{false};
};

} // namespace QAccelPlot