Commit d73cc74b authored by Tharindu De Silva's avatar Tharindu De Silva Committed by Marcus D. Hanwell
Browse files

ENH: Improved tick label positioning in charts.

Implemented the tick label positioning described in the VisWeek 2010
paper "An Extension of Wilkinson's Algorithm for Positioning Tick Labels
on Axes".

Change-Id: Ia5b9df612e8a8753e7f605ef634fd4409d2b42f7
parent ef91d70c
......@@ -16,6 +16,7 @@ SET(Kit_SRCS
vtkAbstractContextBufferId.cxx
vtkAbstractContextItem.cxx
vtkAxis.cxx
vtkAxisExtended.cxx
vtkBlockItem.cxx
vtkBrush.cxx
vtkChart.cxx
......
......@@ -24,6 +24,7 @@ IF(VTK_USE_RENDERING AND VTK_USE_VIEWS)
TestHistogram2D.cxx
TestLegendHiddenPlots.cxx
TestLinePlot.cxx
TestLinePlot2.cxx
TestLinePlotInteraction.cxx
TestMultipleChartRenderers.cxx
TestMultipleRenderers.cxx
......@@ -46,6 +47,7 @@ IF(VTK_USE_RENDERING AND VTK_USE_VIEWS)
set(TestGLSLError 12)
set(TestChartsOn3DError 16)
SET(TestLinePlotError 25)
SET(TestLinePlot2Error 25)
SET(TestLinePlotInteractionError 25)
SET(TestMultipleRenderersError 25)
SET(TestMultipleScalarsToColorsError 25)
......
/*=========================================================================
Program: Visualization Toolkit
Module: TestLinePlot2.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 "vtkRenderWindow.h"
#include "vtkSmartPointer.h"
#include "vtkChartXY.h"
#include "vtkPlot.h"
#include "vtkTable.h"
#include "vtkFloatArray.h"
#include "vtkContextView.h"
#include "vtkContextScene.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkNew.h"
// Traced data from Talbot et. al paper
static double data_x[] = {8.1, 8.6, 8.65, 8.9, 8.95, 9.2, 9.4, 9.6, 9.9, 10, 10.1, 10.1, 10.15, 10.3, 10.35, 10.5, 10.52, 10.55,
10.85, 10.95, 11.05, 11.07, 11.15, 11.3, 11.4, 11.6, 11.95, 12.6, 12.85, 13.1, 14.1};
static double data_y[] = {59.9, 60.5, 54.1, 54.25, 49, 50, 48, 45.2, 51.1, 47, 51, 45.8, 51.1, 47.2, 52, 46, 48, 47.6, 49, 41.5, 45.5, 44.7,
46.5, 44.1, 48.5, 44.8, 45.1, 39, 38.7, 38.9, 37.8};
//----------------------------------------------------------------------------
int TestLinePlot2( int, char * [] )
{
// Set up a 2D scene, add an XY chart to it
vtkNew<vtkContextView> view;
view->GetRenderWindow()->SetSize(400, 300);
vtkNew<vtkChartXY> chart;
view->GetScene()->AddItem(chart.GetPointer());
// Create a table with some points in it...
vtkNew<vtkTable> table;
vtkNew<vtkFloatArray> arrX;
arrX->SetName("X Axis");
table->AddColumn(arrX.GetPointer());
vtkNew<vtkFloatArray> arrC;
arrC->SetName("Y Axis");
table->AddColumn(arrC.GetPointer());
int numPoints = 31;
table->SetNumberOfRows(numPoints);
for (int i = 0; i < numPoints; ++i)
{
table->SetValue(i, 0, data_x[i] );
table->SetValue(i, 1, data_y[i]);
}
// Add a plot of points, setting the colors etc
vtkPlot *line = chart->AddPlot(vtkChart::POINTS);
line->SetInput(table.GetPointer(), 0, 1);
line->SetColor(0, 255, 0, 255);
line->SetWidth(1.0);
//Finally render the scene
view->GetRenderWindow()->SetMultiSamples(0);
view->GetInteractor()->Initialize();
view->GetInteractor()->Start();
return EXIT_SUCCESS;
}
/*=========================================================================
Program: Visualization Toolkit
......@@ -26,6 +27,8 @@
#include "vtksys/ios/sstream"
#include "vtkObjectFactory.h"
#include "vtkAxisExtended.h"
#include <algorithm>
#include <limits>
#include "math.h"
......@@ -69,6 +72,8 @@ vtkAxis::vtkAxis()
this->Notation = STANDARD_NOTATION;
this->Behavior = 0;
this->Pen = vtkPen::New();
this->TitleAppended = false;
this->Pen->SetColor(0, 0, 0);
this->Pen->SetWidth(1.0);
this->GridPen = vtkPen::New();
......@@ -372,7 +377,7 @@ bool vtkAxis::Paint(vtkContext2D *painter)
//-----------------------------------------------------------------------------
void vtkAxis::SetMinimum(double minimum)
{
{
minimum = std::max(minimum, this->MinimumLimit);
if (this->Minimum == minimum)
{
......@@ -477,7 +482,7 @@ void vtkAxis::SetNotation(int notation)
void vtkAxis::AutoScale()
{
// Calculate the min and max, set the number of ticks and the tick spacing
this->TickInterval = this->CalculateNiceMinMax(this->Minimum, this->Maximum);
//this->TickInterval = this->CalculateNiceMinMax(this->Minimum, this->Maximum); // Tharindu - This method is called for log scales in GenerateTickLabels(,)
this->UsingNiceMinMax = true;
this->GenerateTickLabels(this->Minimum, this->Maximum);
}
......@@ -491,7 +496,8 @@ void vtkAxis::RecalculateTickSpacing()
{
double min = this->Minimum;
double max = this->Maximum;
this->TickInterval = this->CalculateNiceMinMax(min, max);
// this->TickInterval = this->CalculateNiceMinMax(min, max); // Tharindu shifted to log scales only, in GenerateTickLabels(,)
if (this->UsingNiceMinMax)
{
this->GenerateTickLabels(this->Minimum, this->Maximum);
......@@ -647,6 +653,7 @@ vtkRectf vtkAxis::GetBoundingRect(vtkContext2D* painter)
titleBounds.GetData());
}
if (vertical)
{
bounds.SetWidth(widest + titleBounds.GetWidth() + this->Margins[0]);
......@@ -668,6 +675,7 @@ vtkRectf vtkAxis::GetBoundingRect(vtkContext2D* painter)
void vtkAxis::GenerateTickLabels(double min, double max)
{
// Now calculate the tick labels, and positions within the axis range
this->TickPositions->SetNumberOfTuples(0);
this->TickLabels->SetNumberOfTuples(0);
......@@ -677,6 +685,10 @@ void vtkAxis::GenerateTickLabels(double min, double max)
{
// We calculate the first tick mark for lowest order of magnitude.
// and the last for the highest order of magnitude.
this->TickInterval = this->CalculateNiceMinMax(min, max); // Added by Tharindu to only log scales
bool niceTickMark = false;
int minOrder = 0;
int maxOrder = 0;
......@@ -719,6 +731,71 @@ void vtkAxis::GenerateTickLabels(double min, double max)
else
{
// Now calculate the tick labels, and positions within the axis range
//This gets the tick interval and max, min of labeling from the Extended algorithm
double scaling = 0.0;
bool axisVertical = false;
if(this->Point1[0] == 0 && this->Point2[0] == 0) // When the axis is not initialized
{
scaling = 500 /
(this->Maximum - this->Minimum); /// 500 is an intial guess for the length of the axis in pixels
}
else
{
if (this->Point1[0] == this->Point2[0]) // x1 == x2, therefore vertical
{
scaling = (this->Point2[1] - this->Point1[1]) /
(this->Maximum - this->Minimum);
axisVertical = true;
}
else
{
scaling = (this->Point2[0] - this->Point1[0]) /
(this->Maximum - this->Minimum);
}
}
int fontSize = this->LabelProperties->GetFontSize();
int minFontSize = 8 ; // this->LabelProperties->GetFontSizeMinValue(); Min font size is zero
vtkAxisExtended* tickPositionExtended = vtkAxisExtended::New();
// The following parameters are required for the legibility part in the optimization
// tickPositionExtended->SetFontSize(fontSize);
tickPositionExtended->SetDesiredFontSize(fontSize);
tickPositionExtended->SetPrecision(this->Precision);
tickPositionExtended->SetIsAxisVertical(axisVertical);
vtkVector3d values = tickPositionExtended->GenerateExtendedTickLabels(min, max, 4, scaling); // Value 4 is hard coded for the user desired tick spacing
min = values[0];
max = values[1];
this->TickInterval = values[2];
if(min < this->Minimum)
{
this->Minimum = min;
}
if(max > this->Maximum)
{
this->Maximum = max;
}
this->Notation = tickPositionExtended->GetLabelFormat();
this->LabelProperties->SetFontSize(tickPositionExtended->GetFontSize());
if(tickPositionExtended->GetOrientation() == 1)
{
this->LabelProperties->SetOrientation(90); // Set this to 90 to make the labels vertical
}
double mult = max > min ? 1.0 : -1.0;
double range = 0.0;
int n = 0;
......@@ -767,25 +844,27 @@ void vtkAxis::GenerateTickLabels(double min, double max)
value = pow(double(10.0), double(value));
}
// Now create a label for the tick position
vtksys_ios::ostringstream ostr;
ostr.imbue(vtkstd::locale::classic());
if (this->Notation > 0)
{
ostr.precision(this->Precision);
}
if (this->Notation == SCIENTIFIC_NOTATION)
{
// Scientific notation
ostr.setf(vtksys_ios::ios::scientific, vtksys_ios::ios::floatfield);
}
else if (this->Notation == FIXED_NOTATION)
{
ostr.setf(ios::fixed, ios::floatfield);
}
ostr << value;
this->TickLabels->InsertNextValue(ostr.str());
GenerateLabelFormat(this->Notation,value);
//vtksys_ios::ostringstream ostr;
//ostr.imbue(vtkstd::locale::classic());
//if (this->Notation > 0)
// {
// ostr.precision(this->Precision);
// }
//if (this->Notation == 1)
// {
// // Scientific notation
// ostr.setf(vtksys_ios::ios::scientific, vtksys_ios::ios::floatfield);
// }
//else if (this->Notation == 2)
// {
// ostr.setf(ios::fixed, ios::floatfield);
// }
//ostr << value;
//this->TickLabels->InsertNextValue(ostr.str());
}
}
this->TickMarksDirty = false;
}
......@@ -822,6 +901,116 @@ void vtkAxis::GenerateTickLabels()
}
}
//-------------------------------------------------------------------
// This methods generates tick labels for 8 different format notations
// 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)
// 7 - Factored Decimals e.g. 5 (thousands)
// 8 - Factored Scientific 5 (10^6)
void vtkAxis::GenerateLabelFormat(int notation, double n)
{
vtksys_ios::ostringstream ostr;
ostr.imbue(vtkstd::locale::classic());
switch(notation)
{
case 1:
ostr << n;
ostr.precision(this->Precision);
ostr.setf(vtksys_ios::ios::scientific, vtksys_ios::ios::floatfield);
this->TickLabels->InsertNextValue(ostr.str());
break;
case 2:
ostr << n;
if((std::ceil(n)-std::floor(n)) != 0.0 )
{
ostr.precision(this->Precision);
}
this->TickLabels->InsertNextValue(ostr.str());
break;
case 3:
ostr.setf(ios::fixed, ios::floatfield);
ostr << n/1000.0 << "K";
if((std::ceil(n/1000.0)-std::floor(n/1000.0)) != 0.0 )
{
ostr.precision(this->Precision);
}
this->TickLabels->InsertNextValue(ostr.str()); // minus three zeros + K
break;
case 4:
ostr.setf(ios::fixed, ios::floatfield);
ostr << n/1000.0 ;
if((std::ceil(n/1000.0)-std::floor(n/1000.0)) != 0.0 )
{
ostr.precision(this->Precision);
}
if(!TitleAppended)
{
this->Title.append(" (K)");
TitleAppended = true;
}
this->TickLabels->InsertNextValue(ostr.str());// minus three zeros
break;
case 5:
ostr.setf(ios::fixed, ios::floatfield);
ostr << n/1000000.0 << "M";
if((std::ceil(n/1000000.0)-std::floor(n/1000000.0)) != 0.0 )
{
ostr.precision(this->Precision);
}
this->TickLabels->InsertNextValue(ostr.str()); // minus six zeros
break;
case 6:
ostr.precision(this->Precision);
ostr.setf(ios::fixed, ios::floatfield);
ostr << n/1000000.0;
if((std::ceil(n/1000000.0)-std::floor(n/1000000.0)) != 0.0 )
{
ostr.precision(this->Precision);
}
if(!TitleAppended)
{
this->Title.append(" (M)");
TitleAppended = true;
}
this->TickLabels->InsertNextValue(ostr.str()); // minus six zeros + M
break;
case 7:
ostr.precision(this->Precision);
ostr.setf(ios::fixed, ios::floatfield);
ostr << n/1000.0;
if((std::ceil(n/1000.0)-std::floor(n/1000.0)) != 0.0 )
{
ostr.precision(this->Precision);
}
if(!TitleAppended)
{
this->Title.append(" ('000)");
TitleAppended = true;
}
this->TickLabels->InsertNextValue(ostr.str()); // Three 0's get reduced
break;
case 8:
ostr.precision(this->Precision);
ostr.setf(vtksys_ios::ios::scientific, vtksys_ios::ios::floatfield);
ostr << n/1000.0 ;
if(!TitleAppended)
{
this->Title.append(" ('000)");
TitleAppended = true;
}
this->TickLabels->InsertNextValue(ostr.str());
break;
}
}
//-----------------------------------------------------------------------------
double vtkAxis::CalculateNiceMinMax(double &min, double &max)
{
......@@ -1067,6 +1256,7 @@ void vtkAxis::GenerateLogScaleTickMarks(int order,
if (this->Notation == SCIENTIFIC_NOTATION)
{
ostr.setf(vtksys_ios::ios::scientific, vtksys_ios::ios::floatfield);
}
else if (this->Notation == FIXED_NOTATION)
{
......
......@@ -45,7 +45,7 @@ public:
// Description:
// Enumeration of the axis locations in a conventional XY chart. Other
// layouts are possible.
enum {
enum {
LEFT = 0,
BOTTOM,
RIGHT,
......@@ -271,6 +271,8 @@ protected:
// Generate tick labels from the supplied double array of tick positions.
void GenerateTickLabels();
void GenerateLabelFormat(int notation, double n);
// Description:
// Calculate the next "nicest" numbers above and below the current minimum.
// \return the "nice" spacing of the numbers.
......@@ -330,6 +332,8 @@ protected:
int Notation; // The notation to use (standard, scientific, mixed)
int Behavior; // The behaviour of the axis (auto, fixed, custom).
float MaxLabel[2]; // The widest/tallest axis label.
bool TitleAppended; // THis keeps track if the title is updated when the label formats are changed
// in Extended Axis Labeling algorithm
// Description:
// This object stores the vtkPen that controls how the axis is drawn.
......
/*=========================================================================
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 "math.h"
#include <algorithm>
#include "vtkAxisExtended.h"
#include "vtkStdString.h"
#include "vtksys/ios/sstream"
#include "vtkObjectFactory.h"
vtkStandardNewMacro(vtkAxisExtended);
// This method return a value to make step sizes corresponding to low q and j values more preferable
double vtkAxisExtended::Simplicity(int qIndex, int qLength, int j, double lmin, double lmax, double lstep)
{
double eps = VTK_DBL_EPSILON * 100;
int v = 1 ;
qIndex++;
double rem = fmod(lmin,lstep);
if((rem < eps || (lstep - rem ) < eps ) && lmin <=0 && lmax >=0 )
{
v =0;
}
else
{
v=1; // v is 1 is lebelling includes zero
}
return 1 - (qIndex-1)/(qLength-1) - j + v;
}
// This method returns the maximum possible value of simplicity value given q and j
double vtkAxisExtended::SimplicityMax(int qIndex, int qLength, int j)
{
int v =1;
qIndex++;
return 1 - (qIndex-1)/(qLength-1) - j + v;
}
// 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)
{
double coverage = 1- 0.5 * (pow(dmax- lmax, 2) + pow(dmin- lmin, 2) / pow(0.1*(dmax-dmin),2));
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)
{
double half = (span - range)/2;
return 1- 0.5 * (pow(half, 2) + pow(half, 2) / pow(0.1*(range),2));
}
else
{
return 1.0;
}
}
// 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)
{
double r = (k-1)/(lmax-lmin);
double rt = (m-1) / (std::max(lmax,dmax) - std::min(dmin,lmin));
return 2 - std::max(r/rt , rt/r);
}
// Derives the maximum values for density given k (number of ticks) and m (user given)
double vtkAxisExtended::DensityMax(int k, double m)
{
if(k >= m)
{
return 2 - (k-1)/(m-1);
}
else
{
return 1;
}
}
// This methods gives a weighing factor for each label depending on the range
// The coding for the different formats
// 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)
// 7 - Factored Decimals e.g. 5 (thousands)
// 8 - Factored Scientific 5 (10^6)
double vtkAxisExtended::FormatLegibilityScore(double n, int format)
{
switch(format)
{
case 1:
return 0.25;
break;
case 2:
if( abs(n)>0.0001 && abs(n) < 1000000)
{
return 1.0;
}
else
{
return 0.0;
}
break;
case 3:
if( abs(n)>1000 && abs(n) < 1000000)
{
return 0.75;
}
else
{
return 0.0;
}
break;
case 4:
if( abs(n)>1000 && abs(n) < 1000000)
{
return 0.4;
}
else
{
return 0.0;
}
break;
case 5:
if( abs(n)>1000000 && abs(n) < 1000000000)
{
return 0.75;
}
else
{
return 0.0;
}
break;
case 6:
if( abs(n)>1000000 && abs(n) < 1000000000)
{
return 0.4;
}
else
{
return 0.0;
}
break;
case 7:
return 0.5;
break;
case 8:
return 0.3;
break;
default:
return 0.0;
break;
}
}