/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkChartXY.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

#include "vtkChartXY.h"

#include "vtkContext2D.h"
#include "vtkPen.h"
#include "vtkBrush.h"
#include "vtkColorSeries.h"
#include "vtkChartSelectionHelper.h"

#include "vtkMath.h"
#include "vtkTransform2D.h"
#include "vtkContextScene.h"
#include "vtkContextMouseEvent.h"
#include "vtkContextKeyEvent.h"
#include "vtkContextTransform.h"
#include "vtkContextClip.h"
#include "vtkPoints2D.h"
#include "vtkVector.h"
#include "vtkVectorOperators.h"

#include "vtkPlotArea.h"
#include "vtkPlotBar.h"
#include "vtkPlotBag.h"
#include "vtkPlotFunctionalBag.h"
#include "vtkPlotStacked.h"
#include "vtkPlotLine.h"
#include "vtkPlotPoints.h"
#include "vtkContextMapper2D.h"

#include "vtkAxis.h"
#include "vtkPlotGrid.h"
#include "vtkChartLegend.h"
#include "vtkTooltipItem.h"

#include "vtkDataSetAttributes.h"
#include "vtkTable.h"
#include "vtkIdTypeArray.h"

#include "vtkAnnotationLink.h"
#include "vtkSelection.h"
#include "vtkSelectionNode.h"
#include "vtkSmartPointer.h"

#include "vtkObjectFactory.h"
#include "vtkCommand.h"

#include "vtkStdString.h"
#include "vtkTextProperty.h"

#include "vtkDataArray.h"
#include "vtkStringArray.h"

// My STL containers
#include <vector>
#include <algorithm>

//-----------------------------------------------------------------------------
class vtkChartXYPrivate
{
public:
  vtkChartXYPrivate()
  {
    this->Colors = vtkSmartPointer<vtkColorSeries>::New();
    this->Clip = vtkSmartPointer<vtkContextClip>::New();
    this->Borders[0] = 60;
    this->Borders[1] = 50;
    this->Borders[2] = 20;
    this->Borders[3] = 20;
  }
  vtkPlot* GetPlotByColumn(vtkIdType columnId)
  {
    std::vector<vtkPlot*>::iterator it =
          this->plots.begin();
      for ( ; it != this->plots.end(); ++it)
      {
        vtkPlot* plot = *it;
        vtkTable* table = plot->GetInput();
        const int idx = 1; // column
        if (table &&
            table->GetColumn(columnId) ==
            plot->GetData()->GetInputAbstractArrayToProcess(idx, table))
        {
          return plot;
        }
      }
      return 0;
  }

  std::vector<vtkPlot *> plots; // Charts can contain multiple plots of data
  std::vector<vtkContextTransform *> PlotCorners; // Stored by corner...
  std::vector<vtkAxis *> axes; // Charts can contain multiple axes
  vtkSmartPointer<vtkColorSeries> Colors; // Colors in the chart
  vtkSmartPointer<vtkContextClip> Clip; // Colors in the chart
  int Borders[4];
};

//-----------------------------------------------------------------------------
vtkStandardNewMacro(vtkChartXY);

//-----------------------------------------------------------------------------
vtkChartXY::vtkChartXY()
{
  this->ChartPrivate = new vtkChartXYPrivate;

  this->AutoAxes = true;
  this->HiddenAxisBorder = 20;

  // The plots are drawn in a clipped, transformed area.
  this->AddItem(this->ChartPrivate->Clip);

  // The grid is drawn first in this clipped, transformed area.
  vtkPlotGrid *grid1 = vtkPlotGrid::New();
  this->ChartPrivate->Clip->AddItem(grid1);
  grid1->Delete();

  // The second grid for the far side/top axis
  vtkPlotGrid *grid2 = vtkPlotGrid::New();
  this->ChartPrivate->Clip->AddItem(grid2);
  grid2->Delete();

  // Set up the bottom-left transform, the rest are often not required (set up
  // on demand if used later). Add it as a child item, rendered automatically.
  vtkSmartPointer<vtkContextTransform> corner =
      vtkSmartPointer<vtkContextTransform>::New();
  this->ChartPrivate->PlotCorners.push_back(corner);
  this->ChartPrivate->Clip->AddItem(corner); // Child list maintains ownership.

  // Next is the axes
  for (int i = 0; i < 4; ++i)
  {
    this->ChartPrivate->axes.push_back(vtkAxis::New());
    // By default just show the left and bottom axes
    this->ChartPrivate->axes.back()->SetVisible(i < 2 ? true : false);
    this->AttachAxisRangeListener(this->ChartPrivate->axes.back());
    this->AddItem(this->ChartPrivate->axes.back());
  }
  this->ChartPrivate->axes[vtkAxis::LEFT]->SetPosition(vtkAxis::LEFT);
  this->ChartPrivate->axes[vtkAxis::BOTTOM]->SetPosition(vtkAxis::BOTTOM);
  this->ChartPrivate->axes[vtkAxis::RIGHT]->SetPosition(vtkAxis::RIGHT);
  this->ChartPrivate->axes[vtkAxis::TOP]->SetPosition(vtkAxis::TOP);

  // Set up the x and y axes - should be configured based on data
  this->ChartPrivate->axes[vtkAxis::LEFT]->SetTitle("Y Axis");
  this->ChartPrivate->axes[vtkAxis::BOTTOM]->SetTitle("X Axis");

  grid1->SetXAxis(this->ChartPrivate->axes[vtkAxis::BOTTOM]);
  grid1->SetYAxis(this->ChartPrivate->axes[vtkAxis::LEFT]);
  grid2->SetXAxis(this->ChartPrivate->axes[vtkAxis::TOP]);
  grid2->SetYAxis(this->ChartPrivate->axes[vtkAxis::RIGHT]);

  // Then the legend is drawn
  this->Legend = vtkSmartPointer<vtkChartLegend>::New();
  this->Legend->SetChart(this);
  this->Legend->SetVisible(false);
  this->AddItem(this->Legend);

  this->PlotTransformValid = false;

  this->DrawBox = false;
  this->DrawSelectionPolygon = false;
  this->DrawNearestPoint = false;
  this->DrawAxesAtOrigin = false;
  this->BarWidthFraction = 0.8f;

  this->Tooltip = vtkSmartPointer<vtkTooltipItem>::New();
  this->Tooltip->SetVisible(false);
  this->AddItem(this->Tooltip);
  this->LayoutChanged = true;

  this->ForceAxesToBounds = false;
  this->ZoomWithMouseWheel = true;
  this->AdjustLowerBoundForLogPlot = false;
}

//-----------------------------------------------------------------------------
vtkChartXY::~vtkChartXY()
{
  for (unsigned int i = 0; i < this->ChartPrivate->plots.size(); ++i)
  {
    this->ChartPrivate->plots[i]->Delete();
  }
  for (size_t i = 0; i < 4; ++i)
  {
    this->ChartPrivate->axes[i]->Delete();
  }
  delete this->ChartPrivate;
  this->ChartPrivate = 0;
}

//-----------------------------------------------------------------------------
void vtkChartXY::Update()
{
  // Perform any necessary updates that are not graphical
  // Update the plots if necessary
  for (size_t i = 0; i < this->ChartPrivate->plots.size(); ++i)
  {
    this->ChartPrivate->plots[i]->Update();
  }
  this->Legend->Update();

  // Update the selections if necessary.
  if (this->AnnotationLink)
  {
    this->AnnotationLink->Update();
    vtkSelection *selection =
      vtkSelection::SafeDownCast(this->AnnotationLink->GetOutputDataObject(2));
    // Two major selection methods - row based or plot based.
    if (this->SelectionMethod == vtkChart::SELECTION_ROWS)
    {
      vtkSelectionNode *node = selection->GetNumberOfNodes() > 0?
        selection->GetNode(0) : NULL;
      vtkIdTypeArray *idArray = node?
          vtkArrayDownCast<vtkIdTypeArray>(node->GetSelectionList()) : NULL;
      std::vector<vtkPlot*>::iterator it =
          this->ChartPrivate->plots.begin();
      for ( ; it != this->ChartPrivate->plots.end(); ++it)
      {
        // Use the first selection node for all plots to select the rows.
        (*it)->SetSelection(idArray);
      }
    }
    else if (this->SelectionMethod == vtkChart::SELECTION_PLOTS)
    {
      for (unsigned int i = 0; i < selection->GetNumberOfNodes(); ++i)
      {
        vtkSelectionNode *node = selection->GetNode(i);
        vtkIdTypeArray *idArray =
            vtkArrayDownCast<vtkIdTypeArray>(node->GetSelectionList());
        vtkPlot *selectionPlot =
            vtkPlot::SafeDownCast(node->GetProperties()->Get(vtkSelectionNode::PROP()));
        // Now iterate through the plots to update selection data
        std::vector<vtkPlot*>::iterator it =
            this->ChartPrivate->plots.begin();
        for ( ; it != this->ChartPrivate->plots.end(); ++it)
        {
          if (selectionPlot == *it)
          {
            (*it)->SetSelection(idArray);
          }
        }
      }
    }
    else if (this->SelectionMethod == vtkChart::SELECTION_COLUMNS)
    {
      // Retrieve all the selected plots
      std::vector<vtkPlot*> selectedPlots;
      for (unsigned int i = 0; i < selection->GetNumberOfNodes(); ++i)
      {
        vtkSelectionNode *node = selection->GetNode(i);
        vtkIdTypeArray *selectedColumns =
            vtkArrayDownCast<vtkIdTypeArray>(node->GetSelectionList());
        vtkIdType* ptr = reinterpret_cast<vtkIdType*>(selectedColumns->GetVoidPointer(0));
        for (vtkIdType j = 0; j < selectedColumns->GetNumberOfTuples(); ++j)
        {
          vtkPlot* selectedPlot = this->ChartPrivate->GetPlotByColumn(ptr[j]);
          if (selectedPlot)
          {
            selectedPlots.push_back(selectedPlot);
          }
        }
      }
      // Now iterate through the plots to update selection data
      std::vector<vtkPlot*>::iterator it =
          this->ChartPrivate->plots.begin();
      for ( ; it != this->ChartPrivate->plots.end(); ++it)
      {
        vtkPlot* plot = *it;
        vtkIdTypeArray* plotSelection = 0;
        bool ownPlotSelection = false;
        bool isSelected =
          std::find(selectedPlots.begin(), selectedPlots.end(), plot) !=
          selectedPlots.end();
        if (isSelected)
        {
          static int idx = 1; // y
          vtkAbstractArray* column = plot->GetData()->GetInputAbstractArrayToProcess(
            idx, plot->GetInput());
          plotSelection = plot->GetSelection();
          if (!plotSelection || plotSelection->GetNumberOfTuples() != column->GetNumberOfTuples())
          {
            plotSelection = vtkIdTypeArray::New();
            ownPlotSelection = true;
            for (vtkIdType j = 0; j < column->GetNumberOfTuples(); ++j)
            {
              plotSelection->InsertNextValue(j);
            }
          }
        }
        plot->SetSelection(plotSelection);
        if (ownPlotSelection)
        {
          plotSelection->Delete();
        }
      }
    }
  }
  else
  {
    vtkDebugMacro("No annotation link set.");
  }

  this->CalculateBarPlots();

  if (this->AutoAxes)
  {
    vtkTuple<bool, 4> visibilities(false);
    for (int i = 0; i < static_cast<int>(this->ChartPrivate->PlotCorners.size()); ++i)
    {
      int visible = 0;
      for (unsigned int j = 0;
           j < this->ChartPrivate->PlotCorners[i]->GetNumberOfItems(); ++j)
      {
        if (vtkPlot::SafeDownCast(this->ChartPrivate->PlotCorners[i]
                                  ->GetItem(j))->GetVisible())
        {
          ++visible;
        }
      }
      if (visible)
      {
        visibilities[i % 4] = true;
        visibilities[(i+1) % 4] = true;
      }
    }
    for (int i = 0; i < 4; ++i)
    {
      this->ChartPrivate->axes[i]->SetVisible(visibilities[i]);
    }
  }
}

