// DataChart.cpp: implementation of the CDataChart class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "chart.h"
#include "DataChart.h"
#include <math.h>
#define AREA_MARGINS 80 // fraction of area
#define AREA_TITLE 10 // fraction of area
#define AREA_YAXIS 7 // percentage
#define AREA_XAXIS 10 // percentage
#define AREA_LEGEND 30 // Maximum percentage for legend
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// CDataChart
CDataChart::CDataChart()
{
// set defaul value
m_nStyle = STYLE_LINE;
m_clrBkGnd = RGB( 255, 255, 255 );
m_strTitle = _T("");
m_strXText = _T("");
m_strYText = _T("");
m_nYTicks = 20;
m_bShowYScale = true;
m_bShowXScale = true;
m_bShowBaseLine = false;
m_bShowGrid = false;
m_bShowLegend = true;
m_nRoundY = 10;
m_nXMin = 32768;
m_nXMax = 0;
m_nYMin = 0;
m_nYMax = 0;
m_nXLabelStep = 1;
m_dataset = (List *)new List();
srand( (unsigned)time( NULL ) );
}
CDataChart::~CDataChart()
{
delete m_dataset;
}
BEGIN_MESSAGE_MAP(CDataChart, CWnd)
//{{AFX_MSG_MAP(CDataChart)
ON_WM_PAINT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CDataChart message handlers
void CDataChart::OnPaint()
{
CPaintDC dc(this); // device context for painting
if(m_nStyle == STYLE_LINE)
{
CalcDatas();
DrawLine(dc);
}
else if(m_nStyle == STYLE_PIE)
DrawPie(dc);
}
bool CDataChart::CopyToClipboard()
{
//
// Special thanks to Zafir Anjum for a large part of following code.
//
CBitmap bitmap;
CClientDC dc(this);
CDC memDC;
BeginWaitCursor();
memDC.CreateCompatibleDC(&dc);
bitmap.CreateCompatibleBitmap(&dc, m_rectArea.Width(),m_rectArea.Height() );
CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);
if(m_nStyle == STYLE_LINE)
DrawLine(memDC);
else if(m_nStyle == STYLE_PIE)
DrawPie(memDC);
this->OpenClipboard() ;
EmptyClipboard() ;
SetClipboardData (CF_BITMAP, bitmap.GetSafeHandle() ) ;
CloseClipboard () ;
bitmap.Detach();
EndWaitCursor();
return true;
}
///////////////////////////////////////////////////////////////////////
//
// PaintBkGnd
//
// arguments
//
// dc = Decive Context
//
// return
//
// true if ok, else false
//
bool CDataChart::PaintBkGnd(CDC &dc)
{
CBrush brsh( m_clrBkGnd );
CBrush* pOldBrush = dc.SelectObject( &brsh );
dc.Rectangle( m_rectArea );
dc.SelectObject( pOldBrush );
return true;
}
//
// DrawTitle
//
// arguments
//
// dc = Decive Context
//
// return
//
// true if ok, else false
//
bool CDataChart::DrawTitle(CDC & dc)
{
if( m_strTitle.IsEmpty() )
return false;
CFont font, *pFontOld;
font.CreateFont( m_rectTitle.Height(), 0, 0, 0, FW_NORMAL,
FALSE, FALSE, FALSE, ANSI_CHARSET,
OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY,
DEFAULT_PITCH, "Arial");
COLORREF clrBkOld = dc.SetBkColor( m_clrBkGnd );
pFontOld = dc.SelectObject( &font );
dc.DrawText( m_strTitle, m_rectTitle, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
dc.SetBkColor( clrBkOld );
dc.SelectObject( pFontOld );
return true;
}
//
// DrawHorzLine
//
// arguments
//
// dc = Decive Context
//
// return
//
// true if ok, else false
//
bool CDataChart::DrawHorzLine(CDC & dc)
{
double nTicks = (double)GetYTicks();
if( !nTicks )
return false;
double nY = (m_nYMax - m_nYMin)/nTicks, nTemp;
int f;
CPen pen( PS_SOLID, 1, RGB(0,0,0)), *pPenOld;
pPenOld = dc.SelectObject( &pen );
for( f=0; f<=nTicks; f++ ) {
nTemp = m_rectData.bottom - ( nY*f ) * m_rectData.Height()/(m_nYMax-m_nYMin);
dc.MoveTo( m_rectData.left , (int)nTemp );
dc.LineTo( m_rectData.right, (int)nTemp );
}
dc.SelectObject( pPenOld );
return true;
}
//
// DrawVertLine
//
// arguments
//
// dc = Decive Context
//
// return
//
// true if ok, else false
//
bool CDataChart::DrawVertLine(CDC & dc)
{
if( !m_nXMax )
return false;
int f, nCount, offset;
nCount = (int)((double)(m_nXMax - m_nXMin)/(double)m_nXLabelStep);
CPen pen( PS_SOLID, 1, RGB(0,0,0)), *pPenOld;
pPenOld = dc.SelectObject( &pen );
for( f=0; f<=nCount; f++) {
offset = (int)((double)m_rectData.Width()/(double)nCount*(double)f);
dc.MoveTo( m_rectData.left + offset, m_rectData.top );
dc.LineTo( m_rectData.left + offset, m_rectData.bottom );
}
// dc.MoveTo( m_rectData.left + m_rectData.Width(), m_rectData.top );
// dc.LineTo( m_rectData.left + m_rectData.Width(), m_rectData.bottom );
dc.SelectObject( pPenOld );
return true;
}
//
// DrawXScale
//
// arguments
//
// dc = Decive Context
//
// return
//
// true if ok, else false
//
bool CDataChart::DrawXScale(CDC & dc)
{
if(!m_nXLabelStep)
return false;
int nCount = (int)((double)(m_nXMax - m_nXMin)/(double)m_nXLabelStep);
int f, nFontSize = (int)(m_rectXAxis.Height()/3.0);
double nBar;
CRect rectTemp;
const int nBkModeOld = dc.SetBkMode( TRANSPARENT );
COLORREF clrBlack = RGB( 0, 0, 0), clrOld;
CPen pen(PS_SOLID, 3, clrBlack ), *pPenOld;
CFont font, *pFontOld;
CString sBuffer;
font.CreateFont( nFontSize, 0, 0, 0, FW_NORMAL,
FALSE, FALSE, FALSE, ANSI_CHARSET,
OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY,
DEFAULT_PITCH, "Arial");
pPenOld = dc.SelectObject(&pen);
pFontOld = dc.SelectObject(&font);
clrOld = dc.SetTextColor( clrBlack );
nBar = (double)m_rectData.Width()/(double)(nCount*2);
// draw text
for( f=0; f<=nCount; f++ ) {
rectTemp.top = m_rectXAxis.top;
rectTemp.bottom = m_rectXAxis.bottom;
rectTemp.left = m_rectXAxis.left + (int)((double)m_rectData.Width()/(double)nCount*(double)f - nBar);
rectTemp.right = m_rectXAxis.left + (int)((double)m_rectData.Width()/(double)nCount*(double)f + nBar);
sBuffer.Format("%d", m_nXMin + f*m_nXLabelStep );
dc.DrawText( sBuffer, rectTemp, DT_CENTER | DT_TOP | DT_SINGLELINE );
}
if( !m_strXText.IsEmpty() ) {
int nFontXTextSize = nFontSize*2;
CFont fontXText;
fontXText.CreateFont( nFontXTextSize, 0, 0, 0, FW_NORMAL,
FALSE, FALSE, FALSE, ANSI_CHARSET,
OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY,
DEFAULT_PITCH, "Arial");
dc.SelectObject(&fontXText);
dc.DrawText( m_strXText, m_rectXAxis, DT_CENTER | DT_BOTTOM | DT_SINGLELINE );
}
dc.SelectObject(pPenOld);
dc.SelectObject(pFontOld);
dc.SetTextColor(clrOld);
dc.SetBkMode(nBkModeOld);
return true;
}
//
// DrawYScale
//
// arguments
//
// dc = Decive Context
//
// return
//
// true if ok, else false
//
bool CDataChart::DrawYScale(CDC & dc)
{
if( !m_bShowYScale )
return false;
int nTicks;
if( !(nTicks = GetYTicks()) )
return false;
// nY is the size of a division
double nY = (m_nYMax - m_nYMin)/nTicks, nTemp1, nTemp2;
int f, nFontSize = (int)(m_rectYAxis.Width()/4);
CString sBuffer;
const int nBkModeOld = dc.SetBkMode( TRANSPARENT );
COLORREF clrBlack = RGB( 0, 0, 0), clrOld;
CPen pen(PS_SOLID, 3, clrBlack ), *pPenOld;
CFont font, *pFontOld;
font.CreateFont( nFontSize, 0, 0, 0, FW_NORMAL,
FALSE, FALSE, FALSE, ANSI_CHARSET,
OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY,
DEFAULT_PITCH, "Arial");
pPenOld = dc.SelectObject(&pen);
pFontOld = dc.SelectObject(&font);
clrOld = dc.SetTextColor( clrBlack );
// draw text
for( f=0; f<=nTicks; f++ ) {
nTemp1 = m_rectYAxis.bottom + nFontSize/2 - ( nY*(f) ) * m_rectData.Height()/(m_nYMax-m_nYMin);
nTemp2 = m_rectYAxis.bottom + nFontSize/2 - ( nY*(f+1) ) * m_rectData.Height()/(m_nYMax-m_nYMin);
sBuffer.Format("%g", m_nYMin + nY*f );
dc.DrawText( sBuffer, CRect( m_rectYAxis.left,(int)nTemp2, m_rectYAxis.right, (int)nTemp1 ), DT_RIGHT | DT_BOTTOM | DT_SINGLELINE );
}
if( !m_strYText.IsEmpty() ) {
int nFontYTextSize = nFontSize*2;
CFont fontYText;
fontYText.CreateFont( nFontYTextSize, 0, 900, 0, FW_NORMAL,
FALSE, FALSE, FALSE, ANSI_CHARSET,
OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY,
DEFAULT_PITCH, "Arial");
dc.SelectObject(&fontYText);
dc.DrawText( m_strYText, m_rectYAxis, DT_BOTTOM | DT_LEFT | DT_SINGLELINE );
}
dc.SelectObject(pPenOld);
dc.SelectObject(pFontOld);
dc.SetTextColor(clrOld);
dc.SetBkMode(nBkModeOld);
return true;
}
//
// DrawDataset
//
// arguments
//
// dc = Decive Context
// ds = Dataset
//
// return
//
// true if ok, else false
//
bool CDataChart::DrawDataset(CDC &dc, CDataSeries *ds)
{
// let's calc the bar size
double nBarWidth, nSample, nTemp;
int i, f, nXTemp, nCount;
nCount = (int)((double)(m_nXMax - m_nXMin)/(double)m_nXLabelStep);
nBarWidth = (double)m_rectData.Width()/(double)nCount;
if(!ds->Get(0, f, nSample))
return true;
// let's rock
CPen pen( PS_SOLID, ds->GetSize(), ds->GetColor() ), *pPenOld;
CBrush brush( ds->GetColor() ), *pBrushOld;
pPenOld = dc.SelectObject( &pen );
pBrushOld = dc.SelectObject( &brush );
// nTemp will contains a parametrized data
nXTemp = (int)((double)m_rectData.Width()/(double)(m_nXMax-m_nXMin)*(double)(f-m_nXMin));
nTemp = ( nSample - m_nYMin) * m_rectData.Height()/(m_nYMax-m_nYMin);
dc.MoveTo( m_rectData.left + nXTemp, m_rectData.bottom - (int)nTemp );
for( i = 0; i < ds->GetDatasetSize(); i++ )
{
ds->Get(i, f, nSample);
nXTemp = (int)((double)m_rectData.Width()/(double)(m_nXMax-m_nXMin)*(double)(f-m_nXMin));
nTemp = ( nSample - m_nYMin) * m_rectData.Height()/(m_nYMax-m_nYMin);
dc.LineTo( m_rectData.left + nXTemp, m_rectData.bottom - (int)(nTemp) );
}
dc.SelectObject( pPenOld );
dc.SelectObject( pBrushOld );
for( i=0; i<ds->GetDatasetSize(); i++ )
{
ds->Get(i, f, nSample);
if( ds->GetMarker() != DATASET_MARKER_NONE )
{
nXTemp = (int)((double)m_rectData.Width()/(double)(m_nXMax-m_nXMin)*(double)(f-m_nXMin));
nTemp = ( nSample - m_nYMin) * m_rectData.Height()/(m_nYMax-m_nYMin);
DrawLinMarker(dc, ds, m_rectData.left + nXTemp, m_rectData.bottom - (int)nTemp);
}
}
return true;
}
//
// CalcDatas
// calculate all useful variables starting from the control size
//
// arguments
//
// none
//
// return
//
// true if ok, else false
//
bool CDataChart::CalcDatas()
{
double nTemp1, nTemp2;
int f=0, nTemp3, nTemp4;
CDataSeries *pdata;
GetClientRect( m_rectArea );
m_rectUsable.top = m_rectArea.top + m_rectArea.Height()/AREA_MARGINS;
m_rectUsable.bottom = m_rectArea.bottom - m_rectArea.Height()/AREA_MARGINS;
m_rectUsable.left = m_rectArea.left + m_rectArea.Width() /AREA_MARGINS;
m_rectUsable.right = m_rectArea.right - m_rectArea.Width() /AREA_MARGINS;
// let's calc everything
if( !m_strTitle.IsEmpty() ) {
m_rectTitle.top = m_rectUsable.top;
m_rectTitle.left = m_rectUsable.left;
m_rectTitle.bottom = m_rectUsable.bottom/AREA_TITLE;
m_rectTitle.right = m_rectUsable.right;
m_rectGraph.top = m_rectTitle.bottom;
m_rectGraph.left = m_rectUsable.left;
m_rectGraph.bottom = m_rectUsable.bottom;
m_rectGraph.right = m_rectUsable.right;
} else {
m_rectGraph = m_rectUsable;
}
m_rectYAxis.top = m_rectGraph.top;
m_rectYAxis.left = m_rectGraph.left;
m_rectYAxis.bottom = m_rectGraph.top + m_rectGraph.Height()*(100-AREA_XAXIS)/100;
m_rectYAxis.right = m_rectGraph.left + m_rectGraph.Width()*(AREA_YAXIS)/100;
CalcLegend();
m_rectXAxis.top = m_rectGraph.top + m_rectGraph.Height()*(100-AREA_XAXIS)/100;
m_rectXAxis.left = m_rectGraph.left + m_rectGraph.Width()*(AREA_YAXIS)/100;
m_rectXAxis.bottom = m_rectGraph.bottom;
m_rectXAxis.right = m_rectGraph.right - m_rectLegend.Width() - 5;
m_rectData.top = m_rectGraph.top;
m_rectData.bottom = m_rectXAxis.top;
m_rectData.left = m_rectYAxis.right;
m_rectData.right = m_rectGraph.right - m_rectLegend.Width() - 5;
for(f = 0; f < m_dataset->size(); f++)
{
pdata = (CDataSeries *) m_dataset->get(f);
pdata->GetXMinMax(nTemp3, nTemp4);
m_nXMin = min( m_nXMin ,nTemp3 );
m_nXMax = max( m_nXMax, nTemp4 );
pdata->GetYMinMax(nTemp1, nTemp2);
m_nYMin = min( m_nYMin ,nTemp1 );
m_nYMax = max( m_nYMax, nTemp2 );
}
// Hey man, there's nothing to draw
if( m_nXMax == 0 )
return false;
if((m_nXMax-m_nXMin)%m_nXLabelStep != 0)
m_nXMax = ((m_nXMax - m_nXMin)/m_nXLabelStep + 1)*m_nXLabelStep + m_nXMin;
// now I modify m_nYMin & m_nXMax to improve readability
m_nYMin -= (m_nYMax - m_nYMin)*0.0;
m_nYMax += (m_nYMax - m_nYMin)*0.0;
// whid this 'strange' function I can set m_nYmin & m_nYMax so that
// they are multiply of m_nRoundY
if( m_nRoundY > 0.0 ) {
if(m_nYMin >= 0)
m_nYMin = 0;
else
m_nYMin = (((int)m_nYMin-(int)m_nRoundY)/(int)m_nRoundY)*m_nRoundY;
m_nYMax = (((int)m_nYMax+(int)m_nRoundY)/(int)m_nRoundY)*m_nRoundY;
}
// now nYMin & nYMax contain absolute min and absolute max
// and these data can be used to calc the graphic's Y scale factor
// nXMax contains the maximum number of elements, useful to
// calculate the X scale factor
return true;
}
//
// Redraw
// redraw everything
//
// arguments
//
// none
//
// return
//
// true if ok, else false
//
bool CDataChart::Redraw()
{
Invalidate(true);
GetParent()->SendMessage( WM_PAINT, 0, 0 );
return true;
}
CDataSeries * CDataChart::LookUp(int nDatasetIndex)
{
CDataSeries *pdata;
for(int i = 0; i < m_dataset->size(); i++)
{
pdata = (CDataSeries *)m_dataset->get(i);
if(pdata->GetID() == nDatasetIndex)
return pdata;
}
return NULL;
}
//
// SetData
// Append a data into the dataset
//
// arguments
//
// nDatasetIndex = dataset index
// nIndex = index
// nData = value
//
// return
//
// true if ok, else false
//
void CDataChart::Add(int nDatasetIndex, int nIndex, double nData)
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
{
pdata->Add(nIndex, nData);
return;
}
else
{
CString label;
pdata = (CDataSeries *) new CDataSeries();
pdata->SetID(nDatasetIndex);
pdata->Add(nIndex, nData);
m_dataset->add(pdata);
label = pdata->GetLabel();
if(label.IsEmpty())
{
label.Format("Data Series %d", nDatasetIndex);
pdata->SetLabel(label);
}
}
return;
}
//
// SetData
// Modify a data into the dataset
//
// arguments
//
// nDatasetIndex = dataset index
// nIndex = index
// nData = value
//
// return
//
// true if ok, else false
//
bool CDataChart::SetData(int nDatasetIndex, int nIndex, double nData)
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
return pdata->SetData(nIndex, nData);
return false;
}
//
// GetData
// get data from the dataset
//
// arguments
//
// nDatasetIndex = dataset index
// nIndex = index
// nData = value
//
// return
//
// true if ok, else false
//
bool CDataChart::GetData(int nDatasetIndex, int nIndex, double& nData)
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
return pdata->GetData(nIndex, nData);
return false;
}
//
// SetDatasetStyle
//
// arguments
//
// nDatasetIndex = dataset index
// nStyle = style
//
// return
//
// true if ok, else false
//
bool CDataChart::SetStyle(int nStyle)
{
m_nStyle = nStyle;
return true;
}
//
// GetDatasetStyle
//
// arguments
//
// nDatasetIndex = dataset index
// nStyle = style
//
// return
//
// true if ok, else false
//
bool CDataChart::GetStyle(int& nStyle)
{
nStyle = m_nStyle;
return true;
}
bool CDataChart::SetDatasetLabel( int nDatasetIndex, CString sLabel )
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
{
pdata->SetLabel(sLabel);
return true;
}
return false;
}
bool CDataChart::GetDatasetLabel( int nDatasetIndex, CString& sLabel )
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
{
sLabel = pdata->GetLabel();
return true;
}
return false;
}
//
// SetDatasetMarker
//
// arguments
//
// nDatasetIndex = dataset index
// nMarker = marker
//
// return
//
// true if ok, else false
//
bool CDataChart::SetDatasetMarker(int nDatasetIndex, int nMarker)
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
return pdata->SetMarker(nMarker);
return false;
}
bool CDataChart::SetDatasetMarkerRatio(int nDatasetIndex, int nRatio)
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
return pdata->SetMarkerRatio(nRatio);
return false;
}
//
// GetDatasetMarker
//
// arguments
//
// nDatasetIndex = dataset index
// nMarker = marker
//
// return
//
// true if ok, else false
//
bool CDataChart::GetDatasetMarker(int nDatasetIndex, int& nMarker)
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
{
nMarker = pdata->GetMarker();
return true;
}
return false;
}
bool CDataChart::GetDatasetMarkerRatio(int nDatasetIndex, int& nRatio)
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
{
nRatio = pdata->GetMarkerRatio();
return true;
}
return false;
}
//
// SetDatasetPenSize
//
// arguments
//
// nDatasetIndex = dataset index
// nSize = pen size in pixel or bar size (range 1-10)
//
// return
//
// true if ok, else false
//
bool CDataChart::SetDatasetPenSize(int nDatasetIndex, int nSize)
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
return pdata->SetSize(nSize);
return false;
}
//
// GetDatasetPenSize
//
// arguments
//
// nDatasetIndex = dataset index
// nSize = size
//
// return
//
// true if ok, else false
//
bool CDataChart::GetDatasetPenSize(int nDatasetIndex, int& nSize)
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
{
nSize = pdata->GetSize();
return true;
}
return false;
}
//
// SetDatasetPenColor
//
// arguments
//
// nDatasetIndex = dataset index
// clr = color
//
// return
//
// true if ok, else false
//
bool CDataChart::SetDatasetPenColor(int nDatasetIndex, COLORREF clr)
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
return pdata->SetColor(clr);
return false;
}
//
// GetDatasetPenColor
//
// arguments
//
// nDatasetIndex = dataset index
// clr = color
//
// return
//
// true if ok, else false
//
bool CDataChart::GetDatasetPenColor(int nDatasetIndex, COLORREF& clr)
{
CDataSeries *pdata;
if((pdata = LookUp(nDatasetIndex)) != NULL)
{
clr = pdata->GetColor();
return true;
}
return false;
}
//
// SetBkGnd
//
// arguments
//
// clr = color
//
// return
//
// true if ok, else false
//
bool CDataChart::SetBkGnd(COLORREF clr)
{
m_clrBkGnd = clr;
Redraw();
return true;
}
//
// SetBkGnd
//
// arguments
//
// none
//
// return
//
// true if ok, else false
//
bool CDataChart::SetBkGnd()
{
CColorDialog dlg( GetBkGnd() );
if( dlg.DoModal() == IDOK )
return SetBkGnd( dlg.GetColor() );
else
return false;
}
//
// GetBkGnd
//
// arguments
//
// none
//
// return
//
// background color
//
COLORREF CDataChart::GetBkGnd()
{
return m_clrBkGnd;
}
//
// SetTitle
//
// arguments
//
// sTitle = title
//
// return
//
// true if ok, else false
//
bool CDataChart::SetTitle(CString strTitle)
{
m_strTitle = strTitle;
Redraw();
return true;
}
//
// GetTitle
//
// arguments
//
// none
//
// return
//
// title
//
CString CDataChart::GetTitle()
{
return m_strTitle;
}
//
// SetYTicks
//
// arguments
//
// nTicks = Y divisions (or ticks) in range 0-100
//
// return
//
// true if ok, else false
//
bool CDataChart::SetYTicks(int nTicks)
{
m_nYTicks = nTicks;
m_nYTicks = min( m_nYTicks, 100 );
m_nYTicks = max( m_nYTicks, 0 );
return true;
}
//
// GetYTicks
//
// arguments
//
// none
//
// return
//
// nTicks
//
int CDataChart::GetYTicks()
{
return m_nYTicks;
}
//
// SetRoundY
// let's try to improve readability
//
// arguments
//
// nRoundY = rounding value
//
// return
//
// true if ok, else false
//
bool CDataChart::SetRoundY(double nRound)
{
if( nRound <= 0)
return false;
m_nRoundY = nRound;
return true;
}
//
// GetRoundY
//
// arguments
//
// none
//
// return
//
// nRoundY = rounding value
//
double CDataChart::GetRoundY()
{
return m_nRoundY;
}
//
// SetYText
//
// arguments
//
// sText = text
//
// return
//
// true if ok, else false
//
bool CDataChart::SetYText(CString sText)
{
m_strYText = sText;
Redraw();
return true;
}
//
// GetYText
//
// arguments
//
// none
//
// return
//
// sText = text
//
CString CDataChart::GetYText()
{
return m_strYText;
}
//
// SetXText
//
// arguments
//
// sText = text
//
// return
//
// true if ok, else false
//
bool CDataChart::SetXText(CString sText)
{
m_strXText = sText;
Redraw();
return true;
}
//
// GetXText
//
// arguments
//
// none
//
// return
//
// sText = text
//
CString CDataChart::GetXText()
{
return m_strXText;
}
//
// SetXLabelStep
//
// arguments
//
// nStep = x label step
//
// return
//
// true if ok, else false
//
bool CDataChart::SetXLabelStep(int nStep)
{
m_nXLabelStep = max( nStep, 1 );
return true;
}
//
// GetXLabelStep
//
// arguments
//
// none
//
// return
//
// nStep = x label step
//
int CDataChart::GetXLabelStep()
{
return m_nXLabelStep;
}
bool CDataChart::CalcLegend()
{
m_rectLegend.top = m_rectGraph.top;
m_rectLegend.bottom = m_rectGraph.bottom;
m_rectLegend.right = m_rectGraph.right;
m_rectLegend.left = m_rectGraph.right;
CDataSeries *pdata;
int maxLegend, widLegend, maxTextWidth, titleWidth;
CString label;
if(m_bShowLegend)
{
maxLegend = m_rectGraph.Width()*(AREA_LEGEND)/100;
titleWidth = 20 + 48 + 20;
maxTextWidth = 0;
for(int i = 0; i < m_dataset->size(); i++)
{
pdata = (CDataSeries *)m_dataset->get(i);
label = pdata->GetLabel();
if(label.IsEmpty())
{
label.Format("Data Series %d", pdata->GetID());
pdata->SetLabel(label);
}
maxTextWidth = max(maxTextWidth, 5 + 30 + 5 + label.GetLength() * pdata->GetLegendFontSize()/2 + 5);
}
widLegend = min(maxLegend, max(titleWidth, maxTextWidth));
m_rectLegend.left = m_rectLegend.right - widLegend;
}
return true;
}
bool CDataChart::DrawLegend(CDC &dc)
{
if(!m_bShowLegend)
return true;
int legendL, legendT, legendR, legendB, legendWidth, nHeight;
int i, j, legendHeight, maxLegend;
CDataSeries *pdata;
CString label;
CString line;
TEXTMETRIC tm;
int *legendLine;
// Display the rectangle
CPen pen( PS_SOLID, 1, RGB(0, 0, 0)), *pPenOld;
pPenOld = dc.SelectObject( &pen );
legendHeight = 10 + 12 + 5;
legendLine = CalcLegendLine();
maxLegend = m_rectGraph.Width()*(AREA_LEGEND)/100;
for(i = 0; i < m_dataset->size(); i++)
{
pdata = (CDataSeries *)m_dataset->get(i);
if(legendLine[i] > 0)
{
legendHeight += 5 + pdata->GetLegendFontSize() + 5;
for(j = 0; j < legendLine[i] - 1; j++)
legendHeight += pdata->GetLegendFontSize() + 5;
}
}
// rectangle
legendHeight += 2;
legendL = m_rectLegend.left + 5;
legendT = min((int)((double)m_rectUsable.Height() * 0.382), m_rectGraph.top + (m_rectGraph.Height() - legendHeight)/2);
legendB = legendT + legendHeight;
legendR = m_rectLegend.right - 5;
legendWidth = legendR - legendL;
dc.Rectangle(legendL, legendT, legendR, legendB);
dc.SelectObject( pPenOld );
nHeight = 0;
// Title for "Legend"
CFont legendTitleFont;
legendTitleFont.CreateFont(16, 0, 0, 0, 700, FALSE, FALSE, 0,
ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN,"Arial");
CFont* pOldFont = (CFont*) dc.SelectObject(&legendTitleFont);
dc.GetTextMetrics(&tm);
int charWidth = tm.tmAveCharWidth;
dc.TextOut(legendL + (legendWidth / 2) - (3 * charWidth), legendT + 5, "Legend");
dc.SelectObject(pOldFont);
nHeight += 10 + 12 + 5;
for(i = 0; i < m_dataset->size(); i++)
{
pdata = (CDataSeries *)m_dataset->get(i);
label = pdata->GetLabel();
// now we draw the short line and markers
CPen pen( PS_SOLID, pdata->GetSize(), pdata->GetColor() ), *pPenOld;
CBrush brush( pdata->GetColor() ), *pBrushOld;
pPenOld = dc.SelectObject( &pen );
pBrushOld = dc.SelectObject( &brush );
dc.MoveTo(legendL + 5, legendT + nHeight + 6);
dc.LineTo(legendL + 5 + 30, legendT + nHeight + 6);
DrawLinMarker(dc, pdata, legendL + 5 + 15, legendT + nHeight + 6);
dc.SelectObject( pPenOld );
dc.SelectObject( pBrushOld );
// display the text
CFont legendFont;
legendFont.CreateFont(pdata->GetLegendFontSize(), 0, 0, 0, 700, FALSE, FALSE, 0,
ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN,"Arial");
pOldFont = (CFont*) dc.SelectObject(&legendFont);
int perLine, lineLoc;
if(legendLine[i] == 1)
{
dc.TextOut(legendL + 5 + 30 + 5, legendT + nHeight, label);
nHeight += 5 + 12 + 5;
}
else if(legendLine[i] > 1)
{
//nHeight += 5;
perLine = 2 * (int)((double)(maxLegend - (5 + 30 + 5 + 5))/(double)pdata->GetLegendFontSize());
lineLoc = 0;
for(j = 0; j < legendLine[i]; j++)
{
line = label.Mid(lineLoc, perLine);
lineLoc += line.GetLength();
// if this is the last line, and there are still some
// words remained
if(j == legendLine[i]-1 && lineLoc < label.GetLength())
{
line.SetAt(line.GetLength() - 1, '.');
line.SetAt(line.GetLength() - 2, '.');
line.SetAt(line.GetLength() - 3, '.');
line.SetAt(line.GetLength() - 4, ' ');
}
dc.TextOut(legendL + 5 + 30 + 5, legendT + nHeight, line);
nHeight += 12 + 5;
}
}
dc.SelectObject(pOldFont);
}
return true;
}
void CDataChart::DrawLinMarker(CDC &dc, CDataSeries *ds, int x, int y)
{
CPen pen( PS_SOLID, ds->GetSize(), ds->GetColor() ), *pPenOld;
CBrush brush( ds->GetColor() ), *pBrushOld;
pPenOld = dc.SelectObject( &pen );
pBrushOld = dc.SelectObject( &brush );
int m_ratio = ds->GetMarkerRatio();
POINT* pPoint = new POINT[ 5 ];
switch( ds->GetMarker() )
{
case DATASET_MARKER_TRI:
pPoint[ 0 ].x = x;
pPoint[ 0 ].y = y - ds->GetSize()*m_ratio;
pPoint[ 1 ].x = x + ds->GetSize()*m_ratio;
pPoint[ 1 ].y = y + ds->GetSize()*m_ratio;
pPoint[ 2 ].x = x - ds->GetSize()*m_ratio;
pPoint[ 2 ].y = y + ds->GetSize()*m_ratio;
dc.Polygon( pPoint, 3 );
break;
case DATASET_MARKER_BOX:
pPoint[ 0 ].x = x - ds->GetSize()*m_ratio;
pPoint[ 0 ].y = y - ds->GetSize()*m_ratio;
pPoint[ 1 ].x = x + ds->GetSize()*m_ratio;
pPoint[ 1 ].y = y - ds->GetSize()*m_ratio;
pPoint[ 2 ].x = x + ds->GetSize()*m_ratio;
pPoint[ 2 ].y = y + ds->GetSize()*m_ratio;
pPoint[ 3 ].x = x - ds->GetSize()*m_ratio;
pPoint[ 3 ].y = y + ds->GetSize()*m_ratio;
dc.Polygon( pPoint, 4 );
break;
case DATASET_MARKER_SPH:
pPoint[ 0 ].x = x - ds->GetSize()*m_ratio;
pPoint[ 0 ].y = y - ds->GetSize()*m_ratio;
pPoint[ 1 ].x = x + ds->GetSize()*m_ratio;
pPoint[ 1 ].y = y + ds->GetSize()*m_ratio;
dc.Ellipse( pPoint[0].x, pPoint[0].y, pPoint[1].x, pPoint[1].y );
break;
case DATASET_MARKER_DIA:
pPoint[ 0 ].x = x;
pPoint[ 0 ].y = y - ds->GetSize()*m_ratio;
pPoint[ 1 ].x = x + ds->GetSize()*m_ratio;
pPoint[ 1 ].y = y;
pPoint[ 2 ].x = x;
pPoint[ 2 ].y = y + ds->GetSize()*m_ratio;
pPoint[ 3 ].x = x - ds->GetSize()*m_ratio;
pPoint[ 3 ].y = y;
dc.Polygon( pPoint, 4 );
break;
default:
break;
}
delete []pPoint;
dc.SelectObject( pPenOld );
dc.SelectObject( pBrushOld );
return;
}
// Let's figure out how many lines should be displayed for
// each legend.
int * CDataChart::CalcLegendLine()
{
int *dataLines, *result;
int i, maxHeight, maxLegend, perLine, textWidth;
CDataSeries *pdata;
CString label;
bool done;
maxHeight = m_rectData.Height() - 1 - 10 - 12 - 5 -1;
maxLegend = m_rectGraph.Width()*(AREA_LEGEND)/100;
dataLines = new int[m_dataset->size()];
result = new int[m_dataset->size()];
for(i = 0; i < m_dataset->size(); i++)
{
result[i] = 0;
pdata = (CDataSeries *)m_dataset->get(i);
label = pdata->GetLabel();
textWidth = 5 + 30 + 5 + label.GetLength() * pdata->GetLegendFontSize()/2 + 5;
if(textWidth > maxLegend)
{
perLine = 2 * (int)((double)(maxLegend - (5 + 30 + 5 + 5))/(double)pdata->GetLegendFontSize());
if(label.GetLength() % perLine == 0)
dataLines[i] = label.GetLength() / perLine;
else
dataLines[i] = label.GetLength() / perLine + 1;
}
else
dataLines[i] = 1;
}
done = false;
for(i = 0; i < m_dataset->size(); i++)
{
result[i] = 1;
if(sumLegendHeight(result) > maxHeight)
{
result[i] = 0;
done = true;
break;
}
dataLines[i]--;
}
if(allLegendClear(dataLines))
done = true;
i = 0;
while(!done)
{
if(i >= m_dataset->size())
{
i = 0;
continue;
}
if(dataLines[i] > 0)
{
result[i]++;
if(sumLegendHeight(result) > maxHeight)
{
result[i]--;
done = true;
break;
}
dataLines[i]--;
}
if(allLegendClear(dataLines))
done = true;
i++;
}
delete []dataLines;
return result;
}
// Check whether all the legend can be shown
bool CDataChart::allLegendClear(int *legendLine)
{
bool done = true;
for(int i = 0; i < m_dataset->size(); i++)
{
if(legendLine[i] > 0)
done = false;
}
return done;
}
int CDataChart::sumLegendHeight(int *legendLine)
{
CDataSeries *pdata;
CString label;
int totalHeight;
totalHeight = 0;
for(int i = 0; i < m_dataset->size(); i++)
{
pdata = (CDataSeries *)m_dataset->get(i);
totalHeight += 5 + pdata->GetLegendFontSize() + 5;
for(int j = 0; j < legendLine[i]-1; j++)
totalHeight += pdata->GetLegendFontSize() + 5;
}
return totalHeight;
}
void CDataChart::DrawPie(CDC &dc)
{
// Calculate the area for title and pie(s)
GetClientRect( m_rectArea );
m_rectUsable.top = m_rectArea.top + m_rectArea.Height()/AREA_MARGINS;
m_rectUsable.bottom = m_rectArea.bottom - m_rectArea.Height()/AREA_MARGINS;
m_rectUsable.left = m_rectArea.left + m_rectArea.Width() /AREA_MARGINS;
m_rectUsable.right = m_rectArea.right - m_rectArea.Width() /AREA_MARGINS;
// let's calc everything
if( !m_strTitle.IsEmpty() ) {
m_rectTitle.top = m_rectUsable.top;
m_rectTitle.left = m_rectUsable.left;
m_rectTitle.bottom = m_rectUsable.bottom/AREA_TITLE;
m_rectTitle.right = m_rectUsable.right;
m_rectGraph.top = m_rectTitle.bottom;
m_rectGraph.left = m_rectUsable.left;
m_rectGraph.bottom = m_rectUsable.bottom;
m_rectGraph.right = m_rectUsable.right;
} else {
m_rectGraph = m_rectUsable;
}
PaintBkGnd( dc );
// display title
DrawTitle(dc);
int matrixX, matrixY, pieX, pieY, xDiv, yDiv;
CDataSeries *pdata;
CRect rect;
matrixY = (int)sqrt(m_dataset->size());
if(m_dataset->size() % matrixY == 0)
matrixX = m_dataset->size()/matrixY;
else
matrixX = m_dataset->size()/matrixY + 1;
xDiv = m_rectGraph.Width()/matrixX;
yDiv = m_rectGraph.Height()/matrixY;
for(int i = 0; i < m_dataset->size(); i++)
{
pdata = (CDataSeries *)m_dataset->get(i);
pieX = i % matrixX;
pieY = i / matrixX;
rect.top = m_rectGraph.top + pieY*yDiv;
rect.bottom = rect.top + yDiv;
rect.left = m_rectGraph.left + pieX*xDiv;
rect.right = rect.left + xDiv;
DrawPieDataset(dc, pdata, rect);
}
}
void CDataChart::DrawPieDataset(CDC &dc, CDataSeries *ds, CRect rectArea)
{
double *Slices, total, yValue, dataSum, degree, labelDegree;
int i, xValue, radius, clrRound;
int lastXLocation, lastYLocation, newXLocation, newYLocation, centreX, centreY;
int labelX, labelY;
CRect pieRect;
CRect titleRect;
CRect labelRect;
COLORREF barColor;
CPen* pOldPen;
CBrush* pOldBrush;
CString label;
int numLess;
// Normalization
total = 0;
Slices = (double *) new double[ds->GetDatasetSize()];
for(i = 0; i < ds->GetDatasetSize(); i++)
{
ds->Get(i, xValue, yValue);
Slices[i] = yValue;
total += yValue;
}
for(i = 0; i < ds->GetDatasetSize(); i++)
Slices[i] /= total;
// draw title
titleRect.bottom = rectArea.bottom;
titleRect.right = rectArea.right;
titleRect.left = rectArea.left;
titleRect.top = rectArea.bottom - 2 - 12 - 2;
//draw normal pie
centreX = rectArea.left + rectArea.Width()/2;
centreY = rectArea.top + (rectArea.Height()-titleRect.Height())/2;
radius = (min(rectArea.Height()-titleRect.Height(), rectArea.Width()) - 60)/2;
titleRect.top = min(titleRect.top, centreY + radius + 2 + 12 + 2);
DrawText(dc, ds->GetLabel(), titleRect, 12, DT_CENTER);
lastXLocation = centreX + radius;
lastYLocation = centreY;
pieRect.top = centreY - radius;
pieRect.left = centreX - radius;
pieRect.bottom = centreY + radius;
pieRect.right = centreX + radius;
dataSum = 0.0;
clrRound = 0;
numLess = 0;
for(i = 0; i < ds->GetDatasetSize(); i++)
{
ds->Get(i, xValue, yValue);
if(Slices[i] < 3.1415926535/720.0)
{
numLess++;
continue;
}
labelDegree = (dataSum + Slices[i]/2.0)*2.0*3.1415926535;
dataSum += Slices[i];
degree = dataSum*2.0*3.1415926535;
newXLocation = centreX + (int)((double)radius*cos(degree));
newYLocation = centreY - (int)((double)radius*sin(degree));
if(clrRound >= COLOR_NUM)
clrRound = 0;
barColor = clrList[clrRound];
clrRound++;
CPoint p1 (lastXLocation, lastYLocation);
CPoint p2 (newXLocation, newYLocation);
CBrush brush (barColor);
CPen piePen (PS_SOLID, 1, barColor);
pOldPen = dc.SelectObject(&piePen);
pOldBrush = dc.SelectObject(&brush);
dc.Pie(pieRect, p1, p2);
dc.SelectObject(pOldBrush);
dc.SelectObject(pOldPen);
// Now we draw the label for each data slice.
labelX = centreX + (int)((double)radius*cos(labelDegree));
labelY = centreY - (int)((double)radius*sin(labelDegree));
label.Format("%d", xValue);
if(labelX >= centreX && labelY <= centreY)
{
// first Quadrant
labelRect.right = rectArea.right;
labelRect.bottom = labelY;
labelRect.top = labelY - 12;
labelRect.left = labelX;
DrawText(dc, label, labelRect, 12, DT_LEFT);
}
else if(labelX < centreX && labelY <= centreY)
{
// second Quadrant
labelRect.right = labelX;
labelRect.bottom = labelY;
labelRect.top = labelY - 12;
labelRect.left = rectArea.left;
DrawText(dc, label, labelRect, 12, DT_RIGHT);
}
else if(labelX < centreX && labelY > centreY)
{
// third Quadrant
labelRect.left = rectArea.left;
labelRect.right = labelX;
labelRect.top = labelY;
labelRect.bottom = labelY + 12;
DrawText(dc, label, labelRect, 12, DT_RIGHT);
}
else if(labelX >= centreX && labelY > centreY)
{
// fourth Quadrant
labelRect.left = labelX;
labelRect.right = rectArea.right;
labelRect.top = labelY;
labelRect.bottom = labelY + 12;
DrawText(dc, label, labelRect, 12, DT_LEFT);
}
lastXLocation = newXLocation;
lastYLocation = newYLocation;
}
if(lastXLocation != centreX + radius && lastYLocation != centreY)
{
CPoint p1 (lastXLocation, lastYLocation);
CPoint p2 (centreX + radius, centreY);
CBrush brush (BLACK);
CPen piePen (PS_SOLID, 1, BLACK);
pOldPen = dc.SelectObject(&piePen);
pOldBrush = dc.SelectObject(&brush);
dc.Pie(pieRect, p1, p2);
dc.SelectObject(pOldBrush);
dc.SelectObject(pOldPen);
labelRect.left = centreX + radius;
labelRect.right = rectArea.right;
labelRect.top = centreY;
labelRect.bottom = centreY + 12;
label.Format("%ds'", numLess);
DrawText(dc, label, labelRect, 12, DT_LEFT);
}
delete Slices;
}
void CDataChart::DrawText(CDC &dc, CString textValue, CRect rectArea, int fontSize, UINT format)
{
CString textBuf;
const int nBkModeOld = dc.SetBkMode( TRANSPARENT );
COLORREF clrBlack = RGB( 0, 0, 0), clrOld;
CPen textPen(PS_SOLID, 3, clrBlack ), *pPenOld;
CFont textFont, *pFontOld;
int maxWord;
maxWord = rectArea.Width()/(fontSize/2 - 1);
textFont.CreateFont( fontSize, 0, 0, 0, FW_NORMAL,
FALSE, FALSE, FALSE, ANSI_CHARSET,
OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY,
DEFAULT_PITCH, "Arial");
pPenOld = dc.SelectObject(&textPen);
pFontOld = dc.SelectObject(&textFont);
clrOld = dc.SetTextColor( clrBlack );
if(textValue.GetLength() > maxWord)
{
textBuf = textValue.Left(maxWord);
if(textBuf.GetLength() > 10)
{
textBuf.SetAt(maxWord-1, '.');
textBuf.SetAt(maxWord-2, '.');
textBuf.SetAt(maxWord-3, '.');
textBuf.SetAt(maxWord-4, ' ');
}
else
{
textBuf.SetAt(maxWord-1, '.');
}
}
else
textBuf = textValue;
dc.DrawText( textBuf, rectArea, format | DT_TOP | DT_SINGLELINE );
dc.SelectObject(pPenOld);
dc.SelectObject(pFontOld);
dc.SetTextColor(clrOld);
dc.SetBkMode(nBkModeOld);
return;
}
void CDataChart::DrawLine(CDC &dc)
{
CDataSeries *pdata;
CPen *pPenOld;
PaintBkGnd( dc );
DrawTitle( dc );
// Draw Axes
// draw Y
dc.MoveTo( m_rectYAxis.right, m_rectYAxis.bottom );
dc.LineTo( m_rectYAxis.right, m_rectYAxis.top );
// draw X
dc.MoveTo( m_rectXAxis.left , m_rectXAxis.top );
dc.LineTo( m_rectXAxis.right, m_rectXAxis.top );
if(m_bShowYScale)
DrawYScale( dc );
if(m_bShowXScale)
DrawXScale( dc );
// Draw Grid
if(m_bShowGrid)
{
DrawHorzLine( dc );
DrawVertLine( dc );
}
// Draw Base line
if( m_nYMin < 0 && m_bShowBaseLine)
{
CPen baseLinePen( PS_SOLID, 1, RGB(0,0,0));
double nTemp = ( - m_nYMin) * m_rectData.Height()/(m_nYMax-m_nYMin); // this is the zero baseline
pPenOld = dc.SelectObject( &baseLinePen );
dc.MoveTo( m_rectData.left , m_rectData.bottom - (int)nTemp );
dc.LineTo( m_rectData.right, m_rectData.bottom - (int)nTemp );
dc.SelectObject( pPenOld );
}
// Draw dataset from last to first so I can show
// first dataset in foreground, below the second dataset and so on
for( int i = m_dataset->size() - 1; i>=0; i--)
{
pdata = (CDataSeries *)m_dataset->get(i);
DrawDataset( dc, pdata );
}
if(m_bShowLegend)
DrawLegend(dc);
}