ASE Home Page Products Download Purchase Support About ASE
ChartDirector Support
Forum HomeForum Home   SearchSearch

Message ListMessage List     Post MessagePost Message

  How to scroll & zoom real time charts
Posted by Sai on Nov-09-2012 21:36
Attachments:
I am writing application MFC.
I able add,zoom & scroll fixed size data.
Even I able to add dynamic size data i.e. real time data. I able to add scroll & its scroll bar info. But how do I scroll the real time data & how do I add zoom feature to real time charts.
I am attaching code & let me know what changes should I do
CRealtimedemoDlg.cpp
// realtimedemoDlg.cpp : implementation file
//

#include "stdafx.h"
#include "ExactChart.h"
#include "realtimedemoDlg.h"
#include <math.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

/////////////////////////////////////////////////////////////////////////////
// CRealtimedemoDlg dialog

static const int DataRateTimer = 1;
static const int ChartUpdateTimer = 2;
static const int DataInterval = 250;

//
// Constructor
//
CRealtimedemoDlg::CRealtimedemoDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CRealtimedemoDlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CRealtimedemoDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CRealtimedemoDlg)
    DDX_Control(pDX, IDC_GammaValue, m_ValueC);
    DDX_Control(pDX, IDC_BetaValue, m_ValueB);
    DDX_Control(pDX, IDC_AlphaValue, m_ValueA);
    DDX_Control(pDX, IDC_ChartViewer, m_ChartViewer);
    DDX_Control(pDX, IDC_RunPB, m_RunPB);
    DDX_Control(pDX, IDC_UpdatePeriod, m_UpdatePeriod);
    DDX_Control(pDX, IDC_HScrollBar, m_HScrollBar);
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CRealtimedemoDlg, CDialog)
    //{{AFX_MSG_MAP(CRealtimedemoDlg)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_WM_TIMER()
    ON_WM_DESTROY()
    ON_BN_CLICKED(IDC_RunPB, OnRunPB)
    ON_BN_CLICKED(IDC_FreezePB, OnFreezePB)
    ON_CBN_SELCHANGE(IDC_UpdatePeriod, OnSelchangeUpdatePeriod)
    ON_CONTROL(CVN_ViewPortChanged, IDC_ChartViewer, OnViewPortChanged)
    ON_WM_MOUSEWHEEL()
    //}}AFX_MSG_MAP
	ON_WM_HSCROLL()
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CRealtimedemoDlg message handlers

//
// Initialization
//
BOOL CRealtimedemoDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // *** code automatically generated by VC++ MFC AppWizard ***
    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);         // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon
    
    //
    // Initialize member variables
    //
    m_extBgColor = getDefaultBgColor();     // Default background color

    // Clear data arrays to Chart::NoValue
    //for (int i = 0; i < sampleSize; ++i)
    //    m_timeStamps[i] = m_dataSeriesA[i] = m_dataSeriesB[i] = m_dataSeriesC[i] = Chart::NoValue;
	

    // Set m_nextDataTime to the current time. It is used by the real time random number 
    // generator so it knows what timestamp should be used for the next data point.
    SYSTEMTIME st;
    GetLocalTime(&st);
    m_nextDataTime = Chart::chartTime(st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, 
        st.wSecond) + st.wMilliseconds / 1000.0;

    //
    // Initialize controls
    //

    // Set up the data acquisition mechanism. In this demo, we just use a timer to get a 
    // sample every 250ms.
    SetTimer(DataRateTimer, DataInterval, 0);

    // The chart update rate (in ms)
    m_UpdatePeriod.SelectString(0, _T("1000"));
    
    // Load icons for the Run/Freeze buttons
    //loadButtonIcon(IDC_RunPB, IDI_RunPB, 100, 20);
    //loadButtonIcon(IDC_FreezePB, IDI_FreezePB, 100, 20);

    // Initially set the Run mode
    m_RunPB.SetCheck(1);
    OnRunPB();

    return TRUE;
}

// *** code automatically generated by VC++ MFC AppWizard ***
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon.  For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CRealtimedemoDlg::OnPaint() 
{
    if (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialog::OnPaint();
    }
}

// *** code automatically generated by VC++ MFC AppWizard ***
// The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CRealtimedemoDlg::OnQueryDragIcon()
{
    return (HCURSOR) m_hIcon;
}

//
// User clicks on the Run pushbutton
//
void CRealtimedemoDlg::OnRunPB() 
{
    // Enable chart update timer
    CString s;
    m_UpdatePeriod.GetLBText(m_UpdatePeriod.GetCurSel(), s);
    SetTimer(ChartUpdateTimer, _tcstol(s, 0, 0), 0);
}

//
// User clicks on the Freeze pushbutton
//
void CRealtimedemoDlg::OnFreezePB() 
{
    // Disable chart update timer
    KillTimer(ChartUpdateTimer);    
}

//
// Handles timer events
//
void CRealtimedemoDlg::OnTimer(UINT_PTR nIDEvent) 
{
    switch (nIDEvent)
    {
    case DataRateTimer:
        // Is data acquisition timer - get a new data sample
        getData();
        break;
    case ChartUpdateTimer:
        // Is chart update timer - request chart update
        m_ChartViewer.updateViewPort(true, false);      
		initChartViewer(&m_ChartViewer);
        break;
    }
    
    CDialog::OnTimer(nIDEvent);
}

//
// View port changed event
//
void CRealtimedemoDlg::OnViewPortChanged()
{
     // In addition to updating the chart, we may also need to update other controls that
    // changes based on the view port.
    updateControls(&m_ChartViewer);

    // Update the chart if necessary
    if (m_ChartViewer.needUpdateChart())
        drawChart(&m_ChartViewer);

    // We need to update the track line too. If the mouse is moving on the chart (eg. if 
    // the user drags the mouse on the chart to scroll it), the track line will be updated
    // in the MouseMovePlotArea event. Otherwise, we need to update the track line here.
	//if (!m_ChartViewer.isInMouseMoveEvent())//if i enable the code it will get crash
	//{
 //       //trackLineLegend((XYChart *)m_ChartViewer.getChart(), m_ChartViewer.getPlotAreaMouseX());
	//	if(m_ChartViewer.getChart())
	//		m_ChartViewer.updateDisplay();
 //   }
}

