File indexing completed on 2024-06-09 04:18:32

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 "KChartPolarCoordinatePlane.h"
0010 #include "KChartPolarCoordinatePlane_p.h"
0011 
0012 #include "KChartPainterSaver_p.h"
0013 #include "KChartChart.h"
0014 #include "KChartPaintContext.h"
0015 #include "KChartAbstractDiagram.h"
0016 #include "KChartAbstractPolarDiagram.h"
0017 #include "KChartPolarDiagram.h"
0018 #include "KChartMath_p.h"
0019 
0020 #include <QFont>
0021 #include <QList>
0022 #include <QtDebug>
0023 #include <QPainter>
0024 #include <QTimer>
0025 
0026 
0027 using namespace KChart;
0028 
0029 #define d d_func()
0030 
0031 PolarCoordinatePlane::PolarCoordinatePlane ( Chart* parent )
0032     : AbstractCoordinatePlane ( new Private(), parent )
0033 {
0034     // this block left empty intentionally
0035 }
0036 
0037 PolarCoordinatePlane::~PolarCoordinatePlane()
0038 {
0039     // this block left empty intentionally
0040 }
0041 
0042 void PolarCoordinatePlane::init()
0043 {
0044     // this block left empty intentionally
0045 }
0046 
0047 void PolarCoordinatePlane::addDiagram ( AbstractDiagram* diagram )
0048 {
0049     Q_ASSERT_X ( dynamic_cast<AbstractPolarDiagram*> ( diagram ),
0050                  "PolarCoordinatePlane::addDiagram", "Only polar"
0051                  "diagrams can be added to a polar coordinate plane!" );
0052     AbstractCoordinatePlane::addDiagram ( diagram );
0053     connect ( diagram,  SIGNAL (layoutChanged(KChart::AbstractDiagram*)),
0054               SLOT (slotLayoutChanged(KChart::AbstractDiagram*)) );
0055 
0056 }
0057 
0058 void PolarCoordinatePlane::paint ( QPainter* painter )
0059 {
0060     AbstractDiagramList diags = diagrams();
0061     if ( d->coordinateTransformations.size() != diags.size() ) {
0062         // diagrams have not been set up yet
0063         return;
0064     }
0065      // need at least one so d->currentTransformation can be a valid pointer
0066     Q_ASSERT( !d->coordinateTransformations.isEmpty() );
0067 
0068     PaintContext ctx;
0069     ctx.setPainter ( painter );
0070     ctx.setCoordinatePlane ( this );
0071     ctx.setRectangle ( geometry() /*d->contentRect*/ );
0072 
0073     // 1. ask (only!) PolarDiagrams if they need additional space for data labels / data comments
0074 
0075     const qreal oldZoomX = zoomFactorX();
0076     const qreal oldZoomY = zoomFactorY();
0077     d->newZoomX = oldZoomX;
0078     d->newZoomY = oldZoomY;
0079     for ( int i = 0; i < diags.size(); i++ ) {
0080         d->currentTransformation = & ( d->coordinateTransformations[i] );
0081         qreal zoomX;
0082         qreal zoomY;
0083         PolarDiagram* polarDia = dynamic_cast<PolarDiagram*> ( diags[i] );
0084         if ( polarDia ) {
0085             polarDia->paint( &ctx, true, zoomX, zoomY );
0086             d->newZoomX = qMin( d->newZoomX, zoomX );
0087             d->newZoomY = qMin( d->newZoomY, zoomY );
0088         }
0089     }
0090 
0091     if ( d->newZoomX != oldZoomX || d->newZoomY != oldZoomY ) {
0092         //qDebug() << "new zoom:" << d->newZoomY << " old zoom:" << oldZoomY;
0093         d->currentTransformation = nullptr; // not painting anymore until we get called again
0094         QMetaObject::invokeMethod( this, "adjustZoomAndRepaint", Qt::QueuedConnection );
0095         return;
0096     }
0097 
0098     // 2. there was room enough for the labels, so start drawing
0099 
0100     // paint the coordinate system rulers:
0101     d->currentTransformation = &d->coordinateTransformations.first();
0102     d->grid->drawGrid( &ctx );
0103 
0104     // paint the diagrams which will re-use their DataValueTextInfoList(s) filled in step 1:
0105     for ( int i = 0; i < diags.size(); i++ ) {
0106         d->currentTransformation = & ( d->coordinateTransformations[i] );
0107         PainterSaver painterSaver( painter );
0108         PolarDiagram* polarDia = dynamic_cast<PolarDiagram*>( diags[i] );
0109         if ( polarDia ) {
0110             qreal dummy1, dummy2;
0111             polarDia->paint( &ctx, false, dummy1, dummy2 );
0112         } else {
0113             diags[i]->paint( &ctx );
0114         }
0115     }
0116     d->currentTransformation = nullptr;
0117 }
0118 
0119 
0120 void PolarCoordinatePlane::adjustZoomAndRepaint()
0121 {
0122     const qreal newZoom = qMin(d->newZoomX, d->newZoomY);
0123     setZoomFactors(newZoom, newZoom);
0124     update();
0125 }
0126 
0127 
0128 void PolarCoordinatePlane::resizeEvent ( QResizeEvent* )
0129 {
0130     d->initialResizeEventReceived = true;
0131     layoutDiagrams();
0132 }
0133 
0134 void PolarCoordinatePlane::layoutDiagrams()
0135 {
0136     // the rectangle the diagrams cover in the *plane*:
0137     // (Why -3? We save 1px on each side for the antialiased drawing, and
0138     // respect the way QPainter calculates the width of a painted rect (the
0139     // size is the rectangle size plus the pen width). This way, most clipping
0140     // for regular pens should be avoided. When pens with a penWidth or larger
0141     // than 1 are used, this may not b sufficient.
0142     const QRect rect( areaGeometry() );
0143     d->contentRect = QRectF ( 1, 1, rect.width() - 3, rect.height() - 3 );
0144 
0145     const ZoomParameters zoom = d->coordinateTransformations.isEmpty() ? ZoomParameters()
0146                                                                        : d->coordinateTransformations.front().zoom;
0147     // FIXME distribute space according to options:
0148     const qreal oldStartPosition = startPosition();
0149     d->coordinateTransformations.clear();
0150     const auto ds = diagrams();
0151     for ( AbstractDiagram* diagram : ds ) {
0152             AbstractPolarDiagram *polarDiagram = dynamic_cast<AbstractPolarDiagram*>( diagram );
0153             Q_ASSERT( polarDiagram );
0154             QPair<QPointF, QPointF> dataBoundariesPair = polarDiagram->dataBoundaries();
0155 
0156             const qreal angleUnit = 360 / polarDiagram->valueTotals();
0157 //qDebug() << "--------------------------------------------------------";
0158             const qreal radius = qAbs( dataBoundariesPair.first.y() ) + dataBoundariesPair.second.y();
0159 //qDebug() << radius <<"="<<dataBoundariesPair.second.y();
0160             const qreal diagramWidth = radius * 2; // == height
0161             const qreal planeWidth = d->contentRect.width();
0162             const qreal planeHeight = d->contentRect.height();
0163             const qreal radiusUnit = qMin( planeWidth, planeHeight ) / diagramWidth;
0164 //qDebug() << radiusUnit <<"=" << "qMin( "<<planeWidth<<","<< planeHeight <<") / "<<diagramWidth;
0165             QPointF coordinateOrigin = QPointF ( planeWidth / 2, planeHeight / 2 );
0166             coordinateOrigin += d->contentRect.topLeft();
0167 
0168             CoordinateTransformation diagramTransposition;
0169             diagramTransposition.originTranslation = coordinateOrigin;
0170             diagramTransposition.radiusUnit = radiusUnit;
0171             diagramTransposition.angleUnit = angleUnit;
0172             diagramTransposition.startPosition = oldStartPosition;
0173             diagramTransposition.zoom = zoom;
0174             diagramTransposition.minValue = dataBoundariesPair.first.y() < 0 ? dataBoundariesPair.first.y() : 0.0;
0175             d->coordinateTransformations.append( diagramTransposition );
0176         }
0177     update();
0178 }
0179 
0180 const QPointF PolarCoordinatePlane::translate( const QPointF& diagramPoint ) const
0181 {
0182     Q_ASSERT_X ( d->currentTransformation != nullptr, "PolarCoordinatePlane::translate",
0183                  "Only call translate() from within paint()." );
0184     return  d->currentTransformation->translate ( diagramPoint );
0185 }
0186 
0187 const QPointF PolarCoordinatePlane::translatePolar( const QPointF& diagramPoint ) const
0188 {
0189     Q_ASSERT_X ( d->currentTransformation != nullptr, "PolarCoordinatePlane::translate",
0190                  "Only call translate() from within paint()." );
0191     return  d->currentTransformation->translatePolar ( diagramPoint );
0192 }
0193 
0194 qreal PolarCoordinatePlane::angleUnit() const
0195 {
0196     Q_ASSERT_X ( d->currentTransformation != nullptr, "PolarCoordinatePlane::angleUnit",
0197                  "Only call angleUnit() from within paint()." );
0198     return  d->currentTransformation->angleUnit;
0199 }
0200 
0201 qreal PolarCoordinatePlane::radiusUnit() const
0202 {
0203     Q_ASSERT_X ( d->currentTransformation != nullptr, "PolarCoordinatePlane::radiusUnit",
0204                  "Only call radiusUnit() from within paint()." );
0205     return  d->currentTransformation->radiusUnit;
0206 }
0207 
0208 void PolarCoordinatePlane::slotLayoutChanged ( AbstractDiagram* )
0209 {
0210     if ( d->initialResizeEventReceived ) layoutDiagrams();
0211 }
0212 
0213 void PolarCoordinatePlane::setStartPosition( qreal degrees )
0214 {
0215     Q_ASSERT_X ( diagram(), "PolarCoordinatePlane::setStartPosition",
0216                  "setStartPosition() needs a diagram to be associated to the plane." );
0217     for ( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin();
0218                                                 it != d->coordinateTransformations.end();
0219                                                 ++it )
0220     {
0221         CoordinateTransformation& trans = *it;
0222         trans.startPosition = degrees;
0223     }
0224 }
0225 
0226 qreal PolarCoordinatePlane::startPosition() const
0227 {
0228     return d->coordinateTransformations.isEmpty()
0229         ? 0.0
0230         :  d->coordinateTransformations.first().startPosition;
0231 }
0232 
0233 qreal PolarCoordinatePlane::zoomFactorX() const
0234 {
0235     return d->coordinateTransformations.isEmpty()
0236         ? 1.0
0237         : d->coordinateTransformations.first().zoom.xFactor;
0238 }
0239 
0240 qreal PolarCoordinatePlane::zoomFactorY() const
0241 {
0242     return d->coordinateTransformations.isEmpty()
0243         ? 1.0
0244         : d->coordinateTransformations.first().zoom.yFactor;
0245 }
0246 
0247 void PolarCoordinatePlane::setZoomFactors( qreal factorX, qreal factorY )
0248 {
0249     setZoomFactorX( factorX );
0250     setZoomFactorY( factorY );
0251 }
0252 
0253 void PolarCoordinatePlane::setZoomFactorX( qreal factor )
0254 {
0255     for ( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin();
0256                                                 it != d->coordinateTransformations.end();
0257                                                 ++it )
0258     {
0259         CoordinateTransformation& trans = *it;
0260         trans.zoom.xFactor = factor;
0261     }
0262 }
0263 
0264 void PolarCoordinatePlane::setZoomFactorY( qreal factor )
0265 {
0266     for ( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin();
0267                                                 it != d->coordinateTransformations.end();
0268                                                 ++it )
0269     {
0270         CoordinateTransformation& trans = *it;
0271         trans.zoom.yFactor = factor;
0272     }
0273 }
0274 
0275 QPointF PolarCoordinatePlane::zoomCenter() const
0276 {
0277     return d->coordinateTransformations.isEmpty()
0278         ? QPointF( 0.5, 0.5 )
0279         : QPointF( d->coordinateTransformations.first().zoom.xCenter, d->coordinateTransformations.first().zoom.yCenter );
0280 }
0281 
0282 void PolarCoordinatePlane::setZoomCenter( const QPointF& center )
0283 {
0284     for ( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin();
0285                                                 it != d->coordinateTransformations.end();
0286                                                 ++it )
0287     {
0288         CoordinateTransformation& trans = *it;
0289         trans.zoom.xCenter = center.x();
0290         trans.zoom.yCenter = center.y();
0291     }
0292 }
0293 
0294 DataDimensionsList PolarCoordinatePlane::getDataDimensionsList() const
0295 {
0296     DataDimensionsList l;
0297 
0298     //FIXME(khz): do the real calculation
0299 
0300     return l;
0301 }
0302 
0303 void KChart::PolarCoordinatePlane::setGridAttributes(
0304     bool circular,
0305     const GridAttributes& a )
0306 {
0307     if ( circular )
0308         d->gridAttributesCircular = a;
0309     else
0310         d->gridAttributesSagittal = a;
0311     setHasOwnGridAttributes( circular, true );
0312     update();
0313     Q_EMIT propertiesChanged();
0314 }
0315 
0316 void KChart::PolarCoordinatePlane::resetGridAttributes(
0317     bool circular )
0318 {
0319     setHasOwnGridAttributes( circular, false );
0320     update();
0321 }
0322 
0323 const GridAttributes KChart::PolarCoordinatePlane::gridAttributes(
0324     bool circular ) const
0325 {
0326     if ( hasOwnGridAttributes( circular ) ) {
0327         if ( circular )
0328             return d->gridAttributesCircular;
0329         else
0330             return d->gridAttributesSagittal;
0331     } else {
0332         return globalGridAttributes();
0333     }
0334 }
0335 
0336 QRectF KChart::PolarCoordinatePlane::Private::contentsRect( const KChart::PolarCoordinatePlane* plane )
0337 {
0338     QRectF contentsRect;
0339     QPointF referencePointAtTop = plane->translate( QPointF( 1, 0 ) );
0340     QPointF temp = plane->translate( QPointF( 0, 0 ) ) - referencePointAtTop;
0341     const qreal offset = temp.y();
0342     referencePointAtTop.setX( referencePointAtTop.x() - offset );
0343     contentsRect.setTopLeft( referencePointAtTop );
0344     contentsRect.setBottomRight( referencePointAtTop + QPointF( 2.0 * offset, 2.0 * offset) );
0345     return contentsRect;
0346 }
0347 
0348 void KChart::PolarCoordinatePlane::setHasOwnGridAttributes(
0349     bool circular, bool on )
0350 {
0351     if ( circular )
0352         d->hasOwnGridAttributesCircular = on;
0353     else
0354         d->hasOwnGridAttributesSagittal = on;
0355     Q_EMIT propertiesChanged();
0356 }
0357 
0358 bool KChart::PolarCoordinatePlane::hasOwnGridAttributes(
0359     bool circular ) const
0360 {
0361     return
0362         ( circular )
0363         ? d->hasOwnGridAttributesCircular
0364         : d->hasOwnGridAttributesSagittal;
0365 }