├── LICENSE ├── README.md ├── docs ├── example1.png ├── example2.png └── rbtree.png ├── imgui_graphnode.cpp ├── imgui_graphnode.h ├── imgui_graphnode_demo.cpp ├── imgui_graphnode_demo.h ├── imgui_graphnode_internal.cpp └── imgui_graphnode_internal.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Alexis Bevillard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # imgui-graphnode 2 | [Dear ImGui](https://github.com/ocornut/imgui) helper functions to display nodes using [graphviz](https://graphviz.org/) library 3 | 4 | ## Dependencies 5 | 6 | - [Dear ImGui](https://github.com/ocornut/imgui) 7 | - [graphviz](https://graphviz.org/) 8 | 9 | ## Compilation 10 | 11 | Add the following files to your project: 12 | - imgui_graphnode.cpp 13 | - imgui_graphnode.h 14 | - imgui_graphnode_internal.cpp 15 | - imgui_graphnode_internal.h 16 | - imgui_graphnode_demo.cpp (optional) 17 | - imgui_graphnode_demo.h (optional) 18 | 19 | ## Usage 20 | 21 | A small set of functions are provided, allowing to draw a graph by declaring nodes and edges. 22 | ```c++ 23 | void ImGuiNodeGraph::CreateContext(); 24 | void ImGuiNodeGraph::DestroyContext(); 25 | bool ImGuiNodeGraph::BeginNodeGraph(char const * id, ImGuiGraphNodeLayout layout = ImGuiGraphNodeLayout_Dot, float pixel_per_unit = 100.f); 26 | void ImGuiNodeGraph::NodeGraphAddNode(char const * id); 27 | void ImGuiNodeGraph::NodeGraphAddNode(char const * id, ImVec4 const & color, ImVec4 const & fillcolor); 28 | void ImGuiNodeGraph::NodeGraphAddEdge(char const * id, char const * node_id_a, char const * node_id_b); 29 | void ImGuiNodeGraph::NodeGraphAddEdge(char const * id, char const * node_id_a, char const * node_id_b, ImVec4 const & color); 30 | void ImGuiNodeGraph::EndNodeGraph(); 31 | ``` 32 | 33 | ## Examples 34 | 35 | Examples source code can be found in imgui_graphnode_demo.cpp 36 | 37 | ```c++ 38 | if (ImGuiGraphNode::BeginNodeGraph(ImGuiGraphNodeLayout_Circo)) 39 | { 40 | ImGuiGraphNode::NodeGraphAddNode("A"); 41 | ImGuiGraphNode::NodeGraphAddNode("B"); 42 | ImGuiGraphNode::NodeGraphAddNode("C"); 43 | ImGuiGraphNode::NodeGraphAddNode("D"); 44 | ImGuiGraphNode::NodeGraphAddEdge("a->b", "A", "B"); 45 | ImGuiGraphNode::NodeGraphAddEdge("b->c", "B", "C"); 46 | ImGuiGraphNode::NodeGraphAddEdge("c->d", "C", "D"); 47 | ImGuiGraphNode::NodeGraphAddEdge("d->a", "D", "A"); 48 | ImGuiGraphNode::EndNodeGraph(); 49 | } 50 | ``` 51 | ![alt text](https://github.com/bevilla/imgui-graphnode/raw/master/docs/example1.png "Example 1") 52 | 53 | ```c++ 54 | if (ImGuiGraphNode::BeginNodeGraph(ImGuiGraphNodeLayout_Dot)) 55 | { 56 | ImGuiGraphNode::NodeGraphAddNode("LR_0"); 57 | ImGuiGraphNode::NodeGraphAddNode("LR_1"); 58 | ImGuiGraphNode::NodeGraphAddNode("LR_2"); 59 | ImGuiGraphNode::NodeGraphAddNode("LR_3"); 60 | ImGuiGraphNode::NodeGraphAddNode("LR_4"); 61 | ImGuiGraphNode::NodeGraphAddNode("LR_5"); 62 | ImGuiGraphNode::NodeGraphAddNode("LR_6"); 63 | ImGuiGraphNode::NodeGraphAddNode("LR_7"); 64 | ImGuiGraphNode::NodeGraphAddNode("LR_8"); 65 | ImGuiGraphNode::NodeGraphAddEdge("SS(B)", "LR_0", "LR_2"); 66 | ImGuiGraphNode::NodeGraphAddEdge("SS(S)", "LR_0", "LR_1"); 67 | ImGuiGraphNode::NodeGraphAddEdge("S($end)", "LR_1", "LR_3"); 68 | ImGuiGraphNode::NodeGraphAddEdge("SS(b)", "LR_2", "LR_6"); 69 | ImGuiGraphNode::NodeGraphAddEdge("SS(a)", "LR_2", "LR_5"); 70 | ImGuiGraphNode::NodeGraphAddEdge("S(A)", "LR_2", "LR_4"); 71 | ImGuiGraphNode::NodeGraphAddEdge("S(b)", "LR_5", "LR_7"); 72 | ImGuiGraphNode::NodeGraphAddEdge("S(a)", "LR_5", "LR_5"); 73 | ImGuiGraphNode::NodeGraphAddEdge("S(b)", "LR_6", "LR_6"); 74 | ImGuiGraphNode::NodeGraphAddEdge("S(a)", "LR_6", "LR_5"); 75 | ImGuiGraphNode::NodeGraphAddEdge("S(b)", "LR_7", "LR_8"); 76 | ImGuiGraphNode::NodeGraphAddEdge("S(a)", "LR_7", "LR_5"); 77 | ImGuiGraphNode::NodeGraphAddEdge("S(b)", "LR_8", "LR_6"); 78 | ImGuiGraphNode::NodeGraphAddEdge("S(a)", "LR_8", "LR_5"); 79 | ImGuiGraphNode::EndNodeGraph(); 80 | } 81 | ``` 82 | ![alt text](https://github.com/bevilla/imgui-graphnode/raw/master/docs/example2.png "Example 2") 83 | 84 | ![alt text](https://github.com/bevilla/imgui-graphnode/raw/master/docs/rbtree.png "Red-black tree") 85 | -------------------------------------------------------------------------------- /docs/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevilla/imgui-graphnode/23beaded2fdb1ec9c42ecfca624919664567169f/docs/example1.png -------------------------------------------------------------------------------- /docs/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevilla/imgui-graphnode/23beaded2fdb1ec9c42ecfca624919664567169f/docs/example2.png -------------------------------------------------------------------------------- /docs/rbtree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevilla/imgui-graphnode/23beaded2fdb1ec9c42ecfca624919664567169f/docs/rbtree.png -------------------------------------------------------------------------------- /imgui_graphnode.cpp: -------------------------------------------------------------------------------- 1 | #include "imgui_graphnode.h" 2 | #include "imgui_graphnode_internal.h" 3 | #include "imgui_internal.h" 4 | 5 | static float IsPointInRectangle_IsLeft(ImVec2 const & p0, ImVec2 const & p1, ImVec2 const & p2) 6 | { 7 | return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y); 8 | } 9 | 10 | static bool IsPointInRectangle(ImVec2 const & a, ImVec2 const & b, ImVec2 const & c, ImVec2 const & d, ImVec2 const & p) 11 | { 12 | return IsPointInRectangle_IsLeft(a, b, p) > 0 13 | && IsPointInRectangle_IsLeft(b, c, p) > 0 14 | && IsPointInRectangle_IsLeft(c, d, p) > 0 15 | && IsPointInRectangle_IsLeft(d, a, p) > 0; 16 | } 17 | 18 | void IMGUI_GRAPHNODE_NAMESPACE::CreateContext() 19 | { 20 | IM_ASSERT(g_ctx.gvcontext == nullptr); 21 | g_ctx.gvcontext = gvContext(); 22 | } 23 | 24 | void IMGUI_GRAPHNODE_NAMESPACE::DestroyContext() 25 | { 26 | IM_ASSERT(g_ctx.gvcontext != nullptr); 27 | gvFreeContext(g_ctx.gvcontext); 28 | g_ctx.gvcontext = nullptr; 29 | } 30 | 31 | bool IMGUI_GRAPHNODE_NAMESPACE::BeginNodeGraph(char const * id, ImGuiGraphNodeLayout layout, float pixel_per_unit) 32 | { 33 | g_ctx.lastid = ImGui::GetID(id); 34 | auto & cache = g_ctx.graph_caches[g_ctx.lastid]; 35 | IM_ASSERT(g_ctx.gvgraph == nullptr); 36 | IM_ASSERT(cache.graphid_current.empty()); 37 | g_ctx.gvgraph = agopen(const_cast("g"), Agdirected, 0); 38 | cache.layout = layout; 39 | cache.pixel_per_unit = pixel_per_unit; 40 | 41 | char graphid_buf[16] = { 0 }; 42 | snprintf(graphid_buf, sizeof(graphid_buf) - 1, "%d", (int)layout); 43 | cache.graphid_current += graphid_buf; 44 | 45 | return true; 46 | } 47 | 48 | void IMGUI_GRAPHNODE_NAMESPACE::NodeGraphAddNode(char const * id) 49 | { 50 | ImVec4 const color = ImGui::GetStyle().Colors[ImGuiCol_Text]; 51 | ImVec4 const fillcolor = ImVec4(0.f, 0.f, 0.f, 0.f); 52 | NodeGraphAddNode(id, color, fillcolor); 53 | } 54 | 55 | void IMGUI_GRAPHNODE_NAMESPACE::NodeGraphAddNode(char const * id, ImVec4 const & color, ImVec4 const & fillcolor) 56 | { 57 | auto & cache = g_ctx.graph_caches[g_ctx.lastid]; 58 | IM_ASSERT(g_ctx.gvgraph != nullptr); 59 | Agnode_t * const n = agnode(g_ctx.gvgraph, ImGuiIDToString(id), 1); 60 | IM_ASSERT(n != nullptr); 61 | IMGUI_GRAPHNODE_CREATE_LABEL_ALLOCA(text, id); 62 | auto const color_str = ImVec4ColorToString(color); 63 | auto const fillcolor_str = ImVec4ColorToString(fillcolor); 64 | agsafeset(n, (char *)"label", text, ""); 65 | agsafeset(n, (char *)"color", color_str, ""); 66 | agsafeset(n, (char *)"fillcolor", fillcolor_str, ""); 67 | 68 | cache.graphid_current += id; 69 | cache.graphid_current += color_str; 70 | cache.graphid_current += fillcolor_str; 71 | 72 | ImGuiID const imid = ImGui::GetID(id); 73 | auto const it = cache.graph.nodesBB.find(imid); 74 | ImRect const bb = it != cache.graph.nodesBB.end() ? it->second : ImRect(); 75 | ImGui::ItemAdd(bb, imid); 76 | } 77 | 78 | void IMGUI_GRAPHNODE_NAMESPACE::NodeGraphAddEdge(char const * id, char const * node_id_a, char const * node_id_b) 79 | { 80 | ImVec4 const color = ImGui::GetStyle().Colors[ImGuiCol_Text]; 81 | NodeGraphAddEdge(id, node_id_a, node_id_b, color); 82 | } 83 | 84 | void IMGUI_GRAPHNODE_NAMESPACE::NodeGraphAddEdge(char const * id, char const * node_id_a, char const * node_id_b, ImVec4 const & color) 85 | { 86 | auto & cache = g_ctx.graph_caches[g_ctx.lastid]; 87 | IM_ASSERT(g_ctx.gvgraph != nullptr); 88 | Agnode_t * const a = agnode(g_ctx.gvgraph, ImGuiIDToString(node_id_a), 0); 89 | Agnode_t * const b = agnode(g_ctx.gvgraph, ImGuiIDToString(node_id_b), 0); 90 | IM_ASSERT(a != nullptr); 91 | IM_ASSERT(b != nullptr); 92 | Agedge_t * const e = agedge(g_ctx.gvgraph, a, b, ImGuiIDToString(id), 1); 93 | IM_ASSERT(e != nullptr); 94 | IMGUI_GRAPHNODE_CREATE_LABEL_ALLOCA(text, id); 95 | auto const color_str = ImVec4ColorToString(color); 96 | agsafeset(e, (char *)"label", text, ""); 97 | ImGuiID const imid = ImGui::GetID(id, ImGui::FindRenderedTextEnd(id)); 98 | char identifier[16]; 99 | sprintf(identifier, "#%x", imid); 100 | // graphviz library doesn't serialize the edge's identifier, so we use the 101 | // color field to store the ImGuiID, which will later be used to retrieve 102 | // the edge's properties. 103 | agsafeset(e, (char *)"color", identifier, ""); 104 | cache.edgeIdToInfo[imid] = ImGuiGraphNode_EdgeInfo { ImGui::GetColorU32(color) }; 105 | 106 | cache.graphid_current += id; 107 | cache.graphid_current += node_id_a; 108 | cache.graphid_current += node_id_b; 109 | cache.graphid_current += color_str; 110 | 111 | ImGui::ItemAdd(ImRect(), imid); 112 | auto const it = cache.graph.edgesRectangle.find(imid); 113 | if (it != cache.graph.edgesRectangle.end()) 114 | { 115 | for (auto const & rect : it->second) 116 | { 117 | // Uncomment to draw edge bouding boxes 118 | //ImVec2 lines[] { rect.a, rect.b, rect.c, rect.d }; 119 | //ImGui::GetWindowDrawList()->AddPolyline(lines, 4, IM_COL32(255, 0, 0, 255), ImDrawFlags_Closed, 2.0f); 120 | 121 | if (IsPointInRectangle(rect.a, rect.b, rect.c, rect.d, ImGui::GetIO().MousePos)) 122 | { 123 | GImGui->LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; 124 | break; 125 | } 126 | } 127 | } 128 | } 129 | 130 | int ImGuiGraphNodeFillDrawNodeBuffer(ImGuiGraphNode_Graph & graph, ImGuiGraphNode_DrawNode * drawnodes, ImVec2 cursor_pos, float ppu) 131 | { 132 | int const count = (int)graph.nodes.size(); 133 | 134 | if (drawnodes) 135 | { 136 | constexpr int num_segments = IMGUI_GRAPHNODE_DRAW_NODE_PATH_COUNT - 1; 137 | static_assert(num_segments > 0, ""); 138 | float a_min = 0.f; 139 | float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; 140 | 141 | for (int i = 0; i < count; ++i) 142 | { 143 | ImGuiGraphNode_Node const & node = graph.nodes[i]; 144 | ImVec2 const textsize = ImGui::CalcTextSize(node.label.c_str()); 145 | 146 | for (int j = 0; j <= num_segments; j++) 147 | { 148 | const float a = a_min + ((float)j / (float)num_segments) * (a_max - a_min); 149 | drawnodes[i].path[j].x = cursor_pos.x + (node.pos.x + ImCos(a) * node.size.x / 2.f) * ppu; 150 | drawnodes[i].path[j].y = cursor_pos.y + ((graph.size.y - node.pos.y) + ImSin(a) * node.size.y / 2.f) * ppu; 151 | } 152 | drawnodes[i].textpos.x = cursor_pos.x + node.pos.x * ppu - textsize.x / 2.f; 153 | drawnodes[i].textpos.y = cursor_pos.y + (graph.size.y - node.pos.y) * ppu - textsize.y / 2.f; 154 | drawnodes[i].text = node.label.c_str(); 155 | drawnodes[i].color = node.color; 156 | drawnodes[i].fillcolor = node.fillcolor; 157 | 158 | ImRect const bb( 159 | cursor_pos.x + (node.pos.x - node.size.x / 2.f) * ppu, 160 | cursor_pos.y + ((graph.size.y - node.pos.y) - node.size.y / 2.f) * ppu, 161 | cursor_pos.x + (node.pos.x + node.size.x / 2.f) * ppu, 162 | cursor_pos.y + ((graph.size.y - node.pos.y) + node.size.y / 2.f) * ppu 163 | ); 164 | ImGuiID const imid = atol(node.name.c_str()); 165 | graph.nodesBB[imid] = bb; 166 | } 167 | } 168 | return count; 169 | } 170 | 171 | int ImGuiGraphNodeFillDrawEdgeBuffer(ImGuiGraphNode_Graph & graph, ImGuiGraphNode_DrawEdge * drawedges, ImVec2 cursor_pos, float ppu) 172 | { 173 | int const count = (int)graph.edges.size(); 174 | 175 | if (drawedges) 176 | { 177 | constexpr int points_count = IMGUI_GRAPHNODE_DRAW_EDGE_PATH_COUNT; 178 | static_assert(points_count > 1, ""); 179 | 180 | for (int i = 0; i < count; ++i) 181 | { 182 | ImGuiGraphNode_Edge const & edge = graph.edges[i]; 183 | ImVec2 const textsize = ImGui::CalcTextSize(edge.label.c_str()); 184 | 185 | for (size_t j = 0; j < (edge.points.size() - 1); ++j) 186 | { 187 | ImVec2 const p1( 188 | cursor_pos.x + edge.points[j].x * ppu, 189 | cursor_pos.y + (graph.size.y - edge.points[j].y) * ppu 190 | ); 191 | ImVec2 const p2( 192 | cursor_pos.x + edge.points[j + 1].x * ppu, 193 | cursor_pos.y + (graph.size.y - edge.points[j + 1].y) * ppu 194 | ); 195 | ImVec2 const dir(p2.x - p1.x, p2.y - p1.y); 196 | ImVec2 left(-dir.y, dir.x); 197 | ImVec2 right(dir.y, -dir.x); 198 | float const magLeft = ImSqrt(left.x * left.x + left.y * left.y); 199 | float const magRight = ImSqrt(right.x * right.x + right.y * right.y); 200 | 201 | left.x /= magLeft; 202 | left.y /= magLeft; 203 | right.x /= magRight; 204 | right.y /= magRight; 205 | 206 | constexpr float k = 3.f; 207 | ImVec2 const a(p1.x + left.x * k, p1.y + left.y * k); 208 | ImVec2 const b(p1.x + right.x * k, p1.y + right.y * k); 209 | ImVec2 const c(p2.x + right.x * k, p2.y + right.y * k); 210 | ImVec2 const d(p2.x + left.x * k, p2.y + left.y * k); 211 | 212 | graph.edgesRectangle[edge.id].push_back({ a, b, c, d }); 213 | } 214 | for (int x = 0; x < points_count; ++x) 215 | { 216 | drawedges[i].path[x] = ImGuiGraphNode_BezierVec2(edge.points.data(), (int)edge.points.size(), x / float(points_count - 1)); 217 | drawedges[i].path[x].y = graph.size.y - drawedges[i].path[x].y; 218 | drawedges[i].path[x].x *= ppu; 219 | drawedges[i].path[x].y *= ppu; 220 | drawedges[i].path[x].x += cursor_pos.x; 221 | drawedges[i].path[x].y += cursor_pos.y; 222 | } 223 | drawedges[i].textpos.x = cursor_pos.x + edge.labelPos.x * ppu - textsize.x / 2.f; 224 | drawedges[i].textpos.y = cursor_pos.y + (graph.size.y - edge.labelPos.y) * ppu - textsize.y / 2.f; 225 | drawedges[i].text = edge.label.c_str(); 226 | drawedges[i].color = edge.color; 227 | 228 | ImVec2 const lastpoint = drawedges[i].path[points_count - 1]; 229 | float dirx = lastpoint.x - drawedges[i].path[points_count - 2].x; 230 | float diry = lastpoint.y - drawedges[i].path[points_count - 2].y; 231 | float const mag = ImSqrt(dirx * dirx + diry * diry); 232 | float const mul1 = ppu * 0.1f; 233 | float const mul2 = ppu * 0.0437f; 234 | 235 | dirx /= mag; 236 | diry /= mag; 237 | drawedges[i].arrow1.x = lastpoint.x - dirx * mul1 - diry * mul2; 238 | drawedges[i].arrow1.y = lastpoint.y - diry * mul1 + dirx * mul2; 239 | drawedges[i].arrow2.x = lastpoint.x - dirx * mul1 + diry * mul2; 240 | drawedges[i].arrow2.y = lastpoint.y - diry * mul1 - dirx * mul2; 241 | drawedges[i].arrow3 = lastpoint; 242 | } 243 | } 244 | return count; 245 | } 246 | 247 | void IMGUI_GRAPHNODE_NAMESPACE::EndNodeGraph() 248 | { 249 | auto & cache = g_ctx.graph_caches[g_ctx.lastid]; 250 | float const ppu = cache.pixel_per_unit; 251 | ImVec2 const cursor_pos = ImGui::GetCursorScreenPos(); 252 | ImDrawList * const drawlist = ImGui::GetWindowDrawList(); 253 | 254 | if (cache.graphid_current != cache.graphid_previous) 255 | { 256 | ImGuiGraphNodeRenderGraphLayout(cache); 257 | cache.graphid_previous = cache.graphid_current; 258 | cache.cursor_previous.x = cursor_pos.x - 1; // force recompute draw buffers 259 | } 260 | cache.graphid_current.clear(); 261 | agclose(g_ctx.gvgraph); 262 | g_ctx.gvgraph = nullptr; 263 | 264 | cache.cursor_current = cursor_pos; 265 | if (cache.cursor_current.x != cache.cursor_previous.x || cache.cursor_current.y != cache.cursor_previous.y) 266 | { 267 | cache.drawnodes.resize(ImGuiGraphNodeFillDrawNodeBuffer(cache.graph, nullptr, cursor_pos, ppu)); 268 | ImGuiGraphNodeFillDrawNodeBuffer(cache.graph, cache.drawnodes.data(), cursor_pos, ppu); 269 | cache.drawedges.resize(ImGuiGraphNodeFillDrawEdgeBuffer(cache.graph, nullptr, cursor_pos, ppu)); 270 | ImGuiGraphNodeFillDrawEdgeBuffer(cache.graph, cache.drawedges.data(), cursor_pos, ppu); 271 | cache.cursor_previous = cache.cursor_current; 272 | } 273 | 274 | for (auto const & node : cache.drawnodes) 275 | { 276 | drawlist->AddConvexPolyFilled(node.path, IMGUI_GRAPHNODE_DRAW_NODE_PATH_COUNT, node.fillcolor); 277 | drawlist->AddPolyline(node.path, IMGUI_GRAPHNODE_DRAW_NODE_PATH_COUNT, node.color, ImDrawFlags_Closed, 1.f); 278 | drawlist->AddText(node.textpos, node.color, node.text); 279 | } 280 | for (auto const & edge : cache.drawedges) 281 | { 282 | drawlist->AddText(edge.textpos, edge.color, edge.text); 283 | drawlist->AddPolyline(edge.path, IMGUI_GRAPHNODE_DRAW_EDGE_PATH_COUNT, edge.color, ImDrawFlags_None, 1.f); 284 | drawlist->AddTriangleFilled(edge.arrow1, edge.arrow2, edge.arrow3, edge.color); 285 | } 286 | ImGui::Dummy(ImVec2(cache.graph.size.x * ppu, cache.graph.size.y * ppu)); 287 | } 288 | -------------------------------------------------------------------------------- /imgui_graphnode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef IMGUI_GRAPHNODE_H_ 4 | #define IMGUI_GRAPHNODE_H_ 5 | 6 | #include "imgui.h" 7 | 8 | #ifndef IMGUI_GRAPHNODE_NAMESPACE 9 | #define IMGUI_GRAPHNODE_NAMESPACE ImGuiGraphNode 10 | #endif /* !IMGUI_GRAPHNODE_NAMESPACE */ 11 | 12 | typedef int ImGuiGraphNodeLayout; 13 | 14 | enum ImGuiGraphNodeLayout_ 15 | { 16 | ImGuiGraphNodeLayout_Circo, 17 | ImGuiGraphNodeLayout_Dot, 18 | ImGuiGraphNodeLayout_Fdp, 19 | ImGuiGraphNodeLayout_Neato, 20 | ImGuiGraphNodeLayout_Osage, 21 | ImGuiGraphNodeLayout_Sfdp, 22 | ImGuiGraphNodeLayout_Twopi 23 | }; 24 | 25 | namespace IMGUI_GRAPHNODE_NAMESPACE 26 | { 27 | IMGUI_API void CreateContext(); 28 | IMGUI_API void DestroyContext(); 29 | IMGUI_API bool BeginNodeGraph(char const * id, ImGuiGraphNodeLayout layout = ImGuiGraphNodeLayout_Dot, float pixel_per_unit = 100.f); 30 | IMGUI_API void NodeGraphAddNode(char const * id); 31 | IMGUI_API void NodeGraphAddNode(char const * id, ImVec4 const & color, ImVec4 const & fillcolor); 32 | IMGUI_API void NodeGraphAddEdge(char const * id, char const * node_id_a, char const * node_id_b); 33 | IMGUI_API void NodeGraphAddEdge(char const * id, char const * node_id_a, char const * node_id_b, ImVec4 const & color); 34 | IMGUI_API void EndNodeGraph(); 35 | } 36 | 37 | #endif /* !IMGUI_GRAPHNODE_H_ */ 38 | -------------------------------------------------------------------------------- /imgui_graphnode_demo.cpp: -------------------------------------------------------------------------------- 1 | #include "imgui_graphnode.h" 2 | #include "imgui_graphnode_internal.h" 3 | #include "imgui_graphnode_demo.h" 4 | 5 | template 6 | struct RBNode 7 | { 8 | T value; 9 | RBNode * parent; 10 | RBNode * left; 11 | RBNode * right; 12 | bool red; 13 | }; 14 | 15 | template > 16 | struct RBTree 17 | { 18 | RBTree() : 19 | root(nullptr), 20 | compare() 21 | { 22 | } 23 | 24 | ~RBTree() 25 | { 26 | clear(); 27 | } 28 | 29 | void ll(RBNode * x) 30 | { 31 | RBNode * p = x->parent; 32 | RBNode * g = p->parent; 33 | RBNode * gg = g->parent; 34 | RBNode * u = g->right; 35 | RBNode * pright = p->right; 36 | bool pcolor = p->red; 37 | 38 | IM_ASSERT(p != u); 39 | IM_ASSERT(pright != x); 40 | p->right = g; 41 | g->parent = p; 42 | g->left = pright; 43 | if (pright) pright->parent = g; 44 | p->parent = gg; 45 | p->red = g->red; 46 | g->red = pcolor; 47 | if (gg) 48 | { 49 | if (gg->left == g) gg->left = p; 50 | else if (gg->right == g) gg->right = p; 51 | else IM_ASSERT(false); 52 | } 53 | else 54 | { 55 | root = p; 56 | } 57 | if (!p->parent) 58 | { 59 | root = p; 60 | } 61 | } 62 | 63 | void lr(RBNode * x) 64 | { 65 | RBNode * p = x->parent; 66 | RBNode * g = p->parent; 67 | RBNode * xleft = x->left; 68 | 69 | p->right = xleft; 70 | if (xleft) 71 | { 72 | xleft->parent = p; 73 | } 74 | x->left = p; 75 | p->parent = x; 76 | x->parent = g; 77 | g->left = x; 78 | ll(p); 79 | } 80 | 81 | void rr(RBNode * x) 82 | { 83 | RBNode * p = x->parent; 84 | RBNode * g = p->parent; 85 | RBNode * gg = g->parent; 86 | RBNode * u = g->left; 87 | RBNode * pleft = p->left; 88 | bool pcolor = p->red; 89 | 90 | IM_ASSERT(p != u); 91 | IM_ASSERT(pleft != x); 92 | p->left = g; 93 | g->parent = p; 94 | g->right = pleft; 95 | if (pleft) pleft->parent = g; 96 | p->parent = gg; 97 | p->red = g->red; 98 | g->red = pcolor; 99 | if (gg) 100 | { 101 | if (gg->left == g) gg->left = p; 102 | else if (gg->right == g) gg->right = p; 103 | else IM_ASSERT(false); 104 | } 105 | else 106 | { 107 | root = p; 108 | } 109 | if (!p->parent) 110 | { 111 | root = p; 112 | } 113 | } 114 | 115 | void rl(RBNode * x) 116 | { 117 | RBNode * p = x->parent; 118 | RBNode * g = p->parent; 119 | RBNode * xright = x->right; 120 | 121 | p->left = xright; 122 | if (xright) 123 | { 124 | xright->parent = p; 125 | } 126 | x->right = p; 127 | p->parent = x; 128 | x->parent = g; 129 | g->right = x; 130 | rr(p); 131 | } 132 | 133 | void fix(RBNode * x) 134 | { 135 | RBNode * p = x->parent; 136 | RBNode * g = p ? p->parent : nullptr; 137 | RBNode * u = g ? (p == g->left ? g->right : g->left) : nullptr; 138 | 139 | if (x == root) 140 | { 141 | x->red = false; 142 | } 143 | else if (p && p->red) 144 | { 145 | if (u && u->red) 146 | { 147 | p->red = false; 148 | u->red = false; 149 | g->red = true; 150 | fix(g); 151 | } 152 | else 153 | { 154 | if (g->left == p && p->left == x) 155 | { 156 | ll(x); 157 | } 158 | else if (g->left == p && p->right == x) 159 | { 160 | lr(x); 161 | } 162 | else if (g->right == p && p->right == x) 163 | { 164 | rr(x); 165 | } 166 | else if (g->right == p && p->left == x) 167 | { 168 | rl(x); 169 | } 170 | else 171 | { 172 | IM_ASSERT(false); 173 | } 174 | if (x->parent) 175 | { 176 | fix(x->parent); 177 | } 178 | } 179 | } 180 | } 181 | 182 | void insert(RBNode * newnode, RBNode * parent, RBNode * leaf, bool parentcmp) 183 | { 184 | if (leaf) 185 | { 186 | parentcmp = compare(newnode->value, leaf->value); 187 | if (parentcmp) 188 | { 189 | insert(newnode, leaf, leaf->left, parentcmp); 190 | } 191 | else 192 | { 193 | insert(newnode, leaf, leaf->right, parentcmp); 194 | } 195 | } 196 | else 197 | { 198 | newnode->parent = parent; 199 | if (parentcmp) 200 | { 201 | parent->left = newnode; 202 | } 203 | else 204 | { 205 | parent->right = newnode; 206 | } 207 | } 208 | } 209 | 210 | void insert(T const & value) 211 | { 212 | RBNode * newnode = new RBNode(); 213 | 214 | newnode->value = value; 215 | newnode->parent = nullptr; 216 | newnode->left = nullptr; 217 | newnode->right = nullptr; 218 | newnode->red = true; 219 | if (root) 220 | { 221 | insert(newnode, nullptr, root, false); 222 | } 223 | else 224 | { 225 | root = newnode; 226 | } 227 | fix(newnode); 228 | } 229 | 230 | void clear(RBNode * node) 231 | { 232 | if (node) 233 | { 234 | clear(node->left); 235 | clear(node->right); 236 | delete node; 237 | } 238 | } 239 | 240 | void clear() 241 | { 242 | clear(root); 243 | root = nullptr; 244 | } 245 | 246 | RBNode * find(RBNode * node, T const & value) 247 | { 248 | if (!node) return nullptr; 249 | 250 | if (compare(value, node->value)) 251 | { 252 | return find(node->left, value); 253 | } 254 | else 255 | { 256 | if (compare(node->value, value)) 257 | { 258 | return find(node->right, value); 259 | } 260 | else 261 | { 262 | return node; 263 | } 264 | } 265 | } 266 | 267 | RBNode * find(T const & value) 268 | { 269 | return find(root, value); 270 | } 271 | 272 | RBNode * root; 273 | Compare compare; 274 | }; 275 | 276 | void draw_rbnode(RBNode * node, RBNode * found_node) 277 | { 278 | char * nodea = nullptr; 279 | char * nodeb = nullptr; 280 | char edge[64]; 281 | int len = 0; 282 | 283 | len = snprintf(nullptr, 0, "%s##%p", node->value.c_str(), (void *)node); 284 | nodea = (char *)alloca(len + 1); 285 | sprintf(nodea, "%s##%p", node->value.c_str(), (void *)node); 286 | ImGuiGraphNode::NodeGraphAddNode( 287 | nodea, 288 | node == found_node ? ImVec4(0.f, 1.f, 0.f, 1.f) : ImVec4(1.f, 1.f, 1.f, 1.f), 289 | node->red ? ImVec4(1.f, 0.f, 0.f, 0.7f) : ImVec4(0.f, 0.f, 0.f, 0.7f) 290 | ); 291 | for (auto * child : { node->left, node->right }) 292 | { 293 | if (child) 294 | { 295 | draw_rbnode(child, found_node); 296 | len = snprintf(nullptr, 0, "%s##%p", child->value.c_str(), (void *)child); 297 | nodeb = (char *)alloca(len + 1); 298 | sprintf(nodeb, "%s##%p", child->value.c_str(), (void *)child); 299 | sprintf(edge, "##%p->%p", (void *)node, (void *)child); 300 | ImGuiGraphNode::NodeGraphAddEdge(edge, nodea, nodeb); 301 | } 302 | } 303 | } 304 | 305 | void draw_rbtree(ImGuiGraphNodeLayout layout, float ppu) 306 | { 307 | static RBTree tree; 308 | static char bufadd[64] = { 0 }; 309 | static char bufsearch[64] = { 0 }; 310 | static RBNode * found_node = nullptr; 311 | static bool lazy_init = true; 312 | 313 | if (ImGui::Button("clear")) 314 | { 315 | found_node = nullptr; 316 | tree.clear(); 317 | } 318 | ImGui::SameLine(); 319 | if (ImGui::Button("lorem ipsum") || lazy_init) 320 | { 321 | lazy_init = false; 322 | tree.clear(); 323 | tree.insert("lorem"); 324 | tree.insert("ipsum"); 325 | tree.insert("dolor"); 326 | tree.insert("sit"); 327 | tree.insert("amet"); 328 | tree.insert("consectetur"); 329 | tree.insert("adipiscing"); 330 | tree.insert("elit"); 331 | tree.insert("sed"); 332 | tree.insert("do"); 333 | tree.insert("eiusmod"); 334 | tree.insert("tempor"); 335 | tree.insert("incididunt"); 336 | tree.insert("ut"); 337 | tree.insert("labore"); 338 | tree.insert("et"); 339 | tree.insert("dolore"); 340 | tree.insert("magna"); 341 | tree.insert("aliqua"); 342 | } 343 | if (ImGui::InputTextWithHint("add", "type text and press enter to add a new node", bufadd, sizeof(bufadd) - 1, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) 344 | { 345 | tree.insert(bufadd); 346 | *bufadd = '\0'; 347 | } 348 | if (ImGui::InputTextWithHint("search", "type text to search node", bufsearch, sizeof(bufsearch) - 1, ImGuiInputTextFlags_AutoSelectAll)) 349 | { 350 | found_node = tree.find(bufsearch); 351 | } 352 | if (*bufsearch && !found_node) 353 | { 354 | ImGui::SameLine(); 355 | ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "node '%s' not found", bufsearch); 356 | } 357 | if (ImGuiGraphNode::BeginNodeGraph("example3", layout, ppu)) 358 | { 359 | if (tree.root) 360 | { 361 | draw_rbnode(tree.root, found_node); 362 | } 363 | ImGuiGraphNode::EndNodeGraph(); 364 | } 365 | } 366 | 367 | void draw_example1(ImGuiGraphNodeLayout layout, float ppu) 368 | { 369 | if (ImGuiGraphNode::BeginNodeGraph("example1", layout, ppu)) 370 | { 371 | ImGuiGraphNode::NodeGraphAddNode("A"); 372 | ImGuiGraphNode::NodeGraphAddNode("B"); 373 | ImGuiGraphNode::NodeGraphAddNode("C"); 374 | ImGuiGraphNode::NodeGraphAddNode("D"); 375 | ImGuiGraphNode::NodeGraphAddEdge("a->b", "A", "B"); 376 | ImGuiGraphNode::NodeGraphAddEdge("b->c", "B", "C"); 377 | ImGuiGraphNode::NodeGraphAddEdge("c->d", "C", "D"); 378 | ImGuiGraphNode::NodeGraphAddEdge("d->a", "D", "A"); 379 | ImGuiGraphNode::EndNodeGraph(); 380 | } 381 | } 382 | 383 | void draw_example2(ImGuiGraphNodeLayout layout, float ppu) 384 | { 385 | if (ImGuiGraphNode::BeginNodeGraph("example2", layout, ppu)) 386 | { 387 | ImGuiGraphNode::NodeGraphAddNode("LR_0"); 388 | ImGuiGraphNode::NodeGraphAddNode("LR_1"); 389 | ImGuiGraphNode::NodeGraphAddNode("LR_2"); 390 | ImGuiGraphNode::NodeGraphAddNode("LR_3"); 391 | ImGuiGraphNode::NodeGraphAddNode("LR_4"); 392 | ImGuiGraphNode::NodeGraphAddNode("LR_5"); 393 | ImGuiGraphNode::NodeGraphAddNode("LR_6"); 394 | ImGuiGraphNode::NodeGraphAddNode("LR_7"); 395 | ImGuiGraphNode::NodeGraphAddNode("LR_8"); 396 | ImGuiGraphNode::NodeGraphAddEdge("SS(B)", "LR_0", "LR_2"); 397 | ImGuiGraphNode::NodeGraphAddEdge("SS(S)", "LR_0", "LR_1"); 398 | ImGuiGraphNode::NodeGraphAddEdge("S($end)", "LR_1", "LR_3"); 399 | ImGuiGraphNode::NodeGraphAddEdge("SS(b)", "LR_2", "LR_6"); 400 | ImGuiGraphNode::NodeGraphAddEdge("SS(a)", "LR_2", "LR_5"); 401 | ImGuiGraphNode::NodeGraphAddEdge("S(A)", "LR_2", "LR_4"); 402 | ImGuiGraphNode::NodeGraphAddEdge("S(b)", "LR_5", "LR_7"); 403 | ImGuiGraphNode::NodeGraphAddEdge("S(a)", "LR_5", "LR_5"); 404 | ImGuiGraphNode::NodeGraphAddEdge("S(b)", "LR_6", "LR_6"); 405 | ImGuiGraphNode::NodeGraphAddEdge("S(a)", "LR_6", "LR_5"); 406 | ImGuiGraphNode::NodeGraphAddEdge("S(b)", "LR_7", "LR_8"); 407 | ImGuiGraphNode::NodeGraphAddEdge("S(a)", "LR_7", "LR_5"); 408 | ImGuiGraphNode::NodeGraphAddEdge("S(b)", "LR_8", "LR_6"); 409 | ImGuiGraphNode::NodeGraphAddEdge("S(a)", "LR_8", "LR_5"); 410 | ImGuiGraphNode::EndNodeGraph(); 411 | } 412 | } 413 | 414 | void draw_clickable(ImGuiGraphNodeLayout layout, float ppu) 415 | { 416 | static bool nodeClickedA = false; 417 | static bool nodeHoveredA = false; 418 | static bool nodeClickedB = false; 419 | static bool nodeHoveredB = false; 420 | static bool edgeClicked = false; 421 | static bool edgeHovered = false; 422 | 423 | if (ImGuiGraphNode::BeginNodeGraph("clickable", layout, ppu)) 424 | { 425 | // First node 426 | ImVec4 color = ImVec4(1.f, 1.f, 1.f, 1.f); 427 | if (nodeClickedA) 428 | color = ImVec4(1.f, 0.f, 0.f, 1.f); 429 | if (nodeHoveredA) 430 | color = ImVec4(color.x * 0.7f, color.y * 0.7f, color.z * 0.7f, 1.f); 431 | ImGuiGraphNode::NodeGraphAddNode("A", color, ImVec4(0.f, 0.f, 0.f, 0.f)); 432 | if (ImGui::IsItemClicked()) 433 | nodeClickedA = !nodeClickedA; 434 | nodeHoveredA = ImGui::IsItemHovered(); 435 | 436 | // Second node 437 | color = ImVec4(1.f, 1.f, 1.f, 1.f); 438 | if (nodeClickedB) 439 | color = ImVec4(1.f, 0.f, 0.f, 1.f); 440 | if (nodeHoveredB) 441 | color = ImVec4(color.x * 0.7f, color.y * 0.7f, color.z * 0.7f, 1.f); 442 | ImGuiGraphNode::NodeGraphAddNode("B", color, ImVec4(0.f, 0.f, 0.f, 0.f)); 443 | if (ImGui::IsItemClicked()) 444 | nodeClickedB = !nodeClickedB; 445 | nodeHoveredB = ImGui::IsItemHovered(); 446 | 447 | // Edge 448 | color = ImVec4(1.f, 1.f, 1.f, 1.f); 449 | if (edgeClicked) 450 | color = ImVec4(1.f, 0.f, 0.f, 1.f); 451 | if (edgeHovered) 452 | color = ImVec4(color.x * 0.7f, color.y * 0.7f, color.z * 0.7f, 1.f); 453 | ImGuiGraphNode::NodeGraphAddEdge("a->b", "A", "B", color); 454 | if (ImGui::IsItemClicked()) 455 | edgeClicked = !edgeClicked; 456 | edgeHovered = ImGui::IsItemHovered(); 457 | 458 | ImGuiGraphNode::EndNodeGraph(); 459 | } 460 | } 461 | 462 | void IMGUI_GRAPHNODE_NAMESPACE::ShowGraphNodeDemoWindow(bool * p_open) 463 | { 464 | static ImGuiGraphNodeLayout layout = ImGuiGraphNodeLayout_Circo; 465 | static bool autoresize = true; 466 | static float ppu = 100.f; 467 | int flags = 0; 468 | 469 | if (autoresize) 470 | { 471 | flags |= ImGuiWindowFlags_AlwaysAutoResize; 472 | } 473 | if (ImGui::Begin("ImGuiGraphNode demo window", p_open, flags)) 474 | { 475 | auto const items_getter = [](void *, int idx, char const ** out_text) 476 | { 477 | *out_text = ImGuiGraphNode_GetEngineNameFromLayoutEnum((ImGuiGraphNodeLayout)idx); 478 | return true; 479 | }; 480 | ImGui::Checkbox("auto resize window", &autoresize); 481 | ImGui::Combo("layout", (int *)&layout, items_getter, nullptr, 7); 482 | ImGui::SliderFloat("pixel per unit", &ppu, 30.f, 200.f); 483 | if (ImGui::BeginTabBar("tabbar", ImGuiTabBarFlags_None)) 484 | { 485 | bool drawExample1 = ImGui::BeginTabItem("Example 1"); 486 | if (ImGui::IsItemClicked()) 487 | { 488 | layout = ImGuiGraphNodeLayout_Circo; 489 | } 490 | if (drawExample1) 491 | { 492 | draw_example1(layout, ppu); 493 | ImGui::EndTabItem(); 494 | } 495 | 496 | bool drawExample2 = ImGui::BeginTabItem("Example 2"); 497 | if (ImGui::IsItemClicked()) 498 | { 499 | layout = ImGuiGraphNodeLayout_Dot; 500 | } 501 | if (drawExample2) 502 | { 503 | draw_example2(layout, ppu); 504 | ImGui::EndTabItem(); 505 | } 506 | 507 | bool drawRBTree = ImGui::BeginTabItem("Red-black tree"); 508 | if (ImGui::IsItemClicked()) 509 | { 510 | layout = ImGuiGraphNodeLayout_Dot; 511 | } 512 | if (drawRBTree) 513 | { 514 | draw_rbtree(layout, ppu); 515 | ImGui::EndTabItem(); 516 | } 517 | 518 | bool drawClickable = ImGui::BeginTabItem("Clickable"); 519 | if (ImGui::IsItemClicked()) 520 | { 521 | layout = ImGuiGraphNodeLayout_Circo; 522 | } 523 | if (drawClickable) 524 | { 525 | draw_clickable(layout, ppu); 526 | ImGui::EndTabItem(); 527 | } 528 | 529 | ImGui::EndTabBar(); 530 | } 531 | } 532 | ImGui::End(); 533 | } 534 | -------------------------------------------------------------------------------- /imgui_graphnode_demo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef IMGUI_GRAPHNODE_DEMO_H_ 4 | #define IMGUI_GRAPHNODE_DEMO_H_ 5 | 6 | #include "imgui_graphnode.h" 7 | 8 | namespace IMGUI_GRAPHNODE_NAMESPACE 9 | { 10 | IMGUI_API void ShowGraphNodeDemoWindow(bool * p_open = nullptr); 11 | } 12 | 13 | #endif /* !IMGUI_GRAPHNODE_DEMO_H_ */ 14 | -------------------------------------------------------------------------------- /imgui_graphnode_internal.cpp: -------------------------------------------------------------------------------- 1 | #include "imgui_graphnode.h" 2 | #include "imgui_graphnode_internal.h" 3 | #include "imgui_internal.h" 4 | 5 | ImGuiGraphNodeContext g_ctx; 6 | 7 | ImGuiGraphNode_ShortString<32> ImGuiIDToString(char const * id) 8 | { 9 | ImGuiGraphNode_ShortString<32> str; 10 | 11 | sprintf(str.buf, "%u", ImGui::GetID(id)); 12 | return str; 13 | } 14 | 15 | ImGuiGraphNode_ShortString<16> ImVec4ColorToString(ImVec4 const & color) 16 | { 17 | ImGuiGraphNode_ShortString<16> str; 18 | ImU32 const rgba = ImGui::ColorConvertFloat4ToU32(color); 19 | 20 | sprintf(str.buf, "#%x", rgba); 21 | return str; 22 | } 23 | 24 | ImU32 ImGuiGraphNode_StringToU32Color(char const * color) 25 | { 26 | ImU32 rgba; 27 | 28 | sscanf(color, "#%x", &rgba); 29 | return rgba; 30 | } 31 | 32 | ImVec4 ImGuiGraphNode_StringToImVec4Color(char const * color) 33 | { 34 | return ImGui::ColorConvertU32ToFloat4(ImGuiGraphNode_StringToU32Color(color)); 35 | } 36 | 37 | char * ImGuiGraphNode_ReadToken(char ** stringp) 38 | { 39 | if (*stringp && **stringp == '"') 40 | { 41 | *stringp += 1; 42 | char * token = strsep(stringp, "\""); 43 | strsep(stringp, " "); 44 | return token; 45 | } 46 | return strsep(stringp, " "); 47 | } 48 | 49 | char * ImGuiGraphNode_ReadLine(char ** stringp) 50 | { 51 | return strsep(stringp, "\n"); 52 | } 53 | 54 | bool ImGuiGraphNode_ReadGraphFromMemory(ImGuiGraphNodeContextCache & cache, char const * data, size_t size) 55 | { 56 | char * copy = static_cast(alloca(sizeof(*copy) * size + 1)); 57 | char * line = nullptr; 58 | 59 | memcpy(copy, data, size); 60 | copy[size] = '\0'; 61 | while ((line = ImGuiGraphNode_ReadLine(©)) != nullptr) 62 | { 63 | char * token = ImGuiGraphNode_ReadToken(&line); 64 | 65 | if (strcmp(token, "graph") == 0) 66 | { 67 | cache.graph.scale = atof(ImGuiGraphNode_ReadToken(&line)); 68 | cache.graph.size.x = atof(ImGuiGraphNode_ReadToken(&line)); 69 | cache.graph.size.y = atof(ImGuiGraphNode_ReadToken(&line)); 70 | } 71 | else if (strcmp(token, "node") == 0) 72 | { 73 | ImGuiGraphNode_Node node; 74 | 75 | node.name = ImGuiGraphNode_ReadToken(&line); 76 | node.pos.x = atof(ImGuiGraphNode_ReadToken(&line)); 77 | node.pos.y = atof(ImGuiGraphNode_ReadToken(&line)); 78 | node.size.x = atof(ImGuiGraphNode_ReadToken(&line)); 79 | node.size.y = atof(ImGuiGraphNode_ReadToken(&line)); 80 | node.label = ImGuiGraphNode_ReadToken(&line); 81 | ImGuiGraphNode_ReadToken(&line); // style 82 | ImGuiGraphNode_ReadToken(&line); // shape 83 | node.color = ImGuiGraphNode_StringToU32Color(ImGuiGraphNode_ReadToken(&line)); 84 | node.fillcolor = ImGuiGraphNode_StringToU32Color(ImGuiGraphNode_ReadToken(&line)); 85 | cache.graph.nodes.push_back(node); 86 | } 87 | else if (strcmp(token, "edge") == 0) 88 | { 89 | ImGuiGraphNode_Edge edge; 90 | 91 | edge.tail = ImGuiGraphNode_ReadToken(&line); 92 | edge.head = ImGuiGraphNode_ReadToken(&line); 93 | int const n = atoi(ImGuiGraphNode_ReadToken(&line)); 94 | edge.points.resize(n); 95 | for (int i = 0; i < n; ++i) 96 | { 97 | edge.points[i].x = atof(ImGuiGraphNode_ReadToken(&line)); 98 | edge.points[i].y = atof(ImGuiGraphNode_ReadToken(&line)); 99 | } 100 | 101 | char const * s1 = ImGuiGraphNode_ReadToken(&line); 102 | char const * s2 = ImGuiGraphNode_ReadToken(&line); 103 | char const * s3 = ImGuiGraphNode_ReadToken(&line); 104 | char const * s4 = ImGuiGraphNode_ReadToken(&line); (void)s4; // style 105 | char const * s5 = ImGuiGraphNode_ReadToken(&line); 106 | char const * identifier = nullptr; 107 | 108 | if (s3) 109 | { 110 | edge.label = s1; 111 | edge.labelPos.x = atof(s2); 112 | edge.labelPos.y = atof(s3); 113 | identifier = s5 + 1; 114 | } 115 | else 116 | { 117 | identifier = s2 + 1; 118 | } 119 | 120 | // Edge ImGuiID is stored in the color property. 121 | // It is used to access edge info, as graphviz doesn't serialize 122 | // the edge's identifier. The actual color is then retrieve from 123 | // the context cache. 124 | edge.id = strtoul(identifier, nullptr, 16); 125 | auto const it = cache.edgeIdToInfo.find(edge.id); 126 | IM_ASSERT(it != cache.edgeIdToInfo.end()); 127 | edge.color = it->second.color; 128 | 129 | cache.graph.edges.push_back(edge); 130 | } 131 | else if (strcmp(token, "stop") == 0) 132 | { 133 | return true; 134 | } 135 | else 136 | { 137 | IM_ASSERT(false); 138 | } 139 | } 140 | return true; 141 | } 142 | 143 | char const * ImGuiGraphNode_GetEngineNameFromLayoutEnum(ImGuiGraphNodeLayout layout) 144 | { 145 | switch (layout) 146 | { 147 | case ImGuiGraphNodeLayout_Circo: return "circo"; 148 | case ImGuiGraphNodeLayout_Dot: return "dot"; 149 | case ImGuiGraphNodeLayout_Fdp: return "fdp"; 150 | case ImGuiGraphNodeLayout_Neato: return "neato"; 151 | case ImGuiGraphNodeLayout_Osage: return "osage"; 152 | case ImGuiGraphNodeLayout_Sfdp: return "sfdp"; 153 | case ImGuiGraphNodeLayout_Twopi: return "twopi"; 154 | default: 155 | IM_ASSERT(false); 156 | return ""; 157 | } 158 | } 159 | 160 | float ImGuiGraphNode_BSplineVec2ComputeK(ImVec2 const * p, float const * t, int i, int k, float x) 161 | { 162 | auto const f = [](float const * t, int i, int k, float x) -> float 163 | { 164 | if (t[i + k] == t[i]) 165 | return 0.f; 166 | return (x - t[i]) / (t[i + k] - t[i]); 167 | }; 168 | auto g = ImGuiGraphNode_BSplineVec2ComputeK; 169 | if (k == 0) 170 | return (x < t[i] || x >= t[i + 1]) ? 0.f : 1.f; 171 | return f(t, i, k, x) * g(p, t, i, k - 1, x) + (1.f - f(t, i + 1, k, x)) * g(p, t, i + 1, k - 1, x); 172 | } 173 | 174 | ImVec2 ImGuiGraphNode_BSplineVec2(ImVec2 const * p, int m, int n, float x) 175 | { 176 | if (n > (m - 2)) n = m - 2; 177 | int const knotscount = m + n + 1; 178 | float * const knots = (float *)alloca(knotscount * sizeof(*knots)); 179 | int i = 0; 180 | 181 | for (; i <= n; ++i) knots[i] = 0.f; 182 | for (; i < m; ++i) knots[i] = i / (float)(m + n); 183 | for (; i < knotscount; ++i) knots[i] = 1.f; 184 | 185 | ImVec2 result(0.f, 0.f); 186 | 187 | for (i = 0; i < m; ++i) 188 | { 189 | float const k = ImGuiGraphNode_BSplineVec2ComputeK(p, knots, i, n, x); 190 | 191 | result.x += p[i].x * k; 192 | result.y += p[i].y * k; 193 | } 194 | return result; 195 | } 196 | 197 | ImVec2 ImGuiGraphNode_BSplineVec2(ImVec2 const * points, int count, float x) 198 | { 199 | return ImGuiGraphNode_BSplineVec2(points, count, 3, ImClamp(x, 0.f, 0.9999f)); 200 | } 201 | 202 | #define IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_1(_line, _cell) \ 203 | ImGuiGraphNode_BinomialCoefficient(_line, _cell), \ 204 | 205 | #define IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_2(_line, _cell) \ 206 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_1(_line, _cell) \ 207 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_1(_line, _cell + 1) 208 | 209 | #define IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_4(_line, _cell) \ 210 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_2(_line, _cell) \ 211 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_2(_line, _cell + 2) 212 | 213 | #define IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_8(_line, _cell) \ 214 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_4(_line, _cell) \ 215 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_4(_line, _cell + 4) 216 | 217 | #define IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_16(_line, _cell) \ 218 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_8(_line, _cell) \ 219 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_8(_line, _cell + 8) 220 | 221 | #define IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_1(_line) \ 222 | { IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_CELL_16(_line, 0) }, 223 | 224 | #define IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_2(_line) \ 225 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_1(_line) \ 226 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_1(_line + 1) 227 | 228 | #define IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_4(_line) \ 229 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_2(_line) \ 230 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_2(_line + 2) 231 | 232 | #define IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_8(_line) \ 233 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_4(_line) \ 234 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_4(_line + 4) 235 | 236 | #define IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_16(_line) \ 237 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_8(_line) \ 238 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_8(_line + 8) 239 | 240 | constexpr int ImGuiGraphNode_BinomialCoefficient(int n, int k) 241 | { 242 | return (n == 0 || k == 0 || n == k) ? 1 : ImGuiGraphNode_BinomialCoefficient(n - 1, k - 1) + ImGuiGraphNode_BinomialCoefficient(n - 1, k); 243 | } 244 | 245 | constexpr int ImGuiGraphNode_BinomialCoefficient_Table[16][16] = 246 | { 247 | IMGUIGRAPHNODE_BINOMIALCOEFFICIENT_TABLE_LINE_16(0) 248 | }; 249 | 250 | int ImGuiGraphNode_BinomialCoefficientTable(int n, int k) 251 | { 252 | if (n >= 0 && n < 16 && k >= 0 && k < 16) 253 | { 254 | return ImGuiGraphNode_BinomialCoefficient_Table[n][k]; 255 | } 256 | else 257 | { 258 | return ImGuiGraphNode_BinomialCoefficient(n, k); 259 | } 260 | } 261 | 262 | ImVec2 ImGuiGraphNode_BezierVec2(ImVec2 const * points, int count, float x) 263 | { 264 | ImVec2 result(0.f, 0.f); 265 | 266 | for (int i = 0; i < count; ++i) 267 | { 268 | float const k = ImGuiGraphNode_BinomialCoefficientTable(count - 1, i) * ImPow(1 - x, count - i - 1) * ImPow(x, i); 269 | 270 | result.x += points[i].x * k; 271 | result.y += points[i].y * k; 272 | } 273 | return result; 274 | } 275 | 276 | void ImGuiGraphNodeRenderGraphLayout(ImGuiGraphNodeContextCache & cache) 277 | { 278 | char * data = nullptr; 279 | unsigned int size = 0; 280 | char const * const engine = ImGuiGraphNode_GetEngineNameFromLayoutEnum(cache.layout); 281 | int ok = 0; 282 | 283 | cache.graph = ImGuiGraphNode_Graph(); 284 | IM_ASSERT(g_ctx.gvcontext != nullptr); 285 | IM_ASSERT(g_ctx.gvgraph != nullptr); 286 | agattr(g_ctx.gvgraph, AGEDGE, (char *)"dir", "none"); 287 | ok = gvLayout(g_ctx.gvcontext, g_ctx.gvgraph, engine); 288 | IM_ASSERT(ok == 0); 289 | ok = gvRenderData(g_ctx.gvcontext, g_ctx.gvgraph, "plain", &data, &size); 290 | IM_ASSERT(ok == 0); 291 | ImGuiGraphNode_ReadGraphFromMemory(cache, data, size); 292 | gvFreeRenderData(data); 293 | gvFreeLayout(g_ctx.gvcontext, g_ctx.gvgraph); 294 | } 295 | -------------------------------------------------------------------------------- /imgui_graphnode_internal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef IMGUI_GRAPHNODE_INTERNAL_H_ 4 | #define IMGUI_GRAPHNODE_INTERNAL_H_ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | extern "C" 11 | { 12 | #include 13 | } 14 | 15 | #include "imgui_graphnode.h" 16 | #include "imgui_internal.h" 17 | 18 | #define IMGUI_GRAPHNODE_CREATE_LABEL_ALLOCA(_name, _label) \ 19 | char * _name; \ 20 | do { \ 21 | char const * const end = ImGui::FindRenderedTextEnd(_label); \ 22 | size_t const size = end - _label; \ 23 | _name = (char *)alloca(size + 1); \ 24 | memcpy(_name, _label, size); \ 25 | _name[size] = '\0'; \ 26 | } while(0) 27 | 28 | #define IMGUI_GRAPHNODE_DRAW_NODE_PATH_COUNT 32 29 | #define IMGUI_GRAPHNODE_DRAW_EDGE_PATH_COUNT 64 30 | 31 | struct ImGuiGraphNode_Node 32 | { 33 | std::string name; 34 | std::string label; 35 | ImVec2 pos; 36 | ImVec2 size; 37 | ImU32 color; 38 | ImU32 fillcolor; 39 | }; 40 | 41 | struct ImGuiGraphNode_Edge 42 | { 43 | std::vector points; 44 | std::string tail; 45 | std::string head; 46 | std::string label; 47 | ImVec2 labelPos; 48 | ImU32 color; 49 | ImGuiID id; 50 | }; 51 | 52 | struct ImGuiGraphNode_EdgeInfo 53 | { 54 | ImU32 color; 55 | }; 56 | 57 | struct ImGuiGraphNode_EdgeRectangle 58 | { 59 | ImVec2 a, b, c, d; 60 | }; 61 | 62 | struct ImGuiGraphNode_Graph 63 | { 64 | std::map nodesBB; 65 | std::map> edgesRectangle; 66 | std::vector nodes; 67 | std::vector edges; 68 | ImVec2 size; 69 | float scale; 70 | }; 71 | 72 | struct ImGuiGraphNode_DrawNode 73 | { 74 | ImVec2 path[IMGUI_GRAPHNODE_DRAW_NODE_PATH_COUNT]; 75 | ImVec2 textpos; 76 | char const * text; 77 | ImU32 color; 78 | ImU32 fillcolor; 79 | }; 80 | 81 | struct ImGuiGraphNode_DrawEdge 82 | { 83 | ImVec2 path[IMGUI_GRAPHNODE_DRAW_EDGE_PATH_COUNT]; 84 | ImVec2 arrow1; 85 | ImVec2 arrow2; 86 | ImVec2 arrow3; 87 | ImVec2 textpos; 88 | char const * text; 89 | ImU32 color; 90 | }; 91 | 92 | struct ImGuiGraphNodeContextCache 93 | { 94 | std::map edgeIdToInfo; 95 | ImGuiGraphNode_Graph graph; 96 | ImGuiGraphNodeLayout layout = ImGuiGraphNodeLayout_Dot; 97 | float pixel_per_unit = 100.f; 98 | std::vector drawnodes; 99 | std::vector drawedges; 100 | ImVec2 cursor_previous; 101 | ImVec2 cursor_current; 102 | std::string graphid_previous; 103 | std::string graphid_current; 104 | }; 105 | 106 | struct ImGuiGraphNodeContext 107 | { 108 | GVC_t * gvcontext = nullptr; 109 | graph_t * gvgraph = nullptr; 110 | ImGuiID lastid = 0; 111 | std::map graph_caches; 112 | }; 113 | 114 | extern ImGuiGraphNodeContext g_ctx; 115 | 116 | template 117 | class ImGuiGraphNode_ShortString 118 | { 119 | public: 120 | friend ImGuiGraphNode_ShortString<32> ImGuiIDToString(char const * id); 121 | friend ImGuiGraphNode_ShortString<16> ImVec4ColorToString(ImVec4 const & color); 122 | 123 | operator char *() { return buf; } 124 | operator char const *() const { return buf; } 125 | 126 | private: 127 | char buf[N]; 128 | }; 129 | 130 | IMGUI_API ImGuiGraphNode_ShortString<32> ImGuiIDToString(char const * id); 131 | IMGUI_API ImGuiGraphNode_ShortString<16> ImVec4ColorToString(ImVec4 const & color); 132 | IMGUI_API ImU32 ImGuiGraphNode_StringToU32Color(char const * color); 133 | IMGUI_API ImVec4 ImGuiGraphNode_StringToImVec4Color(char const * color); 134 | IMGUI_API char * ImGuiGraphNode_ReadToken(char ** stringp); 135 | IMGUI_API char * ImGuiGraphNode_ReadLine(char ** stringp); 136 | IMGUI_API bool ImGuiGraphNode_ReadGraphFromMemory(ImGuiGraphNodeContextCache & cache, char const * data, size_t size); 137 | IMGUI_API char const * ImGuiGraphNode_GetEngineNameFromLayoutEnum(ImGuiGraphNodeLayout layout); 138 | IMGUI_API ImVec2 ImGuiGraphNode_BezierVec2(ImVec2 const * points, int count, float x); 139 | IMGUI_API ImVec2 ImGuiGraphNode_BSplineVec2(ImVec2 const * points, int count, float x); 140 | IMGUI_API void ImGuiGraphNodeRenderGraphLayout(ImGuiGraphNodeContextCache & cache); 141 | 142 | #endif /* !IMGUI_GRAPHNODE_INTERNAL_H_ */ 143 | --------------------------------------------------------------------------------