{"id":338667,"date":"2022-09-23T09:00:28","date_gmt":"2022-09-23T09:00:28","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=338667"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=338667","title":{"rendered":"<span>\u0420\u0438\u0441\u0443\u0435\u043c \u043a\u0430\u0440\u0442\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 Qt Quick \u0438 GraphViz<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/82c\/22a\/b41\/82c22ab41f5f749ca3d94813cb1d8a76.png\" width=\"760\" height=\"339\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/82c\/22a\/b41\/82c22ab41f5f749ca3d94813cb1d8a76.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0420\u0435\u0448\u0438\u043b \u0437\u0430\u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u043c\u0443 Jaeger UI. \u042d\u0442\u043e<\/p>\n<ul>\n<li>\n<p>\u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 \u043a\u0430\u0440\u0442\u044b \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043f\u043e \u0442\u0440\u0435\u0439\u0441\u0443;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0449\u0438\u043a \u043b\u043e\u0433\u043e\u0432 \u0431\u0435\u0437 \u043f\u0438\u043a\u0441\u0435\u043b\u044c\u0445\u0430\u043d\u0442\u0438\u043d\u0433\u0430 \u0438 \u0440\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u044f \u0441\u043f\u0430\u043d\u043e\u0432.<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f Qt Widgets \u0435\u0441\u0442\u044c \u043e\u0431\u0435\u0440\u0442\u043a\u0430 \u0432 \u0432\u0438\u0434\u0435 <a href=\"https:\/\/github.com\/nbergont\/qgv\" rel=\"noopener noreferrer nofollow\">nbergont\/qgv<\/a>, \u0430 \u0445\u043e\u0447\u0435\u0442\u0441\u044f \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430 Qt Quick.<\/p>\n<p>\u041a\u0430\u043a \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u044b Qt Quick:<\/p>\n<pre><code class=\"json\">Flickable {     topMargin: 80     leftMargin: 80     bottomMargin: 80     rightMargin: 80      contentWidth: svcMap.width     contentHeight: svcMap.height      ServiceMap {         id: svcMap         visible: true                  graph: item.graph         delegate: Rectangle {             implicitHeight: content.height + 10             implicitWidth: content.width + 10              visible: true             border.color: \"black\"             border.width: 1              ColumnLayout {                 id: content                 x: 5                 y: 5                  Text {                     text: node.name                     Layout.minimumWidth: 100                     font.bold: true                 }                  Rectangle {                     visible: node.hasEdges                     height: 1                     width: 10                     Layout.fillWidth: true                     color: \"gray\"                 }                  Repeater {                     model: node.operations                      Text {                         text: modelData                     }                 }             }             MouseArea {                 anchors.fill: parent                 onClicked: {                     nodeItem.setNode(node);                 }             }         }     } }<\/code><\/pre>\n<p><em>ServiceMap<\/em> \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u043c \u0432\u043e <em>Flickable<\/em> \u043d\u0430 \u0441\u043b\u0443\u0447\u0430\u0439 \u0435\u0441\u043b\u0438 \u0433\u0440\u0430\u0444 \u043d\u0435 \u0432\u043b\u0435\u0437\u0435\u0442 \u0432 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0435 \u0433\u0440\u0430\u043d\u0438\u0446\u044b. \u0414\u0435\u043b\u0435\u0433\u0430\u0442 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0440\u0430\u0437\u043c\u0435\u0440 \u0438\u0441\u0445\u043e\u0434\u044f \u0438\u0437 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 <strong>node<\/strong>.\u041d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0421++ \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0448\u0430\u0433\u043e\u0432:<\/p>\n<ul>\n<li>\n<p>\u043f\u0440\u043e\u0431\u0435\u0436\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0432\u0435\u0440\u0448\u0438\u043d\u0430\u043c \u0438 \u0440\u0435\u0431\u0440\u0430\u043c \u0433\u0440\u0430\u0444\u0430, \u0441\u043e\u0437\u0434\u0430\u0442\u044c <em>QQuickItem<\/em> \u0441\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e\u043c <strong>node<\/strong>, \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0440\u0430\u0437\u043c\u0435\u0440\u0430;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u0431\u0435\u0436\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0432\u0435\u0440\u0448\u0438\u043d\u0430\u043c \u0438 \u0440\u0435\u0431\u0440\u0430\u043c \u0433\u0440\u0430\u0444\u0430, \u0441\u043e\u0437\u0434\u0430\u0442\u044c <em>Agnode_t<\/em>, <em>Agedge_t<\/em> \u0432 <em>GraphViz<\/em>, \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0440\u0430\u0441\u0447\u0435\u0442 \u0433\u0440\u0430\u0444\u0430 \u0432 <em>GraphViz<\/em>;<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u0435\u0440\u0448\u0438\u043d\u0430\u043c \u0438 \u0440\u0435\u0431\u0440\u0430\u043c \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b.<\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <em>ServiceMap<\/em>, \u0434\u043b\u044f \u0434\u0435\u043b\u0435\u0433\u0430\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0442\u0438\u043f <em>QQmlComponent<\/em>:<\/p>\n<pre><code class=\"cpp\">struct ServiceMapCtx;  class ServiceMap : public QQuickItem {     Q_OBJECT     Q_PROPERTY(TraceGraph graph READ getGraph WRITE setGraph NOTIFY notifyGraphChanged)     Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY notifyDelegateChanged) public:     static constexpr qreal DPI = 72.0; \/\/https:\/\/graphviz.org\/doc\/info\/attrs.html      explicit ServiceMap(QQuickItem *parent = nullptr);     ~ServiceMap();      const TraceGraph &amp;getGraph() const;     void setGraph(const TraceGraph &amp;data);      QQmlComponent *delegate() const;     void setDelegate(QQmlComponent *delegate);  signals:      void notifyGraphChanged();     void notifyDelegateChanged();  private:     void makeServiceGraph();     void makeQuickNodes();     void computeLayout();     void resetGraph();  private:     std::unique_ptr&lt;ServiceMapCtx> m_ctx;     TraceGraph m_trace;     QQmlComponent *m_delegate;      QVector&lt;ServiceMapNode> m_nodes;     QVector&lt;ServiceMapEdge> m_edges; };<\/code><\/pre>\n<p>\u041d\u0435\u043c\u043d\u043e\u0436\u043a\u043e \u0445\u0435\u043b\u043f\u0435\u0440\u043e\u0432, <em>GraphViz<\/em> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0438, \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432:<\/p>\n<pre><code class=\"cpp\">namespace { struct ContextDeleter {     void operator()(GVC_t *ctx) const     {         gvFinalize(ctx);         if (gvFreeContext(ctx) != 0) {             qWarning() &lt;&lt; \"gvFreeContext != 0\";         }     } };  struct GraphDeleter {     void operator()(Agraph_t *graph) const     {         if (agclose(graph) != 0) {             qWarning() &lt;&lt; \"agclose != 0\";         }     } };  using GVContextPtr = std::unique_ptr&lt;GVC_t, ContextDeleter>; using GVGraphPtr = std::unique_ptr&lt;Agraph_t, GraphDeleter>;  template&lt;typename NodeType> void setAttribute(NodeType *node, const QString &amp;key, const QString &amp;value) {     char empty[] = \"\";      auto k = key.toLatin1();     auto v = value.toLatin1();     agsafeset(node, k.data(), v.data(), empty); }   } \/\/ namespace \/\/... struct ServiceMapCtx {     ServiceMapCtx()         : ctx(gvContext())         , graph(agopen(\"service_map\", Agdirected, NULL))     {         setGraphAttribute(\"label\", \"service map\");          setGraphAttribute(\"rankdir\", \"LR\");         setGraphAttribute(\"nodesep\", \"0.5\");         \/\/setGraphAttribute(\"splines\", \"ortho\");          setNodeAttribute(\"shape\", \"box\");         setEdgeAttribute(\"minlen\", \"3\");     }      ~ServiceMapCtx() { gvFreeLayout(ctx.get(), graph.get()); }      void setNodeAttribute(const QString &amp;name, const QString &amp;value)     {         if (graph) {             agattr(graph.get(), AGNODE, name.toLocal8Bit().data(), value.toLocal8Bit().data());         }     }      void setGraphAttribute(const QString &amp;name, const QString &amp;value)     {         if (graph) {             agattr(graph.get(), AGRAPH, name.toLocal8Bit().data(), value.toLocal8Bit().data());         }     }      void setEdgeAttribute(const QString &amp;name, const QString &amp;value)     {         if (graph) {             agattr(graph.get(), AGEDGE, name.toLocal8Bit().data(), value.toLocal8Bit().data());         }     }      GVContextPtr ctx;     GVGraphPtr graph; };  ServiceMap::ServiceMap(QQuickItem *parent)     : QQuickItem(parent)     , m_ctx(std::make_unique&lt;ServiceMapCtx>())     , m_delegate(nullptr) {     setFlag(QQuickItem::ItemHasContents); }  ServiceMap::~ServiceMap() {}<\/code><\/pre>\n<p>\u0414\u043b\u044f \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0445 <em>QQuickItem<\/em> \u043d\u0443\u0436\u043d\u043e \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u0444\u043b\u0430\u0433 <em>QQuickItem::ItemHasContents<\/em> \u0432 true, \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0447\u0442\u043e item \u0438\u043c\u0435\u0435\u0442 \u0434\u0435\u0442\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0442\u044c. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0432\u0435\u0440\u0448\u0438\u043d \u0438 \u0440\u0435\u0431\u0435\u0440:<\/p>\n<pre><code class=\"cpp\">void ServiceMap::makeQuickNodes() {     for (auto &amp;node : m_nodes) {         auto creationCtx = m_delegate->creationContext();         auto ctx = new QQmlContext(creationCtx ? creationCtx : qmlContext(this));          auto item = m_delegate->beginCreate(ctx);         if (item) {             ctx->setContextProperty(\"node\", QVariant::fromValue(node));              auto quickItem = qobject_cast&lt;QQuickItem *>(item);              quickItem->setParentItem(this);             quickItem->setZ(1);             node.qmlObject = quickItem;         } else {             qCritical() &lt;&lt; \"failed create QQuickItem from delegate\" &lt;&lt; m_delegate->errors();         }         m_delegate->completeCreate();     }      for (auto &amp;edge : m_edges) {         edge.qmlObject = new EdgeItem;         edge.qmlObject->setZ(2);         edge.qmlObject->setParentItem(this);     } }<\/code><\/pre>\n<p>\u0412\u0435\u0440\u0448\u0438\u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u0430\u043f\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 <em>beginCreate<\/em>\/<em>completeCreate<\/em>, \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 <strong>node<\/strong>. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0432\u0435\u0440\u0448\u0438\u043d\u044b \u0438 \u0443\u0437\u043b\u044b \u0432 <em>GraphViz<\/em>, \u0434\u043b\u0438\u043d\u044b \u0437\u0430\u0434\u0430\u044e\u0442\u0441\u044f \u0432 \u0434\u044e\u0439\u043c\u0430\u0445, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0437\u0430\u0448\u0438\u0442\u043e 72 DPI:<\/p>\n<pre><code class=\"cpp\">void applySize(ServiceMapNode &amp;node, qreal DPI) {     if (node.qmlObject) {         auto size = node.qmlObject->size();         auto widthIn = QString::number(qreal(size.width()) \/ DPI);         auto heightIn = QString::number(qreal(size.height()) \/ DPI);         setAttribute(node.gvNode, \"width\", widthIn);         setAttribute(node.gvNode, \"height\", heightIn);         setAttribute(node.gvNode, \"fixedsize\", \"true\");     } }  \/\/... auto graph = m_ctx->graph.get(); QHash&lt;graph::Process *, Agnode_t *> nodeMap; for (auto &amp;node : m_nodes) {     node.gvNode = agnode(graph, NULL, true);     nodeMap.insert(node.process, node.gvNode);     applySize(node, DPI); }  for (auto &amp;edge : m_edges) {     auto from = nodeMap[edge.from];     auto to = nodeMap[edge.to];     edge.gvEdge = agedge(graph, from, to, NULL, TRUE); }<\/code><\/pre>\n<p>\u0421\u0430\u043c \u0440\u0430\u0441\u0447\u0435\u0442 \u0433\u0440\u0430\u0444\u0430 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <em>gvLayout <\/em>\u0438\u0437 gvc.h(libgvc). \u0412 \u044d\u0442\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0440\u0430\u0441\u0447\u0435\u0442\u0430 \u0438 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"cpp\">if (gvLayout(m_ctx->ctx.get(), graph, \"dot\") != 0) {     qCritical() &lt;&lt; \"Layout render error\" &lt;&lt; agerrors() &lt;&lt; QString::fromLocal8Bit(aglasterr()); }<\/code><\/pre>\n<p>\u0412\u044b\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0440 <em>ServiceMap<\/em>, \u0433\u0434\u0435 <code>UR<\/code> \u044d\u0442\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0432\u0435\u0440\u0445\u043d\u0435\u0433\u043e \u043f\u0440\u0430\u0432\u043e\u0433\u043e \u0443\u0433\u043b\u0430 <code>typedef struct { pointf LL, UR; } boxf<\/code>:<\/p>\n<pre><code class=\"cpp\">qreal gvGraphHeight = GD_bb(graph).UR.y; qreal gvGraphWidth = GD_bb(graph).UR.x; setImplicitHeight(gvGraphHeight); setImplicitWidth(gvGraphWidth);<\/code><\/pre>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u043d\u0443\u0436\u043d\u043e \u0440\u0430\u0441\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u0435\u0440\u0448\u0438\u043d\u044b <em>QQuickItem<\/em> \u043f\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043b  <em>GraphViz<\/em>. \u0412 <em>GraphViz<\/em> \u043e\u0441\u044c Y \u0438\u0434\u0435\u0442 \u0441\u043d\u0438\u0437\u0443 \u0432\u0432\u0435\u0440\u0445, \u0430 \u0432 Qt \u0441\u0432\u0435\u0440\u0445\u0443 \u0432 \u043d\u0438\u0437. \u0410 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430 \u0432\u0435\u0440\u0448\u0438\u043d\u044b <em>GraphViz<\/em> \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0446\u0435\u043d\u0442\u0440\u0435 \u0444\u0438\u0433\u0443\u0440\u044b. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0440\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0438 \u0441\u043c\u0435\u0449\u0430\u0435\u043c \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b:<\/p>\n<pre><code class=\"cpp\">QPointF centerToOrigin(const QPointF &amp;p, qreal width, qreal height) {     return QPointF(p.x() - width \/ 2, p.y() - height \/ 2); } \/\/... for (auto &amp;node : m_nodes) {     auto gvPos = ND_coord(node.gvNode);     QPoint pt(gvPos.x, gvGraphHeight - gvPos.y);     auto org = centerToOrigin(pt, node.qmlObject->width(), node.qmlObject->height());     node.qmlObject->setPosition(org); }<\/code><\/pre>\n<p>\u0421 \u0440\u0435\u0431\u0440\u0430\u043c\u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435. \u0412\u044b\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0435\u043c \u0442\u043e\u0447\u043a\u0438, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0431\u0443\u0434\u0435\u043c \u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u043d\u0438\u044e:<\/p>\n<pre><code class=\"cpp\">for (auto &amp;edge : m_edges) {     auto spline = ED_spl(edge.gvEdge);     QVector&lt;QPointF> points;      if (spline->size != 0) {         bezier bez = spline->list[0];         points.reserve(bez.size);          for (int i = 0; i &lt; bez.size; ++i) {             auto &amp;p = bez.list[i];             points &lt;&lt; QPointF(p.x, gvGraphHeight - p.y);         }         points &lt;&lt; QPointF(spline->list->ep.x, gvGraphHeight - spline->list->ep.y);     }      edge.qmlObject->setPoints(points);<\/code><\/pre>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442 \u0441 \u043d\u0435\u043e\u0431\u044b\u0447\u043d\u043e\u0439 \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0435\u0439. \u0412 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 <a href=\"https:\/\/doc.qt.io\/qt-6\/qtquick-codesamples.html#qt-quick-demos\" rel=\"noopener noreferrer nofollow\">Qt Quick Examples and Tutorials<\/a> \u0435\u0441\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0449\u0438\u0435 \u043d\u0430\u0441 \u0432\u0435\u0449\u0438. \u041e\u0442 \u0442\u0443\u0434\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0433\u0440\u0430\u0444\u043e\u043c \u0441\u0446\u0435\u043d\u044b(<em>Scene Graph<\/em>),  \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0432\u043e\u0437\u044c\u043c\u0435\u043c \u0434\u0432\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 <a href=\"https:\/\/doc.qt.io\/qt-6\/qtquick-scenegraph-graph-example.html\" rel=\"noopener noreferrer nofollow\">Graph<\/a> \u0438 <a href=\"https:\/\/doc.qt.io\/qt-6\/qtquick-scenegraph-customgeometry-example.html\" rel=\"noopener noreferrer nofollow\">Custom Geometry<\/a>. \u0422.\u043a. \u044d\u0442\u043e \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f, \u0442\u043e \u0434\u043b\u044f \u0440\u0435\u0431\u0435\u0440 \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u043a\u043e\u0434 \u043d\u0430 \u0432\u044b\u0431\u0440\u043e\u0441:<\/p>\n<pre><code class=\"cpp\">class EdgeItem : public QQuickItem {     Q_OBJECT     QML_ELEMENT public:     explicit EdgeItem(QQuickItem *parent = nullptr);     QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;      void setPoints(const QVector&lt;QPointF> &amp;points);  private:     QVector&lt;QPointF> m_points;     QSGGeometryNode *m_arrowNode; }; \/\/\/... EdgeItem::EdgeItem(QQuickItem *parent)     : QQuickItem(parent)     , m_arrowNode(nullptr) {     setFlag(ItemHasContents); }  QSGNode *EdgeItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) {     if (!m_arrowNode) {         m_arrowNode = new QSGGeometryNode;         auto geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 3);         geometry->setLineWidth(1);         geometry->setDrawingMode(QSGGeometry::DrawTriangles);         m_arrowNode->setGeometry(geometry);         m_arrowNode->setFlag(QSGNode::OwnsGeometry);         auto *material = new QSGFlatColorMaterial;         material->setColor(QColor(\"black\"));         m_arrowNode->setMaterial(material);         m_arrowNode->setFlag(QSGNode::OwnsMaterial);         geometry->allocate(3);     }      QSGGeometryNode *node = nullptr;     QSGGeometry *geometry = nullptr;      if (!oldNode) {         node = new QSGGeometryNode;         geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(),                                    std::max(m_points.size() - 1, qsizetype(0)));         geometry->setLineWidth(1);         geometry->setDrawingMode(QSGGeometry::DrawLineStrip);         node->setGeometry(geometry);         node->setFlag(QSGNode::OwnsGeometry);         auto *material = new QSGFlatColorMaterial;         material->setColor(QColor(\"black\"));         node->setMaterial(material);         node->setFlag(QSGNode::OwnsMaterial);          node->appendChildNode(m_arrowNode);     } else {         node = static_cast&lt;QSGGeometryNode *>(oldNode);         geometry = node->geometry();         geometry->allocate(m_points.size() - 1);     }      QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D();      for (int i = 0; i &lt; m_points.size() - 1; ++i) {         vertices[i].set(m_points[i].x(), m_points[i].y());     }      if (!m_points.isEmpty()) {         QLineF line(m_points[m_points.size() - 2], m_points[m_points.size() - 1]);         QLineF n = line.normalVector();         QPointF o(n.dx() \/ 3.0, n.dy() \/ 3.0);          auto arrVertices = m_arrowNode->geometry()->vertexDataAsPoint2D();         arrVertices[0].set(line.p1().x() + o.x(), line.p1().y() + o.y());         arrVertices[1].set(line.p2().x(), line.p2().y());         arrVertices[2].set(line.p1().x() - o.x(), line.p1().y() - o.y());     }      node->markDirty(QSGNode::DirtyGeometry);     return node; }  void EdgeItem::setPoints(const QVector&lt;QPointF> &amp;points) {     m_points = points;     update(); }<\/code><\/pre>\n<p>\u042d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e. \u0415\u0441\u043b\u0438 \u0437\u0430\u0434\u0430\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u0440\u0435\u0431\u0435\u0440 <code>setGraphAttribute(\"splines\", \"ortho\")<\/code>, \u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e06\/c86\/e43\/e06c86e430e6a34f783470bfb672ce2f.png\" width=\"840\" height=\"398\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e06\/c86\/e43\/e06c86e430e6a34f783470bfb672ce2f.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u041a\u043e\u0434 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u043d\u0430 <a href=\"https:\/\/github.com\/RPG-18\/jgv\" rel=\"noopener noreferrer nofollow\">RPG-18\/jgv<\/a> \u0438 \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u043b\u0438\u0447\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u043a\u043e\u0434\u0430 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/689496\/\"> https:\/\/habr.com\/ru\/post\/689496\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u0420\u0435\u0448\u0438\u043b \u0437\u0430\u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u043c\u0443 Jaeger UI. \u042d\u0442\u043e<\/p>\n<ul>\n<li>\n<p>\u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 \u043a\u0430\u0440\u0442\u044b \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043f\u043e \u0442\u0440\u0435\u0439\u0441\u0443;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0449\u0438\u043a \u043b\u043e\u0433\u043e\u0432 \u0431\u0435\u0437 \u043f\u0438\u043a\u0441\u0435\u043b\u044c\u0445\u0430\u043d\u0442\u0438\u043d\u0433\u0430 \u0438 \u0440\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u044f \u0441\u043f\u0430\u043d\u043e\u0432.<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f Qt Widgets \u0435\u0441\u0442\u044c \u043e\u0431\u0435\u0440\u0442\u043a\u0430 \u0432 \u0432\u0438\u0434\u0435 <a href=\"https:\/\/github.com\/nbergont\/qgv\" rel=\"noopener noreferrer nofollow\">nbergont\/qgv<\/a>, \u0430 \u0445\u043e\u0447\u0435\u0442\u0441\u044f \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430 Qt Quick.<\/p>\n<p>\u041a\u0430\u043a \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u044b Qt Quick:<\/p>\n<pre><code class=\"json\">Flickable {     topMargin: 80     leftMargin: 80     bottomMargin: 80     rightMargin: 80      contentWidth: svcMap.width     contentHeight: svcMap.height      ServiceMap {         id: svcMap         visible: true                  graph: item.graph         delegate: Rectangle {             implicitHeight: content.height + 10             implicitWidth: content.width + 10              visible: true             border.color: \"black\"             border.width: 1              ColumnLayout {                 id: content                 x: 5                 y: 5                  Text {                     text: node.name                     Layout.minimumWidth: 100                     font.bold: true                 }                  Rectangle {                     visible: node.hasEdges                     height: 1                     width: 10                     Layout.fillWidth: true                     color: \"gray\"                 }                  Repeater {                     model: node.operations                      Text {                         text: modelData                     }                 }             }             MouseArea {                 anchors.fill: parent                 onClicked: {                     nodeItem.setNode(node);                 }             }         }     } }<\/code><\/pre>\n<p><em>ServiceMap<\/em> \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u043c \u0432\u043e <em>Flickable<\/em> \u043d\u0430 \u0441\u043b\u0443\u0447\u0430\u0439 \u0435\u0441\u043b\u0438 \u0433\u0440\u0430\u0444 \u043d\u0435 \u0432\u043b\u0435\u0437\u0435\u0442 \u0432 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0435 \u0433\u0440\u0430\u043d\u0438\u0446\u044b. \u0414\u0435\u043b\u0435\u0433\u0430\u0442 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0440\u0430\u0437\u043c\u0435\u0440 \u0438\u0441\u0445\u043e\u0434\u044f \u0438\u0437 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 <strong>node<\/strong>.\u041d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0421++ \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0448\u0430\u0433\u043e\u0432:<\/p>\n<ul>\n<li>\n<p>\u043f\u0440\u043e\u0431\u0435\u0436\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0432\u0435\u0440\u0448\u0438\u043d\u0430\u043c \u0438 \u0440\u0435\u0431\u0440\u0430\u043c \u0433\u0440\u0430\u0444\u0430, \u0441\u043e\u0437\u0434\u0430\u0442\u044c <em>QQuickItem<\/em> \u0441\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e\u043c <strong>node<\/strong>, \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0440\u0430\u0437\u043c\u0435\u0440\u0430;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u0431\u0435\u0436\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0432\u0435\u0440\u0448\u0438\u043d\u0430\u043c \u0438 \u0440\u0435\u0431\u0440\u0430\u043c \u0433\u0440\u0430\u0444\u0430, \u0441\u043e\u0437\u0434\u0430\u0442\u044c <em>Agnode_t<\/em>, <em>Agedge_t<\/em> \u0432 <em>GraphViz<\/em>, \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0440\u0430\u0441\u0447\u0435\u0442 \u0433\u0440\u0430\u0444\u0430 \u0432 <em>GraphViz<\/em>;<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u0435\u0440\u0448\u0438\u043d\u0430\u043c \u0438 \u0440\u0435\u0431\u0440\u0430\u043c \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b.<\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <em>ServiceMap<\/em>, \u0434\u043b\u044f \u0434\u0435\u043b\u0435\u0433\u0430\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0442\u0438\u043f <em>QQmlComponent<\/em>:<\/p>\n<pre><code class=\"cpp\">struct ServiceMapCtx;  class ServiceMap : public QQuickItem {     Q_OBJECT     Q_PROPERTY(TraceGraph graph READ getGraph WRITE setGraph NOTIFY notifyGraphChanged)     Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY notifyDelegateChanged) public:     static constexpr qreal DPI = 72.0; \/\/https:\/\/graphviz.org\/doc\/info\/attrs.html      explicit ServiceMap(QQuickItem *parent = nullptr);     ~ServiceMap();      const TraceGraph &amp;getGraph() const;     void setGraph(const TraceGraph &amp;data);      QQmlComponent *delegate() const;     void setDelegate(QQmlComponent *delegate);  signals:      void notifyGraphChanged();     void notifyDelegateChanged();  private:     void makeServiceGraph();     void makeQuickNodes();     void computeLayout();     void resetGraph();  private:     std::unique_ptr&lt;ServiceMapCtx> m_ctx;     TraceGraph m_trace;     QQmlComponent *m_delegate;      QVector&lt;ServiceMapNode> m_nodes;     QVector&lt;ServiceMapEdge> m_edges; };<\/code><\/pre>\n<p>\u041d\u0435\u043c\u043d\u043e\u0436\u043a\u043e \u0445\u0435\u043b\u043f\u0435\u0440\u043e\u0432, <em>GraphViz<\/em> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0438, \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432:<\/p>\n<pre><code class=\"cpp\">namespace { struct ContextDeleter {     void operator()(GVC_t *ctx) const     {         gvFinalize(ctx);         if (gvFreeContext(ctx) != 0) {             qWarning() &lt;&lt; \"gvFreeContext != 0\";         }     } };  struct GraphDeleter {     void operator()(Agraph_t *graph) const     {         if (agclose(graph) != 0) {             qWarning() &lt;&lt; \"agclose != 0\";         }     } };  using GVContextPtr = std::unique_ptr&lt;GVC_t, ContextDeleter>; using GVGraphPtr = std::unique_ptr&lt;Agraph_t, GraphDeleter>;  template&lt;typename NodeType> void setAttribute(NodeType *node, const QString &amp;key, const QString &amp;value) {     char empty[] = \"\";      auto k = key.toLatin1();     auto v = value.toLatin1();     agsafeset(node, k.data(), v.data(), empty); }   } \/\/ namespace \/\/... struct ServiceMapCtx {     ServiceMapCtx()         : ctx(gvContext())         , graph(agopen(\"service_map\", Agdirected, NULL))     {         setGraphAttribute(\"label\", \"service map\");          setGraphAttribute(\"rankdir\", \"LR\");         setGraphAttribute(\"nodesep\", \"0.5\");         \/\/setGraphAttribute(\"splines\", \"ortho\");          setNodeAttribute(\"shape\", \"box\");         setEdgeAttribute(\"minlen\", \"3\");     }      ~ServiceMapCtx() { gvFreeLayout(ctx.get(), graph.get()); }      void setNodeAttribute(const QString &amp;name, const QString &amp;value)     {         if (graph) {             agattr(graph.get(), AGNODE, name.toLocal8Bit().data(), value.toLocal8Bit().data());         }     }      void setGraphAttribute(const QString &amp;name, const QString &amp;value)     {         if (graph) {             agattr(graph.get(), AGRAPH, name.toLocal8Bit().data(), value.toLocal8Bit().data());         }     }      void setEdgeAttribute(const QString &amp;name, const QString &amp;value)     {         if (graph) {             agattr(graph.get(), AGEDGE, name.toLocal8Bit().data(), value.toLocal8Bit().data());         }     }      GVContextPtr ctx;     GVGraphPtr graph; };  ServiceMap::ServiceMap(QQuickItem *parent)     : QQuickItem(parent)     , m_ctx(std::make_unique&lt;ServiceMapCtx>())     , m_delegate(nullptr) {     setFlag(QQuickItem::ItemHasContents); }  ServiceMap::~ServiceMap() {}<\/code><\/pre>\n<p>\u0414\u043b\u044f \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0445 <em>QQuickItem<\/em> \u043d\u0443\u0436\u043d\u043e \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u0444\u043b\u0430\u0433 <em>QQuickItem::ItemHasContents<\/em> \u0432 true, \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0447\u0442\u043e item \u0438\u043c\u0435\u0435\u0442 \u0434\u0435\u0442\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0442\u044c. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0432\u0435\u0440\u0448\u0438\u043d \u0438 \u0440\u0435\u0431\u0435\u0440:<\/p>\n<pre><code class=\"cpp\">void ServiceMap::makeQuickNodes() {     for (auto &amp;node : m_nodes) {         auto creationCtx = m_delegate->creationContext();         auto ctx = new QQmlContext(creationCtx ? creationCtx : qmlContext(this));          auto item = m_delegate->beginCreate(ctx);         if (item) {             ctx->setContextProperty(\"node\", QVariant::fromValue(node));              auto quickItem = qobject_cast&lt;QQuickItem *>(item);              quickItem->setParentItem(this);             quickItem->setZ(1);             node.qmlObject = quickItem;         } else {             qCritical() &lt;&lt; \"failed create QQuickItem from delegate\" &lt;&lt; m_delegate->errors();         }         m_delegate->completeCreate();     }      for (auto &amp;edge : m_edges) {         edge.qmlObject = new EdgeItem;         edge.qmlObject->setZ(2);         edge.qmlObject->setParentItem(this);     } }<\/code><\/pre>\n<p>\u0412\u0435\u0440\u0448\u0438\u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u0430\u043f\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 <em>beginCreate<\/em>\/<em>completeCreate<\/em>, \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 <strong>node<\/strong>. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0432\u0435\u0440\u0448\u0438\u043d\u044b \u0438 \u0443\u0437\u043b\u044b \u0432 <em>GraphViz<\/em>, \u0434\u043b\u0438\u043d\u044b \u0437\u0430\u0434\u0430\u044e\u0442\u0441\u044f \u0432 \u0434\u044e\u0439\u043c\u0430\u0445, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0437\u0430\u0448\u0438\u0442\u043e 72 DPI:<\/p>\n<pre><code class=\"cpp\">void applySize(ServiceMapNode &amp;node, qreal DPI) {     if (node.qmlObject) {         auto size = node.qmlObject->size();         auto widthIn = QString::number(qreal(size.width()) \/ DPI);         auto heightIn = QString::number(qreal(size.height()) \/ DPI);         setAttribute(node.gvNode, \"width\", widthIn);         setAttribute(node.gvNode, \"height\", heightIn);         setAttribute(node.gvNode, \"fixedsize\", \"true\");     } }  \/\/... auto graph = m_ctx->graph.get(); QHash&lt;graph::Process *, Agnode_t *> nodeMap; for (auto &amp;node : m_nodes) {     node.gvNode = agnode(graph, NULL, true);     nodeMap.insert(node.process, node.gvNode);     applySize(node, DPI); }  for (auto &amp;edge : m_edges) {     auto from = nodeMap[edge.from];     auto to = nodeMap[edge.to];     edge.gvEdge = agedge(graph, from, to, NULL, TRUE); }<\/code><\/pre>\n<p>\u0421\u0430\u043c \u0440\u0430\u0441\u0447\u0435\u0442 \u0433\u0440\u0430\u0444\u0430 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <em>gvLayout <\/em>\u0438\u0437 gvc.h(libgvc). \u0412 \u044d\u0442\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0440\u0430\u0441\u0447\u0435\u0442\u0430 \u0438 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"cpp\">if (gvLayout(m_ctx->ctx.get(), graph, \"dot\") != 0) {     qCritical() &lt;&lt; \"Layout render error\" &lt;&lt; agerrors() &lt;&lt; QString::fromLocal8Bit(aglasterr()); }<\/code><\/pre>\n<p>\u0412\u044b\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0440\u0430\u0437\u043c\u0435\u0440 <em>ServiceMap<\/em>, \u0433\u0434\u0435 <code>UR<\/code> \u044d\u0442\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0432\u0435\u0440\u0445\u043d\u0435\u0433\u043e \u043f\u0440\u0430\u0432\u043e\u0433\u043e \u0443\u0433\u043b\u0430 <code>typedef struct { pointf LL, UR; } boxf<\/code>:<\/p>\n<pre><code class=\"cpp\">qreal gvGraphHeight = GD_bb(graph).UR.y; qreal gvGraphWidth = GD_bb(graph).UR.x; setImplicitHeight(gvGraphHeight); setImplicitWidth(gvGraphWidth);<\/code><\/pre>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u043d\u0443\u0436\u043d\u043e \u0440\u0430\u0441\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u0435\u0440\u0448\u0438\u043d\u044b <em>QQuickItem<\/em> \u043f\u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043b  <em>GraphViz<\/em>. \u0412 <em>GraphViz<\/em> \u043e\u0441\u044c Y \u0438\u0434\u0435\u0442 \u0441\u043d\u0438\u0437\u0443 \u0432\u0432\u0435\u0440\u0445, \u0430 \u0432 Qt \u0441\u0432\u0435\u0440\u0445\u0443 \u0432 \u043d\u0438\u0437. \u0410 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430 \u0432\u0435\u0440\u0448\u0438\u043d\u044b <em>GraphViz<\/em> \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0446\u0435\u043d\u0442\u0440\u0435 \u0444\u0438\u0433\u0443\u0440\u044b. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0440\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0438 \u0441\u043c\u0435\u0449\u0430\u0435\u043c \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b:<\/p>\n<pre><code class=\"cpp\">QPointF centerToOrigin(const QPointF &amp;p, qreal width, qreal height) {     return QPointF(p.x() - width \/ 2, p.y() - height \/ 2); } \/\/... for (auto &amp;node : m_nodes) {     auto gvPos = ND_coord(node.gvNode);     QPoint pt(gvPos.x, gvGraphHeight - gvPos.y);     auto org = centerToOrigin(pt, node.qmlObject->width(), node.qmlObject->height());     node.qmlObject->setPosition(org); }<\/code><\/pre>\n<p>\u0421 \u0440\u0435\u0431\u0440\u0430\u043c\u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435. \u0412\u044b\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0435\u043c \u0442\u043e\u0447\u043a\u0438, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0431\u0443\u0434\u0435\u043c \u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u043d\u0438\u044e:<\/p>\n<pre><code class=\"cpp\">for (auto &amp;edge : m_edges) {     auto spline = ED_spl(edge.gvEdge);     QVector&lt;QPointF> points;      if (spline->size != 0) {         bezier bez = spline->list[0];         points.reserve(bez.size);          for (int i = 0; i &lt; bez.size; ++i) {             auto &amp;p = bez.list[i];             points &lt;&lt; QPointF(p.x, gvGraphHeight - p.y);         }         points &lt;&lt; QPointF(spline->list->ep.x, gvGraphHeight - spline->list->ep.y);     }      edge.qmlObject->setPoints(points);<\/code><\/pre>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442 \u0441 \u043d\u0435\u043e\u0431\u044b\u0447\u043d\u043e\u0439 \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0435\u0439. \u0412 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 <a href=\"https:\/\/doc.qt.io\/qt-6\/qtquick-codesamples.html#qt-quick-demos\" rel=\"noopener noreferrer nofollow\">Qt Quick Examples and Tutorials<\/a> \u0435\u0441\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0449\u0438\u0435 \u043d\u0430\u0441 \u0432\u0435\u0449\u0438. \u041e\u0442 \u0442\u0443\u0434\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0433\u0440\u0430\u0444\u043e\u043c \u0441\u0446\u0435\u043d\u044b(<em>Scene Graph<\/em>),  \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0432\u043e\u0437\u044c\u043c\u0435\u043c \u0434\u0432\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 <a href=\"https:\/\/doc.qt.io\/qt-6\/qtquick-scenegraph-graph-example.html\" rel=\"noopener noreferrer nofollow\">Graph<\/a> \u0438 <a href=\"https:\/\/doc.qt.io\/qt-6\/qtquick-scenegraph-customgeometry-example.html\" rel=\"noopener noreferrer nofollow\">Custom Geometry<\/a>. \u0422.\u043a. \u044d\u0442\u043e \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f, \u0442\u043e \u0434\u043b\u044f \u0440\u0435\u0431\u0435\u0440 \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u043a\u043e\u0434 \u043d\u0430 \u0432\u044b\u0431\u0440\u043e\u0441:<\/p>\n<pre><code class=\"cpp\">class EdgeItem : public QQuickItem {     Q_OBJECT     QML_ELEMENT public:     explicit EdgeItem(QQuickItem *parent = nullptr);     QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;      void setPoints(const QVector&lt;QPointF> &amp;points);  private:     QVector&lt;QPointF> m_points;     QSGGeometryNode *m_arrowNode; }; \/\/\/... EdgeItem::EdgeItem(QQuickItem *parent)     : QQuickItem(parent)     , m_arrowNode(nullptr) {     setFlag(ItemHasContents); }  QSGNode *EdgeItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) <\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-338667","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/338667","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=338667"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/338667\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=338667"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=338667"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=338667"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}