File indexing completed on 2024-12-15 04:02:26

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 "KChartNormalLyingBarDiagram_p.h"
0010 
0011 #include <QModelIndex>
0012 
0013 #include "KChartBarDiagram.h"
0014 #include "KChartTextAttributes.h"
0015 #include "KChartAttributesModel.h"
0016 #include "KChartAbstractCartesianDiagram.h"
0017 
0018 using namespace KChart;
0019 using namespace std;
0020 
0021 NormalLyingBarDiagram::NormalLyingBarDiagram( BarDiagram* d )
0022     : BarDiagramType( d )
0023 {
0024 }
0025 
0026 BarDiagram::BarType NormalLyingBarDiagram::type() const
0027 {
0028     return BarDiagram::Normal;
0029 }
0030 
0031 // TODO there is a lot of duplication between this and the non-lying bar diagram, fix it someday...
0032 const QPair<QPointF, QPointF> NormalLyingBarDiagram::calculateDataBoundaries() const
0033 {
0034     const int rowCount = compressor().modelDataRows();
0035     const int colCount = compressor().modelDataColumns();
0036 
0037     const qreal xMin = 0.0;
0038     const qreal xMax = rowCount;
0039     qreal yMin = 0.0;
0040     qreal yMax = 0.0;
0041 
0042     bool isFirst = true;
0043     for ( int column = 0; column < colCount; ++column ) {
0044         for ( int row = 0; row < rowCount; ++row ) {
0045             const CartesianDiagramDataCompressor::CachePosition position( row, column );
0046             const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
0047             const qreal value = ISNAN( point.value ) ? 0.0 : point.value;
0048             // this is always true yMin can be 0 in case all values
0049             // are the same
0050             // same for yMax it can be zero if all values are negative
0051             if ( isFirst ) {
0052                 yMin = value;
0053                 yMax = value;
0054                 isFirst = false;
0055             } else {
0056                 yMin = qMin( yMin, value );
0057                 yMax = qMax( yMax, value );
0058             }
0059         }
0060     }
0061 
0062     // special cases
0063     if ( yMax == yMin ) {
0064         if ( yMin == 0.0 ) {
0065             yMax = 0.1; // we need at least a range
0066         } else if ( yMax < 0.0 ) {
0067             yMax = 0.0; // they are the same and negative
0068         } else if ( yMin > 0.0 ) {
0069             yMin = 0.0; // they are the same but positive
0070         }
0071     }
0072     const QPointF bottomLeft( QPointF( yMin, xMin ) );
0073     const QPointF topRight( QPointF( yMax, xMax ) );
0074 
0075     return QPair< QPointF, QPointF >( bottomLeft, topRight );
0076 }
0077 
0078 void NormalLyingBarDiagram::paint( PaintContext* ctx )
0079 {
0080     // FIXME: in all LyingBarDiagram types, the datasets are rendered top to bottom, but the abscissa
0081     // (in that case Y axis) ticks are still bottom to top. So tick labels are in reverse order.
0082     reverseMapper().clear();
0083 
0084     const QPair<QPointF,QPointF> boundaries = diagram()->dataBoundaries(); // cached
0085 
0086     const QPointF boundLeft = ctx->coordinatePlane()->translate( boundaries.first ) ;
0087     const QPointF boundRight = ctx->coordinatePlane()->translate( boundaries.second );
0088 
0089     const int rowCount = attributesModel()->rowCount( attributesModelRootIndex() );
0090     const int colCount = attributesModel()->columnCount( attributesModelRootIndex() );
0091 
0092     BarAttributes ba = diagram()->barAttributes();
0093     qreal barWidth = 0;
0094     qreal maxDepth = 0;
0095     qreal width = boundLeft.y() - boundRight.y();
0096     qreal groupWidth = width / rowCount;
0097     qreal spaceBetweenBars = 0;
0098     qreal spaceBetweenGroups = 0;
0099 
0100     if ( ba.useFixedBarWidth() ) {
0101 
0102         barWidth = ba.fixedBarWidth();
0103         groupWidth += barWidth;
0104 
0105         // Pending Michel set a min and max value for the groupWidth
0106         // related to the area.width
0107         if ( groupWidth < 0 )
0108             groupWidth = 0;
0109 
0110         if ( groupWidth * rowCount > width )
0111             groupWidth = width / rowCount;
0112     }
0113 
0114     // maxLimit: allow the space between bars to be larger until area.width()
0115     // is covered by the groups.
0116     qreal maxLimit = rowCount * ( groupWidth + ( colCount - 1 ) * ba.fixedDataValueGap() );
0117 
0118     //Pending Michel: FixMe
0119     if ( ba.useFixedDataValueGap() ) {
0120         if ( width > maxLimit )
0121             spaceBetweenBars += ba.fixedDataValueGap();
0122         else
0123             spaceBetweenBars = ( width / rowCount - groupWidth ) / ( colCount - 1 );
0124     }
0125 
0126     if ( ba.useFixedValueBlockGap() ) {
0127         spaceBetweenGroups += ba.fixedValueBlockGap();
0128     }
0129 
0130     calculateValueAndGapWidths( rowCount, colCount,groupWidth,
0131                                 barWidth, spaceBetweenBars, spaceBetweenGroups );
0132 
0133     LabelPaintCache lpc;
0134 
0135     for ( int row = 0; row < rowCount; row++ ) {
0136     qreal offset = -groupWidth / 2 + spaceBetweenGroups / 2;
0137 
0138         if ( ba.useFixedDataValueGap() ) {
0139             if ( spaceBetweenBars > 0 ) {
0140                 if ( width > maxLimit ) {
0141                     offset -= ba.fixedDataValueGap();
0142                 } else {
0143                     offset -= ( width / rowCount - groupWidth ) / ( colCount - 1 );
0144                 }
0145             } else {
0146                 offset += barWidth / 2;
0147             }
0148         }
0149 
0150         for ( int column = colCount-1; column >= 0; --column ) {
0151             // paint one group
0152             const CartesianDiagramDataCompressor::CachePosition position( row,  column );
0153             const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
0154             const QModelIndex sourceIndex = attributesModel()->mapToSource( point.index );
0155 
0156             QPointF dataPoint( 0, ( point.key + 0.5 ) );
0157             const QPointF topLeft = ctx->coordinatePlane()->translate( dataPoint );
0158             dataPoint.rx() += point.value;
0159             const QPointF bottomRight = ctx->coordinatePlane()->translate( dataPoint ) +
0160                                         QPointF( 0, barWidth );
0161 
0162             const QRectF rect = QRectF( topLeft, bottomRight ).translated( 1.0, offset );
0163             m_private->addLabel( &lpc, sourceIndex, nullptr, PositionPoints( rect ), Position::North,
0164                                  Position::South, point.value );
0165 
0166             paintBars( ctx, sourceIndex, rect, maxDepth );
0167 
0168             offset += barWidth + spaceBetweenBars;
0169         }
0170     }
0171     m_private->paintDataValueTextsAndMarkers( ctx, lpc, false );
0172 }