]> git.tdb.fi Git - ext/subsurface.git/blob - profile.c
5cd88885ab9a7b52a86f0ad76c1b34773ae6147d
[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,(y)*maxy/scaley
117
118 static void plot_depth_text(struct dive *dive, cairo_t *cr,
119         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 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                 if (sec <= maxtime) {
201                         depth = to_feet(sample->depth);
202                         cairo_line_to(cr, SCALE(sec, depth));
203                 }
204         }
205         scaley = 1.0;
206         cairo_line_to(cr, SCALE(MIN(sec,maxtime), 0));
207         cairo_line_to(cr, SCALE(begins, 0));
208         cairo_close_path(cr);
209         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.20);
210         cairo_fill_preserve(cr);
211         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.80);
212         cairo_stroke(cr);
213 }
214
215 /* gets both the actual start and end pressure as well as the scaling factors */
216 static int get_cylinder_pressure_range(struct dive *dive, double *scalex, double *scaley,
217                                        pressure_t *startp, pressure_t *endp)
218 {
219         int i;
220         int min, max;
221
222         *scalex = round_seconds_up(dive->duration.seconds);
223
224         max = 0;
225         min = 5000000;
226         if (startp)
227                 startp->mbar = endp->mbar = 0;
228
229         for (i = 0; i < dive->samples; i++) {
230                 int mbar;
231                 struct sample *sample = dive->sample + i;
232
233                 /* FIXME! We only track cylinder 0 right now */
234                 if (sample->cylinderindex)
235                         continue;
236                 mbar = sample->cylinderpressure.mbar;
237                 if (!mbar)
238                         continue;
239                 if (mbar < min)
240                         min = mbar;
241                 if (mbar > max)
242                         max = mbar;
243         }
244         if (startp)
245                 startp->mbar = max;
246         if (endp)
247                 endp->mbar = min;
248         if (!max)
249                 return 0;
250         *scaley = max * 1.5;
251         return 1;
252 }
253
254 static void plot_cylinder_pressure(struct dive *dive, cairo_t *cr,
255         double maxx, double maxy)
256 {
257         int i, sec = -1;
258         double scalex, scaley;
259
260         if (!get_cylinder_pressure_range(dive, &scalex, &scaley, NULL, NULL))
261                 return;
262
263         cairo_set_source_rgba(cr, 0.2, 1.0, 0.2, 0.80);
264
265         cairo_move_to(cr, SCALE(0, dive->cylinder[0].start.mbar));
266         for (i = 1; i < dive->samples; i++) {
267                 int mbar;
268                 struct sample *sample = dive->sample + i;
269
270                 mbar = sample->cylinderpressure.mbar;
271                 if (!mbar)
272                         continue;
273                 sec = sample->time.seconds;
274                 if (sec <= dive->duration.seconds)
275                         cairo_line_to(cr, SCALE(sec, mbar));
276         }
277         /*
278          * We may have "surface time" events, in which case we don't go
279          * back to dive duration
280          */
281         if (sec < dive->duration.seconds)
282                 cairo_line_to(cr, SCALE(dive->duration.seconds, dive->cylinder[0].end.mbar));
283         cairo_stroke(cr);
284 }
285
286 /*
287  * Return air usage (in liters).
288  */
289 static double calculate_airuse(struct dive *dive)
290 {
291         double airuse = 0;
292         int i;
293
294         for (i = 0; i < MAX_CYLINDERS; i++) {
295                 cylinder_t *cyl = dive->cylinder + i;
296                 int size = cyl->type.size.mliter;
297                 double kilo_atm;
298
299                 if (!size)
300                         continue;
301
302                 kilo_atm = (cyl->start.mbar - cyl->end.mbar) / 1013250.0;
303
304                 /* Liters of air at 1 atm == milliliters at 1k atm*/
305                 airuse += kilo_atm * size;
306         }
307         return airuse;
308 }
309
310 static void plot_info(struct dive *dive, cairo_t *cr,
311         double maxx, double maxy)
312 {
313         text_render_options_t tro = {0.2, 1.0, 0.2, LEFT};
314         const double liters_per_cuft = 28.317;
315         const char *unit;
316         double airuse;
317
318         airuse = calculate_airuse(dive);
319         if (!airuse)
320                 return;
321
322         /* I really need to start addign some unit setting thing */
323         switch (output_units.volume) {
324         case LITER:
325                 unit = "l";
326                 break;
327         case CUFT:
328                 unit = "cuft";
329                 airuse /= liters_per_cuft;
330                 break;
331         }
332         plot_text(cr, &tro, maxx*0.8, maxy*0.8, "vol: %4.2f %s", airuse, unit);
333         if (dive->duration.seconds) {
334                 double pressure = 1 + (dive->meandepth.mm / 10000.0);
335                 double sac = airuse / pressure * 60 / dive->duration.seconds;
336                 plot_text(cr, &tro, maxx*0.8, maxy*0.85, "SAC: %4.2f %s/min", sac, unit);
337         }
338 }
339
340 static void plot_cylinder_pressure_text(struct dive *dive, cairo_t *cr,
341         double maxx, double maxy)
342 {
343         double scalex, scaley;
344         pressure_t startp, endp;
345
346         cairo_set_font_size(cr, 10);
347
348         if (get_cylinder_pressure_range(dive, &scalex, &scaley,
349                                         &startp, &endp)) {
350                 int start, end;
351                 const char *unit = "bar";
352
353                 switch (output_units.pressure) {
354                 case PASCAL:
355                         start = startp.mbar * 100;
356                         end = startp.mbar * 100;
357                         unit = "pascal";
358                         break;
359                 case BAR:
360                         start = startp.mbar / 1000;
361                         end = endp.mbar / 1000;
362                         unit = "bar";
363                         break;
364                 case PSI:
365                         start = startp.mbar / 68.95;
366                         end = endp.mbar / 68.95;
367                         unit = "psi";
368                         break;
369                 }
370
371                 text_render_options_t tro = {0.2, 1.0, 0.2, LEFT};
372                 plot_text(cr, &tro, SCALE(0, startp.mbar), "%d %s", start, unit);
373                 plot_text(cr, &tro, SCALE(dive->duration.seconds, endp.mbar),
374                           "%d %s", end, unit);
375         }
376 }
377
378 static void plot(cairo_t *cr, int w, int h, struct dive *dive)
379 {
380         double topx, topy, maxx, maxy;
381         double scalex, scaley;
382
383         topx = w / 20.0;
384         topy = h / 20.0;
385         maxx = (w - 2*topx);
386         maxy = (h - 2*topy);
387         cairo_translate(cr, topx, topy);
388
389         /* Cylinder pressure plot */
390         plot_cylinder_pressure(dive, cr, maxx, maxy);
391
392         /* Depth profile */
393         plot_depth_profile(dive, cr, maxx, maxy);
394
395         /* Text on top of all graphs.. */
396         plot_depth_text(dive, cr, maxx, maxy);
397         plot_cylinder_pressure_text(dive, cr, maxx, maxy);
398
399         /* And info box in the lower right corner.. */
400         plot_info(dive, cr, maxx, maxy);
401
402         /* Bounding box last */
403         scalex = scaley = 1.0;
404         cairo_set_source_rgb(cr, 1, 1, 1);
405         cairo_move_to(cr, SCALE(0,0));
406         cairo_line_to(cr, SCALE(0,1));
407         cairo_line_to(cr, SCALE(1,1));
408         cairo_line_to(cr, SCALE(1,0));
409         cairo_close_path(cr);
410         cairo_stroke(cr);
411
412 }
413
414 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
415 {
416         struct dive *dive = current_dive;
417         cairo_t *cr;
418         int w,h;
419
420         w = widget->allocation.width;
421         h = widget->allocation.height;
422
423         cr = gdk_cairo_create(widget->window);
424         cairo_set_source_rgb(cr, 0, 0, 0);
425         cairo_paint(cr);
426
427         if (dive)
428                 plot(cr, w, h, dive);
429
430         cairo_destroy(cr);
431
432         return FALSE;
433 }
434
435 GtkWidget *dive_profile_widget(void)
436 {
437         GtkWidget *da;
438
439         da = gtk_drawing_area_new();
440         gtk_widget_set_size_request(da, 450, 350);
441         g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL);
442
443         return da;
444 }