]> git.tdb.fi Git - ext/subsurface.git/blob - profile.c
Fix drawing artifacts with dives that have samples past the dive duration
[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                                        double *startp, double *endp)
218 {
219         int i;
220         double min, max;
221         double bar;
222
223         *scalex = round_seconds_up(dive->duration.seconds);
224
225         max = 0;
226         min = 5000;
227         if (startp)
228                 *startp = *endp = 0.0;
229
230         for (i = 0; i < dive->samples; i++) {
231                 struct sample *sample = dive->sample + i;
232
233                 /* FIXME! We only track cylinder 0 right now */
234                 if (sample->cylinderindex)
235                         continue;
236                 if (!sample->cylinderpressure.mbar)
237                         continue;
238                 bar = sample->cylinderpressure.mbar;
239                 if (bar != 0.0 && startp && *startp == 0.0)
240                         *startp = bar;
241                 if (bar < min)
242                         min = bar;
243                 if (bar > max)
244                         max = bar;
245         }
246         if (endp)
247                 *endp = bar;
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         double airuse;
316
317         airuse = calculate_airuse(dive);
318         if (!airuse)
319                 return;
320
321         /* I really need to start addign some unit setting thing */
322         airuse /= liters_per_cuft;
323         plot_text(cr, &tro, maxx*0.8, maxy*0.8, "cuft: %4.2f", airuse);
324         if (dive->duration.seconds) {
325                 double pressure = 1 + (dive->meandepth.mm / 10000.0);
326                 double sac = airuse / pressure * 60 / dive->duration.seconds;
327                 plot_text(cr, &tro, maxx*0.8, maxy*0.85, "SAC: %4.2f", sac);
328         }
329 }
330
331 static void plot_cylinder_pressure_text(struct dive *dive, cairo_t *cr,
332         double maxx, double maxy)
333 {
334         double scalex, scaley;
335         double startp,endp;
336
337         cairo_set_font_size(cr, 10);
338
339         if (get_cylinder_pressure_range(dive, &scalex, &scaley,
340                                         &startp, &endp)) {
341                 text_render_options_t tro = {0.2, 1.0, 0.2, LEFT};
342                 plot_text(cr, &tro, SCALE(0, startp), "%3.0f bar", startp/1000.0);
343                 plot_text(cr, &tro, SCALE(dive->duration.seconds, endp),
344                           "%3.0f bar", endp/1000.0);
345         }
346 }
347
348 static void plot(cairo_t *cr, int w, int h, struct dive *dive)
349 {
350         double topx, topy, maxx, maxy;
351         double scalex, scaley;
352
353         topx = w / 20.0;
354         topy = h / 20.0;
355         maxx = (w - 2*topx);
356         maxy = (h - 2*topy);
357         cairo_translate(cr, topx, topy);
358
359         /* Cylinder pressure plot */
360         plot_cylinder_pressure(dive, cr, maxx, maxy);
361
362         /* Depth profile */
363         plot_depth_profile(dive, cr, maxx, maxy);
364
365         /* Text on top of all graphs.. */
366         plot_depth_text(dive, cr, maxx, maxy);
367         plot_cylinder_pressure_text(dive, cr, maxx, maxy);
368
369         /* And info box in the lower right corner.. */
370         plot_info(dive, cr, maxx, maxy);
371
372         /* Bounding box last */
373         scalex = scaley = 1.0;
374         cairo_set_source_rgb(cr, 1, 1, 1);
375         cairo_move_to(cr, SCALE(0,0));
376         cairo_line_to(cr, SCALE(0,1));
377         cairo_line_to(cr, SCALE(1,1));
378         cairo_line_to(cr, SCALE(1,0));
379         cairo_close_path(cr);
380         cairo_stroke(cr);
381
382 }
383
384 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
385 {
386         struct dive *dive = current_dive;
387         cairo_t *cr;
388         int w,h;
389
390         w = widget->allocation.width;
391         h = widget->allocation.height;
392
393         cr = gdk_cairo_create(widget->window);
394         cairo_set_source_rgb(cr, 0, 0, 0);
395         cairo_paint(cr);
396
397         if (dive)
398                 plot(cr, w, h, dive);
399
400         cairo_destroy(cr);
401
402         return FALSE;
403 }
404
405 GtkWidget *dive_profile_widget(void)
406 {
407         GtkWidget *da;
408
409         da = gtk_drawing_area_new();
410         gtk_widget_set_size_request(da, 450, 350);
411         g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL);
412
413         return da;
414 }