diff options
author | Berthold Stoeger <bstoeger@mail.tuwien.ac.at> | 2021-01-07 14:38:37 +0100 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2021-01-10 15:16:52 -0800 |
commit | e7907c494f7f78831c2e2d523f7eb43e25ee77c0 (patch) | |
tree | cbd169e1b2160569364a62b755ba082d7485af23 | |
parent | f6b857b8fe787b1d76d20ecc9407994da1139354 (diff) | |
download | subsurface-e7907c494f7f78831c2e2d523f7eb43e25ee77c0.tar.gz |
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>
32 files changed, 298 insertions, 268 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index d25313e96..0fa9032ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -301,7 +301,7 @@ endif() #set up the subsurface_link_libraries variable set(SUBSURFACE_LINK_LIBRARIES ${SUBSURFACE_LINK_LIBRARIES} ${LIBDIVECOMPUTER_LIBRARIES} ${LIBGIT2_LIBRARIES} ${LIBUSB_LIBRARIES} ${LIBMTP_LIBRARIES}) 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() # hack to build successfully on LGTM diff --git a/backend-shared/roundrectitem.cpp b/backend-shared/roundrectitem.cpp index 2dbfd7b03..52200b017 100644 --- a/backend-shared/roundrectitem.cpp +++ b/backend-shared/roundrectitem.cpp @@ -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 *) { painter->save(); diff --git a/backend-shared/roundrectitem.h b/backend-shared/roundrectitem.h index d3f58782f..b8cb3f89b 100644 --- a/backend-shared/roundrectitem.h +++ b/backend-shared/roundrectitem.h @@ -7,6 +7,7 @@ class RoundRectItem : public QGraphicsRectItem { public: RoundRectItem(double radius, QGraphicsItem *parent); + RoundRectItem(double radius); private: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; double radius; diff --git a/desktop-widgets/qml/statsview2.qml b/desktop-widgets/qml/statsview2.qml new file mode 100644 index 000000000..ccad7fd1e --- /dev/null +++ b/desktop-widgets/qml/statsview2.qml @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 +import QtQuick 2.0 +import org.subsurfacedivelog.mobile 1.0 + +StatsView { +} diff --git a/stats/qml/statsview.qrc b/desktop-widgets/qml/statsview2.qrc index aeb65167e..d54f6c6b9 100644 --- a/stats/qml/statsview.qrc +++ b/desktop-widgets/qml/statsview2.qrc @@ -1,5 +1,5 @@ <RCC> <qresource prefix="/qml"> - <file>statsview.qml</file> + <file>statsview2.qml</file> </qresource> </RCC> diff --git a/desktop-widgets/statswidget.cpp b/desktop-widgets/statswidget.cpp index 01fd16eb1..e0090395f 100644 --- a/desktop-widgets/statswidget.cpp +++ b/desktop-widgets/statswidget.cpp @@ -70,6 +70,7 @@ QSize ChartItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMod return size; } +static const QUrl urlStatsView = QUrl(QStringLiteral("qrc:/qml/statsview2.qml")); StatsWidget::StatsWidget(QWidget *parent) : QWidget(parent) { 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.var2Binner, QOverload<int>::of(&QComboBox::activated), this, &StatsWidget::var2BinnerChanged); 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 @@ -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. -static void setBinList(QLabel *label, QComboBox *combo, const StatsState::BinnerList &list) +static void setBinList(QComboBox *combo, const StatsState::BinnerList &list) { combo->clear(); combo->setEnabled(!list.binners.empty()); @@ -114,8 +122,8 @@ void StatsWidget::updateUi() int pos = charts.update(uiState.charts); ui.chartType->setCurrentIndex(pos); ui.chartType->setItemDelegate(new ChartItemDelegate); - setBinList(ui.var1BinnerLabel, ui.var1Binner, uiState.binners1); - setBinList(ui.var2BinnerLabel, ui.var2Binner, uiState.binners2); + setBinList(ui.var1Binner, uiState.binners1); + setBinList(ui.var2Binner, uiState.binners2); setVariableList(ui.var2Operation, uiState.operations2); // Add checkboxes for additional features @@ -129,7 +137,8 @@ void StatsWidget::updateUi() ui.features->addWidget(check); } - ui.stats->plot(state); + if (view) + view->plot(state); } void StatsWidget::closeStats() diff --git a/desktop-widgets/statswidget.h b/desktop-widgets/statswidget.h index 40e6d106c..b85d89730 100644 --- a/desktop-widgets/statswidget.h +++ b/desktop-widgets/statswidget.h @@ -9,6 +9,7 @@ #include <memory> class QCheckBox; +class StatsView; class StatsWidget : public QWidget { Q_OBJECT @@ -27,6 +28,7 @@ slots: private: Ui::StatsWidget ui; StatsState state; + StatsView *view; void updateUi(); std::vector<std::unique_ptr<QCheckBox>> features; diff --git a/desktop-widgets/statswidget.ui b/desktop-widgets/statswidget.ui index 684b0fa58..a20fb4a8f 100644 --- a/desktop-widgets/statswidget.ui +++ b/desktop-widgets/statswidget.ui @@ -105,7 +105,7 @@ </layout> </item> <item> - <widget class="StatsView" name="stats"> + <widget class="QQuickWidget" name="stats"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <horstretch>0</horstretch> @@ -116,14 +116,6 @@ </item> </layout> </widget> - <customwidgets> - <customwidget> - <class>StatsView</class> - <extends>QQuickWidget</extends> - <header>stats/statsview.h</header> - <container>1</container> - </customwidget> - </customwidgets> <resources> <include location="../subsurface.qrc"/> </resources> diff --git a/stats/barseries.cpp b/stats/barseries.cpp index 5727745a0..986aa7e0d 100644 --- a/stats/barseries.cpp +++ b/stats/barseries.cpp @@ -2,6 +2,7 @@ #include "barseries.h" #include "informationbox.h" #include "statscolors.h" +#include "statshelper.h" #include "statstranslations.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); } -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, std::vector<QString> valueBinNames) : - StatsSeries(chart, xAxis, yAxis), + StatsSeries(scene, xAxis, yAxis), horizontal(horizontal), stacked(stacked), categoryName(categoryName), 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, 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) { 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, 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) { 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, std::vector<QString> valueBinNames, 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) { 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) { items.reserve(labels.size()); for (const QString &label: labels) { - items.emplace_back(new QGraphicsSimpleTextItem(chart)); + items.emplace_back(createItem<QGraphicsSimpleTextItem>(scene)); items.back()->setText(label); items.back()->setZValue(ZValues::seriesLabels); QRectF rect = items.back()->boundingRect(); @@ -175,7 +176,7 @@ void BarSeries::BarLabel::updatePosition(bool horizontal, bool center, const QRe 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, const QString &binName, const StatsOperationResults &res, int total, 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; for (auto &[v, label]: values) { 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()) - 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) 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. if (subitems.empty()) 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()); } @@ -403,7 +404,7 @@ bool BarSeries::hover(QPointF pos) Item &item = items[highlighted.bar]; item.highlight(index.subitem, true, binCount()); if (!information) - information.reset(new InformationBox(chart)); + information = createItemPtr<InformationBox>(scene); information->setText(makeInfo(item, highlighted.subitem), pos); } else { information.reset(); diff --git a/stats/barseries.h b/stats/barseries.h index 114df540a..299998bfb 100644 --- a/stats/barseries.h +++ b/stats/barseries.h @@ -12,9 +12,7 @@ #include <vector> #include <QGraphicsRectItem> -namespace QtCharts { - class QAbstractAxis; -} +class QGraphicsScene; class InformationBox; class StatsVariable; @@ -49,13 +47,13 @@ public: // Note: this expects that all items are added with increasing pos // and that no bar is inside another bar, i.e. lowerBound and upperBound // are ordered identically. - BarSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, + BarSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis, bool horizontal, const QString &categoryName, 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, 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, std::vector<QString> valueBinNames, const std::vector<MultiItem> &items); @@ -65,7 +63,7 @@ public: bool hover(QPointF pos) override; void unhighlight() override; 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, std::vector<QString> valueBinNames); @@ -85,7 +83,7 @@ private: std::vector<std::unique_ptr<QGraphicsSimpleTextItem>> items; double totalWidth, totalHeight; // Size of the item 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 updatePosition(bool horizontal, bool center, const QRectF &rect, int bin_nr, int binCount); void highlight(bool highlight, int bin_nr, int binCount); @@ -109,7 +107,7 @@ private: const QString binName; StatsOperationResults res; 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, const QString &binName, const StatsOperationResults &res, int total, bool horizontal, bool stacked, int binCount); diff --git a/stats/boxseries.cpp b/stats/boxseries.cpp index 805e8cc8e..c684eff8b 100644 --- a/stats/boxseries.cpp +++ b/stats/boxseries.cpp @@ -2,6 +2,7 @@ #include "boxseries.h" #include "informationbox.h" #include "statscolors.h" +#include "statshelper.h" #include "statstranslations.h" #include "zvalues.h" @@ -12,9 +13,9 @@ static const double boxWidth = 0.8; // 1.0 = full width of category 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) : - StatsSeries(chart, xAxis, yAxis), + StatsSeries(scene, xAxis, yAxis), 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) : - box(chart), - topWhisker(chart), bottomWhisker(chart), - topBar(chart), bottomBar(chart), - center(chart), lowerBound(lowerBound), upperBound(upperBound), q(q), binName(binName) { @@ -38,6 +35,12 @@ BoxSeries::Item::Item(QtCharts::QChart *chart, BoxSeries *series, double lowerBo topBar.setZValue(ZValues::series); bottomBar.setZValue(ZValues::series); center.setZValue(ZValues::series); + scene->addItem(&box); + scene->addItem(&topWhisker); + scene->addItem(&bottomWhisker); + scene->addItem(&topBar); + scene->addItem(&bottomBar); + scene->addItem(¢er); highlight(false); 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) { - 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() @@ -147,7 +150,7 @@ bool BoxSeries::hover(QPointF pos) Item &item = *items[highlighted]; item.highlight(true); if (!information) - information.reset(new InformationBox(chart)); + information = createItemPtr<InformationBox>(scene); information->setText(formatInformation(item), pos); } else { information.reset(); diff --git a/stats/boxseries.h b/stats/boxseries.h index 43b177619..175ad771e 100644 --- a/stats/boxseries.h +++ b/stats/boxseries.h @@ -14,10 +14,11 @@ #include <QGraphicsRectItem> class InformationBox; +class QGraphicsScene; class BoxSeries : public StatsSeries { public: - BoxSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, + BoxSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis, const QString &variable, const QString &unit, int decimals); ~BoxSeries(); @@ -44,7 +45,7 @@ private: double lowerBound, upperBound; StatsQuartiles q; 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 highlight(bool highlight); }; diff --git a/stats/informationbox.cpp b/stats/informationbox.cpp index 727b6fe83..c11104333 100644 --- a/stats/informationbox.cpp +++ b/stats/informationbox.cpp @@ -4,6 +4,7 @@ #include <QChart> #include <QFontMetrics> +#include <QGraphicsScene> static const QColor informationBorderColor(Qt::black); 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 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)); setBrush(informationColor); @@ -37,7 +38,7 @@ void InformationBox::setText(const std::vector<QString> &text, QPointF pos) void InformationBox::setPos(QPointF pos) { - QRectF plotArea = chart->plotArea(); + QRectF plotArea = scene()->sceneRect(); double x = pos.x() + distanceFromPointer; if (x + width >= plotArea.right()) { @@ -79,6 +80,6 @@ void InformationBox::addLine(const QString &s) int InformationBox::recommendedMaxLines() const { 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; } diff --git a/stats/informationbox.h b/stats/informationbox.h index 0c71447aa..741df537f 100644 --- a/stats/informationbox.h +++ b/stats/informationbox.h @@ -10,19 +10,16 @@ #include <memory> #include <QFont> -namespace QtCharts { - class QChart; -} struct dive; +class QGraphicsScene; // Information window showing data of highlighted dive struct InformationBox : RoundRectItem { - InformationBox(QtCharts::QChart *chart); + InformationBox(); void setText(const std::vector<QString> &text, QPointF pos); void setPos(QPointF pos); int recommendedMaxLines() const; private: - QtCharts::QChart *chart; QFont font; // For future specialization. double width, height; void addLine(const QString &s); diff --git a/stats/legend.cpp b/stats/legend.cpp index 99340a8f9..27607fb51 100644 --- a/stats/legend.cpp +++ b/stats/legend.cpp @@ -4,8 +4,8 @@ #include "zvalues.h" #include <QFontMetrics> +#include <QGraphicsScene> #include <QGraphicsSceneMouseEvent> -#include <QGraphicsWidget> #include <QPen> 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 legendBorderColor(Qt::black); -Legend::Legend(QGraphicsWidget *chart, const std::vector<QString> &names) : - RoundRectItem(legendBoxBorderRadius, chart), chart(chart), +Legend::Legend(const std::vector<QString> &names) : + RoundRectItem(legendBoxBorderRadius), displayedItems(0), width(0.0), height(0.0) { setZValue(ZValues::legend); @@ -40,8 +40,6 @@ Legend::Legend(QGraphicsWidget *chart, const std::vector<QString> &names) : } setPen(QPen(legendBorderColor, legendBorderSize)); setBrush(QBrush(legendColor)); - - resize(); // Draw initial legend } Legend::Entry::Entry(const QString &name, int idx, int numBins, QGraphicsItem *parent) : @@ -70,7 +68,7 @@ void Legend::resize() if (entries.empty()) 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. // Not sure if that makes sense - this might need some optimization. @@ -110,7 +108,7 @@ void Legend::updatePosition() if (displayedItems <= 0) return hide(); // 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))); for (int i = 0; i < displayedItems; ++i) { QPointF itemPos = pos + entries[i].pos; diff --git a/stats/legend.h b/stats/legend.h index 2baf6f67b..c643a41f3 100644 --- a/stats/legend.h +++ b/stats/legend.h @@ -8,11 +8,12 @@ #include <memory> #include <vector> +class QGraphicsScene; class QGraphicsSceneMouseEvent; class Legend : public RoundRectItem { public: - Legend(QGraphicsWidget *chart, const std::vector<QString> &names); + Legend(const std::vector<QString> &names); void hover(QPointF pos); void resize(); // called when the chart size changes. private: @@ -24,7 +25,6 @@ private: double width; Entry(const QString &name, int idx, int numBins, QGraphicsItem *parent); }; - QGraphicsWidget *chart; int displayedItems; double width; double height; diff --git a/stats/pieseries.cpp b/stats/pieseries.cpp index 108046b23..ac43b6d3a 100644 --- a/stats/pieseries.cpp +++ b/stats/pieseries.cpp @@ -2,6 +2,7 @@ #include "pieseries.h" #include "informationbox.h" #include "statscolors.h" +#include "statshelper.h" #include "statstranslations.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 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) : - item(new QGraphicsEllipseItem(chart)), + item(createItemPtr<QGraphicsEllipseItem>(scene)), name(name), count(count) { @@ -38,10 +39,10 @@ PieSeries::Item::Item(QtCharts::QChart *chart, const QString &name, int from, in if (labels) { double percentage = count * 100.0 / totalCount; 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); - outerLabel.reset(new QGraphicsSimpleTextItem(name, chart)); + outerLabel = createItemPtr<QGraphicsSimpleTextItem>(scene, name); outerLabel->setBrush(QBrush(darkLabelColor)); 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) : - StatsSeries(chart, xAxis, yAxis), + StatsSeries(scene, xAxis, yAxis), categoryName(categoryName), highlighted(-1) { @@ -147,7 +148,7 @@ PieSeries::PieSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis int act = 0; for (auto it2 = sorted.begin(); it2 != it; ++it2) { 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; } @@ -157,7 +158,7 @@ PieSeries::PieSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis for (auto it2 = it; it2 != sorted.end(); ++it2) other.push_back({ data[*it2].first, data[*it2].second }); 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() { - QRectF plotRect = chart->plotArea(); + QRectF plotRect = scene->sceneRect(); center = plotRect.center(); radius = std::min(plotRect.width(), plotRect.height()) * pieSize / 2.0; 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()) { items[highlighted].highlight(highlighted, true, (int)items.size()); if (!information) - information.reset(new InformationBox(chart)); + information = createItemPtr<InformationBox>(scene); information->setText(makeInfo(highlighted), pos); } else { information.reset(); diff --git a/stats/pieseries.h b/stats/pieseries.h index 242cfa781..c7e2af319 100644 --- a/stats/pieseries.h +++ b/stats/pieseries.h @@ -11,14 +11,16 @@ class InformationBox; class QGraphicsEllipseItem; +class QGraphicsScene; class QGraphicsSimpleTextItem; +class QRectF; class PieSeries : public StatsSeries { public: // The pie series is initialized with (name, count) pairs. // 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. - 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); ~PieSeries(); @@ -42,7 +44,7 @@ private: double angleTo; // In fraction of total int count; 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); void updatePositions(const QRectF &rect, const QPointF ¢er, double radius); void highlight(int bin_nr, bool highlight, int numBins); diff --git a/stats/qml/statsview.qml b/stats/qml/statsview.qml deleted file mode 100644 index 24f1fe9d3..000000000 --- a/stats/qml/statsview.qml +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -import QtQuick 2.0 -import QtCharts 2.0 - -ChartView { - antialiasing: true - localizeNumbers: true -} diff --git a/stats/scatterseries.cpp b/stats/scatterseries.cpp index de7a8a3bd..67687de72 100644 --- a/stats/scatterseries.cpp +++ b/stats/scatterseries.cpp @@ -2,6 +2,7 @@ #include "scatterseries.h" #include "informationbox.h" #include "statscolors.h" +#include "statshelper.h" #include "statstranslations.h" #include "statsvariables.h" #include "zvalues.h" @@ -16,9 +17,9 @@ static const int scatterItemDiameter = 10; 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) : - StatsSeries(chart, xAxis, yAxis), + StatsSeries(scene, xAxis, yAxis), varX(varX), varY(varY) { } @@ -58,8 +59,8 @@ static const QPixmap &scatterPixmap(bool highlight) return highlight ? *scatterPixmapHighlightedPtr : *scatterPixmapPtr; } -ScatterSeries::Item::Item(QtCharts::QChart *chart, ScatterSeries *series, dive *d, double pos, double value) : - item(new QGraphicsPixmapItem(scatterPixmap(false), chart)), +ScatterSeries::Item::Item(QGraphicsScene *scene, ScatterSeries *series, dive *d, double pos, double value) : + item(createItemPtr<QGraphicsPixmapItem>(scene, scatterPixmap(false))), d(d), pos(pos), value(value) @@ -82,7 +83,7 @@ void ScatterSeries::Item::highlight(bool highlight) 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() @@ -173,7 +174,7 @@ bool ScatterSeries::hover(QPointF pos) return false; } else { if (!information) - information.reset(new InformationBox(chart)); + information = createItemPtr<InformationBox>(scene); std::vector<QString> text; text.reserve(highlighted.size() * 5); diff --git a/stats/scatterseries.h b/stats/scatterseries.h index 212a8e4ea..ec25369ae 100644 --- a/stats/scatterseries.h +++ b/stats/scatterseries.h @@ -11,13 +11,14 @@ #include <QGraphicsRectItem> class QGraphicsPixmapItem; +class QGraphicsScene; class InformationBox; struct StatsVariable; struct dive; class ScatterSeries : public StatsSeries { public: - ScatterSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis, + ScatterSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis, const StatsVariable &varX, const StatsVariable &varY); ~ScatterSeries(); @@ -36,7 +37,7 @@ private: std::unique_ptr<QGraphicsPixmapItem> item; dive *d; 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 highlight(bool highlight); }; diff --git a/stats/statsaxis.cpp b/stats/statsaxis.cpp index 019a16256..4d70488da 100644 --- a/stats/statsaxis.cpp +++ b/stats/statsaxis.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include "statsaxis.h" #include "statscolors.h" +#include "statshelper.h" #include "statstranslations.h" #include "statsvariables.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 axisTitleSpaceVertical = 2.0; // Space between labels and title -StatsAxis::StatsAxis(QtCharts::QChart *chart, const QString &titleIn, bool horizontal, bool labelsBetweenTicks) : - QGraphicsLineItem(chart), - chart(chart), horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks), +StatsAxis::StatsAxis(const QString &titleIn, bool horizontal, bool labelsBetweenTicks) : + horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks), 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 @@ -35,7 +35,7 @@ StatsAxis::StatsAxis(QtCharts::QChart *chart, const QString &titleIn, bool horiz setPen(QPen(axisColor, axisWidth)); setZValue(ZValues::axes); if (!titleIn.isEmpty()) { - title = std::make_unique<QGraphicsSimpleTextItem>(titleIn, chart); + title.reset(new QGraphicsSimpleTextItem(titleIn, this)); title->setFont(titleFont); title->setBrush(darkLabelColor); if (!horizontal) @@ -114,8 +114,8 @@ double StatsAxis::height() const (labelsBetweenTicks ? 0.0 : axisTickSizeHorizontal); } -StatsAxis::Label::Label(const QString &name, double pos, QtCharts::QChart *chart, const QFont &font) : - label(new QGraphicsSimpleTextItem(name, chart)), +StatsAxis::Label::Label(const QString &name, double pos, QGraphicsScene *scene, const QFont &font) : + label(createItem<QGraphicsSimpleTextItem>(scene, name)), pos(pos) { 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) { - labels.emplace_back(label, pos, chart, labelFont); + labels.emplace_back(label, pos, scene(), labelFont); } -StatsAxis::Tick::Tick(double pos, QtCharts::QChart *chart) : - item(new QGraphicsLineItem(chart)), +StatsAxis::Tick::Tick(double pos, QGraphicsScene *scene) : + item(createItemPtr<QGraphicsLineItem>(scene)), pos(pos) { item->setPen(QPen(axisColor, axisTickWidth)); @@ -138,7 +138,7 @@ StatsAxis::Tick::Tick(double pos, QtCharts::QChart *chart) : void StatsAxis::addTick(double pos) { - ticks.emplace_back(pos, chart); + ticks.emplace_back(pos, scene()); } 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) : - StatsAxis(chart, title, horizontal, false), +ValueAxis::ValueAxis(const QString &title, double min, double max, int decimals, bool horizontal) : + StatsAxis(title, horizontal, false), 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) : - ValueAxis(chart, title, 0.0, (double)count, 0, horizontal), +CountAxis::CountAxis(const QString &title, int count, bool horizontal) : + ValueAxis(title, 0.0, (double)count, 0, horizontal), count(count) { } @@ -328,27 +328,31 @@ void CountAxis::updateLabels() } } -CategoryAxis::CategoryAxis(QtCharts::QChart *chart, const QString &title, const std::vector<QString> &labelsIn, bool horizontal) : - StatsAxis(chart, title, horizontal, true) +CategoryAxis::CategoryAxis(const QString &title, const std::vector<QString> &labels, bool horizontal) : + StatsAxis(title, horizontal, true), + labelsText(labels) { - labels.reserve(labelsIn.size()); - ticks.reserve(labelsIn.size() + 1); + setRange(-0.5, static_cast<double>(labels.size()) + 0.5); +} + +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: labelsIn) { + for (const QString &s: labelsText) { addLabel(s, pos); addTick(pos + 0.5); pos += 1.0; } - setRange(-0.5, static_cast<double>(labelsIn.size()) - 0.5); -} - -void CategoryAxis::updateLabels() -{ } -HistogramAxis::HistogramAxis(QtCharts::QChart *chart, const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) : - StatsAxis(chart, title, horizontal, false), +HistogramAxis::HistogramAxis(const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) : + StatsAxis(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 @@ -545,7 +549,7 @@ static std::vector<HistogramAxisEntry> timeRangeToBins(double from, double to) return res; } -DateAxis::DateAxis(QtCharts::QChart *chart, const QString &title, double from, double to, bool horizontal) : - HistogramAxis(chart, title, timeRangeToBins(from, to), horizontal) +DateAxis::DateAxis(const QString &title, double from, double to, bool horizontal) : + HistogramAxis(title, timeRangeToBins(from, to), horizontal) { } diff --git a/stats/statsaxis.h b/stats/statsaxis.h index d9fdd6b41..dbcfb6fbd 100644 --- a/stats/statsaxis.h +++ b/stats/statsaxis.h @@ -11,11 +11,9 @@ #include <QGraphicsLineItem> #include <QValueAxis> -namespace QtCharts { - class QChart; -} +class QGraphicsScene; -class StatsAxis : QGraphicsLineItem { +class StatsAxis : public QGraphicsLineItem { public: virtual ~StatsAxis(); // 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 protected: - StatsAxis(QtCharts::QChart *chart, const QString &title, bool horizontal, bool labelsBetweenTicks); - QtCharts::QChart *chart; + StatsAxis(const QString &title, bool horizontal, bool labelsBetweenTicks); struct Label { std::unique_ptr<QGraphicsSimpleTextItem> label; 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; void addLabel(const QString &label, double pos); @@ -49,7 +46,7 @@ protected: struct Tick { std::unique_ptr<QGraphicsLineItem> item; double pos; - Tick(double pos, QtCharts::QChart *chart); + Tick(double pos, QGraphicsScene *scene); }; std::vector<Tick> ticks; void addTick(double pos); @@ -70,7 +67,7 @@ private: class ValueAxis : public StatsAxis { 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: double min, max; int decimals; @@ -79,7 +76,7 @@ private: class CountAxis : public ValueAxis { public: - CountAxis(QtCharts::QChart *chart, const QString &title, int count, bool horizontal); + CountAxis(const QString &title, int count, bool horizontal); private: int count; void updateLabels() override; @@ -87,8 +84,9 @@ private: class CategoryAxis : public StatsAxis { 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: + std::vector<QString> labelsText; void updateLabels(); }; @@ -100,7 +98,7 @@ struct HistogramAxisEntry { class HistogramAxis : public StatsAxis { 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: void updateLabels() override; std::vector<HistogramAxisEntry> bin_values; @@ -109,7 +107,7 @@ private: class DateAxis : public HistogramAxis { 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 diff --git a/stats/statscolors.h b/stats/statscolors.h index e9775e2a6..050b8a3ab 100644 --- a/stats/statscolors.h +++ b/stats/statscolors.h @@ -5,6 +5,7 @@ #include <QColor> +inline const QColor backgroundColor(Qt::white); inline const QColor fillColor(0x44, 0x76, 0xaa); inline const QColor borderColor(0x66, 0xb2, 0xff); inline const QColor highlightedColor(Qt::yellow); diff --git a/stats/statsgrid.cpp b/stats/statsgrid.cpp index fe99b1762..11b05882b 100644 --- a/stats/statsgrid.cpp +++ b/stats/statsgrid.cpp @@ -2,6 +2,7 @@ #include "statsgrid.h" #include "statsaxis.h" #include "statscolors.h" +#include "statshelper.h" #include "zvalues.h" #include <QChart> @@ -10,8 +11,8 @@ static const double gridWidth = 1.0; static const Qt::PenStyle gridStyle = Qt::SolidLine; -StatsGrid::StatsGrid(QtCharts::QChart *chart, const StatsAxis &xAxis, const StatsAxis &yAxis) - : chart(chart), xAxis(xAxis), yAxis(yAxis) +StatsGrid::StatsGrid(QGraphicsScene *scene, const StatsAxis &xAxis, const StatsAxis &yAxis) + : scene(scene), xAxis(xAxis), yAxis(yAxis) { } @@ -24,12 +25,12 @@ void StatsGrid::updatePositions() return; 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()->setZValue(ZValues::grid); } 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()->setZValue(ZValues::grid); } diff --git a/stats/statsgrid.h b/stats/statsgrid.h index 0fdb2a188..47b48b3ac 100644 --- a/stats/statsgrid.h +++ b/stats/statsgrid.h @@ -7,16 +7,14 @@ #include <QGraphicsLineItem> class StatsAxis; -namespace QtCharts { - class QChart; -}; +class QGraphicsScene; class StatsGrid { public: - StatsGrid(QtCharts::QChart *chart, const StatsAxis &xAxis, const StatsAxis &yAxis); + StatsGrid(QGraphicsScene *scene, const StatsAxis &xAxis, const StatsAxis &yAxis); void updatePositions(); private: - QtCharts::QChart *chart; + QGraphicsScene *scene; const StatsAxis &xAxis, &yAxis; std::vector<std::unique_ptr<QGraphicsLineItem>> lines; }; diff --git a/stats/statshelper.h b/stats/statshelper.h new file mode 100644 index 000000000..0ea39763d --- /dev/null +++ b/stats/statshelper.h @@ -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 diff --git a/stats/statsseries.cpp b/stats/statsseries.cpp index 5996508a4..2b7a5adea 100644 --- a/stats/statsseries.cpp +++ b/stats/statsseries.cpp @@ -2,8 +2,8 @@ #include "statsseries.h" #include "statsaxis.h" -StatsSeries::StatsSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) : - chart(chart), xAxis(xAxis), yAxis(yAxis) +StatsSeries::StatsSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) : + scene(scene), xAxis(xAxis), yAxis(yAxis) { } diff --git a/stats/statsseries.h b/stats/statsseries.h index 5c1833c8a..2494569e6 100644 --- a/stats/statsseries.h +++ b/stats/statsseries.h @@ -4,26 +4,20 @@ #ifndef STATS_SERIES_H #define STATS_SERIES_H -#include <QScatterSeries> +#include <QPointF> -namespace QtCharts { - class QChart; -} +class QGraphicsScene; class StatsAxis; -// We derive from a proper scatter series to get access to the map-to -// 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 { +class StatsSeries { public: - StatsSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis); + StatsSeries(QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis); virtual ~StatsSeries(); 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 void unhighlight() = 0; // Unhighlight any highlighted item. protected: - QtCharts::QChart *chart; + QGraphicsScene *scene; StatsAxis *xAxis, *yAxis; // May be zero for charts without axes (pie charts). QPointF toScreen(QPointF p); }; diff --git a/stats/statsview.cpp b/stats/statsview.cpp index f42296fc2..1533583ba 100644 --- a/stats/statsview.cpp +++ b/stats/statsview.cpp @@ -6,7 +6,9 @@ #include "pieseries.h" #include "scatterseries.h" #include "statsaxis.h" +#include "statscolors.h" #include "statsgrid.h" +#include "statshelper.h" #include "statsstate.h" #include "statstranslations.h" #include "statsvariables.h" @@ -15,12 +17,35 @@ #include "core/subsurface-qt/divelistnotifier.h" #include <cmath> -#include <QQuickItem> -#include <QAbstractSeries> -#include <QChart> +#include <QGraphicsScene> #include <QGraphicsSceneHoverEvent> #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 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 titleBorder = 2.0; // Border between title and chart -static const QUrl urlStatsView = QUrl(QStringLiteral("qrc:/qml/statsview.qml")); - -// 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), +StatsView::StatsView(QQuickItem *parent) : QQuickItem(parent), highlightedSeries(nullptr), xAxis(nullptr), - yAxis(nullptr), - eventFilter(this) + yAxis(nullptr) { - setResizeMode(QQuickWidget::SizeRootObjectToView); - // 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); - chart->setAcceptHoverEvents(true); - chart->legend()->setVisible(false); - } + setFlag(ItemHasContents, true); + + connect(&diveListNotifier, &DiveListNotifier::numShownChanged, this, &StatsView::replotIfVisible); + + setAcceptHoverEvents(true); QFont font; titleFont = QFont(font.family(), font.pointSize(), QFont::Light); // Make configurable } +StatsView::StatsView() : StatsView(nullptr) +{ +} + StatsView::~StatsView() { } -void StatsView::plotAreaChanged(const QRectF &r) +void StatsView::plotAreaChanged(const QSizeF &s) { - double left = r.x() + sceneBorder; - double top = r.y() + sceneBorder; - double right = r.right() - sceneBorder; - double bottom = r.bottom() - sceneBorder; + // Make sure that image is at least one pixel wide / high, otherwise + // the painter starts acting up. + int w = std::max(1, static_cast<int>(floor(s.width()))); + 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; if (title) @@ -140,8 +134,13 @@ void StatsView::replotIfVisible() plot(state); } -void StatsView::hover(QPointF pos) +void StatsView::hoverEnterEvent(QHoverEvent *) { +} + +void StatsView::hoverMoveEvent(QHoverEvent *event) +{ + QPointF pos(event->pos()); for (auto &series: series) { if (series->hover(pos)) { if (series.get() != highlightedSeries) { @@ -149,7 +148,7 @@ void StatsView::hover(QPointF pos) highlightedSeries->unhighlight(); highlightedSeries = series.get(); } - return; + return update(); } } @@ -157,13 +156,14 @@ void StatsView::hover(QPointF pos) if (highlightedSeries) { highlightedSeries->unhighlight(); highlightedSeries = nullptr; + update(); } } template <typename T, class... 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.back()->updatePositions(); return res; @@ -175,7 +175,7 @@ void StatsView::setTitle(const QString &s) title.reset(); return; } - title = std::make_unique<QGraphicsSimpleTextItem>(s, chart); + title = createItemPtr<QGraphicsSimpleTextItem>(&scene, s); title->setFont(titleFont); } @@ -183,7 +183,7 @@ void StatsView::updateTitlePos() { if (!title) return; - QRectF rect = chart->plotArea(); + QRectF rect = scene.sceneRect(); title->setPos(sceneBorder + (rect.width() - title->boundingRect().width()) / 2.0, sceneBorder); } @@ -191,7 +191,7 @@ void StatsView::updateTitlePos() template <typename T, class... 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); return res; } @@ -201,13 +201,11 @@ void StatsView::setAxes(StatsAxis *x, StatsAxis *y) xAxis = x; yAxis = y; if (x && y) - grid = std::make_unique<StatsGrid>(chart, *x, *y); + grid = std::make_unique<StatsGrid>(&scene, *x, *y); } void StatsView::reset() { - if (!chart) - return; highlightedSeries = nullptr; xAxis = yAxis = nullptr; legend.reset(); @@ -215,7 +213,6 @@ void StatsView::reset() quartileMarkers.clear(); regressionLines.clear(); histogramMarkers.clear(); - chart->removeAllSeries(); grid.reset(); axes.clear(); title.reset(); @@ -225,12 +222,13 @@ void StatsView::plot(const StatsState &stateIn) { state = stateIn; plotChart(); - plotAreaChanged(chart->plotArea()); + plotAreaChanged(scene.sceneRect().size()); + update(); } void StatsView::plotChart() { - if (!chart || !state.var1) + if (!state.var1) return; 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. if (showLegend) - legend = std::make_unique<Legend>(chart, data.vbinNames); + legend = createItemPtr<Legend>(&scene, data.vbinNames); std::vector<BarSeries::MultiItem> items; 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); if (showLegend) - legend = std::make_unique<Legend>(chart, series->binNames()); + legend = createItemPtr<Legend>(&scene, series->binNames()); } void StatsView::plotDiscreteBoxChart(const std::vector<dive *> &dives, @@ -689,17 +687,17 @@ void StatsView::plotDiscreteScatter(const std::vector<dive *> &dives, if (quartiles) { StatsQuartiles quartiles = StatsVariable::quartiles(array); if (quartiles.isValid()) { - quartileMarkers.emplace_back(x, quartiles.q1, chart, catAxis, valAxis); - quartileMarkers.emplace_back(x, quartiles.q2, chart, catAxis, valAxis); - quartileMarkers.emplace_back(x, quartiles.q3, chart, catAxis, valAxis); + quartileMarkers.emplace_back(x, quartiles.q1, &scene, catAxis, valAxis); + quartileMarkers.emplace_back(x, quartiles.q2, &scene, catAxis, valAxis); + quartileMarkers.emplace_back(x, quartiles.q3, &scene, catAxis, valAxis); } } x += 1.0; } } -StatsView::QuartileMarker::QuartileMarker(double pos, double value, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) : - item(new QGraphicsLineItem(chart)), +StatsView::QuartileMarker::QuartileMarker(double pos, double value, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) : + item(createItemPtr<QGraphicsLineItem>(scene)), xAxis(xAxis), yAxis(yAxis), pos(pos), value(value) @@ -719,8 +717,8 @@ void StatsView::QuartileMarker::updatePosition() x + quartileMarkerSize / 2.0, y); } -StatsView::RegressionLine::RegressionLine(double a, double b, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) : - item(new QGraphicsLineItem(chart)), +StatsView::RegressionLine::RegressionLine(double a, double b, QPen pen, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) : + item(createItemPtr<QGraphicsLineItem>(scene)), xAxis(xAxis), yAxis(yAxis), a(a), b(b) { @@ -751,8 +749,8 @@ void StatsView::RegressionLine::updatePosition() xAxis->toScreen(maxX), yAxis->toScreen(a * maxX + b)); } -StatsView::HistogramMarker::HistogramMarker(double val, bool horizontal, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) : - item(new QGraphicsLineItem(chart)), +StatsView::HistogramMarker::HistogramMarker(double val, bool horizontal, QPen pen, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) : + item(createItemPtr<QGraphicsLineItem>(scene)), xAxis(xAxis), yAxis(yAxis), 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) { - 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) { - 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. @@ -942,7 +940,7 @@ void StatsView::plotHistogramStackedChart(const std::vector<dive *> &dives, BarPlotData data(categoryBins, *valueBinner); if (showLegend) - legend = std::make_unique<Legend>(chart, data.vbinNames); + legend = createItemPtr<Legend>(&scene, data.vbinNames); CountAxis *valAxis = createCountAxis(data.maxCategoryCount, isHorizontal); diff --git a/stats/statsview.h b/stats/statsview.h index fd8ec1e2c..fbf51d46e 100644 --- a/stats/statsview.h +++ b/stats/statsview.h @@ -5,7 +5,10 @@ #include "statsstate.h" #include <memory> #include <QFont> -#include <QQuickWidget> +#include <QGraphicsScene> +#include <QImage> +#include <QPainter> +#include <QQuickItem> struct dive; struct StatsBinner; @@ -13,10 +16,6 @@ struct StatsBin; struct StatsState; struct StatsVariable; -namespace QtCharts { - class QAbstractSeries; - class QChart; -} class QGraphicsLineItem; class QGraphicsSimpleTextItem; class StatsSeries; @@ -26,21 +25,31 @@ class HistogramAxis; class StatsAxis; class StatsGrid; class Legend; +class QSGTexture; enum class ChartSubType : int; enum class StatsOperation : int; -class StatsView : public QQuickWidget { +class StatsView : public QQuickItem { Q_OBJECT public: - StatsView(QWidget *parent = NULL); + StatsView(); + StatsView(QQuickItem *parent); ~StatsView(); void plot(const StatsState &state); private slots: - void plotAreaChanged(const QRectF &plotArea); void replotIfVisible(); 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 setAxes(StatsAxis *x, StatsAxis *y); void plotBarChart(const std::vector<dive *> &dives, @@ -102,7 +111,7 @@ private: std::unique_ptr<QGraphicsLineItem> item; StatsAxis *xAxis, *yAxis; 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(); }; @@ -112,7 +121,7 @@ private: StatsAxis *xAxis, *yAxis; double a, b; // y = ax + b 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 @@ -122,14 +131,13 @@ private: double val; bool horizontal; 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 addHistogramMarker(double pos, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis); StatsState state; - QtCharts::QChart *chart; QFont titleFont; std::vector<std::unique_ptr<StatsAxis>> axes; std::unique_ptr<StatsGrid> grid; @@ -142,17 +150,8 @@ private: StatsSeries *highlightedSeries; StatsAxis *xAxis, *yAxis; - // This is unfortunate: we can't derive from QChart, because the chart is allocated by QML. - // Therefore, we have to listen to hover events using an events-filter. - // 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); + void hoverEnterEvent(QHoverEvent *event) override; + void hoverMoveEvent(QHoverEvent *event) override; }; #endif diff --git a/subsurface-helper.cpp b/subsurface-helper.cpp index 7120405dc..45cadcfa1 100644 --- a/subsurface-helper.cpp +++ b/subsurface-helper.cpp @@ -4,6 +4,7 @@ #include "map-widget/qmlmapwidgethelper.h" #include "qt-models/maplocationmodel.h" +#include "stats/statsview.h" #include "core/qt-gui.h" #include "core/settings/qPref.h" #include "core/ssrf.h" @@ -213,4 +214,5 @@ static void register_qml_types(QQmlEngine *engine) register_qml_type<MapWidgetHelper>("MapWidgetHelper"); register_qml_type<MapLocationModel>("MapLocationModel"); + register_qml_type<StatsView>("StatsView"); } |