statistics: convert chart to QQuickItem

It turns out that the wrong base class was used for the chart.
QQuickWidget can only be used on desktop, not in a mobile UI.

Therefore, turn this into a QQuickItem and move the container
QQuickWidget into desktop-only code.

Currently, this code is insane: The chart is rendered onto a
QGraphicsScene (as it was before), which is then rendered into
a QImage, which is transformed into a QSGTexture, which is then
projected onto the device. This is performed on every mouse
move event, since these events in general change the position
of the info-box.

The plan is to slowly convert elements such as the info-box into
QQuickItems. Browsing the QtQuick documentation, this will
not be much fun.

Also note that the rendering currently tears, flickers and has
antialiasing artifacts, most likely owing to integer (QImage)
to floating point (QGraphicsScene, QQuickItem) conversion
problems. The data flow is
QGraphicsScene (float) -> QImage (int) -> QQuickItem (float).

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2021-01-07 14:38:37 +01:00 committed by Dirk Hohndel
parent f6b857b8fe
commit e7907c494f
32 changed files with 299 additions and 269 deletions

View file

@ -301,7 +301,7 @@ endif()
#set up the subsurface_link_libraries variable #set up the subsurface_link_libraries variable
set(SUBSURFACE_LINK_LIBRARIES ${SUBSURFACE_LINK_LIBRARIES} ${LIBDIVECOMPUTER_LIBRARIES} ${LIBGIT2_LIBRARIES} ${LIBUSB_LIBRARIES} ${LIBMTP_LIBRARIES}) set(SUBSURFACE_LINK_LIBRARIES ${SUBSURFACE_LINK_LIBRARIES} ${LIBDIVECOMPUTER_LIBRARIES} ${LIBGIT2_LIBRARIES} ${LIBUSB_LIBRARIES} ${LIBMTP_LIBRARIES})
if (NOT SUBSURFACE_TARGET_EXECUTABLE MATCHES "DownloaderExecutable") if (NOT SUBSURFACE_TARGET_EXECUTABLE MATCHES "DownloaderExecutable")
qt5_add_resources(SUBSURFACE_RESOURCES subsurface.qrc map-widget/qml/map-widget.qrc stats/qml/statsview.qrc) qt5_add_resources(SUBSURFACE_RESOURCES subsurface.qrc map-widget/qml/map-widget.qrc desktop-widgets/qml/statsview2.qrc)
endif() endif()
# hack to build successfully on LGTM # hack to build successfully on LGTM

View file

@ -7,6 +7,10 @@ RoundRectItem::RoundRectItem(double radius, QGraphicsItem *parent) : QGraphicsRe
{ {
} }
RoundRectItem::RoundRectItem(double radius) : RoundRectItem(radius, nullptr)
{
}
void RoundRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) void RoundRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
{ {
painter->save(); painter->save();

View file

@ -7,6 +7,7 @@
class RoundRectItem : public QGraphicsRectItem { class RoundRectItem : public QGraphicsRectItem {
public: public:
RoundRectItem(double radius, QGraphicsItem *parent); RoundRectItem(double radius, QGraphicsItem *parent);
RoundRectItem(double radius);
private: private:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
double radius; double radius;

View file

@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
import QtQuick 2.0
import org.subsurfacedivelog.mobile 1.0
StatsView {
}

View file

@ -1,5 +1,5 @@
<RCC> <RCC>
<qresource prefix="/qml"> <qresource prefix="/qml">
<file>statsview.qml</file> <file>statsview2.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -70,6 +70,7 @@ QSize ChartItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMod
return size; return size;
} }
static const QUrl urlStatsView = QUrl(QStringLiteral("qrc:/qml/statsview2.qml"));
StatsWidget::StatsWidget(QWidget *parent) : QWidget(parent) StatsWidget::StatsWidget(QWidget *parent) : QWidget(parent)
{ {
ui.setupUi(this); ui.setupUi(this);
@ -83,6 +84,13 @@ StatsWidget::StatsWidget(QWidget *parent) : QWidget(parent)
connect(ui.var1Binner, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var1BinnerChanged); connect(ui.var1Binner, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var1BinnerChanged);
connect(ui.var2Binner, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var2BinnerChanged); connect(ui.var2Binner, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var2BinnerChanged);
connect(ui.var2Operation, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var2OperationChanged); connect(ui.var2Operation, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var2OperationChanged);
ui.stats->setSource(urlStatsView);
ui.stats->setResizeMode(QQuickWidget::SizeRootObjectToView);
QQuickItem *root = ui.stats->rootObject();
view = qobject_cast<StatsView *>(root);
if (!view)
qWarning("Oops. The root of the StatsView is not a StatsView.");
} }
// Initialize QComboBox with list of variables // Initialize QComboBox with list of variables
@ -96,7 +104,7 @@ static void setVariableList(QComboBox *combo, const StatsState::VariableList &li
} }
// Initialize QComboBox and QLabel of binners. Hide if there are no binners. // Initialize QComboBox and QLabel of binners. Hide if there are no binners.
static void setBinList(QLabel *label, QComboBox *combo, const StatsState::BinnerList &list) static void setBinList(QComboBox *combo, const StatsState::BinnerList &list)
{ {
combo->clear(); combo->clear();
combo->setEnabled(!list.binners.empty()); combo->setEnabled(!list.binners.empty());
@ -114,8 +122,8 @@ void StatsWidget::updateUi()
int pos = charts.update(uiState.charts); int pos = charts.update(uiState.charts);
ui.chartType->setCurrentIndex(pos); ui.chartType->setCurrentIndex(pos);
ui.chartType->setItemDelegate(new ChartItemDelegate); ui.chartType->setItemDelegate(new ChartItemDelegate);
setBinList(ui.var1BinnerLabel, ui.var1Binner, uiState.binners1); setBinList(ui.var1Binner, uiState.binners1);
setBinList(ui.var2BinnerLabel, ui.var2Binner, uiState.binners2); setBinList(ui.var2Binner, uiState.binners2);
setVariableList(ui.var2Operation, uiState.operations2); setVariableList(ui.var2Operation, uiState.operations2);
// Add checkboxes for additional features // Add checkboxes for additional features
@ -129,7 +137,8 @@ void StatsWidget::updateUi()
ui.features->addWidget(check); ui.features->addWidget(check);
} }
ui.stats->plot(state); if (view)
view->plot(state);
} }
void StatsWidget::closeStats() void StatsWidget::closeStats()

View file

