632 lines
15 KiB
C++
632 lines
15 KiB
C++
|
/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
|
||
|
* Qwt Widget Library
|
||
|
* Copyright (C) 1997 Josef Wilgen
|
||
|
* Copyright (C) 2002 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_plot_rescaler.h"
|
||
|
#include "qwt_plot.h"
|
||
|
#include "qwt_scale_div.h"
|
||
|
#include "qwt_interval.h"
|
||
|
#include "qwt_plot_canvas.h"
|
||
|
#include <qevent.h>
|
||
|
#include <qalgorithms.h>
|
||
|
|
||
|
class QwtPlotRescaler::AxisData
|
||
|
{
|
||
|
public:
|
||
|
AxisData():
|
||
|
aspectRatio( 1.0 ),
|
||
|
expandingDirection( QwtPlotRescaler::ExpandUp )
|
||
|
{
|
||
|
}
|
||
|
|
||
|
double aspectRatio;
|
||
|
QwtInterval intervalHint;
|
||
|
QwtPlotRescaler::ExpandingDirection expandingDirection;
|
||
|
mutable QwtScaleDiv scaleDiv;
|
||
|
};
|
||
|
|
||
|
class QwtPlotRescaler::PrivateData
|
||
|
{
|
||
|
public:
|
||
|
PrivateData():
|
||
|
referenceAxis( QwtPlot::xBottom ),
|
||
|
rescalePolicy( QwtPlotRescaler::Expanding ),
|
||
|
isEnabled( false ),
|
||
|
inReplot( 0 )
|
||
|
{
|
||
|
}
|
||
|
|
||
|
int referenceAxis;
|
||
|
RescalePolicy rescalePolicy;
|
||
|
QwtPlotRescaler::AxisData axisData[QwtPlot::axisCnt];
|
||
|
bool isEnabled;
|
||
|
|
||
|
mutable int inReplot;
|
||
|
};
|
||
|
|
||
|
/*!
|
||
|
Constructor
|
||
|
|
||
|
\param canvas Canvas
|
||
|
\param referenceAxis Reference axis, see RescalePolicy
|
||
|
\param policy Rescale policy
|
||
|
|
||
|
\sa setRescalePolicy(), setReferenceAxis()
|
||
|
*/
|
||
|
QwtPlotRescaler::QwtPlotRescaler( QWidget *canvas,
|
||
|
int referenceAxis, RescalePolicy policy ):
|
||
|
QObject( canvas )
|
||
|
{
|
||
|
d_data = new PrivateData;
|
||
|
d_data->referenceAxis = referenceAxis;
|
||
|
d_data->rescalePolicy = policy;
|
||
|
|
||
|
setEnabled( true );
|
||
|
}
|
||
|
|
||
|
//! Destructor
|
||
|
QwtPlotRescaler::~QwtPlotRescaler()
|
||
|
{
|
||
|
delete d_data;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\brief En/disable the rescaler
|
||
|
|
||
|
When enabled is true an event filter is installed for
|
||
|
the canvas, otherwise the event filter is removed.
|
||
|
|
||
|
\param on true or false
|
||
|
\sa isEnabled(), eventFilter()
|
||
|
*/
|
||
|
void QwtPlotRescaler::setEnabled( bool on )
|
||
|
{
|
||
|
if ( d_data->isEnabled != on )
|
||
|
{
|
||
|
d_data->isEnabled = on;
|
||
|
|
||
|
QWidget *w = canvas();
|
||
|
if ( w )
|
||
|
{
|
||
|
if ( d_data->isEnabled )
|
||
|
w->installEventFilter( this );
|
||
|
else
|
||
|
w->removeEventFilter( this );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return true when enabled, false otherwise
|
||
|
\sa setEnabled, eventFilter()
|
||
|
*/
|
||
|
bool QwtPlotRescaler::isEnabled() const
|
||
|
{
|
||
|
return d_data->isEnabled;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Change the rescale policy
|
||
|
|
||
|
\param policy Rescale policy
|
||
|
\sa rescalePolicy()
|
||
|
*/
|
||
|
void QwtPlotRescaler::setRescalePolicy( RescalePolicy policy )
|
||
|
{
|
||
|
d_data->rescalePolicy = policy;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return Rescale policy
|
||
|
\sa setRescalePolicy()
|
||
|
*/
|
||
|
QwtPlotRescaler::RescalePolicy QwtPlotRescaler::rescalePolicy() const
|
||
|
{
|
||
|
return d_data->rescalePolicy;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Set the reference axis ( see RescalePolicy )
|
||
|
|
||
|
\param axis Axis index ( QwtPlot::Axis )
|
||
|
\sa referenceAxis()
|
||
|
*/
|
||
|
void QwtPlotRescaler::setReferenceAxis( int axis )
|
||
|
{
|
||
|
d_data->referenceAxis = axis;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return Reference axis ( see RescalePolicy )
|
||
|
\sa setReferenceAxis()
|
||
|
*/
|
||
|
int QwtPlotRescaler::referenceAxis() const
|
||
|
{
|
||
|
return d_data->referenceAxis;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Set the direction in which all axis should be expanded
|
||
|
|
||
|
\param direction Direction
|
||
|
\sa expandingDirection()
|
||
|
*/
|
||
|
void QwtPlotRescaler::setExpandingDirection(
|
||
|
ExpandingDirection direction )
|
||
|
{
|
||
|
for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
|
||
|
setExpandingDirection( axis, direction );
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Set the direction in which an axis should be expanded
|
||
|
|
||
|
\param axis Axis index ( see QwtPlot::AxisId )
|
||
|
\param direction Direction
|
||
|
\sa expandingDirection()
|
||
|
*/
|
||
|
void QwtPlotRescaler::setExpandingDirection(
|
||
|
int axis, ExpandingDirection direction )
|
||
|
{
|
||
|
if ( axis >= 0 && axis < QwtPlot::axisCnt )
|
||
|
d_data->axisData[axis].expandingDirection = direction;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return Direction in which an axis should be expanded
|
||
|
|
||
|
\param axis Axis index ( see QwtPlot::AxisId )
|
||
|
\sa setExpandingDirection()
|
||
|
*/
|
||
|
QwtPlotRescaler::ExpandingDirection
|
||
|
QwtPlotRescaler::expandingDirection( int axis ) const
|
||
|
{
|
||
|
if ( axis >= 0 && axis < QwtPlot::axisCnt )
|
||
|
return d_data->axisData[axis].expandingDirection;
|
||
|
|
||
|
return ExpandBoth;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Set the aspect ratio between the scale of the reference axis
|
||
|
and the other scales. The default ratio is 1.0
|
||
|
|
||
|
\param ratio Aspect ratio
|
||
|
\sa aspectRatio()
|
||
|
*/
|
||
|
void QwtPlotRescaler::setAspectRatio( double ratio )
|
||
|
{
|
||
|
for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
|
||
|
setAspectRatio( axis, ratio );
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Set the aspect ratio between the scale of the reference axis
|
||
|
and another scale. The default ratio is 1.0
|
||
|
|
||
|
\param axis Axis index ( see QwtPlot::AxisId )
|
||
|
\param ratio Aspect ratio
|
||
|
\sa aspectRatio()
|
||
|
*/
|
||
|
void QwtPlotRescaler::setAspectRatio( int axis, double ratio )
|
||
|
{
|
||
|
if ( ratio < 0.0 )
|
||
|
ratio = 0.0;
|
||
|
|
||
|
if ( axis >= 0 && axis < QwtPlot::axisCnt )
|
||
|
d_data->axisData[axis].aspectRatio = ratio;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return Aspect ratio between an axis and the reference axis.
|
||
|
|
||
|
\param axis Axis index ( see QwtPlot::AxisId )
|
||
|
\sa setAspectRatio()
|
||
|
*/
|
||
|
double QwtPlotRescaler::aspectRatio( int axis ) const
|
||
|
{
|
||
|
if ( axis >= 0 && axis < QwtPlot::axisCnt )
|
||
|
return d_data->axisData[axis].aspectRatio;
|
||
|
|
||
|
return 0.0;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Set an interval hint for an axis
|
||
|
|
||
|
In Fitting mode, the hint is used as minimal interval
|
||
|
that always needs to be displayed.
|
||
|
|
||
|
\param axis Axis, see QwtPlot::Axis
|
||
|
\param interval Axis
|
||
|
\sa intervalHint(), RescalePolicy
|
||
|
*/
|
||
|
void QwtPlotRescaler::setIntervalHint( int axis,
|
||
|
const QwtInterval &interval )
|
||
|
{
|
||
|
if ( axis >= 0 && axis < QwtPlot::axisCnt )
|
||
|
d_data->axisData[axis].intervalHint = interval;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\param axis Axis, see QwtPlot::Axis
|
||
|
\return Interval hint
|
||
|
\sa setIntervalHint(), RescalePolicy
|
||
|
*/
|
||
|
QwtInterval QwtPlotRescaler::intervalHint( int axis ) const
|
||
|
{
|
||
|
if ( axis >= 0 && axis < QwtPlot::axisCnt )
|
||
|
return d_data->axisData[axis].intervalHint;
|
||
|
|
||
|
return QwtInterval();
|
||
|
}
|
||
|
|
||
|
//! \return plot canvas
|
||
|
QWidget *QwtPlotRescaler::canvas()
|
||
|
{
|
||
|
return qobject_cast<QWidget *>( parent() );
|
||
|
}
|
||
|
|
||
|
//! \return plot canvas
|
||
|
const QWidget *QwtPlotRescaler::canvas() const
|
||
|
{
|
||
|
return qobject_cast<const QWidget *>( parent() );
|
||
|
}
|
||
|
|
||
|
//! \return plot widget
|
||
|
QwtPlot *QwtPlotRescaler::plot()
|
||
|
{
|
||
|
QWidget *w = canvas();
|
||
|
if ( w )
|
||
|
w = w->parentWidget();
|
||
|
|
||
|
return qobject_cast<QwtPlot *>( w );
|
||
|
}
|
||
|
|
||
|
//! \return plot widget
|
||
|
const QwtPlot *QwtPlotRescaler::plot() const
|
||
|
{
|
||
|
const QWidget *w = canvas();
|
||
|
if ( w )
|
||
|
w = w->parentWidget();
|
||
|
|
||
|
return qobject_cast<const QwtPlot *>( w );
|
||
|
}
|
||
|
|
||
|
//! Event filter for the plot canvas
|
||
|
bool QwtPlotRescaler::eventFilter( QObject *object, QEvent *event )
|
||
|
{
|
||
|
if ( object && object == canvas() )
|
||
|
{
|
||
|
switch ( event->type() )
|
||
|
{
|
||
|
case QEvent::Resize:
|
||
|
{
|
||
|
canvasResizeEvent( static_cast<QResizeEvent *>( event ) );
|
||
|
break;
|
||
|
}
|
||
|
case QEvent::PolishRequest:
|
||
|
{
|
||
|
rescale();
|
||
|
break;
|
||
|
}
|
||
|
default:;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Event handler for resize events of the plot canvas
|
||
|
|
||
|
\param event Resize event
|
||
|
\sa rescale()
|
||
|
*/
|
||
|
void QwtPlotRescaler::canvasResizeEvent( QResizeEvent* event )
|
||
|
{
|
||
|
int left, top, right, bottom;
|
||
|
canvas()->getContentsMargins( &left, &top, &right, &bottom );
|
||
|
|
||
|
const QSize marginSize( left + right, top + bottom );
|
||
|
|
||
|
const QSize newSize = event->size() - marginSize;
|
||
|
const QSize oldSize = event->oldSize() - marginSize;
|
||
|
|
||
|
rescale( oldSize, newSize );
|
||
|
}
|
||
|
|
||
|
//! Adjust the plot axes scales
|
||
|
void QwtPlotRescaler::rescale() const
|
||
|
{
|
||
|
const QSize size = canvas()->contentsRect().size();
|
||
|
rescale( size, size );
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Adjust the plot axes scales
|
||
|
|
||
|
\param oldSize Previous size of the canvas
|
||
|
\param newSize New size of the canvas
|
||
|
*/
|
||
|
void QwtPlotRescaler::rescale(
|
||
|
const QSize &oldSize, const QSize &newSize ) const
|
||
|
{
|
||
|
if ( newSize.isEmpty() )
|
||
|
return;
|
||
|
|
||
|
QwtInterval intervals[QwtPlot::axisCnt];
|
||
|
for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
|
||
|
intervals[axis] = interval( axis );
|
||
|
|
||
|
const int refAxis = referenceAxis();
|
||
|
intervals[refAxis] = expandScale( refAxis, oldSize, newSize );
|
||
|
|
||
|
for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
|
||
|
{
|
||
|
if ( aspectRatio( axis ) > 0.0 && axis != refAxis )
|
||
|
intervals[axis] = syncScale( axis, intervals[refAxis], newSize );
|
||
|
}
|
||
|
|
||
|
updateScales( intervals );
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Calculate the new scale interval of a plot axis
|
||
|
|
||
|
\param axis Axis index ( see QwtPlot::AxisId )
|
||
|
\param oldSize Previous size of the canvas
|
||
|
\param newSize New size of the canvas
|
||
|
|
||
|
\return Calculated new interval for the axis
|
||
|
*/
|
||
|
QwtInterval QwtPlotRescaler::expandScale( int axis,
|
||
|
const QSize &oldSize, const QSize &newSize ) const
|
||
|
{
|
||
|
const QwtInterval oldInterval = interval( axis );
|
||
|
|
||
|
QwtInterval expanded = oldInterval;
|
||
|
switch ( rescalePolicy() )
|
||
|
{
|
||
|
case Fixed:
|
||
|
{
|
||
|
break; // do nothing
|
||
|
}
|
||
|
case Expanding:
|
||
|
{
|
||
|
if ( !oldSize.isEmpty() )
|
||
|
{
|
||
|
double width = oldInterval.width();
|
||
|
if ( orientation( axis ) == Qt::Horizontal )
|
||
|
width *= double( newSize.width() ) / oldSize.width();
|
||
|
else
|
||
|
width *= double( newSize.height() ) / oldSize.height();
|
||
|
|
||
|
expanded = expandInterval( oldInterval,
|
||
|
width, expandingDirection( axis ) );
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case Fitting:
|
||
|
{
|
||
|
double dist = 0.0;
|
||
|
for ( int ax = 0; ax < QwtPlot::axisCnt; ax++ )
|
||
|
{
|
||
|
const double d = pixelDist( ax, newSize );
|
||
|
if ( d > dist )
|
||
|
dist = d;
|
||
|
}
|
||
|
if ( dist > 0.0 )
|
||
|
{
|
||
|
double width;
|
||
|
if ( orientation( axis ) == Qt::Horizontal )
|
||
|
width = newSize.width() * dist;
|
||
|
else
|
||
|
width = newSize.height() * dist;
|
||
|
|
||
|
expanded = expandInterval( intervalHint( axis ),
|
||
|
width, expandingDirection( axis ) );
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return expanded;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Synchronize an axis scale according to the scale of the reference axis
|
||
|
|
||
|
\param axis Axis index ( see QwtPlot::AxisId )
|
||
|
\param reference Interval of the reference axis
|
||
|
\param size Size of the canvas
|
||
|
|
||
|
\return New interval for axis
|
||
|
*/
|
||
|
QwtInterval QwtPlotRescaler::syncScale( int axis,
|
||
|
const QwtInterval& reference, const QSize &size ) const
|
||
|
{
|
||
|
double dist;
|
||
|
if ( orientation( referenceAxis() ) == Qt::Horizontal )
|
||
|
dist = reference.width() / size.width();
|
||
|
else
|
||
|
dist = reference.width() / size.height();
|
||
|
|
||
|
if ( orientation( axis ) == Qt::Horizontal )
|
||
|
dist *= size.width();
|
||
|
else
|
||
|
dist *= size.height();
|
||
|
|
||
|
dist /= aspectRatio( axis );
|
||
|
|
||
|
QwtInterval intv;
|
||
|
if ( rescalePolicy() == Fitting )
|
||
|
intv = intervalHint( axis );
|
||
|
else
|
||
|
intv = interval( axis );
|
||
|
|
||
|
intv = expandInterval( intv, dist, expandingDirection( axis ) );
|
||
|
|
||
|
return intv;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\return Orientation of an axis
|
||
|
\param axis Axis index ( see QwtPlot::AxisId )
|
||
|
*/
|
||
|
Qt::Orientation QwtPlotRescaler::orientation( int axis ) const
|
||
|
{
|
||
|
if ( axis == QwtPlot::yLeft || axis == QwtPlot::yRight )
|
||
|
return Qt::Vertical;
|
||
|
|
||
|
return Qt::Horizontal;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
\param axis Axis index ( see QwtPlot::AxisId )
|
||
|
\return Normalized interval of an axis
|
||
|
*/
|
||
|
QwtInterval QwtPlotRescaler::interval( int axis ) const
|
||
|
{
|
||
|
if ( axis < 0 || axis >= QwtPlot::axisCnt )
|
||
|
return QwtInterval();
|
||
|
|
||
|
return plot()->axisScaleDiv( axis ).interval().normalized();
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Expand the interval
|
||
|
|
||
|
\param interval Interval to be expanded
|
||
|
\param width Distance to be added to the interval
|
||
|
\param direction Direction of the expand operation
|
||
|
|
||
|
\return Expanded interval
|
||
|
*/
|
||
|
QwtInterval QwtPlotRescaler::expandInterval(
|
||
|
const QwtInterval &interval, double width,
|
||
|
ExpandingDirection direction ) const
|
||
|
{
|
||
|
QwtInterval expanded = interval;
|
||
|
|
||
|
switch ( direction )
|
||
|
{
|
||
|
case ExpandUp:
|
||
|
expanded.setMinValue( interval.minValue() );
|
||
|
expanded.setMaxValue( interval.minValue() + width );
|
||
|
break;
|
||
|
|
||
|
case ExpandDown:
|
||
|
expanded.setMaxValue( interval.maxValue() );
|
||
|
expanded.setMinValue( interval.maxValue() - width );
|
||
|
break;
|
||
|
|
||
|
case ExpandBoth:
|
||
|
default:
|
||
|
expanded.setMinValue( interval.minValue() +
|
||
|
interval.width() / 2.0 - width / 2.0 );
|
||
|
expanded.setMaxValue( expanded.minValue() + width );
|
||
|
}
|
||
|
return expanded;
|
||
|
}
|
||
|
|
||
|
double QwtPlotRescaler::pixelDist( int axis, const QSize &size ) const
|
||
|
{
|
||
|
const QwtInterval intv = intervalHint( axis );
|
||
|
|
||
|
double dist = 0.0;
|
||
|
if ( !intv.isNull() )
|
||
|
{
|
||
|
if ( axis == referenceAxis() )
|
||
|
dist = intv.width();
|
||
|
else
|
||
|
{
|
||
|
const double r = aspectRatio( axis );
|
||
|
if ( r > 0.0 )
|
||
|
dist = intv.width() * r;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( dist > 0.0 )
|
||
|
{
|
||
|
if ( orientation( axis ) == Qt::Horizontal )
|
||
|
dist /= size.width();
|
||
|
else
|
||
|
dist /= size.height();
|
||
|
}
|
||
|
|
||
|
return dist;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Update the axes scales
|
||
|
|
||
|
\param intervals Scale intervals
|
||
|
*/
|
||
|
void QwtPlotRescaler::updateScales(
|
||
|
QwtInterval intervals[QwtPlot::axisCnt] ) const
|
||
|
{
|
||
|
if ( d_data->inReplot >= 5 )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
QwtPlot *plt = const_cast<QwtPlot *>( plot() );
|
||
|
|
||
|
const bool doReplot = plt->autoReplot();
|
||
|
plt->setAutoReplot( false );
|
||
|
|
||
|
for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
|
||
|
{
|
||
|
if ( axis == referenceAxis() || aspectRatio( axis ) > 0.0 )
|
||
|
{
|
||
|
double v1 = intervals[axis].minValue();
|
||
|
double v2 = intervals[axis].maxValue();
|
||
|
|
||
|
if ( !plt->axisScaleDiv( axis ).isIncreasing() )
|
||
|
qSwap( v1, v2 );
|
||
|
|
||
|
if ( d_data->inReplot >= 1 )
|
||
|
d_data->axisData[axis].scaleDiv = plt->axisScaleDiv( axis );
|
||
|
|
||
|
if ( d_data->inReplot >= 2 )
|
||
|
{
|
||
|
QList<double> ticks[QwtScaleDiv::NTickTypes];
|
||
|
for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ )
|
||
|
ticks[i] = d_data->axisData[axis].scaleDiv.ticks( i );
|
||
|
|
||
|
plt->setAxisScaleDiv( axis, QwtScaleDiv( v1, v2, ticks ) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
plt->setAxisScale( axis, v1, v2 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QwtPlotCanvas *canvas = qobject_cast<QwtPlotCanvas *>( plt->canvas() );
|
||
|
|
||
|
bool immediatePaint = false;
|
||
|
if ( canvas )
|
||
|
{
|
||
|
immediatePaint = canvas->testPaintAttribute( QwtPlotCanvas::ImmediatePaint );
|
||
|
canvas->setPaintAttribute( QwtPlotCanvas::ImmediatePaint, false );
|
||
|
}
|
||
|
|
||
|
plt->setAutoReplot( doReplot );
|
||
|
|
||
|
d_data->inReplot++;
|
||
|
plt->replot();
|
||
|
d_data->inReplot--;
|
||
|
|
||
|
if ( canvas && immediatePaint )
|
||
|
{
|
||
|
canvas->setPaintAttribute( QwtPlotCanvas::ImmediatePaint, true );
|
||
|
}
|
||
|
}
|