Browse Source

basic filter support

Nils Schneider 8 years ago
parent
commit
09bdb7d61a
8 changed files with 246 additions and 38 deletions
  1. 76 0
      lib/datadistributor.js
  2. 34 0
      lib/filters/filtergui.js
  3. 32 0
      lib/filters/genericnode.js
  4. 33 0
      lib/filters/nodefilter.js
  5. 19 27
      lib/gui.js
  6. 23 11
      lib/proportions.js
  7. 28 0
      scss/_filters.scss
  8. 1 0
      scss/main.scss

+ 76 - 0
lib/datadistributor.js

@@ -0,0 +1,76 @@
+define([], function () {
+  return function () {
+    var targets = []
+    var filterObservers = []
+    var filters = []
+    var filteredData
+    var data
+
+    function remove(d) {
+      targets = targets.filter( function (e) { return d !== e } )
+    }
+
+    function add(d) {
+      targets.push(d)
+
+      if (filteredData !== undefined)
+        d.setData(filteredData)
+    }
+
+    function setData(d) {
+      data = d
+      refresh()
+    }
+
+    function refresh() {
+      if (data === undefined)
+        return
+
+      filteredData = filters.reduce( function (a, f) {
+        return f.run(a)
+      }, data)
+
+      targets.forEach( function (t) {
+        t.setData(filteredData)
+      })
+    }
+
+    function notifyObservers() {
+      filterObservers.forEach( function (d) {
+        d.filtersChanged(filters)
+      })
+    }
+
+    function addFilter(d) {
+      filters.push(d)
+      notifyObservers()
+      d.setRefresh(refresh)
+      refresh()
+    }
+
+    function removeFilter(d) {
+      filters = filters.filter( function (e) { return d !== e } )
+      notifyObservers()
+      refresh()
+    }
+
+    function watchFilters(d) {
+      filterObservers.push(d)
+
+      d.filtersChanged(filters)
+
+      return function () {
+        filterObservers = filterObservers.filter( function (e) { return d !== e })
+      }
+    }
+
+    return { add: add,
+             remove: remove,
+             setData: setData,
+             addFilter: addFilter,
+             removeFilter: removeFilter,
+             watchFilters: watchFilters,
+             refresh: refresh
+           }
+  }
+})

+ 34 - 0
lib/filters/filtergui.js

@@ -0,0 +1,34 @@
+define([], function () {
+  return function (distributor) {
+    var container = document.createElement("ul")
+    container.classList.add("filters")
+
+    function render(el) {
+      el.appendChild(container)
+    }
+
+    function filtersChanged(filters) {
+      while (container.firstChild)
+        container.removeChild(container.firstChild)
+
+      filters.forEach( function (d) {
+        var li = document.createElement("li")
+        var div = document.createElement("div")
+        container.appendChild(li)
+        li.appendChild(div)
+        d.render(div)
+
+        var button = document.createElement("button")
+        button.textContent = ""
+        button.onclick = function () {
+          distributor.removeFilter(d)
+        }
+        li.appendChild(button)
+      })
+    }
+
+    return { render: render,
+             filtersChanged: filtersChanged
+           }
+  }
+})

+ 32 - 0
lib/filters/genericnode.js

@@ -0,0 +1,32 @@
+define(["filters/nodefilter"], function (nodefilter) {
+  return function (name, key, value, f) {
+    function run(d) {
+      var o = dictGet(d, key.slice(0))
+
+      if (f)
+        o = f(o)
+
+      return o === value
+    }
+
+    function setRefresh() {
+    }
+
+    function render(el) {
+      var label = document.createElement("label")
+      label.textContent = name + " "
+
+      var strong = document.createElement("strong")
+      strong.textContent = value
+
+      label.appendChild(strong)
+
+      el.appendChild(label)
+    }
+
+    return { run: nodefilter(run),
+             setRefresh: setRefresh,
+             render: render
+           }
+  }
+})

+ 33 - 0
lib/filters/nodefilter.js

@@ -0,0 +1,33 @@
+define([], function () {
+  return function (filter) {
+    return function (data) {
+      var n = Object.create(data)
+      n.nodes = {}
+
+      for (var key in data.nodes) {
+        n.nodes[key] = data.nodes[key].filter(filter)
+      }
+
+      var filteredIds = new Set()
+
+      n.graph = {}
+      n.graph.nodes = data.graph.nodes.filter( function (d) {
+        if (!d.node)
+          return true
+
+        var r = filter(d.node)
+
+        if (r)
+          filteredIds.add(d.id)
+
+        return r
+      })
+
+      n.graph.links = data.graph.links.filter( function (d) {
+        return filteredIds.has(d.source.id) && filteredIds.has(d.target.id)
+      })
+
+      return n
+    }
+  }
+})

+ 19 - 27
lib/gui.js

@@ -1,13 +1,13 @@
 define([ "chroma-js", "map", "sidebar", "tabs", "container", "meshstats",
          "legend", "linklist", "nodelist", "simplenodelist", "infobox/main",
-         "proportions", "forcegraph", "title", "about" ],
+         "proportions", "forcegraph", "title", "about", "datadistributor",
+         "filters/filtergui" ],
 function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
           Nodelist, SimpleNodelist, Infobox, Proportions, ForceGraph,
