Return to Appendix B.

GRAPH.CPP
// Matt Streeter
// 2/27/00
// GRAPH.CPP
// Implementation of class TESGraphWindow; window class used to graph fitness
// scores.
// Copyright Matt Streeter, 2000.  All rights reserved

#include "graph.h"

#include <math.h>
#include <stdio.h>
#include <string.h>

#define GRAPH_DIVERSITY 0

#define ES_GRAPH_VAR_MIN 1
#define ES_GRAPH_VAR_MAX 2
#define ES_GRAPH_VAR_AVG 3
#define ES_GRAPH_VAR_DIVERSITY 4

DEFINE_RESPONSE_TABLE1(TESGraphWindow, TWindow)
/*
  EV_COMMAND(CM_SCALELINEAR, CmScaleLinear),
  EV_COMMAND(CM_SCALELOGARITHMIC, CmScaleLogarithmic),
  EV_COMMAND(CM_SCALESIGMOID, CmScaleSigmoid),
  EV_COMMAND_ENABLE(CM_SCALELINEAR,CeScaleLinear),
  EV_COMMAND_ENABLE(CM_SCALELOGARITHMIC,CeScaleLogarithmic),
  EV_COMMAND_ENABLE(CM_SCALESIGMOID,CeScaleSigmoid),*/
  EV_WM_SIZE,
END_RESPONSE_TABLE;

#define MAX_LINE_LEN 512

#define WINDOW_LBORDER 40
#define WINDOW_RBORDER 10
#define WINDOW_TBORDER 40
#define WINDOW_BBORDER 60

#define SCALE_MARK_LEN 5
#define MIN_VSCALE_MARK_SPACING 20.0
#define MIN_HSCALE_MARK_SPACING 40.0
// space between vertical scale label and scale mark
#define VSCALE_TEXT_SPACING 2

#define FITNESS_VOFFSET 10
#define FITNESS_FONT_FACE "Times New Roman"
#define FITNESS_FONT_HEIGHT 20

#define GEN_VOFFSET 25
#define GEN_FONT_FACE "Times New Roman"
#define GEN_FONT_HEIGHT 25

#define SCALE_LINEAR			1
#define SCALE_LOGARITHMIC	2
#define SCALE_SIGMOID		3

#define DEFAULT_SCALE SCALE_LINEAR

#define LOG_MIN -4
#define LOG_MAX 24

#define BACKGROUND_COLOR 0,0,96

#define LABEL_FONT_FACE "Times New Roman"
#define LABEL_FONT_HEIGHT 15

TColor TESGraphWindow::mMaxColor(255,0,0);
TColor TESGraphWindow::mAvgColor(0,0,255);
TColor TESGraphWindow::mDiversityColor(128,128,128);
TColor TESGraphWindow::mMinColor(0,255,0);
TColor TESGraphWindow::mBorderColor(255,255,255);

TESGraphWindow::TESGraphWindow(TWindow *pParent,char *pTitle,
	GenerationScore **ppScoreList,TESGraphWindow **ppThis):
	TESWindow(pParent,pTitle)
{
	Attr.Style=WS_VISIBLE|WS_OVERLAPPEDWINDOW;
	Attr.X=50+(pParent?pParent->Attr.X+3:0);
	Attr.Y=200+(pParent?pParent->Attr.Y+25:0);
	Attr.W=400;
	Attr.H=275;
	mbScale=DEFAULT_SCALE;
	mppScoreList=ppScoreList;
	mppThis=ppThis;
	(*mppThis)=this;
}

TESGraphWindow::~TESGraphWindow()
{
	if(mppThis)
		(*mppThis)=0;
}

