summaryrefslogtreecommitdiffstats
path: root/libkdchart/KDChartPainter.cpp
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2011-07-04 22:38:03 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2011-07-04 22:38:03 +0000
commitdadc34655c3ab961b0b0b94a10eaaba710f0b5e8 (patch)
tree99e72842fe687baea16376a147619b6048d7e441 /libkdchart/KDChartPainter.cpp
downloadkmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.tar.gz
kmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.zip
Added kmymoney
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kmymoney@1239792 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'libkdchart/KDChartPainter.cpp')
-rw-r--r--libkdchart/KDChartPainter.cpp2984
1 files changed, 2984 insertions, 0 deletions
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 <KDChartParams.h>
+#if defined ( SUN7 ) || defined (_SGIAPI) || defined ( Q_WS_WIN)
+ #include <math.h>
+#else
+ #include <cmath>
+ #include <stdlib.h>
+#endif
+
+#include <KDDrawText.h>
+#include <KDChartPainter.h>
+#include <KDChartEnums.h>
+#include <KDChartParams.h>
+#include <KDChartCustomBox.h>
+#include <KDChartTableBase.h>
+#include <KDChartDataRegion.h>
+#include <KDChartUnknownTypeException.h>
+#include <KDChartNotEnoughSpaceException.h>
+#include <KDChartBarPainter.h>
+#include <KDChartAreaPainter.h>
+#include <KDChartLinesPainter.h>
+#include <KDChartPiePainter.h>
+#include <KDChartPolarPainter.h>
+#include <KDChartRingPainter.h>
+#include <KDChartHiLoPainter.h>
+#include <KDChartBWPainter.h>
+#include <KDChartTextPiece.h>
+
+#include <KDChart.h> // for static method KDChart::painterToDrawRect()
+
+#include <qpainter.h>
+#include <qpaintdevice.h>
+#include <qpaintdevicemetrics.h>
+
+#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<int>(posX) + rect.center().x(),
+ static_cast<int>(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<int>(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 <int>((txtMetrics.height() - (int)(txtMetrics.height() * 0.1))*0.85);
+
+ //int legHeight = static_cast <int> (_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<uint>(_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<int>(areaWidthP1000 * extraLinesLength) / 2
+ : extraLinesLength / 2;
+ const int vertLenP2 = (0 > extraLinesLength)
+ ? static_cast<int>(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 );
+ }
+ }
+ }
+}
+
+