www.pudn.com > RMS2000_C.rar > Chart.cpp
// Chart.cpp : implementation file
// Originator : Kris Jearakul tuktun@hotmail.com
// http://krisj.iwarp.com
// Comment : Use with your own risk !!
#include "stdafx.h"
#include "Chart.h"
#include "math.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CChart
CChart::CChart()
{
SetRange(-10,10,-10,10);
strChartTitle = "Untitled Chart" ;
strLabelX = "x" ;
strLabelY = "y" ;
iChartType = 2 ;
nGridX = 10 ;
nGridY = 10 ;
bIsSerieAllocated = FALSE ;
bLogScale = FALSE ;
bFontIsCreate = FALSE ;
bBkNeedToUpdate = TRUE ;
nPlotIndex = 0 ;
nPointCount = 0 ;
nSerieCount = 1 ;
m_BGColor = RGB(0,255,255);
m_AxisColor = RGB(0,0,0);
m_GridColor = RGB(120,120,120);
}
CChart::~CChart()
{
for( int i = 0 ; i < nSerieCount ; i++){
mpSerie[i].FreeSerie() ;
}
if(memBkDC.GetSafeHdc() != NULL)
memBkDC.SelectObject(m_pOldBkBitmap);
if( bFontIsCreate ) {
delete pLegendFontY ;
delete pLegendFontX ;
delete pTitleFont ;
}
}
BEGIN_MESSAGE_MAP(CChart, CWnd)
//{{AFX_MSG_MAP(CChart)
ON_WM_PAINT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CChart message handlers
BOOL CChart::Create(DWORD dwStyle, CRect &rect, CWnd *pParent, UINT id)
{
//DWORD style = dwStyle & ( ~WS_BORDER ) ;
//static CString className = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW);
BOOL result ;
result = CWnd::CreateEx(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE,
NULL, NULL, dwStyle,
rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top,
pParent->GetSafeHwnd(), (HMENU)id) ;
if( !result )
AfxMessageBox("Error creating window");
m_ctlRect = rect ;
pParent->ClientToScreen(m_ctlRect) ;
ScreenToClient(m_ctlRect) ;
CreateFont(); // Get system font for label draw
bFontIsCreate = TRUE ;
CalcRect() ; // Compute rectangle
ResCalc(); // Compute resolution per dot .
return result ;
}
void CChart::CalcRect()
{
int offsetX , offsetY ;
if(m_ctlRect)
{
m_clientRect.left = m_ctlRect.left+2 ;
m_clientRect.right = m_ctlRect.right-2 ;
m_clientRect.top = m_ctlRect.top+2 ;
m_clientRect.bottom = m_ctlRect.bottom-2;
} else {
m_clientRect = m_ctlRect ;
}
//Calculate offset of window to be 4/5 of its window .
offsetY = m_clientRect.Height()/5 ;
offsetX = m_clientRect.Width()/5 ;
m_axisRect.left = m_clientRect.left + (offsetX) ;
m_axisRect.right = m_clientRect.right - (offsetX/2) ;
m_axisRect.top = m_clientRect.top + (offsetY) ;
m_axisRect.bottom = m_clientRect.bottom - (offsetY) ;
}
//////////////////////////////////////////////////////////
// Draw box and fill color inside it .
void CChart::DrawBorder(CDC *pDC)
{
CBrush brushCtl;//(m_BGColor);
brushCtl.CreateSolidBrush(m_BGColor);
pDC->Rectangle(m_clientRect);
pDC->FillRect(m_clientRect,&brushCtl) ;
}
void CChart::DrawAxis(CDC *pDC)
{
CPen *old , pen(PS_SOLID, 2, m_AxisColor) ;
old = pDC->SelectObject(&pen) ;
double dcenter ;
switch( iChartType ) {
case 0:
pDC->MoveTo(Corrdinate(dRangeX[MIN],dRangeY[MIN]));
pDC->LineTo(Corrdinate(dRangeX[MAX],dRangeY[MIN]));
pDC->MoveTo(Corrdinate(dRangeX[MIN],dRangeY[MIN]));
pDC->LineTo(Corrdinate(dRangeX[MIN],dRangeY[MAX]));
break;
case 1:
dcenter = (dRangeY[MAX] + dRangeY[MIN]) / 2 ;
pDC->MoveTo(Corrdinate(dRangeX[MIN],dcenter));
pDC->LineTo(Corrdinate(dRangeX[MAX],dcenter));
pDC->MoveTo(Corrdinate(dRangeX[MIN],dRangeY[MIN]));
pDC->LineTo(Corrdinate(dRangeX[MIN],dRangeY[MAX]));
break;
case 2:
//draw vertical axis
dcenter = (dRangeX[MAX] + dRangeX[MIN]) /2 ;
pDC->MoveTo(Corrdinate(dcenter,dRangeY[MIN]));
pDC->LineTo(Corrdinate(dcenter,dRangeY[MAX]));
//draw horizontal axis
dcenter = (dRangeY[MAX] + dRangeY[MIN]) /2 ;
pDC->MoveTo(Corrdinate(dRangeX[MIN],dcenter));
pDC->LineTo(Corrdinate(dRangeX[MAX],dcenter));
break;
}
pDC->SelectObject(old) ;
}
CPoint CChart::Corrdinate(double x, double y)
{
double rx , ry ;
int xPixel , yPixel ;
CPoint retPt ;
rx = x - dRangeX[MIN] ; // Calculate horizontal offset from origin
ry = y - dRangeY[MIN] ; // Calculate vertical offset from origin .
// Convert offset to be number of pixel on screen .
xPixel = (int)(rx / dResX) ;
yPixel = (int)(ry / dResY) ;
//Calulate point to be drawn .
retPt.x= xPixel + m_axisRect.left ;
retPt.y= m_axisRect.bottom - yPixel;
return retPt ;
}
void CChart::Plot(CDC *pDC)
{
// If there is no data available for plot then return .
if ( !nPlotIndex ) return ;
CPen *old , *pen ;
CPoint pt ;
int i;
for( i = 0 ; i < nSerieCount ; i++) {
// Create the new pen as the color of serie
pen = new CPen(PS_SOLID,0,mpSerie[i].m_plotColor);
old = pDC->SelectObject(pen);
// calculate the corrdinate of ploting point.
pt = Corrdinate(mpSerie[i].dValueX[0],mpSerie[i].dValueY[0]) ;
pDC->MoveTo(pt);
//Start plot all available data .
for(int index = 1 ; index <= nPlotIndex ; index++){
pt = Corrdinate(mpSerie[i].dValueX[index],
mpSerie[i].dValueY[index]) ;
pDC->LineTo(pt) ;
}
pDC->SelectObject(old);
delete pen ;
}
}
void CChart::DrawChartTitle(CDC *pDC)
{
int x , y ;
int oldbkmode ;
CFont *old ;
old = pDC->SelectObject(pTitleFont);
oldbkmode = pDC->SetBkMode(TRANSPARENT) ;
CSize textSize = pDC->GetTextExtent(strChartTitle,
strChartTitle.GetLength()) ;
//Calculate centre window corrdinate of text ;
pDC->SetTextAlign(TA_LEFT);
y = m_ctlRect.top + (textSize.cy/2) ;
x = m_clientRect.left +
(m_clientRect.Width()/2) - (textSize.cx/2) ;
pDC->TextOut(x,y,strChartTitle) ;
pDC->SetBkMode(oldbkmode);
pDC->SelectObject(old);
}
void CChart::DrawGrid(CDC *pDC)
{
CPen *old , pen(PS_SOLID, 0, m_GridColor);
CPoint m_start, m_stop ;
int i ;
double x ,y ;
double step ;
old = pDC->SelectObject(&pen);
// Draw vertical grid
step = (dRangeX[MAX] - dRangeX[MIN]) / (double)nGridX ;
for( i = 0 ; i <= nGridX ; i++ )
{
x = dRangeX[MIN] + (step * (double)i) ;
m_start = Corrdinate(x,dRangeY[MIN]);
m_stop = Corrdinate(x,dRangeY[MAX]);
pDC->MoveTo(m_start);
pDC->LineTo(m_stop);
}
// Draw horizontal grid.
step = (dRangeY[MAX] - dRangeY[MIN]) / (double)nGridY ;
for( i = 0 ; i <= nGridY ; i++ )
{
y = dRangeY[MIN] + (step * (double)i) ;
m_start = Corrdinate(dRangeX[MIN],y) ;
m_stop = Corrdinate(dRangeX[MAX],y) ;
pDC->MoveTo(m_start);
pDC->LineTo(m_stop);
}
pDC->SelectObject(old);
}
BOOL CChart::SetRange(double dMinX, double dMaxX,
double dMinY,double dMaxY)
{
bBkNeedToUpdate = TRUE ; // Background need to be re-draw
dRangeY[MIN] = dMinY ;
dRangeY[MAX] = dMaxY ;
dRangeX[MAX] = dMaxX ;
dRangeX[MIN] = dMinX ;
ResCalc();
return TRUE ;
}
////////////////////////////////////////////////////////
//Calculate resolution per dot
void CChart::ResCalc()
{
double dpixelx, dpixely ;
dpixelx = (double)m_axisRect.Width() ;
dpixely = (double)m_axisRect.Height() ;
dResY = (dRangeY[MAX] - dRangeY[MIN]) / dpixely ;
dResX = (dRangeX[MAX] - dRangeX[MIN]) / dpixelx ;
}
///////////////////////////////////////////////////////
// Set Axis style
//
void CChart::SetAxisStyle(int iStyle)
{
if( (iStyle > 2) || (iStyle < 0) ) return ;
iChartType = iStyle ;
}
////////////////////////////////////////////////////////
// Draw 1/5 fine scale in log mode
// in order to implement log grid set bLogScale = TRUE
void CChart::DrawLogGrid(CDC *pDC)
{
// Change this number for changing number of fine scales.
const int FINE_SCALE = 5 ;
CPen *old , pen(PS_SOLID, 0, RGB(192,192,192)) ;
CPoint m_start, m_stop ;
int i ;
int j ;
double y ;
double step ;
double Ymax , Ymin ;
Ymax = dRangeY[MAX] ;
Ymin = dRangeY[MIN] ;
//Remap scale to 0 - nGridY
SetRange(dRangeX[MIN],dRangeX[MAX],0,nGridY);
old = pDC->SelectObject(&pen);
for( j = (int)dRangeY[MIN] ; j < (int)dRangeY[MAX] ; j ++) {
y = (double)j;
step = (pow(10,y+1) - pow(10,y)) /(double)FINE_SCALE ;
for(i = 0 ; i < FINE_SCALE ; i++ )
{
y = log10(pow(10,y) + step) ;
m_start = Corrdinate(dRangeX[MIN],y) ;
m_stop = Corrdinate(dRangeX[MAX],y) ;
pDC->MoveTo(m_start);
pDC->LineTo(m_stop);
}
}
SetRange(dRangeX[MIN],dRangeX[MAX],Ymin,Ymax ) ;
pDC->SelectObject(old);
}
////////////////////////////////////////
// Allocate memory for plotting
BOOL CChart::AllocSerie(int nSerie)
{
if( !bIsSerieAllocated )
{
for(int i = 0 ; i < nSerieCount ; i++ ) {
if ( !mpSerie[i].AllocSerie(nSerie)) {
AfxMessageBox("Can not allocate serie") ;
return FALSE ;
}
}
}
nPointCount = nSerie ;
bIsSerieAllocated = TRUE ;
return TRUE ;
}
BOOL CChart::SetXYValue(double x, double y, int index, int iSerieIdx)
{
if(!bIsSerieAllocated ) return FALSE ;
if (iSerieIdx > (nSerieCount-1)) return FALSE ;
// Prevent writting to unallocated buffer
if(index >= nPointCount ) return FALSE ;
// Clip the ploting area if it exceed ranged .
if(x > dRangeX[MAX] ) x = dRangeX[MAX] ;
if(x < dRangeX[MIN] ) x = dRangeX[MIN] ;
if(y > dRangeY[MAX] ) y = dRangeY[MAX] ;
if(y < dRangeY[MIN] ) y = dRangeY[MIN];
mpSerie[iSerieIdx].dValueX[index] = x ;
mpSerie[iSerieIdx].dValueY[index] = y ;
mpSerie[iSerieIdx].bIsPlotAvailable = TRUE ;
nPlotIndex = index ;
return TRUE ;
}
//////////////////////////////////////////////////
// Set chart title
void CChart::SetChartTitle(CString str)
{
strChartTitle = str ;
bBkNeedToUpdate = TRUE ; // Background need to be re-draw
}
////////////////////////////////////////////////////
// Set number of grids on x axis and y axis
void CChart::SetGridXYNumber(int gridx, int gridy)
{
nGridX = gridx ;
nGridY = gridy ;
bBkNeedToUpdate = TRUE ;
}
//////////////////////////////////////////////////////
// Create desired fonts for draw legend in x axis and
// y axis .
void CChart::CreateFont()
{
//Create system font ..
LOGFONT d_lf ;
// Init desired font
memset(&d_lf, 0, sizeof(LOGFONT));
lstrcpy(d_lf.lfFaceName, "Times New Roman") ;
// Initial font size
// Get a screen DC
CWindowDC wdc(NULL) ;
const int cyPixels = wdc.GetDeviceCaps(LOGPIXELSY);
d_lf.lfHeight = -1 * MulDiv(7, cyPixels, 72);
// Create a new font 7 pts.
pLegendFontY = new CFont() ;
pLegendFontY->CreateFontIndirect(&d_lf);
d_lf.lfHeight = -1 * MulDiv(12, cyPixels, 72);
d_lf.lfWeight = FW_BOLD ;
pTitleFont = new CFont();
pTitleFont->CreateFontIndirect(&d_lf);
d_lf.lfWeight = 0 ;
d_lf.lfOrientation = 900 ; // Rotate font 90 degree for x axis
d_lf.lfEscapement = 900 ;
d_lf.lfHeight = -1 * MulDiv(7, cyPixels, 72);
pLegendFontX = new CFont() ;
pLegendFontX->CreateFontIndirect(&d_lf);
}
void CChart::DrawGridLabel(CDC *pDC)
{
CFont *oldFont ;
oldFont = pDC->SelectObject(pLegendFontY);
int i ;
double res , y ;
CPoint cal_pt ;
CSize txtSize ;
CString str ;
res = (dRangeY[MAX] - dRangeY[MIN]) / nGridY ;
pDC->SetTextAlign(TA_RIGHT);
pDC->SetBkMode(TRANSPARENT);
for ( i = 0 ; i <= nGridY ; i++)
{
y = dRangeY[MIN] + (res * (double)i) ;
cal_pt = Corrdinate(dRangeX[MIN],y) ;
str.Format("%5.2f",y) ;
txtSize = pDC->GetTextExtent(str) ;
cal_pt.x -= 5 ;
cal_pt.y -= txtSize.cy/2 ;
pDC->TextOut(cal_pt.x,cal_pt.y,str) ;
}
txtSize = pDC->GetTextExtent(strLabelX);
cal_pt.x = m_ctlRect.CenterPoint().x + (txtSize.cx/2) ;
cal_pt.y = m_ctlRect.bottom - txtSize.cy - 5;
pDC->TextOut(cal_pt.x,cal_pt.y,strLabelX);
pDC->SelectObject(pLegendFontX);
res = (dRangeX[MAX] - dRangeX[MIN]) / nGridX ;
for ( i = 0 ; i <= nGridX ; i ++ )
{
y = dRangeX[MIN] + ( res * (double)i);
cal_pt = Corrdinate(y,dRangeY[MIN]);
str.Format("%5.1f",y);
txtSize = pDC->GetTextExtent(str) ;
cal_pt.x -= txtSize.cy/2 ;
cal_pt.y += 5 ;
pDC->TextOut(cal_pt.x,cal_pt.y,str);
}
txtSize = pDC->GetTextExtent(strLabelY);
cal_pt.x = m_ctlRect.left + (txtSize.cy/2) ;
cal_pt.y = m_ctlRect.CenterPoint().y - (txtSize.cx/2) ;
pDC->TextOut(cal_pt.x,cal_pt.y,strLabelY);
pDC->SelectObject(oldFont);
}
void CChart::ClearChart()
{
nPlotIndex = 0 ;
InvalidateRect(m_clientRect);
}
void CChart::PrintChart(CDC *pDC,int x , int y)
{
int xPixel ;
int yPixel ;
int oldmapmode ;
CDC *dc = GetDC();
xPixel = pDC->GetDeviceCaps(LOGPIXELSX);
yPixel = pDC->GetDeviceCaps(LOGPIXELSY);
//Calculate ratio to be zoomed.
xPixel = xPixel /dc->GetDeviceCaps(LOGPIXELSX);
yPixel = yPixel /dc->GetDeviceCaps(LOGPIXELSY);
ReleaseDC(dc);
oldmapmode = pDC->SetMapMode(MM_ANISOTROPIC);
pDC->SetViewportExt(xPixel,yPixel);
pDC->SetViewportOrg(x,y);
DrawBorder(pDC);
pDC->DrawEdge(m_ctlRect,BDR_SUNKENINNER|BDR_SUNKENOUTER, BF_RECT);
DrawChartTitle(pDC);
if ( bLogScale )
DrawLogGrid(pDC);
DrawGrid(pDC);
DrawAxis(pDC) ;
DrawGridLabel(pDC);
Plot(pDC) ;
pDC->SetMapMode(oldmapmode);
}
void CChart::SetChartLabel(CString strX, CString strY)
{
strLabelX = strX ;
strLabelY = strY ;
}
void CChart::OnPaint()
{
CPaintDC dc(this); // device context for painting
CDC memPlotDC ;
CBitmap *oldBitmap ;
CBitmap m_plotBitmap ;
// Check if background need to be redrawn
if (bBkNeedToUpdate){
DrawBackGround(&dc);
bBkNeedToUpdate = FALSE ;
}
memPlotDC.CreateCompatibleDC(&dc) ;
m_plotBitmap.CreateCompatibleBitmap(&dc,
m_clientRect.Width(), m_clientRect.Height());
oldBitmap = (CBitmap*)memPlotDC.SelectObject(&m_plotBitmap);
// BitBlt background .
memPlotDC.BitBlt(0,0, m_clientRect.Width(), m_clientRect.Height(),
&memBkDC,0,0,SRCCOPY);
Plot(&memPlotDC);
dc.BitBlt(0,0, m_clientRect.Width(), m_clientRect.Height(),
&memPlotDC,0,0,SRCCOPY);
memPlotDC.SelectObject(oldBitmap);
}
void CChart::DrawBackGround(CDC *pDC)
{
if(memBkDC.GetSafeHdc() == NULL) {
memBkDC.CreateCompatibleDC(pDC);
m_BkBitmap.CreateCompatibleBitmap(pDC,
m_clientRect.Width(), m_clientRect.Height());
}
m_pOldBkBitmap = (CBitmap*)memBkDC.SelectObject(&m_BkBitmap) ;
DrawBorder(&memBkDC);
DrawAxis(&memBkDC);
DrawGrid(&memBkDC);
if(bLogScale)
DrawLogGrid(&memBkDC);
DrawGridLabel(&memBkDC);
DrawChartTitle(&memBkDC);
}
void CChart::Invalidate(BOOL bErase )
{
if ( bErase )
bBkNeedToUpdate = TRUE ;
CWnd::Invalidate(bErase) ;
}
////////////////////////////////////////////////////
// Source of CPlotSerie
CPlotSerie::CPlotSerie()
{
dValueX = NULL ;
dValueY = NULL ;
bIsPlotAvailable = FALSE ;
bBufferAllocated = FALSE ;
m_plotColor = RGB(0,0,0);
}
BOOL CPlotSerie::AllocSerie(UINT nPoint)
{
dValueX = (double*)malloc(nPoint * sizeof (double) ) ;
dValueY = (double*)malloc(nPoint * sizeof (double) ) ;
if ( (dValueX == NULL) || (dValueY == NULL) )
return FALSE ;
bBufferAllocated = TRUE ;
return TRUE ;
}
BOOL CPlotSerie::FreeSerie()
{
if ( bBufferAllocated ) {
free(dValueX) ;
free(dValueY) ;
}
bBufferAllocated = FALSE ;
return TRUE ;
}