436 lines
15 KiB
C++
436 lines
15 KiB
C++
/****************************************************************************
|
|
** Copyright (C) 2001-2011 Klaralvdalens Datakonsult AB. All rights reserved.
|
|
**
|
|
** This file is part of the KD Chart library.
|
|
**
|
|
** Licensees holding valid commercial KD Chart licenses may use this file in
|
|
** accordance with the KD Chart Commercial License Agreement provided with
|
|
** the Software.
|
|
**
|
|
**
|
|
** This file may be distributed and/or modified under the terms of the
|
|
** GNU General Public License version 2 and version 3 as published by the
|
|
** Free Software Foundation and appearing in the file LICENSE.GPL.txt included.
|
|
**
|
|
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
|
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
**
|
|
** Contact info@kdab.com if any conditions of this licensing are not
|
|
** clear to you.
|
|
**
|
|
**********************************************************************/
|
|
|
|
#include "KDChartPolarCoordinatePlane.h"
|
|
#include "KDChartPolarCoordinatePlane_p.h"
|
|
|
|
#include "KDChartPainterSaver_p.h"
|
|
#include "KDChartChart.h"
|
|
#include "KDChartPaintContext.h"
|
|
#include "KDChartAbstractDiagram.h"
|
|
#include "KDChartAbstractPolarDiagram.h"
|
|
#include "KDChartPolarDiagram.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include <QFont>
|
|
#include <QList>
|
|
#include <QtDebug>
|
|
#include <QPainter>
|
|
#include <QTimer>
|
|
|
|
#include <KDABLibFakes>
|
|
|
|
using namespace KDChart;
|
|
|
|
#define d d_func()
|
|
|
|
|
|
/*
|
|
#ifndef M_PI
|
|
#define M_PI 3.14159265358979323846
|
|
#endif
|
|
#define DEGTORAD(d) (d)*M_PI/180
|
|
|
|
struct PolarCoordinatePlane::CoordinateTransformation
|
|
{
|
|
// represents the distance of the diagram coordinate origin to the
|
|
// origin of the coordinate plane space:
|
|
QPointF originTranslation;
|
|
double radiusUnit;
|
|
double angleUnit;
|
|
|
|
ZoomParameters zoom;
|
|
|
|
static QPointF polarToCartesian( double R, double theta )
|
|
{
|
|
return QPointF( R * cos( DEGTORAD( theta ) ), R * sin( DEGTORAD( theta ) ) );
|
|
}
|
|
|
|
inline const QPointF translate( const QPointF& diagramPoint ) const
|
|
{
|
|
// calculate the polar coordinates
|
|
const double x = diagramPoint.x() * radiusUnit;
|
|
const double y = ( diagramPoint.y() * angleUnit) - 90;
|
|
// convert to cartesian coordinates
|
|
QPointF cartesianPoint = polarToCartesian( x, y );
|
|
cartesianPoint.setX( cartesianPoint.x() * zoom.xFactor );
|
|
cartesianPoint.setY( cartesianPoint.y() * zoom.yFactor );
|
|
|
|
QPointF newOrigin = originTranslation;
|
|
double minOrigin = qMin( newOrigin.x(), newOrigin.y() );
|
|
newOrigin.setX( newOrigin.x() + minOrigin * ( 1 - zoom.xCenter * 2 ) * zoom.xFactor );
|
|
newOrigin.setY( newOrigin.y() + minOrigin * ( 1 - zoom.yCenter * 2 ) * zoom.yFactor );
|
|
|
|
return newOrigin + cartesianPoint;
|
|
}
|
|
|
|
inline const QPointF translatePolar( const QPointF& diagramPoint ) const
|
|
{
|
|
return QPointF( diagramPoint.x() * angleUnit, diagramPoint.y() * radiusUnit );
|
|
}
|
|
};
|
|
|
|
class PolarCoordinatePlane::Private
|
|
{
|
|
public:
|
|
Private()
|
|
:currentTransformation(0),
|
|
initialResizeEventReceived(false )
|
|
{}
|
|
|
|
|
|
// the coordinate plane will calculate coordinate transformations for all
|
|
// diagrams and store them here:
|
|
CoordinateTransformationList coordinateTransformations;
|
|
// when painting, this pointer selects the coordinate transformation for
|
|
// the current diagram:
|
|
CoordinateTransformation* currentTransformation;
|
|
// the reactangle occupied by the diagrams, in plane coordinates
|
|
QRectF contentRect;
|
|
// true after the first resize event came in
|
|
bool initialResizeEventReceived;
|
|
};
|
|
*/
|
|
|
|
PolarCoordinatePlane::PolarCoordinatePlane ( Chart* parent )
|
|
: AbstractCoordinatePlane ( new Private(), parent )
|
|
{
|
|
// this bloc left empty intentionally
|
|
}
|
|
|
|
PolarCoordinatePlane::~PolarCoordinatePlane()
|
|
{
|
|
// this bloc left empty intentionally
|
|
}
|
|
|
|
void PolarCoordinatePlane::init()
|
|
{
|
|
// this bloc left empty intentionally
|
|
}
|
|
|
|
void PolarCoordinatePlane::addDiagram ( AbstractDiagram* diagram )
|
|
{
|
|
Q_ASSERT_X ( dynamic_cast<AbstractPolarDiagram*> ( diagram ),
|
|
"PolarCoordinatePlane::addDiagram", "Only polar"
|
|
"diagrams can be added to a polar coordinate plane!" );
|
|
AbstractCoordinatePlane::addDiagram ( diagram );
|
|
connect ( diagram, SIGNAL ( layoutChanged ( AbstractDiagram* ) ),
|
|
SLOT ( slotLayoutChanged ( AbstractDiagram* ) ) );
|
|
|
|
}
|
|
|
|
void PolarCoordinatePlane::paint ( QPainter* painter )
|
|
{
|
|
AbstractDiagramList diags = diagrams();
|
|
if ( d->coordinateTransformations.size() == diags.size() )
|
|
{
|
|
PaintContext ctx;
|
|
ctx.setPainter ( painter );
|
|
ctx.setCoordinatePlane ( this );
|
|
ctx.setRectangle ( geometry() /*d->contentRect*/ );
|
|
|
|
// 1. ask the diagrams if they need additional space for data labels / data comments
|
|
const qreal oldZoomX = zoomFactorX();
|
|
const qreal oldZoomY = zoomFactorY();
|
|
d->newZoomX = oldZoomX;
|
|
d->newZoomY = oldZoomY;
|
|
for ( int i = 0; i < diags.size(); i++ )
|
|
{
|
|
d->currentTransformation = & ( d->coordinateTransformations[i] );
|
|
qreal zoomX;
|
|
qreal zoomY;
|
|
PolarDiagram* polarDia = dynamic_cast<PolarDiagram*> ( diags[i] );
|
|
if( polarDia ){
|
|
polarDia->paint ( &ctx, true, zoomX, zoomY );
|
|
d->newZoomX = qMin(d->newZoomX, zoomX);
|
|
d->newZoomY = qMin(d->newZoomY, zoomY);
|
|
}
|
|
}
|
|
d->currentTransformation = 0;
|
|
|
|
// if re-scaling is needed start the timer and bail out
|
|
if( d->newZoomX != oldZoomX || d->newZoomY != oldZoomY ){
|
|
//qDebug()<<"new zoom:"<<d->newZoomY<<" old zoom"<<oldZoomY;
|
|
QTimer::singleShot(10, this, SLOT(adjustZoomAndRepaint()));
|
|
return;
|
|
}
|
|
|
|
// 2. there was room enough for the labels, so we start drawing
|
|
|
|
// paint the coordinate system rulers:
|
|
d->currentTransformation = & ( d->coordinateTransformations.first() );
|
|
|
|
d->grid->drawGrid( &ctx );
|
|
|
|
// paint the diagrams which will re-use their DataValueTextInfoList(s) filled in step 1:
|
|
for ( int i = 0; i < diags.size(); i++ )
|
|
{
|
|
d->currentTransformation = & ( d->coordinateTransformations[i] );
|
|
PainterSaver painterSaver( painter );
|
|
PolarDiagram* polarDia = dynamic_cast<PolarDiagram*> ( diags[i] );
|
|
if( polarDia ){
|
|
qreal dummy1, dummy2;
|
|
polarDia->paint ( &ctx, false, dummy1, dummy2 );
|
|
}else{
|
|
diags[i]->paint ( &ctx );
|
|
}
|
|
}
|
|
d->currentTransformation = 0;
|
|
} // else: diagrams have not been set up yet
|
|
}
|
|
|
|
|
|
void PolarCoordinatePlane::adjustZoomAndRepaint()
|
|
{
|
|
const qreal newZoom = qMin(d->newZoomX, d->newZoomY);
|
|
setZoomFactors(newZoom, newZoom);
|
|
update();
|
|
}
|
|
|
|
|
|
void PolarCoordinatePlane::resizeEvent ( QResizeEvent* )
|
|
{
|
|
d->initialResizeEventReceived = true;
|
|
layoutDiagrams();
|
|
}
|
|
|
|
void PolarCoordinatePlane::layoutDiagrams()
|
|
{
|
|
// the rectangle the diagrams cover in the *plane*:
|
|
// (Why -3? We save 1px on each side for the antialiased drawing, and
|
|
// respect the way QPainter calculates the width of a painted rect (the
|
|
// size is the rectangle size plus the pen width). This way, most clipping
|
|
// for regular pens should be avoided. When pens with a penWidth or larger
|
|
// than 1 are used, this may not b sufficient.
|
|
const QRect rect( areaGeometry() );
|
|
d->contentRect = QRectF ( 1, 1, rect.width() - 3, rect.height() - 3 );
|
|
|
|
const ZoomParameters zoom = d->coordinateTransformations.isEmpty() ? ZoomParameters()
|
|
: d->coordinateTransformations.front().zoom;
|
|
// FIXME distribute space according to options:
|
|
const qreal oldStartPosition = startPosition();
|
|
d->coordinateTransformations.clear();
|
|
Q_FOREACH( AbstractDiagram* diagram, diagrams() )
|
|
{
|
|
AbstractPolarDiagram *polarDiagram = dynamic_cast<AbstractPolarDiagram*>( diagram );
|
|
Q_ASSERT( polarDiagram );
|
|
QPair<QPointF, QPointF> dataBoundariesPair = polarDiagram->dataBoundaries();
|
|
|
|
const double angleUnit = 360 / polarDiagram->valueTotals();
|
|
//qDebug() << "--------------------------------------------------------";
|
|
const double radius = qAbs( dataBoundariesPair.first.y() ) + dataBoundariesPair.second.y();
|
|
//qDebug() << radius <<"="<<dataBoundariesPair.second.y();
|
|
const double diagramWidth = radius * 2; // == height
|
|
const double planeWidth = d->contentRect.width();
|
|
const double planeHeight = d->contentRect.height();
|
|
const double radiusUnit = qMin( planeWidth, planeHeight ) / diagramWidth;
|
|
//qDebug() << radiusUnit <<"=" << "qMin( "<<planeWidth<<","<< planeHeight <<") / "<<diagramWidth;
|
|
QPointF coordinateOrigin = QPointF ( planeWidth / 2, planeHeight / 2 );
|
|
coordinateOrigin += d->contentRect.topLeft();
|
|
|
|
CoordinateTransformation diagramTransposition;
|
|
diagramTransposition.originTranslation = coordinateOrigin;
|
|
diagramTransposition.radiusUnit = radiusUnit;
|
|
diagramTransposition.angleUnit = angleUnit;
|
|
diagramTransposition.startPosition = oldStartPosition;
|
|
diagramTransposition.zoom = zoom;
|
|
diagramTransposition.minValue = dataBoundariesPair.first.y() < 0 ? dataBoundariesPair.first.y() : 0.0;
|
|
d->coordinateTransformations.append( diagramTransposition );
|
|
}
|
|
}
|
|
|
|
const QPointF PolarCoordinatePlane::translate( const QPointF& diagramPoint ) const
|
|
{
|
|
Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::translate",
|
|
"Only call translate() from within paint()." );
|
|
return d->currentTransformation->translate ( diagramPoint );
|
|
}
|
|
|
|
const QPointF PolarCoordinatePlane::translatePolar( const QPointF& diagramPoint ) const
|
|
{
|
|
Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::translate",
|
|
"Only call translate() from within paint()." );
|
|
return d->currentTransformation->translatePolar ( diagramPoint );
|
|
}
|
|
|
|
qreal PolarCoordinatePlane::angleUnit() const
|
|
{
|
|
Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::angleUnit",
|
|
"Only call angleUnit() from within paint()." );
|
|
return d->currentTransformation->angleUnit;
|
|
}
|
|
|
|
qreal PolarCoordinatePlane::radiusUnit() const
|
|
{
|
|
Q_ASSERT_X ( d->currentTransformation != 0, "PolarCoordinatePlane::radiusUnit",
|
|
"Only call radiusUnit() from within paint()." );
|
|
return d->currentTransformation->radiusUnit;
|
|
}
|
|
|
|
void PolarCoordinatePlane::slotLayoutChanged ( AbstractDiagram* )
|
|
{
|
|
if ( d->initialResizeEventReceived ) layoutDiagrams();
|
|
}
|
|
|
|
void PolarCoordinatePlane::setStartPosition( qreal degrees )
|
|
{
|
|
Q_ASSERT_X ( diagram(), "PolarCoordinatePlane::setStartPosition",
|
|
"setStartPosition() needs a diagram to be associated to the plane." );
|
|
for( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin();
|
|
it != d->coordinateTransformations.end();
|
|
++it )
|
|
{
|
|
CoordinateTransformation& trans = *it;
|
|
trans.startPosition = degrees;
|
|
}
|
|
}
|
|
|
|
qreal PolarCoordinatePlane::startPosition() const
|
|
{
|
|
return d->coordinateTransformations.isEmpty()
|
|
? 0.0
|
|
: d->coordinateTransformations.first().startPosition;
|
|
}
|
|
|
|
double PolarCoordinatePlane::zoomFactorX() const
|
|
{
|
|
return d->coordinateTransformations.isEmpty()
|
|
? 1.0
|
|
: d->coordinateTransformations.first().zoom.xFactor;
|
|
}
|
|
|
|
double PolarCoordinatePlane::zoomFactorY() const
|
|
{
|
|
return d->coordinateTransformations.isEmpty()
|
|
? 1.0
|
|
: d->coordinateTransformations.first().zoom.yFactor;
|
|
}
|
|
|
|
void PolarCoordinatePlane::setZoomFactors( double factorX, double factorY )
|
|
{
|
|
setZoomFactorX( factorX );
|
|
setZoomFactorY( factorY );
|
|
}
|
|
|
|
void PolarCoordinatePlane::setZoomFactorX( double factor )
|
|
{
|
|
for( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin();
|
|
it != d->coordinateTransformations.end();
|
|
++it )
|
|
{
|
|
CoordinateTransformation& trans = *it;
|
|
trans.zoom.xFactor = factor;
|
|
}
|
|
}
|
|
|
|
void PolarCoordinatePlane::setZoomFactorY( double factor )
|
|
{
|
|
for( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin();
|
|
it != d->coordinateTransformations.end();
|
|
++it )
|
|
{
|
|
CoordinateTransformation& trans = *it;
|
|
trans.zoom.yFactor = factor;
|
|
}
|
|
}
|
|
|
|
QPointF PolarCoordinatePlane::zoomCenter() const
|
|
{
|
|
return d->coordinateTransformations.isEmpty()
|
|
? QPointF( 0.5, 0.5 )
|
|
: QPointF( d->coordinateTransformations.first().zoom.xCenter, d->coordinateTransformations.first().zoom.yCenter );
|
|
}
|
|
|
|
void PolarCoordinatePlane::setZoomCenter( const QPointF& center )
|
|
{
|
|
for( CoordinateTransformationList::iterator it = d->coordinateTransformations.begin();
|
|
it != d->coordinateTransformations.end();
|
|
++it )
|
|
{
|
|
CoordinateTransformation& trans = *it;
|
|
trans.zoom.xCenter = center.x();
|
|
trans.zoom.yCenter = center.y();
|
|
}
|
|
}
|
|
|
|
DataDimensionsList PolarCoordinatePlane::getDataDimensionsList() const
|
|
{
|
|
DataDimensionsList l;
|
|
|
|
//FIXME(khz): do the real calculation
|
|
|
|
return l;
|
|
}
|
|
|
|
void KDChart::PolarCoordinatePlane::setGridAttributes(
|
|
bool circular,
|
|
const GridAttributes& a )
|
|
{
|
|
if( circular )
|
|
d->gridAttributesCircular = a;
|
|
else
|
|
d->gridAttributesSagittal = a;
|
|
setHasOwnGridAttributes( circular, true );
|
|
update();
|
|
emit propertiesChanged();
|
|
}
|
|
|
|
void KDChart::PolarCoordinatePlane::resetGridAttributes(
|
|
bool circular )
|
|
{
|
|
setHasOwnGridAttributes( circular, false );
|
|
update();
|
|
}
|
|
|
|
const GridAttributes KDChart::PolarCoordinatePlane::gridAttributes(
|
|
bool circular ) const
|
|
{
|
|
if( hasOwnGridAttributes( circular ) ){
|
|
if( circular )
|
|
return d->gridAttributesCircular;
|
|
else
|
|
return d->gridAttributesSagittal;
|
|
}else{
|
|
return globalGridAttributes();
|
|
}
|
|
}
|
|
|
|
void KDChart::PolarCoordinatePlane::setHasOwnGridAttributes(
|
|
bool circular, bool on )
|
|
{
|
|
if( circular )
|
|
d->hasOwnGridAttributesCircular = on;
|
|
else
|
|
d->hasOwnGridAttributesSagittal = on;
|
|
emit propertiesChanged();
|
|
}
|
|
|
|
bool KDChart::PolarCoordinatePlane::hasOwnGridAttributes(
|
|
bool circular ) const
|
|
{
|
|
return
|
|
( circular )
|
|
? d->hasOwnGridAttributesCircular
|
|
: d->hasOwnGridAttributesSagittal;
|
|
}
|