KDeclarative

plotter.cpp
1 /*
2  This file is part of the KDE project
3 
4  SPDX-FileCopyrightText: 2014 Fredrik Höglund <[email protected]>
5  SPDX-FileCopyrightText: 2014 Marco Martin <[email protected]>
6  SPDX-FileCopyrightText: 2015 David Edmundson <[email protected]>
7 
8  SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10 
11 #include "plotter.h"
12 
13 
14 #include <QOpenGLContext>
15 #include <QOpenGLShaderProgram>
16 
17 #include <QPainterPath>
18 #include <QPolygonF>
19 
20 #include <QVector2D>
21 #include <QMatrix4x4>
22 
23 #include <QSGTexture>
24 #include <QSGSimpleTextureNode>
25 
26 
27 #include <QDebug>
28 
29 #include <math.h>
30 
31 //completely arbitrary
32 static int s_defaultSampleSize = 40;
33 
34 PlotData::PlotData(QObject *parent)
35  : QObject(parent),
36  m_min(std::numeric_limits<qreal>::max()),
37  m_max(std::numeric_limits<qreal>::min()),
38  m_sampleSize(s_defaultSampleSize)
39 {
40  m_values.reserve(s_defaultSampleSize);
41  for (int i = 0; i < s_defaultSampleSize; ++i) {
42  m_values << 0.0;
43  }
44 }
45 
46 void PlotData::setColor(const QColor &color)
47 {
48  if (m_color == color) {
49  return;
50  }
51 
52  m_color = color;
53 
54  Q_EMIT colorChanged();
55 }
56 
57 QColor PlotData::color() const
58 {
59  return m_color;
60 }
61 
62 qreal PlotData::max() const
63 {
64  return m_max;
65 }
66 
67 qreal PlotData::min() const
68 {
69  return m_min;
70 }
71 
72 void PlotData::setSampleSize(int size)
73 {
74  if (m_sampleSize == size) {
75  return;
76  }
77 
78  m_values.reserve(size);
79  if (m_values.size() > size) {
80  const int numberToRemove = (m_values.size() - size);
81  for (int i = 0; i < numberToRemove; ++i) {
82  m_values.removeFirst();
83  }
84  } else if (m_values.size() < size) {
85  const int numberToAdd = (size - m_values.size());
86  for (int i = 0; i < numberToAdd; ++i) {
87  m_values.prepend(0.0);
88  }
89  }
90 
91  m_sampleSize = size;
92 }
93 
95 {
96  return m_label;
97 }
98 
99 void PlotData::setLabel(const QString &label)
100 {
101  if (m_label == label) {
102  return;
103  }
104 
105  m_label = label;
106  Q_EMIT labelChanged();
107 }
108 
109 void PlotData::addSample(qreal value)
110 {
111 
112  //assume at this point we'll have to pop a single time to stay in size
113  if (m_values.size() >= m_sampleSize) {
114  m_values.removeFirst();
115  }
116 
117  m_values.push_back(value);
118 
119  m_max = std::numeric_limits<qreal>::min();
120  m_min = std::numeric_limits<qreal>::max();
121  for (auto v : qAsConst(m_values)) {
122  if (v > m_max) {
123  m_max = v;
124  } else if (v < m_min) {
125  m_min = v;
126  }
127  }
128 
129  Q_EMIT valuesChanged();
130 }
131 
133 {
134  return m_values;
135 }
136 
137 const char *vs_source =
138  "attribute vec4 vertex;\n"
139  "varying float gradient;\n"
140 
141  "uniform mat4 matrix;\n"
142  "uniform float yMin;\n"
143  "uniform float yMax;\n"
144 
145  "void main(void) {\n"
146  " gradient = (vertex.y - yMin) / (yMax - yMin);"
147  " gl_Position = matrix * vertex;\n"
148  "}";
149 
150 const char *fs_source =
151  "uniform vec4 color1;\n"
152  "uniform vec4 color2;\n"
153 
154  "varying float gradient;\n"
155 
156  "void main(void) {\n"
157  " gl_FragColor = mix(color1, color2, gradient);\n"
158  "}";
159 
160 
161 
162 
163 // --------------------------------------------------
164 
165 
166 
167 class PlotTexture : public QSGTexture
168 {
169 public:
170  PlotTexture(QOpenGLContext *ctx);
171  ~PlotTexture() override;
172 
173  void bind() override final;
174  bool hasAlphaChannel() const override final { return true; }
175  bool hasMipmaps() const override final { return false; }
176  int textureId() const override final { return m_texture; }
177  QSize textureSize() const override final { return m_size; }
178 
179  void recreate(const QSize &size);
180  GLuint fbo() const { return m_fbo; }
181 
182 private:
183  GLuint m_texture = 0;
184  GLuint m_fbo = 0;
185  GLenum m_internalFormat;
186  bool m_haveTexStorage;
187  QSize m_size;
188 };
189 
190 PlotTexture::PlotTexture(QOpenGLContext *ctx) : QSGTexture()
191 {
193 
194  if (ctx->isOpenGLES()) {
195  m_haveTexStorage = version >= qMakePair(3, 0) || ctx->hasExtension("GL_EXT_texture_storage");
196  m_internalFormat = m_haveTexStorage ? GL_RGBA8 : GL_RGBA;
197  } else {
198  m_haveTexStorage = version >= qMakePair(4, 2) || ctx->hasExtension("GL_ARB_texture_storage");
199  m_internalFormat = GL_RGBA8;
200  }
201 
202  glGenFramebuffers(1, &m_fbo);
203 }
204 
205 PlotTexture::~PlotTexture()
206 {
207  if (m_texture) {
208  glDeleteTextures(1, &m_texture);
209  }
210 
211  glDeleteFramebuffers(1, &m_fbo);
212 }
213 
214 void PlotTexture::bind()
215 {
216  glBindTexture(GL_TEXTURE_2D, m_texture);
217 }
218 
219 void PlotTexture::recreate(const QSize &size)
220 {
221  if (m_texture) {
222  glDeleteTextures(1, &m_texture);
223  }
224 
225  glGenTextures(1, &m_texture);
226  glBindTexture(GL_TEXTURE_2D, m_texture);
227  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
228  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
229  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
230  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
231  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
232 
233  if (m_haveTexStorage) {
234  glTexStorage2D(GL_TEXTURE_2D, 1, m_internalFormat, size.width(), size.height());
235  } else {
236  glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, size.width(), size.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
237  }
238 
239  glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
240  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0);
241 
242  m_size = size;
243 }
244 
245 class PlotSGNode: public QSGSimpleTextureNode
246 {
247 public:
248  PlotSGNode();
249  void bind() {
250  m_program->bind();
251  }
252  void setMatrix(const QMatrix4x4 &matrix) {
253  m_program->setUniformValue(u_matrix, matrix);
254  }
255  void setColor1(const QColor &color) {
256  m_program->setUniformValue(u_color1, color);
257  }
258  void setColor2(const QColor &color) {
259  m_program->setUniformValue(u_color2, color);
260  }
261  void setYMin(float min) {
262  m_program->setUniformValue(u_yMin, min);
263  }
264  void setYMax(float max) {
265  m_program->setUniformValue(u_yMax, max);
266  }
267  ~PlotSGNode() = default;
268 private:
270  int u_matrix;
271  int u_color1;
272  int u_color2;
273  int u_yMin;
274  int u_yMax;
275 
276 };
277 
278 PlotSGNode::PlotSGNode():
279  m_program(new QOpenGLShaderProgram)
280 {
281  setOwnsTexture(true);
282  m_program->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vs_source);
283  m_program->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fs_source);
284  m_program->bindAttributeLocation("vertex", 0);
285  m_program->link();
286 
287  u_yMin = m_program->uniformLocation("yMin");
288  u_yMax = m_program->uniformLocation("yMax");
289  u_color1 = m_program->uniformLocation("color1");
290  u_color2 = m_program->uniformLocation("color2");
291  u_matrix = m_program->uniformLocation("matrix");
292 }
293 
294 
295 
296 // ----------------------
297 
298 Plotter::Plotter(QQuickItem *parent)
299  : QQuickItem(parent),
300  m_min(0),
301  m_max(0),
302  m_rangeMax(0),
303  m_rangeMin(0),
304  m_sampleSize(s_defaultSampleSize),
305  m_horizontalLineCount(5),
306  m_stacked(true),
307  m_autoRange(true)
308 {
309  setFlag(ItemHasContents);
310  connect(this, &Plotter::windowChanged, this, [this]() {
311  if (m_window) {
312  disconnect(m_window.data(), &QQuickWindow::beforeRendering, this, &Plotter::render);
313  }
314  m_window.clear();
315  //when the window changes, the node gets deleted
316  m_node = nullptr;
317  });
318 }
319 
320 Plotter::~Plotter()
321 {
322 }
323 
324 qreal Plotter::max() const
325 {
326  return m_max;
327 }
328 
329 qreal Plotter::min() const
330 {
331  return m_min;
332 }
333 
334 int Plotter::sampleSize() const
335 {
336  return m_sampleSize;
337 }
338 
339 void Plotter::setSampleSize(int size)
340 {
341  if (m_sampleSize == size) {
342  return;
343  }
344 
345  m_sampleSize = size;
346 
347  m_mutex.lock();
348  for (auto data : qAsConst(m_plotData)) {
349  data->setSampleSize(size);
350  }
351  m_mutex.unlock();
352 
353  update();
354  Q_EMIT sampleSizeChanged();
355 }
356 
357 bool Plotter::isStacked() const
358 {
359  return m_stacked;
360 }
361 
362 void Plotter::setStacked(bool stacked)
363 {
364  if (m_stacked == stacked) {
365  return;
366  }
367 
368  m_stacked = stacked;
369 
370  Q_EMIT stackedChanged();
371  update();
372 }
373 
374 bool Plotter::isAutoRange() const
375 {
376  return m_autoRange;
377 }
378 
379 void Plotter::setAutoRange(bool autoRange)
380 {
381  if (m_autoRange == autoRange) {
382  return;
383  }
384 
385  m_autoRange = autoRange;
386 
387  Q_EMIT autoRangeChanged();
388  normalizeData();
389  update();
390 }
391 
392 qreal Plotter::rangeMax() const
393 {
394  if (m_autoRange) {
395  return m_max;
396  } else {
397  return m_rangeMax;
398  }
399 }
400 
401 void Plotter::setRangeMax(qreal max)
402 {
403  if (m_rangeMax == max) {
404  return;
405  }
406 
407  m_rangeMax = max;
408 
409  Q_EMIT rangeMaxChanged();
410  normalizeData();
411  update();
412 }
413 
414 qreal Plotter::rangeMin() const
415 {
416  if (m_autoRange) {
417  return m_min;
418  } else {
419  return m_rangeMin;
420  }
421 }
422 
423 void Plotter::setRangeMin(qreal min)
424 {
425  if (m_rangeMin == min) {
426  return;
427  }
428 
429  m_rangeMin = min;
430 
431  Q_EMIT rangeMinChanged();
432  normalizeData();
433  update();
434 }
435 
436 void Plotter::setGridColor(const QColor &color)
437 {
438  if (m_gridColor == color) {
439  return;
440  }
441 
442  m_gridColor = color;
443 
444  Q_EMIT gridColorChanged();
445 }
446 
447 QColor Plotter::gridColor() const
448 {
449  return m_gridColor;
450 }
451 
452 int Plotter::horizontalGridLineCount()
453 {
454  return m_horizontalLineCount;
455 }
456 
457 void Plotter::setHorizontalGridLineCount(int count)
458 {
459  if (m_horizontalLineCount == count) {
460  return;
461  }
462 
463  m_horizontalLineCount = count;
464  Q_EMIT horizontalGridLineCountChanged();
465 }
466 
467 
468 void Plotter::addSample(qreal value)
469 {
470  if (m_plotData.count() != 1) {
471  qWarning() << "Must add a new value per data set, pass an array of values instead";
472  return;
473  }
474 
475  addSample(QList<qreal>() << value);
476 }
477 
478 void Plotter::addSample(const QList<qreal> &value)
479 {
480  if (value.count() != m_plotData.count()) {
481  qWarning() << "Must add a new value per data set";
482  return;
483  }
484 
485  int i = 0;
486  m_mutex.lock();
487  for (auto data : qAsConst(m_plotData)) {
488  data->addSample(value.value(i));
489  ++i;
490  }
491  m_mutex.unlock();
492 
493  normalizeData();
494 
495  update();
496 }
497 
498 void Plotter::dataSet_append(QQmlListProperty<PlotData> *list, PlotData *item)
499 {
500  //encase all m_plotData access in a mutex, since rendering is usually done in another thread
501  Plotter *p = static_cast<Plotter *>(list->object);
502  p->m_mutex.lock();
503  p->m_plotData.append(item);
504  p->m_mutex.unlock();
505 }
506 
507 int Plotter::dataSet_count(QQmlListProperty<PlotData> *list)
508 {
509  Plotter *p = static_cast<Plotter *>(list->object);
510  return p->m_plotData.count();
511 }
512 
513 PlotData *Plotter::dataSet_at(QQmlListProperty<PlotData> *list, int index)
514 {
515  Plotter *p = static_cast<Plotter *>(list->object);
516  p->m_mutex.lock();
517  PlotData *d = p->m_plotData.at(index);
518  p->m_mutex.unlock();
519  return d;
520 }
521 
522 void Plotter::dataSet_clear(QQmlListProperty<PlotData> *list)
523 {
524  Plotter *p = static_cast<Plotter *>(list->object);
525 
526  p->m_mutex.lock();
527  p->m_plotData.clear();
528  p->m_mutex.unlock();
529 }
530 
531 
532 QQmlListProperty<PlotData> Plotter::dataSets()
533 {
534  return QQmlListProperty<PlotData>(this, nullptr, Plotter::dataSet_append, Plotter::dataSet_count, Plotter::dataSet_at, Plotter::dataSet_clear);
535 }
536 
537 
538 
539 // Catmull-Rom interpolation
540 QPainterPath Plotter::interpolate(const QVector<qreal> &p, qreal x0, qreal x1) const
541 {
542  QPainterPath path;
543 
544  const QMatrix4x4 matrix( 0, 1, 0, 0,
545  -1/6., 1, 1/6., 0,
546  0, 1/6., 1, -1/6.,
547  0, 0, 1, 0);
548 
549  const qreal xDelta = (x1 - x0) / (p.count() - 3);
550  qreal x = x0 - xDelta;
551 
552  path.moveTo(x0, p[0]);
553 
554  for (int i = 1; i < p.count() - 2; i++) {
555  const QMatrix4x4 points(x, p[i-1], 0, 0,
556  x + xDelta * 1, p[i+0], 0, 0,
557  x + xDelta * 2, p[i+1], 0, 0,
558  x + xDelta * 3, p[i+2], 0, 0);
559 
560  const QMatrix4x4 res = matrix * points;
561 
562  path.cubicTo(res(1, 0), res(1, 1),
563  res(2, 0), res(2, 1),
564  res(3, 0), res(3, 1));
565 
566  x += xDelta;
567  }
568 
569  return path;
570 }
571 
572 void Plotter::render()
573 {
574  if (!window() || !m_node || !m_node->texture()) {
575  return;
576  }
577 
578  GLuint rb;
579 
580  if (m_haveMSAA && m_haveFramebufferBlit) {
581  // Allocate a temporary MSAA renderbuffer
582  glGenRenderbuffers(1, &rb);
583  glBindRenderbuffer(GL_RENDERBUFFER, rb);
584  glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_samples, m_internalFormat, width(), height());
585 
586  // Attach it to the framebuffer object
587  glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
588  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb);
589  } else {
590  // If we don't have MSAA support we render directly into the texture
591  glBindFramebuffer(GL_FRAMEBUFFER, static_cast<PlotTexture*>(m_node->texture())->fbo());
592  }
593 
594  glViewport(0, 0, width(), height());
595 
596  // Clear the color buffer
597  glClearColor(0.0, 0.0, 0.0, 0.0);
598  glClear(GL_COLOR_BUFFER_BIT);
599 
600  // Add horizontal lines
601  qreal lineSpacing = height() / m_horizontalLineCount;
602 
603  QVector<QVector2D> vertices;
604 
605  //don't draw the bottom line that will come later
606  for (int i = 0; i < m_horizontalLineCount; i++) {
607  int lineY = ceil(i * lineSpacing)+1; //floor +1 makes the entry at point 0 on pixel 1
608  vertices << QVector2D(0, lineY) << QVector2D(width(), lineY);
609  }
610  //bottom line
611  vertices << QVector2D(0, height()-1) << QVector2D(width(), height()-1);
612 
613 
614  // Tessellate
615  float min = height();
616  float max = height();
617 
618  QHash<PlotData *, QPair<int, int> > verticesCounts;
619 
620  //encase all m_plotData access in a mutex, since rendering is usually done in another thread
621  m_mutex.lock();
622  int roundedHeight = qRound(height());
623  int roundedWidth = qRound(width());
624 
625  for (auto data : qAsConst(m_plotData)) {
626  // Interpolate the data set
627  const QPainterPath path = interpolate(data->m_normalizedValues, 0, roundedWidth);
628 
629  // Flatten the path
630  const QList<QPolygonF> polygons = path.toSubpathPolygons();
631 
632  for (const QPolygonF &p : polygons) {
633  verticesCounts[data].first = 0;
634  vertices << QVector2D(p.first().x(), roundedHeight);
635 
636  for (int i = 0; i < p.count()-1; i++) {
637  min = qMin<float>(min, roundedHeight - p[i].y());
638  vertices << QVector2D(p[i].x(), roundedHeight - p[i].y());
639  vertices << QVector2D((p[i].x() + p[i+1].x()) / 2.0, roundedHeight);
640  verticesCounts[data].first += 2;
641  }
642 
643  min = qMin<float>(min, roundedHeight - p.last().y());
644  vertices << QVector2D(p.last().x(), roundedHeight - p.last().y());
645  vertices << QVector2D(p.last().x(), roundedHeight);
646  verticesCounts[data].first += 3;
647  }
648 
649  for (const QPolygonF &p : polygons) {
650  verticesCounts[data].second = 0;
651 
652  for (int i = 0; i < p.count()-1; i++) {
653  min = qMin<float>(min, roundedHeight - p[i].y());
654  vertices << QVector2D(p[i].x(), roundedHeight - p[i].y());
655  verticesCounts[data].second += 1;
656  }
657 
658  vertices << QVector2D(p.last().x(), roundedHeight - p.last().y());
659  verticesCounts[data].second += 1;
660  min = qMin<float>(min, roundedHeight - p.last().y());
661  }
662  }
663  m_mutex.unlock();
664 
665  // Upload vertices
666  GLuint vbo;
667  glGenBuffers(1, &vbo);
668 
669  glBindBuffer(GL_ARRAY_BUFFER, vbo);
670  glBufferData(GL_ARRAY_BUFFER, vertices.count() * sizeof(QVector2D), vertices.constData(), GL_STATIC_DRAW);
671 
672  // Set up the array
673  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(QVector2D), nullptr);
674  glEnableVertexAttribArray(0);
675 
676  // Bind the shader program
677  m_node->bind();
678  m_node->setMatrix(m_matrix);
679 
680  // Draw the lines
681  QColor color1 = m_gridColor;
682  QColor color2 = m_gridColor;
683  color1.setAlphaF(0.10);
684  color2.setAlphaF(0.40);
685  m_node->setYMin((float) 0.0);
686  m_node->setYMax((float) height());
687  m_node->setColor1(color1);
688  m_node->setColor2(color2);
689 
690  glDrawArrays(GL_LINES, 0, (m_horizontalLineCount+1) * 2 );
691 
692  // Enable alpha blending
693  glEnable(GL_BLEND);
694  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
695 
696  QPair<int, int> oldCount;
697  m_mutex.lock();
698  for (auto data : qAsConst(m_plotData)) {
699  color2 = data->color();
700  color2.setAlphaF(0.60);
701  // Draw the graph
702  m_node->setYMin(min);
703  m_node->setYMax(max);
704  m_node->setColor1(data->color());
705  m_node->setColor2(color2);
706 
707  //+2 is for the bottom line
708  glDrawArrays(GL_TRIANGLE_STRIP, m_horizontalLineCount*2 + 2 + oldCount.first + oldCount.second, verticesCounts[data].first);
709 
710  oldCount.first += verticesCounts[data].first;
711 
712  m_node->setColor1(data->color());
713  m_node->setColor2(data->color());
714  glDrawArrays(GL_LINE_STRIP, m_horizontalLineCount*2 + 2 + oldCount.first + oldCount.second, verticesCounts[data].second);
715 
716  oldCount.second += verticesCounts[data].second;
717  }
718  m_mutex.unlock();
719 
720  glDisable(GL_BLEND);
721 
722  m_node->setColor1(m_gridColor);
723  m_node->setColor2(m_gridColor);
724  glDrawArrays(GL_LINES, vertices.count()-2, 2);
725 
726  if (m_haveMSAA && m_haveFramebufferBlit) {
727  // Resolve the MSAA buffer
728  glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
729  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, static_cast<PlotTexture*>(m_node->texture())->fbo());
730  glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
731 
732  // Delete the render buffer
733  glDeleteRenderbuffers(1, &rb);
734  }
735 
736  // Delete the VBO
737  glDeleteBuffers(1, &vbo);
738 }
739 
740 QSGNode *Plotter::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
741 {
742  Q_UNUSED(updatePaintNodeData)
743  if (!window()->openglContext()) {
744  delete oldNode;
745  return nullptr;
746  }
747 
748  PlotSGNode *n = static_cast<PlotSGNode *>(oldNode);
749 
750  if (width() == 0 && height() == 0) {
751  delete oldNode;
752  return nullptr;
753  }
754 
755  if (!n) {
756  n = new PlotSGNode();
757  n->setTexture(new PlotTexture(window()->openglContext()));
758  n->setFiltering(QSGTexture::Linear);
759 
760  m_node = n;
761  if (m_window) {
762  disconnect(m_window.data(), &QQuickWindow::beforeRendering, this, &Plotter::render);
763  }
765  m_window = window();
766  }
767 
768  if (!m_initialized) {
769  glGenFramebuffers(1, &m_fbo);
770 
771  QOpenGLContext *ctx = window()->openglContext();
772  QPair<int, int> version = ctx->format().version();
773 
774  if (ctx->isOpenGLES()) {
775  m_haveMSAA = version >= qMakePair(3, 0) || ctx->hasExtension("GL_NV_framebuffer_multisample");
776  m_haveFramebufferBlit = version >= qMakePair(3, 0) || ctx->hasExtension("GL_NV_framebuffer_blit");
777  m_haveInternalFormatQuery = version >= qMakePair(3, 0);
778  m_internalFormat = version >= qMakePair(3, 0) ? GL_RGBA8 : GL_RGBA;
779  } else {
780  m_haveMSAA = version >= qMakePair(3, 2) || ctx->hasExtension("GL_ARB_framebuffer_object") ||
781  ctx->hasExtension("GL_EXT_framebuffer_multisample");
782  m_haveFramebufferBlit = version >= qMakePair(3, 0) || ctx->hasExtension("GL_ARB_framebuffer_object") ||
783  ctx->hasExtension("GL_EXT_framebuffer_blit");
784  m_haveInternalFormatQuery = version >= qMakePair(4, 2) || ctx->hasExtension("GL_ARB_internalformat_query");
785  m_internalFormat = GL_RGBA8;
786  }
787 
788  // Query the maximum sample count for the internal format
789  if (m_haveInternalFormatQuery) {
790  int count = 0;
791  glGetInternalformativ(GL_RENDERBUFFER, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &count);
792 
793  if (count > 0) {
794  QVector<int> samples(count);
795  glGetInternalformativ(GL_RENDERBUFFER, m_internalFormat, GL_SAMPLES, count, samples.data());
796 
797  // The samples are returned in descending order. Choose the highest value.
798  m_samples = samples.at(0);
799  } else {
800  m_samples = 0;
801  }
802  } else if (m_haveMSAA) {
803  glGetIntegerv(GL_MAX_SAMPLES, &m_samples);
804  } else {
805  m_samples = 0;
806  }
807 
808  m_initialized = true;
809  }
810 
811  //we need a size always equal or smaller, size.toSize() won't do
812  const QSize targetTextureSize(qRound(boundingRect().size().width()), qRound(boundingRect().size().height()));
813  if (n->texture()->textureSize() != targetTextureSize) {
814  static_cast<PlotTexture *>(n->texture())->recreate(targetTextureSize);
815  m_matrix = QMatrix4x4();
816  m_matrix.ortho(0, targetTextureSize.width(), 0, targetTextureSize.height(), -1, 1);
817  }
818 
819  n->setRect(QRect(QPoint(0,0), targetTextureSize));
820  return n;
821 }
822 
823 void Plotter::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
824 {
825  QQuickItem::geometryChanged(newGeometry, oldGeometry);
826  normalizeData();
827 }
828 
829 void Plotter::normalizeData()
830 {
831  if (m_plotData.isEmpty()) {
832  return;
833  }
834  //normalize data
835  m_max = std::numeric_limits<qreal>::min();
836  m_min = std::numeric_limits<qreal>::max();
837  qreal adjustedMax = m_max;
838  qreal adjustedMin = m_min;
839  m_mutex.lock();
840  if (m_stacked) {
841  PlotData *previousData = nullptr;
842  auto i = m_plotData.constEnd();
843  do {
844  --i;
845  PlotData *data = *i;
846  data->m_normalizedValues.clear();
847  data->m_normalizedValues.resize(data->values().count());
848  if (previousData) {
849  for (int i = 0; i < data->values().count(); ++i) {
850  data->m_normalizedValues[i] = data->values().value(i) + previousData->m_normalizedValues.value(i);
851 
852  if (data->m_normalizedValues[i] > adjustedMax) {
853  adjustedMax = data->m_normalizedValues[i];
854  }
855  if (data->m_normalizedValues[i] < adjustedMin) {
856  adjustedMin = data->m_normalizedValues[i];
857  }
858  }
859  } else {
860  data->m_normalizedValues = data->values().toVector();
861  if (data->max() > adjustedMax) {
862  adjustedMax = data->max();
863  }
864  if (data->min() < adjustedMin) {
865  adjustedMin = data->min();
866  }
867  }
868  previousData = data;
869 
870  //global max and global min
871  if (data->max() > m_max) {
872  m_max = data->max();
873  }
874  if (data->min() < m_min) {
875  m_min = data->min();
876  }
877  } while (i != m_plotData.constBegin());
878 
879  } else {
880  for (auto data : qAsConst(m_plotData)) {
881  data->m_normalizedValues.clear();
882  data->m_normalizedValues = data->values().toVector();
883  //global max and global min
884  if (data->max() > m_max) {
885  adjustedMax = m_max = data->max();
886  }
887  if (data->min() < m_min) {
888  adjustedMin = m_min = data->min();
889  }
890  }
891  }
892  m_mutex.unlock();
893 
894  if (adjustedMin > 0.0 && adjustedMax > 0.0)
895  adjustedMin = 0.0;
896 
897  if (adjustedMin < 0.0 && adjustedMax < 0.0)
898  adjustedMax = 0.0;
899 
900  if (m_autoRange || m_rangeMax > m_rangeMin) {
901  if (!m_autoRange) {
902  adjustedMax = m_rangeMax;
903  adjustedMin = m_rangeMin;
904  }
905 
906  qreal adjust;
907  //this should never happen, remove?
908  if (qFuzzyCompare(adjustedMax - adjustedMin, std::numeric_limits<qreal>::min())) {
909  adjust = 1;
910  } else {
911  adjust = (height() / (adjustedMax - adjustedMin));
912  }
913 
914  //normalizebased on global max and min
915  m_mutex.lock();
916  for (auto data : qAsConst(m_plotData)) {
917  for (int i = 0; i < data->values().count(); ++i) {
918  data->m_normalizedValues[i] = (data->m_normalizedValues.value(i) - adjustedMin) * adjust;
919  }
920  }
921  m_mutex.unlock();
922  }
923 }
924 
int width() const const
KJOBWIDGETS_EXPORT QWidget * window(KJob *job)
QSurfaceFormat format() const const
T & last()
void cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &endPoint)
void moveTo(const QPointF &point)
QVector< T > toVector() const const
T & first()
virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void beforeRendering()
T value(int i) const const
T value(int i) const const
void clear()
int count(const T &value) const const
bool hasExtension(const QByteArray &extension) const const
void resize(int size)
QString label
text Label of the data set: note this is purely a model, it will need a Label somewhere to be actuall...
Definition: plotter.h:49
bool isOpenGLES() const const
qreal min
Minimum value of this data set.
Definition: plotter.h:69
const T * constData() const const
a Plotter can draw a graph of values arriving from an arbitrary number of data sources to show their ...
Definition: plotter.h:43
virtual QSGNode * updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData)
QColor color
Color to plot this data set.
Definition: plotter.h:54
QPair< int, int > version() const const
int height() const const
int count(const T &value) const const
void update(Part *part, const QByteArray &data, qint64 dataSize)
void setAlphaF(qreal alpha)
DirectConnection
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QList< QPolygonF > toSubpathPolygons(const QMatrix &matrix) const const
Q_EMITQ_EMIT
qreal max
Maximum value of this data set.
Definition: plotter.h:64
QList< qreal > values
All the values currently in this data set.
Definition: plotter.h:59
KDB_EXPORT KDbVersionInfo version()
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Jan 25 2021 22:44:28 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.