//==========================================================================//
//                                  Includes                                //
//==========================================================================//


#include <stdio.h>
#include "perfmon.h"
#include "grafdisp.h"      // external declarations for this file

#include "grafdata.h"      // for InsertGraph, et al.
#include "graph.h"
#include "legend.h"
#include "line.h"          // for LineCreatePen
#include "perfmops.h"      // for DoWindowDrag
#include "playback.h"      // for PlayingBackLog
#include "valuebar.h"
#include "utils.h"
#include "timeline.h"      // for IsTLineWindowUp & TLineRedraw 

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

// this macro is used in doing a simple DDA (Digital Differential Analyzer)
// * 10 + 5 is to make the result round up with .5
#define DDA_DISTRIBUTE(TotalTics, numOfData) \
   ((TotalTics * 10 / numOfData) + 5) / 10

HDC   hGraphDisplayDC ;
//=============================//
// GraphDisplay Class          //
//=============================//


TCHAR   szGraphDisplayWindowClass[] = TEXT("PerfmonGraphDisplayClass") ;
#define dwGraphDisplayClassStyle    (CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_OWNDC)
#define iGraphDisplayClassExtra     (0)
#define iGraphDisplayWindowExtra    (0)
#define dwGraphDisplayWindowStyle   (WS_CHILD | WS_VISIBLE)



//==========================================================================//
//                              Local Functions                             //
//==========================================================================//
BOOL UpdateTimeLine (HDC hDC, PGRAPHSTRUCT pGraph, BOOL getLastTimeLocation) ;

#if 0
PGRAPHSTRUCT GraphData (HWND hWndGraphDisplay)
/*
   Effect:        Return the graph associated with graph display window
                  hWndGraphData. At the present time, we only have one
                  graph data window and one graph structure. In the
                  future, we may have several of each. The graph structure
                  is conceptually instance data of the graph display
                  window. Use of this function allows for easier additions
                  to the code.
*/
   {
   return (pGraphs) ;
   }
#endif

INT ScaleAndInvertY (FLOAT ey, 
                     PLINESTRUCT pLineStruct,
                     PGRAPHSTRUCT pGraph)
/*
   Effect:        Given data value ey, scale and fit the value to fit
                  within the graph data area of the window, considering
                  the scale set for the line and the current size of the
                  data rectangle.
*/   
   {  // ScaleAndInvertY
   INT     yGraphDataHeight,               // Height of graph area
           yInverted ;                     // Scaled & Inverted Y.
   FLOAT   eppd,
           eyScaled ;


   // Take care of any scaling now, at output time.
   ey *= pLineStruct->eScale ;

   // Calculate the Cy of the graph area.
   yGraphDataHeight = pGraph->rectData.bottom - pGraph->rectData.top ;

   // Calculate the pixels per data point.
   eppd = (FLOAT) ((FLOAT) yGraphDataHeight / (FLOAT) pGraph->gOptions.iVertMax) ;
   eyScaled = eppd * ey ;
   yInverted = (INT) (((FLOAT) yGraphDataHeight) - eyScaled) ;

   yInverted += pGraph->rectData.top ;
   
   // Clamp the range to fit with in the graph portion of the windows
   yInverted = PinInclusive (yInverted, 
                             pGraph->rectData.top, pGraph->rectData.bottom) ;
   return (yInverted) ;
   }  // ScaleAndInvertY



BOOL DrawGrid (HDC hDC, 
               PGRAPHSTRUCT pGraph,
               LPRECT lpRect,
               BOOL bForPaint)
/*
   Effect:        Draw the grid lines in the graph display window.
                  These grid lines are in the graph data area only,
                  which is indicated by pGraph->rectData.

   Called By:     OnPaint only.
*/
   {  // DrawGrid
   int            iGrid, iLines ;
   int            xGrid, yGrid ;
   POINT          aPoints [4 * iGraphMaxTics] ;
   DWORD          aCounts [2 * iGraphMaxTics] ;
   HPEN           hPenPrevious ;
   int            bottomAdjust ;

   if (!pGraph->gOptions.bHorzGridChecked && 
       !pGraph->gOptions.bVertGridChecked)   
      return (FALSE) ;


   hPenPrevious = SelectPen (hDC, IsPrinterDC (hDC) ?
      GetStockObject (BLACK_PEN) : pGraph->hGridPen) ;

   iLines = 0 ;

   if (pGraph->gOptions.bHorzGridChecked)
      {
      for (iGrid = 1 ;
           iGrid < pGraph->yNumTics ;
           iGrid++)
         {  // for
         yGrid = pGraph->ayTics[iGrid] + pGraph->rectData.top ;
         if (yGrid >= lpRect->top &&
             yGrid <= lpRect->bottom)
            {  // if
            aPoints[2 * iLines].x = lpRect->left ;
            aPoints[2 * iLines].y = yGrid ;
            aPoints[2 * iLines + 1].x = lpRect->right ;
            aPoints[2 * iLines + 1].y = yGrid ;

            aCounts[iLines] = 2 ;
            iLines++ ;
            }  // if
         }  // for
      }  // if

   if (pGraph->gOptions.bVertGridChecked)
      {
      bottomAdjust = lpRect->bottom + (bForPaint ? 1 : 0) ;
      for (iGrid = 1 ;
           iGrid < pGraph->xNumTics ;
           iGrid++)
         {  // for
         xGrid = pGraph->axTics[iGrid] + pGraph->rectData.left ;
         if (xGrid >= lpRect->left &&
             xGrid <= lpRect->right)
            {  // if
            aPoints[2 * iLines].x = xGrid ;
            aPoints[2 * iLines].y = lpRect->top ;
            aPoints[2 * iLines + 1].x = xGrid ;
            aPoints[2 * iLines + 1].y = bottomAdjust ;

            aCounts[iLines] = 2 ;
            iLines++ ;
            }  // if
         }  // for
      }  // if

   if (iLines)
      PolyPolyline (hDC, aPoints, aCounts, iLines) ;

   SelectPen (hDC, hPenPrevious) ;

   return (TRUE) ;
   }  // DrawGrid



