From 1101db57874d449c3c8277cc057a9127753544ea Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Tue, 2 Aug 2016 10:25:55 +0200 Subject: [PATCH] (#805) Added category legend and mapper --- ApplicationCode/CMakeLists.txt | 1 + .../ProjectDataModel/RimEclipseView.cpp | 19 +- .../ProjectDataModel/RimGeoMechView.cpp | 6 +- .../ProjectDataModel/RimLegendConfig.cpp | 172 +++++- .../ProjectDataModel/RimLegendConfig.h | 28 +- ApplicationCode/UserInterface/RiuViewer.cpp | 8 + CMakeLists.txt | 2 + Fwk/AppFwk/cafVizExtensions/CMakeLists.txt | 4 + .../cafVizExtensions/cafCategoryLegend.cpp | 567 ++++++++++++++++++ .../cafVizExtensions/cafCategoryLegend.h | 100 +++ .../cafVizExtensions/cafCategoryMapper.cpp | 198 ++++++ .../cafVizExtensions/cafCategoryMapper.h | 43 ++ 12 files changed, 1119 insertions(+), 29 deletions(-) create mode 100644 Fwk/AppFwk/cafVizExtensions/cafCategoryLegend.cpp create mode 100644 Fwk/AppFwk/cafVizExtensions/cafCategoryLegend.h create mode 100644 Fwk/AppFwk/cafVizExtensions/cafCategoryMapper.cpp create mode 100644 Fwk/AppFwk/cafVizExtensions/cafCategoryMapper.h diff --git a/ApplicationCode/CMakeLists.txt b/ApplicationCode/CMakeLists.txt index 1ef6b5b057..391d2643a3 100644 --- a/ApplicationCode/CMakeLists.txt +++ b/ApplicationCode/CMakeLists.txt @@ -21,6 +21,7 @@ include_directories( ${cafUserInterface_SOURCE_DIR} ${cafPdmCvf_SOURCE_DIR} ${CommonCode_SOURCE_DIR} + ${cafVizExtensions_SOURCE_DIR} ${ResInsight_SOURCE_DIR}/ThirdParty ${ResInsight_SOURCE_DIR}/ThirdParty/NRLib/nrlib/well diff --git a/ApplicationCode/ProjectDataModel/RimEclipseView.cpp b/ApplicationCode/ProjectDataModel/RimEclipseView.cpp index 98ef6e8829..8a32e8f680 100644 --- a/ApplicationCode/ProjectDataModel/RimEclipseView.cpp +++ b/ApplicationCode/ProjectDataModel/RimEclipseView.cpp @@ -944,12 +944,7 @@ void RimEclipseView::updateLegends() this->cellEdgeResult()->legendConfig->setAutomaticRanges(globalMin, globalMax, globalMin, globalMax); m_viewer->addColorLegendToBottomLeftCorner(this->cellEdgeResult()->legendConfig->legend()); - - cvf::OverlayScalarMapperLegend* scalarMapperLegend = dynamic_cast(this->cellEdgeResult()->legendConfig->legend()); - if (scalarMapperLegend) - { - scalarMapperLegend->setTitle(cvfqt::Utils::toString(QString("Edge Results: \n") + this->cellEdgeResult()->resultVariable)); - } + this->cellEdgeResult()->legendConfig->setTitle(cvfqt::Utils::toString(QString("Edge Results: \n") + this->cellEdgeResult()->resultVariable)); } else { @@ -991,13 +986,15 @@ void RimEclipseView::updateMinMaxValuesAndAddLegendToView(QString legendLabel, R resultColors->legendConfig()->setClosestToZeroValues(globalPosClosestToZero, globalNegClosestToZero, localPosClosestToZero, localNegClosestToZero); resultColors->legendConfig()->setAutomaticRanges(globalMin, globalMax, localMin, localMax); - m_viewer->addColorLegendToBottomLeftCorner(resultColors->legendConfig()->legend()); - - cvf::OverlayScalarMapperLegend* scalarMapperLegend = dynamic_cast(resultColors->legendConfig()->legend()); - if (scalarMapperLegend) + if (resultColors->hasCategoryResult()) { - scalarMapperLegend->setTitle(cvfqt::Utils::toString(legendLabel + resultColors->resultVariable())); + size_t adjustedTimeStep = m_currentTimeStep; + if (resultColors->hasStaticResult()) adjustedTimeStep = 0; + resultColors->legendConfig()->setCategories(cellResultsData->uniqueCellScalarValues(resultColors->scalarResultIndex()), cellResultsData->uniqueCellScalarValues(resultColors->scalarResultIndex(), adjustedTimeStep)); } + + m_viewer->addColorLegendToBottomLeftCorner(resultColors->legendConfig()->legend()); + resultColors->legendConfig()->setTitle(cvfqt::Utils::toString(legendLabel + resultColors->resultVariable())); } diff --git a/ApplicationCode/ProjectDataModel/RimGeoMechView.cpp b/ApplicationCode/ProjectDataModel/RimGeoMechView.cpp index 20303051a1..39fa9bfcb2 100644 --- a/ApplicationCode/ProjectDataModel/RimGeoMechView.cpp +++ b/ApplicationCode/ProjectDataModel/RimGeoMechView.cpp @@ -435,11 +435,7 @@ void RimGeoMechView::updateLegends() legendTitle += " [Bar]"; } - cvf::OverlayScalarMapperLegend* scalarMapperLegend = dynamic_cast(cellResult()->legendConfig->legend()); - if (scalarMapperLegend) - { - scalarMapperLegend->setTitle(legendTitle); - } + cellResult()->legendConfig->setTitle(legendTitle); } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationCode/ProjectDataModel/RimLegendConfig.cpp b/ApplicationCode/ProjectDataModel/RimLegendConfig.cpp index 8b5d2f8c8d..2444e3ddab 100644 --- a/ApplicationCode/ProjectDataModel/RimLegendConfig.cpp +++ b/ApplicationCode/ProjectDataModel/RimLegendConfig.cpp @@ -22,8 +22,12 @@ #include "RiaApplication.h" +#include "RimEclipseCellColors.h" #include "RimEclipseView.h" +#include "cafCategoryLegend.h" +#include "cafCategoryMapper.h" + #include "cafFactory.h" #include "cafPdmFieldCvfColor.h" #include "cafPdmFieldCvfMat4d.h" @@ -60,10 +64,11 @@ namespace caf { addItem(RimLegendConfig::OPPOSITE_NORMAL,"OPPOSITE_NORMAL", "Full color, Blue on top"); addItem(RimLegendConfig::WHITE_PINK, "WHITE_PIMK", "White to pink"); addItem(RimLegendConfig::PINK_WHITE, "PINK_WHITE", "Pink to white"); - addItem(RimLegendConfig::BLUE_WHITE_RED, "BLUE_WHITE_RED", "Blue, white, red"); - addItem(RimLegendConfig::RED_WHITE_BLUE, "RED_WHITE_BLUE", "Red, white, blue"); + addItem(RimLegendConfig::BLUE_WHITE_RED, "BLUE_WHITE_RED", "Blue, white, red"); + addItem(RimLegendConfig::RED_WHITE_BLUE, "RED_WHITE_BLUE", "Red, white, blue"); addItem(RimLegendConfig::WHITE_BLACK, "WHITE_BLACK", "White to black"); addItem(RimLegendConfig::BLACK_WHITE, "BLACK_WHITE", "Black to white"); + addItem(RimLegendConfig::CATEGORY, "CATEGORY", "Category colors"); setDefault(RimLegendConfig::NORMAL); } } @@ -76,6 +81,7 @@ namespace caf { addItem(RimLegendConfig::LINEAR_CONTINUOUS, "LinearContinuous", "Continuous Linear"); addItem(RimLegendConfig::LOG10_CONTINUOUS, "Log10Continuous", "Continuous Logarithmic"); addItem(RimLegendConfig::LOG10_DISCRETE, "Log10Discrete", "Discrete Logarithmic"); + addItem(RimLegendConfig::CATEGORY_INTEGER, "Category", "Category"); setDefault(RimLegendConfig::LINEAR_CONTINUOUS); } } @@ -124,8 +130,11 @@ RimLegendConfig::RimLegendConfig() m_currentScalarMapper = m_linDiscreteScalarMapper; + m_categoryMapper = new caf::CategoryMapper; + cvf::Font* standardFont = RiaApplication::instance()->standardFont(); m_scalarMapperLegend = new cvf::OverlayScalarMapperLegend(standardFont); + m_categoryLegend = new caf::CategoryLegend(standardFont, m_categoryMapper.p()); updateFieldVisibility(); updateLegend(); @@ -149,7 +158,8 @@ void RimLegendConfig::fieldChangedByUi(const caf::PdmFieldHandle* changedField, int upperLimit = std::numeric_limits::max(); m_numLevels = cvf::Math::clamp(m_numLevels.v(), 1, upperLimit); } - else if (changedField == &m_rangeMode) + else if (changedField == &m_rangeMode || + changedField == &m_mappingMode) { if (m_rangeMode == USER_DEFINED) { @@ -189,6 +199,8 @@ void RimLegendConfig::updateLegend() posClosestToZero = m_globalAutoPosClosestToZero; negClosestToZero = m_globalAutoNegClosestToZero; + + m_categoryMapper->setCategories(m_globalCategories); } else if (m_rangeMode == AUTOMATIC_CURRENT_TIMESTEP) { @@ -197,6 +209,8 @@ void RimLegendConfig::updateLegend() posClosestToZero = m_localAutoPosClosestToZero; negClosestToZero = m_localAutoNegClosestToZero; + + m_categoryMapper->setCategories(m_localCategories); } else { @@ -207,7 +221,6 @@ void RimLegendConfig::updateLegend() negClosestToZero = m_globalAutoNegClosestToZero; } - m_linDiscreteScalarMapper->setRange(adjustedMin, adjustedMax); m_linSmoothScalarMapper->setRange(adjustedMin, adjustedMax); @@ -329,6 +342,36 @@ void RimLegendConfig::updateLegend() } break; + case CATEGORY: + { + // Based on http://stackoverflow.com/questions/470690/how-to-automatically-generate-n-distinct-colors + // and Kelly Colors + legendColors.reserve(20); + legendColors.add(cvf::Color3ub(255, 179, 0)); // vivid_yellow + legendColors.add(cvf::Color3ub(128, 62, 117)); // strong_purple + legendColors.add(cvf::Color3ub(255, 104, 0)); // vivid_orange + legendColors.add(cvf::Color3ub(166, 189, 215)); // very_light_blue + legendColors.add(cvf::Color3ub(193, 0, 32)); // vivid_red + legendColors.add(cvf::Color3ub(206, 162, 98)); // grayish_yellow + legendColors.add(cvf::Color3ub(129, 112, 102)); // medium_gray + + // these aren't good for people with defective color vision + legendColors.add(cvf::Color3ub( 0, 125, 52)); // vivid_green + legendColors.add(cvf::Color3ub(246, 118, 142)); //strong_purplish_pink + legendColors.add(cvf::Color3ub( 0, 83, 138)); //strong_blue + legendColors.add(cvf::Color3ub(255, 122, 92)); //strong_yellowish_pink + legendColors.add(cvf::Color3ub( 83, 55, 122)); //strong_violet + legendColors.add(cvf::Color3ub(255, 142, 0)); //vivid_orange_yellow + legendColors.add(cvf::Color3ub(179, 40, 81)); //strong_purplish_red + legendColors.add(cvf::Color3ub(244, 200, 0)); //vivid_greenish_yellow + legendColors.add(cvf::Color3ub(127, 24, 13)); //strong_reddish_brown + legendColors.add(cvf::Color3ub(147, 170, 0)); //vivid_yellowish_green + legendColors.add(cvf::Color3ub( 89, 51, 21)); //deep_yellowish_brown + legendColors.add(cvf::Color3ub(241, 58, 19)); //vivid_reddish_orange + legendColors.add(cvf::Color3ub( 35, 44, 22)); //dark_olive_green + } + break; + } m_linDiscreteScalarMapper->setColors(legendColors); @@ -336,6 +379,8 @@ void RimLegendConfig::updateLegend() m_logSmoothScalarMapper->setColors(legendColors); m_linSmoothScalarMapper->setColors(legendColors); + m_categoryMapper->setColors(legendColors); + m_linDiscreteScalarMapper->setLevelCount(m_numLevels, true); m_logDiscreteScalarMapper->setLevelCount(m_numLevels, true); m_logSmoothScalarMapper->setLevelCount(m_numLevels, true); @@ -355,11 +400,17 @@ void RimLegendConfig::updateLegend() case LOG10_DISCRETE: m_currentScalarMapper = m_logDiscreteScalarMapper.p(); break; + case CATEGORY_INTEGER: + m_currentScalarMapper = m_categoryMapper.p(); + break; default: break; } - m_scalarMapperLegend->setScalarMapper(m_currentScalarMapper.p()); + if (m_currentScalarMapper != m_categoryMapper.p()) + { + m_scalarMapperLegend->setScalarMapper(m_currentScalarMapper.p()); + } double decadesInRange = 0; if (m_mappingMode == LOG10_CONTINUOUS || m_mappingMode == LOG10_DISCRETE) @@ -468,7 +519,14 @@ void RimLegendConfig::initAfterRead() //-------------------------------------------------------------------------------------------------- void RimLegendConfig::updateFieldVisibility() { - if (m_rangeMode == USER_DEFINED) + bool showRangeItems = m_mappingMode == CATEGORY_INTEGER ? false : true; + + m_numLevels.uiCapability()->setUiHidden(!showRangeItems); + m_precision.uiCapability()->setUiHidden(!showRangeItems); + m_tickNumberFormat.uiCapability()->setUiHidden(!showRangeItems); + m_rangeMode.uiCapability()->setUiHidden(!showRangeItems); + + if (showRangeItems && m_rangeMode == USER_DEFINED) { m_userDefinedMaxValue.uiCapability()->setUiHidden(false); m_userDefinedMinValue.uiCapability()->setUiHidden(false); @@ -537,6 +595,7 @@ cvf::ref RimLegendConfig::interpolateColorArray(const cvf::C } */ + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -548,6 +607,7 @@ void RimLegendConfig::recreateLegend() cvf::Font* standardFont = RiaApplication::instance()->standardFont(); m_scalarMapperLegend = new cvf::OverlayScalarMapperLegend(standardFont); + m_categoryLegend = new caf::CategoryLegend(standardFont, m_categoryMapper.p()); updateLegend(); } @@ -620,12 +680,53 @@ void RimLegendConfig::setClosestToZeroValues(double globalPosClosestToZero, doub } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimLegendConfig::setCategories(const std::set& globalCategories, const std::set& localCategories) +{ + m_globalCategories.resize(globalCategories.size()); + m_localCategories.resize(localCategories.size()); + + { + size_t i = 0; + for (auto val : globalCategories) + { + m_globalCategories.set(i++, val); + } + } + + { + size_t i = 0; + for (auto val : localCategories) + { + m_localCategories.set(i++, val); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimLegendConfig::setTitle(const cvf::String& title) +{ + m_scalarMapperLegend->setTitle(title); + m_categoryLegend->setTitle(title); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvf::OverlayItem* RimLegendConfig::legend() { - return m_scalarMapperLegend.p(); + if (m_currentScalarMapper == m_categoryMapper) + { + return m_categoryLegend.p(); + } + else + { + return m_scalarMapperLegend.p(); + } } //-------------------------------------------------------------------------------------------------- @@ -648,3 +749,60 @@ void RimLegendConfig::defineUiOrdering(QString uiConfigName, caf::PdmUiOrdering& } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QList RimLegendConfig::calculateValueOptions(const caf::PdmFieldHandle* fieldNeedingOptions, bool* useOptionsOnly) +{ + QStringList optionTexts; + + bool isCategoryResult = false; + RimEclipseCellColors* cellColors = NULL; + this->firstAnchestorOrThisOfType(cellColors); + if (cellColors && cellColors->hasCategoryResult()) + { + isCategoryResult = true; + } + + if (fieldNeedingOptions == &m_mappingMode) + { + // This is an app enum field, see cafInternalPdmFieldTypeSpecializations.h for the default specialization of this type + + optionTexts << m_mappingMode.v().uiText(LINEAR_DISCRETE); + optionTexts << m_mappingMode.v().uiText(LINEAR_CONTINUOUS); + optionTexts << m_mappingMode.v().uiText(LOG10_CONTINUOUS); + optionTexts << m_mappingMode.v().uiText(LOG10_DISCRETE); + + if (isCategoryResult) + { + optionTexts << m_mappingMode.v().uiText(CATEGORY_INTEGER); + } + } + else if (fieldNeedingOptions == &m_colorRangeMode) + { + // This is an app enum field, see cafInternalPdmFieldTypeSpecializations.h for the default specialization of this type + + optionTexts << m_colorRangeMode.v().uiText(NORMAL); + optionTexts << m_colorRangeMode.v().uiText(OPPOSITE_NORMAL); + optionTexts << m_colorRangeMode.v().uiText(WHITE_PINK); + optionTexts << m_colorRangeMode.v().uiText(PINK_WHITE); + optionTexts << m_colorRangeMode.v().uiText(BLUE_WHITE_RED); + optionTexts << m_colorRangeMode.v().uiText(RED_WHITE_BLUE); + optionTexts << m_colorRangeMode.v().uiText(WHITE_BLACK); + optionTexts << m_colorRangeMode.v().uiText(BLACK_WHITE); + + if (isCategoryResult) + { + optionTexts << m_colorRangeMode.v().uiText(CATEGORY); + } + } + + QList optionList; + for (int i = 0; i < optionTexts.size(); ++i) + { + optionList.push_back(caf::PdmOptionItemInfo(optionTexts[i], static_cast(i))); + } + + return optionList; +} + diff --git a/ApplicationCode/ProjectDataModel/RimLegendConfig.h b/ApplicationCode/ProjectDataModel/RimLegendConfig.h index 75a0d75651..0533991ace 100644 --- a/ApplicationCode/ProjectDataModel/RimLegendConfig.h +++ b/ApplicationCode/ProjectDataModel/RimLegendConfig.h @@ -21,13 +21,10 @@ #pragma once #include "cvfBase.h" #include "cvfObject.h" -#include "cvfVector2.h" #include "cvfArray.h" #include "cafPdmObject.h" #include "cafPdmField.h" -#include "cafPdmPointer.h" -#include "cafAppEnum.h" namespace cvf { @@ -38,6 +35,13 @@ namespace cvf class ScalarMapperDiscreteLinear; class ScalarMapperDiscreteLog; class ScalarMapper; + class String; +} + +namespace caf +{ + class CategoryLegend; + class CategoryMapper; } class RimView; @@ -75,7 +79,8 @@ public: WHITE_BLACK, BLACK_WHITE, BLUE_WHITE_RED, - RED_WHITE_BLUE + RED_WHITE_BLUE, + CATEGORY }; typedef caf::AppEnum ColorRangeEnum; @@ -85,7 +90,8 @@ public: LINEAR_DISCRETE, LINEAR_CONTINUOUS, LOG10_CONTINUOUS, - LOG10_DISCRETE + LOG10_DISCRETE, + CATEGORY_INTEGER }; enum NumberFormatType { AUTO, SCIENTIFIC, FIXED}; @@ -94,6 +100,9 @@ public: void setColorRangeMode(ColorRangesType colorMode); void setAutomaticRanges(double globalMin, double globalMax, double localMin, double localMax); void setClosestToZeroValues(double globalPosClosestToZero, double globalNegClosestToZero, double localPosClosestToZero, double localNegClosestToZero); + void setCategories(const std::set& globalCategories, const std::set& localCategories); + + void setTitle(const cvf::String& title); cvf::ScalarMapper* scalarMapper() { return m_currentScalarMapper.p(); } cvf::OverlayItem* legend(); @@ -103,6 +112,8 @@ protected: virtual void fieldChangedByUi(const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue); virtual void initAfterRead(); virtual void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ); + virtual QList calculateValueOptions(const caf::PdmFieldHandle* fieldNeedingOptions, bool* useOptionsOnly); + private: void updateLegend(); void updateFieldVisibility(); @@ -120,6 +131,9 @@ private: cvf::ref m_currentScalarMapper; cvf::ref m_scalarMapperLegend; + + cvf::ref m_categoryMapper; + cvf::ref m_categoryLegend; double m_globalAutoMax; double m_globalAutoMin; @@ -131,6 +145,9 @@ private: double m_localAutoPosClosestToZero; double m_localAutoNegClosestToZero; + cvf::IntArray m_globalCategories; + cvf::IntArray m_localCategories; + // Fields caf::PdmField m_numLevels; caf::PdmField m_precision; @@ -140,5 +157,4 @@ private: caf::PdmField m_userDefinedMinValue; caf::PdmField > m_colorRangeMode; caf::PdmField > m_mappingMode; - }; diff --git a/ApplicationCode/UserInterface/RiuViewer.cpp b/ApplicationCode/UserInterface/RiuViewer.cpp index f4f7e6a7eb..fb01e7fc9f 100644 --- a/ApplicationCode/UserInterface/RiuViewer.cpp +++ b/ApplicationCode/UserInterface/RiuViewer.cpp @@ -37,6 +37,7 @@ #include "RiuSimpleHistogramWidget.h" #include "RiuViewerCommands.h" +#include "cafCategoryLegend.h" #include "cafCeetronPlusNavigation.h" #include "cafEffectGenerator.h" @@ -635,6 +636,13 @@ void RiuViewer::updateLegendTextAndTickMarkColor(cvf::OverlayItem* legend) scalarMapperLegend->setColor(contrastColor); scalarMapperLegend->setLineColor(contrastColor); } + + caf::CategoryLegend* categoryLegend = dynamic_cast(legend); + if (categoryLegend) + { + categoryLegend->setColor(contrastColor); + categoryLegend->setLineColor(contrastColor); + } } //-------------------------------------------------------------------------------------------------- diff --git a/CMakeLists.txt b/CMakeLists.txt index 43d4bc0cca..71f233cb36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -312,6 +312,7 @@ add_subdirectory(Fwk/AppFwk/cafCommand) add_subdirectory(Fwk/AppFwk/cafUserInterface) add_subdirectory(Fwk/AppFwk/cafPdmCvf) add_subdirectory(Fwk/AppFwk/CommonCode) +add_subdirectory(Fwk/AppFwk/cafVizExtensions) #add_subdirectory(Fwk/AppFwk/cafTests/cafTestCvfApplication) @@ -331,6 +332,7 @@ list(APPEND APP_FWK_LIBRARIES cafTensor CommonCode + cafVizExtensions ) set_property(TARGET diff --git a/Fwk/AppFwk/cafVizExtensions/CMakeLists.txt b/Fwk/AppFwk/cafVizExtensions/CMakeLists.txt index 6805a00f4d..1311154319 100644 --- a/Fwk/AppFwk/cafVizExtensions/CMakeLists.txt +++ b/Fwk/AppFwk/cafVizExtensions/CMakeLists.txt @@ -15,6 +15,10 @@ include_directories( ) add_library( ${PROJECT_NAME} + cafCategoryLegend.cpp + cafCategoryLegend.h + cafCategoryMapper.cpp + cafCategoryMapper.h cafTransparentWBRenderConfiguration.h cafTransparentWBRenderConfiguration.cpp TranspWB_CombinationFrag.glsl diff --git a/Fwk/AppFwk/cafVizExtensions/cafCategoryLegend.cpp b/Fwk/AppFwk/cafVizExtensions/cafCategoryLegend.cpp new file mode 100644 index 0000000000..abc707ecc9 --- /dev/null +++ b/Fwk/AppFwk/cafVizExtensions/cafCategoryLegend.cpp @@ -0,0 +1,567 @@ + +#include "cafCategoryLegend.h" +#include "cafCategoryMapper.h" + +#include "cvfBase.h" +#include "cvfBufferObjectManaged.h" +#include "cvfCamera.h" +#include "cvfFont.h" +#include "cvfGeometryBuilderDrawableGeo.h" +#include "cvfGeometryUtils.h" +#include "cvfGlyph.h" +#include "cvfMatrixState.h" +#include "cvfOpenGL.h" +#include "cvfOpenGLResourceManager.h" +#include "cvfRenderStateDepth.h" +#include "cvfRenderStateLine.h" +#include "cvfShaderProgram.h" +#include "cvfShaderProgramGenerator.h" +#include "cvfShaderSourceProvider.h" +#include "cvfShaderSourceRepository.h" +#include "cvfTextDrawer.h" +#include "cvfUniform.h" +#include "cvfViewport.h" + +#ifndef CVF_OPENGL_ES +#include "cvfRenderState_FF.h" +#endif + +#include "cvfScalarMapper.h" + + +using namespace cvf; + +namespace caf { + + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +CategoryLegend::CategoryLegend(Font* font, const CategoryMapper* categoryMapper) + : m_sizeHint(200, 200), + m_color(Color3::BLACK), + m_lineColor(Color3::BLACK), + m_lineWidth(1), + m_font(font), + m_categoryMapper(categoryMapper) +{ + CVF_ASSERT(font); + CVF_ASSERT(!font->isEmpty()); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +CategoryLegend::~CategoryLegend() +{ + // Empty destructor to avoid errors with undefined types when cvf::ref's destructor gets called +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::Vec2ui CategoryLegend::sizeHint() +{ + return m_sizeHint; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::setSizeHint(const Vec2ui& size) +{ + m_sizeHint = size; +} + +//-------------------------------------------------------------------------------------------------- +/// Set color of the text and lines to be rendered +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::setColor(const Color3f& color) +{ + m_color = color; +} + + +//-------------------------------------------------------------------------------------------------- +/// Returns the color of the text and lines +//-------------------------------------------------------------------------------------------------- +const Color3f& CategoryLegend::color() const +{ + return m_color; +} + +//-------------------------------------------------------------------------------------------------- +/// Set the title (text that will be rendered above the legend) +/// +/// The legend supports multi-line titles. Separate each line with a '\n' character +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::setTitle(const String& title) +{ + // Title + if (title.isEmpty()) + { + m_titleStrings.clear(); + } + else + { + m_titleStrings = title.split("\n"); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +String CategoryLegend::title() const +{ + String title; + for (size_t i = 0; i < m_titleStrings.size(); ++i) + { + title += m_titleStrings[i]; + + if (i != m_titleStrings.size() - 1) + { + title += "\n"; + } + } + + return title; +} + +//-------------------------------------------------------------------------------------------------- +/// Hardware rendering using shader programs +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::render(OpenGLContext* oglContext, const Vec2i& position, const Vec2ui& size) +{ + render(oglContext, position, size, false); +} + +//-------------------------------------------------------------------------------------------------- +/// Software rendering using software +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::renderSoftware(OpenGLContext* oglContext, const Vec2i& position, const Vec2ui& size) +{ + render(oglContext, position, size, true); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool CategoryLegend::pick(int oglXCoord, int oglYCoord, const Vec2i& position, const Vec2ui& size) +{ + Recti oglRect(position, size.x(), size.y()); + + OverlayColorLegendLayoutInfo layoutInViewPortCoords(oglRect.min(), Vec2ui(oglRect.width(), oglRect.height())); + layoutInfo(&layoutInViewPortCoords); + + Vec2i legendBarOrigin = oglRect.min(); + legendBarOrigin.x() += static_cast(layoutInViewPortCoords.legendRect.min().x()); + legendBarOrigin.y() += static_cast(layoutInViewPortCoords.legendRect.min().y()); + + Recti legendBarRect = Recti(legendBarOrigin, static_cast(layoutInViewPortCoords.legendRect.width()), static_cast(layoutInViewPortCoords.legendRect.height())); + + if ((oglXCoord > legendBarRect.min().x()) && (oglXCoord < legendBarRect.max().x()) && + (oglYCoord > legendBarRect.min().y()) && (oglYCoord < legendBarRect.max().y())) + { + return true; + } + + return false; +} + +//-------------------------------------------------------------------------------------------------- +/// Set up camera/viewport and render +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::render(OpenGLContext* oglContext, const Vec2i& position, const Vec2ui& size, bool software) +{ + if (size.x() <= 0 || size.y() <= 0) + { + return; + } + + Camera camera; + camera.setViewport(position.x(), position.y(), size.x(), size.y()); + camera.setProjectionAsPixelExact2D(); + camera.setViewMatrix(Mat4d::IDENTITY); + camera.applyOpenGL(); + camera.viewport()->applyOpenGL(oglContext, Viewport::CLEAR_DEPTH); + + // Get layout information + // Todo: Cache this between renderings. Update only when needed. + OverlayColorLegendLayoutInfo layout(position, size); + layoutInfo(&layout); + + // Set up text drawer + TextDrawer textDrawer(m_font.p()); + setupTextDrawer(&textDrawer, &layout); + + // Do the actual rendering + if (software) + { + renderLegendImmediateMode(oglContext, &layout); + textDrawer.renderSoftware(oglContext, camera); + } + else + { + const MatrixState matrixState(camera); + renderLegend(oglContext, &layout, matrixState); + textDrawer.render(oglContext, camera); + } + + CVF_CHECK_OGL(oglContext); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::setupTextDrawer(TextDrawer* textDrawer, OverlayColorLegendLayoutInfo* layout) +{ + if (m_categoryMapper.isNull()) + { + return; + } + + CVF_ASSERT(layout); + + textDrawer->setVerticalAlignment(TextDrawer::CENTER); + textDrawer->setTextColor(m_color); + + m_visibleCategoryLabels.clear(); + + const float textX = layout->tickX + 5; + + const float overlapTolerance = 1.2f * layout->charHeight; + float lastVisibleTextY = 0.0; + + CVF_ASSERT(m_categoryMapper.notNull()); + size_t numLabels = m_categoryMapper->categories().size(); + + float categoryHeight = static_cast(layout->legendRect.height() / numLabels); + + for (size_t it = 0; it < numLabels; it++) + { + float textY = static_cast(layout->legendRect.min().y() + it * categoryHeight + categoryHeight / 2); + + // Always draw first and last tick label. For all others, skip drawing if text ends up + // on top of the previous label. + if (it != 0 && it != (numLabels - 1)) + { + if (cvf::Math::abs(textY - lastVisibleTextY) < overlapTolerance) + { + m_visibleCategoryLabels.push_back(false); + continue; + } + // Make sure it does not overlap the last tick as well + + float lastTickY = static_cast(layout->legendRect.max().y()); + + if (cvf::Math::abs(textY - lastTickY) < overlapTolerance) + { + m_visibleCategoryLabels.push_back(false); + continue; + } + } + + double tickValue = m_categoryMapper->categories()[it]; + String valueString = String::number(tickValue); + + Vec2f pos(textX, textY); + textDrawer->addText(valueString, pos); + + lastVisibleTextY = textY; + m_visibleCategoryLabels.push_back(true); + } + + float titleY = static_cast(layout->size.y()) - layout->margins.y() - layout->charHeight / 2.0f; + for (size_t it = 0; it < m_titleStrings.size(); it++) + { + Vec2f pos(layout->margins.x(), titleY); + textDrawer->addText(m_titleStrings[it], pos); + + titleY -= layout->lineSpacing; + } +} + +//-------------------------------------------------------------------------------------------------- +/// Draw the legend using shader programs +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::renderLegend(OpenGLContext* oglContext, OverlayColorLegendLayoutInfo* layout, const MatrixState& matrixState) +{ + CVF_CALLSITE_OPENGL(oglContext); + + CVF_TIGHT_ASSERT(layout); + CVF_TIGHT_ASSERT(layout->size.x() > 0); + CVF_TIGHT_ASSERT(layout->size.y() > 0); + + RenderStateDepth depth(false); + depth.applyOpenGL(oglContext); + RenderStateLine line(static_cast(m_lineWidth)); + line.applyOpenGL(oglContext); + + // All vertices. Initialized here to set Z to zero once and for all. + static float vertexArray[] = + { + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f + }; + + // Per vector convenience pointers + float* v0 = &vertexArray[0]; + float* v1 = &vertexArray[3]; + float* v2 = &vertexArray[6]; + float* v3 = &vertexArray[9]; + float* v4 = &vertexArray[12]; + + // Constant coordinates + v0[0] = v3[0] = layout->x0; + v1[0] = v4[0] = layout->x1; + + // Connects + static const ushort trianglesConnects[] = { 0, 1, 4, 0, 4, 3 }; + + ref shaderProgram = oglContext->resourceManager()->getLinkedUnlitColorShaderProgram(oglContext); + CVF_TIGHT_ASSERT(shaderProgram.notNull()); + + if (shaderProgram->useProgram(oglContext)) + { + shaderProgram->clearUniformApplyTracking(); + shaderProgram->applyFixedUniforms(oglContext, matrixState); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glEnableVertexAttribArray(ShaderProgram::VERTEX); + glVertexAttribPointer(ShaderProgram::VERTEX, 3, GL_FLOAT, GL_FALSE, 0, vertexArray); + + // Render color bar as one colored quad per pixel + + int legendHeightPixelCount = static_cast(layout->legendRect.height()); + if (m_categoryMapper.notNull()) + { + int iPx; + for (iPx = 0; iPx < legendHeightPixelCount; iPx++) + { + const Color3ub& clr = m_categoryMapper->mapToColor(m_categoryMapper->domainValue((iPx + 0.5) / legendHeightPixelCount)); + float y0 = static_cast(layout->legendRect.min().y() + iPx); + float y1 = static_cast(layout->legendRect.min().y() + iPx + 1); + + // Dynamic coordinates for rectangle + v0[1] = v1[1] = y0; + v3[1] = v4[1] = y1; + + // Draw filled rectangle elements + { + UniformFloat uniformColor("u_color", Color4f(Color3f(clr))); + shaderProgram->applyUniform(oglContext, uniformColor); + +#ifdef CVF_OPENGL_ES + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, trianglesConnects); +#else + glDrawRangeElements(GL_TRIANGLES, 0, 4, 6, GL_UNSIGNED_SHORT, trianglesConnects); +#endif + } + } + } + + // Render frame + + // Dynamic coordinates for tickmarks-lines + bool isRenderingFrame = true; + if (isRenderingFrame) + { + v0[0] = v2[0] = layout->legendRect.min().x() - 0.5f; + v1[0] = v3[0] = layout->legendRect.max().x() - 0.5f; + v0[1] = v1[1] = layout->legendRect.min().y() - 0.5f; + v2[1] = v3[1] = layout->legendRect.max().y() - 0.5f; + static const ushort frameConnects[] = { 0, 1, 1, 3, 3, 2, 2, 0 }; + + UniformFloat uniformColor("u_color", Color4f(m_lineColor)); + shaderProgram->applyUniform(oglContext, uniformColor); + +#ifdef CVF_OPENGL_ES + glDrawElements(GL_LINES, 8, GL_UNSIGNED_SHORT, frameConnects); +#else + glDrawRangeElements(GL_LINES, 0, 3, 8, GL_UNSIGNED_SHORT, frameConnects); +#endif + } + + glDisableVertexAttribArray(ShaderProgram::VERTEX); + + CVF_TIGHT_ASSERT(shaderProgram.notNull()); + shaderProgram->useNoProgram(oglContext); + + // Reset render states + RenderStateDepth resetDepth; + resetDepth.applyOpenGL(oglContext); + + RenderStateLine resetLine; + resetLine.applyOpenGL(oglContext); + + CVF_CHECK_OGL(oglContext); +} + + +//-------------------------------------------------------------------------------------------------- +/// Draw the legend using immediate mode OpenGL +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::renderLegendImmediateMode(OpenGLContext* oglContext, OverlayColorLegendLayoutInfo* layout) +{ +#ifdef CVF_OPENGL_ES + CVF_UNUSED(layout); + CVF_FAIL_MSG("Not supported on OpenGL ES"); +#else + CVF_TIGHT_ASSERT(layout); + CVF_TIGHT_ASSERT(layout->size.x() > 0); + CVF_TIGHT_ASSERT(layout->size.y() > 0); + + RenderStateDepth depth(false); + depth.applyOpenGL(oglContext); + + RenderStateLighting_FF lighting(false); + lighting.applyOpenGL(oglContext); + + // All vertices. Initialized here to set Z to zero once and for all. + static float vertexArray[] = + { + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, + }; + + // Per vector convenience pointers + float* v0 = &vertexArray[0]; + float* v1 = &vertexArray[3]; + float* v2 = &vertexArray[6]; + float* v3 = &vertexArray[9]; + float* v4 = &vertexArray[12]; + + // Constant coordinates + v0[0] = v3[0] = layout->x0; + v1[0] = v4[0] = layout->x1; + + // Render color bar as one colored quad per pixel + + int legendHeightPixelCount = static_cast(layout->legendRect.height()); + if (m_categoryMapper.notNull()) + { + int iPx; + for (iPx = 0; iPx < legendHeightPixelCount; iPx++) + { + const Color3ub& clr = m_categoryMapper->mapToColor(m_categoryMapper->domainValue((iPx + 0.5) / legendHeightPixelCount)); + float y0 = static_cast(layout->legendRect.min().y() + iPx); + float y1 = static_cast(layout->legendRect.min().y() + iPx + 1); + + // Dynamic coordinates for rectangle + v0[1] = v1[1] = y0; + v3[1] = v4[1] = y1; + + // Draw filled rectangle elements + glColor3ubv(clr.ptr()); + glBegin(GL_TRIANGLE_FAN); + glVertex3fv(v0); + glVertex3fv(v1); + glVertex3fv(v4); + glVertex3fv(v3); + glEnd(); + } + } + + // Render frame + + // Dynamic coordinates for tickmarks-lines + bool isRenderingFrame = true; + if (isRenderingFrame) + { + v0[0] = v2[0] = layout->legendRect.min().x() - 0.5f; + v1[0] = v3[0] = layout->legendRect.max().x() - 0.5f; + v0[1] = v1[1] = layout->legendRect.min().y() - 0.5f; + v2[1] = v3[1] = layout->legendRect.max().y() - 0.5f; + + glColor3fv(m_color.ptr()); + glBegin(GL_LINES); + glVertex3fv(v0); + glVertex3fv(v1); + glVertex3fv(v1); + glVertex3fv(v3); + glVertex3fv(v3); + glVertex3fv(v2); + glVertex3fv(v2); + glVertex3fv(v0); + glEnd(); + + } + + // Reset render states + RenderStateLighting_FF resetLighting; + resetLighting.applyOpenGL(oglContext); + RenderStateDepth resetDepth; + resetDepth.applyOpenGL(oglContext); + + CVF_CHECK_OGL(oglContext); +#endif // CVF_OPENGL_ES +} + +//-------------------------------------------------------------------------------------------------- +/// Get layout information +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::layoutInfo(OverlayColorLegendLayoutInfo* layout) +{ + CVF_TIGHT_ASSERT(layout); + + ref glyph = m_font->getGlyph(L'A'); + layout->charHeight = static_cast(glyph->height()); + layout->lineSpacing = layout->charHeight*1.5f; + layout->margins = Vec2f(4.0f, 4.0f); + + float legendWidth = 25.0f; + float legendHeight = static_cast(layout->size.y()) - 2 * layout->margins.y() - static_cast(m_titleStrings.size())*layout->lineSpacing - layout->lineSpacing; + layout->legendRect = Rectf(layout->margins.x(), layout->margins.y() + layout->charHeight / 2.0f, legendWidth, legendHeight); + + if (layout->legendRect.width() < 1 || layout->legendRect.height() < 1) + { + return; + } + + layout->x0 = layout->margins.x(); + layout->x1 = layout->margins.x() + layout->legendRect.width(); + layout->tickX = layout->x1 + 5; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::setLineColor(const Color3f& lineColor) +{ + m_lineColor = lineColor; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +const Color3f& CategoryLegend::lineColor() const +{ + return m_lineColor; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void CategoryLegend::setLineWidth(int lineWidth) +{ + m_lineWidth = lineWidth; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +int CategoryLegend::lineWidth() const +{ + return m_lineWidth; +} + + +} // namespace cvf + diff --git a/Fwk/AppFwk/cafVizExtensions/cafCategoryLegend.h b/Fwk/AppFwk/cafVizExtensions/cafCategoryLegend.h new file mode 100644 index 0000000000..54d68d38aa --- /dev/null +++ b/Fwk/AppFwk/cafVizExtensions/cafCategoryLegend.h @@ -0,0 +1,100 @@ + +#pragma once + +#include "cvfArray.h" +#include "cvfCamera.h" +#include "cvfOverlayItem.h" +#include "cvfRect.h" +#include "cvfString.h" + +namespace cvf { + +class Font; +class ShaderProgram; +class MatrixState; +class TextDrawer; +} + +namespace caf { +class CategoryMapper; + +//================================================================================================== +// +// +//================================================================================================== +class CategoryLegend : public cvf::OverlayItem +{ +public: + CategoryLegend(cvf::Font* font, const CategoryMapper* categoryMapper); + virtual ~CategoryLegend(); + + virtual cvf::Vec2ui sizeHint(); + + virtual void render(cvf::OpenGLContext* oglContext, const cvf::Vec2i& position, const cvf::Vec2ui& size); + virtual void renderSoftware(cvf::OpenGLContext* oglContext, const cvf::Vec2i& position, const cvf::Vec2ui& size); + virtual bool pick(int oglXCoord, int oglYCoord, const cvf::Vec2i& position, const cvf::Vec2ui& size); + + + void setSizeHint(const cvf::Vec2ui& size); + + void setColor(const cvf::Color3f& color); + const cvf::Color3f& color() const; + + void setLineColor(const cvf::Color3f& lineColor); + const cvf::Color3f& lineColor() const; + void setLineWidth(int lineWidth); + int lineWidth() const; + + void setTitle(const cvf::String& title); + cvf::String title() const; + +protected: + + //================================================================================================== + // + // Helper for storing layout info + // + //================================================================================================== + struct OverlayColorLegendLayoutInfo + { + OverlayColorLegendLayoutInfo(const cvf::Vec2i& pos, const cvf::Vec2ui& setSize) + { + position = pos; + size = setSize; + } + + float charHeight; + float lineSpacing; + cvf::Vec2f margins; + float tickX; + float x0, x1; + + cvf::Rectf legendRect; + + cvf::Vec2i position; + cvf::Vec2ui size; + }; + + + void render(cvf::OpenGLContext* oglContext, const cvf::Vec2i& position, const cvf::Vec2ui& size, bool software); + virtual void renderLegend(cvf::OpenGLContext* oglContext, OverlayColorLegendLayoutInfo* layout, const cvf::MatrixState& matrixState); + virtual void renderLegendImmediateMode(cvf::OpenGLContext* oglContext, OverlayColorLegendLayoutInfo* layout); + virtual void setupTextDrawer(cvf::TextDrawer* textDrawer, OverlayColorLegendLayoutInfo* layout); + + void layoutInfo(OverlayColorLegendLayoutInfo* layout); + +protected: + std::vector m_visibleCategoryLabels; // Skip labels ending up on top of previous visible label + + cvf::Vec2ui m_sizeHint; // Pixel size of the color legend area + + cvf::Color3f m_color; + cvf::Color3f m_lineColor; + int m_lineWidth; + std::vector m_titleStrings; + cvf::ref m_font; + + cvf::cref m_categoryMapper; +}; + +} diff --git a/Fwk/AppFwk/cafVizExtensions/cafCategoryMapper.cpp b/Fwk/AppFwk/cafVizExtensions/cafCategoryMapper.cpp new file mode 100644 index 0000000000..41100d1a90 --- /dev/null +++ b/Fwk/AppFwk/cafVizExtensions/cafCategoryMapper.cpp @@ -0,0 +1,198 @@ + +#include "cafCategoryMapper.h" + +#include "cvfBase.h" +#include "cvfMath.h" +#include "cvfOverlayColorLegend.h" +#include "cvfTextureImage.h" + +using namespace cvf; + +namespace caf { + + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +CategoryMapper::CategoryMapper() + : m_textureSize(2048) +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void CategoryMapper::setCategories(const IntArray& categories) +{ + m_categories = categories; + + ref colorArr = ScalarMapper::colorTableArray(ColorTable::NORMAL); + + setColors(*(colorArr.p())); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void CategoryMapper::setColors(const Color3ubArray& colorArray) +{ + m_colors.resize(m_categories.size()); + + for (size_t i = 0; i < m_categories.size(); i++) + { + size_t colIdx = i % colorArray.size(); + m_colors[i] = colorArray[colIdx]; + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::IntArray CategoryMapper::categories() const +{ + return m_categories; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +Vec2f CategoryMapper::mapToTextureCoord(double categoryValue) const +{ + double normVal = normalizedValue(categoryValue); + + return Vec2f(static_cast(normVal), 0.5f); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +Color3ub CategoryMapper::mapToColor(double categoryValue) const +{ + int catIndex = categoryIndexForCategory(categoryValue); + + if (catIndex != -1) + { + uint colorCount = static_cast(m_colors.size()); + CVF_ASSERT(colorCount > static_cast(catIndex)); + + return m_colors[catIndex]; + } + else + { + return Color3::BLACK; + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void CategoryMapper::majorTickValues(std::vector* domainValues) const +{ + // Not intended to be supported + CVF_ASSERT(false); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double CategoryMapper::normalizedValue(double categoryValue) const +{ + int catIndex = categoryIndexForCategory(categoryValue); + + if (catIndex != -1) + { + double halfLevelHeight = 1.0 / (m_categories.size() * 2); + + double normVal = static_cast(catIndex) / static_cast(m_categories.size()); + + return normVal + halfLevelHeight; + } + else + { + return 0.0; + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double CategoryMapper::domainValue(double normalizedValue) const +{ + double clampedValue = cvf::Math::clamp(normalizedValue, 0.0, 1.0); + + if (m_categories.size() == 0) + { + return 0.0; + } + + size_t catIndex = static_cast(clampedValue * m_categories.size()); + return m_categories[catIndex]; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +int CategoryMapper::categoryIndexForCategory(double domainValue) const +{ + int catIndex = -1; + + size_t i = 0; + while (i < m_categories.size() && catIndex == -1) + { + if (m_categories[i] == domainValue) + { + catIndex = static_cast(i); + } + + i++; + } + + return catIndex; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool CategoryMapper::updateTexture(TextureImage* image) const +{ + CVF_ASSERT(image); + + image->allocate(m_textureSize, 1); + + // For now fill with white so we can see any errors more easily + image->fill(Color4ub(Color3::WHITE)); + + const uint numColors = static_cast(m_colors.size()); + if (numColors < 1) + { + return false; + } + + const uint numPixelsPerColor = m_textureSize / numColors; + CVF_ASSERT(numPixelsPerColor >= 1); + + uint ic; + for (ic = 0; ic < numColors; ic++) + { + const Color4ub clr(m_colors[ic], 255); + + uint ip; + for (ip = 0; ip < numPixelsPerColor; ip++) + { + image->setPixel(ic*numPixelsPerColor + ip, 0, clr); + } + } + + // In cases where we're not using the entire texture we might get into problems with texture coordinate precision on the graphics hardware. + // Therefore we set one extra pixel with the 'highest' color in the color table + if (numColors*numPixelsPerColor < m_textureSize) + { + const Color4ub topClr(m_colors[numColors - 1], 255); + image->setPixel(numColors*numPixelsPerColor, 0, topClr); + } + + return true; +} + +} // namespace cvf + diff --git a/Fwk/AppFwk/cafVizExtensions/cafCategoryMapper.h b/Fwk/AppFwk/cafVizExtensions/cafCategoryMapper.h new file mode 100644 index 0000000000..f832cdf58c --- /dev/null +++ b/Fwk/AppFwk/cafVizExtensions/cafCategoryMapper.h @@ -0,0 +1,43 @@ + +#pragma once + +#include "cvfObject.h" +#include "cvfScalarMapper.h" + +namespace caf { + + +//================================================================================================== +// +// +//================================================================================================== +class CategoryMapper : public cvf::ScalarMapper +{ +public: + CategoryMapper(); + + void setCategories(const cvf::IntArray& categories); + void setColors(const cvf::Color3ubArray& colorArray); + + cvf::IntArray categories() const; + + virtual cvf::Vec2f mapToTextureCoord(double scalarValue) const; + virtual bool updateTexture(cvf::TextureImage* image) const; + + virtual cvf::Color3ub mapToColor(double normalizedValue) const; + + virtual void majorTickValues(std::vector* domainValues) const; + virtual double normalizedValue(double domainValue) const; + virtual double domainValue(double normalizedValue) const; + +private: + int categoryIndexForCategory(double domainValue) const; + +private: + cvf::Color3ubArray m_colors; + cvf::uint m_textureSize; // The size of texture that updateTexture() is will produce. + + cvf::IntArray m_categories; +}; + +}