mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
stats: break out common QtQuick part of the code
Move most of the QtQuick code to its own directory, so that it can be reused in the future for the chart. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
533cff96f3
commit
2eebae13dd
34 changed files with 1031 additions and 915 deletions
|
@ -420,6 +420,7 @@ if(MAPSUPPORT)
|
|||
add_subdirectory(map-widget)
|
||||
endif()
|
||||
add_subdirectory(mobile-widgets)
|
||||
add_subdirectory(qt-quick)
|
||||
add_subdirectory(stats)
|
||||
endif()
|
||||
add_subdirectory(backend-shared)
|
||||
|
@ -473,6 +474,7 @@ if (SUBSURFACE_TARGET_EXECUTABLE MATCHES "MobileExecutable")
|
|||
subsurface_commands
|
||||
subsurface_corelib
|
||||
subsurface_stats
|
||||
subsurface_qtquick
|
||||
kirigamiplugin
|
||||
${SUBSURFACE_LINK_LIBRARIES}
|
||||
)
|
||||
|
@ -499,6 +501,7 @@ elseif (SUBSURFACE_TARGET_EXECUTABLE MATCHES "DesktopExecutable")
|
|||
subsurface_commands
|
||||
subsurface_corelib
|
||||
subsurface_stats
|
||||
subsurface_qtquick
|
||||
${SUBSURFACE_LINK_LIBRARIES}
|
||||
)
|
||||
add_dependencies(subsurface_desktop_preferences subsurface_generated_ui)
|
||||
|
|
|
@ -129,6 +129,8 @@ SOURCES += subsurface-mobile-main.cpp \
|
|||
backend-shared/exportfuncs.cpp \
|
||||
backend-shared/plannershared.cpp \
|
||||
backend-shared/roundrectitem.cpp \
|
||||
qt-quick/chartitem.cpp \
|
||||
qt-quick/chartview.cpp \
|
||||
stats/statsvariables.cpp \
|
||||
stats/statsview.cpp \
|
||||
stats/barseries.cpp \
|
||||
|
@ -286,6 +288,10 @@ HEADERS += \
|
|||
backend-shared/exportfuncs.h \
|
||||
backend-shared/plannershared.h \
|
||||
backend-shared/roundrectitem.h \
|
||||
qt-quick/chartitem.h \
|
||||
qt-quick/chartitemhelper.h \
|
||||
qt-quick/chartitem_ptr.h \
|
||||
qt-quick/chartview.h \
|
||||
stats/barseries.h \
|
||||
stats/boxseries.h \
|
||||
stats/chartitem.h \
|
||||
|
|
19
qt-quick/CMakeLists.txt
Normal file
19
qt-quick/CMakeLists.txt
Normal file
|
@ -0,0 +1,19 @@
|
|||
# code for qt-quick based charts
|
||||
include_directories(.
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_BINARY_DIR}
|
||||
)
|
||||
|
||||
set(SUBSURFACE_QTQUICK_SRCS
|
||||
chartitem.cpp
|
||||
chartitem.h
|
||||
chartitemhelper.h
|
||||
chartitem_ptr.h
|
||||
chartview.cpp
|
||||
chartview.h
|
||||
)
|
||||
|
||||
source_group("Subsurface qtquick sourcecode" FILES ${SUBSURFACE_QTQUICK_SRCS})
|
||||
|
||||
add_library(subsurface_qtquick STATIC ${SUBSURFACE_QTQUICK_SRCS})
|
||||
target_link_libraries(subsurface_qtquick ${QT_LIBRARIES})
|
246
qt-quick/chartitem.cpp
Normal file
246
qt-quick/chartitem.cpp
Normal file
|
@ -0,0 +1,246 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "chartitem.h"
|
||||
#include "chartitemhelper.h"
|
||||
#include "chartview.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <QQuickWindow>
|
||||
#include <QSGFlatColorMaterial>
|
||||
#include <QSGImageNode>
|
||||
|
||||
ChartItem::ChartItem(ChartView &v, size_t z) :
|
||||
dirty(false), prev(nullptr), next(nullptr),
|
||||
zValue(z), view(v)
|
||||
{
|
||||
// Register before the derived constructors run, so that the
|
||||
// derived classes can mark the item as dirty in the constructor.
|
||||
v.registerChartItem(*this);
|
||||
}
|
||||
|
||||
ChartItem::~ChartItem()
|
||||
{
|
||||
}
|
||||
|
||||
QSizeF ChartItem::sceneSize() const
|
||||
{
|
||||
return view.size();
|
||||
}
|
||||
|
||||
void ChartItem::markDirty()
|
||||
{
|
||||
view.registerDirtyChartItem(*this);
|
||||
}
|
||||
|
||||
static int round_up(double f)
|
||||
{
|
||||
return static_cast<int>(ceil(f));
|
||||
}
|
||||
|
||||
ChartPixmapItem::ChartPixmapItem(ChartView &v, size_t z) : HideableChartItem(v, z),
|
||||
positionDirty(false), textureDirty(false)
|
||||
{
|
||||
}
|
||||
|
||||
ChartPixmapItem::~ChartPixmapItem()
|
||||
{
|
||||
painter.reset(); // Make sure to destroy painter before image that is painted on
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setTextureDirty()
|
||||
{
|
||||
textureDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setPositionDirty()
|
||||
{
|
||||
positionDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::render()
|
||||
{
|
||||
if (!node) {
|
||||
createNode(view.w()->createImageNode());
|
||||
view.addQSGNode(node.get(), zValue);
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (!img) {
|
||||
resize(QSizeF(1,1));
|
||||
img->fill(Qt::transparent);
|
||||
}
|
||||
if (textureDirty) {
|
||||
texture.reset(view.w()->createTextureFromImage(*img, QQuickWindow::TextureHasAlphaChannel));
|
||||
node->node->setTexture(texture.get());
|
||||
textureDirty = false;
|
||||
}
|
||||
if (positionDirty) {
|
||||
node->node->setRect(rect);
|
||||
positionDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ChartPixmapItem::resize(QSizeF size)
|
||||
{
|
||||
painter.reset();
|
||||
img.reset(new QImage(round_up(size.width()), round_up(size.height()), QImage::Format_ARGB32));
|
||||
painter.reset(new QPainter(img.get()));
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
rect.setSize(size);
|
||||
setTextureDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setPos(QPointF pos)
|
||||
{
|
||||
rect.moveTopLeft(pos);
|
||||
setPositionDirty();
|
||||
}
|
||||
|
||||
QRectF ChartPixmapItem::getRect() const
|
||||
{
|
||||
return rect;
|
||||
}
|
||||
|
||||
ChartRectItem::ChartRectItem(ChartView &v, size_t z,
|
||||
const QPen &pen, const QBrush &brush, double radius) : ChartPixmapItem(v, z),
|
||||
pen(pen), brush(brush), radius(radius)
|
||||
{
|
||||
}
|
||||
|
||||
ChartRectItem::~ChartRectItem()
|
||||
{
|
||||
}
|
||||
|
||||
void ChartRectItem::resize(QSizeF size)
|
||||
{
|
||||
ChartPixmapItem::resize(size);
|
||||
img->fill(Qt::transparent);
|
||||
painter->setPen(pen);
|
||||
painter->setBrush(brush);
|
||||
QSize imgSize = img->size();
|
||||
int width = pen.width();
|
||||
QRect rect(width / 2, width / 2, imgSize.width() - width, imgSize.height() - width);
|
||||
painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize);
|
||||
}
|
||||
|
||||
ChartTextItem::ChartTextItem(ChartView &v, size_t z, const QFont &f, const std::vector<QString> &text, bool center) :
|
||||
ChartPixmapItem(v, z), f(f), center(center)
|
||||
{
|
||||
QFontMetrics fm(f);
|
||||
double totalWidth = 1.0;
|
||||
fontHeight = static_cast<double>(fm.height());
|
||||
double totalHeight = std::max(1.0, static_cast<double>(text.size()) * fontHeight);
|
||||
|
||||
items.reserve(text.size());
|
||||
for (const QString &s: text) {
|
||||
double w = fm.size(Qt::TextSingleLine, s).width();
|
||||
items.push_back({ s, w });
|
||||
if (w > totalWidth)
|
||||
totalWidth = w;
|
||||
}
|
||||
resize(QSizeF(totalWidth, totalHeight));
|
||||
}
|
||||
|
||||
ChartTextItem::ChartTextItem(ChartView &v, size_t z, const QFont &f, const QString &text) :
|
||||
ChartTextItem(v, z, f, std::vector<QString>({ text }), true)
|
||||
{
|
||||
}
|
||||
|
||||
void ChartTextItem::setColor(const QColor &c)
|
||||
{
|
||||
setColor(c, Qt::transparent);
|
||||
}
|
||||
|
||||
void ChartTextItem::setColor(const QColor &c, const QColor &background)
|
||||
{
|
||||
img->fill(background);
|
||||
double y = 0.0;
|
||||
painter->setPen(QPen(c));
|
||||
painter->setFont(f);
|
||||
double totalWidth = getRect().width();
|
||||
for (const auto &[s, w]: items) {
|
||||
double x = center ? round((totalWidth - w) / 2.0) : 0.0;
|
||||
QRectF rect(x, y, w, fontHeight);
|
||||
painter->drawText(rect, s);
|
||||
y += fontHeight;
|
||||
}
|
||||
setTextureDirty();
|
||||
}
|
||||
|
||||
ChartLineItemBase::~ChartLineItemBase()
|
||||
{
|
||||
}
|
||||
|
||||
void ChartLineItemBase::setLine(QPointF fromIn, QPointF toIn)
|
||||
{
|
||||
from = fromIn;
|
||||
to = toIn;
|
||||
positionDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void ChartLineItem::render()
|
||||
{
|
||||
if (!node) {
|
||||
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2));
|
||||
geometry->setDrawingMode(QSGGeometry::DrawLines);
|
||||
material.reset(new QSGFlatColorMaterial);
|
||||
createNode();
|
||||
node->setGeometry(geometry.get());
|
||||
node->setMaterial(material.get());
|
||||
view.addQSGNode(node.get(), zValue);
|
||||
positionDirty = materialDirty = true;
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (positionDirty) {
|
||||
// Attention: width is a geometry property and therefore handled by position dirty!
|
||||
geometry->setLineWidth(static_cast<float>(width));
|
||||
auto vertices = geometry->vertexDataAsPoint2D();
|
||||
setPoint(vertices[0], from);
|
||||
setPoint(vertices[1], to);
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
|
||||
if (materialDirty) {
|
||||
material->setColor(color);
|
||||
node->markDirty(QSGNode::DirtyMaterial);
|
||||
}
|
||||
|
||||
positionDirty = materialDirty = false;
|
||||
}
|
||||
|
||||
void ChartRectLineItem::render()
|
||||
{
|
||||
if (!node) {
|
||||
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 5));
|
||||
geometry->setDrawingMode(QSGGeometry::DrawLineStrip);
|
||||
material.reset(new QSGFlatColorMaterial);
|
||||
createNode();
|
||||
node->setGeometry(geometry.get());
|
||||
node->setMaterial(material.get());
|
||||
view.addQSGNode(node.get(), zValue);
|
||||
positionDirty = materialDirty = true;
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (positionDirty) {
|
||||
// Attention: width is a geometry property and therefore handled by position dirty!
|
||||
geometry->setLineWidth(static_cast<float>(width));
|
||||
auto vertices = geometry->vertexDataAsPoint2D();
|
||||
setPoint(vertices[0], from);
|
||||
setPoint(vertices[1], QPointF(from.x(), to.y()));
|
||||
setPoint(vertices[2], to);
|
||||
setPoint(vertices[3], QPointF(to.x(), from.y()));
|
||||
setPoint(vertices[4], from);
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
|
||||
if (materialDirty) {
|
||||
material->setColor(color);
|
||||
node->markDirty(QSGNode::DirtyMaterial);
|
||||
}
|
||||
|
||||
positionDirty = materialDirty = false;
|
||||
}
|
169
qt-quick/chartitem.h
Normal file
169
qt-quick/chartitem.h
Normal file
|
@ -0,0 +1,169 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Wrappers around QSGImageNode that allow painting onto an image
|
||||
// and then turning that into a texture to be displayed in a QQuickItem.
|
||||
#ifndef CHART_ITEM_H
|
||||
#define CHART_ITEM_H
|
||||
|
||||
#include "chartitem_ptr.h"
|
||||
#include "chartitemhelper.h"
|
||||
|
||||
#include <memory>
|
||||
#include <QPainter>
|
||||
|
||||
class ChartView;
|
||||
class QSGGeometry;
|
||||
class QSGGeometryNode;
|
||||
class QSGFlatColorMaterial;
|
||||
class QSGImageNode;
|
||||
class QSGRectangleNode;
|
||||
class QSGTexture;
|
||||
class QSGTextureMaterial;
|
||||
|
||||
class ChartItem {
|
||||
public:
|
||||
// Only call on render thread!
|
||||
virtual void render() = 0;
|
||||
bool dirty; // If true, call render() when rebuilding the scene
|
||||
ChartItem *prev, *next; // Double linked list of items
|
||||
const size_t zValue;
|
||||
virtual ~ChartItem(); // Attention: must only be called by render thread.
|
||||
protected:
|
||||
ChartItem(ChartView &v, size_t z);
|
||||
QSizeF sceneSize() const;
|
||||
ChartView &view;
|
||||
void markDirty();
|
||||
};
|
||||
|
||||
template <typename Node>
|
||||
class HideableChartItem : public ChartItem {
|
||||
protected:
|
||||
HideableChartItem(ChartView &v, size_t z);
|
||||
std::unique_ptr<Node> node;
|
||||
bool visible;
|
||||
bool visibleChanged;
|
||||
template<class... Args>
|
||||
void createNode(Args&&... args); // Call to create node with visibility flag.
|
||||
void updateVisible(); // Must be called by child class to update visibility flag!
|
||||
public:
|
||||
void setVisible(bool visible);
|
||||
};
|
||||
|
||||
// A shortcut for ChartItems based on a hideable proxy item
|
||||
template <typename Node>
|
||||
using HideableChartProxyItem = HideableChartItem<HideableQSGNode<QSGProxyNode<Node>>>;
|
||||
|
||||
// A chart item that blits a precalculated pixmap onto the scene.
|
||||
class ChartPixmapItem : public HideableChartProxyItem<QSGImageNode> {
|
||||
public:
|
||||
ChartPixmapItem(ChartView &v, size_t z);
|
||||
~ChartPixmapItem();
|
||||
|
||||
void setPos(QPointF pos);
|
||||
void render() override;
|
||||
QRectF getRect() const;
|
||||
protected:
|
||||
void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*.
|
||||
std::unique_ptr<QPainter> painter;
|
||||
std::unique_ptr<QImage> img;
|
||||
void setTextureDirty();
|
||||
void setPositionDirty();
|
||||
QRectF rect;
|
||||
private:
|
||||
bool positionDirty; // true if the position changed since last render
|
||||
bool textureDirty; // true if the pixmap changed since last render
|
||||
std::unique_ptr<QSGTexture> texture;
|
||||
};
|
||||
|
||||
// Draw a rectangular background after resize. Children are responsible for calling update().
|
||||
class ChartRectItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartRectItem(ChartView &v, size_t z, const QPen &pen, const QBrush &brush, double radius);
|
||||
~ChartRectItem();
|
||||
void resize(QSizeF size);
|
||||
private:
|
||||
QPen pen;
|
||||
QBrush brush;
|
||||
double radius;
|
||||
};
|
||||
|
||||
// Attention: text is only drawn after calling setColor()!
|
||||
class ChartTextItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartTextItem(ChartView &v, size_t z, const QFont &f, const std::vector<QString> &text, bool center);
|
||||
ChartTextItem(ChartView &v, size_t z, const QFont &f, const QString &text);
|
||||
void setColor(const QColor &color); // Draw on transparent background
|
||||
void setColor(const QColor &color, const QColor &background); // Fill rectangle with given background color
|
||||
private:
|
||||
const QFont &f;
|
||||
double fontHeight;
|
||||
bool center;
|
||||
struct Item {
|
||||
QString s;
|
||||
double width;
|
||||
};
|
||||
std::vector<Item> items;
|
||||
};
|
||||
|
||||
// Common data for line and rect items. Both are represented by two points.
|
||||
class ChartLineItemBase : public HideableChartItem<HideableQSGNode<QSGGeometryNode>> {
|
||||
public:
|
||||
ChartLineItemBase(ChartView &v, size_t z, QColor color, double width);
|
||||
~ChartLineItemBase();
|
||||
void setLine(QPointF from, QPointF to);
|
||||
protected:
|
||||
QPointF from, to;
|
||||
QColor color;
|
||||
double width;
|
||||
bool positionDirty;
|
||||
bool materialDirty;
|
||||
std::unique_ptr<QSGFlatColorMaterial> material;
|
||||
std::unique_ptr<QSGGeometry> geometry;
|
||||
};
|
||||
|
||||
class ChartLineItem : public ChartLineItemBase {
|
||||
public:
|
||||
using ChartLineItemBase::ChartLineItemBase;
|
||||
void render() override;
|
||||
};
|
||||
|
||||
// A simple rectangle without fill. Specified by any two opposing vertices.
|
||||
class ChartRectLineItem : public ChartLineItemBase {
|
||||
public:
|
||||
using ChartLineItemBase::ChartLineItemBase;
|
||||
void render() override;
|
||||
};
|
||||
|
||||
// Implementation detail of templates - move to serparate header file
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::setVisible(bool visibleIn)
|
||||
{
|
||||
if (visible == visibleIn)
|
||||
return;
|
||||
visible = visibleIn;
|
||||
visibleChanged = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
template<class... Args>
|
||||
void HideableChartItem<Node>::createNode(Args&&... args)
|
||||
{
|
||||
node.reset(new Node(visible, std::forward<Args>(args)...));
|
||||
visibleChanged = false;
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
HideableChartItem<Node>::HideableChartItem(ChartView &v, size_t z) : ChartItem(v, z),
|
||||
visible(true), visibleChanged(false)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::updateVisible()
|
||||
{
|
||||
if (visibleChanged)
|
||||
node->setVisible(visible);
|
||||
visibleChanged = false;
|
||||
}
|
||||
|
||||
#endif
|
51
qt-quick/chartitem_ptr.h
Normal file
51
qt-quick/chartitem_ptr.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// A stupid pointer class that initializes to null and can be copy
|
||||
// assigned. This is for historical reasons: unique_ptrs to ChartItems
|
||||
// were replaced by plain pointers. Instead of nulling the plain pointers
|
||||
// in the constructors, use this. Ultimately, we might think about making
|
||||
// this thing smarter, once removal of individual ChartItems is implemented.
|
||||
#ifndef CHARITEM_PTR_H
|
||||
#define CHARITEM_PTR_H
|
||||
|
||||
template <typename T>
|
||||
class ChartItemPtr {
|
||||
friend class ChartView; // Only the chart view can create these pointers
|
||||
T *ptr;
|
||||
ChartItemPtr(T *ptr) : ptr(ptr)
|
||||
{
|
||||
}
|
||||
public:
|
||||
ChartItemPtr() : ptr(nullptr)
|
||||
{
|
||||
}
|
||||
ChartItemPtr(const ChartItemPtr &p) : ptr(p.ptr)
|
||||
{
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
ptr = nullptr;
|
||||
}
|
||||
ChartItemPtr &operator=(const ChartItemPtr &p)
|
||||
{
|
||||
ptr = p.ptr;
|
||||
return *this;
|
||||
}
|
||||
operator bool() const
|
||||
{
|
||||
return !!ptr;
|
||||
}
|
||||
bool operator!() const
|
||||
{
|
||||
return !ptr;
|
||||
}
|
||||
T &operator*() const
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
T *operator->() const
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
100
qt-quick/chartitemhelper.h
Normal file
100
qt-quick/chartitemhelper.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// QSGNode template jugglery to overcome API flaws.
|
||||
|
||||
#ifndef CHARTITEM_HELPER_H
|
||||
#define CHARTITEM_HELPER_H
|
||||
|
||||
#include <memory>
|
||||
#include <QSGNode>
|
||||
|
||||
// In general, we want chart items to be hideable. For example to show/hide
|
||||
// labels on demand. Very sadly, the QSG API is absolutely terrible with
|
||||
// respect to temporarily disabling. Instead of simply having a flag,
|
||||
// a QSGNode is queried using the "isSubtreeBlocked()" virtual function(!).
|
||||
//
|
||||
// Not only is this a slow operation performed on every single node, it
|
||||
// also is often not possible to override this function: For improved
|
||||
// performance, the documentation recommends to create QSG nodes via
|
||||
// QQuickWindow. This provides nodes optimized for the actual hardware.
|
||||
// However, this obviously means that these nodes cannot be derived from!
|
||||
//
|
||||
// In that case, there are two possibilities: Add a proxy node with an
|
||||
// overridden "isSubtreeBlocked()" function or remove the node from the
|
||||
// scene. The former was chosen here, because it is less complex.
|
||||
//
|
||||
// The following slightly cryptic templates are used to unify the two
|
||||
// cases: The QSGNode is generated by our own code or the QSGNode is
|
||||
// obtained from QQuickWindow.
|
||||
//
|
||||
// The "HideableQSGNode<Node>" template augments the QSGNode "Node"
|
||||
// by a "setVisible()" function and overrides "isSubtreeBlocked()"
|
||||
//
|
||||
// The "QSGProxyNode<Node>" template is a QSGNode with a single
|
||||
// child of type "Node".
|
||||
//
|
||||
// Thus, if the node can be created, use:
|
||||
// HideableQSGNode<NodeTypeThatCanBeCreated> node
|
||||
// and if the node can only be obtained from QQuickWindow, use:
|
||||
// HideableQSGNode<QSGProxyNode<NodeThatCantBeCreated>> node
|
||||
// The latter should obviously be typedef-ed.
|
||||
//
|
||||
// Yes, that's all horrible, but if nothing else it teaches us about
|
||||
// composition.
|
||||
template <typename Node>
|
||||
class HideableQSGNode : public Node {
|
||||
bool hidden;
|
||||
bool isSubtreeBlocked() const override final;
|
||||
public:
|
||||
template<class... Args>
|
||||
HideableQSGNode(bool visible, Args&&... args);
|
||||
void setVisible(bool visible);
|
||||
};
|
||||
|
||||
template <typename Node>
|
||||
class QSGProxyNode : public QSGNode {
|
||||
public:
|
||||
std::unique_ptr<Node> node;
|
||||
QSGProxyNode(Node *node);
|
||||
};
|
||||
|
||||
// Implementation detail of templates - move to serparate header file
|
||||
template <typename Node>
|
||||
QSGProxyNode<Node>::QSGProxyNode(Node *node) : node(node)
|
||||
{
|
||||
appendChildNode(node);
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
bool HideableQSGNode<Node>::isSubtreeBlocked() const
|
||||
{
|
||||
return hidden;
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
template<class... Args>
|
||||
HideableQSGNode<Node>::HideableQSGNode(bool visible, Args&&... args) :
|
||||
Node(std::forward<Args>(args)...),
|
||||
hidden(!visible)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableQSGNode<Node>::setVisible(bool visible)
|
||||
{
|
||||
hidden = !visible;
|
||||
Node::markDirty(QSGNode::DirtySubtreeBlocked);
|
||||
}
|
||||
|
||||
// Helper function to set points
|
||||
inline void setPoint(QSGGeometry::Point2D &v, const QPointF &p)
|
||||
{
|
||||
v.set(static_cast<float>(p.x()), static_cast<float>(p.y()));
|
||||
}
|
||||
|
||||
inline void setPoint(QSGGeometry::TexturedPoint2D &v, const QPointF &p, const QPointF &t)
|
||||
{
|
||||
v.set(static_cast<float>(p.x()), static_cast<float>(p.y()),
|
||||
static_cast<float>(t.x()), static_cast<float>(t.y()));
|
||||
}
|
||||
|
||||
#endif
|
243
qt-quick/chartview.cpp
Normal file
243
qt-quick/chartview.cpp
Normal file
|
@ -0,0 +1,243 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "chartview.h"
|
||||
#include "chartitem.h"
|
||||
|
||||
#include <QQuickWindow>
|
||||
#include <QSGRectangleNode>
|
||||
|
||||
ChartView::ChartView(QQuickItem *parent, size_t maxZ) : QQuickItem(parent),
|
||||
maxZ(maxZ),
|
||||
backgroundDirty(true),
|
||||
rootNode(nullptr)
|
||||
{
|
||||
setFlag(ItemHasContents, true);
|
||||
}
|
||||
|
||||
ChartView::~ChartView()
|
||||
{
|
||||
}
|
||||
|
||||
// Define a hideable dummy QSG node that is used as a parent node to make
|
||||
// all objects of a z-level visible / invisible.
|
||||
using ZNode = HideableQSGNode<QSGNode>;
|
||||
|
||||
class RootNode : public QSGNode
|
||||
{
|
||||
public:
|
||||
RootNode(ChartView &view, QColor backgroundColor, size_t maxZ);
|
||||
~RootNode();
|
||||
ChartView &view;
|
||||
std::unique_ptr<QSGRectangleNode> backgroundNode; // solid background
|
||||
// We entertain one node per Z-level.
|
||||
std::vector<std::unique_ptr<ZNode>> zNodes;
|
||||
};
|
||||
|
||||
RootNode::RootNode(ChartView &view, QColor backgroundColor, size_t maxZ) : view(view)
|
||||
{
|
||||
zNodes.resize(maxZ);
|
||||
|
||||
// Add a background rectangle with a solid color. This could
|
||||
// also be done on the widget level, but would have to be done
|
||||
// separately for desktop and mobile, so do it here.
|
||||
backgroundNode.reset(view.w()->createRectangleNode());
|
||||
appendChildNode(backgroundNode.get());
|
||||
|
||||
for (auto &zNode: zNodes) {
|
||||
zNode.reset(new ZNode(true));
|
||||
appendChildNode(zNode.get());
|
||||
}
|
||||
}
|
||||
|
||||
RootNode::~RootNode()
|
||||
{
|
||||
view.emergencyShutdown();
|
||||
}
|
||||
|
||||
void ChartView::freeDeletedChartItems()
|
||||
{
|
||||
ChartItem *nextitem;
|
||||
for (ChartItem *item = deletedItems.first; item; item = nextitem) {
|
||||
nextitem = item->next;
|
||||
delete item;
|
||||
}
|
||||
deletedItems.clear();
|
||||
}
|
||||
|
||||
QSGNode *ChartView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
|
||||
{
|
||||
// The QtQuick drawing interface is utterly bizzare with a distinct 1980ies-style memory management.
|
||||
// This is just a copy of what is found in Qt's documentation.
|
||||
RootNode *n = static_cast<RootNode *>(oldNode);
|
||||
if (!n)
|
||||
n = rootNode = new RootNode(*this, backgroundColor, maxZ);
|
||||
|
||||
// Delete all chart items that are marked for deletion.
|
||||
freeDeletedChartItems();
|
||||
|
||||
if (backgroundDirty) {
|
||||
rootNode->backgroundNode->setRect(plotRect);
|
||||
backgroundDirty = false;
|
||||
}
|
||||
|
||||
for (ChartItem *item = dirtyItems.first; item; item = item->next) {
|
||||
item->render();
|
||||
item->dirty = false;
|
||||
}
|
||||
dirtyItems.splice(cleanItems);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// When reparenting the QQuickWidget, QtQuick decides to delete our rootNode
|
||||
// and with it all the QSG nodes, even though we have *not* given the
|
||||
// permission to do so! If the widget is reused, we try to delete the
|
||||
// stale items, whose nodes have already been deleted by QtQuick, leading
|
||||
// to a double-free(). Instead of searching for the cause of this behavior,
|
||||
// let's just hook into the rootNodes destructor and delete the objects
|
||||
// in a controlled manner, so that QtQuick has no more access to them.
|
||||
void ChartView::emergencyShutdown()
|
||||
{
|
||||
// Mark clean and dirty chart items for deletion...
|
||||
cleanItems.splice(deletedItems);
|
||||
dirtyItems.splice(deletedItems);
|
||||
|
||||
// ...and delete them.
|
||||
freeDeletedChartItems();
|
||||
|
||||
// Now delete all the pointers we might have to chart features,
|
||||
// axes, etc. Note that all pointers to chart items are non
|
||||
// owning, so this only resets stale references, but does not
|
||||
// lead to any additional deletion of chart items.
|
||||
resetPointers();
|
||||
|
||||
// The rootNode is being deleted -> remove the reference to that
|
||||
rootNode = nullptr;
|
||||
}
|
||||
|
||||
void ChartView::clearItems()
|
||||
{
|
||||
cleanItems.splice(deletedItems);
|
||||
dirtyItems.splice(deletedItems);
|
||||
}
|
||||
|
||||
void ChartView::addQSGNode(QSGNode *node, size_t z)
|
||||
{
|
||||
size_t idx = std::clamp(z, (size_t)0, maxZ);
|
||||
rootNode->zNodes[idx]->appendChildNode(node);
|
||||
}
|
||||
|
||||
void ChartView::registerChartItem(ChartItem &item)
|
||||
{
|
||||
cleanItems.append(item);
|
||||
}
|
||||
|
||||
void ChartView::registerDirtyChartItem(ChartItem &item)
|
||||
{
|
||||
if (item.dirty)
|
||||
return;
|
||||
cleanItems.remove(item);
|
||||
dirtyItems.append(item);
|
||||
item.dirty = true;
|
||||
}
|
||||
|
||||
void ChartView::deleteChartItemInternal(ChartItem &item)
|
||||
{
|
||||
if (item.dirty)
|
||||
dirtyItems.remove(item);
|
||||
else
|
||||
cleanItems.remove(item);
|
||||
deletedItems.append(item);
|
||||
}
|
||||
|
||||
ChartView::ChartItemList::ChartItemList() : first(nullptr), last(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void ChartView::ChartItemList::clear()
|
||||
{
|
||||
first = last = nullptr;
|
||||
}
|
||||
|
||||
void ChartView::ChartItemList::remove(ChartItem &item)
|
||||
{
|
||||
if (item.next)
|
||||
item.next->prev = item.prev;
|
||||
else
|
||||
last = item.prev;
|
||||
if (item.prev)
|
||||
item.prev->next = item.next;
|
||||
else
|
||||
first = item.next;
|
||||
item.prev = item.next = nullptr;
|
||||
}
|
||||
|
||||
void ChartView::ChartItemList::append(ChartItem &item)
|
||||
{
|
||||
if (!first) {
|
||||
first = &item;
|
||||
} else {
|
||||
item.prev = last;
|
||||
last->next = &item;
|
||||
}
|
||||
last = &item;
|
||||
}
|
||||
|
||||
void ChartView::ChartItemList::splice(ChartItemList &l2)
|
||||
{
|
||||
if (!first) // if list is empty -> nothing to do.
|
||||
return;
|
||||
if (!l2.first) {
|
||||
l2 = *this;
|
||||
} else {
|
||||
l2.last->next = first;
|
||||
first->prev = l2.last;
|
||||
l2.last = last;
|
||||
}
|
||||
clear();
|
||||
}
|
||||
|
||||
QQuickWindow *ChartView::w() const
|
||||
{
|
||||
return window();
|
||||
}
|
||||
|
||||
void ChartView::setBackgroundColor(QColor color)
|
||||
{
|
||||
backgroundColor = color;
|
||||
if (rootNode)
|
||||
rootNode->backgroundNode->setColor(color);
|
||||
}
|
||||
|
||||
QSizeF ChartView::size() const
|
||||
{
|
||||
return boundingRect().size();
|
||||
}
|
||||
|
||||
QRectF ChartView::plotArea() const
|
||||
{
|
||||
return plotRect;
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void ChartView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||
#else
|
||||
void ChartView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||
#endif
|
||||
{
|
||||
plotRect = QRectF(QPointF(0.0, 0.0), newGeometry.size());
|
||||
backgroundDirty = true;
|
||||
plotAreaChanged(plotRect.size());
|
||||
|
||||
// Do we need to call the base-class' version of geometryChanged? Probably for QML?
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||
#else
|
||||
QQuickItem::geometryChanged(newGeometry, oldGeometry);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ChartView::setLayerVisibility(size_t z, bool visible)
|
||||
{
|
||||
if (rootNode && z < rootNode->zNodes.size())
|
||||
rootNode->zNodes[z]->setVisible(visible);
|
||||
}
|
97
qt-quick/chartview.h
Normal file
97
qt-quick/chartview.h
Normal file
|
@ -0,0 +1,97 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#ifndef CHART_VIEW_H
|
||||
#define CHART_VIEW_H
|
||||
|
||||
#include "chartitem_ptr.h"
|
||||
|
||||
#include <QQuickItem>
|
||||
|
||||
class ChartItem;
|
||||
class QSGTexture;
|
||||
class RootNode; // Internal implementation detail
|
||||
|
||||
class ChartView : public QQuickItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ChartView(QQuickItem *parent, size_t maxZ);
|
||||
~ChartView();
|
||||
|
||||
QQuickWindow *w() const; // Make window available to items
|
||||
QSizeF size() const;
|
||||
QRectF plotArea() const;
|
||||
void setBackgroundColor(QColor color); // Chart must be replot for color to become effective.
|
||||
void addQSGNode(QSGNode *node, size_t z); // Must only be called in render thread!
|
||||
void registerChartItem(ChartItem &item);
|
||||
void registerDirtyChartItem(ChartItem &item);
|
||||
void emergencyShutdown(); // Called when QQuick decides to delete our root node.
|
||||
|
||||
// Create a chart item and add it to the scene.
|
||||
// The item must not be deleted by the caller, but can be
|
||||
// scheduled for deletion using deleteChartItem() below.
|
||||
// Most items can be made invisible, which is preferred over deletion.
|
||||
// All items on the scene will be deleted once the chart is reset.
|
||||
template <typename T, class... Args>
|
||||
ChartItemPtr<T> createChartItem(Args&&... args);
|
||||
|
||||
template <typename T>
|
||||
void deleteChartItem(ChartItemPtr<T> &item);
|
||||
|
||||
protected:
|
||||
void setLayerVisibility(size_t z, bool visible);
|
||||
void clearItems();
|
||||
|
||||
// This is called when Qt decided to reset our rootNode, which invalidates all items on the chart.
|
||||
// The base class must invalidate all pointers and references.
|
||||
virtual void resetPointers() = 0;
|
||||
private:
|
||||
// QtQuick related things
|
||||
size_t maxZ;
|
||||
bool backgroundDirty;
|
||||
QRectF plotRect;
|
||||
QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override;
|
||||
QColor backgroundColor;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
#else
|
||||
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
#endif
|
||||
virtual void plotAreaChanged(const QSizeF &size) = 0;
|
||||
|
||||
RootNode *rootNode;
|
||||
|
||||
// There are three double linked lists of chart items:
|
||||
// clean items, dirty items and items to be deleted.
|
||||
// Note that only the render thread must delete chart items,
|
||||
// and therefore these lists are the only owning pointers
|
||||
// to chart items. All other pointers are non-owning and
|
||||
// can therefore become stale.
|
||||
struct ChartItemList {
|
||||
ChartItemList();
|
||||
ChartItem *first, *last;
|
||||
void append(ChartItem &item);
|
||||
void remove(ChartItem &item);
|
||||
void clear();
|
||||
void splice(ChartItemList &list);
|
||||
};
|
||||
ChartItemList cleanItems, dirtyItems, deletedItems;
|
||||
void deleteChartItemInternal(ChartItem &item);
|
||||
void freeDeletedChartItems();
|
||||
};
|
||||
|
||||
// This implementation detail must be known to users of the class.
|
||||
// Perhaps move it into a statsview_impl.h file.
|
||||
template <typename T, class... Args>
|
||||
ChartItemPtr<T> ChartView::createChartItem(Args&&... args)
|
||||
{
|
||||
return ChartItemPtr(new T(*this, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ChartView::deleteChartItem(ChartItemPtr<T> &item)
|
||||
{
|
||||
deleteChartItemInternal(*item);
|
||||
item.reset();
|
||||
}
|
||||
|
||||
#endif
|
|
@ -412,7 +412,7 @@ bool BarSeries::hover(QPointF pos)
|
|||
Item &item = items[highlighted.bar];
|
||||
item.highlight(index.subitem, true, binCount(), theme);
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information = view.createChartItem<InformationBox>(theme);
|
||||
information->setText(makeInfo(item, highlighted.subitem), pos);
|
||||
information->setVisible(true);
|
||||
} else {
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
#ifndef BAR_SERIES_H
|
||||
#define BAR_SERIES_H
|
||||
|
||||
#include "statshelper.h"
|
||||
#include "statsseries.h"
|
||||
#include "statsvariables.h"
|
||||
#include "qt-quick/chartitem_ptr.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
|
|
@ -130,7 +130,7 @@ bool BoxSeries::hover(QPointF pos)
|
|||
Item &item = *items[highlighted];
|
||||
item.highlight(true, theme);
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information = view.createChartItem<InformationBox>(theme);
|
||||
information->setText(formatInformation(item), pos);
|
||||
information->setVisible(true);
|
||||
} else {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "statscolors.h"
|
||||
#include "statsview.h"
|
||||
#include "core/globals.h"
|
||||
#include "qt-quick/chartitemhelper.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <QQuickWindow>
|
||||
|
@ -14,104 +15,10 @@
|
|||
|
||||
static int selectionOverlayPixelSize = 2;
|
||||
|
||||
static int round_up(double f)
|
||||
{
|
||||
return static_cast<int>(ceil(f));
|
||||
}
|
||||
|
||||
ChartItem::ChartItem(StatsView &v, size_t z) :
|
||||
dirty(false), prev(nullptr), next(nullptr),
|
||||
zValue(z), view(v)
|
||||
{
|
||||
// Register before the derived constructors run, so that the
|
||||
// derived classes can mark the item as dirty in the constructor.
|
||||
v.registerChartItem(*this);
|
||||
}
|
||||
|
||||
ChartItem::~ChartItem()
|
||||
{
|
||||
}
|
||||
|
||||
QSizeF ChartItem::sceneSize() const
|
||||
{
|
||||
return view.size();
|
||||
}
|
||||
|
||||
void ChartItem::markDirty()
|
||||
{
|
||||
view.registerDirtyChartItem(*this);
|
||||
}
|
||||
|
||||
ChartPixmapItem::ChartPixmapItem(StatsView &v, size_t z) : HideableChartItem(v, z),
|
||||
positionDirty(false), textureDirty(false)
|
||||
{
|
||||
}
|
||||
|
||||
ChartPixmapItem::~ChartPixmapItem()
|
||||
{
|
||||
painter.reset(); // Make sure to destroy painter before image that is painted on
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setTextureDirty()
|
||||
{
|
||||
textureDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setPositionDirty()
|
||||
{
|
||||
positionDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::render()
|
||||
{
|
||||
if (!node) {
|
||||
createNode(view.w()->createImageNode());
|
||||
view.addQSGNode(node.get(), zValue);
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (!img) {
|
||||
resize(QSizeF(1,1));
|
||||
img->fill(Qt::transparent);
|
||||
}
|
||||
if (textureDirty) {
|
||||
texture.reset(view.w()->createTextureFromImage(*img, QQuickWindow::TextureHasAlphaChannel));
|
||||
node->node->setTexture(texture.get());
|
||||
textureDirty = false;
|
||||
}
|
||||
if (positionDirty) {
|
||||
node->node->setRect(rect);
|
||||
positionDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ChartPixmapItem::resize(QSizeF size)
|
||||
{
|
||||
painter.reset();
|
||||
img.reset(new QImage(round_up(size.width()), round_up(size.height()), QImage::Format_ARGB32));
|
||||
painter.reset(new QPainter(img.get()));
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
rect.setSize(size);
|
||||
setTextureDirty();
|
||||
}
|
||||
|
||||
void ChartPixmapItem::setPos(QPointF pos)
|
||||
{
|
||||
rect.moveTopLeft(pos);
|
||||
setPositionDirty();
|
||||
}
|
||||
|
||||
QRectF ChartPixmapItem::getRect() const
|
||||
{
|
||||
return rect;
|
||||
}
|
||||
|
||||
static const int scatterItemDiameter = 10;
|
||||
static const int scatterItemBorder = 1;
|
||||
|
||||
ChartScatterItem::ChartScatterItem(StatsView &v, size_t z, const StatsTheme &theme, bool selected) : HideableChartItem(v, z),
|
||||
ChartScatterItem::ChartScatterItem(ChartView &v, size_t z, const StatsTheme &theme, bool selected) : HideableChartItem(v, z),
|
||||
theme(theme),
|
||||
positionDirty(false), textureDirty(false),
|
||||
highlight(selected ? Highlight::Selected : Highlight::Unselected)
|
||||
|
@ -123,7 +30,7 @@ ChartScatterItem::~ChartScatterItem()
|
|||
{
|
||||
}
|
||||
|
||||
static QSGTexture *createScatterTexture(StatsView &view, const QColor &color, const QColor &borderColor)
|
||||
static QSGTexture *createScatterTexture(ChartView &view, const QColor &color, const QColor &borderColor)
|
||||
{
|
||||
QImage img(scatterItemDiameter, scatterItemDiameter, QImage::Format_ARGB32);
|
||||
img.fill(Qt::transparent);
|
||||
|
@ -214,73 +121,7 @@ QRectF ChartScatterItem::getRect() const
|
|||
return rect;
|
||||
}
|
||||
|
||||
ChartRectItem::ChartRectItem(StatsView &v, size_t z,
|
||||
const QPen &pen, const QBrush &brush, double radius) : ChartPixmapItem(v, z),
|
||||
pen(pen), brush(brush), radius(radius)
|
||||
{
|
||||
}
|
||||
|
||||
ChartRectItem::~ChartRectItem()
|
||||
{
|
||||
}
|
||||
|
||||
void ChartRectItem::resize(QSizeF size)
|
||||
{
|
||||
ChartPixmapItem::resize(size);
|
||||
img->fill(Qt::transparent);
|
||||
painter->setPen(pen);
|
||||
painter->setBrush(brush);
|
||||
QSize imgSize = img->size();
|
||||
int width = pen.width();
|
||||
QRect rect(width / 2, width / 2, imgSize.width() - width, imgSize.height() - width);
|
||||
painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize);
|
||||
}
|
||||
|
||||
ChartTextItem::ChartTextItem(StatsView &v, size_t z, const QFont &f, const std::vector<QString> &text, bool center) :
|
||||
ChartPixmapItem(v, z), f(f), center(center)
|
||||
{
|
||||
QFontMetrics fm(f);
|
||||
double totalWidth = 1.0;
|
||||
fontHeight = static_cast<double>(fm.height());
|
||||
double totalHeight = std::max(1.0, static_cast<double>(text.size()) * fontHeight);
|
||||
|
||||
items.reserve(text.size());
|
||||
for (const QString &s: text) {
|
||||
double w = fm.size(Qt::TextSingleLine, s).width();
|
||||
items.push_back({ s, w });
|
||||
if (w > totalWidth)
|
||||
totalWidth = w;
|
||||
}
|
||||
resize(QSizeF(totalWidth, totalHeight));
|
||||
}
|
||||
|
||||
ChartTextItem::ChartTextItem(StatsView &v, size_t z, const QFont &f, const QString &text) :
|
||||
ChartTextItem(v, z, f, std::vector<QString>({ text }), true)
|
||||
{
|
||||
}
|
||||
|
||||
void ChartTextItem::setColor(const QColor &c)
|
||||
{
|
||||
setColor(c, Qt::transparent);
|
||||
}
|
||||
|
||||
void ChartTextItem::setColor(const QColor &c, const QColor &background)
|
||||
{
|
||||
img->fill(background);
|
||||
double y = 0.0;
|
||||
painter->setPen(QPen(c));
|
||||
painter->setFont(f);
|
||||
double totalWidth = getRect().width();
|
||||
for (const auto &[s, w]: items) {
|
||||
double x = center ? round((totalWidth - w) / 2.0) : 0.0;
|
||||
QRectF rect(x, y, w, fontHeight);
|
||||
painter->drawText(rect, s);
|
||||
y += fontHeight;
|
||||
}
|
||||
setTextureDirty();
|
||||
}
|
||||
|
||||
ChartPieItem::ChartPieItem(StatsView &v, size_t z, const StatsTheme &theme, double borderWidth) : ChartPixmapItem(v, z),
|
||||
ChartPieItem::ChartPieItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth) : ChartPixmapItem(v, z),
|
||||
theme(theme),
|
||||
borderWidth(borderWidth)
|
||||
{
|
||||
|
@ -322,101 +163,12 @@ void ChartPieItem::resize(QSizeF size)
|
|||
img->fill(Qt::transparent);
|
||||
}
|
||||
|
||||
ChartLineItemBase::ChartLineItemBase(StatsView &v, size_t z, QColor color, double width) : HideableChartItem(v, z),
|
||||
ChartLineItemBase::ChartLineItemBase(ChartView &v, size_t z, QColor color, double width) : HideableChartItem(v, z),
|
||||
color(color), width(width), positionDirty(false), materialDirty(false)
|
||||
{
|
||||
}
|
||||
|
||||
ChartLineItemBase::~ChartLineItemBase()
|
||||
{
|
||||
}
|
||||
|
||||
void ChartLineItemBase::setLine(QPointF fromIn, QPointF toIn)
|
||||
{
|
||||
from = fromIn;
|
||||
to = toIn;
|
||||
positionDirty = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
// Helper function to set points
|
||||
void setPoint(QSGGeometry::Point2D &v, const QPointF &p)
|
||||
{
|
||||
v.set(static_cast<float>(p.x()), static_cast<float>(p.y()));
|
||||
}
|
||||
|
||||
void setPoint(QSGGeometry::TexturedPoint2D &v, const QPointF &p, const QPointF &t)
|
||||
{
|
||||
v.set(static_cast<float>(p.x()), static_cast<float>(p.y()),
|
||||
static_cast<float>(t.x()), static_cast<float>(t.y()));
|
||||
}
|
||||
|
||||
void ChartLineItem::render()
|
||||
{
|
||||
if (!node) {
|
||||
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2));
|
||||
geometry->setDrawingMode(QSGGeometry::DrawLines);
|
||||
material.reset(new QSGFlatColorMaterial);
|
||||
createNode();
|
||||
node->setGeometry(geometry.get());
|
||||
node->setMaterial(material.get());
|
||||
view.addQSGNode(node.get(), zValue);
|
||||
positionDirty = materialDirty = true;
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (positionDirty) {
|
||||
// Attention: width is a geometry property and therefore handled by position dirty!
|
||||
geometry->setLineWidth(static_cast<float>(width));
|
||||
auto vertices = geometry->vertexDataAsPoint2D();
|
||||
setPoint(vertices[0], from);
|
||||
setPoint(vertices[1], to);
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
|
||||
if (materialDirty) {
|
||||
material->setColor(color);
|
||||
node->markDirty(QSGNode::DirtyMaterial);
|
||||
}
|
||||
|
||||
positionDirty = materialDirty = false;
|
||||
}
|
||||
|
||||
void ChartRectLineItem::render()
|
||||
{
|
||||
if (!node) {
|
||||
geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 5));
|
||||
geometry->setDrawingMode(QSGGeometry::DrawLineStrip);
|
||||
material.reset(new QSGFlatColorMaterial);
|
||||
createNode();
|
||||
node->setGeometry(geometry.get());
|
||||
node->setMaterial(material.get());
|
||||
view.addQSGNode(node.get(), zValue);
|
||||
positionDirty = materialDirty = true;
|
||||
}
|
||||
updateVisible();
|
||||
|
||||
if (positionDirty) {
|
||||
// Attention: width is a geometry property and therefore handled by position dirty!
|
||||
geometry->setLineWidth(static_cast<float>(width));
|
||||
auto vertices = geometry->vertexDataAsPoint2D();
|
||||
setPoint(vertices[0], from);
|
||||
setPoint(vertices[1], QPointF(from.x(), to.y()));
|
||||
setPoint(vertices[2], to);
|
||||
setPoint(vertices[3], QPointF(to.x(), from.y()));
|
||||
setPoint(vertices[4], from);
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
|
||||
if (materialDirty) {
|
||||
material->setColor(color);
|
||||
node->markDirty(QSGNode::DirtyMaterial);
|
||||
}
|
||||
|
||||
positionDirty = materialDirty = false;
|
||||
}
|
||||
|
||||
ChartBarItem::ChartBarItem(StatsView &v, size_t z, const StatsTheme &theme, double borderWidth) : HideableChartItem(v, z),
|
||||
ChartBarItem::ChartBarItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth) : HideableChartItem(v, z),
|
||||
theme(theme),
|
||||
borderWidth(borderWidth), selected(false),
|
||||
positionDirty(false), colorDirty(false), selectedDirty(false)
|
||||
|
@ -548,7 +300,7 @@ QRectF ChartBarItem::getRect() const
|
|||
return rect;
|
||||
}
|
||||
|
||||
ChartBoxItem::ChartBoxItem(StatsView &v, size_t z, const StatsTheme &theme, double borderWidth) :
|
||||
ChartBoxItem::ChartBoxItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth) :
|
||||
ChartBarItem(v, z, theme, borderWidth)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,113 +1,17 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Wrappers around QSGImageNode that allow painting onto an image
|
||||
// and then turning that into a texture to be displayed in a QQuickItem.
|
||||
#ifndef CHART_ITEM_H
|
||||
#define CHART_ITEM_H
|
||||
// Chart item specific to the statistics module
|
||||
#ifndef STATS_CHART_ITEM_H
|
||||
#define STATS_CHART_ITEM_H
|
||||
|
||||
#include "statshelper.h"
|
||||
#include "qt-quick/chartitem.h"
|
||||
|
||||
#include <memory>
|
||||
#include <QPainter>
|
||||
|
||||
class QSGGeometry;
|
||||
class QSGGeometryNode;
|
||||
class QSGFlatColorMaterial;
|
||||
class QSGImageNode;
|
||||
class QSGRectangleNode;
|
||||
class QSGTexture;
|
||||
class QSGTextureMaterial;
|
||||
class StatsTheme;
|
||||
class StatsView;
|
||||
|
||||
class ChartItem {
|
||||
public:
|
||||
// Only call on render thread!
|
||||
virtual void render() = 0;
|
||||
bool dirty; // If true, call render() when rebuilding the scene
|
||||
ChartItem *prev, *next; // Double linked list of items
|
||||
const size_t zValue;
|
||||
virtual ~ChartItem(); // Attention: must only be called by render thread.
|
||||
protected:
|
||||
ChartItem(StatsView &v, size_t z);
|
||||
QSizeF sceneSize() const;
|
||||
StatsView &view;
|
||||
void markDirty();
|
||||
};
|
||||
|
||||
template <typename Node>
|
||||
class HideableChartItem : public ChartItem {
|
||||
protected:
|
||||
HideableChartItem(StatsView &v, size_t z);
|
||||
std::unique_ptr<Node> node;
|
||||
bool visible;
|
||||
bool visibleChanged;
|
||||
template<class... Args>
|
||||
void createNode(Args&&... args); // Call to create node with visibility flag.
|
||||
void updateVisible(); // Must be called by child class to update visibility flag!
|
||||
public:
|
||||
void setVisible(bool visible);
|
||||
};
|
||||
|
||||
// A shortcut for ChartItems based on a hideable proxy item
|
||||
template <typename Node>
|
||||
using HideableChartProxyItem = HideableChartItem<HideableQSGNode<QSGProxyNode<Node>>>;
|
||||
|
||||
// A chart item that blits a precalculated pixmap onto the scene.
|
||||
class ChartPixmapItem : public HideableChartProxyItem<QSGImageNode> {
|
||||
public:
|
||||
ChartPixmapItem(StatsView &v, size_t z);
|
||||
~ChartPixmapItem();
|
||||
|
||||
void setPos(QPointF pos);
|
||||
void render() override;
|
||||
QRectF getRect() const;
|
||||
protected:
|
||||
void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*.
|
||||
std::unique_ptr<QPainter> painter;
|
||||
std::unique_ptr<QImage> img;
|
||||
void setTextureDirty();
|
||||
void setPositionDirty();
|
||||
QRectF rect;
|
||||
private:
|
||||
bool positionDirty; // true if the position changed since last render
|
||||
bool textureDirty; // true if the pixmap changed since last render
|
||||
std::unique_ptr<QSGTexture> texture;
|
||||
};
|
||||
|
||||
// Draw a rectangular background after resize. Children are responsible for calling update().
|
||||
class ChartRectItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartRectItem(StatsView &v, size_t z, const QPen &pen, const QBrush &brush, double radius);
|
||||
~ChartRectItem();
|
||||
void resize(QSizeF size);
|
||||
private:
|
||||
QPen pen;
|
||||
QBrush brush;
|
||||
double radius;
|
||||
};
|
||||
|
||||
// Attention: text is only drawn after calling setColor()!
|
||||
class ChartTextItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartTextItem(StatsView &v, size_t z, const QFont &f, const std::vector<QString> &text, bool center);
|
||||
ChartTextItem(StatsView &v, size_t z, const QFont &f, const QString &text);
|
||||
void setColor(const QColor &color); // Draw on transparent background
|
||||
void setColor(const QColor &color, const QColor &background); // Fill rectangle with given background color
|
||||
private:
|
||||
const QFont &f;
|
||||
double fontHeight;
|
||||
bool center;
|
||||
struct Item {
|
||||
QString s;
|
||||
double width;
|
||||
};
|
||||
std::vector<Item> items;
|
||||
};
|
||||
class ChartView;
|
||||
|
||||
// A pie chart item: draws disk segments onto a pixmap.
|
||||
class ChartPieItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartPieItem(StatsView &v, size_t z, const StatsTheme &theme, double borderWidth);
|
||||
ChartPieItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth);
|
||||
void drawSegment(double from, double to, QColor fill, QColor border, bool selected); // from and to are relative (0-1 is full disk).
|
||||
void resize(QSizeF size); // As in base class, but clears the canvas
|
||||
private:
|
||||
|
@ -115,39 +19,10 @@ private:
|
|||
double borderWidth;
|
||||
};
|
||||
|
||||
// Common data for line and rect items. Both are represented by two points.
|
||||
class ChartLineItemBase : public HideableChartItem<HideableQSGNode<QSGGeometryNode>> {
|
||||
public:
|
||||
ChartLineItemBase(StatsView &v, size_t z, QColor color, double width);
|
||||
~ChartLineItemBase();
|
||||
void setLine(QPointF from, QPointF to);
|
||||
protected:
|
||||
QPointF from, to;
|
||||
QColor color;
|
||||
double width;
|
||||
bool positionDirty;
|
||||
bool materialDirty;
|
||||
std::unique_ptr<QSGFlatColorMaterial> material;
|
||||
std::unique_ptr<QSGGeometry> geometry;
|
||||
};
|
||||
|
||||
class ChartLineItem : public ChartLineItemBase {
|
||||
public:
|
||||
using ChartLineItemBase::ChartLineItemBase;
|
||||
void render() override;
|
||||
};
|
||||
|
||||
// A simple rectangle without fill. Specified by any two opposing vertices.
|
||||
class ChartRectLineItem : public ChartLineItemBase {
|
||||
public:
|
||||
using ChartLineItemBase::ChartLineItemBase;
|
||||
void render() override;
|
||||
};
|
||||
|
||||
// A bar in a bar chart: a rectangle bordered by lines.
|
||||
class ChartBarItem : public HideableChartProxyItem<QSGRectangleNode> {
|
||||
public:
|
||||
ChartBarItem(StatsView &v, size_t z, const StatsTheme &theme, double borderWidth);
|
||||
ChartBarItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth);
|
||||
~ChartBarItem();
|
||||
void setColor(QColor color, QColor borderColor);
|
||||
void setRect(const QRectF &rect);
|
||||
|
@ -177,7 +52,7 @@ private:
|
|||
// A box-and-whiskers item. This is a bit lazy: derive from the bar item and add whiskers.
|
||||
class ChartBoxItem : public ChartBarItem {
|
||||
public:
|
||||
ChartBoxItem(StatsView &v, size_t z, const StatsTheme &theme, double borderWidth);
|
||||
ChartBoxItem(ChartView &v, size_t z, const StatsTheme &theme, double borderWidth);
|
||||
~ChartBoxItem();
|
||||
void setBox(const QRectF &rect, double min, double max, double median); // The rect describes Q1, Q3.
|
||||
QRectF getRect() const; // Note: this extends the center rectangle to include the whiskers.
|
||||
|
@ -195,7 +70,7 @@ private:
|
|||
// scatter item here, but so it is for now.
|
||||
class ChartScatterItem : public HideableChartProxyItem<QSGImageNode> {
|
||||
public:
|
||||
ChartScatterItem(StatsView &v, size_t z, const StatsTheme &theme, bool selected);
|
||||
ChartScatterItem(ChartView &v, size_t z, const StatsTheme &theme, bool selected);
|
||||
~ChartScatterItem();
|
||||
|
||||
// Currently, there is no highlighted and selected status.
|
||||
|
@ -219,37 +94,4 @@ private:
|
|||
Highlight highlight;
|
||||
};
|
||||
|
||||
// Implementation detail of templates - move to serparate header file
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::setVisible(bool visibleIn)
|
||||
{
|
||||
if (visible == visibleIn)
|
||||
return;
|
||||
visible = visibleIn;
|
||||
visibleChanged = true;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
template<class... Args>
|
||||
void HideableChartItem<Node>::createNode(Args&&... args)
|
||||
{
|
||||
node.reset(new Node(visible, std::forward<Args>(args)...));
|
||||
visibleChanged = false;
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
HideableChartItem<Node>::HideableChartItem(StatsView &v, size_t z) : ChartItem(v, z),
|
||||
visible(true), visibleChanged(false)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableChartItem<Node>::updateVisible()
|
||||
{
|
||||
if (visibleChanged)
|
||||
node->setVisible(visible);
|
||||
visibleChanged = false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
static const double histogramMarkerWidth = 2.0;
|
||||
|
||||
HistogramMarker::HistogramMarker(StatsView &view, double val, bool horizontal,
|
||||
HistogramMarker::HistogramMarker(ChartView &view, double val, bool horizontal,
|
||||
QColor color, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||
ChartLineItem(view, ChartZValue::ChartFeatures, color, histogramMarkerWidth),
|
||||
xAxis(xAxis), yAxis(yAxis),
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
#include "chartitem.h"
|
||||
|
||||
class StatsAxis;
|
||||
class StatsView;
|
||||
|
||||
// A line marking median or mean in histograms
|
||||
class HistogramMarker : public ChartLineItem {
|
||||
public:
|
||||
HistogramMarker(StatsView &view, double val, bool horizontal, QColor color, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
HistogramMarker(ChartView &view, double val, bool horizontal, QColor color, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
void updatePosition();
|
||||
private:
|
||||
StatsAxis *xAxis, *yAxis;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "informationbox.h"
|
||||
#include "statscolors.h"
|
||||
#include "statsview.h"
|
||||
#include "zvalues.h"
|
||||
|
||||
#include <QFontMetrics>
|
||||
|
@ -9,11 +8,11 @@ static const int informationBorder = 2;
|
|||
static const double informationBorderRadius = 4.0; // Radius of rounded corners
|
||||
static const int distanceFromPointer = 10; // Distance to place box from mouse pointer or scatter item
|
||||
|
||||
InformationBox::InformationBox(StatsView &v) :
|
||||
InformationBox::InformationBox(ChartView &v, const StatsTheme &theme) :
|
||||
ChartRectItem(v, ChartZValue::InformationBox,
|
||||
QPen(v.getCurrentTheme().informationBorderColor, informationBorder),
|
||||
QBrush(v.getCurrentTheme().informationColor), informationBorderRadius),
|
||||
theme(v.getCurrentTheme()),
|
||||
QPen(theme.informationBorderColor, informationBorder),
|
||||
QBrush(theme.informationColor), informationBorderRadius),
|
||||
theme(theme),
|
||||
width(0.0),
|
||||
height(0.0)
|
||||
{
|
||||
|
|
|
@ -10,11 +10,12 @@
|
|||
#include <memory>
|
||||
|
||||
struct dive;
|
||||
class StatsView;
|
||||
class ChartView;
|
||||
class StatsTheme;
|
||||
|
||||
// Information window showing data of highlighted dive
|
||||
struct InformationBox : ChartRectItem {
|
||||
InformationBox(StatsView &);
|
||||
InformationBox(ChartView &, const StatsTheme &theme);
|
||||
void setText(const std::vector<QString> &text, QPointF pos);
|
||||
void setPos(QPointF pos);
|
||||
int recommendedMaxLines() const;
|
||||
|
|
|
@ -14,12 +14,12 @@ static const double legendBoxBorderRadius = 4.0; // radius of rounded corners
|
|||
static const double legendBoxScale = 0.8; // 1.0: text-height of the used font
|
||||
static const double legendInternalBorderSize = 2.0;
|
||||
|
||||
Legend::Legend(StatsView &view, const std::vector<QString> &names) :
|
||||
Legend::Legend(ChartView &view, const StatsTheme &theme, const std::vector<QString> &names) :
|
||||
ChartRectItem(view, ChartZValue::Legend,
|
||||
QPen(view.getCurrentTheme().legendBorderColor, legendBorderSize),
|
||||
QBrush(view.getCurrentTheme().legendColor), legendBoxBorderRadius),
|
||||
QPen(theme.legendBorderColor, legendBorderSize),
|
||||
QBrush(theme.legendColor), legendBoxBorderRadius),
|
||||
displayedItems(0), width(0.0), height(0.0),
|
||||
theme(view.getCurrentTheme()),
|
||||
theme(theme),
|
||||
posInitialized(false)
|
||||
{
|
||||
entries.reserve(names.size());
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#ifndef STATS_LEGEND_H
|
||||
#define STATS_LEGEND_H
|
||||
|
||||
#include "chartitem.h"
|
||||
#include "qt-quick/chartitem.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
@ -13,7 +13,7 @@ class StatsTheme;
|
|||
|
||||
class Legend : public ChartRectItem {
|
||||
public:
|
||||
Legend(StatsView &view, const std::vector<QString> &names);
|
||||
Legend(ChartView &view, const StatsTheme &theme, const std::vector<QString> &names);
|
||||
void resize(); // called when the chart size changes.
|
||||
void setPos(QPointF pos); // Attention: not virtual - always call on this class.
|
||||
private:
|
||||
|
|
|
@ -257,7 +257,7 @@ bool PieSeries::hover(QPointF pos)
|
|||
if (highlighted >= 0 && highlighted < (int)items.size()) {
|
||||
items[highlighted].highlight(*item, highlighted, true, (int)items.size(), theme);
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information = view.createChartItem<InformationBox>(theme);
|
||||
information->setText(makeInfo(highlighted), pos);
|
||||
information->setVisible(true);
|
||||
} else {
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
#ifndef PIE_SERIES_H
|
||||
#define PIE_SERIES_H
|
||||
|
||||
#include "statshelper.h"
|
||||
#include "statsseries.h"
|
||||
#include "qt-quick/chartitem_ptr.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
static const double quartileMarkerSize = 15.0;
|
||||
|
||||
QuartileMarker::QuartileMarker(StatsView &view, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||
ChartLineItem(view, ChartZValue::ChartFeatures, view.getCurrentTheme().quartileMarkerColor, 2.0),
|
||||
QuartileMarker::QuartileMarker(ChartView &view, const StatsTheme &theme, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||
ChartLineItem(view, ChartZValue::ChartFeatures, theme.quartileMarkerColor, 2.0),
|
||||
xAxis(xAxis), yAxis(yAxis),
|
||||
pos(pos),
|
||||
value(value)
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
#include "chartitem.h"
|
||||
|
||||
class StatsAxis;
|
||||
class StatsView;
|
||||
|
||||
class QuartileMarker : public ChartLineItem {
|
||||
public:
|
||||
QuartileMarker(StatsView &view, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
QuartileMarker(ChartView &view, const StatsTheme &theme, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
~QuartileMarker();
|
||||
void updatePosition();
|
||||
private:
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
|
||||
static const double regressionLineWidth = 2.0;
|
||||
|
||||
RegressionItem::RegressionItem(StatsView &view, regression_data reg,
|
||||
RegressionItem::RegressionItem(ChartView &view, const StatsTheme &theme, regression_data reg,
|
||||
StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||
ChartPixmapItem(view, ChartZValue::ChartFeatures),
|
||||
theme(view.getCurrentTheme()),
|
||||
theme(theme),
|
||||
xAxis(xAxis), yAxis(yAxis), reg(reg),
|
||||
regression(true), confidence(true)
|
||||
{
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
class StatsAxis;
|
||||
class StatsTheme;
|
||||
class StatsView;
|
||||
|
||||
struct regression_data {
|
||||
double a,b;
|
||||
|
@ -16,7 +15,7 @@ struct regression_data {
|
|||
|
||||
class RegressionItem : public ChartPixmapItem {
|
||||
public:
|
||||
RegressionItem(StatsView &view, regression_data data, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
RegressionItem(ChartView &view, const StatsTheme &theme, regression_data data, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||
~RegressionItem();
|
||||
void updatePosition();
|
||||
void setFeatures(bool regression, bool confidence);
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#include "chartitem.h"
|
||||
#include "informationbox.h"
|
||||
#include "statscolors.h"
|
||||
#include "statshelper.h"
|
||||
#include "statstranslations.h"
|
||||
#include "statsvariables.h"
|
||||
#include "statsview.h"
|
||||
|
@ -183,7 +182,7 @@ bool ScatterSeries::hover(QPointF pos)
|
|||
return false;
|
||||
} else {
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information = view.createChartItem<InformationBox>(theme);
|
||||
|
||||
std::vector<QString> text;
|
||||
text.reserve(highlighted.size() * 5);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "statshelper.h"
|
||||
#include "statsseries.h"
|
||||
#include "qt-quick/chartitem_ptr.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "statsaxis.h"
|
||||
#include "statscolors.h"
|
||||
#include "statshelper.h"
|
||||
#include "statstranslations.h"
|
||||
#include "statsvariables.h"
|
||||
#include "statsview.h"
|
||||
|
@ -25,9 +24,9 @@ static const double axisLabelSpaceVertical = 2.0; // Space between axis or ticks
|
|||
static const double axisTitleSpaceHorizontal = 2.0; // Space between labels and title
|
||||
static const double axisTitleSpaceVertical = 2.0; // Space between labels and title
|
||||
|
||||
StatsAxis::StatsAxis(StatsView &view, const QString &title, bool horizontal, bool labelsBetweenTicks) :
|
||||
StatsAxis::StatsAxis(ChartView &view, const StatsTheme &theme, const QString &title, bool horizontal, bool labelsBetweenTicks) :
|
||||
ChartPixmapItem(view, ChartZValue::Axes),
|
||||
theme(view.getCurrentTheme()),
|
||||
theme(theme),
|
||||
line(view.createChartItem<ChartLineItem>(ChartZValue::Axes, theme.axisColor, axisWidth)),
|
||||
title(title), horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks),
|
||||
size(1.0), zeroOnScreen(0.0), min(0.0), max(1.0), labelWidth(0.0)
|
||||
|
@ -256,8 +255,9 @@ void StatsAxis::setPos(QPointF pos)
|
|||
}
|
||||
}
|
||||
|
||||
ValueAxis::ValueAxis(StatsView &view, const QString &title, double min, double max, int decimals, bool horizontal) :
|
||||
StatsAxis(view, title, horizontal, false),
|
||||
ValueAxis::ValueAxis(ChartView &view, const StatsTheme &theme,
|
||||
const QString &title, double min, double max, int decimals, bool horizontal) :
|
||||
StatsAxis(view, theme, title, horizontal, false),
|
||||
min(min), max(max), decimals(decimals)
|
||||
{
|
||||
// Avoid degenerate cases
|
||||
|
@ -317,8 +317,8 @@ void ValueAxis::updateLabels()
|
|||
}
|
||||
}
|
||||
|
||||
CountAxis::CountAxis(StatsView &view, const QString &title, int count, bool horizontal) :
|
||||
ValueAxis(view, title, 0.0, (double)count, 0, horizontal),
|
||||
CountAxis::CountAxis(ChartView &view, const StatsTheme &theme, const QString &title, int count, bool horizontal) :
|
||||
ValueAxis(view, theme, title, 0.0, (double)count, 0, horizontal),
|
||||
count(count)
|
||||
{
|
||||
}
|
||||
|
@ -376,8 +376,9 @@ void CountAxis::updateLabels()
|
|||
}
|
||||
}
|
||||
|
||||
CategoryAxis::CategoryAxis(StatsView &view, const QString &title, const std::vector<QString> &labels, bool horizontal) :
|
||||
StatsAxis(view, title, horizontal, true),
|
||||
CategoryAxis::CategoryAxis(ChartView &view, const StatsTheme &theme,
|
||||
const QString &title, const std::vector<QString> &labels, bool horizontal) :
|
||||
StatsAxis(view, theme, title, horizontal, true),
|
||||
labelsText(labels)
|
||||
{
|
||||
if (!labels.empty())
|
||||
|
@ -437,8 +438,9 @@ void CategoryAxis::updateLabels()
|
|||
}
|
||||
}
|
||||
|
||||
HistogramAxis::HistogramAxis(StatsView &view, const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) :
|
||||
StatsAxis(view, title, horizontal, false),
|
||||
HistogramAxis::HistogramAxis(ChartView &view, const StatsTheme &theme,
|
||||
const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) :
|
||||
StatsAxis(view, theme, title, horizontal, false),
|
||||
bin_values(std::move(bins))
|
||||
{
|
||||
if (bin_values.size() < 2) // Less than two makes no sense -> there must be at least one category
|
||||
|
@ -643,7 +645,7 @@ static std::vector<HistogramAxisEntry> timeRangeToBins(double from, double to)
|
|||
return res;
|
||||
}
|
||||
|
||||
DateAxis::DateAxis(StatsView &view, const QString &title, double from, double to, bool horizontal) :
|
||||
HistogramAxis(view, title, timeRangeToBins(from, to), horizontal)
|
||||
DateAxis::DateAxis(ChartView &view, const StatsTheme &theme, const QString &title, double from, double to, bool horizontal) :
|
||||
HistogramAxis(view, theme, title, timeRangeToBins(from, to), horizontal)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -3,12 +3,10 @@
|
|||
#define STATS_AXIS_H
|
||||
|
||||
#include "chartitem.h"
|
||||
#include "statshelper.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class StatsView;
|
||||
class ChartLineItem;
|
||||
class QFontMetrics;
|
||||
|
||||
|
@ -34,7 +32,7 @@ public:
|
|||
|
||||
std::vector<double> ticksPositions() const; // Positions in screen coordinates
|
||||
protected:
|
||||
StatsAxis(StatsView &view, const QString &title, bool horizontal, bool labelsBetweenTicks);
|
||||
StatsAxis(ChartView &view, const StatsTheme &theme, const QString &title, bool horizontal, bool labelsBetweenTicks);
|
||||
|
||||
const StatsTheme &theme; // Initialized once in constructor.
|
||||
ChartItemPtr<ChartLineItem> line;
|
||||
|
@ -73,7 +71,8 @@ private:
|
|||
|
||||
class ValueAxis : public StatsAxis {
|
||||
public:
|
||||
ValueAxis(StatsView &view, const QString &title, double min, double max, int decimals, bool horizontal);
|
||||
ValueAxis(ChartView &view, const StatsTheme &theme, const QString &title,
|
||||
double min, double max, int decimals, bool horizontal);
|
||||
private:
|
||||
double min, max;
|
||||
int decimals;
|
||||
|
@ -83,7 +82,7 @@ private:
|
|||
|
||||
class CountAxis : public ValueAxis {
|
||||
public:
|
||||
CountAxis(StatsView &view, const QString &title, int count, bool horizontal);
|
||||
CountAxis(ChartView &view, const StatsTheme &theme, const QString &title, int count, bool horizontal);
|
||||
private:
|
||||
int count;
|
||||
void updateLabels() override;
|
||||
|
@ -92,7 +91,8 @@ private:
|
|||
|
||||
class CategoryAxis : public StatsAxis {
|
||||
public:
|
||||
CategoryAxis(StatsView &view, const QString &title, const std::vector<QString> &labels, bool horizontal);
|
||||
CategoryAxis(ChartView &view, const StatsTheme &theme, const QString &title,
|
||||
const std::vector<QString> &labels, bool horizontal);
|
||||
private:
|
||||
std::vector<QString> labelsText;
|
||||
void updateLabels();
|
||||
|
@ -107,7 +107,8 @@ struct HistogramAxisEntry {
|
|||
|
||||
class HistogramAxis : public StatsAxis {
|
||||
public:
|
||||
HistogramAxis(StatsView &view, const QString &title, std::vector<HistogramAxisEntry> bin_values, bool horizontal);
|
||||
HistogramAxis(ChartView &view, const StatsTheme &theme, const QString &title,
|
||||
std::vector<HistogramAxisEntry> bin_values, bool horizontal);
|
||||
private:
|
||||
void updateLabels() override;
|
||||
std::pair<QString, QString> getFirstLastLabel() const override;
|
||||
|
@ -117,7 +118,7 @@ private:
|
|||
|
||||
class DateAxis : public HistogramAxis {
|
||||
public:
|
||||
DateAxis(StatsView &view, const QString &title, double from, double to, bool horizontal);
|
||||
DateAxis(ChartView &view, const StatsTheme &theme, const QString &title, double from, double to, bool horizontal);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// The background grid of a chart
|
||||
|
||||
#include "statshelper.h"
|
||||
#include "qt-quick/chartitem_ptr.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Helper functions to render the stats. Includes
|
||||
// QSGNode template jugglery to overcome API flaws.
|
||||
// Helper functions to render the stats.
|
||||
#ifndef STATSHELPER_H
|
||||
#define STATSHELPER_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <QPointF>
|
||||
#include <QSGNode>
|
||||
#include "core/dive.h"
|
||||
|
||||
struct dive;
|
||||
|
||||
// Round positions to integer values to avoid ugly artifacts
|
||||
QPointF roundPos(const QPointF &p);
|
||||
|
@ -16,128 +14,4 @@ QPointF roundPos(const QPointF &p);
|
|||
// Are all dives in this vector selected?
|
||||
bool allDivesSelected(const std::vector<dive *> &dives);
|
||||
|
||||
// A stupid pointer class that initializes to null and can be copy
|
||||
// assigned. This is for historical reasons: unique_ptrs to ChartItems
|
||||
// were replaced by plain pointers. Instead of nulling the plain pointers
|
||||
// in the constructors, use this. Ultimately, we might think about making
|
||||
// this thing smarter, once removal of individual ChartItems is implemented.
|
||||
template <typename T>
|
||||
class ChartItemPtr {
|
||||
friend class StatsView; // Only the stats view can create these pointers
|
||||
T *ptr;
|
||||
ChartItemPtr(T *ptr) : ptr(ptr)
|
||||
{
|
||||
}
|
||||
public:
|
||||
ChartItemPtr() : ptr(nullptr)
|
||||
{
|
||||
}
|
||||
ChartItemPtr(const ChartItemPtr &p) : ptr(p.ptr)
|
||||
{
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
ptr = nullptr;
|
||||
}
|
||||
ChartItemPtr &operator=(const ChartItemPtr &p)
|
||||
{
|
||||
ptr = p.ptr;
|
||||
return *this;
|
||||
}
|
||||
operator bool() const
|
||||
{
|
||||
return !!ptr;
|
||||
}
|
||||
bool operator!() const
|
||||
{
|
||||
return !ptr;
|
||||
}
|
||||
T &operator*() const
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
T *operator->() const
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
|
||||
// In general, we want chart items to be hideable. For example to show/hide
|
||||
// labels on demand. Very sadly, the QSG API is absolutely terrible with
|
||||
// respect to temporarily disabling. Instead of simply having a flag,
|
||||
// a QSGNode is queried using the "isSubtreeBlocked()" virtual function(!).
|
||||
//
|
||||
// Not only is this a slow operation performed on every single node, it
|
||||
// also is often not possible to override this function: For improved
|
||||
// performance, the documentation recommends to create QSG nodes via
|
||||
// QQuickWindow. This provides nodes optimized for the actual hardware.
|
||||
// However, this obviously means that these nodes cannot be derived from!
|
||||
//
|
||||
// In that case, there are two possibilities: Add a proxy node with an
|
||||
// overridden "isSubtreeBlocked()" function or remove the node from the
|
||||
// scene. The former was chosen here, because it is less complex.
|
||||
//
|
||||
// The following slightly cryptic templates are used to unify the two
|
||||
// cases: The QSGNode is generated by our own code or the QSGNode is
|
||||
// obtained from QQuickWindow.
|
||||
//
|
||||
// The "HideableQSGNode<Node>" template augments the QSGNode "Node"
|
||||
// by a "setVisible()" function and overrides "isSubtreeBlocked()"
|
||||
//
|
||||
// The "QSGProxyNode<Node>" template is a QSGNode with a single
|
||||
// child of type "Node".
|
||||
//
|
||||
// Thus, if the node can be created, use:
|
||||
// HideableQSGNode<NodeTypeThatCanBeCreated> node
|
||||
// and if the node can only be obtained from QQuickWindow, use:
|
||||
// HideableQSGNode<QSGProxyNode<NodeThatCantBeCreated>> node
|
||||
// The latter should obviously be typedef-ed.
|
||||
//
|
||||
// Yes, that's all horrible, but if nothing else it teaches us about
|
||||
// composition.
|
||||
template <typename Node>
|
||||
class HideableQSGNode : public Node {
|
||||
bool hidden;
|
||||
bool isSubtreeBlocked() const override final;
|
||||
public:
|
||||
template<class... Args>
|
||||
HideableQSGNode(bool visible, Args&&... args);
|
||||
void setVisible(bool visible);
|
||||
};
|
||||
|
||||
template <typename Node>
|
||||
class QSGProxyNode : public QSGNode {
|
||||
public:
|
||||
std::unique_ptr<Node> node;
|
||||
QSGProxyNode(Node *node);
|
||||
};
|
||||
|
||||
// Implementation detail of templates - move to serparate header file
|
||||
template <typename Node>
|
||||
QSGProxyNode<Node>::QSGProxyNode(Node *node) : node(node)
|
||||
{
|
||||
appendChildNode(node);
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
bool HideableQSGNode<Node>::isSubtreeBlocked() const
|
||||
{
|
||||
return hidden;
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
template<class... Args>
|
||||
HideableQSGNode<Node>::HideableQSGNode(bool visible, Args&&... args) :
|
||||
Node(std::forward<Args>(args)...),
|
||||
hidden(!visible)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Node>
|
||||
void HideableQSGNode<Node>::setVisible(bool visible)
|
||||
{
|
||||
hidden = !visible;
|
||||
Node::markDirty(QSGNode::DirtySubtreeBlocked);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -22,29 +22,21 @@
|
|||
#include "core/trip.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickWindow>
|
||||
#include <QSGImageNode>
|
||||
#include <QSGRectangleNode>
|
||||
#include <QSGTexture>
|
||||
|
||||
// Constants that control the graph layouts
|
||||
static const double sceneBorder = 5.0; // Border between scene edges and statitistics view
|
||||
static const double titleBorder = 2.0; // Border between title and chart
|
||||
static const double selectionLassoWidth = 2.0; // Border between title and chart
|
||||
|
||||
StatsView::StatsView(QQuickItem *parent) : QQuickItem(parent),
|
||||
maxZ(ChartZValue::Count),
|
||||
backgroundDirty(true),
|
||||
StatsView::StatsView(QQuickItem *parent) : ChartView(parent, ChartZValue::Count),
|
||||
currentTheme(&getStatsTheme(false)),
|
||||
backgroundColor(currentTheme->backgroundColor),
|
||||
highlightedSeries(nullptr),
|
||||
xAxis(nullptr),
|
||||
yAxis(nullptr),
|
||||
draggedItem(nullptr),
|
||||
restrictDives(false),
|
||||
rootNode(nullptr)
|
||||
restrictDives(false)
|
||||
{
|
||||
setBackgroundColor(currentTheme->backgroundColor);
|
||||
setFlag(ItemHasContents, true);
|
||||
|
||||
connect(&diveListNotifier, &DiveListNotifier::numShownChanged, this, &StatsView::replotIfVisible);
|
||||
|
@ -122,190 +114,6 @@ void StatsView::mouseReleaseEvent(QMouseEvent *)
|
|||
}
|
||||
}
|
||||
|
||||
// Define a hideable dummy QSG node that is used as a parent node to make
|
||||
// all objects of a z-level visible / invisible.
|
||||
using ZNode = HideableQSGNode<QSGNode>;
|
||||
|
||||
class RootNode : public QSGNode
|
||||
{
|
||||
public:
|
||||
RootNode(StatsView &view, QColor backgroundColor, size_t maxZ);
|
||||
~RootNode();
|
||||
StatsView &view;
|
||||
std::unique_ptr<QSGRectangleNode> backgroundNode; // solid background
|
||||
// We entertain one node per Z-level.
|
||||
std::vector<std::unique_ptr<ZNode>> zNodes;
|
||||
};
|
||||
|
||||
RootNode::RootNode(StatsView &view, QColor backgroundColor, size_t maxZ) : view(view)
|
||||
{
|
||||
zNodes.resize(maxZ);
|
||||
|
||||
// Add a background rectangle with a solid color. This could
|
||||
// also be done on the widget level, but would have to be done
|
||||
// separately for desktop and mobile, so do it here.
|
||||
backgroundNode.reset(view.w()->createRectangleNode());
|
||||
appendChildNode(backgroundNode.get());
|
||||
|
||||
for (auto &zNode: zNodes) {
|
||||
zNode.reset(new ZNode(true));
|
||||
appendChildNode(zNode.get());
|
||||
}
|
||||
}
|
||||
|
||||
RootNode::~RootNode()
|
||||
{
|
||||
view.emergencyShutdown();
|
||||
}
|
||||
|
||||
void StatsView::freeDeletedChartItems()
|
||||
{
|
||||
ChartItem *nextitem;
|
||||
for (ChartItem *item = deletedItems.first; item; item = nextitem) {
|
||||
nextitem = item->next;
|
||||
delete item;
|
||||
}
|
||||
deletedItems.clear();
|
||||
}
|
||||
|
||||
QSGNode *StatsView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
|
||||
{
|
||||
// The QtQuick drawing interface is utterly bizzare with a distinct 1980ies-style memory management.
|
||||
// This is just a copy of what is found in Qt's documentation.
|
||||
RootNode *n = static_cast<RootNode *>(oldNode);
|
||||
if (!n)
|
||||
n = rootNode = new RootNode(*this, backgroundColor, maxZ);
|
||||
|
||||
// Delete all chart items that are marked for deletion.
|
||||
freeDeletedChartItems();
|
||||
|
||||
if (backgroundDirty) {
|
||||
rootNode->backgroundNode->setRect(plotRect);
|
||||
backgroundDirty = false;
|
||||
}
|
||||
|
||||
for (ChartItem *item = dirtyItems.first; item; item = item->next) {
|
||||
item->render();
|
||||
item->dirty = false;
|
||||
}
|
||||
dirtyItems.splice(cleanItems);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// When reparenting the QQuickWidget, QtQuick decides to delete our rootNode
|
||||
// and with it all the QSG nodes, even though we have *not* given the
|
||||
// permission to do so! If the widget is reused, we try to delete the
|
||||
// stale items, whose nodes have already been deleted by QtQuick, leading
|
||||
// to a double-free(). Instead of searching for the cause of this behavior,
|
||||
// let's just hook into the rootNodes destructor and delete the objects
|
||||
// in a controlled manner, so that QtQuick has no more access to them.
|
||||
void StatsView::emergencyShutdown()
|
||||
{
|
||||
// Mark clean and dirty chart items for deletion...
|
||||
cleanItems.splice(deletedItems);
|
||||
dirtyItems.splice(deletedItems);
|
||||
|
||||
// ...and delete them.
|
||||
freeDeletedChartItems();
|
||||
|
||||
// Now delete all the pointers we might have to chart features,
|
||||
// axes, etc. Note that all pointers to chart items are non
|
||||
// owning, so this only resets stale references, but does not
|
||||
// lead to any additional deletion of chart items.
|
||||
reset();
|
||||
|
||||
// The rootNode is being deleted -> remove the reference to that
|
||||
rootNode = nullptr;
|
||||
}
|
||||
|
||||
void StatsView::addQSGNode(QSGNode *node, size_t z)
|
||||
{
|
||||
size_t idx = std::clamp(z, (size_t)0, maxZ);
|
||||
rootNode->zNodes[idx]->appendChildNode(node);
|
||||
}
|
||||
|
||||
void StatsView::registerChartItem(ChartItem &item)
|
||||
{
|
||||
cleanItems.append(item);
|
||||
}
|
||||
|
||||
void StatsView::registerDirtyChartItem(ChartItem &item)
|
||||
{
|
||||
if (item.dirty)
|
||||
return;
|
||||
cleanItems.remove(item);
|
||||
dirtyItems.append(item);
|
||||
item.dirty = true;
|
||||
}
|
||||
|
||||
void StatsView::deleteChartItemInternal(ChartItem &item)
|
||||
{
|
||||
if (item.dirty)
|
||||
dirtyItems.remove(item);
|
||||
else
|
||||
cleanItems.remove(item);
|
||||
deletedItems.append(item);
|
||||
}
|
||||
|
||||
StatsView::ChartItemList::ChartItemList() : first(nullptr), last(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void StatsView::ChartItemList::clear()
|
||||
{
|
||||
first = last = nullptr;
|
||||
}
|
||||
|
||||
void StatsView::ChartItemList::remove(ChartItem &item)
|
||||
{
|
||||
if (item.next)
|
||||
item.next->prev = item.prev;
|
||||
else
|
||||
last = item.prev;
|
||||
if (item.prev)
|
||||
item.prev->next = item.next;
|
||||
else
|
||||
first = item.next;
|
||||
item.prev = item.next = nullptr;
|
||||
}
|
||||
|
||||
void StatsView::ChartItemList::append(ChartItem &item)
|
||||
{
|
||||
if (!first) {
|
||||
first = &item;
|
||||
} else {
|
||||
item.prev = last;
|
||||
last->next = &item;
|
||||
}
|
||||
last = &item;
|
||||
}
|
||||
|
||||
void StatsView::ChartItemList::splice(ChartItemList &l2)
|
||||
{
|
||||
if (!first) // if list is empty -> nothing to do.
|
||||
return;
|
||||
if (!l2.first) {
|
||||
l2 = *this;
|
||||
} else {
|
||||
l2.last->next = first;
|
||||
first->prev = l2.last;
|
||||
l2.last = last;
|
||||
}
|
||||
clear();
|
||||
}
|
||||
|
||||
QQuickWindow *StatsView::w() const
|
||||
{
|
||||
return window();
|
||||
}
|
||||
|
||||
void StatsView::setBackgroundColor(QColor color)
|
||||
{
|
||||
backgroundColor = color;
|
||||
rootNode->backgroundNode->setColor(color);
|
||||
}
|
||||
|
||||
void StatsView::setTheme(bool dark)
|
||||
{
|
||||
currentTheme = &getStatsTheme(dark);
|
||||
|
@ -317,34 +125,6 @@ const StatsTheme &StatsView::getCurrentTheme() const
|
|||
return *currentTheme;
|
||||
}
|
||||
|
||||
QSizeF StatsView::size() const
|
||||
{
|
||||
return boundingRect().size();
|
||||
}
|
||||
|
||||
QRectF StatsView::plotArea() const
|
||||
{
|
||||
return plotRect;
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void StatsView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||
#else
|
||||
void StatsView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||
#endif
|
||||
{
|
||||
plotRect = QRectF(QPointF(0.0, 0.0), newGeometry.size());
|
||||
backgroundDirty = true;
|
||||
plotAreaChanged(plotRect.size());
|
||||
|
||||
// Do we need to call the base-class' version of geometryChanged? Probably for QML?
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||
#else
|
||||
QQuickItem::geometryChanged(newGeometry, oldGeometry);
|
||||
#endif
|
||||
}
|
||||
|
||||
void StatsView::plotAreaChanged(const QSizeF &s)
|
||||
{
|
||||
double left = sceneBorder;
|
||||
|
@ -491,7 +271,7 @@ void StatsView::updateTitlePos()
|
|||
template <typename T, class... Args>
|
||||
T *StatsView::createAxis(const QString &title, Args&&... args)
|
||||
{
|
||||
return &*createChartItem<T>(title, std::forward<Args>(args)...);
|
||||
return &*createChartItem<T>(*currentTheme, title, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void StatsView::setAxes(StatsAxis *x, StatsAxis *y)
|
||||
|
@ -503,6 +283,12 @@ void StatsView::setAxes(StatsAxis *x, StatsAxis *y)
|
|||
}
|
||||
|
||||
void StatsView::reset()
|
||||
{
|
||||
resetPointers();
|
||||
clearItems();
|
||||
}
|
||||
|
||||
void StatsView::resetPointers()
|
||||
{
|
||||
highlightedSeries = nullptr;
|
||||
xAxis = yAxis = nullptr;
|
||||
|
@ -514,10 +300,6 @@ void StatsView::reset()
|
|||
medianMarker.reset();
|
||||
selectionRect.reset();
|
||||
|
||||
// Mark clean and dirty chart items for deletion
|
||||
cleanItems.splice(deletedItems);
|
||||
dirtyItems.splice(deletedItems);
|
||||
|
||||
series.clear();
|
||||
quartileMarkers.clear();
|
||||
grid.reset();
|
||||
|
@ -547,7 +329,7 @@ void StatsView::plot(const StatsState &stateIn)
|
|||
state = stateIn;
|
||||
plotChart();
|
||||
updateFeatures(); // Show / hide chart features, such as legend, etc.
|
||||
plotAreaChanged(plotRect.size());
|
||||
plotAreaChanged(plotArea().size());
|
||||
update();
|
||||
}
|
||||
|
||||
|
@ -618,8 +400,7 @@ void StatsView::updateFeatures()
|
|||
legend->setVisible(state.legend);
|
||||
|
||||
// For labels, we are brutal: simply show/hide the whole z-level with the labels
|
||||
if (rootNode)
|
||||
rootNode->zNodes[ChartZValue::SeriesLabels]->setVisible(state.labels);
|
||||
setLayerVisibility(ChartZValue::SeriesLabels, state.labels);
|
||||
|
||||
if (meanMarker)
|
||||
meanMarker->setVisible(state.mean);
|
||||
|
@ -772,7 +553,7 @@ void StatsView::plotBarChart(const std::vector<dive *> &dives,
|
|||
setAxes(catAxis, valAxis);
|
||||
|
||||
// Paint legend first, because the bin-names will be moved away from.
|
||||
legend = createChartItem<Legend>(data.vbinNames);
|
||||
legend = createChartItem<Legend>(*currentTheme, data.vbinNames);
|
||||
|
||||
std::vector<BarSeries::MultiItem> items;
|
||||
items.reserve(data.hbins.size());
|
||||
|
@ -997,7 +778,7 @@ void StatsView::plotPieChart(const std::vector<dive *> &dives, ChartSortMode sor
|
|||
|
||||
PieSeries *series = createSeries<PieSeries>(categoryVariable->name(), std::move(data), sortMode);
|
||||
|
||||
legend = createChartItem<Legend>(series->binNames());
|
||||
legend = createChartItem<Legend>(*currentTheme, series->binNames());
|
||||
}
|
||||
|
||||
void StatsView::plotDiscreteBoxChart(const std::vector<dive *> &dives,
|
||||
|
@ -1067,11 +848,11 @@ void StatsView::plotDiscreteScatter(const std::vector<dive *> &dives,
|
|||
StatsQuartiles quartiles = StatsVariable::quartiles(array);
|
||||
if (quartiles.isValid()) {
|
||||
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
||||
x, quartiles.q1, catAxis, valAxis));
|
||||
*currentTheme, x, quartiles.q1, catAxis, valAxis));
|
||||
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
||||
x, quartiles.q2, catAxis, valAxis));
|
||||
*currentTheme, x, quartiles.q2, catAxis, valAxis));
|
||||
quartileMarkers.push_back(createChartItem<QuartileMarker>(
|
||||
x, quartiles.q3, catAxis, valAxis));
|
||||
*currentTheme, x, quartiles.q3, catAxis, valAxis));
|
||||
}
|
||||
x += 1.0;
|
||||
}
|
||||
|
@ -1222,7 +1003,7 @@ void StatsView::plotHistogramStackedChart(const std::vector<dive *> &dives,
|
|||
*categoryBinner, categoryBins, !isHorizontal);
|
||||
|
||||
BarPlotData data(categoryBins, *valueBinner);
|
||||
legend = createChartItem<Legend>(data.vbinNames);
|
||||
legend = createChartItem<Legend>(*currentTheme, data.vbinNames);
|
||||
|
||||
CountAxis *valAxis = createCountAxis(data.maxCategoryCount, isHorizontal);
|
||||
|
||||
|
@ -1373,5 +1154,5 @@ void StatsView::plotScatter(const std::vector<dive *> &dives, const StatsVariabl
|
|||
// y = ax + b
|
||||
struct regression_data reg = linear_regression(points);
|
||||
if (!std::isnan(reg.a))
|
||||
regressionItem = createChartItem<RegressionItem>(reg, xAxis, yAxis);
|
||||
regressionItem = createChartItem<RegressionItem>(*currentTheme, reg, xAxis, yAxis);
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
#define STATS_VIEW_H
|
||||
|
||||
#include "statsstate.h"
|
||||
#include "statshelper.h"
|
||||
#include "statsselection.h"
|
||||
#include "qt-quick/chartview.h"
|
||||
|
||||
#include <memory>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QQuickItem>
|
||||
|
||||
struct dive;
|
||||
struct StatsBinner;
|
||||
|
@ -18,7 +18,6 @@ struct StatsVariable;
|
|||
|
||||
class StatsSeries;
|
||||
class CategoryAxis;
|
||||
class ChartItem;
|
||||
class ChartRectLineItem;
|
||||
class ChartTextItem;
|
||||
class CountAxis;
|
||||
|
@ -30,14 +29,12 @@ class StatsAxis;
|
|||
class StatsGrid;
|
||||
class StatsTheme;
|
||||
class Legend;
|
||||
class QSGTexture;
|
||||
class RootNode; // Internal implementation detail
|
||||
|
||||
enum class ChartSubType : int;
|
||||
enum class StatsOperation : int;
|
||||
enum class ChartSortMode : int;
|
||||
|
||||
class StatsView : public QQuickItem {
|
||||
class StatsView : public ChartView {
|
||||
Q_OBJECT
|
||||
public:
|
||||
StatsView();
|
||||
|
@ -49,44 +46,15 @@ public:
|
|||
void restrictToSelection();
|
||||
void unrestrict();
|
||||
int restrictionCount() const; // <0: no restriction
|
||||
QQuickWindow *w() const; // Make window available to items
|
||||
QSizeF size() const;
|
||||
QRectF plotArea() const;
|
||||
void setBackgroundColor(QColor color); // Chart must be replot for color to become effective.
|
||||
void setTheme(bool dark); // Chart must be replot for theme to become effective.
|
||||
const StatsTheme &getCurrentTheme() const;
|
||||
void addQSGNode(QSGNode *node, size_t z); // Must only be called in render thread!
|
||||
void registerChartItem(ChartItem &item);
|
||||
void registerDirtyChartItem(ChartItem &item);
|
||||
void emergencyShutdown(); // Called when QQuick decides to delete our root node.
|
||||
|
||||
// Create a chart item and add it to the scene.
|
||||
// The item must not be deleted by the caller, but can be
|
||||
// scheduled for deletion using deleteChartItem() below.
|
||||
// Most items can be made invisible, which is preferred over deletion.
|
||||
// All items on the scene will be deleted once the chart is reset.
|
||||
template <typename T, class... Args>
|
||||
ChartItemPtr<T> createChartItem(Args&&... args);
|
||||
|
||||
template <typename T>
|
||||
void deleteChartItem(ChartItemPtr<T> &item);
|
||||
private slots:
|
||||
void replotIfVisible();
|
||||
void divesSelected(const QVector<dive *> &dives);
|
||||
private:
|
||||
// QtQuick related things
|
||||
size_t maxZ;
|
||||
bool backgroundDirty;
|
||||
QRectF plotRect;
|
||||
QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
#else
|
||||
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
#endif
|
||||
void plotAreaChanged(const QSizeF &size);
|
||||
void plotAreaChanged(const QSizeF &size) override;
|
||||
void reset(); // clears all series and axes
|
||||
void resetPointers() override;
|
||||
void setAxes(StatsAxis *x, StatsAxis *y);
|
||||
void plotBarChart(const std::vector<dive *> &dives,
|
||||
ChartSubType subType, ChartSortMode sortMode,
|
||||
|
@ -144,7 +112,6 @@ private:
|
|||
|
||||
StatsState state;
|
||||
const StatsTheme *currentTheme;
|
||||
QColor backgroundColor;
|
||||
std::vector<std::unique_ptr<StatsSeries>> series;
|
||||
std::unique_ptr<StatsGrid> grid;
|
||||
std::vector<ChartItemPtr<QuartileMarker>> quartileMarkers;
|
||||
|
@ -167,40 +134,6 @@ private:
|
|||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
RootNode *rootNode;
|
||||
|
||||
// There are three double linked lists of chart items:
|
||||
// clean items, dirty items and items to be deleted.
|
||||
// Note that only the render thread must delete chart items,
|
||||
// and therefore these lists are the only owning pointers
|
||||
// to chart items. All other pointers are non-owning and
|
||||
// can therefore become stale.
|
||||
struct ChartItemList {
|
||||
ChartItemList();
|
||||
ChartItem *first, *last;
|
||||
void append(ChartItem &item);
|
||||
void remove(ChartItem &item);
|
||||
void clear();
|
||||
void splice(ChartItemList &list);
|
||||
};
|
||||
ChartItemList cleanItems, dirtyItems, deletedItems;
|
||||
void deleteChartItemInternal(ChartItem &item);
|
||||
void freeDeletedChartItems();
|
||||
};
|
||||
|
||||
// This implementation detail must be known to users of the class.
|
||||
// Perhaps move it into a statsview_impl.h file.
|
||||
template <typename T, class... Args>
|
||||
ChartItemPtr<T> StatsView::createChartItem(Args&&... args)
|
||||
{
|
||||
return ChartItemPtr(new T(*this, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void StatsView::deleteChartItem(ChartItemPtr<T> &item)
|
||||
{
|
||||
deleteChartItemInternal(*item);
|
||||
item.reset();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue