diff options
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"); } |