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"
+}