BiAuditGraph.php.network-graph.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. var DBG = DBG || false;
  2. var DBG1 = true;
  3. if ('undefined' === typeof HTML_ID_REF_GRAPH) throw "Missing HTML_ID_REF_GRAPH";
  4. if (!RAPORT_ID) throw "Missing RAPORT_ID";
  5. if (!API_URL) throw "Missing API_URL";
  6. if (!global.p5VendorJs) throw "Missing p5VendorJs";
  7. if (!global.vis) throw "Missing vis";
  8. if (!global.p5VendorJs.Typeahead) throw "Missing Typeahead";
  9. var createReactClass = global.p5VendorJs.createReactClass;
  10. var h = global.p5VendorJs.React.createElement;
  11. var React = global.p5VendorJs.React;
  12. var ReactDOM = global.p5VendorJs.ReactDOM;
  13. var Redux = global.p5VendorJs.Redux;
  14. var ReduxThunk = global.p5VendorJs.ReduxThunk;
  15. var createStoreWithThunkMiddleware = Redux.applyMiddleware(ReduxThunk)(Redux.createStore); // TODO: to vendor.js
  16. var Typeahead = global.p5VendorJs.Typeahead;
  17. var mapStatsNodeToGraphNode = function (node) {
  18. return {
  19. id: node['@object'] + '.' + node['@primaryKey'],
  20. label: node['@label'],
  21. value: 1,
  22. level: 0,
  23. }
  24. }
  25. { // TODO: mv to another file
  26. function getinitialState() {
  27. var nodes = (STATS && STATS.nodes) ? STATS.nodes : [];
  28. var edges = (STATS && STATS.edges) ? STATS.edges : [];
  29. DBG1 && console.log('DBG: STATS', STATS);
  30. return {
  31. nodes: nodes.map(mapStatsNodeToGraphNode),
  32. edges: edges,
  33. isLoading: false,
  34. sentRequestId: 0,
  35. receivedRequestId: 0,
  36. }
  37. }
  38. function networkGraphStore(state, action) {
  39. var prevState = state || getinitialState();
  40. DBG1 && console.log('DBG: store', { prevState, action, actionType: action.type });
  41. switch (action.type) {
  42. case 'SET_SENT_REQUEST_ID': return Object.assign(prevState, {
  43. sentRequestId: action.sentRequestId,
  44. isLoading: true
  45. });
  46. case 'SET_RESPONSE': return Object.assign(prevState, {
  47. receivedRequestId: action.requestId,
  48. nodes: action.body.nodes.map(mapStatsNodeToGraphNode),
  49. edges: action.body.edges,
  50. isLoading: (prevState.sentRequestId > action.requestId) ? true : false,
  51. });
  52. default: return prevState;
  53. }
  54. }
  55. function createNetworkGraphStoreActions() {
  56. var setSentRequestId = function (sentRequestId) {
  57. return { type: 'SET_SENT_REQUEST_ID', sentRequestId: sentRequestId };
  58. }
  59. var setResponse = function (response) {
  60. return Object.assign(response, { type: 'SET_RESPONSE' });
  61. }
  62. var fetchData = function (raportId, params) {
  63. return function (dispatch, getState) {
  64. var state = getState();
  65. var this__requestId = state.sentRequestId + 1;
  66. dispatch(setSentRequestId(this__requestId));
  67. var reqPromise = window.fetch(API_URL, {
  68. method: 'GET',
  69. credentials: 'same-origin',
  70. }).then(function (response) {
  71. return response.json();
  72. });
  73. return reqPromise.then(function (response) {
  74. var items = response;
  75. var state = getState();
  76. if (state.receivedRequestId > this__requestId) {
  77. DBG1 && console.log('DBG: skiped response', { 'state.receivedRequestId': state.receivedRequestId, this__requestId });
  78. return;
  79. }
  80. dispatch(
  81. setResponse({
  82. requestId: this__requestId,
  83. msg: "Pobrano dane",
  84. body: response.body
  85. })
  86. );
  87. }).catch(function (e) {
  88. p5UI__notifyAjaxCallback({type: 'error', msg: 'Wystąpił błąd #GS1: ' + e});
  89. // dispatch( setErrorMsg(e) ); // TODO: show error with msg and refresh button
  90. });
  91. }
  92. }
  93. return {
  94. fetchData: fetchData,
  95. }
  96. }
  97. }
  98. var defaultOptions = {
  99. nodes: {
  100. shape: 'dot',
  101. scaling: {
  102. min: 20, max: 30,
  103. label: {
  104. min: 8, max: 16, drawThreshold: 9, maxVisible: 20
  105. }
  106. },
  107. font: { color: "#666", size: 10, face: 'Helvetica Neue, Helvetica, Arial' }
  108. // font: "8px Helvetica Neue, Helvetica, Arial"
  109. },
  110. interaction: {
  111. hover: true,
  112. hoverConnectedEdges: false,
  113. selectConnectedEdges: true,
  114. },
  115. };
  116. var TMP_COUNTER = 1;
  117. var p5UI__NetworkGraph = createReactClass({
  118. _network: null,
  119. _visOutputRef: React.createRef(),
  120. _nodes: new vis.DataSet(),
  121. _edges: new vis.DataSet(),
  122. getStateFromStore: function () {
  123. var state = this.props.store.getState();
  124. return {
  125. isLoading: state.isLoading,
  126. receivedRequestId: state.receivedRequestId, // to force render after udpate nodes, edges
  127. }
  128. },
  129. getInitialState: function () {
  130. return {
  131. initialized: false,
  132. isLoading: false,
  133. receivedRequestId: null,
  134. };
  135. },
  136. setOutputRef: function (elem) {
  137. this._visOutputRef = elem;
  138. },
  139. componentDidMount: function () {
  140. var data = { nodes: this._nodes, edges: this._edges };
  141. this._network = new vis.Network(this._visOutputRef, data, defaultOptions);
  142. // bindNetwork();
  143. this.setState({ initialized: true });
  144. this._unsubscribe = this.props.store.subscribe(this._storeUpdated)
  145. },
  146. _storeUpdated: function () {
  147. this.setState( this.getStateFromStore() )
  148. },
  149. testOnClick1: function () {
  150. TMP_COUNTER++;
  151. this._nodes.add({ id: TMP_COUNTER, label: "Node " + TMP_COUNTER, value: TMP_COUNTER, level: 0 });
  152. },
  153. testOnClick2: function () {
  154. TMP_COUNTER++;
  155. var ids = this._nodes.getIds();
  156. var totalNodes = ids.length
  157. if (totalNodes < 3) return;
  158. var fromIdx = Math.floor(Math.random() * totalNodes);
  159. var toIdx = Math.floor(Math.random() * totalNodes);
  160. this._edges.add({ from: ids[fromIdx], to: ids[toIdx] });
  161. },
  162. testOnClick3: function () {
  163. this.props.store.dispatch( this.props.storeActions.fetchData( this.props.raportId ) );
  164. },
  165. shouldComponentUpdate: function (nextProps, nextState) {
  166. if (this.state.receivedRequestId !== nextState.receivedRequestId) { // add missing nodes
  167. var state = this.props.store.getState();
  168. DBG1 && console.warn("TODO: update nodes, edges", { state });
  169. // this._edges.add( edge_or_edges_array )
  170. if (state.nodes && state.nodes.length) {
  171. var __nodes = this._nodes;
  172. state.nodes.forEach(function (node) {
  173. __nodes.add(node);
  174. })
  175. }
  176. // edge: { from: page, to: subpageID, color:getEdgeColor(level), level: level, selectionWidth:2, hoverWidth:0 }
  177. if (state.edges && state.edges.length) {
  178. var __edges = this._edges;
  179. // state.edges.forEach(function (edge) {
  180. state.edges.slice(0, 30).forEach(function (edge) {
  181. __edges.add({
  182. from: edge.source,
  183. to: edge.target,
  184. });
  185. })
  186. }
  187. }
  188. if (this.props.selected.length != nextProps.selected.length) {
  189. var edges = []; // TODO: by nodes
  190. this._network.setData({ nodes: nextProps.selected, edges: edges });
  191. }
  192. return false;
  193. },
  194. render: function () {
  195. DBG1 && console.log('DBG:render');
  196. return h('div', {}, [
  197. h('div', {
  198. ref: this.setOutputRef,
  199. style: {
  200. 'min-height': 600,
  201. 'height': 600,
  202. },
  203. }),
  204. h('div', {}, [
  205. h('button', { onClick: this.testOnClick1 }, [ "TEST 1 ", h('small', [], "(+ node)") ]),
  206. h('button', { onClick: this.testOnClick2 }, [ "TEST 2 ", h('small', [], "(+ edge)") ]),
  207. h('button', { onClick: this.testOnClick3 }, [ "TEST 3 ", h('small', [], "(+ fetch)") ]),
  208. ]),
  209. ]);
  210. }
  211. });
  212. var p5UI__PanelNetworkGraph = createReactClass({
  213. getInitialState: function () {
  214. return {
  215. selected: [],
  216. }
  217. },
  218. handleSelectFeatures: function (selected) {
  219. DBG1 && console.log('DBG:typeahead selected:', { selected })
  220. this.setState({ selected: selected });
  221. },
  222. render: function () {
  223. var state = this.props.store.getState()
  224. DBG1 && console.log('DBG: state', state);
  225. var options = state.nodes || [];
  226. return h(React.Fragment, {}, [
  227. h(Typeahead, {
  228. labelKey: "label",
  229. multiple: true,
  230. options: options,
  231. placeholder: "Wybierz",
  232. bsSize: 'large',
  233. onChange: this.handleSelectFeatures,
  234. }),
  235. h(p5UI__NetworkGraph, {
  236. raportId: this.props.raportId,
  237. store: this.props.store,
  238. storeActions: this.props.storeActions,
  239. selected: this.state.selected,
  240. })
  241. ])
  242. }
  243. })
  244. ReactDOM.render(
  245. h(p5UI__PanelNetworkGraph, {
  246. raportId: RAPORT_ID,
  247. store: createStoreWithThunkMiddleware(networkGraphStore),
  248. storeActions: createNetworkGraphStoreActions(),
  249. }),
  250. document.getElementById(HTML_ID_REF_GRAPH)
  251. );