Arc in QGraphicsScene

10.9k views Asked by At

I want to implement arc in QGraphicsScene. I want that on clicking of three points my arc should be drawn such that on clicking of three points arc is drawn where first point will be starting of arc, second will be any point on arc and third will be end point of arc. I have tried studing drawArc function but got confused with startangle and spanangle. I was unable to set them dynamically. Please suggest me some way to proceed.


I tried the solution to embend it in my project but got the following error:

error: cannot allocate an object of abstract type 'arc'
                 arcItem = new arc(++id, startP, midP, endP);

Can you please help me out to solve the problem. I am giving a part of code to my project. In mousepress event of cadgraphicsscene I have done following thing.

cadgraphicsscene.cpp

    void CadGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
    {
        // mousePressEvent in the graphicsScene
        if(mouseEvent->button() == Qt::LeftButton)
        {
            switch (entityMode)
            {

            case ArcMode:
                if (mFirstClick)
                {
                    startP = mouseEvent->scenePos();
                    mFirstClick = false;
                    mSecondClick = true;
                }

                else if (!mFirstClick && mSecondClick)
                {
                    midP = mouseEvent->scenePos();
                    mFirstClick = false;
                    mSecondClick = false;
                    mThirdClick = true;
                }

                else if (!mSecondClick && mThirdClick)
                {
                    endP = mouseEvent->scenePos();
                    mThirdClick = false;
                    mPaintFlag = true;
                }

                if (mPaintFlag)
                {
                    arcItem = new arc(++id, startP, midP, endP);
                    itemList.append(arcItem);
                    mUndoStack->push(new CadCommandAdd(this, arcItem));
                    setFlags();
                }
            }
        }
   } 

arc.cpp

#include "arc.h"

arc::arc(int i, QPointF point1, QPointF point2, QPointF point3)
{
    // assigns id
    id = i;
    p1 = point1;
    p2 = point2;
    p3 = point3;

    lineBC(point2, point3);
    lineAC(point1, point3);
    lineBA(point2, point1);

    rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));

    bisectorBC(lineBC.pointAt(0.5), lineBC.p2());
    bisectorBC.setAngle(lineBC.normalVector().angle());

    bisectorBA(lineBA.pointAt(0.5), lineBA.p2());
    bisectorBA.setAngle(lineBA.normalVector().angle());


    bisectorBA.intersect(bisectorBC, &center);

    ellipse = new QGraphicsEllipseItem(center.x() - rad, center.y() - rad, rad*2, rad*2);

    lineOA(center, point1);
    lineOC(center, point3);
}

arc::arc(int i, QLineF start, QLineF end)
{
    // assigns id
    id = i;

    lineOA.angle() = start;
    lineOC.angle() - lineOA.angle() = end;
}

int arc::type() const
{
    // Enable the use of qgraphicsitem_cast with arc item.
    return Type;
}

void arc::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                QWidget *widget)
{
    QPen paintpen;
    painter->setRenderHint(QPainter::Antialiasing);
    paintpen.setWidth(1);

    if (isSelected())
    {
        // sets brush for end points
        painter->setBrush(Qt::SolidPattern);
        paintpen.setColor(Qt::red);
        painter->setPen(paintpen);

        paintpen.setStyle(Qt::DashLine);
        paintpen.setColor(Qt::black);
        painter->setPen(paintpen);
        painter->drawArc(ellipse->boundingRect(),lineOA.angle(),lineOC.angle() - lineOA.angle());
    }
    else
    {
        painter->setBrush(Qt::SolidPattern);
        paintpen.setColor(Qt::black);
        painter->setPen(paintpen);
        painter->drawArc(ellipse->boundingRect(),lineOA.angle(),lineOC.angle() - lineOA.angle());

    }
}

arc.h

include <QGraphicsItem>

#include "qmath.h"
class arc : public QObject, public QGraphicsItem
{
    Q_OBJECT
public:
    arc(int, QPointF, QPointF, QPointF);
    arc(int, QLineF, QLineF);


    virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                       QWidget *widget);
    enum { Type = UserType + 6 };
    int type() const;
    int id;

    QPointF startP, midP, endP, p1, p2, p3,center;
    QLineF lineBC;
    QLineF lineAC;
    QLineF lineBA;
    QLineF lineOA;
    QLineF lineOC;
    QLineF bisectorBC;
    QLineF bisectorBA;
    QGraphicsEllipseItem *ellipse;
    qreal rad;

private:
    QVector<QPointF> stuff;

};

#endif // ARC_H

Please help me out to solve the error.

2

There are 2 answers

0
phyatt On BEST ANSWER

