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