//-----------------------------------------------------------------------------
bool vtkChartXY::Paint(vtkContext2D *painter)
{
  // This is where everything should be drawn, or dispatched to other methods.
  vtkDebugMacro(<< "Paint event called.");
  if (!this->Visible)
  {
    // The geometry of the chart must be valid before anything can be drawn
    return false;
  }

  vtkVector2i geometry(0, 0);
  bool recalculateTransform = false;
  if (this->LayoutStrategy == vtkChart::FILL_SCENE)
  {
    geometry = vtkVector2i(this->GetScene()->GetSceneWidth(),
                           this->GetScene()->GetSceneHeight());
    if (geometry.GetX() != this->Geometry[0] || geometry.GetY() != this->Geometry[1])
    {
      recalculateTransform = true;
      this->LayoutChanged = true;
    }
    this->SetSize(vtkRectf(0.0, 0.0, geometry.GetX(), geometry.GetY()));
  }

  int visiblePlots = 0;
  for (size_t i = 0; i < this->ChartPrivate->plots.size(); ++i)
  {
    if (this->ChartPrivate->plots[i]->GetVisible())
    {
      ++visiblePlots;
    }
  }
  if (visiblePlots == 0 && !this->RenderEmpty)
  {
    // Nothing to plot, so don't draw anything.
    return false;
  }

  this->Update();

  if (this->MTime < this->ChartPrivate->axes[0]->GetMTime())
  {
    // Cause the plot transform to be recalculated if necessary
    recalculateTransform = true;
    this->LayoutChanged = true;
  }

  this->UpdateLayout(painter);

  // Recalculate the plot transform, min and max values if necessary
  if (!this->PlotTransformValid)
  {
    this->RecalculatePlotBounds();
    recalculateTransform = true;
  }
  if (this->UpdateLayout(painter) || recalculateTransform)
  {
    this->RecalculatePlotTransforms();
  }

  // Now that plot transforms, including whether to use log scaling and the
  // shift-scale factors, have been updated, we give the vtkPlot instances an
  // opportunity to update caches.
  for (size_t i = 0; i < this->ChartPrivate->plots.size(); ++i)
  {
    this->ChartPrivate->plots[i]->UpdateCache();
  }

  // Update the clipping if necessary
  this->ChartPrivate->Clip->SetClip(this->Point1[0], this->Point1[1],
                                    this->Point2[0]-this->Point1[0],
                                    this->Point2[1]-this->Point1[1]);

  // draw background
  if(this->BackgroundBrush)
  {
    painter->GetPen()->SetLineType(vtkPen::NO_PEN);
    painter->ApplyBrush(this->BackgroundBrush);
    painter->DrawRect(this->Point1[0], this->Point1[1],
                      this->Geometry[0], this->Geometry[1]);
  }

  // Use the scene to render most of the chart.
  this->PaintChildren(painter);

  // Draw the selection box if necessary
  if (this->DrawBox)
  {
    painter->GetBrush()->SetColor(255, 255, 255, 0);
    painter->GetPen()->SetColor(0, 0, 0, 255);
    painter->GetPen()->SetWidth(1.0);
    painter->GetPen()->SetLineType(vtkPen::SOLID_LINE);
    painter->DrawRect(this->MouseBox.GetX(), this->MouseBox.GetY(),
                      this->MouseBox.GetWidth(), this->MouseBox.GetHeight());
  }

  // Draw the selection polygon if necessary
  if (this->DrawSelectionPolygon)
  {
    painter->GetBrush()->SetColor(255, 0, 0, 0);
    painter->GetPen()->SetColor(0, 255, 0, 255);
    painter->GetPen()->SetWidth(2.0);
    painter->GetPen()->SetLineType(vtkPen::SOLID_LINE);

    const vtkContextPolygon &polygon = this->SelectionPolygon;

    // draw each line segment
    for(vtkIdType i = 0; i < polygon.GetNumberOfPoints() - 1; i++)
    {
      const vtkVector2f &a = polygon.GetPoint(i);
      const vtkVector2f &b = polygon.GetPoint(i+1);

      painter->DrawLine(a.GetX(), a.GetY(), b.GetX(), b.GetY());
    }

    // draw a line from the end to the start
    if(polygon.GetNumberOfPoints() >= 3)
    {
      const vtkVector2f &start = polygon.GetPoint(0);
      const vtkVector2f &end = polygon.GetPoint(polygon.GetNumberOfPoints() - 1);

      painter->DrawLine(start.GetX(), start.GetY(), end.GetX(), end.GetY());
    }
  }

  if (this->Title)
  {
    int offset = 0; // title margin.
    vtkAxis* topAxis = this->ChartPrivate->axes[vtkAxis::TOP];
    if (topAxis->GetVisible())
    {
      vtkRectf bounds = topAxis->GetBoundingRect(painter);
      offset += static_cast<int>(bounds.GetHeight());
    }
    vtkPoints2D *rect = vtkPoints2D::New();
    rect->InsertNextPoint(this->Point1[0], this->Point2[1] + offset);
    rect->InsertNextPoint(this->Point2[0]-this->Point1[0], 10);
    painter->ApplyTextProp(this->TitleProperties);
    painter->DrawStringRect(rect, this->Title);
    rect->Delete();
  }

  return true;
}

//-----------------------------------------------------------------------------
void vtkChartXY::CalculateBarPlots()
{
  // Calculate the width, spacing and offsets for the bar plot - they are grouped
  size_t n = this->ChartPrivate->plots.size();
  std::vector<vtkPlotBar *> bars;
  for (size_t i = 0; i < n; ++i)
  {
    vtkPlotBar* bar = vtkPlotBar::SafeDownCast(this->ChartPrivate->plots[i]);
    if (bar && bar->GetVisible())
    {
      bars.push_back(bar);
    }
  }
  if (bars.size())
  {
    // We have some bar plots - work out offsets etc.
    float barWidth = 0.1;
    vtkPlotBar* bar = bars[0];
    if (!bar->GetUseIndexForXSeries())
    {
      vtkTable *table = bar->GetData()->GetInput();
      if (table)
      {
        vtkDataArray* x = bar->GetData()->GetInputArrayToProcess(0, table);
        if (x && x->GetNumberOfTuples() > 1)
        {
          double x0 = x->GetTuple1(0);
          double x1 = x->GetTuple1(1);
          float width = static_cast<float>(fabs(x1 - x0) * this->BarWidthFraction);
          barWidth = width / bars.size();
        }
      }
    }
    else
    {
      barWidth = 1.0f / bars.size() * this->BarWidthFraction;
    }

    // Now set the offsets and widths on each bar
    // The offsetIndex deals with the fact that half the bars
    // must shift to the left of the point and half to the right
    int offsetIndex = static_cast<int>(bars.size() - 1);
    for (size_t i = 0; i < bars.size(); ++i)
    {
      bars[i]->SetWidth(barWidth);
      bars[i]->SetOffset(offsetIndex * (barWidth / 2));
      // Increment by two since we need to shift by half widths
      // but make room for entire bars. Increment backwards because
      // offsets are always subtracted and Positive offsets move
      // the bar leftwards.  Negative offsets will shift the bar
      // to the right.
      offsetIndex -= 2;
      //bars[i]->SetOffset(float(bars.size()-i-1)*(barWidth/2));
    }
  }
}

