// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkPlotBar.h"

#include "vtkArrayDispatch.h"
#include "vtkAxis.h"
#include "vtkBrush.h"
#include "vtkColorSeries.h"
#include "vtkContext2D.h"
#include "vtkContextDevice2D.h"
#include "vtkContextMapper2D.h"
#include "vtkDataArrayRange.h"
#include "vtkDataSetAttributes.h"
#include "vtkFloatArray.h"
#include "vtkIdTypeArray.h"
#include "vtkLookupTable.h"
#include "vtkNew.h"
#include "vtkPen.h"
#include "vtkPoints2D.h"
#include "vtkRect.h"
#include "vtkSmartPointer.h"
#include "vtkStringArray.h"
#include "vtkTable.h"
#include "vtkTimeStamp.h"

#include "vtkObjectFactory.h"

#include <algorithm>
#include <map>
#include <vector>

//------------------------------------------------------------------------------
VTK_ABI_NAMESPACE_BEGIN
namespace
{

// Copy the two arrays into the points array
struct CopyToPoints2Worker
{
  template <typename A, typename B>
  void operator()(A* a, B* b, vtkSmartPointer<vtkPoints2D> points,
    vtkSmartPointer<vtkPoints2D> previousPoints, int n, int logScale, int orientation,
    const vtkRectd& ss) const
  {
    const auto ad = vtk::DataArrayValueRange<1>(a);
    const auto bd = vtk::DataArrayValueRange<1>(b);
    points->SetNumberOfPoints(n);
    for (int i = 0; i < n; ++i)
    {
      float tmpA = static_cast<float>(0.0);
      float tmpB = static_cast<float>(0.0);
      if (orientation == vtkPlotBar::VERTICAL)
      {
        tmpA = ((ad[i] + ss[0]) * ss[2]);
        tmpB = ((bd[i] + ss[1]) * ss[3]);
      }
      else
      {
        tmpA = ((ad[i] + ss[1]) * ss[3]);
        tmpB = ((bd[i] + ss[0]) * ss[2]);
      }
      if (previousPoints && static_cast<int>(previousPoints->GetNumberOfPoints()) == n)
      {
        auto prev = previousPoints->GetPoint(i)[1];
        tmpB = tmpB + prev;
      }
      points->SetPoint(i, logScale & 1 ? log10(tmpA) : tmpA, logScale & 2 ? log10(tmpB) : tmpB);
    }
  }
};

// Copy one array into the points array, use the index of that array as x
struct CopyToPointsWorker
{
  template <typename A>
  void operator()(A* a, vtkPoints2D* points, vtkPoints2D* previousPoints, int n, int logScale,
    int orientation, const vtkRectd& ss)
  {
    points->SetNumberOfPoints(n);
    const auto ad = vtk::DataArrayValueRange<1>(a);
    for (int i = 0; i < n; ++i)
    {
      float tmpA = static_cast<float>(0.0);
      if (orientation == vtkPlotBar::VERTICAL)
      {
        tmpA = (ad[i] + ss[1]) * ss[3];
      }
      else
      {
        tmpA = (ad[i] + ss[0]) * ss[2];
      }
      if (previousPoints && static_cast<int>(previousPoints->GetNumberOfPoints()) == n)
      {
        auto prev = previousPoints->GetPoint(i)[1];
        tmpA = tmpA + prev;
      }
      points->SetPoint(i,
        static_cast<float>((logScale & 1) ? log10(static_cast<double>(i + 1.0)) : i),
        static_cast<float>((logScale & 2) ? log10(static_cast<double>(tmpA)) : tmpA));
    }
  }
};

} // namespace

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

class vtkPlotBarSegment : public vtkObject
{
public:
  vtkTypeMacro(vtkPlotBarSegment, vtkObject);
  static vtkPlotBarSegment* New();

  vtkPlotBarSegment()
  {
    this->Bar = nullptr;
    this->Points = nullptr;
    this->Sorted = nullptr;
    this->Previous = nullptr;
    this->Colors = nullptr;
  }

  ~vtkPlotBarSegment() override { delete this->Sorted; }

