[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-panel-devel
Subject:    Re: [Panel-devel] Signalplotter for Plasma
From:       Percy Leonhardt <mailings () eris23 ! de>
Date:       2007-08-01 18:10:58
Message-ID: 200708012010.58658.mailings () eris23 ! de
[Download RAW message or body]

On Tuesday 31 July 2007, Aaron J. Seigo wrote:
> yes, please do so ... you can either check them into
> trunk/playground/base/plasma/widgets (the latter directory doesn't exist
> atm) or send to this mailing list...

I attached them as I am not sure what to do with them in the mentioned folder 
i.e. how the widgets in there should get compiled. Should they become a 
library like libplasma? When the applets will be spread to different areas in 
the next time (extragear, kdebase etc) how can I make sure that these widgets 
are available when compiling my applet?

If you want you can explain to me in short how it is supposed to work and I 
will try to commit it that way (with some copy & paste from other CMakeLists) 
or - if it is faster for you - simply commit the files yourself. ;-)

	Percy

["signalplotter.cpp" (text/x-c++src)]

/*
    KSysGuard, the KDE System Guard

    Copyright (c) 1999 - 2002 Chris Schlaeger <cs@kde.org>
    Copyright (c) 2006 John Tapsell <tapsell@kde.org>

    This program is free software; you can redistribute it and/or
    modify it under the terms of version 2 of the GNU General Public
    License 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 <math.h>
#include <string.h>

#include <QtGui/QPainter>
#include <QtGui/QPixmap>
#include <QtGui/QPainterPath>
#include <QtGui/QPolygon>

#include <KDebug>
#include <KGlobal>
#include <KLocale>
#include <KApplication>
#include <KStandardDirs>

#include <plasma/svg.h>

#include "signalplotter.h"

QHash<QString, Plasma::Svg *> SignalPlotter::sSvgRenderer ;

SignalPlotter::SignalPlotter( Plasma::Widget *parent)
    : Plasma::Widget( parent)
{
  mPrecision = 0;
  mBezierCurveOffset = 0;
  mSamples = 0;
  mMinValue = mMaxValue = 0.0;
  mNiceMinValue = mNiceMaxValue = 0.0;
  mNiceRange = 0;
  mUseAutoRange = true;
  mScaleDownBy = 1;
  mShowThinFrame = true;

  // Anything smaller than this does not make sense.
  setMinimumSize( QSizeF( 16, 16 ) );

  mShowVerticalLines = true;
  mVerticalLinesColor = QColor("black");
  mVerticalLinesDistance = 30;
  mVerticalLinesScroll = true;
  mVerticalLinesOffset = 0;
  mHorizontalScale = 1;

  mShowHorizontalLines = true;
  mHorizontalLinesColor = QColor("black");
  mHorizontalLinesCount = 5;

  mShowLabels = true;
  mShowTopBar = true;
  mStackBeams = true;
  mFillBeams = true;

  mBackgroundColor = QColor(0,0,0);
}

SignalPlotter::~SignalPlotter()
{
}

Qt::Orientations SignalPlotter::expandingDirections() const
{
    return Qt::Horizontal | Qt::Vertical;
}

QString SignalPlotter::translatedUnit() const {
  return mUnit;
}
void SignalPlotter::setTranslatedUnit(const QString &unit) {
  mUnit= unit;
}


void SignalPlotter::addBeam( const QColor &color )
{
  QLinkedList< QList<double> >::Iterator it;
  //When we add a new beam, go back and set the data for this beam to 0 for all the \
other times. This is because it makes it easier for  //moveSensors
  for(it = mBeamData.begin(); it != mBeamData.end(); ++it) {
    (*it).append(0);
  }
  mBeamColors.append(color);
  mBeamColorsDark.append(color.dark(150));
}

void SignalPlotter::addSample( const QList<double>& sampleBuf )
{
  if(mSamples < 4) {
    //It might be possible, under some race conditions, for addSample to be called \
before mSamples is set  //This is just to be safe
    kDebug(1215) << "Error - mSamples is only " << mSamples << endl;
    updateDataBuffers();
    kDebug(1215) << "mSamples is now " << mSamples << endl;
    if(mSamples < 4)
      return;
  }
  mBeamData.prepend(sampleBuf);
  Q_ASSERT(sampleBuf.count() == mBeamColors.count());
  if((unsigned int)mBeamData.size() > mSamples) {
    mBeamData.removeLast(); // we have too many.  Remove the last item
    if((unsigned int)mBeamData.size() > mSamples)
      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(mBezierCurveOffset >= 2) mBezierCurveOffset = 0;
  else mBezierCurveOffset++;

  Q_ASSERT((uint)mBeamData.size() >= mBezierCurveOffset);

  /* If the vertical lines are scrolling, increment the offset
   * so they move with the data. */
  if ( mVerticalLinesScroll ) {
    mVerticalLinesOffset = ( mVerticalLinesOffset + mHorizontalScale)
                           % mVerticalLinesDistance;
  }
  update();
}