BOOL DrawBarChartData (HDC hDC, 
                       PGRAPHSTRUCT pGraph)
   {  // DrawBarChartData
   PLINESTRUCT pLineStruct ;
   PFLOAT      pDataPoints ;
   INT         nLegendItems,
               cx,
               cxBar,
               xDataPoint,
               y ;
   RECT        rectBar ;
   RECT        rectBkgrnd ;
   HBRUSH      hOldBrush ;
   FLOAT       eValue ;
   PLINESTRUCT pCurrentLine ;

   // Determine how many items are in the legend.

   nLegendItems = 0 ;

   for (pLineStruct = pGraph->pLineFirst ;
        pLineStruct ;
        pLineStruct = pLineStruct->pLineNext)
      {  // for
      nLegendItems++ ;
      }  // for

   if (nLegendItems == 0)
      return(FALSE) ;

   // get current select line for highlighting
   if (pGraph->HighLightOnOff)
      {
      pCurrentLine = CurrentGraphLine (hWndGraph) ;
      }
   else
      {
      pCurrentLine = NULL ;
      }

   // Determine the width of each bar.
   cx = pGraph->rectData.right - pGraph->rectData.left ;


   if (PlayingBackLog())
      {
      // get the average using the start and stop data point 
      // from the log file
      PlaybackLines (pGraph->pSystemFirst, 
                     pGraph->pLineFirst, 
                     PlaybackLog.StartIndexPos.iPosition) ;
      PlaybackLines (pGraph->pSystemFirst, 
                     pGraph->pLineFirst, 
                     PlaybackLog.StopIndexPos.iPosition) ;
      }
   else
      {
      // Loop through all the DataLines and draw a bar for
      // it's last value.
      xDataPoint = pGraph->gKnownValue % pGraph->gMaxValues ;
      }

   rectBar.bottom = pGraph->rectData.bottom + 1 ;

   rectBkgrnd = pGraph->rectData ;

   hOldBrush = SelectBrush (hDC, hBrushFace) ;

   PatBlt (hDC, 
           rectBkgrnd.left, rectBkgrnd.top,
           rectBkgrnd.right - rectBkgrnd.left,
           rectBkgrnd.bottom - rectBkgrnd.top + 1,
           PATCOPY) ;
   DrawGrid(hDC, pGraph, &(rectBkgrnd), FALSE) ;

   rectBar.right = pGraph->rectData.left ;
   for (pLineStruct = pGraph->pLineFirst ;
        pLineStruct ;
        pLineStruct = pLineStruct->pLineNext)
      {  // for
      pDataPoints = pLineStruct->lnValues ;

      if (PlayingBackLog())
         {
         eValue = (*(pLineStruct->valNext))(pLineStruct) ;
         }
      else
         {
         eValue = pDataPoints[xDataPoint] ;
         }


      y = ScaleAndInvertY (eValue,
                           pLineStruct,
                           pGraph) ;

      rectBar.left   = rectBar.right ;
      rectBar.top    = y ;

      // nomore line to draw
      if (nLegendItems == 0 )
         {
         break ;
         }

      cxBar = DDA_DISTRIBUTE (cx, nLegendItems) ;
      rectBar.right  = rectBar.left + cxBar ;

      // setup for next DDA
      nLegendItems-- ;
      cx -= cxBar ;

      // NOTE: this handle creation should be moved to line
      //       create time.

      if (pCurrentLine == pLineStruct)
         {
         SetBkColor (hDC, crWhite) ;
         }
      else
         {
         SetBkColor (hDC, pLineStruct->Visual.crColor) ;
         }
       ExtTextOut (hDC, rectBar.right, rectBar.top, ETO_OPAQUE,
         &rectBar, NULL, 0, NULL) ;
      }  // for

   return (TRUE) ;
   }


/***************************************************************************
 * DrawTLGraphData - Draw Time Line Graph Data.
 *
 *  Some notes about drawing the DataPoint graphs.
 *
 *      1]  It's real expensive to make a GDI call. So, we do not
 *          make a MoveToEx and LineTo call for each point.  Instead
 *          we create a polyline and send it down to GDI.
 *
 *      2]  The X coordinates for each point in the polyline is generated
 *          from our favorite xDataPoint to xWindows DDA.
 *
 *      3]  The Y coordinate is generated from the pLineStruct->lnValues[x]
 *          data associated with each line.
 ***************************************************************************/
