1 /*****************************************************************************
6 ** This is part of the CTSim program
7 ** Copyright (C) 1983-2000 Kevin Rosenberg
9 ** $Id: ezplot.cpp,v 1.14 2000/09/04 09:06:46 kevin Exp $
11 ** This program is free software; you can redistribute it and/or modify
12 ** it under the terms of the GNU General Public License (version 2) as
13 ** published by the Free Software Foundation.
15 ** This program is distributed in the hope that it will be useful,
16 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ** GNU General Public License for more details.
20 ** You should have received a copy of the GNU General Public License
21 ** along with this program; if not, write to the Free Software
22 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 ******************************************************************************/
30 static const double TICKRATIO = 0.4; // ratio of minor to major tick lengths
31 static const int MAXNUMFMT = 15; // maximum length of a numeric format
32 static const double DEF_CHARHEIGHT = (1./43.); //size of characters in NDC
33 static const double DEF_CHARWIDTH = (1./80.); // size of characters in NDC
34 static const int DEF_CURVE_CLR = C_RED;
37 EZPlotCurve::EZPlotCurve (const double* xData, const double* yData, int n, int color, int linestyle, int symbol, int symfreq, const string& legend)
38 : x(NULL), y(NULL), m_sLegend (legend)
43 int copyCount = n * sizeof(double);
44 memcpy (x, xData, copyCount);
45 memcpy (y, yData, copyCount);
49 m_iLineStyle = linestyle;
51 m_iSymbolFreq = symfreq;
54 EZPlotCurve::~EZPlotCurve ()
62 EZPlot::addCurve (const double *y, int n)
66 for (int i = 0; i < n; i++)
74 EZPlot::addCurve (const float *y, int n)
78 for (int i = 0; i < n; i++)
81 addCurve (yDouble, n);
86 EZPlot::addCurve (const float x[], const double y[], int num)
90 for (int i = 0; i < num; i++)
93 addCurve (dx, y, num);
97 EZPlot::addCurve (const double x[], const float y[], int num)
101 for (int i = 0; i < num; i++)
104 addCurve (x, dy, num);
109 EZPlot::addCurve (const double x[], const double y[], int num)
114 EZPlotCurve* pCurve = new EZPlotCurve (x, y, num, o_color, o_linestyle, o_symbol, o_symfreq, c_legend);
115 m_vecCurves.push_back (pCurve);
121 for (EZPlotCurveIterator i = m_vecCurves.begin(); i != m_vecCurves.end(); i++)
126 EZPlot::clearCurves ()
128 for (EZPlotCurveIterator i = m_vecCurves.begin(); i != m_vecCurves.end(); i++)
130 m_vecCurves.erase (m_vecCurves.begin(), m_vecCurves.end());
135 EZPlot::EZPlot (SGP& sgp)
142 EZPlot::initPlotSettings ()
144 charheight = DEF_CHARHEIGHT;
145 charwidth = DEF_CHARWIDTH;
168 o_color = DEF_CURVE_CLR;
171 o_linestyle = SGP::LS_SOLID;
178 o_legendbox = INSIDE;
191 clr_axis = C_BLACK; // set fixed colors
195 clr_number = C_GREEN;
201 * plot Plots all curves collected by addCurves ()
207 * This routine plots the curves that have stored by addCurves().
210 * drawAxes() & make_numfmt()
216 if (m_vecCurves.size() <= 0)
219 rSGP.setWindow (0., 0., 1., 1.);
221 if (s_textsize == TRUE) {
222 charheight = v_textsize;
223 charwidth = rSGP.getCharWidth();
225 charheight = rSGP.getCharHeight();
226 charwidth = rSGP.getCharWidth();
229 const EZPlotCurve& firstCurve = *m_vecCurves[0];
230 double xmin = firstCurve.x[0]; // extent of curves in world coord
232 double ymin = firstCurve.y[0];
235 for (EZPlotCurveConstIterator iterCurve = m_vecCurves.begin(); iterCurve != m_vecCurves.end(); iterCurve++) {
236 const EZPlotCurve& curve = **iterCurve;
238 for (int ip = 0; ip < curve.m_iPointCount; ip++) {
239 if (curve.x[ip] > xmax)
241 else if (curve.x[ip] < xmin)
243 if (curve.y[ip] > ymax)
245 else if (curve.y[ip] < ymin)
250 // extend graph limits for user defined axis cross positions
251 if (s_xcross == TRUE) {
254 else if (v_xcross > xmax)
258 if (s_ycross == TRUE) {
261 else if (v_ycross > ymax)
265 // find nice endpoints for axes
266 if (! axis_scale (xmin, xmax, o_xmajortick - 1, &xgw_min, &xgw_max, &x_nint) || ! axis_scale (ymin, ymax, o_ymajortick - 1, &ygw_min, &ygw_max, &y_nint))
269 // check if user set x-axis extents
270 if (s_xmin == TRUE) {
272 x_nint = o_xmajortick - 1;
274 if (s_xmax == TRUE) {
276 x_nint = o_xmajortick - 1;
279 // check if user set y-axis extents
280 if (s_ymin == TRUE) {
282 y_nint = o_ymajortick - 1;
284 if (s_ymax == TRUE) {
286 y_nint = o_ymajortick - 1;
289 // calculate increment between major axis in world coordinates
290 xw_tickinc = (xgw_max - xgw_min) / x_nint;
291 yw_tickinc = (ygw_max - ygw_min) / y_nint;
293 // we have now calcuated xgw_min, xyw_max, ygw_min, & ygw_max
295 // set the number of decimal point to users' setting or default
296 // Two formats for numbers: Fixed: -nnn.f and Exponent: -n.fffE+eee
297 if (s_lxfrac == TRUE)
302 if (s_lyfrac == TRUE)
307 make_numfmt (x_numfmt, &x_fldwid, &x_frac, xgw_min, xgw_max, x_nint);
308 make_numfmt (y_numfmt, &y_fldwid, &y_frac, ygw_min, ygw_max, y_nint);
310 xtl_wid = x_fldwid * charwidth; // calc size of tick labels
311 ytl_wid = y_fldwid * charwidth;
312 tl_height = charheight;
314 // calculate the extent of the plot frame
317 xp_max = xp_min + o_xlength;
318 yp_max = yp_min + o_ylength;
320 xp_min = clamp (xp_min, 0., 1.);
321 xp_max = clamp (xp_max, 0., 1.);
322 yp_min = clamp (yp_min, 0., 1.);
323 yp_max = clamp (yp_max, 0., 1.);
325 xa_min = xp_min; // extent of axes
330 // adjust frame for title
331 if (c_title.length() > 0)
332 ya_max -= 2 * charheight;
333 title_row = ya_max + 2 * charheight;
335 // calculate legend box boundaries
336 int max_leg = 0; // longest legend in characters
337 int num_leg = 0; // number of legend titles
338 for (EZPlotCurveConstIterator iterCurve = m_vecCurves.begin(); iterCurve != m_vecCurves.end(); iterCurve++) {
339 const EZPlotCurve& curve = **iterCurve;
340 int nLegend = curve.m_sLegend.length();
343 max_leg = max (max_leg, nLegend);
347 if (num_leg > 0 && o_legendbox != NOLEGEND) {
348 double leg_width = (max_leg + 2) * charwidth; // size of legend box
349 double leg_height = num_leg * 3 * charheight;
351 if (s_xlegend == TRUE)
355 if (o_legendbox == OUTSIDE)
356 xa_max -= (leg_width + 0.5 * charwidth);
358 xl_min = xl_max - leg_width;
360 if (s_ylegend == TRUE)
365 yl_min = yl_max - leg_height;
367 rSGP.setColor (clr_legend);
368 rSGP.drawRect (xl_min, yl_min, xl_max, yl_max);
370 int iLegend = 0; // current legend position
371 for (EZPlotCurveIterator iterCurve = m_vecCurves.begin(); iterCurve != m_vecCurves.end(); iterCurve++) {
372 const EZPlotCurve& curve = **iterCurve;
374 if (curve.m_sLegend.length() == 0)
377 double xmin = xl_min + 1.0 * charwidth;
378 double xmax = xl_max - 1.0 * charwidth;
379 double y = yl_max - (2.0 + iLegend * 3) * charheight;
381 rSGP.moveAbs (xmin, y + 0.5 * charheight);
382 rSGP.drawText (curve.m_sLegend);
383 rSGP.setColor (curve.m_iColor);
384 if (curve.m_iLineStyle != SGP::LS_NOLINE) {
385 rSGP.setLineStyle (curve.m_iLineStyle);
386 rSGP.moveAbs (xmin, y);
387 rSGP.lineAbs (xmax, y);
389 if (curve.m_iSymbol > 0) {
390 double xinc = (xmax - xmin) / (5 - 1);
391 rSGP.setLineStyle (SGP::LS_SOLID);
392 for (int j = 0; j < 5; j++) {
393 rSGP.moveAbs (xmin + j * xinc, y);
394 symbol(curve.m_iSymbol, 0.5 * charwidth, 0.5 * charheight);
397 ++iLegend; // move to next legend position
399 } // end legend printing
401 // calculate the extent of the axes
403 /*-------------------------*/
404 /* adjust frame for labels */
405 /*-------------------------*/
408 if (c_xlabel.length() > 0)
409 ya_min += 3.0 * charheight;
410 xlbl_row = xp_min; // put x-label on bottom of plot frame
413 if (c_ylabel.length() > 0)
414 xa_min += 3.0 * charwidth; // reverse rSGP.setTextSize because writing text sideways
415 ylbl_col = xp_min + 2 * charwidth;
417 /*------------------------------*/
418 /* adjust frame for tick labels */
419 /*------------------------------*/
421 // Calc offset of tick labels from axes
422 if (o_xaxis == NOAXIS || o_xtlabel == FALSE)
424 else if (o_xticks == BELOW)
425 xtl_ofs = -1.5 * charheight; // kr
426 else if (o_xticks == ABOVE)
427 xtl_ofs = 1.5 * charheight;
429 if (o_yaxis == NOAXIS || o_ytlabel == FALSE)
431 else if (o_yticks == LEFT)
432 ytl_ofs = -(1 + y_fldwid) * charwidth;
433 else if (o_yticks == RIGHT)
434 ytl_ofs = 1.5 * charwidth;
441 // see if need to shrink axis extents and/or tick extents
442 if (xtl_ofs != 0.0 && s_ycross == FALSE) {
443 if (o_xticks == BELOW) {
444 ya_min += 2.5 * charheight;
446 } else if (o_xticks == ABOVE) {
448 yt_min = ya_min + 2.5 * charheight;
450 } else // noaxis, no t-labels, or user set cross
453 if (ytl_ofs != 0.0 && s_xcross == FALSE) {
454 if (o_yticks == LEFT) {
455 xa_min += (1 + y_fldwid) * charwidth;
457 } else if (o_yticks == RIGHT) {
459 xt_min = xa_min + ytl_ofs + y_fldwid * charwidth;
464 // decrease size of graph, if necessary, to accommadate space
465 // between axis boundary and boundary of ticks
466 double x_added_ticks = -1; // number of tick spaces added to axis
467 double y_added_ticks = -1;
468 if (o_xaxis == NOAXIS || o_xtlabel == FALSE)
470 if (o_yaxis == NOAXIS || o_ytlabel == FALSE)
473 if (o_grid == TRUE) {
474 if (x_added_ticks < 0)
476 if (y_added_ticks < 0)
480 if (x_added_ticks < 0) {
481 if (o_yticks == LEFT || s_ycross)
487 if (y_added_ticks < 0) {
488 if (o_xticks == BELOW || s_xcross)
494 xn_tickinc = (xt_max - xt_min) / (x_nint + x_added_ticks);
495 yn_tickinc = (yt_max - yt_min) / (y_nint + y_added_ticks);
497 xt_min += 0.5 * x_added_ticks * xn_tickinc;
498 xt_max -= 0.5 * x_added_ticks * xn_tickinc;
499 yt_min += 0.5 * y_added_ticks * yn_tickinc;
500 yt_max -= 0.5 * y_added_ticks * yn_tickinc;
507 //------------------------------------------------------------------------
509 m_xWorldScale = (xgn_max - xgn_min) / (xgw_max - xgw_min);
510 m_yWorldScale = (ygn_max - ygn_min) / (ygw_max - ygw_min);
514 rSGP.setLineStyle (SGP::LS_SOLID);
517 // size of symbol in NDC
518 double symwidth = charwidth;
519 double symheight = charheight;
521 for (EZPlotCurveIterator iterCurve = m_vecCurves.begin(); iterCurve != m_vecCurves.end(); iterCurve++) {
522 const EZPlotCurve& curve = **iterCurve;
524 rSGP.setColor (curve.m_iColor);
526 if (curve.m_iLineStyle != SGP::LS_NOLINE) {
527 rSGP.setLineStyle (curve.m_iLineStyle);
528 double x = convertWorldToNDC_X (curve.x[0]);
529 double y = convertWorldToNDC_Y (curve.y[0]);
531 for (int i = 1; i < curve.m_iPointCount; i++) {
532 x = convertWorldToNDC_X (curve.x[i]);
533 y = convertWorldToNDC_Y (curve.y[i]);
537 if (curve.m_iSymbol > 0) {
538 rSGP.setLineStyle (SGP::LS_SOLID);
539 double x = convertWorldToNDC_X (curve.x[0]);
540 double y = convertWorldToNDC_Y (curve.y[0]);
542 symbol (curve.m_iSymbol, symwidth, symheight);
543 for (int i = 1; i < curve.m_iPointCount; i++)
544 if (i % curve.m_iSymbolFreq == 0 || i == curve.m_iPointCount - 1) {
545 x = convertWorldToNDC_X (curve.x[i]);
546 y = convertWorldToNDC_Y (curve.y[i]);
548 symbol (curve.m_iSymbol, symwidth, symheight);
557 * drawAxes INTERNAL routine to draw axis & label them
567 double xticklen = 0, yticklen = 0; // length of ticks in NDC
568 double minorinc; // increment between minor axes
569 double xaxispos, yaxispos; // crossing of axes
571 bool axis_near; // TRUE if axis too close to print t-label
576 rSGP.setTextSize (charheight);
577 rSGP.setTextColor (1, -1);
579 if (o_xticks == ABOVE)
580 xticklen = charheight;
581 else if (o_xticks == BELOW)
582 xticklen = -charheight;
584 if (o_yticks == RIGHT)
585 yticklen = charwidth;
586 else if (o_yticks == LEFT)
587 yticklen = -charwidth;
589 if (c_title.length() > 0) {
591 rSGP.setTextSize (charheight * 2.0);
592 rSGP.getTextExtent (c_title.c_str(), &wText, &hText);
593 rSGP.moveAbs (xa_min + (xa_max-xa_min)/2 - wText/2, title_row);
594 rSGP.setTextColor (clr_title, -1);
595 rSGP.drawText (c_title);
596 rSGP.setTextSize (charheight);
599 if (o_grid == TRUE || o_box == TRUE) {
600 rSGP.setColor (clr_axis);
601 rSGP.moveAbs (xa_min, ya_min);
602 rSGP.lineAbs (xa_max, ya_min);
603 rSGP.lineAbs (xa_max, ya_max);
604 rSGP.lineAbs (xa_min, ya_max);
605 rSGP.lineAbs (xa_min, ya_min);
608 // calculate position of axes
611 if (s_ycross == TRUE) { // convert users' world-coord
612 xaxispos = convertWorldToNDC_Y (v_ycross);// axis to its position in NDC
613 x = convertWorldToNDC_X (xgw_min);
618 if (s_xcross == TRUE) { // convert users' world-coord
619 yaxispos = convertWorldToNDC_X (v_xcross);// axis to its NDC position
620 y = convertWorldToNDC_Y (ygw_min);
628 if (o_xaxis == LINEAR) {
631 rSGP.setColor (clr_axis);
632 if (o_tag && !o_grid && !o_box && s_xcross) {
633 rSGP.moveAbs (xa_min, xaxispos - charheight);
634 rSGP.lineAbs (xa_min, xaxispos + charheight);
636 rSGP.moveAbs (xa_min, xaxispos);
637 rSGP.lineAbs (xa_max, xaxispos);
638 if (o_tag && !o_grid && !o_box) {
639 rSGP.moveAbs (xa_max, xaxispos - charheight);
640 rSGP.lineAbs (xa_max, xaxispos + charheight);
643 if (o_grid == TRUE) {
644 rSGP.setColor (clr_grid);
645 for (i = 0; i <= x_nint; i++) {
646 rSGP.moveAbs (xt_min + xn_tickinc * i, ya_max);
647 rSGP.lineAbs (xt_min + xn_tickinc * i, ya_min);
650 rSGP.moveAbs (xa_min + (xa_max-xa_min)/2 - c_xlabel.length()*charwidth, xlbl_row);
651 rSGP.setTextSize (charheight * 2.0);
652 rSGP.setTextColor (clr_label, -1);
653 rSGP.drawText (c_xlabel);
654 rSGP.setTextSize (charheight);
655 minorinc = xn_tickinc / (o_xminortick + 1);
657 for (i = 0; i <= x_nint; i++) {
658 x = xt_min + xn_tickinc * i;
659 rSGP.setColor (clr_axis);
660 rSGP.moveAbs (x, xaxispos);
661 rSGP.lineAbs (x, xaxispos + xticklen);
663 for (j = 1; j <= o_xminortick; j++) {
664 x2 = x + minorinc * j;
665 rSGP.moveAbs (x2, xaxispos);
666 rSGP.lineAbs (x2, xaxispos + TICKRATIO * xticklen);
669 if (xaxispos + xtl_ofs > ya_min && o_yaxis != NOAXIS) {
670 double xw = xgw_min + i * xw_tickinc;
671 double x = convertWorldToNDC_X (xw);
672 double d = x - yaxispos;
673 if (o_yticks == RIGHT && d >= 0 && d < 0.9 * xn_tickinc)
675 if (o_yticks == LEFT && d <= 0 && d > -0.9 * xn_tickinc)
679 if (o_xtlabel == TRUE && axis_near == FALSE) {
680 snprintf (str, sizeof(str), x_numfmt, xgw_min + xw_tickinc * i);
681 numstr = str_skip_head (str, " ");
682 rSGP.moveAbs (x-strlen(numstr)*charwidth/2, xaxispos + xtl_ofs);
683 rSGP.setTextColor (clr_number, -1);
684 rSGP.drawText (numstr);
694 if (o_yaxis == LINEAR) {
696 rSGP.setColor (clr_axis);
697 if (o_tag && !o_grid && !o_box && s_ycross) {
698 rSGP.moveAbs (yaxispos - charwidth, ya_min);
699 rSGP.lineAbs (yaxispos + charwidth, ya_min);
701 rSGP.moveAbs (yaxispos, ya_min);
702 rSGP.lineAbs (yaxispos, ya_max);
703 if (o_tag && !o_grid && !o_box) {
704 rSGP.moveAbs (yaxispos - charwidth, ya_max);
705 rSGP.lineAbs (yaxispos + charwidth, ya_max);
708 if (o_grid == TRUE) {
709 rSGP.setColor (clr_grid);
710 for (i = 0; i <= y_nint; i++) {
711 y = yt_min + yn_tickinc * i;
712 rSGP.moveAbs (xa_max, y);
713 rSGP.lineAbs (xa_min, y);
716 rSGP.moveAbs (ylbl_col, ya_min + (ya_max-ya_min)/2 - c_ylabel.length()*charheight);
717 rSGP.setTextAngle (HALFPI);
718 rSGP.setTextSize (2 * charheight);
719 rSGP.setTextColor (clr_label, -1);
720 rSGP.drawText (c_ylabel);
721 rSGP.setTextAngle (0.0);
722 rSGP.setTextSize (charheight);
723 minorinc = yn_tickinc / (o_yminortick + 1);
725 for (i = 0; i <= y_nint; i++) {
726 y = yt_min + yn_tickinc * i;
727 rSGP.setColor (clr_axis);
728 rSGP.moveAbs (yaxispos, y);
729 rSGP.lineAbs (yaxispos + yticklen, y);
731 for (j = 1; j <= o_yminortick; j++) {
732 y2 = y + minorinc * j;
733 rSGP.moveAbs (yaxispos, y2);
734 rSGP.lineAbs (yaxispos + TICKRATIO * yticklen, y2);
737 if (yaxispos + ytl_ofs > xa_min && o_xaxis != NOAXIS) {
738 double yw = ygw_min + i * yw_tickinc;
739 double y = convertWorldToNDC_Y (yw);
740 double d = y - xaxispos;
741 if (o_xticks == ABOVE && d >= 0 && d < 0.9 * yn_tickinc)
743 if (o_xticks == BELOW && d <= 0 && d > -0.9 * yn_tickinc)
746 if (o_ytlabel == TRUE && axis_near == FALSE) {
747 snprintf (str, sizeof(str), y_numfmt, ygw_min + yw_tickinc * i);
748 rSGP.moveAbs (yaxispos + ytl_ofs, y + 0.5 * charheight);
749 rSGP.setTextColor (clr_number, -1);
758 EZPlot::symbol (int sym, double symwidth, double symheight)
763 if (sym == SB_CROSS) {
764 rSGP.moveRel (-0.5 * symwidth, -0.5 * symheight);
765 rSGP.lineRel (symwidth, symheight);
766 rSGP.moveRel (-symwidth, 0.0);
767 rSGP.lineRel (symwidth, -symheight);
768 rSGP.moveRel (-0.5 * symwidth, 0.5 * symheight);
769 } else if (sym == SB_PLUS) {
770 rSGP.moveRel (-0.5 * symwidth, 0.0);
771 rSGP.lineRel (symwidth, 0.0);
772 rSGP.moveRel (-0.5 * symwidth, -0.5 * symheight);
773 rSGP.lineRel (0.0, symheight);
774 rSGP.moveRel (0.0, -0.5 * symheight);
775 } else if (sym == SB_BOX) {
776 rSGP.moveRel (-0.5 * symwidth, -0.5 * symheight);
777 rSGP.lineRel (symwidth, 0.0);
778 rSGP.lineRel (0.0, symheight);
779 rSGP.lineRel (-symwidth, 0.0);
780 rSGP.lineRel (0.0, -symheight);
781 rSGP.moveRel (0.5 * symwidth, 0.5 * symheight);
782 } else if (sym == SB_CIRCLE) {
783 rSGP.drawCircle (symwidth);
784 } else if (sym == SB_ERRORBAR) {
785 rSGP.moveRel (-0.5 * symwidth, 0.5 * symheight);
786 rSGP.lineRel (symwidth, 0.0);
787 rSGP.moveRel (-0.5 * symwidth, 0.0);
788 rSGP.lineRel (0.0, -symheight);
789 rSGP.moveRel (-0.5 * symwidth, 0.0);
790 rSGP.lineRel (symwidth, 0.0);
791 rSGP.moveRel (-0.5 * symwidth, 0.5 * symheight);
798 * axis_scale calculates graph axis scaling
801 * retval = axis_scale (min, max, nint, minp, maxp, nintp,
802 * rec_total, rec_frac)
805 * double min Smallest value to plot
806 * double max Largest value to plot
807 * int nint Number of intervals desired
810 * int retval FALSE if illegal parameters, else TRUE
811 * double *minp Minimum graph value
812 * double *maxp Maximum graph value
813 * int *nintp Number of intervals for graph
814 * int *rec_total Recommended field width for printing out the number
815 * int *rec_frac Recommended number of digits for print fraction
819 EZPlot::axis_scale (double min, double max, int nint, double *minp, double *maxp, int *nintp)
821 if (min >= max || nint < 1) {
822 sys_error (ERR_WARNING, "Invalid params: min=%lf, max=%lf, num intervals=%d [axis_scale]", min, max, nint);
827 double a = fabs(min);
828 if (fabs(min) < fabs(max))
830 double scale = pow (10.0, floor(log10(a)));
832 double mina = min / scale;
833 double maxa = max / scale;
834 double d = (maxa - mina) / nint;
836 double e = floor (log10(d));
837 double f = d / pow (10.0, e);
841 else if (f < sqrt (10.0))
843 else if (f < sqrt (50.0))
845 double wdt = v * pow (10.0, e);
846 double g = floor (mina / wdt);
847 if (fabs(g + 1 - mina / wdt) < j)
853 double h = floor (maxa / wdt) + 1.0;
854 if (fabs(maxa / wdt + 1 - h) < j)
860 *nintp = static_cast<int>(h - g);
861 if (fabs(*maxp) >= 10.0 || fabs(*minp) >= 10.0) {
862 scale = scale * 10.0;
874 * make_numfmt Make a numeric format string
877 * make_numfmt (fmtstr, fldwid, nfrac, min, max, nint)
878 * char *fmtstr Returned format string for printf()
879 * int *fldwid Returned field width
880 * int *nfrac If < 0, then calculate best number of
881 * fraction places & return that value
882 * If >= 0, then use that number of places
883 * double min Minimum value
884 * double max Maximum value
885 * int nint Number of intervals between min & max
888 * This routine is written as an INTERNAL routine for EZPLOT
902 EZPlot::make_numfmt (char *fmtstr, int *fldwid, int *nfrac, double minval, double maxval, int nint)
904 int wid, frac, expon;
906 double delta = (maxval - minval) / nint;
907 double absmin = fabs(minval);
908 double absmax = fabs(maxval);
909 double logt = log10( max(absmin, absmax) );
911 if (fabs(logt) >= 6) { // use exponential format
917 if (*nfrac < 0) { // calculate frac
918 delta /= pow (10., floor(logt)); // scale delta
919 frac = static_cast<int>(fabs(trunc(log10(delta)))) + 1;
921 frac = 1; // to be safe, add decimal pt
922 } else // use users' frac
925 wid = 2 + frac + expon;
926 if (minval < 0. || maxval < 0.)
928 sprintf (fmtstr, "%s%d%s%d%s", "%", wid, ".", frac, "g");
929 } else { // use fixed format
930 wid = static_cast<int>(trunc(logt)) + 1;
933 if (minval < 0. || maxval < 0.)
936 if (*nfrac < 0) { // calculate frac
937 if (delta >= 0.999999)
938 frac = 1; // add a decimal pt to be safe
940 frac = static_cast<int>(fabs(trunc(log10(delta)))) + 1;
941 } else // use users' frac
945 sprintf (fmtstr, "%s%d%s%d%s", "%", wid, ".", frac, "f");