mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-07 00:15:48 -04:00
[plots] Add QgsBarChart and QgsLineChart classes, implement category typed axis rendering
This commit is contained in:
parent
a2caa8ea75
commit
a0c45d9380
@ -13,25 +13,27 @@ try:
|
||||
QgsPlot.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
QgsAbstractPlotSeries.__virtual_methods__ = ['categories']
|
||||
QgsAbstractPlotSeries.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
Qgs2DPlot.__virtual_methods__ = ['render', 'renderContent', 'interiorPlotArea']
|
||||
Qgs2DPlot.__overridden_methods__ = ['writeXml', 'readXml']
|
||||
Qgs2DPlot.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
QgsXyPlotSeries.__overridden_methods__ = ['categories']
|
||||
QgsXyPlotSeries.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
Qgs2DXyPlot.__overridden_methods__ = ['writeXml', 'readXml', 'render', 'interiorPlotArea']
|
||||
Qgs2DXyPlot.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
QgsAbstractPlotSeries.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
QgsXyPlotSeries.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
QgsPlotData.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
|
69
python/PyQt6/core/auto_generated/plot/qgschart.sip.in
Normal file
69
python/PyQt6/core/auto_generated/plot/qgschart.sip.in
Normal file
@ -0,0 +1,69 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/plot/qgschart.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.py again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsBarChart : Qgs2DXyPlot
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
A simple bar chart class.
|
||||
|
||||
.. warning::
|
||||
|
||||
This class is not considered stable API, and may change in future!
|
||||
|
||||
.. versionadded:: 4.0
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgschart.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsBarChart();
|
||||
~QgsBarChart();
|
||||
|
||||
virtual void renderContent( QgsRenderContext &context, const QRectF &plotArea, const QgsPlotData &plotData = QgsPlotData() );
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
class QgsLineChart : Qgs2DXyPlot
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
A simple line chart class.
|
||||
|
||||
.. warning::
|
||||
|
||||
This class is not considered stable API, and may change in future!
|
||||
|
||||
.. versionadded:: 4.0
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgschart.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsLineChart();
|
||||
~QgsLineChart();
|
||||
|
||||
virtual void renderContent( QgsRenderContext &context, const QRectF &plotArea, const QgsPlotData &plotData = QgsPlotData() );
|
||||
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/plot/qgschart.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.py again *
|
||||
************************************************************************/
|
@ -52,13 +52,16 @@ class QgsAbstractPlotSeries
|
||||
public:
|
||||
|
||||
QgsAbstractPlotSeries();
|
||||
~QgsAbstractPlotSeries();
|
||||
virtual ~QgsAbstractPlotSeries();
|
||||
|
||||
QString name() const;
|
||||
void setName( const QString &name );
|
||||
QgsSymbol *symbol() const;
|
||||
void setSymbol( QgsSymbol *symbol );
|
||||
void setSymbol( QgsSymbol *symbol /Transfer/ );
|
||||
virtual QList<QVariant> categories() const;
|
||||
|
||||
private:
|
||||
QgsAbstractPlotSeries( const QgsAbstractPlotSeries &other );
|
||||
};
|
||||
|
||||
class QgsXyPlotSeries : QgsAbstractPlotSeries
|
||||
@ -72,9 +75,14 @@ class QgsXyPlotSeries : QgsAbstractPlotSeries
|
||||
QgsXyPlotSeries();
|
||||
~QgsXyPlotSeries();
|
||||
|
||||
virtual QList<QVariant> categories() const;
|
||||
|
||||
|
||||
void append( const QVariant &x, const double &y );
|
||||
void clear();
|
||||
|
||||
private:
|
||||
QgsXyPlotSeries( const QgsXyPlotSeries &other );
|
||||
};
|
||||
|
||||
class QgsPlotData
|
||||
@ -89,7 +97,7 @@ class QgsPlotData
|
||||
~QgsPlotData();
|
||||
|
||||
QList<QgsAbstractPlotSeries *> series() const;
|
||||
void addSeries( QgsAbstractPlotSeries *series );
|
||||
void addSeries( QgsAbstractPlotSeries *series /Transfer/ );
|
||||
void clearSeries();
|
||||
|
||||
};
|
||||
|
@ -518,6 +518,7 @@
|
||||
%Include auto_generated/painting/qgspaintenginehack.sip
|
||||
%Include auto_generated/painting/qgspainting.sip
|
||||
%Include auto_generated/pdf/qgspdfrenderer.sip
|
||||
%Include auto_generated/plot/qgschart.sip
|
||||
%Include auto_generated/plot/qgsplot.sip
|
||||
%Include auto_generated/pointcloud/qgspointcloudattribute.sip
|
||||
%Include auto_generated/pointcloud/qgspointcloudattributebyramprenderer.sip
|
||||
|
@ -13,25 +13,27 @@ try:
|
||||
QgsPlot.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
QgsAbstractPlotSeries.__virtual_methods__ = ['categories']
|
||||
QgsAbstractPlotSeries.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
Qgs2DPlot.__virtual_methods__ = ['render', 'renderContent', 'interiorPlotArea']
|
||||
Qgs2DPlot.__overridden_methods__ = ['writeXml', 'readXml']
|
||||
Qgs2DPlot.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
QgsXyPlotSeries.__overridden_methods__ = ['categories']
|
||||
QgsXyPlotSeries.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
Qgs2DXyPlot.__overridden_methods__ = ['writeXml', 'readXml', 'render', 'interiorPlotArea']
|
||||
Qgs2DXyPlot.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
QgsAbstractPlotSeries.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
QgsXyPlotSeries.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
QgsPlotData.__group__ = ['plot']
|
||||
except (NameError, AttributeError):
|
||||
|
69
python/core/auto_generated/plot/qgschart.sip.in
Normal file
69
python/core/auto_generated/plot/qgschart.sip.in
Normal file
@ -0,0 +1,69 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/plot/qgschart.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.py again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsBarChart : Qgs2DXyPlot
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
A simple bar chart class.
|
||||
|
||||
.. warning::
|
||||
|
||||
This class is not considered stable API, and may change in future!
|
||||
|
||||
.. versionadded:: 4.0
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgschart.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsBarChart();
|
||||
~QgsBarChart();
|
||||
|
||||
virtual void renderContent( QgsRenderContext &context, const QRectF &plotArea, const QgsPlotData &plotData = QgsPlotData() );
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
class QgsLineChart : Qgs2DXyPlot
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
A simple line chart class.
|
||||
|
||||
.. warning::
|
||||
|
||||
This class is not considered stable API, and may change in future!
|
||||
|
||||
.. versionadded:: 4.0
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgschart.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsLineChart();
|
||||
~QgsLineChart();
|
||||
|
||||
virtual void renderContent( QgsRenderContext &context, const QRectF &plotArea, const QgsPlotData &plotData = QgsPlotData() );
|
||||
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/plot/qgschart.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.py again *
|
||||
************************************************************************/
|
@ -52,13 +52,16 @@ class QgsAbstractPlotSeries
|
||||
public:
|
||||
|
||||
QgsAbstractPlotSeries();
|
||||
~QgsAbstractPlotSeries();
|
||||
virtual ~QgsAbstractPlotSeries();
|
||||
|
||||
QString name() const;
|
||||
void setName( const QString &name );
|
||||
QgsSymbol *symbol() const;
|
||||
void setSymbol( QgsSymbol *symbol );
|
||||
void setSymbol( QgsSymbol *symbol /Transfer/ );
|
||||
virtual QList<QVariant> categories() const;
|
||||
|
||||
private:
|
||||
QgsAbstractPlotSeries( const QgsAbstractPlotSeries &other );
|
||||
};
|
||||
|
||||
class QgsXyPlotSeries : QgsAbstractPlotSeries
|
||||
@ -72,9 +75,14 @@ class QgsXyPlotSeries : QgsAbstractPlotSeries
|
||||
QgsXyPlotSeries();
|
||||
~QgsXyPlotSeries();
|
||||
|
||||
virtual QList<QVariant> categories() const;
|
||||
|
||||
|
||||
void append( const QVariant &x, const double &y );
|
||||
void clear();
|
||||
|
||||
private:
|
||||
QgsXyPlotSeries( const QgsXyPlotSeries &other );
|
||||
};
|
||||
|
||||
class QgsPlotData
|
||||
@ -89,7 +97,7 @@ class QgsPlotData
|
||||
~QgsPlotData();
|
||||
|
||||
QList<QgsAbstractPlotSeries *> series() const;
|
||||
void addSeries( QgsAbstractPlotSeries *series );
|
||||
void addSeries( QgsAbstractPlotSeries *series /Transfer/ );
|
||||
void clearSeries();
|
||||
|
||||
};
|
||||
|
@ -518,6 +518,7 @@
|
||||
%Include auto_generated/painting/qgspaintenginehack.sip
|
||||
%Include auto_generated/painting/qgspainting.sip
|
||||
%Include auto_generated/pdf/qgspdfrenderer.sip
|
||||
%Include auto_generated/plot/qgschart.sip
|
||||
%Include auto_generated/plot/qgsplot.sip
|
||||
%Include auto_generated/pointcloud/qgspointcloudattribute.sip
|
||||
%Include auto_generated/pointcloud/qgspointcloudattributebyramprenderer.sip
|
||||
|
@ -90,6 +90,7 @@ set(QGIS_CORE_SRCS
|
||||
gps/qgssatelliteinformation.cpp
|
||||
gps/qgsvectorlayergpslogger.cpp
|
||||
|
||||
plot/qgschart.cpp
|
||||
plot/qgsplot.cpp
|
||||
|
||||
symbology/qgs25drenderer.cpp
|
||||
@ -1739,6 +1740,7 @@ set(QGIS_CORE_HDRS
|
||||
|
||||
pdf/qgspdfrenderer.h
|
||||
|
||||
plot/qgschart.h
|
||||
plot/qgsplot.h
|
||||
|
||||
pointcloud/qgspointcloudattribute.h
|
||||
|
163
src/core/plot/qgschart.cpp
Normal file
163
src/core/plot/qgschart.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
/***************************************************************************
|
||||
qgsplot.cpp
|
||||
---------------
|
||||
begin : March 2022
|
||||
copyright : (C) 2022 by Nyall Dawson
|
||||
email : nyall dot dawson at gmail dot com
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgschart.h"
|
||||
#include "qgsexpressioncontextutils.h"
|
||||
#include "qgssymbol.h"
|
||||
#include "qgssymbollayer.h"
|
||||
#include "qgsfillsymbol.h"
|
||||
#include "qgslinesymbol.h"
|
||||
|
||||
void QgsBarChart::renderContent( QgsRenderContext &context, const QRectF &plotArea, const QgsPlotData &plotData )
|
||||
{
|
||||
const QList<QgsAbstractPlotSeries *> seriesList = plotData.series();
|
||||
if ( seriesList.isEmpty() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QList<QVariant> categories = seriesList.at( 0 )->categories();
|
||||
if ( categories.isEmpty() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QgsExpressionContextScope *chartScope = new QgsExpressionContextScope( QStringLiteral( "chart" ) );
|
||||
const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), chartScope );
|
||||
|
||||
context.painter()->save();
|
||||
context.painter()->setClipRect( plotArea );
|
||||
|
||||
const double xScale = plotArea.width() / ( xMaximum() - xMinimum() );
|
||||
const double yScale = plotArea.height() / ( yMaximum() - yMinimum() );
|
||||
const double categoriesWidth = plotArea.width() / categories.size();
|
||||
const double barsWidth = categoriesWidth / 2;
|
||||
const double barWidth = barsWidth / seriesList.size();
|
||||
int seriesIndex = 0;
|
||||
for ( const QgsAbstractPlotSeries *series : seriesList )
|
||||
{
|
||||
QgsFillSymbol *symbol = dynamic_cast<QgsFillSymbol *>( series->symbol() );
|
||||
if ( !symbol )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
symbol->startRender( context );
|
||||
|
||||
const double barStartAdjustement = -( barsWidth / 2 ) + barWidth * seriesIndex;
|
||||
if ( const QgsXyPlotSeries *xySeries = dynamic_cast<const QgsXyPlotSeries *>( series ) )
|
||||
{
|
||||
const QList<std::pair<QVariant, double>> data = xySeries->data();
|
||||
for ( const std::pair<QVariant, double> &pair : data )
|
||||
{
|
||||
double x, y;
|
||||
if ( xAxis().type() == Qgis::PlotAxisType::ValueType )
|
||||
{
|
||||
x = ( pair.first.toDouble() ) * xScale + barStartAdjustement;
|
||||
}
|
||||
else if ( xAxis().type() == Qgis::PlotAxisType::CategoryType )
|
||||
{
|
||||
x = ( categoriesWidth * categories.indexOf( pair.first ) ) + ( categoriesWidth / 2 ) + barStartAdjustement;
|
||||
}
|
||||
y = ( pair.second - yMinimum() ) * yScale;
|
||||
|
||||
const double zero = ( 0.0 - yMinimum() ) * yScale;
|
||||
const QPoint topLeft( plotArea.left() + x,
|
||||
plotArea.y() + plotArea.height() - y );
|
||||
const QPoint bottomRight( plotArea.left() + x + barWidth,
|
||||
plotArea.y() + plotArea.height() - zero );
|
||||
|
||||
chartScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "chart_value" ), pair.second, true ) );
|
||||
symbol->renderPolygon( QPolygonF( QRectF( topLeft, bottomRight ) ), nullptr, nullptr, context );
|
||||
}
|
||||
}
|
||||
|
||||
symbol->stopRender( context );
|
||||
seriesIndex++;
|
||||
}
|
||||
|
||||
context.painter()->restore();
|
||||
}
|
||||
|
||||
void QgsLineChart::renderContent( QgsRenderContext &context, const QRectF &plotArea, const QgsPlotData &plotData )
|
||||
{
|
||||
const QList<QgsAbstractPlotSeries *> seriesList = plotData.series();
|
||||
if ( seriesList.isEmpty() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QList<QVariant> categories = seriesList.at( 0 )->categories();
|
||||
if ( categories.isEmpty() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QgsExpressionContextScope *chartScope = new QgsExpressionContextScope( QStringLiteral( "chart" ) );
|
||||
const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), chartScope );
|
||||
|
||||
context.painter()->save();
|
||||
context.painter()->setClipRect( plotArea );
|
||||
|
||||
const double xScale = plotArea.width() / ( xMaximum() - xMinimum() );
|
||||
const double yScale = plotArea.height() / ( yMaximum() - yMinimum() );
|
||||
const double categoriesWidth = plotArea.width() / categories.size();
|
||||
int seriesIndex = 0;
|
||||
for ( const QgsAbstractPlotSeries *series : seriesList )
|
||||
{
|
||||
QgsLineSymbol *symbol = dynamic_cast<QgsLineSymbol *>( series->symbol() );
|
||||
if ( !symbol )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
symbol->startRender( context );
|
||||
|
||||
if ( const QgsXyPlotSeries *xySeries = dynamic_cast<const QgsXyPlotSeries *>( series ) )
|
||||
{
|
||||
const QList<std::pair<QVariant, double>> data = xySeries->data();
|
||||
QVector<QPointF> points;
|
||||
QList<double> values;
|
||||
points.fill( QPointF(), xAxis().type() == Qgis::PlotAxisType::ValueType ? data.size() : categories.size() );
|
||||
int dataIndex = 0;
|
||||
for ( const std::pair<QVariant, double> &pair : data )
|
||||
{
|
||||
double x, y;
|
||||
if ( xAxis().type() == Qgis::PlotAxisType::ValueType )
|
||||
{
|
||||
x = ( pair.first.toDouble() ) * xScale;
|
||||
}
|
||||
else if ( xAxis().type() == Qgis::PlotAxisType::CategoryType )
|
||||
{
|
||||
x = ( categoriesWidth * categories.indexOf( pair.first ) ) + ( categoriesWidth / 2 );
|
||||
}
|
||||
y = ( pair.second - yMinimum() ) * yScale;
|
||||
|
||||
values << pair.second;
|
||||
points.replace( xAxis().type() == Qgis::PlotAxisType::ValueType ? dataIndex : categories.indexOf( pair.first ), QPointF( plotArea.x() + x,
|
||||
plotArea.y() + plotArea.height() - y ) );
|
||||
dataIndex++;
|
||||
}
|
||||
|
||||
chartScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "chart_value" ), QVariant::fromValue( values ), true ) );
|
||||
symbol->renderPolyline( QPolygonF( points ), nullptr, context );
|
||||
}
|
||||
|
||||
symbol->stopRender( context );
|
||||
seriesIndex++;
|
||||
}
|
||||
|
||||
context.painter()->restore();
|
||||
}
|
70
src/core/plot/qgschart.h
Normal file
70
src/core/plot/qgschart.h
Normal file
@ -0,0 +1,70 @@
|
||||
/***************************************************************************
|
||||
qgschart.h
|
||||
---------------
|
||||
begin : June 2025
|
||||
copyright : (C) 2025 by Mathieu
|
||||
email : mathieu at opengis dot ch
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
#ifndef QGSCHART_H
|
||||
#define QGSCHART_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgis_sip.h"
|
||||
#include "qgsplot.h"
|
||||
|
||||
|
||||
/**
|
||||
* \brief A simple bar chart class.
|
||||
*
|
||||
* \warning This class is not considered stable API, and may change in future!
|
||||
*
|
||||
* \ingroup core
|
||||
* \since QGIS 4.0
|
||||
*/
|
||||
class CORE_EXPORT QgsBarChart : public Qgs2DXyPlot
|
||||
{
|
||||
public:
|
||||
|
||||
QgsBarChart() = default;
|
||||
~QgsBarChart() = default;
|
||||
|
||||
void renderContent( QgsRenderContext &context, const QRectF &plotArea, const QgsPlotData &plotData = QgsPlotData() ) override;
|
||||
|
||||
private:
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* \brief A simple line chart class.
|
||||
*
|
||||
* \warning This class is not considered stable API, and may change in future!
|
||||
*
|
||||
* \ingroup core
|
||||
* \since QGIS 4.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLineChart : public Qgs2DXyPlot
|
||||
{
|
||||
public:
|
||||
|
||||
QgsLineChart() = default;
|
||||
~QgsLineChart() = default;
|
||||
|
||||
void renderContent( QgsRenderContext &context, const QRectF &plotArea, const QgsPlotData &plotData = QgsPlotData() ) override;
|
||||
|
||||
private:
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif
|
@ -334,10 +334,18 @@ void Qgs2DXyPlot::render( QgsRenderContext &context, const QgsPlotData &plotData
|
||||
|
||||
QgsNumericFormatContext numericContext;
|
||||
|
||||
// categories
|
||||
QList<QVariant> categories;
|
||||
const QList<QgsAbstractPlotSeries *> seriesList = plotData.series();
|
||||
if ( !seriesList.isEmpty() )
|
||||
{
|
||||
categories = seriesList.at( 0 )->categories();
|
||||
}
|
||||
|
||||
// calculate text metrics
|
||||
double maxYAxisLabelWidth = 0;
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
|
||||
if ( mYAxis.labelInterval() > 0 )
|
||||
if ( mYAxis.type() == Qgis::PlotAxisType::ValueType && mYAxis.labelInterval() > 0 )
|
||||
{
|
||||
for ( double currentY = firstYLabel; ; currentY += mYAxis.labelInterval() )
|
||||
{
|
||||
@ -374,6 +382,14 @@ void Qgs2DXyPlot::render( QgsRenderContext &context, const QgsPlotData &plotData
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ( mYAxis.type() == Qgis::PlotAxisType::CategoryType )
|
||||
{
|
||||
for ( int i = 0; i < categories.size(); i++ )
|
||||
{
|
||||
const QString text = categories.at( i ).toString();
|
||||
maxYAxisLabelWidth = std::max( maxYAxisLabelWidth, QgsTextRenderer::textWidth( context, mYAxis.textFormat(), { text } ) );
|
||||
}
|
||||
}
|
||||
|
||||
const double chartAreaLeft = plotArea.left();
|
||||
const double chartAreaRight = plotArea.right();
|
||||
@ -398,139 +414,180 @@ void Qgs2DXyPlot::render( QgsRenderContext &context, const QgsPlotData &plotData
|
||||
// grid lines
|
||||
|
||||
// x
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
|
||||
double nextMajorXGrid = firstMajorXGrid;
|
||||
int objectNumber = 0;
|
||||
for ( double currentX = firstMinorXGrid; objectNumber < MAX_OBJECTS && ( currentX <= mMaxX && !qgsDoubleNear( currentX, mMaxX, xTolerance ) ); currentX += mXAxis.gridIntervalMinor(), ++objectNumber )
|
||||
if ( mXAxis.type() == Qgis::PlotAxisType::ValueType )
|
||||
{
|
||||
bool isMinor = true;
|
||||
if ( qgsDoubleNear( currentX, nextMajorXGrid, xTolerance ) )
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
|
||||
double nextMajorXGrid = firstMajorXGrid;
|
||||
int objectNumber = 0;
|
||||
for ( double currentX = firstMinorXGrid; objectNumber < MAX_OBJECTS && ( currentX <= mMaxX && !qgsDoubleNear( currentX, mMaxX, xTolerance ) ); currentX += mXAxis.gridIntervalMinor(), ++objectNumber )
|
||||
{
|
||||
isMinor = false;
|
||||
nextMajorXGrid += mXAxis.gridIntervalMajor();
|
||||
bool isMinor = true;
|
||||
if ( qgsDoubleNear( currentX, nextMajorXGrid, xTolerance ) )
|
||||
{
|
||||
isMinor = false;
|
||||
nextMajorXGrid += mXAxis.gridIntervalMajor();
|
||||
}
|
||||
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
|
||||
|
||||
QgsLineSymbol *currentGridSymbol = isMinor ? mXAxis.gridMinorSymbol() : mXAxis.gridMajorSymbol();
|
||||
currentGridSymbol->renderPolyline( QPolygonF(
|
||||
QVector<QPointF>
|
||||
{
|
||||
QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, chartAreaBottom ),
|
||||
QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, chartAreaTop )
|
||||
} ), nullptr, context );
|
||||
}
|
||||
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
|
||||
|
||||
QgsLineSymbol *currentGridSymbol = isMinor ? mXAxis.gridMinorSymbol() : mXAxis.gridMajorSymbol();
|
||||
currentGridSymbol->renderPolyline( QPolygonF(
|
||||
QVector<QPointF>
|
||||
{
|
||||
QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, chartAreaBottom ),
|
||||
QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, chartAreaTop )
|
||||
} ), nullptr, context );
|
||||
}
|
||||
|
||||
// y
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
|
||||
double nextMajorYGrid = firstMajorYGrid;
|
||||
objectNumber = 0;
|
||||
for ( double currentY = firstMinorYGrid; objectNumber < MAX_OBJECTS && ( currentY <= mMaxY && !qgsDoubleNear( currentY, mMaxY, yTolerance ) ); currentY += mYAxis.gridIntervalMinor(), ++objectNumber )
|
||||
if ( mYAxis.type() == Qgis::PlotAxisType::ValueType )
|
||||
{
|
||||
bool isMinor = true;
|
||||
if ( qgsDoubleNear( currentY, nextMajorYGrid, yTolerance ) )
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
|
||||
double nextMajorYGrid = firstMajorYGrid;
|
||||
int objectNumber = 0;
|
||||
for ( double currentY = firstMinorYGrid; objectNumber < MAX_OBJECTS && ( currentY <= mMaxY && !qgsDoubleNear( currentY, mMaxY, yTolerance ) ); currentY += mYAxis.gridIntervalMinor(), ++objectNumber )
|
||||
{
|
||||
isMinor = false;
|
||||
nextMajorYGrid += mYAxis.gridIntervalMajor();
|
||||
bool isMinor = true;
|
||||
if ( qgsDoubleNear( currentY, nextMajorYGrid, yTolerance ) )
|
||||
{
|
||||
isMinor = false;
|
||||
nextMajorYGrid += mYAxis.gridIntervalMajor();
|
||||
}
|
||||
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
|
||||
|
||||
QgsLineSymbol *currentGridSymbol = isMinor ? mYAxis.gridMinorSymbol() : mYAxis.gridMajorSymbol();
|
||||
currentGridSymbol->renderPolyline( QPolygonF(
|
||||
QVector<QPointF>
|
||||
{
|
||||
QPointF( chartAreaLeft, chartAreaBottom - ( currentY - mMinY ) * yScale ),
|
||||
QPointF( chartAreaRight, chartAreaBottom - ( currentY - mMinY ) * yScale )
|
||||
} ), nullptr, context );
|
||||
}
|
||||
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
|
||||
|
||||
QgsLineSymbol *currentGridSymbol = isMinor ? mYAxis.gridMinorSymbol() : mYAxis.gridMajorSymbol();
|
||||
currentGridSymbol->renderPolyline( QPolygonF(
|
||||
QVector<QPointF>
|
||||
{
|
||||
QPointF( chartAreaLeft, chartAreaBottom - ( currentY - mMinY ) * yScale ),
|
||||
QPointF( chartAreaRight, chartAreaBottom - ( currentY - mMinY ) * yScale )
|
||||
} ), nullptr, context );
|
||||
}
|
||||
|
||||
// axis labels
|
||||
|
||||
// x
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
|
||||
objectNumber = 0;
|
||||
if ( mXAxis.labelInterval() > 0 )
|
||||
if ( mXAxis.type() == Qgis::PlotAxisType::ValueType )
|
||||
{
|
||||
for ( double currentX = firstXLabel; ; currentX += mXAxis.labelInterval(), ++objectNumber )
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
|
||||
int objectNumber = 0;
|
||||
if ( mXAxis.labelInterval() > 0 )
|
||||
{
|
||||
const bool hasMoreLabels = objectNumber + 1 < MAX_OBJECTS && ( currentX + mXAxis.labelInterval() <= mMaxX || qgsDoubleNear( currentX + mXAxis.labelInterval(), mMaxX, xTolerance ) );
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
|
||||
QString text = mXAxis.numericFormat()->formatDouble( currentX, numericContext );
|
||||
switch ( mXAxis.labelSuffixPlacement() )
|
||||
for ( double currentX = firstXLabel; ; currentX += mXAxis.labelInterval(), ++objectNumber )
|
||||
{
|
||||
case Qgis::PlotAxisSuffixPlacement::NoLabels:
|
||||
break;
|
||||
const bool hasMoreLabels = objectNumber + 1 < MAX_OBJECTS && ( currentX + mXAxis.labelInterval() <= mMaxX || qgsDoubleNear( currentX + mXAxis.labelInterval(), mMaxX, xTolerance ) );
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentX, true ) );
|
||||
QString text = mXAxis.numericFormat()->formatDouble( currentX, numericContext );
|
||||
switch ( mXAxis.labelSuffixPlacement() )
|
||||
{
|
||||
case Qgis::PlotAxisSuffixPlacement::NoLabels:
|
||||
break;
|
||||
|
||||
case Qgis::PlotAxisSuffixPlacement::EveryLabel:
|
||||
text += xAxisSuffix;
|
||||
break;
|
||||
|
||||
case Qgis::PlotAxisSuffixPlacement::FirstLabel:
|
||||
if ( objectNumber == 0 )
|
||||
case Qgis::PlotAxisSuffixPlacement::EveryLabel:
|
||||
text += xAxisSuffix;
|
||||
break;
|
||||
break;
|
||||
|
||||
case Qgis::PlotAxisSuffixPlacement::LastLabel:
|
||||
if ( !hasMoreLabels )
|
||||
text += xAxisSuffix;
|
||||
break;
|
||||
case Qgis::PlotAxisSuffixPlacement::FirstLabel:
|
||||
if ( objectNumber == 0 )
|
||||
text += xAxisSuffix;
|
||||
break;
|
||||
|
||||
case Qgis::PlotAxisSuffixPlacement::FirstAndLastLabels:
|
||||
if ( objectNumber == 0 || !hasMoreLabels )
|
||||
text += xAxisSuffix;
|
||||
case Qgis::PlotAxisSuffixPlacement::LastLabel:
|
||||
if ( !hasMoreLabels )
|
||||
text += xAxisSuffix;
|
||||
break;
|
||||
|
||||
case Qgis::PlotAxisSuffixPlacement::FirstAndLastLabels:
|
||||
if ( objectNumber == 0 || !hasMoreLabels )
|
||||
text += xAxisSuffix;
|
||||
break;
|
||||
}
|
||||
|
||||
QgsTextRenderer::drawText( QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, mSize.height() - context.convertToPainterUnits( mMargins.bottom(), Qgis::RenderUnit::Millimeters ) ),
|
||||
0, Qgis::TextHorizontalAlignment::Center, { text }, context, mXAxis.textFormat() );
|
||||
if ( !hasMoreLabels )
|
||||
break;
|
||||
}
|
||||
|
||||
QgsTextRenderer::drawText( QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, mSize.height() - context.convertToPainterUnits( mMargins.bottom(), Qgis::RenderUnit::Millimeters ) ),
|
||||
}
|
||||
}
|
||||
else if ( mXAxis.type() == Qgis::PlotAxisType::CategoryType )
|
||||
{
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "x" ), true ) );
|
||||
const double categoryWidth = plotArea.width() / categories.size();
|
||||
for ( int i = 0; i < categories.size(); i++ )
|
||||
{
|
||||
const double currentX = ( i * categoryWidth ) + categoryWidth / 2.0;
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), categories.at( i ), true ) );
|
||||
const QString text = categories.at( i ).toString();
|
||||
QgsTextRenderer::drawText( QPointF( currentX + chartAreaLeft, mSize.height() - context.convertToPainterUnits( mMargins.bottom(), Qgis::RenderUnit::Millimeters ) ),
|
||||
0, Qgis::TextHorizontalAlignment::Center, { text }, context, mXAxis.textFormat() );
|
||||
if ( !hasMoreLabels )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// y
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
|
||||
objectNumber = 0;
|
||||
if ( mYAxis.labelInterval() > 0 )
|
||||
if ( mYAxis.type() == Qgis::PlotAxisType::ValueType )
|
||||
{
|
||||
for ( double currentY = firstYLabel; ; currentY += mYAxis.labelInterval(), ++objectNumber )
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
|
||||
int objectNumber = 0;
|
||||
if ( mYAxis.labelInterval() > 0 )
|
||||
{
|
||||
const bool hasMoreLabels = objectNumber + 1 < MAX_OBJECTS && ( currentY + mYAxis.labelInterval() <= mMaxY || qgsDoubleNear( currentY + mYAxis.labelInterval(), mMaxY, yTolerance ) );
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
|
||||
QString text = mYAxis.numericFormat()->formatDouble( currentY, numericContext );
|
||||
switch ( mYAxis.labelSuffixPlacement() )
|
||||
for ( double currentY = firstYLabel; ; currentY += mYAxis.labelInterval(), ++objectNumber )
|
||||
{
|
||||
case Qgis::PlotAxisSuffixPlacement::NoLabels:
|
||||
break;
|
||||
const bool hasMoreLabels = objectNumber + 1 < MAX_OBJECTS && ( currentY + mYAxis.labelInterval() <= mMaxY || qgsDoubleNear( currentY + mYAxis.labelInterval(), mMaxY, yTolerance ) );
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), currentY, true ) );
|
||||
QString text = mYAxis.numericFormat()->formatDouble( currentY, numericContext );
|
||||
switch ( mYAxis.labelSuffixPlacement() )
|
||||
{
|
||||
case Qgis::PlotAxisSuffixPlacement::NoLabels:
|
||||
break;
|
||||
|
||||
case Qgis::PlotAxisSuffixPlacement::EveryLabel:
|
||||
text += yAxisSuffix;
|
||||
break;
|
||||
|
||||
case Qgis::PlotAxisSuffixPlacement::FirstLabel:
|
||||
if ( objectNumber == 0 )
|
||||
case Qgis::PlotAxisSuffixPlacement::EveryLabel:
|
||||
text += yAxisSuffix;
|
||||
break;
|
||||
break;
|
||||
|
||||
case Qgis::PlotAxisSuffixPlacement::LastLabel:
|
||||
if ( !hasMoreLabels )
|
||||
text += yAxisSuffix;
|
||||
break;
|
||||
case Qgis::PlotAxisSuffixPlacement::FirstLabel:
|
||||
if ( objectNumber == 0 )
|
||||
text += yAxisSuffix;
|
||||
break;
|
||||
|
||||
case Qgis::PlotAxisSuffixPlacement::FirstAndLastLabels:
|
||||
if ( objectNumber == 0 || !hasMoreLabels )
|
||||
text += yAxisSuffix;
|
||||
case Qgis::PlotAxisSuffixPlacement::LastLabel:
|
||||
if ( !hasMoreLabels )
|
||||
text += yAxisSuffix;
|
||||
break;
|
||||
|
||||
case Qgis::PlotAxisSuffixPlacement::FirstAndLastLabels:
|
||||
if ( objectNumber == 0 || !hasMoreLabels )
|
||||
text += yAxisSuffix;
|
||||
break;
|
||||
}
|
||||
|
||||
const double height = QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { text } );
|
||||
QgsTextRenderer::drawText( QPointF(
|
||||
maxYAxisLabelWidth + context.convertToPainterUnits( mMargins.left(), Qgis::RenderUnit::Millimeters ),
|
||||
chartAreaBottom - ( currentY - mMinY ) * yScale + height / 2 ),
|
||||
0, Qgis::TextHorizontalAlignment::Right, { text }, context, mYAxis.textFormat(), false );
|
||||
if ( !hasMoreLabels )
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else if ( mYAxis.type() == Qgis::PlotAxisType::CategoryType )
|
||||
{
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis" ), QStringLiteral( "y" ), true ) );
|
||||
const double categoryHeight = plotArea.height() / categories.size();
|
||||
for ( int i = 0; i < categories.size(); i++ )
|
||||
{
|
||||
const double currentY = ( i * categoryHeight ) + categoryHeight / 2.0;
|
||||
plotScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "plot_axis_value" ), categories.at( i ), true ) );
|
||||
const QString text = categories.at( i ).toString();
|
||||
const double height = QgsTextRenderer::textHeight( context, mYAxis.textFormat(), { text } );
|
||||
QgsTextRenderer::drawText( QPointF(
|
||||
maxYAxisLabelWidth + context.convertToPainterUnits( mMargins.left(), Qgis::RenderUnit::Millimeters ),
|
||||
chartAreaBottom - ( currentY - mMinY ) * yScale + height / 2 ),
|
||||
chartAreaBottom - currentY + height / 2 ),
|
||||
0, Qgis::TextHorizontalAlignment::Right, { text }, context, mYAxis.textFormat(), false );
|
||||
if ( !hasMoreLabels )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -906,19 +963,33 @@ void QgsAbstractPlotSeries::setName( const QString &name )
|
||||
|
||||
QgsSymbol *QgsAbstractPlotSeries::symbol() const
|
||||
{
|
||||
return mSymbol;
|
||||
return mSymbol.get();
|
||||
}
|
||||
|
||||
void QgsAbstractPlotSeries::setSymbol( QgsSymbol *symbol )
|
||||
{
|
||||
delete mSymbol;
|
||||
mSymbol = symbol;
|
||||
mSymbol.reset( symbol );
|
||||
}
|
||||
|
||||
QList<QVariant> QgsAbstractPlotSeries::categories() const
|
||||
{
|
||||
return QList<QVariant>();
|
||||
}
|
||||
|
||||
//
|
||||
// QgsXyPlotSeries
|
||||
//
|
||||
|
||||
QList<QVariant> QgsXyPlotSeries::categories() const
|
||||
{
|
||||
QList<QVariant> categories;
|
||||
for ( const std::pair<QVariant, double> &pair : std::as_const( mData ) )
|
||||
{
|
||||
categories << pair.first;
|
||||
}
|
||||
return categories;
|
||||
}
|
||||
|
||||
QList<std::pair<QVariant, double>> QgsXyPlotSeries::data() const
|
||||
{
|
||||
return mData;
|
||||
|
@ -68,17 +68,21 @@ class CORE_EXPORT QgsAbstractPlotSeries
|
||||
public:
|
||||
|
||||
QgsAbstractPlotSeries() = default;
|
||||
~QgsAbstractPlotSeries() = default;
|
||||
virtual ~QgsAbstractPlotSeries() = default;
|
||||
|
||||
QString name() const;
|
||||
void setName( const QString &name );
|
||||
QgsSymbol *symbol() const;
|
||||
void setSymbol( QgsSymbol *symbol );
|
||||
void setSymbol( QgsSymbol *symbol SIP_TRANSFER );
|
||||
virtual QList<QVariant> categories() const;
|
||||
|
||||
private:
|
||||
#ifdef SIP_RUN
|
||||
QgsAbstractPlotSeries( const QgsAbstractPlotSeries &other );
|
||||
#endif
|
||||
|
||||
QString mName;
|
||||
QgsSymbol *mSymbol = nullptr;
|
||||
std::unique_ptr<QgsSymbol> mSymbol;
|
||||
};
|
||||
|
||||
class CORE_EXPORT QgsXyPlotSeries : public QgsAbstractPlotSeries
|
||||
@ -88,11 +92,16 @@ class CORE_EXPORT QgsXyPlotSeries : public QgsAbstractPlotSeries
|
||||
QgsXyPlotSeries() = default;
|
||||
~QgsXyPlotSeries() = default;
|
||||
|
||||
QList<QVariant> categories() const override;
|
||||
|
||||
QList<std::pair<QVariant, double>> data() const SIP_SKIP;
|
||||
void append( const QVariant &x, const double &y );
|
||||
void clear();
|
||||
|
||||
private:
|
||||
#ifdef SIP_RUN
|
||||
QgsXyPlotSeries( const QgsXyPlotSeries &other );
|
||||
#endif
|
||||
|
||||
QList<std::pair<QVariant, double>> mData;
|
||||
};
|
||||
@ -105,7 +114,7 @@ class CORE_EXPORT QgsPlotData
|
||||
~QgsPlotData();
|
||||
|
||||
QList<QgsAbstractPlotSeries *> series() const;
|
||||
void addSeries( QgsAbstractPlotSeries *series );
|
||||
void addSeries( QgsAbstractPlotSeries *series SIP_TRANSFER );
|
||||
void clearSeries();
|
||||
|
||||
private:
|
||||
|
Loading…
x
Reference in New Issue
Block a user