Преглед изворни кода

added flow diagram to RefGraph

Piotr Labudda пре 8 година
родитељ
комит
7c0a5e6614
2 измењених фајлова са 179 додато и 0 уклоњено
  1. 8 0
      SE/se-lib/Route/RefGraph.php
  2. 171 0
      SE/se-lib/Route/RefGraph.php.view.js

+ 8 - 0
SE/se-lib/Route/RefGraph.php

@@ -12,10 +12,17 @@ class Route_RefGraph extends RouteBase {
 		if (!$namespace) throw new Exception("Missing namespace");
 		$primaryKey = V::get('primaryKey', '', $_GET);
 		if (!$primaryKey) throw new Exception("Missing primaryKey");
+		$show_flow_diagram = 1; // (V::get('FLOW', 0, $_GET, 'int') > 0);
 		echo UI::h('div', [ 'id' => "ref_graph_node", 'style' => "width:98%; height:500px; border:1px solid silver; margin:8px auto; padding:0" ], [
 			"Pobieranie danych..."
 		]);
 
+		if ($show_flow_diagram) { // $_GET['FLOW'] = 1
+			echo UI::h('svg', [ 'width' => "200%", 'height' => "500", 'id' => "d3-sankey-test" ]);
+			echo UI::h('script', [ 'src' => "https://d3js.org/d3.v4.js" ]);
+			echo UI::h('script', [ 'src' => "https://unpkg.com/d3-sankey@0.7.1" ]);
+		}
+
 		echo UI::h('script', [ 'src' => "static/visjs/vis.min.js" ]);
 		echo UI::h('style', [ 'type' => "text/css", 'src' => "static/visjs/vis.min.css" ]);
 		UI::inlineJS(__FILE__ . '.view.js', [
@@ -28,6 +35,7 @@ class Route_RefGraph extends RouteBase {
 			// 'WFS_URL' => "https://biuro.biall-net.pl/dev-pl/se-master/wfs-data.php/default_db/",
 			// 'JS_CHANNEL_UPDATE_NAME' => $jsFunction['JS_CHANNEL_UPDATE_NAME'],
 			'DBG' => (V::get('DBG', 0, $_GET, 'int') > 0),
+			'SHOW_FLOW_DIAGRAM' => $show_flow_diagram,
 		]);
 	}
 

+ 171 - 0
SE/se-lib/Route/RefGraph.php.view.js

@@ -6,6 +6,7 @@ if ('undefined' === typeof PRIMARY_KEY) throw "Missing PRIMARY_KEY";
 // if ('undefined' === typeof FUNCTION_FETCH_PARENTS) throw "Missing FUNCTION_FETCH_PARENTS";
 // if ('undefined' === typeof JS_CHANNEL_UPDATE_NAME) throw "Missing JS_CHANNEL_UPDATE_NAME";
 var DBG = DBG || 0;
+var SHOW_FLOW_DIAGRAM = SHOW_FLOW_DIAGRAM || 0;
 
 var _isFullScreen = true; // TODO: get from arg
 var _html = {
@@ -17,6 +18,19 @@ var _html = {
 var _nodes = new vis.DataSet(); // [ { id: '', label: '' }, ... ]
 var _edges = new vis.DataSet(); // [ { id: '', from: '', to: '' }, ... ]
 var _network = null; // graph object
+
+if (SHOW_FLOW_DIAGRAM) {
+	var _sankeyNode = null;
+	var _sankeyLink = null;
+	var sankey = null;
+	var _sankeySvgNode = null;
+	var _sankeySvgWidth = 0;
+	var _sankeyHeight = 0;
+	var _sankeyFormatNumber = d3.format(",.0f");
+	var _sankeyFormat = function(d) { return _sankeyFormatNumber(d) + " TWh"; };
+	var _sankeyColor = d3.scaleOrdinal(d3.schemeCategory10);
+}
+
 var _todoGraphData = [ { nodes: [], edges: [] } ]; // data by levels
 var _defaultWfsParams = {
 	resolve: 'all',
@@ -132,11 +146,37 @@ function dataMakeFetchMoreEdge(parentNodeId, fetchMoreNode) {
 	p5WFS_GetFeature(featureTypeName, wfsParams).then(function (features) {
 		if(DBG)console.log('features', features)
 		updateWfsResponse(features, featureTypeName)
+
+		updateSankeyDiagram()
+
 	}).catch(function (e) {
 		if(DBG)console.warn(e)
 		p5UI__notifyAjaxCallback({ type: 'error', msg: e })
 	})
 
+	if (SHOW_FLOW_DIAGRAM) {
+		_sankeySvgNode = document.getElementById("d3-sankey-test")
+		var d3SvgNode = d3.select(_sankeySvgNode);
+		var svgWidth = d3SvgNode.attr("width")
+		if ('%' === svgWidth.substr(-1)) {
+			svgWidth = parseInt(svgWidth.substr(0, svgWidth.length - 1))
+			svgWidth = (isNaN(svgWidth) || svgWidth > 100 || svgWidth < 0) ? 100 : svgWidth
+			var parentNodeStyle = getComputedStyle(_sankeySvgNode.parentNode)
+			var wrapNodeWidth = _sankeySvgNode.parentNode.clientWidth - parseFloat(parentNodeStyle.paddingLeft || 0) - parseFloat(parentNodeStyle.paddingRight || 0);
+			d3SvgNode.attr("width", (100 === svgWidth)
+				? wrapNodeWidth
+				: Math.floor(wrapNodeWidth * svgWidth / 100)
+			)
+		}
+		_sankeySvgWidth = parseInt(d3SvgNode.attr("width"));
+		_sankeyHeight = parseInt(d3SvgNode.attr("height"));
+
+		sankey = d3.sankey()
+			.nodeWidth(15)
+			.nodePadding(10)
+			.extent([ [ 1, 1 ], [ _sankeySvgWidth - 1, _sankeyHeight - 6 ] ]);
+	}
+
 	renderLayout()
 })();
 
@@ -204,6 +244,7 @@ function updateWfsResponse(features, featureTypeName) {
 			}
 		});
 	}
