CustomLineGraph.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. #include "CustomLineGraph.h"
  2. #include <QDebug>
  3. #include <QBrush>
  4. #include <random>
  5. #include <algorithm>
  6. #define X_AXIS_GAP_AMOUNT 5
  7. #define Y_AXIS_GAP_AMOUNT 10
  8. #define X_AXIS_NUMBER_LINE_LENGTH 5
  9. #define Y_AXIS_NUMBER_LINE_LENGTH 5
  10. CustomLineGraph::CustomLineGraph(QWidget* parent, QString title, int lineWidth, double circleRadius):QWidget(parent), title(title), lineWidth(lineWidth), circleRadius(circleRadius), linePen(Qt::blue), rectPen(Qt::red), axisPen(Qt::black)
  11. {
  12. linePen.setWidth(lineWidth);
  13. linePen.setJoinStyle(Qt::PenJoinStyle::RoundJoin);
  14. //Populate data with DummyData
  15. //Draw Points
  16. std::random_device rd; //Will be used to obtain a seed for the random number engine
  17. std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
  18. std::uniform_real_distribution<double> doubleDistr(0.0, 1.0);
  19. hueoffset = doubleDistr(gen);
  20. }
  21. CustomLineGraph::~CustomLineGraph()
  22. {
  23. }
  24. void CustomLineGraph::paintEvent(QPaintEvent* event)
  25. {
  26. QPainter painter(this);
  27. painter.setRenderHint(QPainter::RenderHint::HighQualityAntialiasing);
  28. if (seriesVec.empty()) {
  29. painter.setPen(axisPen);
  30. painter.setFont(QFont("Arial", 12, QFont::Bold));
  31. painter.drawText(rect(), Qt::AlignCenter, "No Data connected");
  32. return;
  33. }
  34. //Calculate LineRect
  35. QRect lineRect(rect());
  36. lineRect.setBottom(lineRect.bottom() - 20);
  37. lineRect.setLeft(lineRect.left() + 40);
  38. lineRect.setTop(lineRect.top() + 20);
  39. lineRect.setRight(lineRect.right() - 10);
  40. lineRect.setWidth(lineRect.width() - (lineRect.width() % X_AXIS_GAP_AMOUNT) + 1);
  41. lineRect.setHeight(lineRect.height() - (lineRect.height() % Y_AXIS_GAP_AMOUNT) + 1);
  42. //draw Title
  43. painter.setPen(axisPen);
  44. painter.setFont(QFont("Arial", 12));
  45. QRect titleRect(QPoint(rect().left(), rect().top()), QPoint(lineRect.right(), lineRect.top()));
  46. painter.drawText(titleRect, Qt::AlignCenter, title);
  47. painter.setFont(QFont("Arial", 8));
  48. //draw X-Axis
  49. QRect xAxisRect(QPoint(lineRect.left(), lineRect.bottom()), QPoint(lineRect.right(), rect().bottom()));
  50. QPainterPath xAxisPath;
  51. xAxisPath.moveTo(xAxisRect.left(), xAxisRect.top());
  52. xAxisPath.lineTo(xAxisRect.right(), xAxisRect.top());
  53. int xGap = xAxisRect.width() / X_AXIS_GAP_AMOUNT;
  54. QRect textRect(0, 0, 40, 9);
  55. for (int i = 0; i < 11; i++) {
  56. xAxisPath.moveTo(xAxisRect.left() + i * xGap, xAxisRect.top() + X_AXIS_NUMBER_LINE_LENGTH);
  57. xAxisPath.lineTo(xAxisRect.left() + i * xGap, xAxisRect.top());
  58. textRect.moveCenter(QPoint(xAxisRect.left() + i * xGap, xAxisRect.top() + 10));
  59. painter.drawText(textRect, Qt::AlignCenter, xAxisNumbers[i]);
  60. }
  61. painter.drawPath(xAxisPath);
  62. //draw Y-Axis
  63. QRect yAxisRect(QPoint(rect().left(), lineRect.top()), QPoint(lineRect.left(), lineRect.bottom()));
  64. QPainterPath yAxisPath;
  65. yAxisPath.moveTo(yAxisRect.right(), yAxisRect.bottom());
  66. yAxisPath.lineTo(yAxisRect.right(), yAxisRect.top());
  67. int yGap = yAxisRect.height() / Y_AXIS_GAP_AMOUNT;
  68. for (int i = 0; i < 11; i++) {
  69. yAxisPath.moveTo(yAxisRect.right() - Y_AXIS_NUMBER_LINE_LENGTH, yAxisRect.bottom() - i * yGap);
  70. yAxisPath.lineTo(yAxisRect.right(), yAxisRect.bottom() - i * yGap);
  71. textRect.moveCenter(QPoint(yAxisRect.right() - Y_AXIS_NUMBER_LINE_LENGTH - 20, yAxisRect.bottom() - i * yGap));
  72. painter.drawText(textRect, Qt::AlignCenter, yAxisNumbers[i]);
  73. }
  74. painter.drawPath(yAxisPath);
  75. painter.setPen(linePen);
  76. double stregth_factorX = lineRect.width() / rangeGraphX;
  77. double stregth_factorY = lineRect.height() / rangeGraphY;
  78. for (LineGraphSeries lgs : seriesVec) {
  79. linePen.setColor(lgs.color);
  80. painter.setPen(linePen);
  81. QPointF translation(lineRect.left(), lineRect.top());
  82. if (!lgs.type == LineGraphSeries::Dot) {
  83. QPainterPath painterPath;
  84. painterPath.moveTo(transformPoint(lgs.data[0], stregth_factorX, stregth_factorY));
  85. for(int i = 1; i < lgs.data.size(); i++) {
  86. painterPath.lineTo(transformPoint(lgs.data[i], stregth_factorX, stregth_factorY));
  87. }
  88. painterPath.translate(translation);
  89. painter.drawPath(painterPath);
  90. }
  91. /* only draw when circle can be seen*/
  92. if (!(lineWidth > 4 * circleRadius || circleRadius == 0)) {
  93. painter.setBrush(lgs.color);
  94. for (int i = 0; i < lgs.data.size(); i++) {
  95. painter.drawEllipse(transformPoint(lgs.data[i], stregth_factorX, stregth_factorY) + translation, circleRadius, circleRadius);
  96. }
  97. painter.setBrush(Qt::BrushStyle::NoBrush);
  98. }
  99. }
  100. }
  101. QPointF CustomLineGraph::transformPoint(QPointF& point, double stregth_factorX, double stregth_factorY)
  102. {
  103. return QPointF((point.x() - minGraphX)* stregth_factorX , (maxGraphY - point.y()) * stregth_factorY);
  104. }
  105. void CustomLineGraph::calculateMinMaxGraphXY()
  106. {
  107. minGraphX = std::min_element(std::begin(seriesVec), std::end(seriesVec), [](const LineGraphSeries& a, const LineGraphSeries& b) -> bool {return a.minX < b.minX; })->minX;
  108. maxGraphX = std::max_element(std::begin(seriesVec), std::end(seriesVec), [](const LineGraphSeries& a, const LineGraphSeries& b) -> bool {return a.maxX < b.maxX; })->maxX;
  109. minGraphY = std::min_element(std::begin(seriesVec), std::end(seriesVec), [](const LineGraphSeries& a, const LineGraphSeries& b) -> bool {return a.minY < b.minY; })->minY;
  110. maxGraphY = std::max_element(std::begin(seriesVec), std::end(seriesVec), [](const LineGraphSeries& a, const LineGraphSeries& b) -> bool {return a.maxY < b.maxY; })->maxY;
  111. rangeGraphX = std::abs(maxGraphX - minGraphX);
  112. rangeGraphY = std::abs(maxGraphY - minGraphY);
  113. if (std::abs(rangeGraphX) < 0.01) {
  114. minGraphX -= 1.0;
  115. maxGraphX += 1.0;
  116. rangeGraphX = 2;
  117. }
  118. if (std::abs(rangeGraphY) < 0.01) {
  119. minGraphY -= 1.0;
  120. maxGraphY += 1.0;
  121. rangeGraphY = 2;
  122. }
  123. }
  124. void CustomLineGraph::popullateLineGraphSeries(std::vector<QPointF>& line, LineGraphSeries& lgs)
  125. {
  126. lgs.data = line;
  127. auto pairX = std::minmax_element(std::begin(lgs.data), std::end(lgs.data), [](const QPointF& a, const QPointF& b) -> bool {return a.x() < b.x(); });
  128. lgs.minX = pairX.first->x();
  129. lgs.maxX = pairX.second->x();
  130. auto pairY = std::minmax_element(std::begin(lgs.data), std::end(lgs.data), [](const QPointF& a, const QPointF& b) -> bool {return a.y() < b.y(); });
  131. lgs.minY = pairY.first->y();
  132. lgs.maxY = pairY.second->y();
  133. }
  134. void CustomLineGraph::addSeries(std::vector<QPointF>& line, QColor color, LineGraphSeries::SeriesType type)
  135. {
  136. if (line.empty()) {
  137. return;
  138. }
  139. LineGraphSeries lgs;
  140. lgs.type = type;
  141. popullateLineGraphSeries(line, lgs);
  142. lgs.color = color;
  143. seriesVec.push_back(lgs);
  144. calculateMinMaxGraphXY();
  145. generateAxisNumberStrings();
  146. }
  147. QColor CustomLineGraph::generateNextColorForGraph()
  148. {
  149. /* http://devmag.org.za/2012/07/29/how-to-choose-colours-procedurally-algorithms/
  150. use golden ratio 0.618033988749895f
  151. */
  152. hueoffset = std::fmod(hueoffset + 0.618033988749895f, 1.0);
  153. return QColor::fromHsvF(hueoffset, 0.83, 1.0);
  154. }
  155. void CustomLineGraph::generateAxisNumberStrings()
  156. {
  157. for (int i = 0; i < 11; i++) {
  158. xAxisNumbers[i] = QString::number(minGraphX + i * (rangeGraphX / (double)X_AXIS_GAP_AMOUNT), 'f', 1);
  159. yAxisNumbers[i] = QString::number(minGraphY + i * (rangeGraphY / (double)Y_AXIS_GAP_AMOUNT), 'f', 1);
  160. }
  161. }
  162. void CustomLineGraph::generateAndAddRandomLine()
  163. {
  164. std::random_device rd; //Will be used to obtain a seed for the random number engine
  165. std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
  166. std::uniform_real_distribution<double> realDistr(-30, 30);
  167. std::uniform_int_distribution<int> intScaleDistr(1, 3);
  168. for (int randomSeries = 0; randomSeries < 1; randomSeries++) {
  169. std::vector<QPointF> randomPointVec(101);
  170. int scale = intScaleDistr(gen);
  171. for (int i = 0; i < randomPointVec.size(); i++) {
  172. randomPointVec[i] = QPointF(i + 500, i * scale + realDistr(gen));
  173. }
  174. addLine(randomPointVec);
  175. }
  176. }
  177. void CustomLineGraph::addLine(std::vector<QPointF>& line)
  178. {
  179. addLine(line, generateNextColorForGraph());
  180. }
  181. void CustomLineGraph::addLine(std::vector<QPointF>& line, QColor color)
  182. {
  183. addSeries(line, color, LineGraphSeries::Line);
  184. }
  185. void CustomLineGraph::addDots(std::vector<QPointF>& dots)
  186. {
  187. addSeries(dots, generateNextColorForGraph(), LineGraphSeries::Dot);
  188. }
  189. void CustomLineGraph::addDots(std::vector<QPointF>& dots, QColor color)
  190. {
  191. addSeries(dots, color, LineGraphSeries::Dot);
  192. }