// 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");
}
*/