[PATCH 1/2] Display yearly/monthly statistics

Miika Turkia miika.turkia at gmail.com
Mon Sep 10 12:17:28 PDT 2012


Display yearly statistics on a statistics window with option to expand
the viewing on monthly level. The amount of dives along with basic
information like duration, depth, water temperature and air consumption
is displayed in yearly and monthly level. Thus you are able to compare
e.g. development of air consumption or diving activity from year to
year.

Signed-off-by: Miika Turkia <miika.turkia at gmail.com>
---
 dive.h       |    2 +
 gtk-gui.c    |    2 +
 statistics.c |  265 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 268 insertions(+), 1 deletion(-)

diff --git a/dive.h b/dive.h
index 42b5f77..6826132 100644
--- a/dive.h
+++ b/dive.h
@@ -394,6 +394,8 @@ extern void show_dive_equipment(struct dive *, int w_idx);
 
 extern void show_dive_stats(struct dive *);
 
+extern void show_yearly_stats(void);
+
 extern void update_dive(struct dive *new_dive);
 extern void save_dives(const char *filename);
 
diff --git a/gtk-gui.c b/gtk-gui.c
index 79933fb..8de794c 100644
--- a/gtk-gui.c
+++ b/gtk-gui.c
@@ -758,6 +758,7 @@ static GtkActionEntry menu_items[] = {
 	{ "AddDive",        GTK_STOCK_ADD, "Add Dive", NULL, NULL, G_CALLBACK(add_dive_cb) },
 	{ "Preferences",    GTK_STOCK_PREFERENCES, "Preferences", PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) },
 	{ "Renumber",       NULL, "Renumber", NULL, NULL, G_CALLBACK(renumber_dialog) },