//
// User changes the chart update period
//
void CRealtimedemoDlg::OnSelchangeUpdatePeriod() 
{
    if (m_RunPB.GetCheck())
    {
        // Call freeze then run to use the new chart update period
        OnFreezePB();
        OnRunPB();
    }   
}

/////////////////////////////////////////////////////////////////////////////
// CRealtimedemoDlg methods

//
// A utility to shift a new data value into a data array
//
static void shiftData(double *data, int len, double newValue)
{
    memmove(data, data + 1, sizeof(*data) * (len - 1));
    data[len - 1] = newValue;
}

//
// The data acquisition routine. In this demo, this is invoked every 250ms.
//
void CRealtimedemoDlg::getData()
{
    // The current time in millisecond resolution
    SYSTEMTIME st;
    GetLocalTime(&st);
    double now = Chart::chartTime(st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, 
        st.wSecond) + st.wMilliseconds / 1000.0;
    
    // This is our formula for the random number generator
    do
    {
        // Get a data sample
        double p = m_nextDataTime * 4;
        double dataA = 20 + cos(p * 129241) * 10 + 1 / (cos(p) * cos(p) + 0.01);
        double dataB = 150 + 100 * sin(p / 27.7) * sin(p / 10.1);
        double dataC = 150 + 100 * cos(p / 6.7) * cos(p / 11.9);

        // Shift the values into the arrays
		m_dataSeriesA.push_back(dataA);
		m_dataSeriesB.push_back(dataB);
		m_dataSeriesC.push_back(dataC);
		m_timeStamps.push_back(m_nextDataTime);

        m_nextDataTime += DataInterval / 1000.0;
    }
    while (m_nextDataTime < now);   
}

//
// Draw the chart and display it in the given viewer
//
void CRealtimedemoDlg::drawChart(CChartViewer *viewer)
{
	 int startIndex = (int)floor(viewer->getValueAtViewPort("x", viewer->getViewPortLeft()));
	int endIndex = (int)ceil(viewer->getValueAtViewPort("x", viewer->getViewPortLeft() +
        viewer->getViewPortWidth()));
    int noOfPoints = endIndex - startIndex + 1;
	

    // Create an XYChart object 600 x 270 pixels in size, with light grey (f4f4f4) 
    // background, black (000000) border, 1 pixel raised effect, and with a rounded frame.
    XYChart *c = new XYChart(600, 270, 0xf4f4f4, 0x000000, 1);
	c->setRoundedFrame(m_extBgColor);
	
    
    // Set the plotarea at (55, 62) and of size 520 x 175 pixels. Use white (ffffff) 
    // background. Enable both horizontal and vertical grids by setting their colors to 
    // grey (cccccc). Set clipping mode to clip the data lines to the plot area.
    c->setPlotArea(55, 62, 520, 175, 0xffffff, -1, -1, 0xcccccc, 0xcccccc);
    c->setClipping();

    // Add a title to the chart using 15 pts Times New Roman Bold Italic font, with a light
    // grey (dddddd) background, black (000000) border, and a glass like raised effect.
    c->addTitle("Field Intensity at Observation Satellite", "timesbi.ttf", 15
        )->setBackground(0xdddddd, 0x000000, Chart::glassEffect());
            
    // Add a legend box at the top of the plot area with 9pts Arial Bold font. We set the 
    // legend box to the same width as the plot area and use grid layout (as opposed to 
    // flow or top/down layout). This distributes the 3 legend icons evenly on top of the 
    // plot area.
    LegendBox *b = c->addLegend2(55, 33, 3, "arialbd.ttf", 9);
    b->setBackground(Chart::Transparent, Chart::Transparent);
    b->setWidth(520);

    // Configure the y-axis with a 10pts Arial Bold axis title
    c->yAxis()->setTitle("Intensity (V/m)", "arialbd.ttf", 10);

    // Configure the x-axis to auto-scale with at least 75 pixels between major tick and 
    // 15  pixels between minor ticks. This shows more minor grid lines on the chart.
    c->xAxis()->setTickDensity(75, 15);

    // Set the axes width to 2 pixels
    c->xAxis()->setWidth(2);
    c->yAxis()->setWidth(2);

	int nSize = m_dataSeriesA.size();
    // Now we add the data to the chart. 
    if (nSize)
    {
		int nStartIndex = 0;
		if(nSize > sampleSize)
			nStartIndex = nSize - sampleSize;
		double lastTime = m_timeStamps[nSize - 1];
		
        // Set up the x-axis to show the time range in the data buffer
		if(nSize < sampleSize)
			c->xAxis()->setDateScale(lastTime - DataInterval * (sampleSize) / 1000, lastTime);
		else
			c->xAxis()->setDateScale(lastTime - DataInterval * (nSize - nStartIndex) / 1000, lastTime);
        
        // Set the x-axis label format
        c->xAxis()->setLabelFormat("{value|hh:nn:ss:fff}");

        // Create a line layer to plot the lines
        LineLayer *layer = c->addLineLayer();
		

        // The x-coordinates are the timeStamps.
		layer->setXData(DoubleArray(&m_timeStamps[nStartIndex], nSize - nStartIndex));

        // The 3 data series are used to draw 3 lines. Here we put the latest data values
        // as part of the data set name, so you can see them updated in the legend box.
        char buffer[1024];
        sprintf(buffer, "Alpha: <*bgColor=FFCCCC*> %.2f ", m_dataSeriesA[nSize - 1]);
        layer->addDataSet(DoubleArray(&m_dataSeriesA[nStartIndex], nSize - nStartIndex), 0xff0000, buffer);
        
        sprintf(buffer, "Beta: <*bgColor=CCFFCC*> %.2f ", m_dataSeriesB[nSize - 1]);
		layer->addDataSet(DoubleArray(&m_dataSeriesB[nStartIndex], nSize - nStartIndex), 0x00cc00, buffer);
        
        sprintf(buffer, "Gamma: <*bgColor=CCCCFF*> %.2f ", m_dataSeriesC[nSize - 1]);
        layer->addDataSet(DoubleArray(&m_dataSeriesC[nStartIndex], nSize - nStartIndex), 0x0000ff, buffer);
    }

    // Set the chart image to the WinChartViewer
    viewer->setChart(c);
    delete c;
}

