1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <private/abstractdomain_p.h> |
5 | #include <private/qabstractaxis_p.h> |
6 | #include <QtCore/QtMath> |
7 | #include <cmath> |
8 | |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | AbstractDomain::AbstractDomain(QObject *parent) |
12 | : QObject(parent), |
13 | m_minX(0), |
14 | m_maxX(0), |
15 | m_minY(0), |
16 | m_maxY(0), |
17 | m_signalsBlocked(false), |
18 | m_zoomed(false), |
19 | m_zoomResetMinX(0), |
20 | m_zoomResetMaxX(0), |
21 | m_zoomResetMinY(0), |
22 | m_zoomResetMaxY(0), |
23 | m_reverseX(false), |
24 | m_reverseY(false) |
25 | |
26 | { |
27 | } |
28 | |
29 | AbstractDomain::~AbstractDomain() |
30 | { |
31 | } |
32 | |
33 | void AbstractDomain::setSize(const QSizeF &size) |
34 | { |
35 | if (!size.isValid()) |
36 | return; |
37 | |
38 | if (m_size != size) { |
39 | m_size=size; |
40 | emit updated(); |
41 | } |
42 | } |
43 | |
44 | QSizeF AbstractDomain::size() const |
45 | { |
46 | return m_size; |
47 | } |
48 | |
49 | void AbstractDomain::setRangeX(qreal min, qreal max) |
50 | { |
51 | setRange(minX: min, maxX: max, minY: m_minY, maxY: m_maxY); |
52 | } |
53 | |
54 | void AbstractDomain::setRangeY(qreal min, qreal max) |
55 | { |
56 | setRange(minX: m_minX, maxX: m_maxX, minY: min, maxY: max); |
57 | } |
58 | |
59 | void AbstractDomain::setMinX(qreal min) |
60 | { |
61 | setRange(minX: min, maxX: m_maxX, minY: m_minY, maxY: m_maxY); |
62 | } |
63 | |
64 | void AbstractDomain::setMaxX(qreal max) |
65 | { |
66 | setRange(minX: m_minX, maxX: max, minY: m_minY, maxY: m_maxY); |
67 | } |
68 | |
69 | void AbstractDomain::setMinY(qreal min) |
70 | { |
71 | setRange(minX: m_minX, maxX: m_maxX, minY: min, maxY: m_maxY); |
72 | } |
73 | |
74 | void AbstractDomain::setMaxY(qreal max) |
75 | { |
76 | setRange(minX: m_minX, maxX: m_maxX, minY: m_minY, maxY: max); |
77 | } |
78 | |
79 | qreal AbstractDomain::spanX() const |
80 | { |
81 | Q_ASSERT(m_maxX >= m_minX); |
82 | return m_maxX - m_minX; |
83 | } |
84 | |
85 | qreal AbstractDomain::spanY() const |
86 | { |
87 | Q_ASSERT(m_maxY >= m_minY); |
88 | return m_maxY - m_minY; |
89 | } |
90 | |
91 | bool AbstractDomain::isEmpty() const |
92 | { |
93 | return qFuzzyCompare(p1: spanX(), p2: 0) || qFuzzyCompare(p1: spanY(), p2: 0) || m_size.isEmpty(); |
94 | } |
95 | |
96 | // handlers |
97 | |
98 | void AbstractDomain::handleVerticalAxisRangeChanged(qreal min, qreal max) |
99 | { |
100 | setRangeY(min, max); |
101 | } |
102 | |
103 | void AbstractDomain::handleHorizontalAxisRangeChanged(qreal min, qreal max) |
104 | { |
105 | setRangeX(min, max); |
106 | } |
107 | |
108 | void AbstractDomain::handleReverseXChanged(bool reverse) |
109 | { |
110 | m_reverseX = reverse; |
111 | emit updated(); |
112 | } |
113 | |
114 | void AbstractDomain::handleReverseYChanged(bool reverse) |
115 | { |
116 | m_reverseY = reverse; |
117 | emit updated(); |
118 | } |
119 | |
120 | void AbstractDomain::blockRangeSignals(bool block) |
121 | { |
122 | if (m_signalsBlocked!=block) { |
123 | m_signalsBlocked=block; |
124 | if (!block) { |
125 | emit rangeHorizontalChanged(min: m_minX,max: m_maxX); |
126 | emit rangeVerticalChanged(min: m_minY,max: m_maxY); |
127 | } |
128 | } |
129 | } |
130 | |
131 | void AbstractDomain::zoomReset() |
132 | { |
133 | if (m_zoomed) { |
134 | setRange(minX: m_zoomResetMinX, |
135 | maxX: m_zoomResetMaxX, |
136 | minY: m_zoomResetMinY, |
137 | maxY: m_zoomResetMaxY); |
138 | m_zoomed = false; |
139 | } |
140 | } |
141 | |
142 | void AbstractDomain::storeZoomReset() |
143 | { |
144 | if (!m_zoomed) { |
145 | m_zoomed = true; |
146 | m_zoomResetMinX = m_minX; |
147 | m_zoomResetMaxX = m_maxX; |
148 | m_zoomResetMinY = m_minY; |
149 | m_zoomResetMaxY = m_maxY; |
150 | } |
151 | } |
152 | |
153 | //algorithm defined by Paul S.Heckbert GraphicalGems I |
154 | |
155 | void AbstractDomain::looseNiceNumbers(qreal &min, qreal &max, int &ticksCount) |
156 | { |
157 | qreal range = niceNumber(x: max - min, ceiling: true); //range with ceiling |
158 | qreal step = niceNumber(x: range / (ticksCount - 1), ceiling: false); |
159 | min = std::floor(x: min / step); |
160 | max = std::ceil(x: max / step); |
161 | ticksCount = int(max - min) + 1; |
162 | min *= step; |
163 | max *= step; |
164 | } |
165 | |
166 | //nice numbers can be expressed as form of 1*10^n, 2* 10^n or 5*10^n |
167 | |
168 | qreal AbstractDomain::niceNumber(qreal x, bool ceiling) |
169 | { |
170 | qreal z = qPow(x: 10, y: qFloor(v: std::log10(x: x))); //find corresponding number of the form of 10^n than is smaller than x |
171 | qreal q = x / z; //q<10 && q>=1; |
172 | |
173 | if (ceiling) { |
174 | if (q <= 1.0) q = 1; |
175 | else if (q <= 2.0) q = 2; |
176 | else if (q <= 5.0) q = 5; |
177 | else q = 10; |
178 | } else { |
179 | if (q < 1.5) q = 1; |
180 | else if (q < 3.0) q = 2; |
181 | else if (q < 7.0) q = 5; |
182 | else q = 10; |
183 | } |
184 | return q * z; |
185 | } |
186 | |
187 | bool AbstractDomain::attachAxis(QAbstractAxis *axis) |
188 | { |
189 | if (axis->orientation() == Qt::Vertical) { |
190 | // Color axis isn't connected to range-related slots/signals as it doesn't need |
191 | // geometry domain and it doesn't need to handle zooming or scrolling. |
192 | if (axis->type() != QAbstractAxis::AxisTypeColor) { |
193 | QObject::connect(sender: axis->d_ptr.data(), SIGNAL(rangeChanged(qreal, qreal)), receiver: this, |
194 | SLOT(handleVerticalAxisRangeChanged(qreal, qreal))); |
195 | QObject::connect(sender: this, SIGNAL(rangeVerticalChanged(qreal, qreal)), receiver: axis->d_ptr.data(), |
196 | SLOT(handleRangeChanged(qreal, qreal))); |
197 | } |
198 | QObject::connect(sender: axis, signal: &QAbstractAxis::reverseChanged, |
199 | context: this, slot: &AbstractDomain::handleReverseYChanged); |
200 | m_reverseY = axis->isReverse(); |
201 | } |
202 | |
203 | if (axis->orientation() == Qt::Horizontal) { |
204 | if (axis->type() != QAbstractAxis::AxisTypeColor) { |
205 | QObject::connect(sender: axis->d_ptr.data(), SIGNAL(rangeChanged(qreal, qreal)), receiver: this, |
206 | SLOT(handleHorizontalAxisRangeChanged(qreal, qreal))); |
207 | QObject::connect(sender: this, SIGNAL(rangeHorizontalChanged(qreal, qreal)), receiver: axis->d_ptr.data(), |
208 | SLOT(handleRangeChanged(qreal, qreal))); |
209 | } |
210 | QObject::connect(sender: axis, signal: &QAbstractAxis::reverseChanged, |
211 | context: this, slot: &AbstractDomain::handleReverseXChanged); |
212 | m_reverseX = axis->isReverse(); |
213 | } |
214 | |
215 | return true; |
216 | } |
217 | |
218 | bool AbstractDomain::detachAxis(QAbstractAxis *axis) |
219 | { |
220 | if (axis->orientation() == Qt::Vertical) { |
221 | QObject::disconnect(sender: axis->d_ptr.data(), SIGNAL(rangeChanged(qreal,qreal)), receiver: this, SLOT(handleVerticalAxisRangeChanged(qreal,qreal))); |
222 | QObject::disconnect(sender: this, SIGNAL(rangeVerticalChanged(qreal,qreal)), receiver: axis->d_ptr.data(), SLOT(handleRangeChanged(qreal,qreal))); |
223 | QObject::disconnect(sender: axis, signal: &QAbstractAxis::reverseChanged, |
224 | receiver: this, slot: &AbstractDomain::handleReverseYChanged); |
225 | } |
226 | |
227 | if (axis->orientation() == Qt::Horizontal) { |
228 | QObject::disconnect(sender: axis->d_ptr.data(), SIGNAL(rangeChanged(qreal,qreal)), receiver: this, SLOT(handleHorizontalAxisRangeChanged(qreal,qreal))); |
229 | QObject::disconnect(sender: this, SIGNAL(rangeHorizontalChanged(qreal,qreal)), receiver: axis->d_ptr.data(), SLOT(handleRangeChanged(qreal,qreal))); |
230 | QObject::disconnect(sender: axis, signal: &QAbstractAxis::reverseChanged, |
231 | receiver: this, slot: &AbstractDomain::handleReverseXChanged); |
232 | } |
233 | |
234 | return true; |
235 | } |
236 | |
237 | // operators |
238 | |
239 | bool Q_AUTOTEST_EXPORT operator== (const AbstractDomain &domain1, const AbstractDomain &domain2) |
240 | { |
241 | return (qFuzzyIsNull(d: domain1.m_maxX - domain2.m_maxX) |
242 | && qFuzzyIsNull(d: domain1.m_maxY - domain2.m_maxY) |
243 | && qFuzzyIsNull(d: domain1.m_minX - domain2.m_minX) |
244 | && qFuzzyIsNull(d: domain1.m_minY - domain2.m_minY)); |
245 | } |
246 | |
247 | |
248 | bool Q_AUTOTEST_EXPORT operator!= (const AbstractDomain &domain1, const AbstractDomain &domain2) |
249 | { |
250 | return !(domain1 == domain2); |
251 | } |
252 | |
253 | |
254 | QDebug Q_AUTOTEST_EXPORT operator<<(QDebug dbg, const AbstractDomain &domain) |
255 | { |
256 | #ifdef QT_NO_TEXTSTREAM |
257 | Q_UNUSED(domain); |
258 | #else |
259 | dbg.nospace() << "AbstractDomain(" << domain.m_minX << ',' << domain.m_maxX << ',' << domain.m_minY << ',' << domain.m_maxY << ')' << domain.m_size; |
260 | #endif |
261 | return dbg.maybeSpace(); |
262 | } |
263 | |
264 | // This function adjusts min/max ranges to failsafe values if negative/zero values are attempted. |
265 | void AbstractDomain::adjustLogDomainRanges(qreal &min, qreal &max) |
266 | { |
267 | if (min <= 0) { |
268 | min = 1.0; |
269 | if (max <= min) |
270 | max = min + 1.0; |
271 | } |
272 | } |
273 | |
274 | // This function fixes the zoom rect based on axis reversals |
275 | QRectF AbstractDomain::fixZoomRect(const QRectF &rect) |
276 | { |
277 | QRectF fixRect = rect; |
278 | if (m_reverseX || m_reverseY) { |
279 | QPointF center = rect.center(); |
280 | if (m_reverseX) |
281 | center.setX(m_size.width() - center.x()); |
282 | if (m_reverseY) |
283 | center.setY(m_size.height() - center.y()); |
284 | fixRect.moveCenter(p: QPointF(center.x(), center.y())); |
285 | } |
286 | |
287 | return fixRect; |
288 | } |
289 | |
290 | QT_END_NAMESPACE |
291 | |
292 | #include "moc_abstractdomain_p.cpp" |
293 | |