KD Chart 2
[rev.2.5]
|
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 #include "PaintingHelpers_p.h" 00026 00027 #include <limits> 00028 00029 using namespace KDChart; 00030 using namespace std; 00031 00032 PercentPlotter::PercentPlotter( Plotter* d ) 00033 : PlotterType( d ) 00034 { 00035 } 00036 00037 Plotter::PlotType PercentPlotter::type() const 00038 { 00039 return Plotter::Percent; 00040 } 00041 00042 const QPair< QPointF, QPointF > PercentPlotter::calculateDataBoundaries() const 00043 { 00044 const int rowCount = compressor().modelDataRows(); 00045 const int colCount = compressor().modelDataColumns(); 00046 qreal xMin = std::numeric_limits< qreal >::quiet_NaN(); 00047 qreal xMax = std::numeric_limits< qreal >::quiet_NaN(); 00048 const qreal yMin = 0.0; 00049 const qreal yMax = 100.0; 00050 00051 for( int column = 0; column < colCount; ++column ) 00052 { 00053 for ( int row = 0; row < rowCount; ++row ) 00054 { 00055 const CartesianDiagramDataCompressor::CachePosition position( row, column ); 00056 const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); 00057 00058 const qreal valueX = ISNAN( point.key ) ? 0.0 : point.key; 00059 00060 if( ISNAN( xMin ) ) 00061 { 00062 xMin = valueX; 00063 xMax = valueX; 00064 } 00065 else 00066 { 00067 xMin = qMin( xMin, valueX ); 00068 xMax = qMax( xMax, valueX ); 00069 } 00070 } 00071 } 00072 00073 // NOTE: calculateDataBoundaries must return the *real* data boundaries! 00074 // i.e. we may NOT fake yMin to be qMin( 0.0, yMin ) 00075 // (khz, 2008-01-24) 00076 const QPointF bottomLeft( QPointF( xMin, yMin ) ); 00077 const QPointF topRight( QPointF( xMax, yMax ) ); 00078 return QPair< QPointF, QPointF >( bottomLeft, topRight ); 00079 } 00080 00081 class Value 00082 { 00083 public: 00084 Value() 00085 : value( std::numeric_limits< qreal >::quiet_NaN() ) 00086 { 00087 } 00088 // allow implicit conversion 00089 Value( qreal value ) 00090 : value( value ) 00091 { 00092 } 00093 operator qreal() const 00094 { 00095 return value; 00096 } 00097 00098 private: 00099 qreal value; 00100 }; 00101 00102 void PercentPlotter::paint( PaintContext* ctx ) 00103 { 00104 reverseMapper().clear(); 00105 00106 Q_ASSERT( dynamic_cast< CartesianCoordinatePlane* >( ctx->coordinatePlane() ) ); 00107 const CartesianCoordinatePlane* const plane = static_cast< CartesianCoordinatePlane* >( ctx->coordinatePlane() ); 00108 const int colCount = compressor().modelDataColumns(); 00109 const int rowCount = compressor().modelDataRows(); 00110 00111 if( colCount == 0 || rowCount == 0 ) 00112 return; 00113 00114 LabelPaintCache lpc; 00115 00116 // this map contains the y-values to each x-value 00117 QMap< qreal, 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< qreal, qreal > yValueSums; 00133 // the x-values 00134 QList< qreal > xValues = diagramValues.keys(); 00135 // make sure it's sorted 00136 qSort( xValues ); 00137 Q_FOREACH( const qreal 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< qreal, Value >, QModelIndex > left; 00150 QPair< QPair< qreal, 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 qreal leftX = left.first.first; 00177 const qreal rightX = right.first.first; 00178 const qreal leftY = left.first.second; 00179 const qreal 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 qreal() ) ) 00184 data.second = left.second; 00185 } 00186 00187 // sum it up 00188 if( !ISNAN( yValues[ column ].first.operator qreal() ) ) 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< qreal, 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 qreal extraY = 0.0; 00221 for( int col = column - 1; col >= 0; --col ) 00222 { 00223 const qreal 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 addLabel( &lpc, sourceIndex, pts, Position::NorthWest, 00252 Position::SouthWest, value ); 00253 if( !ISNAN( lastPoint.key ) && !ISNAN( lastPoint.value ) ) 00254 { 00255 PaintingHelpers::paintAreas( m_private, ctx, 00256 attributesModel()->mapToSource( lastPoint.index ), 00257 areas, laCell.transparency() ); 00258 lineList.append( LineAttributesInfo( sourceIndex, a, b ) ); 00259 } 00260 } 00261 00262 // wrap it up: 00263 laPreviousCell = laCell; 00264 lastPoint = point; 00265 lastExtraY = extraY; 00266 lastValue = value; 00267 } 00268 PaintingHelpers::paintElements( m_private, ctx, lpc, lineList ); 00269 } 00270 }