Click here to Skip to main content
15,891,136 members
Articles / Desktop Programming / MFC

An MFC Chart Control with Enhanced User Interface

Rate me:
Please Sign up or sign in to vote.
4.92/5 (102 votes)
17 Jun 2013CPOL112 min read 439.7K   98.4K   390  
An MFC linear chart control with enhanced appearance.
// ChartContainer.cpp : implementation file
//

#include "stdafx.h"
#include <ctime>
#include "ChartDef.h"
#include "Chart.h"
#include "DataLabel.h"
#include "ChartContainer.h"
#include "ChartDataView.h"
#include "ChartsXMLSerializer.h"
#include "Util.h"
#include <Shlwapi.h>

using namespace std;
using namespace Gdiplus;


///////////////////////////////////////////////////////////////////////////////
// CChartContainer

IMPLEMENT_DYNAMIC(CChartContainer, CWnd)

CChartContainer::CChartContainer(string_t name) : m_colBkground((ARGB)Color::White), 
                                     m_colBorder((ARGB)Color::Black),
                                     m_colAxis((ARGB)Color::Black), 
                                     m_colZoomBorder((ARGB)Color::Red), 
                                     m_colLegPnts((ARGB)Color::Red),
                                     m_colDataViewPnts((ARGB)Color::BlueViolet), 
                                     m_colLegBkgnd(Color(0xFFFFFFE0)),
                                     m_colLegBorder((ARGB)Color::Black),
                                     m_colGrid((ARGB)Color::Gray), 
                                     m_minExtY(DBL_MAX), m_maxExtY(-DBL_MAX), 
                                     m_startX(DBL_MAX),m_endX(-DBL_MAX), 
                                     m_axOffsXF(0.0f), m_axXPos(BETWEEN), m_axYPos(MIDDLE), 
                                     m_scX(1.0), m_scY(1.0), 
                                     m_bTracking(false), m_bHasNamesLeg(false), m_bUserEnabled(true),
                                     m_bAxBoundaries(false),
                                     m_chartSelIdx(-1), m_dataViewChartIdx(-1), m_precision(3),
                                     m_tmpZX(0.0), m_tmpZY(0.0),
                                     m_pMatrixD(NULL), 
                                     m_dataLegPntD(PointD(DBL_MAX, DBL_MAX)), m_epsX(0.0), 
                                     m_pDataWnd(NULL), m_pLegWnd(NULL),
                                     m_pChartDataView(NULL), m_chModeX(MODE_FULLX), m_chModeY(MODE_FULLY), 
                                     m_name(name), m_labelX(string_t(_T("X"))), m_pLabValStrFnPtr(&GetLabelValStr)
                                    
{
}

CChartContainer::~CChartContainer()
{
  if (m_pChartDataView != NULL)
  {
    if(IsWindow(m_pChartDataView->m_hWnd))
      m_pChartDataView->DestroyWindow();
    delete m_pChartDataView;
    m_pChartDataView = NULL;      // Really you do not need this line
  }

  DestroyLabWnd(m_pDataWnd);
  DestroyLabWnd(m_pLegWnd);

  if (m_pMatrixD != NULL)
  {
    delete m_pMatrixD;
    m_pMatrixD = NULL;
  }

  if (!m_mapCharts.empty())
  {
    MAP_CHARTS::iterator it = m_mapCharts.begin();
    MAP_CHARTS::iterator itE = m_mapCharts.end();
    for (; it != itE; ++it)
    {
      delete it->second;
      it->second = NULL;
    }
    m_mapCharts.clear();  // Again, just in case
  }
}

CChartContainer& CChartContainer::operator=(const CChartContainer& rhs)
{
  if (this != &rhs)
  {
    m_pDataWnd = DestroyLabWnd(m_pDataWnd); // NULL
    m_pLegWnd  = DestroyLabWnd(m_pLegWnd);  //NULL

    m_colBkground   = rhs.m_colBkground;
    m_colBorder     = rhs.m_colBorder;
    m_colAxis       = rhs.m_colAxis;
    m_colZoomBorder = rhs.m_colZoomBorder;
    m_colLegPnts    = rhs.m_colLegPnts;
    m_colDataViewPnts = rhs.m_colDataViewPnts;
    m_colLegBkgnd   = rhs.m_colLegBkgnd;
    m_colLegBorder  = rhs.m_colLegBorder;
    m_colGrid       = rhs.m_colGrid;

    m_minExtY       = rhs.m_minExtY;
    m_maxExtY       = rhs.m_maxExtY;
    m_startX        = rhs.m_startX;
    m_endX          = rhs.m_endX;

    m_axOffsXF      = 0.0f;         // Can't be defined until container wnd is created
    m_axXPos        = BETWEEN;
    m_axYPos        = MIDDLE;

    m_scX = 1.0;     // Will be calculated in OnPaint()
    m_scY = 1.0; 
   
    m_bTracking        = rhs.m_bTracking;    // Call ShowLegend after wnd creation to see
    m_bHasNamesLeg     = rhs.m_bHasNamesLeg;
    m_bUserEnabled     = rhs.m_bUserEnabled;
    m_dataLegPntD      = rhs.m_dataLegPntD;  // data legend window
    m_epsX             = rhs.m_epsX;         // Neighbourhood to search for nearest pnts
    m_bAxBoundaries    = rhs.m_bAxBoundaries, 
    m_chartSelIdx      = rhs.m_chartSelIdx; 
    m_dataViewChartIdx = -1;  // No Data View for transfer

    if (m_pMatrixD)
      delete m_pMatrixD;
    m_pMatrixD        = NULL; // To calculate in OnPaint()
   
    m_precision       = rhs.m_precision;

    m_name            = rhs.m_name;
    m_labelX          = rhs.m_labelX.empty() ? string_t(_T("X")) : rhs.m_labelX;;
    m_pLabValStrFnPtr = rhs.m_pLabValStrFnPtr;

// Copy charts 
    if (!m_mapCharts.empty())
    {
      MAP_CHARTS::iterator itDest = m_mapCharts.begin();
      MAP_CHARTS::iterator itDestE = m_mapCharts.end();
      for (; itDest!= itDestE; ++itDest)
        delete (itDest->second);
      m_mapCharts.clear();
    }

    MAP_CHARTS::const_iterator itCharts = rhs.m_mapCharts.begin();
    MAP_CHARTS::const_iterator itChartsE = rhs.m_mapCharts.end();
    for (; itCharts != itChartsE; ++itCharts)
    {
      CChart* chartPtr = itCharts->second->CloneChart(true);
      m_mapCharts.insert(MAP_CHARTS::value_type(itCharts->first, chartPtr));
    }

    m_vHistX  = rhs.m_vHistX;
    m_vHistY  = rhs.m_vHistY;
    m_chModeX = rhs.m_chModeX;
    m_chModeY = rhs.m_chModeY;

    m_mapSelPntsD = rhs.m_mapSelPntsD;
    m_mapDataViewPntsD.clear();

    if (IsWindow(m_hWnd) && IsWindowVisible())
    {
      if (rhs.IsLabWndExist(false))
        CopyDataLegendWnd();
      else
        RefreshWnd();

      if (rhs.IsLabWndExist(true)) 
        ShowNamesLegend();
    }
  }
  return *this;
}

bool CChartContainer::CreateChartCtrlWnd(DWORD dwExStyle, DWORD dwStyle, 
                                        const CRect& wndRect, CWnd* pParent, UINT nID)    
{
  BOOL bRes = CWnd::CreateEx(dwExStyle,
                                AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_SAVEBITS),
                                NULL,
                                dwStyle,
                                wndRect,
                                pParent,
                                nID
                             );

  if (bRes)
  {
    if (m_bTracking&&!m_mapSelPntsD.empty()&&!m_pDataWnd->GetSafeHwnd())
      CopyDataLegendWnd();
    if (m_bHasNamesLeg&&!m_pLegWnd->GetSafeHwnd())
      ShowNamesLegend();

  }

  return bRes == TRUE ? true : false;
}

///////////////////////////////////////////////////////////////////////////////
// Helpers and operations

CChartContainer* CChartContainer::CloneChartContainer(string_t name, bool bCloneState)
{
  CChartContainer* pContainer = new CChartContainer;

  if (name.empty())
    pContainer->m_name   = m_name;
  else
    pContainer->m_name   = name;

  MAP_CHARTS::iterator itCharts = m_mapCharts.begin();
  MAP_CHARTS::iterator itChartsE = m_mapCharts.end();
  for (; itCharts != itChartsE; ++itCharts)
  {
    CChart* cloneChartPtr = itCharts->second->CloneChart(bCloneState);
    pContainer->m_mapCharts.insert(MAP_CHARTS::value_type(cloneChartPtr->GetChartIdx(),
                                                                        cloneChartPtr));
  }

  pContainer->m_colBkground     = m_colBkground;
  pContainer->m_colBorder       = m_colBorder;
  pContainer->m_colAxis         = m_colAxis;
  pContainer->m_colZoomBorder   = m_colZoomBorder;
  pContainer->m_colLegPnts      = m_colLegPnts;
  pContainer->m_colDataViewPnts = m_colDataViewPnts;
  pContainer->m_colLegBkgnd     = m_colLegBkgnd;
  pContainer->m_colLegBorder    = m_colLegBorder;
  pContainer->m_colGrid         = m_colGrid;

  pContainer->m_labelX          = m_labelX;
  pContainer->m_pLabValStrFnPtr = m_pLabValStrFnPtr;

 if (bCloneState)
  {
    pContainer->m_bTracking     = m_bTracking;
    pContainer->m_bHasNamesLeg  = m_bHasNamesLeg;
    pContainer->m_dataLegPntD   = m_dataLegPntD;
    pContainer->m_epsX            = m_epsX;

    pContainer->m_startX        = m_startX;
    pContainer->m_endX          = m_endX;
    pContainer->m_minExtY       = m_minExtY;
    pContainer->m_maxExtY       = m_maxExtY;
    pContainer->m_vHistX        = m_vHistX;
    pContainer->m_vHistY        = m_vHistY;
    pContainer->m_bUserEnabled  = m_bUserEnabled;
    pContainer->m_chartSelIdx   = m_chartSelIdx;
    pContainer->m_bAxBoundaries = m_bAxBoundaries;
    pContainer->m_chModeX       = m_chModeX;
    pContainer->m_chModeY       = m_chModeY;

    if (m_bTracking && IsLabWndExist(false))
      pContainer->m_mapSelPntsD = m_mapSelPntsD;
      
  }
  else
  {
    pContainer->m_startX       = GetInitialStartX();
    pContainer->m_endX         = GetInitialEndX();
    pContainer->m_minExtY      = GetInitialMinExtY();
    pContainer->m_maxExtY      = GetInitialMaxExtY();
    pContainer->m_vHistX.clear(); // Just in case; the constructor sets them empty
    pContainer->m_vHistY.clear();
    pContainer->m_chModeX      = MODE_FULLX;
    pContainer->m_chModeY      = MODE_FULLY;
  }

  pContainer->m_precision     = m_precision;

  return pContainer;
}

CChart* CChartContainer::CloneChart(int chartIdx, bool bCloneState)
{
  MAP_CHARTS::iterator it = m_mapCharts.find(chartIdx);
  if (it != m_mapCharts.end())
    return it->second->CloneChart(bCloneState);
  return NULL;
}

///////////////////////////////////////////////////////////////////////////////
// Setting colors

void CChartContainer::SetBkColor(Color col, bool bRedraw)
{
  m_colBkground = col;
  if (bRedraw)
    RefreshWnd();
}

void CChartContainer::SetBorderColor(Color col, bool bRedraw)
{
  m_colBorder = col;
  if (bRedraw)
    RefreshWnd();
}

void CChartContainer::SetAxesColor(Color col, bool bRedraw)
{
  m_colAxis = col;
  if (bRedraw)
    RefreshWnd();
}

void CChartContainer::SetZoomBorderColor(Color col)
{
  if (m_colZoomBorder.GetValue() != col.GetValue())
  {
    m_colZoomBorder = col;

    if (m_chModeX == MODE_ZOOMINGX)
    {
      PointD tmpPntD(m_tmpZX, 0.0);      
      PointF tmpPntF = m_pMatrixD->TransformToPntF(1.0, tmpPntD);
      CPoint tmpPnt  = CPointFromPntF(tmpPntF);
      DrawZoomBorderX(tmpPnt.x);
    }
    else if (m_chModeY == MODE_ZOOMINGY)
    {
      PointD tmpPntD(0.0, m_tmpZY);      
      PointF tmpPntF = m_pMatrixD->TransformToPntF(1.0, tmpPntD);
      CPoint tmpPnt  = CPointFromPntF(tmpPntF);
      DrawZoomBorderY(tmpPnt.y);
    }
  }
}

void CChartContainer::SetLegPntsColor(Color col, bool bRedraw)
{
  m_colLegPnts = col;
  if (bRedraw && IsLabWndVisible(false))
    RefreshWnd();
}

void CChartContainer::SetDataViewPntsColor(Color col, bool bRedraw)
{
  m_colDataViewPnts = col;
  if (bRedraw && (!m_mapDataViewPntsD.empty()))
    RefreshWnd();
}

void CChartContainer::SetLegBkgndColor(Color col, bool bRedraw)
{
  m_colLegBkgnd = col;
  if (m_pLegWnd != NULL)
  {
    m_pLegWnd->SetBkColor(col);
    if (bRedraw && m_pLegWnd->IsWindowVisible())
          RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOERASE);
  }

  if (m_pDataWnd != NULL)
  {
    m_pDataWnd->SetBkColor(col);
    if (bRedraw && m_pDataWnd->IsWindowVisible())
          RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOERASE);
  }
}

void CChartContainer::SetLegBorderColor(Color col, bool bRedraw)
{
  m_colLegBorder = col;
  if (m_pLegWnd != NULL)
  {
    m_pLegWnd->SetBorderColor(col);
    if (bRedraw && m_pLegWnd->IsWindowVisible())
          RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOERASE);
  }

  if (m_pDataWnd != NULL)
  {
    m_pDataWnd->SetBorderColor(col);
    if (bRedraw && m_pDataWnd->IsWindowVisible())
          RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOERASE);
  }
}

void CChartContainer::SetGridColor(Color col, bool bRedraw)
{
  m_colGrid = col;
  if (bRedraw)
    RefreshWnd();
}

double CChartContainer::GetInitialStartX(void) const
{
  return m_vHistX.empty() ? m_startX : m_vHistX.front().first;
}

double CChartContainer::GetInitialEndX(void) const
{
  return m_vHistX.empty() ? m_endX : m_vHistX.front().second;
}

double CChartContainer::GetInitialMinExtY(void) const
{
  return m_vHistY.empty() ? m_minExtY : m_vHistY.front().first;
}

double CChartContainer::GetInitialMaxExtY(void) const
{
  return m_vHistY.empty() ? m_maxExtY : m_vHistY.front().second;
}

void CChartContainer::EnableUser(bool bEnable, bool bClearState)
{
  m_bUserEnabled = bEnable;
  if (!m_bUserEnabled && bClearState && !m_mapCharts.empty())
  {
    m_chartSelIdx = SelectChart(-1);
    m_bTracking = false;
    m_bHasNamesLeg = false;
    m_dataLegPntD = PointD(DBL_MAX, DBL_MAX);
    m_pDataWnd = DestroyLabWnd(m_pDataWnd, false);
    m_pLegWnd  = DestroyLabWnd(m_pLegWnd);
    m_pChartDataView = DestroyChartDataView();
    ResetCharts(true);
  }
}

void CChartContainer::UpdateExtX(double minExtX, double maxExtX, bool bRedraw)
{
  if (maxExtX < minExtX)                   // Possible if from app
    return;

  double initStartX = GetInitialStartX();   // Old initial m_startX, m_endX
  double initEndX   = GetInitialEndX();

  double startX, endX;

  if (initStartX > initEndX)  // The container is empty
  {
    startX = minExtX;
    endX   = maxExtX;
  }
  else
  {
    startX = min(minExtX, initStartX);
    endX   = max(maxExtX, initEndX);
  }

  if (startX == endX)
  {
    endX += fabs(startX*0.01);;
  }

  if (m_vHistX.size() > 0)   // Was zoomed or panned
    m_vHistX.front() = make_pair(startX, endX);
  else                      // Has no history
  {
   m_startX = startX;
   m_endX   = endX;
  }

  if (bRedraw)
  {
    if (m_bTracking&& IsLabWndExist(true))
      UpdateDataLegend(false);
    else
      RefreshWnd();
  }
}

void CChartContainer::UpdateExtY(double minExtY, double maxExtY, bool bRedraw)
{
  if (maxExtY < minExtY)                   // Possible if from app
    return;

  double initMinY = GetInitialMinExtY();   // Old initial m_startX, m_endX
  double initMaxY = GetInitialMaxExtY();

  double startY, endY;

  if (initMinY > initMaxY)  // The container is empty
  {
    startY = minExtY;
    endY   = maxExtY;
  }
  else
  {
    startY = min(minExtY, initMinY);
    endY   = max(maxExtY, initMaxY);
  }

  if (startY == endY)
  {
    double delta = fabs(startY*0.01);
    startY -= delta*4.0;
    endY   += delta;
  }

  if (m_vHistY.size() > 0)   // Was zoomed or panned
    m_vHistY.front() = make_pair(startY, endY);
  else                      // Has no history
  {
   m_minExtY = startY;
   m_maxExtY = endY;
  }

  if (bRedraw)
  {
    if (m_bTracking&& IsLabWndExist(false))
      UpdateDataLegend(true);
    else
      RefreshWnd();
  }
}

PAIR_DBLS CChartContainer::SetExtX(bool bRedraw)
{
  double startX, endX;
  std::tie(startX, endX) = GetMinMaxExtX();
  if (startX == endX)
    endX += fabs(startX*0.01);

  if (m_vHistX.size() > 0)   // Was zoomed or panned
    m_vHistX.front() = make_pair(startX, endX);
  else                      // Has no history
  {
   m_startX = startX;
   m_endX   = endX;
  }

  if (bRedraw)
  {
    if (m_bTracking&& IsLabWndExist(true))
      UpdateDataLegend(false);
    else
      RefreshWnd();
  }

  return make_pair(startX, endX);
}

PAIR_DBLS CChartContainer::SetExtY(bool bRedraw)
{
  double minY, maxY;
  std::tie(minY, maxY) = GetMinMaxExtY();

  if (minY == maxY)
  {
    double delta = fabs(maxY*0.01);
    minY -= delta*4.0;
    maxY += delta;
  }

  if (m_vHistY.size() > 0)   // Was zoomed or panned
    m_vHistY.front() = make_pair(minY, maxY);
  else                      // Has no history
  {
   m_minExtY = minY;
   m_maxExtY = maxY;
  }

  if (bRedraw)
  {
    if (m_bTracking&& IsLabWndExist(false))
      UpdateDataLegend(true);
    else
      RefreshWnd();
  }

  return make_pair(minY, maxY);
}

void CChartContainer::SetContainerPrecision(int precision, bool bRedraw)
{
  m_precision = precision;
  if (bRedraw)
  {
    if (IsLabWndVisible(false)) // This version of Update redraws dataWnd only
      UpdateDataLegend(false);  //(m_mapSelPntsD, m_pDataWnd->m_mapLabs);
    else if (m_bAxBoundaries)
      RefreshWnd();
    if (m_dataViewChartIdx != -1)
      UpdateDataView(m_dataViewChartIdx, F_NODATACHANGE);
  }
}

size_t CChartContainer::SetDataViewSelPnts(int chartIdx, 
                            const MAP_SELPNTSD& mapSelPntsD, V_CHARTDATAD* pChartData)
{
  if (chartIdx == -1)
    return 0;
  if (chartIdx != m_dataViewChartIdx)
    return 0;
  if (m_pChartDataView != NULL)
  {
    m_pChartDataView->m_mapSelCells = mapSelPntsD;
    if (pChartData == NULL)
    {
      m_pChartDataView->RefreshSelCells();
      if (IsWindow(m_pChartDataView->m_hWnd))
      {
        if (m_pChartDataView->IsIconic())
          m_pChartDataView->ShowWindow(SW_RESTORE);
        m_pChartDataView->RedrawWindow(NULL, NULL, 
                RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOERASE|RDW_ALLCHILDREN);
      }
    }
    else
      ReplaceChartData(chartIdx, *pChartData, false, false, false, true);
  }
  return mapSelPntsD.size();
}

bool CChartContainer::ClearDataViewChartIdx(void)
{
  if (m_pChartDataView != NULL) 
  {
    if (m_dataViewChartIdx != -1)
    {
      m_dataViewChartIdx = -1;
      UpdateDataViewPnts(-1, 0, PointD(0.0, 0.0), false);
    }

    DestroyChartDataView();
    return true;
  }
  return false;
}

void CChartContainer::SetAxisXName(string_t nameX, bool bRedraw)
{
  m_labelX = NormalizeString(nameX, STR_MAXLEN, STR_NORMSIGN);

  if (bRedraw)
  {
    if (IsLabWndVisible(false))
      UpdateDataLegend(false);
    else if (m_bAxBoundaries)
      RefreshWnd();
    if (m_dataViewChartIdx != -1)
      UpdateDataView(m_dataViewChartIdx, F_NODATACHANGE);
  }
}

void CChartContainer::SetLabXValStrFn(val_label_str_fn pLabValStrFn, bool bRedraw)
{
  m_pLabValStrFnPtr = pLabValStrFn;

  if (bRedraw)
  {
    if (IsLabWndVisible(false))
      UpdateDataLegend(false);
    else if (m_bAxBoundaries)
      RefreshWnd();
    if (m_dataViewChartIdx != -1)
      UpdateDataView(m_dataViewChartIdx, F_NODATACHANGE);
  }
}

bool CChartContainer::HasChartWithData(int chartIdx, bool bVisibleOnly) const
{
  MAP_CHARTS::const_iterator it = chartIdx == -1 ? m_mapCharts.cbegin() :
                                                            m_mapCharts.find(chartIdx);
  bool bHasChart = false;

  for (; it != m_mapCharts.cend(); ++it)
  {
    if (it->second->HasData())
    {
      if (!bVisibleOnly||it->second->IsChartVisible())
        bHasChart = true;
    }
    if (bHasChart ||( chartIdx != -1))
      break;
 } 

  return bHasChart;
}

int CChartContainer::HasChartDifferentPnts(int chartIdx, bool bVisibleOnly) const
{
  MAP_CHARTS::const_iterator it = chartIdx == -1 ? m_mapCharts.cbegin() :
                                                            m_mapCharts.find(chartIdx);
  int resIdx = -1;

  for (; it != m_mapCharts.cend(); ++it)
  { 
    CChart* pChart = it->second;
    if ((pChart->GetMaxValX() > pChart->GetMinValX())||
        (pChart->GetMaxValY() > pChart->GetMinValY())) 
    {
      if (!bVisibleOnly||pChart->IsChartVisible())
        resIdx = pChart->GetChartIdx();
    }
    if ((resIdx > -1) ||( chartIdx != -1))
      break;
  }

  return resIdx;
}

bool CChartContainer::HasVisiblePntsDInYBand(int chartIdx, double bottomY, double topY)
{
  bool bHasPntsD = false;
  CRect clR;
  GetClientRect(&clR);
  CPoint pntTop(clR.left, clR.top);
  CPoint pntBottom(clR.right, clR.bottom);
  PointD pntTopD, pntBottomD;
  MatrixD* pMatrixD = GetTransformMatrixD(m_startX, m_endX, bottomY, topY);
  MousePntToPntD(pntTop, pntTopD, pMatrixD);
  MousePntToPntD(pntBottom, pntBottomD, pMatrixD);
  delete pMatrixD;

  auto chartIt = chartIdx == -1 ? m_mapCharts.cbegin() : m_mapCharts.find(chartIdx);

  for (; chartIt != m_mapCharts.cend(); ++chartIt)
  {
    CChart* chartPtr = chartIt->second;
    if (!chartPtr->IsChartVisible())
      continue;

    double locScY = chartPtr->GetLocScaleY();
    double topY = pntTopD.Y;
    double bottomY = pntBottomD.Y;
// Select visible X-part of the chart
    V_CHARTDATAD::iterator itB, itE;
    std::tie(itB, itE) = chartPtr->GetStartEndDataIterators(chartPtr->m_vDataPnts, m_startX, m_endX);
    itB = find_if(itB, itE, 
      [topY, bottomY, locScY](const PointD& pntD) {return in_range(bottomY, topY, locScY*pntD.Y);});
    if (itB != itE)
    {
      bHasPntsD = true;
      break;
    }
    if (chartIdx != -1)
      break;
  }
  
  return bHasPntsD;
}

