diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2011-07-04 22:38:03 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2011-07-04 22:38:03 +0000 |
commit | dadc34655c3ab961b0b0b94a10eaaba710f0b5e8 (patch) | |
tree | 99e72842fe687baea16376a147619b6048d7e441 /libkdchart/KDChartBWPainter.cpp | |
download | kmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.tar.gz kmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.zip |
Added kmymoney
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kmymoney@1239792 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'libkdchart/KDChartBWPainter.cpp')
-rw-r--r-- | libkdchart/KDChartBWPainter.cpp | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/libkdchart/KDChartBWPainter.cpp b/libkdchart/KDChartBWPainter.cpp new file mode 100644 index 0000000..96bd2bc --- /dev/null +++ b/libkdchart/KDChartBWPainter.cpp @@ -0,0 +1,483 @@ +/* -*- Mode: C++ -*- + KDChart - a multi-platform charting engine + */ + +/**************************************************************************** + ** Copyright (C) 2001-2003 Klarälvdalens Datakonsult AB. All rights reserved. + ** + ** This file is part of the KDChart library. + ** + ** This file may be distributed and/or modified under the terms of the + ** GNU General Public License version 2 as published by the Free Software + ** Foundation and appearing in the file LICENSE.GPL included in the + ** packaging of this file. + ** + ** Licensees holding valid commercial KDChart licenses may use this file in + ** accordance with the KDChart Commercial License Agreement provided with + ** the Software. + ** + ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + ** + ** See http://www.klaralvdalens-datakonsult.se/?page=products for + ** information about KDChart Commercial License Agreements. + ** + ** Contact [email protected] if any conditions of this + ** licensing are not clear to you. + ** + **********************************************************************/ +#include "KDChartBWPainter.h" +#include <KDChartParams.h> +#include "KDChartTextPiece.h" + +#include <qpainter.h> +#if COMPAT_QT_VERSION >= 0x030000 +#include <qmemarray.h> +#else +#include <qarray.h> +#define QMemArray QArray +#endif + +#include <stdlib.h> + +/** + \class KDChartBWPainter KDChartBWPainter.h + + \brief A chart painter implementation that can paint Box&Whisker charts. + */ + +/** + Constructor. Sets up internal data structures as necessary. + + \param params the KDChartParams structure that defines the chart + */ + KDChartBWPainter::KDChartBWPainter( KDChartParams* params ) : +KDChartAxesPainter( params ) +{ + // This constructor intentionally left blank so far; we cannot setup the + // geometry yet since we do not know the size of the painter. +} + + +/** + Destructor. + */ +KDChartBWPainter::~KDChartBWPainter() +{ + // intentionally left blank +} + + +void quicksort( QMemArray<double>& a, int lo, int hi ) +{ + int i=lo, j=hi; + double h; + double x=a[(lo+hi)/2]; + do + { + while (a[i]<x) i++; + while (a[j]>x) j--; + if (i<=j) + { + h=a[i]; a[i]=a[j]; a[j]=h; + i++; j--; + } + } while (i<=j); + if (lo<j) quicksort(a, lo, j); + if (i<hi) quicksort(a, i, hi); +} + + +// The following function returns the number of used cells containing a double. +int KDChartBWPainter::calculateStats( KDChartTableDataBase& data, + uint dataset ) +{ + const uint nMax = data.usedCols(); + int nUsed = 0; + QMemArray<double> values( nMax ); + double sum = 0.0; + QVariant vVal; + if( data.sorted() ){ + for( uint i=0; i<nMax; ++i){ + if( data.cellCoord( dataset, i, vVal, 1 ) && + QVariant::Double == vVal.type() ) { + values[nUsed] = vVal.toDouble(); + sum += values[nUsed]; + ++nUsed; + } + } + }else{ + // make copy of the dataset and look if it is sorted + bool sorted = true; + double last = 0.0; // <-- avoids an annoying compile-time warning + for( uint i=0; i<nMax; ++i){ + if( data.cellCoord( dataset, i, vVal, 1 ) && + QVariant::Double == vVal.type() ) { + values[nUsed] = vVal.toDouble(); + if( nUsed // skip 1st value + && last > values[nUsed] ) + sorted = false; + last = values[nUsed]; + sum += last; + ++nUsed; + } + } + if( !sorted ){ + // sort our copy of the dataset + quicksort( values, 0, nUsed-1 ); + } + } + + // Values now contains all used values without empty gaps. + // nUsed contains their number, so values[nUsed-1] is the last value. + + if( nUsed ){ + // store some values + stats[ KDChartParams::MaxValue ] = values[nUsed-1]; + stats[ KDChartParams::MinValue ] = values[0]; + + stats[ KDChartParams::MeanValue ] = sum / nUsed; + // calculate statistics + bool bOdd = 1 == nUsed % 2; + // find median + int nUd2 = nUsed/2; + if( bOdd ) + stats[ KDChartParams::Median ] = values[ nUd2 ]; + else + stats[ KDChartParams::Median ] = + (values[ QMAX(nUd2-1, 0) ] + values[ nUd2 ]) /2; + // find last value of lower quartile + nLastQ1 = QMAX( nUd2-1, 0 ); + // find 1st value of lower quartile + nFirstQ1 = nLastQ1 / 2; + + // determine how many values are below the median ( == how many are above it) + int nLowerCount = nLastQ1 - nFirstQ1 + 1; + + // find 1st value of upper quartile + nFirstQ3 = bOdd ? QMIN( nUd2+1, nUsed-1 ) : nUd2; + // find last value of upper quartile + nLastQ3 = nFirstQ3 + nLowerCount - 1; + + // find quartiles + bool bOdd2 = 1 == nLowerCount % 2; + // find lower quartile + if( bOdd2 ) + stats[ KDChartParams::Quartile1 ] = values[ nFirstQ1 ]; + else + stats[ KDChartParams::Quartile1 ] = + (values[ QMAX(nFirstQ1-1, 0) ] + values[ nFirstQ1 ]) /2; + // find upper quartile + if( bOdd2 ){ + stats[ KDChartParams::Quartile3 ] = values[ nLastQ3 ]; +} + else{ + //qDebug(" "+QString::number(nLastQ3)+" "+QString::number(KDChartParams::Quartile3) + // +" "+QString::number(nUsed)+" "+QString::number(QMIN(nLastQ3+1, nUsed-1))); + stats[ KDChartParams::Quartile3 ] = + (values[ nLastQ3 ] + values[ QMIN(nLastQ3+1, nUsed-1) ]) /2; +} + // find the interquartile range (IQR) + double iqr = stats[ KDChartParams::Quartile3 ] - stats[ KDChartParams::Quartile1 ]; + + // calculate the fences + double upperInner, lowerInner, upperOuter, lowerOuter; + params()->bWChartFences( upperInner, lowerInner, + upperOuter, lowerOuter ); + stats[ KDChartParams::UpperInnerFence ] = + stats[ KDChartParams::Quartile3 ] + iqr * upperInner; + stats[ KDChartParams::LowerInnerFence ] = + stats[ KDChartParams::Quartile1 ] - iqr * lowerInner; + stats[ KDChartParams::UpperOuterFence ] = + stats[ KDChartParams::Quartile3 ] + iqr * upperOuter; + stats[ KDChartParams::LowerOuterFence ] = + stats[ KDChartParams::Quartile1 ] - iqr * lowerOuter; + } + return nUsed; +} + + +/** + This method is a specialization that returns a fallback legend text + appropriate for BW that do not have the same notion of a dataset like + e.g. bars. + + This method is only used when automatic legends are used, because + manual and first-column legends do not need fallback texts. + + \param uint dataset the dataset number for which to generate a + fallback text + \return the fallback text to use for describing the specified + dataset in the legend + */ +QString KDChartBWPainter::fallbackLegendText( uint dataset ) const +{ + return QObject::tr( "Series " ) + QString::number( dataset + 1 ); +} + + +/** + This methods returns the number of elements to be shown in the + legend in case fallback texts are used. + + This method is only used when automatic legends are used, because + manual and first-column legends do not need fallback texts. + + \return the number of fallback texts to use + */ +uint KDChartBWPainter::numLegendFallbackTexts( KDChartTableDataBase* data ) const +{ + return data->usedRows(); +} + + +bool KDChartBWPainter::isNormalMode() const +{ + return KDChartParams::BWNormal == params()->bWChartSubType(); +} + +int KDChartBWPainter::clipShiftUp( bool, double ) const +{ + return 0; +} + +/** + Paints the actual data area and registers the region for the data + points if \a regions is not 0. + + \param painter the QPainter onto which the chart should be painted + \param data the data that will be displayed as a chart + \param paint2nd specifies whether the main chart or the additional chart is to be drawn now + \param regions a pointer to a list of regions that will be filled + with regions representing the data segments, if not null + */ +void KDChartBWPainter::specificPaintData( QPainter* painter, + const QRect& 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 ) +{ + //double areaHeightP1000 = logHeight / 1000.0; + + uint datasetNum = ( chartDatasetEnd - chartDatasetStart ) + 1; + + double pixelsPerUnit = 0.0; + if( 0.0 != axisPara->trueAxisHigh() - axisPara->trueAxisLow() ) + pixelsPerUnit = logHeight / (axisPara->trueAxisHigh() - axisPara->trueAxisLow()); + else + pixelsPerUnit = logHeight / 10; + + // Distance between the individual "stocks" + double pointDist = logWidth / ( ( double ) datasetNum ); + + // compute the position of the 0 axis + double zeroXAxisI = axisPara->axisZeroLineStartY() - _dataRect.y(); + + const int lineWidth = static_cast<int>( pointDist / 66.0 ) * QMAX(params()->lineWidth(), 1); + const int lineWidthD2 = lineWidth * 2 / 3; + + const bool noBrush = Qt::NoBrush == params()->bWChartBrush().style(); + + // Loop over the datasets, draw one box and whisker shape for each series. + for ( uint dataset = chartDatasetStart; + dataset <= chartDatasetEnd; + ++dataset ) { + + if( dataset >= datasetStart && + dataset <= datasetEnd && + 0 < calculateStats( *data, dataset ) ) { + const QColor color( params()->dataColor( dataset ) ); + // transform calculated values + double drawUOF = stats[ KDChartParams::UpperOuterFence ] * pixelsPerUnit; + double drawUIF = stats[ KDChartParams::UpperInnerFence ] * pixelsPerUnit; + double drawQu3 = stats[ KDChartParams::Quartile3 ] * pixelsPerUnit; + double drawMed = stats[ KDChartParams::Median ] * pixelsPerUnit; + double drawQu1 = stats[ KDChartParams::Quartile1 ] * pixelsPerUnit; + double drawLIF = stats[ KDChartParams::LowerInnerFence ] * pixelsPerUnit; + double drawLOF = stats[ KDChartParams::LowerOuterFence ] * pixelsPerUnit; + double drawMax = stats[ KDChartParams::MaxValue ] * pixelsPerUnit; + double drawMin = stats[ KDChartParams::MinValue ] * pixelsPerUnit; + double drawMean= stats[ KDChartParams::MeanValue ] * pixelsPerUnit; + // get whisker values + double drawUWhisker = QMIN(drawUIF, drawMax); + double drawLWhisker = QMAX(drawLIF, drawMin); + // get the box width + const int boxWidth = QMAX( 6, static_cast<int>( pointDist * 0.2 ) ); + // get marker size (for the outliers and/or for the median value) + int markWidth = params()->bWChartOutValMarkerSize(); + bool drawOutliers = ( 0 != markWidth ); + if( drawOutliers ){ + if( 0 > markWidth) + markWidth = QMAX( 4, markWidth * boxWidth / -100 ); + else + markWidth = QMAX( 4, markWidth ); + } + else + markWidth = boxWidth * 25 / 100; // use the default for the Median marker + + painter->setPen( QPen( color, lineWidth ) ); + painter->setBrush( params()->bWChartBrush() ); + // draw the box + int boxWidthD2 = boxWidth / 2; + int xPos = static_cast<int>( + pointDist * ( (double)(dataset - chartDatasetStart) + 0.5 ) + - lineWidth / 2); + painter->drawRect( xPos - boxWidthD2, + static_cast<int>( zeroXAxisI - drawQu3 ), + boxWidth, + static_cast<int>( drawQu3 - drawQu1) + 1 ); + // draw the median + painter->drawLine( xPos - boxWidthD2, + static_cast<int>( zeroXAxisI - drawMed ), + xPos - boxWidthD2 + boxWidth, + static_cast<int>( zeroXAxisI - drawMed ) ); + // draw the whisker + painter->drawLine( xPos - boxWidthD2, + static_cast<int>( zeroXAxisI - drawUWhisker ), + xPos - boxWidthD2 + boxWidth, + static_cast<int>( zeroXAxisI - drawUWhisker ) ); + painter->drawLine( xPos, + static_cast<int>( zeroXAxisI - drawUWhisker ), + xPos, + static_cast<int>( zeroXAxisI - drawQu3 ) ); + painter->drawLine( xPos - boxWidthD2, + static_cast<int>( zeroXAxisI - drawLWhisker ), + xPos - boxWidthD2 + boxWidth, + static_cast<int>( zeroXAxisI - drawLWhisker ) ); + painter->drawLine( xPos, + static_cast<int>( zeroXAxisI - drawLWhisker ), + xPos, + static_cast<int>( zeroXAxisI - drawQu1 ) ); + // draw the values + int xPos2 = static_cast<int>( + pointDist * ( (double)(dataset - chartDatasetStart) + 0.5 ) + - lineWidthD2 / 2); + int markWidthD2 = QMAX(markWidth / 2, 2); + int markWidthD25 = QMAX(static_cast<int>( 0.85 * markWidth / 2.0), 2); + int markWidthD35 = QMAX(static_cast<int>( 0.85 * markWidth / 3.0), 2); + // draw the outliers + if( drawOutliers ){ + const uint nMax = data->usedCols(); + int drawVal; + QVariant vVal; + for( uint i=0; i<nMax; ++i) + if( data->cellCoord( dataset, i, vVal, 1 ) && + QVariant::Double == vVal.type() ) { + drawVal = static_cast<int>( pixelsPerUnit * vVal.toDouble() ); + if( drawLOF > drawVal || drawUOF < drawVal ) { + painter->setPen( Qt::NoPen ); + painter->drawChord( xPos2 - markWidthD2, + static_cast<int>(zeroXAxisI - drawVal) - markWidthD2, + markWidth, + markWidth, + 0, 5760 ); + painter->setPen( QPen( color, lineWidthD2 ) ); + painter->drawArc( xPos2 - markWidthD2, + static_cast<int>(zeroXAxisI - drawVal) - markWidthD2, + markWidth, + markWidth, + 0, 5760 ); + } else if( drawLIF > drawVal || drawUIF < drawVal ) { + painter->setPen( Qt::NoPen ); + painter->drawChord( xPos2 - markWidthD2, + static_cast<int>( zeroXAxisI - drawVal) - markWidthD2, + markWidth, + markWidth, + 0, 5760 ); + painter->setPen( QPen( color, lineWidthD2 ) ); + painter->drawLine( xPos2, + static_cast<int>(zeroXAxisI - drawVal) - markWidthD2, + xPos2, + static_cast<int>(zeroXAxisI - drawVal) + markWidthD2 ); + painter->drawLine( xPos2 - markWidthD25, + static_cast<int>(zeroXAxisI - drawVal) - markWidthD35, + xPos2 + markWidthD25, + static_cast<int>(zeroXAxisI - drawVal) + markWidthD35 ); + painter->drawLine( xPos2 + markWidthD25, + static_cast<int>(zeroXAxisI - drawVal) - markWidthD35, + xPos2 - markWidthD25, + static_cast<int>(zeroXAxisI - drawVal) + markWidthD35 ); + } + } + } + // draw the mean value + bool evenLineWidthD2 = floor( ((double)lineWidthD2)/2.0 ) == ((double)lineWidthD2)/2.0; + painter->setPen( params()->bWChartBrush().color() ); + painter->drawChord( xPos2 - markWidthD2-1 - (evenLineWidthD2 ? 0 : 1), + static_cast<int>( zeroXAxisI - drawMean ) - markWidthD2 - 1, + markWidthD2*2 + (evenLineWidthD2 ? 2 : 3), + markWidthD2*2 + (evenLineWidthD2 ? 2 : 3), + 0, 5760 ); + if( noBrush ) { + // use different color brightness for the Mean marker + int h,s,v; + color.hsv(&h,&s,&v); + painter->setPen( QPen( 128 > v ? color.light(150) : color.dark(150), + lineWidthD2 ) ); + } else + painter->setPen( QPen( color, lineWidthD2 ) ); + painter->drawLine( xPos2 - markWidthD2 - (evenLineWidthD2 ? 0 : 1), + static_cast<int>( zeroXAxisI - drawMean ), + xPos2 + markWidthD2, + static_cast<int>( zeroXAxisI - drawMean ) ); + painter->drawLine( xPos2 - (evenLineWidthD2 ? 0 : 1), + static_cast<int>( zeroXAxisI - drawMean ) - markWidthD2, + xPos2 - (evenLineWidthD2 ? 0 : 1), + static_cast<int>( zeroXAxisI - drawMean ) + markWidthD2 + (evenLineWidthD2 ? 0 : 1) ); + + // draw the statistical value texts + painter->setPen( Qt::NoPen ); + for( int ii = KDChartParams::BWStatValSTART; + ii <= KDChartParams::BWStatValEND; + ++ii ){ + KDChartParams::BWStatVal i = (KDChartParams::BWStatVal)ii; + if( params()->bWChartPrintStatistics( i ) ) { + QFont statFont( params()->bWChartStatisticsFont( i ) ); + float nTxtHeight = statFont.pointSizeFloat(); + if ( params()->bWChartStatisticsUseRelSize( i ) ) { + nTxtHeight = params()->bWChartStatisticsFontRelSize( i ) + * boxWidth / 100; + statFont.setPointSizeFloat( nTxtHeight ); + } + double drawStat = pixelsPerUnit * stats[i]; + KDChartTextPiece statText( painter, QString::number( stats[i] ), + statFont ); + int tw = statText.width(); + int xDelta = ( KDChartParams::MaxValue == i + || KDChartParams::MeanValue == i + || KDChartParams::MinValue == i ) + ? -1 * (tw + static_cast<int>( 1.3*boxWidthD2 )) + : static_cast<int>( 1.3*boxWidthD2 ); + QBrush brush( params()->bWChartStatisticsBrush( i ) ); + painter->setBrush( brush ); + int y = static_cast<int>( zeroXAxisI - drawStat - nTxtHeight/2); + painter->drawRect( xPos + xDelta - 1, + y, + tw + 2, + QMAX(static_cast < int > ( nTxtHeight ), 8) + 1 ); + statText.draw( painter, + xPos + xDelta, + y, + ourClipRect, + params()->bWChartStatisticsColor( i ), + 0 ); + } + } + + } else + continue; // we cannot display this value + } +} |