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 }