GraphView.cpp 8.2 KB

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