var DBG = DBG || false; var DBG1 = true; if ('undefined' === typeof HTML_ID_REF_GRAPH) throw "Missing HTML_ID_REF_GRAPH"; if (!RAPORT_ID) throw "Missing RAPORT_ID"; if (!API_URL) throw "Missing API_URL"; if (!global.p5VendorJs) throw "Missing p5VendorJs"; if (!global.vis) throw "Missing vis"; if (!global.p5VendorJs.Typeahead) throw "Missing Typeahead"; var createReactClass = global.p5VendorJs.createReactClass; var h = global.p5VendorJs.React.createElement; var React = global.p5VendorJs.React; var ReactDOM = global.p5VendorJs.ReactDOM; var Redux = global.p5VendorJs.Redux; var ReduxThunk = global.p5VendorJs.ReduxThunk; var createStoreWithThunkMiddleware = Redux.applyMiddleware(ReduxThunk)(Redux.createStore); // TODO: to vendor.js var Typeahead = global.p5VendorJs.Typeahead; var mapStatsNodeToGraphNode = function (node) { return { id: node['@object'] + '.' + node['@primaryKey'], label: node['@label'], value: 1, level: 0, } } { // TODO: mv to another file function getinitialState() { var nodes = (STATS && STATS.nodes) ? STATS.nodes : []; var edges = (STATS && STATS.edges) ? STATS.edges : []; DBG1 && console.log('DBG: STATS', STATS); return { nodes: nodes.map(mapStatsNodeToGraphNode), edges: edges, isLoading: false, sentRequestId: 0, receivedRequestId: 0, } } function networkGraphStore(state, action) { var prevState = state || getinitialState(); DBG1 && console.log('DBG: store', { prevState, action, actionType: action.type }); switch (action.type) { case 'SET_SENT_REQUEST_ID': return Object.assign(prevState, { sentRequestId: action.sentRequestId, isLoading: true }); case 'SET_RESPONSE': return Object.assign(prevState, { receivedRequestId: action.requestId, nodes: action.body.nodes.map(mapStatsNodeToGraphNode), edges: action.body.edges, isLoading: (prevState.sentRequestId > action.requestId) ? true : false, }); default: return prevState; } } function createNetworkGraphStoreActions() { var setSentRequestId = function (sentRequestId) { return { type: 'SET_SENT_REQUEST_ID', sentRequestId: sentRequestId }; } var setResponse = function (response) { return Object.assign(response, { type: 'SET_RESPONSE' }); } var fetchData = function (raportId, params) { return function (dispatch, getState) { var state = getState(); var this__requestId = state.sentRequestId + 1; dispatch(setSentRequestId(this__requestId)); var reqPromise = window.fetch(API_URL, { method: 'GET', credentials: 'same-origin', }).then(function (response) { return response.json(); }); return reqPromise.then(function (response) { var items = response; var state = getState(); if (state.receivedRequestId > this__requestId) { DBG1 && console.log('DBG: skiped response', { 'state.receivedRequestId': state.receivedRequestId, this__requestId }); return; } dispatch( setResponse({ requestId: this__requestId, msg: "Pobrano dane", body: response.body }) ); }).catch(function (e) { p5UI__notifyAjaxCallback({type: 'error', msg: 'Wystąpił błąd #GS1: ' + e}); // dispatch( setErrorMsg(e) ); // TODO: show error with msg and refresh button }); } } return { fetchData: fetchData, } } } var defaultOptions = { nodes: { shape: 'dot', scaling: { min: 20, max: 30, label: { min: 8, max: 16, drawThreshold: 9, maxVisible: 20 } }, font: { color: "#666", size: 10, face: 'Helvetica Neue, Helvetica, Arial' } // font: "8px Helvetica Neue, Helvetica, Arial" }, interaction: { hover: true, hoverConnectedEdges: false, selectConnectedEdges: true, }, }; var TMP_COUNTER = 1; var p5UI__NetworkGraph = createReactClass({ _network: null, _visOutputRef: React.createRef(), _nodes: new vis.DataSet(), _edges: new vis.DataSet(), getStateFromStore: function () { var state = this.props.store.getState(); return { isLoading: state.isLoading, receivedRequestId: state.receivedRequestId, // to force render after udpate nodes, edges } }, getInitialState: function () { return { initialized: false, isLoading: false, receivedRequestId: null, }; }, setOutputRef: function (elem) { this._visOutputRef = elem; }, componentDidMount: function () { var data = { nodes: this._nodes, edges: this._edges }; this._network = new vis.Network(this._visOutputRef, data, defaultOptions); // bindNetwork(); this.setState({ initialized: true }); this._unsubscribe = this.props.store.subscribe(this._storeUpdated) }, _storeUpdated: function () { this.setState( this.getStateFromStore() ) }, testOnClick1: function () { TMP_COUNTER++; this._nodes.add({ id: TMP_COUNTER, label: "Node " + TMP_COUNTER, value: TMP_COUNTER, level: 0 }); }, testOnClick2: function () { TMP_COUNTER++; var ids = this._nodes.getIds(); var totalNodes = ids.length if (totalNodes < 3) return; var fromIdx = Math.floor(Math.random() * totalNodes); var toIdx = Math.floor(Math.random() * totalNodes); this._edges.add({ from: ids[fromIdx], to: ids[toIdx] }); }, testOnClick3: function () { this.props.store.dispatch( this.props.storeActions.fetchData( this.props.raportId ) ); }, shouldComponentUpdate: function (nextProps, nextState) { if (this.state.receivedRequestId !== nextState.receivedRequestId) { // add missing nodes var state = this.props.store.getState(); DBG1 && console.warn("TODO: update nodes, edges", { state }); // this._edges.add( edge_or_edges_array ) if (state.nodes && state.nodes.length) { var __nodes = this._nodes; state.nodes.forEach(function (node) { __nodes.add(node); }) } // edge: { from: page, to: subpageID, color:getEdgeColor(level), level: level, selectionWidth:2, hoverWidth:0 } if (state.edges && state.edges.length) { var __edges = this._edges; // state.edges.forEach(function (edge) { state.edges.slice(0, 30).forEach(function (edge) { __edges.add({ from: edge.source, to: edge.target, }); }) } } if (this.props.selected.length != nextProps.selected.length) { var edges = []; // TODO: by nodes this._network.setData({ nodes: nextProps.selected, edges: edges }); } return false; }, render: function () { DBG1 && console.log('DBG:render'); return h('div', {}, [ h('div', { ref: this.setOutputRef, style: { 'min-height': 600, 'height': 600, }, }), h('div', {}, [ h('button', { onClick: this.testOnClick1 }, [ "TEST 1 ", h('small', [], "(+ node)") ]), h('button', { onClick: this.testOnClick2 }, [ "TEST 2 ", h('small', [], "(+ edge)") ]), h('button', { onClick: this.testOnClick3 }, [ "TEST 3 ", h('small', [], "(+ fetch)") ]), ]), ]); } }); var p5UI__PanelNetworkGraph = createReactClass({ getInitialState: function () { return { selected: [], } }, handleSelectFeatures: function (selected) { DBG1 && console.log('DBG:typeahead selected:', { selected }) this.setState({ selected: selected }); }, render: function () { var state = this.props.store.getState() DBG1 && console.log('DBG: state', state); var options = state.nodes || []; return h(React.Fragment, {}, [ h(Typeahead, { labelKey: "label", multiple: true, options: options, placeholder: "Wybierz", bsSize: 'large', onChange: this.handleSelectFeatures, }), h(p5UI__NetworkGraph, { raportId: this.props.raportId, store: this.props.store, storeActions: this.props.storeActions, selected: this.state.selected, }) ]) } }) ReactDOM.render( h(p5UI__PanelNetworkGraph, { raportId: RAPORT_ID, store: createStoreWithThunkMiddleware(networkGraphStore), storeActions: createNetworkGraphStoreActions(), }), document.getElementById(HTML_ID_REF_GRAPH) );