@ -9,6 +9,7 @@
#include <memory> #include <memory>
class QCheckBox; class QCheckBox;
class StatsView;
class StatsWidget : public QWidget { class StatsWidget : public QWidget {
Q_OBJECT Q_OBJECT
@ -27,6 +28,7 @@ slots:
private: private:
Ui::StatsWidget ui; Ui::StatsWidget ui;
StatsState state; StatsState state;
StatsView *view;
void updateUi(); void updateUi();
std::vector<std::unique_ptr<QCheckBox>> features; std::vector<std::unique_ptr<QCheckBox>> features;

View file

@ -105,7 +105,7 @@
</layout> </layout>
</item> </item>
<item> <item>
<widget class="StatsView" name="stats"> <widget class="QQuickWidget" name="stats">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -116,14 +116,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>StatsView</class>
<extends>QQuickWidget</extends>
<header>stats/statsview.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources> <resources>
<include location="../subsurface.qrc"/> <include location="../subsurface.qrc"/>
</resources> </resources>

View file

@ -2,6 +2,7 @@
#include "barseries.h" #include "barseries.h"
#include "informationbox.h" #include "informationbox.h"
#include "statscolors.h" #include "statscolors.h"
#include "statshelper.h"
#include "statstranslations.h" #include "statstranslations.h"
#include "zvalues.h" #include "zvalues.h"
@ -27,19 +28,19 @@ bool BarSeries::Index::operator==(const Index &i2) const
return std::tie(bar, subitem) == std::tie(i2.bar, i2.subitem); return std::tie(bar, subitem) == std::tie(i2.bar, i2.subitem);
} }
BarSeries::BarSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries::BarSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, bool stacked, const QString &categoryName, bool horizontal, bool stacked, const QString &categoryName,
const StatsVariable *valueVariable, std::vector<QString> valueBinNames) : const StatsVariable *valueVariable, std::vector<QString> valueBinNames) :
StatsSeries(chart, xAxis, yAxis), StatsSeries(scene, xAxis, yAxis),
horizontal(horizontal), stacked(stacked), categoryName(categoryName), horizontal(horizontal), stacked(stacked), categoryName(categoryName),
valueVariable(valueVariable), valueBinNames(std::move(valueBinNames)) valueVariable(valueVariable), valueBinNames(std::move(valueBinNames))
{ {
} }
BarSeries::BarSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries::BarSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, const QString &categoryName, bool horizontal, const QString &categoryName,
const std::vector<CountItem> &items) : const std::vector<CountItem> &items) :
BarSeries(chart, xAxis, yAxis, horizontal, false, categoryName, nullptr, std::vector<QString>()) BarSeries(scene, xAxis, yAxis, horizontal, false, categoryName, nullptr, std::vector<QString>())
{ {
for (const CountItem &item: items) { for (const CountItem &item: items) {
StatsOperationResults res; StatsOperationResults res;
@ -50,10 +51,10 @@ BarSeries::BarSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis
} }
} }
BarSeries::BarSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries::BarSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, const QString &categoryName, const StatsVariable *valueVariable, bool horizontal, const QString &categoryName, const StatsVariable *valueVariable,
const std::vector<ValueItem> &items) : const std::vector<ValueItem> &items) :
BarSeries(chart, xAxis, yAxis, horizontal, false, categoryName, valueVariable, std::vector<QString>()) BarSeries(scene, xAxis, yAxis, horizontal, false, categoryName, valueVariable, std::vector<QString>())
{ {
for (const ValueItem &item: items) { for (const ValueItem &item: items) {
add_item(item.lowerBound, item.upperBound, makeSubItems(item.value, item.label), add_item(item.lowerBound, item.upperBound, makeSubItems(item.value, item.label),
@ -61,11 +62,11 @@ BarSeries::BarSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis
} }
} }
BarSeries::BarSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries::BarSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable, bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable,
std::vector<QString> valueBinNames, std::vector<QString> valueBinNames,
const std::vector<MultiItem> &items) : const std::vector<MultiItem> &items) :
BarSeries(chart, xAxis, yAxis, horizontal, stacked, categoryName, valueVariable, std::move(valueBinNames)) BarSeries(scene, xAxis, yAxis, horizontal, stacked, categoryName, valueVariable, std::move(valueBinNames))
{ {
for (const MultiItem &item: items) { for (const MultiItem &item: items) {
StatsOperationResults res; StatsOperationResults res;
@ -85,12 +86,12 @@ BarSeries::~BarSeries()
{ {
} }
BarSeries::BarLabel::BarLabel(QtCharts::QChart *chart, const std::vector<QString> &labels, int bin_nr, int binCount) : BarSeries::BarLabel::BarLabel(QGraphicsScene *scene, const std::vector<QString> &labels, int bin_nr, int binCount) :
totalWidth(0.0), totalHeight(0.0), isOutside(false) totalWidth(0.0), totalHeight(0.0), isOutside(false)
{ {
items.reserve(labels.size()); items.reserve(labels.size());
for (const QString &label: labels) { for (const QString &label: labels) {
items.emplace_back(new QGraphicsSimpleTextItem(chart)); items.emplace_back(createItem<QGraphicsSimpleTextItem>(scene));
items.back()->setText(label); items.back()->setText(label);
items.back()->setZValue(ZValues::seriesLabels); items.back()->setZValue(ZValues::seriesLabels);
QRectF rect = items.back()->boundingRect(); QRectF rect = items.back()->boundingRect();
@ -175,7 +176,7 @@ void BarSeries::BarLabel::updatePosition(bool horizontal, bool center, const QRe
highlight(false, bin_nr, binCount); highlight(false, bin_nr, binCount);
} }
BarSeries::Item::Item(QtCharts::QChart *chart, BarSeries *series, double lowerBound, double upperBound, BarSeries::Item::Item(QGraphicsScene *scene, BarSeries *series, double lowerBound, double upperBound,
std::vector<SubItem> subitemsIn, std::vector<SubItem> subitemsIn,
const QString &binName, const StatsOperationResults &res, int total, const QString &binName, const StatsOperationResults &res, int total,
bool horizontal, bool stacked, int binCount) : bool horizontal, bool stacked, int binCount) :
@ -264,9 +265,9 @@ std::vector<BarSeries::SubItem> BarSeries::makeSubItems(const std::vector<std::p
int bin_nr = 0; int bin_nr = 0;
for (auto &[v, label]: values) { for (auto &[v, label]: values) {
if (v > 0.0) { if (v > 0.0) {
res.push_back({ std::make_unique<QGraphicsRectItem>(chart), {}, from, from + v, bin_nr }); res.push_back({ createItemPtr<QGraphicsRectItem>(scene), {}, from, from + v, bin_nr });
if (!label.empty()) if (!label.empty())
res.back().label = std::make_unique<BarLabel>(chart, label, bin_nr, binCount()); res.back().label = std::make_unique<BarLabel>(scene, label, bin_nr, binCount());
} }
if (stacked) if (stacked)
from += v; from += v;
@ -292,7 +293,7 @@ void BarSeries::add_item(double lowerBound, double upperBound, std::vector<SubIt
// Don't add empty items, as that messes with the "find item under mouse" routine. // Don't add empty items, as that messes with the "find item under mouse" routine.
if (subitems.empty()) if (subitems.empty())
return; return;
items.emplace_back(chart, this, lowerBound, upperBound, std::move(subitems), binName, res, items.emplace_back(scene, this, lowerBound, upperBound, std::move(subitems), binName, res,
total, horizontal, stacked, binCount()); total, horizontal, stacked, binCount());
} }
@ -403,7 +404,7 @@ bool BarSeries::hover(QPointF pos)
Item &item = items[highlighted.bar]; Item &item = items[highlighted.bar];
item.highlight(index.subitem, true, binCount()); item.highlight(index.subitem, true, binCount());
if (!information) if (!information)
information.reset(new InformationBox(chart)); information = createItemPtr<InformationBox>(scene);
information->setText(makeInfo(item, highlighted.subitem), pos); information->setText(makeInfo(item, highlighted.subitem), pos);
} else { } else {
information.reset(); information.reset();

View file

@ -12,9 +12,7 @@
#include <vector> #include <vector>
#include <QGraphicsRectItem> #include <QGraphicsRectItem>
namespace QtCharts { class QGraphicsScene;
class QAbstractAxis;
}
class InformationBox; class InformationBox;
class StatsVariable; class StatsVariable;
@ -49,13 +47,13 @@ public:
// Note: this expects that all items are added with increasing pos // Note: this expects that all items are added with increasing pos
// and that no bar is inside another bar, i.e. lowerBound and upperBound // and that no bar is inside another bar, i.e. lowerBound and upperBound
// are ordered identically. // are ordered identically.
BarSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, const QString &categoryName, bool horizontal, const QString &categoryName,
const std::vector<CountItem> &items); const std::vector<CountItem> &items);
BarSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, const QString &categoryName, const StatsVariable *valueVariable, bool horizontal, const QString &categoryName, const StatsVariable *valueVariable,
const std::vector<ValueItem> &items); const std::vector<ValueItem> &items);
BarSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable, bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable,
std::vector<QString> valueBinNames, std::vector<QString> valueBinNames,
const std::vector<MultiItem> &items); const std::vector<MultiItem> &items);
@ -65,7 +63,7 @@ public:
bool hover(QPointF pos) override; bool hover(QPointF pos) override;
void unhighlight() override; void unhighlight() override;
private: private:
BarSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable, bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable,
std::vector<QString> valueBinNames); std::vector<QString> valueBinNames);
@ -85,7 +83,7 @@ private:
std::vector<std::unique_ptr<QGraphicsSimpleTextItem>> items; std::vector<std::unique_ptr<QGraphicsSimpleTextItem>> items;
double totalWidth, totalHeight; // Size of the item double totalWidth, totalHeight; // Size of the item
bool isOutside; // Is shown outside of bar bool isOutside; // Is shown outside of bar
BarLabel(QtCharts::QChart *chart, const std::vector<QString> &labels, int bin_nr, int binCount); BarLabel(QGraphicsScene *scene, const std::vector<QString> &labels, int bin_nr, int binCount);
void setVisible(bool visible); void setVisible(bool visible);
void updatePosition(bool horizontal, bool center, const QRectF &rect, int bin_nr, int binCount); void updatePosition(bool horizontal, bool center, const QRectF &rect, int bin_nr, int binCount);
void highlight(bool highlight, int bin_nr, int binCount); void highlight(bool highlight, int bin_nr, int binCount);
@ -109,7 +107,7 @@ private:
const QString binName; const QString binName;
StatsOperationResults res; StatsOperationResults res;
int total; int total;
Item(QtCharts::QChart *chart, BarSeries *series, double lowerBound, double upperBound, Item(QGraphicsScene *scene, BarSeries *series, double lowerBound, double upperBound,
std::vector<SubItem> subitems, std::vector<SubItem> subitems,
const QString &binName, const StatsOperationResults &res, int total, bool horizontal, const QString &binName, const StatsOperationResults &res, int total, bool horizontal,
bool stacked, int binCount); bool stacked, int binCount);

View file

@ -2,6 +2,7 @@
#include "boxseries.h" #include "boxseries.h"
#include "informationbox.h" #include "informationbox.h"
#include "statscolors.h" #include "statscolors.h"
#include "statshelper.h"
#include "statstranslations.h" #include "statstranslations.h"
#include "zvalues.h" #include "zvalues.h"
@ -12,9 +13,9 @@
static const double boxWidth = 0.8; // 1.0 = full width of category static const double boxWidth = 0.8; // 1.0 = full width of category
static const int boxBorderWidth = 2; static const int boxBorderWidth = 2;
BoxSeries::BoxSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, BoxSeries::BoxSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
const QString &variable, const QString &unit, int decimals) : const QString &variable, const QString &unit, int decimals) :
StatsSeries(chart, xAxis, yAxis), StatsSeries(scene, xAxis, yAxis),
variable(variable), unit(unit), decimals(decimals), highlighted(-1) variable(variable), unit(unit), decimals(decimals), highlighted(-1)
{ {
} }
@ -23,12 +24,8 @@ BoxSeries::~BoxSeries()
{ {
} }
BoxSeries::Item::Item(QtCharts::QChart *chart, BoxSeries *series, double lowerBound, double upperBound, BoxSeries::Item::Item(QGraphicsScene *scene, BoxSeries *series, double lowerBound, double upperBound,
const StatsQuartiles &q, const QString &binName) : const StatsQuartiles &q, const QString &binName) :
box(chart),
topWhisker(chart), bottomWhisker(chart),
topBar(chart), bottomBar(chart),
center(chart),
lowerBound(lowerBound), upperBound(upperBound), q(q), lowerBound(lowerBound), upperBound(upperBound), q(q),
binName(binName) binName(binName)
{ {
@ -38,6 +35,12 @@ BoxSeries::Item::Item(QtCharts::QChart *chart, BoxSeries *series, double lowerBo
topBar.setZValue(ZValues::series); topBar.setZValue(ZValues::series);
bottomBar.setZValue(ZValues::series); bottomBar.setZValue(ZValues::series);
center.setZValue(ZValues::series); center.setZValue(ZValues::series);
scene->addItem(&box);
scene->addItem(&topWhisker);
scene->addItem(&bottomWhisker);
scene->addItem(&topBar);
scene->addItem(&bottomBar);
scene->addItem(&center);
highlight(false); highlight(false);
updatePosition(series); updatePosition(series);
} }
@ -89,7 +92,7 @@ void BoxSeries::Item::updatePosition(BoxSeries *series)
void BoxSeries::append(double lowerBound, double upperBound, const StatsQuartiles &q, const QString &binName) void BoxSeries::append(double lowerBound, double upperBound, const StatsQuartiles &q, const QString &binName)
{ {
items.emplace_back(new Item(chart, this, lowerBound, upperBound, q, binName)); items.emplace_back(new Item(scene, this, lowerBound, upperBound, q, binName));
} }
void BoxSeries::updatePositions() void BoxSeries::updatePositions()
@ -147,7 +150,7 @@ bool BoxSeries::hover(QPointF pos)
Item &item = *items[highlighted]; Item &item = *items[highlighted];
item.highlight(true); item.highlight(true);
if (!information) if (!information)
information.reset(new InformationBox(chart)); information = createItemPtr<InformationBox>(scene);
information->setText(formatInformation(item), pos); information->setText(formatInformation(item), pos);
} else { } else {
information.reset(); information.reset();

View file

@ -14,10 +14,11 @@
#include <QGraphicsRectItem> #include <QGraphicsRectItem>
class InformationBox; class InformationBox;
class QGraphicsScene;
class BoxSeries : public StatsSeries { class BoxSeries : public StatsSeries {
public: public:
BoxSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, BoxSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
const QString &variable, const QString &unit, int decimals); const QString &variable, const QString &unit, int decimals);
~BoxSeries(); ~BoxSeries();
@ -44,7 +45,7 @@ private:
double lowerBound, upperBound; double lowerBound, upperBound;
StatsQuartiles q; StatsQuartiles q;
QString binName; QString binName;
Item(QtCharts::QChart *chart, BoxSeries *series, double lowerBound, double upperBound, const StatsQuartiles &q, const QString &binName); Item(QGraphicsScene *scene, BoxSeries *series, double lowerBound, double upperBound, const StatsQuartiles &q, const QString &binName);
void updatePosition(BoxSeries *series); void updatePosition(BoxSeries *series);
void highlight(bool highlight); void highlight(bool highlight);
}; };

