summaryrefslogtreecommitdiffstats
path: root/libkdchart/KDChartPiePainter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libkdchart/KDChartPiePainter.cpp')
-rw-r--r--libkdchart/KDChartPiePainter.cpp831
1 files changed, 831 insertions, 0 deletions
diff --git a/libkdchart/KDChartPiePainter.cpp b/libkdchart/KDChartPiePainter.cpp
new file mode 100644
index 0000000..f1715f1
--- /dev/null
+++ b/libkdchart/KDChartPiePainter.cpp
@@ -0,0 +1,831 @@
+/* -*- 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 info@klaralvdalens-datakonsult.se if any conditions of this
+ ** licensing are not clear to you.
+ **
+ **********************************************************************/
+#include "KDChartEnums.h"
+#include "KDChartPiePainter.h"
+#include "KDChartParams.h"
+
+#include <qpainter.h>
+#include <qvaluestack.h>
+#include <qmessagebox.h>
+
+#define DEGTORAD(d) (d)*M_PI/180
+
+#include <math.h>
+
+/**
+ \class KDChartPiePainter KDChartPiePainter.h
+
+ \brief A chart painter implementation that can paint pie charts.
+
+ PENDING(kalle) Write more documentation.
+ */
+
+/**
+ Constructor. Sets up internal data structures as necessary.
+
+ \param params the KDChartParams structure that defines the chart
+ */
+ KDChartPiePainter::KDChartPiePainter( KDChartParams* params ) :
+KDChartPainter( 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.
+ */
+KDChartPiePainter::~KDChartPiePainter()
+{
+ // intentionally left blank
+}
+
+//static bool bHelp=true;
+
+/**
+ Paints the actual data area.
+
+ \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 KDChartPiePainter::paintData( QPainter* painter,
+ KDChartTableDataBase* data,
+ bool paint2nd,
+ KDChartDataRegionList* regions )
+{
+//bHelp=true;
+ uint chart = paint2nd ? 1 : 0;
+
+ QRect ourClipRect( _dataRect );
+ ourClipRect.addCoords( -1,-1,1,1 );
+
+ const QWMatrix & world = painter->worldMatrix();
+ ourClipRect =
+#if COMPAT_QT_VERSION >= 0x030000
+ world.mapRect( ourClipRect );
+#else
+ world.map( ourClipRect );
+#endif
+
+ painter->setClipRect( ourClipRect );
+
+ // find which dataset to paint
+ uint dataset;
+ if ( !params()->findDataset( KDChartParams::DataEntry
+ ,
+ dataset, dataset ) ) {
+ return ; // nothing to draw
+ }
+
+ if ( dataset == KDCHART_ALL_DATASETS )
+ // setChartSourceMode() has not been used (or all datasets have been
+ // configured to be used); use the first dataset by
+ // default
+ dataset = 0;
+
+
+ // Number of values: If -1, use all values, otherwise use the
+ // specified number of values.
+ if ( params()->numValues() != -1 )
+ _numValues = params()->numValues();
+ else
+ _numValues = data->usedCols();
+
+ _startAngles.resize( _numValues );
+ _angleLens.resize( _numValues );
+
+ // compute position
+ _size = QMIN( _dataRect.width(), _dataRect.height() ); // initial size
+ // if the pies explode, we need to give them additional space =>
+ // make the basic size smaller
+ if ( params()->explode() ) {
+ double doubleSize = ( double ) _size;
+ doubleSize /= ( 1.0 + params()->explodeFactor() * 2 );
+ _size = ( int ) doubleSize;
+ }
+
+ int sizeFor3DEffect = 0;
+ if ( !params()->threeDPies() ) {
+
+ int x = ( _dataRect.width() == _size ) ? 0 : ( ( _dataRect.width() - _size ) / 2 );
+ int y = ( _dataRect.height() == _size ) ? 0 : ( ( _dataRect.height() - _size ) / 2 );
+ _position = QRect( x, y, _size, _size );
+ _position.moveBy( _dataRect.left(), _dataRect.top() );
+ } else {
+ // threeD: width is the maximum possible width; height is 1/2 of that
+ int x = ( _dataRect.width() == _size ) ? 0 : ( ( _dataRect.width() - _size ) / 2 );
+ int height = _size;
+ // make sure that the height plus the threeDheight is not more than the
+ // available size
+ if ( params()->threeDPieHeight() >= 0 ) {
+ // positive pie height: absolute value
+ sizeFor3DEffect = params()->threeDPieHeight();
+ height = _size - sizeFor3DEffect;
+ } else {
+ // negative pie height: relative value
+ sizeFor3DEffect = -( int ) ( ( ( double ) params()->threeDPieHeight() / 100.0 ) * ( double ) height );
+ height = _size - sizeFor3DEffect;
+ }
+ int y = ( _dataRect.height() == height ) ? 0 : ( ( _dataRect.height() - height - sizeFor3DEffect ) / 2 );
+
+ _position = QRect( _dataRect.left() + x, _dataRect.top() + y,
+ _size, height );
+ // _position.moveBy( _dataRect.left(), _dataRect.top() );
+ }
+
+ double sum = data->rowAbsSum( dataset );
+ if( sum==0 ) //nothing to draw
+ return;
+ double sectorsPerValue = 5760.0 / sum; // 5760 == 16*360, number of sections in Qt circle
+
+ int currentValue = params()->pieStart() * 16;
+ bool atLeastOneValue = false; // guard against completely empty tables
+ QVariant vValY;
+ for ( int value = 0; value < _numValues; value++ ) {
+ // is there anything at all at this value
+ /* see above for meaning of 16 */
+
+ if( data->cellCoord( dataset, value, vValY, 1 ) &&
+ QVariant::Double == vValY.type() ){
+ _startAngles[ value ] = currentValue;
+ const double cellValue = fabs( vValY.toDouble() );
+ _angleLens[ value ] = ( int ) floor( cellValue * sectorsPerValue + 0.5 );
+ atLeastOneValue = true;
+ } else { // mark as non-existent
+ _angleLens[ value ] = 0;
+ if ( value > 0 )
+ _startAngles[ value ] = _startAngles[ value - 1 ];
+ else
+ _startAngles[ value ] = currentValue;
+ }
+
+ currentValue = _startAngles[ value ] + _angleLens[ value ];
+ }
+
+ // If there was no value at all, bail out, to avoid endless loops
+ // later on (e.g. in findPieAt()).
+ if( !atLeastOneValue )
+ return;
+
+
+ // Find the backmost pie which is at +90° and needs to be drawn
+ // first
+ int backmostpie = findPieAt( 90 * 16 );
+ // Find the frontmost pie (at -90°/+270°) that should be drawn last
+ int frontmostpie = findPieAt( 270 * 16 );
+ // and put the backmost pie on the TODO stack to initialize it,
+ // but only if it is not the frontmostpie
+ QValueStack < int > todostack;
+ if ( backmostpie != frontmostpie )
+ todostack.push( backmostpie );
+ else {
+ // Otherwise, try to find something else
+ int leftOfCurrent = findLeftPie( backmostpie );
+ if ( leftOfCurrent != frontmostpie ) {
+ todostack.push( leftOfCurrent );
+ } else {
+ int rightOfCurrent = findRightPie( backmostpie );
+ if ( rightOfCurrent != frontmostpie ) {
+ todostack.push( rightOfCurrent );
+ }
+ }
+ // If we get here, there was nothing else, and we will bail
+ // out of the while loop below.
+ }
+
+ // The list with pies that have already been drawn
+
+ QValueList < int > donelist;
+
+ // Draw pies until the todostack is empty or only the frontmost
+ // pie is there
+ while ( !todostack.isEmpty() &&
+ !( ( todostack.count() == 1 ) &&
+ ( ( todostack.top() == frontmostpie ) ) ) ) {
+ // The while loop cannot be cancelled if frontmostpie is on
+ // top of the stack, but this is also backmostpie (can happen
+ // when one of the pies covers more than 1/2 of the circle. In
+ // this case, we need to find something else to put on the
+ // stack to get things going.
+
+ // take one pie from the stack
+ int currentpie = todostack.pop();
+ // if this pie was already drawn, ignore it
+ if ( donelist.find( currentpie ) != donelist.end() )
+ continue;
+
+ // If this pie is the frontmost pie, put it back, but at the
+ // second position (otherwise, there would be an endless
+ // loop). If this pie is the frontmost pie, there must be at
+ // least one other pie, otherwise the loop would already have
+ // been terminated by the loop condition.
+ if ( currentpie == frontmostpie ) {
+ Q_ASSERT( !todostack.isEmpty() );
+ // QValueStack::exchange() would be nice here...
+ int secondpie = todostack.pop();
+ if ( currentpie == secondpie )
+ // no need to have the second pie twice on the stack,
+ // forget about one instance and take the third
+ // instead
+ if ( todostack.isEmpty() )
+ break; // done anyway
+ else
+ secondpie = todostack.pop();
+ todostack.push( currentpie );
+ todostack.push( secondpie );
+ continue;
+ }
+
+ // When we get here, we can just draw the pie and proceed.
+ drawOnePie( painter, data, dataset, currentpie, chart,
+ sizeFor3DEffect,
+ regions );
+
+ // Mark the pie just drawn as done.
+ donelist.append( currentpie );
+
+ // Now take the pie to the left and to the right, check
+ // whether these have not been painted already, and put them
+ // on the stack.
+ int leftOfCurrent = findLeftPie( currentpie );
+ if ( donelist.find( leftOfCurrent ) == donelist.end() )
+ todostack.push( leftOfCurrent );
+ int rightOfCurrent = findRightPie( currentpie );
+ if ( donelist.find( rightOfCurrent ) == donelist.end() )
+ todostack.push( rightOfCurrent );
+ }
+
+ // now only the frontmost pie is left to draw
+ drawOnePie( painter, data, dataset, frontmostpie, chart,
+ sizeFor3DEffect,
+ regions );
+}
+
+
+/**
+ Internal method that draws one of the pies in a pie chart.
+
+ \param painter the QPainter to draw in
+ \param dataset the dataset to draw the pie for
+ \param pie the pie to draw
+ \param the chart to draw the pie in
+ \param regions a pointer to a list of regions that will be filled
+ with regions representing the data segments, if not null
+ */
+void KDChartPiePainter::drawOnePie( QPainter* painter,
+ KDChartTableDataBase* /*data*/,
+ uint dataset, uint pie, uint chart,
+ uint threeDPieHeight,
+ KDChartDataRegionList* regions )
+{
+ // Is there anything to draw at all?
+ int angleLen = _angleLens[ ( int ) pie ];
+ if ( angleLen ) {
+ int startAngle = _startAngles[ ( int ) pie ];
+
+ KDChartDataRegion* datReg = 0;
+ QRegion* region = 0;
+ bool mustDeleteRegion = false;
+ if ( regions ){
+ region = new QRegion();
+ mustDeleteRegion = true;
+ }
+
+ QRect drawPosition = _position;
+ if ( params()->explode() ) {
+ // need to compute a new position for each or some of the pie
+ QValueList<int> explodeList = params()->explodeValues();
+ if( explodeList.count() == 0 || // nothing on list, explode all
+ explodeList.find( pie ) != explodeList.end() ) {
+ double explodeAngle = ( startAngle + angleLen / 2 ) / 16;
+ double explodeAngleRad = DEGTORAD( explodeAngle );
+ double cosAngle = cos( explodeAngleRad );
+ double sinAngle = -sin( explodeAngleRad );
+
+ // find the explode factor for this particular pie
+ double explodeFactor = 0.0;
+ QMap<int,double> explodeFactors = params()->explodeFactors();
+ if( !explodeFactors.contains( pie ) ) // not on factors list, use default
+ explodeFactor = params()->explodeFactor();
+ else // on factors list, use segment-specific value
+ explodeFactor = explodeFactors[pie];
+
+ double explodeX = explodeFactor * _size * cosAngle;
+ double explodeY = explodeFactor * _size * sinAngle;
+ drawPosition.moveBy( static_cast<int>( explodeX ), static_cast<int>( explodeY ) );
+ } else
+ drawPosition = _position;
+ } else
+ drawPosition = _position;
+
+ // The 3D effect needs to be drawn first because it could
+ // otherwise partly hide the pie itself.
+ if ( params()->threeDPies() ) {
+ draw3DEffect( painter, drawPosition, dataset, pie, chart,
+ threeDPieHeight,
+ params()->explode(), region );
+ }
+
+ painter->setBrush( params()->dataColor( pie ) );
+ if ( angleLen == 5760 ) {
+ // full circle, avoid nasty line in the middle
+ painter->drawEllipse( drawPosition );
+ if ( regions ) {
+ QPointArray hitregion;
+ hitregion.makeEllipse( drawPosition.x(), drawPosition.y(),
+ drawPosition.width(),
+ drawPosition.height() );
+ datReg = new KDChartDataRegion( region->unite( QRegion( hitregion ) ),
+ dataset,
+ pie,
+ chart );
+ datReg->points[ KDChartEnums::PosCenter ]
+ = drawPosition.center();
+ datReg->points[ KDChartEnums::PosCenterRight ]
+ = pointOnCircle( drawPosition, 0 );
+ datReg->points[ KDChartEnums::PosTopRight ]
+ = pointOnCircle( drawPosition, 720 );
+ datReg->points[ KDChartEnums::PosTopCenter ]
+ = pointOnCircle( drawPosition, 1440 );
+ datReg->points[ KDChartEnums::PosTopLeft ]
+ = pointOnCircle( drawPosition, 2160 );
+ datReg->points[ KDChartEnums::PosCenterLeft ]
+ = pointOnCircle( drawPosition, 2880 );
+ datReg->points[ KDChartEnums::PosBottomLeft ]
+ = pointOnCircle( drawPosition, 3600 );
+ datReg->points[ KDChartEnums::PosBottomCenter ]
+ = pointOnCircle( drawPosition, 4320 );
+ datReg->points[ KDChartEnums::PosBottomRight ]
+ = pointOnCircle( drawPosition, 5040 );
+ datReg->startAngle = 2880;
+ datReg->angleLen = 5760;
+ regions->append( datReg );
+ }
+ } else {
+ // draw the top of this piece
+ // Start with getting the points for the arc.
+ const int arcPoints = angleLen;
+ QPointArray collect(arcPoints+2);
+ int i=0;
+ for ( ; i<=angleLen; ++i){
+ collect.setPoint(i, pointOnCircle( drawPosition, startAngle+i ));
+ }
+ // Adding the center point of the piece.
+ collect.setPoint(i, drawPosition.center() );
+
+
+
+ painter->drawPolygon( collect );
+
+//if( bHelp ){
+// painter->drawPolyline( collect );
+//bHelp=false;
+//}
+
+
+
+ if ( regions ) {
+ QPointArray hitregion;
+ hitregion.makeArc( drawPosition.x(), drawPosition.y(),
+ drawPosition.width(),
+ drawPosition.height(),
+ ( int ) startAngle, ( int ) angleLen );
+ hitregion.resize( hitregion.size() + 1 );
+ hitregion.setPoint( hitregion.size() - 1,
+ drawPosition.center() );
+ datReg = new KDChartDataRegion( region->unite( QRegion( hitregion ) ),
+ dataset,
+ pie,
+ chart );
+
+ datReg->points[ KDChartEnums::PosTopLeft ]
+ = pointOnCircle( drawPosition, startAngle + angleLen );
+ datReg->points[ KDChartEnums::PosTopCenter ]
+ = pointOnCircle( drawPosition, startAngle + angleLen / 2 );
+ datReg->points[ KDChartEnums::PosTopRight ]
+ = pointOnCircle( drawPosition, startAngle );
+
+ datReg->points[ KDChartEnums::PosBottomLeft ] = drawPosition.center();
+ datReg->points[ KDChartEnums::PosBottomCenter ]
+ = datReg->points[ KDChartEnums::PosBottomLeft ];
+ datReg->points[ KDChartEnums::PosBottomRight ]
+ = datReg->points[ KDChartEnums::PosBottomLeft ];
+
+ datReg->points[ KDChartEnums::PosCenterLeft ]
+ = QPoint( ( datReg->points[ KDChartEnums::PosTopLeft ].x()
+ + datReg->points[ KDChartEnums::PosBottomLeft ].x() ) / 2,
+ ( datReg->points[ KDChartEnums::PosTopLeft ].y()
+ + datReg->points[ KDChartEnums::PosBottomLeft ].y() ) / 2 );
+ datReg->points[ KDChartEnums::PosCenter ]
+ = QPoint( ( datReg->points[ KDChartEnums::PosTopCenter ].x()
+ + datReg->points[ KDChartEnums::PosBottomCenter ].x() ) / 2,
+ ( datReg->points[ KDChartEnums::PosTopCenter ].y()
+ + datReg->points[ KDChartEnums::PosBottomCenter ].y() ) / 2 );
+ datReg->points[ KDChartEnums::PosCenterRight ]
+ = QPoint( ( datReg->points[ KDChartEnums::PosTopRight ].x()
+ + datReg->points[ KDChartEnums::PosBottomRight ].x() ) / 2,
+ ( datReg->points[ KDChartEnums::PosTopRight ].y()
+ + datReg->points[ KDChartEnums::PosBottomRight ].y() ) / 2 );
+
+ datReg->startAngle = startAngle;
+ datReg->angleLen = angleLen;
+ regions->append( datReg );
+ }
+ }
+ if( mustDeleteRegion )
+ delete region;
+ }
+}
+
+
+/**
+ Internal method that draws the shadow creating the 3D effect of a pie
+
+ \param painter the QPainter to draw in
+ \param rect the position to draw at
+ \param dataset the dataset to draw the pie for
+ \param pie the pie to draw the shadow for
+ \param the chart to draw the pie in
+ \param threeDHeight the height of the shadow
+ \param regions a pointer to a list of regions that will be filled
+ with regions representing the data segments, if not null
+ */
+void KDChartPiePainter::draw3DEffect( QPainter* painter,
+ const QRect& drawPosition,
+ uint dataset, uint pie, uint chart,
+ uint threeDHeight,
+ bool /*explode*/,
+ QRegion* region )
+{
+ // NOTE: We cannot optimize away drawing some of the effects (even
+ // when not exploding), because some of the pies might be left out
+ // in future versions which would make some of the normally hidden
+ // pies visible. Complex hidden-line algorithms would be much more
+ // expensive than just drawing for nothing.
+
+ // No need to save the brush, will be changed on return from this
+ // method anyway.
+ painter->setBrush( QBrush( params()->dataShadow1Color( pie ),
+ params()->shadowPattern() ) );
+
+ int startAngle = _startAngles[ ( int ) pie ];
+ int endAngle = startAngle + _angleLens[ ( int ) pie ];
+ // Normalize angles
+ while ( startAngle >= 5760 )
+ startAngle -= 5760;
+ while ( endAngle >= 5760 )
+ endAngle -= 5760;
+ Q_ASSERT( startAngle >= 0 && startAngle <= 360 * 16 );
+ Q_ASSERT( endAngle >= 0 && endAngle <= 360 * 16 );
+
+ //int centerY = drawPosition.center().y();
+
+ if ( startAngle == endAngle ||
+ startAngle == endAngle - 5760 ) { // full circle
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, 2880, 5760, region );
+ } else if ( startAngle <= 90 * 16 ) {
+ if ( endAngle <= 90 * 16 ) {
+ if ( startAngle <= endAngle ) {
+ /// starts and ends in first quadrant, less than 1/4
+ drawStraightEffectSegment( painter, drawPosition, dataset,
+ pie, chart, threeDHeight, startAngle,
+ region );
+ } else {
+ /// starts and ends in first quadrant, more than 3/4
+ drawStraightEffectSegment( painter, drawPosition, dataset,
+ pie, chart, threeDHeight, startAngle,
+ region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, 2880, 5760, region );
+ }
+ } else if ( endAngle <= 180 * 16 ) {
+ /// starts in first quadrant, ends in second quadrant,
+ /// less than 1/2
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, region );
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, endAngle, region );
+ } else if ( endAngle <= 270 * 16 ) {
+ /// starts in first quadrant, ends in third quadrant
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle,
+ region );
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, endAngle, region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, 2880, endAngle, region );
+ } else { // 270*16 < endAngle < 360*16
+ /// starts in first quadrant, ends in fourth quadrant,
+ /// more than 3/4
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, 2880, endAngle, region );
+ }
+ } else if ( startAngle <= 180 * 16 ) {
+ if ( endAngle <= 90 * 16 ) {
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, 2880, 5760, region );
+ } else if ( endAngle <= 180 * 16 ) {
+ if ( startAngle <= endAngle ) {
+ /// starts in second quadrant, ends in second
+ /// quadrant, less than 1/4
+ drawStraightEffectSegment( painter, drawPosition, dataset,
+ pie, chart, threeDHeight, endAngle,
+ region );
+ } else {
+ /// starts in second quadrant, ends in second
+ /// quadrant, more than 1/4
+ drawStraightEffectSegment( painter, drawPosition, dataset,
+ pie, chart, threeDHeight, endAngle,
+ region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, 2880, 5760, region );
+ }
+ } else if ( endAngle <= 270 * 16 ) {
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, endAngle, region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, 2880, endAngle, region );
+ } else { // 270*16 < endAngle < 360*16
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, 2880, endAngle, region );
+ }
+ } else if ( startAngle <= 270 * 16 ) {
+ if ( endAngle <= 90 * 16 ) {
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, 5760, region );
+ } else if ( endAngle <= 180 * 16 ) {
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, endAngle, region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, 5760, region );
+ } else if ( endAngle <= 270 * 16 ) {
+ if ( startAngle <= endAngle ) {
+ /// starts in third quadrant, ends in third quadrant,
+ /// less than 1/4
+ drawStraightEffectSegment( painter, drawPosition, dataset,
+ pie, chart, threeDHeight, endAngle,
+ region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, endAngle,
+ region );
+ } else {
+ /// starts in third quadrant, ends in third quadrant,
+ /// more than 3/4
+ drawStraightEffectSegment( painter, drawPosition, dataset,
+ pie, chart, threeDHeight, endAngle,
+ region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, 2880, endAngle,
+ region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, 5760,
+ region );
+ }
+ } else { // 270*16 < endAngle < 360*16
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, endAngle,
+ region );
+ }
+ } else { // 270*16 < startAngle < 360*16
+ if ( endAngle <= 90 * 16 ) {
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, 5760, region );
+ } else if ( endAngle <= 180 * 16 ) {
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, region );
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, endAngle, region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, 5760, region );
+ } else if ( endAngle <= 270 * 16 ) {
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, region );
+ drawStraightEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, endAngle, region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, 2880, endAngle, region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, 5760, region );
+ } else { // 270*16 < endAngle < 360*16
+ if ( startAngle <= endAngle ) {
+ /// starts in fourth quadrant, ends in fourth
+ /// quadrant, less than 1/4
+ drawStraightEffectSegment( painter, drawPosition, dataset,
+ pie, chart, threeDHeight, startAngle,
+ region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, endAngle,
+ region );
+ } else {
+ /// starts in fourth quadrant, ends in fourth
+ /// quadrant, more than 3/4
+ drawStraightEffectSegment( painter, drawPosition, dataset,
+ pie, chart, threeDHeight, startAngle,
+ region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, startAngle, 5760,
+ region );
+ drawArcEffectSegment( painter, drawPosition, dataset, pie, chart,
+ threeDHeight, 2880, endAngle, region );
+ }
+ }
+ }
+}
+
+
+/**
+ Internal method that draws a segment with a straight 3D effect
+
+ \param painter the QPainter to draw in
+ \param rect the position to draw at
+ \param threeDHeight the height of the shadow
+ \param angle the angle of the segment
+ \param regions a pointer to a list of regions that will be filled
+ with regions representing the data segments, if not null
+ */
+void KDChartPiePainter::drawStraightEffectSegment( QPainter* painter,
+ const QRect& rect,
+ uint /*dataset*/, uint /*pie*/, uint /*chart*/,
+ int threeDHeight,
+ int angle,
+ QRegion* region )
+{
+ QPoint center = rect.center();
+ QPointArray points( 4 );
+ QPoint circlePoint = pointOnCircle( rect, angle );
+ points.setPoint( 0, center );
+ points.setPoint( 1, circlePoint );
+ points.setPoint( 2, circlePoint.x(), circlePoint.y() + threeDHeight );
+ points.setPoint( 3, center.x(),
+ center.y() + threeDHeight );
+ painter->drawPolygon( points );
+ if ( region )
+ *region += QRegion( points );
+}
+
+
+/**
+ Internal method that draws a segment with an arc 3D effect
+
+ \param painter the QPainter to draw in
+ \param rect the position to draw at
+ \param threeDHeight the height of the shadow
+ \param startAngle the starting angle of the segment
+ \param endAngle the ending angle of the segment
+ \param regions a pointer to a list of regions that will be filled
+ with regions representing the data segments, if not null
+ */
+void KDChartPiePainter::drawArcEffectSegment( QPainter* painter,
+ const QRect& rect,
+ uint /*dataset*/, uint /*pie*/, uint /*chart*/,
+ int threeDHeight,
+ int startAngle,
+ int endAngle,
+ QRegion* region )
+{
+ // Start with getting the points for the inner arc.
+ const int startA = QMIN(startAngle, endAngle);
+ const int endA = QMAX(startAngle, endAngle);
+ const int arcPoints = endA-startA+1;
+ QPointArray collect(arcPoints * 2);
+ for ( int angle=endA; angle>=startA; --angle){
+ collect.setPoint(endA-angle, pointOnCircle( rect, angle ));
+ }
+
+ // Now copy these arcs again into the same array, but in the
+ // opposite direction and moved down by the 3D height.
+ for ( int i = arcPoints - 1; i >= 0; --i ) {
+ QPoint pointOnFirstArc = collect.point( i );
+ pointOnFirstArc.setY( pointOnFirstArc.y() + threeDHeight );
+ collect.setPoint( arcPoints * 2 - i - 1, pointOnFirstArc );
+ }
+ painter->drawPolygon( collect );
+ if ( region )
+ *region += QRegion( collect );
+}
+
+
+/**
+ Internal method that finds the pie that is located at the position
+ specified by \c angle.
+
+ \param angle the angle at which to search for a pie
+ \return the number of the pie found
+ */
+uint KDChartPiePainter::findPieAt( int angle )
+{
+ for ( int i = 0; i < _numValues; i++ ) {
+ int endseg = _startAngles[ i ] + _angleLens[ i ];
+ if ( ( _startAngles[ i ] <= angle ) &&
+ ( endseg >= angle ) )
+ // found!
+ return i;
+ }
+
+ // If we have not found it, try wrap around
+ return findPieAt( angle + 5760 );
+}
+
+
+/**
+ Internal method that finds the pie that is located to the left of
+ the pie specified by \c pie.
+
+ \param pie the pie to start the search from
+ \return the number of the pie to the left of \c pie
+ */
+uint KDChartPiePainter::findLeftPie( uint pie )
+{
+ if ( pie == 0 )
+ if ( _numValues > 1 )
+ return _numValues - 1;
+ else
+ return 0;
+ else {
+ return pie - 1;
+ }
+}
+
+
+/**
+ Internal method that finds the pie that is located to the right of
+ the pie specified by \c pie.
+
+ \param pie the pie to start the search from
+ \return the number of the pie to the right of \c pie
+ */
+uint KDChartPiePainter::findRightPie( uint pie )
+{
+ int rightpie = pie + 1;
+ if ( rightpie == _numValues )
+ rightpie = 0;
+ return rightpie;
+}
+
+
+/**
+ This method is a specialization that returns a fallback legend text
+ appropriate for pies that do not have more than one dataset
+
+ 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 KDChartPiePainter::fallbackLegendText( uint dataset ) const
+{
+ return QObject::tr( "Item " ) + 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 KDChartPiePainter::numLegendFallbackTexts( KDChartTableDataBase* data ) const
+{
+ return data->usedCols();
+}