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 }