QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
Loading...
Searching...
No Matches
qgslayoutchartwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutchartwidget.cpp
3 --------------------------
4 begin : August 2025
5 copyright : (C) 2025 by Mathieu
6 email : mathieu at opengis dot ch
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
20#include "qgsapplication.h"
21#include "qgsgui.h"
22#include "qgslayout.h"
24#include "qgslayoutitemchart.h"
25#include "qgsplotregistry.h"
26#include "qgsplotwidget.h"
27
28#include "moc_qgslayoutchartwidget.cpp"
29
31 : QgsLayoutItemBaseWidget( nullptr, chartItem )
32 , mChartItem( chartItem )
33{
34 setupUi( this );
35
36 //add widget for general composer item properties
37 mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, chartItem );
38 mainLayout->addWidget( mItemPropertiesWidget );
39
40 QMap<QString, QString> plotTypes = QgsApplication::instance()->plotRegistry()->plotTypes();
41 for ( auto plotTypesIterator = plotTypes.keyValueBegin(); plotTypesIterator != plotTypes.keyValueEnd(); ++plotTypesIterator )
42 {
43 mChartTypeComboBox->addItem( plotTypesIterator->second, plotTypesIterator->first );
44 }
45
46 mLayerComboBox->setFilters( Qgis::LayerFilter::VectorLayer );
47
48 connect( mChartTypeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutChartWidget::mChartTypeComboBox_currentIndexChanged );
49 connect( mChartPropertiesButton, &QPushButton::clicked, this, &QgsLayoutChartWidget::mChartPropertiesButton_clicked );
50 connect( mFlipAxesCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutChartWidget::mFlipAxesCheckBox_stateChanged );
51
52 connect( mLayerComboBox, &QgsMapLayerComboBox::layerChanged, this, &QgsLayoutChartWidget::changeLayer );
53 connect( mLayerComboBox, &QgsMapLayerComboBox::layerChanged, mSortExpressionWidget, &QgsFieldExpressionWidget::setLayer );
54 connect( mSortCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutChartWidget::mSortCheckBox_stateChanged );
55 connect( mSortExpressionWidget, static_cast<void ( QgsFieldExpressionWidget::* )( const QString &, bool )>( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsLayoutChartWidget::changeSortExpression );
56 connect( mSortDirectionButton, &QToolButton::clicked, this, &QgsLayoutChartWidget::mSortDirectionButton_clicked );
57
58 connect( mSeriesListWidget, &QListWidget::currentItemChanged, this, &QgsLayoutChartWidget::mSeriesListWidget_currentItemChanged );
59 connect( mSeriesListWidget, &QListWidget::itemChanged, this, &QgsLayoutChartWidget::mSeriesListWidget_itemChanged );
60 connect( mAddSeriesPushButton, &QPushButton::clicked, this, &QgsLayoutChartWidget::mAddSeriesPushButton_clicked );
61 connect( mRemoveSeriesPushButton, &QPushButton::clicked, this, &QgsLayoutChartWidget::mRemoveSeriesPushButton_clicked );
62 connect( mSeriesPropertiesButton, &QPushButton::clicked, this, &QgsLayoutChartWidget::mSeriesPropertiesButton_clicked );
63
64 mLinkedMapComboBox->setCurrentLayout( mChartItem->layout() );
65 mLinkedMapComboBox->setItemType( QgsLayoutItemRegistry::LayoutMap );
66 connect( mLinkedMapComboBox, &QgsLayoutItemComboBox::itemChanged, this, &QgsLayoutChartWidget::mLinkedMapComboBox_itemChanged );
67
68 connect( mFilterOnlyVisibleFeaturesCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutChartWidget::mFilterOnlyVisibleFeaturesCheckBox_stateChanged );
69 connect( mIntersectAtlasCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutChartWidget::mIntersectAtlasCheckBox_stateChanged );
70
71 setGuiElementValues();
72 updateButtonsState();
73
74 connect( mChartItem, &QgsLayoutObject::changed, this, &QgsLayoutChartWidget::setGuiElementValues );
75}
76
78{
79 if ( mItemPropertiesWidget )
80 mItemPropertiesWidget->setMasterLayout( masterLayout );
81}
82
84{
86 return false;
87
88 if ( mChartItem )
89 {
90 disconnect( mChartItem, &QgsLayoutObject::changed, this, &QgsLayoutChartWidget::setGuiElementValues );
91 }
92
93 mChartItem = qobject_cast<QgsLayoutItemChart *>( item );
94 mItemPropertiesWidget->setItem( mChartItem );
95
96 if ( mChartItem )
97 {
98 connect( mChartItem, &QgsLayoutObject::changed, this, &QgsLayoutChartWidget::setGuiElementValues );
99 }
100
101 setGuiElementValues();
102
103 return true;
104}
105
106void QgsLayoutChartWidget::setGuiElementValues()
107{
108 if ( mChartItem )
109 {
110 whileBlocking( mChartTypeComboBox )->setCurrentIndex( mChartTypeComboBox->findData( mChartItem->plot()->type() ) );
111 whileBlocking( mLayerComboBox )->setLayer( mChartItem->sourceLayer() );
112
113 Qgs2DXyPlot *plot2DXy = dynamic_cast<Qgs2DXyPlot *>( mChartItem->plot() );
114 if ( plot2DXy )
115 {
116 mFlipAxesCheckBox->setEnabled( true );
117 whileBlocking( mFlipAxesCheckBox )->setChecked( plot2DXy->flipAxes() );
118 }
119 else
120 {
121 mFlipAxesCheckBox->setEnabled( false );
122 whileBlocking( mFlipAxesCheckBox )->setChecked( false );
123 }
124
125 whileBlocking( mSortCheckBox )->setCheckState( mChartItem->sortFeatures() ? Qt::Checked : Qt::Unchecked );
126
127 whileBlocking( mSortDirectionButton )->setEnabled( mChartItem->sortFeatures() );
128 whileBlocking( mSortDirectionButton )->setArrowType( mChartItem->sortAscending() ? Qt::UpArrow : Qt::DownArrow );
129
130 whileBlocking( mSortExpressionWidget )->setEnabled( mChartItem->sortFeatures() );
131 whileBlocking( mSortExpressionWidget )->setLayer( mChartItem->sourceLayer() );
132 whileBlocking( mSortExpressionWidget )->setField( mChartItem->sortExpression() );
133
134 mSeriesListWidget->clear();
135 const QList<QgsLayoutItemChart::SeriesDetails> seriesList = mChartItem->seriesList();
136 for ( const QgsLayoutItemChart::SeriesDetails &series : seriesList )
137 {
138 addSeriesListItem( series.name() );
139 }
140
141 if ( !seriesList.isEmpty() )
142 {
143 mSeriesListWidget->setCurrentRow( 0 );
144 }
145 else
146 {
147 mSeriesPropertiesButton->setEnabled( false );
148 }
149
150 whileBlocking( mFilterOnlyVisibleFeaturesCheckBox )->setChecked( mChartItem->filterOnlyVisibleFeatures() );
151 mLinkedMapLabel->setEnabled( mFilterOnlyVisibleFeaturesCheckBox->isChecked() );
152 mLinkedMapComboBox->setEnabled( mFilterOnlyVisibleFeaturesCheckBox->isChecked() );
153 whileBlocking( mLinkedMapComboBox )->setItem( mChartItem->map() );
154
155 whileBlocking( mIntersectAtlasCheckBox )->setChecked( mChartItem->filterToAtlasFeature() );
156 }
157 else
158 {
159 mSeriesListWidget->clear();
160 }
161}
162
163void QgsLayoutChartWidget::mChartTypeComboBox_currentIndexChanged( int )
164{
165 if ( !mChartItem )
166 {
167 return;
168 }
169
170 const QString plotType = mChartTypeComboBox->currentData().toString();
171 if ( mChartItem->plot()->type() == plotType )
172 {
173 return;
174 }
175
176 QgsPlot *oldPlot = mChartItem->plot();
177 QgsPlot *newPlot = QgsApplication::instance()->plotRegistry()->createPlot( plotType );
178 // copy relevant properties from the old plot
179 newPlot->initFromPlot( oldPlot );
180 Qgs2DXyPlot *newPlot2DXy = dynamic_cast<Qgs2DXyPlot *>( newPlot );
181 if ( newPlot2DXy )
182 {
183 mFlipAxesCheckBox->setEnabled( true );
184 mFlipAxesCheckBox->setChecked( newPlot2DXy->flipAxes() );
185 }
186 else
187 {
188 mFlipAxesCheckBox->setEnabled( false );
189 mFlipAxesCheckBox->setChecked( false );
190 }
191
192 mChartItem->beginCommand( tr( "Change Chart Type" ) );
193 mChartItem->setPlot( newPlot );
194 mChartItem->update();
195 mChartItem->endCommand();
196}
197
198void QgsLayoutChartWidget::mChartPropertiesButton_clicked()
199{
200 if ( !mChartItem )
201 {
202 return;
203 }
204
206
207 const QString plotType = mChartTypeComboBox->currentData().toString();
208 QgsPlotAbstractMetadata *abstractMetadata = QgsApplication::instance()->plotRegistry()->plotMetadata( plotType );
209 if ( !abstractMetadata )
210 {
211 return;
212 }
213
214 QgsPlotWidget *widget = abstractMetadata->createPlotWidget( this );
215 if ( !widget )
216 {
217 return;
218 }
219
220 widget->registerExpressionContextGenerator( mChartItem );
221 widget->setPlot( mChartItem->plot() );
222
223 connect( widget, &QgsPanelWidget::widgetChanged, this, [this, widget]() {
224 if ( !mChartItem )
225 {
226 return;
227 }
228
229 mChartItem->beginCommand( tr( "Modify Chart" ) );
230 mChartItem->setPlot( widget->createPlot() );
231 mChartItem->endCommand();
232 mChartItem->update();
233 } );
234
235 openPanel( widget );
236}
237
238void QgsLayoutChartWidget::mFlipAxesCheckBox_stateChanged( int state )
239{
240 if ( !mChartItem )
241 {
242 return;
243 }
244
245 Qgs2DXyPlot *plot2DXy = dynamic_cast<Qgs2DXyPlot *>( mChartItem->plot() );
246 if ( !plot2DXy )
247 {
248 return;
249 }
250
251 mChartItem->beginCommand( tr( "Modify Chart" ) );
252 plot2DXy->setFlipAxes( state == Qt::Checked );
253 mChartItem->endCommand();
254 mChartItem->update();
255}
256
257void QgsLayoutChartWidget::changeLayer( QgsMapLayer *layer )
258{
259 if ( !mChartItem )
260 {
261 return;
262 }
263
264 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
265 if ( !vl )
266 {
267 return;
268 }
269
270 mChartItem->beginCommand( tr( "Change Chart Source Layer" ) );
271 mChartItem->setSourceLayer( vl );
272 mChartItem->update();
273 mChartItem->endCommand();
274 updateButtonsState();
275}
276
277void QgsLayoutChartWidget::changeSortExpression( const QString &expression, bool )
278{
279 if ( !mChartItem )
280 {
281 return;
282 }
283
284 mChartItem->beginCommand( tr( "Change Chart Source Sort Expression" ) );
285 mChartItem->setSortExpression( expression );
286 mChartItem->update();
287 mChartItem->endCommand();
288}
289
290void QgsLayoutChartWidget::mSortCheckBox_stateChanged( int state )
291{
292 if ( !mChartItem )
293 return;
294
295 if ( state == Qt::Checked )
296 {
297 mSortDirectionButton->setEnabled( true );
298 mSortExpressionWidget->setEnabled( true );
299 }
300 else
301 {
302 mSortDirectionButton->setEnabled( false );
303 mSortExpressionWidget->setEnabled( false );
304 }
305
306 mChartItem->beginCommand( tr( "Toggle Atlas Sorting" ) );
307 mChartItem->setSortFeatures( state == Qt::Checked );
308 mChartItem->update();
309 mChartItem->endCommand();
310}
311
312void QgsLayoutChartWidget::mSortDirectionButton_clicked()
313{
314 if ( !mChartItem )
315 {
316 return;
317 }
318
319 Qt::ArrowType at = mSortDirectionButton->arrowType();
320 at = ( at == Qt::UpArrow ) ? Qt::DownArrow : Qt::UpArrow;
321 mSortDirectionButton->setArrowType( at );
322
323 mChartItem->beginCommand( tr( "Change Chart Source Sort Direction" ) );
324 mChartItem->setSortAscending( at == Qt::UpArrow );
325 mChartItem->update();
326 mChartItem->endCommand();
327}
328
329QListWidgetItem *QgsLayoutChartWidget::addSeriesListItem( const QString &name )
330{
331 QListWidgetItem *item = new QListWidgetItem( name, nullptr );
332 item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable );
333 mSeriesListWidget->addItem( item );
334 return item;
335}
336
337void QgsLayoutChartWidget::mSeriesListWidget_currentItemChanged( QListWidgetItem *current, QListWidgetItem * )
338{
339 mSeriesPropertiesButton->setEnabled( static_cast<bool>( current ) );
340}
341
342void QgsLayoutChartWidget::mSeriesListWidget_itemChanged( QListWidgetItem *item )
343{
344 if ( !mChartItem )
345 {
346 return;
347 }
348
349 QList<QgsLayoutItemChart::SeriesDetails> seriesList = mChartItem->seriesList();
350 const int idx = mSeriesListWidget->row( item );
351 if ( idx >= seriesList.size() )
352 {
353 return;
354 }
355
356 mChartItem->beginCommand( tr( "Rename Chart Series" ) );
357 seriesList[idx].setName( item->text() );
358 mChartItem->setSeriesList( seriesList );
359 mChartItem->endCommand();
360}
361
362void QgsLayoutChartWidget::mAddSeriesPushButton_clicked()
363{
364 if ( !mChartItem )
365 {
366 return;
367 }
368
369 QList<QgsLayoutItemChart::SeriesDetails> seriesList = mChartItem->seriesList();
370 const QString itemName = tr( "Series %1" ).arg( seriesList.size() + 1 );
371 addSeriesListItem( itemName );
372
373 mChartItem->beginCommand( tr( "Add Chart Series" ) );
374 seriesList << QgsLayoutItemChart::SeriesDetails( itemName );
375 mChartItem->setSeriesList( seriesList );
376 mChartItem->endCommand();
377 mChartItem->update();
378
379 mSeriesListWidget->setCurrentRow( mSeriesListWidget->count() - 1 );
380 mSeriesListWidget_currentItemChanged( mSeriesListWidget->currentItem(), nullptr );
381 updateButtonsState();
382}
383
384void QgsLayoutChartWidget::mRemoveSeriesPushButton_clicked()
385{
386 QListWidgetItem *item = mSeriesListWidget->currentItem();
387 if ( !item || !mChartItem )
388 {
389 return;
390 }
391
392 QList<QgsLayoutItemChart::SeriesDetails> seriesList = mChartItem->seriesList();
393 const int idx = mSeriesListWidget->row( item );
394 if ( idx >= seriesList.size() )
395 {
396 return;
397 }
398
399 QListWidgetItem *deletedItem = mSeriesListWidget->takeItem( mSeriesListWidget->row( item ) );
400 delete deletedItem;
401
402 mChartItem->beginCommand( tr( "Remove Chart Series" ) );
403 seriesList.removeAt( idx );
404 mChartItem->setSeriesList( seriesList );
405 mChartItem->endCommand();
406 mChartItem->update();
407 updateButtonsState();
408}
409
410void QgsLayoutChartWidget::mSeriesPropertiesButton_clicked()
411{
412 QListWidgetItem *item = mSeriesListWidget->currentItem();
413 if ( !item || !mChartItem )
414 {
415 return;
416 }
417
418 QList<QgsLayoutItemChart::SeriesDetails> seriesList = mChartItem->seriesList();
419 const int idx = mSeriesListWidget->row( item );
420 if ( idx >= seriesList.size() )
421 {
422 return;
423 }
424
425 QgsLayoutChartSeriesDetailsWidget *widget = new QgsLayoutChartSeriesDetailsWidget( mChartItem->sourceLayer(), idx, seriesList[idx], this );
426 widget->registerExpressionContextGenerator( mChartItem );
427 widget->setPanelTitle( tr( "Series Details" ) );
428 connect( widget, &QgsPanelWidget::widgetChanged, this, [this, widget]() {
429 if ( !mChartItem )
430 {
431 return;
432 }
433
434 QList<QgsLayoutItemChart::SeriesDetails> seriesList = mChartItem->seriesList();
435 const int idx = widget->index();
436 if ( idx >= seriesList.size() )
437 {
438 return;
439 }
440
441 mChartItem->beginCommand( tr( "Modify Chart Series" ) );
442 seriesList[idx].setXExpression( widget->xExpression() );
443 seriesList[idx].setYExpression( widget->yExpression() );
444 seriesList[idx].setFilterExpression( widget->filterExpression() );
445 mChartItem->setSeriesList( seriesList );
446 mChartItem->endCommand();
447 mChartItem->update();
448 } );
449
450 openPanel( widget );
451}
452
453void QgsLayoutChartWidget::mLinkedMapComboBox_itemChanged( QgsLayoutItem *item )
454{
455 if ( !mChartItem )
456 {
457 return;
458 }
459
460 mChartItem->beginCommand( tr( "Change Chart Map Item" ) );
461 mChartItem->setMap( qobject_cast<QgsLayoutItemMap *>( item ) );
462 mChartItem->endCommand();
463 mChartItem->update();
464}
465
466void QgsLayoutChartWidget::mFilterOnlyVisibleFeaturesCheckBox_stateChanged( int state )
467{
468 if ( !mChartItem )
469 {
470 return;
471 }
472
473 mChartItem->beginCommand( tr( "Toggle Visible Features Only Filter" ) );
474 const bool useOnlyVisibleFeatures = ( state == Qt::Checked );
475 mChartItem->setFilterOnlyVisibleFeatures( useOnlyVisibleFeatures );
476 mChartItem->endCommand();
477 mChartItem->update();
478
479 //enable/disable map combobox based on state of checkbox
480 mLinkedMapComboBox->setEnabled( state == Qt::Checked );
481 mLinkedMapLabel->setEnabled( state == Qt::Checked );
482}
483
484void QgsLayoutChartWidget::mIntersectAtlasCheckBox_stateChanged( int state )
485{
486 if ( !mChartItem )
487 {
488 return;
489 }
490
491 mChartItem->beginCommand( tr( "Toggle Chart Atlas Filter" ) );
492 const bool filterToAtlas = ( state == Qt::Checked );
493 mChartItem->setFilterToAtlasFeature( filterToAtlas );
494 mChartItem->endCommand();
495 mChartItem->update();
496}
497
498void QgsLayoutChartWidget::updateButtonsState()
499{
500 if ( !mChartItem )
501 {
502 return;
503 }
504
505 const bool enable = qobject_cast<QgsVectorLayer *>( mLayerComboBox->currentLayer() ) != nullptr;
506 mSortCheckBox->setEnabled( enable );
507 mAddSeriesPushButton->setEnabled( enable );
508 mRemoveSeriesPushButton->setEnabled( mSeriesListWidget->count() > 0 );
509}
Base class for 2-dimensional plot/chart/graphs with an X and Y axes.
Definition qgsplot.h:687
void setFlipAxes(bool flipAxes)
Sets whether the X and Y axes are flipped.
Definition qgsplot.cpp:1224
bool flipAxes() const
Returns whether the X and Y axes are flipped.
Definition qgsplot.h:856
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QgsPlotRegistry * plotRegistry()
Returns the application's plot registry, used for plot types.
A widget for selection of layer fields or expression creation.
void setLayer(QgsMapLayer *layer)
Sets the layer used to display the fields and expression.
void fieldChanged(const QString &fieldName)
Emitted when the currently selected field changes.
static void initPlotWidgets()
Initializes plot widgets.
Definition qgsgui.cpp:461
QString yExpression() const
Returns the Y-axis expression.
QString filterExpression() const
Returns the filter expression.
QString xExpression() const
Returns the X-axis expression.
void registerExpressionContextGenerator(QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
void setMasterLayout(QgsMasterLayoutInterface *masterLayout) override
Sets the master layout associated with the item.
bool setNewItem(QgsLayoutItem *item) override
Attempts to update the widget to show the properties for the specified item.
QgsLayoutChartWidget(QgsLayoutItemChart *chartItem)
constructor
QgsLayoutItemBaseWidget(QWidget *parent SIP_TRANSFERTHIS, QgsLayoutObject *layoutObject)
Constructor for QgsLayoutItemBaseWidget, linked with the specified layoutObject.
A layout item subclass that renders chart plots.
void itemChanged(QgsLayoutItem *item)
Emitted whenever the currently selected item changes.
A widget for controlling the common properties of layout items (e.g.
Base class for graphical items within a QgsLayout.
int type() const override
Returns a unique graphics item type identifier.
void changed()
Emitted when the object's properties change.
void layerChanged(QgsMapLayer *layer)
Emitted whenever the currently selected layer changes.
Base class for all map layer types.
Definition qgsmaplayer.h:83
Interface for master layout type objects, such as print layouts and reports.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
void widgetChanged()
Emitted when the widget state changes.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
virtual QgsPlotWidget * createPlotWidget(QWidget *parent=nullptr)=0
Creates a widget for configuring plot of this type.
QgsPlot * createPlot(const QString &type) const
Creates a new instance of a plot given the type.
QMap< QString, QString > plotTypes() const
Returns a map of available plot types to translated name.
QgsPlotAbstractMetadata * plotMetadata(const QString &type) const
Returns the metadata for the specified plot type.
void registerExpressionContextGenerator(QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
virtual QgsPlot * createPlot()=0
Creates a plot defined by the current settings in the widget.
virtual void setPlot(QgsPlot *plot)=0
Sets the widget to match the settings of the plot.
virtual void initFromPlot(const QgsPlot *plot)
Initializes properties of this plot from an existing plot, transferring all applicable settings.
Definition qgsplot.cpp:101
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6880