QGIS/external/qwt-6.3.0/qwt_plot_layout.cpp
Juergen E. Fischer 33fc476d89 * replace external qwtpolar with qwt 6.3
* require qwt >=6.2 (and fallback to internal 6.3 if system's qwt doesn't suffice)
* debian doesn't have qwt for Qt6 and won't have it for trixie
2025-07-23 07:11:51 +10:00

1707 lines
49 KiB
C++

/******************************************************************************
* 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_layout.h"
#include "qwt_text.h"
#include "qwt_text_label.h"
#include "qwt_scale_widget.h"
#include "qwt_abstract_legend.h"
#include "qwt_math.h"
#include <qmargins.h>
namespace
{
class LayoutData
{
public:
struct LegendData
{
void init( const QwtAbstractLegend* legend )
{
if ( legend )
{
frameWidth = legend->frameWidth();
hScrollExtent = legend->scrollExtent( Qt::Horizontal );
vScrollExtent = legend->scrollExtent( Qt::Vertical );
hint = legend->sizeHint();
}
}
QSize legendHint( const QwtAbstractLegend* legend, const QRectF& rect ) const
{
const int w = qMin( hint.width(), qwtFloor( rect.width() ) );
int h = legend->heightForWidth( w );
if ( h <= 0 )
h = hint.height();
return QSize( w, h );
}
int frameWidth;
int hScrollExtent;
int vScrollExtent;
QSize hint;
};
struct LabelData
{
void init( const QwtTextLabel* label )
{
frameWidth = 0;
text = QwtText();
if ( label )
{
text = label->text();
if ( !( text.testPaintAttribute( QwtText::PaintUsingTextFont ) ) )
text.setFont( label->font() );
frameWidth = label->frameWidth();
}
}
QwtText text;
int frameWidth;
};
struct ScaleData
{
void init( const QwtScaleWidget* axisWidget )
{
isVisible = true;
scaleWidget = axisWidget;
scaleFont = axisWidget->font();
start = axisWidget->startBorderDist();
end = axisWidget->endBorderDist();
baseLineOffset = axisWidget->margin();
tickOffset = axisWidget->margin();
if ( axisWidget->scaleDraw()->hasComponent( QwtAbstractScaleDraw::Ticks ) )
tickOffset += axisWidget->scaleDraw()->maxTickLength();
dimWithoutTitle = axisWidget->dimForLength( QWIDGETSIZE_MAX, scaleFont );
if ( !axisWidget->title().isEmpty() )
dimWithoutTitle -= axisWidget->titleHeightForWidth( QWIDGETSIZE_MAX );
}
void reset()
{
isVisible = false;
start = 0;
end = 0;
baseLineOffset = 0;
tickOffset = 0.0;
dimWithoutTitle = 0;
}
bool isVisible;
const QwtScaleWidget* scaleWidget;
QFont scaleFont;
int start;
int end;
int baseLineOffset;
double tickOffset;
int dimWithoutTitle;
};
struct CanvasData
{
void init( const QWidget* canvas )
{
const QMargins m = canvas->contentsMargins();
contentsMargins[ QwtAxis::YLeft ] = m.left();
contentsMargins[ QwtAxis::XTop ] = m.top();
contentsMargins[ QwtAxis::YRight ] = m.right();
contentsMargins[ QwtAxis::XBottom ] = m.bottom();
}
int contentsMargins[ QwtAxis::AxisPositions ];
};
public:
enum Label
{
Title,
Footer,
NumLabels
};
LayoutData( const QwtPlot* );
bool hasSymmetricYAxes() const;
inline ScaleData& axisData( QwtAxisId axisId )
{
return m_scaleData[ axisId ];
}
inline const ScaleData& axisData( QwtAxisId axisId ) const
{
return m_scaleData[ axisId ];
}
inline double tickOffset( int axisPos ) const
{
return axisData( axisPos ).tickOffset;
}
LegendData legendData;
LabelData labelData[ NumLabels ];
CanvasData canvasData;
private:
ScaleData m_scaleData[ QwtAxis::AxisPositions ];
};
/*
Extract all layout relevant data from the plot components
*/
LayoutData::LayoutData( const QwtPlot* plot )
{
legendData.init( plot->legend() );
labelData[ Title ].init( plot->titleLabel() );
labelData[ Footer ].init( plot->footerLabel() );
for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
{
{
const QwtAxisId axisId( axisPos );
ScaleData& scaleData = axisData( axisId );
if ( plot->isAxisVisible( axisId ) )
{
const QwtScaleWidget* scaleWidget = plot->axisWidget( axisId );
scaleData.init( scaleWidget );
}
else
{
scaleData.reset();
}
}
}
canvasData.init( plot->canvas() );
}
bool LayoutData::hasSymmetricYAxes() const
{
using namespace QwtAxis;
return m_scaleData[ YLeft ].isVisible == m_scaleData[ YRight ].isVisible;
}
}
namespace
{
class LayoutHintData
{
public:
LayoutHintData( const QwtPlot* plot );
int alignedSize( const QwtAxisId ) const;
inline int yAxesWidth() const
{
using namespace QwtAxis;
return axesWidth( YLeft ) + axesWidth( YRight );
}
inline int yAxesHeight() const
{
using namespace QwtAxis;
return qMax( axesHeight( YLeft ), axesHeight( YRight ) );
}
inline int xAxesHeight() const
{
using namespace QwtAxis;
return axesHeight( XTop ) + axesHeight( XBottom );
}
inline int xAxesWidth() const
{
using namespace QwtAxis;
return qMax( axesWidth( XTop ), axesWidth( XBottom ) );
}
private:
struct ScaleData
{
ScaleData()
{
w = h = minLeft = minRight = tickOffset = 0;
}
int w;
int h;
int minLeft;
int minRight;
int tickOffset;
};
const ScaleData& axisData( QwtAxisId axisId ) const
{
return m_scaleData[ axisId ];
}
ScaleData& axisData( QwtAxisId axisId )
{
return m_scaleData[ axisId ];
}
inline int axesWidth( int axisPos ) const
{
return m_scaleData[axisPos].w;
}
inline int axesHeight( int axisPos ) const
{
return m_scaleData[axisPos].h;
}
int m_canvasBorder[QwtAxis::AxisPositions];
ScaleData m_scaleData[QwtAxis::AxisPositions];
};
LayoutHintData::LayoutHintData( const QwtPlot* plot )
{
using namespace QwtAxis;
const QMargins m = plot->canvas()->contentsMargins();
int contentsMargins[ 4 ];
contentsMargins[ YLeft ] = m.left();
contentsMargins[ XTop ] = m.top();
contentsMargins[ YRight ] = m.right();
contentsMargins[ XBottom ] = m.bottom();
for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
{
m_canvasBorder[axisPos] = contentsMargins[axisPos] +
plot->plotLayout()->canvasMargin( axisPos ) + 1;
{
const QwtAxisId axisId( axisPos );
if ( plot->isAxisVisible( axisId ) )
{
const QwtScaleWidget* scl = plot->axisWidget( axisId );
const QSize hint = scl->minimumSizeHint();
ScaleData& sd = axisData( axisId );
sd.w = hint.width();
sd.h = hint.height();
scl->getBorderDistHint( sd.minLeft, sd.minRight );
{
sd.tickOffset = scl->margin();
if ( scl->scaleDraw()->hasComponent( QwtAbstractScaleDraw::Ticks ) )
sd.tickOffset += qwtCeil( scl->scaleDraw()->maxTickLength() );
}
}
}
}
for ( int axis = 0; axis < AxisPositions; axis++ )
{
const int sz = alignedSize( axis );
ScaleData& sd = axisData( axis );
if ( isXAxis( axis ) )
sd.w = sz;
else
sd.h = sz;
}
}
int LayoutHintData::alignedSize( const QwtAxisId axisId ) const
{
using namespace QwtAxis;
const ScaleData& sd = axisData( axisId );
if ( sd.w && isXAxis( axisId ) )
{
int w = sd.w;
if ( const int leftW = axesWidth( YLeft ) )
{
const int shiftLeft = sd.minLeft - m_canvasBorder[YLeft];
if ( shiftLeft > 0 )
w -= qMin( shiftLeft, leftW );
}
if ( const int rightW = axesWidth( YRight ) )
{
const int shiftRight = sd.minRight - m_canvasBorder[YRight];
if ( shiftRight > 0 )
w -= qMin( shiftRight, rightW );
}
return w;
}
if ( sd.h && isYAxis( axisId ) )
{
int h = sd.h;
if ( axesHeight( XBottom ) )
{
const int shiftBottom = sd.minLeft - m_canvasBorder[XBottom];
if ( shiftBottom > 0 )
h -= qMin( shiftBottom, axisData( XBottom ).tickOffset );
}
if ( axesHeight( XTop ) )
{
const int shiftTop = sd.minRight - m_canvasBorder[XTop];
if ( shiftTop > 0 )
h -= qMin( shiftTop, axisData( XTop ).tickOffset );
}
return h;
}
return 0;
}
}
namespace
{
class LayoutEngine
{
public:
struct Dimensions
{
Dimensions()
{
dimTitle = dimFooter = 0;
for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
m_dimAxes[axisPos] = 0;
}
inline int dimAxis( QwtAxisId axisId ) const
{
return m_dimAxes[ axisId ];
}
void setDimAxis( QwtAxisId axisId, int dim )
{
m_dimAxes[ axisId ] = dim;
}
inline int dimAxes( int axisPos ) const
{
return m_dimAxes[ axisPos ];
}
inline int dimYAxes() const
{
return dimAxes( QwtAxis::YLeft ) + dimAxes( QwtAxis::YRight );
}
inline int dimXAxes() const
{
return dimAxes( QwtAxis::XTop ) + dimAxes( QwtAxis::XBottom );
}
inline QRectF centered( const QRectF& rect, const QRectF& labelRect ) const
{
QRectF r = labelRect;
r.setX( rect.left() + dimAxes( QwtAxis::YLeft ) );
r.setWidth( rect.width() - dimYAxes() );
return r;
}
inline QRectF innerRect( const QRectF& rect ) const
{
QRectF r(
rect.x() + dimAxes( QwtAxis::YLeft ),
rect.y() + dimAxes( QwtAxis::XTop ),
rect.width() - dimYAxes(),
rect.height() - dimXAxes() );
if ( r.width() < 0 )
{
r.setX( rect.center().x() );
r.setWidth( 0 );
}
if ( r.height() < 0 )
{
r.setY( rect.center().y() );
r.setHeight( 0 );
}
return r;
}
int dimTitle;
int dimFooter;
private:
int m_dimAxes[QwtAxis::AxisPositions];
};
LayoutEngine()
: m_legendPos( QwtPlot::BottomLegend )
, m_legendRatio( 1.0 )
, m_spacing( 5 )
{
}
QRectF layoutLegend( QwtPlotLayout::Options,
const LayoutData::LegendData&, const QRectF&, const QSize& ) const;
QRectF alignLegend( const QSize& legendHint,
const QRectF& canvasRect, const QRectF& legendRect ) const;
void alignScales( QwtPlotLayout::Options,
const LayoutData&, QRectF& canvasRect,
QRectF scaleRect[QwtAxis::AxisPositions] ) const;
Dimensions layoutDimensions( QwtPlotLayout::Options,
const LayoutData&, const QRectF& ) const;
inline void setSpacing( unsigned int spacing ) { m_spacing = spacing; }
inline unsigned int spacing() const { return m_spacing; }
inline void setAlignCanvas( int axisPos, bool on ) { m_alignCanvas[ axisPos ] = on; }
inline bool alignCanvas( int axisPos ) const { return m_alignCanvas[ axisPos ]; }
inline void setCanvasMargin( int axisPos, int margin ) { m_canvasMargin[ axisPos ] = margin; }
inline int canvasMargin( int axisPos ) const { return m_canvasMargin[ axisPos ]; }
inline void setLegendPos( QwtPlot::LegendPosition pos ) { m_legendPos = pos; }
inline QwtPlot::LegendPosition legendPos() const { return m_legendPos; }
inline void setLegendRatio( double ratio ) { m_legendRatio = ratio; }
inline double legendRatio() const { return m_legendRatio; }
private:
int heightForWidth( LayoutData::Label, const LayoutData&,
QwtPlotLayout::Options, double width, int axesWidth ) const;
QwtPlot::LegendPosition m_legendPos;
double m_legendRatio;
unsigned int m_canvasMargin[QwtAxis::AxisPositions];
bool m_alignCanvas[QwtAxis::AxisPositions];
unsigned int m_spacing;
};
}
QRectF LayoutEngine::layoutLegend( QwtPlotLayout::Options options,
const LayoutData::LegendData& legendData,
const QRectF& rect, const QSize& legendHint ) const
{
int dim;
if ( m_legendPos == QwtPlot::LeftLegend
|| m_legendPos == QwtPlot::RightLegend )
{
// We don't allow vertical legends to take more than
// half of the available space.
dim = qMin( legendHint.width(), int( rect.width() * m_legendRatio ) );
if ( !( options & QwtPlotLayout::IgnoreScrollbars ) )
{
if ( legendHint.height() > rect.height() )
{
// The legend will need additional
// space for the vertical scrollbar.
dim += legendData.hScrollExtent;
}
}
}
else
{
dim = qMin( legendHint.height(), int( rect.height() * m_legendRatio ) );
dim = qMax( dim, legendData.vScrollExtent );
}
QRectF legendRect = rect;
switch ( m_legendPos )
{
case QwtPlot::LeftLegend:
{
legendRect.setWidth( dim );
break;
}
case QwtPlot::RightLegend:
{
legendRect.setX( rect.right() - dim );
legendRect.setWidth( dim );
break;
}
case QwtPlot::TopLegend:
{
legendRect.setHeight( dim );
break;
}
case QwtPlot::BottomLegend:
{
legendRect.setY( rect.bottom() - dim );
legendRect.setHeight( dim );
break;
}
}
return legendRect;
}
QRectF LayoutEngine::alignLegend( const QSize& legendHint,
const QRectF& canvasRect, const QRectF& legendRect ) const
{
QRectF alignedRect = legendRect;
if ( m_legendPos == QwtPlot::BottomLegend
|| m_legendPos == QwtPlot::TopLegend )
{
if ( legendHint.width() < canvasRect.width() )
{
alignedRect.setX( canvasRect.x() );
alignedRect.setWidth( canvasRect.width() );
}
}
else
{
if ( legendHint.height() < canvasRect.height() )
{
alignedRect.setY( canvasRect.y() );
alignedRect.setHeight( canvasRect.height() );
}
}
return alignedRect;
}
int LayoutEngine::heightForWidth(
LayoutData::Label labelType, const LayoutData& layoutData,
QwtPlotLayout::Options options,
double width, int axesWidth ) const
{
const LayoutData::LabelData& labelData = layoutData.labelData[ labelType ];
if ( labelData.text.isEmpty() )
return 0;
double w = width;
if ( !layoutData.hasSymmetricYAxes() )
{
// center to the canvas
w -= axesWidth;
}
int d = qwtCeil( labelData.text.heightForWidth( w ) );
if ( !( options & QwtPlotLayout::IgnoreFrames ) )
d += 2 * labelData.frameWidth;
return d;
}
LayoutEngine::Dimensions LayoutEngine::layoutDimensions( QwtPlotLayout::Options options,
const LayoutData& layoutData, const QRectF& rect ) const
{
using namespace QwtAxis;
Dimensions dimensions;
int backboneOffset[AxisPositions];
for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
{
backboneOffset[axisPos] = 0;
if ( !( options & QwtPlotLayout::IgnoreFrames ) )
backboneOffset[axisPos] += layoutData.canvasData.contentsMargins[axisPos];
if ( !m_alignCanvas[axisPos] )
backboneOffset[axisPos] += m_canvasMargin[axisPos];
}
bool done = false;
while ( !done )
{
done = true;
// the size for the 4 axis depend on each other. Expanding
// the height of a horizontal axis will shrink the height
// for the vertical axis, shrinking the height of a vertical
// axis will result in a line break what will expand the
// width and results in shrinking the width of a horizontal
// axis what might result in a line break of a horizontal
// axis ... . So we loop as long until no size changes.
if ( !( options & QwtPlotLayout::IgnoreTitle ) )
{
const int d = heightForWidth(
LayoutData::Title, layoutData, options,
rect.width(), dimensions.dimYAxes() );
if ( d > dimensions.dimTitle )
{
dimensions.dimTitle = d;
done = false;
}
}
if ( !( options & QwtPlotLayout::IgnoreFooter ) )
{
const int d = heightForWidth(
LayoutData::Footer, layoutData, options,
rect.width(), dimensions.dimYAxes() );
if ( d > dimensions.dimFooter )
{
dimensions.dimFooter = d;
done = false;
}
}
for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
{
{
const QwtAxisId axisId( axisPos );
const LayoutData::ScaleData& scaleData = layoutData.axisData( axisId );
if ( scaleData.isVisible )
{
double length;
if ( isXAxis( axisPos ) )
{
length = rect.width() - dimensions.dimYAxes();
length -= scaleData.start + scaleData.end;
if ( dimensions.dimAxes( YRight ) > 0 )
length -= 1;
length += qMin( dimensions.dimAxes( YLeft ),
scaleData.start - backboneOffset[YLeft] );
length += qMin( dimensions.dimAxes( YRight ),
scaleData.end - backboneOffset[YRight] );
}
else // y axis
{
length = rect.height() - dimensions.dimXAxes();
length -= scaleData.start + scaleData.end;
length -= 1;
if ( dimensions.dimAxes( XBottom ) <= 0 )
length -= 1;
if ( dimensions.dimAxes( XTop ) <= 0 )
length -= 1;
/*
The tick labels of the y axes are always left/right from the
backbone/ticks of the x axes - but we have to take care,
that the labels don't overlap.
*/
if ( dimensions.dimAxes( XBottom ) > 0 )
{
length += qMin(
layoutData.tickOffset( XBottom ),
double( scaleData.start - backboneOffset[XBottom] ) );
}
if ( dimensions.dimAxes( XTop ) > 0 )
{
length += qMin(
layoutData.tickOffset( XTop ),
double( scaleData.end - backboneOffset[XTop] ) );
}
if ( dimensions.dimTitle > 0 )
length -= dimensions.dimTitle + m_spacing;
}
int d = scaleData.dimWithoutTitle;
if ( !scaleData.scaleWidget->title().isEmpty() )
{
d += scaleData.scaleWidget->titleHeightForWidth( qwtFloor( length ) );
}
if ( d > dimensions.dimAxis( axisId ) )
{
dimensions.setDimAxis( axisId, d );
done = false;
}
}
}
}
}
return dimensions;
}
void LayoutEngine::alignScales( QwtPlotLayout::Options options,
const LayoutData& layoutData, QRectF& canvasRect,
QRectF scaleRect[QwtAxis::AxisPositions] ) const
{
using namespace QwtAxis;
int backboneOffset[AxisPositions];
for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
{
backboneOffset[axisPos] = 0;
if ( !m_alignCanvas[axisPos] )
{
backboneOffset[axisPos] += m_canvasMargin[axisPos];
}
if ( !( options & QwtPlotLayout::IgnoreFrames ) )
{
backboneOffset[axisPos] +=
layoutData.canvasData.contentsMargins[axisPos];
}
}
for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
{
{
QRectF& axisRect = scaleRect[axisPos];
if ( !axisRect.isValid() )
continue;
const QwtAxisId axisId( axisPos );
const int startDist = layoutData.axisData( axisId ).start;
const int endDist = layoutData.axisData( axisId ).end;
if ( isXAxis( axisPos ) )
{
const QRectF& leftScaleRect = scaleRect[YLeft];
const int leftOffset = backboneOffset[YLeft] - startDist;
if ( leftScaleRect.isValid() )
{
const double dx = leftOffset + leftScaleRect.width();
if ( m_alignCanvas[YLeft] && dx < 0.0 )
{
/*
The axis needs more space than the width
of the left scale.
*/
const double cLeft = canvasRect.left(); // qreal -> double
canvasRect.setLeft( qwtMaxF( cLeft, axisRect.left() - dx ) );
}
else
{
const double minLeft = leftScaleRect.left();
const double left = axisRect.left() + leftOffset;
axisRect.setLeft( qwtMaxF( left, minLeft ) );
}
}
else
{
if ( m_alignCanvas[YLeft] && leftOffset < 0 )
{
canvasRect.setLeft( qwtMaxF( canvasRect.left(),
axisRect.left() - leftOffset ) );
}
else
{
if ( leftOffset > 0 )
axisRect.setLeft( axisRect.left() + leftOffset );
}
}
const QRectF& rightScaleRect = scaleRect[YRight];
const int rightOffset = backboneOffset[YRight] - endDist + 1;
if ( rightScaleRect.isValid() )
{
const double dx = rightOffset + rightScaleRect.width();
if ( m_alignCanvas[YRight] && dx < 0 )
{
/*
The axis needs more space than the width
of the right scale.
*/
const double cRight = canvasRect.right(); // qreal -> double
canvasRect.setRight( qwtMinF( cRight, axisRect.right() + dx ) );
}
const double maxRight = rightScaleRect.right();
const double right = axisRect.right() - rightOffset;
axisRect.setRight( qwtMinF( right, maxRight ) );
}
else
{
if ( m_alignCanvas[YRight] && rightOffset < 0 )
{
canvasRect.setRight( qwtMinF( canvasRect.right(),
axisRect.right() + rightOffset ) );
}
else
{
if ( rightOffset > 0 )
axisRect.setRight( axisRect.right() - rightOffset );
}
}
}
else // y axes
{
const QRectF& bottomScaleRect = scaleRect[XBottom];
const int bottomOffset = backboneOffset[XBottom] - endDist + 1;
if ( bottomScaleRect.isValid() )
{
const double dy = bottomOffset + bottomScaleRect.height();
if ( m_alignCanvas[XBottom] && dy < 0 )
{
/*
The axis needs more space than the height
of the bottom scale.
*/
const double cBottom = canvasRect.bottom(); // qreal -> double
canvasRect.setBottom( qwtMinF( cBottom, axisRect.bottom() + dy ) );
}
else
{
const double maxBottom = bottomScaleRect.top() +
layoutData.tickOffset( XBottom );
const double bottom = axisRect.bottom() - bottomOffset;
axisRect.setBottom( qwtMinF( bottom, maxBottom ) );
}
}
else
{
if ( m_alignCanvas[XBottom] && bottomOffset < 0 )
{
canvasRect.setBottom( qwtMinF( canvasRect.bottom(),
axisRect.bottom() + bottomOffset ) );
}
else
{
if ( bottomOffset > 0 )
axisRect.setBottom( axisRect.bottom() - bottomOffset );
}
}
const QRectF& topScaleRect = scaleRect[XTop];
const int topOffset = backboneOffset[XTop] - startDist;
if ( topScaleRect.isValid() )
{
const double dy = topOffset + topScaleRect.height();
if ( m_alignCanvas[XTop] && dy < 0 )
{
/*
The axis needs more space than the height
of the top scale.
*/
const double cTop = canvasRect.top(); // qreal -> double
canvasRect.setTop( qwtMaxF( cTop, axisRect.top() - dy ) );
}
else
{
const double minTop = topScaleRect.bottom() -
layoutData.tickOffset( XTop );
const double top = axisRect.top() + topOffset;
axisRect.setTop( qwtMaxF( top, minTop ) );
}
}
else
{
if ( m_alignCanvas[XTop] && topOffset < 0 )
{
canvasRect.setTop( qwtMaxF( canvasRect.top(),
axisRect.top() - topOffset ) );
}
else
{
if ( topOffset > 0 )
axisRect.setTop( axisRect.top() + topOffset );
}
}
}
}
}
/*
The canvas has been aligned to the scale with largest
border distances. Now we have to realign the other scale.
*/
for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
{
{
const QwtAxisId axisId( axisPos );
QRectF& sRect = scaleRect[axisPos];
const LayoutData::ScaleData& axisData = layoutData.axisData( axisId );
if ( !sRect.isValid() )
continue;
if ( isXAxis( axisPos ) )
{
if ( m_alignCanvas[YLeft] )
{
double y = canvasRect.left() - axisData.start;
if ( !( options & QwtPlotLayout::IgnoreFrames ) )
y += layoutData.canvasData.contentsMargins[YLeft];
sRect.setLeft( y );
}
if ( m_alignCanvas[YRight] )
{
double y = canvasRect.right() - 1 + axisData.end;
if ( !( options & QwtPlotLayout::IgnoreFrames ) )
y -= layoutData.canvasData.contentsMargins[YRight];
sRect.setRight( y );
}
if ( m_alignCanvas[axisPos] )
{
if ( axisPos == XTop )
sRect.setBottom( canvasRect.top() );
else
sRect.setTop( canvasRect.bottom() );
}
}
else
{
if ( m_alignCanvas[XTop] )
{
double x = canvasRect.top() - axisData.start;
if ( !( options & QwtPlotLayout::IgnoreFrames ) )
x += layoutData.canvasData.contentsMargins[XTop];
sRect.setTop( x );
}
if ( m_alignCanvas[XBottom] )
{
double x = canvasRect.bottom() - 1 + axisData.end;
if ( !( options & QwtPlotLayout::IgnoreFrames ) )
x -= layoutData.canvasData.contentsMargins[XBottom];
sRect.setBottom( x );
}
if ( m_alignCanvas[axisPos] )
{
if ( axisPos == YLeft )
sRect.setRight( canvasRect.left() );
else
sRect.setLeft( canvasRect.right() );
}
}
}
}
}
class QwtPlotLayout::PrivateData
{
public:
QRectF titleRect;
QRectF footerRect;
QRectF legendRect;
QRectF scaleRects[QwtAxis::AxisPositions];
QRectF canvasRect;
LayoutEngine engine;
};
/*!
\brief Constructor
*/
QwtPlotLayout::QwtPlotLayout()
{
m_data = new PrivateData;
setLegendPosition( QwtPlot::BottomLegend );
setCanvasMargin( 4 );
setAlignCanvasToScales( false );
invalidate();
}
//! Destructor
QwtPlotLayout::~QwtPlotLayout()
{
delete m_data;
}
/*!
Change a margin of the canvas. The margin is the space
above/below the scale ticks. A negative margin will
be set to -1, excluding the borders of the scales.
\param margin New margin
\param axisPos One of QwtAxis::Position. Specifies where the position of the margin.
-1 means margin at all borders.
\sa canvasMargin()
\warning The margin will have no effect when alignCanvasToScale() is true
*/
void QwtPlotLayout::setCanvasMargin( int margin, int axisPos )
{
if ( margin < -1 )
margin = -1;
LayoutEngine& engine = m_data->engine;
if ( axisPos == -1 )
{
for ( axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
engine.setCanvasMargin( axisPos, margin );
}
else if ( QwtAxis::isValid( axisPos ) )
{
engine.setCanvasMargin( axisPos, margin );
}
}
/*!
\param axisPos Axis position
\return Margin around the scale tick borders
\sa setCanvasMargin()
*/
int QwtPlotLayout::canvasMargin( int axisPos ) const
{
if ( !QwtAxis::isValid( axisPos ) )
return 0;
return m_data->engine.canvasMargin( axisPos );
}
/*!
\brief Set the align-canvas-to-axis-scales flag for all axes
\param on True/False
\sa setAlignCanvasToScale(), alignCanvasToScale()
*/
void QwtPlotLayout::setAlignCanvasToScales( bool on )
{
for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
m_data->engine.setAlignCanvas( axisPos, on );
}
/*!
Change the align-canvas-to-axis-scales setting. The canvas may:
- extend beyond the axis scale ends to maximize its size,
- align with the axis scale ends to control its size.
The axisId parameter is somehow confusing as it identifies a border
of the plot and not the axes, that are aligned. F.e when QwtAxis::YLeft
is set, the left end of the the x-axes ( QwtAxis::XTop, QwtAxis::XBottom )
is aligned.
\param axisPos Axis position
\param on New align-canvas-to-axis-scales setting
\sa setCanvasMargin(), alignCanvasToScale(), setAlignCanvasToScales()
\warning In case of on == true canvasMargin() will have no effect
*/
void QwtPlotLayout::setAlignCanvasToScale( int axisPos, bool on )
{
if ( QwtAxis::isValid( axisPos ) )
m_data->engine.setAlignCanvas( axisPos, on );
}
/*!
Return the align-canvas-to-axis-scales setting. The canvas may:
- extend beyond the axis scale ends to maximize its size
- align with the axis scale ends to control its size.
\param axisPos Axis position
\return align-canvas-to-axis-scales setting
\sa setAlignCanvasToScale(), setAlignCanvasToScale(), setCanvasMargin()
*/
bool QwtPlotLayout::alignCanvasToScale( int axisPos ) const
{
if ( !QwtAxis::isValid( axisPos ) )
return false;
return m_data->engine.alignCanvas( axisPos );
}
/*!
Change the spacing of the plot. The spacing is the distance
between the plot components.
\param spacing New spacing
\sa setCanvasMargin(), spacing()
*/
void QwtPlotLayout::setSpacing( int spacing )
{
m_data->engine.setSpacing( qMax( 0, spacing ) );
}
/*!
\return Spacing
\sa margin(), setSpacing()
*/
int QwtPlotLayout::spacing() const
{
return m_data->engine.spacing();
}
/*!
\brief Specify the position of the legend
\param pos The legend's position.
\param ratio Ratio between legend and the bounding rectangle
of title, footer, canvas and axes. The legend will be shrunk
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 QwtPlot::setLegendPosition()
*/
void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos, double ratio )
{
if ( ratio > 1.0 )
ratio = 1.0;
LayoutEngine& engine = m_data->engine;
switch ( pos )
{
case QwtPlot::TopLegend:
case QwtPlot::BottomLegend:
{
if ( ratio <= 0.0 )
ratio = 0.33;
engine.setLegendRatio( ratio );
engine.setLegendPos( pos );
break;
}
case QwtPlot::LeftLegend:
case QwtPlot::RightLegend:
{
if ( ratio <= 0.0 )
ratio = 0.5;
engine.setLegendRatio( ratio );
engine.setLegendPos( pos );
break;
}
default:
break;
}
}
/*!
\brief Specify the position of the legend
\param pos The legend's position. Valid values are
\c QwtPlot::LeftLegend, \c QwtPlot::RightLegend,
\c QwtPlot::TopLegend, \c QwtPlot::BottomLegend.
\sa QwtPlot::setLegendPosition()
*/
void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos )
{
setLegendPosition( pos, 0.0 );
}
/*!
\return Position of the legend
\sa setLegendPosition(), QwtPlot::setLegendPosition(),
QwtPlot::legendPosition()
*/
QwtPlot::LegendPosition QwtPlotLayout::legendPosition() const
{
return m_data->engine.legendPos();
}
/*!
Specify the relative size of the legend in the plot
\param ratio Ratio between legend and the bounding rectangle
of title, footer, canvas and axes. The legend will be shrunk
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.
*/
void QwtPlotLayout::setLegendRatio( double ratio )
{
setLegendPosition( legendPosition(), ratio );
}
/*!
\return The relative size of the legend in the plot.
\sa setLegendPosition()
*/
double QwtPlotLayout::legendRatio() const
{
return m_data->engine.legendRatio();
}
/*!
\brief Set the geometry for the title
This method is intended to be used from derived layouts
overloading activate()
\sa titleRect(), activate()
*/
void QwtPlotLayout::setTitleRect( const QRectF& rect )
{
m_data->titleRect = rect;
}
/*!
\return Geometry for the title
\sa activate(), invalidate()
*/
QRectF QwtPlotLayout::titleRect() const
{
return m_data->titleRect;
}
/*!
\brief Set the geometry for the footer
This method is intended to be used from derived layouts
overloading activate()
\sa footerRect(), activate()
*/
void QwtPlotLayout::setFooterRect( const QRectF& rect )
{
m_data->footerRect = rect;
}
/*!
\return Geometry for the footer
\sa activate(), invalidate()
*/
QRectF QwtPlotLayout::footerRect() const
{
return m_data->footerRect;
}
/*!
\brief Set the geometry for the legend
This method is intended to be used from derived layouts
overloading activate()
\param rect Rectangle for the legend
\sa legendRect(), activate()
*/
void QwtPlotLayout::setLegendRect( const QRectF& rect )
{
m_data->legendRect = rect;
}
/*!
\return Geometry for the legend
\sa activate(), invalidate()
*/
QRectF QwtPlotLayout::legendRect() const
{
return m_data->legendRect;
}
/*!
\brief Set the geometry for an axis
This method is intended to be used from derived layouts
overloading activate()
\param axisId Axis
\param rect Rectangle for the scale
\sa scaleRect(), activate()
*/
void QwtPlotLayout::setScaleRect( QwtAxisId axisId, const QRectF& rect )
{
if ( QwtAxis::isValid( axisId ) )
m_data->scaleRects[axisId] = rect;
}
/*!
\param axisId Axis
\return Geometry for the scale
\sa activate(), invalidate()
*/
QRectF QwtPlotLayout::scaleRect( QwtAxisId axisId ) const
{
if ( QwtAxis::isValid( axisId ) )
return m_data->scaleRects[axisId];
return QRectF();
}
/*!
\brief Set the geometry for the canvas
This method is intended to be used from derived layouts
overloading activate()
\sa canvasRect(), activate()
*/
void QwtPlotLayout::setCanvasRect( const QRectF& rect )
{
m_data->canvasRect = rect;
}
/*!
\return Geometry for the canvas
\sa activate(), invalidate()
*/
QRectF QwtPlotLayout::canvasRect() const
{
return m_data->canvasRect;
}
/*!
Invalidate the geometry of all components.
\sa activate()
*/
void QwtPlotLayout::invalidate()
{
m_data->titleRect = m_data->footerRect =
m_data->legendRect = m_data->canvasRect = QRectF();
for ( int axisPos = 0; axisPos < QwtAxis::AxisPositions; axisPos++ )
m_data->scaleRects[axisPos] = QRect();
}
/*!
\return Minimum size hint
\param plot Plot widget
\sa QwtPlot::minimumSizeHint()
*/
QSize QwtPlotLayout::minimumSizeHint( const QwtPlot* plot ) const
{
LayoutHintData hintData( plot );
const int xAxesWidth = hintData.xAxesWidth();
const int yAxesHeight = hintData.yAxesHeight();
const QWidget* canvas = plot->canvas();
const QMargins m = canvas->contentsMargins();
const QSize minCanvasSize = canvas->minimumSize();
int w = hintData.yAxesWidth();
int cw = xAxesWidth + m.left() + 1 + m.right() + 1;
w += qMax( cw, minCanvasSize.width() );
int h = hintData.xAxesHeight();
int ch = yAxesHeight + m.top() + 1 + m.bottom() + 1;
h += qMax( ch, minCanvasSize.height() );
const QwtTextLabel* labels[2];
labels[0] = plot->titleLabel();
labels[1] = plot->footerLabel();
for ( int i = 0; i < 2; i++ )
{
const QwtTextLabel* label = labels[i];
if ( label && !label->text().isEmpty() )
{
// we center on the plot canvas.
const bool centerOnCanvas = !( plot->isAxisVisible( QwtAxis::YLeft )
&& plot->isAxisVisible( QwtAxis::YRight ) );
int labelW = w;
if ( centerOnCanvas )
{
labelW -= hintData.yAxesWidth();
}
int labelH = label->heightForWidth( labelW );
if ( labelH > labelW ) // Compensate for a long title
{
w = labelW = labelH;
if ( centerOnCanvas )
w += hintData.yAxesWidth();
labelH = label->heightForWidth( labelW );
}
h += labelH + spacing();
}
}
// Compute the legend contribution
const QwtAbstractLegend* legend = plot->legend();
if ( legend && !legend->isEmpty() )
{
const LayoutEngine& engine = m_data->engine;
if ( engine.legendPos() == QwtPlot::LeftLegend
|| engine.legendPos() == QwtPlot::RightLegend )
{
int legendW = legend->sizeHint().width();
int legendH = legend->heightForWidth( legendW );
if ( legend->frameWidth() > 0 )
w += spacing();
if ( legendH > h )
legendW += legend->scrollExtent( Qt::Horizontal );
if ( engine.legendRatio() < 1.0 )
legendW = qMin( legendW, int( w / ( 1.0 - engine.legendRatio() ) ) );
w += legendW + spacing();
}
else
{
int legendW = qMin( legend->sizeHint().width(), w );
int legendH = legend->heightForWidth( legendW );
if ( legend->frameWidth() > 0 )
h += spacing();
if ( engine.legendRatio() < 1.0 )
legendH = qMin( legendH, int( h / ( 1.0 - engine.legendRatio() ) ) );
h += legendH + spacing();
}
}
return QSize( w, h );
}
/*!
\brief Recalculate the geometry of all components.
\param plot Plot to be layout
\param plotRect Rectangle where to place the components
\param options Layout options
\sa invalidate(), titleRect(), footerRect()
legendRect(), scaleRect(), canvasRect()
*/
void QwtPlotLayout::activate( const QwtPlot* plot,
const QRectF& plotRect, Options options )
{
invalidate();
QRectF rect( plotRect ); // undistributed rest of the plot rect
// We extract all layout relevant parameters from the widgets,
// and save them to m_data->layoutData.
LayoutData layoutData( plot );
QSize legendHint;
if ( !( options & IgnoreLegend )
&& plot->legend() && !plot->legend()->isEmpty() )
{
legendHint = layoutData.legendData.legendHint( plot->legend(), rect );
m_data->legendRect = m_data->engine.layoutLegend(
options, layoutData.legendData, rect, legendHint );
// subtract m_data->legendRect from rect
const QRegion region( rect.toRect() );
rect = region.subtracted( m_data->legendRect.toRect() ).boundingRect();
switch ( m_data->engine.legendPos() )
{
case QwtPlot::LeftLegend:
{
rect.setLeft( rect.left() + spacing() );
break;
}
case QwtPlot::RightLegend:
{
rect.setRight( rect.right() - spacing() );
break;
}
case QwtPlot::TopLegend:
{
rect.setTop( rect.top() + spacing() );
break;
}
case QwtPlot::BottomLegend:
{
rect.setBottom( rect.bottom() - spacing() );
break;
}
}
}
/*
+---+-----------+---+
| Title |
+---+-----------+---+
| | Axis | |
+---+-----------+---+
| A | | A |
| x | Canvas | x |
| i | | i |
| s | | s |
+---+-----------+---+
| | Axis | |
+---+-----------+---+
| Footer |
+---+-----------+---+
*/
// title, footer and axes include text labels. The height of each
// label depends on its line breaks, that depend on the width
// for the label. A line break in a horizontal text will reduce
// the available width for vertical texts and vice versa.
// layoutDimensions finds the height/width for title, footer and axes
// including all line breaks.
using namespace QwtAxis;
const LayoutEngine::Dimensions dimensions =
m_data->engine.layoutDimensions( options, layoutData, rect );
if ( dimensions.dimTitle > 0 )
{
QRectF& labelRect = m_data->titleRect;
labelRect.setRect( rect.left(), rect.top(), rect.width(), dimensions.dimTitle );
rect.setTop( labelRect.bottom() + spacing() );
if ( !layoutData.hasSymmetricYAxes() )
{
// if only one of the y axes is missing we align
// the title centered to the canvas
labelRect = dimensions.centered( rect, labelRect );
}
}
if ( dimensions.dimFooter > 0 )
{
QRectF& labelRect = m_data->footerRect;
labelRect.setRect( rect.left(), rect.bottom() - dimensions.dimFooter,
rect.width(), dimensions.dimFooter );
rect.setBottom( labelRect.top() - spacing() );
if ( !layoutData.hasSymmetricYAxes() )
{
// if only one of the y axes is missing we align
// the footer centered to the canvas
labelRect = dimensions.centered( rect, labelRect );
}
}
m_data->canvasRect = dimensions.innerRect( rect );
for ( int axisPos = 0; axisPos < AxisPositions; axisPos++ )
{
// set the rects for the axes
const int pos = 0;
{
const QwtAxisId axisId( axisPos );
if ( dimensions.dimAxis( axisId ) )
{
const int dim = dimensions.dimAxis( axisId );
const QRectF& canvasRect = m_data->canvasRect;
QRectF& scaleRect = m_data->scaleRects[axisId];
scaleRect = canvasRect;
switch ( axisPos )
{
case YLeft:
{
scaleRect.setX( canvasRect.left() - pos - dim );
scaleRect.setWidth( dim );
break;
}
case YRight:
{
scaleRect.setX( canvasRect.right() + pos );
scaleRect.setWidth( dim );
break;
}
case XBottom:
{
scaleRect.setY( canvasRect.bottom() + pos );
scaleRect.setHeight( dim );
break;
}
case XTop:
{
scaleRect.setY( canvasRect.top() - pos - dim );
scaleRect.setHeight( dim );
break;
}
}
scaleRect = scaleRect.normalized();
}
}
}
// +---+-----------+---+
// | <- Axis -> |
// +-^-+-----------+-^-+
// | | | | | |
// | | | |
// | A | | A |
// | x | Canvas | x |
// | i | | i |
// | s | | s |
// | | | |
// | | | | | |
// +-V-+-----------+-V-+
// | <- Axis -> |
// +---+-----------+---+
// The ticks of the axes - not the labels above - should
// be aligned to the canvas. So we try to use the empty
// corners to extend the axes, so that the label texts
// left/right of the min/max ticks are moved into them.
m_data->engine.alignScales( options, layoutData,
m_data->canvasRect, m_data->scaleRects );
if ( !m_data->legendRect.isEmpty() )
{
// We prefer to align the legend to the canvas - not to
// the complete plot - if possible.
m_data->legendRect = m_data->engine.alignLegend(
legendHint, m_data->canvasRect, m_data->legendRect );
}
}