Hello community, here is the log from the commit of package kquickcharts for openSUSE:Factory checked in at 2020-12-15 12:29:20 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kquickcharts (Old) and /work/SRC/openSUSE:Factory/.kquickcharts.new.2328 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kquickcharts" Tue Dec 15 12:29:20 2020 rev:13 rq:855456 version:5.77.0 Changes: -------- --- /work/SRC/openSUSE:Factory/kquickcharts/kquickcharts.changes 2020-11-23 10:32:58.209524372 +0100 +++ /work/SRC/openSUSE:Factory/.kquickcharts.new.2328/kquickcharts.changes 2020-12-15 12:31:36.788059708 +0100 @@ -1,0 +2,9 @@ +Sat Dec 5 18:56:54 UTC 2020 - Christophe Giboudeaux <[email protected]> + +- Update to 5.77.0 + * New feature release + * For more details please see: + * https://kde.org/announcements/kde-frameworks-5.77.0 +- Too many changes to list here. + +------------------------------------------------------------------- Old: ---- kquickcharts-5.76.0.tar.xz kquickcharts-5.76.0.tar.xz.sig New: ---- kquickcharts-5.77.0.tar.xz kquickcharts-5.77.0.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kquickcharts.spec ++++++ --- /var/tmp/diff_new_pack.LImXFa/_old 2020-12-15 12:31:37.472060259 +0100 +++ /var/tmp/diff_new_pack.LImXFa/_new 2020-12-15 12:31:37.476060262 +0100 @@ -16,14 +16,14 @@ # -%define _tar_path 5.76 +%define _tar_path 5.77 # Full KF5 version (e.g. 5.33.0) %{!?_kf5_version: %global _kf5_version %{version}} # Last major and minor KF5 version (e.g. 5.33) %{!?_kf5_bugfix_version: %define _kf5_bugfix_version %(echo %{_kf5_version} | awk -F. '{print $1"."$2}')} %bcond_without lang Name: kquickcharts -Version: 5.76.0 +Version: 5.77.0 Release: 0 Summary: Set of charts for QtQuick applications License: LGPL-2.1-or-later @@ -36,9 +36,9 @@ %endif BuildRequires: extra-cmake-modules >= %{_kf5_bugfix_version} BuildRequires: kf5-filesystem -BuildRequires: cmake(Qt5Qml) >= 5.12.0 -BuildRequires: cmake(Qt5Quick) >= 5.12.0 -BuildRequires: cmake(Qt5QuickControls2) >= 5.12.0 +BuildRequires: cmake(Qt5Qml) >= 5.13.0 +BuildRequires: cmake(Qt5Quick) >= 5.13.0 +BuildRequires: cmake(Qt5QuickControls2) >= 5.13.0 Requires: kirigami2 Requires: libqt5-qtquickcontrols2 ++++++ kquickcharts-5.76.0.tar.xz -> kquickcharts-5.77.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kquickcharts-5.76.0/CMakeLists.txt new/kquickcharts-5.77.0/CMakeLists.txt --- old/kquickcharts-5.76.0/CMakeLists.txt 2020-11-07 12:42:59.000000000 +0100 +++ new/kquickcharts-5.77.0/CMakeLists.txt 2020-12-05 11:13:26.000000000 +0100 @@ -1,12 +1,12 @@ cmake_minimum_required(VERSION 3.5) -set(KF5_VERSION "5.76.0") # handled by release scripts -set(KF5_DEP_VERSION "5.76.0") # handled by release scripts +set(KF5_VERSION "5.77.0") # handled by release scripts +set(KF5_DEP_VERSION "5.77.0") # handled by release scripts project(KQuickCharts VERSION ${KF5_VERSION}) include(FeatureSummary) -find_package(ECM 5.76.0 NO_MODULE) +find_package(ECM 5.77.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) @@ -27,7 +27,7 @@ option(BUILD_EXAMPLES "Build example applications" OFF) -set(REQUIRED_QT_VERSION 5.12.0) +set(REQUIRED_QT_VERSION 5.13.0) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Qml Quick QuickControls2) add_subdirectory(controls) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kquickcharts-5.76.0/controls/LineChartControl.qml new/kquickcharts-5.77.0/controls/LineChartControl.qml --- old/kquickcharts-5.76.0/controls/LineChartControl.qml 2020-11-07 12:42:59.000000000 +0100 +++ new/kquickcharts-5.77.0/controls/LineChartControl.qml 2020-12-05 11:13:26.000000000 +0100 @@ -38,6 +38,8 @@ property alias xAxisSource: xAxisLabels.source property alias yAxisSource: yAxisLabels.source + property alias pointDelegate: lineChart.pointDelegate + background: Rectangle { color: Theme.backgroundColor } contentItem: Item { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kquickcharts-5.76.0/examples/charts/LineChart.qml new/kquickcharts-5.77.0/examples/charts/LineChart.qml --- old/kquickcharts-5.76.0/examples/charts/LineChart.qml 2020-11-07 12:42:59.000000000 +0100 +++ new/kquickcharts-5.77.0/examples/charts/LineChart.qml 2020-12-05 11:13:26.000000000 +0100 @@ -53,6 +53,25 @@ ] names: ["Example 1", "Example 2", "Example 3"] + + pointDelegate: Item { + Rectangle { + anchors.centerIn: parent + width: lineChart.lineWidth + Kirigami.Units.smallSpacing; + height: width + radius: width / 2; + color: parent.Charts.LineChart.color + + MouseArea { + id: mouse + anchors.fill: parent + hoverEnabled: true + } + + ToolTip.visible: mouse.containsMouse + ToolTip.text: "%1: %2".arg(parent.Charts.LineChart.name).arg(parent.Charts.LineChart.value) + } + } } } @@ -67,6 +86,7 @@ Label { text: "Fill Opacity" } SpinBox { from: 0; to: 100; value: lineChart.fillOpacity * 100; onValueModified: lineChart.fillOpacity = value / 100; } CheckBox { text: "Stacked"; checked: lineChart.stacked; onToggled: lineChart.stacked = checked } + CheckBox { text: "Smooth"; checked: lineChart.chart.smooth; onToggled: lineChart.chart.smooth = checked } } Frame { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kquickcharts-5.76.0/src/LineChart.cpp new/kquickcharts-5.77.0/src/LineChart.cpp --- old/kquickcharts-5.76.0/src/LineChart.cpp 2020-11-07 12:42:59.000000000 +0100 +++ new/kquickcharts-5.77.0/src/LineChart.cpp 2020-12-05 11:13:26.000000000 +0100 @@ -17,7 +17,9 @@ #include "scenegraph/LineChartNode.h" #include "scenegraph/LineGridNode.h" -QVector<QVector2D> interpolate(const QVector<QVector2D> &points, qreal start, qreal end, qreal height); +QVector<QPointF> solveControlPoints(const QVector<QPointF> input); +QVector<QPair<QPointF, QPointF>> calculateControlPoints(const QVector<QVector2D> &points, qreal height); +QVector<QVector2D> interpolate(const QVector<QVector2D> &points, qreal height); QColor colorWithAlpha(const QColor &color, qreal opacity) { @@ -29,6 +31,76 @@ return result; } +LineChartAttached::LineChartAttached(QObject* parent) + : QObject(parent) +{ +} + +QVariant LineChartAttached::value() const +{ + return m_value; +} + +void LineChartAttached::setValue(const QVariant& value) +{ + if (value == m_value) { + return; + } + + m_value = value; + Q_EMIT valueChanged(); +} + +QColor LineChartAttached::color() const +{ + return m_color; +} + +void LineChartAttached::setColor(const QColor& color) +{ + if (color == m_color) { + return; + } + + m_color = color; + Q_EMIT colorChanged(); +} + +QString LineChartAttached::name() const +{ + return m_name; +} + +void LineChartAttached::setName(const QString & newName) +{ + if (newName == m_name) { + return; + } + + m_name = newName; + Q_EMIT nameChanged(); +} + +QString LineChartAttached::shortName() const +{ + if (m_shortName.isEmpty()) { + return m_name; + } else { + return m_shortName; + } +} + +void LineChartAttached::setShortName(const QString & newShortName) +{ + if (newShortName == m_shortName) { + return; + } + + m_shortName = newShortName; + Q_EMIT shortNameChanged(); +} + + LineChart::LineChart(QQuickItem *parent) : XYChart(parent) { @@ -56,7 +128,7 @@ } m_smooth = smooth; - update(); + polish(); Q_EMIT smoothChanged(); } @@ -98,21 +170,109 @@ Q_EMIT fillColorSourceChanged(); } -QSGNode *LineChart::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) +QQmlComponent *LineChart::pointDelegate() const { - Q_UNUSED(data); + return m_pointDelegate; +} - if (!node) { - node = new QSGNode(); +void LineChart::setPointDelegate(QQmlComponent *newPointDelegate) +{ + if (newPointDelegate == m_pointDelegate) { + return; } + m_pointDelegate = newPointDelegate; + for (auto entry : qAsConst(m_pointDelegates)) { + qDeleteAll(entry); + } + m_pointDelegates.clear(); + polish(); + Q_EMIT pointDelegateChanged(); +} + +void LineChart::updatePolish() +{ if (m_rangeInvalid) { updateComputedRange(); m_rangeInvalid = false; } - if (stacked()) { - m_previousValues.clear(); + QVector<QVector2D> previousValues; + + const auto range = computedRange(); + const auto sources = valueSources(); + for (int i = 0; i < sources.size(); ++i) { + auto valueSource = sources.at(i); + + float stepSize = width() / (range.distanceX - 1); + QVector<QVector2D> values(range.distanceX); + auto generator = [&, i = range.startX]() mutable -> QVector2D { + float value = 0; + if (range.distanceY != 0) { + value = (valueSource->item(i).toFloat() - range.startY) / range.distanceY; + } + + auto result = QVector2D{direction() == Direction::ZeroAtStart ? i * stepSize + : float(boundingRect().right()) - i * stepSize, value}; + i++; + return result; + }; + + if (direction() == Direction::ZeroAtStart) { + std::generate_n(values.begin(), range.distanceX, generator); + } else { + std::generate_n(values.rbegin(), range.distanceX, generator); + } + + if (stacked() && !previousValues.isEmpty()) { + if (values.size() != previousValues.size()) { + qWarning() << "Value source" << valueSource->objectName() + << "has a different number of elements from the previuous source. Ignoring stacking for this source."; + } else { + std::for_each(values.begin(), values.end(), [previousValues, i = 0](QVector2D &point) mutable { + point.setY(point.y() + previousValues.at(i++).y()); + }); + } + } + previousValues = values; + + if (m_pointDelegate) { + auto& delegates = m_pointDelegates[valueSource]; + if (delegates.size() != values.size()) { + qDeleteAll(delegates); + createPointDelegates(values, i); + } else { + for (int item = 0; item < values.size(); ++item) { + auto delegate = delegates.at(item); + updatePointDelegate(delegate, values.at(item), valueSource->item(item), i); + } + } + } + + if (m_smooth) { + m_values[valueSource] = interpolate(values, height()); + } else { + m_values[valueSource] = values; + } + } + + const auto pointKeys = m_pointDelegates.keys(); + for (auto key : pointKeys) { + if (!sources.contains(key)) { + qDeleteAll(m_pointDelegates[key]); + m_pointDelegates.remove(key); + } + } + + update(); +} + +QSGNode *LineChart::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + + if (!node) { + node = new QSGNode(); } const auto sources = valueSources(); @@ -141,7 +301,16 @@ void LineChart::onDataChanged() { m_rangeInvalid = true; - update(); + polish(); +} + +void LineChart::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) +{ + XYChart::geometryChanged(newGeometry, oldGeometry); + + if (newGeometry != oldGeometry) { + polish(); + } } void LineChart::updateLineNode(LineChartNode *node, const QColor &lineColor, const QColor &fillColor, ChartDataSource *valueSource) @@ -155,112 +324,159 @@ node->setFillColor(fillColor); node->setLineWidth(m_lineWidth); - auto range = computedRange(); + auto values = m_values.value(valueSource); + node->setValues(values); +} - float stepSize = width() / (range.distanceX - 1); - QVector<QVector2D> values(range.distanceX); - auto generator = [&, i = range.startX]() mutable -> QVector2D { - float value = 0; - if (range.distanceY != 0) { - value = (valueSource->item(i).toFloat() - range.startY) / range.distanceY; - } +QVector<QVector2D> interpolate(const QVector<QVector2D> &points, qreal height) //, qreal start, qreal end, qreal height) +{ + if (points.size() < 2) { + return points; + } - auto result = QVector2D{direction() == Direction::ZeroAtStart ? i * stepSize : float(boundingRect().right()) - i * stepSize, - value}; - i++; - return result; - }; + auto controlPoints = calculateControlPoints(points, height); - if (direction() == Direction::ZeroAtStart) { - std::generate_n(values.begin(), range.distanceX, generator); - } else { - std::generate_n(values.rbegin(), range.distanceX, generator); + QPainterPath path; + path.moveTo(0.0, points.first().y() * height); + + for (int i = 0; i < points.size() - 1; ++i) { + auto controlPoint = controlPoints.at(i); + auto nextPoint = QPointF{points.at(i + 1).x(), points.at(i + 1).y() * height}; + path.cubicTo(controlPoint.first, controlPoint.second, nextPoint); } - if (stacked() && !m_previousValues.isEmpty()) { - if (values.size() != m_previousValues.size()) { - qWarning() << "Value source" << valueSource->objectName() - << "has a different number of elements from the previuous source. Ignoring stacking for this source."; - } else { - std::for_each( - values.begin(), values.end(), [this, i = 0](QVector2D &point) mutable { point.setY(point.y() + m_previousValues.at(i++).y()); }); + QVector<QVector2D> result; + + const auto polygons = path.toSubpathPolygons(); + auto pointCount = std::accumulate(polygons.begin(), polygons.end(), 0, [](int current, const QPolygonF &polygon) { + return current + polygon.size(); + }); + result.reserve(pointCount); + + for (const auto &polygon : polygons) { + for (auto point : polygon) { + result.append(QVector2D{float(point.x()), float(point.y() / height)}); } } - m_previousValues = values; - if (m_smooth) { - values = interpolate(values, 0.0, width(), height()); + return result; +} + +void LineChart::createPointDelegates(const QVector<QVector2D> &values, int sourceIndex) +{ + auto valueSource = valueSources().at(sourceIndex); + + QVector<QQuickItem*> delegates; + for (int i = 0; i < values.size(); ++i) { + auto delegate = qobject_cast<QQuickItem*>(m_pointDelegate->beginCreate(qmlContext(m_pointDelegate))); + if (!delegate) { + qWarning() << "Delegate creation for point" << i << "of value source" << valueSource->objectName() + << "failed, make sure pointDelegate is a QQuickItem"; + delegate = new QQuickItem(this); + } + + delegate->setParent(this); + delegate->setParentItem(this); + updatePointDelegate(delegate, values.at(i), valueSource->item(i), sourceIndex); + + m_pointDelegate->completeCreate(); + + delegates.append(delegate); } - node->setValues(values); + m_pointDelegates.insert(valueSource, delegates); } -QVector<QVector2D> interpolate(const QVector<QVector2D> &points, qreal start, qreal end, qreal height) +void LineChart::updatePointDelegate(QQuickItem *delegate, const QVector2D &position, const QVariant &value, int sourceIndex) { - QPainterPath path; - if (points.size() < 4) - return points; + auto pos = QPointF{position.x() - delegate->width() / 2, (1.0 - position.y()) * height() - delegate->height() / 2}; + delegate->setPosition(pos); - const auto sixth = 1.f / 6.f; + auto attached = static_cast<LineChartAttached*>(qmlAttachedPropertiesObject<LineChart>(delegate, true)); + attached->setValue(value); + attached->setColor(colorSource() ? colorSource()->item(sourceIndex).value<QColor>() : Qt::black); + attached->setName(nameSource() ? nameSource()->item(sourceIndex).toString() : QString{}); + attached->setShortName(shortNameSource() ? shortNameSource()->item(sourceIndex).toString() : QString{}); +} - const qreal xDelta = (end - start) / (points.count() - 3); - qreal x = start - xDelta; +QVector<QPair<QPointF, QPointF>> calculateControlPoints(const QVector<QVector2D> &points, qreal height) +{ + // This is based on + // https://www.codeproject.com/Articles/31859/Draw-a-Smooth-Curve-through-a-Set-of-2D-Points-wit + // and calculates the control points based on the derivative of the curve. - path.moveTo(start, points[0].y() * height); + auto count = points.size() - 1; + QVector<QPair<QPointF, QPointF>> result(count); - for (int i = 1; i < points.count() - 2; i++) { - // This code was: - // - // QMatrix4x4 matrix( 0, 1, 0, 0, - // -1/6, 1, 1/6, 0, - // 0, 1/6, 1, -1/6, - // 0, 0, 1, 0); - // QMatrix4x4 p(x + xDelta * 0, points[i - 1].y() * height, 0, 0, - // x + xDelta * 1, points[i + 0].y() * height, 0, 0, - // x + xDelta * 2, points[i + 1].y() * height, 0, 0, - // x + xDelta * 3, points[i + 2].y() * height, 0, 0) - // QMatrix4x4 res = matrix * p; - // path.cubicTo(res(1,0), res(1, 1), res(2, 0), res(2, 1), res(3, 0), res(3, 1)) - // - // The below calculations calculate the used elements from the matrix directly, avoiding - // most of an expensive matrix multiplication. - - auto p0 = points[i - 1].y() * height; - auto p1 = points[i].y() * height; - auto p2 = points[i + 1].y() * height; - auto p3 = points[i + 2].y() * height; - - //res(1, 0) = (-1/6, 1, 1/6, 0) dot (x, x + xDelta, x + xDelta * 2, x + xDelta * 3) - auto res10 = (3 * x + 4 * xDelta) / 3.f; - //res(1, 1) = (-1/6, 1, 1/6, 0) dot (p[i-1].y, p[i].y, p[i+1].y, p[i+2].y) - auto res11 = -sixth * p0 + p1 + sixth * p2; - //res(2, 0) = (0, 1/6, 1, -1/6) dot (x, x + xDelta, x + xDelta * 2, x + xDelta * 3) - auto res20 = (3 * x + 5 * xDelta) / 3.f; - //res(2, 1) = (0, 1/6, 1, -1/6) dot (p[i-1].y, p[i].y, p[i+1].y, p[i+2].y) - auto res21 = sixth * p1 + p2 + -sixth * p3; - //res(3, 0) = (0, 0, 1, 0) dot (x, x + xDelta, x + xDelta * 2, x + xDelta * 3) - auto res30 = x + 2 * xDelta; - //res(3, 1) = (0, 0, 1, 0) dot (p[i-1].y, p[i].y, p[i+1].y, p[i+2].y) - auto res31 = p2; + const auto first = QPointF{points.first().x(), points.first().y() * height}; + const auto last = QPointF{points.last().x(), points.last().y() * height}; - path.cubicTo(res10, res11, res20, res21, res30, res31); + if (count == 1) { + auto &controlPoint = result[0]; - x += xDelta; - } + controlPoint.first.rx() = (2.0 * first.x() + last.x()) / 3.0; + controlPoint.first.ry() = (2.0 * first.y() + last.y()) / 3.0; - QVector<QVector2D> result; + controlPoint.second.rx() = 2.0 * controlPoint.first.x() - first.x(); + controlPoint.second.ry() = 2.0 * controlPoint.first.y() - first.y(); - const auto polygons = path.toSubpathPolygons(); - auto pointCount = std::accumulate(polygons.begin(), polygons.end(), 0, [](int current, const QPolygonF &polygon) { - return current + polygon.size(); + return result; + } + + QVector<QPointF> coordinates(count); + std::generate_n(coordinates.begin() + 1, count - 2, [&points, height, i = 1]() mutable { + auto x = 4.0 * points[i].x() + 2.0 * points[i + 1].x(); + auto y = 4.0 * points[i].y() * height + 2.0 * points[i + 1].y() * height; + i++; + return QPointF{x, y}; }); - result.reserve(pointCount); - for (const auto &polygon : polygons) { - for (auto point : polygon) { - result.append(QVector2D{float(point.x()), float(point.y() / height)}); + coordinates.first().rx() = first.x() + 2.0 * points.at(1).x(); + coordinates.first().ry() = first.y() + 2.0 * points.at(1).y() * height; + coordinates.last().rx() = (8.0 * points.at(count - 1).x() + last.x()) / 2.0; + coordinates.last().ry() = (8.0 * points.at(count - 1).y() * height + last.y()) / 2.0; + + const auto solved = solveControlPoints(coordinates); + + for (int i = 0; i < count; ++i) { + auto &controlPoint = result[i]; + controlPoint.first = solved[i]; + + if (i < count - 1) { + controlPoint.second = QPointF{ + 2.0 * points.at(i + 1).x() - solved.at(i + 1).x(), + 2.0 * points.at(i + 1).y() * height - solved.at(i + 1).y() + }; + } else { + controlPoint.second = QPointF{ + (last.x() + solved.at(count - 1).x()) / 2.0, + (last.y() + solved.at(count - 1).y()) / 2.0 + }; } } return result; +} + +QVector<QPointF> solveControlPoints(const QVector<QPointF> input) +{ + auto count = input.size(); + QVector<QPointF> result(count); + QVector<double> temp(count); + + double b = 2.0; + result.first() = input.first() / b; + + for (int i = 1; i < count; ++i) { + temp[i] = 1.0 / b; + b = (i < count - 1 ? 4.0 : 3.5) - temp[i]; + result[i].rx() = (input[i].x() - result[i - 1].x()) / b; + result[i].ry() = (input[i].y() - result[i - 1].y()) / b; + } + + for (int i = 1; i < count; ++i) { + result[count - i - 1] -= temp[count - i] * result[count - i]; + } + + return result; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kquickcharts-5.76.0/src/LineChart.h new/kquickcharts-5.77.0/src/LineChart.h --- old/kquickcharts-5.76.0/src/LineChart.h 2020-11-07 12:42:59.000000000 +0100 +++ new/kquickcharts-5.77.0/src/LineChart.h 2020-12-05 11:13:26.000000000 +0100 @@ -15,13 +15,64 @@ class LineChartNode; /** + * An attached property that is exposed to point delegates created in line charts. + * + * \sa LineChart::pointDelegate + */ +class LineChartAttached : public QObject +{ + Q_OBJECT + /** + * The value at the current point. + */ + Q_PROPERTY(QVariant value READ value NOTIFY valueChanged) + /** + * The color at the current point. + */ + Q_PROPERTY(QColor color READ color NOTIFY colorChanged) + /** + * The name at the current point. + */ + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + /** + * The short name at the current point. + */ + Q_PROPERTY(QString shortName READ shortName NOTIFY shortNameChanged) + +public: + LineChartAttached(QObject *parent = nullptr); + + QVariant value() const; + void setValue(const QVariant &value); + Q_SIGNAL void valueChanged(); + + QColor color() const; + void setColor(const QColor &color); + Q_SIGNAL void colorChanged(); + + QString name() const; + void setName(const QString &newName); + Q_SIGNAL void nameChanged(); + + QString shortName() const; + void setShortName(const QString & newShortName); + Q_SIGNAL void shortNameChanged(); + +private: + QVariant m_value; + QColor m_color; + QString m_name; + QString m_shortName; +}; + +/** * A line chart. * * ## Usage example * * \snippet snippets/linechart.qml example * - * \image html linechart.png "The resulting bar chart." + * \image html linechart.png "The resulting line chart." */ class LineChart : public XYChart { @@ -49,6 +100,19 @@ * with the fillOpacity used as its opacity. */ Q_PROPERTY(ChartDataSource *fillColorSource READ fillColorSource WRITE setFillColorSource NOTIFY fillColorSourceChanged) + /** + * A delegate that will be placed at each line chart point. + * + * When this is not null, the specified component will be used to + * instantiate an object for each point in the chart. These objects will + * then be placed centered at positions corresponding to the points on the + * chart. Each instance will have access to the attached properties of + * LineChartAttached through LineChart attached object. + * + * \note The component assigned to this property is expected to create a + * QQuickItem, since the created object needs to be positioned. + */ + Q_PROPERTY(QQmlComponent *pointDelegate READ pointDelegate WRITE setPointDelegate NOTIFY pointDelegateChanged) public: explicit LineChart(QQuickItem *parent = nullptr); @@ -69,19 +133,36 @@ void setFillColorSource(ChartDataSource *newFillColorSource); Q_SIGNAL void fillColorSourceChanged(); + QQmlComponent *pointDelegate() const; + void setPointDelegate(QQmlComponent *newPointDelegate); + Q_SIGNAL void pointDelegateChanged(); + + static LineChartAttached *qmlAttachedProperties(QObject *object) + { + return new LineChartAttached(object); + } + protected: + void updatePolish() override; QSGNode *updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) override; void onDataChanged() override; + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; private: void updateLineNode(LineChartNode *node, const QColor &lineColor, const QColor &fillColor, ChartDataSource *valueSource); + void createPointDelegates(const QVector<QVector2D> &values, int sourceIndex); + void updatePointDelegate(QQuickItem *delegate, const QVector2D &position, const QVariant &value, int sourceIndex); bool m_smooth = false; qreal m_lineWidth = 1.0; qreal m_fillOpacity = 0.0; bool m_rangeInvalid = true; - QVector<QVector2D> m_previousValues; ChartDataSource *m_fillColorSource = nullptr; + QHash<ChartDataSource*, QVector<QVector2D>> m_values; + QQmlComponent *m_pointDelegate = nullptr; + QHash<ChartDataSource*, QVector<QQuickItem*>> m_pointDelegates; }; +QML_DECLARE_TYPEINFO(LineChart, QML_HAS_ATTACHED_PROPERTIES) + #endif // LINECHART_H _______________________________________________ openSUSE Commits mailing list -- [email protected] To unsubscribe, email [email protected] List Netiquette: https://en.opensuse.org/openSUSE:Mailing_list_netiquette List Archives: https://lists.opensuse.org/archives/list/[email protected]
