Spaces:
Build error
Build error
| () => { | |
| var script = document.createElement("script"); | |
| script.src = "https://visjs.github.io/vis-network/standalone/umd/vis-network.min.js"; | |
| document.head.appendChild(script); | |
| // var current_options; | |
| // var node_pass_prob, prelen, max_path, node_tokens, node_probs, links, tarlen; | |
| // var visible_nodes, map_max_path; | |
| // var visible_edges, sorted_links_out, sorted_links_in; | |
| // var nodes, edges, network; | |
| // var disable_edge_select; | |
| showing_node = undefined; | |
| showing_edge = undefined; | |
| function obtain_node_object(node_id) { | |
| var title = "Node " + node_id.toString() + " (Pass Prob = " + node_pass_prob[node_id].toFixed(2) + ")\n"; | |
| title += "==========\n" | |
| for(var j=0;j<5;j++){ | |
| if(node_probs[node_id][j]> 0.01){ | |
| title += node_tokens[node_id][j] + "\t" + node_probs[node_id][j].toFixed(2) + "\n"; | |
| } | |
| } | |
| var currentNode = {"id": node_id, "label": node_tokens[node_id][0], "value":node_pass_prob[node_id], "title": title}; | |
| if (current_options.show_node_detail){ | |
| currentNode.label = title; | |
| } | |
| if(map_max_path[node_id] !== undefined){ | |
| currentNode.x = map_max_path[node_id].x; | |
| currentNode.y = map_max_path[node_id].y; | |
| currentNode.fixed = true; | |
| currentNode.mass = 5; | |
| currentNode.color = {border: "red", background: "orange", highlight: {border: "red", background: "#ffcc66"}}; | |
| } | |
| return currentNode; | |
| } | |
| function set_node_visibility(node_id, flag){ | |
| if(visible_nodes[node_id] == flag) return; | |
| visible_nodes[node_id] = flag; | |
| if(flag){ | |
| nodes.add(obtain_node_object(node_id)); | |
| }else{ | |
| nodes.remove(node_id); | |
| } | |
| } | |
| function update_visible_nodes(clear_state=false){ | |
| if(typeof visible_nodes === "undefined" || clear_state){ | |
| visible_nodes = [...Array(prelen)].map((_, __) => false); // size: [prelen] | |
| map_max_path = {}; // {node_id -> position on max_path} | |
| var accumulated_x = 0; | |
| for (var i=0;i<tarlen;i++){ | |
| accumulated_x += node_tokens[max_path[i]][0].length * 5 | |
| var y = Math.floor(Math.random() * 3) * 100 - 100; | |
| map_max_path[max_path[i]] = {position: i, x: accumulated_x, y:y}; | |
| accumulated_x += node_tokens[max_path[i]][0].length * 5 + 100; | |
| } | |
| nodes = new vis.DataSet(); | |
| } | |
| for (var i=0;i<prelen;i++){ | |
| if(node_pass_prob[i] >= current_options.minimum_node_pass_prob || map_max_path[i] !== undefined){ | |
| set_node_visibility(i, true); | |
| }else{ | |
| set_node_visibility(i, false); | |
| } | |
| } | |
| } | |
| function update_node_details(){ | |
| for(var i=0;i<prelen;i++) if (visible_nodes[i]){ | |
| currentNode = obtain_node_object(i); | |
| nodes.updateOnly(currentNode); | |
| } | |
| } | |
| function obtain_edge_object(i, j){ | |
| var edge_id = i.toString() + "-" + j.toString(); | |
| var label = links[i][j].toFixed(2); | |
| var pass_label = (node_pass_prob[i] * links[i][j]).toFixed(2); | |
| var title = "From Node " + i.toString() + " to Node " + j.toString() + "\n" + "Transition Probability:" + label + "\nPassing Probability:" + pass_label; | |
| var currentEdge = {id: edge_id, | |
| from: i, to: j, value: links[i][j] * node_pass_prob[i], title: title}; | |
| if (map_max_path[i] !== undefined && map_max_path[j] !== undefined && map_max_path[i].position + 1 == map_max_path[j].position){ | |
| currentEdge.color = "red"; | |
| } | |
| if(current_options.show_edge_label){ | |
| currentEdge.label = label; | |
| }else{ | |
| currentEdge.label = " "; | |
| } | |
| return currentEdge; | |
| } | |
| function set_edge_visibility(i, j, flag){ | |
| if(visible_edges[i][j] == flag) return; | |
| visible_edges[i][j] = flag; | |
| if(flag){ | |
| edges.add(obtain_edge_object(i, j)); | |
| }else{ | |
| var edge_id = i.toString() + "-" + j.toString(); | |
| edges.remove(edge_id); | |
| } | |
| } | |
| function update_visible_edges(clear_state=false){ | |
| if(typeof visible_edges === "undefined" || clear_state){ | |
| visible_edges = [...Array(prelen)].map((_, __) => {return [...Array(prelen)].map((_, __) => false);}); // size: [prelen, prelen] | |
| sorted_links_out = []; // size: [prelen, prelen] | |
| for (var i=0;i<prelen;i++){ | |
| // sort the out edge according to their prob | |
| sorted_links_out.push(links[i].map((val, idx) => {return {'idx': idx, 'val': val};}). | |
| sort((v1, v2) => v2.val - v1.val) | |
| ); | |
| } | |
| sorted_links_in = []; // size: [prelen, prelen], element {idx, val}, from big to small | |
| for (var i=0;i<prelen;i++){ | |
| links_in = [] | |
| for(var j=0;j<prelen;j++) links_in.push({idx: j, val: links[j][i] * node_pass_prob[j]}) | |
| sorted_links_in.push(links_in.sort((v1, v2) => v2.val - v1.val)); | |
| } | |
| edges = new vis.DataSet(); | |
| } | |
| var next_visible_edges = [...Array(prelen)].map((_, __) => {return [...Array(prelen)].map((_, __) => false);}); // size: [prelen, prelen] | |
| var links_in_num = [...Array(prelen)].map((_, __) => 0); | |
| for (var i=0;i<prelen - 1;i++){ | |
| if(!visible_nodes[i]) continue; | |
| // select visible out edge of node i | |
| var left_visible_edge_num = current_options.max_out_edge_num; | |
| var left_visible_edge_prob = current_options.max_out_edge_prob; | |
| for(var j=0; j<prelen;j++){ | |
| var idx = sorted_links_out[i][j]['idx']; | |
| if (sorted_links_out[i][j]['val'] < current_options.minimum_edge_prob) break; | |
| if (visible_nodes[idx]){ | |
| links_in_num[idx]++; | |
| next_visible_edges[i][idx] = true; | |
| left_visible_edge_num--; | |
| left_visible_edge_prob -= sorted_links_out[i][j]['val']; | |
| if (left_visible_edge_num==0 || left_visible_edge_prob < 0){ | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| if(current_options.force_in_edge){ | |
| // add at least one in edge per node | |
| for (var i=0;i<prelen;i++){ | |
| if(i == 0 || !visible_nodes[i] || links_in_num[i] > 0) continue; | |
| for(var j=0; j<prelen;j++){ | |
| var idx = sorted_links_in[i][j]['idx']; | |
| if (visible_nodes[idx]){ | |
| next_visible_edges[idx][i] = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| for(var i=0;i<prelen;i++){ | |
| for(var j=i+1;j<prelen;j++){ | |
| set_edge_visibility(i, j, next_visible_edges[i][j]); | |
| } | |
| } | |
| } | |
| function update_edge_label(){ | |
| for(var i=0;i<prelen;i++){ | |
| for(var j=i+1;j<prelen;j++) if (visible_edges[i][j]){ | |
| currentEdge = obtain_edge_object(i, j); | |
| edges.updateOnly(currentEdge); | |
| } | |
| } | |
| } | |
| function customScalingFunction(min,max,total,value) { | |
| min = 0; | |
| var scale = 1 / (max - min); | |
| return Math.max(0,(value - min)*scale); | |
| } | |
| function escapeHtml(unsafe) | |
| { | |
| return unsafe | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """) | |
| .replace(/'/g, "'"); | |
| } | |
| function get_jumpable_node(idx){ | |
| if(visible_nodes[idx]){ | |
| return "<a href=\"javascript:network.selectNodes(["+ idx.toString() +"]);show_hint_node(" + idx.toString() + ");\">" + idx.toString() + "</a>"; | |
| }else{ | |
| return "<a class=\"invisible\" href=\"javascript:show_hint_node(["+ idx.toString() +"]);network.unselectAll();\">" + idx.toString() + "</a>"; | |
| } | |
| } | |
| function get_jumpable_edge(i, j, label){ | |
| var edge_id = i.toString() + "-" + j.toString(); | |
| if(visible_edges[i][j]){ | |
| return "<a href=\"javascript:network.selectEdges(['"+ edge_id +"']);show_hint_edge('" + edge_id + "');\">" + label + "</a>"; | |
| }else{ | |
| return "<a class=\"invisible\" href=\"javascript:show_hint_edge('" + edge_id + "');network.unselectAll();\">" + label + "</a>"; | |
| } | |
| } | |
| show_hint_node = function(node_id){ | |
| showing_node = node_id; | |
| showing_edge = undefined; | |
| var title = "<p>You selected <b>Node " + node_id.toString() + "</b> "; | |
| if (visible_nodes[node_id]){ | |
| title += "(<a href=\"javascript:network.fit({nodes:[" + node_id.toString() + "],animation:true});\">Find Me!</a>). " | |
| }else{ | |
| title += "(Not shown). " | |
| } | |
| title += "Passing Probability: <b>" + node_pass_prob[node_id].toFixed(2) + "</b>. You can click the links below to jump to other edges or nodes.</p>"; | |
| document.getElementById("hintsupper").innerHTML = title; | |
| title = "<table><thead><tr><th>Rank</th><th>Candidate</th><th>Probability</th></tr></thead><tbody>"; | |
| for (var j=0;j<5;j++){ | |
| title += "<tr><td>#" + (j+1).toString() + "</td><td>" + escapeHtml(node_tokens[node_id][j]) + "</td><td>" + node_probs[node_id][j].toFixed(2) + "</td></tr>"; | |
| } | |
| title += "</tbody>" | |
| title += "<p>Top-5 Token Candidates: </p>"; | |
| document.getElementById("hintsleft").innerHTML = title; | |
| title = "<table><thead><tr><th>Rank</th><th>To</th><th>Transition Prob.</th><th>Passing Prob.</th></tr></thead><tbody>"; | |
| for (var j=0;j<prelen;j++){ | |
| var idx = sorted_links_out[node_id][j].idx; | |
| if(j < 5 || visible_edges[node_id][idx]){ | |
| title += "<tr><td>" + get_jumpable_edge(node_id, idx, "#" + (j+1).toString()) + "</td><td>" + get_jumpable_node(idx) + "</td><td>" + links[node_id][idx].toFixed(2) + "</td><td>" + | |
| (node_pass_prob[node_id] * links[node_id][idx]).toFixed(2) + "</td></tr>"; | |
| } | |
| } | |
| title += "</tbody>" | |
| title += "<p>Top Outgoing Edges: </p>" | |
| document.getElementById("hintscenter").innerHTML = title; | |
| title = "<table><thead><tr><th>Rank</th><th>From</th><th>Transition Prob.</th><th>Passing Prob.</th></tr></thead><tbody>"; | |
| for (var j=0;j<prelen;j++){ | |
| var idx = sorted_links_in[node_id][j].idx; | |
| if(j < 5 || visible_edges[idx][node_id]){ | |
| title += "<tr><td>" + get_jumpable_edge(idx, node_id, "#" + (j+1).toString()) + "</td><td>" + get_jumpable_node(idx) + "</td><td>" + links[idx][node_id].toFixed(2) + "</td><td>" + | |
| (node_pass_prob[idx] * links[idx][node_id]).toFixed(2) + "</td></tr>"; | |
| } | |
| } | |
| title += "</tbody>" | |
| title += "<p>Top Incoming Edges: </p>" | |
| document.getElementById("hintsright").innerHTML = title; | |
| document.getElementById("hintsbottom").innerHTML = | |
| "<br>"+ | |
| "Passing probability of a node V represents how likely the node will be choosen in a random path, i.e., P(V \\in A). <br>" + | |
| "Passing probability of an edge from U to V represents how likely the node V follows the node U in a random path, i.e., P(a_i = U && a_{i+1} = V). <br>" + | |
| "Token probability represents how likely a token is predicted on the given node, i.e., P(y_i| v_{a_{i}}). <br>" + | |
| "Transition probability represents how likely a specific node is following the given node, i.e. P(a_{i+1} | a_{i}).<br>" | |
| } | |
| show_hint_edge = function(edge_id){ | |
| showing_edge = edge_id; | |
| showing_node = undefined; | |
| var i = parseInt(edge_id.split("-")[0]); | |
| var j = parseInt(edge_id.split("-")[1]); | |
| var label = links[i][j].toFixed(2); | |
| var passing_label = (links[i][j] * node_pass_prob[i]).toFixed(2); | |
| var title = "You selected an edge from <b>Node " + get_jumpable_node(i) + " to Node " + get_jumpable_node(j) + "</b>." | |
| if (visible_edges[i][j]){ | |
| title += "(<a href=\"javascript:network.fit({nodes:[" + i.toString() + "," + j.toString() + "],animation:true});\">Find Me!</a>). " | |
| }else{ | |
| title += "(Not shown). " | |
| } | |
| title += "<br> You can click the links above to jump to the nodes. <br><br>" | |
| title += "Transition Probability:<b>" + label + "</b><br>"; | |
| title += "Passing Probability:<b>" + passing_label + "</b><br>"; | |
| title += "<br>" + | |
| "Transition probability represents how likely a specific node is following the given node, i.e. P(a_{i+1} | a_{i}).<br>" + | |
| "Passing probability of an edge from U to V represents how likely the node V follows the node U in a random path, i.e., P(a_i = U && a_{i+1} = V). <br>" | |
| document.getElementById("hintsupper").innerHTML = title; | |
| document.getElementById("hintsleft").innerHTML = ""; | |
| document.getElementById("hintsright").innerHTML = ""; | |
| document.getElementById("hintscenter").innerHTML = ""; | |
| document.getElementById("hintsbottom").innerHTML = ""; | |
| } | |
| function clear_hint(){ | |
| showing_node = undefined; | |
| showing_edge = undefined; | |
| document.getElementById("hintsupper").innerHTML = "Use scroll to zoom in or out. Select or Hover over nodes and edges for more information ... (Try dragging nodes to replace them.)"; | |
| document.getElementById("hintsleft").innerHTML = ""; | |
| document.getElementById("hintsright").innerHTML = ""; | |
| document.getElementById("hintscenter").innerHTML = ""; | |
| document.getElementById("hintsbottom").innerHTML = ""; | |
| } | |
| startNetwork = function(graph_info, options) { | |
| current_options = options; | |
| global_graph_info = graph_info; | |
| node_pass_prob = graph_info['node_pass_prob'][0] // size: [prelen] | |
| prelen = node_pass_prob.length | |
| max_path = graph_info['max_paths'][0] // size: [tarlen] | |
| tarlen = max_path.length | |
| node_tokens = graph_info['node_tokens'][0] // size: [prelen, 5] | |
| node_probs = graph_info['node_probs'][0] // size: [prelen, 5] | |
| links = graph_info['links'][0] // size: [prelen, prelen] | |
| update_visible_nodes(true); | |
| update_visible_edges(true); | |
| // create a network | |
| var container = document.getElementById("daggraph"); | |
| var data = { | |
| nodes: nodes, | |
| edges: edges, | |
| }; | |
| network_options = { | |
| nodes: { | |
| shape: "ellipse", | |
| scaling: { | |
| label: { | |
| min: 8, | |
| max: 20, | |
| }, | |
| customScalingFunction: customScalingFunction, | |
| }, | |
| }, | |
| edges: { | |
| arrowStrikethrough: false, | |
| arrows: "to", | |
| smooth: { | |
| type: "continuous" | |
| }, | |
| color: "#2B7CE9", | |
| font: { align: "bottom" }, | |
| length: 120, | |
| scaling: { | |
| min: 0.5, | |
| max: 3, | |
| label: { | |
| min: 8, | |
| max: 15, | |
| }, | |
| customScalingFunction: customScalingFunction, | |
| } | |
| } | |
| }; | |
| network = new vis.Network(container, data, network_options); | |
| network.off("dragStart"); | |
| network.on("dragStart", function (params) { | |
| var idx = this.getNodeAt(params.pointer.DOM); | |
| if (idx !== undefined) { | |
| // console.log("dragstart " + idx.toString()); | |
| if (map_max_path[idx] !== undefined){ | |
| data.nodes.update({id: idx, fixed: false}); | |
| } | |
| } | |
| }); | |
| network.off("dragEnd"); | |
| network.on("dragEnd", function (params) { | |
| var idx = this.getNodeAt(params.pointer.DOM); | |
| if (idx !== undefined){ | |
| // console.log("dragend " + idx.toString()); | |
| if (map_max_path[idx] !== undefined){ | |
| data.nodes.update({id: idx, fixed: true}); | |
| map_max_path[idx].x = params.pointer.canvas.x; | |
| map_max_path[idx].y = params.pointer.canvas.y; | |
| } | |
| } | |
| }); | |
| disable_edge_select = false; | |
| network.off("selectNode"); | |
| network.on("selectNode", function (params) { | |
| var node_id = params.nodes[0]; | |
| show_hint_node(node_id); | |
| disable_edge_select = true; | |
| setTimeout(() => {disable_edge_select=false;}, 200); | |
| }); | |
| network.off("selectEdge"); | |
| network.on("selectEdge", function (params) { | |
| if(disable_edge_select) return; | |
| var edge_id = params.edges[0]; | |
| show_hint_edge(edge_id); | |
| }); | |
| network.off("deselectNode"); | |
| network.on("deselectNode", function (params) { | |
| clear_hint(); | |
| showing_node = undefined; | |
| showing_edge = undefined; | |
| }); | |
| network.off("deselectEdge"); | |
| network.on("deselectEdge", function (params) { | |
| clear_hint(); | |
| }); | |
| } | |
| updateNetwork = function(options) { | |
| if(typeof node_pass_prob === "undefined") return; | |
| old_options = current_options; | |
| current_options = options; | |
| if(options.minimum_node_pass_prob != old_options.minimum_node_pass_prob){ | |
| update_visible_nodes(); | |
| } | |
| if(options.minimum_node_pass_prob != old_options.minimum_node_pass_prob || | |
| options.minimum_edge_prob != old_options.minimum_edge_prob || | |
| options.max_out_edge_num != old_options.max_out_edge_num || | |
| options.max_out_edge_prob != old_options.max_out_edge_prob || | |
| options.force_in_edge != old_options.force_in_edge){ | |
| update_visible_edges(); | |
| } | |
| if(options.show_node_detail != old_options.show_node_detail){ | |
| if(options.show_node_detail) { | |
| network_options.nodes.shape = "dot"; | |
| network_options.nodes.scaling.label.min=10; | |
| network_options.nodes.scaling.label.max=10; | |
| }else{ | |
| network_options.nodes.shape = "ellipse"; | |
| network_options.nodes.scaling.label.min=8; | |
| network_options.nodes.scaling.label.max=20; | |
| } | |
| network.setOptions(network_options); | |
| update_node_details(); | |
| } | |
| if(options.show_edge_label != old_options.show_edge_label){ | |
| update_edge_label(); | |
| } | |
| if(showing_node != undefined){ | |
| show_hint_node(showing_node); | |
| } | |
| if(showing_edge != undefined){ | |
| show_hint_edge(showing_edge); | |
| } | |
| } | |
| } | |