日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Elastic Nodes Example 翻译及学习整理

發布時間:2025/3/15 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Elastic Nodes Example 翻译及学习整理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • Elastic Nodes Example 翻譯及學習整理
    • 題記:
    • 簡介:
    • Node Class Definition
    • Edge Class Definition
    • GraphWidget Class Definition
    • The main() Function

Elastic Nodes Example 翻譯及學習整理

題記:

因為最近的一個項目需要實現圖像交互,好在Qt有現成的一些示例。示例是全英文的,還是翻譯整理一遍,這樣印象會更深刻些。

簡介:

該示例演示了如何實現在場景中的圖形交互。


具體有一下幾個方面:
1、在交互(如鼠標拖放鍵盤敲擊等)過程中如何實現節點之間的連線。
2、實現基本的一些交互,鼠標單擊、拖放節點。鼠標滾輪縮放視圖。空格鍵讓節點位置隨機變化。

我們知道,QGraphicsView 幫助GraphicsScene c類更好的實現圖形項的交互,如縮放和旋轉。

本示例的程序文檔結構很簡單,主要由 一個Node class, 一個 Edge class,還有 GraphWidget組成。
其中 Node class 實現的是小圓球的節點,Edge class實現的是節點之間的連線,GraphWidge class 實現的是一個窗體。main()函數實現窗體的顯示,以及事件的循環。

Node Class Definition

該類實現的三個主要目標:

1、繪制具有極性漸變填充的小圓球。

2、實現與其他小球的交互。

3、計算拖拽的拉力,從而拉動各個節點。

以下是該類的聲明部分

class Node : public QGraphicsItem{public:Node(GraphWidget *graphWidget);void addEdge(Edge *edge);QList<Edge *> edges() const;enum { Type = UserType + 1 };int type() const override { return Type; }void calculateForces();bool advancePosition();QRectF boundingRect() const override;QPainterPath shape() const override;void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;protected:QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;void mousePressEvent(QGraphicsSceneMouseEvent *event) override;void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;private:QList<Edge *> edgeList;QPointF newPos;GraphWidget *graph;};

首先,Node class 繼承自QGraphicsItem,從而重寫兩個強制的函數boundingRect() and paint() 來實現圖形繪制。 通過重寫shape() 來確保碰撞檢測。

為了實現節點間連線的管理控制,該類同時提供了API來增加節點間的連線,以及列表的方式來管理各個相互連接的節點。

advancePosition() 來實現節點的步進。
calculateForces() 來計算拖拽的力度,從而使得臨近的節點發生相應的移動。

itemChange() 來相應狀態的變化(如節點位置發生變化時),mousePressEvent() and mouseReleaseEvent() 來更新圖像項的顯示。

接著,我們來看下Node 的構造函數:

Node::Node(GraphWidget *graphWidget): graph(graphWidget){setFlag(ItemIsMovable);setFlag(ItemSendsGeometryChanges);setCacheMode(DeviceCoordinateCache);setZValue(-1);}

在構造函數中,
旗標ItemIsMovable 設置圖形項是可以被移動。
旗標ItemSendsGeometryChanges 確保itemChange() 通知位置發生移動。
DeviceCoordinateCache用來加速圖形的渲染。
為了確保節點始終顯示在“連線”的上層(碰撞檢測時有用),這里我們設置圖形項的Z值為-1.

在構造函數里還設置了一個GraphWidget 指針,用來存儲“this”指針,因為我們過會訪問到。

