// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkSpiderPlotActor.h"

#include "vtkAlgorithmOutput.h"
#include "vtkAxisActor2D.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkFieldData.h"
#include "vtkGlyphSource2D.h"
#include "vtkLegendBoxActor.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper2D.h"
#include "vtkProperty2D.h"
#include "vtkStringFormatter.h"
#include "vtkTextMapper.h"
#include "vtkTextProperty.h"
#include "vtkTrivialProducer.h"
#include "vtkUnsignedCharArray.h"
#include "vtkViewport.h"
#include "vtkWindow.h"

#include <string>
#include <vector>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkSpiderPlotActor);

vtkCxxSetObjectMacro(vtkSpiderPlotActor, LabelTextProperty, vtkTextProperty);
vtkCxxSetObjectMacro(vtkSpiderPlotActor, TitleTextProperty, vtkTextProperty);

// PIMPL'd list of labels
class vtkAxisLabelArray : public std::vector<std::string>
{
};

// PIMPL'd list of ranges
struct vtkAxisRange
{
  double Min;
  double Max;
  vtkAxisRange()
    : Min(0.0)
    , Max(0.0)
  {
  }
  vtkAxisRange(double min, double max)
    : Min(min)
    , Max(max)
  {
  }
};
class vtkAxisRanges : public std::vector<vtkAxisRange>
{
};

class vtkSpiderPlotActorConnection : public vtkAlgorithm
{
public:
  static vtkSpiderPlotActorConnection* New();
  vtkTypeMacro(vtkSpiderPlotActorConnection, vtkAlgorithm);

  vtkSpiderPlotActorConnection() { this->SetNumberOfInputPorts(1); }
};

vtkStandardNewMacro(vtkSpiderPlotActorConnection);

//------------------------------------------------------------------------------
// Instantiate object
vtkSpiderPlotActor::vtkSpiderPlotActor()
{
  // Actor2D positions
  this->PositionCoordinate->SetCoordinateSystemToNormalizedViewport();
  this->PositionCoordinate->SetValue(0.1, 0.1);
  this->Position2Coordinate->SetCoordinateSystemToNormalizedViewport();
  this->Position2Coordinate->SetValue(0.9, 0.8);
  this->Position2Coordinate->SetReferenceCoordinate(nullptr);

  this->ConnectionHolder = vtkSpiderPlotActorConnection::New();

  this->IndependentVariables = VTK_IV_COLUMN;
  this->TitleVisibility = 1;
  this->Title = nullptr;
  this->Labels = new vtkAxisLabelArray;
  this->Ranges = new vtkAxisRanges;
  this->LabelMappers = nullptr;
  this->LabelActors = nullptr;
  this->LabelTextProperty = vtkTextProperty::New();
  this->LabelTextProperty->SetFontSize(12);
  this->LabelTextProperty->SetBold(1);
  this->LabelTextProperty->SetItalic(1);
  this->LabelTextProperty->SetShadow(0);
  this->LabelTextProperty->SetFontFamilyToArial();
  this->TitleTextProperty = vtkTextProperty::New();
  this->TitleTextProperty->ShallowCopy(this->LabelTextProperty);
  this->TitleTextProperty->SetFontSize(24);
  this->TitleTextProperty->SetBold(1);
  this->TitleTextProperty->SetItalic(0);
  this->TitleTextProperty->SetShadow(1);
  this->TitleTextProperty->SetFontFamilyToArial();
  this->LabelVisibility = 1;
  this->NumberOfRings = 2;

  this->LegendVisibility = 1;

  this->LegendActor->GetPositionCoordinate()->SetCoordinateSystemToViewport();
  this->LegendActor->GetPosition2Coordinate()->SetCoordinateSystemToViewport();
  this->LegendActor->GetPosition2Coordinate()->SetReferenceCoordinate(nullptr);
  this->LegendActor->BorderOff();
  this->LegendActor->SetNumberOfEntries(100); // initial allocation
  this->LegendActor->SetPadding(2);
  this->LegendActor->ScalarVisibilityOff();
  this->GlyphSource = vtkGlyphSource2D::New();
  this->GlyphSource->SetGlyphTypeToNone();
  this->GlyphSource->DashOn();
  this->GlyphSource->FilledOff();
  this->GlyphSource->Update();

  this->PlotData = vtkPolyData::New();
  this->PlotMapper = vtkPolyDataMapper2D::New();
  this->PlotMapper->SetInputData(this->PlotData);
  this->PlotActor = vtkActor2D::New();
  this->PlotActor->SetMapper(this->PlotMapper);

  this->TitleMapper = vtkTextMapper::New();
  this->TitleActor = vtkActor2D::New();
  this->TitleActor->SetMapper(this->TitleMapper);
  this->TitleActor->GetPositionCoordinate()->SetCoordinateSystemToViewport();

  this->N = 0;
  this->Mins = nullptr;
  this->Maxs = nullptr;

  this->WebData = vtkPolyData::New();
  this->WebMapper = vtkPolyDataMapper2D::New();
  this->WebMapper->SetInputData(this->WebData);
  this->WebActor = vtkActor2D::New();
  this->WebActor->SetMapper(this->WebMapper);

  this->LastPosition[0] = this->LastPosition[1] = this->LastPosition2[0] = this->LastPosition2[1] =
    0;

  this->P1[0] = this->P1[1] = this->P2[0] = this->P2[1] = 0.0;
}

