• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

Plasma

signalplotter.cpp

Go to the documentation of this file.
00001 /*
00002  *   KSysGuard, the KDE System Guard
00003  *
00004  *   Copyright 1999 - 2002 Chris Schlaeger <cs@kde.org>
00005  *   Copyright 2006 John Tapsell <tapsell@kde.org>
00006  *
00007  *   This program is free software; you can redistribute it and/or modify
00008  *   it under the terms of the GNU Library General Public License as
00009  *   published by the Free Software Foundation; either version 2, or
00010  *   (at your option) any later version.
00011 
00012  *
00013  *   This program is distributed in the hope that it will be useful,
00014  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016  *   GNU General Public License for more details.
00017  *
00018  *   You should have received a copy of the GNU General Public License
00019  *   along with this program; if not, write to the Free Software
00020  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00021  */
00022 
00023 #include "signalplotter.h"
00024 
00025 #include <math.h>
00026 #include <string.h>
00027 
00028 #include <QApplication>
00029 #include <QList>
00030 #include <QPalette>
00031 #include <QtGui/QPainter>
00032 #include <QtGui/QPixmap>
00033 #include <QtGui/QPainterPath>
00034 #include <QtGui/QPolygon>
00035 
00036 #include <kdebug.h>
00037 #include <kglobal.h>
00038 #include <klocale.h>
00039 #include <kstandarddirs.h>
00040 #include <kiconloader.h>
00041 
00042 #include <plasma/svg.h>
00043 #include <plasma/theme.h>
00044 
00045 namespace Plasma
00046 {
00047 
00048 class SignalPlotterPrivate
00049 {
00050 public:
00051     SignalPlotterPrivate()
00052         : svgBackground(0)
00053     { }
00054 
00055     ~SignalPlotterPrivate()
00056     {
00057     }
00058 
00059     void themeChanged()
00060     {
00061         Plasma::Theme *theme = Plasma::Theme::defaultTheme();
00062         backgroundColor = theme->color(Theme::BackgroundColor);
00063         fontColor = theme->color(Theme::TextColor);
00064         borderColor = fontColor;
00065         verticalLinesColor = fontColor;
00066         verticalLinesColor.setAlphaF(0.4);
00067         horizontalLinesColor = verticalLinesColor;
00068     }
00069 
00070     int precision;
00071     uint samples;
00072     uint bezierCurveOffset;
00073 
00074     double scaledBy;
00075     double verticalMin;
00076     double verticalMax;
00077     double niceVertMin;
00078     double niceVertMax;
00079     double niceVertRange;
00080 
00081     uint verticalLinesOffset;
00082     uint verticalLinesDistance;
00083     QColor verticalLinesColor;
00084 
00085     bool showHorizontalLines;
00086     uint horizontalScale;
00087     uint horizontalLinesCount;
00088     QColor horizontalLinesColor;
00089 
00090     Svg *svgBackground;
00091     QString svgFilename;
00092 
00093     QColor fontColor;
00094     QColor borderColor;
00095     QColor backgroundColor;
00096     QPixmap backgroundPixmap;
00097 
00098     QFont font;
00099     QString title;
00100     QString unit;
00101 
00102     QList<PlotColor> plotColors;
00103     QList<QList<double> > plotData;
00104 
00105     bool fillPlots : 1;
00106     bool showLabels : 1;
00107     bool showTopBar : 1;
00108     bool stackPlots : 1;
00109     bool useAutoRange : 1;
00110     bool showThinFrame : 1;
00111 
00112     bool showVerticalLines : 1;
00113     bool verticalLinesScroll : 1;
00114 };
00115 
00116 SignalPlotter::SignalPlotter(QGraphicsItem *parent)
00117     : QGraphicsWidget(parent),
00118       d(new SignalPlotterPrivate)
00119 {
00120     d->precision = 0;
00121     d->bezierCurveOffset = 0;
00122     d->samples = 0;
00123     d->verticalMin = d->verticalMax = 0.0;
00124     d->niceVertMin = d->niceVertMax = 0.0;
00125     d->niceVertRange = 0;
00126     d->useAutoRange = true;
00127     d->scaledBy = 1;
00128     d->showThinFrame = true;
00129 
00130     d->showVerticalLines = true;
00131     d->verticalLinesDistance = 30;
00132     d->verticalLinesScroll = true;
00133     d->verticalLinesOffset = 0;
00134     d->horizontalScale = 1;
00135 
00136     d->showHorizontalLines = true;
00137     d->horizontalLinesCount = 5;
00138 
00139     d->showLabels = true;
00140     d->showTopBar = true;
00141     d->stackPlots = true;
00142     d->fillPlots = true;
00143 
00144     // Anything smaller than this does not make sense.
00145     setMinimumSize(QSizeF(KIconLoader::SizeSmall, KIconLoader::SizeSmall));
00146 
00147     setSvgBackground("widgets/plot-background");
00148 
00149     connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), SLOT(themeChanged()));
00150     d->themeChanged();
00151 
00152     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
00153 }
00154 
00155 SignalPlotter::~SignalPlotter()
00156 {
00157     delete d;
00158 }
00159 
00160 QString SignalPlotter::unit() const
00161 {
00162     return d->unit;
00163 }
00164 void SignalPlotter::setUnit(const QString &unit)
00165 {
00166     d->unit= unit;
00167 }
00168 
00169 void SignalPlotter::addPlot(const QColor &color)
00170 {
00171     // When we add a new plot, go back and set the data for this plot to 0 for
00172     // all the other times. This is because it makes it easier for moveSensors.
00173     foreach (QList<double> data, d->plotData) {
00174         data.append(0);
00175     }
00176     PlotColor newColor;
00177     newColor.color = color;
00178     newColor.darkColor = color.dark(150);
00179     d->plotColors.append(newColor);
00180 }
00181 
00182 void SignalPlotter::addSample(const QList<double>& sampleBuf)
00183 {
00184     if (d->samples < 4) {
00185         // It might be possible, under some race conditions, for addSample
00186         // to be called before d->samples is set. This is just to be safe.
00187         kDebug() << "Error - d->samples is only " << d->samples;
00188         updateDataBuffers();
00189         kDebug() << "d->samples is now " << d->samples;
00190         if (d->samples < 4) {
00191             return;
00192         }
00193     }
00194     d->plotData.prepend(sampleBuf);
00195     Q_ASSERT(sampleBuf.count() == d->plotColors.count());
00196     if ((uint)d->plotData.size() > d->samples) {
00197         d->plotData.removeLast(); // we have too many.  Remove the last item
00198         if ((uint)d->plotData.size() > d->samples) {
00199             // If we still have too many, then we have resized the widget.
00200             // Remove one more.  That way we will slowly resize to the new size
00201             d->plotData.removeLast();
00202         }
00203     }
00204 
00205     if (d->bezierCurveOffset >= 2) {
00206         d->bezierCurveOffset = 0;
00207     } else {
00208         d->bezierCurveOffset++;
00209     }
00210 
00211     Q_ASSERT((uint)d->plotData.size() >= d->bezierCurveOffset);
00212 
00213     // If the vertical lines are scrolling, increment the offset
00214     // so they move with the data.
00215     if (d->verticalLinesScroll) {
00216         d->verticalLinesOffset =
00217             (d->verticalLinesOffset + d->horizontalScale) % d->verticalLinesDistance;
00218     }
00219     update();
00220 }
00221 
00222 void SignalPlotter::reorderPlots(const QList<uint>& newOrder)
00223 {
00224     if (newOrder.count() != d->plotColors.count()) {
00225         kDebug() << "neworder has " << newOrder.count()
00226                  << " and plot colors is " << d->plotColors.count();
00227         return;
00228     }
00229     foreach (QList<double> data, d->plotData) {
00230         if (newOrder.count() != data.count()) {
00231             kDebug() << "Serious problem in move sample.  plotdata[i] has "
00232                      << data.count() << " and neworder has " << newOrder.count();
00233         } else {
00234             QList<double> newPlot;
00235             for (int i = 0; i < newOrder.count(); i++) {
00236                 int newIndex = newOrder[i];
00237                 newPlot.append(data.at(newIndex));
00238             }
00239             data = newPlot;
00240         }
00241     }
00242     QList<PlotColor> newPlotColors;
00243     for (int i = 0; i < newOrder.count(); i++) {
00244         int newIndex = newOrder[i];
00245         PlotColor newColor = d->plotColors.at(newIndex);
00246         newPlotColors.append(newColor);
00247     }
00248     d->plotColors = newPlotColors;
00249 }
00250 
00251 void SignalPlotter::setVerticalRange(double min, double max)
00252 {
00253     d->verticalMin = min;
00254     d->verticalMax = max;
00255     calculateNiceRange();
00256 }
00257 
00258 QList<PlotColor> &SignalPlotter::plotColors()
00259 {
00260     return d->plotColors;
00261 }
00262 
00263 void SignalPlotter::removePlot(uint pos)
00264 {
00265     if (pos >= (uint)d->plotColors.size()) {
00266         return;
00267     }
00268     d->plotColors.removeAt(pos);
00269 
00270     foreach (QList<double> data, d->plotData) {
00271         if ((uint)data.size() >= pos) {
00272             data.removeAt(pos);
00273         }
00274     }
00275 }
00276 
00277 void SignalPlotter::scale(qreal delta)
00278 {
00279     if (d->scaledBy == delta) {
00280         return;
00281     }
00282     d->scaledBy = delta;
00283     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00284     calculateNiceRange();
00285 }
00286 
00287 qreal SignalPlotter::scaledBy() const
00288 {
00289     return d->scaledBy;
00290 }
00291 
00292 void SignalPlotter::setTitle(const QString &title)
00293 {
00294     if (d->title == title) {
00295         return;
00296     }
00297     d->title = title;
00298     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00299 }
00300 
00301 QString SignalPlotter::title() const
00302 {
00303     return d->title;
00304 }
00305 
00306 void SignalPlotter::setUseAutoRange(bool value)
00307 {
00308     d->useAutoRange = value;
00309     calculateNiceRange();
00310     // this change will be detected in paint and the image cache regenerated
00311 }
00312 
00313 bool SignalPlotter::useAutoRange() const
00314 {
00315     return d->useAutoRange;
00316 }
00317 
00318 double SignalPlotter::verticalMinValue() const
00319 {
00320     return d->verticalMin;
00321 }
00322 
00323 double SignalPlotter::verticalMaxValue() const
00324 {
00325     return d->verticalMax;
00326 }
00327 
00328 void SignalPlotter::setHorizontalScale(uint scale)
00329 {
00330     if (scale == d->horizontalScale) {
00331         return;
00332     }
00333 
00334     d->horizontalScale = scale;
00335     updateDataBuffers();
00336     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00337 }
00338 
00339 uint SignalPlotter::horizontalScale() const
00340 {
00341     return d->horizontalScale;
00342 }
00343 
00344 void SignalPlotter::setShowVerticalLines(bool value)
00345 {
00346     if (d->showVerticalLines == value) {
00347         return;
00348     }
00349     d->showVerticalLines = value;
00350     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00351 }
00352 
00353 bool SignalPlotter::showVerticalLines() const
00354 {
00355     return d->showVerticalLines;
00356 }
00357 
00358 void SignalPlotter::setVerticalLinesColor(const QColor &color)
00359 {
00360     if (d->verticalLinesColor == color) {
00361         return;
00362     }
00363     d->verticalLinesColor = color;
00364     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00365 }
00366 
00367 QColor SignalPlotter::verticalLinesColor() const
00368 {
00369     return d->verticalLinesColor;
00370 }
00371 
00372 void SignalPlotter::setVerticalLinesDistance(uint distance)
00373 {
00374     if (distance == d->verticalLinesDistance) {
00375         return;
00376     }
00377     d->verticalLinesDistance = distance;
00378     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00379 }
00380 
00381 uint SignalPlotter::verticalLinesDistance() const
00382 {
00383     return d->verticalLinesDistance;
00384 }
00385 
00386 void SignalPlotter::setVerticalLinesScroll(bool value)
00387 {
00388     if (value == d->verticalLinesScroll) {
00389         return;
00390     }
00391     d->verticalLinesScroll = value;
00392     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00393 }
00394 
00395 bool SignalPlotter::verticalLinesScroll() const
00396 {
00397     return d->verticalLinesScroll;
00398 }
00399 
00400 void SignalPlotter::setShowHorizontalLines(bool value)
00401 {
00402     if (value == d->showHorizontalLines) {
00403         return;
00404     }
00405     d->showHorizontalLines = value;
00406     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00407 }
00408 
00409 bool SignalPlotter::showHorizontalLines() const
00410 {
00411     return d->showHorizontalLines;
00412 }
00413 
00414 void SignalPlotter::setFontColor(const QColor &color)
00415 {
00416     d->fontColor = color;
00417 }
00418 
00419 QColor SignalPlotter::fontColor() const
00420 {
00421     return d->fontColor;
00422 }
00423 
00424 void SignalPlotter::setHorizontalLinesColor(const QColor &color)
00425 {
00426     if (color == d->horizontalLinesColor) {
00427         return;
00428     }
00429     d->horizontalLinesColor = color;
00430     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00431 }
00432 
00433 QColor SignalPlotter::horizontalLinesColor() const
00434 {
00435     return d->horizontalLinesColor;
00436 }
00437 
00438 void SignalPlotter::setHorizontalLinesCount(uint count)
00439 {
00440     if (count == d->horizontalLinesCount) {
00441         return;
00442     }
00443     d->horizontalLinesCount = count;
00444     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00445     calculateNiceRange();
00446 }
00447 
00448 uint SignalPlotter::horizontalLinesCount() const
00449 {
00450     return d->horizontalLinesCount;
00451 }
00452 
00453 void SignalPlotter::setShowLabels(bool value)
00454 {
00455     if (value == d->showLabels) {
00456         return;
00457     }
00458     d->showLabels = value;
00459     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00460 }
00461 
00462 bool SignalPlotter::showLabels() const
00463 {
00464     return d->showLabels;
00465 }
00466 
00467 void SignalPlotter::setShowTopBar(bool value)
00468 {
00469     if (d->showTopBar == value) {
00470         return;
00471     }
00472     d->showTopBar = value;
00473     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00474 }
00475 
00476 bool SignalPlotter::showTopBar() const
00477 {
00478     return d->showTopBar;
00479 }
00480 
00481 void SignalPlotter::setFont(const QFont &font)
00482 {
00483     d->font = font;
00484     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00485 }
00486 
00487 QFont SignalPlotter::font() const
00488 {
00489     return d->font;
00490 }
00491 
00492 QString SignalPlotter::svgBackground()
00493 {
00494     return d->svgFilename;
00495 }
00496 
00497 void SignalPlotter::setSvgBackground(const QString &filename)
00498 {
00499     if (d->svgFilename == filename) {
00500         return;
00501     }
00502 
00503     if (!filename.isEmpty() && filename[0] == '/') {
00504         KStandardDirs *kstd = KGlobal::dirs();
00505         d->svgFilename = kstd->findResource("data", "ksysguard/" + filename);
00506     } else {
00507         d->svgFilename = filename;
00508     }
00509 
00510     delete d->svgBackground;
00511     d->svgBackground = 0;
00512     if (!d->svgFilename.isEmpty()) {
00513         d->svgBackground = new Svg(this);
00514         d->svgBackground->setImagePath(d->svgFilename);
00515     }
00516 
00517 }
00518 
00519 void SignalPlotter::setBackgroundColor(const QColor &color)
00520 {
00521     if (color == d->backgroundColor) {
00522         return;
00523     }
00524     d->backgroundColor = color;
00525     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00526 }
00527 
00528 QColor SignalPlotter::backgroundColor() const
00529 {
00530     return d->backgroundColor;
00531 }
00532 
00533 void SignalPlotter::setThinFrame(bool set)
00534 {
00535     if (d->showThinFrame == set) {
00536         return;
00537     }
00538     d->showThinFrame = set;
00539     d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
00540 }
00541 
00542 bool SignalPlotter::thinFrame() const
00543 {
00544     return d->showThinFrame;
00545 }
00546 
00547 void SignalPlotter::setStackPlots(bool stack)
00548 {
00549     d->stackPlots = stack;
00550     d->fillPlots = stack;
00551 }
00552 
00553 bool SignalPlotter::stackPlots() const
00554 {
00555     return d->stackPlots;
00556 }
00557 
00558 void SignalPlotter::updateDataBuffers()
00559 {
00560     // This is called when the widget has resized
00561     //
00562     // Determine new number of samples first.
00563     //  +0.5 to ensure rounding up
00564     //  +4 for extra data points so there is
00565     //     1) no wasted space and
00566     //     2) no loss of precision when drawing the first data point.
00567     d->samples = static_cast<uint>(((size().width() - 2) /
00568                                       d->horizontalScale) + 4.5);
00569 }
00570 
00571 QPixmap SignalPlotter::getSnapshotImage(uint w, uint height)
00572 {
00573     uint horizontalStep = (uint)((1.0 * w / size().width()) + 0.5); // get the closest integer horizontal step
00574     uint newWidth = (uint) (horizontalStep * size().width());
00575     QPixmap image = QPixmap(newWidth, height);
00576     QPainter p(&image);
00577     drawWidget(&p, newWidth, height, newWidth);
00578     p.end();
00579     return image;
00580 }
00581 
00582 void SignalPlotter::setGeometry(const QRectF &geometry)
00583 {
00584     // First update our size, then update the data buffers accordingly.
00585     QGraphicsWidget::setGeometry(geometry);
00586     updateDataBuffers();
00587 }
00588 
00589 void SignalPlotter::paint(QPainter *painter,
00590                           const QStyleOptionGraphicsItem *option, QWidget *widget)
00591 {
00592     Q_UNUSED(option);
00593     Q_UNUSED(widget);
00594 
00595     uint w = (uint) size().width();
00596     uint h = (uint) size().height();
00597 
00598     // Do not do repaints when the widget is not yet setup properly.
00599     if (w <= 2) {
00600         return;
00601     }
00602 
00603     drawWidget(painter, w, h, d->horizontalScale);
00604 }
00605 
00606 void SignalPlotter::drawWidget(QPainter *p, uint w, uint height, int horizontalScale)
00607 {
00608     uint h = height; // h will become the height of just the bit we draw the plots in
00609     p->setFont(d->font);
00610 
00611     uint fontheight = p->fontMetrics().height();
00612     if (d->verticalMin < d->niceVertMin ||
00613         d->verticalMax > d->niceVertMax ||
00614         d->verticalMax < (d->niceVertRange * 0.75 + d->niceVertMin) ||
00615         d->niceVertRange == 0) {
00616         calculateNiceRange();
00617     }
00618     QPen pen;
00619     pen.setWidth(1);
00620     pen.setCapStyle(Qt::RoundCap);
00621     p->setPen(pen);
00622 
00623     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
00624     h-= top;
00625 
00626     // Check if there's enough room to actually show a top bar.
00627     // Must be enough room for a bar at the top, plus horizontal
00628     // lines each of a size with room for a scale.
00629     bool showTopBar = d->showTopBar &&  h > (fontheight/*top bar size*/ +5/*smallest reasonable size for a graph*/);
00630     if (showTopBar) {
00631         top += fontheight; // The top bar has the same height as fontheight. Thus the top of the graph is at fontheight
00632         h -= fontheight;
00633     }
00634     if (d->backgroundPixmap.isNull() ||
00635         (uint)d->backgroundPixmap.size().height() != height ||
00636         (uint)d->backgroundPixmap.size().width() != w) {
00637         // recreate on resize etc
00638         d->backgroundPixmap = QPixmap(w, height);
00639         d->backgroundPixmap.fill(Qt::transparent);
00640         QPainter pCache(&d->backgroundPixmap);
00641         pCache.setRenderHint(QPainter::Antialiasing, false);
00642         pCache.setFont(d->font);
00643 
00644         drawBackground(&pCache, w, height);
00645 
00646         if (d->showThinFrame) {
00647             drawThinFrame(&pCache, w, height);
00648             // We have a 'frame' in the bottom and right - so subtract them from the view
00649             h--;
00650             w--;
00651             pCache.setClipRect(0, 0, w, height-1);
00652         }
00653 
00654         if (showTopBar) {
00655             int separatorX = w / 2;
00656             drawTopBarFrame(&pCache, separatorX, top);
00657         }
00658 
00659         // Draw scope-like grid vertical lines if it doesn't move.
00660         // If it does move, draw it in the dynamic part of the code.
00661         if (!d->verticalLinesScroll && d->showVerticalLines && w > 60) {
00662             drawVerticalLines(&pCache, top, w, h);
00663         }
00664 
00665         if (d->showHorizontalLines) {
00666             drawHorizontalLines(&pCache, top, w, h);
00667         }
00668 
00669     } else {
00670         if (d->showThinFrame) {
00671             // We have a 'frame' in the bottom and right - so subtract them from the view
00672             h--;
00673             w--;
00674         }
00675     }
00676     p->drawPixmap(0, 0, d->backgroundPixmap);
00677     p->setRenderHint(QPainter::Antialiasing, true);
00678 
00679     if (showTopBar) {
00680         int separatorX = w / 2;
00681         int topBarWidth = w - separatorX -2;
00682         drawTopBarContents(p, separatorX, topBarWidth, top -1);
00683     }
00684 
00685     p->setClipRect(0, top, w, h);
00686     // Draw scope-like grid vertical lines
00687     if (d->verticalLinesScroll && d->showVerticalLines && w > 60) {
00688         drawVerticalLines(p, top, w, h);
00689     }
00690 
00691     drawPlots(p, top, w, h, horizontalScale);
00692 
00693     if (d->showLabels && w > 60 && h > (fontheight + 1)) {
00694         // if there's room to draw the labels, then draw them!
00695         drawAxisText(p, top, h);
00696     }
00697 }
00698 
00699 void SignalPlotter::drawBackground(QPainter *p, int w, int h)
00700 {
00701     if (d->svgBackground) {
00702         d->svgBackground->resize(w, h);
00703         d->svgBackground->paint(p, 0, 0);
00704     } else {
00705         p->fillRect(0, 0, w, h, d->backgroundColor);
00706     }
00707 }
00708 
00709 void SignalPlotter::drawThinFrame(QPainter *p, int w, int h)
00710 {
00711     // Draw a line along the bottom and the right side of the
00712     // widget to create a 3D like look.
00713     p->setPen(d->borderColor);
00714     p->drawLine(0, h - 1, w - 1, h - 1);
00715     p->drawLine(w - 1, 0, w - 1, h - 1);
00716 }
00717 
00718 void SignalPlotter::calculateNiceRange()
00719 {
00720     d->niceVertRange = d->verticalMax - d->verticalMin;
00721     // If the range is too small we will force it to 1.0 since it
00722     // looks a lot nicer.
00723     if (d->niceVertRange < 0.000001) {
00724         d->niceVertRange = 1.0;
00725     }
00726 
00727     d->niceVertMin = d->verticalMin;
00728     if (d->verticalMin != 0.0) {
00729         double dim = pow(10, floor(log10(fabs(d->verticalMin)))) / 2;
00730         if (d->verticalMin < 0.0) {
00731             d->niceVertMin = dim * floor(d->verticalMin / dim);
00732         } else {
00733             d->niceVertMin = dim * ceil(d->verticalMin / dim);
00734         }
00735         d->niceVertRange = d->verticalMax - d->niceVertMin;
00736         if (d->niceVertRange < 0.000001) {
00737             d->niceVertRange = 1.0;
00738         }
00739     }
00740     // Massage the range so that the grid shows some nice values.
00741     double step = d->niceVertRange / (d->scaledBy * (d->horizontalLinesCount + 1));
00742     int logdim = (int)floor(log10(step));
00743     double dim = pow((double)10.0, logdim) / 2;
00744     int a = (int)ceil(step / dim);
00745     if (logdim >= 0) {
00746         d->precision = 0;
00747     } else if (a % 2 == 0) {
00748         d->precision = -logdim;
00749     } else {
00750         d->precision = 1 - logdim;
00751     }
00752     d->niceVertRange = d->scaledBy * dim * a * (d->horizontalLinesCount + 1);
00753     d->niceVertMax = d->niceVertMin + d->niceVertRange;
00754 }
00755 
00756 void SignalPlotter::drawTopBarFrame(QPainter *p, int separatorX, int height)
00757 {
00758     // Draw horizontal bar with current sensor values at top of display.
00759     // Remember that it has a height of 'height'. Thus the lowest pixel
00760     // it can draw on is height-1 since we count from 0.
00761     p->setPen(Qt::NoPen);
00762     p->setPen(d->fontColor);
00763     p->drawText(0, 1, separatorX, height, Qt::AlignCenter, d->title);
00764     p->setPen(d->horizontalLinesColor);
00765     p->drawLine(separatorX - 1, 1, separatorX - 1, height - 1);
00766 }
00767 
00768 void SignalPlotter::drawTopBarContents(QPainter *p, int x, int width, int height)
00769 {
00770     // The height is the height of the contents, so this will be
00771     // one pixel less than the height of the topbar
00772     double bias = -d->niceVertMin;
00773     double scaleFac = width / d->niceVertRange;
00774     // The top bar shows the current values of all the plot data.
00775     // This iterates through each different plot and plots the newest data for each.
00776     if (!d->plotData.isEmpty()) {
00777         QList<double> newestData = d->plotData.first();
00778         for (int i = newestData.count()-1; i >= 0; --i) {
00779             double newest_datapoint = newestData.at(i);
00780             int start = x + (int)(bias * scaleFac);
00781             int end = x + (int)((bias += newest_datapoint) * scaleFac);
00782             int start2 = qMin(start, end);
00783             end = qMax(start, end);
00784             start = start2;
00785 
00786             // If the rect is wider than 2 pixels we draw only the last
00787             // pixels with the bright color. The rest is painted with
00788             // a 50% darker color.
00789 
00790             p->setPen(Qt::NoPen);
00791             QLinearGradient  linearGrad(QPointF(start, 1), QPointF(end, 1));
00792             linearGrad.setColorAt(0, d->plotColors[i].darkColor);
00793             linearGrad.setColorAt(1, d->plotColors[i].color);
00794             p->fillRect(start, 1, end - start, height-1, QBrush(linearGrad));
00795         }
00796     }
00797 }
00798 
00799 void SignalPlotter::drawVerticalLines(QPainter *p, int top, int w, int h)
00800 {
00801     p->setPen(d->verticalLinesColor);
00802     for (int x = d->verticalLinesOffset; x < (w - 2); x += d->verticalLinesDistance) {
00803         p->drawLine(w - x, top, w - x, h + top -1);
00804     }
00805 }
00806 
00807 void SignalPlotter::drawPlots(QPainter *p, int top, int w, int h, int horizontalScale)
00808 {
00809     Q_ASSERT(d->niceVertRange != 0);
00810 
00811     if (d->niceVertRange == 0) {
00812         d->niceVertRange = 1;
00813     }
00814     double scaleFac = (h - 1) / d->niceVertRange;
00815 
00816     int xPos = 0;
00817     QList< QList<double> >::Iterator it = d->plotData.begin();
00818 
00819     p->setPen(Qt::NoPen);
00820     // In autoRange mode we determine the range and plot the values in
00821     // one go. This is more efficiently than running through the
00822     // buffers twice but we do react on recently discarded samples as
00823     // well as new samples one plot too late. So the range is not
00824     // correct if the recently discarded samples are larger or smaller
00825     // than the current extreme values. But we can probably live with
00826     // this.
00827 
00828     // These values aren't used directly anywhere.  Instead we call
00829     // calculateNiceRange()  which massages these values into a nicer
00830     // values.  Rounding etc.  This means it's safe to change these values
00831     // without affecting any other drawings.
00832     if (d->useAutoRange) {
00833         d->verticalMin = d->verticalMax = 0.0;
00834     }
00835 
00836     // d->bezierCurveOffset is how many points we have at the start.
00837     // All the bezier curves are in groups of 3, with the first of the
00838     // next group being the last point of the previous group
00839 
00840     // Example, when d->bezierCurveOffset == 0, and we have data, then just
00841     // plot a normal bezier curve. (we will have at least 3 points in this case)
00842     // When d->bezierCurveOffset == 1, then we want a bezier curve that uses
00843     // the first data point and the second data point.  Then the next group
00844     // starts from the second data point.
00845     //
00846     // When d->bezierCurveOffset == 2, then we want a bezier curve that
00847     // uses the first, second and third data.
00848     for (uint i = 0; it != d->plotData.end() && i < d->samples; ++i) {
00849         QPen pen;
00850         pen.setWidth(1);
00851         pen.setCapStyle(Qt::FlatCap);
00852 
00853         // We will plot 1 bezier curve for every 3 points, with the 4th point
00854         // being the end of one bezier curve and the start of the second.
00855         // This does means the bezier curves will not join nicely, but it
00856         // should be better than nothing.
00857         QList<double> datapoints = *it;
00858         QList<double> prev_datapoints = datapoints;
00859         QList<double> prev_prev_datapoints = datapoints;
00860         QList<double> prev_prev_prev_datapoints = datapoints;
00861 
00862         if (i == 0 && d->bezierCurveOffset > 0) {
00863             // We are plotting an incomplete bezier curve - we don't have
00864             // all the data we want. Try to cope.
00865             xPos += horizontalScale * d->bezierCurveOffset;
00866             if (d->bezierCurveOffset == 1) {
00867                 prev_datapoints = *it;
00868                 ++it; // Now we are on the first element of the next group, if it exists
00869                 if (it != d->plotData.end()) {
00870                     prev_prev_prev_datapoints = prev_prev_datapoints = *it;
00871                 } else {
00872                     prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints;
00873                 }
00874             } else {
00875                 // d->bezierCurveOffset must be 2 now
00876                 prev_datapoints = *it;
00877                 Q_ASSERT(it != d->plotData.end());
00878                 ++it;
00879                 prev_prev_datapoints = *it;
00880                 Q_ASSERT(it != d->plotData.end());
00881                 ++it; // Now we are on the first element of the next group, if it exists
00882                 if (it != d->plotData.end()) {
00883                     prev_prev_prev_datapoints = *it;
00884                 } else {
00885                     prev_prev_prev_datapoints = prev_prev_datapoints;
00886                 }
00887             }
00888         } else {
00889             // We have a group of 3 points at least.  That's 1 start point and 2 control points.
00890             xPos += horizontalScale * 3;
00891             it++;
00892             if (it != d->plotData.end()) {
00893                 prev_datapoints = *it;
00894                 it++;
00895                 if (it != d->plotData.end()) {
00896                     prev_prev_datapoints = *it;
00897                     it++;  // We are now on the next set of data points
00898                     if (it != d->plotData.end()) {
00899                         // We have this datapoint, so use it for our finish point
00900                         prev_prev_prev_datapoints = *it;
00901                     } else {
00902                         // We don't have the next set, so use our last control
00903                         // point as our finish point
00904                         prev_prev_prev_datapoints = prev_prev_datapoints;
00905                     }
00906                 } else {
00907                     prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints;
00908                 }
00909             } else {
00910                 prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints = datapoints;
00911             }
00912         }
00913 
00914         float x0 = w - xPos + 3.0 * horizontalScale;
00915         float x1 = w - xPos + 2.0 * horizontalScale;
00916         float x2 = w - xPos + 1.0 * horizontalScale;
00917         float x3 = w - xPos;
00918         float y0 = h - 1 + top;
00919         float y1 = y0;
00920         float y2 = y0;
00921         float y3 = y0;
00922 
00923         int offset = 0; // Our line is 2 pixels thick.  This means that when we draw the area, we need to offset
00924         double max_y = 0;
00925         double min_y = 0;
00926         for (int j = qMin(datapoints.size(), d->plotColors.size()) - 1; j >=0; --j) {
00927             if (d->useAutoRange) {
00928                 // If we use autorange, then we need to prepare the min and max values for _next_ time we paint.
00929                 // If we are stacking the plots, then we need to add the maximums together.
00930                 double current_maxvalue =
00931                     qMax(datapoints[j],
00932                          qMax(prev_datapoints[j],
00933                               qMax(prev_prev_datapoints[j],
00934                                    prev_prev_prev_datapoints[j])));
00935                 double current_minvalue =
00936                     qMin(datapoints[j],
00937                          qMin(prev_datapoints[j],
00938                               qMin(prev_prev_datapoints[j],
00939                                    prev_prev_prev_datapoints[j])));
00940                 d->verticalMax = qMax(d->verticalMax, current_maxvalue);
00941                 d->verticalMin = qMin(d->verticalMin, current_maxvalue);
00942                 if (d->stackPlots) {
00943                     max_y += current_maxvalue;
00944                     min_y += current_minvalue;
00945                 }
00946             }
00947 
00948             // Draw polygon only if enough data points are available.
00949             if (j < prev_prev_prev_datapoints.count() &&
00950                  j < prev_prev_datapoints.count() &&
00951                  j < prev_datapoints.count()) {
00952 
00953                 // The height of the whole widget is h+top->  The height of
00954                 // the area we are plotting in is just h.
00955                 // The y coordinate system starts from the top, so at the
00956                 // bottom the y coordinate is h+top.
00957                 // So to draw a point at value y', we need to put this at  h+top-y'
00958                 float delta_y0;
00959                 delta_y0 = (datapoints[j] - d->niceVertMin) * scaleFac;
00960 
00961                 float delta_y1;
00962                 delta_y1 = (prev_datapoints[j] - d->niceVertMin) * scaleFac;
00963 
00964                 float delta_y2;
00965                 delta_y2 = (prev_prev_datapoints[j] - d->niceVertMin) * scaleFac;
00966 
00967                 float delta_y3;
00968                 delta_y3 = (prev_prev_prev_datapoints[j] - d->niceVertMin) * scaleFac;
00969 
00970                 QPainterPath path;
00971                 if (d->stackPlots && offset) {
00972                     // we don't want the lines to overdraw each other.
00973                     // This isn't a great solution though :(
00974                     if (delta_y0 < 3) {
00975                         delta_y0=3;
00976                     }
00977                     if (delta_y1 < 3) {
00978                         delta_y1=3;
00979                     }
00980                     if (delta_y2 < 3) {
00981                         delta_y2=3;
00982                     }
00983                     if (delta_y3 < 3) {
00984                         delta_y3=3;
00985                     }
00986                 }
00987                 path.moveTo(x0, y0 - delta_y0);
00988                 path.cubicTo(x1, y1 - delta_y1, x2, y2 - delta_y2, x3, y3 - delta_y3);
00989 
00990                 if (d->fillPlots) {
00991                     QPainterPath path2(path);
00992                     QLinearGradient myGradient(0,(h - 1 + top), 0, (h - 1 + top) / 5);
00993                     Q_ASSERT(d->plotColors.size() >= j);
00994                     QColor c0(d->plotColors[j].darkColor);
00995                     QColor c1(d->plotColors[j].color);
00996                     c0.setAlpha(150);
00997                     c1.setAlpha(150);
00998                     myGradient.setColorAt(0, c0);
00999                     myGradient.setColorAt(1, c1);
01000 
01001                     path2.lineTo(x3, y3 - offset);
01002                     if (d->stackPlots) {
01003                         // offset is set to 1 after the first plot is drawn,
01004                         // so we don't trample on top of the 2pt thick line
01005                         path2.cubicTo(x2, y2 - offset, x1, y1 - offset, x0, y0 - offset);
01006                     } else {
01007                         path2.lineTo(x0, y0 - 1);
01008                     }
01009                     p->setBrush(myGradient);
01010                     p->setPen(Qt::NoPen);
01011                     p->drawPath(path2);
01012                 }
01013                 p->setBrush(Qt::NoBrush);
01014                 Q_ASSERT(d->plotColors.size() >= j);
01015                 pen.setColor(d->plotColors[j].color);
01016                 p->setPen(pen);
01017                 p->drawPath(path);
01018 
01019                 if (d->stackPlots) {
01020                     // We can draw the plots stacked on top of each other.
01021                     // This means that say plot 0 has the value 2 and plot
01022                     // 1 has the value 3, then we plot plot 0 at 2 and plot 1 at 2+3 = 5.
01023                     y0 -= delta_y0;
01024                     y1 -= delta_y1;
01025                     y2 -= delta_y2;
01026                     y3 -= delta_y3;
01027                     offset = 1;  // see the comment further up for int offset;
01028                 }
01029             }
01030             if (d->useAutoRange && d->stackPlots) {
01031                 d->verticalMax = qMax(max_y, d->verticalMax);
01032                 d->verticalMin = qMin(min_y, d->verticalMin);
01033             }
01034         }
01035     }
01036 }
01037 
01038 void SignalPlotter::drawAxisText(QPainter *p, int top, int h)
01039 {
01040     // Draw horizontal lines and values. Lines are always drawn.
01041     // Values are only draw when width is greater than 60.
01042     QString val;
01043 
01044     // top = 0 or font.height depending on whether there's a topbar or not
01045     // h = graphing area.height - i.e. the actual space we have to draw inside
01046     // Note we are drawing from 0,0 as the top left corner. So we have to add on top
01047     // to get to the top of where we are drawing so top+h is the height of the widget.
01048     p->setPen(d->fontColor);
01049     double stepsize = d->niceVertRange / (d->scaledBy * (d->horizontalLinesCount + 1));
01050     int step =
01051         (int)ceil((d->horizontalLinesCount+1) *
01052                   (p->fontMetrics().height() + p->fontMetrics().leading() / 2.0) / h);
01053     if (step == 0) {
01054         step = 1;
01055     }
01056     for (int y = d->horizontalLinesCount + 1; y >= 1; y-= step) {
01057         int y_coord =
01058             top + (y * (h - 1)) / (d->horizontalLinesCount + 1); // Make sure it's y*h first to avoid rounding bugs
01059         if (y_coord - p->fontMetrics().ascent() < top) {
01060             // at most, only allow 4 pixels of the text to be covered up
01061             // by the top bar. Otherwise just don't bother to draw it
01062             continue;
01063         }
01064         double value;
01065         if ((uint)y == d->horizontalLinesCount + 1) {
01066             value = d->niceVertMin; // sometimes using the formulas gives us a value very slightly off
01067         } else {
01068             value = d->niceVertMax / d->scaledBy - y * stepsize;
01069         }
01070 
01071         QString number = KGlobal::locale()->formatNumber(value, d->precision);
01072         val = QString("%1 %2").arg(number, d->unit);
01073         p->drawText(6, y_coord - 3, val);
01074     }
01075 }
01076 
01077 void SignalPlotter::drawHorizontalLines(QPainter *p, int top, int w, int h)
01078 {
01079     p->setPen(d->horizontalLinesColor);
01080     for (uint y = 0; y <= d->horizontalLinesCount + 1; y++) {
01081         // 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
01082         int y_coord =  top + (y * (h - 1)) / (d->horizontalLinesCount + 1);  // Make sure it's y*h first to avoid rounding bugs
01083         p->drawLine(0, y_coord, w - 2, y_coord);
01084     }
01085 }
01086 
01087 double SignalPlotter::lastValue(uint i) const
01088 {
01089     if (d->plotData.isEmpty() || d->plotData.first().size() <= (int)i) {
01090         return 0;
01091     }
01092     return d->plotData.first()[i];
01093 }
01094 
01095 QString SignalPlotter::lastValueAsString(uint i) const
01096 {
01097     if (d->plotData.isEmpty()) {
01098         return QString();
01099     }
01100     double value = d->plotData.first()[i] / d->scaledBy; // retrieve the newest value for this plot then scale it correct
01101     QString number = KGlobal::locale()->formatNumber(value, (value >= 100)?0:2);
01102     return QString("%1 %2").arg(number, d->unit);
01103 }
01104 
01105 } // Plasma namespace
01106 
01107 #include "signalplotter.moc"
01108 

Plasma

Skip menu "Plasma"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal