]> git.tdb.fi Git - ext/subsurface.git/blob - profile.c
Add more explicit contributing explanation
[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_profile(struct dive *dive, cairo_t *cr,
112         double topx, double topy, double maxx, double maxy)
113 {
114         double scalex, scaley;
115         int begins, sec, depth;
116         int i, samples;
117         struct sample *sample;
118         int maxtime, maxdepth, mindepth;
119
120         samples = dive->samples;
121         if (!samples)
122                 return;
123
124         cairo_set_line_width(cr, 2);
125
126         /* Get plot scaling limits */
127         maxtime = round_seconds_up(dive->duration.seconds);
128         maxdepth = round_feet_up(to_feet(dive->maxdepth));
129
130         /* Time markers: every 5 min */
131         scalex = maxtime;
132         scaley = 1.0;
133         for (i = 5*60; i < maxtime; i += 5*60) {
134                 cairo_move_to(cr, SCALE(i, 0));
135                 cairo_line_to(cr, SCALE(i, 1));
136         }
137
138         /* Depth markers: every 15 ft */
139         scalex = 1.0;
140         scaley = maxdepth;
141         cairo_set_source_rgba(cr, 1, 1, 1, 0.5);
142         for (i = 15; i < maxdepth; i += 15) {
143                 cairo_move_to(cr, SCALE(0, i));
144                 cairo_line_to(cr, SCALE(1, i));
145         }
146         cairo_stroke(cr);
147
148         /* Show mean depth */
149         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.40);
150         cairo_move_to(cr, SCALE(0, to_feet(dive->meandepth)));
151         cairo_line_to(cr, SCALE(1, to_feet(dive->meandepth)));
152         cairo_stroke(cr);
153
154         scalex = maxtime;
155
156         sample = dive->sample;
157         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.80);
158         begins = sample->time.seconds;
159         cairo_move_to(cr, SCALE(sample->time.seconds, to_feet(sample->depth)));
160         for (i = 1; i < dive->samples; i++) {
161                 sample++;
162                 sec = sample->time.seconds;
163                 depth = to_feet(sample->depth);
164                 cairo_line_to(cr, SCALE(sec, depth));
165         }
166         scaley = 1.0;
167         cairo_line_to(cr, SCALE(sec, 0));
168         cairo_line_to(cr, SCALE(begins, 0));
169         cairo_close_path(cr);
170         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.20);
171         cairo_fill_preserve(cr);
172         cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.80);
173         cairo_stroke(cr);
174
175         scalex = maxtime;
176         scaley = maxdepth;
177
178         maxdepth = mindepth = 0;
179         maxtime = 0;
180         cairo_set_font_size(cr, 14);
181         cairo_set_source_rgb(cr, 1, 0.2, 0.2);
182         i = 0;
183         while ((i = next_minmax(dive, i, 1)) != 0) {
184                 sample = dive->sample+i;
185                 sec = sample->time.seconds;
186                 depth = to_feet(sample->depth);
187                 plot_text(cr, SCALE(sec, depth), "%d ft", depth);
188                 i = next_minmax(dive, i, 0);
189                 if (!i)
190                         break;
191         }
192 }
193
194 static int get_cylinder_pressure_range(struct dive *dive, double *scalex, double *scaley)
195 {
196         int i;
197         double min, max;
198
199         *scalex = round_seconds_up(dive->duration.seconds);
200
201         max = 0;
202         min = 5000;
203         for (i = 0; i < dive->samples; i++) {
204                 struct sample *sample = dive->sample + i;
205                 double bar;
206
207                 /* FIXME! We only track cylinder 0 right now */
208                 if (sample->cylinderindex)
209                         continue;
210                 if (!sample->cylinderpressure.mbar)
211                         continue;
212                 bar = sample->cylinderpressure.mbar;
213                 if (bar < min)
214                         min = bar;
215                 if (bar > max)
216                         max = bar;
217         }
218         if (!max)
219                 return 0;
220         *scaley = max * 1.5;
221         return 1;
222 }
223
224 static void plot_cylinder_pressure(struct dive *dive, cairo_t *cr,
225         double topx, double topy, double maxx, double maxy)
226 {
227         int i, sec = -1;
228         double scalex, scaley;
229
230         if (!get_cylinder_pressure_range(dive, &scalex, &scaley))
231                 return;
232
233         cairo_set_source_rgba(cr, 0.2, 1.0, 0.2, 0.80);
234
235         cairo_move_to(cr, SCALE(0, dive->cylinder[0].start.mbar));
236         for (i = 1; i < dive->samples; i++) {
237                 int mbar;
238                 struct sample *sample = dive->sample + i;
239
240                 mbar = sample->cylinderpressure.mbar;
241                 if (!mbar)
242                         continue;
243                 sec = sample->time.seconds;
244                 cairo_line_to(cr, SCALE(sec, mbar));
245         }
246         /*
247          * We may have "surface time" events, in which case we don't go
248          * back to dive duration
249          */
250         if (sec < dive->duration.seconds)
251                 cairo_line_to(cr, SCALE(dive->duration.seconds, dive->cylinder[0].end.mbar));
252         cairo_stroke(cr);
253 }
254
255 static void plot(cairo_t *cr, int w, int h, struct dive *dive)
256 {
257         double topx, topy, maxx, maxy;
258         double scalex, scaley;
259
260         topx = w / 20.0;
261         topy = h / 20.0;
262         maxx = (w - 2*topx);
263         maxy = (h - 2*topy);
264
265         /* Depth profile */
266         plot_profile(dive, cr, topx, topy, maxx, maxy);
267
268         /* Cylinder pressure plot? */
269         plot_cylinder_pressure(dive, cr, topx, topy, maxx, maxy);
270
271         /* Bounding box last */
272         scalex = scaley = 1.0;
273         cairo_set_source_rgb(cr, 1, 1, 1);
274         cairo_move_to(cr, SCALE(0,0));
275         cairo_line_to(cr, SCALE(0,1));
276         cairo_line_to(cr, SCALE(1,1));
277         cairo_line_to(cr, SCALE(1,0));
278         cairo_close_path(cr);
279         cairo_stroke(cr);
280
281 }
282
283 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
284 {
285         struct dive *dive = current_dive;
286         cairo_t *cr;
287         int w,h;
288
289         w = widget->allocation.width;
290         h = widget->allocation.height;
291
292         cr = gdk_cairo_create(widget->window);
293         cairo_set_source_rgb(cr, 0, 0, 0);
294         cairo_paint(cr);
295
296         if (dive)
297                 plot(cr, w, h, dive);
298
299         cairo_destroy(cr);
300
301         return FALSE;
302 }
303
304 GtkWidget *dive_profile_widget(void)
305 {
306         GtkWidget *da;
307
308         da = gtk_drawing_area_new();
309         gtk_widget_set_size_request(da, 450, 350);
310         g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL);
311
312         return da;
313 }