/////////////////////////////////////////////////////////////////////////////
// General utilities

//
// Get the default background color
//
int CRealtimedemoDlg::getDefaultBgColor()
{
    LOGBRUSH LogBrush; 
    HBRUSH hBrush = (HBRUSH)SendMessage(WM_CTLCOLORDLG, (WPARAM)CClientDC(this).m_hDC, 
        (LPARAM)m_hWnd); 
    ::GetObject(hBrush, sizeof(LOGBRUSH), &LogBrush); 
    int ret = LogBrush.lbColor;
    return ((ret & 0xff) << 16) | (ret & 0xff00) | ((ret & 0xff0000) >> 16);
}

//
// Load an icon resource into a button
//
void CRealtimedemoDlg::loadButtonIcon(int buttonId, int iconId, int width, int height)
{
    GetDlgItem(buttonId)->SendMessage(BM_SETIMAGE, IMAGE_ICON, (LPARAM)::LoadImage(
        AfxGetResourceHandle(), MAKEINTRESOURCE(iconId), IMAGE_ICON, width, height, 
        LR_DEFAULTCOLOR));  
}

void CRealtimedemoDlg::initChartViewer(CChartViewer *viewer)
{
    // Set the full x range to be the duration of the data
	int nMaxlen = m_timeStamps.size() < sampleSize ? sampleSize : m_timeStamps.size();
    viewer->setFullRange("x", 0, nMaxlen);

    // Initialize the view port to show the latest 20% of the time range
    viewer->setViewPortWidth(0.1);
    viewer->setViewPortLeft(1 - viewer->getViewPortWidth());

    // Set the maximum zoom to 10 points
    //viewer->setZoomInWidthLimit(10.0 / nMaxlen);

    // Initially set the mouse to drag to scroll mode.
    //m_PointerPB.SetCheck(1);
    viewer->setMouseUsage(Chart::MouseUsageScroll);
}

BOOL CRealtimedemoDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	// Process the mouse wheel only if the mouse is over the plot area
	if (!m_ChartViewer.isMouseOnPlotArea())
		return FALSE;

    // We zoom in or out by 10% depending on the mouse wheel direction.
	double newVpWidth = m_ChartViewer.getViewPortWidth() * (zDelta > 0 ? 0.9 : 1 / 0.9);
	double newVpHeight = m_ChartViewer.getViewPortHeight() * (zDelta > 0 ? 0.9 : 1 / 0.9);

    // We do not zoom beyond the zoom width or height limits.
    newVpWidth = max(m_ChartViewer.getZoomInWidthLimit(), min(newVpWidth,
        m_ChartViewer.getZoomOutWidthLimit()));
    newVpHeight = max(m_ChartViewer.getZoomInHeightLimit(), min(newVpWidth,
        m_ChartViewer.getZoomOutHeightLimit()));
	
	if ((newVpWidth != m_ChartViewer.getViewPortWidth()) || 
		(newVpHeight != m_ChartViewer.getViewPortHeight()))
	{
		// Set the view port position and size so that the point under the mouse remains under
		// the mouse after zooming.

		double deltaX = (m_ChartViewer.getPlotAreaMouseX() - m_ChartViewer.getPlotAreaLeft()) * 
			(m_ChartViewer.getViewPortWidth() - newVpWidth) / m_ChartViewer.getPlotAreaWidth();
		m_ChartViewer.setViewPortLeft(m_ChartViewer.getViewPortLeft() + deltaX);
		m_ChartViewer.setViewPortWidth(newVpWidth);

		double deltaY = (m_ChartViewer.getPlotAreaMouseY() - m_ChartViewer.getPlotAreaTop()) *
			(m_ChartViewer.getViewPortHeight() - newVpHeight) / m_ChartViewer.getPlotAreaHeight();
		m_ChartViewer.setViewPortTop(m_ChartViewer.getViewPortTop() + deltaY);
		m_ChartViewer.setViewPortHeight(newVpHeight);

	    m_ChartViewer.updateViewPort(true, false);
	}

	return TRUE;
}

void CRealtimedemoDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	double newViewPortLeft = moveScrollBar(nSBCode, nPos, pScrollBar);

	// Update the view port if the scroll bar has really moved
	if (newViewPortLeft != m_ChartViewer.getViewPortLeft()) 
	{
		m_ChartViewer.setViewPortLeft(moveScrollBar(nSBCode, nPos, pScrollBar));
		m_ChartViewer.updateViewPort(true, false);
	}
}

//
// Handle scroll bar events
//
double CRealtimedemoDlg::moveScrollBar(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    //
    // Get current scroll bar position
    //
    SCROLLINFO info;
    info.cbSize = sizeof(SCROLLINFO);
    info.fMask = SIF_ALL;
    pScrollBar->GetScrollInfo(&info);

    //
    // Compute new position based on the type of scroll bar events
    //
    int newPos = info.nPos;
    switch (nSBCode)
    {
    case SB_LEFT:
        newPos = info.nMin;
        break;
    case SB_RIGHT:
        newPos = info.nMax;
        break;
    case SB_LINELEFT:
        newPos -= (info.nPage > 10) ? info.nPage / 10 : 1;
        break;
    case SB_LINERIGHT:
        newPos += (info.nPage > 10) ? info.nPage / 10 : 1;
        break;
    case SB_PAGELEFT:
        newPos -= info.nPage;
        break;
    case SB_PAGERIGHT:
        newPos += info.nPage;
        break;
    case SB_THUMBTRACK:
        newPos = info.nTrackPos;
        break;
    }
    if (newPos < info.nMin) newPos = info.nMin;
    if (newPos > info.nMax) newPos = info.nMax;
    
    // Update the scroll bar with the new position
    pScrollBar->SetScrollPos(newPos);

    // Returns the position of the scroll bar as a ratio of its total length
    return ((double)(newPos - info.nMin)) / (info.nMax - info.nMin);
}

