KD Chart 2
[rev.2.5]
|
00001 /**************************************************************************** 00002 ** Copyright (C) 2001-2012 Klaralvdalens Datakonsult AB. All rights reserved. 00003 ** 00004 ** This file is part of the KD Chart library. 00005 ** 00006 ** Licensees holding valid commercial KD Chart licenses may use this file in 00007 ** accordance with the KD Chart Commercial License Agreement provided with 00008 ** the Software. 00009 ** 00010 ** 00011 ** This file may be distributed and/or modified under the terms of the 00012 ** GNU General Public License version 2 and version 3 as published by the 00013 ** Free Software Foundation and appearing in the file LICENSE.GPL included. 00014 ** 00015 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE 00016 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 00017 ** 00018 ** Contact info@kdab.com if any conditions of this licensing are not 00019 ** clear to you. 00020 ** 00021 **********************************************************************/ 00022 00023 #include "KDChartRadarDiagram.h" 00024 #include "KDChartRadarDiagram_p.h" 00025 00026 #include <QPainter> 00027 #include "KDChartAttributesModel.h" 00028 #include "KDChartPaintContext.h" 00029 #include "KDChartPainterSaver_p.h" 00030 #include "KDChartDataValueAttributes.h" 00031 00032 #include <KDABLibFakes> 00033 00034 using namespace KDChart; 00035 00036 RadarDiagram::Private::Private() : 00037 closeDatasets( false ), 00038 reverseData( false ), 00039 fillAlpha( 0.0 ) 00040 { 00041 } 00042 00043 RadarDiagram::Private::~Private() {} 00044 00045 #define d d_func() 00046 00047 RadarDiagram::RadarDiagram( QWidget* parent, RadarCoordinatePlane* plane ) : 00048 AbstractPolarDiagram( new Private( ), parent, plane ) 00049 { 00050 //init(); 00051 } 00052 00053 RadarDiagram::~RadarDiagram() 00054 { 00055 00056 } 00057 00058 void RadarDiagram::init() 00059 { 00060 } 00061 00062 00066 RadarDiagram * RadarDiagram::clone() const 00067 { 00068 RadarDiagram* newDiagram = new RadarDiagram( new Private( *d ) ); 00069 // This needs to be copied after the fact 00070 newDiagram->d->closeDatasets = d->closeDatasets; 00071 return newDiagram; 00072 } 00073 00074 const QPair<QPointF, QPointF> RadarDiagram::calculateDataBoundaries () const 00075 { 00076 if ( !checkInvariants(true) ) return QPair<QPointF, QPointF>( QPointF( 0, 0 ), QPointF( 0, 0 ) ); 00077 const int rowCount = model()->rowCount(rootIndex()); 00078 const int colCount = model()->columnCount(rootIndex()); 00079 qreal xMin = 0.0; 00080 qreal xMax = colCount; 00081 qreal yMin = 0, yMax = 0; 00082 for ( int iCol=0; iCol<colCount; ++iCol ) { 00083 for ( int iRow=0; iRow< rowCount; ++iRow ) { 00084 qreal value = model()->data( model()->index( iRow, iCol, rootIndex() ) ).toReal(); // checked 00085 yMax = qMax( yMax, value ); 00086 yMin = qMin( yMin, value ); 00087 } 00088 } 00089 QPointF bottomLeft ( QPointF( xMin, yMin ) ); 00090 QPointF topRight ( QPointF( xMax, yMax ) ); 00091 return QPair<QPointF, QPointF> ( bottomLeft, topRight ); 00092 } 00093 00094 00095 00096 void RadarDiagram::paintEvent ( QPaintEvent*) 00097 { 00098 QPainter painter ( viewport() ); 00099 PaintContext ctx; 00100 ctx.setPainter ( &painter ); 00101 ctx.setRectangle( QRectF ( 0, 0, width(), height() ) ); 00102 paint ( &ctx ); 00103 } 00104 00105 void RadarDiagram::paint( PaintContext* ctx ) 00106 { 00107 qreal dummy1, dummy2; 00108 paint( ctx, true, dummy1, dummy2 ); 00109 paint( ctx, false, dummy1, dummy2 ); 00110 } 00111 00112 static qreal fitFontSizeToGeometry( const QString& text, const QFont& font, const QRectF& geometry, const TextAttributes& ta ) 00113 { 00114 QFont f = font; 00115 const qreal origResult = f.pointSizeF(); 00116 qreal result = origResult; 00117 const QSizeF mySize = geometry.size(); 00118 if( mySize.isNull() ) 00119 return result; 00120 00121 const QString t = text; 00122 QFontMetrics fm( f ); 00123 while( true ) 00124 { 00125 const QSizeF textSize = rotatedRect( fm.boundingRect( t ), ta.rotation() ).normalized().size(); 00126 00127 if( textSize.height() <= mySize.height() && textSize.width() <= mySize.width() ) 00128 return result; 00129 00130 result -= 0.5; 00131 if( result <= 0.0 ) 00132 return origResult; 00133 f.setPointSizeF( result ); 00134 fm = QFontMetrics( f ); 00135 } 00136 } 00137 00138 static QPointF scaleToRealPosition( const QPointF& origin, const QRectF& sourceRect, const QRectF& destRect, const AbstractCoordinatePlane& plane ) 00139 { 00140 QPointF result = plane.translate( origin ); 00141 result -= sourceRect.topLeft(); 00142 result.setX( result.x() / sourceRect.width() * destRect.width() ); 00143 result.setY( result.y() / sourceRect.height() * destRect.height() ); 00144 result += destRect.topLeft(); 00145 return result; 00146 } 00147 00148 void RadarDiagram::setReverseData( bool val ) 00149 { 00150 d->reverseData = val; 00151 } 00152 bool RadarDiagram::reverseData() 00153 { 00154 return d->reverseData; 00155 } 00156 00157 // local structure to remember the settings of a polygon inclusive the used color and pen. 00158 struct Polygon { 00159 QPolygonF polygon; 00160 QBrush brush; 00161 QPen pen; 00162 Polygon(const QPolygonF &polygon, const QBrush &brush, const QPen &pen) : polygon(polygon), brush(brush), pen(pen) {} 00163 }; 00164 00165 void RadarDiagram::paint( PaintContext* ctx, 00166 bool calculateListAndReturnScale, 00167 qreal& newZoomX, qreal& newZoomY ) 00168 { 00169 // note: Not having any data model assigned is no bug 00170 // but we can not draw a diagram then either. 00171 if ( !checkInvariants(true) ) 00172 return; 00173 d->reverseMapper.clear(); 00174 00175 const int rowCount = model()->rowCount( rootIndex() ); 00176 const int colCount = model()->columnCount( rootIndex() ); 00177 00178 int iRow, iCol; 00179 00180 const qreal min = dataBoundaries().first.y(); 00181 const qreal r = qAbs( min ) + dataBoundaries().second.y(); 00182 const qreal step = ( r - qAbs( min ) ) / ( numberOfGridRings() ); 00183 00184 RadarCoordinatePlane* plane = dynamic_cast<RadarCoordinatePlane*>(ctx->coordinatePlane()); 00185 TextAttributes ta = plane->textAttributes(); 00186 QRectF fontRect = ctx->rectangle(); 00187 fontRect.setSize( QSizeF( fontRect.width(), step / 2.0 ) ); 00188 const qreal labelFontSize = fitFontSizeToGeometry( QString::fromLatin1( "TestXYWQgqy" ), ta.font(), fontRect, ta ); 00189 QFont labelFont = ta.font(); 00190 ctx->painter()->setPen( ta.pen() ); 00191 labelFont.setPointSizeF( labelFontSize ); 00192 const QFontMetricsF metric( labelFont ); 00193 const qreal labelHeight = metric.height(); 00194 QPointF offset; 00195 QRectF destRect = ctx->rectangle(); 00196 if ( ta.isVisible() ) 00197 { 00198 destRect.setY( destRect.y() + 2 * labelHeight ); 00199 destRect.setHeight( destRect.height() - 4 * labelHeight ); 00200 } 00201 00202 if( calculateListAndReturnScale ){ 00203 ctx->painter()->save(); 00204 // Check if all of the data value texts / data comments will fit 00205 // into the available space: 00206 d->labelPaintCache.clear(); 00207 ctx->painter()->save(); 00208 for ( iCol=0; iCol < colCount; ++iCol ) { 00209 for ( iRow=0; iRow < rowCount; ++iRow ) { 00210 QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked 00211 const qreal value = model()->data( index ).toReal(); 00212 QPointF point = scaleToRealPosition( QPointF( value, iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() ); 00213 d->addLabel( &d->labelPaintCache, index, 0, PositionPoints( point ), 00214 Position::Center, Position::Center, value ); 00215 } 00216 } 00217 ctx->painter()->restore(); 00218 const qreal oldZoomX = coordinatePlane()->zoomFactorX(); 00219 const qreal oldZoomY = coordinatePlane()->zoomFactorY(); 00220 newZoomX = oldZoomX; 00221 newZoomY = oldZoomY; 00222 if( d->labelPaintCache.paintReplay.count() ) { 00223 QRectF txtRectF; 00224 d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true, true, &txtRectF ); 00225 const QRect txtRect = txtRectF.toRect(); 00226 const QRect curRect = coordinatePlane()->geometry(); 00227 const qreal gapX = qMin( txtRect.left() - curRect.left(), curRect.right() - txtRect.right() ); 00228 const qreal gapY = qMin( txtRect.top() - curRect.top(), curRect.bottom() - txtRect.bottom() ); 00229 newZoomX = oldZoomX; 00230 newZoomY = oldZoomY; 00231 if( gapX < 0.0 ) 00232 newZoomX *= 1.0 + (gapX-1.0) / curRect.width(); 00233 if( gapY < 0.0 ) 00234 newZoomY *= 1.0 + (gapY-1.0) / curRect.height(); 00235 } 00236 ctx->painter()->restore(); 00237 00238 }else{ 00239 // Iterate through data sets and create a list of polygons out of them. 00240 QList<Polygon> polygons; 00241 for ( iCol=0; iCol < colCount; ++iCol ) { 00242 //TODO(khz): As of yet RadarDiagram can not show per-segment line attributes 00243 // but it draws every polyline in one go - using one color. 00244 // This needs to be enhanced to allow for cell-specific settings 00245 // in the same way as LineDiagram does it. 00246 QPolygonF polygon; 00247 QPointF point0; 00248 for ( iRow=0; iRow < rowCount; ++iRow ) { 00249 QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked 00250 const qreal value = model()->data( index ).toReal(); 00251 QPointF point = scaleToRealPosition( QPointF( value, d->reverseData ? ( rowCount - iRow ) : iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() ); 00252 polygon.append( point ); 00253 if( ! iRow ) 00254 point0= point; 00255 } 00256 if( closeDatasets() && rowCount ) 00257 polygon.append( point0 ); 00258 00259 QBrush brush = qVariantValue<QBrush>( d->datasetAttrs( iCol, KDChart::DatasetBrushRole ) ); 00260 QPen p( model()->headerData( iCol, Qt::Horizontal, KDChart::DatasetPenRole ).value< QPen >() ); 00261 if ( p.style() != Qt::NoPen ) 00262 { 00263 polygons.append( Polygon(polygon, brush, PrintingParameters::scalePen( p )) ); 00264 } 00265 } 00266 00267 // first fill the areas with the brush-color and the defined alpha-value. 00268 if (d->fillAlpha > 0.0) { 00269 Q_FOREACH(const Polygon& p, polygons) { 00270 PainterSaver painterSaver( ctx->painter() ); 00271 ctx->painter()->setRenderHint ( QPainter::Antialiasing ); 00272 QBrush br = p.brush; 00273 QColor c = br.color(); 00274 c.setAlphaF(d->fillAlpha); 00275 br.setColor(c); 00276 ctx->painter()->setBrush( br ); 00277 ctx->painter()->setPen( p.pen ); 00278 ctx->painter()->drawPolygon( p.polygon ); 00279 } 00280 } 00281 00282 // then draw the poly-lines. 00283 Q_FOREACH(const Polygon& p, polygons) { 00284 PainterSaver painterSaver( ctx->painter() ); 00285 ctx->painter()->setRenderHint ( QPainter::Antialiasing ); 00286 ctx->painter()->setBrush( p.brush ); 00287 ctx->painter()->setPen( p.pen ); 00288 ctx->painter()->drawPolyline( p.polygon ); 00289 } 00290 00291 d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true ); 00292 } 00293 } 00294 00295 void RadarDiagram::resize ( const QSizeF& ) 00296 { 00297 } 00298 00299 /*virtual*/ 00300 qreal RadarDiagram::valueTotals () const 00301 { 00302 return model()->rowCount(rootIndex()); 00303 } 00304 00305 /*virtual*/ 00306 qreal RadarDiagram::numberOfValuesPerDataset() const 00307 { 00308 return model() ? model()->rowCount(rootIndex()) : 0.0; 00309 } 00310 00311 /*virtual*/ 00312 qreal RadarDiagram::numberOfGridRings() const 00313 { 00314 return 5; // FIXME 00315 } 00316 00317 void RadarDiagram::setCloseDatasets( bool closeDatasets ) 00318 { 00319 d->closeDatasets = closeDatasets; 00320 } 00321 00322 bool RadarDiagram::closeDatasets() const 00323 { 00324 return d->closeDatasets; 00325 } 00326 00327 qreal RadarDiagram::fillAlpha() const 00328 { 00329 return d->fillAlpha; 00330 } 00331 00332 void RadarDiagram::setFillAlpha(qreal alphaF) 00333 { 00334 d->fillAlpha = alphaF; 00335 } 00336 00337 void RadarDiagram::resizeEvent ( QResizeEvent*) 00338 { 00339 } 00340 00341