+	{ "YearlyStats",    NULL, "Yearly Statistics", NULL, NULL, G_CALLBACK(show_yearly_stats) },
 	{ "SelectEvents",   NULL, "SelectEvents", NULL, NULL, G_CALLBACK(selectevents_dialog) },
 	{ "Quit",           GTK_STOCK_QUIT, NULL,   CTRLCHAR "Q", NULL, G_CALLBACK(quit) },
 	{ "About",          GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
@@ -794,6 +795,7 @@ static const gchar* ui_string = " \
 				<menuitem name=\"Renumber\" action=\"Renumber\" /> \
 				<menuitem name=\"Autogroup\" action=\"Autogroup\" /> \
 				<menuitem name=\"Toggle Zoom\" action=\"ToggleZoom\" /> \
+				<menuitem name=\"YearlyStats\" action=\"YearlyStats\" /> \
 				<menu name=\"View\" action=\"ViewMenuAction\"> \
 					<menuitem name=\"List\" action=\"ViewList\" /> \
 					<menuitem name=\"Profile\" action=\"ViewProfile\" /> \
diff --git a/statistics.c b/statistics.c
index a536173..86128ae 100644
--- a/statistics.c
+++ b/statistics.c
@@ -13,6 +13,8 @@
 #include <stdlib.h>
 #include <stdarg.h>
 #include <time.h>
+#include <stdbool.h>
+#include <gdk/gdkkeysyms.h>
 
 #include "dive.h"
 #include "display.h"
@@ -54,6 +56,7 @@ typedef struct {
 static total_stats_widget_t stats_w;
 
 typedef struct {
+	int period;
 	duration_t total_time;
 	/* avg_time is simply total_time / nr -- let's not keep this */
 	duration_t shortest_time;
@@ -74,7 +77,32 @@ typedef struct {
 
 static stats_t stats;
 static stats_t stats_selection;
-
+static stats_t *stats_monthly = NULL;
+static stats_t *stats_yearly = NULL;
+
+bool init_yearly = 1;
+GtkTreeIter yearly_iter;
+
+enum {
+	YEAR,
+	DIVES,
+	TOTAL_TIME,
+	AVERAGE_TIME,
+	SHORTEST_TIME,
+	LONGEST_TIME,
+	AVG_DEPTH,
+	MIN_DEPTH,
+	MAX_DEPTH,
+	AVG_SAC,
+	MIN_SAC,
+	MAX_SAC,
+	AVG_TEMP,
+	MIN_TEMP,
+	MAX_TEMP,
+	N_COLUMNS
+};
+
+static char * get_time_string(int seconds, int maxdays);
 
 static void process_dive(struct dive *dp, stats_t *stats)
 {
@@ -117,10 +145,200 @@ static void process_dive(struct dive *dp, stats_t *stats)
 	}
 }
 
+static void init_tree(GtkWidget *tree)
+{
+
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+	GtkTreeStore *store;
+	int i;
+
+	PangoFontDescription *font_desc = pango_font_description_from_string(divelist_font);
+	gtk_widget_modify_font(tree, font_desc);
+	pango_font_description_free(font_desc);
+
+	renderer = gtk_cell_renderer_text_new ();
+	char *columns[] = {
+		"Year\n > Month", "#", "Duration\nTotal", "\nAverage",
+		"\nShortest", "\nLongest", "Depth\nAverage", "\nMinimum",
+		"\nMaximum", "SAC\nAverage", "\nMinimum", "\nMaximum", "Temperature\nAverage",
+		"\nMinimum", "\nMaximum" };
+
+	/* Add all the columns to the tree view */
+	for (i = 0; i < N_COLUMNS; ++i) {
+		column = gtk_tree_view_column_new();
+		gtk_tree_view_column_set_title(column, columns[i]);
+		gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+		renderer = gtk_cell_renderer_text_new();
+		gtk_tree_view_column_pack_start(column, renderer, TRUE);
+		gtk_tree_view_column_add_attribute(column, renderer, "text", i);
+		gtk_tree_view_column_set_resizable(column, TRUE);
+	}
+
+	/* Field types */
+	store = gtk_tree_store_new (
+			N_COLUMNS,	// Columns in structure
+			G_TYPE_STRING,	// Period (year or month)
+			G_TYPE_STRING,	// Number of dives
+			G_TYPE_STRING,	// Total duration
+			G_TYPE_STRING,	// Average dive duation
+			G_TYPE_STRING,	// Shortest dive
+			G_TYPE_STRING,	// Longest dive
+			G_TYPE_STRING,	// Average depth
+			G_TYPE_STRING,	// Shallowest dive
+			G_TYPE_STRING,	// Deepest dive
+			G_TYPE_STRING,	// Average air consumption (SAC)
+			G_TYPE_STRING,	// Minimum SAC
+			G_TYPE_STRING,	// Maximum SAC
+			G_TYPE_STRING,	// Average temperature
+			G_TYPE_STRING,	// Minimum temperature
+			G_TYPE_STRING	// Maximum temperature
+			);
+
+	gtk_tree_view_set_model (GTK_TREE_VIEW (tree), GTK_TREE_MODEL (store));
+	g_object_unref (store);
+}
+
+static void add_cell_to_tree(GtkWidget *tree, char *value, int index, gboolean row, GtkTreeIter *parent)
+{
+	GtkTreeStore *store;
+
+	store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree)));
+
+	if (row)
+		gtk_tree_store_append(store, &yearly_iter, parent);
+	gtk_tree_store_set(store, &yearly_iter, index, value, -1);
+}
+
+static char * get_minutes(int seconds)
+{
+	static char buf[80];
+	int minutes = seconds / 60;
+	int sec = seconds % 60;
+	snprintf(buf, sizeof(buf), "%d:%.2d", minutes, sec);
+	return buf;
+}
+
+void process_interval_stats(GtkWidget *tree, stats_t stats_interval, GtkTreeIter *parent)
+{
+	double value;
+	int decimals;
+	const char *unit;
+	char value_str[40];
+
+	/* Year or month */
+	snprintf(value_str, sizeof(value_str), "%d", stats_interval.period);
+	add_cell_to_tree(tree, value_str, 0, 1, parent);
+	/* Dives */
+	snprintf(value_str, sizeof(value_str), "%d", stats_interval.selection_size);
+	add_cell_to_tree(tree, value_str, 1, 0, parent);
+	/* Total duration */
+	add_cell_to_tree(tree, get_time_string(stats_interval.total_time.seconds, 0), 2, 0, parent);
+	/* Average dive duration */
+	add_cell_to_tree(tree, get_minutes(stats_interval.total_time.seconds / stats_interval.selection_size), 3, 0, parent);
+	/* Shortest duration */
+	add_cell_to_tree(tree, get_minutes(stats_interval.shortest_time.seconds), 4, 0, parent);
+	/* Longest duration */
+	add_cell_to_tree(tree, get_minutes(stats_interval.longest_time.seconds), 5, 0, parent);
+	/* Average depth */
+	value = get_depth_units(stats_interval.avg_depth.mm, &decimals, &unit);
+	snprintf(value_str, sizeof(value_str), "%.*f %s", decimals, value, unit);
+	add_cell_to_tree(tree, value_str, 6, 0, parent);
+	/* Smallest maximum depth */
+	value = get_depth_units(stats_interval.min_depth.mm, &decimals, &unit);
+	snprintf(value_str, sizeof(value_str), "%.*f %s", decimals, value, unit);
+	add_cell_to_tree(tree, value_str, 7, 0, parent);
+	/* Deepest maximum depth */
+	value = get_depth_units(stats_interval.max_depth.mm, &decimals, &unit);
+	snprintf(value_str, sizeof(value_str), "%.*f %s", decimals, value, unit);
+	add_cell_to_tree(tree, value_str, 8, 0, parent);
+	/* Average air consumption */
+	value = get_volume_units(stats_interval.avg_sac.mliter, &decimals, &unit);
+	snprintf(value_str, sizeof(value_str), "%.*f %s/min\t", decimals, value, unit);
+	add_cell_to_tree(tree, value_str, 9, 0, parent);
+	/* Smallest average air consumption */
+	value = get_volume_units(stats_interval.min_sac.mliter, &decimals, &unit);
+	snprintf(value_str, sizeof(value_str), "%.*f %s/min\t", decimals, value, unit);
+	add_cell_to_tree(tree, value_str, 10, 0, parent);
+	/* Biggest air consumption */
+	value = get_volume_units(stats_interval.max_sac.mliter, &decimals, &unit);
+	snprintf(value_str, sizeof(value_str), "%.*f %s/min\t", decimals, value, unit);
+	add_cell_to_tree(tree, value_str, 11, 0, parent);
+	/* Average water temperature */
+	value = get_temp_units(stats_interval.min_temp, &unit);
+	if (stats_interval.combined_temp && stats_interval.combined_count) {
+		snprintf(value_str, sizeof(value_str), "%.1f %s", stats_interval.combined_temp / (stats_interval.combined_count * 1.0), unit);
+		add_cell_to_tree(tree, value_str, 12, 0, parent);
+	} else {
+		add_cell_to_tree(tree, "", 12, 0, parent);
+	}
+	/* Coldest water temperature */
+	snprintf(value_str, sizeof(value_str), "%.1f %s\t", value, unit);
+	add_cell_to_tree(tree, value_str, 13, 0, parent);
+	/* Warmest water temperature */
+	value = get_temp_units(stats_interval.max_temp, &unit);
+	snprintf(value_str, sizeof(value_str), "%.1f %s", value, unit);
+	add_cell_to_tree(tree, value_str, 14, 0, parent);
+}
+
+static void key_press_event(GtkWidget *window, GdkEventKey *event, gpointer data)
+{
+	if (event->string != NULL && event->keyval == GDK_Escape)
+		gtk_widget_destroy(window);
+}
+
+
+void show_yearly_stats()
+{
+	int i, j, combined_months, month_iter = 0;
+	GtkWidget *window;
+	GtkWidget *tree, *sw;
+	GtkTreeIter parent_iter;
+
+	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	sw = gtk_scrolled_window_new (NULL, NULL);
+	tree = gtk_tree_view_new ();
+
+	gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+	gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
+	gtk_window_set_title(GTK_WINDOW(window), "Yearly Statistics");
+	gtk_container_set_border_width(GTK_CONTAINER(window), 5);
+	GTK_WINDOW(window)->allow_shrink = TRUE;
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_ETCHED_IN);
+
+	gtk_container_add (GTK_CONTAINER (sw), tree);
+	gtk_container_add (GTK_CONTAINER (window), sw);
+
+	/* Display the yearly statistics on top level
+	 * Monthly statistics are available by expanding a year */
+	init_tree(tree);
+	for (i = 0; stats_yearly != NULL && stats_yearly[i].period; ++i) {
+		process_interval_stats(tree, stats_yearly[i], NULL);
+		parent_iter = yearly_iter;
+		combined_months = 0;
+
+		for (j = 0; combined_months < stats_yearly[i].selection_size; ++j) {
+			combined_months += stats_monthly[month_iter].selection_size;
+			process_interval_stats(tree, stats_monthly[month_iter++], &parent_iter);
+		}
+	}
+
+	g_signal_connect (G_OBJECT (window), "key_press_event", G_CALLBACK (key_press_event), NULL);
+	gtk_widget_show_all(window);
+}
+
 static void process_all_dives(struct dive *dive, struct dive **prev_dive)
 {
 	int idx;
 	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;
+	unsigned int size;
 
 	*prev_dive = NULL;
 	memset(&stats, 0, sizeof(stats));
@@ -139,7 +357,52 @@ static void process_all_dives(struct dive *dive, struct dive **prev_dive)
 				*prev_dive = dive_table.dives[idx-1];
 		}
 		process_dive(dp, &stats);
