File indexing completed on 2024-05-12 04:20:37
0001 /* 0002 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. 0003 * 0004 * This file is part of the KD Chart library. 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include <KChartWidget.h> 0010 #include <KChartWidget_p.h> 0011 0012 #include <KChartAbstractDiagram.h> 0013 #include <KChartBarDiagram.h> 0014 #include <KChartChart.h> 0015 #include <KChartAbstractCoordinatePlane.h> 0016 #include <KChartLineDiagram.h> 0017 #include <KChartPlotter.h> 0018 #include <KChartPieDiagram.h> 0019 #include <KChartPolarDiagram.h> 0020 #include <KChartRingDiagram.h> 0021 #include <KChartLegend.h> 0022 #include "KChartMath_p.h" 0023 0024 #include <QDebug> 0025 0026 #define d d_func() 0027 0028 using namespace KChart; 0029 0030 Widget::Private::Private( Widget * qq ) 0031 : q( qq ), 0032 layout( q ), 0033 m_model( q ), 0034 m_chart( q ), 0035 m_cartPlane( &m_chart ), 0036 m_polPlane( &m_chart ), 0037 usedDatasetWidth( 0 ) 0038 { 0039 KDAB_SET_OBJECT_NAME( layout ); 0040 KDAB_SET_OBJECT_NAME( m_model ); 0041 KDAB_SET_OBJECT_NAME( m_chart ); 0042 0043 layout.addWidget( &m_chart ); 0044 } 0045 0046 Widget::Private::~Private() {} 0047 0048 0049 0050 Widget::Widget( QWidget* parent ) : 0051 QWidget(parent), _d( new Private( this ) ) 0052 { 0053 // as default we have a cartesian coordinate plane ... 0054 // ... and a line diagram 0055 setType( Line ); 0056 } 0057 0058 Widget::~Widget() 0059 { 0060 delete _d; _d = nullptr; 0061 } 0062 0063 void Widget::init() 0064 { 0065 } 0066 0067 void Widget::setDataset( int column, const QVector< qreal > & data, const QString& title ) 0068 { 0069 if ( ! checkDatasetWidth( 1 ) ) 0070 return; 0071 0072 QStandardItemModel & model = d->m_model; 0073 0074 justifyModelSize( data.size(), column + 1 ); 0075 0076 for ( int i = 0; i < data.size(); ++i ) 0077 { 0078 const QModelIndex index = model.index( i, column ); 0079 model.setData( index, QVariant( data[i] ), Qt::DisplayRole ); 0080 } 0081 if ( ! title.isEmpty() ) 0082 model.setHeaderData( column, Qt::Horizontal, QVariant( title ) ); 0083 } 0084 0085 void Widget::setDataset( int column, const QVector< QPair< qreal, qreal > > & data, const QString& title ) 0086 { 0087 if ( ! checkDatasetWidth( 2 )) 0088 return; 0089 0090 QStandardItemModel & model = d->m_model; 0091 0092 justifyModelSize( data.size(), (column + 1) * 2 ); 0093 0094 for ( int i = 0; i < data.size(); ++i ) 0095 { 0096 QModelIndex index = model.index( i, column * 2 ); 0097 model.setData( index, QVariant( data[i].first ), Qt::DisplayRole ); 0098 0099 index = model.index( i, column * 2 + 1 ); 0100 model.setData( index, QVariant( data[i].second ), Qt::DisplayRole ); 0101 } 0102 if ( ! title.isEmpty() ) { 0103 model.setHeaderData( column, Qt::Horizontal, QVariant( title ) ); 0104 } 0105 } 0106 0107 void Widget::setDataCell( int row, int column, qreal data ) 0108 { 0109 if ( ! checkDatasetWidth( 1 ) ) 0110 return; 0111 0112 QStandardItemModel & model = d->m_model; 0113 0114 justifyModelSize( row + 1, column + 1 ); 0115 0116 const QModelIndex index = model.index( row, column ); 0117 model.setData( index, QVariant( data ), Qt::DisplayRole ); 0118 } 0119 0120 void Widget::setDataCell( int row, int column, QPair< qreal, qreal > data ) 0121 { 0122 if ( ! checkDatasetWidth( 2 )) 0123 return; 0124 0125 QStandardItemModel & model = d->m_model; 0126 0127 justifyModelSize( row + 1, (column + 1) * 2 ); 0128 0129 QModelIndex index = model.index( row, column * 2 ); 0130 model.setData( index, QVariant( data.first ), Qt::DisplayRole ); 0131 0132 index = model.index( row, column * 2 + 1 ); 0133 model.setData( index, QVariant( data.second ), Qt::DisplayRole ); 0134 } 0135 0136 /* 0137 * Resets all data. 0138 */ 0139 void Widget::resetData() 0140 { 0141 d->m_model.clear(); 0142 d->usedDatasetWidth = 0; 0143 } 0144 0145 void Widget::setGlobalLeading( int left, int top, int right, int bottom ) 0146 { 0147 d->m_chart.setGlobalLeading( left, top, right, bottom ); 0148 } 0149 0150 void Widget::setGlobalLeadingLeft( int leading ) 0151 { 0152 d->m_chart.setGlobalLeadingLeft( leading ); 0153 } 0154 0155 int Widget::globalLeadingLeft() const 0156 { 0157 return d->m_chart.globalLeadingLeft(); 0158 } 0159 0160 void Widget::setGlobalLeadingTop( int leading ) 0161 { 0162 d->m_chart.setGlobalLeadingTop( leading ); 0163 } 0164 0165 int Widget::globalLeadingTop() const 0166 { 0167 return d->m_chart.globalLeadingTop(); 0168 } 0169 0170 void Widget::setGlobalLeadingRight( int leading ) 0171 { 0172 d->m_chart.setGlobalLeadingRight( leading ); 0173 } 0174 0175 int Widget::globalLeadingRight() const 0176 { 0177 return d->m_chart.globalLeadingRight(); 0178 } 0179 0180 void Widget::setGlobalLeadingBottom( int leading ) 0181 { 0182 d->m_chart.setGlobalLeadingBottom( leading ); 0183 } 0184 0185 int Widget::globalLeadingBottom() const 0186 { 0187 return d->m_chart.globalLeadingBottom(); 0188 } 0189 0190 KChart::HeaderFooter* Widget::firstHeaderFooter() 0191 { 0192 return d->m_chart.headerFooter(); 0193 } 0194 0195 QList<KChart::HeaderFooter*> Widget::allHeadersFooters() 0196 { 0197 return d->m_chart.headerFooters(); 0198 } 0199 0200 void Widget::addHeaderFooter( const QString& text, 0201 HeaderFooter::HeaderFooterType type, 0202 Position position) 0203 { 0204 HeaderFooter* newHeader = new HeaderFooter( &d->m_chart ); 0205 newHeader->setType( type ); 0206 newHeader->setPosition( position ); 0207 newHeader->setText( text ); 0208 d->m_chart.addHeaderFooter( newHeader ); // we need this explicit call ! 0209 } 0210 0211 void Widget::addHeaderFooter( HeaderFooter* header ) 0212 { 0213 header->setParent( &d->m_chart ); 0214 d->m_chart.addHeaderFooter( header ); // we need this explicit call ! 0215 } 0216 0217 void Widget::replaceHeaderFooter( HeaderFooter* header, HeaderFooter* oldHeader ) 0218 { 0219 header->setParent( &d->m_chart ); 0220 d->m_chart.replaceHeaderFooter( header, oldHeader ); 0221 } 0222 0223 void Widget::takeHeaderFooter( HeaderFooter* header ) 0224 { 0225 d->m_chart.takeHeaderFooter( header ); 0226 } 0227 0228 KChart::Legend* Widget::legend() 0229 { 0230 return d->m_chart.legend(); 0231 } 0232 0233 QList<KChart::Legend*> Widget::allLegends() 0234 { 0235 return d->m_chart.legends(); 0236 } 0237 0238 void Widget::addLegend( Position position ) 0239 { 0240 Legend* legend = new Legend( diagram(), &d->m_chart ); 0241 legend->setPosition( position ); 0242 d->m_chart.addLegend( legend ); 0243 } 0244 0245 void Widget::addLegend( Legend* legend ) 0246 { 0247 legend->setDiagram( diagram() ); 0248 legend->setParent( &d->m_chart ); 0249 d->m_chart.addLegend( legend ); 0250 } 0251 0252 void Widget::replaceLegend( Legend* legend, Legend* oldLegend ) 0253 { 0254 legend->setDiagram( diagram() ); 0255 legend->setParent( &d->m_chart ); 0256 d->m_chart.replaceLegend( legend, oldLegend ); 0257 } 0258 0259 void Widget::takeLegend( Legend* legend ) 0260 { 0261 d->m_chart.takeLegend( legend ); 0262 } 0263 0264 AbstractDiagram* Widget::diagram() 0265 { 0266 if ( coordinatePlane() == nullptr ) 0267 qDebug() << "diagram(): coordinatePlane() was NULL"; 0268 0269 return coordinatePlane()->diagram(); 0270 } 0271 0272 BarDiagram* Widget::barDiagram() 0273 { 0274 return dynamic_cast<BarDiagram*>( diagram() ); 0275 } 0276 LineDiagram* Widget::lineDiagram() 0277 { 0278 return dynamic_cast<LineDiagram*>( diagram() ); 0279 } 0280 Plotter* Widget::plotter() 0281 { 0282 return dynamic_cast<Plotter*>( diagram() ); 0283 } 0284 PieDiagram* Widget::pieDiagram() 0285 { 0286 return dynamic_cast<PieDiagram*>( diagram() ); 0287 } 0288 RingDiagram* Widget::ringDiagram() 0289 { 0290 return dynamic_cast<RingDiagram*>( diagram() ); 0291 } 0292 PolarDiagram* Widget::polarDiagram() 0293 { 0294 return dynamic_cast<PolarDiagram*>( diagram() ); 0295 } 0296 0297 AbstractCoordinatePlane* Widget::coordinatePlane() 0298 { 0299 return d->m_chart.coordinatePlane(); 0300 } 0301 0302 static bool isCartesian( KChart::Widget::ChartType type ) 0303 { 0304 return (type == KChart::Widget::Bar) || (type == KChart::Widget::Line); 0305 } 0306 0307 static bool isPolar( KChart::Widget::ChartType type ) 0308 { 0309 return (type == KChart::Widget::Pie) 0310 || (type == KChart::Widget::Ring) 0311 || (type == KChart::Widget::Polar); 0312 } 0313 0314 void Widget::setType( ChartType chartType, SubType chartSubType ) 0315 { 0316 AbstractDiagram* diag = nullptr; 0317 const ChartType oldType = type(); 0318 0319 if ( chartType != oldType ) { 0320 if ( chartType != NoType ) { 0321 if ( isCartesian( chartType ) && ! isCartesian( oldType ) ) 0322 { 0323 if ( coordinatePlane() == &d->m_polPlane ) { 0324 d->m_chart.takeCoordinatePlane( &d->m_polPlane ); 0325 d->m_chart.addCoordinatePlane( &d->m_cartPlane ); 0326 } else { 0327 d->m_chart.replaceCoordinatePlane( &d->m_cartPlane ); 0328 } 0329 } 0330 else if ( isPolar( chartType ) && ! isPolar( oldType ) ) 0331 { 0332 if ( coordinatePlane() == &d->m_cartPlane ) { 0333 d->m_chart.takeCoordinatePlane( &d->m_cartPlane ); 0334 d->m_chart.addCoordinatePlane( &d->m_polPlane ); 0335 } else { 0336 d->m_chart.replaceCoordinatePlane( &d->m_polPlane ); 0337 } 0338 } 0339 } 0340 switch ( chartType ) { 0341 case Bar: 0342 diag = new BarDiagram( &d->m_chart, &d->m_cartPlane ); 0343 break; 0344 case Line: 0345 diag = new LineDiagram( &d->m_chart, &d->m_cartPlane ); 0346 break; 0347 case Plot: 0348 diag = new Plotter( &d->m_chart, &d->m_cartPlane ); 0349 break; 0350 case Pie: 0351 diag = new PieDiagram( &d->m_chart, &d->m_polPlane ); 0352 break; 0353 case Polar: 0354 diag = new PolarDiagram( &d->m_chart, &d->m_polPlane ); 0355 break; 0356 case Ring: 0357 diag = new RingDiagram( &d->m_chart, &d->m_polPlane ); 0358 break; 0359 case NoType: 0360 break; 0361 } 0362 if ( diag != nullptr ) { 0363 if ( isCartesian( oldType ) && isCartesian( chartType ) ) { 0364 AbstractCartesianDiagram *oldDiag = 0365 qobject_cast<AbstractCartesianDiagram*>( coordinatePlane()->diagram() ); 0366 AbstractCartesianDiagram *newDiag = 0367 qobject_cast<AbstractCartesianDiagram*>( diag ); 0368 const auto axes = oldDiag->axes(); 0369 for ( CartesianAxis* axis : axes ) { 0370 oldDiag->takeAxis( axis ); 0371 newDiag->addAxis ( axis ); 0372 } 0373 } 0374 0375 const auto legends = d->m_chart.legends(); 0376 for ( Legend* l : legends ) { 0377 l->setDiagram( diag ); 0378 } 0379 0380 diag->setModel( &d->m_model ); 0381 coordinatePlane()->replaceDiagram( diag ); 0382 0383 //checkDatasetWidth( d->usedDatasetWidth ); 0384 } 0385 //coordinatePlane()->setGridNeedsRecalculate(); 0386 } 0387 0388 if ( chartType != NoType ) { 0389 if ( chartType != oldType || chartSubType != subType() ) 0390 setSubType( chartSubType ); 0391 d->m_chart.resize( size() ); // triggering immediate update 0392 } 0393 } 0394 0395 template< class DiagramType, class Subtype > 0396 void setSubtype( AbstractDiagram *_dia, Subtype st) 0397 { 0398 if ( DiagramType *dia = qobject_cast< DiagramType * >( _dia ) ) { 0399 dia->setType( st ); 0400 } 0401 } 0402 0403 void Widget::setSubType( SubType subType ) 0404 { 0405 // ### at least PieDiagram, PolarDiagram and RingDiagram are unhandled here 0406 0407 AbstractDiagram *dia = diagram(); 0408 switch ( subType ) { 0409 case Normal: 0410 setSubtype< BarDiagram >( dia, BarDiagram::Normal ); 0411 setSubtype< LineDiagram >( dia, LineDiagram::Normal ); 0412 setSubtype< Plotter >( dia, Plotter::Normal ); 0413 break; 0414 case Stacked: 0415 setSubtype< BarDiagram >( dia, BarDiagram::Stacked ); 0416 setSubtype< LineDiagram >( dia, LineDiagram::Stacked ); 0417 // setSubtype< Plotter >( dia, Plotter::Stacked ); 0418 break; 0419 case Percent: 0420 setSubtype< BarDiagram >( dia, BarDiagram::Percent ); 0421 setSubtype< LineDiagram >( dia, LineDiagram::Percent ); 0422 setSubtype< Plotter >( dia, Plotter::Percent ); 0423 break; 0424 case Rows: 0425 setSubtype< BarDiagram >( dia, BarDiagram::Rows ); 0426 break; 0427 default: 0428 Q_ASSERT_X ( false, "Widget::setSubType", "Sub-type not supported!" ); 0429 break; 0430 } 0431 } 0432 0433 Widget::ChartType Widget::type() const 0434 { 0435 // PENDING(christoph) save the type out-of-band: 0436 AbstractDiagram * const dia = const_cast<Widget*>( this )->diagram(); 0437 if ( qobject_cast< BarDiagram* >( dia ) ) 0438 return Bar; 0439 else if ( qobject_cast< LineDiagram* >( dia ) ) 0440 return Line; 0441 else if ( qobject_cast< Plotter* >( dia ) ) 0442 return Plot; 0443 else if ( qobject_cast< PieDiagram* >( dia ) ) 0444 return Pie; 0445 else if ( qobject_cast< PolarDiagram* >( dia ) ) 0446 return Polar; 0447 else if ( qobject_cast< RingDiagram* >( dia ) ) 0448 return Ring; 0449 else 0450 return NoType; 0451 } 0452 0453 Widget::SubType Widget::subType() const 0454 { 0455 // PENDING(christoph) save the type out-of-band: 0456 Widget::SubType retVal = Normal; 0457 0458 AbstractDiagram * const dia = const_cast<Widget*>( this )->diagram(); 0459 BarDiagram* barDia = qobject_cast< BarDiagram* >( dia ); 0460 LineDiagram* lineDia = qobject_cast< LineDiagram* >( dia ); 0461 Plotter* plotterDia = qobject_cast< Plotter* >( dia ); 0462 0463 //FIXME(khz): Add the impl for these chart types - or remove them from here: 0464 // PieDiagram* pieDia = qobject_cast< PieDiagram* >( diagram() ); 0465 // PolarDiagram* polarDia = qobject_cast< PolarDiagram* >( diagram() ); 0466 // RingDiagram* ringDia = qobject_cast< RingDiagram* >( diagram() ); 0467 0468 #define TEST_SUB_TYPE(DIAGRAM, INTERNALSUBTYPE, SUBTYPE) \ 0469 { \ 0470 if ( DIAGRAM && DIAGRAM->type() == INTERNALSUBTYPE ) \ 0471 retVal = SUBTYPE; \ 0472 } 0473 const Widget::ChartType mainType = type(); 0474 switch ( mainType ) 0475 { 0476 case Bar: 0477 TEST_SUB_TYPE( barDia, BarDiagram::Normal, Normal ); 0478 TEST_SUB_TYPE( barDia, BarDiagram::Stacked, Stacked ); 0479 TEST_SUB_TYPE( barDia, BarDiagram::Percent, Percent ); 0480 TEST_SUB_TYPE( barDia, BarDiagram::Rows, Rows ); 0481 break; 0482 case Line: 0483 TEST_SUB_TYPE( lineDia, LineDiagram::Normal, Normal ); 0484 TEST_SUB_TYPE( lineDia, LineDiagram::Stacked, Stacked ); 0485 TEST_SUB_TYPE( lineDia, LineDiagram::Percent, Percent ); 0486 break; 0487 case Plot: 0488 TEST_SUB_TYPE( plotterDia, Plotter::Normal, Normal ); 0489 TEST_SUB_TYPE( plotterDia, Plotter::Percent, Percent ); 0490 break; 0491 case Pie: 0492 // no impl. yet 0493 break; 0494 case Polar: 0495 // no impl. yet 0496 break; 0497 case Ring: 0498 // no impl. yet 0499 break; 0500 default: 0501 Q_ASSERT_X ( false, 0502 "Widget::subType", "Chart type not supported!" ); 0503 break; 0504 } 0505 return retVal; 0506 } 0507 0508 0509 bool Widget::checkDatasetWidth( int width ) 0510 { 0511 if ( width == diagram()->datasetDimension() ) 0512 { 0513 d->usedDatasetWidth = width; 0514 return true; 0515 } 0516 qDebug() << "The current diagram type doesn't support this data dimension."; 0517 return false; 0518 /* if ( d->usedDatasetWidth == width || d->usedDatasetWidth == 0 ) { 0519 d->usedDatasetWidth = width; 0520 diagram()->setDatasetDimension( width ); 0521 return true; 0522 } 0523 qDebug() << "It's impossible to mix up the different setDataset() methods on the same widget."; 0524 return false;*/ 0525 } 0526 0527 void Widget::justifyModelSize( int rows, int columns ) 0528 { 0529 QAbstractItemModel & model = d->m_model; 0530 const int currentRows = model.rowCount(); 0531 const int currentCols = model.columnCount(); 0532 0533 if ( currentCols < columns ) 0534 if ( ! model.insertColumns( currentCols, columns - currentCols )) 0535 qDebug() << "justifyModelSize: could not increase model size."; 0536 if ( currentRows < rows ) 0537 if ( ! model.insertRows( currentRows, rows - currentRows )) 0538 qDebug() << "justifyModelSize: could not increase model size."; 0539 0540 Q_ASSERT( model.rowCount() >= rows ); 0541 Q_ASSERT( model.columnCount() >= columns ); 0542 }