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 }