cc2861f3f82f41b201e44e296fa4e75838719061
1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
7 #if 0
8 #include "rrd_tool.h"
9 #endif
11 #include <sys/stat.h>
12 #ifdef WIN32
13 #include <io.h>
14 #include <fcntl.h>
15 #endif
17 #include "rrd_graph.h"
18 #include "rrd_graph_helper.h"
20 /* some constant definitions */
23 #ifndef RRD_DEFAULT_FONT
24 #ifdef WIN32
25 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
26 #else
27 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
28 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
29 #endif
30 #endif
33 text_prop_t text_prop[] = {
34 { 10.0, RRD_DEFAULT_FONT }, /* default */
35 { 12.0, RRD_DEFAULT_FONT }, /* title */
36 { 8.0, RRD_DEFAULT_FONT }, /* axis */
37 { 10.0, RRD_DEFAULT_FONT }, /* unit */
38 { 10.0, RRD_DEFAULT_FONT } /* legend */
39 };
41 xlab_t xlab[] = {
42 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
43 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
44 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
45 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
46 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
47 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
48 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
49 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
50 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
51 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
52 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
53 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
54 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
55 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
56 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
57 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
58 };
60 /* sensible logarithmic y label intervals ...
61 the first element of each row defines the possible starting points on the
62 y axis ... the other specify the */
64 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
65 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
66 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
67 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
68 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
69 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
70 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
71 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
73 /* sensible y label intervals ...*/
75 ylab_t ylab[]= {
76 {0.1, {1,2, 5,10}},
77 {0.2, {1,5,10,20}},
78 {0.5, {1,2, 4,10}},
79 {1.0, {1,2, 5,10}},
80 {2.0, {1,5,10,20}},
81 {5.0, {1,2, 4,10}},
82 {10.0, {1,2, 5,10}},
83 {20.0, {1,5,10,20}},
84 {50.0, {1,2, 4,10}},
85 {100.0, {1,2, 5,10}},
86 {200.0, {1,5,10,20}},
87 {500.0, {1,2, 4,10}},
88 {0.0, {0,0,0,0}}};
91 gfx_color_t graph_col[] = /* default colors */
92 { 0xFFFFFFFF, /* canvas */
93 0xF0F0F0FF, /* background */
94 0xD0D0D0FF, /* shade A */
95 0xA0A0A0FF, /* shade B */
96 0x909090FF, /* grid */
97 0xE05050FF, /* major grid */
98 0x000000FF, /* font */
99 0x000000FF, /* frame */
100 0xFF0000FF /* arrow */
101 };
104 /* #define DEBUG */
106 #ifdef DEBUG
107 # define DPRINT(x) (void)(printf x, printf("\n"))
108 #else
109 # define DPRINT(x)
110 #endif
113 /* initialize with xtr(im,0); */
114 int
115 xtr(image_desc_t *im,time_t mytime){
116 static double pixie;
117 if (mytime==0){
118 pixie = (double) im->xsize / (double)(im->end - im->start);
119 return im->xorigin;
120 }
121 return (int)((double)im->xorigin
122 + pixie * ( mytime - im->start ) );
123 }
125 /* translate data values into y coordinates */
126 int
127 ytr(image_desc_t *im, double value){
128 static double pixie;
129 double yval;
130 if (isnan(value)){
131 if(!im->logarithmic)
132 pixie = (double) im->ysize / (im->maxval - im->minval);
133 else
134 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
135 yval = im->yorigin;
136 } else if(!im->logarithmic) {
137 yval = im->yorigin - pixie * (value - im->minval) + 0.5;
138 } else {
139 if (value < im->minval) {
140 yval = im->yorigin;
141 } else {
142 yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
143 }
144 }
145 /* make sure we don't return anything too unreasonable. GD lib can
146 get terribly slow when drawing lines outside its scope. This is
147 especially problematic in connection with the rigid option */
148 if (! im->rigid) {
149 return (int)yval;
150 } else if ((int)yval > im->yorigin) {
151 return im->yorigin+2;
152 } else if ((int) yval < im->yorigin - im->ysize){
153 return im->yorigin - im->ysize - 2;
154 } else {
155 return (int)yval;
156 }
157 }
161 /* conversion function for symbolic entry names */
164 #define conv_if(VV,VVV) \
165 if (strcmp(#VV, string) == 0) return VVV ;
167 enum gf_en gf_conv(char *string){
169 conv_if(PRINT,GF_PRINT)
170 conv_if(GPRINT,GF_GPRINT)
171 conv_if(COMMENT,GF_COMMENT)
172 conv_if(HRULE,GF_HRULE)
173 conv_if(VRULE,GF_VRULE)
174 conv_if(LINE,GF_LINE)
175 conv_if(AREA,GF_AREA)
176 conv_if(STACK,GF_STACK)
177 conv_if(TICK,GF_TICK)
178 conv_if(DEF,GF_DEF)
179 conv_if(CDEF,GF_CDEF)
180 conv_if(VDEF,GF_VDEF)
181 conv_if(PART,GF_PART)
183 return (-1);
184 }
186 enum gfx_if_en if_conv(char *string){
188 conv_if(PNG,IF_PNG)
189 conv_if(SVG,IF_SVG)
190 conv_if(EPS,IF_EPS)
192 return (-1);
193 }
195 enum tmt_en tmt_conv(char *string){
197 conv_if(SECOND,TMT_SECOND)
198 conv_if(MINUTE,TMT_MINUTE)
199 conv_if(HOUR,TMT_HOUR)
200 conv_if(DAY,TMT_DAY)
201 conv_if(WEEK,TMT_WEEK)
202 conv_if(MONTH,TMT_MONTH)
203 conv_if(YEAR,TMT_YEAR)
204 return (-1);
205 }
207 enum grc_en grc_conv(char *string){
209 conv_if(BACK,GRC_BACK)
210 conv_if(CANVAS,GRC_CANVAS)
211 conv_if(SHADEA,GRC_SHADEA)
212 conv_if(SHADEB,GRC_SHADEB)
213 conv_if(GRID,GRC_GRID)
214 conv_if(MGRID,GRC_MGRID)
215 conv_if(FONT,GRC_FONT)
216 conv_if(FRAME,GRC_FRAME)
217 conv_if(ARROW,GRC_ARROW)
219 return -1;
220 }
222 enum text_prop_en text_prop_conv(char *string){
224 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
225 conv_if(TITLE,TEXT_PROP_TITLE)
226 conv_if(AXIS,TEXT_PROP_AXIS)
227 conv_if(UNIT,TEXT_PROP_UNIT)
228 conv_if(LEGEND,TEXT_PROP_LEGEND)
229 return -1;
230 }
233 #undef conv_if
237 int
238 im_free(image_desc_t *im)
239 {
240 long i,ii;
241 if (im == NULL) return 0;
242 for(i=0;i<im->gdes_c;i++){
243 if (im->gdes[i].data_first){
244 /* careful here, because a single pointer can occur several times */
245 free (im->gdes[i].data);
246 if (im->gdes[i].ds_namv){
247 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
248 free(im->gdes[i].ds_namv[ii]);
249 free(im->gdes[i].ds_namv);
250 }
251 }
252 free (im->gdes[i].p_data);
253 free (im->gdes[i].rpnp);
254 }
255 free(im->gdes);
256 gfx_destroy(im->canvas);
257 return 0;
258 }
260 /* find SI magnitude symbol for the given number*/
261 void
262 auto_scale(
263 image_desc_t *im, /* image description */
264 double *value,
265 char **symb_ptr,
266 double *magfact
267 )
268 {
270 char *symbol[] = {"a", /* 10e-18 Atto */
271 "f", /* 10e-15 Femto */
272 "p", /* 10e-12 Pico */
273 "n", /* 10e-9 Nano */
274 "u", /* 10e-6 Micro */
275 "m", /* 10e-3 Milli */
276 " ", /* Base */
277 "k", /* 10e3 Kilo */
278 "M", /* 10e6 Mega */
279 "G", /* 10e9 Giga */
280 "T", /* 10e12 Tera */
281 "P", /* 10e15 Peta */
282 "E"};/* 10e18 Exa */
284 int symbcenter = 6;
285 int sindex;
287 if (*value == 0.0 || isnan(*value) ) {
288 sindex = 0;
289 *magfact = 1.0;
290 } else {
291 sindex = floor(log(fabs(*value))/log((double)im->base));
292 *magfact = pow((double)im->base, (double)sindex);
293 (*value) /= (*magfact);
294 }
295 if ( sindex <= symbcenter && sindex >= -symbcenter) {
296 (*symb_ptr) = symbol[sindex+symbcenter];
297 }
298 else {
299 (*symb_ptr) = "?";
300 }
301 }
304 /* find SI magnitude symbol for the numbers on the y-axis*/
305 void
306 si_unit(
307 image_desc_t *im /* image description */
308 )
309 {
311 char symbol[] = {'a', /* 10e-18 Atto */
312 'f', /* 10e-15 Femto */
313 'p', /* 10e-12 Pico */
314 'n', /* 10e-9 Nano */
315 'u', /* 10e-6 Micro */
316 'm', /* 10e-3 Milli */
317 ' ', /* Base */
318 'k', /* 10e3 Kilo */
319 'M', /* 10e6 Mega */
320 'G', /* 10e9 Giga */
321 'T', /* 10e12 Tera */
322 'P', /* 10e15 Peta */
323 'E'};/* 10e18 Exa */
325 int symbcenter = 6;
326 double digits;
328 if (im->unitsexponent != 9999) {
329 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
330 digits = floor(im->unitsexponent / 3);
331 } else {
332 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
333 }
334 im->magfact = pow((double)im->base , digits);
336 #ifdef DEBUG
337 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
338 #endif
340 if ( ((digits+symbcenter) < sizeof(symbol)) &&
341 ((digits+symbcenter) >= 0) )
342 im->symbol = symbol[(int)digits+symbcenter];
343 else
344 im->symbol = ' ';
345 }
347 /* move min and max values around to become sensible */
349 void
350 expand_range(image_desc_t *im)
351 {
352 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
353 600.0,500.0,400.0,300.0,250.0,
354 200.0,125.0,100.0,90.0,80.0,
355 75.0,70.0,60.0,50.0,40.0,30.0,
356 25.0,20.0,10.0,9.0,8.0,
357 7.0,6.0,5.0,4.0,3.5,3.0,
358 2.5,2.0,1.8,1.5,1.2,1.0,
359 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
361 double scaled_min,scaled_max;
362 double adj;
363 int i;
367 #ifdef DEBUG
368 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
369 im->minval,im->maxval,im->magfact);
370 #endif
372 if (isnan(im->ygridstep)){
373 if(im->extra_flags & ALTAUTOSCALE) {
374 /* measure the amplitude of the function. Make sure that
375 graph boundaries are slightly higher then max/min vals
376 so we can see amplitude on the graph */
377 double delt, fact;
379 delt = im->maxval - im->minval;
380 adj = delt * 0.1;
381 fact = 2.0 * pow(10.0,
382 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
383 if (delt < fact) {
384 adj = (fact - delt) * 0.55;
385 #ifdef DEBUG
386 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
387 #endif
388 }
389 im->minval -= adj;
390 im->maxval += adj;
391 }
392 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
393 /* measure the amplitude of the function. Make sure that
394 graph boundaries are slightly higher than max vals
395 so we can see amplitude on the graph */
396 adj = (im->maxval - im->minval) * 0.1;
397 im->maxval += adj;
398 }
399 else {
400 scaled_min = im->minval / im->magfact;
401 scaled_max = im->maxval / im->magfact;
403 for (i=1; sensiblevalues[i] > 0; i++){
404 if (sensiblevalues[i-1]>=scaled_min &&
405 sensiblevalues[i]<=scaled_min)
406 im->minval = sensiblevalues[i]*(im->magfact);
408 if (-sensiblevalues[i-1]<=scaled_min &&
409 -sensiblevalues[i]>=scaled_min)
410 im->minval = -sensiblevalues[i-1]*(im->magfact);
412 if (sensiblevalues[i-1] >= scaled_max &&
413 sensiblevalues[i] <= scaled_max)
414 im->maxval = sensiblevalues[i-1]*(im->magfact);
416 if (-sensiblevalues[i-1]<=scaled_max &&
417 -sensiblevalues[i] >=scaled_max)
418 im->maxval = -sensiblevalues[i]*(im->magfact);
419 }
420 }
421 } else {
422 /* adjust min and max to the grid definition if there is one */
423 im->minval = (double)im->ylabfact * im->ygridstep *
424 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
425 im->maxval = (double)im->ylabfact * im->ygridstep *
426 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
427 }
429 #ifdef DEBUG
430 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
431 im->minval,im->maxval,im->magfact);
432 #endif
433 }
436 /* reduce data reimplementation by Alex */
438 void
439 reduce_data(
440 enum cf_en cf, /* which consolidation function ?*/
441 unsigned long cur_step, /* step the data currently is in */
442 time_t *start, /* start, end and step as requested ... */
443 time_t *end, /* ... by the application will be ... */
444 unsigned long *step, /* ... adjusted to represent reality */
445 unsigned long *ds_cnt, /* number of data sources in file */
446 rrd_value_t **data) /* two dimensional array containing the data */
447 {
448 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
449 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
450 rrd_value_t *srcptr,*dstptr;
452 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
453 dstptr = *data;
454 srcptr = *data;
455 row_cnt = ((*end)-(*start))/cur_step;
457 #ifdef DEBUG
458 #define DEBUG_REDUCE
459 #endif
460 #ifdef DEBUG_REDUCE
461 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
462 row_cnt,reduce_factor,*start,*end,cur_step);
463 for (col=0;col<row_cnt;col++) {
464 printf("time %10lu: ",*start+(col+1)*cur_step);
465 for (i=0;i<*ds_cnt;i++)
466 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
467 printf("\n");
468 }
469 #endif
471 /* We have to combine [reduce_factor] rows of the source
472 ** into one row for the destination. Doing this we also
473 ** need to take care to combine the correct rows. First
474 ** alter the start and end time so that they are multiples
475 ** of the new step time. We cannot reduce the amount of
476 ** time so we have to move the end towards the future and
477 ** the start towards the past.
478 */
479 end_offset = (*end) % (*step);
480 start_offset = (*start) % (*step);
482 /* If there is a start offset (which cannot be more than
483 ** one destination row), skip the appropriate number of
484 ** source rows and one destination row. The appropriate
485 ** number is what we do know (start_offset/cur_step) of
486 ** the new interval (*step/cur_step aka reduce_factor).
487 */
488 #ifdef DEBUG_REDUCE
489 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
490 printf("row_cnt before: %lu\n",row_cnt);
491 #endif
492 if (start_offset) {
493 (*start) = (*start)-start_offset;
494 skiprows=reduce_factor-start_offset/cur_step;
495 srcptr+=skiprows* *ds_cnt;
496 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
497 row_cnt-=skiprows;
498 }
499 #ifdef DEBUG_REDUCE
500 printf("row_cnt between: %lu\n",row_cnt);
501 #endif
503 /* At the end we have some rows that are not going to be
504 ** used, the amount is end_offset/cur_step
505 */
506 if (end_offset) {
507 (*end) = (*end)-end_offset+(*step);
508 skiprows = end_offset/cur_step;
509 row_cnt-=skiprows;
510 }
511 #ifdef DEBUG_REDUCE
512 printf("row_cnt after: %lu\n",row_cnt);
513 #endif
515 /* Sanity check: row_cnt should be multiple of reduce_factor */
516 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
518 if (row_cnt%reduce_factor) {
519 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
520 row_cnt,reduce_factor);
521 printf("BUG in reduce_data()\n");
522 exit(1);
523 }
525 /* Now combine reduce_factor intervals at a time
526 ** into one interval for the destination.
527 */
529 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
530 for (col=0;col<(*ds_cnt);col++) {
531 rrd_value_t newval=DNAN;
532 unsigned long validval=0;
534 for (i=0;i<reduce_factor;i++) {
535 if (isnan(srcptr[i*(*ds_cnt)+col])) {
536 continue;
537 }
538 validval++;
539 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
540 else {
541 switch (cf) {
542 case CF_HWPREDICT:
543 case CF_DEVSEASONAL:
544 case CF_DEVPREDICT:
545 case CF_SEASONAL:
546 case CF_AVERAGE:
547 newval += srcptr[i*(*ds_cnt)+col];
548 break;
549 case CF_MINIMUM:
550 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
551 break;
552 case CF_FAILURES:
553 /* an interval contains a failure if any subintervals contained a failure */
554 case CF_MAXIMUM:
555 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
556 break;
557 case CF_LAST:
558 newval = srcptr[i*(*ds_cnt)+col];
559 break;
560 }
561 }
562 }
563 if (validval == 0){newval = DNAN;} else{
564 switch (cf) {
565 case CF_HWPREDICT:
566 case CF_DEVSEASONAL:
567 case CF_DEVPREDICT:
568 case CF_SEASONAL:
569 case CF_AVERAGE:
570 newval /= validval;
571 break;
572 case CF_MINIMUM:
573 case CF_FAILURES:
574 case CF_MAXIMUM:
575 case CF_LAST:
576 break;
577 }
578 }
579 *dstptr++=newval;
580 }
581 srcptr+=(*ds_cnt)*reduce_factor;
582 row_cnt-=reduce_factor;
583 }
584 /* If we had to alter the endtime, we didn't have enough
585 ** source rows to fill the last row. Fill it with NaN.
586 */
587 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
588 #ifdef DEBUG_REDUCE
589 row_cnt = ((*end)-(*start))/ *step;
590 srcptr = *data;
591 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
592 row_cnt,*start,*end,*step);
593 for (col=0;col<row_cnt;col++) {
594 printf("time %10lu: ",*start+(col+1)*(*step));
595 for (i=0;i<*ds_cnt;i++)
596 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
597 printf("\n");
598 }
599 #endif
600 }
603 /* get the data required for the graphs from the
604 relevant rrds ... */
606 int
607 data_fetch( image_desc_t *im )
608 {
609 int i,ii;
610 int skip;
611 /* pull the data from the log files ... */
612 for (i=0;i<im->gdes_c;i++){
613 /* only GF_DEF elements fetch data */
614 if (im->gdes[i].gf != GF_DEF)
615 continue;
617 skip=0;
618 /* do we have it already ?*/
619 for (ii=0;ii<i;ii++){
620 if (im->gdes[ii].gf != GF_DEF)
621 continue;
622 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
623 && (im->gdes[i].cf == im->gdes[ii].cf)){
624 /* OK the data it is here already ...
625 * we just copy the header portion */
626 im->gdes[i].start = im->gdes[ii].start;
627 im->gdes[i].end = im->gdes[ii].end;
628 im->gdes[i].step = im->gdes[ii].step;
629 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
630 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
631 im->gdes[i].data = im->gdes[ii].data;
632 im->gdes[i].data_first = 0;
633 skip=1;
634 }
635 if (skip)
636 break;
637 }
638 if (! skip) {
639 unsigned long ft_step = im->gdes[i].step ;
641 if((rrd_fetch_fn(im->gdes[i].rrd,
642 im->gdes[i].cf,
643 &im->gdes[i].start,
644 &im->gdes[i].end,
645 &ft_step,
646 &im->gdes[i].ds_cnt,
647 &im->gdes[i].ds_namv,
648 &im->gdes[i].data)) == -1){
649 return -1;
650 }
651 im->gdes[i].data_first = 1;
653 if (ft_step < im->gdes[i].step) {
654 reduce_data(im->gdes[i].cf,
655 ft_step,
656 &im->gdes[i].start,
657 &im->gdes[i].end,
658 &im->gdes[i].step,
659 &im->gdes[i].ds_cnt,
660 &im->gdes[i].data);
661 } else {
662 im->gdes[i].step = ft_step;
663 }
664 }
666 /* lets see if the required data source is realy there */
667 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
668 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
669 im->gdes[i].ds=ii; }
670 }
671 if (im->gdes[i].ds== -1){
672 rrd_set_error("No DS called '%s' in '%s'",
673 im->gdes[i].ds_nam,im->gdes[i].rrd);
674 return -1;
675 }
677 }
678 return 0;
679 }
681 /* evaluate the expressions in the CDEF functions */
683 /*************************************************************
684 * CDEF stuff
685 *************************************************************/
687 long
688 find_var_wrapper(void *arg1, char *key)
689 {
690 return find_var((image_desc_t *) arg1, key);
691 }
693 /* find gdes containing var*/
694 long
695 find_var(image_desc_t *im, char *key){
696 long ii;
697 for(ii=0;ii<im->gdes_c-1;ii++){
698 if((im->gdes[ii].gf == GF_DEF
699 || im->gdes[ii].gf == GF_VDEF
700 || im->gdes[ii].gf == GF_CDEF)
701 && (strcmp(im->gdes[ii].vname,key) == 0)){
702 return ii;
703 }
704 }
705 return -1;
706 }
708 /* find the largest common denominator for all the numbers
709 in the 0 terminated num array */
710 long
711 lcd(long *num){
712 long rest;
713 int i;
714 for (i=0;num[i+1]!=0;i++){
715 do {
716 rest=num[i] % num[i+1];
717 num[i]=num[i+1]; num[i+1]=rest;
718 } while (rest!=0);
719 num[i+1] = num[i];
720 }
721 /* return i==0?num[i]:num[i-1]; */
722 return num[i];
723 }
725 /* run the rpn calculator on all the VDEF and CDEF arguments */
726 int
727 data_calc( image_desc_t *im){
729 int gdi;
730 int dataidx;
731 long *steparray, rpi;
732 int stepcnt;
733 time_t now;
734 rpnstack_t rpnstack;
736 rpnstack_init(&rpnstack);
738 for (gdi=0;gdi<im->gdes_c;gdi++){
739 /* Look for GF_VDEF and GF_CDEF in the same loop,
740 * so CDEFs can use VDEFs and vice versa
741 */
742 switch (im->gdes[gdi].gf) {
743 case GF_VDEF:
744 /* A VDEF has no DS. This also signals other parts
745 * of rrdtool that this is a VDEF value, not a CDEF.
746 */
747 im->gdes[gdi].ds_cnt = 0;
748 if (vdef_calc(im,gdi)) {
749 rrd_set_error("Error processing VDEF '%s'"
750 ,im->gdes[gdi].vname
751 );
752 rpnstack_free(&rpnstack);
753 return -1;
754 }
755 break;
756 case GF_CDEF:
757 im->gdes[gdi].ds_cnt = 1;
758 im->gdes[gdi].ds = 0;
759 im->gdes[gdi].data_first = 1;
760 im->gdes[gdi].start = 0;
761 im->gdes[gdi].end = 0;
762 steparray=NULL;
763 stepcnt = 0;
764 dataidx=-1;
766 /* Find the variables in the expression.
767 * - VDEF variables are substituted by their values
768 * and the opcode is changed into OP_NUMBER.
769 * - CDEF variables are analized for their step size,
770 * the lowest common denominator of all the step
771 * sizes of the data sources involved is calculated
772 * and the resulting number is the step size for the
773 * resulting data source.
774 */
775 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
776 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
777 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
778 if (im->gdes[ptr].ds_cnt == 0) {
779 #if 0
780 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
781 im->gdes[gdi].vname,
782 im->gdes[ptr].vname);
783 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
784 #endif
785 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
786 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
787 } else {
788 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
789 rrd_set_error("realloc steparray");
790 rpnstack_free(&rpnstack);
791 return -1;
792 };
794 steparray[stepcnt-1] = im->gdes[ptr].step;
796 /* adjust start and end of cdef (gdi) so
797 * that it runs from the latest start point
798 * to the earliest endpoint of any of the
799 * rras involved (ptr)
800 */
801 if(im->gdes[gdi].start < im->gdes[ptr].start)
802 im->gdes[gdi].start = im->gdes[ptr].start;
804 if(im->gdes[gdi].end == 0 ||
805 im->gdes[gdi].end > im->gdes[ptr].end)
806 im->gdes[gdi].end = im->gdes[ptr].end;
808 /* store pointer to the first element of
809 * the rra providing data for variable,
810 * further save step size and data source
811 * count of this rra
812 */
813 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
814 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
815 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
817 /* backoff the *.data ptr; this is done so
818 * rpncalc() function doesn't have to treat
819 * the first case differently
820 */
821 } /* if ds_cnt != 0 */
822 } /* if OP_VARIABLE */
823 } /* loop through all rpi */
825 /* move the data pointers to the correct period */
826 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
827 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
828 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
829 if(im->gdes[gdi].start > im->gdes[ptr].start) {
830 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
831 }
832 }
833 }
836 if(steparray == NULL){
837 rrd_set_error("rpn expressions without DEF"
838 " or CDEF variables are not supported");
839 rpnstack_free(&rpnstack);
840 return -1;
841 }
842 steparray[stepcnt]=0;
843 /* Now find the resulting step. All steps in all
844 * used RRAs have to be visited
845 */
846 im->gdes[gdi].step = lcd(steparray);
847 free(steparray);
848 if((im->gdes[gdi].data = malloc((
849 (im->gdes[gdi].end-im->gdes[gdi].start)
850 / im->gdes[gdi].step)
851 * sizeof(double)))==NULL){
852 rrd_set_error("malloc im->gdes[gdi].data");
853 rpnstack_free(&rpnstack);
854 return -1;
855 }
857 /* Step through the new cdef results array and
858 * calculate the values
859 */
860 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
861 now<=im->gdes[gdi].end;
862 now += im->gdes[gdi].step)
863 {
864 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
866 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
867 * in this case we are advancing by timesteps;
868 * we use the fact that time_t is a synonym for long
869 */
870 if (rpn_calc(rpnp,&rpnstack,(long) now,
871 im->gdes[gdi].data,++dataidx) == -1) {
872 /* rpn_calc sets the error string */
873 rpnstack_free(&rpnstack);
874 return -1;
875 }
876 } /* enumerate over time steps within a CDEF */
877 break;
878 default:
879 continue;
880 }
881 } /* enumerate over CDEFs */
882 rpnstack_free(&rpnstack);
883 return 0;
884 }
886 /* massage data so, that we get one value for each x coordinate in the graph */
887 int
888 data_proc( image_desc_t *im ){
889 long i,ii;
890 double pixstep = (double)(im->end-im->start)
891 /(double)im->xsize; /* how much time
892 passes in one pixel */
893 double paintval;
894 double minval=DNAN,maxval=DNAN;
896 unsigned long gr_time;
898 /* memory for the processed data */
899 for(i=0;i<im->gdes_c;i++){
900 if((im->gdes[i].gf==GF_LINE) ||
901 (im->gdes[i].gf==GF_AREA) ||
902 (im->gdes[i].gf==GF_TICK) ||
903 (im->gdes[i].gf==GF_STACK)){
904 if((im->gdes[i].p_data = malloc((im->xsize +1)
905 * sizeof(rrd_value_t)))==NULL){
906 rrd_set_error("malloc data_proc");
907 return -1;
908 }
909 }
910 }
912 for(i=0;i<im->xsize;i++){
913 long vidx;
914 gr_time = im->start+pixstep*i; /* time of the
915 current step */
916 paintval=0.0;
918 for(ii=0;ii<im->gdes_c;ii++){
919 double value;
920 switch(im->gdes[ii].gf){
921 case GF_LINE:
922 case GF_AREA:
923 case GF_TICK:
924 paintval = 0.0;
925 case GF_STACK:
926 vidx = im->gdes[ii].vidx;
928 value =
929 im->gdes[vidx].data[
930 ((unsigned long)floor(
931 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
932 )
933 ) *im->gdes[vidx].ds_cnt
934 +im->gdes[vidx].ds];
936 if (! isnan(value)) {
937 paintval += value;
938 im->gdes[ii].p_data[i] = paintval;
939 /* GF_TICK: the data values are not relevant for min and max */
940 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
941 if (isnan(minval) || paintval < minval)
942 minval = paintval;
943 if (isnan(maxval) || paintval > maxval)
944 maxval = paintval;
945 }
946 } else {
947 im->gdes[ii].p_data[i] = DNAN;
948 }
949 break;
950 case GF_PRINT:
951 case GF_GPRINT:
952 case GF_COMMENT:
953 case GF_HRULE:
954 case GF_VRULE:
955 case GF_DEF:
956 case GF_CDEF:
957 case GF_VDEF:
958 case GF_PART:
959 break;
960 }
961 }
962 }
964 /* if min or max have not been asigned a value this is because
965 there was no data in the graph ... this is not good ...
966 lets set these to dummy values then ... */
968 if (isnan(minval)) minval = 0.0;
969 if (isnan(maxval)) maxval = 1.0;
971 /* adjust min and max values */
972 if (isnan(im->minval)
973 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
974 && im->minval > minval))
975 im->minval = minval;
976 if (isnan(im->maxval)
977 || (!im->rigid
978 && im->maxval < maxval)){
979 if (im->logarithmic)
980 im->maxval = maxval * 1.1;
981 else
982 im->maxval = maxval;
983 }
984 /* make sure min and max are not equal */
985 if (im->minval == im->maxval) {
986 im->maxval *= 1.01;
987 if (! im->logarithmic) {
988 im->minval *= 0.99;
989 }
991 /* make sure min and max are not both zero */
992 if (im->maxval == 0.0) {
993 im->maxval = 1.0;
994 }
996 }
997 return 0;
998 }
1002 /* identify the point where the first gridline, label ... gets placed */
1004 time_t
1005 find_first_time(
1006 time_t start, /* what is the initial time */
1007 enum tmt_en baseint, /* what is the basic interval */
1008 long basestep /* how many if these do we jump a time */
1009 )
1010 {
1011 struct tm tm;
1012 tm = *localtime(&start);
1013 switch(baseint){
1014 case TMT_SECOND:
1015 tm.tm_sec -= tm.tm_sec % basestep; break;
1016 case TMT_MINUTE:
1017 tm.tm_sec=0;
1018 tm.tm_min -= tm.tm_min % basestep;
1019 break;
1020 case TMT_HOUR:
1021 tm.tm_sec=0;
1022 tm.tm_min = 0;
1023 tm.tm_hour -= tm.tm_hour % basestep; break;
1024 case TMT_DAY:
1025 /* we do NOT look at the basestep for this ... */
1026 tm.tm_sec=0;
1027 tm.tm_min = 0;
1028 tm.tm_hour = 0; break;
1029 case TMT_WEEK:
1030 /* we do NOT look at the basestep for this ... */
1031 tm.tm_sec=0;
1032 tm.tm_min = 0;
1033 tm.tm_hour = 0;
1034 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1035 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1036 break;
1037 case TMT_MONTH:
1038 tm.tm_sec=0;
1039 tm.tm_min = 0;
1040 tm.tm_hour = 0;
1041 tm.tm_mday = 1;
1042 tm.tm_mon -= tm.tm_mon % basestep; break;
1044 case TMT_YEAR:
1045 tm.tm_sec=0;
1046 tm.tm_min = 0;
1047 tm.tm_hour = 0;
1048 tm.tm_mday = 1;
1049 tm.tm_mon = 0;
1050 tm.tm_year -= (tm.tm_year+1900) % basestep;
1052 }
1053 return mktime(&tm);
1054 }
1055 /* identify the point where the next gridline, label ... gets placed */
1056 time_t
1057 find_next_time(
1058 time_t current, /* what is the initial time */
1059 enum tmt_en baseint, /* what is the basic interval */
1060 long basestep /* how many if these do we jump a time */
1061 )
1062 {
1063 struct tm tm;
1064 time_t madetime;
1065 tm = *localtime(¤t);
1066 do {
1067 switch(baseint){
1068 case TMT_SECOND:
1069 tm.tm_sec += basestep; break;
1070 case TMT_MINUTE:
1071 tm.tm_min += basestep; break;
1072 case TMT_HOUR:
1073 tm.tm_hour += basestep; break;
1074 case TMT_DAY:
1075 tm.tm_mday += basestep; break;
1076 case TMT_WEEK:
1077 tm.tm_mday += 7*basestep; break;
1078 case TMT_MONTH:
1079 tm.tm_mon += basestep; break;
1080 case TMT_YEAR:
1081 tm.tm_year += basestep;
1082 }
1083 madetime = mktime(&tm);
1084 } while (madetime == -1); /* this is necessary to skip impssible times
1085 like the daylight saving time skips */
1086 return madetime;
1088 }
1091 /* calculate values required for PRINT and GPRINT functions */
1093 int
1094 print_calc(image_desc_t *im, char ***prdata)
1095 {
1096 long i,ii,validsteps;
1097 double printval;
1098 time_t printtime;
1099 int graphelement = 0;
1100 long vidx;
1101 int max_ii;
1102 double magfact = -1;
1103 char *si_symb = "";
1104 char *percent_s;
1105 int prlines = 1;
1106 if (im->imginfo) prlines++;
1107 for(i=0;i<im->gdes_c;i++){
1108 switch(im->gdes[i].gf){
1109 case GF_PRINT:
1110 prlines++;
1111 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1112 rrd_set_error("realloc prdata");
1113 return 0;
1114 }
1115 case GF_GPRINT:
1116 /* PRINT and GPRINT can now print VDEF generated values.
1117 * There's no need to do any calculations on them as these
1118 * calculations were already made.
1119 */
1120 vidx = im->gdes[i].vidx;
1121 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1122 printval = im->gdes[vidx].vf.val;
1123 printtime = im->gdes[vidx].vf.when;
1124 } else { /* need to calculate max,min,avg etcetera */
1125 max_ii =((im->gdes[vidx].end
1126 - im->gdes[vidx].start)
1127 / im->gdes[vidx].step
1128 * im->gdes[vidx].ds_cnt);
1129 printval = DNAN;
1130 validsteps = 0;
1131 for( ii=im->gdes[vidx].ds;
1132 ii < max_ii;
1133 ii+=im->gdes[vidx].ds_cnt){
1134 if (! finite(im->gdes[vidx].data[ii]))
1135 continue;
1136 if (isnan(printval)){
1137 printval = im->gdes[vidx].data[ii];
1138 validsteps++;
1139 continue;
1140 }
1142 switch (im->gdes[i].cf){
1143 case CF_HWPREDICT:
1144 case CF_DEVPREDICT:
1145 case CF_DEVSEASONAL:
1146 case CF_SEASONAL:
1147 case CF_AVERAGE:
1148 validsteps++;
1149 printval += im->gdes[vidx].data[ii];
1150 break;
1151 case CF_MINIMUM:
1152 printval = min( printval, im->gdes[vidx].data[ii]);
1153 break;
1154 case CF_FAILURES:
1155 case CF_MAXIMUM:
1156 printval = max( printval, im->gdes[vidx].data[ii]);
1157 break;
1158 case CF_LAST:
1159 printval = im->gdes[vidx].data[ii];
1160 }
1161 }
1162 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1163 if (validsteps > 1) {
1164 printval = (printval / validsteps);
1165 }
1166 }
1167 } /* prepare printval */
1169 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1170 if (im->gdes[i].gf == GF_PRINT){
1171 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1172 sprintf((*prdata)[prlines-2],"%s (%lu)",
1173 ctime(&printtime),printtime);
1174 (*prdata)[prlines-1] = NULL;
1175 } else {
1176 sprintf(im->gdes[i].legend,"%s (%lu)",
1177 ctime(&printtime),printtime);
1178 graphelement = 1;
1179 }
1180 } else {
1181 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1182 /* Magfact is set to -1 upon entry to print_calc. If it
1183 * is still less than 0, then we need to run auto_scale.
1184 * Otherwise, put the value into the correct units. If
1185 * the value is 0, then do not set the symbol or magnification
1186 * so next the calculation will be performed again. */
1187 if (magfact < 0.0) {
1188 auto_scale(im,&printval,&si_symb,&magfact);
1189 if (printval == 0.0)
1190 magfact = -1.0;
1191 } else {
1192 printval /= magfact;
1193 }
1194 *(++percent_s) = 's';
1195 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1196 auto_scale(im,&printval,&si_symb,&magfact);
1197 }
1199 if (im->gdes[i].gf == GF_PRINT){
1200 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1201 if (bad_format(im->gdes[i].format)) {
1202 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1203 return -1;
1204 }
1205 #ifdef HAVE_SNPRINTF
1206 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1207 #else
1208 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1209 #endif
1210 (*prdata)[prlines-1] = NULL;
1211 } else {
1212 /* GF_GPRINT */
1214 if (bad_format(im->gdes[i].format)) {
1215 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1216 return -1;
1217 }
1218 #ifdef HAVE_SNPRINTF
1219 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1220 #else
1221 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1222 #endif
1223 graphelement = 1;
1224 }
1225 }
1226 break;
1227 case GF_LINE:
1228 case GF_AREA:
1229 case GF_TICK:
1230 case GF_STACK:
1231 case GF_HRULE:
1232 case GF_VRULE:
1233 graphelement = 1;
1234 break;
1235 case GF_COMMENT:
1236 case GF_DEF:
1237 case GF_CDEF:
1238 case GF_VDEF:
1239 case GF_PART:
1240 break;
1241 }
1242 }
1243 return graphelement;
1244 }
1247 /* place legends with color spots */
1248 int
1249 leg_place(image_desc_t *im)
1250 {
1251 /* graph labels */
1252 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1253 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1254 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1255 int fill=0, fill_last;
1256 int leg_c = 0;
1257 int leg_x = border, leg_y = im->yimg;
1258 int leg_cc;
1259 int glue = 0;
1260 int i,ii, mark = 0;
1261 char prt_fctn; /*special printfunctions */
1262 int *legspace;
1264 if( !(im->extra_flags & NOLEGEND) ) {
1265 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1266 rrd_set_error("malloc for legspace");
1267 return -1;
1268 }
1270 for(i=0;i<im->gdes_c;i++){
1271 fill_last = fill;
1273 leg_cc = strlen(im->gdes[i].legend);
1275 /* is there a controle code ant the end of the legend string ? */
1276 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1277 prt_fctn = im->gdes[i].legend[leg_cc-1];
1278 leg_cc -= 2;
1279 im->gdes[i].legend[leg_cc] = '\0';
1280 } else {
1281 prt_fctn = '\0';
1282 }
1283 /* remove exess space */
1284 while (prt_fctn=='g' &&
1285 leg_cc > 0 &&
1286 im->gdes[i].legend[leg_cc-1]==' '){
1287 leg_cc--;
1288 im->gdes[i].legend[leg_cc]='\0';
1289 }
1290 if (leg_cc != 0 ){
1291 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1293 if (fill > 0){
1294 /* no interleg space if string ends in \g */
1295 fill += legspace[i];
1296 }
1297 if (im->gdes[i].gf != GF_GPRINT &&
1298 im->gdes[i].gf != GF_COMMENT) {
1299 fill += box;
1300 }
1301 fill += gfx_get_text_width(im->canvas, fill+border,
1302 im->text_prop[TEXT_PROP_LEGEND].font,
1303 im->text_prop[TEXT_PROP_LEGEND].size,
1304 im->tabwidth,
1305 im->gdes[i].legend);
1306 leg_c++;
1307 } else {
1308 legspace[i]=0;
1309 }
1310 /* who said there was a special tag ... ?*/
1311 if (prt_fctn=='g') {
1312 prt_fctn = '\0';
1313 }
1314 if (prt_fctn == '\0') {
1315 if (i == im->gdes_c -1 ) prt_fctn ='l';
1317 /* is it time to place the legends ? */
1318 if (fill > im->ximg - 2*border){
1319 if (leg_c > 1) {
1320 /* go back one */
1321 i--;
1322 fill = fill_last;
1323 leg_c--;
1324 prt_fctn = 'j';
1325 } else {
1326 prt_fctn = 'l';
1327 }
1329 }
1330 }
1333 if (prt_fctn != '\0'){
1334 leg_x = border;
1335 if (leg_c >= 2 && prt_fctn == 'j') {
1336 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1337 } else {
1338 glue = 0;
1339 }
1340 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1341 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1343 for(ii=mark;ii<=i;ii++){
1344 if(im->gdes[ii].legend[0]=='\0')
1345 continue;
1346 im->gdes[ii].leg_x = leg_x;
1347 im->gdes[ii].leg_y = leg_y;
1348 leg_x +=
1349 gfx_get_text_width(im->canvas, leg_x,
1350 im->text_prop[TEXT_PROP_LEGEND].font,
1351 im->text_prop[TEXT_PROP_LEGEND].size,
1352 im->tabwidth,
1353 im->gdes[ii].legend)
1354 + legspace[ii]
1355 + glue;
1356 if (im->gdes[ii].gf != GF_GPRINT &&
1357 im->gdes[ii].gf != GF_COMMENT)
1358 leg_x += box;
1359 }
1360 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1361 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1362 fill = 0;
1363 leg_c = 0;
1364 mark = ii;
1365 }
1366 }
1367 im->yimg = leg_y;
1368 free(legspace);
1369 }
1370 return 0;
1371 }
1373 /* create a grid on the graph. it determines what to do
1374 from the values of xsize, start and end */
1376 /* the xaxis labels are determined from the number of seconds per pixel
1377 in the requested graph */
1381 int
1382 horizontal_grid(image_desc_t *im)
1383 {
1384 double range;
1385 double scaledrange;
1386 int pixel,i;
1387 int sgrid,egrid;
1388 double gridstep;
1389 double scaledstep;
1390 char graph_label[100];
1391 double X0,X1,Y0;
1392 int labfact,gridind;
1393 int decimals, fractionals;
1394 char labfmt[64];
1396 labfact=2;
1397 gridind=-1;
1398 range = im->maxval - im->minval;
1399 scaledrange = range / im->magfact;
1401 /* does the scale of this graph make it impossible to put lines
1402 on it? If so, give up. */
1403 if (isnan(scaledrange)) {
1404 return 0;
1405 }
1407 /* find grid spaceing */
1408 pixel=1;
1409 if(isnan(im->ygridstep)){
1410 if(im->extra_flags & ALTYGRID) {
1411 /* find the value with max number of digits. Get number of digits */
1412 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1413 if(decimals <= 0) /* everything is small. make place for zero */
1414 decimals = 1;
1416 fractionals = floor(log10(range));
1417 if(fractionals < 0) /* small amplitude. */
1418 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1419 else
1420 sprintf(labfmt, "%%%d.1f", decimals + 1);
1421 gridstep = pow((double)10, (double)fractionals);
1422 if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1423 gridstep = 0.1;
1424 /* should have at least 5 lines but no more then 15 */
1425 if(range/gridstep < 5)
1426 gridstep /= 10;
1427 if(range/gridstep > 15)
1428 gridstep *= 10;
1429 if(range/gridstep > 5) {
1430 labfact = 1;
1431 if(range/gridstep > 8)
1432 labfact = 2;
1433 }
1434 else {
1435 gridstep /= 5;
1436 labfact = 5;
1437 }
1438 }
1439 else {
1440 for(i=0;ylab[i].grid > 0;i++){
1441 pixel = im->ysize / (scaledrange / ylab[i].grid);
1442 if (gridind == -1 && pixel > 5) {
1443 gridind = i;
1444 break;
1445 }
1446 }
1448 for(i=0; i<4;i++) {
1449 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1450 labfact = ylab[gridind].lfac[i];
1451 break;
1452 }
1453 }
1455 gridstep = ylab[gridind].grid * im->magfact;
1456 }
1457 } else {
1458 gridstep = im->ygridstep;
1459 labfact = im->ylabfact;
1460 }
1462 X0=im->xorigin;
1463 X1=im->xorigin+im->xsize;
1465 sgrid = (int)( im->minval / gridstep - 1);
1466 egrid = (int)( im->maxval / gridstep + 1);
1467 scaledstep = gridstep/im->magfact;
1468 for (i = sgrid; i <= egrid; i++){
1469 Y0=ytr(im,gridstep*i);
1470 if ( Y0 >= im->yorigin-im->ysize
1471 && Y0 <= im->yorigin){
1472 if(i % labfact == 0){
1473 if (i==0 || im->symbol == ' ') {
1474 if(scaledstep < 1){
1475 if(im->extra_flags & ALTYGRID) {
1476 sprintf(graph_label,labfmt,scaledstep*i);
1477 }
1478 else {
1479 sprintf(graph_label,"%4.1f",scaledstep*i);
1480 }
1481 } else {
1482 sprintf(graph_label,"%4.0f",scaledstep*i);
1483 }
1484 }else {
1485 if(scaledstep < 1){
1486 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1487 } else {
1488 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1489 }
1490 }
1492 gfx_new_text ( im->canvas,
1493 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1494 im->graph_col[GRC_FONT],
1495 im->text_prop[TEXT_PROP_AXIS].font,
1496 im->text_prop[TEXT_PROP_AXIS].size,
1497 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1498 graph_label );
1499 gfx_new_line ( im->canvas,
1500 X0-2,Y0,
1501 X1+2,Y0,
1502 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1504 } else {
1505 gfx_new_line ( im->canvas,
1506 X0-1,Y0,
1507 X1+1,Y0,
1508 GRIDWIDTH, im->graph_col[GRC_GRID] );
1510 }
1511 }
1512 }
1513 return 1;
1514 }
1516 /* logaritmic horizontal grid */
1517 int
1518 horizontal_log_grid(image_desc_t *im)
1519 {
1520 double pixpex;
1521 int ii,i;
1522 int minoridx=0, majoridx=0;
1523 char graph_label[100];
1524 double X0,X1,Y0;
1525 double value, pixperstep, minstep;
1527 /* find grid spaceing */
1528 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1530 if (isnan(pixpex)) {
1531 return 0;
1532 }
1534 for(i=0;yloglab[i][0] > 0;i++){
1535 minstep = log10(yloglab[i][0]);
1536 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1537 if(yloglab[i][ii+2]==0){
1538 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1539 break;
1540 }
1541 }
1542 pixperstep = pixpex * minstep;
1543 if(pixperstep > 5){minoridx = i;}
1544 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1545 }
1547 X0=im->xorigin;
1548 X1=im->xorigin+im->xsize;
1549 /* paint minor grid */
1550 for (value = pow((double)10, log10(im->minval)
1551 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1552 value <= im->maxval;
1553 value *= yloglab[minoridx][0]){
1554 if (value < im->minval) continue;
1555 i=0;
1556 while(yloglab[minoridx][++i] > 0){
1557 Y0 = ytr(im,value * yloglab[minoridx][i]);
1558 if (Y0 <= im->yorigin - im->ysize) break;
1559 gfx_new_line ( im->canvas,
1560 X0-1,Y0,
1561 X1+1,Y0,
1562 GRIDWIDTH, im->graph_col[GRC_GRID] );
1563 }
1564 }
1566 /* paint major grid and labels*/
1567 for (value = pow((double)10, log10(im->minval)
1568 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1569 value <= im->maxval;
1570 value *= yloglab[majoridx][0]){
1571 if (value < im->minval) continue;
1572 i=0;
1573 while(yloglab[majoridx][++i] > 0){
1574 Y0 = ytr(im,value * yloglab[majoridx][i]);
1575 if (Y0 <= im->yorigin - im->ysize) break;
1576 gfx_new_line ( im->canvas,
1577 X0-2,Y0,
1578 X1+2,Y0,
1579 MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1581 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1582 gfx_new_text ( im->canvas,
1583 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1584 im->graph_col[GRC_FONT],
1585 im->text_prop[TEXT_PROP_AXIS].font,
1586 im->text_prop[TEXT_PROP_AXIS].size,
1587 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1588 graph_label );
1589 }
1590 }
1591 return 1;
1592 }
1595 void
1596 vertical_grid(
1597 image_desc_t *im )
1598 {
1599 int xlab_sel; /* which sort of label and grid ? */
1600 time_t ti, tilab;
1601 long factor;
1602 char graph_label[100];
1603 double X0,Y0,Y1; /* points for filled graph and more*/
1606 /* the type of time grid is determined by finding
1607 the number of seconds per pixel in the graph */
1610 if(im->xlab_user.minsec == -1){
1611 factor=(im->end - im->start)/im->xsize;
1612 xlab_sel=0;
1613 while ( xlab[xlab_sel+1].minsec != -1
1614 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1615 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1616 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1617 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1618 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1619 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1620 im->xlab_user.labst = xlab[xlab_sel].labst;
1621 im->xlab_user.precis = xlab[xlab_sel].precis;
1622 im->xlab_user.stst = xlab[xlab_sel].stst;
1623 }
1625 /* y coords are the same for every line ... */
1626 Y0 = im->yorigin;
1627 Y1 = im->yorigin-im->ysize;
1630 /* paint the minor grid */
1631 for(ti = find_first_time(im->start,
1632 im->xlab_user.gridtm,
1633 im->xlab_user.gridst);
1634 ti < im->end;
1635 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1636 ){
1637 /* are we inside the graph ? */
1638 if (ti < im->start || ti > im->end) continue;
1639 X0 = xtr(im,ti);
1640 gfx_new_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1642 }
1644 /* paint the major grid */
1645 for(ti = find_first_time(im->start,
1646 im->xlab_user.mgridtm,
1647 im->xlab_user.mgridst);
1648 ti < im->end;
1649 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1650 ){
1651 /* are we inside the graph ? */
1652 if (ti < im->start || ti > im->end) continue;
1653 X0 = xtr(im,ti);
1654 gfx_new_line(im->canvas,X0,Y0+2, X0,Y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1656 }
1657 /* paint the labels below the graph */
1658 for(ti = find_first_time(im->start,
1659 im->xlab_user.labtm,
1660 im->xlab_user.labst);
1661 ti <= im->end;
1662 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1663 ){
1664 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1665 /* are we inside the graph ? */
1666 if (ti < im->start || ti > im->end) continue;
1668 #if HAVE_STRFTIME
1669 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1670 #else
1671 # error "your libc has no strftime I guess we'll abort the exercise here."
1672 #endif
1673 gfx_new_text ( im->canvas,
1674 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1675 im->graph_col[GRC_FONT],
1676 im->text_prop[TEXT_PROP_AXIS].font,
1677 im->text_prop[TEXT_PROP_AXIS].size,
1678 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1679 graph_label );
1681 }
1683 }
1686 void
1687 axis_paint(
1688 image_desc_t *im
1689 )
1690 {
1691 /* draw x and y axis */
1692 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1693 im->xorigin+im->xsize,im->yorigin-im->ysize,
1694 GRIDWIDTH, im->graph_col[GRC_GRID]);
1696 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1697 im->xorigin+im->xsize,im->yorigin-im->ysize,
1698 GRIDWIDTH, im->graph_col[GRC_GRID]);
1700 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1701 im->xorigin+im->xsize+4,im->yorigin,
1702 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1704 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1705 im->xorigin,im->yorigin-im->ysize-4,
1706 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1709 /* arrow for X axis direction */
1710 gfx_new_area ( im->canvas,
1711 im->xorigin+im->xsize+3, im->yorigin-3,
1712 im->xorigin+im->xsize+3, im->yorigin+4,
1713 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1714 im->graph_col[GRC_ARROW]);
1718 }
1720 void
1721 grid_paint(image_desc_t *im)
1722 {
1723 long i;
1724 int res=0;
1725 double X0,Y0; /* points for filled graph and more*/
1726 gfx_node_t *node;
1728 /* draw 3d border */
1729 node = gfx_new_area (im->canvas, 0,im->yimg,
1730 2,im->yimg-2,
1731 2,2,im->graph_col[GRC_SHADEA]);
1732 gfx_add_point( node , im->ximg - 2, 2 );
1733 gfx_add_point( node , im->ximg, 0 );
1734 gfx_add_point( node , 0,0 );
1735 /* gfx_add_point( node , 0,im->yimg ); */
1737 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1738 im->ximg-2,im->yimg-2,
1739 im->ximg - 2, 2,
1740 im->graph_col[GRC_SHADEB]);
1741 gfx_add_point( node , im->ximg,0);
1742 gfx_add_point( node , im->ximg,im->yimg);
1743 gfx_add_point( node , 0,im->yimg);
1744 /* gfx_add_point( node , 0,im->yimg ); */
1747 if (im->draw_x_grid == 1 )
1748 vertical_grid(im);
1750 if (im->draw_y_grid == 1){
1751 if(im->logarithmic){
1752 res = horizontal_log_grid(im);
1753 } else {
1754 res = horizontal_grid(im);
1755 }
1757 /* dont draw horizontal grid if there is no min and max val */
1758 if (! res ) {
1759 char *nodata = "No Data found";
1760 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1761 im->graph_col[GRC_FONT],
1762 im->text_prop[TEXT_PROP_AXIS].font,
1763 im->text_prop[TEXT_PROP_AXIS].size,
1764 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1765 nodata );
1766 }
1767 }
1769 /* yaxis description */
1770 if (im->canvas->imgformat != IF_PNG) {
1771 gfx_new_text( im->canvas,
1772 7, (im->yorigin - im->ysize/2),
1773 im->graph_col[GRC_FONT],
1774 im->text_prop[TEXT_PROP_AXIS].font,
1775 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1776 GFX_H_CENTER, GFX_V_CENTER,
1777 im->ylegend);
1778 } else {
1779 /* horrible hack until we can actually print vertically */
1780 {
1781 int n;
1782 int l=strlen(im->ylegend);
1783 char s[2];
1784 for (n=0;n<strlen(im->ylegend);n++) {
1785 s[0]=im->ylegend[n];
1786 s[1]='\0';
1787 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1788 im->graph_col[GRC_FONT],
1789 im->text_prop[TEXT_PROP_AXIS].font,
1790 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1791 GFX_H_CENTER, GFX_V_CENTER,
1792 s);
1793 }
1794 }
1795 }
1797 /* graph title */
1798 gfx_new_text( im->canvas,
1799 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1800 im->graph_col[GRC_FONT],
1801 im->text_prop[TEXT_PROP_TITLE].font,
1802 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1803 GFX_H_CENTER, GFX_V_CENTER,
1804 im->title);
1806 /* graph labels */
1807 if( !(im->extra_flags & NOLEGEND) ) {
1808 for(i=0;i<im->gdes_c;i++){
1809 if(im->gdes[i].legend[0] =='\0')
1810 continue;
1812 /* im->gdes[i].leg_y is the bottom of the legend */
1813 X0 = im->gdes[i].leg_x;
1814 Y0 = im->gdes[i].leg_y;
1815 /* Box needed? */
1816 if ( im->gdes[i].gf != GF_GPRINT
1817 && im->gdes[i].gf != GF_COMMENT) {
1818 int boxH, boxV;
1820 boxH = gfx_get_text_width(im->canvas, 0,
1821 im->text_prop[TEXT_PROP_AXIS].font,
1822 im->text_prop[TEXT_PROP_AXIS].size,
1823 im->tabwidth,"M") * 1.25;
1824 boxV = boxH;
1826 node = gfx_new_area(im->canvas,
1827 X0,Y0-boxV,
1828 X0,Y0,
1829 X0+boxH,Y0,
1830 im->gdes[i].col);
1831 gfx_add_point ( node, X0+boxH, Y0-boxV );
1832 node = gfx_new_line(im->canvas,
1833 X0,Y0-boxV, X0,Y0,
1834 1,0x000000FF);
1835 gfx_add_point(node,X0+boxH,Y0);
1836 gfx_add_point(node,X0+boxH,Y0-boxV);
1837 gfx_close_path(node);
1838 X0 += boxH / 1.25 * 2;
1839 }
1840 gfx_new_text ( im->canvas, X0, Y0,
1841 im->graph_col[GRC_FONT],
1842 im->text_prop[TEXT_PROP_AXIS].font,
1843 im->text_prop[TEXT_PROP_AXIS].size,
1844 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1845 im->gdes[i].legend );
1846 }
1847 }
1848 }
1851 /*****************************************************
1852 * lazy check make sure we rely need to create this graph
1853 *****************************************************/
1855 int lazy_check(image_desc_t *im){
1856 FILE *fd = NULL;
1857 int size = 1;
1858 struct stat imgstat;
1860 if (im->lazy == 0) return 0; /* no lazy option */
1861 if (stat(im->graphfile,&imgstat) != 0)
1862 return 0; /* can't stat */
1863 /* one pixel in the existing graph is more then what we would
1864 change here ... */
1865 if (time(NULL) - imgstat.st_mtime >
1866 (im->end - im->start) / im->xsize)
1867 return 0;
1868 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1869 return 0; /* the file does not exist */
1870 switch (im->canvas->imgformat) {
1871 case IF_PNG:
1872 size = PngSize(fd,&(im->ximg),&(im->yimg));
1873 break;
1874 default:
1875 size = 1;
1876 }
1877 fclose(fd);
1878 return size;
1879 }
1881 void
1882 pie_part(image_desc_t *im, gfx_color_t color,
1883 double PieCenterX, double PieCenterY, double Radius,
1884 double startangle, double endangle)
1885 {
1886 gfx_node_t *node;
1887 double angle;
1888 double step=M_PI/50; /* Number of iterations for the circle;
1889 ** 10 is definitely too low, more than
1890 ** 50 seems to be overkill
1891 */
1893 /* Strange but true: we have to work clockwise or else
1894 ** anti aliasing nor transparency don't work.
1895 **
1896 ** This test is here to make sure we do it right, also
1897 ** this makes the for...next loop more easy to implement.
1898 ** The return will occur if the user enters a negative number
1899 ** (which shouldn't be done according to the specs) or if the
1900 ** programmers do something wrong (which, as we all know, never
1901 ** happens anyway :)
1902 */
1903 if (endangle<startangle) return;
1905 /* Hidden feature: Radius decreases each full circle */
1906 angle=startangle;
1907 while (angle>=2*M_PI) {
1908 angle -= 2*M_PI;
1909 Radius *= 0.8;
1910 }
1912 node=gfx_new_area(im->canvas,
1913 PieCenterX+sin(startangle)*Radius,
1914 PieCenterY-cos(startangle)*Radius,
1915 PieCenterX,
1916 PieCenterY,
1917 PieCenterX+sin(endangle)*Radius,
1918 PieCenterY-cos(endangle)*Radius,
1919 color);
1920 for (angle=endangle;angle-startangle>=step;angle-=step) {
1921 gfx_add_point(node,
1922 PieCenterX+sin(angle)*Radius,
1923 PieCenterY-cos(angle)*Radius );
1924 }
1925 }
1927 int
1928 graph_size_location(image_desc_t *im, int elements, int piechart )
1929 {
1930 /* The actual size of the image to draw is determined from
1931 ** several sources. The size given on the command line is
1932 ** the graph area but we need more as we have to draw labels
1933 ** and other things outside the graph area
1934 */
1936 /* +-+-------------------------------------------+
1937 ** |l|.................title.....................|
1938 ** |e+--+-------------------------------+--------+
1939 ** |b| b| | |
1940 ** |a| a| | pie |
1941 ** |l| l| main graph area | chart |
1942 ** |.| .| | area |
1943 ** |t| y| | |
1944 ** |r+--+-------------------------------+--------+
1945 ** |e| | x-axis labels | |
1946 ** |v+--+-------------------------------+--------+
1947 ** | |..............legends......................|
1948 ** +-+-------------------------------------------+
1949 */
1950 int Xvertical=0, Yvertical=0,
1951 Xtitle =0, Ytitle =0,
1952 Xylabel =0, Yylabel =0,
1953 Xmain =0, Ymain =0,
1954 Xpie =0, Ypie =0,
1955 Xxlabel =0, Yxlabel =0,
1956 #if 0
1957 Xlegend =0, Ylegend =0,
1958 #endif
1959 Xspacing =10, Yspacing =10;
1961 if (im->ylegend[0] != '\0') {
1962 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
1963 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
1964 }
1966 if (im->title[0] != '\0') {
1967 /* The title is placed "inbetween" two text lines so it
1968 ** automatically has some vertical spacing. The horizontal
1969 ** spacing is added here, on each side.
1970 */
1971 Xtitle = gfx_get_text_width(im->canvas, 0,
1972 im->text_prop[TEXT_PROP_TITLE].font,
1973 im->text_prop[TEXT_PROP_TITLE].size,
1974 im->tabwidth,
1975 im->title) + 2*Xspacing;
1976 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
1977 }
1979 if (elements) {
1980 Xmain=im->xsize;
1981 Ymain=im->ysize;
1982 if (im->draw_x_grid) {
1983 Xxlabel=Xmain;
1984 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
1985 }
1986 if (im->draw_y_grid) {
1987 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
1988 Yylabel=Ymain;
1989 }
1990 }
1992 if (piechart) {
1993 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
1994 Xpie=im->piesize;
1995 Ypie=im->piesize;
1996 }
1998 /* Now calculate the total size. Insert some spacing where
1999 desired. im->xorigin and im->yorigin need to correspond
2000 with the lower left corner of the main graph area or, if
2001 this one is not set, the imaginary box surrounding the
2002 pie chart area. */
2004 /* The legend width cannot yet be determined, as a result we
2005 ** have problems adjusting the image to it. For now, we just
2006 ** forget about it at all; the legend will have to fit in the
2007 ** size already allocated.
2008 */
2009 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2010 if (Xmain) im->ximg += Xspacing;
2011 if (Xpie) im->ximg += Xspacing;
2012 im->xorigin = Xspacing + Xylabel;
2013 if (Xtitle > im->ximg) im->ximg = Xtitle;
2014 if (Xvertical) {
2015 im->ximg += Xvertical;
2016 im->xorigin += Xvertical;
2017 }
2018 xtr(im,0);
2020 /* The vertical size is interesting... we need to compare
2021 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2022 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2023 ** start even thinking about Ylegend.
2024 **
2025 ** Do it in three portions: First calculate the inner part,
2026 ** then do the legend, then adjust the total height of the img.
2027 */
2029 /* reserve space for main and/or pie */
2030 im->yimg = Ymain + Yxlabel;
2031 if (im->yimg < Ypie) im->yimg = Ypie;
2032 im->yorigin = im->yimg - Yxlabel;
2033 /* reserve space for the title *or* some padding above the graph */
2034 if (Ytitle) {
2035 im->yimg += Ytitle;
2036 im->yorigin += Ytitle;
2037 } else {
2038 im->yimg += Yspacing;
2039 im->yorigin += Yspacing;
2040 }
2041 /* reserve space for padding below the graph */
2042 im->yimg += Yspacing;
2043 ytr(im,DNAN);
2045 /* Determine where to place the legends onto the image.
2046 ** Adjust im->yimg to match the space requirements.
2047 */
2048 if(leg_place(im)==-1)
2049 return -1;
2051 /* last of three steps: check total height of image */
2052 if (im->yimg < Yvertical) im->yimg = Yvertical;
2054 #if 0
2055 if (Xlegend > im->ximg) {
2056 im->ximg = Xlegend;
2057 /* reposition Pie */
2058 #endif
2060 /* The pie is placed in the upper right hand corner,
2061 ** just below the title (if any) and with sufficient
2062 ** padding.
2063 */
2064 if (elements) {
2065 im->pie_x = im->ximg - Xspacing - Xpie/2;
2066 im->pie_y = im->yorigin-Ymain+Ypie/2;
2067 } else {
2068 im->pie_x = im->ximg/2;
2069 im->pie_y = im->yorigin-Ypie/2;
2070 }
2072 return 0;
2073 }
2075 /* draw that picture thing ... */
2076 int
2077 graph_paint(image_desc_t *im, char ***calcpr)
2078 {
2079 int i,ii;
2080 int lazy = lazy_check(im);
2081 int piechart = 0;
2082 double PieStart=0.0;
2083 FILE *fo;
2084 gfx_node_t *node;
2086 double areazero = 0.0;
2087 enum gf_en stack_gf = GF_PRINT;
2088 graph_desc_t *lastgdes = NULL;
2090 /* if we are lazy and there is nothing to PRINT ... quit now */
2091 if (lazy && im->prt_c==0) return 0;
2093 /* pull the data from the rrd files ... */
2095 if(data_fetch(im)==-1)
2096 return -1;
2098 /* evaluate VDEF and CDEF operations ... */
2099 if(data_calc(im)==-1)
2100 return -1;
2102 /* check if we need to draw a piechart */
2103 for(i=0;i<im->gdes_c;i++){
2104 if (im->gdes[i].gf == GF_PART) {
2105 piechart=1;
2106 break;
2107 }
2108 }
2110 /* calculate and PRINT and GPRINT definitions. We have to do it at
2111 * this point because it will affect the length of the legends
2112 * if there are no graph elements we stop here ...
2113 * if we are lazy, try to quit ...
2114 */
2115 i=print_calc(im,calcpr);
2116 if(i<0) return -1;
2117 if(((i==0)&&(piechart==0)) || lazy) return 0;
2119 /* If there's only the pie chart to draw, signal this */
2120 if (i==0) piechart=2;
2122 /* get actual drawing data and find min and max values*/
2123 if(data_proc(im)==-1)
2124 return -1;
2126 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2128 if(!im->rigid && ! im->logarithmic)
2129 expand_range(im); /* make sure the upper and lower limit are
2130 sensible values */
2132 /**************************************************************
2133 *** Calculating sizes and locations became a bit confusing ***
2134 *** so I moved this into a separate function. ***
2135 **************************************************************/
2136 if(graph_size_location(im,i,piechart)==-1)
2137 return -1;
2139 /* the actual graph is created by going through the individual
2140 graph elements and then drawing them */
2142 node=gfx_new_area ( im->canvas,
2143 0, 0,
2144 im->ximg, 0,
2145 im->ximg, im->yimg,
2146 im->graph_col[GRC_BACK]);
2148 gfx_add_point(node,0, im->yimg);
2150 if (piechart != 2) {
2151 node=gfx_new_area ( im->canvas,
2152 im->xorigin, im->yorigin,
2153 im->xorigin + im->xsize, im->yorigin,
2154 im->xorigin + im->xsize, im->yorigin-im->ysize,
2155 im->graph_col[GRC_CANVAS]);
2157 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2159 if (im->minval > 0.0)
2160 areazero = im->minval;
2161 if (im->maxval < 0.0)
2162 areazero = im->maxval;
2164 axis_paint(im);
2165 }
2167 if (piechart) {
2168 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2169 }
2171 for(i=0;i<im->gdes_c;i++){
2172 switch(im->gdes[i].gf){
2173 case GF_CDEF:
2174 case GF_VDEF:
2175 case GF_DEF:
2176 case GF_PRINT:
2177 case GF_GPRINT:
2178 case GF_COMMENT:
2179 case GF_HRULE:
2180 case GF_VRULE:
2181 break;
2182 case GF_TICK:
2183 for (ii = 0; ii < im->xsize; ii++)
2184 {
2185 if (!isnan(im->gdes[i].p_data[ii]) &&
2186 im->gdes[i].p_data[ii] > 0.0)
2187 {
2188 /* generate a tick */
2189 gfx_new_line(im->canvas, im -> xorigin + ii,
2190 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2191 im -> xorigin + ii,
2192 im -> yorigin,
2193 1.0,
2194 im -> gdes[i].col );
2195 }
2196 }
2197 break;
2198 case GF_LINE:
2199 case GF_AREA:
2200 stack_gf = im->gdes[i].gf;
2201 case GF_STACK:
2202 /* fix data points at oo and -oo */
2203 for(ii=0;ii<im->xsize;ii++){
2204 if (isinf(im->gdes[i].p_data[ii])){
2205 if (im->gdes[i].p_data[ii] > 0) {
2206 im->gdes[i].p_data[ii] = im->maxval ;
2207 } else {
2208 im->gdes[i].p_data[ii] = im->minval ;
2209 }
2211 }
2212 } /* for */
2214 if (im->gdes[i].col != 0x0){
2215 /* GF_LINE and friend */
2216 if(stack_gf == GF_LINE ){
2217 node = NULL;
2218 for(ii=1;ii<im->xsize;ii++){
2219 if ( ! isnan(im->gdes[i].p_data[ii-1])
2220 && ! isnan(im->gdes[i].p_data[ii])){
2221 if (node == NULL){
2222 node = gfx_new_line(im->canvas,
2223 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2224 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2225 im->gdes[i].linewidth,
2226 im->gdes[i].col);
2227 } else {
2228 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2229 }
2230 } else {
2231 node = NULL;
2232 }
2233 }
2234 } else {
2235 int area_start=-1;
2236 node = NULL;
2237 for(ii=1;ii<im->xsize;ii++){
2238 /* open an area */
2239 if ( ! isnan(im->gdes[i].p_data[ii-1])
2240 && ! isnan(im->gdes[i].p_data[ii])){
2241 if (node == NULL){
2242 float ybase = 0.0;
2243 if (im->gdes[i].gf == GF_STACK) {
2244 ybase = ytr(im,lastgdes->p_data[ii-1]);
2245 } else {
2246 ybase = ytr(im,areazero);
2247 }
2248 area_start = ii-1;
2249 node = gfx_new_area(im->canvas,
2250 ii-1+im->xorigin,ybase,
2251 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2252 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2253 im->gdes[i].col
2254 );
2255 } else {
2256 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2257 }
2258 }
2260 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2261 /* GF_AREA STACK type*/
2262 if (im->gdes[i].gf == GF_STACK ) {
2263 int iii;
2264 for (iii=ii-1;iii>area_start;iii--){
2265 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2266 }
2267 } else {
2268 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2269 };
2270 node=NULL;
2271 };
2272 }
2273 } /* else GF_LINE */
2274 } /* if color != 0x0 */
2275 /* make sure we do not run into trouble when stacking on NaN */
2276 for(ii=0;ii<im->xsize;ii++){
2277 if (isnan(im->gdes[i].p_data[ii])) {
2278 double ybase = 0.0;
2279 if (lastgdes) {
2280 ybase = ytr(im,lastgdes->p_data[ii-1]);
2281 };
2282 if (isnan(ybase) || !lastgdes ){
2283 ybase = ytr(im,areazero);
2284 }
2285 im->gdes[i].p_data[ii] = ybase;
2286 }
2287 }
2288 lastgdes = &(im->gdes[i]);
2289 break;
2290 case GF_PART:
2291 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2292 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2294 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2295 pie_part(im,im->gdes[i].col,
2296 im->pie_x,im->pie_y,im->piesize*0.4,
2297 M_PI*2.0*PieStart/100.0,
2298 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2299 PieStart += im->gdes[i].yrule;
2300 }
2301 break;
2302 } /* switch */
2303 }
2304 if (piechart==2) {
2305 im->draw_x_grid=0;
2306 im->draw_y_grid=0;
2307 }
2308 /* grid_paint also does the text */
2309 grid_paint(im);
2311 /* the RULES are the last thing to paint ... */
2312 for(i=0;i<im->gdes_c;i++){
2314 switch(im->gdes[i].gf){
2315 case GF_HRULE:
2316 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2317 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2318 };
2319 if(im->gdes[i].yrule >= im->minval
2320 && im->gdes[i].yrule <= im->maxval)
2321 gfx_new_line(im->canvas,
2322 im->xorigin,ytr(im,im->gdes[i].yrule),
2323 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2324 1.0,im->gdes[i].col);
2325 break;
2326 case GF_VRULE:
2327 if(im->gdes[i].xrule == 0) { /* fetch variable */
2328 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2329 };
2330 if(im->gdes[i].xrule >= im->start
2331 && im->gdes[i].xrule <= im->end)
2332 gfx_new_line(im->canvas,
2333 xtr(im,im->gdes[i].xrule),im->yorigin,
2334 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2335 1.0,im->gdes[i].col);
2336 break;
2337 default:
2338 break;
2339 }
2340 }
2343 if (strcmp(im->graphfile,"-")==0) {
2344 #ifdef WIN32
2345 /* Change translation mode for stdout to BINARY */
2346 _setmode( _fileno( stdout ), O_BINARY );
2347 #endif
2348 fo = stdout;
2349 } else {
2350 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2351 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2352 strerror(errno));
2353 return (-1);
2354 }
2355 }
2356 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2357 if (strcmp(im->graphfile,"-") != 0)
2358 fclose(fo);
2359 return 0;
2360 }
2363 /*****************************************************
2364 * graph stuff
2365 *****************************************************/
2367 int
2368 gdes_alloc(image_desc_t *im){
2370 long def_step = (im->end-im->start)/im->xsize;
2372 if (im->step > def_step) /* step can be increassed ... no decreassed */
2373 def_step = im->step;
2375 im->gdes_c++;
2377 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2378 * sizeof(graph_desc_t)))==NULL){
2379 rrd_set_error("realloc graph_descs");
2380 return -1;
2381 }
2384 im->gdes[im->gdes_c-1].step=def_step;
2385 im->gdes[im->gdes_c-1].start=im->start;
2386 im->gdes[im->gdes_c-1].end=im->end;
2387 im->gdes[im->gdes_c-1].vname[0]='\0';
2388 im->gdes[im->gdes_c-1].data=NULL;
2389 im->gdes[im->gdes_c-1].ds_namv=NULL;
2390 im->gdes[im->gdes_c-1].data_first=0;
2391 im->gdes[im->gdes_c-1].p_data=NULL;
2392 im->gdes[im->gdes_c-1].rpnp=NULL;
2393 im->gdes[im->gdes_c-1].col = 0x0;
2394 im->gdes[im->gdes_c-1].legend[0]='\0';
2395 im->gdes[im->gdes_c-1].rrd[0]='\0';
2396 im->gdes[im->gdes_c-1].ds=-1;
2397 im->gdes[im->gdes_c-1].p_data=NULL;
2398 return 0;
2399 }
2401 /* copies input untill the first unescaped colon is found
2402 or until input ends. backslashes have to be escaped as well */
2403 int
2404 scan_for_col(char *input, int len, char *output)
2405 {
2406 int inp,outp=0;
2407 for (inp=0;
2408 inp < len &&
2409 input[inp] != ':' &&
2410 input[inp] != '\0';
2411 inp++){
2412 if (input[inp] == '\\' &&
2413 input[inp+1] != '\0' &&
2414 (input[inp+1] == '\\' ||
2415 input[inp+1] == ':')){
2416 output[outp++] = input[++inp];
2417 }
2418 else {
2419 output[outp++] = input[inp];
2420 }
2421 }
2422 output[outp] = '\0';
2423 return inp;
2424 }
2426 /* Some surgery done on this function, it became ridiculously big.
2427 ** Things moved:
2428 ** - initializing now in rrd_graph_init()
2429 ** - options parsing now in rrd_graph_options()
2430 ** - script parsing now in rrd_graph_script()
2431 */
2432 int
2433 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2434 {
2435 image_desc_t im;
2437 rrd_graph_init(&im);
2439 rrd_graph_options(argc,argv,&im);
2440 if (rrd_test_error()) return -1;
2442 if (strlen(argv[optind])>=MAXPATH) {
2443 rrd_set_error("filename (including path) too long");
2444 return -1;
2445 }
2446 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2447 im.graphfile[MAXPATH-1]='\0';
2449 rrd_graph_script(argc,argv,&im);
2450 if (rrd_test_error()) return -1;
2452 /* Everything is now read and the actual work can start */
2454 (*prdata)=NULL;
2455 if (graph_paint(&im,prdata)==-1){
2456 im_free(&im);
2457 return -1;
2458 }
2460 /* The image is generated and needs to be output.
2461 ** Also, if needed, print a line with information about the image.
2462 */
2464 *xsize=im.ximg;
2465 *ysize=im.yimg;
2466 if (im.imginfo) {
2467 char *filename;
2468 if (!(*prdata)) {
2469 /* maybe prdata is not allocated yet ... lets do it now */
2470 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2471 rrd_set_error("malloc imginfo");
2472 return -1;
2473 };
2474 }
2475 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2476 ==NULL){
2477 rrd_set_error("malloc imginfo");
2478 return -1;
2479 }
2480 filename=im.graphfile+strlen(im.graphfile);
2481 while(filename > im.graphfile) {
2482 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2483 filename--;
2484 }
2486 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2487 }
2488 im_free(&im);
2489 return 0;
2490 }
2492 void
2493 rrd_graph_init(image_desc_t *im)
2494 {
2495 int i;
2497 im->xlab_user.minsec = -1;
2498 im->ximg=0;
2499 im->yimg=0;
2500 im->xsize = 400;
2501 im->ysize = 100;
2502 im->step = 0;
2503 im->ylegend[0] = '\0';
2504 im->title[0] = '\0';
2505 im->minval = DNAN;
2506 im->maxval = DNAN;
2507 im->unitsexponent= 9999;
2508 im->extra_flags= 0;
2509 im->rigid = 0;
2510 im->imginfo = NULL;
2511 im->lazy = 0;
2512 im->logarithmic = 0;
2513 im->ygridstep = DNAN;
2514 im->draw_x_grid = 1;
2515 im->draw_y_grid = 1;
2516 im->base = 1000;
2517 im->prt_c = 0;
2518 im->gdes_c = 0;
2519 im->gdes = NULL;
2520 im->canvas = gfx_new_canvas();
2522 for(i=0;i<DIM(graph_col);i++)
2523 im->graph_col[i]=graph_col[i];
2525 for(i=0;i<DIM(text_prop);i++){
2526 im->text_prop[i].size = text_prop[i].size;
2527 im->text_prop[i].font = text_prop[i].font;
2528 }
2529 }
2531 void
2532 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2533 {
2534 int stroff;
2535 char *parsetime_error = NULL;
2536 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2537 time_t start_tmp=0,end_tmp=0;
2538 long long_tmp;
2539 struct time_value start_tv, end_tv;
2540 gfx_color_t color;
2542 parsetime("end-24h", &start_tv);
2543 parsetime("now", &end_tv);
2545 while (1){
2546 static struct option long_options[] =
2547 {
2548 {"start", required_argument, 0, 's'},
2549 {"end", required_argument, 0, 'e'},
2550 {"x-grid", required_argument, 0, 'x'},
2551 {"y-grid", required_argument, 0, 'y'},
2552 {"vertical-label",required_argument,0,'v'},
2553 {"width", required_argument, 0, 'w'},
2554 {"height", required_argument, 0, 'h'},
2555 {"interlaced", no_argument, 0, 'i'},
2556 {"upper-limit",required_argument, 0, 'u'},
2557 {"lower-limit",required_argument, 0, 'l'},
2558 {"rigid", no_argument, 0, 'r'},
2559 {"base", required_argument, 0, 'b'},
2560 {"logarithmic",no_argument, 0, 'o'},
2561 {"color", required_argument, 0, 'c'},
2562 {"font", required_argument, 0, 'n'},
2563 {"title", required_argument, 0, 't'},
2564 {"imginfo", required_argument, 0, 'f'},
2565 {"imgformat", required_argument, 0, 'a'},
2566 {"lazy", no_argument, 0, 'z'},
2567 {"zoom", required_argument, 0, 'm'},
2568 {"no-legend", no_argument, 0, 'g'},
2569 {"alt-y-grid", no_argument, 0, 257 },
2570 {"alt-autoscale", no_argument, 0, 258 },
2571 {"alt-autoscale-max", no_argument, 0, 259 },
2572 {"units-exponent",required_argument, 0, 260},
2573 {"step", required_argument, 0, 261},
2574 {0,0,0,0}};
2575 int option_index = 0;
2576 int opt;
2579 opt = getopt_long(argc, argv,
2580 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2581 long_options, &option_index);
2583 if (opt == EOF)
2584 break;
2586 switch(opt) {
2587 case 257:
2588 im->extra_flags |= ALTYGRID;
2589 break;
2590 case 258:
2591 im->extra_flags |= ALTAUTOSCALE;
2592 break;
2593 case 259:
2594 im->extra_flags |= ALTAUTOSCALE_MAX;
2595 break;
2596 case 'g':
2597 im->extra_flags |= NOLEGEND;
2598 break;
2599 case 260:
2600 im->unitsexponent = atoi(optarg);
2601 break;
2602 case 261:
2603 im->step = atoi(optarg);
2604 break;
2605 case 's':
2606 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2607 rrd_set_error( "start time: %s", parsetime_error );
2608 return;
2609 }
2610 break;
2611 case 'e':
2612 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2613 rrd_set_error( "end time: %s", parsetime_error );
2614 return;
2615 }
2616 break;
2617 case 'x':
2618 if(strcmp(optarg,"none") == 0){
2619 im->draw_x_grid=0;
2620 break;
2621 };
2623 if(sscanf(optarg,
2624 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2625 scan_gtm,
2626 &im->xlab_user.gridst,
2627 scan_mtm,
2628 &im->xlab_user.mgridst,
2629 scan_ltm,
2630 &im->xlab_user.labst,
2631 &im->xlab_user.precis,
2632 &stroff) == 7 && stroff != 0){
2633 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2634 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2635 rrd_set_error("unknown keyword %s",scan_gtm);
2636 return;
2637 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2638 rrd_set_error("unknown keyword %s",scan_mtm);
2639 return;
2640 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2641 rrd_set_error("unknown keyword %s",scan_ltm);
2642 return;
2643 }
2644 im->xlab_user.minsec = 1;
2645 im->xlab_user.stst = im->xlab_form;
2646 } else {
2647 rrd_set_error("invalid x-grid format");
2648 return;
2649 }
2650 break;
2651 case 'y':
2653 if(strcmp(optarg,"none") == 0){
2654 im->draw_y_grid=0;
2655 break;
2656 };
2658 if(sscanf(optarg,
2659 "%lf:%d",
2660 &im->ygridstep,
2661 &im->ylabfact) == 2) {
2662 if(im->ygridstep<=0){
2663 rrd_set_error("grid step must be > 0");
2664 return;
2665 } else if (im->ylabfact < 1){
2666 rrd_set_error("label factor must be > 0");
2667 return;
2668 }
2669 } else {
2670 rrd_set_error("invalid y-grid format");
2671 return;
2672 }
2673 break;
2674 case 'v':
2675 strncpy(im->ylegend,optarg,150);
2676 im->ylegend[150]='\0';
2677 break;
2678 case 'u':
2679 im->maxval = atof(optarg);
2680 break;
2681 case 'l':
2682 im->minval = atof(optarg);
2683 break;
2684 case 'b':
2685 im->base = atol(optarg);
2686 if(im->base != 1024 && im->base != 1000 ){
2687 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2688 return;
2689 }
2690 break;
2691 case 'w':
2692 long_tmp = atol(optarg);
2693 if (long_tmp < 10) {
2694 rrd_set_error("width below 10 pixels");
2695 return;
2696 }
2697 im->xsize = long_tmp;
2698 break;
2699 case 'h':
2700 long_tmp = atol(optarg);
2701 if (long_tmp < 10) {
2702 rrd_set_error("height below 10 pixels");
2703 return;
2704 }
2705 im->ysize = long_tmp;
2706 break;
2707 case 'i':
2708 im->canvas->interlaced = 1;
2709 break;
2710 case 'r':
2711 im->rigid = 1;
2712 break;
2713 case 'f':
2714 im->imginfo = optarg;
2715 break;
2716 case 'a':
2717 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2718 rrd_set_error("unsupported graphics format '%s'",optarg);
2719 return;
2720 }
2721 break;
2722 case 'z':
2723 im->lazy = 1;
2724 break;
2725 case 'o':
2726 im->logarithmic = 1;
2727 if (isnan(im->minval))
2728 im->minval=1;
2729 break;
2730 case 'c':
2731 if(sscanf(optarg,
2732 "%10[A-Z]#%8lx",
2733 col_nam,&color) == 2){
2734 int ci;
2735 if((ci=grc_conv(col_nam)) != -1){
2736 im->graph_col[ci]=color;
2737 } else {
2738 rrd_set_error("invalid color name '%s'",col_nam);
2739 }
2740 } else {
2741 rrd_set_error("invalid color def format");
2742 return;
2743 }
2744 break;
2745 case 'n':{
2746 /* originally this used char *prop = "" and
2747 ** char *font = "dummy" however this results
2748 ** in a SEG fault, at least on RH7.1
2749 **
2750 ** The current implementation isn't proper
2751 ** either, font is never freed and prop uses
2752 ** a fixed width string
2753 */
2754 char prop[100];
2755 double size = 1;
2756 char *font;
2758 font=malloc(255);
2759 if(sscanf(optarg,
2760 "%10[A-Z]:%lf:%s",
2761 prop,&size,font) == 3){
2762 int sindex;
2763 if((sindex=text_prop_conv(prop)) != -1){
2764 im->text_prop[sindex].size=size;
2765 im->text_prop[sindex].font=font;
2766 if (sindex==0) { /* the default */
2767 im->text_prop[TEXT_PROP_TITLE].size=size;
2768 im->text_prop[TEXT_PROP_TITLE].font=font;
2769 im->text_prop[TEXT_PROP_AXIS].size=size;
2770 im->text_prop[TEXT_PROP_AXIS].font=font;
2771 im->text_prop[TEXT_PROP_UNIT].size=size;
2772 im->text_prop[TEXT_PROP_UNIT].font=font;
2773 im->text_prop[TEXT_PROP_LEGEND].size=size;
2774 im->text_prop[TEXT_PROP_LEGEND].font=font;
2775 }
2776 } else {
2777 rrd_set_error("invalid fonttag '%s'",prop);
2778 return;
2779 }
2780 } else {
2781 rrd_set_error("invalid text property format");
2782 return;
2783 }
2784 break;
2785 }
2786 case 'm':
2787 im->canvas->zoom = atof(optarg);
2788 if (im->canvas->zoom <= 0.0) {
2789 rrd_set_error("zoom factor must be > 0");
2790 return;
2791 }
2792 break;
2793 case 't':
2794 strncpy(im->title,optarg,150);
2795 im->title[150]='\0';
2796 break;
2798 case '?':
2799 if (optopt != 0)
2800 rrd_set_error("unknown option '%c'", optopt);
2801 else
2802 rrd_set_error("unknown option '%s'",argv[optind-1]);
2803 return;
2804 }
2805 }
2807 if (optind >= argc) {
2808 rrd_set_error("missing filename");
2809 return;
2810 }
2812 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2813 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2814 return;
2815 }
2817 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2818 /* error string is set in parsetime.c */
2819 return;
2820 }
2822 if (start_tmp < 3600*24*365*10){
2823 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2824 return;
2825 }
2827 if (end_tmp < start_tmp) {
2828 rrd_set_error("start (%ld) should be less than end (%ld)",
2829 start_tmp, end_tmp);
2830 return;
2831 }
2833 im->start = start_tmp;
2834 im->end = end_tmp;
2835 }
2837 void
2838 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2839 {
2840 int i;
2841 char symname[100];
2842 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2844 for (i=optind+1;i<argc;i++) {
2845 int argstart=0;
2846 int strstart=0;
2847 graph_desc_t *gdp;
2848 char *line;
2849 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2850 double d;
2851 double linewidth;
2852 int j,k,l,m;
2854 /* Each command is one element from *argv[], we call this "line".
2855 **
2856 ** Each command defines the most current gdes inside struct im.
2857 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2858 */
2859 gdes_alloc(im);
2860 gdp=&im->gdes[im->gdes_c-1];
2861 line=argv[i];
2863 /* function:newvname=string[:ds-name:CF] for xDEF
2864 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2865 ** function:vname#color[:num[:string]] for TICK
2866 ** function:vname-or-num#color[:string] for xRULE,PART
2867 ** function:vname:CF:string for xPRINT
2868 ** function:string for COMMENT
2869 */
2870 argstart=0;
2872 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2873 if (argstart==0) {
2874 rrd_set_error("Cannot parse function in line: %s",line);
2875 im_free(im);
2876 return;
2877 }
2878 if(sscanf(funcname,"LINE%lf",&linewidth)){
2879 im->gdes[im->gdes_c-1].gf = GF_LINE;
2880 im->gdes[im->gdes_c-1].linewidth = linewidth;
2881 } else {
2882 if ((gdp->gf=gf_conv(funcname))==-1) {
2883 rrd_set_error("'%s' is not a valid function name",funcname);
2884 im_free(im);
2885 return;
2886 }
2887 }
2889 /* If the error string is set, we exit at the end of the switch */
2890 switch (gdp->gf) {
2891 case GF_COMMENT:
2892 if (rrd_graph_legend(gdp,&line[argstart])==0)
2893 rrd_set_error("Cannot parse comment in line: %s",line);
2894 break;
2895 case GF_PART:
2896 case GF_VRULE:
2897 case GF_HRULE:
2898 j=k=l=m=0;
2899 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2900 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2901 if (k+m==0) {
2902 rrd_set_error("Cannot parse name or num in line: %s",line);
2903 break;
2904 }
2905 if (j!=0) {
2906 gdp->xrule=d;
2907 gdp->yrule=d;
2908 argstart+=j;
2909 } else if (!rrd_graph_check_vname(im,vname,line)) {
2910 gdp->xrule=0;
2911 gdp->yrule=DNAN;
2912 argstart+=l;
2913 } else break; /* exit due to wrong vname */
2914 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2915 argstart+=j;
2916 if (strlen(&line[argstart])!=0) {
2917 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2918 rrd_set_error("Cannot parse comment in line: %s",line);
2919 }
2920 break;
2921 case GF_STACK:
2922 if (linepass==0) {
2923 rrd_set_error("STACK must follow another graphing element");
2924 break;
2925 }
2926 case GF_LINE:
2927 case GF_AREA:
2928 case GF_TICK:
2929 j=k=0;
2930 linepass=1;
2931 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2932 if (j+1!=k)
2933 rrd_set_error("Cannot parse vname in line: %s",line);
2934 else if (rrd_graph_check_vname(im,vname,line))
2935 rrd_set_error("Undefined vname '%s' in line: %s",line);
2936 else
2937 k=rrd_graph_color(im,&line[argstart],line,1);
2938 if (rrd_test_error()) break;
2939 argstart=argstart+j+k;
2940 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2941 j=0;
2942 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2943 argstart+=j;
2944 }
2945 if (strlen(&line[argstart])!=0)
2946 if (rrd_graph_legend(gdp,&line[++argstart])==0)
2947 rrd_set_error("Cannot parse legend in line: %s",line);
2948 break;
2949 case GF_PRINT:
2950 im->prt_c++;
2951 case GF_GPRINT:
2952 j=0;
2953 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2954 if (j==0) {
2955 rrd_set_error("Cannot parse vname in line: '%s'",line);
2956 break;
2957 }
2958 argstart+=j;
2959 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2960 j=0;
2961 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2963 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2964 #define VIDX im->gdes[gdp->vidx]
2965 switch (k) {
2966 case -1: /* looks CF but is not really CF */
2967 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2968 break;
2969 case 0: /* CF present and correct */
2970 if (VIDX.gf == GF_VDEF)
2971 rrd_set_error("Don't use CF when printing VDEF");
2972 argstart+=j;
2973 break;
2974 case 1: /* CF not present */
2975 if (VIDX.gf == GF_VDEF) rrd_clear_error();
2976 else rrd_set_error("Printing DEF or CDEF needs CF");
2977 break;
2978 default:
2979 rrd_set_error("Oops, bug in GPRINT scanning");
2980 }
2981 #undef VIDX
2982 if (rrd_test_error()) break;
2984 if (strlen(&line[argstart])!=0) {
2985 if (rrd_graph_legend(gdp,&line[argstart])==0)
2986 rrd_set_error("Cannot parse legend in line: %s",line);
2987 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2988 strcpy(gdp->format, gdp->legend);
2989 break;
2990 case GF_DEF:
2991 case GF_VDEF:
2992 case GF_CDEF:
2993 j=0;
2994 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2995 if (j==0) {
2996 rrd_set_error("Could not parse line: %s",line);
2997 break;
2998 }
2999 if (find_var(im,gdp->vname)!=-1) {
3000 rrd_set_error("Variable '%s' in line '%s' already in use\n",
3001 gdp->vname,line);
3002 break;
3003 }
3004 argstart+=j;
3005 switch (gdp->gf) {
3006 case GF_DEF:
3007 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
3008 j=k=0;
3009 sscanf(&line[argstart],
3010 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
3011 gdp->ds_nam, symname, &j, &k);
3012 if ((j==0)||(k!=0)) {
3013 rrd_set_error("Cannot parse DS or CF in '%s'",line);
3014 break;
3015 }
3016 rrd_graph_check_CF(im,symname,line);
3017 break;
3018 case GF_VDEF:
3019 j=0;
3020 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
3021 if (j==0) {
3022 rrd_set_error("Cannot parse vname in line '%s'",line);
3023 break;
3024 }
3025 argstart+=j;
3026 if (rrd_graph_check_vname(im,vname,line)) return;
3027 if ( im->gdes[gdp->vidx].gf != GF_DEF
3028 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3029 rrd_set_error("variable '%s' not DEF nor "
3030 "CDEF in VDEF '%s'", vname,gdp->vname);
3031 break;
3032 }
3033 vdef_parse(gdp,&line[argstart+strstart]);
3034 break;
3035 case GF_CDEF:
3036 if (strstr(&line[argstart],":")!=NULL) {
3037 rrd_set_error("Error in RPN, line: %s",line);
3038 break;
3039 }
3040 if ((gdp->rpnp = rpn_parse(
3041 (void *)im,
3042 &line[argstart],
3043 &find_var_wrapper)
3044 )==NULL)
3045 rrd_set_error("invalid rpn expression in: %s",line);
3046 break;
3047 default: break;
3048 }
3049 break;
3050 default: rrd_set_error("Big oops");
3051 }
3052 if (rrd_test_error()) {
3053 im_free(im);
3054 return;
3055 }
3056 }
3058 if (im->gdes_c==0){
3059 rrd_set_error("can't make a graph without contents");
3060 im_free(im); /* ??? is this set ??? */
3061 return;
3062 }
3063 }
3064 int
3065 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3066 {
3067 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3068 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3069 return -1;
3070 }
3071 return 0;
3072 }
3073 int
3074 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3075 {
3076 char *color;
3077 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3079 color=strstr(var,"#");
3080 if (color==NULL) {
3081 if (optional==0) {
3082 rrd_set_error("Found no color in %s",err);
3083 return 0;
3084 }
3085 return 0;
3086 } else {
3087 int n=0;
3088 char *rest;
3089 gfx_color_t col;
3091 rest=strstr(color,":");
3092 if (rest!=NULL)
3093 n=rest-color;
3094 else
3095 n=strlen(color);
3097 switch (n) {
3098 case 7:
3099 sscanf(color,"#%6lx%n",&col,&n);
3100 col = (col << 8) + 0xff /* shift left by 8 */;
3101 if (n!=7) rrd_set_error("Color problem in %s",err);
3102 break;
3103 case 9:
3104 sscanf(color,"#%8lx%n",&col,&n);
3105 if (n==9) break;
3106 default:
3107 rrd_set_error("Color problem in %s",err);
3108 }
3109 if (rrd_test_error()) return 0;
3110 gdp->col = col;
3111 return n;
3112 }
3113 }
3114 int
3115 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3116 {
3117 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3118 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3119 return -1;
3120 }
3121 return 0;
3122 }
3123 int
3124 rrd_graph_legend(graph_desc_t *gdp, char *line)
3125 {
3126 int i;
3128 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3130 return (strlen(&line[i])==0);
3131 }
3134 int bad_format(char *fmt) {
3135 char *ptr;
3136 int n=0;
3138 ptr = fmt;
3139 while (*ptr != '\0') {
3140 if (*ptr == '%') {ptr++;
3141 if (*ptr == '\0') return 1;
3142 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3143 ptr++;
3144 }
3145 if (*ptr == '\0') return 1;
3146 if (*ptr == 'l') {
3147 ptr++;
3148 n++;
3149 if (*ptr == '\0') return 1;
3150 if (*ptr == 'e' || *ptr == 'f') {
3151 ptr++;
3152 } else { return 1; }
3153 }
3154 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3155 else { return 1; }
3156 } else {
3157 ++ptr;
3158 }
3159 }
3160 return (n!=1);
3161 }
3162 int
3163 vdef_parse(gdes,str)
3164 struct graph_desc_t *gdes;
3165 char *str;
3166 {
3167 /* A VDEF currently is either "func" or "param,func"
3168 * so the parsing is rather simple. Change if needed.
3169 */
3170 double param;
3171 char func[30];
3172 int n;
3174 n=0;
3175 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3176 if (n==strlen(str)) { /* matched */
3177 ;
3178 } else {
3179 n=0;
3180 sscanf(str,"%29[A-Z]%n",func,&n);
3181 if (n==strlen(str)) { /* matched */
3182 param=DNAN;
3183 } else {
3184 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3185 ,str
3186 ,gdes->vname
3187 );
3188 return -1;
3189 }
3190 }
3191 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3192 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3193 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3194 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3195 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3196 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3197 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3198 else {
3199 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3200 ,func
3201 ,gdes->vname
3202 );
3203 return -1;
3204 };
3206 switch (gdes->vf.op) {
3207 case VDEF_PERCENT:
3208 if (isnan(param)) { /* no parameter given */
3209 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3210 ,func
3211 ,gdes->vname
3212 );
3213 return -1;
3214 };
3215 if (param>=0.0 && param<=100.0) {
3216 gdes->vf.param = param;
3217 gdes->vf.val = DNAN; /* undefined */
3218 gdes->vf.when = 0; /* undefined */
3219 } else {
3220 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3221 ,param
3222 ,gdes->vname
3223 );
3224 return -1;
3225 };
3226 break;
3227 case VDEF_MAXIMUM:
3228 case VDEF_AVERAGE:
3229 case VDEF_MINIMUM:
3230 case VDEF_TOTAL:
3231 case VDEF_FIRST:
3232 case VDEF_LAST:
3233 if (isnan(param)) {
3234 gdes->vf.param = DNAN;
3235 gdes->vf.val = DNAN;
3236 gdes->vf.when = 0;
3237 } else {
3238 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3239 ,func
3240 ,gdes->vname
3241 );
3242 return -1;
3243 };
3244 break;
3245 };
3246 return 0;
3247 }
3248 int
3249 vdef_calc(im,gdi)
3250 image_desc_t *im;
3251 int gdi;
3252 {
3253 graph_desc_t *src,*dst;
3254 rrd_value_t *data;
3255 long step,steps;
3257 dst = &im->gdes[gdi];
3258 src = &im->gdes[dst->vidx];
3259 data = src->data + src->ds;
3260 steps = (src->end - src->start) / src->step;
3262 #if 0
3263 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3264 ,src->start
3265 ,src->end
3266 ,steps
3267 );
3268 #endif
3270 switch (dst->vf.op) {
3271 case VDEF_PERCENT: {
3272 rrd_value_t * array;
3273 int field;
3276 if ((array = malloc(steps*sizeof(double)))==NULL) {
3277 rrd_set_error("malloc VDEV_PERCENT");
3278 return -1;
3279 }
3280 for (step=0;step < steps; step++) {
3281 array[step]=data[step*src->ds_cnt];
3282 }
3283 qsort(array,step,sizeof(double),vdef_percent_compar);
3285 field = (steps-1)*dst->vf.param/100;
3286 dst->vf.val = array[field];
3287 dst->vf.when = 0; /* no time component */
3288 #if 0
3289 for(step=0;step<steps;step++)
3290 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3291 #endif
3292 }
3293 break;
3294 case VDEF_MAXIMUM:
3295 step=0;
3296 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3297 if (step == steps) {
3298 dst->vf.val = DNAN;
3299 dst->vf.when = 0;
3300 } else {
3301 dst->vf.val = data[step*src->ds_cnt];
3302 dst->vf.when = src->start + (step+1)*src->step;
3303 }
3304 while (step != steps) {
3305 if (finite(data[step*src->ds_cnt])) {
3306 if (data[step*src->ds_cnt] > dst->vf.val) {
3307 dst->vf.val = data[step*src->ds_cnt];
3308 dst->vf.when = src->start + (step+1)*src->step;
3309 }
3310 }
3311 step++;
3312 }
3313 break;
3314 case VDEF_TOTAL:
3315 case VDEF_AVERAGE: {
3316 int cnt=0;
3317 double sum=0.0;
3318 for (step=0;step<steps;step++) {
3319 if (finite(data[step*src->ds_cnt])) {
3320 sum += data[step*src->ds_cnt];
3321 cnt ++;
3322 };
3323 }
3324 if (cnt) {
3325 if (dst->vf.op == VDEF_TOTAL) {
3326 dst->vf.val = sum*src->step;
3327 dst->vf.when = cnt*src->step; /* not really "when" */
3328 } else {
3329 dst->vf.val = sum/cnt;
3330 dst->vf.when = 0; /* no time component */
3331 };
3332 } else {
3333 dst->vf.val = DNAN;
3334 dst->vf.when = 0;
3335 }
3336 }
3337 break;
3338 case VDEF_MINIMUM:
3339 step=0;
3340 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3341 if (step == steps) {
3342 dst->vf.val = DNAN;
3343 dst->vf.when = 0;
3344 } else {
3345 dst->vf.val = data[step*src->ds_cnt];
3346 dst->vf.when = src->start + (step+1)*src->step;
3347 }
3348 while (step != steps) {
3349 if (finite(data[step*src->ds_cnt])) {
3350 if (data[step*src->ds_cnt] < dst->vf.val) {
3351 dst->vf.val = data[step*src->ds_cnt];
3352 dst->vf.when = src->start + (step+1)*src->step;
3353 }
3354 }
3355 step++;
3356 }
3357 break;
3358 case VDEF_FIRST:
3359 /* The time value returned here is one step before the
3360 * actual time value. This is the start of the first
3361 * non-NaN interval.
3362 */
3363 step=0;
3364 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3365 if (step == steps) { /* all entries were NaN */
3366 dst->vf.val = DNAN;
3367 dst->vf.when = 0;
3368 } else {
3369 dst->vf.val = data[step*src->ds_cnt];
3370 dst->vf.when = src->start + step*src->step;
3371 }
3372 break;
3373 case VDEF_LAST:
3374 /* The time value returned here is the
3375 * actual time value. This is the end of the last
3376 * non-NaN interval.
3377 */
3378 step=steps-1;
3379 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3380 if (step < 0) { /* all entries were NaN */
3381 dst->vf.val = DNAN;
3382 dst->vf.when = 0;
3383 } else {
3384 dst->vf.val = data[step*src->ds_cnt];
3385 dst->vf.when = src->start + (step+1)*src->step;
3386 }
3387 break;
3388 }
3389 return 0;
3390 }
3392 /* NaN < -INF < finite_values < INF */
3393 int
3394 vdef_percent_compar(a,b)
3395 const void *a,*b;
3396 {
3397 /* Equality is not returned; this doesn't hurt except
3398 * (maybe) for a little performance.
3399 */
3401 /* First catch NaN values. They are smallest */
3402 if (isnan( *(double *)a )) return -1;
3403 if (isnan( *(double *)b )) return 1;
3405 /* NaN doesn't reach this part so INF and -INF are extremes.
3406 * The sign from isinf() is compatible with the sign we return
3407 */
3408 if (isinf( *(double *)a )) return isinf( *(double *)a );
3409 if (isinf( *(double *)b )) return isinf( *(double *)b );
3411 /* If we reach this, both values must be finite */
3412 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3413 }