A Subclass of QGraphicsItem, that takes 3 points, and intersects the three with an arc of a circle. The second point is always in the middle. (Selectablity and other properties haven't been fully implemented or tested).

Note: Qt Creator includes more advanced examples of subclassed QGraphicsItems such as Colliding Mice, and 40,000 chips examples.

http://qt-project.org/doc/qt-5/examples-graphicsview.html

Also to enable QObject signals and slots and properties from a QGraphicsItem, you should use QGraphicsObject.

Note: added onto github here.

arcgraphicsitem.h

#ifndef ARCGRAPHICSITEM_H
#define ARCGRAPHICSITEM_H

#include <QGraphicsItem>
#include <QLineF>
#include <QPointF>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QWidget>

class ArcGraphicsItem : public QGraphicsItem
{

public:
    ArcGraphicsItem();
    ArcGraphicsItem(int i, QPointF point0, QPointF point1, QPointF point2);
    ~ArcGraphicsItem();

    QRectF boundingRect() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

    int type() const { return Type;}
    int id() {return m_id;}

    QPainterPath shape() const;
protected:

private:
    void init();

    enum { Type = UserType + 6 };
    int m_id;

    QPointF startP, midP, endP, p1, p2, p3, center;
    QLineF lineBC;
    QLineF lineAC;
    QLineF lineBA;
    QLineF lineOA;
    QLineF lineOB;
    QLineF lineOC;
    QLineF bisectorBC;
    QLineF bisectorBA;
    qreal startAngle;
    qreal spanAngle;

    QRectF circle;
    QRectF boundingRectTemp;
    qreal rad;
};

#endif // ARCGRAPHICSITEM_H

arcgraphicsitem.cpp

#include "arcgraphicsitem.h"
#include "qmath.h"
#include <QPen>
#include <QDebug>
#include <QPainterPath>

ArcGraphicsItem::ArcGraphicsItem(int i,
                                 QPointF point1,
                                 QPointF point2,
                                 QPointF point3)
    : m_id(i), p1(point1), p2(point2), p3(point3)

{
    init();
}


ArcGraphicsItem::ArcGraphicsItem()
{
    p1 = QPointF(0,0);
    p2 = QPointF(0,1);
    p3 = QPointF(1,1);
    m_id = -1;
    init();
}

ArcGraphicsItem::~ArcGraphicsItem()
{

}

void ArcGraphicsItem::init()
{
    lineBC = QLineF(p2, p3);
    lineAC = QLineF(p1, p3);
    lineBA = QLineF(p2, p1);

    rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));

    bisectorBC = QLineF(lineBC.pointAt(0.5), lineBC.p2());
    bisectorBC.setAngle(lineBC.normalVector().angle());

    bisectorBA = QLineF(lineBA.pointAt(0.5), lineBA.p2());
    bisectorBA.setAngle(lineBA.normalVector().angle());
    bisectorBA.intersect(bisectorBC, &center);

    circle = QRectF(center.x() - rad, center.y() - rad, rad*2, rad*2);

    lineOA = QLineF(center, p1);
    lineOB = QLineF(center, p2);
    lineOC = QLineF(center, p3);


    startAngle = lineOA.angle();
    spanAngle = lineOA.angleTo(lineOC);
    // Make sure that the span angle covers all three points with the second point in the middle
    if(qAbs(spanAngle) < qAbs(lineOA.angleTo(lineOB)) || qAbs(spanAngle) < qAbs(lineOB.angleTo(lineOC)))
    {
        // swap the end point and invert the spanAngle
        startAngle = lineOC.angle();
        spanAngle = 360 - spanAngle;
    }

    int w = 10;
    boundingRectTemp = circle.adjusted(-w, -w, w, w);
}

QRectF ArcGraphicsItem::boundingRect() const
{
    // outer most edges
    return boundingRectTemp;
}

void ArcGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QPen paintpen;
    painter->setRenderHint(QPainter::Antialiasing);
    paintpen.setWidth(1);
    // Draw arc

    if(isSelected())
    {
        painter->setBrush(Qt::SolidPattern);
        paintpen.setColor(Qt::black);
        painter->setPen(paintpen);
    }
    else
    {
        paintpen.setStyle(Qt::DashLine);
        paintpen.setColor(Qt::black);
        painter->setPen(paintpen);
    }

    QPainterPath path;
    path.arcMoveTo(circle,startAngle);
    path.arcTo(circle, startAngle, spanAngle);

    // Draw points

    if (isSelected())
    {
        // sets brush for end points
        painter->setBrush(Qt::SolidPattern);
        paintpen.setColor(Qt::red);
        painter->setPen(paintpen);

        qreal ptRad = 10;
        painter->drawEllipse(p1, ptRad, ptRad);
        painter->drawEllipse(p2, ptRad, ptRad);
        painter->drawEllipse(p3, ptRad, ptRad);
    }

    painter->setBrush(Qt::NoBrush);
    painter->drawPath(path);
}

QPainterPath ArcGraphicsItem::shape() const
{
    QPainterPath path;
    path.arcMoveTo(circle,startAngle);
    path.arcTo(circle, startAngle, spanAngle);
    return path;
}

Hope that helps

3
phyatt On

This sounds like it could be solved with some relatively simple math:

https://www.google.com/search?q=define%20circle%20three%20points

https://math.stackexchange.com/a/213678