+		if (init_yearly) {
+			/* allocate sufficient space to hold the worst
+			 * case (one dive per year or all dives during
+			 * one month) */
+			if (stats_yearly == NULL) {
+				size = sizeof(stats_t) * (dive_table.nr + 1);
+				stats_yearly = malloc(size);
+				stats_monthly = malloc(size);
+				if (!stats_yearly || !stats_monthly)
+					return;
+				memset(stats_yearly, 0, size);
+				memset(stats_monthly, 0, size);
+			}
+
+			/* yearly statistics */
+			tm = gmtime(&dp->when);
+			if (current_year == 0)
+				current_year = tm->tm_year + 1900;
+
+			if (current_year != tm->tm_year + 1900) {
+				current_year = tm->tm_year + 1900;
+				process_dive(dp, &(stats_yearly[++year_iter]));
+			} else
+				process_dive(dp, &(stats_yearly[year_iter]));
+
+			stats_yearly[year_iter].selection_size++;
+			stats_yearly[year_iter].period = current_year;
+
+			/* 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, &(stats_monthly[month_iter]));
+			stats_monthly[month_iter].selection_size++;
+			stats_monthly[month_iter].period = current_month;
+			prev_month = current_month;
+			prev_year = current_year;
+		}
 	}
+	init_yearly = 0;
 }
 
 /* make sure we skip the selected summary entries */
-- 
1.7.9.5



More information about the subsurface mailing list