//
// Initialize the CChartViewer 
//
void CRealtimedemoDlg::updateControls(CChartViewer *viewer)
{
    // In this demo, we need to update the scroll bar to reflect the view port position and
    // width of the view port.

    m_HScrollBar.EnableWindow(viewer->getViewPortWidth() < 1);
    if (viewer->getViewPortWidth() < 1)
    {
        SCROLLINFO info;
        info.cbSize = sizeof(SCROLLINFO);
        info.fMask = SIF_ALL;
        info.nMin = 0;
        info.nMax = 0x1fffffff;
	    info.nPage = (int)ceil(viewer->getViewPortWidth() * (info.nMax - info.nMin));
        info.nPos = (int)(0.5 + viewer->getViewPortLeft() * (info.nMax - info.nMin)) + info.nMin;
        m_HScrollBar.SetScrollInfo(&info);
    }
}
CRealtimedemoDlg.h
// realtimedemoDlg.h : header file
//

#pragma once

#include "ChartViewer.h"
#include <afxmt.h>
#include "stdafx.h"

// The number of samples per data series used in this demo
const int sampleSize = 200;


// CRealtimedemoDlg dialog
class CRealtimedemoDlg : public CDialog
{
// Construction
public:
	CRealtimedemoDlg(CWnd* pParent = NULL);	// standard constructor

// Dialog Data
	enum { IDD = IDD_REALTIMEDEMO_DIALOG };
	CStatic	m_ValueC;
	CStatic	m_ValueB;
	CStatic	m_ValueA;
	CChartViewer m_ChartViewer;
	CButton	m_RunPB;
	CSpinButtonCtrl	m_AlarmSpin;
	CComboBox	m_UpdatePeriod;

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV support


// Implementation
protected:
	HICON m_hIcon;

	// Generated message map functions
	virtual BOOL OnInitDialog();
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	afx_msg void OnTimer(UINT_PTR nIDEvent);
	afx_msg void OnRunPB();
	afx_msg void OnFreezePB();
	afx_msg void OnSelchangeUpdatePeriod();
	afx_msg void OnViewPortChanged();
	DECLARE_MESSAGE_MAP()


private:
	//double m_timeStamps[sampleSize];	// The timestamps for the data series
	//double m_dataSeriesA[sampleSize];	// The values for the data series A
	//double m_dataSeriesB[sampleSize];	// The values for the data series B
	//double m_dataSeriesC[sampleSize];	// The values for the data series C
	vector<double> m_timeStamps;	// The timestamps for the data series
	vector<double> m_dataSeriesA;	// The values for the data series A
	vector<double> m_dataSeriesB;	// The values for the data series B
	vector<double> m_dataSeriesC;	// The values for the data series C

	double m_nextDataTime;	// Used by the random number generator to generate real time data.
	int m_extBgColor;		// The default background color.

	// Shift new data values into the real time data series 
	void getData();
	
	// Draw chart
	void drawChart(CChartViewer *viewer);

	// utility to get default background color
	int getDefaultBgColor();
	// utility to load icon resource to a button
	void loadButtonIcon(int buttonId, int iconId, int width, int height);
	void initChartViewer(CChartViewer *viewer);
	BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
public:
	afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
	double moveScrollBar(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
	CScrollBar m_HScrollBar;
	void updateControls(CChartViewer *viewer);
};

  Re: How to scroll & zoom real time charts
Posted by Peter Kwan on Nov-10-2012 02:09
Attachments:
Hi Sai,

Realtime charts in ChartDirector is quite simple. So I suggest you to start with a zoomable and scrollable chart, like the "Zooming and Scrolling with Track Line" sample code, and modify it to use realtime data.

In the original "Zooming and Scrolling with Track Line", the data arrays are filled with random numbers. To use realtime data, you just need to fill the data arrays with Chart::NoValue. Then when the realtime data comes in, you can modify the data array to fill it with your actual data.

If you want the chart to automatically scroll as new data comes in, you can add code to move the view port when the data comes in. If you want to restrict the user to scroll only to the region that has data (instead of scrolling to the empty part of the array), you can add code in OnViewPortChanged to check if the view port is within the data range, and move the view port back to within the data range if necessary

I have attached two files from the original "Zooming and Scrolling with Track Line" sample code, modified to use Realtime data. They are designed to automatically scroll the chart if the user has scrolled the chart to the rightmost position, otherwise it will not automatically scroll the chart as new data comes in (so that the user can view historical data easily). There are also code to restrict the user from scrolling to the "future range" where the data have not yet come in.

To try the attached files, simply use them to replace the files of the same name in the original "Zooming and Scrolling with Track Line" sample code.

Hope this can help.

Regards
Peter Kwan
zoomscrolltrackDlg.cpp
// zoomscrolltrackDlg.cpp : implementation file
//

#include "stdafx.h"
#include "zoomscrolltrack.h"
#include "zoomscrolltrackDlg.h"
#include "chartdir.h"
#include <math.h>
#include <vector>
#include <sstream>
#include <algorithm>

using namespace std;

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

/////////////////////////////////////////////////////////////////////////////
// CZoomscrolltrackDlg dialog


// The number of samples per data series used in this demo
static const int sampleSize = 10001;
static const int DataInterval = 1000;

//
// Constructor
//
CZoomscrolltrackDlg::CZoomscrolltrackDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CZoomscrolltrackDlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

//
// Destructor
//
CZoomscrolltrackDlg::~CZoomscrolltrackDlg()
{
    delete m_ChartViewer.getChart();
}

void CZoomscrolltrackDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CZoomscrolltrackDlg)
    DDX_Control(pDX, IDC_PointerPB, m_PointerPB);
    DDX_Control(pDX, IDC_HScrollBar, m_HScrollBar);
    DDX_Control(pDX, IDC_ChartViewer, m_ChartViewer);
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CZoomscrolltrackDlg, CDialog)
    //{{AFX_MSG_MAP(CZoomscrolltrackDlg)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_WM_DESTROY()
    ON_BN_CLICKED(IDC_PointerPB, OnPointerPB)
    ON_BN_CLICKED(IDC_ZoomInPB, OnZoomInPB)
    ON_BN_CLICKED(IDC_ZoomOutPB, OnZoomOutPB)
    ON_WM_HSCROLL()
    ON_WM_MOUSEWHEEL()
    ON_WM_TIMER()
    ON_CONTROL(CVN_ViewPortChanged, IDC_ChartViewer, OnViewPortChanged)
    ON_CONTROL(CVN_MouseMovePlotArea, IDC_ChartViewer, OnMouseMovePlotArea)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CZoomscrolltrackDlg message handlers

