1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
5
6#include <private/chartpresenter_p.h>
7#include <QtCharts/QChart>
8#include <private/chartitem_p.h>
9#include <private/qchart_p.h>
10#include <QtCharts/QAbstractAxis>
11#include <private/qabstractaxis_p.h>
12#include <private/chartdataset_p.h>
13#include <private/chartanimation_p.h>
14#include <private/qabstractseries_p.h>
15#include <QtCharts/QAreaSeries>
16#include <private/chartaxiselement_p.h>
17#include <private/chartbackground_p.h>
18#include <private/cartesianchartlayout_p.h>
19#include <private/polarchartlayout_p.h>
20#include <private/charttitle_p.h>
21#include <QtCore/QRegularExpression>
22#include <QtCore/QTimer>
23#include <QtGui/QTextDocument>
24#include <QtWidgets/QGraphicsScene>
25#include <QtWidgets/QGraphicsView>
26
27QT_BEGIN_NAMESPACE
28
29ChartPresenter::ChartPresenter(QChart *chart, QChart::ChartType type)
30 : QObject(chart),
31 m_chart(chart),
32 m_options(QChart::NoAnimation),
33 m_animationDuration(ChartAnimationDuration),
34 m_animationCurve(QEasingCurve::OutQuart),
35 m_state(ShowState),
36 m_background(0),
37 m_plotAreaBackground(0),
38 m_title(0),
39 m_localizeNumbers(false)
40#ifndef QT_NO_OPENGL
41 , m_glWidget(0)
42 , m_glUseWidget(true)
43#endif
44{
45 if (type == QChart::ChartTypeCartesian)
46 m_layout = new CartesianChartLayout(this);
47 else if (type == QChart::ChartTypePolar)
48 m_layout = new PolarChartLayout(this);
49 Q_ASSERT(m_layout);
50}
51
52ChartPresenter::~ChartPresenter()
53{
54#ifndef QT_NO_OPENGL
55 delete m_glWidget.data();
56#endif
57}
58
59void ChartPresenter::setFixedGeometry(const QRectF &rect)
60{
61 if (rect == m_fixedRect)
62 return;
63 const bool isSame = m_fixedRect == m_rect;
64 m_fixedRect = rect;
65 if (m_fixedRect.isNull()) {
66 // Update to the latest geometry properly if changed
67 if (!isSame) {
68 updateGeometry(rect: m_rect);
69 m_layout->updateGeometry();
70 }
71 } else {
72 updateGeometry(rect: m_fixedRect);
73 }
74}
75
76void ChartPresenter::setGeometry(const QRectF rect)
77{
78 if (rect.isValid() && m_rect != rect) {
79 m_rect = rect;
80 if (!m_fixedRect.isNull())
81 return;
82 updateGeometry(rect);
83 }
84}
85
86void ChartPresenter::updateGeometry(const QRectF &rect)
87{
88 foreach (ChartItem *chart, m_chartItems) {
89 chart->domain()->setSize(rect.size());
90 chart->setPos(rect.topLeft());
91 }
92#ifndef QT_NO_OPENGL
93 if (!m_glWidget.isNull())
94 m_glWidget->setGeometry(rect.toRect());
95#endif
96 emit plotAreaChanged(plotArea: rect);
97}
98
99QRectF ChartPresenter::geometry() const
100{
101 return m_fixedRect.isNull() ? m_rect : m_fixedRect;
102}
103
104void ChartPresenter::handleAxisAdded(QAbstractAxis *axis)
105{
106 axis->d_ptr->initializeGraphics(parent: rootItem());
107 axis->d_ptr->initializeAnimations(options: m_options, duration: m_animationDuration, curve&: m_animationCurve);
108 ChartAxisElement *item = axis->d_ptr->axisItem();
109 item->setPresenter(this);
110 item->setThemeManager(m_chart->d_ptr->m_themeManager);
111 m_axisItems<<item;
112 m_axes<<axis;
113 m_layout->invalidate();
114}
115
116void ChartPresenter::handleAxisRemoved(QAbstractAxis *axis)
117{
118 ChartAxisElement *item = axis->d_ptr->m_item.release();
119 if (item->animation())
120 item->animation()->stopAndDestroyLater();
121 item->hide();
122 item->disconnect();
123 item->deleteLater();
124 m_axisItems.removeAll(t: item);
125 m_axes.removeAll(t: axis);
126 m_layout->invalidate();
127}
128
129
130void ChartPresenter::handleSeriesAdded(QAbstractSeries *series)
131{
132 series->d_ptr->initializeGraphics(parent: rootItem());
133 series->d_ptr->initializeAnimations(options: m_options, duration: m_animationDuration, curve&: m_animationCurve);
134 series->d_ptr->setPresenter(this);
135 ChartItem *chart = series->d_ptr->chartItem();
136 chart->setPresenter(this);
137 chart->setThemeManager(m_chart->d_ptr->m_themeManager);
138 chart->setDataSet(m_chart->d_ptr->m_dataset);
139 chart->domain()->setSize(geometry().size());
140 chart->setPos(geometry().topLeft());
141 chart->handleDomainUpdated(); //this could be moved to intializeGraphics when animator is refactored
142 m_chartItems<<chart;
143 m_series<<series;
144 m_layout->invalidate();
145}
146
147void ChartPresenter::handleSeriesRemoved(QAbstractSeries *series)
148{
149 ChartItem *chart = series->d_ptr->m_item.release();
150 chart->hide();
151 chart->cleanup();
152 series->disconnect(receiver: chart);
153 chart->deleteLater();
154 if (chart->animation())
155 chart->animation()->stopAndDestroyLater();
156 m_chartItems.removeAll(t: chart);
157 m_series.removeAll(t: series);
158 m_layout->invalidate();
159}
160
161void ChartPresenter::setAnimationOptions(QChart::AnimationOptions options)
162{
163 if (m_options != options) {
164 QChart::AnimationOptions oldOptions = m_options;
165 m_options = options;
166 if (options.testFlag(flag: QChart::SeriesAnimations) != oldOptions.testFlag(flag: QChart::SeriesAnimations)) {
167 foreach (QAbstractSeries *series, m_series)
168 series->d_ptr->initializeAnimations(options: m_options, duration: m_animationDuration,
169 curve&: m_animationCurve);
170 }
171 if (options.testFlag(flag: QChart::GridAxisAnimations) != oldOptions.testFlag(flag: QChart::GridAxisAnimations)) {
172 foreach (QAbstractAxis *axis, m_axes)
173 axis->d_ptr->initializeAnimations(options: m_options, duration: m_animationDuration, curve&: m_animationCurve);
174 }
175 m_layout->invalidate(); // So that existing animations don't just stop halfway
176 }
177}
178
179void ChartPresenter::setAnimationDuration(int msecs)
180{
181 if (m_animationDuration != msecs) {
182 m_animationDuration = msecs;
183 foreach (QAbstractSeries *series, m_series)
184 series->d_ptr->initializeAnimations(options: m_options, duration: m_animationDuration, curve&: m_animationCurve);
185 foreach (QAbstractAxis *axis, m_axes)
186 axis->d_ptr->initializeAnimations(options: m_options, duration: m_animationDuration, curve&: m_animationCurve);
187 m_layout->invalidate(); // So that existing animations don't just stop halfway
188 }
189}
190
191void ChartPresenter::setAnimationEasingCurve(const QEasingCurve &curve)
192{
193 if (m_animationCurve != curve) {
194 m_animationCurve = curve;
195 foreach (QAbstractSeries *series, m_series)
196 series->d_ptr->initializeAnimations(options: m_options, duration: m_animationDuration, curve&: m_animationCurve);
197 foreach (QAbstractAxis *axis, m_axes)
198 axis->d_ptr->initializeAnimations(options: m_options, duration: m_animationDuration, curve&: m_animationCurve);
199 m_layout->invalidate(); // So that existing animations don't just stop halfway
200 }
201}
202
203void ChartPresenter::setState(State state,QPointF point)
204{
205 m_state=state;
206 m_statePoint=point;
207}
208
209QChart::AnimationOptions ChartPresenter::animationOptions() const
210{
211 return m_options;
212}
213
214void ChartPresenter::createBackgroundItem()
215{
216 if (!m_background) {
217 m_background = new ChartBackground(rootItem());
218 m_background->setPen(Qt::NoPen); // Theme doesn't touch pen so don't use default
219 m_background->setBrush(QChartPrivate::defaultBrush());
220 m_background->setZValue(ChartPresenter::BackgroundZValue);
221 }
222}
223
224void ChartPresenter::createPlotAreaBackgroundItem()
225{
226 if (!m_plotAreaBackground) {
227 if (m_chart->chartType() == QChart::ChartTypeCartesian)
228 m_plotAreaBackground = new QGraphicsRectItem(rootItem());
229 else
230 m_plotAreaBackground = new QGraphicsEllipseItem(rootItem());
231 // Use transparent pen instead of Qt::NoPen, as Qt::NoPen causes
232 // antialising artifacts with axis lines for some reason.
233 m_plotAreaBackground->setAcceptedMouseButtons({});
234 m_plotAreaBackground->setPen(QPen(Qt::transparent));
235 m_plotAreaBackground->setBrush(Qt::NoBrush);
236 m_plotAreaBackground->setZValue(ChartPresenter::PlotAreaZValue);
237 m_plotAreaBackground->setVisible(false);
238 }
239}
240
241void ChartPresenter::createTitleItem()
242{
243 if (!m_title) {
244 m_title = new ChartTitle(rootItem());
245 m_title->setZValue(ChartPresenter::BackgroundZValue);
246 }
247}
248
249void ChartPresenter::startAnimation(ChartAnimation *animation)
250{
251 animation->stop();
252 QTimer::singleShot(msec: 0, receiver: animation, SLOT(startChartAnimation()));
253}
254
255void ChartPresenter::setBackgroundBrush(const QBrush &brush)
256{
257 createBackgroundItem();
258 m_background->setBrush(brush);
259 m_layout->invalidate();
260}
261
262QBrush ChartPresenter::backgroundBrush() const
263{
264 if (!m_background)
265 return QBrush();
266 return m_background->brush();
267}
268
269void ChartPresenter::setBackgroundPen(const QPen &pen)
270{
271 createBackgroundItem();
272 m_background->setPen(pen);
273 m_layout->invalidate();
274}
275
276QPen ChartPresenter::backgroundPen() const
277{
278 if (!m_background)
279 return QPen();
280 return m_background->pen();
281}
282
283void ChartPresenter::setBackgroundRoundness(qreal diameter)
284{
285 createBackgroundItem();
286 m_background->setDiameter(diameter);
287 m_layout->invalidate();
288}
289
290qreal ChartPresenter::backgroundRoundness() const
291{
292 if (!m_background)
293 return 0;
294 return m_background->diameter();
295}
296
297void ChartPresenter::setPlotAreaBackgroundBrush(const QBrush &brush)
298{
299 createPlotAreaBackgroundItem();
300 m_plotAreaBackground->setBrush(brush);
301 m_layout->invalidate();
302}
303
304QBrush ChartPresenter::plotAreaBackgroundBrush() const
305{
306 if (!m_plotAreaBackground)
307 return QBrush();
308 return m_plotAreaBackground->brush();
309}
310
311void ChartPresenter::setPlotAreaBackgroundPen(const QPen &pen)
312{
313 createPlotAreaBackgroundItem();
314 m_plotAreaBackground->setPen(pen);
315 m_layout->invalidate();
316}
317
318QPen ChartPresenter::plotAreaBackgroundPen() const
319{
320 if (!m_plotAreaBackground)
321 return QPen();
322 return m_plotAreaBackground->pen();
323}
324
325void ChartPresenter::setTitle(const QString &title)
326{
327 createTitleItem();
328 m_title->setText(title);
329 m_layout->invalidate();
330}
331
332QString ChartPresenter::title() const
333{
334 if (!m_title)
335 return QString();
336 return m_title->text();
337}
338
339void ChartPresenter::setTitleFont(const QFont &font)
340{
341 createTitleItem();
342 m_title->setFont(font);
343 m_layout->invalidate();
344}
345
346QFont ChartPresenter::titleFont() const
347{
348 if (!m_title)
349 return QFont();
350 return m_title->font();
351}
352
353void ChartPresenter::setTitleBrush(const QBrush &brush)
354{
355 createTitleItem();
356 m_title->setDefaultTextColor(brush.color());
357 m_layout->invalidate();
358}
359
360QBrush ChartPresenter::titleBrush() const
361{
362 if (!m_title)
363 return QBrush();
364 return QBrush(m_title->defaultTextColor());
365}
366
367void ChartPresenter::setBackgroundVisible(bool visible)
368{
369 createBackgroundItem();
370 m_background->setVisible(visible);
371}
372
373
374bool ChartPresenter::isBackgroundVisible() const
375{
376 if (!m_background)
377 return false;
378 return m_background->isVisible();
379}
380
381void ChartPresenter::setPlotAreaBackgroundVisible(bool visible)
382{
383 createPlotAreaBackgroundItem();
384 m_plotAreaBackground->setVisible(visible);
385}
386
387bool ChartPresenter::isPlotAreaBackgroundVisible() const
388{
389 if (!m_plotAreaBackground)
390 return false;
391 return m_plotAreaBackground->isVisible();
392}
393
394void ChartPresenter::setBackgroundDropShadowEnabled(bool enabled)
395{
396 createBackgroundItem();
397 m_background->setDropShadowEnabled(enabled);
398}
399
400bool ChartPresenter::isBackgroundDropShadowEnabled() const
401{
402 if (!m_background)
403 return false;
404 return m_background->isDropShadowEnabled();
405}
406
407void ChartPresenter::setLocalizeNumbers(bool localize)
408{
409 m_localizeNumbers = localize;
410 m_layout->invalidate();
411}
412
413void ChartPresenter::setLocale(const QLocale &locale)
414{
415 m_locale = locale;
416 m_layout->invalidate();
417}
418
419AbstractChartLayout *ChartPresenter::layout()
420{
421 return m_layout;
422}
423
424QLegend *ChartPresenter::legend()
425{
426 return m_chart->legend();
427}
428
429void ChartPresenter::setVisible(bool visible)
430{
431 m_chart->setVisible(visible);
432}
433
434ChartBackground *ChartPresenter::backgroundElement()
435{
436 return m_background;
437}
438
439QAbstractGraphicsShapeItem *ChartPresenter::plotAreaElement()
440{
441 return m_plotAreaBackground;
442}
443
444QList<ChartAxisElement *> ChartPresenter::axisItems() const
445{
446 return m_axisItems;
447}
448
449QList<ChartItem *> ChartPresenter::chartItems() const
450{
451 return m_chartItems;
452}
453
454ChartTitle *ChartPresenter::titleElement()
455{
456 return m_title;
457}
458
459template <int TSize>
460struct TextBoundCache
461{
462 struct element
463 {
464 quint32 lastUsed;
465 QRectF bounds;
466 };
467 QHash<QString, element> elements;
468 quint32 usedCounter = 0;
469 QGraphicsTextItem dummyText;
470
471 QRectF bounds(const QFont &font, const QString &text)
472 {
473 const QString key = font.key() + text;
474 auto elem = elements.find(key);
475 if (elem != elements.end()) {
476 usedCounter++;
477 elem->lastUsed = usedCounter;
478 return elem->bounds;
479 }
480 dummyText.setFont(font);
481 dummyText.setHtml(text);
482 const QRectF bounds = dummyText.boundingRect();
483 if (elements.size() >= TSize) {
484 auto elem = std::min_element(elements.begin(), elements.end(),
485 [](const element &a, const element &b) {
486 return a.lastUsed < b.lastUsed;
487 });
488 if (elem != elements.end()) {
489 const QString key = elem.key();
490 elements.remove(key);
491 }
492 }
493 elements.insert(key, {usedCounter++, bounds});
494 return bounds;
495 }
496 QTextDocument *document()
497 {
498 return dummyText.document();
499 }
500};
501
502QRectF ChartPresenter::textBoundingRect(const QFont &font, const QString &text, qreal angle)
503{
504 static TextBoundCache<32> textBoundCache;
505 static bool initMargin = true;
506 if (initMargin) {
507 textBoundCache.document()->setDocumentMargin(textMargin());
508 initMargin = false;
509 }
510
511 QRectF boundingRect = textBoundCache.bounds(font, text);
512
513 // Take rotation into account
514 if (angle) {
515 QTransform transform;
516 transform.rotate(a: angle);
517 boundingRect = transform.mapRect(boundingRect);
518 }
519
520 return boundingRect;
521}
522
523// boundingRect parameter returns the rotated bounding rect of the text
524QString ChartPresenter::truncatedText(const QFont &font, const QString &text, qreal angle,
525 qreal maxWidth, qreal maxHeight, QRectF &boundingRect)
526{
527 QString truncatedString(text);
528 boundingRect = textBoundingRect(font, text: truncatedString, angle);
529 if (boundingRect.width() > maxWidth || boundingRect.height() > maxHeight) {
530 // It can be assumed that almost any amount of string manipulation is faster
531 // than calculating one bounding rectangle, so first prepare a list of truncated strings
532 // to try.
533 static QRegularExpression truncateMatcher(QStringLiteral("&#?[0-9a-zA-Z]*;$"));
534
535 QList<QString> testStrings(text.size());
536 int count(0);
537 static QLatin1Char closeTag('>');
538 static QLatin1Char openTag('<');
539 static QLatin1Char semiColon(';');
540 static QLatin1String ellipsis("...");
541 while (truncatedString.size() > 1) {
542 int chopIndex(-1);
543 int chopCount(1);
544 QChar lastChar(truncatedString.at(i: truncatedString.size() - 1));
545
546 if (lastChar == closeTag)
547 chopIndex = truncatedString.lastIndexOf(c: openTag);
548 else if (lastChar == semiColon)
549 chopIndex = truncatedString.indexOf(re: truncateMatcher);
550
551 if (chopIndex != -1)
552 chopCount = truncatedString.size() - chopIndex;
553 truncatedString.chop(n: chopCount);
554 testStrings[count] = truncatedString + ellipsis;
555 count++;
556 }
557
558 // Binary search for best fit
559 int minIndex(0);
560 int maxIndex(count - 1);
561 int bestIndex(count);
562 QRectF checkRect;
563
564 while (maxIndex >= minIndex) {
565 int mid = (maxIndex + minIndex) / 2;
566 checkRect = textBoundingRect(font, text: testStrings.at(i: mid), angle);
567 if (checkRect.width() > maxWidth || checkRect.height() > maxHeight) {
568 // Checked index too large, all under this are also too large
569 minIndex = mid + 1;
570 } else {
571 // Checked index fits, all over this also fit
572 maxIndex = mid - 1;
573 bestIndex = mid;
574 boundingRect = checkRect;
575 }
576 }
577 // Default to "..." if nothing fits
578 if (bestIndex == count) {
579 boundingRect = textBoundingRect(font, text: ellipsis, angle);
580 truncatedString = ellipsis;
581 } else {
582 truncatedString = testStrings.at(i: bestIndex);
583 }
584 }
585
586 return truncatedString;
587}
588
589QString ChartPresenter::numberToString(double value, char f, int prec)
590{
591 if (m_localizeNumbers)
592 return m_locale.toString(f: value, format: f, precision: prec);
593 else
594 return QString::number(value, format: f, precision: prec);
595}
596
597QString ChartPresenter::numberToString(int value)
598{
599 if (m_localizeNumbers)
600 return m_locale.toString(i: value);
601 else
602 return QString::number(value);
603}
604
605void ChartPresenter::updateGLWidget()
606{
607#ifndef QT_NO_OPENGL
608 // GLWidget pointer is wrapped in QPointer as its parent is not in our control, and therefore
609 // can potentially get deleted unexpectedly.
610 if (!m_glWidget.isNull() && m_glWidget->needsReset()) {
611 m_glWidget->hide();
612 delete m_glWidget.data();
613 m_glWidget.clear();
614 }
615 if (m_glWidget.isNull() && m_glUseWidget && m_chart->scene()) {
616 // Find the view of the scene. If the scene has multiple views, only the first view is
617 // chosen.
618 QList<QGraphicsView *> views = m_chart->scene()->views();
619 if (views.size()) {
620 QGraphicsView *firstView = views.at(i: 0);
621 m_glWidget = new GLWidget(m_chart->d_ptr->m_dataset->glXYSeriesDataManager(),
622 m_chart, firstView);
623 m_glWidget->setGeometry(geometry().toRect());
624 m_glWidget->show();
625 }
626 }
627 // Make sure we update the widget in a timely manner
628 if (!m_glWidget.isNull())
629 m_glWidget->update();
630#endif
631}
632
633QT_END_NAMESPACE
634
635#include "moc_chartpresenter_p.cpp"
636

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtcharts/src/charts/chartpresenter.cpp