  void Configure(vtkPlotBar* bar, vtkDataArray* xArray, vtkDataArray* yArray, vtkAxis* xAxis,
    vtkAxis* yAxis, vtkPlotBarSegment* prev)
  {
    this->Bar = bar;
    this->Previous = prev;
    if (!this->Points)
    {
      this->Points = vtkSmartPointer<vtkPoints2D>::New();
    }
    // For the atypical case that Configure is called on a non-fresh "this"
    delete this->Sorted;

    int logScale = (xAxis->GetLogScaleActive() ? 1 : 0) + (yAxis->GetLogScaleActive() ? 2 : 0);
    if (xArray)
    {
      using CopyPointsDispatch =
        vtkArrayDispatch::Dispatch2ByValueType<vtkArrayDispatch::Reals, vtkArrayDispatch::Reals>;
      CopyToPoints2Worker cp2worker;
      if (!CopyPointsDispatch::Execute(xArray, yArray, cp2worker, this->Points,
            this->Previous ? this->Previous->Points : nullptr, xArray->GetNumberOfTuples(),
            logScale, this->Bar->GetOrientation(), this->Bar->GetShiftScale()))
      {
        cp2worker(xArray, yArray, this->Points, this->Previous ? this->Previous->Points : nullptr,
          xArray->GetNumberOfTuples(), logScale, this->Bar->GetOrientation(),
          this->Bar->GetShiftScale());
      }
    }
    else
    { // Using Index for X Series
      using CopyPointsDispatch = vtkArrayDispatch::DispatchByValueType<vtkArrayDispatch::Reals>;
      CopyToPointsWorker cpworker;
      if (!CopyPointsDispatch::Execute(yArray, cpworker, this->Points,
            this->Previous ? this->Previous->Points : nullptr, yArray->GetNumberOfTuples(),
            logScale, this->Bar->GetOrientation(), this->Bar->GetShiftScale()))
      {
        cpworker(yArray, this->Points, this->Previous ? this->Previous->Points : nullptr,
          yArray->GetNumberOfTuples(), logScale, this->Bar->GetOrientation(),
          this->Bar->GetShiftScale());
      }
    }
  }

  void Paint(
    vtkContext2D* painter, vtkPen* pen, vtkBrush* brush, float width, float offset, int orientation)
  {
    painter->ApplyPen(pen);
    painter->ApplyBrush(brush);
    int n = this->Points->GetNumberOfPoints();
    float* f = vtkArrayDownCast<vtkFloatArray>(this->Points->GetData())->GetPointer(0);
    float* p = nullptr;
    if (this->Previous)
    {
      p = vtkArrayDownCast<vtkFloatArray>(this->Previous->Points->GetData())->GetPointer(0);
    }

    for (int i = 0; i < n; ++i)
    {
      if (this->Colors)
      {
        if (this->Colors->GetNumberOfComponents() == 3)
        {
          painter->GetBrush()->SetColor(vtkColor3ub(this->Colors->GetPointer(i * 3)));
        }
        else if (this->Colors->GetNumberOfComponents() == 4)
        {
          painter->GetBrush()->SetColor(vtkColor4ub(this->Colors->GetPointer(i * 4)));
        }
        else
        {
          vtkErrorMacro(<< "Number of components not supported: "
                        << this->Colors->GetNumberOfComponents());
        }
      }
      if (orientation == vtkPlotBar::VERTICAL)
      {
        if (p)
        {
          painter->DrawRect(
            f[2 * i] - (width / 2) - offset, p[2 * i + 1], width, f[2 * i + 1] - p[2 * i + 1]);
        }
        else
        {
          painter->DrawRect(f[2 * i] - (width / 2) - offset, 0.0, width, f[2 * i + 1]);
        }
      }
      else // HORIZONTAL orientation
      {
        if (p)
        {
          painter->DrawRect(
            p[2 * i + 1], f[2 * i] - (width / 2) - offset, f[2 * i + 1] - p[2 * i + 1], width);
        }
        else
        {
          painter->DrawRect(0.0, (f[2 * i] - (width / 2) - offset), f[2 * i + 1], width);
        }
      }
    }
    // Paint selections if there are any.
    vtkIdTypeArray* selection = this->Bar->GetSelection();
    if (!selection)
    {
      return;
    }
    painter->ApplyBrush(this->Bar->GetSelectionBrush());
    for (vtkIdType j = 0; j < selection->GetNumberOfTuples(); ++j)
    {
      int i = selection->GetValue(j);
      if (orientation == vtkPlotBar::VERTICAL)
      {
        if (p)
        {
          painter->DrawRect(
            f[2 * i] - (width / 2) - offset, p[2 * i + 1], width, f[2 * i + 1] - p[2 * i + 1]);
        }
        else
        {
          painter->DrawRect(f[2 * i] - (width / 2) - offset, 0.0, width, f[2 * i + 1]);
        }
      }
      else // HORIZONTAL orientation
      {
        if (p)
        {
          painter->DrawRect(
            p[2 * i + 1], f[2 * i] - (width / 2) - offset, f[2 * i + 1] - p[2 * i + 1], width);
        }
        else
        {
          painter->DrawRect(0.0, f[2 * i] - (width / 2) - offset, f[2 * i + 1], width);
        }
      }
    }
  }

  vtkIdType GetNearestPoint(
    const vtkVector2f& point, vtkVector2f* location, float width, float offset, int orientation)
  {
    if (!this->Points && this->Points->GetNumberOfPoints())
    {
      return -1;
    }

    // The extent of any given bar is half a width on either
    // side of the point with which it is associated.
    float halfWidth = width / 2.0;

    // If orientation is VERTICAL, search normally. For HORIZONTAL,
    // simply transpose the X and Y coordinates of the target, as the rest
    // of the search uses the assumption that X = bar position, Y = bar
    // value; swapping the target X and Y is simpler than swapping the
    // X and Y of all the other references to the bar data.
    vtkVector2f targetPoint(point);
    if (orientation == vtkPlotBar::HORIZONTAL)
    {
      targetPoint.Set(point.GetY(), point.GetX()); // Swap x and y
    }

    this->CreateSortedPoints();

    // Get the left-most bar we might hit
    vtkIndexedVector2f lowPoint;
    lowPoint.index = 0;
    lowPoint.pos = vtkVector2f(targetPoint.GetX() - (offset * -1) - halfWidth, 0.0f);

    // Set up our search array, use the STL lower_bound algorithm
    VectorPIMPL::iterator low;
    VectorPIMPL& v = *this->Sorted;
    low = std::lower_bound(v.begin(), v.end(), lowPoint);

    while (low != v.end())
    {
      // Does the bar surround the point?
      if (low->pos.GetX() - halfWidth - offset < targetPoint.GetX() &&
        low->pos.GetX() + halfWidth - offset > targetPoint.GetX())
      {
        // Is the point within the vertical extent of the bar?
        if ((targetPoint.GetY() >= 0 && targetPoint.GetY() < low->pos.GetY()) ||
          (targetPoint.GetY() < 0 && targetPoint.GetY() > low->pos.GetY()))
        {
          *location = low->pos;
          vtkRectd ss = this->Bar->GetShiftScale();
          location->SetX((location->GetX() - ss.GetX()) / ss.GetWidth());
          location->SetY((location->GetY() - ss.GetY()) / ss.GetHeight());
          return static_cast<vtkIdType>(low->index);
        }
      }
      // Is the left side of the bar beyond the point?
      if (low->pos.GetX() - offset - halfWidth > targetPoint.GetX())
      {
        break;
      }
      ++low;
    }
    return -1;
  }

  void CreateSortedPoints()
  {
    // Sorted points, used when searching for the nearest point.
    if (!this->Sorted)
    {
      vtkIdType n = this->Points->GetNumberOfPoints();
      vtkVector2f* data = reinterpret_cast<vtkVector2f*>(
        vtkAOSDataArrayTemplate<float>::FastDownCast(this->Points->GetData())->GetPointer(0));
      this->Sorted = new VectorPIMPL(data, n);
      std::sort(this->Sorted->begin(), this->Sorted->end());
    }
  }

