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