diff options
-rw-r--r-- | .qmake.conf | 2 | ||||
-rw-r--r-- | dist/changes-5.15.2 | 37 | ||||
-rw-r--r-- | src/charts/axis/cartesianchartaxis.cpp | 27 | ||||
-rw-r--r-- | src/charts/axis/chartaxiselement.cpp | 28 | ||||
-rw-r--r-- | src/charts/axis/horizontalaxis.cpp | 21 | ||||
-rw-r--r-- | src/charts/axis/valueaxis/chartvalueaxisx.cpp | 13 | ||||
-rw-r--r-- | src/charts/axis/valueaxis/chartvalueaxisy.cpp | 13 | ||||
-rw-r--r-- | src/charts/axis/valueaxis/qvalueaxis.cpp | 2 | ||||
-rw-r--r-- | src/charts/chartpresenter.cpp | 53 | ||||
-rw-r--r-- | src/charts/domain/abstractdomain.cpp | 3 | ||||
-rw-r--r-- | src/charts/layout/abstractchartlayout.cpp | 15 | ||||
-rw-r--r-- | src/charts/legend/legendmarkeritem.cpp | 2 | ||||
-rw-r--r-- | src/charts/legend/qlegend.cpp | 2 | ||||
-rw-r--r-- | src/charts/piechart/qpieslice.cpp | 2 | ||||
-rw-r--r-- | src/charts/xychart/qhxymodelmapper.cpp | 12 | ||||
-rw-r--r-- | src/charts/xychart/qvxymodelmapper.cpp | 12 | ||||
-rw-r--r-- | src/charts/xychart/qxymodelmapper.cpp | 27 | ||||
-rw-r--r-- | src/chartsqml2/declarativechart.cpp | 8 | ||||
-rw-r--r-- | src/chartsqml2/declarativeopenglrendernode.cpp | 3 | ||||
-rw-r--r-- | tests/auto/qbarseries/BLACKLIST | 2 |
20 files changed, 196 insertions, 88 deletions
diff --git a/.qmake.conf b/.qmake.conf index 05561703..4a55f521 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -2,4 +2,4 @@ load(qt_build_config) DEFINES += QT_NO_JAVA_STYLE_ITERATORS QT_NO_LINKED_LIST -MODULE_VERSION = 5.15.3 +MODULE_VERSION = 5.15.13 diff --git a/dist/changes-5.15.2 b/dist/changes-5.15.2 new file mode 100644 index 00000000..564c6cf9 --- /dev/null +++ b/dist/changes-5.15.2 @@ -0,0 +1,37 @@ +Qt 5.15.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.15.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + https://doc.qt.io/qt-5.15/index.html + +The Qt version 5.15 series is binary compatible with the 5.14.x series. +Applications compiled for 5.14 will continue to run with 5.15. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Important Behavior Changes * +**************************************************************************** + +**************************************************************************** +* Library * +**************************************************************************** + + - [QTBUG-75500] Prevent zoom-out beyond infinite linear max double values + When zooming out domains that include log axes, prevent zoom-out when + the new zoom level would result in a max value that is above the upper + range of double (represented as inf). Additionally, prevent zoom-out + when the log value exceeds the number of pixels in the log axis + dimension. Major gridlines are created at each integral log value, and + it does not make sense to zoom out beyond such point. + The bug was: Stack overflow on high zoom out of QChart with a + logarithmic axis + diff --git a/src/charts/axis/cartesianchartaxis.cpp b/src/charts/axis/cartesianchartaxis.cpp index 935a9601..6389c17e 100644 --- a/src/charts/axis/cartesianchartaxis.cpp +++ b/src/charts/axis/cartesianchartaxis.cpp @@ -130,15 +130,16 @@ void CartesianChartAxis::updateMinorTickItems() expectedCount = qMax(expectedCount, 0); } else { const qreal interval = valueAxis->tickInterval(); - qreal firstMajorTick = valueAxis->tickAnchor(); + const qreal anchor = valueAxis->tickAnchor(); const qreal max = valueAxis->max(); const qreal min = valueAxis->min(); const int _minorTickCount = valueAxis->minorTickCount(); - if (min < firstMajorTick) - firstMajorTick = firstMajorTick - qCeil((firstMajorTick - min) / interval) * interval; - else - firstMajorTick = firstMajorTick + int((min - firstMajorTick) / interval) * interval; + // Find the closest major tick <= the min of the range, even if it's not drawn! + // This is where we'll start counting minor ticks from, because minor ticks + // might need to be drawn even before the first major tick. + const qreal ticksFromAnchor = (anchor - min) / interval; + const qreal firstMajorTick = anchor - std::ceil(ticksFromAnchor) * interval; const qreal deltaMinor = interval / qreal(_minorTickCount + 1); qreal minorTick = firstMajorTick + deltaMinor; @@ -265,7 +266,7 @@ bool CartesianChartAxis::isEmpty() { return axisGeometry().isEmpty() || gridGeometry().isEmpty() - || qFuzzyCompare(min(), max()); + || qFuzzyIsNull(max() - min()); } void CartesianChartAxis::setGeometry(const QRectF &axis, const QRectF &grid) @@ -371,16 +372,16 @@ void CartesianChartAxis::updateLabelsValues(QValueAxis *axis) static_cast<ValueAxisLabel *>(labelItems().at(i))->setValue(value); } } else { - qreal value = axis->tickAnchor(); - if (value > min()) - value = value - int((value - min()) / axis->tickInterval()) * axis->tickInterval(); - else - value = value + qCeil((min() - value) / axis->tickInterval()) * axis->tickInterval(); + const qreal anchor = axis->tickAnchor(); + const qreal interval = axis->tickInterval(); + const qreal ticksFromAnchor = (anchor - min()) / interval; + const qreal firstMajorTick = anchor - std::floor(ticksFromAnchor) * interval; int i = axis->isReverse() ? labelItems().count()-1 : 0; - while (value <= max() || qFuzzyCompare(value, max())) { + qreal value = firstMajorTick; + while (value <= max()) { static_cast<ValueAxisLabel *>(labelItems().at(i))->setValue(value); - value += axis->tickInterval(); + value += interval; i += axis->isReverse() ? -1 : 1; } } diff --git a/src/charts/axis/chartaxiselement.cpp b/src/charts/axis/chartaxiselement.cpp index 8db5ef36..2a874216 100644 --- a/src/charts/axis/chartaxiselement.cpp +++ b/src/charts/axis/chartaxiselement.cpp @@ -339,7 +339,7 @@ bool ChartAxisElement::isEmpty() { return axisGeometry().isEmpty() || gridGeometry().isEmpty() - || qFuzzyCompare(min(), max()); + || qFuzzyIsNull(max() - min()); } qreal ChartAxisElement::min() const @@ -419,20 +419,22 @@ QStringList ChartAxisElement::createValueLabels(qreal min, qreal max, int ticks, return labels; if (format.isEmpty()) { - int n = qMax(int(-qFloor(std::log10((max - min) / (ticks - 1)))), 0) + 1; + // Calculate how many decimal digits are needed to show difference between ticks, + // for example tick marks 1.002 and 1.003 have a difference of 0.001 and need 3 decimals. + // For differences >= 1 (positive log10) use always 1 decimal. + double l10 = std::log10((max - min) / (ticks - 1)); + int n = qMax(int(-qFloor(l10)), 0) + 1; if (tickType == QValueAxis::TicksFixed) { for (int i = 0; i < ticks; i++) { qreal value = min + (i * (max - min) / (ticks - 1)); labels << presenter()->numberToString(value, 'f', n); } } else { - qreal value = tickAnchor; - if (value > min) - value = value - int((value - min) / tickInterval) * tickInterval; - else - value = value + qCeil((min - value) / tickInterval) * tickInterval; + const qreal ticksFromAnchor = (tickAnchor - min) / tickInterval; + const qreal firstMajorTick = tickAnchor - std::floor(ticksFromAnchor) * tickInterval; - while (value <= max || qFuzzyCompare(value, max)) { + qreal value = firstMajorTick; + while (value <= max) { labels << presenter()->numberToString(value, 'f', n); value += tickInterval; } @@ -468,13 +470,11 @@ QStringList ChartAxisElement::createValueLabels(qreal min, qreal max, int ticks, labels << formatLabel(formatSpec, array, value, precision, preStr, postStr); } } else { - qreal value = tickAnchor; - if (value > min) - value = value - int((value - min) / tickInterval) * tickInterval; - else - value = value + qCeil((min - value) / tickInterval) * tickInterval; + const qreal ticksFromAnchor = (tickAnchor - min) / tickInterval; + const qreal firstMajorTick = tickAnchor - std::floor(ticksFromAnchor) * tickInterval; - while (value <= max || qFuzzyCompare(value, max)) { + qreal value = firstMajorTick; + while (value <= max) { labels << formatLabel(formatSpec, array, value, precision, preStr, postStr); value += tickInterval; } diff --git a/src/charts/axis/horizontalaxis.cpp b/src/charts/axis/horizontalaxis.cpp index 3bbc5ebe..a54339aa 100644 --- a/src/charts/axis/horizontalaxis.cpp +++ b/src/charts/axis/horizontalaxis.cpp @@ -102,7 +102,6 @@ void HorizontalAxis::updateGeometry() else if (axis()->alignment() == Qt::AlignBottom) arrowItem->setLine(gridRect.left(), axisRect.top(), gridRect.right(), axisRect.top()); - qreal width = 0; const QLatin1String ellipsis("..."); //title @@ -133,6 +132,8 @@ void HorizontalAxis::updateGeometry() QList<QGraphicsItem *> lines = gridItems(); QList<QGraphicsItem *> shades = shadeItems(); + qreal last_label_max_x = 0; + for (int i = 0; i < layout.size(); ++i) { //items QGraphicsLineItem *gridItem = static_cast<QGraphicsLineItem*>(lines.at(i)); @@ -160,6 +161,7 @@ void HorizontalAxis::updateGeometry() labelItem->setHtml(text); } else { qreal labelWidth = axisRect.width() / layout.count() - (2 * labelPadding()); + // Replace digits with ellipsis "..." if number does not fit QString truncatedText = ChartPresenter::truncatedText(axis()->labelsFont(), text, axis()->labelsAngle(), labelWidth, @@ -255,13 +257,14 @@ void HorizontalAxis::updateGeometry() labelItem->setPos(labelPos.toPoint()); //label overlap detection - compensate one pixel for rounding errors - if ((labelItem->pos().x() < width && labelItem->toPlainText() == ellipsis) || forceHide || - (labelItem->pos().x() + (widthDiff / 2.0)) < (axisRect.left() - 1.0) || - (labelItem->pos().x() + (widthDiff / 2.0) - 1.0) > axisRect.right()) { + if ((labelItem->pos().x() < last_label_max_x && labelItem->toPlainText() == ellipsis) + || forceHide + || (labelItem->pos().x() + (widthDiff / 2.0)) < (axisRect.left() - 1.0) + || (labelItem->pos().x() + (widthDiff / 2.0) - 1.0) > axisRect.right()) { labelItem->setVisible(false); } else { labelItem->setVisible(true); - width = boundingRect.width() + labelItem->pos().x(); + last_label_max_x = boundingRect.width() + labelItem->pos().x(); } //shades @@ -433,12 +436,12 @@ void HorizontalAxis::updateMinorTickGeometry() qreal minorArrowLineItemY2; switch (axis()->alignment()) { case Qt::AlignTop: - minorArrowLineItemY1 = gridGeometry().bottom(); - minorArrowLineItemY2 = gridGeometry().bottom() - labelPadding() / 2.0; + minorArrowLineItemY1 = gridGeometry().top(); + minorArrowLineItemY2 = gridGeometry().top() - labelPadding() / 2.0; break; case Qt::AlignBottom: - minorArrowLineItemY1 = gridGeometry().top(); - minorArrowLineItemY2 = gridGeometry().top() + labelPadding() / 2.0; + minorArrowLineItemY1 = gridGeometry().bottom(); + minorArrowLineItemY2 = gridGeometry().bottom() + labelPadding() / 2.0; break; default: minorArrowLineItemY1 = 0.0; diff --git a/src/charts/axis/valueaxis/chartvalueaxisx.cpp b/src/charts/axis/valueaxis/chartvalueaxisx.cpp index 3eac86e0..db943480 100644 --- a/src/charts/axis/valueaxis/chartvalueaxisx.cpp +++ b/src/charts/axis/valueaxis/chartvalueaxisx.cpp @@ -75,22 +75,21 @@ QVector<qreal> ChartValueAxisX::calculateLayout() const return points; } else { // QValueAxis::TicksDynamic const qreal interval = m_axis->tickInterval(); - qreal value = m_axis->tickAnchor(); + const qreal anchor = m_axis->tickAnchor(); const qreal maxValue = max(); const qreal minValue = min(); - // Find the first major tick right after the min of range - if (value > minValue) - value = value - int((value - minValue) / interval) * interval; - else - value = value + qCeil((minValue - value) / interval) * interval; + // Find the first major tick right after the min of the range + const qreal ticksFromAnchor = (anchor - minValue) / interval; + const qreal firstMajorTick = anchor - std::floor(ticksFromAnchor) * interval; const QRectF &gridRect = gridGeometry(); const qreal deltaX = gridRect.width() / (maxValue - minValue); QVector<qreal> points; const qreal leftPos = gridRect.left(); - while (value <= maxValue || qFuzzyCompare(value, maxValue)) { + qreal value = firstMajorTick; + while (value <= maxValue) { points << (value - minValue) * deltaX + leftPos; value += interval; } diff --git a/src/charts/axis/valueaxis/chartvalueaxisy.cpp b/src/charts/axis/valueaxis/chartvalueaxisy.cpp index c4868fc2..3ffeddf9 100644 --- a/src/charts/axis/valueaxis/chartvalueaxisy.cpp +++ b/src/charts/axis/valueaxis/chartvalueaxisy.cpp @@ -76,22 +76,21 @@ QVector<qreal> ChartValueAxisY::calculateLayout() const return points; } else { const qreal interval = m_axis->tickInterval(); - qreal value = m_axis->tickAnchor(); + const qreal anchor = m_axis->tickAnchor(); const qreal maxValue = max(); const qreal minValue = min(); - // Find the first major tick right after the min of range - if (value > minValue) - value = value - int((value - minValue) / interval) * interval; - else - value = value + qCeil((minValue - value) / interval) * interval; + // Find the first major tick right after the min of the range + const qreal ticksFromAnchor = (anchor - minValue) / interval; + const qreal firstMajorTick = anchor - std::floor(ticksFromAnchor) * interval; const QRectF &gridRect = gridGeometry(); const qreal deltaY = gridRect.height() / (maxValue - minValue); QVector<qreal> points; const qreal bottomPos = gridRect.bottom(); - while (value <= maxValue || qFuzzyCompare(value, maxValue)) { + qreal value = firstMajorTick; + while (value <= maxValue) { points << (value - minValue) * -deltaY + bottomPos; value += interval; } diff --git a/src/charts/axis/valueaxis/qvalueaxis.cpp b/src/charts/axis/valueaxis/qvalueaxis.cpp index 905168d7..90954483 100644 --- a/src/charts/axis/valueaxis/qvalueaxis.cpp +++ b/src/charts/axis/valueaxis/qvalueaxis.cpp @@ -200,7 +200,7 @@ QT_CHARTS_BEGIN_NAMESPACE \sa QString::asprintf() */ /*! - \qmlproperty real ValueAxis::labelFormat + \qmlproperty string ValueAxis::labelFormat The format string supports the following conversion specifiers, length modifiers, and flags provided by \c printf() in the standard C++ library: d, i, o, x, X, f, F, e, E, g, G, c. diff --git a/src/charts/chartpresenter.cpp b/src/charts/chartpresenter.cpp index 6be0b141..f482adf3 100644 --- a/src/charts/chartpresenter.cpp +++ b/src/charts/chartpresenter.cpp @@ -98,7 +98,7 @@ void ChartPresenter::setFixedGeometry(const QRectF &rect) void ChartPresenter::setGeometry(const QRectF rect) { - if (m_rect != rect) { + if (rect.isValid() && m_rect != rect) { m_rect = rect; if (!m_fixedRect.isNull()) return; @@ -478,18 +478,59 @@ ChartTitle *ChartPresenter::titleElement() return m_title; } +template <int TSize> +struct TextBoundCache +{ + struct element + { + quint32 lastUsed; + QRectF bounds; + }; + QHash<QString, element> elements; + quint32 usedCounter = 0; + QGraphicsTextItem dummyText; + + QRectF bounds(const QFont &font, const QString &text) + { + const QString key = font.key() + text; + auto elem = elements.find(key); + if (elem != elements.end()) { + usedCounter++; + elem->lastUsed = usedCounter; + return elem->bounds; + } + dummyText.setFont(font); + dummyText.setHtml(text); + const QRectF bounds = dummyText.boundingRect(); + if (elements.size() >= TSize) { + auto elem = std::min_element(elements.begin(), elements.end(), + [](const element &a, const element &b) { + return a.lastUsed < b.lastUsed; + }); + if (elem != elements.end()) { + const QString key = elem.key(); + elements.remove(key); + } + } + elements.insert(key, {usedCounter++, bounds}); + return bounds; + } + QTextDocument *document() + { + return dummyText.document(); + } +}; + QRectF ChartPresenter::textBoundingRect(const QFont &font, const QString &text, qreal angle) { - static QGraphicsTextItem dummyTextItem; + static TextBoundCache<32> textBoundCache; static bool initMargin = true; if (initMargin) { - dummyTextItem.document()->setDocumentMargin(textMargin()); + textBoundCache.document()->setDocumentMargin(textMargin()); initMargin = false; } - dummyTextItem.setFont(font); - dummyTextItem.setHtml(text); - QRectF boundingRect = dummyTextItem.boundingRect(); + QRectF boundingRect = textBoundCache.bounds(font, text); // Take rotation into account if (angle) { diff --git a/src/charts/domain/abstractdomain.cpp b/src/charts/domain/abstractdomain.cpp index 36e4974a..be50d7cf 100644 --- a/src/charts/domain/abstractdomain.cpp +++ b/src/charts/domain/abstractdomain.cpp @@ -58,6 +58,9 @@ AbstractDomain::~AbstractDomain() void AbstractDomain::setSize(const QSizeF &size) { + if (!size.isValid()) + return; + if (m_size != size) { m_size=size; emit updated(); diff --git a/src/charts/layout/abstractchartlayout.cpp b/src/charts/layout/abstractchartlayout.cpp index fd3c5e97..c612d197 100644 --- a/src/charts/layout/abstractchartlayout.cpp +++ b/src/charts/layout/abstractchartlayout.cpp @@ -53,6 +53,7 @@ void AbstractChartLayout::setGeometry(const QRectF &rect) { if (!rect.isValid()) return; + // If the chart has a fixed geometry then don't update visually const bool updateLayout = (!m_presenter->isFixedGeometry() || m_presenter->geometry() == rect); if (m_presenter->chart()->isVisible()) { @@ -73,12 +74,14 @@ void AbstractChartLayout::setGeometry(const QRectF &rect) contentGeometry = calculateAxisGeometry(contentGeometry, axes, updateLayout); - m_presenter->setGeometry(contentGeometry); - if (updateLayout) { - if (m_presenter->chart()->chartType() == QChart::ChartTypeCartesian) - static_cast<QGraphicsRectItem *>(m_presenter->plotAreaElement())->setRect(contentGeometry); - else - static_cast<QGraphicsEllipseItem *>(m_presenter->plotAreaElement())->setRect(contentGeometry); + if (contentGeometry.isValid()) { + m_presenter->setGeometry(contentGeometry); + if (updateLayout) { + if (m_presenter->chart()->chartType() == QChart::ChartTypeCartesian) + static_cast<QGraphicsRectItem *>(m_presenter->plotAreaElement())->setRect(contentGeometry); + else + static_cast<QGraphicsEllipseItem *>(m_presenter->plotAreaElement())->setRect(contentGeometry); + } } } diff --git a/src/charts/legend/legendmarkeritem.cpp b/src/charts/legend/legendmarkeritem.cpp index 2986b687..7121783d 100644 --- a/src/charts/legend/legendmarkeritem.cpp +++ b/src/charts/legend/legendmarkeritem.cpp @@ -126,7 +126,7 @@ QFont LegendMarkerItem::font() const void LegendMarkerItem::setLabel(const QString label) { m_label = label; - updateGeometry(); + m_marker->invalidateLegend(); } QString LegendMarkerItem::label() const diff --git a/src/charts/legend/qlegend.cpp b/src/charts/legend/qlegend.cpp index 6f5c2bb1..1a4f0ea2 100644 --- a/src/charts/legend/qlegend.cpp +++ b/src/charts/legend/qlegend.cpp @@ -772,7 +772,7 @@ void QLegendPrivate::handleCountChanged() } // Re-insert createdMarkers into m_markers in correct order. - if (pos != -1 || pos == m_markers.size()) { + if (pos == -1 || pos == m_markers.size()) { m_markers.append(createdMarkers); } else { for (int c = createdMarkers.size() - 1; c >= 0; --c) diff --git a/src/charts/piechart/qpieslice.cpp b/src/charts/piechart/qpieslice.cpp index f36f93a3..8ed844b6 100644 --- a/src/charts/piechart/qpieslice.cpp +++ b/src/charts/piechart/qpieslice.cpp @@ -108,11 +108,13 @@ QT_CHARTS_BEGIN_NAMESPACE /*! \property QPieSlice::label \brief The label of the slice. + \note The string can be HTML formatted. \sa labelVisible, labelBrush, labelFont, labelArmLengthFactor */ /*! \qmlproperty string PieSlice::label The label of the slice. + \note The string can be HTML formatted. */ /*! diff --git a/src/charts/xychart/qhxymodelmapper.cpp b/src/charts/xychart/qhxymodelmapper.cpp index f11201c2..b6f907b7 100644 --- a/src/charts/xychart/qhxymodelmapper.cpp +++ b/src/charts/xychart/qhxymodelmapper.cpp @@ -40,12 +40,14 @@ QT_CHARTS_BEGIN_NAMESPACE Model mappers enable using a data model derived from the QAbstractItemModel class as a data source for a chart. A horizontal model mapper is used to create a connection between a line, spline, or scatter series and the data - model that holds the consecutive data point coordinates on rows. + model that has \e X and \e Y rows for the coordinates and holds the data + points for the XYSeries as columns. A \e TableModel is a natural choice + for the model. Both model and series properties can be used to manipulate the data. The model mapper keeps the series and the data model in sync. - \sa QVXYModelMapper, QXYSeries + \sa QVXYModelMapper, QXYSeries, {Model Data Example} */ /*! \qmltype HXYModelMapper @@ -57,12 +59,14 @@ QT_CHARTS_BEGIN_NAMESPACE Model mappers enable using a data model derived from the QAbstractItemModel class as a data source for a chart. A horizontal model mapper is used to create a connection between a line, spline, or scatter series and the data - model that holds the consecutive data point coordinates on rows. + model that has \e X and \e Y rows for the coordinates and holds the data + points for the XYSeries as columns. A \e TableModel is a natural choice + for the model. Both model and series properties can be used to manipulate the data. The model mapper keeps the series and the data model in sync. - \sa VXYModelMapper, XYSeries + \sa VXYModelMapper, XYSeries, {Model Data Example} */ /*! diff --git a/src/charts/xychart/qvxymodelmapper.cpp b/src/charts/xychart/qvxymodelmapper.cpp index 9b5655b7..eaad1796 100644 --- a/src/charts/xychart/qvxymodelmapper.cpp +++ b/src/charts/xychart/qvxymodelmapper.cpp @@ -40,12 +40,14 @@ QT_CHARTS_BEGIN_NAMESPACE Model mappers enable using a data model derived from the QAbstractItemModel class as a data source for a chart. A vertical model mapper is used to create a connection between a line, spline, or scatter series and the data - model that holds the consecutive data point coordinates in columns. + model that has \e X and \e Y columns for the coordinates and holds the data + points for the XYSeries as rows. A \e TableModel is a natural choice + for the model. Both model and series properties can be used to manipulate the data. The model mapper keeps the series and the data model in sync. - \sa QHXYModelMapper, QXYSeries + \sa QHXYModelMapper, QXYSeries, {Model Data Example} */ /*! \qmltype VXYModelMapper @@ -57,12 +59,14 @@ QT_CHARTS_BEGIN_NAMESPACE Model mappers enable using a data model derived from the QAbstractItemModel class as a data source for a chart. A vertical model mapper is used to create a connection between a line, spline, or scatter series and the data - model that holds the consecutive data point coordinates in columns. + model that has \e X and \e Y columns for the coordinates and holds the data + points for the XYSeries as rows. A \e TableModel is a natural choice + for the model. Both model and series properties can be used to manipulate the data. The model mapper keeps the series and the data model in sync. - \sa HXYModelMapper, XYSeries + \sa HXYModelMapper, XYSeries, {Model Data Example} */ /*! diff --git a/src/charts/xychart/qxymodelmapper.cpp b/src/charts/xychart/qxymodelmapper.cpp index d1f2e9f5..553b67a6 100644 --- a/src/charts/xychart/qxymodelmapper.cpp +++ b/src/charts/xychart/qxymodelmapper.cpp @@ -367,31 +367,32 @@ void QXYModelMapperPrivate::modelUpdated(QModelIndex topLeft, QModelIndex bottom blockSeriesSignals(); QModelIndex index; - QPointF oldPoint; QPointF newPoint; + int indexColumn = 0; + int indexRow = 0; for (int row = topLeft.row(); row <= bottomRight.row(); row++) { for (int column = topLeft.column(); column <= bottomRight.column(); column++) { index = topLeft.sibling(row, column); - if (m_orientation == Qt::Vertical && (index.column() == m_xSection || index.column() == m_ySection)) { - if (index.row() >= m_first && (m_count == - 1 || index.row() < m_first + m_count)) { - QModelIndex xIndex = xModelIndex(index.row() - m_first); - QModelIndex yIndex = yModelIndex(index.row() - m_first); + indexColumn = index.column(); + indexRow = index.row(); + if (m_orientation == Qt::Vertical && (indexColumn == m_xSection || indexColumn == m_ySection)) { + if (indexRow >= m_first && (m_count == - 1 || indexRow < m_first + m_count)) { + QModelIndex xIndex = xModelIndex(indexRow - m_first); + QModelIndex yIndex = yModelIndex(indexRow - m_first); if (xIndex.isValid() && yIndex.isValid()) { - oldPoint = m_series->points().at(index.row() - m_first); newPoint.setX(valueFromModel(xIndex)); newPoint.setY(valueFromModel(yIndex)); - m_series->replace(index.row() - m_first, newPoint); + m_series->replace(indexRow - m_first, newPoint); } } - } else if (m_orientation == Qt::Horizontal && (index.row() == m_xSection || index.row() == m_ySection)) { - if (index.column() >= m_first && (m_count == - 1 || index.column() < m_first + m_count)) { - QModelIndex xIndex = xModelIndex(index.column() - m_first); - QModelIndex yIndex = yModelIndex(index.column() - m_first); + } else if (m_orientation == Qt::Horizontal && (indexRow == m_xSection || indexRow == m_ySection)) { + if (indexColumn >= m_first && (m_count == - 1 || indexColumn < m_first + m_count)) { + QModelIndex xIndex = xModelIndex(indexColumn - m_first); + QModelIndex yIndex = yModelIndex(indexColumn - m_first); if (xIndex.isValid() && yIndex.isValid()) { - oldPoint = m_series->points().at(index.column() - m_first); newPoint.setX(valueFromModel(xIndex)); newPoint.setY(valueFromModel(yIndex)); - m_series->replace(index.column() - m_first, newPoint); + m_series->replace(indexColumn - m_first, newPoint); } } } diff --git a/src/chartsqml2/declarativechart.cpp b/src/chartsqml2/declarativechart.cpp index a58e67c7..84fc9871 100644 --- a/src/chartsqml2/declarativechart.cpp +++ b/src/chartsqml2/declarativechart.cpp @@ -61,6 +61,7 @@ #include <QtCore/QTimer> #include <QtCore/QThread> #include <QtQuick/QQuickWindow> +#include <QtWidgets/QGraphicsLayout> QT_CHARTS_BEGIN_NAMESPACE @@ -632,9 +633,12 @@ void DeclarativeChart::seriesAxisAttachHelper(QAbstractSeries *series, QAbstract { if (!series->attachedAxes().contains(axis)) { // Remove & delete old axes that are not attached to any other series + // Detach old axis from series so that if it is shared with other series + // It can be deleted. foreach (QAbstractAxis* oldAxis, m_chart->axes(orientation, series)) { bool otherAttachments = false; if (oldAxis != axis) { + series->detachAxis(oldAxis); foreach (QAbstractSeries *oldSeries, m_chart->series()) { if (oldSeries != series && oldSeries->attachedAxes().contains(oldAxis)) { otherAttachments = true; @@ -1496,6 +1500,10 @@ QPointF DeclarativeChart::mapToPosition(const QPointF &value, QAbstractSeries *s void DeclarativeChart::setPlotArea(const QRectF &rect) { m_chart->setPlotArea(rect); + + // If plotArea is set inside ChartView, contentGeometry is updated too early and we end up + // with incorrect plotArea. Invalidate the layout to correct the geometry. + m_chart->layout()->invalidate(); } QT_CHARTS_END_NAMESPACE diff --git a/src/chartsqml2/declarativeopenglrendernode.cpp b/src/chartsqml2/declarativeopenglrendernode.cpp index 58f7aef6..ff15034c 100644 --- a/src/chartsqml2/declarativeopenglrendernode.cpp +++ b/src/chartsqml2/declarativeopenglrendernode.cpp @@ -229,7 +229,8 @@ void DeclarativeOpenGLRenderNode::setSeriesData(bool mapDirty, const GLXYDataMap GLXYSeriesData *data = oldMap.take(i.key()); const GLXYSeriesData *newData = i.value(); if (!data || newData->dirty) { - data = new GLXYSeriesData; + if (!data) + data = new GLXYSeriesData; *data = *newData; } m_xyDataMap.insert(i.key(), data); diff --git a/tests/auto/qbarseries/BLACKLIST b/tests/auto/qbarseries/BLACKLIST index a1b10c85..c8b10b7d 100644 --- a/tests/auto/qbarseries/BLACKLIST +++ b/tests/auto/qbarseries/BLACKLIST @@ -1,2 +1,4 @@ [mousehovered] macos +# QTBUG-111293 +sles-15 |