rock_widget_collection  0.1
qcustomplot.cc
Go to the documentation of this file.
1 /***************************************************************************
2 ** **
3 ** QCustomPlot, a simple to use, modern plotting widget for Qt **
4 ** Copyright (C) 2012 Emanuel Eichhammer **
5 ** **
6 ** This program is free software: you can redistribute it and/or modify **
7 ** it under the terms of the GNU General Public License as published by **
8 ** the Free Software Foundation, either version 3 of the License, or **
9 ** (at your option) any later version. **
10 ** **
11 ** This program is distributed in the hope that it will be useful, **
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of **
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
14 ** GNU General Public License for more details. **
15 ** **
16 ** You should have received a copy of the GNU General Public License **
17 ** along with this program. If not, see http://www.gnu.org/licenses/. **
18 ** **
19 ****************************************************************************
20 ** Author: Emanuel Eichhammer **
21 ** Website/Contact: http://www.WorksLikeClockwork.com/ **
22 ** Date: 02.02.12 **
23 ****************************************************************************/
24 
135 #include "qcustomplot.h"
136 
137 // ================================================================================
138 // =================== QCPData
139 // ================================================================================
140 
159  key(0),
160  value(0),
161  keyErrorPlus(0),
162  keyErrorMinus(0),
163  valueErrorPlus(0),
164  valueErrorMinus(0)
165 {
166 }
167 
168 // ================================================================================
169 // =================== QCPCurveData
170 // ================================================================================
171 
187  t(0),
188  key(0),
189  value(0)
190 {
191 }
192 
193 
194 // ================================================================================
195 // =================== QCPBarData
196 // ================================================================================
197 
212  key(0),
213  value(0)
214 {
215 }
216 
217 // ================================================================================
218 // =================== QCPGraph
219 // ================================================================================
220 
256 QCPGraph::QCPGraph(QCPAxis *keyAxis, QCPAxis *valueAxis) :
257  QCPAbstractPlottable(keyAxis, valueAxis)
258 {
259  mData = new QCPDataMap;
260  mPen.setColor(Qt::blue);
261  mPen.setStyle(Qt::SolidLine);
262  mErrorPen.setColor(Qt::black);
263  mBrush.setColor(Qt::blue);
264  mBrush.setStyle(Qt::NoBrush);
265  mLineStyle = LSLine;
267  mScatterSize = 6;
268  mErrorType = ETNone;
269  mErrorBarSize = 6;
270  mErrorBarSkipSymbol = true;
271  mChannelFillGraph = 0;
272 }
273 
275 {
276  if (mParentPlot)
277  {
278  // if another graph has a channel fill towards this graph, set it to zero
279  for (int i=0; i<mParentPlot->graphCount(); ++i)
280  {
281  if (mParentPlot->graph(i)->channelFillGraph() == this)
283  }
284  }
285  delete mData;
286 }
287 
296 {
297  if (copy)
298  {
299  *mData = *data;
300  } else
301  {
302  delete mData;
303  mData = data;
304  }
305 }
306 
313 void QCPGraph::setData(const QVector<double> &key, const QVector<double> &value)
314 {
315  mData->clear();
316  int n = key.size();
317  n = qMin(n, value.size());
318  QCPData newData;
319  for (int i=0; i<n; ++i)
320  {
321  newData.key = key[i];
322  newData.value = value[i];
323  mData->insertMulti(newData.key, newData);
324  }
325 }
326 
334 void QCPGraph::setDataValueError(const QVector<double> &key, const QVector<double> &value, const QVector<double> &valueError)
335 {
336  mData->clear();
337  int n = key.size();
338  n = qMin(n, value.size());
339  n = qMin(n, valueError.size());
340  QCPData newData;
341  for (int i=0; i<n; ++i)
342  {
343  newData.key = key[i];
344  newData.value = value[i];
345  newData.valueErrorMinus = valueError[i];
346  newData.valueErrorPlus = valueError[i];
347  mData->insertMulti(key[i], newData);
348  }
349 }
350 
360 void QCPGraph::setDataValueError(const QVector<double> &key, const QVector<double> &value, const QVector<double> &valueErrorMinus, const QVector<double> &valueErrorPlus)
361 {
362  mData->clear();
363  int n = key.size();
364  n = qMin(n, value.size());
365  n = qMin(n, valueErrorMinus.size());
366  n = qMin(n, valueErrorPlus.size());
367  QCPData newData;
368  for (int i=0; i<n; ++i)
369  {
370  newData.key = key[i];
371  newData.value = value[i];
372  newData.valueErrorMinus = valueErrorMinus[i];
373  newData.valueErrorPlus = valueErrorPlus[i];
374  mData->insertMulti(key[i], newData);
375  }
376 }
377 
385 void QCPGraph::setDataKeyError(const QVector<double> &key, const QVector<double> &value, const QVector<double> &keyError)
386 {
387  mData->clear();
388  int n = key.size();
389  n = qMin(n, value.size());
390  n = qMin(n, keyError.size());
391  QCPData newData;
392  for (int i=0; i<n; ++i)
393  {
394  newData.key = key[i];
395  newData.value = value[i];
396  newData.keyErrorMinus = keyError[i];
397  newData.keyErrorPlus = keyError[i];
398  mData->insertMulti(key[i], newData);
399  }
400 }
401 
411 void QCPGraph::setDataKeyError(const QVector<double> &key, const QVector<double> &value, const QVector<double> &keyErrorMinus, const QVector<double> &keyErrorPlus)
412 {
413  mData->clear();
414  int n = key.size();
415  n = qMin(n, value.size());
416  n = qMin(n, keyErrorMinus.size());
417  n = qMin(n, keyErrorPlus.size());
418  QCPData newData;
419  for (int i=0; i<n; ++i)
420  {
421  newData.key = key[i];
422  newData.value = value[i];
423  newData.keyErrorMinus = keyErrorMinus[i];
424  newData.keyErrorPlus = keyErrorPlus[i];
425  mData->insertMulti(key[i], newData);
426  }
427 }
428 
436 void QCPGraph::setDataBothError(const QVector<double> &key, const QVector<double> &value, const QVector<double> &keyError, const QVector<double> &valueError)
437 {
438  mData->clear();
439  int n = key.size();
440  n = qMin(n, value.size());
441  n = qMin(n, valueError.size());
442  n = qMin(n, keyError.size());
443  QCPData newData;
444  for (int i=0; i<n; ++i)
445  {
446  newData.key = key[i];
447  newData.value = value[i];
448  newData.keyErrorMinus = keyError[i];
449  newData.keyErrorPlus = keyError[i];
450  newData.valueErrorMinus = valueError[i];
451  newData.valueErrorPlus = valueError[i];
452  mData->insertMulti(key[i], newData);
453  }
454 }
455 
465 void QCPGraph::setDataBothError(const QVector<double> &key, const QVector<double> &value, const QVector<double> &keyErrorMinus, const QVector<double> &keyErrorPlus, const QVector<double> &valueErrorMinus, const QVector<double> &valueErrorPlus)
466 {
467  mData->clear();
468  int n = key.size();
469  n = qMin(n, value.size());
470  n = qMin(n, valueErrorMinus.size());
471  n = qMin(n, valueErrorPlus.size());
472  n = qMin(n, keyErrorMinus.size());
473  n = qMin(n, keyErrorPlus.size());
474  QCPData newData;
475  for (int i=0; i<n; ++i)
476  {
477  newData.key = key[i];
478  newData.value = value[i];
479  newData.keyErrorMinus = keyErrorMinus[i];
480  newData.keyErrorPlus = keyErrorPlus[i];
481  newData.valueErrorMinus = valueErrorMinus[i];
482  newData.valueErrorPlus = valueErrorPlus[i];
483  mData->insertMulti(key[i], newData);
484  }
485 }
486 
487 
495 {
496  mLineStyle = (LineStyle)ls;
497 }
498 
505 {
507 }
508 
515 void QCPGraph::setScatterSize(double size)
516 {
517  mScatterSize = size;
518 }
519 
525 void QCPGraph::setScatterPixmap(const QPixmap &pixmap)
526 {
527  mScatterPixmap = pixmap;
528 }
529 
534 {
536 }
537 
542 void QCPGraph::setErrorPen(const QPen &pen)
543 {
544  mErrorPen = pen;
545 }
546 
550 void QCPGraph::setErrorBarSize(double size)
551 {
552  mErrorBarSize = size;
553 }
554 
565 {
566  mErrorBarSkipSymbol = enabled;
567 }
568 
579 {
580  // prevent setting channel target to this graph itself:
581  if (targetGraph == this)
582  {
583  qDebug() << FUNCNAME << "targetGraph is self";
584  mChannelFillGraph = 0;
585  return;
586  }
587  // prevent setting channel target to a graph not in the plot:
588  if (targetGraph && targetGraph->mParentPlot != mParentPlot)
589  {
590  qDebug() << FUNCNAME << "targetGraph not in same plot";
591  mChannelFillGraph = 0;
592  return;
593  }
594 
595  mChannelFillGraph = targetGraph;
596 }
597 
602 void QCPGraph::addData(const QCPDataMap &dataMap)
603 {
604  mData->unite(dataMap);
605 }
606 
612 {
613  mData->insertMulti(data.key, data);
614 }
615 
620 void QCPGraph::addData(double key, double value)
621 {
622  QCPData newData;
623  newData.key = key;
624  newData.value = value;
625  mData->insertMulti(newData.key, newData);
626 }
627 
632 void QCPGraph::addData(const QVector<double> &keys, const QVector<double> &values)
633 {
634  int n = qMin(keys.size(), values.size());
635  QCPData newData;
636  for (int i=0; i<n; ++i)
637  {
638  newData.key = keys[i];
639  newData.value = values[i];
640  mData->insertMulti(newData.key, newData);
641  }
642 }
643 
649 {
650  QCPDataMap::iterator it = mData->begin();
651  while (it != mData->end() && it.key() < key)
652  it = mData->erase(it);
653 }
654 
660 {
661  if (mData->isEmpty()) return;
662  QCPDataMap::iterator it = mData->upperBound(key);
663  while (it != mData->end())
664  it = mData->erase(it);
665 }
666 
674 void QCPGraph::removeData(double fromKey, double toKey)
675 {
676  if (fromKey >= toKey || mData->isEmpty()) return;
677  QCPDataMap::iterator it = mData->upperBound(fromKey);
678  QCPDataMap::iterator itEnd = mData->upperBound(toKey);
679  while (it != itEnd)
680  it = mData->erase(it);
681 }
682 
691 void QCPGraph::removeData(double key)
692 {
693  mData->remove(key);
694 }
695 
701 {
702  mData->clear();
703 }
704 
710 void QCPGraph::rescaleAxes(bool onlyEnlarge, bool includeErrorBars) const
711 {
712  rescaleKeyAxis(onlyEnlarge, includeErrorBars);
713  rescaleValueAxis(onlyEnlarge, includeErrorBars);
714 }
715 
721 void QCPGraph::rescaleKeyAxis(bool onlyEnlarge, bool includeErrorBars) const
722 {
723  // this code is a copy of QCPAbstractPlottable::rescaleKeyAxis with the only change
724  // is that getKeyRange is passed the includeErrorBars value.
725  if (mData->isEmpty()) return;
726 
727  SignDomain signDomain = SDBoth;
729  signDomain = (mKeyAxis->range().upper < 0 ? SDNegative : SDPositive);
730 
731  bool validRange;
732  QCPRange newRange = getKeyRange(validRange, signDomain, includeErrorBars);
733 
734  if (validRange)
735  {
736  if (onlyEnlarge)
737  {
738  if (mKeyAxis->range().lower < newRange.lower)
739  newRange.lower = mKeyAxis->range().lower;
740  if (mKeyAxis->range().upper > newRange.upper)
741  newRange.upper = mKeyAxis->range().upper;
742  }
743  mKeyAxis->setRange(newRange);
744  }
745 }
746 
752 void QCPGraph::rescaleValueAxis(bool onlyEnlarge, bool includeErrorBars) const
753 {
754  // this code is a copy of QCPAbstractPlottable::rescaleValueAxis with the only change
755  // is that getValueRange is passed the includeErrorBars value.
756  if (mData->isEmpty()) return;
757 
758  SignDomain signDomain = SDBoth;
760  signDomain = (mValueAxis->range().upper < 0 ? SDNegative : SDPositive);
761 
762  bool validRange;
763  QCPRange newRange = getValueRange(validRange, signDomain, includeErrorBars);
764 
765  if (validRange)
766  {
767  if (onlyEnlarge)
768  {
769  if (mValueAxis->range().lower < newRange.lower)
770  newRange.lower = mValueAxis->range().lower;
771  if (mValueAxis->range().upper > newRange.upper)
772  newRange.upper = mValueAxis->range().upper;
773  }
774  mValueAxis->setRange(newRange);
775  }
776 }
777 
778 /* inherits documentation from base class */
779 void QCPGraph::draw(QPainter *painter) const
780 {
781  if (!mVisible) return;
782  if (mKeyAxis->range().size() <= 0) return;
783  if (mData->isEmpty()) return;
784  if (mLineStyle == LSNone && mScatterStyle == SSNone) return;
785  painter->setClipRect(mKeyAxis->axisRect().united(mValueAxis->axisRect()));
786 
787  // allocate line and (if necessary) point vectors:
788  QVector<QPointF> *lineData = new QVector<QPointF>;
789  QVector<QCPData> *pointData = 0;
790  if (mScatterStyle != SSNone)
791  pointData = new QVector<QCPData>;
792 
793  // fill vectors with data appropriate to plot style:
794  getPlotData(lineData, pointData);
795 
796  // draw fill of graph:
797  drawFill(painter, lineData);
798 
799  // draw line:
800  if (mLineStyle == LSImpulse)
801  drawImpulsePlot(painter, lineData);
802  else if (mLineStyle != LSNone)
803  drawLinePlot(painter, lineData); // also step plots can be drawn as a line plot
804 
805  // draw scatters:
806  if (pointData)
807  drawScatterPlot(painter, pointData);
808 
809  // free allocated line and point vectors:
810  delete lineData;
811  if (pointData)
812  delete pointData;
813 }
814 
815 /* inherits documentation from base class */
816 void QCPGraph::drawLegendIcon(QPainter *painter, const QRect &rect) const
817 {
818  // draw fill:
819  if (mBrush.style() != Qt::NoBrush)
820  {
821  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGraphs));
822  painter->fillRect(rect.left(), rect.top()+rect.height()/2.0, rect.width(), rect.height()/3.0, mBrush);
823  }
824  // draw line vertically centered:
825  if (mLineStyle != LSNone)
826  {
827  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGraphs));
828  painter->setPen(mPen);
829  painter->drawLine(rect.left(), rect.top()+rect.height()/2.0, rect.right()+5, rect.top()+rect.height()/2.0); // +5 on x2 else last segment is missing from dashed/dotted pens
830  }
831  // draw scatter symbol:
832  if (mScatterStyle != SSNone)
833  {
834  if (mScatterStyle == SSPixmap && (mScatterPixmap.size().width() > rect.width() || mScatterPixmap.size().height() > rect.height()))
835  {
836  // handle pixmap scatters that are larger than legend icon rect separately.
837  // We resize them and draw them manually, instead of calling drawScatter:
838  QSize newSize = mScatterPixmap.size();
839  newSize.scale(rect.size(), Qt::KeepAspectRatio);
840  QRect targetRect;
841  targetRect.setSize(newSize);
842  targetRect.moveCenter(rect.center());
843  bool smoothBackup = painter->testRenderHint(QPainter::SmoothPixmapTransform);
844  painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
845  painter->drawPixmap(targetRect, mScatterPixmap);
846  painter->setRenderHint(QPainter::SmoothPixmapTransform, smoothBackup);
847  } else // mScatterStyle != SSPixmap
848  {
849  painter->setPen(mPen);
850  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEScatters));
851  drawScatter(painter, rect.center().x()+1, rect.center().y()+1, mScatterStyle);
852  }
853  }
854 }
855 
868 void QCPGraph::getPlotData(QVector<QPointF> *lineData, QVector<QCPData> *pointData) const
869 {
870  switch(mLineStyle)
871  {
872  case LSNone: getScatterPlotData(pointData); break;
873  case LSLine: getLinePlotData(lineData, pointData); break;
874  case LSStepLeft: getStepLeftPlotData(lineData, pointData); break;
875  case LSStepRight: getStepRightPlotData(lineData, pointData); break;
876  case LSStepCenter: getStepCenterPlotData(lineData, pointData); break;
877  case LSImpulse: getImpulsePlotData(lineData, pointData); break;
878  }
879 }
880 
891 void QCPGraph::getScatterPlotData(QVector<QCPData> *pointData) const
892 {
893  if (!pointData) return;
894 
895  // get visible data range:
896  QCPDataMap::const_iterator lower, upper;
897  int dataCount;
898  getVisibleDataBounds(lower, upper, dataCount);
899  // prepare vectors:
900  if (pointData)
901  pointData->resize(dataCount);
902 
903  // position data points:
904  QCPDataMap::const_iterator it = lower;
905  QCPDataMap::const_iterator upperEnd = upper+1;
906  int i = 0;
907  if (mKeyAxis->orientation() == Qt::Vertical)
908  {
909  while (it != upperEnd)
910  {
911  (*pointData)[i] = it.value();
912  ++i;
913  ++it;
914  }
915  } else // key axis is horizontal
916  {
917  while (it != upperEnd)
918  {
919  (*pointData)[i] = it.value();
920  ++i;
921  ++it;
922  }
923  }
924 }
925 
936 void QCPGraph::getLinePlotData(QVector<QPointF> *lineData, QVector<QCPData> *pointData) const
937 {
938  // get visible data range:
939  QCPDataMap::const_iterator lower, upper;
940  int dataCount;
941  getVisibleDataBounds(lower, upper, dataCount);
942  // prepare vectors:
943  if (lineData)
944  {
945  // added 2 to reserve memory for lower/upper fill base points that might be needed for fill
946  lineData->reserve(dataCount+2);
947  lineData->resize(dataCount);
948  }
949  if (pointData)
950  pointData->resize(dataCount);
951 
952  // position data points:
953  QCPDataMap::const_iterator it = lower;
954  QCPDataMap::const_iterator upperEnd = upper+1;
955  int i = 0;
956  if (mKeyAxis->orientation() == Qt::Vertical)
957  {
958  while (it != upperEnd)
959  {
960  if (pointData)
961  (*pointData)[i] = it.value();
962  (*lineData)[i].setX(mValueAxis->coordToPixel(it.value().value));
963  (*lineData)[i].setY(mKeyAxis->coordToPixel(it.key()));
964  ++i;
965  ++it;
966  }
967  } else // key axis is horizontal
968  {
969  while (it != upperEnd)
970  {
971  if (pointData)
972  (*pointData)[i] = it.value();
973  (*lineData)[i].setX(mKeyAxis->coordToPixel(it.key()));
974  (*lineData)[i].setY(mValueAxis->coordToPixel(it.value().value));
975  ++i;
976  ++it;
977  }
978  }
979 }
980 
991 void QCPGraph::getStepLeftPlotData(QVector<QPointF> *lineData, QVector<QCPData> *pointData) const
992 {
993  // get visible data range:
994  QCPDataMap::const_iterator lower, upper;
995  int dataCount;
996  getVisibleDataBounds(lower, upper, dataCount);
997  // prepare vectors:
998  if (lineData)
999  {
1000  // added 2 to reserve memory for lower/upper fill base points that might be needed for fill
1001  // multiplied by 2 because step plot needs two polyline points per one actual data point
1002  lineData->reserve(dataCount*2+2);
1003  lineData->resize(dataCount*2);
1004  }
1005  if (pointData)
1006  pointData->resize(dataCount);
1007 
1008  // position data points:
1009  QCPDataMap::const_iterator it = lower;
1010  QCPDataMap::const_iterator upperEnd = upper+1;
1011  int i = 0;
1012  int ipoint = 0;
1013  if (mKeyAxis->orientation() == Qt::Vertical)
1014  {
1015  double lastValue = mValueAxis->coordToPixel(it.value().value);
1016  double key;
1017  while (it != upperEnd)
1018  {
1019  if (pointData)
1020  {
1021  (*pointData)[ipoint] = it.value();
1022  ++ipoint;
1023  }
1024  key = mKeyAxis->coordToPixel(it.key());
1025  (*lineData)[i].setX(lastValue);
1026  (*lineData)[i].setY(key);
1027  ++i;
1028  lastValue = mValueAxis->coordToPixel(it.value().value);
1029  (*lineData)[i].setX(lastValue);
1030  (*lineData)[i].setY(key);
1031  ++i;
1032  ++it;
1033  }
1034  } else // key axis is horizontal
1035  {
1036  double lastValue = mValueAxis->coordToPixel(it.value().value);
1037  double key;
1038  while (it != upperEnd)
1039  {
1040  if (pointData)
1041  {
1042  (*pointData)[ipoint] = it.value();
1043  ++ipoint;
1044  }
1045  key = mKeyAxis->coordToPixel(it.key());
1046  (*lineData)[i].setX(key);
1047  (*lineData)[i].setY(lastValue);
1048  ++i;
1049  lastValue = mValueAxis->coordToPixel(it.value().value);
1050  (*lineData)[i].setX(key);
1051  (*lineData)[i].setY(lastValue);
1052  ++i;
1053  ++it;
1054  }
1055  }
1056 }
1057 
1068 void QCPGraph::getStepRightPlotData(QVector<QPointF> *lineData, QVector<QCPData> *pointData) const
1069 {
1070  // get visible data range:
1071  QCPDataMap::const_iterator lower, upper;
1072  int dataCount;
1073  getVisibleDataBounds(lower, upper, dataCount);
1074  // prepare vectors:
1075  if (lineData)
1076  {
1077  // added 2 to reserve memory for lower/upper fill base points that might be needed for fill
1078  // multiplied by 2 because step plot needs two polyline points per one actual data point
1079  lineData->reserve(dataCount*2+2);
1080  lineData->resize(dataCount*2);
1081  }
1082  if (pointData)
1083  pointData->resize(dataCount);
1084 
1085  // position points:
1086  QCPDataMap::const_iterator it = lower;
1087  QCPDataMap::const_iterator upperEnd = upper+1;
1088  int i = 0;
1089  int ipoint = 0;
1090  if (mKeyAxis->orientation() == Qt::Vertical)
1091  {
1092  double lastKey = mKeyAxis->coordToPixel(it.key());
1093  double value;
1094  while (it != upperEnd)
1095  {
1096  if (pointData)
1097  {
1098  (*pointData)[ipoint] = it.value();
1099  ++ipoint;
1100  }
1101  value = mValueAxis->coordToPixel(it.value().value);
1102  (*lineData)[i].setX(value);
1103  (*lineData)[i].setY(lastKey);
1104  ++i;
1105  lastKey = mKeyAxis->coordToPixel(it.key());
1106  (*lineData)[i].setX(value);
1107  (*lineData)[i].setY(lastKey);
1108  ++i;
1109  ++it;
1110  }
1111  } else // key axis is horizontal
1112  {
1113  double lastKey = mKeyAxis->coordToPixel(it.key());
1114  double value;
1115  while (it != upperEnd)
1116  {
1117  if (pointData)
1118  {
1119  (*pointData)[ipoint] = it.value();
1120  ++ipoint;
1121  }
1122  value = mValueAxis->coordToPixel(it.value().value);
1123  (*lineData)[i].setX(lastKey);
1124  (*lineData)[i].setY(value);
1125  ++i;
1126  lastKey = mKeyAxis->coordToPixel(it.key());
1127  (*lineData)[i].setX(lastKey);
1128  (*lineData)[i].setY(value);
1129  ++i;
1130  ++it;
1131  }
1132  }
1133 }
1134 
1145 void QCPGraph::getStepCenterPlotData(QVector<QPointF> *lineData, QVector<QCPData> *pointData) const
1146 {
1147  // get visible data range:
1148  QCPDataMap::const_iterator lower, upper;
1149  int dataCount;
1150  getVisibleDataBounds(lower, upper, dataCount);
1151  // prepare vectors:
1152  if (lineData)
1153  {
1154  // added 2 to reserve memory for lower/upper fill base points that might be needed for base fill
1155  // multiplied by 2 because step plot needs two polyline points per one actual data point
1156  lineData->reserve(dataCount*2+2);
1157  lineData->resize(dataCount*2);
1158  }
1159  if (pointData)
1160  pointData->resize(dataCount);
1161 
1162  // position points:
1163  QCPDataMap::const_iterator it = lower;
1164  QCPDataMap::const_iterator upperEnd = upper+1;
1165  int i = 0;
1166  int ipoint = 0;
1167  if (mKeyAxis->orientation() == Qt::Vertical)
1168  {
1169  double lastKey = mKeyAxis->coordToPixel(it.key());
1170  double lastValue = mValueAxis->coordToPixel(it.value().value);
1171  double key;
1172  if (pointData)
1173  {
1174  (*pointData)[ipoint] = it.value();
1175  ++ipoint;
1176  }
1177  (*lineData)[i].setX(lastValue);
1178  (*lineData)[i].setY(lastKey);
1179  ++it;
1180  ++i;
1181  while (it != upperEnd)
1182  {
1183  if (pointData)
1184  {
1185  (*pointData)[ipoint] = it.value();
1186  ++ipoint;
1187  }
1188  key = (mKeyAxis->coordToPixel(it.key())-lastKey)*0.5 + lastKey;
1189  (*lineData)[i].setX(lastValue);
1190  (*lineData)[i].setY(key);
1191  ++i;
1192  lastValue = mValueAxis->coordToPixel(it.value().value);
1193  lastKey = mKeyAxis->coordToPixel(it.key());
1194  (*lineData)[i].setX(lastValue);
1195  (*lineData)[i].setY(key);
1196  ++it;
1197  ++i;
1198  }
1199  (*lineData)[i].setX(lastValue);
1200  (*lineData)[i].setY(lastKey);
1201  } else // key axis is horizontal
1202  {
1203  double lastKey = mKeyAxis->coordToPixel(it.key());
1204  double lastValue = mValueAxis->coordToPixel(it.value().value);
1205  double key;
1206  if (pointData)
1207  {
1208  (*pointData)[ipoint] = it.value();
1209  ++ipoint;
1210  }
1211  (*lineData)[i].setX(lastKey);
1212  (*lineData)[i].setY(lastValue);
1213  ++it;
1214  ++i;
1215  while (it != upperEnd)
1216  {
1217  if (pointData)
1218  {
1219  (*pointData)[ipoint] = it.value();
1220  ++ipoint;
1221  }
1222  key = (mKeyAxis->coordToPixel(it.key())-lastKey)*0.5 + lastKey;
1223  (*lineData)[i].setX(key);
1224  (*lineData)[i].setY(lastValue);
1225  ++i;
1226  lastValue = mValueAxis->coordToPixel(it.value().value);
1227  lastKey = mKeyAxis->coordToPixel(it.key());
1228  (*lineData)[i].setX(key);
1229  (*lineData)[i].setY(lastValue);
1230  ++it;
1231  ++i;
1232  }
1233  (*lineData)[i].setX(lastKey);
1234  (*lineData)[i].setY(lastValue);
1235  }
1236 }
1237 
1248 void QCPGraph::getImpulsePlotData(QVector<QPointF> *lineData, QVector<QCPData> *pointData) const
1249 {
1250  // get visible data range:
1251  QCPDataMap::const_iterator lower, upper;
1252  int dataCount;
1253  getVisibleDataBounds(lower, upper, dataCount);
1254  // prepare vectors:
1255  if (lineData)
1256  {
1257  // no need to reserve 2 extra points, because there is no fill for impulse plot
1258  lineData->resize(dataCount*2);
1259  }
1260  if (pointData)
1261  pointData->resize(dataCount);
1262 
1263  // position data points:
1264  QCPDataMap::const_iterator it = lower;
1265  QCPDataMap::const_iterator upperEnd = upper+1;
1266  int i = 0;
1267  int ipoint = 0;
1268  if (mKeyAxis->orientation() == Qt::Vertical)
1269  {
1270  double zeroPointX = mValueAxis->coordToPixel(0);
1271  double key;
1272  while (it != upperEnd)
1273  {
1274  if (pointData)
1275  {
1276  (*pointData)[ipoint] = it.value();
1277  ++ipoint;
1278  }
1279  key = mKeyAxis->coordToPixel(it.key());
1280  (*lineData)[i].setX(zeroPointX);
1281  (*lineData)[i].setY(key);
1282  ++i;
1283  (*lineData)[i].setX(mValueAxis->coordToPixel(it.value().value));
1284  (*lineData)[i].setY(key);
1285  ++i;
1286  ++it;
1287  }
1288  } else // key axis is horizontal
1289  {
1290  double zeroPointY = mValueAxis->coordToPixel(0);
1291  double key;
1292  while (it != upperEnd)
1293  {
1294  if (pointData)
1295  {
1296  (*pointData)[ipoint] = it.value();
1297  ++ipoint;
1298  }
1299  key = mKeyAxis->coordToPixel(it.key());
1300  (*lineData)[i].setX(key);
1301  (*lineData)[i].setY(zeroPointY);
1302  ++i;
1303  (*lineData)[i].setX(key);
1304  (*lineData)[i].setY(mValueAxis->coordToPixel(it.value().value));
1305  ++i;
1306  ++it;
1307  }
1308  }
1309 }
1310 
1322 void QCPGraph::drawFill(QPainter *painter, QVector<QPointF> *lineData) const
1323 {
1324  if (mLineStyle == LSImpulse) return; // fill doesn't make sense for impulse plot
1325  if (mBrush.style() == Qt::NoBrush || mBrush.color().alpha() == 0) return;
1326 
1327  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEFills));
1328  if (!mChannelFillGraph)
1329  {
1330  // draw base fill under graph, fill goes all the way to the zero-value-line:
1331  addFillBasePoints(lineData);
1332  painter->setPen(Qt::NoPen);
1333  painter->setBrush(mBrush);
1334  painter->drawPolygon(QPolygonF(*lineData));
1335  removeFillBasePoints(lineData);
1336  } else
1337  {
1338  // draw channel fill between this graph and mChannelFillGraph:
1339  painter->setPen(Qt::NoPen);
1340  painter->setBrush(mBrush);
1341  painter->drawPolygon(getChannelFillPolygon(lineData));
1342  }
1343 }
1344 
1352 void QCPGraph::drawScatterPlot(QPainter *painter, QVector<QCPData> *pointData) const
1353 {
1354  // draw error bars:
1355  if (mErrorType != ETNone)
1356  {
1357  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEErrorBars));
1358  painter->setPen(mErrorPen);
1359  if (mKeyAxis->orientation() == Qt::Vertical)
1360  {
1361  for (int i=0; i<pointData->size(); ++i)
1362  drawError(painter, mValueAxis->coordToPixel(pointData->at(i).value), mKeyAxis->coordToPixel(pointData->at(i).key), pointData->at(i));
1363  } else
1364  {
1365  for (int i=0; i<pointData->size(); ++i)
1366  drawError(painter, mKeyAxis->coordToPixel(pointData->at(i).key), mValueAxis->coordToPixel(pointData->at(i).value), pointData->at(i));
1367  }
1368  }
1369 
1370  // draw scatter point symbols:
1371  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEScatters));
1372  painter->setPen(mPen);
1373  painter->setBrush(mBrush);
1374  if (mKeyAxis->orientation() == Qt::Vertical)
1375  {
1376  for (int i=0; i<pointData->size(); ++i)
1377  drawScatter(painter, mValueAxis->coordToPixel(pointData->at(i).value), mKeyAxis->coordToPixel(pointData->at(i).key), mScatterStyle);
1378  } else
1379  {
1380  for (int i=0; i<pointData->size(); ++i)
1381  drawScatter(painter, mKeyAxis->coordToPixel(pointData->at(i).key), mValueAxis->coordToPixel(pointData->at(i).value), mScatterStyle);
1382  }
1383 }
1384 
1393 void QCPGraph::drawLinePlot(QPainter *painter, QVector<QPointF> *lineData) const
1394 {
1395  // draw line of graph:
1396  if (mPen.style() != Qt::NoPen && mPen.color().alpha() != 0)
1397  {
1398  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGraphs));
1399  painter->setPen(mPen);
1400  painter->setBrush(Qt::NoBrush);
1401  painter->drawPolyline(QPolygonF(*lineData));
1402  }
1403 }
1404 
1411 void QCPGraph::drawImpulsePlot(QPainter *painter, QVector<QPointF> *lineData) const
1412 {
1413  // draw impulses:
1414  if (mPen.style() != Qt::NoPen && mPen.color().alpha() != 0)
1415  {
1416  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGraphs));
1417  painter->setPen(mPen);
1418  painter->setBrush(Qt::NoBrush);
1419  painter->drawLines(*lineData);
1420  }
1421 }
1422 
1431 void QCPGraph::drawScatter(QPainter *painter, double x, double y, ScatterStyle style) const
1432 {
1433  // If you change this correction, make sure pdf exported scatters are properly centered in error bars!
1434  // There seems to be some kind of discrepancy for different paint devices here.
1435  if (style == SSCross || style == SSPlus)
1436  {
1437  x = x-0.7; // paint system correction, else, we don't get pixel exact matches (Qt problem)
1438  y = y-0.4; // paint system correction, else, we don't get pixel exact matches (Qt problem)
1439  }
1440 
1441  double w = mScatterSize/2.0;
1442  switch (style)
1443  {
1444  case SSDot:
1445  {
1446  painter->drawPoint(QPointF(x, y));
1447  break;
1448  }
1449  case SSCross:
1450  {
1451  painter->drawLine(QLineF(x-w, y-w, x+w, y+w));
1452  painter->drawLine(QLineF(x-w, y+w, x+w, y-w));
1453  break;
1454  }
1455  case SSPlus:
1456  {
1457  painter->drawLine(QLineF(x-w, y, x+w, y));
1458  painter->drawLine(QLineF(x, y+w, x, y-w));
1459  break;
1460  }
1461  case SSCircle:
1462  {
1463  painter->setBrush(Qt::NoBrush);
1464  painter->drawEllipse(x-w,y-w,mScatterSize,mScatterSize);
1465  break;
1466  }
1467  case SSDisc:
1468  {
1469  painter->setBrush(QBrush(painter->pen().color()));
1470  painter->drawEllipse(QPointF(x,y), w, w);
1471  break;
1472  }
1473  case SSSquare:
1474  {
1475  painter->setBrush(Qt::NoBrush);
1476  painter->drawRect(x-w,y-w,mScatterSize,mScatterSize);
1477  break;
1478  }
1479  case SSStar:
1480  {
1481  painter->drawLine(QLineF(x-w, y, x+w, y));
1482  painter->drawLine(QLineF(x, y+w, x, y-w));
1483  painter->drawLine(QLineF(x-w*0.707, y-w*0.707, x+w*0.707, y+w*0.707));
1484  painter->drawLine(QLineF(x-w*0.707, y+w*0.707, x+w*0.707, y-w*0.707));
1485  break;
1486  }
1487  case SSTriangle:
1488  {
1489  painter->drawLine(QLineF(x-w, y+0.755*w, x+w, y+0.755*w));
1490  painter->drawLine(QLineF(x+w, y+0.755*w, x, y-0.977*w));
1491  painter->drawLine(QLineF(x, y-0.977*w, x-w, y+0.755*w));
1492  break;
1493  }
1494  case SSTriangleInverted:
1495  {
1496  painter->drawLine(QLineF(x-w, y-0.755*w, x+w, y-0.755*w));
1497  painter->drawLine(QLineF(x+w, y-0.755*w, x, y+0.977*w));
1498  painter->drawLine(QLineF(x, y+0.977*w, x-w, y-0.755*w));
1499  break;
1500  }
1501  case SSCrossSquare:
1502  {
1503  painter->setBrush(Qt::NoBrush);
1504  painter->drawLine(QLineF(x-w, y-w, x+w*0.95, y+w*0.95));
1505  painter->drawLine(QLineF(x-w, y+w*0.95, x+w*0.95, y-w));
1506  painter->drawRect(x-w,y-w,mScatterSize,mScatterSize);
1507  break;
1508  }
1509  case SSPlusSquare:
1510  {
1511  painter->setBrush(Qt::NoBrush);
1512  painter->drawLine(QLineF(x-w, y, x+w*0.95, y));
1513  painter->drawLine(QLineF(x, y+w, x, y-w));
1514  painter->drawRect(x-w,y-w,mScatterSize,mScatterSize);
1515  break;
1516  }
1517  case SSCrossCircle:
1518  {
1519  painter->setBrush(Qt::NoBrush);
1520  painter->drawLine(QLineF(x-w*0.707, y-w*0.707, x+w*0.67, y+w*0.67));
1521  painter->drawLine(QLineF(x-w*0.707, y+w*0.67, x+w*0.67, y-w*0.707));
1522  painter->drawEllipse(x-w,y-w,mScatterSize,mScatterSize);
1523  break;
1524  }
1525  case SSPlusCircle:
1526  {
1527  painter->setBrush(Qt::NoBrush);
1528  painter->drawLine(QLineF(x-w, y, x+w, y));
1529  painter->drawLine(QLineF(x, y+w, x, y-w));
1530  painter->drawEllipse(x-w,y-w,mScatterSize,mScatterSize);
1531  break;
1532  }
1533  case SSPeace:
1534  {
1535  painter->setBrush(Qt::NoBrush);
1536  painter->drawLine(QLineF(x, y-w, x, y+w));
1537  painter->drawLine(QLineF(x, y, x-w*0.707, y+w*0.707));
1538  painter->drawLine(QLineF(x, y, x+w*0.707, y+w*0.707));
1539  painter->drawEllipse(x-w,y-w,mScatterSize,mScatterSize);
1540  break;
1541  }
1542  case SSPixmap:
1543  {
1544  painter->drawPixmap(x-mScatterPixmap.width()*0.5, y-mScatterPixmap.height()*0.5, mScatterPixmap);
1545  // if something in here is changed, adapt SSPixmap scatter style case in drawLegendIcon(), too
1546  break;
1547  }
1548  default: break;
1549  }
1550 }
1551 
1559 void QCPGraph::drawError(QPainter *painter, double x, double y, const QCPData &data) const
1560 {
1561  double a, b; // positions of error bar bounds in pixels
1562  double barWidthHalf = mErrorBarSize*0.5;
1563  double skipSymbolMargin = mScatterSize*1.25; // pixels left blank per side, when mErrorBarSkipSymbol is true
1564 
1565  if (!mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEErrorBars))
1566  {
1567  x = x-0.9; // paint system correction, else, we don't get pixel exact matches (Qt problem)
1568  y = y-0.9; // paint system correction, else, we don't get pixel exact matches (Qt problem)
1569  }
1570 
1571  if (mKeyAxis->orientation() == Qt::Vertical)
1572  {
1573  // draw key error vertically and value error horizontally
1574  if (mErrorType == ETKey || mErrorType == ETBoth)
1575  {
1576  a = mKeyAxis->coordToPixel(data.key-data.keyErrorMinus);
1577  b = mKeyAxis->coordToPixel(data.key+data.keyErrorPlus);
1578  if (mKeyAxis->rangeReversed())
1579  qSwap(a,b);
1580  // draw spine:
1581  if (mErrorBarSkipSymbol)
1582  {
1583  if (a-y > skipSymbolMargin) // don't draw spine if error is so small it's within skipSymbolmargin
1584  painter->drawLine(QLineF(x, a, x, y+skipSymbolMargin));
1585  if (y-b > skipSymbolMargin)
1586  painter->drawLine(QLineF(x, y-skipSymbolMargin, x, b));
1587  } else
1588  painter->drawLine(QLineF(x, a, x, b));
1589  // draw handles:
1590  painter->drawLine(QLineF(x-barWidthHalf, a, x+barWidthHalf, a));
1591  painter->drawLine(QLineF(x-barWidthHalf, b, x+barWidthHalf, b));
1592  }
1593  if (mErrorType == ETValue || mErrorType == ETBoth)
1594  {
1595  a = mValueAxis->coordToPixel(data.value-data.valueErrorMinus);
1596  b = mValueAxis->coordToPixel(data.value+data.valueErrorPlus);
1597  if (mValueAxis->rangeReversed())
1598  qSwap(a,b);
1599  // draw spine:
1600  if (mErrorBarSkipSymbol)
1601  {
1602  if (x-a > skipSymbolMargin) // don't draw spine if error is so small it's within skipSymbolmargin
1603  painter->drawLine(QLineF(a, y, x-skipSymbolMargin, y));
1604  if (b-x > skipSymbolMargin)
1605  painter->drawLine(QLineF(x+skipSymbolMargin, y, b, y));
1606  } else
1607  painter->drawLine(QLineF(a, y, b, y));
1608  // draw handles:
1609  painter->drawLine(QLineF(a, y-barWidthHalf, a, y+barWidthHalf));
1610  painter->drawLine(QLineF(b, y-barWidthHalf, b, y+barWidthHalf));
1611  }
1612  } else
1613  {
1614  // draw value error vertically and key error horizontally
1615  if (mErrorType == ETKey || mErrorType == ETBoth)
1616  {
1617  a = mKeyAxis->coordToPixel(data.key-data.keyErrorMinus);
1618  b = mKeyAxis->coordToPixel(data.key+data.keyErrorPlus);
1619  if (mKeyAxis->rangeReversed())
1620  qSwap(a,b);
1621  // draw spine:
1622  if (mErrorBarSkipSymbol)
1623  {
1624  if (x-a > skipSymbolMargin) // don't draw spine if error is so small it's within skipSymbolmargin
1625  painter->drawLine(QLineF(a, y, x-skipSymbolMargin, y));
1626  if (b-x > skipSymbolMargin)
1627  painter->drawLine(QLineF(x+skipSymbolMargin, y, b, y));
1628  } else
1629  painter->drawLine(QLineF(a, y, b, y));
1630  // draw handles:
1631  painter->drawLine(QLineF(a, y-barWidthHalf, a, y+barWidthHalf));
1632  painter->drawLine(QLineF(b, y-barWidthHalf, b, y+barWidthHalf));
1633  }
1634  if (mErrorType == ETValue || mErrorType == ETBoth)
1635  {
1636  a = mValueAxis->coordToPixel(data.value-data.valueErrorMinus);
1637  b = mValueAxis->coordToPixel(data.value+data.valueErrorPlus);
1638  if (mValueAxis->rangeReversed())
1639  qSwap(a,b);
1640  // draw spine:
1641  if (mErrorBarSkipSymbol)
1642  {
1643  if (a-y > skipSymbolMargin) // don't draw spine if error is so small it's within skipSymbolmargin
1644  painter->drawLine(QLineF(x, a, x, y+skipSymbolMargin));
1645  if (y-b > skipSymbolMargin)
1646  painter->drawLine(QLineF(x, y-skipSymbolMargin, x, b));
1647  } else
1648  painter->drawLine(QLineF(x, a, x, b));
1649  // draw handles:
1650  painter->drawLine(QLineF(x-barWidthHalf, a, x+barWidthHalf, a));
1651  painter->drawLine(QLineF(x-barWidthHalf, b, x+barWidthHalf, b));
1652  }
1653  }
1654 }
1655 
1669 void QCPGraph::getVisibleDataBounds(QCPDataMap::const_iterator &lower, QCPDataMap::const_iterator &upper, int &count) const
1670 {
1671  // get visible data range as QMap iterators
1672  QCPDataMap::const_iterator lbound = mData->lowerBound(mKeyAxis->range().lower);
1673  QCPDataMap::const_iterator ubound = mData->upperBound(mKeyAxis->range().upper)-1;
1674  bool lowoutlier = lbound != mData->constBegin(); // indicates whether there exist points below axis range
1675  bool highoutlier = ubound+1 != mData->constEnd(); // indicates whether there exist points above axis range
1676  lower = (lowoutlier ? lbound-1 : lbound); // data pointrange that will be actually drawn
1677  upper = (highoutlier ? ubound+1 : ubound); // data pointrange that will be actually drawn
1678 
1679  // count number of points in range lower to upper (including them), so we can allocate array for them in draw functions:
1680  QCPDataMap::const_iterator it = lower;
1681  count = 1;
1682  while (it != upper)
1683  {
1684  ++it;
1685  ++count;
1686  }
1687 }
1688 
1703 void QCPGraph::addFillBasePoints(QVector<QPointF> *lineData) const
1704 {
1705  // append points that close the polygon fill at the key axis:
1706  if (mKeyAxis->orientation() == Qt::Vertical)
1707  {
1708  *lineData << upperFillBasePoint(lineData->last().y());
1709  *lineData << lowerFillBasePoint(lineData->first().y());
1710  } else
1711  {
1712  *lineData << upperFillBasePoint(lineData->last().x());
1713  *lineData << lowerFillBasePoint(lineData->first().x());
1714  }
1715 }
1716 
1722 void QCPGraph::removeFillBasePoints(QVector<QPointF> *lineData) const
1723 {
1724  lineData->remove(lineData->size()-2, 2);
1725 }
1726 
1740 QPointF QCPGraph::lowerFillBasePoint(double lowerKey) const
1741 {
1742  QPointF point;
1744  {
1745  if (mKeyAxis->axisType() == QCPAxis::ATLeft)
1746  {
1747  point.setX(mValueAxis->coordToPixel(0));
1748  point.setY(lowerKey);
1749  } else if (mKeyAxis->axisType() == QCPAxis::ATRight)
1750  {
1751  point.setX(mValueAxis->coordToPixel(0));
1752  point.setY(lowerKey);
1753  } else if (mKeyAxis->axisType() == QCPAxis::ATTop)
1754  {
1755  point.setX(lowerKey);
1756  point.setY(mValueAxis->coordToPixel(0));
1757  } else if (mKeyAxis->axisType() == QCPAxis::ATBottom)
1758  {
1759  point.setX(lowerKey);
1760  point.setY(mValueAxis->coordToPixel(0));
1761  }
1762  } else // mValueAxis->mScaleType == QCPAxis::STLogarithmic
1763  {
1764  // In logarithmic scaling we can't just draw to value zero so we just fill all the way
1765  // to the axis which is in the direction towards zero
1766  if (mKeyAxis->orientation() == Qt::Vertical)
1767  {
1768  if ((mValueAxis->range().upper < 0 && !mValueAxis->rangeReversed()) ||
1769  (mValueAxis->range().upper > 0 && mValueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
1770  point.setX(mKeyAxis->axisRect().right());
1771  else
1772  point.setX(mKeyAxis->axisRect().left());
1773  point.setY(lowerKey);
1775  {
1776  point.setX(lowerKey);
1777  if ((mValueAxis->range().upper < 0 && !mValueAxis->rangeReversed()) ||
1778  (mValueAxis->range().upper > 0 && mValueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
1779  point.setY(mKeyAxis->axisRect().top());
1780  else
1781  point.setY(mKeyAxis->axisRect().bottom());
1782  }
1783  }
1784  return point;
1785 }
1786 
1800 QPointF QCPGraph::upperFillBasePoint(double upperKey) const
1801 {
1802  QPointF point;
1804  {
1805  if (mKeyAxis->axisType() == QCPAxis::ATLeft)
1806  {
1807  point.setX(mValueAxis->coordToPixel(0));
1808  point.setY(upperKey);
1809  } else if (mKeyAxis->axisType() == QCPAxis::ATRight)
1810  {
1811  point.setX(mValueAxis->coordToPixel(0));
1812  point.setY(upperKey);
1813  } else if (mKeyAxis->axisType() == QCPAxis::ATTop)
1814  {
1815  point.setX(upperKey);
1816  point.setY(mValueAxis->coordToPixel(0));
1817  } else if (mKeyAxis->axisType() == QCPAxis::ATBottom)
1818  {
1819  point.setX(upperKey);
1820  point.setY(mValueAxis->coordToPixel(0));
1821  }
1822  } else // mValueAxis->mScaleType == QCPAxis::STLogarithmic
1823  {
1824  // In logarithmic scaling we can't just draw to value 0 so we just fill all the way
1825  // to the axis which is in the direction towards 0
1826  if (mKeyAxis->orientation() == Qt::Vertical)
1827  {
1828  if ((mValueAxis->range().upper < 0 && !mValueAxis->rangeReversed()) ||
1829  (mValueAxis->range().upper > 0 && mValueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
1830  point.setX(mKeyAxis->axisRect().right());
1831  else
1832  point.setX(mKeyAxis->axisRect().left());
1833  point.setY(upperKey);
1835  {
1836  point.setX(upperKey);
1837  if ((mValueAxis->range().upper < 0 && !mValueAxis->rangeReversed()) ||
1838  (mValueAxis->range().upper > 0 && mValueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
1839  point.setY(mKeyAxis->axisRect().top());
1840  else
1841  point.setY(mKeyAxis->axisRect().bottom());
1842  }
1843  }
1844  return point;
1845 }
1846 
1856 const QPolygonF QCPGraph::getChannelFillPolygon(const QVector<QPointF> *lineData) const
1857 {
1859  return QPolygonF(); // don't have same axis orientation, can't fill that (Note: if keyAxis fits, valueAxis will fit too, because it's always orthogonal to keyAxis)
1860 
1861  if (lineData->isEmpty()) return QPolygonF();
1862  QVector<QPointF> otherData;
1863  mChannelFillGraph->getPlotData(&otherData, 0);
1864  if (otherData.isEmpty()) return QPolygonF();
1865  QVector<QPointF> thisData;
1866  thisData.reserve(lineData->size()+otherData.size()); // because we will join both vectors at end of this function
1867  for (int i=0; i<lineData->size(); ++i) // don't use the vector<<(vector), it squeezes internally, which ruins the performance tuning with reserve()
1868  thisData << lineData->at(i);
1869 
1870  // pointers to be able to swap them, depending which data range needs cropping:
1871  QVector<QPointF> *staticData = &thisData;
1872  QVector<QPointF> *croppedData = &otherData;
1873 
1874  // crop both vectors to ranges in which the keys overlap (which coord is key, depends on axisType):
1875  if (mKeyAxis->orientation() == Qt::Horizontal)
1876  {
1877  // x is key
1878  // if an axis range is reversed, the data point keys will be descending. Reverse them, since following algorithm assumes ascending keys:
1879  if (staticData->first().x() > staticData->last().x())
1880  {
1881  int size = staticData->size();
1882  for (int i=0; i<size/2; ++i)
1883  qSwap((*staticData)[i], (*staticData)[size-1-i]);
1884  }
1885  if (croppedData->first().x() > croppedData->last().x())
1886  {
1887  int size = croppedData->size();
1888  for (int i=0; i<size/2; ++i)
1889  qSwap((*croppedData)[i], (*croppedData)[size-1-i]);
1890  }
1891  // crop lower bound:
1892  if (staticData->first().x() < croppedData->first().x()) // other one must be cropped
1893  qSwap(staticData, croppedData);
1894  int lowBound = findIndexBelowX(croppedData, staticData->first().x());
1895  if (lowBound == -1) return QPolygonF(); // key ranges have no overlap
1896  croppedData->remove(0, lowBound);
1897  // set lowest point of cropped data to fit exactly key position of first static data
1898  // point via linear interpolation:
1899  if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
1900  double slope;
1901  if (croppedData->at(1).x()-croppedData->at(0).x() != 0)
1902  slope = (croppedData->at(1).y()-croppedData->at(0).y())/(croppedData->at(1).x()-croppedData->at(0).x());
1903  else
1904  slope = 0;
1905  (*croppedData)[0].setY(croppedData->at(0).y()+slope*(staticData->first().x()-croppedData->at(0).x()));
1906  (*croppedData)[0].setX(staticData->first().x());
1907 
1908  // crop upper bound:
1909  if (staticData->last().x() > croppedData->last().x()) // other one must be cropped
1910  qSwap(staticData, croppedData);
1911  int highBound = findIndexAboveX(croppedData, staticData->last().x());
1912  if (highBound == -1) return QPolygonF(); // key ranges have no overlap
1913  croppedData->remove(highBound+1, croppedData->size()-(highBound+1));
1914  // set highest point of cropped data to fit exactly key position of last static data
1915  // point via linear interpolation:
1916  if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
1917  int li = croppedData->size()-1; // last index
1918  if (croppedData->at(li).x()-croppedData->at(li-1).x() != 0)
1919  slope = (croppedData->at(li).y()-croppedData->at(li-1).y())/(croppedData->at(li).x()-croppedData->at(li-1).x());
1920  else
1921  slope = 0;
1922  (*croppedData)[li].setY(croppedData->at(li-1).y()+slope*(staticData->last().x()-croppedData->at(li-1).x()));
1923  (*croppedData)[li].setX(staticData->last().x());
1924  } else // mKeyAxis->orientation() == Qt::Vertical
1925  {
1926  // y is key
1927  // similar to "x is key" but switched x,y. Further, lower/upper meaning is inverted compared to x,
1928  // because in pixel coordinates, y increases from top to bottom, not bottom to top like data coordinate.
1929  // if an axis range is reversed, the data point keys will be descending. Reverse them, since following algorithm assumes ascending keys:
1930  if (staticData->first().y() < staticData->last().y())
1931  {
1932  int size = staticData->size();
1933  for (int i=0; i<size/2; ++i)
1934  qSwap((*staticData)[i], (*staticData)[size-1-i]);
1935  }
1936  if (croppedData->first().y() < croppedData->last().y())
1937  {
1938  int size = croppedData->size();
1939  for (int i=0; i<size/2; ++i)
1940  qSwap((*croppedData)[i], (*croppedData)[size-1-i]);
1941  }
1942  // crop lower bound:
1943  if (staticData->first().y() > croppedData->first().y()) // other one must be cropped
1944  qSwap(staticData, croppedData);
1945  int lowBound = findIndexAboveY(croppedData, staticData->first().y());
1946  if (lowBound == -1) return QPolygonF(); // key ranges have no overlap
1947  croppedData->remove(0, lowBound);
1948  // set lowest point of cropped data to fit exactly key position of first static data
1949  // point via linear interpolation:
1950  if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
1951  double slope;
1952  if (croppedData->at(1).y()-croppedData->at(0).y() != 0) // avoid division by zero in step plots
1953  slope = (croppedData->at(1).x()-croppedData->at(0).x())/(croppedData->at(1).y()-croppedData->at(0).y());
1954  else
1955  slope = 0;
1956  (*croppedData)[0].setX(croppedData->at(0).x()+slope*(staticData->first().y()-croppedData->at(0).y()));
1957  (*croppedData)[0].setY(staticData->first().y());
1958 
1959  // crop upper bound:
1960  if (staticData->last().y() < croppedData->last().y()) // other one must be cropped
1961  qSwap(staticData, croppedData);
1962  int highBound = findIndexBelowY(croppedData, staticData->last().y());
1963  if (highBound == -1) return QPolygonF(); // key ranges have no overlap
1964  croppedData->remove(highBound+1, croppedData->size()-(highBound+1));
1965  // set highest point of cropped data to fit exactly key position of last static data
1966  // point via linear interpolation:
1967  if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
1968  int li = croppedData->size()-1; // last index
1969  if (croppedData->at(li).y()-croppedData->at(li-1).y() != 0) // avoid division by zero in step plots
1970  slope = (croppedData->at(li).x()-croppedData->at(li-1).x())/(croppedData->at(li).y()-croppedData->at(li-1).y());
1971  else
1972  slope = 0;
1973  (*croppedData)[li].setX(croppedData->at(li-1).x()+slope*(staticData->last().y()-croppedData->at(li-1).y()));
1974  (*croppedData)[li].setY(staticData->last().y());
1975  }
1976 
1977  // return joined:
1978  for (int i=otherData.size()-1; i>=0; --i) // insert reversed, otherwise the polygon will be twisted
1979  thisData << otherData.at(i);
1980  return QPolygonF(thisData);
1981 }
1982 
1990 int QCPGraph::findIndexAboveX(const QVector<QPointF> *data, double x) const
1991 {
1992  for (int i=data->size()-1; i>=0; --i)
1993  {
1994  if (data->at(i).x() < x)
1995  {
1996  if (i<data->size()-1)
1997  return i+1;
1998  else
1999  return data->size()-1;
2000  }
2001  }
2002  return -1;
2003 }
2004 
2012 int QCPGraph::findIndexBelowX(const QVector<QPointF> *data, double x) const
2013 {
2014  for (int i=0; i<data->size(); ++i)
2015  {
2016  if (data->at(i).x() > x)
2017  {
2018  if (i>0)
2019  return i-1;
2020  else
2021  return 0;
2022  }
2023  }
2024  return -1;
2025 }
2026 
2034 int QCPGraph::findIndexAboveY(const QVector<QPointF> *data, double y) const
2035 {
2036  for (int i=0; i<data->size(); ++i)
2037  {
2038  if (data->at(i).y() < y)
2039  {
2040  if (i>0)
2041  return i-1;
2042  else
2043  return 0;
2044  }
2045  }
2046  return -1;
2047 }
2048 
2056 int QCPGraph::findIndexBelowY(const QVector<QPointF> *data, double y) const
2057 {
2058  for (int i=data->size()-1; i>=0; --i)
2059  {
2060  if (data->at(i).y() > y)
2061  {
2062  if (i<data->size()-1)
2063  return i+1;
2064  else
2065  return data->size()-1;
2066  }
2067  }
2068  return -1;
2069 }
2070 
2071 /* inherits documentation from base class */
2072 QCPRange QCPGraph::getKeyRange(bool &validRange, SignDomain inSignDomain) const
2073 {
2074  // just call the specialized version which takes an additional argument whether error bars
2075  // should also be taken into consideration for range calculation. We set this to true here.
2076  return getKeyRange(validRange, inSignDomain, true);
2077 }
2078 
2079 /* inherits documentation from base class */
2080 QCPRange QCPGraph::getValueRange(bool &validRange, SignDomain inSignDomain) const
2081 {
2082  // just call the specialized version which takes an additional argument whether error bars
2083  // should also be taken into consideration for range calculation. We set this to true here.
2084  return getValueRange(validRange, inSignDomain, true);
2085 }
2086 
2092 QCPRange QCPGraph::getKeyRange(bool &validRange, SignDomain inSignDomain, bool includeErrors) const
2093 {
2094  QCPRange range;
2095  bool haveLower = false;
2096  bool haveUpper = false;
2097 
2098  double current, currentErrorMinus, currentErrorPlus;
2099 
2100  if (inSignDomain == SDBoth) // range may be anywhere
2101  {
2102  QCPDataMap::const_iterator it = mData->constBegin();
2103  while (it != mData->constEnd())
2104  {
2105  current = it.value().key;
2106  currentErrorMinus = (includeErrors ? it.value().keyErrorMinus : 0);
2107  currentErrorPlus = (includeErrors ? it.value().keyErrorPlus : 0);
2108  if (current-currentErrorMinus < range.lower || !haveLower)
2109  {
2110  range.lower = current-currentErrorMinus;
2111  haveLower = true;
2112  }
2113  if (current+currentErrorPlus > range.upper || !haveUpper)
2114  {
2115  range.upper = current+currentErrorPlus;
2116  haveUpper = true;
2117  }
2118  it++;
2119  }
2120  } else if (inSignDomain == SDNegative) // range may only be in the negative sign domain
2121  {
2122  QCPDataMap::const_iterator it = mData->constBegin();
2123  while (it != mData->constEnd())
2124  {
2125  current = it.value().key;
2126  currentErrorMinus = (includeErrors ? it.value().keyErrorMinus : 0);
2127  currentErrorPlus = (includeErrors ? it.value().keyErrorPlus : 0);
2128  if ((current-currentErrorMinus < range.lower || !haveLower) && current-currentErrorMinus < 0)
2129  {
2130  range.lower = current-currentErrorMinus;
2131  haveLower = true;
2132  }
2133  if ((current+currentErrorPlus > range.upper || !haveUpper) && current+currentErrorPlus < 0)
2134  {
2135  range.upper = current+currentErrorPlus;
2136  haveUpper = true;
2137  }
2138  if (includeErrors) // in case point is in valid sign domain but errobars stretch beyond it, we still want to geht that point.
2139  {
2140  if ((current < range.lower || !haveLower) && current < 0)
2141  {
2142  range.lower = current;
2143  haveLower = true;
2144  }
2145  if ((current > range.upper || !haveUpper) && current < 0)
2146  {
2147  range.upper = current;
2148  haveUpper = true;
2149  }
2150  }
2151  it++;
2152  }
2153  } else if (inSignDomain == SDPositive) // range may only be in the positive sign domain
2154  {
2155  QCPDataMap::const_iterator it = mData->constBegin();
2156  while (it != mData->constEnd())
2157  {
2158  current = it.value().key;
2159  currentErrorMinus = (includeErrors ? it.value().keyErrorMinus : 0);
2160  currentErrorPlus = (includeErrors ? it.value().keyErrorPlus : 0);
2161  if ((current-currentErrorMinus < range.lower || !haveLower) && current-currentErrorMinus > 0)
2162  {
2163  range.lower = current-currentErrorMinus;
2164  haveLower = true;
2165  }
2166  if ((current+currentErrorPlus > range.upper || !haveUpper) && current+currentErrorPlus > 0)
2167  {
2168  range.upper = current+currentErrorPlus;
2169  haveUpper = true;
2170  }
2171  if (includeErrors) // in case point is in valid sign domain but errobars stretch beyond it, we still want to get that point.
2172  {
2173  if ((current < range.lower || !haveLower) && current > 0)
2174  {
2175  range.lower = current;
2176  haveLower = true;
2177  }
2178  if ((current > range.upper || !haveUpper) && current > 0)
2179  {
2180  range.upper = current;
2181  haveUpper = true;
2182  }
2183  }
2184  it++;
2185  }
2186  }
2187 
2188  validRange = haveLower && haveUpper;
2189  return range;
2190 }
2191 
2197 QCPRange QCPGraph::getValueRange(bool &validRange, SignDomain inSignDomain, bool includeErrors) const
2198 {
2199  QCPRange range;
2200  bool haveLower = false;
2201  bool haveUpper = false;
2202 
2203  double current, currentErrorMinus, currentErrorPlus;
2204 
2205  if (inSignDomain == SDBoth) // range may be anywhere
2206  {
2207  QCPDataMap::const_iterator it = mData->constBegin();
2208  while (it != mData->constEnd())
2209  {
2210  current = it.value().value;
2211  currentErrorMinus = (includeErrors ? it.value().valueErrorMinus : 0);
2212  currentErrorPlus = (includeErrors ? it.value().valueErrorPlus : 0);
2213  if (current-currentErrorMinus < range.lower || !haveLower)
2214  {
2215  range.lower = current-currentErrorMinus;
2216  haveLower = true;
2217  }
2218  if (current+currentErrorPlus > range.upper || !haveUpper)
2219  {
2220  range.upper = current+currentErrorPlus;
2221  haveUpper = true;
2222  }
2223  it++;
2224  }
2225  } else if (inSignDomain == SDNegative) // range may only be in the negative sign domain
2226  {
2227  QCPDataMap::const_iterator it = mData->constBegin();
2228  while (it != mData->constEnd())
2229  {
2230  current = it.value().value;
2231  currentErrorMinus = (includeErrors ? it.value().valueErrorMinus : 0);
2232  currentErrorPlus = (includeErrors ? it.value().valueErrorPlus : 0);
2233  if ((current-currentErrorMinus < range.lower || !haveLower) && current-currentErrorMinus < 0)
2234  {
2235  range.lower = current-currentErrorMinus;
2236  haveLower = true;
2237  }
2238  if ((current+currentErrorPlus > range.upper || !haveUpper) && current+currentErrorPlus < 0)
2239  {
2240  range.upper = current+currentErrorPlus;
2241  haveUpper = true;
2242  }
2243  if (includeErrors) // in case point is in valid sign domain but errobars stretch beyond it, we still want to get that point.
2244  {
2245  if ((current < range.lower || !haveLower) && current < 0)
2246  {
2247  range.lower = current;
2248  haveLower = true;
2249  }
2250  if ((current > range.upper || !haveUpper) && current < 0)
2251  {
2252  range.upper = current;
2253  haveUpper = true;
2254  }
2255  }
2256  it++;
2257  }
2258  } else if (inSignDomain == SDPositive) // range may only be in the positive sign domain
2259  {
2260  QCPDataMap::const_iterator it = mData->constBegin();
2261  while (it != mData->constEnd())
2262  {
2263  current = it.value().value;
2264  currentErrorMinus = (includeErrors ? it.value().valueErrorMinus : 0);
2265  currentErrorPlus = (includeErrors ? it.value().valueErrorPlus : 0);
2266  if ((current-currentErrorMinus < range.lower || !haveLower) && current-currentErrorMinus > 0)
2267  {
2268  range.lower = current-currentErrorMinus;
2269  haveLower = true;
2270  }
2271  if ((current+currentErrorPlus > range.upper || !haveUpper) && current+currentErrorPlus > 0)
2272  {
2273  range.upper = current+currentErrorPlus;
2274  haveUpper = true;
2275  }
2276  if (includeErrors) // in case point is in valid sign domain but errobars stretch beyond it, we still want to geht that point.
2277  {
2278  if ((current < range.lower || !haveLower) && current > 0)
2279  {
2280  range.lower = current;
2281  haveLower = true;
2282  }
2283  if ((current > range.upper || !haveUpper) && current > 0)
2284  {
2285  range.upper = current;
2286  haveUpper = true;
2287  }
2288  }
2289  it++;
2290  }
2291  }
2292 
2293  validRange = haveLower && haveUpper;
2294  return range;
2295 }
2296 
2297 
2298 // ================================================================================
2299 // =================== QCPRange
2300 // ================================================================================
2316 const double QCPRange::minRange = 1e-280;
2317 
2326 const double QCPRange::maxRange = 1e250;
2327 
2332  lower(0),
2333  upper(0)
2334 {
2335 }
2336 
2341 {
2342  this->lower = lower;
2343  this->upper = upper;
2344 }
2345 
2349 double QCPRange::size() const
2350 {
2351  return upper-lower;
2352 }
2353 
2357 double QCPRange::center() const
2358 {
2359  return (upper+lower)*0.5;
2360 }
2361 
2367 {
2368  if (lower > upper)
2369  qSwap(lower, upper);
2370 }
2371 
2385 {
2386  double rangeFac = 1e-3;
2387  QCPRange sanitizedRange(lower, upper);
2388  sanitizedRange.normalize();
2389  // can't have range spanning negative and positive values in log plot, so change range to fix it
2390  //if (qFuzzyCompare(sanitizedRange.lower+1, 1) && !qFuzzyCompare(sanitizedRange.upper+1, 1))
2391  if (sanitizedRange.lower == 0.0 && sanitizedRange.upper != 0.0)
2392  {
2393  // case lower is 0
2394  if (rangeFac < sanitizedRange.upper*rangeFac)
2395  sanitizedRange.lower = rangeFac;
2396  else
2397  sanitizedRange.lower = sanitizedRange.upper*rangeFac;
2398  } //else if (!qFuzzyCompare(lower+1, 1) && qFuzzyCompare(upper+1, 1))
2399  else if (sanitizedRange.lower != 0.0 && sanitizedRange.upper == 0.0)
2400  {
2401  // case upper is 0
2402  if (-rangeFac > sanitizedRange.lower*rangeFac)
2403  sanitizedRange.upper = -rangeFac;
2404  else
2405  sanitizedRange.upper = sanitizedRange.lower*rangeFac;
2406  } else if (sanitizedRange.lower < 0 && sanitizedRange.upper > 0)
2407  {
2408  // find out whether negative or positive interval is wider to decide which sign domain will be chosen
2409  if (-sanitizedRange.lower > sanitizedRange.upper)
2410  {
2411  // negative is wider, do same as in case upper is 0
2412  if (-rangeFac > sanitizedRange.lower*rangeFac)
2413  sanitizedRange.upper = -rangeFac;
2414  else
2415  sanitizedRange.upper = sanitizedRange.lower*rangeFac;
2416  } else
2417  {
2418  // positive is wider, do same as in case lower is 0
2419  if (rangeFac < sanitizedRange.upper*rangeFac)
2420  sanitizedRange.lower = rangeFac;
2421  else
2422  sanitizedRange.lower = sanitizedRange.upper*rangeFac;
2423  }
2424  }
2425  // due to normalization, case lower>0 && upper<0 should never occur, because that implies upper<lower
2426  return sanitizedRange;
2427 }
2428 
2434 {
2435  QCPRange sanitizedRange(lower, upper);
2436  sanitizedRange.normalize();
2437  return sanitizedRange;
2438 }
2439 
2448 bool QCPRange::validRange(double lower, double upper)
2449 {
2450  /*
2451  return (lower > -maxRange &&
2452  upper < maxRange &&
2453  fabs(lower-upper) > minRange &&
2454  (lower < -minRange || lower > minRange) &&
2455  (upper < -minRange || upper > minRange));
2456  */
2457  return (lower > -maxRange &&
2458  upper < maxRange &&
2459  fabs(lower-upper) > minRange &&
2460  fabs(lower-upper) < maxRange);
2461 }
2462 
2472 bool QCPRange::validRange(const QCPRange &range)
2473 {
2474  /*
2475  return (range.lower > -maxRange &&
2476  range.upper < maxRange &&
2477  fabs(range.lower-range.upper) > minRange &&
2478  fabs(range.lower-range.upper) < maxRange &&
2479  (range.lower < -minRange || range.lower > minRange) &&
2480  (range.upper < -minRange || range.upper > minRange));
2481  */
2482  return (range.lower > -maxRange &&
2483  range.upper < maxRange &&
2484  fabs(range.lower-range.upper) > minRange &&
2485  fabs(range.lower-range.upper) < maxRange);
2486 }
2487 
2488 
2489 // ================================================================================
2490 // =================== QCPLegend
2491 // ================================================================================
2492 
2506  QObject(parentPlot)
2507 {
2508  mParentPlot = parentPlot;
2509  setVisible(true);
2510  setBorderPen(QPen(Qt::black));
2511  setIconBorderPen(Qt::NoPen);
2512  setBrush(QBrush(Qt::white));
2513  setFont(parentPlot->font());
2515  setSize(100, 28);
2516  setMinimumSize(100, 0);
2517  setAutoSize(true);
2518 
2519  setMargin(12, 12, 12, 12);
2520  setPadding(8, 8, 3, 3);
2521  setIconSize(32, 18);
2522  setItemSpacing(3);
2523  setIconTextPadding(7);
2524 }
2525 
2527 {
2528  clearItems();
2529 }
2530 
2534 void QCPLegend::setBorderPen(const QPen &pen)
2535 {
2536  mBorderPen = pen;
2537 }
2538 
2542 void QCPLegend::setBrush(const QBrush &brush)
2543 {
2544  mBrush = brush;
2545 }
2546 
2552 void QCPLegend::setFont(const QFont &font)
2553 {
2554  mFont = font;
2555  for (int i=0; i<mItems.size(); ++i)
2556  mItems.at(i)->setFont(mFont);
2557 }
2558 
2567 {
2568  mPositionStyle = legendPositionStyle;
2569 }
2570 
2575 void QCPLegend::setPosition(const QPoint &pixelPosition)
2576 {
2577  mPosition = pixelPosition;
2578 }
2579 
2590 {
2591  mAutoSize = on;
2592 }
2593 
2603 void QCPLegend::setSize(const QSize &size)
2604 {
2605  mSize = size;
2606 }
2607 
2610 void QCPLegend::setSize(int width, int height)
2611 {
2612  mSize = QSize(width, height);
2613 }
2614 
2627 {
2628  mMinimumSize = size;
2629 }
2630 
2633 void QCPLegend::setMinimumSize(int width, int height)
2634 {
2635  mMinimumSize = QSize(width, height);
2636 }
2637 
2642 {
2643  mVisible = on;
2644 }
2645 
2651 void QCPLegend::setPaddingLeft(int padding)
2652 {
2653  mPaddingLeft = padding;
2654 }
2655 
2662 {
2663  mPaddingRight = padding;
2664 }
2665 
2671 void QCPLegend::setPaddingTop(int padding)
2672 {
2673  mPaddingTop = padding;
2674 }
2675 
2682 {
2683  mPaddingBottom = padding;
2684 }
2685 
2691 void QCPLegend::setPadding(int left, int right, int top, int bottom)
2692 {
2693  mPaddingLeft = left;
2694  mPaddingRight = right;
2695  mPaddingTop = top;
2696  mPaddingBottom = bottom;
2697 }
2698 
2704 {
2705  mMarginLeft = margin;
2706 }
2707 
2713 {
2714  mMarginRight = margin;
2715 }
2716 
2721 void QCPLegend::setMarginTop(int margin)
2722 {
2723  mMarginTop = margin;
2724 }
2725 
2731 {
2732  mMarginBottom = margin;
2733 }
2734 
2739 void QCPLegend::setMargin(int left, int right, int top, int bottom)
2740 {
2741  mMarginLeft = left;
2742  mMarginRight = right;
2743  mMarginTop = top;
2744  mMarginBottom = bottom;
2745 }
2746 
2752 void QCPLegend::setItemSpacing(int spacing)
2753 {
2754  mItemSpacing = spacing;
2755 }
2756 
2762 void QCPLegend::setIconSize(const QSize &size)
2763 {
2764  mIconSize = size;
2765 }
2766 
2769 void QCPLegend::setIconSize(int width, int height)
2770 {
2771  mIconSize.setWidth(width);
2772  mIconSize.setHeight(height);
2773 }
2774 
2784 {
2785  mIconTextPadding = padding;
2786 }
2787 
2795 void QCPLegend::setIconBorderPen(const QPen &pen)
2796 {
2797  mIconBorderPen = pen;
2798 }
2799 
2806 {
2807  if (index >= 0 && index < mItems.size())
2808  return mItems[index];
2809  else
2810  return 0;
2811 }
2812 
2820 {
2821  for (int i=0; i<mItems.size(); ++i)
2822  {
2823  if (QCPPlottableLegendItem *lip = dynamic_cast<QCPPlottableLegendItem*>(mItems.at(i)))
2824  {
2825  if (lip->plottable() == plottable)
2826  return lip;
2827  }
2828  }
2829  return 0;
2830 }
2831 
2837 {
2838  return mItems.size();
2839 }
2840 
2845 {
2846  return mItems.contains(item);
2847 }
2848 
2856 {
2857  return itemWithPlottable(plottable);
2858 }
2859 
2868 {
2869  if (!mItems.contains(item))
2870  {
2871  mItems.append(item);
2872  return true;
2873  } else
2874  return false;
2875 }
2876 
2884 bool QCPLegend::removeItem(int index)
2885 {
2886  if (index >= 0 && index < mItems.size())
2887  {
2888  delete mItems.at(index);
2889  mItems.removeAt(index);
2890  return true;
2891  } else
2892  return false;
2893 }
2894 
2904 {
2905  return removeItem(mItems.indexOf(item));
2906 }
2907 
2912 {
2913  qDeleteAll(mItems);
2914  mItems.clear();
2915 }
2916 
2923 {
2924  if (mAutoSize)
2925  {
2927  }
2929 }
2930 
2935 void QCPLegend::draw(QPainter *painter)
2936 {
2937  if (!mVisible) return;
2938  painter->save();
2939  painter->setBrush(mBrush);
2940  painter->setPen(mBorderPen);
2941  // draw background rect:
2942  painter->drawRect(QRect(mPosition, mSize));
2943  // draw legend items:
2944  painter->setClipRect(QRect(mPosition, mSize).adjusted(1, 1, 0, 0));
2945  painter->setPen(QPen());
2946  painter->setBrush(Qt::NoBrush);
2947  int currentTop = mPosition.y()+mPaddingTop;
2948  for (int i=0; i<mItems.size(); ++i)
2949  {
2950  QSize itemSize = mItems.at(i)->size(QSize(mSize.width(), 0));
2951  painter->save(); // this might be user subclass, so we save painter outside - just in case
2952  mItems.at(i)->draw(painter, QRect(QPoint(mPosition.x()+mPaddingLeft, currentTop), itemSize));
2953  painter->restore();
2954  currentTop += itemSize.height()+mItemSpacing;
2955  }
2956  painter->restore();
2957 }
2958 
2959 int QCPLegend::getItemIndex(const QPoint *point)
2960 {
2961  if (!mVisible) return -1;
2962 
2963  int currentTop = mPosition.y() + mPaddingTop;
2964 
2965  for (int i=0; i<mItems.size(); ++i)
2966  {
2967  QSize itemSize = mItems.at(i)->size(QSize(mSize.width(), 0));
2968  QPoint itemPosTopLeft = QPoint(mPosition.x()+mPaddingLeft, currentTop);
2969 
2970  if (point->x() >= itemPosTopLeft.x() &&
2971  point->x() <= itemPosTopLeft.x()+itemSize.width() &&
2972  point->y() >= itemPosTopLeft.y() &&
2973  point->y() <= itemPosTopLeft.y()+itemSize.height())
2974  {
2975  return i;
2976  }
2977 
2978  currentTop += itemSize.height()+mItemSpacing;
2979  }
2980 
2981  return -1;
2982 }
2990 {
2991  int width = mMinimumSize.width()-mPaddingLeft-mPaddingRight; // start with minimum width and only expand from there
2992  int currentTop;
2993  bool repeat = true;
2994  int repeatCount = 0;
2995  while (repeat && repeatCount < 3) // repeat until we find self-consistent width (usually 2 runs)
2996  {
2997  repeat = false;
2998  currentTop = mPaddingTop;
2999  for (int i=0; i<mItems.size(); ++i)
3000  {
3001  QSize s = mItems.at(i)->size(QSize(width, 0));
3002  currentTop += s.height();
3003  if (i < mItems.size()-1) // vertical spacer for all but last item
3004  currentTop += mItemSpacing;
3005  if (width < s.width())
3006  {
3007  width = s.width();
3008  repeat = true; // changed width, so need a new run with new width to let other items adapt their height to that new width
3009  }
3010  }
3011  repeatCount++;
3012  }
3013  if (repeat)
3014  qDebug() << FUNCNAME << "hit repeat limit for iterative width calculation";
3015  currentTop += mPaddingBottom;
3016  width += mPaddingLeft+mPaddingRight;
3017 
3018  mSize.setWidth(width);
3019  if (currentTop > mMinimumSize.height())
3020  mSize.setHeight(currentTop);
3021  else
3022  mSize.setHeight(mMinimumSize.height());
3023 }
3024 
3030 {
3031  if (mPositionStyle == PSTopLeft)
3032  {
3033  mPosition = mParentPlot->mAxisRect.topLeft() + QPoint(mMarginLeft, mMarginTop);
3034  } else if (mPositionStyle == PSTop)
3035  {
3036  mPosition = mParentPlot->mAxisRect.topLeft() + QPoint(mParentPlot->mAxisRect.width()/2.0-mSize.width()/2.0, mMarginTop);
3037  } else if (mPositionStyle == PSTopRight)
3038  {
3039  mPosition = mParentPlot->mAxisRect.topRight() + QPoint(-mMarginRight-mSize.width(), mMarginTop);
3040  } else if (mPositionStyle == PSRight)
3041  {
3042  mPosition = mParentPlot->mAxisRect.topRight() + QPoint(-mMarginRight-mSize.width(), mParentPlot->mAxisRect.height()/2.0-mSize.height()/2.0);
3043  } else if (mPositionStyle == PSBottomRight)
3044  {
3045  mPosition = mParentPlot->mAxisRect.bottomRight() + QPoint(-mMarginRight-mSize.width(), -mMarginBottom-mSize.height());
3046  } else if (mPositionStyle == PSBottom)
3047  {
3048  mPosition = mParentPlot->mAxisRect.bottomLeft() + QPoint(mParentPlot->mAxisRect.width()/2.0-mSize.width()/2.0, -mMarginBottom-mSize.height());
3049  } else if (mPositionStyle == PSBottomLeft)
3050  {
3051  mPosition = mParentPlot->mAxisRect.bottomLeft() + QPoint(mMarginLeft, -mMarginBottom-mSize.height());
3052  } else if (mPositionStyle == PSLeft)
3053  {
3054  mPosition = mParentPlot->mAxisRect.topLeft() + QPoint(mMarginLeft, mParentPlot->mAxisRect.height()/2.0-mSize.height()/2.0);
3055  }
3056 }
3057 
3058 
3059 // ================================================================================
3060 // =================== QCPAxis
3061 // ================================================================================
3062 
3075 {
3076  mParentPlot = parentPlot;
3077  mTickVector = new QVector<double>;
3078  mSubTickVector = new QVector<double>;
3079  mTickVectorLabels = new QVector<QString>;
3080  setAxisType(type);
3081  setAxisRect(parentPlot->axisRect());
3082  setScaleType(STLinear);
3083  setScaleLogBase(10);
3084 
3085  setVisible(true);
3086  setRange(0, 5);
3087  setRangeReversed(false);
3088 
3089  setTicks(true);
3090  setTickStep(1);
3091  setAutoTickCount(6);
3092  setAutoTicks(true);
3093  setAutoTickLabels(true);
3094  setAutoTickStep(true);
3095  setTickLabelFont(parentPlot->font());
3096  setTickLength(5);
3097  setTickPen(QPen(Qt::black));
3098  setTickLabels(true);
3099  setTickLabelType(LTNumber);
3100  setTickLabelRotation(0);
3101  setDateTimeFormat("hh:mm:ss\ndd.MM.yy");
3102  setNumberFormat("gbd");
3103  setNumberPrecision(6);
3104  setLabel("");
3105  setLabelFont(parentPlot->font());
3106 
3107  setAutoSubTicks(true);
3108  setSubTickCount(4);
3109  setSubTickLength(2);
3110  setSubTickPen(QPen(Qt::black));
3111 
3112  QPen gPen;
3113  gPen.setColor(QColor(200,200,200));
3114  gPen.setStyle(Qt::DotLine);
3115  setGridPen(gPen);
3116  setGrid(true);
3117  QPen subgPen;
3118  subgPen.setColor(QColor(220,220,220));
3119  subgPen.setStyle(Qt::DotLine);
3120  setSubGridPen(subgPen);
3121  setSubGrid(false);
3122  QPen zlinePen;
3123  zlinePen.setColor(QColor(200,200,200));
3124  setZeroLinePen(zlinePen);
3125  setBasePen(QPen(Qt::black));
3126 
3127  setPadding(0);
3128  if (type == ATTop)
3129  {
3130  setTickLabelPadding(3);
3131  setLabelPadding(6);
3132  } else if (type == ATRight)
3133  {
3134  setTickLabelPadding(7);
3135  setLabelPadding(12);
3136  } else if (type == ATBottom)
3137  {
3138  setTickLabelPadding(3);
3139  setLabelPadding(3);
3140  } else if (type == ATLeft)
3141  {
3142  setTickLabelPadding(5);
3143  setLabelPadding(10);
3144  }
3145 }
3146 
3148 {
3149  delete mTickVector;
3150  delete mTickVectorLabels;
3151  delete mSubTickVector;
3152 }
3153 
3158 QString QCPAxis::numberFormat() const
3159 {
3160  QString result;
3161  result.append(mNumberFormatChar);
3162  if (mNumberBeautifulPowers)
3163  {
3164  result.append("b");
3165  if (mNumberMultiplyCross)
3166  result.append("c");
3167  }
3168  return result;
3169 }
3170 
3178 {
3179  mAxisType = type;
3180  mOrientation = (type == ATBottom || type == ATTop) ? Qt::Horizontal : Qt::Vertical;
3181 }
3182 
3190 void QCPAxis::setAxisRect(const QRect &rect)
3191 {
3192  mAxisRect = rect;
3193 }
3194 
3209 {
3210  mScaleType = type;
3211  if (mScaleType == STLogarithmic)
3212  mRange = mRange.sanitizedForLogScale();
3213 }
3214 
3222 void QCPAxis::setScaleLogBase(double base)
3223 {
3224  if (base > 1)
3225  {
3226  mScaleLogBase = base;
3227  mScaleLogBaseLogInv = 1.0/log(mScaleLogBase); // buffer for faster baseLog() calculation
3228  } else
3229  qDebug() << FUNCNAME << "Invalid logarithmic scale base (must be greater 1):" << base;
3230 }
3231 
3240 void QCPAxis::setRange(const QCPRange &range)
3241 {
3242  if (!QCPRange::validRange(range)) return;
3243  if (mScaleType == STLogarithmic)
3244  {
3245  mRange = range.sanitizedForLogScale();
3246  } else
3247  {
3248  mRange = range.sanitizedForLinScale();
3249  }
3250  emit rangeChanged(mRange);
3251 }
3252 
3259 void QCPAxis::setRange(double lower, double upper)
3260 {
3261  if (!QCPRange::validRange(lower, upper)) return;
3262  mRange.lower = lower;
3263  mRange.upper = upper;
3264  if (mScaleType == STLogarithmic)
3265  {
3266  mRange = mRange.sanitizedForLogScale();
3267  } else
3268  {
3269  mRange = mRange.sanitizedForLinScale();
3270  }
3271  emit rangeChanged(mRange);
3272 }
3273 
3287 void QCPAxis::setRange(double position, double size, Qt::AlignmentFlag alignment)
3288 {
3289  if (alignment == Qt::AlignLeft)
3290  setRange(position, position+size);
3291  else if (alignment == Qt::AlignRight)
3292  setRange(position-size, position);
3293  else // alignment == Qt::AlignCenter
3294  setRange(position-size/2.0, position+size/2.0);
3295 }
3296 
3301 void QCPAxis::setRangeLower(double lower)
3302 {
3303  mRange.lower = lower;
3304  if (mScaleType == STLogarithmic)
3305  {
3306  mRange = mRange.sanitizedForLogScale();
3307  } else
3308  {
3309  mRange = mRange.sanitizedForLinScale();
3310  }
3311  emit rangeChanged(mRange);
3312 }
3313 
3318 void QCPAxis::setRangeUpper(double upper)
3319 {
3320  mRange.upper = upper;
3321  if (mScaleType == STLogarithmic)
3322  {
3323  mRange = mRange.sanitizedForLogScale();
3324  } else
3325  {
3326  mRange = mRange.sanitizedForLinScale();
3327  }
3328  emit rangeChanged(mRange);
3329 }
3330 
3338 void QCPAxis::setRangeReversed(bool reversed)
3339 {
3340  mRangeReversed = reversed;
3341 }
3342 
3348 void QCPAxis::setVisible(bool on)
3349 {
3350  mVisible = on;
3351 }
3352 
3357 void QCPAxis::setGrid(bool show)
3358 {
3359  mGrid = show;
3360 }
3361 
3366 void QCPAxis::setSubGrid(bool show)
3367 {
3368  mSubGrid = show;
3369 }
3370 
3381 {
3382  mAutoTicks = on;
3383 }
3384 
3389 void QCPAxis::setAutoTickCount(int approximateCount)
3390 {
3391  mAutoTickCount = approximateCount;
3392 }
3393 
3403 {
3404  mAutoTickLabels = on;
3405 }
3406 
3418 {
3419  mAutoTickStep = on;
3420 }
3421 
3429 {
3430  mAutoSubTicks = on;
3431 }
3432 
3437 void QCPAxis::setTicks(bool show)
3438 {
3439  mTicks = show;
3440 }
3441 
3445 void QCPAxis::setTickLabels(bool show)
3446 {
3447  mTickLabels = show;
3448 }
3449 
3455 {
3456  mTickLabelPadding = padding;
3457 }
3458 
3470 {
3471  mTickLabelType = type;
3472 }
3473 
3478 {
3479  mTickLabelFont = font;
3480 }
3481 
3487 void QCPAxis::setTickLabelRotation(double degrees)
3488 {
3489  mTickLabelRotation = qBound(-90.0, degrees, 90.0);
3490 }
3491 
3497 void QCPAxis::setDateTimeFormat(const QString &format)
3498 {
3499  mDateTimeFormat = format;
3500 }
3501 
3538 void QCPAxis::setNumberFormat(const QString &formatCode)
3539 {
3540  if (formatCode.length() < 1) return;
3541 
3542  // interpret first char as number format char:
3543  QString allowedFormatChars = "eEfgG";
3544  if (allowedFormatChars.contains(formatCode.at(0)))
3545  {
3546  mNumberFormatChar = formatCode.at(0).toAscii();
3547  } else
3548  {
3549  qDebug() << FUNCNAME << "Invalid number format code (first char not in 'eEfgG'):" << formatCode;
3550  return;
3551  }
3552  if (formatCode.length() < 2)
3553  {
3554  mNumberBeautifulPowers = false;
3555  mNumberMultiplyCross = false;
3556  return;
3557  }
3558 
3559  // interpret second char as indicator for beautiful decimal powers:
3560  if (formatCode.at(1) == 'b' && (mNumberFormatChar == 'e' || mNumberFormatChar == 'g'))
3561  {
3562  mNumberBeautifulPowers = true;
3563  } else
3564  {
3565  qDebug() << FUNCNAME << "Invalid number format code (second char not 'b' or first char neither 'e' nor 'g'):" << formatCode;
3566  return;
3567  }
3568  if (formatCode.length() < 3)
3569  {
3570  mNumberMultiplyCross = false;
3571  return;
3572  }
3573 
3574  // interpret third char as indicator for dot or cross multiplication symbol:
3575  if (formatCode.at(2) == 'c')
3576  {
3577  mNumberMultiplyCross = true;
3578  } else if (formatCode.at(2) == 'd')
3579  {
3580  mNumberMultiplyCross = false;
3581  } else
3582  {
3583  qDebug() << FUNCNAME << "Invalid number format code (third char neither 'c' nor 'd'):" << formatCode;
3584  return;
3585  }
3586 }
3587 
3599 void QCPAxis::setNumberPrecision(int precision)
3600 {
3601  mNumberPrecision = precision;
3602 }
3603 
3609 void QCPAxis::setTickStep(double step)
3610 {
3611  mTickStep = step;
3612 }
3613 
3628 void QCPAxis::setTickVector(QVector<double> *vec, bool copy)
3629 {
3630  if (copy)
3631  {
3632  *mTickVector = *vec;
3633  } else
3634  {
3635  delete mTickVector;
3636  mTickVector = vec;
3637  }
3638 }
3639 
3655 void QCPAxis::setTickVectorLabels(QVector<QString> *vec, bool copy)
3656 {
3657  if (copy)
3658  {
3659  *mTickVectorLabels = *vec;
3660  } else
3661  {
3662  delete mTickVectorLabels;
3663  mTickVectorLabels = vec;
3664  }
3665 }
3666 
3674 void QCPAxis::setTickLength(int inside, int outside)
3675 {
3676  mTickLengthIn = inside;
3677  mTickLengthOut = outside;
3678 }
3679 
3690 {
3691  mSubTickCount = count;
3692 }
3693 
3701 void QCPAxis::setSubTickLength(int inside, int outside)
3702 {
3703  mSubTickLengthIn = inside;
3704  mSubTickLengthOut = outside;
3705 }
3706 
3710 void QCPAxis::setBasePen(const QPen &pen)
3711 {
3712  mBasePen = pen;
3713 }
3714 
3719 void QCPAxis::setGridPen(const QPen &pen)
3720 {
3721  mGridPen = pen;
3722 }
3723 
3729 void QCPAxis::setSubGridPen(const QPen &pen)
3730 {
3731  mSubGridPen = pen;
3732 }
3733 
3741 void QCPAxis::setZeroLinePen(const QPen &pen)
3742 {
3743  mZeroLinePen = pen;
3744 }
3745 
3750 void QCPAxis::setTickPen(const QPen &pen)
3751 {
3752  mTickPen = pen;
3753 }
3754 
3759 void QCPAxis::setSubTickPen(const QPen &pen)
3760 {
3761  mSubTickPen = pen;
3762 }
3763 
3767 void QCPAxis::setLabelFont(const QFont &font)
3768 {
3769  mLabelFont = font;
3770 }
3771 
3775 void QCPAxis::setLabel(const QString &str)
3776 {
3777  mLabel = str;
3778 }
3779 
3784 void QCPAxis::setLabelPadding(int padding)
3785 {
3786  mLabelPadding = padding;
3787 }
3788 
3802 void QCPAxis::setPadding(int padding)
3803 {
3804  mPadding = padding;
3805 }
3806 
3813 void QCPAxis::moveRange(double diff)
3814 {
3815  if (mScaleType == STLinear)
3816  {
3817  mRange.lower += diff;
3818  mRange.upper += diff;
3819  } else // mScaleType == STLogarithmic
3820  {
3821  mRange.lower *= diff;
3822  mRange.upper *= diff;
3823  }
3824  emit rangeChanged(mRange);
3825 }
3826 
3833 void QCPAxis::scaleRange(double factor, double center)
3834 {
3835 
3836  if (mScaleType == STLinear)
3837  {
3838  QCPRange newRange;
3839  newRange.lower = (mRange.lower-center)*factor + center;
3840  newRange.upper = (mRange.upper-center)*factor + center;
3841  if (QCPRange::validRange(newRange))
3842  mRange = newRange.sanitizedForLinScale();
3843  } else // mScaleType == STLogarithmic
3844  {
3845  if ((mRange.upper < 0 && center < 0) || (mRange.upper > 0 && center > 0)) // make sure center has same sign as range
3846  {
3847  QCPRange newRange;
3848  newRange.lower = pow(mRange.lower/center, factor)*center;
3849  newRange.upper = pow(mRange.upper/center, factor)*center;
3850  if (QCPRange::validRange(newRange))
3851  mRange = newRange.sanitizedForLogScale();
3852  } else
3853  qDebug() << FUNCNAME << "center of scaling operation doesn't lie in same logarithmic sign domain as range:" << center;
3854  }
3855  emit rangeChanged(mRange);
3856 }
3857 
3866 void QCPAxis::setScaleRatio(const QCPAxis *otherAxis, double ratio)
3867 {
3868  int otherPixelSize, ownPixelSize;
3869 
3870  if (otherAxis->orientation() == Qt::Horizontal)
3871  otherPixelSize = otherAxis->mAxisRect.width();
3872  else
3873  otherPixelSize = otherAxis->mAxisRect.height();
3874 
3875  if (orientation() == Qt::Horizontal)
3876  ownPixelSize = mAxisRect.width();
3877  else
3878  ownPixelSize = mAxisRect.height();
3879 
3880  double newRangeSize = ratio*otherAxis->mRange.size()*ownPixelSize/(double)otherPixelSize;
3881  setRange(range().center(), newRangeSize, Qt::AlignCenter);
3882 }
3883 
3888 double QCPAxis::pixelToCoord(double value) const
3889 {
3890  if (orientation() == Qt::Horizontal)
3891  {
3892  if (mScaleType == STLinear)
3893  {
3894  if (mRangeReversed)
3895  return -(value-mAxisRect.left())/(double)mAxisRect.width()*mRange.size()+mRange.upper;
3896  else
3897  return (value-mAxisRect.left())/(double)mAxisRect.width()*mRange.size()+mRange.lower;
3898  } else // mScaleType == STLogarithmic
3899  {
3900  if (mRangeReversed)
3901  return pow(mRange.upper/mRange.lower, (mAxisRect.left()-value)/(double)mAxisRect.width())*mRange.upper;
3902  else
3903  return pow(mRange.upper/mRange.lower, (value-mAxisRect.left())/(double)mAxisRect.width())*mRange.lower;
3904  }
3905  } else // orientation() == Qt::Vertical
3906  {
3907  if (mScaleType == STLinear)
3908  {
3909  if (mRangeReversed)
3910  return -(mAxisRect.bottom()-value)/(double)mAxisRect.height()*mRange.size()+mRange.upper;
3911  else
3912  return (mAxisRect.bottom()-value)/(double)mAxisRect.height()*mRange.size()+mRange.lower;
3913  } else // mScaleType == STLogarithmic
3914  {
3915  if (mRangeReversed)
3916  return pow(mRange.upper/mRange.lower, (value-mAxisRect.bottom())/(double)mAxisRect.height())*mRange.upper;
3917  else
3918  return pow(mRange.upper/mRange.lower, (mAxisRect.bottom()-value)/(double)mAxisRect.height())*mRange.lower;
3919  }
3920  }
3921 }
3922 
3927 double QCPAxis::coordToPixel(double value) const
3928 {
3929  if (orientation() == Qt::Horizontal)
3930  {
3931  if (mScaleType == STLinear)
3932  {
3933  if (mRangeReversed)
3934  return (mRange.upper-value)/mRange.size()*mAxisRect.width()+mAxisRect.left();
3935  else
3936  return (value-mRange.lower)/mRange.size()*mAxisRect.width()+mAxisRect.left();
3937  } else // mScaleType == STLogarithmic
3938  {
3939  if (value >= 0 && mRange.upper < 0) // invalid value for logarithmic scale, just draw it outside visible range
3940  return mRangeReversed ? mAxisRect.left()-200 : mAxisRect.right()+200;
3941  else if (value <= 0 && mRange.upper > 0) // invalid value for logarithmic scale, just draw it outside visible range
3942  return mRangeReversed ? mAxisRect.right()+200 : mAxisRect.left()-200;
3943  else
3944  {
3945  if (mRangeReversed)
3946  return baseLog(mRange.upper/value)/baseLog(mRange.upper/mRange.lower)*mAxisRect.width()+mAxisRect.left();
3947  else
3948  return baseLog(value/mRange.lower)/baseLog(mRange.upper/mRange.lower)*mAxisRect.width()+mAxisRect.left();
3949  }
3950  }
3951  } else // orientation() == Qt::Vertical
3952  {
3953  if (mScaleType == STLinear)
3954  {
3955  if (mRangeReversed)
3956  return mAxisRect.bottom()-(mRange.upper-value)/mRange.size()*mAxisRect.height();
3957  else
3958  return mAxisRect.bottom()-(value-mRange.lower)/mRange.size()*mAxisRect.height();
3959  } else // mScaleType == STLogarithmic
3960  {
3961  if (value >= 0 && mRange.upper < 0) // invalid value for logarithmic scale, just draw it outside visible range
3962  return mRangeReversed ? mAxisRect.bottom()+200 : mAxisRect.top()-200;
3963  else if (value <= 0 && mRange.upper > 0) // invalid value for logarithmic scale, just draw it outside visible range
3964  return mRangeReversed ? mAxisRect.top()-200 : mAxisRect.bottom()+200;
3965  else
3966  {
3967  if (mRangeReversed)
3968  return mAxisRect.bottom()-baseLog(mRange.upper/value)/baseLog(mRange.upper/mRange.lower)*mAxisRect.height();
3969  else
3970  return mAxisRect.bottom()-baseLog(value/mRange.lower)/baseLog(mRange.upper/mRange.lower)*mAxisRect.height();
3971  }
3972  }
3973  }
3974 }
3975 
3985 {
3986  if ((!mTicks && !mTickLabels && !mGrid) || mRange.size() <= 0) return;
3987 
3988  // fill tick vectors, either by auto generating or by notifying user to fill the vectors himself
3989  if (mAutoTicks)
3990  {
3991  generateAutoTicks();
3992  } else
3993  {
3994  emit ticksRequest();
3995  }
3996 
3997  if (mTickVector->isEmpty())
3998  {
3999  mSubTickVector->clear();
4000  return;
4001  }
4002 
4003  // generate subticks between ticks:
4004  mSubTickVector->resize((mTickVector->size()-1)*mSubTickCount);
4005  if (mSubTickCount > 0)
4006  {
4007  double subTickStep = 0;
4008  double subTickPosition = 0;
4009  int subTickIndex = 0;
4010  bool done = false;
4011  for (int i=1; i<mTickVector->size(); ++i)
4012  {
4013  subTickStep = (mTickVector->at(i)-mTickVector->at(i-1))/(double)(mSubTickCount+1);
4014  for (int k=1; k<=mSubTickCount; ++k)
4015  {
4016  subTickPosition = mTickVector->at(i-1) + k*subTickStep;
4017  if (subTickPosition < mRange.lower)
4018  continue;
4019  if (subTickPosition > mRange.upper)
4020  {
4021  done = true;
4022  break;
4023  }
4024  (*mSubTickVector)[subTickIndex] = subTickPosition;
4025  subTickIndex++;
4026  }
4027  if (done) break;
4028  }
4029  mSubTickVector->resize(subTickIndex);
4030  }
4031 
4032  // generate tick labels according to tick positions:
4033  mExponentialChar = mParentPlot->locale().exponential(); // will be needed when drawing the numbers generated here, in drawTickLabel()
4034  mPositiveSignChar = mParentPlot->locale().positiveSign(); // will be needed when drawing the numbers generated here, in drawTickLabel()
4035  if (mAutoTickLabels)
4036  {
4037  int vecsize = mTickVector->size();
4038  mTickVectorLabels->resize(vecsize);
4039  if (mTickLabelType == LTNumber)
4040  {
4041  for (int i=0; i<vecsize; ++i)
4042  (*mTickVectorLabels)[i] = mParentPlot->locale().toString(mTickVector->at(i), mNumberFormatChar, mNumberPrecision);
4043  } else if (mTickLabelType == LTDateTime)
4044  {
4045  for (int i=0; i<vecsize; ++i)
4046  (*mTickVectorLabels)[i] = mParentPlot->locale().toString(QDateTime::fromTime_t(mTickVector->at(i)), mDateTimeFormat);
4047  }
4048  } else // mAutoTickLabels == false
4049  {
4050  if (mAutoTicks) // ticks generated automatically, but not ticklabels, so emit ticksRequest here for labels
4051  {
4052  emit ticksRequest();
4053  }
4054  // make sure provided tick label vector has correct (minimal) length:
4055  if (mTickVectorLabels->size() < mTickVector->size())
4056  mTickVectorLabels->resize(mTickVector->size());
4057  }
4058 }
4059 
4070 {
4071  if (mScaleType == STLinear)
4072  {
4073  if (mAutoTickStep)
4074  {
4075  // Generate tick positions according to linear scaling:
4076  mTickStep = mRange.size()/(double)mAutoTickCount; // mAutoTickCount ticks on average
4077  double magnitudeFactor = pow(10, (int)floor(log10(mTickStep))); // get magnitude factor e.g. 0.01, 1, 10, 1000 etc.
4078  double tickStepMantissa = mTickStep/magnitudeFactor;
4079  if (tickStepMantissa < 5)
4080  {
4081  // round digit after decimal point to 0.5
4082  mTickStep = (int)(tickStepMantissa*2)/2.0*magnitudeFactor;
4083  } else
4084  {
4085  // round to first digit in multiples of 2
4086  mTickStep = (int)((tickStepMantissa/10.0)*5)/5.0*10*magnitudeFactor;
4087  }
4088  }
4089  if (mAutoSubTicks)
4090  mSubTickCount = calculateAutoSubTickCount(mTickStep);
4091  // Generate tick positions according to mTickStep:
4092  int firstStep = floor(mRange.lower/mTickStep);
4093  int lastStep = ceil(mRange.upper/mTickStep);
4094  int tickcount = lastStep-firstStep+1;
4095  if (tickcount < 0) tickcount = 0;
4096  mTickVector->resize(tickcount);
4097  for (int i=0; i<tickcount; ++i)
4098  {
4099  (*mTickVector)[i] = (firstStep+i)*mTickStep;
4100  }
4101  } else // mScaleType == STLogarithmic
4102  {
4103  // Generate tick positions according to logbase scaling:
4104  if (mRange.lower > 0 && mRange.upper > 0) // positive range
4105  {
4106  double lowerMag = basePow((int)floor(baseLog(mRange.lower)));
4107  double currentMag = lowerMag;
4108  mTickVector->clear();
4109  mTickVector->append(currentMag);
4110  while (currentMag < mRange.upper && currentMag > 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
4111  {
4112  currentMag *= mScaleLogBase;
4113  mTickVector->append(currentMag);
4114  }
4115  } else if (mRange.lower < 0 && mRange.upper < 0) // negative range
4116  {
4117  double lowerMag = -basePow((int)ceil(baseLog(-mRange.lower)));
4118  double currentMag = lowerMag;
4119  mTickVector->clear();
4120  mTickVector->append(currentMag);
4121  while (currentMag < mRange.upper && currentMag < 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
4122  {
4123  currentMag /= mScaleLogBase;
4124  mTickVector->append(currentMag);
4125  }
4126  } else // invalid range for logarithmic scale, because lower and upper have different sign
4127  {
4128  mTickVector->clear();
4129  qDebug() << FUNCNAME << "Invalid range for logarithmic plot: " << mRange.lower << "-" << mRange.upper;
4130  }
4131  }
4132 }
4133 
4147 int QCPAxis::calculateAutoSubTickCount(double tickStep) const
4148 {
4149  int result = mSubTickCount; // default to current setting, if no proper value can be found
4150 
4151  // get mantissa of tickstep:
4152  double magnitudeFactor = pow(10, (int)floor(log10(tickStep))); // get magnitude factor e.g. 0.01, 1, 10, 1000 etc.
4153  double tickStepMantissa = tickStep/magnitudeFactor;
4154 
4155  // separate integer and fractional part of mantissa:
4156  double epsilon = 0.01;
4157  double intPartf;
4158  int intPart;
4159  double fracPart = modf(tickStepMantissa, &intPartf);
4160  intPart = intPartf;
4161 
4162  // handle cases with (almost) integer mantissa:
4163  if (fracPart < epsilon || 1.0-fracPart < epsilon)
4164  {
4165  if (1.0-fracPart < epsilon)
4166  intPart++;
4167  switch (intPart)
4168  {
4169  case 1: result = 4; break; // 1.0 -> 0.2 substep
4170  case 2: result = 3; break; // 2.0 -> 0.5 substep
4171  case 3: result = 2; break; // 3.0 -> 1.0 substep
4172  case 4: result = 3; break; // 4.0 -> 1.0 substep
4173  case 5: result = 4; break; // 5.0 -> 1.0 substep
4174  case 6: result = 2; break; // 6.0 -> 2.0 substep
4175  case 7: result = 6; break; // 7.0 -> 1.0 substep
4176  case 8: result = 3; break; // 8.0 -> 2.0 substep
4177  case 9: result = 2; break; // 9.0 -> 3.0 substep
4178  }
4179  } else
4180  {
4181  // handle cases with significantly fractional mantissa:
4182  if (qAbs(fracPart-0.5) < epsilon) // *.5 mantissa
4183  {
4184  switch (intPart)
4185  {
4186  case 1: result = 2; break; // 1.5 -> 0.5 substep
4187  case 2: result = 4; break; // 2.5 -> 0.5 substep
4188  case 3: result = 4; break; // 3.5 -> 0.7 substep
4189  case 4: result = 2; break; // 4.5 -> 1.5 substep
4190  case 5: result = 4; break; // 5.5 -> 1.1 substep (won't occur with autoTickStep from here on)
4191  case 6: result = 4; break; // 6.5 -> 1.3 substep
4192  case 7: result = 2; break; // 7.5 -> 2.5 substep
4193  case 8: result = 4; break; // 8.5 -> 1.7 substep
4194  case 9: result = 4; break; // 9.5 -> 1.9 substep
4195  }
4196  }
4197  // if mantissa fraction isnt 0.0 or 0.5, don't bother finding good sub tick marks, leave default
4198  }
4199 
4200  return result;
4201 }
4202 
4209 void QCPAxis::drawGrid(QPainter *painter)
4210 {
4211  if (!mVisible || (!mGrid && mZeroLinePen.style() == Qt::NoPen)) return;
4212  painter->save();
4213  int lowTick, highTick;
4214  visibleTickBounds(lowTick, highTick);
4215  int t; // helper variable, result of coordinate-to-pixel transforms
4216  if (orientation() == Qt::Horizontal)
4217  {
4218  // draw zeroline:
4219  int zeroLineIndex = -1;
4220  if (mZeroLinePen.style() != Qt::NoPen && mRange.lower < 0 && mRange.upper > 0)
4221  {
4222  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEZeroLine));
4223  painter->setPen(mZeroLinePen);
4224  double epsilon = mRange.size()*1E-6; // for comparing double to zero
4225  for (int i=lowTick; i <= highTick; ++i)
4226  {
4227  if (fabs(mTickVector->at(i)) < epsilon)
4228  {
4229  zeroLineIndex = i;
4230  t = coordToPixel(mTickVector->at(i)); // x
4231  painter->drawLine(t, mAxisRect.bottom(), t, mAxisRect.top());
4232  break;
4233  }
4234  }
4235  }
4236  // draw grid lines:
4237  if (mGrid)
4238  {
4239  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGrid));
4240  painter->setPen(mGridPen);
4241  for (int i=lowTick; i <= highTick; ++i)
4242  {
4243  if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline
4244  t = coordToPixel(mTickVector->at(i)); // x
4245  painter->drawLine(t, mAxisRect.bottom(), t, mAxisRect.top());
4246  }
4247  }
4248  } else
4249  {
4250  // draw zeroline:
4251  int zeroLineIndex = -1;
4252  if (mZeroLinePen.style() != Qt::NoPen && mRange.lower < 0 && mRange.upper > 0)
4253  {
4254  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEZeroLine));
4255  painter->setPen(mZeroLinePen);
4256  double epsilon = mRange.size()*1E-6; // for comparing double to zero
4257  for (int i=lowTick; i <= highTick; ++i)
4258  {
4259  if (fabs(mTickVector->at(i)) < epsilon)
4260  {
4261  zeroLineIndex = i;
4262  t = coordToPixel(mTickVector->at(i)); // y
4263  painter->drawLine(mAxisRect.left(), t, mAxisRect.right(), t);
4264  break;
4265  }
4266  }
4267  }
4268  // draw grid lines:
4269  if (mGrid)
4270  {
4271  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGrid));
4272  painter->setPen(mGridPen);
4273  for (int i=lowTick; i <= highTick; ++i)
4274  {
4275  if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline
4276  t = coordToPixel(mTickVector->at(i)); // y
4277  painter->drawLine(mAxisRect.left(), t, mAxisRect.right(), t);
4278  }
4279  }
4280  }
4281  painter->restore();
4282 }
4283 
4289 void QCPAxis::drawSubGrid(QPainter *painter)
4290 {
4291  if (!mVisible || !mSubGrid || !mGrid) return;
4292  painter->save();
4293  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AESubGrid));
4294 
4295  int t; // helper variable, result of coordinate-to-pixel transforms
4296  painter->setPen(mSubGridPen);
4297  if (orientation() == Qt::Horizontal)
4298  {
4299  for (int i=0; i<mSubTickVector->size(); ++i)
4300  {
4301  t = coordToPixel(mSubTickVector->at(i)); // x
4302  painter->drawLine(t, mAxisRect.bottom(), t, mAxisRect.top());
4303  }
4304  } else
4305  {
4306  for (int i=0; i<mSubTickVector->size(); ++i)
4307  {
4308  t = coordToPixel(mSubTickVector->at(i)); // y
4309  painter->drawLine(mAxisRect.left(), t, mAxisRect.right(), t);
4310  }
4311  }
4312  painter->restore();
4313 }
4314 
4320 void QCPAxis::drawAxis(QPainter *painter)
4321 {
4322  if (!mVisible) return;
4323  painter->save();
4324  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEAxes));
4325  QPoint origin;
4326  if (mAxisType == ATLeft)
4327  origin = mAxisRect.bottomLeft();
4328  else if (mAxisType == ATRight)
4329  origin = mAxisRect.bottomRight();
4330  else if (mAxisType == ATTop)
4331  origin = mAxisRect.topLeft();
4332  else if (mAxisType == ATBottom)
4333  origin = mAxisRect.bottomLeft();
4334 
4335  int xCor = 0, yCor = 0; // paint system correction, for pixel exact matches (affects baselines and ticks of top/right axes)
4336  if (mAxisType == ATTop)
4337  yCor = -1;
4338  else if (mAxisType == ATRight)
4339  xCor = 1;
4340 
4341  int margin = 0;
4342  int lowTick, highTick;
4343  visibleTickBounds(lowTick, highTick);
4344  int t; // helper variable, result of coordinate-to-pixel transforms
4345 
4346  /* draw axes */
4347  // baselines:
4348  painter->setPen(mBasePen);
4349  if (orientation() == Qt::Horizontal)
4350  painter->drawLine(origin+QPoint(xCor, yCor), origin+QPoint(mAxisRect.width()+xCor, yCor));
4351  else
4352  painter->drawLine(origin+QPoint(xCor, yCor), origin+QPoint(xCor, -mAxisRect.height()+yCor));
4353 
4354  // ticks:
4355  if (mTicks)
4356  {
4357  painter->setPen(mTickPen);
4358  // direction of ticks ("inward" is right for left axis and left for right axis)
4359  int tickDir = (mAxisType == ATBottom || mAxisType == ATRight) ? -1 : 1;
4360  if (orientation() == Qt::Horizontal)
4361  {
4362  for (int i=lowTick; i <= highTick; ++i)
4363  {
4364  t = coordToPixel(mTickVector->at(i)); // x
4365  painter->drawLine(t+xCor, origin.y()-mTickLengthOut*tickDir+yCor, t+xCor, origin.y()+mTickLengthIn*tickDir+yCor);
4366  }
4367  } else
4368  {
4369  for (int i=lowTick; i <= highTick; ++i)
4370  {
4371  t = coordToPixel(mTickVector->at(i)); // y
4372  painter->drawLine(origin.x()-mTickLengthOut*tickDir+xCor, t+yCor, origin.x()+mTickLengthIn*tickDir+xCor, t+yCor);
4373  }
4374  }
4375  }
4376 
4377  // subticks:
4378  if (mTicks && mSubTickCount > 0)
4379  {
4380  painter->setPen(mSubTickPen);
4381  // direction of ticks ("inward" is right for left axis and left for right axis)
4382  int tickDir = (mAxisType == ATBottom || mAxisType == ATRight) ? -1 : 1;
4383  if (orientation() == Qt::Horizontal)
4384  {
4385  for (int i=0; i<mSubTickVector->size(); ++i) // no need to check bounds because subticks are always only created inside current mRange
4386  {
4387  t = coordToPixel(mSubTickVector->at(i));
4388  painter->drawLine(t+xCor, origin.y()-mSubTickLengthOut*tickDir+yCor, t+xCor, origin.y()+mSubTickLengthIn*tickDir+yCor);
4389  }
4390  } else
4391  {
4392  for (int i=0; i<mSubTickVector->size(); ++i)
4393  {
4394  t = coordToPixel(mSubTickVector->at(i));
4395  painter->drawLine(origin.x()-mSubTickLengthOut*tickDir+xCor, t+yCor, origin.x()+mSubTickLengthIn*tickDir+xCor, t+yCor);
4396  }
4397  }
4398  }
4399  margin += qMax(0, qMax(mTickLengthOut, mSubTickLengthOut));
4400 
4401  // tick labels:
4402  QSize tickLabelsSize; // size of largest tick label, for offset calculation of axis label
4403  if (mTickLabels)
4404  {
4405  margin += mTickLabelPadding;
4406  painter->setFont(mTickLabelFont);
4407  for (int i=lowTick; i <= highTick; ++i)
4408  {
4409  t = coordToPixel(mTickVector->at(i));
4410  drawTickLabel(painter, t, margin, mTickVectorLabels->at(i), &tickLabelsSize);
4411  }
4412  }
4413  if (orientation() == Qt::Horizontal)
4414  margin += tickLabelsSize.height();
4415  else
4416  margin += tickLabelsSize.width();
4417 
4418  // axis label:
4419  if (!mLabel.isEmpty())
4420  {
4421  margin += mLabelPadding;
4422  painter->setFont(mLabelFont);
4423  QRect bounds;
4424  bounds = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip, mLabel);
4425  if (mAxisType == ATLeft)
4426  {
4427  QTransform oldTransform = painter->transform();
4428  painter->translate((origin.x()-margin-bounds.height()), origin.y());
4429  painter->rotate(-90);
4430  painter->drawText(0, 0, mAxisRect.height(), bounds.height(), Qt::TextDontClip | Qt::AlignCenter, mLabel);
4431  painter->setTransform(oldTransform);
4432  }
4433  else if (mAxisType == ATRight)
4434  {
4435  QTransform oldTransform = painter->transform();
4436  painter->translate((origin.x()+margin+bounds.height()), origin.y()-mAxisRect.height());
4437  painter->rotate(90);
4438  painter->drawText(0, 0, mAxisRect.height(), bounds.height(), Qt::TextDontClip | Qt::AlignCenter, mLabel);
4439  painter->setTransform(oldTransform);
4440  }
4441  else if (mAxisType == ATTop)
4442  painter->drawText(origin.x(), origin.y()-margin-bounds.height(), mAxisRect.width(), bounds.height(), Qt::TextDontClip | Qt::AlignCenter, mLabel);
4443  else if (mAxisType == ATBottom)
4444  painter->drawText(origin.x(), origin.y()+margin, mAxisRect.width(), bounds.height(), Qt::TextDontClip | Qt::AlignCenter, mLabel);
4445  }
4446 
4447  painter->restore();
4448 }
4449 
4468 void QCPAxis::drawTickLabel(QPainter *painter, double position, int distanceToAxis, const QString &text, QSize *tickLabelsSize)
4469 {
4470  // warning: if you change anything here, also adapt getMaxTickLabelSize() accordingly!
4471 
4472  // determine whether beautiful decimal powers should be used
4473  bool useBeautifulPowers = false;
4474  int ePos = -1;
4475  if (mAutoTickLabels && mNumberBeautifulPowers && mTickLabelType == LTNumber)
4476  {
4477  ePos = text.indexOf('e');
4478  if (ePos > -1)
4479  useBeautifulPowers = true;
4480  }
4481 
4482  // calculate text bounding rects and do string preparation for beautiful decimal powers:
4483  QRect bounds, baseBounds, expBounds;
4484  QString basePart, expPart;
4485  QFont expFont;
4486  if (useBeautifulPowers)
4487  {
4488  // split string parts for part of number/symbol that will be drawn normally and part that will be drawn as exponent:
4489  basePart = text.left(ePos);
4490  // in log scaling, we want to turn "1*10^n" into "10^n", else add multiplication sign and decimal base:
4491  if (mScaleType == STLogarithmic && basePart == "1")
4492  basePart = "10";
4493  else
4494  basePart += (mNumberMultiplyCross ? QString(QChar(215)) : QString(QChar(183))) + "10";
4495  expPart = text.mid(ePos+1);
4496  // clip "+" and leading zeros off expPart:
4497  while (expPart.at(1) == '0' && expPart.length() > 2) // length > 2 so we leave one zero when numberFormatChar is 'e'
4498  expPart.remove(1, 1);
4499  if (expPart.at(0) == mPositiveSignChar)
4500  expPart.remove(0, 1);
4501  // prepare smaller font for exponent:
4502  expFont = painter->font();
4503  expFont.setPointSize(expFont.pointSize()*0.75);
4504  // calculate bounding rects of base part, exponent part and total one:
4505  baseBounds = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip, basePart);
4506  QFontMetrics expFontMetrics(expFont);
4507  expBounds = expFontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip, expPart);
4508  bounds = baseBounds.adjusted(0, 0, expBounds.width(), 0);
4509  } else // useBeautifulPowers == false
4510  {
4511  bounds = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter, text);
4512  }
4513 
4514  // if using rotated tick labels, transform bounding rect, too:
4515  QRect rotatedBounds = bounds;
4516  if (!qFuzzyCompare(mTickLabelRotation+1.0, 1.0))
4517  {
4518  QTransform transform;
4519  transform.rotate(mTickLabelRotation);
4520  rotatedBounds = transform.mapRect(bounds);
4521  }
4522  // expand passed tickLabelsSize if current tick label is larger:
4523  if (rotatedBounds.width() > tickLabelsSize->width())
4524  tickLabelsSize->setWidth(rotatedBounds.width());
4525  if (rotatedBounds.height() > tickLabelsSize->height())
4526  tickLabelsSize->setHeight(rotatedBounds.height());
4527 
4528  // calculate coordinates (non-trivial, for best visual appearance):
4529  // short explanation for bottom axis: The anchor, i.e. the point in the label that is placed horizontally under the
4530  // corresponding tick is always on the label side that is closer to the axis (e.g. the left side of the text when we're
4531  // rotating clockwise). On that side, the height-edge is halved and the resulting median point is defined the anchor. This way,
4532  // a 90 degree rotated text will be centered under the tick (i.e. displaced horizontally by half its height). At the
4533  // same time, a 45 degree rotated text will "point toward" its tick, as is typical for rotated tick labels.
4534  bool doRotation = !qFuzzyCompare(mTickLabelRotation+1.0, 1.0);
4535  double angle = mTickLabelRotation/180.0*M_PI;
4536  int x=0,y=0;
4537  if (mAxisType == ATLeft)
4538  {
4539  if (doRotation)
4540  {
4541  if (mTickLabelRotation > 0)
4542  {
4543  x = mAxisRect.left()-cos(angle)*bounds.width()-distanceToAxis;
4544  y = position-sin(angle)*bounds.width()-cos(angle)*bounds.height()/2.0;
4545  } else
4546  {
4547  x = mAxisRect.left()-cos(-angle)*bounds.width()-sin(-angle)*bounds.height()-distanceToAxis;
4548  y = position+sin(-angle)*bounds.width()-cos(-angle)*bounds.height()/2.0;
4549  }
4550  } else
4551  {
4552  x = mAxisRect.left()-bounds.width()-distanceToAxis;
4553  y = position-bounds.height()/2.0;
4554  }
4555  } else if (mAxisType == ATRight)
4556  {
4557  if (doRotation)
4558  {
4559  if (mTickLabelRotation > 0)
4560  {
4561  x = mAxisRect.right()+sin(angle)*bounds.height()+distanceToAxis;
4562  y = position-cos(angle)*bounds.height()/2.0;
4563  } else
4564  {
4565  x = mAxisRect.right()+distanceToAxis;
4566  y = position-cos(-angle)*bounds.height()/2.0;
4567  }
4568  } else
4569  {
4570  x = mAxisRect.right()+distanceToAxis;
4571  y = position-bounds.height()/2.0;
4572  }
4573  } else if (mAxisType == ATTop)
4574  {
4575  if (doRotation)
4576  {
4577  if (mTickLabelRotation > 0)
4578  {
4579  x = position-cos(angle)*bounds.width()+sin(angle)*bounds.height()/2.0;
4580  y = mAxisRect.top()-sin(angle)*bounds.width()-cos(angle)*bounds.height()-distanceToAxis;
4581  } else
4582  {
4583  x = position-sin(-angle)*bounds.height()/2.0;
4584  y = mAxisRect.top()-cos(-angle)*bounds.height()-distanceToAxis;
4585  }
4586  } else
4587  {
4588  x = position-bounds.width()/2.0;
4589  y = mAxisRect.top()-bounds.height()-distanceToAxis;
4590  }
4591  } else if (mAxisType == ATBottom)
4592  {
4593  if (doRotation)
4594  {
4595  if (mTickLabelRotation > 0)
4596  {
4597  x = position+sin(angle)*bounds.height()/2.0;
4598  y = mAxisRect.bottom()+distanceToAxis;
4599  } else
4600  {
4601  x = position-cos(-angle)*bounds.width()-sin(-angle)*bounds.height()/2.0;
4602  y = mAxisRect.bottom()+sin(-angle)*bounds.width()+distanceToAxis;
4603  }
4604  } else
4605  {
4606  x = position-bounds.width()/2.0;
4607  y = mAxisRect.bottom()+distanceToAxis;
4608  }
4609  }
4610 
4611  // if label would be partly clipped by widget border on sides, don't draw it:
4612  if (orientation() == Qt::Horizontal)
4613  {
4614  if (x+bounds.width() > mParentPlot->mViewport.right() ||
4615  x < mParentPlot->mViewport.left())
4616  return;
4617  } else
4618  {
4619  if (y+bounds.height() > mParentPlot->mViewport.bottom() ||
4620  y < mParentPlot->mViewport.top())
4621  return;
4622  }
4623 
4624  // transform painter to position/rotation:
4625  QTransform oldTransform = painter->transform();
4626  painter->translate(x, y);
4627  if (doRotation)
4628  painter->rotate(mTickLabelRotation);
4629  // draw text:
4630  if (useBeautifulPowers)
4631  {
4632  // draw base:
4633  painter->drawText(0, 0, 0, 0, Qt::TextDontClip, basePart);
4634  // draw exponent:
4635  painter->setFont(expFont);
4636  painter->drawText(baseBounds.width()+1, 0, expBounds.width(), expBounds.height(), Qt::TextDontClip, expPart);
4637  painter->setFont(mTickLabelFont);
4638  } else // useBeautifulPowers == false
4639  {
4640  painter->drawText(0, 0, bounds.width(), bounds.height(), Qt::TextDontClip | Qt::AlignHCenter, text);
4641  }
4642 
4643  // reset rotation/translation transform to what it was before:
4644  painter->setTransform(oldTransform);
4645 }
4646 
4655 void QCPAxis::getMaxTickLabelSize(const QFont &font, const QString &text, QSize *tickLabelsSize) const
4656 {
4657  // This function does the same as drawTickLabel but omits the actual drawing
4658  // changes involve creating extra QFontMetrics instances for font, since painter->fontMetrics() isn't available
4659 
4660  // determine whether beautiful powers should be used
4661  bool useBeautifulPowers = false;
4662  int ePos=-1;
4663  if (mAutoTickLabels && mNumberBeautifulPowers && mTickLabelType == LTNumber)
4664  {
4665  ePos = text.indexOf(mExponentialChar);
4666  if (ePos > -1)
4667  useBeautifulPowers = true;
4668  }
4669 
4670  // calculate and draw text, depending on whether beautiful powers are applicable or not:
4671  QRect bounds, baseBounds, expBounds;
4672  QString basePart, expPart;
4673  QFont expFont;
4674  if (useBeautifulPowers)
4675  {
4676  // split string parts for part of number/symbol that will be drawn normally and part that will be drawn as exponent:
4677  basePart = text.left(ePos);
4678  // in log scaling, we want to turn "1*10^n" into "10^n", else add multiplication sign and decimal base:
4679  if (mScaleType == STLogarithmic && basePart == "1")
4680  basePart = "10";
4681  else
4682  basePart += (mNumberMultiplyCross ? QString(QChar(215)) : QString(QChar(183))) + "10";
4683  expPart = text.mid(ePos+1);
4684  // clip "+" and leading zeros off expPart:
4685  while (expPart.at(1) == '0' && expPart.length() > 2) // length > 2 so we leave one zero when numberFormatChar is 'e'
4686  expPart.remove(1, 1);
4687  if (expPart.at(0) == mPositiveSignChar)
4688  expPart.remove(0, 1);
4689  // prepare smaller font for exponent:
4690  expFont = font;
4691  expFont.setPointSize(expFont.pointSize()*0.75);
4692  // calculate bounding rects of base part, exponent part and total one:
4693  QFontMetrics baseFontMetrics(font);
4694  baseBounds = baseFontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip, basePart);
4695  QFontMetrics expFontMetrics(expFont);
4696  expBounds = expFontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip, expPart);
4697  bounds = baseBounds.adjusted(0, 0, expBounds.width(), 0);
4698  } else // useBeautifulPowers == false
4699  {
4700  QFontMetrics fontMetrics(font);
4701  bounds = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter, text);
4702  }
4703 
4704  // if rotated tick labels, transform bounding rect, too:
4705  QRect rotatedBounds = bounds;
4706  if (!qFuzzyCompare(mTickLabelRotation+1.0, 1.0))
4707  {
4708  QTransform transform;
4709  transform.rotate(mTickLabelRotation);
4710  rotatedBounds = transform.mapRect(bounds);
4711  }
4712  // expand passed tickLabelsSize if current tick label is larger:
4713  if (rotatedBounds.width() > tickLabelsSize->width())
4714  tickLabelsSize->setWidth(rotatedBounds.width());
4715  if (rotatedBounds.height() > tickLabelsSize->height())
4716  tickLabelsSize->setHeight(rotatedBounds.height());
4717 }
4718 
4728 void QCPAxis::visibleTickBounds(int &lowIndex, int &highIndex) const
4729 {
4730  lowIndex = 0;
4731  highIndex = -1;
4732  // make sure only ticks that are in visible range are returned
4733  for (int i=0; i < mTickVector->size(); ++i)
4734  {
4735  lowIndex = i;
4736  if (mTickVector->at(i) >= mRange.lower) break;
4737  }
4738  for (int i=mTickVector->size()-1; i >= 0; --i)
4739  {
4740  highIndex = i;
4741  if (mTickVector->at(i) <= mRange.upper) break;
4742  }
4743 }
4744 
4752 double QCPAxis::baseLog(double value) const
4753 {
4754  return log(value)*mScaleLogBaseLogInv;
4755 }
4756 
4763 double QCPAxis::basePow(double value) const
4764 {
4765  return pow(mScaleLogBase, value);
4766 }
4767 
4782 {
4783  // run through similar steps as QCPAxis::drawAxis, and caluclate margin needed to fit axis and its labels
4784  int margin = 0;
4785 
4786  if (mVisible)
4787  {
4788  int lowTick, highTick;
4789  visibleTickBounds(lowTick, highTick);
4790 
4791  // get length of tick marks reaching outside axis rect:
4792  margin += qMax(0, qMax(mTickLengthOut, mSubTickLengthOut));
4793  // calculate size of tick labels:
4794  QSize tickLabelsSize(0, 0);
4795  if (mTickLabels)
4796  {
4797  for (int i=lowTick; i <= highTick; ++i)
4798  {
4799  getMaxTickLabelSize(mTickLabelFont, mTickVectorLabels->at(i), &tickLabelsSize);
4800  }
4801  if (orientation() == Qt::Horizontal)
4802  margin += tickLabelsSize.height() + mTickLabelPadding;
4803  else
4804  margin += tickLabelsSize.width() + mTickLabelPadding;
4805  }
4806 
4807  // calculate size of axis label (only height needed, because left/right labels are rotated by 90 degrees):
4808  if (!mLabel.isEmpty())
4809  {
4810  QFontMetrics fontMetrics(mLabelFont);
4811  QRect bounds;
4812  bounds = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter | Qt::AlignVCenter, mLabel);
4813  margin += bounds.height() + mLabelPadding;
4814  }
4815  }
4816 
4817  margin += mPadding;
4818 
4819  if (margin < 15) // need a bit of margin if no axis text is shown at all (i.e. only baseline and tick lines, or no axis at all)
4820  margin = 15;
4821  return margin;
4822 }
4823 
4824 
4825 // ================================================================================
4826 // =================== QCustomPlot
4827 // ================================================================================
4828 
4842 QCustomPlot::QCustomPlot(QWidget *parent) :
4843  QWidget(parent)
4844 {
4845  setMouseTracking(true);
4846  QLocale currentLocale = locale();
4847  currentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
4848  setLocale(currentLocale);
4849 
4850  buffer = QPixmap(size());
4851  mViewport = rect();
4852  mDragging = false;
4853  QFont titleFont;
4854  titleFont.setPointSize(14);
4855  titleFont.setBold(true);
4856  setTitleFont(titleFont);
4857  setTitle("");
4858  setColor(Qt::white);
4859  setAntialiasedElements(AEGraphs | AEScatters | AEFills);
4860  legend = new QCPLegend(this);
4861  legend->setVisible(false);
4862  setAutoAddPlottableToLegend(true);
4863  xAxis = new QCPAxis(this, QCPAxis::ATBottom);
4864  yAxis = new QCPAxis(this, QCPAxis::ATLeft);
4865  xAxis2 = new QCPAxis(this, QCPAxis::ATTop);
4866  yAxis2 = new QCPAxis(this, QCPAxis::ATRight);
4867  xAxis2->setGrid(false);
4868  yAxis2->setGrid(false);
4869  xAxis2->setZeroLinePen(Qt::NoPen);
4870  yAxis2->setZeroLinePen(Qt::NoPen);
4871  xAxis2->setVisible(false);
4872  yAxis2->setVisible(false);
4873  setAxisBackground(QPixmap());
4874  setAxisBackgroundScaled(true);
4875  setAxisBackgroundScaledMode(Qt::KeepAspectRatioByExpanding);
4876 
4877  setRangeDragAxes(xAxis, yAxis);
4878  setRangeZoomAxes(xAxis, yAxis);
4879  setRangeDrag(0);
4880  setRangeZoom(0);
4881  setRangeZoomFactor(0.85);
4882 
4883  setMargin(0, 0, 0, 0);
4884  setAutoMargin(true);
4885  replot();
4886 }
4887 
4888 QCustomPlot::~QCustomPlot()
4889 {
4890  clearPlottables();
4891  delete legend;
4892  delete xAxis;
4893  delete yAxis;
4894  delete xAxis2;
4895  delete yAxis2;
4896 }
4897 
4902 QCPAxis *QCustomPlot::rangeDragAxis(Qt::Orientation orientation)
4903 {
4904  return (orientation == Qt::Horizontal ? mRangeDragHorzAxis : mRangeDragVertAxis);
4905 }
4906 
4911 QCPAxis *QCustomPlot::rangeZoomAxis(Qt::Orientation orientation)
4912 {
4913  return (orientation == Qt::Horizontal ? mRangeZoomHorzAxis : mRangeZoomVertAxis);
4914 }
4915 
4920 double QCustomPlot::rangeZoomFactor(Qt::Orientation orientation)
4921 {
4922  return (orientation == Qt::Horizontal ? mRangeZoomFactorHorz : mRangeZoomFactorVert);
4923 }
4924 
4931 void QCustomPlot::setTitle(const QString &title)
4932 {
4933  mTitle = title;
4934 }
4935 
4941 {
4942  mTitleFont = font;
4943 }
4944 
4955 void QCustomPlot::setAxisRect(const QRect &arect)
4956 {
4957  mMarginLeft = arect.left()-mViewport.left();
4958  mMarginRight = mViewport.right()-arect.right();
4959  mMarginTop = arect.top()-mViewport.top();
4960  mMarginBottom = mViewport.bottom()-arect.bottom();
4961  updateAxisRect();
4962 }
4963 
4969 {
4970  mMarginLeft = margin;
4971  updateAxisRect();
4972 }
4973 
4979 {
4980  mMarginRight = margin;
4981  updateAxisRect();
4982 }
4983 
4989 {
4990  mMarginTop = margin;
4991  updateAxisRect();
4992 }
4993 
4999 {
5000  mMarginBottom = margin;
5001  updateAxisRect();
5002 }
5003 
5011 void QCustomPlot::setMargin(int left, int right, int top, int bottom)
5012 {
5013  mMarginLeft = left;
5014  mMarginRight = right;
5015  mMarginTop = top;
5016  mMarginBottom = bottom;
5017  updateAxisRect();
5018 }
5019 
5026 void QCustomPlot::setAutoMargin(bool enabled)
5027 {
5028  mAutoMargin = enabled;
5029 }
5030 
5034 void QCustomPlot::setColor(const QColor &color)
5035 {
5036  mColor = color;
5037 }
5038 
5048 void QCustomPlot::setRangeDrag(Qt::Orientations orientations)
5049 {
5050  mRangeDrag = orientations;
5051 }
5052 
5062 void QCustomPlot::setRangeZoom(Qt::Orientations orientations)
5063 {
5064  mRangeZoom = orientations;
5065 }
5066 
5071 void QCustomPlot::setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical)
5072 {
5073  if (horizontal)
5074  mRangeDragHorzAxis = horizontal;
5075  if (vertical)
5076  mRangeDragVertAxis = vertical;
5077 }
5078 
5084 void QCustomPlot::setRangeZoomAxes(QCPAxis *horizontal, QCPAxis *vertical)
5085 {
5086  if (horizontal)
5087  mRangeZoomHorzAxis = horizontal;
5088  if (vertical)
5089  mRangeZoomVertAxis = vertical;
5090 }
5091 
5100 void QCustomPlot::setRangeZoomFactor(double horizontalFactor, double verticalFactor)
5101 {
5102  mRangeZoomFactorHorz = horizontalFactor;
5103  mRangeZoomFactorVert = verticalFactor;
5104 }
5105 
5114 {
5115  mRangeZoomFactorHorz = factor;
5116  mRangeZoomFactorVert = factor;
5117 }
5118 
5122 void QCustomPlot::setAntialiasedElements(const AntialiasedElements &antialiasedElements)
5123 {
5124  mAntialiasedElements = antialiasedElements;
5125 }
5126 
5130 void QCustomPlot::setAntialiasedElement(AntialiasedElement antialiasedElement, bool enabled)
5131 {
5132  if (!enabled && mAntialiasedElements.testFlag(antialiasedElement))
5133  mAntialiasedElements &= ~antialiasedElement;
5134  else if (enabled && !mAntialiasedElements.testFlag(antialiasedElement))
5135  mAntialiasedElements |= antialiasedElement;
5136 }
5137 
5145 {
5146  mAutoAddPlottableToLegend = on;
5147 }
5148 
5157 void QCustomPlot::setAxisBackground(const QPixmap &pm)
5158 {
5159  mAxisBackground = pm;
5160  mScaledAxisBackground = QPixmap();
5161 }
5162 
5168 void QCustomPlot::setAxisBackground(const QPixmap &pm, bool scaled, Qt::AspectRatioMode mode)
5169 {
5170  mAxisBackground = pm;
5171  mScaledAxisBackground = QPixmap();
5172  mAxisBackgroundScaled = scaled;
5173  mAxisBackgroundScaledMode = mode;
5174 }
5175 
5188 {
5189  mAxisBackgroundScaled = scaled;
5190 }
5191 
5197 void QCustomPlot::setAxisBackgroundScaledMode(Qt::AspectRatioMode mode)
5198 {
5199  mAxisBackgroundScaledMode = mode;
5200 }
5201 
5211 {
5212  if (index >= 0 && index < mPlottables.size())
5213  {
5214  return mPlottables.at(index);
5215  } else
5216  {
5217  qDebug() << FUNCNAME << "index out of bounds:" << index;
5218  return 0;
5219  }
5220 }
5221 
5230 {
5231  if (!mPlottables.isEmpty())
5232  {
5233  return mPlottables.last();
5234  } else
5235  return 0;
5236 }
5237 
5249 {
5250  if (!mPlottables.contains(plottable) && plottable->parentPlot() == this)
5251  {
5252  mPlottables.append(plottable);
5253  // possibly add plottable to legend:
5254  if (mAutoAddPlottableToLegend)
5255  plottable->addToLegend();
5256  // special handling for QCPGraphs to maintain the simple graph interface:
5257  if (QCPGraph *graph = dynamic_cast<QCPGraph*>(plottable))
5258  mGraphs.append(graph);
5259  return true;
5260  } else
5261  {
5262  qDebug() << FUNCNAME << "plottable either already in list or not created with this QCustomPlot as parent:" << plottable;
5263  return false;
5264  }
5265 }
5266 
5275 {
5276  if (mPlottables.contains(plottable))
5277  {
5278  // remove plottable from legend:
5279  plottable->removeFromLegend();
5280  // special handling for QCPGraphs to maintain the simple graph interface:
5281  if (QCPGraph *graph = dynamic_cast<QCPGraph*>(plottable))
5282  mGraphs.removeOne(graph);
5283  // remove plottable:
5284  delete plottable;
5285  mPlottables.removeOne(plottable);
5286  return true;
5287  } else
5288  {
5289  qDebug() << FUNCNAME << "plottable not in list:" << plottable;
5290  return false;
5291  }
5292 }
5293 
5299 {
5300  if (index >= 0 && index < mPlottables.size())
5301  return removePlottable(mPlottables[index]);
5302  else
5303  {
5304 
5305  qDebug() << FUNCNAME << "index out of bounds:" << index;
5306  return false;
5307  }
5308 }
5309 
5318 {
5319  int c = mPlottables.size();
5320  for (int i=c-1; i >= 0; --i)
5321  removePlottable(mPlottables[i]);
5322  return c;
5323 }
5324 
5331 {
5332  return mPlottables.size();
5333 }
5334 
5343 QCPGraph *QCustomPlot::graph(int index) const
5344 {
5345  if (index >= 0 && index < mGraphs.size())
5346  {
5347  return mGraphs.at(index);
5348  } else
5349  {
5350  qDebug() << FUNCNAME << "index out of bounds:" << index;
5351  return 0;
5352  }
5353 }
5354 
5363 {
5364  if (!mGraphs.isEmpty())
5365  {
5366  return mGraphs.last();
5367  } else
5368  return 0;
5369 }
5370 
5384 {
5385  if (!keyAxis) keyAxis = xAxis;
5386  if (!valueAxis) valueAxis = yAxis;
5387  QCPGraph *newGraph = new QCPGraph(keyAxis, valueAxis);
5388  if (addPlottable(newGraph))
5389  {
5390  newGraph->setName("Graph "+QString::number(mGraphs.size()));
5391  return newGraph;
5392  } else
5393  {
5394  delete newGraph;
5395  return 0;
5396  }
5397 }
5398 
5409 {
5410  return removePlottable(graph);
5411 }
5412 
5418 {
5419  if (index >= 0 && index < mGraphs.size())
5420  return removeGraph(mGraphs[index]);
5421  else
5422  return false;
5423 }
5424 
5431 {
5432  int c = mGraphs.size();
5433  for (int i=c-1; i >= 0; --i)
5434  removeGraph(mGraphs[i]);
5435  return c;
5436 }
5437 
5444 {
5445  return mGraphs.size();
5446 }
5447 
5453 {
5454  QPainter painter(&buffer);
5455  if (!painter.isActive()) // might happen if QCustomPlot has width or height zero
5456  {
5457  qDebug() << FUNCNAME << "Couldn't activate painter on buffer";
5458  return;
5459  }
5460  painter.fillRect(rect(), mColor);
5461  draw(&painter);
5462  update();
5463 }
5464 
5486 {
5487  xAxis2->setVisible(true);
5488  yAxis2->setVisible(true);
5489 
5490  xAxis2->setTickLabels(false);
5491  yAxis2->setTickLabels(false);
5492 
5493  xAxis2->setAutoSubTicks(xAxis->autoSubTicks());
5494  yAxis2->setAutoSubTicks(yAxis->autoSubTicks());
5495 
5496  xAxis2->setAutoTickCount(xAxis->autoTickCount());
5497  yAxis2->setAutoTickCount(yAxis->autoTickCount());
5498 
5499  xAxis2->setAutoTickStep(xAxis->autoTickStep());
5500  yAxis2->setAutoTickStep(yAxis->autoTickStep());
5501 
5502  xAxis2->setScaleType(xAxis->scaleType());
5503  yAxis2->setScaleType(yAxis->scaleType());
5504 
5505  xAxis2->setScaleLogBase(xAxis->scaleLogBase());
5506  yAxis2->setScaleLogBase(yAxis->scaleLogBase());
5507 
5508  xAxis2->setTicks(xAxis->ticks());
5509  yAxis2->setTicks(yAxis->ticks());
5510 
5511  xAxis2->setSubTickCount(xAxis->subTickCount());
5512  yAxis2->setSubTickCount(yAxis->subTickCount());
5513 
5514  xAxis2->setTickStep(xAxis->tickStep());
5515  yAxis2->setTickStep(yAxis->tickStep());
5516 
5517  xAxis2->setRange(xAxis->range());
5518  yAxis2->setRange(yAxis->range());
5519 
5520  xAxis2->setRangeReversed(xAxis->rangeReversed());
5521  yAxis2->setRangeReversed(yAxis->rangeReversed());
5522 }
5523 
5531 {
5532  if (mPlottables.isEmpty()) return;
5533 
5534  mPlottables.at(0)->rescaleAxes(false); // onlyEnlarge disabled on first plottable
5535  for (int i=1; i<mPlottables.size(); ++i)
5536  mPlottables.at(i)->rescaleAxes(true); // onlyEnlarge enabled on all other plottables
5537 }
5538 
5564 void QCustomPlot::savePdf(const QString &fileName, bool noCosmeticPen, int width, int height)
5565 {
5566  int newWidth, newHeight;
5567  if (width == 0 || height == 0)
5568  {
5569  newWidth = this->width();
5570  newHeight = this->height();
5571  } else
5572  {
5573  newWidth = width;
5574  newHeight = height;
5575  }
5576 
5577  QPrinter printer(QPrinter::ScreenResolution);
5578  printer.setOutputFileName(fileName);
5579  printer.setFullPage(true);
5580  QRect oldViewport = mViewport;
5581  mViewport = QRect(0, 0, newWidth, newHeight);
5582  updateAxisRect();
5583  printer.setPaperSize(mViewport.size(), QPrinter::DevicePixel);
5584  QPainter printpainter(&printer);
5585  printpainter.setWindow(mViewport);
5586  printpainter.setRenderHint(QPainter::NonCosmeticDefaultPen, noCosmeticPen);
5587  draw(&printpainter);
5588  mViewport = oldViewport;
5589  updateAxisRect();
5590 }
5591 
5592 /*
5593  Function for providing svg export. Requires the QtSvg module
5594  This is Not tested and will require some modifications!
5595 
5596 void QCustomPlot::saveSvg(const QString &fileName)
5597 {
5598  QSvgGenerator generator;
5599  generator.setFileName(fileName);
5600  generator.setSize(QSize(200, 200));
5601  generator.setViewBox(QRect(0, 0, 200, 200));
5602  generator.setTitle("");
5603  generator.setDescription("");
5604  QPainter painter(&generator);
5605  draw(&painter);
5606 }
5607 */
5608 
5625 void QCustomPlot::savePng(const QString &fileName, int width, int height)
5626 {
5627  int newWidth, newHeight;
5628  if (width == 0 || height == 0)
5629  {
5630  newWidth = this->width();
5631  newHeight = this->height();
5632  } else
5633  {
5634  newWidth = width;
5635  newHeight = height;
5636  }
5637 
5638  QPixmap pngBuffer(newWidth, newHeight);
5639  QPainter painter(&pngBuffer);
5640  painter.fillRect(pngBuffer.rect(), mColor);
5641  QRect oldViewport = mViewport;
5642  mViewport = QRect(0, 0, newWidth, newHeight);
5643  updateAxisRect();
5644  draw(&painter);
5645  mViewport = oldViewport;
5646  updateAxisRect();
5647  pngBuffer.save(fileName);
5648 }
5649 
5672 void QCustomPlot::savePngScaled(const QString &fileName, double scale, int width, int height)
5673 {
5674  int newWidth, newHeight;
5675  if (width == 0 || height == 0)
5676  {
5677  newWidth = this->width();
5678  newHeight = this->height();
5679  } else
5680  {
5681  newWidth = width;
5682  newHeight = height;
5683  }
5684 
5685  int scaledWidth = scale*newWidth;
5686  int scaledHeight = scale*newHeight;
5687 
5688  QPixmap pngBuffer(scaledWidth, scaledHeight);
5689  QPainter painter(&pngBuffer);
5690  painter.setRenderHint(QPainter::NonCosmeticDefaultPen);
5691  painter.fillRect(pngBuffer.rect(), mColor);
5692  QRect oldViewport = mViewport;
5693  mViewport = QRect(0, 0, newWidth, newHeight);
5694  updateAxisRect();
5695  painter.scale(scale, scale);
5696  draw(&painter);
5697  mViewport = oldViewport;
5698  updateAxisRect();
5699  pngBuffer.save(fileName);
5700 }
5701 
5708 void QCustomPlot::paintEvent(QPaintEvent *event)
5709 {
5710  Q_UNUSED(event);
5711  QPainter painter(this);
5712  painter.drawPixmap(0, 0, buffer);
5713 }
5714 
5722 void QCustomPlot::resizeEvent(QResizeEvent *event)
5723 {
5724  // resize and repaint the buffer:
5725  buffer = QPixmap(event->size());
5726  mViewport = rect();
5727  updateAxisRect();
5728  replot();
5729 }
5730 
5735 void QCustomPlot::mouseDoubleClickEvent(QMouseEvent *event)
5736 {
5737  emit mouseDoubleClick(event);
5738 }
5739 
5750 void QCustomPlot::mousePressEvent(QMouseEvent *event)
5751 {
5752  if (event->buttons() & Qt::LeftButton)
5753  {
5754  mDragging = true;
5755  mDragStart = event->pos();
5756  mDragStartHorzRange = mRangeDragHorzAxis->range();
5757  mDragStartVertRange = mRangeDragVertAxis->range();
5758  } else
5759  {
5760  mDragging = false;
5761  }
5762 
5763  // check if mouse press was on a legend item
5764  int itemIdx = legend->getItemIndex(&event->pos());
5765 
5766  if (itemIdx >= 0)
5767  {
5768  emit mousePressOnLegendItem(event, QVariant(itemIdx));
5769  } else
5770  {
5771  emit mousePressOnPlotArea(event);
5772  }
5773 
5774  emit mousePress(event);
5775 }
5776 
5784 void QCustomPlot::mouseMoveEvent(QMouseEvent *event)
5785 {
5786  emit mouseMove(event);
5787  if (mDragging)
5788  {
5789  if (mRangeDrag.testFlag(Qt::Horizontal))
5790  {
5791  if (mRangeDragHorzAxis->mScaleType == QCPAxis::STLinear)
5792  {
5793  double diff = mRangeDragHorzAxis->pixelToCoord(mDragStart.x()) - mRangeDragHorzAxis->pixelToCoord(event->pos().x());
5794  mRangeDragHorzAxis->setRange(mDragStartHorzRange.lower+diff, mDragStartHorzRange.upper+diff);
5795  } else if (mRangeDragHorzAxis->mScaleType == QCPAxis::STLogarithmic)
5796  {
5797  double diff = mRangeDragHorzAxis->pixelToCoord(mDragStart.x()) / mRangeDragHorzAxis->pixelToCoord(event->pos().x());
5798  mRangeDragHorzAxis->setRange(mDragStartHorzRange.lower*diff, mDragStartHorzRange.upper*diff);
5799  }
5800  }
5801  if (mRangeDrag.testFlag(Qt::Vertical))
5802  {
5803  if (mRangeDragVertAxis->mScaleType == QCPAxis::STLinear)
5804  {
5805  double diff = mRangeDragVertAxis->pixelToCoord(mDragStart.y()) - mRangeDragVertAxis->pixelToCoord(event->pos().y());
5806  mRangeDragVertAxis->setRange(mDragStartVertRange.lower+diff, mDragStartVertRange.upper+diff);
5807  } else if (mRangeDragVertAxis->mScaleType == QCPAxis::STLogarithmic)
5808  {
5809  double diff = mRangeDragVertAxis->pixelToCoord(mDragStart.y()) / mRangeDragVertAxis->pixelToCoord(event->pos().y());
5810  mRangeDragVertAxis->setRange(mDragStartVertRange.lower*diff, mDragStartVertRange.upper*diff);
5811  }
5812  }
5813  if (mRangeDrag != 0) // if either vertical or horizontal drag was enabled, do a replot
5814  replot();
5815  }
5816 }
5817 
5826 void QCustomPlot::mouseReleaseEvent(QMouseEvent *event)
5827 {
5828  mDragging = false;
5829  emit mouseRelease(event);
5830 }
5831 
5848 void QCustomPlot::wheelEvent(QWheelEvent *event)
5849 {
5850  emit mouseWheel(event);
5851  if (mRangeZoom != 0)
5852  {
5853  double factor;
5854  double wheelSteps = event->delta()/120.0; // a single step delta is +/-120 usually
5855  if (mRangeZoom.testFlag(Qt::Horizontal))
5856  {
5857  factor = pow(mRangeZoomFactorHorz, wheelSteps);
5858  mRangeZoomHorzAxis->scaleRange(factor, mRangeZoomHorzAxis->pixelToCoord(event->pos().x()));
5859  }
5860  if (mRangeZoom.testFlag(Qt::Vertical))
5861  {
5862  factor = pow(mRangeZoomFactorVert, wheelSteps);
5863  mRangeZoomVertAxis->scaleRange(factor, mRangeZoomVertAxis->pixelToCoord(event->pos().y()));
5864  }
5865  replot();
5866  }
5867 }
5868 
5875 void QCustomPlot::draw(QPainter *painter)
5876 {
5877 
5878  // draw title:
5879  QRect titleBounds;
5880  if (!mTitle.isEmpty())
5881  {
5882  painter->setFont(mTitleFont);
5883  titleBounds = painter->fontMetrics().boundingRect(0, 0, titleBounds.width(), titleBounds.height(), Qt::TextDontClip | Qt::AlignHCenter | Qt::AlignVCenter, mTitle);
5884  painter->drawText(mViewport.left(), mViewport.top(), mViewport.width(), titleBounds.height(), Qt::TextDontClip | Qt::AlignHCenter | Qt::AlignVCenter, mTitle);
5885  }
5886 
5887  // prepare values of ticks and tick strings:
5888  xAxis->generateTickVectors();
5889  yAxis->generateTickVectors();
5890  xAxis2->generateTickVectors();
5891  yAxis2->generateTickVectors();
5892 
5893  // set auto margin such that tick/axis labels etc. are not clipped:
5894  if (mAutoMargin)
5895  {
5896  setMargin(yAxis->calculateMargin(),
5897  yAxis2->calculateMargin(),
5898  xAxis2->calculateMargin()+titleBounds.height(),
5899  xAxis->calculateMargin());
5900  }
5901 
5902  // draw axis background:
5903  drawAxisBackground(painter);
5904 
5905  // draw grids (and zerolines):
5906  xAxis->drawSubGrid(painter);
5907  yAxis->drawSubGrid(painter);
5908  xAxis2->drawSubGrid(painter);
5909  yAxis2->drawSubGrid(painter);
5910  xAxis->drawGrid(painter);
5911  yAxis->drawGrid(painter);
5912  xAxis2->drawGrid(painter);
5913  yAxis2->drawGrid(painter);
5914 
5915  // draw all plottables:
5916  for (int i=0; i < mPlottables.size(); ++i)
5917  {
5918  painter->save(); // since this might be user subclass, we save painter outside - just in case
5919  mPlottables.at(i)->draw(painter);
5920  painter->restore();
5921  }
5922 
5923  // draw axes, ticks and axis labels:
5924  xAxis->drawAxis(painter);
5925  yAxis->drawAxis(painter);
5926  xAxis2->drawAxis(painter);
5927  yAxis2->drawAxis(painter);
5928 
5929  // draw legend:
5930  legend->reArrange();
5931  legend->draw(painter);
5932 }
5933 
5946 void QCustomPlot::drawAxisBackground(QPainter *painter)
5947 {
5948  if (!mAxisBackground.isNull())
5949  {
5950  if (mAxisBackgroundScaled)
5951  {
5952  // check whether mScaledAxisBackground needs to be updated:
5953  QSize scaledSize(mAxisBackground.size());
5954  scaledSize.scale(mAxisRect.size(), mAxisBackgroundScaledMode);
5955  if (mScaledAxisBackground.size() != scaledSize)
5956  mScaledAxisBackground = mAxisBackground.scaled(mAxisRect.size(), mAxisBackgroundScaledMode, Qt::SmoothTransformation);
5957  painter->drawPixmap(mAxisRect.topLeft(), mScaledAxisBackground, QRect(0, 0, mAxisRect.width(), mAxisRect.height()) & mScaledAxisBackground.rect());
5958  } else
5959  {
5960  painter->drawPixmap(mAxisRect.topLeft(), mAxisBackground, QRect(0, 0, mAxisRect.width(), mAxisRect.height()));
5961  }
5962  }
5963 }
5964 
5973 {
5974  mAxisRect = mViewport.adjusted(mMarginLeft, mMarginTop, -mMarginRight, -mMarginBottom);
5975  xAxis->setAxisRect(mAxisRect);
5976  yAxis->setAxisRect(mAxisRect);
5977  xAxis2->setAxisRect(mAxisRect);
5978  yAxis2->setAxisRect(mAxisRect);
5979 }
5980 
5981 
5982 // ================================================================================
5983 // =================== QCPAbstractPlottable
5984 // ================================================================================
5985 
6031 /* start of documentation of pure virtual functions */
6032 
6079 /* end of documentation of pure virtual functions */
6080 
6093  mParentPlot(keyAxis->parentPlot()),
6094  mName(""),
6095  mVisible(true),
6096  mPen(Qt::black),
6097  mBrush(Qt::NoBrush),
6098  mKeyAxis(keyAxis),
6099  mValueAxis(valueAxis)
6100 {
6101  if (keyAxis->parentPlot() != valueAxis->parentPlot())
6102  qDebug() << FUNCNAME << "Parent plot of keyAxis is not the same as that of valueAxis.";
6103  if (keyAxis->orientation() == valueAxis->orientation())
6104  qDebug() << FUNCNAME << "keyAxis and valueAxis must be orthogonal to each other.";
6105 }
6106 
6112 {
6113  mName = name;
6114 }
6115 
6121 {
6122  mVisible = visible;
6123 }
6124 
6135 {
6136  mPen = pen;
6137 }
6138 
6149 {
6150  mBrush = brush;
6151 }
6152 
6161 {
6162  mKeyAxis = axis;
6163 }
6164 
6173 {
6174  mValueAxis = axis;
6175 }
6176 
6188 void QCPAbstractPlottable::rescaleAxes(bool onlyEnlarge) const
6189 {
6190  rescaleKeyAxis(onlyEnlarge);
6191  rescaleValueAxis(onlyEnlarge);
6192 }
6193 
6199 void QCPAbstractPlottable::rescaleKeyAxis(bool onlyEnlarge) const
6200 {
6201  SignDomain signDomain = SDBoth;
6203  signDomain = (mKeyAxis->range().upper < 0 ? SDNegative : SDPositive);
6204 
6205  bool validRange;
6206  QCPRange newRange = getKeyRange(validRange, signDomain);
6207 
6208  if (validRange)
6209  {
6210  if (onlyEnlarge)
6211  {
6212  if (mKeyAxis->range().lower < newRange.lower)
6213  newRange.lower = mKeyAxis->range().lower;
6214  if (mKeyAxis->range().upper > newRange.upper)
6215  newRange.upper = mKeyAxis->range().upper;
6216  }
6217  mKeyAxis->setRange(newRange);
6218  }
6219 }
6220 
6226 void QCPAbstractPlottable::rescaleValueAxis(bool onlyEnlarge) const
6227 {
6228  SignDomain signDomain = SDBoth;
6230  signDomain = (mValueAxis->range().upper < 0 ? SDNegative : SDPositive);
6231 
6232  bool validRange;
6233  QCPRange newRange = getValueRange(validRange, signDomain);
6234 
6235  if (validRange)
6236  {
6237  if (onlyEnlarge)
6238  {
6239  if (mValueAxis->range().lower < newRange.lower)
6240  newRange.lower = mValueAxis->range().lower;
6241  if (mValueAxis->range().upper > newRange.upper)
6242  newRange.upper = mValueAxis->range().upper;
6243  }
6244  mValueAxis->setRange(newRange);
6245  }
6246 }
6247 
6261 {
6263  {
6265  return true;
6266  } else
6267  return false;
6268 }
6269 
6281 {
6283  return mParentPlot->legend->removeItem(lip);
6284  else
6285  return false;
6286 }
6287 
6298 void QCPAbstractPlottable::coordsToPixels(double key, double value, double &x, double &y) const
6299 {
6300  if (mKeyAxis->orientation() == Qt::Horizontal)
6301  {
6302  x = mKeyAxis->coordToPixel(key);
6303  y = mValueAxis->coordToPixel(value);
6304  } else
6305  {
6306  y = mKeyAxis->coordToPixel(key);
6307  x = mValueAxis->coordToPixel(value);
6308  }
6309 }
6310 
6316 const QPointF QCPAbstractPlottable::coordsToPixels(double key, double value) const
6317 {
6318  if (mKeyAxis->orientation() == Qt::Horizontal)
6319  return QPointF(mKeyAxis->coordToPixel(key), mValueAxis->coordToPixel(value));
6320  else
6321  return QPointF(mValueAxis->coordToPixel(value), mKeyAxis->coordToPixel(key));
6322 }
6323 
6324 
6325 // ================================================================================
6326 // =================== QCPAbstractLegendItem
6327 // ================================================================================
6346 /* start documentation of pure virtual functions */
6347 
6362 /* end documentation of pure virtual functions */
6363 
6369  mParentLegend(parent),
6370  mFont(parent->font())
6371 {
6372 }
6373 
6380 {
6381  mFont = font;
6382 }
6383 
6384 
6385 // ================================================================================
6386 // =================== QCPPlottableLegendItem
6387 // ================================================================================
6419  QCPAbstractLegendItem(parent),
6420  mPlottable(plottable)
6421 {
6422  // take default values from parent legend:
6423  mIconSize = parent->iconSize();
6424  mIconBorderPen = parent->iconBorderPen();
6425  mIconTextPadding = parent->iconTextPadding();
6426  mTextWrap = false;
6427 }
6428 
6438 {
6439  mTextWrap = wrap;
6440 }
6441 
6451 void QCPPlottableLegendItem::draw(QPainter *painter, const QRect &rect) const
6452 {
6453  if (!mPlottable) return;
6454  painter->setFont(mFont);
6455  QRect textRect;
6456  QRect iconRect(rect.topLeft(), mIconSize);
6457  if (mTextWrap)
6458  {
6459  // take width from rect since our text should wrap there (only icon must fit at least):
6460  textRect = painter->fontMetrics().boundingRect(0, 0, rect.width()-mIconTextPadding-mIconSize.width(), rect.height(), Qt::TextDontClip | Qt::TextWordWrap, mPlottable->name());
6461  if (textRect.height() < mIconSize.height()) // text smaller than icon, center text vertically in icon height
6462  {
6463  painter->drawText(rect.x()+mIconSize.width()+mIconTextPadding, rect.y(), rect.width()-mIconTextPadding-mIconSize.width(), mIconSize.height(), Qt::TextDontClip | Qt::TextWordWrap, mPlottable->name());
6464  } else // text bigger than icon, position top of text with top of icon
6465  {
6466  painter->drawText(rect.x()+mIconSize.width()+mIconTextPadding, rect.y(), rect.width()-mIconTextPadding-mIconSize.width(), textRect.height(), Qt::TextDontClip | Qt::TextWordWrap, mPlottable->name());
6467  }
6468  } else
6469  {
6470  // text can't wrap (except with explicit newlines), center at current item size (icon size)
6471  textRect = painter->fontMetrics().boundingRect(0, 0, 0, rect.height(), Qt::TextDontClip, mPlottable->name());
6472  if (textRect.height() < mIconSize.height()) // text smaller than icon, center text vertically in icon height
6473  {
6474  painter->drawText(rect.x()+mIconSize.width()+mIconTextPadding, rect.y(), rect.width(), mIconSize.height(), Qt::TextDontClip, mPlottable->name());
6475  } else // text bigger than icon, position top of text with top of icon
6476  {
6477  painter->drawText(rect.x()+mIconSize.width()+mIconTextPadding, rect.y(), rect.width(), textRect.height(), Qt::TextDontClip, mPlottable->name());
6478  }
6479  }
6480  // draw icon:
6481  painter->save();
6482  painter->setClipRect(iconRect, Qt::IntersectClip);
6483  mPlottable->drawLegendIcon(painter, iconRect);
6484  painter->restore();
6485  // draw icon border:
6486  if (mIconBorderPen.style() != Qt::NoPen)
6487  {
6488  painter->setPen(mIconBorderPen);
6489  painter->setBrush(Qt::NoBrush);
6490  painter->drawRect(iconRect);
6491  }
6492 }
6493 
6505 QSize QCPPlottableLegendItem::size(const QSize &targetSize) const
6506 {
6507  if (!mPlottable) return QSize();
6508  QSize result(0, 0);
6509  QRect textRect;
6510  QFontMetrics fontMetrics(mFont);
6511  if (mTextWrap)
6512  {
6513  // take width from targetSize since our text can wrap (Only icon must fit at least):
6514  textRect = fontMetrics.boundingRect(0, 0, targetSize.width()-mIconTextPadding-mIconSize.width(), mIconSize.height(), Qt::TextDontClip | Qt::TextWordWrap, mPlottable->name());
6515  } else
6516  {
6517  // text can't wrap (except with explicit newlines), center at current item size (icon size)
6518  textRect = fontMetrics.boundingRect(0, 0, 0, mIconSize.height(), Qt::TextDontClip, mPlottable->name());
6519  }
6520  result.setWidth(mIconSize.width() + mIconTextPadding + textRect.width());
6521  result.setHeight(qMax(textRect.height(), mIconSize.height()));
6522  return result;
6523 }
6524 
6525 // ================================================================================
6526 // =================== QCPCurve
6527 // ================================================================================
6560 QCPCurve::QCPCurve(QCPAxis *keyAxis, QCPAxis *valueAxis) :
6561  QCPAbstractPlottable(keyAxis, valueAxis)
6562 {
6563  mData = new QCPCurveDataMap;
6564  mPen.setColor(Qt::blue);
6565  mPen.setStyle(Qt::SolidLine);
6566  mBrush.setColor(Qt::blue);
6567  mBrush.setStyle(Qt::NoBrush);
6568 }
6569 
6571 {
6572  delete mData;
6573 }
6574 
6583 {
6584  if (copy)
6585  {
6586  *mData = *data;
6587  } else
6588  {
6589  delete mData;
6590  mData = data;
6591  }
6592 }
6593 
6600 void QCPCurve::setData(const QVector<double> &t, const QVector<double> &key, const QVector<double> &value)
6601 {
6602  mData->clear();
6603  int n = t.size();
6604  n = qMin(n, key.size());
6605  n = qMin(n, value.size());
6606  QCPCurveData newData;
6607  for (int i=0; i<n; ++i)
6608  {
6609  newData.t = t[i];
6610  newData.key = key[i];
6611  newData.value = value[i];
6612  mData->insertMulti(newData.t, newData);
6613  }
6614 }
6615 
6621 void QCPCurve::setData(const QVector<double> &key, const QVector<double> &value)
6622 {
6623  mData->clear();
6624  int n = key.size();
6625  n = qMin(n, value.size());
6626  QCPCurveData newData;
6627  for (int i=0; i<n; ++i)
6628  {
6629  newData.t = i; // no t vector given, so we assign t the index of the key/value pair
6630  newData.key = key[i];
6631  newData.value = value[i];
6632  mData->insertMulti(newData.t, newData);
6633  }
6634 }
6635 
6641 {
6642  mData->unite(dataMap);
6643 }
6644 
6650 {
6651  mData->insertMulti(data.t, data);
6652 }
6653 
6658 void QCPCurve::addData(double t, double key, double value)
6659 {
6660  QCPCurveData newData;
6661  newData.t = t;
6662  newData.key = key;
6663  newData.value = value;
6664  mData->insertMulti(newData.t, newData);
6665 }
6666 
6675 void QCPCurve::addData(double key, double value)
6676 {
6677  QCPCurveData newData;
6678  if (!mData->isEmpty())
6679  newData.t = (mData->constEnd()-1).key()+1;
6680  else
6681  newData.t = 0;
6682  newData.key = key;
6683  newData.value = value;
6684  mData->insertMulti(newData.t, newData);
6685 }
6686 
6691 void QCPCurve::addData(const QVector<double> &ts, const QVector<double> &keys, const QVector<double> &values)
6692 {
6693  int n = ts.size();
6694  n = qMin(n, keys.size());
6695  n = qMin(n, values.size());
6696  QCPCurveData newData;
6697  for (int i=0; i<n; ++i)
6698  {
6699  newData.t = ts[i];
6700  newData.key = keys[i];
6701  newData.value = values[i];
6702  mData->insertMulti(newData.t, newData);
6703  }
6704 }
6705 
6711 {
6712  QCPCurveDataMap::iterator it = mData->begin();
6713  while (it != mData->end() && it.key() < t)
6714  it = mData->erase(it);
6715 }
6716 
6722 {
6723  if (mData->isEmpty()) return;
6724  QCPCurveDataMap::iterator it = mData->upperBound(t);
6725  while (it != mData->end())
6726  it = mData->erase(it);
6727 }
6728 
6736 void QCPCurve::removeData(double fromt, double tot)
6737 {
6738  if (fromt >= tot || mData->isEmpty()) return;
6739  QCPCurveDataMap::iterator it = mData->upperBound(fromt);
6740  QCPCurveDataMap::iterator itEnd = mData->upperBound(tot);
6741  while (it != itEnd)
6742  it = mData->erase(it);
6743 }
6744 
6754 void QCPCurve::removeData(double t)
6755 {
6756  mData->remove(t);
6757 }
6758 
6764 {
6765  mData->clear();
6766 }
6767 
6768 /* inherits documentation from base class */
6769 void QCPCurve::draw(QPainter *painter) const
6770 {
6771  if (!mVisible) return;
6772  if (mData->isEmpty()) return;
6773  painter->setClipRect(mKeyAxis->axisRect().united(mValueAxis->axisRect()));
6774 
6775  // allocate line vector:
6776  QVector<QPointF> *lineData = new QVector<QPointF>;
6777  // fill with curve data:
6778  getCurveData(lineData);
6779  // draw curve fill:
6780  if (mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0)
6781  {
6782  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEFills));
6783  painter->setPen(Qt::NoPen);
6784  painter->setBrush(mBrush);
6785  painter->drawPolygon(QPolygonF(*lineData));
6786  }
6787  // draw curve line:
6788  if (mPen.style() != Qt::NoPen && mPen.color().alpha() != 0)
6789  {
6790  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGraphs));
6791  painter->setPen(mPen);
6792  painter->setBrush(Qt::NoBrush);
6793  painter->drawPolyline(QPolygonF(*lineData));
6794  }
6795  // free allocated line data:
6796  delete lineData;
6797 }
6798 
6799 /* inherits documentation from base class */
6800 void QCPCurve::drawLegendIcon(QPainter *painter, const QRect &rect) const
6801 {
6802  // draw fill:
6803  if (mBrush.style() != Qt::NoBrush)
6804  {
6805  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGraphs));
6806  painter->fillRect(rect.left(), rect.top()+rect.height()/2.0, rect.width(), rect.height()/3.0, mBrush);
6807  }
6808  // draw line vertically centered:
6809  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGraphs));
6810  painter->setPen(mPen);
6811  painter->drawLine(rect.left(), rect.top()+rect.height()/2.0, rect.right()+5, rect.top()+rect.height()/2.0); // +5 on x2 else last segment is missing from dashed/dotted pens
6812 }
6813 
6819 void QCPCurve::getCurveData(QVector<QPointF> *lineData) const
6820 {
6821  /* Edges of axis rect R divide outside space into 9 regions:
6822  1__|_4_|__7
6823  2__|_R_|__8
6824  3 | 6 | 9
6825  General idea: If the two points of a line segment are in the same region (that is not R), the line segment is omitted.
6826  The region inside R has index 5.
6827  */
6828  lineData->reserve(mData->size());
6829  QCPCurveDataMap::const_iterator it;
6830  int lastRegion = 5;
6831  int currentRegion = 5;
6832  double RLeft = mKeyAxis->range().lower;
6833  double RRight = mKeyAxis->range().upper;
6834  double RBottom = mValueAxis->range().lower;
6835  double RTop = mValueAxis->range().upper;
6836  double x, y; // current key/value
6837  bool addedLastAlready = true;
6838  bool firstPoint = true; // first point must always be drawn, to make sure fill works correctly
6839  for (it = mData->constBegin(); it != mData->constEnd(); ++it)
6840  {
6841  x = it.value().key;
6842  y = it.value().value;
6843  // determine current region:
6844  if (x < RLeft) // region 123
6845  {
6846  if (y > RTop)
6847  currentRegion = 1;
6848  else if (y < RBottom)
6849  currentRegion = 3;
6850  else
6851  currentRegion = 2;
6852  } else if (x > RRight) // region 789
6853  {
6854  if (y > RTop)
6855  currentRegion = 7;
6856  else if (y < RBottom)
6857  currentRegion = 9;
6858  else
6859  currentRegion = 8;
6860  } else // region 456
6861  {
6862  if (y > RTop)
6863  currentRegion = 4;
6864  else if (y < RBottom)
6865  currentRegion = 6;
6866  else
6867  currentRegion = 5;
6868  }
6869 
6870  /*
6871  Watch out, the next part is very tricky, since it modifies the curve such that it seems like
6872  the whole thing is still drawn, but actually the points outside the axisRect are simplified
6873  ("optimized") greatly. There are some subtle special cases when line segments are large and
6874  thereby each subsequent point may be in a different region or even skip some.
6875  */
6876  // determine whether to keep current point:
6877  if (currentRegion == 5 || (firstPoint && mBrush.style() != Qt::NoBrush)) // current is in R, add current and last if it wasn't added already
6878  {
6879  if (!addedLastAlready) // in case curve just entered R, make sure the last point outside R is also drawn correctly
6880  lineData->append(coordsToPixels((it-1).value().key, (it-1).value().value)); // add last point to vector
6881  else if (lastRegion != 5) // added last already. If that's the case, we probably added it at optimized position. So go back and make sure it's at original position
6882  {
6883  if (!firstPoint) // because on firstPoint, currentRegion is 5 and addedLastAlready is true, although there is no last point
6884  lineData->replace(lineData->size()-1, coordsToPixels((it-1).value().key, (it-1).value().value));
6885  }
6886  lineData->append(coordsToPixels(it.value().key, it.value().value)); // add current point to vector
6887  addedLastAlready = true; // so in next iteration, we don't add this point twice
6888  } else if (currentRegion != lastRegion) // changed region, add current and last if not added already
6889  {
6890  // using outsideCoordsToPixels instead of coorsToPixels for optimized point placement (places points just outside axisRect instead of potentially far away)
6891 
6892  // if we're coming from R or we skip diagonally over the edge regions (so line might still be visible in R), we can't place points optimized
6893  if (lastRegion == 5 || // coming from R
6894  ((lastRegion==2 && currentRegion==4) || (lastRegion==4 && currentRegion==2)) || // skip top left diagonal
6895  ((lastRegion==4 && currentRegion==8) || (lastRegion==8 && currentRegion==4)) || // skip top right diagonal
6896  ((lastRegion==8 && currentRegion==6) || (lastRegion==6 && currentRegion==8)) || // skip bottom right diagonal
6897  ((lastRegion==6 && currentRegion==2) || (lastRegion==2 && currentRegion==6)) // skip bottom left diagonal
6898  )
6899  {
6900  // always add last point if not added already, original:
6901  if (!addedLastAlready)
6902  lineData->append(coordsToPixels((it-1).value().key, (it-1).value().value));
6903  // add current point, original:
6904  lineData->append(coordsToPixels(it.value().key, it.value().value));
6905  } else // no special case that forbids optimized point placement, so do it:
6906  {
6907  // always add last point if not added already, optimized:
6908  if (!addedLastAlready)
6909  lineData->append(outsideCoordsToPixels((it-1).value().key, (it-1).value().value, currentRegion));
6910  // add current point, optimized:
6911  lineData->append(outsideCoordsToPixels(it.value().key, it.value().value, currentRegion));
6912  }
6913  addedLastAlready = true; // so that if next point enters 5, or crosses another region boundary, we don't add this point twice
6914  } else // neither in R, nor crossed a region boundary, skip current point
6915  {
6916  addedLastAlready = false;
6917  }
6918  lastRegion = currentRegion;
6919  firstPoint = false;
6920  }
6921  // If curve ends outside R, we want to add very last point so the fill looks like it should when the curve started inside R:
6922  if (lastRegion != 5 && mBrush.style() != Qt::NoBrush && !mData->isEmpty())
6923  lineData->append(coordsToPixels((mData->constEnd()-1).value().key, (mData->constEnd()-1).value().value));
6924 }
6925 
6936 QPointF QCPCurve::outsideCoordsToPixels(double key, double value, int region) const
6937 {
6938  int margin = 10;
6939  QRect axisRect = mKeyAxis->axisRect().united(mValueAxis->axisRect());
6940  QPointF result = coordsToPixels(key, value);
6941  switch (region)
6942  {
6943  case 2: result.setX(axisRect.left()-margin); break; // left
6944  case 8: result.setX(axisRect.right()+margin); break; // right
6945  case 4: result.setY(axisRect.top()-margin); break; // top
6946  case 6: result.setY(axisRect.bottom()+margin); break; // bottom
6947  case 1: result.setX(axisRect.left()-margin);
6948  result.setY(axisRect.top()-margin); break; // top left
6949  case 7: result.setX(axisRect.right()+margin);
6950  result.setY(axisRect.top()-margin); break; // top right
6951  case 9: result.setX(axisRect.right()+margin);
6952  result.setY(axisRect.bottom()+margin); break; // bottom right
6953  case 3: result.setX(axisRect.left()-margin);
6954  result.setY(axisRect.bottom()+margin); break; // bottom left
6955  }
6956  return result;
6957 }
6958 
6959 /* inherits documentation from base class */
6960 QCPRange QCPCurve::getKeyRange(bool &validRange, SignDomain inSignDomain) const
6961 {
6962  QCPRange range;
6963  bool haveLower = false;
6964  bool haveUpper = false;
6965 
6966  double current;
6967 
6968  QCPCurveDataMap::const_iterator it = mData->constBegin();
6969  while (it != mData->constEnd())
6970  {
6971  current = it.value().key;
6972  if (inSignDomain == SDBoth || (inSignDomain == SDNegative && current < 0) || (inSignDomain == SDPositive && current > 0))
6973  {
6974  if (current < range.lower || !haveLower)
6975  {
6976  range.lower = current;
6977  haveLower = true;
6978  }
6979  if (current > range.upper || !haveUpper)
6980  {
6981  range.upper = current;
6982  haveUpper = true;
6983  }
6984  }
6985  ++it;
6986  }
6987 
6988  validRange = haveLower && haveUpper;
6989  return range;
6990 }
6991 
6992 /* inherits documentation from base class */
6993 QCPRange QCPCurve::getValueRange(bool &validRange, SignDomain inSignDomain) const
6994 {
6995  QCPRange range;
6996  bool haveLower = false;
6997  bool haveUpper = false;
6998 
6999  double current;
7000 
7001  QCPCurveDataMap::const_iterator it = mData->constBegin();
7002  while (it != mData->constEnd())
7003  {
7004  current = it.value().value;
7005  if (inSignDomain == SDBoth || (inSignDomain == SDNegative && current < 0) || (inSignDomain == SDPositive && current > 0))
7006  {
7007  if (current < range.lower || !haveLower)
7008  {
7009  range.lower = current;
7010  haveLower = true;
7011  }
7012  if (current > range.upper || !haveUpper)
7013  {
7014  range.upper = current;
7015  haveUpper = true;
7016  }
7017  }
7018  ++it;
7019  }
7020 
7021  validRange = haveLower && haveUpper;
7022  return range;
7023 }
7024 
7025 // ================================================================================
7026 // =================== QCPBars
7027 // ================================================================================
7082  QCPAbstractPlottable(keyAxis, valueAxis),
7083  mBarBelow(0),
7084  mBarAbove(0)
7085 {
7086  mData = new QCPBarDataMap;
7087  mPen.setColor(Qt::blue);
7088  mPen.setStyle(Qt::SolidLine);
7089  mBrush.setColor(QColor(40, 50, 255, 30));
7090  mBrush.setStyle(Qt::SolidPattern);
7091  mWidth = 0.75;
7092 }
7093 
7095 {
7096  if (mBarBelow || mBarAbove)
7097  connectBars(mBarBelow, mBarAbove); // take this bar out of any stacking
7098  delete mData;
7099 }
7100 
7105 {
7106  mWidth = width;
7107 }
7108 
7117 {
7118  if (copy)
7119  {
7120  *mData = *data;
7121  } else
7122  {
7123  delete mData;
7124  mData = data;
7125  }
7126 }
7127 
7134 void QCPBars::setData(const QVector<double> &key, const QVector<double> &value)
7135 {
7136  mData->clear();
7137  int n = key.size();
7138  n = qMin(n, value.size());
7139  QCPBarData newData;
7140  for (int i=0; i<n; ++i)
7141  {
7142  newData.key = key[i];
7143  newData.value = value[i];
7144  mData->insertMulti(newData.key, newData);
7145  }
7146 }
7147 
7163 {
7164  if (bars == this) return;
7165  if (bars->keyAxis() != mKeyAxis || bars->valueAxis() != mValueAxis)
7166  {
7167  qDebug() << FUNCNAME << "passed QCPBars* doesn't have same key and value axis as this QCPBars";
7168  return;
7169  }
7170  // remove from stacking:
7171  connectBars(mBarBelow, mBarAbove); // Note: also works if one (or both) of them is 0
7172  // if new bar given, insert this bar below it:
7173  if (bars)
7174  {
7175  if (bars->mBarBelow)
7176  connectBars(bars->mBarBelow, this);
7177  connectBars(this, bars);
7178  }
7179 }
7180 
7196 {
7197  if (bars == this) return;
7198  if (bars && (bars->keyAxis() != mKeyAxis || bars->valueAxis() != mValueAxis))
7199  {
7200  qDebug() << FUNCNAME << "passed QCPBars* doesn't have same key and value axis as this QCPBars";
7201  return;
7202  }
7203  // remove from stacking:
7204  connectBars(mBarBelow, mBarAbove); // Note: also works if one (or both) of them is 0
7205  // if new bar given, insert this bar above it:
7206  if (bars)
7207  {
7208  if (bars->mBarAbove)
7209  connectBars(this, bars->mBarAbove);
7210  connectBars(bars, this);
7211  }
7212 }
7213 
7218 void QCPBars::addData(const QCPBarDataMap &dataMap)
7219 {
7220  mData->unite(dataMap);
7221 }
7222 
7228 {
7229  mData->insertMulti(data.key, data);
7230 }
7231 
7236 void QCPBars::addData(double key, double value)
7237 {
7238  QCPBarData newData;
7239  newData.key = key;
7240  newData.value = value;
7241  mData->insertMulti(newData.key, newData);
7242 }
7243 
7248 void QCPBars::addData(const QVector<double> &keys, const QVector<double> &values)
7249 {
7250  int n = keys.size();
7251  n = qMin(n, values.size());
7252  QCPBarData newData;
7253  for (int i=0; i<n; ++i)
7254  {
7255  newData.key = keys[i];
7256  newData.value = values[i];
7257  mData->insertMulti(newData.key, newData);
7258  }
7259 }
7260 
7266 {
7267  QCPBarDataMap::iterator it = mData->begin();
7268  while (it != mData->end() && it.key() < key)
7269  it = mData->erase(it);
7270 }
7271 
7277 {
7278  if (mData->isEmpty()) return;
7279  QCPBarDataMap::iterator it = mData->upperBound(key);
7280  while (it != mData->end())
7281  it = mData->erase(it);
7282 }
7283 
7291 void QCPBars::removeData(double fromKey, double toKey)
7292 {
7293  if (fromKey >= toKey || mData->isEmpty()) return;
7294  QCPBarDataMap::iterator it = mData->upperBound(fromKey);
7295  QCPBarDataMap::iterator itEnd = mData->upperBound(toKey);
7296  while (it != itEnd)
7297  it = mData->erase(it);
7298 }
7299 
7308 void QCPBars::removeData(double key)
7309 {
7310  mData->remove(key);
7311 }
7312 
7318 {
7319  mData->clear();
7320 }
7321 
7322 /* inherits documentation from base class */
7323 void QCPBars::draw(QPainter *painter) const
7324 {
7325  if (!mVisible || mData->isEmpty()) return;
7326  painter->setClipRect(mKeyAxis->axisRect().united(mValueAxis->axisRect()));
7327 
7328  QCPBarDataMap::const_iterator it;
7329  for (it = mData->constBegin(); it != mData->constEnd(); ++it)
7330  {
7331  if (it.key()+mWidth*0.5 < mKeyAxis->range().lower || it.key()-mWidth*0.5 > mKeyAxis->range().upper)
7332  continue;
7333  QPolygonF barPolygon = getBarPolygon(it.key(), it.value().value);
7334  // draw bar fill:
7335  if (mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0)
7336  {
7337  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEFills));
7338  painter->setPen(Qt::NoPen);
7339  painter->setBrush(mBrush);
7340  painter->drawPolygon(barPolygon);
7341  }
7342  // draw bar line:
7343  if (mPen.style() != Qt::NoPen && mPen.color().alpha() != 0)
7344  {
7345  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGraphs));
7346  painter->setPen(mPen);
7347  painter->setBrush(Qt::NoBrush);
7348  painter->drawPolyline(barPolygon);
7349  }
7350  }
7351 }
7352 
7353 /* inherits documentation from base class */
7354 void QCPBars::drawLegendIcon(QPainter *painter, const QRect &rect) const
7355 {
7356  // draw filled rect:
7357  painter->setBrush(mBrush);
7358  painter->setPen(mPen);
7359  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGraphs));
7360  QRect r = QRect(0, 0, rect.width()*0.67, rect.height()*0.67);
7361  r.moveCenter(rect.center());
7362  painter->drawRect(r);
7363 }
7364 
7370 QPolygonF QCPBars::getBarPolygon(double key, double value) const
7371 {
7372  QPolygonF result;
7373  double baseValue = getBaseValue(key, value >= 0); // calculates base value dependant on stacking
7374  result << coordsToPixels(key-mWidth*0.5, baseValue);
7375  result << coordsToPixels(key-mWidth*0.5, baseValue+value);
7376  result << coordsToPixels(key+mWidth*0.5, baseValue+value);
7377  result << coordsToPixels(key+mWidth*0.5, baseValue);
7378  return result;
7379 }
7380 
7390 double QCPBars::getBaseValue(double key, bool positive) const
7391 {
7392  if (mBarBelow)
7393  {
7394  double max = 0;
7395  // find bars of mBarBelow that are approximately at key and find largest one:
7396  QCPBarDataMap::const_iterator it = mBarBelow->mData->lowerBound(key-mWidth*0.1);
7397  QCPBarDataMap::const_iterator itEnd = mBarBelow->mData->upperBound(key+mWidth*0.1);
7398  while (it != itEnd)
7399  {
7400  if ((positive && it.value().value > max) ||
7401  (!positive && it.value().value < max))
7402  max = it.value().value;
7403  ++it;
7404  }
7405  // recurse down the bar-stack to find the total height:
7406  return max + mBarBelow->getBaseValue(key, positive);
7407  } else
7408  return 0;
7409 }
7410 
7420 {
7421  if (!lower && !upper) return;
7422 
7423  if (!lower) // disconnect upper at bottom
7424  {
7425  // disconnect old bar below upper:
7426  if (upper->mBarBelow && upper->mBarBelow->mBarAbove == upper)
7427  upper->mBarBelow->mBarAbove = 0;
7428  upper->mBarBelow = 0;
7429  } else if (!upper) // disconnect lower at top
7430  {
7431  // disconnect old bar above lower:
7432  if (lower->mBarAbove && lower->mBarAbove->mBarBelow == lower)
7433  lower->mBarAbove->mBarBelow = 0;
7434  lower->mBarAbove = 0;
7435  } else // connect lower and upper
7436  {
7437  // disconnect old bar above lower:
7438  if (lower->mBarAbove && lower->mBarAbove->mBarBelow == lower)
7439  lower->mBarAbove->mBarBelow = 0;
7440  // disconnect old bar below upper:
7441  if (upper->mBarBelow && upper->mBarBelow->mBarAbove == upper)
7442  upper->mBarBelow->mBarAbove = 0;
7443  lower->mBarAbove = upper;
7444  upper->mBarBelow = lower;
7445  }
7446 }
7447 
7448 /* inherits documentation from base class */
7449 QCPRange QCPBars::getKeyRange(bool &validRange, SignDomain inSignDomain) const
7450 {
7451  QCPRange range;
7452  bool haveLower = false;
7453  bool haveUpper = false;
7454 
7455  double current;
7456  double barWidthHalf = mWidth*0.5;
7457  QCPBarDataMap::const_iterator it = mData->constBegin();
7458  while (it != mData->constEnd())
7459  {
7460  current = it.value().key;
7461  if (inSignDomain == SDBoth || (inSignDomain == SDNegative && current+barWidthHalf < 0) || (inSignDomain == SDPositive && current-barWidthHalf > 0))
7462  {
7463  if (current-barWidthHalf < range.lower || !haveLower)
7464  {
7465  range.lower = current-barWidthHalf;
7466  haveLower = true;
7467  }
7468  if (current+barWidthHalf > range.upper || !haveUpper)
7469  {
7470  range.upper = current+barWidthHalf;
7471  haveUpper = true;
7472  }
7473  }
7474  ++it;
7475  }
7476 
7477  validRange = haveLower && haveUpper;
7478  return range;
7479 }
7480 
7481 /* inherits documentation from base class */
7482 QCPRange QCPBars::getValueRange(bool &validRange, SignDomain inSignDomain) const
7483 {
7484  QCPRange range;
7485  bool haveLower = true; // set to true, because 0 should always be visible in bar charts
7486  bool haveUpper = true; // set to true, because 0 should always be visible in bar charts
7487 
7488  double current;
7489 
7490  QCPBarDataMap::const_iterator it = mData->constBegin();
7491  while (it != mData->constEnd())
7492  {
7493  current = it.value().value + getBaseValue(it.value().key, it.value().value >= 0);
7494  if (inSignDomain == SDBoth || (inSignDomain == SDNegative && current < 0) || (inSignDomain == SDPositive && current > 0))
7495  {
7496  if (current < range.lower || !haveLower)
7497  {
7498  range.lower = current;
7499  haveLower = true;
7500  }
7501  if (current > range.upper || !haveUpper)
7502  {
7503  range.upper = current;
7504  haveUpper = true;
7505  }
7506  }
7507  ++it;
7508  }
7509 
7510  validRange = !qFuzzyCompare(range.lower+1.0, range.upper+1.0);
7511  return range;
7512 }
7513 
7514 
7515 // ================================================================================
7516 // =================== QCPStatisticalBox
7517 // ================================================================================
7578  QCPAbstractPlottable(keyAxis, valueAxis),
7579  mKey(0),
7580  mMinimum(0),
7581  mLowerQuartile(0),
7582  mMedian(0),
7583  mUpperQuartile(0),
7584  mMaximum(0)
7585 {
7586  QPen whiskerPen;
7587  whiskerPen.setStyle(Qt::DashLine);
7588  whiskerPen.setCapStyle(Qt::FlatCap);
7589  setWhiskerPen(whiskerPen);
7590  setWhiskerWidth(0.2);
7591 
7592  QPen medianPen;
7593  medianPen.setWidthF(3);
7594  medianPen.setCapStyle(Qt::FlatCap);
7595  setMedianPen(medianPen);
7596 
7597  setBrush(Qt::NoBrush);
7598  setWidth(0.5);
7599 
7600  QPen outlierPen;
7601  outlierPen.setColor(Qt::blue);
7602  setOutlierPen(outlierPen);
7603  setOutlierBrush(Qt::NoBrush);
7604  setOutlierSize(5);
7605 }
7606 
7608 {
7609 }
7610 
7615 {
7616  mKey = key;
7617 }
7618 
7626 {
7627  mMinimum = value;
7628 }
7629 
7638 {
7639  mLowerQuartile = value;
7640 }
7641 
7650 {
7651  mMedian = value;
7652 }
7653 
7662 {
7663  mUpperQuartile = value;
7664 }
7665 
7673 {
7674  mMaximum = value;
7675 }
7676 
7684 void QCPStatisticalBox::setOutliers(const QVector<double> &values)
7685 {
7686  mOutliers = values;
7687 }
7688 
7694 void QCPStatisticalBox::setData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum)
7695 {
7696  setKey(key);
7697  setMinimum(minimum);
7698  setLowerQuartile(lowerQuartile);
7699  setMedian(median);
7700  setUpperQuartile(upperQuartile);
7701  setMaximum(maximum);
7702 }
7703 
7710 {
7711  mWidth = width;
7712 }
7713 
7720 {
7721  mWhiskerWidth = width;
7722 }
7723 
7733 {
7734  mWhiskerPen = pen;
7735 }
7736 
7744 {
7745  mWhiskerBarPen = pen;
7746 }
7747 
7755 {
7756  mMedianPen = pen;
7757 }
7758 
7765 {
7766  mOutlierSize = pixels;
7767 }
7768 
7775 {
7776  mOutlierPen = pen;
7777 }
7778 
7785 {
7786  mOutlierBrush = brush;
7787 }
7788 
7789 /* inherits documentation from base class */
7791 {
7792  setOutliers(QVector<double>());
7793  setKey(0);
7794  setMinimum(0);
7795  setLowerQuartile(0);
7796  setMedian(0);
7797  setUpperQuartile(0);
7798  setMaximum(0);
7799 }
7800 
7801 /* inherits documentation from base class */
7802 void QCPStatisticalBox::draw(QPainter *painter) const
7803 {
7804  if (!mVisible) return;
7805  painter->setClipRect(mKeyAxis->axisRect().united(mValueAxis->axisRect()));
7806 
7807  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGraphs));
7808  drawQuartileBox(painter);
7809  drawMedian(painter);
7810  drawWhiskers(painter);
7811  drawOutliers(painter);
7812 }
7813 
7814 /* inherits documentation from base class */
7815 void QCPStatisticalBox::drawLegendIcon(QPainter *painter, const QRect &rect) const
7816 {
7817  // draw filled rect:
7818  painter->setRenderHint(QPainter::Antialiasing, mParentPlot->antialiasedElements().testFlag(QCustomPlot::AEGraphs));
7819  painter->setPen(mPen);
7820  painter->setBrush(mBrush);
7821  QRect r = QRect(0, 0, rect.width()*0.67, rect.height()*0.67);
7822  r.moveCenter(rect.center());
7823  painter->drawRect(r);
7824 }
7825 
7830 void QCPStatisticalBox::drawQuartileBox(QPainter *painter) const
7831 {
7832  QRectF box;
7833  box.setTopLeft(coordsToPixels(mKey-mWidth*0.5, mUpperQuartile));
7834  box.setBottomRight(coordsToPixels(mKey+mWidth*0.5, mLowerQuartile));
7835  painter->setPen(mPen);
7836  painter->setBrush(mBrush);
7837  painter->drawRect(box);
7838 }
7839 
7844 void QCPStatisticalBox::drawMedian(QPainter *painter) const
7845 {
7846  QLineF medianLine;
7847  medianLine.setP1(coordsToPixels(mKey-mWidth*0.5, mMedian));
7848  medianLine.setP2(coordsToPixels(mKey+mWidth*0.5, mMedian));
7849  painter->setPen(mMedianPen);
7850  painter->drawLine(medianLine);
7851 }
7852 
7857 void QCPStatisticalBox::drawWhiskers(QPainter *painter) const
7858 {
7859  QLineF backboneMin, backboneMax, barMin, barMax;
7860  backboneMax.setPoints(coordsToPixels(mKey, mUpperQuartile), coordsToPixels(mKey, mMaximum));
7861  backboneMin.setPoints(coordsToPixels(mKey, mLowerQuartile), coordsToPixels(mKey, mMinimum));
7864  painter->setPen(mWhiskerPen);
7865  painter->drawLine(backboneMin);
7866  painter->drawLine(backboneMax);
7867  painter->setPen(mWhiskerBarPen);
7868  painter->drawLine(barMin);
7869  painter->drawLine(barMax);
7870 }
7871 
7876 void QCPStatisticalBox::drawOutliers(QPainter *painter) const
7877 {
7878  painter->setPen(mOutlierPen);
7879  painter->setBrush(mOutlierBrush);
7880  for (int i=0; i<mOutliers.size(); ++i)
7881  painter->drawEllipse(coordsToPixels(mKey, mOutliers.at(i)), mOutlierSize*0.5, mOutlierSize*0.5);
7882 }
7883 
7884 /* inherits documentation from base class */
7885 QCPRange QCPStatisticalBox::getKeyRange(bool &validRange, SignDomain inSignDomain) const
7886 {
7887  validRange = mWidth > 0;
7888  if (inSignDomain == SDBoth)
7889  {
7890  return QCPRange(mKey-mWidth*0.5, mKey+mWidth*0.5);
7891  } else if (inSignDomain == SDNegative)
7892  {
7893  if (mKey+mWidth*0.5 < 0)
7894  return QCPRange(mKey-mWidth*0.5, mKey+mWidth*0.5);
7895  else if (mKey < 0)
7896  return QCPRange(mKey-mWidth*0.5, mKey);
7897  else
7898  {
7899  validRange = false;
7900  return QCPRange();
7901  }
7902  } else if (inSignDomain == SDPositive)
7903  {
7904  if (mKey-mWidth*0.5 > 0)
7905  return QCPRange(mKey-mWidth*0.5, mKey+mWidth*0.5);
7906  else if (mKey > 0)
7907  return QCPRange(mKey, mKey+mWidth*0.5);
7908  else
7909  {
7910  validRange = false;
7911  return QCPRange();
7912  }
7913  }
7914  validRange = false;
7915  return QCPRange();
7916 }
7917 
7918 /* inherits documentation from base class */
7919 QCPRange QCPStatisticalBox::getValueRange(bool &validRange, SignDomain inSignDomain) const
7920 {
7921  if (inSignDomain == SDBoth)
7922  {
7923  double lower = qMin(mMinimum, qMin(mMedian, mLowerQuartile));
7924  double upper = qMax(mMaximum, qMax(mMedian, mUpperQuartile));
7925  for (int i=0; i<mOutliers.size(); ++i)
7926  {
7927  if (mOutliers.at(i) < lower)
7928  lower = mOutliers.at(i);
7929  if (mOutliers.at(i) > upper)
7930  upper = mOutliers.at(i);
7931  }
7932  validRange = upper > lower;
7933  return QCPRange(lower, upper);
7934  } else
7935  {
7936  QVector<double> values; // values that must be considered (i.e. all outliers and the five box-parameters)
7937  values.reserve(mOutliers.size() + 5);
7938  values << mMaximum << mUpperQuartile << mMedian << mLowerQuartile << mMinimum;
7939  values << mOutliers;
7940  // go through values and find the ones in legal range:
7941  bool haveUpper = false;
7942  bool haveLower = false;
7943  double upper = 0;
7944  double lower = 0;
7945  for (int i=0; i<values.size(); ++i)
7946  {
7947  if ((inSignDomain == SDNegative && values.at(i) < 0) ||
7948  (inSignDomain == SDPositive && values.at(i) > 0))
7949  {
7950  if (values.at(i) > upper || !haveUpper)
7951  {
7952  upper = values.at(i);
7953  haveUpper = true;
7954  }
7955  if (values.at(i) < lower || !haveLower)
7956  {
7957  lower = values.at(i);
7958  haveLower = true;
7959  }
7960  }
7961  }
7962  // return the bounds if we found some sensible values:
7963  if (haveLower && haveUpper && !qFuzzyCompare(upper+1.0, lower+1.0))
7964  {
7965  validRange = true;
7966  return QCPRange(lower, upper);
7967  } else
7968  {
7969  validRange = false;
7970  return QCPRange();
7971  }
7972  }
7973 }
7974 
7975 
7976 
7977 
7978 
7979 
7980 
7981 
7982 
7983 
7984 
7985 
7986 
7987 
a square which is not filled, with a plus inside
Definition: qcustomplot.h:206
void setOutliers(const QVector< double > &values)
double size() const
Error bars for the key dimension of the data point are shown.
Definition: qcustomplot.h:217
QCPGraph * addGraph(QCPAxis *keyAxis=0, QCPAxis *valueAxis=0)
QCPStatisticalBox(QCPAxis *keyAxis, QCPAxis *valueAxis)
void setWhiskerBarPen(const QPen &pen)
int clearPlottables()
Legend is positioned in the bottom right corner of the axis rect with distance to the border correspo...
Definition: qcustomplot.h:566
static const double maxRange
Definition: qcustomplot.h:509
Axis is vertical and on the right side of the axis rect of the parent QCustomPlot.
Definition: qcustomplot.h:712
void setLineStyle(int ls)
Definition: qcustomplot.cc:494
line is drawn as steps where the step height is the value of the right data point ...
Definition: qcustomplot.h:184
void setDateTimeFormat(const QString &format)
QPen mErrorPen
Definition: qcustomplot.h:279
int plottableCount() const
void setAutoTickLabels(bool on)
Legend is horizontally centered at the top of the axis rect with distance to the border corresponding...
Definition: qcustomplot.h:563
void setRangeZoomFactor(double horizontalFactor, double verticalFactor)
void setScaleRatio(const QCPAxis *otherAxis, double ratio=1.0)
QMap< double, QCPData > QCPDataMap
Definition: qcustomplot.h:75
line is drawn as steps where the step is in between two data points
Definition: qcustomplot.h:185
QCPAxis(QCustomPlot *parentPlot, AxisType type)
A class holding the data of one single data point for QCPCurve.
Definition: qcustomplot.h:79
virtual int calculateAutoSubTickCount(double tickStep) const
void rescaleKeyAxis(bool onlyEnlarge=false) const
void setAutoMargin(bool enabled)
A legend item representing a plottable with an icon and the plottable name.
Definition: qcustomplot.h:533
QCPGraph * mChannelFillGraph
Definition: qcustomplot.h:287
void setIconTextPadding(int padding)
double mErrorBarSize
Definition: qcustomplot.h:285
QFont font() const
Definition: qcustomplot.h:580
a square which is not filled, with a cross inside
Definition: qcustomplot.h:205
virtual void rescaleValueAxis(bool onlyEnlarge, bool includeErrorBars) const
Definition: qcustomplot.cc:752
void setMedianPen(const QPen &pen)
void getScatterPlotData(QVector< QCPData > *pointData) const
Definition: qcustomplot.cc:891
double maximum() const
Definition: qcustomplot.h:436
void visibleTickBounds(int &lowIndex, int &highIndex) const
virtual void mouseMoveEvent(QMouseEvent *event)
virtual ~QCPGraph()
Definition: qcustomplot.cc:274
a circle which is filled with the color of the graph&#39;s pen (not the brush!)
Definition: qcustomplot.h:200
void setBasePen(const QPen &pen)
void setRangeZoomAxes(QCPAxis *horizontal, QCPAxis *vertical)
double key
Definition: qcustomplot.h:65
The abstract base class for all items in a QCPLegend.
Definition: qcustomplot.h:512
bool hasItemWithPlottable(const QCPAbstractPlottable *plottable) const
const QCPDataMap * data() const
Definition: qcustomplot.h:228
void setPositionStyle(PositionStyle legendPositionStyle)
virtual void draw(QPainter *painter) const
void setMarginBottom(int margin)
void setName(const QString &name)
void setPaddingRight(int padding)
void setRangeReversed(bool reversed)
QPointF upperFillBasePoint(double upperKey) const
void setAxisRect(const QRect &arect)
QRect axisRect() const
Definition: qcustomplot.h:743
void setWhiskerWidth(double width)
int mIconTextPadding
Definition: qcustomplot.h:653
virtual int calculateMargin() const
const QCPRange range() const
Definition: qcustomplot.h:746
void drawScatter(QPainter *painter, double x, double y, ScatterStyle style) const
virtual ~QCPAxis()
virtual void wheelEvent(QWheelEvent *event)
void getImpulsePlotData(QVector< QPointF > *lineData, QVector< QCPData > *pointData) const
void removeDataBefore(double key)
Definition: qcustomplot.cc:648
virtual void generateTickVectors()
double value
Definition: qcustomplot.h:65
void setTickLabels(bool show)
void setBrush(const QBrush &brush)
void setVisible(bool on)
double mScatterSize
Definition: qcustomplot.h:282
int mPaddingBottom
Definition: qcustomplot.h:651
void setRange(double lower, double upper)
double width() const
Definition: qcustomplot.h:438
void setLowerQuartile(double value)
void setRangeDrag(Qt::Orientations orientations)
void setWhiskerPen(const QPen &pen)
void setAutoTickStep(bool on)
int findIndexAboveY(const QVector< QPointF > *data, double y) const
void setSubTickCount(int count)
void setAutoTicks(bool on)
void setMarginLeft(int margin)
void setPosition(const QPoint &pixelPosition)
static bool validRange(double lower, double upper)
QBrush mBrush
Definition: qcustomplot.h:645
void setAutoSubTicks(bool on)
void setValueAxis(QCPAxis *axis)
void setPadding(int padding)
A plottable representing a bar chart in a plot.
Definition: qcustomplot.h:372
virtual void rescaleKeyAxis(bool onlyEnlarge, bool includeErrorBars) const
Definition: qcustomplot.cc:721
QCPBars * mBarBelow
Definition: qcustomplot.h:407
QFont mFont
Definition: qcustomplot.h:646
bool removeGraph(QCPGraph *graph)
QCustomPlot * parentPlot() const
Definition: qcustomplot.h:741
QCustomPlot * mParentPlot
Definition: qcustomplot.h:153
void setAxisRect(const QRect &rect)
void setWidth(double width)
void moveAbove(QCPBars *bars)
virtual void mousePressEvent(QMouseEvent *event)
void moveBelow(QCPBars *bars)
void setScaleLogBase(double base)
QSize mIconSize
Definition: qcustomplot.h:648
void setRangeUpper(double upper)
virtual QCPRange getKeyRange(bool &validRange, SignDomain inSignDomain=SDBoth) const
a cross (x)
Definition: qcustomplot.h:197
void reArrange()
int getItemIndex(const QPoint *point)
void setMarginBottom(int margin)
double value
Definition: qcustomplot.h:83
void drawFill(QPainter *painter, QVector< QPointF > *lineData) const
QRect mAxisRect
Definition: qcustomplot.h:1014
virtual void draw(QPainter *painter)
void setTickLabelRotation(double degrees)
int mMarginBottom
Definition: qcustomplot.h:652
QPen mIconBorderPen
Definition: qcustomplot.h:644
virtual ~QCPStatisticalBox()
QCPBars(QCPAxis *keyAxis, QCPAxis *valueAxis)
void setRangeZoom(Qt::Orientations orientations)
void setZeroLinePen(const QPen &pen)
void setSize(const QSize &size)
virtual ~QCPBars()
void setFont(const QFont &font)
Legend is positioned in the top left corner of the axis rect with distance to the border correspondin...
Definition: qcustomplot.h:562
void setWidth(double width)
double valueErrorPlus
Definition: qcustomplot.h:67
QBrush brush() const
Definition: qcustomplot.h:579
void setGridPen(const QPen &pen)
void removeDataBefore(double key)
virtual void drawMedian(QPainter *painter) const
The abstract base class for all data representing objects in a plot.
Definition: qcustomplot.h:112
QPoint mPosition
Definition: qcustomplot.h:647
void savePngScaled(const QString &fileName, double scale, int width=0, int height=0)
void getVisibleDataBounds(QCPDataMap::const_iterator &lower, QCPDataMap::const_iterator &upper, int &count) const
QCPRange sanitizedForLogScale() const
QPen medianPen() const
Definition: qcustomplot.h:442
Both sign domains, including zero, i.e. all (rational) numbers.
Definition: qcustomplot.h:150
an equilateral triangle which is not filled, standing on corner
Definition: qcustomplot.h:204
void setAutoTickCount(int approximateCount)
virtual void clearData()
No error bars are shown.
Definition: qcustomplot.h:216
QPointF outsideCoordsToPixels(double key, double value, int region) const
void removeData(double fromt, double tot)
QPointF lowerFillBasePoint(double lowerKey) const
virtual ~QCPCurve()
void removeDataAfter(double t)
double valueErrorMinus
Definition: qcustomplot.h:67
double minimum() const
Definition: qcustomplot.h:432
void setErrorBarSize(double size)
Definition: qcustomplot.cc:550
virtual void clearData()
Definition: qcustomplot.cc:700
void setTickLabelFont(const QFont &font)
Normal linear scaling.
Definition: qcustomplot.h:731
void setMarginRight(int margin)
PositionStyle mPositionStyle
Definition: qcustomplot.h:649
virtual void drawLegendIcon(QPainter *painter, const QRect &rect) const
A plottable representing a graph in a plot.
Definition: qcustomplot.h:171
int iconTextPadding() const
Definition: qcustomplot.h:597
QCPAbstractLegendItem(QCPLegend *parent)
QCPCurveDataMap * mData
Definition: qcustomplot.h:358
void setGrid(bool show)
void getStepCenterPlotData(QVector< QPointF > *lineData, QVector< QCPData > *pointData) const
void setRangeLower(double lower)
The central class which is also the QWidget which displays the plot and interacts with the user...
Definition: qcustomplot.h:895
void drawLinePlot(QPainter *painter, QVector< QPointF > *lineData) const
void setMedian(double value)
void setMinimum(double value)
Manages a single axis inside a QCustomPlot.
Definition: qcustomplot.h:667
double upperQuartile() const
Definition: qcustomplot.h:435
virtual ~QCPLegend()
double lowerQuartile() const
Definition: qcustomplot.h:433
double width() const
Definition: qcustomplot.h:381
void setTickPen(const QPen &pen)
void setMaximum(double value)
int findIndexAboveX(const QVector< QPointF > *data, double x) const
Legend is positioned in the top right corner of the axis rect with distance to the border correspondi...
Definition: qcustomplot.h:564
Manages a legend inside a QCustomPlot.
Definition: qcustomplot.h:554
a circle which is not filled, with one vertical and two downward diagonal lines
Definition: qcustomplot.h:209
virtual QCPRange getKeyRange(bool &validRange, SignDomain inSignDomain=SDBoth) const
bool addPlottable(QCPAbstractPlottable *plottable)
int mPaddingLeft
Definition: qcustomplot.h:651
QCPAbstractPlottable * plottable()
void removeData(double fromKey, double toKey)
void setAxisType(AxisType type)
virtual void drawQuartileBox(QPainter *painter) const
virtual void calculateAutoPosition()
bool mAutoSize
Definition: qcustomplot.h:650
void addData(const QCPCurveDataMap &dataMap)
void setupFullAxesBox()
int itemCount() const
QCPGraph * graph() const
void setKeyAxis(QCPAxis *axis)
void savePng(const QString &fileName, int width=0, int height=0)
QPolygonF getBarPolygon(double key, double value) const
a circle which is not filled
Definition: qcustomplot.h:199
Qt::Orientation orientation() const
Definition: qcustomplot.h:827
ScatterStyle mScatterStyle
Definition: qcustomplot.h:281
virtual void clearData()
void setFont(const QFont &font)
Legend is horizontally centered at the bottom of the axis rect with distance to the border correspond...
Definition: qcustomplot.h:567
int findIndexBelowX(const QVector< QPointF > *data, double x) const
QCPAbstractPlottable(QCPAxis *keyAxis, QCPAxis *valueAxis)
virtual QCPRange getValueRange(bool &validRange, SignDomain inSignDomain=SDBoth) const
virtual QSize size(const QSize &targetSize) const
an equilateral triangle which is not filled, standing on baseline
Definition: qcustomplot.h:203
double getBaseValue(double key, bool positive) const
QCPAxis * keyAxis() const
Definition: qcustomplot.h:126
void setDataBothError(const QVector< double > &key, const QVector< double > &value, const QVector< double > &keyError, const QVector< double > &valueError)
Definition: qcustomplot.cc:436
void setPen(const QPen &pen)
void setUpperQuartile(double value)
const QCPBarDataMap * data() const
Definition: qcustomplot.h:384
void setOutlierBrush(const QBrush &brush)
void rescaleAxes()
int findIndexBelowY(const QVector< QPointF > *data, double y) const
virtual bool removeFromLegend() const
QMap< double, QCPBarData > QCPBarDataMap
Definition: qcustomplot.h:108
void removeData(double fromKey, double toKey)
Definition: qcustomplot.cc:674
double center() const
a square which is not filled
Definition: qcustomplot.h:201
virtual void drawLegendIcon(QPainter *painter, const QRect &rect) const
void setVisible(bool on)
virtual void drawLegendIcon(QPainter *painter, const QRect &rect) const
Definition: qcustomplot.cc:816
Axis is horizontal and on the bottom side of the axis rect of the parent QCustomPlot.
Definition: qcustomplot.h:714
void removeFillBasePoints(QVector< QPointF > *lineData) const
virtual QCPRange getKeyRange(bool &validRange, SignDomain inSignDomain=SDBoth) const =0
Legend is vertically centered at the left of the axis rect with distance to the border corresponding ...
Definition: qcustomplot.h:569
A class holding the data of one single data point for QCPGraph.
Definition: qcustomplot.h:61
QCPPlottableLegendItem(QCPLegend *parent, const QCPAbstractPlottable *plottable)
virtual QCPRange getValueRange(bool &validRange, SignDomain inSignDomain=SDBoth) const =0
void setSubGridPen(const QPen &pen)
QPen pen() const
Definition: qcustomplot.h:124
QCPAxis * rangeDragAxis(Qt::Orientation orientation)
void setTickLength(int inside, int outside=0)
line is drawn as steps where the step height is the value of the left data point
Definition: qcustomplot.h:183
Legend is vertically centered at the right of the axis rect with distance to the border corresponding...
Definition: qcustomplot.h:565
void setScatterSize(double size)
Definition: qcustomplot.cc:515
void coordsToPixels(double key, double value, double &x, double &y) const
void setErrorBarSkipSymbol(bool enabled)
Definition: qcustomplot.cc:564
void setTextWrap(bool wrap)
double median() const
Definition: qcustomplot.h:434
void normalize()
int mMarginLeft
Definition: qcustomplot.h:652
bool visible() const
Definition: qcustomplot.h:123
void getPlotData(QVector< QPointF > *lineData, QVector< QCPData > *pointData) const
Definition: qcustomplot.cc:868
void setTitleFont(const QFont &font)
Logarithmic scaling with correspondingly transformed plots and (major) tick marks at every base power...
Definition: qcustomplot.h:732
data points are represented by a straight line parallel to the value axis, which ranges down/up to th...
Definition: qcustomplot.h:186
virtual void getMaxTickLabelSize(const QFont &font, const QString &text, QSize *tickLabelsSize) const
void setScatterPixmap(const QPixmap &pixmap)
Definition: qcustomplot.cc:525
void setIconBorderPen(const QPen &pen)
const QCPCurveDataMap * data() const
Definition: qcustomplot.h:338
virtual void drawSubGrid(QPainter *painter)
void setAutoSize(bool on)
void setScaleType(ScaleType type)
virtual void drawWhiskers(QPainter *painter) const
void setNumberPrecision(int precision)
static const double minRange
Definition: qcustomplot.h:508
void removeDataBefore(double t)
virtual void drawLegendIcon(QPainter *painter, const QRect &rect) const =0
double keyErrorPlus
Definition: qcustomplot.h:66
QVector< double > mOutliers
Definition: qcustomplot.h:469
void setTickVectorLabels(QVector< QString > *vec, bool copy=false)
void rescaleAxes(bool onlyEnlarge=false) const
void setData(QCPBarDataMap *data, bool copy=false)
static void connectBars(QCPBars *lower, QCPBars *upper)
bool rangeReversed() const
Definition: qcustomplot.h:747
bool mVisible
Definition: qcustomplot.h:650
QCustomPlot * parentPlot() const
Definition: qcustomplot.h:121
void setPaddingBottom(int padding)
The negative sign domain, i.e. numbers smaller than zero.
Definition: qcustomplot.h:149
void scaleRange(double factor, double center)
void getCurveData(QVector< QPointF > *lineData) const
void setData(QCPCurveDataMap *data, bool copy=false)
bool mErrorBarSkipSymbol
Definition: qcustomplot.h:286
double baseLog(double value) const
QCPBars * mBarAbove
Definition: qcustomplot.h:407
QSize mMinimumSize
Definition: qcustomplot.h:648
void rescaleValueAxis(bool onlyEnlarge=false) const
double key
Definition: qcustomplot.h:100
QCPGraph(QCPAxis *keyAxis, QCPAxis *valueAxis)
Definition: qcustomplot.cc:256
virtual QCPRange getKeyRange(bool &validRange, SignDomain inSignDomain=SDBoth) const
int mItemSpacing
Definition: qcustomplot.h:653
void setDataKeyError(const QVector< double > &key, const QVector< double > &value, const QVector< double > &keyError)
Definition: qcustomplot.cc:385
virtual bool addToLegend() const
void setPaddingTop(int padding)
QString name() const
Definition: qcustomplot.h:122
Axis is horizontal and on the top side of the axis rect of the parent QCustomPlot.
Definition: qcustomplot.h:713
double pixelToCoord(double value) const
Error bars for both key and value dimensions of the data point are shown.
Definition: qcustomplot.h:219
virtual QCPRange getValueRange(bool &validRange, SignDomain inSignDomain=SDBoth) const
void addData(const QCPBarDataMap &dataMap)
QSize size() const
Definition: qcustomplot.h:584
void setColor(const QColor &color)
QSize iconSize() const
Definition: qcustomplot.h:596
void clearItems()
void setTickStep(double step)
const AntialiasedElements antialiasedElements() const
Definition: qcustomplot.h:949
int mPaddingTop
Definition: qcustomplot.h:651
void setTickLabelType(LabelType type)
void removeDataAfter(double key)
bool hasItem(QCPAbstractLegendItem *item) const
void getLinePlotData(QVector< QPointF > *lineData, QVector< QCPData > *pointData) const
Definition: qcustomplot.cc:936
Error bars for the value dimension of the data point are shown.
Definition: qcustomplot.h:218
void moveRange(double diff)
void setLabelFont(const QFont &font)
virtual void mouseDoubleClickEvent(QMouseEvent *event)
virtual QCPRange getValueRange(bool &validRange, SignDomain inSignDomain=SDBoth) const
ErrorType mErrorType
Definition: qcustomplot.h:284
double keyErrorMinus
Definition: qcustomplot.h:66
void setData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum)
void setSubTickPen(const QPen &pen)
int clearGraphs()
void setTickVector(QVector< double > *vec, bool copy=false)
void addFillBasePoints(QVector< QPointF > *lineData) const
QSize mSize
Definition: qcustomplot.h:648
void setNumberFormat(const QString &formatCode)
void setMargin(int left, int right, int top, int bottom)
virtual void rescaleAxes(bool onlyEnlarge, bool includeErrorBars) const
Definition: qcustomplot.cc:710
ErrorType errorType() const
Definition: qcustomplot.h:233
QCPRange sanitizedForLinScale() const
QCPLegend * legend
Definition: qcustomplot.h:1007
void setItemSpacing(int spacing)
void setMarginTop(int margin)
double rangeZoomFactor(Qt::Orientation orientation)
double basePow(double value) const
QPen whiskerPen() const
Definition: qcustomplot.h:440
double key
Definition: qcustomplot.h:83
int mMarginTop
Definition: qcustomplot.h:652
void setMarginTop(int margin)
QCustomPlot * mParentPlot
Definition: qcustomplot.h:656
QCPAbstractLegendItem * item(int index) const
QString numberFormat() const
QCPDataMap * mData
Definition: qcustomplot.h:278
void setErrorType(ErrorType errorType)
Definition: qcustomplot.cc:533
a circle which is not filled, with a plus inside
Definition: qcustomplot.h:208
bool addItem(QCPAbstractLegendItem *item)
void setVisible(bool visible)
QRect mAxisRect
Definition: qcustomplot.h:844
void setDataValueError(const QVector< double > &key, const QVector< double > &value, const QVector< double > &valueError)
Definition: qcustomplot.cc:334
a plus (+)
Definition: qcustomplot.h:198
void setTickLabelPadding(int padding)
virtual void resizeEvent(QResizeEvent *event)
virtual QCPRange getKeyRange(bool &validRange, SignDomain inSignDomain=SDBoth) const
void setBrush(const QBrush &brush)
void updateAxisRect()
QCPGraph * graph(int index) const
QCPPlottableLegendItem * itemWithPlottable(const QCPAbstractPlottable *plottable) const
void setOutlierPen(const QPen &pen)
void setKey(double key)
void setTitle(const QString &title)
virtual void drawGrid(QPainter *painter)
QCPBarDataMap * mData
Definition: qcustomplot.h:405
QCPAxis * valueAxis() const
Definition: qcustomplot.h:127
QCPCurve(QCPAxis *keyAxis, QCPAxis *valueAxis)
const QPolygonF getChannelFillPolygon(const QVector< QPointF > *lineData) const
QRect mViewport
Definition: qcustomplot.h:1013
void setAutoAddPlottableToLegend(bool on)
void setAntialiasedElements(const AntialiasedElements &antialiasedElements)
void setAntialiasedElement(AntialiasedElement antialiasedElement, bool enabled)
void setAxisBackgroundScaledMode(Qt::AspectRatioMode mode)
void removeDataAfter(double key)
Definition: qcustomplot.cc:659
void setMargin(int left, int right, int top, int bottom)
void setPadding(int left, int right, int top, int bottom)
The positive sign domain, i.e. numbers greater than zero.
Definition: qcustomplot.h:151
void setBorderPen(const QPen &pen)
double key() const
Definition: qcustomplot.h:431
void savePdf(const QString &fileName, bool noCosmeticPen=false, int width=0, int height=0)
void setIconSize(const QSize &size)
virtual void mouseReleaseEvent(QMouseEvent *event)
int mMarginRight
Definition: qcustomplot.h:652
void setData(QCPDataMap *data, bool copy=false)
Definition: qcustomplot.cc:295
double coordToPixel(double value) const
void setPaddingLeft(int padding)
virtual void drawAxisBackground(QPainter *painter)
QPen iconBorderPen() const
Definition: qcustomplot.h:598
void setAxisBackground(const QPixmap &pm)
#define FUNCNAME
Definition: qcustomplot.h:50
QPixmap mScatterPixmap
Definition: qcustomplot.h:283
void setTicks(bool show)
void setAxisBackgroundScaled(bool scaled)
void setErrorPen(const QPen &pen)
Definition: qcustomplot.cc:542
double upper
Definition: qcustomplot.h:494
virtual void drawLegendIcon(QPainter *painter, const QRect &rect) const
void drawError(QPainter *painter, double x, double y, const QCPData &data) const
void setSubTickLength(int inside, int outside=0)
void setMinimumSize(const QSize &size)
Represents the range an axis is encompassing.
Definition: qcustomplot.h:491
virtual void calculateAutoSize()
QRect axisRect() const
Definition: qcustomplot.h:937
QPen outlierPen() const
Definition: qcustomplot.h:444
virtual void drawAxis(QPainter *painter)
const QCPAbstractPlottable * mPlottable
Definition: qcustomplot.h:544
double lower
Definition: qcustomplot.h:494
no scatter symbols are drawn (e.g. data only represented with lines, see setLineStyle) ...
Definition: qcustomplot.h:195
int graphCount() const
QFont font() const
Definition: qcustomplot.h:519
virtual void generateAutoTicks()
bool removePlottable(QCPAbstractPlottable *plottable)
void setOutlierSize(double pixels)
ScaleType scaleType() const
Definition: qcustomplot.h:744
int mPaddingRight
Definition: qcustomplot.h:651
bool removeItem(int index)
virtual void draw(QPainter *painter) const
Definition: qcustomplot.cc:779
friend class QCPPlottableLegendItem
Definition: qcustomplot.h:168
void addData(const QCPDataMap &dataMap)
Definition: qcustomplot.cc:602
virtual void draw(QPainter *painter)
void drawScatterPlot(QPainter *painter, QVector< QCPData > *pointData) const
a star with eight arms, i.e. a combination of cross and plus
Definition: qcustomplot.h:202
virtual void paintEvent(QPaintEvent *event)
QList< QCPAbstractLegendItem * > mItems
Definition: qcustomplot.h:657
QCPRange mRange
Definition: qcustomplot.h:841
void setLabel(const QString &str)
QMap< double, QCPCurveData > QCPCurveDataMap
Definition: qcustomplot.h:92
virtual void draw(QPainter *painter) const
void setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical)
virtual void drawOutliers(QPainter *painter) const
void setMarginLeft(int margin)
virtual void clearData()
data points are connected by a straight line
Definition: qcustomplot.h:182
LineStyle mLineStyle
Definition: qcustomplot.h:280
void setMarginRight(int margin)
virtual void drawTickLabel(QPainter *painter, double position, int distanceToAxis, const QString &text, QSize *tickLabelsSize)
Axis is vertical and on the left side of the axis rect of the parent QCustomPlot. ...
Definition: qcustomplot.h:711
QCPLegend(QCustomPlot *parentPlot)
QBrush brush() const
Definition: qcustomplot.h:125
virtual void draw(QPainter *painter) const
virtual QCPRange getValueRange(bool &validRange, SignDomain inSignDomain=SDBoth) const
a single pixel, setScatterSize has no influence on its size.
Definition: qcustomplot.h:196
a circle which is not filled, with a cross inside
Definition: qcustomplot.h:207
AxisType axisType() const
Definition: qcustomplot.h:742
void setChannelFillGraph(QCPGraph *targetGraph)
Definition: qcustomplot.cc:578
void getStepRightPlotData(QVector< QPointF > *lineData, QVector< QCPData > *pointData) const
void setSubGrid(bool show)
double mWidth
Definition: qcustomplot.h:406
void setScatterStyle(int ss)
Definition: qcustomplot.cc:504
double value
Definition: qcustomplot.h:100
QCPAxis * rangeZoomAxis(Qt::Orientation orientation)
QCPGraph * channelFillGraph() const
Definition: qcustomplot.h:237
Legend is positioned in the bottom left corner of the axis rect with distance to the border correspon...
Definition: qcustomplot.h:568
void getStepLeftPlotData(QVector< QPointF > *lineData, QVector< QCPData > *pointData) const
Definition: qcustomplot.cc:991
void drawImpulsePlot(QPainter *painter, QVector< QPointF > *lineData) const
virtual void draw(QPainter *painter, const QRect &rect) const
QPen mBorderPen
Definition: qcustomplot.h:644
QPoint position() const
Definition: qcustomplot.h:582
a custom pixmap specified by setScatterPixmap, centered on the data point coordinates. setScatterSize has no influence on its size.
Definition: qcustomplot.h:210
A class holding the data of one single data point (one bar) for QCPBars.
Definition: qcustomplot.h:96
void setLabelPadding(int padding)