void SignalPlotter::reorderBeams( const QList<int>& newOrder )
{
  if(newOrder.count() != mBeamColors.count()) {
    kDebug(1215) << "neworder has " << newOrder.count() << " and beam colors is " << \
mBeamColors.count() << endl;  return;
  }
  QLinkedList< QList<double> >::Iterator it;
  for(it = mBeamData.begin(); it != mBeamData.end(); ++it) {
    if(newOrder.count() != (*it).count()) {
      kDebug(1215) << "Serious problem in move sample.  beamdata[i] has " << \
(*it).count() << " and neworder has " << newOrder.count() << endl;  } else {
     QList<double> 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(mBeamColorsDark.at(newIndex));
  }
  mBeamColors = newBeamColors;
  mBeamColorsDark = newBeamColorsDark;
}


void SignalPlotter::changeRange( double min, double max )
{
  mMinValue = min;
  mMaxValue = max;
  calculateNiceRange();
}

QList<QColor> &SignalPlotter::beamColors()
{
  return mBeamColors;
}

void SignalPlotter::removeBeam( uint pos )
{
  if(pos >= (uint)mBeamColors.size()) return;
  mBeamColors.removeAt( pos );

  QLinkedList< QList<double> >::Iterator i;
  for(i = mBeamData.begin(); i != mBeamData.end(); ++i) {
    if( (uint)(*i).size() >= pos)
      (*i).removeAt(pos);
  }
}

void SignalPlotter::setScaleDownBy( double value )
{
  if(mScaleDownBy == value) return;
  mScaleDownBy = value;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
  calculateNiceRange();
}
double SignalPlotter::scaleDownBy() const { return mScaleDownBy; }

void SignalPlotter::setTitle( const QString &title )
{
  if(mTitle == title) return;
  mTitle = title;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

QString SignalPlotter::title() const
{
  return mTitle;
}

void SignalPlotter::setUseAutoRange( bool value )
{
  mUseAutoRange = value;
  calculateNiceRange();
  //this change will be detected in paint and the image cache regenerated
}

bool SignalPlotter::useAutoRange() const
{
  return mUseAutoRange;
}

void SignalPlotter::setMinValue( double min )
{
  mMinValue = min;
  calculateNiceRange();
  //this change will be detected in paint and the image cache regenerated
}

double SignalPlotter::minValue() const
{
  return mMinValue;
}

void SignalPlotter::setMaxValue( double max )
{
  mMaxValue = max;
  calculateNiceRange();
  //this change will be detected in paint and the image cache regenerated
}

double SignalPlotter::maxValue() const
{
  return mMaxValue;
}

void SignalPlotter::setHorizontalScale( uint scale )
{
  if (scale == mHorizontalScale)
     return;

  mHorizontalScale = scale;
  updateDataBuffers();
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

int SignalPlotter::horizontalScale() const
{
  return mHorizontalScale;
}

void SignalPlotter::setShowVerticalLines( bool value )
{
  if(mShowVerticalLines == value) return;
  mShowVerticalLines = value;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

bool SignalPlotter::showVerticalLines() const
{
  return mShowVerticalLines;
}

void SignalPlotter::setVerticalLinesColor( const QColor &color )
{
  if(mVerticalLinesColor == color) return;
  mVerticalLinesColor = color;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

QColor SignalPlotter::verticalLinesColor() const
{
  return mVerticalLinesColor;
}

void SignalPlotter::setVerticalLinesDistance( uint distance )
{
  if(distance == mVerticalLinesDistance) return;
  mVerticalLinesDistance = distance;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

int SignalPlotter::verticalLinesDistance() const
{
  return mVerticalLinesDistance;
}

void SignalPlotter::setVerticalLinesScroll( bool value )
{
  if(value == mVerticalLinesScroll) return;
  mVerticalLinesScroll = value;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

bool SignalPlotter::verticalLinesScroll() const
{
  return mVerticalLinesScroll;
}

void SignalPlotter::setShowHorizontalLines( bool value )
{
  if(value == mShowHorizontalLines) return;
  mShowHorizontalLines = value;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

bool SignalPlotter::showHorizontalLines() const
{
  return mShowHorizontalLines;
}
void SignalPlotter::setFontColor( const QColor &color )
{
  mFontColor = color;
}

QColor SignalPlotter::fontColor() const
{
  return mFontColor;
}


void SignalPlotter::setHorizontalLinesColor( const QColor &color )
{
  if(color == mHorizontalLinesColor) return;
  mHorizontalLinesColor = color;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

QColor SignalPlotter::horizontalLinesColor() const
{
  return mHorizontalLinesColor;
}

void SignalPlotter::setHorizontalLinesCount( uint count )
{
  if(count == mHorizontalLinesCount) return;
  mHorizontalLinesCount = count;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
  calculateNiceRange();
}

int SignalPlotter::horizontalLinesCount() const
{
  return mHorizontalLinesCount;
}

void SignalPlotter::setShowLabels( bool value )
{
  if(value == mShowLabels) return;
  mShowLabels = value;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

bool SignalPlotter::showLabels() const
{
  return mShowLabels;
}

void SignalPlotter::setShowTopBar( bool value )
{
  if(mShowTopBar == value) return;
  mShowTopBar = value;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

bool SignalPlotter::showTopBar() const
{
  return mShowTopBar;
}

void SignalPlotter::setFont( const QFont &font )
{
  mFont = font;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

QFont SignalPlotter::font() const
{
  return mFont;
}
QString SignalPlotter::svgBackground() {
  return mSvgFilename;
}
void SignalPlotter::setSvgBackground( const QString &filename )
{
  if(mSvgFilename == filename) return;

  if (!filename.isEmpty() && filename[0] == '/') {
    KStandardDirs* kstd = KGlobal::dirs();
    mSvgFilename = kstd->findResource( "data", "ksysguard/" + filename);
  } else {
    mSvgFilename = filename;
  }
  //NOTE:  We don't free the old svg renderer.  This means that it will leak if we \
set it to use one svg, then reset it to use another svg.  //The svg rendererer object \
will be created on demand in drawBackground }

void SignalPlotter::setBackgroundColor( const QColor &color )
{
  if(color == mBackgroundColor) return;
  mBackgroundColor = color;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

QColor SignalPlotter::backgroundColor() const
{
  return mBackgroundColor;
}

void SignalPlotter::setThinFrame( bool set)
{
  if(mShowThinFrame == set) return;
  mShowThinFrame = set;
  mBackgroundImage = QImage(); //we changed a paint setting, so reset the cache
}

void SignalPlotter::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. */
  mSamples = static_cast<uint>( ( ( size().width() - 2 ) /
                                mHorizontalScale ) + 4.5 );
}

QImage SignalPlotter::getSnapshotImage(uint w, uint height)
{
  uint horizontalStep = (uint)((1.0*w/size().width())+0.5); //get the closest integer \
horizontal step  uint newWidth = horizontalStep * size().width();
  QImage image = QImage(newWidth, height, QImage::Format_RGB32);
  QPainter p(&image);
  drawWidget(&p, newWidth, height, newWidth);
  return image;
}
void SignalPlotter::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, \
QWidget *widget) {
  Q_UNUSED(option);
  Q_UNUSED(widget);

  uint w = size().width();
  uint h = size().height();

  /* Do not do repaints when the widget is not yet setup properly. */
  if ( w <= 2 )
    return;

  drawWidget(painter, w, h, mHorizontalScale);
}
void SignalPlotter::drawWidget(QPainter *p, uint w, uint height, int horizontalScale)
{
  uint h = height; //h will become the height of just the bit we draw the beams in
  p->setFont( mFont );

  uint fontheight = p->fontMetrics().height();
  if(mMinValue < mNiceMinValue || mMaxValue > mNiceMaxValue || mMaxValue < \
(mNiceRange*0.75 + mNiceMinValue) || mNiceRange == 0)  calculateNiceRange();
  QPen pen;
  pen.setWidth(1);
  pen.setCapStyle(Qt::RoundCap);
  p->setPen(pen);

  uint top = p->pen().width() / 2; //The y position of the top of the graph.  \
Basically this is one more than the height of the top bar  h-= top;

  //check if there's enough room to actually show a top bar. Must be enough room for \
a bar at the top, plus horizontal lines each of a size with room for a scale  bool \
showTopBar = mShowTopBar &&  h > (fontheight/*top bar size*/ +5/*smallest reasonable \
size for a graph*/ );  if(showTopBar) {
    top += fontheight; //The top bar has the same height as fontheight. Thus the top \
of the graph is at fontheight  h -= fontheight;
  }
  if(mBackgroundImage.isNull() || (uint)mBackgroundImage.size().height() != height || \
(uint)mBackgroundImage.size().width() != w) { //recreate on resize etc  \
mBackgroundImage = QImage(w, height, QImage::Format_RGB32);  QPainter \
pCache(&mBackgroundImage);  pCache.setRenderHint(QPainter::Antialiasing, false);
    pCache.setFont( mFont );

    drawBackground(&pCache, w, height);

    if(mShowThinFrame) {
      drawThinFrame(&pCache, w, height);
      //We have a 'frame' in the bottom and right - so subtract them from the view
      h--;
      w--;
      pCache.setClipRect( 0, 0, w, height-1 );
    }

    if(showTopBar) {
      int seperatorX = w / 2;
      drawTopBarFrame(&pCache, w, seperatorX, top);
    }

    /* 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(!mVerticalLinesScroll && mShowVerticalLines \
&& w > 60)  drawVerticalLines(&pCache, top, w, h);

    if ( mShowHorizontalLines )
      drawHorizontalLines(&pCache, top, w, h);

  } else {
    if(mShowThinFrame) {
      //We have a 'frame' in the bottom and right - so subtract them from the view
      h--;
      w--;
   }
  }
  p->drawImage(0,0, mBackgroundImage);
  p->setRenderHint(QPainter::Antialiasing, true);

  if ( showTopBar ) {
    int seperatorX = w / 2;
    int topBarWidth = w - seperatorX -2;
    drawTopBarContents(p, seperatorX, topBarWidth, top -1);
  }

  p->setClipRect( 0, top, w, h);
  /* Draw scope-like grid vertical lines */
  if ( mVerticalLinesScroll && mShowVerticalLines && w > 60 )
    drawVerticalLines(p, top, w, h);

  drawBeams(p, top, w, h, horizontalScale);

  if( mShowLabels && w > 60 && h > ( fontheight + 1 ) )   //if there's room to draw \
the labels, then draw them!  drawAxisText(p, top, h);

}
void SignalPlotter::drawBackground(QPainter *p, int w, int h)
{
  p->fillRect(0,0,w, h, mBackgroundColor);

  if(mSvgFilename.isEmpty())
    return; //nothing to draw, return

  Plasma::Svg *svgRenderer;
  if(!sSvgRenderer.contains(mSvgFilename)) {
    svgRenderer = new Plasma::Svg(mSvgFilename);
    sSvgRenderer.insert(mSvgFilename, svgRenderer);
  } else {
    svgRenderer = sSvgRenderer[mSvgFilename];
  }

  svgRenderer->resize(w, h);
  svgRenderer->paint(p, 0, 0);
}

void SignalPlotter::drawThinFrame(QPainter *p, int w, int h)
{
  /* Draw white line along the bottom and the right side of the
   * widget to create a 3D like look. */
  p->setPen( kapp->palette().color( QPalette::Light ) );
  p->drawLine( 0, h - 1, w - 1, h - 1 );
  p->drawLine( w - 1, 0, w - 1, h - 1 );
}

void SignalPlotter::calculateNiceRange()
{
  mNiceRange = mMaxValue - mMinValue;
  /* If the range is too small we will force it to 1.0 since it
   * looks a lot nicer. */
  if ( mNiceRange < 0.000001 )
    mNiceRange = 1.0;

  mNiceMinValue = mMinValue;
//  if ( mUseAutoRange ) {
    if ( mMinValue != 0.0 ) {
      double dim = pow( 10, floor( log10( fabs( mMinValue ) ) ) ) / 2;
      if ( mMinValue < 0.0 )
        mNiceMinValue = dim * floor( mMinValue / dim );
      else
        mNiceMinValue = dim * ceil( mMinValue / dim );
      mNiceRange = mMaxValue - mNiceMinValue;
      if ( mNiceRange < 0.000001 )
        mNiceRange = 1.0;
    }
    // Massage the range so that the grid shows some nice values.
    double step = mNiceRange / (mScaleDownBy*(mHorizontalLinesCount+1));
    int logdim = (int)floor( log10( step ) );
    double dim = pow( 10, logdim ) / 2;
    int a = (int)ceil( step / dim );
    if(logdim >= 0)
        mPrecision = 0;
    else if( a % 2 == 0){
        mPrecision =-logdim;
    } else {
	mPrecision = 1-logdim;
    }
    mNiceRange = mScaleDownBy*dim * a * (mHorizontalLinesCount+1);
//  }
  mNiceMaxValue = mNiceMinValue + mNiceRange;
}


void SignalPlotter::drawTopBarFrame(QPainter *p, int fullWidth, int seperatorX, int \
height) {
      /* Draw horizontal bar with current sensor values at top of display. */

      //remember that it has a height of 'height'.  Thus the lowest pixel it can draw \
on is height-1 since we count from 0  p->setPen( Qt::NoPen);
//      p->fillRect( 0, 0, fullWidth, height-1, QBrush(QColor(255,255,255,60)));
      p->setPen( mFontColor );
      p->drawText(0, 1, seperatorX, height, Qt::AlignCenter, mTitle );
      p->setPen( mHorizontalLinesColor );
      p->drawLine( seperatorX - 1, 1, seperatorX - 1, height-1 );
}

void SignalPlotter::drawTopBarContents(QPainter *p, int x, int width, int height)
{
  //The height is the height of the contents, so this will be one pixel less than the \
height of the topbar  double bias = -mNiceMinValue;
  double scaleFac = width / mNiceRange;
  QList<QColor>::Iterator col;
  col = mBeamColors.end();
  /**
   * The top bar shows the current values of all the beam data.
   * This iterates through each different beam and plots the newest data for each
   */
  if ( !mBeamData.isEmpty() ) {
    QList<double> newestData = mBeamData.first();
    for(int i = newestData.count()-1; i >= 0; --i) {
      double newest_datapoint = newestData.at(i);
      int start = x + (int)( bias * scaleFac );
      int end = x + (int)( ( bias += newest_datapoint ) * scaleFac );
      int start2 = qMin(start,end);
      end = qMax(start,end);
      start = start2;

      /* If the rect is wider than 2 pixels we draw only the last
       * pixels with the bright color. The rest is painted with
       * a 50% darker color. */

      p->setPen(Qt::NoPen);
      QLinearGradient  linearGrad( QPointF(start,1), QPointF(end, 1));
      linearGrad.setColorAt(0, mBeamColorsDark[i]);
      linearGrad.setColorAt(1, mBeamColors[i]);
      p->fillRect( start, 1, end - start, height-1, QBrush(linearGrad));
    }
  }
}
void SignalPlotter::drawVerticalLines(QPainter *p, int top, int w, int h)
{
  p->setPen( mVerticalLinesColor );
  for ( int x = mVerticalLinesOffset; x < ( w - 2 ); x += mVerticalLinesDistance )
      p->drawLine( w - x, top, w - x, h + top -1 );
}

void SignalPlotter::drawBeams(QPainter *p, int top, int w, int h, int \
horizontalScale) {
  Q_ASSERT(mNiceRange != 0); if(mNiceRange == 0) mNiceRange = 1;
  double scaleFac = (h-1) / mNiceRange;

  int xPos = 0;
  QLinkedList< QList<double> >::Iterator it = mBeamData.begin();

  p->setPen(Qt::NoPen);
  /* In autoRange mode we determine the range and plot the values in
   * one go. This is more efficiently than running through the
   * buffers twice but we do react on recently discarded samples as
   * well as new samples one plot too late. So the range is not
   * correct if the recently discarded samples are larger or smaller
   * than the current extreme values. But we can probably live with
   * this.
   *
   * These values aren't used directly anywhere.  Instead we call
   * calculateNiceRange()  which massages these values into a nicer
   * values.  Rounding etc.  This means it's safe to change these values
   * without affecting any other drawings
   * */
  if ( mUseAutoRange )
    mMinValue = mMaxValue = 0.0;

  /* mBezierCurveOffset is how many points we have at the start.
   * All the bezier curves are in groups of 3, with the first of the next group being \
                the last point
   * of the previous group->
   *
   * Example, when mBezierCurveOffset == 0, and we have data, then just plot a normal \
                bezier curve
   * (we will have at least 3 points in this case)
   * When mBezierCurveOffset == 1, then we want a bezier curve that uses the first \
                data point and
   * the second data point.  Then the next group starts from the second data point.
   * When mBezierCurveOffset == 2, then we want a bezier curve that uses the first, \
                second and third data
   *
   */
  for (unsigned int i = 0; it != mBeamData.end() && i < mSamples; ++i) {
    QPen pen;
    pen.setWidth(1);
    pen.setCapStyle(Qt::FlatCap);

    /**
     * We will plot 1 bezier curve for every 3 points, with the 4th point being the \
                end
     * of one bezier curve and the start of the second.
     * This does means the bezier curves will not join nicely,
     * but it should be better than nothing.
     */

    QList<double> datapoints = *it;
    QList<double> prev_datapoints = datapoints;
    QList<double> prev_prev_datapoints = datapoints;
    QList<double> prev_prev_prev_datapoints = datapoints;

    if (i == 0 && mBezierCurveOffset>0) {
      /**
       * We are plotting an incomplete bezier curve - we don't have all the data we \
                want.
       * Try to cope
       */
      xPos += horizontalScale*mBezierCurveOffset;
      if (mBezierCurveOffset == 1) {
        prev_datapoints = *it;
        ++it; //Now we are on the first element of the next group, if it exists
        if (it != mBeamData.end()) {
          prev_prev_prev_datapoints = prev_prev_datapoints = *it;
        } else {
          prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints;
        }
      } else {
        // mBezierCurveOffset must be 2 now
        prev_datapoints = *it;
        Q_ASSERT(it != mBeamData.end());
        ++it;
        prev_prev_datapoints = *it;
        Q_ASSERT(it != mBeamData.end());
        ++it; //Now we are on the first element of the next group, if it exists
        if (it != mBeamData.end()) {
          prev_prev_prev_datapoints = *it;
        } else {
          prev_prev_prev_datapoints = prev_prev_datapoints;
        }
      }
    } else {
      /**
       * We have a group of 3 points at least.  That's 1 start point and 2 control \
                points.
       */
      xPos += horizontalScale*3;
      it++;
      if (it != mBeamData.end()) {
        prev_datapoints = *it;
        it++;
        if (it != mBeamData.end()) {
          prev_prev_datapoints = *it;
          it++;  //We are now on the next set of data points
          if (it != mBeamData.end()) {
            // We have this datapoint, so use it for our finish point
            prev_prev_prev_datapoints = *it;
          } else {
            // We don't have the next set, so use our last control point as our \
finish point  prev_prev_prev_datapoints = prev_prev_datapoints;
          }
        } else {
          prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints;
        }
      } else {
          prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints = \
datapoints;  }
    }


    float x0 = w - xPos + 3.0*horizontalScale;
    float x1 = w - xPos + 2.0*horizontalScale;
    float x2 = w - xPos + 1.0*horizontalScale;
    float x3 = w - xPos;
    float y0 = h -1 + top;
    float y1 = y0;
    float y2 = y0;
    float y3 = y0;

    int offset = 0; //Our line is 2 pixels thick.  This means that when we draw the \
area, we need to offset  double max_y=0;
    double min_y=0;
    for (int j =  qMin(datapoints.size(), mBeamColors.size())-1; j >=0 ; --j) {
      if ( mUseAutoRange) {
        //If we use autorange, then we need to prepare the min and max values for \
_next_ time we paint  //if we are stacking the beams, then we need to add the \
maximums together  double current_maxvalue = qMax(datapoints[j], \
qMax(prev_datapoints[j], qMax(prev_prev_datapoints[j], \
prev_prev_prev_datapoints[j])));  double current_minvalue = qMin(datapoints[j], \
qMin(prev_datapoints[j], qMin(prev_prev_datapoints[j], \
prev_prev_prev_datapoints[j])));  mMaxValue = qMax(mMaxValue, current_maxvalue);
	mMinValue = qMin(mMinValue, current_maxvalue);
	if( mStackBeams ) {
	  max_y += current_maxvalue;
	  min_y += current_minvalue;
	}
      }

      /*
       * Draw polygon only if enough data points are available.
       */
      if ( j < prev_prev_prev_datapoints.count() &&
           j < prev_prev_datapoints.count() &&
           j < prev_datapoints.count() ) {

        QPolygon curve( 4 );


        /* The height of the whole widget is h+top->  The height of the area we are \
                plotting in is just h.
	 * The y coordinate system starts from the top, so at the bottom the y coordinate is \
                h+top
	 * So to draw a point at value y', we need to put this at  h+top-y'
	 */
	float delta_y0;
        delta_y0 = (datapoints[j] - mNiceMinValue)*scaleFac;

	float delta_y1;
	delta_y1 = (prev_datapoints[j] - mNiceMinValue)*scaleFac;

	float delta_y2;
	delta_y2 = (prev_prev_datapoints[j] - mNiceMinValue)*scaleFac;

	float delta_y3;
	delta_y3 = (prev_prev_prev_datapoints[j] - mNiceMinValue)*scaleFac;

        QPainterPath path;
	if(mStackBeams && offset) {
		//we don't want the lines to overdraw each other.  This isn't a great solution \
though :(  if(delta_y0 < 3) delta_y0=3;
	  if(delta_y1 < 3) delta_y1=3;
	  if(delta_y2 < 3) delta_y2=3;
	  if(delta_y3 < 3) delta_y3=3;
	}
	path.moveTo( x0,y0-delta_y0);
        path.cubicTo( x1,y1-delta_y1,x2,y2-delta_y2,x3,y3-delta_y3 );

	if(mFillBeams) {
	  QPainterPath path2(path);
          QLinearGradient myGradient(0,(h-1+top),0,(h-1+top)/5);
	  Q_ASSERT(mBeamColorsDark.size() >= j);
	  Q_ASSERT(mBeamColors.size() >= j);
	  QColor c0(mBeamColorsDark[j]);
	  QColor c1(mBeamColors[j]);
	  c0.setAlpha(150);
	  c1.setAlpha(150);
	  myGradient.setColorAt(0, c0);
          myGradient.setColorAt(1, c1);

          path2.lineTo( x3,y3-offset);
	  if(mStackBeams)
	    path2.cubicTo( x2,y2-offset,x1,y1-offset,x0,y0-offset); //offset is set to 1 \
after the first beam is drawn, so we don't trample on top of the 2pt thick line  else
	    path2.lineTo(x0,y0-1);
          p->setBrush(myGradient);
          p->setPen(Qt::NoPen);
          p->drawPath( path2 );
	}
	p->setBrush(Qt::NoBrush);
	Q_ASSERT(mBeamColors.size() >= j);
	pen.setColor(mBeamColors[j]);
	p->setPen(pen);
        p->drawPath( path );

	if(mStackBeams) {
          //We can draw the beams stacked on top of each other.  This means that say \
beam 0 has the value 2 and beam  // 1 has the value 3, then we plot beam 0 at 2 and \
beam 1 at 2+3 = 5.  y0-=delta_y0;
	  y1-=delta_y1;
	  y2-=delta_y2;
	  y3-=delta_y3;
	  offset = 1;  //see the comment further up for int offset;
	}
      }
      if ( mUseAutoRange && mStackBeams) {
	mMaxValue = qMax(max_y, mMaxValue);
	mMinValue = qMin(min_y, mMinValue);
      }
    }
  }
}
void SignalPlotter::drawAxisText(QPainter *p, int top, int h)
{
  /* Draw horizontal lines and values. Lines are always drawn.
   * Values are only draw when width is greater than 60 */
  QString val;
  /* top = 0 or  font.height    depending on whether there's a topbar or not
   * h = graphing area.height   - i.e. the actual space we have to draw inside
   *
   * Note we are drawing from 0,0 as the top left corner.  So we have to add on top \
                to get to the top of where we are drawing
   * so top+h is the height of the widget
   */

  p->setPen( mFontColor );
  double stepsize = mNiceRange/(mScaleDownBy*(mHorizontalLinesCount+1));
  int step = (int)ceil((mHorizontalLinesCount+1) * (p->fontMetrics().height() + \
p->fontMetrics().leading()/2.0) / h);  if(step ==0) step = 1;
  for ( int y = mHorizontalLinesCount+1; y >= 1; y-= step) {
    int y_coord =  top + (y * (h-1)) / (mHorizontalLinesCount+1);  //Make sure it's \
y*h first to avoid rounding bugs  if(y_coord - p->fontMetrics().ascent() < top) \
continue;  //at most, only allow 4 pixels of the text to be covered up by the top \
bar.  Otherwise just don't bother to draw it  double value;
    if((uint)y == mHorizontalLinesCount+1)
        value = mNiceMinValue; //sometimes using the formulas gives us a value very \
slightly off  else
        value = mNiceMaxValue/mScaleDownBy - y * stepsize;

    QString number = KGlobal::locale()->formatNumber( value, mPrecision);
    val = QString( "%1 %2" ).arg( number, mUnit );
    p->drawText( 6, y_coord - 3, val );
  }
}

void SignalPlotter::drawHorizontalLines(QPainter *p, int top, int w, int h)
{
  p->setPen( mHorizontalLinesColor );
  for ( uint 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 =  top + (y * (h-1)) / \
(mHorizontalLinesCount+1);  //Make sure it's y*h first to avoid rounding bugs  \
p->drawLine( 0, y_coord, w - 2, y_coord);  }
}

double SignalPlotter::lastValue( int i) const
{
  if(mBeamData.isEmpty() || mBeamData.first().size() <= i) return 0;
  return mBeamData.first()[i];
}
QString SignalPlotter::lastValueAsString( int i) const
{
  if(mBeamData.isEmpty()) return QString();
  double value = mBeamData.first()[i] / mScaleDownBy; //retrieve the newest value for \
this beam then scale it correct  QString number = KGlobal::locale()->formatNumber( \
value, (value >= 100)?0:2);  return QString( "%1 %2").arg(number, mUnit);
}


["signalplotter.h" (text/x-c++hdr)]

/*
    KSysGuard, the KDE System Guard

    Copyright (c) 1999 - 2001 Chris Schlaeger <cs@kde.org>

    This program is free software; you can redistribute it and/or
    modify it under the terms of version 2 of the GNU General Public
    License 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.

*/

#ifndef SIGNALPLOTTER_H
#define SIGNALPLOTTER_H


#include <QWidget>
#include <QResizeEvent>
#include <QPaintEvent>
#include <QLinkedList>
#include <QImage>

#include <plasma/widgets/widget.h>

class QColor;
namespace Plasma
{
    class Svg;
} // namespace Plasma

class SignalPlotter : public Plasma::Widget
{
  public:
    SignalPlotter( Widget *parent = 0);
    ~SignalPlotter();

    Qt::Orientations expandingDirections() const;

    /** Add a new line to the graph plotter, with the specified color.
     *  Note that the order you add the beams must be the same order that
     *  the same data is given in. (Unless you reorder the beams)
     */
    void addBeam( const QColor &color );
    /** Add data to the graph, and advance the graph by one time period.
     *  The data must be given as a list in the same order that the beams were
     *  added (or consequently reordered)
     */
    void addSample( const QList<double> &samples );
    /** Reorder the beams into the order given.  For example:
     * \code
     *   KSignalPlotter *s = KSignalPlotter(parent);
     *   s->addBeam(Qt::Blue);
     *   s->addBeam(Qt::Green);
     *   QList neworder;
     *   neworder << 1 << 0;
     *   reorderBeams( newOrder);
     *   //Now the order is Green then Blue
     * \endcode
     */
    void reorderBeams( const QList<int>& newOrder );

    /** Removes the beam at the specified index.
     */
    void removeBeam( uint pos );

    /** Return the list of beam (the graph lines) colors, in the order
     *  that the beams
     *  were added (or later reordered)
     */
    QList<QColor> &beamColors();

    /** Set the title of the graph.  Drawn in the top left. */
    void setTitle( const QString &title );
    /** Get the title of the graph.  Drawn in the top left. */
    QString title() const;

    /** Set the units.  Drawn on the vertical axis of the graph.
     *  Must be already translated into the local language.
     */
    void setTranslatedUnit( const QString &unit );
    /** Return the units used on the horizontal axis of the graph.
     */
    QString translatedUnit() const;

    /** Scale all the values down by the given amount.  This is useful
     *  when the data is given in, say, kilobytes, but you set the
     *  units as megabytes.  Thus you would have to call this with @p value
     *  set to 1024.  This affects all the data already entered.
     */
    void setScaleDownBy( double value );
    /** Amount scaled down by.  @see setScaleDownBy */
    double scaleDownBy() const;


    /** Set the minimum and maximum values on the vertical axis
     *  automatically from the data available.
     */
    void setUseAutoRange( bool value );
    /** Whether the vertical axis range is set automatically.
     */
    bool useAutoRange() const;
    /** Change the minimum and maximum values drawn on the graph.
     *  Note that these values are sanitised.  For example, if you
     *  set the minimum as 3, and the maximum as 97, then the graph
     *  would be drawn between 0 and 100.  The algorithm to determine
     *  this "nice range" attempts to minimize the number of non-zero
     *  digits.
     *
     *  Use setAutoRange instead to determine the range automatically
     *  from the data.
     */
    void changeRange( double min, double max );
    /** Set the min value of the vertical axis.  @see changeRange */
    void setMinValue( double min );
    /** Get the min value of the vertical axis.  @see changeRange */
    double minValue() const;
    /** Set the max value of the vertical axis.  @see changeRange */
    void setMaxValue( double max );
    /** Get the max value of the vertical axis.  @see changeRange */
    double maxValue() const;

    /** Set the number of pixels horizontally between data points */
    void setHorizontalScale( uint scale );
    /** The number of pixels horizontally between data points*/
    int horizontalScale() const;

    /** Whether to draw the vertical grid lines */
    void setShowVerticalLines( bool value );
    /** Whether to draw the vertical grid lines */
    bool showVerticalLines() const;

    /** The color of the vertical grid lines */
    void setVerticalLinesColor( const QColor &color );
    /** The color of the vertical grid lines */
    QColor verticalLinesColor() const;

    /** The horizontal distance between the vertical grid lines */
    void setVerticalLinesDistance( uint distance );
    /** The horizontal distance between the vertical grid lines */
    int verticalLinesDistance() const;

    /** Whether the vertical lines move with the data */
    void setVerticalLinesScroll( bool value );
    /** Whether the vertical lines move with the data */
    bool verticalLinesScroll() const;

    /** Whether to draw the horizontal grid lines */
    void setShowHorizontalLines( bool value );
    /** Whether to draw the horizontal grid lines */
    bool showHorizontalLines() const;

    /** The color of the horizontal grid lines */
    void setHorizontalLinesColor( const QColor &color );
    /** The color of the horizontal grid lines */
    QColor horizontalLinesColor() const;

    /** The color of the font used for the axis */
    void setFontColor( const QColor &color );
    /** The color of the font used for the axis */
    QColor fontColor() const;

    /** The font used for the axis */
    void setFont( const QFont &font );
    /** The font used for the axis */
    QFont font() const;

    /** The number of horizontal lines to draw.  Doesn't include the top
     *  most and bottom most lines. */
    void setHorizontalLinesCount( uint count );
    /** The number of horizontal lines to draw.  Doesn't include the top
     *  most and bottom most lines. */
    int horizontalLinesCount() const;

    /** Whether to show the vertical axis labels */
    void setShowLabels( bool value );
    /** Whether to show the vertical axis labels */
    bool showLabels() const;

    /** Whether to show the title etc at the top.  Even if set, it
     *  won't be shown if there isn't room */
    void setShowTopBar( bool value );
    /** Whether to show the title etc at the top.  Even if set, it
     *  won't be shown if there isn't room */
    bool showTopBar() const;

    /** The color to set the background.  This might not be seen
     *  if an svg is also set.*/
    void setBackgroundColor( const QColor &color );
    /** The color to set the background.  This might not be seen
     *  if an svg is also set.*/
    QColor backgroundColor() const;

    /** The filename of the svg background.  Set to empty to disable
     *  again. */
    void setSvgBackground( const QString &filename );
    /** The filename of the svg background.  Set to empty to disable
     *  again. */
    QString svgBackground();

    /** Return the last value that we have for beam i.
     *  Returns 0 if not known
     */
    double lastValue( int i) const;
    /** Return a translated string like:   "34 %" or "100 KB" for beam i
     */
    QString lastValueAsString( int i) const;

    /**  Whether to show a white line on the left and bottom of the widget, for a 3D \
                effect
     */
    void setThinFrame( bool set);

    /** Whether to stack the beams on top of each other.  The first beam
     *  added will be at the bottom.  The next beam will be drawn on top,
     *  and so on. */
    void setStackBeams( bool stack) { mStackBeams = stack; mFillBeams = stack; }
    /** Whether to stack the beams.  @see setStackBeams */
    bool stackBeams() const { return mStackBeams;}
    /** Render the graph to the specified width and height, and return it
     *  as an image.  This is useful, for example, if you draw a small version
     *  of the graph, but then want to show a large version in a tooltip etc */
    QImage getSnapshotImage(uint width, uint height);

  protected:
    void updateDataBuffers();

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget \
*widget);

    void drawWidget(QPainter *p, uint w, uint height, int horizontalScale);
    void drawBackground(QPainter *p, int w, int h);
    void drawThinFrame(QPainter *p, int w, int h);
    void calculateNiceRange();
    void drawTopBarFrame(QPainter *p, int fullWidth, int seperatorX, int height);
    void drawTopBarContents(QPainter *p, int x, int width, int height);
    void drawVerticalLines(QPainter *p, int top, int w, int h);
    /** Used from paint().  Draws all the beams on the paint device, given the top, \
                width and height and the range of values to show
     */
    void drawBeams(QPainter *p, int top, int w, int h, int horizontalScale);
    void drawAxisText(QPainter *p, int top, int h);
    void drawHorizontalLines(QPainter *p, int top, int w, int h);

  private:
    /** We make the svg renderer static so that an svg renderer is shared among all \
of the images.  This is because a svg renderer takes up a lot of memory, so we want \
                to
     *  share them as much as we can */
    static QHash<QString, Plasma::Svg *> sSvgRenderer;
    QString mSvgFilename;

    QImage mBackgroundImage;  //A cache of the svg

    double mMinValue;
    double mMaxValue;

    double mNiceMinValue;
    double mNiceMaxValue;
    double mNiceRange;

    double mScaleDownBy;
    bool mUseAutoRange;

    /**  Whether to show a white line on the left and bottom of the widget, for a 3D \
effect */  bool mShowThinFrame;

    /** Whether to stack the beams on top of each other */
    bool mStackBeams;
    /** Whether to fill the area underneath the beams */
    bool mFillBeams;

    uint mGraphStyle;

    bool mShowVerticalLines;
    int mPrecision;
    QColor mVerticalLinesColor;
    uint mVerticalLinesDistance;
    bool mVerticalLinesScroll;
    uint mVerticalLinesOffset;
    uint mHorizontalScale;

    bool mShowHorizontalLines;
    QColor mHorizontalLinesColor;
    uint mHorizontalLinesCount;

    bool mShowLabels;
    bool mShowTopBar;
    uint mBezierCurveOffset;

    QColor mBackgroundColor;
    QColor mFontColor;

    QLinkedList < QList<double> > mBeamData; // Every item in the linked list \
contains a set of data points to plot.  The first item is the newest  QList< QColor> \
mBeamColors;  //These colors match up against the QList<double>  in mBeamData  QList< \
QColor> mBeamColorsDark;  //These colors match up against the QList<double> in \
mBeamData, and are darker than mBeamColors.  Done for gradient effects

    unsigned int mSamples; //This is what mBeamData.size() should equal when full.  \
When we start off and have no data then mSamples will be higher.  If we resize the \
widget so it's smaller, then for a short while this will be smaller  int \
mNewestIndex; //The index to the newest item added.  newestIndex+1   is the second \
newest, and so on

    QString mTitle;
    QString mUnit;

    QFont mFont;
};

#endif



_______________________________________________
Panel-devel mailing list
Panel-devel@kde.org
https://mail.kde.org/mailman/listinfo/panel-devel


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic