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
KDE 4.6 API Reference