Adjusted makefile for no `.' in PATH.
[stripchart.git] / stripchart.c
1 /*
2   stripchart - tools for quick & dirty data plotting.
3   Copyright (C) 2008, William Trevor King
4
5   This program is free software; you can redistribute it and/or
6   modify it under the terms of the GNU General Public License as
7   published by the Free Software Foundation; either version 3 of the
8   License, or (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful, but
11   WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
13   See the GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18   02111-1307, USA.
19
20   The author may be contacted at <wking@drexel.edu> on the Internet, or
21   write to Trevor King, Drexel University, Physics Dept., 3141 Chestnut St.,
22   Philadelphia PA 19104, USA.
23
24  
25   Plot simple (hopefully fast) strip charts.
26   X bits from Xlib tutorial by Ch. Tronche (http://tronche.lri.fr:8000/)
27   see http://tronche.com/gui/x/xlib/
28   and http://tronche.com/gui/x/xlib-tutorial/2nd-program-anatomy.html
29   
30   Double buffering code following Mark Vojkovich
31   http://www.xfree86.org/~mvojkovi/skull.tar.gz
32  */
33
34 #include <pthread.h> /* to adapt to user input */
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h> /* strlen */
38 //#include <plot.h> /* This is "plot.h", the public header file for GNU libplot,
39 //                   a shared library for 2-dimensional vector graphics */
40 #include <X11/Xthreads.h> /* since the callback thread and the main thread both call X */
41 #include <X11/Xlib.h>
42 #include <X11/Xatom.h> /* X datatypes, XA_STRING */
43 #include <X11/Xutil.h> /* XTextProperty, XParseGeometry */
44 #include "err_mac.h"
45 #include "stripchart.h"
46
47 //#define VERBOSE_LOCKING
48 //#define DEBUGGING_MODE /* Synchronize X */
49 #define USE_COLORS        /* Non-monochrome output */
50 #define USE_DOUBLE_BUFFER /* Write to a pixmap first to reduce screen redraw flicker */
51 #define ENABLE_AUTOSCALE  /* Calculate minX and minY from the data on mouse-clicks. */
52 #define WARN_WHEN_OOB     /* Generate some kind of warning output when out of bounds. */
53
54 #ifdef ENABLE_AUTOSCALE
55 #include <math.h> /* floor(), ceil() */
56 #endif
57
58 struct stripchart_struct {
59   double minY;
60   double maxY;
61   double *data; /**< 1-D circular buffer of points */
62   int points; /**< length of the buffer */
63   int active_points; /**< while the buffer is still filling */
64   int current_point; /**< our place in the buffer */
65 #ifdef ENABLE_AUTOSCALE
66   double scale_res;
67 #endif
68
69   /* display information for X */
70   Display *display;
71   int screen;
72   int depth;
73   Window rootwindow;
74   Window window;
75   XID drawable; /* either window or buffer, depending on use of double buffering */
76   GC gc; /**< graphics context */
77   char *title;
78   int ulx;
79   int uly;
80   unsigned int width;
81   unsigned int height;
82   int point_radius; /* in pixels */
83   int axes_on;
84   XFontStruct *font;
85   int blackpixel;
86   int whitepixel;
87 #ifdef USE_COLORS
88   XColor greenx, green; /* The X server and screen's respective takes on green */
89 #ifdef WARN_WHEN_OOB
90   XColor redx, red;
91 #endif /* WARN_WHEN_OOB */
92 #endif /* USE_COLORS */
93 #ifdef USE_DOUBLE_BUFFER
94   Pixmap buffer;
95   GC copy_gc;
96 #endif
97   /* pthread information so we can have a callback thread */
98   pthread_mutex_t lock;
99   int stop;
100   /* done signal, so main thread can lock until callback wraps up? */
101
102   /* derived quantites to avoid repead calculations */
103   double xscale;    /* pxls between x values */
104   double yscale;    /* pxls between per unit y */
105 };
106
107 /* Chart logic function */
108 static int scale(stripchart_t *chart);
109 static int autoscale(stripchart_t *chart);
110 static int draw(stripchart_t *chart);
111
112 /* X setup and drawing functions */
113 static int setup_X (stripchart_t *chart);
114 static int close_X (stripchart_t *chart);
115 static int set_title(stripchart_t *chart);
116 static int get_font(stripchart_t *chart);
117 #ifdef USE_COLORS
118 static int get_color(stripchart_t *chart, char *color_name, XColor *cx, XColor *c);
119 #endif
120 static int clear_window(stripchart_t *chart);
121 static int draw_point(stripchart_t *chart, int x, float y);
122 static int draw_axes(stripchart_t *chart);
123 static int flush_window(stripchart_t *chart);
124
125 /* Callback functions */
126 static int setup_callback_thread(stripchart_t *chart);
127 static void *callback_thread(void *arg /* chart */);
128 static int wakeup_callback_thread(stripchart_t *chart);
129
130 /* Synchronization functions */
131 static int lock_chart(stripchart_t *chart);
132 static int unlock_chart(stripchart_t *chart);
133
134 int stripchart_create(stripchart_t **pChart,
135                       char *title,
136                       char *geometry, /* eg "570x570+0+0" for upper left*/
137                       double minY, double maxY,
138                       double scale_res, /* only read if ENABLE_AUTOSCALE */
139                       int points_shown_at_once,
140                       int axes_on)
141 {
142   stripchart_t *chart;
143   int rc;
144
145   AST(pChart != NULL);
146   AST(*pChart == NULL);
147   AST(geometry != NULL);
148   AST(maxY > minY);
149   AST(points_shown_at_once >= 1);
150   
151   chart = (stripchart_t *)malloc(sizeof(stripchart_t));
152   *pChart = chart;
153   AST(chart != NULL);
154   
155   chart->data = NULL;
156   chart->title = NULL;
157   chart->font = NULL;
158
159   chart->minY = minY;
160   chart->maxY = maxY;
161 #ifdef ENABLE_AUTOSCALE
162   chart->scale_res = scale_res;
163 #endif
164   chart->points = points_shown_at_once;
165   chart->data = (double *)malloc(sizeof(double)*chart->points);
166   AST(chart->data != NULL);
167   chart->active_points = 0;
168   chart->current_point = 0;
169
170   chart->title = title;
171   /* Set geometry defaults */
172   chart->width=500;
173   chart->height=200;
174   chart->ulx=0;
175   chart->uly=0;
176   /* Parse geometry, rc is bitmask saying which were set */
177   rc = XParseGeometry(geometry, &chart->ulx, &chart->uly,
178                       &chart->width, &chart->height);
179   chart->point_radius = 2;
180   chart->axes_on = axes_on;
181   CHK( scale(chart) );
182   CHK( setup_X (chart) );
183   CHK( setup_callback_thread (chart) );
184
185   return SUCCESS;
186 }
187
188 /* needs a locked chart */
189 static int scale(stripchart_t *chart)
190 {
191   /* xpxl = xscale * xindex , so (points-1) all the way on the right */
192   chart->xscale = ((double)chart->width)/((double)chart->points-1.0);
193   /* ypxl = yscale * (y-ymax), so ymax = 0, ymin = height */
194   chart->yscale = ((double)chart->height)/(chart->minY - chart->maxY);
195   return SUCCESS;
196 }
197
198 #ifdef ENABLE_AUTOSCALE
199 /* needs a locked chart */
200 static int autoscale(stripchart_t *chart)
201 {
202   double dataMax, dataMin;
203   int i;
204
205   /* Ensure we have some data */
206   if (chart->active_points == 0)
207     return SUCCESS;
208   /* Find the extreme data points */
209   dataMax = dataMin = chart->data[0];
210   for (i=1; i < chart->active_points; i++) {
211     if (chart->data[i] > dataMax)
212       dataMax = chart->data[i];
213     else if (chart->data[i] < dataMin)
214       dataMin = chart->data[i];
215   }
216   /* round out to units of scale_res */
217   chart->maxY = ceil(dataMax/chart->scale_res)*chart->scale_res;
218   chart->minY = floor(dataMin/chart->scale_res)*chart->scale_res;
219
220   return SUCCESS;
221 }
222 #endif /* def ENABLE_AUTOSCALE */
223
224 /* needs a locked chart */
225 static int draw(stripchart_t *chart)
226 {
227   int i, j;
228
229   CHK( clear_window(chart) );
230   /* dots */
231   j = chart->current_point;
232   for (i=0; i != chart->active_points; i++)
233     CHK( draw_point(chart, i, chart->data[(j++)%chart->points]) );
234
235   if (chart->axes_on)
236     CHK( draw_axes(chart) );
237   CHK( flush_window(chart) );
238
239   return SUCCESS;
240 }
241
242 int stripchart_point(stripchart_t *chart, double value)
243 {
244   int i, j;
245
246   AST( chart != NULL );
247   CHK( lock_chart(chart) );
248
249   /* Add point */
250   if (chart->active_points != chart->points) { /* no overwrite */
251     chart->data[chart->active_points++] = value;
252   } else { /* overwite an old point */
253     chart->data[chart->current_point++] = value;
254     chart->current_point %= chart->points;
255   }
256   CHK(draw(chart));
257   CHK( unlock_chart(chart) );
258
259   return SUCCESS;
260 }
261
262 int stripchart_destroy(stripchart_t **pChart)
263 {
264   stripchart_t *chart;
265
266   AST(pChart != NULL);
267   chart = *pChart;
268   CHK( lock_chart(chart) );
269   chart = *pChart;
270   *pChart = NULL;
271   if (chart != NULL) {
272     chart->stop = TRUE;
273   }
274   CHK( unlock_chart(chart) );
275   CHK( wakeup_callback_thread(chart) );
276   return SUCCESS;
277 }
278
279 /* called by the callback_thread
280  * when it sees that it is stop time */
281 static int cleanup(stripchart_t *chart)
282 {
283   AST(chart != NULL);
284   if (chart->data != NULL)
285     free(chart->data);
286   close_X(chart);
287   free(chart);
288   return SUCCESS;
289 }
290
291 /* X setup and drawing functions */
292
293 static int setup_X (stripchart_t *chart)
294 {
295   XEvent event;
296   XSizeHints hints = {0};
297 #ifdef USE_DOUBLE_BUFFER
298   XGCValues vals;
299 #endif
300
301   /* Make X safe for threads */
302   AST(XInitThreads() != 0);
303
304   /* link to a display */
305   chart->display = XOpenDisplay(NULL); /* argument? */
306   if (chart->display == NULL) {
307     fprintf(stderr, "Error connecting to %s\n", XDisplayName(NULL));
308     AST(chart->display != NULL);
309   }
310
311 #ifdef DEBUGGING_MODE
312   XSynchronize(chart->display, 1);
313 #endif
314
315   chart->screen = DefaultScreen(chart->display);
316   chart->depth = DefaultDepth(chart->display, chart->screen);
317   chart->rootwindow = DefaultRootWindow(chart->display);
318   /* get some colors */
319   chart->blackpixel = BlackPixel(chart->display, chart->screen);
320   chart->whitepixel = WhitePixel(chart->display, chart->screen);
321
322   /* create a window, window border != window manager's border */
323   chart->window = XCreateSimpleWindow(chart->display, chart->rootwindow,
324                           chart->ulx, chart->uly, chart->width, chart->height,
325                           0 /* border width */,
326                           chart->blackpixel /* border color */,
327                           chart->blackpixel /* bg color */);
328   AST( chart->window );
329 #ifdef USE_DOUBLE_BUFFER
330   chart->buffer = XCreatePixmap(chart->display, chart->rootwindow,
331                                 chart->width, chart->height, chart->depth);
332   AST( chart->buffer );
333   chart->drawable = chart->buffer;
334 #else
335   chart->drawable = chart->window;
336 #endif
337
338   CHK( set_title(chart) );
339   //XMoveWindow(chart->display, chart->window, -1,-1);
340
341   /* we want MapNotify events for
342    * Structural events: window has appeared on the screen (been mapped),
343    *  resized, moved, etc.
344    * Exposure events: server lost information about the contents of a window
345    * ButtonPress : mouse button clicked on window
346    */
347 #ifdef ENABLE_AUTOSCALE
348   XSelectInput(chart->display, chart->window,
349                StructureNotifyMask |
350                ExposureMask |
351                ButtonPressMask);
352 #else
353   XSelectInput(chart->display, chart->window,
354                StructureNotifyMask |
355                ExposureMask);
356 #endif
357   /* Map the window (make it appear on the screen) */
358   XMapWindow(chart->display, chart->window);
359
360   /* Create a "Graphics Context"
361    * X is stateless, so the server doesn't remember attributes
362    * like drawing color, the thickness of the lines and so on.
363    * We have to pass these parameters to the server on each drawing request.
364    * To avoid passing two dozens of parameters, many of them unchanged,
365    * X uses an object called the Graphics Context, or GC for short.
366    */
367   chart->gc = XCreateGC(chart->display, chart->drawable, 0, NULL);
368   
369 #ifdef USE_COLORS
370   CHK( get_color(chart, "green", &chart->greenx, &chart->green) );
371 #ifdef WARN_WHEN_OOB
372   CHK( get_color(chart, "red", &chart->redx, &chart->red) );
373 #endif /* WARN_WHEN_OOB */
374 #endif /* USE_COLORS */
375 #ifdef USE_DOUBLE_BUFFER
376   /* turn off GraphicsExpose events for XCopyArea() requests */
377   vals.graphics_exposures = False;
378   chart->copy_gc = XCreateGC(chart->display, chart->window, GCGraphicsExposures, &vals);
379 #endif
380   /* Prepare to draw with a white color */
381   XSetForeground(chart->display, chart->gc, chart->whitepixel);
382   /* leave other parameters at their default values */
383   if (chart->axes_on)
384     CHK( get_font(chart) );
385
386   /* Wait for a MapNotify event */
387   for (;;) {
388     XNextEvent(chart->display, &event);
389     if (event.type == MapNotify)
390       break;
391   }
392
393   return SUCCESS;
394 }
395
396 static int close_X(stripchart_t *chart)
397 {
398   XDestroyWindow(chart->display, chart->window);
399   XFreePixmap(chart->display, chart->window);
400   XFreeGC(chart->display, chart->gc);
401 #ifdef USE_DOUBLE_BUFFER
402   XFreeGC(chart->display, chart->copy_gc);
403 #endif
404   if (chart->font != NULL)
405     XFreeFontInfo(NULL, chart->font, 1);
406     /* free (char **names, XFontStruct *free_info, int actual_count) ? */
407   XCloseDisplay(chart->display);
408 }
409
410 static int set_title(stripchart_t *chart)
411 {
412   XTextProperty windowName;
413   windowName.value    = (unsigned char *) chart->title;
414   windowName.encoding = XA_STRING;
415   windowName.format   = 8;
416   windowName.nitems   = strlen((char *) windowName.value);
417
418   /* request a title from the windows manager */
419   XSetWMName(chart->display, chart->window, &windowName);
420
421   return SUCCESS;
422 }
423
424 static int get_font(stripchart_t *chart)
425 {
426   GContext gcont;
427
428   gcont = XGContextFromGC(chart->gc);
429   chart->font = XQueryFont(chart->display, gcont);
430
431   return SUCCESS;
432 }
433
434 #ifdef USE_COLORS
435 static int get_color(stripchart_t *chart, char *color_name, XColor *cx, XColor *c)
436 {
437   Colormap colormap;
438
439   //AST( XLookupColor(chart->display, colormap, color_name,
440   //                NULL, &green_col) != 0 );
441   
442   if (DisplayPlanes(chart->display, chart->screen) != 1) {
443     colormap = DefaultColormap(chart->display, chart->screen);
444     if (XAllocNamedColor(chart->display, colormap, color_name, cx, c)) {
445       /* use colors (how do I free the named color?) */
446       /* cx is the idealized color, c is the best the current screen can do */
447       //XSetForeground(chart->display, chart->gc2, color_screen.pixel);
448       return SUCCESS;
449     }
450   }
451   /* just use white */
452   cx->pixel = chart->whitepixel;
453   c->pixel = chart->whitepixel;
454
455   return FAILURE;    
456 }
457 #endif /* def USE_COLORS */
458
459 static int clear_window(stripchart_t *chart)
460 {
461   int x, y;
462 #ifdef USE_DOUBLE_BUFFER
463   XSetForeground(chart->display, chart->gc, chart->blackpixel);
464   XFillRectangle(chart->display, chart->buffer, chart->gc, 0, 0, chart->width, chart->height);
465   XSetForeground(chart->display, chart->gc, chart->whitepixel);
466   /* Send the requests to the server */
467   //XFlush(chart->display);
468 #else
469   XClearWindow(chart->display, chart->window);
470 #endif
471   return SUCCESS;
472 }
473
474 static int flush_window(stripchart_t *chart)
475 {
476 #ifdef USE_DOUBLE_BUFFER
477   /* XCopyArea(display, src, dest, gc, src_x, src_y, width, height,  dest_x, dest_y) */
478   XCopyArea(chart->display, chart->buffer, chart->window, chart->copy_gc, 0, 0,
479             chart->width, chart->height, 0, 0);
480   XFlush(chart->display);
481 #else
482   XFlush(chart->display);
483 #endif
484   return SUCCESS;
485 }
486
487 static int draw_point(stripchart_t *chart, int x, float y)
488 {
489   int xpxl, ypxl;
490   /* find the center of the circle in pixels */
491   xpxl = (int) (chart->xscale * (float)x);
492   ypxl = (int) (chart->yscale * (y - chart->maxY));
493
494 #ifdef WARN_WHEN_OOB
495 #ifdef USE_COLORS
496   if (y > chart->maxY) {
497     ypxl = 0; /* draw red point at the top of the screen */
498     XSetForeground(chart->display, chart->gc, chart->red.pixel); 
499   } else if (y < chart->minY) {
500     ypxl = chart->height; /* draw red point at the bottom of the screen */
501     XSetForeground(chart->display, chart->gc, chart->red.pixel); 
502   }
503 #endif
504 #endif
505
506   /* draw a circle, coords are (x,y) for upper left of bounding box
507    * and the height and width of the bounding box.
508    * Then start and stop angles in degrees * 64
509    * XDrawArc and XFillArc take identical arguments, pick your favorite.
510    */
511   XDrawArc(chart->display, chart->drawable, chart->gc,
512            xpxl - chart->point_radius, ypxl - chart->point_radius,
513            2*chart->point_radius, 2*chart->point_radius,
514            0, 360*64);
515
516   // XDrawLine(chart->display, chart->drawable, chart->gc, 10, 60, 180, 20);
517
518 #ifdef WARN_WHEN_OOB
519 #ifdef USE_COLORS
520   XSetForeground(chart->display, chart->gc, chart->whitepixel); 
521 #endif
522 #endif
523
524   return SUCCESS;
525 }
526
527 static int draw_axes(stripchart_t *chart)
528 {
529   char buffer[80] = {0};
530   
531 #ifdef USE_COLORS
532   XSetForeground(chart->display, chart->gc, chart->green.pixel);
533 #endif
534   /* lable y-max in upper left */
535   sprintf(buffer, "%g", chart->maxY);
536   XDrawString(chart->display, chart->drawable, chart->gc,
537               0,0+chart->font->ascent,buffer,strlen(buffer));
538
539   /* label y-min in lower left */
540   sprintf(buffer, "%g", chart->minY);
541   XDrawString(chart->display, chart->drawable, chart->gc,
542               0,chart->height-chart->font->descent+1,
543               buffer,strlen(buffer));
544
545   /* draw X axis */
546   if (chart->maxY > 0 && chart->minY < 0) {
547     int ypxl = (int) (-chart->yscale * chart->maxY);
548     XDrawLine(chart->display, chart->drawable, chart->gc,
549               0, ypxl, chart->width, ypxl);
550   }
551 #ifdef USE_COLORS
552   XSetForeground(chart->display, chart->gc, chart->whitepixel);
553 #endif
554
555   return SUCCESS;
556 }
557
558
559 static int handle_X_callbacks(stripchart_t *chart)
560 {
561   XEvent event;
562
563   /* Wait for a Expose event (which neccessitates redrawing. */
564   for (;;) {
565     XNextEvent(chart->display, &event);
566     CHK( lock_chart(chart) );
567     if (chart->stop == TRUE) {
568       CHK( unlock_chart(chart) );
569       goto done;
570     }
571     switch(event.type) {
572     case Expose:
573       CHK( draw(chart) );
574       break;
575     case ConfigureNotify:
576       {
577         XConfigureEvent *pe = (XConfigureEvent *)&event;
578         chart->ulx = pe->x;
579         chart->uly = pe->y;
580         chart->width = pe->width;
581         chart->height = pe->height;
582       }
583 #ifdef USE_DOUBLE_BUFFER
584       XFreePixmap(chart->display, chart->buffer);
585       chart->buffer = XCreatePixmap(chart->display, chart->rootwindow,
586                                       chart->width, chart->height, chart->depth);
587       AST( chart->buffer != 0 );
588       chart->drawable = chart->buffer;
589       //XClearWindow(chart->display, chart->window); /* ? */
590 #endif
591       CHK( scale(chart) );     /* Map max and min to pixel values */
592       CHK( draw(chart) );
593       break;
594 #ifdef ENABLE_AUTOSCALE
595     case ButtonPress:
596       CHK( autoscale(chart) ); /* Calc new max and min values */
597       CHK( scale(chart) );     /* Map max and min to pixel values */
598       CHK( draw(chart) );
599       break;
600 #endif
601     default:
602       break; /* ignore other events */
603     } 
604     CHK( unlock_chart(chart) );
605   }
606  done:
607   return SUCCESS;
608 }
609
610 /* Callback functions */
611
612 static int setup_callback_thread(stripchart_t *chart)
613 {
614   pthread_t tid;
615   pthread_attr_t attr;
616
617   CHK( pthread_mutex_init(&chart->lock, NULL) );
618   CHK( pthread_attr_init(&attr) );
619   CHK( pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) );
620   CHK( pthread_create(&tid, &attr, &callback_thread, (void *)chart) );
621   CHK( pthread_attr_destroy(&attr) );
622
623   return SUCCESS;
624 }
625
626 static void *callback_thread(void *arg /* chart */)
627 {
628   stripchart_t *chart = (stripchart_t *)arg;
629   handle_X_callbacks(chart);
630   pthread_mutex_destroy(&chart->lock);
631   cleanup(chart);
632   return NULL;
633 }
634
635 static int wakeup_callback_thread(stripchart_t *chart)
636 {
637   XEvent event;
638   event.type = Expose;
639   /* the callback thread is blocking while waiting for events,
640      send it one to wake it, so it checks the stop value */
641   XSendEvent (chart->display, chart->window,
642               FALSE, /* don't neet to propogate, we'll be listening */
643               ExposureMask,
644               &event);
645   return SUCCESS;
646 }
647
648 /* Synchronization functions */
649
650 static int lock_chart(stripchart_t *chart)
651 {
652 #ifdef VERBOSE_LOCKING
653   fprintf(stderr, "Thread %lu locking %p\n",
654           (unsigned long int)pthread_self(), &chart->lock);
655 #endif
656   if (pthread_mutex_lock(&chart->lock) != 0) {
657     perror("lock");
658     AST(1==0);
659   }
660 #ifdef VERBOSE_LOCKING
661   fprintf(stderr, "Thread %lu locked %p\n",
662           (unsigned long int)pthread_self(), &chart->lock);
663 #endif
664   return SUCCESS;
665 }
666
667 static int unlock_chart(stripchart_t *chart)
668 {
669 #ifdef VERBOSE_LOCKING
670   fprintf(stderr, "Thread %lu unlocking %p\n",
671           (unsigned long int)pthread_self(), &chart->lock);
672 #endif
673   if(pthread_mutex_unlock(&chart->lock) != 0) {
674     perror("unlock");
675     AST(1==0);
676   }
677 #ifdef VERBOSE_LOCKING
678   fprintf(stderr, "Thread %lu unlocked %p\n",
679           (unsigned long int)pthread_self(), &chart->lock);
680 #endif
681   return SUCCESS;
682 }