// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkAxisActor2D.h" #include "vtkCellArray.h" #include "vtkMath.h" #include "vtkMathUtilities.h" #include "vtkNumberToString.h" #include "vtkObjectFactory.h" #include "vtkPolyData.h" #include "vtkPolyDataMapper2D.h" #include "vtkProperty2D.h" #include "vtkStringFormatter.h" #include "vtkTextMapper.h" #include "vtkTextProperty.h" #include "vtkViewport.h" #include "vtkWindow.h" #include #include #include VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkAxisActor2D); vtkCxxSetSmartPointerMacro(vtkAxisActor2D, LabelTextProperty, vtkTextProperty); vtkCxxSetSmartPointerMacro(vtkAxisActor2D, TitleTextProperty, vtkTextProperty); namespace legacy { // this is a helper function that computes some useful functions // for an axis. It returns the number of ticks static int vtkAxisActor2DComputeTicks(double sRange[2], double& interval, double& root) { // first we try assuming the first value is reasonable int numTicks; double range = fabs(sRange[1] - sRange[0]); int rootPower = static_cast(floor(log10(range) - 1)); root = pow(10.0, rootPower); // val will be between 10 and 100 inclusive of 10 but not 100 double val = range / root; // first we check for an exact match for (numTicks = 5; numTicks < 9; ++numTicks) { if (fabs(val / (numTicks - 1.0) - floor(val / (numTicks - 1.0))) < .0001) { interval = val * root / (numTicks - 1.0); return numTicks; } } // if there isn't an exact match find a reasonable value int newIntScale = 10; if (val > 10) { newIntScale = 12; } if (val > 12) { newIntScale = 15; } if (val > 15) { newIntScale = 18; } if (val > 18) { newIntScale = 20; } if (val > 20) { newIntScale = 25; } if (val > 25) { newIntScale = 30; } if (val > 30) { newIntScale = 40; } if (val > 40) { newIntScale = 50; } if (val > 50) { newIntScale = 60; } if (val > 60) { newIntScale = 70; } if (val > 70) { newIntScale = 80; } if (val > 80) { newIntScale = 90; } if (val > 90) { newIntScale = 100; } // how many ticks should we have switch (newIntScale) { case 12: case 20: case 40: case 80: numTicks = 5; break; case 18: case 30: case 60: case 90: numTicks = 7; break; case 10: case 15: case 25: case 50: case 100: numTicks = 6; break; case 70: numTicks = 8; break; } interval = newIntScale * root / (numTicks - 1.0); return numTicks; } void ComputeRange(double inRange[2], double outRange[2], int& numTicks, double& interval) { // Handle the range double sRange[2]; if (inRange[0] < inRange[1]) { sRange[0] = inRange[0]; sRange[1] = inRange[1]; } else if (inRange[0] > inRange[1]) { sRange[1] = inRange[0]; sRange[0] = inRange[1]; } else // they're equal, so perturb them by 1 percent { double perturb = 100.; if (inRange[0] == 0.0) { // if they are both zero, then just perturb about zero sRange[0] = -1 / perturb; sRange[1] = 1 / perturb; } else { sRange[0] = inRange[0] - inRange[0] / perturb; sRange[1] = inRange[0] + inRange[0] / perturb; } } double root; numTicks = vtkAxisActor2DComputeTicks(sRange, interval, root); // is the starting point reasonable? if (fabs(sRange[0] / root - floor(sRange[0] / root)) < 0.01) { outRange[0] = sRange[0]; outRange[1] = outRange[0] + (numTicks - 1.0) * interval; } else { // OK the starting point is not a good number, so we must widen the range // First see if the current range will handle moving the start point outRange[0] = floor(sRange[0] / root) * root; if (outRange[0] + (numTicks - 1.0) * interval <= sRange[1]) { outRange[1] = outRange[0] + (numTicks - 1.0) * interval; } else { // Finally in this case we must switch to a larger range to // have reasonable starting and ending values sRange[0] = outRange[0]; numTicks = vtkAxisActor2DComputeTicks(sRange, interval, root); outRange[1] = outRange[0] + (numTicks - 1.0) * interval; } } // Adjust if necessary if (inRange[0] > inRange[1]) { sRange[0] = outRange[1]; outRange[1] = outRange[0]; outRange[0] = sRange[0]; interval = -interval; } } } namespace details { constexpr int MAX_FONT_SIZE = 1000; constexpr std::array ACCEPTABLE_LABELS = { 10, 12, 15, 18, 20, 25, 30, 40, 50, 60, 80, 90, 100 }; /** * Compute an interval that split range depending on targetedNumTicks, * while being "rounded" for nice display. Actually, we snap the labels * to be one of the following, multiply by a power of ten: * [10, 12, 15, 18, 20, 25, 30, 40, 50, 60, 80, 90] * (see ACCEPTABLE_LABELS) * * Return the computed number of ticks, that may differ from the target. */ int SnapTicksToRoundValues(double range[2], int targetedNumTicks, double& interval) { double delta = std::fabs(range[1] - range[0]); double roughInterval = delta / targetedNumTicks; // get order of magnitude of the range int rootPower = static_cast(std::floor(std::log10(roughInterval) - 1)); double root = std::pow(10.0, rootPower); // roundedInterval will be between 10 and 100 inclusive of 10 but not 100 // and has 2 significant digits int roundedInterval = roughInterval / root; auto resulting = std::lower_bound(ACCEPTABLE_LABELS.begin(), ACCEPTABLE_LABELS.end(), roundedInterval); if (resulting != ACCEPTABLE_LABELS.end()) { roundedInterval = *resulting; } // scale back rounded interval to actual range interval = roundedInterval * root; int numTicks = (delta / interval) + 1; return numTicks; } /** * Update Range, so outRange can be split into `numberOfTick` * rounded values, including bounds. * * @see SnapTicksToRoundValues */ void AdjustAndSplitRange(double inRange[2], int inNumTicks, double outRange[2], int& numberOfTicks) { // Handle the range double sRange[2]; if (inRange[0] < inRange[1]) { sRange[0] = inRange[0]; sRange[1] = inRange[1]; } else if (inRange[0] > inRange[1]) { sRange[1] = inRange[0]; sRange[0] = inRange[1]; } else // they're equal, so perturb them by 1 percent { double perturb = 100.; if (inRange[0] == 0.0) { // if they are both zero, then just perturb about zero sRange[0] = -1 / perturb; sRange[1] = 1 / perturb; } else { sRange[0] = inRange[0] - inRange[0] / perturb; sRange[1] = inRange[0] + inRange[0] / perturb; } } double interval; numberOfTicks = details::SnapTicksToRoundValues(sRange, inNumTicks, interval); // round range to start on a multiple of Interval. outRange[0] = static_cast(sRange[0] / interval) * interval; outRange[1] = outRange[0] + (numberOfTicks - 1) * interval; // Adjust if necessary if (inRange[0] > inRange[1]) { sRange[0] = outRange[1]; outRange[1] = outRange[0]; outRange[0] = sRange[0]; interval = -interval; } } }; //------------------------------------------------------------------------------ // Instantiate this object. vtkAxisActor2D::vtkAxisActor2D() { this->PositionCoordinate->SetCoordinateSystemToNormalizedViewport(); this->PositionCoordinate->SetValue(0.0, 0.0); this->Position2Coordinate->SetCoordinateSystemToNormalizedViewport(); this->Position2Coordinate->SetValue(0.75, 0.0); this->Position2Coordinate->SetReferenceCoordinate(nullptr); this->Title = nullptr; this->LabelTextProperty = vtkSmartPointer::New(); this->LabelTextProperty->SetBold(1); this->LabelTextProperty->SetItalic(1); this->LabelTextProperty->SetShadow(1); this->LabelTextProperty->SetFontFamilyToArial(); this->TitleTextProperty = vtkSmartPointer::New(); this->TitleTextProperty->ShallowCopy(this->LabelTextProperty); this->LabelFormat = new char[10]; auto result = vtk::format_to_n(this->LabelFormat, 10, "{:s}", "{:<#6.3g}"); *result.out = '\0'; this->TitleMapper = vtkTextMapper::New(); this->TitleActor = vtkActor2D::New(); this->TitleActor->SetMapper(this->TitleMapper); this->LabelMappers.reserve(this->NumberOfLabels); this->LabelActors.reserve(this->NumberOfLabels); for (int i = 0; i < this->NumberOfLabels; ++i) { vtkNew mapper; vtkNew actor; actor->SetMapper(mapper); this->LabelMappers.emplace_back(mapper); this->LabelActors.emplace_back(actor); } this->AxisMapper->SetInputData(this->Axis); this->AxisActor->SetMapper(this->AxisMapper); } //------------------------------------------------------------------------------ vtkAxisActor2D::~vtkAxisActor2D() { delete[] this->LabelFormat; this->LabelFormat = nullptr; this->TitleMapper->Delete(); this->TitleActor->Delete(); delete[] this->Title; this->Title = nullptr; } //------------------------------------------------------------------------------ vtkTextProperty* vtkAxisActor2D::GetTitleTextProperty() { return this->TitleTextProperty; } //------------------------------------------------------------------------------ vtkTextProperty* vtkAxisActor2D::GetLabelTextProperty() { return this->LabelTextProperty; } //------------------------------------------------------------------------------ int vtkAxisActor2D::UpdateGeometryAndRenderOpaqueGeometry(vtkViewport* viewport, bool render) { this->BuildAxis(viewport); if (render) { return this->RenderOpaqueGeometry(viewport); } return 0; } //------------------------------------------------------------------------------ // Build the axis, ticks, title, and labels and render. int vtkAxisActor2D::RenderOpaqueGeometry(vtkViewport* viewport) { int renderedSomething = 0; this->BuildAxis(viewport); // Everything is built, just have to render if (this->Title != nullptr && this->Title[0] != 0 && this->TitleVisibility) { renderedSomething += this->TitleActor->RenderOpaqueGeometry(viewport); } if (this->AxisVisibility || this->TickVisibility) { renderedSomething += this->AxisActor->RenderOpaqueGeometry(viewport); } if (this->LabelVisibility) { for (int i = 0; i < this->NumberOfLabelsBuilt; i++) { renderedSomething += this->LabelActors[i]->RenderOpaqueGeometry(viewport); } } return renderedSomething; } //------------------------------------------------------------------------------ // Render the axis, ticks, title, and labels. int vtkAxisActor2D::RenderOverlay(vtkViewport* viewport) { int renderedSomething = 0; this->BuildAxis(viewport); // Everything is built, just have to render if (this->Title != nullptr && this->Title[0] != 0 && this->TitleVisibility) { renderedSomething += this->TitleActor->RenderOverlay(viewport); } if (this->AxisVisibility || this->TickVisibility) { renderedSomething += this->AxisActor->RenderOverlay(viewport); } if (this->LabelVisibility) { for (int i = 0; i < this->NumberOfLabelsBuilt; i++) { renderedSomething += this->LabelActors[i]->RenderOverlay(viewport); } } return renderedSomething; } //------------------------------------------------------------------------------ // Description: // Does this prop have some translucent polygonal geometry? vtkTypeBool vtkAxisActor2D::HasTranslucentPolygonalGeometry() { return 0; } //------------------------------------------------------------------------------ // 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 vtkAxisActor2D::ReleaseGraphicsResources(vtkWindow* win) { this->TitleActor->ReleaseGraphicsResources(win); for (auto& actor : this->LabelActors) { actor->ReleaseGraphicsResources(win); } this->AxisActor->ReleaseGraphicsResources(win); } //------------------------------------------------------------------------------ void vtkAxisActor2D::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); if (this->TitleTextProperty) { os << indent << "Title Text Property:\n"; this->TitleTextProperty->PrintSelf(os, indent.GetNextIndent()); } else { os << indent << "Title Text Property: (none)\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 << "Title: " << (this->Title ? this->Title : "(none)") << "\n"; os << indent << "Ruler Mode: " << (this->RulerMode ? "On" : "Off") << "\n"; os << indent << "Ruler Distance: " << this->GetRulerDistance() << "\n"; os << indent << "Number Of Labels: " << this->NumberOfLabels << "\n"; os << indent << "Number Of Labels Built: " << this->NumberOfLabelsBuilt << "\n"; os << indent << "Range: (" << this->Range[0] << ", " << this->Range[1] << ")\n"; os << indent << "Label value notation: " << this->GetNotation() << "\n"; os << indent << "Label value precision: " << this->GetPrecision() << "\n"; os << indent << "Label Format: " << this->LabelFormat << "\n"; os << indent << "Font Factor: " << this->FontFactor << "\n"; os << indent << "Label Factor: " << this->LabelFactor << "\n"; os << indent << "Tick Length: " << this->TickLength << "\n"; os << indent << "Tick Offset: " << this->TickOffset << "\n"; os << indent << "Adjust Labels: " << (this->AdjustLabels ? "On\n" : "Off\n"); os << indent << "Snap Labels To Grid: " << (this->SnapLabelsToGrid ? "On\n" : "Off\n"); os << indent << "Axis Visibility: " << (this->AxisVisibility ? "On\n" : "Off\n"); os << indent << "Tick Visibility: " << (this->TickVisibility ? "On\n" : "Off\n"); os << indent << "Label Visibility: " << (this->LabelVisibility ? "On\n" : "Off\n"); os << indent << "Title Visibility: " << (this->TitleVisibility ? "On\n" : "Off\n"); os << indent << "MinorTickLength: " << this->MinorTickLength << endl; os << indent << "NumberOfMinorTicks: " << this->NumberOfMinorTicks << endl; os << indent << "TitlePosition: " << this->TitlePosition << endl; os << indent << "Size Font Relative To Axis: " << (this->SizeFontRelativeToAxis ? "On\n" : "Off\n"); } //------------------------------------------------------------------------------ bool vtkAxisActor2D::PositionsChangedOrViewportResized(vtkViewport* viewport) { // Check to see whether we have to rebuild everything // Viewport change may not require rebuild int* currentPosition = this->PositionCoordinate->GetComputedViewportValue(viewport); int* currentPosition2 = this->Position2Coordinate->GetComputedViewportValue(viewport); bool positionsHaveChanged = (currentPosition[0] != this->LastPosition[0] || currentPosition[1] != this->LastPosition[1] || currentPosition2[0] != this->LastPosition2[0] || currentPosition2[1] != this->LastPosition2[1]); // See whether fonts have to be rebuilt (font size depends on viewport size) int* size = viewport->GetSize(); bool viewportSizeHasChanged = (this->LastSize[0] != size[0] || this->LastSize[1] != size[1]); return positionsHaveChanged || viewportSizeHasChanged; } //------------------------------------------------------------------------------ bool vtkAxisActor2D::ShouldRebuild(vtkViewport* viewport) { if (this->TitleVisibility && !this->TitleTextProperty) { vtkErrorMacro(<< "Need title text property to render axis actor"); return false; } if (this->LabelVisibility && !this->LabelTextProperty) { vtkErrorMacro(<< "Need label text property to render axis actor"); return false; } if (!viewport->GetVTKWindow()) { return false; } bool recentBuild = viewport->GetMTime() < this->BuildTime && viewport->GetVTKWindow()->GetMTime() < this->BuildTime && this->GetMTime() < this->BuildTime && (!this->LabelVisibility || this->LabelTextProperty->GetMTime() < this->BuildTime) && (!this->TitleVisibility || this->TitleTextProperty->GetMTime() < this->BuildTime); if (!this->PositionsChangedOrViewportResized(viewport) && recentBuild) { return false; } return true; } //------------------------------------------------------------------------------ double vtkAxisActor2D::GetViewportAxisLength(vtkViewport* viewport) { int* x = this->PositionCoordinate->GetComputedViewportValue(viewport); double p1[3]; p1[0] = x[0]; p1[1] = x[1]; p1[2] = 0.0; x = this->Position2Coordinate->GetComputedViewportValue(viewport); double p2[3]; p2[0] = x[0]; p2[1] = x[1]; p2[2] = 0.0; double axis[3]; vtkMath::Subtract(p2, p1, axis); return vtkMath::Norm(axis); } //------------------------------------------------------------------------------ double vtkAxisActor2D::GetViewportRulerDistance(vtkViewport* viewport) { double wp1[3], wp2[3], wp21[3]; this->PositionCoordinate->GetValue(wp1); this->Position2Coordinate->GetValue(wp2); vtkMath::Subtract(wp2, wp1, wp21); const double worldLength = vtkMath::Norm(wp21); // Tick distance was computed in world coordinates, convert to viewport // coordinates. double length = this->GetViewportAxisLength(viewport); const double worldToLocalRatio = (worldLength <= 0.0 ? 0.0 : length / worldLength); return this->RulerDistance * worldToLocalRatio; } //------------------------------------------------------------------------------ double vtkAxisActor2D::GetAxisAngle(vtkViewport* viewport) { int* p1 = this->PositionCoordinate->GetComputedViewportValue(viewport); int* p2 = this->Position2Coordinate->GetComputedViewportValue(viewport); double deltaX = p2[0] - p1[0]; double deltaY = p2[1] - p1[1]; return (deltaX == 0. && deltaY == 0.) ? 0. : std::atan2(deltaY, deltaX); } //------------------------------------------------------------------------------ void vtkAxisActor2D::UpdateTicksValueAndPosition(vtkViewport* viewport) { // viewport distances const double viewportAxisLength = this->GetViewportAxisLength(viewport); const double viewportRulerDistance = this->GetViewportRulerDistance(viewport); // normalized on axis size. const double majorLengthRatio = this->RulerMode ? viewportRulerDistance / viewportAxisLength : 1. / (this->AdjustedNumberOfLabels - 1); const double minorLengthRatio = majorLengthRatio / (this->NumberOfMinorTicks + 1); // values (in `Range` unit) const double majorDelta = (this->AdjustedRange[1] - this->AdjustedRange[0]) * majorLengthRatio; const double minorDelta = (this->AdjustedRange[1] - this->AdjustedRange[0]) * minorLengthRatio; // factor for Range to Axis normalized value conversion. const double scale = 1. / (this->Range[1] - this->Range[0]); this->TickValues.clear(); this->NormalizedTickPositions.clear(); const double minValue = std::min(this->Range[0], this->Range[1]); const double maxValue = std::max(this->Range[0], this->Range[1]); int startingTick = this->SkipFirstTick ? 1 : 0; for (int major = startingTick; major < this->AdjustedNumberOfLabels; major++) { double value = this->AdjustedRange[0] + major * majorDelta; double position = (value - this->Range[0]) * scale; if (position < 0 || value < minValue || position > 1 || value > maxValue) { continue; } this->NormalizedTickPositions.push_back(position); this->TickValues.push_back(value); for (int minor = 1; minor <= this->NumberOfMinorTicks; minor++) { value = value + minorDelta; position = (value - this->Range[0]) * scale; if (position > 1) { continue; } this->NormalizedTickPositions.push_back(position); } } this->NumberOfLabelsBuilt = static_cast(this->TickValues.size()); } //------------------------------------------------------------------------------ void vtkAxisActor2D::BuildTicksPolyData(vtkViewport* viewport) { this->Axis->Initialize(); vtkNew pts; vtkNew lines; this->Axis->SetPoints(pts); this->Axis->SetLines(lines); // Generate the axis and tick marks. // We'll do our computation in viewport coordinates. First determine the // location of the endpoints. int* x = this->PositionCoordinate->GetComputedViewportValue(viewport); double axisStart[3]; axisStart[0] = x[0]; axisStart[1] = x[1]; axisStart[2] = 0.0; x = this->Position2Coordinate->GetComputedViewportValue(viewport); double axisEnd[3]; axisEnd[0] = x[0]; axisEnd[1] = x[1]; axisEnd[2] = 0.0; // axis extremity vtkIdType axisPoints[2]; axisPoints[0] = pts->InsertNextPoint(axisStart); // Generate point along axis (as well as tick points) double theta = this->GetAxisAngle(viewport); double normalizedAxis[3]; vtkMath::Subtract(axisEnd, axisStart, normalizedAxis); double axisLength = vtkMath::Normalize(normalizedAxis); int totalNumberOfTicks = static_cast(this->NormalizedTickPositions.size()); this->TicksStartPos->SetNumberOfPoints(totalNumberOfTicks); for (int tick = 0; tick < totalNumberOfTicks; tick++) { int tickLength = 0; if (tick % (this->NumberOfMinorTicks + 1) == 0) { tickLength = this->TickLength; } else { tickLength = this->MinorTickLength; } double tickPos[3]; tickPos[2] = 0; tickPos[0] = axisStart[0] + this->NormalizedTickPositions[tick] * normalizedAxis[0] * axisLength; tickPos[1] = axisStart[1] + this->NormalizedTickPositions[tick] * normalizedAxis[1] * axisLength; this->TicksStartPos->SetPoint(tick, tickPos); vtkIdType tickPoints[2]; tickPoints[0] = pts->InsertNextPoint(tickPos); tickPos[0] = tickPos[0] + tickLength * std::sin(theta); tickPos[1] = tickPos[1] - tickLength * std::cos(theta); tickPoints[1] = pts->InsertNextPoint(tickPos); if (this->TickVisibility) { lines->InsertNextCell(2, tickPoints); } } // last point axisPoints[1] = pts->InsertNextPoint(axisEnd); // Add the axis if requested if (this->AxisVisibility) { lines->InsertNextCell(2, axisPoints); } } //------------------------------------------------------------------------------ void vtkAxisActor2D::BuildLabels(vtkViewport* viewport) { // Update the labels text. Do it only if the range has been adjusted, // i.e. if we think that new labels must be created. // WARNING: if LabelFormat has changed, they should be recreated too // but at this point the check on LabelFormat is "included" in // UpdateAdjustedRange(), which is the function that update // AdjustedRangeBuildTime or not. vtkMTimeType labeltime = this->AdjustedRangeBuildTime; int nbOfLabels = static_cast(this->TickValues.size()); if (this->NumberOfLabelsBuilt != nbOfLabels) { vtkErrorMacro("Inconsistent number of labels. Got " << nbOfLabels << " values but expects " << this->NumberOfLabelsBuilt); } if (nbOfLabels < 1) { return; } if (this->AdjustedRangeBuildTime > this->BuildTime) { for (int i = 0; i < nbOfLabels; i++) { double val = this->TickValues[i]; if (this->GetNotation() == vtkNumberToString::Mixed) { // Use default legend notation : don't use vtkNumberToString // for the default setting in order to ensure retrocompatibility std::string labelFormat = this->LabelFormat ? vtk::to_std_format(this->LabelFormat) : ""; char string[512]; VTK_FORMAT_IF_ERROR_BREAK( auto result = vtk::format_to_n(string, sizeof(string), labelFormat, val); *result.out = '\0'); this->LabelMappers[i]->SetInput(string); } else { vtkNumberToString converter; converter.SetNotation(this->GetNotation()); converter.SetPrecision(this->GetPrecision()); std::string formattedString = converter.Convert(val); this->LabelMappers[i]->SetInput(formattedString.c_str()); } } // Check if the label text has changed labeltime = std::max(this->LabelMappers[this->NumberOfLabelsBuilt - 1]->GetMTime(), labeltime); } // Copy prop and text prop eventually for (int i = 0; i < this->NumberOfLabelsBuilt; i++) { if (this->LabelTextProperty->GetMTime() > this->BuildTime || this->AdjustedRangeBuildTime > this->BuildTime) { // Shallow copy here so that the size of the label prop is not // affected by the automatic adjustment of its text mapper's // size (i.e. its mapper's text property is identical except // for the font size which will be modified later). This // allows text actors to share the same text property, and in // that case specifically allows the title and label text prop // to be the same. this->LabelMappers[i]->GetTextProperty()->ShallowCopy(this->LabelTextProperty); } } int* size = viewport->GetSize(); double *xp1, *xp2, len = 0.0; if (this->SizeFontRelativeToAxis) { xp1 = this->PositionCoordinate->GetComputedDoubleViewportValue(viewport); xp2 = this->Position2Coordinate->GetComputedDoubleViewportValue(viewport); len = std::sqrt((xp2[0] - xp1[0]) * (xp2[0] - xp1[0]) + (xp2[1] - xp1[1]) * (xp2[1] - xp1[1])); } // Resize the mappers if needed (i.e. viewport has changed, than // font size should be changed, or label text property has changed, // or some of the labels have changed (got bigger for example) if (this->PositionsChangedOrViewportResized(viewport) || this->LabelTextProperty->GetMTime() > this->BuildTime || labeltime > this->BuildTime) { if (!this->UseFontSizeFromProperty) { if (!this->SizeFontRelativeToAxis) { std::vector rawMappers; rawMappers.reserve(this->LabelMappers.size()); for (auto& m : this->LabelMappers) { rawMappers.push_back(m); } vtkTextMapper::SetMultipleRelativeFontSize(viewport, rawMappers.data(), static_cast(rawMappers.size()), size, this->LastMaxLabelSize, 0.015 * this->FontFactor * this->LabelFactor); } else { int minFontSize = details::MAX_FONT_SIZE; int fontSize; int minLabel = 0; for (int i = 0; i < this->NumberOfLabelsBuilt; i++) { fontSize = this->LabelMappers[i]->SetConstrainedFontSize(viewport, static_cast((1.0 / this->NumberOfLabelsBuilt) * len), static_cast(0.2 * len)); if (fontSize < minFontSize) { minFontSize = fontSize; minLabel = i; } } for (int i = 0; i < this->NumberOfLabelsBuilt; i++) { this->LabelMappers[i]->GetTextProperty()->SetFontSize(minFontSize); } this->LabelMappers[minLabel]->GetSize(viewport, this->LastMaxLabelSize); } } else { this->LabelMappers[0]->GetSize(viewport, this->LastMaxLabelSize); } } vtkPoints* pts = this->Axis->GetPoints(); const vtkIdType nbOfPoints = pts->GetNumberOfPoints(); // Position the mappers for (int i = 0; i < this->NumberOfLabelsBuilt; i++) { double xTick[3]; vtkIdType tickId = (this->NumberOfMinorTicks + 1) * i; // first point in the list is the Axis start, not a tick point. vtkIdType startPointId = tickId * 2 + 1; vtkIdType endPointId = startPointId + 1; if (endPointId >= nbOfPoints) { vtkErrorMacro("Trying to use an inexisting tick when computing label position."); return; } pts->GetPoint(endPointId, xTick); double theta = this->GetAxisAngle(viewport); double textAngle = this->LabelTextProperty->GetOrientation(); textAngle = vtkMath::RadiansFromDegrees(textAngle); vtkAxisActor2D::SetOffsetPosition(xTick, theta, this->LastMaxLabelSize[0], this->LastMaxLabelSize[1], this->TickOffset, this->LabelActors[i]); } } //------------------------------------------------------------------------------ void vtkAxisActor2D::SetTitleFontSize(vtkViewport* viewport, int stringSize[2]) { // the mapper returns the global bounding box. Artificially set orientation to 0 // in this scope to get the local bounding box in `stringSize` double originalAngle = this->TitleMapper->GetTextProperty()->GetOrientation(); this->TitleMapper->GetTextProperty()->SetOrientation(0); if (this->PositionsChangedOrViewportResized(viewport) || this->TitleTextProperty->GetMTime() > this->BuildTime) { if (!this->UseFontSizeFromProperty) { if (!this->SizeFontRelativeToAxis) { int* size = viewport->GetSize(); vtkTextMapper::SetRelativeFontSize( this->TitleMapper, viewport, size, stringSize, 0.015 * this->FontFactor); } else { double *xp1, *xp2, len = 0.0; if (this->SizeFontRelativeToAxis) { xp1 = this->PositionCoordinate->GetComputedDoubleViewportValue(viewport); xp2 = this->Position2Coordinate->GetComputedDoubleViewportValue(viewport); len = std::sqrt( (xp2[0] - xp1[0]) * (xp2[0] - xp1[0]) + (xp2[1] - xp1[1]) * (xp2[1] - xp1[1])); } this->TitleMapper->SetConstrainedFontSize( viewport, static_cast(0.33 * len), static_cast(0.2 * len)); this->TitleMapper->GetSize(viewport, stringSize); } } else { this->TitleMapper->GetSize(viewport, stringSize); } } else { this->TitleMapper->GetSize(viewport, stringSize); } // Restore the orientation. this->TitleMapper->GetTextProperty()->SetOrientation(originalAngle); } //------------------------------------------------------------------------------ void vtkAxisActor2D::BuildTitle(vtkViewport* viewport) { this->TitleMapper->SetInput(this->Title); if (this->TitleTextProperty->GetMTime() > this->BuildTime) { // Shallow copy here so that the size of the title prop is not // affected by the automatic adjustment of its text mapper's // size (i.e. its mapper's text property is identical except for // the font size which will be modified later). This allows text // actors to share the same text property, and in that case // specifically allows the title and label text prop to be the same. this->TitleMapper->GetTextProperty()->ShallowCopy(this->TitleTextProperty); } int stringSize[2]; this->SetTitleFontSize(viewport, stringSize); int* x1 = this->PositionCoordinate->GetComputedViewportValue(viewport); int* x2 = this->Position2Coordinate->GetComputedViewportValue(viewport); double tickPosition[3]; tickPosition[0] = x1[0] + (x2[0] - x1[0]) * this->TitlePosition; tickPosition[1] = x1[1] + (x2[1] - x1[1]) * this->TitlePosition; double textOrientation = this->TitleTextProperty->GetOrientation(); double theta = vtkMath::RadiansFromDegrees(textOrientation); double offset = this->TickLength + this->TickOffset; if (this->LabelVisibility) { offset += vtkAxisActor2D::ComputeStringOffset( this->LastMaxLabelSize[0], this->LastMaxLabelSize[1], theta); } int textPos[2]; this->ShiftPosition( tickPosition, theta, stringSize[0], stringSize[1], static_cast(offset), textPos); this->TitleActor->SetPosition(textPos[0], textPos[1]); } //------------------------------------------------------------------------------ void vtkAxisActor2D::UpdateCachedInformations(vtkViewport* viewport) { int* x = this->PositionCoordinate->GetComputedViewportValue(viewport); this->LastPosition[0] = x[0]; this->LastPosition[1] = x[1]; x = this->Position2Coordinate->GetComputedViewportValue(viewport); this->LastPosition2[0] = x[0]; this->LastPosition2[1] = x[1]; int* size = viewport->GetSize(); this->LastSize[0] = size[0]; this->LastSize[1] = size[1]; } //------------------------------------------------------------------------------ void vtkAxisActor2D::BuildAxis(vtkViewport* viewport) { if (!this->ShouldRebuild(viewport)) { return; } vtkDebugMacro(<< "Rebuilding axis"); this->AxisActor->SetProperty(this->GetProperty()); this->UpdateAdjustedRange(); this->UpdateTicksValueAndPosition(viewport); int nLabels = this->NumberOfLabelsBuilt; if (this->LabelActors.size() < static_cast(nLabels)) { size_t oldSize = this->LabelActors.size(); this->LabelActors.resize(nLabels); this->LabelMappers.resize(nLabels); for (size_t i = oldSize; i < static_cast(nLabels); ++i) { this->LabelMappers[i] = vtkSmartPointer::New(); this->LabelActors[i] = vtkSmartPointer::New(); this->LabelActors[i]->SetMapper(this->LabelMappers[i]); } } else if (this->LabelActors.size() > static_cast(nLabels)) { this->LabelActors.resize(nLabels); this->LabelMappers.resize(nLabels); } this->BuildTicksPolyData(viewport); if (this->LabelVisibility) { this->BuildLabels(viewport); } if (this->Title != nullptr && this->Title[0] != 0 && this->TitleVisibility) { this->BuildTitle(viewport); } this->UpdateCachedInformations(viewport); this->BuildTime.Modified(); } //------------------------------------------------------------------------------ vtkPoints* vtkAxisActor2D::GetTickPositions() { return this->TicksStartPos; } //------------------------------------------------------------------------------ void vtkAxisActor2D::UpdateAdjustedRange() { // Try not to update/adjust the range to often, do not update it // if the object has not been modified. // Nevertheless, try the following optimization: there is no need to // update the range if the position coordinate of this actor have // changed. But since vtkActor2D::GetMTime() includes the check for // both Position and Position2 coordinates, we will have to bypass // it. // NOLINTNEXTLINE(bugprone-parent-virtual-call) if (this->vtkActor2D::Superclass::GetMTime() <= this->AdjustedRangeBuildTime) { return; } if (this->SnapLabelsToGrid) { details::AdjustAndSplitRange( this->Range, this->NumberOfLabels, this->AdjustedRange, this->AdjustedNumberOfLabels); } else if (this->AdjustLabels) { double interval; vtkAxisActor2D::ComputeRange(this->Range, this->AdjustedRange, this->NumberOfLabels, this->AdjustedNumberOfLabels, interval); } else { this->AdjustedNumberOfLabels = this->NumberOfLabels; this->AdjustedRange[0] = this->Range[0]; this->AdjustedRange[1] = this->Range[1]; } if (this->RulerMode) { double wp1[3], wp2[3], wp21[3]; this->PositionCoordinate->GetValue(wp1); this->Position2Coordinate->GetValue(wp2); vtkMath::Subtract(wp2, wp1, wp21); const double worldLength = vtkMath::Norm(wp21); this->AdjustedNumberOfLabels = worldLength / this->RulerDistance; if (vtkMathUtilities::FuzzyCompare( this->AdjustedNumberOfLabels * this->RulerDistance, worldLength)) { this->AdjustedNumberOfLabels += 1; } this->AdjustedNumberOfLabels += 2; } if (this->AdjustedNumberOfLabels < 1) { vtkWarningMacro( "Axis expects to have at least 1 label. Will use 1 instead of the computed number " << this->AdjustedNumberOfLabels); this->AdjustedNumberOfLabels = 1; } this->AdjustedRangeBuildTime.Modified(); } //------------------------------------------------------------------------------ void vtkAxisActor2D::ComputeRange(double inRange[2], double outRange[2], int vtkNotUsed(inNumTicks), int& numTicks, double& interval) { legacy::ComputeRange(inRange, outRange, numTicks, interval); } //------------------------------------------------------------------------------ // Position text with respect to a point (xTick) where the angle of the line // from the point to the center of the text is given by theta. The offset // is the spacing between ticks and labels. void vtkAxisActor2D::SetOffsetPosition( double xTick[3], double theta, int stringWidth, int stringHeight, int offset, vtkActor2D* actor) { double x, y, center[2]; int pos[2]; x = stringWidth / 2.0 + offset; y = stringHeight / 2.0 + offset; center[0] = xTick[0] + x * std::sin(theta); center[1] = xTick[1] - y * std::cos(theta); pos[0] = static_cast(center[0] - stringWidth / 2.0); pos[1] = static_cast(center[1] - stringHeight / 2.0); actor->SetPosition(pos[0], pos[1]); } //------------------------------------------------------------------------------ void vtkAxisActor2D::ShiftPosition( double xTick[3], double textAngle, int stringWidth, int stringHeight, int offset, int finalPos[2]) { // Text Horizontal: center text finalPos[0] = xTick[0] - stringWidth / 2 * std::cos(textAngle); finalPos[1] = xTick[1] - stringWidth / 2 * std::sin(textAngle); // Text Vertical: put text "under" axes finalPos[0] += stringHeight * std::sin(textAngle); finalPos[1] -= stringHeight * std::cos(textAngle); // Axis Vertical: add extra offset finalPos[0] += offset * std::sin(textAngle); finalPos[1] -= offset * std::cos(textAngle); } //------------------------------------------------------------------------------ double vtkAxisActor2D::ComputeStringOffset(double width, double height, double theta) { double f1 = height * std::cos(theta); double f2 = width * std::sin(theta); return (1.2 * std::sqrt(f1 * f1 + f2 * f2)); } //------------------------------------------------------------------------------ void vtkAxisActor2D::ShallowCopy(vtkProp* prop) { vtkAxisActor2D* a = vtkAxisActor2D::SafeDownCast(prop); if (a != nullptr) { this->SetRange(a->GetRange()); this->SetNumberOfLabels(a->GetNumberOfLabels()); this->SetLabelFormat(a->GetLabelFormat()); this->SetAdjustLabels(a->GetAdjustLabels()); this->SetSnapLabelsToGrid(a->GetSnapLabelsToGrid()); this->SetTitle(a->GetTitle()); this->SetTickLength(a->GetTickLength()); this->SetTickOffset(a->GetTickOffset()); this->SetAxisVisibility(a->GetAxisVisibility()); this->SetTickVisibility(a->GetTickVisibility()); this->SetLabelVisibility(a->GetLabelVisibility()); this->SetTitleVisibility(a->GetTitleVisibility()); this->SetFontFactor(a->GetFontFactor()); this->SetLabelFactor(a->GetLabelFactor()); this->SetLabelTextProperty(a->GetLabelTextProperty()); this->SetTitleTextProperty(a->GetTitleTextProperty()); } // Now do superclass this->vtkActor2D::ShallowCopy(prop); } VTK_ABI_NAMESPACE_END