• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.14.38 API Reference
  • KDE Home
  • Contact Us
 

Plasma

  • plasma
  • widgets
signalplotter.cpp
Go to the documentation of this file.
1/*
2 * KSysGuard, the KDE System Guard
3 *
4 * Copyright 1999 - 2002 Chris Schlaeger <cs@kde.org>
5 * Copyright 2006 John Tapsell <tapsell@kde.org>
6 *
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.
11
12 *
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.
17 *
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.
21 */
22
23#include "signalplotter.h"
24
25#include <math.h>
26#include <string.h>
27
28#include <QApplication>
29#include <QList>
30#include <QPalette>
31#include <QtGui/QPainter>
32#include <QtGui/QPixmap>
33#include <QtGui/QPainterPath>
34#include <QtGui/QPolygon>
35
36#include <kdebug.h>
37#include <kglobal.h>
38#include <klocale.h>
39#include <kstandarddirs.h>
40#include <kiconloader.h>
41
42#include <plasma/svg.h>
43#include <plasma/theme.h>
44
45namespace Plasma
46{
47
48class SignalPlotterPrivate
49{
50public:
51 SignalPlotterPrivate()
52 : svgBackground(0)
53 { }
54
55 ~SignalPlotterPrivate()
56 {
57 }
58
59 void themeChanged()
60 {
61 Plasma::Theme *theme = Plasma::Theme::defaultTheme();
62 backgroundColor = theme->color(Theme::BackgroundColor);
63 fontColor = theme->color(Theme::TextColor);
64 borderColor = fontColor;
65 verticalLinesColor = fontColor;
66 verticalLinesColor.setAlphaF(0.4);
67 horizontalLinesColor = verticalLinesColor;
68 }
69
70 int precision;
71 uint samples;
72 uint bezierCurveOffset;
73
74 double scaledBy;
75 double verticalMin;
76 double verticalMax;
77 double niceVertMin;
78 double niceVertMax;
79 double niceVertRange;
80
81 uint verticalLinesOffset;
82 uint verticalLinesDistance;
83 QColor verticalLinesColor;
84
85 bool showHorizontalLines;
86 uint horizontalScale;
87 uint horizontalLinesCount;
88 QColor horizontalLinesColor;
89
90 Svg *svgBackground;
91 QString svgFilename;
92
93 QColor fontColor;
94 QColor borderColor;
95 QColor backgroundColor;
96 QPixmap backgroundPixmap;
97
98 QFont font;
99 QString title;
100 QString unit;
101
102 QList<PlotColor> plotColors;
103 QList<QList<double> > plotData;
104
105 bool fillPlots : 1;
106 bool showLabels : 1;
107 bool showTopBar : 1;
108 bool stackPlots : 1;
109 bool useAutoRange : 1;
110 bool showThinFrame : 1;
111
112 bool showVerticalLines : 1;
113 bool verticalLinesScroll : 1;
114};
115
116SignalPlotter::SignalPlotter(QGraphicsItem *parent)
117 : QGraphicsWidget(parent),
118 d(new SignalPlotterPrivate)
119{
120 d->precision = 0;
121 d->bezierCurveOffset = 0;
122 d->samples = 0;
123 d->verticalMin = d->verticalMax = 0.0;
124 d->niceVertMin = d->niceVertMax = 0.0;
125 d->niceVertRange = 0;
126 d->useAutoRange = true;
127 d->scaledBy = 1;
128 d->showThinFrame = true;
129
130 d->showVerticalLines = true;
131 d->verticalLinesDistance = 30;
132 d->verticalLinesScroll = true;
133 d->verticalLinesOffset = 0;
134 d->horizontalScale = 1;
135
136 d->showHorizontalLines = true;
137 d->horizontalLinesCount = 5;
138
139 d->showLabels = true;
140 d->showTopBar = true;
141 d->stackPlots = true;
142 d->fillPlots = true;
143
144 // Anything smaller than this does not make sense.
145 setMinimumSize(QSizeF(KIconLoader::SizeSmall, KIconLoader::SizeSmall));
146
147 setSvgBackground("widgets/plot-background");
148
149 connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), SLOT(themeChanged()));
150 d->themeChanged();
151
152 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
153}
154
155SignalPlotter::~SignalPlotter()
156{
157 delete d;
158}
159
160QString SignalPlotter::unit() const
161{
162 return d->unit;
163}
164void SignalPlotter::setUnit(const QString &unit)
165{
166 d->unit= unit;
167}
168
169void SignalPlotter::addPlot(const QColor &color)
170{
171 // When we add a new plot, go back and set the data for this plot to 0 for
172 // all the other times. This is because it makes it easier for moveSensors.
173 foreach (QList<double> data, d->plotData) {
174 data.append(0);
175 }
176 PlotColor newColor;
177 newColor.color = color;
178 newColor.darkColor = color.dark(150);
179 d->plotColors.append(newColor);
180}
181
182void SignalPlotter::addSample(const QList<double>& sampleBuf)
183{
184 if (d->samples < 4) {
185 // It might be possible, under some race conditions, for addSample
186 // to be called before d->samples is set. This is just to be safe.
187 kDebug() << "Error - d->samples is only " << d->samples;
188 updateDataBuffers();
189 kDebug() << "d->samples is now " << d->samples;
190 if (d->samples < 4) {
191 return;
192 }
193 }
194 d->plotData.prepend(sampleBuf);
195 Q_ASSERT(sampleBuf.count() == d->plotColors.count());
196 if ((uint)d->plotData.size() > d->samples) {
197 d->plotData.removeLast(); // we have too many. Remove the last item
198 if ((uint)d->plotData.size() > d->samples) {
199 // If we still have too many, then we have resized the widget.
200 // Remove one more. That way we will slowly resize to the new size
201 d->plotData.removeLast();
202 }
203 }
204
205 if (d->bezierCurveOffset >= 2) {
206 d->bezierCurveOffset = 0;
207 } else {
208 d->bezierCurveOffset++;
209 }
210
211 Q_ASSERT((uint)d->plotData.size() >= d->bezierCurveOffset);
212
213 // If the vertical lines are scrolling, increment the offset
214 // so they move with the data.
215 if (d->verticalLinesScroll) {
216 d->verticalLinesOffset =
217 (d->verticalLinesOffset + d->horizontalScale) % d->verticalLinesDistance;
218 }
219 update();
220}
221
222void SignalPlotter::reorderPlots(const QList<uint>& newOrder)
223{
224 if (newOrder.count() != d->plotColors.count()) {
225 kDebug() << "neworder has " << newOrder.count()
226 << " and plot colors is " << d->plotColors.count();
227 return;
228 }
229 foreach (QList<double> data, d->plotData) {
230 if (newOrder.count() != data.count()) {
231 kDebug() << "Serious problem in move sample. plotdata[i] has "
232 << data.count() << " and neworder has " << newOrder.count();
233 } else {
234 QList<double> newPlot;
235 for (int i = 0; i < newOrder.count(); i++) {
236 int newIndex = newOrder[i];
237 newPlot.append(data.at(newIndex));
238 }
239 data = newPlot;
240 }
241 }
242 QList<PlotColor> newPlotColors;
243 for (int i = 0; i < newOrder.count(); i++) {
244 int newIndex = newOrder[i];
245 PlotColor newColor = d->plotColors.at(newIndex);
246 newPlotColors.append(newColor);
247 }
248 d->plotColors = newPlotColors;
249}
250
251void SignalPlotter::setVerticalRange(double min, double max)
252{
253 d->verticalMin = min;
254 d->verticalMax = max;
255 calculateNiceRange();
256}
257
258QList<PlotColor> &SignalPlotter::plotColors()
259{
260 return d->plotColors;
261}
262
263void SignalPlotter::removePlot(uint pos)
264{
265 if (pos >= (uint)d->plotColors.size()) {
266 return;
267 }
268 d->plotColors.removeAt(pos);
269
270 foreach (QList<double> data, d->plotData) {
271 if ((uint)data.size() >= pos) {
272 data.removeAt(pos);
273 }
274 }
275}
276
277void SignalPlotter::scale(qreal delta)
278{
279 if (d->scaledBy == delta) {
280 return;
281 }
282 d->scaledBy = delta;
283 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
284 calculateNiceRange();
285}
286
287qreal SignalPlotter::scaledBy() const
288{
289 return d->scaledBy;
290}
291
292void SignalPlotter::setTitle(const QString &title)
293{
294 if (d->title == title) {
295 return;
296 }
297 d->title = title;
298 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
299}
300
301QString SignalPlotter::title() const
302{
303 return d->title;
304}
305
306void SignalPlotter::setUseAutoRange(bool value)
307{
308 d->useAutoRange = value;
309 calculateNiceRange();
310 // this change will be detected in paint and the image cache regenerated
311}
312
313bool SignalPlotter::useAutoRange() const
314{
315 return d->useAutoRange;
316}
317
318double SignalPlotter::verticalMinValue() const
319{
320 return d->verticalMin;
321}
322
323double SignalPlotter::verticalMaxValue() const
324{
325 return d->verticalMax;
326}
327
328void SignalPlotter::setHorizontalScale(uint scale)
329{
330 if (scale == d->horizontalScale) {
331 return;
332 }
333
334 d->horizontalScale = scale;
335 updateDataBuffers();
336 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
337}
338
339uint SignalPlotter::horizontalScale() const
340{
341 return d->horizontalScale;
342}
343
344void SignalPlotter::setShowVerticalLines(bool value)
345{
346 if (d->showVerticalLines == value) {
347 return;
348 }
349 d->showVerticalLines = value;
350 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
351}
352
353bool SignalPlotter::showVerticalLines() const
354{
355 return d->showVerticalLines;
356}
357
358void SignalPlotter::setVerticalLinesColor(const QColor &color)
359{
360 if (d->verticalLinesColor == color) {
361 return;
362 }
363 d->verticalLinesColor = color;
364 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
365}
366
367QColor SignalPlotter::verticalLinesColor() const
368{
369 return d->verticalLinesColor;
370}
371
372void SignalPlotter::setVerticalLinesDistance(uint distance)
373{
374 if (distance == d->verticalLinesDistance) {
375 return;
376 }
377 d->verticalLinesDistance = distance;
378 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
379}
380
381uint SignalPlotter::verticalLinesDistance() const
382{
383 return d->verticalLinesDistance;
384}
385
386void SignalPlotter::setVerticalLinesScroll(bool value)
387{
388 if (value == d->verticalLinesScroll) {
389 return;
390 }
391 d->verticalLinesScroll = value;
392 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
393}
394
395bool SignalPlotter::verticalLinesScroll() const
396{
397 return d->verticalLinesScroll;
398}
399
400void SignalPlotter::setShowHorizontalLines(bool value)
401{
402 if (value == d->showHorizontalLines) {
403 return;
404 }
405 d->showHorizontalLines = value;
406 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
407}
408
409bool SignalPlotter::showHorizontalLines() const
410{
411 return d->showHorizontalLines;
412}
413
414void SignalPlotter::setFontColor(const QColor &color)
415{
416 d->fontColor = color;
417}
418
419QColor SignalPlotter::fontColor() const
420{
421 return d->fontColor;
422}
423
424void SignalPlotter::setHorizontalLinesColor(const QColor &color)
425{
426 if (color == d->horizontalLinesColor) {
427 return;
428 }
429 d->horizontalLinesColor = color;
430 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
431}
432
433QColor SignalPlotter::horizontalLinesColor() const
434{
435 return d->horizontalLinesColor;
436}
437
438void SignalPlotter::setHorizontalLinesCount(uint count)
439{
440 if (count == d->horizontalLinesCount) {
441 return;
442 }
443 d->horizontalLinesCount = count;
444 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
445 calculateNiceRange();
446}
447
448uint SignalPlotter::horizontalLinesCount() const
449{
450 return d->horizontalLinesCount;
451}
452
453void SignalPlotter::setShowLabels(bool value)
454{
455 if (value == d->showLabels) {
456 return;
457 }
458 d->showLabels = value;
459 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
460}
461
462bool SignalPlotter::showLabels() const
463{
464 return d->showLabels;
465}
466
467void SignalPlotter::setShowTopBar(bool value)
468{
469 if (d->showTopBar == value) {
470 return;
471 }
472 d->showTopBar = value;
473 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
474}
475
476bool SignalPlotter::showTopBar() const
477{
478 return d->showTopBar;
479}
480
481void SignalPlotter::setFont(const QFont &font)
482{
483 d->font = font;
484 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
485}
486
487QFont SignalPlotter::font() const
488{
489 return d->font;
490}
491
492QString SignalPlotter::svgBackground()
493{
494 return d->svgFilename;
495}
496
497void SignalPlotter::setSvgBackground(const QString &filename)
498{
499 if (d->svgFilename == filename) {
500 return;
501 }
502
503 if (!filename.isEmpty() && filename[0] == '/') {
504 KStandardDirs *kstd = KGlobal::dirs();
505 d->svgFilename = kstd->findResource("data", "ksysguard/" + filename);
506 } else {
507 d->svgFilename = filename;
508 }
509
510 delete d->svgBackground;
511 d->svgBackground = 0;
512 if (!d->svgFilename.isEmpty()) {
513 d->svgBackground = new Svg(this);
514 d->svgBackground->setImagePath(d->svgFilename);
515 }
516
517}
518
519void SignalPlotter::setBackgroundColor(const QColor &color)
520{
521 if (color == d->backgroundColor) {
522 return;
523 }
524 d->backgroundColor = color;
525 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
526}
527
528QColor SignalPlotter::backgroundColor() const
529{
530 return d->backgroundColor;
531}
532
533void SignalPlotter::setThinFrame(bool set)
534{
535 if (d->showThinFrame == set) {
536 return;
537 }
538 d->showThinFrame = set;
539 d->backgroundPixmap = QPixmap(); // we changed a paint setting, so reset the cache
540}
541
542bool SignalPlotter::thinFrame() const
543{
544 return d->showThinFrame;
545}
546
547void SignalPlotter::setStackPlots(bool stack)
548{
549 d->stackPlots = stack;
550 d->fillPlots = stack;
551}
552
553bool SignalPlotter::stackPlots() const
554{
555 return d->stackPlots;
556}
557
558void SignalPlotter::updateDataBuffers()
559{
560 // This is called when the widget has resized
561 //
562 // Determine new number of samples first.
563 // +0.5 to ensure rounding up
564 // +4 for extra data points so there is
565 // 1) no wasted space and
566 // 2) no loss of precision when drawing the first data point.
567 d->samples = static_cast<uint>(((size().width() - 2) /
568 d->horizontalScale) + 4.5);
569}
570
571QPixmap SignalPlotter::getSnapshotImage(uint w, uint height)
572{
573 uint horizontalStep = (uint)((1.0 * w / size().width()) + 0.5); // get the closest integer horizontal step
574 uint newWidth = (uint) (horizontalStep * size().width());
575 QPixmap image = QPixmap(newWidth, height);
576 QPainter p(&image);
577 drawWidget(&p, newWidth, height, newWidth);
578 p.end();
579 return image;
580}
581
582void SignalPlotter::setGeometry(const QRectF &geometry)
583{
584 // First update our size, then update the data buffers accordingly.
585 QGraphicsWidget::setGeometry(geometry);
586 updateDataBuffers();
587}
588
589void SignalPlotter::paint(QPainter *painter,
590 const QStyleOptionGraphicsItem *option, QWidget *widget)
591{
592 Q_UNUSED(option);
593 Q_UNUSED(widget);
594
595 uint w = (uint) size().width();
596 uint h = (uint) size().height();
597
598 // Do not do repaints when the widget is not yet setup properly.
599 if (w <= 2) {
600 return;
601 }
602
603 drawWidget(painter, w, h, d->horizontalScale);
604}
605
606void SignalPlotter::drawWidget(QPainter *p, uint w, uint height, int horizontalScale)
607{
608 uint h = height; // h will become the height of just the bit we draw the plots in
609 p->setFont(d->font);
610
611 uint fontheight = p->fontMetrics().height();
612 if (d->verticalMin < d->niceVertMin ||
613 d->verticalMax > d->niceVertMax ||
614 d->verticalMax < (d->niceVertRange * 0.75 + d->niceVertMin) ||
615 d->niceVertRange == 0) {
616 calculateNiceRange();
617 }
618 QPen pen;
619 pen.setWidth(1);
620 pen.setCapStyle(Qt::RoundCap);
621 p->setPen(pen);
622
623 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
624 h-= top;
625
626 // Check if there's enough room to actually show a top bar.
627 // Must be enough room for a bar at the top, plus horizontal
628 // lines each of a size with room for a scale.
629 bool showTopBar = d->showTopBar && h > (fontheight/*top bar size*/ +5/*smallest reasonable size for a graph*/);
630 if (showTopBar) {
631 top += fontheight; // The top bar has the same height as fontheight. Thus the top of the graph is at fontheight
632 h -= fontheight;
633 }
634 if (d->backgroundPixmap.isNull() ||
635 (uint)d->backgroundPixmap.size().height() != height ||
636 (uint)d->backgroundPixmap.size().width() != w) {
637 // recreate on resize etc
638 d->backgroundPixmap = QPixmap(w, height);
639 d->backgroundPixmap.fill(Qt::transparent);
640 QPainter pCache(&d->backgroundPixmap);
641 pCache.setRenderHint(QPainter::Antialiasing, false);
642 pCache.setFont(d->font);
643
644 drawBackground(&pCache, w, height);
645
646 if (d->showThinFrame) {
647 drawThinFrame(&pCache, w, height);
648 // We have a 'frame' in the bottom and right - so subtract them from the view
649 h--;
650 w--;
651 pCache.setClipRect(0, 0, w, height-1);
652 }
653
654 if (showTopBar) {
655 int separatorX = w / 2;
656 drawTopBarFrame(&pCache, separatorX, top);
657 }
658
659 // Draw scope-like grid vertical lines if it doesn't move.
660 // If it does move, draw it in the dynamic part of the code.
661 if (!d->verticalLinesScroll && d->showVerticalLines && w > 60) {
662 drawVerticalLines(&pCache, top, w, h);
663 }
664
665 if (d->showHorizontalLines) {
666 drawHorizontalLines(&pCache, top, w, h);
667 }
668
669 } else {
670 if (d->showThinFrame) {
671 // We have a 'frame' in the bottom and right - so subtract them from the view
672 h--;
673 w--;
674 }
675 }
676 p->drawPixmap(0, 0, d->backgroundPixmap);
677 p->setRenderHint(QPainter::Antialiasing, true);
678
679 if (showTopBar) {
680 int separatorX = w / 2;
681 int topBarWidth = w - separatorX -2;
682 drawTopBarContents(p, separatorX, topBarWidth, top -1);
683 }
684
685 p->setClipRect(0, top, w, h, Qt::IntersectClip);
686 // Draw scope-like grid vertical lines
687 if (d->verticalLinesScroll && d->showVerticalLines && w > 60) {
688 drawVerticalLines(p, top, w, h);
689 }
690
691 drawPlots(p, top, w, h, horizontalScale);
692
693 if (d->showLabels && w > 60 && h > (fontheight + 1)) {
694 // if there's room to draw the labels, then draw them!
695 drawAxisText(p, top, h);
696 }
697}
698
699void SignalPlotter::drawBackground(QPainter *p, int w, int h)
700{
701 if (d->svgBackground) {
702 d->svgBackground->resize(w, h);
703 d->svgBackground->paint(p, 0, 0);
704 } else {
705 p->fillRect(0, 0, w, h, d->backgroundColor);
706 }
707}
708
709void SignalPlotter::drawThinFrame(QPainter *p, int w, int h)
710{
711 // Draw a line along the bottom and the right side of the
712 // widget to create a 3D like look.
713 p->setPen(d->borderColor);
714 p->drawLine(0, h - 1, w - 1, h - 1);
715 p->drawLine(w - 1, 0, w - 1, h - 1);
716}
717
718void SignalPlotter::calculateNiceRange()
719{
720 d->niceVertRange = d->verticalMax - d->verticalMin;
721 // If the range is too small we will force it to 1.0 since it
722 // looks a lot nicer.
723 if (d->niceVertRange < 0.000001) {
724 d->niceVertRange = 1.0;
725 }
726
727 d->niceVertMin = d->verticalMin;
728 if (d->verticalMin != 0.0) {
729 double dim = pow(10, floor(log10(fabs(d->verticalMin)))) / 2;
730 if (d->verticalMin < 0.0) {
731 d->niceVertMin = dim * floor(d->verticalMin / dim);
732 } else {
733 d->niceVertMin = dim * ceil(d->verticalMin / dim);
734 }
735 d->niceVertRange = d->verticalMax - d->niceVertMin;
736 if (d->niceVertRange < 0.000001) {
737 d->niceVertRange = 1.0;
738 }
739 }
740 // Massage the range so that the grid shows some nice values.
741 double step = d->niceVertRange / (d->scaledBy * (d->horizontalLinesCount + 1));
742 int logdim = (int)floor(log10(step));
743 double dim = pow((double)10.0, logdim) / 2;
744 int a = (int)ceil(step / dim);
745 if (logdim >= 0) {
746 d->precision = 0;
747 } else if (a % 2 == 0) {
748 d->precision = -logdim;
749 } else {
750 d->precision = 1 - logdim;
751 }
752 d->niceVertRange = d->scaledBy * dim * a * (d->horizontalLinesCount + 1);
753 d->niceVertMax = d->niceVertMin + d->niceVertRange;
754}
755
756void SignalPlotter::drawTopBarFrame(QPainter *p, int separatorX, int height)
757{
758 // Draw horizontal bar with current sensor values at top of display.
759 // Remember that it has a height of 'height'. Thus the lowest pixel
760 // it can draw on is height-1 since we count from 0.
761 p->setPen(Qt::NoPen);
762 p->setPen(d->fontColor);
763 p->drawText(0, 1, separatorX, height, Qt::AlignCenter, d->title);
764 p->setPen(d->horizontalLinesColor);
765 p->drawLine(separatorX - 1, 1, separatorX - 1, height - 1);
766}
767
768void SignalPlotter::drawTopBarContents(QPainter *p, int x, int width, int height)
769{
770 // The height is the height of the contents, so this will be
771 // one pixel less than the height of the topbar
772 double bias = -d->niceVertMin;
773 double scaleFac = width / d->niceVertRange;
774 // The top bar shows the current values of all the plot data.
775 // This iterates through each different plot and plots the newest data for each.
776 if (!d->plotData.isEmpty()) {
777 QList<double> newestData = d->plotData.first();
778 for (int i = newestData.count()-1; i >= 0; --i) {
779 double newest_datapoint = newestData.at(i);
780 int start = x + (int)(bias * scaleFac);
781 int end = x + (int)((bias += newest_datapoint) * scaleFac);
782 int start2 = qMin(start, end);
783 end = qMax(start, end);
784 start = start2;
785
786 // If the rect is wider than 2 pixels we draw only the last
787 // pixels with the bright color. The rest is painted with
788 // a 50% darker color.
789
790 p->setPen(Qt::NoPen);
791 QLinearGradient linearGrad(QPointF(start, 1), QPointF(end, 1));
792 linearGrad.setColorAt(0, d->plotColors[i].darkColor);
793 linearGrad.setColorAt(1, d->plotColors[i].color);
794 p->fillRect(start, 1, end - start, height-1, QBrush(linearGrad));
795 }
796 }
797}
798
799void SignalPlotter::drawVerticalLines(QPainter *p, int top, int w, int h)
800{
801 p->setPen(d->verticalLinesColor);
802 for (int x = d->verticalLinesOffset; x < (w - 2); x += d->verticalLinesDistance) {
803 p->drawLine(w - x, top, w - x, h + top -1);
804 }
805}
806
807void SignalPlotter::drawPlots(QPainter *p, int top, int w, int h, int horizontalScale)
808{
809 Q_ASSERT(d->niceVertRange != 0);
810
811 if (d->niceVertRange == 0) {
812 d->niceVertRange = 1;
813 }
814 double scaleFac = (h - 1) / d->niceVertRange;
815
816 int xPos = 0;
817 QList< QList<double> >::Iterator it = d->plotData.begin();
818
819 p->setPen(Qt::NoPen);
820 // In autoRange mode we determine the range and plot the values in
821 // one go. This is more efficiently than running through the
822 // buffers twice but we do react on recently discarded samples as
823 // well as new samples one plot too late. So the range is not
824 // correct if the recently discarded samples are larger or smaller
825 // than the current extreme values. But we can probably live with
826 // this.
827
828 // These values aren't used directly anywhere. Instead we call
829 // calculateNiceRange() which massages these values into a nicer
830 // values. Rounding etc. This means it's safe to change these values
831 // without affecting any other drawings.
832 if (d->useAutoRange) {
833 d->verticalMin = d->verticalMax = 0.0;
834 }
835
836 // d->bezierCurveOffset is how many points we have at the start.
837 // All the bezier curves are in groups of 3, with the first of the
838 // next group being the last point of the previous group
839
840 // Example, when d->bezierCurveOffset == 0, and we have data, then just
841 // plot a normal bezier curve. (we will have at least 3 points in this case)
842 // When d->bezierCurveOffset == 1, then we want a bezier curve that uses
843 // the first data point and the second data point. Then the next group
844 // starts from the second data point.
845 //
846 // When d->bezierCurveOffset == 2, then we want a bezier curve that
847 // uses the first, second and third data.
848 for (uint i = 0; it != d->plotData.end() && i < d->samples; ++i) {
849 QPen pen;
850 pen.setWidth(1);
851 pen.setCapStyle(Qt::FlatCap);
852
853 // We will plot 1 bezier curve for every 3 points, with the 4th point
854 // being the end of one bezier curve and the start of the second.
855 // This does means the bezier curves will not join nicely, but it
856 // should be better than nothing.
857 QList<double> datapoints = *it;
858 QList<double> prev_datapoints = datapoints;
859 QList<double> prev_prev_datapoints = datapoints;
860 QList<double> prev_prev_prev_datapoints = datapoints;
861
862 if (i == 0 && d->bezierCurveOffset > 0) {
863 // We are plotting an incomplete bezier curve - we don't have
864 // all the data we want. Try to cope.
865 xPos += horizontalScale * d->bezierCurveOffset;
866 if (d->bezierCurveOffset == 1) {
867 prev_datapoints = *it;
868 ++it; // Now we are on the first element of the next group, if it exists
869 if (it != d->plotData.end()) {
870 prev_prev_prev_datapoints = prev_prev_datapoints = *it;
871 } else {
872 prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints;
873 }
874 } else {
875 // d->bezierCurveOffset must be 2 now
876 prev_datapoints = *it;
877 Q_ASSERT(it != d->plotData.end());
878 ++it;
879 prev_prev_datapoints = *it;
880 Q_ASSERT(it != d->plotData.end());
881 ++it; // Now we are on the first element of the next group, if it exists
882 if (it != d->plotData.end()) {
883 prev_prev_prev_datapoints = *it;
884 } else {
885 prev_prev_prev_datapoints = prev_prev_datapoints;
886 }
887 }
888 } else {
889 // We have a group of 3 points at least. That's 1 start point and 2 control points.
890 xPos += horizontalScale * 3;
891 it++;
892 if (it != d->plotData.end()) {
893 prev_datapoints = *it;
894 it++;
895 if (it != d->plotData.end()) {
896 prev_prev_datapoints = *it;
897 it++; // We are now on the next set of data points
898 if (it != d->plotData.end()) {
899 // We have this datapoint, so use it for our finish point
900 prev_prev_prev_datapoints = *it;
901 } else {
902 // We don't have the next set, so use our last control
903 // point as our finish point
904 prev_prev_prev_datapoints = prev_prev_datapoints;
905 }
906 } else {
907 prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints;
908 }
909 } else {
910 prev_prev_prev_datapoints = prev_prev_datapoints = prev_datapoints = datapoints;
911 }
912 }
913
914 float x0 = w - xPos + 3.0 * horizontalScale;
915 float x1 = w - xPos + 2.0 * horizontalScale;
916 float x2 = w - xPos + 1.0 * horizontalScale;
917 float x3 = w - xPos;
918 float y0 = h - 1 + top;
919 float y1 = y0;
920 float y2 = y0;
921 float y3 = y0;
922
923 int offset = 0; // Our line is 2 pixels thick. This means that when we draw the area, we need to offset
924 double max_y = 0;
925 double min_y = 0;
926 for (int j = qMin(datapoints.size(), d->plotColors.size()) - 1; j >=0; --j) {
927 if (d->useAutoRange) {
928 // If we use autorange, then we need to prepare the min and max values for _next_ time we paint.
929 // If we are stacking the plots, then we need to add the maximums together.
930 double current_maxvalue =
931 qMax(datapoints[j],
932 qMax(prev_datapoints[j],
933 qMax(prev_prev_datapoints[j],
934 prev_prev_prev_datapoints[j])));
935 double current_minvalue =
936 qMin<double>(datapoints[j],
937 qMin(prev_datapoints[j],
938 qMin(prev_prev_datapoints[j],
939 prev_prev_prev_datapoints[j])));
940 d->verticalMax = qMax(d->verticalMax, current_maxvalue);
941 d->verticalMin = qMin(d->verticalMin, current_maxvalue);
942 if (d->stackPlots) {
943 max_y += current_maxvalue;
944 min_y += current_minvalue;
945 }
946 }
947
948 // Draw polygon only if enough data points are available.
949 if (j < prev_prev_prev_datapoints.count() &&
950 j < prev_prev_datapoints.count() &&
951 j < prev_datapoints.count()) {
952
953 // The height of the whole widget is h+top-> The height of
954 // the area we are plotting in is just h.
955 // The y coordinate system starts from the top, so at the
956 // bottom the y coordinate is h+top.
957 // So to draw a point at value y', we need to put this at h+top-y'
958 float delta_y0;
959 delta_y0 = (datapoints[j] - d->niceVertMin) * scaleFac;
960
961 float delta_y1;
962 delta_y1 = (prev_datapoints[j] - d->niceVertMin) * scaleFac;
963
964 float delta_y2;
965 delta_y2 = (prev_prev_datapoints[j] - d->niceVertMin) * scaleFac;
966
967 float delta_y3;
968 delta_y3 = (prev_prev_prev_datapoints[j] - d->niceVertMin) * scaleFac;
969
970 QPainterPath path;
971 if (d->stackPlots && offset) {
972 // we don't want the lines to overdraw each other.
973 // This isn't a great solution though :(
974 if (delta_y0 < 3) {
975 delta_y0=3;
976 }
977 if (delta_y1 < 3) {
978 delta_y1=3;
979 }
980 if (delta_y2 < 3) {
981 delta_y2=3;
982 }
983 if (delta_y3 < 3) {
984 delta_y3=3;
985 }
986 }
987 path.moveTo(x0, y0 - delta_y0);
988 path.cubicTo(x1, y1 - delta_y1, x2, y2 - delta_y2, x3, y3 - delta_y3);
989
990 if (d->fillPlots) {
991 QPainterPath path2(path);
992 QLinearGradient myGradient(0,(h - 1 + top), 0, (h - 1 + top) / 5);
993 Q_ASSERT(d->plotColors.size() >= j);
994 QColor c0(d->plotColors[j].darkColor);
995 QColor c1(d->plotColors[j].color);
996 c0.setAlpha(150);
997 c1.setAlpha(150);
998 myGradient.setColorAt(0, c0);
999 myGradient.setColorAt(1, c1);
1000
1001 path2.lineTo(x3, y3 - offset);
1002 if (d->stackPlots) {
1003 // offset is set to 1 after the first plot is drawn,
1004 // so we don't trample on top of the 2pt thick line
1005 path2.cubicTo(x2, y2 - offset, x1, y1 - offset, x0, y0 - offset);
1006 } else {
1007 path2.lineTo(x0, y0 - 1);
1008 }
1009 p->setBrush(myGradient);
1010 p->setPen(Qt::NoPen);
1011 p->drawPath(path2);
1012 }
1013 p->setBrush(Qt::NoBrush);
1014 Q_ASSERT(d->plotColors.size() >= j);
1015 pen.setColor(d->plotColors[j].color);
1016 p->setPen(pen);
1017 p->drawPath(path);
1018
1019 if (d->stackPlots) {
1020 // We can draw the plots stacked on top of each other.
1021 // This means that say plot 0 has the value 2 and plot
1022 // 1 has the value 3, then we plot plot 0 at 2 and plot 1 at 2+3 = 5.
1023 y0 -= delta_y0;
1024 y1 -= delta_y1;
1025 y2 -= delta_y2;
1026 y3 -= delta_y3;
1027 offset = 1; // see the comment further up for int offset;
1028 }
1029 }
1030 if (d->useAutoRange && d->stackPlots) {
1031 d->verticalMax = qMax(max_y, d->verticalMax);
1032 d->verticalMin = qMin(min_y, d->verticalMin);
1033 }
1034 }
1035 }
1036}
1037
1038void SignalPlotter::drawAxisText(QPainter *p, int top, int h)
1039{
1040 // Draw horizontal lines and values. Lines are always drawn.
1041 // Values are only draw when width is greater than 60.
1042 QString val;
1043
1044 // top = 0 or font.height depending on whether there's a topbar or not
1045 // h = graphing area.height - i.e. the actual space we have to draw inside
1046 // Note we are drawing from 0,0 as the top left corner. So we have to add on top
1047 // to get to the top of where we are drawing so top+h is the height of the widget.
1048 p->setPen(d->fontColor);
1049 double stepsize = d->niceVertRange / (d->scaledBy * (d->horizontalLinesCount + 1));
1050 int step =
1051 (int)ceil((d->horizontalLinesCount+1) *
1052 (p->fontMetrics().height() + p->fontMetrics().leading() / 2.0) / h);
1053 if (step == 0) {
1054 step = 1;
1055 }
1056 for (int y = d->horizontalLinesCount + 1; y >= 1; y-= step) {
1057 int y_coord =
1058 top + (y * (h - 1)) / (d->horizontalLinesCount + 1); // Make sure it's y*h first to avoid rounding bugs
1059 if (y_coord - p->fontMetrics().ascent() < top) {
1060 // at most, only allow 4 pixels of the text to be covered up
1061 // by the top bar. Otherwise just don't bother to draw it
1062 continue;
1063 }
1064 double value;
1065 if ((uint)y == d->horizontalLinesCount + 1) {
1066 value = d->niceVertMin; // sometimes using the formulas gives us a value very slightly off
1067 } else {
1068 value = d->niceVertMax / d->scaledBy - y * stepsize;
1069 }
1070
1071 QString number = KGlobal::locale()->formatNumber(value, d->precision);
1072 val = QString("%1 %2").arg(number, d->unit);
1073 p->drawText(6, y_coord - 3, val);
1074 }
1075}
1076
1077void SignalPlotter::drawHorizontalLines(QPainter *p, int top, int w, int h)
1078{
1079 p->setPen(d->horizontalLinesColor);
1080 for (uint y = 0; y <= d->horizontalLinesCount + 1; y++) {
1081 // 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
1082 int y_coord = top + (y * (h - 1)) / (d->horizontalLinesCount + 1); // Make sure it's y*h first to avoid rounding bugs
1083 p->drawLine(0, y_coord, w - 2, y_coord);
1084 }
1085}
1086
1087double SignalPlotter::lastValue(uint i) const
1088{
1089 if (d->plotData.isEmpty() || d->plotData.first().size() <= (int)i) {
1090 return 0;
1091 }
1092 return d->plotData.first()[i];
1093}
1094
1095QString SignalPlotter::lastValueAsString(uint i) const
1096{
1097 if (d->plotData.isEmpty()) {
1098 return QString();
1099 }
1100 double value = d->plotData.first()[i] / d->scaledBy; // retrieve the newest value for this plot then scale it correct
1101 QString number = KGlobal::locale()->formatNumber(value, (value >= 100)?0:2);
1102 return QString("%1 %2").arg(number, d->unit);
1103}
1104
1105} // Plasma namespace
1106
1107#include "signalplotter.moc"
1108
Plasma::SignalPlotter::drawTopBarFrame
void drawTopBarFrame(QPainter *p, int separatorX, int height)
Definition: signalplotter.cpp:756
Plasma::SignalPlotter::drawTopBarContents
void drawTopBarContents(QPainter *p, int x, int width, int height)
Definition: signalplotter.cpp:768
Plasma::SignalPlotter::drawHorizontalLines
void drawHorizontalLines(QPainter *p, int top, int w, int h)
Definition: signalplotter.cpp:1077
Plasma::SignalPlotter::setSvgBackground
void setSvgBackground(const QString &filename)
The filename of the svg background.
Definition: signalplotter.cpp:497
Plasma::SignalPlotter::font
QFont font
Definition: signalplotter.h:60
Plasma::SignalPlotter::drawWidget
void drawWidget(QPainter *p, uint w, uint height, int horizontalScale)
Definition: signalplotter.cpp:606
Plasma::SignalPlotter::title
QString title
Definition: signalplotter.h:48
Plasma::SignalPlotter::unit
QString unit
Definition: signalplotter.h:49
Plasma::SignalPlotter::drawPlots
void drawPlots(QPainter *p, int top, int w, int h, int horizontalScale)
Definition: signalplotter.cpp:807
Plasma::SignalPlotter::drawAxisText
void drawAxisText(QPainter *p, int top, int h)
Definition: signalplotter.cpp:1038
Plasma::SignalPlotter::updateDataBuffers
void updateDataBuffers()
Definition: signalplotter.cpp:558
Plasma::SignalPlotter::drawBackground
void drawBackground(QPainter *p, int w, int h)
Definition: signalplotter.cpp:699
Plasma::SignalPlotter::showTopBar
bool showTopBar
Definition: signalplotter.h:63
Plasma::SignalPlotter::drawThinFrame
void drawThinFrame(QPainter *p, int w, int h)
Definition: signalplotter.cpp:709
Plasma::SignalPlotter::drawVerticalLines
void drawVerticalLines(QPainter *p, int top, int w, int h)
Definition: signalplotter.cpp:799
Plasma::SignalPlotter::scale
qreal scale
Definition: signalplotter.h:50
Plasma::SignalPlotter::horizontalScale
uint horizontalScale
Definition: signalplotter.h:52
Plasma::SignalPlotter::calculateNiceRange
void calculateNiceRange()
Definition: signalplotter.cpp:718
Plasma::Svg
A theme aware image-centric SVG class.
Definition: svg.h:57
Plasma::Theme
Interface to the Plasma theme.
Definition: theme.h:57
Plasma::Theme::color
Q_INVOKABLE QColor color(ColorRole role) const
Returns the text color to be used by items resting on the background.
Definition: theme.cpp:918
Plasma::Theme::defaultTheme
static Theme * defaultTheme()
Singleton pattern accessor.
Definition: theme.cpp:544
Plasma::Theme::BackgroundColor
@ BackgroundColor
the default background color
Definition: theme.h:66
Plasma::Theme::TextColor
@ TextColor
the text color to be used by items resting on the background
Definition: theme.h:63
QGraphicsWidget
QStyleOptionGraphicsItem
QWidget
Plasma
Namespace for everything in libplasma.
Definition: abstractdialogmanager.cpp:25
signalplotter.h
Plasma::PlotColor
Definition: signalplotter.h:35
Plasma::PlotColor::color
QColor color
Definition: signalplotter.h:36
Plasma::PlotColor::darkColor
QColor darkColor
Definition: signalplotter.h:37
svg.h
theme.h
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 20 2023 00:00:00 by doxygen 1.9.6 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

Plasma

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

kdelibs-4.14.38 API Reference

Skip menu "kdelibs-4.14.38 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal