• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

libplasma

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

libplasma

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

API Reference

Skip menu "API Reference"
  • KWin
  •   KWin Libraries
  • Libraries
  •   libkworkspace
  •   libplasma
  •   libsolidcontrol
  •   libtaskmanager
  • Plasma
  •   Animators
  •   Applets
  •   Engines
  • Solid Modules
Generated for API Reference by doxygen 1.5.4
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