//------------------------------------------------------------------------------
vtkSpiderPlotActor::~vtkSpiderPlotActor()
{
  this->ConnectionHolder->Delete();
  this->ConnectionHolder = nullptr;

  delete[] this->Title;
  this->Title = nullptr;

  delete this->Labels;
  delete this->Ranges;
  this->SetLabelTextProperty(nullptr);
  this->SetTitleTextProperty(nullptr);

  this->GlyphSource->Delete();

  this->Initialize();

  this->TitleMapper->Delete();
  this->TitleMapper = nullptr;
  this->TitleActor->Delete();
  this->TitleActor = nullptr;

  this->WebData->Delete();
  this->WebMapper->Delete();
  this->WebActor->Delete();

  this->PlotData->Delete();
  this->PlotMapper->Delete();
  this->PlotActor->Delete();
}

//------------------------------------------------------------------------------
void vtkSpiderPlotActor::SetInputConnection(vtkAlgorithmOutput* ao)
{
  this->ConnectionHolder->SetInputConnection(ao);
}

//------------------------------------------------------------------------------
void vtkSpiderPlotActor::SetInputData(vtkDataObject* dobj)
{
  vtkTrivialProducer* tp = vtkTrivialProducer::New();
  tp->SetOutput(dobj);
  this->SetInputConnection(tp->GetOutputPort());
  tp->Delete();
}

//------------------------------------------------------------------------------
vtkDataObject* vtkSpiderPlotActor::GetInput()
{
  return this->ConnectionHolder->GetInputDataObject(0, 0);
}

//------------------------------------------------------------------------------
// Free-up axes and related stuff
void vtkSpiderPlotActor::Initialize()
{
  if (this->LabelActors)
  {
    for (int i = 0; i < this->N; i++)
    {
      this->LabelMappers[i]->Delete();
      this->LabelActors[i]->Delete();
    }
    delete[] this->LabelMappers;
    this->LabelMappers = nullptr;
    delete[] this->LabelActors;
    this->LabelActors = nullptr;
  }

  if (this->Mins)
  {
    delete[] this->Mins;
    this->Mins = nullptr;
    delete[] this->Maxs;
    this->Maxs = nullptr;
  }

  this->N = 0;
}

//------------------------------------------------------------------------------
// Plot scalar data for each input dataset.
int vtkSpiderPlotActor::RenderOverlay(vtkViewport* viewport)
{
  int renderedSomething = 0;

  if (!this->BuildPlot(viewport))
  {
    return 0;
  }

  // Done rebuilding, render as appropriate.
  if (this->GetInput() == nullptr || this->N <= 0)
  {
    vtkErrorMacro(<< "Nothing to plot!");
    return 0;
  }

  if (this->TitleVisibility)
  {
    renderedSomething += this->TitleActor->RenderOverlay(viewport);
  }

  this->WebActor->SetProperty(this->GetProperty());
  renderedSomething += this->WebActor->RenderOverlay(viewport);
  renderedSomething += this->PlotActor->RenderOverlay(viewport);

  if (this->LabelVisibility)
  {
    for (int i = 0; i < this->N; i++)
    {
      renderedSomething += this->LabelActors[i]->RenderOverlay(viewport);
    }
  }

  if (this->LegendVisibility)
  {
    renderedSomething += this->LegendActor->RenderOverlay(viewport);
  }

  return renderedSomething;
}

//------------------------------------------------------------------------------
// Plot scalar data for each input dataset.
int vtkSpiderPlotActor::RenderOpaqueGeometry(vtkViewport* viewport)
{
  int renderedSomething = 0;

  if (!this->BuildPlot(viewport))
  {
    return 0;
  }

  // Done rebuilding, render as appropriate.
  if (this->GetInput() == nullptr || this->N <= 0)
  {
    vtkErrorMacro(<< "Nothing to plot!");
    return 0;
  }

  if (this->TitleVisibility)
  {
    renderedSomething += this->TitleActor->RenderOpaqueGeometry(viewport);
  }

  this->WebActor->SetProperty(this->GetProperty());
  renderedSomething += this->WebActor->RenderOpaqueGeometry(viewport);
  renderedSomething += this->PlotActor->RenderOpaqueGeometry(viewport);

  if (this->LabelVisibility)
  {
    for (int i = 0; i < this->N; i++)
    {
      renderedSomething += this->LabelActors[i]->RenderOpaqueGeometry(viewport);
    }
  }

  if (this->LegendVisibility)
  {
    renderedSomething += this->LegendActor->RenderOpaqueGeometry(viewport);
  }

  return renderedSomething;
}

//------------------------------------------------------------------------------
// Description:
// Does this prop have some translucent polygonal geometry?
vtkTypeBool vtkSpiderPlotActor::HasTranslucentPolygonalGeometry()
{
  return 0;
}

//------------------------------------------------------------------------------
int vtkSpiderPlotActor::BuildPlot(vtkViewport* viewport)
{
  // Initialize
  vtkDebugMacro(<< "Building spider plot");

  // Make sure input is up to date, and that the data is the correct shape to
  // plot.
  if (!this->GetInput())
  {
    vtkErrorMacro(<< "Nothing to plot!");
    return 0;
  }

  if (!this->TitleTextProperty)
  {
    vtkErrorMacro(<< "Need title text property to render plot");
    return 0;
  }
  if (!this->LabelTextProperty)
  {
    vtkErrorMacro(<< "Need label text property to render plot");
    return 0;
  }

  // Viewport change may not require rebuild
  int positionsHaveChanged = 0;
  if (viewport->GetMTime() > this->BuildTime ||
    (viewport->GetVTKWindow() && viewport->GetVTKWindow()->GetMTime() > this->BuildTime))
  {
    int* lastPosition = this->PositionCoordinate->GetComputedViewportValue(viewport);
    int* lastPosition2 = this->Position2Coordinate->GetComputedViewportValue(viewport);
    if (lastPosition[0] != this->LastPosition[0] || lastPosition[1] != this->LastPosition[1] ||
      lastPosition2[0] != this->LastPosition2[0] || lastPosition2[1] != this->LastPosition2[1])
    {
      this->LastPosition[0] = lastPosition[0];
      this->LastPosition[1] = lastPosition[1];
      this->LastPosition2[0] = lastPosition2[0];
      this->LastPosition2[1] = lastPosition2[1];
      positionsHaveChanged = 1;
    }
  }

  // Check modified time to see whether we have to rebuild.
  this->ConnectionHolder->GetInputAlgorithm()->Update();

  if (positionsHaveChanged || this->GetMTime() > this->BuildTime ||
    this->GetInput()->GetMTime() > this->BuildTime ||
    this->LabelTextProperty->GetMTime() > this->BuildTime ||
    this->TitleTextProperty->GetMTime() > this->BuildTime)
  {
    vtkDebugMacro(<< "Rebuilding plot");

    // Build axes
    const int* size = viewport->GetSize();
    if (!this->PlaceAxes(viewport, size))
    {
      return 0;
    }

    this->BuildTime.Modified();
  } // If need to rebuild the plot

  return 1;
}