//-----------------------------------------------------------------------------
void vtkChartXY::RecalculatePlotTransforms()
{
  for (int i = 0; i < int(this->ChartPrivate->PlotCorners.size()); ++i)
  {
    if (this->ChartPrivate->PlotCorners[i]->GetNumberOfItems())
    {
      vtkAxis *xAxis = 0;
      vtkAxis *yAxis = 0;
      // Get the appropriate axes, and recalculate the transform.
      switch (i)
      {
        case 0:
        {
          xAxis = this->ChartPrivate->axes[vtkAxis::BOTTOM];
          yAxis = this->ChartPrivate->axes[vtkAxis::LEFT];
          break;
        }
        case 1:
          xAxis = this->ChartPrivate->axes[vtkAxis::BOTTOM];
          yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
          break;
        case 2:
          xAxis = this->ChartPrivate->axes[vtkAxis::TOP];
          yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
          break;
        case 3:
          xAxis = this->ChartPrivate->axes[vtkAxis::TOP];
          yAxis = this->ChartPrivate->axes[vtkAxis::LEFT];
          break;
        default:
          vtkWarningMacro(
            "Error: default case in recalculate plot transforms.");
      }
      this->CalculatePlotTransform(
        xAxis, yAxis, this->ChartPrivate->PlotCorners[i]->GetTransform());
      // Now we need to set the scale factor on the plots to ensure they rescale
      // their input data when necessary.
      vtkRectd shiftScale(xAxis->GetShift(), yAxis->GetShift(),
                          xAxis->GetScalingFactor(), yAxis->GetScalingFactor());
      for (unsigned int j = 0;
           j < this->ChartPrivate->PlotCorners[i]->GetNumberOfItems(); ++j)
      {
        vtkPlot *plot =
            vtkPlot::SafeDownCast(this->ChartPrivate->PlotCorners[i]->GetItem(j));
        if (plot)
        {
          plot->SetShiftScale(shiftScale);
        }
      }
    }
  }
  this->PlotTransformValid = true;
}

//-----------------------------------------------------------------------------
int vtkChartXY::GetPlotCorner(vtkPlot *plot)
{
  vtkAxis *x = plot->GetXAxis();
  vtkAxis *y = plot->GetYAxis();
  if (x == this->ChartPrivate->axes[vtkAxis::BOTTOM] &&
      y == this->ChartPrivate->axes[vtkAxis::LEFT])
  {
    return 0;
  }
  else if (x == this->ChartPrivate->axes[vtkAxis::BOTTOM] &&
           y == this->ChartPrivate->axes[vtkAxis::RIGHT])
  {
    return 1;
  }
  else if (x == this->ChartPrivate->axes[vtkAxis::TOP] &&
           y == this->ChartPrivate->axes[vtkAxis::RIGHT])
  {
    return 2;
  }
  else if (x == this->ChartPrivate->axes[vtkAxis::TOP] &&
           y == this->ChartPrivate->axes[vtkAxis::LEFT])
  {
    return 3;
  }
  else
  {
    // Should never happen.
    return 4;
  }
}

//-----------------------------------------------------------------------------
void vtkChartXY::SetPlotCorner(vtkPlot *plot, int corner)
{
  if (corner < 0 || corner > 3)
  {
    vtkWarningMacro("Invalid corner specified, should be between 0 and 3: "
                    << corner);
    return;
  }
  if (this->GetPlotCorner(plot) == corner)
  {
    return;
  }
  this->RemovePlotFromCorners(plot);
  // Grow the plot corners if necessary
  while (static_cast<int>(this->ChartPrivate->PlotCorners.size() - 1) < corner)
  {
    vtkNew<vtkContextTransform> transform;
    this->ChartPrivate->PlotCorners.push_back(transform.GetPointer());
    this->ChartPrivate->Clip->AddItem(transform.GetPointer()); // Clip maintains ownership.
  }
  this->ChartPrivate->PlotCorners[corner]->AddItem(plot);
  if (corner == 0)
  {
    plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::BOTTOM]);
    plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::LEFT]);
  }
  else if (corner == 1)
  {
    plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::BOTTOM]);
    plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::RIGHT]);
  }
  else if (corner == 2)
  {
    plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::TOP]);
    plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::RIGHT]);
  }
  else if (corner == 3)
  {
    plot->SetXAxis(this->ChartPrivate->axes[vtkAxis::TOP]);
    plot->SetYAxis(this->ChartPrivate->axes[vtkAxis::LEFT]);
  }
  this->PlotTransformValid = false;
}

//-----------------------------------------------------------------------------
void vtkChartXY::RecalculatePlotBounds()
{
  // Get the bounds of each plot, and each axis  - ordering as laid out below
  double y1[] = { 0.0, 0.0 }; // left -> 0
  double x1[] = { 0.0, 0.0 }; // bottom -> 1
  double y2[] = { 0.0, 0.0 }; // right -> 2
  double x2[] = { 0.0, 0.0 }; // top -> 3
  // Store whether the ranges have been initialized - follows same order
  bool initialized[] = { false, false, false, false };

  std::vector<vtkPlot*>::iterator it;
  double bounds[4] = { 0.0, 0.0, 0.0, 0.0 };
  for (it = this->ChartPrivate->plots.begin();
       it != this->ChartPrivate->plots.end(); ++it)
  {
    if ((*it)->GetVisible() == false)
    {
      continue;
    }
    (*it)->GetBounds(bounds);
    if (bounds[1] - bounds[0] < 0.0)
    {
      // skip uninitialized bounds.
      continue;
    }
    int corner = this->GetPlotCorner(*it);

    // Initialize the appropriate ranges, or push out the ranges
    if ((corner == 0 || corner == 3)) // left
    {
      if (!initialized[0])
      {
        y1[0] = bounds[2];
        y1[1] = bounds[3];
        initialized[0] = true;
      }
      else
      {
        if (y1[0] > bounds[2]) // min
        {
          y1[0] = bounds[2];
        }
        if (y1[1] < bounds[3]) // max
        {
          y1[1] = bounds[3];
        }
      }
    }
    if ((corner == 0 || corner == 1)) // bottom
    {
      if (!initialized[1])
      {
        x1[0] = bounds[0];
        x1[1] = bounds[1];
        initialized[1] = true;
      }
      else
      {
        if (x1[0] > bounds[0]) // min
        {
          x1[0] = bounds[0];
        }
        if (x1[1] < bounds[1]) // max
        {
          x1[1] = bounds[1];
        }
      }
    }
    if ((corner == 1 || corner == 2)) // right
    {
      if (!initialized[2])
      {
        y2[0] = bounds[2];
        y2[1] = bounds[3];
        initialized[2] = true;
      }
      else
      {
        if (y2[0] > bounds[2]) // min
        {
          y2[0] = bounds[2];
        }
        if (y2[1] < bounds[3]) // max
        {
          y2[1] = bounds[3];
        }
      }
    }
    if ((corner == 2 || corner == 3)) // top
    {
      if (!initialized[3])
      {
        x2[0] = bounds[0];
        x2[1] = bounds[1];
        initialized[3] = true;
      }
      else
      {
        if (x2[0] > bounds[0]) // min
        {
          x2[0] = bounds[0];
        }
        if (x2[1] < bounds[1]) // max
        {
          x2[1] = bounds[1];
        }
      }
    }
  }

  // Now set the newly calculated bounds on the axes
  for (int i = 0; i < 4; ++i)
  {
    vtkAxis *axis = this->ChartPrivate->axes[i];
    double *range = 0;
    switch (i)
    {
      case 0:
        range = y1;
        break;
      case 1:
        range = x1;
        break;
      case 2:
        range = y2;
        break;
      case 3:
        range = x2;
        break;
      default:
        return;
    }

    if (this->AdjustLowerBoundForLogPlot && axis->GetLogScale() &&
        range[0] <= 0.)
    {
      if (range[1] <= 0.)
      {
        // All of the data is negative, so we arbitrarily set the axis range to
        // be positive and show no data
        range[1] = 1.;
      }
      // The minimum value is set to either 4 decades below the max or to 1,
      // regardless of the true minimum value (which is less than 0)
      range[0] = (range[1] < 1.e4 ? range[1] / 1.e4 : 1.);
    }
    if (this->ForceAxesToBounds)
    {
      axis->SetMinimumLimit(range[0]);
      axis->SetMaximumLimit(range[1]);
    }
    if (axis->GetBehavior() == vtkAxis::AUTO && initialized[i])
    {
      axis->SetRange(range[0], range[1]);
      axis->AutoScale();
    }
  }

  this->Modified();
}

