Commit ef328bcf authored by David C. Lonie's avatar David C. Lonie

Fix vtkAxis scaling when TileScale is set on the renwin.

Now vtkAxis will show the same number of ticks and the
same relative text size when magnifying the image. Text
will still be a little funky if the scaling is anisotropic,
but at the moment our freetype engine doesn't support
anisotropic dpi (not sure if the MPL backend can or not).

New API: vtkContextScene::GetLogicalTileScale() returns
the renderwindow tilescale or (1, 1) depending on whether
ScaleTiles is enabled.

There are some random fixes where vtkAxis objects were being
used without a scene, which led to segfaults when trying to
look up the tile scale internally.
parent 48b5509f
......@@ -52,6 +52,7 @@ vtk_add_test_cxx(${vtk-module}CxxTests tests
TestChartDouble.cxx
TestChartDoubleColors.cxx
TestChartMatrix.cxx
TestChartTileScaling.cxx
TestChartUnicode.cxx
TestChartsOn3D.cxx
TestChartXYInvertedAxis.cxx
......
......@@ -161,6 +161,7 @@ int TestAxes(int , char * [])
vtkNew<vtkAxis> logAxis;
double plainRange[2] = {0.1, 1000.0};
double logRange[2];
logAxis->SetScene(view->GetScene());
logAxis->SetUnscaledRange(plainRange);
logAxis->LogScaleOn();
logAxis->GetUnscaledRange(NULL); // Insure NULL pointers are ignored.
......
/*=========================================================================
Program: Visualization Toolkit
Module: TestLinePlot.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 "vtkChartXY.h"
#include "vtkContextScene.h"
#include "vtkContextView.h"
#include "vtkFloatArray.h"
#include "vtkNew.h"
#include "vtkPlot.h"
#include "vtkPNGWriter.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkSmartPointer.h"
#include "vtkTable.h"
//------------------------------------------------------------------------------
// This test mainly checks that the tick marks have the same relative spacing
// regardless of the current vtkRenderWindow::TileScale. Take care if replacing
// baselines, as the tick spacing should match the result obtained without the
// SetTileScale call.
//
// Note: At the moment (6/2/2015), there is an issue with the data / gridmarks
// not rendering properly at the tile 'seams', as can be seen in the 'valid'
// baseline. Just noting that this is expected for now.
//
int TestChartTileScaling( int, char *[])
{
vtkNew<vtkContextView> view;
view->GetRenderWindow()->SetMultiSamples(0);
// Needed for image export to work on all platforms:
view->GetRenderWindow()->SwapBuffersOff();
view->GetRenderWindow()->SetSize(400, 300);
// Set tile scale up.
view->GetRenderWindow()->SetTileScale(2);
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> arrS;
arrS->SetName("Sine");
table->AddColumn(arrS.GetPointer());
vtkNew<vtkFloatArray> arr1;
arr1->SetName("One");
table->AddColumn(arr1.GetPointer());
// Test charting with a few more points...
int numPoints = 69;
float inc = 7.5 / (numPoints-1);
table->SetNumberOfRows(numPoints);
for (int i = 0; i < numPoints; ++i)
{
table->SetValue(i, 0, i * inc);
table->SetValue(i, 1, sin(i * inc) + 0.0);
table->SetValue(i, 2, 1.0);
}
// Add multiple line plots, setting the colors etc
vtkPlot *line = chart->AddPlot(vtkChart::LINE);
line->SetInputData(table.GetPointer(), 0, 1);
line->SetColor(0, 255, 0, 255);
line->SetWidth(1.0);
line = chart->AddPlot(vtkChart::LINE);
line->SetInputData(table.GetPointer(), 0, 2);
line->SetColor(255, 0, 0, 255);
line->SetWidth(5.0);
view->GetInteractor()->Initialize();
view->GetInteractor()->Start();
return EXIT_SUCCESS;
}
......@@ -18,6 +18,7 @@
#include "vtkMath.h"
#include "vtkNew.h"
#include "vtkContext2D.h"
#include "vtkContextScene.h"
#include "vtkPen.h"
#include "vtkChart.h"
#include "vtkTextProperty.h"
......@@ -1373,18 +1374,20 @@ double vtkAxis::CalculateNiceMinMax(double &min, double &max)
return range / double(this->NumberOfTicks - 1);
}
vtkVector2i tileScale = this->Scene->GetLogicalTileScale();
float pixelRange = 0;
float tickPixelSpacing = 0;
if (this->Position == vtkAxis::LEFT || this->Position == vtkAxis::RIGHT
|| this->Position == vtkAxis::PARALLEL)
{
pixelRange = this->Position2.GetY() - this->Position1.GetY();
tickPixelSpacing = 30;
tickPixelSpacing = 30 * tileScale.GetX();
}
else
{
pixelRange = this->Position2.GetX() - this->Position1.GetX();
tickPixelSpacing = 45;
tickPixelSpacing = 45 * tileScale.GetY();
}
double niceTickSpacing = 0.0;
......
......@@ -75,6 +75,7 @@ vtkChartBox::vtkChartBox()
{
this->Storage = new vtkChartBox::Private;
this->Storage->Plot->SetParent(this);
this->AddItem(this->Storage->YAxis.GetPointer());
this->GeometryValid = false;
this->Selection = vtkIdTypeArray::New();
this->SelectedColumn = -1;
......@@ -398,7 +399,10 @@ void vtkChartBox::UpdateGeometry(vtkContext2D* painter)
// Take up the entire window right now, this could be made configurable
this->SetGeometry(geometry.GetData());
this->SetBorders(leftBorder, 30, 0, 20);
vtkVector2i tileScale = this->Scene->GetLogicalTileScale();
this->SetBorders(leftBorder, 30 * tileScale.GetY(),
0, 20 * tileScale.GetY());
int nbPlots = static_cast<int>(this->Storage->XPosition.size());
// Iterate through the axes and set them up to span the chart area.
......
......@@ -116,6 +116,7 @@ void vtkChartParallelCoordinates::Update()
for (std::vector<vtkAxis *>::iterator it = this->Storage->Axes.begin();
it != this->Storage->Axes.end(); ++it)
{
this->RemoveItem(*it);
(*it)->Delete();
}
this->Storage->Axes.clear();
......@@ -125,6 +126,7 @@ void vtkChartParallelCoordinates::Update()
{
vtkAxis* axis = vtkAxis::New();
axis->SetPosition(vtkAxis::PARALLEL);
this->AddItem(axis);
this->Storage->Axes.push_back(axis);
}
this->Storage->AxesSelections.resize(this->Storage->Axes.size(),
......@@ -353,7 +355,10 @@ void vtkChartParallelCoordinates::UpdateGeometry()
{
// Take up the entire window right now, this could be made configurable
this->SetGeometry(geometry.GetData());
this->SetBorders(60, 50, 60, 20);
vtkVector2i tileScale = this->Scene->GetLogicalTileScale();
this->SetBorders(60 * tileScale.GetX(), 50 * tileScale.GetY(),
60 * tileScale.GetX(), 20 * tileScale.GetY());
// Iterate through the axes and set them up to span the chart area.
int xStep = (this->Point2[0] - this->Point1[0]) /
......
......@@ -98,7 +98,11 @@ bool vtkChartPie::Paint(vtkContext2D *painter)
{
// Take up the entire window right now, this could be made configurable
this->SetGeometry(geometry);
this->SetBorders(20, 20, 20, 20);
vtkVector2i tileScale = this->Scene->GetLogicalTileScale();
this->SetBorders(20 * tileScale.GetX(), 20 * tileScale.GetY(),
20 * tileScale.GetX(), 20 * tileScale.GetY());
// Put the legend in the top corner of the chart
vtkRectf rect = this->Legend->GetBoundingRect(painter);
this->Legend->SetPoint(this->Point2[0] - rect.GetWidth(),
......
......@@ -833,6 +833,9 @@ bool vtkChartXY::UpdateLayout(vtkContext2D* painter)
// their bounds, and to update the chart in response to that.
bool changed = false;
vtkVector2i tileScale = this->Scene->GetLogicalTileScale();
vtkVector2i hiddenAxisBorder = tileScale * this->HiddenAxisBorder;
// Axes
if (this->LayoutStrategy == vtkChart::FILL_SCENE ||
this->LayoutStrategy == vtkChart::FILL_RECT)
......@@ -862,13 +865,20 @@ bool vtkChartXY::UpdateLayout(vtkContext2D* painter)
painter->ComputeStringBounds(this->Title, bounds);
if (bounds[3] > 0)
{
border += 5 /* title margin */
border += (5 * tileScale.GetY()) /* title margin */
+ bounds[3]; // add the title text height to the border.
}
}
border = border < this->HiddenAxisBorder ? this->HiddenAxisBorder :
border;
if (i == vtkAxis::TOP || i == vtkAxis::BOTTOM)
{
border = std::max(border, hiddenAxisBorder.GetY());
}
else
{
border = std::max(border, hiddenAxisBorder.GetX());
}
if (this->ChartPrivate->Borders[i] != border)
{
this->ChartPrivate->Borders[i] = border;
......@@ -881,8 +891,8 @@ bool vtkChartXY::UpdateLayout(vtkContext2D* painter)
{
if (this->DrawAxesAtOrigin)
{
this->SetBorders(this->HiddenAxisBorder,
this->HiddenAxisBorder,
this->SetBorders(hiddenAxisBorder.GetX(),
hiddenAxisBorder.GetY(),
this->ChartPrivate->Borders[2],
this->ChartPrivate->Borders[3]);
// Get the screen coordinates for the origin, and move the axes there.
......@@ -967,6 +977,8 @@ int vtkChartXY::GetLegendBorder(vtkContext2D* painter, int axisPosition)
return 0;
}
vtkVector2i tileScale = this->Scene->GetLogicalTileScale();
int padding = 10;
vtkVector2i legendSize(0, 0);
vtkVector2i legendAlignment(this->Legend->GetHorizontalAlignment(),
......@@ -980,12 +992,12 @@ int vtkChartXY::GetLegendBorder(vtkContext2D* painter, int axisPosition)
if (axisPosition == vtkAxis::LEFT &&
legendAlignment.GetX() == vtkChartLegend::LEFT)
{
return legendSize.GetX() + padding;
return legendSize.GetX() + padding * tileScale.GetX();
}
else if (axisPosition == vtkAxis::RIGHT &&
legendAlignment.GetX() == vtkChartLegend::RIGHT)
{
return legendSize.GetX() + padding;
return legendSize.GetX() + padding * tileScale.GetX();
}
else if ((axisPosition == vtkAxis::TOP || axisPosition == vtkAxis::BOTTOM) &&
(legendAlignment.GetX() == vtkChartLegend::LEFT ||
......@@ -996,12 +1008,12 @@ int vtkChartXY::GetLegendBorder(vtkContext2D* painter, int axisPosition)
else if (axisPosition == vtkAxis::TOP &&
legendAlignment.GetY() == vtkChartLegend::TOP)
{
return legendSize.GetY() + padding;
return legendSize.GetY() + padding * tileScale.GetY();
}
else if (axisPosition == vtkAxis::BOTTOM &&
legendAlignment.GetY() == vtkChartLegend::BOTTOM)
{
return legendSize.GetY() + padding;
return legendSize.GetY() + padding * tileScale.GetY();
}
else
{
......
......@@ -37,6 +37,7 @@ vtkColorLegend::vtkColorLegend()
this->Interpolate = true;
this->Axis = vtkSmartPointer<vtkAxis>::New();
this->Axis->SetPosition(vtkAxis::RIGHT);
this->AddItem(this->Axis);
this->SetInline(false);
this->SetHorizontalAlignment(vtkChartLegend::RIGHT);
this->SetVerticalAlignment(vtkChartLegend::BOTTOM);
......
......@@ -387,6 +387,15 @@ bool vtkScatterPlotMatrix::Paint(vtkContext2D *painter)
return Superclass::Paint(painter);
}
void vtkScatterPlotMatrix::SetScene(vtkContextScene *scene)
{
// The internal axis shouldn't be a child as it isn't rendered with the
// chart, but it does need access to the scene.
this->Private->TestAxis->SetScene(scene);
this->Superclass::SetScene(scene);
}
bool vtkScatterPlotMatrix::SetActivePlot(const vtkVector2i &pos)
{
if (pos.GetX() + pos.GetY() + 1 < this->Size.GetX() && pos.GetX() < this->Size.GetX() &&
......
......@@ -63,6 +63,8 @@ public:
// Paint event for the chart matrix.
virtual bool Paint(vtkContext2D *painter);
virtual void SetScene(vtkContextScene *scene);
// Description:
// Set the active plot, the one that will be displayed in the top-right.
// This defaults to (0, n-2), the plot below the first histogram on the left.
......
......@@ -231,6 +231,17 @@ int vtkContextScene::GetSceneHeight()
return this->Geometry[1];
}
//-----------------------------------------------------------------------------
vtkVector2i vtkContextScene::GetLogicalTileScale()
{
vtkVector2i result(1);
if (this->ScaleTiles && this->Renderer && this->Renderer->GetRenderWindow())
{
this->Renderer->GetRenderWindow()->GetTileScale(result.GetData());
}
return result;
}
//-----------------------------------------------------------------------------
void vtkContextScene::SetDirty(bool isDirty)
{
......
......@@ -26,6 +26,7 @@
#include "vtkRenderingContext2DModule.h" // For export macro
#include "vtkObject.h"
#include "vtkWeakPointer.h" // Needed for weak pointer to the window.
#include "vtkVector.h" // For vtkVector return type.
class vtkContext2D;
class vtkAbstractContextItem;
......@@ -132,6 +133,13 @@ public:
vtkGetMacro(ScaleTiles, bool);
vtkBooleanMacro(ScaleTiles, bool);
// Description:
// The tile scale of the target vtkRenderWindow. Hardcoded pixel offsets, etc
// should properly account for these <x, y> scale factors. This will simply
// return vtkVector2i(1, 1) if ScaleTiles is false or if this->Renderer is
// NULL.
vtkVector2i GetLogicalTileScale();
// Description:
// This should not be necessary as the context view should take care of
// rendering.
......
......@@ -863,16 +863,24 @@ void vtkOpenGLContextDevice2D::DrawString(float *point,
float p[] = { std::floor(point[0] * xScale) / xScale,
std::floor(point[1] * yScale) / yScale };
// TODO this currently ignores vtkContextScene::ScaleTiles. Not sure how to
// get at that from here, but this is better than ignoring scaling altogether.
// TODO Also, FreeType supports anisotropic DPI. Might be needed if the
// tileScale isn't homogeneous, but we'll need to update the textrenderer API
// and see if MPL/mathtext can support it.
int tileScale[2];
this->RenderWindow->GetTileScale(tileScale);
int dpi = this->RenderWindow->GetDPI() * std::max(tileScale[0], tileScale[1]);
// Cache rendered text strings
vtkTextureImageCache<UTF16TextPropertyKey>::CacheData &cache =
this->Storage->TextTextureCache.GetCacheData(
UTF16TextPropertyKey(this->TextProp, string));
UTF16TextPropertyKey(this->TextProp, string, dpi));
vtkImageData* image = cache.ImageData;
if (image->GetNumberOfPoints() == 0 && image->GetNumberOfCells() == 0)
{
int textDims[2];
if (!this->TextRenderer->RenderString(this->TextProp, string,
this->RenderWindow->GetDPI(), image,
if (!this->TextRenderer->RenderString(this->TextProp, string, dpi, image,
textDims))
{
return;
......@@ -936,14 +944,17 @@ void vtkOpenGLContextDevice2D::ComputeStringBounds(const vtkUnicodeString &strin
bounds[3] = static_cast<float>(0);
return;
}
int tileScale[2];
this->RenderWindow->GetTileScale(tileScale);
GLfloat mv[16];
glGetFloatv(GL_MODELVIEW_MATRIX, mv);
float xScale = mv[0];
float yScale = mv[5];
bounds[0] = static_cast<float>(0);
bounds[1] = static_cast<float>(0);
bounds[2] = static_cast<float>(box.GetX() / xScale);
bounds[3] = static_cast<float>(box.GetY() / yScale);
bounds[2] = static_cast<float>((box.GetX() / xScale) * tileScale[0]);
bounds[3] = static_cast<float>((box.GetY() / yScale) * tileScale[1]);
}
//-----------------------------------------------------------------------------
......@@ -963,16 +974,25 @@ void vtkOpenGLContextDevice2D::DrawMathTextString(float point[2],
float p[] = { std::floor(point[0]), std::floor(point[1]) };
// TODO this currently ignores vtkContextScene::ScaleTiles. Not sure how to
// get at that from here, but this is better than ignoring scaling altogether.
// TODO Also, FreeType supports anisotropic DPI. Might be needed if the
// tileScale isn't homogeneous, but we'll need to update the textrenderer API
// and see if MPL/mathtext can support it.
int tileScale[2];
this->RenderWindow->GetTileScale(tileScale);
int dpi = this->RenderWindow->GetDPI() * std::max(tileScale[0], tileScale[1]);
// Cache rendered text strings
vtkTextureImageCache<UTF8TextPropertyKey>::CacheData &cache =
this->Storage->MathTextTextureCache.GetCacheData(
UTF8TextPropertyKey(this->TextProp, string));
UTF8TextPropertyKey(this->TextProp, string, dpi));
vtkImageData* image = cache.ImageData;
if (image->GetNumberOfPoints() == 0 && image->GetNumberOfCells() == 0)
{
int textDims[2];
if (!mathText->RenderString(string.c_str(), image, this->TextProp,
this->RenderWindow->GetDPI(), textDims))
if (!mathText->RenderString(string.c_str(), image, this->TextProp, dpi,
textDims))
{
return;
}
......
......@@ -179,7 +179,8 @@ struct TextPropertyKey
// Description:
// Creates a TextPropertyKey.
TextPropertyKey(vtkTextProperty* textProperty, const StringType& text)
TextPropertyKey(vtkTextProperty* textProperty, const StringType& text,
int dpi)
{
this->TextPropertyId = GetIdFromTextProperty(textProperty);
this->FontSize = textProperty->GetFontSize();
......@@ -190,6 +191,7 @@ struct TextPropertyKey
static_cast<unsigned char>(color[2] * 255),
static_cast<unsigned char>(textProperty->GetOpacity() * 255));
this->Text = text;
this->DPI = dpi;
}
// Description:
......@@ -203,7 +205,8 @@ struct TextPropertyKey
this->Color[0] == other.Color[0] &&
this->Color[1] == other.Color[1] &&
this->Color[2] == other.Color[2] &&
this->Color[3] == other.Color[3];
this->Color[3] == other.Color[3] &&
this->DPI == other.DPI;
}
unsigned short FontSize;
......@@ -211,6 +214,7 @@ struct TextPropertyKey
// States in the function not to use more than 32 bits - int works fine here.
unsigned int TextPropertyId;
StringType Text;
int DPI;
};
typedef TextPropertyKey<vtkStdString> UTF8TextPropertyKey;
......
......@@ -1244,16 +1244,24 @@ void vtkOpenGLContextDevice2D::DrawString(float *point,
float p[] = { std::floor(point[0] * xScale) / xScale,
std::floor(point[1] * yScale) / yScale };
// TODO this currently ignores vtkContextScene::ScaleTiles. Not sure how to
// get at that from here, but this is better than ignoring scaling altogether.
// TODO Also, FreeType supports anisotropic DPI. Might be needed if the
// tileScale isn't homogeneous, but we'll need to update the textrenderer API
// and see if MPL/mathtext can support it.
int tileScale[2];
this->RenderWindow->GetTileScale(tileScale);
int dpi = this->RenderWindow->GetDPI() * std::max(tileScale[0], tileScale[1]);
// Cache rendered text strings
vtkTextureImageCache<UTF16TextPropertyKey>::CacheData &cache =
this->Storage->TextTextureCache.GetCacheData(
UTF16TextPropertyKey(this->TextProp, string));
UTF16TextPropertyKey(this->TextProp, string, dpi));
vtkImageData* image = cache.ImageData;
if (image->GetNumberOfPoints() == 0 && image->GetNumberOfCells() == 0)
{
int textDims[2];
if (!this->TextRenderer->RenderString(this->TextProp, string,
this->RenderWindow->GetDPI(), image,
if (!this->TextRenderer->RenderString(this->TextProp, string, dpi, image,
textDims))
{
return;
......@@ -1326,13 +1334,17 @@ void vtkOpenGLContextDevice2D::ComputeStringBounds(const vtkUnicodeString &strin
bounds[3] = static_cast<float>(0);
return;
}
int tileScale[2];
this->RenderWindow->GetTileScale(tileScale);
double *mv = this->ModelMatrix->GetMatrix()->Element[0];
float xScale = mv[0];
float yScale = mv[5];
bounds[0] = static_cast<float>(0);
bounds[1] = static_cast<float>(0);
bounds[2] = static_cast<float>(box.GetX() / xScale);
bounds[3] = static_cast<float>(box.GetY() / yScale);
bounds[2] = static_cast<float>((box.GetX() / xScale) * tileScale[0]);
bounds[3] = static_cast<float>((box.GetY() / yScale) * tileScale[1]);
}
//-----------------------------------------------------------------------------
......@@ -1352,16 +1364,25 @@ void vtkOpenGLContextDevice2D::DrawMathTextString(float point[2],
float p[] = { std::floor(point[0]), std::floor(point[1]) };
// TODO this currently ignores vtkContextScene::ScaleTiles. Not sure how to
// get at that from here, but this is better than ignoring scaling altogether.
// TODO Also, FreeType supports anisotropic DPI. Might be needed if the
// tileScale isn't homogeneous, but we'll need to update the textrenderer API
// and see if MPL/mathtext can support it.
int tileScale[2];
this->RenderWindow->GetTileScale(tileScale);
int dpi = this->RenderWindow->GetDPI() * std::max(tileScale[0], tileScale[1]);
// Cache rendered text strings
vtkTextureImageCache<UTF8TextPropertyKey>::CacheData &cache =
this->Storage->MathTextTextureCache.GetCacheData(
UTF8TextPropertyKey(this->TextProp, string));
UTF8TextPropertyKey(this->TextProp, string, dpi));
vtkImageData* image = cache.ImageData;
if (image->GetNumberOfPoints() == 0 && image->GetNumberOfCells() == 0)
{
int textDims[2];
if (!mathText->RenderString(string.c_str(), image, this->TextProp,
this->RenderWindow->GetDPI(), textDims))
if (!mathText->RenderString(string.c_str(), image, this->TextProp, dpi,
textDims))
{
return;
}
......
......@@ -179,7 +179,8 @@ struct TextPropertyKey
// Description:
// Creates a TextPropertyKey.
TextPropertyKey(vtkTextProperty* textProperty, const StringType& text)
TextPropertyKey(vtkTextProperty* textProperty, const StringType& text,
int dpi)
{
this->TextPropertyId = GetIdFromTextProperty(textProperty);
this->FontSize = textProperty->GetFontSize();
......@@ -190,6 +191,7 @@ struct TextPropertyKey
static_cast<unsigned char>(color[2] * 255),
static_cast<unsigned char>(textProperty->GetOpacity() * 255));
this->Text = text;
this->DPI = dpi;
}
// Description:
......@@ -203,7 +205,8 @@ struct TextPropertyKey
this->Color[0] == other.Color[0] &&
this->Color[1] == other.Color[1] &&
this->Color[2] == other.Color[2] &&
this->Color[3] == other.Color[3];
this->Color[3] == other.Color[3] &&
this->DPI == other.DPI;
}
unsigned short FontSize;
......@@ -211,6 +214,7 @@ struct TextPropertyKey
// States in the function not to use more than 32 bits - int works fine here.
unsigned int TextPropertyId;
StringType Text;
int DPI;
};
typedef TextPropertyKey<vtkStdString> UTF8TextPropertyKey;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment