Vespucci  1.0.0
qcustomplot.cpp
Go to the documentation of this file.
1 /***************************************************************
2  * Vespucci-QCP, a very very minor modification to QCustomPlot *
3  * Modifications as noted in text (C) 2014 Daniel Foose *
4  * QCP IP notice repeated verbatim below *
5  * *************************************************************/
6 /***************************************************************************
7 ** **
8 ** QCustomPlot, an easy to use, modern plotting widget for Qt **
9 ** Copyright (C) 2011, 2012, 2013, 2014 Emanuel Eichhammer **
10 ** **
11 ** This program is free software: you can redistribute it and/or modify **
12 ** it under the terms of the GNU General Public License as published by **
13 ** the Free Software Foundation, either version 3 of the License, or **
14 ** (at your option) any later version. **
15 ** **
16 ** This program is distributed in the hope that it will be useful, **
17 ** but WITHOUT ANY WARRANTY; without even the implied warranty of **
18 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
19 ** GNU General Public License for more details. **
20 ** **
21 ** You should have received a copy of the GNU General Public License **
22 ** along with this program. If not, see http://www.gnu.org/licenses/. **
23 ** **
24 ****************************************************************************
25 ** Author: Emanuel Eichhammer **
26 ** Website/Contact: http://www.qcustomplot.com/ **
27 ** Date: 27.12.14 **
28 ** Version: 1.3.0 **
29 ****************************************************************************/
30 
31 #include "qcustomplot.h"
32 
33 
34 
38 
56  QPainter(),
57  mModes(pmDefault),
58  mIsAntialiasing(false)
59 {
60  // don't setRenderHint(QPainter::NonCosmeticDefautPen) here, because painter isn't active yet and
61  // a call to begin() will follow
62 }
63 
70 QCPPainter::QCPPainter(QPaintDevice *device) :
71  QPainter(device),
73  mIsAntialiasing(false)
74 {
75 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) // before Qt5, default pens used to be cosmetic if NonCosmeticDefaultPen flag isn't set. So we set it to get consistency across Qt versions.
76  if (isActive())
77  setRenderHint(QPainter::NonCosmeticDefaultPen);
78 #endif
79 }
80 
82 {
83 }
84 
91 void QCPPainter::setPen(const QPen &pen)
92 {
93  QPainter::setPen(pen);
94  if (mModes.testFlag(pmNonCosmetic))
96 }
97 
105 void QCPPainter::setPen(const QColor &color)
106 {
107  QPainter::setPen(color);
108  if (mModes.testFlag(pmNonCosmetic))
109  makeNonCosmetic();
110 }
111 
119 void QCPPainter::setPen(Qt::PenStyle penStyle)
120 {
121  QPainter::setPen(penStyle);
122  if (mModes.testFlag(pmNonCosmetic))
123  makeNonCosmetic();
124 }
125 
134 void QCPPainter::drawLine(const QLineF &line)
135 {
136  if (mIsAntialiasing || mModes.testFlag(pmVectorized))
137  QPainter::drawLine(line);
138  else
139  QPainter::drawLine(line.toLine());
140 }
141 
148 void QCPPainter::setAntialiasing(bool enabled)
149 {
150  setRenderHint(QPainter::Antialiasing, enabled);
151  if (mIsAntialiasing != enabled)
152  {
153  mIsAntialiasing = enabled;
154  if (!mModes.testFlag(pmVectorized)) // antialiasing half-pixel shift only needed for rasterized outputs
155  {
156  if (mIsAntialiasing)
157  translate(0.5, 0.5);
158  else
159  translate(-0.5, -0.5);
160  }
161  }
162 }
163 
168 void QCPPainter::setModes(QCPPainter::PainterModes modes)
169 {
170  mModes = modes;
171 }
172 
184 bool QCPPainter::begin(QPaintDevice *device)
185 {
186  bool result = QPainter::begin(device);
187 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) // before Qt5, default pens used to be cosmetic if NonCosmeticDefaultPen flag isn't set. So we set it to get consistency across Qt versions.
188  if (result)
189  setRenderHint(QPainter::NonCosmeticDefaultPen);
190 #endif
191  return result;
192 }
193 
200 {
201  if (!enabled && mModes.testFlag(mode))
202  mModes &= ~mode;
203  else if (enabled && !mModes.testFlag(mode))
204  mModes |= mode;
205 }
206 
216 {
218  QPainter::save();
219 }
220 
230 {
231  if (!mAntialiasingStack.isEmpty())
233  else
234  qDebug() << Q_FUNC_INFO << "Unbalanced save/restore";
235  QPainter::restore();
236 }
237 
243 {
244  if (qFuzzyIsNull(pen().widthF()))
245  {
246  QPen p = pen();
247  p.setWidth(1);
248  QPainter::setPen(p);
249  }
250 }
251 
252 
256 
318 /* start documentation of inline functions */
319 
339 /* end documentation of inline functions */
340 
348  mSize(6),
349  mShape(ssNone),
350  mPen(Qt::NoPen),
351  mBrush(Qt::NoBrush),
352  mPenDefined(false)
353 {
354 }
355 
364  mSize(size),
365  mShape(shape),
366  mPen(Qt::NoPen),
367  mBrush(Qt::NoBrush),
368  mPenDefined(false)
369 {
370 }
371 
377  mSize(size),
378  mShape(shape),
379  mPen(QPen(color)),
380  mBrush(Qt::NoBrush),
381  mPenDefined(true)
382 {
383 }
384 
389 QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QColor &color, const QColor &fill, double size) :
390  mSize(size),
391  mShape(shape),
392  mPen(QPen(color)),
393  mBrush(QBrush(fill)),
394  mPenDefined(true)
395 {
396 }
397 
413 QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QPen &pen, const QBrush &brush, double size) :
414  mSize(size),
415  mShape(shape),
416  mPen(pen),
417  mBrush(brush),
418  mPenDefined(pen.style() != Qt::NoPen)
419 {
420 }
421 
427  mSize(5),
428  mShape(ssPixmap),
429  mPen(Qt::NoPen),
430  mBrush(Qt::NoBrush),
431  mPixmap(pixmap),
432  mPenDefined(false)
433 {
434 }
435 
445 QCPScatterStyle::QCPScatterStyle(const QPainterPath &customPath, const QPen &pen, const QBrush &brush, double size) :
446  mSize(size),
447  mShape(ssCustom),
448  mPen(pen),
449  mBrush(brush),
450  mCustomPath(customPath),
451  mPenDefined(pen.style() != Qt::NoPen)
452 {
453 }
454 
461 {
462  mSize = size;
463 }
464 
474 {
475  mShape = shape;
476 }
477 
486 void QCPScatterStyle::setPen(const QPen &pen)
487 {
488  mPenDefined = true;
489  mPen = pen;
490 }
491 
498 void QCPScatterStyle::setBrush(const QBrush &brush)
499 {
500  mBrush = brush;
501 }
502 
510 void QCPScatterStyle::setPixmap(const QPixmap &pixmap)
511 {
513  mPixmap = pixmap;
514 }
515 
522 {
525 }
526 
536 void QCPScatterStyle::applyTo(QCPPainter *painter, const QPen &defaultPen) const
537 {
538  painter->setPen(mPenDefined ? mPen : defaultPen);
539  painter->setBrush(mBrush);
540 }
541 
550 void QCPScatterStyle::drawShape(QCPPainter *painter, QPointF pos) const
551 {
552  drawShape(painter, pos.x(), pos.y());
553 }
554 
558 void QCPScatterStyle::drawShape(QCPPainter *painter, double x, double y) const
559 {
560  double w = mSize/2.0;
561  switch (mShape)
562  {
563  case ssNone: break;
564  case ssDot:
565  {
566  painter->drawLine(QPointF(x, y), QPointF(x+0.0001, y));
567  break;
568  }
569  case ssCross:
570  {
571  painter->drawLine(QLineF(x-w, y-w, x+w, y+w));
572  painter->drawLine(QLineF(x-w, y+w, x+w, y-w));
573  break;
574  }
575  case ssPlus:
576  {
577  painter->drawLine(QLineF(x-w, y, x+w, y));
578  painter->drawLine(QLineF( x, y+w, x, y-w));
579  break;
580  }
581  case ssCircle:
582  {
583  painter->drawEllipse(QPointF(x , y), w, w);
584  break;
585  }
586  case ssDisc:
587  {
588  QBrush b = painter->brush();
589  painter->setBrush(painter->pen().color());
590  painter->drawEllipse(QPointF(x , y), w, w);
591  painter->setBrush(b);
592  break;
593  }
594  case ssSquare:
595  {
596  painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
597  break;
598  }
599  case ssDiamond:
600  {
601  painter->drawLine(QLineF(x-w, y, x, y-w));
602  painter->drawLine(QLineF( x, y-w, x+w, y));
603  painter->drawLine(QLineF(x+w, y, x, y+w));
604  painter->drawLine(QLineF( x, y+w, x-w, y));
605  break;
606  }
607  case ssStar:
608  {
609  painter->drawLine(QLineF(x-w, y, x+w, y));
610  painter->drawLine(QLineF( x, y+w, x, y-w));
611  painter->drawLine(QLineF(x-w*0.707, y-w*0.707, x+w*0.707, y+w*0.707));
612  painter->drawLine(QLineF(x-w*0.707, y+w*0.707, x+w*0.707, y-w*0.707));
613  break;
614  }
615  case ssTriangle:
616  {
617  painter->drawLine(QLineF(x-w, y+0.755*w, x+w, y+0.755*w));
618  painter->drawLine(QLineF(x+w, y+0.755*w, x, y-0.977*w));
619  painter->drawLine(QLineF( x, y-0.977*w, x-w, y+0.755*w));
620  break;
621  }
622  case ssTriangleInverted:
623  {
624  painter->drawLine(QLineF(x-w, y-0.755*w, x+w, y-0.755*w));
625  painter->drawLine(QLineF(x+w, y-0.755*w, x, y+0.977*w));
626  painter->drawLine(QLineF( x, y+0.977*w, x-w, y-0.755*w));
627  break;
628  }
629  case ssCrossSquare:
630  {
631  painter->drawLine(QLineF(x-w, y-w, x+w*0.95, y+w*0.95));
632  painter->drawLine(QLineF(x-w, y+w*0.95, x+w*0.95, y-w));
633  painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
634  break;
635  }
636  case ssPlusSquare:
637  {
638  painter->drawLine(QLineF(x-w, y, x+w*0.95, y));
639  painter->drawLine(QLineF( x, y+w, x, y-w));
640  painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
641  break;
642  }
643  case ssCrossCircle:
644  {
645  painter->drawLine(QLineF(x-w*0.707, y-w*0.707, x+w*0.670, y+w*0.670));
646  painter->drawLine(QLineF(x-w*0.707, y+w*0.670, x+w*0.670, y-w*0.707));
647  painter->drawEllipse(QPointF(x, y), w, w);
648  break;
649  }
650  case ssPlusCircle:
651  {
652  painter->drawLine(QLineF(x-w, y, x+w, y));
653  painter->drawLine(QLineF( x, y+w, x, y-w));
654  painter->drawEllipse(QPointF(x, y), w, w);
655  break;
656  }
657  case ssPeace:
658  {
659  painter->drawLine(QLineF(x, y-w, x, y+w));
660  painter->drawLine(QLineF(x, y, x-w*0.707, y+w*0.707));
661  painter->drawLine(QLineF(x, y, x+w*0.707, y+w*0.707));
662  painter->drawEllipse(QPointF(x, y), w, w);
663  break;
664  }
665  case ssPixmap:
666  {
667  painter->drawPixmap(x-mPixmap.width()*0.5, y-mPixmap.height()*0.5, mPixmap);
668  break;
669  }
670  case ssCustom:
671  {
672  QTransform oldTransform = painter->transform();
673  painter->translate(x, y);
674  painter->scale(mSize/6.0, mSize/6.0);
675  painter->drawPath(mCustomPath);
676  painter->setTransform(oldTransform);
677  break;
678  }
679  }
680 }
681 
682 
686 
729 /* start documentation of inline functions */
730 
745 /* end documentation of inline functions */
746 
755 QCPLayer::QCPLayer(QCustomPlot *parentPlot, const QString &layerName) :
756  QObject(parentPlot),
757  mParentPlot(parentPlot),
758  mName(layerName),
759  mIndex(-1), // will be set to a proper value by the QCustomPlot layer creation function
760  mVisible(true)
761 {
762  // Note: no need to make sure layerName is unique, because layer
763  // management is done with QCustomPlot functions.
764 }
765 
767 {
768  // If child layerables are still on this layer, detach them, so they don't try to reach back to this
769  // then invalid layer once they get deleted/moved themselves. This only happens when layers are deleted
770  // directly, like in the QCustomPlot destructor. (The regular layer removal procedure for the user is to
771  // call QCustomPlot::removeLayer, which moves all layerables off this layer before deleting it.)
772 
773  while (!mChildren.isEmpty())
774  mChildren.last()->setLayer(0); // removes itself from mChildren via removeChild()
775 
776  if (mParentPlot->currentLayer() == this)
777  qDebug() << Q_FUNC_INFO << "The parent plot's mCurrentLayer will be a dangling pointer. Should have been set to a valid layer or 0 beforehand.";
778 }
779 
789 {
790  mVisible = visible;
791 }
792 
803 void QCPLayer::addChild(QCPLayerable *layerable, bool prepend)
804 {
805  if (!mChildren.contains(layerable))
806  {
807  if (prepend)
808  mChildren.prepend(layerable);
809  else
810  mChildren.append(layerable);
811  } else
812  qDebug() << Q_FUNC_INFO << "layerable is already child of this layer" << reinterpret_cast<quintptr>(layerable);
813 }
814 
825 {
826  if (!mChildren.removeOne(layerable))
827  qDebug() << Q_FUNC_INFO << "layerable is not child of this layer" << reinterpret_cast<quintptr>(layerable);
828 }
829 
830 
834 
847 /* start documentation of inline functions */
848 
862 /* end documentation of inline functions */
863 /* start documentation of pure virtual functions */
864 
905 /* end documentation of pure virtual functions */
906 /* start documentation of signals */
907 
916 /* end documentation of signals */
917 
936 QCPLayerable::QCPLayerable(QCustomPlot *plot, QString targetLayer, QCPLayerable *parentLayerable) :
937  QObject(plot),
938  mVisible(true),
939  mParentPlot(plot),
940  mParentLayerable(parentLayerable),
941  mLayer(0),
942  mAntialiased(true)
943 {
944  if (mParentPlot)
945  {
946  if (targetLayer.isEmpty())
948  else if (!setLayer(targetLayer))
949  qDebug() << Q_FUNC_INFO << "setting QCPlayerable initial layer to" << targetLayer << "failed.";
950  }
951 }
952 
954 {
955  if (mLayer)
956  {
957  mLayer->removeChild(this);
958  mLayer = 0;
959  }
960 }
961 
968 {
969  mVisible = on;
970 }
971 
979 {
980  return moveToLayer(layer, false);
981 }
982 
988 bool QCPLayerable::setLayer(const QString &layerName)
989 {
990  if (!mParentPlot)
991  {
992  qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
993  return false;
994  }
995  if (QCPLayer *layer = mParentPlot->layer(layerName))
996  {
997  return setLayer(layer);
998  } else
999  {
1000  qDebug() << Q_FUNC_INFO << "there is no layer with name" << layerName;
1001  return false;
1002  }
1003 }
1004 
1012 {
1013  mAntialiased = enabled;
1014 }
1015 
1030 {
1031  return mVisible && (!mLayer || mLayer->visible()) && (!mParentLayerable || mParentLayerable.data()->realVisibility());
1032 }
1033 
1068 double QCPLayerable::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
1069 {
1070  Q_UNUSED(pos)
1071  Q_UNUSED(onlySelectable)
1072  Q_UNUSED(details)
1073  return -1.0;
1074 }
1075 
1094 {
1095  if (mParentPlot)
1096  {
1097  qDebug() << Q_FUNC_INFO << "called with mParentPlot already initialized";
1098  return;
1099  }
1100 
1101  if (!parentPlot)
1102  qDebug() << Q_FUNC_INFO << "called with parentPlot zero";
1103 
1106 }
1107 
1120 {
1122 }
1123 
1133 {
1134  if (layer && !mParentPlot)
1135  {
1136  qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
1137  return false;
1138  }
1139  if (layer && layer->parentPlot() != mParentPlot)
1140  {
1141  qDebug() << Q_FUNC_INFO << "layer" << layer->name() << "is not in same QCustomPlot as this layerable";
1142  return false;
1143  }
1144 
1145  QCPLayer *oldLayer = mLayer;
1146  if (mLayer)
1147  mLayer->removeChild(this);
1148  mLayer = layer;
1149  if (mLayer)
1150  mLayer->addChild(this, prepend);
1151  if (mLayer != oldLayer)
1152  emit layerChanged(mLayer);
1153  return true;
1154 }
1155 
1163 void QCPLayerable::applyAntialiasingHint(QCPPainter *painter, bool localAntialiased, QCP::AntialiasedElement overrideElement) const
1164 {
1165  if (mParentPlot && mParentPlot->notAntialiasedElements().testFlag(overrideElement))
1166  painter->setAntialiasing(false);
1167  else if (mParentPlot && mParentPlot->antialiasedElements().testFlag(overrideElement))
1168  painter->setAntialiasing(true);
1169  else
1170  painter->setAntialiasing(localAntialiased);
1171 }
1172 
1190 {
1191  Q_UNUSED(parentPlot)
1192 }
1193 
1206 {
1207  return QCP::iSelectOther;
1208 }
1209 
1220 {
1221  if (mParentPlot)
1222  return mParentPlot->viewport();
1223  else
1224  return QRect();
1225 }
1226 
1255 void QCPLayerable::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
1256 {
1257  Q_UNUSED(event)
1258  Q_UNUSED(additive)
1259  Q_UNUSED(details)
1260  Q_UNUSED(selectionStateChanged)
1261 }
1262 
1275 void QCPLayerable::deselectEvent(bool *selectionStateChanged)
1276 {
1277  Q_UNUSED(selectionStateChanged)
1278 }
1279 
1280 
1284 
1299 const double QCPRange::minRange = 1e-280;
1300 
1309 const double QCPRange::maxRange = 1e250;
1310 
1315  lower(0),
1316  upper(0)
1317 {
1318 }
1319 
1323 QCPRange::QCPRange(double lower, double upper) :
1324  lower(lower),
1325  upper(upper)
1326 {
1327  normalize();
1328 }
1329 
1333 double QCPRange::size() const
1334 {
1335  return upper-lower;
1336 }
1337 
1341 double QCPRange::center() const
1342 {
1343  return (upper+lower)*0.5;
1344 }
1345 
1351 {
1352  if (lower > upper)
1353  qSwap(lower, upper);
1354 }
1355 
1364 void QCPRange::expand(const QCPRange &otherRange)
1365 {
1366  if (lower > otherRange.lower)
1367  lower = otherRange.lower;
1368  if (upper < otherRange.upper)
1369  upper = otherRange.upper;
1370 }
1371 
1372 
1379 QCPRange QCPRange::expanded(const QCPRange &otherRange) const
1380 {
1381  QCPRange result = *this;
1382  result.expand(otherRange);
1383  return result;
1384 }
1385 
1399 {
1400  double rangeFac = 1e-3;
1401  QCPRange sanitizedRange(lower, upper);
1402  sanitizedRange.normalize();
1403  // can't have range spanning negative and positive values in log plot, so change range to fix it
1404  //if (qFuzzyCompare(sanitizedRange.lower+1, 1) && !qFuzzyCompare(sanitizedRange.upper+1, 1))
1405  if (sanitizedRange.lower == 0.0 && sanitizedRange.upper != 0.0)
1406  {
1407  // case lower is 0
1408  if (rangeFac < sanitizedRange.upper*rangeFac)
1409  sanitizedRange.lower = rangeFac;
1410  else
1411  sanitizedRange.lower = sanitizedRange.upper*rangeFac;
1412  } //else if (!qFuzzyCompare(lower+1, 1) && qFuzzyCompare(upper+1, 1))
1413  else if (sanitizedRange.lower != 0.0 && sanitizedRange.upper == 0.0)
1414  {
1415  // case upper is 0
1416  if (-rangeFac > sanitizedRange.lower*rangeFac)
1417  sanitizedRange.upper = -rangeFac;
1418  else
1419  sanitizedRange.upper = sanitizedRange.lower*rangeFac;
1420  } else if (sanitizedRange.lower < 0 && sanitizedRange.upper > 0)
1421  {
1422  // find out whether negative or positive interval is wider to decide which sign domain will be chosen
1423  if (-sanitizedRange.lower > sanitizedRange.upper)
1424  {
1425  // negative is wider, do same as in case upper is 0
1426  if (-rangeFac > sanitizedRange.lower*rangeFac)
1427  sanitizedRange.upper = -rangeFac;
1428  else
1429  sanitizedRange.upper = sanitizedRange.lower*rangeFac;
1430  } else
1431  {
1432  // positive is wider, do same as in case lower is 0
1433  if (rangeFac < sanitizedRange.upper*rangeFac)
1434  sanitizedRange.lower = rangeFac;
1435  else
1436  sanitizedRange.lower = sanitizedRange.upper*rangeFac;
1437  }
1438  }
1439  // due to normalization, case lower>0 && upper<0 should never occur, because that implies upper<lower
1440  return sanitizedRange;
1441 }
1442 
1448 {
1449  QCPRange sanitizedRange(lower, upper);
1450  sanitizedRange.normalize();
1451  return sanitizedRange;
1452 }
1453 
1457 bool QCPRange::contains(double value) const
1458 {
1459  return value >= lower && value <= upper;
1460 }
1461 
1470 bool QCPRange::validRange(double lower, double upper)
1471 {
1472  /*
1473  return (lower > -maxRange &&
1474  upper < maxRange &&
1475  qAbs(lower-upper) > minRange &&
1476  (lower < -minRange || lower > minRange) &&
1477  (upper < -minRange || upper > minRange));
1478  */
1479  return (lower > -maxRange &&
1480  upper < maxRange &&
1481  qAbs(lower-upper) > minRange &&
1482  qAbs(lower-upper) < maxRange);
1483 }
1484 
1494 bool QCPRange::validRange(const QCPRange &range)
1495 {
1496  /*
1497  return (range.lower > -maxRange &&
1498  range.upper < maxRange &&
1499  qAbs(range.lower-range.upper) > minRange &&
1500  qAbs(range.lower-range.upper) < maxRange &&
1501  (range.lower < -minRange || range.lower > minRange) &&
1502  (range.upper < -minRange || range.upper > minRange));
1503  */
1504  return (range.lower > -maxRange &&
1505  range.upper < maxRange &&
1506  qAbs(range.lower-range.upper) > minRange &&
1507  qAbs(range.lower-range.upper) < maxRange);
1508 }
1509 
1510 
1514 
1551 /* start documentation of inline functions */
1552 
1559 /* end documentation of inline functions */
1560 
1565  QObject(parentPlot),
1566  mParentPlot(parentPlot)
1567 {
1568  mChildren.insert(QCP::msLeft, QList<QCPLayoutElement*>());
1569  mChildren.insert(QCP::msRight, QList<QCPLayoutElement*>());
1570  mChildren.insert(QCP::msTop, QList<QCPLayoutElement*>());
1571  mChildren.insert(QCP::msBottom, QList<QCPLayoutElement*>());
1572 }
1573 
1575 {
1576  clear();
1577 }
1578 
1584 {
1585  QHashIterator<QCP::MarginSide, QList<QCPLayoutElement*> > it(mChildren);
1586  while (it.hasNext())
1587  {
1588  it.next();
1589  if (!it.value().isEmpty())
1590  return false;
1591  }
1592  return true;
1593 }
1594 
1600 {
1601  // make all children remove themselves from this margin group:
1602  QHashIterator<QCP::MarginSide, QList<QCPLayoutElement*> > it(mChildren);
1603  while (it.hasNext())
1604  {
1605  it.next();
1606  const QList<QCPLayoutElement*> elements = it.value();
1607  for (int i=elements.size()-1; i>=0; --i)
1608  elements.at(i)->setMarginGroup(it.key(), 0); // removes itself from mChildren via removeChild
1609  }
1610 }
1611 
1623 {
1624  // query all automatic margins of the layout elements in this margin group side and find maximum:
1625  int result = 0;
1626  const QList<QCPLayoutElement*> elements = mChildren.value(side);
1627  for (int i=0; i<elements.size(); ++i)
1628  {
1629  if (!elements.at(i)->autoMargins().testFlag(side))
1630  continue;
1631  int m = qMax(elements.at(i)->calculateAutoMargin(side), QCP::getMarginValue(elements.at(i)->minimumMargins(), side));
1632  if (m > result)
1633  result = m;
1634  }
1635  return result;
1636 }
1637 
1645 {
1646  if (!mChildren[side].contains(element))
1647  mChildren[side].append(element);
1648  else
1649  qDebug() << Q_FUNC_INFO << "element is already child of this margin group side" << reinterpret_cast<quintptr>(element);
1650 }
1651 
1659 {
1660  if (!mChildren[side].removeOne(element))
1661  qDebug() << Q_FUNC_INFO << "element is not child of this margin group side" << reinterpret_cast<quintptr>(element);
1662 }
1663 
1664 
1668 
1695 /* start documentation of inline functions */
1696 
1744 /* end documentation of inline functions */
1745 
1750  QCPLayerable(parentPlot), // parenthood is changed as soon as layout element gets inserted into a layout (except for top level layout)
1751  mParentLayout(0),
1752  mMinimumSize(),
1753  mMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX),
1754  mRect(0, 0, 0, 0),
1755  mOuterRect(0, 0, 0, 0),
1756  mMargins(0, 0, 0, 0),
1757  mMinimumMargins(0, 0, 0, 0),
1758  mAutoMargins(QCP::msAll)
1759 {
1760 }
1761 
1763 {
1764  setMarginGroup(QCP::msAll, 0); // unregister at margin groups, if there are any
1765  // unregister at layout:
1766  if (qobject_cast<QCPLayout*>(mParentLayout)) // the qobject_cast is just a safeguard in case the layout forgets to call clear() in its dtor and this dtor is called by QObject dtor
1767  mParentLayout->take(this);
1768 }
1769 
1782 {
1783  if (mOuterRect != rect)
1784  {
1785  mOuterRect = rect;
1786  mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom());
1787  }
1788 }
1789 
1802 {
1803  if (mMargins != margins)
1804  {
1805  mMargins = margins;
1806  mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom());
1807  }
1808 }
1809 
1820 {
1821  if (mMinimumMargins != margins)
1822  {
1824  }
1825 }
1826 
1837 void QCPLayoutElement::setAutoMargins(QCP::MarginSides sides)
1838 {
1839  mAutoMargins = sides;
1840 }
1841 
1851 void QCPLayoutElement::setMinimumSize(const QSize &size)
1852 {
1853  if (mMinimumSize != size)
1854  {
1855  mMinimumSize = size;
1856  if (mParentLayout)
1858  }
1859 }
1860 
1865 void QCPLayoutElement::setMinimumSize(int width, int height)
1866 {
1867  setMinimumSize(QSize(width, height));
1868 }
1869 
1874 void QCPLayoutElement::setMaximumSize(const QSize &size)
1875 {
1876  if (mMaximumSize != size)
1877  {
1878  mMaximumSize = size;
1879  if (mParentLayout)
1881  }
1882 }
1883 
1888 void QCPLayoutElement::setMaximumSize(int width, int height)
1889 {
1890  setMaximumSize(QSize(width, height));
1891 }
1892 
1904 void QCPLayoutElement::setMarginGroup(QCP::MarginSides sides, QCPMarginGroup *group)
1905 {
1906  QVector<QCP::MarginSide> sideVector;
1907  if (sides.testFlag(QCP::msLeft)) sideVector.append(QCP::msLeft);
1908  if (sides.testFlag(QCP::msRight)) sideVector.append(QCP::msRight);
1909  if (sides.testFlag(QCP::msTop)) sideVector.append(QCP::msTop);
1910  if (sides.testFlag(QCP::msBottom)) sideVector.append(QCP::msBottom);
1911 
1912  for (int i=0; i<sideVector.size(); ++i)
1913  {
1914  QCP::MarginSide side = sideVector.at(i);
1915  if (marginGroup(side) != group)
1916  {
1917  QCPMarginGroup *oldGroup = marginGroup(side);
1918  if (oldGroup) // unregister at old group
1919  oldGroup->removeChild(side, this);
1920 
1921  if (!group) // if setting to 0, remove hash entry. Else set hash entry to new group and register there
1922  {
1923  mMarginGroups.remove(side);
1924  } else // setting to a new group
1925  {
1926  mMarginGroups[side] = group;
1927  group->addChild(side, this);
1928  }
1929  }
1930  }
1931 }
1932 
1946 {
1947  if (phase == upMargins)
1948  {
1949  if (mAutoMargins != QCP::msNone)
1950  {
1951  // set the margins of this layout element according to automatic margin calculation, either directly or via a margin group:
1952  QMargins newMargins = mMargins;
1953  foreach (QCP::MarginSide side, QList<QCP::MarginSide>() << QCP::msLeft << QCP::msRight << QCP::msTop << QCP::msBottom)
1954  {
1955  if (mAutoMargins.testFlag(side)) // this side's margin shall be calculated automatically
1956  {
1957  if (mMarginGroups.contains(side))
1958  QCP::setMarginValue(newMargins, side, mMarginGroups[side]->commonMargin(side)); // this side is part of a margin group, so get the margin value from that group
1959  else
1960  QCP::setMarginValue(newMargins, side, calculateAutoMargin(side)); // this side is not part of a group, so calculate the value directly
1961  // apply minimum margin restrictions:
1962  if (QCP::getMarginValue(newMargins, side) < QCP::getMarginValue(mMinimumMargins, side))
1963  QCP::setMarginValue(newMargins, side, QCP::getMarginValue(mMinimumMargins, side));
1964  }
1965  }
1966  setMargins(newMargins);
1967  }
1968  }
1969 }
1970 
1979 {
1980  return mMinimumSize;
1981 }
1982 
1991 {
1992  return mMaximumSize;
1993 }
1994 
2002 QList<QCPLayoutElement*> QCPLayoutElement::elements(bool recursive) const
2003 {
2004  Q_UNUSED(recursive)
2005  return QList<QCPLayoutElement*>();
2006 }
2007 
2019 double QCPLayoutElement::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
2020 {
2021  Q_UNUSED(details)
2022 
2023  if (onlySelectable)
2024  return -1;
2025 
2026  if (QRectF(mOuterRect).contains(pos))
2027  {
2028  if (mParentPlot)
2029  return mParentPlot->selectionTolerance()*0.99;
2030  else
2031  {
2032  qDebug() << Q_FUNC_INFO << "parent plot not defined";
2033  return -1;
2034  }
2035  } else
2036  return -1;
2037 }
2038 
2045 {
2046  foreach (QCPLayoutElement* el, elements(false))
2047  {
2048  if (!el->parentPlot())
2049  el->initializeParentPlot(parentPlot);
2050  }
2051 }
2052 
2063 {
2065 }
2066 
2070 
2094 /* start documentation of pure virtual functions */
2095 
2138 /* end documentation of pure virtual functions */
2139 
2145 {
2146 }
2147 
2157 {
2158  QCPLayoutElement::update(phase);
2159 
2160  // set child element rects according to layout:
2161  if (phase == upLayout)
2162  updateLayout();
2163 
2164  // propagate update call to child elements:
2165  const int elCount = elementCount();
2166  for (int i=0; i<elCount; ++i)
2167  {
2168  if (QCPLayoutElement *el = elementAt(i))
2169  el->update(phase);
2170  }
2171 }
2172 
2173 /* inherits documentation from base class */
2174 QList<QCPLayoutElement*> QCPLayout::elements(bool recursive) const
2175 {
2176  const int c = elementCount();
2177  QList<QCPLayoutElement*> result;
2178 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
2179  result.reserve(c);
2180 #endif
2181  for (int i=0; i<c; ++i)
2182  result.append(elementAt(i));
2183  if (recursive)
2184  {
2185  for (int i=0; i<c; ++i)
2186  {
2187  if (result.at(i))
2188  result << result.at(i)->elements(recursive);
2189  }
2190  }
2191  return result;
2192 }
2193 
2202 {
2203 }
2204 
2214 bool QCPLayout::removeAt(int index)
2215 {
2216  if (QCPLayoutElement *el = takeAt(index))
2217  {
2218  delete el;
2219  return true;
2220  } else
2221  return false;
2222 }
2223 
2234 {
2235  if (take(element))
2236  {
2237  delete element;
2238  return true;
2239  } else
2240  return false;
2241 }
2242 
2249 {
2250  for (int i=elementCount()-1; i>=0; --i)
2251  {
2252  if (elementAt(i))
2253  removeAt(i);
2254  }
2255  simplify();
2256 }
2257 
2267 {
2268  if (QWidget *w = qobject_cast<QWidget*>(parent()))
2269  w->updateGeometry();
2270  else if (QCPLayout *l = qobject_cast<QCPLayout*>(parent()))
2271  l->sizeConstraintsChanged();
2272 }
2273 
2287 {
2288 }
2289 
2290 
2304 {
2305  if (el)
2306  {
2307  el->mParentLayout = this;
2308  el->setParentLayerable(this);
2309  el->setParent(this);
2310  if (!el->parentPlot())
2312  } else
2313  qDebug() << Q_FUNC_INFO << "Null element passed";
2314 }
2315 
2327 {
2328  if (el)
2329  {
2330  el->mParentLayout = 0;
2331  el->setParentLayerable(0);
2332  el->setParent(mParentPlot);
2333  // Note: Don't initializeParentPlot(0) here, because layout element will stay in same parent plot
2334  } else
2335  qDebug() << Q_FUNC_INFO << "Null element passed";
2336 }
2337 
2367 QVector<int> QCPLayout::getSectionSizes(QVector<int> maxSizes, QVector<int> minSizes, QVector<double> stretchFactors, int totalSize) const
2368 {
2369  if (maxSizes.size() != minSizes.size() || minSizes.size() != stretchFactors.size())
2370  {
2371  qDebug() << Q_FUNC_INFO << "Passed vector sizes aren't equal:" << maxSizes << minSizes << stretchFactors;
2372  return QVector<int>();
2373  }
2374  if (stretchFactors.isEmpty())
2375  return QVector<int>();
2376  int sectionCount = stretchFactors.size();
2377  QVector<double> sectionSizes(sectionCount);
2378  // if provided total size is forced smaller than total minimum size, ignore minimum sizes (squeeze sections):
2379  int minSizeSum = 0;
2380  for (int i=0; i<sectionCount; ++i)
2381  minSizeSum += minSizes.at(i);
2382  if (totalSize < minSizeSum)
2383  {
2384  // new stretch factors are minimum sizes and minimum sizes are set to zero:
2385  for (int i=0; i<sectionCount; ++i)
2386  {
2387  stretchFactors[i] = minSizes.at(i);
2388  minSizes[i] = 0;
2389  }
2390  }
2391 
2392  QList<int> minimumLockedSections;
2393  QList<int> unfinishedSections;
2394  for (int i=0; i<sectionCount; ++i)
2395  unfinishedSections.append(i);
2396  double freeSize = totalSize;
2397 
2398  int outerIterations = 0;
2399  while (!unfinishedSections.isEmpty() && outerIterations < sectionCount*2) // the iteration check ist just a failsafe in case something really strange happens
2400  {
2401  ++outerIterations;
2402  int innerIterations = 0;
2403  while (!unfinishedSections.isEmpty() && innerIterations < sectionCount*2) // the iteration check ist just a failsafe in case something really strange happens
2404  {
2405  ++innerIterations;
2406  // find section that hits its maximum next:
2407  int nextId = -1;
2408  double nextMax = 1e12;
2409  for (int i=0; i<unfinishedSections.size(); ++i)
2410  {
2411  int secId = unfinishedSections.at(i);
2412  double hitsMaxAt = (maxSizes.at(secId)-sectionSizes.at(secId))/stretchFactors.at(secId);
2413  if (hitsMaxAt < nextMax)
2414  {
2415  nextMax = hitsMaxAt;
2416  nextId = secId;
2417  }
2418  }
2419  // check if that maximum is actually within the bounds of the total size (i.e. can we stretch all remaining sections so far that the found section
2420  // actually hits its maximum, without exceeding the total size when we add up all sections)
2421  double stretchFactorSum = 0;
2422  for (int i=0; i<unfinishedSections.size(); ++i)
2423  stretchFactorSum += stretchFactors.at(unfinishedSections.at(i));
2424  double nextMaxLimit = freeSize/stretchFactorSum;
2425  if (nextMax < nextMaxLimit) // next maximum is actually hit, move forward to that point and fix the size of that section
2426  {
2427  for (int i=0; i<unfinishedSections.size(); ++i)
2428  {
2429  sectionSizes[unfinishedSections.at(i)] += nextMax*stretchFactors.at(unfinishedSections.at(i)); // increment all sections
2430  freeSize -= nextMax*stretchFactors.at(unfinishedSections.at(i));
2431  }
2432  unfinishedSections.removeOne(nextId); // exclude the section that is now at maximum from further changes
2433  } else // next maximum isn't hit, just distribute rest of free space on remaining sections
2434  {
2435  for (int i=0; i<unfinishedSections.size(); ++i)
2436  sectionSizes[unfinishedSections.at(i)] += nextMaxLimit*stretchFactors.at(unfinishedSections.at(i)); // increment all sections
2437  unfinishedSections.clear();
2438  }
2439  }
2440  if (innerIterations == sectionCount*2)
2441  qDebug() << Q_FUNC_INFO << "Exceeded maximum expected inner iteration count, layouting aborted. Input was:" << maxSizes << minSizes << stretchFactors << totalSize;
2442 
2443  // now check whether the resulting section sizes violate minimum restrictions:
2444  bool foundMinimumViolation = false;
2445  for (int i=0; i<sectionSizes.size(); ++i)
2446  {
2447  if (minimumLockedSections.contains(i))
2448  continue;
2449  if (sectionSizes.at(i) < minSizes.at(i)) // section violates minimum
2450  {
2451  sectionSizes[i] = minSizes.at(i); // set it to minimum
2452  foundMinimumViolation = true; // make sure we repeat the whole optimization process
2453  minimumLockedSections.append(i);
2454  }
2455  }
2456  if (foundMinimumViolation)
2457  {
2458  freeSize = totalSize;
2459  for (int i=0; i<sectionCount; ++i)
2460  {
2461  if (!minimumLockedSections.contains(i)) // only put sections that haven't hit their minimum back into the pool
2462  unfinishedSections.append(i);
2463  else
2464  freeSize -= sectionSizes.at(i); // remove size of minimum locked sections from available space in next round
2465  }
2466  // reset all section sizes to zero that are in unfinished sections (all others have been set to their minimum):
2467  for (int i=0; i<unfinishedSections.size(); ++i)
2468  sectionSizes[unfinishedSections.at(i)] = 0;
2469  }
2470  }
2471  if (outerIterations == sectionCount*2)
2472  qDebug() << Q_FUNC_INFO << "Exceeded maximum expected outer iteration count, layouting aborted. Input was:" << maxSizes << minSizes << stretchFactors << totalSize;
2473 
2474  QVector<int> result(sectionCount);
2475  for (int i=0; i<sectionCount; ++i)
2476  result[i] = qRound(sectionSizes.at(i));
2477  return result;
2478 }
2479 
2480 
2484 
2505  mColumnSpacing(5),
2506  mRowSpacing(5)
2507 {
2508 }
2509 
2511 {
2512  // clear all child layout elements. This is important because only the specific layouts know how
2513  // to handle removing elements (clear calls virtual removeAt method to do that).
2514  clear();
2515 }
2516 
2525 QCPLayoutElement *QCPLayoutGrid::element(int row, int column) const
2526 {
2527  if (row >= 0 && row < mElements.size())
2528  {
2529  if (column >= 0 && column < mElements.first().size())
2530  {
2531  if (QCPLayoutElement *result = mElements.at(row).at(column))
2532  return result;
2533  else
2534  qDebug() << Q_FUNC_INFO << "Requested cell is empty. Row:" << row << "Column:" << column;
2535  } else
2536  qDebug() << Q_FUNC_INFO << "Invalid column. Row:" << row << "Column:" << column;
2537  } else
2538  qDebug() << Q_FUNC_INFO << "Invalid row. Row:" << row << "Column:" << column;
2539  return 0;
2540 }
2541 
2548 {
2549  return mElements.size();
2550 }
2551 
2558 {
2559  if (mElements.size() > 0)
2560  return mElements.first().size();
2561  else
2562  return 0;
2563 }
2564 
2576 {
2577  if (element)
2578  {
2579  if (!hasElement(row, column))
2580  {
2581  if (element->layout()) // remove from old layout first
2582  element->layout()->take(element);
2583  expandTo(row+1, column+1);
2584  mElements[row][column] = element;
2585  adoptElement(element);
2586  return true;
2587  } else
2588  qDebug() << Q_FUNC_INFO << "There is already an element in the specified row/column:" << row << column;
2589  } else
2590  qDebug() << Q_FUNC_INFO << "Can't add null element to row/column:" << row << column;
2591  return false;
2592 }
2593 
2600 bool QCPLayoutGrid::hasElement(int row, int column)
2601 {
2602  if (row >= 0 && row < rowCount() && column >= 0 && column < columnCount())
2603  return mElements.at(row).at(column);
2604  else
2605  return false;
2606 }
2607 
2619 void QCPLayoutGrid::setColumnStretchFactor(int column, double factor)
2620 {
2621  if (column >= 0 && column < columnCount())
2622  {
2623  if (factor > 0)
2624  mColumnStretchFactors[column] = factor;
2625  else
2626  qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor;
2627  } else
2628  qDebug() << Q_FUNC_INFO << "Invalid column:" << column;
2629 }
2630 
2642 void QCPLayoutGrid::setColumnStretchFactors(const QList<double> &factors)
2643 {
2644  if (factors.size() == mColumnStretchFactors.size())
2645  {
2646  mColumnStretchFactors = factors;
2647  for (int i=0; i<mColumnStretchFactors.size(); ++i)
2648  {
2649  if (mColumnStretchFactors.at(i) <= 0)
2650  {
2651  qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << mColumnStretchFactors.at(i);
2652  mColumnStretchFactors[i] = 1;
2653  }
2654  }
2655  } else
2656  qDebug() << Q_FUNC_INFO << "Column count not equal to passed stretch factor count:" << factors;
2657 }
2658 
2670 void QCPLayoutGrid::setRowStretchFactor(int row, double factor)
2671 {
2672  if (row >= 0 && row < rowCount())
2673  {
2674  if (factor > 0)
2675  mRowStretchFactors[row] = factor;
2676  else
2677  qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor;
2678  } else
2679  qDebug() << Q_FUNC_INFO << "Invalid row:" << row;
2680 }
2681 
2693 void QCPLayoutGrid::setRowStretchFactors(const QList<double> &factors)
2694 {
2695  if (factors.size() == mRowStretchFactors.size())
2696  {
2697  mRowStretchFactors = factors;
2698  for (int i=0; i<mRowStretchFactors.size(); ++i)
2699  {
2700  if (mRowStretchFactors.at(i) <= 0)
2701  {
2702  qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << mRowStretchFactors.at(i);
2703  mRowStretchFactors[i] = 1;
2704  }
2705  }
2706  } else
2707  qDebug() << Q_FUNC_INFO << "Row count not equal to passed stretch factor count:" << factors;
2708 }
2709 
2716 {
2717  mColumnSpacing = pixels;
2718 }
2719 
2726 {
2727  mRowSpacing = pixels;
2728 }
2729 
2744 void QCPLayoutGrid::expandTo(int newRowCount, int newColumnCount)
2745 {
2746  // add rows as necessary:
2747  while (rowCount() < newRowCount)
2748  {
2749  mElements.append(QList<QCPLayoutElement*>());
2750  mRowStretchFactors.append(1);
2751  }
2752  // go through rows and expand columns as necessary:
2753  int newColCount = qMax(columnCount(), newColumnCount);
2754  for (int i=0; i<rowCount(); ++i)
2755  {
2756  while (mElements.at(i).size() < newColCount)
2757  mElements[i].append(0);
2758  }
2759  while (mColumnStretchFactors.size() < newColCount)
2760  mColumnStretchFactors.append(1);
2761 }
2762 
2769 void QCPLayoutGrid::insertRow(int newIndex)
2770 {
2771  if (mElements.isEmpty() || mElements.first().isEmpty()) // if grid is completely empty, add first cell
2772  {
2773  expandTo(1, 1);
2774  return;
2775  }
2776 
2777  if (newIndex < 0)
2778  newIndex = 0;
2779  if (newIndex > rowCount())
2780  newIndex = rowCount();
2781 
2782  mRowStretchFactors.insert(newIndex, 1);
2783  QList<QCPLayoutElement*> newRow;
2784  for (int col=0; col<columnCount(); ++col)
2785  newRow.append((QCPLayoutElement*)0);
2786  mElements.insert(newIndex, newRow);
2787 }
2788 
2796 {
2797  if (mElements.isEmpty() || mElements.first().isEmpty()) // if grid is completely empty, add first cell
2798  {
2799  expandTo(1, 1);
2800  return;
2801  }
2802 
2803  if (newIndex < 0)
2804  newIndex = 0;
2805  if (newIndex > columnCount())
2806  newIndex = columnCount();
2807 
2808  mColumnStretchFactors.insert(newIndex, 1);
2809  for (int row=0; row<rowCount(); ++row)
2810  mElements[row].insert(newIndex, (QCPLayoutElement*)0);
2811 }
2812 
2813 /* inherits documentation from base class */
2815 {
2816  QVector<int> minColWidths, minRowHeights, maxColWidths, maxRowHeights;
2817  getMinimumRowColSizes(&minColWidths, &minRowHeights);
2818  getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
2819 
2820  int totalRowSpacing = (rowCount()-1) * mRowSpacing;
2821  int totalColSpacing = (columnCount()-1) * mColumnSpacing;
2822  QVector<int> colWidths = getSectionSizes(maxColWidths, minColWidths, mColumnStretchFactors.toVector(), mRect.width()-totalColSpacing);
2823  QVector<int> rowHeights = getSectionSizes(maxRowHeights, minRowHeights, mRowStretchFactors.toVector(), mRect.height()-totalRowSpacing);
2824 
2825  // go through cells and set rects accordingly:
2826  int yOffset = mRect.top();
2827  for (int row=0; row<rowCount(); ++row)
2828  {
2829  if (row > 0)
2830  yOffset += rowHeights.at(row-1)+mRowSpacing;
2831  int xOffset = mRect.left();
2832  for (int col=0; col<columnCount(); ++col)
2833  {
2834  if (col > 0)
2835  xOffset += colWidths.at(col-1)+mColumnSpacing;
2836  if (mElements.at(row).at(col))
2837  mElements.at(row).at(col)->setOuterRect(QRect(xOffset, yOffset, colWidths.at(col), rowHeights.at(row)));
2838  }
2839  }
2840 }
2841 
2842 /* inherits documentation from base class */
2844 {
2845  return rowCount()*columnCount();
2846 }
2847 
2848 /* inherits documentation from base class */
2850 {
2851  if (index >= 0 && index < elementCount())
2852  return mElements.at(index / columnCount()).at(index % columnCount());
2853  else
2854  return 0;
2855 }
2856 
2857 /* inherits documentation from base class */
2859 {
2860  if (QCPLayoutElement *el = elementAt(index))
2861  {
2862  releaseElement(el);
2863  mElements[index / columnCount()][index % columnCount()] = 0;
2864  return el;
2865  } else
2866  {
2867  qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
2868  return 0;
2869  }
2870 }
2871 
2872 /* inherits documentation from base class */
2874 {
2875  if (element)
2876  {
2877  for (int i=0; i<elementCount(); ++i)
2878  {
2879  if (elementAt(i) == element)
2880  {
2881  takeAt(i);
2882  return true;
2883  }
2884  }
2885  qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take";
2886  } else
2887  qDebug() << Q_FUNC_INFO << "Can't take null element";
2888  return false;
2889 }
2890 
2891 /* inherits documentation from base class */
2892 QList<QCPLayoutElement*> QCPLayoutGrid::elements(bool recursive) const
2893 {
2894  QList<QCPLayoutElement*> result;
2895  int colC = columnCount();
2896  int rowC = rowCount();
2897 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
2898  result.reserve(colC*rowC);
2899 #endif
2900  for (int row=0; row<rowC; ++row)
2901  {
2902  for (int col=0; col<colC; ++col)
2903  {
2904  result.append(mElements.at(row).at(col));
2905  }
2906  }
2907  if (recursive)
2908  {
2909  int c = result.size();
2910  for (int i=0; i<c; ++i)
2911  {
2912  if (result.at(i))
2913  result << result.at(i)->elements(recursive);
2914  }
2915  }
2916  return result;
2917 }
2918 
2923 {
2924  // remove rows with only empty cells:
2925  for (int row=rowCount()-1; row>=0; --row)
2926  {
2927  bool hasElements = false;
2928  for (int col=0; col<columnCount(); ++col)
2929  {
2930  if (mElements.at(row).at(col))
2931  {
2932  hasElements = true;
2933  break;
2934  }
2935  }
2936  if (!hasElements)
2937  {
2938  mRowStretchFactors.removeAt(row);
2939  mElements.removeAt(row);
2940  if (mElements.isEmpty()) // removed last element, also remove stretch factor (wouldn't happen below because also columnCount changed to 0 now)
2941  mColumnStretchFactors.clear();
2942  }
2943  }
2944 
2945  // remove columns with only empty cells:
2946  for (int col=columnCount()-1; col>=0; --col)
2947  {
2948  bool hasElements = false;
2949  for (int row=0; row<rowCount(); ++row)
2950  {
2951  if (mElements.at(row).at(col))
2952  {
2953  hasElements = true;
2954  break;
2955  }
2956  }
2957  if (!hasElements)
2958  {
2959  mColumnStretchFactors.removeAt(col);
2960  for (int row=0; row<rowCount(); ++row)
2961  mElements[row].removeAt(col);
2962  }
2963  }
2964 }
2965 
2966 /* inherits documentation from base class */
2968 {
2969  QVector<int> minColWidths, minRowHeights;
2970  getMinimumRowColSizes(&minColWidths, &minRowHeights);
2971  QSize result(0, 0);
2972  for (int i=0; i<minColWidths.size(); ++i)
2973  result.rwidth() += minColWidths.at(i);
2974  for (int i=0; i<minRowHeights.size(); ++i)
2975  result.rheight() += minRowHeights.at(i);
2976  result.rwidth() += qMax(0, columnCount()-1) * mColumnSpacing + mMargins.left() + mMargins.right();
2977  result.rheight() += qMax(0, rowCount()-1) * mRowSpacing + mMargins.top() + mMargins.bottom();
2978  return result;
2979 }
2980 
2981 /* inherits documentation from base class */
2983 {
2984  QVector<int> maxColWidths, maxRowHeights;
2985  getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
2986 
2987  QSize result(0, 0);
2988  for (int i=0; i<maxColWidths.size(); ++i)
2989  result.setWidth(qMin(result.width()+maxColWidths.at(i), QWIDGETSIZE_MAX));
2990  for (int i=0; i<maxRowHeights.size(); ++i)
2991  result.setHeight(qMin(result.height()+maxRowHeights.at(i), QWIDGETSIZE_MAX));
2992  result.rwidth() += qMax(0, columnCount()-1) * mColumnSpacing + mMargins.left() + mMargins.right();
2993  result.rheight() += qMax(0, rowCount()-1) * mRowSpacing + mMargins.top() + mMargins.bottom();
2994  return result;
2995 }
2996 
3009 void QCPLayoutGrid::getMinimumRowColSizes(QVector<int> *minColWidths, QVector<int> *minRowHeights) const
3010 {
3011  *minColWidths = QVector<int>(columnCount(), 0);
3012  *minRowHeights = QVector<int>(rowCount(), 0);
3013  for (int row=0; row<rowCount(); ++row)
3014  {
3015  for (int col=0; col<columnCount(); ++col)
3016  {
3017  if (mElements.at(row).at(col))
3018  {
3019  QSize minHint = mElements.at(row).at(col)->minimumSizeHint();
3020  QSize min = mElements.at(row).at(col)->minimumSize();
3021  QSize final(min.width() > 0 ? min.width() : minHint.width(), min.height() > 0 ? min.height() : minHint.height());
3022  if (minColWidths->at(col) < final.width())
3023  (*minColWidths)[col] = final.width();
3024  if (minRowHeights->at(row) < final.height())
3025  (*minRowHeights)[row] = final.height();
3026  }
3027  }
3028  }
3029 }
3030 
3043 void QCPLayoutGrid::getMaximumRowColSizes(QVector<int> *maxColWidths, QVector<int> *maxRowHeights) const
3044 {
3045  *maxColWidths = QVector<int>(columnCount(), QWIDGETSIZE_MAX);
3046  *maxRowHeights = QVector<int>(rowCount(), QWIDGETSIZE_MAX);
3047  for (int row=0; row<rowCount(); ++row)
3048  {
3049  for (int col=0; col<columnCount(); ++col)
3050  {
3051  if (mElements.at(row).at(col))
3052  {
3053  QSize maxHint = mElements.at(row).at(col)->maximumSizeHint();
3054  QSize max = mElements.at(row).at(col)->maximumSize();
3055  QSize final(max.width() < QWIDGETSIZE_MAX ? max.width() : maxHint.width(), max.height() < QWIDGETSIZE_MAX ? max.height() : maxHint.height());
3056  if (maxColWidths->at(col) > final.width())
3057  (*maxColWidths)[col] = final.width();
3058  if (maxRowHeights->at(row) > final.height())
3059  (*maxRowHeights)[row] = final.height();
3060  }
3061  }
3062  }
3063 }
3064 
3065 
3069 
3087 /* start documentation of inline functions */
3088 
3095 /* end documentation of inline functions */
3096 
3101 {
3102 }
3103 
3105 {
3106  // clear all child layout elements. This is important because only the specific layouts know how
3107  // to handle removing elements (clear calls virtual removeAt method to do that).
3108  clear();
3109 }
3110 
3115 {
3116  if (elementAt(index))
3117  return mInsetPlacement.at(index);
3118  else
3119  {
3120  qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3121  return ipFree;
3122  }
3123 }
3124 
3129 Qt::Alignment QCPLayoutInset::insetAlignment(int index) const
3130 {
3131  if (elementAt(index))
3132  return mInsetAlignment.at(index);
3133  else
3134  {
3135  qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3136  return 0;
3137  }
3138 }
3139 
3144 QRectF QCPLayoutInset::insetRect(int index) const
3145 {
3146  if (elementAt(index))
3147  return mInsetRect.at(index);
3148  else
3149  {
3150  qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3151  return QRectF();
3152  }
3153 }
3154 
3161 {
3162  if (elementAt(index))
3163  mInsetPlacement[index] = placement;
3164  else
3165  qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3166 }
3167 
3176 void QCPLayoutInset::setInsetAlignment(int index, Qt::Alignment alignment)
3177 {
3178  if (elementAt(index))
3179  mInsetAlignment[index] = alignment;
3180  else
3181  qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3182 }
3183 
3195 void QCPLayoutInset::setInsetRect(int index, const QRectF &rect)
3196 {
3197  if (elementAt(index))
3198  mInsetRect[index] = rect;
3199  else
3200  qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
3201 }
3202 
3203 /* inherits documentation from base class */
3205 {
3206  for (int i=0; i<mElements.size(); ++i)
3207  {
3208  QRect insetRect;
3209  QSize finalMinSize, finalMaxSize;
3210  QSize minSizeHint = mElements.at(i)->minimumSizeHint();
3211  QSize maxSizeHint = mElements.at(i)->maximumSizeHint();
3212  finalMinSize.setWidth(mElements.at(i)->minimumSize().width() > 0 ? mElements.at(i)->minimumSize().width() : minSizeHint.width());
3213  finalMinSize.setHeight(mElements.at(i)->minimumSize().height() > 0 ? mElements.at(i)->minimumSize().height() : minSizeHint.height());
3214  finalMaxSize.setWidth(mElements.at(i)->maximumSize().width() < QWIDGETSIZE_MAX ? mElements.at(i)->maximumSize().width() : maxSizeHint.width());
3215  finalMaxSize.setHeight(mElements.at(i)->maximumSize().height() < QWIDGETSIZE_MAX ? mElements.at(i)->maximumSize().height() : maxSizeHint.height());
3216  if (mInsetPlacement.at(i) == ipFree)
3217  {
3218  insetRect = QRect(rect().x()+rect().width()*mInsetRect.at(i).x(),
3219  rect().y()+rect().height()*mInsetRect.at(i).y(),
3220  rect().width()*mInsetRect.at(i).width(),
3221  rect().height()*mInsetRect.at(i).height());
3222  if (insetRect.size().width() < finalMinSize.width())
3223  insetRect.setWidth(finalMinSize.width());
3224  if (insetRect.size().height() < finalMinSize.height())
3225  insetRect.setHeight(finalMinSize.height());
3226  if (insetRect.size().width() > finalMaxSize.width())
3227  insetRect.setWidth(finalMaxSize.width());
3228  if (insetRect.size().height() > finalMaxSize.height())
3229  insetRect.setHeight(finalMaxSize.height());
3230  } else if (mInsetPlacement.at(i) == ipBorderAligned)
3231  {
3232  insetRect.setSize(finalMinSize);
3233  Qt::Alignment al = mInsetAlignment.at(i);
3234  if (al.testFlag(Qt::AlignLeft)) insetRect.moveLeft(rect().x());
3235  else if (al.testFlag(Qt::AlignRight)) insetRect.moveRight(rect().x()+rect().width());
3236  else insetRect.moveLeft(rect().x()+rect().width()*0.5-finalMinSize.width()*0.5); // default to Qt::AlignHCenter
3237  if (al.testFlag(Qt::AlignTop)) insetRect.moveTop(rect().y());
3238  else if (al.testFlag(Qt::AlignBottom)) insetRect.moveBottom(rect().y()+rect().height());
3239  else insetRect.moveTop(rect().y()+rect().height()*0.5-finalMinSize.height()*0.5); // default to Qt::AlignVCenter
3240  }
3241  mElements.at(i)->setOuterRect(insetRect);
3242  }
3243 }
3244 
3245 /* inherits documentation from base class */
3247 {
3248  return mElements.size();
3249 }
3250 
3251 /* inherits documentation from base class */
3253 {
3254  if (index >= 0 && index < mElements.size())
3255  return mElements.at(index);
3256  else
3257  return 0;
3258 }
3259 
3260 /* inherits documentation from base class */
3262 {
3263  if (QCPLayoutElement *el = elementAt(index))
3264  {
3265  releaseElement(el);
3266  mElements.removeAt(index);
3267  mInsetPlacement.removeAt(index);
3268  mInsetAlignment.removeAt(index);
3269  mInsetRect.removeAt(index);
3270  return el;
3271  } else
3272  {
3273  qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
3274  return 0;
3275  }
3276 }
3277 
3278 /* inherits documentation from base class */
3280 {
3281  if (element)
3282  {
3283  for (int i=0; i<elementCount(); ++i)
3284  {
3285  if (elementAt(i) == element)
3286  {
3287  takeAt(i);
3288  return true;
3289  }
3290  }
3291  qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take";
3292  } else
3293  qDebug() << Q_FUNC_INFO << "Can't take null element";
3294  return false;
3295 }
3296 
3306 double QCPLayoutInset::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
3307 {
3308  Q_UNUSED(details)
3309  if (onlySelectable)
3310  return -1;
3311 
3312  for (int i=0; i<mElements.size(); ++i)
3313  {
3314  // inset layout shall only return positive selectTest, if actually an inset object is at pos
3315  // else it would block the entire underlying QCPAxisRect with its surface.
3316  if (mElements.at(i)->realVisibility() && mElements.at(i)->selectTest(pos, onlySelectable) >= 0)
3317  return mParentPlot->selectionTolerance()*0.99;
3318  }
3319  return -1;
3320 }
3321 
3333 void QCPLayoutInset::addElement(QCPLayoutElement *element, Qt::Alignment alignment)
3334 {
3335  if (element)
3336  {
3337  if (element->layout()) // remove from old layout first
3338  element->layout()->take(element);
3339  mElements.append(element);
3340  mInsetPlacement.append(ipBorderAligned);
3341  mInsetAlignment.append(alignment);
3342  mInsetRect.append(QRectF(0.6, 0.6, 0.4, 0.4));
3343  adoptElement(element);
3344  } else
3345  qDebug() << Q_FUNC_INFO << "Can't add null element";
3346 }
3347 
3360 {
3361  if (element)
3362  {
3363  if (element->layout()) // remove from old layout first
3364  element->layout()->take(element);
3365  mElements.append(element);
3366  mInsetPlacement.append(ipFree);
3367  mInsetAlignment.append(Qt::AlignRight|Qt::AlignTop);
3368  mInsetRect.append(rect);
3369  adoptElement(element);
3370  } else
3371  qDebug() << Q_FUNC_INFO << "Can't add null element";
3372 }
3373 
3374 
3378 
3403  mStyle(esNone),
3404  mWidth(8),
3405  mLength(10),
3406  mInverted(false)
3407 {
3408 }
3409 
3414  mStyle(style),
3415  mWidth(width),
3416  mLength(length),
3417  mInverted(inverted)
3418 {
3419 }
3420 
3425 {
3426  mStyle = style;
3427 }
3428 
3436 {
3437  mWidth = width;
3438 }
3439 
3447 {
3448  mLength = length;
3449 }
3450 
3460 {
3461  mInverted = inverted;
3462 }
3463 
3474 {
3475  switch (mStyle)
3476  {
3477  case esNone:
3478  return 0;
3479 
3480  case esFlatArrow:
3481  case esSpikeArrow:
3482  case esLineArrow:
3483  case esSkewedBar:
3484  return qSqrt(mWidth*mWidth+mLength*mLength); // items that have width and length
3485 
3486  case esDisc:
3487  case esSquare:
3488  case esDiamond:
3489  case esBar:
3490  case esHalfBar:
3491  return mWidth*1.42; // items that only have a width -> width*sqrt(2)
3492 
3493  }
3494  return 0;
3495 }
3496 
3509 {
3510  switch (mStyle)
3511  {
3512  case esNone:
3513  case esLineArrow:
3514  case esSkewedBar:
3515  case esBar:
3516  case esHalfBar:
3517  return 0;
3518 
3519  case esFlatArrow:
3520  return mLength;
3521 
3522  case esDisc:
3523  case esSquare:
3524  case esDiamond:
3525  return mWidth*0.5;
3526 
3527  case esSpikeArrow:
3528  return mLength*0.8;
3529  }
3530  return 0;
3531 }
3532 
3538 void QCPLineEnding::draw(QCPPainter *painter, const QVector2D &pos, const QVector2D &dir) const
3539 {
3540  if (mStyle == esNone)
3541  return;
3542 
3543  QVector2D lengthVec(dir.normalized());
3544  if (lengthVec.isNull())
3545  lengthVec = QVector2D(1, 0);
3546  QVector2D widthVec(-lengthVec.y(), lengthVec.x());
3547  lengthVec *= (float)(mLength*(mInverted ? -1 : 1));
3548  widthVec *= (float)(mWidth*0.5*(mInverted ? -1 : 1));
3549 
3550  QPen penBackup = painter->pen();
3551  QBrush brushBackup = painter->brush();
3552  QPen miterPen = penBackup;
3553  miterPen.setJoinStyle(Qt::MiterJoin); // to make arrow heads spikey
3554  QBrush brush(painter->pen().color(), Qt::SolidPattern);
3555  switch (mStyle)
3556  {
3557  case esNone: break;
3558  case esFlatArrow:
3559  {
3560  QPointF points[3] = {pos.toPointF(),
3561  (pos-lengthVec+widthVec).toPointF(),
3562  (pos-lengthVec-widthVec).toPointF()
3563  };
3564  painter->setPen(miterPen);
3565  painter->setBrush(brush);
3566  painter->drawConvexPolygon(points, 3);
3567  painter->setBrush(brushBackup);
3568  painter->setPen(penBackup);
3569  break;
3570  }
3571  case esSpikeArrow:
3572  {
3573  QPointF points[4] = {pos.toPointF(),
3574  (pos-lengthVec+widthVec).toPointF(),
3575  (pos-lengthVec*0.8f).toPointF(),
3576  (pos-lengthVec-widthVec).toPointF()
3577  };
3578  painter->setPen(miterPen);
3579  painter->setBrush(brush);
3580  painter->drawConvexPolygon(points, 4);
3581  painter->setBrush(brushBackup);
3582  painter->setPen(penBackup);
3583  break;
3584  }
3585  case esLineArrow:
3586  {
3587  QPointF points[3] = {(pos-lengthVec+widthVec).toPointF(),
3588  pos.toPointF(),
3589  (pos-lengthVec-widthVec).toPointF()
3590  };
3591  painter->setPen(miterPen);
3592  painter->drawPolyline(points, 3);
3593  painter->setPen(penBackup);
3594  break;
3595  }
3596  case esDisc:
3597  {
3598  painter->setBrush(brush);
3599  painter->drawEllipse(pos.toPointF(), mWidth*0.5, mWidth*0.5);
3600  painter->setBrush(brushBackup);
3601  break;
3602  }
3603  case esSquare:
3604  {
3605  QVector2D widthVecPerp(-widthVec.y(), widthVec.x());
3606  QPointF points[4] = {(pos-widthVecPerp+widthVec).toPointF(),
3607  (pos-widthVecPerp-widthVec).toPointF(),
3608  (pos+widthVecPerp-widthVec).toPointF(),
3609  (pos+widthVecPerp+widthVec).toPointF()
3610  };
3611  painter->setPen(miterPen);
3612  painter->setBrush(brush);
3613  painter->drawConvexPolygon(points, 4);
3614  painter->setBrush(brushBackup);
3615  painter->setPen(penBackup);
3616  break;
3617  }
3618  case esDiamond:
3619  {
3620  QVector2D widthVecPerp(-widthVec.y(), widthVec.x());
3621  QPointF points[4] = {(pos-widthVecPerp).toPointF(),
3622  (pos-widthVec).toPointF(),
3623  (pos+widthVecPerp).toPointF(),
3624  (pos+widthVec).toPointF()
3625  };
3626  painter->setPen(miterPen);
3627  painter->setBrush(brush);
3628  painter->drawConvexPolygon(points, 4);
3629  painter->setBrush(brushBackup);
3630  painter->setPen(penBackup);
3631  break;
3632  }
3633  case esBar:
3634  {
3635  painter->drawLine((pos+widthVec).toPointF(), (pos-widthVec).toPointF());
3636  break;
3637  }
3638  case esHalfBar:
3639  {
3640  painter->drawLine((pos+widthVec).toPointF(), pos.toPointF());
3641  break;
3642  }
3643  case esSkewedBar:
3644  {
3645  if (qFuzzyIsNull(painter->pen().widthF()) && !painter->modes().testFlag(QCPPainter::pmNonCosmetic))
3646  {
3647  // if drawing with cosmetic pen (perfectly thin stroke, happens only in vector exports), draw bar exactly on tip of line
3648  painter->drawLine((pos+widthVec+lengthVec*0.2f*(mInverted?-1:1)).toPointF(),
3649  (pos-widthVec-lengthVec*0.2f*(mInverted?-1:1)).toPointF());
3650  } else
3651  {
3652  // if drawing with thick (non-cosmetic) pen, shift bar a little in line direction to prevent line from sticking through bar slightly
3653  painter->drawLine((pos+widthVec+lengthVec*0.2f*(mInverted?-1:1)+dir.normalized()*qMax(1.0f, (float)painter->pen().widthF())*0.5f).toPointF(),
3654  (pos-widthVec-lengthVec*0.2f*(mInverted?-1:1)+dir.normalized()*qMax(1.0f, (float)painter->pen().widthF())*0.5f).toPointF());
3655  }
3656  break;
3657  }
3658  }
3659 }
3660 
3666 void QCPLineEnding::draw(QCPPainter *painter, const QVector2D &pos, double angle) const
3667 {
3668  draw(painter, pos, QVector2D(qCos(angle), qSin(angle)));
3669 }
3670 
3671 
3675 
3695  QCPLayerable(parentAxis->parentPlot(), QString(), parentAxis),
3696  mParentAxis(parentAxis)
3697 {
3698  // warning: this is called in QCPAxis constructor, so parentAxis members should not be accessed/called
3699  setParent(parentAxis);
3700  setPen(QPen(QColor(200,200,200), 0, Qt::DotLine));
3701  setSubGridPen(QPen(QColor(220,220,220), 0, Qt::DotLine));
3702  setZeroLinePen(QPen(QColor(200,200,200), 0, Qt::SolidLine));
3703  setSubGridVisible(false);
3704  setAntialiased(false);
3705  setAntialiasedSubGrid(false);
3706  setAntialiasedZeroLine(false);
3707 }
3708 
3715 {
3717 }
3718 
3723 {
3724  mAntialiasedSubGrid = enabled;
3725 }
3726 
3731 {
3732  mAntialiasedZeroLine = enabled;
3733 }
3734 
3738 void QCPGrid::setPen(const QPen &pen)
3739 {
3740  mPen = pen;
3741 }
3742 
3746 void QCPGrid::setSubGridPen(const QPen &pen)
3747 {
3748  mSubGridPen = pen;
3749 }
3750 
3757 void QCPGrid::setZeroLinePen(const QPen &pen)
3758 {
3759  mZeroLinePen = pen;
3760 }
3761 
3776 {
3778 }
3779 
3786 {
3787  if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
3788 
3789  if (mSubGridVisible)
3790  drawSubGridLines(painter);
3791  drawGridLines(painter);
3792 }
3793 
3801 {
3802  if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
3803 
3804  int lowTick = mParentAxis->mLowestVisibleTick;
3805  int highTick = mParentAxis->mHighestVisibleTick;
3806  double t; // helper variable, result of coordinate-to-pixel transforms
3807  if (mParentAxis->orientation() == Qt::Horizontal)
3808  {
3809  // draw zeroline:
3810  int zeroLineIndex = -1;
3811  if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 && mParentAxis->mRange.upper > 0)
3812  {
3814  painter->setPen(mZeroLinePen);
3815  double epsilon = mParentAxis->range().size()*1E-6; // for comparing double to zero
3816  for (int i=lowTick; i <= highTick; ++i)
3817  {
3818  if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon)
3819  {
3820  zeroLineIndex = i;
3821  t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x
3822  painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
3823  break;
3824  }
3825  }
3826  }
3827  // draw grid lines:
3829  painter->setPen(mPen);
3830  for (int i=lowTick; i <= highTick; ++i)
3831  {
3832  if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline
3833  t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x
3834  painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
3835  }
3836  } else
3837  {
3838  // draw zeroline:
3839  int zeroLineIndex = -1;
3840  if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 && mParentAxis->mRange.upper > 0)
3841  {
3843  painter->setPen(mZeroLinePen);
3844  double epsilon = mParentAxis->mRange.size()*1E-6; // for comparing double to zero
3845  for (int i=lowTick; i <= highTick; ++i)
3846  {
3847  if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon)
3848  {
3849  zeroLineIndex = i;
3850  t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y
3851  painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
3852  break;
3853  }
3854  }
3855  }
3856  // draw grid lines:
3858  painter->setPen(mPen);
3859  for (int i=lowTick; i <= highTick; ++i)
3860  {
3861  if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline
3862  t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y
3863  painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
3864  }
3865  }
3866 }
3867 
3875 {
3876  if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
3877 
3879  double t; // helper variable, result of coordinate-to-pixel transforms
3880  painter->setPen(mSubGridPen);
3881  if (mParentAxis->orientation() == Qt::Horizontal)
3882  {
3883  for (int i=0; i<mParentAxis->mSubTickVector.size(); ++i)
3884  {
3886  painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
3887  }
3888  } else
3889  {
3890  for (int i=0; i<mParentAxis->mSubTickVector.size(); ++i)
3891  {
3893  painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
3894  }
3895  }
3896 }
3897 
3898 
3902 
3921 /* start of documentation of inline functions */
3922 
3944 /* end of documentation of inline functions */
3945 /* start of documentation of signals */
3946 
3992 /* end of documentation of signals */
3993 
4002  QCPLayerable(parent->parentPlot(), QString(), parent),
4003  // axis base:
4004  mAxisType(type),
4005  mAxisRect(parent),
4006  mPadding(5),
4007  mOrientation(orientation(type)),
4008  mSelectableParts(spAxis | spTickLabels | spAxisLabel),
4009  mSelectedParts(spNone),
4010  mBasePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
4011  mSelectedBasePen(QPen(Qt::blue, 2)),
4012  // axis label:
4013  mLabel(),
4014  mLabelFont(mParentPlot->font()),
4015  mSelectedLabelFont(QFont(mLabelFont.family(), mLabelFont.pointSize(), QFont::Bold)),
4016  mLabelColor(Qt::black),
4017  mSelectedLabelColor(Qt::blue),
4018  // tick labels:
4019  mTickLabels(true),
4020  mAutoTickLabels(true),
4021  mTickLabelType(ltNumber),
4022  mTickLabelFont(mParentPlot->font()),
4023  mSelectedTickLabelFont(QFont(mTickLabelFont.family(), mTickLabelFont.pointSize(), QFont::Bold)),
4024  mTickLabelColor(Qt::black),
4025  mSelectedTickLabelColor(Qt::blue),
4026  mDateTimeFormat(QLatin1String("hh:mm:ss\ndd.MM.yy")),
4027  mDateTimeSpec(Qt::LocalTime),
4028  mNumberPrecision(6),
4029  mNumberFormatChar('g'),
4030  mNumberBeautifulPowers(true),
4031  // ticks and subticks:
4032  mTicks(true),
4033  mTickStep(1),
4034  mSubTickCount(4),
4035  mAutoTickCount(6),
4036  mAutoTicks(true),
4037  mAutoTickStep(true),
4038  mAutoSubTicks(true),
4039  mTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
4040  mSelectedTickPen(QPen(Qt::blue, 2)),
4041  mSubTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
4042  mSelectedSubTickPen(QPen(Qt::blue, 2)),
4043  // scale and range:
4044  mRange(0, 5),
4045  mRangeReversed(false),
4046  mScaleType(stLinear),
4047  mScaleLogBase(10),
4048  mScaleLogBaseLogInv(1.0/qLn(mScaleLogBase)),
4049  // internal members:
4050  mGrid(new QCPGrid(this)),
4051  mAxisPainter(new QCPAxisPainterPrivate(parent->parentPlot())),
4052  mLowestVisibleTick(0),
4053  mHighestVisibleTick(-1),
4054  mCachedMarginValid(false),
4055  mCachedMargin(0)
4056 {
4057  mGrid->setVisible(false);
4058  setAntialiased(false);
4059  setLayer(mParentPlot->currentLayer()); // it's actually on that layer already, but we want it in front of the grid, so we place it on there again
4060 
4061  if (type == atTop)
4062  {
4064  setLabelPadding(6);
4065  } else if (type == atRight)
4066  {
4068  setLabelPadding(12);
4069  } else if (type == atBottom)
4070  {
4072  setLabelPadding(3);
4073  } else if (type == atLeft)
4074  {
4076  setLabelPadding(10);
4077  }
4078 }
4079 
4081 {
4082  delete mAxisPainter;
4083 }
4084 
4085 /* No documentation as it is a property getter */
4087 {
4089 }
4090 
4091 /* No documentation as it is a property getter */
4093 {
4095 }
4096 
4097 /* No documentation as it is a property getter */
4099 {
4100  return mAxisPainter->tickLabelSide;
4101 }
4102 
4103 /* No documentation as it is a property getter */
4104 QString QCPAxis::numberFormat() const
4105 {
4106  QString result;
4107  result.append(mNumberFormatChar);
4109  {
4110  result.append(QLatin1Char('b'));
4112  result.append(QLatin1Char('c'));
4113  }
4114  return result;
4115 }
4116 
4117 /* No documentation as it is a property getter */
4119 {
4120  return mAxisPainter->tickLengthIn;
4121 }
4122 
4123 /* No documentation as it is a property getter */
4125 {
4126  return mAxisPainter->tickLengthOut;
4127 }
4128 
4129 /* No documentation as it is a property getter */
4131 {
4132  return mAxisPainter->subTickLengthIn;
4133 }
4134 
4135 /* No documentation as it is a property getter */
4137 {
4139 }
4140 
4141 /* No documentation as it is a property getter */
4143 {
4144  return mAxisPainter->labelPadding;
4145 }
4146 
4147 /* No documentation as it is a property getter */
4148 int QCPAxis::offset() const
4149 {
4150  return mAxisPainter->offset;
4151 }
4152 
4153 /* No documentation as it is a property getter */
4155 {
4156  return mAxisPainter->lowerEnding;
4157 }
4158 
4159 /* No documentation as it is a property getter */
4161 {
4162  return mAxisPainter->upperEnding;
4163 }
4164 
4179 {
4180  if (mScaleType != type)
4181  {
4182  mScaleType = type;
4183  if (mScaleType == stLogarithmic)
4185  mCachedMarginValid = false;
4187  }
4188 }
4189 
4197 void QCPAxis::setScaleLogBase(double base)
4198 {
4199  if (base > 1)
4200  {
4201  mScaleLogBase = base;
4202  mScaleLogBaseLogInv = 1.0/qLn(mScaleLogBase); // buffer for faster baseLog() calculation
4203  mCachedMarginValid = false;
4204  } else
4205  qDebug() << Q_FUNC_INFO << "Invalid logarithmic scale base (must be greater 1):" << base;
4206 }
4207 
4217 {
4218  if (range.lower == mRange.lower && range.upper == mRange.upper)
4219  return;
4220 
4221  if (!QCPRange::validRange(range)) return;
4222  QCPRange oldRange = mRange;
4223  if (mScaleType == stLogarithmic)
4224  {
4225  mRange = range.sanitizedForLogScale();
4226  } else
4227  {
4228  mRange = range.sanitizedForLinScale();
4229  }
4230  mCachedMarginValid = false;
4231  emit rangeChanged(mRange);
4232  emit rangeChanged(mRange, oldRange);
4233 }
4234 
4245 void QCPAxis::setSelectableParts(const SelectableParts &selectable)
4246 {
4247  if (mSelectableParts != selectable)
4248  {
4249  mSelectableParts = selectable;
4251  }
4252 }
4253 
4269 void QCPAxis::setSelectedParts(const SelectableParts &selected)
4270 {
4271  if (mSelectedParts != selected)
4272  {
4273  mSelectedParts = selected;
4275  }
4276 }
4277 
4287 void QCPAxis::setRange(double lower, double upper)
4288 {
4289  if (lower == mRange.lower && upper == mRange.upper)
4290  return;
4291 
4292  if (!QCPRange::validRange(lower, upper)) return;
4293  QCPRange oldRange = mRange;
4294  mRange.lower = lower;
4295  mRange.upper = upper;
4296  if (mScaleType == stLogarithmic)
4297  {
4299  } else
4300  {
4302  }
4303  mCachedMarginValid = false;
4304  emit rangeChanged(mRange);
4305  emit rangeChanged(mRange, oldRange);
4306 }
4307 
4319 void QCPAxis::setRange(double position, double size, Qt::AlignmentFlag alignment)
4320 {
4321  if (alignment == Qt::AlignLeft)
4322  setRange(position, position+size);
4323  else if (alignment == Qt::AlignRight)
4324  setRange(position-size, position);
4325  else // alignment == Qt::AlignCenter
4326  setRange(position-size/2.0, position+size/2.0);
4327 }
4328 
4333 void QCPAxis::setRangeLower(double lower)
4334 {
4335  if (mRange.lower == lower)
4336  return;
4337 
4338  QCPRange oldRange = mRange;
4339  mRange.lower = lower;
4340  if (mScaleType == stLogarithmic)
4341  {
4343  } else
4344  {
4346  }
4347  mCachedMarginValid = false;
4348  emit rangeChanged(mRange);
4349  emit rangeChanged(mRange, oldRange);
4350 }
4351 
4356 void QCPAxis::setRangeUpper(double upper)
4357 {
4358  if (mRange.upper == upper)
4359  return;
4360 
4361  QCPRange oldRange = mRange;
4362  mRange.upper = upper;
4363  if (mScaleType == stLogarithmic)
4364  {
4366  } else
4367  {
4369  }
4370  mCachedMarginValid = false;
4371  emit rangeChanged(mRange);
4372  emit rangeChanged(mRange, oldRange);
4373 }
4374 
4384 void QCPAxis::setRangeReversed(bool reversed)
4385 {
4386  if (mRangeReversed != reversed)
4387  {
4388  mRangeReversed = reversed;
4389  mCachedMarginValid = false;
4390  }
4391 }
4392 
4409 {
4410  if (mAutoTicks != on)
4411  {
4412  mAutoTicks = on;
4413  mCachedMarginValid = false;
4414  }
4415 }
4416 
4428 void QCPAxis::setAutoTickCount(int approximateCount)
4429 {
4430  if (mAutoTickCount != approximateCount)
4431  {
4432  if (approximateCount > 0)
4433  {
4434  mAutoTickCount = approximateCount;
4435  mCachedMarginValid = false;
4436  } else
4437  qDebug() << Q_FUNC_INFO << "approximateCount must be greater than zero:" << approximateCount;
4438  }
4439 }
4440 
4457 {
4458  if (mAutoTickLabels != on)
4459  {
4460  mAutoTickLabels = on;
4461  mCachedMarginValid = false;
4462  }
4463 }
4464 
4478 {
4479  if (mAutoTickStep != on)
4480  {
4481  mAutoTickStep = on;
4482  mCachedMarginValid = false;
4483  }
4484 }
4485 
4496 {
4497  if (mAutoSubTicks != on)
4498  {
4499  mAutoSubTicks = on;
4500  mCachedMarginValid = false;
4501  }
4502 }
4503 
4510 void QCPAxis::setTicks(bool show)
4511 {
4512  if (mTicks != show)
4513  {
4514  mTicks = show;
4515  mCachedMarginValid = false;
4516  }
4517 }
4518 
4522 void QCPAxis::setTickLabels(bool show)
4523 {
4524  if (mTickLabels != show)
4525  {
4526  mTickLabels = show;
4527  mCachedMarginValid = false;
4528  }
4529 }
4530 
4536 {
4537  if (mAxisPainter->tickLabelPadding != padding)
4538  {
4540  mCachedMarginValid = false;
4541  }
4542 }
4543 
4566 {
4567  if (mTickLabelType != type)
4568  {
4569  mTickLabelType = type;
4570  mCachedMarginValid = false;
4571  }
4572 }
4573 
4579 void QCPAxis::setTickLabelFont(const QFont &font)
4580 {
4581  if (font != mTickLabelFont)
4582  {
4583  mTickLabelFont = font;
4584  mCachedMarginValid = false;
4585  }
4586 }
4587 
4593 void QCPAxis::setTickLabelColor(const QColor &color)
4594 {
4595  if (color != mTickLabelColor)
4596  {
4597  mTickLabelColor = color;
4598  mCachedMarginValid = false;
4599  }
4600 }
4601 
4611 void QCPAxis::setTickLabelRotation(double degrees)
4612 {
4613  if (!qFuzzyIsNull(degrees-mAxisPainter->tickLabelRotation))
4614  {
4615  mAxisPainter->tickLabelRotation = qBound(-90.0, degrees, 90.0);
4616  mCachedMarginValid = false;
4617  }
4618 }
4619 
4628 {
4629  mAxisPainter->tickLabelSide = side;
4630  mCachedMarginValid = false;
4631 }
4632 
4641 void QCPAxis::setDateTimeFormat(const QString &format)
4642 {
4643  if (mDateTimeFormat != format)
4644  {
4645  mDateTimeFormat = format;
4646  mCachedMarginValid = false;
4647  }
4648 }
4649 
4660 void QCPAxis::setDateTimeSpec(const Qt::TimeSpec &timeSpec)
4661 {
4662  mDateTimeSpec = timeSpec;
4663 }
4664 
4701 void QCPAxis::setNumberFormat(const QString &formatCode)
4702 {
4703  if (formatCode.isEmpty())
4704  {
4705  qDebug() << Q_FUNC_INFO << "Passed formatCode is empty";
4706  return;
4707  }
4708  mCachedMarginValid = false;
4709 
4710  // interpret first char as number format char:
4711  QString allowedFormatChars(QLatin1String("eEfgG"));
4712  if (allowedFormatChars.contains(formatCode.at(0)))
4713  {
4714  mNumberFormatChar = QLatin1Char(formatCode.at(0).toLatin1());
4715  } else
4716  {
4717  qDebug() << Q_FUNC_INFO << "Invalid number format code (first char not in 'eEfgG'):" << formatCode;
4718  return;
4719  }
4720  if (formatCode.length() < 2)
4721  {
4722  mNumberBeautifulPowers = false;
4724  return;
4725  }
4726 
4727  // interpret second char as indicator for beautiful decimal powers:
4728  if (formatCode.at(1) == QLatin1Char('b') && (mNumberFormatChar == QLatin1Char('e') || mNumberFormatChar == QLatin1Char('g')))
4729  {
4730  mNumberBeautifulPowers = true;
4731  } else
4732  {
4733  qDebug() << Q_FUNC_INFO << "Invalid number format code (second char not 'b' or first char neither 'e' nor 'g'):" << formatCode;
4734  return;
4735  }
4736  if (formatCode.length() < 3)
4737  {
4739  return;
4740  }
4741 
4742  // interpret third char as indicator for dot or cross multiplication symbol:
4743  if (formatCode.at(2) == QLatin1Char('c'))
4744  {
4746  } else if (formatCode.at(2) == QLatin1Char('d'))
4747  {
4749  } else
4750  {
4751  qDebug() << Q_FUNC_INFO << "Invalid number format code (third char neither 'c' nor 'd'):" << formatCode;
4752  return;
4753  }
4754 }
4755 
4767 void QCPAxis::setNumberPrecision(int precision)
4768 {
4769  if (mNumberPrecision != precision)
4770  {
4771  mNumberPrecision = precision;
4772  mCachedMarginValid = false;
4773  }
4774 }
4775 
4781 void QCPAxis::setTickStep(double step)
4782 {
4783  if (mTickStep != step)
4784  {
4785  mTickStep = step;
4786  mCachedMarginValid = false;
4787  }
4788 }
4789 
4803 void QCPAxis::setTickVector(const QVector<double> &vec)
4804 {
4805  // don't check whether mTickVector != vec here, because it takes longer than we would save
4806  mTickVector = vec;
4807  mCachedMarginValid = false;
4808 }
4809 
4821 void QCPAxis::setTickVectorLabels(const QVector<QString> &vec)
4822 {
4823  // don't check whether mTickVectorLabels != vec here, because it takes longer than we would save
4824  mTickVectorLabels = vec;
4825  mCachedMarginValid = false;
4826 }
4827 
4836 void QCPAxis::setTickLength(int inside, int outside)
4837 {
4838  setTickLengthIn(inside);
4839  setTickLengthOut(outside);
4840 }
4841 
4849 {
4850  if (mAxisPainter->tickLengthIn != inside)
4851  {
4852  mAxisPainter->tickLengthIn = inside;
4853  }
4854 }
4855 
4863 void QCPAxis::setTickLengthOut(int outside)
4864 {
4865  if (mAxisPainter->tickLengthOut != outside)
4866  {
4867  mAxisPainter->tickLengthOut = outside;
4868  mCachedMarginValid = false; // only outside tick length can change margin
4869  }
4870 }
4871 
4884 {
4885  mSubTickCount = count;
4886 }
4887 
4896 void QCPAxis::setSubTickLength(int inside, int outside)
4897 {
4898  setSubTickLengthIn(inside);
4899  setSubTickLengthOut(outside);
4900 }
4901 
4909 {
4910  if (mAxisPainter->subTickLengthIn != inside)
4911  {
4912  mAxisPainter->subTickLengthIn = inside;
4913  }
4914 }
4915 
4924 {
4925  if (mAxisPainter->subTickLengthOut != outside)
4926  {
4927  mAxisPainter->subTickLengthOut = outside;
4928  mCachedMarginValid = false; // only outside tick length can change margin
4929  }
4930 }
4931 
4937 void QCPAxis::setBasePen(const QPen &pen)
4938 {
4939  mBasePen = pen;
4940 }
4941 
4947 void QCPAxis::setTickPen(const QPen &pen)
4948 {
4949  mTickPen = pen;
4950 }
4951 
4957 void QCPAxis::setSubTickPen(const QPen &pen)
4958 {
4959  mSubTickPen = pen;
4960 }
4961 
4967 void QCPAxis::setLabelFont(const QFont &font)
4968 {
4969  if (mLabelFont != font)
4970  {
4971  mLabelFont = font;
4972  mCachedMarginValid = false;
4973  }
4974 }
4975 
4981 void QCPAxis::setLabelColor(const QColor &color)
4982 {
4983  mLabelColor = color;
4984 }
4985 
4990 void QCPAxis::setLabel(const QString &str)
4991 {
4992  if (mLabel != str)
4993  {
4994  mLabel = str;
4995  mCachedMarginValid = false;
4996  }
4997 }
4998 
5005 {
5006  if (mAxisPainter->labelPadding != padding)
5007  {
5009  mCachedMarginValid = false;
5010  }
5011 }
5012 
5024 {
5025  if (mPadding != padding)
5026  {
5027  mPadding = padding;
5028  mCachedMarginValid = false;
5029  }
5030 }
5031 
5041 {
5043 }
5044 
5050 void QCPAxis::setSelectedTickLabelFont(const QFont &font)
5051 {
5052  if (font != mSelectedTickLabelFont)
5053  {
5054  mSelectedTickLabelFont = font;
5055  // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
5056  }
5057 }
5058 
5064 void QCPAxis::setSelectedLabelFont(const QFont &font)
5065 {
5066  mSelectedLabelFont = font;
5067  // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
5068 }
5069 
5075 void QCPAxis::setSelectedTickLabelColor(const QColor &color)
5076 {
5077  if (color != mSelectedTickLabelColor)
5078  {
5079  mSelectedTickLabelColor = color;
5080  }
5081 }
5082 
5088 void QCPAxis::setSelectedLabelColor(const QColor &color)
5089 {
5090  mSelectedLabelColor = color;
5091 }
5092 
5098 void QCPAxis::setSelectedBasePen(const QPen &pen)
5099 {
5100  mSelectedBasePen = pen;
5101 }
5102 
5108 void QCPAxis::setSelectedTickPen(const QPen &pen)
5109 {
5110  mSelectedTickPen = pen;
5111 }
5112 
5118 void QCPAxis::setSelectedSubTickPen(const QPen &pen)
5119 {
5120  mSelectedSubTickPen = pen;
5121 }
5122 
5134 {
5135  mAxisPainter->lowerEnding = ending;
5136 }
5137 
5149 {
5150  mAxisPainter->upperEnding = ending;
5151 }
5152 
5161 {
5162  QCPRange oldRange = mRange;
5163  if (mScaleType == stLinear)
5164  {
5165  mRange.lower += diff;
5166  mRange.upper += diff;
5167  } else // mScaleType == stLogarithmic
5168  {
5169  mRange.lower *= diff;
5170  mRange.upper *= diff;
5171  }
5172  mCachedMarginValid = false;
5173  emit rangeChanged(mRange);
5174  emit rangeChanged(mRange, oldRange);
5175 }
5176 
5183 void QCPAxis::scaleRange(double factor, double center)
5184 {
5185  QCPRange oldRange = mRange;
5186  if (mScaleType == stLinear)
5187  {
5188  QCPRange newRange;
5189  newRange.lower = (mRange.lower-center)*factor + center;
5190  newRange.upper = (mRange.upper-center)*factor + center;
5191  if (QCPRange::validRange(newRange))
5192  mRange = newRange.sanitizedForLinScale();
5193  } else // mScaleType == stLogarithmic
5194  {
5195  if ((mRange.upper < 0 && center < 0) || (mRange.upper > 0 && center > 0)) // make sure center has same sign as range
5196  {
5197  QCPRange newRange;
5198  newRange.lower = qPow(mRange.lower/center, factor)*center;
5199  newRange.upper = qPow(mRange.upper/center, factor)*center;
5200  if (QCPRange::validRange(newRange))
5201  mRange = newRange.sanitizedForLogScale();
5202  } else
5203  qDebug() << Q_FUNC_INFO << "Center of scaling operation doesn't lie in same logarithmic sign domain as range:" << center;
5204  }
5205  mCachedMarginValid = false;
5206  emit rangeChanged(mRange);
5207  emit rangeChanged(mRange, oldRange);
5208 }
5209 
5223 void QCPAxis::setScaleRatio(const QCPAxis *otherAxis, double ratio)
5224 {
5225  int otherPixelSize, ownPixelSize;
5226 
5227  if (otherAxis->orientation() == Qt::Horizontal)
5228  otherPixelSize = otherAxis->axisRect()->width();
5229  else
5230  otherPixelSize = otherAxis->axisRect()->height();
5231 
5232  if (orientation() == Qt::Horizontal)
5233  ownPixelSize = axisRect()->width();
5234  else
5235  ownPixelSize = axisRect()->height();
5236 
5237  double newRangeSize = ratio*otherAxis->range().size()*ownPixelSize/(double)otherPixelSize;
5238  setRange(range().center(), newRangeSize, Qt::AlignCenter);
5239 }
5240 
5247 void QCPAxis::rescale(bool onlyVisiblePlottables)
5248 {
5249  QList<QCPAbstractPlottable*> p = plottables();
5250  QCPRange newRange;
5251  bool haveRange = false;
5252  for (int i=0; i<p.size(); ++i)
5253  {
5254  if (!p.at(i)->realVisibility() && onlyVisiblePlottables)
5255  continue;
5256  QCPRange plottableRange;
5257  bool currentFoundRange;
5259  if (mScaleType == stLogarithmic)
5261  if (p.at(i)->keyAxis() == this)
5262  plottableRange = p.at(i)->getKeyRange(currentFoundRange, signDomain);
5263  else
5264  plottableRange = p.at(i)->getValueRange(currentFoundRange, signDomain);
5265  if (currentFoundRange)
5266  {
5267  if (!haveRange)
5268  newRange = plottableRange;
5269  else
5270  newRange.expand(plottableRange);
5271  haveRange = true;
5272  }
5273  }
5274  if (haveRange)
5275  {
5276  if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
5277  {
5278  double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
5279  if (mScaleType == stLinear)
5280  {
5281  newRange.lower = center-mRange.size()/2.0;
5282  newRange.upper = center+mRange.size()/2.0;
5283  } else // mScaleType == stLogarithmic
5284  {
5285  newRange.lower = center/qSqrt(mRange.upper/mRange.lower);
5286  newRange.upper = center*qSqrt(mRange.upper/mRange.lower);
5287  }
5288  }
5289  setRange(newRange);
5290  }
5291 }
5292 
5296 double QCPAxis::pixelToCoord(double value) const
5297 {
5298  if (orientation() == Qt::Horizontal)
5299  {
5300  if (mScaleType == stLinear)
5301  {
5302  if (!mRangeReversed)
5303  return (value-mAxisRect->left())/(double)mAxisRect->width()*mRange.size()+mRange.lower;
5304  else
5305  return -(value-mAxisRect->left())/(double)mAxisRect->width()*mRange.size()+mRange.upper;
5306  } else // mScaleType == stLogarithmic
5307  {
5308  if (!mRangeReversed)
5309  return qPow(mRange.upper/mRange.lower, (value-mAxisRect->left())/(double)mAxisRect->width())*mRange.lower;
5310  else
5311  return qPow(mRange.upper/mRange.lower, (mAxisRect->left()-value)/(double)mAxisRect->width())*mRange.upper;
5312  }
5313  } else // orientation() == Qt::Vertical
5314  {
5315  if (mScaleType == stLinear)
5316  {
5317  if (!mRangeReversed)
5318  return (mAxisRect->bottom()-value)/(double)mAxisRect->height()*mRange.size()+mRange.lower;
5319  else
5320  return -(mAxisRect->bottom()-value)/(double)mAxisRect->height()*mRange.size()+mRange.upper;
5321  } else // mScaleType == stLogarithmic
5322  {
5323  if (!mRangeReversed)
5324  return qPow(mRange.upper/mRange.lower, (mAxisRect->bottom()-value)/(double)mAxisRect->height())*mRange.lower;
5325  else
5326  return qPow(mRange.upper/mRange.lower, (value-mAxisRect->bottom())/(double)mAxisRect->height())*mRange.upper;
5327  }
5328  }
5329 }
5330 
5334 double QCPAxis::coordToPixel(double value) const
5335 {
5336  if (orientation() == Qt::Horizontal)
5337  {
5338  if (mScaleType == stLinear)
5339  {
5340  if (!mRangeReversed)
5341  return (value-mRange.lower)/mRange.size()*mAxisRect->width()+mAxisRect->left();
5342  else
5343  return (mRange.upper-value)/mRange.size()*mAxisRect->width()+mAxisRect->left();
5344  } else // mScaleType == stLogarithmic
5345  {
5346  if (value >= 0 && mRange.upper < 0) // invalid value for logarithmic scale, just draw it outside visible range
5347  return !mRangeReversed ? mAxisRect->right()+200 : mAxisRect->left()-200;
5348  else if (value <= 0 && mRange.upper > 0) // invalid value for logarithmic scale, just draw it outside visible range
5349  return !mRangeReversed ? mAxisRect->left()-200 : mAxisRect->right()+200;
5350  else
5351  {
5352  if (!mRangeReversed)
5354  else
5356  }
5357  }
5358  } else // orientation() == Qt::Vertical
5359  {
5360  if (mScaleType == stLinear)
5361  {
5362  if (!mRangeReversed)
5363  return mAxisRect->bottom()-(value-mRange.lower)/mRange.size()*mAxisRect->height();
5364  else
5365  return mAxisRect->bottom()-(mRange.upper-value)/mRange.size()*mAxisRect->height();
5366  } else // mScaleType == stLogarithmic
5367  {
5368  if (value >= 0 && mRange.upper < 0) // invalid value for logarithmic scale, just draw it outside visible range
5369  return !mRangeReversed ? mAxisRect->top()-200 : mAxisRect->bottom()+200;
5370  else if (value <= 0 && mRange.upper > 0) // invalid value for logarithmic scale, just draw it outside visible range
5371  return !mRangeReversed ? mAxisRect->bottom()+200 : mAxisRect->top()-200;
5372  else
5373  {
5374  if (!mRangeReversed)
5376  else
5378  }
5379  }
5380  }
5381 }
5382 
5393 {
5394  if (!mVisible)
5395  return spNone;
5396 
5397  if (mAxisPainter->axisSelectionBox().contains(pos.toPoint()))
5398  return spAxis;
5399  else if (mAxisPainter->tickLabelsSelectionBox().contains(pos.toPoint()))
5400  return spTickLabels;
5401  else if (mAxisPainter->labelSelectionBox().contains(pos.toPoint()))
5402  return spAxisLabel;
5403  else
5404  return spNone;
5405 }
5406 
5407 /* inherits documentation from base class */
5408 double QCPAxis::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
5409 {
5410  if (!mParentPlot) return -1;
5411  SelectablePart part = getPartAt(pos);
5412  if ((onlySelectable && !mSelectableParts.testFlag(part)) || part == spNone)
5413  return -1;
5414 
5415  if (details)
5416  details->setValue(part);
5417  return mParentPlot->selectionTolerance()*0.99;
5418 }
5419 
5427 QList<QCPAbstractPlottable*> QCPAxis::plottables() const
5428 {
5429  QList<QCPAbstractPlottable*> result;
5430  if (!mParentPlot) return result;
5431 
5432  for (int i=0; i<mParentPlot->mPlottables.size(); ++i)
5433  {
5434  if (mParentPlot->mPlottables.at(i)->keyAxis() == this ||mParentPlot->mPlottables.at(i)->valueAxis() == this)
5435  result.append(mParentPlot->mPlottables.at(i));
5436  }
5437  return result;
5438 }
5439 
5445 QList<QCPGraph*> QCPAxis::graphs() const
5446 {
5447  QList<QCPGraph*> result;
5448  if (!mParentPlot) return result;
5449 
5450  for (int i=0; i<mParentPlot->mGraphs.size(); ++i)
5451  {
5452  if (mParentPlot->mGraphs.at(i)->keyAxis() == this || mParentPlot->mGraphs.at(i)->valueAxis() == this)
5453  result.append(mParentPlot->mGraphs.at(i));
5454  }
5455  return result;
5456 }
5457 
5464 QList<QCPAbstractItem*> QCPAxis::items() const
5465 {
5466  QList<QCPAbstractItem*> result;
5467  if (!mParentPlot) return result;
5468 
5469  for (int itemId=0; itemId<mParentPlot->mItems.size(); ++itemId)
5470  {
5471  QList<QCPItemPosition*> positions = mParentPlot->mItems.at(itemId)->positions();
5472  for (int posId=0; posId<positions.size(); ++posId)
5473  {
5474  if (positions.at(posId)->keyAxis() == this || positions.at(posId)->valueAxis() == this)
5475  {
5476  result.append(mParentPlot->mItems.at(itemId));
5477  break;
5478  }
5479  }
5480  }
5481  return result;
5482 }
5483 
5489 {
5490  switch (side)
5491  {
5492  case QCP::msLeft: return atLeft;
5493  case QCP::msRight: return atRight;
5494  case QCP::msTop: return atTop;
5495  case QCP::msBottom: return atBottom;
5496  default: break;
5497  }
5498  qDebug() << Q_FUNC_INFO << "Invalid margin side passed:" << (int)side;
5499  return atLeft;
5500 }
5501 
5506 {
5507  switch (type)
5508  {
5509  case atLeft: return atRight; break;
5510  case atRight: return atLeft; break;
5511  case atBottom: return atTop; break;
5512  case atTop: return atBottom; break;
5513  default: qDebug() << Q_FUNC_INFO << "invalid axis type"; return atLeft; break;
5514  }
5515 }
5516 
5525 {
5526  if (!mParentPlot) return;
5527  if ((!mTicks && !mTickLabels && !mGrid->visible()) || mRange.size() <= 0) return;
5528 
5529  // fill tick vectors, either by auto generating or by notifying user to fill the vectors himself
5530  if (mAutoTicks)
5531  {
5533  } else
5534  {
5535  emit ticksRequest();
5536  }
5537 
5539  if (mTickVector.isEmpty())
5540  {
5541  mSubTickVector.clear();
5542  return;
5543  }
5544 
5545  // generate subticks between ticks:
5546  mSubTickVector.resize((mTickVector.size()-1)*mSubTickCount);
5547  if (mSubTickCount > 0)
5548  {
5549  double subTickStep = 0;
5550  double subTickPosition = 0;
5551  int subTickIndex = 0;
5552  bool done = false;
5555  for (int i=lowTick+1; i<=highTick; ++i)
5556  {
5557  subTickStep = (mTickVector.at(i)-mTickVector.at(i-1))/(double)(mSubTickCount+1);
5558  for (int k=1; k<=mSubTickCount; ++k)
5559  {
5560  subTickPosition = mTickVector.at(i-1) + k*subTickStep;
5561  if (subTickPosition < mRange.lower)
5562  continue;
5563  if (subTickPosition > mRange.upper)
5564  {
5565  done = true;
5566  break;
5567  }
5568  mSubTickVector[subTickIndex] = subTickPosition;
5569  subTickIndex++;
5570  }
5571  if (done) break;
5572  }
5573  mSubTickVector.resize(subTickIndex);
5574  }
5575 
5576  // generate tick labels according to tick positions:
5577  if (mAutoTickLabels)
5578  {
5579  int vecsize = mTickVector.size();
5580  mTickVectorLabels.resize(vecsize);
5581  if (mTickLabelType == ltNumber)
5582  {
5583  for (int i=mLowestVisibleTick; i<=mHighestVisibleTick; ++i)
5584  mTickVectorLabels[i] = mParentPlot->locale().toString(mTickVector.at(i), mNumberFormatChar.toLatin1(), mNumberPrecision);
5585  } else if (mTickLabelType == ltDateTime)
5586  {
5587  for (int i=mLowestVisibleTick; i<=mHighestVisibleTick; ++i)
5588  {
5589 #if QT_VERSION < QT_VERSION_CHECK(4, 7, 0) // use fromMSecsSinceEpoch function if available, to gain sub-second accuracy on tick labels (e.g. for format "hh:mm:ss:zzz")
5590  mTickVectorLabels[i] = mParentPlot->locale().toString(QDateTime::fromTime_t(mTickVector.at(i)).toTimeSpec(mDateTimeSpec), mDateTimeFormat);
5591 #else
5592  mTickVectorLabels[i] = mParentPlot->locale().toString(QDateTime::fromMSecsSinceEpoch(mTickVector.at(i)*1000).toTimeSpec(mDateTimeSpec), mDateTimeFormat);
5593 #endif
5594  }
5595  }
5596  } else // mAutoTickLabels == false
5597  {
5598  if (mAutoTicks) // ticks generated automatically, but not ticklabels, so emit ticksRequest here for labels
5599  {
5600  emit ticksRequest();
5601  }
5602  // make sure provided tick label vector has correct (minimal) length:
5603  if (mTickVectorLabels.size() < mTickVector.size())
5604  mTickVectorLabels.resize(mTickVector.size());
5605  }
5606 }
5607 
5618 {
5619  if (mScaleType == stLinear)
5620  {
5621  if (mAutoTickStep)
5622  {
5623  // Generate tick positions according to linear scaling:
5624  mTickStep = mRange.size()/(double)(mAutoTickCount+1e-10); // mAutoTickCount ticks on average, the small addition is to prevent jitter on exact integers
5625  double magnitudeFactor = qPow(10.0, qFloor(qLn(mTickStep)/qLn(10.0))); // get magnitude factor e.g. 0.01, 1, 10, 1000 etc.
5626  double tickStepMantissa = mTickStep/magnitudeFactor;
5627  if (tickStepMantissa < 5)
5628  {
5629  // round digit after decimal point to 0.5
5630  mTickStep = (int)(tickStepMantissa*2)/2.0*magnitudeFactor;
5631  } else
5632  {
5633  // round to first digit in multiples of 2
5634  mTickStep = (int)(tickStepMantissa/2.0)*2.0*magnitudeFactor;
5635  }
5636  }
5637  if (mAutoSubTicks)
5639  // Generate tick positions according to mTickStep:
5640  qint64 firstStep = floor(mRange.lower/mTickStep); // do not use qFloor here, or we'll lose 64 bit precision
5641  qint64 lastStep = ceil(mRange.upper/mTickStep); // do not use qCeil here, or we'll lose 64 bit precision
5642  int tickcount = lastStep-firstStep+1;
5643  if (tickcount < 0) tickcount = 0;
5644  mTickVector.resize(tickcount);
5645  for (int i=0; i<tickcount; ++i)
5646  mTickVector[i] = (firstStep+i)*mTickStep;
5647  } else // mScaleType == stLogarithmic
5648  {
5649  // Generate tick positions according to logbase scaling:
5650  if (mRange.lower > 0 && mRange.upper > 0) // positive range
5651  {
5652  double lowerMag = basePow(qFloor(baseLog(mRange.lower)));
5653  double currentMag = lowerMag;
5654  mTickVector.clear();
5655  mTickVector.append(currentMag);
5656  while (currentMag < mRange.upper && currentMag > 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
5657  {
5658  currentMag *= mScaleLogBase;
5659  mTickVector.append(currentMag);
5660  }
5661  } else if (mRange.lower < 0 && mRange.upper < 0) // negative range
5662  {
5663  double lowerMag = -basePow(qCeil(baseLog(-mRange.lower)));
5664  double currentMag = lowerMag;
5665  mTickVector.clear();
5666  mTickVector.append(currentMag);
5667  while (currentMag < mRange.upper && currentMag < 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
5668  {
5669  currentMag /= mScaleLogBase;
5670  mTickVector.append(currentMag);
5671  }
5672  } else // invalid range for logarithmic scale, because lower and upper have different sign
5673  {
5674  mTickVector.clear();
5675  qDebug() << Q_FUNC_INFO << "Invalid range for logarithmic plot: " << mRange.lower << "-" << mRange.upper;
5676  }
5677  }
5678 }
5679 
5694 {
5695  int result = mSubTickCount; // default to current setting, if no proper value can be found
5696 
5697  // get mantissa of tickstep:
5698  double magnitudeFactor = qPow(10.0, qFloor(qLn(tickStep)/qLn(10.0))); // get magnitude factor e.g. 0.01, 1, 10, 1000 etc.
5699  double tickStepMantissa = tickStep/magnitudeFactor;
5700 
5701  // separate integer and fractional part of mantissa:
5702  double epsilon = 0.01;
5703  double intPartf;
5704  int intPart;
5705  double fracPart = modf(tickStepMantissa, &intPartf);
5706  intPart = intPartf;
5707 
5708  // handle cases with (almost) integer mantissa:
5709  if (fracPart < epsilon || 1.0-fracPart < epsilon)
5710  {
5711  if (1.0-fracPart < epsilon)
5712  ++intPart;
5713  switch (intPart)
5714  {
5715  case 1: result = 4; break; // 1.0 -> 0.2 substep
5716  case 2: result = 3; break; // 2.0 -> 0.5 substep
5717  case 3: result = 2; break; // 3.0 -> 1.0 substep
5718  case 4: result = 3; break; // 4.0 -> 1.0 substep
5719  case 5: result = 4; break; // 5.0 -> 1.0 substep
5720  case 6: result = 2; break; // 6.0 -> 2.0 substep
5721  case 7: result = 6; break; // 7.0 -> 1.0 substep
5722  case 8: result = 3; break; // 8.0 -> 2.0 substep
5723  case 9: result = 2; break; // 9.0 -> 3.0 substep
5724  }
5725  } else
5726  {
5727  // handle cases with significantly fractional mantissa:
5728  if (qAbs(fracPart-0.5) < epsilon) // *.5 mantissa
5729  {
5730  switch (intPart)
5731  {
5732  case 1: result = 2; break; // 1.5 -> 0.5 substep
5733  case 2: result = 4; break; // 2.5 -> 0.5 substep
5734  case 3: result = 4; break; // 3.5 -> 0.7 substep
5735  case 4: result = 2; break; // 4.5 -> 1.5 substep
5736  case 5: result = 4; break; // 5.5 -> 1.1 substep (won't occur with autoTickStep from here on)
5737  case 6: result = 4; break; // 6.5 -> 1.3 substep
5738  case 7: result = 2; break; // 7.5 -> 2.5 substep
5739  case 8: result = 4; break; // 8.5 -> 1.7 substep
5740  case 9: result = 4; break; // 9.5 -> 1.9 substep
5741  }
5742  }
5743  // if mantissa fraction isnt 0.0 or 0.5, don't bother finding good sub tick marks, leave default
5744  }
5745 
5746  return result;
5747 }
5748 
5749 /* inherits documentation from base class */
5750 void QCPAxis::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
5751 {
5752  Q_UNUSED(event)
5753  SelectablePart part = details.value<SelectablePart>();
5754  if (mSelectableParts.testFlag(part))
5755  {
5756  SelectableParts selBefore = mSelectedParts;
5757  setSelectedParts(additive ? mSelectedParts^part : part);
5758  if (selectionStateChanged)
5759  *selectionStateChanged = mSelectedParts != selBefore;
5760  }
5761 }
5762 
5763 /* inherits documentation from base class */
5764 void QCPAxis::deselectEvent(bool *selectionStateChanged)
5765 {
5766  SelectableParts selBefore = mSelectedParts;
5768  if (selectionStateChanged)
5769  *selectionStateChanged = mSelectedParts != selBefore;
5770 }
5771 
5786 {
5788 }
5789 
5796 {
5797  const int lowTick = mLowestVisibleTick;
5798  const int highTick = mHighestVisibleTick;
5799  QVector<double> subTickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
5800  QVector<double> tickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
5801  QVector<QString> tickLabels; // the final vector passed to QCPAxisPainter
5802  tickPositions.reserve(highTick-lowTick+1);
5803  tickLabels.reserve(highTick-lowTick+1);
5804  subTickPositions.reserve(mSubTickVector.size());
5805 
5806  if (mTicks)
5807  {
5808  for (int i=lowTick; i<=highTick; ++i)
5809  {
5810  tickPositions.append(coordToPixel(mTickVector.at(i)));
5811  if (mTickLabels)
5812  tickLabels.append(mTickVectorLabels.at(i));
5813  }
5814 
5815  if (mSubTickCount > 0)
5816  {
5817  const int subTickCount = mSubTickVector.size();
5818  for (int i=0; i<subTickCount; ++i) // no need to check bounds because subticks are always only created inside current mRange
5819  subTickPositions.append(coordToPixel(mSubTickVector.at(i)));
5820  }
5821  }
5822  // transfer all properties of this axis to QCPAxisPainterPrivate which it needs to draw the axis.
5823  // Note that some axis painter properties are already set by direct feed-through with QCPAxis setters
5838  mAxisPainter->tickPositions = tickPositions;
5840  mAxisPainter->subTickPositions = subTickPositions;
5841  mAxisPainter->draw(painter);
5842 }
5843 
5860 void QCPAxis::visibleTickBounds(int &lowIndex, int &highIndex) const
5861 {
5862  bool lowFound = false;
5863  bool highFound = false;
5864  lowIndex = 0;
5865  highIndex = -1;
5866 
5867  for (int i=0; i < mTickVector.size(); ++i)
5868  {
5869  if (mTickVector.at(i) >= mRange.lower)
5870  {
5871  lowFound = true;
5872  lowIndex = i;
5873  break;
5874  }
5875  }
5876  for (int i=mTickVector.size()-1; i >= 0; --i)
5877  {
5878  if (mTickVector.at(i) <= mRange.upper)
5879  {
5880  highFound = true;
5881  highIndex = i;
5882  break;
5883  }
5884  }
5885 
5886  if (!lowFound && highFound)
5887  lowIndex = highIndex+1;
5888  else if (lowFound && !highFound)
5889  highIndex = lowIndex-1;
5890 }
5891 
5900 double QCPAxis::baseLog(double value) const
5901 {
5902  return qLn(value)*mScaleLogBaseLogInv;
5903 }
5904 
5912 double QCPAxis::basePow(double value) const
5913 {
5914  return qPow(mScaleLogBase, value);
5915 }
5916 
5923 {
5924  return mSelectedParts.testFlag(spAxis) ? mSelectedBasePen : mBasePen;
5925 }
5926 
5933 {
5934  return mSelectedParts.testFlag(spAxis) ? mSelectedTickPen : mTickPen;
5935 }
5936 
5943 {
5945 }
5946 
5953 {
5955 }
5956 
5963 {
5965 }
5966 
5973 {
5975 }
5976 
5983 {
5985 }
5986 
6002 {
6003  if (!mVisible) // if not visible, directly return 0, don't cache 0 because we can't react to setVisible in QCPAxis
6004  return 0;
6005 
6006  if (mCachedMarginValid)
6007  return mCachedMargin;
6008 
6009  // run through similar steps as QCPAxis::draw, and caluclate margin needed to fit axis and its labels
6010  int margin = 0;
6011 
6012  int lowTick, highTick;
6013  visibleTickBounds(lowTick, highTick);
6014  QVector<double> tickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
6015  QVector<QString> tickLabels; // the final vector passed to QCPAxisPainter
6016  tickPositions.reserve(highTick-lowTick+1);
6017  tickLabels.reserve(highTick-lowTick+1);
6018  if (mTicks)
6019  {
6020  for (int i=lowTick; i<=highTick; ++i)
6021  {
6022  tickPositions.append(coordToPixel(mTickVector.at(i)));
6023  if (mTickLabels)
6024  tickLabels.append(mTickVectorLabels.at(i));
6025  }
6026  }
6027  // transfer all properties of this axis to QCPAxisPainterPrivate which it needs to calculate the size.
6028  // Note that some axis painter properties are already set by direct feed-through with QCPAxis setters
6035  mAxisPainter->tickPositions = tickPositions;
6037  margin += mAxisPainter->size();
6038  margin += mPadding;
6039 
6040  mCachedMargin = margin;
6041  mCachedMarginValid = true;
6042  return margin;
6043 }
6044 
6045 /* inherits documentation from base class */
6047 {
6048  return QCP::iSelectAxes;
6049 }
6050 
6051 
6055 
6073  type(QCPAxis::atLeft),
6074  basePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
6075  lowerEnding(QCPLineEnding::esNone),
6076  upperEnding(QCPLineEnding::esNone),
6077  labelPadding(0),
6078  tickLabelPadding(0),
6079  tickLabelRotation(0),
6081  substituteExponent(true),
6082  numberMultiplyCross(false),
6083  tickLengthIn(5),
6084  tickLengthOut(0),
6085  subTickLengthIn(2),
6086  subTickLengthOut(0),
6087  tickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
6088  subTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
6089  offset(0),
6090  abbreviateDecimalPowers(false),
6091  reversedEndings(false),
6092  mParentPlot(parentPlot),
6093  mLabelCache(16) // cache at most 16 (tick) labels
6094 {
6095 }
6096 
6098 {
6099 }
6100 
6109 {
6110  QByteArray newHash = generateLabelParameterHash();
6111  if (newHash != mLabelParameterHash)
6112  {
6113  mLabelCache.clear();
6114  mLabelParameterHash = newHash;
6115  }
6116 
6117  QPoint origin;
6118  switch (type)
6119  {
6120  case QCPAxis::atLeft: origin = axisRect.bottomLeft() +QPoint(-offset, 0); break;
6121  case QCPAxis::atRight: origin = axisRect.bottomRight()+QPoint(+offset, 0); break;
6122  case QCPAxis::atTop: origin = axisRect.topLeft() +QPoint(0, -offset); break;
6123  case QCPAxis::atBottom: origin = axisRect.bottomLeft() +QPoint(0, +offset); break;
6124  }
6125 
6126  double xCor = 0, yCor = 0; // paint system correction, for pixel exact matches (affects baselines and ticks of top/right axes)
6127  switch (type)
6128  {
6129  case QCPAxis::atTop: yCor = -1; break;
6130  case QCPAxis::atRight: xCor = 1; break;
6131  default: break;
6132  }
6133 
6134  int margin = 0;
6135  // draw baseline:
6136  QLineF baseLine;
6137  painter->setPen(basePen);
6138  if (QCPAxis::orientation(type) == Qt::Horizontal)
6139  baseLine.setPoints(origin+QPointF(xCor, yCor), origin+QPointF(axisRect.width()+xCor, yCor));
6140  else
6141  baseLine.setPoints(origin+QPointF(xCor, yCor), origin+QPointF(xCor, -axisRect.height()+yCor));
6142  if (reversedEndings)
6143  baseLine = QLineF(baseLine.p2(), baseLine.p1()); // won't make a difference for line itself, but for line endings later
6144  painter->drawLine(baseLine);
6145 
6146  // draw ticks:
6147  if (!tickPositions.isEmpty())
6148  {
6149  painter->setPen(tickPen);
6150  int tickDir = (type == QCPAxis::atBottom || type == QCPAxis::atRight) ? -1 : 1; // direction of ticks ("inward" is right for left axis and left for right axis)
6151  if (QCPAxis::orientation(type) == Qt::Horizontal)
6152  {
6153  for (int i=0; i<tickPositions.size(); ++i)
6154  painter->drawLine(QLineF(tickPositions.at(i)+xCor, origin.y()-tickLengthOut*tickDir+yCor, tickPositions.at(i)+xCor, origin.y()+tickLengthIn*tickDir+yCor));
6155  } else
6156  {
6157  for (int i=0; i<tickPositions.size(); ++i)
6158  painter->drawLine(QLineF(origin.x()-tickLengthOut*tickDir+xCor, tickPositions.at(i)+yCor, origin.x()+tickLengthIn*tickDir+xCor, tickPositions.at(i)+yCor));
6159  }
6160  }
6161 
6162  // draw subticks:
6163  if (!subTickPositions.isEmpty())
6164  {
6165  painter->setPen(subTickPen);
6166  // direction of ticks ("inward" is right for left axis and left for right axis)
6167  int tickDir = (type == QCPAxis::atBottom || type == QCPAxis::atRight) ? -1 : 1;
6168  if (QCPAxis::orientation(type) == Qt::Horizontal)
6169  {
6170  for (int i=0; i<subTickPositions.size(); ++i)
6171  painter->drawLine(QLineF(subTickPositions.at(i)+xCor, origin.y()-subTickLengthOut*tickDir+yCor, subTickPositions.at(i)+xCor, origin.y()+subTickLengthIn*tickDir+yCor));
6172  } else
6173  {
6174  for (int i=0; i<subTickPositions.size(); ++i)
6175  painter->drawLine(QLineF(origin.x()-subTickLengthOut*tickDir+xCor, subTickPositions.at(i)+yCor, origin.x()+subTickLengthIn*tickDir+xCor, subTickPositions.at(i)+yCor));
6176  }
6177  }
6178  margin += qMax(0, qMax(tickLengthOut, subTickLengthOut));
6179 
6180  // draw axis base endings:
6181  bool antialiasingBackup = painter->antialiasing();
6182  painter->setAntialiasing(true); // always want endings to be antialiased, even if base and ticks themselves aren't
6183  painter->setBrush(QBrush(basePen.color()));
6184  QVector2D baseLineVector(baseLine.dx(), baseLine.dy());
6186  lowerEnding.draw(painter, QVector2D(baseLine.p1())-baseLineVector.normalized()*lowerEnding.realLength()*(lowerEnding.inverted()?-1:1), -baseLineVector);
6188  upperEnding.draw(painter, QVector2D(baseLine.p2())+baseLineVector.normalized()*upperEnding.realLength()*(upperEnding.inverted()?-1:1), baseLineVector);
6189  painter->setAntialiasing(antialiasingBackup);
6190 
6191  // tick labels:
6192  QRect oldClipRect;
6193  if (tickLabelSide == QCPAxis::lsInside) // if using inside labels, clip them to the axis rect
6194  {
6195  oldClipRect = painter->clipRegion().boundingRect();
6196  painter->setClipRect(axisRect);
6197  }
6198  QSize tickLabelsSize(0, 0); // size of largest tick label, for offset calculation of axis label
6199  if (!tickLabels.isEmpty())
6200  {
6202  margin += tickLabelPadding;
6203  painter->setFont(tickLabelFont);
6204  painter->setPen(QPen(tickLabelColor));
6205  const int maxLabelIndex = qMin(tickPositions.size(), tickLabels.size());
6206  int distanceToAxis = margin;
6208  distanceToAxis = -(qMax(tickLengthIn, subTickLengthIn)+tickLabelPadding);
6209  for (int i=0; i<maxLabelIndex; ++i)
6210  placeTickLabel(painter, tickPositions.at(i), distanceToAxis, tickLabels.at(i), &tickLabelsSize);
6212  margin += (QCPAxis::orientation(type) == Qt::Horizontal) ? tickLabelsSize.height() : tickLabelsSize.width();
6213  }
6215  painter->setClipRect(oldClipRect);
6216 
6217  // axis label:
6218  QRect labelBounds;
6219  if (!label.isEmpty())
6220  {
6221  margin += labelPadding;
6222  painter->setFont(labelFont);
6223  painter->setPen(QPen(labelColor));
6224  labelBounds = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip, label);
6225  if (type == QCPAxis::atLeft)
6226  {
6227  QTransform oldTransform = painter->transform();
6228  painter->translate((origin.x()-margin-labelBounds.height()), origin.y());
6229  painter->rotate(-90);
6230  painter->drawText(0, 0, axisRect.height(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
6231  painter->setTransform(oldTransform);
6232  }
6233  else if (type == QCPAxis::atRight)
6234  {
6235  QTransform oldTransform = painter->transform();
6236  painter->translate((origin.x()+margin+labelBounds.height()), origin.y()-axisRect.height());
6237  painter->rotate(90);
6238  painter->drawText(0, 0, axisRect.height(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
6239  painter->setTransform(oldTransform);
6240  }
6241  else if (type == QCPAxis::atTop)
6242  painter->drawText(origin.x(), origin.y()-margin-labelBounds.height(), axisRect.width(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
6243  else if (type == QCPAxis::atBottom)
6244  painter->drawText(origin.x(), origin.y()+margin, axisRect.width(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
6245  }
6246 
6247  // set selection boxes:
6248  int selectionTolerance = 0;
6249  if (mParentPlot)
6250  selectionTolerance = mParentPlot->selectionTolerance();
6251  else
6252  qDebug() << Q_FUNC_INFO << "mParentPlot is null";
6253  int selAxisOutSize = qMax(qMax(tickLengthOut, subTickLengthOut), selectionTolerance);
6254  int selAxisInSize = selectionTolerance;
6255  int selTickLabelSize;
6256  int selTickLabelOffset;
6258  {
6259  selTickLabelSize = (QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width());
6260  selTickLabelOffset = qMax(tickLengthOut, subTickLengthOut)+tickLabelPadding;
6261  } else
6262  {
6263  selTickLabelSize = -(QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width());
6264  selTickLabelOffset = -(qMax(tickLengthIn, subTickLengthIn)+tickLabelPadding);
6265  }
6266  int selLabelSize = labelBounds.height();
6267  int selLabelOffset = qMax(tickLengthOut, subTickLengthOut)+(!tickLabels.isEmpty() && tickLabelSide == QCPAxis::lsOutside ? tickLabelPadding+selTickLabelSize : 0)+labelPadding;
6268  if (type == QCPAxis::atLeft)
6269  {
6270  mAxisSelectionBox.setCoords(origin.x()-selAxisOutSize, axisRect.top(), origin.x()+selAxisInSize, axisRect.bottom());
6271  mTickLabelsSelectionBox.setCoords(origin.x()-selTickLabelOffset-selTickLabelSize, axisRect.top(), origin.x()-selTickLabelOffset, axisRect.bottom());
6272  mLabelSelectionBox.setCoords(origin.x()-selLabelOffset-selLabelSize, axisRect.top(), origin.x()-selLabelOffset, axisRect.bottom());
6273  } else if (type == QCPAxis::atRight)
6274  {
6275  mAxisSelectionBox.setCoords(origin.x()-selAxisInSize, axisRect.top(), origin.x()+selAxisOutSize, axisRect.bottom());
6276  mTickLabelsSelectionBox.setCoords(origin.x()+selTickLabelOffset+selTickLabelSize, axisRect.top(), origin.x()+selTickLabelOffset, axisRect.bottom());
6277  mLabelSelectionBox.setCoords(origin.x()+selLabelOffset+selLabelSize, axisRect.top(), origin.x()+selLabelOffset, axisRect.bottom());
6278  } else if (type == QCPAxis::atTop)
6279  {
6280  mAxisSelectionBox.setCoords(axisRect.left(), origin.y()-selAxisOutSize, axisRect.right(), origin.y()+selAxisInSize);
6281  mTickLabelsSelectionBox.setCoords(axisRect.left(), origin.y()-selTickLabelOffset-selTickLabelSize, axisRect.right(), origin.y()-selTickLabelOffset);
6282  mLabelSelectionBox.setCoords(axisRect.left(), origin.y()-selLabelOffset-selLabelSize, axisRect.right(), origin.y()-selLabelOffset);
6283  } else if (type == QCPAxis::atBottom)
6284  {
6285  mAxisSelectionBox.setCoords(axisRect.left(), origin.y()-selAxisInSize, axisRect.right(), origin.y()+selAxisOutSize);
6286  mTickLabelsSelectionBox.setCoords(axisRect.left(), origin.y()+selTickLabelOffset+selTickLabelSize, axisRect.right(), origin.y()+selTickLabelOffset);
6287  mLabelSelectionBox.setCoords(axisRect.left(), origin.y()+selLabelOffset+selLabelSize, axisRect.right(), origin.y()+selLabelOffset);
6288  }
6289  mAxisSelectionBox = mAxisSelectionBox.normalized();
6291  mLabelSelectionBox = mLabelSelectionBox.normalized();
6292  // draw hitboxes for debug purposes:
6293  //painter->setBrush(Qt::NoBrush);
6294  //painter->drawRects(QVector<QRect>() << mAxisSelectionBox << mTickLabelsSelectionBox << mLabelSelectionBox);
6295 }
6296 
6303 {
6304  int result = 0;
6305 
6306  // get length of tick marks pointing outwards:
6307  if (!tickPositions.isEmpty())
6308  result += qMax(0, qMax(tickLengthOut, subTickLengthOut));
6309 
6310  // calculate size of tick labels:
6312  {
6313  QSize tickLabelsSize(0, 0);
6314  if (!tickLabels.isEmpty())
6315  {
6316  for (int i=0; i<tickLabels.size(); ++i)
6317  getMaxTickLabelSize(tickLabelFont, tickLabels.at(i), &tickLabelsSize);
6318  result += QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width();
6319  result += tickLabelPadding;
6320  }
6321  }
6322 
6323  // calculate size of axis label (only height needed, because left/right labels are rotated by 90 degrees):
6324  if (!label.isEmpty())
6325  {
6326  QFontMetrics fontMetrics(labelFont);
6327  QRect bounds;
6328  bounds = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter | Qt::AlignVCenter, label);
6329  result += bounds.height() + labelPadding;
6330  }
6331 
6332  return result;
6333 }
6334 
6342 {
6343  mLabelCache.clear();
6344 }
6345 
6354 {
6355  QByteArray result;
6356  result.append(QByteArray::number(tickLabelRotation));
6357  result.append(QByteArray::number((int)tickLabelSide));
6358  result.append(QByteArray::number((int)substituteExponent));
6359  result.append(QByteArray::number((int)numberMultiplyCross));
6360  result.append(tickLabelColor.name().toLatin1()+QByteArray::number(tickLabelColor.alpha(), 16));
6361  result.append(tickLabelFont.toString().toLatin1());
6362  return result;
6363 }
6364 
6384 void QCPAxisPainterPrivate::placeTickLabel(QCPPainter *painter, double position, int distanceToAxis, const QString &text, QSize *tickLabelsSize)
6385 {
6386  // warning: if you change anything here, also adapt getMaxTickLabelSize() accordingly!
6387  if (text.isEmpty()) return;
6388  QSize finalSize;
6389  QPointF labelAnchor;
6390  switch (type)
6391  {
6392  case QCPAxis::atLeft: labelAnchor = QPointF(axisRect.left()-distanceToAxis-offset, position); break;
6393  case QCPAxis::atRight: labelAnchor = QPointF(axisRect.right()+distanceToAxis+offset, position); break;
6394  case QCPAxis::atTop: labelAnchor = QPointF(position, axisRect.top()-distanceToAxis-offset); break;
6395  case QCPAxis::atBottom: labelAnchor = QPointF(position, axisRect.bottom()+distanceToAxis+offset); break;
6396  }
6397  if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && !painter->modes().testFlag(QCPPainter::pmNoCaching)) // label caching enabled
6398  {
6399  if (!mLabelCache.contains(text)) // no cached label exists, create it
6400  {
6401  CachedLabel *newCachedLabel = new CachedLabel;
6402  TickLabelData labelData = getTickLabelData(painter->font(), text);
6403  newCachedLabel->offset = getTickLabelDrawOffset(labelData)+labelData.rotatedTotalBounds.topLeft();
6404  newCachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size());
6405  newCachedLabel->pixmap.fill(Qt::transparent);
6406  QCPPainter cachePainter(&newCachedLabel->pixmap);
6407  cachePainter.setPen(painter->pen());
6408  drawTickLabel(&cachePainter, -labelData.rotatedTotalBounds.topLeft().x(), -labelData.rotatedTotalBounds.topLeft().y(), labelData);
6409  mLabelCache.insert(text, newCachedLabel, 1);
6410  }
6411  // draw cached label:
6412  const CachedLabel *cachedLabel = mLabelCache.object(text);
6413  // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
6415  {
6416  if (QCPAxis::orientation(type) == Qt::Horizontal)
6417  {
6418  if (labelAnchor.x()+cachedLabel->offset.x()+cachedLabel->pixmap.width() > viewportRect.right() ||
6419  labelAnchor.x()+cachedLabel->offset.x() < viewportRect.left())
6420  return;
6421  } else
6422  {
6423  if (labelAnchor.y()+cachedLabel->offset.y()+cachedLabel->pixmap.height() >viewportRect.bottom() ||
6424  labelAnchor.y()+cachedLabel->offset.y() < viewportRect.top())
6425  return;
6426  }
6427  }
6428  painter->drawPixmap(labelAnchor+cachedLabel->offset, cachedLabel->pixmap);
6429  finalSize = cachedLabel->pixmap.size();
6430  } else // label caching disabled, draw text directly on surface:
6431  {
6432  TickLabelData labelData = getTickLabelData(painter->font(), text);
6433  QPointF finalPosition = labelAnchor + getTickLabelDrawOffset(labelData);
6434  // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
6436  {
6437  if (QCPAxis::orientation(type) == Qt::Horizontal)
6438  {
6439  if (finalPosition.x()+(labelData.rotatedTotalBounds.width()+labelData.rotatedTotalBounds.left()) > viewportRect.right() ||
6440  finalPosition.x()+labelData.rotatedTotalBounds.left() < viewportRect.left())
6441  return;
6442  } else
6443  {
6444  if (finalPosition.y()+(labelData.rotatedTotalBounds.height()+labelData.rotatedTotalBounds.top()) > viewportRect.bottom() ||
6445  finalPosition.y()+labelData.rotatedTotalBounds.top() < viewportRect.top())
6446  return;
6447  }
6448  }
6449  drawTickLabel(painter, finalPosition.x(), finalPosition.y(), labelData);
6450  finalSize = labelData.rotatedTotalBounds.size();
6451  }
6452 
6453  // expand passed tickLabelsSize if current tick label is larger:
6454  if (finalSize.width() > tickLabelsSize->width())
6455  tickLabelsSize->setWidth(finalSize.width());
6456  if (finalSize.height() > tickLabelsSize->height())
6457  tickLabelsSize->setHeight(finalSize.height());
6458 }
6459 
6469 void QCPAxisPainterPrivate::drawTickLabel(QCPPainter *painter, double x, double y, const TickLabelData &labelData) const
6470 {
6471  // backup painter settings that we're about to change:
6472  QTransform oldTransform = painter->transform();
6473  QFont oldFont = painter->font();
6474 
6475  // transform painter to position/rotation:
6476  painter->translate(x, y);
6477  if (!qFuzzyIsNull(tickLabelRotation))
6478  painter->rotate(tickLabelRotation);
6479 
6480  // draw text:
6481  if (!labelData.expPart.isEmpty()) // indicator that beautiful powers must be used
6482  {
6483  painter->setFont(labelData.baseFont);
6484  painter->drawText(0, 0, 0, 0, Qt::TextDontClip, labelData.basePart);
6485  painter->setFont(labelData.expFont);
6486  painter->drawText(labelData.baseBounds.width()+1, 0, labelData.expBounds.width(), labelData.expBounds.height(), Qt::TextDontClip, labelData.expPart);
6487  } else
6488  {
6489  painter->setFont(labelData.baseFont);
6490  painter->drawText(0, 0, labelData.totalBounds.width(), labelData.totalBounds.height(), Qt::TextDontClip | Qt::AlignHCenter, labelData.basePart);
6491  }
6492 
6493  // reset painter settings to what it was before:
6494  painter->setTransform(oldTransform);
6495  painter->setFont(oldFont);
6496 }
6497 
6507 {
6508  TickLabelData result;
6509 
6510  // determine whether beautiful decimal powers should be used
6511  bool useBeautifulPowers = false;
6512  int ePos = -1;
6513  if (substituteExponent)
6514  {
6515  ePos = text.indexOf(QLatin1Char('e'));
6516  if (ePos > -1)
6517  useBeautifulPowers = true;
6518  }
6519 
6520  // calculate text bounding rects and do string preparation for beautiful decimal powers:
6521  result.baseFont = font;
6522  if (result.baseFont.pointSizeF() > 0) // On some rare systems, this sometimes is initialized with -1 (Qt bug?), so we check here before possibly setting a negative value in the next line
6523  result.baseFont.setPointSizeF(result.baseFont.pointSizeF()+0.05); // QFontMetrics.boundingRect has a bug for exact point sizes that make the results oscillate due to internal rounding
6524  if (useBeautifulPowers)
6525  {
6526  // split text into parts of number/symbol that will be drawn normally and part that will be drawn as exponent:
6527  result.basePart = text.left(ePos);
6528  // in log scaling, we want to turn "1*10^n" into "10^n", else add multiplication sign and decimal base:
6529  if (abbreviateDecimalPowers && result.basePart == QLatin1String("1"))
6530  result.basePart = QLatin1String("10");
6531  else
6532  result.basePart += (numberMultiplyCross ? QString(QChar(215)) : QString(QChar(183))) + QLatin1String("10");
6533  result.expPart = text.mid(ePos+1);
6534  // clip "+" and leading zeros off expPart:
6535  while (result.expPart.length() > 2 && result.expPart.at(1) == QLatin1Char('0')) // length > 2 so we leave one zero when numberFormatChar is 'e'
6536  result.expPart.remove(1, 1);
6537  if (!result.expPart.isEmpty() && result.expPart.at(0) == QLatin1Char('+'))
6538  result.expPart.remove(0, 1);
6539  // prepare smaller font for exponent:
6540  result.expFont = font;
6541  result.expFont.setPointSize(result.expFont.pointSize()*0.75);
6542  // calculate bounding rects of base part, exponent part and total one:
6543  result.baseBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.basePart);
6544  result.expBounds = QFontMetrics(result.expFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.expPart);
6545  result.totalBounds = result.baseBounds.adjusted(0, 0, result.expBounds.width()+2, 0); // +2 consists of the 1 pixel spacing between base and exponent (see drawTickLabel) and an extra pixel to include AA
6546  } else // useBeautifulPowers == false
6547  {
6548  result.basePart = text;
6549  result.totalBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter, result.basePart);
6550  }
6551  result.totalBounds.moveTopLeft(QPoint(0, 0)); // want bounding box aligned top left at origin, independent of how it was created, to make further processing simpler
6552 
6553  // calculate possibly different bounding rect after rotation:
6554  result.rotatedTotalBounds = result.totalBounds;
6555  if (!qFuzzyIsNull(tickLabelRotation))
6556  {
6557  QTransform transform;
6558  transform.rotate(tickLabelRotation);
6559  result.rotatedTotalBounds = transform.mapRect(result.rotatedTotalBounds);
6560  }
6561 
6562  return result;
6563 }
6564 
6576 {
6577  /*
6578  calculate label offset from base point at tick (non-trivial, for best visual appearance): short
6579  explanation for bottom axis: The anchor, i.e. the point in the label that is placed
6580  horizontally under the corresponding tick is always on the label side that is closer to the
6581  axis (e.g. the left side of the text when we're rotating clockwise). On that side, the height
6582  is halved and the resulting point is defined the anchor. This way, a 90 degree rotated text
6583  will be centered under the tick (i.e. displaced horizontally by half its height). At the same
6584  time, a 45 degree rotated text will "point toward" its tick, as is typical for rotated tick
6585  labels.
6586  */
6587  bool doRotation = !qFuzzyIsNull(tickLabelRotation);
6588  bool flip = qFuzzyCompare(qAbs(tickLabelRotation), 90.0); // perfect +/-90 degree flip. Indicates vertical label centering on vertical axes.
6589  double radians = tickLabelRotation/180.0*M_PI;
6590  int x=0, y=0;
6591  if ((type == QCPAxis::atLeft && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atRight && tickLabelSide == QCPAxis::lsInside)) // Anchor at right side of tick label
6592  {
6593  if (doRotation)
6594  {
6595  if (tickLabelRotation > 0)
6596  {
6597  x = -qCos(radians)*labelData.totalBounds.width();
6598  y = flip ? -labelData.totalBounds.width()/2.0 : -qSin(radians)*labelData.totalBounds.width()-qCos(radians)*labelData.totalBounds.height()/2.0;
6599  } else
6600  {
6601  x = -qCos(-radians)*labelData.totalBounds.width()-qSin(-radians)*labelData.totalBounds.height();
6602  y = flip ? +labelData.totalBounds.width()/2.0 : +qSin(-radians)*labelData.totalBounds.width()-qCos(-radians)*labelData.totalBounds.height()/2.0;
6603  }
6604  } else
6605  {
6606  x = -labelData.totalBounds.width();
6607  y = -labelData.totalBounds.height()/2.0;
6608  }
6609  } else if ((type == QCPAxis::atRight && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atLeft && tickLabelSide == QCPAxis::lsInside)) // Anchor at left side of tick label
6610  {
6611  if (doRotation)
6612  {
6613  if (tickLabelRotation > 0)
6614  {
6615  x = +qSin(radians)*labelData.totalBounds.height();
6616  y = flip ? -labelData.totalBounds.width()/2.0 : -qCos(radians)*labelData.totalBounds.height()/2.0;
6617  } else
6618  {
6619  x = 0;
6620  y = flip ? +labelData.totalBounds.width()/2.0 : -qCos(-radians)*labelData.totalBounds.height()/2.0;
6621  }
6622  } else
6623  {
6624  x = 0;
6625  y = -labelData.totalBounds.height()/2.0;
6626  }
6627  } else if ((type == QCPAxis::atTop && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atBottom && tickLabelSide == QCPAxis::lsInside)) // Anchor at bottom side of tick label
6628  {
6629  if (doRotation)
6630  {
6631  if (tickLabelRotation > 0)
6632  {
6633  x = -qCos(radians)*labelData.totalBounds.width()+qSin(radians)*labelData.totalBounds.height()/2.0;
6634  y = -qSin(radians)*labelData.totalBounds.width()-qCos(radians)*labelData.totalBounds.height();
6635  } else
6636  {
6637  x = -qSin(-radians)*labelData.totalBounds.height()/2.0;
6638  y = -qCos(-radians)*labelData.totalBounds.height();
6639  }
6640  } else
6641  {
6642  x = -labelData.totalBounds.width()/2.0;
6643  y = -labelData.totalBounds.height();
6644  }
6645  } else if ((type == QCPAxis::atBottom && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atTop && tickLabelSide == QCPAxis::lsInside)) // Anchor at top side of tick label
6646  {
6647  if (doRotation)
6648  {
6649  if (tickLabelRotation > 0)
6650  {
6651  x = +qSin(radians)*labelData.totalBounds.height()/2.0;
6652  y = 0;
6653  } else
6654  {
6655  x = -qCos(-radians)*labelData.totalBounds.width()-qSin(-radians)*labelData.totalBounds.height()/2.0;
6656  y = +qSin(-radians)*labelData.totalBounds.width();
6657  }
6658  } else
6659  {
6660  x = -labelData.totalBounds.width()/2.0;
6661  y = 0;
6662  }
6663  }
6664 
6665  return QPointF(x, y);
6666 }
6667 
6675 void QCPAxisPainterPrivate::getMaxTickLabelSize(const QFont &font, const QString &text, QSize *tickLabelsSize) const
6676 {
6677  // note: this function must return the same tick label sizes as the placeTickLabel function.
6678  QSize finalSize;
6679  if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && mLabelCache.contains(text)) // label caching enabled and have cached label
6680  {
6681  const CachedLabel *cachedLabel = mLabelCache.object(text);
6682  finalSize = cachedLabel->pixmap.size();
6683  } else // label caching disabled or no label with this text cached:
6684  {
6685  TickLabelData labelData = getTickLabelData(font, text);
6686  finalSize = labelData.rotatedTotalBounds.size();
6687  }
6688 
6689  // expand passed tickLabelsSize if current tick label is larger:
6690  if (finalSize.width() > tickLabelsSize->width())
6691  tickLabelsSize->setWidth(finalSize.width());
6692  if (finalSize.height() > tickLabelsSize->height())
6693  tickLabelsSize->setHeight(finalSize.height());
6694 }
6695 
6696 
6700 
6769 /* start of documentation of pure virtual functions */
6770 
6816 /* end of documentation of pure virtual functions */
6817 /* start of documentation of signals */
6818 
6832 /* end of documentation of signals */
6833 
6846  QCPLayerable(keyAxis->parentPlot(), QString(), keyAxis->axisRect()),
6847  mName(),
6848  mAntialiasedFill(true),
6849  mAntialiasedScatters(true),
6850  mAntialiasedErrorBars(false),
6851  mPen(Qt::black),
6852  mSelectedPen(Qt::black),
6853  mBrush(Qt::NoBrush),
6854  mSelectedBrush(Qt::NoBrush),
6855  mKeyAxis(keyAxis),
6856  mValueAxis(valueAxis),
6857  mSelectable(true),
6858  mSelected(false)
6859 {
6860  if (keyAxis->parentPlot() != valueAxis->parentPlot())
6861  qDebug() << Q_FUNC_INFO << "Parent plot of keyAxis is not the same as that of valueAxis.";
6862  if (keyAxis->orientation() == valueAxis->orientation())
6863  qDebug() << Q_FUNC_INFO << "keyAxis and valueAxis must be orthogonal to each other.";
6864 }
6865 
6871 {
6872  mName = name;
6873 }
6874 
6882 {
6883  mAntialiasedFill = enabled;
6884 }
6885 
6893 {
6894  mAntialiasedScatters = enabled;
6895 }
6896 
6904 {
6905  mAntialiasedErrorBars = enabled;
6906 }
6907 
6908 
6919 {
6920  mPen = pen;
6921 }
6922 
6930 {
6931  mSelectedPen = pen;
6932 }
6933 
6944 {
6945  mBrush = brush;
6946 }
6947 
6955 {
6957 }
6958 
6971 {
6972  mKeyAxis = axis;
6973 }
6974 
6987 {
6988  mValueAxis = axis;
6989 }
6990 
7001 {
7002  if (mSelectable != selectable)
7003  {
7006  }
7007 }
7008 
7024 {
7025  if (mSelected != selected)
7026  {
7027  mSelected = selected;
7029  }
7030 }
7031 
7045 void QCPAbstractPlottable::rescaleAxes(bool onlyEnlarge) const
7046 {
7047  rescaleKeyAxis(onlyEnlarge);
7048  rescaleValueAxis(onlyEnlarge);
7049 }
7050 
7056 void QCPAbstractPlottable::rescaleKeyAxis(bool onlyEnlarge) const
7057 {
7058  QCPAxis *keyAxis = mKeyAxis.data();
7059  if (!keyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; return; }
7060 
7061  SignDomain signDomain = sdBoth;
7062  if (keyAxis->scaleType() == QCPAxis::stLogarithmic)
7063  signDomain = (keyAxis->range().upper < 0 ? sdNegative : sdPositive);
7064 
7065  bool foundRange;
7066  QCPRange newRange = getKeyRange(foundRange, signDomain);
7067  if (foundRange)
7068  {
7069  if (onlyEnlarge)
7070  newRange.expand(keyAxis->range());
7071  if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
7072  {
7073  double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
7074  if (keyAxis->scaleType() == QCPAxis::stLinear)
7075  {
7076  newRange.lower = center-keyAxis->range().size()/2.0;
7077  newRange.upper = center+keyAxis->range().size()/2.0;
7078  } else // scaleType() == stLogarithmic
7079  {
7080  newRange.lower = center/qSqrt(keyAxis->range().upper/keyAxis->range().lower);
7081  newRange.upper = center*qSqrt(keyAxis->range().upper/keyAxis->range().lower);
7082  }
7083  }
7084  keyAxis->setRange(newRange);
7085  }
7086 }
7087 
7096 void QCPAbstractPlottable::rescaleValueAxis(bool onlyEnlarge) const
7097 {
7098  QCPAxis *valueAxis = mValueAxis.data();
7099  if (!valueAxis) { qDebug() << Q_FUNC_INFO << "invalid value axis"; return; }
7100 
7101  SignDomain signDomain = sdBoth;
7102  if (valueAxis->scaleType() == QCPAxis::stLogarithmic)
7103  signDomain = (valueAxis->range().upper < 0 ? sdNegative : sdPositive);
7104 
7105  bool foundRange;
7106  QCPRange newRange = getValueRange(foundRange, signDomain);
7107  if (foundRange)
7108  {
7109  if (onlyEnlarge)
7110  newRange.expand(valueAxis->range());
7111  if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
7112  {
7113  double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
7114  if (valueAxis->scaleType() == QCPAxis::stLinear)
7115  {
7116  newRange.lower = center-valueAxis->range().size()/2.0;
7117  newRange.upper = center+valueAxis->range().size()/2.0;
7118  } else // scaleType() == stLogarithmic
7119  {
7120  newRange.lower = center/qSqrt(valueAxis->range().upper/valueAxis->range().lower);
7121  newRange.upper = center*qSqrt(valueAxis->range().upper/valueAxis->range().lower);
7122  }
7123  }
7124  valueAxis->setRange(newRange);
7125  }
7126 }
7127 
7141 {
7142  if (!mParentPlot || !mParentPlot->legend)
7143  return false;
7144 
7146  {
7148  return true;
7149  } else
7150  return false;
7151 }
7152 
7164 {
7165  if (!mParentPlot->legend)
7166  return false;
7167 
7169  return mParentPlot->legend->removeItem(lip);
7170  else
7171  return false;
7172 }
7173 
7174 /* inherits documentation from base class */
7176 {
7177  if (mKeyAxis && mValueAxis)
7178  return mKeyAxis.data()->axisRect()->rect() & mValueAxis.data()->axisRect()->rect();
7179  else
7180  return QRect();
7181 }
7182 
7183 /* inherits documentation from base class */
7185 {
7186  return QCP::iSelectPlottables;
7187 }
7188 
7199 void QCPAbstractPlottable::coordsToPixels(double key, double value, double &x, double &y) const
7200 {
7201  QCPAxis *keyAxis = mKeyAxis.data();
7202  QCPAxis *valueAxis = mValueAxis.data();
7203  if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
7204 
7205  if (keyAxis->orientation() == Qt::Horizontal)
7206  {
7207  x = keyAxis->coordToPixel(key);
7208  y = valueAxis->coordToPixel(value);
7209  } else
7210  {
7211  y = keyAxis->coordToPixel(key);
7212  x = valueAxis->coordToPixel(value);
7213  }
7214 }
7215 
7221 const QPointF QCPAbstractPlottable::coordsToPixels(double key, double value) const
7222 {
7223  QCPAxis *keyAxis = mKeyAxis.data();
7224  QCPAxis *valueAxis = mValueAxis.data();
7225  if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); }
7226 
7227  if (keyAxis->orientation() == Qt::Horizontal)
7228  return QPointF(keyAxis->coordToPixel(key), valueAxis->coordToPixel(value));
7229  else
7230  return QPointF(valueAxis->coordToPixel(value), keyAxis->coordToPixel(key));
7231 }
7232 
7243 void QCPAbstractPlottable::pixelsToCoords(double x, double y, double &key, double &value) const
7244 {
7245  QCPAxis *keyAxis = mKeyAxis.data();
7246  QCPAxis *valueAxis = mValueAxis.data();
7247  if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
7248 
7249  if (keyAxis->orientation() == Qt::Horizontal)
7250  {
7251  key = keyAxis->pixelToCoord(x);
7252  value = valueAxis->pixelToCoord(y);
7253  } else
7254  {
7255  key = keyAxis->pixelToCoord(y);
7256  value = valueAxis->pixelToCoord(x);
7257  }
7258 }
7259 
7265 void QCPAbstractPlottable::pixelsToCoords(const QPointF &pixelPos, double &key, double &value) const
7266 {
7267  pixelsToCoords(pixelPos.x(), pixelPos.y(), key, value);
7268 }
7269 
7276 {
7277  return mSelected ? mSelectedPen : mPen;
7278 }
7279 
7286 {
7287  return mSelected ? mSelectedBrush : mBrush;
7288 }
7289 
7304 {
7306 }
7307 
7320 {
7322 }
7323 
7336 {
7338 }
7339 
7352 {
7354 }
7355 
7366 double QCPAbstractPlottable::distSqrToLine(const QPointF &start, const QPointF &end, const QPointF &point) const
7367 {
7368  QVector2D a(start);
7369  QVector2D b(end);
7370  QVector2D p(point);
7371  QVector2D v(b-a);
7372 
7373  double vLengthSqr = v.lengthSquared();
7374  if (!qFuzzyIsNull(vLengthSqr))
7375  {
7376  double mu = QVector2D::dotProduct(p-a, v)/vLengthSqr;
7377  if (mu < 0)
7378  return (a-p).lengthSquared();
7379  else if (mu > 1)
7380  return (b-p).lengthSquared();
7381  else
7382  return ((a + mu*v)-p).lengthSquared();
7383  } else
7384  return (a-p).lengthSquared();
7385 }
7386 
7387 /* inherits documentation from base class */
7388 void QCPAbstractPlottable::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
7389 {
7390  Q_UNUSED(event)
7391  Q_UNUSED(details)
7392  if (mSelectable)
7393  {
7394  bool selBefore = mSelected;
7395  setSelected(additive ? !mSelected : true);
7396  if (selectionStateChanged)
7397  *selectionStateChanged = mSelected != selBefore;
7398  }
7399 }
7400 
7401 /* inherits documentation from base class */
7402 void QCPAbstractPlottable::deselectEvent(bool *selectionStateChanged)
7403 {
7404  if (mSelectable)
7405  {
7406  bool selBefore = mSelected;
7407  setSelected(false);
7408  if (selectionStateChanged)
7409  *selectionStateChanged = mSelected != selBefore;
7410  }
7411 }
7412 
7413 
7417 
7439 /* start documentation of inline functions */
7440 
7451 /* end documentation of inline functions */
7452 
7458 QCPItemAnchor::QCPItemAnchor(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString name, int anchorId) :
7459  mName(name),
7460  mParentPlot(parentPlot),
7461  mParentItem(parentItem),
7462  mAnchorId(anchorId)
7463 {
7464 }
7465 
7467 {
7468  // unregister as parent at children:
7469  foreach (QCPItemPosition *child, mChildrenX.toList())
7470  {
7471  if (child->parentAnchorX() == this)
7472  child->setParentAnchorX(0); // this acts back on this anchor and child removes itself from mChildrenX
7473  }
7474  foreach (QCPItemPosition *child, mChildrenY.toList())
7475  {
7476  if (child->parentAnchorY() == this)
7477  child->setParentAnchorY(0); // this acts back on this anchor and child removes itself from mChildrenY
7478  }
7479 }
7480 
7488 {
7489  if (mParentItem)
7490  {
7491  if (mAnchorId > -1)
7492  {
7494  } else
7495  {
7496  qDebug() << Q_FUNC_INFO << "no valid anchor id set:" << mAnchorId;
7497  return QPointF();
7498  }
7499  } else
7500  {
7501  qDebug() << Q_FUNC_INFO << "no parent item set";
7502  return QPointF();
7503  }
7504 }
7505 
7515 {
7516  if (!mChildrenX.contains(pos))
7517  mChildrenX.insert(pos);
7518  else
7519  qDebug() << Q_FUNC_INFO << "provided pos is child already" << reinterpret_cast<quintptr>(pos);
7520 }
7521 
7529 {
7530  if (!mChildrenX.remove(pos))
7531  qDebug() << Q_FUNC_INFO << "provided pos isn't child" << reinterpret_cast<quintptr>(pos);
7532 }
7533 
7543 {
7544  if (!mChildrenY.contains(pos))
7545  mChildrenY.insert(pos);
7546  else
7547  qDebug() << Q_FUNC_INFO << "provided pos is child already" << reinterpret_cast<quintptr>(pos);
7548 }
7549 
7557 {
7558  if (!mChildrenY.remove(pos))
7559  qDebug() << Q_FUNC_INFO << "provided pos isn't child" << reinterpret_cast<quintptr>(pos);
7560 }
7561 
7562 
7566 
7601 /* start documentation of inline functions */
7602 
7624 /* end documentation of inline functions */
7625 
7631 QCPItemPosition::QCPItemPosition(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString name) :
7632  QCPItemAnchor(parentPlot, parentItem, name),
7633  mPositionTypeX(ptAbsolute),
7634  mPositionTypeY(ptAbsolute),
7635  mKey(0),
7636  mValue(0),
7637  mParentAnchorX(0),
7638  mParentAnchorY(0)
7639 {
7640 }
7641 
7643 {
7644  // unregister as parent at children:
7645  // Note: this is done in ~QCPItemAnchor again, but it's important QCPItemPosition does it itself, because only then
7646  // the setParentAnchor(0) call the correct QCPItemPosition::pixelPoint function instead of QCPItemAnchor::pixelPoint
7647  foreach (QCPItemPosition *child, mChildrenX.toList())
7648  {
7649  if (child->parentAnchorX() == this)
7650  child->setParentAnchorX(0); // this acts back on this anchor and child removes itself from mChildrenX
7651  }
7652  foreach (QCPItemPosition *child, mChildrenY.toList())
7653  {
7654  if (child->parentAnchorY() == this)
7655  child->setParentAnchorY(0); // this acts back on this anchor and child removes itself from mChildrenY
7656  }
7657  // unregister as child in parent:
7658  if (mParentAnchorX)
7660  if (mParentAnchorY)
7662 }
7663 
7664 /* can't make this a header inline function, because QPointer breaks with forward declared types, see QTBUG-29588 */
7666 {
7667  return mAxisRect.data();
7668 }
7669 
7696 {
7697  setTypeX(type);
7698  setTypeY(type);
7699 }
7700 
7709 {
7710  if (mPositionTypeX != type)
7711  {
7712  // if switching from or to coordinate type that isn't valid (e.g. because axes or axis rect
7713  // were deleted), don't try to recover the pixelPoint() because it would output a qDebug warning.
7714  bool retainPixelPosition = true;
7715  if ((mPositionTypeX == ptPlotCoords || type == ptPlotCoords) && (!mKeyAxis || !mValueAxis))
7716  retainPixelPosition = false;
7717  if ((mPositionTypeX == ptAxisRectRatio || type == ptAxisRectRatio) && (!mAxisRect))
7718  retainPixelPosition = false;
7719 
7720  QPointF pixel;
7721  if (retainPixelPosition)
7722  pixel = pixelPoint();
7723 
7724  mPositionTypeX = type;
7725 
7726  if (retainPixelPosition)
7727  setPixelPoint(pixel);
7728  }
7729 }
7730 
7739 {
7740  if (mPositionTypeY != type)
7741  {
7742  // if switching from or to coordinate type that isn't valid (e.g. because axes or axis rect
7743  // were deleted), don't try to recover the pixelPoint() because it would output a qDebug warning.
7744  bool retainPixelPosition = true;
7745  if ((mPositionTypeY == ptPlotCoords || type == ptPlotCoords) && (!mKeyAxis || !mValueAxis))
7746  retainPixelPosition = false;
7747  if ((mPositionTypeY == ptAxisRectRatio || type == ptAxisRectRatio) && (!mAxisRect))
7748  retainPixelPosition = false;
7749 
7750  QPointF pixel;
7751  if (retainPixelPosition)
7752  pixel = pixelPoint();
7753 
7754  mPositionTypeY = type;
7755 
7756  if (retainPixelPosition)
7757  setPixelPoint(pixel);
7758  }
7759 }
7760 
7780 {
7781  bool successX = setParentAnchorX(parentAnchor, keepPixelPosition);
7782  bool successY = setParentAnchorY(parentAnchor, keepPixelPosition);
7783  return successX && successY;
7784 }
7785 
7794 {
7795  // make sure self is not assigned as parent:
7796  if (parentAnchor == this)
7797  {
7798  qDebug() << Q_FUNC_INFO << "can't set self as parent anchor" << reinterpret_cast<quintptr>(parentAnchor);
7799  return false;
7800  }
7801  // make sure no recursive parent-child-relationships are created:
7802  QCPItemAnchor *currentParent = parentAnchor;
7803  while (currentParent)
7804  {
7805  if (QCPItemPosition *currentParentPos = currentParent->toQCPItemPosition())
7806  {
7807  // is a QCPItemPosition, might have further parent, so keep iterating
7808  if (currentParentPos == this)
7809  {
7810  qDebug() << Q_FUNC_INFO << "can't create recursive parent-child-relationship" << reinterpret_cast<quintptr>(parentAnchor);
7811  return false;
7812  }
7813  currentParent = currentParentPos->parentAnchorX();
7814  } else
7815  {
7816  // is a QCPItemAnchor, can't have further parent. Now make sure the parent items aren't the
7817  // same, to prevent a position being child of an anchor which itself depends on the position,
7818  // because they're both on the same item:
7819  if (currentParent->mParentItem == mParentItem)
7820  {
7821  qDebug() << Q_FUNC_INFO << "can't set parent to be an anchor which itself depends on this position" << reinterpret_cast<quintptr>(parentAnchor);
7822  return false;
7823  }
7824  break;
7825  }
7826  }
7827 
7828  // if previously no parent set and PosType is still ptPlotCoords, set to ptAbsolute:
7831 
7832  // save pixel position:
7833  QPointF pixelP;
7834  if (keepPixelPosition)
7835  pixelP = pixelPoint();
7836  // unregister at current parent anchor:
7837  if (mParentAnchorX)
7839  // register at new parent anchor:
7840  if (parentAnchor)
7841  parentAnchor->addChildX(this);
7843  // restore pixel position under new parent:
7844  if (keepPixelPosition)
7845  setPixelPoint(pixelP);
7846  else
7847  setCoords(0, coords().y());
7848  return true;
7849 }
7850 
7859 {
7860  // make sure self is not assigned as parent:
7861  if (parentAnchor == this)
7862  {
7863  qDebug() << Q_FUNC_INFO << "can't set self as parent anchor" << reinterpret_cast<quintptr>(parentAnchor);
7864  return false;
7865  }
7866  // make sure no recursive parent-child-relationships are created:
7867  QCPItemAnchor *currentParent = parentAnchor;
7868  while (currentParent)
7869  {
7870  if (QCPItemPosition *currentParentPos = currentParent->toQCPItemPosition())
7871  {
7872  // is a QCPItemPosition, might have further parent, so keep iterating
7873  if (currentParentPos == this)
7874  {
7875  qDebug() << Q_FUNC_INFO << "can't create recursive parent-child-relationship" << reinterpret_cast<quintptr>(parentAnchor);
7876  return false;
7877  }
7878  currentParent = currentParentPos->parentAnchorY();
7879  } else
7880  {
7881  // is a QCPItemAnchor, can't have further parent. Now make sure the parent items aren't the
7882  // same, to prevent a position being child of an anchor which itself depends on the position,
7883  // because they're both on the same item:
7884  if (currentParent->mParentItem == mParentItem)
7885  {
7886  qDebug() << Q_FUNC_INFO << "can't set parent to be an anchor which itself depends on this position" << reinterpret_cast<quintptr>(parentAnchor);
7887  return false;
7888  }
7889  break;
7890  }
7891  }
7892 
7893  // if previously no parent set and PosType is still ptPlotCoords, set to ptAbsolute:
7896 
7897  // save pixel position:
7898  QPointF pixelP;
7899  if (keepPixelPosition)
7900  pixelP = pixelPoint();
7901  // unregister at current parent anchor:
7902  if (mParentAnchorY)
7904  // register at new parent anchor:
7905  if (parentAnchor)
7906  parentAnchor->addChildY(this);
7908  // restore pixel position under new parent:
7909  if (keepPixelPosition)
7910  setPixelPoint(pixelP);
7911  else
7912  setCoords(coords().x(), 0);
7913  return true;
7914 }
7915 
7934 {
7935  mKey = key;
7936  mValue = value;
7937 }
7938 
7944 void QCPItemPosition::setCoords(const QPointF &pos)
7945 {
7946  setCoords(pos.x(), pos.y());
7947 }
7948 
7956 {
7957  QPointF result;
7958 
7959  // determine X:
7960  switch (mPositionTypeX)
7961  {
7962  case ptAbsolute:
7963  {
7964  result.rx() = mKey;
7965  if (mParentAnchorX)
7966  result.rx() += mParentAnchorX->pixelPoint().x();
7967  break;
7968  }
7969  case ptViewportRatio:
7970  {
7971  result.rx() = mKey*mParentPlot->viewport().width();
7972  if (mParentAnchorX)
7973  result.rx() += mParentAnchorX->pixelPoint().x();
7974  else
7975  result.rx() += mParentPlot->viewport().left();
7976  break;
7977  }
7978  case ptAxisRectRatio:
7979  {
7980  if (mAxisRect)
7981  {
7982  result.rx() = mKey*mAxisRect.data()->width();
7983  if (mParentAnchorX)
7984  result.rx() += mParentAnchorX->pixelPoint().x();
7985  else
7986  result.rx() += mAxisRect.data()->left();
7987  } else
7988  qDebug() << Q_FUNC_INFO << "Item position type x is ptAxisRectRatio, but no axis rect was defined";
7989  break;
7990  }
7991  case ptPlotCoords:
7992  {
7993  if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal)
7994  result.rx() = mKeyAxis.data()->coordToPixel(mKey);
7995  else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal)
7996  result.rx() = mValueAxis.data()->coordToPixel(mValue);
7997  else
7998  qDebug() << Q_FUNC_INFO << "Item position type x is ptPlotCoords, but no axes were defined";
7999  break;
8000  }
8001  }
8002 
8003  // determine Y:
8004  switch (mPositionTypeY)
8005  {
8006  case ptAbsolute:
8007  {
8008  result.ry() = mValue;
8009  if (mParentAnchorY)
8010  result.ry() += mParentAnchorY->pixelPoint().y();
8011  break;
8012  }
8013  case ptViewportRatio:
8014  {
8015  result.ry() = mValue*mParentPlot->viewport().height();
8016  if (mParentAnchorY)
8017  result.ry() += mParentAnchorY->pixelPoint().y();
8018  else
8019  result.ry() += mParentPlot->viewport().top();
8020  break;
8021  }
8022  case ptAxisRectRatio:
8023  {
8024  if (mAxisRect)
8025  {
8026  result.ry() = mValue*mAxisRect.data()->height();
8027  if (mParentAnchorY)
8028  result.ry() += mParentAnchorY->pixelPoint().y();
8029  else
8030  result.ry() += mAxisRect.data()->top();
8031  } else
8032  qDebug() << Q_FUNC_INFO << "Item position type y is ptAxisRectRatio, but no axis rect was defined";
8033  break;
8034  }
8035  case ptPlotCoords:
8036  {
8037  if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical)
8038  result.ry() = mKeyAxis.data()->coordToPixel(mKey);
8039  else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical)
8040  result.ry() = mValueAxis.data()->coordToPixel(mValue);
8041  else
8042  qDebug() << Q_FUNC_INFO << "Item position type y is ptPlotCoords, but no axes were defined";
8043  break;
8044  }
8045  }
8046 
8047  return result;
8048 }
8049 
8056 {
8057  mKeyAxis = keyAxis;
8059 }
8060 
8067 {
8068  mAxisRect = axisRect;
8069 }
8070 
8082 {
8083  double x = pixelPoint.x();
8084  double y = pixelPoint.y();
8085 
8086  switch (mPositionTypeX)
8087  {
8088  case ptAbsolute:
8089  {
8090  if (mParentAnchorX)
8091  x -= mParentAnchorX->pixelPoint().x();
8092  break;
8093  }
8094  case ptViewportRatio:
8095  {
8096  if (mParentAnchorX)
8097  x -= mParentAnchorX->pixelPoint().x();
8098  else
8099  x -= mParentPlot->viewport().left();
8100  x /= (double)mParentPlot->viewport().width();
8101  break;
8102  }
8103  case ptAxisRectRatio:
8104  {
8105  if (mAxisRect)
8106  {
8107  if (mParentAnchorX)
8108  x -= mParentAnchorX->pixelPoint().x();
8109  else
8110  x -= mAxisRect.data()->left();
8111  x /= (double)mAxisRect.data()->width();
8112  } else
8113  qDebug() << Q_FUNC_INFO << "Item position type x is ptAxisRectRatio, but no axis rect was defined";
8114  break;
8115  }
8116  case ptPlotCoords:
8117  {
8118  if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal)
8119  x = mKeyAxis.data()->pixelToCoord(x);
8120  else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal)
8121  y = mValueAxis.data()->pixelToCoord(x);
8122  else
8123  qDebug() << Q_FUNC_INFO << "Item position type x is ptPlotCoords, but no axes were defined";
8124  break;
8125  }
8126  }
8127 
8128  switch (mPositionTypeY)
8129  {
8130  case ptAbsolute:
8131  {
8132  if (mParentAnchorY)
8133  y -= mParentAnchorY->pixelPoint().y();
8134  break;
8135  }
8136  case ptViewportRatio:
8137  {
8138  if (mParentAnchorY)
8139  y -= mParentAnchorY->pixelPoint().y();
8140  else
8141  y -= mParentPlot->viewport().top();
8142  y /= (double)mParentPlot->viewport().height();
8143  break;
8144  }
8145  case ptAxisRectRatio:
8146  {
8147  if (mAxisRect)
8148  {
8149  if (mParentAnchorY)
8150  y -= mParentAnchorY->pixelPoint().y();
8151  else
8152  y -= mAxisRect.data()->top();
8153  y /= (double)mAxisRect.data()->height();
8154  } else
8155  qDebug() << Q_FUNC_INFO << "Item position type y is ptAxisRectRatio, but no axis rect was defined";
8156  break;
8157  }
8158  case ptPlotCoords:
8159  {
8160  if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical)
8161  x = mKeyAxis.data()->pixelToCoord(y);
8162  else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical)
8163  y = mValueAxis.data()->pixelToCoord(y);
8164  else
8165  qDebug() << Q_FUNC_INFO << "Item position type y is ptPlotCoords, but no axes were defined";
8166  break;
8167  }
8168  }
8169 
8170  setCoords(x, y);
8171 }
8172 
8173 
8177 
8317 /* start of documentation of inline functions */
8318 
8334 /* end of documentation of inline functions */
8335 /* start documentation of pure virtual functions */
8336 
8347 /* end documentation of pure virtual functions */
8348 /* start documentation of signals */
8349 
8355 /* end documentation of signals */
8356 
8361  QCPLayerable(parentPlot),
8362  mClipToAxisRect(false),
8363  mSelectable(true),
8364  mSelected(false)
8365 {
8366  QList<QCPAxisRect*> rects = parentPlot->axisRects();
8367  if (rects.size() > 0)
8368  {
8369  setClipToAxisRect(true);
8370  setClipAxisRect(rects.first());
8371  }
8372 }
8373 
8375 {
8376  // don't delete mPositions because every position is also an anchor and thus in mAnchors
8377  qDeleteAll(mAnchors);
8378 }
8379 
8380 /* can't make this a header inline function, because QPointer breaks with forward declared types, see QTBUG-29588 */
8382 {
8383  return mClipAxisRect.data();
8384 }
8385 
8393 {
8394  mClipToAxisRect = clip;
8395  if (mClipToAxisRect)
8397 }
8398 
8406 {
8407  mClipAxisRect = rect;
8408  if (mClipToAxisRect)
8410 }
8411 
8422 {
8423  if (mSelectable != selectable)
8424  {
8427  }
8428 }
8429 
8445 {
8446  if (mSelected != selected)
8447  {
8448  mSelected = selected;
8450  }
8451 }
8452 
8463 QCPItemPosition *QCPAbstractItem::position(const QString &name) const
8464 {
8465  for (int i=0; i<mPositions.size(); ++i)
8466  {
8467  if (mPositions.at(i)->name() == name)
8468  return mPositions.at(i);
8469  }
8470  qDebug() << Q_FUNC_INFO << "position with name not found:" << name;
8471  return 0;
8472 }
8473 
8484 QCPItemAnchor *QCPAbstractItem::anchor(const QString &name) const
8485 {
8486  for (int i=0; i<mAnchors.size(); ++i)
8487  {
8488  if (mAnchors.at(i)->name() == name)
8489  return mAnchors.at(i);
8490  }
8491  qDebug() << Q_FUNC_INFO << "anchor with name not found:" << name;
8492  return 0;
8493 }
8494 
8503 bool QCPAbstractItem::hasAnchor(const QString &name) const
8504 {
8505  for (int i=0; i<mAnchors.size(); ++i)
8506  {
8507  if (mAnchors.at(i)->name() == name)
8508  return true;
8509  }
8510  return false;
8511 }
8512 
8523 {
8525  return mClipAxisRect.data()->rect();
8526  else
8527  return mParentPlot->viewport();
8528 }
8529 
8544 {
8546 }
8547 
8560 double QCPAbstractItem::distSqrToLine(const QPointF &start, const QPointF &end, const QPointF &point) const
8561 {
8562  QVector2D a(start);
8563  QVector2D b(end);
8564  QVector2D p(point);
8565  QVector2D v(b-a);
8566 
8567  double vLengthSqr = v.lengthSquared();
8568  if (!qFuzzyIsNull(vLengthSqr))
8569  {
8570  double mu = QVector2D::dotProduct(p-a, v)/vLengthSqr;
8571  if (mu < 0)
8572  return (a-p).lengthSquared();
8573  else if (mu > 1)
8574  return (b-p).lengthSquared();
8575  else
8576  return ((a + mu*v)-p).lengthSquared();
8577  } else
8578  return (a-p).lengthSquared();
8579 }
8580 
8598 double QCPAbstractItem::rectSelectTest(const QRectF &rect, const QPointF &pos, bool filledRect) const
8599 {
8600  double result = -1;
8601 
8602  // distance to border:
8603  QList<QLineF> lines;
8604  lines << QLineF(rect.topLeft(), rect.topRight()) << QLineF(rect.bottomLeft(), rect.bottomRight())
8605  << QLineF(rect.topLeft(), rect.bottomLeft()) << QLineF(rect.topRight(), rect.bottomRight());
8606  double minDistSqr = std::numeric_limits<double>::max();
8607  for (int i=0; i<lines.size(); ++i)
8608  {
8609  double distSqr = distSqrToLine(lines.at(i).p1(), lines.at(i).p2(), pos);
8610  if (distSqr < minDistSqr)
8611  minDistSqr = distSqr;
8612  }
8613  result = qSqrt(minDistSqr);
8614 
8615  // filled rect, allow click inside to count as hit:
8616  if (filledRect && result > mParentPlot->selectionTolerance()*0.99)
8617  {
8618  if (rect.contains(pos))
8619  result = mParentPlot->selectionTolerance()*0.99;
8620  }
8621  return result;
8622 }
8623 
8634 QPointF QCPAbstractItem::anchorPixelPoint(int anchorId) const
8635 {
8636  qDebug() << Q_FUNC_INFO << "called on item which shouldn't have any anchors (this method not reimplemented). anchorId" << anchorId;
8637  return QPointF();
8638 }
8639 
8655 {
8656  if (hasAnchor(name))
8657  qDebug() << Q_FUNC_INFO << "anchor/position with name exists already:" << name;
8658  QCPItemPosition *newPosition = new QCPItemPosition(mParentPlot, this, name);
8659  mPositions.append(newPosition);
8660  mAnchors.append(newPosition); // every position is also an anchor
8661  newPosition->setAxes(mParentPlot->xAxis, mParentPlot->yAxis);
8662  newPosition->setType(QCPItemPosition::ptPlotCoords);
8663  if (mParentPlot->axisRect())
8664  newPosition->setAxisRect(mParentPlot->axisRect());
8665  newPosition->setCoords(0, 0);
8666  return newPosition;
8667 }
8668 
8688 QCPItemAnchor *QCPAbstractItem::createAnchor(const QString &name, int anchorId)
8689 {
8690  if (hasAnchor(name))
8691  qDebug() << Q_FUNC_INFO << "anchor/position with name exists already:" << name;
8692  QCPItemAnchor *newAnchor = new QCPItemAnchor(mParentPlot, this, name, anchorId);
8693  mAnchors.append(newAnchor);
8694  return newAnchor;
8695 }
8696 
8697 /* inherits documentation from base class */
8698 void QCPAbstractItem::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
8699 {
8700  Q_UNUSED(event)
8701  Q_UNUSED(details)
8702  if (mSelectable)
8703  {
8704  bool selBefore = mSelected;
8705  setSelected(additive ? !mSelected : true);
8706  if (selectionStateChanged)
8707  *selectionStateChanged = mSelected != selBefore;
8708  }
8709 }
8710 
8711 /* inherits documentation from base class */
8712 void QCPAbstractItem::deselectEvent(bool *selectionStateChanged)
8713 {
8714  if (mSelectable)
8715  {
8716  bool selBefore = mSelected;
8717  setSelected(false);
8718  if (selectionStateChanged)
8719  *selectionStateChanged = mSelected != selBefore;
8720  }
8721 }
8722 
8723 /* inherits documentation from base class */
8725 {
8726  return QCP::iSelectItems;
8727 }
8728 
8729 
8734 
8747 /* start of documentation of inline functions */
8748 
8770 /* end of documentation of inline functions */
8771 /* start of documentation of signals */
8772 
8960 /* end of documentation of signals */
8961 /* start of documentation of public members */
8962 
9034 /* end of documentation of public members */
9035 
9039 QCustomPlot::QCustomPlot(QWidget *parent) :
9040  QWidget(parent),
9041  xAxis(0),
9042  yAxis(0),
9043  xAxis2(0),
9044  yAxis2(0),
9045  legend(0),
9046  mPlotLayout(0),
9047  mAutoAddPlottableToLegend(true),
9048  mAntialiasedElements(QCP::aeNone),
9049  mNotAntialiasedElements(QCP::aeNone),
9050  mInteractions(0),
9051  mSelectionTolerance(8),
9052  mNoAntialiasingOnDrag(false),
9053  mBackgroundBrush(Qt::white, Qt::SolidPattern),
9054  mBackgroundScaled(true),
9055  mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
9056  mCurrentLayer(0),
9057  mPlottingHints(QCP::phCacheLabels|QCP::phForceRepaint),
9058  mMultiSelectModifier(Qt::ControlModifier),
9059  mPaintBuffer(size()),
9060  mMouseEventElement(0),
9061  mReplotting(false)
9062 {
9063  setAttribute(Qt::WA_NoMousePropagation);
9064  setAttribute(Qt::WA_OpaquePaintEvent);
9065  setMouseTracking(true);
9066  QLocale currentLocale = locale();
9067  currentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
9068  setLocale(currentLocale);
9069 
9070  // create initial layers:
9071  mLayers.append(new QCPLayer(this, QLatin1String("background")));
9072  mLayers.append(new QCPLayer(this, QLatin1String("grid")));
9073  mLayers.append(new QCPLayer(this, QLatin1String("main")));
9074  mLayers.append(new QCPLayer(this, QLatin1String("axes")));
9075  mLayers.append(new QCPLayer(this, QLatin1String("legend")));
9077  setCurrentLayer(QLatin1String("main"));
9078 
9079  // create initial layout, axis rect and legend:
9080  mPlotLayout = new QCPLayoutGrid;
9082  mPlotLayout->setParent(this); // important because if parent is QWidget, QCPLayout::sizeConstraintsChanged will call QWidget::updateGeometry