QGIS API Documentation 4.3.0-Master (b46232e4f9a)
Loading...
Searching...
No Matches
qgslinechartplot.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslinechartplot.cpp
3 --------------------
4 begin : June 2025
5 copyright : (C) 2025 by Mathieu
6 email : mathieu at opengis dot ch
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgslinechartplot.h"
19
21#include "qgspainting.h"
22#include "qgssymbol.h"
23#include "qgssymbollayer.h"
24#include "qgssymbollayerutils.h"
26
27#include <QString>
28
29using namespace Qt::StringLiterals;
30
36
37void QgsLineChartPlot::renderContent( QgsRenderContext &context, QgsPlotRenderContext &, const QRectF &plotArea, const QgsPlotData &plotData )
38{
39 if ( mLineSymbols.empty() && mMarkerSymbols.empty() )
40 {
41 return;
42 }
43
44 const QList<QgsAbstractPlotSeries *> seriesList = plotData.series();
45 if ( seriesList.isEmpty() )
46 {
47 return;
48 }
49
50 const QStringList categories = plotData.categories();
51 switch ( xAxis().type() )
52 {
54 if ( categories.isEmpty() )
55 {
56 return;
57 }
58 break;
59
61 break;
62 }
63
64 QgsExpressionContextScope *chartScope = new QgsExpressionContextScope( u"chart"_s );
65 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), chartScope );
66
67 QgsScopedQPainterState painterState( context.painter() );
68 context.painter()->setClipRect( plotArea );
69
70 double minX = xMinimum();
71 double maxX = xMaximum();
72 double minY = yMinimum();
73 double maxY = yMaximum();
74 double majorIntervalX = xAxis().gridIntervalMajor();
75 double minorIntervalX = xAxis().gridIntervalMinor();
76 double labelIntervalX = xAxis().labelInterval();
77 double majorIntervalY = yAxis().gridIntervalMajor();
78 double minorIntervalY = yAxis().gridIntervalMinor();
79 double labelIntervalY = yAxis().labelInterval();
80 Qgs2DXyPlot::applyDataDefinedProperties( context, minX, maxX, minY, maxY, majorIntervalX, minorIntervalX, labelIntervalX, majorIntervalY, minorIntervalY, labelIntervalY );
81
82 double xScale = 0.0;
83 double yScale = 0.0;
84 double categoriesWidth = 0.0;
85 if ( flipAxes() )
86 {
87 xScale = plotArea.height() / ( maxX - minX );
88 yScale = plotArea.width() / ( maxY - minY );
89 categoriesWidth = plotArea.height() / static_cast<double>( categories.size() );
90 }
91 else
92 {
93 xScale = plotArea.width() / ( maxX - minX );
94 yScale = plotArea.height() / ( maxY - minY );
95 categoriesWidth = plotArea.width() / static_cast<double>( categories.size() );
96 }
97
98 int seriesIndex = 0;
99 for ( const QgsAbstractPlotSeries *series : seriesList )
100 {
101 QgsLineSymbol *lSymbol = !mLineSymbols.empty() ? lineSymbolAt( seriesIndex % mLineSymbols.size() ) : nullptr;
102 QgsMarkerSymbol *mSymbol = !mMarkerSymbols.empty() ? markerSymbolAt( seriesIndex % mMarkerSymbols.size() ) : nullptr;
103 if ( !lSymbol && !mSymbol )
104 {
105 continue;
106 }
107
108 if ( lSymbol )
109 {
110 lSymbol->startRender( context );
111 }
112
113 if ( mSymbol )
114 {
115 mSymbol->startRender( context );
116 }
117
118 chartScope->addVariable( QgsExpressionContextScope::StaticVariable( u"chart_series_name"_s, series->name(), true ) );
119
120 if ( const QgsXyPlotSeries *xySeries = dynamic_cast<const QgsXyPlotSeries *>( series ) )
121 {
122 const QList<std::pair<double, double>> data = xySeries->data();
123 QVector<QPointF> points;
124 points.fill( QPointF(), xAxis().type() == Qgis::PlotAxisType::Interval ? data.size() : categories.size() );
125 int dataIndex = 0;
126 for ( const std::pair<double, double> &pair : data )
127 {
128 if ( !std::isnan( pair.second ) )
129 {
130 double x = 0;
131 switch ( xAxis().type() )
132 {
134 if ( pair.first < 0 || pair.first >= categories.size() )
135 {
136 continue;
137 }
138 x = ( categoriesWidth * pair.first ) + ( categoriesWidth / 2 );
139 break;
141 x = ( pair.first - minX ) * xScale;
142 break;
143 }
144 double y = ( pair.second - minY ) * yScale;
145
146 QPointF point;
147 if ( flipAxes() )
148 {
149 point = QPointF( plotArea.x() + y, plotArea.bottom() - x );
150 }
151 else
152 {
153 point = QPointF( plotArea.x() + x, plotArea.y() + plotArea.height() - y );
154 }
155 points.replace( xAxis().type() == Qgis::PlotAxisType::Interval ? dataIndex : pair.first, point );
156 }
157 dataIndex++;
158 }
159
160 if ( lSymbol )
161 {
162 chartScope->removeVariable( u"chart_value"_s );
163 QVector<QPointF> line;
164 for ( const QPointF &point : points )
165 {
166 if ( !point.isNull() )
167 {
168 line << point;
169 }
170 else
171 {
172 if ( !line.isEmpty() )
173 {
174 lSymbol->renderPolyline( QPolygonF( line ), nullptr, context );
175 line.clear();
176 }
177 }
178 }
179 if ( !line.isEmpty() )
180 {
181 lSymbol->renderPolyline( QPolygonF( line ), nullptr, context );
182 }
183 }
184 if ( mSymbol )
185 {
186 int pointIndex = 0;
187 for ( const QPointF &point : points )
188 {
189 if ( !point.isNull() )
190 {
191 double value = 0;
192 switch ( xAxis().type() )
193 {
195 value = data.at( pointIndex ).second;
196 break;
197
199 bool found = false;
200 for ( const std::pair<double, double> &pair : data )
201 {
202 if ( pair.first == pointIndex )
203 {
204 found = true;
205 value = pair.second;
206 chartScope->addVariable( QgsExpressionContextScope::StaticVariable( u"chart_category"_s, categories[pair.first], true ) );
207 break;
208 }
209 }
210 if ( !found )
211 {
212 continue;
213 }
214 break;
215 }
216
217 chartScope->addVariable( QgsExpressionContextScope::StaticVariable( u"chart_value"_s, value, true ) );
218 mSymbol->renderPoint( point, nullptr, context );
219 }
220 pointIndex++;
221 }
222 }
223 }
224
225 if ( lSymbol )
226 {
227 lSymbol->stopRender( context );
228 }
229 if ( mSymbol )
230 {
231 mSymbol->stopRender( context );
232 }
233
234 seriesIndex++;
235 }
236}
237
239{
240 if ( index < 0 || index >= static_cast<int>( mMarkerSymbols.size() ) )
241 {
242 return nullptr;
243 }
244
245 return mMarkerSymbols[index].get();
246}
247
249{
250 if ( index < 0 )
251 {
252 return;
253 }
254
255 if ( index + 1 >= static_cast<int>( mMarkerSymbols.size() ) )
256 {
257 mMarkerSymbols.resize( index + 1 );
258 }
259
260 mMarkerSymbols[index].reset( symbol );
261}
262
264{
265 if ( index < 0 || index >= static_cast<int>( mLineSymbols.size() ) )
266 {
267 return nullptr;
268 }
269
270 return mLineSymbols[index].get();
271}
272
274{
275 if ( index < 0 )
276 {
277 return;
278 }
279
280 if ( index + 1 >= static_cast<int>( mLineSymbols.size() ) )
281 {
282 mLineSymbols.resize( index + 1 );
283 }
284
285 mLineSymbols[index].reset( symbol );
286}
287
288bool QgsLineChartPlot::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
289{
290 Qgs2DXyPlot::writeXml( element, document, context );
291
292 QDomElement markerSymbolsElement = document.createElement( u"markerSymbols"_s );
293 for ( int i = 0; i < static_cast<int>( mMarkerSymbols.size() ); i++ )
294 {
295 QDomElement markerSymbolElement = document.createElement( u"markerSymbol"_s );
296 markerSymbolElement.setAttribute( u"index"_s, QString::number( i ) );
297 if ( mMarkerSymbols[i] )
298 {
299 markerSymbolElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mMarkerSymbols[i].get(), document, context ) );
300 }
301 markerSymbolsElement.appendChild( markerSymbolElement );
302 }
303 element.appendChild( markerSymbolsElement );
304
305 QDomElement lineSymbolsElement = document.createElement( u"lineSymbols"_s );
306 for ( int i = 0; i < static_cast<int>( mLineSymbols.size() ); i++ )
307 {
308 QDomElement lineSymbolElement = document.createElement( u"lineSymbol"_s );
309 lineSymbolElement.setAttribute( u"index"_s, QString::number( i ) );
310 if ( mLineSymbols[i] )
311 {
312 lineSymbolElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mLineSymbols[i].get(), document, context ) );
313 }
314 lineSymbolsElement.appendChild( lineSymbolElement );
315 }
316 element.appendChild( lineSymbolsElement );
317
318 return true;
319}
320
321bool QgsLineChartPlot::readXml( const QDomElement &element, const QgsReadWriteContext &context )
322{
323 Qgs2DXyPlot::readXml( element, context );
324
325 const QDomNodeList markerSymbolsList = element.firstChildElement( u"markerSymbols"_s ).childNodes();
326 for ( int i = 0; i < markerSymbolsList.count(); i++ )
327 {
328 const QDomElement markerSymbolElement = markerSymbolsList.at( i ).toElement();
329 const int index = markerSymbolElement.attribute( u"index"_s, u"-1"_s ).toInt();
330 if ( index >= 0 )
331 {
332 if ( markerSymbolElement.hasChildNodes() )
333 {
334 const QDomElement symbolElement = markerSymbolElement.firstChildElement( u"symbol"_s );
335 setMarkerSymbolAt( index, QgsSymbolLayerUtils::loadSymbol< QgsMarkerSymbol >( symbolElement, context ).release() );
336 }
337 else
338 {
339 setMarkerSymbolAt( index, nullptr );
340 }
341 }
342 }
343
344 const QDomNodeList lineSymbolsList = element.firstChildElement( u"lineSymbols"_s ).childNodes();
345 for ( int i = 0; i < lineSymbolsList.count(); i++ )
346 {
347 const QDomElement lineSymbolElement = lineSymbolsList.at( i ).toElement();
348 const int index = lineSymbolElement.attribute( u"index"_s, u"-1"_s ).toInt();
349 if ( index >= 0 )
350 {
351 if ( lineSymbolElement.hasChildNodes() )
352 {
353 const QDomElement symbolElement = lineSymbolElement.firstChildElement( u"symbol"_s );
354 setLineSymbolAt( index, QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( symbolElement, context ).release() );
355 }
356 else
357 {
358 setLineSymbolAt( index, nullptr );
359 }
360 }
361 }
362
363 return true;
364}
365
370
372{
373 QgsLineChartPlot *chart = dynamic_cast<QgsLineChartPlot *>( plot );
374 if ( !chart )
375 {
376 return nullptr;
377 }
378
379 return new QgsVectorLayerXyPlotDataGatherer( chart->xAxis().type() );
380}
381
383{
384 if ( !plot )
385 {
386 return;
387 }
388
389 if ( const Qgs2DXyPlot *plotXy = dynamic_cast<const Qgs2DXyPlot *>( plot ) )
390 {
392 }
393 else if ( const Qgs2DPlot *plotPie = dynamic_cast<const Qgs2DPlot *>( plot ) )
394 {
396 }
397
398 if ( const QgsLineChartPlot *lineChartPlot = dynamic_cast<const QgsLineChartPlot *>( plot ) )
399 {
400 for ( int idx = 0; idx < lineChartPlot->lineSymbolCount(); idx++ )
401 {
402 setLineSymbolAt( idx, lineChartPlot->lineSymbolAt( idx )->clone() );
403 }
404 for ( int idx = 0; idx < lineChartPlot->markerSymbolCount(); idx++ )
405 {
406 setMarkerSymbolAt( idx, lineChartPlot->markerSymbolAt( idx )->clone() );
407 }
408 }
409}
@ Categorical
The axis represents categories.
Definition qgis.h:3574
@ Interval
The axis represents a range of values.
Definition qgis.h:3573
void copyCommonProperties(const Qgs2DPlot *other)
Copies all Qgs2DPlot-level properties (size, margins, data-defined properties) from other to this plo...
Definition qgsplot.cpp:397
Qgs2DPlot()
Constructor for Qgs2DPlot.
Definition qgsplot.cpp:277
void copyCommonProperties(const Qgs2DXyPlot *other)
Copies all Qgs2DXyPlot-level properties (axis ranges, axes, flip state, background and border symbols...
Definition qgsplot.cpp:1350
double yMaximum() const
Returns the maximum value of the y axis.
Definition qgsplot.h:769
double xMinimum() const
Returns the minimum value of the x axis.
Definition qgsplot.h:727
bool readXml(const QDomElement &element, const QgsReadWriteContext &context) override
Reads the plot's properties from an XML element.
Definition qgsplot.cpp:450
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:1229
QgsPlotAxis & yAxis()
Returns a reference to the plot's y axis.
Definition qgsplot.h:797
bool flipAxes() const
Returns whether the X and Y axes are flipped.
Definition qgsplot.h:856
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
Definition qgsplot.h:783
Qgs2DXyPlot()
Constructor for Qgs2DXyPlot.
Definition qgsplot.cpp:414
double yMinimum() const
Returns the minimum value of the y axis.
Definition qgsplot.h:741
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition qgsplot.cpp:422
double xMaximum() const
Returns the maximum value of the x axis.
Definition qgsplot.h:755
An abstract class used to encapsulate the data for a plot series.
Definition qgsplot.h:210
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
bool removeVariable(const QString &name)
Removes a variable from the context scope, if found.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
QgsMarkerSymbol * markerSymbolAt(int index) const
Returns the marker symbol for the series with matching index.
QgsLineSymbol * lineSymbolAt(int index) const
Returns the line symbol for the series with matching index.
static QgsVectorLayerAbstractPlotDataGatherer * createDataGatherer(QgsPlot *plot)
Returns a new data gatherer for a given line chart plot.
void setLineSymbolAt(int index, QgsLineSymbol *symbol)
Sets the line symbol to use for the series with matching index.
void initFromPlot(const QgsPlot *plot) override
Initializes properties of this plot from an existing plot, transferring all applicable settings.
void setMarkerSymbolAt(int index, QgsMarkerSymbol *symbol)
Sets the fill symbol to use for the series with matching index.
QString type() const override
Returns the plot's type.
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
void renderContent(QgsRenderContext &context, QgsPlotRenderContext &plotContext, const QRectF &plotArea, const QgsPlotData &plotData=QgsPlotData()) override
Renders the plot content.
bool readXml(const QDomElement &element, const QgsReadWriteContext &context) override
Reads the plot's properties from an XML element.
static QgsLineChartPlot * create()
Returns a new line chart.
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.
A marker symbol type, for rendering Point and MultiPoint geometries.
void renderPoint(QPointF point, const QgsFeature *f, QgsRenderContext &context, int layer=-1, bool selected=false)
Renders the symbol at the specified point, using the given render context.
double gridIntervalMinor() const
Returns the interval of minor grid lines for the axis.
Definition qgsplot.h:389
double gridIntervalMajor() const
Returns the interval of major grid lines for the axis.
Definition qgsplot.h:403
Qgis::PlotAxisType type() const
Returns the axis type.
Definition qgsplot.cpp:124
double labelInterval() const
Returns the interval of labels for the axis.
Definition qgsplot.h:417
Encapsulates one or more plot series.
Definition qgsplot.h:303
QStringList categories() const
Returns the name of the series' categories.
Definition qgsplot.cpp:1518
QList< QgsAbstractPlotSeries * > series() const
Returns the list of series forming the plot data.
Definition qgsplot.cpp:1499
static QgsLineSymbol * lineChartLineSymbol()
Returns the default line symbol to use for line charts.
Definition qgsplot.cpp:1419
static QgsMarkerSymbol * lineChartMarkerSymbol()
Returns the default marker symbol to use for line charts.
Definition qgsplot.cpp:1413
Contains information about the context of a plot rendering operation.
Definition qgsplot.h:191
QgsPlot()=default
A container for the context for various read/write operations on objects.
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
Scoped object for saving and restoring a QPainter object's state.
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.
void stopRender(QgsRenderContext &context)
Ends the rendering process.
void startRender(QgsRenderContext &context, const QgsFields &fields=QgsFields())
Begins the rendering process for the symbol.
An abstract vector layer plot data gatherer base class.
An vector layer plot data gatherer class for XY series.
Encapsulates the data for an XY plot series.
Definition qgsplot.h:263
Single variable definition for use within a QgsExpressionContextScope.