more unfinished changes, process tab for dashboard
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
#include "sparklinecontent.hpp"
|
||||
|
||||
#include <qmath.h>
|
||||
#include <qpainter.h>
|
||||
#include <qpen.h>
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
SparklineContent::SparklineContent(QQuickItem* parent)
|
||||
: QQuickPaintedItem(parent) {
|
||||
setAntialiasing(true);
|
||||
}
|
||||
|
||||
qreal SparklineContent::stepX() const {
|
||||
if (m_historyLength < 2 || m_viewportW <= 0.0) return 0.0;
|
||||
if (m_sliding) return m_viewportW / static_cast<qreal>(m_historyLength - 1);
|
||||
|
||||
const int len = qMax(
|
||||
2,
|
||||
(m_line1 && m_line1->count() >= 2) ? m_line1->count()
|
||||
: (m_line2 ? m_line2->count() : 2));
|
||||
return m_viewportW / static_cast<qreal>(len - 1);
|
||||
}
|
||||
|
||||
void SparklineContent::setViewportSize(
|
||||
qreal viewportWidth, qreal viewportHeight) {
|
||||
if (qFuzzyCompare(m_viewportW, viewportWidth) &&
|
||||
qFuzzyCompare(m_viewportH, viewportHeight))
|
||||
return;
|
||||
m_viewportW = viewportWidth;
|
||||
m_viewportH = viewportHeight;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setLine1(internal::CircularBuffer* buffer) {
|
||||
if (m_line1 == buffer) return;
|
||||
if (m_line1) disconnect(m_line1, nullptr, this, nullptr);
|
||||
m_line1 = buffer;
|
||||
connectBuffer(buffer);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setLine2(internal::CircularBuffer* buffer) {
|
||||
if (m_line2 == buffer) return;
|
||||
if (m_line2) disconnect(m_line2, nullptr, this, nullptr);
|
||||
m_line2 = buffer;
|
||||
connectBuffer(buffer);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::connectBuffer(internal::CircularBuffer* buffer) {
|
||||
if (!buffer) return;
|
||||
// New data, or the buffer's own smoothingRadius changed (it re-emits
|
||||
// valuesChanged in that case too) -> geometry is stale.
|
||||
connect(
|
||||
buffer,
|
||||
&internal::CircularBuffer::valuesChanged,
|
||||
this,
|
||||
&SparklineContent::invalidate);
|
||||
connect(buffer, &QObject::destroyed, this, [this, buffer]() {
|
||||
if (m_line1 == buffer) m_line1 = nullptr;
|
||||
if (m_line2 == buffer) m_line2 = nullptr;
|
||||
invalidate();
|
||||
});
|
||||
}
|
||||
|
||||
void SparklineContent::setLine1Color(const QColor& color) {
|
||||
if (m_line1Color == color) return;
|
||||
m_line1Color = color;
|
||||
update(); // visual-only, no geometry rebuild needed
|
||||
}
|
||||
|
||||
void SparklineContent::setLine2Color(const QColor& color) {
|
||||
if (m_line2Color == color) return;
|
||||
m_line2Color = color;
|
||||
update();
|
||||
}
|
||||
|
||||
void SparklineContent::setLine1FillAlpha(qreal alpha) {
|
||||
if (qFuzzyCompare(m_line1FillAlpha, alpha)) return;
|
||||
m_line1FillAlpha = alpha;
|
||||
update();
|
||||
}
|
||||
|
||||
void SparklineContent::setLine2FillAlpha(qreal alpha) {
|
||||
if (qFuzzyCompare(m_line2FillAlpha, alpha)) return;
|
||||
m_line2FillAlpha = alpha;
|
||||
update();
|
||||
}
|
||||
|
||||
void SparklineContent::setMaxValue(qreal value) {
|
||||
if (qFuzzyCompare(m_maxValue, value)) return;
|
||||
m_maxValue = value;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setLineWidth(qreal width) {
|
||||
if (qFuzzyCompare(m_lineWidth, width)) return;
|
||||
m_lineWidth = width;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setCurveSmoothing(bool enabled) {
|
||||
if (m_curveSmoothing == enabled) return;
|
||||
m_curveSmoothing = enabled;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setHistoryLength(int length) {
|
||||
if (m_historyLength == length) return;
|
||||
m_historyLength = length;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::setSliding(bool sliding) {
|
||||
if (m_sliding == sliding) return;
|
||||
m_sliding = sliding;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SparklineContent::invalidate() {
|
||||
m_dirty = true;
|
||||
applyGeometry();
|
||||
update();
|
||||
}
|
||||
|
||||
void SparklineContent::applyGeometry() {
|
||||
const qreal pad = m_sliding ? stepX() : 0.0;
|
||||
setSize(QSizeF(m_viewportW + pad, m_viewportH));
|
||||
}
|
||||
|
||||
SparklineContent::LineGeometry SparklineContent::buildGeometry(
|
||||
internal::CircularBuffer* buffer) const {
|
||||
LineGeometry geom;
|
||||
if (!buffer || buffer->count() < 2 || m_maxValue <= 0.0 ||
|
||||
m_viewportW <= 0.0)
|
||||
return geom;
|
||||
if (m_historyLength < 2) return geom;
|
||||
|
||||
const int len = buffer->count();
|
||||
const qreal step = stepX();
|
||||
if (step <= 0.0) return geom;
|
||||
|
||||
qreal startX = 0.0;
|
||||
if (m_sliding)
|
||||
startX = m_viewportW - (len - 1) * step + step;
|
||||
else
|
||||
startX = 0.0;
|
||||
|
||||
const qreal h = m_viewportH;
|
||||
const qreal strokePad = qCeil(m_lineWidth / 2);
|
||||
const qreal curvePad = 3.0;
|
||||
const qreal plotTop = strokePad + curvePad;
|
||||
const qreal plotBottom = h - strokePad;
|
||||
const qreal fillBottom = h;
|
||||
const qreal plotH = qMax<qreal>(1.0, plotBottom - plotTop);
|
||||
|
||||
QVector<QPointF> points;
|
||||
points.reserve(len);
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
const qreal value =
|
||||
qBound<qreal>(0.0, buffer->smoothedAt(i), m_maxValue);
|
||||
|
||||
const qreal x = startX + i * step;
|
||||
const qreal y = plotTop + (1.0 - (value / m_maxValue)) * plotH;
|
||||
points.append(QPointF(x, y));
|
||||
}
|
||||
|
||||
QPainterPath linePath;
|
||||
linePath.moveTo(points[0]);
|
||||
|
||||
if (m_curveSmoothing && points.size() > 2) {
|
||||
const qreal tension = 1.0;
|
||||
const qreal k = tension / 6.0;
|
||||
const int n = points.size();
|
||||
|
||||
for (int i = 0; i < n - 1; ++i) {
|
||||
const QPointF& p0 = points[qMax(i - 1, 0)];
|
||||
const QPointF& p1 = points[i];
|
||||
const QPointF& p2 = points[i + 1];
|
||||
const QPointF& p3 = points[qMin(i + 2, n - 1)];
|
||||
|
||||
QPointF c1 = p1 + (p2 - p0) * k;
|
||||
QPointF c2 = p2 - (p3 - p1) * k;
|
||||
|
||||
const qreal segMinY = qMin(p1.y(), p2.y());
|
||||
const qreal segMaxY = qMax(p1.y(), p2.y());
|
||||
c1.setY(qBound(segMinY, c1.y(), segMaxY));
|
||||
c2.setY(qBound(segMinY, c2.y(), segMaxY));
|
||||
|
||||
linePath.cubicTo(c1, c2, p2);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < points.size() - 1; ++i) {
|
||||
const QPointF& p0 = points[i];
|
||||
const QPointF& p1 = points[i + 1];
|
||||
const qreal ctrlX = (p0.x() + p1.x()) * 0.5;
|
||||
linePath.cubicTo(QPointF(ctrlX, p0.y()), QPointF(ctrlX, p1.y()), p1);
|
||||
}
|
||||
}
|
||||
|
||||
geom.stroke = linePath;
|
||||
|
||||
QPainterPath fillPath = linePath;
|
||||
fillPath.lineTo(startX + (len - 1) * step, fillBottom);
|
||||
fillPath.lineTo(startX, fillBottom);
|
||||
fillPath.closeSubpath();
|
||||
geom.fill = fillPath;
|
||||
geom.hasData = true;
|
||||
|
||||
return geom;
|
||||
}
|
||||
|
||||
void SparklineContent::rebuildIfNeeded() {
|
||||
if (!m_dirty) return;
|
||||
m_geom1 = buildGeometry(m_line1);
|
||||
m_geom2 = buildGeometry(m_line2);
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
void SparklineContent::paint(QPainter* painter) {
|
||||
rebuildIfNeeded();
|
||||
if (!m_geom1.hasData && !m_geom2.hasData) return;
|
||||
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
auto drawOne =
|
||||
[&](const LineGeometry& geom, const QColor& color, qreal fillAlpha) {
|
||||
if (!geom.hasData) return;
|
||||
|
||||
QColor fillColor = color;
|
||||
fillColor.setAlphaF(static_cast<float>(fillAlpha));
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(fillColor);
|
||||
painter->drawPath(geom.fill);
|
||||
|
||||
QPen pen(color, m_lineWidth);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setJoinStyle(Qt::RoundJoin);
|
||||
painter->setPen(pen);
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
painter->drawPath(geom.stroke);
|
||||
};
|
||||
|
||||
drawOne(m_geom1, m_line1Color, m_line1FillAlpha);
|
||||
drawOne(m_geom2, m_line2Color, m_line2FillAlpha);
|
||||
}
|
||||
|
||||
} // namespace ZShell::components
|
||||
Reference in New Issue
Block a user