summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2021-01-20 14:36:59 +0100
committerGravatar Dirk Hohndel <dirk@hohndel.org>2021-02-13 13:02:54 -0800
commit18a5b5b5930247ed880cfbe8f94778b4c19b0bb2 (patch)
treecdaf28e252a7e512774379e2f12e981e1f68ccb3
parent622e9ba373f898a0d51fc009f3615d83ffd3a7fc (diff)
downloadsubsurface-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.cpp57
-rw-r--r--stats/barseries.h26
-rw-r--r--stats/pieseries.cpp43
-rw-r--r--stats/pieseries.h7
-rw-r--r--stats/statsvariables.cpp50
-rw-r--r--stats/statsvariables.h4
-rw-r--r--stats/statsview.cpp102
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 &center, 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,