View file

@ -4,6 +4,7 @@
#include <QChart> #include <QChart>
#include <QFontMetrics> #include <QFontMetrics>
#include <QGraphicsScene>
static const QColor informationBorderColor(Qt::black); static const QColor informationBorderColor(Qt::black);
static const QColor informationColor(0xff, 0xff, 0x00, 192); // Note: fourth argument is opacity static const QColor informationColor(0xff, 0xff, 0x00, 192); // Note: fourth argument is opacity
@ -11,7 +12,7 @@ static const int informationBorder = 2;
static const double informationBorderRadius = 4.0; // Radius of rounded corners 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 static const int distanceFromPointer = 10; // Distance to place box from mouse pointer or scatter item
InformationBox::InformationBox(QtCharts::QChart *chart) : RoundRectItem(informationBorderRadius, chart), chart(chart) InformationBox::InformationBox() : RoundRectItem(informationBorderRadius, nullptr)
{ {
setPen(QPen(informationBorderColor, informationBorder)); setPen(QPen(informationBorderColor, informationBorder));
setBrush(informationColor); setBrush(informationColor);
@ -37,7 +38,7 @@ void InformationBox::setText(const std::vector<QString> &text, QPointF pos)
void InformationBox::setPos(QPointF pos) void InformationBox::setPos(QPointF pos)
{ {
QRectF plotArea = chart->plotArea(); QRectF plotArea = scene()->sceneRect();
double x = pos.x() + distanceFromPointer; double x = pos.x() + distanceFromPointer;
if (x + width >= plotArea.right()) { if (x + width >= plotArea.right()) {
@ -79,6 +80,6 @@ void InformationBox::addLine(const QString &s)
int InformationBox::recommendedMaxLines() const int InformationBox::recommendedMaxLines() const
{ {
QFontMetrics fm(font); QFontMetrics fm(font);
int maxHeight = static_cast<int>(chart->plotArea().height()); int maxHeight = static_cast<int>(scene()->sceneRect().height());
return maxHeight * 2 / fm.height() / 3; return maxHeight * 2 / fm.height() / 3;
} }

View file

@ -10,19 +10,16 @@
#include <memory> #include <memory>
#include <QFont> #include <QFont>
namespace QtCharts {
class QChart;
}
struct dive; struct dive;
class QGraphicsScene;
// Information window showing data of highlighted dive // Information window showing data of highlighted dive
struct InformationBox : RoundRectItem { struct InformationBox : RoundRectItem {
InformationBox(QtCharts::QChart *chart); InformationBox();
void setText(const std::vector<QString> &text, QPointF pos); void setText(const std::vector<QString> &text, QPointF pos);
void setPos(QPointF pos); void setPos(QPointF pos);
int recommendedMaxLines() const; int recommendedMaxLines() const;
private: private:
QtCharts::QChart *chart;
QFont font; // For future specialization. QFont font; // For future specialization.
double width, height; double width, height;
void addLine(const QString &s); void addLine(const QString &s);

View file

@ -4,8 +4,8 @@
#include "zvalues.h" #include "zvalues.h"
#include <QFontMetrics> #include <QFontMetrics>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent> #include <QGraphicsSceneMouseEvent>
#include <QGraphicsWidget>
#include <QPen> #include <QPen>
static const double legendBorderSize = 2.0; static const double legendBorderSize = 2.0;
@ -16,8 +16,8 @@ static const double legendInternalBorderSize = 2.0;
static const QColor legendColor(0x00, 0x8e, 0xcc, 192); // Note: fourth argument is opacity static const QColor legendColor(0x00, 0x8e, 0xcc, 192); // Note: fourth argument is opacity
static const QColor legendBorderColor(Qt::black); static const QColor legendBorderColor(Qt::black);
Legend::Legend(QGraphicsWidget *chart, const std::vector<QString> &names) : Legend::Legend(const std::vector<QString> &names) :
RoundRectItem(legendBoxBorderRadius, chart), chart(chart), RoundRectItem(legendBoxBorderRadius),
displayedItems(0), width(0.0), height(0.0) displayedItems(0), width(0.0), height(0.0)
{ {
setZValue(ZValues::legend); setZValue(ZValues::legend);
@ -40,8 +40,6 @@ Legend::Legend(QGraphicsWidget *chart, const std::vector<QString> &names) :
} }
setPen(QPen(legendBorderColor, legendBorderSize)); setPen(QPen(legendBorderColor, legendBorderSize));
setBrush(QBrush(legendColor)); setBrush(QBrush(legendColor));
resize(); // Draw initial legend
} }
Legend::Entry::Entry(const QString &name, int idx, int numBins, QGraphicsItem *parent) : Legend::Entry::Entry(const QString &name, int idx, int numBins, QGraphicsItem *parent) :
@ -70,7 +68,7 @@ void Legend::resize()
if (entries.empty()) if (entries.empty())
return hide(); return hide();
QSizeF size = chart->size(); QSizeF size = scene()->sceneRect().size();
// Silly heuristics: make the legend at most half as high and half as wide as the chart. // Silly heuristics: make the legend at most half as high and half as wide as the chart.
// Not sure if that makes sense - this might need some optimization. // Not sure if that makes sense - this might need some optimization.
@ -110,7 +108,7 @@ void Legend::updatePosition()
if (displayedItems <= 0) if (displayedItems <= 0)
return hide(); return hide();
// For now, place the legend in the top right corner. // For now, place the legend in the top right corner.
QPointF pos(chart->size().width() - width - 10.0, 10.0); QPointF pos(scene()->sceneRect().width() - width - 10.0, 10.0);
setRect(QRectF(pos, QSizeF(width, height))); setRect(QRectF(pos, QSizeF(width, height)));
for (int i = 0; i < displayedItems; ++i) { for (int i = 0; i < displayedItems; ++i) {
QPointF itemPos = pos + entries[i].pos; QPointF itemPos = pos + entries[i].pos;

View file

@ -8,11 +8,12 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
class QGraphicsScene;
class QGraphicsSceneMouseEvent; class QGraphicsSceneMouseEvent;
class Legend : public RoundRectItem { class Legend : public RoundRectItem {
public: public:
Legend(QGraphicsWidget *chart, const std::vector<QString> &names); Legend(const std::vector<QString> &names);
void hover(QPointF pos); void hover(QPointF pos);
void resize(); // called when the chart size changes. void resize(); // called when the chart size changes.
private: private:
@ -24,7 +25,6 @@ private:
double width; double width;
Entry(const QString &name, int idx, int numBins, QGraphicsItem *parent); Entry(const QString &name, int idx, int numBins, QGraphicsItem *parent);
}; };
QGraphicsWidget *chart;
int displayedItems; int displayedItems;
double width; double width;
double height; double height;

View file

@ -2,6 +2,7 @@
#include "pieseries.h" #include "pieseries.h"
#include "informationbox.h" #include "informationbox.h"
#include "statscolors.h" #include "statscolors.h"
#include "statshelper.h"
#include "statstranslations.h" #include "statstranslations.h"
#include "zvalues.h" #include "zvalues.h"
@ -16,9 +17,9 @@ static const double pieBorderWidth = 1.0;
static const double innerLabelRadius = 0.75; // 1.0 = at outer border of pie static const double innerLabelRadius = 0.75; // 1.0 = at outer border of pie
static const double outerLabelRadius = 1.01; // 1.0 = at outer border of pie static const double outerLabelRadius = 1.01; // 1.0 = at outer border of pie
PieSeries::Item::Item(QtCharts::QChart *chart, const QString &name, int from, int count, int totalCount, PieSeries::Item::Item(QGraphicsScene *scene, const QString &name, int from, int count, int totalCount,
int bin_nr, int numBins, bool labels) : int bin_nr, int numBins, bool labels) :
item(new QGraphicsEllipseItem(chart)), item(createItemPtr<QGraphicsEllipseItem>(scene)),
name(name), name(name),
count(count) count(count)
{ {
@ -38,10 +39,10 @@ PieSeries::Item::Item(QtCharts::QChart *chart, const QString &name, int from, in
if (labels) { if (labels) {
double percentage = count * 100.0 / totalCount; double percentage = count * 100.0 / totalCount;
QString innerLabelText = QStringLiteral("%1\%").arg(loc.toString(percentage, 'f', 1)); QString innerLabelText = QStringLiteral("%1\%").arg(loc.toString(percentage, 'f', 1));
innerLabel.reset(new QGraphicsSimpleTextItem(innerLabelText, chart)); innerLabel = createItemPtr<QGraphicsSimpleTextItem>(scene, innerLabelText);
innerLabel->setZValue(ZValues::seriesLabels); innerLabel->setZValue(ZValues::seriesLabels);
outerLabel.reset(new QGraphicsSimpleTextItem(name, chart)); outerLabel = createItemPtr<QGraphicsSimpleTextItem>(scene, name);
outerLabel->setBrush(QBrush(darkLabelColor)); outerLabel->setBrush(QBrush(darkLabelColor));
outerLabel->setZValue(ZValues::seriesLabels); outerLabel->setZValue(ZValues::seriesLabels);
} }
@ -85,9 +86,9 @@ void PieSeries::Item::highlight(int bin_nr, bool highlight, int numBins)
} }
} }
PieSeries::PieSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName, PieSeries::PieSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName,
const std::vector<std::pair<QString, int>> &data, bool keepOrder, bool labels) : const std::vector<std::pair<QString, int>> &data, bool keepOrder, bool labels) :
StatsSeries(chart, xAxis, yAxis), StatsSeries(scene, xAxis, yAxis),
categoryName(categoryName), categoryName(categoryName),
highlighted(-1) highlighted(-1)
{ {
@ -147,7 +148,7 @@ PieSeries::PieSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis
int act = 0; int act = 0;
for (auto it2 = sorted.begin(); it2 != it; ++it2) { for (auto it2 = sorted.begin(); it2 != it; ++it2) {
int count = data[*it2].second; int count = data[*it2].second;
items.emplace_back(chart, data[*it2].first, act, count, totalCount, (int)items.size(), numBins, labels); items.emplace_back(scene, data[*it2].first, act, count, totalCount, (int)items.size(), numBins, labels);
act += count; act += count;
} }
@ -157,7 +158,7 @@ PieSeries::PieSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis
for (auto it2 = it; it2 != sorted.end(); ++it2) for (auto it2 = it; it2 != sorted.end(); ++it2)
other.push_back({ data[*it2].first, data[*it2].second }); other.push_back({ data[*it2].first, data[*it2].second });
QString name = StatsTranslations::tr("other (%1 items)").arg(other.size()); QString name = StatsTranslations::tr("other (%1 items)").arg(other.size());
items.emplace_back(chart, name, act, totalCount - act, totalCount, (int)items.size(), numBins, labels); items.emplace_back(scene, name, act, totalCount - act, totalCount, (int)items.size(), numBins, labels);
} }
} }
@ -167,7 +168,7 @@ PieSeries::~PieSeries()
void PieSeries::updatePositions() void PieSeries::updatePositions()
{ {
QRectF plotRect = chart->plotArea(); QRectF plotRect = scene->sceneRect();
center = plotRect.center(); center = plotRect.center();
radius = std::min(plotRect.width(), plotRect.height()) * pieSize / 2.0; radius = std::min(plotRect.width(), plotRect.height()) * pieSize / 2.0;
QRectF rect(center.x() - radius, center.y() - radius, 2.0 * radius, 2.0 * radius); QRectF rect(center.x() - radius, center.y() - radius, 2.0 * radius, 2.0 * radius);
@ -246,7 +247,7 @@ bool PieSeries::hover(QPointF pos)
if (highlighted >= 0 && highlighted < (int)items.size()) { if (highlighted >= 0 && highlighted < (int)items.size()) {
items[highlighted].highlight(highlighted, true, (int)items.size()); items[highlighted].highlight(highlighted, true, (int)items.size());
if (!information) if (!information)
information.reset(new InformationBox(chart)); information = createItemPtr<InformationBox>(scene);
information->setText(makeInfo(highlighted), pos); information->setText(makeInfo(highlighted), pos);
} else { } else {
information.reset(); information.reset();

View file

@ -11,14 +11,16 @@
class InformationBox; class InformationBox;
class QGraphicsEllipseItem; class QGraphicsEllipseItem;
class QGraphicsScene;
class QGraphicsSimpleTextItem; class QGraphicsSimpleTextItem;
class QRectF;
class PieSeries : public StatsSeries { class PieSeries : public StatsSeries {
public: public:
// The pie series is initialized with (name, count) pairs. // The pie series is initialized with (name, count) pairs.
// If keepOrder is false, bins will be sorted by size, otherwise the sorting // If keepOrder is false, bins will be sorted by size, otherwise the sorting
// of the shown bins will be retained. Small bins are omitted for clarity. // of the shown bins will be retained. Small bins are omitted for clarity.
PieSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName, PieSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName,
const std::vector<std::pair<QString, int>> &data, bool keepOrder, bool labels); const std::vector<std::pair<QString, int>> &data, bool keepOrder, bool labels);
~PieSeries(); ~PieSeries();
@ -42,7 +44,7 @@ private:
double angleTo; // In fraction of total double angleTo; // In fraction of total
int count; int count;
QPointF innerLabelPos, outerLabelPos; // With respect to a (-1, -1)-(1, 1) rectangle. QPointF innerLabelPos, outerLabelPos; // With respect to a (-1, -1)-(1, 1) rectangle.
Item(QtCharts::QChart *chart, const QString &name, int from, int count, int totalCount, Item(QGraphicsScene *scene, const QString &name, int from, int count, int totalCount,
int bin_nr, int numBins, bool labels); int bin_nr, int numBins, bool labels);
void updatePositions(const QRectF &rect, const QPointF &center, double radius); void updatePositions(const QRectF &rect, const QPointF &center, double radius);
void highlight(int bin_nr, bool highlight, int numBins); void highlight(int bin_nr, bool highlight, int numBins);

View file

@ -1,8 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
import QtQuick 2.0
import QtCharts 2.0
ChartView {
antialiasing: true
localizeNumbers: true
}

View file

@ -2,6 +2,7 @@
#include "scatterseries.h" #include "scatterseries.h"
#include "informationbox.h" #include "informationbox.h"
#include "statscolors.h" #include "statscolors.h"
#include "statshelper.h"
#include "statstranslations.h" #include "statstranslations.h"
#include "statsvariables.h" #include "statsvariables.h"
#include "zvalues.h" #include "zvalues.h"
@ -16,9 +17,9 @@
static const int scatterItemDiameter = 10; static const int scatterItemDiameter = 10;
static const int scatterItemBorder = 1; static const int scatterItemBorder = 1;
ScatterSeries::ScatterSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, ScatterSeries::ScatterSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
const StatsVariable &varX, const StatsVariable &varY) : const StatsVariable &varX, const StatsVariable &varY) :
StatsSeries(chart, xAxis, yAxis), StatsSeries(scene, xAxis, yAxis),
varX(varX), varY(varY) varX(varX), varY(varY)
{ {
} }
@ -58,8 +59,8 @@ static const QPixmap &scatterPixmap(bool highlight)
return highlight ? *scatterPixmapHighlightedPtr : *scatterPixmapPtr; return highlight ? *scatterPixmapHighlightedPtr : *scatterPixmapPtr;
} }
ScatterSeries::Item::Item(QtCharts::QChart *chart, ScatterSeries *series, dive *d, double pos, double value) : ScatterSeries::Item::Item(QGraphicsScene *scene, ScatterSeries *series, dive *d, double pos, double value) :
item(new QGraphicsPixmapItem(scatterPixmap(false), chart)), item(createItemPtr<QGraphicsPixmapItem>(scene, scatterPixmap(false))),
d(d), d(d),
pos(pos), pos(pos),
value(value) value(value)
@ -82,7 +83,7 @@ void ScatterSeries::Item::highlight(bool highlight)
void ScatterSeries::append(dive *d, double pos, double value) void ScatterSeries::append(dive *d, double pos, double value)
{ {
items.emplace_back(chart, this, d, pos, value); items.emplace_back(scene, this, d, pos, value);
} }
void ScatterSeries::updatePositions() void ScatterSeries::updatePositions()
@ -173,7 +174,7 @@ bool ScatterSeries::hover(QPointF pos)
return false; return false;
} else { } else {
if (!information) if (!information)
information.reset(new InformationBox(chart)); information = createItemPtr<InformationBox>(scene);
std::vector<QString> text; std::vector<QString> text;
text.reserve(highlighted.size() * 5); text.reserve(highlighted.size() * 5);

View file

@ -11,13 +11,14 @@
#include <QGraphicsRectItem> #include <QGraphicsRectItem>
class QGraphicsPixmapItem; class QGraphicsPixmapItem;
class QGraphicsScene;
class InformationBox; class InformationBox;
struct StatsVariable; struct StatsVariable;
struct dive; struct dive;
class ScatterSeries : public StatsSeries { class ScatterSeries : public StatsSeries {
public: public:
ScatterSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, ScatterSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis,
const StatsVariable &varX, const StatsVariable &varY); const StatsVariable &varX, const StatsVariable &varY);
~ScatterSeries(); ~ScatterSeries();
@ -36,7 +37,7 @@ private:
std::unique_ptr<QGraphicsPixmapItem> item; std::unique_ptr<QGraphicsPixmapItem> item;
dive *d; dive *d;
double pos, value; double pos, value;
Item(QtCharts::QChart *chart, ScatterSeries *series, dive *d, double pos, double value); Item(QGraphicsScene *scene, ScatterSeries *series, dive *d, double pos, double value);
void updatePosition(ScatterSeries *series); void updatePosition(ScatterSeries *series);
void highlight(bool highlight); void highlight(bool highlight);
}; };

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include "statsaxis.h" #include "statsaxis.h"
#include "statscolors.h" #include "statscolors.h"
#include "statshelper.h"
#include "statstranslations.h" #include "statstranslations.h"
#include "statsvariables.h" #include "statsvariables.h"
#include "zvalues.h" #include "zvalues.h"
@ -23,9 +24,8 @@ 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 axisTitleSpaceHorizontal = 2.0; // Space between labels and title
static const double axisTitleSpaceVertical = 2.0; // Space between labels and title static const double axisTitleSpaceVertical = 2.0; // Space between labels and title
StatsAxis::StatsAxis(QtCharts::QChart *chart, const QString &titleIn, bool horizontal, bool labelsBetweenTicks) : StatsAxis::StatsAxis(const QString &titleIn, bool horizontal, bool labelsBetweenTicks) :
QGraphicsLineItem(chart), horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks),
chart(chart), horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks),
size(1.0), zeroOnScreen(0.0), min(0.0), max(1.0), labelWidth(0.0) size(1.0), zeroOnScreen(0.0), min(0.0), max(1.0), labelWidth(0.0)
{ {
// use a Light version of the application fond for both labels and title // use a Light version of the application fond for both labels and title
@ -35,7 +35,7 @@ StatsAxis::StatsAxis(QtCharts::QChart *chart, const QString &titleIn, bool horiz
setPen(QPen(axisColor, axisWidth)); setPen(QPen(axisColor, axisWidth));
setZValue(ZValues::axes); setZValue(ZValues::axes);
if (!titleIn.isEmpty()) { if (!titleIn.isEmpty()) {
title = std::make_unique<QGraphicsSimpleTextItem>(titleIn, chart); title.reset(new QGraphicsSimpleTextItem(titleIn, this));
title->setFont(titleFont); title->setFont(titleFont);
title->setBrush(darkLabelColor); title->setBrush(darkLabelColor);
if (!horizontal) if (!horizontal)
@ -114,8 +114,8 @@ double StatsAxis::height() const
(labelsBetweenTicks ? 0.0 : axisTickSizeHorizontal); (labelsBetweenTicks ? 0.0 : axisTickSizeHorizontal);
} }
StatsAxis::Label::Label(const QString &name, double pos, QtCharts::QChart *chart, const QFont &font) : StatsAxis::Label::Label(const QString &name, double pos, QGraphicsScene *scene, const QFont &font) :
label(new QGraphicsSimpleTextItem(name, chart)), label(createItem<QGraphicsSimpleTextItem>(scene, name)),
pos(pos) pos(pos)
{ {
label->setBrush(QBrush(darkLabelColor)); label->setBrush(QBrush(darkLabelColor));
@ -125,11 +125,11 @@ StatsAxis::Label::Label(const QString &name, double pos, QtCharts::QChart *chart
void StatsAxis::addLabel(const QString &label, double pos) void StatsAxis::addLabel(const QString &label, double pos)
{ {
labels.emplace_back(label, pos, chart, labelFont); labels.emplace_back(label, pos, scene(), labelFont);
} }
StatsAxis::Tick::Tick(double pos, QtCharts::QChart *chart) : StatsAxis::Tick::Tick(double pos, QGraphicsScene *scene) :
item(new QGraphicsLineItem(chart)), item(createItemPtr<QGraphicsLineItem>(scene)),
pos(pos) pos(pos)
{ {
item->setPen(QPen(axisColor, axisTickWidth)); item->setPen(QPen(axisColor, axisTickWidth));
@ -138,7 +138,7 @@ StatsAxis::Tick::Tick(double pos, QtCharts::QChart *chart) :
void StatsAxis::addTick(double pos) void StatsAxis::addTick(double pos)
{ {
ticks.emplace_back(pos, chart); ticks.emplace_back(pos, scene());
} }
std::vector<double> StatsAxis::ticksPositions() const std::vector<double> StatsAxis::ticksPositions() const
@ -220,8 +220,8 @@ void StatsAxis::setPos(QPointF pos)
} }
} }
ValueAxis::ValueAxis(QtCharts::QChart *chart, const QString &title, double min, double max, int decimals, bool horizontal) : ValueAxis::ValueAxis(const QString &title, double min, double max, int decimals, bool horizontal) :
StatsAxis(chart, title, horizontal, false), StatsAxis(title, horizontal, false),
min(min), max(max), decimals(decimals) min(min), max(max), decimals(decimals)
{ {
} }
@ -275,8 +275,8 @@ void ValueAxis::updateLabels()
} }
} }
CountAxis::CountAxis(QtCharts::QChart *chart, const QString &title, int count, bool horizontal) : CountAxis::CountAxis(const QString &title, int count, bool horizontal) :
ValueAxis(chart, title, 0.0, (double)count, 0, horizontal), ValueAxis(title, 0.0, (double)count, 0, horizontal),
count(count) count(count)
{ {
} }
@ -328,27 +328,31 @@ void CountAxis::updateLabels()
} }
} }
CategoryAxis::CategoryAxis(QtCharts::QChart *chart, const QString &title, const std::vector<QString> &labelsIn, bool horizontal) : CategoryAxis::CategoryAxis(const QString &title, const std::vector<QString> &labels, bool horizontal) :
StatsAxis(chart, title, horizontal, true) StatsAxis(title, horizontal, true),
labelsText(labels)
{ {
labels.reserve(labelsIn.size()); setRange(-0.5, static_cast<double>(labels.size()) + 0.5);
ticks.reserve(labelsIn.size() + 1);
double pos = 0.0;
addTick(-0.5);
for (const QString &s: labelsIn) {
addLabel(s, pos);
addTick(pos + 0.5);
pos += 1.0;
}
setRange(-0.5, static_cast<double>(labelsIn.size()) - 0.5);
} }
void CategoryAxis::updateLabels() void CategoryAxis::updateLabels()
{ {
// TODO: paint ellipses if space too small
labels.clear();
ticks.clear();
labels.reserve(labelsText.size());
ticks.reserve(labelsText.size() + 1);
double pos = 0.0;
addTick(-0.5);
for (const QString &s: labelsText) {
addLabel(s, pos);
addTick(pos + 0.5);
pos += 1.0;
}
} }
HistogramAxis::HistogramAxis(QtCharts::QChart *chart, const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) : HistogramAxis::HistogramAxis(const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) :
StatsAxis(chart, title, horizontal, false), StatsAxis(title, horizontal, false),
bin_values(std::move(bins)) bin_values(std::move(bins))
{ {
if (bin_values.size() < 2) // Less than two makes no sense -> there must be at least one category if (bin_values.size() < 2) // Less than two makes no sense -> there must be at least one category
@ -545,7 +549,7 @@ static std::vector<HistogramAxisEntry> timeRangeToBins(double from, double to)
return res; return res;
} }
DateAxis::DateAxis(QtCharts::QChart *chart, const QString &title, double from, double to, bool horizontal) : DateAxis::DateAxis(const QString &title, double from, double to, bool horizontal) :
HistogramAxis(chart, title, timeRangeToBins(from, to), horizontal) HistogramAxis(title, timeRangeToBins(from, to), horizontal)
{ {
} }

