kchart

KDChartBWPainter.cpp

00001 /* -*- Mode: C++ -*-
00002    KDChart - a multi-platform charting engine
00003    */
00004 
00005 /****************************************************************************
00006  ** Copyright (C) 2001-2003 Klarälvdalens Datakonsult AB.  All rights reserved.
00007  **
00008  ** This file is part of the KDChart library.
00009  **
00010  ** This file may be distributed and/or modified under the terms of the
00011  ** GNU General Public License version 2 as published by the Free Software
00012  ** Foundation and appearing in the file LICENSE.GPL included in the
00013  ** packaging of this file.
00014  **
00015  ** Licensees holding valid commercial KDChart licenses may use this file in
00016  ** accordance with the KDChart Commercial License Agreement provided with
00017  ** the Software.
00018  **
00019  ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00020  ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00021  **
00022  ** See http://www.klaralvdalens-datakonsult.se/?page=products for
00023  **   information about KDChart Commercial License Agreements.
00024  **
00025  ** Contact info@klaralvdalens-datakonsult.se if any conditions of this
00026  ** licensing are not clear to you.
00027  **
00028  **********************************************************************/
00029 #include "KDChartBWPainter.h"
00030 #include <KDChartParams.h>
00031 #include "KDChartTextPiece.h"
00032 
00033 #include <qpainter.h>
00034 #if COMPAT_QT_VERSION >= 0x030000
00035 #include <qmemarray.h>
00036 #else
00037 #include <qarray.h>
00038 #define QMemArray QArray
00039 #endif
00040 
00041 #include <stdlib.h>
00042 
00054     KDChartBWPainter::KDChartBWPainter( KDChartParams* params ) :
00055 KDChartAxesPainter( params )
00056 {
00057     // This constructor intentionally left blank so far; we cannot setup the
00058     // geometry yet since we do not know the size of the painter.
00059 }
00060 
00061 
00065 KDChartBWPainter::~KDChartBWPainter()
00066 {
00067     // intentionally left blank
00068 }
00069 
00070 
00071 void quicksort( QMemArray<double>& a, int lo, int hi )
00072 {
00073     int    i=lo, j=hi;
00074     double h;
00075     double x=a[(lo+hi)/2];
00076     do
00077     {
00078         while (a[i]<x) i++;
00079         while (a[j]>x) j--;
00080         if (i<=j)
00081         {
00082             h=a[i]; a[i]=a[j]; a[j]=h;
00083             i++; j--;
00084         }
00085     } while (i<=j);
00086     if (lo<j) quicksort(a, lo, j);
00087     if (i<hi) quicksort(a, i, hi);
00088 }
00089 
00090 
00091 // The following function returns the number of used cells containing a double.
00092 int KDChartBWPainter::calculateStats( KDChartTableDataBase& data,
00093         uint dataset )
00094 {
00095     const uint nMax = data.usedCols();
00096     int nUsed = 0;
00097     QMemArray<double> values( nMax );
00098     double sum = 0.0;
00099     QVariant vVal;
00100     if( data.sorted() ){
00101         for( uint i=0; i<nMax; ++i){
00102             if( data.cellCoord( dataset, i, vVal, 1 ) &&
00103                 QVariant::Double == vVal.type() ) {
00104                 values[nUsed] = vVal.toDouble();
00105                 sum += values[nUsed];
00106                 ++nUsed;
00107             }
00108         }
00109     }else{
00110         // make copy of the dataset and look if it is sorted
00111         bool sorted = true;
00112         double last = 0.0; //  <--  avoids an annoying compile-time warning
00113         for( uint i=0; i<nMax; ++i){
00114             if( data.cellCoord( dataset, i, vVal, 1 ) &&
00115                 QVariant::Double == vVal.type() ) {
00116                 values[nUsed] = vVal.toDouble();
00117                 if(    nUsed // skip 1st value
00118                     && last > values[nUsed] )
00119                     sorted = false;
00120                 last = values[nUsed];
00121                 sum += last;
00122                 ++nUsed;
00123             }
00124         }
00125         if( !sorted ){
00126             // sort our copy of the dataset
00127             quicksort( values, 0, nUsed-1 );
00128         }
00129     }
00130 
00131     // Values now contains all used values without empty gaps.
00132     // nUsed contains their number, so values[nUsed-1] is the last value.
00133 
00134     if( nUsed ){
00135         // store some values
00136         stats[ KDChartParams::MaxValue  ] = values[nUsed-1];
00137         stats[ KDChartParams::MinValue  ] = values[0];
00138 
00139         stats[ KDChartParams::MeanValue ] = sum / nUsed;
00140         // calculate statistics
00141         bool bOdd = 1 == nUsed % 2;
00142         // find median
00143         int nUd2 = nUsed/2;
00144         if( bOdd )
00145             stats[ KDChartParams::Median ] = values[ nUd2 ];
00146         else
00147             stats[ KDChartParams::Median ] =
00148                 (values[ QMAX(nUd2-1, 0) ] + values[ nUd2 ]) /2;
00149         // find last value of lower quartile
00150         nLastQ1  = QMAX( nUd2-1, 0 );
00151         // find 1st value of lower quartile
00152         nFirstQ1 = nLastQ1 / 2;
00153 
00154         // determine how many values are below the median ( == how many are above it)
00155         int nLowerCount = nLastQ1 - nFirstQ1 + 1;
00156 
00157         // find 1st value of upper quartile
00158         nFirstQ3 = bOdd ? QMIN( nUd2+1, nUsed-1 ) : nUd2;
00159         // find last value of upper quartile
00160         nLastQ3  = nFirstQ3 + nLowerCount - 1;
00161 
00162         // find quartiles
00163         bool bOdd2 = 1 == nLowerCount % 2;
00164         // find lower quartile
00165         if( bOdd2 )
00166             stats[ KDChartParams::Quartile1 ] = values[ nFirstQ1 ];
00167         else
00168             stats[ KDChartParams::Quartile1 ] =
00169                 (values[ QMAX(nFirstQ1-1, 0) ] + values[ nFirstQ1 ]) /2;
00170         // find upper quartile
00171         if( bOdd2 ){
00172             stats[ KDChartParams::Quartile3 ] = values[ nLastQ3 ];
00173 }
00174         else{
00175             //qDebug("  "+QString::number(nLastQ3)+"  "+QString::number(KDChartParams::Quartile3)
00176             //      +"  "+QString::number(nUsed)+"  "+QString::number(QMIN(nLastQ3+1, nUsed-1)));
00177             stats[ KDChartParams::Quartile3 ] =
00178                 (values[ nLastQ3 ] + values[ QMIN(nLastQ3+1, nUsed-1) ]) /2;
00179 }
00180         // find the interquartile range (IQR)
00181         double iqr = stats[ KDChartParams::Quartile3 ] - stats[ KDChartParams::Quartile1 ];
00182 
00183         // calculate the fences
00184         double upperInner, lowerInner, upperOuter, lowerOuter;
00185         params()->bWChartFences( upperInner, lowerInner,
00186                 upperOuter, lowerOuter );
00187         stats[ KDChartParams::UpperInnerFence ] =
00188             stats[ KDChartParams::Quartile3 ] + iqr * upperInner;
00189         stats[ KDChartParams::LowerInnerFence ] =
00190             stats[ KDChartParams::Quartile1 ] - iqr * lowerInner;
00191         stats[ KDChartParams::UpperOuterFence ] =
00192             stats[ KDChartParams::Quartile3 ] + iqr * upperOuter;
00193         stats[ KDChartParams::LowerOuterFence ] =
00194             stats[ KDChartParams::Quartile1 ] - iqr * lowerOuter;
00195     }
00196     return nUsed;
00197 }
00198 
00199 
00213 QString KDChartBWPainter::fallbackLegendText( uint dataset ) const
00214 {
00215     return QObject::tr( "Series " ) + QString::number( dataset + 1 );
00216 }
00217 
00218 
00228 uint KDChartBWPainter::numLegendFallbackTexts( KDChartTableDataBase* data ) const
00229 {
00230     return data->usedRows();
00231 }
00232 
00233 
00234 bool KDChartBWPainter::isNormalMode() const
00235 {
00236     return KDChartParams::BWNormal == params()->bWChartSubType();
00237 }
00238 
00239 int KDChartBWPainter::clipShiftUp( bool, double ) const
00240 {
00241     return 0;
00242 }
00243 
00254 void KDChartBWPainter::specificPaintData( QPainter* painter,
00255         const QRect& ourClipRect,
00256         KDChartTableDataBase* data,
00257         KDChartDataRegionList* /*regions*/,
00258         const KDChartAxisParams* axisPara,
00259         bool /*bNormalMode*/,
00260         uint /*chart*/,
00261         double logWidth,
00262         double /*areaWidthP1000*/,
00263         double logHeight,
00264         double /*axisYOffset*/,
00265         double /*minColumnValue*/,
00266         double /*maxColumnValue*/,
00267         double /*columnValueDistance*/,
00268         uint chartDatasetStart,
00269         uint chartDatasetEnd,
00270         uint datasetStart,
00271         uint datasetEnd )
00272 {
00273     //double areaHeightP1000 = logHeight / 1000.0;
00274 
00275     uint datasetNum = ( chartDatasetEnd - chartDatasetStart ) + 1;
00276 
00277     double pixelsPerUnit = 0.0;
00278     if( 0.0 != axisPara->trueAxisHigh() - axisPara->trueAxisLow() )
00279         pixelsPerUnit = logHeight / (axisPara->trueAxisHigh() - axisPara->trueAxisLow());
00280     else
00281         pixelsPerUnit = logHeight / 10;
00282 
00283     // Distance between the individual "stocks"
00284     double pointDist = logWidth / ( ( double ) datasetNum );
00285 
00286     // compute the position of the 0 axis
00287     double zeroXAxisI = axisPara->axisZeroLineStartY() - _dataRect.y();
00288 
00289     const int lineWidth   = static_cast<int>( pointDist / 66.0 ) * QMAX(params()->lineWidth(), 1);
00290     const int lineWidthD2 = lineWidth * 2 / 3;
00291 
00292     const bool noBrush = Qt::NoBrush == params()->bWChartBrush().style();
00293 
00294     // Loop over the datasets, draw one box and whisker shape for each series.
00295     for ( uint dataset  = chartDatasetStart;
00296             dataset <= chartDatasetEnd;
00297             ++dataset ) {
00298 
00299         if( dataset >= datasetStart &&
00300                 dataset <= datasetEnd &&
00301                 0 < calculateStats( *data, dataset ) ) {
00302             const QColor color( params()->dataColor( dataset ) );
00303             // transform calculated values
00304             double drawUOF = stats[ KDChartParams::UpperOuterFence ] * pixelsPerUnit;
00305             double drawUIF = stats[ KDChartParams::UpperInnerFence ] * pixelsPerUnit;
00306             double drawQu3 = stats[ KDChartParams::Quartile3       ] * pixelsPerUnit;
00307             double drawMed = stats[ KDChartParams::Median          ] * pixelsPerUnit;
00308             double drawQu1 = stats[ KDChartParams::Quartile1       ] * pixelsPerUnit;
00309             double drawLIF = stats[ KDChartParams::LowerInnerFence ] * pixelsPerUnit;
00310             double drawLOF = stats[ KDChartParams::LowerOuterFence ] * pixelsPerUnit;
00311             double drawMax = stats[ KDChartParams::MaxValue        ] * pixelsPerUnit;
00312             double drawMin = stats[ KDChartParams::MinValue        ] * pixelsPerUnit;
00313             double drawMean= stats[ KDChartParams::MeanValue       ] * pixelsPerUnit;
00314             // get whisker values
00315             double drawUWhisker = QMIN(drawUIF, drawMax);
00316             double drawLWhisker = QMAX(drawLIF, drawMin);
00317             // get the box width
00318             const int boxWidth = QMAX( 6, static_cast<int>( pointDist * 0.2 ) );
00319             // get marker size (for the outliers and/or for the median value)
00320             int markWidth = params()->bWChartOutValMarkerSize();
00321             bool drawOutliers = ( 0 != markWidth );
00322             if( drawOutliers ){
00323                 if( 0 > markWidth)
00324                     markWidth = QMAX( 4, markWidth * boxWidth / -100 );
00325                 else
00326                     markWidth = QMAX( 4, markWidth );
00327             }
00328             else
00329                 markWidth = boxWidth * 25 / 100; // use the default for the Median marker
00330 
00331             painter->setPen( QPen( color, lineWidth ) );
00332             painter->setBrush( params()->bWChartBrush() );
00333             // draw the box
00334             int boxWidthD2 = boxWidth / 2;
00335             int xPos = static_cast<int>(
00336                     pointDist * ( (double)(dataset - chartDatasetStart) + 0.5 )
00337                     - lineWidth / 2);
00338             painter->drawRect( xPos - boxWidthD2,
00339                     static_cast<int>( zeroXAxisI - drawQu3 ),
00340                     boxWidth,
00341                     static_cast<int>( drawQu3 - drawQu1) + 1 );
00342             // draw the median
00343             painter->drawLine( xPos - boxWidthD2,
00344                     static_cast<int>( zeroXAxisI - drawMed ),
00345                     xPos - boxWidthD2 + boxWidth,
00346                     static_cast<int>( zeroXAxisI - drawMed ) );
00347             // draw the whisker
00348             painter->drawLine( xPos - boxWidthD2,
00349                     static_cast<int>( zeroXAxisI - drawUWhisker ),
00350                     xPos - boxWidthD2 + boxWidth,
00351                     static_cast<int>( zeroXAxisI - drawUWhisker ) );
00352             painter->drawLine( xPos,
00353                     static_cast<int>( zeroXAxisI - drawUWhisker ),
00354                     xPos,
00355                     static_cast<int>( zeroXAxisI - drawQu3 ) );
00356             painter->drawLine( xPos - boxWidthD2,
00357                     static_cast<int>( zeroXAxisI - drawLWhisker ),
00358                     xPos - boxWidthD2 + boxWidth,
00359                     static_cast<int>( zeroXAxisI - drawLWhisker ) );
00360             painter->drawLine( xPos,
00361                     static_cast<int>( zeroXAxisI - drawLWhisker ),
00362                     xPos,
00363                     static_cast<int>( zeroXAxisI - drawQu1 ) );
00364             // draw the values
00365             int xPos2 = static_cast<int>(
00366                     pointDist * ( (double)(dataset - chartDatasetStart) + 0.5 )
00367                     - lineWidthD2 / 2);
00368             int markWidthD2 =  QMAX(markWidth / 2, 2);
00369             int markWidthD25 = QMAX(static_cast<int>( 0.85 * markWidth / 2.0), 2);
00370             int markWidthD35 = QMAX(static_cast<int>( 0.85 * markWidth / 3.0), 2);
00371             // draw the outliers
00372             if( drawOutliers ){
00373                 const uint nMax = data->usedCols();
00374                 int drawVal;
00375                 QVariant vVal;
00376                 for( uint i=0; i<nMax; ++i)
00377                     if( data->cellCoord( dataset, i, vVal, 1 ) &&
00378                         QVariant::Double == vVal.type() ) {
00379                         drawVal = static_cast<int>( pixelsPerUnit * vVal.toDouble() );
00380                         if( drawLOF > drawVal || drawUOF < drawVal ) {
00381                             painter->setPen( Qt::NoPen );
00382                             painter->drawChord( xPos2 - markWidthD2,
00383                                     static_cast<int>(zeroXAxisI - drawVal) - markWidthD2,
00384                                     markWidth,
00385                                     markWidth,
00386                                     0, 5760 );
00387                             painter->setPen( QPen( color, lineWidthD2 ) );
00388                             painter->drawArc( xPos2 - markWidthD2,
00389                                     static_cast<int>(zeroXAxisI - drawVal) - markWidthD2,
00390                                     markWidth,
00391                                     markWidth,
00392                                     0, 5760 );
00393                         } else if( drawLIF > drawVal || drawUIF < drawVal ) {
00394                             painter->setPen( Qt::NoPen );
00395                             painter->drawChord( xPos2 - markWidthD2,
00396                                     static_cast<int>( zeroXAxisI - drawVal) - markWidthD2,
00397                                     markWidth,
00398                                     markWidth,
00399                                     0, 5760 );
00400                             painter->setPen( QPen( color, lineWidthD2 ) );
00401                             painter->drawLine( xPos2,
00402                                     static_cast<int>(zeroXAxisI - drawVal) - markWidthD2,
00403                                     xPos2,
00404                                     static_cast<int>(zeroXAxisI - drawVal) + markWidthD2 );
00405                             painter->drawLine( xPos2 - markWidthD25,
00406                                     static_cast<int>(zeroXAxisI - drawVal) - markWidthD35,
00407                                     xPos2 + markWidthD25,
00408                                     static_cast<int>(zeroXAxisI - drawVal) + markWidthD35 );
00409                             painter->drawLine( xPos2 + markWidthD25,
00410                                     static_cast<int>(zeroXAxisI - drawVal) - markWidthD35,
00411                                     xPos2 - markWidthD25,
00412                                     static_cast<int>(zeroXAxisI - drawVal) + markWidthD35 );
00413                         }
00414                     }
00415             }
00416             // draw the mean value
00417             bool evenLineWidthD2 = floor( ((double)lineWidthD2)/2.0 ) == ((double)lineWidthD2)/2.0;
00418             painter->setPen( params()->bWChartBrush().color() );
00419             painter->drawChord( xPos2 - markWidthD2-1 - (evenLineWidthD2 ? 0 : 1),
00420                     static_cast<int>( zeroXAxisI - drawMean ) - markWidthD2 - 1,
00421                     markWidthD2*2 + (evenLineWidthD2 ? 2 : 3),
00422                     markWidthD2*2 + (evenLineWidthD2 ? 2 : 3),
00423                     0, 5760 );
00424             if( noBrush ) {
00425                 // use different color brightness for the Mean marker
00426                 int h,s,v;
00427                 color.hsv(&h,&s,&v);
00428                 painter->setPen( QPen( 128 > v ? color.light(150) : color.dark(150),
00429                             lineWidthD2 ) );
00430             } else
00431                 painter->setPen( QPen( color, lineWidthD2 ) );
00432             painter->drawLine( xPos2 - markWidthD2 - (evenLineWidthD2 ? 0 : 1),
00433                                static_cast<int>( zeroXAxisI - drawMean ),
00434                                xPos2 + markWidthD2,
00435                                static_cast<int>( zeroXAxisI - drawMean ) );
00436             painter->drawLine( xPos2 - (evenLineWidthD2 ? 0 : 1),
00437                                static_cast<int>( zeroXAxisI - drawMean ) - markWidthD2,
00438                                xPos2 - (evenLineWidthD2 ? 0 : 1),
00439                                static_cast<int>( zeroXAxisI - drawMean ) + markWidthD2 + (evenLineWidthD2 ? 0 : 1) );
00440 
00441             // draw the statistical value texts
00442             painter->setPen( Qt::NoPen );
00443             for( int ii =  KDChartParams::BWStatValSTART;
00444                     ii <= KDChartParams::BWStatValEND;
00445                     ++ii ){
00446                 KDChartParams::BWStatVal i = (KDChartParams::BWStatVal)ii;
00447                 if( params()->bWChartPrintStatistics( i ) ) {
00448                     QFont statFont( params()->bWChartStatisticsFont( i ) );
00449                     float nTxtHeight = statFont.pointSizeFloat();
00450                     if ( params()->bWChartStatisticsUseRelSize( i ) ) {
00451                         nTxtHeight = params()->bWChartStatisticsFontRelSize( i )
00452                             * boxWidth / 100;
00453                         statFont.setPointSizeFloat( nTxtHeight );
00454                     }
00455                     double drawStat = pixelsPerUnit * stats[i];
00456                     KDChartTextPiece statText( painter, QString::number( stats[i] ),
00457                             statFont );
00458                     int tw = statText.width();
00459                     int xDelta = (    KDChartParams::MaxValue  == i
00460                             || KDChartParams::MeanValue == i
00461                             || KDChartParams::MinValue  == i )
00462                         ? -1 * (tw + static_cast<int>( 1.3*boxWidthD2 ))
00463                         : static_cast<int>( 1.3*boxWidthD2 );
00464                     QBrush brush( params()->bWChartStatisticsBrush( i ) );
00465                     painter->setBrush( brush );
00466                     int y = static_cast<int>( zeroXAxisI - drawStat - nTxtHeight/2);
00467                     painter->drawRect( xPos + xDelta - 1,
00468                             y,
00469                             tw + 2,
00470                             QMAX(static_cast < int > ( nTxtHeight ), 8) + 1 );
00471                     statText.draw( painter,
00472                             xPos + xDelta,
00473                             y,
00474                             ourClipRect,
00475                             params()->bWChartStatisticsColor( i ),
00476                             0 );
00477                 }
00478             }
00479 
00480         } else
00481             continue; // we cannot display this value
00482     }
00483 }
KDE Home | KDE Accessibility Home | Description of Access Keys