home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Professional / OS2PRO194.ISO / os2 / wps / progs / chaos / sources / graph.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-12-12  |  15.0 KB  |  431 lines

  1. /*  PMChaos.   (C) Copyright Matthew Austern, 1993.
  2.  
  3.     Permission granted to use, distribute, and modify, provided that this
  4.     notice remains intact.  If you distribute a modified version, you must
  5.     identify your modifications as such.
  6. */
  7.  
  8. #define INCL_PM
  9. #define INCL_DOS
  10. #include <os2.h>
  11. #include <stdio.h>
  12. #include <string.h>
  13. #include <stdlib.h>
  14. #include <math.h>
  15. #include "graph.h"
  16.  
  17. /* Precompute some information we need for drawing the axes.  We need to
  18.    do this every time the range changes, or every time the window is resized,
  19.    or its font changes, but not every time it's redrawn. 
  20.  
  21.    NOTE: I assume that (0,0), in window coordinates, is the lower left corner
  22.    of the window.
  23. */
  24.  
  25.                 /* Set size of the current font. */
  26. static void ScaleFont (HPS PS, unsigned short size)
  27. {
  28.   SIZEF FontSize;
  29.   
  30.   FontSize.cx = FontSize.cy = MAKEFIXED(size, 0);
  31.   DosEnterCritSec();
  32.   GpiSetCharBox(PS, &FontSize);
  33.   DosExitCritSec();
  34. }
  35.  
  36.  
  37. static int GetFont(HPS PS, unsigned short size, long ID)
  38. {
  39.   FATTRS Attrib;        /* Font attributes. */
  40.  
  41.   Attrib.usRecordLength = sizeof(FATTRS);
  42.   Attrib.fsSelection = Attrib.lMatch = Attrib.idRegistry = Attrib.fsType = 0;
  43.   Attrib.usCodePage = 850;    /* Standard code page. */
  44.   Attrib.lMaxBaselineExt = Attrib.lAveCharWidth = 12; /* Size of font. */
  45.   Attrib.fsFontUse = FATTR_FONTUSE_OUTLINE | FATTR_FONTUSE_TRANSFORMABLE;
  46.   strcpy(Attrib.szFacename, "Times New Roman Bold");
  47.  
  48.   if (GpiCreateLogFont(PS, 0, ID, &Attrib) != FONT_MATCH) {
  49.     GpiDeleteSetId(PS, ID);
  50.     return 0;
  51.   }
  52.   else {
  53.     DosEnterCritSec();
  54.     GpiSetCharSet(PS, ID);
  55.     DosExitCritSec();
  56.     ScaleFont(PS, size);
  57.     return 1;
  58.   }
  59. }
  60.  
  61.   
  62. static long TextHeight (HPS PS, char *Text)
  63. {
  64.   POINTL Corners[TXTBOX_COUNT];
  65.   GpiQueryTextBox(PS,
  66.           (long) strlen(Text), Text,
  67.           TXTBOX_COUNT,Corners);
  68.   return Corners[TXTBOX_TOPLEFT].y - Corners[TXTBOX_BOTTOMLEFT].y;
  69. }
  70.  
  71. static long TextWidth (HPS PS, char *Text)
  72. {
  73.   POINTL Corners[TXTBOX_COUNT];
  74.   GpiQueryTextBox(PS,
  75.           (long) strlen(Text), Text,
  76.           TXTBOX_COUNT,Corners);
  77.   return Corners[TXTBOX_TOPRIGHT].x - Corners[TXTBOX_TOPLEFT].x;
  78. }
  79.  
  80. /* This function returns the value that should be given to ScaleFont to
  81.    get back a font of the current size.  It assumes that the CharBox is
  82.    square, i.e., that we're not stretching or squishing the fonts. 
  83. */
  84. static unsigned short CurrentFontSize (HPS PS)
  85. {
  86.   SIZEF theCharBox;
  87.  
  88.   DosEnterCritSec();
  89.   GpiQueryCharBox(PS, &theCharBox);
  90.   DosExitCritSec();
  91.   return (unsigned short) (theCharBox.cx / 0x10000);
  92.                 /* theCharBox.cx is a fixed-point number. */
  93.                 /*  The numerical value we want is this  */
  94.                 /*  number divided by 65536. */
  95. }
  96.   
  97.  
  98. void SizeAxes(HPS hps,
  99.               long Width, long Height,
  100.               double xMin, double xMax, double yMin, double yMax,
  101.           char *xLabel, char *yLabel,
  102.               struct GraphInfo *G)
  103. {
  104.   double xrange, yrange, x_log, y_log, x_mantissa, y_mantissa;
  105.   double x, y; 
  106.   unsigned long maxTextHeight, maxXTextWidth, maxYTextWidth;
  107.   unsigned long TotalTextWidth, TotalTextHeight; /* Space taken up by text. */
  108.   char buffer[100];
  109.   int nXTicks, nYTicks;        /* Number of ticks on each axis. */
  110.   FONTMETRICS theFont;
  111.  
  112.   G->xMin = xMin;        /* Copy information about window to the */
  113.   G->xMax = xMax;        /*  GraphInfo structure. */
  114.   G->yMin = yMin;
  115.   G->yMax = yMax;
  116.   G->WindowWidth = Width;
  117.   G->WindowHeight = Height;
  118.   
  119.   strncpy(G->xLabel, xLabel, MaxGraphLabelLength); /* Save axis labels. */
  120.   strncpy(G->yLabel, yLabel, MaxGraphLabelLength);
  121.   G->xLabel[MaxGraphLabelLength-1] = G->yLabel[MaxGraphLabelLength-1] = 0;
  122.  
  123.   xrange = xMax-xMin;       
  124.   yrange = yMax-yMin;
  125.   if (xrange <= 0 || yrange <= 0) /* Negative range makes no sense. */
  126.     return;
  127.   
  128.  
  129.   /* Decide where to put the ticks.  Rule: let L be the mantissa of
  130.      the range.  If L is greater then 2, put major ticks at integers; 
  131.      otherwise put them at tenth-integers. */
  132.   x_log = log10(xrange);
  133.   y_log = log10(yrange);
  134.   x_mantissa   = pow(10, x_log - floor(x_log));
  135.   y_mantissa   = pow(10, y_log - floor(y_log));
  136.  
  137.   G->xTickInc = pow(10, floor(x_mantissa > 2 ? x_log : x_log - 1));
  138.   G->yTickInc = pow(10, floor(y_mantissa > 2 ? y_log : y_log - 1));
  139.  
  140.   G->xFirstTick = G->xTickInc * ceil(G->xMin/G->xTickInc);
  141.   G->yFirstTick = G->yTickInc * ceil(G->yMin/G->yTickInc);
  142.  
  143.   nXTicks = floor((G->xMax - G->xFirstTick) / G->xTickInc) + 1;
  144.   nYTicks = floor((G->yMax - G->yFirstTick) / G->yTickInc) + 1;
  145.  
  146.   /* Decide on the font size.  Rule: if we can, then just use the size of
  147.      the default font.  However, make sure it fits.  Find out how many
  148.      x labels and y labels we're putting on, and make sure the font is small
  149.      enough so they'll fit in the window.  
  150.  
  151.      For y labels this is easy: if there are nYTicks labels on the y 
  152.      axis, then the height of the font must be smaller than
  153.      WindowHeight/(nYTicks+3), where the 3 is because of the margins.  
  154.      
  155.      For x labels, we need to know the width of each label.  We'll just
  156.      find the width of the largest label, and then multiply by the number
  157.      of labels.
  158.  
  159.      Note that the x margin is also a bit tricky.  It must be wide enough
  160.      to hold the labels on the y axis; it must also, however, be wide 
  161.      enought to hold half of a label on the x axis.  (What if there's
  162.      a tick at the far left of the graph, after all?)
  163.   */
  164.  
  165.   G->FontPointSize = CurrentFontSize(hps); /* Default font size for labels. */
  166.   GetFont(hps, G->FontPointSize, 1); /* Change to font with the right size. */
  167.  
  168.   maxXTextWidth = maxYTextWidth = 0;
  169.   for (x = G->xFirstTick; x < xMax; x += G->xTickInc) {
  170.     sprintf(buffer, "%lg \0", x);
  171.     maxXTextWidth = max(maxXTextWidth, TextWidth(hps, buffer));
  172.   }
  173.   for (y = G->yFirstTick; y < G->yMax; y += G->yTickInc) {
  174.     sprintf (buffer, "%lg\0", y);
  175.     maxYTextWidth = max(maxYTextWidth, TextWidth(hps, buffer));
  176.   }
  177.   
  178.   GpiQueryFontMetrics(hps, sizeof(FONTMETRICS), &theFont);
  179.   TotalTextHeight = theFont.lMaxBaselineExt * (nYTicks+4);
  180.   TotalTextWidth  = maxXTextWidth * (nXTicks+2)
  181.                   + max(2*maxYTextWidth, maxXTextWidth);
  182.                 /* We know how much space we need at the */
  183.                 /*  default font size; readjust the font */
  184.                 /*  as necessary to fit in available space. */
  185.   if (TotalTextHeight > G->WindowHeight || TotalTextWidth > G->WindowWidth) {
  186.     G->FontPointSize =
  187.       min((G->FontPointSize * G->WindowHeight) / TotalTextHeight,
  188.       (G->FontPointSize * G->WindowWidth)  / TotalTextWidth);
  189.       ScaleFont(hps, G->FontPointSize);
  190.   }
  191.  
  192.   /* Now decide on the size of the margins.  We have to leave enough space 
  193.      around the graph for the labels. */
  194.                                 /* First, check height of the x labels. */
  195.   maxTextHeight = maxYTextWidth = 0;
  196.   for (x = G->xFirstTick; x < xMax; x += G->xTickInc) {
  197.     sprintf (buffer, "%lg\0", x);
  198.     maxTextHeight = max(maxTextHeight, TextHeight(hps, buffer));
  199.     maxXTextWidth = max(maxXTextWidth, TextWidth(hps, buffer));
  200.   }
  201.                                 /* Do the same for width of the y labels. */
  202.   maxXTextWidth = 0;
  203.   for (y = G->yFirstTick; y < G->yMax; y += G->yTickInc) {
  204.     sprintf (buffer, "%lg\0", y);
  205.     maxYTextWidth = max(maxYTextWidth, TextWidth(hps, buffer));
  206.   }
  207.                 /* Look at font metrics again, and  */
  208.                 /*  squirrel away some of the information. */
  209.   GpiQueryFontMetrics(hps, sizeof(FONTMETRICS), &theFont);
  210.   G->FontHeight = theFont.lLowerCaseAscent;
  211.   G->FontWidth  = theFont.lAveCharWidth;
  212.                 /* Finally, return the margin information. */ 
  213.   G->xRightMargin = max(maxYTextWidth, maxXTextWidth/2) + 2*G->FontWidth;
  214.   G->yTopMargin   = maxTextHeight  * 5/4;
  215.   G->xLeftMargin  = G->xRightMargin + 2*G->FontHeight; /* Extra space for */
  216.   G->yBotMargin   = G->yTopMargin   + 2*G->FontHeight; /*  the axis names. */
  217.  
  218.   G->xConvFactor = (G->WindowWidth - G->xLeftMargin- G->xRightMargin) / xrange;
  219.   G->yConvFactor = (G->WindowHeight- G->yBotMargin - G->yTopMargin) / yrange;
  220.  
  221.   G->MajorTickSize = (G->FontHeight*4)/5;
  222.   G->HalfTickSize  =  G->FontHeight/2;
  223.   G->MinorTickSize =  G->FontHeight/4;
  224.  
  225.   G->xLabelWidth = TextWidth(hps, G->xLabel);
  226.   G->yLabelWidth = TextWidth(hps, G->yLabel);
  227.  
  228.   GpiDeleteSetId(hps, 1);    /* We don't need the font again until it's */
  229.                 /*  actually time to display the text. */
  230. }
  231.  
  232. static void MakeXTick(HPS hps, int x, int y, int length)
  233. {
  234.   POINTL Position;
  235.   Position.x = x;
  236.   Position.y = y;
  237.   GpiMove(hps, &Position);
  238.  
  239.   Position.y += length;
  240.   GpiLine(hps, &Position);
  241.  
  242.   Position.y = y;
  243.   GpiMove(hps, &Position);
  244. }
  245.  
  246. static void MakeYTick(HPS hps, int x, int y, int length) 
  247. {
  248.   POINTL Position;
  249.   Position.x = x;
  250.   Position.y = y;
  251.   GpiMove(hps, &Position);
  252.  
  253.   Position.x += length;
  254.   GpiLine(hps, &Position);
  255.  
  256.   Position.x = x;
  257.   GpiMove(hps, &Position);
  258. }
  259.  
  260. /* Display a string at (x,y).  Leaves current position unchanged. */
  261. static void DrawText(HPS PS, char *text, long x, long y)
  262. {
  263.   POINTL Pos;            /* Where to start drawing string. */
  264.   long TextLength;
  265.  
  266.   Pos.x = x;
  267.   Pos.y = y;
  268.   TextLength = (long) strlen(text);
  269.   GpiCharStringAt(PS, &Pos, TextLength, text);
  270. }
  271.  
  272. void DrawAxes(HPS hps, struct GraphInfo *G)
  273. {
  274.   long SavedPS;
  275.   double x, y;
  276.   long WinX, WinY;        /* x and y in Gpi coordinates. */
  277.   double xMinorTickInc, yMinorTickInc, xFirstMinorTick, yFirstMinorTick;
  278.   int xMajorTickIndex, yMajorTickIndex;
  279.   char label[100];        /* Text that we put at a major tick. */
  280.   long LabelWidth;        /* Size of text, in Gpi coordinates. */
  281.   POINTL LowerLeftCorner, UpperRightCorner;
  282.   static GRADIENTL VertAngle = {0, 1}; /* Used to rotate text. */
  283.   int i;
  284.  
  285.   SavedPS = GpiSavePS(hps);
  286.   GetFont(hps, G->FontPointSize, 1); /* Use correctly scaled font. */
  287.                                     /* Draw the 0 axes. */
  288.   GpiSetLineType(hps, LINETYPE_DOT);
  289.   GpiSetColor(hps, CLR_PALEGRAY);
  290.   if (G->yMin<0 && G->yMax>0) {
  291.     POINTL Left, Right;
  292.     Left.x  = G->xLeftMargin;
  293.     Right.x = G->WindowWidth - G->xRightMargin;
  294.     Left.y  = (long) (G->yBotMargin - G->yMin * G->yConvFactor);
  295.     Right.y = Left.y;
  296.     GpiMove(hps, &Left);
  297.     GpiLine(hps, &Right);
  298.   }
  299.   if (G->xMin<0 && G->xMax>0) {
  300.     POINTL Top, Bottom;
  301.     Bottom.y = G->yBotMargin;
  302.     Top.y    = G->WindowHeight - G->yTopMargin;
  303.     Bottom.x = (long) (G->xLeftMargin - G->xMin * G->xConvFactor);
  304.     Top.x    = Bottom.x;
  305.     GpiMove(hps, &Bottom);
  306.     GpiLine(hps, &Top);
  307.   }
  308.                                 /* Draw the box. */
  309.   LowerLeftCorner.x  = G->xLeftMargin;
  310.   LowerLeftCorner.y  = G->yBotMargin;
  311.   UpperRightCorner.x = G->WindowWidth  - G->xRightMargin;
  312.   UpperRightCorner.y = G->WindowHeight - G->yTopMargin;
  313.   GpiSetColor(hps, CLR_DEFAULT);
  314.   GpiSetLineType(hps, LINETYPE_SOLID);
  315.   GpiMove(hps, &LowerLeftCorner);
  316.   GpiBox(hps, DRO_OUTLINE, &UpperRightCorner, 0, 0); 
  317.  
  318.                                 /* Hardest part: draw and label the ticks. */
  319.   yMinorTickInc  = G->yTickInc/10; /* Do the y axis. */
  320.   yMajorTickIndex= (long) (floor((G->yFirstTick - G->yMin)/yMinorTickInc));
  321.   yFirstMinorTick= G->yFirstTick - yMinorTickInc * yMajorTickIndex;
  322.   for (i=0, y=yFirstMinorTick; y < G->yMax; i++, y += yMinorTickInc) {
  323.     if (((i-yMajorTickIndex) % 10) == 0) { /* It's a major tick. */
  324.       WinY = (long) (G->yBotMargin + (y - G->yMin) * G->yConvFactor);
  325.       MakeYTick(hps, G->xLeftMargin, WinY, G->MajorTickSize);   
  326.       if (fabs(y) < yMinorTickInc*1.e-6)
  327.         strcpy(label, "0");
  328.       else
  329.         sprintf(label, "%lg\0", y);
  330.       LabelWidth = TextWidth(hps, label);
  331.       DrawText(hps, label,
  332.            G->xLeftMargin - LabelWidth - G->FontWidth,
  333.            WinY - G->FontHeight/2);
  334.    }
  335.     else if (((i - yMajorTickIndex) % 5) == 0) { /* It's a half tick. */
  336.       MakeYTick (hps,
  337.                  G->xLeftMargin,
  338.          (int)(G->yBotMargin + (y-G->yMin) * G->yConvFactor),
  339.                  G->HalfTickSize);
  340.     }
  341.     else {                          /* It's a minor tick. */
  342.       MakeYTick (hps,
  343.                  G->xLeftMargin,
  344.          (int)(G->yBotMargin + (y-G->yMin) * G->yConvFactor),
  345.                  G->MinorTickSize);
  346.     }
  347.   }
  348.  
  349.   xMinorTickInc  = G->xTickInc/10; /* Do the x axis. */
  350.   xMajorTickIndex= (int) (floor((G->xFirstTick - G->xMin)/xMinorTickInc));
  351.   xFirstMinorTick= G->xFirstTick - xMinorTickInc * xMajorTickIndex;
  352.   for (i=0, x=xFirstMinorTick; x < G->xMax; i++, x += xMinorTickInc) {
  353.     if (((i-xMajorTickIndex) % 10) == 0) { /* It's a major tick. */
  354.       WinX = (long) (G->xLeftMargin + (x - G->xMin) * G->xConvFactor);
  355.       MakeXTick(hps, WinX, G->yBotMargin, G->MajorTickSize);
  356.       if (fabs(x) < xMinorTickInc*1.e-6)
  357.         strcpy(label, "0");
  358.       else
  359.         sprintf(label, "%lg\0", x);
  360.       LabelWidth = TextWidth(hps, label);
  361.       DrawText(hps, label,
  362.            WinX - LabelWidth/2, G->yBotMargin - 3*G->FontHeight/2);
  363.     }
  364.     else if (((i - xMajorTickIndex) % 5) == 0)  /* It's a half tick. */
  365.       MakeXTick (hps,
  366.                  (int)(G->xLeftMargin + (x-G->xMin) * G->xConvFactor),
  367.          G->yBotMargin,
  368.                  G->HalfTickSize); 
  369.     else        
  370.       MakeXTick (hps,
  371.                  (int)(G->xLeftMargin + (x-G->xMin) * G->xConvFactor),
  372.          G->yBotMargin,
  373.                  G->MinorTickSize); 
  374.   }
  375.  
  376.                 /* Draw the axis names. */
  377.   GpiSetBackColor(hps, CLR_PALEGRAY);
  378.   GpiSetBackMix(hps, BM_OVERPAINT);
  379.   GpiSetColor(hps, CLR_DARKBLUE);
  380.   DrawText(hps,            /* The x axis. */
  381.        G->xLabel,
  382.        G->xLeftMargin + (G->WindowWidth- G->xLeftMargin- G->xRightMargin)/2
  383.                       - G->xLabelWidth/2,
  384.        G->FontHeight);
  385.  
  386.   GpiSetCharAngle(hps, &VertAngle); /* Rotate the text by 90 degrees.  Note */
  387.                     /*  that this won't work unless we've */
  388.                     /*  got an outline font. */
  389.   DrawText(hps,
  390.        G->yLabel,
  391.        G->FontHeight * 7/4,
  392.        G->yBotMargin + (G->WindowHeight - G->yBotMargin - G->yTopMargin)/2
  393.                      - G->yLabelWidth/2);
  394.  
  395.   GpiDeleteSetId(hps, 1);    /* Get rid of font we've used. */
  396.   GpiRestorePS(hps, SavedPS);    /* Get rid of changes we've made to 
  397.                 /*   the PS parameters. */
  398. }
  399.  
  400. void ClipToGraph (HPS PS, struct GraphInfo *G)
  401. {
  402.   POINTL LowerLeftCorner, UpperRightCorner;
  403.   RECTL ClipRectangle;
  404.   HRGN ClipRegion, oldClipRegion;
  405.  
  406.   LowerLeftCorner.x  = G->xLeftMargin;
  407.   LowerLeftCorner.y  = G->yBotMargin;
  408.   UpperRightCorner.x = G->WindowWidth  - G->xRightMargin;
  409.   UpperRightCorner.y = G->WindowHeight - G->yTopMargin;
  410.  
  411.   GpiConvert(PS, CVTC_PAGE, CVTC_DEVICE, 1L, &LowerLeftCorner);    
  412.   GpiConvert(PS, CVTC_PAGE, CVTC_DEVICE, 1L, &UpperRightCorner);    
  413.  
  414.   ClipRectangle.xLeft  = LowerLeftCorner.x;
  415.   ClipRectangle.xRight = UpperRightCorner.x;
  416.   ClipRectangle.yBottom= LowerLeftCorner.y;
  417.   ClipRectangle.yTop   = UpperRightCorner.y;
  418.  
  419.   ClipRegion = GpiCreateRegion(PS, 1, &ClipRectangle);
  420.   GpiSetClipRegion(PS, ClipRegion, &oldClipRegion);
  421.   GpiDestroyRegion(PS, oldClipRegion);
  422. }
  423.  
  424. void UndoClipping (HPS PS)
  425. {
  426.   HRGN oldClipRegion;
  427.  
  428.   GpiSetClipRegion(PS, 0, &oldClipRegion);
  429.   GpiDestroyRegion(PS, oldClipRegion);
  430. }
  431.