#include "pch.h" #include "Plott.h" #include "util.h" #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 30 #define X_AXIS_LEGEND_TOP_BEGIN 12 #define Y_AXIS_LEGEND_LEFT_BEGIN 12 #define MARGIN_TOP 20 #define MARGIN_BOTTOM 25 #define MARGIN_LEFT 50 #define MARGIN_RIGHT 10 Plott::Plott(QWidget *parent) : QWidget(parent) { setFocusPolicy(Qt::ClickFocus); } Plott::~Plott() { } void Plott::setVisibleWindow(double xMin, double xMax, double yMin, double yMax) { window.xMin = xMin; window.xMax = xMax; window.yMin = yMin; window.yMax = yMax; } void Plott::setDefaultVisibleWindow(double xMin, double xMax, double yMin, double yMax) { defaultWindow.xMin = xMin; defaultWindow.xMax = xMax; defaultWindow.yMin = yMin; defaultWindow.yMax = yMax; } void Plott::resetToDefaultWindow() { window.xMin = defaultWindow.xMin; window.xMax = defaultWindow.xMax; window.yMin = defaultWindow.yMin; window.yMax = defaultWindow.yMax; } void Plott::setAxisLegend(QString xAxisLegend, QString yAxisLegend) { this->xAxisLegend = xAxisLegend; this->yAxisLegend = yAxisLegend; } QPointF Plott::transformGraphToView(QPointF graphpoint) const { QRect graphDisplayRect = getDisplayRect(); QPointF translation(graphDisplayRect.left(), graphDisplayRect.top()); double stregth_factorX = graphDisplayRect.width() / std::abs(window.xMax - window.xMin); double stregth_factorY = graphDisplayRect.height() / std::abs(window.yMax - window.yMin); return QPointF((graphpoint.x() - window.xMin) * stregth_factorX, (window.yMax - graphpoint.y()) * stregth_factorY) + translation; } QPointF Plott::transformGraphToView(QPointF graphpoint, double stregth_factorX, double stregth_factorY, QPointF translation) const { return QPointF((graphpoint.x() - window.xMin) * stregth_factorX, (window.yMax - graphpoint.y()) * stregth_factorY) + translation; } QPointF Plott::transformViewToGraph(QPointF viewpoint) const { QRect graphDisplayRect = getDisplayRect(); QPointF translation(graphDisplayRect.left(), graphDisplayRect.top()); double stregth_factorX = graphDisplayRect.width() / std::abs(window.xMax - window.xMin); double stregth_factorY = graphDisplayRect.height() / std::abs(window.yMax - window.yMin); return QPointF(((viewpoint.x() - translation.x()) / stregth_factorX) + window.xMin, window.yMax - ((viewpoint.y() - translation.y()) / stregth_factorY)); } QPointF Plott::transformViewToGraph(QPointF viewpoint, double stregth_factorX, double stregth_factorY, QPointF translation) const { return QPointF(((viewpoint.x() - translation.x()) / stregth_factorX) + window.xMin, window.yMax - ((viewpoint.y() - translation.y()) / stregth_factorY)); } void Plott::drawData(QPainter& painter) { } QRect Plott::getDisplayRect() const { QRect graphDisplayRect(rect()); graphDisplayRect.setBottom(graphDisplayRect.bottom() - MARGIN_BOTTOM); graphDisplayRect.setLeft(graphDisplayRect.left() + MARGIN_LEFT); graphDisplayRect.setTop(graphDisplayRect.top() + MARGIN_TOP); graphDisplayRect.setRight(graphDisplayRect.right() - MARGIN_RIGHT); graphDisplayRect.setWidth(graphDisplayRect.width() - (graphDisplayRect.width() % X_AXIS_GAP_AMOUNT) + 1); graphDisplayRect.setHeight(graphDisplayRect.height() - (graphDisplayRect.height() % Y_AXIS_GAP_AMOUNT) + 1); return graphDisplayRect; } // Draws the axis void Plott::paintEvent(QPaintEvent* event) { std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now(); QPainter painter(this); painter.setRenderHint(QPainter::RenderHint::HighQualityAntialiasing); QRect graphDisplayRect = getDisplayRect(); int xGap = graphDisplayRect.width() / X_AXIS_GAP_AMOUNT; int yGap = graphDisplayRect.height() / Y_AXIS_GAP_AMOUNT; if(enableGrid){ QPainterPath gridPath; for (int i = 1; i < X_AXIS_GAP_AMOUNT; i++) { gridPath.moveTo(graphDisplayRect.left() + i * xGap, graphDisplayRect.top()); gridPath.lineTo(graphDisplayRect.left() + i * xGap, graphDisplayRect.bottom()); } for (int i = 0; i < Y_AXIS_GAP_AMOUNT + 1; i++) { gridPath.moveTo(graphDisplayRect.left(), graphDisplayRect.bottom() - i * yGap); gridPath.lineTo(graphDisplayRect.right(), graphDisplayRect.bottom() - i * yGap); } QPen graypen(QColor(150, 150, 150)); painter.setPen(graypen); painter.drawPath(gridPath); } drawData(painter); //Draw White Rect for axis QPen blackpen(QColor(0, 0, 0)); blackpen.setWidth(1); QPen whitePen(QColor(255, 255, 255)); whitePen.setWidth(1); painter.setPen(whitePen); painter.setBrush(whitePen.color()); QRect leftRect(rect().topLeft(), QPoint(graphDisplayRect.left() - 1, rect().bottom())); QRect topRect(rect().topLeft(), QPoint(rect().right(), graphDisplayRect.top() - 1)); QRect bottomRect(QPoint(rect().left(), graphDisplayRect.bottom()), rect().bottomRight()); QRect rightRect(QPoint(graphDisplayRect.right(), rect().top()), rect().bottomRight()); painter.drawRect(leftRect); painter.drawRect(topRect); painter.drawRect(bottomRect); painter.drawRect(rightRect); painter.setBrush(Qt::BrushStyle::NoBrush); //Font for Axis; painter.setFont(QFont("Arial", 8)); //draw X-Axis painter.setPen(blackpen); 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()); QRect textRect(0, 0, 40, 9); for (int i = 0; i < X_AXIS_GAP_AMOUNT + 1; 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, QString::number(util::linearInterpolate(window.xMin, window.xMax, (1/ (double)X_AXIS_GAP_AMOUNT )*i), 'f', 1)); } painter.drawPath(xAxisPath); //Draw XAxisLegend QRect xAxisLegendRect(QPoint(xAxisRect.left(), rect().bottom() - X_AXIS_LEGEND_TOP_BEGIN), rect().bottomRight()); painter.drawText(xAxisLegendRect, Qt::AlignCenter, xAxisLegend); //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()); for (int i = 0; i < Y_AXIS_GAP_AMOUNT + 1; 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::AlignRight | Qt::AlignVCenter, QString::number(util::linearInterpolate(window.yMin, window.yMax, (1 / (double)Y_AXIS_GAP_AMOUNT) * i), 'f', 1)); } painter.drawPath(yAxisPath); //Draw YAxisLegend //to draw vertical the painter have to translated and rotated (order is crucial) QRect yAxisLegendRect(0, 0, graphDisplayRect.bottom(), Y_AXIS_LEGEND_LEFT_BEGIN); painter.translate(0,graphDisplayRect.bottom()); painter.rotate(-90.0); painter.drawText(yAxisLegendRect, Qt::AlignCenter, yAxisLegend); painter.rotate(+90.0); painter.translate(0, -graphDisplayRect.bottom()); if (isLeftMousePressed) { QColor blue(0, 122, 204); QColor gray(120, 120, 120); //QPointF value = transformViewToGraph(oldPositionForMouseEvent); QPointF viewPoint = transformGraphToView(mousePressedValue); if (graphDisplayRect.top() <= viewPoint.y() && viewPoint.y() <= graphDisplayRect.bottom()) { painter.setPen(gray); painter.drawLine(QPointF(graphDisplayRect.left() - Y_AXIS_NUMBER_LINE_LENGTH, viewPoint.y()), QPointF(graphDisplayRect.right(), viewPoint.y())); textRect.moveCenter(QPoint(graphDisplayRect.left() - Y_AXIS_NUMBER_GAP_LENGTH, viewPoint.y())); painter.fillRect(textRect, whitePen.color()); painter.setPen(blue); painter.drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, QString::number(mousePressedValue.y(), 'f', 1)); } if (graphDisplayRect.left() <= viewPoint.x() && viewPoint.x() <= graphDisplayRect.right()) { painter.setPen(gray); painter.drawLine(QPointF(viewPoint.x(), graphDisplayRect.top()), QPointF(viewPoint.x(), graphDisplayRect.bottom() + X_AXIS_NUMBER_LINE_LENGTH)); textRect.moveCenter(QPoint(viewPoint.x(), graphDisplayRect.bottom() + X_AXIS_NUMBER_GAP_LENGTH)); painter.fillRect(textRect, whitePen.color()); painter.setPen(blue); painter.drawText(textRect, Qt::AlignCenter, QString::number(mousePressedValue.x(), 'f', 1)); } } if (isRightMousePressed) { QColor blue(0, 122, 204); painter.setPen(blue); painter.drawRect(QRectF(transformGraphToView(this->mousePressedValue), oldPositionForMouseEvent)); } std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now(); std::chrono::milliseconds timeElapsed = std::chrono::duration_cast(end - start); } void Plott::keyPressEvent(QKeyEvent* event) { double percent = 0.1; switch (event->key()) { case Qt::Key_Up: window.moveUp(percent); break; case Qt::Key_Right: window.moveRight(percent); break; case Qt::Key_Left: window.moveLeft(percent); break; case Qt::Key_Down: window.moveDown(percent); break; case Qt::Key_PageUp: case Qt::Key_Plus: if (event->modifiers() & Qt::ShiftModifier) { //ZoomInY window.ZoomInY(percent); } else if (event->modifiers() & Qt::ControlModifier) { //ZoomInX window.ZoomInX(percent); } else { //Zoom window.ZoomIn(percent); } break; case Qt::Key_PageDown: case Qt::Key_Minus: if (event->modifiers() & Qt::ShiftModifier) { //ZoomOutX window.ZoomOutY(percent); } else if (event->modifiers() & Qt::ControlModifier) { //ZoomOutX window.ZoomOutX(percent); } else { //ZoomOut window.ZoomOut(percent); } break; case Qt::Key_R: resetToDefaultWindow(); default: QWidget::keyPressEvent(event); } update(); } void Plott::wheelEvent(QWheelEvent* event) { double percent = 0.1; QPoint numDegrees = event->angleDelta() / 8; if (!numDegrees.isNull()) { QPoint numSteps = numDegrees / 15; switch (numSteps.y()) { case 1: if (event->modifiers() & Qt::ShiftModifier) { window.ZoomInY(percent); } else if (event->modifiers() & Qt::ControlModifier) { window.ZoomInX(percent); } else { window.ZoomIn(percent); } break; case -1: if (event->modifiers() & Qt::ShiftModifier) { window.ZoomOutY(percent); } else if (event->modifiers() & Qt::ControlModifier) { window.ZoomOutX(percent); } else { window.ZoomOut(percent); } break; default: break; } update(); } event->accept(); } void Plott::mouseMoveEvent(QMouseEvent* event) { if (isLeftMousePressed) { //Move window by calculating the change in visibleWindow QPointF delta = event->pos() - oldPositionForMouseEvent; oldPositionForMouseEvent = event->pos(); double rangeX = window.rangeX(); int width = (rect().right() - 10) - (rect().left() + 50); width -= (width % X_AXIS_GAP_AMOUNT) + 1; double pixelWidthX = rangeX / (double)width; double rangeY = window.rangeY(); int height = (rect().bottom() - 20) - (rect().top() + 20); height -= (height % Y_AXIS_GAP_AMOUNT) + 1; double pixelWidthY = rangeY / (double)height; window.xMin += -delta.x() * pixelWidthX; window.xMax += -delta.x() * pixelWidthX; window.yMin += delta.y() * pixelWidthY; window.yMax += delta.y() * pixelWidthY; update(); } else if (isRightMousePressed) { oldPositionForMouseEvent = event->pos(); update(); } } void Plott::mousePressEvent(QMouseEvent* event) { if (event->buttons() == Qt::LeftButton) { isLeftMousePressed = true; } else if (event->buttons() == Qt::RightButton) { isRightMousePressed = true; } //To calculate the delta for mouse move event oldPositionForMouseEvent = event->pos(); mousePressedValue = transformViewToGraph(oldPositionForMouseEvent); update(); } void Plott::mouseReleaseEvent(QMouseEvent* event) { if (isRightMousePressed) { QPointF mouseViewPostion = transformGraphToView(mousePressedValue); if (std::abs(mouseViewPostion.x() - oldPositionForMouseEvent.x()) > 0 || std::abs(mouseViewPostion.y() - oldPositionForMouseEvent.y()) > 0) { //set visible window to selection VisibleWindow selectedWindow(0,0,0,0); QPointF endPosition = transformViewToGraph(oldPositionForMouseEvent); bool pressedIsLowerX = mousePressedValue.x() < endPosition.x(); selectedWindow.xMin = pressedIsLowerX? mousePressedValue.x() : endPosition.x(); selectedWindow.xMax = pressedIsLowerX ? endPosition.x() : mousePressedValue.x(); bool pressedIsLowerY = mousePressedValue.y() < endPosition.y(); selectedWindow.yMin = pressedIsLowerY ? mousePressedValue.y() : endPosition.y(); selectedWindow.yMax = pressedIsLowerY ? endPosition.y() : mousePressedValue.y(); handleSelectedWindow(selectedWindow, event); } } isLeftMousePressed = false; isRightMousePressed = false; update(); } void Plott::handleSelectedWindow(VisibleWindow& window, QMouseEvent* event) { setWindow(window); } void Plott::setWindow(VisibleWindow& window) { this->window = window; update(); } void VisibleWindow::moveUp(double percent) { double range = rangeY(); yMin += percent * range; yMax += percent * range; } void VisibleWindow::moveDown(double percent) { double range = rangeY(); yMin -= percent * range; yMax -= percent * range; } void VisibleWindow::moveLeft(double percent) { double range = rangeX(); xMin -= percent * range; xMax -= percent * range; } void VisibleWindow::moveRight(double percent) { double range = rangeX(); xMin += percent * range; xMax += percent * range; } void VisibleWindow::ZoomIn(double percent) { //Both sides same percentage percent /= 2; double Xrange = rangeX(); if (Xrange == 0) Xrange = 1.0; xMin += percent * Xrange; xMax -= percent * Xrange; double Yrange = rangeY(); if (Yrange == 0) Yrange = 1.0; yMin += percent * Yrange; yMax -= percent * Yrange; } void VisibleWindow::ZoomInX(double percent) { percent /= 2; double Xrange = rangeX(); if (Xrange == 0) Xrange = 1.0; xMin += percent * Xrange; xMax -= percent * Xrange; } void VisibleWindow::ZoomInY(double percent) { percent /= 2; double Yrange = rangeY(); if (Yrange == 0) Yrange = 1.0; yMin += percent * Yrange; yMax -= percent * Yrange; } void VisibleWindow::ZoomOut(double percent) { //Both sides same percentage percent /= 2; double Xrange = rangeX(); if (Xrange == 0) Xrange = 1.0; xMin -= percent * Xrange; xMax += percent * Xrange; double Yrange = rangeY(); if (Yrange == 0) Yrange = 1.0; yMin -= percent * Yrange; yMax += percent * Yrange; } void VisibleWindow::ZoomOutX(double percent) { percent /= 2; double Xrange = rangeX(); if (Xrange == 0) Xrange = 1.0; xMin -= percent * Xrange; xMax += percent * Xrange; } void VisibleWindow::ZoomOutY(double percent) { percent /= 2; double Yrange = rangeY(); if (Yrange == 0) Yrange = 1.0; yMin -= percent * Yrange; yMax += percent * Yrange; } double VisibleWindow::rangeX() const { return xMax - xMin; } double VisibleWindow::rangeY() const { return yMax - yMin; } bool VisibleWindow::inBoundX(QPointF point) const { return point.x() >= xMin && point.x() <= xMax; } bool VisibleWindow::inBoundY(QPointF point) const { return point.y() >= yMin && point.y() <= yMax; } bool VisibleWindow::inBound(QPointF point) const { return inBoundX(point) && inBoundY(point); }