+
 }
 
 function isP5LinkObject(json) {
@@ -391,6 +432,8 @@ function fetchNodeMoreRefs(selectedNode, featureID) {
 				_edges.add(dataMakeFetchMoreEdge(parentNodeId, nodeObject))
 			}
 
+			updateSankeyDiagram()
+
 			return "Pobrano nowe dane"
 		}).then(function (msg) {
 			p5UI__notifyAjaxCallback({ type: 'info', msg: msg })
@@ -431,6 +474,9 @@ function fetchNodeRecurse(selectedNode, featureID) {
 			})
 			if (!refFields.length) return "Brak danych"
 			updateWfsResponse(features, featureTypeName)
+
+			updateSankeyDiagram()
+
 			return "Pobrano nowe dane"
 		}).then(function (msg) {
 			p5UI__notifyAjaxCallback({ type: 'info', msg: msg })
@@ -532,6 +578,131 @@ function renderSelectedNode(node) {
 	_html['selected'].innerHTML = '' + node.id + ' ' + gotToTableLink + ' ' + editLink;
 }
 
+function updateSankeyDiagram() {
+	console.log('updateSankeyDiagram..');
+	if (!SHOW_FLOW_DIAGRAM || !sankey) return;
+	// var sankeyData = { // example
+	// 	nodes: [
+	// 		/*  0 */ { name: "A" },
+	// 		/*  1 */ { name: "B" },
+	// 		/*  2 */ { name: "C" },
+	// 		/*  3 */ { name: "D" },
+	// 		/*  4 */ { name: "E" },
+	// 		/*  5 */ { name: "F" },
+	// 	],
+	// 	links: [
+	// 		{ "source": 0, "target": 1, "value": 100 },
+	// 		{ "source": 1, "target": 2, "value": 100 },
+	// 		{ "source": 2, "target": 3, "value": 100 },
+	// 		{ "source": 3, "target": 4, "value": 100 },
+	// 		{ "source": 5, "target": 2, "value": 100 },
+	// 		{ "source": 0, "target": 4, "value": 100 },
+	// 	]
+	// }
+
+	p5Utils__clearNode(_sankeySvgNode)
+	var d3SvgNode = d3.select(_sankeySvgNode);
+	_sankeyLink = d3SvgNode.append("g")
+			.attr("class", "links")
+			.attr("fill", "none")
+			.attr("stroke", "#000")
+			.attr("stroke-opacity", 0.2)
+		.selectAll("path");
+
+	_sankeyNode = d3SvgNode.append("g")
+			.attr("class", "nodes")
+			.attr("font-family", "sans-serif")
+			.attr("font-size", 10)
+		.selectAll("g");
+
+	// var sankeyInstance = sankey(sankeyData);
+	var mapVisNodeIdToIdx = _nodes.getIds().reduce(function (ret, id, idx) {
+		ret[id] = idx;
+		return ret;
+	}, {});
+	var countOutLinks = _edges.get().reduce(function (ret, edge) {
+		if (!mapVisNodeIdToIdx.hasOwnProperty(edge.from)) throw "Missing to in mapVisNodeIdToIdx["+edge.from+"]"
+		var idxTarget = mapVisNodeIdToIdx[edge.from]
+		ret[idxTarget] = (ret[idxTarget] > 0) ? ret[idxTarget] + 1 : 1
+		console.log('DBG:sankey: countOutLinks reduce', edge, ret);
+		return ret
+	}, {})
+	console.log('DBG:sankey: mapVisNodeIdToIdx', mapVisNodeIdToIdx);
+	console.log('DBG:sankey: countOutLinks', countOutLinks);
+	var sankeyInstance = sankey({
+		nodes: _nodes.map(function (node) {
+			return {
+				name: node.label
+			}
+		}),
+		links: _edges.map(function (edge) {
+			if (!mapVisNodeIdToIdx.hasOwnProperty(edge.from)) throw "Missing from in mapVisNodeIdToIdx["+edge.from+"]"
+			if (!mapVisNodeIdToIdx.hasOwnProperty(edge.to)) throw "Missing to in mapVisNodeIdToIdx["+edge.to+"]"
+			var idxTarget = mapVisNodeIdToIdx[edge.to]
+			return {
+				source: mapVisNodeIdToIdx[edge.from],
+				target: idxTarget,
+				value: (countOutLinks[idxTarget] || 1) * 10
+			}
+		}),
+	});
+	// var sankeyInstance = sankey()
+	// .nodes(
+	// 	_nodes.map(function (node) {
+	// 		return {
+	// 			name: node.label
+	// 		}
+	// 	})
+	// )
+	// .edges(
+	// 	_edges.map(function (edge) {
+	// 		if (!mapVisNodeIdToIdx.hasOwnProperty(edge.from)) throw "Missing from in mapVisNodeIdToIdx["+edge.from+"]"
+	// 		if (!mapVisNodeIdToIdx.hasOwnProperty(edge.to)) throw "Missing to in mapVisNodeIdToIdx["+edge.to+"]"
+	// 		var idxTarget = mapVisNodeIdToIdx[edge.to]
+	// 		return {
+	// 			source: mapVisNodeIdToIdx[edge.from],
+	// 			target: idxTarget,
+	// 			value: (countOutLinks[idxTarget] || 1) * 10
+	// 		}
+	// 	})
+	// )
+	// .layout(32);
+
+	_sankeyLink = _sankeyLink
+		.data(sankeyInstance.links)
+		.enter().append("path")
+			.attr("d", d3.sankeyLinkHorizontal())
+			.attr("stroke-width", function(d) { return Math.max(1, d.width); });
+
+	_sankeyLink.append("title")
+		.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + _sankeyFormat(d.value); });
+
+	_sankeyNode = _sankeyNode
+		.data(sankeyInstance.nodes)
+		.enter().append("g");
+
+	_sankeyNode.append("rect")
+		.attr("x", function(d) { return d.x0; })
+		.attr("y", function(d) { return d.y0; })
+		.attr("height", function(d) { return d.y1 - d.y0; })
+		.attr("width", function(d) { return d.x1 - d.x0; })
+		.attr("fill", function(d) { return _sankeyColor(d.name.replace(/ .*/, "")); })
+		.attr("stroke", "#000");
+
+	_sankeyNode.append("text")
+		.attr("x", function(d) { return d.x0 - 6; })
+		.attr("y", function(d) { return (d.y1 + d.y0) / 2; })
+		.attr("dy", "0.35em")
+		.attr("text-anchor", "end")
+		.text(function(d) { return d.name; })
+		.filter(function(d) { return d.x0 < _sankeySvgWidth / 2; })
+		.attr("x", function(d) { return d.x1 + 6; })
+		.attr("text-anchor", "start");
+
+	_sankeyNode.append("title")
+		.text(function(d) { return d.name + "\n" + _sankeyFormat(d.value); });
+}
+
 // global['FUNCTION_FETCH_CHILDRENS'] = 'refGraphFetchChildrens'
 // global['FUNCTION_FETCH_PARENTS'] = 'refGraphFetchParents'
 // global['_nodes'] = _nodes // TODO: DBG