//------------------------------------------------------------------------------
static inline int vtkSpiderPlotActorGetComponent(
  vtkFieldData* field, vtkIdType tuple, int component, double* val)
{
  int array_comp;
  int array_index = field->GetArrayContainingComponent(component, array_comp);
  if (array_index < 0)
  {
    return 0;
  }
  vtkDataArray* da = field->GetArray(array_index);
  if (!da)
  {
    // non-numeric array.
    return 0;
  }
  *val = da->GetComponent(tuple, array_comp);
  return 1;
}

#define VTK_RING_PTS 64
//------------------------------------------------------------------------------
int vtkSpiderPlotActor::PlaceAxes(vtkViewport* viewport, const int* vtkNotUsed(size))
{
  vtkIdType i, j, k;
  vtkDataObject* input = this->GetInput();
  vtkFieldData* field = input->GetFieldData();
  double v = 0.0;

  this->Initialize();

  if (!field)
  {
    return 0;
  }

  // Determine the shape of the field
  int numComponents = field->GetNumberOfComponents(); // number of components
  // Note: numComponents also includes the non-numeric arrays.

  int numColumns = 0;             // number of "columns" -- includes only numeric arrays.
  vtkIdType numRows = VTK_ID_MAX; // figure out number of rows
  vtkIdType numTuples;
  vtkDataArray* array;
  for (i = 0; i < field->GetNumberOfArrays(); i++)
  {
    array = field->GetArray(i);
    if (!array)
    {
      // skip over non-numeric arrays.
      continue;
    }
    numColumns += array->GetNumberOfComponents();
    numTuples = array->GetNumberOfTuples();
    numRows = std::min(numTuples, numRows);
  }

  // Determine the number of independent variables
  if (this->IndependentVariables == VTK_IV_COLUMN)
  {
    this->N = numColumns;
  }
  else // row
  {
    this->N = numRows;
  }

  if (this->N <= 0 || this->N >= VTK_ID_MAX)
  {
    this->N = 0;
    vtkErrorMacro(<< "No field data to plot");
    return 0;
  }

  // We need to loop over the field to determine the range of
  // each independent variable.
  this->Mins = new double[this->N];
  this->Maxs = new double[this->N];
  for (i = 0; i < this->N; i++)
  {
    this->Mins[i] = VTK_DOUBLE_MAX;
    this->Maxs[i] = -VTK_DOUBLE_MAX;
  }

  if (this->Ranges->size() < static_cast<unsigned int>(this->N))
  { // ranges not specified
    if (this->IndependentVariables == VTK_IV_COLUMN)
    {
      k = 0;
      for (j = 0; j < numComponents; j++)
      {
        int array_comp, array_index;
        array_index = field->GetArrayContainingComponent(j, array_comp);
        if (array_index < 0 || !field->GetArray(array_index))
        {
          // non-numeric component, simply skip it.
          continue;
        }
        for (i = 0; i < numRows; i++)
        {
          // v = field->GetComponent(i,j);
          ::vtkSpiderPlotActorGetComponent(field, i, j, &v);
          this->Mins[k] = std::min(v, this->Mins[k]);
          this->Maxs[k] = std::max(v, this->Maxs[k]);
        }
        k++;
      }
    }
    else // row
    {
      for (j = 0; j < numRows; j++)
      {
        for (i = 0; i < numComponents; i++)
        {
          // v = field->GetComponent(j,i);
          if (::vtkSpiderPlotActorGetComponent(field, j, i, &v) == 0)
          {
            // non-numeric component, simply skip.
            continue;
          }
          this->Mins[j] = std::min(v, this->Mins[j]);
          this->Maxs[j] = std::max(v, this->Maxs[j]);
        }
      }
    }
    for (i = 0; i < this->N; i++)
    {
      this->Ranges->push_back(vtkAxisRange(this->Mins[i], this->Maxs[i]));
    }
  }    // if automatic range computation
  else // specified correctly
  {
    vtkAxisRange range;
    for (i = 0; i < this->N; i++)
    {
      range = this->Ranges->at(i);
      this->Mins[i] = range.Min;
      this->Maxs[i] = range.Max;
    }
  }

  // Get the location of the corners of the box
  double* p1 = this->PositionCoordinate->GetComputedDoubleViewportValue(viewport);
  double* p2 = this->Position2Coordinate->GetComputedDoubleViewportValue(viewport);
  this->P1[0] = (p1[0] < p2[0] ? p1[0] : p2[0]);
  this->P1[1] = (p1[1] < p2[1] ? p1[1] : p2[1]);
  this->P2[0] = (p1[0] > p2[0] ? p1[0] : p2[0]);
  this->P2[1] = (p1[1] > p2[1] ? p1[1] : p2[1]);
  p1 = this->P1;
  p2 = this->P2;

  // Create the spider web
  // Determine the center of the spider plot. Leave room for the title and the legend
  double titleSpace = 0.0, legendSpace = 0.0;
  if (this->TitleVisibility)
  {
    titleSpace = 0.1;
  }
  if (this->LegendVisibility)
  {
    legendSpace = 0.15;
  }

  double d1 = p2[0] - legendSpace * (p2[0] - p1[0]) - p1[0];
  double d2 = p2[1] - titleSpace * (p2[1] - p1[1]) - p1[1];

  this->Center[0] = p1[0] + d1 / 2.0;
  this->Center[1] = p1[1] + d2 / 2.0;
  this->Center[2] = 0.0;
  this->Radius = (d1 < d2 ? d1 : d2);
  this->Radius /= 2.0;

  // Now generate the web points
  this->WebData->Initialize(); // remove old polydata, if any
  vtkPoints* webPts = vtkPoints::New();
  webPts->Allocate(this->NumberOfRings * VTK_RING_PTS);
  vtkCellArray* webLines = vtkCellArray::New();
  webLines->AllocateEstimate(this->N + this->NumberOfRings, VTK_RING_PTS);
  this->WebData->SetPoints(webPts);
  this->WebData->SetLines(webLines);
  vtkIdType ptId, pIds[VTK_RING_PTS + 1];
  double currentTheta, x[3];
  x[2] = 0.0;

  // Specify the positions for the axes
  this->Theta = 2.0 * vtkMath::Pi() / this->N;
  pIds[0] = webPts->InsertNextPoint(this->Center);
  for (i = 0; i < this->N; i++)
  {
    x[0] = this->Center[0] + this->Radius * cos(i * this->Theta);
    x[1] = this->Center[1] + this->Radius * sin(i * this->Theta);
    pIds[1] = webPts->InsertNextPoint(x);
    webLines->InsertNextCell(2, pIds);
  }
  if (this->NumberOfRings > 0)
  {
    double currentRadius, deltaRadius = this->Radius / this->NumberOfRings;
    currentTheta = 2.0 * vtkMath::Pi() / VTK_RING_PTS;
    for (i = 0; i < this->NumberOfRings; i++)
    {
      currentRadius = static_cast<double>(i + 1) * deltaRadius;
      for (j = 0; j < VTK_RING_PTS; j++)
      {
        x[0] = this->Center[0] + currentRadius * cos(j * currentTheta);
        x[1] = this->Center[1] + currentRadius * sin(j * currentTheta);
        pIds[j] = webPts->InsertNextPoint(x);
      }
      pIds[VTK_RING_PTS] = pIds[0];
      webLines->InsertNextCell(VTK_RING_PTS + 1, pIds);
    }
  }

  // Produce labels around the rim of the plot
  int minFontSize = 1000, fontSize, tsize[2];
  if (this->LabelVisibility)
  {
    this->LabelActors = new vtkActor2D*[this->N];
    this->LabelMappers = new vtkTextMapper*[this->N];
    char label[1024];
    const char* str;
    for (i = 0; i < this->N; i++)
    {
      x[0] = this->Center[0] + (this->Radius + 5) * cos(i * this->Theta);
      x[1] = this->Center[1] + (this->Radius + 5) * sin(i * this->Theta);
      this->LabelMappers[i] = vtkTextMapper::New();
      if ((str = this->GetAxisLabel(i)) != nullptr)
      {
        this->LabelMappers[i]->SetInput(str);
      }
      else
      {
        auto result = vtk::format_to_n(label, sizeof(label), "{:d}", i);
        *result.out = '\0';
        this->LabelMappers[i]->SetInput(label);
      }
      this->LabelMappers[i]->GetTextProperty()->ShallowCopy(this->LabelTextProperty);
      tsize[0] = static_cast<int>(0.15 * d1);
      tsize[1] = static_cast<int>(0.15 * d2);
      fontSize = this->LabelMappers[i]->SetConstrainedFontSize(viewport, tsize[0], tsize[1]);
      minFontSize = (fontSize < minFontSize ? fontSize : minFontSize);

      this->LabelActors[i] = vtkActor2D::New();
      this->LabelActors[i]->SetMapper(this->LabelMappers[i]);
      this->LabelActors[i]->GetPositionCoordinate()->SetCoordinateSystemToViewport();
      this->LabelActors[i]->SetPosition(x);
      // depending on the qudrant, the text is aligned differently
      if (x[0] >= this->Center[0] && x[1] >= this->Center[1])
      {
        this->LabelMappers[i]->GetTextProperty()->SetJustificationToLeft();
        this->LabelMappers[i]->GetTextProperty()->SetVerticalJustificationToBottom();
      }
      else if (x[0] < this->Center[0] && x[1] >= this->Center[1])
      {
        this->LabelMappers[i]->GetTextProperty()->SetJustificationToRight();
        this->LabelMappers[i]->GetTextProperty()->SetVerticalJustificationToBottom();
      }
      else if (x[0] < this->Center[0] && x[1] < this->Center[1])
      {
        this->LabelMappers[i]->GetTextProperty()->SetJustificationToRight();
        this->LabelMappers[i]->GetTextProperty()->SetVerticalJustificationToTop();
      }
      else if (x[0] >= this->Center[0] && x[1] < this->Center[1])
      {
        this->LabelMappers[i]->GetTextProperty()->SetJustificationToLeft();
        this->LabelMappers[i]->GetTextProperty()->SetVerticalJustificationToTop();
      }
    } // for all axes
    // Now reset font sizes to the same value
    for (i = 0; i < this->N; i++)
    {
      this->LabelMappers[i]->GetTextProperty()->SetFontSize(minFontSize);
    }
  }

  // Now generate the lines to plot
  this->PlotData->Initialize(); // remove old polydata, if any
  vtkPoints* pts = vtkPoints::New();
  pts->Allocate(numRows * numColumns);
  vtkCellArray* lines = vtkCellArray::New();
  vtkUnsignedCharArray* colors = vtkUnsignedCharArray::New();
  colors->SetNumberOfComponents(3);
  this->PlotData->SetPoints(pts);
  this->PlotData->SetLines(lines);
  this->PlotData->GetCellData()->SetScalars(colors);
  colors->Delete();

  this->LegendActor->GetProperty()->DeepCopy(this->GetProperty());

  double r, *color;
  vtkIdType firstId = 0;
  char buf[1024];
  if (this->IndependentVariables == VTK_IV_COLUMN)
  {
    lines->AllocateEstimate(numRows, numColumns);
    for (j = 0; j < numRows; j++)
    {
      lines->InsertNextCell(numColumns + 1);
      color = this->LegendActor->GetEntryColor(j);
      colors->InsertNextTuple3(255 * color[0], 255 * color[1], 255 * color[2]);
      this->LegendActor->SetEntrySymbol(j, this->GlyphSource->GetOutput());
      auto result = vtk::format_to_n(buf, sizeof(buf), "{:d}", j);
      *result.out = '\0';
      this->LegendActor->SetEntryString(j, buf);
      for (i = 0, k = 0; i < numColumns && k < numComponents; k++)
      {
        if (::vtkSpiderPlotActorGetComponent(field, j, k, &v) == 0)
        {
          // skip non-numeric components.
          continue;
        }
        if ((this->Maxs[i] - this->Mins[i]) == 0.0)
        {
          r = 0.0;
        }
        else
        {
          r = this->Radius * (v - this->Mins[i]) / (this->Maxs[i] - this->Mins[i]);
        }
        x[0] = this->Center[0] + r * cos(i * this->Theta);
        x[1] = this->Center[1] + r * sin(i * this->Theta);
        ptId = pts->InsertNextPoint(x);
        firstId = (i == 0 ? ptId : firstId);
        lines->InsertCellPoint(ptId);
        i++;
      }
      lines->InsertCellPoint(firstId);
    }
  }
  else // row
  {
    lines->AllocateEstimate(numColumns, numRows);
    for (j = 0; j < numComponents; j++)
    {
      int array_comp;
      int array_index = field->GetArrayContainingComponent(j, array_comp);
      if (!field->GetArray(array_index))
      {
        // non-numeric component, skip it.
        continue;
      }
      lines->InsertNextCell(numColumns + 1);
      color = this->LegendActor->GetEntryColor(j);
      colors->InsertNextTuple3(255 * color[0], 255 * color[1], 255 * color[2]);
      this->LegendActor->SetEntrySymbol(j, this->GlyphSource->GetOutput());
      auto result = vtk::format_to_n(buf, sizeof(buf), "{:d}", j);
      *result.out = '\0';
      this->LegendActor->SetEntryString(j, buf);
      for (i = 0; i < numRows; i++)
      {
        vtkSpiderPlotActorGetComponent(field, i, j, &v);
        if ((this->Maxs[i] - this->Mins[i]) == 0.0)
        {
          r = 0.0;
        }
        else
        {
          r = this->Radius * (v - this->Mins[i]) / (this->Maxs[i] - this->Mins[i]);
        }
        x[0] = this->Center[0] + r * cos(i * this->Theta);
        x[1] = this->Center[1] + r * sin(i * this->Theta);
        ptId = pts->InsertNextPoint(x);
        firstId = (i == 0 ? ptId : firstId);
        lines->InsertCellPoint(ptId);
      }
      lines->InsertCellPoint(firstId);
    }
  }

  // Display the legend
  if (this->LegendVisibility)
  {
    this->LegendActor->GetPositionCoordinate()->SetValue(
      p1[0] + 0.85 * (p2[0] - p1[0]), p1[1] + 0.20 * (p2[1] - p1[1]));
    this->LegendActor->GetPosition2Coordinate()->SetValue(p2[0], p1[1] + 0.80 * (p2[1] - p1[1]));
  }

  // Build title
  this->TitleMapper->SetInput(this->Title);
  if (this->TitleTextProperty->GetMTime() > this->BuildTime)
  {
    // Shallow copy here since the justification is changed but we still
    // want to allow actors to share the same text property, and in that case
    // specifically allow the title and label text prop to be the same.
    this->TitleMapper->GetTextProperty()->ShallowCopy(this->TitleTextProperty);
    this->TitleMapper->GetTextProperty()->SetJustificationToCentered();
  }

  // We could do some caching here, but hey, that's just the title
  tsize[0] = static_cast<int>(0.25 * d1);
  tsize[1] = static_cast<int>(0.15 * d2);
  this->TitleMapper->SetConstrainedFontSize(viewport, tsize[0], tsize[1]);

  this->TitleActor->GetPositionCoordinate()->SetValue(
    this->Center[0], this->Center[1] + this->Radius + tsize[1]);

  // Clean up
  webPts->Delete();
  webLines->Delete();
  pts->Delete();
  lines->Delete();

  return 1;
}
#undef VTK_RING_PTS

