kchart

KDChartBWPainter.cpp
1/*
2 KDChart - a multi-platform charting engine
3 */
4
5/****************************************************************************
6 ** Copyright (C) 2001-2003 Klarälvdalens Datakonsult AB. All rights reserved.
7 **
8 ** This file is part of the KDChart library.
9 **
10 ** This file may be distributed and/or modified under the terms of the
11 ** GNU General Public License version 2 as published by the Free Software
12 ** Foundation and appearing in the file LICENSE.GPL included in the
13 ** packaging of this file.
14 **
15 ** Licensees holding valid commercial KDChart licenses may use this file in
16 ** accordance with the KDChart Commercial License Agreement provided with
17 ** the Software.
18 **
19 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
20 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21 **
22 ** See http://www.klaralvdalens-datakonsult.se/?page=products for
23 ** information about KDChart Commercial License Agreements.
24 **
25 ** Contact info@klaralvdalens-datakonsult.se if any conditions of this
26 ** licensing are not clear to you.
27 **
28 **********************************************************************/
29#include "KDChartBWPainter.h"
30#include <KDChartParams.h>
31#include "KDChartTextPiece.h"
32
33#include <tqpainter.h>
34#include <tqmemarray.h>
35
36#include <stdlib.h>
37
50KDChartAxesPainter( params )
51{
52 // This constructor intentionally left blank so far; we cannot setup the
53 // geometry yet since we do not know the size of the painter.
54}
55
56
61{
62 // intentionally left blank
63}
64
65
66void quicksort( TQMemArray<double>& a, int lo, int hi )
67{
68 int i=lo, j=hi;
69 double h;
70 double x=a[(lo+hi)/2];
71 do
72 {
73 while (a[i]<x) i++;
74 while (a[j]>x) j--;
75 if (i<=j)
76 {
77 h=a[i]; a[i]=a[j]; a[j]=h;
78 i++; j--;
79 }
80 } while (i<=j);
81 if (lo<j) quicksort(a, lo, j);
82 if (i<hi) quicksort(a, i, hi);
83}
84
85
86// The following function returns the number of used cells containing a double.
87int KDChartBWPainter::calculateStats( KDChartTableDataBase& data,
88 uint dataset )
89{
90 const uint nMax = data.usedCols();
91 int nUsed = 0;
92 TQMemArray<double> values( nMax );
93 double sum = 0.0;
94 TQVariant vVal;
95 if( data.sorted() ){
96 for( uint i=0; i<nMax; ++i){
97 if( data.cellCoord( dataset, i, vVal, 1 ) &&
98 TQVariant::Double == vVal.type() ) {
99 values[nUsed] = vVal.toDouble();
100 sum += values[nUsed];
101 ++nUsed;
102 }
103 }
104 }else{
105 // make copy of the dataset and look if it is sorted
106 bool sorted = true;
107 double last = 0.0; // <-- avoids an annoying compile-time warning
108 for( uint i=0; i<nMax; ++i){
109 if( data.cellCoord( dataset, i, vVal, 1 ) &&
110 TQVariant::Double == vVal.type() ) {
111 values[nUsed] = vVal.toDouble();
112 if( nUsed // skip 1st value
113 && last > values[nUsed] )
114 sorted = false;
115 last = values[nUsed];
116 sum += last;
117 ++nUsed;
118 }
119 }
120 if( !sorted ){
121 // sort our copy of the dataset
122 quicksort( values, 0, nUsed-1 );
123 }
124 }
125
126 // Values now contains all used values without empty gaps.
127 // nUsed contains their number, so values[nUsed-1] is the last value.
128
129 if( nUsed ){
130 // store some values
131 stats[ KDChartParams::MaxValue ] = values[nUsed-1];
132 stats[ KDChartParams::MinValue ] = values[0];
133
134 stats[ KDChartParams::MeanValue ] = sum / nUsed;
135 // calculate statistics
136 bool bOdd = 1 == nUsed % 2;
137 // find median
138 int nUd2 = nUsed/2;
139 if( bOdd )
140 stats[ KDChartParams::Median ] = values[ nUd2 ];
141 else
142 stats[ KDChartParams::Median ] =
143 (values[ TQMAX(nUd2-1, 0) ] + values[ nUd2 ]) /2;
144 // find last value of lower quartile
145 nLastQ1 = TQMAX( nUd2-1, 0 );
146 // find 1st value of lower quartile
147 nFirstQ1 = nLastQ1 / 2;
148
149 // determine how many values are below the median ( == how many are above it)
150 int nLowerCount = nLastQ1 - nFirstQ1 + 1;
151
152 // find 1st value of upper quartile
153 nFirstQ3 = bOdd ? TQMIN( nUd2+1, nUsed-1 ) : nUd2;
154 // find last value of upper quartile
155 nLastQ3 = nFirstQ3 + nLowerCount - 1;
156
157 // find quartiles
158 bool bOdd2 = 1 == nLowerCount % 2;
159 // find lower quartile
160 if( bOdd2 )
161 stats[ KDChartParams::Quartile1 ] = values[ nFirstQ1 ];
162 else
163 stats[ KDChartParams::Quartile1 ] =
164 (values[ TQMAX(nFirstQ1-1, 0) ] + values[ nFirstQ1 ]) /2;
165 // find upper quartile
166 if( bOdd2 ){
167 stats[ KDChartParams::Quartile3 ] = values[ nLastQ3 ];
168}
169 else{
170 //tqDebug(" "+TQString::number(nLastQ3)+" "+TQString::number(KDChartParams::Quartile3)
171 // +" "+TQString::number(nUsed)+" "+TQString::number(TQMIN(nLastQ3+1, nUsed-1)));
172 stats[ KDChartParams::Quartile3 ] =
173 (values[ nLastQ3 ] + values[ TQMIN(nLastQ3+1, nUsed-1) ]) /2;
174}
175 // find the interquartile range (IQR)
176 double iqr = stats[ KDChartParams::Quartile3 ] - stats[ KDChartParams::Quartile1 ];
177
178 // calculate the fences
179 double upperInner, lowerInner, upperOuter, lowerOuter;
180 params()->bWChartFences( upperInner, lowerInner,
181 upperOuter, lowerOuter );
182 stats[ KDChartParams::UpperInnerFence ] =
183 stats[ KDChartParams::Quartile3 ] + iqr * upperInner;
184 stats[ KDChartParams::LowerInnerFence ] =
185 stats[ KDChartParams::Quartile1 ] - iqr * lowerInner;
186 stats[ KDChartParams::UpperOuterFence ] =
187 stats[ KDChartParams::Quartile3 ] + iqr * upperOuter;
188 stats[ KDChartParams::LowerOuterFence ] =
189 stats[ KDChartParams::Quartile1 ] - iqr * lowerOuter;
190 }
191 return nUsed;
192}
193
194
208TQString KDChartBWPainter::fallbackLegendText( uint dataset ) const
209{
210 return TQObject::tr( "Series " ) + TQString::number( dataset + 1 );
211}
212
213
224{
225 return data->usedRows();
226}
227
228
229bool KDChartBWPainter::isNormalMode() const
230{
231 return KDChartParams::BWNormal == params()->bWChartSubType();
232}
233
234int KDChartBWPainter::clipShiftUp( bool, double ) const
235{
236 return 0;
237}
238
249void KDChartBWPainter::specificPaintData( TQPainter* painter,
250 const TQRect& ourClipRect,
252 KDChartDataRegionList* /*regions*/,
253 const KDChartAxisParams* axisPara,
254 bool /*bNormalMode*/,
255 uint /*chart*/,
256 double logWidth,
257 double /*areaWidthP1000*/,
258 double logHeight,
259 double /*axisYOffset*/,
260 double /*minColumnValue*/,
261 double /*maxColumnValue*/,
262 double /*columnValueDistance*/,
263 uint chartDatasetStart,
264 uint chartDatasetEnd,
265 uint datasetStart,
266 uint datasetEnd )
267{
268 //double areaHeightP1000 = logHeight / 1000.0;
269
270 uint datasetNum = ( chartDatasetEnd - chartDatasetStart ) + 1;
271
272 double pixelsPerUnit = 0.0;
273 if( 0.0 != axisPara->trueAxisHigh() - axisPara->trueAxisLow() )
274 pixelsPerUnit = logHeight / (axisPara->trueAxisHigh() - axisPara->trueAxisLow());
275 else
276 pixelsPerUnit = logHeight / 10;
277
278 // Distance between the individual "stocks"
279 double pointDist = logWidth / ( ( double ) datasetNum );
280
281 // compute the position of the 0 axis
282 double zeroXAxisI = axisPara->axisZeroLineStartY() - _dataRect.y();
283
284 const int lineWidth = static_cast<int>( pointDist / 66.0 ) * TQMAX(params()->lineWidth(), 1);
285 const int lineWidthD2 = lineWidth * 2 / 3;
286
287 const bool noBrush = TQt::NoBrush == params()->bWChartBrush().style();
288
289 // Loop over the datasets, draw one box and whisker shape for each series.
290 for ( uint dataset = chartDatasetStart;
291 dataset <= chartDatasetEnd;
292 ++dataset ) {
293
294 if( dataset >= datasetStart &&
295 dataset <= datasetEnd &&
296 0 < calculateStats( *data, dataset ) ) {
297 const TQColor color( params()->dataColor( dataset ) );
298 // transform calculated values
299 double drawUOF = stats[ KDChartParams::UpperOuterFence ] * pixelsPerUnit;
300 double drawUIF = stats[ KDChartParams::UpperInnerFence ] * pixelsPerUnit;
301 double drawQu3 = stats[ KDChartParams::Quartile3 ] * pixelsPerUnit;
302 double drawMed = stats[ KDChartParams::Median ] * pixelsPerUnit;
303 double drawQu1 = stats[ KDChartParams::Quartile1 ] * pixelsPerUnit;
304 double drawLIF = stats[ KDChartParams::LowerInnerFence ] * pixelsPerUnit;
305 double drawLOF = stats[ KDChartParams::LowerOuterFence ] * pixelsPerUnit;
306 double drawMax = stats[ KDChartParams::MaxValue ] * pixelsPerUnit;
307 double drawMin = stats[ KDChartParams::MinValue ] * pixelsPerUnit;
308 double drawMean= stats[ KDChartParams::MeanValue ] * pixelsPerUnit;
309 // get whisker values
310 double drawUWhisker = TQMIN(drawUIF, drawMax);
311 double drawLWhisker = TQMAX(drawLIF, drawMin);
312 // get the box width
313 const int boxWidth = TQMAX( 6, static_cast<int>( pointDist * 0.2 ) );
314 // get marker size (for the outliers and/or for the median value)
315 int markWidth = params()->bWChartOutValMarkerSize();
316 bool drawOutliers = ( 0 != markWidth );
317 if( drawOutliers ){
318 if( 0 > markWidth)
319 markWidth = TQMAX( 4, markWidth * boxWidth / -100 );
320 else
321 markWidth = TQMAX( 4, markWidth );
322 }
323 else
324 markWidth = boxWidth * 25 / 100; // use the default for the Median marker
325
326 painter->setPen( TQPen( color, lineWidth ) );
327 painter->setBrush( params()->bWChartBrush() );
328 // draw the box
329 int boxWidthD2 = boxWidth / 2;
330 int xPos = static_cast<int>(
331 pointDist * ( (double)(dataset - chartDatasetStart) + 0.5 )
332 - lineWidth / 2);
333 painter->drawRect( xPos - boxWidthD2,
334 static_cast<int>( zeroXAxisI - drawQu3 ),
335 boxWidth,
336 static_cast<int>( drawQu3 - drawQu1) + 1 );
337 // draw the median
338 painter->drawLine( xPos - boxWidthD2,
339 static_cast<int>( zeroXAxisI - drawMed ),
340 xPos - boxWidthD2 + boxWidth,
341 static_cast<int>( zeroXAxisI - drawMed ) );
342 // draw the whisker
343 painter->drawLine( xPos - boxWidthD2,
344 static_cast<int>( zeroXAxisI - drawUWhisker ),
345 xPos - boxWidthD2 + boxWidth,
346 static_cast<int>( zeroXAxisI - drawUWhisker ) );
347 painter->drawLine( xPos,
348 static_cast<int>( zeroXAxisI - drawUWhisker ),
349 xPos,
350 static_cast<int>( zeroXAxisI - drawQu3 ) );
351 painter->drawLine( xPos - boxWidthD2,
352 static_cast<int>( zeroXAxisI - drawLWhisker ),
353 xPos - boxWidthD2 + boxWidth,
354 static_cast<int>( zeroXAxisI - drawLWhisker ) );
355 painter->drawLine( xPos,
356 static_cast<int>( zeroXAxisI - drawLWhisker ),
357 xPos,
358 static_cast<int>( zeroXAxisI - drawQu1 ) );
359 // draw the values
360 int xPos2 = static_cast<int>(
361 pointDist * ( (double)(dataset - chartDatasetStart) + 0.5 )
362 - lineWidthD2 / 2);
363 int markWidthD2 = TQMAX(markWidth / 2, 2);
364 int markWidthD25 = TQMAX(static_cast<int>( 0.85 * markWidth / 2.0), 2);
365 int markWidthD35 = TQMAX(static_cast<int>( 0.85 * markWidth / 3.0), 2);
366 // draw the outliers
367 if( drawOutliers ){
368 const uint nMax = data->usedCols();
369 int drawVal;
370 TQVariant vVal;
371 for( uint i=0; i<nMax; ++i)
372 if( data->cellCoord( dataset, i, vVal, 1 ) &&
373 TQVariant::Double == vVal.type() ) {
374 drawVal = static_cast<int>( pixelsPerUnit * vVal.toDouble() );
375 if( drawLOF > drawVal || drawUOF < drawVal ) {
376 painter->setPen( TQt::NoPen );
377 painter->drawChord( xPos2 - markWidthD2,
378 static_cast<int>(zeroXAxisI - drawVal) - markWidthD2,
379 markWidth,
380 markWidth,
381 0, 5760 );
382 painter->setPen( TQPen( color, lineWidthD2 ) );
383 painter->drawArc( xPos2 - markWidthD2,
384 static_cast<int>(zeroXAxisI - drawVal) - markWidthD2,
385 markWidth,
386 markWidth,
387 0, 5760 );
388 } else if( drawLIF > drawVal || drawUIF < drawVal ) {
389 painter->setPen( TQt::NoPen );
390 painter->drawChord( xPos2 - markWidthD2,
391 static_cast<int>( zeroXAxisI - drawVal) - markWidthD2,
392 markWidth,
393 markWidth,
394 0, 5760 );
395 painter->setPen( TQPen( color, lineWidthD2 ) );
396 painter->drawLine( xPos2,
397 static_cast<int>(zeroXAxisI - drawVal) - markWidthD2,
398 xPos2,
399 static_cast<int>(zeroXAxisI - drawVal) + markWidthD2 );
400 painter->drawLine( xPos2 - markWidthD25,
401 static_cast<int>(zeroXAxisI - drawVal) - markWidthD35,
402 xPos2 + markWidthD25,
403 static_cast<int>(zeroXAxisI - drawVal) + markWidthD35 );
404 painter->drawLine( xPos2 + markWidthD25,
405 static_cast<int>(zeroXAxisI - drawVal) - markWidthD35,
406 xPos2 - markWidthD25,
407 static_cast<int>(zeroXAxisI - drawVal) + markWidthD35 );
408 }
409 }
410 }
411 // draw the mean value
412 bool evenLineWidthD2 = floor( ((double)lineWidthD2)/2.0 ) == ((double)lineWidthD2)/2.0;
413 painter->setPen( params()->bWChartBrush().color() );
414 painter->drawChord( xPos2 - markWidthD2-1 - (evenLineWidthD2 ? 0 : 1),
415 static_cast<int>( zeroXAxisI - drawMean ) - markWidthD2 - 1,
416 markWidthD2*2 + (evenLineWidthD2 ? 2 : 3),
417 markWidthD2*2 + (evenLineWidthD2 ? 2 : 3),
418 0, 5760 );
419 if( noBrush ) {
420 // use different color brightness for the Mean marker
421 int h,s,v;
422 color.hsv(&h,&s,&v);
423 painter->setPen( TQPen( 128 > v ? color.light(150) : color.dark(150),
424 lineWidthD2 ) );
425 } else
426 painter->setPen( TQPen( color, lineWidthD2 ) );
427 painter->drawLine( xPos2 - markWidthD2 - (evenLineWidthD2 ? 0 : 1),
428 static_cast<int>( zeroXAxisI - drawMean ),
429 xPos2 + markWidthD2,
430 static_cast<int>( zeroXAxisI - drawMean ) );
431 painter->drawLine( xPos2 - (evenLineWidthD2 ? 0 : 1),
432 static_cast<int>( zeroXAxisI - drawMean ) - markWidthD2,
433 xPos2 - (evenLineWidthD2 ? 0 : 1),
434 static_cast<int>( zeroXAxisI - drawMean ) + markWidthD2 + (evenLineWidthD2 ? 0 : 1) );
435
436 // draw the statistical value texts
437 painter->setPen( TQt::NoPen );
438 for( int ii = KDChartParams::BWStatValSTART;
439 ii <= KDChartParams::BWStatValEND;
440 ++ii ){
442 if( params()->bWChartPrintStatistics( i ) ) {
443 TQFont statFont( params()->bWChartStatisticsFont( i ) );
444 float nTxtHeight = statFont.pointSizeFloat();
445 if ( params()->bWChartStatisticsUseRelSize( i ) ) {
446 nTxtHeight = params()->bWChartStatisticsFontRelSize( i )
447 * boxWidth / 100;
448 statFont.setPointSizeFloat( nTxtHeight );
449 }
450 double drawStat = pixelsPerUnit * stats[i];
451 KDChartTextPiece statText( painter, TQString::number( stats[i] ),
452 statFont );
453 int tw = statText.width();
454 int xDelta = ( KDChartParams::MaxValue == i
455 || KDChartParams::MeanValue == i
456 || KDChartParams::MinValue == i )
457 ? -1 * (tw + static_cast<int>( 1.3*boxWidthD2 ))
458 : static_cast<int>( 1.3*boxWidthD2 );
459 TQBrush brush( params()->bWChartStatisticsBrush( i ) );
460 painter->setBrush( brush );
461 int y = static_cast<int>( zeroXAxisI - drawStat - nTxtHeight/2);
462 painter->drawRect( xPos + xDelta - 1,
463 y,
464 tw + 2,
465 TQMAX(static_cast < int > ( nTxtHeight ), 8) + 1 );
466 statText.draw( painter,
467 xPos + xDelta,
468 y,
469 ourClipRect,
470 params()->bWChartStatisticsColor( i ),
471 0 );
472 }
473 }
474
475 } else
476 continue; // we cannot display this value
477 }
478}
Header for all common chart parameters.
A common base class for classes that implement chart painters for chart types ith axes.
access the chart axis parameters.
double trueAxisHigh() const
Returns the true end value of the ordinate axis labels as is was calculated and set by setTrueAxisLow...
double trueAxisLow() const
Returns the true start value of the ordinate axis labels as is was calculated and set by setTrueAxisL...
double axisZeroLineStartY() const
Returns the not-rounded y-position where drawing of this axis zero line started.
virtual void specificPaintData(TQPainter *painter, const TQRect &ourClipRect, KDChartTableDataBase *data, KDChartDataRegionList *regions, const KDChartAxisParams *axisPara, bool bNormalMode, uint chart, double logWidth, double areaWidthP1000, double logHeight, double axisYOffset, double minColumnValue, double maxColumnValue, double columnValueDistance, uint chartDatasetStart, uint chartDatasetEnd, uint datasetStart, uint datasetEnd)
Paints the actual data area and registers the region for the data points if regions is not 0.
virtual TQString fallbackLegendText(uint dataset) const
This method is a specialization that returns a fallback legend text appropriate for BW that do not ha...
virtual ~KDChartBWPainter()
Destructor.
virtual uint numLegendFallbackTexts(KDChartTableDataBase *data) const
This methods returns the number of elements to be shown in the legend in case fallback texts are used...
KDChartBWPainter(KDChartParams *params)
Constructor.
The collection class used by KD Chart to store data region information.
Bundles the display parameters of a chart.
int bWChartOutValMarkerSize() const
Returns how big outlier markers (representing the values outside the inner fences) will be drawn.
void bWChartFences(double &upperInner, double &lowerInner, double &upperOuter, double &lowerOuter) const
Retrievs the factors to be used to calculate the position of the inner (or outer, resp....
int bWChartStatisticsFontRelSize(BWStatVal statValue) const
Returns the relative size (in per mille of the chart width) of the font to be used for printing the r...
TQBrush bWChartBrush() const
Returns the brush to be used to fill the BoxWhisker charts.
TQColor bWChartStatisticsColor(BWStatVal statValue) const
Returns the colour of the font to be used for printing the respective statistical values.
BWStatVal
The different statistical values that can be printed into or asked from a Box and Whisker chart.
int lineWidth() const
Returns the line width of the lines in line charts.
bool bWChartPrintStatistics(BWStatVal statValue) const
Returns whether the respective statistical values will be printed near to the drawing.
BWChartSubType bWChartSubType() const
Returns the BoxWhisker chart subtype.
KD Chart's build-in table data for an easy way of storing data values.
virtual uint usedCols() const
Returns the number of cols in the table that actually contain data.
virtual bool cellCoord(uint _row, uint _col, TQVariant &_value, int coordinate=1) const =0
Returns one of the coordinate data value(s) stored in a cell.
virtual uint usedRows() const
Returns the number of rows in the table that actually contain data.
virtual bool sorted() const
Returns whether the table is sorted.