View file

@ -11,11 +11,9 @@
#include <QGraphicsLineItem> #include <QGraphicsLineItem>
#include <QValueAxis> #include <QValueAxis>
namespace QtCharts { class QGraphicsScene;
class QChart;
}
class StatsAxis : QGraphicsLineItem { class StatsAxis : public QGraphicsLineItem {
public: public:
virtual ~StatsAxis(); virtual ~StatsAxis();
// Returns minimum and maximum of shown range, not of data points. // Returns minimum and maximum of shown range, not of data points.
@ -34,13 +32,12 @@ public:
std::vector<double> ticksPositions() const; // Positions in screen coordinates std::vector<double> ticksPositions() const; // Positions in screen coordinates
protected: protected:
StatsAxis(QtCharts::QChart *chart, const QString &title, bool horizontal, bool labelsBetweenTicks); StatsAxis(const QString &title, bool horizontal, bool labelsBetweenTicks);
QtCharts::QChart *chart;
struct Label { struct Label {
std::unique_ptr<QGraphicsSimpleTextItem> label; std::unique_ptr<QGraphicsSimpleTextItem> label;
double pos; double pos;
Label(const QString &name, double pos, QtCharts::QChart *chart, const QFont &font); Label(const QString &name, double pos, QGraphicsScene *scene, const QFont &font);
}; };
std::vector<Label> labels; std::vector<Label> labels;
void addLabel(const QString &label, double pos); void addLabel(const QString &label, double pos);
@ -49,7 +46,7 @@ protected:
struct Tick { struct Tick {
std::unique_ptr<QGraphicsLineItem> item; std::unique_ptr<QGraphicsLineItem> item;
double pos; double pos;
Tick(double pos, QtCharts::QChart *chart); Tick(double pos, QGraphicsScene *scene);
}; };
std::vector<Tick> ticks; std::vector<Tick> ticks;
void addTick(double pos); void addTick(double pos);
@ -70,7 +67,7 @@ private:
class ValueAxis : public StatsAxis { class ValueAxis : public StatsAxis {
public: public:
ValueAxis(QtCharts::QChart *chart, const QString &title, double min, double max, int decimals, bool horizontal); ValueAxis(const QString &title, double min, double max, int decimals, bool horizontal);
private: private:
double min, max; double min, max;
int decimals; int decimals;
@ -79,7 +76,7 @@ private:
class CountAxis : public ValueAxis { class CountAxis : public ValueAxis {
public: public:
CountAxis(QtCharts::QChart *chart, const QString &title, int count, bool horizontal); CountAxis(const QString &title, int count, bool horizontal);
private: private:
int count; int count;
void updateLabels() override; void updateLabels() override;
@ -87,8 +84,9 @@ private:
class CategoryAxis : public StatsAxis { class CategoryAxis : public StatsAxis {
public: public:
CategoryAxis(QtCharts::QChart *chart, const QString &title, const std::vector<QString> &labels, bool horizontal); CategoryAxis(const QString &title, const std::vector<QString> &labels, bool horizontal);
private: private:
std::vector<QString> labelsText;
void updateLabels(); void updateLabels();
}; };
@ -100,7 +98,7 @@ struct HistogramAxisEntry {
class HistogramAxis : public StatsAxis { class HistogramAxis : public StatsAxis {
public: public:
HistogramAxis(QtCharts::QChart *chart, const QString &title, std::vector<HistogramAxisEntry> bin_values, bool horizontal); HistogramAxis(const QString &title, std::vector<HistogramAxisEntry> bin_values, bool horizontal);
private: private:
void updateLabels() override; void updateLabels() override;
std::vector<HistogramAxisEntry> bin_values; std::vector<HistogramAxisEntry> bin_values;
@ -109,7 +107,7 @@ private:
class DateAxis : public HistogramAxis { class DateAxis : public HistogramAxis {
public: public:
DateAxis(QtCharts::QChart *chart, const QString &title, double from, double to, bool horizontal); DateAxis(const QString &title, double from, double to, bool horizontal);
}; };
#endif #endif

View file

@ -5,6 +5,7 @@
#include <QColor> #include <QColor>
inline const QColor backgroundColor(Qt::white);
inline const QColor fillColor(0x44, 0x76, 0xaa); inline const QColor fillColor(0x44, 0x76, 0xaa);
inline const QColor borderColor(0x66, 0xb2, 0xff); inline const QColor borderColor(0x66, 0xb2, 0xff);
inline const QColor highlightedColor(Qt::yellow); inline const QColor highlightedColor(Qt::yellow);

View file

@ -2,6 +2,7 @@
#include "statsgrid.h" #include "statsgrid.h"
#include "statsaxis.h" #include "statsaxis.h"
#include "statscolors.h" #include "statscolors.h"
#include "statshelper.h"
#include "zvalues.h" #include "zvalues.h"
#include <QChart> #include <QChart>
@ -10,8 +11,8 @@
static const double gridWidth = 1.0; static const double gridWidth = 1.0;
static const Qt::PenStyle gridStyle = Qt::SolidLine; static const Qt::PenStyle gridStyle = Qt::SolidLine;
StatsGrid::StatsGrid(QtCharts::QChart *chart, const StatsAxis &xAxis, const StatsAxis &yAxis) StatsGrid::StatsGrid(QGraphicsScene *scene, const StatsAxis &xAxis, const StatsAxis &yAxis)
: chart(chart), xAxis(xAxis), yAxis(yAxis) : scene(scene), xAxis(xAxis), yAxis(yAxis)
{ {
} }
@ -24,12 +25,12 @@ void StatsGrid::updatePositions()
return; return;
for (double x: xtics) { for (double x: xtics) {
lines.emplace_back(new QGraphicsLineItem(x, ytics.front(), x, ytics.back(), chart)); lines.emplace_back(createItem<QGraphicsLineItem>(scene, x, ytics.front(), x, ytics.back()));
lines.back()->setPen(QPen(gridColor, gridWidth, gridStyle)); lines.back()->setPen(QPen(gridColor, gridWidth, gridStyle));
lines.back()->setZValue(ZValues::grid); lines.back()->setZValue(ZValues::grid);
} }
for (double y: ytics) { for (double y: ytics) {
lines.emplace_back(new QGraphicsLineItem(xtics.front(), y, xtics.back(), y, chart)); lines.emplace_back(createItem<QGraphicsLineItem>(scene, xtics.front(), y, xtics.back(), y));
lines.back()->setPen(QPen(gridColor, gridWidth, gridStyle)); lines.back()->setPen(QPen(gridColor, gridWidth, gridStyle));
lines.back()->setZValue(ZValues::grid); lines.back()->setZValue(ZValues::grid);
} }

View file

@ -7,16 +7,14 @@
#include <QGraphicsLineItem> #include <QGraphicsLineItem>
class StatsAxis; class StatsAxis;
namespace QtCharts { class QGraphicsScene;
class QChart;
};
class StatsGrid { class StatsGrid {
public: public:
StatsGrid(QtCharts::QChart *chart, const StatsAxis &xAxis, const StatsAxis &yAxis); StatsGrid(QGraphicsScene *scene, const StatsAxis &xAxis, const StatsAxis &yAxis);
void updatePositions(); void updatePositions();
private: private:
QtCharts::QChart *chart; QGraphicsScene *scene;
const StatsAxis &xAxis, &yAxis; const StatsAxis &xAxis, &yAxis;
std::vector<std::unique_ptr<QGraphicsLineItem>> lines; std::vector<std::unique_ptr<QGraphicsLineItem>> lines;
}; };

25
stats/statshelper.h Normal file
View file

@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0
// Helper functions to render the stats. Currently only
// contains a small template to create scene-items. This
// is for historical reasons to ease transition from QtCharts
// and might be removed.
#ifndef STATSHELPER_H
#include <memory>
#include <QGraphicsScene>
template <typename T, class... Args>
T *createItem(QGraphicsScene *scene, Args&&... args)
{
T *res = new T(std::forward<Args>(args)...);
scene->addItem(res);
return res;
}
template <typename T, class... Args>
std::unique_ptr<T> createItemPtr(QGraphicsScene *scene, Args&&... args)
{
return std::unique_ptr<T>(createItem<T>(scene, std::forward<Args>(args)...));
}
#endif

View file

@ -2,8 +2,8 @@
#include "statsseries.h" #include "statsseries.h"
#include "statsaxis.h" #include "statsaxis.h"
StatsSeries::StatsSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) : StatsSeries::StatsSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) :
chart(chart), xAxis(xAxis), yAxis(yAxis) scene(scene), xAxis(xAxis), yAxis(yAxis)
{ {
} }

View file

@ -4,26 +4,20 @@
#ifndef STATS_SERIES_H #ifndef STATS_SERIES_H
#define STATS_SERIES_H #define STATS_SERIES_H
#include <QScatterSeries> #include <QPointF>
namespace QtCharts { class QGraphicsScene;
class QChart;
}
class StatsAxis; class StatsAxis;
// We derive from a proper scatter series to get access to the map-to class StatsSeries {
// and map-from coordinates calls. But we don't use any of its functionality.
// This should be removed in due course.
class StatsSeries : public QtCharts::QScatterSeries {
public: public:
StatsSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis); StatsSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis);
virtual ~StatsSeries(); virtual ~StatsSeries();
virtual void updatePositions() = 0; // Called if chart geometry changes. virtual void updatePositions() = 0; // Called if chart geometry changes.
virtual bool hover(QPointF pos) = 0; // Called on mouse movement. Return true if an item of this series is highlighted. virtual bool hover(QPointF pos) = 0; // Called on mouse movement. Return true if an item of this series is highlighted.
virtual void unhighlight() = 0; // Unhighlight any highlighted item. virtual void unhighlight() = 0; // Unhighlight any highlighted item.
protected: protected:
QtCharts::QChart *chart; QGraphicsScene *scene;
StatsAxis *xAxis, *yAxis; // May be zero for charts without axes (pie charts). StatsAxis *xAxis, *yAxis; // May be zero for charts without axes (pie charts).
QPointF toScreen(QPointF p); QPointF toScreen(QPointF p);
}; };

View file

@ -6,7 +6,9 @@
#include "pieseries.h" #include "pieseries.h"
#include "scatterseries.h" #include "scatterseries.h"
#include "statsaxis.h" #include "statsaxis.h"
#include "statscolors.h"
#include "statsgrid.h" #include "statsgrid.h"
#include "statshelper.h"
#include "statsstate.h" #include "statsstate.h"
#include "statstranslations.h" #include "statstranslations.h"
#include "statsvariables.h" #include "statsvariables.h"
@ -15,12 +17,35 @@
#include "core/subsurface-qt/divelistnotifier.h" #include "core/subsurface-qt/divelistnotifier.h"
#include <cmath> #include <cmath>
#include <QQuickItem> #include <QGraphicsScene>
#include <QAbstractSeries>
#include <QChart>
#include <QGraphicsSceneHoverEvent> #include <QGraphicsSceneHoverEvent>
#include <QGraphicsSimpleTextItem> #include <QGraphicsSimpleTextItem>
#include <QLocale> #include <QQuickItem>
#include <QQuickWindow>
#include <QSGImageNode>
#include <QSGTexture>
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.
QSGImageNode *n = static_cast<QSGImageNode *>(oldNode);
if (!n)
n = window()->createImageNode();
QRectF rect = boundingRect();
if (plotRect != rect) {
plotRect = rect;
plotAreaChanged(plotRect.size());
}
img->fill(backgroundColor);
scene.render(painter.get());
texture.reset(window()->createTextureFromImage(*img, QQuickWindow::TextureIsOpaque));
n->setTexture(texture.get());
n->setRect(rect);
return n;
}
// Constants that control the graph layouts // Constants that control the graph layouts
static const QColor quartileMarkerColor(Qt::red); static const QColor quartileMarkerColor(Qt::red);
@ -28,76 +53,45 @@ static const double quartileMarkerSize = 15.0;
static const double sceneBorder = 5.0; // Border between scene edges and statitistics view 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 titleBorder = 2.0; // Border between title and chart
static const QUrl urlStatsView = QUrl(QStringLiteral("qrc:/qml/statsview.qml")); StatsView::StatsView(QQuickItem *parent) : QQuickItem(parent),
// We use QtQuick's ChartView so that we can show the statistics on mobile.
// However, accessing the ChartView from C++ is maliciously cumbersome and
// the full QChart interface is not exported. Fortunately, the interface
// leaks the QChart object: We can create a dummy-series and access the chart
// object via the chart() accessor function. By creating a "PieSeries", the
// ChartView does not automatically add axes.
static QtCharts::QChart *getChart(QQuickItem *item)
{
QtCharts::QAbstractSeries *abstract_series;
if (!item)
return nullptr;
if (!QMetaObject::invokeMethod(item, "createSeries", Qt::AutoConnection,
Q_RETURN_ARG(QtCharts::QAbstractSeries *, abstract_series),
Q_ARG(int, QtCharts::QAbstractSeries::SeriesTypePie),
Q_ARG(QString, QString()))) {
qWarning("Couldn't call createSeries()");
return nullptr;
}
QtCharts::QChart *res = abstract_series->chart();
res->removeSeries(abstract_series);
delete abstract_series;
return res;
}
bool StatsView::EventFilter::eventFilter(QObject *o, QEvent *event)
{
if (event->type() == QEvent::GraphicsSceneHoverMove) {
QGraphicsSceneHoverEvent *hover = static_cast<QGraphicsSceneHoverEvent *>(event);
view->hover(hover->pos());
return true;
}
return QObject::eventFilter(o, event);
}
StatsView::StatsView(QWidget *parent) : QQuickWidget(parent),
highlightedSeries(nullptr), highlightedSeries(nullptr),
xAxis(nullptr), xAxis(nullptr),
yAxis(nullptr), yAxis(nullptr)
eventFilter(this)
{ {
setResizeMode(QQuickWidget::SizeRootObjectToView); setFlag(ItemHasContents, true);
// if we get a failure to load the QML file (e.g., when the QtCharts QML modules aren't found)
// the chart will be null
setSource(urlStatsView);
chart = getChart(rootObject());
if (chart) {
connect(chart, &QtCharts::QChart::plotAreaChanged, this, &StatsView::plotAreaChanged);
connect(&diveListNotifier, &DiveListNotifier::numShownChanged, this, &StatsView::replotIfVisible);
chart->installEventFilter(&eventFilter); connect(&diveListNotifier, &DiveListNotifier::numShownChanged, this, &StatsView::replotIfVisible);
chart->setAcceptHoverEvents(true);
chart->legend()->setVisible(false); setAcceptHoverEvents(true);
}
QFont font; QFont font;
titleFont = QFont(font.family(), font.pointSize(), QFont::Light); // Make configurable titleFont = QFont(font.family(), font.pointSize(), QFont::Light); // Make configurable
} }
StatsView::StatsView() : StatsView(nullptr)
{
}
StatsView::~StatsView() StatsView::~StatsView()
{ {
} }
void StatsView::plotAreaChanged(const QRectF &r) void StatsView::plotAreaChanged(const QSizeF &s)
{ {
double left = r.x() + sceneBorder; // Make sure that image is at least one pixel wide / high, otherwise
double top = r.y() + sceneBorder; // the painter starts acting up.
double right = r.right() - sceneBorder; int w = std::max(1, static_cast<int>(floor(s.width())));
double bottom = r.bottom() - sceneBorder; int h = std::max(1, static_cast<int>(floor(s.height())));
scene.setSceneRect(QRectF(0, 0, static_cast<double>(w), static_cast<double>(h)));
painter.reset();
img.reset(new QImage(w, h, QImage::Format_RGB32));
painter.reset(new QPainter(img.get()));
painter->setRenderHint(QPainter::Antialiasing);
double left = sceneBorder;
double top = sceneBorder;
double right = s.width() - sceneBorder;
double bottom = s.height() - sceneBorder;
const double minSize = 30.0; const double minSize = 30.0;
if (title) if (title)
@ -140,8 +134,13 @@ void StatsView::replotIfVisible()
plot(state); plot(state);
} }
void StatsView::hover(QPointF pos) void StatsView::hoverEnterEvent(QHoverEvent *)
{ {
}
void StatsView::hoverMoveEvent(QHoverEvent *event)
{
QPointF pos(event->pos());
for (auto &series: series) { for (auto &series: series) {
if (series->hover(pos)) { if (series->hover(pos)) {
if (series.get() != highlightedSeries) { if (series.get() != highlightedSeries) {
@ -149,7 +148,7 @@ void StatsView::hover(QPointF pos)
highlightedSeries->unhighlight(); highlightedSeries->unhighlight();
highlightedSeries = series.get(); highlightedSeries = series.get();
} }
return; return update();
} }
} }
@ -157,13 +156,14 @@ void StatsView::hover(QPointF pos)
if (highlightedSeries) { if (highlightedSeries) {
highlightedSeries->unhighlight(); highlightedSeries->unhighlight();
highlightedSeries = nullptr; highlightedSeries = nullptr;
update();
} }
} }
template <typename T, class... Args> template <typename T, class... Args>
T *StatsView::createSeries(Args&&... args) T *StatsView::createSeries(Args&&... args)
{ {
T *res = new T(chart, xAxis, yAxis, std::forward<Args>(args)...); T *res = new T(&scene, xAxis, yAxis, std::forward<Args>(args)...);
series.emplace_back(res); series.emplace_back(res);
series.back()->updatePositions(); series.back()->updatePositions();
return res; return res;
@ -175,7 +175,7 @@ void StatsView::setTitle(const QString &s)
title.reset(); title.reset();
return; return;
} }
title = std::make_unique<QGraphicsSimpleTextItem>(s, chart); title = createItemPtr<QGraphicsSimpleTextItem>(&scene, s);
title->setFont(titleFont); title->setFont(titleFont);
} }
@ -183,7 +183,7 @@ void StatsView::updateTitlePos()
{ {
if (!title) if (!title)
return; return;
QRectF rect = chart->plotArea(); QRectF rect = scene.sceneRect();
title->setPos(sceneBorder + (rect.width() - title->boundingRect().width()) / 2.0, title->setPos(sceneBorder + (rect.width() - title->boundingRect().width()) / 2.0,
sceneBorder); sceneBorder);
} }
@ -191,7 +191,7 @@ void StatsView::updateTitlePos()
template <typename T, class... Args> template <typename T, class... Args>
T *StatsView::createAxis(const QString &title, Args&&... args) T *StatsView::createAxis(const QString &title, Args&&... args)
{ {
T *res = new T(chart, title, std::forward<Args>(args)...); T *res = createItem<T>(&scene, title, std::forward<Args>(args)...);
axes.emplace_back(res); axes.emplace_back(res);
return res; return res;
} }
@ -201,13 +201,11 @@ void StatsView::setAxes(StatsAxis *x, StatsAxis *y)
xAxis = x; xAxis = x;
yAxis = y; yAxis = y;
if (x && y) if (x && y)
grid = std::make_unique<StatsGrid>(chart, *x, *y); grid = std::make_unique<StatsGrid>(&scene, *x, *y);
} }
void StatsView::reset() void StatsView::reset()
{ {
if (!chart)
return;
highlightedSeries = nullptr; highlightedSeries = nullptr;
xAxis = yAxis = nullptr; xAxis = yAxis = nullptr;
legend.reset(); legend.reset();
@ -215,7 +213,6 @@ void StatsView::reset()
quartileMarkers.clear(); quartileMarkers.clear();
regressionLines.clear(); regressionLines.clear();
histogramMarkers.clear(); histogramMarkers.clear();
chart->removeAllSeries();
grid.reset(); grid.reset();
axes.clear(); axes.clear();
title.reset(); title.reset();
@ -225,12 +222,13 @@ void StatsView::plot(const StatsState &stateIn)
{ {
state = stateIn; state = stateIn;
plotChart(); plotChart();
plotAreaChanged(chart->plotArea()); plotAreaChanged(scene.sceneRect().size());
update();
} }
void StatsView::plotChart() void StatsView::plotChart()
{ {
if (!chart || !state.var1) if (!state.var1)
return; return;
reset(); reset();
@ -402,7 +400,7 @@ void StatsView::plotBarChart(const std::vector<dive *> &dives,
// Paint legend first, because the bin-names will be moved away from. // Paint legend first, because the bin-names will be moved away from.
if (showLegend) if (showLegend)
legend = std::make_unique<Legend>(chart, data.vbinNames); legend = createItemPtr<Legend>(&scene, data.vbinNames);
std::vector<BarSeries::MultiItem> items; std::vector<BarSeries::MultiItem> items;
items.reserve(data.hbin_counts.size()); items.reserve(data.hbin_counts.size());
@ -619,7 +617,7 @@ void StatsView::plotPieChart(const std::vector<dive *> &dives,
PieSeries *series = createSeries<PieSeries>(categoryVariable->name(), data, keepOrder, labels); PieSeries *series = createSeries<PieSeries>(categoryVariable->name(), data, keepOrder, labels);
if (showLegend) if (showLegend)
legend = std::make_unique<Legend>(chart, series->binNames()); legend = createItemPtr<Legend>(&scene, series->binNames());
} }
void StatsView::plotDiscreteBoxChart(const std::vector<dive *> &dives, void StatsView::plotDiscreteBoxChart(const std::vector<dive *> &dives,
@ -689,17 +687,17 @@ void StatsView::plotDiscreteScatter(const std::vector<dive *> &dives,
if (quartiles) { if (quartiles) {
StatsQuartiles quartiles = StatsVariable::quartiles(array); StatsQuartiles quartiles = StatsVariable::quartiles(array);
if (quartiles.isValid()) { if (quartiles.isValid()) {
quartileMarkers.emplace_back(x, quartiles.q1, chart, catAxis, valAxis); quartileMarkers.emplace_back(x, quartiles.q1, &scene, catAxis, valAxis);
quartileMarkers.emplace_back(x, quartiles.q2, chart, catAxis, valAxis); quartileMarkers.emplace_back(x, quartiles.q2, &scene, catAxis, valAxis);
quartileMarkers.emplace_back(x, quartiles.q3, chart, catAxis, valAxis); quartileMarkers.emplace_back(x, quartiles.q3, &scene, catAxis, valAxis);
} }
} }
x += 1.0; x += 1.0;
} }
} }
StatsView::QuartileMarker::QuartileMarker(double pos, double value, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) : StatsView::QuartileMarker::QuartileMarker(double pos, double value, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) :
item(new QGraphicsLineItem(chart)), item(createItemPtr<QGraphicsLineItem>(scene)),
xAxis(xAxis), yAxis(yAxis), xAxis(xAxis), yAxis(yAxis),
pos(pos), pos(pos),
value(value) value(value)
@ -719,8 +717,8 @@ void StatsView::QuartileMarker::updatePosition()
x + quartileMarkerSize / 2.0, y); x + quartileMarkerSize / 2.0, y);
} }
StatsView::RegressionLine::RegressionLine(double a, double b, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) : StatsView::RegressionLine::RegressionLine(double a, double b, QPen pen, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) :
item(new QGraphicsLineItem(chart)), item(createItemPtr<QGraphicsLineItem>(scene)),
xAxis(xAxis), yAxis(yAxis), xAxis(xAxis), yAxis(yAxis),
a(a), b(b) a(a), b(b)
{ {
@ -751,8 +749,8 @@ void StatsView::RegressionLine::updatePosition()
xAxis->toScreen(maxX), yAxis->toScreen(a * maxX + b)); xAxis->toScreen(maxX), yAxis->toScreen(a * maxX + b));
} }
StatsView::HistogramMarker::HistogramMarker(double val, bool horizontal, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) : StatsView::HistogramMarker::HistogramMarker(double val, bool horizontal, QPen pen, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) :
item(new QGraphicsLineItem(chart)), item(createItemPtr<QGraphicsLineItem>(scene)),
xAxis(xAxis), yAxis(yAxis), xAxis(xAxis), yAxis(yAxis),
val(val), horizontal(horizontal) val(val), horizontal(horizontal)
{ {
@ -777,12 +775,12 @@ void StatsView::HistogramMarker::updatePosition()
void StatsView::addHistogramMarker(double pos, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis) void StatsView::addHistogramMarker(double pos, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis)
{ {
histogramMarkers.emplace_back(pos, isHorizontal, pen, chart, xAxis, yAxis); histogramMarkers.emplace_back(pos, isHorizontal, pen, &scene, xAxis, yAxis);
} }
void StatsView::addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, StatsAxis *xAxis, StatsAxis *yAxis) void StatsView::addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, StatsAxis *xAxis, StatsAxis *yAxis)
{ {
regressionLines.emplace_back(a, b, QPen(Qt::red), chart, xAxis, yAxis); regressionLines.emplace_back(a, b, QPen(Qt::red), &scene, xAxis, yAxis);
} }
// Yikes, we get our data in different kinds of (bin, value) pairs. // Yikes, we get our data in different kinds of (bin, value) pairs.
@ -942,7 +940,7 @@ void StatsView::plotHistogramStackedChart(const std::vector<dive *> &dives,
BarPlotData data(categoryBins, *valueBinner); BarPlotData data(categoryBins, *valueBinner);
if (showLegend) if (showLegend)
legend = std::make_unique<Legend>(chart, data.vbinNames); legend = createItemPtr<Legend>(&scene, data.vbinNames);
CountAxis *valAxis = createCountAxis(data.maxCategoryCount, isHorizontal); CountAxis *valAxis = createCountAxis(data.maxCategoryCount, isHorizontal);

View file

@ -5,7 +5,10 @@
#include "statsstate.h" #include "statsstate.h"
#include <memory> #include <memory>
#include <QFont> #include <QFont>
#include <QQuickWidget> #include <QGraphicsScene>
#include <QImage>
#include <QPainter>
#include <QQuickItem>
struct dive; struct dive;
struct StatsBinner; struct StatsBinner;
@ -13,10 +16,6 @@ struct StatsBin;
struct StatsState; struct StatsState;
struct StatsVariable; struct StatsVariable;
namespace QtCharts {
class QAbstractSeries;
class QChart;
}
class QGraphicsLineItem; class QGraphicsLineItem;
class QGraphicsSimpleTextItem; class QGraphicsSimpleTextItem;
class StatsSeries; class StatsSeries;
@ -26,21 +25,31 @@ class HistogramAxis;
class StatsAxis; class StatsAxis;
class StatsGrid; class StatsGrid;
class Legend; class Legend;
class QSGTexture;
enum class ChartSubType : int; enum class ChartSubType : int;
enum class StatsOperation : int; enum class StatsOperation : int;
class StatsView : public QQuickWidget { class StatsView : public QQuickItem {
Q_OBJECT Q_OBJECT
public: public:
StatsView(QWidget *parent = NULL); StatsView();
StatsView(QQuickItem *parent);
~StatsView(); ~StatsView();
void plot(const StatsState &state); void plot(const StatsState &state);
private slots: private slots:
void plotAreaChanged(const QRectF &plotArea);
void replotIfVisible(); void replotIfVisible();
private: private:
// QtQuick related things
QRectF plotRect;
QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override;
std::unique_ptr<QImage> img;
std::unique_ptr<QPainter> painter;
QGraphicsScene scene;
std::unique_ptr<QSGTexture> texture;
void plotAreaChanged(const QSizeF &size);
void reset(); // clears all series and axes void reset(); // clears all series and axes
void setAxes(StatsAxis *x, StatsAxis *y); void setAxes(StatsAxis *x, StatsAxis *y);
void plotBarChart(const std::vector<dive *> &dives, void plotBarChart(const std::vector<dive *> &dives,
@ -102,7 +111,7 @@ private:
std::unique_ptr<QGraphicsLineItem> item; std::unique_ptr<QGraphicsLineItem> item;
StatsAxis *xAxis, *yAxis; StatsAxis *xAxis, *yAxis;
double pos, value; double pos, value;
QuartileMarker(double pos, double value, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis); QuartileMarker(double pos, double value, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis);
void updatePosition(); void updatePosition();
}; };
@ -112,7 +121,7 @@ private:
StatsAxis *xAxis, *yAxis; StatsAxis *xAxis, *yAxis;
double a, b; // y = ax + b double a, b; // y = ax + b
void updatePosition(); void updatePosition();
RegressionLine(double a, double b, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis); RegressionLine(double a, double b, QPen pen, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis);
}; };
// A line marking median or mean in histograms // A line marking median or mean in histograms
@ -122,14 +131,13 @@ private:
double val; double val;
bool horizontal; bool horizontal;
void updatePosition(); void updatePosition();
HistogramMarker(double val, bool horizontal, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis); HistogramMarker(double val, bool horizontal, QPen pen, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis);
}; };
void addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, StatsAxis *xAxis, StatsAxis *yAxis); void addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, StatsAxis *xAxis, StatsAxis *yAxis);
void addHistogramMarker(double pos, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis); void addHistogramMarker(double pos, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis);
StatsState state; StatsState state;
QtCharts::QChart *chart;
QFont titleFont; QFont titleFont;
std::vector<std::unique_ptr<StatsAxis>> axes; std::vector<std::unique_ptr<StatsAxis>> axes;
std::unique_ptr<StatsGrid> grid; std::unique_ptr<StatsGrid> grid;
@ -142,17 +150,8 @@ private:
StatsSeries *highlightedSeries; StatsSeries *highlightedSeries;
StatsAxis *xAxis, *yAxis; StatsAxis *xAxis, *yAxis;
// This is unfortunate: we can't derive from QChart, because the chart is allocated by QML. void hoverEnterEvent(QHoverEvent *event) override;
// Therefore, we have to listen to hover events using an events-filter. void hoverMoveEvent(QHoverEvent *event) override;
// Probably we should try to get rid of the QML ChartView.
struct EventFilter : public QObject {
StatsView *view;
EventFilter(StatsView *view) : view(view) {}
private:
bool eventFilter(QObject *o, QEvent *event);
} eventFilter;
friend EventFilter;
void hover(QPointF pos);
}; };
#endif #endif

View file

@ -4,6 +4,7 @@
#include "map-widget/qmlmapwidgethelper.h" #include "map-widget/qmlmapwidgethelper.h"
#include "qt-models/maplocationmodel.h" #include "qt-models/maplocationmodel.h"
#include "stats/statsview.h"
#include "core/qt-gui.h" #include "core/qt-gui.h"
#include "core/settings/qPref.h" #include "core/settings/qPref.h"
#include "core/ssrf.h" #include "core/ssrf.h"
@ -213,4 +214,5 @@ static void register_qml_types(QQmlEngine *engine)
register_qml_type<MapWidgetHelper>("MapWidgetHelper"); register_qml_type<MapWidgetHelper>("MapWidgetHelper");
register_qml_type<MapLocationModel>("MapLocationModel"); register_qml_type<MapLocationModel>("MapLocationModel");
register_qml_type<StatsView>("StatsView");
} }