]> git.tdb.fi Git - ext/subsurface.git/blob - profile.c
febea80250daf8234226b60a3dce9ff7ad4ddb20
[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 static void plot_text(cairo_t *cr, int xpos, int ypos, double x, double y, const char *fmt, ...)
31 {
32         cairo_text_extents_t extents;
33         char buffer[80];
34         va_list args;
35
36         va_start(args, fmt);
37         vsnprintf(buffer, sizeof(buffer), fmt, args);
38         va_end(args);
39
40         cairo_text_extents(cr, buffer, &extents);
41
42         switch (xpos) {
43         case -1:        /* Left-justify */
44                 break;
45         case 0:         /* Center */
46                 x -= extents.width/2 + extents.x_bearing;
47                 break;
48         case 1:         /* Right-justify */
49                 x -= extents.width + extents.x_bearing;
50                 break;
51         }
52         switch (ypos) {
53         case -1:        /* Top-justify */
54                 break;
55         case 0:         /* Center */
56                 y -= extents.height/2 + extents.y_bearing;
57                 break;
58         case 1:         /* Bottom-justify */
59                 y += extents.height * 1.2;
60                 break;
61         }
62
63         cairo_move_to(cr, x, y);
64         cairo_text_path(cr, buffer);
65         cairo_set_source_rgb(cr, 0, 0, 0);
66         cairo_stroke(cr);
67
68         cairo_move_to(cr, x, y);
69         cairo_set_source_rgb(cr, 1, 0, 0);
70         cairo_show_text(cr, buffer);
71 }
72
73 /*
74  * Find the next maximum point in a 10-minute window.
75  *
76  * We exit early if we hit "enough" of a depth reversal,
77  * which is roughly 10 feet.
78  */
79 static int next_minmax(struct dive *dive, int index, int minmax)
80 {
81         const int enough = 3000;
82         int timelimit, depthlimit, result;
83         struct sample *sample = dive->sample + index;
84
85         if (index >= dive->samples)
86                 return 0;
87
88         timelimit = 24*60*60;
89         depthlimit = sample->depth.mm;
90         result = 0;
91
92         for (;;) {
93                 int time, depth;
94
95                 index++;
96                 sample++;
97                 if (index >= dive->samples)
98                         break;
99                 time = sample->time.seconds;
100                 depth = sample->depth.mm;
101                 if (time > timelimit)
102                         break;
103
104                 if (minmax) {
105                         if (depth <= depthlimit) {
106                                 if (depthlimit - depth > enough)
107                                         break;
108                                 continue;
109                         }
110                 } else {
111                         if (depth >= depthlimit) {
112                                 if (depth - depthlimit > enough)
113                                         break;
114                                 continue;
115                         }
116                 }
117
118                 result = index;
119                 depthlimit = depth;
120                 /* Look up to ten minutes into the future */
121                 timelimit = time + 600;
122         }
123         return result;
124 }
125
126 /* Scale to 0,0 -> maxx,maxy */
127 #define SCALE(x,y) (x)*maxx/scalex+topx,(y)*maxy/scaley+topy
128
129 static void plot_depth_text(struct dive *dive, cairo_t *cr,
130         double topx, double topy, double maxx, double maxy)
131 {
132         double scalex, scaley;
133         int maxtime, maxdepth;
134         int i;
135
136         /* Get plot scaling limits */
137         maxtime = round_seconds_up(dive->duration.seconds);
138         maxdepth = round_feet_up(to_feet(dive->maxdepth));
139
140         scalex = maxtime;
141         scaley = maxdepth;
142
143         cairo_set_font_size(cr, 14);
144         cairo_set_source_rgb(cr, 1, 0.2, 0.2);
145         i = 0;
146         while ((i = next_minmax(dive, i, 1)) != 0) {
147                 struct sample *sample = dive->sample+i;
148                 int sec = sample->time.seconds;
149                 int depth = to_feet(sample->depth);
150
151                 plot_text(cr, 0, 1, SCALE(sec, depth), "%d ft", depth);
152                 i = next_minmax(dive, i, 0);
153                 if (!i)
154                         break;
155         }
156 }
157
158 static void plot_depth_profile(struct dive *dive, cairo_t *cr,
159         double topx, double topy, double maxx, double maxy)
160 {
161         double scalex, scaley;
162         int begins, sec, depth;
163         int i, samples;
164         struct sample *sample;
165         int maxtime, maxdepth;
166
167         samples = dive->samples;
168         if (!samples)
169                 return;
170
171         cairo_set_line_width(cr, 2);
172
173         /* Get plot scaling limits */
174         maxtime = round_seconds_up(dive->duration.seconds);
175         maxdepth = round_feet_up(to_feet(dive->maxdepth));
176
177         /* Time markers: every 5 min */
178         scalex = maxtime;
179         scaley = 1.0;
180         for (i = 5*60; i < maxtime; i += 5*60) {
181                 cairo_move_to(cr, SCALE(i, 0));
182                 cairo_line_to(cr, SCALE(i, 1));
183         }
184
185         /* Depth markers: every 15 ft */
186         scalex = 1.0;
187         scaley = maxdepth;
188         cairo_set_source_rgba(cr, 1, 1, 1, 0.5);
189         for (i = 15; i < maxdepth; i += 15) {
190                 cairo_move_to(cr, SCALE(0, i));
191                 cairo_line_to(cr, SCALE(1, i));
192         }
193         cairo_stroke(cr);
194
195         /* Show mean depth */
196         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.40);
197         cairo_move_to(cr, SCALE(0, to_feet(dive->meandepth)));
198         cairo_line_to(cr, SCALE(1, to_feet(dive->meandepth)));
199         cairo_stroke(cr);
200
201         scalex = maxtime;
202
203         sample = dive->sample;
204         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.80);
205         begins = sample->time.seconds;
206         cairo_move_to(cr, SCALE(sample->time.seconds, to_feet(sample->depth)));
207         for (i = 1; i < dive->samples; i++) {
208                 sample++;
209                 sec = sample->time.seconds;
210                 depth = to_feet(sample->depth);
211                 cairo_line_to(cr, SCALE(sec, depth));
212         }
213         scaley = 1.0;
214         cairo_line_to(cr, SCALE(sec, 0));
215         cairo_line_to(cr, SCALE(begins, 0));
216         cairo_close_path(cr);
217         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.20);
218         cairo_fill_preserve(cr);
219         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.80);
220         cairo_stroke(cr);
221 }
222
223 static int get_cylinder_pressure_range(struct dive *dive, double *scalex, double *scaley)
224 {
225         int i;
226         double min, max;
227
228         *scalex = round_seconds_up(dive->duration.seconds);
229
230         max = 0;
231         min = 5000;
232         for (i = 0; i < dive->samples; i++) {
233                 struct sample *sample = dive->sample + i;
234                 double bar;
235
236                 /* FIXME! We only track cylinder 0 right now */
237                 if (sample->cylinderindex)
238                         continue;
239                 if (!sample->cylinderpressure.mbar)
240                         continue;
241                 bar = sample->cylinderpressure.mbar;
242                 if (bar < min)
243                         min = bar;
244                 if (bar > max)
245                         max = bar;
246         }
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 topx, double topy, double maxx, double maxy)
255 {
256         int i, sec = -1;
257         double scalex, scaley;
258
259         if (!get_cylinder_pressure_range(dive, &scalex, &scaley))
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                 cairo_line_to(cr, SCALE(sec, mbar));
274         }
275         /*
276          * We may have "surface time" events, in which case we don't go
277          * back to dive duration
278          */
279         if (sec < dive->duration.seconds)
280                 cairo_line_to(cr, SCALE(dive->duration.seconds, dive->cylinder[0].end.mbar));
281         cairo_stroke(cr);
282 }
283
284 /*
285  * Return air usage (in liters).
286  */
287 static double calculate_airuse(struct dive *dive)
288 {
289         double airuse = 0;
290         int i;
291
292         for (i = 0; i < MAX_CYLINDERS; i++) {
293                 cylinder_t *cyl = dive->cylinder + i;
294                 int size = cyl->type.size.mliter;
295                 double kilo_atm;
296
297                 if (!size)
298                         continue;
299
300                 kilo_atm = (cyl->start.mbar - cyl->end.mbar) / 1013250.0;
301
302                 /* Liters of air at 1 atm == milliliters at 1k atm*/
303                 airuse += kilo_atm * size;
304         }
305         return airuse;
306 }
307
308 static void plot_info(struct dive *dive, cairo_t *cr,
309         double topx, double topy, double maxx, double maxy)
310 {
311         const double liters_per_cuft = 28.317;
312         double airuse;
313
314         airuse = calculate_airuse(dive);
315         if (!airuse)
316                 return;
317
318         /* I really need to start addign some unit setting thing */
319         airuse /= liters_per_cuft;
320         plot_text(cr, 1, 0, maxx*0.95, maxy*0.9, "cuft: %4.2f", airuse);
321         if (dive->duration.seconds) {
322                 double pressure = 1 + (dive->meandepth.mm / 10000.0);
323                 double sac = airuse / pressure * 60 / dive->duration.seconds;
324                 plot_text(cr, 1, 0, maxx*0.95, maxy*0.95, "SAC: %4.2f", sac);
325         }
326 }
327
328 static void plot(cairo_t *cr, int w, int h, struct dive *dive)
329 {
330         double topx, topy, maxx, maxy;
331         double scalex, scaley;
332
333         topx = w / 20.0;
334         topy = h / 20.0;
335         maxx = (w - 2*topx);
336         maxy = (h - 2*topy);
337
338         /* Cylinder pressure plot */
339         plot_cylinder_pressure(dive, cr, topx, topy, maxx, maxy);
340
341         /* Depth profile */
342         plot_depth_profile(dive, cr, topx, topy, maxx, maxy);
343
344         /* Text on top of all graphs.. */
345         plot_depth_text(dive, cr, topx, topy, maxx, maxy);
346
347         /* And info box in the lower right corner.. */
348         plot_info(dive, cr, topx, topy, maxx, maxy);
349
350         /* Bounding box last */
351         scalex = scaley = 1.0;
352         cairo_set_source_rgb(cr, 1, 1, 1);
353         cairo_move_to(cr, SCALE(0,0));
354         cairo_line_to(cr, SCALE(0,1));
355         cairo_line_to(cr, SCALE(1,1));
356         cairo_line_to(cr, SCALE(1,0));
357         cairo_close_path(cr);
358         cairo_stroke(cr);
359
360 }
361
362 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
363 {
364         struct dive *dive = current_dive;
365         cairo_t *cr;
366         int w,h;
367
368         w = widget->allocation.width;
369         h = widget->allocation.height;
370
371         cr = gdk_cairo_create(widget->window);
372         cairo_set_source_rgb(cr, 0, 0, 0);
373         cairo_paint(cr);
374
375         if (dive)
376                 plot(cr, w, h, dive);
377
378         cairo_destroy(cr);
379
380         return FALSE;
381 }
382
383 GtkWidget *dive_profile_widget(void)
384 {
385         GtkWidget *da;
386
387         da = gtk_drawing_area_new();
388         gtk_widget_set_size_request(da, 450, 350);
389         g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL);
390
391         return da;
392 }