  bool SelectPoints(
    const vtkVector2f& min, const vtkVector2f& max, float width, float offset, int orientation)
  {
    if (!this->Points)
    {
      return false;
    }

    this->CreateSortedPoints();

    // If orientation is VERTICAL, search normally. For HORIZONTAL,
    // transpose the selection box.
    vtkVector2f targetMin(min);
    vtkVector2f targetMax(max);
    if (orientation == vtkPlotBar::HORIZONTAL)
    {
      targetMin.Set(min.GetY(), min.GetX());
      targetMax.Set(max.GetY(), max.GetX());
    }

    // The extent of any given bar is half a width on either
    // side of the point with which it is associated.
    float halfWidth = width / 2.0;

    // Get the lowest X coordinate we might hit
    vtkIndexedVector2f lowPoint;
    lowPoint.index = 0;
    lowPoint.pos = vtkVector2f(targetMin.GetX() - (offset * -1) - halfWidth, 0.0f);

    // Set up our search array, use the STL lower_bound algorithm
    VectorPIMPL::iterator low;
    VectorPIMPL& v = *this->Sorted;
    low = std::lower_bound(v.begin(), v.end(), lowPoint);

    std::vector<vtkIdType> selected;

    while (low != v.end())
    {
      // Is the bar's X coordinates at least partially within the box?
      if (low->pos.GetX() + halfWidth - offset > targetMin.GetX() &&
        low->pos.GetX() - halfWidth - offset < targetMax.GetX())
      {
        // Is the bar within the vertical extent of the box?
        if ((targetMin.GetY() > 0 && low->pos.GetY() >= targetMin.GetY()) ||
          (targetMax.GetY() < 0 && low->pos.GetY() <= targetMax.GetY()) ||
          (targetMin.GetY() < 0 && targetMax.GetY() > 0))
        {
          selected.push_back(static_cast<int>(low->index));
        }
      }
      // Is the left side of the bar beyond the box?
      if (low->pos.GetX() - offset - halfWidth > targetMax.GetX())
      {
        break;
      }
      ++low;
    }

    if (selected.empty())
    {
      return false;
    }
    else
    {
      this->Bar->GetSelection()->SetNumberOfTuples(static_cast<vtkIdType>(selected.size()));
      vtkIdType* ptr = this->Bar->GetSelection()->GetPointer(0);
      for (size_t i = 0; i < selected.size(); ++i)
      {
        ptr[i] = selected[i];
      }
      this->Bar->GetSelection()->Modified();
      return true;
    }
  }

  // Indexed vector for sorting
  struct vtkIndexedVector2f
  {
    size_t index;
    vtkVector2f pos;

    // Compare two vtkIndexedVector2f, in X component only
    bool operator<(const vtkIndexedVector2f& v2) const
    {
      return (this->pos.GetX() < v2.pos.GetX());
    }
  };

  class VectorPIMPL : public std::vector<vtkIndexedVector2f>
  {
  public:
    VectorPIMPL(vtkVector2f* array, size_t n)
    {
      this->reserve(n);
      for (size_t i = 0; i < n; ++i)
      {
        vtkIndexedVector2f tmp;
        tmp.index = i;
        tmp.pos = array[i];
        this->push_back(tmp);
      }
    }
  };

  vtkSmartPointer<vtkPlotBarSegment> Previous;
  vtkSmartPointer<vtkPoints2D> Points;
  vtkPlotBar* Bar;
  VectorPIMPL* Sorted;
  vtkVector2d ScalingFactor;
  vtkUnsignedCharArray* Colors;
};

vtkStandardNewMacro(vtkPlotBarSegment);

class vtkPlotBarPrivate
{
public:
  vtkPlotBarPrivate(vtkPlotBar* bar)
    : Bar(bar)
  {
  }

  void Update() { this->Segments.clear(); }

  vtkPlotBarSegment* AddSegment(vtkDataArray* xArray, vtkDataArray* yArray, vtkAxis* xAxis,
    vtkAxis* yAxis, vtkPlotBarSegment* prev = nullptr)
  {
    vtkNew<vtkPlotBarSegment> segment;
    segment->Configure(this->Bar, xArray, yArray, xAxis, yAxis, prev);
    this->Segments.emplace_back(segment.GetPointer());
    return segment.GetPointer();
  }

  void PaintSegments(vtkContext2D* painter, vtkColorSeries* colorSeries, vtkPen* pen,
    vtkBrush* brush, float width, float offset, int orientation)
  {
    int colorInSeries = 0;
    bool useColorSeries = this->Segments.size() > 1;
    for (std::vector<vtkSmartPointer<vtkPlotBarSegment>>::iterator it = this->Segments.begin();
         it != this->Segments.end(); ++it)
    {
      if (useColorSeries && colorSeries)
      {
        brush->SetColor(colorSeries->GetColorRepeating(colorInSeries++).GetData());
      }
      (*it)->Paint(painter, pen, brush, width, offset, orientation);
    }
  }

  vtkIdType GetNearestPoint(const vtkVector2f& point, vtkVector2f* location, float width,
    float offset, int orientation, vtkIdType* segmentIndex)
  {
    vtkIdType segmentIndexCtr = 0;
    for (std::vector<vtkSmartPointer<vtkPlotBarSegment>>::iterator it = this->Segments.begin();
         it != this->Segments.end(); ++it)
    {
      int barIndex = (*it)->GetNearestPoint(point, location, width, offset, orientation);
      if (barIndex != -1)
      {
        if (segmentIndex)
        {
          *segmentIndex = segmentIndexCtr;
        }
        return barIndex;
      }
      ++segmentIndexCtr;
    }
    if (segmentIndex)
    {
      *segmentIndex = -1;
    }
    return -1;
  }