//
// Initialization
//
BOOL CZoomscrolltrackDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // *** code automatically generated by VC++ MFC AppWizard ***
    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);         // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon
    
    // Load icons to mouse usage buttons
    loadButtonIcon(IDC_PointerPB, IDI_PointerPB, 100, 20);  
    loadButtonIcon(IDC_ZoomInPB, IDI_ZoomInPB, 100, 20);    
    loadButtonIcon(IDC_ZoomOutPB, IDI_ZoomOutPB, 100, 20);

	// The data arrays
	m_currentIndex = 0;
	m_timeStamps.resize(sampleSize, Chart::NoValue);
	m_dataSeriesA.resize(sampleSize, Chart::NoValue);
	m_dataSeriesB.resize(sampleSize, Chart::NoValue);
	m_dataSeriesC.resize(sampleSize, Chart::NoValue);

    // Set m_nextDataTime to the current time. It is used by the real time random number 
    // generator so it knows what timestamp should be used for the next data point.
    SYSTEMTIME st;
    GetLocalTime(&st);
    m_nextDataTime = Chart::chartTime(st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, 
        st.wSecond) + st.wMilliseconds / 1000.0;

	// The timer to get the random data
    SetTimer(1, DataInterval, 0);

	// Initialize the CChartViewer
    initChartViewer(&m_ChartViewer);

    // Trigger the ViewPortChanged event to draw the chart
    m_ChartViewer.updateViewPort(true, true);
    return TRUE;
}

// *** code automatically generated by VC++ MFC AppWizard ***
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon.  For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CZoomscrolltrackDlg::OnPaint() 
{
    if (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialog::OnPaint();
    }
}

//
// Handles timer events
//
void CZoomscrolltrackDlg::OnTimer(UINT_PTR nIDEvent) 
{
    // The current time in millisecond resolution
    SYSTEMTIME st;
    GetLocalTime(&st);
    double now = Chart::chartTime(st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, 
        st.wSecond) + st.wMilliseconds / 1000.0;
    
    // This is our formula for the random number generator
    do
    {
        // Get a data sample
        double p = m_nextDataTime * 4;
        double dataA = 20 + cos(p * 129241) * 10 + 1 / (cos(p) * cos(p) + 0.01);
        double dataB = 150 + 100 * sin(p / 27.7) * sin(p / 10.1);
        double dataC = 150 + 100 * cos(p / 6.7) * cos(p / 11.9);

        // After obtaining the new values, we need to update the data arrays.
        if (m_currentIndex < sampleSize)
        {
            // Store the new values in the current index position, and increment the index.
            m_dataSeriesA[m_currentIndex] = dataA;
            m_dataSeriesB[m_currentIndex] = dataB;
            m_dataSeriesC[m_currentIndex] = dataC;
            m_timeStamps[m_currentIndex] = m_nextDataTime;

			// Auto-scroll view port if it is at the last point
			if (m_currentIndex - 1 == (int)ceil(m_ChartViewer.getValueAtViewPort("x", m_ChartViewer.getViewPortLeft() +
				m_ChartViewer.getViewPortWidth())))
				m_ChartViewer.setViewPortLeft(((double)m_currentIndex) / (sampleSize - 1) - m_ChartViewer.getViewPortWidth());		
				
            ++m_currentIndex;
        }

		m_nextDataTime += DataInterval / 1000.0;
    }
    while (m_nextDataTime < now);

    m_ChartViewer.updateViewPort(true, false);      
    CDialog::OnTimer(nIDEvent);
}

// *** code automatically generated by VC++ MFC AppWizard ***
// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CZoomscrolltrackDlg::OnQueryDragIcon()
{
    return (HCURSOR) m_hIcon;
}

//
// User clicks on the Pointer pushbutton
//
void CZoomscrolltrackDlg::OnPointerPB() 
{
    m_ChartViewer.setMouseUsage(Chart::MouseUsageScroll);   
}

//
// User clicks on the Zoom In pushbutton
//
void CZoomscrolltrackDlg::OnZoomInPB() 
{
    m_ChartViewer.setMouseUsage(Chart::MouseUsageZoomIn);   
}

//
// User clicks on the Zoom Out pushbutton
//
void CZoomscrolltrackDlg::OnZoomOutPB() 
{
    m_ChartViewer.setMouseUsage(Chart::MouseUsageZoomOut);  
}

