File indexing completed on 2024-12-15 04:02:27
0001 /* 0002 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. 0003 * 0004 * This file is part of the KD Chart library. 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "KChartPercentPlotter_p.h" 0010 #include "KChartPlotter.h" 0011 #include "PaintingHelpers_p.h" 0012 0013 #include <limits> 0014 0015 using namespace KChart; 0016 using namespace std; 0017 0018 PercentPlotter::PercentPlotter( Plotter* d ) 0019 : PlotterType( d ) 0020 { 0021 } 0022 0023 Plotter::PlotType PercentPlotter::type() const 0024 { 0025 return Plotter::Percent; 0026 } 0027 0028 const QPair< QPointF, QPointF > PercentPlotter::calculateDataBoundaries() const 0029 { 0030 const int rowCount = compressor().modelDataRows(); 0031 const int colCount = compressor().modelDataColumns(); 0032 qreal xMin = std::numeric_limits< qreal >::quiet_NaN(); 0033 qreal xMax = std::numeric_limits< qreal >::quiet_NaN(); 0034 const qreal yMin = 0.0; 0035 const qreal yMax = 100.0; 0036 0037 for ( int column = 0; column < colCount; ++column ) 0038 { 0039 for ( int row = 0; row < rowCount; ++row ) 0040 { 0041 const CartesianDiagramDataCompressor::CachePosition position( row, column ); 0042 const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); 0043 0044 const qreal valueX = ISNAN( point.key ) ? 0.0 : point.key; 0045 0046 if ( ISNAN( xMin ) ) 0047 { 0048 xMin = valueX; 0049 xMax = valueX; 0050 } 0051 else 0052 { 0053 xMin = qMin( xMin, valueX ); 0054 xMax = qMax( xMax, valueX ); 0055 } 0056 } 0057 } 0058 0059 const QPointF bottomLeft( QPointF( xMin, yMin ) ); 0060 const QPointF topRight( QPointF( xMax, yMax ) ); 0061 return QPair< QPointF, QPointF >( bottomLeft, topRight ); 0062 } 0063 0064 class Value 0065 { 0066 public: 0067 Value() 0068 : value( std::numeric_limits< qreal >::quiet_NaN() ) 0069 { 0070 } 0071 // allow implicit conversion 0072 Value( qreal value ) 0073 : value( value ) 0074 { 0075 } 0076 operator qreal() const 0077 { 0078 return value; 0079 } 0080 0081 private: 0082 qreal value; 0083 }; 0084 0085 void PercentPlotter::paint( PaintContext* ctx ) 0086 { 0087 reverseMapper().clear(); 0088 0089 Q_ASSERT( dynamic_cast< CartesianCoordinatePlane* >( ctx->coordinatePlane() ) ); 0090 const CartesianCoordinatePlane* const plane = static_cast< CartesianCoordinatePlane* >( ctx->coordinatePlane() ); 0091 const int colCount = compressor().modelDataColumns(); 0092 const int rowCount = compressor().modelDataRows(); 0093 0094 if ( colCount == 0 || rowCount == 0 ) 0095 return; 0096 0097 LabelPaintCache lpc; 0098 0099 // this map contains the y-values to each x-value 0100 QMap< qreal, QVector< QPair< Value, QModelIndex > > > diagramValues; 0101 0102 for ( int col = 0; col < colCount; ++col ) 0103 { 0104 for ( int row = 0; row < rowCount; ++row ) 0105 { 0106 const CartesianDiagramDataCompressor::CachePosition position( row, col ); 0107 const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position ); 0108 diagramValues[ point.key ].resize( colCount ); 0109 diagramValues[ point.key ][ col ].first = point.value; 0110 diagramValues[ point.key ][ col ].second = point.index; 0111 } 0112 } 0113 0114 // the sums of the y-values per x-value 0115 QMap< qreal, qreal > yValueSums; 0116 // the x-values 0117 QList< qreal > xValues = diagramValues.keys(); 0118 // make sure it's sorted 0119 std::sort(xValues.begin(), xValues.end()); 0120 for ( const qreal xValue : qAsConst(xValues) ) 0121 { 0122 // the y-values to the current x-value 0123 QVector< QPair< Value, QModelIndex > >& yValues = diagramValues[ xValue ]; 0124 Q_ASSERT( yValues.count() == colCount ); 0125 0126 for ( int column = 0; column < colCount; ++column ) 0127 { 0128 QPair< Value, QModelIndex >& data = yValues[ column ]; 0129 // if the index is invalid, there was no value. Let's interpolate. 0130 if ( !data.second.isValid() ) 0131 { 0132 QPair< QPair< qreal, Value >, QModelIndex > left; 0133 QPair< QPair< qreal, Value >, QModelIndex > right; 0134 int xIndex = 0; 0135 // let's find the next lower value 0136 for ( xIndex = xValues.indexOf( xValue ); xIndex >= 0; --xIndex ) 0137 { 0138 if ( diagramValues[ xValues[ xIndex ] ][ column ].second.isValid() ) 0139 { 0140 left.first.first = xValues[ xIndex ]; 0141 left.first.second = diagramValues[ left.first.first ][ column ].first; 0142 left.second = diagramValues[ xValues[ xIndex ] ][ column ].second; 0143 break; 0144 } 0145 } 0146 // let's find the next higher value 0147 for ( xIndex = xValues.indexOf( xValue ); xIndex < xValues.count(); ++xIndex ) 0148 { 0149 if ( diagramValues[ xValues[ xIndex ] ][ column ].second.isValid() ) 0150 { 0151 right.first.first = xValues[ xIndex ]; 0152 right.first.second = diagramValues[ right.first.first ][ column ].first; 0153 right.second = diagramValues[ xValues[ xIndex ] ][ column ].second; 0154 break; 0155 } 0156 } 0157 0158 // interpolate out of them (left and/or right might be invalid, but this doesn't matter here) 0159 const qreal leftX = left.first.first; 0160 const qreal rightX = right.first.first; 0161 const qreal leftY = left.first.second; 0162 const qreal rightY = right.first.second; 0163 0164 data.first = leftY + ( rightY - leftY ) * ( xValue - leftX ) / ( rightX - leftX ); 0165 // if the result is a valid value, let's assign the index, too 0166 if ( !ISNAN( data.first.operator qreal() ) ) 0167 data.second = left.second; 0168 } 0169 0170 // sum it up 0171 if ( !ISNAN( yValues[ column ].first.operator qreal() ) ) 0172 yValueSums[ xValue ] += yValues[ column ].first; 0173 } 0174 } 0175 0176 for ( int column = 0; column < colCount; ++column ) 0177 { 0178 LineAttributesInfoList lineList; 0179 LineAttributes laPreviousCell; 0180 CartesianDiagramDataCompressor::CachePosition previousCellPosition; 0181 0182 CartesianDiagramDataCompressor::DataPoint lastPoint; 0183 0184 qreal lastExtraY = 0.0; 0185 qreal lastValue = 0.0; 0186 0187 QMapIterator< qreal, QVector< QPair< Value, QModelIndex > > > i( diagramValues ); 0188 while ( i.hasNext() ) 0189 { 0190 i.next(); 0191 CartesianDiagramDataCompressor::DataPoint point; 0192 point.key = i.key(); 0193 const QPair< Value, QModelIndex >& data = i.value().at( column ); 0194 point.value = data.first; 0195 point.index = data.second; 0196 0197 if ( ISNAN( point.key ) || ISNAN( point.value ) ) 0198 { 0199 previousCellPosition = CartesianDiagramDataCompressor::CachePosition(); 0200 continue; 0201 } 0202 0203 qreal extraY = 0.0; 0204 for ( int col = column - 1; col >= 0; --col ) 0205 { 0206 const qreal y = i.value().at( col ).first; 0207 if ( !ISNAN( y ) ) 0208 extraY += y; 0209 } 0210 0211 LineAttributes laCell; 0212 0213 const qreal value = ( point.value + extraY ) / yValueSums[ i.key() ] * 100; 0214 0215 const QModelIndex sourceIndex = attributesModel()->mapToSource( point.index ); 0216 // area corners, a + b are the line ends: 0217 const QPointF a( plane->translate( QPointF( lastPoint.key, lastValue ) ) ); 0218 const QPointF b( plane->translate( QPointF( point.key, value ) ) ); 0219 const QPointF c( plane->translate( QPointF( lastPoint.key, lastExtraY / yValueSums[ i.key() ] * 100 ) ) ); 0220 const QPointF d( plane->translate( QPointF( point.key, extraY / yValueSums[ i.key() ] * 100 ) ) ); 0221 // add the line to the list: 0222 laCell = diagram()->lineAttributes( sourceIndex ); 0223 // add data point labels: 0224 const PositionPoints pts = PositionPoints( b, a, d, c ); 0225 // if necessary, add the area to the area list: 0226 QList<QPolygonF> areas; 0227 if ( laCell.displayArea() ) { 0228 QPolygonF polygon; 0229 polygon << a << b << d << c; 0230 areas << polygon; 0231 } 0232 // add the pieces to painting if this is not hidden: 0233 if ( !point.hidden /*&& !ISNAN( lastPoint.key ) && !ISNAN( lastPoint.value ) */) { 0234 m_private->addLabel( &lpc, sourceIndex, nullptr, pts, Position::NorthWest, 0235 Position::NorthWest, value ); 0236 if ( !ISNAN( lastPoint.key ) && !ISNAN( lastPoint.value ) ) 0237 { 0238 PaintingHelpers::paintAreas( m_private, ctx, 0239 attributesModel()->mapToSource( lastPoint.index ), 0240 areas, laCell.transparency() ); 0241 lineList.append( LineAttributesInfo( sourceIndex, a, b ) ); 0242 } 0243 } 0244 0245 // wrap it up: 0246 laPreviousCell = laCell; 0247 lastPoint = point; 0248 lastExtraY = extraY; 0249 lastValue = value; 0250 } 0251 PaintingHelpers::paintElements( m_private, ctx, lpc, lineList ); 0252 } 0253 }