From dadc34655c3ab961b0b0b94a10eaaba710f0b5e8 Mon Sep 17 00:00:00 2001 From: tpearson Date: Mon, 4 Jul 2011 22:38:03 +0000 Subject: Added kmymoney git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kmymoney@1239792 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- libkdchart/KDChartPainter.cpp | 2984 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2984 insertions(+) create mode 100644 libkdchart/KDChartPainter.cpp (limited to 'libkdchart/KDChartPainter.cpp') diff --git a/libkdchart/KDChartPainter.cpp b/libkdchart/KDChartPainter.cpp new file mode 100644 index 0000000..7643589 --- /dev/null +++ b/libkdchart/KDChartPainter.cpp @@ -0,0 +1,2984 @@ +/* -*- 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 +#if defined ( SUN7 ) || defined (_SGIAPI) || defined ( Q_WS_WIN) + #include +#else + #include + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // for static method KDChart::painterToDrawRect() + +#include +#include +#include + +#define DEGTORAD(d) (d)*M_PI/180 + + +/** + \class KDChartPainter KDChartPainter.h + + \brief An abstract base class that defines an interface for classes + that implement chart drawing. + + Applications don't use this class directly (except for + registering/unregistering, see below) new chart implementations, + but instead use the method KDChart::paint() which takes care of the + correct creation and deletion of the painter implementation + used. Or they use KDChartWidget which handles everything + automatically. + + This class cannot be instantiated directly. Even the concrete + subclasses are not instantiated directly, but are instantiated via + KDChartPainter::create() which creates a subclass according to the + parameters passed. + + Application developers can provide their own chart implementations + by subclassing from KDChartPainter, instantiating their subclass + and registering their implementation with + KDChartPainter::registerPainter(). These registrations can be + removed with KDChartPainter::unregisterPainter(). + */ + +/** + Constructor. Will only be called by subclass constructors since + this class can never be instantiated directly. + + \param params the parameters of the chart to be drawn + */ +KDChartPainter::KDChartPainter( KDChartParams* params ) : +_outermostRect( QRect(QPoint(0,0), QSize(0,0))), +_legendTitle( 0 ), +_params( params ), +_legendNewLinesStartAtLeft( true ), +_legendTitleHeight( 0 ), +_legendTitleWidth( 0 ), +_legendTitleMetricsHeight( 0 ) +{ + // This constructor intentionally left blank so far; we cannot setup the + // geometry yet since we do not know the size of the painter. +} + +/** + Destructor. Cleans up any data structures that might have been allocated in + the meantime. + */ +KDChartPainter::~KDChartPainter() +{ + delete _legendTitle; +} + +bool KDChartPainter::calculateAllAxesLabelTextsAndCalcValues( + QPainter*, + KDChartTableDataBase*, + double, + double, + double& ) +{ + // This function intentionally returning false; it is implemented + // by the KDChartAxesPainter class only. + return false; +} + +/** + Creates an object of a concrete subclass of KDChartPainter that + KDChart::paint() (and consequently, the application) can use to + have charts painted. The subclass is determined on the base of the + params parameter which among other things indicates the type of the + chart. + + \param params the parameter set which is used to determine the + painter implementation to be used + \return a pointer to an object of a subclass of KDChartPainter that + can be used to draw charts as defined by the \a params + parameter. Returns 0 if there is no registered + KDChartPainter subclass for the type specified in \a params. This + can only happen with user-defined chart types. + */ +KDChartPainter* KDChartPainter::create( KDChartParams* params, bool make2nd ) +{ + KDChartParams::ChartType cType = make2nd + ? params->additionalChartType() + : params->chartType(); + switch ( cType ) + { + case KDChartParams::Bar: + return new KDChartBarPainter( params ); + case KDChartParams::Line: + return new KDChartLinesPainter( params ); + case KDChartParams::Area: + return new KDChartAreaPainter( params ); + case KDChartParams::Pie: + return new KDChartPiePainter( params ); + case KDChartParams::Ring: + return new KDChartRingPainter( params ); + case KDChartParams::HiLo: + return new KDChartHiLoPainter( params ); + case KDChartParams::BoxWhisker: + return new KDChartBWPainter( params ); + case KDChartParams::Polar: + return new KDChartPolarPainter( params ); + case KDChartParams::NoType: + default: + return 0; + } +} + + +/** + Registers a user-defined painter implementation which is identified + by a string. If there is already a painter implementation + registered under that name, the old registration will be deleted. + + KDChartPainter does not assume ownership of the registered painter, + but you should unregister a painter before deleting an + implementation object to avoid that that object is called after its + deletion. + + \param painterName the name under which the painter implementation + should be registered. This will be matched against the user-defined + chart type name in the KDChartParams structure. + \param painter an implementation object of a user-defined chart + implementation + */ +void KDChartPainter::registerPainter( const QString& /*painterName*/, + KDChartPainter* /*painter*/ ) +{ + // PENDING(kalle) Implement this + qDebug( "Sorry, not implemented: KDChartPainter::registerPainter()" ); +} + + +/** + Unregisters a user-defined painter implementation. Does not delete + the implementation object. If no implementation has been registered + under this name, an exception is thrown if KDChart is compiled with + exceptions, otherwise nothing happens. + + \param the name under which the painter implementation is + registered + */ +void KDChartPainter::unregisterPainter( const QString& /*painterName*/ ) +{ + // PENDING(kalle) Implement this + qDebug( "Sorry, not implemented: KDChartPainter::unregisterPainter()" ); +} + + +/** + Paints the chart for which this chart painter is configured on a + QPainter. This is the method that bundles all the painting + functions that paint specific parts of the chart like axes or + legends. Subclasses can override this method, but should rarely + need to do so. + + \param painter the QPainter onto which the chart should be drawn + \param data the data which will be displayed as a chart + \param regions a pointer to a region list that will be filled with + regions representing the data segments if not null + */ +void KDChartPainter::paint( QPainter* painter, + KDChartTableDataBase* data, + bool paintFirst, + bool paintLast, + KDChartDataRegionList* regions, + const QRect* rect, + bool mustCalculateGeometry ) +{ + + + if( paintFirst && regions ) + regions->clear(); + + // Protect against non-existing data + if( data->usedCols() == 0 && data->usedRows() == 0 ) + return ; + + QRect drawRect; + //Pending Michel: at this point we have to setupGeometry + if( mustCalculateGeometry || _outermostRect.isNull() ){ + if( rect ) + drawRect = *rect; + else if( !KDChart::painterToDrawRect( painter, drawRect ) ){ + qDebug("ERROR: KDChartPainter::paint() could not calculate the drawing area."); + return; + } + setupGeometry( painter, data, drawRect ); + } + else + drawRect = _outermostRect; + + //qDebug("A2: _legendRect:\n%i,%i\n%i,%i", _legendRect.left(),_legendRect.top(),_legendRect.right(),_legendRect.bottom() ); + + + // Note: In addition to the below paintArea calls there might be several + // other paintArea calls regarding to the BASE areas (AreaAxisBASE, + // AreaHdFtBASE, AreaCustomBoxesBASE). + // These additional calls result in smaller areas being drawn inside + // on the larger ones specifies here. + if ( paintFirst ) { + paintArea( painter, KDChartEnums::AreaOutermost ); + paintArea( painter, KDChartEnums::AreaInnermost ); + + paintArea( painter, KDChartEnums::AreaDataAxesLegendHeadersFooters ); + + paintArea( painter, KDChartEnums::AreaHeaders ); + paintArea( painter, KDChartEnums::AreaFooters ); + // header areas are drawn in the following order: + // 1st center: main header, left main header, right main header + // 2nd above: header #0, left header #0, right header #0 + // 3rd below: header #2, left header #2, right header #2 + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosHeader ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosHeaderL ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosHeaderR ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosHeader0 ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosHeader0L ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosHeader0R ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosHeader2 ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosHeader2L ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosHeader2R ); + // footer areas are drawn in the same order as the header areas: + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosFooter ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosFooterL ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosFooterR ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosFooter0 ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosFooter0L ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosFooter0R ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosFooter2 ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosFooter2L ); + paintArea( painter, KDChartEnums::AreaHdFtBASE + KDChartParams::HdFtPosFooter2R ); + + paintHeaderFooter( painter, data ); + + paintArea( painter, KDChartEnums::AreaDataAxesLegend ); + paintArea( painter, KDChartEnums::AreaDataAxes ); + paintArea( painter, KDChartEnums::AreaAxes ); + for( int axis = KDChartAxisParams::AxisPosSTART; + KDChartAxisParams::AxisPosEND >= axis; ++axis ) + paintArea( painter, KDChartEnums::AreaAxisBASE + axis ); + paintArea( painter, KDChartEnums::AreaData ); + paintAxes( painter, data ); + } + + painter->save(); + paintData( painter, data, !paintFirst, regions ); + painter->restore(); + + if ( paintLast ) { + // paint the frame lines of all little data region areas + // on top of all data representations + paintDataRegionAreas( painter, regions ); + if( KDChartParams::Bar != params()->chartType() || + KDChartParams::BarMultiRows != params()->barChartSubType() ) + paintDataValues( painter, data, regions ); + if (params()->legendPosition()!=KDChartParams::NoLegend) + paintArea( painter, KDChartEnums::AreaLegend ); + paintLegend( painter, data ); + paintCustomBoxes( painter, regions ); + } +} + + +/** + Paints an area frame. + */ +void KDChartPainter::paintArea( QPainter* painter, + uint area, + KDChartDataRegionList* regions, + uint dataRow, + uint dataCol, + uint data3rd ) +{ + if( KDChartEnums::AreaCustomBoxesBASE != (KDChartEnums::AreaBASEMask & area) ){ + bool bFound; + const KDChartParams::KDChartFrameSettings* settings = + params()->frameSettings( area, bFound ); + if( bFound ) { + bool allCustomBoxes; + QRect rect( calculateAreaRect( allCustomBoxes, + area, + dataRow, dataCol, data3rd, regions ) ); + + if( !allCustomBoxes ) + paintAreaWithGap( painter, rect, *settings ); + } + } +} + + +void KDChartPainter::paintDataRegionAreas( QPainter* painter, + KDChartDataRegionList* regions ) +{ + if( regions ){ + int iterIdx; + bool bFound; + const KDChartParams::KDChartFrameSettings* settings = + params()->frameSettings( KDChartEnums::AreaChartDataRegion, bFound, &iterIdx ); + while( bFound ) { + bool bDummy; + QRect rect( calculateAreaRect( bDummy, + KDChartEnums::AreaChartDataRegion, + settings->dataRow(), + settings->dataCol(), + settings->data3rd(), + regions ) ); + // NOTE: we can *not* draw any background behind the + // data representations. + // reason: for being able to do that we would have to + // know the respective regions _before_ the + // data representations are drawn; since that + // is impossible, we just draw the borders only + // ( == the corners and the edges ) and ignore the background + // + // (actually: Since the respective interface function does not allow + // specifying a background there is nothing to be ignored anyway.) + settings->frame().paint( painter, + KDFrame::PaintBorder, + trueFrameRect( rect, settings ) ); + settings = params()->nextFrameSettings( bFound, &iterIdx ); + } + } +} + + +QRect KDChartPainter::trueFrameRect( const QRect& orgRect, + const KDChartParams::KDChartFrameSettings* settings ) const +{ + QRect rect( orgRect ); + if( settings ){ + rect.moveBy( -settings->innerGapX(), -settings->innerGapY() ); + rect.setWidth( rect.width() + 2*settings->innerGapX() ); + rect.setHeight( rect.height() + 2*settings->innerGapY() ); + } + return rect; +} + + +/** + Paints an area frame. + This methode is called internally by KDChartPainter::paintArea. + NOTE: areas around KDChartCustomBoxes are _not_ drawn here but + in KDChartCustomBox::paint() which is called by paintCustomBoxes(). + */ +void KDChartPainter::paintAreaWithGap( QPainter* painter, + QRect rect, + const KDChartParams::KDChartFrameSettings& settings ) +{ + if( painter && rect.isValid() ) + settings.frame().paint( painter, + KDFrame::PaintAll, + trueFrameRect( rect, &settings ) ); +} + + +/** + Paints the data value texts near the data representations. + */ +void KDChartPainter::paintDataValues( QPainter* painter, + KDChartTableDataBase* data, + KDChartDataRegionList* regions ) +{ + KDChartDataRegion* region; + if ( painter + && data + && regions + && regions->count() + && params() + && ( params()->printDataValues( 0 ) + || params()->printDataValues( 1 ) ) ) { + + // out of loop status saving + painter->save(); + + QFont font0( params()->dataValuesFont( 0 ) ); + + if( params()->dataValuesUseFontRelSize( 0 ) ) { + float size = QMIN(_areaWidthP1000, _areaHeightP1000) * abs(params()->dataValuesFontRelSize( 0 )); + if ( 9.0 > size ) + size = 9.0; + font0.setPixelSize( static_cast < int > ( size ) ); + } + painter->setFont( font0 ); + QFontMetrics fm0( painter->fontMetrics() ); + double fm0HeightP100( fm0.height() / 100.0 ); + QFont font1( params()->dataValuesFont( 1 ) ); + + if( params()->dataValuesUseFontRelSize( 1 ) ) { + float size = QMIN(_areaWidthP1000, _areaHeightP1000) * abs(params()->dataValuesFontRelSize( 1 )); + if ( 9.0 > size ) + size = 9.0; + font1.setPixelSize( static_cast < int > ( size ) ); + } else + font1.setPixelSize( font0.pixelSize()); + painter->setFont( font1 ); + QFontMetrics fm1( painter->fontMetrics() ); + double fm1HeightP100( fm1.height() / 100.0 ); + + bool lastDigitIrrelevant0 = true; + bool lastDigitIrrelevant1 = true; + // get and format the texts + for ( region=regions->first(); + region != 0; + region = regions->next() ) { + QVariant vValY; + if( data->cellCoord( region->row, region->col, vValY, 1 ) ){ + if( QVariant::String == vValY.type() ){ + const QString sVal( vValY.toString() ); + if( !sVal.isEmpty() ) + region->text = sVal; + }else if( QVariant::Double == vValY.type() ){ + double value( vValY.toDouble() ); + region->negative = 0.0 > value; + double divi( pow( 10.0, params()->dataValuesDivPow10( region->chart ) ) ); + if ( 1.0 != divi ) + value /= divi; + int digits( params()->dataValuesDigitsBehindComma( region->chart ) ); + bool autoDigits( KDCHART_DATA_VALUE_AUTO_DIGITS == digits ); + if( autoDigits ) { + if( 10 < digits ) + digits = 10; + } else + ( region->chart + ? lastDigitIrrelevant1 + : lastDigitIrrelevant0 ) = false; + if( value == KDCHART_NEG_INFINITE ) + region->text = "-LEMNISKATE"; + else if( value == KDCHART_POS_INFINITE ) + region->text = "+LEMNISKATE"; + else { + region->text.setNum( value, 'f', digits ); + if ( autoDigits && region->text.contains( '.' ) ) { + int len = region->text.length(); + while ( 3 < len + && '0' == region->text[ len-1 ] + && '.' != region->text[ len-2 ] ) { + --len; + region->text.truncate( len ); + } + if( '0' != region->text[ len-1 ] ) + ( region->chart + ? lastDigitIrrelevant1 + : lastDigitIrrelevant0 ) = false; + } + } + } + } + } + + if ( lastDigitIrrelevant0 || lastDigitIrrelevant1 ) + for ( region=regions->first(); + region != 0; + region = regions->next() ) + if ( ( ( lastDigitIrrelevant0 && !region->chart ) + || ( lastDigitIrrelevant1 && region->chart ) ) + && region->text.contains( '.' ) + && ( 2 < region->text.length() ) ) + region->text.truncate ( region->text.length() - 2 ); + + + // draw the Data Value Texts and calculate the text regions + painter->setPen( Qt::black ); + + bool allowOverlapping = params()->allowOverlappingDataValueTexts(); + bool drawThisOne; + QRegion lastRegionDone; + + QFontMetrics actFM( painter->fontMetrics() ); + + QFont* oldFont = 0; + int oldRotation = 0; + uint oldChart = UINT_MAX; + uint oldDatacolorNo = UINT_MAX; + for ( region=regions->first(); + region != 0; + region = regions->next() ) { + + // in loop status saving + painter->save(); + + if ( region->text.length() ) { + + QVariant vValY; + bool zero = + data->cellCoord( region->row, region->col, vValY, 1 ) && + QVariant::Double == vValY.type() && + ( 0.0 == vValY.toDouble() || 0 == vValY.toDouble() ); + uint align( params()->dataValuesAnchorAlign( region->chart, + region->negative ) ); + KDChartParams::ChartType cType = region->chart + ? params()->additionalChartType() + : params()->chartType(); + + + // these use the bounding rect of region-region: + bool bIsAreaChart = KDChartParams::Area == cType; + bool rectangular = ( KDChartParams::Bar == cType + || KDChartParams::Line == cType + || bIsAreaChart + || KDChartParams::HiLo == cType + || KDChartParams::BoxWhisker == cType ); + + // these use the nine anchor points stored in region->points + bool circular = ( KDChartParams::Pie == cType + || KDChartParams::Ring == cType + || KDChartParams::Polar == cType ); + + + KDChartEnums::PositionFlag anchorPos( + params()->dataValuesAnchorPosition( region->chart, region->negative ) ); + + QPoint anchor( + rectangular + ? KDChartEnums::positionFlagToPoint( region->rect(), anchorPos ) + : KDChartEnums::positionFlagToPoint( region->points, anchorPos ) ); + + double & fmHeightP100 = region->chart ? fm1HeightP100 : fm0HeightP100; + + int angle = region->startAngle; + switch ( anchorPos ) { + case KDChartEnums::PosTopLeft: + case KDChartEnums::PosCenterLeft: + case KDChartEnums::PosBottomLeft: + angle += region->angleLen; + break; + case KDChartEnums::PosTopCenter: + case KDChartEnums::PosCenter: + case KDChartEnums::PosBottomCenter: + angle += region->angleLen / 2; + break; + /* + case KDChartEnums::PosTopRight: + case KDChartEnums::PosCenterRight: + case KDChartEnums::PosBottomRight: + angle += 0; + break; + */ + default: + break; + } + double anchorDX( params()->dataValuesAnchorDeltaX( region->chart, region->negative ) + * fmHeightP100 ); + double anchorDY( params()->dataValuesAnchorDeltaY( region->chart, region->negative ) + * fmHeightP100 ); + if ( circular ) { + if ( 0.0 != anchorDY ) { + double normAngle = angle / 16; + double normAngleRad = DEGTORAD( normAngle ); + double sinAngle = sin( normAngleRad ); + QPoint& pM = region->points[ KDChartEnums::PosCenter ]; + double dX( pM.x() - anchor.x() ); + double dY( pM.y() - anchor.y() ); + double radialLen( sinAngle ? dY / sinAngle : dY ); + double radialFactor( ( radialLen == 0.0 ) ? 0.0 : ( ( radialLen - anchorDY ) / radialLen ) ); + anchor.setX( static_cast < int > ( pM.x() - dX * radialFactor ) ); + anchor.setY( static_cast < int > ( pM.y() - dY * radialFactor ) ); + } + } else { + anchor.setX( anchor.x() + static_cast < int > ( anchorDX ) ); + anchor.setY( anchor.y() + static_cast < int > ( anchorDY ) ); + } + + + if(anchor.x() < -250){ + anchor.setX(-250); + //qDebug("!! bad negative x position in KDChartPainter::paintDataValues() !!"); + } + if(anchor.y() < -2500){ + anchor.setY(-2500); + //qDebug("!! bad negative y position in KDChartPainter::paintDataValues() !!"); + } + + int rotation( params()->dataValuesRotation( region->chart, + region->negative ) ); + bool incRotationBy90 = false; + if( region->text == "-LEMNISKATE" || + region->text == "+LEMNISKATE" ){ + if( params()->dataValuesShowInfinite( region->chart ) ){ + //bool bIsLineChart = KDChartParams::Line == cType; + if( region->text == "-LEMNISKATE" ) + align = Qt::AlignRight + Qt::AlignVCenter; + else + align = Qt::AlignLeft + Qt::AlignVCenter; + if( !rotation ) + rotation = 90; + else + incRotationBy90 = true; + region->text = " 8 "; + }else{ + region->text = ""; + } + } + + if ( rotation ) { + anchor = painter->worldMatrix().map( anchor ); + + // Temporary solution for fixing the data labels size + // bug when in QPrinter::HighResolution mode: + // There seem to be no backdraws by acting like this, + // but further investigation is required to detect the + // real error in the previous code/ + if ( KDCHART_SAGGITAL_ROTATION == rotation + || KDCHART_TANGENTIAL_ROTATION == rotation ) { + rotation = ( KDCHART_TANGENTIAL_ROTATION == rotation + ? -1440 + : 0 ) + + angle; + rotation /= 16; + if( incRotationBy90 ) + rotation += 90; + if ( 360 <= rotation ) + rotation -= 360; + else if ( 0 > rotation ) + rotation += 360; + rotation = 360 - rotation; + }else if( incRotationBy90 ) + rotation = (rotation + 90) % 360; + + + if( rotation != oldRotation ) { + painter->rotate( rotation - oldRotation ); + // Comment this out - zooming and scrolling + // oldRotation = rotation; + } + + QFont* actFont = region->chart ? &font1 : &font0; + if( oldFont != actFont ) { + painter->setFont( *actFont ); + actFM = QFontMetrics( painter->fontMetrics() ); + // Comment this out - zooming and scrolling + //oldFont = actFont; + } + + KDDrawTextRegionAndTrueRect infosKDD = + KDDrawText::measureRotatedText( painter, + rotation, + anchor, + region->text, + 0, + align, + &actFM, + true, + true, + 5 ); + //anchor = painter->worldMatrix().map( anchor ); + + if( allowOverlapping ) { + drawThisOne = true; + }else { + QRegion sectReg( infosKDD.region.intersect( lastRegionDone ) ); + drawThisOne = sectReg.isEmpty(); + } + if( drawThisOne ) { + lastRegionDone = lastRegionDone.unite( infosKDD.region ); + region->pTextRegion = new QRegion( infosKDD.region ); + + if( params()->dataValuesAutoColor( region->chart ) ) { + if( bIsAreaChart ){ + QColor color( params()->dataColor( region->row ) ); + /* + if( ( (0.0 > anchorDY) && region->negative ) + || ( (0.0 < anchorDY) && !region->negative ) ) + painter->setPen( + QColor( static_cast < int > ( 255- color.red() ), + static_cast < int > ( 255- color.green() ), + static_cast < int > ( 255- color.blue() ) ) ); + else + */ + painter->setPen( color.dark() ); + }else{ + if( zero ) { + if( oldDatacolorNo != UINT_MAX ) { + painter->setPen( Qt::black ); + oldDatacolorNo = UINT_MAX; + } + } + else { + uint datacolorNo = ( KDChartParams::Pie == cType + || KDChartParams::Ring == cType ) + ? region->col + : region->row; + if( oldDatacolorNo != datacolorNo ) { + oldDatacolorNo = datacolorNo; + QColor color( params()->dataColor( datacolorNo ) ); + painter->setPen( QColor( + static_cast < int > (255-color.red() ), + static_cast < int > (255-color.green()), + static_cast < int > (255-color.blue() ))); + } + } + } + } + else if( oldChart != region->chart ) { + oldChart = region->chart; + painter->setPen( params()->dataValuesColor( region->chart ) ); + } + + if( params()->optimizeOutputForScreen() ){ + painter->rotate( -oldRotation ); + oldRotation = 0; + if ( anchor.y() < 0 ) + anchor.setY( -anchor.y() ); + + KDDrawText::drawRotatedText( painter, + rotation, + anchor, + region->text, + region->chart ? &font1 : &font0, + align, + false, // bool showAnchor + 0, // const QFontMetrics* fontMet + false, // bool noFirstrotate + false, // bool noBackrotate + 0, // KDDrawTextRegionAndTrueRect* infos + true ); // bool optimizeOutputForScreen + }else{ + painter->setPen( params()->dataValuesColor( region->chart ) ); + //Pending Michel Painting data value labels rotated. + painter->drawText( infosKDD.x , infosKDD.y , + infosKDD.width, infosKDD.height, + Qt::AlignHCenter | Qt::AlignVCenter | Qt::SingleLine, + region->text ); + + } + + + } // if not intersect + + } else { + + // no rotation: + painter->rotate( -oldRotation ); + oldRotation = 0; + QFontMetrics & fm = region->chart ? fm1 : fm0; + int boundingRectWidth = fm.boundingRect( region->text ).width(); + int leftBearing = fm.leftBearing( region->text[ 0 ] ); + const QChar c = region->text.at( region->text.length() - 1 ); + int rightBearing = fm.rightBearing( c ); + int w = boundingRectWidth + leftBearing + rightBearing + 1; + int h = fm.height(); // ascent + descent + 1 + int dx = 0; + int dy = 0; + switch( align & ( Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter ) ) { + case Qt::AlignRight: + dx = -w+1; + break; + case Qt::AlignHCenter: + // Center on the middle of the bounding rect, not + // the painted area, because numbers appear centered then + dx = -( ( boundingRectWidth / 2 ) + leftBearing ); + break; + } + switch( align & ( Qt::AlignTop | Qt::AlignBottom | Qt::AlignVCenter ) ) { + case Qt::AlignBottom: + dy = -h+1; + break; + case Qt::AlignVCenter: + dy = -h / 2; + break; + } + + QRegion thisRegion( + QRect( anchor.x() + dx, anchor.y() + dy, w, h ) ); + if( allowOverlapping ) + drawThisOne = true; + else { + QRegion sectReg( thisRegion.intersect( lastRegionDone ) ); + drawThisOne = sectReg.isEmpty(); + } + if( drawThisOne ) { + lastRegionDone = lastRegionDone.unite( thisRegion ); + region->pTextRegion = new QRegion( thisRegion ); +#ifdef DEBUG_TEXT_PAINTING + // for testing: + QRect rect( region->pTextRegion->boundingRect() ); + painter->drawRect( rect ); + painter->setPen( Qt::red ); + rect.setLeft( rect.left() + leftBearing ); + rect.setTop( rect.top() + ( fm.height()-fm.boundingRect( region->text ).height() ) /2 ); + rect.setWidth( fm.boundingRect( region->text ).width() ); + rect.setHeight( fm.boundingRect( region->text ).height() ); + painter->drawRect( rect ); + painter->setPen( Qt::black ); +#endif + /* + +NOTE: The following will be REMOVED again once +the layout policy feature is implemented !!! + +*/ + QRect textRect( region->pTextRegion->boundingRect() ); + if( bIsAreaChart ){ + QBrush brush( params()->dataValuesBackground( region->chart ) ); + painter->setBrush( brush ); + painter->setPen( Qt::NoPen ); + QRect rect( textRect ); + rect.moveBy( -2, 0 ); + rect.setWidth( rect.width() + 4 ); + painter->drawRect( rect ); + } + painter->setFont( region->chart ? font1 : font0 ); + if( params()->dataValuesAutoColor( region->chart ) ) { + if( bIsAreaChart ){ + QColor color( params()->dataColor( region->row ) ); + /* + if( ( (0.0 > anchorDY) && region->negative ) + || ( (0.0 < anchorDY) && !region->negative ) ) + painter->setPen( + QColor( static_cast < int > ( 255- color.red() ), + static_cast < int > ( 255- color.green() ), + static_cast < int > ( 255- color.blue() ) ) ); + else + */ + painter->setPen( color.dark() ); + }else{ + if( zero ) + painter->setPen( Qt::black ); + else { + QColor color( params()->dataColor( + ( KDChartParams::Pie == params()->chartType() + || KDChartParams::Ring == params()->chartType() ) + ? region->col + : region->row ) ); + painter->setPen( QColor( static_cast < int > ( 255- color.red() ), + static_cast < int > ( 255- color.green() ), + static_cast < int > ( 255- color.blue() ) ) ); + } + } + }else{ + painter->setPen( params()->dataValuesColor( region->chart ) ); + } + + painter->drawText( textRect.left(), textRect.top(), + textRect.width()+1, textRect.height()+1, + Qt::AlignLeft | Qt::AlignTop, region->text ); + + } + + + } + } + // + painter->restore(); + + } + painter->restore(); + } + +} + + +/** + Paints all custom boxes. + */ +void KDChartPainter::paintCustomBoxes( QPainter* painter, + KDChartDataRegionList* regions ) +{ + // paint all of the custom boxes AND their surrounding frames+background (if any) + bool bGlobalFound; + const KDChartParams::KDChartFrameSettings* globalFrameSettings + = params()->frameSettings( KDChartEnums::AreasCustomBoxes, bGlobalFound ); + + uint idx; + for( idx = 0; idx <= params()->maxCustomBoxIdx(); ++idx ) { + const KDChartCustomBox * box = params()->customBox( idx ); + if( box ) { + // paint border and background + paintArea( painter, + KDChartEnums::AreaCustomBoxesBASE + idx, + regions, + box->dataRow(), + box->dataCol(), + box->data3rd() ); + // retrieve frame information + bool bIndividualFound; + const KDChartParams::KDChartFrameSettings * individualFrameSettings + = params()->frameSettings( KDChartEnums::AreaCustomBoxesBASE + idx, + bIndividualFound ); + const KDChartParams::KDChartFrameSettings * settings + = bIndividualFound ? individualFrameSettings + : bGlobalFound ? globalFrameSettings : 0; + // paint content + const QPoint anchor( calculateAnchor( *box, regions ) ); + box->paint( painter, + anchor, + _areaWidthP1000, + _areaHeightP1000, + settings ? settings->framePtr() : 0, + trueFrameRect( box->trueRect( anchor, _areaWidthP1000, _areaHeightP1000 ), + settings ) ); + } + } +} + + +/** + Calculated the top left corner of a custom box. + */ +QPoint KDChartPainter::calculateAnchor( const KDChartCustomBox & box, + KDChartDataRegionList* regions ) const +{ + QPoint pt(0,0); + + // Recursion handling: + // + // * calculateAnchor() normally calls calculateAreaRect() + // + // * calculateAreaRect() will in turn calls calculateAnchor() in case of + // box.anchorArea() being based on KDChartEnums::AreaCustomBoxesBASE + // + // This is Ok as long as the recursive call of calculateAnchor() is NOT + // intend examination the same box as a previous call. + // + // Rule: + // + // A box may be aligned to another box (and the 2nd box may again be + // aligned to a 3rd box and so on) but NO CIRCULAR alignment is allowed. + // + if( !box.anchorBeingCalculated() ) { + + box.setInternalFlagAnchorBeingCalculated( true ); + + bool allCustomBoxes; + QRect rect( calculateAreaRect( allCustomBoxes, + box.anchorArea(), + box.dataRow(), + box.dataCol(), + box.data3rd(), + regions ) ); + if( allCustomBoxes ) { + // + // Dear user of this library. + // + // You faced the above error during program runtime? + // + // The reason for this is that you may NOT use AreasCustomBoxes + // as a value for the KDChartCustomBox anchor area. + // + // This is due to the fact that an anchor area allways must specify one AREA + // or some contiguous areas that form an area when combined. + // The flag AreasCustomBoxes however specifies a list of custom boxes + // that normally do not form a contiguos ares, so they cannot be used as anchor area. + // + // In order to specify a SINGLE custom box please use AreaCustomBoxBASE+boxId. + // + } + pt = KDChartEnums::positionFlagToPoint( rect, box.anchorPosition() ); + + box.setInternalFlagAnchorBeingCalculated( false ); + } + + return pt; +} + + +/** + Calculated the rectangle covered by an area. + NOTE: KDChartCustomBox areas are _not_ calculated here. + */ +QRect KDChartPainter::calculateAreaRect( bool & allCustomBoxes, + uint area, + uint dataRow, + uint dataCol, + uint /*data3rd*/, + KDChartDataRegionList* regions ) const +{ + QRect rect(0,0, 0,0); + allCustomBoxes = false; + uint pos; + switch( area ) { + case KDChartEnums::AreaData: + rect = _dataRect; + break; + case KDChartEnums::AreaAxes: + break; + case KDChartEnums::AreaLegend: + rect = _legendRect; + break; + case KDChartEnums::AreaDataAxes: + rect = _axesRect; + break; + case KDChartEnums::AreaDataAxesLegend: + rect = _axesRect; + if( _legendRect.isValid() ) { + if( rect.isValid() ) + rect = rect.unite( _legendRect ); + else + rect = _legendRect; + } + break; + case KDChartEnums::AreaHeaders: { + bool bStart = true; + for( pos = KDChartParams::HdFtPosHeadersSTART; + KDChartParams::HdFtPosHeadersEND >= pos; + ++pos ) { + const QRect& r = params()->headerFooterRect( pos ); + if( r.isValid() ) { + if( bStart ) + rect = r; + else + rect = rect.unite( r ); + bStart = false; + } + } + } + break; + case KDChartEnums::AreaFooters: { + bool bStart = true; + for( pos = KDChartParams::HdFtPosFootersSTART; + KDChartParams::HdFtPosFootersEND >= pos; + ++pos ) { + const QRect& r = params()->headerFooterRect( pos ); + if( r.isValid() ) { + if( bStart ) + rect = r; + else + rect = rect.unite( r ); + bStart = false; + } + } + } + break; + case KDChartEnums::AreaDataAxesLegendHeadersFooters: { + rect = _axesRect; + bool bStart = !rect.isValid(); + if( _legendRect.isValid() ) { + if( bStart ) + rect = _legendRect; + else + rect = rect.unite( _legendRect ); + bStart = false; + } + for( pos = KDChartParams::HdFtPosSTART; + KDChartParams::HdFtPosEND >= pos; + ++pos ) { + const QRect& r = params()->headerFooterRect( pos ); + if( r.isValid() ) { + if( bStart ) + rect = r; + else + rect = rect.unite( r ); + bStart = false; + } + } + } + break; + case KDChartEnums::AreaOutermost: + rect = _outermostRect; + break; + case KDChartEnums::AreaInnermost: + rect = _innermostRect; + break; + case KDChartEnums::AreasCustomBoxes: + allCustomBoxes = true; + break; + case KDChartEnums::AreaChartDataRegion: + if( regions ) { + KDChartDataRegion* current; + for ( current = regions->first(); + current != 0; + current = regions->next() ) { + if ( current->row == dataRow + && current->col == dataCol + // + // the line below prepared for true 3-dimensional data charts + // + /* && current->region.thirdDimension == data3rd */ ) { + rect = current->rect(); + break; + } + } + } + break; + case KDChartEnums::AreaUNKNOWN: + break; + + default: { + uint maskBASE = KDChartEnums::AreaBASEMask & area; + pos = area - maskBASE; + if ( KDChartEnums::AreaAxisBASE == maskBASE ) { + rect = params()->axisParams( pos ).axisTrueAreaRect(); + } else if ( KDChartEnums::AreaHdFtBASE == maskBASE ) { + rect = params()->headerFooterRect( pos ); + } else if ( KDChartEnums::AreaCustomBoxesBASE == maskBASE ) { + const KDChartCustomBox * box = params()->customBox( pos ); + if( box ) { + rect = box->trueRect( calculateAnchor( *box, regions ), + _areaWidthP1000, + _areaHeightP1000 ); + } + } + } + } + return rect; +} + + +QPoint KDChartPainter::pointOnCircle( const QRect& rect, double angle ) +{ + // There are two ways of computing this: The simple, but slow one + // is to use QPointArray.makeArc() and take the first point. The + // more advanced, but faster one is to do the trigonometric + // computionations ourselves. Since the comments in + // QPointArray::makeArc() very often say that the code there is + // "poor", we'd better do it outselves... + + double normAngle = angle / 16.0; + double normAngleRad = DEGTORAD( normAngle ); + double cosAngle = cos( normAngleRad ); + double sinAngle = -sin( normAngleRad ); + double posX = floor( cosAngle * ( double ) rect.width() / 2.0 + 0.5 ); + double posY = floor( sinAngle * ( double ) rect.height() / 2.0 + 0.5 ); + return QPoint( static_cast(posX) + rect.center().x(), + static_cast(posY) + rect.center().y() ); + +} + +void KDChartPainter::makeArc( QPointArray& points, + const QRect& rect, + double startAngle, double angles ) +{ + double endAngle = startAngle + angles; + int rCX = rect.center().x(); + int rCY = rect.center().y(); + double rWid2 = ( double ) rect.width() / 2.0; + double rHig2 = ( double ) rect.height() / 2.0; + int numSteps = static_cast(angles); + if( floor( angles ) < angles ) + ++numSteps; + points.resize( numSteps ); + double angle = startAngle; + if( angle < 0.0 ) + angle += 5760.0; + else if( angle >= 5760.0 ) + angle -= 5760.0; + for(int i = 0; i < numSteps; ++i){ + double normAngle = angle / 16.0; + double normAngleRad = DEGTORAD( normAngle ); + double cosAngle = cos( normAngleRad ); + double sinAngle = -sin( normAngleRad ); + double posX = floor( cosAngle * rWid2 + 0.5 ); + double posY = floor( sinAngle * rHig2 + 0.5 ); + points[i] = QPoint( ( int ) posX + rCX, + ( int ) posY + rCY ); + if( i+1 >= numSteps-1 ) + angle = endAngle; // the very last step width may be smaller than 1.0 + else + angle += 1.0; + if( angle >= 5760.0 ) + angle -= 5760.0; + } +} + +/** + Paints the axes for the chart. The implementation in KDChartPainter + does nothing; subclasses for chart types that have axes will + provide the appropriate drawing code here. This method serves as a + fallback for chart types that do not have axes (like pies). + + \param painter the QPainter onto which the chart should be drawn + \param data the data that will be displayed as a chart + */ +void KDChartPainter::paintAxes( QPainter* /*painter*/, KDChartTableDataBase* /*data*/ ) +{ + // This method intentionally left blank. +} + + +int KDChartPainter::legendTitleVertGap() const +{ + return _legendTitleHeight + + static_cast < int > ( _legendTitleMetricsHeight * 0.20 ); +} + + +QFont KDChartPainter::trueLegendFont() const +{ + QFont trueFont = params()->legendFont(); + if ( params()->legendFontUseRelSize() ) { + const double averageValueP1000 = QMIN(_areaWidthP1000, _areaHeightP1000);//( _areaWidthP1000 + _areaHeightP1000 ) / 2.0; + trueFont.setPixelSize( + static_cast < int > ( params()->legendFontRelSize() * averageValueP1000 ) ); + } + return trueFont; +} + + +/** + Calculates the size of the rectangle for horizontal legend orientation. + + \param painter the QPainter onto which the chart should be drawn + */ +void KDChartPainter::calculateHorizontalLegendSize( QPainter* painter, + QSize& size, + bool& legendNewLinesStartAtLeft ) const +{ + + legendNewLinesStartAtLeft = false; + QRect legendRect( _legendRect ); + /* + * Pending Michel reset the left side before calculating + *the new legend position calculation + *otherwise we occasionally reach the edge and get a wrong + *result + */ + + legendRect.setLeft( _innermostRect.left() ); + + const int em2 = 2 * _legendEMSpace; + const int em4 = 4 * _legendEMSpace; + const int emDiv2 = static_cast < int > ( _legendEMSpace / 2.0 ); + + const int xposHori0 = legendRect.left() + _legendEMSpace; + + int xpos = xposHori0; + + int ypos = legendRect.top() + emDiv2; + + // first paint the title, if any + if( _legendTitle ) + xpos += _legendTitleWidth + em4; + + int maxX = _legendTitleWidth + _legendEMSpace; + + // save the x position: here start the item texts if in horizontal mode + int xposHori1 = xpos; + + // add the space of the box plus the space between the box and the text + int x2 = xpos + em2; + + // loop over all the datasets, each one has one row in the legend + // if its data are to be used in at least one of the charts drawn + // *but* only if there is a legend text for it! + const int rightEdge = _innermostRect.right()-_legendEMSpace; + bool bFirstLFWithTitle = _legendTitle; + painter->setFont( trueLegendFont() ); + QFontMetrics txtMetrics( painter->fontMetrics() ); + int dataset; + for ( dataset = 0; dataset < _numLegendTexts; ++dataset ) { + /* + if( KDChartParams::DataEntry == params()->chartSourceMode( dataset ) ) { + */ + if( !_legendTexts[ dataset ].isEmpty() ){ + int txtWidth = txtMetrics.width( _legendTexts[ dataset ] ) + 1; + if( x2 + txtWidth > rightEdge ){ + if( xposHori1 + em2 + txtWidth > rightEdge){ + xposHori1 = xposHori0; + legendNewLinesStartAtLeft = true; + } + xpos = xposHori1; + x2 = xpos + em2; + ypos += bFirstLFWithTitle ? legendTitleVertGap() : _legendSpacing; + bFirstLFWithTitle = false; + } + maxX = QMAX(maxX, x2+txtWidth+_legendEMSpace); + + xpos += txtWidth + em4; + x2 += txtWidth + em4; + } + } + if( bFirstLFWithTitle ) + ypos += _legendTitleHeight; + else + ypos += txtMetrics.height(); + + size.setWidth( maxX - legendRect.left() ); + size.setHeight( ypos + emDiv2 - _legendRect.top() ); +} + + +bool KDChartPainter::mustDrawVerticalLegend() const +{ + return + params()->legendOrientation() == Qt::Vertical || + params()->legendPosition() == KDChartParams::LegendLeft || + params()->legendPosition() == KDChartParams::LegendRight || + params()->legendPosition() == KDChartParams::LegendTopLeft || + params()->legendPosition() == KDChartParams::LegendTopLeftLeft || + params()->legendPosition() == KDChartParams::LegendTopRight || + params()->legendPosition() == KDChartParams::LegendTopRightRight || + params()->legendPosition() == KDChartParams::LegendBottomLeft || + params()->legendPosition() == KDChartParams::LegendBottomLeftLeft || + params()->legendPosition() == KDChartParams::LegendBottomRight || + params()->legendPosition() == KDChartParams::LegendBottomRightRight; +} + +QFont KDChartPainter::trueLegendTitleFont() const +{ + const double averageValueP1000 = QMIN(_areaWidthP1000, _areaHeightP1000);//( _areaWidthP1000 + _areaHeightP1000 ) / 2.0; + QFont font( params()->legendTitleFont() ); + if ( params()->legendTitleFontUseRelSize() ) { + int nTxtHeight = + static_cast < int > ( params()->legendTitleFontRelSize() + * averageValueP1000 ); + font.setPixelSize( nTxtHeight ); + // qDebug("l-t-height %i",nTxtHeight); + } + return font; +} + +/** + Paints the legend for the chart. The implementation in KDChartPainter + draws a standard legend that should be suitable for most chart + types. Subclasses can provide their own implementations. + + \param painter the QPainter onto which the chart should be drawn + \param data the data that will be displayed as a chart + */ +void KDChartPainter::paintLegend( QPainter* painter, + KDChartTableDataBase* /*data*/ ) +{ + if ( params()->legendPosition() == KDChartParams::NoLegend ) + return ; // do not draw legend + + const bool bVertical = mustDrawVerticalLegend(); + painter->save(); + + + bool bFrameFound; + params()->frameSettings( KDChartEnums::AreaLegend, bFrameFound ); + + // start out with a rectangle around the legend + //painter->setPen( QPen( Qt::black, 1 ) ); + //painter->setBrush( QBrush::NoBrush ); + //Pending Michel: let us paint the frame at the end of the drawmarker + //and draw text process, in case we need to resize it then + /* + if( !bFrameFound ) { + painter->drawRect( _legendRect ); + } + */ + //qDebug("B: _legendRect:\n %i,%i\n %i,%i", _legendRect.left(),_legendRect.top(),_legendRect.right(),_legendRect.bottom() ); + //qDebug("B: legendArea():\n %i,%i\n %i,%i\n", _params->legendArea().left(),_params->legendArea().top(),_params->legendArea().right(),_params->legendArea().bottom() ); + + const int em2 = 2 * _legendEMSpace; + const int em4 = 4 * _legendEMSpace; + const int emDiv2 = static_cast < int > ( _legendEMSpace / 2.0 ); + + const int xposHori0 = _legendRect.left() + _legendEMSpace; + + int xpos = xposHori0; + + int ypos = _legendRect.top() + emDiv2; + + + + + // first paint the title, if any + if( _legendTitle ) { + painter->setFont( trueLegendTitleFont() ); + _legendTitle->draw( painter, + xpos, + ypos, + QRegion( xpos, + ypos , + _legendTitleWidth, + _legendTitleHeight ), + params()->legendTitleTextColor() ); + if( bVertical ) + ypos += legendTitleVertGap(); + + else + xpos += _legendTitleWidth + em4; + + } + + // save the x position: here start the item texts if in horizontal mode + const int xposHori1 = _legendNewLinesStartAtLeft ? xposHori0 : xpos; + + // add the space of the box plus the space between the box and the text + int x2 = xpos + em2; + + // loop over all the datasets, each one has one row in the legend + // if its data are to be used in at least one of the charts drawn + // *but* only if there is a legend text for it! + const int rightEdge = _legendRect.right(); + bool bFirstLF = true; + painter->setFont( trueLegendFont() ); + QFontMetrics txtMetrics( painter->fontMetrics() ); + int dataset; + for ( dataset = 0; dataset < _numLegendTexts; ++dataset ) { + /* + if( KDChartParams::DataEntry == params()->chartSourceMode( dataset ) ) { + */ + if( !_legendTexts[ dataset ].isEmpty() ){ + int txtWidth = txtMetrics.width( _legendTexts[ dataset ] ) + 1; + + // calculate the width and height for the marker, relative to the font height + // we need the legend text to be aligned to the marker + // substract a gap. + int legHeight = static_cast ((txtMetrics.height() - (int)(txtMetrics.height() * 0.1))*0.85); + + //int legHeight = static_cast (_legendRect.height()*0.8); + + if( !bVertical && x2 + txtWidth >= rightEdge ){ + _legendRect.setHeight( _legendRect.height() + _legendSpacing ); + xpos = xposHori1; + x2 = xpos + em2; + ypos += bFirstLF ? legendTitleVertGap() : _legendSpacing; + bFirstLF = false; + } + painter->setBrush( QBrush( params()->dataColor( dataset ), + QBrush::SolidPattern ) ); + + if( params()->legendShowLines() ){ + painter->setPen( QPen( params()->dataColor( dataset ), 2, + params()->lineStyle( dataset ) ) ); + painter->drawLine( + xpos - emDiv2, + ypos + emDiv2 + 1, + xpos + static_cast < int > ( _legendEMSpace * 1.5 ), + ypos + emDiv2 + 1); + } + + /* + // draw marker if we have a marker, OR we have no marker and no line + if ( params()->lineMarker() || + params()->lineStyle( dataset ) == Qt::NoPen )*/ + drawMarker( painter, + params(), + _areaWidthP1000, _areaHeightP1000, + _dataRect.x(), _dataRect.y(), + params()->lineMarker() + ? params()->lineMarkerStyle( dataset ) + : KDChartParams::LineMarkerSquare, + params()->dataColor(dataset), + QPoint(xpos + emDiv2, + bVertical? ypos + emDiv2: !bFirstLF ?ypos + _legendSpacing:_legendRect.center().y() - (legHeight / 2 ))/*ypos + emDiv2*/ , + 0, 0, 0, NULL, // these params are deadweight here. TODO + &legHeight /*&_legendEMSpace*/, &legHeight /*&_legendEMSpace*/, + bVertical ? Qt::AlignCenter : (Qt::AlignTop | Qt::AlignHCenter) ); + /* + painter->drawText(_legendRect.topLeft(), "topLeft" ); + painter->drawText(_legendRect.topLeft().x(), _legendRect.center().y(), "center" ); + painter->drawText(_legendRect.bottomLeft(), "bottomLeft" ); + */ + /* old: + painter->setPen( Qt::black ); + painter->drawRect( xpos, + ypos + ( _legendHeight - _legendEMSpace ) / 2, + _legendEMSpace, + _legendEMSpace ); + */ + painter->setPen( params()->legendTextColor() ); + painter->drawText( x2, + bVertical ? ypos : !bFirstLF ? ypos + _legendSpacing : _legendRect.center().y() - (legHeight / 2 ), + txtWidth, + legHeight, + Qt::AlignLeft | Qt::AlignVCenter, + _legendTexts[ dataset ] ); + + if( bVertical ) + ypos += _legendSpacing; + else { + xpos += txtWidth + em4; + x2 += txtWidth + em4; + } + } + } + + painter->setPen( QPen( Qt::black, 1 ) ); + painter->setBrush( QBrush::NoBrush ); + if( !bFrameFound ) + painter->drawRect( _legendRect ); + + + painter->restore(); +} + + +void adjustFromTo(int& from, int& to) +{ + if( abs(from) > abs(to) ){ + int n = from; + from = to; + to = n; + } +} + + +bool KDChartPainter::axesOverlapping( int axis1, int axis2 ) +{ + KDChartAxisParams::AxisPos basicPos = KDChartAxisParams::basicAxisPos( axis1 ); + if( basicPos != KDChartAxisParams::basicAxisPos( axis2 ) ) + // Only axes of the same position can be compared. (e.g. 2 left axes) + return false; + + if( KDChartAxisParams::AxisPosLeft != basicPos && + KDChartAxisParams::AxisPosRight != basicPos ) + // Available space usage only possible for (vertical) ordinate axes. + return false; + + int f1 = params()->axisParams( axis1 ).axisUseAvailableSpaceFrom(); + int t1 = params()->axisParams( axis1 ).axisUseAvailableSpaceTo(); + int f2 = params()->axisParams( axis2 ).axisUseAvailableSpaceFrom(); + int t2 = params()->axisParams( axis2 ).axisUseAvailableSpaceTo(); + adjustFromTo(f1,t1); + adjustFromTo(f2,t2); + // give these values some meaning + // to be able to compare mixed fixed and/or relative figures: + const double guessedAxisHeightP1000 = _areaHeightP1000 * 80.0 / 100.0; + if(f1 < 0) f1 = static_cast < int > ( f1 * -guessedAxisHeightP1000 ); + if(t1 < 0) t1 = static_cast < int > ( t1 * -guessedAxisHeightP1000 ); + if(f2 < 0) f2 = static_cast < int > ( f2 * -guessedAxisHeightP1000 ); + if(t2 < 0) t2 = static_cast < int > ( t2 * -guessedAxisHeightP1000 ); + const bool res = (f1 >= f2 && f1 < t2) || (f2 >= f1 && f2 < t1); + return res; +} + + +void internSetAxisArea( KDChartParams* params, int axis, + int x0, int y0, int w0, int h0 ) +{ + // axis may never occupy more than 1000 per mille of the available space + int nFrom = QMAX(-1000, params->axisParams( axis ).axisUseAvailableSpaceFrom()); + int nTo = QMAX(-1000, params->axisParams( axis ).axisUseAvailableSpaceTo()); + adjustFromTo(nFrom,nTo); + + KDChartAxisParams::AxisPos basicPos = KDChartAxisParams::basicAxisPos( axis ); + int x, y, w, h; + if( KDChartAxisParams::AxisPosBottom == basicPos || + KDChartAxisParams::AxisPosTop == basicPos ){ + + // Note: available space usage is ignored for abscissa axes! + // + //if( nFrom < 0 ) + // x = x0 + w0*nFrom/-1000; + //else + // x = x0 + nFrom; + //y = y0; + //if( nTo < 0 ) + // w = x0 + w0*nTo/-1000 - x; + //else + // w = x0 + nTo - x; + //h = h0; + + x = x0; + y = y0; + w = w0; + h = h0; + + }else{ + x = x0; + if( nTo < 0 ) + y = y0 + h0 - h0*nTo/-1000; + else + y = y0 + h0 - nTo; + w = w0; + if( nFrom < 0 ) + h = y0 + h0 - h0*nFrom/-1000 - y; + else + h = y0 + h0 - nFrom - y; + } + + params->setAxisArea( axis, + QRect( x, + y, + w, + h ) ); +} + + +/** + Paints the header and footers for the chart. The implementation + in KDChartPainter draws a standard header that should be suitable + for most chart types. Subclasses can provide their own implementations. + + \param painter the QPainter onto which the chart should be drawn + \param data the data that will be displayed as a chart + */ +void KDChartPainter::paintHeaderFooter( QPainter* painter, + KDChartTableDataBase* /*data*/ ) +{ + const double averageValueP1000 = QMIN(_areaWidthP1000, _areaHeightP1000);//( _areaWidthP1000 + _areaHeightP1000 ) / 2.0; + + painter->save(); + + for( int iHdFt = KDChartParams::HdFtPosSTART; + iHdFt <= KDChartParams::HdFtPosEND; ++iHdFt ){ + QString txt( params()->headerFooterText( iHdFt ) ); + if ( !txt.isEmpty() ) { + QFont actFont( params()->headerFooterFont( iHdFt ) ); + if ( params()->headerFooterFontUseRelSize( iHdFt ) ) + actFont.setPixelSize( static_cast < int > ( + params()->headerFooterFontRelSize( iHdFt ) * averageValueP1000 ) ); + painter->setPen( params()->headerFooterColor( iHdFt ) ); + painter->setFont( actFont ); + // Note: The alignment flags used here match the rect calculation + // done in KDChartPainter::setupGeometry(). + // AlignTop is done to ensure that the hd/ft texts of the same + // group (e.g. Hd2L and Hd2 and Hd2R) have the same baselines. + + QRect rect( params()->headerFooterRect( iHdFt ) ); + int dXY = iHdFt < KDChartParams::HdFtPosFootersSTART + ? _hdLeading/3 + : _ftLeading/3; + rect.moveBy(dXY, dXY); + rect.setWidth( rect.width() -2*dXY +1 ); + rect.setHeight( rect.height()-2*dXY +1 ); + painter->drawText( rect, + Qt::AlignLeft | Qt::AlignTop | Qt::SingleLine, + txt ); + } + } + painter->restore(); +} + + +int KDChartPainter::calculateHdFtRects( + QPainter* painter, + double averageValueP1000, + int xposLeft, + int xposRight, + bool bHeader, + int& yposTop, + int& yposBottom ) +{ + int& leading = (bHeader ? _hdLeading : _ftLeading); + leading = 0; // pixels between the header (or footer, resp.) text + // and the border of the respective Hd/Ft area + + const int rangesCnt = 3; + const int ranges[ rangesCnt ] + = { bHeader ? KDChartParams::HdFtPosHeaders0START : KDChartParams::HdFtPosFooters0START, + bHeader ? KDChartParams::HdFtPosHeaders1START : KDChartParams::HdFtPosFooters1START, + bHeader ? KDChartParams::HdFtPosHeaders2START : KDChartParams::HdFtPosFooters2START }; + const int rangeSize = 3; + QFontMetrics* metrics[rangesCnt * rangeSize]; + + int i; + for( i = 0; i < rangesCnt*rangeSize; ++i ) + metrics[ i ] = 0; + + int iRange; + int iHdFt; + for( iRange = 0; iRange < rangesCnt; ++iRange ){ + for( i = 0; i < rangeSize; ++i ){ + iHdFt = ranges[iRange] + i; + QString txt( params()->headerFooterText( iHdFt ) ); + if ( !txt.isEmpty() ) { + QFont actFont( params()->headerFooterFont( iHdFt ) ); + if ( params()->headerFooterFontUseRelSize( iHdFt ) ) { + actFont.setPixelSize( static_cast < int > ( + params()->headerFooterFontRelSize( iHdFt ) * averageValueP1000 ) ); + } + painter->setFont( actFont ); + metrics[ iRange*rangeSize + i ] = new QFontMetrics( painter->fontMetrics() ); + leading = QMAX( leading, metrics[ iRange*rangeSize + i ]->lineSpacing() / 2 ); + } + } + } + + if( bHeader ) + ++yposTop;//yposTop += leading/3; + //else + //--yposBottom;//yposBottom -= leading/3; + + int leading23 = leading*2/3 +1; + + for( iRange = + bHeader ? 0 : rangesCnt-1; + bHeader ? iRange < rangesCnt : iRange >= 0; + bHeader ? ++iRange : --iRange ){ + // Ascents and heights must be looked at to ensure that the hd/ft texts + // of the same group (e.g. Hd2L and Hd2 and Hd2R) have equal baselines. + int ascents[rangeSize]; + int heights[rangeSize]; + int widths[ rangeSize]; + int maxAscent = 0; + int maxHeight = 0; + for( i = 0; i < rangeSize; ++i ){ + iHdFt = ranges[iRange] + i; + if ( metrics[ iRange*rangeSize + i ] ) { + QFontMetrics& m = *metrics[ iRange*rangeSize + i ]; + ascents[i] = m.ascent(); + heights[i] = m.height() + leading23; + + // the following adds two spaces to work around a bug in Qt: + // bounding rect sometimes is too small, if using italicized fonts + widths[ i] = m.boundingRect( params()->headerFooterText( iHdFt )+" " ).width() + leading23; + + maxAscent = QMAX( maxAscent, ascents[i] ); + maxHeight = QMAX( maxHeight, heights[i] ); + }else{ + heights[i] = 0; + } + } + + if( !bHeader ) + yposBottom -= maxHeight; + + for( i = 0; i < rangeSize; ++i ){ + if( heights[i] ){ + iHdFt = ranges[iRange] + i; + int x1; + switch( i ){ + case 1: x1 = xposLeft+1; + break; + case 2: x1 = xposRight-widths[i]-1; + break; + default: x1 = xposLeft + (xposRight-xposLeft-widths[i]) / 2; + } + ((KDChartParams*)params())->__internalStoreHdFtRect( iHdFt, + QRect( x1, + bHeader + ? yposTop + maxAscent - ascents[i] + : yposBottom + maxAscent - ascents[i], + widths[ i], + heights[i] - 1 ) ); + } + } + if( bHeader ) + yposTop += leading + maxHeight; + else + yposBottom -= leading; + } + for( i = 0; i < rangesCnt*rangeSize; ++i ) + if( metrics[ i ] ) + delete metrics[ i ]; + return leading; +} + + + +void KDChartPainter::findChartDatasets( KDChartTableDataBase* data, + bool paint2nd, + uint chart, + uint& chartDatasetStart, + uint& chartDatasetEnd ) +{ + chartDatasetStart = 0; + chartDatasetEnd = 0; + if( params()->neverUsedSetChartSourceMode() + || !params()->findDatasets( KDChartParams::DataEntry, + KDChartParams::ExtraLinesAnchor, + chartDatasetStart, + chartDatasetEnd, + chart ) ) { + uint maxRow, maxRowMinus1; + switch ( data->usedRows() ) { + case 0: + return ; + case 1: + maxRow = 0; + maxRowMinus1 = 0; + break; + default: + maxRow = data->usedRows() - 1; + maxRowMinus1 = maxRow; + } + chartDatasetStart = paint2nd ? maxRow : 0; + chartDatasetEnd = paint2nd + ? maxRow + : ( ( KDChartParams::NoType == params()->additionalChartType() ) + ? maxRow + : maxRowMinus1 ); + + } +} + + +void KDChartPainter::calculateAllAxesRects( + QPainter* painter, + bool finalPrecision, + KDChartTableDataBase* data + ) +{ + const bool bIsAreaChart = KDChartParams::Area == params()->chartType(); + const bool bMultiRows = KDChartParams::Bar == params()->chartType() && + KDChartParams::BarMultiRows == params()->barChartSubType(); + + const int trueWidth = _outermostRect.width(); + const int trueHeight = _outermostRect.height(); + const double averageValueP1000 = QMIN(_areaWidthP1000, _areaHeightP1000);//( _areaWidthP1000 + _areaHeightP1000 ) / 2.0; + + // store the axes' 0 offsets + int nAxesLeft0 = _axesRect.left() - _outermostRect.left(); + int nAxesRight0 = _outermostRect.right() - _axesRect.right(); + int nAxesTop0 = _axesRect.top() - _outermostRect.top(); + int nAxesBottom0 = _outermostRect.bottom() - _axesRect.bottom(); + if( bMultiRows ){ + uint chartDatasetStart, chartDatasetEnd; + findChartDatasets( data, false, 0, chartDatasetStart, chartDatasetEnd ); + const int datasets = chartDatasetEnd - chartDatasetStart + 1; + int numValues = 0; + if ( params()->numValues() != -1 ) + numValues = params()->numValues(); + else + numValues = data->usedCols(); + if( datasets ){ + const int additionalGapWidth = static_cast < int > ( 1.0 * _axesRect.width() / (9.75*numValues + 4.0*datasets) * 4.0*datasets ); + nAxesRight0 += additionalGapWidth; + nAxesTop0 += static_cast < int > ( additionalGapWidth * 0.52 ); + //const double widthFactor = additionalGapWidth*1.0 / _axesRect.width(); + //nAxesTop0 += static_cast < int > ( _axesRect.height() * widthFactor ); + } + } + // store the distances to be added to the axes' 0 offsets + int nAxesLeftADD =0; + int nAxesRightADD =0; + int nAxesTopADD =0; + int nAxesBottomADD=0; + + // determine whether the axes widths of one side should be added + // or their maximum should be used + bool bAddLeft = axesOverlapping( KDChartAxisParams::AxisPosLeft, + KDChartAxisParams::AxisPosLeft2 ); + bool bAddRight = axesOverlapping( KDChartAxisParams::AxisPosRight, + KDChartAxisParams::AxisPosRight2 ); + bool bAddTop = axesOverlapping( KDChartAxisParams::AxisPosTop, + KDChartAxisParams::AxisPosTop2 ); + bool bAddBottom = axesOverlapping( KDChartAxisParams::AxisPosBottom, + KDChartAxisParams::AxisPosBottom2 ); + // iterate over all axes + uint iAxis; + for ( iAxis = 0; iAxis < KDCHART_MAX_AXES; ++iAxis ) { + //qDebug( "iAxis %i", iAxis ); + const KDChartAxisParams& para = params()->axisParams( iAxis ); + int areaSize = 0; + + if ( para.axisVisible() + && KDChartAxisParams::AxisTypeUnknown != para.axisType() ) { + + const KDChartAxisParams::AxisPos + basicPos( KDChartAxisParams::basicAxisPos( iAxis ) ); + + int areaMin = para.axisAreaMin(); + int areaMax = para.axisAreaMax(); + if ( 0 > areaMin ) + areaMin = static_cast < int > ( -1.0 * averageValueP1000 * areaMin ); + if ( 0 > areaMax ) + areaMax = static_cast < int > ( -1.0 * averageValueP1000 * areaMax ); + + // make sure areaMin will not be too small + // for the label texts and check if there is an axis Title + switch ( basicPos ) { + case KDChartAxisParams::AxisPosBottom: + case KDChartAxisParams::AxisPosTop: + if ( para.axisLabelsVisible() ) { + int fntHeight; + if ( para.axisLabelsFontUseRelSize() ) + fntHeight = QMAX(static_cast < int > ( para.axisLabelsFontRelSize() * averageValueP1000 ), + para.axisLabelsFontMinSize() ); + else { + painter->setFont( para.axisLabelsFont() ); + QFontMetrics metrics( painter->fontMetrics() ); + fntHeight = metrics.height(); + } + // adjust text height in case of formatted Date/Time values + uint dataDataset, dataDataset2; + if( !params()->findDataset( KDChartParams::DataEntry, + dataDataset, + dataDataset2, + KDCHART_ALL_CHARTS ) ) { + qDebug( "IMPLEMENTATION ERROR: findDataset( DataEntry, ... ) should *always* return true. (a)" ); + dataDataset = KDCHART_ALL_DATASETS; + } + QVariant::Type valType = QVariant::Invalid; + const bool dataCellsHaveSeveralCoordinates = + (KDCHART_ALL_DATASETS == dataDataset) + ? data->cellsHaveSeveralCoordinates( &valType ) + : data->cellsHaveSeveralCoordinates( dataDataset, dataDataset2, &valType ); + QString format( para.axisLabelsDateTimeFormat() ); + if( dataCellsHaveSeveralCoordinates + && QVariant::DateTime == valType ){ + if( KDCHART_AXIS_LABELS_AUTO_DATETIME_FORMAT == format ) + areaMin = QMAX( areaMin, static_cast < int > ( fntHeight * 6.75 ) ); + else + areaMin = QMAX( areaMin, fntHeight * ( 3 + format.contains("\n") ) ); + } + else + areaMin = QMAX( areaMin, fntHeight * 3 ); + } + break; + case KDChartAxisParams::AxisPosLeft: + case KDChartAxisParams::AxisPosRight: + default: + break; + } + + + switch ( para.axisAreaMode() ) { + case KDChartAxisParams::AxisAreaModeAutoSize: + { + areaSize = areaMin; + switch ( basicPos ) { + case KDChartAxisParams::AxisPosBottom: + case KDChartAxisParams::AxisPosTop: + break; + case KDChartAxisParams::AxisPosLeft: + case KDChartAxisParams::AxisPosRight: + if( finalPrecision ){ + internal__KDChart__CalcValues& cv = calcVal[iAxis]; + const int nUsableAxisWidth = static_cast < int > (cv.pTextsW); + const KDChartAxisParams & para = params()->axisParams( iAxis ); + QFont axisLabelsFont( para.axisLabelsFont() ); + if ( para.axisLabelsFontUseRelSize() ) { + axisLabelsFont.setPixelSize( static_cast < int > ( cv.nTxtHeight ) ); + } + painter->setFont( para.axisLabelsFont() ); + QFontMetrics axisLabelsFontMetrics( painter->fontMetrics() ); + const int lenEM( axisLabelsFontMetrics.boundingRect("M").width() ); + const QStringList* labelTexts = para.axisLabelTexts(); + uint nLabels = ( 0 != labelTexts ) + ? labelTexts->count() + : 0; + int maxLabelsWidth = 0; + for ( uint i = 0; i < nLabels; ++i ) + maxLabelsWidth = + QMAX( maxLabelsWidth, + axisLabelsFontMetrics.boundingRect(*labelTexts->at(i)).width() ); + if( nUsableAxisWidth < maxLabelsWidth ) + areaSize = maxLabelsWidth + + (para.axisTrueAreaRect().width() - nUsableAxisWidth) + + lenEM; + } + break; + default: + break; + } + } + break; + case KDChartAxisParams::AxisAreaModeMinMaxSize: + { + qDebug( "Sorry, not implemented: AxisAreaModeMinMaxSize" ); + } + + // + // + // F E A T U R E P L A N N E D F O R F U T U R E . . . + // + // + + // break; + + case KDChartAxisParams::AxisAreaModeFixedSize: + { + areaSize = areaMax ? QMIN( areaMin, areaMax ) : areaMin; + } + break; + } + + //find out if there is a title box + uint idx; + int boxSize = 0; + for( idx = 0; idx <= params()->maxCustomBoxIdx(); ++idx ) { + const KDChartCustomBox * box = params()->customBox( idx ); + if ( box ) + if ( box->parentAxisArea() == KDChartAxisParams::AxisPosBottom + || box->parentAxisArea() == KDChartAxisParams::AxisPosLeft + || box->parentAxisArea() == KDChartAxisParams::AxisPosTop + || box->parentAxisArea() == KDChartAxisParams::AxisPosRight ) + boxSize = box->trueRect(QPoint( 0,0 ), _areaWidthP1000, _areaHeightP1000 ).height(); + } + + areaSize += boxSize; + + switch ( basicPos ) { + case KDChartAxisParams::AxisPosBottom: + if( bAddBottom ) { + //areaSize += boxSize; + nAxesBottomADD += areaSize; + } + else{ + // areaSize += boxSize; + nAxesBottomADD = QMAX( nAxesBottomADD + boxSize, areaSize ); + } + break; + case KDChartAxisParams::AxisPosLeft: + if( bAddLeft ) + nAxesLeftADD += areaSize; + else + nAxesLeftADD = QMAX( nAxesLeftADD + boxSize, areaSize ); + break; + case KDChartAxisParams::AxisPosTop: + if( bAddTop ) + nAxesTopADD += areaSize; + else + nAxesTopADD = QMAX( nAxesTopADD + boxSize, areaSize ); + break; + case KDChartAxisParams::AxisPosRight: + if( bAddRight ) + nAxesRightADD += areaSize; + else + nAxesRightADD = QMAX( nAxesRightADD + boxSize, areaSize ); + break; + default: + break; + } + } + // Note: to prevent users from erroneously calling this + // function we do *not* provide a wrapper for it + // in the KDChartParams class but rather call it + // *directly* using a dirty typecast. + ( ( KDChartAxisParams& ) para ).setAxisTrueAreaSize( areaSize ); + } + int nMinDistance = static_cast < int > ( 30.0 * averageValueP1000 ); + + int nAxesBottom = QMAX( nAxesBottom0 + nAxesBottomADD, nMinDistance ); + + // for micro alignment with the X axis, we adjust the Y axis - but not for Area Charts: + // otherwise the areas drawn would overwrite the Y axis line. + int nAxesLeft = QMAX( nAxesLeft0 + nAxesLeftADD, nMinDistance ) + - (bIsAreaChart ? 0 : 1); + + int nAxesTop = QMAX( nAxesTop0 + nAxesTopADD, nMinDistance ); + + int nAxesRight = QMAX( nAxesRight0 + nAxesRightADD, nMinDistance ); + + int nBottom = params()->axisParams( KDChartAxisParams::AxisPosBottom ).axisTrueAreaSize(); + int nLeft = params()->axisParams( KDChartAxisParams::AxisPosLeft ).axisTrueAreaSize(); + int nTop = params()->axisParams( KDChartAxisParams::AxisPosTop ).axisTrueAreaSize(); + int nRight = params()->axisParams( KDChartAxisParams::AxisPosRight ).axisTrueAreaSize(); + int nBottom2 = params()->axisParams( KDChartAxisParams::AxisPosBottom2 ).axisTrueAreaSize(); + int nLeft2 = params()->axisParams( KDChartAxisParams::AxisPosLeft2 ).axisTrueAreaSize(); + int nTop2 = params()->axisParams( KDChartAxisParams::AxisPosTop2 ).axisTrueAreaSize(); + int nRight2 = params()->axisParams( KDChartAxisParams::AxisPosRight2 ).axisTrueAreaSize(); + + internSetAxisArea( _params, + KDChartAxisParams::AxisPosBottom, + _outermostRect.left() + nAxesLeft, + _outermostRect.top() + trueHeight - nAxesBottom, + trueWidth - nAxesLeft - nAxesRight + 1, + nBottom ); + internSetAxisArea( _params, + KDChartAxisParams::AxisPosLeft, + _outermostRect.left() + (bAddLeft ? nAxesLeft0 + nLeft2 : nAxesLeft0), + _outermostRect.top() + nAxesTop, + nLeft, + trueHeight - nAxesTop - nAxesBottom + 1 ); + + internSetAxisArea( _params, + KDChartAxisParams::AxisPosTop, + _outermostRect.left() + nAxesLeft, + _outermostRect.top() + (bAddTop ? nAxesTop0 + nTop2 : nAxesTop0), + trueWidth - nAxesLeft - nAxesRight + 1, + nTop ); + internSetAxisArea( _params, + KDChartAxisParams::AxisPosRight, + _outermostRect.left() + trueWidth - nAxesRight, + _outermostRect.top() + nAxesTop, + nRight, + trueHeight - nAxesTop - nAxesBottom + 1 ); + + internSetAxisArea( _params, + KDChartAxisParams::AxisPosBottom2, + _outermostRect.left() + nAxesLeft, + _outermostRect.top() + trueHeight - nAxesBottom + (bAddBottom ? nBottom : 0), + trueWidth - nAxesLeft - nAxesRight + 1, + nBottom2 ); + internSetAxisArea( _params, + KDChartAxisParams::AxisPosLeft2, + _outermostRect.left() + nAxesLeft0, + _outermostRect.top() + nAxesTop, + nLeft2, + trueHeight - nAxesTop - nAxesBottom + 1 ); + + internSetAxisArea( _params, + KDChartAxisParams::AxisPosTop2, + _outermostRect.left() + nAxesLeft, + _outermostRect.top() + nAxesTop0, + trueWidth - nAxesLeft - nAxesRight + 1, + nTop2 ); + internSetAxisArea( _params, + KDChartAxisParams::AxisPosRight2, + _outermostRect.left() + trueWidth - nAxesRight + (bAddRight ? nRight : 0), + _outermostRect.top() + nAxesTop, + nRight2, + trueHeight - nAxesTop - nAxesBottom + 1 ); + + _dataRect = QRect( _outermostRect.left() + nAxesLeft, + _outermostRect.top() + nAxesTop, + trueWidth - nAxesLeft - nAxesRight + 1, + trueHeight - nAxesTop - nAxesBottom + 1 ); +} + + + +/** + This method will be called whenever any parameters that affect + geometry have been changed. It will compute the appropriate + positions for the various parts of the chart (legend, axes, data + area etc.). The implementation in KDChartPainter computes a + standard geometry that should be suitable for most chart + types. Subclasses can provide their own implementations. + + \param data the data that will be displayed as a chart + \param drawRect the position and size of the area where the chart + is to be displayed in + */ +void KDChartPainter::setupGeometry( QPainter* painter, + KDChartTableDataBase* data, + const QRect& drawRect ) +{ + //qDebug("INVOKING: KDChartPainter::setupGeometry()"); + // avoid recursion from repaint() being called due to params() changed signals... + const bool oldBlockSignalsState = params()->signalsBlocked(); + const_cast < KDChartParams* > ( params() )->blockSignals( true ); + + _outermostRect = drawRect; + + int yposTop = _outermostRect.topLeft().y(); + int xposLeft = _outermostRect.topLeft().x(); + int yposBottom = _outermostRect.bottomRight().y(); + int xposRight = _outermostRect.bottomRight().x(); + + const int trueWidth = _outermostRect.width(); + const int trueHeight = _outermostRect.height(); + + // Temporary values used to calculate start values xposLeft, yposTop, xposRight, yposBottom. + // They will be replaced immediately after these calculations. + _areaWidthP1000 = trueWidth / 1000.0; + _areaHeightP1000 = trueHeight / 1000.0; + + + xposLeft += 0 < params()->globalLeadingLeft() + ? params()->globalLeadingLeft() + : static_cast < int > ( params()->globalLeadingLeft() * -_areaWidthP1000 ); + yposTop += 0 < params()->globalLeadingTop() + ? params()->globalLeadingTop() + : static_cast < int > ( params()->globalLeadingTop() * -_areaHeightP1000 ); + xposRight -= 0 < params()->globalLeadingRight() + ? params()->globalLeadingRight() + : static_cast < int > ( params()->globalLeadingRight() * -_areaWidthP1000 ); + yposBottom -= 0 < params()->globalLeadingBottom() + ? params()->globalLeadingBottom() + : static_cast < int > ( params()->globalLeadingBottom()* -_areaHeightP1000 ); + + _innermostRect = QRect( QPoint(xposLeft, yposTop), + QPoint(xposRight, yposBottom) ); + + _logicalWidth = xposRight - xposLeft; + _logicalHeight = yposBottom - yposTop; + + // true values (having taken the global leadings into account) + // to be used by all following functions + _areaWidthP1000 = _logicalWidth / 1000.0; + _areaHeightP1000 = _logicalHeight / 1000.0; + + double averageValueP1000 = QMIN(_areaWidthP1000, _areaHeightP1000);//( _areaWidthP1000 + _areaHeightP1000 ) / 2.0; + + // new code design: + // 1. now min-header-leading is text height/2 + // 2. leading or legendSpacing (whichever is larger) + // will be added if legend is below the header(s) + // 3. leading will be added between header and data area + // in case there is no top legend but grid is to be shown. + int headerLineLeading = calculateHdFtRects( + painter, + averageValueP1000, + xposLeft, xposRight, + false, + yposTop, yposBottom ); + calculateHdFtRects( + painter, + averageValueP1000, + xposLeft, xposRight, + true, + yposTop, yposBottom ); + + // Calculate legend position. First check whether there is going + // to be a legend at all: + if ( params()->legendPosition() != KDChartParams::NoLegend ) { + // Now calculate the size needed for the legend + findLegendTexts( data ); + + bool hasLegendTitle = false; + if ( !params()->legendTitleText().isEmpty() ) + hasLegendTitle = true; + + _legendTitleWidth = 0; + if( _legendTitle ) + delete _legendTitle; + _legendTitle = 0; + if ( hasLegendTitle ) { + const QFont font( trueLegendTitleFont() ); + painter->setFont( font ); + QFontMetrics legendTitleMetrics( painter->fontMetrics() ); + _legendTitleMetricsHeight = legendTitleMetrics.height(); + + _legendTitle = new KDChartTextPiece( painter, + params()->legendTitleText(), + font ); + _legendTitleWidth = _legendTitle->width(); + _legendTitleHeight = _legendTitle->height(); + // qDebug("1. _legendTitleHeight %i",_legendTitleHeight); + } + + painter->setFont( trueLegendFont() ); + QFontMetrics legendMetrics( painter->fontMetrics() ); + _legendSpacing = legendMetrics.lineSpacing(); + _legendHeight = legendMetrics.height(); + _legendLeading = legendMetrics.leading(); + + _legendEMSpace = legendMetrics.width( 'M' ); + + int sizeX = 0; + int sizeY = 0; + + for ( int dataset = 0; dataset < _numLegendTexts; dataset++ ) { + sizeX = QMAX( sizeX, legendMetrics.width( _legendTexts[ dataset ] ) ); + if( !_legendTexts[ dataset ].isEmpty() ) + sizeY += _legendSpacing; + } + // add space below the legend's bottom line + sizeY += _legendEMSpace - _legendLeading; + // add space for the legend title if any was set + if ( hasLegendTitle ) + sizeY += legendTitleVertGap(); + + // assume 4 em spaces: before the color box, the color box, after the + // color box and after the legend text + sizeX += ( _legendEMSpace * 4 ); + + // We cannot setup the title width earlier as the title does + // not have a color box. The two em spaces are before the + // color box (where the title does not start yet, it is + // left-aligned with the color boxes) and after the title (to + // have some space before the boundary line comes). + sizeX = QMAX( sizeX, _legendTitleWidth + _legendEMSpace*2 ); + + //qDebug("setupGeometry mustDrawVerticalLegend: %s", mustDrawVerticalLegend() ? "YES":"NO "); + + // PENDING Michel: do that after having calculated the position + if( !mustDrawVerticalLegend() ){ + QSize size; + calculateHorizontalLegendSize( painter, + size, + _legendNewLinesStartAtLeft ); + sizeX = size.width(); + sizeY = size.height(); + } + + switch ( params()->legendPosition() ) { + case KDChartParams::LegendTop: + if ( headerLineLeading ) + yposTop += QMAX( (int)params()->legendSpacing(), headerLineLeading ); + _legendRect = QRect( xposLeft + ( (xposRight-xposLeft) - sizeX ) / 2, + yposTop, sizeX, sizeY ); + yposTop = _legendRect.bottom() + params()->legendSpacing(); + //qDebug("A: _legendRect:\n%i,%i\n%i,%i", _legendRect.left(),_legendRect.top(),_legendRect.right(),_legendRect.bottom() ); + break; + case KDChartParams::LegendBottom: + if ( params()->showGrid() ) + yposTop += headerLineLeading; + _legendRect = QRect( xposLeft + ( (xposRight-xposLeft) - sizeX ) / 2, + yposBottom - sizeY, + sizeX, sizeY ); + yposBottom = _legendRect.top() - params()->legendSpacing(); + break; + case KDChartParams::LegendLeft: + if ( params()->showGrid() ) + yposTop += headerLineLeading; + _legendRect = QRect( xposLeft + 1, ( yposBottom - yposTop - sizeY ) / 2 + + yposTop, + sizeX, sizeY ); + xposLeft = _legendRect.right() + params()->legendSpacing(); + break; + case KDChartParams::LegendRight: + if ( params()->showGrid() ) + yposTop += headerLineLeading; + _legendRect = QRect( xposRight - sizeX - 1, + ( yposBottom - yposTop - sizeY ) / 2 + yposTop, + sizeX, sizeY ); + xposRight = _legendRect.left() - params()->legendSpacing(); + break; + case KDChartParams::LegendTopLeft: + if ( headerLineLeading ) + yposTop += QMAX( (int)params()->legendSpacing(), headerLineLeading ); + _legendRect = QRect( xposLeft + 1, yposTop, sizeX, sizeY ); + yposTop = _legendRect.bottom() + params()->legendSpacing(); + xposLeft = _legendRect.right() + params()->legendSpacing(); + break; + case KDChartParams::LegendTopLeftTop: + if ( headerLineLeading ) + yposTop += QMAX( (int)params()->legendSpacing(), headerLineLeading ); + _legendRect = QRect( xposLeft + 1, yposTop, sizeX, sizeY ); + yposTop = _legendRect.bottom() + params()->legendSpacing(); + break; + case KDChartParams::LegendTopLeftLeft: + if ( headerLineLeading ) + yposTop += QMAX( (int)params()->legendSpacing(), headerLineLeading ); + _legendRect = QRect( xposLeft + 1, yposTop, sizeX, sizeY ); + xposLeft = _legendRect.right() + params()->legendSpacing(); + break; + case KDChartParams::LegendTopRight: + if ( headerLineLeading ) + yposTop += QMAX( (int)params()->legendSpacing(), headerLineLeading ); + _legendRect = QRect( xposRight - sizeX - 1, + yposTop, sizeX, sizeY ); + yposTop = _legendRect.bottom() + params()->legendSpacing(); + xposRight = _legendRect.left() - params()->legendSpacing(); + break; + case KDChartParams::LegendTopRightTop: + if ( headerLineLeading ) + yposTop += QMAX( (int)params()->legendSpacing(), headerLineLeading ); + _legendRect = QRect( xposRight - sizeX - 1, + yposTop, sizeX, sizeY ); + yposTop = _legendRect.bottom() + params()->legendSpacing(); + break; + case KDChartParams::LegendTopRightRight: + if ( headerLineLeading ) + yposTop += QMAX( (int)params()->legendSpacing(), headerLineLeading ); + _legendRect = QRect( xposRight - sizeX - 1, + yposTop, sizeX, sizeY ); + xposRight = _legendRect.left() - params()->legendSpacing(); + break; + case KDChartParams::LegendBottomLeft: + if ( params()->showGrid() ) + yposTop += headerLineLeading; + _legendRect = QRect( xposLeft + 1, yposBottom - sizeY, sizeX, sizeY ); + yposBottom = _legendRect.top() - params()->legendSpacing(); + xposLeft = _legendRect.right() + params()->legendSpacing(); + break; + case KDChartParams::LegendBottomLeftBottom: + if ( params()->showGrid() ) + yposTop += headerLineLeading; + _legendRect = QRect( xposLeft + 1, yposBottom - sizeY, sizeX, sizeY ); + yposBottom = _legendRect.top() - params()->legendSpacing(); + break; + case KDChartParams::LegendBottomLeftLeft: + if ( params()->showGrid() ) + yposTop += headerLineLeading; + _legendRect = QRect( xposLeft + 1, yposBottom - sizeY, sizeX, sizeY ); + xposLeft = _legendRect.right() + params()->legendSpacing(); + break; + case KDChartParams::LegendBottomRight: + if ( params()->showGrid() ) + yposTop += headerLineLeading; + _legendRect = QRect( xposRight - sizeX - 1, + yposBottom - sizeY, sizeX, sizeY ); + yposBottom = _legendRect.top() - params()->legendSpacing(); + xposRight = _legendRect.left() - params()->legendSpacing(); + break; + case KDChartParams::LegendBottomRightBottom: + if ( params()->showGrid() ) + yposTop += headerLineLeading; + _legendRect = QRect( xposRight - sizeX - 1, + yposBottom - sizeY, sizeX, sizeY ); + yposBottom = _legendRect.top() - params()->legendSpacing(); + break; + case KDChartParams::LegendBottomRightRight: + if ( params()->showGrid() ) + yposTop += headerLineLeading; + _legendRect = QRect( xposRight - sizeX - 1, + yposBottom - sizeY, sizeX, sizeY ); + xposRight = _legendRect.left() - params()->legendSpacing(); + break; + default: + // Should not be able to happen + qDebug( "KDChart: Unknown legend position" ); + } + _params->setLegendArea( _legendRect ); + + }else{ + _params->setLegendArea( QRect(QPoint(0,0), QSize(0,0)) ); + } + + + _axesRect = QRect( QPoint(xposLeft, yposTop), QPoint(xposRight, yposBottom) ); + + // important rule: do *not* calculate axes areas for Polar charts! + // (even if left and bottom axes might be set active) + if( KDChartParams::Polar == params()->chartType() ) { + _dataRect = _axesRect; + } else { + // 1st step: make a preliminary approximation of the axes sizes, + // as a basis of following label texts calculation + calculateAllAxesRects( painter, false, data ); + // 2nd step: calculate all labels (preliminary data, will be + // overwritten by KDChartAxesPainter) + // to find out the longest possible axis labels + double dblDummy; + if( calculateAllAxesLabelTextsAndCalcValues( + painter, + data, + _areaWidthP1000, + _areaHeightP1000, + dblDummy ) ) + // 3rd step: calculate the _true_ axes rects based upon + // the preliminary axes labels + calculateAllAxesRects( painter, true, data ); + } + _params->setDataArea( _dataRect ); + + const_cast < KDChartParams* > ( params() )->blockSignals( oldBlockSignalsState ); +} + + +/** + This method implements the algorithm to find the texts for the legend. + */ +void KDChartPainter::findLegendTexts( KDChartTableDataBase* data ) +{ + uint dataset; + QVariant vValY; + switch ( params()->legendSource() ) { + case KDChartParams::LegendManual: { + // The easiest case: Take manually set strings, no matter whether any + // have been set. + _numLegendTexts = numLegendFallbackTexts( data ); + for ( dataset = 0; dataset < static_cast(_numLegendTexts); dataset++ ) + _legendTexts[ dataset ] = params()->legendText( dataset ); + break; + } + case KDChartParams::LegendFirstColumn: { + // Take whatever is in the first column + for ( dataset = 0; dataset < data->usedRows(); dataset++ ){ + if( data->cellCoord( dataset, 0, vValY, 1 ) ){ + if( QVariant::String == vValY.type() ) + _legendTexts[ dataset ] = vValY.toString(); + else + _legendTexts[ dataset ] = ""; + } + } + _numLegendTexts = data->usedRows(); + break; + } + case KDChartParams::LegendAutomatic: { + // First, try the first row + bool notfound = false; + _numLegendTexts = numLegendFallbackTexts( data ); // assume this for cleaner + // code below + for ( dataset = 0; dataset < data->usedRows(); dataset++ ) { + if( data->cellCoord( dataset, 0, vValY, 1 ) ){ + if( QVariant::String == vValY.type() ) + _legendTexts[ dataset ] = vValY.toString(); + else + _legendTexts[ dataset ] = ""; + if( _legendTexts[ dataset ].isEmpty() ){ + notfound = true; + break; + } + } + } + + // If there were no entries for all the datasets, use the manually set + // texts, and resort to Series 1, Series 2, ... where nothing has been + // set. + if ( notfound ) { + for ( dataset = 0; dataset < numLegendFallbackTexts( data ); + dataset++ ) { + _legendTexts[ dataset ] = params()->legendText( dataset ); + if ( _legendTexts[ dataset ].isEmpty() || _legendTexts[ dataset ].isNull() ) { + _legendTexts[ dataset ] = fallbackLegendText( dataset ); + // there + _numLegendTexts = numLegendFallbackTexts( data ); + } + } + } + break; + } + default: + // Should not happen + qDebug( "KDChart: Unknown legend source" ); + } +} + + +/** + This method provides a fallback legend text for the specified + dataset, if there was no other way to determine a legend text, but + a legend should be shown nevertheless. The default is to return + "Series" plus a dataset number (with datasets starting at 1 for + this purpose; inherited painter implementations can override this. + + 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 KDChartPainter::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. By default, this will be + the number of datasets, but specialized painters can override this + (e.g., painters that draw charts that can only display one dataset + will return the number of values instead). + + 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 KDChartPainter::numLegendFallbackTexts( KDChartTableDataBase* data ) const +{ + return data->usedRows(); +} + + +/** + Draws the marker for one data point according to the specified style, color, size. + + \param painter the painter to draw on + \param style what kind of marker is drawn (square, diamond, circle, ...) + \param color the color in which to draw the marker + \param p the center of the marker + \param size the width and height of the marker: both values must be positive. + */ +void KDChartPainter::drawMarker( QPainter* painter, + int style, + const QColor& color, + const QPoint& p, + const QSize& size, + uint align ) +{ + int width = size.width(); + int height = size.height(); + drawMarker( painter, + 0, + 0.0, 0.0, + 0,0, + style, + color, + p, + 0,0,0, + 0, + &width, + &height, + align ); +} + + +/** + Draws the marker for one data point according to the specified style. + + \param painter the painter to draw on + \param style what kind of marker is drawn (square, diamond, circle, ...) + \param color the color in which to draw the marker + \param p the center of the marker + \param dataset the dataset which this marker represents + \param value the value which this marker represents + \param regions a list of regions for data points, a new region for the new + marker will be appended to this list if it is not 0 + + \return pointer to the KDChartDataRegion that was appended to the regions list, + or zero if if parameter regions was zero + */ +KDChartDataRegion* KDChartPainter::drawMarker( QPainter* painter, + const KDChartParams* params, + double areaWidthP1000, + double areaHeightP1000, + int deltaX, + int deltaY, + int style, + const QColor& color, + const QPoint& _p, + uint dataset, uint value, uint chart, + KDChartDataRegionList* regions, + int* width, + int* height, + uint align ) +{ + KDChartDataRegion* datReg = 0; + const double areaSizeP1000 = QMIN(areaWidthP1000, areaHeightP1000); + int xsize = width ? *width : (params ? params->lineMarkerSize().width() : 12); + if( 0 > xsize ) + xsize = static_cast < int > (xsize * -areaSizeP1000); + int ysize = height ? *height : (params ? params->lineMarkerSize().height() : 12); + if( 0 > ysize ) + ysize = static_cast < int > (ysize * -areaSizeP1000); + if( KDChartParams::LineMarkerCross != style ){ + xsize = QMAX( xsize, 4 ); + ysize = QMAX( ysize, 4 ); + } + uint xsize2 = xsize / 2; + uint ysize2 = ysize / 2; + uint xsize4 = xsize / 4; + uint ysize4 = ysize / 4; + uint xsize6 = xsize / 6; + uint ysize6 = ysize / 6; + painter->setPen( color ); + const uint xysize2 = QMIN( xsize2, ysize2 ); + + int x = _p.x(); + int y = _p.y(); + if( align & Qt::AlignLeft ) + x += xsize2; + else if( align & Qt::AlignRight ) + x -= xsize2; + if( align & Qt::AlignTop ) + y += ysize2; + else if( align & Qt::AlignBottom ) + y -= ysize2; + const QPoint p(x, y); + + switch ( style ) { + case KDChartParams::LineMarkerSquare: { + const QPen oldPen( painter->pen() ); + const QBrush oldBrush( painter->brush() ); + painter->setBrush( color ); + painter->setPen( color ); + QRect rect( QPoint( p.x() - xsize2, p.y() - ysize2 ), QPoint( p.x() + xsize2, p.y() + ysize2 ) ); + painter->drawRect( rect ); + // Don't use rect for drawing after this! + rect.moveBy( deltaX, deltaY ); + if ( regions ){ + datReg = + new KDChartDataRegion( + dataset, value, + chart, rect ); + regions->append( datReg ); + } + painter->setPen( oldPen ); + painter->setBrush( oldBrush ); + break; + } + case KDChartParams::LineMarkerDiamond:{ + const QBrush oldBrush( painter->brush() ); + painter->setBrush( color ); + QPointArray points( 4 ); + points.setPoint( 0, p.x() - xsize2, p.y() ); + points.setPoint( 1, p.x(), p.y() - ysize2 ); + points.setPoint( 2, p.x() + xsize2, p.y() ); + points.setPoint( 3, p.x(), p.y() + ysize2 ); + painter->drawPolygon( points ); + // Don't use points for drawing after this! + points.translate( deltaX, deltaY ); + if ( regions ){ + datReg = new KDChartDataRegion( + dataset, value, + chart, points ); + regions->append( datReg ); + } + painter->setBrush( oldBrush ); + break; + } + case KDChartParams::LineMarker1Pixel: { + QRect rect( p, p ); + painter->drawRect( rect ); + // Don't use rect for drawing after this! + rect.moveBy( deltaX, deltaY ); + if ( regions ){ + datReg = new KDChartDataRegion( + dataset, value, + chart, rect ); + regions->append( datReg ); + } + break; + } + case KDChartParams::LineMarker4Pixels:{ + QRect rect( p, QPoint( p.x()+1, p.y()+1 ) ); + painter->drawRect( rect ); + // Don't use rect for drawing after this! + rect.moveBy( deltaX, deltaY ); + if ( regions ){ + datReg = new KDChartDataRegion( + dataset, value, + chart, rect ); + regions->append( datReg ); + } + break; + } + case KDChartParams::LineMarkerRing: { + const QPen oldPen( painter->pen() ); + painter->setPen( QPen( color, QMIN(xsize4, ysize4) ) ); + const QBrush oldBrush( painter->brush() ); + painter->setBrush( Qt::NoBrush ); + painter->drawEllipse( p.x() - xsize2, p.y() - ysize2, xsize, ysize ); + if ( regions ) { + QPointArray points; + points.makeEllipse( p.x() - xsize2, p.y() - ysize2, xsize, ysize ); + // Don't use points for drawing after this! + points.translate( deltaX, deltaY ); + if( points.size() > 0 ){ + datReg = new KDChartDataRegion( + dataset, value, + chart, points ); + regions->append( datReg ); + } + } + painter->setBrush( oldBrush ); + painter->setPen( oldPen ); + break; + } + case KDChartParams::LineMarkerCross: { + const QPen oldPen( painter->pen() ); + painter->setPen( color ); + const QBrush oldBrush( painter->brush() ); + painter->setBrush( color ); + int numPoints = (ysize && xsize) ? 12 : 4; + QPointArray points( numPoints ); + if( ysize && xsize ){ + points.setPoint( 0, p.x() - xsize6, p.y() - ysize6 ); + points.setPoint( 1, p.x() - xsize6, p.y() - ysize2 ); + points.setPoint( 2, p.x() + xsize6, p.y() - ysize2 ); + points.setPoint( 3, p.x() + xsize6, p.y() - ysize6 ); + points.setPoint( 4, p.x() + xsize2, p.y() - ysize6 ); + points.setPoint( 5, p.x() + xsize2, p.y() + ysize6 ); + points.setPoint( 6, p.x() + xsize6, p.y() + ysize6 ); + points.setPoint( 7, p.x() + xsize6, p.y() + ysize2 ); + points.setPoint( 8, p.x() - xsize6, p.y() + ysize2 ); + points.setPoint( 9, p.x() - xsize6, p.y() + ysize6 ); + points.setPoint(10, p.x() - xsize2, p.y() + ysize6 ); + points.setPoint(11, p.x() - xsize2, p.y() - ysize6 ); + }else if( ysize ){ + points.setPoint( 0, p.x() - ysize6, p.y() - ysize2 ); + points.setPoint( 1, p.x() + ysize6, p.y() - ysize2 ); + points.setPoint( 2, p.x() + ysize6, p.y() + ysize2 ); + points.setPoint( 3, p.x() - ysize6, p.y() + ysize2 ); + }else{ + points.setPoint( 0, p.x() - xsize2, p.y() - xsize6 ); + points.setPoint( 1, p.x() + xsize2, p.y() - xsize6 ); + points.setPoint( 2, p.x() + xsize2, p.y() + xsize6 ); + points.setPoint( 3, p.x() - xsize2, p.y() + xsize6 ); + } + painter->drawPolygon( points ); + // Don't use points for drawing after this! + points.translate( deltaX, deltaY ); + if( regions ){ + datReg = new KDChartDataRegion( + dataset, value, + chart, points ); + regions->append( datReg ); + } + painter->setBrush( oldBrush ); + painter->setPen( oldPen ); + break; + } + case KDChartParams::LineMarkerFastCross: { + const QPen oldPen( painter->pen() ); + painter->setPen( color ); + painter->drawLine( QPoint(p.x() - xysize2, p.y()), + QPoint(p.x() + xysize2, p.y()) ); + painter->drawLine( QPoint(p.x(), p.y() - xysize2), + QPoint(p.x(), p.y() + xysize2) ); + QRect rect( QPoint( p.x() - 2, p.y() - 2 ), + QPoint( p.x() + 2, p.y() + 2 ) ); + // Don't use rect for drawing after this! + rect.moveBy( deltaX, deltaY ); + if ( regions ){ + datReg = + new KDChartDataRegion( + dataset, value, + chart, rect ); + regions->append( datReg ); + } + painter->setPen( oldPen ); + break; + } + case KDChartParams::LineMarkerCircle: + default: { + const QBrush oldBrush( painter->brush() ); + painter->setBrush( color ); + painter->drawEllipse( p.x() - xsize2, p.y() - ysize2, xsize, ysize ); + if ( regions ) { + QPointArray points; + points.makeEllipse( p.x() - xsize2, p.y() - ysize2, xsize, ysize ); + // Don't use points for drawing after this! + points.translate( deltaX, deltaY ); + if( points.size() > 0 ){ + datReg = new KDChartDataRegion( + dataset, value, + chart, points ); + regions->append( datReg ); + } + } + painter->setBrush( oldBrush ); + } + } + return datReg; +} + + +void KDChartPainter::drawExtraLinesAndMarkers( + KDChartPropertySet& propSet, + const QPen& defaultPen, + const KDChartParams::LineMarkerStyle& defaultMarkerStyle, + int myPointX, + int myPointY, + QPainter* painter, + const KDChartAxisParams* abscissaPara, + const KDChartAxisParams* ordinatePara, + const double areaWidthP1000, + const double areaHeightP1000, + bool bDrawInFront ) +{ + + // we can safely call the following functions and ignore their + // return values since they will touch the parameters' values + // if the propSet *contains* corresponding own values only. + int iDummy; + uint extraLinesAlign = 0; + if( propSet.hasOwnExtraLinesAlign( iDummy, extraLinesAlign ) + && ( extraLinesAlign + & ( Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter | + Qt::AlignTop | Qt::AlignBottom | Qt::AlignVCenter ) ) ){ + bool extraLinesInFront = false; + propSet.hasOwnExtraLinesInFront( iDummy, extraLinesInFront ); + if( bDrawInFront == extraLinesInFront ){ + const double areaSizeP1000 = QMIN(areaWidthP1000, areaHeightP1000); + int extraLinesLength = -20; + int extraLinesWidth = defaultPen.width(); + QColor extraLinesColor = defaultPen.color(); + Qt::PenStyle extraLinesStyle = defaultPen.style(); + uint extraMarkersAlign = 0; + propSet.hasOwnExtraLinesLength( iDummy, extraLinesLength ); + propSet.hasOwnExtraLinesWidth( iDummy, extraLinesWidth ); + propSet.hasOwnExtraLinesColor( iDummy, extraLinesColor ); + propSet.hasOwnExtraLinesStyle( iDummy, extraLinesStyle ); + const int horiLenP2 = (0 > extraLinesLength) + ? static_cast(areaWidthP1000 * extraLinesLength) / 2 + : extraLinesLength / 2; + const int vertLenP2 = (0 > extraLinesLength) + ? static_cast(areaHeightP1000 * extraLinesLength) / 2 + : extraLinesLength / 2; + // draw the extra line(s) + QPoint pL( (Qt::AlignLeft == (extraLinesAlign & Qt::AlignLeft)) + ? 0 + : (Qt::AlignHCenter == (extraLinesAlign & Qt::AlignHCenter)) + ? myPointX - horiLenP2 + : myPointX, + myPointY ); + QPoint pR( (Qt::AlignRight == (extraLinesAlign & Qt::AlignRight)) + ? abscissaPara->axisTrueAreaRect().width() + : (Qt::AlignHCenter == (extraLinesAlign & Qt::AlignHCenter)) + ? myPointX + horiLenP2 + : myPointX, + myPointY ); + QPoint pT( myPointX, + (Qt::AlignTop == (extraLinesAlign & Qt::AlignTop)) + ? 0 + : (Qt::AlignVCenter == (extraLinesAlign & Qt::AlignVCenter)) + ? myPointY - vertLenP2 + : myPointY ); + QPoint pB( myPointX, + (Qt::AlignBottom == (extraLinesAlign & Qt::AlignBottom)) + ? ordinatePara->axisTrueAreaRect().height() + : (Qt::AlignVCenter == (extraLinesAlign & Qt::AlignVCenter)) + ? myPointY + vertLenP2 + : myPointY ); + const QPen extraPen( extraLinesColor, + 0 > extraLinesWidth + ? static_cast < int > ( areaSizeP1000 * -extraLinesWidth ) + : extraLinesWidth, + extraLinesStyle ); + const QPen oldPen( painter->pen() ); + painter->setPen( extraPen ); + if( extraLinesAlign & ( Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter ) ) + painter->drawLine( pL, pR ); + if( extraLinesAlign & ( Qt::AlignTop | Qt::AlignBottom | Qt::AlignVCenter ) ) + painter->drawLine( pT, pB ); + painter->setPen( oldPen ); + // draw the marker(s) of the extra line(s) + propSet.hasOwnExtraMarkersAlign( iDummy, extraMarkersAlign ); + if( extraMarkersAlign + & ( Qt::AlignLeft | Qt::AlignRight | + Qt::AlignTop | Qt::AlignBottom ) ){ + QSize extraMarkersSize = params()->lineMarkerSize(); + QColor extraMarkersColor = extraLinesColor; + int extraMarkersStyle = defaultMarkerStyle; + propSet.hasOwnExtraMarkersSize( iDummy, extraMarkersSize ); + propSet.hasOwnExtraMarkersColor( iDummy, extraMarkersColor ); + propSet.hasOwnExtraMarkersStyle( iDummy, extraMarkersStyle ); + // draw the extra marker(s) + int w = extraMarkersSize.width(); + int h = extraMarkersSize.height(); + if( w < 0 ) + w = static_cast < int > (w * -areaSizeP1000); + if( h < 0 ) + h = static_cast < int > (h * -areaSizeP1000); + if( extraMarkersAlign & Qt::AlignLeft ) + drawMarker( painter, + params(), + _areaWidthP1000, _areaHeightP1000, + _dataRect.x(), _dataRect.y(), + (KDChartParams::LineMarkerStyle)extraMarkersStyle, + extraMarkersColor, + pL, + 0, 0, 0, 0, + &w, &h, + Qt::AlignCenter ); + if( extraMarkersAlign & Qt::AlignRight ) + drawMarker( painter, + params(), + _areaWidthP1000, _areaHeightP1000, + _dataRect.x(), _dataRect.y(), + (KDChartParams::LineMarkerStyle)extraMarkersStyle, + extraMarkersColor, + pR, + 0, 0, 0, 0, + &w, &h, + Qt::AlignCenter ); + if( extraMarkersAlign & Qt::AlignTop ) + drawMarker( painter, + params(), + _areaWidthP1000, _areaHeightP1000, + _dataRect.x(), _dataRect.y(), + (KDChartParams::LineMarkerStyle)extraMarkersStyle, + extraMarkersColor, + pT, + 0, 0, 0, 0, + &w, &h, + Qt::AlignCenter ); + if( extraMarkersAlign & Qt::AlignBottom ) + drawMarker( painter, + params(), + _areaWidthP1000, _areaHeightP1000, + _dataRect.x(), _dataRect.y(), + (KDChartParams::LineMarkerStyle)extraMarkersStyle, + extraMarkersColor, + pB, + 0, 0, 0, 0, + &w, &h, + Qt::AlignCenter ); + } + } + } +} + + -- cgit v1.2.1