feat: 修正单值属性色标映射与布局

- 将自动单值属性映射到当前色带的最小端颜色
- 在色带上下端显示真实值,手工范围恢复连续渐变
- 完善 Linear 和 Log 单值内部范围校验
- 在读取色带矩形前刷新 VTK 内部布局
- 修正标题、端点标签及上下越界说明的定位与避让
- 同步标题、标签格式和刻度数量变化后的 Overlay 布局
feature/nmNum-postprocessing-view-20260910
lh 1 month ago
parent a8e7d8a79e
commit 14a9e8f627

@ -39,7 +39,7 @@ public:
bool setLabelFormat(const QString& sFormat);
/** @brief 返回调用方最后设置的普通刻度格式。 */
QString getLabelFormat() const;
/** @brief 设置 0~64 个普通刻度;常量色标使用 0。 */
/** @brief 设置 0~64 个普通刻度;单值色标使用 0。 */
bool setNumberOfLabels(int nLabelCount);
/** @brief 返回当前普通刻度数量。 */
int getNumberOfLabels() const;
@ -57,9 +57,9 @@ public:
void setAboveRangeSwatch(bool bVisible, const QString& sText);
/**
* @brief 显示或隐藏常量属性的唯一业务值。
* @brief 在色带上下端同时显示或隐藏单值属性的真实业务值。
*
* 该文字由独立 vtkTextActor 绘制,不启用共享 LUT 的 IndexedLookup,
* 两个文字由独立 vtkTextActor 绘制,不启用共享 LUT 的 IndexedLookup,
* 因而不会改变网格使用的连续颜色映射语义。
*/
void setConstantValueLabel(bool bVisible, const QString& sText);
@ -84,7 +84,8 @@ private:
vtkSmartPointer<vtkPropAssembly> m_pRootAssembly; ///< Scene 挂载的稳定根 Prop。
vtkSmartPointer<vtkScalarBarActor> m_pActor; ///< 图层独占的二维色标 Actor。
vtkSmartPointer<vtkTextActor> m_pTitleActor; ///< 独立左对齐 ASCII 标题。
vtkSmartPointer<vtkTextActor> m_pConstantValueActor; ///< 常量属性唯一值文字。
vtkSmartPointer<vtkTextActor> m_pConstantMinimumValueActor; ///< 单值属性下端值文字。
vtkSmartPointer<vtkTextActor> m_pConstantMaximumValueActor; ///< 单值属性上端值文字。
vtkSmartPointer<vtkPoints> m_pBelowRangeSwatchPoints; ///< 下越界色块四角坐标。
vtkSmartPointer<vtkActor2D> m_pBelowRangeSwatchActor; ///< 下越界独立色块。
vtkSmartPointer<vtkTextActor> m_pBelowRangeTextActor; ///< 下越界说明文字。
@ -93,7 +94,7 @@ private:
vtkSmartPointer<vtkTextActor> m_pAboveRangeTextActor; ///< 上越界说明文字。
QString m_sTitle; ///< 保留无损 Qt 标题,避免从 VTK 窄字符串反向解析。
QString m_sLabelFormat; ///< 普通刻度的 printf 风格格式字符串。
QString m_sConstantValueText; ///< 保留常量值文字,避免从 VTK 反向解析。
QString m_sConstantValueText; ///< 保留单值属性文字,避免从 VTK 反向解析。
QString m_sBelowRangeText; ///< 下越界说明文字状态。
QString m_sAboveRangeText; ///< 上越界说明文字状态。
double m_dPositionX; ///< 色标左下角的归一化视口 X 坐标。

@ -10,17 +10,51 @@
#include <vtkCoordinate.h>
#include <vtkLookupTable.h>
#include <vtkMath.h>
#include <vtkObjectFactory.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper2D.h>
#include <vtkPropAssembly.h>
#include <vtkProperty2D.h>
#include <vtkRenderer.h>
#include <vtkScalarBarActor.h>
#include <vtkTextActor.h>
#include <vtkTextProperty.h>
namespace {
/**
* @brief 只公开 VTK 色标的按需布局入口,供独立 Overlay 在 Render 前读取新几何。
*/
class vtkNmScalarBarActor : public vtkScalarBarActor
{
public:
static vtkNmScalarBarActor* New();
vtkTypeMacro(vtkNmScalarBarActor, vtkScalarBarActor);
/** @brief 立即同步内部色带、刻度和文字布局,不触发 Renderer 绘制。 */
int refreshLayout(vtkViewport* pViewport)
{
return pViewport != nullptr
? RebuildLayoutIfNeeded(pViewport) : 0;
}
protected:
vtkNmScalarBarActor()
{
}
virtual ~vtkNmScalarBarActor()
{
}
private:
vtkNmScalarBarActor(const vtkNmScalarBarActor&);
void operator=(const vtkNmScalarBarActor&);
};
vtkStandardNewMacro(vtkNmScalarBarActor);
const int SCALAR_BAR_LEFT_MARGIN_PIXELS = 20;
const int SCALAR_BAR_TOTAL_WIDTH_PIXELS = 108;
// VTK 内部留白会压缩色带,目标宽度设为 34px 后可见宽度约为 30px。
@ -40,6 +74,7 @@ const int SCALAR_BAR_SWATCH_HEIGHT_PIXELS = 12;
const int SCALAR_BAR_SWATCH_GAP_PIXELS = 4;
const int SCALAR_BAR_TEXT_GAP_PIXELS = 6;
const int SCALAR_BAR_LABEL_GAP_PIXELS = 8;
const int SCALAR_BAR_TITLE_CONTENT_GAP_PIXELS = 4;
const double SCALAR_BAR_NORMAL_BOTTOM_RATIO = 0.22;
const double SCALAR_BAR_NORMAL_HEIGHT_RATIO = 0.67;
const double SCALAR_BAR_SMALL_BOTTOM_RATIO = 0.22;
@ -87,7 +122,7 @@ vtkSmartPointer<vtkActor2D> createRangeSwatchActor(vtkPoints* pPoints)
return pActor;
}
// 独立文本共用字号和颜色;常量值可选择与普通刻度一致的强调样式。
// 独立文本共用字号和颜色;单值端点可选择与普通刻度一致的强调样式。
void configureOverlayTextActor(vtkTextActor* pActor,
bool bUseTickStyle)
{
@ -134,6 +169,34 @@ void configureTitleActor(vtkTextActor* pActor)
pActor->SetVisibility(0);
}
// 优先读取 VTK 按当前 DPI 计算的真实文字高度,尚未挂载时使用稳定回退值。
double getOverlayTextHeightPixels(vtkTextActor* pActor,
vtkRenderer* pRenderer,
double dFallbackHeight)
{
if(pActor == nullptr || pRenderer == nullptr) {
return dFallbackHeight;
}
double dSize[2] = { 0.0, 0.0 };
pActor->GetSize(pRenderer, dSize);
return vtkMath::IsFinite(dSize[1]) && dSize[1] > 0.0
? dSize[1] : dFallbackHeight;
}
// 横向避让仅在极小视口空间不足时使用,正常布局仍保持色块位于色带上方。
double getOverlayTextWidthPixels(vtkTextActor* pActor,
vtkRenderer* pRenderer,
double dFallbackWidth)
{
if(pActor == nullptr || pRenderer == nullptr) {
return dFallbackWidth;
}
double dSize[2] = { 0.0, 0.0 };
pActor->GetSize(pRenderer, dSize);
return vtkMath::IsFinite(dSize[0]) && dSize[0] > 0.0
? dSize[0] : dFallbackWidth;
}
// 更新矩形四角并显式标记 Points 已修改,Mapper 会在下一次 Render 使用新位置。
void updateRectangle(vtkPoints* pPoints,
double dX, double dY,
@ -152,9 +215,10 @@ nmVTKScalarBarLayer::nmVTKScalarBarLayer(const QString& sLayerId)
: nmVTKRenderLayer(sLayerId),
m_pLookupTable(nullptr),
m_pRootAssembly(vtkSmartPointer<vtkPropAssembly>::New()),
m_pActor(vtkSmartPointer<vtkScalarBarActor>::New()),
m_pActor(vtkSmartPointer<vtkNmScalarBarActor>::New()),
m_pTitleActor(vtkSmartPointer<vtkTextActor>::New()),
m_pConstantValueActor(vtkSmartPointer<vtkTextActor>::New()),
m_pConstantMinimumValueActor(vtkSmartPointer<vtkTextActor>::New()),
m_pConstantMaximumValueActor(vtkSmartPointer<vtkTextActor>::New()),
m_pBelowRangeSwatchPoints(vtkSmartPointer<vtkPoints>::New()),
m_pBelowRangeSwatchActor(nullptr),
m_pBelowRangeTextActor(vtkSmartPointer<vtkTextActor>::New()),
@ -204,7 +268,8 @@ nmVTKScalarBarLayer::nmVTKScalarBarLayer(const QString& sLayerId)
m_pAboveRangeSwatchActor =
createRangeSwatchActor(m_pAboveRangeSwatchPoints);
configureTitleActor(m_pTitleActor);
configureOverlayTextActor(m_pConstantValueActor, true);
configureOverlayTextActor(m_pConstantMinimumValueActor, true);
configureOverlayTextActor(m_pConstantMaximumValueActor, true);
configureOverlayTextActor(m_pBelowRangeTextActor, false);
configureOverlayTextActor(m_pAboveRangeTextActor, false);
updateOverlayLayout();
@ -212,7 +277,8 @@ nmVTKScalarBarLayer::nmVTKScalarBarLayer(const QString& sLayerId)
// 根 Assembly 只在构造时注册,运行期仅更新子 Actor 状态。
m_pRootAssembly->AddPart(m_pActor);
m_pRootAssembly->AddPart(m_pTitleActor);
m_pRootAssembly->AddPart(m_pConstantValueActor);
m_pRootAssembly->AddPart(m_pConstantMinimumValueActor);
m_pRootAssembly->AddPart(m_pConstantMaximumValueActor);
m_pRootAssembly->AddPart(m_pBelowRangeSwatchActor);
m_pRootAssembly->AddPart(m_pBelowRangeTextActor);
m_pRootAssembly->AddPart(m_pAboveRangeSwatchActor);
@ -232,7 +298,8 @@ nmVTKScalarBarLayer::~nmVTKScalarBarLayer()
m_pBelowRangeTextActor = nullptr;
m_pBelowRangeSwatchActor = nullptr;
m_pBelowRangeSwatchPoints = nullptr;
m_pConstantValueActor = nullptr;
m_pConstantMaximumValueActor = nullptr;
m_pConstantMinimumValueActor = nullptr;
m_pTitleActor = nullptr;
m_pActor = nullptr;
m_pRootAssembly = nullptr;
@ -248,6 +315,7 @@ void nmVTKScalarBarLayer::setLookupTable(vtkLookupTable* pLookupTable)
m_pLookupTable = pLookupTable;
m_pActor->SetLookupTable(pLookupTable);
updateRangeSwatchColors();
updateOverlayLayout();
}
vtkLookupTable* nmVTKScalarBarLayer::getLookupTable() const
@ -267,6 +335,8 @@ void nmVTKScalarBarLayer::setTitle(const QString& sTitle)
const QByteArray aTitle = sTitle.toUtf8();
m_pTitleActor->SetInput(aTitle.constData());
m_pTitleActor->SetVisibility(sTitle.isEmpty() ? 0 : 1);
// 标题宽度参与极小视口下 Above max 的横向避让。
updateOverlayLayout();
}
QString nmVTKScalarBarLayer::getTitle() const
@ -283,6 +353,8 @@ bool nmVTKScalarBarLayer::setLabelFormat(const QString& sFormat)
m_sLabelFormat = sFormat;
const QByteArray aFormat = sFormat.toLatin1();
m_pActor->SetLabelFormat(aFormat.constData());
// 格式变化可能改变刻度宽度及 VTK 内部实际色带矩形。
updateOverlayLayout();
return true;
}
@ -298,6 +370,8 @@ bool nmVTKScalarBarLayer::setNumberOfLabels(int nLabelCount)
return false;
}
m_pActor->SetNumberOfLabels(nLabelCount);
// 标签数量会影响 VTK 内部色带矩形,独立 Overlay 必须同步其最新位置。
updateOverlayLayout();
return true;
}
@ -387,6 +461,7 @@ void nmVTKScalarBarLayer::setBelowRangeSwatch(
m_pBelowRangeSwatchActor->SetVisibility(bVisible ? 1 : 0);
m_pBelowRangeTextActor->SetVisibility(
bVisible && !m_sBelowRangeText.isEmpty() ? 1 : 0);
updateOverlayLayout();
}
void nmVTKScalarBarLayer::setAboveRangeSwatch(
@ -403,6 +478,7 @@ void nmVTKScalarBarLayer::setAboveRangeSwatch(
m_pAboveRangeSwatchActor->SetVisibility(bVisible ? 1 : 0);
m_pAboveRangeTextActor->SetVisibility(
bVisible && !m_sAboveRangeText.isEmpty() ? 1 : 0);
updateOverlayLayout();
}
void nmVTKScalarBarLayer::setConstantValueLabel(
@ -416,9 +492,13 @@ void nmVTKScalarBarLayer::setConstantValueLabel(
m_sConstantValueText = bVisible ? sText : QString();
const QByteArray aText = m_sConstantValueText.toUtf8();
m_pConstantValueActor->SetInput(aText.constData());
m_pConstantValueActor->SetVisibility(
bVisible && !m_sConstantValueText.isEmpty() ? 1 : 0);
m_pConstantMinimumValueActor->SetInput(aText.constData());
m_pConstantMaximumValueActor->SetInput(aText.constData());
const int nVisibility =
bVisible && !m_sConstantValueText.isEmpty() ? 1 : 0;
m_pConstantMinimumValueActor->SetVisibility(nVisibility);
m_pConstantMaximumValueActor->SetVisibility(nVisibility);
updateOverlayLayout();
}
bool nmVTKScalarBarLayer::setPosition(double dX, double dY)
@ -460,33 +540,49 @@ void nmVTKScalarBarLayer::updateOverlayLayout()
? 1.0 / m_nViewportWidth : 0.01;
const double dPixelY = bHasViewport
? 1.0 / m_nViewportHeight : 0.01;
double dColorX = m_dPositionX;
double dColorY = m_dPositionY;
double dColorWidth = bHasViewport
? SCALAR_BAR_VISIBLE_COLOR_WIDTH_PIXELS * dPixelX
: m_dWidth * 0.33;
double dColorHeight = m_dHeight;
if(dColorWidth > m_dWidth) {
dColorWidth = m_dWidth;
}
// 常量值与普通刻度共用色带右侧的固定像素起点。
const double dLabelX = m_dPositionX + dColorWidth +
SCALAR_BAR_LABEL_GAP_PIXELS * dPixelX;
const double dLabelY = m_dPositionY + m_dHeight * 0.5;
m_pConstantValueActor->GetPositionCoordinate()->SetValue(
dLabelX, dLabelY, 0.0);
vtkRenderer* pRenderer = getAttachedRenderer();
vtkNmScalarBarActor* pLayoutActor =
vtkNmScalarBarActor::SafeDownCast(m_pActor);
if(bHasViewport && pRenderer != nullptr &&
m_pLookupTable != nullptr && pLayoutActor != nullptr &&
pLayoutActor->refreshLayout(pRenderer)) {
// vtkScalarBarActor 的 Position/Size 是包含刻度留白的外框;端点和
// 其他装饰必须先刷新布局,再读取最终计算出的实际彩色色带矩形。
int nColorBarRect[4] = { 0, 0, 0, 0 };
m_pActor->GetScalarBarRect(nColorBarRect, pRenderer);
double dLeft = nColorBarRect[0];
double dBottom = nColorBarRect[1];
double dRight = nColorBarRect[0] + nColorBarRect[2];
double dTop = nColorBarRect[1] + nColorBarRect[3];
pRenderer->ViewportToNormalizedViewport(dLeft, dBottom);
pRenderer->ViewportToNormalizedViewport(dRight, dTop);
if(vtkMath::IsFinite(dLeft) && vtkMath::IsFinite(dBottom) &&
vtkMath::IsFinite(dRight) && vtkMath::IsFinite(dTop) &&
dRight > dLeft && dTop > dBottom) {
dColorX = dLeft;
dColorY = dBottom;
dColorWidth = dRight - dLeft;
dColorHeight = dTop - dBottom;
}
}
// 标题通常位于色带左上方;极小视口下向下钳制,避免越过视口顶边。
const double dDesiredTitleY =
m_dPositionY + m_dHeight +
SCALAR_BAR_TITLE_OFFSET_Y_PIXELS * dPixelY;
const double dMaximumTitleY = bHasViewport
? qMax(0.0, 1.0 -
SCALAR_BAR_TITLE_SAFE_HEIGHT_PIXELS * dPixelY)
: dDesiredTitleY;
const double dTitleY = qMin(dDesiredTitleY, dMaximumTitleY);
m_pTitleActor->GetPositionCoordinate()->SetValue(
m_dPositionX,
dTitleY,
0.0);
// 单值属性在色带上下端重复显示真实值,位置与普通端点刻度一致。
const double dLabelX = dColorX + dColorWidth +
SCALAR_BAR_LABEL_GAP_PIXELS * dPixelX;
m_pConstantMinimumValueActor->GetPositionCoordinate()->SetValue(
dLabelX, dColorY, 0.0);
m_pConstantMaximumValueActor->GetPositionCoordinate()->SetValue(
dLabelX, dColorY + dColorHeight, 0.0);
// 越界色块和文字也按实际像素布局,窗口缩放时不会横向漂移。
const double dSwatchWidth = bHasViewport
@ -498,21 +594,101 @@ void nmVTKScalarBarLayer::updateOverlayLayout()
? SCALAR_BAR_SWATCH_GAP_PIXELS * dPixelY : 0.012;
const double dTextGap = bHasViewport
? SCALAR_BAR_TEXT_GAP_PIXELS * dPixelX : 0.008;
const bool bConstantLabelsVisible =
m_pConstantMinimumValueActor->GetVisibility() != 0 &&
!m_sConstantValueText.isEmpty();
const double dConstantTextHeight = getOverlayTextHeightPixels(
m_pConstantMinimumValueActor, pRenderer,
SCALAR_BAR_LABEL_FONT_SIZE);
const bool bBelowRangeVisible =
m_pBelowRangeSwatchActor->GetVisibility() != 0;
const bool bAboveRangeVisible =
m_pAboveRangeSwatchActor->GetVisibility() != 0;
const double dBelowTextHeight = getOverlayTextHeightPixels(
m_pBelowRangeTextActor, pRenderer,
SCALAR_BAR_LABEL_FONT_SIZE);
const double dAboveTextHeight = getOverlayTextHeightPixels(
m_pAboveRangeTextActor, pRenderer,
SCALAR_BAR_LABEL_FONT_SIZE);
const double dBelowClearance =
bConstantLabelsVisible && bBelowRangeVisible
? qMax(0.0,
(dConstantTextHeight + dBelowTextHeight -
SCALAR_BAR_SWATCH_HEIGHT_PIXELS) * 0.5 * dPixelY)
: 0.0;
const double dAboveClearance =
bConstantLabelsVisible && bAboveRangeVisible
? qMax(0.0,
(dConstantTextHeight + dAboveTextHeight -
SCALAR_BAR_SWATCH_HEIGHT_PIXELS) * 0.5 * dPixelY)
: 0.0;
// 单值端点与越界说明可以同时启用。将越界注记整体向外错开,
// 保证其文字区域不会覆盖位于实际色带端点的单值标签。
const double dBelowY =
m_dPositionY - dSwatchGap - dSwatchHeight;
const double dAboveY =
m_dPositionY + m_dHeight + dSwatchGap;
dColorY - dSwatchGap - dSwatchHeight -
dBelowClearance;
double dAboveX = dColorX;
double dAboveY =
dColorY + dColorHeight + dSwatchGap +
dAboveClearance;
// 标题默认保持原有偏移;Above max 向外避让后若更高,则标题继续上移。
const double dAboveTextHalfHeight =
m_pAboveRangeTextActor->GetVisibility() != 0
? dAboveTextHeight * 0.5 * dPixelY : 0.0;
const double dAboveContentHalfHeight = qMax(
dSwatchHeight * 0.5, dAboveTextHalfHeight);
const double dAboveContentTop =
dAboveY + dSwatchHeight * 0.5 +
dAboveContentHalfHeight;
const double dBaseTitleY =
dColorY + dColorHeight +
SCALAR_BAR_TITLE_OFFSET_Y_PIXELS * dPixelY;
const double dDesiredTitleY = bAboveRangeVisible
? qMax(dBaseTitleY,
dAboveContentTop +
SCALAR_BAR_TITLE_CONTENT_GAP_PIXELS * dPixelY)
: dBaseTitleY;
const double dMaximumTitleY = bHasViewport
? qMax(0.0, 1.0 -
SCALAR_BAR_TITLE_SAFE_HEIGHT_PIXELS * dPixelY)
: dDesiredTitleY;
const double dTitleY = qMin(dDesiredTitleY, dMaximumTitleY);
if(bAboveRangeVisible &&
dTitleY < dAboveContentTop +
SCALAR_BAR_TITLE_CONTENT_GAP_PIXELS * dPixelY) {
// 极小视口无法同时向上容纳标题和 Above max。此时保持标题不越界,
// 并把越界注记放到标题、单值标签两者的右侧,消除水平交叠。
const double dConstantTextWidth = bConstantLabelsVisible
? getOverlayTextWidthPixels(
m_pConstantMaximumValueActor, pRenderer,
m_sConstantValueText.length() *
SCALAR_BAR_LABEL_FONT_SIZE * 0.6)
: 0.0;
const double dTitleWidth = getOverlayTextWidthPixels(
m_pTitleActor, pRenderer,
m_sTitle.length() *
SCALAR_BAR_TITLE_FONT_SIZE * 0.6);
dAboveX = qMax(
dLabelX + dConstantTextWidth * dPixelX + dTextGap,
dColorX + dTitleWidth * dPixelX + dTextGap);
dAboveY = dColorY + dColorHeight - dSwatchHeight * 0.5;
}
m_pTitleActor->GetPositionCoordinate()->SetValue(
dColorX, dTitleY, 0.0);
updateRectangle(m_pBelowRangeSwatchPoints,
m_dPositionX, dBelowY,
dColorX, dBelowY,
dSwatchWidth, dSwatchHeight);
updateRectangle(m_pAboveRangeSwatchPoints,
m_dPositionX, dAboveY,
dAboveX, dAboveY,
dSwatchWidth, dSwatchHeight);
m_pBelowRangeTextActor->GetPositionCoordinate()->SetValue(
m_dPositionX + dSwatchWidth + dTextGap,
dColorX + dSwatchWidth + dTextGap,
dBelowY + dSwatchHeight * 0.5, 0.0);
m_pAboveRangeTextActor->GetPositionCoordinate()->SetValue(
m_dPositionX + dSwatchWidth + dTextGap,
dAboveX + dSwatchWidth + dTextGap,
dAboveY + dSwatchHeight * 0.5, 0.0);
}

@ -119,6 +119,7 @@ bool getFiniteBounds(vtkDataSet* pDataSet, double dBounds[6])
bool getInternalDisplayRange(const double dVisibleRange[2],
bool bLogarithmic,
bool bSingleValueMode,
double dInternalRange[2])
{
if(dVisibleRange == nullptr || dInternalRange == nullptr ||
@ -129,58 +130,65 @@ bool getInternalDisplayRange(const double dVisibleRange[2],
return false;
}
dInternalRange[0] = dVisibleRange[0];
dInternalRange[1] = dVisibleRange[1];
if(dVisibleRange[0] < dVisibleRange[1]) {
dInternalRange[0] = dVisibleRange[0];
dInternalRange[1] = dVisibleRange[1];
return true;
}
if(!bSingleValueMode || dVisibleRange[0] != dVisibleRange[1]) {
return false;
}
// VTK 不接受零宽范围。常量值只扩展内部 LUT,业务和可见范围仍为 [c,c]。
// VTK 不接受零宽范围。单值模式从真实值单向扩展,使 c 位于 LUT 最小端。
const double dValue = dVisibleRange[0];
double dMinimum = 0.0;
double dMaximum = 0.0;
if(bLogarithmic) {
dMinimum = dValue * (1.0 - 1.0e-6);
dMaximum = dValue * (1.0 + 1.0e-6);
if(!vtkMath::IsFinite(dMinimum) || dMinimum <= 0.0 ||
dMinimum >= dValue) {
const double dHalfValue = dValue / 2.0;
dMinimum = dHalfValue > 0.0 && dHalfValue < dValue
? dHalfValue : dValue;
}
if(!vtkMath::IsFinite(dMaximum) || dMaximum <= dValue) {
const double dTwiceValue = dValue * 2.0;
dMaximum = vtkMath::IsFinite(dTwiceValue) &&
dTwiceValue > dValue ? dTwiceValue : dValue;
dMaximum = dValue * 2.0;
}
} else if(dValue == 0.0) {
dMinimum = -1.0e-6;
dMaximum = 1.0e-6;
} else {
const double dEpsilon = qMax(1.0, fabs(dValue)) * 1.0e-6;
dMinimum = dValue - dEpsilon;
dMaximum = dValue + dEpsilon;
if(!vtkMath::IsFinite(dMinimum) || dMinimum >= dValue) {
dMinimum = dValue > 0.0 ? dValue / 2.0 : dValue;
}
if(!vtkMath::IsFinite(dMaximum) || dMaximum <= dValue) {
dMaximum = dValue < 0.0 ? dValue / 2.0 : dValue;
}
}
if(!vtkMath::IsFinite(dMinimum) || !vtkMath::IsFinite(dMaximum) ||
dMinimum >= dMaximum || (bLogarithmic && dMinimum <= 0.0)) {
if(!vtkMath::IsFinite(dMaximum) || dValue >= dMaximum ||
(bLogarithmic && dValue <= 0.0)) {
return false;
}
dInternalRange[0] = dMinimum;
dInternalRange[0] = dValue;
dInternalRange[1] = dMaximum;
return true;
}
bool fillLookupTableWithFirstColor(vtkLookupTable* pLookupTable)
{
if(pLookupTable == nullptr ||
pLookupTable->GetNumberOfTableValues() <= 0) {
return false;
}
// 保留用户选择的色带语义,但让单值网格和色标统一使用第 0 个普通表项。
double dFirstColor[4] = { 0.0, 0.0, 0.0, 1.0 };
pLookupTable->GetTableValue(0, dFirstColor);
for(int nComponent = 0; nComponent < 4; ++nComponent) {
if(!vtkMath::IsFinite(dFirstColor[nComponent])) {
return false;
}
}
const int nTableValueCount = static_cast<int>(
pLookupTable->GetNumberOfTableValues());
for(int nIndex = 0; nIndex < nTableValueCount; ++nIndex) {
pLookupTable->SetTableValue(nIndex, dFirstColor);
}
return true;
}
struct ResultDisplayCandidate
{
ResultDisplayCandidate()
: m_pLookupTable(nullptr),
m_bConstantValueLabel(false)
m_bSingleValueMode(false)
{
m_dBusinessRange[0] = m_dBusinessRange[1] = 0.0;
m_dVisibleRange[0] = m_dVisibleRange[1] = 0.0;
@ -194,7 +202,7 @@ struct ResultDisplayCandidate
double m_dInternalRange[2];
vtkSmartPointer<vtkLookupTable> m_pLookupTable;
QString m_sScalarBarTitle;
bool m_bConstantValueLabel;
bool m_bSingleValueMode;
};
bool prepareDisplayCandidate(
@ -247,6 +255,10 @@ bool prepareDisplayCandidate(
dVisibleRange[1] = oSettings.m_dManualMaximum;
}
const bool bSingleValueMode =
!oSettings.m_bHasManualRange &&
dBusinessRange[0] == dBusinessRange[1];
const bool bLogarithmic =
oSettings.m_eScaleMode == NM_PebiColorScale_Logarithmic;
if(bLogarithmic &&
@ -264,7 +276,8 @@ bool prepareDisplayCandidate(
oSettings.m_sColorMapId);
if(pLookupTable == nullptr ||
!getInternalDisplayRange(dVisibleRange, bLogarithmic,
dInternalRange)) {
bSingleValueMode,
dInternalRange)) {
if(pError != nullptr) {
*pError = controllerText("Unable to construct the color mapping.");
}
@ -277,6 +290,13 @@ bool prepareDisplayCandidate(
} else {
pLookupTable->SetScaleToLinear();
}
if(bSingleValueMode &&
!fillLookupTableWithFirstColor(pLookupTable)) {
if(pError != nullptr) {
*pError = controllerText("Unable to construct the color mapping.");
}
return false;
}
const QColor oBelowColor = oSettings.m_oBelowRangeColor;
const QColor oAboveColor = oSettings.m_oAboveRangeColor;
pLookupTable->SetBelowRangeColor(
@ -295,9 +315,6 @@ bool prepareDisplayCandidate(
pLookupTable->IndexedLookupOff();
pLookupTable->ResetAnnotations();
const bool bConstantValueLabel =
!oSettings.m_bHasManualRange &&
dBusinessRange[0] == dBusinessRange[1];
const QString sTitle =
nmPebiResultPropertyDisplayText::getScalarBarTitle(sPropertyId);
@ -310,7 +327,7 @@ bool prepareDisplayCandidate(
oCandidate.m_dInternalRange[1] = dInternalRange[1];
oCandidate.m_pLookupTable = pLookupTable;
oCandidate.m_sScalarBarTitle = sTitle;
oCandidate.m_bConstantValueLabel = bConstantValueLabel;
oCandidate.m_bSingleValueMode = bSingleValueMode;
return true;
}
@ -397,7 +414,7 @@ public:
m_nViewportHeight(0),
m_bHasPropertyRange(false),
m_bHasBounds(false),
m_bCurrentConstantValueLabel(false),
m_bCurrentSingleValueMode(false),
m_bScalarBarVisible(true),
m_bScaleGridVisible(false),
m_bWellNamesVisible(true)
@ -689,7 +706,7 @@ public:
m_pScalarBarLayer->setViewportSize(
m_nViewportWidth, m_nViewportHeight);
}
const int nLabelCount = m_bCurrentConstantValueLabel
const int nLabelCount = m_bCurrentSingleValueMode
? 0 : m_pScalarBarLayer->getResponsiveLabelCount();
m_pScalarBarLayer->setNumberOfLabels(nLabelCount);
}
@ -702,10 +719,10 @@ public:
m_pScalarBarLayer->setLookupTable(oDisplay.m_pLookupTable);
m_pScalarBarLayer->setTitle(oDisplay.m_sScalarBarTitle);
m_pScalarBarLayer->setLabelFormat(QString::fromLatin1("%.4g"));
m_bCurrentConstantValueLabel = oDisplay.m_bConstantValueLabel;
m_bCurrentSingleValueMode = oDisplay.m_bSingleValueMode;
m_pScalarBarLayer->setConstantValueLabel(
oDisplay.m_bConstantValueLabel,
oDisplay.m_bConstantValueLabel
oDisplay.m_bSingleValueMode,
oDisplay.m_bSingleValueMode
? QString::number(oDisplay.m_dBusinessRange[0], 'g', 6)
: QString());
m_pScalarBarLayer->setBelowRangeSwatch(
@ -763,7 +780,7 @@ public:
m_pScalarBarLayer->setBelowRangeSwatch(false, QString());
m_pScalarBarLayer->setAboveRangeSwatch(false, QString());
m_pScalarBarLayer->setConstantValueLabel(false, QString());
m_bCurrentConstantValueLabel = false;
m_bCurrentSingleValueMode = false;
applyScalarBarLayout();
}
applyLayerVisibility(false);
@ -809,7 +826,7 @@ public:
m_pScalarBarLayer->setAboveRangeSwatch(false, QString());
m_pScalarBarLayer->setConstantValueLabel(false, QString());
}
m_bCurrentConstantValueLabel = false;
m_bCurrentSingleValueMode = false;
applyScalarBarLayout();
if(m_pScaleGridLayer != nullptr) {
m_pScaleGridLayer->setVisible(false);
@ -899,7 +916,7 @@ public:
double m_dBounds[6];
bool m_bHasPropertyRange;
bool m_bHasBounds;
bool m_bCurrentConstantValueLabel; ///< 当前色标是否只显示唯一业务值。
bool m_bCurrentSingleValueMode; ///< 当前是否为自动范围下的单值纯色模式。
bool m_bScalarBarVisible;
bool m_bScaleGridVisible;
bool m_bWellNamesVisible;

Loading…
Cancel
Save