You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1363 lines
34 KiB
C++
1363 lines
34 KiB
C++
/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
|
|
* QwtPolar Widget Library
|
|
* Copyright (C) 2008 Uwe Rathmann
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the Qwt License, Version 1.0
|
|
*****************************************************************************/
|
|
|
|
#include "qwt_polar_plot.h"
|
|
#include "qwt_polar_canvas.h"
|
|
#include "qwt_polar_layout.h"
|
|
#include <qwt_painter.h>
|
|
#include <qwt_scale_engine.h>
|
|
#include <qwt_scale_div.h>
|
|
#include <qwt_text_label.h>
|
|
#include <qwt_round_scale_draw.h>
|
|
#include <qwt_legend.h>
|
|
#include <qwt_dyngrid_layout.h>
|
|
#include <qpointer.h>
|
|
#include <qpaintengine.h>
|
|
#include <qpainter.h>
|
|
#include <qevent.h>
|
|
|
|
static inline double qwtDistance(
|
|
const QPointF &p1, const QPointF &p2 )
|
|
{
|
|
double dx = p2.x() - p1.x();
|
|
double dy = p2.y() - p1.y();
|
|
return qSqrt( dx * dx + dy * dy );
|
|
}
|
|
|
|
class QwtPolarPlot::ScaleData
|
|
{
|
|
public:
|
|
ScaleData():
|
|
isValid( false ),
|
|
scaleEngine( NULL )
|
|
{
|
|
}
|
|
|
|
~ScaleData()
|
|
{
|
|
delete scaleEngine;
|
|
}
|
|
|
|
bool doAutoScale;
|
|
|
|
double minValue;
|
|
double maxValue;
|
|
double stepSize;
|
|
|
|
int maxMajor;
|
|
int maxMinor;
|
|
|
|
bool isValid;
|
|
|
|
QwtScaleDiv scaleDiv;
|
|
QwtScaleEngine *scaleEngine;
|
|
};
|
|
|
|
class QwtPolarPlot::PrivateData
|
|
{
|
|
public:
|
|
QBrush canvasBrush;
|
|
|
|
bool autoReplot;
|
|
|
|
QwtPointPolar zoomPos;
|
|
double zoomFactor;
|
|
|
|
ScaleData scaleData[QwtPolar::ScaleCount];
|
|
QPointer<QwtTextLabel> titleLabel;
|
|
QPointer<QwtPolarCanvas> canvas;
|
|
QPointer<QwtAbstractLegend> legend;
|
|
double azimuthOrigin;
|
|
|
|
QwtPolarLayout *layout;
|
|
};
|
|
|
|
/*!
|
|
Constructor
|
|
\param parent Parent widget
|
|
*/
|
|
QwtPolarPlot::QwtPolarPlot( QWidget *parent ):
|
|
QFrame( parent )
|
|
{
|
|
initPlot( QwtText() );
|
|
}
|
|
|
|
/*!
|
|
Constructor
|
|
\param title Title text
|
|
\param parent Parent widget
|
|
*/
|
|
QwtPolarPlot::QwtPolarPlot( const QwtText &title, QWidget *parent ):
|
|
QFrame( parent )
|
|
{
|
|
initPlot( title );
|
|
}
|
|
|
|
//! Destructor
|
|
QwtPolarPlot::~QwtPolarPlot()
|
|
{
|
|
detachItems( QwtPolarItem::Rtti_PolarItem, autoDelete() );
|
|
|
|
delete d_data->layout;
|
|
delete d_data;
|
|
}
|
|
|
|
/*!
|
|
Change the plot's title
|
|
\param title New title
|
|
*/
|
|
void QwtPolarPlot::setTitle( const QString &title )
|
|
{
|
|
if ( title != d_data->titleLabel->text().text() )
|
|
{
|
|
d_data->titleLabel->setText( title );
|
|
if ( !title.isEmpty() )
|
|
d_data->titleLabel->show();
|
|
else
|
|
d_data->titleLabel->hide();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Change the plot's title
|
|
\param title New title
|
|
*/
|
|
void QwtPolarPlot::setTitle( const QwtText &title )
|
|
{
|
|
if ( title != d_data->titleLabel->text() )
|
|
{
|
|
d_data->titleLabel->setText( title );
|
|
if ( !title.isEmpty() )
|
|
d_data->titleLabel->show();
|
|
else
|
|
d_data->titleLabel->hide();
|
|
}
|
|
}
|
|
|
|
//! \return the plot's title
|
|
QwtText QwtPolarPlot::title() const
|
|
{
|
|
return d_data->titleLabel->text();
|
|
}
|
|
|
|
//! \return the plot's title
|
|
QwtTextLabel *QwtPolarPlot::titleLabel()
|
|
{
|
|
return d_data->titleLabel;
|
|
}
|
|
|
|
//! \return the plot's titel label.
|
|
const QwtTextLabel *QwtPolarPlot::titleLabel() const
|
|
{
|
|
return d_data->titleLabel;
|
|
}
|
|
|
|
/*!
|
|
\brief Insert a legend
|
|
|
|
If the position legend is \c QwtPolarPlot::LeftLegend or \c QwtPolarPlot::RightLegend
|
|
the legend will be organized in one column from top to down.
|
|
Otherwise the legend items will be placed in a table
|
|
with a best fit number of columns from left to right.
|
|
|
|
If pos != QwtPolarPlot::ExternalLegend the plot widget will become
|
|
parent of the legend. It will be deleted when the plot is deleted,
|
|
or another legend is set with insertLegend().
|
|
|
|
\param legend Legend
|
|
\param pos The legend's position. For top/left position the number
|
|
of colums will be limited to 1, otherwise it will be set to
|
|
unlimited.
|
|
|
|
\param ratio Ratio between legend and the bounding rect
|
|
of title, canvas and axes. The legend will be shrinked
|
|
if it would need more space than the given ratio.
|
|
The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0
|
|
it will be reset to the default ratio.
|
|
The default vertical/horizontal ratio is 0.33/0.5.
|
|
|
|
\sa legend(), QwtPolarLayout::legendPosition(),
|
|
QwtPolarLayout::setLegendPosition()
|
|
*/
|
|
void QwtPolarPlot::insertLegend( QwtAbstractLegend *legend,
|
|
QwtPolarPlot::LegendPosition pos, double ratio )
|
|
{
|
|
d_data->layout->setLegendPosition( pos, ratio );
|
|
|
|
if ( legend != d_data->legend )
|
|
{
|
|
if ( d_data->legend && d_data->legend->parent() == this )
|
|
delete d_data->legend;
|
|
|
|
d_data->legend = legend;
|
|
|
|
if ( d_data->legend )
|
|
{
|
|
connect( this,
|
|
SIGNAL( legendDataChanged(
|
|
const QVariant &, const QList<QwtLegendData> & ) ),
|
|
d_data->legend,
|
|
SLOT( updateLegend(
|
|
const QVariant &, const QList<QwtLegendData> & ) )
|
|
);
|
|
|
|
if ( d_data->legend->parent() != this )
|
|
d_data->legend->setParent( this );
|
|
|
|
updateLegend();
|
|
|
|
QwtLegend *lgd = qobject_cast<QwtLegend *>( legend );
|
|
if ( lgd )
|
|
{
|
|
switch ( d_data->layout->legendPosition() )
|
|
{
|
|
case LeftLegend:
|
|
case RightLegend:
|
|
{
|
|
if ( lgd->maxColumns() == 0 )
|
|
lgd->setMaxColumns( 1 ); // 1 column: align vertical
|
|
break;
|
|
}
|
|
case TopLegend:
|
|
case BottomLegend:
|
|
{
|
|
lgd->setMaxColumns( 0 ); // unlimited
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
updateLayout();
|
|
}
|
|
|
|
/*!
|
|
Emit legendDataChanged() for all plot item
|
|
|
|
\sa QwtPlotItem::legendData(), legendDataChanged()
|
|
*/
|
|
void QwtPolarPlot::updateLegend()
|
|
{
|
|
const QwtPolarItemList& itmList = itemList();
|
|
for ( QwtPolarItemIterator it = itmList.begin();
|
|
it != itmList.end(); ++it )
|
|
{
|
|
updateLegend( *it );
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Emit legendDataChanged() for a plot item
|
|
|
|
\param plotItem Plot item
|
|
\sa QwtPlotItem::legendData(), legendDataChanged()
|
|
*/
|
|
void QwtPolarPlot::updateLegend( const QwtPolarItem *plotItem )
|
|
{
|
|
if ( plotItem == NULL )
|
|
return;
|
|
|
|
QList<QwtLegendData> legendData;
|
|
|
|
if ( plotItem->testItemAttribute( QwtPolarItem::Legend ) )
|
|
legendData = plotItem->legendData();
|
|
|
|
const QVariant itemInfo = itemToInfo( const_cast< QwtPolarItem *>( plotItem) );
|
|
Q_EMIT legendDataChanged( itemInfo, legendData );
|
|
}
|
|
|
|
/*!
|
|
\return the plot's legend
|
|
\sa insertLegend()
|
|
*/
|
|
QwtAbstractLegend *QwtPolarPlot::legend()
|
|
{
|
|
return d_data->legend;
|
|
}
|
|
|
|
/*!
|
|
\return the plot's legend
|
|
\sa insertLegend()
|
|
*/
|
|
const QwtAbstractLegend *QwtPolarPlot::legend() const
|
|
{
|
|
return d_data->legend;
|
|
}
|
|
|
|
/*!
|
|
\brief Set the background of the plot area
|
|
|
|
The plot area is the circle around the pole. It's radius
|
|
is defined by the radial scale.
|
|
|
|
\param brush Background Brush
|
|
\sa plotBackground(), plotArea()
|
|
*/
|
|
void QwtPolarPlot::setPlotBackground( const QBrush &brush )
|
|
{
|
|
if ( brush != d_data->canvasBrush )
|
|
{
|
|
d_data->canvasBrush = brush;
|
|
autoRefresh();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\return plot background brush
|
|
\sa plotBackground(), plotArea()
|
|
*/
|
|
const QBrush &QwtPolarPlot::plotBackground() const
|
|
{
|
|
return d_data->canvasBrush;
|
|
}
|
|
|
|
/*!
|
|
\brief Set or reset the autoReplot option
|
|
|
|
If the autoReplot option is set, the plot will be
|
|
updated implicitly by manipulating member functions.
|
|
Since this may be time-consuming, it is recommended
|
|
to leave this option switched off and call replot()
|
|
explicitly if necessary.
|
|
|
|
The autoReplot option is set to false by default, which
|
|
means that the user has to call replot() in order to make
|
|
changes visible.
|
|
\param enable \c true or \c false. Defaults to \c true.
|
|
\sa replot()
|
|
*/
|
|
void QwtPolarPlot::setAutoReplot( bool enable )
|
|
{
|
|
d_data->autoReplot = enable;
|
|
}
|
|
|
|
//! \return true if the autoReplot option is set.
|
|
bool QwtPolarPlot::autoReplot() const
|
|
{
|
|
return d_data->autoReplot;
|
|
}
|
|
|
|
/*!
|
|
\brief Enable autoscaling
|
|
|
|
This member function is used to switch back to autoscaling mode
|
|
after a fixed scale has been set. Autoscaling calculates a useful
|
|
scale division from the bounding interval of all plot items with
|
|
the QwtPolarItem::AutoScale attribute.
|
|
|
|
Autoscaling is only supported for the radial scale and enabled as default.
|
|
|
|
\param scaleId Scale index
|
|
|
|
\sa hasAutoScale(), setScale(), setScaleDiv(),
|
|
QwtPolarItem::boundingInterval()
|
|
*/
|
|
void QwtPolarPlot::setAutoScale( int scaleId )
|
|
{
|
|
if ( scaleId != QwtPolar::ScaleRadius )
|
|
return;
|
|
|
|
ScaleData &scaleData = d_data->scaleData[scaleId];
|
|
if ( !scaleData.doAutoScale )
|
|
{
|
|
scaleData.doAutoScale = true;
|
|
autoRefresh();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\return \c true if autoscaling is enabled
|
|
\param scaleId Scale index
|
|
\sa setAutoScale()
|
|
*/
|
|
bool QwtPolarPlot::hasAutoScale( int scaleId ) const
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return false;
|
|
|
|
return d_data->scaleData[scaleId].doAutoScale;
|
|
}
|
|
|
|
/*!
|
|
Set the maximum number of major scale intervals for a specified scale
|
|
|
|
\param scaleId Scale index
|
|
\param maxMinor maximum number of minor steps
|
|
\sa scaleMaxMajor()
|
|
*/
|
|
void QwtPolarPlot::setScaleMaxMinor( int scaleId, int maxMinor )
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return;
|
|
|
|
maxMinor = qBound( 0, maxMinor, 100 );
|
|
|
|
ScaleData &scaleData = d_data->scaleData[scaleId];
|
|
|
|
if ( maxMinor != scaleData.maxMinor )
|
|
{
|
|
scaleData.maxMinor = maxMinor;
|
|
scaleData.isValid = false;
|
|
autoRefresh();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\return the maximum number of minor ticks for a specified axis
|
|
\param scaleId Scale index
|
|
\sa setScaleMaxMinor()
|
|
*/
|
|
int QwtPolarPlot::scaleMaxMinor( int scaleId ) const
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return 0;
|
|
|
|
return d_data->scaleData[scaleId].maxMinor;
|
|
}
|
|
|
|
/*!
|
|
Set the maximum number of major scale intervals for a specified scale
|
|
|
|
\param scaleId Scale index
|
|
\param maxMajor maximum number of major steps
|
|
\sa scaleMaxMajor()
|
|
*/
|
|
void QwtPolarPlot::setScaleMaxMajor( int scaleId, int maxMajor )
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return;
|
|
|
|
maxMajor = qBound( 1, maxMajor, 10000 );
|
|
|
|
ScaleData &scaleData = d_data->scaleData[scaleId];
|
|
if ( maxMajor != scaleData.maxMinor )
|
|
{
|
|
scaleData.maxMajor = maxMajor;
|
|
scaleData.isValid = false;
|
|
autoRefresh();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\return the maximum number of major ticks for a specified axis
|
|
\param scaleId Scale index
|
|
|
|
\sa setScaleMaxMajor()
|
|
*/
|
|
int QwtPolarPlot::scaleMaxMajor( int scaleId ) const
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return 0;
|
|
|
|
return d_data->scaleData[scaleId].maxMajor;
|
|
}
|
|
|
|
/*!
|
|
Change the scale engine for an axis
|
|
|
|
\param scaleId Scale index
|
|
\param scaleEngine Scale engine
|
|
|
|
\sa axisScaleEngine()
|
|
*/
|
|
void QwtPolarPlot::setScaleEngine( int scaleId, QwtScaleEngine *scaleEngine )
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return;
|
|
|
|
ScaleData &scaleData = d_data->scaleData[scaleId];
|
|
if ( scaleEngine == NULL || scaleEngine == scaleData.scaleEngine )
|
|
return;
|
|
|
|
delete scaleData.scaleEngine;
|
|
scaleData.scaleEngine = scaleEngine;
|
|
|
|
scaleData.isValid = false;
|
|
|
|
autoRefresh();
|
|
}
|
|
|
|
/*!
|
|
\return Scale engine for a specific scale
|
|
|
|
\param scaleId Scale index
|
|
\sa setScaleEngine()
|
|
*/
|
|
QwtScaleEngine *QwtPolarPlot::scaleEngine( int scaleId )
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return NULL;
|
|
|
|
return d_data->scaleData[scaleId].scaleEngine;
|
|
}
|
|
|
|
/*!
|
|
\return Scale engine for a specific scale
|
|
|
|
\param scaleId Scale index
|
|
\sa setScaleEngine()
|
|
*/
|
|
const QwtScaleEngine *QwtPolarPlot::scaleEngine( int scaleId ) const
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return NULL;
|
|
|
|
return d_data->scaleData[scaleId].scaleEngine;
|
|
}
|
|
|
|
/*!
|
|
\brief Disable autoscaling and specify a fixed scale for a selected scale.
|
|
\param scaleId Scale index
|
|
\param min
|
|
\param max minimum and maximum of the scale
|
|
\param stepSize Major step size. If <code>step == 0</code>, the step size is
|
|
calculated automatically using the maxMajor setting.
|
|
\sa setScaleMaxMajor(), setAutoScale()
|
|
*/
|
|
void QwtPolarPlot::setScale( int scaleId,
|
|
double min, double max, double stepSize )
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return;
|
|
|
|
ScaleData &scaleData = d_data->scaleData[scaleId];
|
|
|
|
scaleData.isValid = false;
|
|
|
|
scaleData.minValue = min;
|
|
scaleData.maxValue = max;
|
|
scaleData.stepSize = stepSize;
|
|
scaleData.doAutoScale = false;
|
|
|
|
autoRefresh();
|
|
}
|
|
|
|
/*!
|
|
\brief Disable autoscaling and specify a fixed scale for a selected scale.
|
|
\param scaleId Scale index
|
|
\param scaleDiv Scale division
|
|
\sa setScale(), setAutoScale()
|
|
*/
|
|
void QwtPolarPlot::setScaleDiv( int scaleId, const QwtScaleDiv &scaleDiv )
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return;
|
|
|
|
ScaleData &scaleData = d_data->scaleData[scaleId];
|
|
|
|
scaleData.scaleDiv = scaleDiv;
|
|
scaleData.isValid = true;
|
|
scaleData.doAutoScale = false;
|
|
|
|
autoRefresh();
|
|
}
|
|
|
|
/*!
|
|
\brief Return the scale division of a specified scale
|
|
|
|
scaleDiv(scaleId)->lBound(), scaleDiv(scaleId)->hBound()
|
|
are the current limits of the scale.
|
|
|
|
\param scaleId Scale index
|
|
\return Scale division
|
|
|
|
\sa QwtScaleDiv, setScaleDiv(), setScale()
|
|
*/
|
|
const QwtScaleDiv *QwtPolarPlot::scaleDiv( int scaleId ) const
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return NULL;
|
|
|
|
return &d_data->scaleData[scaleId].scaleDiv;
|
|
}
|
|
|
|
/*!
|
|
\brief Return the scale division of a specified scale
|
|
|
|
scaleDiv(scaleId)->lBound(), scaleDiv(scaleId)->hBound()
|
|
are the current limits of the scale.
|
|
|
|
\param scaleId Scale index
|
|
\return Scale division
|
|
|
|
\sa QwtScaleDiv, setScaleDiv(), setScale()
|
|
*/
|
|
QwtScaleDiv *QwtPolarPlot::scaleDiv( int scaleId )
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return NULL;
|
|
|
|
return &d_data->scaleData[scaleId].scaleDiv;
|
|
}
|
|
|
|
/*!
|
|
\brief Change the origin of the azimuth scale
|
|
|
|
The azimuth origin is the angle where the azimuth scale
|
|
shows the value 0.0. The default origin is 0.0.
|
|
|
|
\param origin New origin
|
|
\sa azimuthOrigin()
|
|
*/
|
|
void QwtPolarPlot::setAzimuthOrigin( double origin )
|
|
{
|
|
origin = ::fmod( origin, 2 * M_PI );
|
|
if ( origin != d_data->azimuthOrigin )
|
|
{
|
|
d_data->azimuthOrigin = origin;
|
|
autoRefresh();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
The azimuth origin is the angle where the azimuth scale
|
|
shows the value 0.0.
|
|
|
|
\return Origin of the azimuth scale
|
|
\sa setAzimuthOrigin()
|
|
*/
|
|
double QwtPolarPlot::azimuthOrigin() const
|
|
{
|
|
return d_data->azimuthOrigin;
|
|
}
|
|
|
|
/*!
|
|
\brief Translate and in/decrease the zoom factor
|
|
|
|
In zoom mode the zoom position is in the center of the
|
|
canvas. The radius of the circle depends on the size of the plot canvas,
|
|
that is devided by the zoom factor. Thus a factor < 1.0 zoom in.
|
|
|
|
Setting an invalid zoom position disables zooming.
|
|
|
|
\param zoomPos Center of the translation
|
|
\param zoomFactor Zoom factor
|
|
|
|
\sa unzoom(), zoomPos(), zoomFactor()
|
|
*/
|
|
void QwtPolarPlot::zoom( const QwtPointPolar &zoomPos, double zoomFactor )
|
|
{
|
|
zoomFactor = qAbs( zoomFactor );
|
|
if ( zoomPos != d_data->zoomPos ||
|
|
zoomFactor != d_data->zoomFactor )
|
|
{
|
|
d_data->zoomPos = zoomPos;
|
|
d_data->zoomFactor = zoomFactor;
|
|
updateLayout();
|
|
autoRefresh();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Unzoom the plot
|
|
\sa zoom()
|
|
*/
|
|
void QwtPolarPlot::unzoom()
|
|
{
|
|
if ( d_data->zoomFactor != 1.0 || d_data->zoomPos.isValid() )
|
|
{
|
|
d_data->zoomFactor = 1.0;
|
|
d_data->zoomPos = QwtPointPolar();
|
|
autoRefresh();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\return Zoom position
|
|
\sa zoom(), zoomFactor()
|
|
*/
|
|
QwtPointPolar QwtPolarPlot::zoomPos() const
|
|
{
|
|
return d_data->zoomPos;
|
|
}
|
|
|
|
/*!
|
|
\return Zoom factor
|
|
\sa zoom(), zoomPos()
|
|
*/
|
|
double QwtPolarPlot::zoomFactor() const
|
|
{
|
|
return d_data->zoomFactor;
|
|
}
|
|
|
|
/*!
|
|
Build a scale map
|
|
|
|
The azimuth map translates between the scale values and angles from
|
|
[0.0, 2 * PI[. The radial map translates scale values into the distance
|
|
from the pole. The radial map is calculated from the current geometry
|
|
of the canvas.
|
|
|
|
\param scaleId Scale index
|
|
\return Map for the scale on the canvas. With this map pixel coordinates can
|
|
translated to plot coordinates and vice versa.
|
|
|
|
\sa QwtScaleMap, transform(), invTransform()
|
|
*/
|
|
QwtScaleMap QwtPolarPlot::scaleMap( int scaleId ) const
|
|
{
|
|
const QRectF pr = plotRect();
|
|
return scaleMap( scaleId, pr.width() / 2.0 );
|
|
}
|
|
|
|
/*!
|
|
Build a scale map
|
|
|
|
The azimuth map translates between the scale values and angles from
|
|
[0.0, 2 * PI[. The radial map translates scale values into the distance
|
|
from the pole.
|
|
|
|
\param scaleId Scale index
|
|
\param radius Radius of the plot are in pixels
|
|
\return Map for the scale on the canvas. With this map pixel coordinates can
|
|
translated to plot coordinates and vice versa.
|
|
|
|
\sa QwtScaleMap, transform(), invTransform()
|
|
*/
|
|
QwtScaleMap QwtPolarPlot::scaleMap( int scaleId, const double radius ) const
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return QwtScaleMap();
|
|
|
|
QwtScaleMap map;
|
|
map.setTransformation( scaleEngine( scaleId )->transformation() );
|
|
|
|
const QwtScaleDiv *sd = scaleDiv( scaleId );
|
|
map.setScaleInterval( sd->lowerBound(), sd->upperBound() );
|
|
|
|
if ( scaleId == QwtPolar::Azimuth )
|
|
{
|
|
map.setPaintInterval( d_data->azimuthOrigin,
|
|
d_data->azimuthOrigin + 2 * M_PI );
|
|
}
|
|
else
|
|
{
|
|
map.setPaintInterval( 0.0, radius );
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
/*!
|
|
\brief Qt event handler
|
|
|
|
Handles QEvent::LayoutRequest and QEvent::PolishRequest
|
|
|
|
\param e Qt Event
|
|
\return True, when the event was processed
|
|
*/
|
|
bool QwtPolarPlot::event( QEvent *e )
|
|
{
|
|
bool ok = QWidget::event( e );
|
|
switch( e->type() )
|
|
{
|
|
case QEvent::LayoutRequest:
|
|
{
|
|
updateLayout();
|
|
break;
|
|
}
|
|
case QEvent::PolishRequest:
|
|
{
|
|
updateLayout();
|
|
replot();
|
|
break;
|
|
}
|
|
default:;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//! Resize and update internal layout
|
|
void QwtPolarPlot::resizeEvent( QResizeEvent *e )
|
|
{
|
|
QFrame::resizeEvent( e );
|
|
updateLayout();
|
|
}
|
|
|
|
void QwtPolarPlot::initPlot( const QwtText &title )
|
|
{
|
|
d_data = new PrivateData;
|
|
d_data->layout = new QwtPolarLayout;
|
|
|
|
QwtText text( title );
|
|
text.setRenderFlags( Qt::AlignCenter | Qt::TextWordWrap );
|
|
|
|
d_data->titleLabel = new QwtTextLabel( text, this );
|
|
d_data->titleLabel->setFont( QFont( fontInfo().family(), 14, QFont::Bold ) );
|
|
if ( !text.isEmpty() )
|
|
d_data->titleLabel->show();
|
|
else
|
|
d_data->titleLabel->hide();
|
|
|
|
d_data->canvas = new QwtPolarCanvas( this );
|
|
|
|
d_data->autoReplot = false;
|
|
d_data->canvasBrush = QBrush( Qt::white );
|
|
|
|
for ( int scaleId = 0; scaleId < QwtPolar::ScaleCount; scaleId++ )
|
|
{
|
|
ScaleData &scaleData = d_data->scaleData[scaleId];
|
|
|
|
if ( scaleId == QwtPolar::Azimuth )
|
|
{
|
|
scaleData.minValue = 0.0;
|
|
scaleData.maxValue = 360.0;
|
|
scaleData.stepSize = 30.0;
|
|
}
|
|
else
|
|
{
|
|
scaleData.minValue = 0.0;
|
|
scaleData.maxValue = 1000.0;
|
|
scaleData.stepSize = 0.0;
|
|
}
|
|
|
|
scaleData.doAutoScale = true;
|
|
|
|
scaleData.maxMinor = 5;
|
|
scaleData.maxMajor = 8;
|
|
|
|
scaleData.isValid = false;
|
|
|
|
scaleData.scaleEngine = new QwtLinearScaleEngine;
|
|
}
|
|
d_data->zoomFactor = 1.0;
|
|
d_data->azimuthOrigin = 0.0;
|
|
|
|
setSizePolicy( QSizePolicy::MinimumExpanding,
|
|
QSizePolicy::MinimumExpanding );
|
|
|
|
for ( int scaleId = 0; scaleId < QwtPolar::ScaleCount; scaleId++ )
|
|
updateScale( scaleId );
|
|
}
|
|
|
|
//! Replots the plot if QwtPlot::autoReplot() is \c true.
|
|
void QwtPolarPlot::autoRefresh()
|
|
{
|
|
if ( d_data->autoReplot )
|
|
replot();
|
|
}
|
|
|
|
//! Rebuild the layout
|
|
void QwtPolarPlot::updateLayout()
|
|
{
|
|
d_data->layout->activate( this, contentsRect() );
|
|
|
|
// resize and show the visible widgets
|
|
if ( d_data->titleLabel )
|
|
{
|
|
if ( !d_data->titleLabel->text().isEmpty() )
|
|
{
|
|
d_data->titleLabel->setGeometry( d_data->layout->titleRect().toRect() );
|
|
if ( !d_data->titleLabel->isVisible() )
|
|
d_data->titleLabel->show();
|
|
}
|
|
else
|
|
d_data->titleLabel->hide();
|
|
}
|
|
|
|
if ( d_data->legend )
|
|
{
|
|
if ( d_data->legend->isEmpty() )
|
|
{
|
|
d_data->legend->hide();
|
|
}
|
|
else
|
|
{
|
|
const QRectF legendRect = d_data->layout->legendRect();
|
|
d_data->legend->setGeometry( legendRect.toRect() );
|
|
d_data->legend->show();
|
|
}
|
|
}
|
|
|
|
d_data->canvas->setGeometry( d_data->layout->canvasRect().toRect() );
|
|
Q_EMIT layoutChanged();
|
|
}
|
|
|
|
/*!
|
|
\brief Redraw the plot
|
|
|
|
If the autoReplot option is not set (which is the default)
|
|
or if any curves are attached to raw data, the plot has to
|
|
be refreshed explicitly in order to make changes visible.
|
|
|
|
\sa setAutoReplot()
|
|
\warning Calls canvas()->repaint, take care of infinite recursions
|
|
*/
|
|
void QwtPolarPlot::replot()
|
|
{
|
|
bool doAutoReplot = autoReplot();
|
|
setAutoReplot( false );
|
|
|
|
for ( int scaleId = 0; scaleId < QwtPolar::ScaleCount; scaleId++ )
|
|
updateScale( scaleId );
|
|
|
|
d_data->canvas->invalidateBackingStore();
|
|
d_data->canvas->repaint();
|
|
|
|
setAutoReplot( doAutoReplot );
|
|
}
|
|
|
|
//! \return the plot's canvas
|
|
QwtPolarCanvas *QwtPolarPlot::canvas()
|
|
{
|
|
return d_data->canvas;
|
|
}
|
|
|
|
//! \return the plot's canvas
|
|
const QwtPolarCanvas *QwtPolarPlot::canvas() const
|
|
{
|
|
return d_data->canvas;
|
|
}
|
|
|
|
/*!
|
|
Redraw the canvas.
|
|
\param painter Painter used for drawing
|
|
\param canvasRect Contents rect of the canvas
|
|
*/
|
|
void QwtPolarPlot::drawCanvas( QPainter *painter,
|
|
const QRectF &canvasRect ) const
|
|
{
|
|
const QRectF cr = canvasRect;
|
|
const QRectF pr = plotRect( cr );
|
|
|
|
const double radius = pr.width() / 2.0;
|
|
|
|
if ( d_data->canvasBrush.style() != Qt::NoBrush )
|
|
{
|
|
painter->save();
|
|
painter->setPen( Qt::NoPen );
|
|
painter->setBrush( d_data->canvasBrush );
|
|
|
|
if ( qwtDistance( pr.center(), cr.topLeft() ) < radius &&
|
|
qwtDistance( pr.center(), cr.topRight() ) < radius &&
|
|
qwtDistance( pr.center(), cr.bottomRight() ) < radius &&
|
|
qwtDistance( pr.center(), cr.bottomLeft() ) < radius )
|
|
{
|
|
QwtPainter::drawRect( painter, cr );
|
|
}
|
|
else
|
|
{
|
|
painter->setRenderHint( QPainter::Antialiasing, true );
|
|
QwtPainter::drawEllipse( painter, pr );
|
|
}
|
|
painter->restore();
|
|
}
|
|
|
|
drawItems( painter,
|
|
scaleMap( QwtPolar::Azimuth, radius ),
|
|
scaleMap( QwtPolar::Radius, radius ),
|
|
pr.center(), radius, canvasRect );
|
|
}
|
|
|
|
/*!
|
|
Redraw the canvas items.
|
|
|
|
\param painter Painter used for drawing
|
|
\param azimuthMap Maps azimuth values to values related to 0.0, M_2PI
|
|
\param radialMap Maps radius values into painter coordinates.
|
|
\param pole Position of the pole in painter coordinates
|
|
\param radius Radius of the complete plot area in painter coordinates
|
|
\param canvasRect Contents rect of the canvas in painter coordinates
|
|
*/
|
|
void QwtPolarPlot::drawItems( QPainter *painter,
|
|
const QwtScaleMap &azimuthMap, const QwtScaleMap &radialMap,
|
|
const QPointF &pole, double radius,
|
|
const QRectF &canvasRect ) const
|
|
{
|
|
const QRectF pr = plotRect( canvasRect );
|
|
|
|
const QwtPolarItemList& itmList = itemList();
|
|
for ( QwtPolarItemIterator it = itmList.begin();
|
|
it != itmList.end(); ++it )
|
|
{
|
|
QwtPolarItem *item = *it;
|
|
if ( item && item->isVisible() )
|
|
{
|
|
painter->save();
|
|
|
|
// Unfortunately circular clipping slows down
|
|
// painting a lot. So we better try to avoid it.
|
|
|
|
bool doClipping = false;
|
|
if ( item->rtti() != QwtPolarItem::Rtti_PolarGrid )
|
|
{
|
|
const QwtInterval intv =
|
|
item->boundingInterval( QwtPolar::Radius );
|
|
|
|
if ( !intv.isValid() )
|
|
doClipping = true;
|
|
else
|
|
{
|
|
if ( radialMap.s1() < radialMap.s2() )
|
|
doClipping = intv.maxValue() > radialMap.s2();
|
|
else
|
|
doClipping = intv.minValue() < radialMap.s2();
|
|
}
|
|
}
|
|
|
|
if ( doClipping )
|
|
{
|
|
const int margin = item->marginHint();
|
|
|
|
const QRectF clipRect = pr.adjusted(
|
|
-margin, -margin, margin, margin );
|
|
if ( !clipRect.contains( canvasRect ) )
|
|
{
|
|
QRegion clipRegion( clipRect.toRect(), QRegion::Ellipse );
|
|
painter->setClipRegion( clipRegion, Qt::IntersectClip );
|
|
}
|
|
}
|
|
|
|
painter->setRenderHint( QPainter::Antialiasing,
|
|
item->testRenderHint( QwtPolarItem::RenderAntialiased ) );
|
|
|
|
item->draw( painter, azimuthMap, radialMap,
|
|
pole, radius, canvasRect );
|
|
|
|
painter->restore();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Rebuild the scale
|
|
\param scaleId Scale index
|
|
*/
|
|
|
|
void QwtPolarPlot::updateScale( int scaleId )
|
|
{
|
|
if ( scaleId < 0 || scaleId >= QwtPolar::ScaleCount )
|
|
return;
|
|
|
|
ScaleData &d = d_data->scaleData[scaleId];
|
|
|
|
double minValue = d.minValue;
|
|
double maxValue = d.maxValue;
|
|
double stepSize = d.stepSize;
|
|
|
|
if ( scaleId == QwtPolar::ScaleRadius && d.doAutoScale )
|
|
{
|
|
QwtInterval interval;
|
|
|
|
const QwtPolarItemList& itmList = itemList();
|
|
for ( QwtPolarItemIterator it = itmList.begin();
|
|
it != itmList.end(); ++it )
|
|
{
|
|
const QwtPolarItem *item = *it;
|
|
if ( item->testItemAttribute( QwtPolarItem::AutoScale ) )
|
|
interval |= item->boundingInterval( scaleId );
|
|
}
|
|
|
|
minValue = interval.minValue();
|
|
maxValue = interval.maxValue();
|
|
|
|
d.scaleEngine->autoScale( d.maxMajor,
|
|
minValue, maxValue, stepSize );
|
|
d.isValid = false;
|
|
}
|
|
|
|
if ( !d.isValid )
|
|
{
|
|
d.scaleDiv = d.scaleEngine->divideScale(
|
|
minValue, maxValue, d.maxMajor, d.maxMinor, stepSize );
|
|
d.isValid = true;
|
|
}
|
|
|
|
const QwtInterval interval = visibleInterval();
|
|
|
|
const QwtPolarItemList& itmList = itemList();
|
|
for ( QwtPolarItemIterator it = itmList.begin();
|
|
it != itmList.end(); ++it )
|
|
{
|
|
QwtPolarItem *item = *it;
|
|
item->updateScaleDiv( *scaleDiv( QwtPolar::Azimuth ),
|
|
*scaleDiv( QwtPolar::Radius ), interval );
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\return Maximum of all item margin hints.
|
|
\sa QwtPolarItem::marginHint()
|
|
*/
|
|
int QwtPolarPlot::plotMarginHint() const
|
|
{
|
|
int margin = 0;
|
|
const QwtPolarItemList& itmList = itemList();
|
|
for ( QwtPolarItemIterator it = itmList.begin();
|
|
it != itmList.end(); ++it )
|
|
{
|
|
QwtPolarItem *item = *it;
|
|
if ( item && item->isVisible() )
|
|
{
|
|
const int hint = item->marginHint();
|
|
if ( hint > margin )
|
|
margin = hint;
|
|
}
|
|
}
|
|
return margin;
|
|
}
|
|
|
|
/*!
|
|
The plot area depends on the size of the canvas
|
|
and the zoom parameters.
|
|
|
|
\return Bounding rect of the plot area
|
|
|
|
*/
|
|
QRectF QwtPolarPlot::plotRect() const
|
|
{
|
|
return plotRect( canvas()->contentsRect() );
|
|
}
|
|
|
|
/*!
|
|
\brief Calculate the bounding rect of the plot area
|
|
|
|
The plot area depends on the zoom parameters.
|
|
|
|
\param canvasRect Rectangle of the canvas
|
|
\return Rectangle for displaying 100% of the plot
|
|
*/
|
|
QRectF QwtPolarPlot::plotRect( const QRectF &canvasRect ) const
|
|
{
|
|
const QwtScaleDiv *sd = scaleDiv( QwtPolar::Radius );
|
|
const QwtScaleEngine *se = scaleEngine( QwtPolar::Radius );
|
|
|
|
const int margin = plotMarginHint();
|
|
const QRectF cr = canvasRect;
|
|
const int radius = qMin( cr.width(), cr.height() ) / 2 - margin;
|
|
|
|
QwtScaleMap map;
|
|
map.setTransformation( se->transformation() );
|
|
map.setPaintInterval( 0.0, radius / d_data->zoomFactor );
|
|
map.setScaleInterval( sd->lowerBound(), sd->upperBound() );
|
|
|
|
double v = map.s1();
|
|
if ( map.s1() <= map.s2() )
|
|
v += d_data->zoomPos.radius();
|
|
else
|
|
v -= d_data->zoomPos.radius();
|
|
v = map.transform( v );
|
|
|
|
const QPointF off =
|
|
QwtPointPolar( d_data->zoomPos.azimuth(), v ).toPoint();
|
|
|
|
QPointF center( cr.center().x(), cr.top() + margin + radius );
|
|
center -= QPointF( off.x(), -off.y() );
|
|
|
|
QRectF rect( 0, 0, 2 * map.p2(), 2 * map.p2() );
|
|
rect.moveCenter( center );
|
|
|
|
return rect;
|
|
}
|
|
|
|
/*!
|
|
\return Bounding interval of the radial scale that is
|
|
visible on the canvas.
|
|
*/
|
|
QwtInterval QwtPolarPlot::visibleInterval() const
|
|
{
|
|
const QwtScaleDiv *sd = scaleDiv( QwtPolar::Radius );
|
|
|
|
const QRectF cRect = canvas()->contentsRect();
|
|
const QRectF pRect = plotRect( cRect );
|
|
if ( cRect.contains( pRect ) || !cRect.intersects( pRect ) )
|
|
{
|
|
return QwtInterval( sd->lowerBound(), sd->upperBound() );
|
|
}
|
|
|
|
const QPointF pole = pRect.center();
|
|
const QRectF scaleRect = pRect & cRect;
|
|
|
|
const QwtScaleMap map = scaleMap( QwtPolar::Radius );
|
|
|
|
double dmin = 0.0;
|
|
double dmax = 0.0;
|
|
if ( scaleRect.contains( pole ) )
|
|
{
|
|
dmin = 0.0;
|
|
|
|
QPointF corners[4];
|
|
corners[0] = scaleRect.bottomRight();
|
|
corners[1] = scaleRect.topRight();
|
|
corners[2] = scaleRect.topLeft();
|
|
corners[3] = scaleRect.bottomLeft();
|
|
|
|
dmax = 0.0;
|
|
for ( int i = 0; i < 4; i++ )
|
|
{
|
|
const double dist = qwtDistance( pole, corners[i] );
|
|
if ( dist > dmax )
|
|
dmax = dist;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( pole.x() < scaleRect.left() )
|
|
{
|
|
if ( pole.y() < scaleRect.top() )
|
|
{
|
|
dmin = qwtDistance( pole, scaleRect.topLeft() );
|
|
dmax = qwtDistance( pole, scaleRect.bottomRight() );
|
|
}
|
|
else if ( pole.y() > scaleRect.bottom() )
|
|
{
|
|
dmin = qwtDistance( pole, scaleRect.bottomLeft() );
|
|
dmax = qwtDistance( pole, scaleRect.topRight() );
|
|
}
|
|
else
|
|
{
|
|
dmin = scaleRect.left() - pole.x();
|
|
dmax = qMax( qwtDistance( pole, scaleRect.bottomRight() ),
|
|
qwtDistance( pole, scaleRect.topRight() ) );
|
|
}
|
|
}
|
|
else if ( pole.x() > scaleRect.right() )
|
|
{
|
|
if ( pole.y() < scaleRect.top() )
|
|
{
|
|
dmin = qwtDistance( pole, scaleRect.topRight() );
|
|
dmax = qwtDistance( pole, scaleRect.bottomLeft() );
|
|
}
|
|
else if ( pole.y() > scaleRect.bottom() )
|
|
{
|
|
dmin = qwtDistance( pole, scaleRect.bottomRight() );
|
|
dmax = qwtDistance( pole, scaleRect.topLeft() );
|
|
}
|
|
else
|
|
{
|
|
dmin = pole.x() - scaleRect.right();
|
|
dmax = qMax( qwtDistance( pole, scaleRect.bottomLeft() ),
|
|
qwtDistance( pole, scaleRect.topLeft() ) );
|
|
}
|
|
}
|
|
else if ( pole.y() < scaleRect.top() )
|
|
{
|
|
dmin = scaleRect.top() - pole.y();
|
|
dmax = qMax( qwtDistance( pole, scaleRect.bottomLeft() ),
|
|
qwtDistance( pole, scaleRect.bottomRight() ) );
|
|
}
|
|
else if ( pole.y() > scaleRect.bottom() )
|
|
{
|
|
dmin = pole.y() - scaleRect.bottom();
|
|
dmax = qMax( qwtDistance( pole, scaleRect.topLeft() ),
|
|
qwtDistance( pole, scaleRect.topRight() ) );
|
|
}
|
|
}
|
|
|
|
const double radius = pRect.width() / 2.0;
|
|
if ( dmax > radius )
|
|
dmax = radius;
|
|
|
|
QwtInterval interval;
|
|
interval.setMinValue( map.invTransform( dmin ) );
|
|
interval.setMaxValue( map.invTransform( dmax ) );
|
|
|
|
return interval;
|
|
}
|
|
|
|
/*!
|
|
\return Layout, responsible for the geometry of the plot components
|
|
*/
|
|
QwtPolarLayout *QwtPolarPlot::plotLayout()
|
|
{
|
|
return d_data->layout;
|
|
}
|
|
|
|
/*!
|
|
\return Layout, responsible for the geometry of the plot components
|
|
*/
|
|
const QwtPolarLayout *QwtPolarPlot::plotLayout() const
|
|
{
|
|
return d_data->layout;
|
|
}
|
|
|
|
/*!
|
|
\brief Attach/Detach a plot item
|
|
|
|
\param plotItem Plot item
|
|
\param on When true attach the item, otherwise detach it
|
|
*/
|
|
void QwtPolarPlot::attachItem( QwtPolarItem *plotItem, bool on )
|
|
{
|
|
if ( on )
|
|
insertItem( plotItem );
|
|
else
|
|
removeItem( plotItem );
|
|
|
|
Q_EMIT itemAttached( plotItem, on );
|
|
|
|
if ( plotItem->testItemAttribute( QwtPolarItem::Legend ) )
|
|
{
|
|
// the item wants to be represented on the legend
|
|
|
|
if ( on )
|
|
{
|
|
updateLegend( plotItem );
|
|
}
|
|
else
|
|
{
|
|
const QVariant itemInfo = itemToInfo( plotItem );
|
|
Q_EMIT legendDataChanged( itemInfo, QList<QwtLegendData>() );
|
|
}
|
|
}
|
|
|
|
if ( autoReplot() )
|
|
update();
|
|
}
|
|
|
|
/*!
|
|
\brief Build an information, that can be used to identify
|
|
a plot item on the legend.
|
|
|
|
The default implementation simply wraps the plot item
|
|
into a QVariant object. When overloading itemToInfo()
|
|
usually infoToItem() needs to reimplemeted too.
|
|
|
|
\code
|
|
QVariant itemInfo;
|
|
qVariantSetValue( itemInfo, plotItem );
|
|
\endcode
|
|
|
|
\param plotItem Plot item
|
|
\sa infoToItem()
|
|
*/
|
|
QVariant QwtPolarPlot::itemToInfo( QwtPolarItem *plotItem ) const
|
|
{
|
|
QVariant itemInfo;
|
|
qVariantSetValue( itemInfo, plotItem );
|
|
|
|
return itemInfo;
|
|
}
|
|
|
|
/*!
|
|
\brief Identify the plot item according to an item info object,
|
|
that has bee generated from itemToInfo().
|
|
|
|
The default implementation simply tries to unwrap a QwtPlotItem
|
|
pointer:
|
|
|
|
\code
|
|
if ( itemInfo.canConvert<QwtPlotItem *>() )
|
|
return qvariant_cast<QwtPlotItem *>( itemInfo );
|
|
\endcode
|
|
\param itemInfo Plot item
|
|
\return A plot item, when successful, otherwise a NULL pointer.
|
|
\sa itemToInfo()
|
|
*/
|
|
QwtPolarItem *QwtPolarPlot::infoToItem( const QVariant &itemInfo ) const
|
|
{
|
|
if ( itemInfo.canConvert<QwtPolarItem *>() )
|
|
return qvariant_cast<QwtPolarItem *>( itemInfo );
|
|
|
|
return NULL;
|
|
}
|