vtkPlotParallelCoordinates.cxx 14.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkPlotParallelCoordinates.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 "vtkPlotParallelCoordinates.h"

#include "vtkChartParallelCoordinates.h"
#include "vtkContext2D.h"
#include "vtkAxis.h"
#include "vtkPen.h"
#include "vtkFloatArray.h"
23
#include "vtkDoubleArray.h"
24 25 26 27 28 29 30
#include "vtkVector.h"
#include "vtkTransform2D.h"
#include "vtkContextDevice2D.h"
#include "vtkContextMapper2D.h"
#include "vtkTable.h"
#include "vtkDataArray.h"
#include "vtkIdTypeArray.h"
31
#include "vtkStringArray.h"
32 33
#include "vtkTimeStamp.h"
#include "vtkInformation.h"
34
#include "vtkSmartPointer.h"
35 36
#include "vtkUnsignedCharArray.h"
#include "vtkLookupTable.h"
37 38 39

// Need to turn some arrays of strings into categories
#include "vtkStringToCategory.h"
40 41 42

#include "vtkObjectFactory.h"

43 44
#include <vector>
#include <algorithm>
45 46

class vtkPlotParallelCoordinates::Private :
47
    public std::vector< std::vector<float> >
48 49 50 51 52 53 54
{
public:
  Private()
  {
    this->SelectionInitialized = false;
  }

55
  std::vector<float> AxisPos;
56 57 58 59 60
  bool SelectionInitialized;
};


//-----------------------------------------------------------------------------
61
vtkStandardNewMacro(vtkPlotParallelCoordinates)
62 63 64 65 66

//-----------------------------------------------------------------------------
vtkPlotParallelCoordinates::vtkPlotParallelCoordinates()
{
  this->Storage = new vtkPlotParallelCoordinates::Private;
67
  this->Pen->SetColor(0, 0, 0, 25);
68

69 70
  this->LookupTable = nullptr;
  this->Colors = nullptr;
71
  this->ScalarVisibility = 0;
72 73 74 75 76
}

//-----------------------------------------------------------------------------
vtkPlotParallelCoordinates::~vtkPlotParallelCoordinates()
{
77 78
  delete this->Storage;
  if (this->LookupTable)
79
  {
80
    this->LookupTable->UnRegister(this);
81
  }
82
  if ( this->Colors != nullptr )
83
  {
84
    this->Colors->UnRegister(this);
85
  }
86 87 88 89 90 91
}

//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::Update()
{
  if (!this->Visible)
92
  {
93
    return;
94
  }
95 96 97
  // Check if we have an input
  vtkTable *table = this->Data->GetInput();
  if (!table)
98
  {
99 100
    vtkDebugMacro(<< "Update event called with no input table set.");
    return;
101
  }
102 103 104 105 106 107 108 109 110 111 112

  this->UpdateTableCache(table);
}

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

  if (!this->Visible)
113
  {
114
    return false;
115
  }
116 117 118

  painter->ApplyPen(this->Pen);

119
  if (this->Storage->empty())
120
  {
121
    return false;
122
  }
123 124 125 126 127 128

  size_t cols = this->Storage->size();
  size_t rows = this->Storage->at(0).size();
  vtkVector2f* line = new vtkVector2f[cols];

  // Update the axis positions
129 130 131
  vtkChartParallelCoordinates *parent =
      vtkChartParallelCoordinates::SafeDownCast(this->Parent);

132
  for (size_t i = 0; i < cols; ++i)
133
  {
134 135
    this->Storage->AxisPos[i] = parent->GetAxis(int(i)) ?
                                parent->GetAxis(int(i))->GetPoint1()[0] :
136
                                0;
137
  }
138 139 140 141 142

  vtkIdType selection = 0;
  vtkIdType id = 0;
  vtkIdType selectionSize = 0;
  if (this->Selection)
143
  {
144 145
    selectionSize = this->Selection->GetNumberOfTuples();
    if (selectionSize)
146
    {
147
      this->Selection->GetTypedTuple(selection, &id);
148
    }
149
  }
150 151

  // Draw all of the lines
152
  painter->ApplyPen(this->Pen);
153
  int ncComps(0);
154
  if (this->ScalarVisibility && this->Colors)
155
  {
156
    ncComps = static_cast<int>(this->Colors->GetNumberOfComponents());
157
  }
158
  if (this->ScalarVisibility && this->Colors && ncComps == 4)