MatrixD* CChartContainer::GetTransformMatrixD(double startX, double endX, double minY, double maxY)
{
  CDC* pDC = GetDC();
  Graphics gr(pDC->m_hDC);

  RectF rGdiF;
  gr.GetVisibleClipBounds(&rGdiF);                 // The same as the clip rect

  rGdiF.Width -= 1.0f;                          
  rGdiF.Height -= 1.0f;
  rGdiF.Inflate(-DR_SPACE, -DR_SPACE);
// Axis X: Its Y Position and Offset; also Y coord of orig point
  PAIR_XAXPOS axXPosY = GetXAxisPos(rGdiF, minY, maxY);
// Axis Y: Its X Position and ofset; also X-coord of orig. point
  PAIR_YAXPOS axYPosX = GetYAxisPos(rGdiF, startX, endX); 

  std::pair<double, double> pair_scales = UpdateScales(rGdiF, 
                                                  startX, endX, minY, maxY);
  double scX = pair_scales.first;
  double scY = pair_scales.second;

  MatrixD matrixD;
  matrixD.Translate(axYPosX.second, axXPosY.second); 
  matrixD.Scale(scX, -scY);

  if ((axYPosX.first != BETWEEN)||(axXPosY.first != MIDDLE))
  {
    double translateX = (axYPosX.first == RIGHT) ? -endX : 
                                  (axYPosX.first == LEFT) ? -startX : 0.0;
    double translateY = (axXPosY.first == BOTTOM) ? -minY : 
                                      (axXPosY.first == TOP) ? -maxY : 0.0;
    matrixD.Translate(float(translateX), float(translateY)); 
  }

  ReleaseDC(pDC);
  return matrixD.Clone();
}

void CChartContainer::SetTransformMatrixD(MatrixD* pMatrixD) 
{ 
  if (m_pMatrixD != NULL) 
    delete m_pMatrixD; 
  m_pMatrixD = pMatrixD->Clone();
}

void CChartContainer::SetTransformMatrixD(void) 
{ 
  if (m_pMatrixD != NULL) 
    delete m_pMatrixD;
  m_pMatrixD = 
        GetTransformMatrixD(m_startX, m_endX, m_minExtY, m_maxExtY);
}

PAIR_DBLS CChartContainer::GetMinMaxExtX(void) const
{
  double fMinExtX = DBL_MAX;
  double fMaxExtX = -DBL_MAX;

  double currMinX, currMaxX;
  CChart* chartPtr;

  MAP_CHARTS::const_iterator it = m_mapCharts.cbegin();
  MAP_CHARTS::const_iterator itE = m_mapCharts.cend();
  for (; it != itE; ++it)
  {
    chartPtr = it->second;
    currMinX = chartPtr->GetMinValX();
    currMaxX = chartPtr->GetMaxValX();
    if (currMinX < fMinExtX)
      fMinExtX = currMinX;
    if (currMaxX > fMaxExtX)
      fMaxExtX = currMaxX;
  }

  return make_pair(fMinExtX, fMaxExtX);
}

PAIR_DBLS CChartContainer::GetMinMaxExtY(void) const
{
  double fMinExtY = DBL_MAX;
  double fMaxExtY = -DBL_MAX;

  double currMinY, currMaxY;
  CChart* chartPtr;

  MAP_CHARTS::const_iterator it = m_mapCharts.cbegin();
  MAP_CHARTS::const_iterator itE = m_mapCharts.cend();
  for (; it != itE; ++it)
  {
    chartPtr = it->second;
    currMinY = chartPtr->GetMinValY();
    currMaxY = chartPtr->GetMaxValY();
    if (currMinY < fMinExtY)
      fMinExtY = currMinY;
    if (currMaxY > fMaxExtY)
      fMaxExtY = currMaxY;
  }

  return make_pair(fMinExtY, fMaxExtY);
}

void CChartContainer::UndoHistStepX(bool bRedraw)
{
  if (m_vHistX.empty())
    return;

  PAIR_POS zh = m_vHistX.back();
  m_startX = zh.first;
  m_endX = zh.second;
  m_vHistX.pop_back();

// Restore state
  if (m_vHistX.empty())
    m_chModeX = MODE_FULLX;
  else 
    m_chModeX = MODE_ZOOMEDX;

  if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible())
  {
    if (m_bTracking && (m_pDataWnd != NULL))
      UpdateDataLegend(true);
    else
      RefreshWnd();
  }
}

void CChartContainer::UndoHistStepY(bool bRedraw)
{
  if (m_vHistY.empty())
    return;

  PAIR_POS zh = m_vHistY.back();
  m_minExtY = zh.first;
  m_maxExtY = zh.second;

  if (m_vHistY.size() > 1)    // Must check whether it is moves only
  {
    auto itZ = adjacent_find(m_vHistY.rbegin(), m_vHistY.rend(),
      [](const PAIR_POS& lhs, const PAIR_POS& rhs) ->bool 
          {return (fabs(1.0 - fabs((rhs.first - lhs.first)/(rhs.second - lhs.second))) > 4.0*DBL_EPSILON);});
    if (itZ == m_vHistY.rend())
      m_chModeY = MODE_MOVEDY;
    else
      m_chModeY = MODE_ZOOMEDY;
  }
  else
    m_chModeY = MODE_FULLY;

  m_vHistY.pop_back();

  if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible())
  {
    if (m_bTracking && (m_pDataWnd != NULL))
      UpdateDataLegend(true);
    else
      RefreshWnd();
  }
}

bool CChartContainer::UnwindHistX(double minX, double maxX)
{
  if (m_vHistX.empty())
    return false;

  PAIR_POS pair_step(minX, maxX);
  auto itB = find_if(m_vHistX.begin(), m_vHistX.end(),
    [minX, maxX](const PAIR_POS& pair_pos) {return (pair_pos.first == minX)&&(pair_pos.second == maxX);});
  if (itB == m_vHistX.end())
    return false;
  m_vHistX.erase(++itB, m_vHistX.end());
  return true;
}

void CChartContainer::OnChangedSize(int cx, int cy)
{
  HideLabWnds();            // Called from parent's/owner's OnSize handler
  MoveWindow(0, 0, cx, cy);
  RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOERASE);
  SetTimer(2, 50, NULL);
}

void CChartContainer::RefreshWnd(void)
{
  CRect clR, legR, dataR;
  GetClientRect(&clR);

  CRgn rgn1, rgn2, rgn3;
  rgn1.CreateRectRgn(0, 0, clR.right, clR.bottom);
  if (IsLabWndVisible(true))
  {
    m_pLegWnd->GetWindowRect(&legR);
    ScreenToClient(&legR);
    rgn2.CreateRectRgn(legR.left, legR.top, legR.right, legR.bottom);
    rgn1.CombineRgn(&rgn1, &rgn2, RGN_DIFF);
  }
  if (IsLabWndVisible(false))
  {
    m_pDataWnd->GetWindowRect(&dataR);
    ScreenToClient(&dataR);
    rgn3.CreateRectRgn(dataR.left, dataR.top, dataR.right, dataR.bottom);
    rgn1.CombineRgn(&rgn1, &rgn3, RGN_DIFF);
  }

  RedrawWindow(NULL, &rgn1, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOERASE);//|RDW_ALLCHILDREN);
}

Status CChartContainer::SaveContainerImage(string_t pathName)
{
  if (!HasChartWithData(-1,true))   // Repeat to provide for standalone use
    return GenericError; 

  Status status = Aborted;
  UINT  num;        // number of image encoders
  UINT  size;       // size, in bytes, of the image encoder array
  
// How many encoders are there? How big (in bytes) is the array of all ImageCodecInfo objects?
  GetImageEncodersSize(&num, &size);
// Create a buffer large enough to hold the array of ImageCodecInfo objects that will be returned by GetImageEncoders.
  ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)(malloc(size));;
// GetImageEncoders creates an array of ImageCodecInfo objects and copies that array into a previously allocated buffer. 
  GetImageEncoders(num, size, pImageCodecInfo);
// Get filter string
  sstream_t stream_t;
  string_t str_t, tmp_t;
  string_t szFilter;
  CLSID clsID;
  typedef std::map<string_t, CLSID> MAP_CLSID;
  typedef MAP_CLSID::value_type TYPE_VALCLSID;
  typedef MAP_CLSID::iterator IT_CLSID;

  MAP_CLSID mapCLSID;

  for(UINT j = 0; j < num; ++j)
  { 
    stream_t << pImageCodecInfo[j].MimeType <<_T("\n");   
    getline(stream_t, str_t);
    size_t delPos = str_t.find(TCHAR('/'), 0);
    str_t.erase(0, delPos + 1);
    clsID = pImageCodecInfo[j].Clsid;
    mapCLSID.insert(TYPE_VALCLSID(str_t, clsID));
    tmp_t = str_t;
    std::transform(tmp_t.begin(), tmp_t.end(), tmp_t.begin(), [](const TCHAR&tch) ->TCHAR {return (TCHAR)toupper(tch);});
    szFilter += tmp_t + string_t(_T(" File|*.")) + str_t + string_t(_T("|")); 
  }
  szFilter += string_t(_T("|"));
  free(pImageCodecInfo);

  if (pathName.empty())     // Let the user choose
  {
    TCHAR szWorkDirPath[255];
    GetModuleFileName(NULL, szWorkDirPath, 255);
    PathRemoveFileSpec(szWorkDirPath);

    string_t dirStr(szWorkDirPath);
    size_t lastSlash = dirStr.find_last_of(_T("\\")) + 1;
    dirStr.erase(lastSlash, dirStr.size() - lastSlash);
    dirStr += string_t(_T("Images"));
    szFilter += string_t(_T("|"));
    CFileDialog fileDlg(FALSE, _T("BMP File"), _T("*.bmp"), 
        OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_NOCHANGEDIR|OFN_EXPLORER,
        szFilter.c_str(), this);

    fileDlg.m_ofn.lpstrInitialDir = dirStr.c_str();
    fileDlg.m_ofn.lpstrTitle = _T("Save As Image");
 
    string_t strTitle(_T("Save "));

    if (fileDlg.DoModal() == IDOK)
    {
      pathName = string_t(fileDlg.GetPathName());
    }
    else
      return Ok;
  }

  if (pathName.empty())
    return InvalidParameter;

  size_t pos = pathName.find(_T("."));
  if (pos == string_t::npos)
    return GenericError;
  
  string_t szExt = pathName.substr(pos);
  pos = szFilter.find(szExt);
  if (pos == string_t::npos)
    return UnknownImageFormat;
  szExt.erase(0, 1);

  IT_CLSID it = mapCLSID.find(szExt);
  if (it != mapCLSID.end())
  {
    SendNotification(CODE_SAVEIMAGE);
    CRect clR;
    GetClientRect(&clR);
    Rect rGdi = CRectToGdiRect(clR);
    Bitmap bmp(rGdi.Width, rGdi.Height);
    DrawContainerToBmp(rGdi, bmp);

    clsID = it->second;
    status = bmp.Save(pathName.c_str(), &clsID);
    SendNotification(CODE_SAVEDIMAGE);
  }
  else status = UnknownImageFormat; 
  return status;
}

HRESULT CChartContainer::SaveChartData(string_t pathName, bool bAll)
{
  HRESULT hr = S_FALSE;
// See what you are going to save
  if (m_mapCharts.empty())
    return hr;
  if (!bAll &&(NULL == GetVisibleChart(m_chartSelIdx)))
    return hr;

// Select file
  if (pathName.empty())       // The user should enter the path
  {
    TCHAR szFilters[]= _T("XML Files (*.xml)|*.xml||");
    TCHAR szWorkDirPath[255];
    GetModuleFileName(NULL, szWorkDirPath, 255);
    PathRemoveFileSpec(szWorkDirPath);

    string_t dirStr(szWorkDirPath);
    size_t lastSlash = dirStr.find_last_of(_T("\\")) + 1;
    dirStr.erase(lastSlash, dirStr.size() - lastSlash);
    dirStr += string_t(_T("Charts"));

    CFileDialog fileDlg(FALSE, _T("xml"), _T("*.xml"), 
        OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_NOCHANGEDIR|OFN_EXPLORER,
        szFilters, this);
    fileDlg.m_ofn.lpstrInitialDir = dirStr.c_str();
    fileDlg.m_ofn.lpstrTitle = _T("Save Charts");
 
    string_t strTitle(_T("Save "));

    if (fileDlg.DoModal() == IDOK)
    {
      pathName = string_t(fileDlg.GetPathName());
    }
  }
  if (pathName.empty())
    return S_OK;

  size_t extPos = pathName.rfind(string_t(_T(".xml")));
  if (extPos != string_t::npos)
  {
    SendNotification(CODE_SAVECHARTS);
    CChartContainer* pContainer = CloneChartContainer(string_t(_T("")), true);
    SendNotification(CODE_SAVEDCHARTS);
    string_t chartName;
    if (m_chartSelIdx != -1)
      chartName = pContainer->GetChartName(m_chartSelIdx);
    hr = CChartsXMLSerializer::ChartDataToXML(pathName.c_str(), pContainer, chartName, bAll);
    delete pContainer;
  }
  else
    hr = S_OK;

  return hr;
}

HRESULT CChartContainer::LoadCharts(LPCTSTR fileName, const MAP_CHARTCOLS& mapContent, bool bClearCharts)
{
  HRESULT hr = CChartsXMLSerializer::XMLToCharts(fileName, this, mapContent, bClearCharts);
  if (hr == S_OK)
  {
    if (IsLabWndExist(false))
      PrepareDataLegend(m_dataLegPntD, m_epsX, m_pDataWnd->m_mapLabs, m_mapSelPntsD, NULL);
 
    UpdateContainerWnds();
  }
  return hr;
}

HRESULT CChartContainer::ReplaceContainerCharts(LPCTSTR fileName)
{
  CChartContainer tmpContainer;
  HRESULT hr =  CChartsXMLSerializer::ReplaceChartsFromXMLFile(fileName, &tmpContainer);
  if (hr == S_OK)
  {
    *this = tmpContainer;
    UpdateContainerWnds();
  }
  return hr;
}

HRESULT CChartContainer::GetChartNamesFromXMLFile(LPCTSTR fileName, MAP_CHARTCOLS& mapContent)
{
  return CChartsXMLSerializer::GetChartNamesFromXMLFile(fileName, mapContent);
}

HRESULT CChartContainer::GetChartNamesFromXMLFile(LPCTSTR fileName, MAP_NAMES& mapNames)
{
  return CChartsXMLSerializer::GetChartNamesFromXMLFile(fileName, mapNames);
}

bool CChartContainer::ExportChartData(string_t chartName, V_CHARTDATAD& vDataPnts) const
{
  CChart* chartPtr = FindChartByName(chartName);
  if (chartPtr != NULL)
  {
    size_t dataSize = chartPtr->m_vDataPnts.size();
    if (dataSize != 0)
    {
      vDataPnts.resize(dataSize);
      copy(chartPtr->m_vDataPnts.cbegin(), chartPtr->m_vDataPnts.cend(), vDataPnts.begin());
    }
    else
      vDataPnts.clear();
    return true;
  }
  return false;
}

bool CChartContainer::ExportChartData(string_t chartName, 
                                std::vector<std::pair<double, double> >& vPairs) const
{
  CChart* chartPtr = FindChartByName(chartName);
  if (chartPtr != NULL)
  {
    size_t dataSize = chartPtr->m_vDataPnts.size();
    if (dataSize != 0)
    {
      vPairs.resize(dataSize);
      for (size_t i = 0; i < dataSize; ++i)
      {
        PointD pntD = chartPtr->m_vDataPnts[i];
        vPairs[i] = make_pair(pntD.X, pntD.Y);
      }
    }
    else
      vPairs.clear();
    return true;
  }
  return false;
}

bool CChartContainer::ExportChartData(string_t chartName, 
                                   std::vector<double>& vX, std::vector<double>& vY) const
{
  CChart* chartPtr = FindChartByName(chartName);
  if (chartPtr != NULL)
  {
    size_t dataSize = chartPtr->m_vDataPnts.size();
    if (dataSize != 0)
    {
      vX.resize(dataSize);
      vY.resize(dataSize);
      for (size_t i = 0; i < dataSize; ++i)
      {
        PointD pntD = chartPtr->m_vDataPnts[i];
        vX[i] = pntD.X;
        vY[i] = pntD.Y;
      }
    }
    else
    {
      vX.clear();
      vY.clear();
    }
    return true;
  }
  return false;
}

// Chart access and manipulation functions
/// 
/// Returns false in the third member of the tuple if the chart does not exist
///
std::tuple<double, double, bool> CChartContainer::GetChartBoundaries(int chartIdx, bool bY) const
{
  MAP_CHARTS::const_iterator itCh = m_mapCharts.find(chartIdx);
  if (itCh != m_mapCharts.cend())
    return bY == false ? make_tuple(itCh->second->GetMinValX(),itCh->second->GetMaxValX(), true):
                         make_tuple(itCh->second->GetMinValY(),itCh->second->GetMaxValY(), true);
  return make_tuple(DBL_MAX, -DBL_MAX, false);
}

CChart* CChartContainer::GetVisibleChart(int chartIdx) const
{
  MAP_CHARTS::const_iterator it = (chartIdx) == -1 ? m_mapCharts.cbegin() :
                                                m_mapCharts.find(chartIdx);
  for (; it != m_mapCharts.cend(); ++it)
  {
    CChart* chartPtr = it->second;
    if (chartPtr->IsChartVisible()&&chartPtr->HasData())
      return chartPtr;
    if (chartIdx != -1)
      break;
  }
  return NULL;
}

bool CChartContainer::IsChartVisible(int chartIdx) const
{
  return GetVisibleChart(chartIdx) == NULL ? false : true;
}

bool CChartContainer::SetChartVisibility(int chartIdx, bool bVisible, bool bRedraw)
{
  bool bRes = false;
  MAP_CHARTS::iterator it = chartIdx == -1 ? m_mapCharts.begin() : m_mapCharts.find(chartIdx);
  MAP_CHARTS::iterator itE = m_mapCharts.end();
  for (; it != itE; ++it)
  {
    bRes = true;
    bool bVisState = it->second->IsChartVisible();
    if (bVisState != bVisible)
    {
      it->second->SetChartVisibility(bVisible);
      if (bVisible == false) 
      {
        if (it->second->IsChartSelected())
        {
          m_chartSelIdx = -1;
          it->second->SelectChart(false);
        }

        if (m_dataViewChartIdx == it->first)
        {
          if (m_pChartDataView != NULL)
          {
            if (IsWindow(m_pChartDataView->m_hWnd))
              m_pChartDataView->DestroyWindow();
            delete m_pChartDataView;
            m_pChartDataView = NULL;
          }
          UpdateDataViewPnts(-1, 0, PointD(0.0, 0.0), false);
        }
      }
    }
    if (chartIdx != -1)
      break;
  }
    
  if (bRedraw && bRes && IsWindow(m_hWnd) && IsWindowVisible()&&HasChartWithData(chartIdx))
    UpdateContainerWnds();
  
  return bRes;
}

int CChartContainer::ToggleChartVisibility(int chartIdx)
{
  MAP_CHARTS::iterator it = m_mapCharts.find(chartIdx);
  if (it == m_mapCharts.end())
    return -1;
  CChart* chartPtr = it->second;

  bool bVisible = chartPtr->ToggleChartVisibility();
  if (!bVisible)
  {
    if (chartPtr->IsChartSelected())
    {
      m_chartSelIdx = -1;
      chartPtr->SelectChart(false);
    }
    if (m_dataViewChartIdx == chartIdx)
    {
      if (m_pChartDataView != NULL)
      {
        if (IsWindow(m_pChartDataView->m_hWnd))
          m_pChartDataView->DestroyWindow();
        delete m_pChartDataView;
        m_pChartDataView = NULL;
      }
      UpdateDataViewPnts(-1, 0, PointD(0.0, 0.0), false);
    }
  }

  if (chartPtr->HasData()&&IsWindow(m_hWnd) && IsWindowVisible())
    UpdateContainerWnds();

  return bVisible ? 1 : 0;
}

std::pair<float, bool> CChartContainer::GetChartPenWidth(int chartIdx) const
{
  MAP_CHARTS::const_iterator it = m_mapCharts.find(chartIdx);
  if (it != m_mapCharts.cend())
    return make_pair(it->second->GetPenWidth(), true);
  return make_pair(0.0f, false);
}

bool CChartContainer::UpdateChartPenWidth(int chartIdx, float penWidth, bool bVisibleOnly, bool bRedraw)
{
  bool bRes = false;
  MAP_CHARTS::iterator it = (chartIdx == -1) ? m_mapCharts.begin() :
                                             m_mapCharts.find(chartIdx);
  for (; it != m_mapCharts.end(); ++it)
  {
    CChart* chartPtr = it->second;
    
    if (chartPtr->GetPenWidth() != penWidth)
    {
      if (!bVisibleOnly ||chartPtr->IsChartVisible())
      {
        chartPtr->SetPenWidth(penWidth);
        bRes = true;
      }
    }
    if (chartIdx != -1)
      break;
  }

  if (bRes && bRedraw && IsWindow(m_hWnd) && IsWindowVisible()&&HasChartWithData(chartIdx, true))
    UpdateContainerWnds();

  return bRes;
}

std::pair<float, bool> CChartContainer::GetChartTension(int chartIdx) const
{
  MAP_CHARTS::const_iterator it = m_mapCharts.find(chartIdx);
  if (it != m_mapCharts.cend())
    return make_pair(it->second->GetTension(), true);
  return make_pair(FLT_MAX, false);
}

bool CChartContainer::SetChartTension(int chartIdx, float fTension, bool bVisibleOnly, bool bRedraw)
{
  bool bRes = false;
  if (in_range(0.0f, 1.0f, fTension))
  {
    MAP_CHARTS::iterator it = (chartIdx != -1) ? m_mapCharts.find(chartIdx): m_mapCharts.begin();
    MAP_CHARTS::iterator itE = m_mapCharts.end();
    for (; it != itE; ++it)
    {
      if (it->second->GetTension() != fTension)
      {
        if (!bVisibleOnly || it->second->IsChartVisible())
        {
          it->second->SetTension(fTension);
          bRes = true;
        }
      }
      if (chartIdx != -1)
        break;
    }

    if (bRes && bRedraw && IsWindow(m_hWnd) && IsWindowVisible() && HasChartWithData(chartIdx, true))
      RefreshWnd();
  }
  return bRes;
}

std::pair<bool, bool> CChartContainer::AreChartPntsAllowed(int chartIdx) const
{
  MAP_CHARTS::const_iterator it = m_mapCharts.find(chartIdx);
  if (it != m_mapCharts.cend())
    return make_pair(it->second->ArePntsAllowed(), true);
  return make_pair(false, false);
}

bool CChartContainer::AllowChartPnts(int chartIdx, bool bAllowed, bool bVisibleOnly, bool bRedraw)
{
  bool bRes = false;
  MAP_CHARTS::iterator it = (chartIdx != -1) ? m_mapCharts.find(chartIdx) :
                                                            m_mapCharts.begin();
  MAP_CHARTS::iterator itE = m_mapCharts.end();
  for (; it != itE; ++it)
  {
    CChart* chartPtr = it->second;

    if (chartPtr->ArePntsAllowed() != bAllowed)
    {
      if (!bVisibleOnly || chartPtr->IsChartVisible())
      {
        it->second->AllowPnts(bAllowed);
        bRes = true;
      }
    }
    if (chartIdx != -1)
      break;
  }

  if (bRes && bRedraw && IsWindow(m_hWnd) && IsWindowVisible() && (-1 < HasChartDifferentPnts(chartIdx, true)))
    RefreshWnd();
 
  return bRes;
}

std::pair<Color, bool> CChartContainer::GetChartColor(int chartIdx) const
{
  if (chartIdx != -1)
  {
    MAP_CHARTS::const_iterator it = m_mapCharts.find(chartIdx);
    if (it != m_mapCharts.cend())
      return make_pair(it->second->GetChartColor(), true);
  }

  return make_pair(Color::Black, false);
}

bool CChartContainer::SetChartColor(int chartIdx, Color col, bool bVisibleOnly, bool bRedraw)
{
  bool bRes = false;

  MAP_CHARTS::iterator it = m_mapCharts.find(chartIdx);
  if (it != m_mapCharts.end())
  {
    CChart* chartPtr = it->second;
    if (chartPtr->GetChartColor().GetValue() != col.GetValue())
    {
      if (!bVisibleOnly || chartPtr->IsChartVisible())
      {
        it->second->SetChartColor(col); 
        if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible() && HasChartWithData(chartIdx, true))
          UpdateContainerWnds();
        bRes = true;
      }
    }
  }
  return bRes;
}

std::pair<DashStyle, bool> CChartContainer::GetChartDashStyle(int chartIdx) const
{
  if (chartIdx != -1)
  {
    MAP_CHARTS::const_iterator it = m_mapCharts.find(chartIdx);
    if (it != m_mapCharts.cend())
      return make_pair(it->second->GetChartDashStyle(), true);
  }

  return make_pair(DashStyleSolid, false);
}

bool CChartContainer::SetChartDashStyle(int chartIdx, DashStyle dashStyle, bool bVisibleOnly, bool bRedraw)
{
  bool bRes = false;
  MAP_CHARTS::iterator it = (chartIdx != -1) ? m_mapCharts.find(chartIdx): m_mapCharts.begin();
  MAP_CHARTS::iterator itE = m_mapCharts.end();

  for (; it != itE; ++it)
  {
    CChart* chartPtr = it->second;

    if (chartPtr->GetChartDashStyle() != dashStyle)
    {
      if (!bVisibleOnly || chartPtr->IsChartVisible())
      {
        chartPtr->SetDashStyle(dashStyle);
        bRes = true;
      }
    }
    if (chartIdx != -1)
      break;
  }

  if (bRes && bRedraw && IsWindow(m_hWnd) && IsWindowVisible() && (-1 <HasChartDifferentPnts(chartIdx, true)))
    UpdateContainerWnds();

  return bRes;
}
  