  bool SelectPoints(
    const vtkVector2f& min, const vtkVector2f& max, float width, float offset, int orientation)
  {
    // Selection functionality not supported for stacked plots (yet)...
    if (this->Segments.size() != 1)
    {
      return false;
    }

    return this->Segments[0]->SelectPoints(min, max, width, offset, orientation);
  }

  std::vector<vtkSmartPointer<vtkPlotBarSegment>> Segments;
  vtkPlotBar* Bar;
  std::map<int, std::string> AdditionalSeries;
  std::string GroupName;
};

//------------------------------------------------------------------------------
vtkStandardNewMacro(vtkPlotBar);

//------------------------------------------------------------------------------
vtkPlotBar::vtkPlotBar()
{
  this->Private = new vtkPlotBarPrivate(this);
  // Points is not a vtkSmartPointer, so set it explicitly to nullptr
  this->Points = nullptr;
  this->Width = 1.0;
  this->Pen->SetWidth(1.0);
  this->Offset = 1.0;
  this->Orientation = vtkPlotBar::VERTICAL;
  this->ScalarVisibility = false;
  this->EnableOpacityMapping = true;
  this->LogX = false;
  this->LogY = false;
}

//------------------------------------------------------------------------------
vtkPlotBar::~vtkPlotBar()
{
  if (this->Points)
  {
    this->Points->Delete();
    this->Points = nullptr;
  }
  delete this->Private;
}

//------------------------------------------------------------------------------
bool vtkPlotBar::Paint(vtkContext2D* painter)
{
  // This is where everything should be drawn, or dispatched to other methods.
  vtkDebugMacro(<< "Paint event called in vtkPlotBar.");

  if (!this->Visible)
  {
    return false;
  }

  this->Private->PaintSegments(painter, this->ColorSeries, this->Pen, this->Brush, this->Width,
    this->Offset, this->Orientation);

  return true;
}

//------------------------------------------------------------------------------
bool vtkPlotBar::PaintLegend(vtkContext2D* painter, const vtkRectf& rect, int legendIndex)
{
  if (this->ColorSeries)
  {
    this->Brush->SetColor(this->ColorSeries->GetColorRepeating(legendIndex).GetData());
  }

  painter->ApplyPen(this->Pen);
  painter->ApplyBrush(this->Brush);
  painter->DrawRect(rect[0], rect[1], rect[2], rect[3]);
  return true;
}

//------------------------------------------------------------------------------
void vtkPlotBar::GetBounds(double bounds[4], bool unscaled)
{
  int seriesLow, seriesHigh, valuesLow, valuesHigh;
  // Don't re-orient the axes for vertical plots or unscaled bounds:
  if (this->Orientation == vtkPlotBar::VERTICAL || unscaled)
  {
    seriesLow = 0;  // Xmin
    seriesHigh = 1; // Xmax
    valuesLow = 2;  // Ymin
    valuesHigh = 3; // Ymax
  }
  else // HORIZONTAL orientation
  {
    seriesLow = 2;  // Ymin
    seriesHigh = 3; // Ymax
    valuesLow = 0;  // Xmin
    valuesHigh = 1; // Xmax
  }

  // Get the x and y arrays (index 0 and 1 respectively)
  vtkTable* table = this->Data->GetInput();
  if (!table)
  {
    return;
  }
  vtkDataArray* x =
    this->UseIndexForXSeries ? nullptr : this->Data->GetInputArrayToProcess(0, table);
  vtkDataArray* y = this->Data->GetInputArrayToProcess(1, table);
  vtkDataSetAttributes* rowData = table->GetRowData();
  if (!y)
  {
    return;
  }

  if (this->UseIndexForXSeries)
  {
    bounds[seriesLow] = 0 - (this->Width / 2);
    bounds[seriesHigh] = y->GetNumberOfTuples() + (this->Width / 2);
  }
  else if (x)
  {
    rowData->GetRange(x->GetName(), &bounds[seriesLow]);
    // We surround our point by Width/2 on either side
    bounds[seriesLow] -= this->Width / 2.0 + this->Offset;
    bounds[seriesHigh] += this->Width / 2.0 - this->Offset;
  }
  else
  {
    return;
  }

  rowData->GetRange(y->GetName(), &bounds[valuesLow]);

  double yRange[2];
  std::map<int, std::string>::iterator it;
  for (it = this->Private->AdditionalSeries.begin(); it != this->Private->AdditionalSeries.end();
       ++it)
  {
    y = vtkArrayDownCast<vtkDataArray>(table->GetColumnByName((*it).second.c_str()));
    rowData->GetRange(y->GetName(), yRange);
    bounds[valuesHigh] += yRange[1];
  }

  // Bar plots always have one of the value bounds at the origin
  if (bounds[valuesLow] > 0.0f)
  {
    bounds[valuesLow] = 0.0;
  }
  else if (bounds[valuesHigh] < 0.0f)
  {
    bounds[valuesHigh] = 0.0;
  }

  if (!unscaled)
  {
    vtkAxis* axes[2];
    axes[seriesLow / 2] = this->GetXAxis();
    axes[valuesLow / 2] = this->GetYAxis();
    if (axes[0]->GetLogScaleActive())
    {
      bounds[0] = log10(fabs(bounds[0]));
      bounds[1] = log10(fabs(bounds[1]));
    }
    if (axes[1]->GetLogScaleActive())
    {
      bounds[2] = log10(fabs(bounds[2]));
      bounds[3] = log10(fabs(bounds[3]));
    }
  }
  vtkDebugMacro(<< "Bounds: " << bounds[0] << "\t" << bounds[1] << "\t" << bounds[2] << "\t"
                << bounds[3]);
}

