+/* create a grid on the graph. it determines what to do
+ from the values of xsize, start and end */
+
+/* the xaxis labels are determined from the number of seconds per pixel
+ in the requested graph */
+
+
+
+int calc_horizontal_grid(
+ image_desc_t *im)
+{
+ double range;
+ double scaledrange;
+ int pixel, i;
+ int gridind = 0;
+ int decimals, fractionals;
+
+ im->ygrid_scale.labfact = 2;
+ range = im->maxval - im->minval;
+ scaledrange = range / im->magfact;
+
+ /* does the scale of this graph make it impossible to put lines
+ on it? If so, give up. */
+ if (isnan(scaledrange)) {
+ return 0;
+ }
+
+ /* find grid spaceing */
+ pixel = 1;
+ if (isnan(im->ygridstep)) {
+ if (im->extra_flags & ALTYGRID) {
+ /* find the value with max number of digits. Get number of digits */
+ decimals =
+ ceil(log10
+ (max(fabs(im->maxval), fabs(im->minval)) *
+ im->viewfactor / im->magfact));
+ if (decimals <= 0) /* everything is small. make place for zero */
+ decimals = 1;
+
+ im->ygrid_scale.gridstep =
+ pow((double) 10,
+ floor(log10(range * im->viewfactor / im->magfact))) /
+ im->viewfactor * im->magfact;
+
+ if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
+ im->ygrid_scale.gridstep = 0.1;
+ /* should have at least 5 lines but no more then 15 */
+ if (range / im->ygrid_scale.gridstep < 5)
+ im->ygrid_scale.gridstep /= 10;
+ if (range / im->ygrid_scale.gridstep > 15)
+ im->ygrid_scale.gridstep *= 10;
+ if (range / im->ygrid_scale.gridstep > 5) {
+ im->ygrid_scale.labfact = 1;
+ if (range / im->ygrid_scale.gridstep > 8)
+ im->ygrid_scale.labfact = 2;
+ } else {
+ im->ygrid_scale.gridstep /= 5;
+ im->ygrid_scale.labfact = 5;
+ }
+ fractionals =
+ floor(log10
+ (im->ygrid_scale.gridstep *
+ (double) im->ygrid_scale.labfact * im->viewfactor /
+ im->magfact));
+ if (fractionals < 0) { /* small amplitude. */
+ int len = decimals - fractionals + 1;
+
+ if (im->unitslength < len + 2)
+ im->unitslength = len + 2;
+ sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
+ -fractionals, (im->symbol != ' ' ? " %c" : ""));
+ } else {
+ int len = decimals + 1;
+
+ if (im->unitslength < len + 2)
+ im->unitslength = len + 2;
+ sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
+ (im->symbol != ' ' ? " %c" : ""));
+ }
+ } else {
+ for (i = 0; ylab[i].grid > 0; i++) {
+ pixel = im->ysize / (scaledrange / ylab[i].grid);
+ gridind = i;
+ if (pixel > 7)
+ break;
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (pixel * ylab[gridind].lfac[i] >=
+ 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
+ im->ygrid_scale.labfact = ylab[gridind].lfac[i];
+ break;
+ }
+ }
+
+ im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
+ }
+ } else {
+ im->ygrid_scale.gridstep = im->ygridstep;
+ im->ygrid_scale.labfact = im->ylabfact;
+ }
+ return 1;
+}
+
+int draw_horizontal_grid(
+ image_desc_t *im)
+{
+ int i;
+ double scaledstep;
+ char graph_label[100];
+ int nlabels = 0;
+ double X0 = im->xorigin;
+ double X1 = im->xorigin + im->xsize;
+
+ int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
+ int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
+ double MaxY;
+
+ scaledstep =
+ im->ygrid_scale.gridstep / (double) im->magfact *
+ (double) im->viewfactor;
+ MaxY = scaledstep * (double) egrid;
+ for (i = sgrid; i <= egrid; i++) {
+ double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
+ double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
+
+ if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
+ && floor(Y0 + 0.5) <= im->yorigin) {
+ /* Make sure at least 2 grid labels are shown, even if it doesn't agree
+ with the chosen settings. Add a label if required by settings, or if
+ there is only one label so far and the next grid line is out of bounds. */
+ if (i % im->ygrid_scale.labfact == 0
+ || (nlabels == 1
+ && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
+ if (im->symbol == ' ') {
+ if (im->extra_flags & ALTYGRID) {
+ sprintf(graph_label, im->ygrid_scale.labfmt,
+ scaledstep * (double) i);
+ } else {
+ if (MaxY < 10) {
+ sprintf(graph_label, "%4.1f",
+ scaledstep * (double) i);
+ } else {
+ sprintf(graph_label, "%4.0f",
+ scaledstep * (double) i);
+ }
+ }
+ } else {
+ char sisym = (i == 0 ? ' ' : im->symbol);
+
+ if (im->extra_flags & ALTYGRID) {
+ sprintf(graph_label, im->ygrid_scale.labfmt,
+ scaledstep * (double) i, sisym);
+ } else {
+ if (MaxY < 10) {
+ sprintf(graph_label, "%4.1f %c",
+ scaledstep * (double) i, sisym);
+ } else {
+ sprintf(graph_label, "%4.0f %c",
+ scaledstep * (double) i, sisym);
+ }
+ }
+ }
+ nlabels++;
+
+ gfx_text(im,
+ X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
+ im->graph_col[GRC_FONT],
+ im->text_prop[TEXT_PROP_AXIS].font,
+ im->text_prop[TEXT_PROP_AXIS].size,
+ im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
+ graph_label);
+ gfx_line(im,
+ X0 - 2, Y0,
+ X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
+ gfx_line(im,
+ X1, Y0,
+ X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
+ gfx_dashed_line(im,
+ X0 - 2, Y0,
+ X1 + 2, Y0,
+ MGRIDWIDTH, im->graph_col[GRC_MGRID],
+ im->grid_dash_on, im->grid_dash_off);
+
+ } else if (!(im->extra_flags & NOMINOR)) {
+ gfx_line(im,
+ X0 - 2, Y0,
+ X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_line(im,
+ X1, Y0,
+ X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im,
+ X0 - 1, Y0,
+ X1 + 1, Y0,
+ GRIDWIDTH, im->graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+
+ }
+ }
+ }
+ return 1;
+}
+
+/* this is frexp for base 10 */
+double frexp10(
+ double,
+ double *);
+double frexp10(
+ double x,
+ double *e)
+{
+ double mnt;
+ int iexp;
+
+ iexp = floor(log(fabs(x)) / log(10));
+ mnt = x / pow(10.0, iexp);
+ if (mnt >= 10.0) {
+ iexp++;
+ mnt = x / pow(10.0, iexp);
+ }
+ *e = iexp;
+ return mnt;
+}
+
+/* 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;
+}
+
+/* logaritmic horizontal grid */
+int horizontal_log_grid(
+ image_desc_t *im)
+{
+ double yloglab[][10] = {
+ {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
+ {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
+ {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
+ {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
+ {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
+ };
+
+ int i, j, val_exp, min_exp;
+ double nex; /* number of decades in data */
+ double logscale; /* scale in logarithmic space */
+ int exfrac = 1; /* decade spacing */
+ int mid = -1; /* row in yloglab for major grid */
+ double mspac; /* smallest major grid spacing (pixels) */
+ int flab; /* first value in yloglab to use */
+ double value, tmp, pre_value;
+ double X0, X1, Y0;
+ char graph_label[100];
+
+ nex = log10(im->maxval / im->minval);
+ logscale = im->ysize / nex;
+
+ /* major spacing for data with high dynamic range */
+ while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
+ if (exfrac == 1)
+ exfrac = 3;
+ else
+ exfrac += 3;
+ }
+
+ /* major spacing for less dynamic data */
+ do {
+ /* search best row in yloglab */
+ mid++;
+ for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
+ mspac = logscale * log10(10.0 / yloglab[mid][i]);
+ } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
+ && yloglab[mid][0] > 0);
+ if (mid)
+ mid--;
+
+ /* find first value in yloglab */
+ for (flab = 0;
+ yloglab[mid][flab] < 10
+ && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
+ if (yloglab[mid][flab] == 10.0) {
+ tmp += 1.0;
+ flab = 0;
+ }
+ val_exp = tmp;
+ if (val_exp % exfrac)
+ val_exp += abs(-val_exp % exfrac);
+
+ X0 = im->xorigin;
+ X1 = im->xorigin + im->xsize;
+
+ /* draw grid */
+ pre_value = DNAN;
+ while (1) {
+
+ value = yloglab[mid][flab] * pow(10.0, val_exp);
+ if (AlmostEqual2sComplement(value, pre_value, 4))
+ break; /* it seems we are not converging */
+
+ pre_value = value;
+
+ Y0 = ytr(im, value);
+ if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
+ break;
+
+ /* major grid line */
+
+ gfx_line(im,
+ X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
+ gfx_line(im,
+ X1, Y0, X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
+
+
+ gfx_dashed_line(im,
+ X0 - 2, Y0,
+ X1 + 2, Y0,
+ MGRIDWIDTH, im->graph_col[GRC_MGRID],
+ im->grid_dash_on, im->grid_dash_off);
+
+ /* label */
+ if (im->extra_flags & FORCE_UNITS_SI) {
+ int scale;
+ double pvalue;
+ char symbol;
+
+ scale = floor(val_exp / 3.0);
+ if (value >= 1.0)
+ pvalue = pow(10.0, val_exp % 3);
+ else
+ pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
+ pvalue *= yloglab[mid][flab];
+
+ if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
+ ((scale + si_symbcenter) >= 0))
+ symbol = si_symbol[scale + si_symbcenter];
+ else
+ symbol = '?';
+
+ sprintf(graph_label, "%3.0f %c", pvalue, symbol);
+ } else
+ sprintf(graph_label, "%3.0e", value);
+ gfx_text(im,
+ X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
+ im->graph_col[GRC_FONT],
+ im->text_prop[TEXT_PROP_AXIS].font,
+ im->text_prop[TEXT_PROP_AXIS].size,
+ im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label);
+
+ /* minor grid */
+ if (mid < 4 && exfrac == 1) {
+ /* find first and last minor line behind current major line
+ * i is the first line and j tha last */
+ if (flab == 0) {
+ min_exp = val_exp - 1;
+ for (i = 1; yloglab[mid][i] < 10.0; i++);
+ i = yloglab[mid][i - 1] + 1;
+ j = 10;
+ } else {
+ min_exp = val_exp;
+ i = yloglab[mid][flab - 1] + 1;
+ j = yloglab[mid][flab];
+ }
+
+ /* draw minor lines below current major line */
+ for (; i < j; i++) {
+
+ value = i * pow(10.0, min_exp);
+ if (value < im->minval)
+ continue;
+
+ Y0 = ytr(im, value);
+ if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
+ break;
+
+ /* draw lines */
+ gfx_line(im,
+ X0 - 2, Y0,
+ X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_line(im,
+ X1, Y0,
+ X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im,
+ X0 - 1, Y0,
+ X1 + 1, Y0,
+ GRIDWIDTH, im->graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+ }
+ } else if (exfrac > 1) {
+ for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
+ value = pow(10.0, i);
+ if (value < im->minval)
+ continue;
+
+ Y0 = ytr(im, value);
+ if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
+ break;
+
+ /* draw lines */
+ gfx_line(im,
+ X0 - 2, Y0,
+ X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_line(im,
+ X1, Y0,
+ X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im,
+ X0 - 1, Y0,
+ X1 + 1, Y0,
+ GRIDWIDTH, im->graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+ }
+ }
+
+ /* next decade */
+ if (yloglab[mid][++flab] == 10.0) {
+ flab = 0;
+ val_exp += exfrac;
+ }
+ }
+
+ /* draw minor lines after highest major line */
+ if (mid < 4 && exfrac == 1) {
+ /* find first and last minor line below current major line
+ * i is the first line and j tha last */
+ if (flab == 0) {
+ min_exp = val_exp - 1;
+ for (i = 1; yloglab[mid][i] < 10.0; i++);
+ i = yloglab[mid][i - 1] + 1;
+ j = 10;
+ } else {
+ min_exp = val_exp;
+ i = yloglab[mid][flab - 1] + 1;
+ j = yloglab[mid][flab];
+ }
+
+ /* draw minor lines below current major line */
+ for (; i < j; i++) {
+
+ value = i * pow(10.0, min_exp);
+ if (value < im->minval)
+ continue;
+
+ Y0 = ytr(im, value);
+ if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
+ break;
+
+ /* draw lines */
+ gfx_line(im,
+ X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_line(im,
+ X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im,
+ X0 - 1, Y0,
+ X1 + 1, Y0,
+ GRIDWIDTH, im->graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+ }
+ }
+ /* fancy minor gridlines */
+ else if (exfrac > 1) {
+ for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
+ value = pow(10.0, i);
+ if (value < im->minval)
+ continue;
+
+ Y0 = ytr(im, value);
+ if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
+ break;
+
+ /* draw lines */
+ gfx_line(im,
+ X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_line(im,
+ X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im,
+ X0 - 1, Y0,
+ X1 + 1, Y0,
+ GRIDWIDTH, im->graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+ }
+ }
+
+ return 1;
+}
+
+
+void vertical_grid(
+ image_desc_t *im)
+{
+ int xlab_sel; /* which sort of label and grid ? */
+ time_t ti, tilab, timajor;
+ long factor;
+ char graph_label[100];
+ double X0, Y0, Y1; /* points for filled graph and more */
+ struct tm tm;
+
+ /* the type of time grid is determined by finding
+ the number of seconds per pixel in the graph */
+
+
+ if (im->xlab_user.minsec == -1) {
+ factor = (im->end - im->start) / im->xsize;
+ xlab_sel = 0;
+ while (xlab[xlab_sel + 1].minsec != -1
+ && xlab[xlab_sel + 1].minsec <= factor) {
+ xlab_sel++;
+ } /* pick the last one */
+ while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
+ && xlab[xlab_sel].length > (im->end - im->start)) {
+ xlab_sel--;
+ } /* go back to the smallest size */
+ im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
+ im->xlab_user.gridst = xlab[xlab_sel].gridst;
+ im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
+ im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
+ im->xlab_user.labtm = xlab[xlab_sel].labtm;
+ im->xlab_user.labst = xlab[xlab_sel].labst;
+ im->xlab_user.precis = xlab[xlab_sel].precis;
+ im->xlab_user.stst = xlab[xlab_sel].stst;
+ }
+
+ /* y coords are the same for every line ... */
+ Y0 = im->yorigin;
+ Y1 = im->yorigin - im->ysize;
+
+
+ /* paint the minor grid */
+ if (!(im->extra_flags & NOMINOR)) {
+ for (ti = find_first_time(im->start,
+ im->xlab_user.gridtm,
+ im->xlab_user.gridst),
+ timajor = find_first_time(im->start,
+ im->xlab_user.mgridtm,
+ im->xlab_user.mgridst);
+ ti < im->end;
+ ti =
+ find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
+ ) {
+ /* are we inside the graph ? */
+ if (ti < im->start || ti > im->end)
+ continue;
+ while (timajor < ti) {
+ timajor = find_next_time(timajor,
+ im->xlab_user.mgridtm,
+ im->xlab_user.mgridst);
+ }
+ if (ti == timajor)
+ continue; /* skip as falls on major grid line */
+ X0 = xtr(im, ti);
+ gfx_line(im, X0, Y1 - 2, X0, Y1, GRIDWIDTH,
+ im->graph_col[GRC_GRID]);
+ gfx_line(im, X0, Y0, X0, Y0 + 2, GRIDWIDTH,
+ im->graph_col[GRC_GRID]);
+ gfx_dashed_line(im, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
+ im->graph_col[GRC_GRID],
+ im->grid_dash_on, im->grid_dash_off);
+
+ }
+ }
+
+ /* paint the major grid */
+ for (ti = find_first_time(im->start,
+ im->xlab_user.mgridtm,
+ im->xlab_user.mgridst);
+ ti < im->end;
+ ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
+ ) {
+ /* are we inside the graph ? */
+ if (ti < im->start || ti > im->end)
+ continue;
+ X0 = xtr(im, ti);
+ gfx_line(im, X0, Y1 - 2, X0, Y1, MGRIDWIDTH,
+ im->graph_col[GRC_MGRID]);
+ gfx_line(im, X0, Y0, X0, Y0 + 3, MGRIDWIDTH,
+ im->graph_col[GRC_MGRID]);
+ gfx_dashed_line(im, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
+ im->graph_col[GRC_MGRID],
+ im->grid_dash_on, im->grid_dash_off);
+
+ }
+ /* paint the labels below the graph */
+ for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
+ im->xlab_user.labtm,
+ im->xlab_user.labst);
+ ti <= im->end - im->xlab_user.precis / 2;
+ ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
+ ) {
+ tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
+ /* are we inside the graph ? */
+ if (tilab < im->start || tilab > im->end)
+ continue;
+
+#if HAVE_STRFTIME
+ localtime_r(&tilab, &tm);
+ strftime(graph_label, 99, im->xlab_user.stst, &tm);
+#else
+# error "your libc has no strftime I guess we'll abort the exercise here."
+#endif
+ gfx_text(im,
+ xtr(im, tilab),
+ Y0 + 3,
+ im->graph_col[GRC_FONT],
+ im->text_prop[TEXT_PROP_AXIS].font,
+ im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
+ GFX_H_CENTER, GFX_V_TOP, graph_label);
+
+ }