QGIS API Documentation 3.99.0-Master (c22de0620c0)
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 "qgssymbol.h"
22#include "qgssymbollayer.h"
23#include "qgssymbollayerutils.h"
25
26#include <QString>
27
28using namespace Qt::StringLiterals;
29
35
36void QgsLineChartPlot::renderContent( QgsRenderContext &context, QgsPlotRenderContext &, const QRectF &plotArea, const QgsPlotData &plotData )
37{
38 if ( mLineSymbols.empty() && mMarkerSymbols.empty() )
39 {
40 return;
41 }
42
43 const QList<QgsAbstractPlotSeries *> seriesList = plotData.series();
44 if ( seriesList.isEmpty() )
45 {
46 return;
47 }
48
49 const QStringList categories = plotData.categories();
50 switch ( xAxis().type() )
51 {
53 if ( categories.isEmpty() )
54 {
55 return;
56 }
57 break;
58
60 break;
61 }
62
63 QgsExpressionContextScope *chartScope = new QgsExpressionContextScope( u"chart"_s );
64 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), chartScope );
65
66 context.painter()->save();
67 context.painter()->setClipRect( plotArea );
68
69 double minX = xMinimum();
70 double maxX = xMaximum();
71 double minY = yMinimum();
72 double maxY = yMaximum();
73 double majorIntervalX = xAxis().gridIntervalMajor();
74 double minorIntervalX = xAxis().gridIntervalMinor();
75 double labelIntervalX = xAxis().labelInterval();
76 double majorIntervalY = yAxis().gridIntervalMajor();
77 double minorIntervalY = yAxis().gridIntervalMinor();
78 double labelIntervalY = yAxis().labelInterval();
79 Qgs2DXyPlot::applyDataDefinedProperties( context, minX, maxX, minY, maxY, majorIntervalX, minorIntervalX, labelIntervalX, majorIntervalY, minorIntervalY, labelIntervalY );
80
81 double xScale = 0.0;
82 double yScale = 0.0;
83 double categoriesWidth = 0.0;
84 if ( flipAxes() )
85 {
86 xScale = plotArea.height() / ( maxX - minX );
87 yScale = plotArea.width() / ( maxY - minY );
88 categoriesWidth = plotArea.height() / static_cast<double>( categories.size() );
89 }
90 else
91 {
92 xScale = plotArea.width() / ( maxX - minX );
93 yScale = plotArea.height() / ( maxY - minY );
94 categoriesWidth = plotArea.width() / static_cast<double>( categories.size() );
95 }
96
97 int seriesIndex = 0;
98 for ( const QgsAbstractPlotSeries *series : seriesList )
99 {
100 QgsLineSymbol *lSymbol = !mLineSymbols.empty() ? lineSymbolAt( seriesIndex % mLineSymbols.size() ) : nullptr;
101 QgsMarkerSymbol *mSymbol = !mMarkerSymbols.empty() ? markerSymbolAt( seriesIndex % mMarkerSymbols.size() ) : nullptr;
102 if ( !lSymbol && !mSymbol )
103 {
104 continue;
105 }
106
107 if ( lSymbol )
108 {
109 lSymbol->startRender( context );
110 }
111
112 if ( mSymbol )
113 {
114 mSymbol->startRender( context );
115 }
116
117 chartScope->addVariable( QgsExpressionContextScope::StaticVariable( u"chart_series_name"_s, series->name(), true ) );
118
119 if ( const QgsXyPlotSeries *xySeries = dynamic_cast<const QgsXyPlotSeries *>( series ) )
120 {
121 const QList<std::pair<double, double>> data = xySeries->data();
122 QVector<QPointF> points;
123 points.fill( QPointF(), xAxis().type() == Qgis::PlotAxisType::Interval ? data.size() : categories.size() );
124 int dataIndex = 0;
125 for ( const std::pair<double, double> &pair : data )
126 {
127 if ( !std::isnan( pair.second ) )
128 {
129 double x = 0;
130 switch ( xAxis().type() )
131 {
133 if ( pair.first < 0 || pair.first >= categories.size() )
134 {
135 continue;
136 }
137 x = ( categoriesWidth * pair.first ) + ( categoriesWidth / 2 );
138 break;
140 x = ( pair.first - minX ) * xScale;
141 break;
142 }
143 double y = ( pair.second - minY ) * yScale;
144
145 QPointF point;
146 if ( flipAxes() )
147 {
148 point = QPointF( plotArea.x() + y, plotArea.bottom() - x );
149 }
150 else
151 {
152 point = QPointF( plotArea.x() + x, plotArea.y() + plotArea.height() - y );
153 }
154 points.replace( xAxis().type() == Qgis::PlotAxisType::Interval ? dataIndex : pair.first, point );
155 }
156 dataIndex++;
157 }
158
159 if ( lSymbol )
160 {
161 chartScope->removeVariable( u"chart_value"_s );
162 QVector<QPointF> line;
163 for ( const QPointF &point : points )
164 {
165 if ( !point.isNull() )
166 {
167 line << point;
168 }
169 else
170 {
171 if ( !line.isEmpty() )
172 {
173 lSymbol->renderPolyline( QPolygonF( line ), nullptr, context );
174 line.clear();
175 }
176 }
177 }
178 if ( !line.isEmpty() )
179 {
180 lSymbol->renderPolyline( QPolygonF( line ), nullptr, context );
181 }
182 }
183 if ( mSymbol )
184 {
185 int pointIndex = 0;
186 for ( const QPointF &point : points )
187 {
188 if ( !point.isNull() )
189 {
190 double value = 0;
191 switch ( xAxis().type() )
192 {
194 value = data.at( pointIndex ).second;
195 break;
196
198 bool found = false;
199 for ( const std::pair<double, double> &pair : data )
200 {
201 if ( pair.first == pointIndex )
202 {
203 found = true;
204 value = pair.second;
205 chartScope->addVariable( QgsExpressionContextScope::StaticVariable( u"chart_category"_s, categories[pair.first], true ) );
206 break;
207 }
208 }
209 if ( !found )
210 {
211 continue;
212 }
213 break;
214 }
215
216 chartScope->addVariable( QgsExpressionContextScope::StaticVariable( u"chart_value"_s, value, true ) );
217 mSymbol->renderPoint( point, nullptr, context );
218 }
219 pointIndex++;
220 }
221 }
222 }
223
224 if ( lSymbol )
225 {
226 lSymbol->stopRender( context );
227 }
228 if ( mSymbol )
229 {
230 mSymbol->stopRender( context );
231 }
232
233 seriesIndex++;
234 }
235
236 context.painter()->restore();
237}
238
240{
241 if ( index < 0 || index >= static_cast<int>( mMarkerSymbols.size() ) )
242 {
243 return nullptr;
244 }
245
246 return mMarkerSymbols[index].get();
247}
248
250{
251 if ( index < 0 )
252 {
253 return;
254 }
255
256 if ( index + 1 >= static_cast<int>( mMarkerSymbols.size() ) )
257 {
258 mMarkerSymbols.resize( index + 1 );
259 }
260
261 mMarkerSymbols[index].reset( symbol );
262}
263
265{
266 if ( index < 0 || index >= static_cast<int>( mLineSymbols.size() ) )
267 {
268 return nullptr;
269 }
270
271 return mLineSymbols[index].get();
272}
273
275{
276 if ( index < 0 )
277 {
278 return;
279 }
280
281 if ( index + 1 >= static_cast<int>( mLineSymbols.size() ) )
282 {
283 mLineSymbols.resize( index + 1 );
284 }
285
286 mLineSymbols[index].reset( symbol );
287}
288
289bool QgsLineChartPlot::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
290{
291 Qgs2DXyPlot::writeXml( element, document, context );
292
293 QDomElement markerSymbolsElement = document.createElement( u"markerSymbols"_s );
294 for ( int i = 0; i < static_cast<int>( mMarkerSymbols.size() ); i++ )
295 {
296 QDomElement markerSymbolElement = document.createElement( u"markerSymbol"_s );
297 markerSymbolElement.setAttribute( u"index"_s, QString::number( i ) );
298 if ( mMarkerSymbols[i] )
299 {
300 markerSymbolElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mMarkerSymbols[i].get(), document, context ) );
301 }
302 markerSymbolsElement.appendChild( markerSymbolElement );
303 }
304 element.appendChild( markerSymbolsElement );
305
306 QDomElement lineSymbolsElement = document.createElement( u"lineSymbols"_s );
307 for ( int i = 0; i < static_cast<int>( mLineSymbols.size() ); i++ )
308 {
309 QDomElement lineSymbolElement = document.createElement( u"lineSymbol"_s );
310 lineSymbolElement.setAttribute( u"index"_s, QString::number( i ) );
311 if ( mLineSymbols[i] )
312 {
313 lineSymbolElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mLineSymbols[i].get(), document, context ) );
314 }
315 lineSymbolsElement.appendChild( lineSymbolElement );
316 }
317 element.appendChild( lineSymbolsElement );
318
319 return true;
320}
321
322bool QgsLineChartPlot::readXml( const QDomElement &element, const QgsReadWriteContext &context )
323{
324 Qgs2DXyPlot::readXml( element, context );
325
326 const QDomNodeList markerSymbolsList = element.firstChildElement( u"markerSymbols"_s ).childNodes();
327 for ( int i = 0; i < markerSymbolsList.count(); i++ )
328 {
329 const QDomElement markerSymbolElement = markerSymbolsList.at( i ).toElement();
330 const int index = markerSymbolElement.attribute( u"index"_s, u"-1"_s ).toInt();
331 if ( index >= 0 )
332 {
333 if ( markerSymbolElement.hasChildNodes() )
334 {
335 const QDomElement symbolElement = markerSymbolElement.firstChildElement( u"symbol"_s );
336 setMarkerSymbolAt( index, QgsSymbolLayerUtils::loadSymbol< QgsMarkerSymbol >( symbolElement, context ).release() );
337 }
338 else
339 {
340 setMarkerSymbolAt( index, nullptr );
341 }
342 }
343 }
344
345 const QDomNodeList lineSymbolsList = element.firstChildElement( u"lineSymbols"_s ).childNodes();
346 for ( int i = 0; i < lineSymbolsList.count(); i++ )
347 {
348 const QDomElement lineSymbolElement = lineSymbolsList.at( i ).toElement();
349 const int index = lineSymbolElement.attribute( u"index"_s, u"-1"_s ).toInt();
350 if ( index >= 0 )
351 {
352 if ( lineSymbolElement.hasChildNodes() )
353 {
354 const QDomElement symbolElement = lineSymbolElement.firstChildElement( u"symbol"_s );
355 setLineSymbolAt( index, QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( symbolElement, context ).release() );
356 }
357 else
358 {
359 setLineSymbolAt( index, nullptr );
360 }
361 }
362 }
363
364 return true;
365}
366
371
373{
374 QgsLineChartPlot *chart = dynamic_cast<QgsLineChartPlot *>( plot );
375 if ( !chart )
376 {
377 return nullptr;
378 }
379
380 return new QgsVectorLayerXyPlotDataGatherer( chart->xAxis().type() );
381}
@ Categorical
The axis represents categories.
Definition qgis.h:3422
@ Interval
The axis represents a range of values.
Definition qgis.h:3421
double yMaximum() const
Returns the maximum value of the y axis.
Definition qgsplot.h:744
double xMinimum() const
Returns the minimum value of the x axis.
Definition qgsplot.h:702
bool readXml(const QDomElement &element, const QgsReadWriteContext &context) override
Reads the plot's properties from an XML element.
Definition qgsplot.cpp:396
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
bool flipAxes() const
Returns whether the X and Y axes are flipped.
Definition qgsplot.h:816
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
Definition qgsplot.h:758
double yMinimum() const
Returns the minimum value of the y axis.
Definition qgsplot.h:716
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition qgsplot.cpp:368
double xMaximum() const
Returns the maximum value of the x axis.
Definition qgsplot.h:730
An abstract class used to encapsulate the data for a plot series.
Definition qgsplot.h:204
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 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:107
double labelInterval() const
Returns the interval of labels for the axis.
Definition qgsplot.h:417
Encapsulates one or more plot series.
Definition qgsplot.h:300
QStringList categories() const
Returns the name of the series' categories.
Definition qgsplot.cpp:1381
QList< QgsAbstractPlotSeries * > series() const
Returns the list of series forming the plot data.
Definition qgsplot.cpp:1362
static QgsLineSymbol * lineChartLineSymbol()
Returns the default line symbol to use for line charts.
Definition qgsplot.cpp:1283
static QgsMarkerSymbol * lineChartMarkerSymbol()
Returns the default marker symbol to use for line charts.
Definition qgsplot.cpp:1277
Contains information about the context of a plot rendering operation.
Definition qgsplot.h:184
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.
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:258
Single variable definition for use within a QgsExpressionContextScope.