7038132c128db2a6ed0f4ef68457676594aadbda
[ctsim.git] / libctgraphics / ezplot.cpp
1 /*****************************************************************************
2 ** FILE IDENTIFICATION
3 **
4 **    EZPLOT                                    
5 **
6 **  This is part of the CTSim program
7 **  Copyright (C) 1983-2000 Kevin Rosenberg
8 **
9 **  $Id: ezplot.cpp,v 1.11 2000/08/22 07:02:48 kevin Exp $
10 **
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.
14 **
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.
19 **
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 ******************************************************************************/
24
25 #include "ezplot.h"
26 #include <algorithm>
27
28
29 // Defaults
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;
35
36
37
38 EZPlotCurve::EZPlotCurve (const double* xData, const double* yData, int n, int color, int linestyle, int symbol, int symfreq, const string& legend)
39   : x(NULL), y(NULL), m_sLegend (legend)
40 {
41   x = new double [n];
42   y = new double [n];
43
44   int copyCount = n * sizeof(double);
45   memcpy (x, xData, copyCount);
46   memcpy (y, yData, copyCount);
47
48   m_iPointCount = n;
49   m_iColor = color;
50   m_iLineStyle = linestyle;
51   m_iSymbol = symbol;
52   m_iSymbolFreq = symfreq;
53 }
54
55 EZPlotCurve::~EZPlotCurve ()
56 {
57   delete x;
58   delete y;
59 }
60
61
62 void 
63 EZPlot::addCurve (const double *y, int n)
64 {
65   double x [n];
66
67   for (int i = 0; i < n; i++)
68     x[i] = i;
69
70   addCurve (x, y, n);
71 }
72
73
74 void
75 EZPlot::addCurve (const float x[], const double y[], int num)
76 {
77   double dx [num];
78
79   for (int i = 0; i < num; i++)
80     dx[i] = x[i];
81
82   addCurve (dx, y, num);
83 }
84
85 void
86 EZPlot::addCurve (const double x[], const float y[], int num)
87 {
88   double dy [num];
89
90   for (int i = 0; i < num; i++)
91     dy[i] = y[i];
92
93   addCurve (x, dy, num);
94 }
95
96
97 void
98 EZPlot::addCurve (const double x[], const double y[], int num)
99 {
100   if (num < 1)
101     return;
102
103   EZPlotCurve* pCurve = new EZPlotCurve (x, y, num, o_color, o_linestyle, o_symbol, o_symfreq, c_legend);
104   m_vecCurves.push_back (pCurve);
105 }
106
107
108 EZPlot::~EZPlot ()
109 {
110   for (EZPlotCurveIterator i = m_vecCurves.begin(); i != m_vecCurves.end(); i++)
111     delete *i;
112 }
113
114 void
115 EZPlot::clearCurves ()
116 {
117   for (EZPlotCurveIterator i = m_vecCurves.begin(); i != m_vecCurves.end(); i++)    
118     delete *i;
119   m_vecCurves.erase (m_vecCurves.begin(), m_vecCurves.end());
120   initPlotSettings();
121 }
122
123
124 EZPlot::EZPlot (SGP& sgp)
125   : rSGP (sgp)
126 {
127     initPlotSettings();
128 }
129
130 void
131 EZPlot::initPlotSettings ()
132 {
133   charwidth = DEF_CHARWIDTH;    
134   charheight = DEF_CHARHEIGHT;
135   
136   c_xlabel = "";
137   c_ylabel =  "";
138   c_title = "";
139   c_legend = "";
140   
141   o_xporigin = 0.0;
142   o_yporigin = 0.0;
143   o_xlength  = 1.0;
144   o_ylength  = 1.0;
145   
146   o_xaxis = LINEAR;
147   o_yaxis = LINEAR;
148   
149   o_grid = FALSE;
150   o_box = FALSE;
151   
152   o_xmajortick = 10;
153   o_ymajortick =  8;
154   o_xminortick =  4;
155   o_yminortick =  4;
156   
157   o_color = DEF_CURVE_CLR;
158   o_symfreq = 1;
159   o_symbol = -1;
160   o_linestyle = SGP::LS_SOLID;
161   
162   o_xtlabel = TRUE;
163   o_ytlabel = TRUE;
164   o_xticks = BELOW;
165   o_yticks = LEFT;
166   
167   o_legendbox = INSIDE;
168   o_tag = FALSE;
169   
170   s_xtitle   = FALSE;
171   s_ytitle   = FALSE;
172   s_xcross   = FALSE;
173   s_ycross   = FALSE;
174   s_lxfrac   = FALSE;
175   s_lyfrac   = FALSE;
176   s_xlegend  = FALSE;
177   s_ylegend  = FALSE;
178   s_textsize = FALSE;
179   
180   clr_axis   = C_BLACK;         /* set fixed colors */
181   clr_title  = C_CYAN;
182   clr_label  = C_CYAN;
183   clr_legend = C_RED;
184   clr_number = C_GREEN;
185   clr_grid   = C_LTGRAY;
186 }
187
188
189 /* NAME
190  *   plot               Plots all curves collected by addCurves ()
191  *
192  * SYNOPSIS
193  *   plot()
194  *
195  * DESCRIPTION
196  *   This routine plots the curves that have stored by addCurves().
197  *
198  * CALLS
199  *   drawAxes() & make_numfmt()
200  */
201
202 void
203 EZPlot::plot ()
204 {
205   double x_added_ticks;         /* number of tick spaces added to axis */
206   double y_added_ticks;
207   double symwidth, symheight;   /* size of symbol in NDC */
208   double leg_width, leg_height; /* size of legend box */
209   int i, j, ip, n;
210   
211   if (m_vecCurves.size() <= 0)
212     return;
213   
214   if (s_textsize == TRUE)
215     charheight = v_textsize;
216   else
217     charheight = DEF_CHARHEIGHT;
218   
219   const EZPlotCurve& firstCurve = *m_vecCurves[0];
220   double xmin = firstCurve.x[0];   // extent of curves in world coord
221   double xmax = xmin;       
222   double ymin = firstCurve.y[0];
223   double ymax = ymin;
224   
225   for (EZPlotCurveConstIterator iterCurve = m_vecCurves.begin(); iterCurve != m_vecCurves.end(); iterCurve++) {
226     const EZPlotCurve& curve = **iterCurve;
227
228     for (ip = 0; ip < curve.m_iPointCount; ip++) {
229       if (curve.x[ip] > xmax)
230         xmax = curve.x[ip];
231       else if (curve.x[ip] < xmin)
232         xmin = curve.x[ip];
233       if (curve.y[ip] > ymax)
234         ymax = curve.y[ip];
235       else if (curve.y[ip] < ymin)
236         ymin = curve.y[ip];
237     }
238   }
239   
240   // extend graph limits for user defined axis cross positions 
241   if (s_xcross == TRUE) {
242     if (v_xcross < xmin)
243       xmin = v_xcross;
244     else if (v_xcross > xmax)
245       xmax = v_xcross;
246   }
247     
248   if (s_ycross == TRUE) {
249     if (v_ycross < ymin)
250       ymin = v_ycross;
251     else if (v_ycross > ymax)
252       ymax = v_ycross;
253   }
254     
255   /* find nice endpoints for axes */
256   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))
257     return;
258     
259   /* check if user set x-axis extents */
260   if (s_xmin == TRUE) {
261     xgw_min = v_xmin;
262     x_nint = o_xmajortick - 1;
263   }
264   if (s_xmax == TRUE) {
265     xgw_max = v_xmax;
266     x_nint = o_xmajortick - 1;
267   }
268   
269   /* check if user set y-axis extents */
270   if (s_ymin == TRUE) {
271     ygw_min = v_ymin;
272     y_nint = o_ymajortick - 1;
273   }
274   if (s_ymax == TRUE) {
275     ygw_max = v_ymax;
276     y_nint = o_ymajortick - 1;
277   }
278     
279   /* calculate increment between major axis in world coordinates */
280   xw_tickinc = (xgw_max - xgw_min) / x_nint;
281   yw_tickinc = (ygw_max - ygw_min) / y_nint;
282     
283   /* we have now calcuated xgw_min, xyw_max, ygw_min, & ygw_max */
284   
285   // set the number of decimal point to users' setting or default 
286   // Two formats for numbers: Fixed:    -nnn.f and  Exponent: -n.fffE+eee
287   if (s_lxfrac == TRUE)
288     x_frac = v_lxfrac;
289   else
290     x_frac = -1;
291     
292   if (s_lyfrac == TRUE)
293     y_frac = v_lyfrac;
294   else
295     y_frac = -1;
296     
297   make_numfmt (x_numfmt, &x_fldwid, &x_frac, xgw_min, xgw_max, x_nint);
298   make_numfmt (y_numfmt, &y_fldwid, &y_frac, ygw_min, ygw_max, y_nint);
299     
300   xtl_wid = x_fldwid * charwidth;               /* calc size of tick labels */
301   ytl_wid = y_fldwid * charwidth;
302   tl_height = charheight;
303   
304   /* calculate the extent of the plot frame */
305   xp_min = o_xporigin;
306   yp_min = o_yporigin;
307   xp_max = xp_min + o_xlength;
308   yp_max = yp_min + o_ylength;
309
310   xp_min = clamp (xp_min, 0., 1.);
311   xp_max = clamp (xp_max, 0., 1.);
312   yp_min = clamp (yp_min, 0., 1.);
313   yp_max = clamp (yp_max, 0., 1.);
314   
315   xa_min = xp_min;              /* extent of axes */
316   xa_max = xp_max;
317   ya_min = yp_min;
318   ya_max = yp_max;
319     
320   /* adjust frame for title */
321   if (c_title.length() > 0)
322     ya_max -= 2.5 * charheight;
323   title_row = ya_max + 0.5 * charheight;
324
325   /* calculate legend box boundaries */
326   int max_leg = 0;                      /* longest legend in characters */
327   int num_leg = 0;                      /* number of legend titles */
328   for (EZPlotCurveConstIterator iterCurve = m_vecCurves.begin(); iterCurve != m_vecCurves.end(); iterCurve++) {
329     const EZPlotCurve& curve = **iterCurve;
330     if ((n = curve.m_sLegend.length()) > 0) {
331       ++num_leg;
332       max_leg = max (max_leg, n);
333     }
334   }
335
336   if (num_leg > 0 && o_legendbox != NOLEGEND) {
337     leg_width  = (max_leg + 2) * charwidth;
338     leg_height = num_leg * 3 * charheight;
339     
340     if (s_xlegend == TRUE)
341       xl_max = v_xlegend;
342     else {
343       xl_max = xa_max;
344       if (o_legendbox == OUTSIDE)
345         xa_max -= (leg_width + 0.5 * charwidth);
346     }
347     xl_min = xl_max - leg_width;
348     
349     if (s_ylegend == TRUE)
350       yl_max = v_ylegend;
351     else
352       yl_max = ya_max;
353     
354     yl_min = yl_max - leg_height;
355
356     rSGP.setWindow  (xl_min, yl_min, xl_max, yl_max);
357     rSGP.setViewport (xl_min, yl_min, xl_max, yl_max);
358     rSGP.setColor (clr_legend);
359     rSGP.drawRect (xl_min, yl_min, xl_max, yl_max);
360
361     n = 0;                      /* current legend position */
362     for (EZPlotCurveIterator iterCurve = m_vecCurves.begin(); iterCurve != m_vecCurves.end(); iterCurve++) {
363         const EZPlotCurve& curve = **iterCurve;
364         
365         if (curve.m_sLegend.length() == 0)
366             continue;
367
368         double xmin = xl_min + 1.0 * charwidth;
369         double xmax = xl_max - 1.0 * charwidth;
370         double y = yl_max - (2.0 + n * 3) * charheight;
371         
372         rSGP.moveAbs (xmin, y + 0.5 * charheight);
373         rSGP.drawText (curve.m_sLegend);
374         rSGP.setColor (curve.m_iColor);
375         if (curve.m_iLineStyle != SGP::LS_NOLINE) {
376             rSGP.setLineStyle (curve.m_iLineStyle);
377             rSGP.moveAbs (xmin, y);
378             rSGP.lineAbs (xmax, y);
379         }
380         if (curve.m_iSymbol > 0) {
381             double xinc = (xmax - xmin) / (5 - 1);
382             rSGP.setLineStyle (SGP::LS_SOLID);
383         for (j = 0; j < 5; j++) {
384           rSGP.moveAbs (xmin + j * xinc, y);
385           symbol(curve.m_iSymbol, 0.5 * charwidth, 0.5 * charheight);
386         }
387       }
388       ++n;              /* move to next legend position */
389     }
390   }   /* end legend printing */
391
392   /* calculate the extent of the axes */
393
394   /*-------------------------*/
395   /* adjust frame for labels */
396   /*-------------------------*/
397   
398   /* x-label */
399   if (c_xlabel.length() > 0)
400     ya_min += 3.0 * charheight;
401   xlbl_row = xp_min;            /* put x-label on bottom of plot frame */
402
403   /* y-label */
404   if (c_ylabel.length() > 0)
405     xa_min += 3.0 * charwidth;  /* reverse rSGP.setTextSize because writing text sideways */
406   ylbl_col = xp_min + 2 * charwidth;
407
408   /*------------------------------*/
409   /* adjust frame for tick labels */
410   /*------------------------------*/
411
412   /* calc offset of tick labels from axes */
413   if (o_xaxis == NOAXIS || o_xtlabel == FALSE)
414     xtl_ofs = 0.0;
415   else if (o_xticks == BELOW)
416     xtl_ofs = -2.5 * charheight;
417   else if (o_xticks == ABOVE)
418     xtl_ofs = 1.5 * charheight;
419   
420   if (o_yaxis == NOAXIS || o_ytlabel == FALSE)
421     ytl_ofs = 0.0;
422   else if (o_yticks == LEFT)
423     ytl_ofs = -(1 + y_fldwid) * charwidth;
424   else if (o_yticks == RIGHT)
425     ytl_ofs = 1.5 * charwidth;
426
427   /* see if need to shrink axis extents and/or tick extents */
428   if (xtl_ofs != 0.0 && s_ycross == FALSE) {
429     if (o_xticks == BELOW) {
430       ya_min += 2.5 * charheight;
431       yt_min = ya_min;
432     } else if (o_xticks == ABOVE) {
433       ya_min += 0.0;
434       yt_min = ya_min + 2.5 * charheight;
435     }
436   } else                        /* noaxis, no t-labels, or user set cross */
437     yt_min = ya_min;
438   
439   if (ytl_ofs != 0.0 && s_xcross == FALSE) {
440     if (o_yticks == LEFT) {
441       xa_min += (1 + y_fldwid) * charwidth;
442       xt_min = xa_min;
443     } else if (o_yticks == RIGHT) {
444       xa_min += 0.0;
445       xt_min = xa_min + ytl_ofs + y_fldwid * charwidth;
446     }
447   } else
448     xt_min = xa_min;
449
450   xt_max = xa_max;
451   yt_max = ya_max;
452   
453   /* decrease size of graph, if necessary, to accommadate space */
454   /* between axis boundary and boundary of ticks */
455   
456   x_added_ticks = -1;
457   y_added_ticks = -1;
458   
459   if (o_xaxis == NOAXIS || o_xtlabel == FALSE)
460     x_added_ticks = 0;
461   if (o_yaxis == NOAXIS || o_ytlabel == FALSE)
462     y_added_ticks = 0;
463   
464   if (o_grid == TRUE) {
465     if (x_added_ticks < 0)
466       x_added_ticks = 2;
467     if (y_added_ticks < 0)
468       y_added_ticks = 2;
469   }
470   
471   if (x_added_ticks < 0) {
472     if (o_yticks == LEFT || s_ycross)
473       x_added_ticks = 1;
474     else
475       x_added_ticks = 2;
476   }
477   
478   if (y_added_ticks < 0) {
479     if (o_xticks == BELOW || s_xcross)
480       y_added_ticks = 1;
481     else
482       y_added_ticks = 2;
483   }
484   
485   xn_tickinc = (xt_max - xt_min) / (x_nint + x_added_ticks);
486   yn_tickinc = (yt_max - yt_min) / (y_nint + y_added_ticks);
487   
488   xt_min += 0.5 * x_added_ticks * xn_tickinc;
489   xt_max -= 0.5 * x_added_ticks * xn_tickinc;
490   yt_min += 0.5 * y_added_ticks * yn_tickinc;
491   yt_max -= 0.5 * y_added_ticks * yn_tickinc;
492   
493   xgn_min = xt_min;
494   xgn_max = xt_max;
495   ygn_min = yt_min;
496   ygn_max = yt_max;
497
498   /*---------------------------------------------------------------------------*/
499   
500   /* PLOT CURVES */
501   
502   rSGP.setLineStyle (SGP::LS_SOLID);
503   drawAxes();
504
505   /* Convert WC in graph boundary to axis boundary */
506   rSGP.setWindow  (xgw_min, ygw_min, xgw_max, ygw_max); /* Graph boundary */
507   rSGP.setViewport (xgn_min, ygn_min, xgn_max, ygn_max);
508   double xminTemp = xa_min, xmaxTemp = xa_max;
509   double yminTemp = ya_min, ymaxTemp = ya_max;
510   rSGP.transformNDCtoMC (&xminTemp, &yminTemp); // calc WC to axis boundaries
511   rSGP.transformNDCtoMC (&xmaxTemp, &ymaxTemp);
512   rSGP.setWindow (xminTemp, yminTemp, xmaxTemp, ymaxTemp); // Set window to axis boundaries
513   rSGP.setViewport (xa_min, ya_min, xa_max, ya_max);    
514   
515   symwidth = charwidth * (xgw_max - xgw_min);
516   symheight = charheight * (ygw_max - ygw_min);
517   
518   for (EZPlotCurveIterator iterCurve = m_vecCurves.begin(); iterCurve != m_vecCurves.end(); iterCurve++) {
519       const EZPlotCurve& curve = **iterCurve;
520
521       rSGP.setColor (curve.m_iColor);
522       if (curve.m_iLineStyle != SGP::LS_NOLINE) {
523         rSGP.setLineStyle (curve.m_iLineStyle);
524         rSGP.polylineAbs (curve.x, curve.y, curve.m_iPointCount);
525       }
526       if (curve.m_iSymbol > 0) {
527         rSGP.setLineStyle (SGP::LS_SOLID);
528         rSGP.moveAbs (curve.x[0], curve.y[0]);
529         symbol (curve.m_iSymbol, symwidth, symheight);
530         for (i = 1; i < curve.m_iPointCount; i++)
531           if (i % curve.m_iSymbolFreq == 0 || i == curve.m_iPointCount - 1) {
532             rSGP.moveAbs (curve.x[i], curve.y[i]);
533             symbol (curve.m_iSymbol, symwidth, symheight);
534           }
535       }
536   }
537 }
538
539
540 /* NAME
541  *   drawAxes                   INTERNAL routine to draw axis & label them
542  *
543  * SYNOPSIS
544  *   drawAxes()
545  */
546
547
548 void 
549 EZPlot::drawAxes(void)
550 {
551   double xticklen = 0, yticklen = 0; /* length of ticks in NDC */
552   double minorinc;              /* increment between minor axes */
553   double xaxispos, yaxispos;    /* crossing of axes */
554   double x, y, x2, y2;
555   bool axis_near;                       /* TRUE if axis too close to print t-label */
556   int i, j;
557   char str[100];
558   char *numstr;
559   
560   rSGP.setTextSize (charheight);
561   rSGP.setTextColor (1, -1);
562   
563   if (o_xticks == ABOVE)
564     xticklen = charheight;
565   else if (o_xticks == BELOW)
566     xticklen = -charheight;
567   
568   if (o_yticks == RIGHT)
569     yticklen = charwidth;
570   else if (o_yticks == LEFT)
571     yticklen = -charwidth;
572   
573   rSGP.setWindow  (xp_min, yp_min, xp_max, yp_max);
574   rSGP.setViewport (xp_min, yp_min, xp_max, yp_max);
575   
576   if (c_title.length() > 0) {
577     rSGP.moveAbs (xa_min + (xa_max-xa_min)/2 - c_title.length()*charwidth, title_row);
578     rSGP.setTextSize (charheight * 2.0);
579     rSGP.setTextColor (clr_title, -1);
580     rSGP.drawText (c_title);
581     rSGP.setTextSize (charheight);
582   }
583   
584   if (o_grid == TRUE || o_box == TRUE) {
585     rSGP.setColor (clr_axis);
586     rSGP.moveAbs (xa_min, ya_min);
587     rSGP.lineAbs (xa_max, ya_min);
588     rSGP.lineAbs (xa_max, ya_max);
589     rSGP.lineAbs (xa_min, ya_max);
590     rSGP.lineAbs (xa_min, ya_min);
591   }
592   
593   /* calculate position of axes */
594   
595   /* x-axis */
596   if (s_ycross == TRUE) {       /* convert users' world-coord */
597     xaxispos = v_ycross;        /* axis to its position in NDC */
598     rSGP.setWindow (xgw_min, ygw_min, xgw_max, ygw_max);
599     rSGP.setViewport (xgn_min, ygn_min, xgn_max, ygn_max);
600     x = xgw_min;
601     rSGP.transformMCtoNDC (x, xaxispos, &x, &xaxispos);
602   } else
603     xaxispos = ya_min;
604   
605   /* y-axis */
606   if (s_xcross == TRUE) {       /* convert users' world-coord */
607     yaxispos = v_xcross;        /* axis to its NDC position */
608     rSGP.setWindow (xgw_min, ygw_min, xgw_max, ygw_max);
609     rSGP.setViewport (xgn_min, ygn_min, xgn_max, ygn_max);
610     y = ygw_min;
611     rSGP.transformMCtoNDC (yaxispos, y, &yaxispos, &y);
612   } else
613     yaxispos = xa_min;
614   
615   /*-------------*/
616   /* draw x-axis */
617   /*-------------*/
618   
619   if (o_xaxis == LINEAR) {
620     rSGP.setWindow (xp_min, yp_min, xp_max, yp_max);
621     rSGP.setViewport (xp_min, yp_min, xp_max, yp_max);
622     
623     /* draw axis line */
624     
625     rSGP.setColor (clr_axis);
626     if (o_tag && !o_grid && !o_box && s_xcross) {
627       rSGP.moveAbs (xa_min, xaxispos - charheight);
628       rSGP.lineAbs (xa_min, xaxispos + charheight);
629     }
630     rSGP.moveAbs (xa_min, xaxispos);
631     rSGP.lineAbs (xa_max, xaxispos);
632     if (o_tag && !o_grid && !o_box) {
633       rSGP.moveAbs (xa_max, xaxispos - charheight);
634       rSGP.lineAbs (xa_max, xaxispos + charheight);
635     }
636     
637     if (o_grid == TRUE) {
638       rSGP.setColor (clr_grid);
639       for (i = 0; i <= x_nint; i++) {
640         rSGP.moveAbs (xt_min + xn_tickinc * i, ya_max);
641         rSGP.lineAbs (xt_min + xn_tickinc * i, ya_min);
642       }
643     }
644     rSGP.moveAbs (xa_min + (xa_max-xa_min)/2 - c_xlabel.length()*charwidth, xlbl_row);
645     rSGP.setTextSize (charheight * 2.0);
646     rSGP.setTextColor (clr_label, -1);
647     rSGP.drawText (c_xlabel);
648     rSGP.setTextSize (charheight);
649     minorinc = xn_tickinc / (o_xminortick + 1);
650
651     for (i = 0; i <= x_nint; i++) {
652       x = xt_min + xn_tickinc * i;
653       rSGP.setColor (clr_axis);
654       rSGP.moveAbs (x, xaxispos);
655       rSGP.lineAbs (x, xaxispos + xticklen);
656       if (i != x_nint)
657         for (j = 1; j <= o_xminortick; j++) {
658           x2 = x + minorinc * j;
659           rSGP.moveAbs (x2, xaxispos);
660           rSGP.lineAbs (x2, xaxispos + TICKRATIO * xticklen);
661         }
662       axis_near = FALSE;
663       if (xaxispos + xtl_ofs > ya_min && o_yaxis != NOAXIS) {
664         double xw, x, y, d;
665         
666         xw = xgw_min + i * xw_tickinc;
667         rSGP.setWindow (xgw_min, ygw_min, xgw_max, ygw_max);
668         rSGP.setViewport (xgn_min, ygn_min, xgn_max, ygn_max);
669         rSGP.transformMCtoNDC (xw, y, &x, &y);
670         rSGP.setWindow (xp_min, yp_min, xp_max, yp_max);
671         rSGP.setViewport (xp_min, yp_min, xp_max, yp_max);
672         d = x - yaxispos;
673         if (o_yticks == RIGHT && d >= 0  && d < 0.9 * xn_tickinc)
674           axis_near = TRUE;
675         if (o_yticks == LEFT && d <= 0 && d > -0.9 * xn_tickinc)
676           axis_near = TRUE;
677       }
678
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);
685       }
686     }
687   }             /* x - axis */
688   
689   
690   /*--------*/
691   /* y-axis */
692   /*--------*/
693   
694   if (o_yaxis == LINEAR) {
695     rSGP.setWindow  (xp_min, yp_min, xp_max, yp_max);
696     rSGP.setViewport (xp_min, yp_min, xp_max, yp_max);
697     
698     rSGP.setColor (clr_axis);
699     if (o_tag && !o_grid && !o_box && s_ycross) {
700       rSGP.moveAbs (yaxispos - charwidth, ya_min);
701       rSGP.lineAbs (yaxispos + charwidth, ya_min);
702     }
703     rSGP.moveAbs (yaxispos, ya_min);
704     rSGP.lineAbs (yaxispos, ya_max);
705     if (o_tag && !o_grid && !o_box) {
706       rSGP.moveAbs (yaxispos - charwidth, ya_max);
707       rSGP.lineAbs (yaxispos + charwidth, ya_max);
708     }
709     
710     if (o_grid == TRUE) {
711       rSGP.setColor (clr_grid);
712       for (i = 0; i <= y_nint; i++) {
713         y = yt_min + yn_tickinc * i;
714         rSGP.moveAbs (xa_max, y);
715         rSGP.lineAbs (xa_min, y);
716       }
717     }
718     rSGP.moveAbs (ylbl_col,ya_min + (ya_max-ya_min)/2 - c_ylabel.length()*charheight);
719     rSGP.setTextAngle (HALFPI);
720     rSGP.setTextSize (2 * charheight);
721     rSGP.setTextColor (clr_label, -1);
722     rSGP.drawText (c_ylabel);
723     rSGP.setTextAngle (0.0);
724     rSGP.setTextSize (charheight);
725     minorinc = yn_tickinc / (o_yminortick + 1);
726     
727     for (i = 0; i <= y_nint; i++) {
728       y = yt_min + yn_tickinc * i;
729       rSGP.setColor (clr_axis);
730       rSGP.moveAbs (yaxispos, y);
731       rSGP.lineAbs (yaxispos + yticklen, y);
732       if (i != y_nint)
733         for (j = 1; j <= o_yminortick; j++) {
734           y2 = y + minorinc * j;
735           rSGP.moveAbs (yaxispos, y2);
736           rSGP.lineAbs (yaxispos + TICKRATIO * yticklen, y2);
737         }
738       axis_near = FALSE;
739       if (yaxispos + ytl_ofs > xa_min && o_xaxis != NOAXIS) {
740         double yw, x, y, d;
741         
742         yw = ygw_min + i * yw_tickinc;
743         rSGP.setWindow (xgw_min, ygw_min, xgw_max, ygw_max);
744         rSGP.setViewport (xgn_min, ygn_min, xgn_max, ygn_max);
745         rSGP.transformMCtoNDC (x, yw, &x, &y);
746         rSGP.setWindow (xp_min, yp_min, xp_max, yp_max);
747         rSGP.setViewport (xp_min, yp_min, xp_max, yp_max);
748         d = y - xaxispos;
749         if (o_xticks == ABOVE && d >= 0 && d < 0.9 * yn_tickinc)
750           axis_near = TRUE;
751         if (o_xticks == BELOW && d <= 0 && d > -0.9 * yn_tickinc)
752           axis_near = TRUE;
753       }
754       if (o_ytlabel == TRUE && axis_near == FALSE) {
755         snprintf (str, sizeof(str), y_numfmt, ygw_min + yw_tickinc * i);
756         rSGP.moveAbs (yaxispos + ytl_ofs, y - 0.5 * charheight);
757         rSGP.setTextColor (clr_number, -1);
758         rSGP.drawText (str);
759       }
760     }
761   }             /* y - axis */
762 }
763
764
765 void 
766 EZPlot::symbol (int sym, double symwidth, double symheight)
767 {
768   if (sym <= 0)
769     return;
770   
771   if (sym == SB_CROSS) {
772     rSGP.moveRel (-0.5 * symwidth, -0.5 * symheight);
773     rSGP.lineRel (symwidth, symheight);
774     rSGP.moveRel (-symwidth, 0.0);
775     rSGP.lineRel (symwidth, -symheight);
776     rSGP.moveRel (-0.5 * symwidth, 0.5 * symheight);
777   } else if (sym == SB_PLUS) {
778     rSGP.moveRel (-0.5 * symwidth, 0.0);
779     rSGP.lineRel (symwidth, 0.0);
780     rSGP.moveRel (-0.5 * symwidth, -0.5 * symheight);
781     rSGP.lineRel (0.0, symheight);
782     rSGP.moveRel (0.0, -0.5 * symheight);
783   } else if (sym == SB_BOX) {
784     rSGP.moveRel (-0.5 * symwidth, -0.5 * symheight);
785     rSGP.lineRel (symwidth, 0.0);
786     rSGP.lineRel (0.0, symheight);
787     rSGP.lineRel (-symwidth, 0.0);
788     rSGP.lineRel (0.0, -symheight);
789     rSGP.moveRel (0.5 * symwidth, 0.5 * symheight);
790   } else if (sym == SB_CIRCLE) {
791     rSGP.drawCircle (symwidth);
792   } else if (sym == SB_ERRORBAR) {
793     rSGP.moveRel (-0.5 * symwidth, 0.5 * symheight);
794     rSGP.lineRel (symwidth, 0.0);
795     rSGP.moveRel (-0.5 * symwidth, 0.0);
796     rSGP.lineRel (0.0, -symheight);
797     rSGP.moveRel (-0.5 * symwidth, 0.0);
798     rSGP.lineRel (symwidth, 0.0);
799     rSGP.moveRel (-0.5 * symwidth, 0.5 * symheight);
800   }
801 }
802
803
804
805 /* NAME
806  *    axis_scale                        calculates graph axis scaling
807  *
808  *  SYNOPSIS:
809  *    retval = axis_scale (min, max, nint, minp, maxp, nintp, 
810  *                         rec_total, rec_frac)
811  *
812  *    INPUT:
813  *      double min         Smallest value to plot
814  *      double max         Largest value to plot
815  *      int    nint        Number of intervals desired
816  *
817  *    OUTPUT:
818  *      int   retval       FALSE if illegal parameters, else TRUE
819  *      double *minp       Minimum graph value
820  *      double *maxp       Maximum graph value
821  *      int    *nintp      Number of intervals for graph
822  *      int    *rec_total  Recommended field width for printing out the number
823  *      int    *rec_frac   Recommended number of digits for print fraction
824  */
825
826 int 
827 EZPlot::axis_scale (double min, double max, int nint, double *minp, double *maxp, int *nintp)
828 {
829   if (min >= max || nint < 1) {
830     sys_error (ERR_WARNING, "Invalid params: min=%lf, max=%lf, num intervals=%d [axis_scale]", min, max, nint);
831     return (FALSE);
832   }
833   
834   double eps = 0.025;
835   double a = fabs(min);
836   if (fabs(min) < fabs(max))
837     a = fabs(max);
838   double scale = pow (10.0, floor(log10(a)));
839  loop:
840   double mina = min / scale;
841   double maxa = max / scale;
842   double d = (maxa - mina) / nint;
843   double j = d * eps;
844   double e = floor (log10(d));
845   double f = d / pow (10.0, e);
846   double v = 10.0;
847   if (f < sqrt(2.0))
848     v = 1.0;
849   else if (f < sqrt (10.0))
850     v = 2.0;
851   else if (f < sqrt (50.0))
852     v = 5.0;
853   double wdt = v * pow (10.0, e);
854   double g = floor (mina / wdt);
855   if (fabs(g + 1 - mina / wdt) < j)
856     g = g + 1;
857   *minp = wdt * g;
858   double h = floor (maxa / wdt) + 1.0;
859   if (fabs(maxa / wdt + 1 - h) < j)
860     h = h - 1;
861   *maxp = wdt * h;
862   *nintp = static_cast<int>(h - g);
863   if (fabs(*maxp) >= 10.0 || fabs(*minp) >= 10.0) {
864     scale = scale * 10.0;
865     goto loop;
866   }
867   
868   *minp *= scale;
869   *maxp *= scale;
870   
871   return (TRUE);
872 }
873
874
875 /* NAME
876  *   make_numfmt                Make a numeric format string
877  *
878  * SYNOPSIS
879  *   make_numfmt (fmtstr, fldwid, nfrac, min, max, nint)
880  *   char *fmtstr               Returned format string for printf()
881  *   int  *fldwid               Returned field width
882  *   int  *nfrac                If < 0, then calculate best number of
883  *                              fraction places & return that value
884  *                              If >= 0, then use that number of places
885  *   double min                 Minimum value
886  *   double max                 Maximum value
887  *   int nint                   Number of intervals between min & max
888  *
889  * DESCRIPTION
890  *   This  routine is written as an INTERNAL routine for EZPLOT
891  */
892
893 static inline double 
894 trunc (double x)
895 {
896   double integer;
897   
898   modf (x, &integer);
899   
900   return (integer);
901 }
902
903 void 
904 EZPlot::make_numfmt (char *fmtstr, int *fldwid, int *nfrac, double minval, double maxval, int nint)
905 {
906   int wid, frac, expon;
907
908   double delta = (maxval - minval) / nint;
909   double absmin = fabs(minval);
910   double absmax = fabs(maxval);
911   double logt = log10( max(absmin, absmax) );
912
913   if (fabs(logt) >= 6) {                /* use exponential format */
914     if (fabs(logt) > 99)
915       expon = 5;                /*  E+102 */
916     else
917       expon = 4;                /*  E+00 */
918     
919     if (*nfrac < 0) {           /* calculate frac */
920       delta /= pow (10., floor(logt));  /* scale delta */
921       frac = static_cast<int>(fabs(trunc(log10(delta)))) + 1;
922       if (frac < 1)
923         frac = 1;               /* to be safe, add decimal pt */
924     } else                      /* use users' frac */
925       frac = *nfrac;
926     
927     wid = 2 + frac + expon;
928     if (minval < 0. || maxval < 0.)
929       ++wid;
930     sprintf (fmtstr, "%s%d%s%d%s", "%", wid, ".", frac, "g");
931   } else {                      /* use fixed format */
932     wid = static_cast<int>(trunc(logt)) + 1;
933     if (wid < 1)
934       wid = 1;
935     if (minval < 0. || maxval < 0.)
936       ++wid;
937     
938     if (*nfrac < 0) {           /* calculate frac */
939       if (delta >= 0.999999)
940         frac = 1;               /* add a decimal pt to be safe */
941       else
942         frac = static_cast<int>(fabs(trunc(log10(delta)))) + 1;
943     } else                      /* use users' frac */
944       frac = *nfrac;
945     
946     wid += 1 + frac;
947     sprintf (fmtstr, "%s%d%s%d%s", "%", wid, ".", frac, "f");
948   }
949
950   *fldwid = wid;
951   *nfrac = frac;
952 }
953