Commits

cleonello  committed 07dc012

Added enhanced legend rendering to mekko charts.

  • Participants
  • Parent commits 578ee3f

Comments (0)

Files changed (4)

File examples/mekkoChart.html

     #chart2 .jqplot-axis {
         font-size: 0.7em;
     }
+    
+    #chart3 .jqplot-title {
+        padding-bottom: 40px;
+    }
   </style>
   <script type="text/javascript" language="javascript">
   
     plot1 = $.jqplot('chart1', [bar1, bar2, bar3], {
         title: 'Revenue Breakdown per Character',
         seriesDefaults:{renderer:$.jqplot.MekkoRenderer},
-        legend:{show:true, numberColumns:2},
+        legend:{show:true},
         axesDefaults:{
             renderer:$.jqplot.MekkoAxisRenderer
         },
         seriesDefaults:{renderer:$.jqplot.MekkoRenderer},
         legend:{
             show:true, 
-            rendererOptions:{labels:legendLabels, placement: "outside"}, 
-            location:'e'
+            location: 'n',
+            labels: legendLabels,
+            rendererOptions:{numberRows: 1, placement: "outside"}
         },
         axesDefaults:{
             renderer:$.jqplot.MekkoAxisRenderer, 
             xaxis:{
                 tickMode:"bar", 
                 tickOptions:{formatString:'$%dM'}
-            }, 
-            x2axis:{
-                show:true, 
-                tickMode:'even', 
-                tickOptions:{formatString:'$%dM'}
             }
         }
     });
     
     <div id="chart2" style="width:500px; height:300px;"></div>
     
-    <p>Legend labels can be specified independently of the series with the "labels" option on the legend.  These will override any labels specified with the series.
+    <p>Legend labels can be specified independently of the series with the "labels" option on the legend.  These will override any labels specified with the series. There are also options to control the number of rows and number of columns in the legend as well as placement.</p>
+    
+    <p>Here the legend is positioned to the "north" and set to render 1 row tall (number of columns determined automatically).  Note that an extra css specification was added to pad the bottom of the title of this chart to give room for the legend above the plot.</p>
     
 <pre>
 legendLabels = ['hotels', 'rides', 'buses', 'instruments', 'totes'];
 
 legend:{
     show:true, 
-    rendererOptions:{labels:legendLabels, placement: "outside"}, 
+    location: 'n',
+    labels: legendLabels,
+    rendererOptions:{numberRows: 1, placement: "outside"}
 },    
 </pre>
 