int CChartContainer::HasSelectedChart(void) const
{
  MAP_CHARTS::const_iterator it = m_mapCharts.begin();
  MAP_CHARTS::const_iterator itE = m_mapCharts.end();
  for (; it != itE; ++it)
  {
    if (it->second->IsChartSelected())
      return it->first;
  }
  return -1;
}

int CChartContainer::SelectChart(int chartIdx, bool bRedraw)
{
  bool bSel = true;
  CChart* chartPtr = NULL;
  if (m_chartSelIdx != -1)    // Has chart to deselect
  {
    chartPtr = GetChart(m_chartSelIdx);
    if (chartPtr != NULL)     // Somebody programmatically deleted before this call
      chartPtr->SelectChart(false);
    if ((chartIdx == -1)||(chartIdx == m_chartSelIdx))
    {
      m_chartSelIdx = -1;
      bSel = false;
    }
  }

  if (bSel)      // New selection was requested
  {
    chartPtr = GetVisibleChart(chartIdx);
    if (chartPtr != NULL)
    {
      chartPtr->SelectChart(true);
      m_chartSelIdx = chartPtr->GetChartIdx();
    }
    else
      m_chartSelIdx = -1;
  }

  if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible() && HasChartWithData(chartIdx, true))
    RefreshWnd();    

  return m_chartSelIdx;

}

int CChartContainer::SelectChartByMouseClick(CPoint pnt, bool bRedraw)
{
  if (!m_mapCharts.empty())
  {
    PointF epsF(CHART_DTPNTSZ, CHART_PNTSTRSH);
    PointF pntF(float(pnt.x), float(pnt.y));

    PointD leftPntD = m_pMatrixD->TransformToPntD(1.0, pntF - epsF);
    PointD rightPntD = m_pMatrixD->TransformToPntD(1.0, pntF + epsF);

    CChart* chartPtr;
    MAP_CHARTS::iterator it = m_mapCharts.begin();
    MAP_CHARTS::iterator itE = m_mapCharts.end();

    V_CHARTDATAF vDataPntsF;

    int chartIdx = -1;
    
    for (; it != itE; ++it)
    {
      chartPtr = it->second;
      if (!chartPtr->HasData())
        continue;
      if (!chartPtr->IsChartVisible())
        continue;
// Now look for sel pnts: Convert to the scteen pntsF
      V_CHARTDATAD vDataPntsD = chartPtr->m_vDataPnts;
      chartPtr->ConvertChartData(vDataPntsD, vDataPntsF, m_pMatrixD, leftPntD.X, rightPntD.X);
      if (vDataPntsF.size() > 0)
      {
        V_CHARTDATAF::iterator itData = find_if(vDataPntsF.begin(), vDataPntsF.end(),
                                                     in_vicinity<double>(pntF, epsF));
        if (itData != vDataPntsF.end())
        {
          chartIdx = chartPtr->GetChartIdx();
          break;
        }
      }
    }
// ChartIdx == -1 if click is not in vicinity of any data point: deselect selected or select forst visible
    SelectChart(chartIdx == -1 ? m_chartSelIdx : chartIdx, bRedraw);
  }

  return m_chartSelIdx;
}

// Chart Y axis precision
int CChartContainer::GetChartPrecisionY(int chartIdx) const
{
  MAP_CHARTS::const_iterator it = m_mapCharts.find(chartIdx);
  if (it != m_mapCharts.cend())
    return it->second->GetPrecisionY();
  return INT_MAX;
}

bool CChartContainer::SetChartPrecisionY(int chartIdx, int precisionY, bool bVisibleOnly, bool bRedraw)
{
  bool bRes = false;
  MAP_CHARTS::iterator it = (chartIdx != -1) ? m_mapCharts.find(chartIdx) :
                                                               m_mapCharts.begin();
  MAP_CHARTS::iterator itE = m_mapCharts.end();
  for (; it != itE; ++it)
  {
    if (!bVisibleOnly || it->second->IsChartVisible())
    {
      if (it->second->GetPrecisionY() != precisionY)
      {
        it->second->SetPrecisionY(precisionY);
        bRes = true;
      }
    }
    if (chartIdx != -1)
      break;
  }

  if (bRes && bRedraw)
  {
    if (IsLabWndVisible(false))
      UpdateDataLegend(false);    
    if (chartIdx == -1)
      chartIdx = m_dataViewChartIdx;
    UpdateDataView(chartIdx, F_NODATACHANGE);
  }
  return bRes;
}

string_t CChartContainer::GetChartName(int chartIdx) const
{
  MAP_CHARTS::const_iterator it = m_mapCharts.find(chartIdx);
  if (it != m_mapCharts.cend())
    return it->second->GetChartName();
  return string_t(_T(""));     // Not a name at all
}

bool CChartContainer::ChangeChartName(int chartIdx, string_t name, bool bRedraw)
{
  MAP_CHARTS::iterator itCh = m_mapCharts.find(chartIdx);
  MAP_CHARTS::iterator it = m_mapCharts.begin();
  MAP_CHARTS::iterator itE = m_mapCharts.end();

  if (itCh != itE)                    // Chart is found
  {
    name = NormalizeString(name, STR_MAXLEN, STR_NORMSIGN); 

    for (; it != itE; ++it)           // Check for uniqueness
    {
      if (it->second->GetChartName() == name)
        return false;
    }
    itCh->second->SetChartName(name); // Assign the new nane
    
    if (bRedraw)
      UpdateContainerWnds(chartIdx);

    return true;
  }
  return false;
}

// Chart Y data name
string_t CChartContainer::GetChartYValName(int chartIdx) const
{
  MAP_CHARTS::const_iterator it = m_mapCharts.find(chartIdx);
  if (it != m_mapCharts.cend())
    return it->second->GetAxisYName();
  return string_t(_T(""));    
}

bool CChartContainer::ChangeChartYValName(int chartIdx, string_t nameY, bool bVisibleOnly, bool bRedraw)
{
  bool bRes = false;

  nameY = NormalizeString(nameY, STR_MAXLEN, STR_NORMSIGN);
  MAP_CHARTS::iterator it = chartIdx != -1 ? m_mapCharts.find(chartIdx) :
                                                                      m_mapCharts.begin();
  MAP_CHARTS::iterator itE = m_mapCharts.end();
  for (; it != itE; ++it)
  {
    if (!bVisibleOnly || it->second->IsChartVisible())
    {
      string_t oldNameY = it->second->GetAxisYName();
      if (oldNameY != nameY)
      {
        it->second->SetAxisYName(nameY);
        bRes = true;
      }
    }
    if (chartIdx != -1)
      break;
  }
   
  if (bRes &&bRedraw)
  {
    if (IsLabWndVisible(false))
      UpdateDataLegend(false);
    UpdateDataView(chartIdx != -1 ? chartIdx : m_dataViewChartIdx, F_NODATACHANGE);
  }
  return bRes;
}

// Function to get Y val strung function
val_label_str_fn CChartContainer::GetLabYValStrFnPtr(int chartIdx) const
{
  MAP_CHARTS::const_iterator it = m_mapCharts.find(chartIdx);
  if (it != m_mapCharts.cend())
    return it->second->GetLabYValStrFnPtr();
  return NULL;
}

bool CChartContainer::SetLabYValStrFn(int chartIdx, val_label_str_fn pLabYValStrFn, bool bVisibleOnly, bool bRedraw)
{
  bool bRes = false;

  MAP_CHARTS::iterator it = chartIdx != -1 ? m_mapCharts.find(chartIdx): m_mapCharts.begin();
  MAP_CHARTS::iterator itE = m_mapCharts.end();

  for (; it != itE; ++it)
  {
    if (!bVisibleOnly || it->second->IsChartVisible())
    {
      val_label_str_fn oldFnPtr = it->second->GetLabYValStrFnPtr();
      if (oldFnPtr != pLabYValStrFn)
      {
        it->second->SetLabYValStrFn(pLabYValStrFn);
        bRes = true;
      }
    }
    if (chartIdx != -1)
      break;
  }
   
  if (bRes && bRedraw)
  {
    if (IsLabWndVisible(false))
      UpdateDataLegend(false);
    UpdateDataView(chartIdx != -1 ? chartIdx : m_dataViewChartIdx, F_NODATACHANGE);
  }  
 
  return bRes;
}

CChart* CChartContainer::GetChart(int chartIdx, bool bVisibleOnly) const
{
  CChart* chartPtr = NULL;
  MAP_CHARTS::const_iterator it = chartIdx == -1 ? m_mapCharts.cbegin() : m_mapCharts.find(chartIdx);
  for (; it != m_mapCharts.end(); ++it)
  {
    chartPtr = it->second;
    if (!bVisibleOnly || chartPtr->IsChartVisible())
    {
      if (chartPtr != NULL)
        break;
    }
    if (chartIdx != -1)
      break;
  }
  return chartPtr;
}

V_CHARTDATAD* CChartContainer::GetChartData(int chartIdx)
{
  CChart* chartPtr = GetChart(chartIdx);
  if (chartPtr != NULL)
    return &(chartPtr->m_vDataPnts);
  return NULL;
}

CChart* CChartContainer::FindChartByName(string_t nameStr) const
{
  CChart* chartPtr = NULL;
  MAP_CHARTS::const_iterator it = m_mapCharts.cbegin();
  MAP_CHARTS::const_iterator itE = m_mapCharts.cend();
  for (; it != itE; ++it)
  {
    if ( it->second->GetChartName() == nameStr)
    {
      chartPtr = it->second;
      break;
    }
  }
  return chartPtr;
}

int CChartContainer::GetChartIdx(string_t chartName) const
{
  CChart* chartPtr = FindChartByName(chartName);
  if (chartPtr != NULL)
    return chartPtr->GetChartIdx();
  return -1;
}

std::pair<double, bool>CChartContainer::GetChartLocScaleY(int chartIdx) const
{
  MAP_CHARTS::const_iterator it = m_mapCharts.find(chartIdx);
  if (it != m_mapCharts.cend())
    return make_pair(it->second->GetLocScaleY(), true);
  return make_pair(0.0, false);
}

bool CChartContainer::UpdateChartLocScaleY(int chartIdx, double locScY, bool bVisibleOnly, bool bRedraw)
{
  bool bRes = false;
  bool bChangeView = false;

  MAP_CHARTS::iterator it = (chartIdx != -1) ? m_mapCharts.find(chartIdx) :
                                                                m_mapCharts.begin();
  MAP_CHARTS::iterator itE = m_mapCharts.end();
  for (; it != itE; ++it)
  {
    CChart* chartPtr = it->second;
    if (!bVisibleOnly || chartPtr->IsChartVisible())
    {
      double locScaleY = chartPtr->GetLocScaleY();
      if (locScaleY != locScY)
      {
        chartPtr->SetLocScaleY(max(0.1, locScY));
        bRes = true;
        if (!bChangeView && bRedraw && chartPtr->IsChartVisible() && chartPtr->HasData())
          bChangeView = true;
      }
    }
    if (chartIdx != -1)
      break;
  }

  if (bChangeView)
  {
    if (IsLabWndVisible(false))
      UpdateDataLegend(false);
    else
      RefreshWnd();
  }
  return bRes;
}

bool CChartContainer::ZoomMoveContainerX(double startX, double endX, bool bRedraw)
{
  if (startX == endX)
    return false;

  double fStartX = GetInitialStartX();
  double fEndX   = GetInitialEndX();

// Don't zoom/move an empty space: one of the points should be inside
  if (in_range(fStartX, fEndX, startX) || in_range(fStartX, fEndX, endX))
  {
    m_vHistX.push_back(PAIR_POS(m_startX, m_endX));
    m_startX = min(startX, endX);
    m_endX   = max(startX, endX);
    m_chModeX = MODE_ZOOMX;   // All prepared but not rendered
    if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible() && HasChartWithData(-1, true))
    {
      if (m_bTracking && IsLabWndExist(false))
        UpdateDataLegend(true);
      else
        RefreshWnd();
    }
    return true;
  }
  return false;
}

CH_MODE CChartContainer::ZoomContainerX(double startX, double endX, bool bRedraw)
{
  if (true == ZoomMoveContainerX(startX, endX, false))
  {
    if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible())
    {
      if (m_bTracking && IsLabWndExist(false))
        UpdateDataLegend(true);
      else
        RefreshWnd();
      m_chModeX = MODE_ZOOMEDX; // Old extensionX is in m_dqHist
    }
    else
      m_chModeX = MODE_ZOOMX;   // Wait for redrawing
  }
  else
    m_chModeX = m_vHistX.empty() ? MODE_FULLX : MODE_ZOOMEDX;

  return m_chModeX;
 }

CH_MODE CChartContainer::MoveContainerChartsX(double deltaX, bool bRedraw)
{
  if (true == ZoomMoveContainerX(m_startX - deltaX, m_endX - deltaX, false))
  {
    if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible())
    {
      if (m_bTracking && IsLabWndExist(false))
        UpdateDataLegend(true);
      else
        RefreshWnd();
      m_chModeX = MODE_ZOOMEDX; // Old extensionX is in m_dqHist
    }
    else
      m_chModeX = MODE_ZOOMX;   // Wait for redrawing
  }
  else
    m_chModeX = m_vHistX.empty() ? MODE_FULLX : MODE_ZOOMEDX;

  return m_chModeX;
 }

bool CChartContainer::ZoomMoveContainerY(double startY, double endY, bool bMoving, bool bRedraw)
{
  if (startY == endY)
    return false;

  if (startY > endY)
    swap(startY, endY);

// Don't zoom/move an empty space: one of the points should be inside the band
  if (HasVisiblePntsDInYBand(-1, startY, endY))
  {
    m_vHistY.push_back(PAIR_POS(m_minExtY, m_maxExtY));
    m_minExtY = startY;
    m_maxExtY = endY;
    if (bMoving && ((m_chModeY == MODE_FULLY)||(m_chModeY == MODE_MOVEDY))) 
      m_chModeY = MODE_MOVEY;
    else
      m_chModeY = MODE_ZOOMY;   // All prepared but not rendered

    if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible())
    {
      if (bMoving && (m_chModeY == MODE_FULLY))
        m_chModeY = MODE_MOVEDY;
      else
        m_chModeY = MODE_ZOOMEDY;   // Had zoom Y before

      if (m_bTracking && IsLabWndExist(false))
        UpdateDataLegend(true);
      else
        RefreshWnd();
    }
    return true;
  }
  return false;
}

CH_MODE CChartContainer::ZoomContainerY(double startY, double endY, bool bRedraw)
{
// Get real minmax ext;
  double rangeY = endY - startY;      // It is called from menu, so startY < endY
  endY    = startY + 0.9*rangeY;      // 0.9 screen height down from the top
  startY += 0.1*rangeY;               // 0.1 screen height up from the bottom

  if (true == ZoomMoveContainerY(startY, endY, false, false))  // Sets chModeY to MODE_ZOOMY on success
  {
    if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible())
    {
      if (m_bTracking && IsLabWndExist(false))
        UpdateDataLegend(true);
      else
        RefreshWnd();

      m_chModeY = MODE_ZOOMEDY; // Old extensionX is in m_dqHist
    }

  }
  
  return m_chModeY;
}

CH_MODE CChartContainer::MoveContainerChartsY(double deltaY, bool bRedraw)
{
  if (true == ZoomMoveContainerY(m_minExtY - deltaY, m_maxExtY - deltaY, true, false)) // Sets modeY to MODE_MOVEY
  {                                                                                    // or MODE_ZOOMY (if was zoomed previously
    if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible())
    {
      if (m_bTracking && IsLabWndExist(false))
        UpdateDataLegend(true);
      else
        RefreshWnd();

      if (m_chModeY == MODE_MOVEY)
        m_chModeY = MODE_MOVEDY;
      else
        m_chModeY = MODE_ZOOMEDY;
    }
  }

  return m_chModeY;
 }


LRESULT CChartContainer::SendNotification(UINT code, int chartIdx)
{
  NMCHART nmchart;
  nmchart.hdr.hwndFrom = m_hWnd;
  nmchart.hdr.idFrom = GetDlgCtrlID();
  nmchart.hdr.code = code;
  nmchart.chartIdx = chartIdx;

  switch (code)
  {
  case CODE_VISIBILITY: nmchart.bState = IsChartVisible(chartIdx);            break;
  case CODE_SHOWPNTS:   nmchart.bState = AreChartPntsAllowed(chartIdx).first; break;
  case CODE_EXTX:  
  case CODE_EXTY:
                        nmchart.minX   = GetStartX();
                        nmchart.maxX   = GetEndX();
                        nmchart.minY   = GetMinY();
                        nmchart.maxY   = GetMaxY();
                        break;
  case CODE_REFRESH:    nmchart.minX   = GetInitialStartX();
                        nmchart.maxX   = GetInitialEndX();
                        nmchart.minY   = GetInitialMinExtY();
                        nmchart.maxY   = GetInitialMaxExtY();
                        break;
  case CODE_SAVEIMAGE:
  case CODE_SAVEDIMAGE: 
  case CODE_SAVECHARTS:
  case CODE_SAVEDCHARTS: 
  case CODE_PRINTING:
  case CODE_PRINTED:    
  case CODE_SCY:        break;
  case CODE_TRACKING:   nmchart.bState = m_bTracking;
                        break;
  default:              return 0;
  }

  CWnd* parentPtr = (CWnd*)GetParent();
  if (parentPtr != NULL)
    return parentPtr->SendMessage(WM_NOTIFY, WPARAM(nmchart.hdr.hwndFrom), LPARAM(&nmchart));
  return 0;
}

// Chart interface functions
int CChartContainer::AddChart(bool bVisible, bool bShowPnts, string_t label,
                               string_t labelY, int precisionY,
                               DashStyle dashStyle, float penWidth, float tension, 
                               Color colChart, V_CHARTDATAD& vData, bool bRedraw)
{
  int chartIdx = GetMaxChartIdx() + 1;
  bool bAddIdx = false;
  if (!label.empty())
  {
    label = NormalizeString(label, STR_MAXLEN, STR_NORMSIGN);
    CChart* twinPtr = FindChartByName(label);
    if (twinPtr != NULL)
      bAddIdx = true;
  }
  else
  {
    label = string_t(_T("Cnart"));
    bAddIdx = true;
  }

  if (bAddIdx)
  {
    _TCHAR buffer_t[64];
    _itot_s(chartIdx, buffer_t, 10);  // Chart idx to string
    string_t idxStr(buffer_t);
    label += string_t(_T("_")) + string_t(buffer_t);
  }

  CChart* chartPtr = new CChart;   

  chartPtr->SetChartAttr(bVisible, bShowPnts, chartIdx, label, labelY, 
                                    precisionY, dashStyle, penWidth, tension, colChart);

  size_t dataSize = vData.size();

// Now transfer data and set min max values
  if (dataSize > 0)
  {
    chartPtr->m_vDataPnts.assign(vData.begin(), vData.end());
    chartPtr->m_vDataPnts.shrink_to_fit();
 
// It is cheaper to sort right away than to look for max/min x and sort later if needed
    if (dataSize > 1)
      std::sort(chartPtr->m_vDataPnts.begin(), chartPtr->m_vDataPnts.end(), less_pnt<double, false>());

    double minValX = chartPtr->m_vDataPnts.front().X;
    double maxValX = chartPtr->m_vDataPnts.back().X;

// Find min and max Y; works even for one-point vector
    PAIR_ITS pair_minmaxY = 
        minmax_element(chartPtr->m_vDataPnts.begin(), chartPtr->m_vDataPnts.end(), 
                                                         less_pnt<double, true>());
    double minValY = pair_minmaxY.first->Y;
    double maxValY = pair_minmaxY.second->Y;

// Save in the CChart
    chartPtr->SetMinValX(minValX);
    chartPtr->SetMaxValX(maxValX);
    chartPtr->SetMinValY(minValY);
    chartPtr->SetMaxValY(maxValY);
  }

// Just in case: idx is unique for this container
  if (m_mapCharts.insert(MAP_CHARTS::value_type(chartPtr->GetChartIdx(), chartPtr)).second == false)
  {
    delete chartPtr;
    return -1;
  }

// Now update the container's min maxes, saving the history of X
  if (dataSize > 0)
  {
// Wil automatically take care of previous one-point charts
    UpdateExtX(chartPtr->GetMinValX(), chartPtr->GetMaxValX());
    UpdateExtY(chartPtr->GetMinValY(), chartPtr->GetMaxValY());

    if (IsWindow(m_hWnd) && m_bTracking && IsLabWndExist(false))
      PrepareDataLegend(m_dataLegPntD, m_epsX, m_pDataWnd->m_mapLabs, m_mapSelPntsD, true);

    if (bRedraw && IsWindow(m_hWnd)&&IsWindowVisible())
      UpdateContainerWnds(-1, true);
  }

  return chartIdx;
}

// Overload for time series
int CChartContainer::AddChart(bool bVisible, bool bShowPnts, string_t label, 
                              string_t labelY, int precisionY, DashStyle dashStyle, 
                              float penWidth, float tension, 
                              Color colChart, std::vector<double>& vTmSeries, 
                              double startX, double stepX, bool bRedraw)
{
  V_CHARTDATAD vData(vTmSeries.size());
  if (vData.size() > 0)
    transform(vTmSeries.begin(), vTmSeries.end(), vData.begin(), time_series_to_pnt<double>(startX, stepX));

  return AddChart(bVisible, bShowPnts, label, labelY, precisionY, 
                                dashStyle, penWidth, tension, colChart, vData, bRedraw);
}

int CChartContainer::AddChart(bool bVisible, bool bShowPnts, string_t label, 
                string_t labelY, int precisionY, DashStyle dashStyle, 
                float penWidth, float tension, Color colChart, 
                std::vector<std::pair<double, double> >& vXYData, bool bRedraw)
{
  V_CHARTDATAD vData(vXYData.size());
  if (vData.size() > 0)
    transform(vXYData.begin(), vXYData.end(), vData.begin(), xy_to_pnt<double>());

  return AddChart(bVisible, bShowPnts, label, labelY, precisionY, 
                                dashStyle, penWidth, tension, colChart, vData, bRedraw);
}

int CChartContainer::AddChart(bool bVisible, bool bShowPnts, string_t label, string_t labelY,
                int precisionY, DashStyle dashStyle, float penWidth, float tension, 
                Color colChart, std::vector<double>& vX, std::vector<double>& vY, 
                bool bRedraw)
{
  size_t sizeX = vX.size();
  size_t sizeY = vY.size();
  if (sizeX != sizeY)
    return -1;

  V_CHARTDATAD vData(sizeX);
  if (vData.size() > 0)
  {
    for (size_t i = 0; i < sizeX; ++i)
      vData[i] = PointD(vX[i], vY[i]);
  }
  return AddChart(bVisible, bShowPnts, label, labelY, precisionY, 
                                dashStyle, penWidth, tension, colChart, vData, bRedraw);
}

// Appends points to the end of chart data vector. Discards all new points with X < m_endX
bool CChartContainer::AppendChartData( int chartIdx, V_CHARTDATAD& vData, bool bUpdate)
{
  size_t dataSize = vData.size();
  if (dataSize == 0)
    return false;

  MAP_CHARTS::iterator it = m_mapCharts.find(chartIdx); 
  if (it != m_mapCharts.end())
  {
    V_CHARTDATAD vLocData(vData);
// Sort; it is cheaper than search for min/max X and check whether they are at start and end
    if (dataSize > 1)
      sort(vLocData.begin(), vLocData.end(), less_pnt<double, false>());

    CChart* chartPtr = it->second;  // Append data vector
    size_t appended = chartPtr->AppendChartData(vLocData);
    if (appended == 0)
      return false;

    if (IsLabWndExist(false))     // Might update m_pMatrix if m_dataLegPntD is in X range
      PrepareDataLegend(m_dataLegPntD, m_epsX, m_pDataWnd->m_mapLabs, m_mapSelPntsD, true);  

    UpdateDataView(chartIdx, F_APPEND);

    if (bUpdate && chartPtr->IsChartVisible())  // Update and display if only one chart has to be processed
    {
      UpdateExtX(chartPtr->GetMinValX(), chartPtr->GetMaxValX()); // New startX/endX are at the front dqHist
      UpdateExtY(chartPtr->GetMinValY(), chartPtr->GetMaxValY()); // New startY/endY are at the front dqHist

      if (IsWindow(m_hWnd) && IsWindowVisible())
        UpdateContainerWnds(chartIdx, true);
    }
    return true;
  }
  return false;
}

// Overload for time series
bool CChartContainer::AppendChartData(int chartIdx,  std::vector<double>& vTmSeries,  double startX,
                                                                              double stepX, bool bUpdate)
{
  if (vTmSeries.size() == 0)
    return false;

  V_CHARTDATAD vData(vTmSeries.size());
  transform(vTmSeries.begin(), vTmSeries.end(), vData.begin(), time_series_to_pnt<double>(startX, stepX));

  return AppendChartData(chartIdx, vData, bUpdate);
}

// Overload for vector of pairs
bool CChartContainer::AppendChartData(int chartIdx,  
              std::vector<std::pair<double, double> >& vXYData, bool bUpdate)
{
  if (vXYData.size() == 0)
    return false;

  V_CHARTDATAD vData(vXYData.size());
  transform(vXYData.begin(), vXYData.end(), vData.begin(), xy_to_pnt<double>());

  return AppendChartData(chartIdx, vData, bUpdate);
}