//
// The ViewPortChanged event handler. This event occurs if the user scrolls or zooms in or 
// out the chart by dragging or clicking on the chart. It can also be triggered by calling
// CChartViewer.updateViewPort.
//
void CZoomscrolltrackDlg::OnViewPortChanged()
{
	// Make sure the chart is within the range that has data (0 to currentIndex)
	if (m_ChartViewer.getViewPortLeft() + m_ChartViewer.getViewPortWidth() > ((double)m_currentIndex) / (sampleSize - 1))
		m_ChartViewer.setViewPortLeft(max(0, ((double)m_currentIndex) / (sampleSize - 1) - m_ChartViewer.getViewPortWidth()));	

    // In addition to updating the chart, we may also need to update other controls that
    // changes based on the view port.
    updateControls(&m_ChartViewer);

    // Update the chart if necessary
    if (m_ChartViewer.needUpdateChart())
        drawChart(&m_ChartViewer);

    // We need to update the track line too. If the mouse is moving on the chart (eg. if 
    // the user drags the mouse on the chart to scroll it), the track line will be updated
    // in the MouseMovePlotArea event. Otherwise, we need to update the track line here.
	if (!m_ChartViewer.isInMouseMoveEvent()) 
	{
        trackLineLegend((XYChart *)m_ChartViewer.getChart(), m_ChartViewer.getPlotAreaMouseX());
        m_ChartViewer.updateDisplay();
    }
}

//
// User clicks on the the horizontal scroll bar 
//
void CZoomscrolltrackDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	double newViewPortLeft = moveScrollBar(nSBCode, nPos, pScrollBar);

	// Update the view port if the scroll bar has really moved
	if (newViewPortLeft != m_ChartViewer.getViewPortLeft()) 
	{
		m_ChartViewer.setViewPortLeft(moveScrollBar(nSBCode, nPos, pScrollBar));
		m_ChartViewer.updateViewPort(true, false);
	}
}

//
// The mouse wheel handler
//
BOOL CZoomscrolltrackDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	// Process the mouse wheel only if the mouse is over the plot area
	if (!m_ChartViewer.isMouseOnPlotArea())
		return FALSE;

    // We zoom in or out by 10% depending on the mouse wheel direction.
	double newVpWidth = m_ChartViewer.getViewPortWidth() * (zDelta > 0 ? 0.9 : 1 / 0.9);
	double newVpHeight = m_ChartViewer.getViewPortHeight() * (zDelta > 0 ? 0.9 : 1 / 0.9);

    // We do not zoom beyond the zoom width or height limits.
    newVpWidth = max(m_ChartViewer.getZoomInWidthLimit(), min(newVpWidth,
        m_ChartViewer.getZoomOutWidthLimit()));
    newVpHeight = max(m_ChartViewer.getZoomInHeightLimit(), min(newVpWidth,
        m_ChartViewer.getZoomOutHeightLimit()));
	
	if ((newVpWidth != m_ChartViewer.getViewPortWidth()) || 
		(newVpHeight != m_ChartViewer.getViewPortHeight()))
	{
		// Set the view port position and size so that the point under the mouse remains under
		// the mouse after zooming.

		double deltaX = (m_ChartViewer.getPlotAreaMouseX() - m_ChartViewer.getPlotAreaLeft()) * 
			(m_ChartViewer.getViewPortWidth() - newVpWidth) / m_ChartViewer.getPlotAreaWidth();
		m_ChartViewer.setViewPortLeft(m_ChartViewer.getViewPortLeft() + deltaX);
		m_ChartViewer.setViewPortWidth(newVpWidth);

		double deltaY = (m_ChartViewer.getPlotAreaMouseY() - m_ChartViewer.getPlotAreaTop()) *
			(m_ChartViewer.getViewPortHeight() - newVpHeight) / m_ChartViewer.getPlotAreaHeight();
		m_ChartViewer.setViewPortTop(m_ChartViewer.getViewPortTop() + deltaY);
		m_ChartViewer.setViewPortHeight(newVpHeight);

	    m_ChartViewer.updateViewPort(true, false);
	}

	return TRUE;
}

//
// Draw track cursor when mouse is moving over plotarea
//
void CZoomscrolltrackDlg::OnMouseMovePlotArea()
{
    // Get the focus to ensure being able to receive mouse wheel events
    m_ChartViewer.SetFocus();

    trackLineLegend((XYChart *)m_ChartViewer.getChart(), m_ChartViewer.getPlotAreaMouseX()); 
    m_ChartViewer.updateDisplay();
}

/////////////////////////////////////////////////////////////////////////////
// CZoomscrolltrackDlg methods

//
// Load the data
//
std::vector<std::string> buffer;
std::vector<const char *>labels;


//
// Initialize the CChartViewer 
//
void CZoomscrolltrackDlg::initChartViewer(CChartViewer *viewer)
{
    // Set the full x range to be the duration of the data
    viewer->setFullRange("x", 0, sampleSize - 1);

    // Initialize the view port to show the earliest 101 samples
    viewer->setViewPortWidth(100.0 / sampleSize);
    viewer->setViewPortLeft(0);

    // Set the maximum zoom to 10 points
    viewer->setZoomInWidthLimit(10.0 /sampleSize);

    // Initially set the mouse to drag to scroll mode.
    m_PointerPB.SetCheck(1);
    viewer->setMouseUsage(Chart::MouseUsageScroll);
}

//
// Initialize the CChartViewer 
//
void CZoomscrolltrackDlg::updateControls(CChartViewer *viewer)
{
    // In this demo, we need to update the scroll bar to reflect the view port position and
    // width of the view port.

    m_HScrollBar.EnableWindow(viewer->getViewPortWidth() < 1);
    if (viewer->getViewPortWidth() < 1)
    {
        SCROLLINFO info;
        info.cbSize = sizeof(SCROLLINFO);
        info.fMask = SIF_ALL;
        info.nMin = 0;
        info.nMax = 0x1fffffff;
	    info.nPage = (int)ceil(viewer->getViewPortWidth() * (info.nMax - info.nMin));
        info.nPos = (int)(0.5 + viewer->getViewPortLeft() * (info.nMax - info.nMin)) + info.nMin;
        m_HScrollBar.SetScrollInfo(&info);
    }
}

