]> git.tdb.fi Git - ext/subsurface.git/blob - profile.c
Print starting and ending pressures
[ext/subsurface.git] / profile.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdarg.h>
4 #include <time.h>
5
6 #include "dive.h"
7 #include "display.h"
8 #include "divelist.h"
9
10 int selected_dive = 0;
11
12 #define ROUND_UP(x,y) ((((x)+(y)-1)/(y))*(y))
13
14 /*
15  * When showing dive profiles, we scale things to the
16  * current dive. However, we don't scale past less than
17  * 30 minutes or 90 ft, just so that small dives show
18  * up as such.
19  */
20 static int round_seconds_up(int seconds)
21 {
22         return MAX(30*60, ROUND_UP(seconds, 60*10));
23 }
24
25 static int round_feet_up(int feet)
26 {
27         return MAX(90, ROUND_UP(feet+5, 15));
28 }
29
30 typedef struct {
31     double r,g,b;
32     enum {CENTER,LEFT} allign;
33 } text_render_options_t;
34
35 static void plot_text(cairo_t *cr, text_render_options_t *tro,
36                       double x, double y, const char *fmt, ...)
37 {
38         cairo_text_extents_t extents;
39         char buffer[80];
40         va_list args;
41
42         va_start(args, fmt);
43         vsnprintf(buffer, sizeof(buffer), fmt, args);
44         va_end(args);
45
46         cairo_text_extents(cr, buffer, &extents);
47
48         if (tro->allign == CENTER)
49             x -= extents.width/2 + extents.x_bearing;
50         y += extents.height * 1.2;
51
52         cairo_move_to(cr, x, y);
53         cairo_text_path(cr, buffer);
54         cairo_set_source_rgb(cr, 0, 0, 0);
55         cairo_stroke(cr);
56
57         cairo_move_to(cr, x, y);
58         cairo_set_source_rgb(cr, tro->r, tro->g, tro->b);
59         cairo_show_text(cr, buffer);
60 }
61
62 /*
63  * Find the next maximum point in a 10-minute window.
64  *
65  * We exit early if we hit "enough" of a depth reversal,
66  * which is roughly 10 feet.
67  */
68 static int next_minmax(struct dive *dive, int index, int minmax)
69 {
70         const int enough = 3000;
71         int timelimit, depthlimit, result;
72         struct sample *sample = dive->sample + index;
73
74         if (index >= dive->samples)
75                 return 0;
76
77         timelimit = 24*60*60;
78         depthlimit = sample->depth.mm;
79         result = 0;
80
81         for (;;) {
82                 int time, depth;
83
84                 index++;
85                 sample++;
86                 if (index >= dive->samples)
87                         break;
88                 time = sample->time.seconds;
89                 depth = sample->depth.mm;
90                 if (time > timelimit)
91                         break;
92
93                 if (minmax) {
94                         if (depth <= depthlimit) {
95                                 if (depthlimit - depth > enough)
96                                         break;
97                                 continue;
98                         }
99                 } else {
100                         if (depth >= depthlimit) {
101                                 if (depth - depthlimit > enough)
102                                         break;
103                                 continue;
104                         }
105                 }
106
107                 result = index;
108                 depthlimit = depth;
109                 /* Look up to ten minutes into the future */
110                 timelimit = time + 600;
111         }
112         return result;
113 }
114
115 /* Scale to 0,0 -> maxx,maxy */
116 #define SCALE(x,y) (x)*maxx/scalex+topx,(y)*maxy/scaley+topy
117
118 static void plot_depth_text(struct dive *dive, cairo_t *cr,
119         double topx, double topy, double maxx, double maxy)
120 {
121         double scalex, scaley;
122         int maxtime, maxdepth;
123         int i;
124
125         /* Get plot scaling limits */
126         maxtime = round_seconds_up(dive->duration.seconds);
127         maxdepth = round_feet_up(to_feet(dive->maxdepth));
128
129         scalex = maxtime;
130         scaley = maxdepth;
131
132         cairo_set_font_size(cr, 14);
133         cairo_set_source_rgb(cr, 1, 0.2, 0.2);
134         i = 0;
135         while ((i = next_minmax(dive, i, 1)) != 0) {
136                 text_render_options_t tro = {1.0, 0.2, 0.2, CENTER};
137                 struct sample *sample = dive->sample+i;
138                 int sec = sample->time.seconds;
139                 int depth = to_feet(sample->depth);
140
141                 plot_text(cr, &tro, SCALE(sec, depth), "%d ft", depth);
142                 i = next_minmax(dive, i, 0);
143                 if (!i)
144                         break;
145         }
146 }
147
148 static void plot_depth_profile(struct dive *dive, cairo_t *cr,
149         double topx, double topy, double maxx, double maxy)
150 {
151         double scalex, scaley;
152         int begins, sec, depth;
153         int i, samples;
154         struct sample *sample;
155         int maxtime, maxdepth;
156
157         samples = dive->samples;
158         if (!samples)
159                 return;
160
161         cairo_set_line_width(cr, 2);
162
163         /* Get plot scaling limits */
164         maxtime = round_seconds_up(dive->duration.seconds);
165         maxdepth = round_feet_up(to_feet(dive->maxdepth));
166
167         /* Time markers: every 5 min */
168         scalex = maxtime;
169         scaley = 1.0;
170         for (i = 5*60; i < maxtime; i += 5*60) {
171                 cairo_move_to(cr, SCALE(i, 0));
172                 cairo_line_to(cr, SCALE(i, 1));
173         }
174
175         /* Depth markers: every 15 ft */
176         scalex = 1.0;
177         scaley = maxdepth;
178         cairo_set_source_rgba(cr, 1, 1, 1, 0.5);
179         for (i = 15; i < maxdepth; i += 15) {
180                 cairo_move_to(cr, SCALE(0, i));
181                 cairo_line_to(cr, SCALE(1, i));
182         }
183         cairo_stroke(cr);
184
185         /* Show mean depth */
186         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.40);
187         cairo_move_to(cr, SCALE(0, to_feet(dive->meandepth)));
188         cairo_line_to(cr, SCALE(1, to_feet(dive->meandepth)));
189         cairo_stroke(cr);
190
191         scalex = maxtime;
192
193         sample = dive->sample;
194         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.80);
195         begins = sample->time.seconds;
196         cairo_move_to(cr, SCALE(sample->time.seconds, to_feet(sample->depth)));
197         for (i = 1; i < dive->samples; i++) {
198                 sample++;
199                 sec = sample->time.seconds;
200                 depth = to_feet(sample->depth);
201                 cairo_line_to(cr, SCALE(sec, depth));
202         }
203         scaley = 1.0;
204         cairo_line_to(cr, SCALE(sec, 0));
205         cairo_line_to(cr, SCALE(begins, 0));
206         cairo_close_path(cr);
207         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.20);
208         cairo_fill_preserve(cr);
209         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.80);
210         cairo_stroke(cr);
211 }
212
213 /* gets both the actual start and end pressure as well as the scaling factors */
214 static int get_cylinder_pressure_range(struct dive *dive, double *scalex, double *scaley,
215                                        double *startp, double *endp)
216 {
217         int i;
218         double min, max;
219         double bar;
220
221         *scalex = round_seconds_up(dive->duration.seconds);
222
223         max = 0;
224         min = 5000;
225         if (startp)
226             *startp = *endp = 0.0;
227
228         for (i = 0; i < dive->samples; i++) {
229                 struct sample *sample = dive->sample + i;
230
231                 /* FIXME! We only track cylinder 0 right now */
232                 if (sample->cylinderindex)
233                         continue;
234                 if (!sample->cylinderpressure.mbar)
235                         continue;
236                 bar = sample->cylinderpressure.mbar;
237                 if (bar != 0.0 && startp && *startp == 0.0)
238                     *startp = bar;
239                 if (bar < min)
240                         min = bar;
241                 if (bar > max)
242                         max = bar;
243         }
244         if (endp)
245             *endp = bar;
246         if (!max)
247                 return 0;
248         *scaley = max * 1.5;
249         return 1;
250 }
251
252 static void plot_cylinder_pressure(struct dive *dive, cairo_t *cr,
253         double topx, double topy, double maxx, double maxy)
254 {
255         int i, sec = -1;
256         double scalex, scaley;
257
258         if (!get_cylinder_pressure_range(dive, &scalex, &scaley, NULL, NULL))
259                 return;
260
261         cairo_set_source_rgba(cr, 0.2, 1.0, 0.2, 0.80);
262
263         cairo_move_to(cr, SCALE(0, dive->cylinder[0].start.mbar));
264         for (i = 1; i < dive->samples; i++) {
265                 int mbar;
266                 struct sample *sample = dive->sample + i;
267
268                 mbar = sample->cylinderpressure.mbar;
269                 if (!mbar)
270                         continue;
271                 sec = sample->time.seconds;
272                 cairo_line_to(cr, SCALE(sec, mbar));
273         }
274         /*
275          * We may have "surface time" events, in which case we don't go
276          * back to dive duration
277          */
278         if (sec < dive->duration.seconds)
279                 cairo_line_to(cr, SCALE(dive->duration.seconds, dive->cylinder[0].end.mbar));
280         cairo_stroke(cr);
281 }
282
283
284 static void plot_cylinder_pressure_text(struct dive *dive, cairo_t *cr,
285         double topx, double topy, double maxx, double maxy)
286 {
287         double scalex, scaley;
288         double startp,endp;
289
290         cairo_set_font_size(cr, 10);
291
292         if (get_cylinder_pressure_range(dive, &scalex, &scaley,
293                                         &startp, &endp)) {
294                 text_render_options_t tro = {0.2, 1.0, 0.2, LEFT};
295                 plot_text(cr, &tro, SCALE(0, startp), "%3.0f bar", startp/1000.0);
296                 plot_text(cr, &tro, SCALE(dive->duration.seconds, endp),
297                           "%3.0f bar", endp/1000.0);
298         }
299 }
300
301 static void plot(cairo_t *cr, int w, int h, struct dive *dive)
302 {
303         double topx, topy, maxx, maxy;
304         double scalex, scaley;
305
306         topx = w / 20.0;
307         topy = h / 20.0;
308         maxx = (w - 2*topx);
309         maxy = (h - 2*topy);
310
311         /* Cylinder pressure plot */
312         plot_cylinder_pressure(dive, cr, topx, topy, maxx, maxy);
313
314         /* Depth profile */
315         plot_depth_profile(dive, cr, topx, topy, maxx, maxy);
316
317         /* Text on top of all graphs.. */
318         plot_depth_text(dive, cr, topx, topy, maxx, maxy);
319         plot_cylinder_pressure_text(dive, cr, topx, topy, maxx, maxy);
320
321         /* Bounding box last */
322         scalex = scaley = 1.0;
323         cairo_set_source_rgb(cr, 1, 1, 1);
324         cairo_move_to(cr, SCALE(0,0));
325         cairo_line_to(cr, SCALE(0,1));
326         cairo_line_to(cr, SCALE(1,1));
327         cairo_line_to(cr, SCALE(1,0));
328         cairo_close_path(cr);
329         cairo_stroke(cr);
330
331 }
332
333 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
334 {
335         struct dive *dive = current_dive;
336         cairo_t *cr;
337         int w,h;
338
339         w = widget->allocation.width;
340         h = widget->allocation.height;
341
342         cr = gdk_cairo_create(widget->window);
343         cairo_set_source_rgb(cr, 0, 0, 0);
344         cairo_paint(cr);
345
346         if (dive)
347                 plot(cr, w, h, dive);
348
349         cairo_destroy(cr);
350
351         return FALSE;
352 }
353
354 GtkWidget *dive_profile_widget(void)
355 {
356         GtkWidget *da;
357
358         da = gtk_drawing_area_new();
359         gtk_widget_set_size_request(da, 450, 350);
360         g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL);
361
362         return da;
363 }