diff options
author | Berthold Stoeger <bstoeger@mail.tuwien.ac.at> | 2021-01-20 14:36:59 +0100 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2021-02-13 13:02:54 -0800 |
commit | 18a5b5b5930247ed880cfbe8f94778b4c19b0bb2 (patch) | |
tree | cdaf28e252a7e512774379e2f12e981e1f68ccb3 | |
parent | 622e9ba373f898a0d51fc009f3615d83ffd3a7fc (diff) | |
download | subsurface-18a5b5b5930247ed880cfbe8f94778b4c19b0bb2.tar.gz |
statistics: use dive instead of count bins
If we want to make bar charts selectable (when clicking on a
bar select the dives the bar represents), then we must store
the dives behind bars. Therefore, use dive-based bins instead
of count based bins in bar charts and pie charts. This gave
some churn because every structure where a count is stored
has to be changed to store a vector of dives. Try to use
move semantics where possible to avoid duplication of dive
lists.
On a positive note, the count_dives() function of the
binners can now be removed, since it is unused.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
-rw-r--r-- | stats/barseries.cpp | 57 | ||||
-rw-r--r-- | stats/barseries.h | 26 | ||||
-rw-r--r-- | stats/pieseries.cpp | 43 | ||||
-rw-r--r-- | stats/pieseries.h | 7 | ||||
-rw-r--r-- | stats/statsvariables.cpp | 50 | ||||
-rw-r--r-- | stats/statsvariables.h | 4 | ||||
-rw-r--r-- | stats/statsview.cpp | 102 |
7 files changed, 136 insertions, 153 deletions
diff --git a/stats/barseries.cpp b/stats/barseries.cpp index 9e8d5d133..8767a04db 100644 --- a/stats/barseries.cpp +++ b/stats/barseries.cpp @@ -40,25 +40,24 @@ BarSeries::BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries::BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, bool horizontal, const QString &categoryName, - const std::vector<CountItem> &items) : + std::vector<CountItem> items) : BarSeries(view, xAxis, yAxis, horizontal, false, categoryName, nullptr, std::vector<QString>()) { - for (const CountItem &item: items) { + for (CountItem &item: items) { StatsOperationResults res; - res.count = item.count; - double value = item.count; - add_item(item.lowerBound, item.upperBound, makeSubItems(value, item.label), + double value = (double)item.dives.size(); + add_item(item.lowerBound, item.upperBound, makeSubItems({ value, std::move(item.dives), std::move(item.label) }), item.binName, res, item.total, horizontal, stacked); } } BarSeries::BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, bool horizontal, const QString &categoryName, const StatsVariable *valueVariable, - const std::vector<ValueItem> &items) : + std::vector<ValueItem> items) : BarSeries(view, 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), + for (ValueItem &item: items) { + add_item(item.lowerBound, item.upperBound, makeSubItems({ item.value, std::move(item.res.dives), std::move(item.label) }), item.binName, item.res, -1, horizontal, stacked); } } @@ -66,19 +65,19 @@ BarSeries::BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries::BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable, std::vector<QString> valueBinNames, - const std::vector<MultiItem> &items) : + std::vector<MultiItem> items) : BarSeries(view, xAxis, yAxis, horizontal, stacked, categoryName, valueVariable, std::move(valueBinNames)) { - for (const MultiItem &item: items) { + for (MultiItem &item: items) { StatsOperationResults res; - std::vector<std::pair<double, std::vector<QString>>> valuesLabels; - valuesLabels.reserve(item.countLabels.size()); + std::vector<SubItemDesc> subitems; + subitems.reserve(item.items.size()); int total = 0; - for (auto &[count, label]: item.countLabels) { - valuesLabels.push_back({ static_cast<double>(count), std::move(label) }); - total += count; + for (auto &[dives, label]: item.items) { + total += (int)dives.size(); + subitems.push_back({ static_cast<double>(dives.size()), std::move(dives), std::move(label) }); } - add_item(item.lowerBound, item.upperBound, makeSubItems(valuesLabels), + add_item(item.lowerBound, item.upperBound, makeSubItems(std::move(subitems)), item.binName, res, total, horizontal, stacked); } } @@ -235,15 +234,16 @@ void BarSeries::SubItem::updatePosition(BarSeries *series, bool horizontal, bool label->updatePosition(horizontal, stacked, rect, bin_nr, binCount, fill); } -std::vector<BarSeries::SubItem> BarSeries::makeSubItems(const std::vector<std::pair<double, std::vector<QString>>> &values) const +std::vector<BarSeries::SubItem> BarSeries::makeSubItems(std::vector<SubItemDesc> items) const { std::vector<SubItem> res; - res.reserve(values.size()); + res.reserve(items.size()); double from = 0.0; int bin_nr = 0; - for (auto &[v, label]: values) { + for (auto &[v, dives, label]: items) { if (v > 0.0) { res.push_back({ view.createChartItem<ChartBarItem>(ChartZValue::Series, barBorderWidth, horizontal), + std::move(dives), {}, from, from + v, bin_nr }); if (!label.empty()) res.back().label = std::make_unique<BarLabel>(view, label, bin_nr, binCount()); @@ -255,9 +255,9 @@ std::vector<BarSeries::SubItem> BarSeries::makeSubItems(const std::vector<std::p return res; } -std::vector<BarSeries::SubItem> BarSeries::makeSubItems(double value, const std::vector<QString> &label) const +std::vector<BarSeries::SubItem> BarSeries::makeSubItems(SubItemDesc item) const { - return makeSubItems(std::vector<std::pair<double, std::vector<QString>>>{ { value, label } }); + return makeSubItems(std::vector<SubItemDesc>{ std::move(item) }); } int BarSeries::binCount() const @@ -329,7 +329,8 @@ static std::vector<QString> makeCountInfo(const QString &binName, const QString // Format information in a value bar chart: the name of the bin and the value with unit. static std::vector<QString> makeValueInfo(const QString &binName, const QString &axisName, - const StatsVariable &valueVariable, const StatsOperationResults &values) + const StatsVariable &valueVariable, const StatsOperationResults &values, + int count) { QLocale loc; int decimals = valueVariable.decimals(); @@ -338,7 +339,7 @@ static std::vector<QString> makeValueInfo(const QString &binName, const QString std::vector<QString> res; res.reserve(operations.size() + 3); res.push_back(QStringLiteral("%1: %2").arg(axisName, binName)); - res.push_back(QStringLiteral("%1: %2").arg(StatsTranslations::tr("Count"), loc.toString(values.count))); + res.push_back(QStringLiteral("%1: %2").arg(StatsTranslations::tr("Count"), loc.toString(count))); res.push_back(QStringLiteral("%1: ").arg(valueVariable.name())); for (StatsOperation op: operations) { QString valueFormatted = loc.toString(values.get(op), 'f', decimals); @@ -349,19 +350,23 @@ static std::vector<QString> makeValueInfo(const QString &binName, const QString std::vector<QString> BarSeries::makeInfo(const Item &item, int subitem_idx) const { + if (item.subitems.empty()) + return {}; if (!valueBinNames.empty() && valueVariable) { if (subitem_idx < 0 || subitem_idx >= (int)item.subitems.size()) return {}; const SubItem &subitem = item.subitems[subitem_idx]; if (subitem.bin_nr < 0 || subitem.bin_nr >= (int)valueBinNames.size()) return {}; - int count = (int)lrint(subitem.value_to - subitem.value_from); + int count = (int)subitem.dives.size(); return makeCountInfo(item.binName, categoryName, valueBinNames[subitem.bin_nr], valueVariable->name(), count, item.total); } else if (valueVariable) { - return makeValueInfo(item.binName, categoryName, *valueVariable, item.res); + int count = (int)item.subitems[0].dives.size(); + return makeValueInfo(item.binName, categoryName, *valueVariable, item.res, count); } else { - return makeCountInfo(item.binName, categoryName, QString(), QString(), item.res.count, item.total); + int count = (int)item.subitems[0].dives.size(); + return makeCountInfo(item.binName, categoryName, QString(), QString(), count, item.total); } } diff --git a/stats/barseries.h b/stats/barseries.h index 9f9586fe8..8c83648ea 100644 --- a/stats/barseries.h +++ b/stats/barseries.h @@ -25,7 +25,7 @@ public: // based charts and for stacked bar charts with multiple items. struct CountItem { double lowerBound, upperBound; - int count; + std::vector<dive *> dives; std::vector<QString> label; QString binName; int total; @@ -35,11 +35,15 @@ public: double value; std::vector<QString> label; QString binName; - StatsOperationResults res; + StatsOperationResults res; // Contains the dives }; struct MultiItem { + struct Item { + std::vector<dive *> dives; + std::vector<QString> label; + }; double lowerBound, upperBound; - std::vector<std::pair<int, std::vector<QString>>> countLabels; + std::vector<Item> items; QString binName; }; @@ -52,14 +56,14 @@ public: // are ordered identically. BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, bool horizontal, const QString &categoryName, - const std::vector<CountItem> &items); + std::vector<CountItem> items); BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, bool horizontal, const QString &categoryName, const StatsVariable *valueVariable, - const std::vector<ValueItem> &items); + std::vector<ValueItem> items); BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable, std::vector<QString> valueBinNames, - const std::vector<MultiItem> &items); + std::vector<MultiItem> items); ~BarSeries(); void updatePositions() override; @@ -93,6 +97,7 @@ private: struct SubItem { ChartItemPtr<ChartBarItem> item; + std::vector<dive *> dives; std::unique_ptr<BarLabel> label; double value_from; double value_to; @@ -127,8 +132,13 @@ private: const StatsVariable *valueVariable; // null: this is count based std::vector<QString> valueBinNames; Index highlighted; - std::vector<SubItem> makeSubItems(double value, const std::vector<QString> &label) const; - std::vector<SubItem> makeSubItems(const std::vector<std::pair<double, std::vector<QString>>> &values) const; + struct SubItemDesc { + double v; + std::vector<dive *> dives; + std::vector<QString> label; + }; + std::vector<SubItem> makeSubItems(SubItemDesc item) const; + std::vector<SubItem> makeSubItems(std::vector<SubItemDesc> items) const; void add_item(double lowerBound, double upperBound, std::vector<SubItem> subitems, const QString &binName, const StatsOperationResults &res, int total, bool horizontal, bool stacked); diff --git a/stats/pieseries.cpp b/stats/pieseries.cpp index d837abe5a..f8c5aa980 100644 --- a/stats/pieseries.cpp +++ b/stats/pieseries.cpp @@ -16,14 +16,15 @@ 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(StatsView &view, const QString &name, int from, int count, int totalCount, +PieSeries::Item::Item(StatsView &view, const QString &name, int from, std::vector<dive *> divesIn, int totalCount, int bin_nr, int numBins) : name(name), - count(count) + dives(std::move(divesIn)) { QFont f; // make configurable QLocale loc; + int count = (int)dives.size(); angleFrom = static_cast<double>(from) / totalCount; angleTo = static_cast<double>(from + count) / totalCount; double meanAngle = M_PI / 2.0 - (from + count / 2.0) / totalCount * M_PI * 2.0; // Note: "-" because we go CW. @@ -74,7 +75,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<std::pair<QString, int>> &data, bool keepOrder) : + std::vector<std::pair<QString, std::vector<dive *>>> data, bool keepOrder) : StatsSeries(view, xAxis, yAxis), item(view.createChartItem<ChartPieItem>(ChartZValue::Series, pieBorderWidth)), categoryName(categoryName), @@ -89,8 +90,8 @@ PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const // Easier to read than std::accumulate totalCount = 0; - for (const auto &[name, count]: data) - totalCount += count; + for (const auto &[name, dives]: data) + totalCount += (int)dives.size(); // First of all, sort from largest to smalles slice. Instead // of sorting the initial array, sort a list of indices, so that @@ -102,19 +103,19 @@ PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const // - do a lexicographic sort by (count, idx) so that for equal counts the order is preserved. std::sort(sorted.begin(), sorted.end(), [&data](int idx1, int idx2) - { return std::make_tuple(-data[idx1].second, idx1) < - std::make_tuple(-data[idx2].second, idx2); }); + { return std::make_tuple(-data[idx1].second.size(), idx1) < + std::make_tuple(-data[idx2].second.size(), idx2); }); auto it = std::find_if(sorted.begin(), sorted.end(), [count=totalCount, &data](int idx) - { return data[idx].second * 100 / count < smallest_slice_percentage; }); + { return (int)data[idx].second.size() * 100 / count < smallest_slice_percentage; }); if (it - sorted.begin() < min_slices) { // Take minimum amount of slices below 50%... int sum = 0; for (auto it2 = sorted.begin(); it2 != it; ++it2) - sum += data[*it2].second; + sum += (int)data[*it2].second.size(); while(it != sorted.end() && sum * 2 < totalCount && it - sorted.begin() < min_slices) { - sum += data[*it].second; + sum += (int)data[*it].second.size(); ++it; } } @@ -135,18 +136,24 @@ PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const items.reserve(numBins); 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); + int count = (int)data[*it2].second.size(); + items.emplace_back(view, data[*it2].first, act, std::move(data[*it2].second), + totalCount, (int)items.size(), numBins); act += count; } // Register the items of the "other" group. if (it != sorted.end()) { + std::vector<dive *> otherDives; + otherDives.reserve(totalCount - act); other.reserve(sorted.end() - it); - for (auto it2 = it; it2 != sorted.end(); ++it2) - other.push_back({ data[*it2].first, data[*it2].second }); + for (auto it2 = it; it2 != sorted.end(); ++it2) { + other.push_back({ data[*it2].first, (int)data[*it2].second.size() }); + for (dive *d: data[*it2].second) + otherDives.push_back(d); + } QString name = StatsTranslations::tr("other (%1 items)").arg(other.size()); - items.emplace_back(view, name, act, totalCount - act, totalCount, (int)items.size(), numBins); + items.emplace_back(view, name, act, std::move(otherDives), totalCount, (int)items.size(), numBins); } } @@ -212,15 +219,15 @@ std::vector<QString> PieSeries::makeInfo(int idx) const // This is the "other" bin. Format all these items and an overview item. res.reserve(other.size() + 1); res.push_back(QString("%1: %2").arg(StatsTranslations::tr("other"), - makePercentageLine(items[idx].count, totalCount))); + makePercentageLine((int)items[idx].dives.size(), totalCount))); for (const OtherItem &item: other) res.push_back(QString("%1: %2").arg(item.name, - makePercentageLine(item.count, totalCount))); + makePercentageLine((int)item.count, totalCount))); } else { // A "normal" item. res.reserve(2); res.push_back(QStringLiteral("%1: %2").arg(categoryName, items[idx].name)); - res.push_back(makePercentageLine(items[idx].count, totalCount)); + res.push_back(makePercentageLine((int)items[idx].dives.size(), totalCount)); } return res; } diff --git a/stats/pieseries.h b/stats/pieseries.h index 0cb5e12cb..4fbf689bd 100644 --- a/stats/pieseries.h +++ b/stats/pieseries.h @@ -10,6 +10,7 @@ #include <vector> #include <QString> +struct dive; struct InformationBox; struct ChartPieItem; struct ChartTextItem; @@ -21,7 +22,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<std::pair<QString, int>> &data, bool keepOrder); + std::vector<std::pair<QString, std::vector<dive *>>> data, bool keepOrder); ~PieSeries(); void updatePositions() override; @@ -42,9 +43,9 @@ private: ChartItemPtr<ChartTextItem> innerLabel, outerLabel; QString name; double angleFrom, angleTo; // In fraction of total - int count; + std::vector<dive *> dives; QPointF innerLabelPos, outerLabelPos; // With respect to a (-1, -1)-(1, 1) rectangle. - Item(StatsView &view, const QString &name, int from, int count, int totalCount, + Item(StatsView &view, const QString &name, int from, std::vector<dive *> dives, int totalCount, 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/statsvariables.cpp b/stats/statsvariables.cpp index 8b2dc9bc3..498314ae7 100644 --- a/stats/statsvariables.cpp +++ b/stats/statsvariables.cpp @@ -434,15 +434,16 @@ StatsOperationResults StatsVariable::applyOperations(const std::vector<dive *> & std::vector<StatsValue> val = values(dives); double sumTime = 0.0; - res.count = (int)val.size(); + res.dives.reserve(val.size()); res.median = quartiles(val).q2; - if (res.count <= 0) + if (val.empty()) return res; res.min = std::numeric_limits<double>::max(); res.max = std::numeric_limits<double>::lowest(); for (auto [v, d]: val) { + res.dives.push_back(d); res.sum += v; res.mean += v; sumTime += d->duration.seconds; @@ -453,19 +454,19 @@ StatsOperationResults StatsVariable::applyOperations(const std::vector<dive *> & res.max = v; } - res.mean /= res.count; + res.mean /= val.size(); res.timeWeightedMean /= sumTime; return res; } StatsOperationResults::StatsOperationResults() : - count(0), median(0.0), mean(0.0), timeWeightedMean(0.0), sum(0.0), min(0.0), max(0.0) + median(0.0), mean(0.0), timeWeightedMean(0.0), sum(0.0), min(0.0), max(0.0) { } bool StatsOperationResults::isValid() const { - return count > 0; + return !dives.empty(); } double StatsOperationResults::get(StatsOperation op) const @@ -584,7 +585,6 @@ struct SimpleBinner : public StatsBinner { public: using Type = decltype(Bin::value); std::vector<StatsBinDives> bin_dives(const std::vector<dive *> &dives, bool fill_empty) const override; - std::vector<StatsBinCount> count_dives(const std::vector<dive *> &dives, bool fill_empty) const override; const Binner &derived() const { return static_cast<const Binner &>(*this); } @@ -664,24 +664,6 @@ std::vector<StatsBinDives> SimpleBinner<Binner, Bin>::bin_dives(const std::vecto return value_vector_to_bin_vector<Bin>(*this, value_bins, fill_empty); } -template<typename Binner, typename Bin> -std::vector<StatsBinCount> SimpleBinner<Binner, Bin>::count_dives(const std::vector<dive *> &dives, bool fill_empty) const -{ - // First, collect a value / counts vector and then produce the final vector - // out of that. I wonder if that is premature optimization? - using Pair = std::pair<Type, int>; - std::vector<Pair> value_bins; - for (const dive *d: dives) { - Type value = derived().to_bin_value(d); - if (is_invalid_value(value)) - continue; - register_bin_value(value_bins, value, [](int &i){ ++i; }); - } - - // Now, turn that into our result array with allocated bin objects. - return value_vector_to_bin_vector<Bin>(*this, value_bins, fill_empty); -} - // A simple binner (see above) that works on continuous (or numeric) variables // and can return bin-ranges. The binner must implement an inc() function // that turns a bin into the next-higher bin. @@ -778,7 +760,6 @@ struct MultiBinner : public StatsBinner { public: using Type = decltype(Bin::value); std::vector<StatsBinDives> bin_dives(const std::vector<dive *> &dives, bool fill_empty) const override; - std::vector<StatsBinCount> count_dives(const std::vector<dive *> &dives, bool fill_empty) const override; const Binner &derived() const { return static_cast<const Binner &>(*this); } @@ -807,25 +788,6 @@ std::vector<StatsBinDives> MultiBinner<Binner, Bin>::bin_dives(const std::vector return value_vector_to_bin_vector<Bin>(*this, value_bins, false); } -template<typename Binner, typename Bin> -std::vector<StatsBinCount> MultiBinner<Binner, Bin>::count_dives(const std::vector<dive *> &dives, bool) const -{ - // First, collect a value / counts vector and then produce the final vector - // out of that. I wonder if that is premature optimization? - using Pair = std::pair<Type, int>; - std::vector<Pair> value_bins; - for (const dive *d: dives) { - for (const Type &s: derived().to_bin_values(d)) { - if (is_invalid_value(s)) - continue; - register_bin_value(value_bins, s, [](int &i){ ++i; }); - } - } - - // Now, turn that into our result array with allocated bin objects. - return value_vector_to_bin_vector<Bin>(*this, value_bins, false); -} - // A binner that works on string-based bins whereby each dive can // produce multiple strings (e.g. dive buddies). The binner must // feature a to_bin_values() function that produces a vector of diff --git a/stats/statsvariables.h b/stats/statsvariables.h index 3469033e2..845ee1a67 100644 --- a/stats/statsvariables.h +++ b/stats/statsvariables.h @@ -33,7 +33,7 @@ enum class StatsOperation : int { // Results of the above operations struct StatsOperationResults { - int count; + std::vector<dive *> dives; double median; double mean; double timeWeightedMean; @@ -77,7 +77,6 @@ struct StatsBinValue { }; using StatsBinDives = StatsBinValue<std::vector<dive *>>; using StatsBinValues = StatsBinValue<std::vector<StatsValue>>; -using StatsBinCount = StatsBinValue<int>; using StatsBinQuartiles = StatsBinValue<StatsQuartiles>; using StatsBinOp = StatsBinValue<StatsOperationResults>; @@ -89,7 +88,6 @@ struct StatsBinner { // The binning functions have a parameter "fill_empty". If true, missing // bins in the range will be filled with empty bins. This only works for continuous variables. virtual std::vector<StatsBinDives> bin_dives(const std::vector<dive *> &dives, bool fill_empty) const = 0; - virtual std::vector<StatsBinCount> count_dives(const std::vector<dive *> &dives, bool fill_empty) const = 0; // Note: these functions will crash with an exception if passed incompatible bins! virtual QString format(const StatsBin &bin) const = 0; diff --git a/stats/statsview.cpp b/stats/statsview.cpp index eec7b4ba5..133f39219 100644 --- a/stats/statsview.cpp +++ b/stats/statsview.cpp @@ -534,10 +534,10 @@ CountAxis *StatsView::createCountAxis(int maxVal, bool isHorizontal) } // For "two-dimensionally" binned plots (eg. stacked bar or grouped bar): -// Counts for each bin on the independent variable, including the total counts for that bin. -struct BinCounts { +// Dives for each bin on the independent variable, including the total counts for that bin. +struct BinDives { StatsBinPtr bin; - std::vector<int> counts; + std::vector<std::vector<dive *>> dives; int total; }; @@ -547,7 +547,7 @@ struct BinCounts { // Perhaps we should determine the bins of all dives first and then // query the counts for precisely those bins? struct BarPlotData { - std::vector<BinCounts> hbin_counts; // For each category bin the counts for all value bins + std::vector<BinDives> hbins; // For each category bin the counts for all value bins std::vector<StatsBinPtr> vbins; std::vector<QString> vbinNames; int maxCount; // Highest count of any bin-combination @@ -561,8 +561,8 @@ BarPlotData::BarPlotData(std::vector<StatsBinDives> &categoryBins, const StatsBi { for (auto &[bin, dives]: categoryBins) { // This moves the bin - the original pointer is invalidated - hbin_counts.push_back({ std::move(bin), std::vector<int>(vbins.size(), 0), 0 }); - for (auto &[vbin, count]: valueBinner.count_dives(dives, false)) { + hbins.push_back({ std::move(bin), std::vector<std::vector<dive *>>(vbins.size()), 0 }); + for (auto &[vbin, dives]: valueBinner.bin_dives(dives, false)) { // Note: we assume that the bins are sorted! auto it = std::lower_bound(vbins.begin(), vbins.end(), vbin, [] (const StatsBinPtr &p, const StatsBinPtr &bin) @@ -573,15 +573,16 @@ BarPlotData::BarPlotData(std::vector<StatsBinDives> &categoryBins, const StatsBi // Attn: this invalidates "vbin", which must not be used henceforth! vbins.insert(it, std::move(vbin)); // Fix the old arrays - for (auto &[bin, v, total]: hbin_counts) - v.insert(v.begin() + pos, 0); + for (auto &[bin, v, total]: hbins) + v.insert(v.begin() + pos, std::vector<dive *>()); } - hbin_counts.back().counts[pos] = count; - hbin_counts.back().total += count; + int count = (int)dives.size(); + hbins.back().dives[pos] = std::move(dives); + hbins.back().total += count; if (count > maxCount) maxCount = count; } - maxCategoryCount = std::max(maxCategoryCount, hbin_counts.back().total); + maxCategoryCount = std::max(maxCategoryCount, hbins.back().total); } vbinNames.reserve(vbins.size()); @@ -601,17 +602,17 @@ static std::vector<QString> makePercentageLabels(int count, int total, bool isHo return { countString, percentageString }; } -// From a list of counts, make (count, label) pairs, where the label +// From a list of dive bins, make (dives, label) pairs, where the label // formats the total number and the percentage of dives. -static std::vector<std::pair<int, std::vector<QString>>> makeCountLabels(const std::vector<int> &counts, int total, bool isHorizontal) +static std::vector<BarSeries::MultiItem::Item> makeMultiItems(std::vector<std::vector<dive *>> bins, int total, bool isHorizontal) { - std::vector<std::pair<int, std::vector<QString>>> count_labels; - count_labels.reserve(counts.size()); - for (int count: counts) { - std::vector<QString> label = makePercentageLabels(count, total, isHorizontal); - count_labels.push_back(std::make_pair(count, label)); + std::vector<BarSeries::MultiItem::Item> res; + res.reserve(bins.size()); + for (std::vector<dive*> &bin: bins) { + std::vector<QString> label = makePercentageLabels((int)bin.size(), total, isHorizontal); + res.push_back({ std::move(bin), std::move(label) }); } - return count_labels; + return res; } void StatsView::plotBarChart(const std::vector<dive *> &dives, @@ -648,15 +649,15 @@ void StatsView::plotBarChart(const std::vector<dive *> &dives, legend = createChartItem<Legend>(data.vbinNames); std::vector<BarSeries::MultiItem> items; - items.reserve(data.hbin_counts.size()); + items.reserve(data.hbins.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, isHorizontal), + for (auto &[hbin, dives, total]: data.hbins) { + items.push_back({ pos - 0.5, pos + 0.5, makeMultiItems(std::move(dives), total, isHorizontal), categoryBinner->formatWithUnit(*hbin) }); pos += 1.0; } - createSeries<BarSeries>(isHorizontal, isStacked, categoryVariable->name(), valueVariable, std::move(data.vbinNames), items); + createSeries<BarSeries>(isHorizontal, isStacked, categoryVariable->name(), valueVariable, std::move(data.vbinNames), std::move(items)); } const double NaN = std::numeric_limits<double>::quiet_NaN(); @@ -770,14 +771,14 @@ void StatsView::plotValueChart(const std::vector<dive *> &dives, pos += 1.0; } - createSeries<BarSeries>(isHorizontal, categoryVariable->name(), valueVariable, items); + createSeries<BarSeries>(isHorizontal, categoryVariable->name(), valueVariable, std::move(items)); } -static int getTotalCount(const std::vector<StatsBinCount> &bins) +static int getTotalCount(const std::vector<StatsBinDives> &bins) { int total = 0; - for (const auto &[bin, count]: bins) - total += count; + for (const auto &[bin, dives]: bins) + total += (int)dives.size(); return total; } @@ -785,10 +786,8 @@ template<typename T> static int getMaxCount(const std::vector<T> &bins) { int res = 0; - for (auto const &[dummy, val]: bins) { - if (val > res) - res = val; - } + for (auto const &[dummy, dives]: bins) + res = std::max(res, (int)(dives.size())); return res; } @@ -801,7 +800,7 @@ void StatsView::plotDiscreteCountChart(const std::vector<dive *> &dives, setTitle(categoryVariable->nameWithBinnerUnit(*categoryBinner)); - std::vector<StatsBinCount> categoryBins = categoryBinner->count_dives(dives, false); + std::vector<StatsBinDives> categoryBins = categoryBinner->bin_dives(dives, false); // If there is nothing to display, quit if (categoryBins.empty()) @@ -824,14 +823,14 @@ void StatsView::plotDiscreteCountChart(const std::vector<dive *> &dives, std::vector<BarSeries::CountItem> items; items.reserve(categoryBins.size()); double pos = 0.0; - for (auto const &[bin, count]: categoryBins) { - std::vector<QString> label = makePercentageLabels(count, total, isHorizontal); - items.push_back({ pos - 0.5, pos + 0.5, count, label, + for (auto const &[bin, dives]: categoryBins) { + std::vector<QString> label = makePercentageLabels((int)dives.size(), total, isHorizontal); + items.push_back({ pos - 0.5, pos + 0.5, std::move(dives), label, categoryBinner->formatWithUnit(*bin), total }); pos += 1.0; } - createSeries<BarSeries>(isHorizontal, categoryVariable->name(), items); + createSeries<BarSeries>(isHorizontal, categoryVariable->name(), std::move(items)); } void StatsView::plotPieChart(const std::vector<dive *> &dives, @@ -842,19 +841,19 @@ void StatsView::plotPieChart(const std::vector<dive *> &dives, setTitle(categoryVariable->nameWithBinnerUnit(*categoryBinner)); - std::vector<StatsBinCount> categoryBins = categoryBinner->count_dives(dives, false); + std::vector<StatsBinDives> categoryBins = categoryBinner->bin_dives(dives, false); // If there is nothing to display, quit if (categoryBins.empty()) return; - std::vector<std::pair<QString, int>> data; + std::vector<std::pair<QString, std::vector<dive *>>> data; data.reserve(categoryBins.size()); - for (auto const &[bin, count]: categoryBins) - data.emplace_back(categoryBinner->formatWithUnit(*bin), count); + for (auto &[bin, dives]: categoryBins) + data.emplace_back(categoryBinner->formatWithUnit(*bin), std::move(dives)); bool keepOrder = categoryVariable->type() != StatsVariable::Type::Discrete; - PieSeries *series = createSeries<PieSeries>(categoryVariable->name(), data, keepOrder); + PieSeries *series = createSeries<PieSeries>(categoryVariable->name(), std::move(data), keepOrder); legend = createChartItem<Legend>(series->binNames()); } @@ -967,7 +966,7 @@ void StatsView::plotHistogramCountChart(const std::vector<dive *> &dives, setTitle(categoryVariable->name()); - std::vector<StatsBinCount> categoryBins = categoryBinner->count_dives(dives, true); + std::vector<StatsBinDives> categoryBins = categoryBinner->bin_dives(dives, true); // If there is nothing to display, quit if (categoryBins.empty()) @@ -990,16 +989,17 @@ void StatsView::plotHistogramCountChart(const std::vector<dive *> &dives, std::vector<BarSeries::CountItem> items; items.reserve(categoryBins.size()); - for (auto const &[bin, count]: categoryBins) { + // Attention: this moves away the dives + for (auto &[bin, dives]: categoryBins) { double lowerBound = categoryBinner->lowerBoundToFloat(*bin); double upperBound = categoryBinner->upperBoundToFloat(*bin); - std::vector<QString> label = makePercentageLabels(count, total, isHorizontal); + std::vector<QString> label = makePercentageLabels((int)dives.size(), total, isHorizontal); - items.push_back({ lowerBound, upperBound, count, label, + items.push_back({ lowerBound, upperBound, std::move(dives), label, categoryBinner->formatWithUnit(*bin), total }); } - createSeries<BarSeries>(isHorizontal, categoryVariable->name(), items); + createSeries<BarSeries>(isHorizontal, categoryVariable->name(), std::move(items)); if (categoryVariable->type() == StatsVariable::Type::Numeric) { double mean = categoryVariable->mean(dives); @@ -1058,7 +1058,7 @@ void StatsView::plotHistogramValueChart(const std::vector<dive *> &dives, categoryBinner->formatWithUnit(*bin), res }); } - createSeries<BarSeries>(isHorizontal, categoryVariable->name(), valueVariable, items); + createSeries<BarSeries>(isHorizontal, categoryVariable->name(), valueVariable, std::move(items)); } void StatsView::plotHistogramStackedChart(const std::vector<dive *> &dives, @@ -1090,16 +1090,16 @@ void StatsView::plotHistogramStackedChart(const std::vector<dive *> &dives, setAxes(catAxis, valAxis); std::vector<BarSeries::MultiItem> items; - items.reserve(data.hbin_counts.size()); + items.reserve(data.hbins.size()); - for (auto &[hbin, counts, total]: data.hbin_counts) { + for (auto &[hbin, dives, total]: data.hbins) { double lowerBound = categoryBinner->lowerBoundToFloat(*hbin); double upperBound = categoryBinner->upperBoundToFloat(*hbin); - items.push_back({ lowerBound, upperBound, makeCountLabels(counts, total, isHorizontal), + items.push_back({ lowerBound, upperBound, makeMultiItems(std::move(dives), total, isHorizontal), categoryBinner->formatWithUnit(*hbin) }); } - createSeries<BarSeries>(isHorizontal, true, categoryVariable->name(), valueVariable, std::move(data.vbinNames), items); + createSeries<BarSeries>(isHorizontal, true, categoryVariable->name(), valueVariable, std::move(data.vbinNames), std::move(items)); } void StatsView::plotHistogramBoxChart(const std::vector<dive *> &dives, |