File indexing completed on 2024-12-15 04:02:35

0001 /*
0002  * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
0003  *
0004  * This file is part of the KD Chart library.
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "KChartRadarDiagram.h"
0010 #include "KChartRadarDiagram_p.h"
0011 
0012 #include "KChartPaintContext.h"
0013 #include "KChartPainterSaver_p.h"
0014 #include "KChartMath_p.h"
0015 
0016 #include <QPainter>
0017 
0018 using namespace KChart;
0019 
0020 RadarDiagram::Private::Private() :
0021     closeDatasets( false ),
0022     reverseData( false ),
0023     fillAlpha( 0.0 )
0024 {
0025 }
0026 
0027 RadarDiagram::Private::~Private() {}
0028 
0029 #define d d_func()
0030 
0031 RadarDiagram::RadarDiagram( QWidget* parent, RadarCoordinatePlane* plane ) :
0032     AbstractPolarDiagram( new Private( ), parent, plane )
0033 {
0034     //init();
0035 }
0036 
0037 RadarDiagram::~RadarDiagram()
0038 {
0039 
0040 }
0041 
0042 void RadarDiagram::init()
0043 {
0044 }
0045 
0046 
0047 RadarDiagram * RadarDiagram::clone() const
0048 {
0049     RadarDiagram* newDiagram = new RadarDiagram( new Private( *d ) );
0050     // This needs to be copied after the fact
0051     newDiagram->d->closeDatasets = d->closeDatasets;
0052     return newDiagram;
0053 }
0054 
0055 const QPair<QPointF, QPointF> RadarDiagram::calculateDataBoundaries () const
0056 {
0057     if ( !checkInvariants(true) ) return QPair<QPointF, QPointF>( QPointF( 0, 0 ), QPointF( 0, 0 ) );
0058     const int rowCount = model()->rowCount(rootIndex());
0059     const int colCount = model()->columnCount(rootIndex());
0060     qreal xMin = 0.0;
0061     qreal xMax = colCount;
0062     qreal yMin = 0, yMax = 0;
0063     for ( int iCol=0; iCol<colCount; ++iCol ) {
0064         for ( int iRow=0; iRow< rowCount; ++iRow ) {
0065             qreal value = model()->data( model()->index( iRow, iCol, rootIndex() ) ).toReal(); // checked
0066             yMax = qMax( yMax, value );
0067             yMin = qMin( yMin, value );
0068         }
0069     }
0070     QPointF bottomLeft ( QPointF( xMin, yMin ) );
0071     QPointF topRight ( QPointF( xMax, yMax ) );
0072     return QPair<QPointF, QPointF> ( bottomLeft,  topRight );
0073 }
0074 
0075 
0076 
0077 void RadarDiagram::paintEvent ( QPaintEvent*)
0078 {
0079     QPainter painter ( viewport() );
0080     PaintContext ctx;
0081     ctx.setPainter ( &painter );
0082     ctx.setRectangle( QRectF ( 0, 0, width(), height() ) );
0083     paint ( &ctx );
0084 }
0085 
0086 void RadarDiagram::paint( PaintContext* ctx )
0087 {
0088     qreal dummy1, dummy2;
0089     paint( ctx, true,  dummy1, dummy2 );
0090     paint( ctx, false, dummy1, dummy2 );
0091 }
0092 
0093 static qreal fitFontSizeToGeometry( const QString& text, const QFont& font, const QRectF& geometry, const TextAttributes& ta )
0094 {
0095     QFont f = font;
0096     const qreal origResult = f.pointSizeF();
0097     qreal result = origResult;
0098     const QSizeF mySize = geometry.size();
0099     if ( mySize.isNull() )
0100         return result;
0101 
0102     const QString t = text;
0103     QFontMetrics fm( f );
0104     while ( true )
0105     {
0106         const QSizeF textSize = rotatedRect( fm.boundingRect( t ), ta.rotation() ).normalized().size();
0107 
0108         if ( textSize.height() <= mySize.height() && textSize.width() <= mySize.width() )
0109             return result;
0110 
0111         result -= 0.5;
0112         if ( result <= 0.0 )
0113             return origResult;
0114         f.setPointSizeF( result );
0115         fm = QFontMetrics( f );
0116     }
0117 }
0118 
0119 static QPointF scaleToRealPosition( const QPointF& origin, const QRectF& sourceRect, const QRectF& destRect, const AbstractCoordinatePlane& plane )
0120 {
0121     QPointF result = plane.translate( origin );
0122     result -= sourceRect.topLeft();
0123     result.setX( result.x() / sourceRect.width() * destRect.width() );
0124     result.setY( result.y() / sourceRect.height() * destRect.height() );
0125     result += destRect.topLeft();
0126     return result;
0127 }
0128 
0129 void RadarDiagram::setReverseData( bool val )
0130 {
0131     d->reverseData = val;
0132 }
0133 bool RadarDiagram::reverseData()
0134 {
0135     return d->reverseData;
0136 }
0137 
0138 // local structure to remember the settings of a polygon inclusive the used color and pen.
0139 struct Polygon {
0140     QPolygonF polygon;
0141     QBrush brush;
0142     QPen pen;
0143     Polygon(const QPolygonF &polygon, const QBrush &brush, const QPen &pen) : polygon(polygon), brush(brush), pen(pen) {}
0144 };
0145 
0146 void RadarDiagram::paint( PaintContext* ctx,
0147                           bool calculateListAndReturnScale,
0148                           qreal& newZoomX, qreal& newZoomY )
0149 {
0150     // note: Not having any data model assigned is no bug
0151     //       but we can not draw a diagram then either.
0152     if ( !checkInvariants(true) )
0153         return;
0154     d->reverseMapper.clear();
0155 
0156     const int rowCount = model()->rowCount( rootIndex() );
0157     const int colCount = model()->columnCount( rootIndex() );
0158 
0159     int iRow, iCol;
0160 
0161     const qreal min = dataBoundaries().first.y();
0162     const qreal r = qAbs( min ) + dataBoundaries().second.y();
0163     const qreal step = ( r - qAbs( min ) ) / ( numberOfGridRings() );
0164     
0165     RadarCoordinatePlane* plane = dynamic_cast<RadarCoordinatePlane*>(ctx->coordinatePlane());
0166     TextAttributes ta = plane->textAttributes();
0167     QRectF fontRect = ctx->rectangle();    
0168     fontRect.setSize( QSizeF( fontRect.width(), step / 2.0 ) );
0169     const qreal labelFontSize = fitFontSizeToGeometry( QString::fromLatin1( "TestXYWQgqy" ), ta.font(), fontRect, ta );
0170     QFont labelFont = ta.font();
0171     ctx->painter()->setPen( ta.pen() );
0172     labelFont.setPointSizeF( labelFontSize );
0173     const QFontMetricsF metric( labelFont );
0174     const qreal labelHeight = metric.height();
0175     QRectF destRect = ctx->rectangle();    
0176     if ( ta.isVisible() )
0177     {        
0178         destRect.setY( destRect.y() + 2 * labelHeight );
0179         destRect.setHeight( destRect.height() - 4 * labelHeight );
0180     }
0181     
0182     if ( calculateListAndReturnScale ) {
0183         ctx->painter()->save();
0184         // Check if all of the data value texts / data comments will fit
0185         // into the available space:
0186         d->labelPaintCache.clear();
0187         ctx->painter()->save();
0188         for ( iCol=0; iCol < colCount; ++iCol ) {
0189             for ( iRow=0; iRow < rowCount; ++iRow ) {
0190                 QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked
0191                 const qreal value = model()->data( index ).toReal();
0192                 QPointF point = scaleToRealPosition( QPointF( value, iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() );
0193                 d->addLabel( &d->labelPaintCache, index, nullptr, PositionPoints( point ),
0194                              Position::Center, Position::Center, value );
0195             }
0196         }
0197         ctx->painter()->restore();
0198         const qreal oldZoomX = coordinatePlane()->zoomFactorX();
0199         const qreal oldZoomY = coordinatePlane()->zoomFactorY();
0200         newZoomX = oldZoomX;
0201         newZoomY = oldZoomY;
0202         if ( d->labelPaintCache.paintReplay.count() ) {
0203             QRectF txtRectF;
0204             d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true, true, &txtRectF );
0205             const QRect txtRect = txtRectF.toRect();
0206             const QRect curRect = coordinatePlane()->geometry();
0207             const qreal gapX = qMin( txtRect.left() - curRect.left(), curRect.right()  - txtRect.right() );
0208             const qreal gapY = qMin( txtRect.top()  - curRect.top(),  curRect.bottom() - txtRect.bottom() );
0209             newZoomX = oldZoomX;
0210             newZoomY = oldZoomY;
0211             if ( gapX < 0.0 )
0212                 newZoomX *= 1.0 + (gapX-1.0) / curRect.width();
0213             if ( gapY < 0.0 )
0214                 newZoomY *= 1.0 + (gapY-1.0) / curRect.height();
0215         }
0216         ctx->painter()->restore();
0217 
0218     } else {
0219         // Iterate through data sets and create a list of polygons out of them.
0220         QList<Polygon> polygons;
0221         for ( iCol=0; iCol < colCount; ++iCol ) {
0222             //TODO(khz): As of yet RadarDiagram can not show per-segment line attributes
0223             //           but it draws every polyline in one go - using one color.
0224             //           This needs to be enhanced to allow for cell-specific settings
0225             //           in the same way as LineDiagram does it.
0226             QPolygonF polygon;
0227             QPointF point0;
0228             for ( iRow=0; iRow < rowCount; ++iRow ) {
0229                 QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked
0230                 const qreal value = model()->data( index ).toReal();
0231                 QPointF point = scaleToRealPosition( QPointF( value, d->reverseData ? ( rowCount - iRow ) : iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() );
0232                 polygon.append( point );
0233                 if ( ! iRow )
0234                     point0= point;
0235             }
0236             if ( closeDatasets() && rowCount )
0237                 polygon.append( point0 );
0238 
0239             QBrush brush = d->datasetAttrs( iCol, KChart::DatasetBrushRole ).value<QBrush>();
0240             QPen p = d->datasetAttrs( iCol, KChart::DatasetPenRole ).value< QPen >();
0241             if ( p.style() != Qt::NoPen )
0242             {
0243                 polygons.append( Polygon( polygon, brush, PrintingParameters::scalePen( p ) ) );
0244             }
0245         }
0246 
0247         // first fill the areas with the brush-color and the defined alpha-value.
0248         if (d->fillAlpha > 0.0) {
0249             for (const Polygon& p : qAsConst(polygons)) {
0250                 PainterSaver painterSaver( ctx->painter() );
0251                 ctx->painter()->setRenderHint ( QPainter::Antialiasing );
0252                 QBrush br = p.brush;
0253                 QColor c = br.color();
0254                 c.setAlphaF(d->fillAlpha);
0255                 br.setColor(c);
0256                 ctx->painter()->setBrush( br );
0257                 ctx->painter()->setPen( p.pen );
0258                 ctx->painter()->drawPolygon( p.polygon );
0259             }
0260         }
0261 
0262         // then draw the poly-lines.
0263         for (const Polygon& p : qAsConst(polygons)) {
0264             PainterSaver painterSaver( ctx->painter() );
0265             ctx->painter()->setRenderHint ( QPainter::Antialiasing );
0266             ctx->painter()->setBrush( p.brush );
0267             ctx->painter()->setPen( p.pen );
0268             ctx->painter()->drawPolyline( p.polygon );
0269         }
0270 
0271         d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true );
0272     }
0273 }
0274 
0275 void RadarDiagram::resize ( const QSizeF& size )
0276 {
0277     AbstractPolarDiagram::resize( size );
0278 }
0279 
0280 /*virtual*/
0281 qreal RadarDiagram::valueTotals () const
0282 {
0283     return model()->rowCount(rootIndex());
0284 }
0285 
0286 /*virtual*/
0287 qreal RadarDiagram::numberOfValuesPerDataset() const
0288 {
0289     return model() ? model()->rowCount(rootIndex()) : 0.0;
0290 }
0291 
0292 /*virtual*/
0293 qreal RadarDiagram::numberOfGridRings() const
0294 {
0295     return 5; // FIXME
0296 }
0297 
0298 void RadarDiagram::setCloseDatasets( bool closeDatasets )
0299 {
0300     d->closeDatasets = closeDatasets;
0301 }
0302 
0303 bool RadarDiagram::closeDatasets() const
0304 {
0305     return d->closeDatasets;
0306 }
0307 
0308 qreal RadarDiagram::fillAlpha() const
0309 {
0310     return d->fillAlpha;
0311 }
0312 
0313 void RadarDiagram::setFillAlpha(qreal alphaF)
0314 {
0315     d->fillAlpha = alphaF;
0316 }
0317 
0318 void RadarDiagram::resizeEvent ( QResizeEvent*)
0319 {
0320 }