]> git.tdb.fi Git - ext/subsurface.git/blob - profile.c
f10e856c29fb19e48cb1c757e31f290bf131fe17
[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, 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         x -= extents.width/2 + extents.x_bearing;
43         y += extents.height * 1.2;
44
45         cairo_move_to(cr, x, y);
46         cairo_text_path(cr, buffer);
47         cairo_set_source_rgb(cr, 0, 0, 0);
48         cairo_stroke(cr);
49
50         cairo_move_to(cr, x, y);
51         cairo_set_source_rgb(cr, 1, 0, 0);
52         cairo_show_text(cr, buffer);
53 }
54
55 /*
56  * Find the next maximum point in a 10-minute window.
57  *
58  * We exit early if we hit "enough" of a depth reversal,
59  * which is roughly 10 feet.
60  */
61 static int next_minmax(struct dive *dive, int index, int minmax)
62 {
63         const int enough = 3000;
64         int timelimit, depthlimit, result;
65         struct sample *sample = dive->sample + index;
66
67         if (index >= dive->samples)
68                 return 0;
69
70         timelimit = 24*60*60;
71         depthlimit = sample->depth.mm;
72         result = 0;
73
74         for (;;) {
75                 int time, depth;
76
77                 index++;
78                 sample++;
79                 if (index >= dive->samples)
80                         break;
81                 time = sample->time.seconds;
82                 depth = sample->depth.mm;
83                 if (time > timelimit)
84                         break;
85
86                 if (minmax) {
87                         if (depth <= depthlimit) {
88                                 if (depthlimit - depth > enough)
89                                         break;
90                                 continue;
91                         }
92                 } else {
93                         if (depth >= depthlimit) {
94                                 if (depth - depthlimit > enough)
95                                         break;
96                                 continue;
97                         }
98                 }
99
100                 result = index;
101                 depthlimit = depth;
102                 /* Look up to ten minutes into the future */
103                 timelimit = time + 600;
104         }
105         return result;
106 }
107
108 /* Scale to 0,0 -> maxx,maxy */
109 #define SCALE(x,y) (x)*maxx/scalex+topx,(y)*maxy/scaley+topy
110
111 static void plot_depth_text(struct dive *dive, cairo_t *cr,
112         double topx, double topy, double maxx, double maxy)
113 {
114         double scalex, scaley;
115         int maxtime, maxdepth;
116         int i;
117
118         /* Get plot scaling limits */
119         maxtime = round_seconds_up(dive->duration.seconds);
120         maxdepth = round_feet_up(to_feet(dive->maxdepth));
121
122         scalex = maxtime;
123         scaley = maxdepth;
124
125         cairo_set_font_size(cr, 14);
126         cairo_set_source_rgb(cr, 1, 0.2, 0.2);
127         i = 0;
128         while ((i = next_minmax(dive, i, 1)) != 0) {
129                 struct sample *sample = dive->sample+i;
130                 int sec = sample->time.seconds;
131                 int depth = to_feet(sample->depth);
132
133                 plot_text(cr, SCALE(sec, depth), "%d ft", depth);
134                 i = next_minmax(dive, i, 0);
135                 if (!i)
136                         break;
137         }
138 }
139
140 static void plot_depth_profile(struct dive *dive, cairo_t *cr,
141         double topx, double topy, double maxx, double maxy)
142 {
143         double scalex, scaley;
144         int begins, sec, depth;
145         int i, samples;
146         struct sample *sample;
147         int maxtime, maxdepth;
148
149         samples = dive->samples;
150         if (!samples)
151                 return;
152
153         cairo_set_line_width(cr, 2);
154
155         /* Get plot scaling limits */
156         maxtime = round_seconds_up(dive->duration.seconds);
157         maxdepth = round_feet_up(to_feet(dive->maxdepth));
158
159         /* Time markers: every 5 min */
160         scalex = maxtime;
161         scaley = 1.0;
162         for (i = 5*60; i < maxtime; i += 5*60) {
163                 cairo_move_to(cr, SCALE(i, 0));
164                 cairo_line_to(cr, SCALE(i, 1));
165         }
166
167         /* Depth markers: every 15 ft */
168         scalex = 1.0;
169         scaley = maxdepth;
170         cairo_set_source_rgba(cr, 1, 1, 1, 0.5);
171         for (i = 15; i < maxdepth; i += 15) {
172                 cairo_move_to(cr, SCALE(0, i));
173                 cairo_line_to(cr, SCALE(1, i));
174         }
175         cairo_stroke(cr);
176
177         /* Show mean depth */
178         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.40);
179         cairo_move_to(cr, SCALE(0, to_feet(dive->meandepth)));
180         cairo_line_to(cr, SCALE(1, to_feet(dive->meandepth)));
181         cairo_stroke(cr);
182
183         scalex = maxtime;
184
185         sample = dive->sample;
186         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.80);
187         begins = sample->time.seconds;
188         cairo_move_to(cr, SCALE(sample->time.seconds, to_feet(sample->depth)));
189         for (i = 1; i < dive->samples; i++) {
190                 sample++;
191                 sec = sample->time.seconds;
192                 depth = to_feet(sample->depth);
193                 cairo_line_to(cr, SCALE(sec, depth));
194         }
195         scaley = 1.0;
196         cairo_line_to(cr, SCALE(sec, 0));
197         cairo_line_to(cr, SCALE(begins, 0));
198         cairo_close_path(cr);
199         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.20);
200         cairo_fill_preserve(cr);
201         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.80);
202         cairo_stroke(cr);
203 }
204
205 static int get_cylinder_pressure_range(struct dive *dive, double *scalex, double *scaley)
206 {
207         int i;
208         double min, max;
209
210         *scalex = round_seconds_up(dive->duration.seconds);
211
212         max = 0;
213         min = 5000;
214         for (i = 0; i < dive->samples; i++) {
215                 struct sample *sample = dive->sample + i;
216                 double bar;
217
218                 /* FIXME! We only track cylinder 0 right now */
219                 if (sample->cylinderindex)
220                         continue;
221                 if (!sample->cylinderpressure.mbar)
222                         continue;
223                 bar = sample->cylinderpressure.mbar;
224                 if (bar < min)
225                         min = bar;
226                 if (bar > max)
227                         max = bar;
228         }
229         if (!max)
230                 return 0;
231         *scaley = max * 1.5;
232         return 1;
233 }
234
235 static void plot_cylinder_pressure(struct dive *dive, cairo_t *cr,
236         double topx, double topy, double maxx, double maxy)
237 {
238         int i, sec = -1;
239         double scalex, scaley;
240
241         if (!get_cylinder_pressure_range(dive, &scalex, &scaley))
242                 return;
243
244         cairo_set_source_rgba(cr, 0.2, 1.0, 0.2, 0.80);
245
246         cairo_move_to(cr, SCALE(0, dive->cylinder[0].start.mbar));
247         for (i = 1; i < dive->samples; i++) {
248                 int mbar;
249                 struct sample *sample = dive->sample + i;
250
251                 mbar = sample->cylinderpressure.mbar;
252                 if (!mbar)
253                         continue;
254                 sec = sample->time.seconds;
255                 cairo_line_to(cr, SCALE(sec, mbar));
256         }
257         /*
258          * We may have "surface time" events, in which case we don't go
259          * back to dive duration
260          */
261         if (sec < dive->duration.seconds)
262                 cairo_line_to(cr, SCALE(dive->duration.seconds, dive->cylinder[0].end.mbar));
263         cairo_stroke(cr);
264 }
265
266 static void plot(cairo_t *cr, int w, int h, struct dive *dive)
267 {
268         double topx, topy, maxx, maxy;
269         double scalex, scaley;
270
271         topx = w / 20.0;
272         topy = h / 20.0;
273         maxx = (w - 2*topx);
274         maxy = (h - 2*topy);
275
276         /* Cylinder pressure plot */
277         plot_cylinder_pressure(dive, cr, topx, topy, maxx, maxy);
278
279         /* Depth profile */
280         plot_depth_profile(dive, cr, topx, topy, maxx, maxy);
281
282         /* Text on top of all graphs.. */
283         plot_depth_text(dive, cr, topx, topy, maxx, maxy);
284
285         /* Bounding box last */
286         scalex = scaley = 1.0;
287         cairo_set_source_rgb(cr, 1, 1, 1);
288         cairo_move_to(cr, SCALE(0,0));
289         cairo_line_to(cr, SCALE(0,1));
290         cairo_line_to(cr, SCALE(1,1));
291         cairo_line_to(cr, SCALE(1,0));
292         cairo_close_path(cr);
293         cairo_stroke(cr);
294
295 }
296
297 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
298 {
299         struct dive *dive = current_dive;
300         cairo_t *cr;
301         int w,h;
302
303         w = widget->allocation.width;
304         h = widget->allocation.height;
305
306         cr = gdk_cairo_create(widget->window);
307         cairo_set_source_rgb(cr, 0, 0, 0);
308         cairo_paint(cr);
309
310         if (dive)
311                 plot(cr, w, h, dive);
312
313         cairo_destroy(cr);
314
315         return FALSE;
316 }
317
318 GtkWidget *dive_profile_widget(void)
319 {
320         GtkWidget *da;
321
322         da = gtk_drawing_area_new();
323         gtk_widget_set_size_request(da, 450, 350);
324         g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL);
325
326         return da;
327 }