// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkLabeledDataMapper.h" #include "vtkActor2D.h" #include "vtkArrayDispatch.h" #include "vtkCompositeDataIterator.h" #include "vtkCompositeDataSet.h" #include "vtkDataArray.h" #include "vtkDataArrayRange.h" #include "vtkDataSet.h" #include "vtkInformation.h" #include "vtkIntArray.h" #include "vtkObjectFactory.h" #include "vtkPlaneCollection.h" #include "vtkPointData.h" #include "vtkPointSet.h" #include "vtkSmartPointer.h" #include "vtkStringArray.h" #include "vtkStringFormatter.h" #include "vtkTextMapper.h" #include "vtkTextProperty.h" #include "vtkTransform.h" #include VTK_ABI_NAMESPACE_BEGIN class vtkLabeledDataMapper::Internals { public: std::map> TextProperties; }; vtkStandardNewMacro(vtkLabeledDataMapper); vtkCxxSetObjectMacro(vtkLabeledDataMapper, Transform, vtkTransform); //------------------------------------------------------------------------------ // Creates a new label mapper vtkLabeledDataMapper::vtkLabeledDataMapper() { this->Implementation = new Internals; this->Input = nullptr; this->LabelMode = VTK_LABEL_IDS; this->LabelFormat = nullptr; this->LabeledComponent = (-1); this->FieldDataArray = 0; this->FieldDataName = nullptr; this->NumberOfLabels = 0; this->NumberOfLabelsAllocated = 0; this->LabelPositions = nullptr; this->TextMappers = nullptr; this->AllocateLabels(50); this->ComponentSeparator = ' '; vtkSmartPointer prop = vtkSmartPointer::New(); prop->SetFontSize(12); prop->SetBold(1); prop->SetItalic(1); prop->SetShadow(1); prop->SetFontFamilyToArial(); this->Implementation->TextProperties[0] = prop; this->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "type"); this->Transform = nullptr; this->CoordinateSystem = vtkLabeledDataMapper::WORLD; } //------------------------------------------------------------------------------ vtkLabeledDataMapper::~vtkLabeledDataMapper() { delete[] this->LabelFormat; delete[] this->LabelPositions; if (this->TextMappers != nullptr) { for (int i = 0; i < this->NumberOfLabelsAllocated; i++) { this->TextMappers[i]->Delete(); } delete[] this->TextMappers; } this->SetFieldDataName(nullptr); this->SetTransform(nullptr); delete this->Implementation; } //------------------------------------------------------------------------------ void vtkLabeledDataMapper::AllocateLabels(int numLabels) { if (numLabels > this->NumberOfLabelsAllocated) { int i; // delete old stuff delete[] this->LabelPositions; this->LabelPositions = nullptr; for (i = 0; i < this->NumberOfLabelsAllocated; i++) { this->TextMappers[i]->Delete(); } delete[] this->TextMappers; this->TextMappers = nullptr; this->NumberOfLabelsAllocated = numLabels; // Allocate and initialize new stuff this->LabelPositions = new double[this->NumberOfLabelsAllocated * 3]; this->TextMappers = new vtkTextMapper*[this->NumberOfLabelsAllocated]; for (i = 0; i < this->NumberOfLabelsAllocated; i++) { this->TextMappers[i] = vtkTextMapper::New(); this->LabelPositions[3 * i] = 0; this->LabelPositions[3 * i + 1] = 0; this->LabelPositions[3 * i + 2] = 0; } } } //------------------------------------------------------------------------------ void vtkLabeledDataMapper::SetLabelTextProperty(vtkTextProperty* prop, int type) { this->Implementation->TextProperties[type] = prop; this->Modified(); } //------------------------------------------------------------------------------ vtkTextProperty* vtkLabeledDataMapper::GetLabelTextProperty(int type) { if (this->Implementation->TextProperties.find(type) != this->Implementation->TextProperties.end()) { return this->Implementation->TextProperties[type]; } return nullptr; } //------------------------------------------------------------------------------ void vtkLabeledDataMapper::SetInputData(vtkDataObject* input) { this->SetInputDataInternal(0, input); } //------------------------------------------------------------------------------ // Specify the input data or filter. vtkDataSet* vtkLabeledDataMapper::GetInput() { return vtkDataSet::SafeDownCast(this->GetInputDataObject(0, 0)); } //------------------------------------------------------------------------------ // Release any graphics resources that are being consumed by this mapper. void vtkLabeledDataMapper::ReleaseGraphicsResources(vtkWindow* win) { if (this->TextMappers != nullptr) { for (int i = 0; i < this->NumberOfLabelsAllocated; i++) { this->TextMappers[i]->ReleaseGraphicsResources(win); } } } //------------------------------------------------------------------------------ void vtkLabeledDataMapper::RenderOverlay(vtkViewport* viewport, vtkActor2D* actor) { for (int i = 0; i < this->NumberOfLabels; i++) { double x[3]; x[0] = this->LabelPositions[3 * i]; x[1] = this->LabelPositions[3 * i + 1]; x[2] = this->LabelPositions[3 * i + 2]; double* pos = x; if (this->Transform) { pos = this->Transform->TransformDoublePoint(x); } if (this->CoordinateSystem == vtkLabeledDataMapper::WORLD) { actor->GetPositionCoordinate()->SetCoordinateSystemToWorld(); actor->GetPositionCoordinate()->SetValue(pos); } else if (this->CoordinateSystem == vtkLabeledDataMapper::DISPLAY) { actor->GetPositionCoordinate()->SetCoordinateSystemToDisplay(); actor->GetPositionCoordinate()->SetValue(pos); } bool show = true; if (this->ClippingPlanes) { for (int p = 0; p < this->GetNumberOfClippingPlanes(); ++p) { if (this->ClippingPlanes->GetItem(p)->FunctionValue(pos) < 0.0) { show = false; } } } if (show) { this->TextMappers[i]->RenderOverlay(viewport, actor); } } } //------------------------------------------------------------------------------ void vtkLabeledDataMapper::RenderOpaqueGeometry(vtkViewport* viewport, vtkActor2D* actor) { vtkTextProperty* tprop = this->Implementation->TextProperties[0]; if (!tprop) { vtkErrorMacro(<< "Need default text property to render labels"); return; } // Updates the input pipeline if needed. this->Update(); vtkDataObject* inputDO = this->GetInputDataObject(0, 0); if (!inputDO) { this->NumberOfLabels = 0; vtkErrorMacro(<< "Need input data to render labels (2)"); return; } // Check for property updates. vtkMTimeType propMTime = 0; std::map>::iterator it, itEnd; it = this->Implementation->TextProperties.begin(); itEnd = this->Implementation->TextProperties.end(); for (; it != itEnd; ++it) { vtkTextProperty* prop = it->second; if (prop && prop->GetMTime() > propMTime) { propMTime = prop->GetMTime(); } } // Check to see whether we have to rebuild everything if (this->GetMTime() > this->BuildTime || inputDO->GetMTime() > this->BuildTime || propMTime > this->BuildTime) { this->BuildLabels(); } for (int i = 0; i < this->NumberOfLabels; i++) { double* pos = &this->LabelPositions[3 * i]; if (this->Transform) { pos = this->Transform->TransformDoublePoint(pos); } if (this->CoordinateSystem == vtkLabeledDataMapper::WORLD) { actor->GetPositionCoordinate()->SetCoordinateSystemToWorld(); actor->GetPositionCoordinate()->SetValue(pos); } else if (this->CoordinateSystem == vtkLabeledDataMapper::DISPLAY) { actor->GetPositionCoordinate()->SetCoordinateSystemToDisplay(); actor->GetPositionCoordinate()->SetValue(pos); } bool show = true; if (this->ClippingPlanes) { for (int p = 0; p < this->GetNumberOfClippingPlanes(); ++p) { if (this->ClippingPlanes->GetItem(p)->FunctionValue(pos) < 0.0) { show = false; } } } if (show) { this->TextMappers[i]->RenderOpaqueGeometry(viewport, actor); } } } //------------------------------------------------------------------------------ void vtkLabeledDataMapper::BuildLabels() { vtkDebugMacro(<< "Rebuilding labels"); vtkDataObject* inputDO = this->GetInputDataObject(0, 0); vtkCompositeDataSet* cd = vtkCompositeDataSet::SafeDownCast(inputDO); vtkDataSet* ds = vtkDataSet::SafeDownCast(inputDO); if (ds) { this->AllocateLabels(ds->GetNumberOfPoints()); this->NumberOfLabels = 0; this->BuildLabelsInternal(ds); } else if (cd) { this->AllocateLabels(cd->GetNumberOfPoints()); this->NumberOfLabels = 0; vtkCompositeDataIterator* iter = cd->NewIterator(); for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem()) { ds = vtkDataSet::SafeDownCast(iter->GetCurrentDataObject()); if (ds) { this->BuildLabelsInternal(ds); } } iter->Delete(); } else { vtkErrorMacro("Unsupported data type: " << inputDO->GetClassName()); } this->BuildTime.Modified(); } //------------------------------------------------------------------------------ struct vtkLabeledDataMapper::vtkLabeledDataMapperFunctor { vtkLabeledDataMapper* Self; vtkIntArray* TypeArr; vtkDataSet* Input; int NumCurLabels; vtkLabeledDataMapperFunctor( vtkLabeledDataMapper* self, vtkIntArray* typeArr, vtkDataSet* input, int numCurLabels) : Self(self) , TypeArr(typeArr) , Input(input) , NumCurLabels(numCurLabels) { } void SetFormattedString(int i, const char* resultString) { this->Self->TextMappers[i + this->Self->NumberOfLabels]->SetInput(resultString); // Find the correct property type int type = 0; if (this->TypeArr) { type = this->TypeArr->GetValue(i); } vtkTextProperty* prop = this->Self->Implementation->TextProperties[type]; if (!prop) { prop = this->Self->Implementation->TextProperties[0]; } this->Self->TextMappers[i + this->Self->NumberOfLabels]->SetTextProperty(prop); double x[3]; this->Input->GetPoint(i, x); this->Self->LabelPositions[3 * (i + this->Self->NumberOfLabels)] = x[0]; this->Self->LabelPositions[3 * (i + this->Self->NumberOfLabels) + 1] = x[1]; this->Self->LabelPositions[3 * (i + this->Self->NumberOfLabels) + 2] = x[2]; } void operator()(const std::string& FormatString) { char formatedString[1024]; for (int i = 0; i < this->NumCurLabels; i++) { VTK_FORMAT_IF_ERROR_RETURN( auto result = vtk::format_to_n(formatedString, sizeof(formatedString), FormatString, i); *result.out = '\0', ); this->SetFormattedString(i, formatedString); } } struct NumericComponent { }; template void operator()(TArray* array, int activeComp, const std::string& FormatString, NumericComponent) { char formatedString[1024]; auto a = vtk::DataArrayTupleRange(array); using ValueType = vtk::GetAPIType; for (int i = 0; i < this->NumCurLabels; i++) { VTK_FORMAT_IF_ERROR_RETURN( auto result = vtk::format_to_n(formatedString, sizeof(formatedString), FormatString, static_cast(a[i][activeComp])); *result.out = '\0', ); this->SetFormattedString(i, formatedString); } } struct NumericVector { }; template void operator()(TArray* array, int numComp, const std::string& FormatString, NumericVector) { char formatedString[1024]; std::string ResultString; auto a = vtk::DataArrayTupleRange(array); using ValueType = vtk::GetAPIType; for (int i = 0; i < this->NumCurLabels; i++) { ResultString = "("; // Print each component in turn and add it to the string. for (int j = 0; j < numComp; ++j) { VTK_FORMAT_IF_ERROR_RETURN( auto result = vtk::format_to_n( formatedString, sizeof(formatedString), FormatString, static_cast(a[i][j])); *result.out = '\0', ); ResultString += formatedString; if (j < (numComp - 1)) { ResultString += this->Self->GetComponentSeparator(); } else { ResultString += ')'; } } this->SetFormattedString(i, ResultString.c_str()); } } void operator()(vtkStringArray* array, const std::string& FormatString) { char formatedString[1024]; for (int i = 0; i < this->NumCurLabels; i++) { // If the user hasn't given us a custom format string then just save the value. if (!this->Self->LabelFormat || std::string_view(this->Self->LabelFormat).empty()) { this->SetFormattedString(i, array->GetValue(i).c_str()); } else // the user specified a label format { VTK_FORMAT_IF_ERROR_RETURN( auto result = vtk::format_to_n(formatedString, sizeof(formatedString), FormatString, static_cast(array->GetValue(i))); *result.out = '\0', ); this->SetFormattedString(i, formatedString); } } } }; //------------------------------------------------------------------------------ void vtkLabeledDataMapper::BuildLabelsInternal(vtkDataSet* input) { int numComp = 0, pointIdLabels = 0, activeComp = 0; vtkAbstractArray* abstractData = nullptr; vtkDataArray* numericData = nullptr; vtkStringArray* stringData = nullptr; if (input->GetNumberOfPoints() == 0) { return; } vtkPointData* pd = input->GetPointData(); vtkIntArray* typeArr = vtkArrayDownCast(this->GetInputAbstractArrayToProcess(0, input)); // figure out what to label, and if we can label it pointIdLabels = 0; switch (this->LabelMode) { case VTK_LABEL_IDS: { pointIdLabels = 1; } break; case VTK_LABEL_SCALARS: if (pd->GetScalars()) { numericData = pd->GetScalars(); } break; case VTK_LABEL_VECTORS: if (pd->GetVectors()) { numericData = pd->GetVectors(); } break; case VTK_LABEL_NORMALS: if (pd->GetNormals()) { numericData = pd->GetNormals(); } break; case VTK_LABEL_TCOORDS: if (pd->GetTCoords()) { numericData = pd->GetTCoords(); } break; case VTK_LABEL_TENSORS: if (pd->GetTensors()) { numericData = pd->GetTensors(); } break; case VTK_LABEL_FIELD_DATA: { int arrayNum; if (this->FieldDataName != nullptr) { vtkDebugMacro(<< "Labeling field data array " << this->FieldDataName); abstractData = pd->GetAbstractArray(this->FieldDataName, arrayNum); } else { arrayNum = (this->FieldDataArray < pd->GetNumberOfArrays() ? this->FieldDataArray : pd->GetNumberOfArrays() - 1); abstractData = pd->GetAbstractArray(arrayNum); } numericData = vtkArrayDownCast(abstractData); stringData = vtkArrayDownCast(abstractData); } break; } // determine number of components and check input if (pointIdLabels) { numComp = 1; } else if (numericData) { numComp = numericData->GetNumberOfComponents(); activeComp = 0; if (this->LabeledComponent >= 0) { activeComp = (this->LabeledComponent < numComp ? this->LabeledComponent : numComp - 1); numComp = 1; } } else { if (stringData) { numComp = stringData->GetNumberOfComponents(); } else { if (this->FieldDataName) { vtkWarningMacro(<< "Could not find label array (" << this->FieldDataName << ") " << "in input."); } else { vtkWarningMacro(<< "Could not find label array (" << "index " << this->FieldDataArray << ") " << "in input."); } return; } } std::string formatString; if (this->LabelFormat && !std::string_view(this->LabelFormat).empty()) { // The user has specified a format string. vtkDebugMacro(<< "Using user-specified format string " << this->LabelFormat); formatString = vtk::to_std_format(this->LabelFormat); } else { // Try to come up with some sane default. if (pointIdLabels) { formatString = "{:d}"; } else if (numericData) { switch (numericData->GetDataType()) { case VTK_VOID: formatString = "0x{:x}"; break; case VTK_BIT: case VTK_SHORT: case VTK_UNSIGNED_SHORT: case VTK_INT: case VTK_UNSIGNED_INT: formatString = "{:d}"; break; case VTK_CHAR: case VTK_SIGNED_CHAR: case VTK_UNSIGNED_CHAR: formatString = "{:c}"; break; case VTK_LONG: case VTK_UNSIGNED_LONG: case VTK_ID_TYPE: case VTK_LONG_LONG: case VTK_UNSIGNED_LONG_LONG: formatString = "{:d}"; break; case VTK_FLOAT: case VTK_DOUBLE: formatString = "{:f}"; break; default: formatString = "BUG - UNKNOWN DATA FORMAT"; break; } } else if (stringData) { formatString = ""; } else { formatString = "BUG - COULDN'T DETECT DATA TYPE"; } vtkDebugMacro(<< "Using default format string " << formatString); } // Done building default format string int numCurLabels = input->GetNumberOfPoints(); // We are assured that // this->NumberOfLabelsAllocated >= (this->NumberOfLabels + numCurLabels) if (this->NumberOfLabelsAllocated < (this->NumberOfLabels + numCurLabels)) { vtkErrorMacro("Number of labels must be allocated before this method is called."); return; } // ---------------------------------------- // Now we actually construct the label strings // vtkLabeledDataMapperFunctor functor(this, typeArr, input, numCurLabels); if (pointIdLabels) { functor(formatString); } else if (numericData) { if (numComp == 1) { if (!vtkArrayDispatch::Dispatch::Execute(numericData, functor, activeComp, formatString, vtkLabeledDataMapperFunctor::NumericComponent())) { functor( numericData, activeComp, formatString, vtkLabeledDataMapperFunctor::NumericComponent()); } } else { if (!vtkArrayDispatch::Dispatch::Execute(numericData, functor, numComp, formatString, vtkLabeledDataMapperFunctor::NumericVector())) { functor(numericData, numComp, formatString, vtkLabeledDataMapperFunctor::NumericVector()); } } } else // rendering string data { functor(stringData, formatString); } this->NumberOfLabels += numCurLabels; } //------------------------------------------------------------------------------ int vtkLabeledDataMapper::FillInputPortInformation(int vtkNotUsed(port), vtkInformation* info) { // Can handle composite datasets. info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataObject"); return 1; } //------------------------------------------------------------------------------ void vtkLabeledDataMapper::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); if (this->Input) { os << indent << "Input: (" << this->Input << ")\n"; } else { os << indent << "Input: (none)\n"; } std::map>::iterator it, itEnd; it = this->Implementation->TextProperties.begin(); itEnd = this->Implementation->TextProperties.end(); for (; it != itEnd; ++it) { vtkTextProperty* prop = it->second; if (prop) { os << indent << "LabelTextProperty " << it->first << ":\n"; prop->PrintSelf(os, indent.GetNextIndent()); } else { os << indent << "LabelTextProperty " << it->first << ": (none)\n"; } } os << indent << "Label Mode: "; if (this->LabelMode == VTK_LABEL_IDS) { os << "Label Ids\n"; } else if (this->LabelMode == VTK_LABEL_SCALARS) { os << "Label Scalars\n"; } else if (this->LabelMode == VTK_LABEL_VECTORS) { os << "Label Vectors\n"; } else if (this->LabelMode == VTK_LABEL_NORMALS) { os << "Label Normals\n"; } else if (this->LabelMode == VTK_LABEL_TCOORDS) { os << "Label TCoords\n"; } else if (this->LabelMode == VTK_LABEL_TENSORS) { os << "Label Tensors\n"; } else { os << "Label Field Data\n"; } os << indent << "Label Format: " << (this->LabelFormat ? this->LabelFormat : "Null") << "\n"; os << indent << "Labeled Component: "; if (this->LabeledComponent < 0) { os << "(All Components)\n"; } else { os << this->LabeledComponent << "\n"; } os << indent << "Field Data Array: " << this->FieldDataArray << "\n"; os << indent << "Field Data Name: " << (this->FieldDataName ? this->FieldDataName : "Null") << "\n"; os << indent << "Transform: " << (this->Transform ? "" : "(none)") << endl; if (this->Transform) { this->Transform->PrintSelf(os, indent.GetNextIndent()); } os << indent << "CoordinateSystem: " << this->CoordinateSystem << endl; } //------------------------------------------------------------------------------ vtkMTimeType vtkLabeledDataMapper::GetMTime() { vtkMTimeType mtime = this->Superclass::GetMTime(); std::map>::iterator it, itEnd; it = this->Implementation->TextProperties.begin(); itEnd = this->Implementation->TextProperties.end(); for (; it != itEnd; ++it) { vtkTextProperty* p = it->second; vtkMTimeType curMTime = p->GetMTime(); mtime = std::max(curMTime, mtime); } return mtime; } //------------------------------------------------------------------------------ const char* vtkLabeledDataMapper::GetLabelText(int label) { assert("label index range" && label >= 0 && label < this->NumberOfLabels); return this->TextMappers[label]->GetInput(); } VTK_ABI_NAMESPACE_END