mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
Merge branch 'po2'
Plotting pO2 / pN2 / PHe
This commit is contained in:
commit
ed620a2e83
4 changed files with 486 additions and 32 deletions
7
color.h
7
color.h
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 *);
|
||||
|
||||
|
|
83
gtk-gui.c
83
gtk-gui.c
|
@ -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);
|
||||
|
|
415
profile.c
415
profile.c
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue