Add setting to control background color for elevation profile charts

This option (available from the new Settings - Options - Elevation tab)
allows users to set a specific color to use as the background color
for elevation profiles. This can make the chart more readable for
certain datasets, eg point clouds with RGB coloring, where the default
background color is too close to the point colors to be discernable.

This is an opt-in setting -- by default the profiles will continue
to display using the standard system background color.
This commit is contained in:
Nyall Dawson 2023-09-11 11:44:02 +10:00 committed by Martin Dobias
parent 467b52264e
commit b168b20516
10 changed files with 334 additions and 38 deletions

View File

@ -240,6 +240,16 @@ Sets the distance ``unit`` used by the canvas.
.. seealso:: :py:func:`distanceUnit` .. seealso:: :py:func:`distanceUnit`
.. versionadded:: 3.32 .. versionadded:: 3.32
%End
void setBackgroundColor( const QColor &color );
%Docstring
Sets the background ``color`` to use for the profile canvas.
The chart text, border and axis color will be automatically updated to ensure
readability with the new background color.
.. versionadded:: 3.34
%End %End
signals: signals:

View File

@ -258,6 +258,7 @@ set(QGIS_APP_SRCS
options/qgsadvancedoptions.cpp options/qgsadvancedoptions.cpp
options/qgscodeeditoroptions.cpp options/qgscodeeditoroptions.cpp
options/qgscustomprojectionoptions.cpp options/qgscustomprojectionoptions.cpp
options/qgselevationoptions.cpp
options/qgsfontoptions.cpp options/qgsfontoptions.cpp
options/qgsgpsdeviceoptions.cpp options/qgsgpsdeviceoptions.cpp
options/qgsgpsoptions.cpp options/qgsgpsoptions.cpp

View File

@ -69,6 +69,7 @@ const QgsSettingsEntryDouble *QgsElevationProfileWidget::settingTolerance = new
const QgsSettingsEntryBool *QgsElevationProfileWidget::settingShowLayerTree = new QgsSettingsEntryBool( QStringLiteral( "show-layer-tree" ), QgsSettingsTree::sTreeElevationProfile, true, QStringLiteral( "Whether the layer tree should be shown for elevation profile plots" ) ); const QgsSettingsEntryBool *QgsElevationProfileWidget::settingShowLayerTree = new QgsSettingsEntryBool( QStringLiteral( "show-layer-tree" ), QgsSettingsTree::sTreeElevationProfile, true, QStringLiteral( "Whether the layer tree should be shown for elevation profile plots" ) );
const QgsSettingsEntryBool *QgsElevationProfileWidget::settingLockAxis = new QgsSettingsEntryBool( QStringLiteral( "lock-axis-ratio" ), QgsSettingsTree::sTreeElevationProfile, false, QStringLiteral( "Whether the the distance and elevation axis scales are locked to each other" ) ); const QgsSettingsEntryBool *QgsElevationProfileWidget::settingLockAxis = new QgsSettingsEntryBool( QStringLiteral( "lock-axis-ratio" ), QgsSettingsTree::sTreeElevationProfile, false, QStringLiteral( "Whether the the distance and elevation axis scales are locked to each other" ) );
const QgsSettingsEntryString *QgsElevationProfileWidget::settingLastExportDir = new QgsSettingsEntryString( QStringLiteral( "last-export-dir" ), QgsSettingsTree::sTreeElevationProfile, QString(), QStringLiteral( "Last elevation profile export directory" ) ); const QgsSettingsEntryString *QgsElevationProfileWidget::settingLastExportDir = new QgsSettingsEntryString( QStringLiteral( "last-export-dir" ), QgsSettingsTree::sTreeElevationProfile, QString(), QStringLiteral( "Last elevation profile export directory" ) );
const QgsSettingsEntryColor *QgsElevationProfileWidget::settingBackgroundColor = new QgsSettingsEntryColor( QStringLiteral( "background-color" ), QgsSettingsTree::sTreeElevationProfile, QColor(), QStringLiteral( "Elevation profile chart background color" ) );
// //
// QgsElevationProfileLayersDialog // QgsElevationProfileLayersDialog
// //
@ -158,6 +159,12 @@ QgsElevationProfileWidget::QgsElevationProfileWidget( const QString &name )
mCanvas->setLockAxisScales( settingLockAxis->value() ); mCanvas->setLockAxisScales( settingLockAxis->value() );
mCanvas->setBackgroundColor( settingBackgroundColor->value() );
connect( QgsGui::instance(), &QgsGui::optionsChanged, this, [ = ]
{
mCanvas->setBackgroundColor( settingBackgroundColor->value() );
} );
mPanTool = new QgsPlotToolPan( mCanvas ); mPanTool = new QgsPlotToolPan( mCanvas );
mLayerTreeView = new QgsAppElevationProfileLayerTreeView( mLayerTree.get() ); mLayerTreeView = new QgsAppElevationProfileLayerTreeView( mLayerTree.get() );

View File

@ -52,6 +52,7 @@ class QgsProfilePoint;
class QgsSettingsEntryDouble; class QgsSettingsEntryDouble;
class QgsSettingsEntryBool; class QgsSettingsEntryBool;
class QgsSettingsEntryString; class QgsSettingsEntryString;
class QgsSettingsEntryColor;
class QgsMapLayerProxyModel; class QgsMapLayerProxyModel;
class QgsAppElevationProfileLayerTreeView : public QgsElevationProfileLayerTreeView class QgsAppElevationProfileLayerTreeView : public QgsElevationProfileLayerTreeView
@ -95,6 +96,7 @@ class QgsElevationProfileWidget : public QWidget
static const QgsSettingsEntryBool *settingShowLayerTree; static const QgsSettingsEntryBool *settingShowLayerTree;
static const QgsSettingsEntryBool *settingLockAxis; static const QgsSettingsEntryBool *settingLockAxis;
static const QgsSettingsEntryString *settingLastExportDir; static const QgsSettingsEntryString *settingLastExportDir;
static const QgsSettingsEntryColor *settingBackgroundColor;
QgsElevationProfileWidget( const QString &name ); QgsElevationProfileWidget( const QString &name );
~QgsElevationProfileWidget(); ~QgsElevationProfileWidget();

View File

@ -0,0 +1,70 @@
/***************************************************************************
qgselevationoptions.h
-------------------------
begin : September 2023
copyright : (C) 2023 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgselevationoptions.h"
#include "qgssettings.h"
#include "qgsapplication.h"
#include "elevation/qgselevationprofilewidget.h"
#include <QDir>
//
// QgsElevationOptionsWidget
//
QgsElevationOptionsWidget::QgsElevationOptionsWidget( QWidget *parent )
: QgsOptionsPageWidget( parent )
{
setupUi( this );
mButtonBackgroundColor->setShowNull( true, tr( "Use Default" ) );
mButtonBackgroundColor->setColorDialogTitle( tr( "Chart Background Color" ) );
const QColor backgroundColor = QgsElevationProfileWidget::settingBackgroundColor->value();
if ( backgroundColor.isValid() )
mButtonBackgroundColor->setColor( backgroundColor );
else
mButtonBackgroundColor->setToNull();
}
void QgsElevationOptionsWidget::apply()
{
QgsElevationProfileWidget::settingBackgroundColor->setValue(
mButtonBackgroundColor->isNull() ? QColor() : mButtonBackgroundColor->color()
);
}
//
// QgsElevationOptionsFactory
//
QgsElevationOptionsFactory::QgsElevationOptionsFactory()
: QgsOptionsWidgetFactory( tr( "Elevation" ), QIcon(), QStringLiteral( "elevation" ) )
{
}
QIcon QgsElevationOptionsFactory::icon() const
{
return QgsApplication::getThemeIcon( QStringLiteral( "propertyicons/elevationscale.svg" ) );
}
QgsOptionsPageWidget *QgsElevationOptionsFactory::createWidget( QWidget *parent ) const
{
return new QgsElevationOptionsWidget( parent );
}
QString QgsElevationOptionsFactory::pagePositionHint() const
{
return QStringLiteral( "mOptionsPageColors" );
}

View File

@ -0,0 +1,58 @@
/***************************************************************************
qgselevationoptions.h
-------------------------
begin : September 2023
copyright : (C) 2023 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSELEVATIONOPTIONS_H
#define QGSELEVATIONOPTIONS_H
#include "ui_qgselevationoptionswidgetbase.h"
#include "qgsoptionswidgetfactory.h"
/**
* \ingroup app
* \class QgsElevationOptionsWidget
* \brief An options widget showing elevation related options.
*
* \since QGIS 3.34
*/
class QgsElevationOptionsWidget : public QgsOptionsPageWidget, private Ui::QgsElevationOptionsWidgetBase
{
Q_OBJECT
public:
/**
* Constructor for QgsElevationOptionsWidget with the specified \a parent widget.
*/
QgsElevationOptionsWidget( QWidget *parent );
void apply() override;
};
class QgsElevationOptionsFactory : public QgsOptionsWidgetFactory
{
Q_OBJECT
public:
QgsElevationOptionsFactory();
QIcon icon() const override;
QgsOptionsPageWidget *createWidget( QWidget *parent = nullptr ) const override;
QString pagePositionHint() const override;
};
#endif // QGSELEVATIONOPTIONS_H

View File

@ -109,6 +109,7 @@
#include <geos_c.h> #include <geos_c.h>
#include "options/qgscodeeditoroptions.h" #include "options/qgscodeeditoroptions.h"
#include "options/qgselevationoptions.h"
#include "options/qgsfontoptions.h" #include "options/qgsfontoptions.h"
#include "options/qgsgpsdeviceoptions.h" #include "options/qgsgpsdeviceoptions.h"
#include "options/qgsgpsoptions.h" #include "options/qgsgpsoptions.h"
@ -1958,6 +1959,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers
mOptionWidgetFactories.emplace_back( QgsScopedOptionsWidgetFactory( std::make_unique< QgsGpsOptionsFactory >() ) ); mOptionWidgetFactories.emplace_back( QgsScopedOptionsWidgetFactory( std::make_unique< QgsGpsOptionsFactory >() ) );
mOptionWidgetFactories.emplace_back( QgsScopedOptionsWidgetFactory( std::make_unique< QgsGpsDeviceOptionsFactory >() ) ); mOptionWidgetFactories.emplace_back( QgsScopedOptionsWidgetFactory( std::make_unique< QgsGpsDeviceOptionsFactory >() ) );
mOptionWidgetFactories.emplace_back( QgsScopedOptionsWidgetFactory( std::make_unique< QgsCustomProjectionOptionsFactory >() ) ); mOptionWidgetFactories.emplace_back( QgsScopedOptionsWidgetFactory( std::make_unique< QgsCustomProjectionOptionsFactory >() ) );
mOptionWidgetFactories.emplace_back( QgsScopedOptionsWidgetFactory( std::make_unique< QgsElevationOptionsFactory >() ) );
mOptionWidgetFactories.emplace_back( QgsScopedOptionsWidgetFactory( std::make_unique< QgsFontOptionsFactory >() ) ); mOptionWidgetFactories.emplace_back( QgsScopedOptionsWidgetFactory( std::make_unique< QgsFontOptionsFactory >() ) );
mOptionWidgetFactories.emplace_back( QgsScopedOptionsWidgetFactory( std::make_unique< QgsUserProfileOptionsFactory >() ) ); mOptionWidgetFactories.emplace_back( QgsScopedOptionsWidgetFactory( std::make_unique< QgsUserProfileOptionsFactory >() ) );

View File

@ -290,7 +290,7 @@ class QgsElevationProfileCrossHairsItem : public QgsPlotCanvasItem
crossHairPen.setStyle( Qt::DashLine ); crossHairPen.setStyle( Qt::DashLine );
crossHairPen.setCapStyle( Qt::FlatCap ); crossHairPen.setCapStyle( Qt::FlatCap );
const QPalette scenePalette = mPlotItem->scene()->palette(); const QPalette scenePalette = mPlotItem->scene()->palette();
QColor penColor = scenePalette.color( QPalette::Text ); QColor penColor = scenePalette.color( QPalette::ColorGroup::Active, QPalette::Text );
penColor.setAlpha( 150 ); penColor.setAlpha( 150 );
crossHairPen.setColor( penColor ); crossHairPen.setColor( penColor );
painter->setPen( crossHairPen ); painter->setPen( crossHairPen );
@ -359,7 +359,7 @@ class QgsElevationProfileCrossHairsItem : public QgsPlotCanvasItem
painter->drawRect( QRectF( yCoordOrigin.x() - textAxisMargin + 1, yCoordOrigin.y() - textAxisMargin - height + 1, yWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) ); painter->drawRect( QRectF( yCoordOrigin.x() - textAxisMargin + 1, yCoordOrigin.y() - textAxisMargin - height + 1, yWidth + 2 * textAxisMargin - 2, height + 2 * textAxisMargin - 2 ) );
painter->setBrush( Qt::NoBrush ); painter->setBrush( Qt::NoBrush );
painter->setPen( scenePalette.color( QPalette::Text ) ); painter->setPen( scenePalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
painter->drawText( xCoordOrigin, xCoordinateText ); painter->drawText( xCoordOrigin, xCoordinateText );
painter->drawText( yCoordOrigin, yCoordinateText ); painter->drawText( yCoordOrigin, yCoordinateText );
@ -383,42 +383,7 @@ QgsElevationProfileCanvas::QgsElevationProfileCanvas( QWidget *parent )
mPlotItem = new QgsElevationProfilePlotItem( this ); mPlotItem = new QgsElevationProfilePlotItem( this );
// follow system color scheme by default // follow system color scheme by default
QPalette customPalette = palette(); setBackgroundColor( QColor() );
{
QgsTextFormat textFormat = mPlotItem->xAxis().textFormat();
textFormat.setColor( customPalette.color( QPalette::Text ) );
mPlotItem->xAxis().setTextFormat( textFormat );
mPlotItem->yAxis().setTextFormat( textFormat );
}
{
std::unique_ptr< QgsFillSymbol > chartFill( mPlotItem->chartBackgroundSymbol()->clone() );
chartFill->setColor( customPalette.color( QPalette::ColorRole::Base ) );
mPlotItem->setChartBackgroundSymbol( chartFill.release() );
}
{
std::unique_ptr< QgsFillSymbol > chartBorder( mPlotItem->chartBorderSymbol()->clone() );
chartBorder->setColor( customPalette.color( QPalette::ColorRole::Text ) );
mPlotItem->setChartBorderSymbol( chartBorder.release() );
}
{
std::unique_ptr< QgsLineSymbol > chartMajorSymbol( mPlotItem->xAxis().gridMajorSymbol()->clone() );
QColor c = customPalette.color( QPalette::ColorRole::Text );
c.setAlpha( 150 );
chartMajorSymbol->setColor( c );
mPlotItem->xAxis().setGridMajorSymbol( chartMajorSymbol->clone() );
mPlotItem->yAxis().setGridMajorSymbol( chartMajorSymbol.release() );
}
{
std::unique_ptr< QgsLineSymbol > chartMinorSymbol( mPlotItem->xAxis().gridMinorSymbol()->clone() );
QColor c = customPalette.color( QPalette::ColorRole::Text );
c.setAlpha( 50 );
chartMinorSymbol->setColor( c );
mPlotItem->xAxis().setGridMinorSymbol( chartMinorSymbol->clone() );
mPlotItem->yAxis().setGridMinorSymbol( chartMinorSymbol.release() );
}
customPalette.setColor( QPalette::ColorRole::Base, customPalette.color( QPalette::ColorRole::Window ) );
setPalette( customPalette );
mCrossHairsItem = new QgsElevationProfileCrossHairsItem( this, mPlotItem ); mCrossHairsItem = new QgsElevationProfileCrossHairsItem( this, mPlotItem );
mCrossHairsItem->setZValue( 100 ); mCrossHairsItem->setZValue( 100 );
@ -631,6 +596,42 @@ void QgsElevationProfileCanvas::setDistanceUnit( Qgis::DistanceUnit unit )
mPlotItem->updatePlot(); mPlotItem->updatePlot();
} }
void QgsElevationProfileCanvas::setBackgroundColor( const QColor &color )
{
if ( !color.isValid() )
{
QPalette customPalette = qApp->palette();
const QColor baseColor = qApp->palette().color( QPalette::ColorRole::Base );
const QColor windowColor = qApp->palette().color( QPalette::ColorRole::Window );
customPalette.setColor( QPalette::ColorRole::Base, windowColor );
customPalette.setColor( QPalette::ColorRole::Window, baseColor );
setPalette( customPalette );
scene()->setPalette( customPalette );
}
else
{
// build custom palette
const bool isDarkTheme = color.lightnessF() < 0.5;
QPalette customPalette = qApp->palette();
customPalette.setColor( QPalette::ColorRole::Window, color );
if ( isDarkTheme )
{
customPalette.setColor( QPalette::ColorRole::Text, QColor( 255, 255, 255 ) );
customPalette.setColor( QPalette::ColorRole::Base, color.lighter( 120 ) );
}
else
{
customPalette.setColor( QPalette::ColorRole::Text, QColor( 0, 0, 0 ) );
customPalette.setColor( QPalette::ColorRole::Base, color.darker( 120 ) );
}
setPalette( customPalette );
scene()->setPalette( customPalette );
}
updateChartFromPalette();
}
bool QgsElevationProfileCanvas::lockAxisScales() const bool QgsElevationProfileCanvas::lockAxisScales() const
{ {
return mLockAxisScales; return mLockAxisScales;
@ -1023,6 +1024,45 @@ void QgsElevationProfileCanvas::refineResults()
scheduleDeferredRegeneration(); scheduleDeferredRegeneration();
} }
void QgsElevationProfileCanvas::updateChartFromPalette()
{
const QPalette chartPalette = palette();
setBackgroundBrush( QBrush( chartPalette.color( QPalette::ColorRole::Base ) ) );
{
QgsTextFormat textFormat = mPlotItem->xAxis().textFormat();
textFormat.setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::Text ) );
mPlotItem->xAxis().setTextFormat( textFormat );
mPlotItem->yAxis().setTextFormat( textFormat );
}
{
std::unique_ptr< QgsFillSymbol > chartFill( mPlotItem->chartBackgroundSymbol()->clone() );
chartFill->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Window ) );
mPlotItem->setChartBackgroundSymbol( chartFill.release() );
}
{
std::unique_ptr< QgsFillSymbol > chartBorder( mPlotItem->chartBorderSymbol()->clone() );
chartBorder->setColor( chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text ) );
mPlotItem->setChartBorderSymbol( chartBorder.release() );
}
{
std::unique_ptr< QgsLineSymbol > chartMajorSymbol( mPlotItem->xAxis().gridMajorSymbol()->clone() );
QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
c.setAlpha( 150 );
chartMajorSymbol->setColor( c );
mPlotItem->xAxis().setGridMajorSymbol( chartMajorSymbol->clone() );
mPlotItem->yAxis().setGridMajorSymbol( chartMajorSymbol.release() );
}
{
std::unique_ptr< QgsLineSymbol > chartMinorSymbol( mPlotItem->xAxis().gridMinorSymbol()->clone() );
QColor c = chartPalette.color( QPalette::ColorGroup::Active, QPalette::ColorRole::Text );
c.setAlpha( 50 );
chartMinorSymbol->setColor( c );
mPlotItem->xAxis().setGridMinorSymbol( chartMinorSymbol->clone() );
mPlotItem->yAxis().setGridMinorSymbol( chartMinorSymbol.release() );
}
mPlotItem->updatePlot();
}
QgsProfilePoint QgsElevationProfileCanvas::canvasPointToPlotPoint( QPointF point ) const QgsProfilePoint QgsElevationProfileCanvas::canvasPointToPlotPoint( QPointF point ) const
{ {
if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) ) if ( !mPlotItem->plotArea().contains( point.x(), point.y() ) )

View File

@ -256,6 +256,16 @@ class GUI_EXPORT QgsElevationProfileCanvas : public QgsPlotCanvas
*/ */
void setDistanceUnit( Qgis::DistanceUnit unit ); void setDistanceUnit( Qgis::DistanceUnit unit );
/**
* Sets the background \a color to use for the profile canvas.
*
* The chart text, border and axis color will be automatically updated to ensure
* readability with the new background color.
*
* \since QGIS 3.34
*/
void setBackgroundColor( const QColor &color );
signals: signals:
/** /**
@ -301,6 +311,7 @@ class GUI_EXPORT QgsElevationProfileCanvas : public QgsPlotCanvas
private: private:
void updateChartFromPalette();
QgsProfileSnapContext snapContext() const; QgsProfileSnapContext snapContext() const;
QgsProfileIdentifyContext identifyContext() const; QgsProfileIdentifyContext identifyContext() const;

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsElevationOptionsWidgetBase</class>
<widget class="QWidget" name="QgsElevationOptionsWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>417</width>
<height>382</height>
</rect>
</property>
<property name="windowTitle">
<string>Raster Elevation Properties</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Profile Chart Appearance</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="1,2">
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Background color</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QgsColorButton" name="mButtonBackgroundColor">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsColorButton</class>
<extends>QToolButton</extends>
<header>qgscolorbutton.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>