KDEUI
kplotwidget.cpp
Go to the documentation of this file.
00001 /* -*- C++ -*- 00002 This file is part of the KDE libraries 00003 Copyright (C) 2003 Jason Harris <kstars@30doradus.org> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License as published by the Free Software Foundation; either 00008 version 2 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kplotwidget.h" 00022 #include "kplotwidget.moc" 00023 00024 #include <math.h> 00025 #include <kdebug.h> 00026 00027 #include <QtGui/QActionEvent> 00028 #include <QHash> 00029 #include <QPainter> 00030 #include <QPixmap> 00031 #include <QToolTip> 00032 #include <QtAlgorithms> 00033 00034 #include "kplotaxis.h" 00035 #include "kplotpoint.h" 00036 #include "kplotobject.h" 00037 00038 #define XPADDING 20 00039 #define YPADDING 20 00040 #define BIGTICKSIZE 10 00041 #define SMALLTICKSIZE 4 00042 #define TICKOFFSET 0 00043 00044 class KPlotWidget::Private 00045 { 00046 public: 00047 Private( KPlotWidget *qq ) 00048 : q( qq ), 00049 cBackground( Qt::black ), cForeground( Qt::white ), cGrid( Qt::gray ), 00050 showGrid( false ), showObjectToolTip( true ), useAntialias( false ) 00051 { 00052 // create the axes and setting their default properties 00053 KPlotAxis *leftAxis = new KPlotAxis(); 00054 leftAxis->setTickLabelsShown( true ); 00055 axes.insert( LeftAxis, leftAxis ); 00056 KPlotAxis *bottomAxis = new KPlotAxis(); 00057 bottomAxis->setTickLabelsShown( true ); 00058 axes.insert( BottomAxis, bottomAxis ); 00059 KPlotAxis *rightAxis = new KPlotAxis(); 00060 axes.insert( RightAxis, rightAxis ); 00061 KPlotAxis *topAxis = new KPlotAxis(); 00062 axes.insert( TopAxis, topAxis ); 00063 } 00064 00065 ~Private() 00066 { 00067 qDeleteAll( objectList ); 00068 qDeleteAll( axes ); 00069 } 00070 00071 KPlotWidget *q; 00072 00073 void calcDataRectLimits( double x1, double x2, double y1, double y2 ); 00082 float rectCost( const QRectF &r ) const; 00083 00084 //Colors 00085 QColor cBackground, cForeground, cGrid; 00086 //draw options 00087 bool showGrid : 1; 00088 bool showObjectToolTip : 1; 00089 bool useAntialias : 1; 00090 //padding 00091 int leftPadding, rightPadding, topPadding, bottomPadding; 00092 // hashmap with the axes we have 00093 QHash<Axis, KPlotAxis*> axes; 00094 // List of KPlotObjects 00095 QList<KPlotObject*> objectList; 00096 // Limits of the plot area in data units 00097 QRectF dataRect, secondDataRect; 00098 // Limits of the plot area in pixel units 00099 QRect pixRect; 00100 //Array holding the mask of "used" regions of the plot 00101 QImage plotMask; 00102 }; 00103 00104 KPlotWidget::KPlotWidget( QWidget * parent ) 00105 : QFrame( parent ), d( new Private( this ) ) 00106 { 00107 setAttribute( Qt::WA_OpaquePaintEvent ); 00108 setAttribute( Qt::WA_NoSystemBackground ); 00109 00110 d->secondDataRect = QRectF(); //default: no secondary data rect 00111 // sets the default limits 00112 d->calcDataRectLimits( 0.0, 1.0, 0.0, 1.0 ); 00113 00114 setDefaultPaddings(); 00115 00116 setMinimumSize( 150, 150 ); 00117 resize( minimumSizeHint() ); 00118 } 00119 00120 KPlotWidget::~KPlotWidget() 00121 { 00122 delete d; 00123 } 00124 00125 QSize KPlotWidget::minimumSizeHint() const 00126 { 00127 return QSize( 150, 150 ); 00128 } 00129 00130 QSize KPlotWidget::sizeHint() const 00131 { 00132 return size(); 00133 } 00134 00135 void KPlotWidget::setLimits( double x1, double x2, double y1, double y2 ) 00136 { 00137 d->calcDataRectLimits( x1, x2, y1, y2 ); 00138 update(); 00139 } 00140 00141 void KPlotWidget::Private::calcDataRectLimits( double x1, double x2, double y1, double y2 ) 00142 { 00143 double XA1, XA2, YA1, YA2; 00144 if (x2<x1) { XA1=x2; XA2=x1; } 00145 else { XA1=x1; XA2=x2; } 00146 if ( y2<y1) { YA1=y2; YA2=y1; } 00147 else { YA1=y1; YA2=y2; } 00148 00149 if ( XA2 == XA1 ) { 00150 kWarning() << "x1 and x2 cannot be equal. Setting x2 = x1 + 1.0"; 00151 XA2 = XA1 + 1.0; 00152 } 00153 if ( YA2 == YA1 ) { 00154 kWarning() << "y1 and y2 cannot be equal. Setting y2 = y1 + 1.0"; 00155 YA2 = YA1 + 1.0; 00156 } 00157 dataRect = QRectF( XA1, YA1, XA2 - XA1, YA2 - YA1 ); 00158 00159 q->axis( LeftAxis )->setTickMarks( dataRect.y(), dataRect.height() ); 00160 q->axis( BottomAxis )->setTickMarks( dataRect.x(), dataRect.width() ); 00161 00162 if ( secondDataRect.isNull() ) 00163 { 00164 q->axis( RightAxis )->setTickMarks( dataRect.y(), dataRect.height() ); 00165 q->axis( TopAxis )->setTickMarks( dataRect.x(), dataRect.width() ); 00166 } 00167 } 00168 00169 void KPlotWidget::setSecondaryLimits( double x1, double x2, double y1, double y2 ) { 00170 double XA1, XA2, YA1, YA2; 00171 if (x2<x1) { XA1=x2; XA2=x1; } 00172 else { XA1=x1; XA2=x2; } 00173 if ( y2<y1) { YA1=y2; YA2=y1; } 00174 else { YA1=y1; YA2=y2; } 00175 00176 if ( XA2 == XA1 ) { 00177 kWarning() << "x1 and x2 cannot be equal. Setting x2 = x1 + 1.0"; 00178 XA2 = XA1 + 1.0; 00179 } 00180 if ( YA2 == YA1 ) { 00181 kWarning() << "y1 and y2 cannot be equal. Setting y2 = y1 + 1.0"; 00182 YA2 = YA1 + 1.0; 00183 } 00184 d->secondDataRect = QRectF( XA1, YA1, XA2-XA1, YA2-YA1 ); 00185 00186 axis(RightAxis)->setTickMarks( d->secondDataRect.y(), d->secondDataRect.height() ); 00187 axis(TopAxis)->setTickMarks( d->secondDataRect.x(), d->secondDataRect.width() ); 00188 00189 update(); 00190 } 00191 00192 void KPlotWidget::clearSecondaryLimits() { 00193 d->secondDataRect = QRectF(); 00194 axis(RightAxis)->setTickMarks( d->dataRect.y(), d->dataRect.height() ); 00195 axis(TopAxis)->setTickMarks( d->dataRect.x(), d->dataRect.width() ); 00196 00197 update(); 00198 } 00199 00200 QRectF KPlotWidget::dataRect() const 00201 { 00202 return d->dataRect; 00203 } 00204 00205 QRectF KPlotWidget::secondaryDataRect() const 00206 { 00207 return d->secondDataRect; 00208 } 00209 00210 void KPlotWidget::addPlotObject( KPlotObject *object ) 00211 { 00212 // skip null pointers 00213 if ( !object ) 00214 return; 00215 d->objectList.append( object ); 00216 update(); 00217 } 00218 00219 void KPlotWidget::addPlotObjects( const QList< KPlotObject* >& objects ) 00220 { 00221 bool addedsome = false; 00222 foreach ( KPlotObject *o, objects ) 00223 { 00224 if ( !o ) 00225 continue; 00226 00227 d->objectList.append( o ); 00228 addedsome = true; 00229 } 00230 if ( addedsome ) 00231 update(); 00232 } 00233 00234 QList< KPlotObject* > KPlotWidget::plotObjects() const 00235 { 00236 return d->objectList; 00237 } 00238 00239 void KPlotWidget::removeAllPlotObjects() 00240 { 00241 if ( d->objectList.isEmpty() ) 00242 return; 00243 00244 qDeleteAll( d->objectList ); 00245 d->objectList.clear(); 00246 update(); 00247 } 00248 00249 void KPlotWidget::resetPlotMask() { 00250 d->plotMask = QImage( pixRect().size(), QImage::Format_ARGB32 ); 00251 QColor fillColor = Qt::black; 00252 fillColor.setAlpha( 128 ); 00253 d->plotMask.fill( fillColor.rgb() ); 00254 } 00255 00256 void KPlotWidget::resetPlot() { 00257 qDeleteAll( d->objectList ); 00258 d->objectList.clear(); 00259 clearSecondaryLimits(); 00260 d->calcDataRectLimits( 0.0, 1.0, 0.0, 1.0 ); 00261 KPlotAxis *a = axis( RightAxis ); 00262 a->setLabel( QString() ); 00263 a->setTickLabelsShown( false ); 00264 a = axis( TopAxis ); 00265 a->setLabel( QString() ); 00266 a->setTickLabelsShown( false ); 00267 axis(KPlotWidget::LeftAxis)->setLabel( QString() ); 00268 axis(KPlotWidget::BottomAxis)->setLabel( QString() ); 00269 resetPlotMask(); 00270 } 00271 00272 void KPlotWidget::replacePlotObject( int i, KPlotObject *o ) 00273 { 00274 // skip null pointers and invalid indexes 00275 if ( !o || i < 0 || i >= d->objectList.count() ) 00276 return; 00277 d->objectList.replace( i, o ); 00278 update(); 00279 } 00280 00281 QColor KPlotWidget::backgroundColor() const 00282 { 00283 return d->cBackground; 00284 } 00285 00286 QColor KPlotWidget::foregroundColor() const 00287 { 00288 return d->cForeground; 00289 } 00290 00291 QColor KPlotWidget::gridColor() const 00292 { 00293 return d->cGrid; 00294 } 00295 00296 void KPlotWidget::setBackgroundColor( const QColor &bg ) { 00297 d->cBackground = bg; 00298 update(); 00299 } 00300 00301 void KPlotWidget::setForegroundColor( const QColor &fg ) 00302 { 00303 d->cForeground = fg; 00304 update(); 00305 } 00306 00307 void KPlotWidget::setGridColor( const QColor &gc ) 00308 { 00309 d->cGrid = gc; 00310 update(); 00311 } 00312 00313 bool KPlotWidget::isGridShown() const 00314 { 00315 return d->showGrid; 00316 } 00317 00318 bool KPlotWidget::isObjectToolTipShown() const 00319 { 00320 return d->showObjectToolTip; 00321 } 00322 00323 bool KPlotWidget::antialiasing() const 00324 { 00325 return d->useAntialias; 00326 } 00327 00328 void KPlotWidget::setAntialiasing( bool b ) 00329 { 00330 d->useAntialias = b; 00331 update(); 00332 } 00333 00334 void KPlotWidget::setShowGrid( bool show ) { 00335 d->showGrid = show; 00336 update(); 00337 } 00338 00339 void KPlotWidget::setObjectToolTipShown( bool show ) 00340 { 00341 d->showObjectToolTip = show; 00342 } 00343 00344 00345 KPlotAxis* KPlotWidget::axis( Axis type ) 00346 { 00347 QHash<Axis, KPlotAxis*>::Iterator it = d->axes.find( type ); 00348 return it != d->axes.end() ? it.value() : 0; 00349 } 00350 00351 const KPlotAxis* KPlotWidget::axis( Axis type ) const 00352 { 00353 QHash<Axis, KPlotAxis*>::ConstIterator it = d->axes.constFind( type ); 00354 return it != d->axes.constEnd() ? it.value() : 0; 00355 } 00356 00357 QRect KPlotWidget::pixRect() const 00358 { 00359 return d->pixRect; 00360 } 00361 00362 QList<KPlotPoint*> KPlotWidget::pointsUnderPoint( const QPoint& p ) const { 00363 QList<KPlotPoint*> pts; 00364 foreach ( KPlotObject *po, d->objectList ) { 00365 foreach ( KPlotPoint *pp, po->points() ) { 00366 if ( ( p - mapToWidget( pp->position() ).toPoint() ).manhattanLength() <= 4 ) 00367 pts << pp; 00368 } 00369 } 00370 00371 return pts; 00372 } 00373 00374 00375 bool KPlotWidget::event( QEvent* e ) { 00376 if ( e->type() == QEvent::ToolTip ) { 00377 if ( d->showObjectToolTip ) 00378 { 00379 QHelpEvent *he = static_cast<QHelpEvent*>( e ); 00380 QList<KPlotPoint*> pts = pointsUnderPoint( he->pos() - QPoint( leftPadding(), topPadding() ) - contentsRect().topLeft() ); 00381 if ( pts.count() > 0 ) { 00382 QToolTip::showText( he->globalPos(), pts.front()->label(), this ); 00383 } 00384 } 00385 e->accept(); 00386 return true; 00387 } 00388 else 00389 return QFrame::event( e ); 00390 } 00391 00392 void KPlotWidget::resizeEvent( QResizeEvent* e ) { 00393 QFrame::resizeEvent( e ); 00394 setPixRect(); 00395 resetPlotMask(); 00396 } 00397 00398 void KPlotWidget::setPixRect() { 00399 int newWidth = contentsRect().width() - leftPadding() - rightPadding(); 00400 int newHeight = contentsRect().height() - topPadding() - bottomPadding(); 00401 // PixRect starts at (0,0) because we will translate by leftPadding(), topPadding() 00402 d->pixRect = QRect( 0, 0, newWidth, newHeight ); 00403 } 00404 00405 QPointF KPlotWidget::mapToWidget( const QPointF& p ) const 00406 { 00407 float px = d->pixRect.left() + d->pixRect.width() * ( p.x() - d->dataRect.x() ) / d->dataRect.width(); 00408 float py = d->pixRect.top() + d->pixRect.height() * ( d->dataRect.y() + d->dataRect.height() - p.y() ) / d->dataRect.height(); 00409 return QPointF( px, py ); 00410 } 00411 00412 void KPlotWidget::maskRect( const QRectF& rf, float fvalue ) { 00413 QRect r = rf.toRect().intersected( d->pixRect ); 00414 int value = int( fvalue ); 00415 QColor newColor; 00416 for ( int ix=r.left(); ix<r.right(); ++ix ) { 00417 for ( int iy=r.top(); iy<r.bottom(); ++iy ) { 00418 newColor = QColor( d->plotMask.pixel(ix,iy) ); 00419 newColor.setAlpha( 200 ); 00420 newColor.setRed( qMin( newColor.red() + value, 255 ) ); 00421 d->plotMask.setPixel( ix, iy, newColor.rgba() ); 00422 } 00423 } 00424 00425 } 00426 00427 void KPlotWidget::maskAlongLine( const QPointF &p1, const QPointF &p2, float fvalue ) { 00428 if ( ! d->pixRect.contains( p1.toPoint() ) && ! d->pixRect.contains( p2.toPoint() ) ) { 00429 return; 00430 } 00431 00432 int value = int( fvalue ); 00433 00434 //Determine slope and zeropoint of line 00435 double m = (p2.y() - p1.y())/(p2.x() - p1.x()); 00436 double y0 = p1.y() - m*p1.x(); 00437 QColor newColor; 00438 00439 //Mask each pixel along the line joining p1 and p2 00440 if ( m > 1.0 || m < -1.0 ) { //step in y-direction 00441 int y1 = int( p1.y() ); 00442 int y2 = int( p2.y() ); 00443 if ( y1 > y2 ) { 00444 y1 = int( p2.y() ); 00445 y2 = int( p1.y() ); 00446 } 00447 00448 for ( int y=y1; y<=y2; ++y ) { 00449 int x = int( (y - y0)/m ); 00450 if ( d->pixRect.contains( x, y ) ) { 00451 newColor = QColor( d->plotMask.pixel(x,y) ); 00452 newColor.setAlpha( 100 ); 00453 newColor.setRed( qMin( newColor.red() + value, 255 ) ); 00454 d->plotMask.setPixel( x, y, newColor.rgba() ); 00455 } 00456 } 00457 00458 } else { //step in x-direction 00459 int x1 = int( p1.x() ); 00460 int x2 = int( p2.x() ); 00461 if ( x1 > x2 ) { 00462 x1 = int( p2.x() ); 00463 x2 = int( p1.x() ); 00464 } 00465 00466 for ( int x=x1; x<=x2; ++x ) { 00467 int y = int( y0 + m*x ); 00468 if ( d->pixRect.contains( x, y ) ) { 00469 newColor = QColor( d->plotMask.pixel(x,y) ); 00470 newColor.setAlpha( 100 ); 00471 newColor.setRed( qMin( newColor.red() + value, 255 ) ); 00472 d->plotMask.setPixel( x, y, newColor.rgba() ); 00473 } 00474 } 00475 } 00476 } 00477 00478 //Determine optimal placement for a text label for point pp. We want 00479 //the label to be near point pp, but we don't want it to overlap with 00480 //other labels or plot elements. We will use a "downhill simplex" 00481 //algorithm to find a label position that minimizes the pixel values 00482 //in the plotMask image over the label's rect(). The sum of pixel 00483 //values in the label's rect is the "cost" of placing the label there. 00484 // 00485 //Because a downhill simplex follows the local gradient to find low 00486 //values, it can get stuck in local minima. To mitigate this, we will 00487 //iteratively attempt each of the initial path offset directions (up, 00488 //down, right, left) in the order of increasing cost at each location. 00489 void KPlotWidget::placeLabel( QPainter *painter, KPlotPoint *pp ) { 00490 int textFlags = Qt::TextSingleLine | Qt::AlignCenter; 00491 00492 QPointF pos = mapToWidget( pp->position() ); 00493 if ( ! d->pixRect.contains( pos.toPoint() ) ) return; 00494 00495 QFontMetricsF fm( painter->font(), painter->device() ); 00496 QRectF bestRect = fm.boundingRect( QRectF( pos.x(), pos.y(), 1, 1 ), textFlags, pp->label() ); 00497 float xStep = 0.5*bestRect.width(); 00498 float yStep = 0.5*bestRect.height(); 00499 float maxCost = 0.05 * bestRect.width() * bestRect.height(); 00500 float bestCost = d->rectCost( bestRect ); 00501 00502 //We will travel along a path defined by the maximum decrease in 00503 //the cost at each step. If this path takes us to a local minimum 00504 //whose cost exceeds maxCost, then we will restart at the 00505 //beginning and select the next-best path. The indices of 00506 //already-tried paths are stored in the TriedPathIndex list. 00507 // 00508 //If we try all four first-step paths and still don't get below 00509 //maxCost, then we'll adopt the local minimum position with the 00510 //best cost (designated as bestBadCost). 00511 int iter = 0; 00512 QList<int> TriedPathIndex; 00513 float bestBadCost = 10000; 00514 QRectF bestBadRect; 00515 00516 //needed to halt iteration from inside the switch 00517 bool flagStop = false; 00518 00519 while ( bestCost > maxCost ) { 00520 //Displace the label up, down, left, right; determine which 00521 //step provides the lowest cost 00522 QRectF upRect = bestRect; 00523 upRect.moveTop( upRect.top() + yStep ); 00524 float upCost = d->rectCost( upRect ); 00525 QRectF downRect = bestRect; 00526 downRect.moveTop( downRect.top() - yStep ); 00527 float downCost = d->rectCost( downRect ); 00528 QRectF leftRect = bestRect; 00529 leftRect.moveLeft( leftRect.left() - xStep ); 00530 float leftCost = d->rectCost( leftRect ); 00531 QRectF rightRect = bestRect; 00532 rightRect.moveLeft( rightRect.left() + xStep ); 00533 float rightCost = d->rectCost( rightRect ); 00534 00535 //which direction leads to the lowest cost? 00536 QList<float> costList; 00537 costList << upCost << downCost << leftCost << rightCost; 00538 int imin = -1; 00539 for ( int i=0; i<costList.size(); ++i ) { 00540 if ( iter == 0 && TriedPathIndex.contains( i ) ) { 00541 continue; //Skip this first-step path, we already tried it! 00542 } 00543 00544 //If this first-step path doesn't improve the cost, 00545 //skip this direction from now on 00546 if ( iter == 0 && costList[i] >= bestCost ) { 00547 TriedPathIndex.append( i ); 00548 continue; 00549 } 00550 00551 if ( costList[i] < bestCost && (imin < 0 || costList[i] < costList[imin]) ) { 00552 00553 imin = i; 00554 } 00555 } 00556 00557 //Make a note that we've tried the current first-step path 00558 if ( iter == 0 && imin >= 0 ) { 00559 TriedPathIndex.append( imin ); 00560 } 00561 00562 //Adopt the step that produced the best cost 00563 switch ( imin ) { 00564 case 0: //up 00565 bestRect.moveTop( upRect.top() ); 00566 bestCost = upCost; 00567 break; 00568 case 1: //down 00569 bestRect.moveTop( downRect.top() ); 00570 bestCost = downCost; 00571 break; 00572 case 2: //left 00573 bestRect.moveLeft( leftRect.left() ); 00574 bestCost = leftCost; 00575 break; 00576 case 3: //right 00577 bestRect.moveLeft( rightRect.left() ); 00578 bestCost = rightCost; 00579 break; 00580 case -1: //no lower cost found! 00581 //We hit a local minimum. Keep the best of these as bestBadRect 00582 if ( bestCost < bestBadCost ) { 00583 bestBadCost = bestCost; 00584 bestBadRect = bestRect; 00585 } 00586 00587 //If all of the first-step paths have now been searched, we'll 00588 //have to adopt the bestBadRect 00589 if ( TriedPathIndex.size() == 4 ) { 00590 bestRect = bestBadRect; 00591 flagStop = true; //halt iteration 00592 break; 00593 } 00594 00595 //If we haven't yet tried all of the first-step paths, start over 00596 if ( TriedPathIndex.size() < 4 ) { 00597 iter = -1; //anticipating the ++iter below 00598 bestRect = fm.boundingRect( QRectF( pos.x(), pos.y(), 1, 1 ), textFlags, pp->label() ); 00599 bestCost = d->rectCost( bestRect ); 00600 } 00601 break; 00602 } 00603 00604 //Halt iteration, because we've tried all directions and 00605 //haven't gotten below maxCost (we'll adopt the best 00606 //local minimum found) 00607 if ( flagStop ) { 00608 break; 00609 } 00610 00611 ++iter; 00612 } 00613 00614 painter->drawText( bestRect, textFlags, pp->label() ); 00615 00616 //Is a line needed to connect the label to the point? 00617 float deltax = pos.x() - bestRect.center().x(); 00618 float deltay = pos.y() - bestRect.center().y(); 00619 float rbest = sqrt( deltax*deltax + deltay*deltay ); 00620 if ( rbest > 20.0 ) { 00621 //Draw a rectangle around the label 00622 painter->setBrush( QBrush() ); 00623 //QPen pen = painter->pen(); 00624 //pen.setStyle( Qt::DotLine ); 00625 //painter->setPen( pen ); 00626 painter->drawRoundRect( bestRect ); 00627 00628 //Now connect the label to the point with a line. 00629 //The line is drawn from the center of the near edge of the rectangle 00630 float xline = bestRect.center().x(); 00631 if ( bestRect.left() > pos.x() ) 00632 xline = bestRect.left(); 00633 if ( bestRect.right() < pos.x() ) 00634 xline = bestRect.right(); 00635 00636 float yline = bestRect.center().y(); 00637 if ( bestRect.top() > pos.y() ) 00638 yline = bestRect.top(); 00639 if ( bestRect.bottom() < pos.y() ) 00640 yline = bestRect.bottom(); 00641 00642 painter->drawLine( QPointF( xline, yline ), pos ); 00643 } 00644 00645 //Mask the label's rectangle so other labels won't overlap it. 00646 maskRect( bestRect ); 00647 } 00648 00649 float KPlotWidget::Private::rectCost( const QRectF &r ) const 00650 { 00651 if ( ! plotMask.rect().contains( r.toRect() ) ) { 00652 return 10000.; 00653 } 00654 00655 //Compute sum of mask values in the rect r 00656 QImage subMask = plotMask.copy( r.toRect() ); 00657 int cost = 0; 00658 for ( int ix=0; ix<subMask.width(); ++ix ) { 00659 for ( int iy=0; iy<subMask.height(); ++iy ) { 00660 cost += QColor( subMask.pixel( ix, iy ) ).red(); 00661 } 00662 } 00663 00664 return float(cost); 00665 } 00666 00667 void KPlotWidget::paintEvent( QPaintEvent *e ) { 00668 // let QFrame draw its default stuff (like the frame) 00669 QFrame::paintEvent( e ); 00670 QPainter p; 00671 00672 p.begin( this ); 00673 p.setRenderHint( QPainter::Antialiasing, d->useAntialias ); 00674 p.fillRect( rect(), backgroundColor() ); 00675 p.translate( leftPadding() + 0.5, topPadding() + 0.5 ); 00676 00677 setPixRect(); 00678 p.setClipRect( d->pixRect ); 00679 p.setClipping( true ); 00680 00681 resetPlotMask(); 00682 00683 foreach( KPlotObject *po, d->objectList ) 00684 po->draw( &p, this ); 00685 00686 //DEBUG: Draw the plot mask 00687 // p.drawImage( 0, 0, d->plotMask ); 00688 00689 p.setClipping( false ); 00690 drawAxes( &p ); 00691 00692 p.end(); 00693 } 00694 00695 void KPlotWidget::drawAxes( QPainter *p ) { 00696 if ( d->showGrid ) { 00697 p->setPen( gridColor() ); 00698 00699 //Grid lines are placed at locations of primary axes' major tickmarks 00700 //vertical grid lines 00701 foreach ( double xx, axis(BottomAxis)->majorTickMarks() ) { 00702 double px = d->pixRect.width() * (xx - d->dataRect.x()) / d->dataRect.width(); 00703 p->drawLine( QPointF( px, 0.0 ), QPointF( px, double(d->pixRect.height()) ) ); 00704 } 00705 //horizontal grid lines 00706 foreach( double yy, axis(LeftAxis)->majorTickMarks() ) { 00707 double py = d->pixRect.height() * ( 1.0 - (yy - d->dataRect.y()) / d->dataRect.height() ); 00708 p->drawLine( QPointF( 0.0, py ), QPointF( double(d->pixRect.width()), py ) ); 00709 } 00710 } 00711 00712 p->setPen( foregroundColor() ); 00713 p->setBrush( Qt::NoBrush ); 00714 00715 //set small font for tick labels 00716 QFont f = p->font(); 00717 int s = f.pointSize(); 00718 f.setPointSize( s - 2 ); 00719 p->setFont( f ); 00720 00721 /*** BottomAxis ***/ 00722 KPlotAxis *a = axis(BottomAxis); 00723 if (a->isVisible()) { 00724 //Draw axis line 00725 p->drawLine( 0, d->pixRect.height(), d->pixRect.width(), d->pixRect.height() ); 00726 00727 // Draw major tickmarks 00728 foreach( double xx, a->majorTickMarks() ) { 00729 double px = d->pixRect.width() * (xx - d->dataRect.x()) / d->dataRect.width(); 00730 if ( px > 0 && px < d->pixRect.width() ) { 00731 p->drawLine( QPointF( px, double(d->pixRect.height() - TICKOFFSET)), 00732 QPointF( px, double(d->pixRect.height() - BIGTICKSIZE - TICKOFFSET)) ); 00733 00734 //Draw ticklabel 00735 if ( a->areTickLabelsShown() ) { 00736 QRect r( int(px) - BIGTICKSIZE, d->pixRect.height()+BIGTICKSIZE, 2*BIGTICKSIZE, BIGTICKSIZE ); 00737 p->drawText( r, Qt::AlignCenter | Qt::TextDontClip, a->tickLabel( xx ) ); 00738 } 00739 } 00740 } 00741 00742 // Draw minor tickmarks 00743 foreach ( double xx, a->minorTickMarks() ) { 00744 double px = d->pixRect.width() * (xx - d->dataRect.x()) / d->dataRect.width(); 00745 if ( px > 0 && px < d->pixRect.width() ) { 00746 p->drawLine( QPointF( px, double(d->pixRect.height() - TICKOFFSET)), 00747 QPointF( px, double(d->pixRect.height() - SMALLTICKSIZE -TICKOFFSET)) ); 00748 } 00749 } 00750 00751 // Draw BottomAxis Label 00752 if ( ! a->label().isEmpty() ) { 00753 QRect r( 0, d->pixRect.height() + 2*YPADDING, d->pixRect.width(), YPADDING ); 00754 p->drawText( r, Qt::AlignCenter, a->label() ); 00755 } 00756 } //End of BottomAxis 00757 00758 /*** LeftAxis ***/ 00759 a = axis(LeftAxis); 00760 if (a->isVisible()) { 00761 //Draw axis line 00762 p->drawLine( 0, 0, 0, d->pixRect.height() ); 00763 00764 // Draw major tickmarks 00765 foreach( double yy, a->majorTickMarks() ) { 00766 double py = d->pixRect.height() * ( 1.0 - (yy - d->dataRect.y()) / d->dataRect.height() ); 00767 if ( py > 0 && py < d->pixRect.height() ) { 00768 p->drawLine( QPointF( TICKOFFSET, py ), QPointF( double(TICKOFFSET + BIGTICKSIZE), py ) ); 00769 00770 //Draw ticklabel 00771 if ( a->areTickLabelsShown() ) { 00772 QRect r( -2*BIGTICKSIZE-SMALLTICKSIZE, int(py)-SMALLTICKSIZE, 2*BIGTICKSIZE, 2*SMALLTICKSIZE ); 00773 p->drawText( r, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, a->tickLabel( yy ) ); 00774 } 00775 } 00776 } 00777 00778 // Draw minor tickmarks 00779 foreach ( double yy, a->minorTickMarks() ) { 00780 double py = d->pixRect.height() * ( 1.0 - (yy - d->dataRect.y()) / d->dataRect.height() ); 00781 if ( py > 0 && py < d->pixRect.height() ) { 00782 p->drawLine( QPointF( TICKOFFSET, py ), QPointF( double(TICKOFFSET + SMALLTICKSIZE), py ) ); 00783 } 00784 } 00785 00786 //Draw LeftAxis Label. We need to draw the text sideways. 00787 if ( ! a->label().isEmpty() ) { 00788 //store current painter translation/rotation state 00789 p->save(); 00790 00791 //translate coord sys to left corner of axis label rectangle, then rotate 90 degrees. 00792 p->translate( -3*XPADDING, d->pixRect.height() ); 00793 p->rotate( -90.0 ); 00794 00795 QRect r( 0, 0, d->pixRect.height(), XPADDING ); 00796 p->drawText( r, Qt::AlignCenter, a->label() ); //draw the label, now that we are sideways 00797 00798 p->restore(); //restore translation/rotation state 00799 } 00800 } //End of LeftAxis 00801 00802 //Prepare for top and right axes; we may need the secondary data rect 00803 double x0 = d->dataRect.x(); 00804 double y0 = d->dataRect.y(); 00805 double dw = d->dataRect.width(); 00806 double dh = d->dataRect.height(); 00807 if ( secondaryDataRect().isValid() ) { 00808 x0 = secondaryDataRect().x(); 00809 y0 = secondaryDataRect().y(); 00810 dw = secondaryDataRect().width(); 00811 dh = secondaryDataRect().height(); 00812 } 00813 00814 /*** TopAxis ***/ 00815 a = axis(TopAxis); 00816 if (a->isVisible()) { 00817 //Draw axis line 00818 p->drawLine( 0, 0, d->pixRect.width(), 0 ); 00819 00820 // Draw major tickmarks 00821 foreach( double xx, a->majorTickMarks() ) { 00822 double px = d->pixRect.width() * (xx - x0) / dw; 00823 if ( px > 0 && px < d->pixRect.width() ) { 00824 p->drawLine( QPointF( px, TICKOFFSET ), QPointF( px, double(BIGTICKSIZE + TICKOFFSET)) ); 00825 00826 //Draw ticklabel 00827 if ( a->areTickLabelsShown() ) { 00828 QRect r( int(px) - BIGTICKSIZE, (int)-1.5*BIGTICKSIZE, 2*BIGTICKSIZE, BIGTICKSIZE ); 00829 p->drawText( r, Qt::AlignCenter | Qt::TextDontClip, a->tickLabel( xx ) ); 00830 } 00831 } 00832 } 00833 00834 // Draw minor tickmarks 00835 foreach ( double xx, a->minorTickMarks() ) { 00836 double px = d->pixRect.width() * (xx - x0) / dw; 00837 if ( px > 0 && px < d->pixRect.width() ) { 00838 p->drawLine( QPointF( px, TICKOFFSET ), QPointF( px, double(SMALLTICKSIZE + TICKOFFSET)) ); 00839 } 00840 } 00841 00842 // Draw TopAxis Label 00843 if ( ! a->label().isEmpty() ) { 00844 QRect r( 0, 0 - 3*YPADDING, d->pixRect.width(), YPADDING ); 00845 p->drawText( r, Qt::AlignCenter, a->label() ); 00846 } 00847 } //End of TopAxis 00848 00849 /*** RightAxis ***/ 00850 a = axis(RightAxis); 00851 if (a->isVisible()) { 00852 //Draw axis line 00853 p->drawLine( d->pixRect.width(), 0, d->pixRect.width(), d->pixRect.height() ); 00854 00855 // Draw major tickmarks 00856 foreach( double yy, a->majorTickMarks() ) { 00857 double py = d->pixRect.height() * ( 1.0 - (yy - y0) / dh ); 00858 if ( py > 0 && py < d->pixRect.height() ) { 00859 p->drawLine( QPointF( double(d->pixRect.width() - TICKOFFSET), py ), 00860 QPointF( double(d->pixRect.width() - TICKOFFSET - BIGTICKSIZE), py ) ); 00861 00862 //Draw ticklabel 00863 if ( a->areTickLabelsShown() ) { 00864 QRect r( d->pixRect.width() + SMALLTICKSIZE, int(py)-SMALLTICKSIZE, 2*BIGTICKSIZE, 2*SMALLTICKSIZE ); 00865 p->drawText( r, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip, a->tickLabel( yy ) ); 00866 } 00867 } 00868 } 00869 00870 // Draw minor tickmarks 00871 foreach ( double yy, a->minorTickMarks() ) { 00872 double py = d->pixRect.height() * ( 1.0 - (yy - y0) / dh ); 00873 if ( py > 0 && py < d->pixRect.height() ) { 00874 p->drawLine( QPointF( double(d->pixRect.width() - 0.0), py ), 00875 QPointF( double(d->pixRect.width() - 0.0 - SMALLTICKSIZE), py ) ); 00876 } 00877 } 00878 00879 //Draw RightAxis Label. We need to draw the text sideways. 00880 if ( ! a->label().isEmpty() ) { 00881 //store current painter translation/rotation state 00882 p->save(); 00883 00884 //translate coord sys to left corner of axis label rectangle, then rotate 90 degrees. 00885 p->translate( d->pixRect.width() + 2*XPADDING, d->pixRect.height() ); 00886 p->rotate( -90.0 ); 00887 00888 QRect r( 0, 0, d->pixRect.height(), XPADDING ); 00889 p->drawText( r, Qt::AlignCenter, a->label() ); //draw the label, now that we are sideways 00890 00891 p->restore(); //restore translation/rotation state 00892 } 00893 } //End of RightAxis 00894 } 00895 00896 int KPlotWidget::leftPadding() const 00897 { 00898 if ( d->leftPadding >= 0 ) 00899 return d->leftPadding; 00900 const KPlotAxis *a = axis( LeftAxis ); 00901 if ( a && a->isVisible() && a->areTickLabelsShown() ) 00902 { 00903 return !a->label().isEmpty() ? 3 * XPADDING : 2 * XPADDING; 00904 } 00905 return XPADDING; 00906 } 00907 00908 int KPlotWidget::rightPadding() const 00909 { 00910 if ( d->rightPadding >= 0 ) 00911 return d->rightPadding; 00912 const KPlotAxis *a = axis( RightAxis ); 00913 if ( a && a->isVisible() && a->areTickLabelsShown() ) 00914 { 00915 return !a->label().isEmpty() ? 3 * XPADDING : 2 * XPADDING; 00916 } 00917 return XPADDING; 00918 } 00919 00920 int KPlotWidget::topPadding() const 00921 { 00922 if ( d->topPadding >= 0 ) 00923 return d->topPadding; 00924 const KPlotAxis *a = axis( TopAxis ); 00925 if ( a && a->isVisible() && a->areTickLabelsShown() ) 00926 { 00927 return !a->label().isEmpty() ? 3 * YPADDING : 2 * YPADDING; 00928 } 00929 return YPADDING; 00930 } 00931 00932 int KPlotWidget::bottomPadding() const 00933 { 00934 if ( d->bottomPadding >= 0 ) 00935 return d->bottomPadding; 00936 const KPlotAxis *a = axis( BottomAxis ); 00937 if ( a && a->isVisible() && a->areTickLabelsShown() ) 00938 { 00939 return !a->label().isEmpty() ? 3 * YPADDING : 2 * YPADDING; 00940 } 00941 return YPADDING; 00942 } 00943 00944 void KPlotWidget::setLeftPadding( int padding ) 00945 { 00946 d->leftPadding = padding; 00947 } 00948 00949 void KPlotWidget::setRightPadding( int padding ) 00950 { 00951 d->rightPadding = padding; 00952 } 00953 00954 void KPlotWidget::setTopPadding( int padding ) 00955 { 00956 d->topPadding = padding; 00957 } 00958 00959 void KPlotWidget::setBottomPadding( int padding ) 00960 { 00961 d->bottomPadding = padding; 00962 } 00963 00964 void KPlotWidget::setDefaultPaddings() 00965 { 00966 d->leftPadding = -1; 00967 d->rightPadding = -1; 00968 d->topPadding = -1; 00969 d->bottomPadding = -1; 00970 } 00971
KDE 4.6 API Reference