159
  {
160
    for (size_t i = 0, nc = 0; i < rows; ++i, nc += ncComps)
161
    {
162
      for (size_t j = 0; j < cols; ++j)
163
      {
164
        line[j].Set(this->Storage->AxisPos[j], (*this->Storage)[j][i]);
165
      }
166
      painter->GetPen()->SetColor(this->Colors->GetPointer(static_cast<vtkIdType>(nc)));
167
      painter->DrawPoly(line[0].GetData(), static_cast<int>(cols));
168
    }
169
  }
170
  else
171
  {
172
    for (size_t i = 0; i < rows; ++i)
173
    {
174
      for (size_t j = 0; j < cols; ++j)
175
      {
176
        line[j].Set(this->Storage->AxisPos[j], (*this->Storage)[j][i]);
177
      }
178
      painter->DrawPoly(line[0].GetData(), static_cast<int>(cols));
179
    }
180
  }
181 182 183

  // Now draw the selected lines
  if (this->Selection)
184
  {
185
    painter->GetPen()->SetColor(255, 0, 0, 100);
186
    for (vtkIdType i = 0; i < this->Selection->GetNumberOfTuples(); ++i)
187
    {
188
      for (size_t j = 0; j < cols; ++j)
189
      {
190
        this->Selection->GetTypedTuple(i, &id);
191 192
        line[j].Set(this->Storage->AxisPos[j], (*this->Storage)[j][id]);
      }
193
      painter->DrawPoly(line[0].GetData(), static_cast<int>(cols));
194
    }
195
  }
196 197 198 199 200 201 202

  delete[] line;

  return true;
}

//-----------------------------------------------------------------------------
203 204
bool vtkPlotParallelCoordinates::PaintLegend(vtkContext2D *painter,
                                             const vtkRectf& rect, int)
205 206
{
  painter->ApplyPen(this->Pen);
207 208
  painter->DrawLine(rect[0]          , rect[1] + 0.5 * rect[3],
                    rect[0] + rect[2], rect[1] + 0.5 * rect[3]);
209 210 211 212
  return true;
}

//-----------------------------------------------------------------------------
213
void vtkPlotParallelCoordinates::GetBounds(double *)
214 215 216 217 218 219 220 221 222
{

}

//-----------------------------------------------------------------------------
bool vtkPlotParallelCoordinates::SetSelectionRange(int axis, float low,
                                                   float high)
{
  if (!this->Selection)
223
  {
224 225
    this->Storage->SelectionInitialized = false;
    this->Selection = vtkIdTypeArray::New();
226
  }
227

228
  if (this->Storage->SelectionInitialized)
229
  {
230 231
    // Further refine the selection that has already been made
    vtkIdTypeArray *array = vtkIdTypeArray::New();
232
    std::vector<float>& col = this->Storage->at(axis);
233
    for (vtkIdType i = 0; i < this->Selection->GetNumberOfTuples(); ++i)
234
    {
235
      vtkIdType id = 0;
236
      this->Selection->GetTypedTuple(i, &id);
237
      if (col[id] >= low && col[id] <= high)
238
      {
239 240 241
        // Remove this point - no longer selected
        array->InsertNextValue(id);
      }
242
    }
243 244
    this->Selection->DeepCopy(array);
    array->Delete();
245
  }
246
  else
247
  {
248
    // First run - ensure the selection list is empty and build it up
249
    std::vector<float>& col = this->Storage->at(axis);
250
    for (size_t i = 0; i < col.size(); ++i)
251
    {
252
      if (col[i] >= low && col[i] <= high)
253
      {
254
        // Remove this point - no longer selected
255
        this->Selection->InsertNextValue(static_cast<vtkIdType>(i));
256 257
      }
    }
258 259
    this->Storage->SelectionInitialized = true;
  }
260 261 262 263 264 265 266 267
  return true;
}

//-----------------------------------------------------------------------------
bool vtkPlotParallelCoordinates::ResetSelectionRange()
{
  this->Storage->SelectionInitialized = false;
  if (this->Selection)
268
  {
269
    this->Selection->SetNumberOfTuples(0);
270
  }
271 272 273
  return true;
}

274
//-----------------------------------------------------------------------------
275
void vtkPlotParallelCoordinates::SetInputData(vtkTable* table)
276
{
277 278
  if (table == this->Data->GetInput() && (!table ||
                                          table->GetMTime() < this->BuildTime))
279
  {
280
    return;
281
  }
282

283
  bool updateVisibility = table != this->Data->GetInput();
284
  this->vtkPlot::SetInputData(table);
285 286
  vtkChartParallelCoordinates *parent =
      vtkChartParallelCoordinates::SafeDownCast(this->Parent);
287

288
  if (parent && table && updateVisibility)
289
  {
290
    parent->SetColumnVisibilityAll(false);
291 292
    // By default make the first 10 columns visible in a plot.
    for (vtkIdType i = 0; i < table->GetNumberOfColumns() && i < 10; ++i)
293
    {
294
      parent->SetColumnVisibility(table->GetColumnName(i), true);
295
    }
296
  }
297
  else if (parent && updateVisibility)
298
  {
299
    // No table, therefore no visible columns
300
    parent->GetVisibleColumns()->SetNumberOfTuples(0);
301
  }
302 303
}

304 305 306 307
//-----------------------------------------------------------------------------
bool vtkPlotParallelCoordinates::UpdateTableCache(vtkTable *table)
{
  // Each axis is a column in our storage array, they are scaled from 0.0 to 1.0
308 309 310
  vtkChartParallelCoordinates *parent =
      vtkChartParallelCoordinates::SafeDownCast(this->Parent);
  if (!parent || !table || table->GetNumberOfColumns() == 0)
311
  {
312
    return false;
313
  }
314

315
  vtkStringArray* cols = parent->GetVisibleColumns();
316

317 318
  this->Storage->resize(cols->GetNumberOfTuples());
  this->Storage->AxisPos.resize(cols->GetNumberOfTuples());
319
  vtkIdType rows = table->GetNumberOfRows();
320 321

  for (vtkIdType i = 0; i < cols->GetNumberOfTuples(); ++i)
322
  {
323
    std::vector<float>& col = this->Storage->at(i);
324
    vtkAxis* axis = parent->GetAxis(i);
325
    col.resize(rows);
326
    vtkSmartPointer<vtkDataArray> data =
327
        vtkArrayDownCast<vtkDataArray>(table->GetColumnByName(cols->GetValue(i)));
328
    if (!data)
329
    {
330
      if (table->GetColumnByName(cols->GetValue(i))->IsA("vtkStringArray"))
331
      {
332 333
        // We have a different kind of column - attempt to make it into an enum
        vtkStringToCategory* stoc = vtkStringToCategory::New();
334
        stoc->SetInputData(table);
335 336 337 338 339 340 341 342
        stoc->SetInputArrayToProcess(0, 0, 0,
                                     vtkDataObject::FIELD_ASSOCIATION_ROWS,
                                     cols->GetValue(i));
        stoc->SetCategoryArrayName("enumPC");
        stoc->Update();
        vtkTable* table2 = vtkTable::SafeDownCast(stoc->GetOutput());
        vtkTable* stringTable = vtkTable::SafeDownCast(stoc->GetOutput(1));
        if (table2)
343
        {
344
          data = vtkArrayDownCast<vtkDataArray>(table2->GetColumnByName("enumPC"));
345
        }
346
        if (stringTable && stringTable->GetColumnByName("Strings"))
347
        {
348
          vtkStringArray* strings =
349
              vtkArrayDownCast<vtkStringArray>(stringTable->GetColumnByName("Strings"));
350 351 352
          vtkSmartPointer<vtkDoubleArray> arr =
              vtkSmartPointer<vtkDoubleArray>::New();
          for (vtkIdType j = 0; j < strings->GetNumberOfTuples(); ++j)
353
          {
354
            arr->InsertNextValue(j);
355
          }
356
          // Now we need to set the range on the string axis
357
          axis->SetCustomTickPositions(arr, strings);
358
          if (strings->GetNumberOfTuples() > 1)
359
          {
360
            axis->SetUnscaledRange(0.0, strings->GetNumberOfTuples()-1);
361
          }
362
          else
363
          {
364
            axis->SetUnscaledRange(-0.1, 0.1);
365
          }
366
          axis->Update();
367
        }
368 369
        stoc->Delete();
      }
370 371
      // If we still don't have a valid data array then skip this column.
      if (!data)
372
      {
373
        continue;
374
      }
375
    }
376 377

    // Also need the range from the appropriate axis, to normalize points
378 379 380
    double min = axis->GetUnscaledMinimum();
    double max = axis->GetUnscaledMaximum();
    double scale = 1.0f / (max - min);
381 382

    for (vtkIdType j = 0; j < rows; ++j)
383
    {
384
      col[j] = (data->GetTuple1(j) - min) * scale;
385
    }
386
  }
387

388
  // Additions for color mapping
389
  if (this->ScalarVisibility && !this->ColorArrayName.empty())
390
  {
391
    vtkDataArray* c =
392
      vtkArrayDownCast<vtkDataArray>(table->GetColumnByName(this->ColorArrayName));
393
    // TODO: Should add support for categorical coloring & try enum lookup
394 395 396
    if (this->Colors)
    {
      this->Colors->UnRegister(this);
397
      this->Colors = nullptr;
398
    }
399
    if (c)
400
    {
401
      if (!this->LookupTable)
402
      {
403
        this->CreateDefaultLookupTable();
404
      }
405 406 407 408
      this->Colors = this->LookupTable->MapScalars(c, VTK_COLOR_MODE_MAP_SCALARS, -1);
      // Consistent register and unregisters
      this->Colors->Register(this);
      this->Colors->Delete();
409 410
    }
  }
411

412 413 414 415
  this->BuildTime.Modified();
  return true;
}

