QGIS API Documentation 3.99.0-Master (357b655ed83)
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 const double xScale = plotArea.width() / ( maxX - minX );
82 const double yScale = plotArea.height() / ( maxY - minY );
83 const double categoriesWidth = plotArea.width() / categories.size();
84 int seriesIndex = 0;
85 for ( const QgsAbstractPlotSeries *series : seriesList )
86 {
87 QgsLineSymbol *lSymbol = !mLineSymbols.empty() ? lineSymbolAt( seriesIndex % mLineSymbols.size() ) : nullptr;
88 QgsMarkerSymbol *mSymbol = !mMarkerSymbols.empty() ? markerSymbolAt( seriesIndex % mMarkerSymbols.size() ) : nullptr;
89 if ( !lSymbol && !mSymbol )
90 {
91 continue;
92 }
93
94 if ( lSymbol )
95 {
96 lSymbol->startRender( context );
97 }
98
99 if ( mSymbol )
100 {
101 mSymbol->startRender( context );
102 }
103
104 if ( const QgsXyPlotSeries *xySeries = dynamic_cast<const QgsXyPlotSeries *>( series ) )
105 {
106 const QList<std::pair<double, double>> data = xySeries->data();
107 QVector<QPointF> points;
108 points.fill( QPointF(), xAxis().type() == Qgis::PlotAxisType::Interval ? data.size() : categories.size() );
109 int dataIndex = 0;
110 for ( const std::pair<double, double> &pair : data )
111 {
112 if ( !std::isnan( pair.second ) )
113 {
114 double x = 0;
115 switch ( xAxis().type() )
116 {
118 if ( pair.first < 0 || pair.first >= categories.size() )
119 {
120 continue;
121 }
122 x = ( categoriesWidth * pair.first ) + ( categoriesWidth / 2 );
123 break;
125 x = ( pair.first - minX ) * xScale;
126 break;
127 }
128 double y = ( pair.second - minY ) * yScale;
129
130 const QPointF point( plotArea.x() + x, plotArea.y() + plotArea.height() - y );
131 points.replace( xAxis().type() == Qgis::PlotAxisType::Interval ? dataIndex : pair.first, point );
132 }
133 dataIndex++;
134 }
135
136 if ( lSymbol )
137 {
138 chartScope->removeVariable( u"chart_value"_s );
139 QVector<QPointF> line;
140 for ( const QPointF &point : points )
141 {
142 if ( !point.isNull() )
143 {
144 line << point;
145 }
146 else
147 {
148 if ( !line.isEmpty() )
149 {
150 lSymbol->renderPolyline( QPolygonF( line ), nullptr, context );
151 line.clear();
152 }
153 }
154 }
155 if ( !line.isEmpty() )
156 {
157 lSymbol->renderPolyline( QPolygonF( line ), nullptr, context );
158 }
159 }
160 if ( mSymbol )
161 {
162 int pointIndex = 0;
163 for ( const QPointF &point : points )
164 {
165 if ( !point.isNull() )
166 {
167 double value = 0;
168 switch ( xAxis().type() )
169 {
171 value = data.at( pointIndex ).second;
172 break;
173
175 bool found = false;
176 for ( const std::pair<double, double> &pair : data )
177 {
178 if ( pair.first == pointIndex )
179 {
180 found = true;
181 value = pair.second;
182 chartScope->addVariable( QgsExpressionContextScope::StaticVariable( u"chart_category"_s, categories[pair.first], true ) );
183 break;
184 }
185 }
186 if ( !found )
187 {
188 continue;
189 }
190 break;
191 }
192
193 chartScope->addVariable( QgsExpressionContextScope::StaticVariable( u"chart_value"_s, value, true ) );
194 mSymbol->renderPoint( point, nullptr, context );
195 }
196 pointIndex++;
197 }
198 }
199 }
200
201 if ( lSymbol )
202 {
203 lSymbol->stopRender( context );
204 }
205 if ( mSymbol )
206 {
207 mSymbol->stopRender( context );
208 }
209
210 seriesIndex++;
211 }
212
213 context.painter()->restore();
214}
215
217{
218 if ( index < 0 || index >= static_cast<int>( mMarkerSymbols.size() ) )
219 {
220 return nullptr;
221 }
222
223 return mMarkerSymbols[index].get();
224}
225
227{
228 if ( index < 0 )
229 {
230 return;
231 }
232
233 if ( index + 1 >= static_cast<int>( mMarkerSymbols.size() ) )
234 {
235 mMarkerSymbols.resize( index + 1 );
236 }
237
238 mMarkerSymbols[index].reset( symbol );
239}
240
242{
243 if ( index < 0 || index >= static_cast<int>( mLineSymbols.size() ) )
244 {
245 return nullptr;
246 }
247
248 return mLineSymbols[index].get();
249}
250
252{
253 if ( index < 0 )
254 {
255 return;
256 }
257
258 if ( index + 1 >= static_cast<int>( mLineSymbols.size() ) )
259 {
260 mLineSymbols.resize( index + 1 );
261 }
262
263 mLineSymbols[index].reset( symbol );
264}
265
266bool QgsLineChartPlot::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
267{
268 Qgs2DXyPlot::writeXml( element, document, context );
269
270 QDomElement markerSymbolsElement = document.createElement( u"markerSymbols"_s );
271 for ( int i = 0; i < static_cast<int>( mMarkerSymbols.size() ); i++ )
272 {
273 QDomElement markerSymbolElement = document.createElement( u"markerSymbol"_s );
274 markerSymbolElement.setAttribute( u"index"_s, QString::number( i ) );
275 if ( mMarkerSymbols[i] )
276 {
277 markerSymbolElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mMarkerSymbols[i].get(), document, context ) );
278 }
279 markerSymbolsElement.appendChild( markerSymbolElement );
280 }
281 element.appendChild( markerSymbolsElement );
282
283 QDomElement lineSymbolsElement = document.createElement( u"lineSymbols"_s );
284 for ( int i = 0; i < static_cast<int>( mLineSymbols.size() ); i++ )
285 {
286 QDomElement lineSymbolElement = document.createElement( u"lineSymbol"_s );
287 lineSymbolElement.setAttribute( u"index"_s, QString::number( i ) );
288 if ( mLineSymbols[i] )
289 {
290 lineSymbolElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QString(), mLineSymbols[i].get(), document, context ) );
291 }
292 lineSymbolsElement.appendChild( lineSymbolElement );
293 }
294 element.appendChild( lineSymbolsElement );
295
296 return true;
297}
298
299bool QgsLineChartPlot::readXml( const QDomElement &element, const QgsReadWriteContext &context )
300{
301 Qgs2DXyPlot::readXml( element, context );
302
303 const QDomNodeList markerSymbolsList = element.firstChildElement( u"markerSymbols"_s ).childNodes();
304 for ( int i = 0; i < markerSymbolsList.count(); i++ )
305 {
306 const QDomElement markerSymbolElement = markerSymbolsList.at( i ).toElement();
307 const int index = markerSymbolElement.attribute( u"index"_s, u"-1"_s ).toInt();
308 if ( index >= 0 )
309 {
310 if ( markerSymbolElement.hasChildNodes() )
311 {
312 const QDomElement symbolElement = markerSymbolElement.firstChildElement( u"symbol"_s );
313 setMarkerSymbolAt( index, QgsSymbolLayerUtils::loadSymbol< QgsMarkerSymbol >( symbolElement, context ).release() );
314 }
315 else
316 {
317 setMarkerSymbolAt( index, nullptr );
318 }
319 }
320 }
321
322 const QDomNodeList lineSymbolsList = element.firstChildElement( u"lineSymbols"_s ).childNodes();
323 for ( int i = 0; i < lineSymbolsList.count(); i++ )
324 {
325 const QDomElement lineSymbolElement = lineSymbolsList.at( i ).toElement();
326 const int index = lineSymbolElement.attribute( u"index"_s, u"-1"_s ).toInt();
327 if ( index >= 0 )
328 {
329 if ( lineSymbolElement.hasChildNodes() )
330 {
331 const QDomElement symbolElement = lineSymbolElement.firstChildElement( u"symbol"_s );
332 setLineSymbolAt( index, QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( symbolElement, context ).release() );
333 }
334 else
335 {
336 setLineSymbolAt( index, nullptr );
337 }
338 }
339 }
340
341 return true;
342}
343
348
350{
351 QgsLineChartPlot *chart = dynamic_cast<QgsLineChartPlot *>( plot );
352 if ( !chart )
353 {
354 return nullptr;
355 }
356
357 return new QgsVectorLayerXyPlotDataGatherer( chart->xAxis().type() );
358}
@ Categorical
The axis represents categories.
Definition qgis.h:3407
@ Interval
The axis represents a range of values.
Definition qgis.h:3406
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:394
void applyDataDefinedProperties(QgsRenderContext &context, double &minX, double &maxX, double &minY, double &maxY, double &majorIntervalX, double &minorIntervalX, double &labelIntervalX, double &majorIntervalY, double &minorIntervalY, double &labelIntervalY) const
Applies 2D XY plot data-defined properties.
Definition qgsplot.cpp:1056
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:368
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: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:1304
QList< QgsAbstractPlotSeries * > series() const
Returns the list of series forming the plot data.
Definition qgsplot.cpp:1285
static QgsLineSymbol * lineChartLineSymbol()
Returns the default line symbol to use for line charts.
Definition qgsplot.cpp:1206
static QgsMarkerSymbol * lineChartMarkerSymbol()
Returns the default marker symbol to use for line charts.
Definition qgsplot.cpp:1200
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.