Merge pull request #2 from Huguet57/master
Implementació de la barra de cerca
diff --git a/api.php b/api.php
index 5bdbb00..26b655a 100644
--- a/api.php
+++ b/api.php
@@ -2,13 +2,13 @@
require_once("config.php");
class write {
- public static function do($json) {
+ public static function funct($json) {
print_r(json_encode($json));
exit();
}
public static function error($n, $msg) {
- self::do(["error" => $n, "msg" => $msg]);
+ self::funct(["error" => $n, "msg" => $msg]);
}
}
diff --git a/css/autocomplete.css b/css/autocomplete.css
new file mode 100644
index 0000000..148b882
--- /dev/null
+++ b/css/autocomplete.css
@@ -0,0 +1,54 @@
+* {
+ box-sizing: border-box;
+}
+
+.autocomplete {
+ /*the container must be positioned relative:*/
+ position: relative;
+ display: inline-block;
+}
+input {
+ border: 1px solid transparent;
+ background-color: #f1f1f1;
+ padding: 10px;
+ font-size: 16px;
+}
+input[type=text] {
+ background-color: #f1f1f1;
+ width: 100%;
+}
+input[type=submit] {
+ background-color: DodgerBlue;
+ color: #fff;
+ cursor: pointer;
+}
+.autocomplete-items {
+ position: absolute;
+ border: 1px solid #d4d4d4;
+ border-bottom: none;
+ border-top: none;
+ z-index: 99;
+ /*position the autocomplete items to be the same width as the container:*/
+ top: auto;
+ left: 0;
+ right: 0;
+ color: black;
+}
+.autocomplete-items div {
+ padding: 10px;
+ cursor: pointer;
+ background-color: #fff;
+ border-bottom: 1px solid #d4d4d4;
+}
+.autocomplete-items div:hover {
+ /*when hovering an item:*/
+ background-color: #e9e9e9;
+}
+.autocomplete-active {
+ /*when navigating through the items using the arrow keys:*/
+ background-color: DodgerBlue !important;
+ color: #ffffff;
+}
+.autocomplete-year {
+ color: #ccc;
+}
\ No newline at end of file
diff --git a/css/dialog.css b/css/dialog.css
new file mode 100644
index 0000000..3f2b73b
--- /dev/null
+++ b/css/dialog.css
@@ -0,0 +1,53 @@
+#dialog-vertex, #dialog-edge{
+ padding: 8px;
+ user-select: auto;
+}
+
+#dialog h2 {
+ font-weight: bold;
+ font-size: 20px;
+}
+
+#dialog h3 {
+ font-weight: bold;
+ font-size: 16px;
+ margin-bottom: 0;
+}
+
+#quit-dialog, #quit2-dialog {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+}
+
+#min-dialog, #max-dialog {
+ position: absolute;
+ top: 8px;
+ right: 48px;
+}
+
+#min-dialog {
+ display: none;
+}
+
+#summary-dialog {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ height: 100px;
+ background-color: white;
+ color: black;
+ z-index: 120;
+}
+
+#summary-vertex {
+ padding: 8px;
+ user-select: auto;
+}
+
+#summary-dialog h2 {
+ font-weight: bold;
+ font-size: 20px;
+ margin: 0;
+}
\ No newline at end of file
diff --git a/css/modal.css b/css/modal.css
new file mode 100644
index 0000000..cdcba8c
--- /dev/null
+++ b/css/modal.css
@@ -0,0 +1,38 @@
+/* The Modal (background) */
+.modal {
+ display: none; /* Hidden by default */
+ position: fixed; /* Stay in place */
+ z-index: 1; /* Sit on top */
+ padding-top: 100px; /* Location of the box */
+ left: 0;
+ top: 0;
+ width: 100%; /* Full width */
+ height: 100%; /* Full height */
+ overflow: auto; /* Enable scroll if needed */
+ background-color: rgb(0,0,0); /* Fallback color */
+ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
+}
+
+/* Modal Content */
+.modal-content {
+ background-color: #fefefe;
+ margin: auto;
+ padding: 20px;
+ border: 1px solid #888;
+ width: 80%;
+}
+
+/* The Close Button */
+.closeBox {
+ color: #aaaaaa;
+ float: right;
+ font-size: 28px;
+ font-weight: bold;
+}
+
+.closeBox:hover,
+.closeBox:focus {
+ color: #000;
+ text-decoration: none;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/css/styles.css b/css/styles.css
index 5c6b812..0343114 100644
--- a/css/styles.css
+++ b/css/styles.css
@@ -37,58 +37,26 @@
z-index: 110;
}
-#dialog-vertex, #dialog-edge{
- padding: 8px;
- user-select: auto;
+.addegde-button {
+ line-height: 20px;
+ font-size: 20px;
+ width: 100%;
+ background-color: rgba(200,0,0,0.4);
}
-#dialog h2 {
- font-weight: bold;
- font-size: 20px;
+.addegde-msg {
+ display: none;
+ line-height: 20px;
+ font-size: 20px;
+ width: 100%;
+ background-color: rgba(0,200,0,0.4);
}
-#dialog h3 {
- font-weight: bold;
- font-size: 16px;
- margin-bottom: 0;
-}
-
-#quit-dialog, #quit2-dialog {
+#searchButton {
position: absolute;
- top: 8px;
- right: 8px;
-}
-
-#min-dialog, #max-dialog {
- position: absolute;
- top: 8px;
- right: 48px;
-}
-
-#min-dialog {
- display: none;
-}
-
-#summary-dialog {
- position: absolute;
- top: 0px;
- left: 0px;
- width: 100%;
- height: 100px;
- background-color: white;
- color: black;
- z-index: 120;
-}
-
-#summary-vertex {
- padding: 8px;
- user-select: auto;
-}
-
-#summary-dialog h2 {
- font-weight: bold;
- font-size: 20px;
- margin: 0;
+ right: 10px;
+ bottom: 110px;
+ z-index: 100;
}
#zoomin {
diff --git a/graf.php b/graf.php
index c0837c0..486afde 100644
--- a/graf.php
+++ b/graf.php
@@ -22,7 +22,13 @@
<meta name=viewport content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="manifest" href="manifest.json">
- <link rel="stylesheet" href="css/styles.css">
+ <link rel="stylesheet" href="css/dialog.css">
+ <link rel="stylesheet" href="css/styles.css">
+
+ <!-- Seach Bar CSS files -->
+ <link rel="stylesheet" href="./css/modal.css"></link>
+ <link rel="stylesheet" href="./css/autocomplete.css"></link>
+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.blue_grey-blue.min.css" />
@@ -33,9 +39,10 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black">
</head>
<body>
- <button id="zoomin" class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored"><i class="material-icons">zoom_in</i></button>
+ <button id="searchButton"class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored"><i class="material-icons">search</i></button>
+ <button id="zoomin" class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored"><i class="material-icons">zoom_in</i></button>
<button id="zoomout" class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-js-ripple-effect mdl-button--colored"><i class="material-icons">zoom_out</i></button>
-
+
<div id="backdrop-container" style="display: none;">
<div id="backdrop"></div>
</div>
@@ -47,11 +54,18 @@
<ul>
<li><b>Any:</b> <span data-fill="year"></span></li>
<li><b>Sexe:</b> <span data-fill="sex"></span></li>
- <li><b>ID:</b> <span data-fill="id"></span></li>
+ <li><b>ID:</b> <span data-fill="id" id="node-id"></span></li>
</ul>
- <h3>Arestes (<span data-fill="n-edges"></span>):</h3>
- <ul data-fill="edges">
- </ul>
+ <div id="addedge-box">
+ <div id="addedge-button" class="addegde-button"><h2>Afegir una nova aresta +</h2></div>
+ <div id="addedge-msg" class="addegde-msg"><h2>Aresta afegida!</h2></div>
+ <input id="addedge-input" type="text" style="display:none;">
+ </div>
+ <div id="edge-list">
+ <h3>Arestes (<span data-fill="n-edges"></span>):</h3>
+ <ul data-fill="edges">
+ </ul>
+ </div>
</div>
<div id="dialog-edge" style="display: none;">
</div>
@@ -64,11 +78,38 @@
<p><span data-fill="year"></span>, <span data-fill="sex"></span>, <span data-fill="id"></span></p>
</div>
</div>
+ <!-- Search Box -->
+ <div id="searchBox" class="modal">
+ <!-- Search Box Content -->
+ <div class="modal-content">
+ <span class="closeBox">×</span>
+ <div class="in-box-content">
+ <center>
+ <form autocomplete="off">
+ <div class="autocomplete">
+ <input id="searchInput" type="text" placeholder="Busca una persona al graf">
+ </div>
+ </form>
+ </center>
+ </div>
+ </div>
+ </div>
+
<div id="graf"></div>
-
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/sigma.js/1.2.0/sigma.min.js"></script>
- <script src="js/script.js"></script>
+ <script>
+ // Initialize the graph and the sigma
+ var s, graf;
+ </script>
+
+ <!-- Search Bar JS files -->
+ <script src="./js/modal.js"></script>
+ <script src="./js/autocomplete.js" ></script>
+
+ <script src="js/script.js"></script>
+
<script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
<!--<script src="js/service-worker.js"></script>-->
</body>
diff --git a/js/autocomplete.js b/js/autocomplete.js
new file mode 100644
index 0000000..4c32827
--- /dev/null
+++ b/js/autocomplete.js
@@ -0,0 +1,199 @@
+function autocomplete(inp, obj, act) {
+ /*the autocomplete function takes two arguments,
+ the text field element and an objay of possible autocompleted values:*/
+ var currentFocus;
+ /*execute a function when someone writes in the text field:*/
+ inp.addEventListener("input", function(e) {
+ var a, b, i, val = this.value;
+ /*close any already open lists of autocompleted values*/
+ closeAllLists();
+ if (!val || val.length < 3) return false;
+ currentFocus = -1;
+
+ /*create a DIV element that will contain the items (values):*/
+ a = document.createElement("DIV");
+ a.setAttribute("id", this.id + "autocomplete-list");
+ a.setAttribute("class", "autocomplete-items");
+ /*append the DIV element as a child of the autocomplete container:*/
+ this.parentNode.appendChild(a);
+
+ /*for each item in the object...*/
+ for (node in obj) {
+ var nomNode = obj[node].name;
+
+ if (nomNode.toUpperCase().includes(val.toUpperCase())) {
+ var parts = nomNode.toUpperCase().split(val.toUpperCase());
+
+ /*create a DIV element for each matching element:*/
+ b = document.createElement("DIV");
+
+ /*make the matching letters bold:*/
+ if (parts[0].length == 0) b.innerHTML = "";
+ else b.innerHTML = nomNode.substr(0, parts[0].length);
+ b.innerHTML += "<strong>" + nomNode.substr(parts[0].length, val.length) + "</strong>";
+ b.innerHTML += nomNode.substr(parts[0].length + val.length);
+ b.innerHTML += " - <span class='autocomplete-year'>" + obj[node].year + "</span>";
+ /*insert a input field that will hold the current object item's value:*/
+ b.innerHTML += "<input type='hidden' value='" + node + "'>";
+
+ /*execute a function when someone clicks on the item value (DIV element):*/
+ b.addEventListener("click", function(e) {
+ /*insert the value for the autocomplete text field:*/
+ var n = this.getElementsByTagName("input")[0].value;
+ inp.value = obj[n].name;
+
+ switch (act) {
+ case "search":
+ // Move camera to desired node
+ cameraGoto(obj[n].x, obj[n].y);
+ // Close the search box
+ var searchBox = document.getElementById('searchBox');
+ searchBox.style.display = "none";
+
+ // Empty the input bar
+ var searchInput = document.getElementById('searchInput');
+ searchInput.value = "";
+ break;
+ case "addEdge":
+ // Add an edge between A and B
+ var sourceID = document.getElementById("node-id").innerText.substr(1);
+ var edgeName = Math.min(sourceID, n) + "_" + Math.max(sourceID, n);
+ if (!graf.edges[edgeName]) {
+ graf.edges[edgeName] = {
+ a: Math.min(sourceID, n),
+ b: Math.max(sourceID, n),
+ votes: 1
+ };
+
+ // Temporary fix, just for testing
+ s.graph.addEdge({
+ id: edgeName,
+ source: sourceID,
+ target: n,
+ size: 0.5
+ });
+
+ clickNode(s, sourceID, s.graph.neighbors(sourceID));
+ dialog.max();
+
+ // Give a message of validation
+ var edgeMSG = document.querySelector("#addedge-msg");
+ addedEdgeMSG(edgeMSG);
+
+ } else {
+ alert("Edge already exists");
+ }
+
+ // Empty the input bar
+ var addEdgeInput = document.getElementById('addedge-input');
+ addEdgeInput.value = "";
+
+ // Return to default view
+ document.querySelector("#addedge-input").style.display = "none";
+ document.querySelector("#edge-list").style.display = "block";
+ break;
+ }
+
+ /*close the list of autocompleted values,
+ (or any other open lists of autocompleted values:*/
+ closeAllLists();
+ });
+
+ // Set "data-edges" attribute and compare with others
+ var nEdges = Object.keys(s.graph.neighbors(node)).length;
+ b.dataset.edges = nEdges;
+ var inserted = false;
+
+ // Sort nodes by degree
+ for (i in a.childNodes) {
+ var child = a.childNodes[i];
+ if (!child.dataset) break;
+ if (nEdges > child.dataset.edges) {
+ a.insertBefore(b, child);
+ inserted = true;
+ break;
+ }
+ }
+
+ if (!inserted) {
+ a.appendChild(b);
+ console.log("inserted");
+ }
+ }
+ }
+ });
+ /*execute a function presses a key on the keyboard:*/
+ inp.addEventListener("keydown", function(e) {
+ var x = document.getElementById(this.id + "autocomplete-list");
+ if (x) x = x.getElementsByTagName("div");
+ if (e.keyCode == 40) {
+ /*If the objow DOWN key is pressed,
+ increase the currentFocus variable:*/
+ currentFocus++;
+ /*and and make the current item more visible:*/
+ addActive(x);
+ } else if (e.keyCode == 38) { //up
+ /*If the objow UP key is pressed,
+ decrease the currentFocus variable:*/
+ currentFocus--;
+ /*and and make the current item more visible:*/
+ addActive(x);
+ } else if (e.keyCode == 13) {
+ /*If the ENTER key is pressed, prevent the form from being submitted,*/
+ e.preventDefault();
+ if (currentFocus > -1) {
+ /*and simulate a click on the "active" item:*/
+ if (x) x[currentFocus].click();
+ }
+ }
+ });
+ function addActive(x) {
+ /*a function to classify an item as "active":*/
+ if (!x) return false;
+ /*start by removing the "active" class on all items:*/
+ removeActive(x);
+ if (currentFocus >= x.length) currentFocus = 0;
+ if (currentFocus < 0) currentFocus = (x.length - 1);
+ /*add class "autocomplete-active":*/
+ x[currentFocus].classList.add("autocomplete-active");
+ }
+ function removeActive(x) {
+ /*a function to remove the "active" class from all autocomplete items:*/
+ for (var i = 0; i < x.length; i++) {
+ x[i].classList.remove("autocomplete-active");
+ }
+ }
+ function closeAllLists(elmnt) {
+ /*close all autocomplete lists in the document,
+ except the one passed as an argument:*/
+ var x = document.getElementsByClassName("autocomplete-items");
+ for (var i = 0; i < x.length; i++) {
+ if (elmnt != x[i] && elmnt != inp) {
+ x[i].parentNode.removeChild(x[i]);
+ }
+ }
+ }
+ /*execute a function when someone clicks in the document:*/
+ document.addEventListener("click", function (e) {
+ closeAllLists(e.target);
+ });
+}
+
+function addedEdgeMSG(edgeMSG) {
+ var opacity = 8;
+ edgeMSG.style.display = "block";
+
+ var opacityChange = window.setInterval(function() {
+ edgeMSG.style.color = "rgba(0,0,0, "+ opacity/10 +")";
+ var edgeList = document.querySelector("#edge-list ul");
+ edgeList.style.backgroundColor = "rgba(0,200,0, "+ opacity/10 +")";
+ --opacity;
+ console.log(opacity);
+ }, 100);
+
+ var backToDef = window.setTimeout(function() {
+ edgeMSG.style.display = "none";
+ edgeMSG.style.color = "black";
+ window.clearInterval(opacityChange);
+ }, 1000);
+}
\ No newline at end of file
diff --git a/js/modal.js b/js/modal.js
new file mode 100644
index 0000000..179e936
--- /dev/null
+++ b/js/modal.js
@@ -0,0 +1,34 @@
+// Get the box
+var searchBox = document.getElementById('searchBox');
+
+// Get the button that opens the box
+var searchButton = document.getElementById("searchButton");
+
+// Get the <span> element that closes the box
+var closeBox = document.getElementsByClassName("closeBox")[0];
+
+// When the user clicks the button, open the box
+searchButton.onclick = function() {
+ dialog.close();
+ searchBox.style.display = "block";
+ // Focus on the search input bar
+ document.getElementById("searchInput").focus();
+}
+
+// When the user clicks on <span> (x), close the box
+closeBox.onclick = function() {
+ searchBox.style.display = "none";
+ // Empty the input bar
+ var searchInput = document.getElementById('searchInput');
+ searchInput.value = "";
+}
+
+// When the user clicks anywhere outside of the box, close it
+window.onclick = function(event) {
+ if (event.target == searchBox) {
+ searchBox.style.display = "none";
+ // Empty the input bar
+ var searchInput = document.getElementById('searchInput');
+ searchInput.value = "";
+ }
+}
\ No newline at end of file
diff --git a/js/script.js b/js/script.js
index 4e74231..6fcf868 100644
--- a/js/script.js
+++ b/js/script.js
@@ -1,5 +1,3 @@
-var s, graf;
-
function xhr(method, url, params, callback) {
var http = new XMLHttpRequest();
if (method == "POST") {
@@ -65,6 +63,7 @@
s.graph.nodes().forEach(function(n) {
n.color = n.originalColor;
+ n.label = n.originalLabel;
});
s.graph.edges().forEach(function(e) {
@@ -80,6 +79,13 @@
min: function() {
document.querySelector("#dialog").style.display = "none";
document.querySelector("#summary-dialog").style.display = "block";
+ },
+ addEdge: function() {
+ document.querySelector("#addedge-input").style.display = "block";
+ document.querySelector("#edge-list").style.display = "none";
+ autocomplete(document.getElementById("addedge-input"), graf.nodes, "addEdge");
+ // Focus on the addEdge input bar
+ document.getElementById("addedge-input").focus();
}
};
@@ -124,6 +130,7 @@
s.graph.addNode({
id: graf.nodes[i].id,
label: graf.nodes[i].name,
+ originalLabel: graf.nodes[i].name,
x: graf.nodes[i].x,
y: graf.nodes[i].y,
size: 10,
@@ -145,32 +152,14 @@
var nodeId = e.data.node.id,
toKeep = s.graph.neighbors(nodeId);
//toKeep[nodeId] = e.data.node;
-
- s.graph.nodes().forEach(function(n) {
- if (toKeep[n.id] || n.id == nodeId) {
- n.color = n.originalColor;
- } else {
- n.color = '#333';
- }
- });
-
- s.graph.edges().forEach(function(e) {
- if ((e.source == nodeId || e.target == nodeId) && (toKeep[e.source] || toKeep[e.target])) {
- e.color = '#fff';
- } else {
- e.color = '#333';
- }
- });
-
- s.refresh();
-
- dialog.show(nodeId, toKeep);
+ clickNode(s, nodeId, toKeep);
});
document.querySelector("#quit-dialog").addEventListener("click", dialog.close);
document.querySelector("#quit2-dialog").addEventListener("click", dialog.close);
document.querySelector("#max-dialog").addEventListener("click", dialog.max);
document.querySelector("#min-dialog").addEventListener("click", dialog.min);
+ document.querySelector("#addedge-button").addEventListener("click", dialog.addEdge);
document.querySelector("#zoomin").addEventListener("click", function() {
s.camera.goTo({
@@ -185,7 +174,40 @@
});
s.refresh();
+ autocomplete(document.getElementById("searchInput"), graf.nodes, "search");
});
}
+function cameraGoto(nodeX, nodeY) {
+ sigma.misc.animation.camera( s.camera,
+ { x: nodeX, y: nodeY, ratio: 1 },
+ { duration: s.settings('animationsTime') || 300 }
+ );
+}
+
+function clickNode(s, nodeId, toKeep) {
+ s.graph.nodes().forEach(function(n) {
+ if (toKeep[n.id] || n.id == nodeId) {
+ n.color = n.originalColor;
+ n.label = n.originalLabel;
+ console.log("Hola");
+ } else {
+ n.color = '#333';
+ n.label = '';
+ }
+ });
+
+ s.graph.edges().forEach(function(e) {
+ if ((e.source == nodeId || e.target == nodeId) && (toKeep[e.source] || toKeep[e.target])) {
+ e.color = '#fff';
+ } else {
+ e.color = '#333';
+ }
+ });
+
+ s.refresh();
+
+ dialog.show(nodeId, toKeep);
+}
+
window.addEventListener("load", init);