10 int selected_dive = 0;
12 #define ROUND_UP(x,y) ((((x)+(y)-1)/(y))*(y))
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
20 static int round_seconds_up(int seconds)
22 return MAX(30*60, ROUND_UP(seconds, 60*10));
25 static int round_feet_up(int feet)
27 return MAX(90, ROUND_UP(feet+5, 15));
30 static void plot_text(cairo_t *cr, int xpos, int ypos, double x, double y, const char *fmt, ...)
32 cairo_text_extents_t extents;
37 vsnprintf(buffer, sizeof(buffer), fmt, args);
40 cairo_text_extents(cr, buffer, &extents);
43 case -1: /* Left-justify */
46 x -= extents.width/2 + extents.x_bearing;
48 case 1: /* Right-justify */
49 x -= extents.width + extents.x_bearing;
53 case -1: /* Top-justify */
56 y -= extents.height/2 + extents.y_bearing;
58 case 1: /* Bottom-justify */
59 y += extents.height * 1.2;
63 cairo_move_to(cr, x, y);
64 cairo_text_path(cr, buffer);
65 cairo_set_source_rgb(cr, 0, 0, 0);
68 cairo_move_to(cr, x, y);
69 cairo_set_source_rgb(cr, 1, 0, 0);
70 cairo_show_text(cr, buffer);
74 * Find the next maximum point in a 10-minute window.
76 * We exit early if we hit "enough" of a depth reversal,
77 * which is roughly 10 feet.
79 static int next_minmax(struct dive *dive, int index, int minmax)
81 const int enough = 3000;
82 int timelimit, depthlimit, result;
83 struct sample *sample = dive->sample + index;
85 if (index >= dive->samples)
89 depthlimit = sample->depth.mm;
97 if (index >= dive->samples)
99 time = sample->time.seconds;
100 depth = sample->depth.mm;
101 if (time > timelimit)
105 if (depth <= depthlimit) {
106 if (depthlimit - depth > enough)
111 if (depth >= depthlimit) {
112 if (depth - depthlimit > enough)
120 /* Look up to ten minutes into the future */
121 timelimit = time + 600;
126 /* Scale to 0,0 -> maxx,maxy */
127 #define SCALE(x,y) (x)*maxx/scalex+topx,(y)*maxy/scaley+topy
129 static void plot_depth_text(struct dive *dive, cairo_t *cr,
130 double topx, double topy, double maxx, double maxy)
132 double scalex, scaley;
133 int maxtime, maxdepth;
136 /* Get plot scaling limits */
137 maxtime = round_seconds_up(dive->duration.seconds);
138 maxdepth = round_feet_up(to_feet(dive->maxdepth));
143 cairo_set_font_size(cr, 14);
144 cairo_set_source_rgb(cr, 1, 0.2, 0.2);
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);
151 plot_text(cr, 0, 1, SCALE(sec, depth), "%d ft", depth);
152 i = next_minmax(dive, i, 0);
158 static void plot_depth_profile(struct dive *dive, cairo_t *cr,
159 double topx, double topy, double maxx, double maxy)
161 double scalex, scaley;
162 int begins, sec, depth;
164 struct sample *sample;
165 int maxtime, maxdepth;
167 samples = dive->samples;
171 cairo_set_line_width(cr, 2);
173 /* Get plot scaling limits */
174 maxtime = round_seconds_up(dive->duration.seconds);
175 maxdepth = round_feet_up(to_feet(dive->maxdepth));
177 /* Time markers: every 5 min */
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));
185 /* Depth markers: every 15 ft */
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));
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)));
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++) {
209 sec = sample->time.seconds;
210 depth = to_feet(sample->depth);
211 cairo_line_to(cr, SCALE(sec, depth));
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);
223 static int get_cylinder_pressure_range(struct dive *dive, double *scalex, double *scaley)
228 *scalex = round_seconds_up(dive->duration.seconds);
232 for (i = 0; i < dive->samples; i++) {
233 struct sample *sample = dive->sample + i;
236 /* FIXME! We only track cylinder 0 right now */
237 if (sample->cylinderindex)
239 if (!sample->cylinderpressure.mbar)
241 bar = sample->cylinderpressure.mbar;
253 static void plot_cylinder_pressure(struct dive *dive, cairo_t *cr,
254 double topx, double topy, double maxx, double maxy)
257 double scalex, scaley;
259 if (!get_cylinder_pressure_range(dive, &scalex, &scaley))
262 cairo_set_source_rgba(cr, 0.2, 1.0, 0.2, 0.80);
264 cairo_move_to(cr, SCALE(0, dive->cylinder[0].start.mbar));
265 for (i = 1; i < dive->samples; i++) {
267 struct sample *sample = dive->sample + i;
269 mbar = sample->cylinderpressure.mbar;
272 sec = sample->time.seconds;
273 cairo_line_to(cr, SCALE(sec, mbar));
276 * We may have "surface time" events, in which case we don't go
277 * back to dive duration
279 if (sec < dive->duration.seconds)
280 cairo_line_to(cr, SCALE(dive->duration.seconds, dive->cylinder[0].end.mbar));
285 * Return air usage (in liters).
287 static double calculate_airuse(struct dive *dive)
292 for (i = 0; i < MAX_CYLINDERS; i++) {
293 cylinder_t *cyl = dive->cylinder + i;
294 int size = cyl->type.size.mliter;
300 kilo_atm = (cyl->start.mbar - cyl->end.mbar) / 1013250.0;
302 /* Liters of air at 1 atm == milliliters at 1k atm*/
303 airuse += kilo_atm * size;
308 static void plot_info(struct dive *dive, cairo_t *cr,
309 double topx, double topy, double maxx, double maxy)
311 const double liters_per_cuft = 28.317;
314 airuse = calculate_airuse(dive);
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);
328 static void plot(cairo_t *cr, int w, int h, struct dive *dive)
330 double topx, topy, maxx, maxy;
331 double scalex, scaley;
338 /* Cylinder pressure plot */
339 plot_cylinder_pressure(dive, cr, topx, topy, maxx, maxy);
342 plot_depth_profile(dive, cr, topx, topy, maxx, maxy);
344 /* Text on top of all graphs.. */
345 plot_depth_text(dive, cr, topx, topy, maxx, maxy);
347 /* And info box in the lower right corner.. */
348 plot_info(dive, cr, topx, topy, maxx, maxy);
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);
362 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
364 struct dive *dive = current_dive;
368 w = widget->allocation.width;
369 h = widget->allocation.height;
371 cr = gdk_cairo_create(widget->window);
372 cairo_set_source_rgb(cr, 0, 0, 0);
376 plot(cr, w, h, dive);
383 GtkWidget *dive_profile_widget(void)
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);