You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
631 lines
15 KiB
631 lines
15 KiB
/* -*- 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 ); |
|
} |
|
}
|
|
|