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

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
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
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