14 #include <QOpenGLContext> 15 #include <QOpenGLShaderProgram> 17 #include <QPainterPath> 24 #include <QSGSimpleTextureNode> 32 static int s_defaultSampleSize = 40;
34 PlotData::PlotData(
QObject *parent)
36 m_min(
std::numeric_limits<qreal>::max()),
37 m_max(
std::numeric_limits<qreal>::min()),
38 m_sampleSize(s_defaultSampleSize)
40 m_values.reserve(s_defaultSampleSize);
41 for (
int i = 0; i < s_defaultSampleSize; ++i) {
46 void PlotData::setColor(
const QColor &color)
48 if (m_color == color) {
72 void PlotData::setSampleSize(
int size)
74 if (m_sampleSize == size) {
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();
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);
99 void PlotData::setLabel(
const QString &label)
101 if (m_label == label) {
109 void PlotData::addSample(qreal value)
113 if (m_values.size() >= m_sampleSize) {
114 m_values.removeFirst();
117 m_values.push_back(value);
119 m_max = std::numeric_limits<qreal>::min();
120 m_min = std::numeric_limits<qreal>::max();
121 for (
auto v : qAsConst(m_values)) {
124 }
else if (v < m_min) {
137 const char *vs_source =
138 "attribute vec4 vertex;\n" 139 "varying float gradient;\n" 141 "uniform mat4 matrix;\n" 142 "uniform float yMin;\n" 143 "uniform float yMax;\n" 145 "void main(void) {\n" 146 " gradient = (vertex.y - yMin) / (yMax - yMin);" 147 " gl_Position = matrix * vertex;\n" 150 const char *fs_source =
151 "uniform vec4 color1;\n" 152 "uniform vec4 color2;\n" 154 "varying float gradient;\n" 156 "void main(void) {\n" 157 " gl_FragColor = mix(color1, color2, gradient);\n" 171 ~PlotTexture()
override;
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; }
179 void recreate(
const QSize &size);
180 GLuint fbo()
const {
return m_fbo; }
183 GLuint m_texture = 0;
185 GLenum m_internalFormat;
186 bool m_haveTexStorage;
195 m_haveTexStorage = version >= qMakePair(3, 0) || ctx->
hasExtension(
"GL_EXT_texture_storage");
196 m_internalFormat = m_haveTexStorage ? GL_RGBA8 : GL_RGBA;
198 m_haveTexStorage = version >= qMakePair(4, 2) || ctx->
hasExtension(
"GL_ARB_texture_storage");
199 m_internalFormat = GL_RGBA8;
202 glGenFramebuffers(1, &m_fbo);
205 PlotTexture::~PlotTexture()
208 glDeleteTextures(1, &m_texture);
211 glDeleteFramebuffers(1, &m_fbo);
214 void PlotTexture::bind()
216 glBindTexture(GL_TEXTURE_2D, m_texture);
219 void PlotTexture::recreate(
const QSize &size)
222 glDeleteTextures(1, &m_texture);
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);
233 if (m_haveTexStorage) {
234 glTexStorage2D(GL_TEXTURE_2D, 1, m_internalFormat, size.
width(), size.
height());
236 glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, size.
width(), size.
height(), 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
239 glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
240 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0);
253 m_program->setUniformValue(u_matrix, matrix);
255 void setColor1(
const QColor &color) {
256 m_program->setUniformValue(u_color1, color);
258 void setColor2(
const QColor &color) {
259 m_program->setUniformValue(u_color2, color);
261 void setYMin(
float min) {
262 m_program->setUniformValue(u_yMin, min);
264 void setYMax(
float max) {
265 m_program->setUniformValue(u_yMax, max);
267 ~PlotSGNode() =
default;
278 PlotSGNode::PlotSGNode():
281 setOwnsTexture(
true);
284 m_program->bindAttributeLocation(
"vertex", 0);
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");
304 m_sampleSize(s_defaultSampleSize),
305 m_horizontalLineCount(5),
309 setFlag(ItemHasContents);
310 connect(
this, &Plotter::windowChanged,
this, [
this]() {
324 qreal Plotter::max()
const 329 qreal Plotter::min()
const 334 int Plotter::sampleSize()
const 339 void Plotter::setSampleSize(
int size)
341 if (m_sampleSize == size) {
348 for (
auto data : qAsConst(m_plotData)) {
349 data->setSampleSize(size);
354 Q_EMIT sampleSizeChanged();
357 bool Plotter::isStacked()
const 362 void Plotter::setStacked(
bool stacked)
364 if (m_stacked == stacked) {
374 bool Plotter::isAutoRange()
const 379 void Plotter::setAutoRange(
bool autoRange)
381 if (m_autoRange == autoRange) {
385 m_autoRange = autoRange;
387 Q_EMIT autoRangeChanged();
392 qreal Plotter::rangeMax()
const 401 void Plotter::setRangeMax(qreal max)
403 if (m_rangeMax == max) {
414 qreal Plotter::rangeMin()
const 423 void Plotter::setRangeMin(qreal min)
425 if (m_rangeMin == min) {
436 void Plotter::setGridColor(
const QColor &color)
438 if (m_gridColor == color) {
444 Q_EMIT gridColorChanged();
447 QColor Plotter::gridColor()
const 452 int Plotter::horizontalGridLineCount()
454 return m_horizontalLineCount;
457 void Plotter::setHorizontalGridLineCount(
int count)
459 if (m_horizontalLineCount == count) {
463 m_horizontalLineCount = count;
464 Q_EMIT horizontalGridLineCountChanged();
468 void Plotter::addSample(qreal value)
470 if (m_plotData.count() != 1) {
471 qWarning() <<
"Must add a new value per data set, pass an array of values instead";
480 if (value.
count() != m_plotData.count()) {
481 qWarning() <<
"Must add a new value per data set";
487 for (
auto data : qAsConst(m_plotData)) {
488 data->addSample(value.
value(i));
501 Plotter *p =
static_cast<Plotter *
>(list->object);
503 p->m_plotData.append(item);
509 Plotter *p =
static_cast<Plotter *
>(list->object);
510 return p->m_plotData.count();
515 Plotter *p =
static_cast<Plotter *
>(list->object);
517 PlotData *d = p->m_plotData.at(index);
524 Plotter *p =
static_cast<Plotter *
>(list->object);
527 p->m_plotData.clear();
534 return QQmlListProperty<PlotData>(
this,
nullptr, Plotter::dataSet_append, Plotter::dataSet_count, Plotter::dataSet_at, Plotter::dataSet_clear);
549 const qreal xDelta = (x1 - x0) / (p.
count() - 3);
550 qreal x = x0 - xDelta;
554 for (
int i = 1; i < p.
count() - 2; i++) {
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);
562 path.
cubicTo(res(1, 0), res(1, 1),
563 res(2, 0), res(2, 1),
564 res(3, 0), res(3, 1));
572 void Plotter::render()
574 if (!
window() || !m_node || !m_node->texture()) {
580 if (m_haveMSAA && m_haveFramebufferBlit) {
582 glGenRenderbuffers(1, &rb);
583 glBindRenderbuffer(GL_RENDERBUFFER, rb);
584 glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_samples, m_internalFormat, width(), height());
587 glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
588 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb);
591 glBindFramebuffer(GL_FRAMEBUFFER, static_cast<PlotTexture*>(m_node->texture())->fbo());
594 glViewport(0, 0, width(), height());
597 glClearColor(0.0, 0.0, 0.0, 0.0);
598 glClear(GL_COLOR_BUFFER_BIT);
601 qreal lineSpacing = height() / m_horizontalLineCount;
606 for (
int i = 0; i < m_horizontalLineCount; i++) {
607 int lineY = ceil(i * lineSpacing)+1;
615 float min = height();
616 float max = height();
622 int roundedHeight = qRound(height());
623 int roundedWidth = qRound(width());
625 for (
auto data : qAsConst(m_plotData)) {
627 const QPainterPath path = interpolate(data->m_normalizedValues, 0, roundedWidth);
633 verticesCounts[data].first = 0;
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;
643 min = qMin<float>(min, roundedHeight - p.
last().y());
646 verticesCounts[data].first += 3;
650 verticesCounts[data].second = 0;
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;
659 verticesCounts[data].second += 1;
660 min = qMin<float>(min, roundedHeight - p.
last().y());
667 glGenBuffers(1, &vbo);
669 glBindBuffer(GL_ARRAY_BUFFER, vbo);
673 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
sizeof(
QVector2D),
nullptr);
674 glEnableVertexAttribArray(0);
678 m_node->setMatrix(m_matrix);
682 QColor color2 = m_gridColor;
685 m_node->setYMin((
float) 0.0);
686 m_node->setYMax((
float) height());
687 m_node->setColor1(color1);
688 m_node->setColor2(color2);
690 glDrawArrays(GL_LINES, 0, (m_horizontalLineCount+1) * 2 );
694 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
698 for (
auto data : qAsConst(m_plotData)) {
699 color2 = data->color();
702 m_node->setYMin(min);
703 m_node->setYMax(max);
704 m_node->setColor1(data->color());
705 m_node->setColor2(color2);
708 glDrawArrays(GL_TRIANGLE_STRIP, m_horizontalLineCount*2 + 2 + oldCount.first + oldCount.second, verticesCounts[data].first);
710 oldCount.first += verticesCounts[data].first;
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);
716 oldCount.second += verticesCounts[data].second;
722 m_node->setColor1(m_gridColor);
723 m_node->setColor2(m_gridColor);
724 glDrawArrays(GL_LINES, vertices.
count()-2, 2);
726 if (m_haveMSAA && m_haveFramebufferBlit) {
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);
733 glDeleteRenderbuffers(1, &rb);
737 glDeleteBuffers(1, &vbo);
742 Q_UNUSED(updatePaintNodeData)
743 if (!window()->openglContext()) {
748 PlotSGNode *n =
static_cast<PlotSGNode *
>(oldNode);
750 if (width() == 0 && height() == 0) {
756 n =
new PlotSGNode();
757 n->setTexture(
new PlotTexture(
window()->openglContext()));
768 if (!m_initialized) {
769 glGenFramebuffers(1, &m_fbo);
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;
780 m_haveMSAA = version >= qMakePair(3, 2) || ctx->
hasExtension(
"GL_ARB_framebuffer_object") ||
782 m_haveFramebufferBlit = version >= qMakePair(3, 0) || ctx->
hasExtension(
"GL_ARB_framebuffer_object") ||
784 m_haveInternalFormatQuery = version >= qMakePair(4, 2) || ctx->
hasExtension(
"GL_ARB_internalformat_query");
785 m_internalFormat = GL_RGBA8;
789 if (m_haveInternalFormatQuery) {
791 glGetInternalformativ(GL_RENDERBUFFER, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &count);
795 glGetInternalformativ(GL_RENDERBUFFER, m_internalFormat, GL_SAMPLES, count, samples.data());
798 m_samples = samples.at(0);
802 }
else if (m_haveMSAA) {
803 glGetIntegerv(GL_MAX_SAMPLES, &m_samples);
808 m_initialized =
true;
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);
816 m_matrix.ortho(0, targetTextureSize.width(), 0, targetTextureSize.height(), -1, 1);
823 void Plotter::geometryChanged(
const QRectF &newGeometry,
const QRectF &oldGeometry)
829 void Plotter::normalizeData()
831 if (m_plotData.isEmpty()) {
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;
842 auto i = m_plotData.constEnd();
846 data->m_normalizedValues.
clear();
850 data->m_normalizedValues[i] = data->
values().
value(i) + previousData->m_normalizedValues.
value(i);
852 if (data->m_normalizedValues[i] > adjustedMax) {
853 adjustedMax = data->m_normalizedValues[i];
855 if (data->m_normalizedValues[i] < adjustedMin) {
856 adjustedMin = data->m_normalizedValues[i];
861 if (data->
max() > adjustedMax) {
862 adjustedMax = data->
max();
864 if (data->
min() < adjustedMin) {
865 adjustedMin = data->
min();
871 if (data->
max() > m_max) {
874 if (data->
min() < m_min) {
877 }
while (i != m_plotData.constBegin());
880 for (
auto data : qAsConst(m_plotData)) {
881 data->m_normalizedValues.
clear();
884 if (data->
max() > m_max) {
885 adjustedMax = m_max = data->
max();
887 if (data->
min() < m_min) {
888 adjustedMin = m_min = data->
min();
894 if (adjustedMin > 0.0 && adjustedMax > 0.0)
897 if (adjustedMin < 0.0 && adjustedMax < 0.0)
900 if (m_autoRange || m_rangeMax > m_rangeMin) {
902 adjustedMax = m_rangeMax;
903 adjustedMin = m_rangeMin;
908 if (qFuzzyCompare(adjustedMax - adjustedMin, std::numeric_limits<qreal>::min())) {
911 adjust = (height() / (adjustedMax - adjustedMin));
916 for (
auto data : qAsConst(m_plotData)) {
918 data->m_normalizedValues[i] = (data->m_normalizedValues.
value(i) - adjustedMin) * adjust;
QSurfaceFormat format() const const
void cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &endPoint)
void moveTo(const QPointF &point)
QVector< T > toVector() const const
virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
T value(int i) const const
T value(int i) const const
int count(const T &value) const const
bool hasExtension(const QByteArray &extension) const const
QString label
text Label of the data set: note this is purely a model, it will need a Label somewhere to be actuall...
bool isOpenGLES() const const
qreal min
Minimum value of this data set.
const T * constData() const const
a Plotter can draw a graph of values arriving from an arbitrary number of data sources to show their ...
virtual QSGNode * updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData)
QColor color
Color to plot this data set.
int count(const T &value) const const
void update(Part *part, const QByteArray &data, qint64 dataSize)
void setAlphaF(qreal alpha)
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
qreal max
Maximum value of this data set.
QList< qreal > values
All the values currently in this data set.
KDB_EXPORT KDbVersionInfo version()