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 "KChartStackedBarDiagram_p.h"
0010 #include <QModelIndex>
0011 
0012 #include "KChartBarDiagram.h"
0013 #include "KChartTextAttributes.h"
0014 #include "KChartAttributesModel.h"
0015 #include "KChartAbstractCartesianDiagram.h"
0016 
0017 using namespace KChart;
0018 
0019 StackedBarDiagram::StackedBarDiagram( BarDiagram* d )
0020     : BarDiagramType( d )
0021 {
0022 }
0023 
0024 BarDiagram::BarType StackedBarDiagram::type() const
0025 {
0026     return BarDiagram::Stacked;
0027 }
0028 
0029 const QPair<QPointF, QPointF> StackedBarDiagram::calculateDataBoundaries() const
0030 {
0031     const int rowCount = compressor().modelDataRows();
0032     const int colCount = compressor().modelDataColumns();
0033 
0034     const qreal xMin = 0.0;
0035     const qreal xMax = rowCount;
0036     qreal yMin = 0.0;
0037     qreal yMax = 0.0;
0038 
0039     bool isFirst = true;
0040     for ( int row = 0; row < rowCount; ++row ) {
0041         // calculate sum of values per column - Find out stacked Min/Max
0042         qreal stackedValues = 0.0;
0043         qreal negativeStackedValues = 0.0;
0044         for ( int col = 0; col < colCount; ++col ) {
0045             const CartesianDiagramDataCompressor::CachePosition position( row, col );
0046             const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
0047             const double value = ISNAN( point.value ) ? 0.0 : point.value;
0048 
0049             if ( value > 0.0 ) {
0050                 stackedValues += value;
0051             } else {
0052                 negativeStackedValues += value;
0053             }
0054 
0055             // this is always true yMin can be 0 in case all values
0056             // are the same
0057             // same for yMax it can be zero if all values are negative
0058             if ( isFirst ) {
0059                 yMin = negativeStackedValues < 0.0 ? negativeStackedValues : stackedValues;
0060                 yMax = stackedValues > 0.0 ? stackedValues : negativeStackedValues;
0061                 isFirst = false;
0062             } else {
0063                 yMin = qMin( qMin( yMin, stackedValues ), negativeStackedValues );
0064                 yMax = qMax( qMax( yMax, stackedValues ), negativeStackedValues );
0065             }
0066         }
0067     }
0068 
0069     // special cases
0070     if ( yMax == yMin ) {
0071         if ( yMin == 0.0 ) {
0072             yMax = 0.1; // we need at least a range
0073         } else if ( yMax < 0.0 ) {
0074             yMax = 0.0; // extend the range to zero
0075         } else if ( yMin > 0.0 ) {
0076             yMin = 0.0; // dito
0077         }
0078     }
0079 
0080     return QPair< QPointF, QPointF >( QPointF( xMin, yMin ), QPointF( xMax, yMax ) );
0081 }
0082 
0083 void StackedBarDiagram::paint( PaintContext* ctx )
0084 {
0085     reverseMapper().clear();
0086 
0087     const QPair<QPointF,QPointF> boundaries = diagram()->dataBoundaries(); // cached
0088 
0089     const QPointF boundLeft = ctx->coordinatePlane()->translate( boundaries.first ) ;
0090     const QPointF boundRight = ctx->coordinatePlane()->translate( boundaries.second );
0091 
0092     const int rowCount = compressor().modelDataRows();
0093     const int colCount = compressor().modelDataColumns();
0094 
0095     BarAttributes ba = diagram()->barAttributes();
0096     qreal barWidth = 0;
0097     qreal maxDepth = 0;
0098     qreal width = boundRight.x() - boundLeft.x();
0099     qreal groupWidth = width / rowCount;
0100     qreal spaceBetweenBars = 0;
0101     qreal spaceBetweenGroups = 0;
0102 
0103     if ( ba.useFixedBarWidth() ) {
0104         barWidth = ba.fixedBarWidth();
0105         groupWidth += barWidth;
0106 
0107         // Pending Michel set a min and max value for the groupWidth
0108         // related to the area.width
0109         if ( groupWidth < 0 )
0110             groupWidth = 0;
0111 
0112         if ( groupWidth  * rowCount > width )
0113             groupWidth = width / rowCount;
0114     }
0115 
0116     // maxLimit: allow the space between bars to be larger until area.width()
0117     // is covered by the groups.
0118     qreal maxLimit = rowCount * (groupWidth + ((colCount-1) * ba.fixedDataValueGap()) );
0119 
0120 
0121     //Pending Michel: FixMe
0122     if ( ba.useFixedDataValueGap() ) {
0123         if ( width > maxLimit )
0124             spaceBetweenBars += ba.fixedDataValueGap();
0125         else
0126             spaceBetweenBars = ((width/rowCount) - groupWidth)/(colCount-1);
0127     }
0128 
0129     if ( ba.useFixedValueBlockGap() )
0130         spaceBetweenGroups += ba.fixedValueBlockGap();
0131 
0132     calculateValueAndGapWidths( rowCount, colCount,groupWidth,
0133                                 barWidth, spaceBetweenBars, spaceBetweenGroups );
0134 
0135     LabelPaintCache lpc;
0136     for ( int col = 0; col < colCount; ++col )
0137     {
0138         qreal offset = spaceBetweenGroups;
0139         if ( ba.useFixedBarWidth() )
0140             offset -= ba.fixedBarWidth();
0141 
0142         CartesianCoordinatePlane *plane = static_cast<CartesianCoordinatePlane*>(ctx->coordinatePlane());
0143         if (plane->isHorizontalRangeReversed()) {
0144             if (offset > 0) {
0145                 offset = 0;
0146             }
0147         } else if ( offset < 0 ) {
0148             offset = 0;
0149         }
0150         for ( int row = 0; row < rowCount; ++row )
0151         {
0152             const CartesianDiagramDataCompressor::CachePosition position( row, col );
0153             const CartesianDiagramDataCompressor::DataPoint p = compressor().data( position );
0154  
0155             const QModelIndex index = attributesModel()->mapToSource( p.index );
0156             ThreeDBarAttributes threeDAttrs = diagram()->threeDBarAttributes( index );
0157             const qreal value = p.value;
0158             qreal stackedValues = 0.0;
0159             qreal key = 0.0;
0160 
0161             if ( threeDAttrs.isEnabled() ) {
0162                 if ( barWidth > 0 )
0163                     barWidth =  (width - ((offset+(threeDAttrs.depth()))*rowCount))/ rowCount;
0164                 if ( barWidth <= 0 ) {
0165                     barWidth = 0;
0166                     maxDepth = offset - (width/rowCount);
0167                 }
0168             } else {
0169                 barWidth =  (width - (offset*rowCount))/ rowCount ;
0170             }
0171 
0172             for ( int k = col; k >= 0; --k )
0173             {
0174                 const CartesianDiagramDataCompressor::CachePosition position( row, k );
0175                 const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
0176                 if ( !ISNAN( point.value ) && (( p.value >= 0.0 && point.value >= 0.0 ) || ( p.value < 0.0 && point.value < 0.0 )) )
0177                     stackedValues += point.value;
0178                 key = point.key;
0179             }
0180 
0181             if (!ISNAN( value ))
0182             {
0183                 const qreal usedDepth = threeDAttrs.depth();
0184 
0185                 QPointF point = ctx->coordinatePlane()->translate( QPointF( key, stackedValues ) );
0186 
0187                 const qreal dy = point.y() - usedDepth;
0188                 if ( dy < 0 ) {
0189                     threeDAttrs.setDepth( point.y() - 1 );
0190                     diagram()->setThreeDBarAttributes( threeDAttrs );
0191                 }
0192 
0193                 point.rx() += offset / 2;
0194                 const QPointF previousPoint = ctx->coordinatePlane()->translate( QPointF( key, stackedValues - value ) );
0195                 const qreal barHeight = previousPoint.y() - point.y();
0196 
0197                 const QRectF rect( point, QSizeF( barWidth , barHeight ) );
0198                 m_private->addLabel( &lpc, index, nullptr, PositionPoints( rect ), Position::North,
0199                                      Position::South, value );
0200                 paintBars( ctx, index, rect, maxDepth );
0201             }
0202         }
0203     }
0204     m_private->paintDataValueTextsAndMarkers( ctx, lpc, false );
0205 }