vtkAxisExtended.cxx 14.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkCellLocator.h

  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 "vtkAxisExtended.h"
17 18

#include "vtkMath.h" // for VTK_DBL_EPSILON
19 20 21
#include "vtkStdString.h"
#include "vtkObjectFactory.h"

22
#include <sstream>
23

24 25
#include <cmath>
#include <algorithm>
26 27 28


vtkStandardNewMacro(vtkAxisExtended);
29 30 31

vtkAxisExtended::vtkAxisExtended()
{
32 33 34 35 36 37 38
  this->FontSize = 0;
  this->DesiredFontSize = 10;
  this->Precision = 3;
  this->LabelFormat = 0;
  this->Orientation = 0;
  this->LabelLegibilityChanged = true;
  this->IsAxisVertical = false;
39 40
}

41
vtkAxisExtended::~vtkAxisExtended() = default;
42

43
// This method return a value to make step sizes corresponding to low q and j values more preferable
44 45
double vtkAxisExtended::Simplicity(int qIndex, int qLength, int j, double lmin,
                                   double lmax, double lstep)
46 47 48
{
  double eps = VTK_DBL_EPSILON * 100;
  int v = 1 ;
49
  ++qIndex;
50 51

  double rem = fmod(lmin,lstep);
52
  if((rem < eps || (lstep - rem ) < eps ) &&  lmin <= 0 && lmax >= 0)
53
  {
54
    v = 0;
55
  }
56
  else
57
  {
58
    v = 1;  // v is 1 is lebelling includes zero
59
  }
60

61
  return 1.0 - (qIndex - 1.0) / (qLength - 1.0) - j + v;
62 63
}

64 65
// This method returns the maximum possible value of simplicity value given q
// and j
66 67
double vtkAxisExtended::SimplicityMax(int qIndex, int qLength, int j)
{
68
  int v = 1;
69
  ++qIndex;
70
  return 1.0 - (qIndex - 1.0) / (qLength - 1.0) - j + v;
71 72
}

73 74 75 76
// This method makes the data range approximately same as the labeling range
// more preferable
double vtkAxisExtended::Coverage(double dmin, double dmax, double lmin,
                                 double lmax)
77
{
78 79
  double coverage = 1.0 - 0.5 * (pow(dmax - lmax, 2) + pow(dmin - lmin, 2)
                                 / pow(0.1 * (dmax - dmin), 2));
80 81 82 83 84 85 86 87 88
  return coverage;
}


//This gives the maximum possible value of coverage given the step size
double vtkAxisExtended::CoverageMax(double dmin, double dmax, double span)
{
  double range = dmax - dmin;
  if (span > range)
89
  {
90 91
    double half = (span - range)/2;
    return 1- 0.5 * (pow(half, 2) + pow(half, 2) / pow(0.1*(range),2));
92
  }
93
  else
94
  {
95
    return 1.0;
96
  }
97 98
}

99 100 101 102
// This method return a value to make the density of the labels close to the
// user given value
double vtkAxisExtended::Density(int k, double m, double dmin, double dmax,
                                double lmin, double lmax)
103 104 105 106
{
  double r = (k-1)/(lmax-lmin);
  double rt = (m-1) / (std::max(lmax,dmax) - std::min(dmin,lmin));

107
  return 2 - std::max(r/rt, rt/r);
108 109
}

110 111
// Derives the maximum values for density given k (number of ticks) and m
// (user given)
112 113 114
double vtkAxisExtended::DensityMax(int k, double m)
{
  if(k >= m)
115
  {
116
    return 2 - (k-1) / (m-1);
117
  }
118
  else
119
  {
120
    return 1;
121
  }
122 123 124
}

