49 mNumericFormat = std::make_unique< QgsBasicNumericFormat >();
51 std::unique_ptr< QgsSimpleLineSymbolLayer > gridMinor = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20, 50 ), 0.1 );
52 gridMinor->setPenCapStyle( Qt::FlatCap );
53 mGridMinorSymbol = std::make_unique< QgsLineSymbol>(
QgsSymbolLayerList( { gridMinor.release() } ) );
55 std::unique_ptr< QgsSimpleLineSymbolLayer > gridMajor = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20, 150 ), 0.1 );
56 gridMajor->setPenCapStyle( Qt::FlatCap );
57 mGridMajorSymbol = std::make_unique< QgsLineSymbol>(
QgsSymbolLayerList( { gridMajor.release() } ) );
64 element.setAttribute( QStringLiteral(
"gridIntervalMinor" ),
qgsDoubleToString( mGridIntervalMinor ) );
65 element.setAttribute( QStringLiteral(
"gridIntervalMajor" ),
qgsDoubleToString( mGridIntervalMajor ) );
66 element.setAttribute( QStringLiteral(
"labelInterval" ),
qgsDoubleToString( mLabelInterval ) );
68 QDomElement numericFormatElement = document.createElement( QStringLiteral(
"numericFormat" ) );
69 mNumericFormat->writeXml( numericFormatElement, document, context );
70 element.appendChild( numericFormatElement );
72 QDomElement gridMajorElement = document.createElement( QStringLiteral(
"gridMajorSymbol" ) );
74 element.appendChild( gridMajorElement );
75 QDomElement gridMinorElement = document.createElement( QStringLiteral(
"gridMinorSymbol" ) );
77 element.appendChild( gridMinorElement );
79 QDomElement textFormatElement = document.createElement( QStringLiteral(
"textFormat" ) );
80 textFormatElement.appendChild( mLabelTextFormat.
writeXml( document, context ) );
81 element.appendChild( textFormatElement );
88 mGridIntervalMinor = element.attribute( QStringLiteral(
"gridIntervalMinor" ) ).toDouble();
89 mGridIntervalMajor = element.attribute( QStringLiteral(
"gridIntervalMajor" ) ).toDouble();
90 mLabelInterval = element.attribute( QStringLiteral(
"labelInterval" ) ).toDouble();
92 const QDomElement numericFormatElement = element.firstChildElement( QStringLiteral(
"numericFormat" ) );
95 const QDomElement gridMajorElement = element.firstChildElement( QStringLiteral(
"gridMajorSymbol" ) ).firstChildElement( QStringLiteral(
"symbol" ) );
96 mGridMajorSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( gridMajorElement, context ) );
97 const QDomElement gridMinorElement = element.firstChildElement( QStringLiteral(
"gridMinorSymbol" ) ).firstChildElement( QStringLiteral(
"symbol" ) );
98 mGridMinorSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( gridMinorElement, context ) );
100 const QDomElement textFormatElement = element.firstChildElement( QStringLiteral(
"textFormat" ) );
101 mLabelTextFormat.
readXml( textFormatElement, context );
108 return mNumericFormat.get();
113 mNumericFormat.reset( format );
118 return mGridMajorSymbol.get();
123 mGridMajorSymbol.reset( symbol );
128 return mGridMinorSymbol.get();
133 mGridMinorSymbol.reset( symbol );
138 return mLabelTextFormat;
143 mLabelTextFormat = format;
152 : mMargins( 2, 2, 2, 2 )
155 std::unique_ptr< QgsSimpleFillSymbolLayer > chartFill = std::make_unique< QgsSimpleFillSymbolLayer >( QColor( 255, 255, 255 ) );
156 mChartBackgroundSymbol = std::make_unique< QgsFillSymbol>(
QgsSymbolLayerList( { chartFill.release() } ) );
158 std::unique_ptr< QgsSimpleLineSymbolLayer > chartBorder = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 20, 20, 20 ), 0.1 );
159 mChartBorderSymbol = std::make_unique< QgsFillSymbol>(
QgsSymbolLayerList( { chartBorder.release() } ) );
171 QDomElement xAxisElement = document.createElement( QStringLiteral(
"xAxis" ) );
172 mXAxis.
writeXml( xAxisElement, document, context );
173 element.appendChild( xAxisElement );
174 QDomElement yAxisElement = document.createElement( QStringLiteral(
"yAxis" ) );
175 mYAxis.
writeXml( yAxisElement, document, context );
176 element.appendChild( yAxisElement );
178 QDomElement backgroundElement = document.createElement( QStringLiteral(
"backgroundSymbol" ) );
180 element.appendChild( backgroundElement );
181 QDomElement borderElement = document.createElement( QStringLiteral(
"borderSymbol" ) );
183 element.appendChild( borderElement );
185 element.setAttribute( QStringLiteral(
"margins" ), mMargins.
toString() );
194 mMinX = element.attribute( QStringLiteral(
"minX" ) ).toDouble();
195 mMaxX = element.attribute( QStringLiteral(
"maxX" ) ).toDouble();
196 mMinY = element.attribute( QStringLiteral(
"minY" ) ).toDouble();
197 mMaxY = element.attribute( QStringLiteral(
"maxY" ) ).toDouble();
199 const QDomElement xAxisElement = element.firstChildElement( QStringLiteral(
"xAxis" ) );
200 mXAxis.
readXml( xAxisElement, context );
201 const QDomElement yAxisElement = element.firstChildElement( QStringLiteral(
"yAxis" ) );
202 mYAxis.
readXml( yAxisElement, context );
204 const QDomElement backgroundElement = element.firstChildElement( QStringLiteral(
"backgroundSymbol" ) ).firstChildElement( QStringLiteral(
"symbol" ) );
205 mChartBackgroundSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( backgroundElement, context ) );
206 const QDomElement borderElement = element.firstChildElement( QStringLiteral(
"borderSymbol" ) ).firstChildElement( QStringLiteral(
"symbol" ) );
207 mChartBorderSymbol.reset( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( borderElement, context ) );
219 mChartBackgroundSymbol->startRender( context );
220 mChartBorderSymbol->startRender( context );
241 double maxYAxisLabelWidth = 0;
243 for (
double currentY = firstYLabel; currentY <= mMaxY && !
qgsDoubleNear( currentY, mMaxY, yTolerance ); currentY += mYAxis.
labelInterval() )
250 const double chartAreaLeft = plotArea.left();
251 const double chartAreaRight = plotArea.right();
252 const double chartAreaTop = plotArea.top();
253 const double chartAreaBottom = plotArea.bottom();
256 mChartBackgroundSymbol->renderPolygon( QPolygonF(
258 QPointF( chartAreaLeft, chartAreaTop ),
259 QPointF( chartAreaRight, chartAreaTop ),
260 QPointF( chartAreaRight, chartAreaBottom ),
261 QPointF( chartAreaLeft, chartAreaBottom ),
262 QPointF( chartAreaLeft, chartAreaTop )
263 } ),
nullptr,
nullptr, context );
265 const double xScale = ( chartAreaRight - chartAreaLeft ) / ( mMaxX - mMinX );
266 const double yScale = ( chartAreaBottom - chartAreaTop ) / ( mMaxY - mMinY );
268 constexpr
int MAX_OBJECTS = 1000;
274 double nextMajorXGrid = firstMajorXGrid;
275 int objectNumber = 0;
276 for (
double currentX = firstMinorXGrid; objectNumber < MAX_OBJECTS && ( currentX <= mMaxX && !
qgsDoubleNear( currentX, mMaxX, xTolerance ) ); currentX += mXAxis.
gridIntervalMinor(), ++objectNumber )
291 QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, chartAreaBottom ),
292 QPointF( ( currentX - mMinX ) * xScale + chartAreaLeft, chartAreaTop )
293 } ),
nullptr, context );
298 double nextMajorYGrid = firstMajorYGrid;
300 for (
double currentY = firstMinorYGrid; objectNumber < MAX_OBJECTS && ( currentY <= mMaxY && !
qgsDoubleNear( currentY, mMaxY, yTolerance ) ); currentY += mYAxis.
gridIntervalMinor(), ++objectNumber )
315 QPointF( chartAreaLeft, chartAreaBottom - ( currentY - mMinY ) * yScale ),
316 QPointF( chartAreaRight, chartAreaBottom - ( currentY - mMinY ) * yScale )
317 } ),
nullptr, context );
325 for (
double currentX = firstXLabel; objectNumber < MAX_OBJECTS && ( currentX <= mMaxX ||
qgsDoubleNear( currentX, mMaxX, xTolerance ) ); currentX += mXAxis.
labelInterval(), ++objectNumber )
336 for (
double currentY = firstYLabel; objectNumber < MAX_OBJECTS && ( currentY <= mMaxY ||
qgsDoubleNear( currentY, mMaxY, yTolerance ) ); currentY += mYAxis.
labelInterval(), ++objectNumber )
343 chartAreaBottom - ( currentY - mMinY ) * yScale + height / 2 ),
351 mChartBorderSymbol->renderPolygon( QPolygonF(
353 QPointF( chartAreaLeft, chartAreaTop ),
354 QPointF( chartAreaRight, chartAreaTop ),
355 QPointF( chartAreaRight, chartAreaBottom ),
356 QPointF( chartAreaLeft, chartAreaBottom ),
357 QPointF( chartAreaLeft, chartAreaTop )
358 } ),
nullptr,
nullptr, context );
360 mChartBackgroundSymbol->stopRender( context );
361 mChartBorderSymbol->stopRender( context );
398 constexpr
int MAX_LABELS = 1000;
402 double maxXAxisLabelHeight = 0;
404 for (
double currentX = firstXLabel; labelNumber < MAX_LABELS && ( currentX <= mMaxX ||
qgsDoubleNear( currentX, mMaxX, xTolerance ) ); currentX += mXAxis.
labelInterval(), labelNumber++ )
411 double maxYAxisLabelWidth = 0;
414 for (
double currentY = firstMinorYGrid; labelNumber < MAX_LABELS && ( currentY <= mMaxY ||
qgsDoubleNear( currentY, mMaxY, yTolerance ) ); currentY += mYAxis.
gridIntervalMinor(), labelNumber ++ )
422 const double rightTextSize = 0;
424 const double topTextSize = 0;
431 return QRectF( leftMargin, topMargin, mSize.width() - rightMargin - leftMargin, mSize.height() - bottomMargin - topMargin );
437 constexpr
double IDEAL_WIDTH = 0.4;
438 constexpr
double TOLERANCE = 0.04;
439 constexpr
int MAX_LABELS = 1000;
446 const double availableWidth = mSize.width() - leftMargin - rightMargin;
447 const double availableHeight = mSize.height() - topMargin - bottomMargin;
451 auto refineIntervalForAxis = [&](
double axisMinimum,
double axisMaximum,
452 const std::function< double(
double ) > &sizeForLabel,
453 double availableSize,
double idealSizePercent,
double sizeTolerancePercent,
454 double & labelInterval,
double & majorInterval,
double & minorInterval )
456 auto roundBase10 = [](
double value )->
double
458 return std::pow( 10, std::floor( std::log10( value ) ) );
462 double totalSize = 0;
463 int initialLabelCount = 0;
465 const double firstLabelPos = std::ceil( axisMinimum / labelInterval ) * labelInterval;
467 for (
double currentPos = firstLabelPos; initialLabelCount <= MAX_LABELS && currentPos <= axisMaximum; currentPos += labelInterval, ++initialLabelCount )
469 totalSize += sizeForLabel( currentPos );
474 if ( initialLabelCount >= MAX_LABELS || ( totalSize / availableSize < ( idealSizePercent - sizeTolerancePercent ) ) || ( totalSize / availableSize > ( idealSizePercent + sizeTolerancePercent ) ) )
477 int numberLabelsInitial = std::floor( availableSize / 30 );
479 double labelIntervalTest = ( axisMaximum - axisMinimum ) / numberLabelsInitial;
480 double baseValue = roundBase10( labelIntervalTest );
481 double candidate = baseValue;
482 int currentMultiplier = 1;
484 int numberLabels = 0;
487 const double firstLabelPosition = std::ceil( axisMinimum / candidate ) * candidate;
488 double totalSize = 0;
490 for (
double currentPos = firstLabelPosition; currentPos <= axisMaximum; currentPos += candidate )
492 totalSize += sizeForLabel( currentPos );
495 if ( numberLabels > MAX_LABELS )
499 if ( numberLabels <= MAX_LABELS && totalSize <= availableSize * idealSizePercent )
502 if ( currentMultiplier == 1 )
503 currentMultiplier = 2;
504 else if ( currentMultiplier == 2 )
505 currentMultiplier = 5;
506 else if ( currentMultiplier == 5 )
509 currentMultiplier = 1;
512 candidate = baseValue * currentMultiplier;
514 labelInterval = candidate;
515 if ( numberLabels < 10 )
517 minorInterval = labelInterval / 2;
518 majorInterval = minorInterval * 4;
522 minorInterval = labelInterval;
523 majorInterval = minorInterval * 5;
532 refineIntervalForAxis( mMinX, mMaxX, [ = ](
double position ) ->
double
534 const QString text = mXAxis.
numericFormat()->formatDouble( position, numericContext );
538 IDEAL_WIDTH, TOLERANCE, labelIntervalX, majorIntervalX, minorIntervalX );
548 refineIntervalForAxis( mMinY, mMaxY, [ = ](
double position ) ->
double
550 const QString text = mYAxis.
numericFormat()->formatDouble( position, numericContext );
553 IDEAL_WIDTH, TOLERANCE, labelIntervalY, majorIntervalY, minorIntervalY );
562 return mChartBackgroundSymbol.get();
567 mChartBackgroundSymbol.reset( symbol );
572 return mChartBorderSymbol.get();
577 mChartBorderSymbol.reset( symbol );