1 /*
2  * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB.  All rights reserved.
3  *
4  * This file is part of the KD Chart library.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "KChartStackedLineDiagram_p.h"
21 
22 #include <QAbstractItemModel>
23 
24 #include "KChartBarDiagram.h"
25 #include "KChartLineDiagram.h"
26 #include "KChartTextAttributes.h"
27 #include "KChartAttributesModel.h"
28 #include "KChartAbstractCartesianDiagram.h"
29 #include "PaintingHelpers_p.h"
30 
31 using namespace KChart;
32 using namespace std;
33 
StackedLineDiagram(LineDiagram * d)34 StackedLineDiagram::StackedLineDiagram( LineDiagram* d )
35     : LineDiagramType( d )
36 {
37 }
38 
type() const39 LineDiagram::LineType StackedLineDiagram::type() const
40 {
41     return LineDiagram::Stacked;
42 }
43 
calculateDataBoundaries() const44 const QPair<QPointF, QPointF> StackedLineDiagram::calculateDataBoundaries() const
45 {
46     const int rowCount = compressor().modelDataRows();
47     const int colCount = compressor().modelDataColumns();
48     const qreal xMin = 0;
49     qreal xMax = diagram()->model() ? diagram()->model()->rowCount( diagram()->rootIndex() ) : 0;
50     if ( !diagram()->centerDataPoints() && diagram()->model() )
51         xMax -= 1;
52     qreal yMin = 0, yMax = 0;
53 
54     bool bStarting = true;
55     for ( int row = 0; row < rowCount; ++row )
56     {
57         // calculate sum of values per column - Find out stacked Min/Max
58         qreal stackedValues = 0.0;
59         qreal negativeStackedValues = 0.0;
60         for ( int col = datasetDimension() - 1; col < colCount; col += datasetDimension() ) {
61             const CartesianDiagramDataCompressor::CachePosition position( row, col );
62             const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
63 
64             if ( ISNAN( point.value ) )
65                 continue;
66 
67             if ( point.value >= 0.0 )
68                 stackedValues += point.value;
69             else
70                 negativeStackedValues += point.value;
71         }
72 
73         if ( bStarting ) {
74             yMin = stackedValues;
75             yMax = stackedValues;
76             bStarting = false;
77         } else {
78             // take in account all stacked values
79             yMin = qMin( qMin( yMin, negativeStackedValues ), stackedValues );
80             yMax = qMax( qMax( yMax, negativeStackedValues ), stackedValues );
81         }
82     }
83 
84     const QPointF bottomLeft( xMin, yMin );
85     const QPointF topRight( xMax, yMax );
86 
87     return QPair<QPointF, QPointF> ( bottomLeft, topRight );
88 }
89 
paint(PaintContext * ctx)90 void StackedLineDiagram::paint( PaintContext* ctx )
91 {
92     reverseMapper().clear();
93 
94     const int columnCount = compressor().modelDataColumns();
95     const int rowCount = compressor().modelDataRows();
96 
97 // FIXME integrate column index retrieval to compressor:
98 //    int maxFound = 0;
99 //    {   // find the last column number that is not hidden
100 //        for ( int iColumn =  datasetDimension() - 1;
101 //             iColumn <  columnCount;
102 //             iColumn += datasetDimension() )
103 //            if ( ! diagram()->isHidden( iColumn ) )
104 //                maxFound = iColumn;
105 //    }
106     //maxFound = columnCount;
107     // ^^^ temp
108 
109     LabelPaintCache lpc;
110     LineAttributesInfoList lineList;
111 
112     QVector< qreal > percentSumValues;
113 
114     QList<QPointF> bottomPoints;
115     bool bFirstDataset = true;
116 
117     for ( int column = 0; column < columnCount; ++column )
118     {
119         CartesianDiagramDataCompressor::CachePosition previousCellPosition;
120 
121         //display area can be set by dataset ( == column) and/or by cell
122         LineAttributes laPreviousCell; // by default no area is drawn
123         QModelIndex indexPreviousCell;
124         QList<QPolygonF> areas;
125         QList<QPointF> points;
126 
127         for ( int row = 0; row < rowCount; ++row ) {
128             const CartesianDiagramDataCompressor::CachePosition position( row, column );
129             CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
130             const QModelIndex sourceIndex = attributesModel()->mapToSource( point.index );
131 
132             const LineAttributes laCell = diagram()->lineAttributes( sourceIndex );
133             const bool bDisplayCellArea = laCell.displayArea();
134 
135             const LineAttributes::MissingValuesPolicy policy = laCell.missingValuesPolicy();
136 
137             if ( ISNAN( point.value ) && policy == LineAttributes::MissingValuesShownAsZero )
138                 point.value = 0.0;
139 
140             qreal stackedValues = 0, nextValues = 0, nextKey = 0;
141             for ( int column2 = column; column2 >= 0; --column2 )
142             {
143                 const CartesianDiagramDataCompressor::CachePosition position( row, column2 );
144                 const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
145                 if ( !ISNAN( point.value ) )
146                 {
147                     stackedValues += point.value;
148                 }
149                 else if ( policy == LineAttributes::MissingValuesAreBridged )
150                 {
151                     const qreal interpolation = interpolateMissingValue( position );
152                     if ( !ISNAN( interpolation ) )
153                         stackedValues += interpolation;
154                 }
155 
156                 //qDebug() << valueForCell( iRow, iColumn2 );
157                 if ( row + 1 < rowCount ) {
158                     const CartesianDiagramDataCompressor::CachePosition position( row + 1, column2 );
159                     const CartesianDiagramDataCompressor::DataPoint point = compressor().data( position );
160                     if ( !ISNAN( point.value ) )
161                     {
162                         nextValues += point.value;
163                     }
164                     else if ( policy == LineAttributes::MissingValuesAreBridged )
165                     {
166                         const qreal interpolation = interpolateMissingValue( position );
167                         if ( !ISNAN( interpolation ) )
168                             nextValues += interpolation;
169                     }
170                     nextKey = point.key;
171                 }
172             }
173             //qDebug() << stackedValues << endl;
174             const QPointF nextPoint = ctx->coordinatePlane()->translate( QPointF( diagram()->centerDataPoints() ? point.key + 0.5 : point.key, stackedValues ) );
175             points << nextPoint;
176 
177             const QPointF ptNorthWest( nextPoint );
178             const QPointF ptSouthWest(
179                 bDisplayCellArea
180                 ? ( bFirstDataset
181                     ? ctx->coordinatePlane()->translate( QPointF( diagram()->centerDataPoints() ? point.key + 0.5 : point.key, 0.0 ) )
182                     : bottomPoints.at( row )
183                     )
184                 : nextPoint );
185             QPointF ptNorthEast;
186             QPointF ptSouthEast;
187 
188             if ( row + 1 < rowCount ) {
189                 QPointF toPoint = ctx->coordinatePlane()->translate( QPointF( diagram()->centerDataPoints() ? nextKey + 0.5 : nextKey, nextValues ) );
190                 lineList.append( LineAttributesInfo( sourceIndex, nextPoint, toPoint ) );
191                 ptNorthEast = toPoint;
192                 ptSouthEast =
193                     bDisplayCellArea
194                     ? ( bFirstDataset
195                         ? ctx->coordinatePlane()->translate( QPointF( diagram()->centerDataPoints() ? nextKey + 0.5 : nextKey, 0.0 ) )
196                         : bottomPoints.at( row + 1 )
197                         )
198                     : toPoint;
199                 if ( areas.count() && laCell != laPreviousCell ) {
200                     PaintingHelpers::paintAreas( m_private, ctx, indexPreviousCell, areas, laPreviousCell.transparency() );
201                     areas.clear();
202                 }
203                 if ( bDisplayCellArea ) {
204                     QPolygonF poly;
205                     poly << ptNorthWest << ptNorthEast << ptSouthEast << ptSouthWest;
206                     areas << poly;
207                     laPreviousCell = laCell;
208                     indexPreviousCell = sourceIndex;
209                 } else {
210                     //qDebug() << "no area shown for row"<<iRow<<"  column"<<iColumn;
211                 }
212             } else {
213                 ptNorthEast = ptNorthWest;
214                 ptSouthEast = ptSouthWest;
215             }
216 
217             const PositionPoints pts( ptNorthWest, ptNorthEast, ptSouthEast, ptSouthWest );
218             if ( !ISNAN( point.value ) )
219                 m_private->addLabel( &lpc, sourceIndex, &position, pts, Position::NorthWest,
220                                      Position::NorthWest, point.value );
221         }
222         if ( areas.count() ) {
223             PaintingHelpers::paintAreas( m_private, ctx, indexPreviousCell, areas, laPreviousCell.transparency() );
224             areas.clear();
225         }
226         bottomPoints = points;
227         bFirstDataset = false;
228     }
229     PaintingHelpers::paintElements( m_private, ctx, lpc, lineList );
230 }
231