//
// Handle scroll bar events
//
double CZoomscrolltrackDlg::moveScrollBar(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    //
    // Get current scroll bar position
    //
    SCROLLINFO info;
    info.cbSize = sizeof(SCROLLINFO);
    info.fMask = SIF_ALL;
    pScrollBar->GetScrollInfo(&info);

    //
    // Compute new position based on the type of scroll bar events
    //
    int newPos = info.nPos;
    switch (nSBCode)
    {
    case SB_LEFT:
        newPos = info.nMin;
        break;
    case SB_RIGHT:
        newPos = info.nMax;
        break;
    case SB_LINELEFT:
        newPos -= (info.nPage > 10) ? info.nPage / 10 : 1;
        break;
    case SB_LINERIGHT:
        newPos += (info.nPage > 10) ? info.nPage / 10 : 1;
        break;
    case SB_PAGELEFT:
        newPos -= info.nPage;
        break;
    case SB_PAGERIGHT:
        newPos += info.nPage;
        break;
    case SB_THUMBTRACK:
        newPos = info.nTrackPos;
        break;
    }
    if (newPos < info.nMin) newPos = info.nMin;
    if (newPos > info.nMax) newPos = info.nMax;
    
    // Update the scroll bar with the new position
    pScrollBar->SetScrollPos(newPos);

    // Returns the position of the scroll bar as a ratio of its total length
    return ((double)(newPos - info.nMin)) / (info.nMax - info.nMin);
}

//
// Draw the chart and display it in the given viewer
//
void CZoomscrolltrackDlg::drawChart(CChartViewer *viewer)
{
	// Get the array indexes that corresponds to the visible start and end dates
    int startIndex = (int)floor(viewer->getValueAtViewPort("x", viewer->getViewPortLeft()));
	int endIndex = (int)ceil(viewer->getValueAtViewPort("x", viewer->getViewPortLeft() +
        viewer->getViewPortWidth()));
    int noOfPoints = endIndex - startIndex + 1;

    // Extract the part of the data array that are visible.
    DoubleArray viewPortTimeStamps = DoubleArray(&(m_timeStamps[0]) + startIndex, noOfPoints);
    DoubleArray viewPortDataSeriesA = DoubleArray(&(m_dataSeriesA[0]) + startIndex, noOfPoints);
    DoubleArray viewPortDataSeriesB = DoubleArray(&(m_dataSeriesB[0]) + startIndex, noOfPoints);
    DoubleArray viewPortDataSeriesC = DoubleArray(&(m_dataSeriesC[0]) + startIndex, noOfPoints);

    //
    // At this stage, we have extracted the visible data. We can use those data to plot the chart.
    //

	///////////////////////////////////////////////////////////////////////////////////////
    // Configure overall chart appearance. 
    ///////////////////////////////////////////////////////////////////////////////////////

    // Create an XYChart object of size 650 x 350 pixels, with a white (ffffff) background and grey 
	// (aaaaaa) border
    XYChart *c = new XYChart(650, 350, 0xffffff, 0xaaaaaa);
    
    // Set the plotarea at (55, 50) with width 90 pixels less than chart width, and height 80 pixels
    // less than chart height. Use a vertical gradient from light blue (f0f6ff) to sky blue (a0c0ff)
    // as background. Set border to transparent and grid lines to white (ffffff).
    c->setPlotArea(55, 50, c->getWidth() - 90, c->getHeight() - 80, c->linearGradientColor(0, 55, 0, 
		c->getHeight() - 35, 0xf0f6ff, 0xa0c0ff), -1, Chart::Transparent, 0xffffff, 0xffffff);

    // As the data can lie outside the plotarea in a zoomed chart, we need enable clipping.
    c->setClipping();

    // Add a title to the chart using 18 pts Times New Roman Bold Italic font
    c->addTitle("   Zooming and Scrolling with Track Line (1)", "timesbi.ttf", 18);

    // Set legend icon style to use line style icon, sized for 8pt font
    c->getLegend()->setLineStyleKey();
    c->getLegend()->setFontSize(8);

    // Set the axis stem to transparent
    c->xAxis()->setColors(Chart::Transparent);
    c->yAxis()->setColors(Chart::Transparent);

    // Add axis title using 10pts Arial Bold Italic font
    c->yAxis()->setTitle("Ionic Temperature (C)", "arialbi.ttf", 10);

    ///////////////////////////////////////////////////////////////////////////////////////
    // Add data to chart
    ///////////////////////////////////////////////////////////////////////////////////////

    // 
    // In this example, we represent the data by lines. You may modify the code below to use other
	// representations (areas, scatter plot, etc).
    //

    // Add a line layer for the lines, using a line width of 2 pixels
    LineLayer *layer = c->addLineLayer();
    layer->setLineWidth(2);

    // In this demo, we do not have too many data points. In real code, the chart may contain a lot
    // of data points when fully zoomed out - much more than the number of horizontal pixels in this
    // plot area. So it is a good idea to use fast line mode.
    layer->setFastLineMode();

    // Now we add the 3 data series to a line layer, using the color red (ff0000), green
    // (00cc00) and blue (0000ff)
	layer->setXData(viewPortTimeStamps);
    layer->addDataSet(viewPortDataSeriesA, 0xff3333, "Alpha");
    layer->addDataSet(viewPortDataSeriesB, 0x008800, "Beta");
    layer->addDataSet(viewPortDataSeriesC, 0x3333CC, "Gamma");

    ///////////////////////////////////////////////////////////////////////////////////////
    // Configure axis scale and labelling
    ///////////////////////////////////////////////////////////////////////////////////////
    
    // Set the x-axis as a date/time axis with the scale according to the view port x range.
	if (viewPortTimeStamps[0] != Chart::NoValue)
	{
	    c->xAxis()->setDateScale(viewPortTimeStamps[0], viewPortTimeStamps[0] + 
			DataInterval * viewPortTimeStamps.len / 1000.0);
		c->xAxis()->setLabelFormat("{value|hh:nn:ss}");
	}

    ///////////////////////////////////////////////////////////////////////////////////////
    // Output the chart
    ///////////////////////////////////////////////////////////////////////////////////////

    delete viewer->getChart();
    viewer->setChart(c);
}