BOOL DrawTLGraphData (HDC hDC, 
                      BOOL bForPaint,
                      PRECT prctPaint, 
                      PGRAPHSTRUCT pGraph)
/*
   Called By:     UpdateGraphDisplay only. 
*/
   {
   PLINESTRUCT pLineStruct ;
   HPEN        hPen = 0 ;
   HPEN        hOldPen ;
   PFLOAT      pDataPoints ;
   INT         i, j,
               iValidValues,
               xDispDataPoint,
               xLeftLimit,
               xRightLimit ;
   PPOINT      pptDataPoints ;
   INT         numOfData, rectWidth, xPos ;
   PLINESTRUCT pCurrentLine ;
   INT         DrawZeroPoint = 0 ;

//   SetBkColor (hDC, crLightGray) ;

   if (!IsPrinterDC (hDC))
      {
      if (bForPaint)
         {
         IntersectClipRect (hDC, 
                            pGraph->rectData.left,
                            pGraph->rectData.top,
                            pGraph->rectData.right,
                            pGraph->rectData.bottom + 1) ;
         }
      else
         {
         IntersectClipRect (hDC, 
                            pGraph->rectData.left,
                            pGraph->rectData.top,
                            PlayingBackLog () ? 
                              pGraph->rectData.right :
                              min (pGraph->rectData.right,
                                   pGraph->gTimeLine.xLastTime + 2),
                            pGraph->rectData.bottom + 1) ;
         }
      }

   xLeftLimit  = prctPaint->left  - pGraph->gTimeLine.ppd - 1 ;
//   xRightLimit = prctPaint->right + pGraph->gTimeLine.ppd ;
   xRightLimit = prctPaint->right ;
   pptDataPoints  = pGraph->pptDataPoints ;

   iValidValues   = pGraph->gTimeLine.iValidValues ;

   if (!PlayingBackLog() &&
      pGraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
      {
      // drawing the 0th at the end of the chart.
      DrawZeroPoint = 1 ;
      if (iValidValues == pGraph->gMaxValues)
         {
         iValidValues++ ;
         }
      }

   // get current select line for highlighting
   if (pGraph->HighLightOnOff)
      {
      pCurrentLine = CurrentGraphLine (hWndGraph) ;
      }
   else
      {
      pCurrentLine = NULL ;
      }

   // loop through lines to plot
   for (pLineStruct = pGraph->pLineFirst ;
        pLineStruct || pCurrentLine;
        pLineStruct = pLineStruct->pLineNext)
      {  // for

      if (pLineStruct == NULL)
         {
         // now draw the current line
         pLineStruct = pCurrentLine ;
         }
      else if (pLineStruct == pCurrentLine)
         {
         // skip this line and draw it later
         continue ;
         }

      // "Localize" some variables from the line data structure.
      pDataPoints    = pLineStruct->lnValues ;


      rectWidth      = pGraph->rectData.right - pGraph->rectData.left ;
      numOfData      = pGraph->gMaxValues - 1 + DrawZeroPoint ;

      // Generate the polyline data.
      xDispDataPoint = pGraph->rectData.left ;

      // Only process points that lie within the update region.
      // Also only process points that have valid data.
      j = 0 ;

      for (i = 0 ; i < iValidValues ; i++)
         {  // for
         if (xDispDataPoint > xRightLimit)
            {
            // we are done!
            break ;
            }
         if (xDispDataPoint >= xLeftLimit)
            {
            // It is within the limits, plot the point
            pptDataPoints[j].x = xDispDataPoint ;
            pptDataPoints[j].y = ScaleAndInvertY (
               (i == pGraph->gMaxValues) ? pDataPoints[0] : pDataPoints[i],
               pLineStruct,
               pGraph) ;
            j++ ;
            }  // if

         // setup for the next point
         if (!numOfData)
            {
            // no more points to go
            break ;
            }

         xPos = DDA_DISTRIBUTE (rectWidth, numOfData) ;
         xDispDataPoint += xPos ;
         numOfData-- ;
         rectWidth -= xPos ;
         }  // for i

      // only need to draw the line if there is point to draw.
      if (j > 0)
         {
         // Set the pen color and draw the polyline.
         if (IsPrinterDC (hDC))
            {
            hPen = LineCreatePen (hDC, &(pLineStruct->Visual), TRUE) ;
            hOldPen = SelectObject (hDC, hPen) ;
            }
         else
            {
            if (pCurrentLine == pLineStruct)
               {
               // highlight this line by turning it into White color
               hOldPen = SelectObject (hDC, hWhitePen) ;
               }
            else
               {
               SelectObject (hDC, pLineStruct->hPen) ;
               }
            }

         Polyline(hDC, pptDataPoints, j) ;

         if (hPen)
            {
            SelectObject (hDC, hOldPen) ;

            if (hPen != hWhitePen)
               {
               DeletePen (hPen) ;
               }
            hPen = 0 ;
            }
         }

      if (pCurrentLine == pLineStruct)
         {
         // We are done...
         break ;
         }
      }  // for pLine

   if (IsTLineWindowUp())
      {
      // re-draw the timelines if need
      TLineRedraw (hDC, pGraph) ;
      }

   // reset the clipping region
   SelectClipRgn (hDC, pGraph->hGraphRgn) ;

   return (TRUE) ;
   }  // DrawTLGraphData



/***************************************************************************
 * bInitTimeLine - Initialize the fields of the time line structure.
 ***************************************************************************/
BOOL bInitTimeLine(PGRAPHSTRUCT pGraph)
{

        pGraph->gTimeLine.xLastTime      = 0 ;
        pGraph->gTimeLine.ppd            = 0 ;
        pGraph->gTimeLine.rppd           = 0 ;
        pGraph->gTimeLine.iValidValues   = 1 ;

        return (TRUE) ;

}


/***************************************************************************
 * Scale Time Line
 *
 *  This routine should be called from the WM_SIZE message.
 *  It does the scaling from the number of data points to the
 *  size of the window.
 ***************************************************************************/
void ScaleTimeLine (PGRAPHSTRUCT pGraph)
   {
   INT     nDataPoints,
           cxClient ;

   // Calculate the pels per data point.
   nDataPoints = pGraph->gMaxValues - 1 ;
   cxClient    = pGraph->rectData.right - pGraph->rectData.left ;

   // ppd  = Pixels per DataPoint.
   // rppd = Remaining Pixels per DataPoint.
   pGraph->gTimeLine.ppd  = cxClient / nDataPoints ;
   pGraph->gTimeLine.rppd = cxClient % nDataPoints ;
   }


void DisplayTimeLine(HDC hDC, PGRAPHSTRUCT pGraph)
/*
   Called By:     OnPaint only.

   Assert:        xDisplayPoint has been set by UpdateTimeLine on this
                  same timer tick.
*/
   {  // DisplayTimeLine
   INT     xDisplayPoint ;
   RECT    rect ;

   if (pGraph->gTimeLine.xLastTime == -1)
      {
      UpdateTimeLine (hGraphDisplayDC, pGraph, TRUE) ;
      }

   // xDisplayPoint is X coordinate to display the time line at.
   if ((xDisplayPoint = pGraph->gTimeLine.xLastTime) == 0)
      return ;

   SelectBrush (hDC, pGraph->hbRed) ;

   if (xDisplayPoint >= pGraph->rectData.right)
      {
      rect.left   = pGraph->rectData.left ;
      }
   else
      {
//      rect.left   = xDisplayPoint++ ;
      rect.left   = xDisplayPoint ;
      }
   rect.top    = pGraph->rectData.top ;
   rect.right  = rect.left + 2 ;
   rect.bottom = pGraph->rectData.bottom ;

//   IntersectRect (&rect, &rect, &pGraph->rectData) ;
   if (rect.right > pGraph->rectData.right)
      {
      rect.right = pGraph->rectData.right ;
      }
   PatBlt (hDC, 
           rect.left, rect.top,
           rect.right - rect.left,
           rect.bottom - rect.top + 1 ,
           PATCOPY) ;
         
   }  // DisplayTimeLine



int SuggestedNumTics (int iRange)
/*
   Effect:        Return an appropriate number of tic marks to display
                  within iRange pixels.

                  These numbers are empirically chosen for pleasing 
                  results.
*/
   {  // SuggestedNumTics
   if (iRange < 20)
      return (0) ;

   if (iRange < 50)
      return (2) ;

   if (iRange < 100)
      return (4) ;

   if (iRange < 150)
      return (5) ;

   if (iRange < 300)
      return (10) ;

   if (iRange < 500)
      return (20) ;

   return (25) ;
   }  // SuggestedNumTics



void SetGridPositions (PGRAPHSTRUCT pGraph)
   {  // SetGridPositions
   int            xDataWidth ;
   int            yDataHeight ;

   int            iCurrentTicPixels ;
   int            iNumTics ;

   int            i ;


   //=============================//
   // Set number of Tics          //
   //=============================//

   xDataWidth = pGraph->rectData.right - pGraph->rectData.left ;
   yDataHeight = pGraph->rectData.bottom - pGraph->rectData.top ;

   pGraph->xNumTics = PinInclusive (SuggestedNumTics (xDataWidth),
                                    0, iGraphMaxTics) ;
   pGraph->yNumTics = PinInclusive (SuggestedNumTics (yDataHeight),
                                    0, iGraphMaxTics) ;

   // if we have more tics than possible integral values, reduce the number
   // of tics.
   if (pGraph->gOptions.iVertMax < pGraph->yNumTics)
      pGraph->yNumTics = pGraph->gOptions.iVertMax ;


   //=============================//
   // Set X Tic Positions         //
   //=============================//

   if (pGraph->xNumTics)
      {
      iNumTics = pGraph->xNumTics ;

      pGraph->axTics[0] = 0 ;
      for (i = 1 ;
           i < pGraph->xNumTics ;
           i++)
         {  // for
         if (iNumTics == 0)
            {
            break ;
            }
         iCurrentTicPixels = DDA_DISTRIBUTE (xDataWidth, iNumTics) ;
         pGraph->axTics [i] = pGraph->axTics [i - 1] + iCurrentTicPixels ;

         xDataWidth -= iCurrentTicPixels ;
         iNumTics-- ;
         }  // for
      }  // if


   //=============================//
   // Set Y Tic Positions         //
   //=============================//

   if (pGraph->yNumTics)
      {
      iNumTics = pGraph->yNumTics ;

      pGraph->ayTics[0] = 0 ;
      for (i = 1 ;
           i < pGraph->yNumTics ;
           i++)
         {  // for
         if (iNumTics == 0)
            {
            break ;
            }
         iCurrentTicPixels = DDA_DISTRIBUTE (yDataHeight, iNumTics) ;
         pGraph->ayTics [i] = pGraph->ayTics [i- 1] + iCurrentTicPixels ;

         yDataHeight -= iCurrentTicPixels ;
         iNumTics-- ;
         }  // for
      }  // if
   }  // SetGridPositions



int GraphVerticalScaleWidth (HDC hDC,
                             PGRAPHSTRUCT pGraph)
   {
   TCHAR          szMaxValue [20] ;
   int            xWidth ;

   if (!pGraph->gOptions.bLabelsChecked)
      return (0) ;


//   SelectFont (hDC, IsPrinterDC (hDC) ? hFontPrinterScales : hFontScales) ;


   TSPRINTF (szMaxValue, TEXT(" %1d "),
            pGraph->gOptions.iVertMax * 10 ) ;

   xWidth = TextWidth (hDC, szMaxValue) ;

   return (xWidth) ;
   }



void DrawGraphScale (HDC hDC, 
                     PGRAPHSTRUCT pGraph)
   {
   TCHAR   szScale [20] ;

   INT     len,
           i,
           nLines,
           iUnitsPerLine ;
   FLOAT   ePercentOfTotal  ;
   FLOAT   eDiff ;
   BOOL    bUseFloatingPt = FALSE ;

   //=============================//
   // Draw Vertical Scale?        //
   //=============================//

   if (!pGraph->gOptions.bLabelsChecked)
       return ;

   // Get the number of lines.
   nLines = pGraph->yNumTics ;

   // Calculate what percentage of the total each line represents.
   ePercentOfTotal = ((FLOAT) 1.0) / ((FLOAT) nLines)  ;

   // Calculate the amount (number of units) of the Vertical max each
   // each line in the graph represents.
   iUnitsPerLine = (INT) ((FLOAT) pGraph->gOptions.iVertMax * ePercentOfTotal) ;
   ePercentOfTotal *= (FLOAT) pGraph->gOptions.iVertMax ;
   eDiff = (FLOAT)iUnitsPerLine - ePercentOfTotal ;
   if (eDiff < (FLOAT) 0.0)
      eDiff = -eDiff ;

   if (eDiff > (FLOAT) 0.1)
      bUseFloatingPt = TRUE ;

   //=============================//
   // Set Drawing Attributes      //
   //=============================//

//   SelectFont (hDC, IsPrinterDC (hDC) ? hFontPrinterScales : hFontScales) ;
   SetBkMode(hDC, TRANSPARENT) ;
   SetTextAlign (hDC, TA_TOP | TA_RIGHT) ;
   SelectObject(hDC, GetStockObject (BLACK_PEN)) ;

   // Set the background color to gray
   if (!IsPrinterDC (hDC))
      FillRect (hDC, &(pGraph->rectVertScale), hbLightGray) ;

   // Now Output each string.
   for (i = 0 ; 
        i < nLines ; 
        i++)
      {  // for
      if (bUseFloatingPt)
         {
         len = TSPRINTF (szScale, TEXT("%1.1f"),
            (FLOAT)pGraph->gOptions.iVertMax - ((FLOAT)i *
             ePercentOfTotal)) ;
         TextOut (hDC,
                  pGraph->rectVertScale.right,
                  pGraph->ayTics[i] +
                  pGraph->rectData.top - HalfTextHeight,
                  szScale,
                  lstrlen(szScale)) ;
         }
      else
         {
         len = TSPRINTF (szScale, TEXT("%d"),
                         pGraph->gOptions.iVertMax - (i * iUnitsPerLine)) ;
         TextOut (hDC,
                  pGraph->rectVertScale.right,
                  pGraph->ayTics[i] +
                  pGraph->rectData.top - HalfTextHeight,
                  szScale,
                  lstrlen(szScale)) ;
         }
      }  // for

   // Output the "min value" separately.
   TextOut (hDC, 
            pGraph->rectVertScale.right,
            pGraph->rectData.bottom - HalfTextHeight,
            TEXT("0"),
            1) ;
   }  // DrawGraphScale



void SizeGraphDisplayComponentsRect (HDC hDC,
                                     PGRAPHSTRUCT pGraph,
                                     RECT rectDisplay)
   {  // SizeGraphDisplayComponentsRect
   int            xScaleWidth ;

   if (!rectDisplay.right || !rectDisplay.bottom)
      return ;

   //=============================//
   // Size the Vertical Scale     //
   //=============================//

   xScaleWidth = GraphVerticalScaleWidth (hDC, pGraph) ;
   pGraph->rectVertScale.left = rectDisplay.left ;
   pGraph->rectVertScale.top = rectDisplay.top ;
   pGraph->rectVertScale.right = rectDisplay.left + xScaleWidth ;
   pGraph->rectVertScale.bottom = rectDisplay.bottom ;

   //=============================//
   // Size the Horizontal Scale   //
   //=============================//

   pGraph->rectHorzScale.left = 0 ;
   pGraph->rectHorzScale.top = 0 ;
   pGraph->rectHorzScale.right = 0 ;
   pGraph->rectHorzScale.bottom = 0 ;

   //=============================//
   // Size the Data Area          //
   //=============================//

   pGraph->rectData.left = pGraph->rectVertScale.right + 3 + ThreeDPad ;
   pGraph->rectData.right = rectDisplay.right - 5 - ThreeDPad ;
   pGraph->rectData.top = rectDisplay.top + 5 + ThreeDPad ;
   pGraph->rectData.bottom = rectDisplay.bottom - 5 - ThreeDPad ;

   SetGridPositions (pGraph) ;
   ScaleTimeLine (pGraph) ;

   //==========================================//
   // Invalidate the last time line poisition  //
   //==========================================//
   pGraph->gTimeLine.xLastTime = -1 ;

   if (pGraph->hGraphRgn)
      {
      DeleteObject (pGraph->hGraphRgn) ;
      }

   pGraph->hGraphRgn = CreateRectRgn (rectDisplay.left,
      rectDisplay.top,
      rectDisplay.right,
      rectDisplay.bottom) ;

   SelectClipRgn (hDC, pGraph->hGraphRgn) ;

   }  // SizeGraphDisplayComponentsRect


void SizeGraphDisplayComponents (HWND hWnd)
/*
   Effect:        Given the graph display window hWnd, of size 
                  (xWidth x yHeight), determine the size and position
                  of the various graph display components: the vertical
                  scale, the horizontal scale, and the data area.

   Called By:     OnSize, any other routine that changes the visibility
                  of a vertical or horizontal scale.

   Note:          This function has multiple return points.
*/
   {  // SizeGraphDisplayComponents
   PGRAPHSTRUCT   pGraph ;
   RECT           rectClient ;

   pGraph = GraphData (hWnd) ;
   GetClientRect (hWnd, &rectClient) ;

   SizeGraphDisplayComponentsRect (hGraphDisplayDC, pGraph, rectClient) ;
   }


void UpdateGraphDisplay (HDC hDC,
                         BOOL bForPaint,
                         LPRECT lpRect,
                         PGRAPHSTRUCT pGraph)
/*
   Effect:        Draw the portions of the graph that change as the
                  graph's values change. This includes the background,
                  the grid, the lines, and the timeline.
*/
   {  // UpdateGraphDisplay
   RECT           rectUpdate ;

   if (!bForPaint && !IsPrinterDC (hDC) && 
       pGraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
      {
      HBRUSH         hOldBrush ;

      rectUpdate = pGraph->rectData ;
      rectUpdate.bottom += 1 ;

      IntersectRect (&rectUpdate, lpRect, &rectUpdate) ;
      hOldBrush = SelectBrush (hDC, hBrushFace) ;

      PatBlt (hDC, 
              rectUpdate.left, rectUpdate.top,
              rectUpdate.right - rectUpdate.left,
              rectUpdate.bottom - rectUpdate.top,
              PATCOPY) ;
      }
   else
      {
      IntersectRect (&rectUpdate, lpRect, &pGraph->rectData) ;
      }
   
   if (pGraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
      {
      DrawGrid(hDC, pGraph, &rectUpdate, bForPaint) ;
      if (pGraph->pLineFirst != NULL)
          {
          DrawTLGraphData(hDC, bForPaint, &rectUpdate, pGraph) ;
          if (!PlayingBackLog ())
             DisplayTimeLine(hDC, pGraph) ;
          }
      }
   else
      {
      DrawBarChartData (hDC, pGraph) ;
      }
   }  // UpdateGraphDisplay

                    
BOOL UpdateTimeLine (HDC hDC, 
                     PGRAPHSTRUCT pGraph,
                     BOOL getLastTimeLocation)
/*
   Called By:     GraphTimer only.

   See Also:      UpdateGraphDisplay.
*/
   {
   INT     i,
           xDisplayPoint,
           xDataPoint ;
   RECT    rctUpdate ;
   INT     xLastTime ;
   INT     rectWidth,
           xPos,
           numOfPoints ;


   if ((xLastTime = pGraph->gTimeLine.xLastTime) != 0)
      {
      if ((pGraph->gKnownValue % pGraph->gMaxValues) == 1)
         {
         // Data wrap around case
         rctUpdate.left   = pGraph->rectData.left ;
         rctUpdate.right  = pGraph->rectData.left +
             pGraph->gTimeLine.ppd + 1 ;
         }
      else
         {
         rctUpdate.left   = xLastTime - pGraph->gTimeLine.ppd ;
         rctUpdate.right  = xLastTime +
             pGraph->gTimeLine.ppd + 1 ;
         }
      rctUpdate.top    = pGraph->rectData.top ;
      rctUpdate.bottom = pGraph->rectData.bottom + 1 ;
      }

   // Calculate where to draw the time line.
   // This is done by running a simple DDA (Digital Differential Analyzer)
   // We have to position the time depending upon the size of the
   // graph window.  In essence we need to calculate the x display
   // coordinate.

   // Note we should wrap Known Value in UpdateGLData.
   // We should also use a data buffer of 256 bytes so we can
   // wrap with and AND.

   // xDataPoint =  pGraph->gKnownValue ;
   xDataPoint =  pGraph->gKnownValue % pGraph->gMaxValues ;

   xDisplayPoint = pGraph->rectData.left ;

   numOfPoints = pGraph->gMaxValues - 1 ;

   if (!PlayingBackLog() &&
      pGraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
      {
      // drawing the 0th at the end of the chart.
      // So, we do have gMaxValues points
      numOfPoints++ ;
      if ((pGraph->gKnownValue % pGraph->gMaxValues) == 0)
         {
         xDataPoint = pGraph->gMaxValues ;
         }
      }

   rectWidth = pGraph->rectData.right - pGraph->rectData.left ;

   for (i = 0 ; i < xDataPoint ; i++)
      {
      if (numOfPoints == 0)
         {
         break ;
         }
      xPos = DDA_DISTRIBUTE (rectWidth, numOfPoints) ;
      xDisplayPoint += xPos ;
      rectWidth -= xPos ;
      numOfPoints-- ;
      }  // for

   pGraph->gTimeLine.xLastTime = xDisplayPoint ;

   if (!getLastTimeLocation && iPerfmonView == IDM_VIEWCHART && !bPerfmonIconic)
      {
      UpdateGraphDisplay (hDC, FALSE, &rctUpdate, pGraph) ;
      }

   return(TRUE) ;
   }


//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//


void /*static*/ OnCreate (HWND hWnd)
/*
   Effect:        Perform all actions needed when a GraphDisplay window is 
                  created.
                  In particular, initialize the graph instance data and
                  create the child windows.

   Called By:     GraphDisplayWndProc, in response to a WM_CREATE message.
*/
   {  // OnCreate
   LOGBRUSH       LogBrush ;
   TEXTMETRIC     tmScales ;

   hGraphDisplayDC = GetDC(hWnd) ;

   SelectFont(hGraphDisplayDC, hFontScales) ;
   GetTextMetrics(hGraphDisplayDC, &tmScales) ;
   HalfTextHeight = tmScales.tmHeight / 2 ;

   SetBkColor (hGraphDisplayDC, crLightGray) ;


   InsertGraph(hWnd) ;
   bInitTimeLine(pGraphs) ;

   pGraphs->hWnd = hWnd ;

   // Create the brush and pen used by the time line.
   // We don't want to create these on every timer tick.

   LogBrush.lbStyle = BS_SOLID ;
   LogBrush.lbColor = RGB(0xff, 0, 0) ;
   LogBrush.lbHatch = 0 ;

   // Now get the system resources we use "all the time"
   pGraphs->hbRed = CreateBrushIndirect(&LogBrush) ;
   pGraphs->hGridPen = CreatePen (PS_SOLID, 1, crGray) ;

   pGraphs->xNumTics = 0 ;
   pGraphs->yNumTics = 0 ;
   }  // OnCreate


void /*static*/ OnSize (HWND hWnd,
                    WORD xWidth,
                    WORD yHeight)
   {  // OnSize
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWnd) ;

   SizeGraphDisplayComponents (hWnd) ;
   }  // OnSize


void /*static*/ OnPaint (HWND hWnd)
   {
   HDC            hDC ;
   PAINTSTRUCT    ps ;
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWnd) ;
   hDC = BeginPaint(hWnd, &ps) ;

   DrawGraphDisplay (hDC, ps.rcPaint, pGraph) ;

   EndPaint(hWnd, &ps) ;
   }  // OnPaint


//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

#ifdef KEEP_PRINT
void PrintGraphDisplay (HDC hDC,
                        PGRAPHSTRUCT pGraph)
   {
   DrawGraphScale (hDC, pGraph) ;


//!!   UpdateGraphDisplay (hDC, TRUE, &(pGraph->rectData), pGraph) ;

   IntersectClipRect (hDC, 0, 0, 10000, 10000) ;
   SelectBrush (hDC, GetStockObject (HOLLOW_BRUSH)) ;
   SelectPen (hDC, GetStockObject (BLACK_PEN)) ;
   Rectangle (hDC, 
              pGraph->rectData.left,
              pGraph->rectData.top,
              pGraph->rectData.right,
              pGraph->rectData.bottom) ;
   }  // PrintGraphDisplay
#endif



void DrawGraphDisplay (HDC hDC,
                       RECT rectDraw,
                       PGRAPHSTRUCT pGraph)
   {
   BOOL        bPaintScale ;
   INT         LocalThreeDPad = ThreeDPad - 1 ;

   // Only draw the vertical labels if the paint rectangle
   // any portion of the window to the left of the graph area.

   bPaintScale = (rectDraw.left <= pGraph->rectVertScale.right) ;
   if (bPaintScale)
      DrawGraphScale (hDC, pGraph) ;
   if (IsPrinterDC (hDC))
      Rectangle (hDC, 
                 pGraph->rectData.left,
                 pGraph->rectData.top,
                 pGraph->rectData.right,
                 pGraph->rectData.bottom) ;
   else
      ThreeDConcave1 (hDC, 
                     pGraph->rectData.left - LocalThreeDPad,
                     pGraph->rectData.top - LocalThreeDPad,
                     pGraph->rectData.right + LocalThreeDPad,
                     pGraph->rectData.bottom + LocalThreeDPad + 1) ;

   UpdateGraphDisplay (hDC, TRUE, &(rectDraw), pGraph) ;
   }  // DrawGraphDisplay


LRESULT APIENTRY GraphDisplayWndProc (HWND hWnd, 
                                      UINT uMsg, 
                                      WPARAM wParam,
                                      LPARAM lParam)
   {  // GraphDisplayWndProc
   LONG        lret = 0L ;
   BOOL        bCallDefProc = FALSE ;

   switch (LOWORD (uMsg))
      {
      case WM_LBUTTONDOWN:
         DoWindowDrag (lParam) ;
         break ;

      case WM_LBUTTONDBLCLK:
         SendMessage (hWndMain, uMsg, wParam, lParam) ;
         break ;

      case WM_CREATE:
         OnCreate (hWnd) ;
         break ;

      case WM_SIZE:
         OnSize (hWnd, LOWORD (lParam), HIWORD (lParam)) ;
         break ;

      case WM_DESTROY:
         KillTimer(hWndMain, GRAPH_TIMER_ID) ;
         break ;

      case WM_PAINT:
         OnPaint (hWnd) ;
         break ;

      case WM_TIMER:
         GraphTimer (hWnd, FALSE) ;
         break ;

      default:
         bCallDefProc = TRUE ;
         break ;
      }  // switch


   if (bCallDefProc)
      {
      lret = DefWindowProc(hWnd, uMsg, wParam, lParam) ;
      }
   return (lret) ;
   }


BOOL GraphDisplayInitializeApplication (void)
   {  // GraphDisplayInitializeApplication
   WNDCLASS       wc ;

   wc.style         = dwGraphDisplayClassStyle ;
   wc.lpfnWndProc   = (WNDPROC) GraphDisplayWndProc ;
   wc.hInstance     = hInstance ;
   wc.cbClsExtra    = iGraphDisplayWindowExtra ;
   wc.cbWndExtra    = iGraphDisplayClassExtra ;
   wc.hIcon         = NULL ;
   wc.hCursor       = LoadCursor(NULL, IDC_ARROW) ;
   wc.hbrBackground = hbLightGray ;
   wc.lpszMenuName  = NULL ;
   wc.lpszClassName = (LPTSTR) szGraphDisplayWindowClass ;

   return (RegisterClass (&wc)) ;
   }  // GraphDisplayInitializeApplication



HWND CreateGraphDisplayWindow (HWND hWndGraph)
   {
   return (CreateWindow (szGraphDisplayWindowClass,   // class
                         NULL,                        // caption
                         dwGraphDisplayWindowStyle,   // window style
                         0, 0,                        // position
                         0, 0,                        // size
                         hWndGraph,                   // parent window
                         NULL,                        // menu
                         hInstance,                  // program instance
                         NULL)) ;                     // user-supplied data
   }



BOOL ToggleGraphRefresh (HWND hWnd)
   {  // ToggleGraphRefresh
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWnd) ;

   if (pGraph->bManualRefresh)
      SetGraphTimer (pGraph) ;
   else
      ClearGraphTimer (pGraph) ;

   pGraph->bManualRefresh = !pGraph->bManualRefresh ;
   return (pGraph->bManualRefresh) ;
   }  // ToggleGraphRefresh

BOOL GraphRefresh (HWND hWnd)
   {  // GraphRefresh
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWnd) ;

   return (pGraph->bManualRefresh) ;
   }  // GraphRefresh


