mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-30 22:20:21 +00:00
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:
parent
622e9ba373
commit
18a5b5b593
7 changed files with 136 additions and 153 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <vector>
|
||||
#include <QString>
|
||||
|
||||
struct dive;
|
||||
struct InformationBox;
|
||||
struct ChartPieItem;
|
||||
struct ChartTextItem;
|
||||
|
@ -21,7 +22,7 @@ public:
|
|||
// If keepOrder is false, bins will be sorted by size, otherwise the sorting
|
||||
// of the shown bins will be retained. Small bins are omitted for clarity.
|
||||
PieSeries(StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName,
|
||||
const std::vector<std::pair<QString, int>> &data, bool keepOrder);
|
||||
std::vector<std::pair<QString, std::vector<dive *>>> data, bool keepOrder);
|
||||
~PieSeries();
|
||||
|
||||
void updatePositions() override;
|
||||
|
@ -42,9 +43,9 @@ private:
|
|||
ChartItemPtr<ChartTextItem> innerLabel, outerLabel;
|
||||
QString name;
|
||||
double angleFrom, angleTo; // In fraction of total
|
||||
int count;
|
||||
std::vector<dive *> dives;
|
||||
QPointF innerLabelPos, outerLabelPos; // With respect to a (-1, -1)-(1, 1) rectangle.
|
||||
Item(StatsView &view, const QString &name, int from, int count, int totalCount,
|
||||
Item(StatsView &view, const QString &name, int from, std::vector<dive *> dives, int totalCount,
|
||||
int bin_nr, int numBins);
|
||||
void updatePositions(const QPointF ¢er, double radius);
|
||||
void highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue