]> git.tdb.fi Git - ext/subsurface.git/blob - profile.c
6ddc23a70a808d1cc4055238e197b7060eb20dcc
[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, mbar;
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                 struct sample *sample = dive->sample + i;
231
232                 /* FIXME! We only track cylinder 0 right now */
233                 if (sample->cylinderindex)
234                         continue;
235                 mbar = sample->cylinderpressure.mbar;
236                 if (!mbar)
237                         continue;
238                 if (mbar && startp && !startp->mbar)
239                         startp->mbar = mbar;
240                 if (mbar < min)
241                         min = mbar;
242                 if (mbar > max)
243                         max = mbar;
244         }
245         if (endp)
246                 endp->mbar = mbar;
247         if (!max)
248                 return 0;
249         *scaley = max * 1.5;
250         return 1;
251 }
252
253 static void plot_cylinder_pressure(struct dive *dive, cairo_t *cr,
254         double maxx, double maxy)
255 {
256         int i, sec = -1;
257         double scalex, scaley;
258
259         if (!get_cylinder_pressure_range(dive, &scalex, &scaley, NULL, NULL))
260                 return;
261
262         cairo_set_source_rgba(cr, 0.2, 1.0, 0.2, 0.80);
263
264         cairo_move_to(cr, SCALE(0, dive->cylinder[0].start.mbar));
265         for (i = 1; i < dive->samples; i++) {
266                 int mbar;
267                 struct sample *sample = dive->sample + i;
268
269                 mbar = sample->cylinderpressure.mbar;
270                 if (!mbar)
271                         continue;
272                 sec = sample->time.seconds;
273                 if (sec <= dive->duration.seconds)
274                         cairo_line_to(cr, SCALE(sec, mbar));
275         }
276         /*
277          * We may have "surface time" events, in which case we don't go
278          * back to dive duration
279          */
280         if (sec < dive->duration.seconds)
281                 cairo_line_to(cr, SCALE(dive->duration.seconds, dive->cylinder[0].end.mbar));
282         cairo_stroke(cr);
283 }
284
285 /*
286  * Return air usage (in liters).
287  */
288 static double calculate_airuse(struct dive *dive)
289 {
290         double airuse = 0;
291         int i;
292
293         for (i = 0; i < MAX_CYLINDERS; i++) {
294                 cylinder_t *cyl = dive->cylinder + i;
295                 int size = cyl->type.size.mliter;
296                 double kilo_atm;
297
298                 if (!size)
299                         continue;
300
301                 kilo_atm = (cyl->start.mbar - cyl->end.mbar) / 1013250.0;
302
303                 /* Liters of air at 1 atm == milliliters at 1k atm*/
304                 airuse += kilo_atm * size;
305         }
306         return airuse;
307 }
308
309 static void plot_info(struct dive *dive, cairo_t *cr,
310         double maxx, double maxy)
311 {
312         text_render_options_t tro = {0.2, 1.0, 0.2, LEFT};
313         const double liters_per_cuft = 28.317;
314         double airuse;
315
316         airuse = calculate_airuse(dive);
317         if (!airuse)
318                 return;
319
320         /* I really need to start addign some unit setting thing */
321         airuse /= liters_per_cuft;
322         plot_text(cr, &tro, maxx*0.8, maxy*0.8, "cuft: %4.2f", airuse);
323         if (dive->duration.seconds) {
324                 double pressure = 1 + (dive->meandepth.mm / 10000.0);
325                 double sac = airuse / pressure * 60 / dive->duration.seconds;
326                 plot_text(cr, &tro, maxx*0.8, maxy*0.85, "SAC: %4.2f", sac);
327         }
328 }
329
330 static void plot_cylinder_pressure_text(struct dive *dive, cairo_t *cr,
331         double maxx, double maxy)
332 {
333         double scalex, scaley;
334         pressure_t startp, endp;
335
336         cairo_set_font_size(cr, 10);
337
338         if (get_cylinder_pressure_range(dive, &scalex, &scaley,
339                                         &startp, &endp)) {
340                 text_render_options_t tro = {0.2, 1.0, 0.2, LEFT};
341                 plot_text(cr, &tro, SCALE(0, startp.mbar), "%3.0f bar", startp.mbar/1000.0);
342                 plot_text(cr, &tro, SCALE(dive->duration.seconds, endp.mbar),
343                           "%3.0f bar", endp.mbar/1000.0);
344         }
345 }
346
347 static void plot(cairo_t *cr, int w, int h, struct dive *dive)
348 {
349         double topx, topy, maxx, maxy;
350         double scalex, scaley;
351
352         topx = w / 20.0;
353         topy = h / 20.0;
354         maxx = (w - 2*topx);
355         maxy = (h - 2*topy);
356         cairo_translate(cr, topx, topy);
357
358         /* Cylinder pressure plot */
359         plot_cylinder_pressure(dive, cr, maxx, maxy);
360
361         /* Depth profile */
362         plot_depth_profile(dive, cr, maxx, maxy);
363
364         /* Text on top of all graphs.. */
365         plot_depth_text(dive, cr, maxx, maxy);
366         plot_cylinder_pressure_text(dive, cr, maxx, maxy);
367
368         /* And info box in the lower right corner.. */
369         plot_info(dive, cr, maxx, maxy);
370
371         /* Bounding box last */
372         scalex = scaley = 1.0;
373         cairo_set_source_rgb(cr, 1, 1, 1);
374         cairo_move_to(cr, SCALE(0,0));
375         cairo_line_to(cr, SCALE(0,1));
376         cairo_line_to(cr, SCALE(1,1));
377         cairo_line_to(cr, SCALE(1,0));
378         cairo_close_path(cr);
379         cairo_stroke(cr);
380
381 }
382
383 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
384 {
385         struct dive *dive = current_dive;
386         cairo_t *cr;
387         int w,h;
388
389         w = widget->allocation.width;
390         h = widget->allocation.height;
391
392         cr = gdk_cairo_create(widget->window);
393         cairo_set_source_rgb(cr, 0, 0, 0);
394         cairo_paint(cr);
395
396         if (dive)
397                 plot(cr, w, h, dive);
398
399         cairo_destroy(cr);
400
401         return FALSE;
402 }
403
404 GtkWidget *dive_profile_widget(void)
405 {
406         GtkWidget *da;
407
408         da = gtk_drawing_area_new();
409         gtk_widget_set_size_request(da, 450, 350);
410         g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL);
411
412         return da;
413 }