QGIS API Documentation 3.99.0-Master (21b3aa880ba)
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
31
32void QgsLineChartPlot::renderContent( QgsRenderContext &context, QgsPlotRenderContext &, const QRectF &plotArea, const QgsPlotData &plotData )
33{
34 if ( mLineSymbols.empty() && mMarkerSymbols.empty() )
35 {
36 return;
37 }
38
39 const QList<QgsAbstractPlotSeries *> seriesList = plotData.series();
40 if ( seriesList.isEmpty() )
41 {
42 return;
43 }
44
45 const QStringList categories = plotData.categories();
46 switch ( xAxis().type() )
47 {
49 if ( categories.isEmpty() )
50 {
51 return;
52 }
53 break;
54
56 break;
57 }
58
59 QgsExpressionContextScope *chartScope = new QgsExpressionContextScope( QStringLiteral( "chart" ) );
60 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), chartScope );
61
62 context.painter()->save();
63 context.painter()->setClipRect( plotArea );
64
65 double minX = xMinimum();
66 double maxX = xMaximum();
67 double minY = yMinimum();
68 double maxY = yMaximum();
69 double majorIntervalX = xAxis().gridIntervalMajor();
70 double minorIntervalX = xAxis().gridIntervalMinor();
71 double labelIntervalX = xAxis().labelInterval();
72 double majorIntervalY = yAxis().gridIntervalMajor();
73 double minorIntervalY = yAxis().gridIntervalMinor();
74 double labelIntervalY = yAxis().labelInterval();
75 Qgs2DXyPlot::applyDataDefinedProperties( context, minX, maxX, minY, maxY, majorIntervalX, minorIntervalX, labelIntervalX, majorIntervalY, minorIntervalY, labelIntervalY );
76
77 const double xScale = plotArea.width() / ( maxX - minX );
78 const double yScale = plotArea.height() / ( maxY - minY );
79 const double categoriesWidth = plotArea.width() / categories.size();
80 int seriesIndex = 0;
81 for ( const QgsAbstractPlotSeries *series : seriesList )
82 {
83 QgsLineSymbol *lSymbol = !mLineSymbols.empty() ? lineSymbolAt( seriesIndex % mLineSymbols.size() ) : nullptr;
84 QgsMarkerSymbol *mSymbol = !mMarkerSymbols.empty() ? markerSymbolAt( seriesIndex % mMarkerSymbols.size() ) : nullptr;
85 if ( !lSymbol && !mSymbol )
86 {
87 continue;
88 }
89
90 if ( lSymbol )
91 {
92 lSymbol->startRender( context );
93 }
94
95 if ( mSymbol )
96 {
97 mSymbol->startRender( context );
98 }
99
100 if ( const QgsXyPlotSeries *xySeries = dynamic_cast<const QgsXyPlotSeries *>( series ) )
101 {
102 const QList<std::pair<double, double>> data = xySeries->data();
103 QVector<QPointF> points;
104 points.fill( QPointF(), xAxis().type() == Qgis::PlotAxisType::Interval ? data.size() : categories.size() );
105 int dataIndex = 0;
106 for ( const std::pair<double, double> &pair : data )
107 {
108 if ( !std::isnan( pair.second ) )
109 {
110 double x = 0;
111 switch ( xAxis().type() )
112 {
114 if ( pair.first < 0 || pair.first >= categories.size() )
115 {
116 continue;
117 }
118 x = ( categoriesWidth * pair.first ) + ( categoriesWidth / 2 );
119 break;
121 x = ( pair.first - minX ) * xScale;
122 break;
123 }
124 double y = ( pair.second - minY ) * yScale;
125
126 const QPointF point( plotArea.x() + x, plotArea.y() + plotArea.height() - y );
127 points.replace( xAxis().type() == Qgis::PlotAxisType::Interval ? dataIndex : pair.first, point );
128 }
129 dataIndex++;
130 }
131
132 if ( lSymbol )
133 {
134 chartScope->removeVariable( QStringLiteral( "chart_value" ) );
135 QVector<QPointF> line;
136 for ( const QPointF &point : points )
137 {
138 if ( !point.isNull() )
139 {
140 line << point;
141 }
142 else
143 {
144 if ( !line.isEmpty() )
145 {
146 lSymbol->renderPolyline( QPolygonF( line ), nullptr, context );
147 line.clear();
148 }
149 }
150 }
151 if ( !line.isEmpty() )
152 {
153 lSymbol->renderPolyline( QPolygonF( line ), nullptr, context );
154 }
155 }
156 if ( mSymbol )
157 {
158 int pointIndex = 0;
159 for ( const QPointF &point : points )
160 {
161 if ( !point.isNull() )
162 {
163 double value = 0;
164 switch ( xAxis().type() )
165 {
167 value = data.at( pointIndex ).second;
168 break;
169
171 bool found = false;
172 for ( const std::pair<double, double> &pair : data )
173 {
174 if ( pair.first == pointIndex )
175 {
176 found = true;
177 value = pair.second;
178 chartScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "chart_category" ), categories[pair.first], true ) );
179 break;
180 }
181 }
182 if ( !found )
183 {
184 continue;
185 }
186 break;
187 }
188
189 chartScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "chart_value" ), value, true ) );
190 mSymbol->renderPoint( point, nullptr, context );
191 }
192 pointIndex++;
193 }
194 }
195 }
196
197 if ( lSymbol )
198 {
199 lSymbol->stopRender( context );
200 }
201 if ( mSymbol )
202 {
203 mSymbol->stopRender( context );
204 }
205
206 seriesIndex++;
207 }
208
209 context.painter()->restore();
210}
211
213{
214 if ( index < 0 || index >= static_cast<int>( mMarkerSymbols.size() ) )
215 {
216 return nullptr;
217 }
218
219 return mMarkerSymbols[index].get();
220}
221
223{
224 if ( index < 0 )
225 {
226 return;
227 }
228
229 if ( index + 1 >= static_cast<int>( mMarkerSymbols.size() ) )
230 {
231 mMarkerSymbols.resize( index + 1 );
232 }
233
234 mMarkerSymbols[index].reset( symbol );
235}
236
238{
239 if ( index < 0 || index >= static_cast<int>( mLineSymbols.size() ) )
240 {
241 return nullptr;
242 }
243
244 return mLineSymbols[index].get();
245}
246
248{
249 if ( index < 0 )
250 {
251 return;
252 }
253
254 if ( index + 1 >= static_cast<int>( mLineSymbols.size() ) )
255 {
256 mLineSymbols.resize( index + 1 );
257 }
258
259 mLineSymbols[index].reset( symbol );
260}
261
262bool QgsLineChartPlot::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
263{
264 Qgs2DXyPlot::writeXml( element, document, context );
265
266 QDomElement markerSymbolsElement = document.createElement( QStringLiteral( "markerSymbols" ) );
267 for ( int i = 0; i < static_cast<int>( mMarkerSymbols.size() ); i++ )
268 {
269 QDomElement markerSymbolElement = document.createElement( QStringLiteral( "markerSymbol" ) );
270 markerSymbolElement.setAttribute( QStringLiteral( "index" ), QString::number( i ) );
271 if ( mMarkerSymbols[i] )
272 {
273 markerSymbolElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mMarkerSymbols[i].get(), document, context ) );
274 }
275 markerSymbolsElement.appendChild( markerSymbolElement );
276 }
277 element.appendChild( markerSymbolsElement );
278
279 QDomElement lineSymbolsElement = document.createElement( QStringLiteral( "lineSymbols" ) );
280 for ( int i = 0; i < static_cast<int>( mLineSymbols.size() ); i++ )
281 {
282 QDomElement lineSymbolElement = document.createElement( QStringLiteral( "lineSymbol" ) );
283 lineSymbolElement.setAttribute( QStringLiteral( "index" ), QString::number( i ) );
284 if ( mLineSymbols[i] )
285 {
286 lineSymbolElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mLineSymbols[i].get(), document, context ) );
287 }
288 lineSymbolsElement.appendChild( lineSymbolElement );
289 }
290 element.appendChild( lineSymbolsElement );
291
292 return true;
293}
294
295bool QgsLineChartPlot::readXml( const QDomElement &element, const QgsReadWriteContext &context )
296{
297 Qgs2DXyPlot::readXml( element, context );
298
299 const QDomNodeList markerSymbolsList = element.firstChildElement( QStringLiteral( "markerSymbols" ) ).childNodes();
300 for ( int i = 0; i < markerSymbolsList.count(); i++ )
301 {
302 const QDomElement markerSymbolElement = markerSymbolsList.at( i ).toElement();
303 const int index = markerSymbolElement.attribute( QStringLiteral( "index" ), QStringLiteral( "-1" ) ).toInt();
304 if ( index >= 0 )
305 {
306 if ( markerSymbolElement.hasChildNodes() )
307 {
308 const QDomElement symbolElement = markerSymbolElement.firstChildElement( QStringLiteral( "symbol" ) );
309 setMarkerSymbolAt( index, QgsSymbolLayerUtils::loadSymbol< QgsMarkerSymbol >( symbolElement, context ).release() );
310 }
311 else
312 {
313 setMarkerSymbolAt( index, nullptr );
314 }
315 }
316 }
317
318 const QDomNodeList lineSymbolsList = element.firstChildElement( QStringLiteral( "lineSymbols" ) ).childNodes();
319 for ( int i = 0; i < lineSymbolsList.count(); i++ )
320 {
321 const QDomElement lineSymbolElement = lineSymbolsList.at( i ).toElement();
322 const int index = lineSymbolElement.attribute( QStringLiteral( "index" ), QStringLiteral( "-1" ) ).toInt();
323 if ( index >= 0 )
324 {
325 if ( lineSymbolElement.hasChildNodes() )
326 {
327 const QDomElement symbolElement = lineSymbolElement.firstChildElement( QStringLiteral( "symbol" ) );
328 setLineSymbolAt( index, QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( symbolElement, context ).release() );
329 }
330 else
331 {
332 setLineSymbolAt( index, nullptr );
333 }
334 }
335 }
336
337 return true;
338}
339
344
346{
347 QgsLineChartPlot *chart = dynamic_cast<QgsLineChartPlot *>( plot );
348 if ( !chart )
349 {
350 return nullptr;
351 }
352
353 return new QgsVectorLayerXyPlotDataGatherer( chart->xAxis().type() );
354}
@ Categorical
The axis represents categories.
Definition qgis.h:3336
@ Interval
The axis represents a range of values.
Definition qgis.h:3335
double yMaximum() const
Returns the maximum value of the y axis.
Definition qgsplot.h:742
double xMinimum() const
Returns the minimum value of the x axis.
Definition qgsplot.h:700
bool readXml(const QDomElement &element, const QgsReadWriteContext &context) override
Reads the plot's properties from an XML element.
Definition qgsplot.cpp:390
void applyDataDefinedProperties(QgsRenderContext &context, double &minX, double &maxX, double &minY, double &maxY, double &majorIntervalX, double &minorIntervalX, double &labelIntervalX, double &majorIntervalY, double &minorIntervalY, double &labelIntervalY) const
Applies 2D XY plot data-defined properties.
Definition qgsplot.cpp:1052
QgsPlotAxis & yAxis()
Returns a reference to the plot's y axis.
Definition qgsplot.h:770
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
Definition qgsplot.h:756
double yMinimum() const
Returns the minimum value of the y axis.
Definition qgsplot.h:714
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Writes the plot's properties into an XML element.
Definition qgsplot.cpp:364
double xMaximum() const
Returns the maximum value of the x axis.
Definition qgsplot.h:728
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:103
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:1300
QList< QgsAbstractPlotSeries * > series() const
Returns the list of series forming the plot data.
Definition qgsplot.cpp:1281
static QgsLineSymbol * lineChartLineSymbol()
Returns the default line symbol to use for line charts.
Definition qgsplot.cpp:1202
static QgsMarkerSymbol * lineChartMarkerSymbol()
Returns the default marker symbol to use for line charts.
Definition qgsplot.cpp:1196
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.