├── .gitattributes ├── README.md ├── nodesoup.hpp ├── LICENSE ├── fruchterman_reingold.hpp ├── nodesoup.cpp ├── kamada_kawai.hpp ├── fruchterman_reingold.cpp ├── kamada_kawai.cpp └── ImNodeSoup.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImNodeSoup 2 | 3 | This is a version of https://github.com/olvb/nodesoup for dear imgui. 4 | 5 | I have adapted nodesoup from Olivier Birot to dear imgui. I just made the changes to allow for an interactive frame rate. All credit goes to him. 6 | 7 | 8 | ![ImNodeSoup](https://user-images.githubusercontent.com/8093144/232327587-5d43ac67-ca95-402f-a280-a08d4056f22b.gif) 9 | 10 | 11 | 12 | Update: added option to move vertices. 13 | 14 | ![ImNodeSoup3](https://user-images.githubusercontent.com/8093144/233800613-a556442f-d800-4789-be84-e0e9959515ce.gif) 15 | 16 | 17 | 18 | 19 | If you want to use it just copy the files to a dear imgui project and adjust the include path. 20 | Add the declaration ``` extern void ShowNodeSoup();``` and call it. 21 | 22 | There are five example graphs that you can choose with a combo box and show them with the Fruchterman-Reingold or Kamada Kawai algorithms. 23 | You can use the mouse wheel for zoom in or zoom out and pan clickin left button. 24 | -------------------------------------------------------------------------------- /nodesoup.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #define IMGUI_DEFINE_MATH_OPERATORS 5 | #include "imgui_internal.h" 6 | 7 | 8 | 9 | struct NsPosition 10 | { 11 | ImVec2 m_Pos; 12 | float m_Radius; 13 | bool m_Fixed; 14 | }; 15 | 16 | 17 | constexpr float kInvalidPos=-1000000.0f; 18 | 19 | 20 | inline double norm(const ImVec2& aImVec2) noexcept 21 | { 22 | return sqrt(aImVec2.x * aImVec2.x + aImVec2.y * aImVec2.y); 23 | } 24 | 25 | inline float sq_norm(const ImVec2& aImVec2) noexcept 26 | { 27 | return aImVec2.x*aImVec2.x + aImVec2.y*aImVec2.y; 28 | } 29 | 30 | 31 | 32 | 33 | namespace nodesoup 34 | { 35 | 36 | // Simple adjaceny list graph structure 37 | using vertex_id_t = std::size_t; 38 | using adj_list_t = std::vector>; 39 | 40 | 41 | // Assigns diameters to vertices based on their degree 42 | void SetRadiuses(const adj_list_t& aAdjList,std::vector& aPositions,float aMinRadius=4.0f,float aK=300.0f); 43 | 44 | // Distribute vertices equally on a 1.0 radius circle (aCircleMode==true) or randomly in unit square (aCircleMode==false) 45 | void SetInitPositions(bool aCircleMode,std::vector& aPositions); 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /fruchterman_reingold.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "nodesoup.hpp" 3 | #include 4 | 5 | namespace nodesoup 6 | { 7 | 8 | 9 | class FruchtermanReingold 10 | { 11 | public: 12 | FruchtermanReingold(const adj_list_t& aAdjList,double aK=15.0); 13 | 14 | void Start(bool aStartCircle=true); 15 | void Step(int aStepSize,int aMaxStep,std::vector& aPositions); 16 | 17 | int GetCurrIter() const noexcept; 18 | int GetMaxIters() const noexcept; 19 | 20 | double GetK() const noexcept; 21 | void SetK(double aK) noexcept; 22 | 23 | double GetEnergy() const noexcept; 24 | 25 | void MovePos(vertex_id_t aVertexId,const ImVec2& aDisp,bool aRecalculate); 26 | 27 | private: 28 | 29 | const adj_list_t& m_AdjList; 30 | double m_K; 31 | double m_KSquared; 32 | double m_Temp; 33 | std::vector m_Mvmts; 34 | 35 | bool m_StartCircle; 36 | int m_CurrIter,m_MaxIter; 37 | 38 | std::vector m_Positions; 39 | 40 | void DoStep(); 41 | void SetInitPositions(); 42 | }; 43 | 44 | 45 | 46 | 47 | inline int FruchtermanReingold::GetCurrIter() const noexcept 48 | { 49 | return m_CurrIter; 50 | } 51 | 52 | inline int FruchtermanReingold::GetMaxIters() const noexcept 53 | { 54 | return m_MaxIter; 55 | } 56 | 57 | inline double FruchtermanReingold::GetK() const noexcept 58 | { 59 | return m_K; 60 | } 61 | 62 | inline double FruchtermanReingold::GetEnergy() const noexcept 63 | { 64 | return m_Temp; 65 | } 66 | 67 | 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /nodesoup.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "nodesoup.hpp" 3 | #include 4 | #include 5 | 6 | 7 | 8 | 9 | 10 | namespace nodesoup 11 | { 12 | 13 | 14 | 15 | 16 | void SetRadiuses(const adj_list_t& aAdjList,std::vector& aPositions,float aMinRadius,float aK) 17 | { 18 | assert(aPositions.size() == aAdjList.size()); 19 | 20 | for (vertex_id_t v_id=0; v_id& aPositions) 32 | { 33 | constexpr float kPIf=3.14159265358979323846f; 34 | 35 | if(aCircleMode) 36 | { 37 | float angle = 2.0f*kPIf/aPositions.size(); 38 | for (vertex_id_t v_id=0; v_id(rand())/static_cast(RAND_MAX)); 50 | float v2=(static_cast(rand())/static_cast(RAND_MAX)); 51 | 52 | aPositions[v_id].m_Pos.x=v1; 53 | aPositions[v_id].m_Pos.y=v2; 54 | aPositions[v_id].m_Fixed=false; 55 | } 56 | } 57 | } 58 | 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /kamada_kawai.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "nodesoup.hpp" 3 | #include 4 | #include 5 | 6 | namespace nodesoup 7 | { 8 | // https://gist.github.com/terakun/b7eff90c889c1485898ec9256ca9f91d 9 | // https://graphsharp.codeplex.com/SourceControl/latest#Source/Graph#/Algorithms/Layout/Simple/FDP/KKLayoutAlgorithm.cs 10 | 11 | 12 | 13 | class KamadaKawai 14 | { 15 | public: 16 | 17 | KamadaKawai(const adj_list_t& aAdjList,double aK=300.0,double aEnergyThreshold=1e-2); 18 | 19 | void Start(bool aStartCircle=true); 20 | void Step(float aWidth,float aHeight,std::vector& aPositions); 21 | 22 | void MovePos(vertex_id_t aVertexId,const ImVec2& aDisp,bool aRecalculate); 23 | 24 | 25 | double GetEnergy() const noexcept; 26 | 27 | private: 28 | 29 | struct Spring 30 | { 31 | double m_Length; 32 | double m_Strength; 33 | }; 34 | 35 | 36 | const adj_list_t& m_AdjList; 37 | const double m_EnergyThreshold; 38 | double m_K; 39 | unsigned int m_SteadyEnergyCount; 40 | double m_MaxVertexEnergy; 41 | vertex_id_t m_VertexId; 42 | 43 | std::vector> m_Springs; 44 | mutable std::vector m_Positions; 45 | 46 | // p m 47 | std::tuple FindMaxVertexEnergy() const noexcept; 48 | // delta m 49 | double ComputeVertexEnergy(vertex_id_t aVertexId) const noexcept; 50 | ImVec2 ComputeNextVertexPosition(vertex_id_t aVertexId) const noexcept; 51 | 52 | void RecalculateSprings(vertex_id_t aVertexId); 53 | 54 | void SetInitPositions(bool aStartCircle); 55 | 56 | void CenterAndScale(float aWidth,float aHeight,std::vector& aPositions) const noexcept; 57 | mutable float m_Scale; 58 | mutable ImVec2 m_Offset; 59 | }; 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /fruchterman_reingold.cpp: -------------------------------------------------------------------------------- 1 | #include "fruchterman_reingold.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace nodesoup 7 | { 8 | 9 | 10 | FruchtermanReingold::FruchtermanReingold(const adj_list_t& aAdjList,double aK) 11 | : m_AdjList(aAdjList) 12 | , m_K(aK) 13 | , m_KSquared(aK* aK) 14 | , m_Temp(10 * sqrt(aAdjList.size())) 15 | , m_Mvmts(m_AdjList.size()) 16 | , m_StartCircle(true) 17 | , m_CurrIter(0), m_MaxIter(0) 18 | { 19 | } 20 | 21 | 22 | 23 | 24 | void FruchtermanReingold::Start(bool aStartCircle) 25 | { 26 | m_Mvmts.resize(m_AdjList.size()); 27 | m_Positions.resize(m_AdjList.size()); 28 | 29 | m_CurrIter=0; 30 | m_MaxIter=0; 31 | 32 | m_StartCircle=aStartCircle; 33 | } 34 | 35 | 36 | 37 | void FruchtermanReingold::DoStep() 38 | { 39 | ImVec2 zero={0.0f,0.0f}; 40 | fill(m_Mvmts.begin(),m_Mvmts.end(), zero); 41 | 42 | // Repulsion force between vertice pairs 43 | for(vertex_id_t v_id=0; v_id 1000.0: not worth computing 57 | if(distance > 1000.0) 58 | { 59 | continue; 60 | } 61 | 62 | double repulsion = m_KSquared / distance; 63 | 64 | m_Mvmts[v_id] += delta/distance * repulsion; 65 | m_Mvmts[other_id] -= delta/distance * repulsion; 66 | } 67 | 68 | // Attraction force between edges 69 | for(vertex_id_t adj_id:m_AdjList[v_id]) 70 | { 71 | if(adj_id>v_id) 72 | { 73 | continue; 74 | } 75 | 76 | ImVec2 delta = m_Positions[v_id].m_Pos-m_Positions[adj_id].m_Pos; 77 | float distance=norm(delta); 78 | if(distance==0.0f) 79 | { 80 | continue; 81 | } 82 | 83 | double attraction = distance*distance / m_K; 84 | 85 | m_Mvmts[v_id] -= delta / distance * attraction; 86 | m_Mvmts[adj_id] += delta / distance * attraction; 87 | } 88 | } 89 | 90 | // Max movement capped by current temperature 91 | for(vertex_id_t v_id=0; v_id0.1) 110 | { 111 | m_Temp*=0.85; 112 | } 113 | else 114 | { 115 | m_Temp=0.1; 116 | } 117 | } 118 | 119 | 120 | 121 | 122 | void FruchtermanReingold::Step(int aStepSize,int aMaxStep,std::vector& aPositions) 123 | { 124 | if(m_CurrIter>=aMaxStep && aMaxStep>0) 125 | { 126 | for(int k=0;k=m_MaxIter) 147 | { 148 | m_CurrIter=1; 149 | m_MaxIter++; 150 | 151 | for(int k=0;k0.0f) 196 | { 197 | m_Positions[aVertexId].m_Fixed=true; 198 | } 199 | } 200 | 201 | 202 | } 203 | -------------------------------------------------------------------------------- /kamada_kawai.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | #include "kamada_kawai.hpp" 8 | 9 | namespace nodesoup 10 | { 11 | 12 | 13 | 14 | static std::vector> floyd_warshall_(const adj_list_t& aAdjList) 15 | { 16 | // build adjacency matrix (infinity = no edge, 1 = edge) 17 | constexpr unsigned int infinity = std::numeric_limits::max() / 2; 18 | std::vector> distances(aAdjList.size(), std::vector(aAdjList.size(), infinity)); 19 | 20 | for(vertex_id_t v_id=0; v_idv_id) 26 | { 27 | distances[v_id][adj_id]=1; 28 | distances[adj_id][v_id]=1; 29 | } 30 | } 31 | } 32 | 33 | // floyd warshall itself, find length of shortest path for each pair of vertices 34 | for(vertex_id_t k=0; k> distances=floyd_warshall_(m_AdjList); 72 | 73 | // find biggest distance 74 | size_t biggest_distance = 0; 75 | for(vertex_id_t v_id=0; v_id biggest_distance) 80 | { 81 | biggest_distance = distances[v_id][other_id]; 82 | } 83 | } 84 | } 85 | 86 | // Ideal length for all edges. we don't really care, the layout is going to be scaled. 87 | // Let's chose 1.0 as the initial positions will be on a 1.0 radius circle, so we're 88 | // on the same order of magnitude 89 | double length=1.0/biggest_distance; 90 | 91 | // init springs lengths and strengths matrices 92 | m_Springs.clear(); 93 | m_Springs.reserve(m_AdjList.size()); 94 | for(vertex_id_t v_id=0; v_id v_springs; 97 | v_springs.reserve(m_AdjList.size()); 98 | 99 | for(vertex_id_t other_id=0; other_id(res); 122 | m_VertexId=std::get(res); 123 | } 124 | 125 | 126 | 127 | 128 | 129 | 130 | #define MAX_VERTEX_ITERS_COUNT 10 131 | #define MAX_STEADY_ENERGY_ITERS_COUNT 50 132 | 133 | 134 | // Reduce the energy of the next vertex with most energy until all the vertices have 135 | // a energy below energy_threshold 136 | void KamadaKawai::Step(float aWidth,float aHeight,std::vector& aPositions) 137 | { 138 | if(m_MaxVertexEnergy>m_EnergyThreshold && m_SteadyEnergyCountm_EnergyThreshold && vertex_count(res); 153 | m_VertexId=std::get(res); 154 | 155 | if(std::abs(m_MaxVertexEnergy-max_vertex_energy_prev) < 1e-20) 156 | { 157 | m_SteadyEnergyCount++; 158 | } 159 | else 160 | { 161 | m_SteadyEnergyCount=0; 162 | } 163 | } 164 | 165 | CenterAndScale(aWidth,aHeight,aPositions); 166 | } 167 | 168 | 169 | 170 | 171 | 172 | // Find @p max_energy_v_id with the most potential energy and @return its energy 173 | // https://gist.github.com/terakun/b7eff90c889c1485898ec9256ca9f91d 174 | std::tuple KamadaKawai::FindMaxVertexEnergy() const noexcept 175 | { 176 | double max_energy=-1.0; 177 | vertex_id_t max_energy_v_id=0; 178 | 179 | for(vertex_id_t v_id=0; v_idmax_energy) 183 | { 184 | max_energy_v_id=v_id; 185 | max_energy=energy; 186 | } 187 | } 188 | 189 | return {max_energy,max_energy_v_id}; 190 | } 191 | 192 | 193 | 194 | 195 | // @return the potential energies of springs between @p v_id and all other vertices 196 | double KamadaKawai::ComputeVertexEnergy(vertex_id_t aVertexId) const noexcept 197 | { 198 | assert(aVertexId((xy_energy * y_energy - yy_energy * x_energy) / denom); 271 | position.y += static_cast((xy_energy * x_energy - xx_energy * y_energy) / denom); 272 | 273 | return position; 274 | } 275 | 276 | 277 | 278 | 279 | void KamadaKawai::CenterAndScale(float aWidth, float aHeight,std::vector& aPositions) const noexcept 280 | { 281 | assert(m_Positions.size()==aPositions.size()); 282 | 283 | // find current dimensions 284 | float x_min = std::numeric_limits::max(); 285 | float x_max = std::numeric_limits::lowest(); 286 | float y_min = std::numeric_limits::max(); 287 | float y_max = std::numeric_limits::lowest(); 288 | 289 | for(vertex_id_t v_id=0; v_idx_max) 296 | { 297 | x_max=m_Positions[v_id].m_Pos.x; 298 | } 299 | 300 | if(m_Positions[v_id].m_Pos.yy_max) 305 | { 306 | y_max=m_Positions[v_id].m_Pos.y; 307 | } 308 | } 309 | 310 | float cur_width =x_max-x_min; 311 | float cur_height=y_max-y_min; 312 | 313 | // compute scale factor (0.9: keep some margin) 314 | float x_scale = aWidth/cur_width; 315 | float y_scale = aHeight/cur_height; 316 | m_Scale = 0.9f * (x_scale0.0f) 357 | { 358 | m_Positions[aVertexId].m_Fixed=true; 359 | } 360 | } 361 | 362 | 363 | 364 | 365 | void KamadaKawai::RecalculateSprings(vertex_id_t aVertexId) 366 | { 367 | std::vector> distances=floyd_warshall_(m_AdjList); 368 | 369 | // find biggest distance 370 | size_t biggest_distance = 0; 371 | for(vertex_id_t v_id=0; v_id biggest_distance) 376 | { 377 | biggest_distance = distances[v_id][other_id]; 378 | } 379 | } 380 | } 381 | 382 | double length=1.0/biggest_distance; 383 | for(vertex_id_t other_id=0; other_id(res); 402 | m_VertexId=std::get(res); 403 | } 404 | 405 | 406 | 407 | 408 | double KamadaKawai::GetEnergy() const noexcept 409 | { 410 | return m_MaxVertexEnergy; 411 | } 412 | 413 | 414 | 415 | 416 | void KamadaKawai::SetInitPositions(bool aStartCircle) 417 | { 418 | m_Positions.resize(m_AdjList.size()); 419 | nodesoup::SetInitPositions(aStartCircle,m_Positions); 420 | } 421 | 422 | 423 | 424 | 425 | } 426 | -------------------------------------------------------------------------------- /ImNodeSoup.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | #include "imgui.h" 13 | 14 | #define IMGUI_DEFINE_MATH_OPERATORS 15 | #include "imgui_internal.h" 16 | 17 | #include 18 | #include "fruchterman_reingold.hpp" 19 | #include "kamada_kawai.hpp" 20 | 21 | 22 | const char* k6_dot=R"str(graph { 23 | v0 -- v1; 24 | v1 -- v2; 25 | v2 -- v3; 26 | v3 -- v4; 27 | v4 -- v5; 28 | v0 -- v5; 29 | v0 -- v2; 30 | v0 -- v3; 31 | v0 -- v4; 32 | v1 -- v3; 33 | v1 -- v4; 34 | v1 -- v5; 35 | v2 -- v4; 36 | v2 -- v5; 37 | v3 -- v5; 38 | })str"; 39 | 40 | 41 | 42 | 43 | const char* k6_2_dot = R"str(graph { 44 | v0 -- v1; 45 | v1 -- v2; 46 | v2 -- v3; 47 | v3 -- v4; 48 | v4 -- v5; 49 | v5 -- v0; 50 | v5 -- v6; 51 | v6 -- v7; 52 | v7 -- v8; 53 | v8 -- v9; 54 | v9 -- v10; 55 | v10 -- v5; 56 | })str"; 57 | 58 | 59 | 60 | 61 | const char* small_dense_dot = R"str(graph { 62 | v0 -- v1; 63 | v0 -- v2; 64 | v0 -- v3; 65 | v1 -- v2; 66 | v1 -- v4; 67 | v2 -- v4; 68 | v2 -- v5; 69 | v3 -- v5; 70 | v3 -- v6; 71 | v4 -- v7; 72 | v5 -- v7; 73 | v5 -- v8; 74 | v5 -- v9; 75 | v5 -- v6; 76 | v6 -- v10; 77 | v7 -- v14; 78 | v7 -- v11; 79 | v8 -- v11; 80 | v8 -- v12; 81 | v8 -- v9; 82 | v9 -- v12; 83 | v9 -- v13; 84 | v9 -- v10; 85 | v10 -- v13; 86 | v10 -- v17; 87 | v11 -- v14; 88 | v11 -- v12; 89 | v12 -- v14; 90 | v12 -- v15; 91 | v12 -- v13; 92 | v13 -- v16; 93 | v13 -- v17; 94 | v14 -- v18; 95 | v14 -- v15; 96 | v15 -- v18; 97 | v15 -- v19; 98 | v15 -- v16; 99 | v16 -- v19; 100 | v16 -- v17; 101 | v17 -- v19; 102 | v18 -- v20; 103 | v19 -- v20; 104 | })str"; 105 | 106 | 107 | 108 | 109 | const char* bin_tree_dot = R"str(graph G { 110 | v0 111 | v0 -- v1 112 | v1 113 | v1 -- v2 114 | v2 115 | v2 -- v3 116 | v2 -- v4 117 | v3 118 | v3 -- v5 119 | v3 -- v6 120 | v4 121 | v4 -- v7 122 | v4 -- v8 123 | v5 124 | v5 -- v9 125 | v5 -- v10 126 | v6 127 | v6 -- v11 128 | v6 -- v12 129 | v7 130 | v7 -- v13 131 | v7 -- v14 132 | v8 133 | v8 -- v15 134 | v9 135 | v9 -- v16 136 | v9 -- v17 137 | v10 138 | v10 -- v18 139 | v10 -- v19 140 | v11 141 | v11 -- v20 142 | v12 143 | v12 -- v21 144 | v13 145 | v13 -- v22 146 | v13 -- v23 147 | v14 148 | v14 -- v24 149 | v15 150 | v15 -- v25 151 | v16 152 | v16 -- v26 153 | v17 154 | v17 -- v27 155 | v18 156 | v18 -- v28 157 | v19 158 | v19 -- v29 159 | v20 160 | v20 -- v30 161 | v21 162 | v21 -- v31 163 | v21 -- v32 164 | v22 165 | v22 -- v33 166 | v22 -- v34 167 | v23 168 | v23 -- v35 169 | v24 170 | v24 -- v36 171 | v24 -- v37 172 | v25 173 | v25 -- v38 174 | v26 175 | v26 -- v39 176 | v27 177 | v27 -- v40 178 | v28 179 | v28 -- v41 180 | v29 181 | v29 -- v42 182 | v29 -- v43 183 | v30 184 | v30 -- v44 185 | v30 -- v45 186 | v31 187 | v31 -- v46 188 | v31 -- v47 189 | v32 190 | v32 -- v48 191 | v33 192 | v33 -- v49 193 | v34 194 | v34 -- v50 195 | v34 -- v51 196 | v35 197 | v35 -- v52 198 | v36 199 | v36 -- v53 200 | v37 201 | v37 -- v54 202 | v38 203 | v38 -- v55 204 | v38 -- v56 205 | v39 206 | v39 -- v57 207 | v39 -- v58 208 | v40 209 | v40 -- v59 210 | v41 211 | v41 -- v60 212 | v41 -- v61 213 | v42 214 | v42 -- v62 215 | v43 216 | v43 -- v63 217 | v44 218 | v44 -- v64 219 | v45 220 | v45 -- v65 221 | v46 222 | v46 -- v66 223 | v47 224 | v47 -- v67 225 | v47 -- v68 226 | v48 227 | v48 -- v69 228 | v49 229 | v49 -- v70 230 | v50 231 | v50 -- v71 232 | v50 -- v72 233 | v51 234 | v51 -- v73 235 | v52 236 | v52 -- v74 237 | v53 238 | v53 -- v75 239 | v54 240 | v54 -- v76 241 | v55 242 | v55 -- v77 243 | v55 -- v78 244 | v56 245 | v56 -- v79 246 | v56 -- v80 247 | v57 248 | v57 -- v81 249 | v57 -- v82 250 | v58 251 | v58 -- v83 252 | v59 253 | v59 -- v84 254 | v60 255 | v60 -- v85 256 | v61 257 | v61 -- v86 258 | v61 -- v87 259 | v62 260 | v62 -- v88 261 | v62 -- v89 262 | v63 263 | v63 -- v90 264 | v63 -- v91 265 | v64 266 | v64 -- v92 267 | v64 -- v93 268 | v65 269 | v65 -- v94 270 | v66 271 | v66 -- v95 272 | v67 273 | v67 -- v96 274 | v68 275 | v68 -- v97 276 | v68 -- v98 277 | v69 278 | v69 -- v99 279 | v70 280 | v71 281 | v72 282 | v73 283 | v74 284 | v75 285 | v76 286 | v77 287 | v78 288 | v79 289 | v80 290 | v81 291 | v82 292 | v83 293 | v84 294 | v85 295 | v86 296 | v87 297 | v88 298 | v89 299 | v90 300 | v91 301 | v92 302 | v93 303 | v94 304 | v95 305 | v96 306 | v97 307 | v98 308 | v99 309 | })str"; 310 | 311 | 312 | 313 | 314 | const char* quad_tree_dot = R"str(graph G { 315 | v0 316 | v0 -- v1 317 | v0 -- v2 318 | v0 -- v3 319 | v0 -- v4 320 | v1 321 | v1 -- v5 322 | v2 323 | v2 -- v6 324 | v2 -- v7 325 | v3 326 | v3 -- v8 327 | v3 -- v9 328 | v4 329 | v4 -- v10 330 | v4 -- v11 331 | v5 332 | v5 -- v12 333 | v5 -- v13 334 | v6 335 | v6 -- v14 336 | v6 -- v15 337 | v6 -- v16 338 | v6 -- v17 339 | v7 340 | v7 -- v18 341 | v7 -- v19 342 | v7 -- v20 343 | v8 344 | v8 -- v21 345 | v8 -- v22 346 | v8 -- v23 347 | v9 348 | v9 -- v24 349 | v10 350 | v10 -- v25 351 | v11 352 | v11 -- v26 353 | v11 -- v27 354 | v12 355 | v12 -- v28 356 | v12 -- v29 357 | v12 -- v30 358 | v12 -- v31 359 | v13 360 | v13 -- v32 361 | v13 -- v33 362 | v13 -- v34 363 | v14 364 | v14 -- v35 365 | v14 -- v36 366 | v15 367 | v15 -- v37 368 | v15 -- v38 369 | v15 -- v39 370 | v16 371 | v16 -- v40 372 | v17 373 | v17 -- v41 374 | v17 -- v42 375 | v17 -- v43 376 | v18 377 | v18 -- v44 378 | v18 -- v45 379 | v18 -- v46 380 | v19 381 | v19 -- v47 382 | v19 -- v48 383 | v20 384 | v20 -- v49 385 | v20 -- v50 386 | v20 -- v51 387 | v21 388 | v21 -- v52 389 | v22 390 | v22 -- v53 391 | v23 392 | v23 -- v54 393 | v23 -- v55 394 | v24 395 | v24 -- v56 396 | v24 -- v57 397 | v24 -- v58 398 | v25 399 | v25 -- v59 400 | v26 401 | v26 -- v60 402 | v26 -- v61 403 | v27 404 | v27 -- v62 405 | v27 -- v63 406 | v28 407 | v28 -- v64 408 | v28 -- v65 409 | v29 410 | v29 -- v66 411 | v29 -- v67 412 | v29 -- v68 413 | v29 -- v69 414 | v30 415 | v30 -- v70 416 | v30 -- v71 417 | v30 -- v72 418 | v31 419 | v31 -- v73 420 | v31 -- v74 421 | v31 -- v75 422 | v32 423 | v32 -- v76 424 | v33 425 | v33 -- v77 426 | v33 -- v78 427 | v33 -- v79 428 | v34 429 | v34 -- v80 430 | v35 431 | v35 -- v81 432 | v35 -- v82 433 | v36 434 | v36 -- v83 435 | v37 436 | v37 -- v84 437 | v37 -- v85 438 | v37 -- v86 439 | v38 440 | v38 -- v87 441 | v38 -- v88 442 | v38 -- v89 443 | v39 444 | v39 -- v90 445 | v39 -- v91 446 | v39 -- v92 447 | v40 448 | v40 -- v93 449 | v40 -- v94 450 | v41 451 | v41 -- v95 452 | v42 453 | v42 -- v96 454 | v42 -- v97 455 | v43 456 | v43 -- v98 457 | v43 -- v99 458 | v44 459 | v45 460 | v46 461 | v47 462 | v48 463 | v49 464 | v50 465 | v51 466 | v52 467 | v53 468 | v54 469 | v55 470 | v56 471 | v57 472 | v58 473 | v59 474 | v60 475 | v61 476 | v62 477 | v63 478 | v64 479 | v65 480 | v66 481 | v67 482 | v68 483 | v69 484 | v70 485 | v71 486 | v72 487 | v73 488 | v74 489 | v75 490 | v76 491 | v77 492 | v78 493 | v79 494 | v80 495 | v81 496 | v82 497 | v83 498 | v84 499 | v85 500 | v86 501 | v87 502 | v88 503 | v89 504 | v90 505 | v91 506 | v92 507 | v93 508 | v94 509 | v95 510 | v96 511 | v97 512 | v98 513 | v99 514 | })str"; 515 | 516 | 517 | 518 | 519 | 520 | 521 | // Read not-too-complicated dot files 522 | nodesoup::adj_list_t read_from_dot(const char* aDotData) 523 | { 524 | nodesoup::adj_list_t adj_list; 525 | 526 | std::istringstream ifs(aDotData); 527 | if (!ifs.good()) 528 | { 529 | return adj_list; 530 | } 531 | 532 | std::unordered_map names; 533 | 534 | auto name_to_vertex_id = [&adj_list,&names](std::string name) -> nodesoup::vertex_id_t 535 | { 536 | if(name[name.size() - 1] == ';') 537 | { 538 | name.erase(name.end() - 1, name.end()); 539 | } 540 | 541 | nodesoup::vertex_id_t v_id; 542 | auto it = names.find(name); 543 | if (it != names.end()) 544 | { 545 | return (*it).second; 546 | } 547 | 548 | v_id = adj_list.size(); 549 | names.insert({ name, v_id }); 550 | adj_list.resize(v_id + 1); 551 | return v_id; 552 | }; 553 | 554 | std::string line; 555 | // skip first line 556 | std::getline(ifs, line); 557 | 558 | while (std::getline(ifs, line)) 559 | { 560 | if (line[0] == '}') 561 | { 562 | break; 563 | } 564 | 565 | std::istringstream iss(line); 566 | std::string name, edge_sign, adj_name; 567 | iss >> name >> edge_sign >> adj_name; 568 | 569 | // add vertex if new 570 | nodesoup::vertex_id_t v_id = name_to_vertex_id(name); 571 | 572 | assert(edge_sign == "--" || edge_sign.size() == 0); 573 | if (edge_sign != "--") 574 | { 575 | continue; 576 | } 577 | 578 | // add adjacent vertex if new 579 | nodesoup::vertex_id_t adj_id = name_to_vertex_id(adj_name); 580 | 581 | // add edge if new 582 | if (find(adj_list[v_id].begin(),adj_list[v_id].end(), adj_id) == adj_list[v_id].end()) 583 | { 584 | adj_list[v_id].push_back(adj_id); 585 | adj_list[adj_id].push_back(v_id); 586 | } 587 | } 588 | 589 | return adj_list; 590 | } 591 | 592 | 593 | 594 | 595 | constexpr std::size_t kInvadidVertex=static_cast(-1); 596 | constexpr float kMinUIDist=9.0f; // TODO: Calculate this with DPI? 597 | const float kWindowInitWidth = 800.0f; 598 | const float kWindowInitHeight= 600.0f; 599 | static ImVec2 gDisp{0.0f,0.0f}; 600 | static float gScale=1.0f; 601 | static nodesoup::vertex_id_t gSelectedVertex=kInvadidVertex; 602 | 603 | 604 | 605 | struct MoveRes 606 | { 607 | bool m_Moved; 608 | bool m_Recalculate; 609 | nodesoup::vertex_id_t m_Index; 610 | ImVec2 m_Disp; 611 | }; 612 | 613 | 614 | 615 | 616 | 617 | static inline float sq_dist(const ImVec2& aPos1,const ImVec2& aPos2) noexcept 618 | { 619 | ImVec2 d=aPos2-aPos1; 620 | return d.x*d.x + d.y*d.y; 621 | } 622 | 623 | 624 | 625 | static ImVec2 GetStartPos() noexcept 626 | { 627 | ImGuiWindow* window = ImGui::GetCurrentWindowRead(); 628 | return window->Pos+gDisp; 629 | } 630 | 631 | 632 | 633 | static nodesoup::vertex_id_t GetPosAt(const ImVec2& aPos,const std::vector& aPositions) 634 | { 635 | ImVec2 origin(kWindowInitWidth / 2.0, kWindowInitHeight / 2.0); 636 | ImVec2 cursor_pos=GetStartPos(); 637 | 638 | for(nodesoup::vertex_id_t v_id=0; v_id& aPositions) 657 | { 658 | MoveRes res; 659 | 660 | ImGuiIO& io = ImGui::GetIO(); 661 | 662 | if(gSelectedVertex==kInvadidVertex) // No hay ninguno seleccionado 663 | { 664 | if(io.MouseDown[1]) 665 | { 666 | ImVec2 mouse_pos=io.MousePos; 667 | gSelectedVertex=GetPosAt(mouse_pos,aPositions); 668 | 669 | 670 | res.m_Index=gSelectedVertex; 671 | res.m_Disp={0.0f,0.0f}; 672 | res.m_Moved=(gSelectedVertex==kInvadidVertex?false:true); 673 | res.m_Recalculate=false; 674 | 675 | return res; 676 | } 677 | else 678 | { 679 | res.m_Moved=false; 680 | res.m_Recalculate=false; 681 | 682 | return res; 683 | } 684 | } 685 | else // Hay un vertice seleccionado 686 | { 687 | if(io.MouseDown[1]) // Lo esta moviendo 688 | { 689 | res.m_Index=gSelectedVertex; 690 | res.m_Disp=io.MouseDelta/gScale; 691 | res.m_Moved=true; 692 | res.m_Recalculate=false; 693 | return res; 694 | } 695 | else // Ha dejado de pulsar 696 | { 697 | res.m_Index=gSelectedVertex; 698 | res.m_Moved=true; 699 | res.m_Recalculate=true; 700 | 701 | if(io.MouseDragMaxDistanceSqr[1]& aPositions,bool aAllowMove 724 | ,bool aDrawDebug) 725 | { 726 | ImGuiWindow* w=ImGui::GetCurrentWindow(); 727 | ImGuiIO& io=ImGui::GetIO(); 728 | 729 | if(io.MouseDownDuration[1]>0.0 && aAllowMove) 730 | { 731 | if(w->InnerClipRect.Contains(io.MousePos)) 732 | { 733 | gDisp+=io.MouseDelta; 734 | } 735 | } 736 | 737 | if(io.MouseWheel>0.0f) 738 | { 739 | gScale*=1.1f; 740 | } 741 | else if(io.MouseWheel<0.0f) 742 | { 743 | gScale*=0.9f; 744 | } 745 | 746 | ImDrawList* draw_list=ImGui::GetWindowDrawList(); 747 | ImVec2 cursor_pos=GetStartPos(); 748 | 749 | ImVec2 origin(kWindowInitWidth/2.0,kWindowInitHeight/2.0); 750 | 751 | const ImU32 node_col=ImGui::GetColorU32(ImGuiCol_ScrollbarGrabActive); 752 | const ImU32 node_fix_col=ImGui::GetColorU32(ImGuiCol_NavHighlight); 753 | const ImU32 arc_col =ImGui::GetColorU32(ImGuiCol_ScrollbarGrab); 754 | const ImU32 txt_col =ImGui::GetColorU32(ImGuiCol_PlotLinesHovered); 755 | 756 | 757 | for(nodesoup::vertex_id_t v_id=0; v_idAddLine(cursor_pos+v_pos,cursor_pos+adj_pos,arc_col); 771 | } 772 | 773 | 774 | draw_list->AddCircleFilled(cursor_pos+ImVec2(v_pos.x,v_pos.y),curr_pos.m_Radius, curr_pos.m_Fixed?node_fix_col:node_col); 775 | if(aDrawDebug) 776 | { 777 | draw_list->AddText(cursor_pos+ImVec2(v_pos.x,v_pos.y), txt_col, std::to_string(v_id).c_str()); 778 | draw_list->AddText(cursor_pos+ImVec2(v_pos.x,v_pos.y+20.0f), txt_col, std::to_string(v_pos.x).c_str()); 779 | draw_list->AddText(cursor_pos+ImVec2(v_pos.x,v_pos.y+40.0f), txt_col, std::to_string(v_pos.y).c_str()); 780 | } 781 | } 782 | 783 | ImGuiContext& g = *GImGui; 784 | ImGui::SetCursorPos({20.0f,w->InnerClipRect.GetHeight()-g.FontSize }); 785 | ImGui::Text("x:%.3f y:%.3f scale:%.3f",gDisp.x,gDisp.y,gScale); 786 | } 787 | 788 | 789 | 790 | 791 | void ShowNodeSoup() 792 | { 793 | static std::vector positions; 794 | 795 | float k=15.0; 796 | 797 | static nodesoup::adj_list_t adj_list; 798 | static nodesoup::FruchtermanReingold fr(adj_list,k); 799 | static nodesoup::KamadaKawai ka(adj_list,k); 800 | 801 | constexpr int kFruchtermanReingold=0; 802 | constexpr int kKamadaKawai=1; 803 | static int method=kFruchtermanReingold; 804 | 805 | constexpr int kCircle=0; 806 | constexpr int kRandom=1; 807 | static int init_mode=kCircle; 808 | 809 | static bool draw_debug=false; 810 | 811 | 812 | ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f), ImGuiCond_Appearing); 813 | ImGui::SetNextWindowSize(ImVec2(kWindowInitWidth,kWindowInitHeight), ImGuiCond_Appearing); 814 | 815 | if (ImGui::Begin("NodeSoup",nullptr)) 816 | { 817 | int prev_method=method; 818 | 819 | ImGui::BeginGroup(); 820 | ImGui::RadioButton("Fruchterman Reingold",&method,kFruchtermanReingold); 821 | ImGui::RadioButton("Kamada Kawai",&method,kKamadaKawai); 822 | ImGui::EndGroup(); 823 | 824 | ImGui::SameLine(350.0f); 825 | 826 | ImGui::BeginGroup(); 827 | ImGui::RadioButton("Init circle",&init_mode,kCircle); 828 | ImGui::RadioButton("Init random",&init_mode,kRandom); 829 | ImGui::EndGroup(); 830 | 831 | const char* items[]={"None","K6","K6-2","Small dense","Bin tree","Quad tree"}; 832 | const char* items_data[] = {"", k6_dot,k6_2_dot,small_dense_dot,bin_tree_dot,quad_tree_dot}; 833 | static int item_current=0; 834 | bool change=ImGui::Combo("Data", &item_current,items,IM_ARRAYSIZE(items)); 835 | ImGui::SameLine(); 836 | change|=ImGui::SmallButton("R"); 837 | 838 | ImGui::NewLine(); 839 | ImGui::Checkbox("Show debug info",&draw_debug); 840 | if(draw_debug) 841 | { 842 | ImGui::NewLine(); 843 | ImGui::Text("Energy: %.3f",static_cast(method==kFruchtermanReingold?fr.GetEnergy() : ka.GetEnergy() )); 844 | } 845 | 846 | if(prev_method!=method || change) 847 | { 848 | adj_list=read_from_dot(items_data[item_current]); 849 | positions.resize(adj_list.size()); 850 | nodesoup::SetRadiuses(adj_list,positions); 851 | 852 | 853 | if(method==kFruchtermanReingold) 854 | { 855 | fr.Start(init_mode==kCircle); 856 | } 857 | else 858 | { 859 | ka.Start(init_mode==kCircle); 860 | } 861 | } 862 | 863 | if(!adj_list.empty()) 864 | { 865 | if(method==kFruchtermanReingold) 866 | { 867 | fr.Step(15,0,positions); 868 | } 869 | else 870 | { 871 | ka.Step(kWindowInitWidth,kWindowInitHeight,positions); 872 | } 873 | } 874 | 875 | MoveRes r=MovePos(positions); 876 | if(r.m_Moved) 877 | { 878 | if(method==kFruchtermanReingold) 879 | { 880 | fr.MovePos(r.m_Index,r.m_Disp,r.m_Recalculate); 881 | } 882 | else 883 | { 884 | ka.MovePos(r.m_Index,r.m_Disp,r.m_Recalculate); 885 | } 886 | } 887 | 888 | DrawData(adj_list,positions,!r.m_Moved,draw_debug); 889 | 890 | ImGui::End(); 891 | } 892 | 893 | } --------------------------------------------------------------------------------