diff --git a/app.js b/app.js index 88f666b..b60fa0b 100644 --- a/app.js +++ b/app.js @@ -7,6 +7,7 @@ require.config({ "moment": "../bower_components/moment/min/moment-with-locales.min", "tablesort": "../bower_components/tablesort/tablesort.min", "tablesort.numeric": "../bower_components/tablesort/src/sorts/tablesort.numeric", + "chartjs": "../bower_components/chartjs/Chart", "helper": "../helper" }, shim: { diff --git a/bower.json b/bower.json index 0c3b47e..238b124 100644 --- a/bower.json +++ b/bower.json @@ -19,7 +19,8 @@ "roboto-slab-fontface": "*", "es6-shim": "~0.27.1", "almond": "~0.3.1", - "r.js": "~2.1.16" + "r.js": "~2.1.16", + "chartjs": "~1.0.2" }, "authors": [ "Nils Schneider " diff --git a/lib/gui.js b/lib/gui.js index c4bb8ed..33a89c7 100644 --- a/lib/gui.js +++ b/lib/gui.js @@ -1,7 +1,8 @@ define([ "chroma-js", "map", "sidebar", "tabs", "container", "meshstats", - "linklist", "nodelist", "simplenodelist", "infobox/main" ], + "linklist", "nodelist", "simplenodelist", "infobox/main", + "proportions" ], function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Linklist, - Nodelist, SimpleNodelist, Infobox) { + Nodelist, SimpleNodelist, Infobox, Proportions) { return function (config, router) { var self = this var dataTargets = [] @@ -20,6 +21,7 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Linklist, var lostnodeslist = new SimpleNodelist(config, "lost", "lastseen", router, "Verschwundene Knoten") var nodelist = new Nodelist(router) var linklist = new Linklist(linkScale, router) + var statistics = new Proportions() dataTargets.push(map) dataTargets.push(meshstats) @@ -27,6 +29,7 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Linklist, dataTargets.push(lostnodeslist) dataTargets.push(nodelist) dataTargets.push(linklist) + dataTargets.push(statistics) overview.add(meshstats) overview.add(newnodeslist) @@ -36,6 +39,7 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Linklist, tabs.add("Übersicht", overview) tabs.add("Alle Knoten", nodelist) tabs.add("Verbindungen", linklist) + tabs.add("Statistiken", statistics) router.addTarget(infobox) router.addTarget(map) diff --git a/lib/proportions.js b/lib/proportions.js new file mode 100644 index 0000000..e2d7c23 --- /dev/null +++ b/lib/proportions.js @@ -0,0 +1,98 @@ +define(["chartjs", "chroma-js"], function (ChartJS, Chroma) { + return function () { + var self = this + var fwTable, hwTable, autoTable + var scale = Chroma.scale("YlGnBu").mode("lab") + + function count(nodes, key, def, f) { + var dict = {} + + nodes.forEach( function (d) { + var v = dictGet(d, key.slice(0)) + + if (f !== undefined) + v = f(v) + + if (v === null) + v = def + + dict[v] = 1 + (v in dict ? dict[v] : 0) + }) + + return Object.keys(dict).map(function (d) { return [d, dict[d]] }) + } + + function fillTable(table, data) { + var max = 0 + data.forEach(function (d) { + if (d[1] > max) + max = d[1] + }) + + data.forEach(function (d) { + var v = d[1] / max + var row = document.createElement("tr") + var th = document.createElement("th") + var td = document.createElement("td") + var span = document.createElement("span") + th.textContent = d[0] + span.style.width = Math.round(v * 100) + "%" + span.style.backgroundColor = scale(v).hex() + var c1 = Chroma.contrast(scale(v), "white") + var c2 = Chroma.contrast(scale(v), "black") + span.style.color = c1 > c2 ? "white" : "black" + span.textContent = d[1] + td.appendChild(span) + row.appendChild(th) + row.appendChild(td) + table.appendChild(row) + }) + } + + self.setData = function (data) { + var nodes = data.nodes.all.filter(online).concat(data.nodes.lost) + + var fwDict = count(nodes, ["nodeinfo", "software", "firmware", "release"], "n/a") + var hwDict = count(nodes, ["nodeinfo", "hardware", "model"], "n/a") + var autoDict = count(nodes, ["nodeinfo", "software", "autoupdater"], "deaktiviert", function (d) { + if (d === null || !d.enabled) + return null + else + return d.branch + }) + + fillTable(fwTable, fwDict.sort(function (a, b) { return b[1] - a[1] })) + fillTable(hwTable, hwDict.sort(function (a, b) { return b[1] - a[1] })) + fillTable(autoTable, autoDict.sort(function (a, b) { return b[1] - a[1] })) + } + + self.render = function (el) { + var h2 + h2 = document.createElement("h2") + h2.textContent = "Firmwareversionen" + el.appendChild(h2) + + fwTable = document.createElement("table") + fwTable.classList.add("proportion") + el.appendChild(fwTable) + + h2 = document.createElement("h2") + h2.textContent = "Hardwaremodelle" + el.appendChild(h2) + + hwTable = document.createElement("table") + hwTable.classList.add("proportion") + el.appendChild(hwTable) + + h2 = document.createElement("h2") + h2.textContent = "Autoupdater" + el.appendChild(h2) + + autoTable = document.createElement("table") + autoTable.classList.add("proportion") + el.appendChild(autoTable) + } + + return self + } +}) diff --git a/style.css b/style.css index 6a43e39..cb58bc1 100644 --- a/style.css +++ b/style.css @@ -237,6 +237,31 @@ button.close:after { animation-iteration-count: infinite; } +.proportion th { + font-weight: normal; + text-align: right !important; + font-size: 0.95em; +} + +.proportion td { + text-align: left !important; + width: 100%; +} + +.proportion td, .proportion th { + white-space: nowrap; +} + +.proportion span { + display: inline-block; + height: 1.4em; + background: black; + padding: 0 0.5em; + font-weight: bold; + min-width: 1.5em; + box-sizing: border-box; +} + @-webkit-keyframes blink { 0% { opacity: 1.0; } 80% { opacity: 1.0; }