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