File indexing completed on 2024-12-15 04:02:31

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 "KChartLeveyJenningsAxis.h"
0010 #include "KChartLeveyJenningsAxis_p.h"
0011 
0012 #include <QDateTime>
0013 #include <QPainter>
0014 
0015 #include "KChartPaintContext.h"
0016 #include "KChartChart.h"
0017 #include "KChartAbstractCartesianDiagram.h"
0018 #include "KChartAbstractGrid.h"
0019 #include "KChartPainterSaver_p.h"
0020 #include "KChartLayoutItems.h"
0021 #include "KChartPrintingParameters.h"
0022 #include "KChartMath_p.h"
0023 
0024 using namespace KChart;
0025 
0026 #define d (d_func())
0027 
0028 LeveyJenningsAxis::LeveyJenningsAxis ( LeveyJenningsDiagram* diagram )
0029     : CartesianAxis ( new Private( diagram, this ), diagram )
0030 {
0031     init();
0032 }
0033 
0034 LeveyJenningsAxis::~LeveyJenningsAxis ()
0035 {
0036     // when we remove the first axis it will unregister itself and
0037     // propagate the next one to the primary, thus the while loop
0038     while ( d->mDiagram ) {
0039         LeveyJenningsDiagram *cd = qobject_cast< LeveyJenningsDiagram* >( d->mDiagram );
0040         cd->takeAxis( this );
0041     }
0042     for ( AbstractDiagram *diagram : qAsConst(d->secondaryDiagrams) ) {
0043         LeveyJenningsDiagram *cd = qobject_cast< LeveyJenningsDiagram* >( diagram );
0044         cd->takeAxis( this );
0045     }
0046 }
0047 
0048 void LeveyJenningsAxis::init ()
0049 {
0050     setType( LeveyJenningsGridAttributes::Expected );
0051     setDateFormat( Qt::TextDate );
0052     const QStringList labels = QStringList() << tr( "-3sd" ) << tr( "-2sd" ) << tr( "mean" )
0053                                              << tr( "+2sd" ) << tr( "+3sd" );
0054 
0055     setLabels( labels );
0056 }
0057 
0058 LeveyJenningsGridAttributes::GridType LeveyJenningsAxis::type() const
0059 {
0060     return d->type;
0061 }
0062 
0063 void LeveyJenningsAxis::setType( LeveyJenningsGridAttributes::GridType type )
0064 {
0065     if ( type != d->type )
0066     {
0067         TextAttributes ta = textAttributes();
0068         QPen pen = ta.pen();
0069         QColor color = type == LeveyJenningsGridAttributes::Expected ? Qt::black : Qt::blue;
0070         if ( qobject_cast< const LeveyJenningsDiagram* >( d->diagram() ) && 
0071             qobject_cast< const LeveyJenningsCoordinatePlane* >( d->diagram()->coordinatePlane() ) )
0072         {
0073             color = qobject_cast< const LeveyJenningsCoordinatePlane* >( d->diagram()->coordinatePlane() )->gridAttributes().gridPen( type ).color();
0074         }
0075         pen.setColor( color );
0076         ta.setPen( pen );
0077         setTextAttributes( ta );
0078     }
0079     d->type = type;
0080 }
0081 
0082 Qt::DateFormat LeveyJenningsAxis::dateFormat() const
0083 {
0084     return d->format;
0085 }
0086 
0087 void LeveyJenningsAxis::setDateFormat(Qt::DateFormat format)
0088 {
0089     d->format = format;
0090 }
0091 
0092 bool LeveyJenningsAxis::compare( const LeveyJenningsAxis* other ) const
0093 {
0094     if ( other == this ) return true;
0095     if ( ! other ) {
0096         //qDebug() << "CartesianAxis::compare() cannot compare to Null pointer";
0097         return false;
0098     }
0099     return  ( static_cast<const CartesianAxis*>(this)->compare( other ) ) &&
0100             ( type() == other->type() );
0101 }
0102 
0103 void LeveyJenningsAxis::paintCtx( PaintContext* context )
0104 {
0105 
0106     Q_ASSERT_X ( d->diagram(), "LeveyJenningsAxis::paint",
0107                  "Function call not allowed: The axis is not assigned to any diagram." );
0108 
0109     LeveyJenningsCoordinatePlane* plane = dynamic_cast<LeveyJenningsCoordinatePlane*>(context->coordinatePlane());
0110     Q_ASSERT_X ( plane, "LeveyJenningsAxis::paint",
0111                  "Bad function call: PaintContext::coodinatePlane() NOT a levey jennings plane." );
0112     Q_UNUSED(plane);
0113     // note: Not having any data model assigned is no bug
0114     //       but we can not draw an axis then either.
0115     if ( ! d->diagram()->model() )
0116         return;
0117 
0118     if ( isOrdinate() )
0119         paintAsOrdinate( context );
0120     else
0121         paintAsAbscissa( context );
0122 }
0123 
0124 void LeveyJenningsAxis::paintAsOrdinate( PaintContext* context )
0125 {
0126     const LeveyJenningsDiagram* const diag = dynamic_cast< const LeveyJenningsDiagram* >( d->diagram() );
0127 
0128     Q_ASSERT( isOrdinate() );
0129     LeveyJenningsCoordinatePlane* plane = dynamic_cast<LeveyJenningsCoordinatePlane*>(context->coordinatePlane());
0130     
0131     const qreal meanValue =         type() == LeveyJenningsGridAttributes::Expected ? diag->expectedMeanValue() 
0132                                                                                     : diag->calculatedMeanValue();
0133     const qreal standardDeviation = type() == LeveyJenningsGridAttributes::Expected ? diag->expectedStandardDeviation() 
0134                                                                                     : diag->calculatedStandardDeviation();
0135     const TextAttributes labelTA = textAttributes();
0136     const bool drawLabels = labelTA.isVisible();
0137 
0138     // nothing to draw, since we've no ticks
0139     if ( !drawLabels )
0140         return;
0141     
0142     const QObject* referenceArea = plane->parent();
0143 
0144     const QVector< qreal > values = QVector< qreal >() << ( meanValue - 3 * standardDeviation )
0145                                                        << ( meanValue - 2 * standardDeviation )
0146                                                        << ( meanValue )
0147                                                        << ( meanValue + 2 * standardDeviation )
0148                                                        << ( meanValue + 3 * standardDeviation );
0149 
0150     Q_ASSERT_X( values.count() <= labels().count(), "LeveyJenningsAxis::paintAsOrdinate", "Need to have at least 5 labels" );
0151 
0152     TextLayoutItem labelItem( tr( "mean" ), 
0153                               labelTA,
0154                               referenceArea,
0155                               KChartEnums::MeasureOrientationMinimum,
0156                               Qt::AlignLeft );
0157 
0158     QPainter* const painter = context->painter();
0159     const PainterSaver ps( painter );
0160     painter->setRenderHint( QPainter::Antialiasing, true );
0161     painter->setClipping( false );
0162     
0163     painter->setPen ( PrintingParameters::scalePen( labelTA.pen() ) ); // perhaps we want to add a setter method later?
0164 
0165     for ( int i = 0; i < values.count(); ++i )
0166     {
0167         const QPointF labelPos = plane->translate( QPointF( 0.0, values.at( i ) ) );
0168         const QString label = customizedLabel( labels().at( i ) );
0169         labelItem.setText( label );
0170         const QSize size = labelItem.sizeHint();
0171         const float xPos = position() == Left ? geometry().right() - size.width() : geometry().left();
0172         labelItem.setGeometry( QRectF( QPointF( xPos, labelPos.y() - size.height() / 2.0 ), size ).toRect() );
0173 
0174         // don't draw labels which aren't in the valid range (might happen for calculated SDs)
0175         if ( values.at( i ) > diag->expectedMeanValue() + 4 * diag->expectedStandardDeviation() )
0176             continue;
0177 
0178         if ( values.at( i ) < diag->expectedMeanValue() - 4 * diag->expectedStandardDeviation() )
0179             continue;
0180 
0181         labelItem.paint( painter );
0182     }    
0183 }
0184 
0185 void LeveyJenningsAxis::paintAsAbscissa( PaintContext* context )
0186 {
0187     Q_ASSERT( isAbscissa() );
0188 
0189     // this triggers drawing of the ticks
0190     setLabels( QStringList() << QString::fromLatin1( " " ) );
0191     CartesianAxis::paintCtx( context );
0192 
0193     const LeveyJenningsDiagram* const diag = dynamic_cast< const LeveyJenningsDiagram* >( d->diagram() );
0194     LeveyJenningsCoordinatePlane* plane = dynamic_cast<LeveyJenningsCoordinatePlane*>(context->coordinatePlane());
0195 
0196     const QObject* referenceArea = plane->parent();
0197     const TextAttributes labelTA = textAttributes();
0198     
0199     const bool drawLabels = labelTA.isVisible();
0200 
0201     if ( !drawLabels )
0202         return;
0203 
0204 
0205     const QPair< QDateTime, QDateTime > range = diag->timeRange();
0206 
0207     QPainter* const painter = context->painter();
0208     const PainterSaver ps( painter );
0209     painter->setRenderHint( QPainter::Antialiasing, true );
0210     painter->setClipping( false );
0211      
0212 
0213     TextLayoutItem labelItem( range.first.date().toString( dateFormat() ), 
0214                               labelTA,
0215                               referenceArea,
0216                               KChartEnums::MeasureOrientationMinimum,
0217                               Qt::AlignLeft );
0218     QSize origSize = labelItem.sizeHint();
0219     if ( range.first.secsTo( range.second ) < 86400 )
0220         labelItem = TextLayoutItem( range.first.toString( dateFormat() ), 
0221                                   labelTA,
0222                                   referenceArea,
0223                                   KChartEnums::MeasureOrientationMinimum,
0224                                   Qt::AlignLeft );
0225     QSize size = labelItem.sizeHint();
0226 
0227     float yPos = position() == Bottom ? geometry().bottom() - size.height() : geometry().top();
0228     labelItem.setGeometry( QRectF( QPointF( geometry().left() - origSize.width() / 2.0, yPos ), size ).toRect() );
0229     labelItem.paint( painter );
0230 
0231     
0232     TextLayoutItem labelItem2( range.second.date().toString( dateFormat() ), 
0233                               labelTA,
0234                               referenceArea,
0235                               KChartEnums::MeasureOrientationMinimum,
0236                               Qt::AlignLeft );
0237     origSize = labelItem2.sizeHint();
0238     if ( range.first.secsTo( range.second ) < 86400 )
0239         labelItem2 = TextLayoutItem( range.second.toString( dateFormat() ), 
0240                                      labelTA,
0241                                      referenceArea,
0242                                      KChartEnums::MeasureOrientationMinimum,
0243                                      Qt::AlignLeft );
0244     size = labelItem2.sizeHint();
0245     yPos = position() == Bottom ? geometry().bottom() - size.height() : geometry().top();
0246     labelItem2.setGeometry( QRectF( QPointF( geometry().right() - size.width() + origSize.width() / 2.0, yPos ), size ).toRect() );
0247     labelItem2.paint( painter );
0248 }