App.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <template>
  2. <div id="app">
  3. <h2><a href='https://github.com/anvaka/ngraph.forcelayout'>ngraph.forcelayout</a> demo
  4. <small class='toggle-settings'><a href='#' @click.prevent='settingsOpen = !settingsOpen'>{{settingsOpen ? 'hide settings' : 'show settings'}}</a></small>
  5. </h2>
  6. <div class='content' v-if='settingsOpen'>
  7. <div class='row'>
  8. <div class='label'>Graph </div>
  9. <select v-model='selectedGraph' :disable='loading' class='value'>
  10. <option v-for="graph in graphs" :key='graph' :value='graph'>{{graph}}</option>
  11. </select>
  12. </div>
  13. <input-value label='Time step' v-model='layoutSettings.timeStep'>
  14. This is integration time step value. The higher it is, the faster nodes will move, but setting it too high
  15. can result in lots of jitter and instability.
  16. </input-value>
  17. <input-value label='Gravity' v-model='layoutSettings.gravity'>
  18. This coefficient defines how strongly each node repels each other.
  19. </input-value>
  20. <input-value label='Ideal spring length' v-model='layoutSettings.springLength'>
  21. What is the ideal length of each spring?
  22. </input-value>
  23. <input-value label='Spring coefficient' v-model='layoutSettings.springCoefficient'>
  24. Higher values makes the spring force stronger, pushing edges closer to the ideal spring length.
  25. </input-value>
  26. <input-value label='Drag coefficient' v-model='layoutSettings.dragCoefficient'>
  27. This coefficient introduces "resistance" from environment. When it is close to 0 the forces
  28. will have a lot of freedom, nothing will be stopping them, and that can result in a very
  29. unstable simulation.
  30. </input-value>
  31. <input-value label='Theta' v-model='layoutSettings.theta'>
  32. This coefficient influences when we apply long distance forces approximation. When this value is
  33. close to 0, the simulation compares forces between every single node (giving O(n^2), slow performance).
  34. Recommended value is 0.8.
  35. </input-value>
  36. <input-value label='Dimensions' v-model='layoutSettings.dimensions' step=1>
  37. Defines number of dimensions of the space where layout is performed. For visualization purpose
  38. 2 or 3 dimensions are normally enough. Note: Memory consumptions grows exponentially with number
  39. of dimensions.
  40. </input-value>
  41. <input-flag label='Follow bounding box' v-model='fixedViewBox' step=1>
  42. Setting this to true will disable pan/zoom but will always keep the graph visible. This is not
  43. part of the layout algorithm. Just a view setting of the renderer.
  44. </input-flag>
  45. <div v-if='loading'>Loading graph...</div>
  46. </div>
  47. <div v-if='!loading' class='layout-box'>
  48. <a href="#" @click.prevent='toggleLayoutRun' class='btn'>{{isRunning ? 'Stop layout' : 'Start layout'}}</a>
  49. </div>
  50. </div>
  51. </template>
  52. <script>
  53. import createGraphScene from './lib/createGraphScene';
  54. import getAvailableGraphs from './lib/getAvailableGraphs';
  55. import loadGraph from './lib/loadGraph';
  56. import bus from './lib/bus';
  57. import queryState from 'query-state';
  58. import InputValue from './components/InputValue';
  59. import InputFlag from './components/InputFlag';
  60. let appState = queryState({
  61. graph: 'Miserables',
  62. timeStep: 0.5,
  63. springLength: 10,
  64. springCoefficient: 0.8,
  65. dragCoefficient: 0.9,
  66. dimensions: 2,
  67. theta: 0.8,
  68. gravity: -12,
  69. }, { useSearch: true });
  70. export default {
  71. name: 'app',
  72. components: {
  73. InputValue,
  74. InputFlag
  75. },
  76. methods: {
  77. toggleLayoutRun() {
  78. this.isRunning = !this.isRunning;
  79. this.scene.runLayout(this.isRunning);
  80. },
  81. loadNewGraph(newGraph) {
  82. this.loading = true;
  83. this.stats = null;
  84. this.isRunning = false;
  85. loadGraph(newGraph).then(newGraph => {
  86. bus.fire('load-graph', newGraph, this.selectedLayout);
  87. this.loading = false;
  88. });
  89. },
  90. onGraphLoaded() {
  91. this.isRunning = false;
  92. }
  93. },
  94. watch: {
  95. layoutSettings: {
  96. deep: true,
  97. handler(newValue) {
  98. this.scene.updateLayoutSettings(newValue);
  99. appState.set(newValue);
  100. }
  101. },
  102. fixedViewBox(newValue) {
  103. this.scene.setFixedViewBox(newValue);
  104. },
  105. selectedGraph(newGraph) {
  106. appState.set('graph', newGraph);
  107. this.loadNewGraph(newGraph);
  108. }
  109. },
  110. data() {
  111. let graphs = getAvailableGraphs();
  112. return {
  113. isRunning: false,
  114. fixedViewBox: false,
  115. selectedGraph: appState.get('graph'),
  116. settingsOpen: window.innerWidth > 500,
  117. loading: false,
  118. layoutSettings: {
  119. timeStep: appState.get('timeStep'),
  120. springLength: appState.get('springLength'),
  121. springCoefficient: appState.get('springCoefficient'),
  122. dragCoefficient: appState.get('dragCoefficient'),
  123. dimensions: appState.get('dimensions'),
  124. theta: appState.get('theta'),
  125. gravity: appState.get('gravity'),
  126. },
  127. graphs
  128. }
  129. },
  130. mounted() {
  131. const canvas = document.getElementById('cnv');
  132. this.scene = createGraphScene(canvas, {...this.layoutSettings});
  133. this.loadNewGraph(this.selectedGraph);
  134. bus.on('load-graph', this.onGraphLoaded);
  135. },
  136. beforeDestroy() {
  137. if (this.scene) {
  138. this.scene.dispose();
  139. }
  140. }
  141. }
  142. </script>
  143. <style lang='stylus'>
  144. small-screen = 500px;
  145. #app {
  146. position: absolute;
  147. width: 400px;
  148. background: rgb(12, 41, 82);
  149. border: 1px solid white;
  150. }
  151. a {
  152. text-decoration: none;
  153. }
  154. .content {
  155. padding: 8px;
  156. }
  157. .row {
  158. display: flex;
  159. flex-direction: row;
  160. align-items: baseline;
  161. }
  162. .row .label {
  163. flex: 1;
  164. }
  165. .row .value {
  166. flex: 1;
  167. }
  168. .row select {
  169. width: 100%;
  170. }
  171. a.btn {
  172. color: rgb(244, 244, 244);
  173. text-decoration: none;
  174. justify-content: center;
  175. align-items: center;
  176. border-top: 1px solid;
  177. height: 32px;
  178. width: 100%;
  179. display: flex;
  180. margin: 0;
  181. }
  182. h2 {
  183. margin: 8px;
  184. font-size: 18px;
  185. font-weight: normal;
  186. a {
  187. color: #267fcd;
  188. }
  189. small a {
  190. position: absolute;
  191. right: 8px;
  192. top: 8px;
  193. font-size: 12px;
  194. }
  195. }
  196. .number {
  197. color: yellow;
  198. font-size: 18px;
  199. }
  200. .names {
  201. position: fixed;
  202. font-size: 24px;
  203. top: 18px;
  204. left: 50%;
  205. transform: translateX(-50%);
  206. }
  207. @media (max-width: small-screen) {
  208. #app {
  209. width: 100%;
  210. }
  211. }
  212. </style>