home *** CD-ROM | disk | FTP | other *** search
/ The UNIX CD Bookshelf / OREILLY_TUCB_UNIX_CD.iso / upt / examples / SOURCES / IPL / PART07.Z / PART07
Encoding:
Text File  |  1998-07-24  |  28.5 KB  |  1,104 lines

  1. Subject:  v21i038:  2D graphic system with table beautifier, Part07/14
  2. Newsgroups: comp.sources.unix
  3. Approved: rsalz@uunet.UU.NET
  4. X-Checksum-Snefru: 480615d0 b4f2cf29 e2047f7b de987b57
  5.  
  6. Submitted-by: Steve Grubb <uunet!lsr-vax!scg>
  7. Posting-number: Volume 21, Issue 38
  8. Archive-name: ipl/part07
  9.  
  10. # ipl part07
  11. #    This is a shell archive.
  12. #    Remove everything above and including the cut line.
  13. #    Then run the rest of the file through sh.
  14. #---------------------- cut here -----------------------------
  15. #!/bin/sh
  16. # shar:    Shell Archiver
  17. #    Run the following text with /bin/sh to create:
  18. #        src/initialize.c
  19. #        src/ipl.d
  20. #        src/ipl.h
  21. #        src/ipl.icon
  22. #        src/ipl.x
  23. #        src/iplps.c
  24. #        src/legend.c
  25. #        src/lib.c
  26. #        src/lineplot.c
  27. cat << \SHAR_EOF > src/initialize.c
  28. /* IPL will be invoked by:   
  29.                  tipl for postscript (to standard output)
  30.                  sipl for sunview previewer
  31.                  vipl for window-oriented composer
  32.                  mipl for terminal-oriented composer
  33. */
  34.  
  35. #include "ipl.x"
  36. #include "gdp.x"  /* for command line arguments */
  37.  
  38. static char fnm[PATHNAME_LEN];
  39. static int filecount;
  40.  
  41. Initialize( )
  42. {
  43. int yr, mon, day, hr, min, sec, i;
  44. char host[30];
  45.  
  46. sysdate( &mon, &day, &yr ); systime( &hr, &min, &sec );
  47.  
  48. strcpy( Templatepath, TEMPLATE_PATH );
  49.  
  50. sprintf( Tempfile, "%s/NTtmp%05d", INSTALL_TMP, getpid() );
  51.  
  52. Dev = Arg[0][ strlen( Arg[0] ) -4 ]; /* get output type */
  53. Hold = 0;
  54. if( !member( Dev, "tsvm" )) { fprintf( stderr, "Use tipl, sipl, vipl or mipl.\n" ); gdp_exit(); }
  55.  
  56. /* composers take off from here.. */
  57. if( smember( Arg[0], "vipl" )) {
  58.     if( Argc < 2 ) { fprintf( stderr, "A control file name argument must be given.\n" ); gdp_exit(); }
  59.     NTinit( Dev );
  60.     vipl_sequence( Arg[1] );
  61.     gdp_exit();
  62.     }
  63.  
  64. /* control file argument given.. */
  65. if( Argc > 1 ) {
  66.     filecount = 1;
  67.     strcpy( fnm, Arg[1] );
  68.     Sfp = fopen( fnm, "r" );
  69.     }
  70.  
  71. if( Dev == 't' && Sfp == NULL ) { fprintf( stderr, "Control file not found.\n" ); exit(); }
  72.  
  73. else if( Sfp == NULL ) {
  74.     NTinit( Dev );
  75.     if( Dev == 's' )siplmenu( "Endoffile" );
  76.     return( 1 );
  77.     }
  78.  
  79. strcpy( Controlfile, fnm );    
  80.  
  81. NTinit( Dev );
  82.  
  83.  
  84. DXlo = DXhi = 0; /* null out data dimensions */
  85. DYlo = DYhi = 0;
  86. sprintf( Buf, "%s %s (%02d%02d%02d %02d:%02d)", getlogin(), (Argc>1)?Arg[1]:"", yr, mon, day, hr, min );
  87.  
  88. strcpy( Stdfont, "/Helvetica" );
  89. StdLw = 1.0;
  90. NTfont( "/Helvetica" ); 
  91. NTptsize( 6 );
  92. NTmov( 0.1, 0.1 ); 
  93. NTtext( Buf );
  94.  
  95. }
  96.  
  97.  
  98. /* ============================================= */
  99. /* start a new control file */
  100. restart( s )
  101. char s[];
  102. {
  103. FILE *fp;
  104.  
  105. if( Sfp != NULL ) fclose( Sfp );
  106.  
  107. if( strcmp( s, "" )==0 ) {
  108.     filecount++;
  109.     if( filecount >= Argc ) gdp_exit();
  110.     strcpy( fnm, Arg[ filecount ] ); /* next file */
  111.     }
  112.  
  113. else if( strlen( s ) > 0 ) strcpy( fnm, s );
  114.  
  115. Sfp = fopen( fnm, "r" );
  116.  
  117. if( Sfp == NULL ) {
  118.     message( fnm, "File not found.", "", "" ); 
  119.     return( 0 ); 
  120.     }
  121.  
  122. strcpy( Controlfile, fnm );    
  123. return( 1 );
  124. }
  125. SHAR_EOF
  126. ############################
  127.  
  128. cat << \SHAR_EOF > src/ipl.d
  129. /* constants */
  130. #include "../install.h"
  131. #define FONTNAME_LEN    30    
  132. #define TMPFILE_PATH    INSTALL_TMP
  133. #define NSLOTS 24    /* number of slots in ASmax[] */
  134.  
  135. #define MOUSE_LEFT 1001
  136. #define MOUSE_MIDDLE 1002
  137. #define MOUSE_RIGHT 1003
  138.  
  139. extern char *getln();
  140. extern FILE *popen();
  141. SHAR_EOF
  142. ############################
  143.  
  144. cat << \SHAR_EOF > src/ipl.h
  145. #include "graphic.x"
  146. #include "ipl.d"
  147.  
  148. char Dev;                /* output device */
  149. char D[ MAX_D_ROWS ][ MAX_D_COLS ][ DATAITEM_LEN ];    /* plot data */ 
  150. int N_d_fields;                    /* number of data fields per row */
  151. int N_d_rows;                    /* number of data rows */
  152. char Buf[HBUFSIZ];                /* general purpose buffers */
  153. char Buf2[512];
  154.  
  155. double Chsz;                /* character size */
  156. double Chh;                /* height of a line of text */
  157. double Chw;                /* character spacing */
  158. double Chd;                /* character direction vector, x comp. */
  159. int Paper = 0;                /* paper orientation */
  160. double Lw;                /* current line width */
  161. double StdLw = 1.0;            /* standard line width */
  162. double Rgb;                /* color identifier */
  163. double Rgbint;                /* color intensity identifier */
  164.  
  165. /* maxes for autoscaling -- currently not used */
  166. double ASmaxes[30] = { 0.1, 0.5, 1, 5, 10, 20, 30, 50, 75, 100, 200, 300, 500, 750, 1000, 1250, 1500, 2000, 2500, 3000, 5000,
  167.                 10000, 20000, 30000, 50000, 100000 };
  168.  
  169. /* default tic increments */
  170. double Incs[30]  = {  0.01, 0.05, 0.1, 1, 1, 2, 5, 5, 5, 10, 20, 20, 50, 50, 50, 50, 
  171.                 100, 100, 100, 200, 500, 500, 1000, 2000, 5000, 5000 };
  172.  
  173. /* default tic stub number formats */
  174. char *Tformats[30]= { "%2.2f", "%2.2f", "%2.1f", "%2.1f", "%2.0f", "%2.0f", "%2.0f", "%2.0f", "%2.0f", "%3.0f", 
  175.             "%3.0f", "%3.0f", "%3.0f", "%3.0f", "%4.0f", "%4.0f", "%4.0f", "%4.0f", "%4.0f", "%4.0f",
  176.             "%4.0f", "%5.0f", "%5.0f", "%5.0f", "%5.0f", "%6.0f" };
  177.  
  178. double DXtic, DYtic;    /* tic increments */
  179.  
  180. char DXticfmt[12], DYticfmt[12];  /* printf format for numeric stubs */
  181.  
  182. char Tempfile[PATHNAME_LEN];    /* name of uniquely named temp file */
  183.  
  184. double Xmin, Xmax, Ymin, Ymax;    /* ranges of the input data, set by proc, before calling global()  */
  185.  
  186. int Hold;    /* TRUE if QuitAppend was called */
  187.  
  188. char Templatepath[PATHNAME_LEN];  /* location of templates and aux. files */
  189.  
  190. char Stdfont[FONTNAME_LEN];  /* page-wide default font */
  191.  
  192. char Controlfile[PATHNAME_LEN] = "";  /* name of control file */
  193. SHAR_EOF
  194. ############################
  195.  
  196. cat << \SHAR_EOF > src/ipl.icon
  197. /* Format_version=1, Width=64, Height=64, Depth=1, Valid_bits_per_item=16
  198.  */
  199.     0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
  200.     0x0018,0x003C,0x0000,0x0000,0x0000,0x000C,0x0000,0x0000,
  201.     0x3C78,0x6C0C,0x0000,0x0000,0x6618,0x760C,0x00FC,0x0000,
  202.     0x6018,0x660C,0x00FC,0x0000,0x3C18,0x660C,0x00CC,0x7E00,
  203.     0x0618,0x660C,0x00CC,0x7E00,0x6618,0x760C,0x00CC,0x6600,
  204.     0x3C18,0x6C0C,0x00CC,0x6600,0x0000,0x6000,0x00CC,0x6600,
  205.     0x0000,0x6000,0x00CC,0x6600,0x0000,0x6000,0x00CC,0x6600,
  206.     0x0660,0x0000,0x00CC,0x6600,0x0660,0x0000,0x00CC,0x6600,
  207.     0x0660,0x03F0,0x00CC,0x6600,0x0660,0x03F0,0x00CC,0x6600,
  208.     0x0660,0x0330,0x00CC,0x6600,0x0660,0x0330,0x00CC,0x663F,
  209.     0x0667,0xE330,0x00CC,0x663F,0x0667,0xE330,0x00CC,0x6633,
  210.     0x0666,0x6330,0x00CC,0x6633,0x0666,0x6330,0x00CC,0x6633,
  211.     0x0666,0x6331,0xF8CC,0x6633,0x0666,0x6331,0xF8CC,0x6633,
  212.     0x0666,0x6331,0x98CC,0x6633,0x0666,0x6331,0x98CC,0x6633,
  213.     0x0666,0x6331,0x98CC,0x6633,0x07E7,0xE3F1,0xF8FC,0x7E3F,
  214.     0xFFFF,0xFFFF,0xFFFF,0xFFFF,0x8040,0x1004,0x0100,0x4010,
  215.     0x8040,0x1004,0x0100,0x4010,0x8040,0x1004,0x0100,0x4010,
  216.     0xAE40,0x1004,0x0100,0x4010,0xE1FE,0x1004,0x0100,0x5810,
  217.     0xF801,0xFFFF,0xDFF7,0xE5FF,0xC800,0x000F,0xC100,0x6410,
  218.     0xC000,0x0000,0x4100,0x4010,0xC000,0x0000,0xBD00,0x4410,
  219.     0xC000,0x0000,0xA501,0xC410,0xC000,0x0000,0xC7F6,0x0DFF,
  220.     0xC000,0x0000,0xC818,0x1810,0xC000,0x0001,0x49E0,0x0810,
  221.     0x8000,0x0001,0xCE80,0x1C10,0x8000,0x0000,0x0000,0x6010,
  222.     0x8000,0x0000,0x0000,0xC010,0x8000,0x0000,0x0000,0xFDFF,
  223.     0x8000,0x0000,0x0001,0x4010,0x8000,0x0000,0x0001,0x4010,
  224.     0xC000,0x0000,0x0002,0x4010,0xC000,0x0000,0x0002,0x43FF,
  225.     0xE000,0x0000,0x0004,0x4201,0xA000,0x0000,0x0007,0xFE01,
  226.     0x9000,0x0000,0x0008,0x427D,0x9000,0x0000,0x0000,0x4201,
  227.     0x8800,0x0000,0x0010,0x427D,0x8600,0x0000,0x0020,0x4201,
  228.     0xF7E0,0x0000,0x0077,0xFE7D,0x8050,0x00C7,0x1840,0x4201,
  229.     0x8050,0x0134,0xE440,0x427D,0x8049,0x820C,0x2220,0x4201,
  230.     0x804E,0x4208,0x2220,0x4201,0xFFFF,0xFFFF,0xFF3F,0xFFFF
  231. SHAR_EOF
  232. ############################
  233.  
  234. cat << \SHAR_EOF > src/ipl.x
  235. #include "graphic.x"
  236. #include "ipl.d"
  237.  
  238. extern char Dev;
  239. extern char D[ MAX_D_ROWS ][ MAX_D_COLS ][ DATAITEM_LEN ];    /* plot data */ 
  240. extern int N_d_fields;                    /* number of data fields per row */
  241. extern int N_d_rows;                    /* number of data rows */
  242. extern char Buf[];                    /* general purpose buffers */
  243. extern char Buf2[];
  244.  
  245. extern double Chsz;                /* character size */
  246. extern double Chw;                /* character spacing */
  247. extern double Chh;                /* height of a line of text */
  248. extern double Chd;                /* character direction vector, x comp. */
  249. extern int Paper;                /* paper orientation */
  250. extern double Lw;                /* current line width */
  251. extern double StdLw;                /* standard line width */
  252. extern double Rgb;                /* color identifier */
  253. extern double Rgbint;                /* color intensity identifier */
  254.  
  255. extern double ASmaxes[];
  256. extern double Incs[]; 
  257. extern char *Tformats[];
  258.  
  259. extern double DXtic, DYtic;    
  260. extern char DXticfmt[], DYticfmt[]; 
  261. extern char Tempfile[];
  262. extern double Xmin, Xmax, Ymin, Ymax;
  263. extern int Hold;
  264. extern char Templatepath[];
  265. extern char Stdfont[];
  266. extern char Controlfile[];
  267. SHAR_EOF
  268. ############################
  269.  
  270. cat << \SHAR_EOF > src/iplps.c
  271. /* Postscript driver for IPL.    
  272.    (c) 1989 Steve Grubb
  273. */
  274.  
  275. #include <stdio.h>
  276. #define YES 1
  277. #define NO 0
  278. #define PORTRAIT 0
  279. #define LANDSCAPE 1
  280. #define MARG_X 14 
  281. #define MARG_Y 8 
  282. #define PAGWIDTH 600;
  283.  
  284. static int orient;        /* paper orientation */
  285. static int orgx, orgy;        /* location of origin on page */
  286. static int theta;        /* current rotation for page orientation */
  287. static char font[50];        /* current font name */
  288. static int chdir;         /* char direction in degrees */
  289. static int curpointsz;        /* current char size in points */
  290. char *getok();
  291. double atof();
  292.  
  293. /* ============================= */
  294. PSsetup( )    /* initialize */
  295. {  
  296. /* set globals */
  297. orient = -1;
  298. strcpy( font, "/Helvetica" );
  299. theta = 0;
  300. chdir = 0;
  301. curpointsz = 10;
  302.  
  303. /* print header */
  304. printf(  
  305. "%%!PS-Adobe-1.0\n%%%%Creator: IPL V1.0(c) 1989 Steve Grubb\n%%%%Pages: (atend)\n" );
  306.  
  307.     
  308. /* set up macros (mainly to reduce output verbosity) */
  309. printf(  "/sset\n" );
  310. printf(  "{ translate rotate } def \n" );
  311. printf(  "/sclr\n" );
  312. printf(  "{ rotate translate } def \n" );
  313. printf(  "/mv { moveto } def\n" );
  314. printf(  "/np { newpath } def\n" );
  315. printf(  "/ln { lineto } def\n" );
  316. printf(  "/st { stroke } def\n" );
  317. printf(  "/sh { show } def\n" );
  318. printf(  "/cent { stringwidth pop sub 2 div 0 rmoveto } def\n" );
  319. printf(  "/rjust { stringwidth pop sub 0 rmoveto } def\n" );
  320.  
  321. /* load default font */
  322. printf(  "%s findfont\n", font );
  323. printf(  "%d scalefont setfont\n", (int) curpointsz );
  324.  
  325. /* set up standard line width */
  326. printf(  "1 setlinewidth\n" );
  327.  
  328. printf(  "%%%%EndProlog\n%%%%Page: ? 1\nsave\n" ); /* begin first page */
  329. }
  330.  
  331.  
  332. /* ============================= */
  333. PSmoveto( x, y )
  334. double x, y;
  335. {
  336.  
  337. /* convert to p.s. units (1/72 inch) */
  338. x = ( x * 72.0 ) +MARG_X; y = ( y * 72.0 ) + MARG_Y; 
  339. printf(  "np\n%-5.2f %-5.2f mv\n", x, y ); 
  340. }
  341.  
  342.  
  343. /* ============================= */
  344. PSlineto( x, y )
  345. double x, y;
  346. {
  347. /* convert to p.s. units */
  348. x = ( x * 72 ) +MARG_X; 
  349. y = ( y * 72 ) +MARG_Y; 
  350. printf(  "%-5.2f %-5.2f ln\n", x, y );
  351. }
  352.  
  353. /* ============================== */
  354. PSstroke( )
  355. {
  356. printf( "st\n" );
  357. }
  358.  
  359.  
  360. /* ============================= */
  361. PSpath( x, y )
  362. double x, y;
  363. {
  364. /* convert to p.s. units */
  365. x = ( x * 72 ) +MARG_X; y = ( y * 72 ) + MARG_Y; 
  366. printf(  "%-5.2f %-5.2f ln\n",  x, y );
  367. }
  368.  
  369.  
  370. /* ============================= */
  371. PSshade( s )
  372. double s;
  373. {
  374. printf(  "closepath\n%3.2f setgray\nfill\n0 setgray\n", s );
  375. }
  376.  
  377. /* ============================== */
  378. PSpaper( i )
  379. int i;
  380. {
  381. if( orient != -1 ) return( 1 );        /* paper orientation - can only be done once per page */
  382. if( i == 1 ) { /* handle landscape mode-- it's put into portrait mode before beginning each page */
  383.     orgx = PAGWIDTH; 
  384.     orgy = 0; 
  385.     theta = 90; 
  386.     printf(  "%d %d %d sset\n", theta, orgx, orgy );
  387.     } 
  388. orient = (int) i;
  389. }
  390.  
  391.  
  392. /* ================================= */
  393. PStext( com, x, y, s, w )
  394. char com;
  395. double x, y;
  396. char s[];
  397. double w;
  398. {
  399. char str[400];
  400. int i, j, k;
  401.  
  402. x = (x*72)+MARG_X;  y = (y*72)+MARG_Y; w *= 72;
  403.  
  404. /* if text direction is not normal do a rotate then move.. */
  405. if( chdir != 0 ) printf(  "%d %-5.2f %-5.2f sset 0 0 mv\n", chdir, x, y ); 
  406. /* normal direction-- do a move.. */
  407. else printf(  "%-5.2f %-5.2f mv ", x, y );
  408.  
  409. if( member( com, "CJ" )) strip_ws( s );
  410.  
  411. /* escape out parens */
  412. for( i = 0, j = 0; i < strlen( s ); i++ ) {
  413.     if( s[i] == '(' || s[i] == ')' ) { str[j++] = '\\'; str[j++] = s[i]; }
  414.     else str[j++] = s[i];
  415.     }
  416. str[j] = '\0';
  417.  
  418. /* centered text */
  419. if( com == 'C' ) printf(  "%-5.2f (%s) cent ", w, str ); 
  420. else if( com == 'J' ) printf(  "%-5.2f (%s) rjust ", w, str );
  421.  
  422. /* do the string */
  423. printf(  "(%s) sh\n", str );
  424.  
  425. if( chdir != 0 ) printf(  "%-5.2f %-5.2f %d sclr\n", -x, -y, -chdir ); /* restore */
  426. }
  427.  
  428.  
  429. /* ================================= */
  430. PSpointsize( p )
  431. int p;
  432. {
  433. if( p != curpointsz ) {     /* char size (specified in points) */
  434.     curpointsz = p;
  435.     printf(  "%s findfont\n", font );
  436.     printf(  "%d scalefont\nsetfont\n", p );
  437.     }
  438. }
  439.  
  440.  
  441. /* ================================== */
  442. PSfont( f )
  443. char f[];
  444. {
  445. if( strcmp( f, font ) != 0 ) {
  446.     strcpy( font, f );
  447.     printf(  "%s findfont\n", font );
  448.     printf(  "%d scalefont setfont\n", curpointsz );
  449.     }
  450. }
  451.  
  452. /* ================================== */
  453. PSchardir( t )
  454. int t;
  455. {
  456. chdir = t;
  457. }
  458.  
  459.  
  460. /* ================================== */
  461. PSlinetype( s, x, y )
  462. char s[];
  463. double x, y;
  464. {
  465. /* X = line width;  Y = dash pattern magnification (0.1 to 10)
  466.  *  S indicates dash pattern.  If S is "0", an unbroken (normal) line is produced.
  467.  *  If S is "1" through "8", a preset dash pattern is used.  Otherwise, S is
  468.  *  assumed to hold the dash pattern string "[ n1 n2 n3.. ]".    
  469.  */
  470. static int dash[10][6]= { {0,0,0,0,0,0}, {1,1}, {3,1}, {5,1}, {2,1,1,1}, {4,1,1,1}, {6,1,1,1}, 
  471.               {2,1,1,1,1,1}, {4,1,1,1,1,1}, {6,1,1,1,1,1} };
  472. int ltype, i;
  473.  
  474. printf(  "%3.1f setlinewidth\n", x );
  475. if( strlen( s ) < 1 || strcmp( s, "0" )==0 ) printf(  "[] 0 setdash\n" );
  476. else     {
  477.     if( strlen( s ) > 1 ) { 
  478.         ltype = 0; 
  479.         sscanf( s, "%d %d %d %d %d %d", &dash[0][0], &dash[0][1], &dash[0][2], 
  480.             &dash[0][3], &dash[0][4], &dash[0][5] );
  481.         }
  482.     else ltype = atoi( s );
  483.     printf(  "[" );
  484.     for( i = 0; i < 6; i++ ) 
  485.         if( dash[ ltype ][ i ] > 0 ) printf(  "%3.1f ", dash[ ltype ][ i ] * y );
  486.     printf(  "] 0 setdash\n" );
  487.     }
  488. }
  489.     
  490.  
  491. /* =================================== */
  492.  
  493. PSshow()
  494. {
  495. if( orient == 1 )printf(  "%d %d %d sclr\n", -orgx, -orgy, -theta ); /* restore rotation */
  496. orient = -1; 
  497. printf( "showpage\nrestore\nsave\n" );
  498. }
  499.  
  500. /* =================================== */
  501. SHAR_EOF
  502. ############################
  503.  
  504. cat << \SHAR_EOF > src/legend.c
  505. /* legend() - creates legend for line and bar graphs */
  506. #include "ipl.x"
  507. #define SHADE    1
  508. #define MARK    4    
  509. #define LINE    8    
  510. #define MAXENTRIES 10
  511.  
  512. Legend( )
  513. {
  514. double    val[MAXENTRIES],
  515.     locx, locy,
  516.     margin,
  517.     piclen,
  518.     lablen,
  519.     depth,
  520.     x, ylo, yhi,
  521.     lx, ly, cx, cy, bxw, ty,
  522.     mrksize,
  523.     stdsize,
  524.     pm[MAXENTRIES],
  525.     ms[MAXENTRIES],
  526.     th[MAXENTRIES],
  527.     thick = 1,
  528.     magnify;
  529. int     i, 
  530.     n,
  531.     format,
  532.     lenmax, 
  533.     outline,
  534.     nms,
  535.     nlt,
  536.     npm,
  537.     nth,
  538.     nmf,
  539.     nent;
  540. char     ent[MAXENTRIES][100],
  541.     poscode[4],
  542.     stdfont[FONTNAME_LEN],
  543.     mrkfont[FONTNAME_LEN],
  544.     lt[MAXENTRIES][3],
  545.     mrk[MAXENTRIES][10],
  546.     mf[MAXENTRIES][20],
  547.     linetype[3];
  548.  
  549.  
  550.  
  551. gget( Buf, "Entry.font" );
  552. strcpy( stdfont, Buf ); NTfont( Buf ); /* go to standard font */
  553.  
  554. strcpy( mrkfont, Stdfont ); /* default mark font */
  555.  
  556. /* position of legend can either be specified using a corner A,B,C,D or by giving an
  557.    x,y location for the upper left corner of the legend.
  558. */
  559. gget( poscode, "Corner" );
  560. gget( Buf, "Location" ); 
  561. if( strlen( Buf ) > 0 ) sscanf( Buf, "%lf %lf", &locx, &locy );
  562. else { locx = 0; locy = 0; }
  563.  
  564. gget( Buf, "Location.system" );
  565. if( strcmp( Buf, "data" )==0 ) { locx = da_x( locx ); locy = da_y( locy ); }
  566.  
  567.  
  568.  
  569. /* get entries and find longest one */
  570. gget( Buf, "Entry" );
  571. lenmax = 0;
  572. nent = countln( Buf );
  573. if( nent > MAXENTRIES ) { fprintf( stderr, "Maximum of 10 legend entries" ); nent = 10; }
  574. getln( "" );
  575. for( i = 0; i < nent; i++ ) {
  576.     strcpy( ent[i], getln( Buf ) );
  577.     if( strlen( ent[i] ) > lenmax ) lenmax = strlen( ent[i] ); 
  578.     }
  579.  
  580. /* figure if we're doing lines or shades, and get 'em */
  581. format = 0;
  582. gget( Buf, "Shade" ); 
  583. if( strlen( Buf ) > 0 ) { 
  584.     format = SHADE; 
  585.     n = sscanf( Buf, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", 
  586.         &val[0],&val[1],&val[2],&val[3],&val[4],&val[5],&val[6],&val[7],&val[8],&val[9] );
  587.     if( n < nent ) { fprintf( stderr, "need %d shades.\n", nent ); gdp_exit(); }
  588.     }
  589. gget( Buf, "Linetype" );
  590. if( strlen( Buf ) > 0 ) {
  591.     format = LINE;
  592.     nlt = sscanf( Buf, "%s %s %s %s %s %s %s %s %s %s", 
  593.         lt[0],lt[1],lt[2],lt[3],lt[4],lt[5],lt[6],lt[7],lt[8],lt[9] );
  594.     gget( Buf, "Linetype.magnify" );
  595.     npm = sscanf( Buf, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", 
  596.         &pm[0],&pm[1],&pm[2],&pm[3],&pm[4],&pm[5],&pm[6],&pm[7],&pm[8],&pm[9] );
  597.     gget( Buf, "Linethick" );
  598.     nth = sscanf( Buf, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", 
  599.         &th[0],&th[1],&th[2],&th[3],&th[4],&th[5],&th[6],&th[7],&th[8],&th[9] );
  600.     }
  601.  
  602.  
  603. gget( Buf, "Mark" );
  604. if( strlen( Buf ) > 0 ) {
  605.     format += MARK;
  606.     n = sscanf( Buf, "%s %s %s %s %s %s %s %s %s %s", 
  607.         mrk[0],mrk[1],mrk[2],mrk[3],mrk[4],mrk[5],mrk[6],mrk[7],mrk[8],mrk[9] );
  608.     if( n < nent ) { fprintf( stderr, "need %d marks\n", nent ); gdp_exit(); }
  609.  
  610.     gget( Buf, "Mark.font" );
  611.     nmf = sscanf( Buf, "%s %s %s %s %s %s %s %s %s %s",
  612.         mf[0], mf[1], mf[2], mf[3], mf[4], mf[5], mf[6], mf[7], mf[8], mf[9] );
  613.  
  614.     gget( Buf, "Mark.size" ); 
  615.     nms = sscanf( Buf, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", 
  616.         &ms[0],&ms[1],&ms[2], &ms[3], &ms[4], &ms[5], &ms[6], &ms[7], &ms[8], &ms[9] );
  617.     }
  618.  
  619.  
  620. margin = .20;
  621. lablen = lenmax * (Chh*0.6); /* lenmax = space in plot units of longest label */
  622.  
  623. if( format == SHADE || format == MARK ) piclen = .20;  /* piclen = size in x of line or swatch */
  624. else piclen = 1.10;
  625.  
  626. depth = (Chh*1.2) * (nent+1);
  627. if( format == SHADE ) depth *= 1.65;
  628.  
  629. /* generate legend */
  630. if( locx != 0 || locy != 0 ) { lx = locx; ly = locy; }
  631. else if( poscode[0] == 'A' ) { lx = Xlo + 0.1; ly = Yhi - 0.1; }
  632. else if( poscode[0] == 'B' ) { lx = Xhi - (lablen+piclen+margin); ly = Yhi - 0.1; }
  633. else if( poscode[0] == 'C' ) { lx = Xhi - (lablen+piclen+margin); ly = Ylo + depth; }
  634. else if( poscode[0] == 'D' ) { lx = Xlo + 0.1; ly = Ylo + depth ; }
  635. else { fprintf( stderr, "Legend location must be given. Use either Corner or Location parameter." ); gdp_exit(); }
  636.  
  637. /* do a legend title */
  638. gget( Buf, "Title.size" );
  639. NTptsize( atof( Buf ) );
  640. gget( Buf, "Title" );
  641. ty = ly + (countln( Buf ) * Chh);
  642. getln( "" );
  643. for( i = 0; i < countln( Buf ); i++ ) {
  644.     NTmov( lx, ty );
  645.     NTcentext( getln( Buf ), (lablen+piclen) );
  646.     ty -= Chh;
  647.     }
  648.  
  649. gget( Buf, "Entry.size" ); stdsize = atof( Buf ); NTptsize( stdsize );
  650.  
  651. gget( Buf, "Backshade" );
  652. if( strlen( Buf ) > 0 ) {
  653.     ab_rect( lx-.08, ly - (depth)+0.02, lx+(lablen+piclen), ly+.08, atof( Buf ), 0 );
  654.     }
  655. gget( Buf, "Outlinebars" );
  656. if( Buf[0] == 'y' ) outline = YES;
  657. else outline = NO;
  658.  
  659. cx = lx; cy = ly;
  660. for( i = 0; i < nent; i++ ) {
  661.  
  662.     if( format >= MARK && i < nms ) mrksize = ms[i];
  663.     if( format >= LINE && i < nlt ) strcpy( linetype, lt[i] );
  664.     if( format >= LINE && i < npm ) magnify = pm[i];
  665.     if( format >= LINE && i < nth ) thick = th[i];
  666.     if( format >= MARK && i < nmf ) strcpy( mrkfont, mf[i] );
  667.  
  668.     /* trap */ if( format >= LINE && ( thick < 0.01 || thick > 20 )) { 
  669.         fprintf( stderr, "Internal error. Try again.\n" ); gdp_exit(); 
  670.         }
  671.  
  672.     if( format == SHADE || format == MARK ) {
  673.         if( format == SHADE ) {
  674.             cy -= 0.1; x = cx; bxw =  0.2; ylo = cy - 0.1; yhi = cy + 0.1;
  675.             if( outline || val[i] == 1.0 )ab_rect( x, ylo, x+bxw, yhi, val[i], 1 );
  676.             else ab_rect( x, ylo, x+bxw, yhi, val[i], 0 );
  677.             }
  678.         else if( format == MARK ) { 
  679.             NTfont( mrkfont ); NTptsize( mrksize );
  680.             if( strncmp( mrk[i], "sym", 3 )==0 ) point( cx, cy, mrk[i], Chh*0.4 );
  681.             else {
  682.                 NTmov( cx, cy-0.06 ); 
  683.                 NTtext( mrk[i] ); 
  684.                 NTfont( stdfont ); 
  685.                 }
  686.             NTmov( cx, cy ); 
  687.             NTfont( stdfont ); NTptsize( stdsize );
  688.             }
  689.         NTmov( cx+0.4, cy-0.06 );
  690.         NTtext( ent[i] );
  691.         }
  692.     else if( format >= LINE ) {
  693.         NTlinetype( linetype, thick, magnify );
  694.         NTmov( cx, cy );
  695.         NTlin( cx+1.0, cy );
  696.         if( format == LINE + MARK ) {
  697.             NTfont( mrkfont ); NTptsize( mrksize );
  698.             if( strncmp( mrk[i], "sym", 3 )==0 ) {
  699.                 point( cx+0.2, cy, mrk[i], Chh*0.4 );
  700.                 point( cx+0.8, cy, mrk[i], Chh*0.4 );
  701.                 }
  702.             else {
  703.                 NTfont( mrkfont ); NTptsize( mrksize ); 
  704.                 NTmov( cx+0.2, cy-0.03 ); NTtext( mrk[i] );
  705.                 NTmov( cx+0.8, cy-0.03 ); NTtext( mrk[i] ); 
  706.                 NTfont( stdfont ); NTptsize( stdsize );
  707.                 }
  708.             NTfont( stdfont ); NTptsize( stdsize );
  709.             }
  710.         NTmov( cx+1.1, cy-0.06 );
  711.         NTtext( ent[i] );
  712.         NTnormline();
  713.         }
  714.     cy -= (Chh*1.2); 
  715.     }
  716. }
  717. SHAR_EOF
  718. ############################
  719.  
  720. cat << \SHAR_EOF > src/lib.c
  721. #include <stdio.h>
  722. #include <ctype.h>
  723.  
  724.  
  725. /* ================================ */
  726. /* getfld()
  727. /* Gets fields which are separated by ":". */
  728. /* Natural ":" which appear in text must be prefixed by a backslash. */
  729. /* Deletes the ':' and all leading and trailing white space. */
  730. /* Leaves index pointing at next char. */
  731.  
  732. getfld( out, line, index )
  733. char out[], line[];
  734. int *index;
  735. {
  736. int i, j, esc;
  737.  
  738. j = 0;
  739. for( i = *index; i < strlen( line ); i++ ) {
  740.     if( line[i] == ' ' || line[i] == '\t' ) continue;
  741.     else break;
  742.     }
  743. for( ; i < strlen( line ); i++ ) {
  744.     if( line[i] == '\\' ) { esc = 1; continue; }
  745.     if( line[i] == ':' && ! esc ) { i++; esc = 0; break; }
  746.     else out[j++] = line[i];
  747.     esc = 0;
  748.     }
  749. j--;
  750. for( ; j > 0; j-- ) {
  751.     if( out[j] == ':' || out[j] == ' ' || out[j] == '\t' || out[j] == '\n' || out[j] == '\0' ) continue;
  752.     else break;
  753.     }
  754. out[ j+1 ] = '\0';
  755. *index = i;
  756. }
  757.  
  758.  
  759. /* ==========================================
  760.  * getok( )
  761. */
  762. #define GETOKMAX    100
  763. char *getok( string, index )
  764.     char    string[];    /* array to obtain token from */
  765.     int    *index;
  766.     {
  767.         static char    tok[GETOKMAX+1];
  768.         register    n;
  769.         while( member( string[(*index)], " \t\n" ) ) (*index)++; 
  770.         /* EAT( SPACE, TAB, EOR, string, (*index) ); */
  771.         for( n=0;
  772.             n <= GETOKMAX &&
  773.             string[*index] != ' '  &&
  774.             string[*index] != '\t'  &&
  775.             string[*index] != '\n'  &&
  776.             string[*index] != '\0'  ;
  777.                 tok[n++] = string[(*index)++] )  ;
  778.         tok[n] = '\0' ;
  779.         if( n > GETOKMAX ) fprintf( stderr, "token %s too long\n", tok );
  780.         return(tok);
  781.     }
  782.  
  783. /* ================================== */
  784. /* goodnum() - checks a token to see if it is a legal number,
  785.     either float or integer, returns 1 if so, 0 if not a
  786.     legal number.  2nd arg is precision, ie the position of
  787.     the decimal point 
  788. */
  789. #include <ctype.h>
  790. #define YES 1
  791. #define NO 0
  792. goodnum( str, prec )
  793. char str[];
  794. int *prec;
  795. {
  796. int l, p, bad, point;
  797. l = strlen( str );
  798. if( l < 1 ) return( 0 );
  799. bad = NO; *prec = -1;
  800. for( p = 0; p < l; p++ ) { 
  801.     if( str[p] == '.' ) { if( *prec == -1 ) *prec = p; else bad=YES; }
  802.     else if( p == 0 && l > 1 && ( str[p] == '-' || str[p] == '+' ) );
  803.     else if( ! isdigit( str[p]) ) bad=YES;
  804.     }
  805. if( bad ) return( 0 );
  806. else return( 1 );
  807. }
  808.  
  809.  
  810. /* =================================== */
  811. /* member() - returns char position if character c is a member of string s, 
  812.         0 otherwise. Char positions start with 1 for this purpose. */
  813. member( c, s )
  814. char c, s[];
  815. {
  816. int i;
  817. for( i = 0; i < strlen( s ); i++ ) if( s[i] == c ) return( i+1 );
  818. return( 0 );
  819. }
  820.  
  821.  
  822.  
  823. /* =================================== */
  824. /* smember() - returns 1 if string s is present as a token in string t,
  825.         0 otherwise. */
  826. smember( s, t )
  827. char s[], t[];
  828. {
  829. char tok[100], *getok();
  830. int i;
  831. i = 0;
  832. while( 1 ) {
  833.     strcpy( tok, getok( t, &i ) );
  834.     if( tok[0] == '\0' ) break;
  835.     if( strcmp( tok, s ) == 0 ) return( 1 );
  836.     }
  837. return( 0 );
  838. }
  839.  
  840.  
  841. /* =================================== */
  842. /* strip white-space off of front and end of string s */
  843. strip_ws( s )
  844. char s[];
  845. {
  846. int i, j;
  847. /* find last significant char and put a null after it */
  848. for( j = strlen( s ) -1; j >= 0; j-- )
  849.     if( !member( s[j], " \t\n" )) break;
  850. s[j+1] = '\0';
  851. /* find 1st significant char at position i */
  852. for( i = 0; i < strlen( s ); i++ ) 
  853.     if( !member( s[i], " \t\n" )) break; 
  854. strcpy( s, &s[i] );
  855. }
  856.  
  857. /* ================= */
  858. sysdate( mon, day, yr )
  859. int    *mon, *day, *yr ;
  860. {
  861.     int    tvec[2], *dtime ;
  862.  
  863.     time( tvec );
  864.     dtime = (int *)(localtime( tvec ));
  865.     *mon = *(dtime+4) + 1 ;
  866.     *day = *(dtime+3)  ;
  867.     *yr = *(dtime+5)  ;
  868. }
  869. /* ================= */
  870. systime( hour, min, sec )
  871. int    *hour, *min, *sec ;
  872. {
  873.     int    tvec[2], *dtime ;
  874.  
  875.     time( tvec );
  876.     dtime = (int *)localtime( tvec );
  877.     *hour = *(dtime+2) ;
  878.     *min = *(dtime+1)  ;
  879.     *sec = *(dtime)  ;
  880. }
  881.  
  882.  
  883. SHAR_EOF
  884. ############################
  885.  
  886. cat << \SHAR_EOF > src/lineplot.c
  887. #include "ipl.x"
  888.  
  889. Lineplot( )
  890. {
  891. int     i, j, p,
  892.     ncurves,
  893.     xfield, yf[10],
  894.     nlab,
  895.     nmrk,
  896.     nsh,
  897.     number,
  898.     nums,
  899.     accum,
  900.     stair,
  901.     x0or1,
  902.     stairbars;
  903.     
  904. double     prvx, prvy,
  905.     x, y,
  906.     cx, cy,
  907.     lblsiz,
  908.     mrksiz,
  909.     size,
  910.     nofs,
  911.     zer,
  912.     pm[10],
  913.     th[10],
  914.     sh[10];
  915.  
  916. char     lt[10][3],
  917.     lb[10][20],
  918.     str[12],
  919.     mk[10][10];
  920.  
  921.  
  922. /* get data fields */
  923. gget( Buf, "Xfield" ); 
  924. xfield = atoi( Buf );
  925.  
  926. gget( Buf, "Xstart.0or1" );
  927. x0or1 = atoi( Buf );
  928.  
  929. gget( Buf, "Yfield" ); 
  930. ncurves = sscanf( Buf, "%d %d %d %d %d %d %d %d %d %d", 
  931.     &yf[0], &yf[1], &yf[2], &yf[3], &yf[4], &yf[5], &yf[6], &yf[7], &yf[8], &yf[9]  );
  932. for( i = 0; i < ncurves; i++ ) if( yf[i] < 1 || yf[i] > N_d_fields ) { fprintf( stderr, "Yfield bad.\n" ); gdp_exit(); }
  933. if( ncurves < 1 ) { fprintf( stderr, "Yfield must be specified.\n" ); gdp_exit(); }
  934.  
  935. gget( Buf, "Accum" ); if( Buf[0] == 'y' ) accum = YES; else accum = NO;
  936.  
  937.  
  938. /* get line parameters for curves */
  939. gget( Buf, "Linetype" );
  940. sscanf( Buf, "%s %s %s %s %s %s %s %s %s %s", 
  941.     lt[0],lt[1],lt[2],lt[3],lt[4],lt[5],lt[6],lt[7],lt[8],lt[9] );
  942. gget( Buf, "Linetype.magnify" );
  943. sscanf( Buf, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", 
  944.     &pm[0],&pm[1],&pm[2],&pm[3],&pm[4],&pm[5],&pm[6],&pm[7],&pm[8],&pm[9] );
  945. gget( Buf, "Linethick" );
  946. sscanf( Buf, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", 
  947.     &th[0],&th[1],&th[2],&th[3],&th[4],&th[5],&th[6],&th[7],&th[8],&th[9] );
  948. gget( Buf, "Mark" );
  949. nmrk = sscanf( Buf, "%s %s %s %s %s %s %s %s %s %s", 
  950.     mk[0],mk[1],mk[2],mk[3],mk[4],mk[5],mk[6],mk[7],mk[8],mk[9] );
  951. gget( Buf, "Mark.size" ); mrksiz = atof( Buf );
  952. NTptsize( mrksiz );
  953. size = Chh * 0.4;
  954.  
  955. gget( Buf, "Shade" );
  956. nsh = sscanf( Buf, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", 
  957.     &sh[0],&sh[1],&sh[2],&sh[3],&sh[4],&sh[5],&sh[6],&sh[7],&sh[8],&sh[9] );
  958. gget( Buf, "Zeroat" ); zer = atof( Buf );
  959.  
  960. gget( Buf, "Label" );
  961. nlab = countln( Buf );
  962. getln( "" );
  963. for( i = 0; i < nlab; i++ ) {
  964.     strcpy( lb[i], getln( Buf ) );
  965.     }
  966.  
  967. gget( Buf, "Stairstep" ); if( Buf[0] == 'y' ) stair = YES; else stair = NO;
  968. gget( Buf, "Stairstep.bars" ); 
  969. if( Buf[0] == 'y' ) { stairbars = YES; stair = YES; }
  970. else stairbars = NO; /* for steps over bars */
  971.  
  972. gget( Buf, "Label.size" ); lblsiz = atof( Buf );
  973.  
  974. gget( Buf, "Numberfinal" ); if( Buf[0] == 'y' ) number = YES; else number = NO;
  975. gget( Buf, "Numbers" ); if( Buf[0] == 'y' ) nums = YES; else nums = NO;
  976. gget( Buf, "Numbers.offset" ); nofs = atof( Buf );
  977.  
  978. NTptsize( lblsiz );
  979.  
  980. /* do shading */
  981. for( j = 0; j < ncurves; j ++ ) {
  982.     if( nsh >= j ) {
  983.         if( stair ) fprintf( stderr, "warning, stairstep can't be combined with shading\n" ); 
  984.         y = atof( D[0][yf[j]-1] );
  985.         if( x0or1 ) x = 1;
  986.         else x = atof( D[0][xfield-1] );
  987.         NTm( x, zer );
  988.         NTp( x, y );
  989.         for( i = 0; i < N_d_rows-1; i++ ) {
  990.             /* get current x, y */
  991.             if( xfield < 1 ) x = i + 2;
  992.             else x = atof( D[i+1][xfield-1] );
  993.             if( accum ) y += atof( D[i+1][yf[j]-1] );
  994.             else y = atof( D[i+1][yf[j]-1] );
  995.             NTp( x, y );
  996.             }
  997.         NTp( x, zer );
  998.         NTshade( sh[j] );
  999.         }
  1000.     }
  1001.  
  1002. /* do the curves */
  1003. for( j = 0; j < ncurves; j ++ ) {
  1004.  
  1005.  
  1006.     NTlinetype( lt[j], th[j], pm[j] );
  1007.  
  1008.     /* find 1st y */
  1009.     y = atof( D[0][yf[j]-1] );
  1010.     /* find 1st x */
  1011.     if( x0or1 ) {
  1012.         if( stairbars ) x = 0.5;
  1013.         else x = 1;
  1014.         }
  1015.     else x = atof( D[0][xfield-1] );
  1016.     prvx = x;
  1017.     prvy = y;
  1018.     
  1019.     /* move to beginning of line */
  1020.     NTm( x, y );
  1021.  
  1022.     for( i = 0; i < N_d_rows-1; i++ ) {
  1023.  
  1024.         /* skip bad values.. */
  1025.         if( ! goodnum( D[i+1][yf[j]-1], &p ) ) {
  1026.             fprintf( stderr, "Warning, row %d field %d is bad (%s)\n", i+2, yf[j], D[i+1][yf[j]-1] );
  1027.             continue;
  1028.             }
  1029.         
  1030.  
  1031.         /* get current x, y */
  1032.         if( xfield < 1 ) {
  1033.             if( stairbars ) x = i+1.5;
  1034.             else x = i+2;
  1035.             }
  1036.         else     x = atof( D[i+1][xfield-1] );
  1037.         if( accum ) y += atof( D[i+1][yf[j]-1] );
  1038.         else y = atof( D[i+1][yf[j]-1] );
  1039.  
  1040.         /* if doing stairsteps, get last x and y */
  1041.         if( stair ) { 
  1042.             if( xfield < 1 ) {
  1043.                 if( stairbars ) prvx = i + .5 ;
  1044.                 else prvx = i+1;
  1045.                 }
  1046.             else prvx = atof( D[i][xfield-1] );
  1047.             if( accum ) prvy += atof( D[i][yf[j]-1] );
  1048.             else prvy = atof( D[i][yf[j]-1] );
  1049.             
  1050.             NTl( x, prvy );
  1051.             if( nums ) { 
  1052.                 NTmov( da_x(prvx), da_y(prvy)+nofs );
  1053.                 sprintf( str, DYticfmt, prvy );
  1054.                 NTcentext( str, da_x(x)-da_x(prvx) ); 
  1055.                 NTm( x, prvy ); 
  1056.                 }
  1057.             }
  1058.         NTl( x, y );
  1059.         if( nums && ! stair ) { 
  1060.             sprintf( str, DYticfmt, y );
  1061.             NTmov( da_x(x)-1, da_y(y)+nofs );
  1062.             NTcentext( str, 2 ); NTm( x, y ); 
  1063.             }
  1064.         }
  1065.     
  1066.     if( stair ) { /* give line a tail */
  1067.         if( nums ) { sprintf( str, DYticfmt, y ); NTmov( da_x( x ), da_y( y )+nofs ); NTtext( str ); }
  1068.         NTm( x, y );
  1069.         x += (DXtic*.7);
  1070.         NTl( x, y );
  1071.         }
  1072.     if( nlab > 0 ) {
  1073.         NTmov( da_x( x )+ 0.05, da_y( y ) - (Chh/2) );
  1074.         NTtext( lb[j] );
  1075.         }
  1076.     if( number ) {
  1077.         sprintf( str, DYticfmt, y );
  1078.         NTmov( da_x( x )+ 0.05, da_y( y ) + (Chh/1.8) );
  1079.         NTtext( str );
  1080.         }
  1081.     /* put marks on lines */
  1082.     if( nmrk >= j ) {
  1083.         if( stair )fprintf( stderr, "warning, stairstep can't be combined with point marks\n"); 
  1084.         y = atof( D[0][yf[j]-1] );
  1085.         if( x0or1 ) x = 1;
  1086.         else x = atof( D[0][xfield-1] );
  1087.         if( strncmp( mk[j], "sym", 3 )==0 ) point( da_x( x ), da_y( y ), mk[j], size );
  1088.         for( i = 0; i < N_d_rows-1; i++ ) {
  1089.             /* get current x, y */
  1090.             if( xfield < 1 ) x = i + 2;
  1091.             else x = atof( D[i+1][xfield-1] );
  1092.             if( accum ) y += atof( D[i+1][yf[j]-1] );
  1093.             else y = atof( D[i+1][yf[j]-1] );
  1094.             if( strncmp( mk[j], "sym", 3 )==0 ) point( da_x( x ), da_y( y ), mk[j], size );
  1095.             }
  1096.         }
  1097.     }
  1098. NTnormline();
  1099. }
  1100. SHAR_EOF
  1101. ############################
  1102.  
  1103.  
  1104.