File indexing completed on 2024-12-15 04:02:27

0001 /* -*- Mode: C++ -*-
0002    KChart - a multi-platform charting engine
0003    */
0004 
0005 /****************************************************************************
0006  ** SPDX-FileCopyrightText: 2005-2007 Klarälvdalens Datakonsult AB. All rights reserved.
0007  **
0008  ** This file is part of the KD Chart library.
0009  **
0010  ** This file may be used under the terms of the GNU General Public
0011  ** License versions 2.0 or 3.0 as published by the Free Software
0012  ** Foundation and appearing in the files LICENSE.GPL2 and LICENSE.GPL3
0013  ** included in the packaging of this file.  Alternatively you may (at
0014  ** your option) use any later version of the GNU General Public
0015  ** License if such license has been publicly approved by
0016  ** Klarälvdalens Datakonsult AB (or its successors, if any).
0017  **
0018  ** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
0019  ** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
0020  ** A PARTICULAR PURPOSE. Klarälvdalens Datakonsult AB reserves all rights
0021  ** not expressly granted herein.
0022  **
0023  ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
0024  ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
0025  **
0026  **********************************************************************/
0027 
0028 #include "KChartStackedPlotter_p.h"
0029 #include "KChartPlotter.h"
0030 #include "PaintingHelpers_p.h"
0031 
0032 #include <limits>
0033 
0034 using namespace KChart;
0035 using namespace std;
0036 
0037 StackedPlotter::StackedPlotter( Plotter* d )
0038     : PlotterType( d )
0039 {
0040 }
0041 
0042 Plotter::PlotType StackedPlotter::type() const
0043 {
0044     return Plotter::Stacked;
0045 }
0046 
0047 const QPair< QPointF, QPointF > StackedPlotter::calculateDataBoundaries() const
0048 {
0049     const int rowCount = compressor().modelDataRows();
0050     const int colCount = compressor().modelDataColumns();
0051     double xMin = 0, xMax = 0;
0052     double yMin = 0, yMax = 0;
0053 
0054     bool bStarting = true;
0055     for( int row = 0; row < rowCount; ++row )
0056     {
0057         // calculate sum of values per column - Find out stacked Min/Max
0058         double stackedValues = 0.0;
0059         double negativeStackedValues = 0.0;
0060         for( int col = 0; col < colCount; ++col ) {
0061             const CartesianDiagramDataCompressor::CachePosition position( row, col );
0062             const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
0063 
0064             if( ISNAN( point.value ) )
0065                 continue;
0066 
0067             if( point.value >= 0.0 )
0068                 stackedValues += point.value;
0069             else
0070                 negativeStackedValues += point.value;
0071         }
0072 
0073         //assume that each value in this row has the same x-value
0074         //very unintuitive...
0075         const CartesianDiagramDataCompressor::CachePosition xPosition( row, 0 );
0076         const CartesianDiagramDataCompressor::DataPoint xPoint = compressor().data( xPosition );
0077         if( bStarting ){
0078             yMin = stackedValues;
0079             yMax = stackedValues;
0080             xMax = xPoint.key;
0081             xMin = xPoint.key;
0082             bStarting = false;
0083         }else{
0084             // take in account all stacked values
0085             yMin = qMin( qMin( yMin, negativeStackedValues ), stackedValues );
0086             yMax = qMax( qMax( yMax, negativeStackedValues ), stackedValues );
0087             xMin = qMin( qreal(xMin), xPoint.key );
0088             xMax = qMax( qreal(xMax), xPoint.key );
0089         }
0090     }
0091 
0092     const QPointF bottomLeft( xMin, yMin );
0093     QPointF topRight( xMax, yMax );
0094 
0095     // prevent degeneration of plot by assigning some minimal size
0096     if (qFuzzyCompare(bottomLeft.x(), topRight.x())) {
0097         topRight.setX(topRight.x() + 10.f);
0098     }
0099     if (qFuzzyCompare(bottomLeft.y(), topRight.y())) {
0100         topRight.setY(topRight.y() + 10.f);
0101     }
0102 
0103     return QPair<QPointF, QPointF> ( bottomLeft, topRight );
0104 }
0105 
0106 void StackedPlotter::paint( PaintContext* ctx )
0107 {
0108     reverseMapper().clear();
0109 
0110 //     const QPair<QPointF, QPointF> boundaries = diagram()->dataBoundaries();
0111 //     const QPointF bottomLeft = boundaries.first;
0112 //     const QPointF topRight = boundaries.second;
0113 
0114     const int columnCount = compressor().modelDataColumns();
0115     const int rowCount = compressor().modelDataRows();
0116 
0117 // FIXME integrate column index retrieval to compressor:
0118 //     int maxFound = 0;
0119 //    {   // find the last column number that is not hidden
0120 //        for( int iColumn =  /*datasetDimension()*/2 - 1;
0121 //             iColumn <  columnCount;
0122 //             iColumn += /*datasetDimension()*/2 )
0123 //            if( ! diagram()->isHidden( iColumn ) )
0124 //                maxFound = iColumn;
0125 //    }
0126 //     maxFound = columnCount;
0127     // ^^^ temp
0128 
0129     LabelPaintCache lpc;
0130     LineAttributesInfoList lineList;
0131 
0132     //FIXME(khz): add LineAttributes::MissingValuesPolicy support for LineDiagram::Stacked and ::Percent
0133 
0134     QList<QPointF> bottomPoints;
0135     bool bFirstDataset = true;
0136 
0137     for( int column = 0; column < columnCount; ++column )
0138     {
0139         CartesianDiagramDataCompressor::CachePosition previousCellPosition;
0140 
0141         //display area can be set by dataset ( == column) and/or by cell
0142         QList<QPointF> points;
0143 
0144         for ( int row = 0; row < rowCount; ++row ) {
0145             const CartesianDiagramDataCompressor::CachePosition position( row, column );
0146             CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
0147             const QModelIndex sourceIndex = attributesModel()->mapToSource( point.index );
0148 
0149             const LineAttributes laCell = diagram()->lineAttributes( sourceIndex );
0150             const bool bDisplayCellArea = laCell.displayArea();
0151 
0152             const LineAttributes::MissingValuesPolicy policy = laCell.missingValuesPolicy();
0153 
0154             if( ISNAN( point.value ) && policy == LineAttributes::MissingValuesShownAsZero )
0155                 point.value = 0.0;
0156 
0157             double stackedValues = 0, nextValues = 0, nextKey = 0;
0158             for ( int column2 = column; column2 >= 0; --column2 )
0159             {
0160                 const CartesianDiagramDataCompressor::CachePosition position( row, column2 );
0161                 const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
0162                 if( !ISNAN( point.value ) )
0163                 {
0164                     stackedValues += point.value;
0165                 }
0166                 else if( policy == LineAttributes::MissingValuesAreBridged )
0167                 {
0168                     const double interpolation = interpolateMissingValue( position );
0169                     if( !ISNAN( interpolation ) )
0170                         stackedValues += interpolation;
0171                 }
0172 
0173                 //qDebug() << valueForCell( iRow, iColumn2 );
0174                 if ( row + 1 < rowCount ){
0175                     const CartesianDiagramDataCompressor::CachePosition position( row + 1, column2 );
0176                     const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
0177                     if( !ISNAN( point.value ) )
0178                     {
0179                         nextValues += point.value;
0180                     }
0181                     else if( policy == LineAttributes::MissingValuesAreBridged )
0182                     {
0183                         const double interpolation = interpolateMissingValue( position );
0184                         if( !ISNAN( interpolation ) )
0185                             nextValues += interpolation;
0186                     }
0187                     nextKey = point.key;
0188                 }
0189             }
0190             //qDebug() << stackedValues << endl;
0191             const QPointF nextPoint = ctx->coordinatePlane()->translate( QPointF( point.key, stackedValues ) );
0192             points << nextPoint;
0193 
0194             const QPointF ptNorthWest( nextPoint );
0195             const QPointF ptSouthWest(
0196                 bDisplayCellArea
0197                 ? ( bFirstDataset
0198                     ? ctx->coordinatePlane()->translate( QPointF( point.key, 0.0 ) )
0199                     : bottomPoints.at( row )
0200                     )
0201                 : nextPoint );
0202             QPointF ptNorthEast;
0203             QPointF ptSouthEast;
0204 
0205             if ( row + 1 < rowCount ){
0206                 QPointF toPoint = ctx->coordinatePlane()->translate( QPointF( nextKey, nextValues ) );
0207                 lineList.append( LineAttributesInfo( sourceIndex, nextPoint, toPoint ) );
0208                 ptNorthEast = toPoint;
0209                 ptSouthEast =
0210                     bDisplayCellArea
0211                     ? ( bFirstDataset
0212                         ? ctx->coordinatePlane()->translate( QPointF( nextKey, 0.0 ) )
0213                         : bottomPoints.at( row + 1 )
0214                         )
0215                     : toPoint;
0216                 if( bDisplayCellArea ){
0217                     QPolygonF poly;
0218                     poly << ptNorthWest << ptNorthEast << ptSouthEast << ptSouthWest;
0219                     QList<QPolygonF> areas;
0220                     areas << poly;
0221                     PaintingHelpers::paintAreas( m_private, ctx, sourceIndex,
0222                                                  areas, laCell.transparency() );
0223                 }else{
0224                     //qDebug() << "no area shown for row"<<iRow<<"  column"<<iColumn;
0225                 }
0226             }else{
0227                 ptNorthEast = ptNorthWest;
0228                 ptSouthEast = ptSouthWest;
0229             }
0230 
0231             if( !ISNAN( point.value ) ) {
0232                 const PositionPoints pts( ptNorthWest, ptNorthEast, ptSouthEast, ptSouthWest );
0233                 m_private->addLabel( &lpc, sourceIndex, &position, pts, Position::NorthWest,
0234                                      Position::NorthWest, point.value );
0235             }
0236         }
0237         bottomPoints = points;
0238         bFirstDataset = false;
0239     }
0240 
0241     PaintingHelpers::paintElements( m_private, ctx, lpc, lineList );
0242 }
0243 
0244 double StackedPlotter::interpolateMissingValue( const CartesianDiagramDataCompressor::CachePosition& pos ) const
0245 {
0246     double leftValue = std::numeric_limits< double >::quiet_NaN();
0247     double rightValue = std::numeric_limits< double >::quiet_NaN();
0248     int missingCount = 1;
0249 
0250     const int column = pos.column;
0251     const int row = pos.row;
0252     const int rowCount = compressor().modelDataRows();
0253 
0254     // iterate back and forth to find valid values
0255     for( int r1 = row - 1; r1 > 0; --r1 )
0256     {
0257         const CartesianDiagramDataCompressor::CachePosition position( r1, column );
0258         const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
0259         leftValue = point.value;
0260         if( !ISNAN( point.value ) )
0261             break;
0262         ++missingCount;
0263     }
0264     for( int r2 = row + 1; r2 < rowCount; ++r2 )
0265     {
0266         const CartesianDiagramDataCompressor::CachePosition position( r2, column );
0267         const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
0268         rightValue = point.value;
0269         if( !ISNAN( point.value ) )
0270             break;
0271         ++missingCount;
0272     }
0273     if( !ISNAN( leftValue ) && !ISNAN( rightValue ) )
0274         return leftValue + ( rightValue - leftValue ) / ( missingCount + 1 );
0275     else
0276         return std::numeric_limits< double >::quiet_NaN();
0277 }