1. okfn
  2. wdmmg-treemap

Commits

Stefan Wehrmeyer  committed d4cae70

Refactor Treemap and Timeseries to use backbone controller and views

  • Participants
  • Parent commits d5b52f7
  • Branches default

Comments (0)

Files changed (2)

File theme/public/js/jitload.js

View file
-var labelType, useGradients, nativeTextSupport, animate;
+var OpenSpending = OpenSpending || {};
 
-(function() {
-  var ua = navigator.userAgent,
-      iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
-      typeOfCanvas = typeof HTMLCanvasElement,
-      nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
-      textSupport = nativeCanvasSupport && (typeof document.createElement('canvas').getContext('2d').fillText == 'function');
-  //I'm setting this based on the fact that ExCanvas provides text support for IE
-  //and that as of today iPhone/iPad current text support is lame
-  labelType = (!nativeCanvasSupport || (textSupport && !iStuff))? 'Native' : 'HTML';
-  nativeTextSupport = labelType == 'Native';
-  useGradients = nativeCanvasSupport;
-  animate = !(iStuff || !nativeCanvasSupport);
-})();
+OpenSpending.DatasetPage = {
+    View: {},
+    Controller: {},
+    init: function(config) {
+        var e = new OpenSpending.DatasetPage.Controller(config);
+        Backbone.history.start();
+        return e;
+    }
+};
 
