QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
qgsplot.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsplot.cpp
3 ---------------
4 begin : March 2022
5 copyright : (C) 2022 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
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
18#include "qgsplot.h"
19
20#include <functional>
21
22#include "qgsapplication.h"
24#include "qgscolorramp.h"
25#include "qgscolorrampimpl.h"
27#include "qgsfillsymbol.h"
28#include "qgsfillsymbollayer.h"
29#include "qgslinesymbol.h"
30#include "qgslinesymbollayer.h"
31#include "qgsmarkersymbol.h"
34#include "qgssymbollayerutils.h"
35#include "qgstextrenderer.h"
36
37QgsPropertiesDefinition QgsPlot::sPropertyDefinitions;
38
39QgsPlot::~QgsPlot() = default;
40
41bool QgsPlot::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
42{
43 element.setAttribute( QStringLiteral( "plotType" ), type() );
44
45 QDomElement dataDefinedPropertiesElement = document.createElement( QStringLiteral( "dataDefinedProperties" ) );
46 mDataDefinedProperties.writeXml( dataDefinedPropertiesElement, QgsPlot::propertyDefinitions() );
47 element.appendChild( dataDefinedPropertiesElement );
48
49 return true;
50}
51
52bool QgsPlot::readXml( const QDomElement &element, const QgsReadWriteContext & )
53{
54 QDomElement dataDefinedPropertiesElement = element.firstChildElement( QStringLiteral( "dataDefinedProperties" ) );
55 mDataDefinedProperties.readXml( dataDefinedPropertiesElement, QgsPlot::propertyDefinitions() );
56
57 return true;
58}
59
60void QgsPlot::initPropertyDefinitions()
61{
62 if ( !sPropertyDefinitions.isEmpty() )
63 return;
64
65 sPropertyDefinitions = QgsPropertiesDefinition
66 {
67 { static_cast< int >( QgsPlot::DataDefinedProperty::MarginLeft ), QgsPropertyDefinition( "dataDefinedPlotMarginLeft", QObject::tr( "Left margin" ), QgsPropertyDefinition::DoublePositive ) },
68 { static_cast< int >( QgsPlot::DataDefinedProperty::MarginTop ), QgsPropertyDefinition( "dataDefinedPlotMarginTop", QObject::tr( "Top margin" ), QgsPropertyDefinition::DoublePositive ) },
69 { static_cast< int >( QgsPlot::DataDefinedProperty::MarginRight ), QgsPropertyDefinition( "dataDefinedPlotMarginRight", QObject::tr( "Right margin" ), QgsPropertyDefinition::DoublePositive ) },
70 { static_cast< int >( QgsPlot::DataDefinedProperty::MarginBottom ), QgsPropertyDefinition( "dataDefinedPlotMarginBottom", QObject::tr( "Bottom margin" ), QgsPropertyDefinition::DoublePositive ) },
71 { static_cast< int >( QgsPlot::DataDefinedProperty::XAxisMajorInterval ), QgsPropertyDefinition( "dataDefinedPlotXAxisMajorInterval", QObject::tr( "Major grid line interval for X-axis" ), QgsPropertyDefinition::DoublePositive ) },
72 { static_cast< int >( QgsPlot::DataDefinedProperty::XAxisMinorInterval ), QgsPropertyDefinition( "dataDefinedPlotXAxisMinorInterval", QObject::tr( "Minor grid line interval for X-axis" ), QgsPropertyDefinition::DoublePositive ) },
73 { static_cast< int >( QgsPlot::DataDefinedProperty::XAxisLabelInterval ), QgsPropertyDefinition( "dataDefinedPlotXAxisLabelInterval", QObject::tr( "Label interval for X-axis" ), QgsPropertyDefinition::DoublePositive ) },
74 { static_cast< int >( QgsPlot::DataDefinedProperty::YAxisMajorInterval ), QgsPropertyDefinition( "dataDefinedPlotYAxisMajorInterval", QObject::tr( "Major grid line interval for Y-axis" ), QgsPropertyDefinition::DoublePositive ) },
75 { static_cast< int >( QgsPlot::DataDefinedProperty::YAxisMinorInterval ), QgsPropertyDefinition( "dataDefinedPlotYAxisMinorInterval", QObject::tr( "Minor grid line interval for Y-axis" ), QgsPropertyDefinition::DoublePositive ) },
76 { static_cast< int >( QgsPlot::DataDefinedProperty::YAxisLabelInterval ), QgsPropertyDefinition( "dataDefinedPlotYAxisLabelInterval", QObject::tr( "Label interval for Y-axis" ), QgsPropertyDefinition::DoublePositive ) },
77 { static_cast< int >( QgsPlot::DataDefinedProperty::XAxisMinimum ), QgsPropertyDefinition( "dataDefinedPlotXAxisMinimum", QObject::tr( "X-axis minimum value" ), QgsPropertyDefinition::DoublePositive ) },
78 { static_cast< int >( QgsPlot::DataDefinedProperty::XAxisMaximum ), QgsPropertyDefinition( "dataDefinedPlotXAxisMaximum", QObject::tr( "X-axis maximum value" ), QgsPropertyDefinition::DoublePositive ) },
79 { static_cast< int >( QgsPlot::DataDefinedProperty::YAxisMinimum ), QgsPropertyDefinition( "dataDefinedPlotYAxisMinimum", QObject::tr( "Y-axis minimum value" ), QgsPropertyDefinition::Double ) },
80 { static_cast< int >( QgsPlot::DataDefinedProperty::YAxisMaximum ), QgsPropertyDefinition( "dataDefinedPlotYAxisMaximum", QObject::tr( "Y-axis maximum value" ), QgsPropertyDefinition::Double ) },
81 };
82}
83
85{
86 QgsPlot::initPropertyDefinitions();
87 return sPropertyDefinitions;
88}
89
90// QgsPlotAxis
91
93{
94 // setup default style
95 mNumericFormat.reset( QgsPlotDefaultSettings::axisLabelNumericFormat() );
96 mGridMinorSymbol.reset( QgsPlotDefaultSettings::axisGridMinorSymbol() );
97 mGridMajorSymbol.reset( QgsPlotDefaultSettings::axisGridMajorSymbol() );
98}
99
100QgsPlotAxis::~QgsPlotAxis() = default;
101
102
104{
105 return mType;
106}
107
109{
110 mType = type;
111}
112
113bool QgsPlotAxis::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
114{
115 element.setAttribute( QStringLiteral( "type" ), qgsEnumValueToKey( mType ) );
116 element.setAttribute( QStringLiteral( "gridIntervalMinor" ), qgsDoubleToString( mGridIntervalMinor ) );
117 element.setAttribute( QStringLiteral( "gridIntervalMajor" ), qgsDoubleToString( mGridIntervalMajor ) );
118 element.setAttribute( QStringLiteral( "labelInterval" ), qgsDoubleToString( mLabelInterval ) );
119 element.setAttribute( QStringLiteral( "suffix" ), mLabelSuffix );
120 element.setAttribute( QStringLiteral( "suffixPlacement" ), qgsEnumValueToKey( mSuffixPlacement ) );
121
122 QDomElement numericFormatElement = document.createElement( QStringLiteral( "numericFormat" ) );
123 mNumericFormat->writeXml( numericFormatElement, document, context );
124 element.appendChild( numericFormatElement );
125
126 QDomElement gridMajorElement = document.createElement( QStringLiteral( "gridMajorSymbol" ) );
127 gridMajorElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mGridMajorSymbol.get(), document, context ) );
128 element.appendChild( gridMajorElement );
129 QDomElement gridMinorElement = document.createElement( QStringLiteral( "gridMinorSymbol" ) );
130 gridMinorElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mGridMinorSymbol.get(), document, context ) );
131 element.appendChild( gridMinorElement );
132
133 QDomElement textFormatElement = document.createElement( QStringLiteral( "textFormat" ) );
134 textFormatElement.appendChild( mLabelTextFormat.writeXml( document, context ) );
135 element.appendChild( textFormatElement );
136
137 return true;
138}
139
140bool QgsPlotAxis::readXml( const QDomElement &element, const QgsReadWriteContext &context )
141{
142 mType = qgsEnumKeyToValue( element.attribute( QStringLiteral( "type" ) ), Qgis::PlotAxisType::Interval );
143 mGridIntervalMinor = element.attribute( QStringLiteral( "gridIntervalMinor" ) ).toDouble();
144 mGridIntervalMajor = element.attribute( QStringLiteral( "gridIntervalMajor" ) ).toDouble();
145 mLabelInterval = element.attribute( QStringLiteral( "labelInterval" ) ).toDouble();
146
147 mLabelSuffix = element.attribute( QStringLiteral( "suffix" ) );
148 mSuffixPlacement = qgsEnumKeyToValue( element.attribute( QStringLiteral( "suffixPlacement" ) ), Qgis::PlotAxisSuffixPlacement::NoLabels );
149
150 const QDomElement numericFormatElement = element.firstChildElement( QStringLiteral( "numericFormat" ) );
151 mNumericFormat.reset( QgsApplication::numericFormatRegistry()->createFromXml( numericFormatElement, context ) );
152
153 const QDomElement gridMajorElement = element.firstChildElement( QStringLiteral( "gridMajorSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
154 mGridMajorSymbol = QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( gridMajorElement, context );
155 const QDomElement gridMinorElement = element.firstChildElement( QStringLiteral( "gridMinorSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
156 mGridMinorSymbol = QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( gridMinorElement, context );
157
158 const QDomElement textFormatElement = element.firstChildElement( QStringLiteral( "textFormat" ) );
159 mLabelTextFormat.readXml( textFormatElement, context );
160
161 return true;
162}
163
165{
166 return mNumericFormat.get();
167}
168
170{
171 mNumericFormat.reset( format );
172}
173
175{
176 return mLabelSuffix;
177}
178
179void QgsPlotAxis::setLabelSuffix( const QString &suffix )
180{
181 mLabelSuffix = suffix;
182}
183
185{
186 return mSuffixPlacement;
187}
188
190{
191 mSuffixPlacement = placement;
192}
193
195{
196 return mGridMajorSymbol.get();
197}
198
200{
201 mGridMajorSymbol.reset( symbol );
202}
203
205{
206 return mGridMinorSymbol.get();
207}
208
210{
211 mGridMinorSymbol.reset( symbol );
212}
213
215{
216 return mLabelTextFormat;
217}
218
220{
221 mLabelTextFormat = format;
222}
223
224
225//
226// Qgs2DPlot
227//
228
230 : mMargins( 2, 2, 2, 2 )
231{
232}
233
234bool Qgs2DPlot::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
235{
236 QgsPlot::writeXml( element, document, context );
237
238 element.setAttribute( QStringLiteral( "margins" ), mMargins.toString() );
239
240 return true;
241}
242
243bool Qgs2DPlot::readXml( const QDomElement &element, const QgsReadWriteContext &context )
244{
245 QgsPlot::readXml( element, context );
246
247 mMargins = QgsMargins::fromString( element.attribute( QStringLiteral( "margins" ) ) );
248
249 return true;
250}
251
252void Qgs2DPlot::render( QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QgsPlotData &plotData )
253{
254 QgsExpressionContextScope *plotScope = new QgsExpressionContextScope( QStringLiteral( "plot" ) );
255 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), plotScope );
256
257 const QRectF plotArea = interiorPlotArea( context, plotContext );
258
259 // give subclasses a chance to draw their content
260 renderContent( context, plotContext, plotArea, plotData );
261}
262
264{
265}
266
267Qgs2DPlot::~Qgs2DPlot() = default;
268
269QSizeF Qgs2DPlot::size() const
270{
271 return mSize;
272}
273
275{
276 mSize = size;
277}
278
280{
281 QgsMargins usedMargins = mMargins;
282 applyDataDefinedProperties( context, usedMargins );
283
284 const double leftMargin = context.convertToPainterUnits( usedMargins.left(), Qgis::RenderUnit::Millimeters );
285 const double rightMargin = context.convertToPainterUnits( usedMargins.right(), Qgis::RenderUnit::Millimeters );
286 const double topMargin = context.convertToPainterUnits( usedMargins.top(), Qgis::RenderUnit::Millimeters );
287 const double bottomMargin = context.convertToPainterUnits( usedMargins.bottom(), Qgis::RenderUnit::Millimeters );
288
289 return QRectF( leftMargin, topMargin, mSize.width() - rightMargin - leftMargin, mSize.height() - bottomMargin - topMargin );
290}
291
293{
294 return mMargins;
295}
296
298{
299 mMargins = margins;
300}
301
303{
304 if ( !dataDefinedProperties().hasActiveProperties() )
305 {
306 return;
307 }
308
310 {
311 bool ok = false;
312 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::MarginLeft, context.expressionContext(), margins.left(), &ok );
313
314 if ( ok )
315 {
316 margins.setLeft( value );
317 }
318 }
320 {
321 bool ok = false;
322 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::MarginRight, context.expressionContext(), margins.right(), &ok );
323
324 if ( ok )
325 {
326 margins.setRight( value );
327 }
328 }
330 {
331 bool ok = false;
332 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::MarginTop, context.expressionContext(), margins.top(), &ok );
333
334 if ( ok )
335 {
336 margins.setTop( value );
337 }
338 }
340 {
341 bool ok = false;
342 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::MarginBottom, context.expressionContext(), margins.bottom(), &ok );
343
344 if ( ok )
345 {
346 margins.setBottom( value );
347 }
348 }
349}
350
351
352//
353// Qgs2DPlot
354//
355
357 : Qgs2DPlot()
358{
359 // setup default style
360 mChartBackgroundSymbol.reset( QgsPlotDefaultSettings::chartBackgroundSymbol() );
361 mChartBorderSymbol.reset( QgsPlotDefaultSettings::chartBorderSymbol() );
362}
363
364bool Qgs2DXyPlot::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
365{
366 Qgs2DPlot::writeXml( element, document, context );
367
368 element.setAttribute( QStringLiteral( "minX" ), qgsDoubleToString( mMinX ) );
369 element.setAttribute( QStringLiteral( "maxX" ), qgsDoubleToString( mMaxX ) );
370 element.setAttribute( QStringLiteral( "minY" ), qgsDoubleToString( mMinY ) );
371 element.setAttribute( QStringLiteral( "maxY" ), qgsDoubleToString( mMaxY ) );
372
373 QDomElement xAxisElement = document.createElement( QStringLiteral( "xAxis" ) );
374 mXAxis.writeXml( xAxisElement, document, context );
375 element.appendChild( xAxisElement );
376 QDomElement yAxisElement = document.createElement( QStringLiteral( "yAxis" ) );
377 mYAxis.writeXml( yAxisElement, document, context );
378 element.appendChild( yAxisElement );
379
380 QDomElement backgroundElement = document.createElement( QStringLiteral( "backgroundSymbol" ) );
381 backgroundElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mChartBackgroundSymbol.get(), document, context ) );
382 element.appendChild( backgroundElement );
383 QDomElement borderElement = document.createElement( QStringLiteral( "borderSymbol" ) );
384 borderElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mChartBorderSymbol.get(), document, context ) );
385 element.appendChild( borderElement );
386
387 return true;
388}
389
390bool Qgs2DXyPlot::readXml( const QDomElement &element, const QgsReadWriteContext &context )
391{
392 Qgs2DPlot::readXml( element, context );
393
394 mMinX = element.attribute( QStringLiteral( "minX" ) ).toDouble();
395 mMaxX = element.attribute( QStringLiteral( "maxX" ) ).toDouble();
396 mMinY = element.attribute( QStringLiteral( "minY" ) ).toDouble();
397 mMaxY = element.attribute( QStringLiteral( "maxY" ) ).toDouble();
398
399 const QDomElement xAxisElement = element.firstChildElement( QStringLiteral( "xAxis" ) );
400 mXAxis.readXml( xAxisElement, context );
401 const QDomElement yAxisElement = element.firstChildElement( QStringLiteral( "yAxis" ) );
402 mYAxis.readXml( yAxisElement, context );
403
404 const QDomElement backgroundElement = element.firstChildElement( QStringLiteral( "backgroundSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
405 mChartBackgroundSymbol = QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( backgroundElement, context );
406 const QDomElement borderElement = element.firstChildElement( QStringLiteral( "borderSymbol" ) ).firstChildElement( QStringLiteral( "symbol" ) );
407 mChartBorderSymbol = QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( borderElement, context );
408
409 return true;
410}
411
412void Qgs2DXyPlot::render( QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QgsPlotData &plotData )
413{
414 QgsExpressionContextScope *plotScope = new QgsExpressionContextScope( QStringLiteral( "plot" ) );
415 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), plotScope );
416
417 mChartBackgroundSymbol->startRender( context );
418 mChartBorderSymbol->startRender( context );
419 mXAxis.gridMinorSymbol()->startRender( context );
420 mYAxis.gridMinorSymbol()->startRender( context );
421 mXAxis.gridMajorSymbol()->startRender( context );
422 mYAxis.gridMajorSymbol()->startRender( context );
423
424 QgsMargins margins = mMargins;
426 double minX = mMinX;
427 double maxX = mMaxX;
428 double minY = mMinY;
429 double maxY = mMaxY;
430 double majorIntervalX = mXAxis.gridIntervalMajor();
431 double minorIntervalX = mXAxis.gridIntervalMinor();
432 double labelIntervalX = mXAxis.labelInterval();
433 double majorIntervalY = mYAxis.gridIntervalMajor();
434 double minorIntervalY = mYAxis.gridIntervalMinor();
435 double labelIntervalY = mYAxis.labelInterval();
436 applyDataDefinedProperties( context, minX, maxX, minY, maxY, majorIntervalX, minorIntervalX, labelIntervalX, majorIntervalY, minorIntervalY, labelIntervalY );
437
438 const double firstMinorXGrid = std::ceil( minX / minorIntervalX ) * minorIntervalX;
439 const double firstMajorXGrid = std::ceil( minX / majorIntervalX ) * majorIntervalX;
440 const double firstMinorYGrid = std::ceil( minY / minorIntervalY ) * minorIntervalY;
441 const double firstMajorYGrid = std::ceil( minY / majorIntervalY ) * majorIntervalY;
442 const double firstXLabel = labelIntervalX > 0 ? std::ceil( minX / labelIntervalX ) * labelIntervalX : 0;
443 const double firstYLabel = labelIntervalY > 0 ? std::ceil( minY / labelIntervalY ) * labelIntervalY : 0;
444
445 const QString xAxisSuffix = mXAxis.labelSuffix();
446 const QString yAxisSuffix = mYAxis.labelSuffix();
447
448 const QRectF plotArea = interiorPlotArea( context, plotContext );
449
450 const double xTolerance = minorIntervalX / 100000;
451 const double yTolerance = minorIntervalY / 100000;
452
453 QgsNumericFormatContext numericContext;
454
455 // categories
456 const QStringList categories = plotData.categories();
457
458 // calculate text metrics
459 double maxYAxisLabelWidth = 0;
460 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
461 switch ( mYAxis.type() )
462 {
464 if ( labelIntervalY > 0 )
465 {
466 for ( double currentY = firstYLabel; ; currentY += labelIntervalY )
467 {
468 const bool hasMoreLabels = currentY + labelIntervalY <= maxY && !qgsDoubleNear( currentY + labelIntervalY, maxY, yTolerance );
469 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
470 QString text = mYAxis.numericFormat()->formatDouble( currentY, numericContext );
471 switch ( mYAxis.labelSuffixPlacement() )
472 {
474 break;
475
477 text += yAxisSuffix;
478 break;
479
481 if ( currentY == firstYLabel )
482 text += yAxisSuffix;
483 break;
484
486 if ( !hasMoreLabels )
487 text += yAxisSuffix;
488 break;
489
491 if ( currentY == firstYLabel || !hasMoreLabels )
492 text += yAxisSuffix;
493 break;
494 }
495
496 maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { text } ) );
497 if ( !hasMoreLabels )
498 break;
499 }
500 }
501 break;
502
504 for ( int i = 0; i < categories.size(); i++ )
505 {
506 maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { categories.at( i ) } ) );
507 }
508 break;
509 }
510
511 const double chartAreaLeft = plotArea.left();
512 const double chartAreaRight = plotArea.right();
513 const double chartAreaTop = plotArea.top();
514 const double chartAreaBottom = plotArea.bottom();
515
516 // chart background
517 mChartBackgroundSymbol->renderPolygon( QPolygonF(
518 {
519 QPointF( chartAreaLeft, chartAreaTop ),
520 QPointF( chartAreaRight, chartAreaTop ),
521 QPointF( chartAreaRight, chartAreaBottom ),
522 QPointF( chartAreaLeft, chartAreaBottom ),
523 QPointF( chartAreaLeft, chartAreaTop )
524 } ), nullptr, nullptr, context );
525
526 const double xScale = ( chartAreaRight - chartAreaLeft ) / ( maxX - minX );
527 const double yScale = ( chartAreaBottom - chartAreaTop ) / ( maxY - minY );
528
529 constexpr int MAX_OBJECTS = 1000;
530
531 // grid lines
532
533 // x
534 switch ( mXAxis.type() )
535 {
537 {
538 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
539 double nextMajorXGrid = firstMajorXGrid;
540 int objectNumber = 0;
541 for ( double currentX = firstMinorXGrid; objectNumber < MAX_OBJECTS && ( currentX <= maxX && !qgsDoubleNear( currentX, maxX, xTolerance ) ); currentX += minorIntervalX, ++objectNumber )
542 {
543 bool isMinor = true;
544 if ( qgsDoubleNear( currentX, nextMajorXGrid, xTolerance ) )
545 {
546 isMinor = false;
547 nextMajorXGrid += majorIntervalX;
548 }
549
550 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
551
552 QgsLineSymbol *currentGridSymbol = isMinor ? mXAxis.gridMinorSymbol() : mXAxis.gridMajorSymbol();
553 currentGridSymbol->renderPolyline( QPolygonF(
554 QVector<QPointF>
555 {
556 QPointF( ( currentX - minX ) * xScale + chartAreaLeft, chartAreaBottom ),
557 QPointF( ( currentX - minX ) * xScale + chartAreaLeft, chartAreaTop )
558 } ), nullptr, context );
559 }
560 break;
561 }
562
564 // No grid lines here, skipping
565 break;
566 }
567
568 // y
569 switch ( mYAxis.type() )
570 {
572 {
573 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
574 double nextMajorYGrid = firstMajorYGrid;
575 int objectNumber = 0;
576 for ( double currentY = firstMinorYGrid; objectNumber < MAX_OBJECTS && ( currentY <= maxY && !qgsDoubleNear( currentY, maxY, yTolerance ) ); currentY += minorIntervalY, ++objectNumber )
577 {
578 bool isMinor = true;
579 if ( qgsDoubleNear( currentY, nextMajorYGrid, yTolerance ) )
580 {
581 isMinor = false;
582 nextMajorYGrid += majorIntervalY;
583 }
584
585 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
586
587 QgsLineSymbol *currentGridSymbol = isMinor ? mYAxis.gridMinorSymbol() : mYAxis.gridMajorSymbol();
588 currentGridSymbol->renderPolyline( QPolygonF(
589 QVector<QPointF>
590 {
591 QPointF( chartAreaLeft, chartAreaBottom - ( currentY - minY ) * yScale ),
592 QPointF( chartAreaRight, chartAreaBottom - ( currentY - minY ) * yScale )
593 } ), nullptr, context );
594 }
595 break;
596 }
597
599 // No grid lines here, skipping
600 break;
601 }
602
603 // axis labels
604
605 // x
606 switch ( mXAxis.type() )
607 {
609 {
610 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
611 int objectNumber = 0;
612 if ( labelIntervalX > 0 )
613 {
614 for ( double currentX = firstXLabel; ; currentX += labelIntervalX, ++objectNumber )
615 {
616 const bool hasMoreLabels = objectNumber + 1 < MAX_OBJECTS && ( currentX + labelIntervalX <= maxX || qgsDoubleNear( currentX + labelIntervalX, maxX, xTolerance ) );
617 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
618 QString text = mXAxis.numericFormat()->formatDouble( currentX, numericContext );
619 switch ( mXAxis.labelSuffixPlacement() )
620 {
622 break;
623
625 text += xAxisSuffix;
626 break;
627
629 if ( objectNumber == 0 )
630 text += xAxisSuffix;
631 break;
632
634 if ( !hasMoreLabels )
635 text += xAxisSuffix;
636 break;
637
639 if ( objectNumber == 0 || !hasMoreLabels )
640 text += xAxisSuffix;
641 break;
642 }
643
644 QgsTextRenderer::drawText( QPointF( ( currentX - minX ) * xScale + chartAreaLeft, mSize.height() - context.convertToPainterUnits( margins.bottom(), Qgis::RenderUnit::Millimeters ) ),
645 0, Qgis::TextHorizontalAlignment::Center, { text }, context, mXAxis.textFormat() );
646 if ( !hasMoreLabels )
647 break;
648 }
649 }
650 break;
651 }
652
654 {
655 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
656 const double categoryWidth = plotArea.width() / categories.size();
657 for ( int i = 0; i < categories.size(); i++ )
658 {
659 const double currentX = ( i * categoryWidth ) + categoryWidth / 2.0;
660 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), categories.at( i ), true ) );
661 QgsTextRenderer::drawText( QPointF( currentX + chartAreaLeft, mSize.height() - context.convertToPainterUnits( margins.bottom(), Qgis::RenderUnit::Millimeters ) ),
662 0, Qgis::TextHorizontalAlignment::Center, { categories.at( i ) }, context, mXAxis.textFormat() );
663 }
664 break;
665 }
666 }
667
668 // y
669 switch ( mYAxis.type() )
670 {
672 {
673 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
674 int objectNumber = 0;
675 if ( labelIntervalY > 0 )
676 {
677 for ( double currentY = firstYLabel; ; currentY += labelIntervalY, ++objectNumber )
678 {
679 const bool hasMoreLabels = objectNumber + 1 < MAX_OBJECTS && ( currentY + labelIntervalY <= maxY || qgsDoubleNear( currentY + labelIntervalY, maxY, yTolerance ) );
680 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
681 QString text = mYAxis.numericFormat()->formatDouble( currentY, numericContext );
682 switch ( mYAxis.labelSuffixPlacement() )
683 {
685 break;
686
688 text += yAxisSuffix;
689 break;
690
692 if ( objectNumber == 0 )
693 text += yAxisSuffix;
694 break;
695
697 if ( !hasMoreLabels )
698 text += yAxisSuffix;
699 break;
700
702 if ( objectNumber == 0 || !hasMoreLabels )
703 text += yAxisSuffix;
704 break;
705 }
706
707 const double height = QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { text } );
709 maxYAxisLabelWidth + context.convertToPainterUnits( margins.left(), Qgis::RenderUnit::Millimeters ),
710 chartAreaBottom - ( currentY - minY ) * yScale + height / 2 ),
711 0, Qgis::TextHorizontalAlignment::Right, { text }, context, mYAxis.textFormat(), false );
712 if ( !hasMoreLabels )
713 break;
714 }
715 }
716 break;
717 }
718
720 {
721 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
722 const double categoryHeight = plotArea.height() / categories.size();
723 for ( int i = 0; i < categories.size(); i++ )
724 {
725 const double currentY = ( i * categoryHeight ) + categoryHeight / 2.0;
726 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), categories.at( i ), true ) );
727 const double height = QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { categories.at( i ) } );
729 maxYAxisLabelWidth + context.convertToPainterUnits( margins.left(), Qgis::RenderUnit::Millimeters ),
730 chartAreaBottom - currentY + height / 2 ),
731 0, Qgis::TextHorizontalAlignment::Right, { categories.at( i ) }, context, mYAxis.textFormat(), false );
732 }
733 break;
734 }
735 }
736
737 // give subclasses a chance to draw their content
738 renderContent( context, plotContext, plotArea, plotData );
739
740 // border
741 mChartBorderSymbol->renderPolygon( QPolygonF(
742 {
743 QPointF( chartAreaLeft, chartAreaTop ),
744 QPointF( chartAreaRight, chartAreaTop ),
745 QPointF( chartAreaRight, chartAreaBottom ),
746 QPointF( chartAreaLeft, chartAreaBottom ),
747 QPointF( chartAreaLeft, chartAreaTop )
748 } ), nullptr, nullptr, context );
749
750 mChartBackgroundSymbol->stopRender( context );
751 mChartBorderSymbol->stopRender( context );
752 mXAxis.gridMinorSymbol()->stopRender( context );
753 mYAxis.gridMinorSymbol()->stopRender( context );
754 mXAxis.gridMajorSymbol()->stopRender( context );
755 mYAxis.gridMajorSymbol()->stopRender( context );
756}
757
758Qgs2DXyPlot::~Qgs2DXyPlot() = default;
759
761{
762 double minX = mMinX;
763 double maxX = mMaxX;
764 double minY = mMinY;
765 double maxY = mMaxY;
766 double majorIntervalX = mXAxis.gridIntervalMajor();
767 double minorIntervalX = mXAxis.gridIntervalMinor();
768 double labelIntervalX = mXAxis.labelInterval();
769 double majorIntervalY = mYAxis.gridIntervalMajor();
770 double minorIntervalY = mYAxis.gridIntervalMinor();
771 double labelIntervalY = mYAxis.labelInterval();
772 applyDataDefinedProperties( context, minX, maxX, minY, maxY, majorIntervalX, minorIntervalX, labelIntervalX, majorIntervalY, minorIntervalY, labelIntervalY );
773
774 QgsExpressionContextScope *plotScope = new QgsExpressionContextScope( QStringLiteral( "plot" ) );
775 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), plotScope );
776
777 const double firstMinorYGrid = std::ceil( minY / minorIntervalY ) * minorIntervalY;
778 const double firstXLabel = labelIntervalX > 0 ? std::ceil( minX / labelIntervalX ) * labelIntervalX : 0;
779
780 const QString xAxisSuffix = mXAxis.labelSuffix();
781 const QString yAxisSuffix = mYAxis.labelSuffix();
782 const double yAxisSuffixWidth = yAxisSuffix.isEmpty() ? 0 : QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { yAxisSuffix } );
783
784 QgsNumericFormatContext numericContext;
785
786 const double xTolerance = minorIntervalX / 100000;
787 const double yTolerance = minorIntervalX / 100000;
788
789 constexpr int MAX_LABELS = 1000;
790
791 // calculate text metrics
792 int labelNumber = 0;
793 double maxXAxisLabelHeight = 0;
794 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
795 if ( labelIntervalX > 0 )
796 {
797 for ( double currentX = firstXLabel; ; currentX += labelIntervalX, labelNumber++ )
798 {
799 const bool hasMoreLabels = labelNumber + 1 < MAX_LABELS && ( currentX + labelIntervalX <= maxX || qgsDoubleNear( currentX + labelIntervalX, maxX, xTolerance ) );
800
801 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
802 QString text = mXAxis.numericFormat()->formatDouble( currentX, numericContext );
803 switch ( mXAxis.labelSuffixPlacement() )
804 {
806 break;
807
809 text += xAxisSuffix;
810 break;
811
813 if ( labelNumber == 0 )
814 text += xAxisSuffix;
815 break;
816
818 if ( !hasMoreLabels )
819 text += xAxisSuffix;
820 break;
821
823 if ( labelNumber == 0 || !hasMoreLabels )
824 text += xAxisSuffix;
825 break;
826 }
827 maxXAxisLabelHeight = std::max( maxXAxisLabelHeight, QgsTextRenderer::textHeight( context, mXAxis.textFormat(), { text } ) );
828 if ( !hasMoreLabels )
829 break;
830 }
831 }
832
833 double maxYAxisLabelWidth = 0;
834 labelNumber = 0;
835 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
836 for ( double currentY = firstMinorYGrid; ; currentY += minorIntervalY, labelNumber ++ )
837 {
838 const bool hasMoreLabels = labelNumber + 1 < MAX_LABELS && ( currentY + minorIntervalY <= maxY || qgsDoubleNear( currentY + minorIntervalY, maxY, yTolerance ) );
839 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
840 const QString text = mYAxis.numericFormat()->formatDouble( currentY, numericContext );
841 double thisLabelWidth = QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { text } );
842 if ( yAxisSuffixWidth > 0 )
843 {
844 switch ( mYAxis.labelSuffixPlacement() )
845 {
847 break;
848
850 thisLabelWidth += yAxisSuffixWidth;
851 break;
852
854 if ( labelNumber == 0 )
855 thisLabelWidth += yAxisSuffixWidth;
856 break;
857
859 if ( !hasMoreLabels )
860 thisLabelWidth += yAxisSuffixWidth;
861 break;
862
864 if ( labelNumber == 0 || !hasMoreLabels )
865 thisLabelWidth += yAxisSuffixWidth;
866 break;
867 }
868 }
869 maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, thisLabelWidth );
870 if ( !hasMoreLabels )
871 break;
872 }
873
874 const double leftTextSize = maxYAxisLabelWidth + context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
875 const double rightTextSize = 0;
876 const double bottomTextSize = maxXAxisLabelHeight + context.convertToPainterUnits( 0.5, Qgis::RenderUnit::Millimeters );
877 const double topTextSize = 0;
878
879 QgsMargins margins = mMargins;
881 const double leftMargin = context.convertToPainterUnits( margins.left(), Qgis::RenderUnit::Millimeters ) + leftTextSize;
882 const double rightMargin = context.convertToPainterUnits( margins.right(), Qgis::RenderUnit::Millimeters ) + rightTextSize;
883 const double topMargin = context.convertToPainterUnits( margins.top(), Qgis::RenderUnit::Millimeters ) + topTextSize;
884 const double bottomMargin = context.convertToPainterUnits( margins.bottom(), Qgis::RenderUnit::Millimeters ) + bottomTextSize;
885
886 return QRectF( leftMargin, topMargin, mSize.width() - rightMargin - leftMargin, mSize.height() - bottomMargin - topMargin );
887}
888
890{
891 if ( !mSize.isValid() )
892 return;
893
894 // aim for about 40% coverage of label text to available space
895 constexpr double IDEAL_WIDTH = 0.4;
896 constexpr double TOLERANCE = 0.04;
897 constexpr int MAX_LABELS = 1000;
898
899 QgsMargins margins = mMargins;
901 const double leftMargin = context.convertToPainterUnits( margins.left(), Qgis::RenderUnit::Millimeters );
902 const double rightMargin = context.convertToPainterUnits( margins.right(), Qgis::RenderUnit::Millimeters );
903 const double topMargin = context.convertToPainterUnits( margins.top(), Qgis::RenderUnit::Millimeters );
904 const double bottomMargin = context.convertToPainterUnits( margins.bottom(), Qgis::RenderUnit::Millimeters );
905
906 const double availableWidth = mSize.width() - leftMargin - rightMargin;
907 const double availableHeight = mSize.height() - topMargin - bottomMargin;
908
909 QgsNumericFormatContext numericContext;
910
911 auto refineIntervalForAxis = [&]( double axisMinimum, double axisMaximum,
912 const std::function< double( double ) > &sizeForLabel,
913 double availableSize, double idealSizePercent, double sizeTolerancePercent,
914 double & labelInterval, double & majorInterval, double & minorInterval )
915 {
916 auto roundBase10 = []( double value )->double
917 {
918 return std::pow( 10, std::floor( std::log10( value ) ) );
919 };
920
921 // if the current interval is good enough, don't change it!
922 double totalSize = 0;
923 int initialLabelCount = 0;
924 {
925 const double firstLabelPos = std::ceil( axisMinimum / labelInterval ) * labelInterval;
926
927 for ( double currentPos = firstLabelPos; initialLabelCount <= MAX_LABELS && currentPos <= axisMaximum; currentPos += labelInterval, ++initialLabelCount )
928 {
929 totalSize += sizeForLabel( currentPos );
930 }
931 }
932
933 // we consider the current interval as "good enough" if it results in somewhere between 20-60% label text coverage over the size
934 if ( initialLabelCount >= MAX_LABELS || ( totalSize / availableSize < ( idealSizePercent - sizeTolerancePercent ) ) || ( totalSize / availableSize > ( idealSizePercent + sizeTolerancePercent ) ) )
935 {
936 // we start with trying to fit 30 labels in and then raise the interval till we're happy
937 int numberLabelsInitial = std::floor( availableSize / 30 );
938
939 double labelIntervalTest = ( axisMaximum - axisMinimum ) / numberLabelsInitial;
940 double baseValue = roundBase10( labelIntervalTest );
941 double candidate = baseValue;
942 int currentMultiplier = 1;
943
944 int numberLabels = 0;
945 while ( true )
946 {
947 const double firstLabelPosition = std::ceil( axisMinimum / candidate ) * candidate;
948 double totalSize = 0;
949 numberLabels = 0;
950 for ( double currentPos = firstLabelPosition; currentPos <= axisMaximum; currentPos += candidate )
951 {
952 totalSize += sizeForLabel( currentPos );
953 numberLabels += 1;
954
955 if ( numberLabels > MAX_LABELS ) // avoid hangs if candidate size is very small
956 break;
957 }
958
959 if ( numberLabels <= MAX_LABELS && totalSize <= availableSize * idealSizePercent )
960 break;
961
962 if ( currentMultiplier == 1 )
963 currentMultiplier = 2;
964 else if ( currentMultiplier == 2 )
965 currentMultiplier = 5;
966 else if ( currentMultiplier == 5 )
967 {
968 baseValue *= 10;
969 currentMultiplier = 1;
970 }
971
972 candidate = baseValue * currentMultiplier;
973 }
974 labelInterval = candidate;
975 if ( numberLabels < 10 )
976 {
977 minorInterval = labelInterval / 2;
978 majorInterval = minorInterval * 4;
979 }
980 else
981 {
982 minorInterval = labelInterval;
983 majorInterval = minorInterval * 5;
984 }
985 }
986 };
987
988 double minX = mMinX;
989 double maxX = mMaxX;
990 double minY = mMinY;
991 double maxY = mMaxY;
992 double majorIntervalX = mXAxis.gridIntervalMajor();
993 double minorIntervalX = mXAxis.gridIntervalMinor();
994 double labelIntervalX = mXAxis.labelInterval();
995 double majorIntervalY = mYAxis.gridIntervalMajor();
996 double minorIntervalY = mYAxis.gridIntervalMinor();
997 double labelIntervalY = mYAxis.labelInterval();
998 applyDataDefinedProperties( context, minX, maxX, minY, maxY, majorIntervalX, minorIntervalX, labelIntervalX, majorIntervalY, minorIntervalY, labelIntervalY );
999
1000 {
1001 const QString suffixX = mXAxis.labelSuffix();
1002 const double suffixWidth = !suffixX.isEmpty() ? QgsTextRenderer::textWidth( context, mXAxis.textFormat(), { suffixX } ) : 0;
1003 refineIntervalForAxis( minX, maxX, [this, &context, suffixWidth, &numericContext]( double position ) -> double
1004 {
1005 const QString text = mXAxis.numericFormat()->formatDouble( position, numericContext );
1006 // this isn't accurate, as we're always considering the suffix to be present... but it's too tricky to actually consider
1007 // the suffix placement!
1008 return QgsTextRenderer::textWidth( context, mXAxis.textFormat(), { text } ) + suffixWidth;
1009 }, availableWidth,
1010 IDEAL_WIDTH, TOLERANCE, labelIntervalX, majorIntervalX, minorIntervalX );
1011 mXAxis.setLabelInterval( labelIntervalX );
1012 mXAxis.setGridIntervalMajor( majorIntervalX );
1013 mXAxis.setGridIntervalMinor( minorIntervalX );
1014 }
1015
1016 {
1017 const QString suffixY = mYAxis.labelSuffix();
1018 refineIntervalForAxis( minY, maxY, [this, &context, suffixY, &numericContext]( double position ) -> double
1019 {
1020 const QString text = mYAxis.numericFormat()->formatDouble( position, numericContext );
1021 // this isn't accurate, as we're always considering the suffix to be present... but it's too tricky to actually consider
1022 // the suffix placement!
1023 return QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { text + suffixY } );
1024 }, availableHeight,
1025 IDEAL_WIDTH, TOLERANCE, labelIntervalY, majorIntervalY, minorIntervalY );
1026 mYAxis.setLabelInterval( labelIntervalY );
1027 mYAxis.setGridIntervalMajor( majorIntervalY );
1028 mYAxis.setGridIntervalMinor( minorIntervalY );
1029 }
1030}
1031
1033{
1034 return mChartBackgroundSymbol.get();
1035}
1036
1038{
1039 mChartBackgroundSymbol.reset( symbol );
1040}
1041
1043{
1044 return mChartBorderSymbol.get();
1045}
1046
1048{
1049 mChartBorderSymbol.reset( symbol );
1050}
1051
1052void Qgs2DXyPlot::applyDataDefinedProperties( QgsRenderContext &context, double &minX, double &maxX, double &minY, double &maxY, double &majorIntervalX, double &minorIntervalX, double &labelIntervalX, double &majorIntervalY, double &minorIntervalY, double &labelIntervalY ) const
1053{
1054 if ( !dataDefinedProperties().hasActiveProperties() )
1055 {
1056 return;
1057 }
1058
1060 {
1061 bool ok = false;
1062 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::XAxisMinimum, context.expressionContext(), minX, &ok );
1063
1064 if ( ok )
1065 {
1066 minX = value;
1067 }
1068 }
1070 {
1071 bool ok = false;
1072 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::XAxisMaximum, context.expressionContext(), maxX, &ok );
1073
1074 if ( ok )
1075 {
1076 maxX = value;
1077 }
1078 }
1080 {
1081 bool ok = false;
1082 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::YAxisMinimum, context.expressionContext(), minY, &ok );
1083
1084 if ( ok )
1085 {
1086 minY = value;
1087 }
1088 }
1090 {
1091 bool ok = false;
1092 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::YAxisMaximum, context.expressionContext(), maxY, &ok );
1093
1094 if ( ok )
1095 {
1096 maxY = value;
1097 }
1098 }
1100 {
1101 bool ok = false;
1102 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::XAxisMajorInterval, context.expressionContext(), majorIntervalX, &ok );
1103
1104 if ( ok )
1105 {
1106 majorIntervalX = value;
1107 }
1108 }
1110 {
1111 bool ok = false;
1112 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::XAxisMinorInterval, context.expressionContext(), minorIntervalX, &ok );
1113
1114 if ( ok )
1115 {
1116 minorIntervalX = value;
1117 }
1118 }
1120 {
1121 bool ok = false;
1122 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::XAxisLabelInterval, context.expressionContext(), labelIntervalX, &ok );
1123
1124 if ( ok )
1125 {
1126 labelIntervalX = value;
1127 }
1128 }
1130 {
1131 bool ok = false;
1132 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::YAxisMajorInterval, context.expressionContext(), majorIntervalY, &ok );
1133
1134 if ( ok )
1135 {
1136 majorIntervalY = value;
1137 }
1138 }
1140 {
1141 bool ok = false;
1142 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::YAxisMinorInterval, context.expressionContext(), minorIntervalY, &ok );
1143
1144 if ( ok )
1145 {
1146 minorIntervalY = value;
1147 }
1148 }
1150 {
1151 bool ok = false;
1152 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::YAxisLabelInterval, context.expressionContext(), labelIntervalY, &ok );
1153
1154 if ( ok )
1155 {
1156 labelIntervalY = value;
1157 }
1158 }
1159}
1160
1161//
1162// QgsPlotDefaultSettings
1163//
1164
1169
1171{
1172 auto gridMajor = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20, 150 ), 0.1 );
1173 gridMajor->setPenCapStyle( Qt::FlatCap );
1174 return new QgsLineSymbol( QgsSymbolLayerList( { gridMajor.release() } ) );
1175}
1176
1178{
1179 auto gridMinor = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20, 50 ), 0.1 );
1180 gridMinor->setPenCapStyle( Qt::FlatCap );
1181 return new QgsLineSymbol( QgsSymbolLayerList( { gridMinor.release() } ) );
1182}
1183
1185{
1186 auto chartFill = std::make_unique< QgsSimpleFillSymbolLayer >( QColor( 255, 255, 255 ) );
1187 return new QgsFillSymbol( QgsSymbolLayerList( { chartFill.release() } ) );
1188}
1189
1191{
1192 auto chartBorder = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20 ), 0.1 );
1193 return new QgsFillSymbol( QgsSymbolLayerList( { chartBorder.release() } ) );
1194}
1195
1197{
1198 auto chartMarker = std::make_unique< QgsSimpleMarkerSymbolLayer>( Qgis::MarkerShape::Circle, 1.8, 0.0, DEFAULT_SCALE_METHOD, QColor( 89, 150, 50 ) );
1199 return new QgsMarkerSymbol( QgsSymbolLayerList( { chartMarker.release() } ) );
1200}
1201
1203{
1204 auto chartLine = std::make_unique< QgsSimpleLineSymbolLayer>( QColor( 89, 150, 50, 100 ), 0.6 );
1205 return new QgsLineSymbol( QgsSymbolLayerList( { chartLine.release() } ) );
1206}
1207
1209{
1210 auto chartFill = std::make_unique< QgsSimpleFillSymbolLayer>( QColor( 89, 150, 50 ) );
1211 return new QgsFillSymbol( QgsSymbolLayerList( { chartFill.release() } ) );
1212}
1213
1215{
1216 auto chartFill = std::make_unique< QgsSimpleFillSymbolLayer>( QColor( 150, 150, 150 ) );
1217 return new QgsFillSymbol( QgsSymbolLayerList( { chartFill.release() } ) );
1218}
1219
1221{
1222 return new QgsPresetSchemeColorRamp( { QColor( 89, 150, 50 ), QColor( 228, 26, 28 ), QColor( 55, 126, 184 ), QColor( 152, 78, 163 ), QColor( 255, 127, 0 ), QColor( 166, 86, 40 ), QColor( 247, 129, 191 ), QColor( 153, 153, 153 ) } );
1223}
1224
1229
1230//
1231// QgsPlotData
1232//
1233
1238
1240 : mCategories( other.mCategories )
1241{
1242 for ( QgsAbstractPlotSeries *series : other.mSeries )
1243 {
1244 addSeries( series->clone() );
1245 }
1246}
1247
1249 : mSeries( std::move( other.mSeries ) )
1250 , mCategories( std::move( other.mCategories ) )
1251{
1252}
1253
1255{
1256 if ( this != &other )
1257 {
1258 clearSeries();
1259
1260 mCategories = other.mCategories;
1261 for ( QgsAbstractPlotSeries *series : other.mSeries )
1262 {
1263 addSeries( series->clone() );
1264 }
1265 }
1266 return *this;
1267}
1268
1270{
1271 if ( this != &other )
1272 {
1273 clearSeries();
1274
1275 mCategories = std::move( other.mCategories );
1276 mSeries = std::move( other.mSeries );
1277 }
1278 return *this;
1279}
1280
1281QList<QgsAbstractPlotSeries *> QgsPlotData::series() const
1282{
1283 return mSeries;
1284}
1285
1287{
1288 if ( !mSeries.contains( series ) )
1289 {
1290 mSeries << series;
1291 }
1292}
1293
1295{
1296 qDeleteAll( mSeries );
1297 mSeries.clear();
1298}
1299
1300QStringList QgsPlotData::categories() const
1301{
1302 return mCategories;
1303}
1304
1305void QgsPlotData::setCategories( const QStringList &categories )
1306{
1307 mCategories = categories;
1308}
1309
1310//
1311// QgsAbstractPlotSeries
1312//
1313
1315{
1316 return mName;
1317}
1318
1320{
1321 mName = name;
1322}
1323
1324//
1325// QgsXyPlotSeries
1326//
1327
1328QList<std::pair<double, double>> QgsXyPlotSeries::data() const
1329{
1330 return mData;
1331}
1332
1333void QgsXyPlotSeries::setData( const QList<std::pair<double, double>> &data )
1334{
1335 mData = data;
1336}
1337
1338void QgsXyPlotSeries::append( double x, double y )
1339{
1340 mData << std::make_pair( x, y );
1341}
1342
1344{
1345 mData.clear();
1346}
1347
1349{
1350 QgsXyPlotSeries *series = new QgsXyPlotSeries();
1351 series->setName( name() );
1352 series->setData( mData );
1353 return series;
1354}
PlotAxisSuffixPlacement
Placement options for suffixes in the labels for axis of plots.
Definition qgis.h:3318
@ FirstAndLastLabels
Place suffix after the first and last label values only.
Definition qgis.h:3323
@ EveryLabel
Place suffix after every value label.
Definition qgis.h:3320
@ FirstLabel
Place suffix after the first label value only.
Definition qgis.h:3321
@ LastLabel
Place suffix after the last label value only.
Definition qgis.h:3322
@ NoLabels
Do not place suffixes.
Definition qgis.h:3319
@ Circle
Circle.
Definition qgis.h:3089
PlotAxisType
Plots axis types.
Definition qgis.h:3334
@ Categorical
The axis represents categories.
Definition qgis.h:3336
@ Interval
The axis represents a range of values.
Definition qgis.h:3335
@ Millimeters
Millimeters.
Definition qgis.h:5184
@ Center
Center align.
Definition qgis.h:2944
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition qgsplot.cpp:234
void applyDataDefinedProperties(QgsRenderContext &context, QgsMargins &margins) const
Applies 2D plot data-defined properties.
Definition qgsplot.cpp:302
virtual void renderContent(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QRectF &plotArea, const QgsPlotData &plotData=QgsPlotData())
Renders the plot content.
Definition qgsplot.cpp:263
void setSize(QSizeF size)
Sets the overall size of the plot (including titles and over components which sit outside the plot ar...
Definition qgsplot.cpp:274
const QgsMargins & margins() const
Returns the margins of the plot area (in millimeters).
Definition qgsplot.cpp:292
~Qgs2DPlot() override
virtual QRectF interiorPlotArea(QgsRenderContext &context, QgsPlotRenderContext &plotContext) const
Returns the area of the plot which corresponds to the actual plot content (excluding all titles and o...
Definition qgsplot.cpp:279
Qgs2DPlot()
Constructor for Qgs2DPlot.
Definition qgsplot.cpp:229
bool readXml(const QDomElement &element, const QgsReadWriteContext &context) override
Reads the plot's properties from an XML element.
Definition qgsplot.cpp:243
virtual void render(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QgsPlotData &plotData=QgsPlotData())
Renders the plot.
Definition qgsplot.cpp:252
QSizeF size() const
Returns the overall size of the plot (in millimeters) (including titles and other components which si...
Definition qgsplot.cpp:269
void setMargins(const QgsMargins &margins)
Sets the margins of the plot area (in millimeters).
Definition qgsplot.cpp:297
bool readXml(const QDomElement &element, const QgsReadWriteContext &context) override
Reads the plot's properties from an XML element.
Definition qgsplot.cpp:390
void setChartBackgroundSymbol(QgsFillSymbol *symbol)
Sets the fill symbol used to render the background of the chart.
Definition qgsplot.cpp:1037
void applyDataDefinedProperties(QgsRenderContext &context, double &minX, double &maxX, double &minY, double &maxY, double &majorIntervalX, double &minorIntervalX, double &labelIntervalX, double &majorIntervalY, double &minorIntervalY, double &labelIntervalY) const
Applies 2D XY plot data-defined properties.
Definition qgsplot.cpp:1052
void setChartBorderSymbol(QgsFillSymbol *symbol)
Sets the symbol used to render the border of the chart.
Definition qgsplot.cpp:1047
~Qgs2DXyPlot() override
void render(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QgsPlotData &plotData=QgsPlotData()) override
Renders the plot.
Definition qgsplot.cpp:412
QgsFillSymbol * chartBackgroundSymbol()
Returns the fill symbol used to render the background of the chart.
Definition qgsplot.cpp:1032
void calculateOptimisedIntervals(QgsRenderContext &context, QgsPlotRenderContext &plotContext)
Automatically sets the grid and label intervals to optimal values for display in the given render con...
Definition qgsplot.cpp:889
Qgs2DXyPlot()
Constructor for Qgs2DXyPlot.
Definition qgsplot.cpp:356
QgsFillSymbol * chartBorderSymbol()
Returns the symbol used to render the border of the chart.
Definition qgsplot.cpp:1042
QRectF interiorPlotArea(QgsRenderContext &context, QgsPlotRenderContext &plotContext) const override
Returns the area of the plot which corresponds to the actual plot content (excluding all titles and o...
Definition qgsplot.cpp:760
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition qgsplot.cpp:364
An abstract class used to encapsulate the data for a plot series.
Definition qgsplot.h:204
void setName(const QString &name)
Sets the series' name.
Definition qgsplot.cpp:1319
QString name() const
Returns the series' name.
Definition qgsplot.cpp:1314
QgsAbstractPlotSeries()=default
static QgsNumericFormatRegistry * numericFormatRegistry()
Gets the registry of available numeric formats.
A numeric formatter which returns a simple text representation of a value.
Abstract base class for color ramps.
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
A line symbol type, for rendering LineString and MultiLineString geometries.
void renderPolyline(const QPolygonF &points, const QgsFeature *f, QgsRenderContext &context, int layer=-1, bool selected=false)
Renders the symbol along the line joining points, using the given render context.
Defines the four margins of a rectangle.
Definition qgsmargins.h:38
double top() const
Returns the top margin.
Definition qgsmargins.h:78
static QgsMargins fromString(const QString &string)
Returns a QgsMargins object decoded from a string, or a null QgsMargins if the string could not be in...
double right() const
Returns the right margin.
Definition qgsmargins.h:84
double bottom() const
Returns the bottom margin.
Definition qgsmargins.h:90
double left() const
Returns the left margin.
Definition qgsmargins.h:72
A marker symbol type, for rendering Point and MultiPoint geometries.
A context for numeric formats.
Abstract base class for numeric formatters, which allow for formatting a numeric value for display.
QgsLineSymbol * gridMinorSymbol()
Returns the line symbol used to render the minor lines in the axis grid.
Definition qgsplot.cpp:204
bool readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the axis' properties from an XML element.
Definition qgsplot.cpp:140
void setGridMajorSymbol(QgsLineSymbol *symbol)
Sets the symbol used to render the major lines in the axis grid.
Definition qgsplot.cpp:199
void setType(Qgis::PlotAxisType type)
Sets the axis type.
Definition qgsplot.cpp:108
void setNumericFormat(QgsNumericFormat *format)
Sets the numeric format used for the axis labels.
Definition qgsplot.cpp:169
QgsTextFormat textFormat() const
Returns the text format used for the axis labels.
Definition qgsplot.cpp:214
void setLabelSuffixPlacement(Qgis::PlotAxisSuffixPlacement placement)
Sets the placement for the axis label suffixes.
Definition qgsplot.cpp:189
Qgis::PlotAxisType type() const
Returns the axis type.
Definition qgsplot.cpp:103
QgsLineSymbol * gridMajorSymbol()
Returns the line symbol used to render the major lines in the axis grid.
Definition qgsplot.cpp:194
void setLabelSuffix(const QString &suffix)
Sets the axis label suffix.
Definition qgsplot.cpp:179
void setTextFormat(const QgsTextFormat &format)
Sets the text format used for the axis labels.
Definition qgsplot.cpp:219
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const
Writes the axis' properties into an XML element.
Definition qgsplot.cpp:113
void setGridMinorSymbol(QgsLineSymbol *symbol)
Sets the symbol used to render the minor lines in the axis grid.
Definition qgsplot.cpp:209
QgsNumericFormat * numericFormat() const
Returns the numeric format used for the axis labels.
Definition qgsplot.cpp:164
Qgis::PlotAxisSuffixPlacement labelSuffixPlacement() const
Returns the placement for the axis label suffixes.
Definition qgsplot.cpp:184
QString labelSuffix() const
Returns the axis label suffix, or an empty string if no label suffix is to be used.
Definition qgsplot.cpp:174
Encapsulates one or more plot series.
Definition qgsplot.h:300
QgsPlotData()=default
QStringList categories() const
Returns the name of the series' categories.
Definition qgsplot.cpp:1300
QgsPlotData & operator=(const QgsPlotData &other)
Definition qgsplot.cpp:1254
void clearSeries()
Clears all series from the plot data.
Definition qgsplot.cpp:1294
QList< QgsAbstractPlotSeries * > series() const
Returns the list of series forming the plot data.
Definition qgsplot.cpp:1281
void setCategories(const QStringList &categories)
Sets the name of the series' categories.
Definition qgsplot.cpp:1305
void addSeries(QgsAbstractPlotSeries *series)
Adds a series to the plot data.
Definition qgsplot.cpp:1286
static QgsFillSymbol * chartBorderSymbol()
Returns the default fill symbol to use for the chart area border.
Definition qgsplot.cpp:1190
static QgsNumericFormat * pieChartNumericFormat()
Returns the default color ramp to use for pie charts.
Definition qgsplot.cpp:1225
static QgsNumericFormat * axisLabelNumericFormat()
Returns the default numeric format to use for plot axis labels.
Definition qgsplot.cpp:1165
static QgsColorRamp * pieChartColorRamp()
Returns the default color ramp to use for pie charts.
Definition qgsplot.cpp:1220
static QgsFillSymbol * barChartFillSymbol()
Returns the default fill symbol to use for bar charts.
Definition qgsplot.cpp:1208
static QgsLineSymbol * lineChartLineSymbol()
Returns the default line symbol to use for line charts.
Definition qgsplot.cpp:1202
static QgsLineSymbol * axisGridMinorSymbol()
Returns the default line symbol to use for axis minor grid lines.
Definition qgsplot.cpp:1177
static QgsFillSymbol * pieChartFillSymbol()
Returns the default fill symbol to use for pie charts.
Definition qgsplot.cpp:1214
static QgsMarkerSymbol * lineChartMarkerSymbol()
Returns the default marker symbol to use for line charts.
Definition qgsplot.cpp:1196
static QgsFillSymbol * chartBackgroundSymbol()
Returns the default fill symbol to use for the chart area background fill.
Definition qgsplot.cpp:1184
static QgsLineSymbol * axisGridMajorSymbol()
Returns the default line symbol to use for axis major grid lines.
Definition qgsplot.cpp:1170
Contains information about the context of a plot rendering operation.
Definition qgsplot.h:184
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the plot property definitions.
Definition qgsplot.cpp:84
virtual ~QgsPlot()
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the plot's property collection, used for data defined overrides.
Definition qgsplot.h:136
virtual QString type() const
Returns the plot's type.
Definition qgsplot.h:121
QgsPropertyCollection mDataDefinedProperties
Definition qgsplot.h:169
@ YAxisMinorInterval
Minor grid line interval for Y axis.
Definition qgsplot.h:105
@ YAxisLabelInterval
Label interval for Y axis.
Definition qgsplot.h:106
@ XAxisMajorInterval
Major grid line interval for X axis.
Definition qgsplot.h:101
@ XAxisMaximum
Maximum X axis value.
Definition qgsplot.h:108
@ XAxisMinimum
Minimum X axis value.
Definition qgsplot.h:107
@ XAxisLabelInterval
Label interval for X axis.
Definition qgsplot.h:103
@ MarginBottom
Bottom margin.
Definition qgsplot.h:100
@ XAxisMinorInterval
Minor grid line interval for X axis.
Definition qgsplot.h:102
@ YAxisMajorInterval
Major grid line interval for Y axis.
Definition qgsplot.h:104
@ YAxisMaximum
Maximum Y axis value.
Definition qgsplot.h:110
@ MarginLeft
Left margin.
Definition qgsplot.h:97
@ MarginRight
Right margin.
Definition qgsplot.h:99
@ YAxisMinimum
Minimum Y axis value.
Definition qgsplot.h:109
virtual bool readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the plot's properties from an XML element.
Definition qgsplot.cpp:52
virtual bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const
Writes the plot's properties into an XML element.
Definition qgsplot.cpp:41
A scheme based color ramp consisting of a list of predefined colors.
Definition for a property.
Definition qgsproperty.h:45
@ Double
Double value (including negative values).
Definition qgsproperty.h:55
@ DoublePositive
Positive double value (including 0).
Definition qgsproperty.h:56
A container for the context for various read/write operations on objects.
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QgsExpressionContext & expressionContext()
Gets the expression context.
static std::unique_ptr< QgsSymbol > loadSymbol(const QDomElement &element, const QgsReadWriteContext &context)
Attempts to load a symbol from a DOM element.
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
Container for all settings relating to text rendering.
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle)
Draws text within a rectangle using the specified settings.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
void setData(const QList< std::pair< double, double > > &data)
Sets the series' list of XY pairs of double.
Definition qgsplot.cpp:1333
QgsAbstractPlotSeries * clone() const override
Clones the series.
Definition qgsplot.cpp:1348
QgsXyPlotSeries()=default
void append(double x, double y)
Appends a pair of X/Y double values to the series.
Definition qgsplot.cpp:1338
void clear()
Clears the series' data.
Definition qgsplot.cpp:1343
QList< std::pair< double, double > > data() const
Returns the series' list of XY pairs of double.
Definition qgsplot.cpp:1328
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:6817
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6524
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6798
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
#define DEFAULT_SCALE_METHOD
Single variable definition for use within a QgsExpressionContextScope.