Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Note

This is an early beta version so don’t expect too much from it. To experience the knowledge graph, click the textbox labelled Search and select items and type the keyword “takeover”. Then, select the “Account Takeover” item. This is because the network graph contains very content at this point of its development. From there, the Knowledge Graph UI will display a number edges and nodes. Click on the nodes to load and explore the graph from there. Use the #knowledge-graph Slack channel to provide feedbacks and contribute to the project.

Easy html macro
theme{"label":"solarized_dark","value":"solarized_dark"}
contentByMode{"html":"<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n <meta charset=\"utf-8\" />\r\n <title>The Open-Measure Interactive Knowledge Graph | <i>Version Beta 0.1</i></title>\r\n <!--\r\nBig hugs to:\r\nhttps://visjs.org/\r\nhttps://select2.org/\r\nhttps://jquery.com/\r\n...and probably many others.\r\n-->\r\n <script src=\"https://code.jquery.com/jquery-3.6.0.slim.min.js\"></script>\r\n <link href=\"https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css\" rel=\"stylesheet\" />\r\n <script src=\"https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js\"></script>\r\n <script type=\"text/javascript\" src=\"https://unpkg.com/vis-data@latest/peer/umd/vis-data.min.js\"></script>\r\n <script type=\"text/javascript\" src=\"https://unpkg.com/vis-network@latest/peer/umd/vis-network.min.js\"></script>\r\n <link rel=\"stylesheet\" type=\"text/css\" href=\"https://unpkg.com/vis-network/styles/vis-network.min.css\" />\r\n <style>\r\n * {\r\n font-family: helvetica\r\n }\r\n\r\n input[type=button] {\r\n border: 2px solid #999999;\r\n border-radius: 10px;\r\n font-size: 1em;\r\n font-weight: bold;\r\n padding: 4px;\r\n text-align: center;\r\n vertical-align: middle;\r\n background-color: #efefef;\r\n }\r\n\r\n #nodeHeaderTitle {\r\n color: black;\r\n font-weight: bold;\r\n width: 80%;\r\n border: 2px solid #999999;\r\n border-radius: 10px;\r\n padding: 6px;\r\n margin: 4px;\r\n vertical-align: middle;\r\n background-color: #5599ff;\r\n }\r\n\r\n #divGraph {\r\n width: 90%;\r\n border: 2px solid #999999;\r\n border-radius: 10px;\r\n height: 550px;\r\n /* BUG: This is a bug, I rather want the DIV element to take all the window available height */\r\n padding: 2px;\r\n margin: 4px;\r\n text-align: center;\r\n vertical-align: middle;\r\n background-color: #ffffff;\r\n }\r\n\r\n div.vis-network div.vis-navigation div.vis-button.vis-up,\r\n div.vis-network div.vis-navigation div.vis-button.vis-down,\r\n div.vis-network div.vis-navigation div.vis-button.vis-left,\r\n div.vis-network div.vis-navigation div.vis-button.vis-right,\r\n div.vis-network div.vis-navigation div.vis-button.vis-zoomIn,\r\n div.vis-network div.vis-navigation div.vis-button.vis-zoomOut,\r\n div.vis-network div.vis-navigation div.vis-button.vis-zoomExtends {\r\n background-image: none !important;\r\n font-size: 2em;\r\n border: 2px solid #999999;\r\n border-radius: 10px;\r\n padding: 2px;\r\n margin: 4px;\r\n text-align: center;\r\n vertical-align: middle;\r\n background-color: #efefef;\r\n }\r\n\r\ndiv.vis-network div.vis-navigation div.vis-button:hover {\r\n box-shadow: none !important;\r\n}\r\n\r\n.vis-button:after {\r\n color: #555555;\r\n}\r\n\r\n.vis-button:hover:after {\r\n color: \"#0066ff\";\r\n}\r\n\r\n.vis-button.vis-up:after {\r\n content: \"▲\";\r\n}\r\n\r\n.vis-button.vis-down:after {\r\n content: \"▼\";\r\n}\r\n\r\n.vis-button.vis-left:after {\r\n content: \"◀\";\r\n}\r\n\r\n.vis-button.vis-right:after {\r\n content: \"▶\";\r\n}\r\n\r\n.vis-button.vis-zoomIn:after {\r\n content: \"+\";\r\n font-weight: bold;\r\n}\r\n\r\n.vis-button.vis-zoomOut:after {\r\n content: \"−\";\r\n font-weight: bold;\r\n}\r\n\r\n.vis-button.vis-zoomExtends:after {\r\n content: \"⤧\";\r\n}\r\n </style>\r\n <script type=\"text/javascript\">\r\n\r\n // Global variables\r\n var visNodes = null;\r\n var visEdges = null;\r\n var visNetwork = null;\r\n var sourceData = null;\r\n\r\n function unloadNode(nodeId) {\r\n visNodes.remove(nodeId);\r\n noSelection();\r\n }\r\n\r\n function noSelection() {\r\n document.getElementById(\"spanFocusedNode\").innerHTML = \"<i>No item selected</i>\";\r\n }\r\n\r\n function resetAll() {\r\n $('#comboboxNodes').val(null).trigger('change');\r\n visNodes.clear();\r\n visEdges.clear();\r\n }\r\n\r\n \r\n /**\r\n * Load the adjacent nodes of a node from the Data Source into the Network Graph.\r\n */\r\n function loadAdjacent(nodeId) {\r\n for (i = 0; i < sourceData.edges.length; i++) {\r\n myEdge = sourceData.edges[i];\r\n if (myEdge.from == nodeId || myEdge.to == nodeId) {\r\n visEdges.update(myEdge);\r\n //visNodes.update(getNode(myEdge.from));\r\n //visNodes.update(getNode(myEdge.to));\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * List the adjacent nodes of a node from the Data Source into the Network Graph.\r\n */\r\n function listAdjacent(nodeId) {\r\n nodesList = [];\r\n for (i = 0; i < sourceData.edges.length; i++) {\r\n edgeSourceData = sourceData.edges[i];\r\n if (edgeSourceData.from == nodeId){ //&& edgeSourceData.to != nodeId){\r\n nodesList.push(edgeSourceData.to);\r\n } \r\n if (edgeSourceData.to == nodeId){ //&& edgeSourceData.from != nodeId){\r\n nodesList.push(edgeSourceData.from);\r\n } \r\n }\r\n return nodesList;\r\n }\r\n\r\n /**\r\n * Load a node from the Data Source into the Network Graph.\r\n */\r\n function loadNode(nodeId) {\r\n // Check if the graph node exists already.\r\n graphNode = visNodes.get(nodeId);\r\n if(!graphNode){\r\n // Load the node in the graph if it was not already previously loaded.\r\n visNodes.update(getNode(nodeId));\r\n console.log(\"Graph Node Loaded: \" + nodeId);\r\n }\r\n\r\n // Assure all node edges are loaded\r\n // Assure all adjacent nodes are also loaded\r\n for (i = 0; i < sourceData.edges.length; i++) {\r\n edgeSourceData = sourceData.edges[i];\r\n if (edgeSourceData.from == nodeId || edgeSourceData.to == nodeId) {\r\n edgeId = edgeSourceData.id;\r\n graphEdge = visEdges.get(edgeId);\r\n if(!graphEdge){\r\n visEdges.update(edgeSourceData);\r\n visNodes.update(getNode(edgeSourceData.from));\r\n visNodes.update(getNode(edgeSourceData.to));\r\n console.log(\"Graph Edge Loaded: \" + edgeId);\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Retrieve a node from the Data Source as a Javascript object.\r\n */\r\n function getNode(nodeId) {\r\n var result = sourceData.nodes.filter(obj => {\r\n return obj.id === nodeId\r\n });\r\n node = result[0];\r\n /* Replace white space with new lines. \r\n TODO: Find a cool algorithm that will break text with more intelligence. */\r\n node.label = node.label.replaceAll(\" \",\"\\n\");\r\n return result[0];\r\n }\r\n\r\n function applyNodeStyleSelected(nodeId){\r\n var nodeObject = visNetwork.body.nodes[nodeId];\r\n nodeObject.setOptions({\r\n font: {\r\n size: 20},\r\n color: {\r\n border: '#999999',\r\n background: '#5599ff',\r\n highlight: {\r\n border: '#999999',\r\n background: '#5599ff'\r\n }}\r\n });\r\n console.log(\"Style=Selected: \" + nodeId);\r\n }\r\n\r\n function applyNodeStyleAdjacent(nodeId){\r\n var nodeObject = visNetwork.body.nodes[nodeId];\r\n nodeObject.setOptions({\r\n font: {\r\n size: 18},\r\n color: {\r\n border: '#aaaaaa',\r\n background: '#aaccff'}\r\n });\r\n console.log(\"Style=Adjacent: \" + nodeId);\r\n }\r\n\r\n function applyNodeStyleNormal(nodeId){\r\n var nodeObject = visNetwork.body.nodes[nodeId];\r\n nodeObject.setOptions({\r\n font: {\r\n size: 14},\r\n color: {\r\n border: '#b3b3b3',\r\n background: '#ffffff'}\r\n });\r\n console.log(\"Style=Normal: \" + nodeId); \r\n }\r\n\r\n /**\r\n * When a node is selected on the graph, load adjacent nodes and apply cool styles.\r\n */\r\n function onNodeSelection(nodeId){\r\n console.log(\"Selected: \" + nodeId);\r\n\r\n // Remove the newly selected node from the nodes to be cleaned.\r\n //cleaningNodes.filter(item => item !== nodeId);\r\n\r\n // If the selection event was triggered by the combobox,\r\n // the newly selected node may not yet be loaded on the graph.\r\n // Also, we need to assure that all adjacent nodes are\r\n // loaded on the graph.\r\n loadNode(nodeId);\r\n\r\n // Clean styles\r\n cleaningNodes = visNodes.getIds();\r\n for(i = 0; i < cleaningNodes.length; i++){\r\n console.log(i);\r\n applyNodeStyleNormal(cleaningNodes[i]);\r\n }\r\n\r\n var nodeData = getNode(nodeId);\r\n var nodeLabel = nodeData.label;\r\n applyNodeStyleSelected(nodeId);\r\n document.getElementById(\"spanFocusedNode\").innerHTML =\r\n '<span id=\"nodeHeaderTitle\">' + nodeLabel + '</span>' +\r\n '&nbsp;<a type=\"button\" href=\"' + nodeData.url + '\" target=\"_blank\">Wiki Page</a>' +\r\n '&nbsp;<input type=\"button\" onclick=\"unloadNode(\\'' + nodeId + '\\');\" value=\"Remove\" />';\r\n\r\n adjacentNodes = listAdjacent(nodeId);\r\n console.log(\"Review adjacent nodes: \" + adjacentNodes);\r\n console.log(adjacentNodes.length);\r\n for(i = 0; i < adjacentNodes.length; i++){\r\n adjacentNodeId = adjacentNodes[i];\r\n // Remove the newly selected node from the nodes to be cleaned.\r\n //cleaningNodes.filter(item => item !== adjacentNodeId);\r\n applyNodeStyleAdjacent(adjacentNodeId);\r\n }\r\n\r\n //visNetwork.selectNodes([nodeId]);\r\n //visNetwork.focus(nodeId); //re-center the graph on the newly selected node\r\n }\r\n\r\n function onNodeUnselection(nodeId){\r\n //document.getElementById(\"spanFocusedNode\").innerHTML = \"\";\r\n }\r\n\r\n $(document).ready(function () {\r\n\r\n fetch('https://raw.githubusercontent.com/Open-Measure/Confluence-Site/master/KnowledgeGraph/data-v2.xlsm.json?v=2')\r\n .then(response => response.json())\r\n .then(function (data) {\r\n\r\n sourceData = data;\r\n\r\n function getEdge(edgeId) {\r\n var result = sourceData.edges.filter(obj => {\r\n return obj.id === nodeId\r\n });\r\n return result[0];\r\n }\r\n\r\n function getMoreInformation(nodeId) {\r\n var targetNode = getNode(nodeId);\r\n window.open(targetNode.url, \"_blank\");\r\n }\r\n\r\n // Prepare SELECT2 options in the desired format\r\n var optionsList = [];\r\n for (i = 0; i < sourceData.nodes.length; i++) {\r\n optionsList.push({ id: sourceData.nodes[i].id, text: sourceData.nodes[i].label });\r\n };\r\n\r\n // Make SELECT2 alive\r\n $('.js-example-basic-multiple').select2({\r\n tags: true,\r\n tokenSeparators: [',', ' '],\r\n data: optionsList,\r\n createTag: function (params) {\r\n // Disable visNodes creation by the user\r\n return undefined;\r\n }\r\n });\r\n\r\n // Attach to the \"When a SELECT2 single item is selected\" event\r\n $('#comboboxNodes').on('select2:select', function (e) {\r\n var nodeId = e.params.data.id;\r\n onNodeSelection(nodeId); /*XXX*/\r\n //loadNode(nodeId);\r\n });\r\n\r\n // Attach to the \"When a SELECT2 item is deselected\" event\r\n $('#comboboxNodes').on('select2:unselect', function (e) {\r\n var nodeId = e.params.data.id;\r\n // Remove the node from the graph.\r\n unloadNode(nodeId);\r\n });\r\n\r\n // Start with a clean SELECT2\r\n noSelection();\r\n\r\n visNodes = new vis.DataSet();\r\n visEdges = new vis.DataSet();\r\n visNetwork = {};\r\n\r\n function startNetwork() {\r\n\r\n var container = document.getElementById(\"divGraph\");\r\n var data = {\r\n nodes: visNodes,\r\n edges: visEdges\r\n };\r\n\r\n /* TODO: It would be better to use HighlightNearest rather\r\n than do all this custom implementation with styles tweaking... */\r\n var options = {\r\n groups:{\r\n useDefaultGroups: true /* We tweak the group use case to populate semantic groups */\r\n },\r\n interaction: {\r\n navigationButtons: true,\r\n keyboard: true,\r\n multiselect: false //,\r\n //hover: true\r\n },\r\n physics: {\r\n enabled: true,\r\n barnesHut: {\r\n //theta: 0.1,\r\n gravitationalConstant: -20000, /*Number. Default to -2000. Gravity attracts. We like repulsion. So the value is negative. If you want the repulsion to be stronger, decrease the value (so -10000, -50000).*/\r\n centralGravity: .3,\r\n springLength: 95, /*Number. Default to 95. The edges are modelled as springs. This springLength here is the the rest length of the spring.*/\r\n springConstant: 0.06, /*Number. Default to 0.04. This is how 'sturdy' the springs are. Higher values mean stronger springs.*/\r\n damping: 0.09, /*Number. Default to 0.09. Accepted range: [0 .. 1]. The damping factor is how much of the velocity from the previous physics simulation iteration carries over to the next iteration.*/\r\n avoidOverlap: 0.0 /*Number. Default to 0. Accepted range: [0 .. 1]. When larger than 0, the size of the node is taken into account. The distance will be calculated from the radius of the encompassing circle of the node for both the gravity model. Value 1 is maximum overlap avoidance.*/\r\n },\r\n maxVelocity: 5,\r\n minVelocity: 1,\r\n solver: 'barnesHut',\r\n stabilization: {\r\n enabled: true,\r\n iterations: 500,\r\n updateInterval: 100,\r\n onlyDynamicEdges: false,\r\n fit: true\r\n },\r\n timestep: 0.4 /*Number. Default to 0.5. The physics simulation is discrete. This means we take a step in time, calculate the forces, move the nodes and take another step. If you increase this number the steps will be too large and the network can get unstable. If you see a lot of jittery movement in the network, you may want to reduce this value a little.*/\r\n },\r\n edges: {\r\n color: { color: \"#aaaaaa\" },\r\n arrows: {\r\n //from: { enabled: true, type: \"bar\" },\r\n //middle: { enabled: true, type: \"triangle\" },\r\n to: { enabled: true, type: \"arrow\" }\r\n }\r\n },\r\n nodes: {\r\n //borderWidth: 1,\r\n //borderWidthSelected: 2,\r\n chosen: true,\r\n color: {\r\n //border: '#b3b3b3',\r\n background: '#ffffff',\r\n highlight: {\r\n //border: '#0000ff'\r\n background: '#5599ff'\r\n },\r\n //hover: {\r\n //border: '#CD5C5C'\r\n //},\r\n },\r\n //size: 14,\r\n shape: 'box' //box is a good option as well.\r\n }\r\n };\r\n\r\n visNetwork = new vis.Network(container, data, options);\r\n visNetwork.on(\"selectNode\", function (params) {\r\n var nodeId = params.nodes[0];\r\n if (nodeId) {\r\n onNodeSelection(nodeId);\r\n }\r\n });\r\n /*\r\n visNetwork.on(\"deselectNode\", function (params) {\r\n var nodeId = params.previousSelection.nodes[0].id;\r\n if(nodeId){\r\n onNodeUnselection(nodeId);\r\n }\r\n });\r\n */\r\n }\r\n\r\n startNetwork();\r\n\r\n }); /*End of data fetching*/\r\n\r\n });\r\n </script>\r\n</head>\r\n\r\n<body>\r\n <!--<h1>The Open-Measure Interactive Knowledge Graph | <i>Version Beta 0.1</i></h1>-->\r\n <label for=\"comboboxNodes\">\r\n Search and select items:\r\n <select class=\"js-example-basic-multiple js-states form-control\" id=\"comboboxNodes\" multiple=\"multiple\"\r\n style=\"width: 75%\">\r\n </select>\r\n </label>\r\n <div>\r\n <span id=\"spanFocusedNode\"></span>\r\n <input type=\"button\" onclick=\"resetAll()\" value=\"Clear All\">\r\n </div>\r\n <div id=\"divGraph\"></div>\r\n</body>\r\n\r\n</html>","javascript":"","css":""}