//------------------------------------------------------------------------------
// Release any graphics resources that are being consumed by this actor.
// The parameter window could be used to determine which graphic
// resources to release.
void vtkSpiderPlotActor::ReleaseGraphicsResources(vtkWindow* win)
{
  this->TitleActor->ReleaseGraphicsResources(win);
  this->LegendActor->ReleaseGraphicsResources(win);
  this->WebActor->ReleaseGraphicsResources(win);
  this->PlotActor->ReleaseGraphicsResources(win);
  for (int i = 0; this->LabelActors && i < this->N; i++)
  {
    this->LabelActors[i]->ReleaseGraphicsResources(win);
  }
}

//------------------------------------------------------------------------------
void vtkSpiderPlotActor::SetAxisLabel(int i, const char* label)
{
  if (i < 0)
  {
    return;
  }

  if (static_cast<unsigned int>(i) >= this->Labels->size())
  {
    this->Labels->resize(i + 1);
  }
  (*this->Labels)[i] = std::string(label);
  this->Modified();
}

//------------------------------------------------------------------------------
const char* vtkSpiderPlotActor::GetAxisLabel(int i)
{
  if (i < 0)
  {
    return nullptr;
  }

  return this->Labels->at(i).c_str();
}

//------------------------------------------------------------------------------
int vtkSpiderPlotActor::GetNumberOfAxisLabels()
{
  return static_cast<int>(this->Labels->size());
}