// Overload for vectors of doubles
bool CChartContainer::AppendChartData(int chartIdx,  
              std::vector<double>& vX, std::vector<double>& vY, bool bUpdate)
{
  size_t sizeX = vX.size();
  size_t sizeY = vY.size();
  if (sizeX != sizeY)
    return false;
  if (sizeX == 0)
    return false;

  V_CHARTDATAD vData(sizeX);
  for (size_t i = 0; i < sizeX; ++i)
    vData[i] = PointD(vX[i], vY[i]);

  return AppendChartData(chartIdx, vData, bUpdate);
}

bool CChartContainer::ReplaceChartData(int chartIdx, V_CHARTDATAD& vData, bool bClip, 
                                                     bool bUpdate, bool bVerbose, bool bRedraw)
{
  MAP_CHARTS::iterator mapIt = m_mapCharts.find(chartIdx);
  if (mapIt == m_mapCharts.end())
    return false;                           // Wrong Chart ID

 // Warning and reset if (bReset)                               
  if (bVerbose && (AfxMessageBox(CString("You will replace all old chart data. Replace anyway?"), 
                                                      MB_YESNO|MB_ICONQUESTION) == IDNO)) 
    return false;

// Sort input vector; it is cheaper than check first whether it was sorted
  V_CHARTDATAD vLocData(vData);
  if (vLocData.size() > 1)
    sort(vLocData.begin(), vLocData.end(), less_pnt<double, false>());

  CChart* chartPtr = mapIt->second;
  
// Define what take from vData
  V_CHARTDATAD::iterator itStart = vLocData.begin();
  V_CHARTDATAD::iterator itEnd = vLocData.end();

  if (bClip) // Asked to keep the new range inside the old startX, endX
  {
    PAIR_ITS pair_its = find_border_pnts(vLocData.begin(), vLocData.end(),
       not_inside_range<double, false>(GetInitialStartX(), GetInitialEndX()));
    itStart = pair_its.first;
    itEnd   = pair_its.second;
    if (itEnd != vLocData.end())
      ++itEnd;
  }

  chartPtr->m_vDataPnts.assign(itStart, itEnd);  
  chartPtr->m_vDataPnts.shrink_to_fit();

  size_t dataSize = chartPtr->m_vDataPnts.size();

// Set new min/max
  double startX = dataSize == 0 ? DBL_MAX : chartPtr->m_vDataPnts.front().X;
  double endX   = dataSize == 0 ? -DBL_MAX : chartPtr->m_vDataPnts.back().X;
  chartPtr->SetMinValX(startX);
  chartPtr->SetMaxValX(endX);

// Update scales - indirect
  double minY = DBL_MAX, maxY = -DBL_MAX;
  if (dataSize > 0)
  {
    std::pair< V_CHARTDATAD::iterator, V_CHARTDATAD::iterator> pair_minmaxY =
         minmax_element(chartPtr->m_vDataPnts.begin(), chartPtr->m_vDataPnts.end(), less_pnt<double, true>());
    minY = pair_minmaxY.first->Y;
    maxY = pair_minmaxY.second->Y;
  }

  chartPtr->SetMinValY(minY);   
  chartPtr->SetMaxValY(maxY);
  chartPtr->SetLocScaleY(1.0);

// Update m_startX,m_endX, min and max Y
  if (!HasChartWithData(-1))
  {
    ResetChartContainer(true);
  } 
  else
  {
    if (IsLabWndExist(false))     // Might update m_pMatrix if m_dataLegPntD is in X range
      PrepareDataLegend(m_dataLegPntD, m_epsX, m_pDataWnd->m_mapLabs, m_mapSelPntsD, true);  

    UpdateDataView(chartIdx, F_REPLACE);

    if (bUpdate)   // Update and display 
    {
      SetExtX();
      SetExtY();

      if (!HasVisiblePntsDInYBand(-1, m_minExtY, m_maxExtY))
        ResetCharts();
      else
      {
        if (!m_vHistX.empty())
          m_vHistX.erase(++m_vHistX.begin(), m_vHistX.end());
        if (!m_vHistY.empty())
          m_vHistY.erase(++m_vHistY.begin(), m_vHistY.end());
      }
    }
  }

  if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible())
      UpdateContainerWnds(chartIdx, true);

  return true;
}

// Overload for time series
bool CChartContainer::ReplaceChartData(int chartIdx,  std::vector<double>& vTmSeries, 
                double startX, double stepX, bool bClip, bool bUpdate, bool bVerbose, bool bRedraw)
{
  V_CHARTDATAD vData;
  if (!vTmSeries.empty())
    vData.resize(vTmSeries.size());
  transform(vTmSeries.begin(), vTmSeries.end(), vData.begin(), time_series_to_pnt<double>(startX, stepX));

  return ReplaceChartData(chartIdx, vData, bClip, bUpdate,  bVerbose, bRedraw);
}

// Overload for vector of pairs
bool CChartContainer::ReplaceChartData(int chartIdx,  
    std::vector<std::pair<double, double> >& vXYData, bool bClip, bool bUpdate, bool bVerbose, bool bRedraw)
{
  V_CHARTDATAD vData;
  if (!vXYData.empty())
    vData.resize(vXYData.size());
  transform(vXYData.begin(), vXYData.end(), vData.begin(), xy_to_pnt<double>());

  return ReplaceChartData(chartIdx, vData, bUpdate, bClip, bVerbose, bRedraw);
}
// Overload for vectors of doubles
bool CChartContainer::ReplaceChartData(int chartIdx,  
      std::vector<double>& vX, std::vector<double>& vY, bool bClip, bool bUpdate, bool bVerbose, bool bRedraw)
{

  size_t sizeX = vX.size();
  size_t sizeY = vY.size();
  if (sizeX != sizeY)
    return false;

  V_CHARTDATAD vData;
  if (sizeX > 0)
    vData.resize(sizeX);
  for (size_t i = 0; i < sizeX; ++i)
    vData[i] = PointD(vX[i], vY[i]);

  return ReplaceChartData(chartIdx, vData, bClip, bUpdate, bVerbose, bRedraw);
}

void CChartContainer::ResetCharts(bool bResetLocY)  
{
// Restore startX, endX
  m_startX = GetInitialStartX();
  m_endX   = GetInitialEndX();
  m_vHistX.clear();

  m_minExtY = GetInitialMinExtY();
  m_maxExtY = GetInitialMaxExtY();
  m_vHistY.clear();

// Restore visibility
  MAP_CHARTS::iterator it = m_mapCharts.begin(), itE = m_mapCharts.end();
  for (; it != itE; ++it)
  {
    CChart* chartPtr = it->second;
    if (!chartPtr->IsChartVisible())
      chartPtr->ToggleChartVisibility();
    if (bResetLocY)
      chartPtr->SetLocScaleY(1.0);
  }

  m_chModeX = MODE_FULLX;
  m_chModeY = MODE_FULLY;

  UpdateContainerWnds(-1, true);
}

void CChartContainer::ResetChartContainer(bool bKeepCharts, bool bResetCols)
{
  CChartContainer* pContainer = new CChartContainer;

// Set defaula colors
  if (bResetCols)
  {
    m_colBkground     = pContainer->GetBkColor();
    m_colBorder       = pContainer->GetBorderColor();
    m_colAxis         = pContainer->GetAxesColor();
    m_colZoomBorder   = pContainer->GetZoomBorderColor();
    m_colLegPnts      = pContainer->GetLegPntsColor();
    m_colDataViewPnts = pContainer->GetDataViewPntsColor(); 
    m_colLegBorder    = pContainer->GetLegBkgndColor();
    m_colLegBorder    = pContainer->GetLegBorderColor();
    m_colGrid         = pContainer->GetGridColor();
  }

//  Set default values     
  m_minExtY           = pContainer->GetMinY();
  m_maxExtY           = pContainer->GetMaxY(); 
  m_startX            = pContainer->GetStartX();
  m_endX              = pContainer->GetEndX(); 
  m_axOffsXF          = pContainer->GetAxisOffsetX();
  m_axXPos            = pContainer->GetXAxisPos();
  m_axYPos            = pContainer->GetYAxisPos(); 
  m_scX               = pContainer->GetScX();
  m_scY               = pContainer->GetScY();;
  m_bTracking         = false;
  m_dataLegPntD       = pContainer->GetDataLegPntD();
  m_bAxBoundaries     = false;
  m_chartSelIdx       = -1;
  m_dataViewChartIdx  = -1;   // Ti be sure, must be -1 if there is no data view
  m_tmpZX             = 0.0;
  m_tmpZY             = 0.0;
  m_chModeX           = pContainer->GetChModeX();
  m_chModeY           = pContainer->GetChModeY();

  delete pContainer;

// Clear maps and vectors
  if (!bKeepCharts && !m_mapCharts.empty())
  {
    MAP_CHARTS::iterator it = m_mapCharts.begin();
    MAP_CHARTS::iterator itE = m_mapCharts.end();
    for (; it != itE; ++it)
      delete it->second;
    m_mapCharts.clear();
  }
  m_mapDataViewPntsD.clear();
  m_vHistX.clear();
  m_vHistY.clear();
// Reset the matrix
  delete m_pMatrixD;
  m_pMatrixD = NULL;
// Get rid of the legend wnds
  m_pDataWnd = DestroyLabWnd(m_pDataWnd);  // Deletes m_mapSelPntsD, reset m_dataLegPntD
  m_pLegWnd  = DestroyLabWnd(m_pLegWnd);
// Clear the data view
 DestroyChartDataView();
}

bool CChartContainer::TruncateChart(int chartIdx, double startX, double endX, 
                                                          bool bUpdateExt, bool bRedraw)
{
  if (!HasChartWithData(chartIdx))                                 // No chart with data
    return false;

  if (startX >= endX)                                              // Invalid parameters
    return false;

  if ((GetInitialStartX() >= startX)&&(GetInitialEndX() <= endX))  // Nothing to truncate
    return false;

  startX = max(startX, GetInitialStartX());
  endX   = min(endX, GetInitialEndX());

  bool bCorrectDataView = false;

  size_t chartCnt = 0;
  MAP_CHARTS::iterator it = (chartIdx == -1) ? m_mapCharts.begin() :
                                                      m_mapCharts.find(chartIdx);
  for (; it != m_mapCharts.end(); ++it)
  {
    CChart* chartPtr = it->second;  // Append data vector 
    if (!chartPtr->HasData())
      continue;
    bool bRes = chartPtr->TruncateChartData(startX, endX);
    if (bRes)
    {
      ++chartCnt;
      if (m_dataViewChartIdx == chartPtr->GetChartIdx())
        bCorrectDataView = true;
    }

    if (chartIdx > -1)
      break;
  }

  if (chartCnt == 0)
    return false;

  if (bUpdateExt) // Update extensions X and Y
  {
    SetExtX();
    SetExtY();

    if (!HasVisiblePntsDInYBand(-1, m_minExtY, m_maxExtY))
      ResetCharts();
    else
    {
      if (!m_vHistX.empty())
        m_vHistX.erase(++m_vHistX.begin(), m_vHistX.end());
      if (!m_vHistY.empty())
        m_vHistY.erase(++m_vHistY.begin(), m_vHistY.end());
    }
  }

  if (bCorrectDataView)
    UpdateDataView(m_dataViewChartIdx, F_TRUNCATE);

  if (m_bTracking && IsLabWndExist(false))    // Will calculate and set the new translation matrix
    PrepareDataLegend(m_dataLegPntD, m_epsX, m_pDataWnd->m_mapLabs, m_mapSelPntsD, true);

// Now redraw
  if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible())
  {
    if (IsLabWndVisible(false))
      UpdateDataLegend(true);    // The matrix was not changed yet
    else
      RefreshWnd();               // The matrix will changed in OnPaint()
  }
  return true;
}

bool CChartContainer::RemoveChart(int chartIdx, bool bCorrectMinMax, bool bRedraw)
{
  MAP_CHARTS::iterator mapIt = m_mapCharts.find(chartIdx);
  if (mapIt == m_mapCharts.end())
    return false;

  CChart* chartPtr = mapIt->second;

  delete chartPtr;
  m_mapCharts.erase(chartIdx);

  if (!HasChartWithData(-1))
    ResetChartContainer(true);
  else
  {
    if (m_chartSelIdx == chartIdx)
    {
      m_chartSelIdx = -1;
    }

    if (m_dataViewChartIdx == chartIdx)
    {
     if (m_pChartDataView != NULL)
       DestroyChartDataView();
    }

    if (bCorrectMinMax)
    {
      SetExtX();
      SetExtY();

      if (!HasVisiblePntsDInYBand(-1, m_minExtY, m_maxExtY))
        ResetCharts();
      else
      {
        if (!m_vHistX.empty())
          m_vHistX.erase(++m_vHistX.begin(), m_vHistX.end());
        if (!m_vHistY.empty())
          m_vHistY.erase(++m_vHistY.begin(), m_vHistY.end());
      }

      if (IsLabWndExist(false))
        PrepareDataLegend(m_dataLegPntD, m_epsX, m_pDataWnd->m_mapLabs, m_mapSelPntsD, bCorrectMinMax);
    }
  }

  if (bRedraw && IsWindow(m_hWnd) && IsWindowVisible())
    UpdateContainerWnds();

  return true;
}

bool CChartContainer::ShowAxisXBoundaries(bool bSet, bool bRedraw)
{
  m_bAxBoundaries = HasChartWithData(-1) ? bSet : false;
  if (bRedraw)
    RefreshWnd();
  return m_bAxBoundaries;
}

// The function is called from OnLButtonDown; the container is visible
CDataWnd* CChartContainer::ShowDataLegend(CPoint pnt)
{
  if (m_mapCharts.empty())            // No charts, no legends
    return m_pDataWnd;     

  if (m_pDataWnd == NULL)             // Need to allocate memory
  {
    CDataWnd* pDataWnd = new CDataWnd(m_colLegBkgnd, m_colLegBorder);
    if (pDataWnd == NULL)
      return NULL;
    m_pDataWnd = pDataWnd;
  }

// After PrepareDataLegend mapSelData might have some points out the range Y, but mapLabs might be empty  
  if (PrepareDataLegend(pnt, m_pDataWnd->m_mapLabs, m_mapSelPntsD) > 0)
  {                                   // Create dataWnd if needed, and calculate dataWnd rects
    m_pDataWnd->CreateLegend(this, pnt, true);
    RefreshWnd();
    m_pDataWnd->ShowLegendWnd();      // Garanteed m_pDataWnd exists
  }
  else
    m_pDataWnd = DestroyLabWnd(m_pDataWnd);

  return m_pDataWnd;
}

CDataWnd* CChartContainer::ShowDataLegend(double dataLegX)
{
  if (!IsWindow(m_hWnd)||!IsWindowVisible())  // Means container wnd was created and is visible
    return m_pDataWnd;
  if (m_mapCharts.empty())                    // No charts, no legends
    return m_pDataWnd;     

  MAP_LABSTR tmpMapStr;
  m_dataLegPntD = PointD(dataLegX, m_minExtY);
// After this point mapSelData might have some points out the range Y, but mapLabs might be empty  
  size_t selPnts = PrepareDataLegend(m_dataLegPntD, CHART_DTPNTSZ/m_scX, tmpMapStr, m_mapSelPntsD, false);
  if (selPnts > 0)
  {
// Set m_pDataLeg
    CDataWnd* pDataWnd = NULL;
    if (m_pDataWnd == NULL)
    {
      pDataWnd = new CDataWnd(m_colLegBkgnd, m_colLegBorder);
      if (pDataWnd == NULL)
        return NULL;
      m_pDataWnd = pDataWnd;
    }
    if (m_pDataWnd)
    {
      m_pDataWnd->m_mapLabs.swap(tmpMapStr);
      PointF dataLegPntF = m_pMatrixD->TransformToPntF(1.0, m_dataLegPntD); 
      CPoint pnt = CPointFromPntF(dataLegPntF);
      m_pDataWnd->CreateLegend(this, pnt, true);
      m_bTracking = true;

      RefreshWnd();
      m_pDataWnd->ShowLegendWnd();  // Garanteed m_pDataWnd exists
      return m_pDataWnd;
    }
  }

  m_bTracking = false;
  m_pDataWnd = DestroyLabWnd(m_pDataWnd);
  return m_pDataWnd;
}

CDataWnd* CChartContainer::ShowNamesLegend(void)
{
  if (IsWindow(m_hWnd)&&IsWindowVisible()&& !m_mapCharts.empty()&& HasChartWithData(-1)) 
  {
    MAP_LABSTR tmpMapStr;
    PrepareNamesLegend(tmpMapStr);
     
// No matter what, allocate memory because there are charts, may be hidden
    CDataWnd* pLegWnd = NULL;
    if (m_pLegWnd == NULL)
    {
      pLegWnd = new CDataWnd(m_colLegBkgnd, m_colLegBorder);
      if (pLegWnd == NULL)
        return NULL;
      m_pLegWnd = pLegWnd;
    }
    if (m_pLegWnd)
    {
      m_pLegWnd->m_mapLabs.swap(tmpMapStr);
// Create dataWnd if needed, and calculate dataWnd rects
      if (m_pLegWnd->CreateLegend(this, CPoint(0, 0), false) == true)
      {
        m_bHasNamesLeg = true;
        RefreshWnd();
        m_pLegWnd->ShowLegendWnd();  // Garanteed m_pLegWnd exists
      }
    }
  }
  else  // It makes no sense to keep memory if there is no container wnd, or charts in container, or chartst with data
    m_pLegWnd = DestroyLabWnd(m_pLegWnd);

  return m_pLegWnd;
}

bool CChartContainer::ShowChartPoints(int chartIdx, bool bShow, bool bRedraw)
{
  bool bRes = false;

  MAP_CHARTS::iterator it = chartIdx == -1 ? m_mapCharts.begin() :
                                                    m_mapCharts.find(chartIdx);
  for (; it != m_mapCharts.end(); ++it)
  {
    CChart* chartPtr = it->second;
    if ((chartPtr->GetMaxValX() > chartPtr->GetMinValX())||(chartPtr->GetMaxValY() > chartPtr->GetMinValY())) 
    {
      chartPtr->AllowPnts(bShow);
      if (chartPtr->IsChartVisible())
        bRes = true;
    }

    if (chartIdx != -1)
      break;
  }

  if (bRes && bRedraw && IsWindow(m_hWnd) && IsWindowVisible())
    UpdateContainerWnds();
  return bRes;
}


void CChartContainer::UpdateDataLegend(bool bChangeMatrix)
{
  if (m_bTracking && IsLabWndExist(false))
  {
    if (bChangeMatrix)
      SetTransformMatrixD();
    UpdateDataLegend(m_mapSelPntsD, m_pDataWnd->m_mapLabs);  

    RefreshWnd(); // UpdateDataLegend draws dataWnd; RefreshWnd redraws the region excluding dataWnd
  }
}

void CChartContainer::CopyDataLegendWnd(void)
{
  if (m_pMatrixD == NULL)
    SetTransformMatrixD();

  if (m_pDataWnd == NULL)
    m_pDataWnd = new CDataWnd(m_colLegBkgnd, m_colLegBorder);
  bool bRes = false;
  if ((m_pDataWnd != NULL)&&!IsWindow(m_pDataWnd->m_hWnd))  
    bRes = m_pDataWnd->CreateLegend(this);
  if (bRes)
    UpdateDataLegend(false);
}

// True for info on m_pLegWnd, false on m_pDataWnd
bool CChartContainer::IsLabWndVisible(bool bLeg) const
{
  CDataWnd* pDataWnd = bLeg ? m_pLegWnd : m_pDataWnd;
  if (pDataWnd != NULL)
    return (IsWindow(pDataWnd->m_hWnd) && pDataWnd->IsWindowVisible())? true : false;

  return false;
}

bool CChartContainer::IsLabWndExist(bool bLeg) const
{
  CDataWnd* pDataWnd = bLeg ? m_pLegWnd : m_pDataWnd;
  if (pDataWnd != NULL)
    return IsWindow(pDataWnd->m_hWnd)? true : false;

  return false;
}

CDataWnd* CChartContainer::DestroyLabWnd(CDataWnd* pWnd, bool bRedraw)
{
  if (pWnd != NULL)
  {
    if (IsWindow(pWnd->m_hWnd))   
    {
      pWnd->DestroyWindow();
      if (pWnd->IsDataLeg())
      {
        m_mapSelPntsD.clear();
        m_dataLegPntD = PointD(DBL_MAX, DBL_MAX);
// To remove selected data line
        if (bRedraw)
          RefreshWnd();
      }
      else
        m_bHasNamesLeg = false;
    } // IsWindow
    delete pWnd;      // Here mapLegs goes out
    pWnd = NULL;
  }
  return pWnd;
}

void CChartContainer::UpdateContainerWnds(int chartIdx, bool bMatrix, DATAVIEW_FLAGS dataChange)
{
  if (m_bTracking && IsLabWndExist(false))
  {
    if (HasChartWithData(-1))
      UpdateDataLegend(bMatrix);
    else
    {
      m_bTracking = false;
      m_pDataWnd = DestroyLabWnd(m_pDataWnd);
    }
  }
  else
    RefreshWnd();

  if (IsLabWndExist(true))
  {
    if (HasChartWithData(-1))
      ShowNamesLegend();
    else
      m_pLegWnd = DestroyLabWnd(m_pLegWnd);
  }

  UpdateDataView(chartIdx, dataChange);
  if (!HasChartWithData(-1))
  {
    delete m_pMatrixD;
    m_pMatrixD = NULL;
  }
}

bool CChartContainer::HideLabWnds(bool bRedraw)
{
  bool bRes = false;
  if (IsLabWndVisible(false))
  {
    bRes = true;
    m_pDataWnd->ShowWindow(SW_HIDE);
    if (bRedraw)    // To remove sel line
      RefreshWnd();
  }
  if (IsLabWndVisible(true))
  {
    bRes = true;
    m_pLegWnd->ShowWindow(SW_HIDE);
  }
  return bRes;
}

CChartDataView* CChartContainer::DestroyChartDataView(void)
{
  if (m_pChartDataView != NULL)
  {
    if (IsWindow(m_pChartDataView->m_hWnd))
      m_pChartDataView->DestroyWindow();
    m_dataViewChartIdx = -1;
    if (!m_mapDataViewPntsD.empty())
      m_mapDataViewPntsD.clear();
    delete m_pChartDataView;
    m_pChartDataView = NULL;
  }
  return m_pChartDataView;
}

// Protected helpers

// To get max chart Idx in the mapCharts
int CChartContainer::GetMaxChartIdx(void) const
{
  int chartIdx = -1;
  if (!m_mapCharts.empty())
  {
    MAP_CHARTS::const_reverse_iterator rIt = m_mapCharts.rbegin();
    chartIdx = rIt->first;
  }

  return chartIdx;
}

void CChartContainer::MousePntToPntD(CPoint pnt, PointD& dataPntD, MatrixD* pMatrixD, double locScY)
{
  ENSURE(pMatrixD != NULL);

  PointF pntF(static_cast<float>(pnt.x), static_cast<float>(pnt.y));
// Return to a native coordinates; in most uses pntD.Y is not important, hence 1.0
  dataPntD = pMatrixD->TransformToPntD(locScY, pntF);

  // Test for NAN
  if (dataPntD.X != dataPntD.X)
    dataPntD.X = DBL_MAX;
  if (dataPntD.Y != dataPntD.Y)
    dataPntD.Y = DBL_MAX;
}

// Calculate scaleX, scaleY
double CChartContainer::UpdateScaleX(const float drawRectWidth, double startX, double endX)
{
  double scX = 1.0;
  if (!m_mapCharts.empty())
  {
    double rangeX = endX - startX;
    scX = drawRectWidth/rangeX;
  }
  return scX;
}

double CChartContainer::UpdateScaleY(const float drawRectHeight, double minY, double maxY)
{
  double scY = 1.0;
  if (!m_mapCharts.empty())
  {
    double rangeY = maxY - minY;
    scY = drawRectHeight/rangeY;
  }
  return scY;
}

// Calculates scX, scY but does not set m_scX, m_scY, leaves it to the user
PAIR_DBLS CChartContainer::UpdateScales(const RectF drawRectF, 
                                  double startX, double endX, double minY, double maxY)
{
  if (m_mapCharts.empty())
    return make_pair(1.0, 1.0);

  RectF dRF = drawRectF;
  if ((m_chModeY == MODE_FULLY)||(m_chModeY == MODE_MOVEDY)||(m_chModeY == MODE_MOVEY))
    dRF.Inflate(0.0f, -0.1f*drawRectF.Height); // Reserve 20% to beautify full picture
  double scX = UpdateScaleX(dRF.Width, startX, endX);
  double scY = UpdateScaleY(dRF.Height, minY, maxY);
  return make_pair(scX, scY);
}

// Zooming X on mouse left click + MK_SHIFT: first click
double CChartContainer::BeginZoomingX(CPoint pnt, bool bDraw)
{
  m_chModeX = MODE_ZOOMINGX;
 
  PointD mousePntD;
  MousePntToPntD(pnt, mousePntD, m_pMatrixD);
  double startX = mousePntD.X;
  if (bDraw)
    DrawZoomBorderX(pnt);

  return startX;
}

