| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Charts module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 or (at your option) any later version |
| ** approved by the KDE Free Qt Foundation. The licenses are as published by |
| ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| #include <private/chartpresenter_p.h> |
| #include <QtCharts/QChart> |
| #include <private/chartitem_p.h> |
| #include <private/qchart_p.h> |
| #include <QtCharts/QAbstractAxis> |
| #include <private/qabstractaxis_p.h> |
| #include <private/chartdataset_p.h> |
| #include <private/chartanimation_p.h> |
| #include <private/qabstractseries_p.h> |
| #include <QtCharts/QAreaSeries> |
| #include <private/chartaxiselement_p.h> |
| #include <private/chartbackground_p.h> |
| #include <private/cartesianchartlayout_p.h> |
| #include <private/polarchartlayout_p.h> |
| #include <private/charttitle_p.h> |
| #include <QtCore/QRegularExpression> |
| #include <QtCore/QTimer> |
| #include <QtGui/QTextDocument> |
| #include <QtWidgets/QGraphicsScene> |
| #include <QtWidgets/QGraphicsView> |
| |
| QT_CHARTS_BEGIN_NAMESPACE |
| |
| ChartPresenter::ChartPresenter(QChart *chart, QChart::ChartType type) |
| : QObject(chart), |
| m_chart(chart), |
| m_options(QChart::NoAnimation), |
| m_animationDuration(ChartAnimationDuration), |
| m_animationCurve(QEasingCurve::OutQuart), |
| m_state(ShowState), |
| m_background(0), |
| m_plotAreaBackground(0), |
| m_title(0), |
| m_localizeNumbers(false) |
| #ifndef QT_NO_OPENGL |
| , m_glWidget(0) |
| , m_glUseWidget(true) |
| #endif |
| { |
| if (type == QChart::ChartTypeCartesian) |
| m_layout = new CartesianChartLayout(this); |
| else if (type == QChart::ChartTypePolar) |
| m_layout = new PolarChartLayout(this); |
| Q_ASSERT(m_layout); |
| } |
| |
| ChartPresenter::~ChartPresenter() |
| { |
| #ifndef QT_NO_OPENGL |
| delete m_glWidget.data(); |
| #endif |
| } |
| |
| void ChartPresenter::setFixedGeometry(const QRectF &rect) |
| { |
| if (rect == m_fixedRect) |
| return; |
| const bool isSame = m_fixedRect == m_rect; |
| m_fixedRect = rect; |
| if (m_fixedRect.isNull()) { |
| // Update to the latest geometry properly if changed |
| if (!isSame) { |
| updateGeometry(m_rect); |
| m_layout->updateGeometry(); |
| } |
| } else { |
| updateGeometry(m_fixedRect); |
| } |
| } |
| |
| void ChartPresenter::setGeometry(const QRectF rect) |
| { |
| if (m_rect != rect) { |
| m_rect = rect; |
| if (!m_fixedRect.isNull()) |
| return; |
| updateGeometry(rect); |
| } |
| } |
| |
| void ChartPresenter::updateGeometry(const QRectF &rect) |
| { |
| foreach (ChartItem *chart, m_chartItems) { |
| chart->domain()->setSize(rect.size()); |
| chart->setPos(rect.topLeft()); |
| } |
| #ifndef QT_NO_OPENGL |
| if (!m_glWidget.isNull()) |
| m_glWidget->setGeometry(rect.toRect()); |
| #endif |
| emit plotAreaChanged(rect); |
| } |
| |
| QRectF ChartPresenter::geometry() const |
| { |
| return m_fixedRect.isNull() ? m_rect : m_fixedRect; |
| } |
| |
| void ChartPresenter::handleAxisAdded(QAbstractAxis *axis) |
| { |
| axis->d_ptr->initializeGraphics(rootItem()); |
| axis->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); |
| ChartAxisElement *item = axis->d_ptr->axisItem(); |
| item->setPresenter(this); |
| item->setThemeManager(m_chart->d_ptr->m_themeManager); |
| m_axisItems<<item; |
| m_axes<<axis; |
| m_layout->invalidate(); |
| } |
| |
| void ChartPresenter::handleAxisRemoved(QAbstractAxis *axis) |
| { |
| ChartAxisElement *item = axis->d_ptr->m_item.take(); |
| if (item->animation()) |
| item->animation()->stopAndDestroyLater(); |
| item->hide(); |
| item->disconnect(); |
| item->deleteLater(); |
| m_axisItems.removeAll(item); |
| m_axes.removeAll(axis); |
| m_layout->invalidate(); |
| } |
| |
| |
| void ChartPresenter::handleSeriesAdded(QAbstractSeries *series) |
| { |
| series->d_ptr->initializeGraphics(rootItem()); |
| series->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); |
| series->d_ptr->setPresenter(this); |
| ChartItem *chart = series->d_ptr->chartItem(); |
| chart->setPresenter(this); |
| chart->setThemeManager(m_chart->d_ptr->m_themeManager); |
| chart->setDataSet(m_chart->d_ptr->m_dataset); |
| chart->domain()->setSize(geometry().size()); |
| chart->setPos(geometry().topLeft()); |
| chart->handleDomainUpdated(); //this could be moved to intializeGraphics when animator is refactored |
| m_chartItems<<chart; |
| m_series<<series; |
| m_layout->invalidate(); |
| } |
| |
| void ChartPresenter::handleSeriesRemoved(QAbstractSeries *series) |
| { |
| ChartItem *chart = series->d_ptr->m_item.take(); |
| chart->hide(); |
| chart->cleanup(); |
| series->disconnect(chart); |
| chart->deleteLater(); |
| if (chart->animation()) |
| chart->animation()->stopAndDestroyLater(); |
| m_chartItems.removeAll(chart); |
| m_series.removeAll(series); |
| m_layout->invalidate(); |
| } |
| |
| void ChartPresenter::setAnimationOptions(QChart::AnimationOptions options) |
| { |
| if (m_options != options) { |
| QChart::AnimationOptions oldOptions = m_options; |
| m_options = options; |
| if (options.testFlag(QChart::SeriesAnimations) != oldOptions.testFlag(QChart::SeriesAnimations)) { |
| foreach (QAbstractSeries *series, m_series) |
| series->d_ptr->initializeAnimations(m_options, m_animationDuration, |
| m_animationCurve); |
| } |
| if (options.testFlag(QChart::GridAxisAnimations) != oldOptions.testFlag(QChart::GridAxisAnimations)) { |
| foreach (QAbstractAxis *axis, m_axes) |
| axis->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); |
| } |
| m_layout->invalidate(); // So that existing animations don't just stop halfway |
| } |
| } |
| |
| void ChartPresenter::setAnimationDuration(int msecs) |
| { |
| if (m_animationDuration != msecs) { |
| m_animationDuration = msecs; |
| foreach (QAbstractSeries *series, m_series) |
| series->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); |
| foreach (QAbstractAxis *axis, m_axes) |
| axis->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); |
| m_layout->invalidate(); // So that existing animations don't just stop halfway |
| } |
| } |
| |
| void ChartPresenter::setAnimationEasingCurve(const QEasingCurve &curve) |
| { |
| if (m_animationCurve != curve) { |
| m_animationCurve = curve; |
| foreach (QAbstractSeries *series, m_series) |
| series->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); |
| foreach (QAbstractAxis *axis, m_axes) |
| axis->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); |
| m_layout->invalidate(); // So that existing animations don't just stop halfway |
| } |
| } |
| |
| void ChartPresenter::setState(State state,QPointF point) |
| { |
| m_state=state; |
| m_statePoint=point; |
| } |
| |
| QChart::AnimationOptions ChartPresenter::animationOptions() const |
| { |
| return m_options; |
| } |
| |
| void ChartPresenter::createBackgroundItem() |
| { |
| if (!m_background) { |
| m_background = new ChartBackground(rootItem()); |
| m_background->setPen(Qt::NoPen); // Theme doesn't touch pen so don't use default |
| m_background->setBrush(QChartPrivate::defaultBrush()); |
| m_background->setZValue(ChartPresenter::BackgroundZValue); |
| } |
| } |
| |
| void ChartPresenter::createPlotAreaBackgroundItem() |
| { |
| if (!m_plotAreaBackground) { |
| if (m_chart->chartType() == QChart::ChartTypeCartesian) |
| m_plotAreaBackground = new QGraphicsRectItem(rootItem()); |
| else |
| m_plotAreaBackground = new QGraphicsEllipseItem(rootItem()); |
| // Use transparent pen instead of Qt::NoPen, as Qt::NoPen causes |
| // antialising artifacts with axis lines for some reason. |
| m_plotAreaBackground->setPen(QPen(Qt::transparent)); |
| m_plotAreaBackground->setBrush(Qt::NoBrush); |
| m_plotAreaBackground->setZValue(ChartPresenter::PlotAreaZValue); |
| m_plotAreaBackground->setVisible(false); |
| } |
| } |
| |
| void ChartPresenter::createTitleItem() |
| { |
| if (!m_title) { |
| m_title = new ChartTitle(rootItem()); |
| m_title->setZValue(ChartPresenter::BackgroundZValue); |
| } |
| } |
| |
| void ChartPresenter::startAnimation(ChartAnimation *animation) |
| { |
| animation->stop(); |
| QTimer::singleShot(0, animation, SLOT(startChartAnimation())); |
| } |
| |
| void ChartPresenter::setBackgroundBrush(const QBrush &brush) |
| { |
| createBackgroundItem(); |
| m_background->setBrush(brush); |
| m_layout->invalidate(); |
| } |
| |
| QBrush ChartPresenter::backgroundBrush() const |
| { |
| if (!m_background) |
| return QBrush(); |
| return m_background->brush(); |
| } |
| |
| void ChartPresenter::setBackgroundPen(const QPen &pen) |
| { |
| createBackgroundItem(); |
| m_background->setPen(pen); |
| m_layout->invalidate(); |
| } |
| |
| QPen ChartPresenter::backgroundPen() const |
| { |
| if (!m_background) |
| return QPen(); |
| return m_background->pen(); |
| } |
| |
| void ChartPresenter::setBackgroundRoundness(qreal diameter) |
| { |
| createBackgroundItem(); |
| m_background->setDiameter(diameter); |
| m_layout->invalidate(); |
| } |
| |
| qreal ChartPresenter::backgroundRoundness() const |
| { |
| if (!m_background) |
| return 0; |
| return m_background->diameter(); |
| } |
| |
| void ChartPresenter::setPlotAreaBackgroundBrush(const QBrush &brush) |
| { |
| createPlotAreaBackgroundItem(); |
| m_plotAreaBackground->setBrush(brush); |
| m_layout->invalidate(); |
| } |
| |
| QBrush ChartPresenter::plotAreaBackgroundBrush() const |
| { |
| if (!m_plotAreaBackground) |
| return QBrush(); |
| return m_plotAreaBackground->brush(); |
| } |
| |
| void ChartPresenter::setPlotAreaBackgroundPen(const QPen &pen) |
| { |
| createPlotAreaBackgroundItem(); |
| m_plotAreaBackground->setPen(pen); |
| m_layout->invalidate(); |
| } |
| |
| QPen ChartPresenter::plotAreaBackgroundPen() const |
| { |
| if (!m_plotAreaBackground) |
| return QPen(); |
| return m_plotAreaBackground->pen(); |
| } |
| |
| void ChartPresenter::setTitle(const QString &title) |
| { |
| createTitleItem(); |
| m_title->setText(title); |
| m_layout->invalidate(); |
| } |
| |
| QString ChartPresenter::title() const |
| { |
| if (!m_title) |
| return QString(); |
| return m_title->text(); |
| } |
| |
| void ChartPresenter::setTitleFont(const QFont &font) |
| { |
| createTitleItem(); |
| m_title->setFont(font); |
| m_layout->invalidate(); |
| } |
| |
| QFont ChartPresenter::titleFont() const |
| { |
| if (!m_title) |
| return QFont(); |
| return m_title->font(); |
| } |
| |
| void ChartPresenter::setTitleBrush(const QBrush &brush) |
| { |
| createTitleItem(); |
| m_title->setDefaultTextColor(brush.color()); |
| m_layout->invalidate(); |
| } |
| |
| QBrush ChartPresenter::titleBrush() const |
| { |
| if (!m_title) |
| return QBrush(); |
| return QBrush(m_title->defaultTextColor()); |
| } |
| |
| void ChartPresenter::setBackgroundVisible(bool visible) |
| { |
| createBackgroundItem(); |
| m_background->setVisible(visible); |
| } |
| |
| |
| bool ChartPresenter::isBackgroundVisible() const |
| { |
| if (!m_background) |
| return false; |
| return m_background->isVisible(); |
| } |
| |
| void ChartPresenter::setPlotAreaBackgroundVisible(bool visible) |
| { |
| createPlotAreaBackgroundItem(); |
| m_plotAreaBackground->setVisible(visible); |
| } |
| |
| bool ChartPresenter::isPlotAreaBackgroundVisible() const |
| { |
| if (!m_plotAreaBackground) |
| return false; |
| return m_plotAreaBackground->isVisible(); |
| } |
| |
| void ChartPresenter::setBackgroundDropShadowEnabled(bool enabled) |
| { |
| createBackgroundItem(); |
| m_background->setDropShadowEnabled(enabled); |
| } |
| |
| bool ChartPresenter::isBackgroundDropShadowEnabled() const |
| { |
| if (!m_background) |
| return false; |
| return m_background->isDropShadowEnabled(); |
| } |
| |
| void ChartPresenter::setLocalizeNumbers(bool localize) |
| { |
| m_localizeNumbers = localize; |
| m_layout->invalidate(); |
| } |
| |
| void ChartPresenter::setLocale(const QLocale &locale) |
| { |
| m_locale = locale; |
| m_layout->invalidate(); |
| } |
| |
| AbstractChartLayout *ChartPresenter::layout() |
| { |
| return m_layout; |
| } |
| |
| QLegend *ChartPresenter::legend() |
| { |
| return m_chart->legend(); |
| } |
| |
| void ChartPresenter::setVisible(bool visible) |
| { |
| m_chart->setVisible(visible); |
| } |
| |
| ChartBackground *ChartPresenter::backgroundElement() |
| { |
| return m_background; |
| } |
| |
| QAbstractGraphicsShapeItem *ChartPresenter::plotAreaElement() |
| { |
| return m_plotAreaBackground; |
| } |
| |
| QList<ChartAxisElement *> ChartPresenter::axisItems() const |
| { |
| return m_axisItems; |
| } |
| |
| QList<ChartItem *> ChartPresenter::chartItems() const |
| { |
| return m_chartItems; |
| } |
| |
| ChartTitle *ChartPresenter::titleElement() |
| { |
| return m_title; |
| } |
| |
| QRectF ChartPresenter::textBoundingRect(const QFont &font, const QString &text, qreal angle) |
| { |
| static QGraphicsTextItem dummyTextItem; |
| static bool initMargin = true; |
| if (initMargin) { |
| dummyTextItem.document()->setDocumentMargin(textMargin()); |
| initMargin = false; |
| } |
| |
| dummyTextItem.setFont(font); |
| dummyTextItem.setHtml(text); |
| QRectF boundingRect = dummyTextItem.boundingRect(); |
| |
| // Take rotation into account |
| if (angle) { |
| QTransform transform; |
| transform.rotate(angle); |
| boundingRect = transform.mapRect(boundingRect); |
| } |
| |
| return boundingRect; |
| } |
| |
| // boundingRect parameter returns the rotated bounding rect of the text |
| QString ChartPresenter::truncatedText(const QFont &font, const QString &text, qreal angle, |
| qreal maxWidth, qreal maxHeight, QRectF &boundingRect) |
| { |
| QString truncatedString(text); |
| boundingRect = textBoundingRect(font, truncatedString, angle); |
| if (boundingRect.width() > maxWidth || boundingRect.height() > maxHeight) { |
| // It can be assumed that almost any amount of string manipulation is faster |
| // than calculating one bounding rectangle, so first prepare a list of truncated strings |
| // to try. |
| static QRegularExpression truncateMatcher(QStringLiteral("&#?[0-9a-zA-Z]*;$")); |
| |
| QVector<QString> testStrings(text.length()); |
| int count(0); |
| static QLatin1Char closeTag('>'); |
| static QLatin1Char openTag('<'); |
| static QLatin1Char semiColon(';'); |
| static QLatin1String ellipsis("..."); |
| while (truncatedString.length() > 1) { |
| int chopIndex(-1); |
| int chopCount(1); |
| QChar lastChar(truncatedString.at(truncatedString.length() - 1)); |
| |
| if (lastChar == closeTag) |
| chopIndex = truncatedString.lastIndexOf(openTag); |
| else if (lastChar == semiColon) |
| chopIndex = truncatedString.indexOf(truncateMatcher); |
| |
| if (chopIndex != -1) |
| chopCount = truncatedString.length() - chopIndex; |
| truncatedString.chop(chopCount); |
| testStrings[count] = truncatedString + ellipsis; |
| count++; |
| } |
| |
| // Binary search for best fit |
| int minIndex(0); |
| int maxIndex(count - 1); |
| int bestIndex(count); |
| QRectF checkRect; |
| |
| while (maxIndex >= minIndex) { |
| int mid = (maxIndex + minIndex) / 2; |
| checkRect = textBoundingRect(font, testStrings.at(mid), angle); |
| if (checkRect.width() > maxWidth || checkRect.height() > maxHeight) { |
| // Checked index too large, all under this are also too large |
| minIndex = mid + 1; |
| } else { |
| // Checked index fits, all over this also fit |
| maxIndex = mid - 1; |
| bestIndex = mid; |
| boundingRect = checkRect; |
| } |
| } |
| // Default to "..." if nothing fits |
| if (bestIndex == count) { |
| boundingRect = textBoundingRect(font, ellipsis, angle); |
| truncatedString = ellipsis; |
| } else { |
| truncatedString = testStrings.at(bestIndex); |
| } |
| } |
| |
| return truncatedString; |
| } |
| |
| QString ChartPresenter::numberToString(double value, char f, int prec) |
| { |
| if (m_localizeNumbers) |
| return m_locale.toString(value, f, prec); |
| else |
| return QString::number(value, f, prec); |
| } |
| |
| QString ChartPresenter::numberToString(int value) |
| { |
| if (m_localizeNumbers) |
| return m_locale.toString(value); |
| else |
| return QString::number(value); |
| } |
| |
| void ChartPresenter::updateGLWidget() |
| { |
| #ifndef QT_NO_OPENGL |
| // GLWidget pointer is wrapped in QPointer as its parent is not in our control, and therefore |
| // can potentially get deleted unexpectedly. |
| if (!m_glWidget.isNull() && m_glWidget->needsReset()) { |
| m_glWidget->hide(); |
| delete m_glWidget.data(); |
| m_glWidget.clear(); |
| } |
| if (m_glWidget.isNull() && m_glUseWidget && m_chart->scene()) { |
| // Find the view of the scene. If the scene has multiple views, only the first view is |
| // chosen. |
| QList<QGraphicsView *> views = m_chart->scene()->views(); |
| if (views.size()) { |
| QGraphicsView *firstView = views.at(0); |
| m_glWidget = new GLWidget(m_chart->d_ptr->m_dataset->glXYSeriesDataManager(), |
| m_chart, firstView); |
| m_glWidget->setGeometry(geometry().toRect()); |
| m_glWidget->show(); |
| } |
| } |
| // Make sure we update the widget in a timely manner |
| if (!m_glWidget.isNull()) |
| m_glWidget->update(); |
| #endif |
| } |
| |
| QT_CHARTS_END_NAMESPACE |
| |
| #include "moc_chartpresenter_p.cpp" |