Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / libs / plasma / widgets / signalplotter.cpp
blobe8085812b6c986e5d1ab0ce7394fa4780409933e
1 /*
2 * KSysGuard, the KDE System Guard
4 * Copyright 1999 - 2002 Chris Schlaeger <cs@kde.org>
5 * Copyright 2006 John Tapsell <tapsell@kde.org>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Library General Public License as
9 * published by the Free Software Foundation; either version 2, or
10 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 #include "signalplotter.h"
25 #include <math.h>
26 #include <string.h>
28 #include <QList>
29 #include <QPalette>
30 #include <QtGui/QPainter>
31 #include <QtGui/QPixmap>
32 #include <QtGui/QPainterPath>
33 #include <QtGui/QPolygon>
35 #include <KDebug>
36 #include <KGlobal>
37 #include <KLocale>
38 #include <KApplication>
39 #include <KStandardDirs>
41 #include <plasma/svg.h>
43 namespace Plasma
46 class SignalPlotter::Private
48 public:
49 Private()
50 : svgBackground(0)
51 { }
52 ~Private() { }
54 int precision;
55 uint samples;
56 uint bezierCurveOffset;
58 double scaledBy;
59 double verticalMin;
60 double verticalMax;
61 double niceVertMin;
62 double niceVertMax;
63 double niceVertRange;
65 bool fillPlots;
66 bool showLabels;
67 bool showTopBar;
68 bool stackPlots;
69 bool useAutoRange;
70 bool showThinFrame;
72 bool showVerticalLines;
73 bool verticalLinesScroll;
74 uint verticalLinesOffset;
75 uint verticalLinesDistance;
76 QColor verticalLinesColor;
78 bool showHorizontalLines;
79 uint horizontalScale;
80 uint horizontalLinesCount;
81 QColor horizontalLinesColor;
83 Svg *svgBackground;
84 QString svgFilename;
86 QColor fontColor;
87 QColor backgroundColor;
88 QPixmap backgroundPixmap;
90 QFont font;
91 QString title;
92 QString unit;
94 QList<PlotColor> plotColors;
95 QList<QList<double> > plotData;
98 SignalPlotter::SignalPlotter(Widget *parent)
99 : Widget(parent),
100 d(new Private)
102 d->precision = 0;
103 d->bezierCurveOffset = 0;
104 d->samples = 0;
105 d->verticalMin = d->verticalMax = 0.0;
106 d->niceVertMin = d->niceVertMax = 0.0;
107 d->niceVertRange = 0;
108 d->useAutoRange = true;
109 d->scaledBy = 1;
110 d->showThinFrame = true;
112 // Anything smaller than this does not make sense.
113 setMinimumSize(QSizeF(16, 16));
115 d->showVerticalLines = true;
116 d->verticalLinesColor = QColor("black");
117 d->verticalLinesDistance = 30;
118 d->verticalLinesScroll = true;
119 d->verticalLinesOffset = 0;
120 d->horizontalScale = 1;
122 d->showHorizontalLines = true;
123 d->horizontalLinesColor = QColor("black");
124 d->horizontalLinesCount = 5;
126 d->showLabels = true;
127 d->showTopBar = true;
128 d->stackPlots = true;
129 d->fillPlots = true;
131 d->svgBackground = 0;
132 d->backgroundColor = QColor(0,0,0);
134 setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding,QSizePolicy::DefaultType);
137 SignalPlotter::~SignalPlotter()
139 delete d;
142 QString SignalPlotter::unit() const
144 return d->unit;
146 void SignalPlotter::setUnit(const QString &unit)
148 d->unit= unit;
151 void SignalPlotter::addPlot(const QColor &color)
153 // When we add a new plot, go back and set the data for this plot to 0 for
154 // all the other times. This is because it makes it easier for moveSensors.
155 foreach (QList<double> data, d->plotData) {
156 data.append(0);
158 PlotColor newColor;
159 newColor.color = color;
160 newColor.darkColor = color.dark(150);
161 d->plotColors.append(newColor);
164 void SignalPlotter::addSample(const QList<double>& sampleBuf)
166 if (d->samples < 4) {
167 // It might be possible, under some race conditions, for addSample
168 // to be called before d->samples is set. This is just to be safe.
169 kDebug(1215) << "Error - d->samples is only " << d->samples << endl;
170 updateDataBuffers();
171 kDebug(1215) << "d->samples is now " << d->samples << endl;
172 if (d->samples < 4)
173 return;
175 d->plotData.prepend(sampleBuf);
176 Q_ASSERT(sampleBuf.count() == d->plotColors.count());
177 if ((uint)d->plotData.size() > d->samples) {
178 d->plotData.removeLast(); // we have too many. Remove the last item
179 if ((uint)d->plotData.size() > d->samples)
180 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
183 if (d->bezierCurveOffset >= 2) d->bezierCurveOffset = 0;
184 else d->bezierCurveOffset++;
186 Q_ASSERT((uint)d->plotData.size() >= d->bezierCurveOffset);
188 // If the vertical lines are scrolling, increment the offset
189 // so they move with the data.
190 if (d->verticalLinesScroll) {
191 d->verticalLinesOffset = (d->verticalLinesOffset + d->horizontalScale)
192 % d->verticalLinesDistance;
194 update();
197 void SignalPlotter::reorderPlots(const QList<uint>& newOrder)
199 if (newOrder.count() != d->plotColors.count()) {
200 kDebug(1215) << "neworder has " << newOrder.count() << " and plot colors is " << d->plotColors.count() << endl;
201 return;
203 foreach (QList<double> data, d->plotData) {
204 if (newOrder.count() != data.count()) {
205 kDebug(1215) << "Serious problem in move sample. plotdata[i] has " << data.count() << " and neworder has " << newOrder.count() << endl;
206 } else {
207 QList<double> newPlot;
208 for (int i = 0; i < newOrder.count(); i++) {
209 int newIndex = newOrder[i];
210 newPlot.append(data.at(newIndex));
212 data = newPlot;
215 QList<PlotColor> newPlotColors;
216 for (int i = 0; i < newOrder.count(); i++) {
217 int newIndex = newOrder[i];
218 PlotColor newColor = d->plotColors.at(newIndex);
219 newPlotColors.append(newColor);
221 d->plotColors = newPlotColors;
224 void SignalPlotter::setVerticalRange(double min, double max)
226 d->verticalMin = min;
227 d->verticalMax = max;
228 calculateNiceRange();
231 QList<PlotColor> &SignalPlotter::plotColors()
233 return d->plotColors;
236 void SignalPlotter::removePlot(uint pos)
238 if (pos >= (uint)d->plotColors.size()) return;
239 d->plotColors.removeAt(pos);
241 foreach (QList<double> data, d->plotData) {
242 if ((uint)data.size() >= pos)
243 data.removeAt(pos);
247 void SignalPlotter::scale(qreal delta)
249 if (d->scaledBy == delta) return;
250 d->scaledBy = delta;
251 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
252 calculateNiceRange();
255 qreal SignalPlotter::scaledBy() const
257 return d->scaledBy;
260 void SignalPlotter::setTitle(const QString &title)
262 if (d->title == title) return;
263 d->title = title;
264 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
267 QString SignalPlotter::title() const
269 return d->title;
272 void SignalPlotter::setUseAutoRange(bool value)
274 d->useAutoRange = value;
275 calculateNiceRange();
276 // this change will be detected in paint and the image cache regenerated
279 bool SignalPlotter::useAutoRange() const
281 return d->useAutoRange;
284 double SignalPlotter::verticalMinValue() const
286 return d->verticalMin;
289 double SignalPlotter::verticalMaxValue() const
291 return d->verticalMax;
294 void SignalPlotter::setHorizontalScale(uint scale)
296 if (scale == d->horizontalScale)
297 return;
299 d->horizontalScale = scale;
300 updateDataBuffers();
301 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
304 uint SignalPlotter::horizontalScale() const
306 return d->horizontalScale;
309 void SignalPlotter::setShowVerticalLines(bool value)
311 if (d->showVerticalLines == value) return;
312 d->showVerticalLines = value;
313 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
316 bool SignalPlotter::showVerticalLines() const
318 return d->showVerticalLines;
321 void SignalPlotter::setVerticalLinesColor(const QColor &color)
323 if (d->verticalLinesColor == color) return;
324 d->verticalLinesColor = color;
325 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
328 QColor SignalPlotter::verticalLinesColor() const
330 return d->verticalLinesColor;
333 void SignalPlotter::setVerticalLinesDistance(uint distance)
335 if (distance == d->verticalLinesDistance) return;
336 d->verticalLinesDistance = distance;
337 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
340 uint SignalPlotter::verticalLinesDistance() const
342 return d->verticalLinesDistance;
345 void SignalPlotter::setVerticalLinesScroll(bool value)
347 if (value == d->verticalLinesScroll) return;
348 d->verticalLinesScroll = value;
349 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
352 bool SignalPlotter::verticalLinesScroll() const
354 return d->verticalLinesScroll;
357 void SignalPlotter::setShowHorizontalLines(bool value)
359 if (value == d->showHorizontalLines) return;
360 d->showHorizontalLines = value;
361 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
364 bool SignalPlotter::showHorizontalLines() const
366 return d->showHorizontalLines;
369 void SignalPlotter::setFontColor(const QColor &color)
371 d->fontColor = color;
374 QColor SignalPlotter::fontColor() const
376 return d->fontColor;
379 void SignalPlotter::setHorizontalLinesColor(const QColor &color)
381 if (color == d->horizontalLinesColor) return;
382 d->horizontalLinesColor = color;
383 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
386 QColor SignalPlotter::horizontalLinesColor() const
388 return d->horizontalLinesColor;
391 void SignalPlotter::setHorizontalLinesCount(uint count)
393 if (count == d->horizontalLinesCount) return;
394 d->horizontalLinesCount = count;
395 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
396 calculateNiceRange();
399 uint SignalPlotter::horizontalLinesCount() const
401 return d->horizontalLinesCount;
404 void SignalPlotter::setShowLabels(bool value)
406 if (value == d->showLabels) return;
407 d->showLabels = value;
408 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
411 bool SignalPlotter::showLabels() const
413 return d->showLabels;
416 void SignalPlotter::setShowTopBar(bool value)
418 if (d->showTopBar == value) return;
419 d->showTopBar = value;
420 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
423 bool SignalPlotter::showTopBar() const
425 return d->showTopBar;
428 void SignalPlotter::setFont(const QFont &font)
430 d->font = font;
431 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
434 QFont SignalPlotter::font() const
436 return d->font;
439 QString SignalPlotter::svgBackground()
441 return d->svgFilename;
444 void SignalPlotter::setSvgBackground(const QString &filename)
446 if (d->svgFilename == filename) return;
448 if (!filename.isEmpty() && filename[0] == '/') {
449 KStandardDirs* kstd = KGlobal::dirs();
450 d->svgFilename = kstd->findResource("data", "ksysguard/" + filename);
451 } else {
452 d->svgFilename = filename;
455 if (!d->svgFilename.isEmpty())
457 if (d->svgBackground) delete d->svgBackground;
458 d->svgBackground = new Svg(d->svgFilename);
463 void SignalPlotter::setBackgroundColor(const QColor &color)
465 if (color == d->backgroundColor) return;
466 d->backgroundColor = color;
467 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
470 QColor SignalPlotter::backgroundColor() const
472 return d->backgroundColor;
475 void SignalPlotter::setThinFrame(bool set)
477 if (d->showThinFrame == set) return;
478 d->showThinFrame = set;
479 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
482 void SignalPlotter::setStackPlots(bool stack)
484 d->stackPlots = stack;
485 d->fillPlots = stack;
488 bool SignalPlotter::stackPlots() const
490 return d->stackPlots;
493 void SignalPlotter::updateDataBuffers()
495 // This is called when the widget has resized
497 // Determine new number of samples first.
498 // +0.5 to ensure rounding up
499 // +4 for extra data points so there is
500 // 1) no wasted space and
501 // 2) no loss of precision when drawing the first data point.
502 d->samples = static_cast<uint>(((size().width() - 2) /
503 d->horizontalScale) + 4.5);
506 QPixmap SignalPlotter::getSnapshotImage(uint w, uint height)
508 uint horizontalStep = (uint) ((1.0*w/size().width())+0.5); // get the closest integer horizontal step
509 uint newWidth = (uint) (horizontalStep * size().width());
510 QPixmap image = QPixmap(newWidth, height);
511 QPainter p(&image);
512 drawWidget(&p, newWidth, height, newWidth);
513 return image;
516 void SignalPlotter::setGeometry(const QRectF &geometry)
518 // First update our size, then update the data buffers accordingly.
519 Widget::setGeometry(geometry);
520 updateDataBuffers();
523 void SignalPlotter::paintWidget(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
525 Q_UNUSED(option);
526 Q_UNUSED(widget);
528 uint w = (uint) size().width();
529 uint h = (uint) size().height();
531 // Do not do repaints when the widget is not yet setup properly.
532 if (w <= 2)
533 return;
535 drawWidget(painter, w, h, d->horizontalScale);
538 void SignalPlotter::drawWidget(QPainter *p, uint w, uint height, int horizontalScale)
540 uint h = height; // h will become the height of just the bit we draw the plots in
541 p->setFont(d->font);
543 uint fontheight = p->fontMetrics().height();
544 if (d->verticalMin < d->niceVertMin || d->verticalMax > d->niceVertMax || d->verticalMax < (d->niceVertRange*0.75 + d->niceVertMin) || d->niceVertRange == 0)
545 calculateNiceRange();
546 QPen pen;
547 pen.setWidth(1);
548 pen.setCapStyle(Qt::RoundCap);
549 p->setPen(pen);
551 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
552 h-= top;
554 // Check if there's enough room to actually show a top bar.
555 // Must be enough room for a bar at the top, plus horizontal
556 // lines each of a size with room for a scale.
557 bool showTopBar = d->showTopBar && h > (fontheight/*top bar size*/ +5/*smallest reasonable size for a graph*/);
558 if (showTopBar) {
559 top += fontheight; // The top bar has the same height as fontheight. Thus the top of the graph is at fontheight
560 h -= fontheight;
562 if (d->backgroundPixmap.isNull() || (uint)d->backgroundPixmap.size().height() != height || (uint)d->backgroundPixmap.size().width() != w) { // recreate on resize etc
563 d->backgroundPixmap = QPixmap(w, height);
564 QPainter pCache(&d->backgroundPixmap);
565 pCache.setRenderHint(QPainter::Antialiasing, false);
566 pCache.setFont(d->font);
568 drawBackground(&pCache, w, height);
570 if (d->showThinFrame) {
571 drawThinFrame(&pCache, w, height);
572 // We have a 'frame' in the bottom and right - so subtract them from the view
573 h--;
574 w--;
575 pCache.setClipRect(0, 0, w, height-1);
578 if (showTopBar) {
579 int seperatorX = w / 2;
580 drawTopBarFrame(&pCache, seperatorX, top);
583 // Draw scope-like grid vertical lines if it doesn't move.
584 // If it does move, draw it in the dynamic part of the code.
585 if (!d->verticalLinesScroll && d->showVerticalLines && w > 60)
586 drawVerticalLines(&pCache, top, w, h);
588 if (d->showHorizontalLines)
589 drawHorizontalLines(&pCache, top, w, h);
591 } else {
592 if (d->showThinFrame) {
593 // We have a 'frame' in the bottom and right - so subtract them from the view
594 h--;
595 w--;
598 p->drawPixmap(0,0, d->backgroundPixmap);
599 p->setRenderHint(QPainter::Antialiasing, true);
601 if (showTopBar) {
602 int seperatorX = w / 2;
603 int topBarWidth = w - seperatorX -2;
604 drawTopBarContents(p, seperatorX, topBarWidth, top -1);
607 p->setClipRect(0, top, w, h);
608 // Draw scope-like grid vertical lines
609 if (d->verticalLinesScroll && d->showVerticalLines && w > 60)
610 drawVerticalLines(p, top, w, h);
612 drawPlots(p, top, w, h, horizontalScale);
614 if (d->showLabels && w > 60 && h > (fontheight + 1)) // if there's room to draw the labels, then draw them!
615 drawAxisText(p, top, h);
619 void SignalPlotter::drawBackground(QPainter *p, int w, int h)
621 p->fillRect(0,0,w, h, d->backgroundColor);
622 if (d->svgBackground)
624 d->svgBackground->resize(w, h);
625 d->svgBackground->paint(p, 0, 0);
629 void SignalPlotter::drawThinFrame(QPainter *p, int w, int h)
631 // Draw white line along the bottom and the right side of the
632 // widget to create a 3D like look.
633 p->setPen(kapp->palette().color(QPalette::Light));
634 p->drawLine(0, h - 1, w - 1, h - 1);
635 p->drawLine(w - 1, 0, w - 1, h - 1);
638 void SignalPlotter::calculateNiceRange()
640 d->niceVertRange = d->verticalMax - d->verticalMin;
641 // If the range is too small we will force it to 1.0 since it
642 // looks a lot nicer.
643 if (d->niceVertRange < 0.000001)
644 d->niceVertRange = 1.0;
646 d->niceVertMin = d->verticalMin;
647 if (d->verticalMin != 0.0) {
648 double dim = pow(10, floor(log10(fabs(d->verticalMin)))) / 2;
649 if (d->verticalMin < 0.0)
650 d->niceVertMin = dim * floor(d->verticalMin / dim);
651 else
652 d->niceVertMin = dim * ceil(d->verticalMin / dim);
653 d->niceVertRange = d->verticalMax - d->niceVertMin;
654 if (d->niceVertRange < 0.000001)
655 d->niceVertRange = 1.0;
657 // Massage the range so that the grid shows some nice values.
658 double step = d->niceVertRange / (d->scaledBy*(d->horizontalLinesCount+1));
659 int logdim = (int)floor(log10(step));
660 double dim = pow((double)10.0, logdim) / 2;
661 int a = (int)ceil(step / dim);
662 if (logdim >= 0)
663 d->precision = 0;
664 else if (a % 2 == 0){
665 d->precision =-logdim;
666 } else {
667 d->precision = 1-logdim;
669 d->niceVertRange = d->scaledBy*dim * a * (d->horizontalLinesCount+1);
670 d->niceVertMax = d->niceVertMin + d->niceVertRange;
674 void SignalPlotter::drawTopBarFrame(QPainter *p, int seperatorX, int height)
676 // Draw horizontal bar with current sensor values at top of display.
677 // Remember that it has a height of 'height'. Thus the lowest pixel
678 // it can draw on is height-1 since we count from 0.
679 p->setPen(Qt::NoPen);
680 p->setPen(d->fontColor);
681 p->drawText(0, 1, seperatorX, height, Qt::AlignCenter, d->title);
682 p->setPen(d->horizontalLinesColor);
683 p->drawLine(seperatorX - 1, 1, seperatorX - 1, height-1);
686 void SignalPlotter::drawTopBarContents(QPainter *p, int x, int width, int height)
688 // The height is the height of the contents, so this will be one pixel less than the height of the topbar
689 double bias = -d->niceVertMin;
690 double scaleFac = width / d->niceVertRange;
691 // The top bar shows the current values of all the plot data.
692 // This iterates through each different plot and plots the newest data for each.
693 if (!d->plotData.isEmpty()) {
694 QList<double> newestData = d->plotData.first();
695 for (int i = newestData.count()-1; i >= 0; --i) {
696 double newest_datapoint = newestData.at(i);
697 int start = x + (int)(bias * scaleFac);
698 int end = x + (int)((bias += newest_datapoint) * scaleFac);
699 int start2 = qMin(start,end);
700 end = qMax(start,end);
701 start = start2;
703 // If the rect is wider than 2 pixels we draw only the last
704 // pixels with the bright color. The rest is painted with
705 // a 50% darker color.
707 p->setPen(Qt::NoPen);
708 QLinearGradient linearGrad(QPointF(start,1), QPointF(end, 1));
709 linearGrad.setColorAt(0, d->plotColors[i].darkColor);
710 linearGrad.setColorAt(1, d->plotColors[i].color);
711 p->fillRect(start, 1, end - start, height-1, QBrush(linearGrad));
716 void SignalPlotter::drawVerticalLines(QPainter *p, int top, int w, int h)
718 p->setPen(d->verticalLinesColor);
719 for (int x = d->verticalLinesOffset; x < (w - 2); x += d->verticalLinesDistance)
720 p->drawLine(w - x, top, w - x, h + top -1);
723 void SignalPlotter::drawPlots(QPainter *p, int top, int w, int h, int horizontalScale)
725 Q_ASSERT(d->niceVertRange != 0); if (d->niceVertRange == 0) d->niceVertRange = 1;
726 double scaleFac = (h-1) / d->niceVertRange;
728 int xPos = 0;
729 QList< QList<double> >::Iterator it = d->plotData.begin();
731 p->setPen(Qt::NoPen);
732 // In autoRange mode we determine the range and plot the values in
733 // one go. This is more efficiently than running through the
734 // buffers twice but we do react on recently discarded samples as
735 // well as new samples one plot too late. So the range is not
736 // correct if the recently discarded samples are larger or smaller
737 // than the current extreme values. But we can probably live with
738 // this.
740 // These values aren't used directly anywhere. Instead we call
741 // calculateNiceRange() which massages these values into a nicer
742 // values. Rounding etc. This means it's safe to change these values
743 // without affecting any other drawings.
744 if (d->useAutoRange)
745 d->verticalMin = d->verticalMax = 0.0;
747 // d->bezierCurveOffset is how many points we have at the start.
748 // All the bezier curves are in groups of 3, with the first of the next group being the last point
749 // of the previous group
751 // Example, when d->bezierCurveOffset == 0, and we have data, then just plot a normal bezier curve.
752 // (we will have at least 3 points in this case)
753 // When d->bezierCurveOffset == 1, then we want a bezier curve that uses the first data point and
754 // the second data point. Then the next group starts from the second data point.
755 // When d->bezierCurveOffset == 2, then we want a bezier curve that uses the first, second and third data.
756 for (uint i = 0; it != d->plotData.end() && i < d->samples; ++i) {
757 QPen pen;
758 pen.setWidth(1);
759 pen.setCapStyle(Qt::FlatCap);
761 // We will plot 1 bezier curve for every 3 points, with the 4th point being the end
762 // of one bezier curve and the start of the second. This does means the bezier curves
763 // will not join nicely, but it should be better than nothing.
764 QList<double> datapoints = *it;
765 QList<double> prev_datapoints = datapoints;
766 QList<double> prev_prev_datapoints = datapoints;
767 QList<double> prev_prev_prev_datapoints = datapoints;
769 if (i == 0 && d->bezierCurveOffset>0) {
770 // We are plotting an incomplete bezier curve - we don't have all the data we want.
771 // Try to cope.
772 xPos += horizontalScale*d->bezierCurveOffset;
773 if (d->bezierCurveOffset == 1) {
774 prev_datapoints = *it;
775 ++it; // Now we are on the first element of the next group, if it exists
776 if (it != d->plotData.end()) {
777 prev_prev_prev_datapoints = prev_prev_datapoints = *it;
778 } else {
779 prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints;
781 } else {
782 // d->bezierCurveOffset must be 2 now
783 prev_datapoints = *it;
784 Q_ASSERT(it != d->plotData.end());
785 ++it;
786 prev_prev_datapoints = *it;
787 Q_ASSERT(it != d->plotData.end());
788 ++it; // Now we are on the first element of the next group, if it exists
789 if (it != d->plotData.end()) {
790 prev_prev_prev_datapoints = *it;
791 } else {
792 prev_prev_prev_datapoints = prev_prev_datapoints;
795 } else {
796 // We have a group of 3 points at least. That's 1 start point and 2 control points.
797 xPos += horizontalScale*3;
798 it++;
799 if (it != d->plotData.end()) {
800 prev_datapoints = *it;
801 it++;
802 if (it != d->plotData.end()) {
803 prev_prev_datapoints = *it;
804 it++; // We are now on the next set of data points
805 if (it != d->plotData.end()) {
806 // We have this datapoint, so use it for our finish point
807 prev_prev_prev_datapoints = *it;
808 } else {
809 // We don't have the next set, so use our last control point as our finish point
810 prev_prev_prev_datapoints = prev_prev_datapoints;
812 } else {
813 prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints;
815 } else {
816 prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints = datapoints;
820 float x0 = w - xPos + 3.0*horizontalScale;
821 float x1 = w - xPos + 2.0*horizontalScale;
822 float x2 = w - xPos + 1.0*horizontalScale;
823 float x3 = w - xPos;
824 float y0 = h -1 + top;
825 float y1 = y0;
826 float y2 = y0;
827 float y3 = y0;
829 int offset = 0; // Our line is 2 pixels thick. This means that when we draw the area, we need to offset
830 double max_y=0;
831 double min_y=0;
832 for (int j = qMin(datapoints.size(), d->plotColors.size())-1; j >=0 ; --j) {
833 if (d->useAutoRange) {
834 // If we use autorange, then we need to prepare the min and max values for _next_ time we paint.
835 // If we are stacking the plots, then we need to add the maximums together.
836 double current_maxvalue = qMax(datapoints[j], qMax(prev_datapoints[j], qMax(prev_prev_datapoints[j], prev_prev_prev_datapoints[j])));
837 double current_minvalue = qMin(datapoints[j], qMin(prev_datapoints[j], qMin(prev_prev_datapoints[j], prev_prev_prev_datapoints[j])));
838 d->verticalMax = qMax(d->verticalMax, current_maxvalue);
839 d->verticalMin = qMin(d->verticalMin, current_maxvalue);
840 if (d->stackPlots) {
841 max_y += current_maxvalue;
842 min_y += current_minvalue;
846 // Draw polygon only if enough data points are available.
847 if (j < prev_prev_prev_datapoints.count() &&
848 j < prev_prev_datapoints.count() &&
849 j < prev_datapoints.count()) {
851 QPolygon curve(4);
853 // The height of the whole widget is h+top-> The height of the area we are plotting in is just h.
854 // The y coordinate system starts from the top, so at the bottom the y coordinate is h+top.
855 // So to draw a point at value y', we need to put this at h+top-y'
856 float delta_y0;
857 delta_y0 = (datapoints[j] - d->niceVertMin)*scaleFac;
859 float delta_y1;
860 delta_y1 = (prev_datapoints[j] - d->niceVertMin)*scaleFac;
862 float delta_y2;
863 delta_y2 = (prev_prev_datapoints[j] - d->niceVertMin)*scaleFac;
865 float delta_y3;
866 delta_y3 = (prev_prev_prev_datapoints[j] - d->niceVertMin)*scaleFac;
868 QPainterPath path;
869 if (d->stackPlots && offset) {
870 // we don't want the lines to overdraw each other. This isn't a great solution though :(
871 if (delta_y0 < 3) delta_y0=3;
872 if (delta_y1 < 3) delta_y1=3;
873 if (delta_y2 < 3) delta_y2=3;
874 if (delta_y3 < 3) delta_y3=3;
876 path.moveTo(x0,y0-delta_y0);
877 path.cubicTo(x1,y1-delta_y1,x2,y2-delta_y2,x3,y3-delta_y3);
879 if (d->fillPlots) {
880 QPainterPath path2(path);
881 QLinearGradient myGradient(0,(h-1+top),0,(h-1+top)/5);
882 Q_ASSERT(d->plotColors.size() >= j);
883 QColor c0(d->plotColors[j].darkColor);
884 QColor c1(d->plotColors[j].color);
885 c0.setAlpha(150);
886 c1.setAlpha(150);
887 myGradient.setColorAt(0, c0);
888 myGradient.setColorAt(1, c1);
890 path2.lineTo(x3,y3-offset);
891 if (d->stackPlots)
892 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
893 else
894 path2.lineTo(x0,y0-1);
895 p->setBrush(myGradient);
896 p->setPen(Qt::NoPen);
897 p->drawPath(path2);
899 p->setBrush(Qt::NoBrush);
900 Q_ASSERT(d->plotColors.size() >= j);
901 pen.setColor(d->plotColors[j].color);
902 p->setPen(pen);
903 p->drawPath(path);
905 if (d->stackPlots) {
906 // We can draw the plots stacked on top of each other.
907 // This means that say plot 0 has the value 2 and plot
908 // 1 has the value 3, then we plot plot 0 at 2 and plot 1 at 2+3 = 5.
909 y0-=delta_y0;
910 y1-=delta_y1;
911 y2-=delta_y2;
912 y3-=delta_y3;
913 offset = 1; // see the comment further up for int offset;
916 if (d->useAutoRange && d->stackPlots) {
917 d->verticalMax = qMax(max_y, d->verticalMax);
918 d->verticalMin = qMin(min_y, d->verticalMin);
924 void SignalPlotter::drawAxisText(QPainter *p, int top, int h)
926 // Draw horizontal lines and values. Lines are always drawn.
927 // Values are only draw when width is greater than 60.
928 QString val;
930 // top = 0 or font.height depending on whether there's a topbar or not
931 // h = graphing area.height - i.e. the actual space we have to draw inside
932 // Note we are drawing from 0,0 as the top left corner. So we have to add on top
933 // to get to the top of where we are drawing so top+h is the height of the widget.
934 p->setPen(d->fontColor);
935 double stepsize = d->niceVertRange/(d->scaledBy*(d->horizontalLinesCount+1));
936 int step = (int)ceil((d->horizontalLinesCount+1) * (p->fontMetrics().height() + p->fontMetrics().leading()/2.0) / h);
937 if (step ==0) step = 1;
938 for (int y = d->horizontalLinesCount+1; y >= 1; y-= step) {
939 int y_coord = top + (y * (h-1)) / (d->horizontalLinesCount+1); // Make sure it's y*h first to avoid rounding bugs
940 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
941 double value;
942 if ((uint)y == d->horizontalLinesCount+1)
943 value = d->niceVertMin; // sometimes using the formulas gives us a value very slightly off
944 else
945 value = d->niceVertMax/d->scaledBy - y * stepsize;
947 QString number = KGlobal::locale()->formatNumber(value, d->precision);
948 val = QString("%1 %2").arg(number, d->unit);
949 p->drawText(6, y_coord - 3, val);
953 void SignalPlotter::drawHorizontalLines(QPainter *p, int top, int w, int h)
955 p->setPen(d->horizontalLinesColor);
956 for (uint y = 0; y <= d->horizontalLinesCount+1; y++) {
957 // 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
958 int y_coord = top + (y * (h-1)) / (d->horizontalLinesCount+1); // Make sure it's y*h first to avoid rounding bugs
959 p->drawLine(0, y_coord, w - 2, y_coord);
963 double SignalPlotter::lastValue(uint i) const
965 if (d->plotData.isEmpty() || d->plotData.first().size() <= (int) i) return 0;
966 return d->plotData.first()[i];
969 QString SignalPlotter::lastValueAsString(uint i) const
971 if (d->plotData.isEmpty()) return QString();
972 double value = d->plotData.first()[i] / d->scaledBy; // retrieve the newest value for this plot then scale it correct
973 QString number = KGlobal::locale()->formatNumber(value, (value >= 100)?0:2);
974 return QString("%1 %2").arg(number, d->unit);
977 } // Plasma namespace