void TESGraphWindow::Paint(TDC& dc, bool, TRect&)
{
	TRect GraphRect;
	GetClientRect(GraphRect);

   dc.FillRect(GraphRect,mBackgroundFillBrush);

	GraphRect.left+=WINDOW_LBORDER;
	GraphRect.top+=WINDOW_TBORDER;
	GraphRect.right-=WINDOW_RBORDER;
	GraphRect.bottom-=WINDOW_BBORDER;

	TPen LinePen(mBorderColor,1);
	dc.SelectObject(LinePen);
	dc.MoveTo(TPoint(GraphRect.left-1,GraphRect.top));
	dc.LineTo(TPoint(GraphRect.left-1,GraphRect.bottom+1));
	dc.LineTo(TPoint(GraphRect.right,GraphRect.bottom+1));

	dc.SetBkColor(mBackgroundColor);
	dc.SetTextColor(mTextColor);

	// Print "Generation" under horizontal axis
	TFont GenFont(GEN_FONT_FACE,GEN_FONT_HEIGHT);
	dc.SelectObject(GenFont);
	TSize Size=dc.GetTextExtent("Generation",10);
	TPoint TextPos(GraphRect.left+(GraphRect.Width()+1-Size.cx)/2,
		GraphRect.bottom+GEN_VOFFSET);
	dc.TextOut(TextPos,"Generation",10);

	// find horizontal scale
	// loop through possible scales: 1,5,10,50,100,500, . . .
	int iScale=1;
	float fHSpacing=0;
	int iNumGenerations;
	byte bToggle=0;

	if(*mppScoreList)
		iNumGenerations=LastScore()->iGeneration;
	else
		iNumGenerations=0;

	if(iNumGenerations)
	{
		while(1)
		{
			fHSpacing=(float)(GraphRect.Width()+1)
				/((float)iNumGenerations/iScale);
			if(fHSpacing>MIN_HSCALE_MARK_SPACING)
			break;

			if(!bToggle)
				iScale*=5;
			else
				iScale*=2;
			bToggle=!bToggle;
		}
	}
	else
	{
		fHSpacing=GraphRect.Width()+2;
	}

	// draw horizontal scale marks
	TPoint Top(GraphRect.left-1,GraphRect.bottom+2);
	TPoint Bottom(GraphRect.left-1,GraphRect.bottom+2+SCALE_MARK_LEN-1);
	float fXCursor=GraphRect.left;
	int iGeneration=0;

	TFont LabelFont(LABEL_FONT_FACE,LABEL_FONT_HEIGHT);
	dc.SelectObject(LabelFont);

	while(fXCursor<=GraphRect.right)
	{
	char pGenStr[200];

		dc.MoveTo(Top);
		dc.LineTo(Bottom);

		itoa(iGeneration,pGenStr,10);
		dc.TextOut(Bottom,pGenStr,strlen(pGenStr));

		fXCursor+=fHSpacing;
		Top.x=fXCursor;
		Bottom.x=Top.x;
		iGeneration+=iScale;
	}

	// find minimum & maximum sample values
	double dMaxDiversity=0.0;

	// get rid of 0 diversity on first sample
	if((*mppScoreList) && (*mppScoreList)->pNext)
		(*mppScoreList)->dDiversity=(*mppScoreList)->pNext->dDiversity;

	GenerationScore *pCurScore=(*mppScoreList);

	while(pCurScore)
	{
		if(pCurScore->dDiversity>dMaxDiversity)
			dMaxDiversity=pCurScore->dDiversity;
		pCurScore=pCurScore->pNext;
	}

	// find vertical scale
	// loop through possible scales: 1.0,.5,.2,.1,.05,.02,.01,. . . .
	float fScale=1.0,fLastScale=1.0;
	float fVSpacing;
	bToggle=0;
	int iCount=0;
	float fScaleDivisors[3]={2.0,2.5,2.0};

	while(1)
	{
		fVSpacing=(float)(GraphRect.Height()+1)/(1.0/fScale);
		if(fVSpacing<MIN_VSCALE_MARK_SPACING)
		{
			fScale=fLastScale;
			break;
		}
		fLastScale=fScale;
		fScale/=fScaleDivisors[(iCount++)%3];
	}

	// draw vertical scale marks
	TPoint Right(GraphRect.left-1,0);
	TPoint Left(GraphRect.left-1-SCALE_MARK_LEN+1,0);
	TPoint TextCursor(0,0);

	// have to write loop conditional as fScaleMark<1.0+fScale rather than
	// fScaleMark<=1.0 due to *wierd* floating point error.
	for(float fScaleMark=0.0;fScaleMark<1.0+fScale;fScaleMark+=fScale)
	{
	char pStr[200];

		float fY=GraphRect.bottom+1-float(GraphRect.Height()+1)*fScaleMark;

		Left.y=fY;
		Right.y=fY;
		dc.MoveTo(Left);
		dc.LineTo(Right);

		char *pFitnessStr=pStr;

		if(!fScaleMark)
		{
			strcpy(pStr,"0");
		}
		else
		{
			sprintf(pStr,"%.1f",fScaleMark);
			// get rid of leading '0' before decimal point
			if(pStr[0]=='0')
				pFitnessStr++;
		}
		TSize TextSize;
		dc.GetTextExtent(pFitnessStr,strlen(pFitnessStr),TextSize);
		TextCursor.y=fY-float(TextSize.cy)/2.0;
		// right-justify text
		TextCursor.x=GraphRect.left-1-SCALE_MARK_LEN
			-VSCALE_TEXT_SPACING-TextSize.cx;
		// display text
		dc.TextOut(TextCursor,pFitnessStr,strlen(pFitnessStr));
	}

	// Print "fitness" in italic above vertical axis
	TFont FitnessFont(FITNESS_FONT_FACE,FITNESS_FONT_HEIGHT,0,0,0,FW_NORMAL,
		DEFAULT_PITCH|FF_DONTCARE,TRUE);
	dc.SelectObject(FitnessFont);
	TextCursor.y=GraphRect.top-FITNESS_VOFFSET-FITNESS_FONT_HEIGHT;
	dc.TextOut(TextCursor,"fitness",7);

	// graph maximum fitness score
	GraphVariable(ES_GRAPH_VAR_MAX,1.0,0.0,mMaxColor,dc,GraphRect);

	// graph average fitness score
	GraphVariable(ES_GRAPH_VAR_AVG,1.0,0.0,mAvgColor,dc,GraphRect);

#if(GRAPH_DIVERSITY)
	// graph diversity
	GraphVariable(ES_GRAPH_VAR_DIVERSITY,dMaxDiversity,0.0,mDiversityColor,
		dc,GraphRect);
#endif

	// graph minimun fitness score in green
	GraphVariable(ES_GRAPH_VAR_MIN,1.0,0.0,mMinColor,dc,GraphRect);
}