//-----------------------------------------------------------------------------
bool vtkChartXY::UpdateLayout(vtkContext2D* painter)
{
  // The main use of this method is currently to query the visible axes for
  // their bounds, and to update the chart in response to that.
  bool changed = false;

  vtkVector2i tileScale = this->Scene->GetLogicalTileScale();
  vtkVector2i hiddenAxisBorder = tileScale * this->HiddenAxisBorder;

  // Axes
  if (this->LayoutStrategy == vtkChart::FILL_SCENE ||
      this->LayoutStrategy == vtkChart::FILL_RECT)
  {
    for (int i = 0; i < 4; ++i)
    {
      int border = 0;
      vtkAxis* axis = this->ChartPrivate->axes[i];
      axis->Update();
      if (axis->GetVisible())
      {
        vtkRectf bounds = axis->GetBoundingRect(painter);
        if (i == vtkAxis::TOP || i == vtkAxis::BOTTOM)
        {// Horizontal axes
          border = int(bounds.GetHeight());
        }
        else
        {// Vertical axes
          border = int(bounds.GetWidth());
        }
      }
      border += this->GetLegendBorder(painter, i);
      if (i == vtkAxis::TOP && this->Title)
      {
        painter->ApplyTextProp(this->TitleProperties);
        float bounds[4];
        painter->ComputeStringBounds(this->Title, bounds);
        if (bounds[3] > 0)
        {
          border += (5 * tileScale.GetY()) /* title margin */
                    + bounds[3]; // add the title text height to the border.
        }
      }

      if (i == vtkAxis::TOP || i == vtkAxis::BOTTOM)
      {
        border = std::max(border, hiddenAxisBorder.GetY());
      }
      else
      {
        border = std::max(border, hiddenAxisBorder.GetX());
      }

      if (this->ChartPrivate->Borders[i] != border)
      {
        this->ChartPrivate->Borders[i] = border;
        changed = true;
      }
    }
  }

  if (this->LayoutChanged || changed)
  {
    if (this->DrawAxesAtOrigin)
    {
      this->SetBorders(hiddenAxisBorder.GetX(),
                       hiddenAxisBorder.GetY(),
                       this->ChartPrivate->Borders[2],
                       this->ChartPrivate->Borders[3]);
      // Get the screen coordinates for the origin, and move the axes there.
      vtkVector2f origin(0.0);
      vtkTransform2D* transform =
          this->ChartPrivate->PlotCorners[0]->GetTransform();
      transform->TransformPoints(origin.GetData(), origin.GetData(), 1);
      // Need to clamp the axes in the plot area.
      if (int(origin[0]) < this->Point1[0])
      {
        origin[0] = this->Point1[0];
      }
      if (int(origin[0]) > this->Point2[0])
      {
        origin[0] = this->Point2[0];
      }
      if (int(origin[1]) < this->Point1[1])
      {
        origin[1] = this->Point1[1];
      }
      if (int(origin[1]) > this->Point2[1])
      {
        origin[1] = this->Point2[1];
      }

      this->ChartPrivate->axes[vtkAxis::BOTTOM]
          ->SetPoint1(this->Point1[0], origin[1]);
      this->ChartPrivate->axes[vtkAxis::BOTTOM]
          ->SetPoint2(this->Point2[0], origin[1]);
      this->ChartPrivate->axes[vtkAxis::LEFT]
          ->SetPoint1(origin[0], this->Point1[1]);
      this->ChartPrivate->axes[vtkAxis::LEFT]
          ->SetPoint2(origin[0], this->Point2[1]);
    }
    else
    {
      if (this->LayoutStrategy == vtkChart::AXES_TO_RECT)
      {
        this->SetBorders(0, 0, 0, 0);
        this->ChartPrivate->axes[0]->GetBoundingRect(painter);
        this->ChartPrivate->axes[1]->GetBoundingRect(painter);
        this->ChartPrivate->axes[2]->GetBoundingRect(painter);
        this->ChartPrivate->axes[3]->GetBoundingRect(painter);
      }
      else
      {
        this->SetBorders(this->ChartPrivate->Borders[0],
                         this->ChartPrivate->Borders[1],
                         this->ChartPrivate->Borders[2],
                         this->ChartPrivate->Borders[3]);
      }
      // This is where we set the axes up too
      // Y axis (left)
      this->ChartPrivate->axes[0]->SetPoint1(this->Point1[0], this->Point1[1]);
      this->ChartPrivate->axes[0]->SetPoint2(this->Point1[0], this->Point2[1]);
      // X axis (bottom)
      this->ChartPrivate->axes[1]->SetPoint1(this->Point1[0], this->Point1[1]);
      this->ChartPrivate->axes[1]->SetPoint2(this->Point2[0], this->Point1[1]);
    }
    // Y axis (right)
    this->ChartPrivate->axes[2]->SetPoint1(this->Point2[0], this->Point1[1]);
    this->ChartPrivate->axes[2]->SetPoint2(this->Point2[0], this->Point2[1]);
    // X axis (top)
    this->ChartPrivate->axes[3]->SetPoint1(this->Point1[0], this->Point2[1]);
    this->ChartPrivate->axes[3]->SetPoint2(this->Point2[0], this->Point2[1]);

    for (int i = 0; i < 4; ++i)
    {
      this->ChartPrivate->axes[i]->Update();
    }
  }
  this->SetLegendPosition(this->Legend->GetBoundingRect(painter));

  return changed;
}

//-----------------------------------------------------------------------------
int vtkChartXY::GetLegendBorder(vtkContext2D* painter, int axisPosition)
{
  if (!this->Legend->GetVisible() || this->Legend->GetInline())
  {
    return 0;
  }

  vtkVector2i tileScale = this->Scene->GetLogicalTileScale();

  int padding = 10;
  vtkVector2i legendSize(0, 0);
  vtkVector2i legendAlignment(this->Legend->GetHorizontalAlignment(),
                              this->Legend->GetVerticalAlignment());
  this->Legend->Update();
  vtkRectf rect = this->Legend->GetBoundingRect(painter);
  legendSize.Set(static_cast<int>(rect.GetWidth()),
                 static_cast<int>(rect.GetHeight()));

  // Figure out the correct place and alignment based on the legend layout.
  if (axisPosition == vtkAxis::LEFT &&
      legendAlignment.GetX() == vtkChartLegend::LEFT)
  {
    return legendSize.GetX() + padding * tileScale.GetX();
  }
  else if (axisPosition == vtkAxis::RIGHT &&
           legendAlignment.GetX() == vtkChartLegend::RIGHT)
  {
    return legendSize.GetX() + padding * tileScale.GetX();
  }
  else if ((axisPosition == vtkAxis::TOP || axisPosition == vtkAxis::BOTTOM) &&
           (legendAlignment.GetX() == vtkChartLegend::LEFT ||
            legendAlignment.GetX() == vtkChartLegend::RIGHT))
  {
    return 0;
  }
  else if (axisPosition == vtkAxis::TOP &&
           legendAlignment.GetY() == vtkChartLegend::TOP)
  {
    return legendSize.GetY() + padding * tileScale.GetY();
  }
  else if (axisPosition == vtkAxis::BOTTOM &&
           legendAlignment.GetY() == vtkChartLegend::BOTTOM)
  {
    return legendSize.GetY() + padding * tileScale.GetY();
  }
  else
  {
    return 0;
  }
}

//-----------------------------------------------------------------------------
void vtkChartXY::SetLegendPosition(const vtkRectf& rect)
{
  // Put the legend in the top corner of the chart
  vtkVector2f pos(0, 0);
  int padding = 5;
  vtkVector2i legendAlignment(this->Legend->GetHorizontalAlignment(),
                              this->Legend->GetVerticalAlignment());

  if (legendAlignment[0] == vtkChartLegend::CUSTOM ||
      legendAlignment[1] == vtkChartLegend::CUSTOM)
  {
    return;
  }

  if (this->Legend->GetInline())
  {
    switch (this->Legend->GetHorizontalAlignment())
    {
      case vtkChartLegend::LEFT:
        pos.SetX(this->Point1[0]);
        break;
      case vtkChartLegend::CENTER:
        pos.SetX(((this->Point2[0] - this->Point1[0]) / 2.0)
                 - rect.GetWidth() / 2.0 + this->Point1[0]);
        break;
      case vtkChartLegend::RIGHT:
      default:
        pos.SetX(this->Point2[0] - rect.GetWidth());
    }
    switch (this->Legend->GetVerticalAlignment())
    {
      case vtkChartLegend::TOP:
        pos.SetY(this->Point2[1] - rect.GetHeight());
        break;
      case vtkChartLegend::CENTER:
        pos.SetY((this->Point2[1] - this->Point1[1]) / 2.0
                 - rect.GetHeight() / 2.0 + this->Point1[1]);
        break;
      case vtkChartLegend::BOTTOM:
      default:
        pos.SetY(this->Point1[1]);
    }
  }
  else
  {
    // Non-inline legends.
    if (legendAlignment.GetX() == vtkChartLegend::LEFT)
    {
      pos.SetX(this->Point1[0] - this->ChartPrivate->Borders[vtkAxis::LEFT]
               + padding);
    }
    else if (legendAlignment.GetX() == vtkChartLegend::RIGHT)
    {
      pos.SetX(this->Point2[0] + this->ChartPrivate->Borders[vtkAxis::RIGHT]
               - rect.GetWidth() - padding);
    }
    else if (legendAlignment.GetX() == vtkChartLegend::CENTER)
    {
      pos.SetX(((this->Point2[0] - this->Point1[0]) / 2.0)
               - (rect.GetWidth() / 2.0) + this->Point1[0]);
      // Check for the special case where the legend is on the top or bottom
      if (legendAlignment.GetY() == vtkChartLegend::TOP)
      {
        pos.SetY(this->Point2[1] + this->ChartPrivate->Borders[vtkAxis::TOP]
                 - rect.GetHeight() - padding);
      }
      else if (legendAlignment.GetY() == vtkChartLegend::BOTTOM)
      {
        pos.SetY(this->Point1[1] - this->ChartPrivate->Borders[vtkAxis::BOTTOM]
                 + padding);
      }
    }
    // Vertical alignment
    if (legendAlignment.GetX() != vtkChartLegend::CENTER)
    {
      if (legendAlignment.GetY() == vtkChartLegend::TOP)
      {
        pos.SetY(this->Point2[1] - rect.GetHeight());
      }
      else if (legendAlignment.GetY() == vtkChartLegend::BOTTOM)
      {
        pos.SetY(this->Point1[1]);
      }
    }
    if (legendAlignment.GetY() == vtkChartLegend::CENTER)
    {
      pos.SetY(((this->Point2[1] - this->Point1[1]) / 2.0)
               - (rect.GetHeight() / 2.0) + this->Point1[1]);
    }
  }

  this->Legend->SetPoint(pos);
}

//-----------------------------------------------------------------------------
vtkPlot * vtkChartXY::AddPlot(int type)
{
  // Use a variable to return the object created (or NULL), this is necessary
  // as the HP compiler is broken (thinks this function does not return) and
  // the MS compiler generates a warning about unreachable code if a redundant
  // return is added at the end.
  vtkPlot *plot = NULL;
  vtkColor3ub color = this->ChartPrivate->Colors->GetColorRepeating(
      static_cast<int>(this->ChartPrivate->plots.size()));
  switch (type)
  {
    case LINE:
    {
      vtkPlotLine *line = vtkPlotLine::New();
      line->GetPen()->SetColor(color.GetData());
      plot = line;
      break;
    }
    case POINTS:
    {
      vtkPlotPoints *points = vtkPlotPoints::New();
      points->GetPen()->SetColor(color.GetData());
      plot = points;
      break;
    }
    case BAR:
    {
      vtkPlotBar *bar = vtkPlotBar::New();
      bar->GetBrush()->SetColor(color.GetData());
      plot = bar;
      break;
    }
    case FUNCTIONALBAG:
    {
      vtkPlotFunctionalBag *bag = vtkPlotFunctionalBag::New();
      bag->GetPen()->SetColor(color.GetData());
      bag->GetBrush()->SetColor(color.GetData());
      plot = bag;
      break;
    }
    case STACKED:
    {
      vtkPlotStacked *stacked = vtkPlotStacked::New();
      stacked->SetParent(this);
      stacked->GetBrush()->SetColor(color.GetData());
      plot = stacked;
      break;
    }
    case BAG:
    {
      vtkPlotBag *bag = vtkPlotBag::New();
      bag->SetParent(this);
      bag->GetBrush()->SetColor(color.GetData());
      plot = bag;
      break;
    }
    case AREA:
    {
      vtkPlotArea* area = vtkPlotArea::New();
      area->SetParent(this);
      area->GetBrush()->SetColor(color.GetData());
      plot = area;
      break;
    }

    default:
      plot = NULL;
  }
  if (plot)
  {
    this->AddPlot(plot);
    plot->Delete();
  }
  return plot;
}

