From ff536e98fc3e88c3c4fd769229cf901a9f272be0 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Tue, 19 Jan 2021 09:54:39 +0100 Subject: [PATCH] statistics: don't replot chart when changing features Up to now, when the user changed the visibility of chart features (legend, quartiles, labels, etc.) the whole chart was replot. Instead, only change the visibility status of these items. After all, this modularity is one of the things the conversion to QSG was all about. Signed-off-by: Berthold Stoeger --- desktop-widgets/statswidget.cpp | 4 +- stats/pieseries.cpp | 20 ++--- stats/pieseries.h | 4 +- stats/statsview.cpp | 154 +++++++++++++++++--------------- stats/statsview.h | 24 ++--- 5 files changed, 107 insertions(+), 99 deletions(-) diff --git a/desktop-widgets/statswidget.cpp b/desktop-widgets/statswidget.cpp index e0090395f..33174778d 100644 --- a/desktop-widgets/statswidget.cpp +++ b/desktop-widgets/statswidget.cpp @@ -185,7 +185,9 @@ void StatsWidget::var2OperationChanged(int idx) void StatsWidget::featureChanged(int idx, bool status) { state.featureChanged(idx, status); - updateUi(); + // No need for a full chart replot - just show/hide the features + if (view) + view->updateFeatures(state); } void StatsWidget::showEvent(QShowEvent *e) diff --git a/stats/pieseries.cpp b/stats/pieseries.cpp index a100d66e0..50c76a8ed 100644 --- a/stats/pieseries.cpp +++ b/stats/pieseries.cpp @@ -17,7 +17,7 @@ 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(StatsView &view, const QString &name, int from, int count, int totalCount, - int bin_nr, int numBins, bool labels) : + int bin_nr, int numBins) : name(name), count(count) { @@ -30,14 +30,12 @@ PieSeries::Item::Item(StatsView &view, const QString &name, int from, int count, innerLabelPos = QPointF(cos(meanAngle) * innerLabelRadius, -sin(meanAngle) * innerLabelRadius); outerLabelPos = QPointF(cos(meanAngle) * outerLabelRadius, -sin(meanAngle) * outerLabelRadius); - if (labels) { - double percentage = count * 100.0 / totalCount; - QString innerLabelText = QStringLiteral("%1\%").arg(loc.toString(percentage, 'f', 1)); - innerLabel = view.createChartItem(ChartZValue::SeriesLabels, f, innerLabelText); + double percentage = count * 100.0 / totalCount; + QString innerLabelText = QStringLiteral("%1\%").arg(loc.toString(percentage, 'f', 1)); + innerLabel = view.createChartItem(ChartZValue::SeriesLabels, f, innerLabelText); - outerLabel = view.createChartItem(ChartZValue::SeriesLabels, f, name); - outerLabel->setColor(darkLabelColor); - } + outerLabel = view.createChartItem(ChartZValue::SeriesLabels, f, name); + outerLabel->setColor(darkLabelColor); } void PieSeries::Item::updatePositions(const QPointF ¢er, double radius) @@ -75,7 +73,7 @@ void PieSeries::Item::highlight(ChartPieItem &item, int bin_nr, bool highlight, } PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName, - const std::vector> &data, bool keepOrder, bool labels) : + const std::vector> &data, bool keepOrder) : StatsSeries(view, xAxis, yAxis), item(view.createChartItem(ChartZValue::Series, pieBorderWidth)), categoryName(categoryName), @@ -137,7 +135,7 @@ PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const int act = 0; for (auto it2 = sorted.begin(); it2 != it; ++it2) { int count = data[*it2].second; - items.emplace_back(view, data[*it2].first, act, count, totalCount, (int)items.size(), numBins, labels); + items.emplace_back(view, data[*it2].first, act, count, totalCount, (int)items.size(), numBins); act += count; } @@ -147,7 +145,7 @@ PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const 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(view, name, act, totalCount - act, totalCount, (int)items.size(), numBins, labels); + items.emplace_back(view, name, act, totalCount - act, totalCount, (int)items.size(), numBins); } } diff --git a/stats/pieseries.h b/stats/pieseries.h index a75909efe..0cb5e12cb 100644 --- a/stats/pieseries.h +++ b/stats/pieseries.h @@ -21,7 +21,7 @@ public: // 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(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName, - const std::vector> &data, bool keepOrder, bool labels); + const std::vector> &data, bool keepOrder); ~PieSeries(); void updatePositions() override; @@ -45,7 +45,7 @@ private: int count; QPointF innerLabelPos, outerLabelPos; // With respect to a (-1, -1)-(1, 1) rectangle. Item(StatsView &view, const QString &name, int from, int count, int totalCount, - int bin_nr, int numBins, bool labels); + int bin_nr, int numBins); void updatePositions(const QPointF ¢er, double radius); void highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins); }; diff --git a/stats/statsview.cpp b/stats/statsview.cpp index 5d7183fc3..e5cd0f8be 100644 --- a/stats/statsview.cpp +++ b/stats/statsview.cpp @@ -80,13 +80,17 @@ void StatsView::mouseReleaseEvent(QMouseEvent *) } } +// Define a hideable dummy QSG node that is used as a parent node to make +// all objects of a z-level visible / invisible. +using ZNode = HideableQSGNode; + class RootNode : public QSGNode { public: RootNode(QQuickWindow *w); std::unique_ptr backgroundNode; // solid background // We entertain one node per Z-level. - std::array, (size_t)ChartZValue::Count> zNodes; + std::array, (size_t)ChartZValue::Count> zNodes; }; RootNode::RootNode(QQuickWindow *w) @@ -99,7 +103,7 @@ RootNode::RootNode(QQuickWindow *w) appendChildNode(backgroundNode.get()); for (auto &zNode: zNodes) { - zNode.reset(new QSGNode); + zNode.reset(new ZNode(true)); appendChildNode(zNode.get()); } } @@ -268,8 +272,10 @@ void StatsView::plotAreaChanged(const QSizeF &s) marker->updatePosition(); if (regressionItem) regressionItem->updatePosition(); - for (auto &marker: histogramMarkers) - marker->updatePosition(); + if (meanMarker) + meanMarker->updatePosition(); + if (medianMarker) + medianMarker->updatePosition(); if (legend) legend->resize(); updateTitlePos(); @@ -369,6 +375,8 @@ void StatsView::reset() title.reset(); legend.reset(); regressionItem.reset(); + meanMarker.reset(); + medianMarker.reset(); // Mark clean and dirty chart items for deletion cleanItems.splice(deletedItems); @@ -376,7 +384,6 @@ void StatsView::reset() series.clear(); quartileMarkers.clear(); - histogramMarkers.clear(); grid.reset(); } @@ -384,10 +391,18 @@ void StatsView::plot(const StatsState &stateIn) { state = stateIn; plotChart(); + updateFeatures(); // Show / hide chart features, such as legend, etc. plotAreaChanged(boundingRect().size()); update(); } +void StatsView::updateFeatures(const StatsState &stateIn) +{ + state = stateIn; + updateFeatures(); + update(); +} + void StatsView::plotChart() { if (!state.var1) @@ -398,27 +413,26 @@ void StatsView::plotChart() switch (state.type) { case ChartType::DiscreteBar: return plotBarChart(dives, state.subtype, state.var1, state.var1Binner, state.var2, - state.var2Binner, state.labels, state.legend); + state.var2Binner); case ChartType::DiscreteValue: return plotValueChart(dives, state.subtype, state.var1, state.var1Binner, state.var2, - state.var2Operation, state.labels); + state.var2Operation); case ChartType::DiscreteCount: - return plotDiscreteCountChart(dives, state.subtype, state.var1, state.var1Binner, state.labels); + return plotDiscreteCountChart(dives, state.subtype, state.var1, state.var1Binner); case ChartType::Pie: - return plotPieChart(dives, state.var1, state.var1Binner, state.labels, state.legend); + return plotPieChart(dives, state.var1, state.var1Binner); case ChartType::DiscreteBox: return plotDiscreteBoxChart(dives, state.var1, state.var1Binner, state.var2); case ChartType::DiscreteScatter: - return plotDiscreteScatter(dives, state.var1, state.var1Binner, state.var2, state.quartiles); + return plotDiscreteScatter(dives, state.var1, state.var1Binner, state.var2); case ChartType::HistogramCount: - return plotHistogramCountChart(dives, state.subtype, state.var1, state.var1Binner, - state.labels, state.median, state.mean); + return plotHistogramCountChart(dives, state.subtype, state.var1, state.var1Binner); case ChartType::HistogramValue: return plotHistogramValueChart(dives, state.subtype, state.var1, state.var1Binner, state.var2, - state.var2Operation, state.labels); + state.var2Operation); case ChartType::HistogramStacked: return plotHistogramStackedChart(dives, state.subtype, state.var1, state.var1Binner, - state.var2, state.var2Binner, state.labels, state.legend); + state.var2, state.var2Binner); case ChartType::HistogramBox: return plotHistogramBoxChart(dives, state.var1, state.var1Binner, state.var2); case ChartType::ScatterPlot: @@ -431,6 +445,25 @@ void StatsView::plotChart() } } +void StatsView::updateFeatures() +{ + if (legend) + legend->setVisible(state.legend); + + // For labels, we are brutal: simply show/hide the whole z-level with the labels + if (rootNode) + rootNode->zNodes[(int)ChartZValue::SeriesLabels]->setVisible(state.labels); + + if (meanMarker) + meanMarker->setVisible(state.mean); + + if (medianMarker) + medianMarker->setVisible(state.median); + + for (ChartItemPtr &marker: quartileMarkers) + marker->setVisible(state.quartiles); +} + template CategoryAxis *StatsView::createCategoryAxis(const QString &name, const StatsBinner &binner, const std::vector &bins, bool isHorizontal) @@ -517,14 +550,12 @@ static std::vector makePercentageLabels(int count, int total, bool isHo // From a list of counts, make (count, label) pairs, where the label // formats the total number and the percentage of dives. -static std::vector>> makeCountLabels(const std::vector &counts, int total, - bool labels, bool isHorizontal) +static std::vector>> makeCountLabels(const std::vector &counts, int total, bool isHorizontal) { std::vector>> count_labels; count_labels.reserve(counts.size()); for (int count: counts) { - std::vector label = labels ? makePercentageLabels(count, total, isHorizontal) - : std::vector(); + std::vector label = makePercentageLabels(count, total, isHorizontal); count_labels.push_back(std::make_pair(count, label)); } return count_labels; @@ -533,7 +564,7 @@ static std::vector>> makeCountLabels(const s void StatsView::plotBarChart(const std::vector &dives, ChartSubType subType, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - const StatsVariable *valueVariable, const StatsBinner *valueBinner, bool labels, bool showLegend) + const StatsVariable *valueVariable, const StatsBinner *valueBinner) { if (!categoryBinner || !valueBinner) return; @@ -561,14 +592,13 @@ void StatsView::plotBarChart(const std::vector &dives, setAxes(catAxis, valAxis); // Paint legend first, because the bin-names will be moved away from. - if (showLegend) - legend = createChartItem(data.vbinNames); + legend = createChartItem(data.vbinNames); std::vector items; items.reserve(data.hbin_counts.size()); double pos = 0.0; for (auto &[hbin, counts, total]: data.hbin_counts) { - items.push_back({ pos - 0.5, pos + 0.5, makeCountLabels(counts, total, labels, isHorizontal), + items.push_back({ pos - 0.5, pos + 0.5, makeCountLabels(counts, total, isHorizontal), categoryBinner->formatWithUnit(*hbin) }); pos += 1.0; } @@ -645,8 +675,7 @@ static std::pair getMinMaxValue(const std::vector &b void StatsView::plotValueChart(const std::vector &dives, ChartSubType subType, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - const StatsVariable *valueVariable, StatsOperation valueAxisOperation, - bool labels) + const StatsVariable *valueVariable, StatsOperation valueAxisOperation) { if (!categoryBinner) return; @@ -681,8 +710,7 @@ void StatsView::plotValueChart(const std::vector &dives, if (res.isValid()) { double height = res.get(valueAxisOperation); QString value = QString("%L1").arg(height, 0, 'f', decimals); - std::vector label = labels ? std::vector { value } - : std::vector(); + std::vector label = std::vector { value }; items.push_back({ pos - 0.5, pos + 0.5, height, label, categoryBinner->formatWithUnit(*bin), res }); } @@ -713,8 +741,7 @@ static int getMaxCount(const std::vector &bins) void StatsView::plotDiscreteCountChart(const std::vector &dives, ChartSubType subType, - const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - bool labels) + const StatsVariable *categoryVariable, const StatsBinner *categoryBinner) { if (!categoryBinner) return; @@ -745,8 +772,7 @@ void StatsView::plotDiscreteCountChart(const std::vector &dives, items.reserve(categoryBins.size()); double pos = 0.0; for (auto const &[bin, count]: categoryBins) { - std::vector label = labels ? makePercentageLabels(count, total, isHorizontal) - : std::vector(); + std::vector label = makePercentageLabels(count, total, isHorizontal); items.push_back({ pos - 0.5, pos + 0.5, count, label, categoryBinner->formatWithUnit(*bin), total }); pos += 1.0; @@ -756,8 +782,7 @@ void StatsView::plotDiscreteCountChart(const std::vector &dives, } void StatsView::plotPieChart(const std::vector &dives, - const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - bool labels, bool showLegend) + const StatsVariable *categoryVariable, const StatsBinner *categoryBinner) { if (!categoryBinner) return; @@ -776,10 +801,9 @@ void StatsView::plotPieChart(const std::vector &dives, data.emplace_back(categoryBinner->formatWithUnit(*bin), count); bool keepOrder = categoryVariable->type() != StatsVariable::Type::Discrete; - PieSeries *series = createSeries(categoryVariable->name(), data, keepOrder, labels); + PieSeries *series = createSeries(categoryVariable->name(), data, keepOrder); - if (showLegend) - legend = createChartItem(series->binNames()); + legend = createChartItem(series->binNames()); } void StatsView::plotDiscreteBoxChart(const std::vector &dives, @@ -818,7 +842,7 @@ void StatsView::plotDiscreteBoxChart(const std::vector &dives, void StatsView::plotDiscreteScatter(const std::vector &dives, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - const StatsVariable *valueVariable, bool quartiles) + const StatsVariable *valueVariable) { if (!categoryBinner) return; @@ -846,26 +870,19 @@ void StatsView::plotDiscreteScatter(const std::vector &dives, for (const auto &[bin, array]: categoryBins) { for (auto [v, d]: array) series->append(d, x, v); - if (quartiles) { - StatsQuartiles quartiles = StatsVariable::quartiles(array); - if (quartiles.isValid()) { - quartileMarkers.push_back(createChartItem( - x, quartiles.q1, catAxis, valAxis)); - quartileMarkers.push_back(createChartItem( - x, quartiles.q2, catAxis, valAxis)); - quartileMarkers.push_back(createChartItem( - x, quartiles.q3, catAxis, valAxis)); - } + StatsQuartiles quartiles = StatsVariable::quartiles(array); + if (quartiles.isValid()) { + quartileMarkers.push_back(createChartItem( + x, quartiles.q1, catAxis, valAxis)); + quartileMarkers.push_back(createChartItem( + x, quartiles.q2, catAxis, valAxis)); + quartileMarkers.push_back(createChartItem( + x, quartiles.q3, catAxis, valAxis)); } x += 1.0; } } -void StatsView::addHistogramMarker(double pos, QColor color, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis) -{ - histogramMarkers.push_back(createChartItem(pos, isHorizontal, color, xAxis, yAxis)); -} - // Yikes, we get our data in different kinds of (bin, value) pairs. // To create a category axis from this, we have to templatify the function. template @@ -890,8 +907,7 @@ HistogramAxis *StatsView::createHistogramAxis(const QString &name, const StatsBi void StatsView::plotHistogramCountChart(const std::vector &dives, ChartSubType subType, - const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - bool labels, bool showMedian, bool showMean) + const StatsVariable *categoryVariable, const StatsBinner *categoryBinner) { if (!categoryBinner) return; @@ -924,8 +940,7 @@ void StatsView::plotHistogramCountChart(const std::vector &dives, for (auto const &[bin, count]: categoryBins) { double lowerBound = categoryBinner->lowerBoundToFloat(*bin); double upperBound = categoryBinner->upperBoundToFloat(*bin); - std::vector label = labels ? makePercentageLabels(count, total, isHorizontal) - : std::vector(); + std::vector label = makePercentageLabels(count, total, isHorizontal); items.push_back({ lowerBound, upperBound, count, label, categoryBinner->formatWithUnit(*bin), total }); @@ -934,24 +949,19 @@ void StatsView::plotHistogramCountChart(const std::vector &dives, createSeries(isHorizontal, categoryVariable->name(), items); if (categoryVariable->type() == StatsVariable::Type::Numeric) { - if (showMean) { - double mean = categoryVariable->mean(dives); - if (!std::isnan(mean)) - addHistogramMarker(mean, Qt::green, isHorizontal, xAxis, yAxis); - } - if (showMedian) { - double median = categoryVariable->quartiles(dives).q2; - if (!std::isnan(median)) - addHistogramMarker(median, Qt::red, isHorizontal, xAxis, yAxis); - } + double mean = categoryVariable->mean(dives); + if (!std::isnan(mean)) + meanMarker = createChartItem(mean, isHorizontal, Qt::green, xAxis, yAxis); + double median = categoryVariable->quartiles(dives).q2; + if (!std::isnan(median)) + medianMarker = createChartItem(median, isHorizontal, Qt::red, xAxis, yAxis); } } void StatsView::plotHistogramValueChart(const std::vector &dives, ChartSubType subType, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - const StatsVariable *valueVariable, StatsOperation valueAxisOperation, - bool labels) + const StatsVariable *valueVariable, StatsOperation valueAxisOperation) { if (!categoryBinner) return; @@ -990,8 +1000,7 @@ void StatsView::plotHistogramValueChart(const std::vector &dives, double lowerBound = categoryBinner->lowerBoundToFloat(*bin); double upperBound = categoryBinner->upperBoundToFloat(*bin); QString value = QString("%L1").arg(height, 0, 'f', decimals); - std::vector label = labels ? std::vector { value } - : std::vector(); + std::vector label = std::vector { value }; items.push_back({ lowerBound, upperBound, height, label, categoryBinner->formatWithUnit(*bin), res }); } @@ -1002,7 +1011,7 @@ void StatsView::plotHistogramValueChart(const std::vector &dives, void StatsView::plotHistogramStackedChart(const std::vector &dives, ChartSubType subType, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - const StatsVariable *valueVariable, const StatsBinner *valueBinner, bool labels, bool showLegend) + const StatsVariable *valueVariable, const StatsBinner *valueBinner) { if (!categoryBinner || !valueBinner) return; @@ -1018,8 +1027,7 @@ void StatsView::plotHistogramStackedChart(const std::vector &dives, *categoryBinner, categoryBins, !isHorizontal); BarPlotData data(categoryBins, *valueBinner); - if (showLegend) - legend = createChartItem(data.vbinNames); + legend = createChartItem(data.vbinNames); CountAxis *valAxis = createCountAxis(data.maxCategoryCount, isHorizontal); @@ -1034,7 +1042,7 @@ void StatsView::plotHistogramStackedChart(const std::vector &dives, for (auto &[hbin, counts, total]: data.hbin_counts) { double lowerBound = categoryBinner->lowerBoundToFloat(*hbin); double upperBound = categoryBinner->upperBoundToFloat(*hbin); - items.push_back({ lowerBound, upperBound, makeCountLabels(counts, total, labels, isHorizontal), + items.push_back({ lowerBound, upperBound, makeCountLabels(counts, total, isHorizontal), categoryBinner->formatWithUnit(*hbin) }); } diff --git a/stats/statsview.h b/stats/statsview.h index dab666ec8..0af91b382 100644 --- a/stats/statsview.h +++ b/stats/statsview.h @@ -43,7 +43,8 @@ public: ~StatsView(); void plot(const StatsState &state); - QQuickWindow *w() const; // Make window available to items + void updateFeatures(const StatsState &state); // Updates the visibility of chart features, such as legend, regression, etc. + QQuickWindow *w() const; // Make window available to items QSizeF size() const; QRectF plotArea() const; void addQSGNode(QSGNode *node, ChartZValue z); // Must only be called in render thread! @@ -75,39 +76,39 @@ private: void plotBarChart(const std::vector &dives, ChartSubType subType, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - const StatsVariable *valueVariable, const StatsBinner *valueBinner, bool labels, bool legend); + const StatsVariable *valueVariable, const StatsBinner *valueBinner); void plotValueChart(const std::vector &dives, ChartSubType subType, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - const StatsVariable *valueVariable, StatsOperation valueAxisOperation, bool labels); + const StatsVariable *valueVariable, StatsOperation valueAxisOperation); void plotDiscreteCountChart(const std::vector &dives, ChartSubType subType, - const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, bool labels); + const StatsVariable *categoryVariable, const StatsBinner *categoryBinner); void plotPieChart(const std::vector &dives, - const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, bool labels, bool legend); + const StatsVariable *categoryVariable, const StatsBinner *categoryBinner); void plotDiscreteBoxChart(const std::vector &dives, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, const StatsVariable *valueVariable); void plotDiscreteScatter(const std::vector &dives, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - const StatsVariable *valueVariable, bool quartiles); + const StatsVariable *valueVariable); void plotHistogramCountChart(const std::vector &dives, ChartSubType subType, - const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - bool labels, bool showMedian, bool showMean); + const StatsVariable *categoryVariable, const StatsBinner *categoryBinner); void plotHistogramValueChart(const std::vector &dives, ChartSubType subType, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - const StatsVariable *valueVariable, StatsOperation valueAxisOperation, bool labels); + const StatsVariable *valueVariable, StatsOperation valueAxisOperation); void plotHistogramStackedChart(const std::vector &dives, ChartSubType subType, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, - const StatsVariable *valueVariable, const StatsBinner *valueBinner, bool labels, bool legend); + const StatsVariable *valueVariable, const StatsBinner *valueBinner); void plotHistogramBoxChart(const std::vector &dives, const StatsVariable *categoryVariable, const StatsBinner *categoryBinner, const StatsVariable *valueVariable); void plotScatter(const std::vector &dives, const StatsVariable *categoryVariable, const StatsVariable *valueVariable); void setTitle(const QString &); void updateTitlePos(); // After resizing, set title to correct position void plotChart(); + void updateFeatures(); // Updates the visibility of chart features, such as legend, regression, etc. template T *createSeries(Args&&... args); @@ -126,14 +127,13 @@ private: // Helper functions to add feature to the chart void addLineMarker(double pos, double low, double high, const QPen &pen, bool isHorizontal); - void addHistogramMarker(double pos, QColor color, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis); StatsState state; QFont titleFont; std::vector> series; std::unique_ptr grid; std::vector> quartileMarkers; - std::vector> histogramMarkers; + ChartItemPtr medianMarker, meanMarker; StatsSeries *highlightedSeries; StatsAxis *xAxis, *yAxis; ChartItemPtr title;