moved kdeaccessibility kdeaddons kdeadmin kdeartwork kdebindings kdeedu kdegames...
[kdeedu.git] / libkdeedu / kdeeduplot / kplotwidget.cpp
blobb800c655204dcd30e2d42e94000f4a0cfa21c58f
1 /***************************************************************************
2 kplotwidget.cpp - A widget for plotting in KStars
3 -------------------
4 begin : Sun 18 May 2003
5 copyright : (C) 2003 by Jason Harris
6 email : kstars@30doradus.org
7 ***************************************************************************/
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
18 #include <math.h> //for log10(), pow(), modf()
19 #include <kdebug.h>
20 #include <qpainter.h>
21 #include <qpixmap.h>
23 #include "kplotwidget.h"
25 KPlotWidget::KPlotWidget( double x1, double x2, double y1, double y2, QWidget *parent, const char* name )
26 : QWidget( parent, name, WNoAutoErase ),
27 dXtick(0.0), dYtick(0.0),
28 nmajX(0), nminX(0), nmajY(0), nminY(0),
29 ShowAxes( true ), ShowTickMarks( true ), ShowTickLabels( true ), ShowGrid( false ),
30 XAxisLabel(), YAxisLabel() {
32 setBackgroundMode( QWidget::NoBackground );
34 //set DataRect
35 setLimits( x1, x2, y1, y2 );
36 setDefaultPadding();
38 //Set PixRect (starts at (0,0) because we will translate by leftPadding(), topPadding() )
39 PixRect = QRect( 0, 0, width() - leftPadding() - rightPadding(),
40 height() - topPadding() - bottomPadding() );
42 buffer = new QPixmap();
44 //default colors:
45 setBGColor( QColor( "black" ) );
46 setFGColor( QColor( "white" ) );
47 setGridColor( QColor( "grey" ) );
49 ObjectList.setAutoDelete( TRUE );
52 KPlotWidget::~KPlotWidget()
54 delete (buffer);
57 void KPlotWidget::setLimits( double x1, double x2, double y1, double y2 ) {
58 double XA1, XA2, YA1, YA2;
59 if (x2<x1) { XA1=x2; XA2=x1; }
60 else { XA1=x1; XA2=x2; }
61 if ( y2<y1) { YA1=y2; YA2=y1; }
62 else { YA1=y1; YA2=y2; }
64 DataRect = DRect( XA1, YA1, XA2-XA1, YA2-YA1 );
65 updateTickmarks();
68 void KPlotWidget::updateTickmarks() {
69 // Determine the number and spacing of tickmarks for the current plot limits.
70 if ( dataWidth() == 0.0 ) {
71 kdWarning() << "X range invalid! " << x() << " to " << x2() << endl;
72 DataRect.setWidth( 1.0 );
73 return;
75 if ( dataHeight() == 0.0 ) {
76 kdWarning() << "Y range invalid! " << y() << " to " << y2() << endl;
77 DataRect.setHeight( 1.0 );
78 return;
81 int nmajor(0), nminor(0);
82 double z(0.0), z2(0.0);
83 double Range(0.0), s(0.0), t(0.0), pwr(0.0), dTick(0.0);
85 //loop over X and Y axes...the z variables substitute for either X or Y
86 for ( unsigned int iaxis=0; iaxis<2; ++iaxis ) {
87 if ( iaxis == 1 ) {
88 z = x(); z2 = x2();
89 } else {
90 z = y(); z2 = y2();
93 //determine size of region to be drawn, in draw units
94 Range = z2 - z;
96 //s is the power-of-ten factor of Range:
97 //Range = t * s; s = 10^(pwr). e.g., Range=350.0 then t=3.5, s = 100.0; pwr = 2.0
98 modf( log10(Range), &pwr );
99 s = pow( 10.0, pwr );
100 t = Range/s;
102 //adjust s and t such that t is between 3 and 5:
103 if ( t < 3.0 ) { t *= 10.0; s /= 10.0; } //t now btwn 3 and 30
104 if ( t < 6.0 ) { //accept current values
105 dTick = s;
106 nmajor = int(t);
107 nminor = 5;
108 } else if ( t < 10.0 ) { //factor of 2
109 dTick = s*2.0;
110 nmajor = int(t/2.0);
111 nminor = 4;
112 } else if ( t < 20.0 ) { //factor of 4
113 dTick = s*4.0;
114 nmajor = int(t/4.0);
115 nminor = 4;
116 } else { //factor of 5
117 dTick = s*5.0;
118 nmajor = int(t/5.0);
119 nminor = 5;
122 if ( iaxis==1 ) { //X axis
123 nmajX = nmajor;
124 nminX = nminor;
125 dXtick = dTick;
126 } else { //Y axis
127 nmajY = nmajor;
128 nminY = nminor;
129 dYtick = dTick;
131 } //end for iaxis
134 void KPlotWidget::resizeEvent( QResizeEvent* /* e */ ) {
135 int newWidth = width() - leftPadding() - rightPadding();
136 int newHeight = height() - topPadding() - bottomPadding();
137 PixRect = QRect( 0, 0, newWidth, newHeight );
139 buffer->resize( width(), height() );
142 void KPlotWidget::paintEvent( QPaintEvent* /* e */ ) {
143 QPainter p;
145 p.begin( buffer );
146 p.fillRect( 0, 0, width(), height(), bgColor() );
147 p.translate( leftPadding(), topPadding() );
149 drawObjects( &p );
150 drawBox( &p );
152 p.end();
153 bitBlt( this, 0, 0, buffer );
156 void KPlotWidget::drawObjects( QPainter *p ) {
157 for ( KPlotObject *po = ObjectList.first(); po; po = ObjectList.next() ) {
159 if ( po->points()->count() ) {
160 //draw the plot object
161 p->setPen( QColor( po->color() ) );
163 switch ( po->type() ) {
164 case KPlotObject::POINTS :
166 p->setBrush( QColor( po->color() ) );
168 for ( DPoint *dp = po->points()->first(); dp; dp = po->points()->next() ) {
169 QPoint q = dp->qpoint( PixRect, DataRect );
170 int x1 = q.x() - po->size()/2;
171 int y1 = q.y() - po->size()/2;
173 switch( po->param() ) {
174 case KPlotObject::CIRCLE : p->drawEllipse( x1, y1, po->size(), po->size() ); break;
175 case KPlotObject::SQUARE : p->drawRect( x1, y1, po->size(), po->size() ); break;
176 case KPlotObject::LETTER : p->drawText( q, po->name().left(1) ); break;
177 default: p->drawPoint( q );
181 p->setBrush( Qt::NoBrush );
182 break;
185 case KPlotObject::CURVE :
187 p->setPen( QPen( QColor( po->color() ), po->size(), (QPen::PenStyle)po->param() ) );
188 DPoint *dp = po->points()->first();
189 p->moveTo( dp->qpoint( PixRect, DataRect ) );
190 for ( dp = po->points()->next(); dp; dp = po->points()->next() )
191 p->lineTo( dp->qpoint( PixRect, DataRect ) );
192 break;
195 case KPlotObject::LABEL : //draw label centered at point in x, and slightly below point in y.
197 QPoint q = po->points()->first()->qpoint( PixRect, DataRect );
198 p->drawText( q.x()-20, q.y()+6, 40, 10, Qt::AlignCenter | Qt::DontClip, po->name() );
199 break;
202 case KPlotObject::POLYGON :
204 p->setPen( QPen( QColor( po->color() ), po->size(), (QPen::PenStyle)po->param() ) );
205 p->setBrush( po->color() );
207 QPointArray a( po->count() );
209 unsigned int i=0;
210 for ( DPoint *dp = po->points()->first(); dp; dp = po->points()->next() )
211 a.setPoint( i++, dp->qpoint( PixRect, DataRect ) );
213 p->drawPolygon( a );
214 break;
217 case KPlotObject::UNKNOWN_TYPE : break;
223 double KPlotWidget::dmod( double a, double b ) { return ( b * ( ( a / b ) - int( a / b ) ) ); }
225 void KPlotWidget::drawBox( QPainter *p ) {
226 //First, fill in padding region with bgColor() to mask out-of-bounds plot data
227 p->setPen( bgColor() );
228 p->setBrush( bgColor() );
230 //left padding ( don't forget: we have translated by XPADDING, YPADDING )
231 p->drawRect( -leftPadding(), -topPadding(), leftPadding(), height() );
233 //right padding
234 p->drawRect( PixRect.width(), -topPadding(), rightPadding(), height() );
236 //top padding
237 p->drawRect( 0, -topPadding(), PixRect.width(), topPadding() );
239 //bottom padding
240 p->drawRect( 0, PixRect.height(), PixRect.width(), bottomPadding() );
242 if ( ShowGrid ) {
243 //Grid lines are placed at locations of primary axes' major tickmarks
244 p->setPen( gridColor() );
246 //vertical grid lines
247 double x0 = x() - dmod( x(), dXtick ); //zeropoint; x(i) is this plus i*dXtick1
248 for ( int ix = 0; ix <= nmajX+1; ix++ ) {
249 int px = int( PixRect.width() * ( (x0 + ix*dXtick - x())/dataWidth() ) );
250 p->drawLine( px, 0, px, PixRect.height() );
253 //horizontal grid lines
254 double y0 = y() - dmod( y(), dYtick ); //zeropoint; y(i) is this plus i*mX
255 for ( int iy = 0; iy <= nmajY+1; iy++ ) {
256 int py = int( PixRect.height() * ( (y0 + iy*dYtick - y())/dataHeight() ) );
257 p->drawLine( 0, py, PixRect.width(), py );
261 p->setPen( fgColor() );
262 p->setBrush( Qt::NoBrush );
264 if ( ShowAxes ) p->drawRect( PixRect ); //box outline
266 if ( ShowTickMarks ) {
267 //spacing between minor tickmarks (in data units)
268 double dminX = dXtick/nminX;
269 double dminY = dYtick/nminY;
271 //set small font for tick labels
272 QFont f = p->font();
273 int s = f.pointSize();
274 f.setPointSize( s - 2 );
275 p->setFont( f );
277 //--- Draw X tickmarks---//
278 double x0 = x() - dmod( x(), dXtick ); //zeropoint; tickmark i is this plus i*dXtick (in data units)
279 if ( x() < 0.0 ) x0 -= dXtick;
281 for ( int ix = 0; ix <= nmajX+1; ix++ ) {
282 //position of tickmark i (in screen units)
283 int px = int( PixRect.width() * ( (x0 + ix*dXtick - x() )/dataWidth() ) );
285 if ( px > 0 && px < PixRect.width() ) {
286 p->drawLine( px, PixRect.height() - 2, px, PixRect.height() - BIGTICKSIZE - 2 );
287 p->drawLine( px, 0, px, BIGTICKSIZE );
290 //tick label
291 if ( ShowTickLabels ) {
292 double lab = x0 + ix*dXtick;
293 if ( fabs(lab)/dXtick < 0.00001 ) lab = 0.0; //fix occassional roundoff error with "0.0" label
295 QString str = QString( "%1" ).arg( lab, 0, 'g', 2 );
296 if ( px > 0 && px < PixRect.width() ) {
297 QRect r( px - BIGTICKSIZE, PixRect.height()+BIGTICKSIZE, 2*BIGTICKSIZE, BIGTICKSIZE );
298 p->drawText( r, Qt::AlignCenter | Qt::DontClip, str );
302 //draw minor ticks
303 for ( int j=0; j < nminX; j++ ) {
304 //position of minor tickmark j (in screen units)
305 int pmin = int( px + PixRect.width()*j*dminX/dataWidth() );
307 if ( pmin > 0 && pmin < PixRect.width() ) {
308 p->drawLine( pmin, PixRect.height() - 2, pmin, PixRect.height() - SMALLTICKSIZE - 2 );
309 p->drawLine( pmin, 0, pmin, SMALLTICKSIZE );
314 //--- Draw Y tickmarks---//
315 double y0 = y() - dmod( y(), dYtick ); //zeropoint; tickmark i is this plus i*dYtick1 (in data units)
316 if ( y() < 0.0 ) y0 -= dYtick;
318 for ( int iy = 0; iy <= nmajY+1; iy++ ) {
319 //position of tickmark i (in screen units)
320 int py = PixRect.height() - int( PixRect.height() * ( (y0 + iy*dYtick - y())/dataHeight() ) );
321 if ( py > 0 && py < PixRect.height() ) {
322 p->drawLine( 0, py, BIGTICKSIZE, py );
323 p->drawLine( PixRect.width()-2, py, PixRect.width()-BIGTICKSIZE-2, py );
326 //tick label
327 if ( ShowTickLabels ) {
328 double lab = y0 + iy*dYtick;
329 if ( fabs(lab)/dYtick < 0.00001 ) lab = 0.0; //fix occassional roundoff error with "0.0" label
331 QString str = QString( "%1" ).arg( lab, 0, 'g', 2 );
332 if ( py > 0 && py < PixRect.height() ) {
333 QRect r( -2*BIGTICKSIZE, py-SMALLTICKSIZE, 2*BIGTICKSIZE, 2*SMALLTICKSIZE );
334 p->drawText( r, Qt::AlignCenter | Qt::DontClip, str );
338 //minor ticks
339 for ( int j=0; j < nminY; j++ ) {
340 //position of minor tickmark j (in screen units)
341 int pmin = int( py - PixRect.height()*j*dminY/dataHeight() );
342 if ( pmin > 0 && pmin < PixRect.height() ) {
343 p->drawLine( 0, pmin, SMALLTICKSIZE, pmin );
344 p->drawLine( PixRect.width()-2, pmin, PixRect.width()-SMALLTICKSIZE-2, pmin );
347 } //end draw Y tickmarks
348 } //end if ( ShowTickMarks )
350 //Draw X Axis Label
351 if ( ! XAxisLabel.isEmpty() ) {
352 QRect r( 0, PixRect.height() + 2*YPADDING, PixRect.width(), YPADDING );
353 p->drawText( r, Qt::AlignCenter, XAxisLabel );
356 //Draw Y Axis Label. We need to draw the text sideways.
357 if ( ! YAxisLabel.isEmpty() ) {
358 //store current painter translation/rotation state
359 p->save();
361 //translate coord sys to left corner of axis label rectangle, then rotate 90 degrees.
362 p->translate( -3*XPADDING, PixRect.height() );
363 p->rotate( -90.0 );
365 QRect r( 0, 0, PixRect.height(), XPADDING );
366 p->drawText( r, Qt::AlignCenter, YAxisLabel ); //draw the label, now that we are sideways
368 p->restore(); //restore translation/rotation state
372 int KPlotWidget::leftPadding() const {
373 if ( LeftPadding >= 0 ) return LeftPadding;
374 if ( ! YAxisLabel.isEmpty() && ShowTickLabels ) return 3*XPADDING;
375 if ( ! YAxisLabel.isEmpty() || ShowTickLabels ) return 2*XPADDING;
376 return XPADDING;
379 int KPlotWidget::rightPadding() const {
380 if ( RightPadding >= 0 ) return RightPadding;
381 return XPADDING;
384 int KPlotWidget::topPadding() const {
385 if ( TopPadding >= 0 ) return TopPadding;
386 return YPADDING;
389 int KPlotWidget::bottomPadding() const {
390 if ( BottomPadding >= 0 ) return BottomPadding;
391 if ( ! XAxisLabel.isEmpty() && ShowTickLabels ) return 3*YPADDING;
392 if ( ! XAxisLabel.isEmpty() || ShowTickLabels ) return 2*YPADDING;
393 return YPADDING;
396 #include "kplotwidget.moc"