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