1 /****************************************************************************
2 * RRDtool 1.2.27 Copyright by Tobi Oetiker, 1997-2008
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
8 #include <sys/stat.h>
10 #ifdef WIN32
11 #include "strftime.h"
12 #endif
13 #include "rrd_tool.h"
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
16 #include <io.h>
17 #include <fcntl.h>
18 #endif
20 #ifdef HAVE_TIME_H
21 #include <time.h>
22 #endif
24 #ifdef HAVE_LOCALE_H
25 #include <locale.h>
26 #endif
28 #include "rrd_graph.h"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
37 #endif
39 text_prop_t text_prop[] = {
40 { 8.0, RRD_DEFAULT_FONT }, /* default */
41 { 9.0, RRD_DEFAULT_FONT }, /* title */
42 { 7.0, RRD_DEFAULT_FONT }, /* axis */
43 { 8.0, RRD_DEFAULT_FONT }, /* unit */
44 { 8.0, RRD_DEFAULT_FONT } /* legend */
45 };
47 xlab_t xlab[] = {
48 {0, 0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
49 {2, 0, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {5, 0, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
51 {10, 0, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
52 {30, 0, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
53 {60, 0, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
54 {60, 24*3600, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,6, 0,"%a %H:%M"},
55 {180, 0, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
56 {180, 24*3600, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,12, 0,"%a %H:%M"},
57 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
58 {600, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
59 {1200, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%d"},
60 {1800, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a %d"},
61 {2400, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
62 {3600, 0, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
63 {3*3600, 0, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
64 {6*3600, 0, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
65 {48*3600, 0, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
66 {315360, 0, TMT_MONTH,3, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%Y"},
67 {10*24*3600, 0, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
68 {-1,0,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
69 };
71 /* sensible y label intervals ...*/
73 ylab_t ylab[]= {
74 {0.1, {1,2, 5,10}},
75 {0.2, {1,5,10,20}},
76 {0.5, {1,2, 4,10}},
77 {1.0, {1,2, 5,10}},
78 {2.0, {1,5,10,20}},
79 {5.0, {1,2, 4,10}},
80 {10.0, {1,2, 5,10}},
81 {20.0, {1,5,10,20}},
82 {50.0, {1,2, 4,10}},
83 {100.0, {1,2, 5,10}},
84 {200.0, {1,5,10,20}},
85 {500.0, {1,2, 4,10}},
86 {0.0, {0,0,0,0}}};
89 gfx_color_t graph_col[] = /* default colors */
90 { 0xFFFFFFFF, /* canvas */
91 0xF0F0F0FF, /* background */
92 0xD0D0D0FF, /* shade A */
93 0xA0A0A0FF, /* shade B */
94 0x90909080, /* grid */
95 0xE0505080, /* major grid */
96 0x000000FF, /* font */
97 0x802020FF, /* arrow */
98 0x202020FF, /* axis */
99 0x000000FF /* frame */
100 };
103 /* #define DEBUG */
105 #ifdef DEBUG
106 # define DPRINT(x) (void)(printf x, printf("\n"))
107 #else
108 # define DPRINT(x)
109 #endif
112 /* initialize with xtr(im,0); */
113 int
114 xtr(image_desc_t *im,time_t mytime){
115 static double pixie;
116 if (mytime==0){
117 pixie = (double) im->xsize / (double)(im->end - im->start);
118 return im->xorigin;
119 }
120 return (int)((double)im->xorigin
121 + pixie * ( mytime - im->start ) );
122 }
124 /* translate data values into y coordinates */
125 double
126 ytr(image_desc_t *im, double value){
127 static double pixie;
128 double yval;
129 if (isnan(value)){
130 if(!im->logarithmic)
131 pixie = (double) im->ysize / (im->maxval - im->minval);
132 else
133 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
134 yval = im->yorigin;
135 } else if(!im->logarithmic) {
136 yval = im->yorigin - pixie * (value - im->minval);
137 } else {
138 if (value < im->minval) {
139 yval = im->yorigin;
140 } else {
141 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
142 }
143 }
144 /* make sure we don't return anything too unreasonable. GD lib can
145 get terribly slow when drawing lines outside its scope. This is
146 especially problematic in connection with the rigid option */
147 if (! im->rigid) {
148 /* keep yval as-is */
149 } else if (yval > im->yorigin) {
150 yval = im->yorigin +0.00001;
151 } else if (yval < im->yorigin - im->ysize){
152 yval = im->yorigin - im->ysize - 0.00001;
153 }
154 return yval;
155 }
159 /* conversion function for symbolic entry names */
162 #define conv_if(VV,VVV) \
163 if (strcmp(#VV, string) == 0) return VVV ;
165 enum gf_en gf_conv(char *string){
167 conv_if(PRINT,GF_PRINT)
168 conv_if(GPRINT,GF_GPRINT)
169 conv_if(COMMENT,GF_COMMENT)
170 conv_if(HRULE,GF_HRULE)
171 conv_if(VRULE,GF_VRULE)
172 conv_if(LINE,GF_LINE)
173 conv_if(AREA,GF_AREA)
174 conv_if(STACK,GF_STACK)
175 conv_if(TICK,GF_TICK)
176 conv_if(DEF,GF_DEF)
177 conv_if(CDEF,GF_CDEF)
178 conv_if(VDEF,GF_VDEF)
179 #ifdef WITH_PIECHART
180 conv_if(PART,GF_PART)
181 #endif
182 conv_if(XPORT,GF_XPORT)
183 conv_if(SHIFT,GF_SHIFT)
185 return (-1);
186 }
188 enum gfx_if_en if_conv(char *string){
190 conv_if(PNG,IF_PNG)
191 conv_if(SVG,IF_SVG)
192 conv_if(EPS,IF_EPS)
193 conv_if(PDF,IF_PDF)
195 return (-1);
196 }
198 enum tmt_en tmt_conv(char *string){
200 conv_if(SECOND,TMT_SECOND)
201 conv_if(MINUTE,TMT_MINUTE)
202 conv_if(HOUR,TMT_HOUR)
203 conv_if(DAY,TMT_DAY)
204 conv_if(WEEK,TMT_WEEK)
205 conv_if(MONTH,TMT_MONTH)
206 conv_if(YEAR,TMT_YEAR)
207 return (-1);
208 }
210 enum grc_en grc_conv(char *string){
212 conv_if(BACK,GRC_BACK)
213 conv_if(CANVAS,GRC_CANVAS)
214 conv_if(SHADEA,GRC_SHADEA)
215 conv_if(SHADEB,GRC_SHADEB)
216 conv_if(GRID,GRC_GRID)
217 conv_if(MGRID,GRC_MGRID)
218 conv_if(FONT,GRC_FONT)
219 conv_if(ARROW,GRC_ARROW)
220 conv_if(AXIS,GRC_AXIS)
221 conv_if(FRAME,GRC_FRAME)
223 return -1;
224 }
226 enum text_prop_en text_prop_conv(char *string){
228 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
229 conv_if(TITLE,TEXT_PROP_TITLE)
230 conv_if(AXIS,TEXT_PROP_AXIS)
231 conv_if(UNIT,TEXT_PROP_UNIT)
232 conv_if(LEGEND,TEXT_PROP_LEGEND)
233 return -1;
234 }
237 #undef conv_if
239 int
240 im_free(image_desc_t *im)
241 {
242 unsigned long i,ii;
244 if (im == NULL) return 0;
245 for(i=0;i<(unsigned)im->gdes_c;i++){
246 if (im->gdes[i].data_first){
247 /* careful here, because a single pointer can occur several times */
248 free (im->gdes[i].data);
249 if (im->gdes[i].ds_namv){
250 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
251 free(im->gdes[i].ds_namv[ii]);
252 free(im->gdes[i].ds_namv);
253 }
254 }
255 free (im->gdes[i].p_data);
256 free (im->gdes[i].rpnp);
257 }
258 free(im->gdes);
259 gfx_destroy(im->canvas);
260 return 0;
261 }
263 /* find SI magnitude symbol for the given number*/
264 void
265 auto_scale(
266 image_desc_t *im, /* image description */
267 double *value,
268 char **symb_ptr,
269 double *magfact
270 )
271 {
273 char *symbol[] = {"a", /* 10e-18 Atto */
274 "f", /* 10e-15 Femto */
275 "p", /* 10e-12 Pico */
276 "n", /* 10e-9 Nano */
277 "u", /* 10e-6 Micro */
278 "m", /* 10e-3 Milli */
279 " ", /* Base */
280 "k", /* 10e3 Kilo */
281 "M", /* 10e6 Mega */
282 "G", /* 10e9 Giga */
283 "T", /* 10e12 Tera */
284 "P", /* 10e15 Peta */
285 "E"};/* 10e18 Exa */
287 int symbcenter = 6;
288 int sindex;
290 if (*value == 0.0 || isnan(*value) ) {
291 sindex = 0;
292 *magfact = 1.0;
293 } else {
294 sindex = floor(log(fabs(*value))/log((double)im->base));
295 *magfact = pow((double)im->base, (double)sindex);
296 (*value) /= (*magfact);
297 }
298 if ( sindex <= symbcenter && sindex >= -symbcenter) {
299 (*symb_ptr) = symbol[sindex+symbcenter];
300 }
301 else {
302 (*symb_ptr) = "?";
303 }
304 }
307 static char si_symbol[] = {
308 'a', /* 10e-18 Atto */
309 'f', /* 10e-15 Femto */
310 'p', /* 10e-12 Pico */
311 'n', /* 10e-9 Nano */
312 'u', /* 10e-6 Micro */
313 'm', /* 10e-3 Milli */
314 ' ', /* Base */
315 'k', /* 10e3 Kilo */
316 'M', /* 10e6 Mega */
317 'G', /* 10e9 Giga */
318 'T', /* 10e12 Tera */
319 'P', /* 10e15 Peta */
320 'E', /* 10e18 Exa */
321 };
322 static const int si_symbcenter = 6;
324 /* find SI magnitude symbol for the numbers on the y-axis*/
325 void
326 si_unit(
327 image_desc_t *im /* image description */
328 )
329 {
331 double digits,viewdigits=0;
333 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
335 if (im->unitsexponent != 9999) {
336 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
337 viewdigits = floor(im->unitsexponent / 3);
338 } else {
339 viewdigits = digits;
340 }
342 im->magfact = pow((double)im->base , digits);
344 #ifdef DEBUG
345 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
346 #endif
348 im->viewfactor = im->magfact / pow((double)im->base , viewdigits);
350 if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) &&
351 ((viewdigits+si_symbcenter) >= 0) )
352 im->symbol = si_symbol[(int)viewdigits+si_symbcenter];
353 else
354 im->symbol = '?';
355 }
357 /* move min and max values around to become sensible */
359 void
360 expand_range(image_desc_t *im)
361 {
362 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
363 600.0,500.0,400.0,300.0,250.0,
364 200.0,125.0,100.0,90.0,80.0,
365 75.0,70.0,60.0,50.0,40.0,30.0,
366 25.0,20.0,10.0,9.0,8.0,
367 7.0,6.0,5.0,4.0,3.5,3.0,
368 2.5,2.0,1.8,1.5,1.2,1.0,
369 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
371 double scaled_min,scaled_max;
372 double adj;
373 int i;
377 #ifdef DEBUG
378 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
379 im->minval,im->maxval,im->magfact);
380 #endif
382 if (isnan(im->ygridstep)){
383 if(im->extra_flags & ALTAUTOSCALE) {
384 /* measure the amplitude of the function. Make sure that
385 graph boundaries are slightly higher then max/min vals
386 so we can see amplitude on the graph */
387 double delt, fact;
389 delt = im->maxval - im->minval;
390 adj = delt * 0.1;
391 fact = 2.0 * pow(10.0,
392 floor(log10(max(fabs(im->minval), fabs(im->maxval))/im->magfact)) - 2);
393 if (delt < fact) {
394 adj = (fact - delt) * 0.55;
395 #ifdef DEBUG
396 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
397 #endif
398 }
399 im->minval -= adj;
400 im->maxval += adj;
401 }
402 else if(im->extra_flags & ALTAUTOSCALE_MIN) {
403 /* measure the amplitude of the function. Make sure that
404 graph boundaries are slightly lower than min vals
405 so we can see amplitude on the graph */
406 adj = (im->maxval - im->minval) * 0.1;
407 im->minval -= adj;
408 }
409 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
410 /* measure the amplitude of the function. Make sure that
411 graph boundaries are slightly higher than max vals
412 so we can see amplitude on the graph */
413 adj = (im->maxval - im->minval) * 0.1;
414 im->maxval += adj;
415 }
416 else {
417 scaled_min = im->minval / im->magfact;
418 scaled_max = im->maxval / im->magfact;
420 for (i=1; sensiblevalues[i] > 0; i++){
421 if (sensiblevalues[i-1]>=scaled_min &&
422 sensiblevalues[i]<=scaled_min)
423 im->minval = sensiblevalues[i]*(im->magfact);
425 if (-sensiblevalues[i-1]<=scaled_min &&
426 -sensiblevalues[i]>=scaled_min)
427 im->minval = -sensiblevalues[i-1]*(im->magfact);
429 if (sensiblevalues[i-1] >= scaled_max &&
430 sensiblevalues[i] <= scaled_max)
431 im->maxval = sensiblevalues[i-1]*(im->magfact);
433 if (-sensiblevalues[i-1]<=scaled_max &&
434 -sensiblevalues[i] >=scaled_max)
435 im->maxval = -sensiblevalues[i]*(im->magfact);
436 }
437 }
438 } else {
439 /* adjust min and max to the grid definition if there is one */
440 im->minval = (double)im->ylabfact * im->ygridstep *
441 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
442 im->maxval = (double)im->ylabfact * im->ygridstep *
443 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
444 }
446 #ifdef DEBUG
447 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
448 im->minval,im->maxval,im->magfact);
449 #endif
450 }
452 void
453 apply_gridfit(image_desc_t *im)
454 {
455 if (isnan(im->minval) || isnan(im->maxval))
456 return;
457 ytr(im,DNAN);
458 if (im->logarithmic) {
459 double ya, yb, ypix, ypixfrac;
460 double log10_range = log10(im->maxval) - log10(im->minval);
461 ya = pow((double)10, floor(log10(im->minval)));
462 while (ya < im->minval)
463 ya *= 10;
464 if (ya > im->maxval)
465 return; /* don't have y=10^x gridline */
466 yb = ya * 10;
467 if (yb <= im->maxval) {
468 /* we have at least 2 y=10^x gridlines.
469 Make sure distance between them in pixels
470 are an integer by expanding im->maxval */
471 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
472 double factor = y_pixel_delta / floor(y_pixel_delta);
473 double new_log10_range = factor * log10_range;
474 double new_ymax_log10 = log10(im->minval) + new_log10_range;
475 im->maxval = pow(10, new_ymax_log10);
476 ytr(im,DNAN); /* reset precalc */
477 log10_range = log10(im->maxval) - log10(im->minval);
478 }
479 /* make sure first y=10^x gridline is located on
480 integer pixel position by moving scale slightly
481 downwards (sub-pixel movement) */
482 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
483 ypixfrac = ypix - floor(ypix);
484 if (ypixfrac > 0 && ypixfrac < 1) {
485 double yfrac = ypixfrac / im->ysize;
486 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
487 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
488 ytr(im,DNAN); /* reset precalc */
489 }
490 } else {
491 /* Make sure we have an integer pixel distance between
492 each minor gridline */
493 double ypos1 = ytr(im, im->minval);
494 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
495 double y_pixel_delta = ypos1 - ypos2;
496 double factor = y_pixel_delta / floor(y_pixel_delta);
497 double new_range = factor * (im->maxval - im->minval);
498 double gridstep = im->ygrid_scale.gridstep;
499 double minor_y, minor_y_px, minor_y_px_frac;
502 if (im->maxval > 0.0)
503 im->maxval = im->minval + new_range;
504 else
505 im->minval = im->maxval - new_range;
506 ytr(im,DNAN); /* reset precalc */
508 /* make sure first minor gridline is on integer pixel y coord */
509 minor_y = gridstep * floor(im->minval / gridstep);
510 while (minor_y < im->minval)
511 minor_y += gridstep;
512 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
513 minor_y_px_frac = minor_y_px - floor(minor_y_px);
514 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
515 double yfrac = minor_y_px_frac / im->ysize;
516 double range = im->maxval - im->minval;
517 im->minval = im->minval - yfrac * range;
518 im->maxval = im->maxval - yfrac * range;
519 ytr(im,DNAN); /* reset precalc */
520 }
521 calc_horizontal_grid(im); /* recalc with changed im->maxval */
522 }
523 }
525 /* reduce data reimplementation by Alex */
527 void
528 reduce_data(
529 enum cf_en cf, /* which consolidation function ?*/
530 unsigned long cur_step, /* step the data currently is in */
531 time_t *start, /* start, end and step as requested ... */
532 time_t *end, /* ... by the application will be ... */
533 unsigned long *step, /* ... adjusted to represent reality */
534 unsigned long *ds_cnt, /* number of data sources in file */
535 rrd_value_t **data) /* two dimensional array containing the data */
536 {
537 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
538 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
539 rrd_value_t *srcptr,*dstptr;
541 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
542 dstptr = *data;
543 srcptr = *data;
544 row_cnt = ((*end)-(*start))/cur_step;
546 #ifdef DEBUG
547 #define DEBUG_REDUCE
548 #endif
549 #ifdef DEBUG_REDUCE
550 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
551 row_cnt,reduce_factor,*start,*end,cur_step);
552 for (col=0;col<row_cnt;col++) {
553 printf("time %10lu: ",*start+(col+1)*cur_step);
554 for (i=0;i<*ds_cnt;i++)
555 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
556 printf("\n");
557 }
558 #endif
560 /* We have to combine [reduce_factor] rows of the source
561 ** into one row for the destination. Doing this we also
562 ** need to take care to combine the correct rows. First
563 ** alter the start and end time so that they are multiples
564 ** of the new step time. We cannot reduce the amount of
565 ** time so we have to move the end towards the future and
566 ** the start towards the past.
567 */
568 end_offset = (*end) % (*step);
569 start_offset = (*start) % (*step);
571 /* If there is a start offset (which cannot be more than
572 ** one destination row), skip the appropriate number of
573 ** source rows and one destination row. The appropriate
574 ** number is what we do know (start_offset/cur_step) of
575 ** the new interval (*step/cur_step aka reduce_factor).
576 */
577 #ifdef DEBUG_REDUCE
578 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
579 printf("row_cnt before: %lu\n",row_cnt);
580 #endif
581 if (start_offset) {
582 (*start) = (*start)-start_offset;
583 skiprows=reduce_factor-start_offset/cur_step;
584 srcptr+=skiprows* *ds_cnt;
585 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
586 row_cnt-=skiprows;
587 }
588 #ifdef DEBUG_REDUCE
589 printf("row_cnt between: %lu\n",row_cnt);
590 #endif
592 /* At the end we have some rows that are not going to be
593 ** used, the amount is end_offset/cur_step
594 */
595 if (end_offset) {
596 (*end) = (*end)-end_offset+(*step);
597 skiprows = end_offset/cur_step;
598 row_cnt-=skiprows;
599 }
600 #ifdef DEBUG_REDUCE
601 printf("row_cnt after: %lu\n",row_cnt);
602 #endif
604 /* Sanity check: row_cnt should be multiple of reduce_factor */
605 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
607 if (row_cnt%reduce_factor) {
608 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
609 row_cnt,reduce_factor);
610 printf("BUG in reduce_data()\n");
611 exit(1);
612 }
614 /* Now combine reduce_factor intervals at a time
615 ** into one interval for the destination.
616 */
618 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
619 for (col=0;col<(*ds_cnt);col++) {
620 rrd_value_t newval=DNAN;
621 unsigned long validval=0;
623 for (i=0;i<reduce_factor;i++) {
624 if (isnan(srcptr[i*(*ds_cnt)+col])) {
625 continue;
626 }
627 validval++;
628 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
629 else {
630 switch (cf) {
631 case CF_HWPREDICT:
632 case CF_DEVSEASONAL:
633 case CF_DEVPREDICT:
634 case CF_SEASONAL:
635 case CF_AVERAGE:
636 newval += srcptr[i*(*ds_cnt)+col];
637 break;
638 case CF_MINIMUM:
639 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
640 break;
641 case CF_FAILURES:
642 /* an interval contains a failure if any subintervals contained a failure */
643 case CF_MAXIMUM:
644 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
645 break;
646 case CF_LAST:
647 newval = srcptr[i*(*ds_cnt)+col];
648 break;
649 }
650 }
651 }
652 if (validval == 0){newval = DNAN;} else{
653 switch (cf) {
654 case CF_HWPREDICT:
655 case CF_DEVSEASONAL:
656 case CF_DEVPREDICT:
657 case CF_SEASONAL:
658 case CF_AVERAGE:
659 newval /= validval;
660 break;
661 case CF_MINIMUM:
662 case CF_FAILURES:
663 case CF_MAXIMUM:
664 case CF_LAST:
665 break;
666 }
667 }
668 *dstptr++=newval;
669 }
670 srcptr+=(*ds_cnt)*reduce_factor;
671 row_cnt-=reduce_factor;
672 }
673 /* If we had to alter the endtime, we didn't have enough
674 ** source rows to fill the last row. Fill it with NaN.
675 */
676 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
677 #ifdef DEBUG_REDUCE
678 row_cnt = ((*end)-(*start))/ *step;
679 srcptr = *data;
680 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
681 row_cnt,*start,*end,*step);
682 for (col=0;col<row_cnt;col++) {
683 printf("time %10lu: ",*start+(col+1)*(*step));
684 for (i=0;i<*ds_cnt;i++)
685 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
686 printf("\n");
687 }
688 #endif
689 }
692 /* get the data required for the graphs from the
693 relevant rrds ... */
695 int
696 data_fetch(image_desc_t *im )
697 {
698 int i,ii;
699 int skip;
701 /* pull the data from the rrd files ... */
702 for (i=0;i< (int)im->gdes_c;i++){
703 /* only GF_DEF elements fetch data */
704 if (im->gdes[i].gf != GF_DEF)
705 continue;
707 skip=0;
708 /* do we have it already ?*/
709 for (ii=0;ii<i;ii++) {
710 if (im->gdes[ii].gf != GF_DEF)
711 continue;
712 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
713 && (im->gdes[i].cf == im->gdes[ii].cf)
714 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
715 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
716 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
717 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
718 /* OK, the data is already there.
719 ** Just copy the header portion
720 */
721 im->gdes[i].start = im->gdes[ii].start;
722 im->gdes[i].end = im->gdes[ii].end;
723 im->gdes[i].step = im->gdes[ii].step;
724 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
725 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
726 im->gdes[i].data = im->gdes[ii].data;
727 im->gdes[i].data_first = 0;
728 skip=1;
729 }
730 if (skip)
731 break;
732 }
733 if (! skip) {
734 unsigned long ft_step = im->gdes[i].step ; /* ft_step will record what we got from fetch */
736 if((rrd_fetch_fn(im->gdes[i].rrd,
737 im->gdes[i].cf,
738 &im->gdes[i].start,
739 &im->gdes[i].end,
740 &ft_step,
741 &im->gdes[i].ds_cnt,
742 &im->gdes[i].ds_namv,
743 &im->gdes[i].data)) == -1){
744 return -1;
745 }
746 im->gdes[i].data_first = 1;
748 if (ft_step < im->gdes[i].step) {
749 reduce_data(im->gdes[i].cf_reduce,
750 ft_step,
751 &im->gdes[i].start,
752 &im->gdes[i].end,
753 &im->gdes[i].step,
754 &im->gdes[i].ds_cnt,
755 &im->gdes[i].data);
756 } else {
757 im->gdes[i].step = ft_step;
758 }
759 }
761 /* lets see if the required data source is really there */
762 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
763 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
764 im->gdes[i].ds=ii; }
765 }
766 if (im->gdes[i].ds== -1){
767 rrd_set_error("No DS called '%s' in '%s'",
768 im->gdes[i].ds_nam,im->gdes[i].rrd);
769 return -1;
770 }
772 }
773 return 0;
774 }
776 /* evaluate the expressions in the CDEF functions */
778 /*************************************************************
779 * CDEF stuff
780 *************************************************************/
782 long
783 find_var_wrapper(void *arg1, char *key)
784 {
785 return find_var((image_desc_t *) arg1, key);
786 }
788 /* find gdes containing var*/
789 long
790 find_var(image_desc_t *im, char *key){
791 long ii;
792 for(ii=0;ii<im->gdes_c-1;ii++){
793 if((im->gdes[ii].gf == GF_DEF
794 || im->gdes[ii].gf == GF_VDEF
795 || im->gdes[ii].gf == GF_CDEF)
796 && (strcmp(im->gdes[ii].vname,key) == 0)){
797 return ii;
798 }
799 }
800 return -1;
801 }
803 /* find the largest common denominator for all the numbers
804 in the 0 terminated num array */
805 long
806 lcd(long *num){
807 long rest;
808 int i;
809 for (i=0;num[i+1]!=0;i++){
810 do {
811 rest=num[i] % num[i+1];
812 num[i]=num[i+1]; num[i+1]=rest;
813 } while (rest!=0);
814 num[i+1] = num[i];
815 }
816 /* return i==0?num[i]:num[i-1]; */
817 return num[i];
818 }
820 /* run the rpn calculator on all the VDEF and CDEF arguments */
821 int
822 data_calc( image_desc_t *im){
824 int gdi;
825 int dataidx;
826 long *steparray, rpi;
827 int stepcnt;
828 time_t now;
829 rpnstack_t rpnstack;
831 rpnstack_init(&rpnstack);
833 for (gdi=0;gdi<im->gdes_c;gdi++){
834 /* Look for GF_VDEF and GF_CDEF in the same loop,
835 * so CDEFs can use VDEFs and vice versa
836 */
837 switch (im->gdes[gdi].gf) {
838 case GF_XPORT:
839 break;
840 case GF_SHIFT: {
841 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
843 /* remove current shift */
844 vdp->start -= vdp->shift;
845 vdp->end -= vdp->shift;
847 /* vdef */
848 if (im->gdes[gdi].shidx >= 0)
849 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
850 /* constant */
851 else
852 vdp->shift = im->gdes[gdi].shval;
854 /* normalize shift to multiple of consolidated step */
855 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
857 /* apply shift */
858 vdp->start += vdp->shift;
859 vdp->end += vdp->shift;
860 break;
861 }
862 case GF_VDEF:
863 /* A VDEF has no DS. This also signals other parts
864 * of rrdtool that this is a VDEF value, not a CDEF.
865 */
866 im->gdes[gdi].ds_cnt = 0;
867 if (vdef_calc(im,gdi)) {
868 rrd_set_error("Error processing VDEF '%s'"
869 ,im->gdes[gdi].vname
870 );
871 rpnstack_free(&rpnstack);
872 return -1;
873 }
874 break;
875 case GF_CDEF:
876 im->gdes[gdi].ds_cnt = 1;
877 im->gdes[gdi].ds = 0;
878 im->gdes[gdi].data_first = 1;
879 im->gdes[gdi].start = 0;
880 im->gdes[gdi].end = 0;
881 steparray=NULL;
882 stepcnt = 0;
883 dataidx=-1;
885 /* Find the variables in the expression.
886 * - VDEF variables are substituted by their values
887 * and the opcode is changed into OP_NUMBER.
888 * - CDEF variables are analized for their step size,
889 * the lowest common denominator of all the step
890 * sizes of the data sources involved is calculated
891 * and the resulting number is the step size for the
892 * resulting data source.
893 */
894 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
895 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
896 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
897 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
898 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
899 #if 0
900 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
901 im->gdes[gdi].vname,
902 im->gdes[ptr].vname);
903 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
904 #endif
905 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
906 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
907 } else { /* normal variables and PREF(variables) */
909 /* add one entry to the array that keeps track of the step sizes of the
910 * data sources going into the CDEF. */
911 if ((steparray =
912 rrd_realloc(steparray,
913 (++stepcnt+1)*sizeof(*steparray)))==NULL){
914 rrd_set_error("realloc steparray");
915 rpnstack_free(&rpnstack);
916 return -1;
917 };
919 steparray[stepcnt-1] = im->gdes[ptr].step;
921 /* adjust start and end of cdef (gdi) so
922 * that it runs from the latest start point
923 * to the earliest endpoint of any of the
924 * rras involved (ptr)
925 */
927 if(im->gdes[gdi].start < im->gdes[ptr].start)
928 im->gdes[gdi].start = im->gdes[ptr].start;
930 if(im->gdes[gdi].end == 0 ||
931 im->gdes[gdi].end > im->gdes[ptr].end)
932 im->gdes[gdi].end = im->gdes[ptr].end;
934 /* store pointer to the first element of
935 * the rra providing data for variable,
936 * further save step size and data source
937 * count of this rra
938 */
939 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
940 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
941 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
943 /* backoff the *.data ptr; this is done so
944 * rpncalc() function doesn't have to treat
945 * the first case differently
946 */
947 } /* if ds_cnt != 0 */
948 } /* if OP_VARIABLE */
949 } /* loop through all rpi */
951 /* move the data pointers to the correct period */
952 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
953 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
954 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
955 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
956 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
958 if(diff > 0)
959 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
960 }
961 }
963 if(steparray == NULL){
964 rrd_set_error("rpn expressions without DEF"
965 " or CDEF variables are not supported");
966 rpnstack_free(&rpnstack);
967 return -1;
968 }
969 steparray[stepcnt]=0;
970 /* Now find the resulting step. All steps in all
971 * used RRAs have to be visited
972 */
973 im->gdes[gdi].step = lcd(steparray);
974 free(steparray);
975 if((im->gdes[gdi].data = malloc((
976 (im->gdes[gdi].end-im->gdes[gdi].start)
977 / im->gdes[gdi].step)
978 * sizeof(double)))==NULL){
979 rrd_set_error("malloc im->gdes[gdi].data");
980 rpnstack_free(&rpnstack);
981 return -1;
982 }
984 /* Step through the new cdef results array and
985 * calculate the values
986 */
987 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
988 now<=im->gdes[gdi].end;
989 now += im->gdes[gdi].step)
990 {
991 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
993 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
994 * in this case we are advancing by timesteps;
995 * we use the fact that time_t is a synonym for long
996 */
997 if (rpn_calc(rpnp,&rpnstack,(long) now,
998 im->gdes[gdi].data,++dataidx) == -1) {
999 /* rpn_calc sets the error string */
1000 rpnstack_free(&rpnstack);
1001 return -1;
1002 }
1003 } /* enumerate over time steps within a CDEF */
1004 break;
1005 default:
1006 continue;
1007 }
1008 } /* enumerate over CDEFs */
1009 rpnstack_free(&rpnstack);
1010 return 0;
1011 }
1013 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
1014 {
1016 int aInt = *(int*)&A;
1017 int bInt = *(int*)&B;
1018 int intDiff;
1019 /* Make sure maxUlps is non-negative and small enough that the
1020 default NAN won't compare as equal to anything. */
1022 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1024 /* Make aInt lexicographically ordered as a twos-complement int */
1026 if (aInt < 0)
1027 aInt = 0x80000000l - aInt;
1029 /* Make bInt lexicographically ordered as a twos-complement int */
1031 if (bInt < 0)
1032 bInt = 0x80000000l - bInt;
1034 intDiff = abs(aInt - bInt);
1036 if (intDiff <= maxUlps)
1037 return 1;
1039 return 0;
1040 }
1042 /* massage data so, that we get one value for each x coordinate in the graph */
1043 int
1044 data_proc( image_desc_t *im ){
1045 long i,ii;
1046 double pixstep = (double)(im->end-im->start)
1047 /(double)im->xsize; /* how much time
1048 passes in one pixel */
1049 double paintval;
1050 double minval=DNAN,maxval=DNAN;
1052 unsigned long gr_time;
1054 /* memory for the processed data */
1055 for(i=0;i<im->gdes_c;i++) {
1056 if((im->gdes[i].gf==GF_LINE) ||
1057 (im->gdes[i].gf==GF_AREA) ||
1058 (im->gdes[i].gf==GF_TICK)) {
1059 if((im->gdes[i].p_data = malloc((im->xsize +1)
1060 * sizeof(rrd_value_t)))==NULL){
1061 rrd_set_error("malloc data_proc");
1062 return -1;
1063 }
1064 }
1065 }
1067 for (i=0;i<im->xsize;i++) { /* for each pixel */
1068 long vidx;
1069 gr_time = im->start+pixstep*i; /* time of the current step */
1070 paintval=0.0;
1072 for (ii=0;ii<im->gdes_c;ii++) {
1073 double value;
1074 switch (im->gdes[ii].gf) {
1075 case GF_LINE:
1076 case GF_AREA:
1077 case GF_TICK:
1078 if (!im->gdes[ii].stack)
1079 paintval = 0.0;
1080 value = im->gdes[ii].yrule;
1081 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1082 /* The time of the data doesn't necessarily match
1083 ** the time of the graph. Beware.
1084 */
1085 vidx = im->gdes[ii].vidx;
1086 if (im->gdes[vidx].gf == GF_VDEF) {
1087 value = im->gdes[vidx].vf.val;
1088 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1089 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1090 value = im->gdes[vidx].data[
1091 (unsigned long) floor(
1092 (double)(gr_time - im->gdes[vidx].start)
1093 / im->gdes[vidx].step)
1094 * im->gdes[vidx].ds_cnt
1095 + im->gdes[vidx].ds
1096 ];
1097 } else {
1098 value = DNAN;
1099 }
1100 };
1102 if (! isnan(value)) {
1103 paintval += value;
1104 im->gdes[ii].p_data[i] = paintval;
1105 /* GF_TICK: the data values are not
1106 ** relevant for min and max
1107 */
1108 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1109 if ((isnan(minval) || paintval < minval ) &&
1110 ! (im->logarithmic && paintval <= 0.0))
1111 minval = paintval;
1112 if (isnan(maxval) || paintval > maxval)
1113 maxval = paintval;
1114 }
1115 } else {
1116 im->gdes[ii].p_data[i] = DNAN;
1117 }
1118 break;
1119 case GF_STACK:
1120 rrd_set_error("STACK should already be turned into LINE or AREA here");
1121 return -1;
1122 break;
1123 default:
1124 break;
1125 }
1126 }
1127 }
1129 /* if min or max have not been asigned a value this is because
1130 there was no data in the graph ... this is not good ...
1131 lets set these to dummy values then ... */
1133 if (im->logarithmic) {
1134 if (isnan(minval)) minval = 0.2;
1135 if (isnan(maxval)) maxval = 5.1;
1136 }
1137 else {
1138 if (isnan(minval)) minval = 0.0;
1139 if (isnan(maxval)) maxval = 1.0;
1140 }
1142 /* adjust min and max values */
1143 if (isnan(im->minval)
1144 /* don't adjust low-end with log scale */ /* why not? */
1145 || ((!im->rigid) && im->minval > minval)
1146 ) {
1147 if (im->logarithmic)
1148 im->minval = minval * 0.5;
1149 else
1150 im->minval = minval;
1151 }
1152 if (isnan(im->maxval)
1153 || (!im->rigid && im->maxval < maxval)
1154 ) {
1155 if (im->logarithmic)
1156 im->maxval = maxval * 2.0;
1157 else
1158 im->maxval = maxval;
1159 }
1160 /* make sure min is smaller than max */
1161 if (im->minval > im->maxval ) {
1162 if (im->maxval > 0)
1163 im->minval = 0.99 * im->maxval;
1164 else
1165 im->minval = 1.01 * im->maxval;
1166 }
1168 /* make sure min and max are not equal */
1169 if ( AlmostEqual2sComplement(im->minval,im->maxval,4)) {
1170 if (im->maxval > 0)
1171 im->maxval *= 1.01;
1172 else
1173 im->maxval *= 0.99;
1175 if (! im->logarithmic) {
1176 if (im->minval > 0)
1177 im->minval *= 0.99;
1178 else
1179 im->minval *= 1.01;
1180 }
1181 /* make sure min and max are not both zero */
1182 if (AlmostEqual2sComplement(im->maxval,0,4)) {
1183 im->maxval = 1.0;
1184 }
1185 }
1186 return 0;
1187 }
1191 /* identify the point where the first gridline, label ... gets placed */
1193 time_t
1194 find_first_time(
1195 time_t start, /* what is the initial time */
1196 enum tmt_en baseint, /* what is the basic interval */
1197 long basestep /* how many if these do we jump a time */
1198 )
1199 {
1200 struct tm tm;
1201 localtime_r(&start, &tm);
1202 switch(baseint){
1203 case TMT_SECOND:
1204 tm.tm_sec -= tm.tm_sec % basestep; break;
1205 case TMT_MINUTE:
1206 tm.tm_sec=0;
1207 tm.tm_min -= tm.tm_min % basestep;
1208 break;
1209 case TMT_HOUR:
1210 tm.tm_sec=0;
1211 tm.tm_min = 0;
1212 tm.tm_hour -= tm.tm_hour % basestep; break;
1213 case TMT_DAY:
1214 /* we do NOT look at the basestep for this ... */
1215 tm.tm_sec=0;
1216 tm.tm_min = 0;
1217 tm.tm_hour = 0; break;
1218 case TMT_WEEK:
1219 /* we do NOT look at the basestep for this ... */
1220 tm.tm_sec=0;
1221 tm.tm_min = 0;
1222 tm.tm_hour = 0;
1223 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1224 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1225 break;
1226 case TMT_MONTH:
1227 tm.tm_sec=0;
1228 tm.tm_min = 0;
1229 tm.tm_hour = 0;
1230 tm.tm_mday = 1;
1231 tm.tm_mon -= tm.tm_mon % basestep; break;
1233 case TMT_YEAR:
1234 tm.tm_sec=0;
1235 tm.tm_min = 0;
1236 tm.tm_hour = 0;
1237 tm.tm_mday = 1;
1238 tm.tm_mon = 0;
1239 tm.tm_year -= (tm.tm_year+1900) % basestep;
1241 }
1242 return mktime(&tm);
1243 }
1244 /* identify the point where the next gridline, label ... gets placed */
1245 time_t
1246 find_next_time(
1247 time_t current, /* what is the initial time */
1248 enum tmt_en baseint, /* what is the basic interval */
1249 long basestep /* how many if these do we jump a time */
1250 )
1251 {
1252 struct tm tm;
1253 time_t madetime;
1254 localtime_r(¤t, &tm);
1255 do {
1256 switch(baseint){
1257 case TMT_SECOND:
1258 tm.tm_sec += basestep; break;
1259 case TMT_MINUTE:
1260 tm.tm_min += basestep; break;
1261 case TMT_HOUR:
1262 tm.tm_hour += basestep; break;
1263 case TMT_DAY:
1264 tm.tm_mday += basestep; break;
1265 case TMT_WEEK:
1266 tm.tm_mday += 7*basestep; break;
1267 case TMT_MONTH:
1268 tm.tm_mon += basestep; break;
1269 case TMT_YEAR:
1270 tm.tm_year += basestep;
1271 }
1272 madetime = mktime(&tm);
1273 } while (madetime == -1); /* this is necessary to skip impssible times
1274 like the daylight saving time skips */
1275 return madetime;
1277 }
1280 /* calculate values required for PRINT and GPRINT functions */
1282 int
1283 print_calc(image_desc_t *im, char ***prdata)
1284 {
1285 long i,ii,validsteps;
1286 double printval;
1287 struct tm tmvdef;
1288 int graphelement = 0;
1289 long vidx;
1290 int max_ii;
1291 double magfact = -1;
1292 char *si_symb = "";
1293 char *percent_s;
1294 int prlines = 1;
1295 /* wow initializing tmvdef is quite a task :-) */
1296 time_t now = time(NULL);
1297 localtime_r(&now,&tmvdef);
1298 if (im->imginfo) prlines++;
1299 for(i=0;i<im->gdes_c;i++){
1300 vidx = im->gdes[i].vidx;
1301 switch(im->gdes[i].gf){
1302 case GF_PRINT:
1303 prlines++;
1304 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1305 rrd_set_error("realloc prdata");
1306 return 0;
1307 }
1308 case GF_GPRINT:
1309 /* PRINT and GPRINT can now print VDEF generated values.
1310 * There's no need to do any calculations on them as these
1311 * calculations were already made.
1312 */
1313 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1314 printval = im->gdes[vidx].vf.val;
1315 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1316 } else { /* need to calculate max,min,avg etcetera */
1317 max_ii =((im->gdes[vidx].end
1318 - im->gdes[vidx].start)
1319 / im->gdes[vidx].step
1320 * im->gdes[vidx].ds_cnt);
1321 printval = DNAN;
1322 validsteps = 0;
1323 for( ii=im->gdes[vidx].ds;
1324 ii < max_ii;
1325 ii+=im->gdes[vidx].ds_cnt){
1326 if (! finite(im->gdes[vidx].data[ii]))
1327 continue;
1328 if (isnan(printval)){
1329 printval = im->gdes[vidx].data[ii];
1330 validsteps++;
1331 continue;
1332 }
1334 switch (im->gdes[i].cf){
1335 case CF_HWPREDICT:
1336 case CF_DEVPREDICT:
1337 case CF_DEVSEASONAL:
1338 case CF_SEASONAL:
1339 case CF_AVERAGE:
1340 validsteps++;
1341 printval += im->gdes[vidx].data[ii];
1342 break;
1343 case CF_MINIMUM:
1344 printval = min( printval, im->gdes[vidx].data[ii]);
1345 break;
1346 case CF_FAILURES:
1347 case CF_MAXIMUM:
1348 printval = max( printval, im->gdes[vidx].data[ii]);
1349 break;
1350 case CF_LAST:
1351 printval = im->gdes[vidx].data[ii];
1352 }
1353 }
1354 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1355 if (validsteps > 1) {
1356 printval = (printval / validsteps);
1357 }
1358 }
1359 } /* prepare printval */
1361 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1362 /* Magfact is set to -1 upon entry to print_calc. If it
1363 * is still less than 0, then we need to run auto_scale.
1364 * Otherwise, put the value into the correct units. If
1365 * the value is 0, then do not set the symbol or magnification
1366 * so next the calculation will be performed again. */
1367 if (magfact < 0.0) {
1368 auto_scale(im,&printval,&si_symb,&magfact);
1369 if (printval == 0.0)
1370 magfact = -1.0;
1371 } else {
1372 printval /= magfact;
1373 }
1374 *(++percent_s) = 's';
1375 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1376 auto_scale(im,&printval,&si_symb,&magfact);
1377 }
1379 if (im->gdes[i].gf == GF_PRINT){
1380 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1381 (*prdata)[prlines-1] = NULL;
1382 if (im->gdes[i].strftm){
1383 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1384 } else {
1385 if (bad_format(im->gdes[i].format)) {
1386 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1387 return -1;
1388 }
1390 #ifdef HAVE_SNPRINTF
1391 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1392 #else
1393 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1394 #endif
1395 }
1396 } else {
1397 /* GF_GPRINT */
1399 if (im->gdes[i].strftm){
1400 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1401 } else {
1402 if (bad_format(im->gdes[i].format)) {
1403 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1404 return -1;
1405 }
1406 #ifdef HAVE_SNPRINTF
1407 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1408 #else
1409 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1410 #endif
1411 }
1412 graphelement = 1;
1413 }
1414 break;
1415 case GF_LINE:
1416 case GF_AREA:
1417 case GF_TICK:
1418 graphelement = 1;
1419 break;
1420 case GF_HRULE:
1421 if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1422 im->gdes[i].yrule=im->gdes[vidx].vf.val;
1423 };
1424 graphelement = 1;
1425 break;
1426 case GF_VRULE:
1427 if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1428 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1429 };
1430 graphelement = 1;
1431 break;
1432 case GF_COMMENT:
1433 case GF_DEF:
1434 case GF_CDEF:
1435 case GF_VDEF:
1436 #ifdef WITH_PIECHART
1437 case GF_PART:
1438 #endif
1439 case GF_SHIFT:
1440 case GF_XPORT:
1441 break;
1442 case GF_STACK:
1443 rrd_set_error("STACK should already be turned into LINE or AREA here");
1444 return -1;
1445 break;
1446 }
1447 }
1448 return graphelement;
1449 }
1452 /* place legends with color spots */
1453 int
1454 leg_place(image_desc_t *im)
1455 {
1456 /* graph labels */
1457 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1458 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1459 int fill=0, fill_last;
1460 int leg_c = 0;
1461 int leg_x = border, leg_y = im->yimg;
1462 int leg_y_prev = im->yimg;
1463 int leg_cc;
1464 int glue = 0;
1465 int i,ii, mark = 0;
1466 char prt_fctn; /*special printfunctions */
1467 int *legspace;
1469 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1470 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1471 rrd_set_error("malloc for legspace");
1472 return -1;
1473 }
1475 for(i=0;i<im->gdes_c;i++){
1476 fill_last = fill;
1478 /* hid legends for rules which are not displayed */
1480 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1481 if (im->gdes[i].gf == GF_HRULE &&
1482 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1483 im->gdes[i].legend[0] = '\0';
1485 if (im->gdes[i].gf == GF_VRULE &&
1486 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1487 im->gdes[i].legend[0] = '\0';
1488 }
1490 leg_cc = strlen(im->gdes[i].legend);
1492 /* is there a controle code ant the end of the legend string ? */
1493 /* and it is not a tab \\t */
1494 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1495 prt_fctn = im->gdes[i].legend[leg_cc-1];
1496 leg_cc -= 2;
1497 im->gdes[i].legend[leg_cc] = '\0';
1498 } else {
1499 prt_fctn = '\0';
1500 }
1501 /* only valid control codes */
1502 if (prt_fctn != 'l' &&
1503 prt_fctn != 'n' && /* a synonym for l */
1504 prt_fctn != 'r' &&
1505 prt_fctn != 'j' &&
1506 prt_fctn != 'c' &&
1507 prt_fctn != 's' &&
1508 prt_fctn != 't' &&
1509 prt_fctn != '\0' &&
1510 prt_fctn != 'g' ) {
1511 free(legspace);
1512 rrd_set_error("Unknown control code at the end of '%s\\%c'",im->gdes[i].legend,prt_fctn);
1513 return -1;
1515 }
1517 /* remove exess space */
1518 if ( prt_fctn == 'n' ){
1519 prt_fctn='l';
1520 }
1522 while (prt_fctn=='g' &&
1523 leg_cc > 0 &&
1524 im->gdes[i].legend[leg_cc-1]==' '){
1525 leg_cc--;
1526 im->gdes[i].legend[leg_cc]='\0';
1527 }
1528 if (leg_cc != 0 ){
1529 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1531 if (fill > 0){
1532 /* no interleg space if string ends in \g */
1533 fill += legspace[i];
1534 }
1535 fill += gfx_get_text_width(im->canvas, fill+border,
1536 im->text_prop[TEXT_PROP_LEGEND].font,
1537 im->text_prop[TEXT_PROP_LEGEND].size,
1538 im->tabwidth,
1539 im->gdes[i].legend, 0);
1540 leg_c++;
1541 } else {
1542 legspace[i]=0;
1543 }
1544 /* who said there was a special tag ... ?*/
1545 if (prt_fctn=='g') {
1546 prt_fctn = '\0';
1547 }
1548 if (prt_fctn == '\0') {
1549 if (i == im->gdes_c -1 ) prt_fctn ='l';
1551 /* is it time to place the legends ? */
1552 if (fill > im->ximg - 2*border){
1553 if (leg_c > 1) {
1554 /* go back one */
1555 i--;
1556 fill = fill_last;
1557 leg_c--;
1558 prt_fctn = 'j';
1559 } else {
1560 prt_fctn = 'l';
1561 }
1563 }
1564 }
1567 if (prt_fctn != '\0'){
1568 leg_x = border;
1569 if (leg_c >= 2 && prt_fctn == 'j') {
1570 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1571 } else {
1572 glue = 0;
1573 }
1574 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1575 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1577 for(ii=mark;ii<=i;ii++){
1578 if(im->gdes[ii].legend[0]=='\0')
1579 continue; /* skip empty legends */
1580 im->gdes[ii].leg_x = leg_x;
1581 im->gdes[ii].leg_y = leg_y;
1582 leg_x +=
1583 gfx_get_text_width(im->canvas, leg_x,
1584 im->text_prop[TEXT_PROP_LEGEND].font,
1585 im->text_prop[TEXT_PROP_LEGEND].size,
1586 im->tabwidth,
1587 im->gdes[ii].legend, 0)
1588 + legspace[ii]
1589 + glue;
1590 }
1591 leg_y_prev = leg_y;
1592 /* only add y space if there was text on the line */
1593 if (leg_x > border || prt_fctn == 's')
1594 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1595 if (prt_fctn == 's')
1596 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1597 fill = 0;
1598 leg_c = 0;
1599 mark = ii;
1600 }
1601 }
1602 im->yimg = leg_y_prev;
1603 /* if we did place some legends we have to add vertical space */
1604 if (leg_y != im->yimg){
1605 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1606 }
1607 free(legspace);
1608 }
1609 return 0;
1610 }
1612 /* create a grid on the graph. it determines what to do
1613 from the values of xsize, start and end */
1615 /* the xaxis labels are determined from the number of seconds per pixel
1616 in the requested graph */
1620 int
1621 calc_horizontal_grid(image_desc_t *im)
1622 {
1623 double range;
1624 double scaledrange;
1625 int pixel,i;
1626 int gridind=0;
1627 int decimals, fractionals;
1629 im->ygrid_scale.labfact=2;
1630 range = im->maxval - im->minval;
1631 scaledrange = range / im->magfact;
1633 /* does the scale of this graph make it impossible to put lines
1634 on it? If so, give up. */
1635 if (isnan(scaledrange)) {
1636 return 0;
1637 }
1639 /* find grid spaceing */
1640 pixel=1;
1641 if(isnan(im->ygridstep)){
1642 if(im->extra_flags & ALTYGRID) {
1643 /* find the value with max number of digits. Get number of digits */
1644 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1645 if(decimals <= 0) /* everything is small. make place for zero */
1646 decimals = 1;
1648 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1650 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1651 im->ygrid_scale.gridstep = 0.1;
1652 /* should have at least 5 lines but no more then 15 */
1653 if(range/im->ygrid_scale.gridstep < 5)
1654 im->ygrid_scale.gridstep /= 10;
1655 if(range/im->ygrid_scale.gridstep > 15)
1656 im->ygrid_scale.gridstep *= 10;
1657 if(range/im->ygrid_scale.gridstep > 5) {
1658 im->ygrid_scale.labfact = 1;
1659 if(range/im->ygrid_scale.gridstep > 8)
1660 im->ygrid_scale.labfact = 2;
1661 }
1662 else {
1663 im->ygrid_scale.gridstep /= 5;
1664 im->ygrid_scale.labfact = 5;
1665 }
1666 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1667 if(fractionals < 0) { /* small amplitude. */
1668 int len = decimals - fractionals + 1;
1669 if (im->unitslength < len+2) im->unitslength = len+2;
1670 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1671 } else {
1672 int len = decimals + 1;
1673 if (im->unitslength < len+2) im->unitslength = len+2;
1674 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1675 }
1676 }
1677 else {
1678 for(i=0;ylab[i].grid > 0;i++){
1679 pixel = im->ysize / (scaledrange / ylab[i].grid);
1680 gridind = i;
1681 if (pixel > 7)
1682 break;
1683 }
1685 for(i=0; i<4;i++) {
1686 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1687 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1688 break;
1689 }
1690 }
1692 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1693 }
1694 } else {
1695 im->ygrid_scale.gridstep = im->ygridstep;
1696 im->ygrid_scale.labfact = im->ylabfact;
1697 }
1698 return 1;
1699 }
1701 int draw_horizontal_grid(image_desc_t *im)
1702 {
1703 int i;
1704 double scaledstep;
1705 char graph_label[100];
1706 int nlabels=0;
1707 double X0=im->xorigin;
1708 double X1=im->xorigin+im->xsize;
1710 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1711 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1712 double MaxY;
1713 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1714 MaxY = scaledstep*(double)egrid;
1715 for (i = sgrid; i <= egrid; i++){
1716 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1717 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1718 if ( floor(Y0+0.5) >= im->yorigin-im->ysize
1719 && floor(Y0+0.5) <= im->yorigin){
1720 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1721 with the chosen settings. Add a label if required by settings, or if
1722 there is only one label so far and the next grid line is out of bounds. */
1723 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1724 if (im->symbol == ' ') {
1725 if(im->extra_flags & ALTYGRID) {
1726 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1727 } else {
1728 if(MaxY < 10) {
1729 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1730 } else {
1731 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1732 }
1733 }
1734 }else {
1735 char sisym = ( i == 0 ? ' ' : im->symbol);
1736 if(im->extra_flags & ALTYGRID) {
1737 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1738 } else {
1739 if(MaxY < 10){
1740 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1741 } else {
1742 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1743 }
1744 }
1745 }
1746 nlabels++;
1748 gfx_new_text ( im->canvas,
1749 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1750 im->graph_col[GRC_FONT],
1751 im->text_prop[TEXT_PROP_AXIS].font,
1752 im->text_prop[TEXT_PROP_AXIS].size,
1753 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1754 graph_label );
1755 gfx_new_dashed_line ( im->canvas,
1756 X0-2,Y0,
1757 X1+2,Y0,
1758 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1759 im->grid_dash_on, im->grid_dash_off);
1761 } else if (!(im->extra_flags & NOMINOR)) {
1762 gfx_new_dashed_line ( im->canvas,
1763 X0-1,Y0,
1764 X1+1,Y0,
1765 GRIDWIDTH, im->graph_col[GRC_GRID],
1766 im->grid_dash_on, im->grid_dash_off);
1768 }
1769 }
1770 }
1771 return 1;
1772 }
1774 /* this is frexp for base 10 */
1775 double frexp10(double, double *);
1776 double frexp10(double x, double *e) {
1777 double mnt;
1778 int iexp;
1780 iexp = floor(log(fabs(x)) / log(10));
1781 mnt = x / pow(10.0, iexp);
1782 if(mnt >= 10.0) {
1783 iexp++;
1784 mnt = x / pow(10.0, iexp);
1785 }
1786 *e = iexp;
1787 return mnt;
1788 }
1791 /* logaritmic horizontal grid */
1792 int
1793 horizontal_log_grid(image_desc_t *im)
1794 {
1795 double yloglab[][10] = {
1796 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1797 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1798 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1799 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1800 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1801 {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
1803 int i, j, val_exp, min_exp;
1804 double nex; /* number of decades in data */
1805 double logscale; /* scale in logarithmic space */
1806 int exfrac = 1; /* decade spacing */
1807 int mid = -1; /* row in yloglab for major grid */
1808 double mspac; /* smallest major grid spacing (pixels) */
1809 int flab; /* first value in yloglab to use */
1810 double value, tmp, pre_value;
1811 double X0,X1,Y0;
1812 char graph_label[100];
1814 nex = log10(im->maxval / im->minval);
1815 logscale = im->ysize / nex;
1817 /* major spacing for data with high dynamic range */
1818 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1819 if(exfrac == 1) exfrac = 3;
1820 else exfrac += 3;
1821 }
1823 /* major spacing for less dynamic data */
1824 do {
1825 /* search best row in yloglab */
1826 mid++;
1827 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1828 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1829 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
1830 if(mid) mid--;
1832 /* find first value in yloglab */
1833 for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
1834 if(yloglab[mid][flab] == 10.0) {
1835 tmp += 1.0;
1836 flab = 0;
1837 }
1838 val_exp = tmp;
1839 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1841 X0=im->xorigin;
1842 X1=im->xorigin+im->xsize;
1844 /* draw grid */
1845 pre_value = DNAN;
1846 while(1) {
1848 value = yloglab[mid][flab] * pow(10.0, val_exp);
1849 if ( AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
1851 pre_value = value;
1853 Y0 = ytr(im, value);
1854 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1856 /* major grid line */
1857 gfx_new_dashed_line ( im->canvas,
1858 X0-2,Y0,
1859 X1+2,Y0,
1860 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1861 im->grid_dash_on, im->grid_dash_off);
1863 /* label */
1864 if (im->extra_flags & FORCE_UNITS_SI) {
1865 int scale;
1866 double pvalue;
1867 char symbol;
1869 scale = floor(val_exp / 3.0);
1870 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1871 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1872 pvalue *= yloglab[mid][flab];
1874 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1875 ((scale+si_symbcenter) >= 0) )
1876 symbol = si_symbol[scale+si_symbcenter];
1877 else
1878 symbol = '?';
1880 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1881 } else
1882 sprintf(graph_label,"%3.0e", value);
1883 gfx_new_text ( im->canvas,
1884 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1885 im->graph_col[GRC_FONT],
1886 im->text_prop[TEXT_PROP_AXIS].font,
1887 im->text_prop[TEXT_PROP_AXIS].size,
1888 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1889 graph_label );
1891 /* minor grid */
1892 if(mid < 4 && exfrac == 1) {
1893 /* find first and last minor line behind current major line
1894 * i is the first line and j tha last */
1895 if(flab == 0) {
1896 min_exp = val_exp - 1;
1897 for(i = 1; yloglab[mid][i] < 10.0; i++);
1898 i = yloglab[mid][i - 1] + 1;
1899 j = 10;
1900 }
1901 else {
1902 min_exp = val_exp;
1903 i = yloglab[mid][flab - 1] + 1;
1904 j = yloglab[mid][flab];
1905 }
1907 /* draw minor lines below current major line */
1908 for(; i < j; i++) {
1910 value = i * pow(10.0, min_exp);
1911 if(value < im->minval) continue;
1913 Y0 = ytr(im, value);
1914 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1916 /* draw lines */
1917 gfx_new_dashed_line ( im->canvas,
1918 X0-1,Y0,
1919 X1+1,Y0,
1920 GRIDWIDTH, im->graph_col[GRC_GRID],
1921 im->grid_dash_on, im->grid_dash_off);
1922 }
1923 }
1924 else if(exfrac > 1) {
1925 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1926 value = pow(10.0, i);
1927 if(value < im->minval) continue;
1929 Y0 = ytr(im, value);
1930 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1932 /* draw lines */
1933 gfx_new_dashed_line ( im->canvas,
1934 X0-1,Y0,
1935 X1+1,Y0,
1936 GRIDWIDTH, im->graph_col[GRC_GRID],
1937 im->grid_dash_on, im->grid_dash_off);
1938 }
1939 }
1941 /* next decade */
1942 if(yloglab[mid][++flab] == 10.0) {
1943 flab = 0;
1944 val_exp += exfrac;
1945 }
1946 }
1948 /* draw minor lines after highest major line */
1949 if(mid < 4 && exfrac == 1) {
1950 /* find first and last minor line below current major line
1951 * i is the first line and j tha last */
1952 if(flab == 0) {
1953 min_exp = val_exp - 1;
1954 for(i = 1; yloglab[mid][i] < 10.0; i++);
1955 i = yloglab[mid][i - 1] + 1;
1956 j = 10;
1957 }
1958 else {
1959 min_exp = val_exp;
1960 i = yloglab[mid][flab - 1] + 1;
1961 j = yloglab[mid][flab];
1962 }
1964 /* draw minor lines below current major line */
1965 for(; i < j; i++) {
1967 value = i * pow(10.0, min_exp);
1968 if(value < im->minval) continue;
1970 Y0 = ytr(im, value);
1971 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1973 /* draw lines */
1974 gfx_new_dashed_line ( im->canvas,
1975 X0-1,Y0,
1976 X1+1,Y0,
1977 GRIDWIDTH, im->graph_col[GRC_GRID],
1978 im->grid_dash_on, im->grid_dash_off);
1979 }
1980 }
1981 /* fancy minor gridlines */
1982 else if(exfrac > 1) {
1983 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1984 value = pow(10.0, i);
1985 if(value < im->minval) continue;
1987 Y0 = ytr(im, value);
1988 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1990 /* draw lines */
1991 gfx_new_dashed_line ( im->canvas,
1992 X0-1,Y0,
1993 X1+1,Y0,
1994 GRIDWIDTH, im->graph_col[GRC_GRID],
1995 im->grid_dash_on, im->grid_dash_off);
1996 }
1997 }
1999 return 1;
2000 }
2003 void
2004 vertical_grid(
2005 image_desc_t *im )
2006 {
2007 int xlab_sel; /* which sort of label and grid ? */
2008 time_t ti, tilab, timajor;
2009 long factor;
2010 char graph_label[100];
2011 double X0,Y0,Y1; /* points for filled graph and more*/
2012 struct tm tm;
2014 /* the type of time grid is determined by finding
2015 the number of seconds per pixel in the graph */
2018 if(im->xlab_user.minsec == -1){
2019 factor=(im->end - im->start)/im->xsize;
2020 xlab_sel=0;
2021 while ( xlab[xlab_sel+1].minsec != -1
2022 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
2023 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
2024 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
2025 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2026 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2027 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2028 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2029 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2030 im->xlab_user.labst = xlab[xlab_sel].labst;
2031 im->xlab_user.precis = xlab[xlab_sel].precis;
2032 im->xlab_user.stst = xlab[xlab_sel].stst;
2033 }
2035 /* y coords are the same for every line ... */
2036 Y0 = im->yorigin;
2037 Y1 = im->yorigin-im->ysize;
2040 /* paint the minor grid */
2041 if (!(im->extra_flags & NOMINOR))
2042 {
2043 for(ti = find_first_time(im->start,
2044 im->xlab_user.gridtm,
2045 im->xlab_user.gridst),
2046 timajor = find_first_time(im->start,
2047 im->xlab_user.mgridtm,
2048 im->xlab_user.mgridst);
2049 ti < im->end;
2050 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2051 ){
2052 /* are we inside the graph ? */
2053 if (ti < im->start || ti > im->end) continue;
2054 while (timajor < ti) {
2055 timajor = find_next_time(timajor,
2056 im->xlab_user.mgridtm, im->xlab_user.mgridst);
2057 }
2058 if (ti == timajor) continue; /* skip as falls on major grid line */
2059 X0 = xtr(im,ti);
2060 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
2061 im->graph_col[GRC_GRID],
2062 im->grid_dash_on, im->grid_dash_off);
2064 }
2065 }
2067 /* paint the major grid */
2068 for(ti = find_first_time(im->start,
2069 im->xlab_user.mgridtm,
2070 im->xlab_user.mgridst);
2071 ti < im->end;
2072 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2073 ){
2074 /* are we inside the graph ? */
2075 if (ti < im->start || ti > im->end) continue;
2076 X0 = xtr(im,ti);
2077 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
2078 im->graph_col[GRC_MGRID],
2079 im->grid_dash_on, im->grid_dash_off);
2081 }
2082 /* paint the labels below the graph */
2083 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2084 im->xlab_user.labtm,
2085 im->xlab_user.labst);
2086 ti <= im->end - im->xlab_user.precis/2;
2087 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2088 ){
2089 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2090 /* are we inside the graph ? */
2091 if (tilab < im->start || tilab > im->end) continue;
2093 #if HAVE_STRFTIME
2094 localtime_r(&tilab, &tm);
2095 strftime(graph_label,99,im->xlab_user.stst, &tm);
2096 #else
2097 # error "your libc has no strftime I guess we'll abort the exercise here."
2098 #endif
2099 gfx_new_text ( im->canvas,
2100 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2101 im->graph_col[GRC_FONT],
2102 im->text_prop[TEXT_PROP_AXIS].font,
2103 im->text_prop[TEXT_PROP_AXIS].size,
2104 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2105 graph_label );
2107 }
2109 }
2112 void
2113 axis_paint(
2114 image_desc_t *im
2115 )
2116 {
2117 /* draw x and y axis */
2118 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2119 im->xorigin+im->xsize,im->yorigin-im->ysize,
2120 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2122 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2123 im->xorigin+im->xsize,im->yorigin-im->ysize,
2124 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2126 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2127 im->xorigin+im->xsize+4,im->yorigin,
2128 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2130 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2131 im->xorigin,im->yorigin-im->ysize-4,
2132 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2135 /* arrow for X and Y axis direction */
2136 gfx_new_area ( im->canvas,
2137 im->xorigin+im->xsize+2, im->yorigin-2,
2138 im->xorigin+im->xsize+2, im->yorigin+3,
2139 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2140 im->graph_col[GRC_ARROW]);
2142 gfx_new_area ( im->canvas,
2143 im->xorigin-2, im->yorigin-im->ysize-2,
2144 im->xorigin+3, im->yorigin-im->ysize-2,
2145 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2146 im->graph_col[GRC_ARROW]);
2148 }
2150 void
2151 grid_paint(image_desc_t *im)
2152 {
2153 long i;
2154 int res=0;
2155 double X0,Y0; /* points for filled graph and more*/
2156 gfx_node_t *node;
2158 /* draw 3d border */
2159 node = gfx_new_area (im->canvas, 0,im->yimg,
2160 2,im->yimg-2,
2161 2,2,im->graph_col[GRC_SHADEA]);
2162 gfx_add_point( node , im->ximg - 2, 2 );
2163 gfx_add_point( node , im->ximg, 0 );
2164 gfx_add_point( node , 0,0 );
2165 /* gfx_add_point( node , 0,im->yimg ); */
2167 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2168 im->ximg-2,im->yimg-2,
2169 im->ximg - 2, 2,
2170 im->graph_col[GRC_SHADEB]);
2171 gfx_add_point( node , im->ximg,0);
2172 gfx_add_point( node , im->ximg,im->yimg);
2173 gfx_add_point( node , 0,im->yimg);
2174 /* gfx_add_point( node , 0,im->yimg ); */
2177 if (im->draw_x_grid == 1 )
2178 vertical_grid(im);
2180 if (im->draw_y_grid == 1){
2181 if(im->logarithmic){
2182 res = horizontal_log_grid(im);
2183 } else {
2184 res = draw_horizontal_grid(im);
2185 }
2187 /* dont draw horizontal grid if there is no min and max val */
2188 if (! res ) {
2189 char *nodata = "No Data found";
2190 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2191 im->graph_col[GRC_FONT],
2192 im->text_prop[TEXT_PROP_AXIS].font,
2193 im->text_prop[TEXT_PROP_AXIS].size,
2194 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2195 nodata );
2196 }
2197 }
2199 /* yaxis unit description */
2200 gfx_new_text( im->canvas,
2201 10, (im->yorigin - im->ysize/2),
2202 im->graph_col[GRC_FONT],
2203 im->text_prop[TEXT_PROP_UNIT].font,
2204 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2205 RRDGRAPH_YLEGEND_ANGLE,
2206 GFX_H_LEFT, GFX_V_CENTER,
2207 im->ylegend);
2209 /* graph title */
2210 gfx_new_text( im->canvas,
2211 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2212 im->graph_col[GRC_FONT],
2213 im->text_prop[TEXT_PROP_TITLE].font,
2214 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2215 GFX_H_CENTER, GFX_V_CENTER,
2216 im->title);
2217 /* rrdtool 'logo' */
2218 gfx_new_text( im->canvas,
2219 im->ximg-7, 7,
2220 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2221 im->text_prop[TEXT_PROP_AXIS].font,
2222 5.5, im->tabwidth, 270,
2223 GFX_H_RIGHT, GFX_V_TOP,
2224 "RRDTOOL / TOBI OETIKER");
2226 /* graph watermark */
2227 if(im->watermark[0] != '\0') {
2228 gfx_new_text( im->canvas,
2229 im->ximg/2, im->yimg-6,
2230 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2231 im->text_prop[TEXT_PROP_AXIS].font,
2232 5.5, im->tabwidth, 0,
2233 GFX_H_CENTER, GFX_V_BOTTOM,
2234 im->watermark);
2235 }
2237 /* graph labels */
2238 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2239 for(i=0;i<im->gdes_c;i++){
2240 if(im->gdes[i].legend[0] =='\0')
2241 continue;
2243 /* im->gdes[i].leg_y is the bottom of the legend */
2244 X0 = im->gdes[i].leg_x;
2245 Y0 = im->gdes[i].leg_y;
2246 gfx_new_text ( im->canvas, X0, Y0,
2247 im->graph_col[GRC_FONT],
2248 im->text_prop[TEXT_PROP_LEGEND].font,
2249 im->text_prop[TEXT_PROP_LEGEND].size,
2250 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2251 im->gdes[i].legend );
2252 /* The legend for GRAPH items starts with "M " to have
2253 enough space for the box */
2254 if ( im->gdes[i].gf != GF_PRINT &&
2255 im->gdes[i].gf != GF_GPRINT &&
2256 im->gdes[i].gf != GF_COMMENT) {
2257 int boxH, boxV;
2259 boxH = gfx_get_text_width(im->canvas, 0,
2260 im->text_prop[TEXT_PROP_LEGEND].font,
2261 im->text_prop[TEXT_PROP_LEGEND].size,
2262 im->tabwidth,"o", 0) * 1.2;
2263 boxV = boxH*1.1;
2265 /* make sure transparent colors show up the same way as in the graph */
2266 node = gfx_new_area(im->canvas,
2267 X0,Y0-boxV,
2268 X0,Y0,
2269 X0+boxH,Y0,
2270 im->graph_col[GRC_BACK]);
2271 gfx_add_point ( node, X0+boxH, Y0-boxV );
2273 node = gfx_new_area(im->canvas,
2274 X0,Y0-boxV,
2275 X0,Y0,
2276 X0+boxH,Y0,
2277 im->gdes[i].col);
2278 gfx_add_point ( node, X0+boxH, Y0-boxV );
2279 node = gfx_new_line(im->canvas,
2280 X0,Y0-boxV,
2281 X0,Y0,
2282 1.0,im->graph_col[GRC_FRAME]);
2283 gfx_add_point(node,X0+boxH,Y0);
2284 gfx_add_point(node,X0+boxH,Y0-boxV);
2285 gfx_close_path(node);
2286 }
2287 }
2288 }
2289 }
2292 /*****************************************************
2293 * lazy check make sure we rely need to create this graph
2294 *****************************************************/
2296 int lazy_check(image_desc_t *im){
2297 FILE *fd = NULL;
2298 int size = 1;
2299 struct stat imgstat;
2301 if (im->lazy == 0) return 0; /* no lazy option */
2302 if (stat(im->graphfile,&imgstat) != 0)
2303 return 0; /* can't stat */
2304 /* one pixel in the existing graph is more then what we would
2305 change here ... */
2306 if (time(NULL) - imgstat.st_mtime >
2307 (im->end - im->start) / im->xsize)
2308 return 0;
2309 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2310 return 0; /* the file does not exist */
2311 switch (im->canvas->imgformat) {
2312 case IF_PNG:
2313 size = PngSize(fd,&(im->ximg),&(im->yimg));
2314 break;
2315 default:
2316 size = 1;
2317 }
2318 fclose(fd);
2319 return size;
2320 }
2322 #ifdef WITH_PIECHART
2323 void
2324 pie_part(image_desc_t *im, gfx_color_t color,
2325 double PieCenterX, double PieCenterY, double Radius,
2326 double startangle, double endangle)
2327 {
2328 gfx_node_t *node;
2329 double angle;
2330 double step=M_PI/50; /* Number of iterations for the circle;
2331 ** 10 is definitely too low, more than
2332 ** 50 seems to be overkill
2333 */
2335 /* Strange but true: we have to work clockwise or else
2336 ** anti aliasing nor transparency don't work.
2337 **
2338 ** This test is here to make sure we do it right, also
2339 ** this makes the for...next loop more easy to implement.
2340 ** The return will occur if the user enters a negative number
2341 ** (which shouldn't be done according to the specs) or if the
2342 ** programmers do something wrong (which, as we all know, never
2343 ** happens anyway :)
2344 */
2345 if (endangle<startangle) return;
2347 /* Hidden feature: Radius decreases each full circle */
2348 angle=startangle;
2349 while (angle>=2*M_PI) {
2350 angle -= 2*M_PI;
2351 Radius *= 0.8;
2352 }
2354 node=gfx_new_area(im->canvas,
2355 PieCenterX+sin(startangle)*Radius,
2356 PieCenterY-cos(startangle)*Radius,
2357 PieCenterX,
2358 PieCenterY,
2359 PieCenterX+sin(endangle)*Radius,
2360 PieCenterY-cos(endangle)*Radius,
2361 color);
2362 for (angle=endangle;angle-startangle>=step;angle-=step) {
2363 gfx_add_point(node,
2364 PieCenterX+sin(angle)*Radius,
2365 PieCenterY-cos(angle)*Radius );
2366 }
2367 }
2369 #endif
2371 int
2372 graph_size_location(image_desc_t *im, int elements
2374 #ifdef WITH_PIECHART
2375 , int piechart
2376 #endif
2378 )
2379 {
2380 /* The actual size of the image to draw is determined from
2381 ** several sources. The size given on the command line is
2382 ** the graph area but we need more as we have to draw labels
2383 ** and other things outside the graph area
2384 */
2386 /* +-+-------------------------------------------+
2387 ** |l|.................title.....................|
2388 ** |e+--+-------------------------------+--------+
2389 ** |b| b| | |
2390 ** |a| a| | pie |
2391 ** |l| l| main graph area | chart |
2392 ** |.| .| | area |
2393 ** |t| y| | |
2394 ** |r+--+-------------------------------+--------+
2395 ** |e| | x-axis labels | |
2396 ** |v+--+-------------------------------+--------+
2397 ** | |..............legends......................|
2398 ** +-+-------------------------------------------+
2399 ** | watermark |
2400 ** +---------------------------------------------+
2401 */
2402 int Xvertical=0,
2403 Ytitle =0,
2404 Xylabel =0,
2405 Xmain =0, Ymain =0,
2406 #ifdef WITH_PIECHART
2407 Xpie =0, Ypie =0,
2408 #endif
2409 Yxlabel =0,
2410 #if 0
2411 Xlegend =0, Ylegend =0,
2412 #endif
2413 Xspacing =15, Yspacing =15,
2415 Ywatermark =4;
2417 if (im->extra_flags & ONLY_GRAPH) {
2418 im->xorigin =0;
2419 im->ximg = im->xsize;
2420 im->yimg = im->ysize;
2421 im->yorigin = im->ysize;
2422 ytr(im,DNAN);
2423 return 0;
2424 }
2426 if (im->ylegend[0] != '\0' ) {
2427 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2428 }
2431 if (im->title[0] != '\0') {
2432 /* The title is placed "inbetween" two text lines so it
2433 ** automatically has some vertical spacing. The horizontal
2434 ** spacing is added here, on each side.
2435 */
2436 /* don't care for the with of the title
2437 Xtitle = gfx_get_text_width(im->canvas, 0,
2438 im->text_prop[TEXT_PROP_TITLE].font,
2439 im->text_prop[TEXT_PROP_TITLE].size,
2440 im->tabwidth,
2441 im->title, 0) + 2*Xspacing; */
2442 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2443 }
2445 if (elements) {
2446 Xmain=im->xsize;
2447 Ymain=im->ysize;
2448 if (im->draw_x_grid) {
2449 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2450 }
2451 if (im->draw_y_grid || im->forceleftspace ) {
2452 Xylabel=gfx_get_text_width(im->canvas, 0,
2453 im->text_prop[TEXT_PROP_AXIS].font,
2454 im->text_prop[TEXT_PROP_AXIS].size,
2455 im->tabwidth,
2456 "0", 0) * im->unitslength;
2457 }
2458 }
2460 #ifdef WITH_PIECHART
2461 if (piechart) {
2462 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2463 Xpie=im->piesize;
2464 Ypie=im->piesize;
2465 }
2466 #endif
2468 /* Now calculate the total size. Insert some spacing where
2469 desired. im->xorigin and im->yorigin need to correspond
2470 with the lower left corner of the main graph area or, if
2471 this one is not set, the imaginary box surrounding the
2472 pie chart area. */
2474 /* The legend width cannot yet be determined, as a result we
2475 ** have problems adjusting the image to it. For now, we just
2476 ** forget about it at all; the legend will have to fit in the
2477 ** size already allocated.
2478 */
2479 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2481 #ifdef WITH_PIECHART
2482 im->ximg += Xpie;
2483 #endif
2485 if (Xmain) im->ximg += Xspacing;
2486 #ifdef WITH_PIECHART
2487 if (Xpie) im->ximg += Xspacing;
2488 #endif
2490 im->xorigin = Xspacing + Xylabel;
2492 /* the length of the title should not influence with width of the graph
2493 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2495 if (Xvertical) { /* unit description */
2496 im->ximg += Xvertical;
2497 im->xorigin += Xvertical;
2498 }
2499 xtr(im,0);
2501 /* The vertical size is interesting... we need to compare
2502 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2503 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2504 ** in order to start even thinking about Ylegend or Ywatermark.
2505 **
2506 ** Do it in three portions: First calculate the inner part,
2507 ** then do the legend, then adjust the total height of the img,
2508 ** adding space for a watermark if one exists;
2509 */
2511 /* reserve space for main and/or pie */
2513 im->yimg = Ymain + Yxlabel;
2515 #ifdef WITH_PIECHART
2516 if (im->yimg < Ypie) im->yimg = Ypie;
2517 #endif
2519 im->yorigin = im->yimg - Yxlabel;
2521 /* reserve space for the title *or* some padding above the graph */
2522 if (Ytitle) {
2523 im->yimg += Ytitle;
2524 im->yorigin += Ytitle;
2525 } else {
2526 im->yimg += 1.5*Yspacing;
2527 im->yorigin += 1.5*Yspacing;
2528 }
2529 /* reserve space for padding below the graph */
2530 im->yimg += Yspacing;
2532 /* Determine where to place the legends onto the image.
2533 ** Adjust im->yimg to match the space requirements.
2534 */
2535 if(leg_place(im)==-1)
2536 return -1;
2538 if (im->watermark[0] != '\0') {
2539 im->yimg += Ywatermark;
2540 }
2542 #if 0
2543 if (Xlegend > im->ximg) {
2544 im->ximg = Xlegend;
2545 /* reposition Pie */
2546 }
2547 #endif
2549 #ifdef WITH_PIECHART
2550 /* The pie is placed in the upper right hand corner,
2551 ** just below the title (if any) and with sufficient
2552 ** padding.
2553 */
2554 if (elements) {
2555 im->pie_x = im->ximg - Xspacing - Xpie/2;
2556 im->pie_y = im->yorigin-Ymain+Ypie/2;
2557 } else {
2558 im->pie_x = im->ximg/2;
2559 im->pie_y = im->yorigin-Ypie/2;
2560 }
2561 #endif
2563 ytr(im,DNAN);
2564 return 0;
2565 }
2567 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2568 /* yes we are loosing precision by doing tos with floats instead of doubles
2569 but it seems more stable this way. */
2572 /* draw that picture thing ... */
2573 int
2574 graph_paint(image_desc_t *im, char ***calcpr)
2575 {
2576 int i,ii;
2577 int lazy = lazy_check(im);
2578 #ifdef WITH_PIECHART
2579 int piechart = 0;
2580 double PieStart=0.0;
2581 #endif
2582 FILE *fo;
2583 gfx_node_t *node;
2585 double areazero = 0.0;
2586 graph_desc_t *lastgdes = NULL;
2588 /* if we are lazy and there is nothing to PRINT ... quit now */
2589 if (lazy && im->prt_c==0) return 0;
2591 /* pull the data from the rrd files ... */
2593 if(data_fetch(im)==-1)
2594 return -1;
2596 /* evaluate VDEF and CDEF operations ... */
2597 if(data_calc(im)==-1)
2598 return -1;
2600 #ifdef WITH_PIECHART
2601 /* check if we need to draw a piechart */
2602 for(i=0;i<im->gdes_c;i++){
2603 if (im->gdes[i].gf == GF_PART) {
2604 piechart=1;
2605 break;
2606 }
2607 }
2608 #endif
2610 /* calculate and PRINT and GPRINT definitions. We have to do it at
2611 * this point because it will affect the length of the legends
2612 * if there are no graph elements we stop here ...
2613 * if we are lazy, try to quit ...
2614 */
2615 i=print_calc(im,calcpr);
2616 if(i<0) return -1;
2617 if(((i==0)
2618 #ifdef WITH_PIECHART
2619 &&(piechart==0)
2620 #endif
2621 ) || lazy) return 0;
2623 #ifdef WITH_PIECHART
2624 /* If there's only the pie chart to draw, signal this */
2625 if (i==0) piechart=2;
2626 #endif
2628 /* get actual drawing data and find min and max values*/
2629 if(data_proc(im)==-1)
2630 return -1;
2632 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2634 if(!im->rigid && ! im->logarithmic)
2635 expand_range(im); /* make sure the upper and lower limit are
2636 sensible values */
2638 if (!calc_horizontal_grid(im))
2639 return -1;
2641 if (im->gridfit)
2642 apply_gridfit(im);
2645 /**************************************************************
2646 *** Calculating sizes and locations became a bit confusing ***
2647 *** so I moved this into a separate function. ***
2648 **************************************************************/
2649 if(graph_size_location(im,i
2650 #ifdef WITH_PIECHART
2651 ,piechart
2652 #endif
2653 )==-1)
2654 return -1;
2656 /* the actual graph is created by going through the individual
2657 graph elements and then drawing them */
2659 node=gfx_new_area ( im->canvas,
2660 0, 0,
2661 0, im->yimg,
2662 im->ximg, im->yimg,
2663 im->graph_col[GRC_BACK]);
2665 gfx_add_point(node,im->ximg, 0);
2667 #ifdef WITH_PIECHART
2668 if (piechart != 2) {
2669 #endif
2670 node=gfx_new_area ( im->canvas,
2671 im->xorigin, im->yorigin,
2672 im->xorigin + im->xsize, im->yorigin,
2673 im->xorigin + im->xsize, im->yorigin-im->ysize,
2674 im->graph_col[GRC_CANVAS]);
2676 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2678 if (im->minval > 0.0)
2679 areazero = im->minval;
2680 if (im->maxval < 0.0)
2681 areazero = im->maxval;
2682 #ifdef WITH_PIECHART
2683 }
2684 #endif
2686 #ifdef WITH_PIECHART
2687 if (piechart) {
2688 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2689 }
2690 #endif
2692 for(i=0;i<im->gdes_c;i++){
2693 switch(im->gdes[i].gf){
2694 case GF_CDEF:
2695 case GF_VDEF:
2696 case GF_DEF:
2697 case GF_PRINT:
2698 case GF_GPRINT:
2699 case GF_COMMENT:
2700 case GF_HRULE:
2701 case GF_VRULE:
2702 case GF_XPORT:
2703 case GF_SHIFT:
2704 break;
2705 case GF_TICK:
2706 for (ii = 0; ii < im->xsize; ii++)
2707 {
2708 if (!isnan(im->gdes[i].p_data[ii]) &&
2709 im->gdes[i].p_data[ii] != 0.0)
2710 {
2711 if (im -> gdes[i].yrule > 0 ) {
2712 gfx_new_line(im->canvas,
2713 im -> xorigin + ii, im->yorigin,
2714 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2715 1.0,
2716 im -> gdes[i].col );
2717 } else if ( im -> gdes[i].yrule < 0 ) {
2718 gfx_new_line(im->canvas,
2719 im -> xorigin + ii, im->yorigin - im -> ysize,
2720 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2721 1.0,
2722 im -> gdes[i].col );
2724 }
2725 }
2726 }
2727 break;
2728 case GF_LINE:
2729 case GF_AREA:
2730 /* fix data points at oo and -oo */
2731 for(ii=0;ii<im->xsize;ii++){
2732 if (isinf(im->gdes[i].p_data[ii])){
2733 if (im->gdes[i].p_data[ii] > 0) {
2734 im->gdes[i].p_data[ii] = im->maxval ;
2735 } else {
2736 im->gdes[i].p_data[ii] = im->minval ;
2737 }
2739 }
2740 } /* for */
2742 /* *******************************************************
2743 a ___. (a,t)
2744 | | ___
2745 ____| | | |
2746 | |___|
2747 -------|--t-1--t--------------------------------
2749 if we know the value at time t was a then
2750 we draw a square from t-1 to t with the value a.
2752 ********************************************************* */
2753 if (im->gdes[i].col != 0x0){
2754 /* GF_LINE and friend */
2755 if(im->gdes[i].gf == GF_LINE ){
2756 double last_y=0.0;
2757 node = NULL;
2758 for(ii=1;ii<im->xsize;ii++){
2759 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2760 node = NULL;
2761 continue;
2762 }
2763 if ( node == NULL ) {
2764 last_y = ytr(im,im->gdes[i].p_data[ii]);
2765 if ( im->slopemode == 0 ){
2766 node = gfx_new_line(im->canvas,
2767 ii-1+im->xorigin,last_y,
2768 ii+im->xorigin,last_y,
2769 im->gdes[i].linewidth,
2770 im->gdes[i].col);
2771 } else {
2772 node = gfx_new_line(im->canvas,
2773 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2774 ii+im->xorigin,last_y,
2775 im->gdes[i].linewidth,
2776 im->gdes[i].col);
2777 }
2778 } else {
2779 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2780 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2781 gfx_add_point(node,ii-1+im->xorigin,new_y);
2782 };
2783 last_y = new_y;
2784 gfx_add_point(node,ii+im->xorigin,new_y);
2785 };
2787 }
2788 } else {
2789 int idxI=-1;
2790 double *foreY=malloc(sizeof(double)*im->xsize*2);
2791 double *foreX=malloc(sizeof(double)*im->xsize*2);
2792 double *backY=malloc(sizeof(double)*im->xsize*2);
2793 double *backX=malloc(sizeof(double)*im->xsize*2);
2794 int drawem = 0;
2795 for(ii=0;ii<=im->xsize;ii++){
2796 double ybase,ytop;
2797 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2798 int cntI=1;
2799 int lastI=0;
2800 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2801 node = gfx_new_area(im->canvas,
2802 backX[0],backY[0],
2803 foreX[0],foreY[0],
2804 foreX[cntI],foreY[cntI], im->gdes[i].col);
2805 while (cntI < idxI) {
2806 lastI = cntI;
2807 cntI++;
2808 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2809 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2810 }
2811 gfx_add_point(node,backX[idxI],backY[idxI]);
2812 while (idxI > 1){
2813 lastI = idxI;
2814 idxI--;
2815 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2816 gfx_add_point(node,backX[idxI],backY[idxI]);
2817 }
2818 idxI=-1;
2819 drawem = 0;
2820 }
2821 if (drawem != 0){
2822 drawem = 0;
2823 idxI=-1;
2824 }
2825 if (ii == im->xsize) break;
2827 /* keep things simple for now, just draw these bars
2828 do not try to build a big and complex area */
2831 if ( im->slopemode == 0 && ii==0){
2832 continue;
2833 }
2834 if ( isnan(im->gdes[i].p_data[ii]) ) {
2835 drawem = 1;
2836 continue;
2837 }
2838 ytop = ytr(im,im->gdes[i].p_data[ii]);
2839 if ( lastgdes && im->gdes[i].stack ) {
2840 ybase = ytr(im,lastgdes->p_data[ii]);
2841 } else {
2842 ybase = ytr(im,areazero);
2843 }
2844 if ( ybase == ytop ){
2845 drawem = 1;
2846 continue;
2847 }
2848 /* every area has to be wound clock-wise,
2849 so we have to make sur base remains base */
2850 if (ybase > ytop){
2851 double extra = ytop;
2852 ytop = ybase;
2853 ybase = extra;
2854 }
2855 if ( im->slopemode == 0 ){
2856 backY[++idxI] = ybase-0.2;
2857 backX[idxI] = ii+im->xorigin-1;
2858 foreY[idxI] = ytop+0.2;
2859 foreX[idxI] = ii+im->xorigin-1;
2860 }
2861 backY[++idxI] = ybase-0.2;
2862 backX[idxI] = ii+im->xorigin;
2863 foreY[idxI] = ytop+0.2;
2864 foreX[idxI] = ii+im->xorigin;
2865 }
2866 /* close up any remaining area */
2867 free(foreY);
2868 free(foreX);
2869 free(backY);
2870 free(backX);
2871 } /* else GF_LINE */
2872 } /* if color != 0x0 */
2873 /* make sure we do not run into trouble when stacking on NaN */
2874 for(ii=0;ii<im->xsize;ii++){
2875 if (isnan(im->gdes[i].p_data[ii])) {
2876 if (lastgdes && (im->gdes[i].stack)) {
2877 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2878 } else {
2879 im->gdes[i].p_data[ii] = areazero;
2880 }
2881 }
2882 }
2883 lastgdes = &(im->gdes[i]);
2884 break;
2885 #ifdef WITH_PIECHART
2886 case GF_PART:
2887 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2888 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2890 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2891 pie_part(im,im->gdes[i].col,
2892 im->pie_x,im->pie_y,im->piesize*0.4,
2893 M_PI*2.0*PieStart/100.0,
2894 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2895 PieStart += im->gdes[i].yrule;
2896 }
2897 break;
2898 #endif
2899 case GF_STACK:
2900 rrd_set_error("STACK should already be turned into LINE or AREA here");
2901 return -1;
2902 break;
2904 } /* switch */
2905 }
2906 #ifdef WITH_PIECHART
2907 if (piechart==2) {
2908 im->draw_x_grid=0;
2909 im->draw_y_grid=0;
2910 }
2911 #endif
2914 /* grid_paint also does the text */
2915 if( !(im->extra_flags & ONLY_GRAPH) )
2916 grid_paint(im);
2919 if( !(im->extra_flags & ONLY_GRAPH) )
2920 axis_paint(im);
2922 /* the RULES are the last thing to paint ... */
2923 for(i=0;i<im->gdes_c;i++){
2925 switch(im->gdes[i].gf){
2926 case GF_HRULE:
2927 if(im->gdes[i].yrule >= im->minval
2928 && im->gdes[i].yrule <= im->maxval)
2929 gfx_new_line(im->canvas,
2930 im->xorigin,ytr(im,im->gdes[i].yrule),
2931 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2932 1.0,im->gdes[i].col);
2933 break;
2934 case GF_VRULE:
2935 if(im->gdes[i].xrule >= im->start
2936 && im->gdes[i].xrule <= im->end)
2937 gfx_new_line(im->canvas,
2938 xtr(im,im->gdes[i].xrule),im->yorigin,
2939 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2940 1.0,im->gdes[i].col);
2941 break;
2942 default:
2943 break;
2944 }
2945 }
2948 if (strcmp(im->graphfile,"-")==0) {
2949 fo = im->graphhandle ? im->graphhandle : stdout;
2950 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2951 /* Change translation mode for stdout to BINARY */
2952 _setmode( _fileno( fo ), O_BINARY );
2953 #endif
2954 } else {
2955 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2956 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2957 rrd_strerror(errno));
2958 return (-1);
2959 }
2960 }
2961 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2962 if (strcmp(im->graphfile,"-") != 0)
2963 fclose(fo);
2964 return 0;
2965 }
2968 /*****************************************************
2969 * graph stuff
2970 *****************************************************/
2972 int
2973 gdes_alloc(image_desc_t *im){
2975 im->gdes_c++;
2976 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2977 * sizeof(graph_desc_t)))==NULL){
2978 rrd_set_error("realloc graph_descs");
2979 return -1;
2980 }
2983 im->gdes[im->gdes_c-1].step=im->step;
2984 im->gdes[im->gdes_c-1].step_orig=im->step;
2985 im->gdes[im->gdes_c-1].stack=0;
2986 im->gdes[im->gdes_c-1].linewidth=0;
2987 im->gdes[im->gdes_c-1].debug=0;
2988 im->gdes[im->gdes_c-1].start=im->start;
2989 im->gdes[im->gdes_c-1].start_orig=im->start;
2990 im->gdes[im->gdes_c-1].end=im->end;
2991 im->gdes[im->gdes_c-1].end_orig=im->end;
2992 im->gdes[im->gdes_c-1].vname[0]='\0';
2993 im->gdes[im->gdes_c-1].data=NULL;
2994 im->gdes[im->gdes_c-1].ds_namv=NULL;
2995 im->gdes[im->gdes_c-1].data_first=0;
2996 im->gdes[im->gdes_c-1].p_data=NULL;
2997 im->gdes[im->gdes_c-1].rpnp=NULL;
2998 im->gdes[im->gdes_c-1].shift=0;
2999 im->gdes[im->gdes_c-1].col = 0x0;
3000 im->gdes[im->gdes_c-1].legend[0]='\0';
3001 im->gdes[im->gdes_c-1].format[0]='\0';
3002 im->gdes[im->gdes_c-1].strftm=0;
3003 im->gdes[im->gdes_c-1].rrd[0]='\0';
3004 im->gdes[im->gdes_c-1].ds=-1;
3005 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
3006 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
3007 im->gdes[im->gdes_c-1].p_data=NULL;
3008 im->gdes[im->gdes_c-1].yrule=DNAN;
3009 im->gdes[im->gdes_c-1].xrule=0;
3010 return 0;
3011 }
3013 /* copies input untill the first unescaped colon is found
3014 or until input ends. backslashes have to be escaped as well */
3015 int
3016 scan_for_col(const char *const input, int len, char *const output)
3017 {
3018 int inp,outp=0;
3019 for (inp=0;
3020 inp < len &&
3021 input[inp] != ':' &&
3022 input[inp] != '\0';
3023 inp++){
3024 if (input[inp] == '\\' &&
3025 input[inp+1] != '\0' &&
3026 (input[inp+1] == '\\' ||
3027 input[inp+1] == ':')){
3028 output[outp++] = input[++inp];
3029 }
3030 else {
3031 output[outp++] = input[inp];
3032 }
3033 }
3034 output[outp] = '\0';
3035 return inp;
3036 }
3037 /* Some surgery done on this function, it became ridiculously big.
3038 ** Things moved:
3039 ** - initializing now in rrd_graph_init()
3040 ** - options parsing now in rrd_graph_options()
3041 ** - script parsing now in rrd_graph_script()
3042 */
3043 int
3044 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
3045 {
3046 image_desc_t im;
3047 rrd_graph_init(&im);
3048 im.graphhandle = stream;
3050 rrd_graph_options(argc,argv,&im);
3051 if (rrd_test_error()) {
3052 im_free(&im);
3053 return -1;
3054 }
3056 if (strlen(argv[optind])>=MAXPATH) {
3057 rrd_set_error("filename (including path) too long");
3058 im_free(&im);
3059 return -1;
3060 }
3061 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3062 im.graphfile[MAXPATH-1]='\0';
3064 rrd_graph_script(argc,argv,&im,1);
3065 if (rrd_test_error()) {
3066 im_free(&im);
3067 return -1;
3068 }
3070 /* Everything is now read and the actual work can start */
3072 (*prdata)=NULL;
3073 if (graph_paint(&im,prdata)==-1){
3074 im_free(&im);
3075 return -1;
3076 }
3078 /* The image is generated and needs to be output.
3079 ** Also, if needed, print a line with information about the image.
3080 */
3082 *xsize=im.ximg;
3083 *ysize=im.yimg;
3084 *ymin=im.minval;
3085 *ymax=im.maxval;
3086 if (im.imginfo) {
3087 char *filename;
3088 if (!(*prdata)) {
3089 /* maybe prdata is not allocated yet ... lets do it now */
3090 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3091 rrd_set_error("malloc imginfo");
3092 return -1;
3093 };
3094 }
3095 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3096 ==NULL){
3097 rrd_set_error("malloc imginfo");
3098 return -1;
3099 }
3100 filename=im.graphfile+strlen(im.graphfile);
3101 while(filename > im.graphfile) {
3102 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3103 filename--;
3104 }
3106 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3107 }
3108 im_free(&im);
3109 return 0;
3110 }
3112 void
3113 rrd_graph_init(image_desc_t *im)
3114 {
3115 unsigned int i;
3117 #ifdef HAVE_TZSET
3118 tzset();
3119 #endif
3120 #ifdef HAVE_SETLOCALE
3121 setlocale(LC_TIME,"");
3122 #ifdef HAVE_MBSTOWCS
3123 setlocale(LC_CTYPE,"");
3124 #endif
3125 #endif
3126 im->yorigin=0;
3127 im->xorigin=0;
3128 im->minval=0;
3129 im->xlab_user.minsec = -1;
3130 im->ximg=0;
3131 im->yimg=0;
3132 im->xsize = 400;
3133 im->ysize = 100;
3134 im->step = 0;
3135 im->ylegend[0] = '\0';
3136 im->title[0] = '\0';
3137 im->watermark[0] = '\0';
3138 im->minval = DNAN;
3139 im->maxval = DNAN;
3140 im->unitsexponent= 9999;
3141 im->unitslength= 6;
3142 im->forceleftspace = 0;
3143 im->symbol = ' ';
3144 im->viewfactor = 1.0;
3145 im->extra_flags= 0;
3146 im->rigid = 0;
3147 im->gridfit = 1;
3148 im->imginfo = NULL;
3149 im->lazy = 0;
3150 im->slopemode = 0;
3151 im->logarithmic = 0;
3152 im->ygridstep = DNAN;
3153 im->draw_x_grid = 1;
3154 im->draw_y_grid = 1;
3155 im->base = 1000;
3156 im->prt_c = 0;
3157 im->gdes_c = 0;
3158 im->gdes = NULL;
3159 im->canvas = gfx_new_canvas();
3160 im->grid_dash_on = 1;
3161 im->grid_dash_off = 1;
3162 im->tabwidth = 40.0;
3164 for(i=0;i<DIM(graph_col);i++)
3165 im->graph_col[i]=graph_col[i];
3167 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3168 {
3169 char *windir;
3170 char rrd_win_default_font[1000];
3171 windir = getenv("windir");
3172 /* %windir% is something like D:\windows or C:\winnt */
3173 if (windir != NULL) {
3174 strncpy(rrd_win_default_font,windir,500);
3175 rrd_win_default_font[500] = '\0';
3176 strcat(rrd_win_default_font,"\\fonts\\");
3177 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3178 for(i=0;i<DIM(text_prop);i++){
3179 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3180 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3181 }
3182 }
3183 }
3184 #endif
3185 {
3186 char *deffont;
3187 deffont = getenv("RRD_DEFAULT_FONT");
3188 if (deffont != NULL) {
3189 for(i=0;i<DIM(text_prop);i++){
3190 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3191 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3192 }
3193 }
3194 }
3195 for(i=0;i<DIM(text_prop);i++){
3196 im->text_prop[i].size = text_prop[i].size;
3197 strcpy(im->text_prop[i].font,text_prop[i].font);
3198 }
3199 }
3201 void
3202 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3203 {
3204 int stroff;
3205 char *parsetime_error = NULL;
3206 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3207 time_t start_tmp=0,end_tmp=0;
3208 long long_tmp;
3209 struct rrd_time_value start_tv, end_tv;
3210 gfx_color_t color;
3211 optind = 0; opterr = 0; /* initialize getopt */
3213 parsetime("end-24h", &start_tv);
3214 parsetime("now", &end_tv);
3216 /* defines for long options without a short equivalent. should be bytes,
3217 and may not collide with (the ASCII value of) short options */
3218 #define LONGOPT_UNITS_SI 255
3220 while (1){
3221 static struct option long_options[] =
3222 {
3223 {"start", required_argument, 0, 's'},
3224 {"end", required_argument, 0, 'e'},
3225 {"x-grid", required_argument, 0, 'x'},
3226 {"y-grid", required_argument, 0, 'y'},
3227 {"vertical-label",required_argument,0,'v'},
3228 {"width", required_argument, 0, 'w'},
3229 {"height", required_argument, 0, 'h'},
3230 {"interlaced", no_argument, 0, 'i'},
3231 {"upper-limit",required_argument, 0, 'u'},
3232 {"lower-limit",required_argument, 0, 'l'},
3233 {"rigid", no_argument, 0, 'r'},
3234 {"base", required_argument, 0, 'b'},
3235 {"logarithmic",no_argument, 0, 'o'},
3236 {"color", required_argument, 0, 'c'},
3237 {"font", required_argument, 0, 'n'},
3238 {"title", required_argument, 0, 't'},
3239 {"imginfo", required_argument, 0, 'f'},
3240 {"imgformat", required_argument, 0, 'a'},
3241 {"lazy", no_argument, 0, 'z'},
3242 {"zoom", required_argument, 0, 'm'},
3243 {"no-legend", no_argument, 0, 'g'},
3244 {"force-rules-legend",no_argument,0, 'F'},
3245 {"only-graph", no_argument, 0, 'j'},
3246 {"alt-y-grid", no_argument, 0, 'Y'},
3247 {"no-minor", no_argument, 0, 'I'},
3248 {"slope-mode", no_argument, 0, 'E'},
3249 {"alt-autoscale", no_argument, 0, 'A'},
3250 {"alt-autoscale-min", no_argument, 0, 'J'},
3251 {"alt-autoscale-max", no_argument, 0, 'M'},
3252 {"no-gridfit", no_argument, 0, 'N'},
3253 {"units-exponent",required_argument, 0, 'X'},
3254 {"units-length",required_argument, 0, 'L'},
3255 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3256 {"step", required_argument, 0, 'S'},
3257 {"tabwidth", required_argument, 0, 'T'},
3258 {"font-render-mode", required_argument, 0, 'R'},
3259 {"font-smoothing-threshold", required_argument, 0, 'B'},
3260 {"watermark", required_argument, 0, 'W'},
3261 {"alt-y-mrtg", no_argument, 0, 1000}, /* this has no effect it is just here to save old apps from crashing when they use it */
3262 {0,0,0,0}};
3263 int option_index = 0;
3264 int opt;
3265 int col_start,col_end;
3267 opt = getopt_long(argc, argv,
3268 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3269 long_options, &option_index);
3271 if (opt == EOF)
3272 break;
3274 switch(opt) {
3275 case 'I':
3276 im->extra_flags |= NOMINOR;
3277 break;
3278 case 'Y':
3279 im->extra_flags |= ALTYGRID;
3280 break;
3281 case 'A':
3282 im->extra_flags |= ALTAUTOSCALE;
3283 break;
3284 case 'J':
3285 im->extra_flags |= ALTAUTOSCALE_MIN;
3286 break;
3287 case 'M':
3288 im->extra_flags |= ALTAUTOSCALE_MAX;
3289 break;
3290 case 'j':
3291 im->extra_flags |= ONLY_GRAPH;
3292 break;
3293 case 'g':
3294 im->extra_flags |= NOLEGEND;
3295 break;
3296 case 'F':
3297 im->extra_flags |= FORCE_RULES_LEGEND;
3298 break;
3299 case LONGOPT_UNITS_SI:
3300 if(im->extra_flags & FORCE_UNITS) {
3301 rrd_set_error("--units can only be used once!");
3302 return;
3303 }
3304 if(strcmp(optarg,"si")==0)
3305 im->extra_flags |= FORCE_UNITS_SI;
3306 else {
3307 rrd_set_error("invalid argument for --units: %s", optarg );
3308 return;
3309 }
3310 break;
3311 case 'X':
3312 im->unitsexponent = atoi(optarg);
3313 break;
3314 case 'L':
3315 im->unitslength = atoi(optarg);
3316 im->forceleftspace = 1;
3317 break;
3318 case 'T':
3319 im->tabwidth = atof(optarg);
3320 break;
3321 case 'S':
3322 im->step = atoi(optarg);
3323 break;
3324 case 'N':
3325 im->gridfit = 0;
3326 break;
3327 case 's':
3328 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3329 rrd_set_error( "start time: %s", parsetime_error );
3330 return;
3331 }
3332 break;
3333 case 'e':
3334 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3335 rrd_set_error( "end time: %s", parsetime_error );
3336 return;
3337 }
3338 break;
3339 case 'x':
3340 if(strcmp(optarg,"none") == 0){
3341 im->draw_x_grid=0;
3342 break;
3343 };
3345 if(sscanf(optarg,
3346 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3347 scan_gtm,
3348 &im->xlab_user.gridst,
3349 scan_mtm,
3350 &im->xlab_user.mgridst,
3351 scan_ltm,
3352 &im->xlab_user.labst,
3353 &im->xlab_user.precis,
3354 &stroff) == 7 && stroff != 0){
3355 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3356 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3357 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3358 rrd_set_error("unknown keyword %s",scan_gtm);
3359 return;
3360 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3361 rrd_set_error("unknown keyword %s",scan_mtm);
3362 return;
3363 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3364 rrd_set_error("unknown keyword %s",scan_ltm);
3365 return;
3366 }
3367 im->xlab_user.minsec = 1;
3368 im->xlab_user.stst = im->xlab_form;
3369 } else {
3370 rrd_set_error("invalid x-grid format");
3371 return;
3372 }
3373 break;
3374 case 'y':
3376 if(strcmp(optarg,"none") == 0){
3377 im->draw_y_grid=0;
3378 break;
3379 };
3381 if(sscanf(optarg,
3382 "%lf:%d",
3383 &im->ygridstep,
3384 &im->ylabfact) == 2) {
3385 if(im->ygridstep<=0){
3386 rrd_set_error("grid step must be > 0");
3387 return;
3388 } else if (im->ylabfact < 1){
3389 rrd_set_error("label factor must be > 0");
3390 return;
3391 }
3392 } else {
3393 rrd_set_error("invalid y-grid format");
3394 return;
3395 }
3396 break;
3397 case 'v':
3398 strncpy(im->ylegend,optarg,150);
3399 im->ylegend[150]='\0';
3400 break;
3401 case 'u':
3402 im->maxval = atof(optarg);
3403 break;
3404 case 'l':
3405 im->minval = atof(optarg);
3406 break;
3407 case 'b':
3408 im->base = atol(optarg);
3409 if(im->base != 1024 && im->base != 1000 ){
3410 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3411 return;
3412 }
3413 break;
3414 case 'w':
3415 long_tmp = atol(optarg);
3416 if (long_tmp < 10) {
3417 rrd_set_error("width below 10 pixels");
3418 return;
3419 }
3420 im->xsize = long_tmp;
3421 break;
3422 case 'h':
3423 long_tmp = atol(optarg);
3424 if (long_tmp < 10) {
3425 rrd_set_error("height below 10 pixels");
3426 return;
3427 }
3428 im->ysize = long_tmp;
3429 break;
3430 case 'i':
3431 im->canvas->interlaced = 1;
3432 break;
3433 case 'r':
3434 im->rigid = 1;
3435 break;
3436 case 'f':
3437 im->imginfo = optarg;
3438 break;
3439 case 'a':
3440 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3441 rrd_set_error("unsupported graphics format '%s'",optarg);
3442 return;
3443 }
3444 break;
3445 case 'z':
3446 im->lazy = 1;
3447 break;
3448 case 'E':
3449 im->slopemode = 1;
3450 break;
3452 case 'o':
3453 im->logarithmic = 1;
3454 break;
3455 case 'c':
3456 if(sscanf(optarg,
3457 "%10[A-Z]#%n%8lx%n",
3458 col_nam,&col_start,&color,&col_end) == 2){
3459 int ci;
3460 int col_len = col_end - col_start;
3461 switch (col_len){
3462 case 3:
3463 color = (
3464 ((color & 0xF00) * 0x110000) |
3465 ((color & 0x0F0) * 0x011000) |
3466 ((color & 0x00F) * 0x001100) |
3467 0x000000FF
3468 );
3469 break;
3470 case 4:
3471 color = (
3472 ((color & 0xF000) * 0x11000) |
3473 ((color & 0x0F00) * 0x01100) |
3474 ((color & 0x00F0) * 0x00110) |
3475 ((color & 0x000F) * 0x00011)
3476 );
3477 break;
3478 case 6:
3479 color = (color << 8) + 0xff /* shift left by 8 */;
3480 break;
3481 case 8:
3482 break;
3483 default:
3484 rrd_set_error("the color format is #RRGGBB[AA]");
3485 return;
3486 }
3487 if((ci=grc_conv(col_nam)) != -1){
3488 im->graph_col[ci]=color;
3489 } else {
3490 rrd_set_error("invalid color name '%s'",col_nam);
3491 return;
3492 }
3493 } else {
3494 rrd_set_error("invalid color def format");
3495 return;
3496 }
3497 break;
3498 case 'n':{
3499 char prop[15];
3500 double size = 1;
3501 int end;
3502 if(sscanf(optarg,
3503 "%10[A-Z]:%lf%n",
3504 prop,&size,&end) >= 2){
3505 int sindex,propidx;
3506 if((sindex=text_prop_conv(prop)) != -1){
3507 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3508 if (size > 0){
3509 im->text_prop[propidx].size=size;
3510 }
3511 if (strlen(prop) > end){
3512 if (prop[end] == ':'){
3513 strncpy(im->text_prop[propidx].font,prop+end+1,255);
3514 im->text_prop[propidx].font[255] = '\0';
3515 } else {
3516 rrd_set_error("expected after font size in '%s'",prop);
3517 return;
3518 }
3519 }
3520 if (propidx==sindex && sindex != 0) break;
3521 }
3522 } else {
3523 rrd_set_error("invalid fonttag '%s'",prop);
3524 return;
3525 }
3526 } else {
3527 rrd_set_error("invalid text property format");
3528 return;
3529 }
3530 break;
3531 }
3532 case 'm':
3533 im->canvas->zoom = atof(optarg);
3534 if (im->canvas->zoom <= 0.0) {
3535 rrd_set_error("zoom factor must be > 0");
3536 return;
3537 }
3538 break;
3539 case 't':
3540 strncpy(im->title,optarg,150);
3541 im->title[150]='\0';
3542 break;
3544 case 'R':
3545 if ( strcmp( optarg, "normal" ) == 0 )
3546 im->canvas->aa_type = AA_NORMAL;
3547 else if ( strcmp( optarg, "light" ) == 0 )
3548 im->canvas->aa_type = AA_LIGHT;
3549 else if ( strcmp( optarg, "mono" ) == 0 )
3550 im->canvas->aa_type = AA_NONE;
3551 else
3552 {
3553 rrd_set_error("unknown font-render-mode '%s'", optarg );
3554 return;
3555 }
3556 break;
3558 case 'B':
3559 im->canvas->font_aa_threshold = atof(optarg);
3560 break;
3562 case 'W':
3563 strncpy(im->watermark,optarg,100);
3564 im->watermark[99]='\0';
3565 break;
3567 case '?':
3568 if (optopt != 0)
3569 rrd_set_error("unknown option '%c'", optopt);
3570 else
3571 rrd_set_error("unknown option '%s'",argv[optind-1]);
3572 return;
3573 }
3574 }
3576 if (optind >= argc) {
3577 rrd_set_error("missing filename");
3578 return;
3579 }
3581 if (im->logarithmic == 1 && im->minval <= 0){
3582 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3583 return;
3584 }
3586 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3587 /* error string is set in parsetime.c */
3588 return;
3589 }
3591 if (start_tmp < 3600*24*365*10){
3592 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3593 return;
3594 }
3596 if (end_tmp < start_tmp) {
3597 rrd_set_error("start (%ld) should be less than end (%ld)",
3598 start_tmp, end_tmp);
3599 return;
3600 }
3602 im->start = start_tmp;
3603 im->end = end_tmp;
3604 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3605 }
3607 int
3608 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3609 {
3610 char *color;
3611 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3613 color=strstr(var,"#");
3614 if (color==NULL) {
3615 if (optional==0) {
3616 rrd_set_error("Found no color in %s",err);
3617 return 0;
3618 }
3619 return 0;
3620 } else {
3621 int n=0;
3622 char *rest;
3623 gfx_color_t col;
3625 rest=strstr(color,":");
3626 if (rest!=NULL)
3627 n=rest-color;
3628 else
3629 n=strlen(color);
3631 switch (n) {
3632 case 7:
3633 sscanf(color,"#%6lx%n",&col,&n);
3634 col = (col << 8) + 0xff /* shift left by 8 */;
3635 if (n!=7) rrd_set_error("Color problem in %s",err);
3636 break;
3637 case 9:
3638 sscanf(color,"#%8lx%n",&col,&n);
3639 if (n==9) break;
3640 default:
3641 rrd_set_error("Color problem in %s",err);
3642 }
3643 if (rrd_test_error()) return 0;
3644 gdp->col = col;
3645 return n;
3646 }
3647 }
3650 int bad_format(char *fmt) {
3651 char *ptr;
3652 int n=0;
3653 ptr = fmt;
3654 while (*ptr != '\0')
3655 if (*ptr++ == '%') {
3657 /* line cannot end with percent char */
3658 if (*ptr == '\0') return 1;
3660 /* '%s', '%S' and '%%' are allowed */
3661 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3663 /* %c is allowed (but use only with vdef!) */
3664 else if (*ptr == 'c') {
3665 ptr++;
3666 n=1;
3667 }
3669 /* or else '% 6.2lf' and such are allowed */
3670 else {
3671 /* optional padding character */
3672 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3674 /* This should take care of 'm.n' with all three optional */
3675 while (*ptr >= '0' && *ptr <= '9') ptr++;
3676 if (*ptr == '.') ptr++;
3677 while (*ptr >= '0' && *ptr <= '9') ptr++;
3679 /* Either 'le', 'lf' or 'lg' must follow here */
3680 if (*ptr++ != 'l') return 1;
3681 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3682 else return 1;
3683 n++;
3684 }
3685 }
3687 return (n!=1);
3688 }
3691 int
3692 vdef_parse(gdes,str)
3693 struct graph_desc_t *gdes;
3694 const char *const str;
3695 {
3696 /* A VDEF currently is either "func" or "param,func"
3697 * so the parsing is rather simple. Change if needed.
3698 */
3699 double param;
3700 char func[30];
3701 int n;
3703 n=0;
3704 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3705 if (n== (int)strlen(str)) { /* matched */
3706 ;
3707 } else {
3708 n=0;
3709 sscanf(str,"%29[A-Z]%n",func,&n);
3710 if (n== (int)strlen(str)) { /* matched */
3711 param=DNAN;
3712 } else {
3713 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3714 ,str
3715 ,gdes->vname
3716 );
3717 return -1;
3718 }
3719 }
3720 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3721 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3722 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3723 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3724 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3725 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3726 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3727 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3728 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3729 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3730 else {
3731 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3732 ,func
3733 ,gdes->vname
3734 );
3735 return -1;
3736 };
3738 switch (gdes->vf.op) {
3739 case VDEF_PERCENT:
3740 if (isnan(param)) { /* no parameter given */
3741 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3742 ,func
3743 ,gdes->vname
3744 );
3745 return -1;
3746 };
3747 if (param>=0.0 && param<=100.0) {
3748 gdes->vf.param = param;
3749 gdes->vf.val = DNAN; /* undefined */
3750 gdes->vf.when = 0; /* undefined */
3751 } else {
3752 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3753 ,param
3754 ,gdes->vname
3755 );
3756 return -1;
3757 };
3758 break;
3759 case VDEF_MAXIMUM:
3760 case VDEF_AVERAGE:
3761 case VDEF_MINIMUM:
3762 case VDEF_TOTAL:
3763 case VDEF_FIRST:
3764 case VDEF_LAST:
3765 case VDEF_LSLSLOPE:
3766 case VDEF_LSLINT:
3767 case VDEF_LSLCORREL:
3768 if (isnan(param)) {
3769 gdes->vf.param = DNAN;
3770 gdes->vf.val = DNAN;
3771 gdes->vf.when = 0;
3772 } else {
3773 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3774 ,func
3775 ,gdes->vname
3776 );
3777 return -1;
3778 };
3779 break;
3780 };
3781 return 0;
3782 }
3785 int
3786 vdef_calc(im,gdi)
3787 image_desc_t *im;
3788 int gdi;
3789 {
3790 graph_desc_t *src,*dst;
3791 rrd_value_t *data;
3792 long step,steps,end;
3794 dst = &im->gdes[gdi];
3795 src = &im->gdes[dst->vidx];
3796 data = src->data + src->ds;
3797 end = (src->end_orig % src->step) == 0 ? src->end_orig : (src->end_orig + src->step - src->end_orig % src->step);
3798 steps = (end - src->start) / src->step;
3799 #if 0
3800 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3801 ,src->start
3802 ,src->end_orig
3803 ,steps
3804 );
3805 #endif
3807 switch (dst->vf.op) {
3808 case VDEF_PERCENT: {
3809 rrd_value_t * array;
3810 int field;
3813 if ((array = malloc(steps*sizeof(double)))==NULL) {
3814 rrd_set_error("malloc VDEV_PERCENT");
3815 return -1;
3816 }
3817 for (step=0;step < steps; step++) {
3818 array[step]=data[step*src->ds_cnt];
3819 }
3820 qsort(array,step,sizeof(double),vdef_percent_compar);
3822 field = (steps-1)*dst->vf.param/100;
3823 dst->vf.val = array[field];
3824 dst->vf.when = 0; /* no time component */
3825 free(array);
3826 #if 0
3827 for(step=0;step<steps;step++)
3828 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3829 #endif
3830 }
3831 break;
3832 case VDEF_MAXIMUM:
3833 step=0;
3834 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3835 if (step == steps) {
3836 dst->vf.val = DNAN;
3837 dst->vf.when = 0;
3838 } else {
3839 dst->vf.val = data[step*src->ds_cnt];
3840 dst->vf.when = src->start + (step+1)*src->step;
3841 }
3842 while (step != steps) {
3843 if (finite(data[step*src->ds_cnt])) {
3844 if (data[step*src->ds_cnt] > dst->vf.val) {
3845 dst->vf.val = data[step*src->ds_cnt];
3846 dst->vf.when = src->start + (step+1)*src->step;
3847 }
3848 }
3849 step++;
3850 }
3851 break;
3852 case VDEF_TOTAL:
3853 case VDEF_AVERAGE: {
3854 int cnt=0;
3855 double sum=0.0;
3856 for (step=0;step<steps;step++) {
3857 if (finite(data[step*src->ds_cnt])) {
3858 sum += data[step*src->ds_cnt];
3859 cnt ++;
3860 };
3861 }
3862 if (cnt) {
3863 if (dst->vf.op == VDEF_TOTAL) {
3864 dst->vf.val = sum*src->step;
3865 dst->vf.when = 0; /* no time component */
3866 } else {
3867 dst->vf.val = sum/cnt;
3868 dst->vf.when = 0; /* no time component */
3869 };
3870 } else {
3871 dst->vf.val = DNAN;
3872 dst->vf.when = 0;
3873 }
3874 }
3875 break;
3876 case VDEF_MINIMUM:
3877 step=0;
3878 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3879 if (step == steps) {
3880 dst->vf.val = DNAN;
3881 dst->vf.when = 0;
3882 } else {
3883 dst->vf.val = data[step*src->ds_cnt];
3884 dst->vf.when = src->start + (step+1)*src->step;
3885 }
3886 while (step != steps) {
3887 if (finite(data[step*src->ds_cnt])) {
3888 if (data[step*src->ds_cnt] < dst->vf.val) {
3889 dst->vf.val = data[step*src->ds_cnt];
3890 dst->vf.when = src->start + (step+1)*src->step;
3891 }
3892 }
3893 step++;
3894 }
3895 break;
3896 case VDEF_FIRST:
3897 /* The time value returned here is one step before the
3898 * actual time value. This is the start of the first
3899 * non-NaN interval.
3900 */
3901 step=0;
3902 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3903 if (step == steps) { /* all entries were NaN */
3904 dst->vf.val = DNAN;
3905 dst->vf.when = 0;
3906 } else {
3907 dst->vf.val = data[step*src->ds_cnt];
3908 dst->vf.when = src->start + step*src->step;
3909 }
3910 break;
3911 case VDEF_LAST:
3912 /* The time value returned here is the
3913 * actual time value. This is the end of the last
3914 * non-NaN interval.
3915 */
3916 step=steps-1;
3917 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3918 if (step < 0) { /* all entries were NaN */
3919 dst->vf.val = DNAN;
3920 dst->vf.when = 0;
3921 } else {
3922 dst->vf.val = data[step*src->ds_cnt];
3923 dst->vf.when = src->start + (step+1)*src->step;
3924 }
3925 break;
3926 case VDEF_LSLSLOPE:
3927 case VDEF_LSLINT:
3928 case VDEF_LSLCORREL:{
3929 /* Bestfit line by linear least squares method */
3931 int cnt=0;
3932 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3933 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3935 for (step=0;step<steps;step++) {
3936 if (finite(data[step*src->ds_cnt])) {
3937 cnt++;
3938 SUMx += step;
3939 SUMxx += step * step;
3940 SUMxy += step * data[step*src->ds_cnt];
3941 SUMy += data[step*src->ds_cnt];
3942 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3943 };
3944 }
3946 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3947 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3948 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3950 if (cnt) {
3951 if (dst->vf.op == VDEF_LSLSLOPE) {
3952 dst->vf.val = slope;
3953 dst->vf.when = 0;
3954 } else if (dst->vf.op == VDEF_LSLINT) {
3955 dst->vf.val = y_intercept;
3956 dst->vf.when = 0;
3957 } else if (dst->vf.op == VDEF_LSLCORREL) {
3958 dst->vf.val = correl;
3959 dst->vf.when = 0;
3960 };
3962 } else {
3963 dst->vf.val = DNAN;
3964 dst->vf.when = 0;
3965 }
3966 }
3967 break;
3968 }
3969 return 0;
3970 }
3972 /* NaN < -INF < finite_values < INF */
3973 int
3974 vdef_percent_compar(a,b)
3975 const void *a,*b;
3976 {
3977 /* Equality is not returned; this doesn't hurt except
3978 * (maybe) for a little performance.
3979 */
3981 /* First catch NaN values. They are smallest */
3982 if (isnan( *(double *)a )) return -1;
3983 if (isnan( *(double *)b )) return 1;
3985 /* NaN doesn't reach this part so INF and -INF are extremes.
3986 * The sign from isinf() is compatible with the sign we return
3987 */
3988 if (isinf( *(double *)a )) return isinf( *(double *)a );
3989 if (isinf( *(double *)b )) return isinf( *(double *)b );
3991 /* If we reach this, both values must be finite */
3992 if ( *(double *)a < *(double *)b ) return -1; else return 1;
3993 }