416 417 418 419
//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::SetLookupTable(vtkScalarsToColors *lut)
{
  if ( this->LookupTable != lut )
420
  {
421
    if ( this->LookupTable)
422
    {
423
      this->LookupTable->UnRegister(this);
424
    }
425 426
    this->LookupTable = lut;
    if (lut)
427
    {
428 429
      lut->Register(this);
    }
430 431
    this->Modified();
  }
432 433 434 435 436
}

//-----------------------------------------------------------------------------
vtkScalarsToColors *vtkPlotParallelCoordinates::GetLookupTable()
{
437
  if ( this->LookupTable == nullptr )
438
  {
439
    this->CreateDefaultLookupTable();
440
  }
441 442 443 444 445 446 447
  return this->LookupTable;
}

//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::CreateDefaultLookupTable()
{
  if ( this->LookupTable)
448
  {
449
    this->LookupTable->UnRegister(this);
450
  }
451 452 453 454 455 456 457
  this->LookupTable = vtkLookupTable::New();
  // Consistent Register/UnRegisters.
  this->LookupTable->Register(this);
  this->LookupTable->Delete();
}

//-----------------------------------------------------------------------------
458
void vtkPlotParallelCoordinates::SelectColorArray(const vtkStdString &arrayName)
459 460 461
{
  vtkTable *table = this->Data->GetInput();
  if (!table)
462
  {
463 464
    vtkDebugMacro(<< "SelectColorArray called with no input table set.");
    return;
465
  }
466
  if (this->ColorArrayName == arrayName)
467
  {
468
    return;
469
  }
470
  for (vtkIdType c = 0; c < table->GetNumberOfColumns(); ++c)
471
  {
472
    if (table->GetColumnName(c) == arrayName)
473
    {
474
      this->ColorArrayName = arrayName;
475 476 477
      this->Modified();
      return;
    }
478
  }
479
  vtkDebugMacro(<< "SelectColorArray called with invalid column name.");
480
  this->ColorArrayName = "";
481 482 483
  this->Modified();
}

484 485 486 487 488 489
//-----------------------------------------------------------------------------
vtkStdString vtkPlotParallelCoordinates::GetColorArrayName()
{
  return this->ColorArrayName;
}

490 491 492 493 494
//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::SelectColorArray(vtkIdType arrayNum)
{
  vtkTable *table = this->Data->GetInput();
  if (!table)
495
  {
496 497
    vtkDebugMacro(<< "SelectColorArray called with no input table set.");
    return;
498
  }
499
  vtkDataArray *col = vtkArrayDownCast<vtkDataArray>(table->GetColumn(arrayNum));
500 501
  // TODO: Should add support for categorical coloring & try enum lookup
  if (!col)
502
  {
503 504
    vtkDebugMacro(<< "SelectColorArray called with invalid column index");
    return;
505
  }
506
  else
507
  {
508
    if (this->ColorArrayName == table->GetColumnName(arrayNum))
509
    {
510
      return;
511
    }
512
    else
513
    {
514
      this->ColorArrayName = table->GetColumnName(arrayNum);
515 516
      this->Modified();
    }
517
  }
518 519
}

520 521 522 523 524
//-----------------------------------------------------------------------------
void vtkPlotParallelCoordinates::PrintSelf(ostream &os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}