diff options
author | Dirk Hohndel <dirk@hohndel.org> | 2012-11-06 08:48:51 +0100 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2012-11-06 08:48:51 +0100 |
commit | ed620a2e835745d8ae6e7ecfeaf3a018dc4825ac (patch) | |
tree | 0b7575359164758db80427531564b07b6d5168b5 | |
parent | a9f44a133d5d74d2329c349535472ecc04aa4277 (diff) | |
parent | 0d33337a2036e494a6d1005c5c738eb54973d37b (diff) | |
download | subsurface-ed620a2e835745d8ae6e7ecfeaf3a018dc4825ac.tar.gz |
Merge branch 'po2'
Plotting pO2 / pN2 / PHe
-rw-r--r-- | color.h | 7 | ||||
-rw-r--r-- | display-gtk.h | 13 | ||||
-rw-r--r-- | gtk-gui.c | 83 | ||||
-rw-r--r-- | profile.c | 415 |
4 files changed, 486 insertions, 32 deletions
@@ -9,6 +9,7 @@ #define FUNGREEN1 { 0.0, 0.4, 0.2, 1 } #define KILLARNEY1 { 0.2, 0.4, 0.2, 1 } #define APPLE1 { 0.2, 0.6, 0.2, 1 } +#define APPLE1_MED_TRANS { 0.2, 0.6, 0.2, 0.5 } #define LIMENADE1 { 0.4, 0.8, 0.0, 1 } #define ATLANTIS1 { 0.4, 0.8, 0.2, 1 } #define ATLANTIS2 { 0.6, 0.8, 0.2, 1 } @@ -38,12 +39,14 @@ // Blues #define GOVERNORBAY2 { 0.2, 0.2, 0.7, 1 } #define GOVERNORBAY1_MED_TRANS { 0.2, 0.2, 0.8, 0.5 } +#define ROYALBLUE2 { 0.2, 0.2, 0.9, 1 } #define ROYALBLUE2_LOW_TRANS { 0.2, 0.2, 0.9, 0.75 } -// Yellows +// Yellows / BROWNS #define SPRINGWOOD1 { 0.95, 0.95, 0.9, 1 } #define BROOM1_LOWER_TRANS { 1.0, 1.0, 0.1, 0.9 } - +#define PEANUT { 0.5, 0.2, 0.1, 1.0 } +#define PEANUT_MED_TRANS { 0.5, 0.2, 0.1, 0.5 } // Magentas #define MEDIUMREDVIOLET1_HIGHER_TRANS { 0.7, 0.2, 0.7, 0.1 } diff --git a/display-gtk.h b/display-gtk.h index f4961b301..22ff9d626 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -21,6 +21,17 @@ typedef struct { gboolean otu; } visible_cols_t; +typedef struct { + gboolean po2; + gboolean pn2; + gboolean phe; +} enabled_graphs_t; + +extern visible_cols_t visible_cols; +extern enabled_graphs_t enabled_graphs; + +#define GRAPHS_ENABLED (enabled_graphs.po2 || enabled_graphs.pn2 || enabled_graphs.phe) + typedef enum { PREF_BOOL, PREF_STRING @@ -53,8 +64,6 @@ extern void quit(GtkWidget *w, gpointer data); extern int is_default_dive_computer_device(const char *name); -extern visible_cols_t visible_cols; - extern const char *divelist_font; extern void set_divelist_font(const char *); @@ -36,7 +36,8 @@ struct units output_units; static GtkWidget *dive_profile; -visible_cols_t visible_cols = {TRUE, FALSE}; +visible_cols_t visible_cols = {TRUE, FALSE, }; +enabled_graphs_t enabled_graphs = { FALSE, }; static const char *default_dive_computer_vendor; static const char *default_dive_computer_product; @@ -503,6 +504,9 @@ OPTIONCALLBACK(totalweight_toggle, visible_cols.totalweight) OPTIONCALLBACK(suit_toggle, visible_cols.suit) OPTIONCALLBACK(cylinder_toggle, visible_cols.cylinder) OPTIONCALLBACK(autogroup_toggle, autogroup) +OPTIONCALLBACK(po2_toggle, enabled_graphs.po2) +OPTIONCALLBACK(pn2_toggle, enabled_graphs.pn2) +OPTIONCALLBACK(phe_toggle, enabled_graphs.phe) static void event_toggle(GtkWidget *w, gpointer _data) { @@ -564,7 +568,7 @@ static void pick_default_file(GtkWidget *w, GtkButton *button) static void preferences_dialog(GtkWidget *w, gpointer data) { int result; - GtkWidget *dialog, *font, *frame, *box, *vbox, *button; + GtkWidget *dialog, *notebook, *font, *frame, *box, *vbox, *button, *xmlfile_button; const char *current_default, *new_default; menu_units = output_units; @@ -576,8 +580,15 @@ static void preferences_dialog(GtkWidget *w, gpointer data) GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); + /* create the notebook for the preferences and attach it to dialog */ + notebook = gtk_notebook_new(); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), notebook, FALSE, FALSE, 5); + + /* vbox that holds the first notebook page */ + vbox = gtk_vbox_new(FALSE, 6); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, + gtk_label_new(_("General Settings"))); frame = gtk_frame_new(_("Units")); - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); box = gtk_vbox_new(FALSE, 6); @@ -609,7 +620,7 @@ static void preferences_dialog(GtkWidget *w, gpointer data) NULL); frame = gtk_frame_new(_("Show Columns")); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); box = gtk_hbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(frame), box); @@ -634,11 +645,6 @@ static void preferences_dialog(GtkWidget *w, gpointer data) gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(sac_toggle), NULL); - button = gtk_check_button_new_with_label(_("OTU")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.otu); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(otu_toggle), NULL); - button = gtk_check_button_new_with_label(_("Weight")); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.totalweight); gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); @@ -650,13 +656,13 @@ static void preferences_dialog(GtkWidget *w, gpointer data) g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(suit_toggle), NULL); frame = gtk_frame_new(_("Divelist Font")); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); font = gtk_font_button_new_with_font(divelist_font); gtk_container_add(GTK_CONTAINER(frame),font); frame = gtk_frame_new(_("Misc. Options")); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); box = gtk_hbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(frame), box); @@ -671,9 +677,47 @@ static void preferences_dialog(GtkWidget *w, gpointer data) box = gtk_hbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(frame), box); current_default = subsurface_default_filename(); - button = gtk_button_new_with_label(current_default); - g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(pick_default_file), button); + xmlfile_button = gtk_button_new_with_label(current_default); + g_signal_connect(G_OBJECT(xmlfile_button), "clicked", + G_CALLBACK(pick_default_file), xmlfile_button); + gtk_box_pack_start(GTK_BOX(box), xmlfile_button, FALSE, FALSE, 6); + + /* vbox that holds the second notebook page */ + vbox = gtk_vbox_new(FALSE, 6); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, + gtk_label_new(_("Tec Settings"))); + + frame = gtk_frame_new(_("Show Columns")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(frame), box); + + button = gtk_check_button_new_with_label(_("OTU")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.otu); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(otu_toggle), NULL); + + frame = gtk_frame_new(_("Show Partial Pressure Graphs in Profile")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(frame), box); + + button = gtk_check_button_new_with_label(_("pO" UTF8_SUBSCRIPT_2)); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), enabled_graphs.po2); gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(po2_toggle), NULL); + + button = gtk_check_button_new_with_label(_("pN" UTF8_SUBSCRIPT_2)); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), enabled_graphs.pn2); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(pn2_toggle), NULL); + + button = gtk_check_button_new_with_label(_("pHe")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), enabled_graphs.phe); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(phe_toggle), NULL); gtk_widget_show_all(dialog); result = gtk_dialog_run(GTK_DIALOG(dialog)); @@ -696,6 +740,7 @@ static void preferences_dialog(GtkWidget *w, gpointer data) subsurface_set_conf("cuft", PREF_BOOL, BOOL_TO_PTR(output_units.volume == CUFT)); subsurface_set_conf("fahrenheit", PREF_BOOL, BOOL_TO_PTR(output_units.temperature == FAHRENHEIT)); subsurface_set_conf("lbs", PREF_BOOL, BOOL_TO_PTR(output_units.weight == LBS)); + subsurface_set_conf("TEMPERATURE", PREF_BOOL, BOOL_TO_PTR(visible_cols.temperature)); subsurface_set_conf("TOTALWEIGHT", PREF_BOOL, BOOL_TO_PTR(visible_cols.totalweight)); subsurface_set_conf("SUIT", PREF_BOOL, BOOL_TO_PTR(visible_cols.suit)); @@ -703,9 +748,15 @@ static void preferences_dialog(GtkWidget *w, gpointer data) subsurface_set_conf("NITROX", PREF_BOOL, BOOL_TO_PTR(visible_cols.nitrox)); subsurface_set_conf("SAC", PREF_BOOL, BOOL_TO_PTR(visible_cols.sac)); subsurface_set_conf("OTU", PREF_BOOL, BOOL_TO_PTR(visible_cols.otu)); + subsurface_set_conf("divelist_font", PREF_STRING, divelist_font); subsurface_set_conf("autogroup", PREF_BOOL, BOOL_TO_PTR(autogroup)); - new_default = strdup(gtk_button_get_label(GTK_BUTTON(button))); + + subsurface_set_conf("po2graph", PREF_BOOL, BOOL_TO_PTR(enabled_graphs.po2)); + subsurface_set_conf("pn2graph", PREF_BOOL, BOOL_TO_PTR(enabled_graphs.pn2)); + subsurface_set_conf("phegraph", PREF_BOOL, BOOL_TO_PTR(enabled_graphs.phe)); + + new_default = strdup(gtk_button_get_label(GTK_BUTTON(xmlfile_button))); /* if we opened the default file and are changing its name, * update existing_filename */ @@ -1052,6 +1103,10 @@ void init_ui(int *argcp, char ***argvp) visible_cols.otu = PTR_TO_BOOL(subsurface_get_conf("OTU", PREF_BOOL)); visible_cols.sac = PTR_TO_BOOL(subsurface_get_conf("SAC", PREF_BOOL)); + enabled_graphs.po2 = PTR_TO_BOOL(subsurface_get_conf("po2graph", PREF_BOOL)); + enabled_graphs.pn2 = PTR_TO_BOOL(subsurface_get_conf("pn2graph", PREF_BOOL)); + enabled_graphs.phe = PTR_TO_BOOL(subsurface_get_conf("phegraph", PREF_BOOL)); + divelist_font = subsurface_get_conf("divelist_font", PREF_STRING); autogroup = PTR_TO_BOOL(subsurface_get_conf("autogroup", PREF_BOOL)); default_filename = subsurface_get_conf("default_filename", PREF_STRING); @@ -8,9 +8,11 @@ #include <stdarg.h> #include <string.h> #include <time.h> +#include <math.h> #include "dive.h" #include "display.h" +#include "display-gtk.h" #include "divelist.h" #include "color.h" @@ -43,6 +45,7 @@ struct plot_info { /* Depth info */ int depth; int smoothed; + double po2, pn2, phe; velocity_t velocity; struct plot_data *min[3]; struct plot_data *max[3]; @@ -68,6 +71,9 @@ typedef enum { /* Velocity colors. Order is still important, ref VELOCITY_COLORS_START_IDX. */ VELO_STABLE, VELO_SLOW, VELO_MODERATE, VELO_FAST, VELO_CRAZY, + /* gas colors */ + PO2, PN2, PHE, + /* Other colors */ TEXT_BACKGROUND, ALERT_BG, ALERT_FG, EVENTS, SAMPLE_DEEP, SAMPLE_SHALLOW, SMOOTHED, MINUTE, TIME_GRID, TIME_TEXT, DEPTH_GRID, MEAN_DEPTH, DEPTH_TOP, @@ -99,6 +105,10 @@ static const color_t profile_color[] = { [VELO_FAST] = {{PIRATEGOLD1, BLACK1_LOW_TRANS}}, [VELO_CRAZY] = {{RED1, BLACK1_LOW_TRANS}}, + [PO2] = {{APPLE1, APPLE1_MED_TRANS}}, + [PN2] = {{BLACK1_LOW_TRANS, BLACK1_LOW_TRANS}}, + [PHE] = {{PEANUT, PEANUT_MED_TRANS}}, + [TEXT_BACKGROUND] = {{CONCRETE1_LOWER_TRANS, WHITE1}}, [ALERT_BG] = {{BROOM1_LOWER_TRANS, BLACK1_LOW_TRANS}}, [ALERT_FG] = {{BLACK1_LOW_TRANS, BLACK1_LOW_TRANS}}, @@ -175,11 +185,13 @@ static void dump_pi (struct plot_info *pi) pi->maxpressure, pi->mintemp, pi->maxtemp); for (i = 0; i < pi->nr; i++) printf(" entry[%d]:{same_cylinder:%d cylinderindex:%d sec:%d pressure:{%d,%d}\n" - " time:%d:%02d temperature:%d depth:%d smoothed:%d}\n", + " time:%d:%02d temperature:%d depth:%d smoothed:%d po2:%lf phe:%lf pn2:%lf sum-pp %lf}\n", i, pi->entry[i].same_cylinder, pi->entry[i].cylinderindex, pi->entry[i].sec, pi->entry[i].pressure[0], pi->entry[i].pressure[1], pi->entry[i].sec / 60, pi->entry[i].sec % 60, - pi->entry[i].temperature, pi->entry[i].depth, pi->entry[i].smoothed); + pi->entry[i].temperature, pi->entry[i].depth, pi->entry[i].smoothed, + pi->entry[i].po2, pi->entry[i].phe, pi->entry[i].pn2, + pi->entry[i].po2 + pi->entry[i].phe + pi->entry[i].pn2); printf(" }\n"); } @@ -211,16 +223,28 @@ static int get_maxtime(struct plot_info *pi) } } +/* get the maximum depth to which we want to plot + * take into account the additional verical space needed to plot + * partial pressure graphs */ static int get_maxdepth(struct plot_info *pi) { unsigned mm = pi->maxdepth; + int md; + if (zoomed_plot) { /* Rounded up to 10m, with at least 3m to spare */ - return ROUND_UP(mm+3000, 10000); + md = ROUND_UP(mm+3000, 10000); } else { /* Minimum 30m, rounded up to 10m, with at least 3m to spare */ - return MAX(30000, ROUND_UP(mm+3000, 10000)); + md = MAX(30000, ROUND_UP(mm+3000, 10000)); } + if (GRAPHS_ENABLED) { + if (md <= 20000) + md += 10000; + else + md += ROUND_UP(md / 2, 10000); + } + return md; } typedef struct { @@ -482,6 +506,329 @@ static void plot_depth_scale(struct graphics_context *gc, struct plot_info *pi) } } +/* ap points to an array of int with pi->nr + 1 elements that is + * ininitialized with just one -1 entry + * this adds entries (if they aren't too close to an existing one) + * and keeps things sorted + * we KNOW the array is big enough to hold all possible indices + * a2p is a secondary array - we insert value at the same relative + * positio as idx in ap */ +static void add_index(int idx, int margin, int **ap, int **a2p, int value) +{ + int j, i = 0; + int *a = *ap; + int *a2 = *a2p; + + while (a[i] != -1 && a[i] < idx) + i++; + if (a[i] == idx) + return; + if (a[i] != -1 && a[i - 1] != -1 && idx - a[i - 1] < margin) + return; + if (a[i] != -1 && a[i] - idx < margin) + return; + j = i; + while (a[j] != -1) + j++; + while (j >= i) { + a[j+1] = a[j]; + a2[j+1] = a2[j]; + j--; + } + a[i] = idx; + a2[i] = value; +} + +#define LI(_i,_j) MAX((_i)-(_j), 0) +#define RI(_i,_j) MIN((_i)+(_j), nr - 1) +#define SPIKE(_i,_s) if (fabs(_s) > fabs(spk_data[_i])) spk_data[_i] = (_s) +/* this is an attempt at a metric that finds spikes in a data series */ +static void calculate_spikyness(int nr, double *data, double *spk_data, int deltax, double deltay) +{ + int i, j; + double dminl, dminr, dmaxl, dmaxr; + +#if DEBUG_PROFILE > 2 + printf("Spike data: \n 0 "); +#endif + for (i = 0; i < nr; i++) { + dminl = dminr = dmaxl = dmaxr = data[i]; + spk_data[i] = 0.0; + for (j = 1; j < deltax; j++) { + if (data[LI(i,j)] < dminl) + dminl = data[LI(i,j)]; + if (data[LI(i,j)] > dmaxl) + dmaxl = data[LI(i,j)]; + + if (data[RI(i,j)] < dminr) + dminr = data[RI(i,j)]; + if (data[RI(i,j)] > dmaxr) + dmaxr = data[RI(i,j)]; + + /* don't do super narrow */ + if (j < deltax / 3) + continue; + /* falling edge on left */ + if (dmaxl == data[i] && dmaxr - data[i] < 0.1 * (data[i] - dminl)) + SPIKE(i,(data[i] - dminl) / j); + /* falling edge on right */ + if (dmaxr == data[i] && dmaxl - data[i] < 0.1 * (data[i] - dminr)) + SPIKE(i,(data[i] - dminr) / j); + + /* minima get a negative spike value */ + /* rising edge on left */ + if (dminl == data[i] && data[i] - dminr < 0.1 * (dmaxl - data[i])) + SPIKE(i,(data[i] - dmaxl) / j); + /* rising edge on right */ + if (dminr == data[i] && data[i] - dminl < 0.1 * (dmaxr - data[i])) + SPIKE(i,(data[i] - dmaxr) / j); + } +#if DEBUG_PROFILE > 2 + fprintf(debugfile, "%.4lf ", spk_data[i]); + if (i % 12 == 11) + fprintf(debugfile, "\n%2d ", (i + 1) / 12); +#endif + } +#if DEBUG_PROFILE > 2 + printf("\n"); +#endif +} + +/* only show one spike in a deltax wide region - pick the highest (and first if the same) */ +static gboolean higher_spike(double *spk_data, int idx, int nr, int deltax) +{ + int i; + double s = fabs(spk_data[idx]); + for (i = MAX(0, idx - deltax); i <= MIN(idx + deltax, nr - 1); i++) + if (fabs(spk_data[i]) > s) + return TRUE; + else if (fabs(spk_data[i]) == s && i < idx) + return TRUE; + return FALSE; +} + +/* this figures out which time stamps provide "interesting" formations in the graphs; + * this includes local minima and maxima as well as long plateaus. + * pass in the function that returns the value at a certain point (as double), + * the delta in time (expressed as number of data points of "significant time") + * the delta at which the value is considered to have been "significantly changed" and + * the number of points to cover + * returns a list of indices that ends with a -1 of times that are "interesting" */ +static void find_points_of_interest(struct plot_info *pi, double (*value_func)(int, struct plot_info *), + int deltax, double deltay, int **poip, int **poip_vpos) +{ + int i, j, nr = pi->nr; + double *data, *data_max, *data_min, *spk_data; + double min, max; + int *pois; + + /* avoid all the function calls by creating a local array and + * have some helper arrays to make our lifes easier */ + + data = malloc(nr * sizeof(double)); + data_max = malloc(nr * sizeof(double)); + data_min = malloc(nr * sizeof(double)); + spk_data = malloc(nr * sizeof(double)); + + pois = *poip = malloc((nr + 1) * sizeof(int)); + *poip_vpos = malloc((nr + 1) * sizeof(int)); + pois[0] = -1; + pois[1] = -1; + + /* copy the data and get the absolute minimum and maximum while we do it */ + for (i = 0; i < nr; i++) { + data_max[i] = data_min[i] = data[i] = value_func(i, pi); + if (i == 0 || data[i] < min) + min = data[i]; + if (i == 0 || data[i] > max) + max = data[i]; + } + /* next find out if there are real spikes in the graph */ + calculate_spikyness(nr, data, spk_data, deltax, deltay); + + /* now process all data points */ + for (i = 0; i < nr; i++) { + /* get the local min/max */ + for (j = MAX(0, i - deltax); j < i + deltax && j < nr; j++) { + if (data[j] < data[i]) + data_min[i] = data[j]; + if (data[j] > data[i]) + data_max[i] = data[j]; + } + /* is i the overall minimum or maximum */ + if (data[i] == max && (i == 0 || data[i - 1] != max)) + add_index(i, deltax, poip, poip_vpos, BOTTOM); + if (data[i] == min && (i == 0 || data[i - 1] != min)) + add_index(i, deltax, poip, poip_vpos, TOP); + /* is i a spike? */ + if (fabs(spk_data[i]) > 0.01 && ! higher_spike(spk_data, i, nr, deltax)) { + if (spk_data[i] > 0.0) + add_index(i, deltax, poip, poip_vpos, BOTTOM); + if (spk_data[i] < 0.0) + add_index(i, deltax, poip, poip_vpos, TOP); + } + /* is i a significant local minimum or maximum? */ + if (data[i] == data_min[i] && data_max[i] - data[i] > deltay) + add_index(i, deltax, poip, poip_vpos, TOP); + if (data[i] == data_max[i] && data[i] - data_min[i] > deltay) + add_index(i, deltax, poip, poip_vpos, BOTTOM); + } + /* still need to search for plateaus */ +} + +static void setup_pp_limits(struct graphics_context *gc, struct plot_info *pi) +{ + int maxdepth; + + gc->leftx = 0; + gc->rightx = get_maxtime(pi); + + /* the maxdepth already includes extra vertical space - and if + * we use 1.5 times the corresponding pressure as maximum partial + * pressure the graph seems to look fine*/ + maxdepth = get_maxdepth(pi); + gc->topy = 1.5 * (maxdepth + 10000) / 10000.0 * 1.01325; + gc->bottomy = 0.0; +} + +static void plot_single_pp_text(struct graphics_context *gc, int sec, double pp, + double vpos, color_indice_t color) +{ + text_render_options_t tro = {12, color, CENTER, vpos}; + plot_text(gc, &tro, sec, pp, "%.1lf", pp); +} + +#define MAXPP(_mpp, _pp) { _mpp = 0; \ + for(i = 0; i< pi->nr; i++) \ + if (pi->entry[i]._pp > _mpp) \ + _mpp = pi->entry[i]._pp; \ + } + +static double po2_value(int idx, struct plot_info *pi) +{ + return pi->entry[idx].po2; +} + +static double pn2_value(int idx, struct plot_info *pi) +{ + return pi->entry[idx].pn2; +} + +static double phe_value(int idx, struct plot_info *pi) +{ + return pi->entry[idx].phe; +} + +static double plot_single_gas_pp_text(struct graphics_context *gc, struct plot_info *pi, + double (*value_func)(int, struct plot_info *), + double value_threshold, int color) +{ + int *pois, *pois_vpos; + int i, two_minutes = 1; + double maxpp = 0.0; + + /* don't bother with local min/max if the dive is under two minutes */ + if (pi->entry[pi->nr - 1].sec > 120) { + int idx = 0; + while (pi->entry[idx].sec == 0) + idx++; + while (pi->entry[idx + two_minutes].sec < 120) + two_minutes++; + } else { + two_minutes = pi->nr; + } + find_points_of_interest(pi, value_func, two_minutes, value_threshold, &pois, &pois_vpos); + for (i = 0; pois[i] != -1; i++) { + struct plot_data *entry = pi->entry + pois[i]; + double value = value_func(pois[i], pi); + +#if DEBUG_PROFILE > 1 + fprintf(debugfile, "POI at %d sec value %lf\n", entry->sec, entry->po2); +#endif + plot_single_pp_text(gc, entry->sec, value, pois_vpos[i], color); + if (value > maxpp) + maxpp = value; + } + free(pois); + free(pois_vpos); + + return maxpp; +} + +static void plot_pp_text(struct graphics_context *gc, struct plot_info *pi) +{ + double pp, dpp, m, maxpp = 0.0; + int hpos; + static const text_render_options_t tro = {11, PN2, LEFT, MIDDLE}; + + setup_pp_limits(gc, pi); + + if (enabled_graphs.po2) { + maxpp = plot_single_gas_pp_text(gc, pi, po2_value, 0.4, PO2); + } + if (enabled_graphs.pn2) { + m = plot_single_gas_pp_text(gc, pi, pn2_value, 0.6, PN2); + if (m > maxpp) + maxpp = m; + } + if (enabled_graphs.phe) { + m = plot_single_gas_pp_text(gc, pi, phe_value, 0.4, PHE); + if (m > maxpp) + maxpp = m; + } + /* while this is somewhat useful, I don't like the way it looks... + * for now I'll leave the code here, but disable it */ + if (0) { + pp = floor(maxpp * 10.0) / 10.0 + 0.2; + dpp = floor(2.0 * pp) / 10.0; + hpos = pi->entry[pi->nr - 1].sec + 30; + for (m = 0.0; m <= pp; m += dpp) + plot_text(gc, &tro, hpos, m, "%.1f", m); + } +} + +static void plot_pp_gas_profile(struct graphics_context *gc, struct plot_info *pi) +{ + int i; + struct plot_data *entry; + + setup_pp_limits(gc, pi); + + if (enabled_graphs.po2) { + set_source_rgba(gc, PO2); + + entry = pi->entry; + move_to(gc, entry->sec, entry->po2); + for (i = 1; i < pi->nr; i++) { + entry++; + line_to(gc, entry->sec, entry->po2); + } + cairo_stroke(gc->cr); + } + if (enabled_graphs.pn2) { + set_source_rgba(gc, PN2); + + entry = pi->entry; + move_to(gc, entry->sec, entry->pn2); + for (i = 1; i < pi->nr; i++) { + entry++; + line_to(gc, entry->sec, entry->pn2); + } + cairo_stroke(gc->cr); + } + if (enabled_graphs.phe) { + set_source_rgba(gc, PHE); + + entry = pi->entry; + move_to(gc, entry->sec, entry->phe); + for (i = 1; i < pi->nr; i++) { + entry++; + line_to(gc, entry->sec, entry->phe); + } + cairo_stroke(gc->cr); + } +} + static void plot_depth_profile(struct graphics_context *gc, struct plot_info *pi) { int i, incr; @@ -546,16 +893,16 @@ static void plot_depth_profile(struct graphics_context *gc, struct plot_info *pi } cairo_stroke(cr); + gc->leftx = 0; gc->rightx = maxtime; + /* Show mean depth */ if (! gc->printer) { set_source_rgba(gc, MEAN_DEPTH); move_to(gc, 0, pi->meandepth); - line_to(gc, 1, pi->meandepth); + line_to(gc, pi->entry[pi->nr - 1].sec, pi->meandepth); cairo_stroke(cr); } - gc->leftx = 0; gc->rightx = maxtime; - /* * These are good for debugging text placement etc, * but not for actual display.. @@ -614,13 +961,15 @@ static int setup_temperature_limits(struct graphics_context *gc, struct plot_inf /* Show temperatures in roughly the lower third, but make sure the scale is at least somewhat reasonable */ delta = maxtemp - mintemp; - if (delta > 3000) { /* more than 3K in fluctuation */ + if (delta > 3000) /* more than 3K in fluctuation */ gc->topy = maxtemp + delta*2; - gc->bottomy = mintemp - delta/2; - } else { + else gc->topy = maxtemp + 1500 + delta*2; - gc->bottomy = mintemp - delta/2; - } + + if (GRAPHS_ENABLED) + gc->bottomy = mintemp - delta * 2; + else + gc->bottomy = mintemp - delta / 2; return maxtemp > mintemp; } @@ -708,7 +1057,11 @@ static int get_cylinder_pressure_range(struct graphics_context *gc, struct plot_ gc->leftx = 0; gc->rightx = get_maxtime(pi); - gc->bottomy = 0; gc->topy = pi->maxpressure * 1.5; + if (GRAPHS_ENABLED) + gc->bottomy = -pi->maxpressure * 0.75; + else + gc->bottomy = 0; + gc->topy = pi->maxpressure * 1.5; return pi->maxpressure != 0; } @@ -1256,7 +1609,8 @@ static struct plot_info *create_plot_info(struct dive *dive, int nr_samples, str lastindex = 0; lastdepth = -1; for (i = 0; i < nr_samples; i++) { - int depth; + int depth, fo2, fhe; + double pressure; int delay = 0; struct sample *sample = dive_sample+i; @@ -1297,6 +1651,16 @@ static struct plot_info *create_plot_info(struct dive *dive, int nr_samples, str depth = entry->depth = sample->depth.mm; entry->cylinderindex = sample->cylinderindex; SENSOR_PRESSURE(entry) = sample->cylinderpressure.mbar; + pressure = (depth + 10000) / 10000.0 * 1.01325; + fo2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille; + fhe = dive->cylinder[sample->cylinderindex].gasmix.he.permille; + + if (!fo2) + fo2 = AIR_PERMILLE; + entry->po2 = fo2 / 1000.0 * pressure; + entry->phe = fhe / 1000.0 * pressure; + entry->pn2 = (1000 - fo2 - fhe) / 1000.0 * pressure; + entry->temperature = sample->temperature.mkelvin; if (depth || lastdepth) @@ -1367,10 +1731,28 @@ static struct plot_info *create_plot_info(struct dive *dive, int nr_samples, str pi->entry[i].same_cylinder = 1; pi->entry[i].cylinderindex = pi->entry[i-1].cylinderindex; INTERPOLATED_PRESSURE(pi->entry + i) = GET_PRESSURE(pi->entry + i - 1); + pi->entry[i].po2 = pi->entry[i-1].po2 / (pi->entry[i].depth + 10000.0) * 10000.0; + pi->entry[i].phe = pi->entry[i-1].phe / (pi->entry[i].depth + 10000.0) * 10000.0; + pi->entry[i].pn2 = 1.01325 - pi->entry[i].po2 - pi->entry[i].phe; pi->entry[i+1].sec = sec + 40; pi->entry[i+1].same_cylinder = 1; pi->entry[i+1].cylinderindex = pi->entry[i-1].cylinderindex; INTERPOLATED_PRESSURE(pi->entry + i + 1) = GET_PRESSURE(pi->entry + i - 1); + pi->entry[i+1].po2 = pi->entry[i].po2; + pi->entry[i+1].phe = pi->entry[i].phe; + pi->entry[i+1].pn2 = pi->entry[i].pn2; + /* make sure the first two pi entries have a sane po2 / phe / pn2 */ + if (pi->entry[1].po2 < 0.01) + pi->entry[1].po2 = pi->entry[2].po2 / (pi->entry[2].depth + 10000.0) * 10000.0; + if (pi->entry[1].phe < 0.01) + pi->entry[1].phe = pi->entry[2].phe / (pi->entry[2].depth + 10000.0) * 10000.0; + pi->entry[1].pn2 = 1.01325 - pi->entry[1].po2 - pi->entry[1].phe; + if (pi->entry[0].po2 < 0.01) + pi->entry[0].po2 = pi->entry[1].po2 / (pi->entry[1].depth + 10000.0) * 10000.0; + if (pi->entry[0].phe < 0.01) + pi->entry[0].phe = pi->entry[1].phe / (pi->entry[1].depth + 10000.0) * 10000.0; + pi->entry[0].pn2 = 1.01325 - pi->entry[0].po2 - pi->entry[0].phe; + /* the number of actual entries - some computers have lots of * depth 0 samples at the end of a dive, we want to make sure * we have exactly one of them at the end */ @@ -1486,6 +1868,11 @@ void plot(struct graphics_context *gc, cairo_rectangle_t *drawing_area, struct d cairo_close_path(gc->cr); cairo_stroke(gc->cr); + if (GRAPHS_ENABLED) { + plot_pp_gas_profile(gc, pi); + plot_pp_text(gc, pi); + } + /* now shift the translation back by half the margin; * this way we can draw the vertical scales on both sides */ cairo_translate(gc->cr, -drawing_area->x / 2.0, 0); |