2014-11-13 19:30:51 +02:00
/*
This file is part of the KDE project
Copyright ( c ) 2006 - 2009 John Tapsell < tapsell @ kde . org >
This program is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public
License version 2 or at your option version 3 as published by
the Free Software Foundation .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
# include "ksignalplotter.h"
# include "ksignalplotter_p.h"
# include <QtGui/QPainter>
# include <QtGui/QPixmap>
# include <QtGui/QPainterPath>
2019-05-30 00:32:02 +00:00
# include <QtGui/QPen>
2015-08-12 13:11:16 +03:00
# include <QtGui/qevent.h>
2014-11-13 19:30:51 +02:00
# include <QEvent>
# include <kdebug.h>
# include <kglobal.h>
# include <klocale.h>
# include <kiconloader.h>
2016-11-16 16:23:22 +00:00
# include <cmath> //For floor, ceil, log10 etc for calculating ranges
2014-11-13 19:30:51 +02:00
# include <limits>
# ifdef SVG_SUPPORT
# include <plasma/svg.h>
# endif
# define VERTICAL_LINE_OFFSET 1
//Never store less 1000 samples if not visible. This is kinda arbituary
# define NUM_SAMPLES_WHEN_INVISIBLE ((uint)1000)
KSignalPlotter : : KSignalPlotter ( QWidget * parent )
: QWidget ( parent ) , d ( new KSignalPlotterPrivate ( this ) )
{
qRegisterMetaType < KLocalizedString > ( " KLocalizedString " ) ;
// Anything smaller than this does not make sense.
setMinimumSize ( KIconLoader : : SizeSmall , KIconLoader : : SizeSmall ) ;
QSizePolicy sizePolicy ( QSizePolicy : : Expanding , QSizePolicy : : Expanding ) ;
sizePolicy . setHeightForWidth ( false ) ;
setSizePolicy ( sizePolicy ) ;
}
KSignalPlotter : : ~ KSignalPlotter ( )
{
delete d ;
}
KLocalizedString KSignalPlotter : : unit ( ) const {
return d - > mUnit ;
}
void KSignalPlotter : : setUnit ( const KLocalizedString & unit ) {
d - > mUnit = unit ;
}
void KSignalPlotter : : addBeam ( const QColor & color )
{
QList < QList < qreal > > : : Iterator it ;
//When we add a new beam, go back and set the data for this beam to NaN for all the other times, to pad it out.
//This is because it makes it easier for moveSensors
for ( it = d - > mBeamData . begin ( ) ; it ! = d - > mBeamData . end ( ) ; + + it ) {
( * it ) . append ( std : : numeric_limits < qreal > : : quiet_NaN ( ) ) ;
}
d - > mBeamColors . append ( color ) ;
d - > mBeamColorsLight . append ( color . lighter ( ) ) ;
}
QColor KSignalPlotter : : beamColor ( int index ) const {
return d - > mBeamColors [ index ] ;
}
void KSignalPlotter : : setBeamColor ( int index , const QColor & color ) {
if ( ! color . isValid ( ) ) {
kDebug ( 1215 ) < < " Invalid color " ;
return ;
}
if ( index > = d - > mBeamColors . count ( ) ) {
kDebug ( 1215 ) < < " Invalid index " < < index ;
return ;
}
Q_ASSERT ( d - > mBeamColors . count ( ) = = d - > mBeamColorsLight . count ( ) ) ;
d - > mBeamColors [ index ] = color ;
d - > mBeamColorsLight [ index ] = color . lighter ( ) ;
}
int KSignalPlotter : : numBeams ( ) const {
return d - > mBeamColors . count ( ) ;
}
void KSignalPlotter : : addSample ( const QList < qreal > & sampleBuf )
{
d - > addSample ( sampleBuf ) ;
d - > mGraphWidget - > update ( ) ;
}
void KSignalPlotter : : reorderBeams ( const QList < int > & newOrder )
{
d - > reorderBeams ( newOrder ) ;
}
void KSignalPlotter : : changeRange ( qreal min , qreal max )
{
if ( min = = d - > mUserMinValue & & max = = d - > mUserMaxValue ) return ;
d - > mUserMinValue = min ;
d - > mUserMaxValue = max ;
d - > calculateNiceRange ( ) ;
}
void KSignalPlotter : : removeBeam ( int index )
{
if ( index > = d - > mBeamColors . size ( ) ) return ;
if ( index > = d - > mBeamColorsLight . size ( ) ) return ;
d - > mBeamColors . removeAt ( index ) ;
d - > mBeamColorsLight . removeAt ( index ) ;
QList < QList < qreal > > : : Iterator i ;
for ( i = d - > mBeamData . begin ( ) ; i ! = d - > mBeamData . end ( ) ; + + i ) {
if ( ( * i ) . size ( ) > = index )
( * i ) . removeAt ( index ) ;
}
if ( d - > mUseAutoRange )
d - > rescale ( ) ;
}
void KSignalPlotter : : setScaleDownBy ( qreal value )
{
if ( d - > mScaleDownBy = = value ) return ;
d - > mScaleDownBy = value ;
d - > mBackgroundImage = QPixmap ( ) ; //we changed a paint setting, so reset the cache
d - > calculateNiceRange ( ) ;
update ( ) ;
}
qreal KSignalPlotter : : scaleDownBy ( ) const
{
return d - > mScaleDownBy ;
}
void KSignalPlotter : : setUseAutoRange ( bool value )
{
d - > mUseAutoRange = value ;
d - > calculateNiceRange ( ) ;
//this change will be detected in paint and the image cache regenerated
}
bool KSignalPlotter : : useAutoRange ( ) const
{
return d - > mUseAutoRange ;
}
void KSignalPlotter : : setMinimumValue ( qreal min )
{
if ( min = = d - > mUserMinValue ) return ;
d - > mUserMinValue = min ;
d - > calculateNiceRange ( ) ;
update ( ) ;
//this change will be detected in paint and the image cache regenerated
}
qreal KSignalPlotter : : minimumValue ( ) const
{
return d - > mUserMinValue ;
}
void KSignalPlotter : : setMaximumValue ( qreal max )
{
if ( max = = d - > mUserMaxValue ) return ;
d - > mUserMaxValue = max ;
d - > calculateNiceRange ( ) ;
update ( ) ;
//this change will be detected in paint and the image cache regenerated
}
qreal KSignalPlotter : : maximumValue ( ) const
{
return d - > mUserMaxValue ;
}
qreal KSignalPlotter : : currentMaximumRangeValue ( ) const
{
return d - > mNiceMaxValue ;
}
qreal KSignalPlotter : : currentMinimumRangeValue ( ) const
{
return d - > mNiceMinValue ;
}
void KSignalPlotter : : setHorizontalScale ( uint scale )
{
if ( scale = = d - > mHorizontalScale | | scale = = 0 )
return ;
d - > mHorizontalScale = scale ;
d - > updateDataBuffers ( ) ;
# ifdef USE_QIMAGE
d - > mScrollableImage = QImage ( ) ;
# else
d - > mScrollableImage = QPixmap ( ) ;
# endif
update ( ) ;
}
int KSignalPlotter : : horizontalScale ( ) const
{
return d - > mHorizontalScale ;
}
void KSignalPlotter : : setShowVerticalLines ( bool value )
{
if ( d - > mShowVerticalLines = = value ) return ;
d - > mShowVerticalLines = value ;
d - > mBackgroundImage = QPixmap ( ) ; //we changed a paint setting, so reset the cache
# ifdef USE_QIMAGE
d - > mScrollableImage = QImage ( ) ;
# else
d - > mScrollableImage = QPixmap ( ) ;
# endif
update ( ) ;
}
bool KSignalPlotter : : showVerticalLines ( ) const
{
return d - > mShowVerticalLines ;
}
void KSignalPlotter : : setVerticalLinesDistance ( uint distance )
{
if ( distance = = d - > mVerticalLinesDistance ) return ;
d - > mVerticalLinesDistance = distance ;
d - > mBackgroundImage = QPixmap ( ) ; //we changed a paint setting, so reset the cache
# ifdef USE_QIMAGE
d - > mScrollableImage = QImage ( ) ;
# else
d - > mScrollableImage = QPixmap ( ) ;
# endif
update ( ) ;
}
uint KSignalPlotter : : verticalLinesDistance ( ) const
{
return d - > mVerticalLinesDistance ;
}
void KSignalPlotter : : setVerticalLinesScroll ( bool value )
{
if ( value = = d - > mVerticalLinesScroll ) return ;
d - > mVerticalLinesScroll = value ;
# ifdef USE_QIMAGE
d - > mScrollableImage = QImage ( ) ;
# else
d - > mScrollableImage = QPixmap ( ) ;
# endif
update ( ) ;
}
bool KSignalPlotter : : verticalLinesScroll ( ) const
{
return d - > mVerticalLinesScroll ;
}
void KSignalPlotter : : setShowHorizontalLines ( bool value )
{
if ( value = = d - > mShowHorizontalLines ) return ;
d - > mShowHorizontalLines = value ;
# ifdef USE_QIMAGE
d - > mScrollableImage = QImage ( ) ;
# else
d - > mScrollableImage = QPixmap ( ) ;
# endif
update ( ) ;
}
bool KSignalPlotter : : showHorizontalLines ( ) const
{
return d - > mShowHorizontalLines ;
}
void KSignalPlotter : : setShowAxis ( bool value )
{
if ( value = = d - > mShowAxis ) return ;
d - > mShowAxis = value ;
d - > mBackgroundImage = QPixmap ( ) ; //we changed a paint setting, so reset the cache
# ifdef USE_QIMAGE
d - > mScrollableImage = QImage ( ) ;
# else
d - > mScrollableImage = QPixmap ( ) ;
# endif
update ( ) ;
}
bool KSignalPlotter : : showAxis ( ) const
{
return d - > mShowAxis ;
}
QString KSignalPlotter : : svgBackground ( ) const {
return d - > mSvgFilename ;
}
void KSignalPlotter : : setSvgBackground ( const QString & filename )
{
if ( d - > mSvgFilename = = filename ) return ;
d - > mSvgFilename = filename ;
# ifdef SVG_SUPPORT
if ( filename . isEmpty ( ) ) {
delete d - > mSvgRenderer ;
d - > mSvgRenderer = NULL ;
} else {
if ( ! d - > mSvgRenderer )
d - > mSvgRenderer = new Plasma : : Svg ( this ) ;
d - > mSvgRenderer - > setImagePath ( d - > mSvgFilename ) ;
}
# endif
d - > mBackgroundImage = QPixmap ( ) ;
update ( ) ;
}
void KSignalPlotter : : setMaxAxisTextWidth ( int axisTextWidth )
{
if ( d - > mAxisTextWidth = = axisTextWidth ) return ;
d - > mAxisTextWidth = axisTextWidth ;
d - > mBackgroundImage = QPixmap ( ) ; //we changed a paint setting, so reset the cache
update ( ) ;
}
int KSignalPlotter : : maxAxisTextWidth ( ) const
{
return d - > mAxisTextWidth ;
}
void KSignalPlotter : : changeEvent ( QEvent * event )
{
switch ( event - > type ( ) ) {
case QEvent : : ApplicationPaletteChange :
case QEvent : : PaletteChange :
case QEvent : : FontChange :
case QEvent : : LanguageChange :
case QEvent : : LocaleChange :
case QEvent : : LayoutDirectionChange :
d - > mBackgroundImage = QPixmap ( ) ; //we changed a paint setting, so reset the cache
update ( ) ;
break ;
default : //Do nothing
break ;
}
}
2023-06-11 20:43:16 +03:00
2014-11-13 19:30:51 +02:00
void KSignalPlotter : : resizeEvent ( QResizeEvent * event )
{
QRect boundingBox ( QPoint ( 0 , 0 ) , event - > size ( ) ) ;
int fontHeight = fontMetrics ( ) . height ( ) ;
if ( d - > mShowAxis & & d - > mAxisTextWidth ! = 0 & & boundingBox . width ( ) > ( d - > mAxisTextWidth * 1.10 + 2 ) & & boundingBox . height ( ) > fontHeight ) { //if there's room to draw the labels, then draw them!
//We want to adjust the size of plotter bit inside so that the axis text aligns nicely at the top and bottom
//but we don't want to sacrifice too much of the available room, so don't use it if it will take more than 20% of the available space
qreal offset = ( fontHeight + 1 ) / 2 ;
if ( offset < boundingBox . height ( ) * 0.1 )
boundingBox . adjust ( 0 , offset , 0 , - offset ) ;
int padding = 1 ;
if ( boundingBox . width ( ) > d - > mAxisTextWidth + 50 )
padding = 10 ; //If there's plenty of room, at 10 pixels for padding the axis text, so that it looks nice
if ( layoutDirection ( ) = = Qt : : RightToLeft )
boundingBox . setRight ( boundingBox . right ( ) - d - > mAxisTextWidth - padding ) ;
else
boundingBox . setLeft ( d - > mAxisTextWidth + padding ) ;
d - > mActualAxisTextWidth = d - > mAxisTextWidth ;
} else {
d - > mActualAxisTextWidth = 0 ;
}
if ( d - > mShowThinFrame )
boundingBox . adjust ( 0 , 0 , - 1 , - 1 ) ;
// Remember bounding box to pass to update, so that we only update the plotting area the next time, if that's the only that thing that has changed
d - > mPlottingArea = boundingBox ;
//Calculate the new number of horizontal lines
int newHorizontalLinesCount = qBound ( 0 , ( int ) ( boundingBox . height ( ) / fontHeight ) - 2 , 4 ) ;
if ( newHorizontalLinesCount ! = d - > mHorizontalLinesCount ) {
d - > mHorizontalLinesCount = newHorizontalLinesCount ;
d - > calculateNiceRange ( ) ;
}
# ifdef USE_QIMAGE
d - > mScrollableImage = QImage ( ) ;
# else
d - > mScrollableImage = QPixmap ( ) ;
# endif
d - > mGraphWidget - > setVisible ( true ) ;
d - > mGraphWidget - > setGeometry ( boundingBox ) ;
d - > updateDataBuffers ( ) ;
}
KSignalPlotterPrivate : : KSignalPlotterPrivate ( KSignalPlotter * q_ptr_ ) : q ( q_ptr_ )
{
mPrecision = 0 ;
mMaxSamples = NUM_SAMPLES_WHEN_INVISIBLE ;
mMinValue = mMaxValue = std : : numeric_limits < qreal > : : quiet_NaN ( ) ;
mUserMinValue = mUserMaxValue = 0.0 ;
mNiceMinValue = mNiceMaxValue = 0.0 ;
mNiceRange = 0 ;
mUseAutoRange = true ;
mScaleDownBy = 1 ;
mShowThinFrame = true ;
mSmoothGraph = true ;
mShowVerticalLines = false ;
mVerticalLinesDistance = 30 ;
mVerticalLinesScroll = true ;
mVerticalLinesOffset = 0 ;
mHorizontalScale = 6 ;
mShowHorizontalLines = true ;
mHorizontalLinesCount = 4 ;
mShowAxis = true ;
mAxisTextWidth = 0 ;
mScrollOffset = 0 ;
mStackBeams = false ;
mFillOpacity = 20 ;
mRescaleTime = 0 ;
mUnit = ki18n ( " %1 " ) ;
mAxisTextOverlapsPlotter = false ;
mActualAxisTextWidth = 0 ;
mGraphWidget = new GraphWidget ( q ) ; ///< This is the widget that draws the actual graph
mGraphWidget - > signalPlotterPrivate = this ;
mGraphWidget - > setVisible ( false ) ;
}
void KSignalPlotterPrivate : : recalculateMaxMinValueForSample ( const QList < qreal > & sampleBuf , int time )
{
if ( mStackBeams ) {
qreal value = 0 ;
for ( int i = sampleBuf . count ( ) - 1 ; i > = 0 ; i - - ) {
qreal newValue = sampleBuf [ i ] ;
2016-11-16 16:23:22 +00:00
if ( ! std : : isinf ( newValue ) & & ! std : : isnan ( newValue ) )
2014-11-13 19:30:51 +02:00
value + = newValue ;
}
2016-11-16 16:23:22 +00:00
if ( std : : isnan ( mMinValue ) | | mMinValue > value ) mMinValue = value ;
if ( std : : isnan ( mMaxValue ) | | mMaxValue < value ) mMaxValue = value ;
2014-11-13 19:30:51 +02:00
if ( value > 0.7 * mMaxValue )
mRescaleTime = time ;
} else {
qreal value ;
for ( int i = sampleBuf . count ( ) - 1 ; i > = 0 ; i - - ) {
value = sampleBuf [ i ] ;
2016-11-16 16:23:22 +00:00
if ( ! std : : isinf ( value ) & & ! std : : isnan ( value ) ) {
if ( std : : isnan ( mMinValue ) | | mMinValue > value ) mMinValue = value ;
if ( std : : isnan ( mMaxValue ) | | mMaxValue < value ) mMaxValue = value ;
2014-11-13 19:30:51 +02:00
if ( value > 0.7 * mMaxValue )
mRescaleTime = time ;
}
}
}
}
void KSignalPlotterPrivate : : rescale ( ) {
mMaxValue = mMinValue = std : : numeric_limits < qreal > : : quiet_NaN ( ) ;
for ( int i = mBeamData . count ( ) - 1 ; i > = 0 ; i - - ) {
recalculateMaxMinValueForSample ( mBeamData [ i ] , i ) ;
}
calculateNiceRange ( ) ;
}
void KSignalPlotterPrivate : : addSample ( const QList < qreal > & sampleBuf )
{
if ( sampleBuf . count ( ) ! = mBeamColors . count ( ) ) {
kDebug ( 1215 ) < < " Sample data discarded - contains wrong number of beams " ;
return ;
}
mBeamData . prepend ( sampleBuf ) ;
if ( ( unsigned int ) mBeamData . size ( ) > mMaxSamples ) {
mBeamData . removeLast ( ) ; // we have too many. Remove the last item
if ( ( unsigned int ) mBeamData . size ( ) > mMaxSamples )
mBeamData . removeLast ( ) ; // If we still have too many, then we have resized the widget. Remove one more. That way we will slowly resize to the new size
}
if ( mUseAutoRange ) {
recalculateMaxMinValueForSample ( sampleBuf , 0 ) ;
if ( mRescaleTime + + > mMaxSamples )
rescale ( ) ;
else if ( mMinValue < mNiceMinValue | | mMaxValue > mNiceMaxValue | | ( mMaxValue > mUserMaxValue & & mNiceRange ! = 1 & & mMaxValue < ( mNiceRange * 0.75 + mNiceMinValue ) ) | | mNiceRange = = 0 )
calculateNiceRange ( ) ;
} else {
if ( mMinValue < mNiceMinValue | | mMaxValue > mNiceMaxValue | | ( mMaxValue > mUserMaxValue & & mNiceRange ! = 1 & & mMaxValue < ( mNiceRange * 0.75 + mNiceMinValue ) ) | | mNiceRange = = 0 )
calculateNiceRange ( ) ;
}
if ( mScrollableImage . isNull ( ) )
return ;
QPainter pCache ( & mScrollableImage ) ;
drawBeamToScrollableImage ( & pCache , 0 ) ;
}
void KSignalPlotterPrivate : : reorderBeams ( const QList < int > & newOrder )
{
if ( newOrder . count ( ) ! = mBeamColors . count ( ) ) {
return ;
}
QList < QList < qreal > > : : Iterator it ;
for ( it = mBeamData . begin ( ) ; it ! = mBeamData . end ( ) ; + + it ) {
if ( newOrder . count ( ) ! = ( * it ) . count ( ) ) {
kWarning ( 1215 ) < < " Serious problem in move sample. beamdata[i] has " < < ( * it ) . count ( ) < < " and neworder has " < < newOrder . count ( ) ;
} else {
QList < qreal > newBeam ;
for ( int i = 0 ; i < newOrder . count ( ) ; i + + ) {
int newIndex = newOrder [ i ] ;
newBeam . append ( ( * it ) . at ( newIndex ) ) ;
}
( * it ) = newBeam ;
}
}
QList < QColor > newBeamColors ;
QList < QColor > newBeamColorsDark ;
for ( int i = 0 ; i < newOrder . count ( ) ; i + + ) {
int newIndex = newOrder [ i ] ;
newBeamColors . append ( mBeamColors . at ( newIndex ) ) ;
newBeamColorsDark . append ( mBeamColorsLight . at ( newIndex ) ) ;
}
mBeamColors = newBeamColors ;
mBeamColorsLight = newBeamColorsDark ;
}
void KSignalPlotterPrivate : : updateDataBuffers ( )
{
/* This is called when the widget has resized
*
* Determine new number of samples first .
* + 0.5 to ensure rounding up
* + 4 for extra data points so there is
* 1 ) no wasted space and
* 2 ) no loss of precision when drawing the first data point .
*/
if ( q - > isVisible ( ) )
mMaxSamples = uint ( q - > size ( ) . width ( ) / mHorizontalScale + 4 ) ;
else //If it's not visible, we can't rely on sensible values for width. Store some minimum number of data points
mMaxSamples = qMin ( ( uint ) ( q - > size ( ) . width ( ) / mHorizontalScale + 4 ) , NUM_SAMPLES_WHEN_INVISIBLE ) ;
}
void KSignalPlotter : : paintEvent ( QPaintEvent * event )
{
if ( testAttribute ( Qt : : WA_PendingResizeEvent ) )
return ; // lets not do this more than necessary, shall we?
QPainter p ( this ) ;
QPainter * painter = & p ;
uint w = size ( ) . width ( ) ;
uint h = size ( ) . height ( ) ;
/* Do not do repaints when the widget is not yet setup properly. */
if ( w < = 2 | | h < = 2 )
return ;
bool onlyDrawPlotter = event & & d - > mPlottingArea . contains ( event - > rect ( ) ) ;
if ( onlyDrawPlotter )
return ; //The painting will be handled entirely by GraphWidget::paintEvent
if ( ! onlyDrawPlotter & & d - > mShowThinFrame )
d - > drawThinFrame ( painter , d - > mPlottingArea . adjusted ( 0 , 0 , 1 , 1 ) ) ; //We have a 'frame' in the bottom and right - so subtract them from the view
if ( d - > mShowAxis ) {
int fontheight = fontMetrics ( ) . height ( ) ;
if ( d - > mPlottingArea . height ( ) > fontheight ) { //if there's room to draw the labels, then draw them!
d - > drawAxisText ( painter , QRect ( 0 , 0 , w , h ) ) ;
}
}
}
2023-06-11 20:43:16 +03:00
2014-11-13 19:30:51 +02:00
void GraphWidget : : paintEvent ( QPaintEvent * )
{
if ( testAttribute ( Qt : : WA_PendingResizeEvent ) )
return ; // lets not do this more than necessary, shall we?
uint w = width ( ) ;
uint h = height ( ) ;
/* Do not do repaints when the widget is not yet setup properly. */
if ( w < = 2 | | h < = 2 )
return ;
QPainter p ( this ) ;
signalPlotterPrivate - > drawWidget ( & p , QRect ( 0 , 0 , w , h ) ) ;
if ( signalPlotterPrivate - > mAxisTextOverlapsPlotter & & signalPlotterPrivate - > mShowAxis ) {
uint fontheight = signalPlotterPrivate - > q - > fontMetrics ( ) . height ( ) ;
if ( h > fontheight ) { //if there's room to draw the labels, then draw them!
QSize originalSize = signalPlotterPrivate - > q - > size ( ) ;
signalPlotterPrivate - > drawAxisText ( & p , QRect ( - signalPlotterPrivate - > mPlottingArea . topLeft ( ) , originalSize ) ) ;
}
}
}
2023-06-11 20:43:16 +03:00
2014-11-13 19:30:51 +02:00
void KSignalPlotterPrivate : : drawWidget ( QPainter * p , const QRect & boundingBox )
{
# ifdef SVG_SUPPORT
if ( ! mSvgFilename . isEmpty ( ) ) {
if ( mBackgroundImage . isNull ( ) | | mBackgroundImage . height ( ) ! = boundingBox . height ( ) | | mBackgroundImage . width ( ) ! = boundingBox . width ( ) ) { //recreate on resize etc
updateSvgBackground ( boundingBox ) ;
}
p - > drawPixmap ( boundingBox , mBackgroundImage ) ;
}
# endif
if ( mScrollableImage . isNull ( ) )
redrawScrollableImage ( ) ;
//We draw the pixmap in two halves, wrapping around the window
if ( mScrollOffset > 1 ) {
# ifdef USE_QIMAGE
p - > drawImage ( boundingBox . right ( ) - mScrollOffset + 2 , boundingBox . top ( ) , mScrollableImage , 0 , 0 , mScrollOffset - 1 , boundingBox . height ( ) ) ;
# else
p - > drawPixmap ( boundingBox . right ( ) - mScrollOffset + 2 , boundingBox . top ( ) , mScrollableImage , 0 , 0 , mScrollOffset - 1 , boundingBox . height ( ) ) ;
# endif
}
int widthOfSecondHalf = boundingBox . width ( ) - mScrollOffset + 1 ;
if ( widthOfSecondHalf > 0 ) {
# ifdef USE_QIMAGE
p - > drawImage ( boundingBox . left ( ) , boundingBox . top ( ) , mScrollableImage , mScrollableImage . width ( ) - widthOfSecondHalf - 1 , 0 , widthOfSecondHalf , boundingBox . height ( ) ) ;
# else
p - > drawPixmap ( boundingBox . left ( ) , boundingBox . top ( ) , mScrollableImage , mScrollableImage . width ( ) - widthOfSecondHalf - 1 , 0 , widthOfSecondHalf , boundingBox . height ( ) ) ;
# endif
}
/* Draw scope-like grid vertical lines if it doesn't move. If it does move, draw it in the dynamic part of the code*/
if ( mShowVerticalLines & & ! mVerticalLinesScroll )
drawVerticalLines ( p , boundingBox ) ;
}
void KSignalPlotterPrivate : : drawBackground ( QPainter * p , const QRect & boundingBox ) const
{
p - > setRenderHint ( QPainter : : Antialiasing , false ) ;
# ifdef SVG_SUPPORT
if ( ! mSvgFilename . isEmpty ( ) ) //our background is an svg, so don't paint over the top of it
p - > fillRect ( boundingBox , Qt : : transparent ) ;
else
# endif
p - > fillRect ( boundingBox , q - > palette ( ) . brush ( QPalette : : Base ) ) ;
if ( mShowHorizontalLines )
drawHorizontalLines ( p , boundingBox . adjusted ( 0 , 0 , 1 , 0 ) ) ;
if ( mShowVerticalLines & & mVerticalLinesScroll )
drawVerticalLines ( p , boundingBox , mVerticalLinesOffset ) ;
p - > setRenderHint ( QPainter : : Antialiasing , true ) ;
}
bool KSignalPlotter : : thinFrame ( ) const
{
return d - > mShowThinFrame ;
}
void KSignalPlotter : : setThinFrame ( bool thinFrame )
{
if ( thinFrame = = d - > mShowThinFrame )
return ;
d - > mShowThinFrame = thinFrame ;
update ( ) ; //Trigger a repaint
}
# ifdef SVG_SUPPORT
void KSignalPlotterPrivate : : updateSvgBackground ( const QRect & boundingBox )
{
Q_ASSERT ( ! mSvgFilename . isEmpty ( ) ) ;
Q_ASSERT ( boundingBox . isNull ( ) ) ;
mBackgroundImage = QPixmap ( boundingBox . width ( ) , boundingBox . height ( ) ) ;
Q_ASSERT ( ! mBackgroundImage . isNull ( ) ) ;
QPainter pCache ( & mBackgroundImage ) ;
pCache . fill ( q - > palette ( ) . color ( QPalette : : Base ) ) ;
svgRenderer - > resize ( boundingBox . size ( ) ) ;
svgRenderer - > paint ( & pCache , 0 , 0 ) ;
}
# endif
void KSignalPlotterPrivate : : redrawScrollableImage ( )
{
//Align width of bounding box to the size of the horizontal scale
int alignedWidth = ( ( mPlottingArea . width ( ) + 1 ) / mHorizontalScale + 1 ) * mHorizontalScale ;
//Redraw the whole thing
# ifdef USE_QIMAGE
mScrollableImage = QImage ( alignedWidth , mPlottingArea . height ( ) , QImage : : Format_ARGB32_Premultiplied ) ;
# else
mScrollableImage = QPixmap ( alignedWidth , mPlottingArea . height ( ) ) ;
# endif
Q_ASSERT ( ! mScrollableImage . isNull ( ) ) ;
mScrollOffset = 0 ;
mVerticalLinesOffset = mVerticalLinesDistance - mHorizontalScale + 1 ; // mVerticalLinesDistance - alignedWidth % mVerticalLinesDistance;
//We need to draw the background for areas without a beam
int withoutBeamWidth = qMax ( mBeamData . size ( ) - 1 , 0 ) * mHorizontalScale ;
QPainter pCache ( & mScrollableImage ) ;
if ( withoutBeamWidth < mScrollableImage . width ( ) )
drawBackground ( & pCache , QRect ( withoutBeamWidth , 0 , alignedWidth - withoutBeamWidth , mScrollableImage . height ( ) ) ) ;
/* Draw scope-like grid vertical lines */
mVerticalLinesOffset = 0 ;
if ( mBeamData . size ( ) > 2 ) {
for ( int i = mBeamData . size ( ) - 2 ; i > = 0 ; i - - )
drawBeamToScrollableImage ( & pCache , i ) ;
}
}
void KSignalPlotterPrivate : : drawThinFrame ( QPainter * p , const QRect & boundingBox )
{
/* Draw a line along the bottom and the right side of the
* widget to create a 3 D like look . */
p - > setRenderHint ( QPainter : : Antialiasing , false ) ;
p - > setPen ( QPen ( q - > palette ( ) . color ( QPalette : : Light ) , 0 ) ) ;
p - > drawLine ( boundingBox . bottomLeft ( ) , boundingBox . bottomRight ( ) ) ;
p - > drawLine ( boundingBox . bottomRight ( ) , boundingBox . topRight ( ) ) ;
p - > setRenderHint ( QPainter : : Antialiasing , true ) ;
}
void KSignalPlotterPrivate : : calculateNiceRange ( )
{
qreal max = mUserMaxValue ;
qreal min = mUserMinValue ;
if ( mUseAutoRange ) {
2016-11-16 16:23:22 +00:00
if ( ! std : : isnan ( mMaxValue ) & & mMaxValue * 0.99 > max ) //Allow max value to go very slightly over the given max, for rounding reasons
2014-11-13 19:30:51 +02:00
max = mMaxValue ;
2016-11-16 16:23:22 +00:00
if ( ! std : : isnan ( mMinValue ) & & mMinValue * 0.99 < min ) {
2014-11-13 19:30:51 +02:00
min = mMinValue ;
}
}
/* If the range is too small we will force it to 1.0 since it
* looks a lot nicer . */
if ( max - min < 0.000001 )
max = min + 1 ;
qreal range = max - min ;
// Massage the range so that the grid shows some nice values.
qreal step ;
int number_lines_above_zero = 0 ;
int number_lines_below_zero = 0 ;
//If y=0 is visible and have at least 1 horizontal lines make sure that we have a line crossing through 0
bool alignToXAxis = min < 0 & & max > 0 & & mHorizontalLinesCount > = 1 ;
if ( alignToXAxis ) {
number_lines_above_zero = int ( mHorizontalLinesCount * max / range ) ;
number_lines_below_zero = mHorizontalLinesCount - number_lines_above_zero - 1 ; //subtract 1 line for the actual 0 line
step = qMax ( max / ( mScaleDownBy * ( number_lines_above_zero + 1 ) ) , - min / ( mScaleDownBy * ( number_lines_below_zero + 1 ) ) ) ;
} else
step = range / ( mScaleDownBy * ( mHorizontalLinesCount + 1 ) ) ;
const int sigFigs = 2 ; //Number of significant figures of the step to use. Update the 0.05 below if this changes
int logdim = ( int ) floor ( log10 ( step ) ) - ( sigFigs - 1 ) ; //find the order of the number, reduced by 1. E.g. if step=1234 then logdim is 3-1=2
qreal dim = pow ( ( qreal ) 10.0 , logdim ) ; //e.g. if step=1234, logdim=2, so dim = 100
int a = ( int ) ceil ( step / dim - 0.000005 ) ; //so a = ceil(1234/100) = ceil(12.34) = 13 (we subtract an epsilon)
if ( logdim > = 0 )
mPrecision = 0 ; //Work out the number of decimal places. e.g. If step=1.5, then logdim = -1 so precision is 1
else if ( a % 10 = = 0 ) //We just happened to round to a nice number, requiring 1 less precision
mPrecision = - logdim - 1 ;
else
mPrecision = - logdim ;
step = dim * a ; //e.g. if step=1234, dim=100, a=13 so step= 1300. So 1234 was rounded up to 1300
range = mScaleDownBy * step * ( mHorizontalLinesCount + 1 ) ;
if ( alignToXAxis ) {
max = mScaleDownBy * step * ( number_lines_above_zero + 1 ) ;
min = - mScaleDownBy * step * ( number_lines_below_zero + 1 ) ;
} else
max = min + range ;
if ( mNiceMinValue = = min & & mNiceRange = = range )
return ; //nothing changed
mNiceMaxValue = max ;
mNiceMinValue = min ;
mNiceRange = range ;
# ifdef USE_QIMAGE
mScrollableImage = QImage ( ) ;
# else
mScrollableImage = QPixmap ( ) ;
# endif
emit q - > axisScaleChanged ( ) ;
q - > update ( ) ;
}
void KSignalPlotterPrivate : : drawVerticalLines ( QPainter * p , const QRect & boundingBox , int offset ) const
{
QColor color = q - > palette ( ) . color ( QPalette : : Window ) ;
if ( ! mVerticalLinesScroll )
color . setAlpha ( 127 ) ;
p - > setPen ( QPen ( color , 0 ) ) ;
p - > setRenderHint ( QPainter : : Antialiasing , false ) ;
for ( int x = boundingBox . right ( ) - ( offset % mVerticalLinesDistance ) ; x > = boundingBox . left ( ) ; x - = mVerticalLinesDistance )
p - > drawLine ( x , boundingBox . top ( ) , x , boundingBox . bottom ( ) ) ;
p - > setRenderHint ( QPainter : : Antialiasing , true ) ;
}
void KSignalPlotterPrivate : : drawBeamToScrollableImage ( QPainter * p , int index )
{
QRect cacheBoundingBox = QRect ( mScrollOffset , 0 , mHorizontalScale , mScrollableImage . height ( ) ) ;
drawBackground ( p , cacheBoundingBox ) ;
drawBeam ( p , cacheBoundingBox , mHorizontalScale , index ) ;
mScrollOffset + = mHorizontalScale ;
mVerticalLinesOffset = ( mVerticalLinesOffset + mHorizontalScale ) % mVerticalLinesDistance ;
if ( mScrollOffset > = mScrollableImage . width ( ) - 1 ) {
mScrollOffset = 0 ;
mVerticalLinesOffset - - ; //We skip over the last pixel drawn
}
}
void KSignalPlotterPrivate : : drawBeam ( QPainter * p , const QRect & boundingBox , int horizontalScale , int index )
{
if ( mNiceRange = = 0 ) return ;
QPen pen ;
if ( mHorizontalScale = = 1 ) //Don't use a pen width of 2 if there's only 1 pixel between points
pen . setWidth ( 1 ) ;
else
pen . setWidth ( 2 ) ;
pen . setCapStyle ( Qt : : FlatCap ) ;
qreal scaleFac = ( boundingBox . height ( ) - 2 ) / mNiceRange ;
if ( mBeamData . size ( ) - 1 < = index )
return ; // Something went wrong?
const QList < qreal > & datapoints = mBeamData [ index ] ;
const QList < qreal > & prev_datapoints = mBeamData [ index + 1 ] ;
bool hasPrevPrevDatapoints = ( index + 2 < mBeamData . size ( ) ) ; //used for bezier curve gradient calculation
const QList < qreal > & prev_prev_datapoints = hasPrevPrevDatapoints ? mBeamData [ index + 2 ] : prev_datapoints ;
qreal x0 = boundingBox . right ( ) ;
qreal x1 = qMax ( boundingBox . right ( ) - horizontalScale , 0 ) ;
qreal xaxis = boundingBox . bottom ( ) ;
if ( mNiceMinValue < 0 )
xaxis = qMax ( qreal ( xaxis + mNiceMinValue * scaleFac ) , qreal ( boundingBox . top ( ) ) ) ;
const int count = qMin ( datapoints . size ( ) , mBeamColors . size ( ) ) ;
QVector < QPainterPath > paths ( count ) ;
QPointF previous_c0 ;
QPointF previous_c1 ;
QPointF previous_c2 ;
QPointF previous_c3 ;
qreal previous_point0 = 0 ;
qreal previous_point1 = 0 ;
qreal previous_point2 = 0 ;
bool firstLine = true ;
for ( int j = 0 ; j < count ; + + j ) {
qreal point0 = datapoints [ j ] ;
2016-11-16 16:23:22 +00:00
if ( std : : isnan ( point0 ) )
2014-11-13 19:30:51 +02:00
continue ; //Just do not draw points with nans. skip them
qreal point1 = prev_datapoints [ j ] ;
qreal point2 = prev_prev_datapoints [ j ] ;
2016-11-16 16:23:22 +00:00
if ( std : : isnan ( point1 ) )
2014-11-13 19:30:51 +02:00
point1 = point0 ;
2016-11-16 16:23:22 +00:00
else if ( mSmoothGraph & & ! std : : isinf ( point1 ) ) {
2014-11-13 19:30:51 +02:00
// Apply a weighted average just to smooth the graph out a bit
// Do not try to smooth infinities or nans
point0 = ( 2 * point0 + point1 ) / 3 ;
2016-11-16 16:23:22 +00:00
if ( ! std : : isnan ( point2 ) & & ! std : : isinf ( point2 ) )
2014-11-13 19:30:51 +02:00
point1 = ( 2 * point1 + point2 ) / 3 ;
// We don't bother to average out y2. This will introduce slight inaccuracies in the gradients, but they aren't really noticeable.
}
2016-11-16 16:23:22 +00:00
if ( std : : isnan ( point2 ) )
2014-11-13 19:30:51 +02:00
point2 = point1 ;
if ( mStackBeams ) {
point0 = previous_point0 = previous_point0 + point0 ;
point1 = previous_point1 = previous_point1 + point1 ;
point2 = previous_point2 = previous_point2 + point2 ;
}
qreal y0 = qBound ( ( qreal ) boundingBox . top ( ) , boundingBox . bottom ( ) - ( point0 - mNiceMinValue ) * scaleFac , ( qreal ) boundingBox . bottom ( ) ) ;
qreal y1 = qBound ( ( qreal ) boundingBox . top ( ) , boundingBox . bottom ( ) - ( point1 - mNiceMinValue ) * scaleFac , ( qreal ) boundingBox . bottom ( ) ) ;
qreal y2 = qBound ( ( qreal ) boundingBox . top ( ) , boundingBox . bottom ( ) - ( point2 - mNiceMinValue ) * scaleFac , ( qreal ) boundingBox . bottom ( ) ) ;
QPainterPath & path = paths [ j ] ;
path . moveTo ( x1 , y1 ) ;
QPointF c1 ( x1 + horizontalScale / 3.0 , ( 4 * y1 - y2 ) / 3.0 ) ; //Control point 1 - same gradient as prev_prev_datapoint to prev_datapoint
QPointF c2 ( x1 + 2 * horizontalScale / 3.0 , ( 2 * y0 + y1 ) / 3.0 ) ; //Control point 2 - same gradient as prev_datapoint to datapoint
QPointF c3 ( x0 , y0 ) ;
path . cubicTo ( c1 , c2 , c3 ) ;
if ( mFillOpacity ) {
QPainterPath fillPath = path ; //Make a copy to do our fill
if ( ! mStackBeams | | firstLine ) {
fillPath . lineTo ( x0 , xaxis ) ;
fillPath . lineTo ( x1 , xaxis ) ;
fillPath . lineTo ( x1 , y1 ) ;
} else {
fillPath . lineTo ( x0 , previous_c3 . y ( ) ) ;
fillPath . cubicTo ( previous_c2 , previous_c1 , previous_c0 ) ;
fillPath . lineTo ( x1 , y1 ) ;
}
if ( mStackBeams ) {
previous_c0 = QPointF ( x1 , y1 ) ;
previous_c1 = c1 ;
previous_c2 = c2 ;
previous_c3 = c3 ;
}
QColor fillColor = mBeamColors [ j ] ;
fillColor . setAlpha ( mFillOpacity ) ;
p - > fillPath ( fillPath , fillColor ) ;
}
firstLine = false ;
}
for ( int j = count - 1 ; j > = 0 ; - - j ) {
if ( mFillOpacity )
pen . setColor ( mBeamColorsLight . at ( j ) ) ;
else
pen . setColor ( mBeamColors . at ( j ) ) ;
p - > strokePath ( paths . at ( j ) , pen ) ;
}
}
void KSignalPlotterPrivate : : drawAxisText ( QPainter * p , const QRect & boundingBox )
{
if ( mHorizontalLinesCount < 0 ) return ;
p - > setFont ( q - > font ( ) ) ;
qreal stepsize = mNiceRange / ( mScaleDownBy * ( mHorizontalLinesCount + 1 ) ) ;
if ( mActualAxisTextWidth = = 0 ) //If we are drawing completely inside the plotter area, using the Text color
p - > setPen ( QPen ( q - > palette ( ) . brush ( QPalette : : Text ) , 0 ) ) ;
else
p - > setPen ( QPen ( q - > palette ( ) . brush ( QPalette : : WindowText ) , 0 ) ) ;
int axisTitleIndex = 1 ;
QString val ;
int numItems = mHorizontalLinesCount + 2 ;
int fontHeight = p - > fontMetrics ( ) . height ( ) ;
if ( numItems = = 2 & & boundingBox . height ( ) < fontHeight * 2 )
numItems = 1 ;
mAxisTextOverlapsPlotter = false ;
for ( int y = 0 ; y < numItems ; y + + , axisTitleIndex + + ) {
int y_coord = boundingBox . top ( ) + ( y * ( boundingBox . height ( ) - fontHeight ) ) / ( mHorizontalLinesCount + 1 ) ; //Make sure it's y*h first to avoid rounding bugs
qreal value ;
if ( y = = mHorizontalLinesCount + 1 )
value = mNiceMinValue / mScaleDownBy ; //sometimes using the formulas gives us a value very slightly off
else
value = mNiceMaxValue / mScaleDownBy - y * stepsize ;
val = scaledValueAsString ( value , mPrecision ) ;
QRect textBoundingRect = p - > fontMetrics ( ) . boundingRect ( val ) ;
if ( textBoundingRect . width ( ) > mActualAxisTextWidth )
mAxisTextOverlapsPlotter = true ;
int offset = qMax ( mActualAxisTextWidth - textBoundingRect . right ( ) , - textBoundingRect . left ( ) ) ;
if ( q - > layoutDirection ( ) = = Qt : : RightToLeft )
p - > drawText ( boundingBox . left ( ) , y_coord , boundingBox . width ( ) - offset , fontHeight + 1 , Qt : : AlignLeft | Qt : : AlignTop , val ) ;
else
p - > drawText ( boundingBox . left ( ) + offset , y_coord , boundingBox . width ( ) - offset , fontHeight + 1 , Qt : : AlignLeft | Qt : : AlignTop , val ) ;
}
}
void KSignalPlotterPrivate : : drawHorizontalLines ( QPainter * p , const QRect & boundingBox ) const
{
if ( mHorizontalLinesCount < = 0 ) return ;
p - > setPen ( QPen ( q - > palette ( ) . color ( QPalette : : Window ) ) ) ;
for ( int y = 0 ; y < = mHorizontalLinesCount + 1 ; y + + ) {
//note that the y_coord starts from 0. so we draw from pixel number 0 to h-1. Thus the -1 in the y_coord
int y_coord = boundingBox . top ( ) + ( y * ( boundingBox . height ( ) - 1 ) ) / ( mHorizontalLinesCount + 1 ) ; //Make sure it's y*h first to avoid rounding bugs
p - > drawLine ( boundingBox . left ( ) , y_coord , boundingBox . right ( ) - 1 , y_coord ) ;
}
}
int KSignalPlotter : : currentAxisPrecision ( ) const
{
return d - > mPrecision ;
}
qreal KSignalPlotter : : lastValue ( int i ) const
{
if ( d - > mBeamData . isEmpty ( ) | | d - > mBeamData . first ( ) . size ( ) < = i ) return std : : numeric_limits < qreal > : : quiet_NaN ( ) ;
return d - > mBeamData . first ( ) . at ( i ) ;
}
QString KSignalPlotter : : lastValueAsString ( int i , int precision ) const
{
2016-11-16 16:23:22 +00:00
if ( d - > mBeamData . isEmpty ( ) | | d - > mBeamData . first ( ) . size ( ) < = i | | std : : isnan ( d - > mBeamData . first ( ) . at ( i ) ) ) return QString ( ) ;
2014-11-13 19:30:51 +02:00
return valueAsString ( d - > mBeamData . first ( ) . at ( i ) , precision ) ; //retrieve the newest value for this beam
}
QString KSignalPlotter : : valueAsString ( qreal value , int precision ) const
{
2016-11-16 16:23:22 +00:00
if ( std : : isnan ( value ) )
2014-11-13 19:30:51 +02:00
return QString ( ) ;
value = value / d - > mScaleDownBy ; // scale the value. E.g. from Bytes to KiB
return d - > scaledValueAsString ( value , precision ) ;
}
QString KSignalPlotterPrivate : : scaledValueAsString ( qreal value , int precision ) const
{
qreal absvalue = qAbs ( value ) ;
if ( precision = = - 1 ) {
if ( absvalue > = 99.5 )
precision = 0 ;
else if ( absvalue > = 0.995 | | ( mScaleDownBy = = 1 & & mNiceMaxValue > 20 ) )
precision = 1 ;
else
precision = 2 ;
}
if ( absvalue < 1E6 ) {
if ( precision = = 0 )
return mUnit . subs ( ( long ) value ) . toString ( ) ;
else
return mUnit . subs ( value , 0 , ' f ' , precision ) . toString ( ) ;
}
else
return mUnit . subs ( value , 0 , ' g ' , precision ) . toString ( ) ;
}
bool KSignalPlotter : : smoothGraph ( ) const
{
return d - > mSmoothGraph ;
}
void KSignalPlotter : : setSmoothGraph ( bool smooth )
{
d - > mSmoothGraph = smooth ;
# ifdef USE_QIMAGE
d - > mScrollableImage = QImage ( ) ;
# else
d - > mScrollableImage = QPixmap ( ) ;
# endif
}
bool KSignalPlotter : : stackGraph ( ) const
{
return d - > mStackBeams ;
}
void KSignalPlotter : : setStackGraph ( bool stack )
{
d - > mStackBeams = stack ;
# ifdef USE_QIMAGE
d - > mScrollableImage = QImage ( ) ;
# else
d - > mScrollableImage = QPixmap ( ) ;
# endif
}
int KSignalPlotter : : fillOpacity ( ) const
{
return d - > mFillOpacity ;
}
void KSignalPlotter : : setFillOpacity ( int fill )
{
d - > mFillOpacity = fill ;
# ifdef USE_QIMAGE
d - > mScrollableImage = QImage ( ) ;
# else
d - > mScrollableImage = QPixmap ( ) ;
# endif
}
QSize KSignalPlotter : : sizeHint ( ) const
{
return QSize ( 200 , 200 ) ; //Just a random size which would usually look okay
}
GraphWidget : : GraphWidget ( QWidget * parent ) : QWidget ( parent )
{
setAttribute ( Qt : : WA_NoSystemBackground ) ;
setAttribute ( Qt : : WA_OpaquePaintEvent ) ;
}