QGIS API Documentation 3.99.0-Master (a8f284845db)
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 element.setAttribute( u"flipAxes"_s, mFlipAxes ? "1"_L1 : "0"_L1 );
378
379 QDomElement xAxisElement = document.createElement( u"xAxis"_s );
380 mXAxis.writeXml( xAxisElement, document, context );
381 element.appendChild( xAxisElement );
382 QDomElement yAxisElement = document.createElement( u"yAxis"_s );
383 mYAxis.writeXml( yAxisElement, document, context );
384 element.appendChild( yAxisElement );
385
386 QDomElement backgroundElement = document.createElement( u"backgroundSymbol"_s );
387 backgroundElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mChartBackgroundSymbol.get(), document, context ) );
388 element.appendChild( backgroundElement );
389 QDomElement borderElement = document.createElement( u"borderSymbol"_s );
390 borderElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mChartBorderSymbol.get(), document, context ) );
391 element.appendChild( borderElement );
392
393 return true;
394}
395
396bool Qgs2DXyPlot::readXml( const QDomElement &element, const QgsReadWriteContext &context )
397{
398 Qgs2DPlot::readXml( element, context );
399
400 mFlipAxes = element.attribute( u"flipAxes"_s ).toInt() == 1;
401
402 mMinX = element.attribute( u"minX"_s ).toDouble();
403 mMaxX = element.attribute( u"maxX"_s ).toDouble();
404 mMinY = element.attribute( u"minY"_s ).toDouble();
405 mMaxY = element.attribute( u"maxY"_s ).toDouble();
406
407 const QDomElement xAxisElement = element.firstChildElement( u"xAxis"_s );
408 mXAxis.readXml( xAxisElement, context );
409 const QDomElement yAxisElement = element.firstChildElement( u"yAxis"_s );
410 mYAxis.readXml( yAxisElement, context );
411
412 const QDomElement backgroundElement = element.firstChildElement( u"backgroundSymbol"_s ).firstChildElement( u"symbol"_s );
413 mChartBackgroundSymbol = QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( backgroundElement, context );
414 const QDomElement borderElement = element.firstChildElement( u"borderSymbol"_s ).firstChildElement( u"symbol"_s );
415 mChartBorderSymbol = QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( borderElement, context );
416
417 return true;
418}
419
420void Qgs2DXyPlot::render( QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QgsPlotData &plotData )
421{
422 QgsExpressionContextScope *plotScope = new QgsExpressionContextScope( u"plot"_s );
423 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), plotScope );
424
425 mChartBackgroundSymbol->startRender( context );
426 mChartBorderSymbol->startRender( context );
427
428 mXAxis.gridMinorSymbol()->startRender( context );
429 mYAxis.gridMinorSymbol()->startRender( context );
430 mXAxis.gridMajorSymbol()->startRender( context );
431 mYAxis.gridMajorSymbol()->startRender( context );
432
433 QgsMargins margins = mMargins;
435 double minX = mMinX;
436 double maxX = mMaxX;
437 double minY = mMinY;
438 double maxY = mMaxY;
439 double majorIntervalX = mXAxis.gridIntervalMajor();
440 double minorIntervalX = mXAxis.gridIntervalMinor();
441 double labelIntervalX = mXAxis.labelInterval();
442 double majorIntervalY = mYAxis.gridIntervalMajor();
443 double minorIntervalY = mYAxis.gridIntervalMinor();
444 double labelIntervalY = mYAxis.labelInterval();
445 applyDataDefinedProperties( context, minX, maxX, minY, maxY, majorIntervalX, minorIntervalX, labelIntervalX, majorIntervalY, minorIntervalY, labelIntervalY );
446
447 QgsPlotAxis *xAxis = nullptr;
448 QgsPlotAxis *yAxis = nullptr;
449 if ( mFlipAxes )
450 {
451 xAxis = &mYAxis;
452 yAxis = &mXAxis;
453 std::swap( minX, minY );
454 std::swap( maxX, maxY );
455 std::swap( majorIntervalX, majorIntervalY );
456 std::swap( minorIntervalX, minorIntervalY );
457 std::swap( labelIntervalX, labelIntervalY );
458 }
459 else
460 {
461 xAxis = &mXAxis;
462 yAxis = &mYAxis;
463 }
464
465 const double firstMinorXGrid = std::ceil( minX / minorIntervalX ) * minorIntervalX;
466 const double firstMajorXGrid = std::ceil( minX / majorIntervalX ) * majorIntervalX;
467 const double firstMinorYGrid = std::ceil( minY / minorIntervalY ) * minorIntervalY;
468 const double firstMajorYGrid = std::ceil( minY / majorIntervalY ) * majorIntervalY;
469 const double firstXLabel = labelIntervalX > 0 ? std::ceil( minX / labelIntervalX ) * labelIntervalX : 0;
470 const double firstYLabel = labelIntervalY > 0 ? std::ceil( minY / labelIntervalY ) * labelIntervalY : 0;
471
472 const QString xAxisSuffix = xAxis->labelSuffix();
473 const QString yAxisSuffix = yAxis->labelSuffix();
474
475 const QRectF plotArea = interiorPlotArea( context, plotContext, plotData );
476
477 const double xTolerance = minorIntervalX / 100000;
478 const double yTolerance = minorIntervalY / 100000;
479
480 QgsNumericFormatContext numericContext;
481
482 constexpr int MAX_OBJECTS = 1000;
483
484 // categories
485 const QStringList categories = plotData.categories();
486
487 // calculate text metrics
488 double maxYAxisLabelWidth = 0;
489 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis"_s, u"y"_s, true ) );
490 switch ( yAxis->type() )
491 {
493 if ( labelIntervalY > 0 )
494 {
495 int objectNumber = 0;
496 for ( double currentY = firstYLabel; ; currentY += labelIntervalY, ++objectNumber )
497 {
498 const bool hasMoreLabels = objectNumber + 1 < MAX_OBJECTS && ( currentY + labelIntervalY <= maxY && !qgsDoubleNear( currentY + labelIntervalY, maxY, yTolerance ) );
499 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis_value"_s, currentY, true ) );
500 QString text = yAxis->numericFormat()->formatDouble( currentY, numericContext );
501 switch ( yAxis->labelSuffixPlacement() )
502 {
504 break;
505
507 text += yAxisSuffix;
508 break;
509
511 if ( currentY == firstYLabel )
512 text += yAxisSuffix;
513 break;
514
516 if ( !hasMoreLabels )
517 text += yAxisSuffix;
518 break;
519
521 if ( currentY == firstYLabel || !hasMoreLabels )
522 text += yAxisSuffix;
523 break;
524 }
525
526 maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, yAxis->textFormat(), { text } ) );
527 if ( !hasMoreLabels )
528 break;
529 }
530 }
531 break;
532
534 for ( int i = 0; i < categories.size(); i++ )
535 {
536 maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, yAxis->textFormat(), { categories.at( i ) } ) );
537 if ( i + 1 >= MAX_OBJECTS )
538 break;
539 }
540 break;
541 }
542
543 const double chartAreaLeft = plotArea.left();
544 const double chartAreaRight = plotArea.right();
545 const double chartAreaTop = plotArea.top();
546 const double chartAreaBottom = plotArea.bottom();
547
548 // chart background
549 mChartBackgroundSymbol->renderPolygon( QPolygonF(
550 {
551 QPointF( chartAreaLeft, chartAreaTop ),
552 QPointF( chartAreaRight, chartAreaTop ),
553 QPointF( chartAreaRight, chartAreaBottom ),
554 QPointF( chartAreaLeft, chartAreaBottom ),
555 QPointF( chartAreaLeft, chartAreaTop )
556 } ), nullptr, nullptr, context );
557
558 const double xScale = ( chartAreaRight - chartAreaLeft ) / ( maxX - minX );
559 const double yScale = ( chartAreaBottom - chartAreaTop ) / ( maxY - minY );
560
561 // grid lines
562
563 // x
564 switch ( xAxis->type() )
565 {
567 {
568 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis"_s, u"x"_s, true ) );
569 double nextMajorXGrid = firstMajorXGrid;
570 int objectNumber = 0;
571 for ( double currentX = firstMinorXGrid; objectNumber < MAX_OBJECTS && ( currentX <= maxX && !qgsDoubleNear( currentX, maxX, xTolerance ) ); currentX += minorIntervalX, ++objectNumber )
572 {
573 bool isMinor = true;
574 if ( qgsDoubleNear( currentX, nextMajorXGrid, xTolerance ) )
575 {
576 isMinor = false;
577 nextMajorXGrid += majorIntervalX;
578 }
579
580 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis_value"_s, currentX, true ) );
581
582 QgsLineSymbol *currentGridSymbol = isMinor ? xAxis->gridMinorSymbol() : xAxis->gridMajorSymbol();
583 currentGridSymbol->renderPolyline( QPolygonF(
584 QVector<QPointF>
585 {
586 QPointF( ( currentX - minX ) * xScale + chartAreaLeft, chartAreaBottom ),
587 QPointF( ( currentX - minX ) * xScale + chartAreaLeft, chartAreaTop )
588 } ), nullptr, context );
589 }
590 break;
591 }
592
594 // No grid lines here, skipping
595 break;
596 }
597
598 // y
599 switch ( yAxis->type() )
600 {
602 {
603 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis"_s, u"y"_s, true ) );
604 double nextMajorYGrid = firstMajorYGrid;
605 int objectNumber = 0;
606 for ( double currentY = firstMinorYGrid; objectNumber < MAX_OBJECTS && ( currentY <= maxY && !qgsDoubleNear( currentY, maxY, yTolerance ) ); currentY += minorIntervalY, ++objectNumber )
607 {
608 bool isMinor = true;
609 if ( qgsDoubleNear( currentY, nextMajorYGrid, yTolerance ) )
610 {
611 isMinor = false;
612 nextMajorYGrid += majorIntervalY;
613 }
614
615 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis_value"_s, currentY, true ) );
616
617 QgsLineSymbol *currentGridSymbol = isMinor ? yAxis->gridMinorSymbol() : yAxis->gridMajorSymbol();
618 currentGridSymbol->renderPolyline( QPolygonF(
619 QVector<QPointF>
620 {
621 QPointF( chartAreaLeft, chartAreaBottom - ( currentY - minY ) * yScale ),
622 QPointF( chartAreaRight, chartAreaBottom - ( currentY - minY ) * yScale )
623 } ), nullptr, context );
624 }
625 break;
626 }
627
629 // No grid lines here, skipping
630 break;
631 }
632
633 // axis labels
634
635 // x
636 switch ( xAxis->type() )
637 {
639 {
640 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis"_s, u"x"_s, true ) );
641 if ( labelIntervalX > 0 )
642 {
643 int objectNumber = 0;
644 for ( double currentX = firstXLabel; ; currentX += labelIntervalX, ++objectNumber )
645 {
646 const bool hasMoreLabels = objectNumber + 1 < MAX_OBJECTS && ( currentX + labelIntervalX <= maxX || qgsDoubleNear( currentX + labelIntervalX, maxX, xTolerance ) );
647 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis_value"_s, currentX, true ) );
648 QString text = xAxis->numericFormat()->formatDouble( currentX, numericContext );
649 switch ( xAxis->labelSuffixPlacement() )
650 {
652 break;
653
655 text += xAxisSuffix;
656 break;
657
659 if ( objectNumber == 0 )
660 text += xAxisSuffix;
661 break;
662
664 if ( !hasMoreLabels )
665 text += xAxisSuffix;
666 break;
667
669 if ( objectNumber == 0 || !hasMoreLabels )
670 text += xAxisSuffix;
671 break;
672 }
673
674 QgsTextRenderer::drawText( QPointF( ( currentX - minX ) * xScale + chartAreaLeft, mSize.height() - context.convertToPainterUnits( margins.bottom(), Qgis::RenderUnit::Millimeters ) ),
675 0, Qgis::TextHorizontalAlignment::Center, { text }, context, xAxis->textFormat() );
676 if ( !hasMoreLabels )
677 break;
678 }
679 }
680 break;
681 }
682
684 {
685 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis"_s, u"x"_s, true ) );
686 const double categoryWidth = plotArea.width() / categories.size();
687 for ( int i = 0; i < categories.size(); i++ )
688 {
689 const double currentX = ( i * categoryWidth ) + categoryWidth / 2.0;
690 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis_value"_s, categories.at( i ), true ) );
691 QgsTextRenderer::drawText( QPointF( currentX + chartAreaLeft, mSize.height() - context.convertToPainterUnits( margins.bottom(), Qgis::RenderUnit::Millimeters ) ),
692 0, Qgis::TextHorizontalAlignment::Center, { categories.at( i ) }, context, xAxis->textFormat() );
693 if ( i + 1 >= MAX_OBJECTS )
694 break;
695 }
696 break;
697 }
698 }
699
700 // y
701 switch ( yAxis->type() )
702 {
704 {
705 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis"_s, u"y"_s, true ) );
706 if ( labelIntervalY > 0 )
707 {
708 int objectNumber = 0;
709 for ( double currentY = firstYLabel; ; currentY += labelIntervalY, ++objectNumber )
710 {
711 const bool hasMoreLabels = objectNumber + 1 < MAX_OBJECTS && ( currentY + labelIntervalY <= maxY || qgsDoubleNear( currentY + labelIntervalY, maxY, yTolerance ) );
712 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis_value"_s, currentY, true ) );
713 QString text = yAxis->numericFormat()->formatDouble( currentY, numericContext );
714 switch ( yAxis->labelSuffixPlacement() )
715 {
717 break;
718
720 text += yAxisSuffix;
721 break;
722
724 if ( objectNumber == 0 )
725 text += yAxisSuffix;
726 break;
727
729 if ( !hasMoreLabels )
730 text += yAxisSuffix;
731 break;
732
734 if ( objectNumber == 0 || !hasMoreLabels )
735 text += yAxisSuffix;
736 break;
737 }
738
739 const double height = QgsTextRenderer::textHeight( context, yAxis->textFormat(), { text } );
741 maxYAxisLabelWidth + context.convertToPainterUnits( margins.left(), Qgis::RenderUnit::Millimeters ),
742 chartAreaBottom - ( currentY - minY ) * yScale + height / 2 ),
743 0, Qgis::TextHorizontalAlignment::Right, { text }, context, yAxis->textFormat(), false );
744 if ( !hasMoreLabels )
745 break;
746 }
747 }
748 break;
749 }
750
752 {
753 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis"_s, u"y"_s, true ) );
754 const double categoryHeight = plotArea.height() / categories.size();
755 for ( int i = 0; i < categories.size(); i++ )
756 {
757 const double currentY = ( i * categoryHeight ) + categoryHeight / 2.0;
758 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis_value"_s, categories.at( i ), true ) );
759 const double height = QgsTextRenderer::textHeight( context, yAxis->textFormat(), { categories.at( i ) } );
761 maxYAxisLabelWidth + context.convertToPainterUnits( margins.left(), Qgis::RenderUnit::Millimeters ),
762 chartAreaBottom - currentY + height / 2 ),
763 0, Qgis::TextHorizontalAlignment::Right, { categories.at( i ) }, context, yAxis->textFormat(), false );
764
765 if ( i + 1 >= MAX_OBJECTS )
766 break;
767 }
768 break;
769 }
770 }
771
772 // give subclasses a chance to draw their content
773 renderContent( context, plotContext, plotArea, plotData );
774
775 // border
776 mChartBorderSymbol->renderPolygon( QPolygonF(
777 {
778 QPointF( chartAreaLeft, chartAreaTop ),
779 QPointF( chartAreaRight, chartAreaTop ),
780 QPointF( chartAreaRight, chartAreaBottom ),
781 QPointF( chartAreaLeft, chartAreaBottom ),
782 QPointF( chartAreaLeft, chartAreaTop )
783 } ), nullptr, nullptr, context );
784
785 mChartBackgroundSymbol->stopRender( context );
786 mChartBorderSymbol->stopRender( context );
787 xAxis->gridMinorSymbol()->stopRender( context );
788 yAxis->gridMinorSymbol()->stopRender( context );
789 xAxis->gridMajorSymbol()->stopRender( context );
790 yAxis->gridMajorSymbol()->stopRender( context );
791}
792
793Qgs2DXyPlot::~Qgs2DXyPlot() = default;
794
796{
797 // categories
798 const QStringList categories = plotData.categories();
799
800 double minX = mMinX;
801 double maxX = mMaxX;
802 double minY = mMinY;
803 double maxY = mMaxY;
804 double majorIntervalX = mXAxis.gridIntervalMajor();
805 double minorIntervalX = mXAxis.gridIntervalMinor();
806 double labelIntervalX = mXAxis.labelInterval();
807 double majorIntervalY = mYAxis.gridIntervalMajor();
808 double minorIntervalY = mYAxis.gridIntervalMinor();
809 double labelIntervalY = mYAxis.labelInterval();
810 applyDataDefinedProperties( context, minX, maxX, minY, maxY, majorIntervalX, minorIntervalX, labelIntervalX, majorIntervalY, minorIntervalY, labelIntervalY );
811
812 const QgsPlotAxis *xAxis = mFlipAxes ? &mYAxis : &mXAxis;
813 const QgsPlotAxis *yAxis = mFlipAxes ? &mXAxis : &mYAxis;
814 if ( mFlipAxes )
815 {
816 std::swap( minX, minY );
817 std::swap( maxX, maxY );
818 std::swap( majorIntervalX, majorIntervalY );
819 std::swap( minorIntervalX, minorIntervalY );
820 std::swap( labelIntervalX, labelIntervalY );
821 }
822
823 QgsExpressionContextScope *plotScope = new QgsExpressionContextScope( u"plot"_s );
824 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), plotScope );
825
826 const double firstMinorYGrid = std::ceil( minY / minorIntervalY ) * minorIntervalY;
827 const double firstXLabel = labelIntervalX > 0 ? std::ceil( minX / labelIntervalX ) * labelIntervalX : 0;
828
829 const QString xAxisSuffix = xAxis->labelSuffix();
830 const QString yAxisSuffix = yAxis->labelSuffix();
831 const double yAxisSuffixWidth = yAxisSuffix.isEmpty() ? 0 : QgsTextRenderer::textWidth( context, yAxis->textFormat(), { yAxisSuffix } );
832
833 QgsNumericFormatContext numericContext;
834
835 const double xTolerance = minorIntervalX / 100000;
836 const double yTolerance = minorIntervalX / 100000;
837
838 constexpr int MAX_LABELS = 1000;
839
840 // calculate text metrics
841 int labelNumber = 0;
842 double maxXAxisLabelHeight = 0;
843 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis"_s, u"x"_s, true ) );
844 switch ( xAxis->type() )
845 {
847 if ( labelIntervalX > 0 )
848 {
849 for ( double currentX = firstXLabel; ; currentX += labelIntervalX, labelNumber++ )
850 {
851 const bool hasMoreLabels = labelNumber + 1 < MAX_LABELS && ( currentX + labelIntervalX <= maxX || qgsDoubleNear( currentX + labelIntervalX, maxX, xTolerance ) );
852
853 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis_value"_s, currentX, true ) );
854 QString text = xAxis->numericFormat()->formatDouble( currentX, numericContext );
855 switch ( xAxis->labelSuffixPlacement() )
856 {
858 break;
859
861 text += xAxisSuffix;
862 break;
863
865 if ( labelNumber == 0 )
866 text += xAxisSuffix;
867 break;
868
870 if ( !hasMoreLabels )
871 text += xAxisSuffix;
872 break;
873
875 if ( labelNumber == 0 || !hasMoreLabels )
876 text += xAxisSuffix;
877 break;
878 }
879 maxXAxisLabelHeight = std::max( maxXAxisLabelHeight, QgsTextRenderer::textHeight( context, xAxis->textFormat(), { text } ) );
880 if ( !hasMoreLabels )
881 break;
882 }
883 }
884 break;
885
887 for ( int i = 0; i < categories.size(); i++ )
888 {
889 maxXAxisLabelHeight = std::max( maxXAxisLabelHeight, QgsTextRenderer::textHeight( context, xAxis->textFormat(), { categories.at( i ) } ) );
890 if ( i + 1 >= MAX_LABELS )
891 break;
892 }
893 break;
894 }
895
896 double maxYAxisLabelWidth = 0;
897 labelNumber = 0;
898 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis"_s, u"y"_s, true ) );
899 switch ( yAxis->type() )
900 {
902 for ( double currentY = firstMinorYGrid; ; currentY += minorIntervalY, labelNumber ++ )
903 {
904 const bool hasMoreLabels = labelNumber + 1 < MAX_LABELS && ( currentY + minorIntervalY <= maxY || qgsDoubleNear( currentY + minorIntervalY, maxY, yTolerance ) );
905 plotScope->addVariable( QgsExpressionContextScope::StaticVariable( u"plot_axis_value"_s, currentY, true ) );
906 const QString text = yAxis->numericFormat()->formatDouble( currentY, numericContext );
907 double thisLabelWidth = QgsTextRenderer::textWidth( context, yAxis->textFormat(), { text } );
908 if ( yAxisSuffixWidth > 0 )
909 {
910 switch ( yAxis->labelSuffixPlacement() )
911 {
913 break;
914
916 thisLabelWidth += yAxisSuffixWidth;
917 break;
918
920 if ( labelNumber == 0 )
921 thisLabelWidth += yAxisSuffixWidth;
922 break;
923
925 if ( !hasMoreLabels )
926 thisLabelWidth += yAxisSuffixWidth;
927 break;
928
930 if ( labelNumber == 0 || !hasMoreLabels )
931 thisLabelWidth += yAxisSuffixWidth;
932 break;
933 }
934 }
935 maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, thisLabelWidth );
936 if ( !hasMoreLabels )
937 break;
938 }
939 break;
940
942 for ( int i = 0; i < categories.size(); i++ )
943 {
944 maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, yAxis->textFormat(), { categories.at( i ) } ) );
945 if ( i + 1 >= MAX_LABELS )
946 break;
947 }
948 break;
949 }
950
951 const double leftTextSize = maxYAxisLabelWidth + context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
952 const double rightTextSize = 0;
953 const double bottomTextSize = maxXAxisLabelHeight + context.convertToPainterUnits( 0.5, Qgis::RenderUnit::Millimeters );
954 const double topTextSize = 0;
955
956 QgsMargins margins = mMargins;
958 const double leftMargin = context.convertToPainterUnits( margins.left(), Qgis::RenderUnit::Millimeters ) + leftTextSize;
959 const double rightMargin = context.convertToPainterUnits( margins.right(), Qgis::RenderUnit::Millimeters ) + rightTextSize;
960 const double topMargin = context.convertToPainterUnits( margins.top(), Qgis::RenderUnit::Millimeters ) + topTextSize;
961 const double bottomMargin = context.convertToPainterUnits( margins.bottom(), Qgis::RenderUnit::Millimeters ) + bottomTextSize;
962
963 return QRectF( leftMargin, topMargin, mSize.width() - rightMargin - leftMargin, mSize.height() - bottomMargin - topMargin );
964}
965
967{
968 if ( !mSize.isValid() )
969 return;
970
971 // aim for about 40% coverage of label text to available space
972 constexpr double IDEAL_WIDTH = 0.4;
973 constexpr double TOLERANCE = 0.04;
974 constexpr int MAX_LABELS = 1000;
975
976 QgsMargins margins = mMargins;
978 const double leftMargin = context.convertToPainterUnits( margins.left(), Qgis::RenderUnit::Millimeters );
979 const double rightMargin = context.convertToPainterUnits( margins.right(), Qgis::RenderUnit::Millimeters );
980 const double topMargin = context.convertToPainterUnits( margins.top(), Qgis::RenderUnit::Millimeters );
981 const double bottomMargin = context.convertToPainterUnits( margins.bottom(), Qgis::RenderUnit::Millimeters );
982
983 QgsNumericFormatContext numericContext;
984
985 auto refineIntervalForAxis = [&]( double axisMinimum, double axisMaximum,
986 const std::function< double( double ) > &sizeForLabel,
987 double availableSize, double idealSizePercent, double sizeTolerancePercent,
988 double & labelInterval, double & majorInterval, double & minorInterval )
989 {
990 auto roundBase10 = []( double value )->double
991 {
992 return std::pow( 10, std::floor( std::log10( value ) ) );
993 };
994
995 // if the current interval is good enough, don't change it!
996 double totalSize = 0;
997 int initialLabelCount = 0;
998 {
999 const double firstLabelPos = std::ceil( axisMinimum / labelInterval ) * labelInterval;
1000
1001 for ( double currentPos = firstLabelPos; initialLabelCount <= MAX_LABELS && currentPos <= axisMaximum; currentPos += labelInterval, ++initialLabelCount )
1002 {
1003 totalSize += sizeForLabel( currentPos );
1004 }
1005 }
1006
1007 // we consider the current interval as "good enough" if it results in somewhere between 20-60% label text coverage over the size
1008 if ( initialLabelCount >= MAX_LABELS || ( totalSize / availableSize < ( idealSizePercent - sizeTolerancePercent ) ) || ( totalSize / availableSize > ( idealSizePercent + sizeTolerancePercent ) ) )
1009 {
1010 // we start with trying to fit 30 labels in and then raise the interval till we're happy
1011 int numberLabelsInitial = std::floor( availableSize / 30 );
1012
1013 double labelIntervalTest = ( axisMaximum - axisMinimum ) / numberLabelsInitial;
1014 double baseValue = roundBase10( labelIntervalTest );
1015 double candidate = baseValue;
1016 int currentMultiplier = 1;
1017
1018 int numberLabels = 0;
1019 while ( true )
1020 {
1021 const double firstLabelPosition = std::ceil( axisMinimum / candidate ) * candidate;
1022 double totalSize = 0;
1023 numberLabels = 0;
1024 for ( double currentPos = firstLabelPosition; currentPos <= axisMaximum; currentPos += candidate )
1025 {
1026 totalSize += sizeForLabel( currentPos );
1027 numberLabels += 1;
1028
1029 if ( numberLabels > MAX_LABELS ) // avoid hangs if candidate size is very small
1030 break;
1031 }
1032
1033 if ( numberLabels <= MAX_LABELS && totalSize <= availableSize * idealSizePercent )
1034 break;
1035
1036 if ( currentMultiplier == 1 )
1037 currentMultiplier = 2;
1038 else if ( currentMultiplier == 2 )
1039 currentMultiplier = 5;
1040 else if ( currentMultiplier == 5 )
1041 {
1042 baseValue *= 10;
1043 currentMultiplier = 1;
1044 }
1045
1046 candidate = baseValue * currentMultiplier;
1047 }
1048 labelInterval = candidate;
1049 if ( numberLabels < 10 )
1050 {
1051 minorInterval = labelInterval / 2;
1052 majorInterval = minorInterval * 4;
1053 }
1054 else
1055 {
1056 minorInterval = labelInterval;
1057 majorInterval = minorInterval * 5;
1058 }
1059 }
1060 };
1061
1062 double minX = mMinX;
1063 double maxX = mMaxX;
1064 double minY = mMinY;
1065 double maxY = mMaxY;
1066 double majorIntervalX = mXAxis.gridIntervalMajor();
1067 double minorIntervalX = mXAxis.gridIntervalMinor();
1068 double labelIntervalX = mXAxis.labelInterval();
1069 double majorIntervalY = mYAxis.gridIntervalMajor();
1070 double minorIntervalY = mYAxis.gridIntervalMinor();
1071 double labelIntervalY = mYAxis.labelInterval();
1072 applyDataDefinedProperties( context, minX, maxX, minY, maxY, majorIntervalX, minorIntervalX, labelIntervalX, majorIntervalY, minorIntervalY, labelIntervalY );
1073
1074 {
1075 const double availableSize = mFlipAxes ? mSize.height() - topMargin - bottomMargin : mSize.width() - leftMargin - rightMargin;
1076 const QString suffixX = mXAxis.labelSuffix();
1077 const double suffixWidth = !suffixX.isEmpty() ? QgsTextRenderer::textWidth( context, mXAxis.textFormat(), { suffixX } ) : 0;
1078 refineIntervalForAxis( minX, maxX, [this, &context, suffixWidth, &numericContext]( double position ) -> double
1079 {
1080 const QString text = mXAxis.numericFormat()->formatDouble( position, numericContext );
1081 // this isn't accurate, as we're always considering the suffix to be present... but it's too tricky to actually consider
1082 // the suffix placement!
1083 return QgsTextRenderer::textWidth( context, mXAxis.textFormat(), { text } ) + suffixWidth;
1084 }, availableSize,
1085 IDEAL_WIDTH, TOLERANCE, labelIntervalX, majorIntervalX, minorIntervalX );
1086 mXAxis.setLabelInterval( labelIntervalX );
1087 mXAxis.setGridIntervalMajor( majorIntervalX );
1088 mXAxis.setGridIntervalMinor( minorIntervalX );
1089 }
1090
1091 {
1092 const double availableSize = mFlipAxes ? mSize.width() - leftMargin - rightMargin : mSize.height() - topMargin - bottomMargin;
1093 const QString suffixY = mYAxis.labelSuffix();
1094 refineIntervalForAxis( minY, maxY, [this, &context, suffixY, &numericContext]( double position ) -> double
1095 {
1096 const QString text = mYAxis.numericFormat()->formatDouble( position, numericContext );
1097 // this isn't accurate, as we're always considering the suffix to be present... but it's too tricky to actually consider
1098 // the suffix placement!
1099 return QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { text + suffixY } );
1100 }, availableSize,
1101 IDEAL_WIDTH, TOLERANCE, labelIntervalY, majorIntervalY, minorIntervalY );
1102 mYAxis.setLabelInterval( labelIntervalY );
1103 mYAxis.setGridIntervalMajor( majorIntervalY );
1104 mYAxis.setGridIntervalMinor( minorIntervalY );
1105 }
1106}
1107
1109{
1110 return mChartBackgroundSymbol.get();
1111}
1112
1114{
1115 mChartBackgroundSymbol.reset( symbol );
1116}
1117
1119{
1120 return mChartBorderSymbol.get();
1121}
1122
1124{
1125 mChartBorderSymbol.reset( symbol );
1126}
1127
1129{
1130 mFlipAxes = flipAxes;
1131}
1132
1133void 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
1134{
1135 if ( !dataDefinedProperties().hasActiveProperties() )
1136 {
1137 return;
1138 }
1139
1141 {
1142 bool ok = false;
1143 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::XAxisMinimum, context.expressionContext(), minX, &ok );
1144
1145 if ( ok )
1146 {
1147 minX = value;
1148 }
1149 }
1151 {
1152 bool ok = false;
1153 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::XAxisMaximum, context.expressionContext(), maxX, &ok );
1154
1155 if ( ok )
1156 {
1157 maxX = value;
1158 }
1159 }
1161 {
1162 bool ok = false;
1163 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::YAxisMinimum, context.expressionContext(), minY, &ok );
1164
1165 if ( ok )
1166 {
1167 minY = value;
1168 }
1169 }
1171 {
1172 bool ok = false;
1173 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::YAxisMaximum, context.expressionContext(), maxY, &ok );
1174
1175 if ( ok )
1176 {
1177 maxY = value;
1178 }
1179 }
1181 {
1182 bool ok = false;
1183 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::XAxisMajorInterval, context.expressionContext(), majorIntervalX, &ok );
1184
1185 if ( ok )
1186 {
1187 majorIntervalX = value;
1188 }
1189 }
1191 {
1192 bool ok = false;
1193 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::XAxisMinorInterval, context.expressionContext(), minorIntervalX, &ok );
1194
1195 if ( ok )
1196 {
1197 minorIntervalX = value;
1198 }
1199 }
1201 {
1202 bool ok = false;
1203 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::XAxisLabelInterval, context.expressionContext(), labelIntervalX, &ok );
1204
1205 if ( ok )
1206 {
1207 labelIntervalX = value;
1208 }
1209 }
1211 {
1212 bool ok = false;
1213 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::YAxisMajorInterval, context.expressionContext(), majorIntervalY, &ok );
1214
1215 if ( ok )
1216 {
1217 majorIntervalY = value;
1218 }
1219 }
1221 {
1222 bool ok = false;
1223 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::YAxisMinorInterval, context.expressionContext(), minorIntervalY, &ok );
1224
1225 if ( ok )
1226 {
1227 minorIntervalY = value;
1228 }
1229 }
1231 {
1232 bool ok = false;
1233 double value = mDataDefinedProperties.valueAsDouble( QgsPlot::DataDefinedProperty::YAxisLabelInterval, context.expressionContext(), labelIntervalY, &ok );
1234
1235 if ( ok )
1236 {
1237 labelIntervalY = value;
1238 }
1239 }
1240}
1241
1242//
1243// QgsPlotDefaultSettings
1244//
1245
1250
1252{
1253 auto gridMajor = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20, 150 ), 0.1 );
1254 gridMajor->setPenCapStyle( Qt::FlatCap );
1255 return new QgsLineSymbol( QgsSymbolLayerList( { gridMajor.release() } ) );
1256}
1257
1259{
1260 auto gridMinor = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20, 50 ), 0.1 );
1261 gridMinor->setPenCapStyle( Qt::FlatCap );
1262 return new QgsLineSymbol( QgsSymbolLayerList( { gridMinor.release() } ) );
1263}
1264
1266{
1267 auto chartFill = std::make_unique< QgsSimpleFillSymbolLayer >( QColor( 255, 255, 255 ) );
1268 return new QgsFillSymbol( QgsSymbolLayerList( { chartFill.release() } ) );
1269}
1270
1272{
1273 auto chartBorder = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20 ), 0.1 );
1274 return new QgsFillSymbol( QgsSymbolLayerList( { chartBorder.release() } ) );
1275}
1276
1278{
1279 auto chartMarker = std::make_unique< QgsSimpleMarkerSymbolLayer>( Qgis::MarkerShape::Circle, 1.8, 0.0, DEFAULT_SCALE_METHOD, QColor( 89, 150, 50 ) );
1280 return new QgsMarkerSymbol( QgsSymbolLayerList( { chartMarker.release() } ) );
1281}
1282
1284{
1285 auto chartLine = std::make_unique< QgsSimpleLineSymbolLayer>( QColor( 89, 150, 50, 100 ), 0.6 );
1286 return new QgsLineSymbol( QgsSymbolLayerList( { chartLine.release() } ) );
1287}
1288
1290{
1291 auto chartFill = std::make_unique< QgsSimpleFillSymbolLayer>( QColor( 89, 150, 50 ) );
1292 return new QgsFillSymbol( QgsSymbolLayerList( { chartFill.release() } ) );
1293}
1294
1296{
1297 auto chartFill = std::make_unique< QgsSimpleFillSymbolLayer>( QColor( 150, 150, 150 ) );
1298 return new QgsFillSymbol( QgsSymbolLayerList( { chartFill.release() } ) );
1299}
1300
1302{
1303 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 ) } );
1304}
1305
1310
1311//
1312// QgsPlotData
1313//
1314
1319
1321 : mCategories( other.mCategories )
1322{
1323 for ( QgsAbstractPlotSeries *series : other.mSeries )
1324 {
1325 addSeries( series->clone() );
1326 }
1327}
1328
1330 : mSeries( std::move( other.mSeries ) )
1331 , mCategories( std::move( other.mCategories ) )
1332{
1333}
1334
1336{
1337 if ( this != &other )
1338 {
1339 clearSeries();
1340
1341 mCategories = other.mCategories;
1342 for ( QgsAbstractPlotSeries *series : other.mSeries )
1343 {
1344 addSeries( series->clone() );
1345 }
1346 }
1347 return *this;
1348}
1349
1351{
1352 if ( this != &other )
1353 {
1354 clearSeries();
1355
1356 mCategories = std::move( other.mCategories );
1357 mSeries = std::move( other.mSeries );
1358 }
1359 return *this;
1360}
1361
1362QList<QgsAbstractPlotSeries *> QgsPlotData::series() const
1363{
1364 return mSeries;
1365}
1366
1368{
1369 if ( !mSeries.contains( series ) )
1370 {
1371 mSeries << series;
1372 }
1373}
1374
1376{
1377 qDeleteAll( mSeries );
1378 mSeries.clear();
1379}
1380
1381QStringList QgsPlotData::categories() const
1382{
1383 return mCategories;
1384}
1385
1386void QgsPlotData::setCategories( const QStringList &categories )
1387{
1388 mCategories = categories;
1389}
1390
1391//
1392// QgsAbstractPlotSeries
1393//
1394
1396{
1397 return mName;
1398}
1399
1401{
1402 mName = name;
1403}
1404
1405//
1406// QgsXyPlotSeries
1407//
1408
1409QList<std::pair<double, double>> QgsXyPlotSeries::data() const
1410{
1411 return mData;
1412}
1413
1414void QgsXyPlotSeries::setData( const QList<std::pair<double, double>> &data )
1415{
1416 mData = data;
1417}
1418
1419void QgsXyPlotSeries::append( double x, double y )
1420{
1421 mData << std::make_pair( x, y );
1422}
1423
1425{
1426 mData.clear();
1427}
1428
1430{
1431 QgsXyPlotSeries *series = new QgsXyPlotSeries();
1432 series->setName( name() );
1433 series->setData( mData );
1434 return series;
1435}
PlotAxisSuffixPlacement
Placement options for suffixes in the labels for axis of plots.
Definition qgis.h:3404
@ FirstAndLastLabels
Place suffix after the first and last label values only.
Definition qgis.h:3409
@ EveryLabel
Place suffix after every value label.
Definition qgis.h:3406
@ FirstLabel
Place suffix after the first label value only.
Definition qgis.h:3407
@ LastLabel
Place suffix after the last label value only.
Definition qgis.h:3408
@ NoLabels
Do not place suffixes.
Definition qgis.h:3405
@ Circle
Circle.
Definition qgis.h:3175
PlotAxisType
Plots axis types.
Definition qgis.h:3420
@ Categorical
The axis represents categories.
Definition qgis.h:3422
@ Interval
The axis represents a range of values.
Definition qgis.h:3421
@ Millimeters
Millimeters.
Definition qgis.h:5306
@ Center
Center align.
Definition qgis.h:3017
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition qgsplot.cpp:238
virtual QRectF interiorPlotArea(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QgsPlotData &plotData=QgsPlotData()) const
Returns the area of the plot which corresponds to the actual plot content (excluding all titles and o...
Definition qgsplot.cpp:283
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
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:396
void setChartBackgroundSymbol(QgsFillSymbol *symbol)
Sets the fill symbol used to render the background of the chart.
Definition qgsplot.cpp:1113
void setFlipAxes(bool flipAxes)
Sets whether the X and Y axes are flipped.
Definition qgsplot.cpp:1128
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:1133
QgsPlotAxis & yAxis()
Returns a reference to the plot's y axis.
Definition qgsplot.h:772
void setChartBorderSymbol(QgsFillSymbol *symbol)
Sets the symbol used to render the border of the chart.
Definition qgsplot.cpp:1123
bool flipAxes() const
Returns whether the X and Y axes are flipped.
Definition qgsplot.h:816
~Qgs2DXyPlot() override
void render(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QgsPlotData &plotData=QgsPlotData()) override
Renders the plot.
Definition qgsplot.cpp:420
QgsFillSymbol * chartBackgroundSymbol()
Returns the fill symbol used to render the background of the chart.
Definition qgsplot.cpp:1108
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:966
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
Definition qgsplot.h:758
Qgs2DXyPlot()
Constructor for Qgs2DXyPlot.
Definition qgsplot.cpp:360
QgsFillSymbol * chartBorderSymbol()
Returns the symbol used to render the border of the chart.
Definition qgsplot.cpp:1118
QRectF interiorPlotArea(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QgsPlotData &plotData=QgsPlotData()) const override
Returns the area of the plot which corresponds to the actual plot content (excluding all titles and o...
Definition qgsplot.cpp:795
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:1400
QString name() const
Returns the series' name.
Definition qgsplot.cpp:1395
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.
Encapsulates the properties of a plot axis.
Definition qgsplot.h:353
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:1381
QgsPlotData & operator=(const QgsPlotData &other)
Definition qgsplot.cpp:1335
void clearSeries()
Clears all series from the plot data.
Definition qgsplot.cpp:1375
QList< QgsAbstractPlotSeries * > series() const
Returns the list of series forming the plot data.
Definition qgsplot.cpp:1362
void setCategories(const QStringList &categories)
Sets the name of the series' categories.
Definition qgsplot.cpp:1386
void addSeries(QgsAbstractPlotSeries *series)
Adds a series to the plot data.
Definition qgsplot.cpp:1367
static QgsFillSymbol * chartBorderSymbol()
Returns the default fill symbol to use for the chart area border.
Definition qgsplot.cpp:1271
static QgsNumericFormat * pieChartNumericFormat()
Returns the default color ramp to use for pie charts.
Definition qgsplot.cpp:1306
static QgsNumericFormat * axisLabelNumericFormat()
Returns the default numeric format to use for plot axis labels.
Definition qgsplot.cpp:1246
static QgsColorRamp * pieChartColorRamp()
Returns the default color ramp to use for pie charts.
Definition qgsplot.cpp:1301
static QgsFillSymbol * barChartFillSymbol()
Returns the default fill symbol to use for bar charts.
Definition qgsplot.cpp:1289
static QgsLineSymbol * lineChartLineSymbol()
Returns the default line symbol to use for line charts.
Definition qgsplot.cpp:1283
static QgsLineSymbol * axisGridMinorSymbol()
Returns the default line symbol to use for axis minor grid lines.
Definition qgsplot.cpp:1258
static QgsFillSymbol * pieChartFillSymbol()
Returns the default fill symbol to use for pie charts.
Definition qgsplot.cpp:1295
static QgsMarkerSymbol * lineChartMarkerSymbol()
Returns the default marker symbol to use for line charts.
Definition qgsplot.cpp:1277
static QgsFillSymbol * chartBackgroundSymbol()
Returns the default fill symbol to use for the chart area background fill.
Definition qgsplot.cpp:1265
static QgsLineSymbol * axisGridMajorSymbol()
Returns the default line symbol to use for axis major grid lines.
Definition qgsplot.cpp:1251
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:1414
QgsAbstractPlotSeries * clone() const override
Clones the series.
Definition qgsplot.cpp:1429
QgsXyPlotSeries()=default
void append(double x, double y)
Appends a pair of X/Y double values to the series.
Definition qgsplot.cpp:1419
void clear()
Clears the series' data.
Definition qgsplot.cpp:1424
QList< std::pair< double, double > > data() const
Returns the series' list of XY pairs of double.
Definition qgsplot.cpp:1409
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:7160
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6867
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7141
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6950
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.