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 }