void Node::addEdge(Edge *edge){edgeList << edge;edge->adjust();}QList<Edge *> Node::edges() const{return edgeList;}```addEdge() 添加與圖形相關的連線。如果當節點的位置發生改變是,連接也會相應改變。edges() 返回節點相關聯的連線列表。```cppvoid Node::calculateForces(){if (!scene() || scene()->mouseGrabberItem() == this) {newPos = pos();return;}

有兩種方式來實現節點的移動。calculateForces()計算拖拽的彈力。另外,用戶可以直接拖拽鼠標點中的節點。

// Sum up all forces pushing this item awayqreal xvel = 0;qreal yvel = 0;foreach (QGraphicsItem *item, scene()->items()) {Node *node = qgraphicsitem_cast<Node *>(item);if (!node)continue;QPointF vec = mapToItem(node, 0, 0);qreal dx = vec.x();qreal dy = vec.y();double l = 2.0 * (dx * dx + dy * dy);if (l > 0) {xvel += (dx * 150.0) / l;yvel += (dy * 150.0) / l;}}

彈力的計算是通過一個算法來實現。

該算法有兩個步驟:
1、計算使得節點分離的力。
2、減去使得節點聚集的力。

首先,我們需要查找所有的節點。

接著我們用mapToItem(),來記錄每個節點的在場景坐標系中的位置。這個位置信息將用來計算拖拽彈力的大小和方向。我們計算每個節點之間力的總和,然后調整分配,最近的節點獲得的力最大。力度的總和記錄在兩個變量 xvel,yvel

接著我們計算節點間聚集的力

// Now subtract all forces pulling items togetherdouble weight = (edgeList.size() + 1) * 10;foreach (Edge *edge, edgeList) {QPointF vec;if (edge->sourceNode() == this)vec = mapToItem(edge->destNode(), 0, 0);elsevec = mapToItem(edge->sourceNode(), 0, 0);xvel -= vec.x() / weight;yvel -= vec.y() / weight;}

節點間連線的長度決定聚集的力的大小。通過遍歷與當前節點相連的每個連線,我們可以使用類似上面的方法來計算得到聚集里的大小和方向。該力從xvel ,yvel減去得到。

if (qAbs(xvel) < 0.1 && qAbs(yvel) < 0.1)xvel = yvel = 0;

從物理理論上來講,拖拽的分離的力,和節點間聚合的力會趨向平衡的。由于計算的精度引起的誤差,我們這里設置當<0.1 時候,就認為是0;

QRectF sceneRect = scene()->sceneRect();newPos = pos() + QPointF(xvel, yvel);newPos.setX(qMin(qMax(newPos.x(), sceneRect.left() + 10), sceneRect.right() - 10));newPos.setY(qMin(qMax(newPos.y(), sceneRect.top() + 10), sceneRect.bottom() - 10));}

最后我們決定節點的新位置。同時我們保證新的坐標位置仍然在我們設定的邊界內。我們這里沒有移動圖形項,移動的功能實現交給了advancePosition()

bool Node::advancePosition(){if (newPos == pos())return false;setPos(newPos);return true;}

advancePosition()實現了節點位置的更新,通過調用GraphWidget::timerEvent()來實現。

QRectF Node::boundingRect() const{qreal adjust = 2;return QRectF( -10 - adjust, -10 - adjust, 23 + adjust, 23 + adjust);}

節點的邊界矩形的大小是20x20,修正值2,是為了補償邊框的粗細大小。3 個單位的值會了在繪制底部的陰影。

QPainterPath Node::shape() const
{
QPainterPath path;
path.addEllipse(-10, -10, 20, 20);
return path;
}

節點的形狀是一個簡單的橢圓。確保圖中拽時,單擊的是節點的內部。

void Node::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *){painter->setPen(Qt::NoPen);painter->setBrush(Qt::darkGray);painter->drawEllipse(-7, -7, 20, 20);QRadialGradient gradient(-3, -3, 10);if (option->state & QStyle::State_Sunken) {gradient.setCenter(3, 3);gradient.setFocalPoint(3, 3);gradient.setColorAt(1, QColor(Qt::yellow).light(120));gradient.setColorAt(0, QColor(Qt::darkYellow).light(120));} else {gradient.setColorAt(0, Qt::yellow);gradient.setColorAt(1, Qt::darkYellow);}painter->setBrush(gradient);painter->setPen(QPen(Qt::black, 0));painter->drawEllipse(-10, -10, 20, 20);}