// This methods gives a weighing factor for each label depending on the range
125
// The coding for the different formats
126 127 128 129 130 131
//   1 - Scientific 5 * 10^6
//   2 - Decimal e.g. 5000
//   3 - K e.g. 5K
//   4 - Factored K e.g. 5(K)
//   5 - M e.g. 5M
//   6 - Factored M e.g. 5(M)
132
//   7 - Factored Decimals e.g. 5 (thousands)
133 134 135 136
//   8 - Factored Scientific 5 (10^6)
double vtkAxisExtended::FormatLegibilityScore(double n, int format)
{
  switch(format)
137
  {
138 139 140
    case 1:
      return 0.25;
    case 2:
141
      if(std::abs(n) > 0.0001 && std::abs(n) < 1000000)
142
      {
143
        return 1.0;
144
      }
145
      else
146
      {
147
        return 0.0;
148
      }
149
    case 3:
150
      if(std::abs(n) > 1000 &&  std::abs(n) < 1000000)
151
      {
152
        return 0.75;
153
      }
154
      else
155
      {
156
        return 0.0;
157
      }
158
    case 4:
159
      if(std::abs(n) > 1000 && std::abs(n) < 1000000)
160
      {
161
        return 0.4;
162
      }
163
      else
164
      {
165
        return 0.0;
166
      }
167
    case 5:
168
      if(std::abs(n) > 1000000 && std::abs(n) < 1000000000)
169
      {
170
        return 0.75;
171
      }
172
      else
173
      {
174
        return 0.0;
175
      }
176
    case 6:
177
      if(std::abs(n) > 1000000 && std::abs(n) < 1000000000)
178
      {
179
        return 0.4;
180
      }
181
      else
182
      {
183
        return 0.0;
184
      }
185 186 187 188 189 190
    case 7:
      return 0.5;
    case 8:
      return 0.3;
    default:
      return 0.0;
191
  }
192 193 194 195 196 197
}


// This method returns the length of the label given the format
int vtkAxisExtended::FormatStringLength(int format, double n, int precision)
{
198
  std::ostringstream ostr;
199
  ostr.imbue(std::locale::classic());
200
  int numSize(0);
201 202

  switch(format)
203
  {
204 205
    case 1:
      ostr.precision(precision);
206
      ostr.setf(std::ios::scientific, std::ios::floatfield);
207 208 209 210 211 212
      ostr<<n;
      numSize = (int) ostr.str().length();
      return numSize;
    case 2:
      ostr << n;
      if((std::ceil(n)-std::floor(n)) != 0.0 )
213
      {
214
        ostr.precision(precision);
215
      }
216 217 218
      // Gets the length of the string with the current format without the end
      // character
      numSize = (int) ostr.str().length()-1;
219 220 221 222 223
      return numSize;
    case 3:
      ostr.setf(ios::fixed, ios::floatfield);
      ostr << n/1000;
      if((std::ceil(n/1000.0)-std::floor(n/1000.0)) != 0.0 )
224
      {
225
        ostr.precision(precision);
226
      }
227 228 229 230 231
      numSize = (int) ostr.str().length()-1;
      return numSize+1; // minus three zeros + K
    case 4:
      ostr.setf(ios::fixed, ios::floatfield);
      ostr << n/1000;
232
      if((std::ceil(n/1000.0)-std::floor(n/1000.0)) != 0.0)
233
      {
234
        ostr.precision(precision);
235
      }
236
      numSize = static_cast<int>(ostr.str().length() - 1);
237 238 239 240
      return numSize; // minus three zeros
    case 5:
      ostr.setf(ios::fixed, ios::floatfield);
      ostr << n/1000000;
241
      if((std::ceil(n/1000000.0) - std::floor(n/1000000.0)) != 0.0)
242
      {
243
        ostr.precision(precision);
244
      }
245
      numSize = (int) ostr.str().length()-1;
246
      return numSize; // minus six zeros
247 248 249 250
    case 6:
      ostr.setf(ios::fixed, ios::floatfield);
      ostr << n/1000000;
      if((std::ceil(n/1000000.0)-std::floor(n/1000000.0)) != 0.0 )
251
      {
252
        ostr.precision(precision);
253
      }
254 255 256 257 258 259
      numSize = (int) ostr.str().length()-1;
      return numSize+1; // minus six zeros + M
    case 7:
      ostr.setf(ios::fixed, ios::floatfield);
      ostr << n/1000;
      if((std::ceil(n/1000.0)-std::floor(n/1000.0)) != 0.0 )
260
      {
261
        ostr.precision(precision);
262
      }
263 264 265 266
      numSize = (int) ostr.str().length()-1;
      return numSize;  // Three 0's get reduced
    case 8:
      ostr.precision(precision);
267
      ostr.setf(std::ios::scientific, std::ios::floatfield);
268 269 270 271 272
      ostr<<n/1000;
      numSize = (int) ostr.str().length();
      return numSize;
    default:
      return 0;
273
  }
274 275
}

276 277
// This methods determines the optimum notation, font size and orientation of
// labels from an exhaustive search
278 279 280
double vtkAxisExtended::Legibility(double lmin, double lmax, double lstep,
                                   double scaling,
                                   vtkVector<int, 3>& parameters)