//-----------------------------------------------------------------------------
vtkIdType vtkChartXY::AddPlot(vtkPlot * plot)
{
  if (plot == NULL)
  {
    return -1;
  }
  plot->Register(this);
  this->ChartPrivate->plots.push_back(plot);
  vtkIdType plotIndex = this->ChartPrivate->plots.size() - 1;
  this->SetPlotCorner(plot, 0);
  // Ensure that the bounds are recalculated
  this->PlotTransformValid = false;
  // Mark the scene as dirty
  if (this->Scene)
  {
    this->Scene->SetDirty(true);
  }
  return plotIndex;
}

//-----------------------------------------------------------------------------
bool vtkChartXY::RemovePlot(vtkIdType index)
{
  if (index < static_cast<vtkIdType>(this->ChartPrivate->plots.size()))
  {
    this->RemovePlotFromCorners(this->ChartPrivate->plots[index]);
    this->ChartPrivate->plots[index]->Delete();
    this->ChartPrivate->plots.erase(this->ChartPrivate->plots.begin()+index);

    // Ensure that the bounds are recalculated
    this->PlotTransformValid = false;
    if (this->Scene)
    {
      // Mark the scene as dirty
      this->Scene->SetDirty(true);
    }
    return true;
  }
  else
  {
    return false;
  }
}

//-----------------------------------------------------------------------------
void vtkChartXY::ClearPlots()
{
  for (unsigned int i = 0; i < this->ChartPrivate->plots.size(); ++i)
  {
    this->ChartPrivate->plots[i]->Delete();
  }
  this->ChartPrivate->plots.clear();
  // Clear the corners too
  for (int i = 0; i < int(this->ChartPrivate->PlotCorners.size()); ++i)
  {
    this->ChartPrivate->PlotCorners[i]->ClearItems();
    if (i > 0)
    {
      this->ChartPrivate->Clip->RemoveItem(this->ChartPrivate->PlotCorners[i]);
    }
  }
  this->ChartPrivate->PlotCorners.resize(1);

  // Ensure that the bounds are recalculated
  this->PlotTransformValid = false;
  if (this->Scene)
  {
    // Mark the scene as dirty
    this->Scene->SetDirty(true);
  }
}

//-----------------------------------------------------------------------------
vtkPlot* vtkChartXY::GetPlot(vtkIdType index)
{
  if (static_cast<vtkIdType>(this->ChartPrivate->plots.size()) > index)
  {
    return this->ChartPrivate->plots[index];
  }
  else
  {
    return NULL;
  }
}

//-----------------------------------------------------------------------------
vtkIdType vtkChartXY::GetPlotIndex(vtkPlot* plot)
{
  int corner = this->GetPlotCorner(plot);
  return corner >= 0 && corner < 4 ?
    this->ChartPrivate->PlotCorners[corner]->GetItemIndex(plot) :
    static_cast<vtkIdType>(-1);
}

//-----------------------------------------------------------------------------
vtkIdType vtkChartXY::RaisePlot(vtkPlot* plot)
{
  vtkIdType plotIndex = this->GetPlotIndex(plot);
  int corner = this->GetPlotCorner(plot);
  if (corner < 0 || corner >=4)
  {
    return plotIndex;
  }
  return this->ChartPrivate->PlotCorners[corner]->Raise(plotIndex);
}

//-----------------------------------------------------------------------------
vtkIdType vtkChartXY::StackPlotAbove(vtkPlot* plot, vtkPlot* under)
{
  vtkIdType plotIndex = this->GetPlotIndex(plot);
  vtkIdType underIndex = this->GetPlotIndex(under);
  int corner = this->GetPlotCorner(plot);
  if (corner < 0 || corner >=4 ||
      underIndex != this->GetPlotCorner(under))
  {
    return plotIndex;
  }
  return this->ChartPrivate->PlotCorners[corner]->StackAbove(plotIndex,
                                                             underIndex);
}

//-----------------------------------------------------------------------------
vtkIdType vtkChartXY::LowerPlot(vtkPlot* plot)
{
  vtkIdType plotIndex = this->GetPlotIndex(plot);
  int corner = this->GetPlotCorner(plot);
  if (corner < 0 || corner >=4)
  {
    return plotIndex;
  }
  return this->ChartPrivate->PlotCorners[corner]->Lower(plotIndex);
}

//-----------------------------------------------------------------------------
vtkIdType vtkChartXY::StackPlotUnder(vtkPlot* plot, vtkPlot* above)
{
  vtkIdType plotIndex = this->GetPlotIndex(plot);
  vtkIdType aboveIndex = this->GetPlotIndex(above);
  int corner = this->GetPlotCorner(plot);
  if (corner < 0 || corner >=4 ||
      corner != this->GetPlotCorner(above))
  {
    return plotIndex;
  }
  return this->ChartPrivate->PlotCorners[corner]->StackUnder(plotIndex,
                                                             aboveIndex);
}

//-----------------------------------------------------------------------------
void vtkChartXY::SetShowLegend(bool visible)
{
  this->vtkChart::SetShowLegend(visible);
  this->Legend->SetVisible(visible);
}

//-----------------------------------------------------------------------------
vtkChartLegend* vtkChartXY::GetLegend()
{
  return this->Legend;
}

//-----------------------------------------------------------------------------
void vtkChartXY::SetTooltip(vtkTooltipItem *tooltip)
{
  if(tooltip == this->Tooltip)
  {
    // nothing to change
    return;
  }

  if(this->Tooltip)
  {
    // remove current tooltip from scene
    this->RemoveItem(this->Tooltip);
  }

  this->Tooltip = tooltip;

  if(this->Tooltip)
  {
    // add new tooltip to scene
    this->AddItem(this->Tooltip);
  }
}

//-----------------------------------------------------------------------------
vtkTooltipItem* vtkChartXY::GetTooltip()
{
  return this->Tooltip;
}

//-----------------------------------------------------------------------------
vtkIdType vtkChartXY::GetNumberOfPlots()
{
  return this->ChartPrivate->plots.size();
}

//-----------------------------------------------------------------------------
vtkAxis* vtkChartXY::GetAxis(int axisIndex)
{
  if (axisIndex < 4)
  {
    return this->ChartPrivate->axes[axisIndex];
  }
  else
  {
    return NULL;
  }
}

//-----------------------------------------------------------------------------
vtkIdType vtkChartXY::GetNumberOfAxes()
{
  return 4;
}


//-----------------------------------------------------------------------------
void vtkChartXY::RecalculateBounds()
{
  // Ensure that the bounds are recalculated
  this->PlotTransformValid = false;
  if (this->Scene)
  {
    // Mark the scene as dirty
    this->Scene->SetDirty(true);
  }
}

//-----------------------------------------------------------------------------
void vtkChartXY::SetSelectionMethod(int method)
{
  if (method == this->SelectionMethod)
  {
    return;
  }
  if (method == vtkChart::SELECTION_PLOTS)
  {
    // Clear the selection on the plots which may be shared between all of them.
    // Now iterate through the plots to update selection data
    std::vector<vtkPlot*>::iterator it =
        this->ChartPrivate->plots.begin();
    for ( ; it != this->ChartPrivate->plots.end(); ++it)
    {
      (*it)->SetSelection(NULL);
    }
  }
  Superclass::SetSelectionMethod(method);
}

//-----------------------------------------------------------------------------
bool vtkChartXY::Hit(const vtkContextMouseEvent &mouse)
{
  if (!this->Interactive)
  {
    return false;
  }
  vtkVector2i pos(mouse.GetScreenPos());
  if (pos[0] > this->Point1[0] &&
      pos[0] < this->Point2[0] &&
      pos[1] > this->Point1[1] &&
      pos[1] < this->Point2[1])
  {
    return true;
  }
  else
  {
    return false;
  }
}

//-----------------------------------------------------------------------------
bool vtkChartXY::MouseEnterEvent(const vtkContextMouseEvent &)
{
  // Find the nearest point on the curves and snap to it
  this->DrawNearestPoint = true;
  return true;
}

