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"
30 #include <QtGui/QPainter>
31 #include <QtGui/QPixmap>
32 #include <QtGui/QPainterPath>
33 #include <QtGui/QPolygon>
38 #include <KApplication>
39 #include <KStandardDirs>
41 #include <plasma/svg.h>
46 class SignalPlotter::Private
56 uint bezierCurveOffset
;
72 bool showVerticalLines
;
73 bool verticalLinesScroll
;
74 uint verticalLinesOffset
;
75 uint verticalLinesDistance
;
76 QColor verticalLinesColor
;
78 bool showHorizontalLines
;
80 uint horizontalLinesCount
;
81 QColor horizontalLinesColor
;
87 QColor backgroundColor
;
88 QPixmap backgroundPixmap
;
94 QList
<PlotColor
> plotColors
;
95 QList
<QList
<double> > plotData
;
98 SignalPlotter::SignalPlotter(Widget
*parent
)
103 d
->bezierCurveOffset
= 0;
105 d
->verticalMin
= d
->verticalMax
= 0.0;
106 d
->niceVertMin
= d
->niceVertMax
= 0.0;
107 d
->niceVertRange
= 0;
108 d
->useAutoRange
= true;
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;
131 d
->svgBackground
= 0;
132 d
->backgroundColor
= QColor(0,0,0);
134 setSizePolicy(QSizePolicy::Expanding
,QSizePolicy::Expanding
,QSizePolicy::DefaultType
);
137 SignalPlotter::~SignalPlotter()
142 QString
SignalPlotter::unit() const
146 void SignalPlotter::setUnit(const QString
&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
) {
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
;
171 kDebug(1215) << "d->samples is now " << d
->samples
<< endl
;
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
;
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
;
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
;
207 QList
<double> newPlot
;
208 for (int i
= 0; i
< newOrder
.count(); i
++) {
209 int newIndex
= newOrder
[i
];
210 newPlot
.append(data
.at(newIndex
));
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
)
247 void SignalPlotter::scale(qreal delta
)
249 if (d
->scaledBy
== delta
) return;
251 d
->backgroundPixmap
= QPixmap(); // we changed a paint setting, so reset the cache
252 calculateNiceRange();
255 qreal
SignalPlotter::scaledBy() const
260 void SignalPlotter::setTitle(const QString
&title
)
262 if (d
->title
== title
) return;
264 d
->backgroundPixmap
= QPixmap(); // we changed a paint setting, so reset the cache
267 QString
SignalPlotter::title() const
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
)
299 d
->horizontalScale
= scale
;
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
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
)
431 d
->backgroundPixmap
= QPixmap(); // we changed a paint setting, so reset the cache
434 QFont
SignalPlotter::font() const
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
);
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
);
512 drawWidget(&p
, newWidth
, height
, newWidth
);
516 void SignalPlotter::setGeometry(const QRectF
&geometry
)
518 // First update our size, then update the data buffers accordingly.
519 Widget::setGeometry(geometry
);
523 void SignalPlotter::paintWidget(QPainter
*painter
, const QStyleOptionGraphicsItem
*option
, QWidget
*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.
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
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();
548 pen
.setCapStyle(Qt::RoundCap
);
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
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*/);
559 top
+= fontheight
; // The top bar has the same height as fontheight. Thus the top of the graph is at 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
575 pCache
.setClipRect(0, 0, w
, height
-1);
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
);
592 if (d
->showThinFrame
) {
593 // We have a 'frame' in the bottom and right - so subtract them from the view
598 p
->drawPixmap(0,0, d
->backgroundPixmap
);
599 p
->setRenderHint(QPainter::Antialiasing
, true);
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
);
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
);
664 else if (a
% 2 == 0){
665 d
->precision
=-logdim
;
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
);
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
;
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
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.
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
) {
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.
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
;
779 prev_prev_prev_datapoints
= prev_prev_datapoints
= prev_datapoints
;
782 // d->bezierCurveOffset must be 2 now
783 prev_datapoints
= *it
;
784 Q_ASSERT(it
!= d
->plotData
.end());
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
;
792 prev_prev_prev_datapoints
= prev_prev_datapoints
;
796 // We have a group of 3 points at least. That's 1 start point and 2 control points.
797 xPos
+= horizontalScale
*3;
799 if (it
!= d
->plotData
.end()) {
800 prev_datapoints
= *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
;
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
;
813 prev_prev_prev_datapoints
= prev_prev_datapoints
= prev_datapoints
;
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
;
824 float y0
= h
-1 + top
;
829 int offset
= 0; // Our line is 2 pixels thick. This means that when we draw the area, we need to offset
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
);
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()) {
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'
857 delta_y0
= (datapoints
[j
] - d
->niceVertMin
)*scaleFac
;
860 delta_y1
= (prev_datapoints
[j
] - d
->niceVertMin
)*scaleFac
;
863 delta_y2
= (prev_prev_datapoints
[j
] - d
->niceVertMin
)*scaleFac
;
866 delta_y3
= (prev_prev_prev_datapoints
[j
] - d
->niceVertMin
)*scaleFac
;
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
);
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
);
887 myGradient
.setColorAt(0, c0
);
888 myGradient
.setColorAt(1, c1
);
890 path2
.lineTo(x3
,y3
-offset
);
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
894 path2
.lineTo(x0
,y0
-1);
895 p
->setBrush(myGradient
);
896 p
->setPen(Qt::NoPen
);
899 p
->setBrush(Qt::NoBrush
);
900 Q_ASSERT(d
->plotColors
.size() >= j
);
901 pen
.setColor(d
->plotColors
[j
].color
);
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.
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.
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
942 if ((uint
)y
== d
->horizontalLinesCount
+1)
943 value
= d
->niceVertMin
; // sometimes using the formulas gives us a value very slightly off
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