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 );