//-----------------------------------------------------------------------------
bool vtkChartXY::MouseMoveEvent(const vtkContextMouseEvent &mouse)
{
  // Iterate through each corner, and check for a nearby point
  for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
  {
    if (this->ChartPrivate->PlotCorners[i]->MouseMoveEvent(mouse))
    {
      return true;
    }
  }

  if (mouse.GetButton() == this->Actions.Pan())
  {
    // Figure out how much the mouse has moved by in plot coordinates - pan
    vtkVector2d screenPos(mouse.GetScreenPos().Cast<double>().GetData());
    vtkVector2d lastScreenPos(mouse.GetLastScreenPos().Cast<double>().GetData());
    vtkVector2d pos(0.0, 0.0);
    vtkVector2d last(0.0, 0.0);

    // Go from screen to scene coordinates to work out the delta
    vtkAxis* xAxis = this->ChartPrivate->axes[vtkAxis::BOTTOM];
    vtkAxis* yAxis = this->ChartPrivate->axes[vtkAxis::LEFT];
    vtkTransform2D *transform =
        this->ChartPrivate->PlotCorners[0]->GetTransform();
    transform->InverseTransformPoints(screenPos.GetData(), pos.GetData(), 1);
    transform->InverseTransformPoints(lastScreenPos.GetData(), last.GetData(), 1);
    vtkVector2d delta = last - pos;
    delta[0] /= xAxis->GetScalingFactor();
    delta[1] /= yAxis->GetScalingFactor();

    // Now move the axes and recalculate the transform
    delta[0] = delta[0] > 0 ?
      std::min(delta[0], xAxis->GetMaximumLimit() - xAxis->GetMaximum()) :
      std::max(delta[0], xAxis->GetMinimumLimit() - xAxis->GetMinimum());
    delta[1] = delta[1] > 0 ?
      std::min(delta[1], yAxis->GetMaximumLimit() - yAxis->GetMaximum()) :
      std::max(delta[1], yAxis->GetMinimumLimit() - yAxis->GetMinimum());
    xAxis->SetMinimum(xAxis->GetMinimum() + delta[0]);
    xAxis->SetMaximum(xAxis->GetMaximum() + delta[0]);
    yAxis->SetMinimum(yAxis->GetMinimum() + delta[1]);
    yAxis->SetMaximum(yAxis->GetMaximum() + delta[1]);

    if (this->ChartPrivate->PlotCorners.size() == 2)
    {
      // Figure out the right axis position, if greater than 2 both will be done
      // in the else if block below.
      screenPos = vtkVector2d(mouse.GetScreenPos().Cast<double>().GetData());
      lastScreenPos =
          vtkVector2d(mouse.GetLastScreenPos().Cast<double>().GetData());
      pos = vtkVector2d(0.0, 0.0);
      last = vtkVector2d(0.0, 0.0);
      yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
      transform = this->ChartPrivate->PlotCorners[1]->GetTransform();
      transform->InverseTransformPoints(screenPos.GetData(), pos.GetData(), 1);
      transform->InverseTransformPoints(lastScreenPos.GetData(), last.GetData(), 1);
      delta = last - pos;
      delta[0] /= xAxis->GetScalingFactor();
      delta[1] /= yAxis->GetScalingFactor();

      // Now move the axes and recalculate the transform
      delta[1] = delta[1] > 0 ?
        std::min(delta[1], yAxis->GetMaximumLimit() - yAxis->GetMaximum()) :
        std::max(delta[1], yAxis->GetMinimumLimit() - yAxis->GetMinimum());
      yAxis->SetMinimum(yAxis->GetMinimum() + delta[1]);
      yAxis->SetMaximum(yAxis->GetMaximum() + delta[1]);
    }
    else if (this->ChartPrivate->PlotCorners.size() > 2)
    {
      // Figure out the right and top axis positions.
      // Go from screen to scene coordinates to work out the delta
      screenPos = vtkVector2d(mouse.GetScreenPos().Cast<double>().GetData());
      lastScreenPos =
          vtkVector2d(mouse.GetLastScreenPos().Cast<double>().GetData());
      pos = vtkVector2d(0.0, 0.0);
      last = vtkVector2d(0.0, 0.0);
      xAxis = this->ChartPrivate->axes[vtkAxis::TOP];
      yAxis = this->ChartPrivate->axes[vtkAxis::RIGHT];
      transform = this->ChartPrivate->PlotCorners[2]->GetTransform();
      transform->InverseTransformPoints(screenPos.GetData(), pos.GetData(), 1);
      transform->InverseTransformPoints(lastScreenPos.GetData(), last.GetData(), 1);
      delta = last - pos;
      delta[0] /= xAxis->GetScalingFactor();
      delta[1] /= yAxis->GetScalingFactor();

      // Now move the axes and recalculate the transform
      delta[0] = delta[0] > 0 ?
        std::min(delta[0], xAxis->GetMaximumLimit() - xAxis->GetMaximum()) :
        std::max(delta[0], xAxis->GetMinimumLimit() - xAxis->GetMinimum());
      delta[1] = delta[1] > 0 ?
        std::min(delta[1], yAxis->GetMaximumLimit() - yAxis->GetMaximum()) :
        std::max(delta[1], yAxis->GetMinimumLimit() - yAxis->GetMinimum());
      xAxis->SetMinimum(xAxis->GetMinimum() + delta[0]);
      xAxis->SetMaximum(xAxis->GetMaximum() + delta[0]);
      yAxis->SetMinimum(yAxis->GetMinimum() + delta[1]);
      yAxis->SetMaximum(yAxis->GetMaximum() + delta[1]);
    }

    this->RecalculatePlotTransforms();
    // Mark the scene as dirty
    this->Scene->SetDirty(true);

    this->InvokeEvent(vtkCommand::InteractionEvent);
  }
  else if (mouse.GetButton() == this->Actions.Zoom() ||
           mouse.GetButton() == this->Actions.Select())
  {
    this->MouseBox.SetWidth(mouse.GetPos().GetX() - this->MouseBox.GetX());
    this->MouseBox.SetHeight(mouse.GetPos().GetY() - this->MouseBox.GetY());
    // Mark the scene as dirty
    this->Scene->SetDirty(true);
  }
  else if (mouse.GetButton() == this->Actions.ZoomAxis())
  {
    vtkVector2d screenPos(mouse.GetScreenPos().Cast<double>().GetData());
    vtkVector2d lastScreenPos(mouse.GetLastScreenPos().Cast<double>().GetData());

    vtkAxis *axes[] = {
      this->ChartPrivate->axes[vtkAxis::BOTTOM],
      this->ChartPrivate->axes[vtkAxis::LEFT],
      this->ChartPrivate->axes[vtkAxis::TOP],
      this->ChartPrivate->axes[vtkAxis::RIGHT]
    };

    for(int i = 0; i < 4; i++)
    {
      vtkAxis *axis = axes[i];
      if(!axis)
      {
        continue;
      }

      // bottom, top -> 0, right, left -> 1
      int side = i % 2;

      // get mouse delta in the given direction for the axis
      double delta = lastScreenPos[side] - screenPos[side];
      if(std::abs(delta) == 0)
      {
        continue;
      }

      // scale and invert delta
      delta /= -100.0;

      // zoom axis range
      double min = axis->GetMinimum();
      double max = axis->GetMaximum();
      double frac = (max - min) * 0.1;
      if (frac > 0.0)
      {
        min += delta*frac;
        max -= delta*frac;
      }
      else
      {
        min -= delta*frac;
        max += delta*frac;
      }
      axis->SetMinimum(min);
      axis->SetMaximum(max);
      axis->RecalculateTickSpacing();
    }

    this->RecalculatePlotTransforms();

    // Mark the scene as dirty
    this->Scene->SetDirty(true);
  }
  else if (mouse.GetButton() == this->Actions.SelectPolygon())
  {
    if(this->SelectionPolygon.GetNumberOfPoints() > 0)
    {
      vtkVector2f lastPoint =
        this->SelectionPolygon.GetPoint(
          this->SelectionPolygon.GetNumberOfPoints() - 1);

      if((lastPoint - mouse.GetPos()).SquaredNorm() > 100)
      {
        this->SelectionPolygon.AddPoint(mouse.GetPos());
      }

      // Mark the scene as dirty
      this->Scene->SetDirty(true);
    }
  }
  else if (mouse.GetButton() == vtkContextMouseEvent::NO_BUTTON)
  {
    this->Scene->SetDirty(true);

    if(this->Tooltip)
    {
      this->Tooltip->SetVisible(this->LocatePointInPlots(mouse));
    }
  }

  return true;
}

//-----------------------------------------------------------------------------
int vtkChartXY::LocatePointInPlot(const vtkVector2f &position,
                                  const vtkVector2f &tolerance,
                                  vtkVector2f &plotPos,
                                  vtkPlot *plot,
                                  vtkIdType &segmentIndex)
{
  if (plot && plot->GetVisible())
  {
    vtkPlotBar* plotBar = vtkPlotBar::SafeDownCast(plot);
    if (plotBar)
    {
      // If the plot is a vtkPlotBar, get the segment index too
      return plotBar->GetNearestPoint(position, tolerance,
                                      &plotPos, &segmentIndex);
    }
    else
    {
      return plot->GetNearestPoint(position, tolerance, &plotPos);
    }
  }
  return -1;
}

//-----------------------------------------------------------------------------
bool vtkChartXY::LocatePointInPlots(const vtkContextMouseEvent &mouse,
                                    int invokeEvent)
{
  size_t n = this->ChartPrivate->plots.size();
  vtkVector2i pos(mouse.GetScreenPos());
  if (pos[0] > this->Point1[0] &&
      pos[0] < this->Point2[0] &&
      pos[1] > this->Point1[1] &&
      pos[1] < this->Point2[1] && n)
  {
    // Iterate through each corner, and check for a nearby point
    for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
    {
      int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]
                                   ->GetNumberOfItems());
      if (items)
      {
        vtkVector2f plotPos, position;
        vtkTransform2D* transform =
            this->ChartPrivate->PlotCorners[i]->GetTransform();
        transform->InverseTransformPoints(mouse.GetPos().GetData(),
                                          position.GetData(), 1);
        // Use a tolerance of +/- 5 pixels
        vtkVector2f tolerance(
          std::fabs(5*(1.0/transform->GetMatrix()->GetElement(0, 0))),
          std::fabs(5*(1.0/transform->GetMatrix()->GetElement(1, 1))));
        // Iterate through the visible plots and return on the first hit
        vtkIdType segmentIndex = -1;

        for (int j = items-1; j >= 0; --j)
        {
          vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->
                                                PlotCorners[i]->GetItem(j));
          int seriesIndex = LocatePointInPlot(position, tolerance, plotPos,
                                              plot, segmentIndex);
          if (seriesIndex >= 0)
          {
            // We found a point, set up the tooltip and return
            vtkRectd ss(plot->GetShiftScale());
            vtkVector2d plotPosd(plotPos[0] / ss[2] - ss[0],
                                 plotPos[1] / ss[3] - ss[1]);
            this->SetTooltipInfo(mouse, plotPosd, seriesIndex, plot,
                                 segmentIndex);
            if (invokeEvent >= 0)
            {
              vtkChartPlotData plotIndex;
              plotIndex.SeriesName = plot->GetLabel();
              plotIndex.Position = plotPos;
              plotIndex.ScreenPosition = mouse.GetScreenPos();
              plotIndex.Index = seriesIndex;
              // Invoke an event, with the client data supplied
              this->InvokeEvent(invokeEvent, static_cast<void*>(&plotIndex));

              if (invokeEvent == vtkCommand::SelectionChangedEvent)
              {
                // Construct a new selection with the selected point in it.
                vtkNew<vtkIdTypeArray> selectionIds;
                selectionIds->InsertNextValue(seriesIndex);
                plot->SetSelection(selectionIds.GetPointer());

                if (this->AnnotationLink)
                {
                  vtkChartSelectionHelper::MakeSelection(this->AnnotationLink,
                                                         selectionIds.GetPointer(),
                                                         plot);
                }
              }
            }
            return true;
          }
        }
      }
    }
  }
  return false;
}

//-----------------------------------------------------------------------------
void vtkChartXY::SetTooltipInfo(const vtkContextMouseEvent& mouse,
                                const vtkVector2d &plotPos,
                                vtkIdType seriesIndex, vtkPlot* plot,
                                vtkIdType segmentIndex)
{
  if (!this->Tooltip)
  {
    return;
  }

  // Have the plot generate its tooltip label
  vtkStdString tooltipLabel = plot->GetTooltipLabel(plotPos, seriesIndex,
                                                    segmentIndex);

  // Set the tooltip
  this->Tooltip->SetText(tooltipLabel);
  this->Tooltip->SetPosition(mouse.GetScreenPos()[0] + 2,
                             mouse.GetScreenPos()[1] + 2);
}

//-----------------------------------------------------------------------------
bool vtkChartXY::MouseLeaveEvent(const vtkContextMouseEvent &)
{
  this->DrawNearestPoint = false;

  if(this->Tooltip)
  {
    this->Tooltip->SetVisible(false);
  }

  return true;
}

//-----------------------------------------------------------------------------
bool vtkChartXY::MouseButtonPressEvent(const vtkContextMouseEvent &mouse)
{
  if(this->Tooltip)
  {
    this->Tooltip->SetVisible(false);
  }

  // Iterate through each corner, and check for a nearby point
  for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
  {
    if (this->ChartPrivate->PlotCorners[i]->MouseButtonPressEvent(mouse))
    {
      return true;
    }
  }
  if (mouse.GetButton() == this->Actions.Pan())
  {
    // The mouse panning action.
    this->MouseBox.Set(mouse.GetPos().GetX(), mouse.GetPos().GetY(), 0.0, 0.0);
    this->DrawBox = false;
    return true;
  }
  else if (mouse.GetButton() == this->Actions.Zoom() ||
           mouse.GetButton() == this->Actions.Select())
  {
    // Selection, for now at least...
    this->MouseBox.Set(mouse.GetPos().GetX(), mouse.GetPos().GetY(), 0.0, 0.0);
    this->DrawBox = true;
    return true;
  }
  else if (mouse.GetButton() == this->Actions.ZoomAxis())
  {
    this->MouseBox.Set(mouse.GetPos().GetX(), mouse.GetPos().GetY(), 0.0, 0.0);
    this->DrawBox = false;
    return true;
  }
  else if (mouse.GetButton() == this->Actions.SelectPolygon())
  {
    this->SelectionPolygon.Clear();
    this->SelectionPolygon.AddPoint(mouse.GetPos());
    this->DrawSelectionPolygon = true;
    return true;
  }
  else if (mouse.GetButton() == this->ActionsClick.Select() ||
           mouse.GetButton() == this->ActionsClick.Notify())
  {
    return true;
  }
  else
  {
    return false;
  }
}

//-----------------------------------------------------------------------------
bool vtkChartXY::MouseButtonReleaseEvent(const vtkContextMouseEvent &mouse)
{
  // Iterate through each corner, and check for a nearby point
  for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
  {
    if (this->ChartPrivate->PlotCorners[i]->MouseButtonReleaseEvent(mouse))
    {
      return true;
    }
  }
  if (mouse.GetButton() > vtkContextMouseEvent::NO_BUTTON &&
      mouse.GetButton() <= vtkContextMouseEvent::RIGHT_BUTTON)
  {
    this->MouseBox.SetWidth(mouse.GetPos().GetX() - this->MouseBox.GetX());
    this->MouseBox.SetHeight(mouse.GetPos().GetY() - this->MouseBox.GetY());
    if ((fabs(this->MouseBox.GetWidth()) < 0.5 && fabs(this->MouseBox.GetHeight()) < 0.5)
        && (mouse.GetButton() == this->Actions.Select() ||
            mouse.GetButton() == this->Actions.Pan()))
    {
      // Invalid box size - treat as a single clicke event
      this->MouseBox.SetWidth(0.0);
      this->MouseBox.SetHeight(0.0);
      this->DrawBox = false;
      if (mouse.GetButton() == this->ActionsClick.Notify())
      {
        this->LocatePointInPlots(mouse, vtkCommand::InteractionEvent);
        return true;
      }
      else if (mouse.GetButton() == this->ActionsClick.Select())
      {
        this->LocatePointInPlots(mouse, vtkCommand::SelectionChangedEvent);
        return true;
      }
      else
      {
        return false;
      }
    }
  }
  if (mouse.GetButton() == this->Actions.Select() ||
      mouse.GetButton() == this->Actions.SelectPolygon())
  {
    // Modifiers or selection modes can affect how selection is performed.
    int selectionMode =
        vtkChartSelectionHelper::GetMouseSelectionMode(mouse,
                                                       this->SelectionMode);
    bool polygonMode(mouse.GetButton() == this->Actions.SelectPolygon());
    this->Scene->SetDirty(true);

    // Update the polygon or box with the last mouse position.
    if (polygonMode)
    {
      this->SelectionPolygon.AddPoint(mouse.GetPos());
      this->DrawSelectionPolygon = false;
    }
    else
    {
      this->MouseBox.SetWidth(mouse.GetPos().GetX() - this->MouseBox.GetX());
      this->MouseBox.SetHeight(mouse.GetPos().GetY() - this->MouseBox.GetY());
      this->DrawBox = false;
    }

    // Check whether we have a valid selection area, exit early if not.
    if (polygonMode && this->SelectionPolygon.GetNumberOfPoints() < 3)
    {
      // There is no polygon to select points in.
      this->SelectionPolygon.Clear();
      return true;
    }
    else if (fabs(this->MouseBox.GetWidth()) < 0.5 ||
             fabs(this->MouseBox.GetHeight()) < 0.5)
    {
      // The box is too small, and no useful selection can be made.
      this->MouseBox.SetWidth(0.0);
      this->MouseBox.SetHeight(0.0);
      return true;
    }

    // Iterate through the plots and build a selection. Two main behaviors are
    // supported - row-based selections add all rows from all plots and set that
    // as the selection, plot-based selections create a selection node for each
    // plot.
    vtkNew<vtkIdTypeArray> oldSelection;
    vtkNew<vtkIdTypeArray> accumulateSelection;
    if (this->SelectionMethod == vtkChart::SELECTION_ROWS)
    {
      // There is only one global selection, we build up a union of all rows
      // selected in all charts and set that on all plots.
      for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
      {
        int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]
                                     ->GetNumberOfItems());
        if (items)
        {
          vtkTransform2D *transform =
              this->ChartPrivate->PlotCorners[i]->GetTransform();
          vtkVector2f min;
          vtkVector2f max;
          vtkContextPolygon polygon;
          this->TransformBoxOrPolygon(polygonMode, transform, mouse.GetPos(),
                                      min, max, polygon);

          // Iterate through the plots and create the selection.
          for (int j = 0; j < items; ++j)
          {
            vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->
                                                  PlotCorners[i]->GetItem(j));
            if (plot && plot->GetVisible() && plot->GetSelectable())
            {
              // There is only really one old selection in this mode.
              if (i == 0 && j == 0)
              {
                oldSelection->DeepCopy(plot->GetSelection());
              }
              // Populate the selection using the appropriate shape.
              if (polygonMode)
              {
                plot->SelectPointsInPolygon(polygon);
              }
              else
              {
                plot->SelectPoints(min, max);
              }

              // Accumulate the selection in each plot.
              vtkChartSelectionHelper::BuildSelection(0,
                                                      vtkContextScene::SELECTION_ADDITION,
                                                      accumulateSelection.GetPointer(),
                                                      plot->GetSelection(),
                                                      0);
            }
          }
        }
      }
      // Now add the accumulated selection to the old selection.
      vtkChartSelectionHelper::BuildSelection(this->AnnotationLink,
                                              selectionMode,
                                              accumulateSelection.GetPointer(),
                                              oldSelection.GetPointer(),
                                              0);
    }
    else if (this->SelectionMethod == vtkChart::SELECTION_PLOTS)
    {
      // We are performing plot based selections.
      for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
      {
        int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]
                                     ->GetNumberOfItems());
        if (items)
        {
          vtkTransform2D *transform =
              this->ChartPrivate->PlotCorners[i]->GetTransform();
          vtkVector2f min;
          vtkVector2f max;
          vtkContextPolygon polygon;
          this->TransformBoxOrPolygon(polygonMode, transform, mouse.GetPos(),
                                      min, max, polygon);

          for (int j = 0; j < items; ++j)
          {
            vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->
                                                  PlotCorners[i]->GetItem(j));
            if (plot && plot->GetVisible() && plot->GetSelectable())
            {
              oldSelection->DeepCopy(plot->GetSelection());
              // Populate the selection using the appropriate shape.
              if (polygonMode)
              {
                plot->SelectPointsInPolygon(polygon);
              }
              else
              {
                plot->SelectPoints(min, max);
              }

              // Combine the selection in this plot with any previous selection.
              vtkChartSelectionHelper::BuildSelection(this->AnnotationLink,
                                                      selectionMode,
                                                      plot->GetSelection(),
                                                      oldSelection.GetPointer(),
                                                      plot);
            }
          }
        }
      }
    }
    else if (this->SelectionMethod == vtkChart::SELECTION_COLUMNS)
    {
      if (this->AnnotationLink)
      {
        this->AnnotationLink->Update();
        vtkSelection *selection =
          vtkSelection::SafeDownCast(this->AnnotationLink->GetOutputDataObject(2));
        vtkSelectionNode *node = selection->GetNumberOfNodes() > 0?
            selection->GetNode(0) : NULL;
        if (node)
        {
          oldSelection->DeepCopy(vtkArrayDownCast<vtkIdTypeArray>(node->GetSelectionList()));
        }
      }
      vtkNew<vtkIdTypeArray> plotSelection;
      // We are performing plot based selections.
      for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
      {
        int items = static_cast<int>(this->ChartPrivate->PlotCorners[i]
                                     ->GetNumberOfItems());
        if (items)
        {
          vtkTransform2D *transform =
              this->ChartPrivate->PlotCorners[i]->GetTransform();
          vtkVector2f min;
          vtkVector2f max;
          vtkContextPolygon polygon;
          this->TransformBoxOrPolygon(polygonMode, transform, mouse.GetPos(),
                                      min, max, polygon);

          for (int j = 0; j < items; ++j)
          {
            vtkPlot* plot = vtkPlot::SafeDownCast(this->ChartPrivate->
                                                  PlotCorners[i]->GetItem(j));
            if (plot && plot->GetVisible() && plot->GetSelectable())
            {
              bool selected = false;
              // Populate the selection using the appropriate shape.
              if (polygonMode)
              {
                selected = plot->SelectPointsInPolygon(polygon);
              }
              else
              {
                selected = plot->SelectPoints(min, max);
              }
              vtkNew<vtkIdTypeArray> plotsSelection;
              if (selected)
              {
                int idx = 1; // y
                vtkAbstractArray* column = plot->GetData()->GetInputAbstractArrayToProcess(
                  idx, plot->GetInput());
                int columnID = -1;
                plot->GetInput()->GetRowData()->GetAbstractArray(column->GetName(), columnID);
                if (plotSelection->GetNumberOfTuples() != column->GetNumberOfTuples())
                {
                  plotSelection->SetNumberOfTuples(0);
                  for (vtkIdType k = 0; k < column->GetNumberOfTuples(); ++k)
                  {
                    plotSelection->InsertNextValue(k);
                  }
                }
                plot->SetSelection(plotSelection.GetPointer());
                accumulateSelection->InsertNextValue(columnID);
              }
            }
          }
        }
      }
      vtkIdType* ptrSelection =
        reinterpret_cast<vtkIdType *>(accumulateSelection->GetVoidPointer(0));
      std::sort(ptrSelection, ptrSelection + accumulateSelection->GetNumberOfTuples());
      // Now add the accumulated selection to the old selection
      vtkChartSelectionHelper::BuildSelection(this->AnnotationLink,
                                              selectionMode,
                                              accumulateSelection.GetPointer(),
                                              oldSelection.GetPointer(),
                                              0);
    }
    this->InvokeEvent(vtkCommand::SelectionChangedEvent);
    this->MouseBox.SetWidth(0.0);
    this->MouseBox.SetHeight(0.0);
    this->SelectionPolygon.Clear();
    return true;
  }
  else if (mouse.GetButton() == this->Actions.Zoom())
  {
    // Check whether a valid zoom box was drawn
    if (fabs(this->MouseBox.GetWidth()) < 0.5 || fabs(this->MouseBox.GetHeight()) < 0.5)
    {
      // Invalid box size - do nothing
      this->MouseBox.SetWidth(0.0);
      this->MouseBox.SetHeight(0.0);
      this->DrawBox = false;
      return true;
    }

    // Zoom into the chart by the specified amount, and recalculate the bounds
    vtkVector2f point2(mouse.GetPos());

    this->ZoomInAxes(this->ChartPrivate->axes[vtkAxis::BOTTOM],
                     this->ChartPrivate->axes[vtkAxis::LEFT],
                     this->MouseBox.GetData(), point2.GetData());
    this->ZoomInAxes(this->ChartPrivate->axes[vtkAxis::TOP],
                     this->ChartPrivate->axes[vtkAxis::RIGHT],
                     this->MouseBox.GetData(), point2.GetData());

    this->RecalculatePlotTransforms();
    this->MouseBox.SetWidth(0.0);
    this->MouseBox.SetHeight(0.0);
    this->DrawBox = false;
    // Mark the scene as dirty
    this->Scene->SetDirty(true);
    this->InvokeEvent(vtkCommand::InteractionEvent);
    return true;
  }
  else if (mouse.GetButton() == this->Actions.ZoomAxis())
  {
    return true;
  }
  return false;
}

