13#include <QPainterPath>
14#include <QQuickWindow>
16#include "RangeGroup.h"
17#include "datasource/ChartDataSource.h"
18#include "scenegraph/LineChartNode.h"
20static const float PixelsPerStep = 2.0;
30 result.
setRedF(result.redF() * opacity);
31 result.setGreenF(result.greenF() * opacity);
32 result.setBlueF(result.blueF() * opacity);
33 result.setAlphaF(opacity);
37LineChartAttached::LineChartAttached(
QObject *parent)
47void LineChartAttached::setValue(
const QVariant &value)
49 if (
value == m_value) {
62void LineChartAttached::setColor(
const QColor &color)
64 if (
color == m_color) {
77void LineChartAttached::setName(
const QString &newName)
79 if (newName == m_name) {
96void LineChartAttached::setShortName(
const QString &newShortName)
98 if (newShortName == m_shortName) {
102 m_shortName = newShortName;
103 Q_EMIT shortNameChanged();
113 return m_interpolate;
123 return m_fillOpacity;
126void LineChart::setInterpolate(
bool newInterpolate)
128 if (newInterpolate == m_interpolate) {
132 m_interpolate = newInterpolate;
134 Q_EMIT interpolateChanged();
137void LineChart::setLineWidth(qreal width)
139 if (qFuzzyCompare(m_lineWidth,
width)) {
145 Q_EMIT lineWidthChanged();
148void LineChart::setFillOpacity(qreal opacity)
150 if (qFuzzyCompare(m_fillOpacity,
opacity)) {
156 Q_EMIT fillOpacityChanged();
161 return m_fillColorSource;
166 if (newFillColorSource == m_fillColorSource) {
170 m_fillColorSource = newFillColorSource;
172 Q_EMIT fillColorSourceChanged();
177 return m_pointDelegate;
180void LineChart::setPointDelegate(
QQmlComponent *newPointDelegate)
182 if (newPointDelegate == m_pointDelegate) {
186 m_pointDelegate = newPointDelegate;
187 for (
auto entry : std::as_const(m_pointDelegates)) {
190 m_pointDelegates.
clear();
192 Q_EMIT pointDelegateChanged();
195void LineChart::updatePolish()
197 if (m_rangeInvalid) {
199 m_rangeInvalid =
false;
206 for (
int i = 0; i < sources.size(); ++i) {
207 auto valueSource = sources.at(i);
209 float stepSize =
width() / (range.distanceX - 1);
211 auto generator = [&, i = range.startX]()
mutable ->
QVector2D {
213 if (range.distanceY != 0) {
214 value = (valueSource->item(i).toFloat() - range.startY) / range.distanceY;
223 std::generate_n(values.begin(), range.distanceX, generator);
225 std::generate_n(values.rbegin(), range.distanceX, generator);
229 if (values.size() != previousValues.
size()) {
230 qWarning() <<
"Value source" << valueSource->objectName()
231 <<
"has a different number of elements from the previous source. Ignoring stacking for this source.";
233 std::for_each(values.begin(), values.end(), [previousValues, i = 0](
QVector2D &point)
mutable {
234 point.setY(point.y() + previousValues.at(i++).y());
238 previousValues = values;
240 if (m_pointDelegate) {
241 auto &delegates = m_pointDelegates[valueSource];
242 if (delegates.size() != values.size()) {
243 qDeleteAll(delegates);
244 createPointDelegates(values, i);
246 for (
int item = 0; item < values.size(); ++item) {
247 auto delegate = delegates.at(item);
248 updatePointDelegate(delegate, values.at(item), valueSource->item(item), i);
254 m_values[valueSource] = interpolatePoints(values,
height());
256 m_values[valueSource] = values;
260 const auto pointKeys = m_pointDelegates.
keys();
261 for (
auto key : pointKeys) {
262 if (!sources.contains(key)) {
263 qDeleteAll(m_pointDelegates[key]);
264 m_pointDelegates.
remove(key);
271QSGNode *LineChart::updatePaintNode(
QSGNode *node, QQuickItem::UpdatePaintNodeData *data)
281 for (
int i = 0; i < sources.size(); ++i) {
282 int childIndex = sources.size() - 1 - i;
286 auto lineNode =
static_cast<LineChartNode *
>(node->
childAtIndex(childIndex));
288 auto fillColor = m_fillColorSource ? m_fillColorSource->item(i).
value<
QColor>() : colorWithAlpha(color, m_fillOpacity);
289 auto lineWidth = i == highlightIndex ? std::max(m_lineWidth, 3.0) : m_lineWidth;
291 if (highlightIndex >= 0 && i != highlightIndex) {
296 updateLineNode(lineNode, sources.at(i), color, fillColor,
lineWidth);
307 if (highlightIndex >= 0) {
321 m_rangeInvalid =
true;
325void LineChart::geometryChange(
const QRectF &newGeometry,
const QRectF &oldGeometry)
328 if (newGeometry != oldGeometry) {
333void LineChart::updateLineNode(LineChartNode *node,
ChartDataSource *valueSource,
const QColor &lineColor,
const QColor &fillColor, qreal lineWidth)
340 node->setLineColor(lineColor);
341 node->setFillColor(fillColor);
344 auto values = m_values.
value(valueSource);
345 node->setValues(values);
347 node->updatePoints();
350void LineChart::createPointDelegates(
const QList<QVector2D> &values,
int sourceIndex)
355 for (
int i = 0; i < values.
size(); ++i) {
358 qWarning() <<
"Delegate creation for point" << i <<
"of value source" << valueSource->
objectName()
359 <<
"failed, make sure pointDelegate is a QQuickItem";
363 delegate->setParent(
this);
364 delegate->setParentItem(
this);
365 updatePointDelegate(delegate, values.
at(i), valueSource->item(i), sourceIndex);
369 delegates.
append(delegate);
372 m_pointDelegates.
insert(valueSource, delegates);
378 delegate->setPosition(pos);
382 if (highlightIndex >= 0) {
383 if (sourceIndex == highlightIndex) {
392 auto attached =
static_cast<LineChartAttached *
>(qmlAttachedPropertiesObject<LineChart>(delegate,
true));
393 attached->setValue(value);
394 attached->setColor(color);
402 if (points.
size() < 2) {
406 auto tangents = calculateTangents(points, height);
413 for (
int i = 0; i < points.
size() - 1; ++i) {
416 auto currentTangent = tangents.at(i);
417 auto nextTangent = tangents.at(i + 1);
419 auto stepCount = int(std::max(1.0f, (
next.x() - current.x()) / PixelsPerStep));
420 auto stepSize = (
next.x() - current.x()) / stepCount;
422 if (stepCount == 1 || qFuzzyIsNull(
next.y() - current.y())) {
428 for (
auto delta = current.x(); delta <
next.x(); delta += stepSize) {
429 auto interpolated = cubicHermite(current, next, delta, currentTangent, nextTangent);
430 interpolated.setY(interpolated.y() / height);
431 result.
append(interpolated);
437 current.setY(current.y() / height);
453 float previousSlope = 0.0;
456 for (
int i = 0; i < points.
size() - 1; ++i) {
457 auto current = points.
at(i);
458 auto next = points.
at(i + 1);
460 previousSlope = slope;
461 slope = (
next.y() * height - current.y() * height) / (
next.x() - current.x());
463 secantSlopes.
append(slope);
467 }
else if (previousSlope * slope < 0.0) {
470 tangents.
append((previousSlope + slope) / 2.0);
475 for (
int i = 0; i < points.
size() - 1; ++i) {
476 auto slope = secantSlopes.
at(i);
478 if (qFuzzyIsNull(slope)) {
480 tangents[i + 1] = 0.0;
484 auto alpha = tangents.
at(i) / slope;
485 auto beta = tangents.
at(i + 1) / slope;
492 tangents[i + 1] = 0.0;
495 auto length = alpha * alpha + beta * beta;
497 auto tau = 3.0 / sqrt(length);
498 tangents[i] = tau * alpha * slope;
499 tangents[i + 1] = tau * beta * slope;
513 const auto delta = second.
x() - first.
x();
514 const auto t = (step - first.
x()) / delta;
518 const auto h00 = 2.0f * std::pow(t, 3.0f) - 3.0f * std::pow(t, 2.0f) + 1.0f;
520 const auto h10 = std::pow(t, 3.0f) - 2.0f * std::pow(t, 2.0f) + t;
522 const auto h01 = -2.0f * std::pow(t, 3.0f) + 3.0f * std::pow(t, 2.0f);
524 const auto h11 = std::pow(t, 3.0f) - std::pow(t, 2.0f);
526 auto result =
QVector2D{step, first.
y() * h00 + delta * mFirst * h10 + second.
y() * h01 + delta * mSecond * h11};
530#include "moc_LineChart.cpp"
Abstract base class for data sources.
QColor desaturate(const QColor &input)
Desaturate and de-emphasise a color.
ChartDataSource * shortNameSource
The data source to use for short names of chart items.
QQmlListProperty< ChartDataSource > valueSources
The data sources providing the data this chart needs to render.
ChartDataSource * nameSource
The data source to use for names of chart items.
ChartDataSource * colorSource
The data source to use for colors of chart items.
int highlight
The index of a value source to highlight.
An attached property that is exposed to point delegates created in line charts.
QVariant value
The value at the current point.
QString shortName
The short name at the current point.
QColor color
The color at the current point.
QString name
The name at the current point.
ChartDataSource * fillColorSource
A data source that supplies color values for the line charts' fill area.
qreal fillOpacity
The opacity of the area below a line.
QQmlComponent * pointDelegate
A delegate that will be placed at each line chart point.
qreal lineWidth
The width of a line in the chart.
bool interpolate
Interpolate the values in the chart so that the lines become smoothed.
void onDataChanged() override
Called when the data of a value source changes.
A base class for Charts that are based on an X/Y grid.
virtual void updateComputedRange()
Re-calculate the chart's range.
Direction direction
Which direction this chart's X axis runs.
bool stacked
Whether the values of each value source should be stacked.
ComputedRange computedRange() const
Get the complete, calculated range for this chart.
@ ZeroAtStart
Zero is at the beginning of the chart, values run from begin to end.
char * toString(const EngineQuery &query)
const QList< QKeySequence > & next()
iterator insert(const Key &key, const T &value)
QList< Key > keys() const const
bool remove(const Key &key)
T value(const Key &key) const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
T qobject_cast(QObject *object)
virtual QObject * beginCreate(QQmlContext *context)
virtual void completeCreate()
QQuickItem(QQuickItem *parent)
virtual QRectF boundingRect() const const
virtual void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
QQuickWindow * window() const const
void appendChildNode(QSGNode *node)
QSGNode * childAtIndex(int i) const const
int childCount() const const
void removeChildNode(QSGNode *node)
bool isEmpty() const const
QTextStream & right(QTextStream &stream)