void TESGraphWindow::GraphVariable(byte bVar,double dMaxVal,double dMinVal,
	TColor& Color,TDC& dc, TRect& rect)
{
double dNormValue;
double dMaxLog,dMinLog;
GenerationScore *pCurScore;
int iScores=GetNumScores();

	if(!iScores)
		return;

	TPen LinePen(Color,1);
	dc.SelectObject(LinePen);

	TPoint CurPoint(rect.left,rect.top), LastPoint(-1,-1);

	if(mbScale==SCALE_LOGARITHMIC)
	{
		dMaxLog=log(dMaxVal);
		dMinLog=log(dMinVal);
	}

	pCurScore=(*mppScoreList);
	int iScoreIndex=0;
	while(pCurScore)
	{
		double dVar=GetVar(bVar,pCurScore);
		CurPoint.x=(int)(rect.left+(iScoreIndex*(long)rect.Width()+iScores-1)
			/iScores);
		switch(mbScale)
		{
		case SCALE_LINEAR:
			if(dMaxVal!=dMinVal)
				dNormValue=(dVar-dMinVal)/(dMaxVal-dMinVal);
			else
				dNormValue=0.0;
			break;

		case SCALE_LOGARITHMIC:
			if(dVar>0 && dMaxLog!=dMinLog)
				dNormValue=(log(dVar)-dMinLog)/(dMaxLog-dMinLog);
			else
				dNormValue=0;
			break;

		case SCALE_SIGMOID:
//			dNormValue=(Sigmoid(dVar)*2.0-1.0);
			dNormValue=0;
			break;
		};

		if(dNormValue<0.0)
			dNormValue=0.0;

		if(dNormValue>1.0)
			dNormValue=1.0;

		CurPoint.y=rect.bottom-dNormValue*rect.Height();

		if(LastPoint.x!=-1)
		{
			dc.MoveTo(LastPoint);
			dc.LineTo(CurPoint);
		}

		LastPoint=CurPoint;

		pCurScore=pCurScore->pNext;
		iScoreIndex++;
	}
}

GenerationScore *TESGraphWindow::LastScore()
{
GenerationScore *pCurScore=(*mppScoreList);

	if(!pCurScore)
		return(0);

	while(pCurScore->pNext)
	{
		pCurScore=pCurScore->pNext;
	}
   return(pCurScore);
}

int TESGraphWindow::GetNumScores()
{
int iScores=0;
GenerationScore *pCurScore=(*mppScoreList);

	while(pCurScore)
	{
		iScores++;
		pCurScore=pCurScore->pNext;
	}
	return(iScores);
}

double TESGraphWindow::GetVar(byte bVar,GenerationScore *pScore)
{
	switch(bVar)
	{
	case ES_GRAPH_VAR_MIN:
		return(pScore->dMin);
	case ES_GRAPH_VAR_MAX:
		return(pScore->dMax);
	case ES_GRAPH_VAR_AVG:
		return(pScore->dAverage);
	case ES_GRAPH_VAR_DIVERSITY:
		return(pScore->dDiversity);
	default:
   	return(0);
	}
}

/*
void TESGraphWindow::CmScaleLinear()
{
	if(mbScale!=SCALE_LINEAR)
	{
		mbScale=SCALE_LINEAR;
		RedrawWindow(0,0,RDW_INVALIDATE);
	}
}

void TESGraphWindow::CmScaleLogarithmic()
{
	if(mbScale!=SCALE_LOGARITHMIC)
	{
		mbScale=SCALE_LOGARITHMIC;
		RedrawWindow(0,0,RDW_INVALIDATE);
	}
}

void TESGraphWindow::CmScaleSigmoid()
{
	if(mbScale!=SCALE_SIGMOID)
	{
		mbScale=SCALE_SIGMOID;
		RedrawWindow(0,0,RDW_INVALIDATE);
	}
}

void TESGraphWindow::CeScaleLinear(TCommandEnabler& ce)
{
	ce.SetCheck(mbScale==SCALE_LINEAR);
}

void TESGraphWindow::CeScaleLogarithmic(TCommandEnabler& ce)
{
	ce.SetCheck(mbScale==SCALE_LOGARITHMIC);
}

void TESGraphWindow::CeScaleSigmoid(TCommandEnabler& ce)
{
	ce.SetCheck(mbScale==SCALE_SIGMOID);
}
*/

void TESGraphWindow::EvSize(UINT SizeType, TSize& Size)
{
  TWindow::EvSize(SizeType, Size);
  if (SizeType != SIZEICONIC)
  {
	 Invalidate(FALSE);
  }
}

/*
void TESGraphApp::InitMainWindow()
{
	SetMainWindow(new TFrameWindow(0,
		"ES Graph",new TESGraphWindow("log.es")));
	GetMainWindow()->AssignMenu("COMMANDS");
}
*/

Return to Appendix B.