mirror of
https://github.com/subsurface/subsurface.git
synced 2024-12-05 00:21:29 +00:00
core: C++-ify statistics.c
The old code was wild: For the yearly statistics it would allocate one entry per dive in the log. Of course, it would also leak C-style strings. Convert the whole thing to somewhat idiomatic C++. Somewhat wasted work, because I'd like to convert the whole thing to the new statistics code. But let's finish the conversion to C++ first. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
6d9dd5d0a1
commit
a1ac99d5ed
12 changed files with 496 additions and 582 deletions
|
@ -75,7 +75,7 @@ SOURCES += subsurface-mobile-main.cpp \
|
||||||
core/import-divinglog.cpp \
|
core/import-divinglog.cpp \
|
||||||
core/import-csv.cpp \
|
core/import-csv.cpp \
|
||||||
core/save-html.cpp \
|
core/save-html.cpp \
|
||||||
core/statistics.c \
|
core/statistics.cpp \
|
||||||
core/worldmap-save.cpp \
|
core/worldmap-save.cpp \
|
||||||
core/libdivecomputer.cpp \
|
core/libdivecomputer.cpp \
|
||||||
core/version.cpp \
|
core/version.cpp \
|
||||||
|
|
|
@ -165,7 +165,7 @@ set(SUBSURFACE_CORE_LIB_SRCS
|
||||||
sha1.cpp
|
sha1.cpp
|
||||||
sha1.h
|
sha1.h
|
||||||
ssrf.h
|
ssrf.h
|
||||||
statistics.c
|
statistics.cpp
|
||||||
statistics.h
|
statistics.h
|
||||||
string-format.h
|
string-format.h
|
||||||
string-format.cpp
|
string-format.cpp
|
||||||
|
|
|
@ -431,7 +431,7 @@ static bool cylinder_used(const cylinder_t *cyl)
|
||||||
start_mbar = cyl->start.mbar ?: cyl->sample_start.mbar;
|
start_mbar = cyl->start.mbar ?: cyl->sample_start.mbar;
|
||||||
end_mbar = cyl->end.mbar ?: cyl->sample_end.mbar;
|
end_mbar = cyl->end.mbar ?: cyl->sample_end.mbar;
|
||||||
|
|
||||||
// More than 5 bar used? This matches statistics.c
|
// More than 5 bar used? This matches statistics.cpp
|
||||||
// heuristics
|
// heuristics
|
||||||
return start_mbar > end_mbar + SOME_GAS;
|
return start_mbar > end_mbar + SOME_GAS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,45 +75,42 @@ static void exportHTMLstatistics(const QString filename, struct htmlExportSettin
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
file.open(QIODevice::WriteOnly | QIODevice::Text);
|
file.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||||
QTextStream out(&file);
|
QTextStream out(&file);
|
||||||
stats_summary_auto_free stats;
|
|
||||||
|
|
||||||
stats_t total_stats;
|
stats_t total_stats;
|
||||||
|
|
||||||
calculate_stats_summary(&stats, hes.selectedOnly);
|
stats_summary stats = calculate_stats_summary(hes.selectedOnly);
|
||||||
total_stats.selection_size = 0;
|
total_stats.selection_size = 0;
|
||||||
total_stats.total_time.seconds = 0;
|
total_stats.total_time.seconds = 0;
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
out << "divestat=[";
|
out << "divestat=[";
|
||||||
if (hes.yearlyStatistics) {
|
if (hes.yearlyStatistics) {
|
||||||
while (stats.stats_yearly != NULL && stats.stats_yearly[i].period) {
|
for (const auto &s: stats.stats_yearly) {
|
||||||
out << "{";
|
out << "{";
|
||||||
out << "\"YEAR\":\"" << stats.stats_yearly[i].period << "\",";
|
out << "\"YEAR\":\"" << s.period << "\",";
|
||||||
out << "\"DIVES\":\"" << stats.stats_yearly[i].selection_size << "\",";
|
out << "\"DIVES\":\"" << s.selection_size << "\",";
|
||||||
out << "\"TOTAL_TIME\":\"" << get_dive_duration_string(stats.stats_yearly[i].total_time.seconds,
|
out << "\"TOTAL_TIME\":\"" << get_dive_duration_string(s.total_time.seconds,
|
||||||
gettextFromC::tr("h"), gettextFromC::tr("min"), gettextFromC::tr("sec"), " ") << "\",";
|
gettextFromC::tr("h"), gettextFromC::tr("min"), gettextFromC::tr("sec"), " ") << "\",";
|
||||||
out << "\"AVERAGE_TIME\":\"" << formatMinutes(stats.stats_yearly[i].total_time.seconds / stats.stats_yearly[i].selection_size) << "\",";
|
out << "\"AVERAGE_TIME\":\"" << formatMinutes(s.total_time.seconds / s.selection_size) << "\",";
|
||||||
out << "\"SHORTEST_TIME\":\"" << formatMinutes(stats.stats_yearly[i].shortest_time.seconds) << "\",";
|
out << "\"SHORTEST_TIME\":\"" << formatMinutes(s.shortest_time.seconds) << "\",";
|
||||||
out << "\"LONGEST_TIME\":\"" << formatMinutes(stats.stats_yearly[i].longest_time.seconds) << "\",";
|
out << "\"LONGEST_TIME\":\"" << formatMinutes(s.longest_time.seconds) << "\",";
|
||||||
out << "\"AVG_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].avg_depth) << "\",";
|
out << "\"AVG_DEPTH\":\"" << get_depth_string(s.avg_depth) << "\",";
|
||||||
out << "\"MIN_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].min_depth) << "\",";
|
out << "\"MIN_DEPTH\":\"" << get_depth_string(s.min_depth) << "\",";
|
||||||
out << "\"MAX_DEPTH\":\"" << get_depth_string(stats.stats_yearly[i].max_depth) << "\",";
|
out << "\"MAX_DEPTH\":\"" << get_depth_string(s.max_depth) << "\",";
|
||||||
out << "\"AVG_SAC\":\"" << get_volume_string(stats.stats_yearly[i].avg_sac) << "\",";
|
out << "\"AVG_SAC\":\"" << get_volume_string(s.avg_sac) << "\",";
|
||||||
out << "\"MIN_SAC\":\"" << get_volume_string(stats.stats_yearly[i].min_sac) << "\",";
|
out << "\"MIN_SAC\":\"" << get_volume_string(s.min_sac) << "\",";
|
||||||
out << "\"MAX_SAC\":\"" << get_volume_string(stats.stats_yearly[i].max_sac) << "\",";
|
out << "\"MAX_SAC\":\"" << get_volume_string(s.max_sac) << "\",";
|
||||||
if (stats.stats_yearly[i].combined_count) {
|
if (s.combined_count) {
|
||||||
temperature_t avg_temp;
|
temperature_t avg_temp;
|
||||||
avg_temp.mkelvin = stats.stats_yearly[i].combined_temp.mkelvin / stats.stats_yearly[i].combined_count;
|
avg_temp.mkelvin = s.combined_temp.mkelvin / s.combined_count;
|
||||||
out << "\"AVG_TEMP\":\"" << get_temperature_string(avg_temp) << "\",";
|
out << "\"AVG_TEMP\":\"" << get_temperature_string(avg_temp) << "\",";
|
||||||
} else {
|
} else {
|
||||||
out << "\"AVG_TEMP\":\"0.0\",";
|
out << "\"AVG_TEMP\":\"0.0\",";
|
||||||
}
|
}
|
||||||
out << "\"MIN_TEMP\":\"" << (stats.stats_yearly[i].min_temp.mkelvin == 0 ? 0 : get_temperature_string(stats.stats_yearly[i].min_temp)) << "\",";
|
out << "\"MIN_TEMP\":\"" << (s.min_temp.mkelvin == 0 ? 0 : get_temperature_string(s.min_temp)) << "\",";
|
||||||
out << "\"MAX_TEMP\":\"" << (stats.stats_yearly[i].max_temp.mkelvin == 0 ? 0 : get_temperature_string(stats.stats_yearly[i].max_temp)) << "\",";
|
out << "\"MAX_TEMP\":\"" << (s.max_temp.mkelvin == 0 ? 0 : get_temperature_string(s.max_temp)) << "\",";
|
||||||
out << "},";
|
out << "},";
|
||||||
total_stats.selection_size += stats.stats_yearly[i].selection_size;
|
total_stats.selection_size += s.selection_size;
|
||||||
total_stats.total_time.seconds += stats.stats_yearly[i].total_time.seconds;
|
total_stats.total_time.seconds += s.total_time.seconds;
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
exportHTMLstatisticsTotal(out, &total_stats);
|
exportHTMLstatisticsTotal(out, &total_stats);
|
||||||
}
|
}
|
||||||
|
|
|
@ -385,14 +385,13 @@ QVector<QPair<QString, int>> selectedDivesGasUsed()
|
||||||
int j;
|
int j;
|
||||||
QMap<QString, int> gasUsed;
|
QMap<QString, int> gasUsed;
|
||||||
for (dive *d: getDiveSelection()) {
|
for (dive *d: getDiveSelection()) {
|
||||||
volume_t *diveGases = get_gas_used(d);
|
std::vector<volume_t> diveGases = get_gas_used(d);
|
||||||
for (j = 0; j < d->cylinders.nr; j++) {
|
for (j = 0; j < d->cylinders.nr; j++) {
|
||||||
if (diveGases[j].mliter) {
|
if (diveGases[j].mliter) {
|
||||||
QString gasName = gasname(get_cylinder(d, j)->gasmix);
|
QString gasName = gasname(get_cylinder(d, j)->gasmix);
|
||||||
gasUsed[gasName] += diveGases[j].mliter;
|
gasUsed[gasName] += diveGases[j].mliter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(diveGases);
|
|
||||||
}
|
}
|
||||||
QVector<QPair<QString, int>> gasUsedOrdered;
|
QVector<QPair<QString, int>> gasUsedOrdered;
|
||||||
gasUsedOrdered.reserve(gasUsed.size());
|
gasUsedOrdered.reserve(gasUsed.size());
|
||||||
|
|
|
@ -1,415 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0
|
|
||||||
/* statistics.c
|
|
||||||
*
|
|
||||||
* core logic for the Info & Stats page -
|
|
||||||
* void calculate_stats_summary(struct stats_summary *out, bool selected_only);
|
|
||||||
* void calculate_stats_selected(stats_t *stats_selection);
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "statistics.h"
|
|
||||||
#include "dive.h"
|
|
||||||
#include "divelog.h"
|
|
||||||
#include "event.h"
|
|
||||||
#include "gettext.h"
|
|
||||||
#include "sample.h"
|
|
||||||
#include "subsurface-time.h"
|
|
||||||
#include "trip.h"
|
|
||||||
#include "units.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
|
|
||||||
static void process_temperatures(struct dive *dp, stats_t *stats)
|
|
||||||
{
|
|
||||||
temperature_t min_temp, mean_temp, max_temp = {.mkelvin = 0};
|
|
||||||
|
|
||||||
max_temp.mkelvin = dp->maxtemp.mkelvin;
|
|
||||||
if (max_temp.mkelvin && (!stats->max_temp.mkelvin || max_temp.mkelvin > stats->max_temp.mkelvin))
|
|
||||||
stats->max_temp.mkelvin = max_temp.mkelvin;
|
|
||||||
|
|
||||||
min_temp.mkelvin = dp->mintemp.mkelvin;
|
|
||||||
if (min_temp.mkelvin && (!stats->min_temp.mkelvin || min_temp.mkelvin < stats->min_temp.mkelvin))
|
|
||||||
stats->min_temp.mkelvin = min_temp.mkelvin;
|
|
||||||
|
|
||||||
if (min_temp.mkelvin || max_temp.mkelvin) {
|
|
||||||
mean_temp.mkelvin = min_temp.mkelvin;
|
|
||||||
if (mean_temp.mkelvin)
|
|
||||||
mean_temp.mkelvin = (mean_temp.mkelvin + max_temp.mkelvin) / 2;
|
|
||||||
else
|
|
||||||
mean_temp.mkelvin = max_temp.mkelvin;
|
|
||||||
stats->combined_temp.mkelvin += mean_temp.mkelvin;
|
|
||||||
stats->combined_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void process_dive(struct dive *dive, stats_t *stats)
|
|
||||||
{
|
|
||||||
int old_tadt, sac_time = 0;
|
|
||||||
int32_t duration = dive->duration.seconds;
|
|
||||||
|
|
||||||
old_tadt = stats->total_average_depth_time.seconds;
|
|
||||||
stats->total_time.seconds += duration;
|
|
||||||
if (duration > stats->longest_time.seconds)
|
|
||||||
stats->longest_time.seconds = duration;
|
|
||||||
if (stats->shortest_time.seconds == 0 || duration < stats->shortest_time.seconds)
|
|
||||||
stats->shortest_time.seconds = duration;
|
|
||||||
if (dive->maxdepth.mm > stats->max_depth.mm)
|
|
||||||
stats->max_depth.mm = dive->maxdepth.mm;
|
|
||||||
if (stats->min_depth.mm == 0 || dive->maxdepth.mm < stats->min_depth.mm)
|
|
||||||
stats->min_depth.mm = dive->maxdepth.mm;
|
|
||||||
stats->combined_max_depth.mm += dive->maxdepth.mm;
|
|
||||||
|
|
||||||
process_temperatures(dive, stats);
|
|
||||||
|
|
||||||
/* Maybe we should drop zero-duration dives */
|
|
||||||
if (!duration)
|
|
||||||
return;
|
|
||||||
if (dive->meandepth.mm) {
|
|
||||||
stats->total_average_depth_time.seconds += duration;
|
|
||||||
stats->avg_depth.mm = lrint((1.0 * old_tadt * stats->avg_depth.mm +
|
|
||||||
duration * dive->meandepth.mm) /
|
|
||||||
stats->total_average_depth_time.seconds);
|
|
||||||
}
|
|
||||||
if (dive->sac > 100) { /* less than .1 l/min is bogus, even with a pSCR */
|
|
||||||
sac_time = stats->total_sac_time.seconds + duration;
|
|
||||||
stats->avg_sac.mliter = lrint((1.0 * stats->total_sac_time.seconds * stats->avg_sac.mliter +
|
|
||||||
duration * dive->sac) /
|
|
||||||
sac_time);
|
|
||||||
if (dive->sac > stats->max_sac.mliter)
|
|
||||||
stats->max_sac.mliter = dive->sac;
|
|
||||||
if (stats->min_sac.mliter == 0 || dive->sac < stats->min_sac.mliter)
|
|
||||||
stats->min_sac.mliter = dive->sac;
|
|
||||||
stats->total_sac_time.seconds = sac_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calculate a summary of the statistics and put in the stats_summary
|
|
||||||
* structure provided in the first parameter.
|
|
||||||
* Before first use, it should be initialized with init_stats_summary().
|
|
||||||
* After use, memory must be released with free_stats_summary().
|
|
||||||
*/
|
|
||||||
void calculate_stats_summary(struct stats_summary *out, bool selected_only)
|
|
||||||
{
|
|
||||||
int idx;
|
|
||||||
int t_idx, d_idx, r;
|
|
||||||
struct dive *dp;
|
|
||||||
struct tm tm;
|
|
||||||
int current_year = 0;
|
|
||||||
int current_month = 0;
|
|
||||||
int year_iter = 0;
|
|
||||||
int month_iter = 0;
|
|
||||||
int prev_month = 0, prev_year = 0;
|
|
||||||
int trip_iter = 0;
|
|
||||||
dive_trip_t *trip_ptr = 0;
|
|
||||||
size_t size, tsize, dsize, tmsize;
|
|
||||||
stats_t stats = { 0 };
|
|
||||||
|
|
||||||
if (divelog.dives->nr > 0) {
|
|
||||||
stats.shortest_time.seconds = divelog.dives->dives[0]->duration.seconds;
|
|
||||||
stats.min_depth.mm = divelog.dives->dives[0]->maxdepth.mm;
|
|
||||||
stats.selection_size = divelog.dives->nr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* allocate sufficient space to hold the worst
|
|
||||||
* case (one dive per year or all dives during
|
|
||||||
* one month) for yearly and monthly statistics*/
|
|
||||||
|
|
||||||
size = sizeof(stats_t) * (divelog.dives->nr + 1);
|
|
||||||
tsize = sizeof(stats_t) * (NUM_DIVEMODE + 1);
|
|
||||||
dsize = sizeof(stats_t) * ((STATS_MAX_DEPTH / STATS_DEPTH_BUCKET) + 1);
|
|
||||||
tmsize = sizeof(stats_t) * ((STATS_MAX_TEMP / STATS_TEMP_BUCKET) + 1);
|
|
||||||
free_stats_summary(out);
|
|
||||||
out->stats_yearly = malloc(size);
|
|
||||||
out->stats_monthly = malloc(size);
|
|
||||||
out->stats_by_trip = malloc(size);
|
|
||||||
out->stats_by_type = malloc(tsize);
|
|
||||||
out->stats_by_depth = malloc(dsize);
|
|
||||||
out->stats_by_temp = malloc(tmsize);
|
|
||||||
if (!out->stats_yearly || !out->stats_monthly || !out->stats_by_trip ||
|
|
||||||
!out->stats_by_type || !out->stats_by_depth || !out->stats_by_temp)
|
|
||||||
return;
|
|
||||||
memset(out->stats_yearly, 0, size);
|
|
||||||
memset(out->stats_monthly, 0, size);
|
|
||||||
memset(out->stats_by_trip, 0, size);
|
|
||||||
memset(out->stats_by_type, 0, tsize);
|
|
||||||
memset(out->stats_by_depth, 0, dsize);
|
|
||||||
memset(out->stats_by_temp, 0, tmsize);
|
|
||||||
out->stats_yearly[0].is_year = true;
|
|
||||||
|
|
||||||
/* Setting the is_trip to true to show the location as first
|
|
||||||
* field in the statistics window */
|
|
||||||
out->stats_by_type[0].location = strdup(translate("gettextFromC", "All (by type stats)"));
|
|
||||||
out->stats_by_type[0].is_trip = true;
|
|
||||||
out->stats_by_type[1].location = strdup(translate("gettextFromC", divemode_text_ui[OC]));
|
|
||||||
out->stats_by_type[1].is_trip = true;
|
|
||||||
out->stats_by_type[2].location = strdup(translate("gettextFromC", divemode_text_ui[CCR]));
|
|
||||||
out->stats_by_type[2].is_trip = true;
|
|
||||||
out->stats_by_type[3].location = strdup(translate("gettextFromC", divemode_text_ui[PSCR]));
|
|
||||||
out->stats_by_type[3].is_trip = true;
|
|
||||||
out->stats_by_type[4].location = strdup(translate("gettextFromC", divemode_text_ui[FREEDIVE]));
|
|
||||||
out->stats_by_type[4].is_trip = true;
|
|
||||||
|
|
||||||
out->stats_by_depth[0].location = strdup(translate("gettextFromC", "All (by max depth stats)"));
|
|
||||||
out->stats_by_depth[0].is_trip = true;
|
|
||||||
|
|
||||||
out->stats_by_temp[0].location = strdup(translate("gettextFromC", "All (by min. temp stats)"));
|
|
||||||
out->stats_by_temp[0].is_trip = true;
|
|
||||||
|
|
||||||
/* this relies on the fact that the dives in the dive_table
|
|
||||||
* are in chronological order */
|
|
||||||
for_each_dive (idx, dp) {
|
|
||||||
if (selected_only && !dp->selected)
|
|
||||||
continue;
|
|
||||||
if (dp->invalid)
|
|
||||||
continue;
|
|
||||||
process_dive(dp, &stats);
|
|
||||||
|
|
||||||
/* yearly statistics */
|
|
||||||
utc_mkdate(dp->when, &tm);
|
|
||||||
if (current_year == 0)
|
|
||||||
current_year = tm.tm_year;
|
|
||||||
|
|
||||||
if (current_year != tm.tm_year) {
|
|
||||||
current_year = tm.tm_year;
|
|
||||||
process_dive(dp, &(out->stats_yearly[++year_iter]));
|
|
||||||
out->stats_yearly[year_iter].is_year = true;
|
|
||||||
} else {
|
|
||||||
process_dive(dp, &(out->stats_yearly[year_iter]));
|
|
||||||
}
|
|
||||||
out->stats_yearly[year_iter].selection_size++;
|
|
||||||
out->stats_yearly[year_iter].period = current_year;
|
|
||||||
|
|
||||||
/* stats_by_type[0] is all the dives combined */
|
|
||||||
out->stats_by_type[0].selection_size++;
|
|
||||||
process_dive(dp, &(out->stats_by_type[0]));
|
|
||||||
|
|
||||||
process_dive(dp, &(out->stats_by_type[dp->dc.divemode + 1]));
|
|
||||||
out->stats_by_type[dp->dc.divemode + 1].selection_size++;
|
|
||||||
|
|
||||||
/* stats_by_depth[0] is all the dives combined */
|
|
||||||
out->stats_by_depth[0].selection_size++;
|
|
||||||
process_dive(dp, &(out->stats_by_depth[0]));
|
|
||||||
|
|
||||||
d_idx = dp->maxdepth.mm / (STATS_DEPTH_BUCKET * 1000);
|
|
||||||
if (d_idx < 0)
|
|
||||||
d_idx = 0;
|
|
||||||
if (d_idx >= STATS_MAX_DEPTH / STATS_DEPTH_BUCKET)
|
|
||||||
d_idx = STATS_MAX_DEPTH / STATS_DEPTH_BUCKET - 1;
|
|
||||||
process_dive(dp, &(out->stats_by_depth[d_idx + 1]));
|
|
||||||
out->stats_by_depth[d_idx + 1].selection_size++;
|
|
||||||
|
|
||||||
/* stats_by_temp[0] is all the dives combined */
|
|
||||||
out->stats_by_temp[0].selection_size++;
|
|
||||||
process_dive(dp, &(out->stats_by_temp[0]));
|
|
||||||
|
|
||||||
t_idx = ((int)mkelvin_to_C(dp->mintemp.mkelvin)) / STATS_TEMP_BUCKET;
|
|
||||||
if (t_idx < 0)
|
|
||||||
t_idx = 0;
|
|
||||||
if (t_idx >= STATS_MAX_TEMP / STATS_TEMP_BUCKET)
|
|
||||||
t_idx = STATS_MAX_TEMP / STATS_TEMP_BUCKET - 1;
|
|
||||||
process_dive(dp, &(out->stats_by_temp[t_idx + 1]));
|
|
||||||
out->stats_by_temp[t_idx + 1].selection_size++;
|
|
||||||
|
|
||||||
if (dp->divetrip != NULL) {
|
|
||||||
if (trip_ptr != dp->divetrip) {
|
|
||||||
trip_ptr = dp->divetrip;
|
|
||||||
trip_iter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* stats_by_trip[0] is all the dives combined */
|
|
||||||
out->stats_by_trip[0].selection_size++;
|
|
||||||
process_dive(dp, &(out->stats_by_trip[0]));
|
|
||||||
out->stats_by_trip[0].is_trip = true;
|
|
||||||
out->stats_by_trip[0].location = strdup(translate("gettextFromC", "All (by trip stats)"));
|
|
||||||
|
|
||||||
process_dive(dp, &(out->stats_by_trip[trip_iter]));
|
|
||||||
out->stats_by_trip[trip_iter].selection_size++;
|
|
||||||
out->stats_by_trip[trip_iter].is_trip = true;
|
|
||||||
out->stats_by_trip[trip_iter].location = dp->divetrip->location;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* monthly statistics */
|
|
||||||
if (current_month == 0) {
|
|
||||||
current_month = tm.tm_mon + 1;
|
|
||||||
} else {
|
|
||||||
if (current_month != tm.tm_mon + 1)
|
|
||||||
current_month = tm.tm_mon + 1;
|
|
||||||
if (prev_month != current_month || prev_year != current_year)
|
|
||||||
month_iter++;
|
|
||||||
}
|
|
||||||
process_dive(dp, &(out->stats_monthly[month_iter]));
|
|
||||||
out->stats_monthly[month_iter].selection_size++;
|
|
||||||
out->stats_monthly[month_iter].period = current_month;
|
|
||||||
prev_month = current_month;
|
|
||||||
prev_year = current_year;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* add labels for depth ranges up to maximum depth seen */
|
|
||||||
if (out->stats_by_depth[0].selection_size) {
|
|
||||||
d_idx = out->stats_by_depth[0].max_depth.mm;
|
|
||||||
if (d_idx > STATS_MAX_DEPTH * 1000)
|
|
||||||
d_idx = STATS_MAX_DEPTH * 1000;
|
|
||||||
for (r = 0; r * (STATS_DEPTH_BUCKET * 1000) < d_idx; ++r)
|
|
||||||
out->stats_by_depth[r+1].is_trip = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* add labels for depth ranges up to maximum temperature seen */
|
|
||||||
if (out->stats_by_temp[0].selection_size) {
|
|
||||||
t_idx = (int)mkelvin_to_C(out->stats_by_temp[0].max_temp.mkelvin);
|
|
||||||
if (t_idx > STATS_MAX_TEMP)
|
|
||||||
t_idx = STATS_MAX_TEMP;
|
|
||||||
for (r = 0; r * STATS_TEMP_BUCKET < t_idx; ++r)
|
|
||||||
out->stats_by_temp[r+1].is_trip = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void free_stats_summary(struct stats_summary *stats)
|
|
||||||
{
|
|
||||||
free(stats->stats_yearly);
|
|
||||||
free(stats->stats_monthly);
|
|
||||||
free(stats->stats_by_trip);
|
|
||||||
free(stats->stats_by_type);
|
|
||||||
free(stats->stats_by_depth);
|
|
||||||
free(stats->stats_by_temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_stats_summary(struct stats_summary *stats)
|
|
||||||
{
|
|
||||||
stats->stats_yearly = NULL;
|
|
||||||
stats->stats_monthly = NULL;
|
|
||||||
stats->stats_by_trip = NULL;
|
|
||||||
stats->stats_by_type = NULL;
|
|
||||||
stats->stats_by_depth = NULL;
|
|
||||||
stats->stats_by_temp = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* make sure we skip the selected summary entries */
|
|
||||||
void calculate_stats_selected(stats_t *stats_selection)
|
|
||||||
{
|
|
||||||
struct dive *dive;
|
|
||||||
unsigned int i, nr;
|
|
||||||
|
|
||||||
memset(stats_selection, 0, sizeof(*stats_selection));
|
|
||||||
|
|
||||||
nr = 0;
|
|
||||||
for_each_dive(i, dive) {
|
|
||||||
if (dive->selected && !dive->invalid) {
|
|
||||||
process_dive(dive, stats_selection);
|
|
||||||
nr++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stats_selection->selection_size = nr;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SOME_GAS 5000 // 5bar drop in cylinder pressure makes cylinder used
|
|
||||||
|
|
||||||
bool has_gaschange_event(const struct dive *dive, const struct divecomputer *dc, int idx)
|
|
||||||
{
|
|
||||||
bool first_gas_explicit = false;
|
|
||||||
const struct event *event = get_next_event(dc->events, "gaschange");
|
|
||||||
while (event) {
|
|
||||||
if (dc->sample && (event->time.seconds == 0 ||
|
|
||||||
(dc->samples && dc->sample[0].time.seconds == event->time.seconds)))
|
|
||||||
first_gas_explicit = true;
|
|
||||||
if (get_cylinder_index(dive, event) == idx)
|
|
||||||
return true;
|
|
||||||
event = get_next_event(event->next, "gaschange");
|
|
||||||
}
|
|
||||||
return !first_gas_explicit && idx == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_cylinder_used(const struct dive *dive, int idx)
|
|
||||||
{
|
|
||||||
const struct divecomputer *dc;
|
|
||||||
cylinder_t *cyl;
|
|
||||||
if (idx < 0 || idx >= dive->cylinders.nr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
cyl = get_cylinder(dive, idx);
|
|
||||||
if ((cyl->start.mbar - cyl->end.mbar) > SOME_GAS)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if ((cyl->sample_start.mbar - cyl->sample_end.mbar) > SOME_GAS)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for_each_dc(dive, dc) {
|
|
||||||
if (has_gaschange_event(dive, dc, idx))
|
|
||||||
return true;
|
|
||||||
else if (dc->divemode == CCR && idx == get_cylinder_idx_by_use(dive, OXYGEN))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_cylinder_prot(const struct dive *dive, int idx)
|
|
||||||
{
|
|
||||||
const struct divecomputer *dc;
|
|
||||||
if (idx < 0 || idx >= dive->cylinders.nr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for_each_dc(dive, dc) {
|
|
||||||
if (has_gaschange_event(dive, dc, idx))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns a dynamically allocated array with dive->cylinders.nr entries,
|
|
||||||
* which has to be freed by the caller */
|
|
||||||
volume_t *get_gas_used(struct dive *dive)
|
|
||||||
{
|
|
||||||
int idx;
|
|
||||||
|
|
||||||
volume_t *gases = malloc(dive->cylinders.nr * sizeof(volume_t));
|
|
||||||
for (idx = 0; idx < dive->cylinders.nr; idx++) {
|
|
||||||
cylinder_t *cyl = get_cylinder(dive, idx);
|
|
||||||
pressure_t start, end;
|
|
||||||
|
|
||||||
start = cyl->start.mbar ? cyl->start : cyl->sample_start;
|
|
||||||
end = cyl->end.mbar ? cyl->end : cyl->sample_end;
|
|
||||||
if (end.mbar && start.mbar > end.mbar)
|
|
||||||
gases[idx].mliter = gas_volume(cyl, start) - gas_volume(cyl, end);
|
|
||||||
else
|
|
||||||
gases[idx].mliter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return gases;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Quite crude reverse-blender-function, but it produces a approx result */
|
|
||||||
static void get_gas_parts(struct gasmix mix, volume_t vol, int o2_in_topup, volume_t *o2, volume_t *he)
|
|
||||||
{
|
|
||||||
volume_t air = {};
|
|
||||||
|
|
||||||
if (gasmix_is_air(mix)) {
|
|
||||||
o2->mliter = 0;
|
|
||||||
he->mliter = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
air.mliter = lrint(((double)vol.mliter * get_n2(mix)) / (1000 - o2_in_topup));
|
|
||||||
he->mliter = lrint(((double)vol.mliter * get_he(mix)) / 1000.0);
|
|
||||||
o2->mliter += vol.mliter - he->mliter - air.mliter;
|
|
||||||
}
|
|
||||||
|
|
||||||
void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot)
|
|
||||||
{
|
|
||||||
int i, j;
|
|
||||||
struct dive *d;
|
|
||||||
for_each_dive (i, d) {
|
|
||||||
if (!d->selected || d->invalid)
|
|
||||||
continue;
|
|
||||||
volume_t *diveGases = get_gas_used(d);
|
|
||||||
for (j = 0; j < d->cylinders.nr; j++) {
|
|
||||||
if (diveGases[j].mliter) {
|
|
||||||
volume_t o2 = {}, he = {};
|
|
||||||
get_gas_parts(get_cylinder(d, j)->gasmix, diveGases[j], O2_IN_AIR, &o2, &he);
|
|
||||||
o2_tot->mliter += o2.mliter;
|
|
||||||
he_tot->mliter += he.mliter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(diveGases);
|
|
||||||
}
|
|
||||||
}
|
|
369
core/statistics.cpp
Normal file
369
core/statistics.cpp
Normal file
|
@ -0,0 +1,369 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/* statistics.cpp
|
||||||
|
*
|
||||||
|
* core logic for the Info & Stats page
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "statistics.h"
|
||||||
|
#include "dive.h"
|
||||||
|
#include "divelog.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "gettext.h"
|
||||||
|
#include "sample.h"
|
||||||
|
#include "subsurface-time.h"
|
||||||
|
#include "trip.h"
|
||||||
|
#include "units.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
static void process_temperatures(struct dive *dp, stats_t &stats)
|
||||||
|
{
|
||||||
|
temperature_t min_temp, mean_temp, max_temp = {.mkelvin = 0};
|
||||||
|
|
||||||
|
max_temp.mkelvin = dp->maxtemp.mkelvin;
|
||||||
|
if (max_temp.mkelvin && (!stats.max_temp.mkelvin || max_temp.mkelvin > stats.max_temp.mkelvin))
|
||||||
|
stats.max_temp.mkelvin = max_temp.mkelvin;
|
||||||
|
|
||||||
|
min_temp.mkelvin = dp->mintemp.mkelvin;
|
||||||
|
if (min_temp.mkelvin && (!stats.min_temp.mkelvin || min_temp.mkelvin < stats.min_temp.mkelvin))
|
||||||
|
stats.min_temp.mkelvin = min_temp.mkelvin;
|
||||||
|
|
||||||
|
if (min_temp.mkelvin || max_temp.mkelvin) {
|
||||||
|
mean_temp.mkelvin = min_temp.mkelvin;
|
||||||
|
if (mean_temp.mkelvin)
|
||||||
|
mean_temp.mkelvin = (mean_temp.mkelvin + max_temp.mkelvin) / 2;
|
||||||
|
else
|
||||||
|
mean_temp.mkelvin = max_temp.mkelvin;
|
||||||
|
stats.combined_temp.mkelvin += mean_temp.mkelvin;
|
||||||
|
stats.combined_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_dive(struct dive *dive, stats_t &stats)
|
||||||
|
{
|
||||||
|
int old_tadt, sac_time = 0;
|
||||||
|
int32_t duration = dive->duration.seconds;
|
||||||
|
|
||||||
|
old_tadt = stats.total_average_depth_time.seconds;
|
||||||
|
stats.total_time.seconds += duration;
|
||||||
|
if (duration > stats.longest_time.seconds)
|
||||||
|
stats.longest_time.seconds = duration;
|
||||||
|
if (stats.shortest_time.seconds == 0 || duration < stats.shortest_time.seconds)
|
||||||
|
stats.shortest_time.seconds = duration;
|
||||||
|
if (dive->maxdepth.mm > stats.max_depth.mm)
|
||||||
|
stats.max_depth.mm = dive->maxdepth.mm;
|
||||||
|
if (stats.min_depth.mm == 0 || dive->maxdepth.mm < stats.min_depth.mm)
|
||||||
|
stats.min_depth.mm = dive->maxdepth.mm;
|
||||||
|
stats.combined_max_depth.mm += dive->maxdepth.mm;
|
||||||
|
|
||||||
|
process_temperatures(dive, stats);
|
||||||
|
|
||||||
|
/* Maybe we should drop zero-duration dives */
|
||||||
|
if (!duration)
|
||||||
|
return;
|
||||||
|
if (dive->meandepth.mm) {
|
||||||
|
stats.total_average_depth_time.seconds += duration;
|
||||||
|
stats.avg_depth.mm = lrint((1.0 * old_tadt * stats.avg_depth.mm +
|
||||||
|
duration * dive->meandepth.mm) /
|
||||||
|
stats.total_average_depth_time.seconds);
|
||||||
|
}
|
||||||
|
if (dive->sac > 100) { /* less than .1 l/min is bogus, even with a pSCR */
|
||||||
|
sac_time = stats.total_sac_time.seconds + duration;
|
||||||
|
stats.avg_sac.mliter = lrint((1.0 * stats.total_sac_time.seconds * stats.avg_sac.mliter +
|
||||||
|
duration * dive->sac) /
|
||||||
|
sac_time);
|
||||||
|
if (dive->sac > stats.max_sac.mliter)
|
||||||
|
stats.max_sac.mliter = dive->sac;
|
||||||
|
if (stats.min_sac.mliter == 0 || dive->sac < stats.min_sac.mliter)
|
||||||
|
stats.min_sac.mliter = dive->sac;
|
||||||
|
stats.total_sac_time.seconds = sac_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate a summary of the statistics and put in the stats_summary
|
||||||
|
* structure provided in the first parameter.
|
||||||
|
* Before first use, it should be initialized with init_stats_summary().
|
||||||
|
* After use, memory must be released with free_stats_summary().
|
||||||
|
*/
|
||||||
|
stats_summary calculate_stats_summary(bool selected_only)
|
||||||
|
{
|
||||||
|
int idx;
|
||||||
|
struct dive *dp;
|
||||||
|
struct tm tm;
|
||||||
|
int current_year = -1;
|
||||||
|
int current_month = 0;
|
||||||
|
int prev_month = 0, prev_year = 0;
|
||||||
|
dive_trip_t *trip_ptr = nullptr;
|
||||||
|
//stats_t stats = { 0 };
|
||||||
|
|
||||||
|
//if (divelog.dives->nr > 0) {
|
||||||
|
// stats.shortest_time.seconds = divelog.dives->dives[0]->duration.seconds;
|
||||||
|
// stats.min_depth.mm = divelog.dives->dives[0]->maxdepth.mm;
|
||||||
|
// stats.selection_size = divelog.dives->nr;
|
||||||
|
//}
|
||||||
|
|
||||||
|
stats_summary out;
|
||||||
|
|
||||||
|
/* stats_by_trip[0] is all the dives combined */
|
||||||
|
out.stats_by_trip.emplace_back();
|
||||||
|
|
||||||
|
/* Setting the is_trip to true to show the location as first
|
||||||
|
* field in the statistics window */
|
||||||
|
out.stats_by_type.resize(NUM_DIVEMODE + 1);
|
||||||
|
out.stats_by_type[0].location = translate("gettextFromC", "All (by type stats)");
|
||||||
|
out.stats_by_type[0].is_trip = true;
|
||||||
|
out.stats_by_type[1].location = translate("gettextFromC", divemode_text_ui[OC]);
|
||||||
|
out.stats_by_type[1].is_trip = true;
|
||||||
|
out.stats_by_type[2].location = translate("gettextFromC", divemode_text_ui[CCR]);
|
||||||
|
out.stats_by_type[2].is_trip = true;
|
||||||
|
out.stats_by_type[3].location = translate("gettextFromC", divemode_text_ui[PSCR]);
|
||||||
|
out.stats_by_type[3].is_trip = true;
|
||||||
|
out.stats_by_type[4].location = translate("gettextFromC", divemode_text_ui[FREEDIVE]);
|
||||||
|
out.stats_by_type[4].is_trip = true;
|
||||||
|
|
||||||
|
out.stats_by_depth.resize((STATS_MAX_DEPTH / STATS_DEPTH_BUCKET) + 1);
|
||||||
|
out.stats_by_depth[0].location = translate("gettextFromC", "All (by max depth stats)");
|
||||||
|
out.stats_by_depth[0].is_trip = true;
|
||||||
|
|
||||||
|
out.stats_by_temp.resize((STATS_MAX_TEMP / STATS_TEMP_BUCKET) + 1);
|
||||||
|
out.stats_by_temp[0].location = translate("gettextFromC", "All (by min. temp stats)");
|
||||||
|
out.stats_by_temp[0].is_trip = true;
|
||||||
|
|
||||||
|
/* this relies on the fact that the dives in the dive_table
|
||||||
|
* are in chronological order */
|
||||||
|
for_each_dive (idx, dp) {
|
||||||
|
if (selected_only && !dp->selected)
|
||||||
|
continue;
|
||||||
|
if (dp->invalid)
|
||||||
|
continue;
|
||||||
|
//process_dive(dp, &stats);
|
||||||
|
|
||||||
|
/* yearly statistics */
|
||||||
|
utc_mkdate(dp->when, &tm);
|
||||||
|
|
||||||
|
if (current_year != tm.tm_year || out.stats_yearly.empty()) {
|
||||||
|
current_year = tm.tm_year;
|
||||||
|
out.stats_yearly.emplace_back();
|
||||||
|
out.stats_yearly.back().is_year = true;
|
||||||
|
}
|
||||||
|
process_dive(dp, out.stats_yearly.back());
|
||||||
|
|
||||||
|
out.stats_yearly.back().selection_size++;
|
||||||
|
out.stats_yearly.back().period = current_year;
|
||||||
|
|
||||||
|
/* stats_by_type[0] is all the dives combined */
|
||||||
|
out.stats_by_type[0].selection_size++;
|
||||||
|
process_dive(dp, out.stats_by_type[0]);
|
||||||
|
|
||||||
|
process_dive(dp, out.stats_by_type[dp->dc.divemode + 1]);
|
||||||
|
out.stats_by_type[dp->dc.divemode + 1].selection_size++;
|
||||||
|
|
||||||
|
/* stats_by_depth[0] is all the dives combined */
|
||||||
|
out.stats_by_depth[0].selection_size++;
|
||||||
|
process_dive(dp, out.stats_by_depth[0]);
|
||||||
|
|
||||||
|
int d_idx = dp->maxdepth.mm / (STATS_DEPTH_BUCKET * 1000);
|
||||||
|
d_idx = std::clamp(d_idx, 0, STATS_MAX_DEPTH / STATS_DEPTH_BUCKET);
|
||||||
|
process_dive(dp, out.stats_by_depth[d_idx + 1]);
|
||||||
|
out.stats_by_depth[d_idx + 1].selection_size++;
|
||||||
|
|
||||||
|
/* stats_by_temp[0] is all the dives combined */
|
||||||
|
out.stats_by_temp[0].selection_size++;
|
||||||
|
process_dive(dp, out.stats_by_temp[0]);
|
||||||
|
|
||||||
|
int t_idx = ((int)mkelvin_to_C(dp->mintemp.mkelvin)) / STATS_TEMP_BUCKET;
|
||||||
|
t_idx = std::clamp(t_idx, 0, STATS_MAX_TEMP / STATS_TEMP_BUCKET);
|
||||||
|
process_dive(dp, out.stats_by_temp[t_idx + 1]);
|
||||||
|
out.stats_by_temp[t_idx + 1].selection_size++;
|
||||||
|
|
||||||
|
if (dp->divetrip != NULL) {
|
||||||
|
if (trip_ptr != dp->divetrip) {
|
||||||
|
trip_ptr = dp->divetrip;
|
||||||
|
out.stats_by_trip.emplace_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stats_by_trip[0] is all the dives combined */
|
||||||
|
/* TODO: yet, this doesn't seem to consider dives outside of trips !? */
|
||||||
|
out.stats_by_trip[0].selection_size++;
|
||||||
|
process_dive(dp, out.stats_by_trip[0]);
|
||||||
|
out.stats_by_trip[0].is_trip = true;
|
||||||
|
out.stats_by_trip[0].location = translate("gettextFromC", "All (by trip stats)");
|
||||||
|
|
||||||
|
process_dive(dp, out.stats_by_trip.back());
|
||||||
|
out.stats_by_trip.back().selection_size++;
|
||||||
|
out.stats_by_trip.back().is_trip = true;
|
||||||
|
out.stats_by_trip.back().location = dp->divetrip->location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* monthly statistics */
|
||||||
|
if (current_month == 0 || out.stats_monthly.empty()) {
|
||||||
|
current_month = tm.tm_mon + 1;
|
||||||
|
out.stats_monthly.emplace_back();
|
||||||
|
} else {
|
||||||
|
if (current_month != tm.tm_mon + 1)
|
||||||
|
current_month = tm.tm_mon + 1;
|
||||||
|
if (prev_month != current_month || prev_year != current_year)
|
||||||
|
out.stats_monthly.emplace_back();
|
||||||
|
}
|
||||||
|
process_dive(dp, out.stats_monthly.back());
|
||||||
|
out.stats_monthly.back().selection_size++;
|
||||||
|
out.stats_monthly.back().period = current_month;
|
||||||
|
prev_month = current_month;
|
||||||
|
prev_year = current_year;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add labels for depth ranges up to maximum depth seen */
|
||||||
|
if (out.stats_by_depth[0].selection_size) {
|
||||||
|
int d_idx = out.stats_by_depth[0].max_depth.mm;
|
||||||
|
if (d_idx > STATS_MAX_DEPTH * 1000)
|
||||||
|
d_idx = STATS_MAX_DEPTH * 1000;
|
||||||
|
for (int r = 0; r * (STATS_DEPTH_BUCKET * 1000) < d_idx; ++r)
|
||||||
|
out.stats_by_depth[r+1].is_trip = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add labels for depth ranges up to maximum temperature seen */
|
||||||
|
if (out.stats_by_temp[0].selection_size) {
|
||||||
|
int t_idx = (int)mkelvin_to_C(out.stats_by_temp[0].max_temp.mkelvin);
|
||||||
|
if (t_idx > STATS_MAX_TEMP)
|
||||||
|
t_idx = STATS_MAX_TEMP;
|
||||||
|
for (int r = 0; r * STATS_TEMP_BUCKET < t_idx; ++r)
|
||||||
|
out.stats_by_temp[r+1].is_trip = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
stats_summary::stats_summary()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
stats_summary::~stats_summary()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/* make sure we skip the selected summary entries */
|
||||||
|
stats_t calculate_stats_selected()
|
||||||
|
{
|
||||||
|
stats_t stats_selection;
|
||||||
|
struct dive *dive;
|
||||||
|
unsigned int i, nr;
|
||||||
|
|
||||||
|
nr = 0;
|
||||||
|
for_each_dive(i, dive) {
|
||||||
|
if (dive->selected && !dive->invalid) {
|
||||||
|
process_dive(dive, stats_selection);
|
||||||
|
nr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats_selection.selection_size = nr;
|
||||||
|
return stats_selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SOME_GAS 5000 // 5bar drop in cylinder pressure makes cylinder used
|
||||||
|
|
||||||
|
bool has_gaschange_event(const struct dive *dive, const struct divecomputer *dc, int idx)
|
||||||
|
{
|
||||||
|
bool first_gas_explicit = false;
|
||||||
|
const struct event *event = get_next_event(dc->events, "gaschange");
|
||||||
|
while (event) {
|
||||||
|
if (dc->sample && (event->time.seconds == 0 ||
|
||||||
|
(dc->samples && dc->sample[0].time.seconds == event->time.seconds)))
|
||||||
|
first_gas_explicit = true;
|
||||||
|
if (get_cylinder_index(dive, event) == idx)
|
||||||
|
return true;
|
||||||
|
event = get_next_event(event->next, "gaschange");
|
||||||
|
}
|
||||||
|
return !first_gas_explicit && idx == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_cylinder_used(const struct dive *dive, int idx)
|
||||||
|
{
|
||||||
|
const struct divecomputer *dc;
|
||||||
|
cylinder_t *cyl;
|
||||||
|
if (idx < 0 || idx >= dive->cylinders.nr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
cyl = get_cylinder(dive, idx);
|
||||||
|
if ((cyl->start.mbar - cyl->end.mbar) > SOME_GAS)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if ((cyl->sample_start.mbar - cyl->sample_end.mbar) > SOME_GAS)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for_each_dc(dive, dc) {
|
||||||
|
if (has_gaschange_event(dive, dc, idx))
|
||||||
|
return true;
|
||||||
|
else if (dc->divemode == CCR && idx == get_cylinder_idx_by_use(dive, OXYGEN))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_cylinder_prot(const struct dive *dive, int idx)
|
||||||
|
{
|
||||||
|
const struct divecomputer *dc;
|
||||||
|
if (idx < 0 || idx >= dive->cylinders.nr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for_each_dc(dive, dc) {
|
||||||
|
if (has_gaschange_event(dive, dc, idx))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns a vector with dive->cylinders.nr entries */
|
||||||
|
std::vector<volume_t> get_gas_used(struct dive *dive)
|
||||||
|
{
|
||||||
|
std::vector<volume_t> gases(dive->cylinders.nr, volume_t { 0 });
|
||||||
|
for (int idx = 0; idx < dive->cylinders.nr; idx++) {
|
||||||
|
cylinder_t *cyl = get_cylinder(dive, idx);
|
||||||
|
pressure_t start, end;
|
||||||
|
|
||||||
|
start = cyl->start.mbar ? cyl->start : cyl->sample_start;
|
||||||
|
end = cyl->end.mbar ? cyl->end : cyl->sample_end;
|
||||||
|
if (end.mbar && start.mbar > end.mbar)
|
||||||
|
gases[idx].mliter = gas_volume(cyl, start) - gas_volume(cyl, end);
|
||||||
|
else
|
||||||
|
gases[idx].mliter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return gases;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quite crude reverse-blender-function, but it produces an approx result.
|
||||||
|
* Returns an (O2, He) pair. */
|
||||||
|
static std::pair<volume_t, volume_t> get_gas_parts(struct gasmix mix, volume_t vol, int o2_in_topup)
|
||||||
|
{
|
||||||
|
if (gasmix_is_air(mix))
|
||||||
|
return { {0}, {0} };
|
||||||
|
|
||||||
|
volume_t air = { (int)lrint(((double)vol.mliter * get_n2(mix)) / (1000 - o2_in_topup)) };
|
||||||
|
volume_t he = { (int)lrint(((double)vol.mliter * get_he(mix)) / 1000.0) };
|
||||||
|
volume_t o2 = { vol.mliter - he.mliter - air.mliter };
|
||||||
|
return std::make_pair(o2, he);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<volume_t, volume_t> selected_dives_gas_parts()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct dive *d;
|
||||||
|
volume_t o2_tot = { 0 }, he_tot = { 0 };
|
||||||
|
for_each_dive (i, d) {
|
||||||
|
if (!d->selected || d->invalid)
|
||||||
|
continue;
|
||||||
|
int j = 0;
|
||||||
|
for (auto &gas: get_gas_used(d)) {
|
||||||
|
if (gas.mliter) {
|
||||||
|
auto [o2, he] = get_gas_parts(get_cylinder(d, j)->gasmix, gas, O2_IN_AIR);
|
||||||
|
o2_tot.mliter += o2.mliter;
|
||||||
|
he_tot.mliter += he.mliter;
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::make_pair(o2_tot, he_tot);
|
||||||
|
}
|
|
@ -13,81 +13,59 @@
|
||||||
|
|
||||||
#define STATS_MAX_DEPTH 300 /* Max depth for stats bucket is 300m */
|
#define STATS_MAX_DEPTH 300 /* Max depth for stats bucket is 300m */
|
||||||
#define STATS_DEPTH_BUCKET 10 /* Size of buckets for depth range */
|
#define STATS_DEPTH_BUCKET 10 /* Size of buckets for depth range */
|
||||||
#define STATS_MAX_TEMP 40 /* Max temp for stats bucket is 40C */
|
#define STATS_MAX_TEMP 40 /* Max temp for stats bucket is 40C */
|
||||||
#define STATS_TEMP_BUCKET 5 /* Size of buckets for temp range */
|
#define STATS_TEMP_BUCKET 5 /* Size of buckets for temp range */
|
||||||
|
|
||||||
struct dive;
|
struct dive;
|
||||||
|
|
||||||
typedef struct
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct stats_t
|
||||||
{
|
{
|
||||||
int period;
|
int period = 0;
|
||||||
duration_t total_time;
|
duration_t total_time = { 0 };
|
||||||
/* total time of dives with non-zero average depth */
|
/* total time of dives with non-zero average depth */
|
||||||
duration_t total_average_depth_time;
|
duration_t total_average_depth_time = { 0 };
|
||||||
/* avg_time is simply total_time / nr -- let's not keep this */
|
/* avg_time is simply total_time / nr -- let's not keep this */
|
||||||
duration_t shortest_time;
|
duration_t shortest_time = { 0 };
|
||||||
duration_t longest_time;
|
duration_t longest_time = { 0 };
|
||||||
depth_t max_depth;
|
depth_t max_depth = { 0 };
|
||||||
depth_t min_depth;
|
depth_t min_depth = { 0 };
|
||||||
depth_t avg_depth;
|
depth_t avg_depth = { 0 };
|
||||||
depth_t combined_max_depth;
|
depth_t combined_max_depth = { 0 };
|
||||||
volume_t max_sac;
|
volume_t max_sac = { 0 };
|
||||||
volume_t min_sac;
|
volume_t min_sac = { 0 };
|
||||||
volume_t avg_sac;
|
volume_t avg_sac = { 0 };
|
||||||
temperature_t max_temp;
|
temperature_t max_temp = { 0 };
|
||||||
temperature_t min_temp;
|
temperature_t min_temp = { 0 };
|
||||||
temperature_sum_t combined_temp;
|
temperature_sum_t combined_temp = { 0 };
|
||||||
unsigned int combined_count;
|
unsigned int combined_count = 0;
|
||||||
unsigned int selection_size;
|
unsigned int selection_size = 0;
|
||||||
duration_t total_sac_time;
|
duration_t total_sac_time = { 0 };
|
||||||
bool is_year;
|
bool is_year = false;
|
||||||
bool is_trip;
|
bool is_trip = false;
|
||||||
char *location;
|
std::string location;
|
||||||
} stats_t;
|
};
|
||||||
|
|
||||||
struct stats_summary {
|
struct stats_summary {
|
||||||
stats_t *stats_yearly;
|
stats_summary();
|
||||||
stats_t *stats_monthly;
|
~stats_summary();
|
||||||
stats_t *stats_by_trip;
|
std::vector<stats_t> stats_yearly;
|
||||||
stats_t *stats_by_type;
|
std::vector<stats_t> stats_monthly;
|
||||||
stats_t *stats_by_depth;
|
std::vector<stats_t> stats_by_trip;
|
||||||
stats_t *stats_by_temp;
|
std::vector<stats_t> stats_by_type;
|
||||||
|
std::vector<stats_t> stats_by_depth;
|
||||||
|
std::vector<stats_t> stats_by_temp;
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef __cplusplus
|
extern stats_summary calculate_stats_summary(bool selected_only);
|
||||||
extern "C" {
|
extern stats_t calculate_stats_selected();
|
||||||
#endif
|
extern std::vector<volume_t> get_gas_used(struct dive *dive);
|
||||||
|
extern std::pair<volume_t, volume_t> selected_dives_gas_parts(); // returns (O2, He) tuple
|
||||||
|
|
||||||
extern void init_stats_summary(struct stats_summary *stats);
|
|
||||||
extern void free_stats_summary(struct stats_summary *stats);
|
|
||||||
extern void calculate_stats_summary(struct stats_summary *stats, bool selected_only);
|
|
||||||
extern void calculate_stats_selected(stats_t *stats_selection);
|
|
||||||
extern volume_t *get_gas_used(struct dive *dive);
|
|
||||||
extern void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For C++ code, provide a convenience version of stats_summary
|
|
||||||
* that initializes the structure on construction and frees
|
|
||||||
* resources when it goes out of scope. Apart from that, it
|
|
||||||
* can be used as a stats_summary replacement.
|
|
||||||
*/
|
|
||||||
#ifdef __cplusplus
|
|
||||||
struct stats_summary_auto_free : public stats_summary {
|
|
||||||
stats_summary_auto_free();
|
|
||||||
~stats_summary_auto_free();
|
|
||||||
};
|
|
||||||
inline stats_summary_auto_free::stats_summary_auto_free()
|
|
||||||
{
|
|
||||||
init_stats_summary(this);
|
|
||||||
}
|
|
||||||
inline stats_summary_auto_free::~stats_summary_auto_free()
|
|
||||||
{
|
|
||||||
free_stats_summary(this);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // STATISTICS_H
|
#endif // STATISTICS_H
|
||||||
|
|
|
@ -124,7 +124,7 @@ void TabDiveInformation::updateProfile()
|
||||||
ui->maximumDepthText->setText(get_depth_string(currentDive->maxdepth, true));
|
ui->maximumDepthText->setText(get_depth_string(currentDive->maxdepth, true));
|
||||||
ui->averageDepthText->setText(get_depth_string(currentDive->meandepth, true));
|
ui->averageDepthText->setText(get_depth_string(currentDive->meandepth, true));
|
||||||
|
|
||||||
volume_t *gases = get_gas_used(currentDive);
|
std::vector<volume_t> gases = get_gas_used(currentDive);
|
||||||
QString volumes;
|
QString volumes;
|
||||||
std::vector<int> mean(currentDive->cylinders.nr), duration(currentDive->cylinders.nr);
|
std::vector<int> mean(currentDive->cylinders.nr), duration(currentDive->cylinders.nr);
|
||||||
struct divecomputer *currentdc = parent.getCurrentDC();
|
struct divecomputer *currentdc = parent.getCurrentDC();
|
||||||
|
@ -148,7 +148,6 @@ void TabDiveInformation::updateProfile()
|
||||||
SACs.append(get_volume_string(sac, true).append(tr("/min")));
|
SACs.append(get_volume_string(sac, true).append(tr("/min")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(gases);
|
|
||||||
ui->gasUsedText->setText(volumes);
|
ui->gasUsedText->setText(volumes);
|
||||||
ui->oxygenHeliumText->setText(gaslist);
|
ui->oxygenHeliumText->setText(gaslist);
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,7 @@ void TabDiveStatistics::cylinderChanged(dive *d)
|
||||||
|
|
||||||
void TabDiveStatistics::updateData(const std::vector<dive *> &, dive *currentDive, int)
|
void TabDiveStatistics::updateData(const std::vector<dive *> &, dive *currentDive, int)
|
||||||
{
|
{
|
||||||
stats_t stats_selection;
|
stats_t stats_selection = calculate_stats_selected();
|
||||||
calculate_stats_selected(&stats_selection);
|
|
||||||
clear();
|
clear();
|
||||||
if (amount_selected > 1 && stats_selection.selection_size >= 1) {
|
if (amount_selected > 1 && stats_selection.selection_size >= 1) {
|
||||||
ui->depthLimits->setMaximum(get_depth_string(stats_selection.max_depth, true));
|
ui->depthLimits->setMaximum(get_depth_string(stats_selection.max_depth, true));
|
||||||
|
@ -135,13 +134,12 @@ void TabDiveStatistics::updateData(const std::vector<dive *> &, dive *currentDiv
|
||||||
vol.mliter = gasPair.second;
|
vol.mliter = gasPair.second;
|
||||||
gasUsedString.append(gasPair.first).append(": ").append(get_volume_string(vol, true)).append("\n");
|
gasUsedString.append(gasPair.first).append(": ").append(get_volume_string(vol, true)).append("\n");
|
||||||
}
|
}
|
||||||
volume_t o2_tot = {}, he_tot = {};
|
auto [o2_tot, he_tot] = selected_dives_gas_parts();
|
||||||
selected_dives_gas_parts(&o2_tot, &he_tot);
|
|
||||||
|
|
||||||
/* No need to show the gas mixing information if diving
|
/* No need to show the gas mixing information if diving
|
||||||
* with pure air, and only display the he / O2 part when
|
* with pure air, and only display the he / O2 part when
|
||||||
* it is used.
|
* it is used.
|
||||||
*/
|
*/
|
||||||
if (he_tot.mliter || o2_tot.mliter) {
|
if (he_tot.mliter || o2_tot.mliter) {
|
||||||
gasUsedString.append(tr("These gases could be\nmixed from Air and using:\n"));
|
gasUsedString.append(tr("These gases could be\nmixed from Air and using:\n"));
|
||||||
if (he_tot.mliter) {
|
if (he_tot.mliter) {
|
||||||
|
|
|
@ -123,10 +123,9 @@ QString TemplateLayout::generateStatistics()
|
||||||
State state;
|
State state;
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
stats_summary_auto_free stats;
|
stats_summary stats = calculate_stats_summary(false);
|
||||||
calculate_stats_summary(&stats, false);
|
for (auto &s: stats.stats_yearly) {
|
||||||
while (stats.stats_yearly != NULL && stats.stats_yearly[i].period) {
|
state.years.append(&s);
|
||||||
state.years.append(&stats.stats_yearly[i]);
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,77 +42,63 @@ YearStatisticsItem::YearStatisticsItem(const stats_t &interval) : stats_interval
|
||||||
|
|
||||||
QVariant YearStatisticsItem::data(int column, int role) const
|
QVariant YearStatisticsItem::data(int column, int role) const
|
||||||
{
|
{
|
||||||
QVariant ret;
|
|
||||||
|
|
||||||
if (role == Qt::FontRole) {
|
if (role == Qt::FontRole) {
|
||||||
QFont font = defaultModelFont();
|
QFont font = defaultModelFont();
|
||||||
font.setBold(stats_interval.is_year);
|
font.setBold(stats_interval.is_year);
|
||||||
return font;
|
return font;
|
||||||
} else if (role != Qt::DisplayRole) {
|
} else if (role != Qt::DisplayRole) {
|
||||||
return ret;
|
return QVariant();
|
||||||
}
|
}
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case YEAR:
|
case YEAR:
|
||||||
if (stats_interval.is_trip) {
|
if (stats_interval.is_trip) {
|
||||||
ret = QString(stats_interval.location);
|
return QString::fromStdString(stats_interval.location);
|
||||||
} else {
|
} else {
|
||||||
ret = stats_interval.period;
|
return stats_interval.period;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case DIVES:
|
case DIVES:
|
||||||
ret = stats_interval.selection_size;
|
return stats_interval.selection_size;
|
||||||
break;
|
|
||||||
case TOTAL_TIME:
|
case TOTAL_TIME:
|
||||||
ret = get_dive_duration_string(stats_interval.total_time.seconds, tr("h"), tr("min"), tr("sec"), " ");
|
return get_dive_duration_string(stats_interval.total_time.seconds, tr("h"), tr("min"), tr("sec"), " ");
|
||||||
break;
|
|
||||||
case AVERAGE_TIME:
|
case AVERAGE_TIME:
|
||||||
ret = formatMinutes(stats_interval.total_time.seconds / stats_interval.selection_size);
|
return formatMinutes(stats_interval.total_time.seconds / stats_interval.selection_size);
|
||||||
break;
|
|
||||||
case SHORTEST_TIME:
|
case SHORTEST_TIME:
|
||||||
ret = formatMinutes(stats_interval.shortest_time.seconds);
|
return formatMinutes(stats_interval.shortest_time.seconds);
|
||||||
break;
|
|
||||||
case LONGEST_TIME:
|
case LONGEST_TIME:
|
||||||
ret = formatMinutes(stats_interval.longest_time.seconds);
|
return formatMinutes(stats_interval.longest_time.seconds);
|
||||||
break;
|
|
||||||
case AVG_DEPTH:
|
case AVG_DEPTH:
|
||||||
ret = get_depth_string(stats_interval.avg_depth);
|
return get_depth_string(stats_interval.avg_depth);
|
||||||
break;
|
|
||||||
case AVG_MAX_DEPTH:
|
case AVG_MAX_DEPTH:
|
||||||
if (stats_interval.selection_size)
|
if (stats_interval.selection_size)
|
||||||
ret = get_depth_string(stats_interval.combined_max_depth.mm / stats_interval.selection_size);
|
return get_depth_string(stats_interval.combined_max_depth.mm / stats_interval.selection_size);
|
||||||
break;
|
break;
|
||||||
case MIN_DEPTH:
|
case MIN_DEPTH:
|
||||||
ret = get_depth_string(stats_interval.min_depth);
|
return get_depth_string(stats_interval.min_depth);
|
||||||
break;
|
|
||||||
case MAX_DEPTH:
|
case MAX_DEPTH:
|
||||||
ret = get_depth_string(stats_interval.max_depth);
|
return get_depth_string(stats_interval.max_depth);
|
||||||
break;
|
|
||||||
case AVG_SAC:
|
case AVG_SAC:
|
||||||
ret = get_volume_string(stats_interval.avg_sac);
|
return get_volume_string(stats_interval.avg_sac);
|
||||||
break;
|
|
||||||
case MIN_SAC:
|
case MIN_SAC:
|
||||||
ret = get_volume_string(stats_interval.min_sac);
|
return get_volume_string(stats_interval.min_sac);
|
||||||
break;
|
|
||||||
case MAX_SAC:
|
case MAX_SAC:
|
||||||
ret = get_volume_string(stats_interval.max_sac);
|
return get_volume_string(stats_interval.max_sac);
|
||||||
break;
|
|
||||||
case AVG_TEMP:
|
case AVG_TEMP:
|
||||||
if (stats_interval.combined_temp.mkelvin && stats_interval.combined_count) {
|
if (stats_interval.combined_temp.mkelvin && stats_interval.combined_count) {
|
||||||
temperature_t avg_temp;
|
temperature_t avg_temp;
|
||||||
avg_temp.mkelvin = stats_interval.combined_temp.mkelvin / stats_interval.combined_count;
|
avg_temp.mkelvin = stats_interval.combined_temp.mkelvin / stats_interval.combined_count;
|
||||||
ret = get_temperature_string(avg_temp);
|
return get_temperature_string(avg_temp);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MIN_TEMP:
|
case MIN_TEMP:
|
||||||
if (stats_interval.min_temp.mkelvin)
|
if (stats_interval.min_temp.mkelvin)
|
||||||
ret = get_temperature_string(stats_interval.min_temp);
|
return get_temperature_string(stats_interval.min_temp);
|
||||||
break;
|
break;
|
||||||
case MAX_TEMP:
|
case MAX_TEMP:
|
||||||
if (stats_interval.max_temp.mkelvin)
|
if (stats_interval.max_temp.mkelvin)
|
||||||
ret = get_temperature_string(stats_interval.max_temp);
|
return get_temperature_string(stats_interval.max_temp);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return ret;
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
YearlyStatisticsModel::YearlyStatisticsModel(QObject *parent) : TreeModel(parent)
|
YearlyStatisticsModel::YearlyStatisticsModel(QObject *parent) : TreeModel(parent)
|
||||||
|
@ -184,17 +170,15 @@ QVariant YearlyStatisticsModel::headerData(int section, Qt::Orientation orientat
|
||||||
|
|
||||||
void YearlyStatisticsModel::update_yearly_stats()
|
void YearlyStatisticsModel::update_yearly_stats()
|
||||||
{
|
{
|
||||||
int i, month = 0;
|
|
||||||
unsigned int j, combined_months;
|
|
||||||
stats_summary_auto_free stats;
|
|
||||||
QString label;
|
QString label;
|
||||||
temperature_t t_range_min,t_range_max;
|
temperature_t t_range_min,t_range_max;
|
||||||
calculate_stats_summary(&stats, false);
|
stats_summary stats = calculate_stats_summary(false);
|
||||||
|
|
||||||
for (i = 0; stats.stats_yearly != NULL && stats.stats_yearly[i].period; ++i) {
|
int month = 0;
|
||||||
YearStatisticsItem *item = new YearStatisticsItem(stats.stats_yearly[i]);
|
for (const auto &s: stats.stats_yearly) {
|
||||||
combined_months = 0;
|
YearStatisticsItem *item = new YearStatisticsItem(s);
|
||||||
for (j = 0; combined_months < stats.stats_yearly[i].selection_size; ++j) {
|
size_t combined_months = 0;
|
||||||
|
while (combined_months < s.selection_size) {
|
||||||
combined_months += stats.stats_monthly[month].selection_size;
|
combined_months += stats.stats_monthly[month].selection_size;
|
||||||
YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_monthly[month]);
|
YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_monthly[month]);
|
||||||
item->children.append(iChild);
|
item->children.append(iChild);
|
||||||
|
@ -205,10 +189,10 @@ void YearlyStatisticsModel::update_yearly_stats()
|
||||||
item->parent = rootItem.get();
|
item->parent = rootItem.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stats.stats_by_trip != NULL && stats.stats_by_trip[0].is_trip == true) {
|
if (stats.stats_by_trip[0].is_trip == true) {
|
||||||
YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_trip[0]);
|
YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_trip[0]);
|
||||||
for (i = 1; stats.stats_by_trip != NULL && stats.stats_by_trip[i].is_trip; ++i) {
|
for (auto it = std::next(stats.stats_by_trip.begin()); it != stats.stats_by_trip.end(); ++it) {
|
||||||
YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_trip[i]);
|
YearStatisticsItem *iChild = new YearStatisticsItem(*it);
|
||||||
item->children.append(iChild);
|
item->children.append(iChild);
|
||||||
iChild->parent = item;
|
iChild->parent = item;
|
||||||
}
|
}
|
||||||
|
@ -217,12 +201,12 @@ void YearlyStatisticsModel::update_yearly_stats()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show the statistic sorted by dive type */
|
/* Show the statistic sorted by dive type */
|
||||||
if (stats.stats_by_type != NULL && stats.stats_by_type[0].selection_size) {
|
if (stats.stats_by_type[0].selection_size) {
|
||||||
YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_type[0]);
|
YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_type[0]);
|
||||||
for (i = 1; i <= NUM_DIVEMODE; ++i) {
|
for (auto it = std::next(stats.stats_by_type.begin()); it != stats.stats_by_type.end(); ++it) {
|
||||||
if (stats.stats_by_type[i].selection_size == 0)
|
if (it->selection_size == 0)
|
||||||
continue;
|
continue;
|
||||||
YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_type[i]);
|
YearStatisticsItem *iChild = new YearStatisticsItem(*it);
|
||||||
item->children.append(iChild);
|
item->children.append(iChild);
|
||||||
iChild->parent = item;
|
iChild->parent = item;
|
||||||
}
|
}
|
||||||
|
@ -231,35 +215,41 @@ void YearlyStatisticsModel::update_yearly_stats()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show the statistic sorted by dive depth */
|
/* Show the statistic sorted by dive depth */
|
||||||
if (stats.stats_by_depth != NULL && stats.stats_by_depth[0].selection_size) {
|
if (stats.stats_by_depth[0].selection_size) {
|
||||||
YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_depth[0]);
|
YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_depth[0]);
|
||||||
for (i = 1; stats.stats_by_depth[i].is_trip; ++i)
|
int i = 0;
|
||||||
if (stats.stats_by_depth[i].selection_size) {
|
for (auto it = std::next(stats.stats_by_depth.begin()); it != stats.stats_by_depth.end(); ++it) {
|
||||||
label = QString(tr("%1 - %2")).arg(get_depth_string((i - 1) * (STATS_DEPTH_BUCKET * 1000), true, false),
|
if (it->selection_size) {
|
||||||
get_depth_string(i * (STATS_DEPTH_BUCKET * 1000), true, false));
|
QString label = QString(tr("%1 - %2")).arg(get_depth_string(i * (STATS_DEPTH_BUCKET * 1000), true, false),
|
||||||
stats.stats_by_depth[i].location = strdup(label.toUtf8().data());
|
get_depth_string((i + 1) * (STATS_DEPTH_BUCKET * 1000), true, false));
|
||||||
YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_depth[i]);
|
it->location = label.toStdString();
|
||||||
|
YearStatisticsItem *iChild = new YearStatisticsItem(*it);
|
||||||
item->children.append(iChild);
|
item->children.append(iChild);
|
||||||
iChild->parent = item;
|
iChild->parent = item;
|
||||||
}
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
rootItem->children.append(item);
|
rootItem->children.append(item);
|
||||||
item->parent = rootItem.get();
|
item->parent = rootItem.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show the statistic sorted by dive temperature */
|
/* Show the statistic sorted by dive temperature */
|
||||||
if (stats.stats_by_temp != NULL && stats.stats_by_temp[0].selection_size) {
|
if (stats.stats_by_temp[0].selection_size) {
|
||||||
YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_temp[0]);
|
YearStatisticsItem *item = new YearStatisticsItem(stats.stats_by_temp[0]);
|
||||||
for (i = 1; stats.stats_by_temp[i].is_trip; ++i)
|
int i = 0;
|
||||||
if (stats.stats_by_temp[i].selection_size) {
|
for (auto it = std::next(stats.stats_by_temp.begin()); it != stats.stats_by_temp.end(); ++it) {
|
||||||
t_range_min.mkelvin = C_to_mkelvin((i - 1) * STATS_TEMP_BUCKET);
|
if (it->selection_size) {
|
||||||
t_range_max.mkelvin = C_to_mkelvin(i * STATS_TEMP_BUCKET);
|
t_range_min.mkelvin = C_to_mkelvin(i * STATS_TEMP_BUCKET);
|
||||||
|
t_range_max.mkelvin = C_to_mkelvin((i + 1) * STATS_TEMP_BUCKET);
|
||||||
label = QString(tr("%1 - %2")).arg(get_temperature_string(t_range_min, true),
|
label = QString(tr("%1 - %2")).arg(get_temperature_string(t_range_min, true),
|
||||||
get_temperature_string(t_range_max, true));
|
get_temperature_string(t_range_max, true));
|
||||||
stats.stats_by_temp[i].location = strdup(label.toUtf8().data());
|
it->location = label.toStdString();
|
||||||
YearStatisticsItem *iChild = new YearStatisticsItem(stats.stats_by_temp[i]);
|
YearStatisticsItem *iChild = new YearStatisticsItem(*it);
|
||||||
item->children.append(iChild);
|
item->children.append(iChild);
|
||||||
iChild->parent = item;
|
iChild->parent = item;
|
||||||
}
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
rootItem->children.append(item);
|
rootItem->children.append(item);
|
||||||
item->parent = rootItem.get();
|
item->parent = rootItem.get();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue