/*=========================================================================
Program:   Visualization Toolkit
Module:    vtkParallelCoordinatesView.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.

=========================================================================*/
/*-------------------------------------------------------------------------
  Copyright 2009 Sandia Corporation.
  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
  the U.S. Government retains certain rights in this software.
  -------------------------------------------------------------------------*/
#include "vtkParallelCoordinatesView.h"

#include "vtkActor2D.h"
#include "vtkAlgorithmOutput.h"
#include "vtkBalloonRepresentation.h"
#include "vtkCellArray.h"
#include "vtkCellArrayIterator.h"
#include "vtkCommand.h"
#include "vtkDataSetAttributes.h"
#include "vtkDoubleArray.h"
#include "vtkIdList.h"
#include "vtkObjectFactory.h"
#include "vtkOutlineSource.h"
#include "vtkParallelCoordinatesHistogramRepresentation.h"
#include "vtkParallelCoordinatesInteractorStyle.h"
#include "vtkParallelCoordinatesRepresentation.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper2D.h"
#include "vtkProperty2D.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRendererCollection.h"
#include "vtkSCurveSpline.h"
#include "vtkSelection.h"
#include "vtkSelectionNode.h"
#include "vtkTable.h"
#include "vtkTextProperty.h"
#include "vtkViewTheme.h"

#include <cassert>
#include <sstream>

vtkStandardNewMacro(vtkParallelCoordinatesView);

//------------------------------------------------------------------------------

vtkParallelCoordinatesView::vtkParallelCoordinatesView()
{
  vtkParallelCoordinatesInteractorStyle* istyle = vtkParallelCoordinatesInteractorStyle::New();
  this->SetInteractorStyle(istyle);
  istyle->Delete();

  this->ReuseSingleRepresentationOn();

  istyle->AddObserver(vtkCommand::StartInteractionEvent, this->GetObserver());
  istyle->AddObserver(vtkCommand::InteractionEvent, this->GetObserver());
  istyle->AddObserver(vtkCommand::EndInteractionEvent, this->GetObserver());
  istyle->AddObserver(vtkCommand::UpdateEvent, this->GetObserver());

  this->BrushData = vtkSmartPointer<vtkPolyData>::New();
  this->BrushMapper = vtkSmartPointer<vtkPolyDataMapper2D>::New();
  this->BrushActor = vtkSmartPointer<vtkActor2D>::New();

  vtkSmartPointer<vtkCoordinate> dummycoord = vtkSmartPointer<vtkCoordinate>::New();
  dummycoord->SetCoordinateSystemToNormalizedViewport();
  this->BrushMapper->SetInputData(this->BrushData);
  this->BrushMapper->SetTransformCoordinate(dummycoord);
  this->BrushActor->SetMapper(this->BrushMapper);
  this->BrushActor->GetProperty()->SetColor(.1, 1.0, 1.0);

  this->InspectMode = VTK_INSPECT_MANIPULATE_AXES;
  this->BrushMode = VTK_BRUSH_LASSO;
  this->BrushOperator = VTK_BRUSHOPERATOR_ADD;
  this->MaximumNumberOfBrushPoints = -1;
  this->NumberOfBrushPoints = 0;
  this->SetMaximumNumberOfBrushPoints(100);
  this->ClearBrushPoints();
  this->FirstFunctionBrushLineDrawn = 0;
  this->CurrentBrushClass = 0;
  this->AxisHighlightPosition = VTK_HIGHLIGHT_CENTER;

  this->SelectedAxisPosition = -1;
  this->HighlightSource = vtkSmartPointer<vtkOutlineSource>::New();
  this->HighlightMapper = vtkSmartPointer<vtkPolyDataMapper2D>::New();
  this->HighlightActor = vtkSmartPointer<vtkActor2D>::New();

  this->HighlightSource->SetBounds(-1, -1, -1, -1, -1, -1);
  this->HighlightMapper->SetInputConnection(this->HighlightSource->GetOutputPort());
  this->HighlightMapper->SetTransformCoordinate(dummycoord);
  this->HighlightActor->SetMapper(this->HighlightMapper);
  this->HighlightActor->GetProperty()->SetColor(.1, 1.0, .1);
  this->HighlightActor->VisibilityOff();

  //  vtkSmartPointer<vtkViewTheme> theme = vtkSmartPointer<vtkViewTheme>::New();
  //  theme->SetBackgroundColor(0,0,0);
  //  theme->SetBackgroundColor2(0,0,0);
  //  this->ApplyViewTheme(theme);
}

//------------------------------------------------------------------------------
vtkParallelCoordinatesView::~vtkParallelCoordinatesView() = default;

void vtkParallelCoordinatesView::PrepareForRendering()
{
  vtkDebugMacro(<< "*** PrepareForRendering called");

  vtkParallelCoordinatesRepresentation* rep =
    vtkParallelCoordinatesRepresentation::SafeDownCast(this->GetRepresentation());

  if (rep)
  {
    vtkRenderer* ren = this->GetRenderer();

    this->Superclass::PrepareForRendering();

    if (!ren->HasViewProp(this->HighlightActor))
    {
      ren->AddActor(this->HighlightActor);
    }
    if (!ren->HasViewProp(this->BrushActor))
    {
      ren->AddActor(this->BrushActor);
    }

    // this is a hack to make sure that the balloon hover text is sitting on top
    if (ren->HasViewProp(this->Balloon))
    {
      this->Renderer->RemoveViewProp(this->Balloon);
      this->Renderer->AddViewProp(this->Balloon);
    }
  }
}

//------------------------------------------------------------------------------
void vtkParallelCoordinatesView::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << "MaximumNumberOfBrushPoints: " << this->MaximumNumberOfBrushPoints << endl;
  os << "BrushOperator: " << this->BrushOperator << endl;
  os << "BrushMode: " << this->BrushMode << endl;
  os << "InspectMode: " << this->InspectMode << endl;
  os << "CurrentBrushClass: " << this->CurrentBrushClass << endl;
}

//------------------------------------------------------------------------------
// The frustum selection code is borrowed from vtkRenderView.
void vtkParallelCoordinatesView::ProcessEvents(
  vtkObject* caller, unsigned long eventId, void* callData)
{

  //  if (caller == this->GetInteractor() || caller == this->GetInteractorStyle())
  if (caller == this->GetInteractorStyle())
  {
    vtkParallelCoordinatesInteractorStyle* style =
      vtkParallelCoordinatesInteractorStyle::SafeDownCast(this->GetInteractorStyle());
    vtkParallelCoordinatesRepresentation* rep =
      vtkParallelCoordinatesRepresentation::SafeDownCast(this->GetRepresentation());

    if (style && rep)
    {
      int state = style->GetState();

      if (eventId == vtkCommand::UpdateEvent)
      {
        rep->ResetAxes();
      }
      else
      {
        switch (state)
        {
          case vtkParallelCoordinatesInteractorStyle::INTERACT_HOVER:
            this->Hover(eventId);
            break;
          case vtkParallelCoordinatesInteractorStyle::INTERACT_INSPECT:
            if (this->InspectMode == VTK_INSPECT_MANIPULATE_AXES)
            {
              this->ManipulateAxes(eventId);
            }
            else if (this->InspectMode == VTK_INSPECT_SELECT_DATA)
            {
              this->SelectData(eventId);
            }
            break;
          case vtkParallelCoordinatesInteractorStyle::INTERACT_ZOOM:
            this->Zoom(eventId);
            break;
          case vtkParallelCoordinatesInteractorStyle::INTERACT_PAN:
            this->Pan(eventId);
            break;
        }
      }

      this->Render();
    }
  }

  this->Superclass::ProcessEvents(caller, eventId, callData);
}

vtkDataRepresentation* vtkParallelCoordinatesView::CreateDefaultRepresentation(
  vtkAlgorithmOutput* conn)
{
  vtkParallelCoordinatesHistogramRepresentation* rep =
    vtkParallelCoordinatesHistogramRepresentation::New();
  rep->SetInputConnection(conn);
  vtkDataObject* data = conn->GetProducer()->GetOutputDataObject(0);
  vtkTable* td = vtkTable::SafeDownCast(data);

  if (!td)
  {
    rep->SetInputArrayToProcess(
      0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS_THEN_CELLS, vtkDataSetAttributes::SCALARS);
  }
  else
  {
    int ncols = td->GetNumberOfColumns();
    for (int i = 0; i < ncols; i++)
    {
      vtkAbstractArray* a = td->GetColumn(i);
      rep->SetInputArrayToProcess(i, 0, 0, vtkDataObject::FIELD_ASSOCIATION_ROWS, a->GetName());
    }
  }

  return rep;
}

int vtkParallelCoordinatesView::SetAxisHighlightPosition(
  vtkParallelCoordinatesRepresentation* rep, int position)
{
  int numAxes = rep->GetNumberOfAxes();

  if (numAxes <= 0)
    return -1;

  double p1[2], p2[2];
  rep->GetPositionAndSize(p1, p2);
  double xpos = rep->GetXCoordinateOfPosition(position);

  if (xpos < 0 || position < 0 || position >= numAxes)
  {
    this->HighlightSource->SetBounds(-1, -1, -1, -1, -1, -1);
    this->HighlightActor->VisibilityOff();
    return -1;
  }
  else
  {
    double xmargin = .3 * p2[0] / static_cast<double>(numAxes);
    double ymargin = .05 * p2[1];
    if (this->AxisHighlightPosition == VTK_HIGHLIGHT_CENTER)
    {
      this->HighlightSource->SetBounds(
        xpos - xmargin, xpos + xmargin, p1[1] + ymargin, p1[1] + p2[1] - ymargin, 0, 0);
    }
    else if (this->AxisHighlightPosition == VTK_HIGHLIGHT_MIN)
    {
      this->HighlightSource->SetBounds(
        xpos - xmargin, xpos + xmargin, p1[1] - ymargin, p1[1] + ymargin, 0, 0);
    }
    else if (this->AxisHighlightPosition == VTK_HIGHLIGHT_MAX)
    {
      this->HighlightSource->SetBounds(
        xpos - xmargin, xpos + xmargin, p1[1] + p2[1] - ymargin, p1[1] + p2[1] + ymargin, 0, 0);
    }
    this->HighlightSource->Update();
    this->HighlightActor->VisibilityOn();
  }

  return position;
}

int vtkParallelCoordinatesView::SetAxisHighlightPosition(
  vtkParallelCoordinatesRepresentation* rep, double position)
{
  int nearestPosition = rep->GetPositionNearXCoordinate(position);

  return this->SetAxisHighlightPosition(rep, nearestPosition);
}

void vtkParallelCoordinatesView::SetBrushMode(int mode)
{
  if (mode < 0 || mode >= VTK_BRUSH_MODECOUNT)
  {
    return;
  }
  else
  {
    this->BrushMode = mode;

    // if we made it into function mode but left early, clear the lines
    if (this->FirstFunctionBrushLineDrawn && this->BrushMode != VTK_BRUSH_FUNCTION)
    {
      this->FirstFunctionBrushLineDrawn = 0;
      this->ClearBrushPoints();
      this->Render();
    }
  }
}

void vtkParallelCoordinatesView::SetBrushOperator(int op)
{
  if (op < 0 || op >= VTK_BRUSHOPERATOR_MODECOUNT)
  {
    return;
  }
  else
  {
    this->BrushOperator = op;
  }
}

void vtkParallelCoordinatesView::SetInspectMode(int mode)
{
  if (mode < 0 || mode >= VTK_INSPECT_MODECOUNT)
  {
    return;
  }
  else
  {
    this->InspectMode = mode;

    if (this->InspectMode != VTK_INSPECT_MANIPULATE_AXES)
    {
      this->HighlightActor->VisibilityOff();
    }
  }
}

void vtkParallelCoordinatesView::SetMaximumNumberOfBrushPoints(int num)
{
  if (num >= 2 && num != this->MaximumNumberOfBrushPoints)
  {
    this->MaximumNumberOfBrushPoints = num;

    vtkSmartPointer<vtkPoints> pts = vtkSmartPointer<vtkPoints>::New();
    pts->SetNumberOfPoints(4 * this->MaximumNumberOfBrushPoints);

    for (int i = 0; i < 4 * this->MaximumNumberOfBrushPoints; i++)
      pts->InsertPoint(i, -1, -1, 0);

    vtkSmartPointer<vtkCellArray> lines = vtkSmartPointer<vtkCellArray>::New();
    lines->AllocateEstimate(4, this->MaximumNumberOfBrushPoints);

    // first line is for a manually drawn curve, for selecting lines
    // second line is for the spline used for angular brushing
    // third and fourth lines are for the splines used for function brushing
    for (int i = 0; i < 4; i++)
    {
      lines->InsertNextCell(this->MaximumNumberOfBrushPoints);
      for (int j = 0; j < this->MaximumNumberOfBrushPoints; j++)
        lines->InsertCellPoint(0);
    }

    this->BrushData->SetPoints(pts);
    this->BrushData->SetLines(lines);
  }
}

void vtkParallelCoordinatesView::ClearBrushPoints()
{
  this->NumberOfBrushPoints = 0;

  vtkIdType npts = this->BrushData->GetPoints()->GetNumberOfPoints();
  for (vtkIdType i = 0; i < npts; i++)
    this->BrushData->GetPoints()->SetPoint(i, -1, -1, 0);

  // clear them for all of the lines
  vtkNew<vtkIdList> cell;
  auto cellIter = vtk::TakeSmartPointer(this->BrushData->GetLines()->NewIterator());
  for (cellIter->GoToFirstCell(); !cellIter->IsDoneWithTraversal(); cellIter->GoToNextCell())
  {
    const vtkIdType cellNum = cellIter->GetCurrentCellId();
    cellIter->GetCurrentCell(cell);

    for (vtkIdType j = 0; j < cell->GetNumberOfIds(); j++)
    {
      cell->SetId(j, cellNum * this->MaximumNumberOfBrushPoints);
    }

    cellIter->ReplaceCurrentCell(cell);
  }

  this->BrushData->Modified();
}

//------------------------------------------------------------------------------
int vtkParallelCoordinatesView::AddLassoBrushPoint(double* p)
{
  if (this->NumberOfBrushPoints >= this->MaximumNumberOfBrushPoints)
    return 0;

  vtkIdType ptid = this->NumberOfBrushPoints;
  this->BrushData->GetPoints()->SetPoint(ptid, p[0], p[1], 0);

  const vtkIdType npts = this->BrushData->GetLines()->GetCellSize(0);
  std::vector<vtkIdType> ptids(static_cast<vtkIdType>(npts));

  for (vtkIdType i = ptid; i < npts; i++)
  {
    ptids[static_cast<size_t>(i)] = ptid;
  }

  this->BrushData->GetLines()->ReplaceCellAtId(0, npts, ptids.data());

  this->NumberOfBrushPoints++;
  this->BrushData->Modified();

  return 1;
}

//------------------------------------------------------------------------------
int vtkParallelCoordinatesView::SetAngleBrushLine(double* p1, double* p2)
{
  this->SetBrushLine(1, p1, p2);
  return 1;
}

//------------------------------------------------------------------------------
int vtkParallelCoordinatesView::SetFunctionBrushLine1(double* p1, double* p2)
{
  this->SetBrushLine(2, p1, p2);
  return 1;
}
//------------------------------------------------------------------------------
int vtkParallelCoordinatesView::SetFunctionBrushLine2(double* p1, double* p2)
{
  this->SetBrushLine(3, p1, p2);
  return 1;
}

//------------------------------------------------------------------------------
int vtkParallelCoordinatesView::SetBrushLine(int line, double* p1, double* p2)
{
  double p1x = p1[0], p1y = p1[1];
  double p2x = p2[0], p2y = p2[1];
  vtkParallelCoordinatesRepresentation* rep =
    vtkParallelCoordinatesRepresentation::SafeDownCast(this->GetRepresentation());
  int numAxes = rep->GetNumberOfAxes();
  double* xs = new double[numAxes];
  rep->GetXCoordinatesOfPositions(xs);

  if (p1x == p2x)
  {
    delete[] xs;
    return 0;
  }

  // swap, if necessary...I don't think the splines like being out of order
  if (p1x > p2x)
  {
    double tmp;
    tmp = p1x;
    p1x = p2x;
    p2x = tmp;
    tmp = p1y;
    p1y = p2y;
    p2y = tmp;
  }

  // find the axis to the left of p1
  int left = numAxes - 1;
  for (int i = 0; i < numAxes; i++)
  {
    if (p1x > xs[i])
    {
      left = i;
    }
    else
    {
      break;
    }
  }

  int right = left + 1;

  // sanity check
  if (right >= numAxes || left < 0)
  {
    delete[] xs;
    return 0;
  }

  // find the points that line (p1-p2) intersects on the left/right axes
  double m = (double)(p2y - p1y) / (double)(p2x - p1x);
  double lefty = p1y - m * (p1x - xs[left]);
  double righty = p1y - m * (p1x - xs[right]);

  p1x = xs[left];
  p2x = xs[right];
  p1y = lefty;
  p2y = righty;
  // p1y = std::max<int>(std::min<int>(lefty,this->YMax),this->YMin);
  // p2y = std::max<int>(std::min<int>(righty,this->YMax),this->YMin);

  // sanity check
  if (p1x >= p2x)
  {
    delete[] xs;
    return 0;
  }

  int pointOffset = line * this->MaximumNumberOfBrushPoints;

  double dx = (double)(p2x - p1x) / (this->MaximumNumberOfBrushPoints - 1);

  if (!rep->GetUseCurves())
  {
    double dy = (double)(p2y - p1y) / (this->MaximumNumberOfBrushPoints - 1);

    for (int i = 0; i < this->MaximumNumberOfBrushPoints; i++)
    {
      this->BrushData->GetPoints()->SetPoint(pointOffset + i, p1x + i * dx, p1y + i * dy, 0);
    }
  }
  else
  {
    vtkSmartPointer<vtkSCurveSpline> spline = vtkSmartPointer<vtkSCurveSpline>::New();
    spline->SetParametricRange(p1x, p2x);
    spline->AddPoint(p1x, p1y);
    spline->AddPoint(p2x, p2y);

    for (int i = 0; i < this->MaximumNumberOfBrushPoints; i++)
    {
      this->BrushData->GetPoints()->SetPoint(
        pointOffset + i, p1x + (double)i * dx, spline->Evaluate(p1x + (double)i * dx), 0);
    }
  }

  vtkNew<vtkIdList> cell;
  this->BrushData->GetLines()->GetCellAtId(line, cell);

  for (vtkIdType j = 0; j < cell->GetNumberOfIds(); j++)
  {
    cell->SetId(j, pointOffset + j);
  }

  this->BrushData->GetLines()->ReplaceCellAtId(line, cell);
  this->BrushData->Modified();

  delete[] xs;
  return 1;
}

//------------------------------------------------------------------------------
void vtkParallelCoordinatesView::GetBrushLine(int line, vtkIdType& npts, vtkIdType const*& ptids)
{
  int cellNum = 0;
  for (this->BrushData->GetLines()->InitTraversal();
       this->BrushData->GetLines()->GetNextCell(npts, ptids); cellNum++)
  {
    if (cellNum == line)
    {
      return;
    }
  }
}

void vtkParallelCoordinatesView::Hover(unsigned long eventId)
{
  vtkParallelCoordinatesInteractorStyle* style =
    vtkParallelCoordinatesInteractorStyle::SafeDownCast(this->GetInteractorStyle());
  vtkParallelCoordinatesRepresentation* rep =
    vtkParallelCoordinatesRepresentation::SafeDownCast(this->GetRepresentation());

  double p1[2], p2[2];
  if (!rep->GetPositionAndSize(p1, p2))
    return;

  double cursorPosition[2] = { 0, 0 };
  style->GetCursorCurrentPosition(this->GetRenderer(), cursorPosition);

  // deal with hovering
  if (this->InspectMode == VTK_INSPECT_MANIPULATE_AXES && eventId == vtkCommand::InteractionEvent)
  {
    // if we're close to the representation
    if (cursorPosition[0] >= 0.0 && cursorPosition[0] <= 1.0 && cursorPosition[1] >= 0.0 &&
      cursorPosition[1] <= 1.0 && cursorPosition[0] > p1[0] - .05 * p2[0] &&
      cursorPosition[0] < p1[0] + 1.05 * p2[0])
    {

      this->SelectedAxisPosition = rep->GetPositionNearXCoordinate(cursorPosition[0]);
      double xpos = rep->GetXCoordinateOfPosition(this->SelectedAxisPosition);

      if (fabs(xpos - cursorPosition[0]) > .05)
      {
        this->SelectedAxisPosition = -1;
      }
      else if (cursorPosition[1] < p1[1] + .05 * p2[1])
      {
        this->AxisHighlightPosition = VTK_HIGHLIGHT_MIN;
      }
      else if (cursorPosition[1] > p1[1] + .95 * p2[1])
      {
        this->AxisHighlightPosition = VTK_HIGHLIGHT_MAX;
      }
      else
      {
        this->AxisHighlightPosition = VTK_HIGHLIGHT_CENTER;
      }
      this->SetAxisHighlightPosition(rep, this->SelectedAxisPosition);
    }
    else
    {
      this->SelectedAxisPosition = -1;
      this->SetAxisHighlightPosition(rep, this->SelectedAxisPosition);
    }
  }
}

void vtkParallelCoordinatesView::ManipulateAxes(unsigned long eventId)
{
  vtkParallelCoordinatesInteractorStyle* style =
    vtkParallelCoordinatesInteractorStyle::SafeDownCast(this->GetInteractorStyle());
  vtkParallelCoordinatesRepresentation* rep =
    vtkParallelCoordinatesRepresentation::SafeDownCast(this->GetRepresentation());

  double cursorPosition[2], cursorLastPosition[2], cursorStartPosition[2];
  style->GetCursorCurrentPosition(this->GetRenderer(), cursorPosition);
  style->GetCursorLastPosition(this->GetRenderer(), cursorLastPosition);
  style->GetCursorStartPosition(this->GetRenderer(), cursorStartPosition);

  double dx = cursorPosition[0] - cursorLastPosition[0];
  double dy = cursorPosition[1] - cursorLastPosition[1];

  if (eventId == vtkCommand::StartInteractionEvent)
  {
  }
  else if (eventId == vtkCommand::InteractionEvent)
  {
    if (this->SelectedAxisPosition >= 0)
    {
      if (AxisHighlightPosition == VTK_HIGHLIGHT_CENTER)
      {
        double xpos = rep->GetXCoordinateOfPosition(this->SelectedAxisPosition);
        this->SelectedAxisPosition =
          rep->SetXCoordinateOfPosition(this->SelectedAxisPosition, xpos + dx);
        this->SetAxisHighlightPosition(rep, this->SelectedAxisPosition);
      }
      else
      {
        double range[] = { 0.0, 0.0 };
        rep->GetRangeAtPosition(this->SelectedAxisPosition, range);

        if (AxisHighlightPosition == VTK_HIGHLIGHT_MAX)
        {
          range[1] += dy * (range[1] - range[0]);
        }
        else if (AxisHighlightPosition == VTK_HIGHLIGHT_MIN)
        {
          range[0] += dy * (range[1] - range[0]);
        }
        rep->SetRangeAtPosition(this->SelectedAxisPosition, range);
      }
    }
  }
  else if (eventId == vtkCommand::EndInteractionEvent)
  {
    this->SelectedAxisPosition = -1;
  }
}

void vtkParallelCoordinatesView::SelectData(unsigned long eventId)
{
  vtkParallelCoordinatesInteractorStyle* style =
    vtkParallelCoordinatesInteractorStyle::SafeDownCast(this->GetInteractorStyle());
  vtkParallelCoordinatesRepresentation* rep =
    vtkParallelCoordinatesRepresentation::SafeDownCast(this->GetRepresentation());

  double cursorPosition[2], cursorStartPosition[2];
  style->GetCursorCurrentPosition(this->GetRenderer(), cursorPosition);
  style->GetCursorStartPosition(this->GetRenderer(), cursorStartPosition);

  // in lasso mode, the user sketches a curve. Lines that are
  // near that curve are selected.
  if (this->BrushMode == VTK_BRUSH_LASSO)
  {
    if (eventId == vtkCommand::StartInteractionEvent)
    {
      this->AddLassoBrushPoint(cursorPosition);
    }
    else if (eventId == vtkCommand::InteractionEvent)
    {
      this->AddLassoBrushPoint(cursorPosition);
    }
    else if (eventId == vtkCommand::EndInteractionEvent)
    {
      const vtkIdType* ptids;
      vtkIdType npts;
      this->BrushData->GetLines()->GetCellAtId(0, npts, ptids);

      vtkSmartPointer<vtkPoints> pts = vtkSmartPointer<vtkPoints>::New();
      for (int i = 0; i < npts; i++)
      {
        pts->InsertNextPoint(this->BrushData->GetPoints()->GetPoint(ptids[i]));
      }

      rep->LassoSelect(this->CurrentBrushClass, this->BrushOperator, pts);
      /*
        vtkSmartPointer<vtkDoubleArray> locs = vtkSmartPointer<vtkDoubleArray>::New();
        locs->SetNumberOfComponents(2);
        locs->SetNumberOfTuples(this->NumberOfBrushPoints);
        for (int i=0; i<this->NumberOfBrushPoints; i++)
        {
        locs->SetTuple(i,this->BrushData->GetPoints()->GetPoint(ptids[i]));
        }

        // generate a selection based on the lasso points
        vtkSmartPointer<vtkSelection> sel = vtkSmartPointer<vtkSelection>::New();
        vtkSmartPointer<vtkParallelCoordinatesSelectionNode> node =
        vtkSmartPointer<vtkParallelCoordinatesSelectionNode>::New();
        node->SetSelectionOperator(this->BrushOperator);
        node->SetSelectionIndex(this->CurrentBrushClass);
        node->SetContentType(vtkSelectionNode::LOCATIONS);
        node->SetFieldType(vtkSelectionNode::ROW);
        node->SetSelectionList(locs);

        sel->AddNode(node);
        rep->Select(this,sel);
      */
      this->ClearBrushPoints();
    }
  }
  // in angle brush mode, the user clicks one point to start the
  // line.  the cursor position is the second endpoint of the line.
  else if (this->BrushMode == VTK_BRUSH_ANGLE)
  {
    if (eventId == vtkCommand::StartInteractionEvent || eventId == vtkCommand::InteractionEvent)
    {
      this->SetAngleBrushLine(cursorStartPosition, cursorPosition);
    }
    else if (eventId == vtkCommand::EndInteractionEvent)
    {
      const vtkIdType* ptids = nullptr;
      vtkIdType npts = 0;
      this->GetBrushLine(1, npts, ptids);

      double p1[3] = { 0, 0, 0 };
      double p2[3] = { 0, 0, 0 };
      this->BrushData->GetPoints()->GetPoint(ptids[0], p1);
      this->BrushData->GetPoints()->GetPoint(ptids[npts - 1], p2);

      rep->AngleSelect(this->CurrentBrushClass, this->BrushOperator, p1, p2);
      /*
        vtkSmartPointer<vtkDoubleArray> locs = vtkSmartPointer<vtkDoubleArray>::New();
        locs->SetNumberOfComponents(2);
        locs->SetNumberOfTuples(2);
        locs->SetTuple(0,p1);
        locs->SetTuple(1,p2);

        // generate a selection based on the angle brush points
        vtkSmartPointer<vtkSelection> sel = vtkSmartPointer<vtkSelection>::New();
        vtkSmartPointer<vtkParallelCoordinatesSelectionNode> node =
        vtkSmartPointer<vtkParallelCoordinatesSelectionNode>::New();
        node->SetSelectionOperator(this->BrushOperator);
        node->SetSelectionIndex(this->CurrentBrushClass);
        node->SetContentType(vtkParallelCoordinatesSelectionNode::ANGLE_LOCATIONS);
        node->SetFieldType(vtkSelectionNode::ROW);
        node->SetSelectionList(locs);

        sel->AddNode(node);
        rep->Select(this,sel);
      */
      this->ClearBrushPoints();
    }
  }
  // same as angle mode, but do two of them.
  else if (this->BrushMode == VTK_BRUSH_FUNCTION)
  {
    if (eventId == vtkCommand::StartInteractionEvent || eventId == vtkCommand::InteractionEvent)
    {
      if (!this->FirstFunctionBrushLineDrawn)
      {
        this->SetFunctionBrushLine1(cursorStartPosition, cursorPosition);
      }
      else
      {
        this->SetFunctionBrushLine2(cursorStartPosition, cursorPosition);
      }
    }
    else if (eventId == vtkCommand::EndInteractionEvent)
    {
      // if the first line isn't finished, keep going with the second.
      if (!this->FirstFunctionBrushLineDrawn)
      {
        this->FirstFunctionBrushLineDrawn = 1;
      }
      // the first line is finished, so do the selection
      else
      {
        const vtkIdType* ptids = nullptr;
        vtkIdType npts = 0;

        double p1[3] = { 0, 0, 0 };
        double p2[3] = { 0, 0, 0 };
        this->GetBrushLine(2, npts, ptids);
        this->BrushData->GetPoints()->GetPoint(ptids[0], p1);
        this->BrushData->GetPoints()->GetPoint(ptids[npts - 1], p2);

        double q1[3] = { 0, 0, 0 };
        double q2[3] = { 0, 0, 0 };
        this->GetBrushLine(3, npts, ptids);
        this->BrushData->GetPoints()->GetPoint(ptids[0], q1);
        this->BrushData->GetPoints()->GetPoint(ptids[npts - 1], q2);

        rep->FunctionSelect(this->CurrentBrushClass, this->BrushOperator, p1, p2, q1, q2);

        /*        vtkSmartPointer<vtkDoubleArray> locs = vtkSmartPointer<vtkDoubleArray>::New();
                  locs->SetNumberOfComponents(2);
                  locs->SetNumberOfTuples(4);
                  locs->SetTuple(0,p1);
                  locs->SetTuple(1,p2);
                  locs->SetTuple(2,q1);
                  locs->SetTuple(3,q2);


                  // generate a selection based on the angle brush points
                  vtkSmartPointer<vtkSelection> sel = vtkSmartPointer<vtkSelection>::New();
                  vtkSmartPointer<vtkParallelCoordinatesSelectionNode> node =
           vtkSmartPointer<vtkParallelCoordinatesSelectionNode>::New();
                  node->SetSelectionOperator(this->BrushOperator);
                  node->SetSelectionIndex(this->CurrentBrushClass);
                  node->SetContentType(vtkParallelCoordinatesSelectionNode::FUNCTION_LOCATIONS);
                  node->SetFieldType(vtkSelectionNode::ROW);
                  node->SetSelectionList(locs);
                  sel->AddNode(node);
                  rep->Select(this,sel);
        */

        this->FirstFunctionBrushLineDrawn = 0;
        this->ClearBrushPoints();
      }
    }
  }
  else if (this->BrushMode == VTK_BRUSH_AXISTHRESHOLD)
  {
  }
}