https://www.khanacademy.org/math/geometry/triangle-properties/perpendicular_bisectors/v/three-points-defining-a-circle

Here is my translation of the math into Qt goodness

// m_points is a QList<QPointF>
// use math to define the circle
QLineF lineBC(m_points.at(1), m_points.at(2));
QLineF lineAC(m_points.at(0), m_points.at(2));
QLineF lineBA(m_points.at(1), m_points.at(0));
qreal rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));

QLineF bisectorBC(lineBC.pointAt(0.5), lineBC.p2());
bisectorBC.setAngle(lineBC.normalVector().angle());

QLineF bisectorBA(lineBA.pointAt(0.5), lineBA.p2());
bisectorBA.setAngle(lineBA.normalVector().angle());

QPointF center;
bisectorBA.intersect(bisectorBC, &center);

qDebug() << rad << center;

QT QGraphicsScene Drawing Arc

QPainterPath* path = new QPainterPath();
path->arcMoveTo(0,0,50,50,20);
path->arcTo(0,0,50,50,20, 90);
scene.addPath(*path);

Putting all of this together into a nice little project turns into this:

https://github.com/peteristhegreat/ThreePointsCircle

qt graphics scene three points circle

main.cpp

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include <QGraphicsView>
#include <QGraphicsScene>
#include "graphicsscene.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QGraphicsView * view = new QGraphicsView;

    GraphicsScene * scene = new GraphicsScene();
    view->setScene(scene);

    view->setSceneRect(-300,-300, 300, 300);
    this->resize(600, 600);

    this->setCentralWidget(view);
}

MainWindow::~MainWindow()
{

}

graphicsscene.h

#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H

#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPointF>
#include <QList>

class GraphicsScene : public QGraphicsScene
{
    Q_OBJECT
public:
    explicit GraphicsScene(QObject *parent = 0);
    virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * mouseEvent);
    virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * mouseEvent);
    virtual void mousePressEvent(QGraphicsSceneMouseEvent * mouseEvent);
    virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * mouseEvent);
signals:

public slots:

private:
    QList <QPointF> m_points;
};

#endif // GRAPHICSSCENE_H

graphicsscene.cpp

#include "graphicsscene.h"
#include <QDebug>
#include <QGraphicsEllipseItem>
#include <QGraphicsPathItem>
#include <QPainterPath>
#include "qmath.h"

GraphicsScene::GraphicsScene(QObject *parent) :
    QGraphicsScene(parent)
{
    this->setBackgroundBrush(Qt::gray);
}

void GraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
    qDebug() << Q_FUNC_INFO << mouseEvent->scenePos();
    QGraphicsScene::mouseDoubleClickEvent(mouseEvent);
}

void GraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
    qDebug() << Q_FUNC_INFO << mouseEvent->scenePos();
    QGraphicsScene::mouseMoveEvent(mouseEvent);
}

void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
    qDebug() << Q_FUNC_INFO << mouseEvent->scenePos();
    QGraphicsScene::mousePressEvent(mouseEvent);
}

void GraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent * me)
{
    qDebug() << Q_FUNC_INFO << me->scenePos();
    int radius = 20;
    QGraphicsEllipseItem * ellipse = this->addEllipse(me->scenePos().x() - radius, me->scenePos().y() - radius, radius*2, radius*2);

    ellipse->setBrush(Qt::white);
    m_points.append(me->scenePos());
    if(m_points.size() == 3)
    {
        // use math to define the circle
        QLineF lineBC(m_points.at(1), m_points.at(2));
        QLineF lineAC(m_points.at(0), m_points.at(2));
        QLineF lineBA(m_points.at(1), m_points.at(0));
        qreal rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));

        QLineF bisectorBC(lineBC.pointAt(0.5), lineBC.p2());
        bisectorBC.setAngle(lineBC.normalVector().angle());

        QLineF bisectorBA(lineBA.pointAt(0.5), lineBA.p2());
        bisectorBA.setAngle(lineBA.normalVector().angle());

        QPointF center;
        bisectorBA.intersect(bisectorBC, &center);

        qDebug() << rad << center;

        bool drawCircle = true;

        QGraphicsEllipseItem * ellipse = new QGraphicsEllipseItem(center.x() - rad, center.y() - rad, rad*2, rad*2);
        if(drawCircle)
            this->addItem(ellipse);

        // add arc
        // this->addItem(path);
        QPainterPath path;
        QLineF lineOA(center, m_points.at(0));
        QLineF lineOC(center, m_points.at(2));
        path.arcMoveTo(ellipse->boundingRect(),lineOA.angle());
        path.arcTo(ellipse->boundingRect(), lineOA.angle(), lineOC.angle() - lineOA.angle());
        QGraphicsPathItem * pathItem = new QGraphicsPathItem(path);
        pathItem->setPen(QPen(Qt::red,10));
        this->addItem(pathItem);

        if(!drawCircle)
            delete ellipse;
        m_points.clear();
    }

    QGraphicsScene::mouseReleaseEvent(me);
}