added draft maddash plugin
authorJordan Augé <jordan.auge@lip6.fr>
Tue, 3 Dec 2013 16:35:05 +0000 (17:35 +0100)
committerJordan Augé <jordan.auge@lip6.fr>
Tue, 3 Dec 2013 16:35:05 +0000 (17:35 +0100)
plugins/maddash/__init__.py [new file with mode: 0644]
plugins/maddash/static/css/maddash.css [new file with mode: 0644]
plugins/maddash/static/css/tipsy.css [new file with mode: 0644]
plugins/maddash/static/img/tipsy.gif [new file with mode: 0644]
plugins/maddash/static/js/.maddash.js.swp [new file with mode: 0644]
plugins/maddash/static/js/jquery.tipsy.js [new file with mode: 0644]
plugins/maddash/static/js/maddash.js [new file with mode: 0644]
plugins/maddash/templates/maddash.html [new file with mode: 0644]

diff --git a/plugins/maddash/__init__.py b/plugins/maddash/__init__.py
new file mode 100644 (file)
index 0000000..0447ff4
--- /dev/null
@@ -0,0 +1,40 @@
+from unfold.plugin import Plugin
+
+class MadDash (Plugin):
+
+    # set checkboxes if a final column with checkboxes is desired
+    # pass columns as the initial set of columns
+    #   if None then this is taken from the query's fields
+    # latitude,longitude, zoom : the starting point
+    def __init__ (self, query = None, query_all = None, **settings):
+        Plugin.__init__ (self, **settings)
+        self.query=query
+        self.query_all = query_all
+        self.query_all_uuid = query_all.query_uuid if query_all else None
+
+    def template_file (self):
+        return "maddash.html"
+
+    def template_env (self, request):
+        env={}
+        return env
+
+    def requirements (self):
+        reqs = {
+            'js_files' : [ 
+                'http://d3js.org/d3.v3.min.js',
+                'js/jquery.tipsy.js',
+                'js/buffer.js', 'js/maddash.js',
+                'js/manifold.js', 'js/manifold-query.js',
+                'js/spin.presets.js', 'js/spin.min.js', 'js/jquery.spin.js', 
+                'js/unfold-helper.js',
+            ],
+            'css_files' : [ 
+                'css/maddash.css',
+                'css/tipsy.css',
+            ],
+        }
+        return reqs
+
+    # the list of things passed to the js plugin
+    def json_settings_list (self): return ['plugin_uuid','query_uuid', 'query_all_uuid']
diff --git a/plugins/maddash/static/css/maddash.css b/plugins/maddash/static/css/maddash.css
new file mode 100644 (file)
index 0000000..93ece29
--- /dev/null
@@ -0,0 +1,37 @@
+#maddash__maddash {
+       height: 600px;
+}
+
+.grid-container{float:left;overflow:hidden;margin-right:10px;}
+.gcol{position:absolute;top:0px;}
+.gactive{background:#3c3c3c; fill:#3c3c3c}
+.grow{overflow:hidden;clear:both;z-index:1;position:relative;}
+.grow-heading{float:left;margin:1px 0px;}
+.gtext{font-family:verdana;font-size:10px;fill:#242424}
+.gactive .gtext {fill:#fff;font-size:11px;}
+.gcell{float:left;cursor:pointer;background:none;}
+.shadow {
+/*        -moz-box-shadow: 0 0 30px 5px #000;
+      -webkit-box-shadow: 0 0 30px 5px #000;
+*/          border:1px solid #fff;
+}
+.ggrid{float:left;position:relative;}
+.gleft{float:left;clear:both}
+.gtop{}
+.gsubcell{float:left;}
+rect{fill:none;}
+.tooltip{text-align:left;font-size:12px;}
+.tooltip .top-tip{padding-bottom:2px;border-bottom:1px solid #fff}
+.tooltip .bottom-tip{padding-top:2px;}  
+.legends{
+        background-color:#fff7fb;
+        border:1px solid #d0d1e6;
+        margin: 5px 5px 5px 5px;
+        font-size:11px;
+        float: left;
+        min-width: 97%;
+        
+}
+.legend{float:left;margin: 4px 10px 4px 10px;}
+.lsymbol{float:left;width:13px;height:13px;margin-right:3px;}
+.ltext{float:left;line-height:13px;font-style:normal;color:#252525}
diff --git a/plugins/maddash/static/css/tipsy.css b/plugins/maddash/static/css/tipsy.css
new file mode 100644 (file)
index 0000000..ae04cd9
--- /dev/null
@@ -0,0 +1,12 @@
+.tipsy { padding: 5px; font-size: 10px; position: absolute; z-index: 100000; }
+  .tipsy-inner { padding: 5px 8px 4px 8px; background-color: #3c3c3c; color: white; max-width: 200px; text-align: center;}
+  .tipsy-inner { border-radius: 3px; -moz-border-radius:3px; -webkit-border-radius:3px; }
+  .tipsy-arrow { position: absolute; background: url('tipsy.gif') no-repeat top left; width: 9px; height: 5px; }
+  .tipsy-n .tipsy-arrow { top: 0; left: 50%; margin-left: -4px; }
+    .tipsy-nw .tipsy-arrow { top: 0; left: 10px; }
+    .tipsy-ne .tipsy-arrow { top: 0; right: 10px; }
+  .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -4px; background-position: bottom left; }
+    .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; background-position: bottom left; }
+    .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; background-position: bottom left; }
+  .tipsy-e .tipsy-arrow { top: 50%; margin-top: -4px; right: 0; width: 5px; height: 9px; background-position: top right; }
+  .tipsy-w .tipsy-arrow { top: 50%; margin-top: -4px; left: 0; width: 5px; height: 9px; }
\ No newline at end of file
diff --git a/plugins/maddash/static/img/tipsy.gif b/plugins/maddash/static/img/tipsy.gif
new file mode 100644 (file)
index 0000000..74eebae
Binary files /dev/null and b/plugins/maddash/static/img/tipsy.gif differ
diff --git a/plugins/maddash/static/js/.maddash.js.swp b/plugins/maddash/static/js/.maddash.js.swp
new file mode 100644 (file)
index 0000000..e8559a9
Binary files /dev/null and b/plugins/maddash/static/js/.maddash.js.swp differ
diff --git a/plugins/maddash/static/js/jquery.tipsy.js b/plugins/maddash/static/js/jquery.tipsy.js
new file mode 100644 (file)
index 0000000..2eb567a
--- /dev/null
@@ -0,0 +1,198 @@
+// tipsy, facebook style tooltips for jquery
+// version 1.0.0a
+// (c) 2008-2010 jason frame [jason@onehackoranother.com]
+// releated under the MIT license
+
+(function($) {
+    
+    function fixTitle($ele) {
+        if ($ele.attr('title') || typeof($ele.attr('original-title')) != 'string') {
+            $ele.attr('original-title', $ele.attr('title') || '').removeAttr('title');
+        }
+    }
+    
+    function Tipsy(element, options) {
+        this.$element = $(element);
+        this.options = options;
+        this.enabled = true;
+        fixTitle(this.$element);
+    }
+    
+    Tipsy.prototype = {
+        show: function() {
+            var title = this.getTitle();
+            if (title && this.enabled) {
+                var $tip = this.tip();
+                
+                $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
+                $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
+                $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body);
+                
+                var pos = $.extend({}, this.$element.offset(), {
+                    width: this.$element[0].offsetWidth,
+                    height: this.$element[0].offsetHeight
+                });
+                
+                var actualWidth = $tip[0].offsetWidth, actualHeight = $tip[0].offsetHeight;
+                var gravity = (typeof this.options.gravity == 'function')
+                                ? this.options.gravity.call(this.$element[0])
+                                : this.options.gravity;
+                
+                var tp;
+                switch (gravity.charAt(0)) {
+                    case 'n':
+                        tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
+                        break;
+                    case 's':
+                        tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
+                        break;
+                    case 'e':
+                        tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
+                        break;
+                    case 'w':
+                        tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
+                        break;
+                }
+                
+                if (gravity.length == 2) {
+                    if (gravity.charAt(1) == 'w') {
+                        tp.left = pos.left + pos.width / 2 - 15;
+                    } else {
+                        tp.left = pos.left + pos.width / 2 - actualWidth + 15;
+                    }
+                }
+                
+                $tip.css(tp).addClass('tipsy-' + gravity);
+                
+                if (this.options.fade) {
+                    $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
+                } else {
+                    $tip.css({visibility: 'visible', opacity: this.options.opacity});
+                }
+            }
+        },
+        
+        hide: function() {
+            if (this.options.fade) {
+                this.tip().stop().fadeOut(function() { $(this).remove(); });
+            } else {
+                this.tip().remove();
+            }
+        },
+        
+        getTitle: function() {
+            var title, $e = this.$element, o = this.options;
+            fixTitle($e);
+            var title, o = this.options;
+            if (typeof o.title == 'string') {
+                title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
+            } else if (typeof o.title == 'function') {
+                title = o.title.call($e[0]);
+            }
+            title = ('' + title).replace(/(^\s*|\s*$)/, "");
+            return title || o.fallback;
+        },
+        
+        tip: function() {
+            if (!this.$tip) {
+                this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"/></div>');
+            }
+            return this.$tip;
+        },
+        
+        validate: function() {
+            if (!this.$element[0].parentNode) {
+                this.hide();
+                this.$element = null;
+                this.options = null;
+            }
+        },
+        
+        enable: function() { this.enabled = true; },
+        disable: function() { this.enabled = false; },
+        toggleEnabled: function() { this.enabled = !this.enabled; }
+    };
+    
+    $.fn.tipsy = function(options) {
+        
+        if (options === true) {
+            return this.data('tipsy');
+        } else if (typeof options == 'string') {
+            return this.data('tipsy')[options]();
+        }
+        
+        options = $.extend({}, $.fn.tipsy.defaults, options);
+        
+        function get(ele) {
+            var tipsy = $.data(ele, 'tipsy');
+            if (!tipsy) {
+                tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
+                $.data(ele, 'tipsy', tipsy);
+            }
+            return tipsy;
+        }
+        
+        function enter() {
+            var tipsy = get(this);
+            tipsy.hoverState = 'in';
+            if (options.delayIn == 0) {
+                tipsy.show();
+            } else {
+                setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
+            }
+        };
+        
+        function leave() {
+            var tipsy = get(this);
+            tipsy.hoverState = 'out';
+            if (options.delayOut == 0) {
+                tipsy.hide();
+            } else {
+                setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
+            }
+        };
+        
+        if (!options.live) this.each(function() { get(this); });
+        
+        if (options.trigger != 'manual') {
+            var binder   = options.live ? 'live' : 'bind',
+                eventIn  = options.trigger == 'hover' ? 'mouseenter' : 'focus',
+                eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
+            this[binder](eventIn, enter)[binder](eventOut, leave);
+        }
+        
+        return this;
+        
+    };
+    
+    $.fn.tipsy.defaults = {
+        delayIn: 0,
+        delayOut: 0,
+        fade: false,
+        fallback: '',
+        gravity: 'n',
+        html: false,
+        live: false,
+        offset: 0,
+        opacity: 0.8,
+        title: 'title',
+        trigger: 'hover'
+    };
+    
+    // Overwrite this method to provide options on a per-element basis.
+    // For example, you could store the gravity in a 'tipsy-gravity' attribute:
+    // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
+    // (remember - do not modify 'options' in place!)
+    $.fn.tipsy.elementOptions = function(ele, options) {
+        return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
+    };
+    
+    $.fn.tipsy.autoNS = function() {
+        return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
+    };
+    
+    $.fn.tipsy.autoWE = function() {
+        return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
+    };
+    
+})(jQuery);
\ No newline at end of file
diff --git a/plugins/maddash/static/js/maddash.js b/plugins/maddash/static/js/maddash.js
new file mode 100644 (file)
index 0000000..110eec1
--- /dev/null
@@ -0,0 +1,583 @@
+/**
+ * MyPlugin:    MadDash
+ * Version:     0.1
+ * Description: Template for writing new plugins and illustrating the different
+ *              possibilities of the plugin API.
+ *              This file is part of the Manifold project 
+ * Requires:    js/plugin.js
+ * URL:         http://www.myslice.info
+ * Author:      Jordan Augé <jordan.auge@lip6.fr>
+ * Copyright:   Copyright 2012-2013 UPMC Sorbonne Universités
+ * License:     GPLv3
+ */
+var SVG='http://www.w3.org/2000/svg';
+var instance = this;
+var colorscale = d3.scale.category10().range(["green", "yellow", "red", "orange", "gray"]);
+
+/* XXX */
+/**
+ * Class: MaDDashGrid
+ * Description: Widget that displays grid of checks. Uses
+ *   d3 and jQuery to draw the grid.
+ * Parameters:
+ *      parentId: id string of the container element
+ *      legendId: id string of the legend element
+ */
+
+(function($){
+
+    var MadDash = Plugin.extend({
+
+        /** XXX to check
+         * @brief Plugin constructor
+         * @param options : an associative array of setting values
+         * @param element : 
+         * @return : a jQuery collection of objects on which the plugin is
+         *     applied, which allows to maintain chainability of calls
+         */
+        init: function(options, element) {
+            // Call the parent constructor, see FAQ when forgotten
+            this._super(options, element);
+
+
+            /* Member variables */
+            //this.canvas = this.id('canvas');
+            this._legend = this.id('legend'); //legendId;
+            this._labels = Array(
+                'un', 'deux', 'trois', 'quatre'
+            )
+
+            this._cellsize = 13;
+            this._cellpadding = 2;
+            this._text_block_size = 130;
+
+            this._map_elements = {};
+            this._num_elements = 0;
+
+            this._max_width_elements  = 50;
+            this._max_height_elements = 30;
+
+            this._buffer_key_list = new Buffer(this._process_key_list, this);
+            this._buffer_records  = new Buffer(this._process_records, this);
+
+            /* Pointers */
+            this._canvas       = d3.select("#" + options.plugin_uuid + '__canvas')
+                .style("width", this._max_width_elements * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size)
+                .style("height", this._max_height_elements * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size)
+            this._left_element = null;
+            this._top_element  = null;
+            this._row_element  = Array();
+            this._grid_element = null;
+            
+
+
+            /* Buffered input */
+//            this._buffer = Array();
+//            this._update_interval = 1000; /* ms to wait, 1000 = 1 second */
+//            setInterval(function(){
+//                var tmp = buffer; /* Switch the buffer out quickly so we aren't scrambled 
+//                                     if you addToBuffer in the middle of this */
+//                buffer = Array();
+//                $thisdocument.getElementById(htmlId).innerHTML = tmp.join("");
+//                //document.getElementById(htmlId).innerHTML = tmp.join("");
+//            }, wait);
+//
+//addToBuffer = function(html){
+//    buffer.push(html);
+//};
+
+            /* Plugin events */
+
+            /* Setup query and record handlers */
+
+            // Explain this will allow query events to be handled
+            // What happens when we don't define some events ?
+            // Some can be less efficient
+            this.listen_query(options.query_uuid);
+            this.listen_query(options.query_all_uuid, 'all');
+
+            /* GUI setup and event binding */
+            // call function
+            this._display_legends();
+            this._init_top();
+            this._init_left();
+            this._init_grid();
+            //this._test();
+
+        },
+
+        _test: function()
+        {
+            data = {
+                "name":"OWAMP",
+                "statusLabels":[
+                    "Loss is 0",null,"Loss is greater than 0","Unable to retrieve data","Check has not yet run"
+                ],
+                "lastUpdateTime":1385376538,
+                "rows":[
+                    {"name":"200.128.79.100","uri":"/maddash/grids/OWAMP/200.128.79.100"},
+                    {"name":"ata.larc.usp.br","uri":"/maddash/grids/OWAMP/ata.larc.usp.br"},
+                    {"name":"mon-lt.fibre.cin.ufpe.br","uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br"}
+                ],
+                "columnNames":[
+                    "200.128.79.100","ata.larc.usp.br","mon-lt.fibre.cin.ufpe.br"
+                ],
+                "checkNames": ["Loss","Loss Reverse"],
+                "grid": [
+                    [
+                        /* First line */
+                        null,
+                        [{
+                            "message":" No one-way delay data returned for direction where src=200.128.79.100 dst=ata.larc.usp.br",
+                            "status":3,
+                            "prevCheckTime":1385376238,
+                            "uri":"/maddash/grids/OWAMP/200.128.79.100/ata.larc.usp.br/Loss"
+                        },{
+                            "message":" No one-way delay data returned for direction where src=ata.larc.usp.br dst=200.128.79.100",
+                            "status":2,"prevCheckTime":1385376178,
+                            "uri":"/maddash/grids/OWAMP/200.128.79.100/ata.larc.usp.br/Loss+Reverse"
+                        }],[{
+                            "message":" Loss is 100.000% ",
+                            "status":2,
+                            "prevCheckTime":1385374877,
+                            "uri":"/maddash/grids/OWAMP/200.128.79.100/mon-lt.fibre.cin.ufpe.br/Loss"
+                        },{
+                            "message":" Loss is 100.000% ",
+                            "status":2,
+                            "prevCheckTime":1385375498,
+                            "uri":"/maddash/grids/OWAMP/200.128.79.100/mon-lt.fibre.cin.ufpe.br/Loss+Reverse"
+                        }]
+                    ],[
+                        /* Second line */
+                        [{
+                            "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
+                            "status":3,
+                            "prevCheckTime":1385376037,
+                            "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/200.128.79.100/Loss"
+                        }, {
+                            "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
+                            "status":3,
+                            "prevCheckTime":1385376117,
+                            "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/200.128.79.100/Loss+Reverse"
+                        }],
+                        null,
+                        [{
+                            "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
+                            "status":3,
+                            "prevCheckTime":1385376117,
+                            "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/mon-lt.fibre.cin.ufpe.br/Loss"
+                        }, {
+                            "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
+                            "status":3,
+                            "prevCheckTime":1385376017,
+                            "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/mon-lt.fibre.cin.ufpe.br/Loss+Reverse"
+                        }]
+                    ],[
+                        /* Third line */
+                        [{
+                            "message":" Loss is 100.000% ",
+                            "status":2,
+                            "prevCheckTime":1385376478,
+                            "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/200.128.79.100/Loss"
+                        },{
+                            "message":" Loss is 100.000% ",
+                            "status":2,
+                            "prevCheckTime":1385375958,
+                            "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/200.128.79.100/Loss+Reverse"
+                        }], [{
+                            "message":" No one-way delay data returned for direction where src=mon-lt.fibre.cin.ufpe.br dst=ata.larc.usp.br",
+                            "status":3,
+                            "prevCheckTime":1385376538,
+                            "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/ata.larc.usp.br/Loss"
+                        },{
+                            "message":" No one-way delay data returned for direction where src=ata.larc.usp.br dst=mon-lt.fibre.cin.ufpe.br",
+                            "status":3,
+                            "prevCheckTime":1385376358,
+                            "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/ata.larc.usp.br/Loss+Reverse"
+                        }],
+                        null
+                    ]
+                ]
+            }
+            this._render(data);
+        },
+
+        /* ------------------------------------------------------------------ */
+        /* Accessors                                                          */
+        /* ------------------------------------------------------------------ */
+
+        setClickHandler: function(f)
+        {
+            this._handleClick = f;
+        },
+
+        setCellSize: function(value)
+        {
+            this._cellSize = value;
+        },
+
+        setCellPadding: function(value)
+        {
+            this._cellPadding = value;
+        },
+
+        setTextBlockSize: function(value)
+        {
+            this._textBlockSize = value;
+        },
+
+
+        
+
+        /* PLUGIN EVENTS */
+        // on_show like in querytable
+
+
+        /* GUI EVENTS */
+
+        // a function to bind events here: click change
+        // how to raise manifold events
+
+
+        /* GUI MANIPULATION */
+
+        /* XXX */
+
+
+        /* TEMPLATES */
+
+        // see in the html template
+        // How to load a template, use of mustache
+
+        /* QUERY HANDLERS */
+
+        // How to make sure the plugin is not desynchronized
+        // He should manifest its interest in filters, fields or records
+        // functions triggered only if the proper listen is done
+
+        // no prefix
+
+        on_filter_added: function(filter)
+        {
+
+        },
+
+        // ... be sure to list all events here
+
+        /* RECORD HANDLERS */
+        on_all_new_record: function(record)
+        {
+            var key_value = record['hrn'];
+            if (!(this._map_elements.hasOwnProperty(key_value))) {
+                /* Add the key_value to the buffer to be drawn */
+                this._buffer_key_list.add(key_value);
+                /* Assign coordinates */
+                this._map_elements[key_value] = this._num_elements++;
+            }
+            /* Add the record to the buffer to be drawn */
+            this._buffer_records.add(record);
+        },
+
+        /* INTERNAL FUNCTIONS */
+
+        _render: function(data)
+        {
+            //TODO: Set title
+            //d3.select("#dashboard_name").html(dashboard.name + " Dashboard");
+            // XXX OLD XXX d3.select("#" + this.parent).html("");
+            //this.elmt().html('')
+
+            this.display_component(data);
+        },
+
+        _init_left: function()
+        {
+            var self = this;
+            this._left_element = this._canvas.append("div")
+              .attr("class", "gleft")
+                .style("overflow-y", "scroll")
+                .style("height", "400px")
+              .append("svg:svg")
+                .attr("width", self._text_block_size)
+                .attr("height", self._max_height_elements * (self._cellsize + 2*self._cellpadding) + 1000)
+
+        },
+
+        _process_left: function(key_list)
+        {
+            var self = this;
+
+            this._left_element = this._left_element
+              .selectAll(".rname")
+                .data(key_list)
+                .enter()
+                  .append("g")
+                  .attr("class", function(d,i){return "grow" + i})
+                  .attr("transform", function(d,i){return "translate(0,"+(i*(self._cellsize+2*self._cellpadding))+")"})
+      
+            this._left_element.append("svg:rect")
+              .attr("class", function(d,i){return "grow" + i})
+              .attr("x",0).attr("y",0)
+              .attr("width",this._text_block_size).attr("height",(this._cellsize+2*this._cellpadding))
+      
+            this._left_element.append("svg:text")
+              .attr("class", "gtext")
+              .attr("transform", "translate("+ (this._text_block_size-5) +",0)")
+              .text(function(d,i){return d}) //strdata.rows[i].name})
+              .attr("text-anchor", "end")
+              .attr("dy", "1.1em")
+
+            // XXX Let's generate fake records to create all rows
+            var records = Array();
+            for(var i = 0; i < key_list.length; i++) {
+                for(var j = 0; j < key_list.length; j++) {
+                    records.push({
+                        'source': key_list[i],
+                        'destination': key_list[j],
+                        'value': Math.floor(Math.random()*4) /* 0 1 2 3 */
+                    });
+                }
+            }
+
+            // Create the rows
+            this._row_element = this._grid_element.selectAll(".grow")
+              .data(key_list)
+              .enter()
+                .append("div")
+                .attr("class", function(d,i){return "grow grow" + i})
+                .style("width", "100%")
+                .style("z-index", 1000)
+                // jordan
+                .style('position', 'absolute')
+/*
+            var cells = this._row_element.selectAll('.gcell')
+                  .data(records) //function(d,r){ return d.map(function(d,i){ return {celldata:d, row:r}}) }, function(d) { return d['source'] + '--' + d['destination']; })
+                  .enter()
+                    .append("div")
+                    .attr("class", "gcell")
+                    .style("height", self._cellsize +"px")
+                    .style("width", self._cellsize +"px")
+                    .style("margin", (self._cellpadding) +"px")
+          
+                    .on("mouseover", function(d,i){
+                      selected_row = d.row; // We could find the row from the _map_elements
+                      selected_col = i;
+                      if(d.celldata){
+                        d3.select(this).style("margin", (self._cellpadding-1) +"px");
+                        d3.select(this).classed("shadow", true);
+                      }
+                      this._canvas.selectAll(".gcol"+ i).classed("gactive", true);
+                      this._canvas.selectAll(".grow"+ d.row).classed("gactive", true);
+                    })
+                    .on("mouseout", function(d,i){
+                      // d3.select(this).classed("shadow", false);
+                      // d3.select(this).style("background-color", color.brighter());
+                      d3.select(this).style("margin", (self._cellpadding) +"px");
+                      d3.select(this).classed("shadow", false);
+                      this._canvas.selectAll(".gcol"+ i).classed("gactive", false);
+                      this._canvas.selectAll(".grow"+ d.row).classed("gactive", false);
+                    })
+*/
+        },
+
+        _init_top: function()
+        {
+            var self = this;
+            this._top_element = this._canvas.append("div")
+                .attr("class", "gtop")
+                .style("margin-left", self._text_block_size + "px")
+                .style("float", "left")
+                .style("overflow-x", "scroll")
+                .style("width", "960px")
+                .append("svg:svg")
+                    .attr("height", self._text_block_size)
+                    .attr("width", self._max_width_elements * (self._cellsize + 2*self._cellpadding) + 90 + 1000)
+
+        },
+
+
+        _process_top: function(key_list)
+        {
+            var self = this;
+
+            this._top_element = this._top_element
+                .selectAll(".rname")
+                    .data(key_list)
+                    .enter()
+                        .append("g")
+                        .attr("class", function(d,i){return "gcol" + i})
+                        .attr("transform", function(d,i){ return "translate("+(i*(self._cellsize+2*self._cellpadding))+",0)"})
+
+            this._top_element.append("svg:rect")
+                .attr("class", function(d,i){return "gcol" + i})
+                .attr("x",0).attr("y",0)
+                .attr("transform", "rotate(45,0,"+ self._text_block_size +") translate (-0.5,3)")
+                .attr("height",self._text_block_size).attr("width",(self._cellsize+self._cellpadding))
+                //.attr("transform", "rotate(35,"+ (this._cellsize+this._cellpadding)/2  + "," + this._text_block_size/2 + ")")
+
+
+            this._top_element.append("svg:text")
+                .attr("class", "gtext")
+                .attr("text-anchor", "start")
+                .attr("dy", "1.5em")
+                .attr("dx", "1em")
+                .attr("transform", "rotate(-45,0,"+ self._text_block_size +")  translate(0,"+ (self._text_block_size-5) + ")")
+                .text(function(d,i){return d})
+
+            // Create the columns... 
+            var cols = this._grid_element.selectAll(".gcol")
+              .data(key_list)
+              .enter()
+                .append("div")
+                .attr("class", function(d,i){return "gcol gcol" + i})
+                .style("width", (self._cellsize+2*self._cellpadding) + "px")
+                .style("height", "100%")
+                .style("left", function(d,i){return (i*(self._cellsize+2*self._cellpadding)) + "px"})
+        },
+
+        _process_key_list: function()
+        {
+            console.log("process key list");
+            var key_list = this._buffer_key_list.get();
+            this._process_top(key_list);
+            this._process_left(key_list);
+            console.log("process key list done");
+        },
+
+        _display_legends: function()
+        {
+            // Color scale = the number of different statuses
+            colorscale.domain(d3.range(0, this._labels.length));
+
+            // Labels
+            var legendsdata = this._labels
+                .map(function(d,i){ return {label:d, color:colorscale(i)} })
+                //.filter(function(d,i){return d.label === null ? false : true})
+
+            // Clear and redraw legend
+            d3.select("#"+this._legend).html("")
+
+            var legends = d3.select("#"+this._legend)
+                .selectAll(".legend")
+                .data(legendsdata)
+                .enter()
+                .append("div").attr("class", "legend");
+            legends.append("div")
+                .attr("class", "lsymbol")
+                .style("background", function(d,i){return d.color})
+                .style("display", function(d,i){return d.label === null ? "none" : "block"})
+            legends.append("div")
+                .attr("class", "ltext")
+                .text(function(d,i){return d.label})
+                .style("display", function(d,i){return d.label === null ? "none" : "block"})
+        },
+
+        _init_grid: function()
+        {
+            this._grid_element = this._canvas
+                .style("width", this._ncols * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size)
+                .append("div")
+                    .attr("class", "ggrid")
+                    // jordan
+                    .style('top', '165px')
+                    .style('left', '145px')
+                    .style('float', 'none')
+                    .style('width', '2000px')
+                    .style('height', '1000px')
+            
+        },
+
+        _process_records: function()
+        {
+            var self = this;
+            var records = this._buffer_records.get();
+            console.log("processing records");
+
+            // XXX Let's generate fake records instead... NxN
+            var _records = Array();
+            for(var i = 0; i < records.length; i++) {
+                for(var j = 0; j < records.length; j++) {
+                    if (Math.random() < 0.2) { /* one out of 5 */
+                        _records.push({
+                            'source': records[i]['hrn'],
+                            'destination': records[j]['hrn'],
+                            'value': Math.floor(Math.random()*4) /* 0 1 2 3 */
+                        })
+                    }
+                }
+            }
+
+            //
+
+            // Rows and columns have been created
+
+            var color = "";
+            var selected_row = 0;
+            var selected_col = 0;
+            var cells = this._row_element.selectAll(".gcell")
+              /* Not sure to understand this part of the code */
+              //uses data.grid initially... this is not good anymore
+              .data(_records, function(d) { return d['source'] + '--' + d['destination']; })
+              .style("background", function(d,i){
+                return colorscale(parseInt(d.value));
+              })
+
+            // Associate a tooltip to each cell thanks to tipsy
+            /*
+            this.elmt().find(".gcell").each(function(i,d){
+              var data = d3.select(this).data()[0];
+              if(data.celldata!=null){
+                var html = "<div class='tooltip'><div class='top-tip'>" + (data.celldata[0]? data.celldata[0].message : "") + "</div><div class='bottom-tip'>" + (data.celldata[1]? data.celldata[1].message : "") + "</div></div>";
+                $(this).tipsy({
+                  html :true,
+                  opacity: 0.9,
+                  title : function(){
+                  return html
+                }})
+              }
+            })
+            */
+      
+            // This seems to create the coloured subcells
+            var temp = cells.selectAll(".gsubcell")
+              .data(function(d,i){return d.celldata===null? [] : d.celldata })
+              .enter()
+                .append("div");
+            temp
+              .style("height", this._cellsize/2 +"px")
+              .style("background", function(d,i){
+                return colorscale(parseInt(d.status));
+              })
+              .on("click", function(d,i){ //CHANGE FROM PORTAL
+                  var that = this;
+                  if(d!=null && d.uri!=null && self.handleClick != null){
+                    self.handleClick(d);
+                  }
+                })
+            console.log("processing records done");
+        },
+
+        display_component: function(data) 
+        {
+            this._display_grid_container(data);
+        },
+
+        _handleClick: function(d)
+        {
+            var uri = d.uri;
+            $.getJSON(uri, function(data) {
+                    var href = data['history'][0].returnParams.graphUrl.replace("https", "http");
+                     window.open( href, "Graph", "menubar=0,location=0,height=700,width=700" );
+             })
+
+        }
+
+
+    });
+
+    /* Plugin registration */
+    $.plugin('MadDash', MadDash);
+
+    // TODO Here use cases for instanciating plugins in different ways like in the pastie.
+
+})(jQuery);
diff --git a/plugins/maddash/templates/maddash.html b/plugins/maddash/templates/maddash.html
new file mode 100644 (file)
index 0000000..8e678e9
--- /dev/null
@@ -0,0 +1,5 @@
+<div id='{{domid}}__maddash'>
+    <div id='{{domid}}__legend'></div>
+    <div id='{{domid}}__canvas'></div>
+</div>
+