2
KDChart - a multi-platform charting engine
5
/****************************************************************************
6
** Copyright (C) 2001-2003 Klar�lvdalens Datakonsult AB. All rights reserved.
8
** This file is part of the KDChart library.
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.
15
** Licensees holding valid commercial KDChart licenses may use this file in
16
** accordance with the KDChart Commercial License Agreement provided with
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.
22
** See http://www.klaralvdalens-datakonsult.se/?page=products for
23
** information about KDChart Commercial License Agreements.
25
** Contact info@klaralvdalens-datakonsult.se if any conditions of this
26
** licensing are not clear to you.
28
**********************************************************************/
29
#include "KDChartBWPainter.h"
30
#include <KDChartParams.h>
31
#include "KDChartTextPiece.h"
34
#if COMPAT_QT_VERSION >= 0x030000
35
#include <qmemarray.h>
38
#define QMemArray QArray
44
\class KDChartBWPainter KDChartBWPainter.h
46
\brief A chart painter implementation that can paint Box&Whisker charts.
50
Constructor. Sets up internal data structures as necessary.
52
\param params the KDChartParams structure that defines the chart
54
KDChartBWPainter::KDChartBWPainter( KDChartParams* params ) :
55
KDChartAxesPainter( params )
57
// This constructor intentionally left blank so far; we cannot setup the
58
// geometry yet since we do not know the size of the painter.
65
KDChartBWPainter::~KDChartBWPainter()
67
// intentionally left blank
71
void quicksort( QMemArray<double>& a, int lo, int hi )
75
double x=a[(lo+hi)/2];
82
h=a[i]; a[i]=a[j]; a[j]=h;
86
if (lo<j) quicksort(a, lo, j);
87
if (i<hi) quicksort(a, i, hi);
91
// The following function returns the number of used cells containing a double.
92
int KDChartBWPainter::calculateStats( KDChartTableDataBase& data,
95
const uint nMax = data.usedCols();
97
QMemArray<double> values( nMax );
100
for( uint i=0; i<nMax; ++i)
101
if( data.cell(dataset, i).isDouble(1) ){
102
values[nUsed] = data.cell(dataset, i).doubleValue(1);
103
sum += values[nUsed];
107
// make copy of the dataset and look if it is sorted
109
double last = 0.0; // <-- avoids an annoying compile-time warning
110
for( uint i=0; i<nMax; ++i){
111
if( data.cell(dataset, i).isDouble(1) ){
112
values[nUsed] = data.cell(dataset, i).doubleValue(1);
113
if( nUsed // skip 1st value
114
&& last > values[nUsed] )
116
last = values[nUsed];
122
// sort our copy of the dataset
123
quicksort( values, 0, nUsed-1 );
127
// Values now contains all used values without empty gaps.
128
// nUsed contains their number, so values[nUsed-1] is the last value.
132
stats[ KDChartParams::MaxValue ] = values[nUsed-1];
133
stats[ KDChartParams::MinValue ] = values[0];
135
stats[ KDChartParams::MeanValue ] = sum / nUsed;
136
// calculate statistics
137
bool bOdd = 1 == nUsed % 2;
141
stats[ KDChartParams::Median ] = values[ nUd2 ];
143
stats[ KDChartParams::Median ] =
144
(values[ QMAX(nUd2-1, 0) ] + values[ nUd2 ]) /2;
145
// find last value of lower quartile
146
nLastQ1 = QMAX( nUd2-1, 0 );
147
// find 1st value of lower quartile
148
nFirstQ1 = nLastQ1 / 2;
150
// determine how many values are below the median ( == how many are above it)
151
int nLowerCount = nLastQ1 - nFirstQ1 + 1;
153
// find 1st value of upper quartile
154
nFirstQ3 = bOdd ? QMIN( nUd2+1, nUsed-1 ) : nUd2;
155
// find last value of upper quartile
156
nLastQ3 = nFirstQ3 + nLowerCount - 1;
159
bool bOdd2 = 1 == nLowerCount % 2;
160
// find lower quartile
162
stats[ KDChartParams::Quartile1 ] = values[ nFirstQ1 ];
164
stats[ KDChartParams::Quartile1 ] =
165
(values[ QMAX(nFirstQ1-1, 0) ] + values[ nFirstQ1 ]) /2;
166
// find upper quartile
168
stats[ KDChartParams::Quartile3 ] = values[ nLastQ3 ];
171
//qDebug(" "+QString::number(nLastQ3)+" "+QString::number(KDChartParams::Quartile3)
172
// +" "+QString::number(nUsed)+" "+QString::number(QMIN(nLastQ3+1, nUsed-1)));
173
stats[ KDChartParams::Quartile3 ] =
174
(values[ nLastQ3 ] + values[ QMIN(nLastQ3+1, nUsed-1) ]) /2;
176
// find the interquartile range (IQR)
177
double iqr = stats[ KDChartParams::Quartile3 ] - stats[ KDChartParams::Quartile1 ];
179
// calculate the fences
180
double upperInner, lowerInner, upperOuter, lowerOuter;
181
params()->bWChartFences( upperInner, lowerInner,
182
upperOuter, lowerOuter );
183
stats[ KDChartParams::UpperInnerFence ] =
184
stats[ KDChartParams::Quartile3 ] + iqr * upperInner;
185
stats[ KDChartParams::LowerInnerFence ] =
186
stats[ KDChartParams::Quartile1 ] - iqr * lowerInner;
187
stats[ KDChartParams::UpperOuterFence ] =
188
stats[ KDChartParams::Quartile3 ] + iqr * upperOuter;
189
stats[ KDChartParams::LowerOuterFence ] =
190
stats[ KDChartParams::Quartile1 ] - iqr * lowerOuter;
197
This method is a specialization that returns a fallback legend text
198
appropriate for BW that do not have the same notion of a dataset like
201
This method is only used when automatic legends are used, because
202
manual and first-column legends do not need fallback texts.
204
\param uint dataset the dataset number for which to generate a
206
\return the fallback text to use for describing the specified
207
dataset in the legend
209
QString KDChartBWPainter::fallbackLegendText( uint dataset ) const
211
return QObject::tr( "Series " ) + QString::number( dataset + 1 );
216
This methods returns the number of elements to be shown in the
217
legend in case fallback texts are used.
219
This method is only used when automatic legends are used, because
220
manual and first-column legends do not need fallback texts.
222
\return the number of fallback texts to use
224
uint KDChartBWPainter::numLegendFallbackTexts( KDChartTableDataBase* data ) const
226
return data->usedRows();
230
bool KDChartBWPainter::isNormalMode() const
232
return KDChartParams::BWNormal == params()->bWChartSubType();
235
int KDChartBWPainter::clipShiftUp( bool, double ) const
241
Paints the actual data area and registers the region for the data
242
points if \a regions is not 0.
244
\param painter the QPainter onto which the chart should be painted
245
\param data the data that will be displayed as a chart
246
\param paint2nd specifies whether the main chart or the additional chart is to be drawn now
247
\param regions a pointer to a list of regions that will be filled
248
with regions representing the data segments, if not null
250
void KDChartBWPainter::specificPaintData( QPainter* painter,
251
const QRect& ourClipRect,
252
KDChartTableDataBase* data,
253
KDChartDataRegionList* /*regions*/,
254
const KDChartAxisParams* axisPara,
255
bool /*bNormalMode*/,
258
double /*areaWidthP1000*/,
260
double /*axisYOffset*/,
261
double /*minColumnValue*/,
262
double /*maxColumnValue*/,
263
double /*columnValueDistance*/,
264
uint chartDatasetStart,
265
uint chartDatasetEnd,
269
//double areaHeightP1000 = logHeight / 1000.0;
271
uint datasetNum = ( chartDatasetEnd - chartDatasetStart ) + 1;
273
double pixelsPerUnit = 0.0;
274
if( 0.0 != axisPara->trueAxisHigh() - axisPara->trueAxisLow() )
275
pixelsPerUnit = logHeight / (axisPara->trueAxisHigh() - axisPara->trueAxisLow());
277
pixelsPerUnit = logHeight / 10;
279
// Distance between the individual "stocks"
280
double pointDist = logWidth / ( ( double ) datasetNum );
282
// compute the position of the 0 axis
283
double zeroXAxisI = axisPara->axisZeroLineStartY() - _dataRect.y();
285
const int lineWidth = static_cast<int>( pointDist / 66.0 ) * QMAX(params()->lineWidth(), 1);
286
const int lineWidthD2 = lineWidth * 2 / 3;
288
const bool noBrush = Qt::NoBrush == params()->bWChartBrush().style();
290
// Loop over the datasets, draw one box and whisker shape for each series.
291
for ( uint dataset = chartDatasetStart;
292
dataset <= chartDatasetEnd;
295
if( dataset >= datasetStart &&
296
dataset <= datasetEnd &&
297
0 < calculateStats( *data, dataset ) ) {
298
const QColor color( params()->dataColor( dataset ) );
299
// transform calculated values
300
double drawUOF = stats[ KDChartParams::UpperOuterFence ] * pixelsPerUnit;
301
double drawUIF = stats[ KDChartParams::UpperInnerFence ] * pixelsPerUnit;
302
double drawQu3 = stats[ KDChartParams::Quartile3 ] * pixelsPerUnit;
303
double drawMed = stats[ KDChartParams::Median ] * pixelsPerUnit;
304
double drawQu1 = stats[ KDChartParams::Quartile1 ] * pixelsPerUnit;
305
double drawLIF = stats[ KDChartParams::LowerInnerFence ] * pixelsPerUnit;
306
double drawLOF = stats[ KDChartParams::LowerOuterFence ] * pixelsPerUnit;
307
double drawMax = stats[ KDChartParams::MaxValue ] * pixelsPerUnit;
308
double drawMin = stats[ KDChartParams::MinValue ] * pixelsPerUnit;
309
double drawMean= stats[ KDChartParams::MeanValue ] * pixelsPerUnit;
310
// get whisker values
311
double drawUWhisker = QMIN(drawUIF, drawMax);
312
double drawLWhisker = QMAX(drawLIF, drawMin);
314
const int boxWidth = QMAX( 6, static_cast<int>( pointDist * 0.2 ) );
315
// get marker size (for the outliers and/or for the median value)
316
int markWidth = params()->bWChartOutValMarkerSize();
317
bool drawOutliers = ( 0 != markWidth );
320
markWidth = QMAX( 4, markWidth * boxWidth / -100 );
322
markWidth = QMAX( 4, markWidth );
325
markWidth = boxWidth * 25 / 100; // use the default for the Median marker
327
painter->setPen( QPen( color, lineWidth ) );
328
painter->setBrush( params()->bWChartBrush() );
330
int boxWidthD2 = boxWidth / 2;
331
int xPos = static_cast<int>(
332
pointDist * ( (double)(dataset - chartDatasetStart) + 0.5 )
334
painter->drawRect( xPos - boxWidthD2,
335
static_cast<int>( zeroXAxisI - drawQu3 ),
337
static_cast<int>( drawQu3 - drawQu1) + 1 );
339
painter->drawLine( xPos - boxWidthD2,
340
static_cast<int>( zeroXAxisI - drawMed ),
341
xPos - boxWidthD2 + boxWidth,
342
static_cast<int>( zeroXAxisI - drawMed ) );
344
painter->drawLine( xPos - boxWidthD2,
345
static_cast<int>( zeroXAxisI - drawUWhisker ),
346
xPos - boxWidthD2 + boxWidth,
347
static_cast<int>( zeroXAxisI - drawUWhisker ) );
348
painter->drawLine( xPos,
349
static_cast<int>( zeroXAxisI - drawUWhisker ),
351
static_cast<int>( zeroXAxisI - drawQu3 ) );
352
painter->drawLine( xPos - boxWidthD2,
353
static_cast<int>( zeroXAxisI - drawLWhisker ),
354
xPos - boxWidthD2 + boxWidth,
355
static_cast<int>( zeroXAxisI - drawLWhisker ) );
356
painter->drawLine( xPos,
357
static_cast<int>( zeroXAxisI - drawLWhisker ),
359
static_cast<int>( zeroXAxisI - drawQu1 ) );
361
int xPos2 = static_cast<int>(
362
pointDist * ( (double)(dataset - chartDatasetStart) + 0.5 )
364
int markWidthD2 = QMAX(markWidth / 2, 2);
365
int markWidthD25 = QMAX(static_cast<int>( 0.85 * markWidth / 2.0), 2);
366
int markWidthD35 = QMAX(static_cast<int>( 0.85 * markWidth / 3.0), 2);
369
const uint nMax = data->usedCols();
371
for( uint i=0; i<nMax; ++i)
372
if( data->cell(dataset, i).isDouble(1) ) {
373
drawVal = static_cast<int>( pixelsPerUnit *
374
data->cell(dataset, i).doubleValue(1) );
375
if( drawLOF > drawVal || drawUOF < drawVal ) {
376
painter->setPen( Qt::NoPen );
377
painter->drawChord( xPos2 - markWidthD2,
378
static_cast<int>(zeroXAxisI - drawVal) - markWidthD2,
382
painter->setPen( QPen( color, lineWidthD2 ) );
383
painter->drawArc( xPos2 - markWidthD2,
384
static_cast<int>(zeroXAxisI - drawVal) - markWidthD2,
388
} else if( drawLIF > drawVal || drawUIF < drawVal ) {
389
painter->setPen( Qt::NoPen );
390
painter->drawChord( xPos2 - markWidthD2,
391
static_cast<int>( zeroXAxisI - drawVal) - markWidthD2,
395
painter->setPen( QPen( color, lineWidthD2 ) );
396
painter->drawLine( xPos2,
397
static_cast<int>(zeroXAxisI - drawVal) - markWidthD2,
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 );
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),
420
// use different color brightness for the Mean marker
423
painter->setPen( QPen( 128 > v ? color.light(150) : color.dark(150),
426
painter->setPen( QPen( color, lineWidthD2 ) );
427
painter->drawLine( xPos2 - markWidthD2 - (evenLineWidthD2 ? 0 : 1),
428
static_cast<int>( zeroXAxisI - drawMean ),
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) );
436
// draw the statistical value texts
437
painter->setPen( Qt::NoPen );
438
for( int ii = KDChartParams::BWStatValSTART;
439
ii <= KDChartParams::BWStatValEND;
441
KDChartParams::BWStatVal i = (KDChartParams::BWStatVal)ii;
442
if( params()->bWChartPrintStatistics( i ) ) {
443
QFont statFont( params()->bWChartStatisticsFont( i ) );
444
float nTxtHeight = statFont.pointSizeFloat();
445
if ( params()->bWChartStatisticsUseRelSize( i ) ) {
446
nTxtHeight = params()->bWChartStatisticsFontRelSize( i )
448
statFont.setPointSizeFloat( nTxtHeight );
450
double drawStat = pixelsPerUnit * stats[i];
451
KDChartTextPiece statText( QString::number( stats[i] ),
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
QBrush brush( params()->bWChartStatisticsBrush( i ) );
460
painter->setBrush( brush );
461
int y = static_cast<int>( zeroXAxisI - drawStat - nTxtHeight/2);
462
painter->drawRect( xPos + xDelta - 1,
465
static_cast<int>( QMAX(nTxtHeight, 8) + 1 ) );
466
statText.draw( painter,
470
params()->bWChartStatisticsColor( i ),
476
continue; // we cannot display this value