該函數實現的是節點的繪制。一開始我們我們繪制一個灰黑的陰影。

然后我們繪制一個有極性漸變填充的圓。帶有渲染填充會比較慢,這就是所以我們一開始就設置DeviceCoordinateCache的原因。該設置可以有效確保不必要的重繪功能。

QVariant Node::itemChange(GraphicsItemChange change, const QVariant &value){switch (change) {case ItemPositionHasChanged:foreach (Edge *edge, edgeList)edge->adjust();graph->itemMoved();break;default:break;};return QGraphicsItem::itemChange(change, value);}

itemChange() 會調整所有連接的節點的位置,這同時也將觸發計算新的force calculations.

這里就是我們為什么要留一個GraphWidget的指針。還有一種同樣實現的方式,那就是運用信號槽關聯,但是這樣的話,Node 需要繼承QGraphicsObject.

void Node::mousePressEvent(QGraphicsSceneMouseEvent *event){update();QGraphicsItem::mousePressEvent(event);}void Node::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){update();QGraphicsItem::mouseReleaseEvent(event);}

因為我們已經設置的圖形是可移動的旗標,所以我們不需要記錄實現鼠標交互時候的坐標,因為它已經提供給我們了,我們只需要重寫該函數的句柄就可以了。

Edge Class Definition

該類實現了帶箭頭的連線。
它包含了指向源節點和目標節點的指針。
提供了adjust() 來保證始終從源節點到目標節點的連線。
下面來看該類的聲明:

Let's take a look at the class declaration:class Edge : public QGraphicsItem{public:Edge(Node *sourceNode, Node *destNode);Node *sourceNode() const;Node *destNode() const;void adjust();enum { Type = UserType + 2 };int type() const override { return Type; }protected:QRectF boundingRect() const override;void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;private:Node *source, *dest;QPointF sourcePoint;QPointF destPoint;qreal arrowSize;};

Edge 繼承了QGraphicsItem,它非常簡單,沒有信號 與槽,沒有屬性。它的構造函數里有兩個指針。我們還提供了提取該兩個指針的函數。

Edge::Edge(Node *sourceNode, Node *destNode): arrowSize(10){setAcceptedMouseButtons(0);source = sourceNode;dest = destNode;source->addEdge(this);dest->addEdge(this);adjust();}

構造函數中初始化了arrowSize 這個變量。
setAcceptedMouseButtons(0). 不接受鼠標的按鈕事件。
更新連線的兩個指針,并通過adjust()來更新連線的起點和終點位置。

Node *Edge::sourceNode() const
{
return source;
}

Node *Edge::destNode() const
{
return dest;
}

返回當前連線的節點的指針。

void Edge::adjust(){if (!source || !dest)return;QLineF line(mapFromItem(source, 0, 0), mapFromItem(dest, 0, 0));qreal length = line.length();prepareGeometryChange();if (length > qreal(20.)) {QPointF edgeOffset((line.dx() * 10) / length, (line.dy() * 10) / length);sourcePoint = line.p1() + edgeOffset;destPoint = line.p2() - edgeOffset;} else {sourcePoint = destPoint = line.p1();}}

這里為了箭頭在節點的輪廓上,而不是節點的中心,我們這里做了edgeOffset補償。

如果vector 小于20,比如節點重合了。這里我們讓兩個指針夠指向同一個源節點。實際上,這是難易發生的。

prepareGeometryChange() 為了使得返回boundingRect()

QRectF Edge::boundingRect() const{if (!source || !dest)return QRectF();qreal penWidth = 1;qreal extra = (penWidth + arrowSize) / 2.0;return QRectF(sourcePoint, QSizeF(destPoint.x() - sourcePoint.x(),destPoint.y() - sourcePoint.y())).normalized().adjusted(-extra, -extra, extra, extra);}

邊界矩形的定義。