281
{
282
  int numTicks = static_cast<int>((lmax - lmin) / lstep);
283
  double* tickPositions = new double[numTicks];
284 285
  int fontSizes[8] = { 8, 9, 10, 12, 14, 18, 20, 24 };
  for(int i = 0; i< numTicks; ++i)
286
  {
287
    tickPositions[i] = lmax + i*lstep;
288
  }
289

290
 // this->LabelLegibilityChanged = true;
291 292 293 294 295 296
  int bestFormat = 1;
  int bestOrientation = 0;
  int bestFontSize = this->DesiredFontSize;

  double bestLegScore = 0.0;

297
  for(int iFormat = 1; iFormat < 9; ++iFormat)
298
  {
299
    double formatLegSum = 0.0;
300
    for(int i = 0; i<numTicks; ++i)
301
    {
302
      formatLegSum += FormatLegibilityScore(tickPositions[i], iFormat);
303
    }
304

305 306
    // Average of label legibility scores
    formatLegSum = formatLegSum / numTicks;
307 308 309 310

    double eps = VTK_DBL_EPSILON * 100;
    int v = 1 ;
    double rem = fmod(lmin,lstep);
311
    if((rem < eps || (lstep - rem ) < eps ) &&  lmin <=0 && lmax >=0)
312
    {
313
      v = 0;
314
    }
315
    else
316
    {
317
      v = 1;  // v is 1 is lebelling includes zero
318
    }
319

320 321
    formatLegSum = 0.9 * formatLegSum + 0.1 * v;

322
    double fontLegSum = 0.0;
323 324 325

    // 8 font sizes are checked
    for (int fontIndex = 0; fontIndex < 8 ; ++fontIndex)
326
    {
327 328
      int iFont = fontSizes[fontIndex];
      if(iFont == this->DesiredFontSize)
329
      {
330
        fontLegSum = 1.0;
331
      }
332 333
      // fontSizes[0] is the minimum font size
      else if ( iFont<this->DesiredFontSize && iFont >= fontSizes[0])
334
      {
335 336
        fontLegSum = 0.2 * (iFont - fontSizes[0] + 1)
            / (this->DesiredFontSize - fontSizes[0]);
337
      }
338
      else
339
      {
340
        fontLegSum = -100.0;
341
      }
342

343
      for(int iOrientation = 0 ; iOrientation <2 ; ++iOrientation)
344
      {
345
        double orientLegSum = (iOrientation == 0) ? 1 : -0.5;
346
        // Here the gap between two consecutive labels is calculated as:
347 348
        // 2*Actual distance (in pixels) among two ticks - string lengths of
        // the two largest labels
349 350
        double overlapLegSum = 1.0;

351
        double legScore = (formatLegSum + fontLegSum + orientLegSum
352
                           + overlapLegSum) / 4;
353
        if(legScore > bestLegScore )
354
        {
355
          if(numTicks>1)
356
          {
357
            double fontExtent;
358 359
            if((this->IsAxisVertical && iOrientation) ||
               (!this->IsAxisVertical && !iOrientation) )
360
            {
361 362 363 364 365
              fontExtent =
                  (FormatStringLength(iFormat,tickPositions[numTicks-1],
                                      this->Precision) +
                   FormatStringLength(iFormat,tickPositions[numTicks-2],
                                      this->Precision))*iFont;
366
            }
367
            else
368
            {
369
              fontExtent = iFont * 2;
370
            }
371 372
            double tickDistance = lstep * scaling;
            double labelingGap= 2*(tickDistance) - fontExtent;
373 374
            // 1.1 for line spacing
            overlapLegSum = std::min(1.0,2 - 3* iFont *1.1 / labelingGap );
375 376 377 378 379 380 381 382
            /*if(labelingGap > 3*iFont)
              {
              overlapLegSum = 1.0;
              }
            else if(labelingGap < 3*iFont && labelingGap > 0)
              {
              overlapLegSum = 2 - 3* iFont / labelingGap ;
              }
383
            else
384 385 386
              {
              overlapLegSum = -100;
              }*/
387
          }
388

389 390
          legScore = (formatLegSum + fontLegSum + orientLegSum +
                      overlapLegSum)/4;
391 392

          if ( legScore > bestLegScore)
393
          {
394 395 396 397 398 399 400
            bestFormat = iFormat;
            bestOrientation = iOrientation;
            bestFontSize = iFont;
            bestLegScore = legScore;
          }
        }
      }
401
    }
402
  }
403

404 405 406
  parameters[0] = bestFormat;
  parameters[1] = bestFontSize;
  parameters[2] = bestOrientation;
407
  delete [] tickPositions;
408
  return bestLegScore;
409 410 411
}

// This method implements the algorithm given in the paper
412 413 414
vtkVector3d vtkAxisExtended::GenerateExtendedTickLabels(double dmin,
                                                        double dmax, double m,
                                                        double scaling)