//------------------------------------------------------------------------------
void vtkPlotBar::GetBounds(double bounds[4])
{
  this->GetBounds(bounds, /*unscaled=*/false);
}

//------------------------------------------------------------------------------
void vtkPlotBar::GetUnscaledInputBounds(double bounds[4])
{
  this->GetBounds(bounds, /*unscaled=*/true);
}

//------------------------------------------------------------------------------
void vtkPlotBar::SetOrientation(int orientation)
{
  if (orientation < 0 || orientation > 1)
  {
    vtkErrorMacro("Error, invalid orientation value supplied: " << orientation);
    return;
  }
  this->Orientation = orientation;
}

//------------------------------------------------------------------------------
void vtkPlotBar::SetColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
{
  this->Brush->SetColor(r, g, b, a);
}

//------------------------------------------------------------------------------
void vtkPlotBar::SetColor(unsigned char r, unsigned char g, unsigned char b)
{
  this->Brush->SetColor(r, g, b);
}

//------------------------------------------------------------------------------
void vtkPlotBar::SetColorF(double r, double g, double b, double a)
{
  this->Brush->SetColorF(r, g, b, a);
}

//------------------------------------------------------------------------------
void vtkPlotBar::SetColorF(double r, double g, double b)
{
  this->Brush->SetColorF(r, g, b);
}

//------------------------------------------------------------------------------
void vtkPlotBar::GetColorF(double rgb[3])
{
  double rgba[4] = { 0.0, 0.0, 0.0, 0.0 };
  this->Brush->GetColorF(rgba);
  rgb[0] = rgba[0];
  rgb[1] = rgba[1];
  rgb[2] = rgba[2];
}

//------------------------------------------------------------------------------
vtkIdType vtkPlotBar::GetNearestPoint(const vtkVector2f& point,
  const vtkVector2f& vtkNotUsed(tolerance), vtkVector2f* location, vtkIdType* segmentIndex)
{
  return this->Private->GetNearestPoint(
    point, location, this->Width, this->Offset, this->Orientation, segmentIndex);
}

//------------------------------------------------------------------------------
vtkStringArray* vtkPlotBar::GetLabels()
{
  // If the label string is empty, return the y column name
  if (this->Labels)
  {
    return this->Labels;
  }
  else if (this->AutoLabels)
  {
    return this->AutoLabels;
  }
  else if (this->Data->GetInput() && this->Data->GetInputArrayToProcess(1, this->Data->GetInput()))
  {
    this->AutoLabels = vtkSmartPointer<vtkStringArray>::New();
    this->AutoLabels->InsertNextValue(
      this->Data->GetInputArrayToProcess(1, this->Data->GetInput())->GetName());

    std::map<int, std::string>::iterator it;
    for (it = this->Private->AdditionalSeries.begin(); it != this->Private->AdditionalSeries.end();
         ++it)
    {
      this->AutoLabels->InsertNextValue((*it).second);
    }
    return this->AutoLabels;
  }
  else
  {
    return nullptr;
  }
}