void Edge::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *){if (!source || !dest)return;QLineF line(sourcePoint, destPoint);if (qFuzzyCompare(line.length(), qreal(0.)))return;

繪制連線時,我們這里設置了兩個異常的返回。

// Draw the line itselfpainter->setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));painter->drawLine(line); // Draw the arrowsdouble angle = std::atan2(-line.dy(), line.dx());QPointF sourceArrowP1 = sourcePoint + QPointF(sin(angle + M_PI / 3) * arrowSize,cos(angle + M_PI / 3) * arrowSize);QPointF sourceArrowP2 = sourcePoint + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize,cos(angle + M_PI - M_PI / 3) * arrowSize);QPointF destArrowP1 = destPoint + QPointF(sin(angle - M_PI / 3) * arrowSize,cos(angle - M_PI / 3) * arrowSize);QPointF destArrowP2 = destPoint + QPointF(sin(angle - M_PI + M_PI / 3) * arrowSize,cos(angle - M_PI + M_PI / 3) * arrowSize);painter->setBrush(Qt::black);painter->drawPolygon(QPolygonF() << line.p1() << sourceArrowP1 << sourceArrowP2);painter->drawPolygon(QPolygonF() << line.p2() << destArrowP1 << destArrowP2);}

繪制連線和箭頭

GraphWidget Class Definition

GraphWidget 是QGraphicsView的子類。class GraphWidget : public QGraphicsView{Q_OBJECTpublic:GraphWidget(QWidget *parent = 0);void itemMoved();public slots:void shuffle();void zoomIn();void zoomOut();protected:void keyPressEvent(QKeyEvent *event) override;void timerEvent(QTimerEvent *event) override;#if QT_CONFIG(wheelevent)void wheelEvent(QWheelEvent *event) override;#endifvoid drawBackground(QPainter *painter, const QRectF &rect) override;void scaleView(qreal scaleFactor);private:int timerId;Node *centerNode;};

該類,初始化了場景。
提供了itemMoved() 來告知場景中的圖形發生了變化。一些事件的重載。以及背景的繪制,視圖的縮放。

GraphWidget::GraphWidget(QWidget *parent): QGraphicsView(parent), timerId(0){QGraphicsScene *scene = new QGraphicsScene(this);scene->setItemIndexMethod(QGraphicsScene::NoIndex);scene->setSceneRect(-200, -200, 400, 400);setScene(scene);setCacheMode(CacheBackground);setViewportUpdateMode(BoundingRectViewportUpdate);setRenderHint(QPainter::Antialiasing);setTransformationAnchor(AnchorUnderMouse);scale(qreal(0.8), qreal(0.8));setMinimumSize(400, 400);setWindowTitle(tr("Elastic Nodes"));

這里初始化了場景,還有設置了場景的大小。
setCacheMode(CacheBackground); 緩存靜態背景。
setViewportUpdateMode(BoundingRectViewportUpdate); 設置視圖的更新模式。
setRenderHint(QPainter::Antialiasing);抗鋸齒。
setTransformationAnchor(AnchorUnderMouse);縮放時以鼠標為中心。

Node *node1 = new Node(this);Node *node2 = new Node(this);Node *node3 = new Node(this);Node *node4 = new Node(this);centerNode = new Node(this);Node *node6 = new Node(this);Node *node7 = new Node(this);Node *node8 = new Node(this);Node *node9 = new Node(this);scene->addItem(node1);scene->addItem(node2);scene->addItem(node3);scene->addItem(node4);scene->addItem(centerNode);scene->addItem(node6);scene->addItem(node7);scene->addItem(node8);scene->addItem(node9);scene->addItem(new Edge(node1, node2));scene->addItem(new Edge(node2, node3));scene->addItem(new Edge(node2, centerNode));scene->addItem(new Edge(node3, node6));scene->addItem(new Edge(node4, node1));scene->addItem(new Edge(node4, centerNode));scene->addItem(new Edge(centerNode, node6));scene->addItem(new Edge(centerNode, node8));scene->addItem(new Edge(node6, node9));scene->addItem(new Edge(node7, node4));scene->addItem(new Edge(node8, node7));scene->addItem(new Edge(node9, node8));node1->setPos(-50, -50);node2->setPos(0, -50);node3->setPos(50, -50);node4->setPos(-50, 0);centerNode->setPos(0, 0);node6->setPos(50, 0);node7->setPos(-50, 50);node8->setPos(0, 50);node9->setPos(50, 50);}

初始化節點和連線。

void GraphWidget::itemMoved(){if (!timerId)timerId = startTimer(1000 / 25);}

GraphWidget 會監測節點的移動。通過定時器事件來更新節點的位置。

void GraphWidget::keyPressEvent(QKeyEvent *event){switch (event->key()) {case Qt::Key_Up:centerNode->moveBy(0, -20);break;case Qt::Key_Down:centerNode->moveBy(0, 20);break;case Qt::Key_Left:centerNode->moveBy(-20, 0);break;case Qt::Key_Right:centerNode->moveBy(20, 0);break;case Qt::Key_Plus:zoomIn();break;case Qt::Key_Minus:zoomOut();break;case Qt::Key_Space:case Qt::Key_Enter:shuffle();break;default:QGraphicsView::keyPressEvent(event);}}

鍵盤事件的實現。

void GraphWidget::timerEvent(QTimerEvent *event){Q_UNUSED(event);QList<Node *> nodes;foreach (QGraphicsItem *item, scene()->items()) {if (Node *node = qgraphicsitem_cast<Node *>(item))nodes << node;}foreach (Node *node, nodes)node->calculateForces();bool itemsMoved = false;foreach (Node *node, nodes) {if (node->advancePosition())itemsMoved = true;}if (!itemsMoved) {killTimer(timerId);timerId = 0;}}

每次只要計時器開啟,該句柄會查找所有的節點,通過計算拖拽的力,然后更新節點的位置。通過advance()的返回值,若果是false,則停止定時器。

void GraphWidget::wheelEvent(QWheelEvent *event){scaleView(pow((double)2, -event->delta() / 240.0));}

鼠標的滾輪縮放。

void GraphWidget::drawBackground(QPainter *painter, const QRectF &rect){Q_UNUSED(rect);// ShadowQRectF sceneRect = this->sceneRect();QRectF rightShadow(sceneRect.right(), sceneRect.top() + 5, 5, sceneRect.height());QRectF bottomShadow(sceneRect.left() + 5, sceneRect.bottom(), sceneRect.width(), 5);if (rightShadow.intersects(rect) || rightShadow.contains(rect))painter->fillRect(rightShadow, Qt::darkGray);if (bottomShadow.intersects(rect) || bottomShadow.contains(rect))painter->fillRect(bottomShadow, Qt::darkGray);// FillQLinearGradient gradient(sceneRect.topLeft(), sceneRect.bottomRight());gradient.setColorAt(0, Qt::white);gradient.setColorAt(1, Qt::lightGray);painter->fillRect(rect.intersected(sceneRect), gradient);painter->setBrush(Qt::NoBrush);painter->drawRect(sceneRect);// TextQRectF textRect(sceneRect.left() + 4, sceneRect.top() + 4,sceneRect.width() - 4, sceneRect.height() - 4);QString message(tr("Click and drag the nodes around, and zoom with the mouse ""wheel or the '+' and '-' keys"));QFont font = painter->font();font.setBold(true);font.setPointSize(14);painter->setFont(font);painter->setPen(Qt::lightGray);painter->drawText(textRect.translated(2, 2), message);painter->setPen(Qt::black);painter->drawText(textRect, message);}

背景的繪制,

void GraphWidget::scaleView(qreal scaleFactor){qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width();if (factor < 0.07 || factor > 100)return;scale(scaleFactor, scaleFactor);}

滾輪幅度的計算。

The main() Function

int main(int argc, char **argv) {QApplication app(argc, argv);GraphWidget *widget = new GraphWidget;QMainWindow mainWindow;mainWindow.setCentralWidget(widget);mainWindow.show();return app.exec(); }

main()函數很簡單,創建了QApplication、mainWindows、GraphWidget,開啟了事件循環。

ps:花了一天的時間閱讀完了,真的是一段段翻譯下來。這個過程還是很耗精力的。移動算法部分是本應用程序的精妙之處,和物理學上的力的合成和分解有關,這里用到定時器來模擬橡筋的力的傳遞過程,非常有意思。算法部分還是有一定難度,等過后慢慢調試理解吧。

總結

以上是生活随笔為你收集整理的Elastic Nodes Example 翻译及学习整理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 免费黄视频在线观看 | 自拍视频在线播放 | 男女一进一出视频 | 亚洲精品乱码久久久久久久久久久久 | 国产精品第3页 | 国内国产精品天干天干 | 日本91网站 | 在线看日本 | 国产探花在线观看 | 男插女av| 日韩av在线播放观看 | 一区二区日本视频 | 日韩av手机在线免费观看 | 欧美日本一道本 | 67194成人在线观看 | 欧美激情精品久久久久久蜜臀 | 亚洲AV无码成人精品区明星换面 | 可以直接看的无码av | 熟妇人妻精品一区二区三区视频 | 最近中文字幕mv免费高清在线 | 国产精品伦一区二区三区免费看 | 成人免费看片39 | 久久99久久99精品免视看婷婷 | 一久久久 | 婷婷六月天| 三级网站在线播放 | av电影中文字幕 | 日韩精品电影一区 | 国产精品视频免费播放 | 亚洲国产日韩精品 | 新超碰在线 | 好吊色视频一区二区 | 日本欧美在线观看 | 亚洲成人一二三 | 中韩毛片 | 成人福利社| 四虎新网址 | 国产区视频在线观看 | 久久午夜鲁丝片 | 午夜影院啊啊啊 | 亚洲乱色熟女一区二区三区 | 中文字幕网站在线观看 | 精品久久久久国产 | 不卡的av在线 | 91福利视频免费观看 | 亚洲天堂视频网 | 亚洲第一黄色片 | 亚洲国产影院 | 国产精品免费视频一区 | 国产精品久久久久久久久夜色 | 欧美视频xxx | 熟女少妇一区二区三区 | 大白屁股一区二区视频 | 日本成人在线播放 | 能直接看的av网站 | 欧美肥妇bwbwbwbxx | 91精品视频网 | 欧美日韩视频免费 | 永久免费汤不热视频 | 国产精品亚洲一区二区三区 | 中国性老太hd大全69 | 精品一区二区三区久久 | 日本人体视频 | 亚洲国产精品成人综合色在线婷婷 | 国产三级日本三级在线播放 | 国产制服丝袜在线 | 亚洲av无码一区二区三区四区 | 天天干天天玩 | 婷婷tv| 国产美女无遮挡永久免费观看 | 国产亚洲精品aaaaaaa片 | 亚洲欧洲视频在线观看 | 综综综综合网 | 国产最新视频在线 | 一区二区三区国产视频 | 欧美激情黑人 | 欲色av | 一级毛毛片 | 亚洲操图 | 久久国产精品免费 | 国产视频一级 | 成人网免费看 | 伊人久久久久久久久久久久久 | 亚洲日批 | 69国产| 7色av| 青青操青青 | 影音先锋激情 | 欧美一级黄| 一本一道久久a久久精品综合 | 蜜桃91丨九色丨蝌蚪91桃色 | 嫩草av久久伊人妇女超级a | 国产午夜精品久久久久 | 国产精品久久久一区二区 | 日批的视频 | a级全黄| 欧美疯狂做受 | 在线天堂www在线国语对白 | 边吃奶边添下面好爽 |