First commit
diff --git a/api.php b/api.php
new file mode 100644
index 0000000..23cd699
--- /dev/null
+++ b/api.php
@@ -0,0 +1,26 @@
+<?php
+class write {
+  public static function do($json) {
+    print_r(json_encode($json));
+    exit();
+  }
+
+  public static function error($n, $msg) {
+    self::do(["error" => $n, "msg" => $msg]);
+  }
+}
+
+if (!isset($_GET["action"])) {
+  write::error(1, "No action provided");
+}
+
+switch ($_GET["action"]) {
+  case "getgraf":
+  $graf = file_get_contents("https://dirba.io/grafo/api.php?req=getGraph");
+  echo $graf;
+  break;
+
+  default:
+  write::error(2, "Unknown action");
+}
+?>
diff --git a/css/styles.css b/css/styles.css
new file mode 100644
index 0000000..5c6b812
--- /dev/null
+++ b/css/styles.css
@@ -0,0 +1,122 @@
+html, body {
+  margin: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #060606;
+  color: white;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+  user-select: none!important;
+  font-family: 'Roboto';
+}
+
+#graf {
+  width: 100%;
+  height: 100%;
+}
+
+#dialog {
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  width: 300px;
+  height: 100%;
+  background-color: white;
+  color: black;
+  z-index: 120;
+  overflow-y: auto;
+}
+
+#backdrop {
+  display: none;
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, .5);
+  z-index: 110;
+}
+
+#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;
+}
+
+#zoomin {
+  position: absolute;
+  right: 10px;
+  bottom: 60px;
+  z-index: 100;
+}
+
+#zoomout {
+  position: absolute;
+  right: 10px;
+  bottom: 10px;
+  z-index: 100;
+}
+
+@media (max-width: 700px) {
+  #dialog {
+    width: Calc(100% - 32px)!important;
+    height: Calc(100% - 32px)!important;
+    margin: 16px;
+  }
+
+  #backdrop {
+    display: block;
+  }
+
+  #min-dialog {
+    display: block!important;
+  }
+}
diff --git a/graf.php b/graf.php
new file mode 100644
index 0000000..ed44ecb
--- /dev/null
+++ b/graf.php
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Graf alternatiu FME</title>
+
+    <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="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" />
+
+    <!-- Apple web app -->
+    <link rel="apple-touch-icon" href="img/graf.png">
+    <meta name="apple-mobile-web-app-title" content="Graf FME">
+    <meta name="apple-mobile-web-app-capable" content="yes">
+    <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="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>
+    <div id="dialog" class="mdl-shadow--2dp" style="display: none;">
+      <button id="quit-dialog" class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect"><i class="material-icons">close</i></button>
+      <button id="min-dialog" class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect"><i class="material-icons">remove</i></button>
+      <div id="dialog-vertex">
+        <h2 data-fill="name"></h2>
+        <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>
+        </ul>
+        <h3>Arestes (<span data-fill="n-edges"></span>):</h3>
+        <ul data-fill="edges">
+        </ul>
+      </div>
+      <div id="dialog-edge" style="display: none;">
+      </div>
+    </div>
+    <div id="summary-dialog" class="mdl-shadow--2dp" style="display: none;">
+      <button id="quit2-dialog" class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect"><i class="material-icons">close</i></button>
+      <button id="max-dialog" class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect"><i class="material-icons">add</i></button>
+      <div id="summary-vertex">
+        <h2 data-fill="name"></h2>
+        <p><span data-fill="year"></span>, <span data-fill="sex"></span>, <span data-fill="id"></span></p>
+      </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 defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
+    <!--<script src="js/service-worker.js"></script>-->
+  </body>
+</html>
diff --git a/img/graf.png b/img/graf.png
new file mode 100644
index 0000000..f97fe5d
--- /dev/null
+++ b/img/graf.png
Binary files differ
diff --git a/img/graf.svg b/img/graf.svg
new file mode 100644
index 0000000..271d998
--- /dev/null
+++ b/img/graf.svg
@@ -0,0 +1,28 @@
+<svg width="194" height="194" xmlns="http://www.w3.org/2000/svg">
+ <g>
+  <title>Layer 1</title>
+  <ellipse fill="#060606" stroke-width="0" fill-opacity="null" cx="97" cy="97" id="svg_10" rx="88" ry="88"/>
+ </g>
+ <g>
+  <title>Layer 2</title>
+  <g id="svg_9">
+   <line fill-opacity="null" x1="48" y1="138" x2="98.5" y2="99" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
+   <line fill-opacity="null" x1="98.5" y1="99" x2="105" y2="150" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
+   <line fill-opacity="null" x1="53" y1="76" x2="149" y2="84" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
+   <line fill-opacity="null" x1="53" y1="76" x2="105" y2="150" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
+   <line fill-opacity="null" x1="98.5" y1="99" x2="149" y2="84" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
+   <line fill-opacity="null" x1="53" y1="76" x2="98.5" y2="99" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
+   <line fill-opacity="null" x1="53" y1="76" x2="121" y2="35" id="svg_1" stroke-linejoin="null" stroke-linecap="null" fill="none" stroke="#FFF" stroke-width="2" stroke-opacity="null"/>
+  </g>
+ </g>
+ <g>
+  <title>Layer 3</title>
+  <ellipse fill="#d61c08" cx="48" cy="138" id="svg_2" rx="8" ry="8"/>
+  <ellipse fill="#0159aa" cx="98.5" cy="99" id="svg_3" rx="8" ry="8"/>
+  <ellipse fill="#0159aa" cx="105" cy="150" id="svg_4" rx="8" ry="8"/>
+  <ellipse fill="#d61c08" cx="53" cy="76" id="svg_5" rx="8" ry="8"/>
+  <ellipse fill="#d61c08" cx="149" cy="84" id="svg_6" rx="8" ry="8"/>
+  <ellipse fill="#0159aa" cx="121" cy="35" id="svg_7" rx="8" ry="8"/>
+  <ellipse fill="#0ca80a" cx="147" cy="123" id="svg_8" rx="8" ry="8"/>
+  </g>
+</svg>
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..ba99d6f
--- /dev/null
+++ b/index.php
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Graf alternatiu FME</title>
+
+    <meta name=viewport content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+
+    <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" />
+
+    <style>
+
+    </style>
+  </head>
+  <body>
+    <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
+      <header class="mdl-layout__header">
+        <div class="mdl-layout__header-row">
+          <!-- Title -->
+          <span class="mdl-layout-title">Graf alternatiu FME</span>
+        </div>
+      </header>
+      <main class="mdl-layout__content">
+        <div class="page-content">
+          <div class="mdl-grid">
+            <div class="mdl-shadow--2dp mdl-cell mdl-cell--4-col mdl-card">
+              <div class="mdl-card__title mdl-card--expand">
+                <h2 class="mdl-card__title-text">El graf</h2>
+              </div>
+              <div class="mdl-card__supporting-text">
+                El graf
+              </div>
+              <div class="mdl-card__actions mdl-card--border">
+                <a href="graf.php" class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect">
+                  Ves-hi
+                </a>
+              </div>
+            </div>
+          </div>
+        </div>
+      </main>
+    </div>
+    <script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
+  </body>
+</html>
diff --git a/js/script.js b/js/script.js
new file mode 100644
index 0000000..4e74231
--- /dev/null
+++ b/js/script.js
@@ -0,0 +1,191 @@
+var s, graf;
+
+function xhr(method, url, params, callback) {
+  var http = new XMLHttpRequest();
+  if (method == "POST") {
+    http.open(method, url, true);
+  } else {
+    if (params != "") {
+      http.open(method, url+"?"+params, true);
+    } else {
+      http.open(method, url, true);
+    }
+  }
+	http.onload = function() {
+    if(this.status != 200) {
+      console.warn("Attention, status code "+this.status+" when loading via xhr url "+url);
+    }
+    callback(this.responseText, this.status);
+  };
+  if (method == "POST") {
+    http.setRequestHeader("Content-type","application/x-www-form-urlencoded");
+    http.send(params);
+  } else {
+    http.send();
+  }
+}
+
+var dialog = {
+  fill: function(data, text, html=false) {
+    var el = document.querySelectorAll("*[data-fill=\""+data+"\"]");
+    for (var i in el) {
+      if (html === true) {
+        el[i].innerHTML = text;
+      } else {
+        el[i].innerText = text;
+      }
+    }
+  },
+  show: function(id, neighbors) {
+    var neighbors = Object.values(neighbors);
+
+    this.fill("name", graf.nodes[id].name);
+    this.fill("year", graf.nodes[id].year);
+    this.fill("sex", graf.nodes[id].sex);
+    this.fill("id", "#"+id);
+    this.fill("n-edges", neighbors.length);
+
+    var list = "";
+    neighbors.forEach(function (a) {
+      list += "<li><b>"+graf.nodes[id].name+" - "+a.label+":</b> "+(graf.edges[id+"_"+a.id] ? graf.edges[id+"_"+a.id].votes : graf.edges[a.id+"_"+id].votes)+" vots</li>";
+    });
+    this.fill("edges", list, true);
+
+    if (window.innerWidth > 700) {
+      document.querySelector("#dialog").style.display = "block";
+      document.querySelector("#backdrop-container").style.display = "block";
+    } else {
+      document.querySelector("#summary-dialog").style.display = "block";
+    }
+  },
+  close: function() {
+    document.querySelector("#dialog").style.display = "none";
+    document.querySelector("#summary-dialog").style.display = "none";
+    document.querySelector("#backdrop-container").style.display = "none";
+
+    s.graph.nodes().forEach(function(n) {
+      n.color = n.originalColor;
+    });
+
+    s.graph.edges().forEach(function(e) {
+      e.color = "#fff";
+    });
+
+    s.refresh();
+  },
+  max: function() {
+    document.querySelector("#summary-dialog").style.display = "none";
+    document.querySelector("#dialog").style.display = "block";
+  },
+  min: function() {
+    document.querySelector("#dialog").style.display = "none";
+    document.querySelector("#summary-dialog").style.display = "block";
+  }
+};
+
+function init() {
+    sigma.classes.graph.addMethod("neighbors", function(nodeId) {
+      var k,
+          neighbors = {},
+          index = this.allNeighborsIndex[nodeId] || [];
+
+      for (k in index) {
+        neighbors[k] = this.nodesIndex[k];
+      }
+
+      return neighbors;
+    });
+
+    s = new sigma({
+      renderers: [{
+        container: "graf",
+        type: "webgl"
+      }],
+      settings: {
+        defaultEdgeColor: "#fff",
+        edgeColor: "default",
+        defaultLabelColor: "#fff",
+        autoRescale: false,
+        zoomMax: 10,
+        //enableEdgeHovering: true,
+        font: "Roboto",
+        labelThreshold: 5
+      }
+    });
+
+    xhr("GET", "api.php", "action=getgraf", function(responseText, status) {
+      graf = JSON.parse(responseText);
+
+      console.log(graf);
+
+      for (var i in graf.nodes) {
+        var ncolor = (graf.nodes[i].sex == "F" ? "#d61c08" : (graf.nodes[i].sex == "M" ? "#0159aa" : "#0ca80a"));
+
+        s.graph.addNode({
+          id: graf.nodes[i].id,
+          label: graf.nodes[i].name,
+          x: graf.nodes[i].x,
+          y: graf.nodes[i].y,
+          size: 10,
+          color: ncolor,
+          originalColor: ncolor
+        });
+      }
+
+      for (var i in graf.edges) {
+        s.graph.addEdge({
+          id: i,
+          source: graf.edges[i].a,
+          target: graf.edges[i].b,
+          size: Math.min(4, Math.max((7/(2*Math.pow(20, 2)))*Math.pow(graf.edges[i].votes, 2) + 1/2, 0.5))
+        });
+      }
+
+      s.bind('clickNode', function(e) {
+        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);
+      });
+
+      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("#zoomin").addEventListener("click", function() {
+        s.camera.goTo({
+          ratio: Math.max(s.camera.settings("zoomMin"), s.camera.ratio / Math.sqrt(2))
+        });
+      });
+
+      document.querySelector("#zoomout").addEventListener("click", function() {
+        s.camera.goTo({
+          ratio: Math.min(s.camera.settings("zoomMax"), s.camera.ratio * Math.sqrt(2))
+        });
+      });
+
+      s.refresh();
+    });
+}
+
+window.addEventListener("load", init);
diff --git a/js/service-worker.js b/js/service-worker.js
new file mode 100644
index 0000000..f5ed189
--- /dev/null
+++ b/js/service-worker.js
@@ -0,0 +1,102 @@
+/*
+ Copyright 2015 Google Inc. All Rights Reserved.
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+'use strict';
+
+// Incrementing CACHE_VERSION will kick off the install event and force previously cached
+// resources to be cached again.
+const CACHE_VERSION = 1;
+let CURRENT_CACHES = {
+  offline: 'offline-v' + CACHE_VERSION
+};
+const OFFLINE_URL = 'offline.html';
+
+function createCacheBustedRequest(url) {
+  let request = new Request(url, {cache: 'reload'});
+  // See https://fetch.spec.whatwg.org/#concept-request-mode
+  // This is not yet supported in Chrome as of M48, so we need to explicitly check to see
+  // if the cache: 'reload' option had any effect.
+  if ('cache' in request) {
+    return request;
+  }
+
+  // If {cache: 'reload'} didn't have any effect, append a cache-busting URL parameter instead.
+  let bustedUrl = new URL(url, self.location.href);
+  bustedUrl.search += (bustedUrl.search ? '&' : '') + 'cachebust=' + Date.now();
+  return new Request(bustedUrl);
+}
+
+self.addEventListener('install', event => {
+  event.waitUntil(
+    // We can't use cache.add() here, since we want OFFLINE_URL to be the cache key, but
+    // the actual URL we end up requesting might include a cache-busting parameter.
+    fetch(createCacheBustedRequest(OFFLINE_URL)).then(function(response) {
+      return caches.open(CURRENT_CACHES.offline).then(function(cache) {
+        return cache.put(OFFLINE_URL, response);
+      });
+    })
+  );
+});
+
+self.addEventListener('activate', event => {
+  // Delete all caches that aren't named in CURRENT_CACHES.
+  // While there is only one cache in this example, the same logic will handle the case where
+  // there are multiple versioned caches.
+  let expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {
+    return CURRENT_CACHES[key];
+  });
+
+  event.waitUntil(
+    caches.keys().then(cacheNames => {
+      return Promise.all(
+        cacheNames.map(cacheName => {
+          if (expectedCacheNames.indexOf(cacheName) === -1) {
+            // If this cache name isn't present in the array of "expected" cache names,
+            // then delete it.
+            console.log('Deleting out of date cache:', cacheName);
+            return caches.delete(cacheName);
+          }
+        })
+      );
+    })
+  );
+});
+
+self.addEventListener('fetch', event => {
+  // We only want to call event.respondWith() if this is a navigation request
+  // for an HTML page.
+  // request.mode of 'navigate' is unfortunately not supported in Chrome
+  // versions older than 49, so we need to include a less precise fallback,
+  // which checks for a GET request with an Accept: text/html header.
+  if (event.request.mode === 'navigate' ||
+      (event.request.method === 'GET' &&
+       event.request.headers.get('accept').includes('text/html'))) {
+    console.log('Handling fetch event for', event.request.url);
+    event.respondWith(
+      fetch(event.request).catch(error => {
+        // The catch is only triggered if fetch() throws an exception, which will most likely
+        // happen due to the server being unreachable.
+        // If fetch() returns a valid HTTP response with an response code in the 4xx or 5xx
+        // range, the catch() will NOT be called. If you need custom handling for 4xx or 5xx
+        // errors, see https://github.com/GoogleChrome/samples/tree/gh-pages/service-worker/fallback-response
+        console.log('Fetch failed; returning offline page instead.', error);
+        return caches.match(OFFLINE_URL);
+      })
+    );
+  }
+
+  // If our if() condition is false, then this fetch handler won't intercept the request.
+  // If there are any other fetch handlers registered, they will get a chance to call
+  // event.respondWith(). If no fetch handlers call event.respondWith(), the request will be
+  // handled by the browser as if there were no service worker involvement.
+});
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..7560119
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,15 @@
+{
+  "short_name": "Graf FME",
+  "name": "El graf alternatiu de la FME",
+  "icons": [
+    {
+      "src": "img/graf.png",
+      "type": "image/png",
+      "sizes": "192x192"
+    }
+  ],
+  "background_color": "#060606",
+  "theme_color": "#060606",
+  "display": "fullscreen",
+  "start_url": "graf.php?utm_source=homescreen"
+}