//
// Draw the track line with legend
//
void CZoomscrolltrackDlg::trackLineLegend(XYChart *c, int mouseX)
{
    // Clear the current dynamic layer and get the DrawArea object to draw on it.
    DrawArea *d = c->initDynamicLayer();

    // The plot area object
    PlotArea *plotArea = c->getPlotArea();

    // Get the data x-value that is nearest to the mouse, and find its pixel coordinate.
    double xValue = c->getNearestXValue(mouseX);
    int xCoor = c->getXCoor(xValue);

    // Draw a vertical track line at the x-position
    d->vline(plotArea->getTopY(), plotArea->getBottomY(), xCoor, d->dashLineColor(0x000000, 0x0101));

    // Container to hold the legend entries
	vector<string> legendEntries;

    // Iterate through all layers to build the legend array
    for (int i = 0; i < c->getLayerCount(); ++i) {
        Layer *layer = c->getLayerByZ(i);

        // The data array index of the x-value
        int xIndex = layer->getXIndexOf(xValue);

        // Iterate through all the data sets in the layer
        for (int j = 0; j < layer->getDataSetCount(); ++j) {
            DataSet *dataSet = layer->getDataSetByZ(j);

            // We are only interested in visible data sets with names
            const char *dataName = dataSet->getDataName();
            int color = dataSet->getDataColor();
			if (dataName && *dataName && (color != Chart::Transparent)) {
                // Build the legend entry, consist of the legend icon, name and data value.
                double dataValue = dataSet->getValue(xIndex);
				ostringstream legendEntry;
				legendEntry << "<*block*>" << dataSet->getLegendIcon() << " " << dataName << ": " <<
					((dataValue == Chart::NoValue) ? "N/A" : c->formatValue(dataValue, "{value|P4}"))
					<< "<*/*>";
				legendEntries.push_back(legendEntry.str());

                // Draw a track dot for data points within the plot area
                int yCoor = c->getYCoor(dataSet->getPosition(xIndex), dataSet->getUseYAxis());
                if ((yCoor >= plotArea->getTopY()) && (yCoor <= plotArea->getBottomY())) {
                    d->circle(xCoor, yCoor, 4, 4, color, color);
                }
            }
        }
    }

    // Create the legend by joining the legend entries
	ostringstream legendText;
	legendText << "<*block,maxWidth=" << plotArea->getWidth() << "*><*block*><*font=arialbd.ttf*>["
		<< c->xAxis()->getFormattedLabel(xValue, "hh:nn:ss") << "]<*/*>";
	for (int i = ((int)legendEntries.size()) - 1; i >= 0; --i)
		legendText << "        " << legendEntries[i];
	
    // Display the legend on the top of the plot area
    TTFText *t = d->text(legendText.str().c_str(), "arial.ttf", 8);
	t->draw(plotArea->getLeftX() + 5, plotArea->getTopY() - 3, 0x000000, Chart::BottomLeft);
	t->destroy();
}

/////////////////////////////////////////////////////////////////////////////
// General utilities

//
// Load an icon resource into a button
//
void CZoomscrolltrackDlg::loadButtonIcon(int buttonId, int iconId, int width, int height)
{
    GetDlgItem(buttonId)->SendMessage(BM_SETIMAGE, IMAGE_ICON, (LPARAM)::LoadImage(
        AfxGetResourceHandle(), MAKEINTRESOURCE(iconId), IMAGE_ICON, width, height, 
        LR_DEFAULTCOLOR));  
}
zoomscrolltrackDlg.h
// zoomscrolltrackDlg.h : header file
//

#pragma once

#include "ChartViewer.h"
#include <afxmt.h>
#include <vector>
#include <string>


// CZoomscrolltrackDlg dialog
class CZoomscrolltrackDlg : public CDialog
{
// Construction
public:
	CZoomscrolltrackDlg(CWnd* pParent = NULL);	// standard constructor
	~CZoomscrolltrackDlg();

// Dialog Data
	enum { IDD = IDD_ZOOMSCROLLTRACK_DIALOG };
	CButton m_PointerPB;
	CScrollBar m_HScrollBar;
	CChartViewer m_ChartViewer;

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV support


// Implementation
protected:
	HICON m_hIcon;

	// Generated message map functions
	virtual BOOL OnInitDialog();
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	afx_msg void OnPointerPB();
	afx_msg void OnZoomInPB();
	afx_msg void OnZoomOutPB();
	afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
	afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
	afx_msg void OnViewPortChanged();	
	afx_msg void OnMouseMovePlotArea();
	afx_msg void OnTimer(UINT_PTR nIDEvent);
	DECLARE_MESSAGE_MAP()


private:
	//
	// Data arrays for the scrollable / zoomable chart.
	// - In this demo, we just use a RanSeries object to generate random data for the chart.
	//
	std::vector<double> m_timeStamps;
	std::vector<double> m_dataSeriesA;
	std::vector<double> m_dataSeriesB;
	std::vector<double> m_dataSeriesC;
	
	// The index of the array position to which new data values are added.
    int m_currentIndex;

	// Used by the random number generator to generate real time data.
	double m_nextDataTime;

    // Initialize the CChartViewer
    void initChartViewer(CChartViewer *viewer);

	// Draw chart
	void drawChart(CChartViewer *viewer);
    void trackLineLegend(XYChart *c, int mouseX);

	// Moves the scroll bar when the user clicks on it
	double moveScrollBar(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);

	// Update controls when the view port changed
    void updateControls(CChartViewer *viewer);

	// utility to load icon resource to a button
	void loadButtonIcon(int buttonId, int iconId, int width, int height);
};