//------------------------------------------------------------------------------
void vtkSpiderPlotActor::SetAxisRange(int i, double min, double max)
{
  if (i < 0)
  {
    return;
  }

  if (static_cast<unsigned int>(i) >= this->Ranges->size())
  {
    this->Ranges->resize(i + 1);
  }
  (*this->Ranges)[i] = vtkAxisRange(min, max);
  this->Modified();
}

//------------------------------------------------------------------------------
void vtkSpiderPlotActor::SetAxisRange(int i, double range[2])
{
  this->SetAxisRange(i, range[0], range[1]);
}

//------------------------------------------------------------------------------
void vtkSpiderPlotActor::GetAxisRange(int i, double range[2])
{
  if (i < 0)
  {
    return;
  }

  vtkAxisRange arange = this->Ranges->at(i);
  range[0] = arange.Min;
  range[1] = arange.Max;
}

//------------------------------------------------------------------------------
int vtkSpiderPlotActor::GetNumberOfAxisRanges()
{
  return static_cast<int>(this->Ranges->size());
}

//------------------------------------------------------------------------------
void vtkSpiderPlotActor::SetPlotColor(int i, double r, double g, double b)
{
  this->LegendActor->SetEntryColor(i, r, g, b);
}

//------------------------------------------------------------------------------
double* vtkSpiderPlotActor::GetPlotColor(int i)
{
  return this->LegendActor->GetEntryColor(i);
}

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

  os << indent << "Input: " << this->GetInput() << "\n";

  os << indent << "Number Of Independent Variables: " << this->N << "\n";
  os << indent << "Independent Variables: ";
  if (this->IndependentVariables == VTK_IV_COLUMN)
  {
    os << "Columns\n";
  }
  else
  {
    os << "Rows\n";
  }

  os << indent << "Title Visibility: " << (this->TitleVisibility ? "On\n" : "Off\n");

  os << indent << "Title: " << (this->Title ? this->Title : "(none)") << "\n";

  if (this->TitleTextProperty)
  {
    os << indent << "Title Text Property:\n";
    this->TitleTextProperty->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << indent << "Title Text Property: (none)\n";
  }

  os << indent << "Label Visibility: " << (this->LabelVisibility ? "On\n" : "Off\n");

  if (this->LabelTextProperty)
  {
    os << indent << "Label Text Property:\n";
    this->LabelTextProperty->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << indent << "Label Text Property: (none)\n";
  }

  os << indent << "Number of Rings: " << this->NumberOfRings << "\n";

  os << indent << "Legend Visibility: " << (this->LegendVisibility ? "On\n" : "Off\n");

  os << indent << "Legend Actor: " << this->LegendActor << "\n";
  this->LegendActor->PrintSelf(os, indent.GetNextIndent());
}
VTK_ABI_NAMESPACE_END
