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.
603 lines
15 KiB
603 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_intervalcurve.h" |
|
#include "qwt_interval_symbol.h" |
|
#include "qwt_scale_map.h" |
|
#include "qwt_clipper.h" |
|
#include "qwt_painter.h" |
|
#include <string.h> |
|
|
|
#include <qpainter.h> |
|
|
|
static inline bool qwtIsHSampleInside( const QwtIntervalSample &sample, |
|
double xMin, double xMax, double yMin, double yMax ) |
|
{ |
|
const double y = sample.value; |
|
const double x1 = sample.interval.minValue(); |
|
const double x2 = sample.interval.maxValue(); |
|
|
|
const bool isOffScreen = ( y < yMin ) || ( y > yMax ) |
|
|| ( x1 < xMin && x2 < xMin ) || ( x1 > xMax && x2 > xMax ); |
|
|
|
return !isOffScreen; |
|
} |
|
|
|
static inline bool qwtIsVSampleInside( const QwtIntervalSample &sample, |
|
double xMin, double xMax, double yMin, double yMax ) |
|
{ |
|
const double x = sample.value; |
|
const double y1 = sample.interval.minValue(); |
|
const double y2 = sample.interval.maxValue(); |
|
|
|
const bool isOffScreen = ( x < xMin ) || ( x > xMax ) |
|
|| ( y1 < yMin && y2 < yMin ) || ( y1 > yMax && y2 > yMax ); |
|
|
|
return !isOffScreen; |
|
} |
|
|
|
class QwtPlotIntervalCurve::PrivateData |
|
{ |
|
public: |
|
PrivateData(): |
|
style( QwtPlotIntervalCurve::Tube ), |
|
symbol( NULL ), |
|
pen( Qt::black ), |
|
brush( Qt::white ) |
|
{ |
|
paintAttributes = QwtPlotIntervalCurve::ClipPolygons; |
|
paintAttributes |= QwtPlotIntervalCurve::ClipSymbol; |
|
|
|
pen.setCapStyle( Qt::FlatCap ); |
|
} |
|
|
|
~PrivateData() |
|
{ |
|
delete symbol; |
|
} |
|
|
|
QwtPlotIntervalCurve::CurveStyle style; |
|
const QwtIntervalSymbol *symbol; |
|
|
|
QPen pen; |
|
QBrush brush; |
|
|
|
QwtPlotIntervalCurve::PaintAttributes paintAttributes; |
|
}; |
|
|
|
/*! |
|
Constructor |
|
\param title Title of the curve |
|
*/ |
|
QwtPlotIntervalCurve::QwtPlotIntervalCurve( const QwtText &title ): |
|
QwtPlotSeriesItem( title ) |
|
{ |
|
init(); |
|
} |
|
|
|
/*! |
|
Constructor |
|
\param title Title of the curve |
|
*/ |
|
QwtPlotIntervalCurve::QwtPlotIntervalCurve( const QString &title ): |
|
QwtPlotSeriesItem( QwtText( title ) ) |
|
{ |
|
init(); |
|
} |
|
|
|
//! Destructor |
|
QwtPlotIntervalCurve::~QwtPlotIntervalCurve() |
|
{ |
|
delete d_data; |
|
} |
|
|
|
//! Initialize internal members |
|
void QwtPlotIntervalCurve::init() |
|
{ |
|
setItemAttribute( QwtPlotItem::Legend, true ); |
|
setItemAttribute( QwtPlotItem::AutoScale, true ); |
|
|
|
d_data = new PrivateData; |
|
setData( new QwtIntervalSeriesData() ); |
|
|
|
setZ( 19.0 ); |
|
} |
|
|
|
//! \return QwtPlotItem::Rtti_PlotIntervalCurve |
|
int QwtPlotIntervalCurve::rtti() const |
|
{ |
|
return QwtPlotIntervalCurve::Rtti_PlotIntervalCurve; |
|
} |
|
|
|
/*! |
|
Specify an attribute how to draw the curve |
|
|
|
\param attribute Paint attribute |
|
\param on On/Off |
|
\sa testPaintAttribute() |
|
*/ |
|
void QwtPlotIntervalCurve::setPaintAttribute( |
|
PaintAttribute attribute, bool on ) |
|
{ |
|
if ( on ) |
|
d_data->paintAttributes |= attribute; |
|
else |
|
d_data->paintAttributes &= ~attribute; |
|
} |
|
|
|
/*! |
|
\return True, when attribute is enabled |
|
\sa PaintAttribute, setPaintAttribute() |
|
*/ |
|
bool QwtPlotIntervalCurve::testPaintAttribute( |
|
PaintAttribute attribute ) const |
|
{ |
|
return ( d_data->paintAttributes & attribute ); |
|
} |
|
|
|
/*! |
|
Initialize data with an array of samples. |
|
\param samples Vector of samples |
|
*/ |
|
void QwtPlotIntervalCurve::setSamples( |
|
const QVector<QwtIntervalSample> &samples ) |
|
{ |
|
setData( new QwtIntervalSeriesData( samples ) ); |
|
} |
|
|
|
/*! |
|
Assign a series of samples |
|
|
|
setSamples() is just a wrapper for setData() without any additional |
|
value - beside that it is easier to find for the developer. |
|
|
|
\param data Data |
|
\warning The item takes ownership of the data object, deleting |
|
it when its not used anymore. |
|
*/ |
|
void QwtPlotIntervalCurve::setSamples( |
|
QwtSeriesData<QwtIntervalSample> *data ) |
|
{ |
|
setData( data ); |
|
} |
|
|
|
/*! |
|
Set the curve's drawing style |
|
|
|
\param style Curve style |
|
\sa CurveStyle, style() |
|
*/ |
|
void QwtPlotIntervalCurve::setStyle( CurveStyle style ) |
|
{ |
|
if ( style != d_data->style ) |
|
{ |
|
d_data->style = style; |
|
|
|
legendChanged(); |
|
itemChanged(); |
|
} |
|
} |
|
|
|
/*! |
|
\return Style of the curve |
|
\sa setStyle() |
|
*/ |
|
QwtPlotIntervalCurve::CurveStyle QwtPlotIntervalCurve::style() const |
|
{ |
|
return d_data->style; |
|
} |
|
|
|
/*! |
|
Assign a symbol. |
|
|
|
\param symbol Symbol |
|
\sa symbol() |
|
*/ |
|
void QwtPlotIntervalCurve::setSymbol( const QwtIntervalSymbol *symbol ) |
|
{ |
|
if ( symbol != d_data->symbol ) |
|
{ |
|
delete d_data->symbol; |
|
d_data->symbol = symbol; |
|
|
|
legendChanged(); |
|
itemChanged(); |
|
} |
|
} |
|
|
|
/*! |
|
\return Current symbol or NULL, when no symbol has been assigned |
|
\sa setSymbol() |
|
*/ |
|
const QwtIntervalSymbol *QwtPlotIntervalCurve::symbol() const |
|
{ |
|
return d_data->symbol; |
|
} |
|
|
|
/*! |
|
Build and assign a pen |
|
|
|
In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it |
|
non cosmetic ( see QPen::isCosmetic() ). This method has been introduced |
|
to hide this incompatibility. |
|
|
|
\param color Pen color |
|
\param width Pen width |
|
\param style Pen style |
|
|
|
\sa pen(), brush() |
|
*/ |
|
void QwtPlotIntervalCurve::setPen( const QColor &color, qreal width, Qt::PenStyle style ) |
|
{ |
|
setPen( QPen( color, width, style ) ); |
|
} |
|
|
|
/*! |
|
\brief Assign a pen |
|
\param pen New pen |
|
\sa pen(), brush() |
|
*/ |
|
void QwtPlotIntervalCurve::setPen( const QPen &pen ) |
|
{ |
|
if ( pen != d_data->pen ) |
|
{ |
|
d_data->pen = pen; |
|
|
|
legendChanged(); |
|
itemChanged(); |
|
} |
|
} |
|
|
|
/*! |
|
\return Pen used to draw the lines |
|
\sa setPen(), brush() |
|
*/ |
|
const QPen& QwtPlotIntervalCurve::pen() const |
|
{ |
|
return d_data->pen; |
|
} |
|
|
|
/*! |
|
Assign a brush. |
|
|
|
The brush is used to fill the area in Tube style(). |
|
|
|
\param brush Brush |
|
\sa brush(), pen(), setStyle(), CurveStyle |
|
*/ |
|
void QwtPlotIntervalCurve::setBrush( const QBrush &brush ) |
|
{ |
|
if ( brush != d_data->brush ) |
|
{ |
|
d_data->brush = brush; |
|
|
|
legendChanged(); |
|
itemChanged(); |
|
} |
|
} |
|
|
|
/*! |
|
\return Brush used to fill the area in Tube style() |
|
\sa setBrush(), setStyle(), CurveStyle |
|
*/ |
|
const QBrush& QwtPlotIntervalCurve::brush() const |
|
{ |
|
return d_data->brush; |
|
} |
|
|
|
/*! |
|
\return Bounding rectangle of all samples. |
|
For an empty series the rectangle is invalid. |
|
*/ |
|
QRectF QwtPlotIntervalCurve::boundingRect() const |
|
{ |
|
QRectF rect = QwtPlotSeriesItem::boundingRect(); |
|
if ( rect.isValid() && orientation() == Qt::Vertical ) |
|
rect.setRect( rect.y(), rect.x(), rect.height(), rect.width() ); |
|
|
|
return rect; |
|
} |
|
|
|
/*! |
|
Draw a subset of the samples |
|
|
|
\param painter Painter |
|
\param xMap Maps x-values into pixel coordinates. |
|
\param yMap Maps y-values into pixel coordinates. |
|
\param canvasRect Contents rectangle of the canvas |
|
\param from Index of the first sample to be painted |
|
\param to Index of the last sample to be painted. If to < 0 the |
|
series will be painted to its last sample. |
|
|
|
\sa drawTube(), drawSymbols() |
|
*/ |
|
void QwtPlotIntervalCurve::drawSeries( QPainter *painter, |
|
const QwtScaleMap &xMap, const QwtScaleMap &yMap, |
|
const QRectF &canvasRect, int from, int to ) const |
|
{ |
|
if ( to < 0 ) |
|
to = dataSize() - 1; |
|
|
|
if ( from < 0 ) |
|
from = 0; |
|
|
|
if ( from > to ) |
|
return; |
|
|
|
switch ( d_data->style ) |
|
{ |
|
case Tube: |
|
drawTube( painter, xMap, yMap, canvasRect, from, to ); |
|
break; |
|
|
|
case NoCurve: |
|
default: |
|
break; |
|
} |
|
|
|
if ( d_data->symbol && |
|
( d_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) ) |
|
{ |
|
drawSymbols( painter, *d_data->symbol, |
|
xMap, yMap, canvasRect, from, to ); |
|
} |
|
} |
|
|
|
/*! |
|
Draw a tube |
|
|
|
Builds 2 curves from the upper and lower limits of the intervals |
|
and draws them with the pen(). The area between the curves is |
|
filled with the brush(). |
|
|
|
\param painter Painter |
|
\param xMap Maps x-values into pixel coordinates. |
|
\param yMap Maps y-values into pixel coordinates. |
|
\param canvasRect Contents rectangle of the canvas |
|
\param from Index of the first sample to be painted |
|
\param to Index of the last sample to be painted. If to < 0 the |
|
series will be painted to its last sample. |
|
|
|
\sa drawSeries(), drawSymbols() |
|
*/ |
|
void QwtPlotIntervalCurve::drawTube( QPainter *painter, |
|
const QwtScaleMap &xMap, const QwtScaleMap &yMap, |
|
const QRectF &canvasRect, int from, int to ) const |
|
{ |
|
const bool doAlign = QwtPainter::roundingAlignment( painter ); |
|
|
|
painter->save(); |
|
|
|
const size_t size = to - from + 1; |
|
QPolygonF polygon( 2 * size ); |
|
QPointF *points = polygon.data(); |
|
|
|
for ( uint i = 0; i < size; i++ ) |
|
{ |
|
QPointF &minValue = points[i]; |
|
QPointF &maxValue = points[2 * size - 1 - i]; |
|
|
|
const QwtIntervalSample intervalSample = sample( from + i ); |
|
if ( orientation() == Qt::Vertical ) |
|
{ |
|
double x = xMap.transform( intervalSample.value ); |
|
double y1 = yMap.transform( intervalSample.interval.minValue() ); |
|
double y2 = yMap.transform( intervalSample.interval.maxValue() ); |
|
if ( doAlign ) |
|
{ |
|
x = qRound( x ); |
|
y1 = qRound( y1 ); |
|
y2 = qRound( y2 ); |
|
} |
|
|
|
minValue.rx() = x; |
|
minValue.ry() = y1; |
|
maxValue.rx() = x; |
|
maxValue.ry() = y2; |
|
} |
|
else |
|
{ |
|
double y = yMap.transform( intervalSample.value ); |
|
double x1 = xMap.transform( intervalSample.interval.minValue() ); |
|
double x2 = xMap.transform( intervalSample.interval.maxValue() ); |
|
if ( doAlign ) |
|
{ |
|
y = qRound( y ); |
|
x1 = qRound( x1 ); |
|
x2 = qRound( x2 ); |
|
} |
|
|
|
minValue.rx() = x1; |
|
minValue.ry() = y; |
|
maxValue.rx() = x2; |
|
maxValue.ry() = y; |
|
} |
|
} |
|
|
|
if ( d_data->brush.style() != Qt::NoBrush ) |
|
{ |
|
painter->setPen( QPen( Qt::NoPen ) ); |
|
painter->setBrush( d_data->brush ); |
|
|
|
if ( d_data->paintAttributes & ClipPolygons ) |
|
{ |
|
const qreal m = 1.0; |
|
const QPolygonF p = QwtClipper::clipPolygonF( |
|
canvasRect.adjusted( -m, -m, m, m ), polygon, true ); |
|
|
|
QwtPainter::drawPolygon( painter, p ); |
|
} |
|
else |
|
{ |
|
QwtPainter::drawPolygon( painter, polygon ); |
|
} |
|
} |
|
|
|
if ( d_data->pen.style() != Qt::NoPen ) |
|
{ |
|
painter->setPen( d_data->pen ); |
|
painter->setBrush( Qt::NoBrush ); |
|
|
|
if ( d_data->paintAttributes & ClipPolygons ) |
|
{ |
|
qreal pw = qMax( qreal( 1.0 ), painter->pen().widthF() ); |
|
const QRectF clipRect = canvasRect.adjusted( -pw, -pw, pw, pw ); |
|
|
|
QPolygonF p; |
|
|
|
p.resize( size ); |
|
::memcpy( p.data(), points, size * sizeof( QPointF ) ); |
|
p = QwtClipper::clipPolygonF( clipRect, p ); |
|
QwtPainter::drawPolyline( painter, p ); |
|
|
|
p.resize( size ); |
|
::memcpy( p.data(), points + size, size * sizeof( QPointF ) ); |
|
p = QwtClipper::clipPolygonF( clipRect, p ); |
|
QwtPainter::drawPolyline( painter, p ); |
|
} |
|
else |
|
{ |
|
QwtPainter::drawPolyline( painter, points, size ); |
|
QwtPainter::drawPolyline( painter, points + size, size ); |
|
} |
|
} |
|
|
|
painter->restore(); |
|
} |
|
|
|
/*! |
|
Draw symbols for a subset of the samples |
|
|
|
\param painter Painter |
|
\param symbol Interval symbol |
|
\param xMap x map |
|
\param yMap y map |
|
\param canvasRect Contents rectangle of the canvas |
|
\param from Index of the first sample to be painted |
|
\param to Index of the last sample to be painted |
|
|
|
\sa setSymbol(), drawSeries(), drawTube() |
|
*/ |
|
void QwtPlotIntervalCurve::drawSymbols( |
|
QPainter *painter, const QwtIntervalSymbol &symbol, |
|
const QwtScaleMap &xMap, const QwtScaleMap &yMap, |
|
const QRectF &canvasRect, int from, int to ) const |
|
{ |
|
painter->save(); |
|
|
|
QPen pen = symbol.pen(); |
|
pen.setCapStyle( Qt::FlatCap ); |
|
|
|
painter->setPen( pen ); |
|
painter->setBrush( symbol.brush() ); |
|
|
|
const QRectF tr = QwtScaleMap::invTransform( xMap, yMap, canvasRect ); |
|
|
|
const double xMin = tr.left(); |
|
const double xMax = tr.right(); |
|
const double yMin = tr.top(); |
|
const double yMax = tr.bottom(); |
|
|
|
const bool doClip = d_data->paintAttributes & ClipSymbol; |
|
|
|
for ( int i = from; i <= to; i++ ) |
|
{ |
|
const QwtIntervalSample s = sample( i ); |
|
|
|
if ( orientation() == Qt::Vertical ) |
|
{ |
|
if ( !doClip || qwtIsVSampleInside( s, xMin, xMax, yMin, yMax ) ) |
|
{ |
|
const double x = xMap.transform( s.value ); |
|
const double y1 = yMap.transform( s.interval.minValue() ); |
|
const double y2 = yMap.transform( s.interval.maxValue() ); |
|
|
|
symbol.draw( painter, orientation(), |
|
QPointF( x, y1 ), QPointF( x, y2 ) ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( !doClip || qwtIsHSampleInside( s, xMin, xMax, yMin, yMax ) ) |
|
{ |
|
const double y = yMap.transform( s.value ); |
|
const double x1 = xMap.transform( s.interval.minValue() ); |
|
const double x2 = xMap.transform( s.interval.maxValue() ); |
|
|
|
symbol.draw( painter, orientation(), |
|
QPointF( x1, y ), QPointF( x2, y ) ); |
|
} |
|
} |
|
} |
|
|
|
painter->restore(); |
|
} |
|
|
|
/*! |
|
\return Icon for the legend |
|
|
|
In case of Tube style() the icon is a plain rectangle filled with the brush(). |
|
If a symbol is assigned it is scaled to size. |
|
|
|
\param index Index of the legend entry |
|
( ignored as there is only one ) |
|
\param size Icon size |
|
|
|
\sa QwtPlotItem::setLegendIconSize(), QwtPlotItem::legendData() |
|
*/ |
|
QwtGraphic QwtPlotIntervalCurve::legendIcon( |
|
int index, const QSizeF &size ) const |
|
{ |
|
Q_UNUSED( index ); |
|
|
|
if ( size.isEmpty() ) |
|
return QwtGraphic(); |
|
|
|
QwtGraphic icon; |
|
icon.setDefaultSize( size ); |
|
icon.setRenderHint( QwtGraphic::RenderPensUnscaled, true ); |
|
|
|
QPainter painter( &icon ); |
|
painter.setRenderHint( QPainter::Antialiasing, |
|
testRenderHint( QwtPlotItem::RenderAntialiased ) ); |
|
|
|
if ( d_data->style == Tube ) |
|
{ |
|
QRectF r( 0, 0, size.width(), size.height() ); |
|
painter.fillRect( r, d_data->brush ); |
|
} |
|
|
|
if ( d_data->symbol && |
|
( d_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) ) |
|
{ |
|
QPen pen = d_data->symbol->pen(); |
|
pen.setWidthF( pen.widthF() ); |
|
pen.setCapStyle( Qt::FlatCap ); |
|
|
|
painter.setPen( pen ); |
|
painter.setBrush( d_data->symbol->brush() ); |
|
|
|
if ( orientation() == Qt::Vertical ) |
|
{ |
|
const double x = 0.5 * size.width(); |
|
|
|
d_data->symbol->draw( &painter, orientation(), |
|
QPointF( x, 0 ), QPointF( x, size.height() - 1.0 ) ); |
|
} |
|
else |
|
{ |
|
const double y = 0.5 * size.height(); |
|
|
|
d_data->symbol->draw( &painter, orientation(), |
|
QPointF( 0.0, y ), QPointF( size.width() - 1.0, y ) ); |
|
} |
|
} |
|
|
|
return icon; |
|
}
|
|
|