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 }