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,
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);
}
}

View file

@ -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);

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 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;
}

View file

@ -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);

View file

@ -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

View file

@ -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;

View file

@ -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,