-function init_treemap(json){
-  var tm = new $jit.TM.Squarified({
-    injectInto: 'mainvis',
-    levelsToShow: 1,
-    titleHeight: 0,
-    animate: animate,
-    
-    offset: 2,
-    Label: {
-      type: 'HTML',
-      size: 12,
-      family: 'Tahoma, Verdana, Arial',
-      color: '#DDE7F0'
-      },
-    Node: {
-      color: '#243448',
-      CanvasStyles: {
-        shadowBlur: 0,
-        shadowColor: '#000'
-      }
+OpenSpending.DatasetPage.Controller = Backbone.Controller.extend({
+        routes: {
+            "": "treemap",
+            "timeseries": "timeseries"
+        },
+        initialize: function(config) {
+            this.config = config;
+            this.view = new OpenSpending.DatasetPage.View();
+            $("#_vis_select").change(function(e){
+                window.location.hash = "#"+e.target.value;
+                return false;
+            });
+        },
+        treemap: function(){
+            this.view.renderTreemap(this.config.treemapData);
+        },
+        timeseries: function(){
+            this.view.renderTimeseries(this.config.timeseriesData);
+        }
+});
+
+OpenSpending.DatasetPage.View = Backbone.View.extend({
+    initialize: function() {
+        this.config = {};
+        var ua = navigator.userAgent,
+          iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
+          typeOfCanvas = typeof HTMLCanvasElement,
+          nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
+          textSupport = nativeCanvasSupport && 
+              (typeof document.createElement('canvas').getContext('2d').fillText == 'function');
+        //I'm setting this based on the fact that ExCanvas provides text support for IE
+        //and that as of today iPhone/iPad current text support is lame
+        this.config.labelType = (!nativeCanvasSupport || (textSupport && !iStuff))? 'Native' : 'HTML';
+        this.config.nativeTextSupport = this.config.labelType == 'Native';
+        this.config.useGradients = nativeCanvasSupport;
+        this.config.animate = !(iStuff || !nativeCanvasSupport);
     },
-    Events: {
-      enable: true,
-      onClick: function(node) {
-        if(node) {
-            document.location = node.data.link;
-        }
-      },
-      onRightClick: function() {
-        tm.out();
-      },
-      onMouseEnter: function(node, eventInfo) {
-        if(node) {
-          node.setCanvasStyle('shadowBlur', 8);
-          node.orig_color = node.getData('color');
-          node.setData('color', '#A3B3C7');
-          tm.fx.plotNode(node, tm.canvas);
-          tm.labels.plotLabel(tm.canvas, node);
-        }
-      },
-      onMouseLeave: function(node) {
-        if(node) {
-          node.removeData('color');
-          node.removeCanvasStyle('shadowBlur');
-          node.setData('color', node.orig_color);
-          tm.plot();
-        }
-      }
+    renderTreemap: function(json) {
+        $("#_vis_select").val("treemap");
+        $("#_time_select").css("visibility", "visible");
+        $("#mainvis").html("");
+        this._renderTreemap(json, this.config);
     },
-    duration: 1000,
-    Tips: {
-      enable: true,
-      type: 'Native',
-      offsetX: 20,
-      offsetY: 20,
-      onShow: function(tip, node, isLeaf, domElement) {
-        var html = '<div class="tip-title">' + node.name +
-            ': ' + node.data.printable_value +
-            '</div><div class="tip-text">';
-        var data = node.data;
-        tip.innerHTML =  html; 
-      }  
+    renderTimeseries: function(json) {
+        $("#_vis_select").val("timeseries");
+        $("#_time_select").css("visibility", "hidden");
+        $("#mainvis").html("");
+        this._renderTimeseries(json, this.config);
     },
-    //Implement this method for retrieving a requested  
-    //subtree that has as root a node with id = nodeId,  
-    //and level as depth. This method could also make a server-side  
-    //call for the requested subtree. When completed, the onComplete   
-    //callback method should be called.  
-    request: function(nodeId, level, onComplete){  
-      // var tree = eval('(' + json + ')');
-      var tree = json;  
-      var subtree = $jit.json.getSubtree(tree, nodeId);  
-      $jit.json.prune(subtree, 1);  
-      onComplete.onComplete(nodeId, subtree);  
+    _renderTreemap: function(json, config){
+        var tm = new $jit.TM.Squarified({
+            injectInto: 'mainvis',
+            levelsToShow: 1,
+            titleHeight: 0,
+            animate: config.animate,
+
+            offset: 2,
+            Label: {
+              type: 'HTML',
+              size: 12,
+              family: 'Tahoma, Verdana, Arial',
+              color: '#DDE7F0'
+              },
+            Node: {
+              color: '#243448',
+              CanvasStyles: {
+                shadowBlur: 0,
+                shadowColor: '#000'
+              }
+            },
+            Events: {
+              enable: true,
+              onClick: function(node) {
+                if(node) {
+                    document.location.href = node.data.link;
+                }
+              },
+              onRightClick: function() {
+                tm.out();
+              },
+              onMouseEnter: function(node, eventInfo) {
+                if(node) {
+                  node.setCanvasStyle('shadowBlur', 8);
+                  node.orig_color = node.getData('color');
+                  node.setData('color', '#A3B3C7');
+                  tm.fx.plotNode(node, tm.canvas);
+                  // tm.labels.plotLabel(tm.canvas, node);
+                }
+              },
+              onMouseLeave: function(node) {
+                if(node) {
+                  node.removeData('color');
+                  node.removeCanvasStyle('shadowBlur');
+                  node.setData('color', node.orig_color);
+                  tm.plot();
+                }
+              }
+            },
+            duration: 1000,
+            Tips: {
+              enable: true,
+              type: 'Native',
+              offsetX: 20,
+              offsetY: 20,
+              onShow: function(tip, node, isLeaf, domElement) {
+                var html = '<div class="tip-title">' + node.name +
+                    ': ' + node.data.printable_value +
+                    '</div><div class="tip-text">';
+                var data = node.data;
+                tip.innerHTML =  html; 
+              }  
+            },
+            //Implement this method for retrieving a requested  
+            //subtree that has as root a node with id = nodeId,  
+            //and level as depth. This method could also make a server-side  
+            //call for the requested subtree. When completed, the onComplete   
+            //callback method should be called.  
+            request: function(nodeId, level, onComplete){  
+              // var tree = eval('(' + json + ')');
+              var tree = json;  
+              var subtree = $jit.json.getSubtree(tree, nodeId);  
+              $jit.json.prune(subtree, 1);  
+              onComplete.onComplete(nodeId, subtree);  
+            },
+            //Add the name of the node in the corresponding label
+            //This method is called once, on label creation and only for DOM labels.
+            onCreateLabel: function(domElement, node){
+                //console.log(node);
+                if (node.data.show_title) {
+                    domElement.innerHTML = "<div class='desc'><h2>" + node.data.printable_value + "</h2>" + node.name + "</div>";
+                } else {
+                    domElement.innerHTML = "&nbsp;";
+                }
+            }
+        });
+        tm.loadJSON(json);
+        tm.refresh();
     },
-    //Add the name of the node in the corresponding label
-    //This method is called once, on label creation and only for DOM labels.
-    onCreateLabel: function(domElement, node){
-	    //console.log(node);
-		if (node.data.show_title) {
-            domElement.innerHTML = "<div class='desc'><h2>" + node.data.printable_value + "</h2>" + node.name + "</div>";
-        } else {
-			domElement.innerHTML = "&nbsp;";
-        }
+    _renderTimeseries: function(json, config) {
+        var ac = new $jit.AreaChart({ 
+        injectInto: 'mainvis',
+        levelsToShow: 1,
+        titleHeight: 0,
+        Margin: {
+            top: 5,
+            left: 5,
+            right: 5,
+            bottom: 5
+        },
+        labelOffset: 10,
+        animate: config.animate,
+        showLabels: true,
+        Label: {
+          type: 'HTML',
+          size: 12,
+          family: 'Tahoma, Verdana, Arial',
+          color: '#000'
+        },
+
+        Node: {
+          /*color: '#243448',*/
+          CanvasStyles: {
+            shadowBlur: 0,
+            shadowColor: '#000'
+          }
+        },
+        Events: {
+            enable: true,
+            onClick: function(node) {
+                console.log(node);
+                if(node) {
+                    document.location.href = json.details[node.name].link;
+                }
+            },
+            onRightClick: function() {
+                ac.out();
+            }
+        },
+        duration: 500,
+        Tips: {
+            enable: true,
+              type: 'Native',
+              offsetX: 20,
+              offsetY: 20,
+              onShow: function(tip, elem) {
+                 tip.innerHTML = "<b>" +
+                     json.details[elem.name].title +
+                    "</b>: " +
+                    OpenSpending.Utils.formatAmount(elem.value);
+                  }
+            }
+        });
+        ac.colors = json.colors;
+        var temp = ac.st.config.onPlaceLabel;
+        ac.st.config.onPlaceLabel = function(domElement, node){
+            temp(domElement, node);
+            var el = $($("div >div", domElement)[1]);
+            if (el) {
+                el.text(OpenSpending.Utils.formatAmount(el.text()));
+            }
+        };
+        ac.loadJSON(json);
     }
-  });
-  tm.loadJSON(json);
-  tm.refresh();
-}
-
-
-function init_timeseries(json) {
-  var ac = new $jit.AreaChart({ 
-    injectInto: 'mainvis',
-    levelsToShow: 1,
-    titleHeight: 0,
-    animate: animate,
-    
-    offset: 2,
-    Label: {
-      type: 'HTML',
-      size: 12,
-      family: 'Tahoma, Verdana, Arial',
-      color: '#DDE7F0'
-      },
-    Node: {
-      /*color: '#243448',*/
-      CanvasStyles: {
-        shadowBlur: 0,
-        shadowColor: '#000'
-      }
-    },
-    Events: {
-      enable: true,
-      onClick: function(node) {
-        if(node) {
-            document.location = node.data.link;
-        }
-      },
-      onRightClick: function() {
-        tm.out();
-      },
-    },
-    duration: 500,
-    Tips: {
-      enable: true,
-      type: 'Native',
-      offsetX: 20,
-      offsetY: 20,
-      onShow: function(tip, elem) {
-         tip.innerHTML = "<b>" + json.details[elem.name].title + "</b>: " + elem.value;
-      }
-    }
-  });
-  ac.loadJSON(json);
-}
+});

File wdmmgext/treemap/plugin.py

View file
 <script src="/js/jitload.js"></script>
 <script>
 $(document).ready(function() {
-    var treemap_data = %s;
-    var timeseries_data = %s;
-    init_treemap(treemap_data);
-    $("#_vis_select").change(function(e){
-        $("#mainvis").html("");
-        if (e.target.value == 'treemap') {
-            init_treemap(treemap_data);
-        } else {
-            init_timeseries(timeseries_data);
-        }
-        return false;
+    OpenSpending.DatasetPage.init({
+        treemapData: %s,
+        timeseriesData: %s
     });
 });
 </script>
 """
 
 VIS_SELECT_SNIPPET = """
-<form id="_vis_form">
-    <select name="_vis" id="_vis_select"> 
+    <select id="_vis_select"> 
         <option value="treemap">Composition</option>
         <option value="timeseries">Time Series</option>
     </select>
-</form>
 """
 
 BODY_SNIPPET = """
             ts_json = self._generate_ts_json(c.viewstate.aggregates, c.times)
             if tree_json is not None:
                 stream = stream | Transformer('//form[@id="_time_form"]')\
-                   .append(HTML(VIS_SELECT_SNIPPET))
+                   .prepend(HTML(VIS_SELECT_SNIPPET))
                 stream = stream | Transformer('html/head')\
                    .append(HTML(HEAD_SNIPPET % (tree_json, ts_json)))
                 stream = stream | Transformer('//div[@id="vis"]')\
                     .append(HTML(BODY_SNIPPET))
-        return stream 
+        return stream
+
+    def _get_color(self, obj, aggregates, time_values):
+        if isinstance(obj, dict) and 'color' in obj:
+            return obj.get('color') 
+        elif isinstance(obj, dict):
+            pcolor = parent_color(obj)
+            crange = list(color_range(pcolor, len(aggregates)))
+            return list(crange)[aggregates.index((obj, time_values))]
+        else:
+            return '#333333'
 
     def _generate_tree_json(self, aggregates, time, total):
         from wdmmg.lib.helpers import dimension_url, render_value
             value = time_values.get(time)
             if value <= 0: 
                 continue
-            if isinstance(obj, dict) and 'color' in obj:
-                color = obj.get('color') 
-            elif isinstance(obj, dict):
-                pcolor = parent_color(obj)
-                crange = list(color_range(pcolor, len(aggregates)))
-                color = list(crange)[aggregates.index((obj, time_values))]
-            else:
-                color = '#333333'
+            color = self._get_color(obj, aggregates, time_values)
             show_title = (value/max(1,total)) > TITLE_CUTOFF
             field = {'children': [],
                      'id': str(obj.get('_id')) if isinstance(obj, dict) else hash(obj),
         to_id = lambda obj: str(obj.get('_id')) if isinstance(obj, dict) else hash(obj)
         label = [to_id(o) for o, tv in aggregates]
         values = []
+        colored = False
+        colors = []
         for time in times:
             _values = []
             for obj, time_values in aggregates:
+                if not colored:
+                    colors.append(self._get_color(obj, aggregates, time_values))
                 _values.append(time_values.get(time, 0))
             values.append({"label": str(time), "values": _values})
+            colored = True
         details = {}
         for obj, ts in aggregates:
             props = {
                     'title': render_value(obj),
                     'link': dimension_url(obj),
-                    'color': '#333333'
                     }
             details[to_id(obj)] = props
         if not len(values):
         return json.dumps({
             'label': label,
             'details': details,
-            'values': values
+            'values': values,
+            'colors': colors
             })