// Zooming on mouse left click + MK_SHIFT: the second click
double CChartContainer::PrepareZoomX(CPoint pnt, double startX, bool bDraw)
{
  PointD mousePntD;
  MousePntToPntD(pnt, mousePntD, m_pMatrixD);
  double endX = mousePntD.X;

  PointF startPntF = m_pMatrixD->TransformToPntF(1.0, PointD(m_tmpZX, 0.0));

  float zoomW = fabs(pnt.x - startPntF.X);
  if (zoomW > DR_SPACE)
  {
    if (true == ZoomMoveContainerX(startX, endX, false)) // old m_endX, m_startX in m_vHistX
    {
      if (bDraw)
        DrawZoomBorderX(pnt);
    }
  }

  return endX;
}

// Restores the mode to ZOOM or FULLX after interruption between the first and second zoom clicks
CH_MODE CChartContainer::RestoreModeX(void)
{
  if (m_chModeX == MODE_ZOOMINGX)
  {
    if (m_vHistX.empty())
      m_chModeX = MODE_FULLX;
    else
      m_chModeX = MODE_ZOOMEDX;
  }
  return m_chModeX;
}

// Zooming Y on mouse double left click + MK_SHIFT: first click
double CChartContainer::BeginZoomingY(CPoint pnt, bool bDraw)
{
  PointD mousePntD;
  MousePntToPntD(pnt, mousePntD, m_pMatrixD);
  double startY = mousePntD.Y;
  if (bDraw)
  {
    RefreshWnd();         // Because first click of a double draws zoom X boundary
    DrawZoomBorderY(pnt);
  }

  m_chModeY = MODE_ZOOMINGY;
  return startY;
}

double CChartContainer::PrepareZoomY(CPoint pnt, double startY, bool bDraw)  // Second click
{
  PointD mousePntD;
  MousePntToPntD(pnt, mousePntD, m_pMatrixD);
  double endY = mousePntD.Y;

  PointF startPntF = m_pMatrixD->TransformToPntF(1.0, PointD(0.0, startY));
  float zoomW = fabs(pnt.y - startPntF.Y);

  if (zoomW > DR_SPACE)
  {
    if (true == ZoomMoveContainerY(startY, endY, false, false)) // old m_endX, m_startX in m_vHistY
    {
      if (bDraw)
        DrawZoomBorderY(pnt);
    }
  }

  return endY;
}

CH_MODE CChartContainer::RestoreModeY(void)
{
  if (m_chModeY == MODE_ZOOMINGY)
  {
    if (m_vHistY.empty())
      m_chModeY = MODE_FULLY;
    else
      m_chModeY = MODE_ZOOMEDY;
  }
  return m_chModeY;
}

// Protected data and names legend functions    
size_t CChartContainer::PrepareDataLegend(CPoint pnt, MAP_LABSTR& mapLabels, 
                                                    MAP_SELPNTSD& mapSelPntsD)
{
  PointD dataLegPntD;
  MousePntToPntD(pnt, dataLegPntD, m_pMatrixD);

  double epsX = CHART_DTPNTSZ/m_pMatrixD->m_scX; // m11 is the scX

  size_t mapLabelsSize = PrepareDataLegend(dataLegPntD, epsX, mapLabels, mapSelPntsD, false);
  if (mapSelPntsD.size() > 0)
  {
    m_dataLegPntD = dataLegPntD;
    m_epsX        = epsX;
  }
  else 
    m_dataLegPntD = PointD(DBL_MAX, DBL_MAX);

  return mapLabelsSize;
}

size_t CChartContainer::PrepareDataLegend(PointD origPntD, double epsX, MAP_LABSTR& mapLabels,
                                                     MAP_SELPNTSD& mapSelPntsD, bool bChangeMatrix)
{
  if (bChangeMatrix)
    SetTransformMatrixD();

  mapSelPntsD.clear();
  mapLabels.clear();

 if (in_range(m_startX, m_endX, origPntD.X)) // Make sense if pnt was sent by a programmer
  {
    CRect clientR;              // Get Y-boundaries in the data space
    GetClientRect(&clientR);
    CPoint topPnt(0, clientR.top);
    CPoint bottomPnt(0, clientR.bottom);
    PointD topPntD;
    PointD bottomPntD;
    MousePntToPntD(topPnt, topPntD, m_pMatrixD);
    MousePntToPntD(bottomPnt, bottomPntD, m_pMatrixD);

//Search the chart map
    PointD selPntD;
    
    MAP_CHARTS::iterator it = m_mapCharts.begin();
    MAP_CHARTS::iterator itE = m_mapCharts.end();
    
    for (; it != itE; ++it)
    {
      CChart* chartPtr= it->second;
      bool bVisible = chartPtr->IsChartVisible();

      PAIR_ITNEAREST pair_res = 
                            chartPtr->GetNearestPointD(origPntD, epsX, selPntD);
      int nSel = pair_res.second;
      if (nSel > 0)    // Might be out of the client rect
      {
        V_CHARTDATAD::iterator itSel = pair_res.first;
        for (int i = nSel; i > 0; --i)
        {
          PointD selPntD = *itSel;
          double selY = selPntD.Y * chartPtr->GetLocScaleY();
          int chartIdx = chartPtr->GetChartIdx();
          mapSelPntsD.insert(MAP_SELPNTSD::value_type(chartIdx, selPntD));
          if (bVisible && in_range(bottomPntD.Y, topPntD.Y, selY))    // Remember, Y was reversed
          {
            TUPLE_LABEL tuple_res = chartPtr->GetSelValString(selPntD, m_labelX, m_precision, m_pLabValStrFnPtr);
            mapLabels.insert(MAP_LABSTR::value_type(chartIdx , tuple_res));
          }
          if (i > 1)
          --itSel;
        }
      }
    }
    
    return mapLabels.size();
  }
  return 0;
}

size_t CChartContainer::PrepareNamesLegend(MAP_LABSTR& mapLabels)
{
  if (m_mapCharts.empty())
    return 0;

  if (!mapLabels.empty())
    mapLabels.clear();

  TUPLE_LABEL tuple_res;
  MAP_CHARTS::iterator it = m_mapCharts.begin();
  MAP_CHARTS::iterator itE = m_mapCharts.end();
  for (; it != itE; ++it)  
  {
    bool bSelected = it->second->GetVisibleChartNameAndVisuals(tuple_res);
    if (bSelected)
      mapLabels.insert(MAP_LABSTR::value_type(it->first, tuple_res));
  }
  return mapLabels.size();
}

// Refreshes label string map according to charts' visibility and X range
size_t CChartContainer::UpdateDataLegend(MAP_SELPNTSD& mapSelPntsD, MAP_LABSTR& mapLabStr)
{
  mapLabStr.clear();
  if (!mapSelPntsD.empty()&& in_range(m_startX, m_endX, m_dataLegPntD.X))
  {
    CRect clRect;
    GetClientRect(&clRect);
    CPoint pntLimYL(0, clRect.bottom);
    CPoint pntLimYR(0, clRect.top);
    PointD pntLimYLD, pntLimYRD;
    MousePntToPntD(pntLimYL, pntLimYLD, m_pMatrixD);
    MousePntToPntD(pntLimYR, pntLimYRD, m_pMatrixD);

    MAP_SELPNTSD::iterator itSel = mapSelPntsD.begin();
    MAP_SELPNTSD::iterator itSelE = mapSelPntsD.end();
    while(itSel != itSelE)
    {
      int chartIdx = itSel->first;
      CChart* chartPtr = GetChart(chartIdx);
      if (chartPtr != NULL)
      {
        if (chartPtr->IsChartVisible())
        {
          PointD selPntD = itSel->second;
          if (in_range(m_startX, m_endX, selPntD.X)&&
            in_range(pntLimYLD.Y, pntLimYRD.Y, selPntD.Y*chartPtr->GetLocScaleY()))
          {
            TUPLE_LABEL tuple_res = chartPtr->GetSelValString(selPntD, m_labelX, m_precision, m_pLabValStrFnPtr);
            mapLabStr.insert(MAP_LABSTR::value_type(chartIdx, tuple_res));
          }
        }
        ++itSel;
      }
      else
       itSel = mapSelPntsD.erase(itSel);
    }
  }

  CPoint origPnt(-1, -1); // Not used on empty mapLabStr
  if (!mapLabStr.empty())
  {
    PointF origPntF = m_pMatrixD->TransformToPntF(1.0, m_dataLegPntD);
    origPnt = CPointFromPntF(origPntF);
  }
// Recalc dataWnd window rects and show data wnd or will hide it
  m_pDataWnd->UpdateDataLegend(mapLabStr, this, origPnt);
  return mapLabStr.size();
}

// Gets axis X pos AX_YPOS (first) and axis X float offsY(second)
PAIR_XAXPOS CChartContainer::GetXAxisPos(RectF rChartF, double minY, double maxY)
{
  AX_YPOS axYPos;
  float vertOffs;

// Let's begin with X axis
  if ((minY < 0)&&(maxY > 0))
  {
    double offsXY = rChartF.Height*fabs(maxY)/(maxY - minY);
    vertOffs = rChartF.GetTop() + float(offsXY);
    axYPos = MIDDLE;        // Y pos of Axis X, somewhere inside the y range
  }
  else if (minY >= 0)
  {
    vertOffs = rChartF.GetBottom();
    axYPos = BOTTOM;        // Y pos of Axis X at bottom of the draw rect
  }
  else if (maxY <= 0)
  {
    vertOffs = rChartF.GetTop();
    axYPos = TOP;           // At the top of the draw rect
  }

  return make_pair(axYPos, vertOffs);
}

// Gets axis Y pos AX_XPOS (first) and axis Y float offsX(second)
PAIR_YAXPOS CChartContainer::GetYAxisPos(RectF rChartF, double startX, double endX)
{
// Y axis
  AX_XPOS axXPos;;
  float horzOffs;

  if ((startX < 0)&&(endX > 0))
  {
    double offsYX = rChartF.Width*fabs(startX)/(endX - startX);
    horzOffs = rChartF.GetLeft() + float(offsYX);
    axXPos = BETWEEN;      // Somewhere between minX, maxX
  }
  else if (startX >= 0)
  {
    horzOffs = rChartF.GetLeft();
    axXPos = LEFT;         // At the left of the draw rect
  }
  else if (endX <= 0)
  {
    horzOffs = rChartF.GetRight();
    axXPos = RIGHT;       // At the right side of the draw rect
  }

  return make_pair(axXPos, horzOffs);
}

bool CChartContainer::ShowDataView(CChart* chartPtr, bool bClearMap, bool bRefresh)
{
  if (m_pChartDataView == NULL)
    m_pChartDataView = new CChartDataView;

  if (m_pChartDataView != NULL)
  {
    if (!IsWindow(m_pChartDataView->m_hWnd))
    {
      CRect parentWndRect;
      GetParent()->GetWindowRect(&parentWndRect); // App main dlg window

      CRect workRect;
      SystemParametersInfo(SPI_GETWORKAREA, NULL, &workRect, 0);

      int leftX  = parentWndRect.right + DV_SPACE;
      int rightX = leftX + DV_RECTW;
      int topY   = parentWndRect.top - DV_SPACE;
      int bottomY = topY + DV_RECTH;

      CRect dataViewRect(leftX, topY, rightX, bottomY);
      CRect interRect;
      interRect.IntersectRect(&dataViewRect, workRect);
      if (interRect != dataViewRect)
      {
        dataViewRect.right = workRect.right - DV_SPACE;
        dataViewRect.left = max(dataViewRect.right - DV_RECTW, workRect.left + DV_SPACE);
        dataViewRect.top = workRect.top + DV_SPACE;
        dataViewRect.bottom = min(dataViewRect.top + DV_RECTH, workRect.bottom - DV_SPACE);
      }

      BOOL bRes =  m_pChartDataView->CreateEx(0,
                                 AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_SAVEBITS),
                                 _T("Chart Data View"), 
                                 WS_POPUPWINDOW|WS_CAPTION|WS_MINIMIZEBOX|WS_VISIBLE,
                                 dataViewRect.left, dataViewRect.top, dataViewRect.Width(), dataViewRect.Height(),
                                 NULL, 
                                 NULL,
                                 NULL);
      if (!bRes)
      {
        delete m_pChartDataView;
        m_pChartDataView = NULL;
        return false;
      }
    }
    else if (m_pChartDataView->IsIconic())
      m_pChartDataView->ShowWindow(SW_RESTORE);

   m_pChartDataView->SetOwner(this);
   int chartIdx = chartPtr->GetChartIdx();
   m_pChartDataView->ShowWaitMessage(chartIdx, chartPtr->m_vDataPnts.size());
   m_pChartDataView->InitParams(chartPtr, bClearMap, this);

    if (m_dataViewChartIdx != chartIdx)
    {
      m_dataViewChartIdx = chartIdx;
      bClearMap = true;
    }

    if (bClearMap)
    {
      m_mapDataViewPntsD.clear();
      if (bRefresh)
        RefreshWnd();
    }
  }
  return true;
}

bool CChartContainer::ShowDataView(const string_t& szChartName, bool bRefresh)
{
  if (m_mapCharts.empty()&&!HasChartWithData(-1, true))
    return false;

  int chartIdx  = GetChartIdx(szChartName);
  if (chartIdx == -1)
    return false;

  int selChartIdx = SelectChart(chartIdx, true);      // Return -1 if the chart is not visible or without data
  if (selChartIdx == -1)
    return false;

  CChart* chartPtr = GetChart(chartIdx);      
  return ShowDataView(chartPtr, bRefresh);
}

bool CChartContainer::UpdateDataView(int chartIdx, DATAVIEW_FLAGS flagsData)
{
  if ((m_pChartDataView != NULL) && (IsWindow(m_pChartDataView->m_hWnd)))
  {
    if ((chartIdx != -1) && (chartIdx == m_dataViewChartIdx))
    {
      CChart* chartPtr = GetChart(chartIdx);
      if ((chartPtr != NULL) && chartPtr->IsChartVisible())
        return m_pChartDataView->UpdateParams(chartPtr, flagsData);
    }
  }
  return false;
}

size_t CChartContainer::UpdateDataViewPntsDMap(void)
{
  if (m_pChartDataView != NULL)
    m_mapDataViewPntsD = m_pChartDataView->m_mapSelCells;
  else
    m_mapDataViewPntsD.clear();

  return m_mapDataViewPntsD.size();
}

// This returns chartPtr->m_loScY as a first, and deltaLocScY as a second; delta = 1% of Y range
PAIR_DBLS CChartContainer::GetLocScYDelta(CChart* chartPtr)
{
  if (IsWindow(m_hWnd))
  {
    CRect clR;
    GetClientRect(&clR);
    double deltaY = 0.01*clR.Height()/m_scY;
    double locScY = chartPtr->GetLocScaleY();
    double maxY = max(fabs(chartPtr->GetMaxValY()), fabs(chartPtr->GetMinValY()));
    double deltaK = deltaY/maxY;
    return make_pair(locScY, deltaK);
  }
  return make_pair(1.0, 0.0);
}

void CChartContainer::MoveChartsByArrowKeysX(UINT nChar)
{
  m_vHistX.push_back(make_pair(m_startX, m_endX));
  double deltaX = 0.0;
  if (nChar == VK_RIGHT)
    deltaX = MOVE_DELTAX*(m_endX - m_startX);
  else if (nChar == VK_LEFT)
    deltaX = -MOVE_DELTAX*(m_endX - m_startX);
  else 
    return;

  MoveContainerChartsX(deltaX, true);
}

void CChartContainer::ChangeLocScaleYByArrowKeys(UINT nChar)
{
  if (m_chartSelIdx > -1)
  {
    MAP_CHARTS::iterator it = m_mapCharts.find(m_chartSelIdx);
    if (it != m_mapCharts.end())
    {
      CChart* chartPtr= it->second;
      if (chartPtr->IsChartSelected() && chartPtr->IsChartVisible())
      {
        std::pair<double, double> pair_scY = GetLocScYDelta(chartPtr);
        double locScY = pair_scY.first;
        double deltaK = pair_scY.second;

        switch (nChar)
        {
        case VK_DOWN: locScY = max(locScY - deltaK, DELTA_LOCY);      break; 
        case VK_UP:   locScY += deltaK;                               break; 
        case VK_PRIOR: locScY += 10.0*deltaK;                         break; 
        case VK_NEXT:locScY = max(locScY - 10.0*deltaK, DELTA_LOCY);  break;
        }

        chartPtr->SetLocScaleY(locScY);  
        
        if (IsLabWndExist(false))
        {
          if (m_pDataWnd->IsWindowVisible())
            m_pDataWnd->ShowWindow(SW_HIDE);
          SetTimer(1, 500, NULL);             // To fight flicker
        }
        RefreshWnd();
        SendNotification(CODE_SCY, chartPtr->GetChartIdx());
     }
   }
 }
}

void CChartContainer::MoveChartsByArrowKeysY(UINT nChar)
{
// Calculate shift
  double shiftValY = 0.0;
  double deltaY = MOVE_DELTAY*(m_maxExtY - m_minExtY);
  switch (nChar)
  {
    case VK_DOWN:  shiftValY = -deltaY;      break; 
    case VK_UP:    shiftValY =  deltaY;      break; 
    case VK_PRIOR: shiftValY = 10.0*deltaY;  break; 
    case VK_NEXT:  shiftValY = -10.0*deltaY; break;
  }

  MoveContainerChartsY(shiftValY, true);
  SendNotification(CODE_EXTY);
}

// Draw functions
PointF CChartContainer::DrawAxes(RectF rClientF,double startX, double endX, double minY, 
                              double maxY, Color colAxis, Graphics* grPtr, float dpiRatio)
{
  Pen axisPen(colAxis, AXIS_PENWIDTH*dpiRatio);  

  PointF pntStartF, pntEndF, pntCoordZeroF;
// Let's begin with X axis

  PAIR_XAXPOS pair_axXYPos = GetXAxisPos(rClientF, minY, maxY);
  m_axYPos = pair_axXYPos.first;
  pntStartF.Y = pntEndF.Y = pair_axXYPos.second;
  pntStartF.X = rClientF.GetLeft() - DR_SPACE*dpiRatio;   // Don't worry: draw area is clipped
  pntEndF.X   = rClientF.GetRight() + DR_SPACE*dpiRatio;
  grPtr->DrawLine(&axisPen, pntStartF, pntEndF);

  pntCoordZeroF.Y = pntStartF.Y;

// Y axis
  PAIR_YAXPOS pair_axYXPos = GetYAxisPos(rClientF, startX, endX);
  m_axXPos = pair_axYXPos.first;
  pntStartF.X = pntEndF.X = pair_axYXPos.second;
  pntStartF.Y = rClientF.GetBottom() + DR_SPACE*dpiRatio;
  pntEndF.Y   = rClientF.GetTop() - DR_SPACE*dpiRatio;
  grPtr->DrawLine(&axisPen, pntStartF, pntEndF);

  pntCoordZeroF.X = pntStartF.X;

  m_axOffsXF = pntCoordZeroF.Y; 
  return pntCoordZeroF;
}

void CChartContainer::DrawBkground(RectF rClientF, Color colBkground, Color colBorder, 
                                                                  Graphics* grPtr)
{
  SolidBrush bkBrush(colBkground);
  grPtr->FillRectangle(&bkBrush, rClientF);

  Pen borderPen(colBorder);
  grPtr->DrawRectangle(&borderPen, rClientF);

  if (this == GetFocus())
  {
    rClientF.Inflate(-3.0f, -3.0f);
    borderPen.SetDashStyle(DashStyleDash);
    grPtr->DrawRectangle(&borderPen, rClientF);
  }
}

bool CChartContainer::DrawSelPntsD(CChart* chartPtr, const MAP_SELPNTSD& mapSelPntsD, 
                GraphicsPath* grPathPtr, Graphics* grPtr, RectF drawRF, float dpiRatio)
{
  if (!in_range(m_startX, m_endX, m_dataLegPntD.X))
    return false;
  if (mapSelPntsD.empty() || (chartPtr == NULL))
    return false;

  drawRF.Inflate(DR_SPACE*dpiRatio, DR_SPACE*dpiRatio);

// Get screen pntsF for the given chart
  V_CHARTDATAD vDataPntsD(mapSelPntsD.size());
  V_CHARTDATAF vDataPntsF;

  int chartIdx = chartPtr->GetChartIdx();
// Find first entry
  MAP_SELPNTSD::const_iterator itSel = mapSelPntsD.find(chartIdx); 
  if (itSel != m_mapSelPntsD.cend())                              // The chart has selected points                         
  {
    size_t count = m_mapSelPntsD.count(chartIdx);
    for (size_t i = 0; i < count; ++i)
    {
      PointD selPntD = itSel->second;
      if (in_range(m_startX, m_endX, selPntD.X))             // Sometimes it will draw out of clip area
      {
        PointF selPntF = m_pMatrixD->TransformToPntF(chartPtr->GetLocScaleY(), selPntD);
        if (drawRF.Contains(selPntF))
          vDataPntsF.push_back(selPntF);
      }
      ++itSel;
    }
// Draw
    if (!vDataPntsF.empty())
    {
      V_CHARTDATAF::iterator it = vDataPntsF.begin(), itE = vDataPntsF.end();
  
      for (; it != itE; ++it)
      {
        PointF selPntF = *it;
        RectF rPntF = RectFFromCenterF(selPntF, dpiRatio*CHART_DTPNTSZ, dpiRatio*CHART_DTPNTSZ);
        rPntF.Inflate(dpiRatio*1.5f, dpiRatio*1.5f);
        grPathPtr->AddEllipse(rPntF);
      }

      Pen selPen(m_colLegPnts, dpiRatio*1.0f);
      grPtr->DrawPath(&selPen, grPathPtr);

      grPathPtr->Reset();
      return true;
    }   // For vDataPntsF
  }
  return false;
}

bool CChartContainer::DrawDataViewPntsD(const MAP_SELPNTSD& mapDataViewPntsD, double startX, 
                          double endX, GraphicsPath* grPathPtr, Graphics* grPtr, float dpiRatio)
{
  if (mapDataViewPntsD.empty())
    return false;
  if (NULL == GetVisibleChart(m_dataViewChartIdx))
    return false;
  
  CChart* chartPtr = GetChart(m_dataViewChartIdx);
  if ((chartPtr == NULL)||!chartPtr->IsChartVisible())
    return false;

// Extract dataPoints from the map into a vector
  V_CHARTDATAD vDataPntsD(mapDataViewPntsD.size());
  transform(mapDataViewPntsD.begin(), mapDataViewPntsD.end(), vDataPntsD.begin(), 
                                                       get_map_value<int, PointD>());
// Exclude elements out of the range and convert to screen pntsF
  V_CHARTDATAF vDataPntsF;
  if (!chartPtr->ConvertChartData(vDataPntsD, vDataPntsF, m_pMatrixD, startX, endX))
    return false;

  vDataPntsF.shrink_to_fit();

// Finally, draw
  V_CHARTDATAF::iterator it = vDataPntsF.begin();
  V_CHARTDATAF::iterator itE = vDataPntsF.end();
  for (; it != itE; ++it)
  {
    RectF rPntF = RectFFromCenterF(*it, dpiRatio*(CHART_DTPNTSZ + 2.0f), 
                                                    dpiRatio*(CHART_DTPNTSZ+ 2.0f));
    grPathPtr->AddEllipse(rPntF);
  }
  
  Pen selPen(m_colDataViewPnts, dpiRatio*2.0f);
  grPtr->DrawPath(&selPen, grPathPtr);
  grPathPtr->Reset();

  return true;
}

void CChartContainer::DrawSelLine(RectF clRF, Graphics* grPtr, int selChartIdx, float dpiRatio)
{
  if (m_mapSelPntsD.empty())
    return;
  if (NULL == GetVisibleChart(-1))
    return;

  bool bShowLine = false;
  if (IsLabWndVisible(false))
    bShowLine = true;
  else if (dpiRatio != 1.0f)   // The second check: on a printout there is no dataWnd
  {
    clRF.Inflate(DR_SPACE*dpiRatio, DR_SPACE*dpiRatio);
    PointF upperLeftF(clRF.X, clRF.Y);
    PointF bottomRightF(clRF.GetRight(), clRF.GetBottom());
    PointD upperLeftD = m_pMatrixD->TransformToPntD(1.0, upperLeftF);
    PointD bottomRightD = m_pMatrixD->TransformToPntD(1.0, bottomRightF);

    auto itSel = selChartIdx > -1 ? m_mapSelPntsD.find(selChartIdx) : m_mapSelPntsD.cbegin();
    auto itSelE = m_mapSelPntsD.cend();
    for (; itSel != itSelE; ++itSel)
    {
      if ((selChartIdx > -1)&&(itSel->first != selChartIdx))
        break;
      PointD pntD = itSel->second;
      CChart* chartPtr = GetChart(itSel->first);
      double locScY = chartPtr->GetLocScaleY();
      PointF pntF = m_pMatrixD->TransformToPntF(locScY, pntD);
      if (clRF.Contains(pntF))
      {
        bShowLine = true; 
        break;
      }
    }
  }

  if (bShowLine)
  {
// Get client coord of the data line; scY does notmatter
    PointF pntF = m_pMatrixD->TransformToPntF(1.0, m_dataLegPntD);
// Again, no problem with printing out of a clipping rectangle
    Pen selPen(m_colLegPnts, dpiRatio*1.0f);
    grPtr->DrawLine(&selPen, pntF.X, clRF.Y, pntF.X, clRF.GetBottom());
  }
}

