Piotr Labudda 7 yıl önce
ebeveyn
işleme
70e184f36d

+ 33 - 10
SE/se-lib/Route/UrlAction/BiAuditGraph.php

@@ -121,6 +121,31 @@ class Route_UrlAction_BiAuditGraph extends RouteBase {
 		$listObjectsNs = $this->_getListPointChildNs();
 		$listPointChildAlias = $this->_getListPointChildAlias();
 
+		$getPointFeatureIdFromPath = function ($item) {
+			$listPointChildAlias = $this->_getListPointChildAlias();
+			return array_reduce($listPointChildAlias, function ($ret, $objAlias) use ($item) {
+				$pk = V::get("pk_{$objAlias}", 0, $item);
+				if ($pk) $ret = "{$objAlias}.{$pk}";
+				return $ret;
+			}, null);
+		};
+
+		$lastPkPath = 0;
+		$lastPos = 0;
+		$fidMatrix = array_map(function ($item) use ($getPointFeatureIdFromPath, &$lastPkPath, &$lastPos) {
+			if ($lastPkPath != $item['pk_Path']) {
+				$lastPos = 0;
+			}
+			$lastPos += 1;
+			$lastPkPath = $item['pk_Path'];
+			return [
+				'pk_Path' => $item['pk_Path'],
+				'pos' => $lastPos,
+				'fid' => $getPointFeatureIdFromPath($item),
+			];
+		}, $matrix);
+		DBG::log($fidMatrix, 'array', '$fidMatrix');
+
 		$listPathLength = array_reduce($matrix, function ($ret, $item) {
 			$ret[ $item['pk_Path'] ] = 1 + V::get($item['pk_Path'], 0, $ret);
 			return $ret;
@@ -139,14 +164,6 @@ class Route_UrlAction_BiAuditGraph extends RouteBase {
 		asort($pointOccurs);
 		DBG::log($pointOccurs, 'array', '$pointOccurs');
 
-		$getPointFeatureIdFromPath = function ($item) {
-			$listPointChildAlias = $this->_getListPointChildAlias();
-			return array_reduce($listPointChildAlias, function ($ret, $objAlias) use ($item) {
-				$pk = V::get("pk_{$objAlias}", 0, $item);
-				if ($pk) $ret = "{$objAlias}.{$pk}";
-				return $ret;
-			}, null);
-		};
 		$prevItem = [];
 		$edges = array_reduce($matrix, function ($ret, $item) use (&$prevItem, $getPointFeatureIdFromPath) {
 			if (!empty($prevItem) && $prevItem['pk_Path'] === $item['pk_Path']) {
@@ -187,6 +204,12 @@ class Route_UrlAction_BiAuditGraph extends RouteBase {
 		$stats['edges_all_to_all'] = $allToAllEdges;
 		$stats['edges'] = $edges;
 		$stats['nodes'] = array_map([ $this, 'getFeature' ], array_keys($pointOccurs));
+		$stats['path_list'] = array_values(array_reduce($fidMatrix, function ($ret, $item) {
+			$pkPath = $item['pk_Path'];
+			if (!array_key_exists($pkPath, $ret)) $ret[ $pkPath ] = [ 'pkPath' => $pkPath, 'fids' => [] ];
+			$ret[ $pkPath ][ 'fids' ][] = $item['fid'];
+			return $ret;
+		}, []));
 		return $stats;
 	}
 
@@ -278,7 +301,6 @@ class Route_UrlAction_BiAuditGraph extends RouteBase {
 		echo $h('div', [ 'id' => "bi_audit_raport-network_graph" ], "TODO: loading data...");
 		echo $h('script', ['src'=>"static/vendor.js?v=a76e2988", 'type'=>"text/javascript"]);
 		echo $h('script', [ 'src' => "static/visjs/vis.min.js" ]);
-		$stats = $this->getOutputStats($raportId);
 		echo UI::h('link', [ 'href' => 'static/react-bootstrap-typeahead.min.css', 'type' => 'text/css', 'rel' => 'stylesheet' ]);
 		UI::inlineJS(__FILE__ . '.network-graph.js', [
 			'HTML_ID_REF_GRAPH' => 'bi_audit_raport-network_graph',
@@ -287,7 +309,8 @@ class Route_UrlAction_BiAuditGraph extends RouteBase {
 			// 'WFS_URL' => Router::getRoute('WfsBiAudit')->getLink(),
 			'RAPORT_ID' => $raportId,
 			'API_URL' => $this->getLink('raportOutputStats', [ 'outputFormat' => "json", 'raportId' => $raportId ]),
-			'STATS' => $stats,
+			'STATS' => $this->getOutputStats($raportId),
+			'DATA_MATRIX' => $this->getRaportOutputMatrix($raportId),
 			'DBG' => (V::get('DBG', 0, $_GET, 'int') > 0),
 		]);
 

+ 264 - 46
SE/se-lib/Route/UrlAction/BiAuditGraph.php.network-graph.js

@@ -1,5 +1,6 @@
 var DBG = DBG || false;
 var DBG1 = true;
+var DBG_INIT_SELECTED = [ "BI_audit_ENERGA_RUM_KONTRAHENCI.18661", "BI_audit_KRS.389967" ];
 
 if ('undefined' === typeof HTML_ID_REF_GRAPH) throw "Missing HTML_ID_REF_GRAPH";
 if (!RAPORT_ID) throw "Missing RAPORT_ID";
@@ -24,23 +25,71 @@ var mapStatsNodeToGraphNode = function (node) {
 		value: 1,
 		level: 0,
 	}
-}
+};
+var mapStatsEdgeToGraphEdge = function (edge) {
+	return {
+		id: Utils.makeUniqueEdgeId(edge['source'], edge['target']),
+		from: edge['source'],
+		to: edge['target'],
+		pathId: edge['pathId'],
+		listPathId: [ edge['pathId'] ],
+	}
+};
+
+//// DBG: Utils:
+// var from, to;
+// from = 'aa.11'; to = 'bb.22'; console.log('DBG: Utils.makeUniqueEdgeId('+from+', '+to+')', Utils.makeUniqueEdgeId(from, to), { cache: Utils.getCacheAllEdgeId().join(',') })
+// from = 'bb.22'; to = 'aa.11'; console.log('DBG: Utils.makeUniqueEdgeId('+from+', '+to+')', Utils.makeUniqueEdgeId(from, to), { cache: Utils.getCacheAllEdgeId().join(',') })
+// from = 'cc.22'; to = 'cc.11'; console.log('DBG: Utils.makeUniqueEdgeId('+from+', '+to+')', Utils.makeUniqueEdgeId(from, to), { cache: Utils.getCacheAllEdgeId().join(',') })
+var Utils = (function __makeUtils() {
+	var Utils = {};
+	var _cacheListFeatureNames = [];
+
+	Utils.fidSplit = function (fid) {
+		var dotPos = fid.indexOf('.');
+		return [ fid.substr(0, dotPos), fid.substr(dotPos + 1) ];
+	}
+
+	Utils.makeUniqueEdgeId = function (from, to) {
+		var listFidArr = [ from, to ].sort().map(Utils.fidSplit);
+		var listIdx = listFidArr.map(function (fidArr) {
+			var cacheIdx = _cacheListFeatureNames.indexOf(fidArr[0]);
+			if (-1 === cacheIdx) {
+				cacheIdx = _cacheListFeatureNames.length;
+				_cacheListFeatureNames.push(fidArr[0]);
+			}
+			return cacheIdx;
+		})
+		return listIdx.map(function (cacheIdx, fidIdx) {
+			return cacheIdx + '.' + listFidArr[fidIdx][1];
+		}).join('-')
+	}
+
+	Utils.getCacheAllEdgeId = function () {
+		return [].concat(_cacheListFeatureNames);
+	}
+
+	return Utils;
+})();
 
-{ // TODO: mv to another file
-	function getinitialState() {
+{ // TODO: RaportOutputPanel - mv to another file
+	var RaportOutputPanel = {};
+	RaportOutputPanel.initialState = function () {
 		var nodes = (STATS && STATS.nodes) ? STATS.nodes : [];
 		var edges = (STATS && STATS.edges) ? STATS.edges : [];
+		var paths = (STATS && STATS.path_list) ? STATS.path_list : [];
 		DBG1 && console.log('DBG: STATS', STATS);
 		return {
 			nodes: nodes.map(mapStatsNodeToGraphNode),
-			edges: edges,
+			edges: edges.map(mapStatsEdgeToGraphEdge),
+			paths: paths,
 			isLoading: false,
 			sentRequestId: 0,
 			receivedRequestId: 0,
 		}
-	}
-	function networkGraphStore(state, action) {
-		var prevState = state || getinitialState();
+	};
+	RaportOutputPanel.store = function (state, action) {
+		var prevState = state || RaportOutputPanel.initialState();
 		DBG1 && console.log('DBG: store', { prevState, action, actionType: action.type });
 		switch (action.type) {
 			case 'SET_SENT_REQUEST_ID': return Object.assign(prevState, {
@@ -55,9 +104,90 @@ var mapStatsNodeToGraphNode = function (node) {
 			});
 			default: return prevState;
 		}
-	}
+	};
+	RaportOutputPanel.createActions = function () {
+		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: skipped 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,
+		}
+	};
+}
 
-	function createNetworkGraphStoreActions() {
+{ // TODO: NetworkGraph - mv to another file
+	var NetworkGraph = {};
+	NetworkGraph.initialState = function () {
+		var nodes = (STATS && STATS.nodes) ? STATS.nodes : [];
+		var edges = (STATS && STATS.edges) ? STATS.edges : [];
+		var paths = (STATS && STATS.path_list) ? STATS.path_list : [];
+		DBG1 && console.log('DBG: STATS', STATS);
+		return {
+			nodes: nodes.map(mapStatsNodeToGraphNode),
+			edges: edges.map(mapStatsEdgeToGraphEdge),
+			paths: paths,
+			isLoading: false,
+			sentRequestId: 0,
+			receivedRequestId: 0,
+		}
+	};
+	NetworkGraph.store = function (state, action) {
+		var prevState = state || NetworkGraph.initialState();
+		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;
+		}
+	};
+	NetworkGraph.createActions = function () {
 		var setSentRequestId = function (sentRequestId) {
 			return { type: 'SET_SENT_REQUEST_ID', sentRequestId: sentRequestId };
 		}
@@ -82,7 +212,7 @@ var mapStatsNodeToGraphNode = function (node) {
 					var state = getState();
 
 					if (state.receivedRequestId > this__requestId) {
-						DBG1 && console.log('DBG: skiped response', { 'state.receivedRequestId': state.receivedRequestId, this__requestId });
+						DBG1 && console.log('DBG: skipped response', { 'state.receivedRequestId': state.receivedRequestId, this__requestId });
 						return;
 					}
 
@@ -103,16 +233,16 @@ var mapStatsNodeToGraphNode = function (node) {
 		return {
 			fetchData: fetchData,
 		}
-	}
+	};
 }
 
-var defaultOptions = {
+var defaultNetworkGraphOptions = {
 	nodes: {
 		shape: 'dot',
 		scaling: {
-			min: 20, max: 30,
+			min: 8, max: 30,
 			label: {
-				min: 8, max: 16, drawThreshold: 9, maxVisible: 20
+				min: 8, max: 16, drawThreshold: 6, maxVisible: 20
 			}
 		},
 		font: { color: "#666", size: 10, face: 'Helvetica Neue, Helvetica, Arial' }
@@ -129,6 +259,8 @@ var TMP_COUNTER = 1;
 var p5UI__NetworkGraph = createReactClass({
 	_network: null,
 	_visOutputRef: React.createRef(),
+	_allNodes: null, // new vis.DataSet(),
+	_allEdges: null, // new vis.DataSet(),
 	_nodes: new vis.DataSet(),
 	_edges: new vis.DataSet(),
 	getStateFromStore: function () {
@@ -139,6 +271,7 @@ var p5UI__NetworkGraph = createReactClass({
 		}
 	},
 	getInitialState: function () {
+		if (this.props.selected) this._updateSelected(this.props.selected);
 		return {
 			initialized: false,
 			isLoading: false,
@@ -150,7 +283,7 @@ var p5UI__NetworkGraph = createReactClass({
 	},
 	componentDidMount: function () {
 		var data = { nodes: this._nodes, edges: this._edges };
-		this._network = new vis.Network(this._visOutputRef, data, defaultOptions);
+		this._network = new vis.Network(this._visOutputRef, data, defaultNetworkGraphOptions);
 	  // bindNetwork();
 	  this.setState({ initialized: true });
 		this._unsubscribe = this.props.store.subscribe(this._storeUpdated)
@@ -177,35 +310,108 @@ var p5UI__NetworkGraph = createReactClass({
 	},
 
 	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.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 });
+			this._updateSelected(nextProps.selected)
 		}
 		return false;
 	},
+	_updateSelected: function (selected) { // TODO: move to RaportOutputPanel Store
+		var state = this.props.store.getState();
+		var idsSelected = (selected || []).map( function (node) { return node.id } )
+
+		var nodes = [];
+		// var edges = state.edges.filter(function (edge) { // use idsSelected - TODO: RMME
+		// 	return ( -1 !== idsSelected.indexOf(edge.source) || -1 !== idsSelected.indexOf(edge.target) );
+		// })
+		{
+			// TODO: find all paths with selected fids
+			var foundPaths = state.paths.filter(function (path) {
+				for (var i = 0, totalFids = path.fids.length, fid = null; i < totalFids; i++) {
+					fid = path.fids[i];
+					DBG && console.log('DBG:foundPaths ', {fid: ''+fid, path});
+					if ( -1 !== idsSelected.indexOf(fid) ) return true;
+				}
+				return false;
+			})
+			// TODO: add missing nodes (not selected but on path)
+			var addFidsToGraph = Array.from(new Set(foundPaths.reduce(function (ret, path) {
+				return ret.concat(path.fids.filter(function (fid) {
+					return ( -1 === idsSelected.indexOf(fid) );
+				}))
+			}, [])));
+			// TODO: view only combined path or all not selected nodes? - trying: not selected nodes
+			// TODO: convert found paths to graph edges
+		}
+		DBG1 && console.warn('DBG: TODO set edges by selectd nodes...', { state_nodes: state.nodes, state_edges: state.edges, idsSelected, foundPaths, addFidsToGraph });
+		this._nodes = new vis.DataSet(selected);
+		{ // TODO: convert fid to node on found list
+			{ // if (!this._allNodes) {
+				this._allNodes = new vis.DataSet();
+				this._allNodes.add(state.nodes);
+			}
+
+			var this__allNodes = this._allNodes;
+			var __nodes = this._nodes;
+			var addNodesToGraph = addFidsToGraph.map(function (fid) {
+				var node = this__allNodes.get(fid)
+				try {
+					DBG1 && console.log('DBG: TODO add node (fid:'+fid+') ', { node, this__allNodes, fid });
+					__nodes.add(node);
+					__nodes.update(Object.assign(node, { color: '#ddd', size: 100 }));
+					DBG1 && console.log('DBG: added red node (fid:'+fid+') ', { node });
+				} catch (e) {
+					DBG1 && console.log(e);
+					DBG1 && console.log('DBG: skip node (fid:'+fid+') already added');
+				}
+			})
+		}
+		DBG1 && console.log('DBG: check edges: ', { 'this._edges.length': this._edges.length, 'state.edges.length': state.edges.length });
+		if (this._edges.length != state.edges.length) {
+			DBG1 && console.log('DBG: TODO: update edges', { 'state.edges': state.edges });
+
+			var this__edges = this._edges;
+			state.edges.forEach(function (edge) {
+				var foundEdge = this__edges.get(edge.id);
+				if (!foundEdge) {
+					this__edges.add(edge);
+				} else {
+					var listPathId = (-1 !== foundEdge.listPathId.indexOf(edge.pathId)) ? foundEdge.listPathId : foundEdge.listPathId.concat(edge.pathId);
+					var mergedEdge = Object.assign(foundEdge, {
+						listPathId: listPathId,
+						width: 1 + (listPathId.length - 1) / 10,
+					});
+					this__edges.update(mergedEdge);
+				}
+			})
+		}
+		if (this._network) {
+			this._network.setData({ nodes: this._nodes, edges: this._edges });
+			this._network.fit() // zoom out to fit container size
+		}
+	},
 	render: function () {
 		DBG1 && console.log('DBG:render');
 		return h('div', {}, [
@@ -225,10 +431,21 @@ var p5UI__NetworkGraph = createReactClass({
 	}
 });
 
-var p5UI__PanelNetworkGraph = createReactClass({
+/**
+ * props.store: networkGraphStore
+ * - used: state.nodes
+ */
+var p5UI__RaportOutputPanel = createReactClass({
 	getInitialState: function () {
+		var initSelected = [];
+		if (DBG_INIT_SELECTED) {
+			var state = this.props.store.getState()
+			initSelected = state.nodes.filter(function (node) {
+				return ( -1 !== DBG_INIT_SELECTED.indexOf(node.id));
+			})
+		}
 		return {
-			selected: [],
+			selected: initSelected,
 		}
 	},
 	handleSelectFeatures: function (selected) {
@@ -238,14 +455,15 @@ var p5UI__PanelNetworkGraph = createReactClass({
 	render: function () {
 		var state = this.props.store.getState()
 		DBG1 && console.log('DBG: state', state);
-		var options = state.nodes || [];
+		var nodes = state.nodes || [];
 		return h(React.Fragment, {}, [
 			h(Typeahead, {
 				labelKey: "label",
 				multiple: true,
-				options: options,
+				options: nodes,
 				placeholder: "Wybierz",
 				bsSize: 'large',
+				selected: this.state.selected,
 				onChange: this.handleSelectFeatures,
 			}),
 			h(p5UI__NetworkGraph, {
@@ -259,10 +477,10 @@ var p5UI__PanelNetworkGraph = createReactClass({
 })
 
 ReactDOM.render(
-	h(p5UI__PanelNetworkGraph, {
+	h(p5UI__RaportOutputPanel, {
 		raportId: RAPORT_ID,
-		store: createStoreWithThunkMiddleware(networkGraphStore),
-		storeActions: createNetworkGraphStoreActions(),
+		store: createStoreWithThunkMiddleware(NetworkGraph.store),
+		storeActions: NetworkGraph.createActions(),
 	}),
 	document.getElementById(HTML_ID_REF_GRAPH)
 );