]> git.tdb.fi Git - ext/subsurface.git/blob - profile.c
Start using 'plot_info' more for dive-time limits
[ext/subsurface.git] / profile.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdarg.h>
4 #include <string.h>
5 #include <time.h>
6
7 #include "dive.h"
8 #include "display.h"
9 #include "divelist.h"
10
11 int selected_dive = 0;
12
13 /* Plot info with smoothing and one-, two- and three-minute minimums and maximums */
14 struct plot_info {
15         int nr;
16         int maxtime;
17         struct plot_data {
18                 int sec;
19                 int val;
20                 int smoothed;
21                 struct plot_data *min[3];
22                 struct plot_data *max[3];
23                 int avg[3];
24         } entry[];
25 };
26 #define plot_info_size(nr) (sizeof(struct plot_info) + (nr)*sizeof(struct plot_data))
27
28 /* Scale to 0,0 -> maxx,maxy */
29 #define SCALEX(gc,x)  (((x)-gc->leftx)/(gc->rightx-gc->leftx)*gc->maxx)
30 #define SCALEY(gc,y)  (((y)-gc->topy)/(gc->bottomy-gc->topy)*gc->maxy)
31 #define SCALE(gc,x,y) SCALEX(gc,x),SCALEY(gc,y)
32
33 static void move_to(struct graphics_context *gc, double x, double y)
34 {
35         cairo_move_to(gc->cr, SCALE(gc, x, y));
36 }
37
38 static void line_to(struct graphics_context *gc, double x, double y)
39 {
40         cairo_line_to(gc->cr, SCALE(gc, x, y));
41 }
42
43 static void set_source_rgba(struct graphics_context *gc, double r, double g, double b, double a)
44 {
45         if (gc->printer) {
46                 /* Black is white and white is black */
47                 double sum = r+g+b;
48                 if (sum > 2)
49                         r = g = b = 0;
50                 else if (sum < 1)
51                         r = g = b = 1;
52         }
53         cairo_set_source_rgba(gc->cr, r, g, b, a);
54 }
55
56 static void set_source_rgb(struct graphics_context *gc, double r, double g, double b)
57 {
58         set_source_rgba(gc, r, g, b, 1);
59 }
60
61 #define ROUND_UP(x,y) ((((x)+(y)-1)/(y))*(y))
62
63 /*
64  * When showing dive profiles, we scale things to the
65  * current dive. However, we don't scale past less than
66  * 30 minutes or 90 ft, just so that small dives show
67  * up as such.
68  */
69 static int get_maxtime(struct plot_info *pi)
70 {
71         int seconds = pi->maxtime;
72         return MAX(30*60, ROUND_UP(seconds, 60*10));
73 }
74
75 static int round_depth_up(depth_t depth)
76 {
77         unsigned mm = depth.mm;
78         /* Minimum 30m */
79         return MAX(30000, ROUND_UP(mm+3000, 10000));
80 }
81
82 typedef struct {
83         int size;
84         double r,g,b;
85         enum {CENTER,LEFT} halign;
86         enum {MIDDLE,TOP,BOTTOM} valign;
87 } text_render_options_t;
88
89 static void plot_text(struct graphics_context *gc, const text_render_options_t *tro,
90                       double x, double y, const char *fmt, ...)
91 {
92         cairo_t *cr = gc->cr;
93         cairo_text_extents_t extents;
94         double dx, dy;
95         char buffer[80];
96         va_list args;
97
98         va_start(args, fmt);
99         vsnprintf(buffer, sizeof(buffer), fmt, args);
100         va_end(args);
101
102         cairo_set_font_size(cr, tro->size);
103         cairo_text_extents(cr, buffer, &extents);
104         dx = 0;
105         switch (tro->halign) {
106         case CENTER:
107                 dx = -(extents.width/2 + extents.x_bearing);
108                 break;
109         case LEFT:
110                 dx = 0;
111                 break;
112         }
113         switch (tro->valign) {
114         case TOP:
115                 dy = extents.height * 1.2;
116                 break;
117         case BOTTOM:
118                 dy = -extents.height * 0.8;
119                 break;
120         case MIDDLE:
121                 dy = 0;
122                 break;
123         }
124
125         move_to(gc, x, y);
126         cairo_rel_move_to(cr, dx, dy);
127
128         cairo_text_path(cr, buffer);
129         set_source_rgb(gc, 0, 0, 0);
130         cairo_stroke(cr);
131
132         move_to(gc, x, y);
133         cairo_rel_move_to(cr, dx, dy);
134
135         set_source_rgb(gc, tro->r, tro->g, tro->b);
136         cairo_show_text(cr, buffer);
137 }
138
139 static void render_depth_sample(struct graphics_context *gc, struct plot_data *entry, const text_render_options_t *tro)
140 {
141         int sec = entry->sec;
142         depth_t depth = { entry->val };
143         const char *fmt;
144         double d;
145
146         switch (output_units.length) {
147         case METERS:
148                 d = depth.mm / 1000.0;
149                 fmt = "%.1f";
150                 break;
151         case FEET:
152                 d = to_feet(depth);
153                 fmt = "%.0f";
154                 break;
155         }
156         plot_text(gc, tro, sec, depth.mm, fmt, d);
157 }
158
159 static void plot_text_samples(struct graphics_context *gc, struct plot_info *pi)
160 {
161         static const text_render_options_t deep = {14, 1.0, 0.2, 0.2, CENTER, TOP};
162         static const text_render_options_t shallow = {14, 1.0, 0.2, 0.2, CENTER, BOTTOM};
163         int i;
164
165         for (i = 0; i < pi->nr; i++) {
166                 struct plot_data *entry = pi->entry + i;
167
168                 if (entry->val < 2000)
169                         continue;
170
171                 if (entry == entry->max[2])
172                         render_depth_sample(gc, entry, &deep);
173
174                 if (entry == entry->min[2])
175                         render_depth_sample(gc, entry, &shallow);
176         }
177 }
178
179 static void plot_depth_text(struct dive *dive, struct graphics_context *gc, struct plot_info *pi)
180 {
181         int maxtime, maxdepth;
182
183         /* Get plot scaling limits */
184         maxtime = get_maxtime(pi);
185         maxdepth = round_depth_up(dive->maxdepth);
186
187         gc->leftx = 0; gc->rightx = maxtime;
188         gc->topy = 0; gc->bottomy = maxdepth;
189
190         plot_text_samples(gc, pi);
191 }
192
193 static void plot_smoothed_profile(struct graphics_context *gc, struct plot_info *pi)
194 {
195         int i;
196         struct plot_data *entry = pi->entry;
197
198         cairo_set_source_rgba(gc->cr, 1, 0.2, 0.2, 0.20);
199         move_to(gc, entry->sec, entry->smoothed);
200         for (i = 1; i < pi->nr; i++) {
201                 entry++;
202                 line_to(gc, entry->sec, entry->smoothed);
203         }
204         cairo_stroke(gc->cr);
205 }
206
207 static void plot_minmax_profile_minute(struct graphics_context *gc, struct plot_info *pi,
208                                 int index, double a)
209 {
210         int i;
211         struct plot_data *entry = pi->entry;
212
213         cairo_set_source_rgba(gc->cr, 1, 0.2, 1, a);
214         move_to(gc, entry->sec, entry->min[index]->val);
215         for (i = 1; i < pi->nr; i++) {
216                 entry++;
217                 line_to(gc, entry->sec, entry->min[index]->val);
218         }
219         for (i = 1; i < pi->nr; i++) {
220                 line_to(gc, entry->sec, entry->max[index]->val);
221                 entry--;
222         }
223         cairo_close_path(gc->cr);
224         cairo_fill(gc->cr);
225 }
226
227 static void plot_minmax_profile(struct graphics_context *gc, struct plot_info *pi)
228 {
229         if (gc->printer)
230                 return;
231         plot_minmax_profile_minute(gc, pi, 2, 0.1);
232         plot_minmax_profile_minute(gc, pi, 1, 0.1);
233         plot_minmax_profile_minute(gc, pi, 0, 0.1);
234 }
235
236 static void plot_depth_profile(struct dive *dive, struct graphics_context *gc, struct plot_info *pi)
237 {
238         int i;
239         cairo_t *cr = gc->cr;
240         int begins, sec, depth;
241         struct plot_data *entry;
242         int maxtime, maxdepth, marker;
243
244         /* Get plot scaling limits */
245         maxtime = get_maxtime(pi);
246         maxdepth = round_depth_up(dive->maxdepth);
247
248         /* Time markers: every 5 min */
249         gc->leftx = 0; gc->rightx = maxtime;
250         gc->topy = 0; gc->bottomy = 1.0;
251         for (i = 5*60; i < maxtime; i += 5*60) {
252                 move_to(gc, i, 0);
253                 line_to(gc, i, 1);
254         }
255
256         /* Depth markers: every 30 ft or 10 m*/
257         gc->leftx = 0; gc->rightx = 1.0;
258         gc->topy = 0; gc->bottomy = maxdepth;
259         switch (output_units.length) {
260         case METERS: marker = 10000; break;
261         case FEET: marker = 9144; break;        /* 30 ft */
262         }
263
264         set_source_rgba(gc, 1, 1, 1, 0.5);
265         for (i = marker; i < maxdepth; i += marker) {
266                 move_to(gc, 0, i);
267                 line_to(gc, 1, i);
268         }
269         cairo_stroke(cr);
270
271         /* Show mean depth */
272         set_source_rgba(gc, 1, 0.2, 0.2, 0.40);
273         move_to(gc, 0, dive->meandepth.mm);
274         line_to(gc, 1, dive->meandepth.mm);
275         cairo_stroke(cr);
276
277         gc->leftx = 0; gc->rightx = maxtime;
278
279         plot_smoothed_profile(gc, pi);
280         plot_minmax_profile(gc, pi);
281
282         entry = pi->entry;
283         set_source_rgba(gc, 1, 0.2, 0.2, 0.80);
284         begins = entry->sec;
285         move_to(gc, entry->sec, entry->val);
286         for (i = 1; i < pi->nr; i++) {
287                 entry++;
288                 sec = entry->sec;
289                 if (sec <= maxtime) {
290                         depth = entry->val;
291                         line_to(gc, sec, depth);
292                 }
293         }
294         gc->topy = 0; gc->bottomy = 1.0;
295         line_to(gc, MIN(sec,maxtime), 0);
296         line_to(gc, begins, 0);
297         cairo_close_path(cr);
298         set_source_rgba(gc, 1, 0.2, 0.2, 0.20);
299         cairo_fill_preserve(cr);
300         set_source_rgba(gc, 1, 0.2, 0.2, 0.80);
301         cairo_stroke(cr);
302 }
303
304 static int setup_temperature_limits(struct dive *dive, struct graphics_context *gc, struct plot_info *pi)
305 {
306         int i;
307         int maxtime, mintemp, maxtemp;
308
309         /* Get plot scaling limits */
310         maxtime = get_maxtime(pi);
311         mintemp = INT_MAX;
312         maxtemp = 0;
313         for (i = 0; i < dive->samples; i++) {
314                 struct sample *sample = dive->sample+i;
315                 int mkelvin = sample->temperature.mkelvin;
316                 if (!mkelvin)
317                         continue;
318                 if (mkelvin > maxtemp)
319                         maxtemp = mkelvin;
320                 if (mkelvin < mintemp)
321                         mintemp = mkelvin;
322         }
323
324         gc->leftx = 0; gc->rightx = maxtime;
325         /* Show temperatures in roughly the lower third */
326         gc->topy = maxtemp + (maxtemp - mintemp)*2;
327         gc->bottomy = mintemp - (maxtemp - mintemp)/2;
328
329         return maxtemp > mintemp;
330 }
331
332 static void plot_single_temp_text(struct graphics_context *gc, int sec, temperature_t temperature)
333 {
334         int deg;
335         const char *unit;
336         static const text_render_options_t tro = {12, 0.2, 0.2, 1.0, LEFT, TOP};
337
338         if (output_units.temperature == FAHRENHEIT) {
339                 deg = to_F(temperature);
340                 unit = "F";
341         } else {
342                 deg = to_C(temperature);
343                 unit = "C";
344         }
345         plot_text(gc, &tro, sec, temperature.mkelvin, "%d %s", deg, unit);
346 }
347
348 static void plot_temperature_text(struct dive *dive, struct graphics_context *gc,
349         struct plot_info *pi)
350 {
351         int i;
352         int last = 0;
353         temperature_t last_temperature, last_printed_temp;
354
355         if (!setup_temperature_limits(dive, gc, pi))
356                 return;
357
358         for (i = 0; i < dive->samples; i++) {
359                 struct sample *sample = dive->sample+i;
360                 if (sample->time.seconds > dive->duration.seconds)
361                         break; /* let's not plot surface temp events */
362                 int mkelvin = sample->temperature.mkelvin;
363                 int sec;
364                 if (!mkelvin)
365                         continue;
366                 last_temperature = sample->temperature;
367                 sec = sample->time.seconds;
368                 if (sec < last)
369                         continue;
370                 last = sec + 300;
371                 plot_single_temp_text(gc,sec,sample->temperature);
372                 last_printed_temp = last_temperature ;
373         }
374         /* it would be nice to print the end temperature, if it's different */
375         if (last_temperature.mkelvin != last_printed_temp.mkelvin) {
376                 plot_single_temp_text(gc,dive->duration.seconds,last_temperature);
377         }
378 }
379
380 static void plot_temperature_profile(struct dive *dive, struct graphics_context *gc,
381         struct plot_info *pi)
382 {
383         int i;
384         cairo_t *cr = gc->cr;
385         int last = 0;
386
387         if (!setup_temperature_limits(dive, gc, pi))
388                 return;
389
390         set_source_rgba(gc, 0.2, 0.2, 1.0, 0.8);
391         for (i = 0; i < dive->samples; i++) {
392                 struct sample *sample = dive->sample+i;
393                 if (sample->time.seconds > dive->duration.seconds)
394                         break; /* let's not plot surface temp events */
395                 int mkelvin = sample->temperature.mkelvin;
396                 if (!mkelvin) {
397                         if (!last)
398                                 continue;
399                         mkelvin = last;
400                 }
401                 if (last)
402                         line_to(gc, sample->time.seconds, mkelvin);
403                 else
404                         move_to(gc, sample->time.seconds, mkelvin);
405                 last = mkelvin;
406         }
407         cairo_stroke(cr);
408 }
409
410 /* gets both the actual start and end pressure as well as the scaling factors */
411 static int get_cylinder_pressure_range(struct dive *dive, struct graphics_context *gc,
412         struct plot_info *pi,
413         pressure_t *startp, pressure_t *endp)
414 {
415         int i;
416         int min, max;
417
418         gc->leftx = 0;
419         gc->rightx = get_maxtime(pi);
420
421         max = 0;
422         min = 5000000;
423         if (startp)
424                 startp->mbar = endp->mbar = 0;
425
426         for (i = 0; i < dive->samples; i++) {
427                 int mbar;
428                 struct sample *sample = dive->sample + i;
429
430                 /* FIXME! We only track cylinder 0 right now */
431                 if (sample->cylinderindex)
432                         continue;
433                 mbar = sample->cylinderpressure.mbar;
434                 if (!mbar)
435                         continue;
436                 if (mbar < min)
437                         min = mbar;
438                 if (mbar > max)
439                         max = mbar;
440         }
441         if (startp)
442                 startp->mbar = max;
443         if (endp)
444                 endp->mbar = min;
445         if (!max)
446                 return 0;
447         gc->topy = 0; gc->bottomy = max * 1.5;
448         return 1;
449 }
450
451 static void plot_cylinder_pressure(struct dive *dive, struct graphics_context *gc,
452         struct plot_info *pi)
453 {
454         int i, sec = -1;
455
456         if (!get_cylinder_pressure_range(dive, gc, pi, NULL, NULL))
457                 return;
458
459         cairo_set_source_rgba(gc->cr, 0.2, 1.0, 0.2, 0.80);
460
461         move_to(gc, 0, dive->cylinder[0].start.mbar);
462         for (i = 1; i < dive->samples; i++) {
463                 int mbar;
464                 struct sample *sample = dive->sample + i;
465
466                 mbar = sample->cylinderpressure.mbar;
467                 if (!mbar)
468                         continue;
469                 sec = sample->time.seconds;
470                 if (sec <= dive->duration.seconds)
471                         line_to(gc, sec, mbar);
472         }
473         /*
474          * We may have "surface time" events, in which case we don't go
475          * back to dive duration
476          */
477         if (sec < dive->duration.seconds)
478                 line_to(gc, dive->duration.seconds, dive->cylinder[0].end.mbar);
479         cairo_stroke(gc->cr);
480 }
481
482 /*
483  * Return air usage (in liters).
484  */
485 static double calculate_airuse(struct dive *dive)
486 {
487         double airuse = 0;
488         int i;
489
490         for (i = 0; i < MAX_CYLINDERS; i++) {
491                 cylinder_t *cyl = dive->cylinder + i;
492                 int size = cyl->type.size.mliter;
493                 double kilo_atm;
494
495                 if (!size)
496                         continue;
497
498                 kilo_atm = (cyl->start.mbar - cyl->end.mbar) / 1013250.0;
499
500                 /* Liters of air at 1 atm == milliliters at 1k atm*/
501                 airuse += kilo_atm * size;
502         }
503         return airuse;
504 }
505
506 static void plot_info(struct dive *dive, struct graphics_context *gc)
507 {
508         text_render_options_t tro = {10, 0.2, 1.0, 0.2, LEFT, TOP};
509         const double liters_per_cuft = 28.317;
510         const char *unit, *desc;
511         double airuse;
512
513         airuse = calculate_airuse(dive);
514         if (!airuse)
515                 return;
516
517         /* I really need to start addign some unit setting thing */
518         switch (output_units.volume) {
519         case LITER:
520                 unit = "l";
521                 break;
522         case CUFT:
523                 unit = "cuft";
524                 airuse /= liters_per_cuft;
525                 break;
526         }
527         plot_text(gc, &tro, 0.8, 0.8, "vol: %4.2f %s", airuse, unit);
528         if (dive->duration.seconds) {
529                 double pressure = 1 + (dive->meandepth.mm / 10000.0);
530                 double sac = airuse / pressure * 60 / dive->duration.seconds;
531                 plot_text(gc, &tro, 0.8, 0.85, "SAC: %4.2f %s/min", sac, unit);
532         }
533         desc = dive->cylinder[0].type.description;
534         if (desc || dive->cylinder[0].gasmix.o2.permille) {
535                 int o2 = dive->cylinder[0].gasmix.o2.permille / 10;
536                 if (!desc)
537                         desc = "";
538                 if (!o2)
539                         o2 = 21;
540                 plot_text(gc, &tro, 0.8, 0.9, "%s (%d%%)", desc, o2);
541         }
542 }
543
544 static void plot_cylinder_pressure_text(struct dive *dive, struct graphics_context *gc,
545         struct plot_info *pi)
546 {
547         pressure_t startp, endp;
548
549         if (get_cylinder_pressure_range(dive, gc, pi, &startp, &endp)) {
550                 int start, end;
551                 const char *unit = "bar";
552
553                 switch (output_units.pressure) {
554                 case PASCAL:
555                         start = startp.mbar * 100;
556                         end = startp.mbar * 100;
557                         unit = "pascal";
558                         break;
559                 case BAR:
560                         start = (startp.mbar + 500) / 1000;
561                         end = (endp.mbar + 500) / 1000;
562                         unit = "bar";
563                         break;
564                 case PSI:
565                         start = to_PSI(startp);
566                         end = to_PSI(endp);
567                         unit = "psi";
568                         break;
569                 }
570
571                 text_render_options_t tro = {10, 0.2, 1.0, 0.2, LEFT, TOP};
572                 plot_text(gc, &tro, 0, startp.mbar, "%d %s", start, unit);
573                 plot_text(gc, &tro, dive->duration.seconds, endp.mbar,
574                           "%d %s", end, unit);
575         }
576 }
577
578 static void analyze_plot_info_minmax_minute(struct plot_data *entry, struct plot_data *first, struct plot_data *last, int index)
579 {
580         struct plot_data *p = entry;
581         int time = entry->sec;
582         int seconds = 90*(index+1);
583         struct plot_data *min, *max;
584         int avg, nr;
585
586         /* Go back 'seconds' in time */
587         while (p > first) {
588                 if (p[-1].sec < time - seconds)
589                         break;
590                 p--;
591         }
592
593         /* Then go forward until we hit an entry past the time */
594         min = max = p;
595         avg = p->val;
596         nr = 1;
597         while (++p < last) {
598                 int val = p->val;
599                 if (p->sec > time + seconds)
600                         break;
601                 avg += val;
602                 nr ++;
603                 if (val < min->val)
604                         min = p;
605                 if (val > max->val)
606                         max = p;
607         }
608         entry->min[index] = min;
609         entry->max[index] = max;
610         entry->avg[index] = (avg + nr/2) / nr;
611 }
612
613 static void analyze_plot_info_minmax(struct plot_data *entry, struct plot_data *first, struct plot_data *last)
614 {
615         analyze_plot_info_minmax_minute(entry, first, last, 0);
616         analyze_plot_info_minmax_minute(entry, first, last, 1);
617         analyze_plot_info_minmax_minute(entry, first, last, 2);
618 }
619
620 static struct plot_info *analyze_plot_info(struct plot_info *pi)
621 {
622         int i;
623         int nr = pi->nr;
624
625         /* Smoothing function: 5-point triangular smooth */
626         for (i = 2; i < nr-2; i++) {
627                 struct plot_data *entry = pi->entry+i;
628                 int val;
629
630                 val = entry[-2].val + 2*entry[-1].val + 3*entry[0].val + 2*entry[1].val + entry[2].val;
631                 entry->smoothed = (val+4) / 9;
632         }
633
634         /* One-, two- and three-minute minmax data */
635         for (i = 0; i < nr; i++) {
636                 struct plot_data *entry = pi->entry +i;
637                 analyze_plot_info_minmax(entry, pi->entry, pi->entry+nr);
638         }
639         
640         return pi;
641 }
642
643 /*
644  * Create a plot-info with smoothing and ranged min/max
645  *
646  * This also makes sure that we have extra empty events on both
647  * sides, so that you can do end-points without having to worry
648  * about it.
649  */
650 static struct plot_info *depth_plot_info(struct dive *dive)
651 {
652         int lastdepth, maxtime;
653         int i, nr = dive->samples + 4, sec;
654         size_t alloc_size = plot_info_size(nr);
655         struct plot_info *pi;
656
657         pi = malloc(alloc_size);
658         if (!pi)
659                 return pi;
660         memset(pi, 0, alloc_size);
661         pi->nr = nr;
662         sec = 0;
663         maxtime = 0;
664         lastdepth = -1;
665         for (i = 0; i < dive->samples; i++) {
666                 int depth;
667                 struct sample *sample = dive->sample+i;
668                 struct plot_data *entry = pi->entry + i + 2;
669
670                 sec = entry->sec = sample->time.seconds;
671                 depth = entry->val = sample->depth.mm;
672                 if (depth || lastdepth)
673                         maxtime = sec;
674                 lastdepth = depth;
675         }
676         if (lastdepth)
677                 maxtime = sec + 20;
678         /* Fill in the last two entries with empty values but valid times */
679         i = dive->samples + 2;
680         pi->entry[i].sec = sec + 20;
681         pi->entry[i+1].sec = sec + 40;
682         pi->maxtime = maxtime;
683
684         return analyze_plot_info(pi);
685 }
686
687 void plot(struct graphics_context *gc, int w, int h, struct dive *dive)
688 {
689         double topx, topy;
690         struct plot_info *pi = depth_plot_info(dive);
691
692         topx = w / 20.0;
693         topy = h / 20.0;
694         cairo_translate(gc->cr, topx, topy);
695         cairo_set_line_width(gc->cr, 2);
696         cairo_set_line_cap(gc->cr, CAIRO_LINE_CAP_ROUND);
697         cairo_set_line_join(gc->cr, CAIRO_LINE_JOIN_ROUND);
698
699         /*
700          * We can use "cairo_translate()" because that doesn't
701          * scale line width etc. But the actual scaling we need
702          * do set up ourselves..
703          *
704          * Snif. What a pity.
705          */
706         gc->maxx = (w - 2*topx);
707         gc->maxy = (h - 2*topy);
708
709         /* Temperature profile */
710         plot_temperature_profile(dive, gc, pi);
711
712         /* Cylinder pressure plot */
713         plot_cylinder_pressure(dive, gc, pi);
714
715         /* Depth profile */
716         plot_depth_profile(dive, gc, pi);
717
718         /* Text on top of all graphs.. */
719         plot_temperature_text(dive, gc, pi);
720         plot_depth_text(dive, gc, pi);
721         plot_cylinder_pressure_text(dive, gc, pi);
722
723         /* And info box in the lower right corner.. */
724         gc->leftx = 0; gc->rightx = 1.0;
725         gc->topy = 0; gc->bottomy = 1.0;
726         plot_info(dive, gc);
727
728         /* Bounding box last */
729         set_source_rgb(gc, 1, 1, 1);
730         move_to(gc, 0, 0);
731         line_to(gc, 0, 1);
732         line_to(gc, 1, 1);
733         line_to(gc, 1, 0);
734         cairo_close_path(gc->cr);
735         cairo_stroke(gc->cr);
736
737 }
738
739 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
740 {
741         struct dive *dive = current_dive;
742         struct graphics_context gc = { .printer = 0 };
743         int w,h;
744
745         w = widget->allocation.width;
746         h = widget->allocation.height;
747
748         gc.cr = gdk_cairo_create(widget->window);
749         set_source_rgb(&gc, 0, 0, 0);
750         cairo_paint(gc.cr);
751
752         if (dive)
753                 plot(&gc, w, h, dive);
754
755         cairo_destroy(gc.cr);
756
757         return FALSE;
758 }
759
760 GtkWidget *dive_profile_widget(void)
761 {
762         GtkWidget *da;
763
764         da = gtk_drawing_area_new();
765         gtk_widget_set_size_request(da, 350, 250);
766         g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL);
767
768         return da;
769 }