void CChartContainer::DrawAxXBoundaryStr(RectF boundRF, Graphics* grPtr, float dpiRatio)
{
  string_t strStartX, strEndX;

  bool bAddEqSign = m_labelX.empty() ? false : true;

  string_t strSpace(_T(" "));
  strStartX = m_labelX + strSpace + m_pLabValStrFnPtr(m_startX, m_precision, bAddEqSign);
  strEndX   = m_labelX + strSpace + m_pLabValStrFnPtr(m_endX, m_precision, bAddEqSign);

  FontFamily fontFamily(_T("Verdana"));
  Gdiplus::Font axFont(&fontFamily, 7.0f, FontStyleBold);
  grPtr->SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);

// Measure strings
  boundRF.Inflate(-dpiRatio*2.0f, -dpiRatio*2.0f);
  RectF startRF, endRF;
  PointF startPntF, endPntF;

  grPtr->MeasureString(strStartX.c_str(), -1, &axFont, boundRF, &startRF); 
  grPtr->MeasureString(strEndX.c_str(), -1, &axFont, boundRF, &endRF);

  if ((m_axYPos == MIDDLE)||(m_axYPos == TOP))
  {
    startPntF.Y = m_axOffsXF + dpiRatio*DR_SPACE;
    endPntF.Y    = m_axOffsXF + dpiRatio*DR_SPACE;
    if ((m_axYPos == MIDDLE)&&((endPntF.Y + startRF.Width) > boundRF.GetBottom()))
    {
       startPntF.Y = m_axOffsXF - startRF.Height - dpiRatio*(3.0f + DR_SPACE);
       endPntF.Y   = m_axOffsXF - endRF.Height - dpiRatio*(3.0f + DR_SPACE);
    }
  }
  else if (m_axYPos == BOTTOM)
  {
    startPntF.Y = m_axOffsXF - startRF.Height - dpiRatio*(3.0f + DR_SPACE);
    endPntF.Y   = m_axOffsXF - endRF.Height - dpiRatio*(3.0f + DR_SPACE);
  }

  if (m_axXPos == BETWEEN)
  {
    startPntF.X = startRF.X  + dpiRatio*DR_SPACE;
    endPntF.X   = boundRF.GetRight() - endRF.Width - dpiRatio*DR_SPACE;
  }
  else if (m_axXPos == LEFT)
  {
    startPntF.X = startRF.X  + dpiRatio*(3.0f + DR_SPACE);
    endPntF.X   = boundRF.GetRight() - endRF.Width - dpiRatio*DR_SPACE;
  }
  else if (m_axXPos == RIGHT)
  {
    startPntF.X = startRF.X  + dpiRatio*DR_SPACE;
    endPntF.X   = boundRF.GetRight() - endRF.Width - dpiRatio*(3.0f + DR_SPACE);
  }
  else
    return;

  startRF.X = startPntF.X;
  startRF.Y = startPntF.Y;
  endRF.X   = endPntF.X;
  endRF.Y   = endPntF.Y;

  SolidBrush axBkBrush(m_colBkground);
  SolidBrush axLabBr(m_colAxis);
  
  grPtr->FillRectangle(&axBkBrush, startRF);
  grPtr->DrawString(strStartX.c_str(), -1, &axFont, startPntF, &axLabBr);

  grPtr->FillRectangle(&axBkBrush, endRF);
  grPtr->DrawString(strEndX.c_str(), -1, &axFont, endPntF, &axLabBr);
}

// Used to draw Zoom Border out of OnPaint function
void CChartContainer::DrawZoomBorderX(CPoint pnt)
{
  CDC* pDC = GetDC();
  GraphicsPath grPath;

  CRect clR;
  GetClientRect(&clR);

  CPoint tmpPnt;
  CRect nameR;
  if (IsLabWndVisible(true))
  {
    m_pLegWnd->GetWindowRect(&nameR);
    ScreenToClient(&nameR);
    tmpPnt = CPoint(pnt.x, nameR.CenterPoint().y);
    if (!nameR.PtInRect(tmpPnt))
      nameR.SetRectEmpty();
  }

  CRect dataR;
  if (IsLabWndVisible(false))
  {
    m_pDataWnd->GetWindowRect(&dataR);
    ScreenToClient(&dataR);
    tmpPnt = CPoint(pnt.x, dataR.CenterPoint().y);
    if (!dataR.PtInRect(tmpPnt))
      dataR.SetRectEmpty();
  }

  Point startPnt(pnt.x, clR.top);
  Point endX;
  if (!nameR.IsRectEmpty())
  {
    grPath.AddLine(startPnt, Point(pnt.x, nameR.top));
    grPath.StartFigure();
    startPnt.Y = nameR.bottom;
    if (!dataR.IsRectEmpty())
    {
      grPath.AddLine(startPnt, Point(pnt.x, dataR.top));
      grPath.StartFigure();
      startPnt.Y = dataR.bottom;
    }
    grPath.AddLine(startPnt, Point(pnt.x, clR.bottom));
  }
  else if (!dataR.IsRectEmpty())
  {
    grPath.AddLine(startPnt, Point(pnt.x, dataR.top));
    grPath.StartFigure();
    startPnt.Y = dataR.bottom;
  }
  grPath.AddLine(startPnt, Point(pnt.x, clR.bottom));

  Graphics gr(pDC->m_hDC);
  Pen brdPen(m_colZoomBorder);
  gr.DrawPath(&brdPen, &grPath);
//  gr.DrawLine(&brdPen, pnt.x, clR.top, pnt.x, dataR.top);//clR.bottom);
  ReleaseDC(pDC);
}

void CChartContainer::DrawZoomBorderY(CPoint pnt)
{
  CDC* pDC = GetDC();
  GraphicsPath grPath;

  CRect clR;
  GetClientRect(&clR);

  CPoint tmpPnt;

  CRect dataR;
  if (IsLabWndVisible(false))
  {
    m_pDataWnd->GetWindowRect(&dataR);
    ScreenToClient(&dataR);
    tmpPnt = CPoint(dataR.CenterPoint().x, pnt.y);
    if (!dataR.PtInRect(tmpPnt))
      dataR.SetRectEmpty();
  }

  CRect nameR;
  if (IsLabWndVisible(true))
  {
    m_pLegWnd->GetWindowRect(&nameR);
    ScreenToClient(&nameR);
    tmpPnt = CPoint(nameR.CenterPoint().x, pnt.y);
    if (!nameR.PtInRect(tmpPnt))
      nameR.SetRectEmpty();
  }

  Point startPnt(clR.left, pnt.y);
  Point endY;

  if (!dataR.IsRectEmpty())
  {
    grPath.AddLine(startPnt, Point(dataR.left, pnt.y));
    grPath.StartFigure();
    startPnt.X = dataR.right;
  }

  if (!nameR.IsRectEmpty())
  {
    grPath.AddLine(startPnt, Point(nameR.left, pnt.y));
    grPath.StartFigure();
    startPnt.X = nameR.right;
    if (!dataR.IsRectEmpty())
    {
      grPath.AddLine(startPnt, Point(dataR.right, pnt.y));
      grPath.StartFigure();
      startPnt.X = dataR.right;
    }
  }

  grPath.AddLine(startPnt, Point(clR.right, pnt.y));

  Graphics gr(pDC->m_hDC);
  Pen brdPen(m_colZoomBorder);
  gr.DrawPath(&brdPen, &grPath);
  ReleaseDC(pDC);
}

void  CChartContainer::DrawGrid(RectF boundRF, PointF coordOrigF, Graphics* grPtr, float dpiRatio)
{
  float fDeltaX = boundRF.Width/10.0f;
  float fDeltaY = boundRF.Height/10.0f;

  float limTop    = boundRF.GetTop();
  float limBottom = boundRF.GetBottom();
  float limLeft   = boundRF.GetLeft();
  float limRight  = boundRF.GetRight();

  Pen gridPen(m_colGrid, 1.0f*dpiRatio);
  gridPen.SetDashStyle(DashStyleDash);
  gridPen.SetDashCap(DashCapRound);

  float xF, yF;

  yF = coordOrigF.Y + fDeltaY;
  while (yF < limBottom)
  {
    grPtr->DrawLine(&gridPen, limLeft, yF, limRight, yF);
    yF += fDeltaY;
  }

  yF = coordOrigF.Y - fDeltaY;
  while (yF > limTop)
  {
    grPtr->DrawLine(&gridPen, limLeft, yF, limRight, yF);
    yF -= fDeltaY;
  }

  xF = coordOrigF.X + fDeltaX;
  while (xF < limRight)
  {
    grPtr->DrawLine(&gridPen, xF, limTop, xF, limBottom);
    xF += fDeltaX;
  }
 
  xF = coordOrigF.X - fDeltaX;
  while (xF > limLeft)
  {
    grPtr->DrawLine(&gridPen, xF, limTop, xF, limBottom);
    xF -= fDeltaX;
  }
}

void CChartContainer::DrawContainerToBmp(Rect rGdi, Bitmap& clBmp)
{
  Graphics* grPtr = Graphics::FromImage(&clBmp);  // As memDC
  grPtr->SetSmoothingMode(SmoothingModeAntiAlias);

  RectF rGdiF = GdiRectToRectF(rGdi);
  rGdiF.Width -= 1.0f;
  rGdiF.Height -= 1.0f;
  DrawBkground(rGdiF, m_colBkground, m_colBorder, grPtr);

  if (m_mapCharts.size() > 0)
  {
    RestoreModeX();  // Might be in the middle of settung zoon borders; annuls the mode
    RectF drawRF = rGdiF;   
    drawRF.Inflate(-DR_SPACE, -DR_SPACE);
 
    if (!HasChartWithData(-1))
      DrawGrid(drawRF, PointF(0.0f, 0.0f), grPtr);
    else
    {
// Draw axes
      PointF pntOrigF = DrawAxes(drawRF, m_startX, m_endX, m_minExtY, m_maxExtY, m_colAxis, grPtr);
// Draw a grid    
      DrawGrid(drawRF, pntOrigF, grPtr);

// Update scales
      std::pair<double, double> pair_scales = 
      UpdateScales(drawRF, m_startX, m_endX, m_minExtY, m_maxExtY);
      m_scX = pair_scales.first;
      m_scY = pair_scales.second;
// Set transform matrix
      MatrixD matrixD;
      matrixD.Translate(pntOrigF.X, pntOrigF.Y);
      matrixD.Scale(m_scX, -m_scY);
// Account for not even axes placement
      if ((m_axXPos != BETWEEN)||(m_axYPos != MIDDLE))
      {
        double translateX = (m_axXPos == RIGHT) ? -m_endX : (m_axXPos == LEFT) ? -m_startX : 0.0;
        double translateY = (m_axYPos == BOTTOM) ? -m_minExtY : (m_axYPos == TOP) ? -m_maxExtY : 0.0;
        matrixD.Translate(translateX, translateY);
      }
      if (m_pMatrixD != NULL)
        delete m_pMatrixD;
      m_pMatrixD = matrixD.Clone();     // Copy constructor is private ??

// Begin draw curves
      GraphicsPath grPath;            // Will be used actoss the drawing routines
      MAP_CHARTS::iterator it = m_mapCharts.begin();
      MAP_CHARTS::iterator itE = m_mapCharts.end();
      for (; it != itE; ++it)
      {
        CChart* chartPtr = it->second;
        if (chartPtr->IsChartVisible() == false)
          continue;
        if (chartPtr->m_vDataPnts.size() == 0)
          continue;
        chartPtr->DrawChartCurve(chartPtr->m_vDataPnts, m_startX, m_endX, 
                                                       m_pMatrixD, &grPath, grPtr);
        if (m_bTracking)              // Draw selection points if any
          DrawSelPntsD(chartPtr, m_mapSelPntsD, &grPath, grPtr, drawRF);
      }

      if (m_bTracking)                // And the sel line too
        DrawSelLine(rGdiF, grPtr);

      if (m_bAxBoundaries)            // Left and right visible X-coordinates
        DrawAxXBoundaryStr(rGdiF, grPtr);

// Now draw children
      if (m_pLegWnd&&IsWindow(m_pLegWnd->m_hWnd)&&m_pLegWnd->IsWindowVisible())
      {
        CRect legWndRect = m_pLegWnd->GetDataWndRect();
        m_pLegWnd->DrawDataWnd(legWndRect.left, legWndRect.top, grPtr);
      }

      if (m_pDataWnd&&IsWindow(m_pDataWnd->m_hWnd)&&m_pDataWnd->IsWindowVisible())
      {
        CRect dataWndRect = m_pDataWnd->GetDataWndRect();
        m_pDataWnd->DrawDataWnd(dataWndRect.left, dataWndRect.top, grPtr);
      }
    }
  }
  delete grPtr;
}

// Printing
int CChartContainer::GetScreenDpi(void)
{
  CPaintDC containerDC(this);
  int scrDpiX = containerDC.GetDeviceCaps(LOGPIXELSX);
  int scrDpiY = containerDC.GetDeviceCaps(LOGPIXELSY);
  ENSURE(scrDpiX == scrDpiY);
  return scrDpiX;
}

TUPLE_PRNLEGLAYOUT CChartContainer::CalcNamesPntLayout(RectF namesRF, 
                         const MAP_PRNDATA& mapPrnData, Graphics* grPtr, float dpiRatio)
{
// Measure strings
  float maxNamesLength = 0, maxScYLength = 0, maxValStrLength = 0;
  float maxNameXStrLength = 0, maxStrXLength = 0; 
  float maxNameYStrLength = 0, maxStrYLength = 0;

  RectF tmpRF; 

// Get max size of the printing cells
  FontFamily fontFamily(_T("Verdana"));
  Gdiplus::Font labelFont(&fontFamily, 8.0f, FontStyleBold);
  grPtr->SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);

  // Measure strings
  MAP_PRNDATA::const_iterator it = mapPrnData.begin();
  MAP_PRNDATA::const_iterator itE = mapPrnData.end();

// Get max strings and str lengths
  while (it != itE)
  {
    TUPLE_PRINT tuple_chart = it->second;
// Names
    string_t tmpStr  = get<IDX_NAME>(tuple_chart);

    if (!tmpStr.empty())
    {
      grPtr->MeasureString(tmpStr.c_str(), -1, &labelFont, namesRF, &tmpRF);
      if (tmpRF.Width > maxNamesLength )
        maxNamesLength = tmpRF.Width;
    }
// Scales
    tmpStr = get<IDX_SCY>(tuple_chart);
    if (!tmpStr.empty())
    {
      grPtr->MeasureString(tmpStr.c_str(), -1, &labelFont, namesRF, &tmpRF);
      if (tmpRF.Width > maxScYLength)
        maxScYLength = tmpRF.Width;
    }
// Sel pnts if any
    tmpStr = get<IDX_VAL>(tuple_chart);
    if (!tmpStr.empty())  // If empty, no selected points
    {
      grPtr->MeasureString(tmpStr.c_str(), -1, &labelFont, namesRF, &tmpRF);
      if (tmpRF.Width > maxValStrLength)
        maxValStrLength = tmpRF.Width;
    }

// X name
    tmpStr = get<IDX_NAMEX>(tuple_chart);
    if (!tmpStr.empty())
    {
      grPtr->MeasureString(tmpStr.c_str(), -1, &labelFont, namesRF, &tmpRF);
      if (tmpRF.Width > maxNameXStrLength)
        maxNameXStrLength = tmpRF.Width;
    }

// X values
    tmpStr = get<IDX_X>(tuple_chart);
    grPtr->MeasureString(tmpStr.c_str(), -1, &labelFont, namesRF, &tmpRF);
    if (tmpRF.Width > maxStrXLength)
      maxStrXLength = tmpRF.Width;

// Y name str
    tmpStr = get<IDX_NAMEY>(tuple_chart);
    if (!tmpStr.empty())
    {
      grPtr->MeasureString(tmpStr.c_str(), -1, &labelFont, namesRF, &tmpRF);
      if (tmpRF.Width > maxNameYStrLength)
        maxNameYStrLength = tmpRF.Width;
    }

// Y values
    tmpStr = get<IDX_Y>(tuple_chart);
    grPtr->MeasureString(tmpStr.c_str(), -1, &labelFont, namesRF, &tmpRF);
    if (tmpRF.Width > maxStrYLength)
      maxStrYLength = tmpRF.Width;

    ++it;
  }

  // Column offsets, begin from the end
  float endX = namesRF.GetRight();
  float startX = namesRF.GetLeft();

  float valYOffsX = endX - maxStrYLength;
  float nameYStrOffsX = maxNameYStrLength == 0 ? valYOffsX : 
                                valYOffsX  - maxNameYStrLength - dpiRatio*LB_BORDERSPACE;   
  float valXOffsX = nameYStrOffsX - maxStrXLength - dpiRatio*LB_BORDERSPACE;
  float nameXStrOffsX = maxNameXStrLength == 0 ? valXOffsX :
                                valXOffsX - maxNameXStrLength - - dpiRatio*LB_BORDERSPACE;
  float valStrOffsX = maxValStrLength == 0 ? nameXStrOffsX :
                              nameXStrOffsX - maxValStrLength - - dpiRatio*LB_BORDERSPACE;
  float scYOffsX = maxScYLength == 0 ? valStrOffsX :
                                valStrOffsX - maxScYLength - dpiRatio*LB_BORDERSPACE;
  float nameStrOffsX = maxNamesLength == 0 ? scYOffsX :
                                  scYOffsX - maxNamesLength - dpiRatio*LB_BORDERSPACE;
  float bulletOffsX = maxNamesLength == 0 ? nameStrOffsX :
                          nameStrOffsX - dpiRatio*LB_BULLETSZ - dpiRatio*LB_BORDERSPACE;
// Adjust offsets:
  int stepsY = 0;
  if (bulletOffsX >= startX)
  {
    float offsShiftX = (bulletOffsX - startX)/2.0f;      // Actually startX + (bulletOffsX - startX)/2 
    bulletOffsX   -= offsShiftX;
    nameStrOffsX  -= offsShiftX;
    scYOffsX      -= offsShiftX;
    valStrOffsX   -= offsShiftX;
    nameXStrOffsX -= offsShiftX;
    valXOffsX     -= offsShiftX;
    nameYStrOffsX -= offsShiftX;
    valYOffsX     -= offsShiftX;
  }
  else
  {
    float offsShiftX = startX - bulletOffsX;
    bulletOffsX  += offsShiftX;
    nameStrOffsX += offsShiftX;
    scYOffsX     += offsShiftX;

    stepsY = STEPY_VALSTR;

    if (valStrOffsX < startX)                             // Three lines
    {
      float valWidth = max(maxStrXLength, maxStrYLength);  // Beautification
      float nmWidth  = max(maxNameXStrLength, maxNameYStrLength);
      valYOffsX      = endX - valWidth;
      nameYStrOffsX  = maxNameYStrLength == 0 ? valYOffsX :
                          valYOffsX - nmWidth - dpiRatio*LB_BORDERSPACE;
      valXOffsX      = valYOffsX;
      nameXStrOffsX  = maxNameXStrLength == 0 ? valXOffsX :
                      valXOffsX - nmWidth - dpiRatio*LB_BORDERSPACE;
      valStrOffsX    = maxValStrLength == 0 ?  nameXStrOffsX :
           min(nameXStrOffsX, nameYStrOffsX) - maxValStrLength - dpiRatio*LB_BORDERSPACE;
      stepsY |= STEPY_VALY;
    }
  }

  TUPLE_PRNLEGLAYOUT tuple_res;

  get<IDX_BULLETSTART>(tuple_res) = bulletOffsX, 
  get<IDX_NAME>(tuple_res)        = nameStrOffsX;
  get<IDX_SCY>(tuple_res)         = scYOffsX;
  get<IDX_VAL>(tuple_res)         = valStrOffsX;;
  get<IDX_NAMEX>(tuple_res)       = nameXStrOffsX;
  get<IDX_X>(tuple_res)           = valXOffsX;
  get<IDX_NAMEY>(tuple_res)       = nameYStrOffsX;
  get<IDX_Y>(tuple_res)           = valYOffsX;

  get<IDX_STEPSY>(tuple_res)      = stepsY;

  return tuple_res;
}

void CChartContainer::PrintCrossLine(PointF pntStartF, PointF pntEndF, SizeF sizeCrossF, 
                                               Pen* penPtr, Graphics* grPtr, float dpiRatio)
{
  PointF pntXF(dpiRatio*sizeCrossF.Width/2, 0.0f);
  PointF pntYF(0.0f, dpiRatio*sizeCrossF.Height/2);
  float space = dpiRatio*(sizeCrossF.Width + 1.0f);
// Lets calculate number of Crosses
  int CrossNmb = static_cast<int>(floor((pntEndF.X - pntStartF.X + dpiRatio*1.0f)/space));

  GraphicsPath grPath;
  pntStartF.X += pntXF.X;   // Move to the center of the first fugure

  for (int i = 0; i < CrossNmb; ++i)
  {
    grPath.AddLine(pntStartF - pntXF, pntStartF + pntXF);
    grPath.StartFigure();
    grPath.AddLine(pntStartF - pntYF, pntStartF + pntYF);
    grPath.StartFigure();

    pntStartF.X += space;
  }
  grPtr->DrawPath(penPtr, &grPath);  
}

void CChartContainer::GetSelValString(RectF drawRF, CChart* chartPtr, double pageScY, 
                                      MAP_PRNDATA& mapPrnData, float dpiRatio)
{
  string_t emptyStr;
  sstream_t stream_t;
  TUPLE_PRINT tuple_chart;

// Insert pen and brush attributes
  get<IDX_COL>(tuple_chart) = SetAlpha(chartPtr->GetChartColor(), 0xFF);
  get<IDX_DSTYLE>(tuple_chart) = chartPtr->GetChartDashStyle();
  get<IDX_PWIDTH>(tuple_chart)  = chartPtr->GetPenWidth();

// Insert the chart name
  get<IDX_NAME>(tuple_chart) = chartPtr->GetChartName();

// Insert the Y-scale in unit/inch
  double scaleY     = pageScY/chartPtr->GetLocScaleY();        //Dots per unit
  int chartIdx      = chartPtr->GetChartIdx();
  int precisionY = chartPtr->GetPrecisionY();
  double minY       = chartPtr->GetMinValY();
  double maxY       = chartPtr->GetMaxValY();
  val_label_str_fn valStrFnPtr = chartPtr->GetLabYValStrFnPtr();
  drawRF.Inflate(DR_SPACE*dpiRatio, DR_SPACE*dpiRatio);

  stream_t << _T("ScaleY = ") << setprecision(precisionY + 1) << scaleY 
         << _T(" units/inch") << _T("\n");
  get<IDX_SCY>(tuple_chart) = stream_t.str();

// Insert selected points values
  MAP_SELPNTSD::const_iterator it = m_mapSelPntsD.find(chartIdx);
  if (it == m_mapSelPntsD.cend()) // It is names only
  {
    get<IDX_VAL>(tuple_chart)   = emptyStr;
    get<IDX_NAMEX>(tuple_chart) = string_t(_T("MinY"));
    get<IDX_X>(tuple_chart)     = valStrFnPtr(minY, precisionY, true);
    get<IDX_NAMEY>(tuple_chart) = string_t(_T("MaxY"));
    get<IDX_Y>(tuple_chart)     = valStrFnPtr(maxY, precisionY, true);
    mapPrnData.insert(MAP_PRNDATA::value_type(chartIdx, tuple_chart));
  }
  else
  { 
    size_t count = m_mapSelPntsD.count(chartIdx);
    for (size_t cnt = 0; cnt < count; ++cnt)
    {
      if (cnt != 0)
      {
        get<IDX_NAME>(tuple_chart) = emptyStr;
        get<IDX_SCY>(tuple_chart)  = emptyStr;
      }

      get<IDX_VAL>(tuple_chart) = string_t(_T("Sel. Point: "));

      PointD selPntD = it->second;        // Value X, Y
      PointF selPntF = m_pMatrixD->TransformToPntF(chartPtr->GetLocScaleY(), selPntD);
      if (drawRF.Contains(selPntF))
      {
        get<IDX_NAMEX>(tuple_chart) = m_labelX;
        bool bEqSign = m_labelX.empty() ? false : true;
        get<IDX_X>(tuple_chart) = valStrFnPtr(selPntD.X, m_precision, bEqSign);
        string_t labelY = chartPtr->GetAxisYName();
        get<IDX_NAMEY>(tuple_chart) = labelY;
        bEqSign = labelY.empty() ? false : true;
        get<IDX_Y>(tuple_chart) = valStrFnPtr(selPntD.Y, precisionY, bEqSign);
      }
      else
      {
        get<IDX_VAL>(tuple_chart)   = string_t(_T("Sel. N/A, "));
        get<IDX_NAMEX>(tuple_chart) = string_t(_T("MinY"));
        get<IDX_X>(tuple_chart)     = valStrFnPtr(minY, precisionY, true);
        get<IDX_NAMEY>(tuple_chart) = string_t(_T("MaxY"));
        get<IDX_Y>(tuple_chart)     = valStrFnPtr(maxY, precisionY, true);
     }

      mapPrnData.insert(MAP_PRNDATA::value_type(chartIdx, tuple_chart));
      ++it;
    }
  }
}

