+/* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
+/* yes we are loosing precision by doing tos with floats instead of doubles
+ but it seems more stable this way. */
+
+static int AlmostEqual2sComplement (float A, float B, int maxUlps)
+{
+
+ int aInt = *(int*)&A;
+ int bInt = *(int*)&B;
+ int intDiff;
+ /* Make sure maxUlps is non-negative and small enough that the
+ default NAN won't compare as equal to anything. */
+
+ /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
+
+ /* Make aInt lexicographically ordered as a twos-complement int */
+
+ if (aInt < 0)
+ aInt = 0x80000000l - aInt;
+
+ /* Make bInt lexicographically ordered as a twos-complement int */
+
+ if (bInt < 0)
+ bInt = 0x80000000l - bInt;
+
+ intDiff = abs(aInt - bInt);
+
+ if (intDiff <= maxUlps)
+ return 1;
+
+ return 0;
+}
+
+/* draw that picture thing ... */
+int
+graph_paint(image_desc_t *im, char ***calcpr)
+{
+ int i,ii;
+ int lazy = lazy_check(im);
+#ifdef WITH_PIECHART
+ int piechart = 0;
+ double PieStart=0.0;
+#endif
+ FILE *fo;
+ gfx_node_t *node;
+
+ double areazero = 0.0;
+ enum gf_en stack_gf = GF_PRINT;
+ graph_desc_t *lastgdes = NULL;
+
+ /* if we are lazy and there is nothing to PRINT ... quit now */
+ if (lazy && im->prt_c==0) return 0;
+
+ /* pull the data from the rrd files ... */
+
+ if(data_fetch(im)==-1)
+ return -1;
+
+ /* evaluate VDEF and CDEF operations ... */
+ if(data_calc(im)==-1)
+ return -1;
+
+#ifdef WITH_PIECHART
+ /* check if we need to draw a piechart */
+ for(i=0;i<im->gdes_c;i++){
+ if (im->gdes[i].gf == GF_PART) {
+ piechart=1;
+ break;
+ }
+ }
+#endif
+
+ /* calculate and PRINT and GPRINT definitions. We have to do it at
+ * this point because it will affect the length of the legends
+ * if there are no graph elements we stop here ...
+ * if we are lazy, try to quit ...
+ */
+ i=print_calc(im,calcpr);
+ if(i<0) return -1;
+ if(((i==0)
+#ifdef WITH_PIECHART
+&&(piechart==0)
+#endif
+) || lazy) return 0;
+
+#ifdef WITH_PIECHART
+ /* If there's only the pie chart to draw, signal this */
+ if (i==0) piechart=2;
+#endif
+
+ /* get actual drawing data and find min and max values*/
+ if(data_proc(im)==-1)
+ return -1;
+
+ if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
+
+ if(!im->rigid && ! im->logarithmic)
+ expand_range(im); /* make sure the upper and lower limit are
+ sensible values */
+
+ if (!calc_horizontal_grid(im))
+ return -1;
+
+ if (im->gridfit)
+ apply_gridfit(im);
+
+
+/**************************************************************
+ *** Calculating sizes and locations became a bit confusing ***
+ *** so I moved this into a separate function. ***
+ **************************************************************/
+ if(graph_size_location(im,i
+#ifdef WITH_PIECHART
+,piechart
+#endif
+)==-1)
+ return -1;
+
+ /* the actual graph is created by going through the individual
+ graph elements and then drawing them */
+
+ node=gfx_new_area ( im->canvas,
+ 0, 0,
+ 0, im->yimg,
+ im->ximg, im->yimg,
+ im->graph_col[GRC_BACK]);
+
+ gfx_add_point(node,im->ximg, 0);
+
+#ifdef WITH_PIECHART
+ if (piechart != 2) {
+#endif
+ node=gfx_new_area ( im->canvas,
+ im->xorigin, im->yorigin,
+ im->xorigin + im->xsize, im->yorigin,
+ im->xorigin + im->xsize, im->yorigin-im->ysize,
+ im->graph_col[GRC_CANVAS]);
+
+ gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
+
+ if (im->minval > 0.0)
+ areazero = im->minval;
+ if (im->maxval < 0.0)
+ areazero = im->maxval;
+#ifdef WITH_PIECHART
+ }
+#endif
+
+#ifdef WITH_PIECHART
+ if (piechart) {
+ pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
+ }
+#endif
+
+ for(i=0;i<im->gdes_c;i++){
+ switch(im->gdes[i].gf){
+ case GF_CDEF:
+ case GF_VDEF:
+ case GF_DEF:
+ case GF_PRINT:
+ case GF_GPRINT:
+ case GF_COMMENT:
+ case GF_HRULE:
+ case GF_VRULE:
+ case GF_XPORT:
+ case GF_SHIFT:
+ break;
+ case GF_TICK:
+ for (ii = 0; ii < im->xsize; ii++)
+ {
+ if (!isnan(im->gdes[i].p_data[ii]) &&
+ im->gdes[i].p_data[ii] > 0.0)
+ {
+ /* generate a tick */
+ gfx_new_line(im->canvas, im -> xorigin + ii,
+ im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
+ im -> xorigin + ii,
+ im -> yorigin,
+ 1.0,
+ im -> gdes[i].col );
+ }
+ }
+ break;
+ case GF_LINE:
+ case GF_AREA:
+ stack_gf = im->gdes[i].gf;
+ case GF_STACK:
+ /* fix data points at oo and -oo */
+ for(ii=0;ii<im->xsize;ii++){
+ if (isinf(im->gdes[i].p_data[ii])){
+ if (im->gdes[i].p_data[ii] > 0) {
+ im->gdes[i].p_data[ii] = im->maxval ;
+ } else {
+ im->gdes[i].p_data[ii] = im->minval ;
+ }
+
+ }
+ } /* for */
+
+ /* *******************************************************
+ a ___. (a,t)
+ | | ___
+ ____| | | |
+ | |___|
+ -------|--t-1--t--------------------------------
+
+ if we know the value at time t was a then
+ we draw a square from t-1 to t with the value a.
+
+ ********************************************************* */
+ if (im->gdes[i].col != 0x0){
+ /* GF_LINE and friend */
+ if(stack_gf == GF_LINE ){
+ double last_y=0.0;
+ node = NULL;
+ for(ii=1;ii<im->xsize;ii++){
+ if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
+ node = NULL;
+ continue;
+ }
+ if ( node == NULL ) {
+ last_y = ytr(im,im->gdes[i].p_data[ii]);
+ if ( im->slopemode == 0 ){
+ node = gfx_new_line(im->canvas,
+ ii-1+im->xorigin,last_y,
+ ii+im->xorigin,last_y,
+ im->gdes[i].linewidth,
+ im->gdes[i].col);
+ } else {
+ node = gfx_new_line(im->canvas,
+ ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
+ ii+im->xorigin,last_y,
+ im->gdes[i].linewidth,
+ im->gdes[i].col);
+ }
+ } else {
+ double new_y = ytr(im,im->gdes[i].p_data[ii]);
+ if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
+ gfx_add_point(node,ii-1+im->xorigin,new_y);
+ };
+ last_y = new_y;
+ gfx_add_point(node,ii+im->xorigin,new_y);
+ };
+
+ }
+ } else {
+ int idxI=-1;
+ double *foreY=malloc(sizeof(double)*im->xsize*2);
+ double *foreX=malloc(sizeof(double)*im->xsize*2);
+ double *backY=malloc(sizeof(double)*im->xsize*2);
+ double *backX=malloc(sizeof(double)*im->xsize*2);
+ int drawem = 0;
+ for(ii=0;ii<=im->xsize;ii++){
+ double ybase,ytop;
+ if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
+ int cntI=1;
+ int lastI=0;
+ while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
+ node = gfx_new_area(im->canvas,
+ backX[0],backY[0],
+ foreX[0],foreY[0],
+ foreX[cntI],foreY[cntI], im->gdes[i].col);
+ while (cntI < idxI) {
+ lastI = cntI;
+ cntI++;
+ while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
+ gfx_add_point(node,foreX[cntI],foreY[cntI]);
+ }
+ gfx_add_point(node,backX[idxI],backY[idxI]);
+ while (idxI > 1){
+ lastI = idxI;
+ idxI--;
+ while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
+ gfx_add_point(node,backX[idxI],backY[idxI]);
+ }
+ idxI=-1;
+ drawem = 0;
+ }
+ if (drawem != 0){
+ drawem = 0;
+ idxI=-1;
+ }
+ if (ii == im->xsize) break;
+
+ /* keep things simple for now, just draw these bars
+ do not try to build a big and complex area */
+
+
+ if ( im->slopemode == 0 && ii==0){
+ continue;
+ }
+ if ( isnan(im->gdes[i].p_data[ii]) ) {
+ drawem = 1;
+ continue;
+ }
+ ytop = ytr(im,im->gdes[i].p_data[ii]);
+ if ( lastgdes && im->gdes[i].stack ) {
+ ybase = ytr(im,lastgdes->p_data[ii]);
+ } else {
+ ybase = ytr(im,areazero);
+ }
+ if ( ybase == ytop ){
+ drawem = 1;
+ continue;
+ }
+ /* every area has to be wound clock-wise,
+ so we have to make sur base remains base */
+ if (ybase > ytop){
+ double extra = ytop;
+ ytop = ybase;
+ ybase = extra;
+ }
+ if ( im->slopemode == 0 ){
+ backY[++idxI] = ybase-0.2;
+ backX[idxI] = ii+im->xorigin-1;
+ foreY[idxI] = ytop+0.2;
+ foreX[idxI] = ii+im->xorigin-1;
+ }
+ backY[++idxI] = ybase-0.2;
+ backX[idxI] = ii+im->xorigin;
+ foreY[idxI] = ytop+0.2;
+ foreX[idxI] = ii+im->xorigin;
+ }
+ /* close up any remaining area */
+ free(foreY);
+ free(foreX);
+ free(backY);
+ free(backX);
+ } /* else GF_LINE */
+ } /* if color != 0x0 */
+ /* make sure we do not run into trouble when stacking on NaN */
+ for(ii=0;ii<im->xsize;ii++){
+ if (isnan(im->gdes[i].p_data[ii])) {
+ if (lastgdes && (im->gdes[i].stack)) {
+ im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
+ } else {
+ im->gdes[i].p_data[ii] = areazero;
+ }
+ }
+ }
+ lastgdes = &(im->gdes[i]);
+ break;
+#ifdef WITH_PIECHART
+ case GF_PART:
+ if(isnan(im->gdes[i].yrule)) /* fetch variable */
+ im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
+
+ if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
+ pie_part(im,im->gdes[i].col,
+ im->pie_x,im->pie_y,im->piesize*0.4,
+ M_PI*2.0*PieStart/100.0,
+ M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
+ PieStart += im->gdes[i].yrule;
+ }
+ break;
+#endif
+
+ } /* switch */
+ }
+#ifdef WITH_PIECHART
+ if (piechart==2) {
+ im->draw_x_grid=0;
+ im->draw_y_grid=0;
+ }
+#endif
+
+
+ /* grid_paint also does the text */
+ if( !(im->extra_flags & ONLY_GRAPH) )
+ grid_paint(im);
+
+
+ if( !(im->extra_flags & ONLY_GRAPH) )
+ axis_paint(im);
+
+ /* the RULES are the last thing to paint ... */
+ for(i=0;i<im->gdes_c;i++){
+
+ switch(im->gdes[i].gf){
+ case GF_HRULE:
+ if(isnan(im->gdes[i].yrule)) { /* fetch variable */
+ im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
+ };
+ if(im->gdes[i].yrule >= im->minval
+ && im->gdes[i].yrule <= im->maxval)
+ gfx_new_line(im->canvas,
+ im->xorigin,ytr(im,im->gdes[i].yrule),
+ im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
+ 1.0,im->gdes[i].col);
+ break;
+ case GF_VRULE:
+ if(im->gdes[i].xrule == 0) { /* fetch variable */
+ im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
+ };
+ if(im->gdes[i].xrule >= im->start
+ && im->gdes[i].xrule <= im->end)
+ gfx_new_line(im->canvas,
+ xtr(im,im->gdes[i].xrule),im->yorigin,
+ xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
+ 1.0,im->gdes[i].col);
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ if (strcmp(im->graphfile,"-")==0) {
+ fo = im->graphhandle ? im->graphhandle : stdout;
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+ /* Change translation mode for stdout to BINARY */
+ _setmode( _fileno( fo ), O_BINARY );
+#endif
+ } else {
+ if ((fo = fopen(im->graphfile,"wb")) == NULL) {
+ rrd_set_error("Opening '%s' for write: %s",im->graphfile,
+ rrd_strerror(errno));
+ return (-1);
+ }
+ }
+ gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
+ if (strcmp(im->graphfile,"-") != 0)
+ fclose(fo);
+ return 0;
+}
+