KD Chart 2 [rev.2.4]
|
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.txt 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 "KDChartPolarCoordinatePlane.h" 00024 #include "KDChartPolarCoordinatePlane_p.h" 00025 00026 #include "KDChartPainterSaver_p.h" 00027 #include "KDChartChart.h" 00028 #include "KDChartPaintContext.h" 00029 #include "KDChartAbstractDiagram.h" 00030 #include "KDChartAbstractPolarDiagram.h" 00031 #include "KDChartPolarDiagram.h" 00032 00033 #include <math.h> 00034 00035 #include <QFont> 00036 #include <QList> 00037 #include <QtDebug> 00038 #include <QPainter> 00039 #include <QTimer> 00040 00041 #include <KDABLibFakes> 00042 00043 using namespace KDChart; 00044 00045 #define d d_func() 00046 00047 00048 /* 00049 #ifndef M_PI 00050 #define M_PI 3.14159265358979323846 00051 #endif 00052 #define DEGTORAD(d) (d)*M_PI/180 00053 00054 struct PolarCoordinatePlane::CoordinateTransformation 00055 { 00056 // represents the distance of the diagram coordinate origin to the 00057 // origin of the coordinate plane space: 00058 QPointF originTranslation; 00059 double radiusUnit; 00060 double angleUnit; 00061 00062 ZoomParameters zoom; 00063 00064 static QPointF polarToCartesian( double R, double theta ) 00065 { 00066 return QPointF( R * cos( DEGTORAD( theta ) ), R * sin( DEGTORAD( theta ) ) ); 00067 } 00068 00069 inline const QPointF translate( const QPointF& diagramPoint ) const 00070 { 00071 // calculate the polar coordinates 00072 const double x = diagramPoint.x() * radiusUnit; 00073 const double y = ( diagramPoint.y() * angleUnit) - 90; 00074 // convert to cartesian coordinates 00075 QPointF cartesianPoint = polarToCartesian( x, y ); 00076 cartesianPoint.setX( cartesianPoint.x() * zoom.xFactor ); 00077 cartesianPoint.setY( cartesianPoint.y() * zoom.yFactor ); 00078 00079 QPointF newOrigin = originTranslation; 00080 double minOrigin = qMin( newOrigin.x(), newOrigin.y() ); 00081 newOrigin.setX( newOrigin.x() + minOrigin * ( 1 - zoom.xCenter * 2 ) * zoom.xFactor ); 00082 newOrigin.setY( newOrigin.y() + minOrigin * ( 1 - zoom.yCenter * 2 ) * zoom.yFactor ); 00083 00084 return newOrigin + cartesianPoint; 00085 } 00086 00087 inline const QPointF translatePolar( const QPointF& diagramPoint ) const 00088 { 00089 return QPointF( diagramPoint.x() * angleUnit, diagramPoint.y() * radiusUnit ); 00090 } 00091 }; 00092 00093 class PolarCoordinatePlane::Private 00094 { 00095 public: 00096 Private() 00097 :currentTransformation(0), 00098 initialResizeEventReceived(false ) 00099 {} 00100 00101 00102 // the coordinate plane will calculate coordinate transformations for all 00103 // diagrams and store them here: 00104 CoordinateTransformationList coordinateTransformations; 00105 // when painting, this pointer selects the coordinate transformation for 00106 // the current diagram: 00107 CoordinateTransformation* currentTransformation; 00108 // the reactangle occupied by the diagrams, in plane coordinates 00109 QRectF contentRect; 00110 // true after the first resize event came in 00111 bool initialResizeEventReceived; 00112 }; 00113 */ 00114 00115 PolarCoordinatePlane::PolarCoordinatePlane ( Chart* parent ) 00116 : AbstractCoordinatePlane ( new Private(), parent ) 00117 { 00118 // this bloc left empty intentionally 00119 } 00120 00121 PolarCoordinatePlane::~PolarCoordinatePlane() 00122 { 00123 // this bloc left empty intentionally 00124 } 00125 00126 void PolarCoordinatePlane::init() 00127 { 00128 // this bloc left empty intentionally 00129 } 00130 00131 void PolarCoordinatePlane::addDiagram ( AbstractDiagram* diagram ) 00132 { 00133 Q_ASSERT_X ( dynamic_cast<AbstractPolarDiagram*> ( diagram ), 00134 "PolarCoordinatePlane::addDiagram", "Only polar" 00135 "diagrams can be added to a polar coordinate plane!" ); 00136 AbstractCoordinatePlane::addDiagram ( diagram ); 00137 connect ( diagram, SIGNAL ( layoutChanged ( AbstractDiagram* ) ), 00138 SLOT ( slotLayoutChanged ( AbstractDiagram* ) ) ); 00139 00140 } 00141 00142 void PolarCoordinatePlane::paint ( QPainter* painter ) 00143 { 00144 AbstractDiagramList diags = diagrams(); 00145 if ( d->coordinateTransformations.size() == diags.size() ) 00146 { 00147 PaintContext ctx; 00148 ctx.setPainter ( painter ); 00149 ctx.setCoordinatePlane ( this ); 00150 ctx.setRectangle ( geometry() /*d->contentRect*/ ); 00151 00152 // 1. ask the diagrams if they need additional space for data labels / data comments 00153 const qreal oldZoomX = zoomFactorX(); 00154 const qreal oldZoomY = zoomFactorY(); 00155 d->newZoomX = oldZoomX; 00156 d->newZoomY = oldZoomY; 00157 for ( int i = 0; i < diags.size(); i++ ) 00158 { 00159 d->currentTransformation = & ( d->coordinateTransformations[i] ); 00160 qreal zoomX; 00161 qreal zoomY; 00162 PolarDiagram* polarDia = dynamic_cast<PolarDiagram*> ( diags[i] ); 00163 if( polarDia ){ 00164 polarDia->paint ( &ctx, true, zoomX, zoomY ); 00165 d->newZoomX = qMin(d->newZoomX, zoomX); 00166 d->newZoomY = qMin(d->newZoomY, zoomY); 00167 } 00168 } 00169 d->currentTransformation = 0; 00170 00171 // if re-scaling is needed start the timer and bail out 00172 if( d->newZoomX != oldZoomX || d->newZoomY != oldZoomY ){ 00173 //qDebug()<<"new zoom:"<<d->newZoomY<<" old zoom"<<oldZoomY; 00174 QTimer::singleShot(10, this, SLOT(adjustZoomAndRepaint())); 00175 return; 00176 } 00177 00178 // 2. there was room enough for the labels, so we start drawing 00179 00180 // paint the coordinate system rulers: 00181 d->currentTransformation = & ( d->coordinateTransformations.first() ); 00182 00183 d->grid->drawGrid( &ctx ); 00184 00185 // paint the diagrams which will re-use their DataValueTextInfoList(s) filled in step 1: 00186 for ( int i = 0; i < diags.size(); i++ ) 00187 { 00188 d->currentTransformation = & ( d->coordinateTransformations[i] ); 00189 PainterSaver painterSaver( painter ); 00190 PolarDiagram* polarDia = dynamic_cast<PolarDiagram*> ( diags[i] ); 00191 if( polarDia ){ 00192 qreal dummy1, dummy2; 00193 polarDia->paint ( &ctx, false, dummy1, dummy2 ); 00194 }else{ 00195 diags[i]->paint ( &ctx ); 00196 } 00197 } 00198 d->currentTransformation = 0; 00199 } // else: diagrams have not been set up yet 00200 } 00201 00202 00203 void PolarCoordinatePlane::adjustZoomAndRepaint() 00204 { 00205 const qreal newZoom = qMin(d->newZoomX, d->newZoomY); 00206 setZoomFactors(newZoom, newZoom); 00207 update(); 00208 } 00209 00210 00211 void PolarCoordinatePlane::resizeEvent ( QResizeEvent* ) 00212 { 00213 d->initialResizeEventReceived = true; 00214 layoutDiagrams(); 00215 } 00216 00217 void PolarCoordinatePlane::layoutDiagrams() 00218 { 00219 // the rectangle the diagrams cover in the *plane*: 00220 // (Why -3? We save 1px on each side for the antialiased drawing, and 00221 // respect the way QPainter calculates the width of a painted rect (the 00222 // size is the rectangle size plus the pen width). This way, most clipping 00223 // for regular pens should be avoided. When pens with a penWidth or larger 00224 // than 1 are used, this may not b sufficient. 00225 const QRect rect( areaGeometry() ); 00226 d->contentRect = QRectF ( 1, 1, rect.width() - 3, rect.height() - 3 ); 00227 00228 const ZoomParameters zoom = d->coordinateTransformations.isEmpty() ? ZoomParameters() 00229 : d->coordinateTransformations.front().zoom; 00230 // FIXME distribute space according to options: 00231 const qreal oldStartPosition = startPosition(); 00232 d->coordinateTransformations.clear(); 00233 Q_FOREACH( AbstractDiagram* diagram, diagrams() ) 00234 { 00235 AbstractPolarDiagram *polarDiagram = dynamic_cast<AbstractPolarDiagram*>( diagram ); 00236 Q_ASSERT( polarDiagram ); 00237 QPair<QPointF, QPointF> dataBoundariesPair = polarDiagram->dataBoundaries(); 00238 00239 const double angleUnit = 360 / polarDiagram->valueTotals(); 00240 //qDebug() << "--------------------------------------------------------"; 00241 const double radius = qAbs( dataBoundariesPair.first.y() ) + dataBoundariesPair.second.y(); 00242 //qDebug() << radius <<"="<<dataBoundariesPair.second.y(); 00243 const double diagramWidth = radius * 2; // == height 00244 const double planeWidth = d->contentRect.width(); 00245 const double planeHeight = d->contentRect.height(); 00246 const double radiusUnit = qMin( planeWidth, planeHeight ) / diagramWidth; 00247 //qDebug() << radiusUnit <<"=" << "qMin( "<<planeWidth<<","<< planeHeight <<") / "<<diagramWidth; 00248 QPointF coordinateOrigin = QPointF ( planeWidth / 2, planeHeight / 2 ); 00249 coordinateOrigin += d->contentRect.topLeft(); 00250 00251 CoordinateTransformation diagramTransposition; 00252 diagramTransposition.originTranslation = coordinateOrigin; 00253 diagramTransposition.radiusUnit = radiusUnit; 00254 diagramTransposition.angleUnit = angleUnit; 00255 diagramTransposition.startPosition = oldStartPosition; 00256 diagramTransposition.zoom = zoom; 00257 diagramTransposition.minValue = dataBoundariesPair.first.y() < 0 ? dataBoundariesPair.first.y() : 0.0; 00258 d->coordinateTransformations.append( diagramTransposition ); 00259 } 00260 } 00261 00262 const QPointF PolarCoordinatePlane::translate( const QPointF& diagramPoint ) const 00263 { 00264 Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::translate", 00265 "Only call translate() from within paint()." ); 00266 return d->currentTransformation->translate ( diagramPoint ); 00267 } 00268 00269 const QPointF PolarCoordinatePlane::translatePolar( const QPointF& diagramPoint ) const 00270 { 00271 Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::translate", 00272 "Only call translate() from within paint()." ); 00273 return d->currentTransformation->translatePolar ( diagramPoint ); 00274 } 00275 00276 qreal PolarCoordinatePlane::angleUnit() const 00277 { 00278 Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::angleUnit", 00279 "Only call angleUnit() from within paint()." ); 00280 return d->currentTransformation->angleUnit; 00281 } 00282 00283 qreal PolarCoordinatePlane::radiusUnit() const 00284 { 00285 Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::radiusUnit", 00286 "Only call radiusUnit() from within paint()." ); 00287 return d->currentTransformation->radiusUnit; 00288 } 00289 00290 void PolarCoordinatePlane::slotLayoutChanged ( AbstractDiagram* ) 00291 { 00292 if ( d->initialResizeEventReceived ) layoutDiagrams(); 00293 } 00294 00295 void PolarCoordinatePlane::setStartPosition( qreal degrees ) 00296 { 00297 Q_ASSERT_X ( diagram(), "PolarCoordinatePlane::setStartPosition", 00298 "setStartPosition() needs a diagram to be associated to the plane." ); 00299 for( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin(); 00300 it != d->coordinateTransformations.end(); 00301 ++it ) 00302 { 00303 CoordinateTransformation& trans = *it; 00304 trans.startPosition = degrees; 00305 } 00306 } 00307 00308 qreal PolarCoordinatePlane::startPosition() const 00309 { 00310 return d->coordinateTransformations.isEmpty() 00311 ? 0.0 00312 : d->coordinateTransformations.first().startPosition; 00313 } 00314 00315 double PolarCoordinatePlane::zoomFactorX() const 00316 { 00317 return d->coordinateTransformations.isEmpty() 00318 ? 1.0 00319 : d->coordinateTransformations.first().zoom.xFactor; 00320 } 00321 00322 double PolarCoordinatePlane::zoomFactorY() const 00323 { 00324 return d->coordinateTransformations.isEmpty() 00325 ? 1.0 00326 : d->coordinateTransformations.first().zoom.yFactor; 00327 } 00328 00329 void PolarCoordinatePlane::setZoomFactors( double factorX, double factorY ) 00330 { 00331 setZoomFactorX( factorX ); 00332 setZoomFactorY( factorY ); 00333 } 00334 00335 void PolarCoordinatePlane::setZoomFactorX( double factor ) 00336 { 00337 for( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin(); 00338 it != d->coordinateTransformations.end(); 00339 ++it ) 00340 { 00341 CoordinateTransformation& trans = *it; 00342 trans.zoom.xFactor = factor; 00343 } 00344 } 00345 00346 void PolarCoordinatePlane::setZoomFactorY( double factor ) 00347 { 00348 for( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin(); 00349 it != d->coordinateTransformations.end(); 00350 ++it ) 00351 { 00352 CoordinateTransformation& trans = *it; 00353 trans.zoom.yFactor = factor; 00354 } 00355 } 00356 00357 QPointF PolarCoordinatePlane::zoomCenter() const 00358 { 00359 return d->coordinateTransformations.isEmpty() 00360 ? QPointF( 0.5, 0.5 ) 00361 : QPointF( d->coordinateTransformations.first().zoom.xCenter, d->coordinateTransformations.first().zoom.yCenter ); 00362 } 00363 00364 void PolarCoordinatePlane::setZoomCenter( const QPointF& center ) 00365 { 00366 for( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin(); 00367 it != d->coordinateTransformations.end(); 00368 ++it ) 00369 { 00370 CoordinateTransformation& trans = *it; 00371 trans.zoom.xCenter = center.x(); 00372 trans.zoom.yCenter = center.y(); 00373 } 00374 } 00375 00376 DataDimensionsList PolarCoordinatePlane::getDataDimensionsList() const 00377 { 00378 DataDimensionsList l; 00379 00380 //FIXME(khz): do the real calculation 00381 00382 return l; 00383 } 00384 00385 void KDChart::PolarCoordinatePlane::setGridAttributes( 00386 bool circular, 00387 const GridAttributes& a ) 00388 { 00389 if( circular ) 00390 d->gridAttributesCircular = a; 00391 else 00392 d->gridAttributesSagittal = a; 00393 setHasOwnGridAttributes( circular, true ); 00394 update(); 00395 emit propertiesChanged(); 00396 } 00397 00398 void KDChart::PolarCoordinatePlane::resetGridAttributes( 00399 bool circular ) 00400 { 00401 setHasOwnGridAttributes( circular, false ); 00402 update(); 00403 } 00404 00405 const GridAttributes KDChart::PolarCoordinatePlane::gridAttributes( 00406 bool circular ) const 00407 { 00408 if( hasOwnGridAttributes( circular ) ){ 00409 if( circular ) 00410 return d->gridAttributesCircular; 00411 else 00412 return d->gridAttributesSagittal; 00413 }else{ 00414 return globalGridAttributes(); 00415 } 00416 } 00417 00418 void KDChart::PolarCoordinatePlane::setHasOwnGridAttributes( 00419 bool circular, bool on ) 00420 { 00421 if( circular ) 00422 d->hasOwnGridAttributesCircular = on; 00423 else 00424 d->hasOwnGridAttributesSagittal = on; 00425 emit propertiesChanged(); 00426 } 00427 00428 bool KDChart::PolarCoordinatePlane::hasOwnGridAttributes( 00429 bool circular ) const 00430 { 00431 return 00432 ( circular ) 00433 ? d->hasOwnGridAttributesCircular 00434 : d->hasOwnGridAttributesSagittal; 00435 }