size_t CChartContainer::PrintChartNames(RectF namesRF, const MAP_PRNDATA& mapPrnData, 
           const TUPLE_PRNLEGLAYOUT& tuple_prnLayout, Graphics* grPtr, float dpiRatio, size_t mapOffs)
{
// Set the font
  FontFamily fontFamily(_T("Verdana"));
  Gdiplus::Font labelFont(&fontFamily, 8.0f, FontStyleBold);
  grPtr->SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);
  float fontHeight = labelFont.GetHeight(grPtr);
  float lineHeight = 1.5f*fontHeight; // In doc. units, 1/300 of inch

// Get offsets
  float lineY    = namesRF.Y;

// Get string layout; do not care whether there are val, nMEx, and nameY  strings
  float bulletStartOffsX = get<IDX_BULLETSTART>(tuple_prnLayout);
  float bulletEndX       = bulletStartOffsX + dpiRatio*LB_BULLETSZ;
  float nmStrOffsX       = get<IDX_NAME>(tuple_prnLayout);;
  float scYStrOffsX      = get<IDX_SCY>(tuple_prnLayout);
  float valStrOffsX      = get<IDX_VAL>(tuple_prnLayout);     
  float nmXOffsX         = get<IDX_NAMEX>(tuple_prnLayout);
  float valXOffsX        = get<IDX_X>(tuple_prnLayout);
  float nmYOffsX         = get<IDX_NAMEY>(tuple_prnLayout);
  float valYOffsX        = get<IDX_Y>(tuple_prnLayout);

  int stepsY             = get<IDX_STEPSY>(tuple_prnLayout);

  Pen bulletPen((ARGB)Color::Black);      // Pen Width is to set later
  SolidBrush txtBr((ARGB)Color::Black);

  MAP_PRNDATA::const_iterator it = mapPrnData.begin();
  MAP_PRNDATA::const_iterator itE = mapPrnData.end();

  advance(it, mapOffs);
  bool bName = mapOffs == 0 ? false : true;   // To begin the new page with full line
  for (; it != itE; ++it)
  {
    TUPLE_PRINT tuple_chart = it->second;     // Get strings prepared
// Set pen and brush attributes
    Color strColor = get<IDX_COL>(tuple_chart);
    bulletPen.SetColor(strColor);
    DashStyle dashStyle = get<IDX_DSTYLE>(tuple_chart);
    bulletPen.SetDashStyle(dashStyle != DashStyleCustom ? dashStyle : DashStyleSolid);
    bulletPen.SetWidth(dashStyle != DashStyleCustom ? 
                                      get<IDX_PWIDTH>(tuple_chart)*dpiRatio : dpiRatio);
    txtBr.SetColor(strColor);

    PointF pntStartF, pntEndF;

    string_t tmpStr = get<IDX_NAME>(tuple_chart); 
    if (!tmpStr.empty())            
    {
      bName = false;
      pntStartF = PointF(bulletStartOffsX, lineY + fontHeight/2.0f);
      pntEndF   = PointF(bulletEndX, lineY + fontHeight/2.0f);

      if (dashStyle != DashStyleCustom)
      {
        grPtr->DrawLine(&bulletPen, pntStartF, pntEndF);
      }
      else
      {
        PrintCrossLine(pntStartF, pntEndF, SizeF(CHART_DTPNTSZ, CHART_DTPNTSZ),  
                                                      &bulletPen, grPtr, dpiRatio);
      }
// Draw name string
      grPtr->DrawString(tmpStr.c_str(), -1, &labelFont, PointF(nmStrOffsX, lineY), &txtBr);
// Draw scY string 
      tmpStr = get<IDX_SCY>(tuple_chart);
      grPtr->DrawString(tmpStr.c_str(), -1, &labelFont, PointF(scYStrOffsX, lineY), &txtBr);
    }
    else if (bName)
    {
      MAP_PRNDATA::const_reverse_iterator rIt = MAP_PRNDATA::const_reverse_iterator(it);
      for (; rIt != mapPrnData.crend(); ++rIt)
      {
        TUPLE_PRINT tuple_nm = rIt->second;
        tmpStr = get<IDX_NAME>(tuple_nm);
        if (tmpStr.empty())
          continue;
        if (dashStyle != DashStyleCustom)
        {
          grPtr->DrawLine(&bulletPen, pntStartF, pntEndF);
        }
        else
        {
           PrintCrossLine(pntStartF, pntEndF, SizeF(CHART_DTPNTSZ, CHART_DTPNTSZ),  
                                                      &bulletPen, grPtr, dpiRatio);
        }
        grPtr->DrawString(tmpStr.c_str(), -1, &labelFont, PointF(nmStrOffsX, lineY), &txtBr);
        tmpStr = get<IDX_SCY>(tuple_nm);
        grPtr->DrawString(tmpStr.c_str(), -1, &labelFont, PointF(scYStrOffsX, lineY), &txtBr);
        bName = false;
        break;
      }
    }

    if (stepsY & STEPY_VALSTR)
      lineY += lineHeight;
    
    tmpStr = get<IDX_VAL>(tuple_chart);
    if (!tmpStr.empty())
      grPtr->DrawString(tmpStr.c_str(), -1, &labelFont, PointF(valStrOffsX, lineY), &txtBr);
    tmpStr = get<IDX_NAMEX>(tuple_chart);
    if (!tmpStr.empty())
      grPtr->DrawString(tmpStr.c_str(), -1, &labelFont, PointF(nmXOffsX, lineY), &txtBr);
    grPtr->DrawString(get<IDX_X>(tuple_chart).c_str(), -1, &labelFont, 
                                                          PointF(valXOffsX, lineY), &txtBr);
    if (stepsY & STEPY_VALY)
      lineY += lineHeight;

    tmpStr = get<IDX_NAMEY>(tuple_chart);
    if (!tmpStr.empty())
      grPtr->DrawString(tmpStr.c_str(), -1, &labelFont, PointF(nmYOffsX, lineY), &txtBr);
    grPtr->DrawString(get<IDX_Y>(tuple_chart).c_str(), -1, &labelFont, 
                                                              PointF(valYOffsX, lineY), &txtBr);

    ++mapOffs;
    lineY += lineHeight;

    if ((lineY + 3*lineHeight) > namesRF.GetBottom())
      return mapOffs;

  }
  return mapOffs;
}

string_t CChartContainer::PrintPageHeader(RectF rGdiF, const CChartContainer* pContainer, 
                                                                         string_t tmStr, Graphics* grPtr)
{
 // Form bounding rectangles
  RectF headerRF = rGdiF;
  headerRF.Height = 80.0f; // Approx. 7 mm 
  headerRF.Inflate(-200.0f, 0.0f);
  headerRF.Offset(0.0f, 250.0f);

// Draw header
  FontFamily fontFamily(_T("Verdana"));
  Gdiplus::Font headerFont(&fontFamily, 16.0f, FontStyleRegular, UnitPoint);
  grPtr->SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);

  SolidBrush brHeader((ARGB)Color::Black);
  StringFormat strFormat;
  strFormat.SetAlignment(StringAlignmentNear);
  strFormat.SetLineAlignment(StringAlignmentCenter);

  grPtr->DrawString(pContainer->m_name.c_str(),-1, &headerFont,  headerRF, &strFormat, &brHeader);

  if (tmStr.empty())
  {
    // Print current local time
    CTime tmD = CTime::GetCurrentTime();
    CString strTmD = tmD.Format(_T("%a, %b %d, %Y, %H:%M"));
    tmStr = string_t(strTmD);
  }

// Draw time string
  strFormat.SetAlignment(StringAlignmentFar);
  grPtr->DrawString(tmStr.c_str(), -1, &headerFont,  headerRF, &strFormat, &brHeader);

  return tmStr;
}

void CChartContainer::PrintCharts(CChartContainer* pContainer, int scrDpiX, HDC printDC)
{
  if (printDC != NULL)
  {
    pContainer->m_bAxBoundaries = true;

    CDC* pDC = new CDC;
    pDC->Attach(printDC); 
    Graphics* grPtr = new Graphics(printDC);

    grPtr->SetPageUnit(UnitDocument);
    grPtr->SetSmoothingMode(SmoothingModeAntiAlias);
// Get dpiRatio
    float dpiRatioX = 300.0f/scrDpiX;

    RectF rGdiF;
    grPtr->GetVisibleClipBounds(&rGdiF);                 // The same as the clip rect

// Begin Printing
    pDC->StartDoc(pContainer->m_name.c_str());          // MFC functions
    pDC->StartPage();

// Draw header
    string_t tmStr;
    tmStr = pContainer->PrintPageHeader(rGdiF, pContainer, tmStr, grPtr);

// Format drawing area and draw a border
    RectF drawRF = rGdiF;   
    drawRF.Inflate(-200.0f, -200.0f);
    drawRF.Height = rGdiF.Width*0.75f;
    drawRF.Offset(0.0f, 200.0f);

    SolidBrush bkBr(pContainer->m_colBkground);
    grPtr->FillRectangle(&bkBr, drawRF);

    Pen pen((ARGB)Color::Black, 3*dpiRatioX);
    pen.SetAlignment(PenAlignmentInset);
    grPtr->DrawRectangle(&pen, drawRF);
// Set clipping region    
    grPtr->SetClip(drawRF);

// Draw charts
    if (pContainer->m_mapCharts.size() > 0)
    {
// Draw axes
      PointF pntOrigF = pContainer->DrawAxes(drawRF, pContainer->m_startX, pContainer->m_endX, 
                        pContainer->m_minExtY, pContainer->m_maxExtY, pContainer->m_colAxis, grPtr);
// Draw Grid
      pContainer->DrawGrid(drawRF, pntOrigF, grPtr, dpiRatioX);
// Set margins for drawing
      drawRF.Inflate(-DR_SPACE*dpiRatioX, -DR_SPACE*dpiRatioX);
// Update scales
      std::pair <double, double> pair_scales = 
        pContainer->UpdateScales(drawRF, pContainer->m_startX,pContainer->m_endX, 
                                                      pContainer->m_minExtY, pContainer->m_maxExtY);
      pContainer->m_scX = pair_scales.first;
      pContainer->m_scY = pair_scales.second;
// Set transform matrix
      MatrixD matrixD;
      matrixD.Translate(pntOrigF.X, pntOrigF.Y); // Add drawRF
      matrixD.Scale(float(pContainer->m_scX), float(-pContainer->m_scY));
      if ((pContainer->m_axXPos != BETWEEN)||(pContainer->m_axYPos != MIDDLE))
      {
        double translateX = (pContainer->m_axXPos == RIGHT) ? -pContainer->m_endX : 
                        (pContainer->m_axXPos == LEFT) ? -pContainer->m_startX : 0.0;
        double translateY = (pContainer->m_axYPos == BOTTOM) ? -pContainer->m_minExtY : 
                        (pContainer->m_axYPos == TOP) ? -pContainer->m_maxExtY : 0.0;
        matrixD.Translate(float(translateX), float(translateY)); 
      }
      if (pContainer->m_pMatrixD != NULL)
        delete pContainer->m_pMatrixD;
      pContainer->m_pMatrixD = matrixD.Clone();     // Can't copy: copy constructor is private

// Calculate page scale Y to display on the legend
      ENSURE (grPtr->GetPageUnit() == UnitDocument); // Sure it is: set it at the start of printing
      PointD pntStartD, pntEndD;
      CPoint pntStart(int(drawRF.X), int(drawRF.Y)), pntEnd(int(drawRF.GetRight()), int(drawRF.GetBottom()));
      pContainer->MousePntToPntD(pntStart, pntStartD, pContainer->m_pMatrixD);
      pContainer->MousePntToPntD(pntEnd, pntEndD, pContainer->m_pMatrixD);
      double pageScY = 300.0*(pntStartD.Y - pntEndD.Y)/drawRF.Height;  // 300 dpi/inch for the Doc. Mode
  
// Begin draw all visible curves or selected only
      int chartSelIdx = pContainer->HasSelectedChart();      
      MAP_LABSTR mapLabStr;
      size_t mapOffs = 0;
 
      GraphicsPath grPath;            // Will be used by the drawing routines
      MAP_CHARTS::iterator it;
      if (chartSelIdx == -1)
        it = pContainer->m_mapCharts.begin();
      else 
       it = pContainer->m_mapCharts.find(chartSelIdx);
      MAP_CHARTS::iterator itE = pContainer->m_mapCharts.end();

      MAP_PRNDATA mapPrnData;

      for (; it != itE; ++it)
      {
        CChart* chartPtr = it->second;
        if (chartPtr->IsChartVisible() == false)
          continue;
        if (chartPtr->m_vDataPnts.size() == 0)
          continue;
        chartPtr->DrawChartCurve(chartPtr->m_vDataPnts, pContainer->m_startX, pContainer->m_endX, 
                                                         pContainer->m_pMatrixD, &grPath, grPtr, dpiRatioX);
        if (pContainer->m_bTracking)
          pContainer->DrawSelPntsD(chartPtr, pContainer->m_mapSelPntsD, &grPath, grPtr, drawRF, dpiRatioX);

        pContainer->GetSelValString(drawRF, chartPtr, pageScY, mapPrnData, dpiRatioX);

        if (chartSelIdx > -1)
          break;
      }

      if (pContainer->m_bTracking)
        pContainer->DrawSelLine(drawRF, grPtr, chartSelIdx, dpiRatioX);

      if (pContainer->m_bAxBoundaries)
        pContainer->DrawAxXBoundaryStr(drawRF, grPtr);
  // Now print the legend: chart names, etc.
      grPtr->ResetClip();
      RectF namesRF(drawRF.X, drawRF.GetBottom() + 100.0f, drawRF.Width, 0.0f);
      namesRF.Height = rGdiF.GetBottom() - namesRF.Y; // temporary; will be set in PrintChartNames
      TUPLE_PRNLEGLAYOUT tuple_prnLayout = 
        pContainer->CalcNamesPntLayout(namesRF, mapPrnData, grPtr, dpiRatioX);
      mapOffs = pContainer->PrintChartNames(namesRF, mapPrnData, tuple_prnLayout, grPtr, dpiRatioX);
      if (mapOffs < mapPrnData.size())        // Print the rest of the legend on next pages
      {
// Get new namesRF
        namesRF = rGdiF;   
        namesRF.Inflate(-200.0f, -200.0f);
        namesRF.Height = rGdiF.Height - 300.0f;
        namesRF.Offset(0.0f, 300.0f);
// Print extra pages
        while (mapOffs < mapPrnData.size())
        {
          pDC->EndPage();
          pDC->StartPage();
          pContainer->PrintPageHeader(rGdiF, pContainer, tmStr, grPtr);
          mapOffs = pContainer->PrintChartNames(namesRF, mapPrnData, tuple_prnLayout, grPtr, 
                                                                dpiRatioX, mapOffs);
        }
      }
    }
// End printing
    pDC->EndPage();
    pDC->EndDoc();

    delete grPtr;
    pDC->Detach();
    delete pDC;
  }
}


BEGIN_MESSAGE_MAP(CChartContainer, CWnd)
  ON_WM_PAINT()
  ON_WM_SIZE()
  ON_WM_GETDLGCODE()
  ON_WM_LBUTTONDOWN()
  ON_WM_LBUTTONDBLCLK()
  ON_WM_LBUTTONUP()
  ON_WM_SETCURSOR()
  ON_WM_MBUTTONDOWN()
  ON_WM_MOUSEWHEEL()
  ON_WM_CONTEXTMENU()
  ON_COMMAND(IDM_CHART_LEGEND, OnMnShowLegend)
  ON_COMMAND(IDM_CHART_BOUNDARIES, OnMnShowAxBoundaries)
  ON_COMMAND(IDM_CHART_REFRESH, OnMnRefresh)
  ON_COMMAND(IDM_CHART_VIEWDATA, OnMnViewData)
  ON_COMMAND(IDM_CHART_SAVE, OnMnSaveCharts)
  ON_COMMAND(IDM_CHART_SAVEIMAGE, OnMnSaveImage)
  ON_COMMAND(IDM_CHART_PRINT, OnMnPrintCharts)

  ON_COMMAND(IDM_CHART_ZOOMINX, OnMnZoomInX) 
  ON_COMMAND(IDM_CHART_MOVERIGHT, OnMnMoveRight)
  ON_COMMAND(IDM_CHART_MOVELEFT, OnMnMoveLeft)
  ON_COMMAND(IDM_CHART_ZOOMINY, OnMnZoomInY)
  ON_COMMAND(IDM_CHART_MOVEUP, OnMnMoveUp)
  ON_COMMAND(IDM_CHART_MOVEDOWN, OnMnMoveDown)
  ON_COMMAND(IDM_CHART_TRACK, OnMnEnableTracking)
  ON_COMMAND(IDM_CHART_SHOWPNTS, OnMnShowPnts)
  ON_COMMAND(IDM_CHART_UNDOZOOMMOVEX, OnMnUndoHistStepX)
  ON_COMMAND(IDM_CHART_UNDOZOOMMOVEY, OnMnUndoHistStepY)
  ON_COMMAND_RANGE(IDM_CHART_FIRSTCHART, IDM_CHART_LASTCHART, OnMnShowChart)
#ifdef _TEST_
  ON_COMMAND(IDM_CHART_TEST, OnMnTest)
#endif
  ON_WM_KEYDOWN()
  ON_WM_KEYUP()
  ON_WM_TIMER()
  ON_WM_KILLFOCUS()
  ON_WM_SETFOCUS()
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////////
// CChartContainer message handlers
void CChartContainer::OnPaint()
{
  CPaintDC dc(this);                              // Device context for painting
  Graphics gr(dc.m_hDC);                          // Graphics to paint

  Rect rGdi;
  gr.GetVisibleClipBounds(&rGdi);                 // The same as the clip rect

  Bitmap clBmp(rGdi.Width, rGdi.Height);          // Mem bitmap
  Graphics* grPtr = Graphics::FromImage(&clBmp);  // As memDC
  grPtr->SetSmoothingMode(SmoothingModeAntiAlias);

  RectF rGdiF = GdiRectToRectF(rGdi);
  rGdiF.Width -= 1.0f;
  rGdiF.Height -= 1.0f;
  DrawBkground(rGdiF, m_colBkground, m_colBorder, grPtr);

  if (m_mapCharts.size() > 0)
  {
    RestoreModeX();  // Might be in the middle of settung zoon borders; annuls the mode
    RestoreModeY();
    RectF drawRF = rGdiF;   
    drawRF.Inflate(-DR_SPACE, -DR_SPACE);
// Draw axes
    if (!HasChartWithData(-1))
      DrawGrid(drawRF, PointF(0.0f, 0.0f), grPtr);
    else  // Proceed normally even with one point data
    {
      PointF pntOrigF = DrawAxes(drawRF, m_startX, m_endX, m_minExtY, m_maxExtY, m_colAxis, grPtr);
// Draw a grid    
      DrawGrid(drawRF, pntOrigF, grPtr);
// Update scales
      std::pair<double, double> pair_scales = 
                  UpdateScales(drawRF, m_startX, m_endX, m_minExtY, m_maxExtY);
      m_scX = pair_scales.first;
      m_scY = pair_scales.second;
// Set transform matrix
      MatrixD matrixD;
      matrixD.Translate(pntOrigF.X, pntOrigF.Y);
      matrixD.Scale(m_scX, -m_scY);
// Account for not even axes placement
      if ((m_axXPos != BETWEEN)||(m_axYPos != MIDDLE))
      {
        double translateX = (m_axXPos == RIGHT) ? -m_endX : (m_axXPos == LEFT) ? -m_startX : 0.0;
        double translateY = (m_axYPos == BOTTOM) ? -m_minExtY : (m_axYPos == TOP) ? -m_maxExtY : 0.0;
        matrixD.Translate(translateX, translateY);
      }
      if (m_pMatrixD != NULL)
        delete m_pMatrixD;
      m_pMatrixD = matrixD.Clone();     // Copy constructor is private ??

// Begin draw curves
      GraphicsPath grPath;            // Will be used actoss the drawing routines
      MAP_CHARTS::iterator it = m_mapCharts.begin();
      MAP_CHARTS::iterator itE = m_mapCharts.end();
      for (; it != itE; ++it)
      {
        CChart* chartPtr = it->second;
        if (chartPtr->IsChartVisible() == false)
          continue;
        if (chartPtr->m_vDataPnts.size() == 0)
          continue;
        chartPtr->DrawChartCurve(chartPtr->m_vDataPnts, m_startX, m_endX, 
                                                       m_pMatrixD, &grPath, grPtr);
        if (m_bTracking&&IsLabWndExist(false))              // Draw selection points if any
          DrawSelPntsD(chartPtr, m_mapSelPntsD, &grPath, grPtr, drawRF);
      }

      if (m_bTracking)                // And the sel line too
        DrawSelLine(rGdiF, grPtr);
      if (!m_mapDataViewPntsD.empty())// Were selected in the data view
        DrawDataViewPntsD(m_mapDataViewPntsD, m_startX, m_endX, &grPath, grPtr);

      if (m_bAxBoundaries)            // Left and right visible X-coordinates
        DrawAxXBoundaryStr(rGdiF, grPtr);
    }   // Normal painring
  }
 // Transfer to the screen
  delete grPtr;
  gr.DrawImage(&clBmp, rGdi);
}

void CChartContainer::OnSize(UINT nType, int cx, int cy)
{
  UNREFERENCED_PARAMETER(cx);
  UNREFERENCED_PARAMETER(cy);

  CWnd* pParent = GetParent();
  if ((pParent == NULL)&&((nType == SIZE_RESTORED)||(nType == SIZE_MAXIMIZED)))
  {
    bool bRes = HideLabWnds();
    if (bRes)
      SetTimer(2, 50, NULL);
  }
}

UINT CChartContainer::OnGetDlgCode()
{
  UINT nCode = CWnd::OnGetDlgCode();
  nCode |= DLGC_WANTARROWS;
  return nCode;
}

void CChartContainer::OnLButtonDown(UINT nFlags, CPoint point)
{
  if ((m_bUserEnabled)&&HasChartWithData(-1, true))
  {
    SetFocus();
    if (nFlags & MK_SHIFT)
    {
      if (m_chModeY == MODE_ZOOMINGY)
        PrepareZoomY(point, m_tmpZY, true);     // Zoom Y
      else if ((m_chModeX != MODE_ZOOMINGX) && (m_chModeX != MODE_ZOOMX))
        m_tmpZX = BeginZoomingX(point, true);    // Sets m_startX, m_chMode = MODE_ZOOMING
      else if (m_chModeX == MODE_ZOOMINGX)
        PrepareZoomX(point, m_tmpZX, true);     // Sets m_startX, m_endX, m_chMode = MODE_ZOOM
    }
    else if (nFlags & MK_CONTROL)
    {
      m_chartSelIdx = SelectChartByMouseClick(point);
      UpdateContainerWnds();
    }
    else if (m_bTracking)
      ShowDataLegend(point);
  }

  CWnd::OnLButtonDown(nFlags, point);
}

void CChartContainer::OnLButtonDblClk(UINT nFlags, CPoint point)
{
  if ((m_bUserEnabled)&&HasChartWithData(-1, true))
  {
    if ((nFlags & MK_SHIFT)&&(m_chModeY != MODE_ZOOMINGY) && (m_chModeY != MODE_ZOOMY))
    {
      m_tmpZY = BeginZoomingY(point, true);    // Sets m_startX, m_chMode = MODE_ZOOMING
    }
  }

  CWnd::OnLButtonDblClk(nFlags, point);
}

void CChartContainer::OnLButtonUp(UINT nFlags, CPoint point)
{
  if (m_bUserEnabled&&HasChartWithData(-1, true))
  {
    if ((m_chModeX == MODE_ZOOMX)||(m_chModeY == MODE_ZOOMY))
    {
      UpdateContainerWnds(-1, true);
 
      m_chModeX = m_vHistX.empty() ? MODE_FULLX : MODE_ZOOMEDX;
      m_chModeY = m_vHistY.empty() ? MODE_FULLY : MODE_ZOOMEDY;
    }
  }

  CWnd::OnLButtonUp(nFlags, point);
}

void CChartContainer::OnMButtonDown(UINT nFlags, CPoint point)
{
  if (m_bUserEnabled)   // Responsibility of the programmer to set visiuals and cursor on m_bUserEnabled
  {
    SetFocus();
    bool bHasVisibleChart = HasChartWithData(-1, true);
    bool bOldTrack = m_bTracking;
    m_bTracking = bHasVisibleChart ? !m_bTracking : false;
    if ((bOldTrack != m_bTracking) && !m_bTracking)
    {
      m_pDataWnd = DestroyLabWnd(m_pDataWnd);
      m_dataLegPntD = PointD(DBL_MAX, DBL_MAX);
    }
  }
 
  CWnd::OnMButtonDown(nFlags, point);
}

