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 "KChartPlotter.h"
21 #include "KChartPlotter_p.h"
22 
23 #include "KChartAbstractGrid.h"
24 #include "KChartPainterSaver_p.h"
25 #include "KChartMath_p.h"
26 
27 #include "KChartNormalPlotter_p.h"
28 #include "KChartPercentPlotter_p.h"
29 #include "KChartStackedPlotter_p.h"
30 
31 using namespace KChart;
32 
Private()33 Plotter::Private::Private()
34     : implementor( nullptr )
35     , normalPlotter( nullptr )
36     , percentPlotter( nullptr )
37     , stackedPlotter( nullptr )
38 {
39 }
40 
~Private()41 Plotter::Private::~Private()
42 {
43     delete normalPlotter;
44     delete percentPlotter;
45     delete stackedPlotter;
46 }
47 
48 
49 #define d d_func()
50 
51 
Plotter(QWidget * parent,CartesianCoordinatePlane * plane)52 Plotter::Plotter( QWidget* parent, CartesianCoordinatePlane* plane ) :
53     AbstractCartesianDiagram( new Private(), parent, plane )
54 {
55     init();
56 }
57 
init()58 void Plotter::init()
59 {
60     d->diagram = this;
61     d->normalPlotter = new NormalPlotter( this );
62     d->percentPlotter = new PercentPlotter( this );
63     d->stackedPlotter = new StackedPlotter( this );
64     d->implementor = d->normalPlotter;
65     QObject* test = d->implementor->plotterPrivate();
66     connect( this, SIGNAL(boundariesChanged()), test, SLOT(changedProperties()) );
67     // The signal is connected to the superclass's slot at this point because the connection happened
68     // in its constructor when "its type was not Plotter yet".
69     disconnect( this, SIGNAL(attributesModelAboutToChange(AttributesModel*,AttributesModel*)),
70                 this, SLOT(connectAttributesModel(AttributesModel*)) );
71     connect( this, SIGNAL(attributesModelAboutToChange(AttributesModel*,AttributesModel*)),
72              this, SLOT(connectAttributesModel(AttributesModel*)) );
73     setDatasetDimensionInternal( 2 );
74 }
75 
~Plotter()76 Plotter::~Plotter()
77 {
78 }
79 
clone() const80 Plotter* Plotter::clone() const
81 {
82     Plotter* newDiagram = new Plotter( new Private( *d ) );
83     newDiagram->setType( type() );
84     return newDiagram;
85 }
86 
compare(const Plotter * other) const87 bool Plotter::compare( const Plotter* other ) const
88 {
89     if ( other == this )
90         return true;
91     if ( other == nullptr )
92         return false;
93     return  // compare the base class
94             ( static_cast< const AbstractCartesianDiagram* >( this )->compare( other ) ) &&
95             // compare own properties
96             ( type() == other->type() );
97 }
98 
connectAttributesModel(AttributesModel * newModel)99 void Plotter::connectAttributesModel( AttributesModel* newModel )
100 {
101     // Order of setting the AttributesModel in compressor and diagram is very important due to slot
102     // invocation order. Refer to the longer comment in
103     // AbstractCartesianDiagram::connectAttributesModel() for details.
104 
105     if ( useDataCompression() == Plotter::NONE )
106     {
107         d->plotterCompressor.setModel( nullptr );
108         AbstractCartesianDiagram::connectAttributesModel( newModel );
109     }
110     else
111     {
112         d->compressor.setModel( nullptr );
113         if ( attributesModel() != d->plotterCompressor.model() )
114         {
115             d->plotterCompressor.setModel( attributesModel() );
116             connect( &d->plotterCompressor, SIGNAL(boundariesChanged()), this, SLOT(setDataBoundariesDirty()) );
117             if ( useDataCompression() != Plotter::SLOPE )
118             {
119                 connect( coordinatePlane(), SIGNAL(internal_geometryChanged(QRect,QRect)),
120                          this, SLOT(setDataBoundariesDirty()) );
121                 connect( coordinatePlane(), SIGNAL(geometryChanged(QRect,QRect)),
122                          this, SLOT(setDataBoundariesDirty()) );
123                 calcMergeRadius();
124             }
125         }
126     }
127 }
128 
useDataCompression() const129 Plotter::CompressionMode Plotter::useDataCompression() const
130 {
131     return d->implementor->useCompression();
132 }
133 
setUseDataCompression(Plotter::CompressionMode value)134 void Plotter::setUseDataCompression( Plotter::CompressionMode value )
135 {
136     if ( useDataCompression() != value )
137     {
138         d->implementor->setUseCompression( value );
139         if ( useDataCompression() != Plotter::NONE )
140         {
141             d->compressor.setModel( nullptr );
142             if ( attributesModel() != d->plotterCompressor.model() )
143                 d->plotterCompressor.setModel( attributesModel() );
144         }
145     }
146 }
147 
maxSlopeChange() const148 qreal Plotter::maxSlopeChange() const
149 {
150     return d->plotterCompressor.maxSlopeChange();
151 }
152 
setMaxSlopeChange(qreal value)153 void Plotter::setMaxSlopeChange( qreal value )
154 {
155     d->plotterCompressor.setMaxSlopeChange( value );
156 }
157 
mergeRadiusPercentage() const158 qreal Plotter::mergeRadiusPercentage() const
159 {
160     return d->mergeRadiusPercentage;
161 }
162 
setMergeRadiusPercentage(qreal value)163 void Plotter::setMergeRadiusPercentage( qreal value )
164 {
165     if ( d->mergeRadiusPercentage != value )
166     {
167         d->mergeRadiusPercentage = value;
168         //d->plotterCompressor.setMergeRadiusPercentage( value );
169         //update();
170     }
171 }
172 
setType(const PlotType type)173 void Plotter::setType( const PlotType type )
174 {
175     if ( d->implementor->type() == type ) {
176         return;
177     }
178     if ( datasetDimension() != 2 )  {
179         Q_ASSERT_X ( false, "setType()",
180                      "This line chart type can only be used with two-dimensional data." );
181         return;
182     }
183     switch ( type ) {
184     case Normal:
185         d->implementor = d->normalPlotter;
186         break;
187     case Percent:
188         d->implementor = d->percentPlotter;
189         break;
190     case Stacked:
191         d->implementor = d->stackedPlotter;
192         break;
193     default:
194         Q_ASSERT_X( false, "Plotter::setType", "unknown plotter subtype" );
195     }
196     bool connection = connect( this, SIGNAL(boundariesChanged()),
197                                d->implementor->plotterPrivate(), SLOT(changedProperties()) );
198     Q_ASSERT( connection );
199     Q_UNUSED( connection );
200 
201     // d->lineType = type;
202     Q_ASSERT( d->implementor->type() == type );
203 
204     setDataBoundariesDirty();
205     Q_EMIT layoutChanged( this );
206     Q_EMIT propertiesChanged();
207 }
208 
type() const209 Plotter::PlotType Plotter::type() const
210 {
211     return d->implementor->type();
212 }
213 
setLineAttributes(const LineAttributes & la)214 void Plotter::setLineAttributes( const LineAttributes& la )
215 {
216     d->attributesModel->setModelData( QVariant::fromValue( la ), LineAttributesRole );
217     Q_EMIT propertiesChanged();
218 }
219 
setLineAttributes(int column,const LineAttributes & la)220 void Plotter::setLineAttributes( int column, const LineAttributes& la )
221 {
222     d->setDatasetAttrs( column, QVariant::fromValue( la ), LineAttributesRole );
223     Q_EMIT propertiesChanged();
224 }
225 
resetLineAttributes(int column)226 void Plotter::resetLineAttributes( int column )
227 {
228     d->resetDatasetAttrs( column, LineAttributesRole );
229     Q_EMIT propertiesChanged();
230 }
231 
setLineAttributes(const QModelIndex & index,const LineAttributes & la)232 void Plotter::setLineAttributes( const QModelIndex & index, const LineAttributes& la )
233 {
234     d->attributesModel->setData( d->attributesModel->mapFromSource( index ),
235                                  QVariant::fromValue( la ), LineAttributesRole );
236     Q_EMIT propertiesChanged();
237 }
238 
resetLineAttributes(const QModelIndex & index)239 void Plotter::resetLineAttributes( const QModelIndex & index )
240 {
241     d->attributesModel->resetData(
242             d->attributesModel->mapFromSource(index), LineAttributesRole );
243     Q_EMIT propertiesChanged();
244 }
245 
lineAttributes() const246 LineAttributes Plotter::lineAttributes() const
247 {
248     return d->attributesModel->data( KChart::LineAttributesRole ).value<LineAttributes>();
249 }
250 
lineAttributes(int column) const251 LineAttributes Plotter::lineAttributes( int column ) const
252 {
253     const QVariant attrs( d->datasetAttrs( column, LineAttributesRole ) );
254     if ( attrs.isValid() )
255         return attrs.value<LineAttributes>();
256     return lineAttributes();
257 }
258 
lineAttributes(const QModelIndex & index) const259 LineAttributes Plotter::lineAttributes( const QModelIndex& index ) const
260 {
261     return d->attributesModel->data(
262         d->attributesModel->mapFromSource( index ), KChart::LineAttributesRole ).value<LineAttributes>();
263 }
264 
setThreeDLineAttributes(const ThreeDLineAttributes & la)265 void Plotter::setThreeDLineAttributes( const ThreeDLineAttributes& la )
266 {
267     setDataBoundariesDirty();
268     d->attributesModel->setModelData( QVariant::fromValue( la ), ThreeDLineAttributesRole );
269     Q_EMIT propertiesChanged();
270 }
271 
setThreeDLineAttributes(int column,const ThreeDLineAttributes & la)272 void Plotter::setThreeDLineAttributes( int column, const ThreeDLineAttributes& la )
273 {
274     setDataBoundariesDirty();
275     d->setDatasetAttrs( column, QVariant::fromValue( la ), ThreeDLineAttributesRole );
276     Q_EMIT propertiesChanged();
277 }
278 
setThreeDLineAttributes(const QModelIndex & index,const ThreeDLineAttributes & la)279 void Plotter::setThreeDLineAttributes( const QModelIndex& index, const ThreeDLineAttributes& la )
280 {
281     setDataBoundariesDirty();
282     d->attributesModel->setData( d->attributesModel->mapFromSource( index ), QVariant::fromValue( la ),
283                                  ThreeDLineAttributesRole );
284     Q_EMIT propertiesChanged();
285 }
286 
threeDLineAttributes() const287 ThreeDLineAttributes Plotter::threeDLineAttributes() const
288 {
289     return d->attributesModel->data( KChart::ThreeDLineAttributesRole ).value<ThreeDLineAttributes>();
290 }
291 
threeDLineAttributes(int column) const292 ThreeDLineAttributes Plotter::threeDLineAttributes( int column ) const
293 {
294     const QVariant attrs( d->datasetAttrs( column, ThreeDLineAttributesRole ) );
295     if ( attrs.isValid() ) {
296         return attrs.value<ThreeDLineAttributes>();
297     }
298     return threeDLineAttributes();
299 }
300 
threeDLineAttributes(const QModelIndex & index) const301 ThreeDLineAttributes Plotter::threeDLineAttributes( const QModelIndex& index ) const
302 {
303     return d->attributesModel->data(
304         d->attributesModel->mapFromSource( index ), KChart::ThreeDLineAttributesRole ).value<ThreeDLineAttributes>();
305 }
306 
threeDItemDepth(const QModelIndex & index) const307 qreal Plotter::threeDItemDepth( const QModelIndex & index ) const
308 {
309     return threeDLineAttributes( index ).validDepth();
310 }
311 
threeDItemDepth(int column) const312 qreal Plotter::threeDItemDepth( int column ) const
313 {
314     return threeDLineAttributes( column ).validDepth();
315 }
316 
setValueTrackerAttributes(const QModelIndex & index,const ValueTrackerAttributes & va)317 void Plotter::setValueTrackerAttributes( const QModelIndex & index, const ValueTrackerAttributes & va )
318 {
319     d->attributesModel->setData( d->attributesModel->mapFromSource( index ),
320                                  QVariant::fromValue( va ), KChart::ValueTrackerAttributesRole );
321     Q_EMIT propertiesChanged();
322 }
323 
valueTrackerAttributes(const QModelIndex & index) const324 ValueTrackerAttributes Plotter::valueTrackerAttributes( const QModelIndex & index ) const
325 {
326     return d->attributesModel->data(
327         d->attributesModel->mapFromSource( index ), KChart::ValueTrackerAttributesRole ).value<ValueTrackerAttributes>();
328 }
329 
resizeEvent(QResizeEvent *)330 void Plotter::resizeEvent ( QResizeEvent* )
331 {
332 }
333 
calculateDataBoundaries() const334 const QPair< QPointF, QPointF > Plotter::calculateDataBoundaries() const
335 {
336     if ( !checkInvariants( true ) )
337         return QPair< QPointF, QPointF >( QPointF( 0, 0 ), QPointF( 0, 0 ) );
338 
339     // note: calculateDataBoundaries() is ignoring the hidden flags.
340     //       That's not a bug but a feature: Hiding data does not mean removing them.
341     // For totally removing data from KD Chart's view people can use e.g. a proxy model ...
342 
343     // calculate boundaries for different line types Normal - Stacked - Percent - Default Normal
344     return d->implementor->calculateDataBoundaries();
345 }
346 
347 
paintEvent(QPaintEvent *)348 void Plotter::paintEvent ( QPaintEvent*)
349 {
350     QPainter painter ( viewport() );
351     PaintContext ctx;
352     ctx.setPainter ( &painter );
353     ctx.setRectangle ( QRectF ( 0, 0, width(), height() ) );
354     paint ( &ctx );
355 }
356 
paint(PaintContext * ctx)357 void Plotter::paint( PaintContext* ctx )
358 {
359     // note: Not having any data model assigned is no bug
360     //       but we can not draw a diagram then either.
361     if ( !checkInvariants( true ) ) return;
362 
363     AbstractCoordinatePlane* const plane = ctx->coordinatePlane();
364     if ( ! plane ) return;
365     d->setCompressorResolution( size(), plane );
366 
367     if ( !AbstractGrid::isBoundariesValid(dataBoundaries()) ) return;
368 
369     const PainterSaver p( ctx->painter() );
370     if ( model()->rowCount( rootIndex() ) == 0 || model()->columnCount( rootIndex() ) == 0 )
371         return; // nothing to paint for us
372 
373     ctx->setCoordinatePlane( plane->sharedAxisMasterPlane( ctx->painter() ) );
374 
375     // paint different line types Normal - Stacked - Percent - Default Normal
376     d->implementor->paint( ctx );
377 
378     ctx->setCoordinatePlane( plane );
379 }
380 
resize(const QSizeF & size)381 void Plotter::resize ( const QSizeF& size )
382 {
383     d->setCompressorResolution( size, coordinatePlane() );
384     if ( useDataCompression() == Plotter::BOTH || useDataCompression() == Plotter::DISTANCE )
385     {
386         d->plotterCompressor.cleanCache();
387         calcMergeRadius();
388     }
389     setDataBoundariesDirty();
390     AbstractCartesianDiagram::resize( size );
391 }
392 
setDataBoundariesDirty()393 void Plotter::setDataBoundariesDirty()
394 {
395     AbstractCartesianDiagram::setDataBoundariesDirty();
396     if ( useDataCompression() == Plotter::DISTANCE || useDataCompression() == Plotter::BOTH )
397     {
398         calcMergeRadius();
399         //d->plotterCompressor.setMergeRadiusPercentage( d->mergeRadiusPercentage );
400     }
401 }
402 
calcMergeRadius()403 void Plotter::calcMergeRadius()
404 {
405     CartesianCoordinatePlane *plane = dynamic_cast< CartesianCoordinatePlane* >( coordinatePlane() );
406     Q_ASSERT( plane );
407     //Q_ASSERT( plane->translate( plane->translateBack( plane->visibleDiagramArea().topLeft() ) ) == plane->visibleDiagramArea().topLeft() );
408     QRectF range = plane->visibleDataRange();
409     //qDebug() << range;
410     const qreal radius = std::sqrt( ( range.x() + range.width() ) * ( range.y() +  range.height() ) );
411     //qDebug() << radius;
412     //qDebug() << radius * d->mergeRadiusPercentage;
413     //qDebug() << d->mergeRadiusPercentage;
414     d->plotterCompressor.setMergeRadius( radius * d->mergeRadiusPercentage );
415 }
416 
417 #if defined(Q_COMPILER_MANGLES_RETURN_TYPE)
418 const
419 #endif
numberOfAbscissaSegments() const420 int Plotter::numberOfAbscissaSegments () const
421 {
422     return d->attributesModel->rowCount( attributesModelRootIndex() );
423 }
424 
425 #if defined(Q_COMPILER_MANGLES_RETURN_TYPE)
426 const
427 #endif
numberOfOrdinateSegments() const428 int Plotter::numberOfOrdinateSegments () const
429 {
430     return d->attributesModel->columnCount( attributesModelRootIndex() );
431 }
432