Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kquickcharts for openSUSE:Factory checked in at 2021-06-16 20:34:08 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kquickcharts (Old) and /work/SRC/openSUSE:Factory/.kquickcharts.new.32437 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kquickcharts" Wed Jun 16 20:34:08 2021 rev:19 rq:899759 version:5.83.0 Changes: -------- --- /work/SRC/openSUSE:Factory/kquickcharts/kquickcharts.changes 2021-05-10 15:37:28.666017441 +0200 +++ /work/SRC/openSUSE:Factory/.kquickcharts.new.32437/kquickcharts.changes 2021-06-16 20:35:46.507215832 +0200 @@ -1,0 +2,14 @@ +Sat Jun 5 11:59:10 UTC 2021 - Christophe Giboudeaux <[email protected]> + +- Update to 5.83.0 + * New feature release + * For more details please see: + * https://kde.org/announcements/frameworks/5/5.83.0 +- Changes since 5.82.0: + * Implement monotonic cubic interpolation for line charts (kde#435268) + * Prevent an infinite loop in LegendLayout when minTotalWidth equals available + * Reduce severity of column not found to debug + * Bump required CMake version to 3.16 + * Expose preferredWidth as a property on LegendLayout and Legend + +------------------------------------------------------------------- Old: ---- kquickcharts-5.82.0.tar.xz kquickcharts-5.82.0.tar.xz.sig New: ---- kquickcharts-5.83.0.tar.xz kquickcharts-5.83.0.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kquickcharts.spec ++++++ --- /var/tmp/diff_new_pack.oWV0VI/_old 2021-06-16 20:35:46.919216535 +0200 +++ /var/tmp/diff_new_pack.oWV0VI/_new 2021-06-16 20:35:46.923216542 +0200 @@ -16,14 +16,14 @@ # -%define _tar_path 5.82 +%define _tar_path 5.83 # 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.82.0 +Version: 5.83.0 Release: 0 Summary: Set of charts for QtQuick applications License: LGPL-2.1-or-later ++++++ kquickcharts-5.82.0.tar.xz -> kquickcharts-5.83.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kquickcharts-5.82.0/.gitignore new/kquickcharts-5.83.0/.gitignore --- old/kquickcharts-5.82.0/.gitignore 2021-05-06 10:45:40.000000000 +0200 +++ new/kquickcharts-5.83.0/.gitignore 2021-06-05 10:58:28.000000000 +0200 @@ -6,3 +6,4 @@ /build .clangd /cmake-build* +.cache diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kquickcharts-5.82.0/CMakeLists.txt new/kquickcharts-5.83.0/CMakeLists.txt --- old/kquickcharts-5.82.0/CMakeLists.txt 2021-05-06 10:45:40.000000000 +0200 +++ new/kquickcharts-5.83.0/CMakeLists.txt 2021-06-05 10:58:28.000000000 +0200 @@ -1,12 +1,12 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.16) -set(KF_VERSION "5.82.0") # handled by release scripts -set(KF_DEP_VERSION "5.82.0") # handled by release scripts +set(KF_VERSION "5.83.0") # handled by release scripts +set(KF_DEP_VERSION "5.83.0") # handled by release scripts project(KQuickCharts VERSION ${KF_VERSION}) include(FeatureSummary) -find_package(ECM 5.82.0 NO_MODULE) +find_package(ECM 5.83.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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kquickcharts-5.82.0/src/CMakeLists.txt new/kquickcharts-5.83.0/src/CMakeLists.txt --- old/kquickcharts-5.82.0/src/CMakeLists.txt 2021-05-06 10:45:40.000000000 +0200 +++ new/kquickcharts-5.83.0/src/CMakeLists.txt 2021-06-05 10:58:28.000000000 +0200 @@ -37,8 +37,6 @@ qt5_add_resources(quickcharts_QRC shaders/shaders.qrc) -add_definitions(-DQT_NO_KEYWORDS) - ecm_qt_declare_logging_category(quickcharts_SRCS HEADER charts_general_logging.h IDENTIFIER GENERAL diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kquickcharts-5.82.0/src/LineChart.cpp new/kquickcharts-5.83.0/src/LineChart.cpp --- old/kquickcharts-5.82.0/src/LineChart.cpp 2021-05-06 10:45:40.000000000 +0200 +++ new/kquickcharts-5.83.0/src/LineChart.cpp 2021-06-05 10:58:28.000000000 +0200 @@ -7,19 +7,22 @@ #include "LineChart.h" +#include <cmath> + #include <QPainter> #include <QPainterPath> #include <QQuickWindow> -#include <numeric> #include "RangeGroup.h" #include "datasource/ChartDataSource.h" #include "scenegraph/LineChartNode.h" #include "scenegraph/LineGridNode.h" -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); +static const float PixelsPerStep = 1.0; + +QVector<QVector2D> interpolate(const QVector<QVector2D> &points, float height); +QVector<float> calculateTangents(const QVector<QVector2D> &points, float height); +QVector2D cubicHermite(const QVector2D &first, const QVector2D &second, float step, float mFirst, float mSecond); QColor colorWithAlpha(const QColor &color, qreal opacity) { @@ -326,40 +329,6 @@ node->setValues(values); } -QVector<QVector2D> interpolate(const QVector<QVector2D> &points, qreal height) //, qreal start, qreal end, qreal height) -{ - if (points.size() < 2) { - return points; - } - - auto controlPoints = calculateControlPoints(points, height); - - 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); - } - - 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)}); - } - } - - return result; -} - void LineChart::createPointDelegates(const QVector<QVector2D> &values, int sourceIndex) { auto valueSource = valueSources().at(sourceIndex); @@ -397,80 +366,133 @@ attached->setShortName(shortNameSource() ? shortNameSource()->item(sourceIndex).toString() : QString{}); } -QVector<QPair<QPointF, QPointF>> calculateControlPoints(const QVector<QVector2D> &points, qreal height) +// Smoothly interpolate between points, using monotonic cubic interpolation. +QVector<QVector2D> interpolate(const QVector<QVector2D> &points, float 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. + if (points.size() < 2) { + return points; + } - auto count = points.size() - 1; - QVector<QPair<QPointF, QPointF>> result(count); + auto tangents = calculateTangents(points, height); - const auto first = QPointF{points.first().x(), points.first().y() * height}; - const auto last = QPointF{points.last().x(), points.last().y() * height}; + QVector<QVector2D> result; + + auto current = QVector2D{0.0, points.first().y() * height}; + result.append(QVector2D{0.0, points.first().y()}); - if (count == 1) { - auto &controlPoint = result[0]; + for (int i = 0; i < points.size() - 1; ++i) { + auto next = QVector2D{points.at(i + 1).x(), points.at(i + 1).y() * height}; - controlPoint.first.rx() = (2.0 * first.x() + last.x()) / 3.0; - controlPoint.first.ry() = (2.0 * first.y() + last.y()) / 3.0; + auto currentTangent = tangents.at(i); + auto nextTangent = tangents.at(i + 1); - controlPoint.second.rx() = 2.0 * controlPoint.first.x() - first.x(); - controlPoint.second.ry() = 2.0 * controlPoint.first.y() - first.y(); + auto stepCount = int(std::max(1.0f, (next.x() - current.x()) / PixelsPerStep)); + auto stepSize = (next.x() - current.x()) / stepCount; - return result; + if (stepCount == 1 || qFuzzyIsNull(next.y() - current.y())) { + result.append(QVector2D{next.x(), next.y() / height}); + current = next; + continue; + } + + for (auto delta = current.x(); delta < next.x(); delta += stepSize) { + auto interpolated = cubicHermite(current, next, delta, currentTangent, nextTangent); + interpolated.setY(interpolated.y() / height); + result.append(interpolated); + } + + current = next; } - 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}; - }); + current.setY(current.y() / height); + result.append(current); - 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; + return result; +} - const auto solved = solveControlPoints(coordinates); +// This calculates the tangents for monotonic cubic spline interpolation. +// See https://en.wikipedia.org/wiki/Monotone_cubic_interpolation for details. +QVector<float> calculateTangents(const QVector<QVector2D> &points, float height) +{ + QVector<float> secantSlopes; + secantSlopes.reserve(points.size()); - for (int i = 0; i < count; ++i) { - auto &controlPoint = result[i]; - controlPoint.first = solved[i]; + QVector<float> tangents; + tangents.reserve(points.size()); - 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()}; + float previousSlope = 0.0; + float slope = 0.0; + + for (int i = 0; i < points.size() - 1; ++i) { + auto current = points.at(i); + auto next = points.at(i + 1); + + previousSlope = slope; + slope = (next.y() * height - current.y() * height) / (next.x() - current.x()); + + secantSlopes.append(slope); + + if (i == 0) { + tangents.append(slope); + } else if (previousSlope * slope < 0.0) { + tangents.append(0.0); } else { - controlPoint.second = QPointF{(last.x() + solved.at(count - 1).x()) / 2.0, // - (last.y() + solved.at(count - 1).y()) / 2.0}; + tangents.append((previousSlope + slope) / 2.0); } } + tangents.append(secantSlopes.last()); - return result; -} + for (int i = 0; i < points.size() - 1; ++i) { + auto slope = secantSlopes.at(i); -QVector<QPointF> solveControlPoints(const QVector<QPointF> input) -{ - auto count = input.size(); - QVector<QPointF> result(count); - QVector<double> temp(count); + if (qFuzzyIsNull(slope)) { + tangents[i] = 0.0; + tangents[i + 1] = 0.0; + continue; + } - double b = 2.0; - result.first() = input.first() / b; + auto alpha = tangents.at(i) / slope; + auto beta = tangents.at(i + 1) / slope; - 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; - } + if (alpha < 0.0) { + tangents[i] = 0.0; + } - for (int i = 1; i < count; ++i) { - result[count - i - 1] -= temp[count - i] * result[count - i]; + if (beta < 0.0) { + tangents[i + 1] = 0.0; + } + + auto length = alpha * alpha + beta * beta; + if (length > 9) { + auto tau = 3.0 / sqrt(length); + tangents[i] = tau * alpha * slope; + tangents[i + 1] = tau * beta * slope; + } } + return tangents; +} + +// Cubic Hermite Interpolation between two points +// Given two points, an X value between those two points and two tangents, this +// will perform cubic hermite interpolation between the two points. +// See https://en.wikipedia.org/wiki/Cubic_Hermite_spline for details as well as +// the above mentioned article on monotonic interpolation. +QVector2D cubicHermite(const QVector2D &first, const QVector2D &second, float step, float mFirst, float mSecond) +{ + const auto delta = second.x() - first.x(); + const auto t = (step - first.x()) / delta; + + // Hermite basis values + // h??????(t) = 2t?? - 3t?? + 1 + const auto h00 = 2.0f * std::pow(t, 3.0f) - 3.0f * std::pow(t, 2.0f) + 1.0f; + // h??????(t) = t?? - 2t?? + t + const auto h10 = std::pow(t, 3.0f) - 2.0f * std::pow(t, 2.0f) + t; + // h??????(t) = -2t?? + 3t?? + const auto h01 = -2.0f * std::pow(t, 3.0f) + 3.0f * std::pow(t, 2.0f); + // h??????(t) = t?? - t?? + const auto h11 = std::pow(t, 3.0f) - std::pow(t, 2.0f); + + auto result = QVector2D{step, first.y() * h00 + delta * mFirst * h10 + second.y() * h01 + delta * mSecond * h11}; return result; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kquickcharts-5.82.0/src/datasource/ModelSource.cpp new/kquickcharts-5.83.0/src/datasource/ModelSource.cpp --- old/kquickcharts-5.82.0/src/datasource/ModelSource.cpp 2021-05-06 10:45:40.000000000 +0200 +++ new/kquickcharts-5.83.0/src/datasource/ModelSource.cpp 2021-06-05 10:58:28.000000000 +0200 @@ -80,7 +80,7 @@ } if (!m_indexColumns && (m_column < 0 || m_column > m_model->columnCount())) { - qCWarning(DATASOURCE) << "ModelSource: Invalid column" << m_column; + qCDebug(DATASOURCE) << "ModelSource: Invalid column" << m_column; return QVariant{}; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kquickcharts-5.82.0/src/decorations/LegendLayout.cpp new/kquickcharts-5.83.0/src/decorations/LegendLayout.cpp --- old/kquickcharts-5.82.0/src/decorations/LegendLayout.cpp 2021-05-06 10:45:40.000000000 +0200 +++ new/kquickcharts-5.83.0/src/decorations/LegendLayout.cpp 2021-06-05 10:58:28.000000000 +0200 @@ -306,7 +306,7 @@ // If the minimum width is less than our width, but the maximum is // larger, we found a correct solution since we can resize the items to // fit within the provided bounds. - if (minTotalWidth < availableWidth && maxTotalWidth >= availableWidth) { + if (minTotalWidth <= availableWidth && maxTotalWidth >= availableWidth) { break; }