void vtkPlotBar::SetGroupName(const vtkStdString& name)
{
  if (this->Private->GroupName != name)
  {
    this->Private->GroupName = name;
    this->Modified();
  }
}

vtkStdString vtkPlotBar::GetGroupName()
{
  return this->Private->GroupName;
}

//------------------------------------------------------------------------------
bool vtkPlotBar::CacheRequiresUpdate()
{
  return this->Superclass::CacheRequiresUpdate() ||
    (this->XAxis && this->LogX != this->XAxis->GetLogScaleActive()) ||
    (this->YAxis && this->LogY != this->YAxis->GetLogScaleActive()) ||
    (this->LookupTable && this->LookupTable->GetMTime() > this->BuildTime);
}

//------------------------------------------------------------------------------
bool vtkPlotBar::UpdateCache()
{
  if (!this->Superclass::UpdateCache())
  {
    return false;
  }
  vtkTable* table = this->Data->GetInput();
  // Get the x and y arrays (index 0 and 1 respectively)
  vtkDataArray* x =
    this->UseIndexForXSeries ? nullptr : this->Data->GetInputArrayToProcess(0, table);
  vtkDataArray* y = this->Data->GetInputArrayToProcess(1, table);
  if (!x && !this->UseIndexForXSeries)
  {
    vtkErrorMacro(<< "No X column is set (index 0).");
    return false;
  }
  else if (!y)
  {
    vtkErrorMacro(<< "No Y column is set (index 1).");
    return false;
  }
  else if (!this->UseIndexForXSeries && x->GetNumberOfTuples() != y->GetNumberOfTuples())
  {
    vtkErrorMacro("The x and y columns must have the same number of elements.");
    return false;
  }

  this->LogX = this->XAxis->GetLogScaleActive();
  this->LogY = this->YAxis->GetLogScaleActive();

  this->Private->Update();

  vtkPlotBarSegment* prev = this->Private->AddSegment(x, y, this->GetXAxis(), this->GetYAxis());

  // Additions for color mapping
  if (this->ScalarVisibility && !this->ColorArrayName.empty())
  {
    vtkDataArray* c =
      vtkArrayDownCast<vtkDataArray>(table->GetColumnByName(this->ColorArrayName.c_str()));
    // TODO: Should add support for categorical coloring & try enum lookup
    if (c)
    {
      if (!this->LookupTable)
      {
        this->CreateDefaultLookupTable();
      }

      int outputFormat = this->EnableOpacityMapping ? VTK_RGBA : VTK_RGB;
      this->Colors = this->LookupTable->MapScalars(c, VTK_COLOR_MODE_MAP_SCALARS, -1, outputFormat);

      prev->Colors = this->Colors;
      this->Colors->Delete();
    }
    else
    {
      this->Colors = nullptr;
      prev->Colors = nullptr;
    }
  }

  std::map<int, std::string>::iterator it;

  for (it = this->Private->AdditionalSeries.begin(); it != this->Private->AdditionalSeries.end();
       ++it)
  {
    y = vtkArrayDownCast<vtkDataArray>(table->GetColumnByName((*it).second.c_str()));
    prev = this->Private->AddSegment(x, y, this->GetXAxis(), this->GetYAxis(), prev);
  }

  this->TooltipDefaultLabelFormat.clear();
  // Set the default tooltip according to the segments
  if (this->Private->Segments.size() > 1)
  {
    this->TooltipDefaultLabelFormat = "{s}: ";
  }
  if (this->IndexedLabels)
  {
    this->TooltipDefaultLabelFormat += "{i}: ";
  }
  this->TooltipDefaultLabelFormat += "{x},  {y}";

  this->BuildTime.Modified();
  return true;
}

//------------------------------------------------------------------------------
void vtkPlotBar::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}

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

void vtkPlotBar::SetInputArray(int index, const vtkStdString& name)
{
  if (index == 0 || index == 1)
  {
    vtkPlot::SetInputArray(index, name);
  }
  else
  {
    this->Private->AdditionalSeries[index] = name;
  }
  this->AutoLabels = nullptr; // No longer valid
}

//------------------------------------------------------------------------------
void vtkPlotBar::SetColorSeries(vtkColorSeries* colorSeries)
{
  if (this->ColorSeries == colorSeries)
  {
    return;
  }
  this->ColorSeries = colorSeries;
  this->Modified();
}

//------------------------------------------------------------------------------
vtkColorSeries* vtkPlotBar::GetColorSeries()
{
  return this->ColorSeries;
}

//------------------------------------------------------------------------------
void vtkPlotBar::SetLookupTable(vtkScalarsToColors* lut)
{
  if (this->LookupTable != lut)
  {
    this->LookupTable = lut;
    this->Modified();
  }
}

//------------------------------------------------------------------------------
vtkScalarsToColors* vtkPlotBar::GetLookupTable()
{
  if (!this->LookupTable)
  {
    this->CreateDefaultLookupTable();
  }
  return this->LookupTable;
}

//------------------------------------------------------------------------------
void vtkPlotBar::CreateDefaultLookupTable()
{
  vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
  // rainbow - blue to red
  lut->SetHueRange(0.6667, 0.0);
  lut->Build();
  // set reasonable defaults in case no data has been set
  double bounds[4] = { 0.0, 1.0, 0.0, 1.0 };
  this->GetBounds(bounds);
  lut->SetRange(bounds[0], bounds[1]);
  this->LookupTable = lut;
}

//------------------------------------------------------------------------------
void vtkPlotBar::SelectColorArray(const vtkStdString& arrayName)
{
  if (this->ColorArrayName == arrayName)
  {
    return;
  }
  vtkTable* table = this->Data->GetInput();
  if (!table)
  {
    vtkWarningMacro(<< "SelectColorArray called with no input table set.");
    return;
  }
  for (vtkIdType i = 0; i < table->GetNumberOfColumns(); ++i)
  {
    if (arrayName == table->GetColumnName(i))
    {
      this->ColorArrayName = arrayName;
      this->Modified();
      return;
    }
  }
  vtkDebugMacro(<< "SelectColorArray called with invalid column name.");
  this->ColorArrayName = "";
  this->Modified();
}

//------------------------------------------------------------------------------
void vtkPlotBar::SelectColorArray(vtkIdType arrayNum)
{
  vtkTable* table = this->Data->GetInput();
  if (!table)
  {
    vtkWarningMacro(<< "SelectColorArray called with no input table set.");
    return;
  }
  vtkDataArray* col = vtkArrayDownCast<vtkDataArray>(table->GetColumn(arrayNum));
  // TODO: Should add support for categorical coloring & try enum lookup
  if (!col)
  {
    vtkDebugMacro(<< "SelectColorArray called with invalid column index");
    return;
  }
  else
  {
    const char* arrayName = table->GetColumnName(arrayNum);
    if (this->ColorArrayName == arrayName || arrayName == nullptr)
    {
      return;
    }
    else
    {
      this->ColorArrayName = arrayName;
      this->Modified();
    }
  }
}

//------------------------------------------------------------------------------
vtkStdString vtkPlotBar::GetColorArrayName()
{
  return this->ColorArrayName;
}

//------------------------------------------------------------------------------
bool vtkPlotBar::SelectPoints(const vtkVector2f& min, const vtkVector2f& max)
{
  if (!this->Selection)
  {
    this->Selection = vtkIdTypeArray::New();
  }
  this->Selection->SetNumberOfTuples(0);

  return this->Private->SelectPoints(min, max, this->Width, this->Offset, this->Orientation);
}

//------------------------------------------------------------------------------
int vtkPlotBar::GetBarsCount()
{
  vtkTable* table = this->Data->GetInput();
  if (!table)
  {
    vtkWarningMacro(<< "GetBarsCount called with no input table set.");
    return 0;
  }
  vtkDataArray* x = this->Data->GetInputArrayToProcess(0, table);
  return x ? x->GetNumberOfTuples() : 0;
}

//------------------------------------------------------------------------------
void vtkPlotBar::GetDataBounds(double bounds[2])
{
  assert(bounds);
  // Get the x and y arrays (index 0 and 1 respectively)
  vtkTable* table = this->Data->GetInput();
  if (!table)
  {
    vtkWarningMacro(<< "GetDataBounds called with no input table set.");
    bounds[0] = VTK_DOUBLE_MAX;
    bounds[1] = VTK_DOUBLE_MIN;
    return;
  }
  vtkDataArray* x = this->Data->GetInputArrayToProcess(0, table);
  if (x)
  {
    table->GetRowData()->GetRange(x->GetName(), bounds);
  }
}
VTK_ABI_NAMESPACE_END
