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 <KChartWidget.h>
21 #include <KChartWidget_p.h>
22
23 #include <KChartAbstractDiagram.h>
24 #include <KChartBarDiagram.h>
25 #include <KChartChart.h>
26 #include <KChartAbstractCoordinatePlane.h>
27 #include <KChartLineDiagram.h>
28 #include <KChartPlotter.h>
29 #include <KChartPieDiagram.h>
30 #include <KChartPolarDiagram.h>
31 #include <KChartRingDiagram.h>
32 #include <KChartLegend.h>
33 #include "KChartMath_p.h"
34
35 #include <QDebug>
36
37 #define d d_func()
38
39 using namespace KChart;
40
Private(Widget * qq)41 Widget::Private::Private( Widget * qq )
42 : q( qq ),
43 layout( q ),
44 m_model( q ),
45 m_chart( q ),
46 m_cartPlane( &m_chart ),
47 m_polPlane( &m_chart ),
48 usedDatasetWidth( 0 )
49 {
50 KDAB_SET_OBJECT_NAME( layout );
51 KDAB_SET_OBJECT_NAME( m_model );
52 KDAB_SET_OBJECT_NAME( m_chart );
53
54 layout.addWidget( &m_chart );
55 }
56
~Private()57 Widget::Private::~Private() {}
58
59
60
Widget(QWidget * parent)61 Widget::Widget( QWidget* parent ) :
62 QWidget(parent), _d( new Private( this ) )
63 {
64 // as default we have a cartesian coordinate plane ...
65 // ... and a line diagram
66 setType( Line );
67 }
68
~Widget()69 Widget::~Widget()
70 {
71 delete _d; _d = nullptr;
72 }
73
init()74 void Widget::init()
75 {
76 }
77
setDataset(int column,const QVector<qreal> & data,const QString & title)78 void Widget::setDataset( int column, const QVector< qreal > & data, const QString& title )
79 {
80 if ( ! checkDatasetWidth( 1 ) )
81 return;
82
83 QStandardItemModel & model = d->m_model;
84
85 justifyModelSize( data.size(), column + 1 );
86
87 for ( int i = 0; i < data.size(); ++i )
88 {
89 const QModelIndex index = model.index( i, column );
90 model.setData( index, QVariant( data[i] ), Qt::DisplayRole );
91 }
92 if ( ! title.isEmpty() )
93 model.setHeaderData( column, Qt::Horizontal, QVariant( title ) );
94 }
95
setDataset(int column,const QVector<QPair<qreal,qreal>> & data,const QString & title)96 void Widget::setDataset( int column, const QVector< QPair< qreal, qreal > > & data, const QString& title )
97 {
98 if ( ! checkDatasetWidth( 2 ))
99 return;
100
101 QStandardItemModel & model = d->m_model;
102
103 justifyModelSize( data.size(), (column + 1) * 2 );
104
105 for ( int i = 0; i < data.size(); ++i )
106 {
107 QModelIndex index = model.index( i, column * 2 );
108 model.setData( index, QVariant( data[i].first ), Qt::DisplayRole );
109
110 index = model.index( i, column * 2 + 1 );
111 model.setData( index, QVariant( data[i].second ), Qt::DisplayRole );
112 }
113 if ( ! title.isEmpty() ) {
114 model.setHeaderData( column, Qt::Horizontal, QVariant( title ) );
115 }
116 }
117
setDataCell(int row,int column,qreal data)118 void Widget::setDataCell( int row, int column, qreal data )
119 {
120 if ( ! checkDatasetWidth( 1 ) )
121 return;
122
123 QStandardItemModel & model = d->m_model;
124
125 justifyModelSize( row + 1, column + 1 );
126
127 const QModelIndex index = model.index( row, column );
128 model.setData( index, QVariant( data ), Qt::DisplayRole );
129 }
130
setDataCell(int row,int column,QPair<qreal,qreal> data)131 void Widget::setDataCell( int row, int column, QPair< qreal, qreal > data )
132 {
133 if ( ! checkDatasetWidth( 2 ))
134 return;
135
136 QStandardItemModel & model = d->m_model;
137
138 justifyModelSize( row + 1, (column + 1) * 2 );
139
140 QModelIndex index = model.index( row, column * 2 );
141 model.setData( index, QVariant( data.first ), Qt::DisplayRole );
142
143 index = model.index( row, column * 2 + 1 );
144 model.setData( index, QVariant( data.second ), Qt::DisplayRole );
145 }
146
147 /*
148 * Resets all data.
149 */
resetData()150 void Widget::resetData()
151 {
152 d->m_model.clear();
153 d->usedDatasetWidth = 0;
154 }
155
setGlobalLeading(int left,int top,int right,int bottom)156 void Widget::setGlobalLeading( int left, int top, int right, int bottom )
157 {
158 d->m_chart.setGlobalLeading( left, top, right, bottom );
159 }
160
setGlobalLeadingLeft(int leading)161 void Widget::setGlobalLeadingLeft( int leading )
162 {
163 d->m_chart.setGlobalLeadingLeft( leading );
164 }
165
globalLeadingLeft() const166 int Widget::globalLeadingLeft() const
167 {
168 return d->m_chart.globalLeadingLeft();
169 }
170
setGlobalLeadingTop(int leading)171 void Widget::setGlobalLeadingTop( int leading )
172 {
173 d->m_chart.setGlobalLeadingTop( leading );
174 }
175
globalLeadingTop() const176 int Widget::globalLeadingTop() const
177 {
178 return d->m_chart.globalLeadingTop();
179 }
180
setGlobalLeadingRight(int leading)181 void Widget::setGlobalLeadingRight( int leading )
182 {
183 d->m_chart.setGlobalLeadingRight( leading );
184 }
185
globalLeadingRight() const186 int Widget::globalLeadingRight() const
187 {
188 return d->m_chart.globalLeadingRight();
189 }
190
setGlobalLeadingBottom(int leading)191 void Widget::setGlobalLeadingBottom( int leading )
192 {
193 d->m_chart.setGlobalLeadingBottom( leading );
194 }
195
globalLeadingBottom() const196 int Widget::globalLeadingBottom() const
197 {
198 return d->m_chart.globalLeadingBottom();
199 }
200
firstHeaderFooter()201 KChart::HeaderFooter* Widget::firstHeaderFooter()
202 {
203 return d->m_chart.headerFooter();
204 }
205
allHeadersFooters()206 QList<KChart::HeaderFooter*> Widget::allHeadersFooters()
207 {
208 return d->m_chart.headerFooters();
209 }
210
addHeaderFooter(const QString & text,HeaderFooter::HeaderFooterType type,Position position)211 void Widget::addHeaderFooter( const QString& text,
212 HeaderFooter::HeaderFooterType type,
213 Position position)
214 {
215 HeaderFooter* newHeader = new HeaderFooter( &d->m_chart );
216 newHeader->setType( type );
217 newHeader->setPosition( position );
218 newHeader->setText( text );
219 d->m_chart.addHeaderFooter( newHeader ); // we need this explicit call !
220 }
221
addHeaderFooter(HeaderFooter * header)222 void Widget::addHeaderFooter( HeaderFooter* header )
223 {
224 header->setParent( &d->m_chart );
225 d->m_chart.addHeaderFooter( header ); // we need this explicit call !
226 }
227
replaceHeaderFooter(HeaderFooter * header,HeaderFooter * oldHeader)228 void Widget::replaceHeaderFooter( HeaderFooter* header, HeaderFooter* oldHeader )
229 {
230 header->setParent( &d->m_chart );
231 d->m_chart.replaceHeaderFooter( header, oldHeader );
232 }
233
takeHeaderFooter(HeaderFooter * header)234 void Widget::takeHeaderFooter( HeaderFooter* header )
235 {
236 d->m_chart.takeHeaderFooter( header );
237 }
238
legend()239 KChart::Legend* Widget::legend()
240 {
241 return d->m_chart.legend();
242 }
243
allLegends()244 QList<KChart::Legend*> Widget::allLegends()
245 {
246 return d->m_chart.legends();
247 }
248
addLegend(Position position)249 void Widget::addLegend( Position position )
250 {
251 Legend* legend = new Legend( diagram(), &d->m_chart );
252 legend->setPosition( position );
253 d->m_chart.addLegend( legend );
254 }
255
addLegend(Legend * legend)256 void Widget::addLegend( Legend* legend )
257 {
258 legend->setDiagram( diagram() );
259 legend->setParent( &d->m_chart );
260 d->m_chart.addLegend( legend );
261 }
262
replaceLegend(Legend * legend,Legend * oldLegend)263 void Widget::replaceLegend( Legend* legend, Legend* oldLegend )
264 {
265 legend->setDiagram( diagram() );
266 legend->setParent( &d->m_chart );
267 d->m_chart.replaceLegend( legend, oldLegend );
268 }
269
takeLegend(Legend * legend)270 void Widget::takeLegend( Legend* legend )
271 {
272 d->m_chart.takeLegend( legend );
273 }
274
diagram()275 AbstractDiagram* Widget::diagram()
276 {
277 if ( coordinatePlane() == nullptr )
278 qDebug() << "diagram(): coordinatePlane() was NULL";
279
280 return coordinatePlane()->diagram();
281 }
282
barDiagram()283 BarDiagram* Widget::barDiagram()
284 {
285 return dynamic_cast<BarDiagram*>( diagram() );
286 }
lineDiagram()287 LineDiagram* Widget::lineDiagram()
288 {
289 return dynamic_cast<LineDiagram*>( diagram() );
290 }
plotter()291 Plotter* Widget::plotter()
292 {
293 return dynamic_cast<Plotter*>( diagram() );
294 }
pieDiagram()295 PieDiagram* Widget::pieDiagram()
296 {
297 return dynamic_cast<PieDiagram*>( diagram() );
298 }
ringDiagram()299 RingDiagram* Widget::ringDiagram()
300 {
301 return dynamic_cast<RingDiagram*>( diagram() );
302 }
polarDiagram()303 PolarDiagram* Widget::polarDiagram()
304 {
305 return dynamic_cast<PolarDiagram*>( diagram() );
306 }
307
coordinatePlane()308 AbstractCoordinatePlane* Widget::coordinatePlane()
309 {
310 return d->m_chart.coordinatePlane();
311 }
312
isCartesian(KChart::Widget::ChartType type)313 static bool isCartesian( KChart::Widget::ChartType type )
314 {
315 return (type == KChart::Widget::Bar) || (type == KChart::Widget::Line);
316 }
317
isPolar(KChart::Widget::ChartType type)318 static bool isPolar( KChart::Widget::ChartType type )
319 {
320 return (type == KChart::Widget::Pie)
321 || (type == KChart::Widget::Ring)
322 || (type == KChart::Widget::Polar);
323 }
324
setType(ChartType chartType,SubType chartSubType)325 void Widget::setType( ChartType chartType, SubType chartSubType )
326 {
327 AbstractDiagram* diag = nullptr;
328 const ChartType oldType = type();
329
330 if ( chartType != oldType ) {
331 if ( chartType != NoType ) {
332 if ( isCartesian( chartType ) && ! isCartesian( oldType ) )
333 {
334 if ( coordinatePlane() == &d->m_polPlane ) {
335 d->m_chart.takeCoordinatePlane( &d->m_polPlane );
336 d->m_chart.addCoordinatePlane( &d->m_cartPlane );
337 } else {
338 d->m_chart.replaceCoordinatePlane( &d->m_cartPlane );
339 }
340 }
341 else if ( isPolar( chartType ) && ! isPolar( oldType ) )
342 {
343 if ( coordinatePlane() == &d->m_cartPlane ) {
344 d->m_chart.takeCoordinatePlane( &d->m_cartPlane );
345 d->m_chart.addCoordinatePlane( &d->m_polPlane );
346 } else {
347 d->m_chart.replaceCoordinatePlane( &d->m_polPlane );
348 }
349 }
350 }
351 switch ( chartType ) {
352 case Bar:
353 diag = new BarDiagram( &d->m_chart, &d->m_cartPlane );
354 break;
355 case Line:
356 diag = new LineDiagram( &d->m_chart, &d->m_cartPlane );
357 break;
358 case Plot:
359 diag = new Plotter( &d->m_chart, &d->m_cartPlane );
360 break;
361 case Pie:
362 diag = new PieDiagram( &d->m_chart, &d->m_polPlane );
363 break;
364 case Polar:
365 diag = new PolarDiagram( &d->m_chart, &d->m_polPlane );
366 break;
367 case Ring:
368 diag = new RingDiagram( &d->m_chart, &d->m_polPlane );
369 break;
370 case NoType:
371 break;
372 }
373 if ( diag != nullptr ) {
374 if ( isCartesian( oldType ) && isCartesian( chartType ) ) {
375 AbstractCartesianDiagram *oldDiag =
376 qobject_cast<AbstractCartesianDiagram*>( coordinatePlane()->diagram() );
377 AbstractCartesianDiagram *newDiag =
378 qobject_cast<AbstractCartesianDiagram*>( diag );
379 Q_FOREACH( CartesianAxis* axis, oldDiag->axes() ) {
380 oldDiag->takeAxis( axis );
381 newDiag->addAxis ( axis );
382 }
383 }
384
385 Q_FOREACH( Legend* l, d->m_chart.legends() ) {
386 l->setDiagram( diag );
387 }
388
389 diag->setModel( &d->m_model );
390 coordinatePlane()->replaceDiagram( diag );
391
392 //checkDatasetWidth( d->usedDatasetWidth );
393 }
394 //coordinatePlane()->setGridNeedsRecalculate();
395 }
396
397 if ( chartType != NoType ) {
398 if ( chartType != oldType || chartSubType != subType() )
399 setSubType( chartSubType );
400 d->m_chart.resize( size() ); // triggering immediate update
401 }
402 }
403
404 template< class DiagramType, class Subtype >
setSubtype(AbstractDiagram * _dia,Subtype st)405 void setSubtype( AbstractDiagram *_dia, Subtype st)
406 {
407 if ( DiagramType *dia = qobject_cast< DiagramType * >( _dia ) ) {
408 dia->setType( st );
409 }
410 }
411
setSubType(SubType subType)412 void Widget::setSubType( SubType subType )
413 {
414 // ### at least PieDiagram, PolarDiagram and RingDiagram are unhandled here
415
416 AbstractDiagram *dia = diagram();
417 switch ( subType ) {
418 case Normal:
419 setSubtype< BarDiagram >( dia, BarDiagram::Normal );
420 setSubtype< LineDiagram >( dia, LineDiagram::Normal );
421 setSubtype< Plotter >( dia, Plotter::Normal );
422 break;
423 case Stacked:
424 setSubtype< BarDiagram >( dia, BarDiagram::Stacked );
425 setSubtype< LineDiagram >( dia, LineDiagram::Stacked );
426 // setSubtype< Plotter >( dia, Plotter::Stacked );
427 break;
428 case Percent:
429 setSubtype< BarDiagram >( dia, BarDiagram::Percent );
430 setSubtype< LineDiagram >( dia, LineDiagram::Percent );
431 setSubtype< Plotter >( dia, Plotter::Percent );
432 break;
433 case Rows:
434 setSubtype< BarDiagram >( dia, BarDiagram::Rows );
435 break;
436 default:
437 Q_ASSERT_X ( false, "Widget::setSubType", "Sub-type not supported!" );
438 break;
439 }
440 }
441
type() const442 Widget::ChartType Widget::type() const
443 {
444 // PENDING(christoph) save the type out-of-band:
445 AbstractDiagram * const dia = const_cast<Widget*>( this )->diagram();
446 if ( qobject_cast< BarDiagram* >( dia ) )
447 return Bar;
448 else if ( qobject_cast< LineDiagram* >( dia ) )
449 return Line;
450 else if ( qobject_cast< Plotter* >( dia ) )
451 return Plot;
452 else if ( qobject_cast< PieDiagram* >( dia ) )
453 return Pie;
454 else if ( qobject_cast< PolarDiagram* >( dia ) )
455 return Polar;
456 else if ( qobject_cast< RingDiagram* >( dia ) )
457 return Ring;
458 else
459 return NoType;
460 }
461
subType() const462 Widget::SubType Widget::subType() const
463 {
464 // PENDING(christoph) save the type out-of-band:
465 Widget::SubType retVal = Normal;
466
467 AbstractDiagram * const dia = const_cast<Widget*>( this )->diagram();
468 BarDiagram* barDia = qobject_cast< BarDiagram* >( dia );
469 LineDiagram* lineDia = qobject_cast< LineDiagram* >( dia );
470 Plotter* plotterDia = qobject_cast< Plotter* >( dia );
471
472 //FIXME(khz): Add the impl for these chart types - or remove them from here:
473 // PieDiagram* pieDia = qobject_cast< PieDiagram* >( diagram() );
474 // PolarDiagram* polarDia = qobject_cast< PolarDiagram* >( diagram() );
475 // RingDiagram* ringDia = qobject_cast< RingDiagram* >( diagram() );
476
477 #define TEST_SUB_TYPE(DIAGRAM, INTERNALSUBTYPE, SUBTYPE) \
478 { \
479 if ( DIAGRAM && DIAGRAM->type() == INTERNALSUBTYPE ) \
480 retVal = SUBTYPE; \
481 }
482 const Widget::ChartType mainType = type();
483 switch ( mainType )
484 {
485 case Bar:
486 TEST_SUB_TYPE( barDia, BarDiagram::Normal, Normal );
487 TEST_SUB_TYPE( barDia, BarDiagram::Stacked, Stacked );
488 TEST_SUB_TYPE( barDia, BarDiagram::Percent, Percent );
489 TEST_SUB_TYPE( barDia, BarDiagram::Rows, Rows );
490 break;
491 case Line:
492 TEST_SUB_TYPE( lineDia, LineDiagram::Normal, Normal );
493 TEST_SUB_TYPE( lineDia, LineDiagram::Stacked, Stacked );
494 TEST_SUB_TYPE( lineDia, LineDiagram::Percent, Percent );
495 break;
496 case Plot:
497 TEST_SUB_TYPE( plotterDia, Plotter::Normal, Normal );
498 TEST_SUB_TYPE( plotterDia, Plotter::Percent, Percent );
499 break;
500 case Pie:
501 // no impl. yet
502 break;
503 case Polar:
504 // no impl. yet
505 break;
506 case Ring:
507 // no impl. yet
508 break;
509 default:
510 Q_ASSERT_X ( false,
511 "Widget::subType", "Chart type not supported!" );
512 break;
513 }
514 return retVal;
515 }
516
517
checkDatasetWidth(int width)518 bool Widget::checkDatasetWidth( int width )
519 {
520 if ( width == diagram()->datasetDimension() )
521 {
522 d->usedDatasetWidth = width;
523 return true;
524 }
525 qDebug() << "The current diagram type doesn't support this data dimension.";
526 return false;
527 /* if ( d->usedDatasetWidth == width || d->usedDatasetWidth == 0 ) {
528 d->usedDatasetWidth = width;
529 diagram()->setDatasetDimension( width );
530 return true;
531 }
532 qDebug() << "It's impossible to mix up the different setDataset() methods on the same widget.";
533 return false;*/
534 }
535
justifyModelSize(int rows,int columns)536 void Widget::justifyModelSize( int rows, int columns )
537 {
538 QAbstractItemModel & model = d->m_model;
539 const int currentRows = model.rowCount();
540 const int currentCols = model.columnCount();
541
542 if ( currentCols < columns )
543 if ( ! model.insertColumns( currentCols, columns - currentCols ))
544 qDebug() << "justifyModelSize: could not increase model size.";
545 if ( currentRows < rows )
546 if ( ! model.insertRows( currentRows, rows - currentRows ))
547 qDebug() << "justifyModelSize: could not increase model size.";
548
549 Q_ASSERT( model.rowCount() >= rows );
550 Q_ASSERT( model.columnCount() >= columns );
551 }
552