415 416 417 418 419 420 421 422
{
  double Q[] = {1, 5, 2, 2.5, 4, 3};
  double w[] = {0.25, 0.2, 0.5, 0.05};
  double eps = VTK_DBL_EPSILON * 100;
  vtkVector3d ans;

  this->LabelLegibilityChanged = false;
  if(dmin > dmax)
423
  {
424 425 426
    double temp = dmin;
    dmin = dmax;
    dmax = temp;
427
  }
428 429

  if( dmax - dmin < eps)
430
  {
431 432 433
    ans[0] = dmin; ans[1]= dmax; ans[2]= m;
    //return Sequence(dmin,dmax, m);
    return ans;
434
  }
435 436 437

  int qLength = 6;//Q.Length(); // Hard Coded

438
  //list<double> best;
439
  double bestScore = -2;
440
  double bestLmin(0), bestLmax(0), bestLstep(0);
441 442 443

  const int INF = 100; //INT_MAX;  Again 100 is hard coded

444
  int j = 1;
445
  while(j < INF)
446
  {
447
    for(int qIndex = 0; qIndex < qLength; ++qIndex)
448
    {
449 450
      double sm = SimplicityMax(qIndex, qLength, j);
      if((w[0]*sm + w[1] + w[2] + w[3]) < bestScore)
451
      {
452 453
        j = INF;
        break;
454
      }
455 456 457

      int k = 2;
      while(k < INF)
458
      {
459 460
        double dm = DensityMax(k,m);
        if((w[0]*sm + w[1] + w[2]*dm + w[3]) < bestScore)
461
        {
462
          break;
463
        }
464 465
        double delta = (dmax- dmin)/((k+1)*j*Q[qIndex]) ;
        double z = ceil(log10(delta));
466
        while(z < INF)
467
        {
David Partyka's avatar
David Partyka committed
468
          double step = j*Q[qIndex]*pow(10.0,z);
469
          //double cm = CoverageMax(dmin, dmax, step*(k-1));
470
          if((w[0]*sm + w[1] + w[2]*dm + w[3]) < bestScore)
471
          {
472
            break;
473
          }
474

475 476
          int minStart = static_cast<int>(std::floor(dmax / step) * j - (k-1) * j);
          int maxStart = static_cast<int>(std::ceil(dmin/step) * j);
477 478

          if(minStart > maxStart)
479
          {
480
            ++z;
481
            continue;
482
          }
483

484
          for(int start = minStart; start <= maxStart; ++start)
485
          {
486 487 488 489 490 491 492 493 494 495 496 497 498
            double lmin = start * (step/j);
            double lmax = lmin + step*(k-1);
            double lstep = step;

            double s = Simplicity(qIndex, qLength, j, lmin, lmax, lstep);
            double c = Coverage(dmin, dmax, lmin, lmax);
            double g = Density(k,m,dmin, dmax, lmin, lmax);

            double score = w[0]*s + w[1]*c + w[2]*g + w[3];

            if(score < bestScore)
               continue;

499
            //vtkVector<double,4> l = this->Legibility(lmin, lmax, lstep, scaling);
500

501 502 503 504 505
            vtkVector<int, 3> legibilityIndex;
            double newScore = this->Legibility(lmin, lmax, lstep, scaling,
                                               legibilityIndex);

            score = w[0] * s + w[1] * c + w[2] * g + w[3] * newScore;
506 507

            if(score > bestScore)
508
            {
509 510 511 512
              bestScore = score;
              bestLmin = lmin;
              bestLmax = lmax;
              bestLstep = lstep;
513 514 515
              this->LabelFormat = legibilityIndex[0]; // label format
              this->FontSize = legibilityIndex[1]; // label font size
              this->Orientation = legibilityIndex[2]; // label orientation
516 517
            }
          }
518
          ++z;
519
        }
520
        ++k;
521 522
      }
    }
523 524
    ++j;
  }
525 526 527 528 529 530
  ans[0] = bestLmin;
  ans[1] = bestLmax;
  ans[2] = bestLstep;
  //vtkVector3d answers(bestLmin, bestLmax, bestLstep);
  // return Sequence(bestLmin, bestLmax, bestLstep);
  return ans;
531
}
532 533 534 535 536 537 538 539 540 541

void vtkAxisExtended::PrintSelf(ostream &os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Orientation: " << this->Orientation << endl;
  os << indent << "FontSize: " << this->FontSize << endl;
  os << indent << "DesiredFontSize: " << this->DesiredFontSize << endl;
  os << indent << "Precision: " << this->Precision << endl;
  os << indent << "LabelFormat: " << this->LabelFormat << endl;
}