1 /***************************************************************************
2 kplotwidget.cpp - A widget for plotting in KStars
4 begin : Sun 18 May 2003
5 copyright : (C) 2003 by Jason Harris
6 email : kstars@30doradus.org
7 ***************************************************************************/
9 /***************************************************************************
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. *
16 ***************************************************************************/
18 #include <math.h> //for log10(), pow(), modf()
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
);
35 setLimits( x1
, x2
, y1
, y2
);
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();
45 setBGColor( QColor( "black" ) );
46 setFGColor( QColor( "white" ) );
47 setGridColor( QColor( "grey" ) );
49 ObjectList
.setAutoDelete( TRUE
);
52 KPlotWidget::~KPlotWidget()
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
);
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 );
75 if ( dataHeight() == 0.0 ) {
76 kdWarning() << "Y range invalid! " << y() << " to " << y2() << endl
;
77 DataRect
.setHeight( 1.0 );
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
) {
93 //determine size of region to be drawn, in draw units
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
);
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
108 } else if ( t
< 10.0 ) { //factor of 2
112 } else if ( t
< 20.0 ) { //factor of 4
116 } else { //factor of 5
122 if ( iaxis
==1 ) { //X axis
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 */ ) {
146 p
.fillRect( 0, 0, width(), height(), bgColor() );
147 p
.translate( leftPadding(), topPadding() );
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
);
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
) );
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() );
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() );
210 for ( DPoint
*dp
= po
->points()->first(); dp
; dp
= po
->points()->next() )
211 a
.setPoint( i
++, dp
->qpoint( PixRect
, DataRect
) );
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() );
234 p
->drawRect( PixRect
.width(), -topPadding(), rightPadding(), height() );
237 p
->drawRect( 0, -topPadding(), PixRect
.width(), topPadding() );
240 p
->drawRect( 0, PixRect
.height(), PixRect
.width(), bottomPadding() );
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
273 int s
= f
.pointSize();
274 f
.setPointSize( s
- 2 );
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
);
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
);
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
);
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
);
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 )
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
361 //translate coord sys to left corner of axis label rectangle, then rotate 90 degrees.
362 p
->translate( -3*XPADDING
, PixRect
.height() );
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
;
379 int KPlotWidget::rightPadding() const {
380 if ( RightPadding
>= 0 ) return RightPadding
;
384 int KPlotWidget::topPadding() const {
385 if ( TopPadding
>= 0 ) return TopPadding
;
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
;
396 #include "kplotwidget.moc"