void GraphTimer (HWND hWnd, 
                 BOOL bForce)
/*
   Effect:        Handle any actions necessary when the graph display
                  window hWnd receives a timer tick. In particular,
                  update the graph display and update the status bar
                  if it is showing.

   Called By:     GraphDisplayWndProc, in response to a WM_TIMER message.
*/
   {  // GraphTimer
   PGRAPHSTRUCT         pGraph ;

   pGraph = GraphData (hWnd) ;

   if (!pGraph->pLineFirst)
      return ;


   if (bForce || !pGraph->bManualRefresh)
      {

   
      HandleGraphTimer () ;

      // If we are displaying a time-line graph then do the
      // calculations for the minimal update region.
      // If were doing a bar graph, then draw the
      // whole graph area.

      if (pGraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
         UpdateTimeLine (hGraphDisplayDC, pGraph, FALSE) ;
      else
         DrawBarChartData (hGraphDisplayDC, pGraph) ;

      // Take care of the status bar window
      StatusTimer (hWndGraphStatus, FALSE) ;

      }  // if
   }  // GraphTimer

// this routine set/reset the line highlight mode
void ChartHighlight (void)
   {
   PGRAPHSTRUCT         pGraph ;


   if (pGraph = GraphData (hWndGraph))
      {
      if (pGraph->pLineFirst)
         {
         // toggle the HightlightOnOff mode
         pGraph->HighLightOnOff = !pGraph->HighLightOnOff ;
         WindowInvalidate (hWndGraphDisplay) ;
         }
      else
         {
         // no line availble, just turn it off
         pGraph->HighLightOnOff = FALSE ;
         }
      }
   }  // ChartHighlight


