home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume8 / graph+ / part02 / dumpgrph.c < prev    next >
Encoding:
C/C++ Source or Header  |  1987-03-01  |  26.4 KB  |  1,158 lines

  1. /*
  2.  * Copyright (C) 1986   Alan Kent
  3.  *
  4.  * Permission is granted to freely distribute part or
  5.  * all of this code as long as it is not for profit
  6.  * and this message is retained in the code.
  7.  *
  8.  * No resposibility is taken for any damage or incorect
  9.  * results this program generates.
  10.  * 
  11.  */
  12.  
  13. /*
  14.  * WARNING: This file is extremely sensative to changes.
  15.  *    Making one minor change may break other features.
  16.  *       This file probably needs to be re-written.
  17.  */
  18.  
  19. #include <stdio.h>
  20. #include <math.h>
  21. #include "graph.h"
  22. #include "y.tab.h"
  23.  
  24.  
  25. #define DEBUG        0
  26. #define DEBUG_LOG    0
  27.  
  28.  
  29. /* this info is for trying to place the text nicely on the screen */
  30. /* however, unfortunately it is different for all the different plotters! */
  31.  
  32. #ifdef  LASER
  33. #define CHAR_WIDTH    48
  34. #define CHAR_HEIGHT    100
  35. #else
  36. #define CHAR_WIDTH    80
  37. #define CHAR_HEIGHT    100
  38. #endif
  39.  
  40.  
  41. #define XSPACE        4096
  42. #define YSPACE        4096
  43. #define XORIGIN        (15*CHAR_WIDTH)
  44. #define YORIGIN        650
  45. #define XRANGE        (XSPACE-XORIGIN)
  46. #define YRANGE        (YSPACE-YORIGIN-3*CHAR_HEIGHT)
  47. #define TICK_SIZE    50
  48. #define TEXT_GAP    400
  49.  
  50. #define TICK_TEXT_GAP    400        /* minimum gap between ticks for log */
  51. #define TICK_GAP    40
  52.  
  53. #define RAD        (XSPACE/200)
  54.  
  55. #define LEGEND_LINE_LENGTH    400
  56.  
  57.  
  58. extern double min_fun ();
  59. extern double max_fun ();
  60. extern double ceil ();
  61. extern double pow ();
  62. extern double log10 ();
  63.  
  64.  
  65. extern graph_st graph[];
  66. extern int num_graphs;
  67. extern char *graph_label;
  68. extern axis_st xaxis;
  69. extern axis_st yaxis;
  70. extern int horiz_legend;
  71. extern int vert_legend;
  72.  
  73.  
  74.  
  75. static double lastx_clip;    /* buffered coordinate for clip function */
  76. static double lasty_clip;
  77.  
  78.  
  79. dump_graphs ()
  80. {
  81.     int i;
  82.  
  83.     if ( num_graphs < 1 ) {
  84.     fprintf ( stderr , "No graphs to print\n" );
  85.     exit ( 0 );
  86.     }
  87.  
  88.     if ( xaxis.linear == LOGRITHMIC ) {
  89.     for ( i = 0; i < num_graphs; i++ )
  90.         log_tab ( graph[i].table , graph[i].table->next );
  91.     }
  92.  
  93.     if ( yaxis.linear == LOGRITHMIC ) {
  94.     for ( i = 0; i < num_graphs; i++ )
  95.         log_tab ( graph[i].table->next , graph[i].table );
  96.     }
  97.  
  98.     /* plot(3x) routines */
  99.     openpl ();
  100.     erase ();
  101.     space ( 0 , 0 , XSPACE , YSPACE );
  102.  
  103.     if ( graph_label != NULL ) {
  104.     move ( XORIGIN + XRANGE / 2 - strlen ( graph_label ) * CHAR_WIDTH / 2 ,
  105.         YSPACE - CHAR_HEIGHT );
  106.     label ( graph_label );
  107.     }
  108.  
  109.     determine_range ( XAXIS , &xaxis );
  110.     determine_range ( YAXIS , &yaxis );
  111.  
  112.     axes ();
  113.  
  114.     dump_legend ();
  115.  
  116.     for ( i = 0; i < num_graphs; i++ )
  117.     draw_graph ( &graph[i] );
  118.  
  119.     /* plot(3x) */
  120.     move ( 0 , 0 );
  121.     closepl ();
  122. }
  123.  
  124.  
  125.  
  126. /* discards values <= 0.0 */
  127.  
  128. static
  129. log_tab ( table , othercol )
  130. table_st *table , *othercol;
  131. {
  132.     int i , j;
  133.     int warn_count;
  134.  
  135.     warn_count = 0;
  136.     j = 0;
  137.     for ( i = 0; i < table->size; i++ ) {
  138.     if ( table->data[i] > 0.0 ) {
  139.         othercol->data[j] = othercol->data[i];
  140.         table->data[j] = log10 ( table->data[i] );
  141.         j++;
  142.     }
  143.     else if ( warn_count++ == 0 )
  144.         warn ( "negative or zero data to be plotted logrithmically discarded" );
  145.     }
  146.     table->size = j;
  147.     othercol->size = j;
  148. }
  149.  
  150.  
  151.  
  152. static
  153. draw_graph ( gptr )
  154. graph_st *gptr;
  155. {
  156.     int i , size;
  157.     int reseti;
  158.     int basex , basey;
  159.     double *xdata , *ydata;
  160.     double lastx , lasty;
  161.     double newx , newy;
  162.  
  163.  
  164.     /* necessary for clip function to work safely */
  165.  
  166.     lasty_clip = -1;
  167.     lastx_clip = -1;
  168.  
  169.     /* no data to plot */
  170.  
  171.     if ( gptr->table->size < 1 )
  172.     return;
  173.  
  174.     xdata = gptr->table->data;
  175.     ydata = gptr->table->next->data;
  176.     size = gptr->table->size;
  177.  
  178.     /* draw the line (with clipping) */
  179.  
  180.     if ( gptr->line_type != NO ) {
  181.     set_line ( gptr->line_type );
  182.     lastx = xdata[0];
  183.     lasty = ydata[0];
  184.     /* start from 0 (not 1) so a single point will come out as a dot */
  185.     for ( i = 0; i < size; i++ ) {
  186.         newx = xdata[i];
  187.         newy = ydata[i];
  188.         clip_line ( lastx , lasty , newx , newy );
  189.         lastx = newx;
  190.         lasty = newy;
  191.     }
  192.     }
  193.  
  194.     /* Draw any points along the line */
  195.  
  196.     if ( gptr->point_type != 0 ) {
  197.     set_line ( SOLID );
  198.     for ( i = 0; i < size; i++ ) {
  199.  
  200.         basex = lxscale ( xdata[i] );
  201.         basey = lyscale ( ydata[i] );
  202.  
  203.         if ( basex >= XORIGIN  &&  basex <= XORIGIN + XRANGE
  204.         &&   basey >= YORIGIN  &&  basey <= YORIGIN + YRANGE )
  205.  
  206.         draw_point ( gptr->point_type , basex , basey );
  207.     }
  208.     }
  209.  
  210.     /* output the graph label */
  211.  
  212.     set_line ( SOLID );
  213.     if ( gptr->label != NULL ) {
  214.     move ( lxscale ( lastx_clip ) , lyscale ( lasty_clip ) );
  215.     /*move ( lxscale ( xdata[size-1] ) , lyscale ( ydata[size-1] ) );*/
  216.     label ( gptr->label );
  217.     }
  218. }
  219.  
  220.  
  221.  
  222. draw_point ( point_type , basex , basey )
  223. int point_type , basex , basey;
  224. {
  225.     if ( point_type & MSK_TRIANGLE ) {
  226.     move ( basex - RAD , basey - RAD * 2 / 3 );
  227.     cont ( basex + RAD , basey - RAD * 2 / 3 );
  228.     cont ( basex , basey + RAD * 4 / 3 );
  229.     cont ( basex - RAD , basey - RAD * 2 / 3 );
  230.     }
  231.     
  232.     if ( point_type & MSK_CROSS ) {
  233.     move ( basex - RAD , basey - RAD );
  234.     cont ( basex + RAD , basey + RAD );
  235.     move ( basex - RAD , basey + RAD );
  236.     cont ( basex + RAD , basey - RAD );
  237.     }
  238.  
  239.     if ( point_type & MSK_PLUS ) {
  240.     move ( basex - RAD , basey - RAD );
  241.     cont ( basex + RAD , basey + RAD );
  242.     move ( basex - RAD , basey + RAD );
  243.     cont ( basex + RAD , basey - RAD );
  244.     }
  245.  
  246.     if ( point_type & MSK_CIRCLE ) {
  247.     circle ( basex , basey , RAD );
  248.     }
  249.     
  250.     if ( point_type & MSK_SQUARE ) {
  251.     /* *4/5 is just to make the shapes appear the same size */
  252.     move ( basex - RAD * 4 / 5 , basey - RAD * 4 / 5 );
  253.     cont ( basex + RAD * 4 / 5 , basey - RAD * 4 / 5 );
  254.     cont ( basex + RAD * 4 / 5 , basey + RAD * 4 / 5 );
  255.     cont ( basex - RAD * 4 / 5 , basey + RAD * 4 / 5 );
  256.     cont ( basex - RAD * 4 / 5 , basey - RAD * 4 / 5 );
  257.     }
  258. }
  259.  
  260.  
  261. clip_line ( x1 , y1 , x2 , y2 )
  262. double x1 , y1 , x2 , y2;
  263. {
  264.     double minx , miny , maxx , maxy;
  265.     int mx1 , my1 , mx2 , my2;
  266.     double xedge , yedge;
  267.  
  268.  
  269.     minx = xaxis.range.min;
  270.     miny = yaxis.range.min;
  271.     maxx = xaxis.range.max;
  272.     maxy = yaxis.range.max;
  273.  
  274.     /* determine which sector the end points are in */
  275.  
  276.     mx1 = sector ( x1 , minx , maxx );
  277.     my1 = sector ( y1 , miny , maxy );
  278.     mx2 = sector ( x2 , minx , maxx );
  279.     my2 = sector ( y2 , miny , maxy );
  280.  
  281.     /* points must not be in same sector and off screen */
  282.  
  283.     if ( mx1 * mx2 != 1  &&  my1 * my2 != 1 ) {
  284.  
  285.     if ( mx1 != 0 ) {
  286.  
  287.         /* move point 1 nearer the x-edge of the frame */
  288.  
  289.         if ( mx1 == 1 )
  290.         xedge = maxx;
  291.         else
  292.         xedge = minx;
  293.         y1 = y1 + ( y2 - y1 ) * ( xedge - x1 ) / ( x2 - x1 );
  294.         x1 = xedge;
  295.  
  296.         /* recompute sector to see if point needs another shift */
  297.  
  298.         my1 = sector ( y1 , miny , maxy );
  299.     }
  300.  
  301.     if ( my1 != 0 ) {
  302.  
  303.         /* move point 1 nearer the y-edge of the frame */
  304.  
  305.         if ( my1 == 1 )
  306.         yedge = maxy;
  307.         else
  308.         yedge = miny;
  309.         x1 = x1 + ( x2 - x1 ) * ( yedge - y1 ) / ( y2 - y1 );
  310.         y1 = yedge;
  311.  
  312.     }
  313.  
  314.     /* ok, now repeat the above for point 2 */
  315.  
  316.     if ( mx2 != 0 ) {
  317.  
  318.         /* move point 2 nearer the x-edge of the frame */
  319.  
  320.         if ( mx2 == 1 )
  321.         xedge = maxx;
  322.         else
  323.         xedge = minx;
  324.         y2 = y2 + ( y1 - y2 ) * ( xedge - x2 ) / ( x1 - x2 );
  325.         x2 = xedge;
  326.  
  327.         /* recompute sector to see if point needs another shift */
  328.  
  329.         my2 = sector ( y2 , miny , maxy );
  330.     }
  331.  
  332.     if ( my2 != 0 ) {
  333.  
  334.         /* move point 2 nearer the y-edge of the frame */
  335.  
  336.         if ( my2 == 1 )
  337.         yedge = maxy;
  338.         else
  339.         yedge = miny;
  340.         x2 = x2 + ( x1 - x2 ) * ( yedge - y2 ) / ( y1 - y2 );
  341.         y2 = yedge;
  342.  
  343.     }
  344.  
  345.     if ( x1 >= minx  &&  x1 <= maxx  &&  x2 >= minx  &&  x2 <= maxx 
  346.     &&   y1 >= miny  &&  y1 <= maxy  &&  y2 >= miny  &&  y2 <= maxy ) {
  347.  
  348.         /* use buffered move and cont calls where possible as some */
  349.         /* plotters do dotted lines better if cont is used for continuous */
  350.         /* lines. Using separate line() calls means the dotted lines */
  351.         /* start again per line */
  352.  
  353.         if ( x1 != lastx_clip  ||  y1 != lasty_clip )
  354.         move ( xscale ( x1 ) , yscale ( y1 ) );
  355.         cont ( xscale ( x2 ) , yscale ( y2 ) );
  356.         lastx_clip = x2;
  357.         lasty_clip = y2;
  358.     }
  359.     }
  360. }
  361.  
  362.  
  363. static int
  364. sector ( val , min , max )
  365. double val , min , max;
  366. {
  367.     if ( val < min ) return ( -1 );
  368.     if ( val > max ) return ( 1 );
  369.     return ( 0 );
  370. }
  371.  
  372.  
  373. static
  374. set_line ( type )
  375. int type;
  376. {
  377.     switch ( type ) {
  378.     case DOTTED : linemod ( "dotted" ); break;
  379.     case DOTDASHED : linemod ( "dotdashed" ); break;
  380.     case SHORTDASHED : linemod ( "shortdashed" ); break;
  381.     case LONGDASHED : linemod ( "longdashed" ); break;
  382.     default : linemod ( "solid" ); break;
  383.     }
  384. }
  385.  
  386.  
  387. static int
  388. xscale ( coord )
  389. double coord;
  390. {
  391.     double xcoord;
  392.  
  393.     xcoord = ( ( coord - xaxis.range.min )
  394.     / ( xaxis.range.max - xaxis.range.min ) ) * (double)XRANGE;
  395.     return ( (int)xcoord + XORIGIN );
  396. }
  397.  
  398.  
  399. static int
  400. yscale ( coord )
  401. double coord;
  402. {
  403.     double ycoord;
  404.  
  405.     ycoord = ( ( coord - yaxis.range.min )
  406.     / ( yaxis.range.max - yaxis.range.min ) ) * (double)YRANGE;
  407.     return ( (int)ycoord + YORIGIN );
  408. }
  409.  
  410.  
  411. static int
  412. lxscale ( coord )
  413. double coord;
  414. {
  415.     int xcoord;
  416.  
  417.     xcoord = xscale ( coord );
  418.     if ( xcoord < 0 )
  419.     return ( 0 );
  420.     if ( xcoord >= XSPACE )
  421.     return ( XSPACE - 1 );
  422.     return ( xcoord );
  423. }
  424.  
  425.  
  426. static int
  427. lyscale ( coord )
  428. double coord;
  429. {
  430.     int ycoord;
  431.  
  432.     ycoord = yscale ( coord );
  433.     if ( ycoord < 0 )
  434.     return ( 0 );
  435.     if ( ycoord >= YSPACE )
  436.     return ( YSPACE - 1 );
  437.     return ( ycoord );
  438. }
  439.  
  440.  
  441. char *
  442. choose_format ( paxis )
  443. struct axis_st *paxis;
  444. {
  445.     double first_log_tick ();
  446.  
  447.     if ( paxis->linear == LOGRITHMIC ) {
  448.     paxis->power_10 = floor ( paxis->range.min );
  449.     paxis->format = "%.0f";
  450.     /* STILL NOT RIGHT
  451.     if ( xscale ( first_log_tick ( xaxis.range.min ) ) >= XRANGE + XORIGIN )
  452.         paxis->format = "%.2f";
  453.     */
  454.     }
  455.     else if ( paxis->auto_tick_size )
  456.     calc_ticks ( paxis , &paxis->tick_size , &paxis->power_10 );
  457.  
  458.     if ( paxis->scale == AUTO
  459.     /* &&  paxis->linear != LOGRITHMIC ???? special scaling ok for log too? */
  460.     &&  paxis->auto_tick_size ) {
  461.     if ( paxis->power_10 == 1.0
  462.     ||   paxis->power_10 == 2.0 ) {
  463.         paxis->format = "%.0f";
  464.         paxis->scale = NO;
  465.     }
  466.     else if ( paxis->tick_size >= 10.0 )
  467.         paxis->format = "%.0f";
  468.     }
  469.     return ( ( paxis->user_format == NULL )
  470.     ? paxis->format : paxis->user_format );
  471. }
  472.  
  473.  
  474. static
  475. axes ()
  476. {
  477.     int xscale ();
  478.     int yscale ();
  479.     int lxscale ();
  480.     int lyscale ();
  481.     double first_log_tick ();
  482.     double next_log_tick ();
  483.     double next_10_tick ();
  484.  
  485.     int base_x , base_y;
  486.     int tick_pos;
  487.     int last_tick_pos;
  488.     int last_text_pos;
  489.     int tick_num;
  490.     int loop_count;
  491.     int i;
  492.     double tick_value;
  493.     double base;
  494.     double xaxis_min , yaxis_min;
  495.     char buf[500];
  496.     char small_buf[2];
  497.     char *p , *beg;
  498.     int longest , length , num_lines , line_num;
  499.     int print_tick;
  500.     int best_log_ticks;
  501.     char *xformat , *yformat;
  502.  
  503.  
  504.     xformat = choose_format ( &xaxis );
  505.     yformat = choose_format ( &yaxis );
  506.  
  507.     base_x = lxscale ( xaxis.range.min );
  508.     base_y = lyscale ( yaxis.range.min );
  509.     base = 10.0;
  510.     set_line ( SOLID );
  511.  
  512.     if ( xaxis.frame != NO ) {
  513.  
  514.     /* First the x-axis */
  515.  
  516.     /* draw base line */
  517.  
  518.     line ( base_x , base_y , base_x + XRANGE - 1 , base_y );
  519.  
  520.     if ( xaxis.frame == OUTLINE )
  521.         line ( base_x , base_y + YRANGE - 1 , base_x + XRANGE - 1 , base_y + YRANGE - 1 );
  522.  
  523.     /* Label the axis */
  524.  
  525.     if ( xaxis.label != NULL ) {
  526.         sprintf ( buf ,
  527.         ( xaxis.scale != AUTO  ||  xaxis.power_10 == 0.0 )
  528.             ? "%s" : "%s x 10^%d" ,
  529.         xaxis.label ,
  530.         (int)xaxis.power_10 );
  531.         move ( base_x + XRANGE / 2 - strlen ( buf ) * CHAR_WIDTH / 2 ,
  532.         base_y - TEXT_GAP );
  533.         label ( buf );
  534. /* fprintf(stderr,"xaxis label '%s'\n",buf);*/
  535.     }
  536.  
  537.     /* Put ticks on the axis */
  538.  
  539. /*fprintf(stderr,"power_10 = %f\n",xaxis.power_10);*/
  540.     tick_num = 0;
  541.     loop_count = 0;
  542.     last_tick_pos = 0;
  543.     last_text_pos = 0;
  544.         
  545.     if ( xaxis.linear == LOGRITHMIC ) {
  546.         xaxis_min = xaxis.range.min;
  547.  
  548. #if DEBUG
  549. fprintf(stderr,"min = %f\n",xaxis.range.min);
  550. #endif
  551.  
  552.         best_log_ticks = xscale ( first_log_tick ( xaxis.range.min ) )
  553.         < XRANGE + XORIGIN;
  554.         best_log_ticks = 1;    /* override for now */
  555.  
  556. #if DEBUG_LOG
  557. fprintf(stderr,"best = %d, xscale(min) = %d\n",best_log_ticks,xscale(first_log_tick(xaxis.range.min)));
  558. #endif
  559.  
  560.     }
  561.     else {
  562.  
  563.         /* now, round the value off! If we dont do that, min */
  564.         /* values that are strange fractions will cause misleading */
  565.         /* values to be printed (%.1f will round fractions to one */
  566.         /* decimal place which will be totally wrong for a tick size */
  567.         /* of 0.1 */
  568.  
  569.         xaxis_min = ceil ( xaxis.range.min
  570.         / ( xaxis.tick_size  * pow ( base , xaxis.power_10 ) ) )
  571.         * ( xaxis.tick_size * pow ( base , xaxis.power_10 ) );
  572. #if DEBUG
  573. fprintf(stderr,"x.min %f, x.tick_size %f, x.power_10 %f , xaxis_min %f\n",
  574. xaxis.range.min,xaxis.tick_size,xaxis.power_10,xaxis_min);
  575. #endif
  576.     }
  577.  
  578.     while ( 1 ) {
  579.  
  580.         /* determine value to display next to tick */
  581.  
  582.         if ( xaxis.linear == LOGRITHMIC ) {
  583.         if ( best_log_ticks ) {
  584.             if ( loop_count == 0 ) {
  585.             tick_value = first_log_tick ( xaxis.range.min );
  586. #if DEBUG_LOG
  587. fprintf(stderr,"min = %f, max = %f, tick = %f\n",xaxis.range.min,xaxis.range.max,tick_value);
  588. #endif
  589.             }
  590.             else
  591.             tick_value = next_log_tick ();
  592.         }
  593.         else {
  594.             tick_value = pow ( 10.0 , xaxis.range.min )
  595.             + (double)tick_num
  596.             * ( pow( 10.0 , xaxis.range.max )
  597.                 - pow ( 10.0 , xaxis.range.min ) ) / 5.0;
  598. #if DEBUG_LOG
  599. fprintf(stderr,"tick value is %f\n",tick_value);
  600. #endif
  601.         }
  602.         }
  603.         else {
  604.         tick_value = xaxis_min + (double)tick_num
  605.             * xaxis.tick_size * pow ( base , xaxis.power_10 );
  606.         }
  607.         loop_count++;
  608.         
  609.         /* determine offset along axis to place tick */
  610.  
  611.         if ( xaxis.linear == LOGRITHMIC )
  612.         tick_pos = xscale ( log10 ( tick_value ) );
  613.         else
  614.         tick_pos = xscale ( tick_value );
  615.  
  616.         /* see if meant to auto scale value */
  617.  
  618.         if ( xaxis.scale == AUTO )
  619.         tick_value /= pow ( base , xaxis.power_10 );
  620. #if DEBUG_LOG
  621. fprintf(stderr,"scaled tickvalue to %f\n",tick_value);
  622. #endif
  623.  
  624.         /* run out of axis? */
  625.  
  626.         if ( tick_pos > XRANGE + XORIGIN )
  627.         break;
  628.  
  629.         /* if logrithmic axis, must also check that ticks wont be */
  630.         /* too close together */
  631.  
  632.         if ( xaxis.linear == LOGRITHMIC
  633.         &&  tick_pos != xscale ( next_10_tick () - 1.0 ) ) {
  634.         /* is tick too close to last tick? */
  635.         if ( tick_pos < last_tick_pos + TICK_GAP ) {
  636. #if DEBUG_LOG
  637. fprintf(stderr,"tick %d (%d) too close to LAST position\n",tick_num,tick_pos);
  638. #endif
  639.             continue;        /* try next tick */
  640.         }
  641.         /* is tick too close to next 1,10,100,1000 value? */
  642.         if ( tick_pos + TICK_GAP > xscale ( next_10_tick () ) ) {
  643. #if DEBUG_LOG
  644. fprintf(stderr,"tick %d (%d) too close to NEXT position\n",tick_num,tick_pos);
  645. fprintf(stderr,"next = %f, xscale(next) = %d\n",next_10_tick (),xscale(next_10_tick()));
  646. #endif
  647.             continue;        /* try next tick */
  648.         }
  649.         last_tick_pos = tick_pos;
  650.         }
  651.  
  652.         /* draw the actual tick */
  653.  
  654.         if ( xaxis.frame == GRID )
  655.         line ( tick_pos , base_y + YRANGE - 1 , tick_pos , base_y );
  656.         else
  657.         line ( tick_pos , base_y , tick_pos , base_y - TICK_SIZE );
  658.  
  659.         /* print the tick value */
  660.  
  661.         print_tick = 0;
  662.         if ( xaxis.linear != LOGRITHMIC )
  663.         print_tick = 1;
  664.         else if ( tick_pos >= last_text_pos + TICK_TEXT_GAP  ) {
  665.         if ( xscale ( 2.0 ) - xscale ( 1.0 ) <= TICK_TEXT_GAP ) {
  666.             if ( tick_pos == xscale ( next_10_tick () - 1.0 ) )
  667.             print_tick = 1;
  668.         }
  669.         else {
  670.             if ( tick_pos + TICK_TEXT_GAP <= xscale ( next_10_tick () ) )
  671.             print_tick = 1;
  672.         }
  673.         }
  674. #if DEBUG|DEBUG_LOG
  675. fprintf(stderr,"tick_pos = %d, 2.0-1.0 = %d, print it %d\n",
  676. tick_pos,xscale(2.0)-xscale(1.0),print_tick);
  677. #endif
  678.  
  679.         if ( print_tick ) {
  680.         sprintf ( buf , xformat , tick_value );
  681.         move ( tick_pos - strlen ( buf ) * CHAR_WIDTH / 2 ,
  682.             base_y - ( xaxis.frame == GRID ? 2 : 3 ) * TICK_SIZE );
  683.         label ( buf );
  684.         last_text_pos = tick_pos;
  685. #if DEBUG|DEBUG_LOG
  686. fprintf(stderr,"TICK %d at %d: '%s'\n",tick_num,tick_pos,buf);
  687. #endif
  688.  
  689.         }
  690.  
  691.         /* loop until tick position off axis */
  692.  
  693.         tick_num++;
  694.         if ( tick_num > 100 )
  695.         abort ( "axis tick algorithm failed!!" );
  696.     }
  697.     }
  698.  
  699.     /* Second, the y-axis */
  700.  
  701.     if ( yaxis.frame != NO ) {
  702.  
  703.     /* draw base line */
  704.  
  705.     line ( base_x , base_y , base_x , base_y + YRANGE - 1 );
  706.  
  707.     if ( yaxis.frame == OUTLINE )
  708.         line ( base_x , base_y + YRANGE - 1 , base_x + XRANGE - 1 , base_y + YRANGE - 1 );
  709.  
  710.     /* Label the axis by writing values DOWN the axis */
  711.  
  712.     if ( yaxis.label != NULL ) {
  713.         sprintf ( buf ,
  714.         ( yaxis.scale != AUTO  ||  yaxis.power_10 == 0.0 )
  715.             ? "%s" : "%s x~10^%d" ,
  716.         yaxis.label ,
  717.         (int)yaxis.power_10 );
  718.  
  719. #ifdef OLD_DOWN_AXIS
  720.         for ( i = 0; buf[i] != '\0'; i++ ) {
  721.         move ( base_x - TEXT_GAP - 2 * CHAR_WIDTH , base_y + YRANGE / 2
  722.             + strlen ( buf ) * CHAR_HEIGHT / 2 - i * CHAR_HEIGHT );
  723.         small_buf[0] = buf[i];
  724.         small_buf[1] = '\0';
  725.         label ( small_buf );
  726.         }
  727. #else
  728.  
  729.         /* split label into many lines, each line separated by a space. */
  730.         /* to allow a space to be force, ~ is mapped to a space at the */
  731.         /* last moment. to place the text, each line is centred and */
  732.         /* placed so that the longest line touches the edge of the */
  733.         /* graph. */
  734.  
  735.         /* first find longest line and number of lines */
  736.  
  737.         longest = 0;
  738.         num_lines = 0;
  739.         p = buf;
  740.         while ( *p != '\0' ) {
  741.         num_lines++;
  742.         length = 0;
  743.         while ( *p != ' '  &&  *p != '\0' ) {
  744.             p++;
  745.             length++;
  746.         }
  747.         if ( *p == ' ' )
  748.             p++;
  749.         if ( length > longest )
  750.             longest = length;
  751.         }
  752.  
  753.         /* now print yaxis label by replacing blanks with \0 and */
  754.         /* ~ with blanks and output each line */
  755.  
  756.         line_num = 0;
  757.         p = buf;
  758.         while ( *p != '\0' ) {
  759.         beg = p;
  760.         length = 0;
  761.         while ( *p != '\0'  &&  *p != ' ' ) {
  762.             if ( *p == '~' )
  763.             *p = ' ';
  764.             length++;
  765.             p++;
  766.         }
  767.         if ( *p == ' ' )
  768.             *p++ = '\0';
  769.         move ( ( longest * CHAR_WIDTH/2 ) - ( length * CHAR_WIDTH/2 ) ,
  770.             base_y + YRANGE / 2 + num_lines * CHAR_HEIGHT
  771.             - line_num * CHAR_HEIGHT * 3 / 2 );
  772.         label ( beg );
  773.         line_num++;
  774.         }
  775.  
  776. #endif
  777.     }
  778.  
  779.     /* Put ticks on the axis */
  780.  
  781.     tick_num = 0;
  782.     last_tick_pos = 0;
  783.     last_text_pos = 0;
  784.     loop_count = 0;
  785.     
  786.     if ( yaxis.linear == LOGRITHMIC ) {
  787.         yaxis_min = yaxis.range.min;
  788.     }
  789.     else {
  790.  
  791.         /* now, round the value off! If we dont do that, min */
  792.         /* values that are strange fractions will cause misleading */
  793.         /* values to be printed (%.1f will round fractions to one */
  794.         /* decimal place which will be totally wrong for a tick size */
  795.         /* of 0.1 */
  796.  
  797.         yaxis_min = ceil ( yaxis.range.min
  798.         / ( yaxis.tick_size * pow ( base , yaxis.power_10 ) ) )
  799.         * ( yaxis.tick_size * pow ( base , yaxis.power_10 ) );
  800.     }
  801.  
  802.     while ( 1 ) {
  803.  
  804.         /* determine value to display next to tick */
  805.  
  806.         if ( yaxis.linear == LOGRITHMIC ) {
  807.         if ( loop_count == 0 )
  808.             tick_value = first_log_tick ( yaxis.range.min );
  809.         else
  810.             tick_value = next_log_tick ();
  811.         }
  812.         else {
  813.         tick_value = yaxis_min + (double)tick_num
  814.             * yaxis.tick_size * pow ( base , yaxis.power_10 );
  815.         }
  816.         loop_count++;
  817.         
  818.         /* determine offset along axis to place tick */
  819.  
  820.         if ( yaxis.linear == LOGRITHMIC )
  821.         tick_pos = yscale ( log10 ( tick_value ) );
  822.         else
  823.         tick_pos = yscale ( tick_value );
  824.  
  825.         /* see if meant to auto scale value */
  826.  
  827.         if ( yaxis.scale == AUTO )
  828.         tick_value /= pow ( base , yaxis.power_10 );
  829.  
  830.         /* run out of axis? */
  831.  
  832.         if ( tick_pos > YRANGE + YORIGIN )
  833.         break;
  834.  
  835.         /* if logrithmic axis, must also check that ticks wont be */
  836.         /* too close together */
  837.  
  838.         if ( yaxis.linear == LOGRITHMIC
  839.         &&  tick_pos != yscale ( next_10_tick () - 1.0 ) ) {
  840.         /* is tick too close to last tick? */
  841.         if ( tick_pos < last_tick_pos + TICK_GAP )
  842.             continue;        /* try next tick */
  843.         /* is tick too close to next 1,10,100,1000 value? */
  844.         if ( tick_pos + TICK_GAP > yscale ( next_10_tick () ) )
  845.             continue;        /* try next tick */
  846.         last_tick_pos = tick_pos;
  847.         }
  848.  
  849.         /* draw the actual tick */
  850.  
  851.         if ( yaxis.frame == GRID )
  852.         line ( base_x + XRANGE - 1 , tick_pos , base_x , tick_pos );
  853.         else
  854.         line ( base_x , tick_pos , base_x - TICK_SIZE , tick_pos );
  855.  
  856.         /* print the tick value */
  857.  
  858.         print_tick = 0;
  859.         if ( yaxis.linear != LOGRITHMIC )
  860.         print_tick = 1;
  861.         else if ( tick_pos >= last_text_pos + TICK_TEXT_GAP  ) {
  862.         if ( yscale ( 2.0 ) - yscale ( 1.0 ) <= TICK_TEXT_GAP ) {
  863.             if ( tick_pos == yscale ( next_10_tick () - 1.0 ) )
  864.             print_tick = 1;
  865.         }
  866.         else {
  867.             if ( tick_pos + TICK_TEXT_GAP <= yscale ( next_10_tick () ) )
  868.             print_tick = 1;
  869.         }
  870.         }
  871.  
  872.         if ( print_tick ) {
  873.         sprintf ( buf , yformat , tick_value );
  874.         move ( base_x - strlen ( buf ) * CHAR_WIDTH
  875.             - ( yaxis.frame == GRID ? 1 : 2 ) * TICK_SIZE ,
  876.             tick_pos - CHAR_WIDTH / 2 );
  877.         label ( buf );
  878.         last_text_pos = tick_pos;
  879.         }
  880.  
  881.         /* loop until tick position off axis */
  882.  
  883.         tick_num++;
  884.         if ( tick_num > 100 )
  885.         abort ( "axis tick algorithm failed!!" );
  886.     }
  887.     }
  888. }
  889.  
  890.  
  891. static
  892. determine_range ( which , paxis )
  893. int which;
  894. axis_st *paxis;
  895. {
  896.     double floor () , log10 () , pow ();
  897.     int i;
  898.     double best_min , best_max;
  899.     double new_min , new_max;
  900.     double round_max;
  901.     table_st *table;
  902.  
  903.  
  904.     /* only automatically calculate range if no range has been specified */
  905.     /* (This is indicated by the min value being greater than the max value) */
  906.  
  907.     if ( paxis->range.min >= paxis->range.max ) {
  908.  
  909.     /* scan through all the graphs and determine the largest and */
  910.     /* smallest value. */
  911.  
  912.     if ( which == XAXIS )
  913.         table = graph[0].table;
  914.     else
  915.         table = graph[0].table->next;
  916.     best_min = min_fun ( table , 0 , table->size - 1 );
  917.     best_max = max_fun ( table , 0 , table->size - 1 );
  918.     for ( i = 1; i < num_graphs; i++ ) {
  919.         if ( which == XAXIS )
  920.         table = graph[i].table;
  921.         else
  922.         table = graph[i].table->next;
  923.         new_min = min_fun ( table , 0 , table->size - 1 );
  924.         new_max = max_fun ( table , 0 , table->size - 1 );
  925.         if ( new_min < best_min )
  926.         best_min = new_min;
  927.         if ( new_max > best_max )
  928.         best_max = new_max;
  929.     }
  930.  
  931.     /* check for zero height graph */
  932.  
  933.     if ( best_min == best_max ) {
  934.         
  935.         /* give the graph some height */
  936.  
  937.         if ( best_min == 0.0 ) {
  938.         best_min = -1.0;
  939.         best_max = 1.0;
  940.         }
  941.         else if ( best_min > 0.0 ) {
  942.         if ( paxis->linear == LOGRITHMIC )
  943.             best_min = best_min / 2.0;
  944.         else
  945.             best_min = 0;
  946.         best_max = 2.0 * best_max;
  947.         }
  948.         else {
  949.         best_min = best_min * 0.9;
  950.         best_max = best_max * 1.1;
  951.         }
  952.     }
  953.  
  954.     /* make min a nice round value (eg: 4-1000 => 0-1000) */
  955.  
  956.     if ( best_max > 0.0 ) {
  957.         round_max = pow ( (double)10.0 , floor ( log10 ( best_max / 3.0 ) ) );
  958.         if ( round_max > 0.0 )
  959.         best_min = floor ( best_min / round_max ) * round_max * 0.999;
  960.     }
  961.     /*
  962.     if ( paxis->linear != LOGRITHMIC ) {
  963.         if ( best_max > 0.0 ) {
  964.         round_max = pow ( (double)10.0 , floor ( log10 ( best_max ) ) );
  965.         best_min = floor ( best_min / round_max ) * round_max;
  966.         }
  967.     }
  968.     else {
  969.         if ( best_max > 1.0 ) {
  970.         round_max = pow ( (double)10.0 , floor ( log10 ( best_max ) ) );
  971.         best_min = floor ( best_min / round_max ) * round_max;
  972.         }
  973.     }
  974.     */
  975. #if 0
  976.     /* to stop 0->infinity graphs from hanging the program */
  977.     if ( paxis->linear == LOGRITHMIC ) {
  978.         if ( best_min < best_max / 5.0 )
  979.         best_min = best_max / 5.0;
  980.             /* remember, this is 10^5.0 */
  981.     }
  982. #endif
  983.     paxis->range.min = best_min;
  984.     paxis->range.max = best_max;
  985.     }
  986.     else if ( paxis->linear == LOGRITHMIC ) {
  987.  
  988.     /* convert values to actual values */
  989.  
  990.     if ( paxis->range.min <= 0.0 )
  991.         abort ( "Minimum value for axis on log graph is <= 0" );
  992.     paxis->range.min = log10 ( paxis->range.min );
  993.     paxis->range.max = log10 ( paxis->range.max );
  994.     }
  995. }
  996.  
  997.  
  998.  
  999. static
  1000. calc_ticks ( paxis , tick , power )
  1001. axis_st *paxis;
  1002. double *tick , *power;
  1003. {
  1004.     double log10 ();
  1005.     double base , delta;
  1006.     int power_of_10;
  1007.     double tick_size;
  1008.  
  1009.     base = 10.0;
  1010.     delta = paxis->range.max - paxis->range.min;
  1011.     power_of_10 = (int) floor ( log10 ( delta ) );
  1012.     tick_size = delta / pow ( base , (double)(power_of_10+1) );
  1013.     if ( tick_size <= 0.5 ) {
  1014.     tick_size = ceil ( (double)( tick_size * 10.0 ) ) / (double)10.0;
  1015.     }
  1016.     else {
  1017.     tick_size = 0.1;
  1018.     power_of_10++;
  1019.     }
  1020.  
  1021.     /* round tick size off to x10 ^3,6,9,12,.... which people understand */
  1022.     switch ( power_of_10 % 3 ) {
  1023.     case 0 :        /* already mult of 3 */
  1024.     break;
  1025.     case 1 :        /* its just too high (by one) */
  1026.     tick_size *= 10.0;
  1027.     power_of_10--;
  1028.     break;
  1029.     case 2 :        /* its just too low (by one) */
  1030.     /*
  1031.     tick_size /= 10.0;
  1032.     power_of_10++;
  1033.     */
  1034.     tick_size *= 100.0;
  1035.     power_of_10 -= 2;
  1036.     break;
  1037.     }
  1038.  
  1039.     *tick = tick_size;
  1040.     *power = (double)power_of_10;
  1041. }
  1042.  
  1043.  
  1044.  
  1045. static int lt_value;
  1046. static int lt_power;
  1047.  
  1048.  
  1049. static double
  1050. first_log_tick ( value )
  1051. double value;
  1052. {
  1053.     double actual;
  1054.     double ret_val;
  1055.  
  1056.     actual = pow ( (double)10.0 , value );
  1057.     lt_power = (int) floor ( log10 ( actual ) );
  1058.     lt_value = (int) ceil ( actual / pow ( (double)10.0 , (double)lt_power ) );
  1059.     while ( lt_value >= 10 ) {
  1060.     lt_value /= 10;
  1061.     lt_power++;
  1062.     }
  1063.     ret_val = (double)lt_value * pow ( (double)10.0 , (double)lt_power );
  1064. /*fprintf(stderr,"first_log_tick: lt_power = %d, lt_value = %d, ret_val = %f\n",lt_power,lt_value,ret_val);*/
  1065.     return ( ret_val );
  1066. }
  1067.  
  1068.  
  1069. static double
  1070. next_log_tick ()
  1071. {
  1072.     lt_value++;
  1073.     while ( lt_value >= 10 ) {
  1074.     lt_value /= 10;
  1075.     lt_power++;
  1076.     }
  1077.     return ( (double)lt_value * pow ( (double)10.0 , (double)lt_power ) );
  1078. }
  1079.  
  1080.  
  1081. static double
  1082. next_10_tick ()
  1083. {
  1084.     return ( (double)( lt_power + 1 ) );
  1085. }
  1086.  
  1087.  
  1088. static
  1089. dump_legend ()
  1090. {
  1091.     int i;
  1092.     int num_lines;
  1093.     int longest;
  1094.     int basex , basey;
  1095.  
  1096.  
  1097.     /* first determine how much is going to be output, and what is the */
  1098.     /* longest string that is to be output */
  1099.  
  1100.     num_lines = 0;
  1101.     longest = 0;
  1102.     for ( i = 0; i < num_graphs; i++ ) {
  1103.     if ( graph[i].legend != NULL ) {
  1104.         num_lines++;
  1105.         if ( strlen ( graph[i].legend ) > longest )
  1106.         longest = strlen ( graph[i].legend );
  1107.     }
  1108.     }
  1109.  
  1110.     /* ok, now we want to work out where to start outputing the legend  */
  1111.  
  1112.     switch ( horiz_legend ) {
  1113.  
  1114.     case LEFT :
  1115.     basex = XORIGIN + CHAR_WIDTH * 3;
  1116.     break;
  1117.  
  1118.     case CENTER :
  1119.     basex = XORIGIN + XRANGE / 2 - ( longest * CHAR_WIDTH - LEGEND_LINE_LENGTH ) / 2;
  1120.     break;
  1121.     
  1122.     case RIGHT :
  1123.     basex = XORIGIN + XRANGE - longest * CHAR_WIDTH - 3 * CHAR_WIDTH - LEGEND_LINE_LENGTH - CHAR_WIDTH;
  1124.     break;
  1125.     }
  1126.  
  1127.     switch ( vert_legend ) {
  1128.  
  1129.     case TOP :
  1130.     basey = YORIGIN + YRANGE - CHAR_HEIGHT * 2;
  1131.     break;
  1132.  
  1133.     case MIDDLE :
  1134.     basey = YORIGIN + YRANGE / 2 + num_lines * CHAR_HEIGHT / 2;
  1135.     break;
  1136.  
  1137.     case BOTTOM :
  1138.     basey = YORIGIN + CHAR_HEIGHT * 1 + num_lines * CHAR_HEIGHT;
  1139.     break;
  1140.     }
  1141.  
  1142.     /* ok, now loop through and acutally output the legend! */
  1143.  
  1144.     for ( i = 0; i < num_graphs; i++ ) {
  1145.     if ( graph[i].legend != NULL ) {
  1146.         if ( graph[i].line_type != NO ) {
  1147.         set_line ( graph[i].line_type );
  1148.         line ( basex , basey , basex + LEGEND_LINE_LENGTH , basey );
  1149.         }
  1150.         set_line ( SOLID );
  1151.         draw_point ( graph[i].point_type , basex + LEGEND_LINE_LENGTH / 2 , basey );
  1152.         move ( basex + LEGEND_LINE_LENGTH + CHAR_WIDTH , basey - CHAR_HEIGHT / 3 );
  1153.         label ( graph[i].legend );
  1154.         basey -= CHAR_HEIGHT;
  1155.     }
  1156.     }
  1157. }
  1158.