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>
This commit is contained in:
Berthold Stoeger 2021-01-20 14:36:59 +01:00 committed by Dirk Hohndel
parent 622e9ba373
commit 18a5b5b593
7 changed files with 136 additions and 153 deletions

View file

@ -40,25 +40,24 @@ BarSeries::BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis,
BarSeries::BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries::BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, const QString &categoryName, 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>()) BarSeries(view, xAxis, yAxis, horizontal, false, categoryName, nullptr, std::vector<QString>())
{ {
for (const CountItem &item: items) { for (CountItem &item: items) {
StatsOperationResults res; StatsOperationResults res;
res.count = item.count; double value = (double)item.dives.size();
double value = item.count; add_item(item.lowerBound, item.upperBound, makeSubItems({ value, std::move(item.dives), std::move(item.label) }),
add_item(item.lowerBound, item.upperBound, makeSubItems(value, item.label),
item.binName, res, item.total, horizontal, stacked); item.binName, res, item.total, horizontal, stacked);
} }
} }
BarSeries::BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries::BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, const QString &categoryName, const StatsVariable *valueVariable, 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>()) BarSeries(view, xAxis, yAxis, horizontal, false, categoryName, valueVariable, std::vector<QString>())
{ {
for (const ValueItem &item: items) { for (ValueItem &item: items) {
add_item(item.lowerBound, item.upperBound, makeSubItems(item.value, item.label), 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); 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, BarSeries::BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable, bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable,
std::vector<QString> valueBinNames, std::vector<QString> valueBinNames,
const std::vector<MultiItem> &items) : std::vector<MultiItem> items) :
BarSeries(view, xAxis, yAxis, horizontal, stacked, categoryName, valueVariable, std::move(valueBinNames)) BarSeries(view, xAxis, yAxis, horizontal, stacked, categoryName, valueVariable, std::move(valueBinNames))
{ {
for (const MultiItem &item: items) { for (MultiItem &item: items) {
StatsOperationResults res; StatsOperationResults res;
std::vector<std::pair<double, std::vector<QString>>> valuesLabels; std::vector<SubItemDesc> subitems;
valuesLabels.reserve(item.countLabels.size()); subitems.reserve(item.items.size());
int total = 0; int total = 0;
for (auto &[count, label]: item.countLabels) { for (auto &[dives, label]: item.items) {
valuesLabels.push_back({ static_cast<double>(count), std::move(label) }); total += (int)dives.size();
total += count; 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); 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); 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; std::vector<SubItem> res;
res.reserve(values.size()); res.reserve(items.size());
double from = 0.0; double from = 0.0;
int bin_nr = 0; int bin_nr = 0;
for (auto &[v, label]: values) { for (auto &[v, dives, label]: items) {
if (v > 0.0) { if (v > 0.0) {
res.push_back({ view.createChartItem<ChartBarItem>(ChartZValue::Series, barBorderWidth, horizontal), res.push_back({ view.createChartItem<ChartBarItem>(ChartZValue::Series, barBorderWidth, horizontal),
std::move(dives),
{}, from, from + v, bin_nr }); {}, from, from + v, bin_nr });
if (!label.empty()) if (!label.empty())
res.back().label = std::make_unique<BarLabel>(view, label, bin_nr, binCount()); 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; 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 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. // 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, 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; QLocale loc;
int decimals = valueVariable.decimals(); int decimals = valueVariable.decimals();
@ -338,7 +339,7 @@ static std::vector<QString> makeValueInfo(const QString &binName, const QString
std::vector<QString> res; std::vector<QString> res;
res.reserve(operations.size() + 3); res.reserve(operations.size() + 3);
res.push_back(QStringLiteral("%1: %2").arg(axisName, binName)); 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())); res.push_back(QStringLiteral("%1: ").arg(valueVariable.name()));
for (StatsOperation op: operations) { for (StatsOperation op: operations) {
QString valueFormatted = loc.toString(values.get(op), 'f', decimals); 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 std::vector<QString> BarSeries::makeInfo(const Item &item, int subitem_idx) const
{ {
if (item.subitems.empty())
return {};
if (!valueBinNames.empty() && valueVariable) { if (!valueBinNames.empty() && valueVariable) {
if (subitem_idx < 0 || subitem_idx >= (int)item.subitems.size()) if (subitem_idx < 0 || subitem_idx >= (int)item.subitems.size())
return {}; return {};
const SubItem &subitem = item.subitems[subitem_idx]; const SubItem &subitem = item.subitems[subitem_idx];
if (subitem.bin_nr < 0 || subitem.bin_nr >= (int)valueBinNames.size()) if (subitem.bin_nr < 0 || subitem.bin_nr >= (int)valueBinNames.size())
return {}; 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], return makeCountInfo(item.binName, categoryName, valueBinNames[subitem.bin_nr],
valueVariable->name(), count, item.total); valueVariable->name(), count, item.total);
} else if (valueVariable) { } 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 { } 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);
} }
} }

View file

@ -25,7 +25,7 @@ public:
// based charts and for stacked bar charts with multiple items. // based charts and for stacked bar charts with multiple items.
struct CountItem { struct CountItem {
double lowerBound, upperBound; double lowerBound, upperBound;
int count; std::vector<dive *> dives;
std::vector<QString> label; std::vector<QString> label;
QString binName; QString binName;
int total; int total;
@ -35,11 +35,15 @@ public:
double value; double value;
std::vector<QString> label; std::vector<QString> label;
QString binName; QString binName;
StatsOperationResults res; StatsOperationResults res; // Contains the dives
}; };
struct MultiItem { struct MultiItem {
struct Item {
std::vector<dive *> dives;
std::vector<QString> label;
};
double lowerBound, upperBound; double lowerBound, upperBound;
std::vector<std::pair<int, std::vector<QString>>> countLabels; std::vector<Item> items;
QString binName; QString binName;
}; };
@ -52,14 +56,14 @@ public:
// are ordered identically. // are ordered identically.
BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, const QString &categoryName, bool horizontal, const QString &categoryName,
const std::vector<CountItem> &items); std::vector<CountItem> items);
BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, const QString &categoryName, const StatsVariable *valueVariable, bool horizontal, const QString &categoryName, const StatsVariable *valueVariable,
const std::vector<ValueItem> &items); std::vector<ValueItem> items);
BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, BarSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis,
bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable, bool horizontal, bool stacked, const QString &categoryName, const StatsVariable *valueVariable,
std::vector<QString> valueBinNames, std::vector<QString> valueBinNames,
const std::vector<MultiItem> &items); std::vector<MultiItem> items);
~BarSeries(); ~BarSeries();
void updatePositions() override; void updatePositions() override;
@ -93,6 +97,7 @@ private:
struct SubItem { struct SubItem {
ChartItemPtr<ChartBarItem> item; ChartItemPtr<ChartBarItem> item;
std::vector<dive *> dives;
std::unique_ptr<BarLabel> label; std::unique_ptr<BarLabel> label;
double value_from; double value_from;
double value_to; double value_to;
@ -127,8 +132,13 @@ private:
const StatsVariable *valueVariable; // null: this is count based const StatsVariable *valueVariable; // null: this is count based
std::vector<QString> valueBinNames; std::vector<QString> valueBinNames;
Index highlighted; Index highlighted;
std::vector<SubItem> makeSubItems(double value, const std::vector<QString> &label) const; struct SubItemDesc {
std::vector<SubItem> makeSubItems(const std::vector<std::pair<double, std::vector<QString>>> &values) const; 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, void add_item(double lowerBound, double upperBound, std::vector<SubItem> subitems,
const QString &binName, const StatsOperationResults &res, int total, bool horizontal, const QString &binName, const StatsOperationResults &res, int total, bool horizontal,
bool stacked); bool stacked);

View file

@ -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 innerLabelRadius = 0.75; // 1.0 = at outer border of pie
static const double outerLabelRadius = 1.01; // 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) : int bin_nr, int numBins) :
name(name), name(name),
count(count) dives(std::move(divesIn))
{ {
QFont f; // make configurable QFont f; // make configurable
QLocale loc; QLocale loc;
int count = (int)dives.size();
angleFrom = static_cast<double>(from) / totalCount; angleFrom = static_cast<double>(from) / totalCount;
angleTo = static_cast<double>(from + count) / 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. 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, 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), StatsSeries(view, xAxis, yAxis),
item(view.createChartItem<ChartPieItem>(ChartZValue::Series, pieBorderWidth)), item(view.createChartItem<ChartPieItem>(ChartZValue::Series, pieBorderWidth)),
categoryName(categoryName), categoryName(categoryName),
@ -89,8 +90,8 @@ PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const
// Easier to read than std::accumulate // Easier to read than std::accumulate
totalCount = 0; totalCount = 0;
for (const auto &[name, count]: data) for (const auto &[name, dives]: data)
totalCount += count; totalCount += (int)dives.size();
// First of all, sort from largest to smalles slice. Instead // First of all, sort from largest to smalles slice. Instead
// of sorting the initial array, sort a list of indices, so that // 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. // - do a lexicographic sort by (count, idx) so that for equal counts the order is preserved.
std::sort(sorted.begin(), sorted.end(), std::sort(sorted.begin(), sorted.end(),
[&data](int idx1, int idx2) [&data](int idx1, int idx2)
{ return std::make_tuple(-data[idx1].second, idx1) < { return std::make_tuple(-data[idx1].second.size(), idx1) <
std::make_tuple(-data[idx2].second, idx2); }); std::make_tuple(-data[idx2].second.size(), idx2); });
auto it = std::find_if(sorted.begin(), sorted.end(), auto it = std::find_if(sorted.begin(), sorted.end(),
[count=totalCount, &data](int idx) [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) { if (it - sorted.begin() < min_slices) {
// Take minimum amount of slices below 50%... // Take minimum amount of slices below 50%...
int sum = 0; int sum = 0;
for (auto it2 = sorted.begin(); it2 != it; ++it2) 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) { while(it != sorted.end() && sum * 2 < totalCount && it - sorted.begin() < min_slices) {
sum += data[*it].second; sum += (int)data[*it].second.size();
++it; ++it;
} }
} }
@ -135,18 +136,24 @@ PieSeries::PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const
items.reserve(numBins); items.reserve(numBins);
int act = 0; int act = 0;
for (auto it2 = sorted.begin(); it2 != it; ++it2) { for (auto it2 = sorted.begin(); it2 != it; ++it2) {
int count = data[*it2].second; int count = (int)data[*it2].second.size();
items.emplace_back(view, data[*it2].first, act, count, totalCount, (int)items.size(), numBins); items.emplace_back(view, data[*it2].first, act, std::move(data[*it2].second),
totalCount, (int)items.size(), numBins);
act += count; act += count;
} }
// Register the items of the "other" group. // Register the items of the "other" group.
if (it != sorted.end()) { if (it != sorted.end()) {
std::vector<dive *> otherDives;
otherDives.reserve(totalCount - act);
other.reserve(sorted.end() - it); other.reserve(sorted.end() - it);
for (auto it2 = it; it2 != sorted.end(); ++it2) for (auto it2 = it; it2 != sorted.end(); ++it2) {
other.push_back({ data[*it2].first, data[*it2].second }); 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()); 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. // This is the "other" bin. Format all these items and an overview item.
res.reserve(other.size() + 1); res.reserve(other.size() + 1);
res.push_back(QString("%1: %2").arg(StatsTranslations::tr("other"), 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) for (const OtherItem &item: other)
res.push_back(QString("%1: %2").arg(item.name, res.push_back(QString("%1: %2").arg(item.name,
makePercentageLine(item.count, totalCount))); makePercentageLine((int)item.count, totalCount)));
} else { } else {
// A "normal" item. // A "normal" item.
res.reserve(2); res.reserve(2);
res.push_back(QStringLiteral("%1: %2").arg(categoryName, items[idx].name)); 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; return res;
} }

View file

@ -10,6 +10,7 @@
#include <vector> #include <vector>
#include <QString> #include <QString>
struct dive;
struct InformationBox; struct InformationBox;
struct ChartPieItem; struct ChartPieItem;
struct ChartTextItem; struct ChartTextItem;
@ -21,7 +22,7 @@ public:
// If keepOrder is false, bins will be sorted by size, otherwise the sorting // 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. // of the shown bins will be retained. Small bins are omitted for clarity.
PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName, 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(); ~PieSeries();
void updatePositions() override; void updatePositions() override;
@ -42,9 +43,9 @@ private:
ChartItemPtr<ChartTextItem> innerLabel, outerLabel; ChartItemPtr<ChartTextItem> innerLabel, outerLabel;
QString name; QString name;
double angleFrom, angleTo; // In fraction of total 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. 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); int bin_nr, int numBins);
void updatePositions(const QPointF &center, double radius); void updatePositions(const QPointF &center, double radius);
void highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins); void highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins);

View file

@ -434,15 +434,16 @@ StatsOperationResults StatsVariable::applyOperations(const std::vector<dive *> &
std::vector<StatsValue> val = values(dives); std::vector<StatsValue> val = values(dives);
double sumTime = 0.0; double sumTime = 0.0;
res.count = (int)val.size(); res.dives.reserve(val.size());
res.median = quartiles(val).q2; res.median = quartiles(val).q2;
if (res.count <= 0) if (val.empty())
return res; return res;
res.min = std::numeric_limits<double>::max(); res.min = std::numeric_limits<double>::max();
res.max = std::numeric_limits<double>::lowest(); res.max = std::numeric_limits<double>::lowest();
for (auto [v, d]: val) { for (auto [v, d]: val) {
res.dives.push_back(d);
res.sum += v; res.sum += v;
res.mean += v; res.mean += v;
sumTime += d->duration.seconds; sumTime += d->duration.seconds;
@ -453,19 +454,19 @@ StatsOperationResults StatsVariable::applyOperations(const std::vector<dive *> &
res.max = v; res.max = v;
} }
res.mean /= res.count; res.mean /= val.size();
res.timeWeightedMean /= sumTime; res.timeWeightedMean /= sumTime;
return res; return res;
} }
StatsOperationResults::StatsOperationResults() : 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 bool StatsOperationResults::isValid() const
{ {
return count > 0; return !dives.empty();
} }
double StatsOperationResults::get(StatsOperation op) const double StatsOperationResults::get(StatsOperation op) const
@ -584,7 +585,6 @@ struct SimpleBinner : public StatsBinner {
public: public:
using Type = decltype(Bin::value); using Type = decltype(Bin::value);
std::vector<StatsBinDives> bin_dives(const std::vector<dive *> &dives, bool fill_empty) const override; 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 { const Binner &derived() const {
return static_cast<const Binner &>(*this); 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); 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 // A simple binner (see above) that works on continuous (or numeric) variables
// and can return bin-ranges. The binner must implement an inc() function // and can return bin-ranges. The binner must implement an inc() function
// that turns a bin into the next-higher bin. // that turns a bin into the next-higher bin.
@ -778,7 +760,6 @@ struct MultiBinner : public StatsBinner {
public: public:
using Type = decltype(Bin::value); using Type = decltype(Bin::value);
std::vector<StatsBinDives> bin_dives(const std::vector<dive *> &dives, bool fill_empty) const override; 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 { const Binner &derived() const {
return static_cast<const Binner &>(*this); 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); 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 // A binner that works on string-based bins whereby each dive can
// produce multiple strings (e.g. dive buddies). The binner must // produce multiple strings (e.g. dive buddies). The binner must
// feature a to_bin_values() function that produces a vector of // feature a to_bin_values() function that produces a vector of

View file

@ -33,7 +33,7 @@ enum class StatsOperation : int {
// Results of the above operations // Results of the above operations
struct StatsOperationResults { struct StatsOperationResults {
int count; std::vector<dive *> dives;
double median; double median;
double mean; double mean;
double timeWeightedMean; double timeWeightedMean;
@ -77,7 +77,6 @@ struct StatsBinValue {
}; };
using StatsBinDives = StatsBinValue<std::vector<dive *>>; using StatsBinDives = StatsBinValue<std::vector<dive *>>;
using StatsBinValues = StatsBinValue<std::vector<StatsValue>>; using StatsBinValues = StatsBinValue<std::vector<StatsValue>>;
using StatsBinCount = StatsBinValue<int>;
using StatsBinQuartiles = StatsBinValue<StatsQuartiles>; using StatsBinQuartiles = StatsBinValue<StatsQuartiles>;
using StatsBinOp = StatsBinValue<StatsOperationResults>; using StatsBinOp = StatsBinValue<StatsOperationResults>;
@ -89,7 +88,6 @@ struct StatsBinner {
// The binning functions have a parameter "fill_empty". If true, missing // 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. // 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<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! // Note: these functions will crash with an exception if passed incompatible bins!
virtual QString format(const StatsBin &bin) const = 0; virtual QString format(const StatsBin &bin) const = 0;

View file

@ -534,10 +534,10 @@ CountAxis *StatsView::createCountAxis(int maxVal, bool isHorizontal)
} }
// For "two-dimensionally" binned plots (eg. stacked bar or grouped bar): // 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. // Dives for each bin on the independent variable, including the total counts for that bin.
struct BinCounts { struct BinDives {
StatsBinPtr bin; StatsBinPtr bin;
std::vector<int> counts; std::vector<std::vector<dive *>> dives;
int total; int total;
}; };
@ -547,7 +547,7 @@ struct BinCounts {
// Perhaps we should determine the bins of all dives first and then // Perhaps we should determine the bins of all dives first and then
// query the counts for precisely those bins? // query the counts for precisely those bins?
struct BarPlotData { 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<StatsBinPtr> vbins;
std::vector<QString> vbinNames; std::vector<QString> vbinNames;
int maxCount; // Highest count of any bin-combination 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) { for (auto &[bin, dives]: categoryBins) {
// This moves the bin - the original pointer is invalidated // This moves the bin - the original pointer is invalidated
hbin_counts.push_back({ std::move(bin), std::vector<int>(vbins.size(), 0), 0 }); hbins.push_back({ std::move(bin), std::vector<std::vector<dive *>>(vbins.size()), 0 });
for (auto &[vbin, count]: valueBinner.count_dives(dives, false)) { for (auto &[vbin, dives]: valueBinner.bin_dives(dives, false)) {
// Note: we assume that the bins are sorted! // Note: we assume that the bins are sorted!
auto it = std::lower_bound(vbins.begin(), vbins.end(), vbin, auto it = std::lower_bound(vbins.begin(), vbins.end(), vbin,
[] (const StatsBinPtr &p, const StatsBinPtr &bin) [] (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! // Attn: this invalidates "vbin", which must not be used henceforth!
vbins.insert(it, std::move(vbin)); vbins.insert(it, std::move(vbin));
// Fix the old arrays // Fix the old arrays
for (auto &[bin, v, total]: hbin_counts) for (auto &[bin, v, total]: hbins)
v.insert(v.begin() + pos, 0); v.insert(v.begin() + pos, std::vector<dive *>());
} }
hbin_counts.back().counts[pos] = count; int count = (int)dives.size();
hbin_counts.back().total += count; hbins.back().dives[pos] = std::move(dives);
hbins.back().total += count;
if (count > maxCount) if (count > maxCount)
maxCount = count; maxCount = count;
} }
maxCategoryCount = std::max(maxCategoryCount, hbin_counts.back().total); maxCategoryCount = std::max(maxCategoryCount, hbins.back().total);
} }
vbinNames.reserve(vbins.size()); vbinNames.reserve(vbins.size());
@ -601,17 +602,17 @@ static std::vector<QString> makePercentageLabels(int count, int total, bool isHo
return { countString, percentageString }; 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. // 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; std::vector<BarSeries::MultiItem::Item> res;
count_labels.reserve(counts.size()); res.reserve(bins.size());
for (int count: counts) { for (std::vector<dive*> &bin: bins) {
std::vector<QString> label = makePercentageLabels(count, total, isHorizontal); std::vector<QString> label = makePercentageLabels((int)bin.size(), total, isHorizontal);
count_labels.push_back(std::make_pair(count, label)); res.push_back({ std::move(bin), std::move(label) });
} }
return count_labels; return res;
} }
void StatsView::plotBarChart(const std::vector<dive *> &dives, 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); legend = createChartItem<Legend>(data.vbinNames);
std::vector<BarSeries::MultiItem> items; std::vector<BarSeries::MultiItem> items;
items.reserve(data.hbin_counts.size()); items.reserve(data.hbins.size());
double pos = 0.0; double pos = 0.0;
for (auto &[hbin, counts, total]: data.hbin_counts) { for (auto &[hbin, dives, total]: data.hbins) {
items.push_back({ pos - 0.5, pos + 0.5, makeCountLabels(counts, total, isHorizontal), items.push_back({ pos - 0.5, pos + 0.5, makeMultiItems(std::move(dives), total, isHorizontal),
categoryBinner->formatWithUnit(*hbin) }); categoryBinner->formatWithUnit(*hbin) });
pos += 1.0; 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(); const double NaN = std::numeric_limits<double>::quiet_NaN();
@ -770,14 +771,14 @@ void StatsView::plotValueChart(const std::vector<dive *> &dives,
pos += 1.0; 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; int total = 0;
for (const auto &[bin, count]: bins) for (const auto &[bin, dives]: bins)
total += count; total += (int)dives.size();
return total; return total;
} }
@ -785,10 +786,8 @@ template<typename T>
static int getMaxCount(const std::vector<T> &bins) static int getMaxCount(const std::vector<T> &bins)
{ {
int res = 0; int res = 0;
for (auto const &[dummy, val]: bins) { for (auto const &[dummy, dives]: bins)
if (val > res) res = std::max(res, (int)(dives.size()));
res = val;
}
return res; return res;
} }
@ -801,7 +800,7 @@ void StatsView::plotDiscreteCountChart(const std::vector<dive *> &dives,
setTitle(categoryVariable->nameWithBinnerUnit(*categoryBinner)); 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 there is nothing to display, quit
if (categoryBins.empty()) if (categoryBins.empty())
@ -824,14 +823,14 @@ void StatsView::plotDiscreteCountChart(const std::vector<dive *> &dives,
std::vector<BarSeries::CountItem> items; std::vector<BarSeries::CountItem> items;
items.reserve(categoryBins.size()); items.reserve(categoryBins.size());
double pos = 0.0; double pos = 0.0;
for (auto const &[bin, count]: categoryBins) { for (auto const &[bin, dives]: categoryBins) {
std::vector<QString> label = makePercentageLabels(count, total, isHorizontal); std::vector<QString> label = makePercentageLabels((int)dives.size(), total, isHorizontal);
items.push_back({ pos - 0.5, pos + 0.5, count, label, items.push_back({ pos - 0.5, pos + 0.5, std::move(dives), label,
categoryBinner->formatWithUnit(*bin), total }); categoryBinner->formatWithUnit(*bin), total });
pos += 1.0; 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, void StatsView::plotPieChart(const std::vector<dive *> &dives,
@ -842,19 +841,19 @@ void StatsView::plotPieChart(const std::vector<dive *> &dives,
setTitle(categoryVariable->nameWithBinnerUnit(*categoryBinner)); 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 there is nothing to display, quit
if (categoryBins.empty()) if (categoryBins.empty())
return; return;
std::vector<std::pair<QString, int>> data; std::vector<std::pair<QString, std::vector<dive *>>> data;
data.reserve(categoryBins.size()); data.reserve(categoryBins.size());
for (auto const &[bin, count]: categoryBins) for (auto &[bin, dives]: categoryBins)
data.emplace_back(categoryBinner->formatWithUnit(*bin), count); data.emplace_back(categoryBinner->formatWithUnit(*bin), std::move(dives));
bool keepOrder = categoryVariable->type() != StatsVariable::Type::Discrete; 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()); legend = createChartItem<Legend>(series->binNames());
} }
@ -967,7 +966,7 @@ void StatsView::plotHistogramCountChart(const std::vector<dive *> &dives,
setTitle(categoryVariable->name()); 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 there is nothing to display, quit
if (categoryBins.empty()) if (categoryBins.empty())
@ -990,16 +989,17 @@ void StatsView::plotHistogramCountChart(const std::vector<dive *> &dives,
std::vector<BarSeries::CountItem> items; std::vector<BarSeries::CountItem> items;
items.reserve(categoryBins.size()); 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 lowerBound = categoryBinner->lowerBoundToFloat(*bin);
double upperBound = categoryBinner->upperBoundToFloat(*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 }); categoryBinner->formatWithUnit(*bin), total });
} }
createSeries<BarSeries>(isHorizontal, categoryVariable->name(), items); createSeries<BarSeries>(isHorizontal, categoryVariable->name(), std::move(items));
if (categoryVariable->type() == StatsVariable::Type::Numeric) { if (categoryVariable->type() == StatsVariable::Type::Numeric) {
double mean = categoryVariable->mean(dives); double mean = categoryVariable->mean(dives);
@ -1058,7 +1058,7 @@ void StatsView::plotHistogramValueChart(const std::vector<dive *> &dives,
categoryBinner->formatWithUnit(*bin), res }); 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, void StatsView::plotHistogramStackedChart(const std::vector<dive *> &dives,
@ -1090,16 +1090,16 @@ void StatsView::plotHistogramStackedChart(const std::vector<dive *> &dives,
setAxes(catAxis, valAxis); setAxes(catAxis, valAxis);
std::vector<BarSeries::MultiItem> items; 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 lowerBound = categoryBinner->lowerBoundToFloat(*hbin);
double upperBound = categoryBinner->upperBoundToFloat(*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) }); 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, void StatsView::plotHistogramBoxChart(const std::vector<dive *> &dives,