#include "pch.h" #include "GraphView.h" #include #include #include #include #define X_AXIS_GAP_AMOUNT 5 #define Y_AXIS_GAP_AMOUNT 10 #define X_AXIS_NUMBER_LINE_LENGTH 5 #define Y_AXIS_NUMBER_LINE_LENGTH 5 #define X_AXIS_NUMBER_GAP_LENGTH 10 #define Y_AXIS_NUMBER_GAP_LENGTH 20 GraphView::GraphView(QWidget* parent, QString title) :QWidget(parent), title(title), linePen(Qt::blue), rectPen(Qt::red), axisPen(Qt::black) { linePen.setJoinStyle(Qt::PenJoinStyle::RoundJoin); //Populate data with DummyData //Draw Points std::random_device rd; //Will be used to obtain a seed for the random number engine std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd() std::uniform_real_distribution doubleDistr(0.0, 1.0); hueoffset = doubleDistr(gen); } GraphView::~GraphView() { } void GraphView::paintEvent(QPaintEvent* event) { QPainter painter(this); painter.setRenderHint(QPainter::RenderHint::HighQualityAntialiasing); if (graphSeriesVec.empty()) { painter.setPen(axisPen); painter.setFont(QFont("Arial", 12, QFont::Bold)); painter.drawText(rect(), Qt::AlignCenter, "No Data connected"); return; } //Calculate LineRect QRect graphDisplayRect(rect()); graphDisplayRect.setBottom(graphDisplayRect.bottom() - 20); graphDisplayRect.setLeft(graphDisplayRect.left() + 50); graphDisplayRect.setTop(graphDisplayRect.top() + 20); graphDisplayRect.setRight(graphDisplayRect.right() - 10); graphDisplayRect.setWidth(graphDisplayRect.width() - (graphDisplayRect.width() % X_AXIS_GAP_AMOUNT) + 1); graphDisplayRect.setHeight(graphDisplayRect.height() - (graphDisplayRect.height() % Y_AXIS_GAP_AMOUNT) + 1); //Font for Axis; painter.setFont(QFont("Arial", 8)); //draw X-Axis QRect xAxisRect(QPoint(graphDisplayRect.left(), graphDisplayRect.bottom()), QPoint(graphDisplayRect.right(), rect().bottom())); QPainterPath xAxisPath; xAxisPath.moveTo(xAxisRect.left(), xAxisRect.top()); xAxisPath.lineTo(xAxisRect.right(), xAxisRect.top()); int xGap = xAxisRect.width() / X_AXIS_GAP_AMOUNT; QRect textRect(0, 0, 40, 9); for (int i = 0; i < 11; i++) { xAxisPath.moveTo(xAxisRect.left() + i * xGap, xAxisRect.top() + X_AXIS_NUMBER_LINE_LENGTH); xAxisPath.lineTo(xAxisRect.left() + i * xGap, xAxisRect.top()); textRect.moveCenter(QPoint(xAxisRect.left() + i * xGap, xAxisRect.top() + X_AXIS_NUMBER_GAP_LENGTH)); painter.drawText(textRect, Qt::AlignCenter, xAxisNumbers[i]); } painter.drawPath(xAxisPath); //draw Y-Axis QRect yAxisRect(QPoint(rect().left(), graphDisplayRect.top()), QPoint(graphDisplayRect.left(), graphDisplayRect.bottom())); QPainterPath yAxisPath; yAxisPath.moveTo(yAxisRect.right(), yAxisRect.bottom()); yAxisPath.lineTo(yAxisRect.right(), yAxisRect.top()); int yGap = yAxisRect.height() / Y_AXIS_GAP_AMOUNT; for (int i = 0; i < 11; i++) { yAxisPath.moveTo(yAxisRect.right() - Y_AXIS_NUMBER_LINE_LENGTH, yAxisRect.bottom() - i * yGap); yAxisPath.lineTo(yAxisRect.right(), yAxisRect.bottom() - i * yGap); textRect.moveCenter(QPoint(yAxisRect.right() - Y_AXIS_NUMBER_GAP_LENGTH, yAxisRect.bottom() - i * yGap)); painter.drawText(textRect, Qt::AlignCenter, yAxisNumbers[i]); } painter.drawPath(yAxisPath); painter.setPen(linePen); double stregth_factorX = graphDisplayRect.width() / rangeGraphX; double stregth_factorY = graphDisplayRect.height() / rangeGraphY; for (GraphSeries graphSeries : graphSeriesVec) { linePen.setColor(graphSeries.color); painter.setPen(linePen); QPointF translation(graphDisplayRect.left(), graphDisplayRect.top()); if (graphSeries.type == GraphSeries::SeriesType::Line || graphSeries.type == GraphSeries::SeriesType::LineDot) { linePen.setWidth(graphSeries.lineWidth); painter.setPen(linePen); QPainterPath painterPath; painterPath.moveTo(transformPoint(graphSeries.data->at(0), stregth_factorX, stregth_factorY)); for (int i = 1; i < graphSeries.data->size(); i++) { painterPath.lineTo(transformPoint(graphSeries.data->at(i), stregth_factorX, stregth_factorY)); } painterPath.translate(translation); painter.drawPath(painterPath); } if (graphSeries.type == GraphSeries::SeriesType::Dot || graphSeries.type == GraphSeries::SeriesType::LineDot) { painter.setBrush(graphSeries.color); for (int i = 0; i < graphSeries.data->size(); i++) { painter.drawEllipse(transformPoint(graphSeries.data->at(i), stregth_factorX, stregth_factorY) + translation, graphSeries.circleRadius, graphSeries.circleRadius); } painter.setBrush(Qt::BrushStyle::NoBrush); } } } QPointF GraphView::transformPoint(QPointF& point, double stregth_factorX, double stregth_factorY) { return QPointF((point.x() - minGraphX) * stregth_factorX, (maxGraphY - point.y()) * stregth_factorY); } void GraphView::calculateMinMaxGraphXY() { minGraphX = std::min_element(std::begin(graphSeriesVec), std::end(graphSeriesVec), [](const GraphSeries& a, const GraphSeries& b) -> bool {return a.minX < b.minX; })->minX; maxGraphX = std::max_element(std::begin(graphSeriesVec), std::end(graphSeriesVec), [](const GraphSeries& a, const GraphSeries& b) -> bool {return a.maxX < b.maxX; })->maxX; minGraphY = std::min_element(std::begin(graphSeriesVec), std::end(graphSeriesVec), [](const GraphSeries& a, const GraphSeries& b) -> bool {return a.minY < b.minY; })->minY; maxGraphY = std::max_element(std::begin(graphSeriesVec), std::end(graphSeriesVec), [](const GraphSeries& a, const GraphSeries& b) -> bool {return a.maxY < b.maxY; })->maxY; rangeGraphX = std::abs(maxGraphX - minGraphX); rangeGraphY = std::abs(maxGraphY - minGraphY); if (std::abs(rangeGraphX) < 0.01) { minGraphX -= 1.0; maxGraphX += 1.0; rangeGraphX = 2; } if (std::abs(rangeGraphY) < 0.01) { minGraphY -= 1.0; maxGraphY += 1.0; rangeGraphY = 2; } } void GraphView::calculateMinMaxXY(std::vector& line, GraphSeries& lgs) { auto pairX = std::minmax_element(lgs.data->begin(), lgs.data->end(), [](const QPointF& a, const QPointF& b) -> bool {return a.x() < b.x(); }); lgs.minX = pairX.first->x(); lgs.maxX = pairX.second->x(); auto pairY = std::minmax_element(lgs.data->begin(), lgs.data->end(), [](const QPointF& a, const QPointF& b) -> bool {return a.y() < b.y(); }); lgs.minY = pairY.first->y(); lgs.maxY = pairY.second->y(); } void GraphView::addSeries(std::vector& line, QColor color, GraphSeries::SeriesType type) { if (line.empty()) { return; } GraphSeries lgs; lgs.data = &line; lgs.type = type; calculateMinMaxXY(line, lgs); lgs.color = color; graphSeriesVec.push_back(lgs); calculateMinMaxGraphXY(); generateAxisNumberStrings(); } QColor GraphView::generateNextColorForGraph() { /* http://devmag.org.za/2012/07/29/how-to-choose-colours-procedurally-algorithms/ use golden ratio 0.618033988749895f */ hueoffset = std::fmod(hueoffset + 0.618033988749895f, 1.0); return QColor::fromHsvF(hueoffset, 0.83, 1.0); } void GraphView::generateAxisNumberStrings() { for (int i = 0; i < 11; i++) { xAxisNumbers[i] = QString::number(minGraphX + i * (rangeGraphX / (double)X_AXIS_GAP_AMOUNT), 'f', 1); yAxisNumbers[i] = QString::number(minGraphY + i * (rangeGraphY / (double)Y_AXIS_GAP_AMOUNT), 'f', 1); } } void GraphView::generateAndAddRandomLine() { std::random_device rd; //Will be used to obtain a seed for the random number engine std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd() std::uniform_real_distribution realDistr(-30, 30); std::uniform_int_distribution intScaleDistr(1, 3); for (int randomSeries = 0; randomSeries < 1; randomSeries++) { std::vector randomPointVec(101); int scale = intScaleDistr(gen); for (int i = 0; i < randomPointVec.size(); i++) { randomPointVec[i] = QPointF(i + 500, i * scale + realDistr(gen)); } addLine(randomPointVec); } } void GraphView::addLine(std::vector& line) { addSeries(line, generateNextColorForGraph(), GraphSeries::SeriesType::Line); } void GraphView::addLine(std::vector& line, QColor color) { addSeries(line, color, GraphSeries::SeriesType::Line); } void GraphView::addDots(std::vector& dots) { addSeries(dots, generateNextColorForGraph(), GraphSeries::SeriesType::Dot); } void GraphView::addDots(std::vector& dots, QColor color) { addSeries(dots, color, GraphSeries::SeriesType::Dot); }