2 stripchart - tools for quick & dirty data plotting.
3 Copyright (C) 2008, William Trevor King
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.
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.
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
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.
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
30 Double buffering code following Mark Vojkovich
31 http://www.xfree86.org/~mvojkovi/skull.tar.gz
34 #include <pthread.h> /* to adapt to user input */
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 */
42 #include <X11/Xatom.h> /* X datatypes, XA_STRING */
43 #include <X11/Xutil.h> /* XTextProperty, XParseGeometry */
45 #include "stripchart.h"
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. */
54 #ifdef ENABLE_AUTOSCALE
55 #include <math.h> /* floor(), ceil() */
58 struct stripchart_struct {
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
69 /* display information for X */
75 XID drawable; /* either window or buffer, depending on use of double buffering */
76 GC gc; /**< graphics context */
82 int point_radius; /* in pixels */
88 XColor greenx, green; /* The X server and screen's respective takes on green */
91 #endif /* WARN_WHEN_OOB */
92 #endif /* USE_COLORS */
93 #ifdef USE_DOUBLE_BUFFER
97 /* pthread information so we can have a callback thread */
100 /* done signal, so main thread can lock until callback wraps up? */
102 /* derived quantites to avoid repead calculations */
103 double xscale; /* pxls between x values */
104 double yscale; /* pxls between per unit y */
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);
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);
118 static int get_color(stripchart_t *chart, char *color_name, XColor *cx, XColor *c);
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);
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);
130 /* Synchronization functions */
131 static int lock_chart(stripchart_t *chart);
132 static int unlock_chart(stripchart_t *chart);
134 int stripchart_create(stripchart_t **pChart,
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,
146 AST(*pChart == NULL);
147 AST(geometry != NULL);
149 AST(points_shown_at_once >= 1);
151 chart = (stripchart_t *)malloc(sizeof(stripchart_t));
161 #ifdef ENABLE_AUTOSCALE
162 chart->scale_res = scale_res;
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;
170 chart->title = title;
171 /* Set geometry defaults */
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;
182 CHK( setup_X (chart) );
183 CHK( setup_callback_thread (chart) );
188 /* needs a locked chart */
189 static int scale(stripchart_t *chart)
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);
198 #ifdef ENABLE_AUTOSCALE
199 /* needs a locked chart */
200 static int autoscale(stripchart_t *chart)
202 double dataMax, dataMin;
205 /* Ensure we have some data */
206 if (chart->active_points == 0)
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];
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;
222 #endif /* def ENABLE_AUTOSCALE */
224 /* needs a locked chart */
225 static int draw(stripchart_t *chart)
229 CHK( clear_window(chart) );
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]) );
236 CHK( draw_axes(chart) );
237 CHK( flush_window(chart) );
242 int stripchart_point(stripchart_t *chart, double value)
246 AST( chart != NULL );
247 CHK( lock_chart(chart) );
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;
257 CHK( unlock_chart(chart) );
262 int stripchart_destroy(stripchart_t **pChart)
268 CHK( lock_chart(chart) );
274 CHK( unlock_chart(chart) );
275 CHK( wakeup_callback_thread(chart) );
279 /* called by the callback_thread
280 * when it sees that it is stop time */
281 static int cleanup(stripchart_t *chart)
284 if (chart->data != NULL)
291 /* X setup and drawing functions */
293 static int setup_X (stripchart_t *chart)
296 XSizeHints hints = {0};
297 #ifdef USE_DOUBLE_BUFFER
301 /* Make X safe for threads */
302 AST(XInitThreads() != 0);
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);
311 #ifdef DEBUGGING_MODE
312 XSynchronize(chart->display, 1);
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);
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;
335 chart->drawable = chart->window;
338 CHK( set_title(chart) );
339 //XMoveWindow(chart->display, chart->window, -1,-1);
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
347 #ifdef ENABLE_AUTOSCALE
348 XSelectInput(chart->display, chart->window,
349 StructureNotifyMask |
353 XSelectInput(chart->display, chart->window,
354 StructureNotifyMask |
357 /* Map the window (make it appear on the screen) */
358 XMapWindow(chart->display, chart->window);
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.
367 chart->gc = XCreateGC(chart->display, chart->drawable, 0, NULL);
370 CHK( get_color(chart, "green", &chart->greenx, &chart->green) );
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);
380 /* Prepare to draw with a white color */
381 XSetForeground(chart->display, chart->gc, chart->whitepixel);
382 /* leave other parameters at their default values */
384 CHK( get_font(chart) );
386 /* Wait for a MapNotify event */
388 XNextEvent(chart->display, &event);
389 if (event.type == MapNotify)
396 static int close_X(stripchart_t *chart)
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);
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);
410 static int set_title(stripchart_t *chart)
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);
418 /* request a title from the windows manager */
419 XSetWMName(chart->display, chart->window, &windowName);
424 static int get_font(stripchart_t *chart)
428 gcont = XGContextFromGC(chart->gc);
429 chart->font = XQueryFont(chart->display, gcont);
435 static int get_color(stripchart_t *chart, char *color_name, XColor *cx, XColor *c)
439 //AST( XLookupColor(chart->display, colormap, color_name,
440 // NULL, &green_col) != 0 );
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);
452 cx->pixel = chart->whitepixel;
453 c->pixel = chart->whitepixel;
457 #endif /* def USE_COLORS */
459 static int clear_window(stripchart_t *chart)
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);
469 XClearWindow(chart->display, chart->window);
474 static int flush_window(stripchart_t *chart)
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);
482 XFlush(chart->display);
487 static int draw_point(stripchart_t *chart, int x, float y)
490 /* find the center of the circle in pixels */
491 xpxl = (int) (chart->xscale * (float)x);
492 ypxl = (int) (chart->yscale * (y - chart->maxY));
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);
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.
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,
516 // XDrawLine(chart->display, chart->drawable, chart->gc, 10, 60, 180, 20);
520 XSetForeground(chart->display, chart->gc, chart->whitepixel);
527 static int draw_axes(stripchart_t *chart)
529 char buffer[80] = {0};
532 XSetForeground(chart->display, chart->gc, chart->green.pixel);
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));
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));
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);
552 XSetForeground(chart->display, chart->gc, chart->whitepixel);
559 static int handle_X_callbacks(stripchart_t *chart)
563 /* Wait for a Expose event (which neccessitates redrawing. */
565 XNextEvent(chart->display, &event);
566 CHK( lock_chart(chart) );
567 if (chart->stop == TRUE) {
568 CHK( unlock_chart(chart) );
575 case ConfigureNotify:
577 XConfigureEvent *pe = (XConfigureEvent *)&event;
580 chart->width = pe->width;
581 chart->height = pe->height;
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); /* ? */
591 CHK( scale(chart) ); /* Map max and min to pixel values */
594 #ifdef ENABLE_AUTOSCALE
596 CHK( autoscale(chart) ); /* Calc new max and min values */
597 CHK( scale(chart) ); /* Map max and min to pixel values */
602 break; /* ignore other events */
604 CHK( unlock_chart(chart) );
610 /* Callback functions */
612 static int setup_callback_thread(stripchart_t *chart)
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) );
626 static void *callback_thread(void *arg /* chart */)
628 stripchart_t *chart = (stripchart_t *)arg;
629 handle_X_callbacks(chart);
630 pthread_mutex_destroy(&chart->lock);
635 static int wakeup_callback_thread(stripchart_t *chart)
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 */
648 /* Synchronization functions */
650 static int lock_chart(stripchart_t *chart)
652 #ifdef VERBOSE_LOCKING
653 fprintf(stderr, "Thread %lu locking %p\n",
654 (unsigned long int)pthread_self(), &chart->lock);
656 if (pthread_mutex_lock(&chart->lock) != 0) {
660 #ifdef VERBOSE_LOCKING
661 fprintf(stderr, "Thread %lu locked %p\n",
662 (unsigned long int)pthread_self(), &chart->lock);
667 static int unlock_chart(stripchart_t *chart)
669 #ifdef VERBOSE_LOCKING
670 fprintf(stderr, "Thread %lu unlocking %p\n",
671 (unsigned long int)pthread_self(), &chart->lock);
673 if(pthread_mutex_unlock(&chart->lock) != 0) {
677 #ifdef VERBOSE_LOCKING
678 fprintf(stderr, "Thread %lu unlocked %p\n",
679 (unsigned long int)pthread_self(), &chart->lock);