void vtkChartXY::ZoomInAxes(vtkAxis *x, vtkAxis *y, float *originf, float *maxf)
{
  vtkNew<vtkTransform2D> transform;
  this->CalculateUnscaledPlotTransform(x, y, transform.GetPointer());
  vtkVector2d origin(originf[0], originf[1]);
  vtkVector2d max(maxf[0], maxf[1]);
  vtkVector2d torigin;
  transform->InverseTransformPoints(origin.GetData(), torigin.GetData(), 1);
  vtkVector2d tmax;
  transform->InverseTransformPoints(max.GetData(), tmax.GetData(), 1);

  // Ensure we preserve the directionality of the axes
  if (x->GetMaximum() > x->GetMinimum())
  {
    x->SetMaximum(torigin[0] > tmax[0] ? torigin[0] : tmax[0]);
    x->SetMinimum(torigin[0] < tmax[0] ? torigin[0] : tmax[0]);
  }
  else
  {
    x->SetMaximum(torigin[0] < tmax[0] ? torigin[0] : tmax[0]);
    x->SetMinimum(torigin[0] > tmax[0] ? torigin[0] : tmax[0]);
  }
  if (y->GetMaximum() > y->GetMinimum())
  {
    y->SetMaximum(torigin[1] > tmax[1] ? torigin[1] : tmax[1]);
    y->SetMinimum(torigin[1] < tmax[1] ? torigin[1] : tmax[1]);
  }
  else
  {
    y->SetMaximum(torigin[1] < tmax[1] ? torigin[1] : tmax[1]);
    y->SetMinimum(torigin[1] > tmax[1] ? torigin[1] : tmax[1]);
  }
  x->RecalculateTickSpacing();
  y->RecalculateTickSpacing();
}

//-----------------------------------------------------------------------------
bool vtkChartXY::MouseWheelEvent(const vtkContextMouseEvent &, int delta)
{
  if(this->Tooltip)
  {
    this->Tooltip->SetVisible(false);
  }
  if (!this->ZoomWithMouseWheel)
  {
    return false;
  }

  // Get the bounds of each plot.
  for (int i = 0; i < 4; ++i)
  {
    vtkAxis *axis = this->ChartPrivate->axes[i];
    double min = axis->GetMinimum();
    double max = axis->GetMaximum();
    double frac = (max - min) * 0.1;
    if (frac > 0.0)
    {
      min += delta*frac;
      max -= delta*frac;
    }
    else
    {
      min -= delta*frac;
      max += delta*frac;
    }
    axis->SetMinimum(min);
    axis->SetMaximum(max);
    axis->RecalculateTickSpacing();
  }

  this->RecalculatePlotTransforms();

  // Mark the scene as dirty
  this->Scene->SetDirty(true);

  this->InvokeEvent(vtkCommand::InteractionEvent);

  return true;
}

//-----------------------------------------------------------------------------
bool vtkChartXY::KeyPressEvent(const vtkContextKeyEvent &key)
{
  switch (key.GetKeyCode())
  {
    // Reset the chart axes
    case 'r':
    case 'R':
      this->RecalculateBounds();
      this->Scene->SetDirty(true);
  }

  return true;
}

//-----------------------------------------------------------------------------
bool vtkChartXY::RemovePlotFromCorners(vtkPlot *plot)
{
  // We know the plot will only ever be in one of the corners
  for (size_t i = 0; i < this->ChartPrivate->PlotCorners.size(); ++i)
  {
    if (this->ChartPrivate->PlotCorners[i]->RemoveItem(plot))
    {
      return true;
    }
  }
  return false;
}

//-----------------------------------------------------------------------------
inline void vtkChartXY::TransformBoxOrPolygon(bool polygonMode,
                                              vtkTransform2D *transform,
                                              const vtkVector2f &mousePosition,
                                              vtkVector2f &min, vtkVector2f &max,
                                              vtkContextPolygon &polygon)
{
  if (polygonMode)
  {
    vtkNew<vtkTransform2D> inverseTransform;
    inverseTransform->SetMatrix(transform->GetMatrix());
    inverseTransform->Inverse();
    polygon =
      this->SelectionPolygon.Transformed(inverseTransform.GetPointer());
  }
  else
  {
    transform->InverseTransformPoints(this->MouseBox.GetData(),
                                      min.GetData(), 1);
    transform->InverseTransformPoints(mousePosition.GetData(),
                                      max.GetData(), 1);
    // Normalize the rectangle selection area before using it.
    if (min.GetX() > max.GetX())
    {
      float tmp = min.GetX();
      min.SetX(max.GetX());
      max.SetX(tmp);
    }
    if (min.GetY() > max.GetY())
    {
      float tmp = min.GetY();
      min.SetY(max.GetY());
      max.SetY(tmp);
    }
  }
}

//-----------------------------------------------------------------------------
void vtkChartXY::PrintSelf(ostream &os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Axes: " << endl;
  for (int i = 0; i < 4; ++i)
  {
    this->ChartPrivate->axes[i]->PrintSelf(os, indent.GetNextIndent());
  }
  if (this->ChartPrivate)
  {
    os << indent << "Number of plots: " << this->ChartPrivate->plots.size()
       << endl;
    for (unsigned int i = 0; i < this->ChartPrivate->plots.size(); ++i)
    {
      os << indent << "Plot " << i << ":" << endl;
      this->ChartPrivate->plots[i]->PrintSelf(os, indent.GetNextIndent());
    }
  }
  os << indent << "ZoomWithMouseWheel: " << this->ZoomWithMouseWheel << endl;

}