void vtkParallelCoordinatesView::Zoom(unsigned long eventId)
{
  vtkParallelCoordinatesInteractorStyle* style =
    vtkParallelCoordinatesInteractorStyle::SafeDownCast(this->GetInteractorStyle());
  vtkParallelCoordinatesRepresentation* rep =
    vtkParallelCoordinatesRepresentation::SafeDownCast(this->GetRepresentation());

  double p1[2], size[2], p2[2];
  rep->GetPositionAndSize(p1, size);
  p2[0] = p1[0] + size[0];
  p2[1] = p1[1] + size[1];

  double cursorPosition[2], cursorLastPosition[2], cursorStartPosition[2];
  style->GetCursorCurrentPosition(this->GetRenderer(), cursorPosition);
  style->GetCursorLastPosition(this->GetRenderer(), cursorLastPosition);
  style->GetCursorStartPosition(this->GetRenderer(), cursorStartPosition);

  double v1[2] = { cursorStartPosition[0] - p1[0], cursorStartPosition[1] - p1[1] };
  double v2[2] = { cursorStartPosition[0] - p2[0], cursorStartPosition[1] - p2[1] };

  double dy = -(cursorPosition[1] - cursorLastPosition[1]);

  if (eventId == vtkCommand::StartInteractionEvent)
  {
  }
  else if (eventId == vtkCommand::InteractionEvent)
  {
    double p1new[2] = { p1[0] + dy * v1[0], p1[1] + dy * v1[1] };
    double p2new[2] = { p2[0] + dy * v2[0], // - p1new[0],
      p2[1] + dy * v2[1] };                 // - p1new[1]};

    double sizenew[] = { p2new[0] - p1new[0], p2new[1] - p1new[1] };

    rep->SetPositionAndSize(p1new, sizenew);
    this->SetAxisHighlightPosition(rep, this->SelectedAxisPosition);
  }
  else if (eventId == vtkCommand::EndInteractionEvent)
  {
  }
}

void vtkParallelCoordinatesView::Pan(unsigned long eventId)
{
  vtkParallelCoordinatesInteractorStyle* style =
    vtkParallelCoordinatesInteractorStyle::SafeDownCast(this->GetInteractorStyle());
  vtkParallelCoordinatesRepresentation* rep =
    vtkParallelCoordinatesRepresentation::SafeDownCast(this->GetRepresentation());

  double p1[2], size[2], p2[2];
  rep->GetPositionAndSize(p1, size);
  p2[0] = p1[0] + size[0];
  p2[1] = p1[1] + size[1];

  double cursorPosition[2], cursorLastPosition[2], cursorStartPosition[2];
  style->GetCursorCurrentPosition(this->GetRenderer(), cursorPosition);
  style->GetCursorLastPosition(this->GetRenderer(), cursorLastPosition);
  style->GetCursorStartPosition(this->GetRenderer(), cursorStartPosition);

  double dx = cursorPosition[0] - cursorLastPosition[0];
  double dy = cursorPosition[1] - cursorLastPosition[1];

  if (eventId == vtkCommand::StartInteractionEvent)
  {
  }
  else if (eventId == vtkCommand::InteractionEvent)
  {
    double p1new[2] = { p1[0] + dx, p1[1] + dy };
    double p2new[2] = { p2[0] + dx, p2[1] + dy };

    double sizenew[] = { p2new[0] - p1new[0], p2new[1] - p1new[1] };

    rep->SetPositionAndSize(p1new, sizenew);
    this->SetAxisHighlightPosition(rep, this->SelectedAxisPosition);
  }
  else if (eventId == vtkCommand::EndInteractionEvent)
  {
  }
}

void vtkParallelCoordinatesView::ApplyViewTheme(vtkViewTheme* theme)
{
  this->Superclass::ApplyViewTheme(theme);
  this->Balloon->GetFrameProperty()->SetColor(theme->GetBackgroundColor()); // CellColor());
  this->Balloon->GetTextProperty()->SetColor(theme->GetCellColor());        // BackgroundColor());
}