File src/jqplot.divTitleRenderer.js

         var r = this.renderer;
         if (!this.text) {
             this.show = false;
-            this._elem = $('<div style="height:0px;width:0px;"></div>');
+            this._elem = $('<div class="jqplot-title" style="height:0px;width:0px;"></div>');
         }
         else if (this.text) {
             // don't trust that a stylesheet is present, set the position.

File src/plugins/jqplot.enhancedLegendRenderer.js

         var legend = this;
         if (this.show) {
             var series = this._series;
-            // make a table.  one line label per row.
             var ss = 'position:absolute;';
             ss += (this.background) ? 'background:'+this.background+';' : '';
             ss += (this.border) ? 'border:'+this.border+';' : '';

File src/plugins/jqplot.mekkoRenderer.js

         // This is a no-op, no shadows on mekko charts.
     };
     
-    // called with scope of legend renderer.
-    $.jqplot.MekkoLegendRenderer = function() {
-        $.jqplot.TableLegendRenderer.call(this);
+    
+    
+    $.jqplot.MekkoLegendRenderer = function(){
+        //
     };
     
-    $.jqplot.MekkoLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
-    $.jqplot.MekkoLegendRenderer.prototype.constructor = $.jqplot.MekkoLegendRenderer;
-    
-    // called with scope of legend
     $.jqplot.MekkoLegendRenderer.prototype.init = function(options) {
-        this.labels = [];
+        // prop: numberRows
+        // Maximum number of rows in the legend.  0 or null for unlimited.
+        this.numberRows = null;
+        // prop: numberColumns
+        // Maximum number of columns in the legend.  0 or null for unlimited.
+        this.numberColumns = null;
+        // this will override the placement option on the Legend object
         this.placement = "outside";
         $.extend(true, this, options);
     };
     
-    // called with context of legend
+    // called with scope of legend
     $.jqplot.MekkoLegendRenderer.prototype.draw = function() {
         var legend = this;
         if (this.show) {
             var series = this._series;
-            // make a table.  one line label per row.
             var ss = 'position:absolute;';
             ss += (this.background) ? 'background:'+this.background+';' : '';
             ss += (this.border) ? 'border:'+this.border+';' : '';
             ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
             ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
             ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
-            this._elem = $('<table class="jqplot-table-legend jqplot-mekko-legend" style="'+ss+'"></table>');
-        
-            var pad = false, i, labels = [], colors = [];
+            this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
+            // Mekko charts  legends don't go by number of series, but by number of data points
+            // in the series.  Refactor things here for that.
+            
+            var pad = false, 
+                reverse = true,    // mekko charts are always stacked, so reverse
+                nr, nc;
             var s = series[0];
             var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
+            
             if (s.show) {
                 var pd = s.data;
-                for (i=0; i<pd.length; i++){
-                    labels.push(this.labels[i] || pd[i][0].toString());
-                    colors.push(colorGenerator.next());  
+                if (this.numberRows) {
+                    nr = this.numberRows;
+                    if (!this.numberColumns)
+                        nc = Math.ceil(pd.length/nr);
+                    else
+                        nc = this.numberColumns;
                 }
-                for (i=pd.length-1; i>-1; i--) {
-                    if (labels[i]) {
-                        this.renderer.addrow.call(this, labels[i], colors[i], pad);
-                        pad = true;
-                    }
+                else if (this.numberColumns) {
+                    nc = this.numberColumns;
+                    nr = Math.ceil(pd.length/this.numberColumns);
+                }
+                else {
+                    nr = pd.length;
+                    nc = 1;
+                }
+                
+                var i, j, tr, td1, td2, lt, rs, color;
+                var idx = 0;    
+                
+                for (i=0; i<nr; i++) {
+                    if (reverse)
+                        tr = $('<tr class="jqplot-table-legend"></tr>').prependTo(this._elem);
+                    else
+                        tr = $('<tr class="jqplot-table-legend"></tr>').appendTo(this._elem);
+                    for (j=0; j<nc; j++) {
+                        if (idx < pd.length){
+                            lt = this.labels[idx] || pd[idx][0].toString();
+                            color = colorGenerator.next();
+                            if (!reverse)
+                                if (i>0)
+                                    pad = true;
+                                else
+                                    pad = false;
+                            else
+                                if (i == nr -1)
+                                    pad = false;
+                                else
+                                    pad = true;
+                
+                            rs = (pad) ? this.rowSpacing : '0';
+                
+                            td1 = $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
+                                '<div><div class="jqplot-table-legend-swatch" style="border-color:'+color+';"></div>'+
+                                '</div></td>');
+                            td2 = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
+                            if (this.escapeHtml)
+                                td2.text(lt);
+                            else 
+                                td2.html(lt);
+                            if (reverse) {
+                                td2.prependTo(tr);
+                                td1.prependTo(tr);
+                            }
+                            else {
+                                td1.appendTo(tr);
+                                td2.appendTo(tr);
+                            }
+                            pad = true;
+                        }
+                        idx++;
+                    }   
                 }
             }
-        }        
+        }
         return this._elem;
     };
     
+    $.jqplot.MekkoLegendRenderer.prototype.pack = function(offsets) {
+        if (this.show) {
+            // fake a grid for positioning
+            var grid = {_top:offsets.top, _left:offsets.left, _right:offsets.right, _bottom:this._plotDimensions.height - offsets.bottom};        
+            if (this.placement == 'inside') {
+                switch (this.location) {
+                    case 'nw':
+                        var a = grid._left + this.xoffset;
+                        var b = grid._top + this.yoffset;
+                        this._elem.css('left', a);
+                        this._elem.css('top', b);
+                        break;
+                    case 'n':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = grid._top + this.yoffset;
+                        this._elem.css('left', a);
+                        this._elem.css('top', b);
+                        break;
+                    case 'ne':
+                        var a = offsets.right + this.xoffset;
+                        var b = grid._top + this.yoffset;
+                        this._elem.css({right:a, top:b});
+                        break;
+                    case 'e':
+                        var a = offsets.right + this.xoffset;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({right:a, top:b});
+                        break;
+                    case 'se':
+                        var a = offsets.right + this.xoffset;
+                        var b = offsets.bottom + this.yoffset;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                    case 's':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = offsets.bottom + this.yoffset;
+                        this._elem.css({left:a, bottom:b});
+                        break;
+                    case 'sw':
+                        var a = grid._left + this.xoffset;
+                        var b = offsets.bottom + this.yoffset;
+                        this._elem.css({left:a, bottom:b});
+                        break;
+                    case 'w':
+                        var a = grid._left + this.xoffset;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    default:  // same as 'se'
+                        var a = grid._right - this.xoffset;
+                        var b = grid._bottom + this.yoffset;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                }
+                
+            }
+            else {
+                switch (this.location) {
+                    case 'nw':
+                        var a = this._plotDimensions.width - grid._left + this.xoffset;
+                        var b = grid._top + this.yoffset;
+                        this._elem.css('right', a);
+                        this._elem.css('top', b);
+                        break;
+                    case 'n':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = this._plotDimensions.height - grid._top + this.yoffset;
+                        this._elem.css('left', a);
+                        this._elem.css('bottom', b);
+                        break;
+                    case 'ne':
+                        var a = this._plotDimensions.width - offsets.right + this.xoffset;
+                        var b = grid._top + this.yoffset;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    case 'e':
+                        var a = this._plotDimensions.width - offsets.right + this.xoffset;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    case 'se':
+                        var a = this._plotDimensions.width - offsets.right + this.xoffset;
+                        var b = offsets.bottom + this.yoffset;
+                        this._elem.css({left:a, bottom:b});
+                        break;
+                    case 's':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = this._plotDimensions.height - offsets.bottom + this.yoffset;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    case 'sw':
+                        var a = this._plotDimensions.width - grid._left + this.xoffset;
+                        var b = offsets.bottom + this.yoffset;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                    case 'w':
+                        var a = this._plotDimensions.width - grid._left + this.xoffset;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({right:a, top:b});
+                        break;
+                    default:  // same as 'se'
+                        var a = grid._right - this.xoffset;
+                        var b = grid._bottom + this.yoffset;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                }
+            }
+        } 
+    };
+    
     // setup default renderers for axes and legend so user doesn't have to
     // called with scope of plot
     function preInit(target, data, options) {