1 /*
2     SPDX-FileCopyrightText: 2007 Krzysztof Kundzicz <athantor@gmail.com>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #include <PlainChartDrawer.h>
7 
8 #include <KLocalizedString>
9 #include <QFileDialog>
10 
11 namespace kt
12 {
13 
PlainChartDrawer(QWidget * p)14 PlainChartDrawer::PlainChartDrawer(QWidget* p) :  QFrame(p), ChartDrawer(), pmCtxMenu(new QMenu(this))
15 {
16     setStyleSheet(QStringLiteral(" background-color: ") % QPalette().color(QPalette::Active, QPalette::Base).name() % QLatin1Char(';'));
17 
18     setContextMenuPolicy(Qt::CustomContextMenu);
19     MakeCtxMenu();
20 
21     connect(this, &PlainChartDrawer::customContextMenuRequested, this, &PlainChartDrawer::showContextMenu);
22 }
23 
~PlainChartDrawer()24 PlainChartDrawer::~PlainChartDrawer()
25 {
26 }
27 
height() const28 inline PlainChartDrawer::wgtunit_t PlainChartDrawer::height() const
29 {
30     return QWidget::height() - 15;
31 }
32 
width() const33 inline PlainChartDrawer::wgtunit_t PlainChartDrawer::width() const
34 {
35     return QWidget::width() - 78;
36 }
37 
TY(const wgtunit_t y) const38 inline PlainChartDrawer::wgtunit_t PlainChartDrawer::TY(const wgtunit_t y) const
39 {
40     return height() - y;
41 }
42 
FindXScreenCoords(const wgtunit_t x) const43 inline PlainChartDrawer::wgtunit_t PlainChartDrawer::FindXScreenCoords(const wgtunit_t x) const
44 {
45     return (width() / mXMax) * x ;
46 }
47 
FindYScreenCoords(const wgtunit_t y) const48 inline PlainChartDrawer::wgtunit_t PlainChartDrawer::FindYScreenCoords(const wgtunit_t y) const
49 {
50     return (height() / mYMax) * y;
51 }
52 
paintEvent(QPaintEvent *)53 void PlainChartDrawer::paintEvent(QPaintEvent*)
54 {
55 
56     QStyleOption opt;
57     opt.init(this);
58 
59     QPainter pnt(this);
60 
61     style()->drawPrimitive(QStyle::PE_Widget, &opt, &pnt, this);
62 
63     pnt.setRenderHint(QPainter::Antialiasing, mAntiAlias);
64     pnt.setRenderHint(QPainter::TextAntialiasing, mAntiAlias);
65 
66     DrawScale(pnt);
67     DrawFrame(pnt);
68     DrawChart(pnt);
69 }
70 
DrawScale(QPainter & rPnt)71 void PlainChartDrawer::DrawScale(QPainter& rPnt)
72 {
73     if (!mYMax) {
74         return;
75     }
76 
77 
78     QPen oldpen = rPnt.pen();
79 
80     QPen pen;
81 
82     if (mBgdGrid) {
83 
84         pen.setColor(QPalette().color(QPalette::AlternateBase));
85         rPnt.setPen(pen);
86 
87         for (wgtunit_t i = 5; i < height(); i += 10) {
88             rPnt.drawLine(0, TY(i), width(), TY(i));
89         }
90 
91         for (wgtunit_t i = 5; i < width(); i += 10) {
92             rPnt.drawLine(i, TY(0), i, TY(height()));
93         }
94     }
95 
96     wgtunit_t scale =  height() / 8;
97 
98     pen.setColor(QPalette().color(QPalette::Text));
99     pen.setWidth(1);
100     pen.setStyle(Qt::DotLine);
101 
102     rPnt.setPen(pen);
103 
104     QFont oldfont(rPnt.font()), qf(oldfont);
105     qf.setStretch(QFont::SemiCondensed);
106     rPnt.setFont(qf);
107 
108     rPnt.drawLine(0, TY(height() - 10), width(), TY(height() - 10));
109     rPnt.drawText(width() + 4, TY(height() - 10) + 4, QString::number(mYMax, 'f', 1));
110 
111     for (wgtunit_t i = 0; i < height() - 15; i += scale) {
112         rPnt.drawLine(0, TY(i), width(), TY(i));
113         rPnt.drawText(width() + 5, TY(i) + 5, QString::number((mYMax / 8.0) * (i / scale), 'f', 1));
114     }
115 
116     rPnt.setPen(oldpen);
117 
118     rPnt.setFont(oldfont);
119 }
120 
DrawFrame(QPainter & rPnt)121 void PlainChartDrawer::DrawFrame(QPainter& rPnt)
122 {
123     QPen oldpen = rPnt.pen();
124     QPen pen;
125 
126     pen.setColor(QPalette().color(QPalette::Text));
127     pen.setWidth(3);
128     rPnt.setPen(pen);
129 
130     QPoint points[3] = {
131         QPoint(0, TY(0)),
132         QPoint(width(), TY(0)),
133         QPoint(width(), TY(height()))
134     };
135 
136     rPnt.drawPolyline(points, 3);
137 
138     QFont oldf(rPnt.font());
139     QFont newf(oldf);
140     newf.setWeight(QFont::Bold);
141     newf.setStretch(QFont::SemiCondensed);
142     newf.setPointSize(10);
143     newf.setUnderline(1);
144     rPnt.setFont(newf);
145 
146 
147     QColor qc(pen.color());
148     qc.setAlphaF(0.75);
149     pen.setColor(qc);
150     rPnt.setPen(pen);
151 
152     rPnt.drawText(width() + 42., TY(-10.), pmUnitName);
153 
154     rPnt.setFont(oldf);
155     rPnt.setPen(oldpen);
156 
157 }
158 
DrawChart(QPainter & rPnt)159 void PlainChartDrawer::DrawChart(QPainter& rPnt)
160 {
161     QPen oldpen = rPnt.pen();
162 
163     for (size_t i = 0; i < pmVals.size(); i++) {
164         DrawChartLine(rPnt, pmVals.at(i));
165         DrawCurrentValue(rPnt, pmVals.at(i), i);
166 
167         if (pmVals.at(i).getMarkMax()) {
168             DrawMaximum(rPnt, pmVals.at(i), i);
169         }
170     }
171 
172     rPnt.setPen(oldpen);
173 }
174 
DrawChartLine(QPainter & rPnt,const ChartDrawerData & rCdd)175 void PlainChartDrawer::DrawChartLine(QPainter& rPnt, const ChartDrawerData& rCdd)
176 {
177 
178     QPen qp = rCdd.getPen();
179     qp.setJoinStyle(Qt::RoundJoin);
180     rPnt.setPen(qp);
181 
182     const ChartDrawerData::val_t& vals = rCdd.getValues();
183 
184     QPointF* l = new QPointF[vals.size()];
185 
186     for (size_t i = 0; i < vals.size(); i++) {
187         l[i] = QPointF(
188                    FindXScreenCoords(i),
189                    TY(FindYScreenCoords(vals.at(i)))
190                );
191     }
192 
193     l[vals.size() - 1] = QPointF(
194 
195                              width(),
196                              TY(FindYScreenCoords(*(vals.end() - 1)))
197                          );
198 
199     rPnt.drawPolyline(l, vals.size());
200     delete [] l;
201 }
202 
DrawCurrentValue(QPainter & rPnt,const ChartDrawerData & rCdd,size_t idx)203 void PlainChartDrawer::DrawCurrentValue(QPainter& rPnt, const ChartDrawerData& rCdd, size_t idx)
204 {
205     QPen qp = rCdd.getPen();
206     qp.setJoinStyle(Qt::RoundJoin);
207 
208     QColor qc(qp.color());
209 
210     QFont oldfont(rPnt.font()), qf(oldfont);
211     qf.setStretch(QFont::SemiCondensed);
212 
213     rPnt.setFont(qf);
214     rPnt.setPen(qp);
215 
216     idx++;
217     wgtunit_t y = -5 + (idx * 16);
218 
219     wgtunit_t val = *(rCdd.getValues().end() - 1);
220 
221     wgtunit_t lenmod;
222 
223     if (val <= 9.99) {
224         lenmod = 19;
225     } else if (val <= 99.99) {
226         lenmod = 14;
227     } else if (val <= 999.99) {
228         lenmod = 7.5;
229     } else if (val <= 9999.99) {
230         lenmod = 1.5;
231     } else {
232         lenmod = -5;
233     }
234 
235 
236     rPnt.setBackgroundMode(Qt::OpaqueMode);
237 
238     rPnt.drawText(QWidget::width() - (40 - lenmod), y, QString::number(val, 'f', 2));
239     rPnt.setBackgroundMode(Qt::TransparentMode);
240 
241     qc.setAlphaF(0.35);
242     qp.setColor(qc);
243     qp.setStyle(Qt::DashLine);
244     rPnt.setPen(qp);
245 
246     QPointF l[3] = {
247         QPointF(width(), TY(FindYScreenCoords(*(rCdd.getValues().end() - 1)))),
248         QPointF(width() + (38 + lenmod), y + 2),
249         QPointF(QWidget::width(), y + 2.5),
250     };
251 
252     rPnt.drawPolyline(l, 3);
253 
254     rPnt.setFont(oldfont);
255 }
256 
DrawMaximum(QPainter & rPnt,const ChartDrawerData & rCdd,size_t idx)257 void PlainChartDrawer::DrawMaximum(QPainter& rPnt, const ChartDrawerData& rCdd, size_t idx)
258 {
259     QPen qp = rCdd.getPen();
260     QBrush oldb = qp.brush();
261     QColor qc(qp.color());
262 
263     std::pair<qreal, size_t> max = rCdd.findMax();
264 
265     qc.setAlphaF(0.7);
266     qp.setColor(qc);
267     qp.setStyle(Qt::DashLine);
268     rPnt.setPen(qp);
269     rPnt.drawLine(FindXScreenCoords(max.second), TY(0), FindXScreenCoords(max.second), TY(height()));
270 
271 
272     idx++;
273     wgtunit_t y = 5.0 + (idx * 14);
274     wgtunit_t x = FindXScreenCoords(max.second);
275 
276     if (x < 35) {
277         x += 5;
278     } else {
279         x -= 35;
280     }
281 
282 
283     qc.setAlphaF(1);
284 
285     qp.setColor(qc);
286     rPnt.setPen(qp);
287     qp.setStyle(Qt::SolidLine);
288     rPnt.setBackgroundMode(Qt::OpaqueMode);
289 
290     QFont oldfont(rPnt.font()), qf(oldfont);
291     qf.setStretch(QFont::SemiCondensed);
292     rPnt.setFont(qf);
293 
294     rPnt.drawText(x, y, QString::number(max.first, 'f', 1));
295     rPnt.setFont(oldfont);
296 
297     rPnt.setBackgroundMode(Qt::TransparentMode);
298 }
299 
MakeCtxMenu()300 void PlainChartDrawer::MakeCtxMenu()
301 {
302 
303     connect(pmCtxMenu->addAction(i18nc("@action:inmenu", "Save as image…")),
304     &QAction::triggered, this, [this](bool) {
305         renderToImage();
306     });
307 
308     pmCtxMenu->addSeparator();
309 
310     connect(pmCtxMenu->addAction(i18nc("@action:inmenu Recalculate the 0Y axis and then redraw the chart", "Rescale")),
311     &QAction::triggered, this, [this](bool) {
312         findSetMax();
313     });
314 
315     pmCtxMenu->addSeparator();
316 
317     QAction* rst = pmCtxMenu->addAction(i18nc("@action:inmenu", "Reset"));
318 
319     connect(rst, &QAction::triggered, this, [this](bool) {
320         zeroAll();
321     });
322 }
323 
showContextMenu(const QPoint & pos)324 void PlainChartDrawer::showContextMenu(const QPoint& pos)
325 {
326     pmCtxMenu->exec(mapToGlobal(pos));
327 }
328 
renderToImage()329 void PlainChartDrawer::renderToImage()
330 {
331     QString saveloc = QFileDialog::getSaveFileName(this, i18n("Select path to save image…"), i18n("Image files") + QLatin1String(" (*.png)"));
332     if (!saveloc.length())
333         return;
334 
335     QImage qi(QWidget::width(), QWidget::height(), QImage::Format_RGB32);
336     render(&qi);
337     qi.save(saveloc, "PNG", 0);
338 }
339 
addValue(const size_t idx,const wgtunit_t val,const bool upd)340 void PlainChartDrawer::addValue(const size_t idx, const wgtunit_t val, const bool upd)
341 {
342     if (idx >= pmVals.size()) {
343         return;
344     } else {
345         pmVals[idx].addValue(val);
346     }
347 
348     if (mCurrMaxMode == MM_Top) {
349         if ((val > 1) && (val > mYMax)) {
350             mYMax = val + 5;
351         }
352     } else if (mCurrMaxMode == MM_Exact) {
353         findSetMax();
354     }
355 
356     if (upd) {
357         update();
358     }
359 
360 }
361 
addDataSet(ChartDrawerData Cdd)362 void PlainChartDrawer::addDataSet(ChartDrawerData Cdd)
363 {
364     Cdd.setSize(mXMax);
365     pmVals.push_back(Cdd);
366 
367     setLegend(makeLegendString());
368 }
369 
insertDataSet(const size_t idx,ChartDrawerData Cdd)370 void PlainChartDrawer::insertDataSet(const size_t idx, ChartDrawerData Cdd)
371 {
372     pmVals.insert(pmVals.begin() + idx, Cdd);
373     setLegend(makeLegendString());
374 }
375 
removeDataSet(const size_t idx)376 void PlainChartDrawer::removeDataSet(const size_t idx)
377 {
378     if (idx >= pmVals.size()) {
379         return;
380     } else {
381         pmVals.erase((pmVals.begin()) + idx);
382     }
383 
384     setLegend(makeLegendString());
385 }
386 
387 
zero(const size_t idx)388 void PlainChartDrawer::zero(const size_t idx)
389 {
390     if (idx >= pmVals.size()) {
391         return;
392     } else {
393         pmVals[idx].zero();
394     }
395 
396     findSetMax();
397 }
398 
zeroAll()399 void PlainChartDrawer::zeroAll()
400 {
401     for (size_t idx = 0; idx < pmVals.size(); idx++) {
402         pmVals[idx].zero();
403     }
404 
405     findSetMax();
406 
407     Q_EMIT Zeroed(this);
408 }
409 
setMaxMode(const MaxMode mm)410 void PlainChartDrawer::setMaxMode(const MaxMode mm)
411 {
412     mCurrMaxMode = mm;
413 }
414 
setXMax(const wgtunit_t x)415 void PlainChartDrawer::setXMax(const wgtunit_t x)
416 {
417     mXMax = x;
418 
419     for (size_t i = 0; i < pmVals.size(); i++) {
420         pmVals.at(i).setSize(x);
421     }
422 }
423 
424 
setYMax(const wgtunit_t y)425 void PlainChartDrawer::setYMax(const wgtunit_t y)
426 {
427     mYMax = y;
428 }
429 
setPen(const size_t idx,const QPen & rP)430 void PlainChartDrawer::setPen(const size_t idx, const QPen& rP)
431 {
432     if (idx >= pmVals.size()) {
433         return;
434     }
435 
436     pmVals.at(idx).setPen(rP);
437 
438     makeLegendString();
439 }
440 
getUuid(const size_t idx) const441 QUuid PlainChartDrawer::getUuid(const size_t idx) const
442 {
443     if (idx >= pmVals.size()) {
444         return nullptr;
445     }
446 
447     return pmVals.at(idx).getUuid();
448 }
449 
setUuid(const size_t idx,const QUuid & rU)450 void PlainChartDrawer::setUuid(const size_t idx, const QUuid& rU)
451 {
452     if (idx >= pmVals.size()) {
453         return;
454     }
455 
456     pmVals.at(idx).setUuid(rU);
457 }
458 
findUuidInSet(const QUuid & rU) const459 int16_t PlainChartDrawer::findUuidInSet(const QUuid& rU) const
460 {
461 
462     for (int16_t i = 0; i < static_cast<int16_t>(pmVals.size()); i++) {
463         if (pmVals.at(i).getUuid() == rU) {
464             return i;
465         }
466     }
467 
468     return -1;
469 }
470 
enableAntiAlias(bool aa)471 void PlainChartDrawer::enableAntiAlias(bool aa)
472 {
473     mAntiAlias = aa;
474 }
475 
findSetMax()476 void PlainChartDrawer::findSetMax()
477 {
478     wgtunit_t max = 1;
479 
480     for (size_t i = 0; i < pmVals.size(); i++) {
481         wgtunit_t locval = pmVals.at(i).findMax().first;
482 
483         if (locval > max) {
484             max = locval;
485         }
486     }
487 
488     mYMax = max + 5;
489 }
490 
makeLegendString()491 QString PlainChartDrawer::makeLegendString()
492 {
493     QString lgnd = i18n("<h1 align='center' style='font-size: large; text-decoration: underline'>Legend:</h1><ul type='square'>");
494 
495     for (size_t i = 0; i < pmVals.size(); i++) {
496         lgnd += i18n("<li><span style='background-color: %1; font-size: 14px; font-family: monospace'>&nbsp;&nbsp;</span>&nbsp;—&nbsp;%2</li>",
497                      pmVals.at(i).getPen().color().name(),
498                      pmVals.at(i).getName()
499                     );
500     }
501 
502     return lgnd + QStringLiteral("</ul>");
503 }
504 
setLegend(const QString & rL)505 void PlainChartDrawer::setLegend(const QString& rL)
506 {
507     setToolTip(rL);
508 }
509 
update()510 void PlainChartDrawer::update()
511 {
512     QFrame::update();
513 }
514 
enableBackgroundGrid(bool bg)515 void PlainChartDrawer::enableBackgroundGrid(bool bg)
516 {
517     mBgdGrid = bg;
518 }
519 
520 } //NS end
521 
522