KD Chart 2 [rev.2.4]
|
00001 /**************************************************************************** 00002 ** Copyright (C) 2001-2012 Klaralvdalens Datakonsult AB. All rights reserved. 00003 ** 00004 ** This file is part of the KD Chart library. 00005 ** 00006 ** Licensees holding valid commercial KD Chart licenses may use this file in 00007 ** accordance with the KD Chart Commercial License Agreement provided with 00008 ** the Software. 00009 ** 00010 ** 00011 ** This file may be distributed and/or modified under the terms of the 00012 ** GNU General Public License version 2 and version 3 as published by the 00013 ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included. 00014 ** 00015 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE 00016 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 00017 ** 00018 ** Contact info@kdab.com if any conditions of this licensing are not 00019 ** clear to you. 00020 ** 00021 **********************************************************************/ 00022 00023 #include "KDChartPercentPlotter_p.h" 00024 #include "KDChartPlotter.h" 00025 00026 #include <limits> 00027 00028 using namespace KDChart; 00029 using namespace std; 00030 00031 PercentPlotter::PercentPlotter( Plotter* d ) 00032 : PlotterType( d ) 00033 { 00034 } 00035 00036 Plotter::PlotType PercentPlotter::type() const 00037 { 00038 return Plotter::Percent; 00039 } 00040 00041 const QPair< QPointF, QPointF > PercentPlotter::calculateDataBoundaries() const 00042 { 00043 const int rowCount = compressor().modelDataRows(); 00044 const int colCount = compressor().modelDataColumns(); 00045 double xMin = std::numeric_limits< double >::quiet_NaN(); 00046 double xMax = std::numeric_limits< double >::quiet_NaN(); 00047 const double yMin = 0.0; 00048 const double yMax = 100.0; 00049 00050 for( int column = 0; column < colCount; ++column ) 00051 { 00052 for ( int row = 0; row < rowCount; ++row ) 00053 { 00054 const CartesianDiagramDataCompressor::CachePosition position( row, column ); 00055 const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); 00056 00057 const double valueX = ISNAN( point.key ) ? 0.0 : point.key; 00058 00059 if( ISNAN( xMin ) ) 00060 { 00061 xMin = valueX; 00062 xMax = valueX; 00063 } 00064 else 00065 { 00066 xMin = qMin( xMin, valueX ); 00067 xMax = qMax( xMax, valueX ); 00068 } 00069 } 00070 } 00071 00072 // NOTE: calculateDataBoundaries must return the *real* data boundaries! 00073 // i.e. we may NOT fake yMin to be qMin( 0.0, yMin ) 00074 // (khz, 2008-01-24) 00075 const QPointF bottomLeft( QPointF( xMin, yMin ) ); 00076 const QPointF topRight( QPointF( xMax, yMax ) ); 00077 return QPair< QPointF, QPointF >( bottomLeft, topRight ); 00078 } 00079 00080 class Value 00081 { 00082 public: 00083 Value() 00084 : value( std::numeric_limits< double >::quiet_NaN() ) 00085 { 00086 } 00087 // allow implicit conversion 00088 Value( double value ) 00089 : value( value ) 00090 { 00091 } 00092 operator double() const 00093 { 00094 return value; 00095 } 00096 00097 private: 00098 double value; 00099 }; 00100 00101 void PercentPlotter::paint( PaintContext* ctx ) 00102 { 00103 reverseMapper().clear(); 00104 00105 Q_ASSERT( dynamic_cast< CartesianCoordinatePlane* >( ctx->coordinatePlane() ) ); 00106 const CartesianCoordinatePlane* const plane = static_cast< CartesianCoordinatePlane* >( ctx->coordinatePlane() ); 00107 const int colCount = compressor().modelDataColumns(); 00108 const int rowCount = compressor().modelDataRows(); 00109 00110 if( colCount == 0 || rowCount == 0 ) 00111 return; 00112 00113 DataValueTextInfoList textInfoList; 00114 LineAttributes::MissingValuesPolicy policy = LineAttributes::MissingValuesAreBridged; // ??? 00115 00116 // this map contains the y-values to each x-value 00117 QMap< double, QVector< QPair< Value, QModelIndex > > > diagramValues; 00118 00119 for( int col = 0; col < colCount; ++col ) 00120 { 00121 for( int row = 0; row < rowCount; ++row ) 00122 { 00123 const CartesianDiagramDataCompressor::CachePosition position( row, col ); 00124 const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); 00125 diagramValues[ point.key ].resize( colCount ); 00126 diagramValues[ point.key ][ col ].first = point.value; 00127 diagramValues[ point.key ][ col ].second = point.index; 00128 } 00129 } 00130 00131 // the sums of the y-values per x-value 00132 QMap< double, double > yValueSums; 00133 // the x-values 00134 QList< double > xValues = diagramValues.keys(); 00135 // make sure it's sorted 00136 qSort( xValues ); 00137 Q_FOREACH( const double xValue, xValues ) 00138 { 00139 // the y-values to the current x-value 00140 QVector< QPair< Value, QModelIndex > >& yValues = diagramValues[ xValue ]; 00141 Q_ASSERT( yValues.count() == colCount ); 00142 00143 for( int column = 0; column < colCount; ++column ) 00144 { 00145 QPair< Value, QModelIndex >& data = yValues[ column ]; 00146 // if the index is invalid, there was no value. Let's interpolate. 00147 if( !data.second.isValid() ) 00148 { 00149 QPair< QPair< double, Value >, QModelIndex > left; 00150 QPair< QPair< double, Value >, QModelIndex > right; 00151 int xIndex = 0; 00152 // let's find the next lower value 00153 for( xIndex = xValues.indexOf( xValue ); xIndex >= 0; --xIndex ) 00154 { 00155 if( diagramValues[ xValues[ xIndex ] ][ column ].second.isValid() ) 00156 { 00157 left.first.first = xValues[ xIndex ]; 00158 left.first.second = diagramValues[ left.first.first ][ column ].first; 00159 left.second = diagramValues[ xValues[ xIndex ] ][ column ].second; 00160 break; 00161 } 00162 } 00163 // let's find the next higher value 00164 for( xIndex = xValues.indexOf( xValue ); xIndex < xValues.count(); ++xIndex ) 00165 { 00166 if( diagramValues[ xValues[ xIndex ] ][ column ].second.isValid() ) 00167 { 00168 right.first.first = xValues[ xIndex ]; 00169 right.first.second = diagramValues[ right.first.first ][ column ].first; 00170 right.second = diagramValues[ xValues[ xIndex ] ][ column ].second; 00171 break; 00172 } 00173 } 00174 00175 // interpolate out of them (left and/or right might be invalid, but this doesn't matter here) 00176 const double leftX = left.first.first; 00177 const double rightX = right.first.first; 00178 const double leftY = left.first.second; 00179 const double rightY = right.first.second; 00180 00181 data.first = leftY + ( rightY - leftY ) * ( xValue - leftX ) / ( rightX - leftX ); 00182 // if the result is a valid value, let's assign the index, too 00183 if( !ISNAN( data.first.operator double() ) ) 00184 data.second = left.second; 00185 } 00186 00187 // sum it up 00188 if( !ISNAN( yValues[ column ].first.operator double() ) ) 00189 yValueSums[ xValue ] += yValues[ column ].first; 00190 } 00191 } 00192 00193 for( int column = 0; column < colCount; ++column ) 00194 { 00195 LineAttributesInfoList lineList; 00196 LineAttributes laPreviousCell; 00197 CartesianDiagramDataCompressor::CachePosition previousCellPosition; 00198 00199 CartesianDiagramDataCompressor::DataPoint lastPoint; 00200 00201 qreal lastExtraY = 0.0; 00202 qreal lastValue = 0.0; 00203 00204 QMapIterator< double, QVector< QPair< Value, QModelIndex > > > i( diagramValues ); 00205 while( i.hasNext() ) 00206 { 00207 i.next(); 00208 CartesianDiagramDataCompressor::DataPoint point; 00209 point.key = i.key(); 00210 const QPair< Value, QModelIndex >& data = i.value().at( column ); 00211 point.value = data.first; 00212 point.index = data.second; 00213 00214 if( ISNAN( point.key ) || ISNAN( point.value ) ) 00215 { 00216 previousCellPosition = CartesianDiagramDataCompressor::CachePosition(); 00217 continue; 00218 } 00219 00220 double extraY = 0.0; 00221 for( int col = column - 1; col >= 0; --col ) 00222 { 00223 const double y = i.value().at( col ).first; 00224 if( !ISNAN( y ) ) 00225 extraY += y; 00226 } 00227 00228 LineAttributes laCell; 00229 00230 const qreal value = ( point.value + extraY ) / yValueSums[ i.key() ] * 100; 00231 00232 const QModelIndex sourceIndex = attributesModel()->mapToSource( point.index ); 00233 // area corners, a + b are the line ends: 00234 const QPointF a( plane->translate( QPointF( lastPoint.key, lastValue ) ) ); 00235 const QPointF b( plane->translate( QPointF( point.key, value ) ) ); 00236 const QPointF c( plane->translate( QPointF( lastPoint.key, lastExtraY / yValueSums[ i.key() ] * 100 ) ) ); 00237 const QPointF d( plane->translate( QPointF( point.key, extraY / yValueSums[ i.key() ] * 100 ) ) ); 00238 // add the line to the list: 00239 laCell = diagram()->lineAttributes( sourceIndex ); 00240 // add data point labels: 00241 const PositionPoints pts = PositionPoints( b, a, d, c ); 00242 // if necessary, add the area to the area list: 00243 QList<QPolygonF> areas; 00244 if ( laCell.displayArea() ) { 00245 QPolygonF polygon; 00246 polygon << a << b << d << c; 00247 areas << polygon; 00248 } 00249 // add the pieces to painting if this is not hidden: 00250 if ( !point.hidden /*&& !ISNAN( lastPoint.key ) && !ISNAN( lastPoint.value ) */) { 00251 appendDataValueTextInfoToList( diagram(), textInfoList, sourceIndex, pts, 00252 Position::NorthWest, Position::SouthWest, 00253 value ); 00254 if( !ISNAN( lastPoint.key ) && !ISNAN( lastPoint.value ) ) 00255 { 00256 paintAreas( ctx, attributesModel()->mapToSource( lastPoint.index ), areas, laCell.transparency() ); 00257 lineList.append( LineAttributesInfo( sourceIndex, a, b ) ); 00258 } 00259 } 00260 00261 // wrap it up: 00262 laPreviousCell = laCell; 00263 lastPoint = point; 00264 lastExtraY = extraY; 00265 lastValue = value; 00266 } 00267 paintElements( ctx, textInfoList, lineList, policy ); 00268 } 00269 }