-          Title, About) {
+          Title, About, DataDistributor, FilterGUI) {
   return function (config, router) {
     var self = this
-    var dataTargets = []
-    var latestData
+    var fanout = new DataDistributor()
     var content
     var contentDiv
 
@@ -17,16 +17,13 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
     var buttons = document.createElement("div")
     buttons.classList.add("buttons")
 
-    function dataTargetRemove(d) {
-      dataTargets = dataTargets.filter( function (e) { return d !== e })
-    }
-
     function removeContent() {
       if (!content)
         return
 
       router.removeTarget(content)
-      dataTargetRemove(content)
+      fanout.remove(content)
+
       content.destroy()
 
       content = null
@@ -38,10 +35,7 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
       content = new K(config, linkScale, sidebar.getWidth, router, buttons)
       content.render(contentDiv)
 
-      if (latestData)
-        content.setData(latestData)
-
-      dataTargets.push(content)
+      fanout.add(content)
       router.addTarget(content)
     }
 
@@ -82,15 +76,15 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
     var lostnodeslist = new SimpleNodelist("lost", "lastseen", router, "Verschwundene Knoten")
     var nodelist = new Nodelist(router)
     var linklist = new Linklist(linkScale, router)
-    var statistics = new Proportions(config)
+    var statistics = new Proportions(config, fanout)
     var about = new About()
 
-    dataTargets.push(meshstats)
-    dataTargets.push(newnodeslist)
-    dataTargets.push(lostnodeslist)
-    dataTargets.push(nodelist)
-    dataTargets.push(linklist)
-    dataTargets.push(statistics)
+    fanout.add(meshstats)
+    fanout.add(newnodeslist)
+    fanout.add(lostnodeslist)
+    fanout.add(nodelist)
+    fanout.add(linklist)
+    fanout.add(statistics)
 
     sidebar.add(header)
     header.add(meshstats)
@@ -99,6 +93,10 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
     overview.add(newnodeslist)
     overview.add(lostnodeslist)
 
+    var filterGUI = new FilterGUI(fanout)
+    fanout.watchFilters(filterGUI)
+    header.add(filterGUI)
+
     sidebar.add(tabs)
     tabs.add("Aktuelles", overview)
     tabs.add("Knoten", nodelist)
@@ -114,13 +112,7 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
 
     router.view("m")
 
-    self.setData = function (data) {
-      latestData = data
-
-      dataTargets.forEach(function (d) {
-        d.setData(data)
-      })
-    }
+    self.setData = fanout.setData
 
     return self
   }

+ 23 - 11
lib/proportions.js

@@ -1,7 +1,7 @@
-define(["chroma-js", "virtual-dom", "numeral-intl", "vercomp" ],
-  function (Chroma, V, numeral, vercomp) {
+define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "vercomp" ],
+  function (Chroma, V, numeral, Filter, vercomp) {
 
-  return function (config) {
+  return function (config, filterManager) {
     var self = this
     var scale = Chroma.scale("YlGnBu").mode("lab")
 
@@ -68,10 +68,18 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "vercomp" ],
         dict[v] = 1 + (v in dict ? dict[v] : 0)
       })
 
-      return Object.keys(dict).map(function (d) { return [d, dict[d]] })
+      return Object.keys(dict).map(function (d) { return [d, dict[d], key, f] })
     }
 
-    function fillTable(table, data) {
+    function addFilter(filter) {
+      return function () {
+        filterManager.addFilter(filter)
+
+        return false
+      }
+    }
+
+    function fillTable(name, table, data) {
       if (!table.last)
         table.last = V.h("table")
 
@@ -86,7 +94,11 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "vercomp" ],
         var c1 = Chroma.contrast(scale(v), "white")
         var c2 = Chroma.contrast(scale(v), "black")
 
-        var th = V.h("th", d[0])
+        var filter = new Filter(name, d[2], d[0], d[3])
+
+        var a = V.h("a", { href: "#", onclick: addFilter(filter) }, d[0])
+
+        var th = V.h("th", a)
         var td = V.h("td", V.h("span", {style: {
                                          width: Math.round(v * 100) + "%",
                                          backgroundColor: scale(v).hex(),
@@ -127,11 +139,11 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "vercomp" ],
           return "(deaktiviert)"
       })
 
-      fillTable(statusTable, statusDict.sort(function (a, b) { return b[1] - a[1] }))
-      fillTable(fwTable, fwDict.sort(function (a, b) { return vercomp(b[0], a[0]) }))
-      fillTable(hwTable, hwDict.sort(function (a, b) { return b[1] - a[1] }))
-      fillTable(geoTable, geoDict.sort(function (a, b) { return b[1] - a[1] }))
-      fillTable(autoTable, autoDict.sort(function (a, b) { return b[1] - a[1] }))
+      fillTable("Status", statusTable, statusDict.sort(function (a, b) { return b[1] - a[1] }))
+      fillTable("Firmware", fwTable, fwDict.sort(function (a, b) { return vercomp(b[0], a[0]) }))
+      fillTable("Hardware", hwTable, hwDict.sort(function (a, b) { return b[1] - a[1] }))
+      fillTable("Koordinaten", geoTable, geoDict.sort(function (a, b) { return b[1] - a[1] }))
+      fillTable("Autom. Updates", autoTable, autoDict.sort(function (a, b) { return b[1] - a[1] }))
     }
 
     self.render = function (el) {

+ 28 - 0
scss/_filters.scss

@@ -0,0 +1,28 @@
+.filters {
+  margin: 0;
+  padding: 0 !important;
+
+  li  {
+    display: flex;
+    padding: 6pt 0 6pt 12pt;
+    align-items: center;
+
+    & > div {
+      flex-grow: 1;
+    }
+
+    @include shadow(1);
+  }
+
+  button {
+    box-shadow: none;
+    margin: 0;
+    padding: 0;
+    background: none;
+    font-size: 1.41em;
+
+    &:hover {
+      box-shadow: none !important;
+    }
+  }
+}

+ 1 - 0
scss/main.scss

@@ -3,6 +3,7 @@
 @import '_base';
 @import '_leaflet';
 @import '_leaflet.label';
+@import '_filters';
 
 $minscreenwidth: 630pt;
 $sidebarwidth: 420pt;