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