BOOL CChartContainer::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
  if (m_bUserEnabled&&HasChartWithData(-1, true))
  {
    if (nFlags & MK_SHIFT)            // Move 
    {
      double deltaX = 0.0;
      if (zDelta < 0)
        deltaX = MOVE_DELTAX*(m_endX - m_startX);
      else if (zDelta > 0)
        deltaX = -MOVE_DELTAX*(m_endX - m_startX);
      else
        return CWnd::OnMouseWheel(nFlags, zDelta, pt);

      MoveContainerChartsX(deltaX, true);
    }
    else if ((nFlags & MK_CONTROL) &&  (m_chartSelIdx != -1))
    {
      MAP_CHARTS::iterator it = m_mapCharts.find(m_chartSelIdx);
      if (it != m_mapCharts.end())
      {
        CChart* chartPtr = it->second;
        if (chartPtr->IsChartVisible() && chartPtr->IsChartSelected())
        {
          std::pair<double, double> pair_scY = GetLocScYDelta(chartPtr);
          double locScY = pair_scY.first;
          double deltaK = pair_scY.second;

          if (zDelta < 0)
            locScY = locScY = max(locScY - deltaK, DELTA_LOCY);
          else
            locScY += deltaK;

          if (IsLabWndExist(false))
          {
            if (m_pDataWnd->IsWindowVisible())
              m_pDataWnd->ShowWindow(SW_HIDE);
            SetTimer(1, 500, NULL);
          }

          chartPtr->SetLocScaleY(locScY);  
          RefreshWnd();
        }
      }
    }
  }
  return CWnd::OnMouseWheel(nFlags, zDelta, pt);
}

void CChartContainer::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
  if (m_bUserEnabled&&HasChartWithData(-1, true))
  {
    if (nChar == VK_DELETE)
    {
      if (IsWindow(m_hWnd)&&IsWindowVisible() &&((m_chModeX == MODE_ZOOMINGX)||(m_chModeY == MODE_ZOOMINGY)))
        RefreshWnd();
    }
    else if ((nChar == VK_LEFT)||(nChar == VK_RIGHT))    // Move
      MoveChartsByArrowKeysX(nChar);
    else if ((nChar == VK_DOWN)||(nChar == VK_UP)||(nChar == VK_PRIOR)||(nChar == VK_NEXT))
    {
      if (m_chartSelIdx != -1)
        ChangeLocScaleYByArrowKeys(nChar);
      else
        MoveChartsByArrowKeysY(nChar);
    }
  }

  CWnd::OnKeyDown(nChar, nRepCnt, nFlags); 
}

BOOL CChartContainer::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
  if (nHitTest == HTCLIENT)
  {
    HCURSOR hCursor = NULL;
    if (m_bTracking)
      hCursor = LoadCursor(NULL, IDC_CROSS);
    else  
      hCursor = LoadCursor(NULL, IDC_ARROW);
    if (hCursor != NULL)
    {
      SetCursor(hCursor);
    }
    return TRUE;

  }
  return CWnd::OnSetCursor(pWnd, nHitTest, message);
}

void CChartContainer::OnTimer(UINT_PTR nIDEvent)
{
  if (nIDEvent == 1)        // Append
  {
    KillTimer(nIDEvent);

    if (m_bTracking && IsLabWndExist(false))
      UpdateDataLegend(true); 
  }
  else if (nIDEvent == 2)   // LocScY or window size was changed or size 
  {
    KillTimer(nIDEvent);
    UpdateContainerWnds();
  }
}

void CChartContainer::OnKillFocus(CWnd* pNewWnd)
{
  CWnd::OnKillFocus(pNewWnd);
  RefreshWnd();
}

void CChartContainer::OnSetFocus(CWnd* pOldWnd)
{
  CWnd::OnSetFocus(pOldWnd);
  RefreshWnd();
}

// Context menu functions
void CChartContainer::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
{
  if (!m_bUserEnabled)
    return;
  if (m_mapCharts.empty())
    return;  

  bool bHasChartWithData = HasChartWithData(-1, true);

  CMenu popMenu;
  popMenu.CreatePopupMenu();   
  if (bHasChartWithData)
  {
    UINT chEnabledLeg = IsLabWndVisible(true) ? MF_CHECKED : MF_UNCHECKED;
    popMenu.AppendMenu(MF_ENABLED|chEnabledLeg, IDM_CHART_LEGEND, _T("Show Legend"));
    popMenu.AppendMenu(MF_SEPARATOR);

    UINT chShowBounds = m_bAxBoundaries ? MF_CHECKED : MF_UNCHECKED;
    popMenu.AppendMenu(MF_ENABLED|chShowBounds, IDM_CHART_BOUNDARIES, _T("Show Axis X boundaries"));
    popMenu.AppendMenu(MF_SEPARATOR);

    popMenu.AppendMenu(MF_ENABLED, IDM_CHART_VIEWDATA, _T("View Chart Data"));
  }

  CMenu popSubMenu1;
  popSubMenu1.CreatePopupMenu();

  popSubMenu1.AppendMenu(MF_ENABLED, IDM_CHART_SAVE, _T("Save chart(s)"));
  if(bHasChartWithData)
  {
    popSubMenu1.AppendMenu(MF_ENABLED, IDM_CHART_SAVEIMAGE, _T("Save Charts As Image"));
    popMenu.AppendMenu(MF_SEPARATOR);
    popSubMenu1.AppendMenuW(MF_ENABLED, IDM_CHART_PRINT, _T("Print charts"));
  }

  string_t subMnStr(_T("Save"));
  if (bHasChartWithData)
    subMnStr += string_t(_T("/Print"));
  subMnStr += string_t(_T(" Charts"));

  popMenu.AppendMenuW(MF_STRING|MF_POPUP, UINT(popSubMenu1.m_hMenu), subMnStr.c_str());
  popMenu.AppendMenu(MF_SEPARATOR);

  if (bHasChartWithData)
  {
    CMenu popSubMenu2;
    popSubMenu2.CreatePopupMenu();

    popSubMenu2.AppendMenu(MF_ENABLED, IDM_CHART_ZOOMINX, _T("Zoom In X "));
    popSubMenu2.AppendMenu(MF_ENABLED, IDM_CHART_MOVERIGHT, _T("Move Right"));
    popSubMenu2.AppendMenu(MF_ENABLED, IDM_CHART_MOVELEFT, _T("Move Left"));
    popSubMenu2.AppendMenu(MF_SEPARATOR);
    popSubMenu2.AppendMenu(MF_ENABLED, IDM_CHART_ZOOMINY, _T("Zoom In Y"));
    popSubMenu2.AppendMenu(MF_ENABLED, IDM_CHART_MOVEUP, _T("Move Up"));
    popSubMenu2.AppendMenu(MF_ENABLED, IDM_CHART_MOVEDOWN, _T("Move Down"));

    popMenu.AppendMenuW(MF_STRING|MF_POPUP, UINT(popSubMenu2.m_hMenu), _T("Zoom/Move"));
    popMenu.AppendMenu(MF_SEPARATOR);

    if (!m_vHistX.empty())
    {
      popMenu.AppendMenu(MF_ENABLED, IDM_CHART_UNDOZOOMMOVEX, _T("Undo Last Zoom/Move X"));
      popMenu.AppendMenu(MF_SEPARATOR);
    }

    if (!m_vHistY.empty())
    {
      popMenu.AppendMenu(MF_ENABLED, IDM_CHART_UNDOZOOMMOVEY, _T("Undo Last Zoom/Move Y"));
      popMenu.AppendMenu(MF_SEPARATOR);
    }

    if (!m_vHistX.empty()||!m_vHistY.empty())
    {
      popMenu.AppendMenu(MF_ENABLED, IDM_CHART_REFRESH, _T("Refresh Charts"));
      popMenu.AppendMenu(MF_SEPARATOR);
    }

// If the mouse has no middle button replace middle button clicks
    if (GetSystemMetrics(SM_CMOUSEBUTTONS) < 3)
    {
      UINT chTracking = m_bTracking ? MF_CHECKED : MF_UNCHECKED;
      popMenu.AppendMenu(MF_ENABLED|chTracking, IDM_CHART_TRACK, _T("Enable Tracking"));

    }

    UINT chShowCharts = MF_CHECKED;         // By default points are shown
    if (m_chartSelIdx != -1)
    {
      CChart* selChartPtr = GetChart(m_chartSelIdx);
      if (!selChartPtr->ArePntsAllowed())
        chShowCharts = MF_UNCHECKED;
    }

    if(HasChartDifferentPnts(-1) > -1)
    {
      popMenu.AppendMenu(MF_ENABLED|chShowCharts, IDM_CHART_SHOWPNTS, _T("Show/Hide Chart Pnts"));
      popMenu.AppendMenu(MF_SEPARATOR);
    }
  }
// Add CChart names (m_mapCharts is not empty)
  string_t mnPrefix1(_T("Show "));
  string_t mnSuffix2(_T(" has no data"));
  string_t mnItemName;
  UINT chkFlag = MF_CHECKED;
  UINT cmdID = IDM_CHART_FIRSTCHART;
  MAP_CHARTS::iterator it = m_mapCharts.begin();
  MAP_CHARTS::iterator itE = m_mapCharts.end();
  for (; it != itE; ++it)
  {
    bool bAllowedToBeDrawn = it->second->HasData();
    UINT enableFlag = bAllowedToBeDrawn ? MF_ENABLED : MF_DISABLED;
    chkFlag = (it->second->IsChartVisible()) ? MF_CHECKED : MF_UNCHECKED;
    if (bAllowedToBeDrawn)
      mnItemName = mnPrefix1 + it->second->GetChartName();
    else
      mnItemName = it->second->GetChartName() + mnSuffix2;
    popMenu.AppendMenu(enableFlag|chkFlag, cmdID + it->first, mnItemName.c_str());
  }
#ifdef _TEST_
  popMenu.AppendMenu(MF_SEPARATOR);
  popMenu.AppendMenu(MF_ENABLED, IDM_CHART_TEST, _T("Test"));
#endif
  popMenu.TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON, point.x, point.y, this);
  popMenu.Detach();
}

void CChartContainer::OnMnShowLegend(void)
{
  if (IsLabWndVisible(true)) 
    m_pLegWnd = DestroyLabWnd(m_pLegWnd);
  else
    ShowNamesLegend();
}

void CChartContainer::OnMnShowAxBoundaries(void)
{
  m_bAxBoundaries = !m_bAxBoundaries;
  RefreshWnd();
}

void CChartContainer::OnMnRefresh(void)
{
  ResetCharts();
  SendNotification(CODE_REFRESH);
}

void CChartContainer::OnMnViewData(void)
{
  if (m_mapCharts.empty())
  {
    AfxMessageBox(_T("Chart Container Is Empty"));
    return;
  }

// Send chart to the data view dlg
  int chartSelIdx = HasSelectedChart();

  if (chartSelIdx == -1)
    chartSelIdx = SelectChart(-1);

  if (chartSelIdx == -1)
  {
    AfxMessageBox(_T("View Chart Data: Has no visible or selected chart"));
    return;
  }
 
  MAP_CHARTS::iterator it = m_mapCharts.find(chartSelIdx);

  if (it == m_mapCharts.end())
    AfxMessageBox(_T("View Chart Data: Can't find selected chart"));

  ShowDataView(it->second, true);
}

void CChartContainer::OnMnSaveCharts(void)
{
  if (S_FALSE == SaveChartData(string_t(_T(""))))  
  {
    AfxMessageBox(_T("Save Failed\n Select the visible chart to save it\n")
                  _T("or remove the selection to save all visible charts\n")
                  _T("and try again"), MB_OK|MB_ICONERROR);
  }
}

void CChartContainer::OnMnSaveImage(void)
{
  Status stat = SaveContainerImage();

  if (stat != Ok)
  {
    string_t errStr(_T("Save Image Failed:\n"));
    switch (stat)
    {
    case InvalidParameter:   errStr += string_t(_T("Cannot get file path name")); break;
    case UnknownImageFormat: errStr += string_t(_T("Invalid Image Format"));      break;
    default:                 errStr += string_t(_T("Unknown Error"));             break;
    }
    AfxMessageBox(errStr.c_str(), MB_OK|MB_ICONERROR);
  }
}

void CChartContainer::OnMnPrintCharts(void)
{
  if (!HasChartWithData(-1, true))
  {
    AfxMessageBox(_T("Has no visible charts to print"), MB_OK|MB_ICONERROR);
    return;
  }

  CPrintDialog printDlg(FALSE, PD_USEDEVMODECOPIES|PD_HIDEPRINTTOFILE|PD_NOSELECTION|PD_RETURNDC);
  printDlg.m_pd.Flags &= ~PD_SHOWHELP;
  printDlg.m_pd.nMinPage = printDlg.m_pd.nMaxPage = 1;
  printDlg.m_pd.nFromPage = printDlg.m_pd.nToPage = 1;

  if (printDlg.DoModal() == IDOK)
  {
    int scrDpiX = GetScreenDpi();
    SendNotification(CODE_PRINTING);
    CChartContainer* pContainer = CloneChartContainer(string_t(_T("")), true);
    SendNotification(CODE_PRINTED);
    PrintCharts(pContainer, scrDpiX, printDlg.GetPrinterDC());
    delete pContainer;
  }
  GlobalFree(printDlg.m_pd.hDevMode);
  GlobalFree(printDlg.m_pd.hDevNames);
}

void CChartContainer::OnMnZoomInX(void)  // ZoomIn from the context menu
{                
  if (m_startX >= m_endX)
    return;

  double fStartZX, fEndZX;
  double extX = m_endX - m_startX;

  if (m_chModeX == MODE_ZOOMINGX)         // Left zoom border was set
  {
    double distStartX = m_tmpZX - m_startX;
    double distEndX   = m_endX - m_tmpZX;
    if (distStartX < distEndX)
    {
      fStartZX = m_tmpZX;
      fEndZX   = m_endX - distStartX;
    }
    else if (distStartX > distEndX)
    {
      fStartZX = m_startX + distEndX;
      fEndZX   = m_tmpZX;
    }
    else
    {
      fStartZX = m_tmpZX + 0.1*extX;
      fEndZX   = m_tmpZX - 0.1*extX;
    }
  }
  else
  {
    fStartZX = m_startX + 0.1*extX;
    fEndZX   = m_endX   - 0.1*extX;
  }

 ZoomContainerX(fStartZX, fEndZX, true);
 SendNotification(CODE_EXTX);
}

void CChartContainer::OnMnMoveRight(void)
{
// Move right
  double deltaX = 10.0*MOVE_DELTAX*(m_endX - m_startX);
  MoveContainerChartsX(deltaX, true);
  SendNotification(CODE_EXTX);
}

void CChartContainer::OnMnMoveLeft(void)
{
// Move left
  double deltaX = -10.0*MOVE_DELTAX*(m_endX - m_startX);
  MoveContainerChartsX(deltaX, true);
  SendNotification(CODE_EXTX);
}

void CChartContainer::OnMnZoomInY(void)
{
  if (m_minExtY >= m_maxExtY)
    return;

  double fStartZY, fEndZY;
  double extY = m_maxExtY - m_minExtY;

  if (m_chModeY == MODE_ZOOMINGY)         // Left zoom border was set
  {
    double distStartY = m_tmpZY - m_minExtY;
    double distEndY   = m_maxExtY - m_tmpZY;
    if (distStartY < distEndY)
    {
      fStartZY = m_tmpZY;
      fEndZY   = m_maxExtY - distStartY;
    }
    else if (distStartY > distEndY)
    {
      fStartZY = m_minExtY + distEndY;
      fEndZY   = m_tmpZY;
    }
    else
    {
      fStartZY = m_minExtY;
      fEndZY   = m_maxExtY;
      if (!m_vHistY.empty())
      {
        fStartZY += 0.1*extY;
        fEndZY   -= 0.1*extY;
      }
    }
  }
  else
  {
    fStartZY = m_minExtY;
    fEndZY   = m_maxExtY;
    if (fStartZY != fEndZY)
    {
      if (m_chModeY != MODE_FULLY)
      {
        fStartZY += 0.1*extY;
        fEndZY   -= 0.1*extY;
      }
    }
  }

 ZoomContainerY(fStartZY, fEndZY, true);
 SendNotification(CODE_EXTY);
}

void CChartContainer::OnMnMoveUp(void)
{
// Move Up
  double deltaY = 10.0*MOVE_DELTAY*(m_maxExtY - m_minExtY);
  MoveContainerChartsY(deltaY, true);
  SendNotification(CODE_EXTY);
}

void CChartContainer::OnMnMoveDown(void)
{
  double deltaY = -10.0*MOVE_DELTAY*(m_maxExtY - m_minExtY);
  MoveContainerChartsY(deltaY, true);
  SendNotification(CODE_EXTY);
}

void CChartContainer::OnMnUndoHistStepX(void)
{
  UndoHistStepX(true);
  SendNotification(CODE_EXTX);
}

void CChartContainer::OnMnUndoHistStepY(void)
{
  UndoHistStepY(true);
  SendNotification(CODE_EXTY);
}

void CChartContainer::OnMnEnableTracking(void)
{
  m_bTracking = !m_bTracking;
  if (!m_bTracking)
  {
    m_pDataWnd = DestroyLabWnd(m_pDataWnd);
    m_dataLegPntD = PointD(DBL_MAX, DBL_MAX);
  }
  SendNotification(CODE_TRACKING);
}

void CChartContainer::OnMnShowPnts(void)
{
  int chartIdx = -1;
  CChart* chartPtr = NULL;
  if (m_chartSelIdx == -1)
  {
    chartIdx = HasChartDifferentPnts(-1, true);
    if (chartIdx != -1)
    {
      m_chartSelIdx = SelectChart(chartIdx);
    }
  }
  else
    chartIdx = HasChartDifferentPnts(m_chartSelIdx, true);

  if (chartIdx == -1)
  {
    AfxMessageBox(_T("Select a chart with enough data points first"), MB_ICONERROR|MB_OK);
    return;
  }
  
  chartPtr = GetChart(m_chartSelIdx);
  ENSURE(chartPtr != NULL);
  bool bShow = chartPtr->ArePntsAllowed();
  chartPtr->AllowPnts(!bShow);
  UpdateContainerWnds();
  SendNotification(CODE_SHOWPNTS, m_chartSelIdx);
}

void CChartContainer::OnMnShowChart(UINT nCmdID)
{
  int chartIdx = nCmdID - IDM_CHART_FIRSTCHART;
  if (ToggleChartVisibility(chartIdx) > -1)
    SendNotification(CODE_VISIBILITY, chartIdx);
}

#ifdef _TEST_

void CChartContainer::OnMnTest(void)
{
//  PAIR_DBLS pd = GetMinMaxExtX();
//  SetContainerPrecision(8, true);
//  CChartContainer* chartContainer = /*CloneChartContainer(string_t(_T("")), true); //*/new CChartContainer;
//  *chartContainer = *this;
//  chartContainer->CreateChartCtrlWnd(0, WS_POPUP|WS_OVERLAPPEDWINDOW|WS_VISIBLE, CRect(100, 100, 500, 400),NULL, NULL);
//  chartContainer.ShowWindow(SW_SHOW);
//    V_CHARTDATAD vReplaceD;

//    V_CHARTDATAD* vChartDataD = GetChartData(1);
//    vReplaceD.assign(vChartDataD->begin(), vChartDataD->end());
//    ReplaceChartData(0, vReplaceD, false, false, false, true);
//    RemoveChart(1, true, true);;
//  vReplaceD.push_back(PointD(5.0, 1.0));
//  V_CHARTDATAD vAppendD;
//  vAppendD.assign(vChartDataD->begin(), vChartDataD->end());
//  AppendChartData(1, vAppendD, true);
//  TruncateChart(0, vChartDataD->begin()->X, (vChartDataD->begin() + 1)->X, false, true);
//  TruncateChart(1, 4.999, 8.0, true, true);
//  RemoveChart(1, true, true);
//  EqualizeVertRanges(0.95, true);
//  SaveContainerImage();
//  SaveChartData(string_t(_T("")), true);
//  MAP_NAMES mapNames;
//  GetChartNamesFromXMLFile(_T("C:\\VS2012\\Projects\\ChartCtrlLib\\Charts\\Test.xml"),  mapNames);
//  auto it = mapNames.begin();
//  ++it;
//  TUPLE_NAMES tN = it->second;
//  string_t name1;
//  string_t name2;
 // string_t name3;
 // string_t name4;
 // std::tie(name1, name2, name3, name4) = tN;
 // ReplaceContainerCharts(_T("C:\\VS2012\\Projects\\ChartCtrlLib\\Charts\\Test.xml")); 
//  SetChartVisibility(2, false, true);
//  ToggleChartVisibility(2);
//  UpdateChartPenWidth(-1, 4.0f, true);
//  SetChartTension(-1, 1.0f, true);
//  std::vector<double> vDt;
//  vDt.push_back(0.465);
//  vDt.push_back(0.865);
//  vDt.push_back(0.665);

//  AddChart(true, true, string_t(_T("")), string_t(_T("Zone")), 5, DashStyleSolid, 2.0f, 0.4f, 
//    Color(ARGB(Color::Green)), vDt, 5.0, 1.0, true);
//  ResetCharts();

  EqualizeVertRanges(0.9, true);
//  AppendChartData(1, vDt, 0.0, 6.0);//, 0, 1, true);
//  m_dataLegPntD = PointD(5.0, 0.0);
//  ShowDataLegend(5.0);
//  ShowNamesLegend();
//  ShowDataView(GetChartName(2), true);

//  CPrintDialog prnDlg(FALSE);
//  BOOL bRes = prnDlg.GetDefaults();
//  PrintCharts(this, 300, prnDlg.m_pd.hDC);

//    ShowAxisXBoundaries(true, true);

//  ResetCharts();
//  ZoomMoveContainer(-5.56, 4.389, true);
//  ShowChartPoints(1, false, true);
//  ToggleChartVisibility(1);
//  bool bEnable = !m_bUserEnabled; 
//  EnableUser(bEnable);
// UpdateChartLocScaleY(-1, 1.5, false, true);
//  string_t name = GetChartName(1);
//  ShowDataView(name, true);
//  SetLegBkgndColor(Color(32, 230, 0, 0), true);
  Color color;

    
}

#endif

void CChartContainer::UpdateDataViewPnts(int chartIdx, size_t dataID, PointD dataPntD, bool bAdd)
{
  bool bRedraw = true;

  if (chartIdx == -1)
  {
    m_mapDataViewPntsD.clear(); 
  }
  else if (m_dataViewChartIdx == chartIdx)
  {
    CChart* chartPtr = GetChart(chartIdx); 
    if (chartPtr != NULL)                   // Chart is not deleted
    {
      if (bAdd)
        m_mapDataViewPntsD.insert(MAP_SELPNTSD::value_type(dataID, dataPntD));
      else
      {
        MAP_SELPNTSD::iterator it = m_mapDataViewPntsD.find(dataID);
        if (it != m_mapDataViewPntsD.end())
          m_mapDataViewPntsD.erase(dataID);
        else
          bRedraw = false;  // Not found the data point
      }
    }
    else                    // Chart was removed, cleanup did not finished
    {
      m_mapDataViewPntsD.clear();
      m_dataViewChartIdx = -1;
      bRedraw = false;      // Chart was redrawn earlier
    }
  }   // Received right chartIdx
  else
    bRedraw = false;

  if (bRedraw && (NULL != GetVisibleChart(m_dataViewChartIdx)))
    RefreshWnd();
}

void CChartContainer::UpdateDataViewPnts(int chartIdx, const MAP_SELPNTSD& mapSelPntsD)
{

  if (chartIdx == m_dataViewChartIdx)
  {
    if (mapSelPntsD.size() > 0)
      m_mapDataViewPntsD = mapSelPntsD;
    else
      m_mapDataViewPntsD.clear();

    if (NULL != GetVisibleChart(chartIdx))
      RefreshWnd();
  }
}

void CChartContainer::EqualizeVertRanges(double spaceMult, bool bRedraw)
{
  MAP_CHARTS mapCharts;
  MAP_CHARTS::const_iterator mapIt = m_mapCharts.cbegin();
  MAP_CHARTS::const_iterator mapItE = m_mapCharts.cend();

  for (; mapIt != mapItE; ++mapIt)
  {
    if (mapIt->second->HasData())
      mapCharts.insert(*mapIt);
  }
                
// Get Range
  if (mapCharts.size() > 1)
  {
    typedef std::pair<double, CChart*> PAIR_CHR;
    typedef std::vector<PAIR_CHR> V_CHR;

    V_CHR vChr(mapCharts.size());
    transform(mapCharts.cbegin(), mapCharts.cend(), vChr.begin(),
      [](const std::pair<int, CChart*>& pair_ch) ->PAIR_CHR 
        {CChart* pChart = pair_ch.second; double val = max(fabs(pChart->GetMaxValY()), fabs(pChart->GetMinValY()));
         return make_pair(val, pChart);});

     sort(vChr.begin(), vChr.end(), [](const PAIR_CHR& lhs, const PAIR_CHR& rhs) ->bool {return lhs.first > rhs.first;});

     double chMaxY = vChr[0].first;
     for_each(++vChr.begin(), vChr.end(),
       [&chMaxY, spaceMult](const PAIR_CHR&  pair_chr) ->void {chMaxY = spaceMult*chMaxY; double locScY = chMaxY/pair_chr.first;
                                                    pair_chr.second->SetLocScaleY(locScY);});

    double minY = GetMinY();
    double maxY = GetMaxY();
    switch (m_axYPos)
    {
    case TOP: maxY = 0.0; break;
    case MIDDLE: maxY = max(fabs(minY), maxY); minY = -maxY; break;
    case BOTTOM: minY = 0.0; break;
    }
    UpdateExtY(minY, maxY, bRedraw);
  }
}

 





By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Verizon Internet Services
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions