Commits

Anonymous committed 3ea7d46

Implemented highilghting for horizontal bar charts and for filled area charts. Working on theming.

Comments (0)

Files changed (9)

examples/barTest.html

   <script language="javascript" type="text/javascript" src="../src/jquery.jqplot.js"></script>
   <script language="javascript" type="text/javascript" src="../src/plugins/jqplot.barRenderer.js"></script>
   <script language="javascript" type="text/javascript" src="../src/plugins/jqplot.categoryAxisRenderer.js"></script>
+  <script language="javascript" type="text/javascript" src="../src/plugins/jqplot.highlighter.js"></script>
   <!-- END: load jqplot -->
 
   <script type="text/javascript" src="barTest.js"></script>
     
     <div id="chart2" style="margin-top:20px; margin-left:20px; width:300px; height:300px;"></div>
     
+    <div><span>Moused Over: </span><span id="info2b">Nothing</span></div>
+    
+    <div id="chart2b" style="margin-top:20px; margin-left:20px; width:300px; height:300px;"></div>
+    
     <p>The next example has the plot's 'captureRightClick' option set to true.  This causes the plot to fire a 'jqplotRightClick' event the the user clicks the right mouse button over a bar.  Here, the 'highlightMouseDown' option is also set to true.  This will highlight a slice on mouse down instead of on move over.  Highlighting will occur for either left or right click.</p>
 
     <div><span>You Right Clicked: </span><span id="info3">Nothing yet</span></div>

examples/barTest.js

                 renderer: $.jqplot.CategoryAxisRenderer,
                 ticks: ticks
             }
-        }
+        },
+        highlighter: { show: false }
     });
     
     plot2 = $.jqplot('chart2', [s1, s2], {
         }
     });
     
+    plot2b = $.jqplot('chart2b', [[[2,1], [4,2], [6,3], [3,4]], [[5,1], [1,2], [3,3], [4,4]]], {
+        seriesDefaults: {
+            renderer:$.jqplot.BarRenderer,
+            shadowAngle: 135,
+            rendererOptions: {
+                barDirection: 'horizontal'
+            }
+        },
+        axes: {
+            yaxis: {
+                renderer: $.jqplot.CategoryAxisRenderer
+            }
+        }
+    });
+    
     plot3 = $.jqplot('chart3', [s1, s2, s3], {
         stackSeries: true,
         captureRightClick: true,
         function (ev) {
             $('#info2').html('Nothing');
         }
-    ); 
+    );
+    
+    $('#chart2b').bind('jqplotDataHighlight', 
+        function (ev, seriesIndex, pointIndex, data) {
+            $('#info2b').html('series: '+seriesIndex+', point: '+pointIndex+', data: '+data);
+        }
+    );
+    
+    $('#chart2b').bind('jqplotDataUnhighlight', 
+        function (ev) {
+            $('#info2b').html('Nothing');
+        }
+    );
     
     $('#chart3').bind('jqplotDataRightClick', 
         function (ev, seriesIndex, pointIndex, data) {

examples/legendLabels.html

 </style>
 <script language="javascript" type="text/javascript">
 $(document).ready(function(){
-      $.jqplot.config.enablePlugins = true;
 
    s1 = [1,6,9,8];
    s2 = [4,3,1,2];
            yoffset: 35
 		},
 		axes: {
-		    xaxis:{min:1, max:4},
+		    xaxis:{min:1, max:4, label: 'Weather Pattern'},
 		    yaxis:{min:0, max:35}
 		}
    });

examples/theming.html

   <script language="javascript" type="text/javascript" src="../src/plugins/jqplot.highlighter.js"></script>
   <script language="javascript" type="text/javascript" src="../src/plugins/jqplot.cursor.js"></script>
   <script language="javascript" type="text/javascript" src="../src/plugins/jqplot.funnelRenderer.js"></script>
+  <script language="javascript" type="text/javascript" src="../src/plugins/jqplot.canvasTextRenderer.js"></script>
+  <script language="javascript" type="text/javascript" src="../src/plugins/jqplot.canvasAxisTickRenderer.js"></script>
   <!-- END: load jqplot -->
 
 <style type="text/css">
 <script language="javascript" type="text/javascript">
 $(document).ready(function(){
 
-   l1 = [['Seoul', 1], ['Paris', 7], ['Singapore',3], ['Hong  Kong',5], ['Chicago', 2], ['New York', 9]];
-   l2 = [11, 9, 5, 12, 14];
-   l3 = [4, 8, 3, 2, 6];
+    l1 = [['Seoul', 1], ['Paris', 7], ['Singapore',3], ['Hong  Kong',5], ['Chicago', 2], ['New York', 9]];
+    l2 = [11, 9, 5, 12, 14];
+    l3 = [4, 8, 5, 3, 6];
+    l4 = [12, 6, 13, 11, 2];
+    l5 = [4, -3, 3, 6, 2, -2];
 
-   plot1 = $.jqplot('chart1',[l1],{
+    pop1980 = [7071639, 2968528, 3005072, 1595138, 789704, 1688210, 785940, 904599, 875538, 629400]
+    pop1990 = [7322564, 3485398, 2783726, 1630553, 983403, 1585577, 935933, 1006877, 1110549, 782248]
+    pop2000 = [8008654, 3694644, 2896051, 1974152, 1322025, 1517550, 1160005, 1188603, 1223418, 895809]
+    pop2008 = [8363710, 3833995, 2853114, 2242193, 1567924, 1447395, 1351305, 1279910, 1279329, 948279]
+
+    ticks = ["New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio", "Dallas", "San Diego", "San Jose"]      
+
+    plot1 = $.jqplot('chart1',[l2],{
        title: 'Line Chart',
-       legend: {
-           show: true,
-           location: 'nw'
-       },
        highlighter: {
            show:true
        },
        cursor: {
            show: true,
            zoom: true
+       }
+    });
+    
+    plot1b = $.jqplot('chart1b',[l2, l3, l4],{
+       stackSeries: true,
+       showMarker: false,
+       seriesDefaults: {
+           fill: true
+       }
+    });
+    
+    plot1c = $.jqplot('chart1c',[l5],{
+       stackSeries: true,
+       showMarker: false,
+       seriesDefaults: {
+           fill: true,
+           fillToZero: true,
+           rendererOptions: {
+               highlightMouseDown: true
+           }
+       }
+    });
+
+    plot2 = $.jqplot('chart2',[pop1980, pop1990, pop2000, pop2008],{
+       title: 'City Population',
+       legend: {
+           show: true
        },
+       seriesDefaults: {
+           renderer: $.jqplot.BarRenderer,
+           fillToZero: true,
+           rendererOptions: {
+               barPadding: 2
+           }
+       },
+       series: [
+          {label: '1980'},
+          {label: '1990'},
+          {label: '2000'},
+          {label: '2008 (est)'}
+       ],
        axes: {
            xaxis: {
-               renderer: $.jqplot.CategoryAxisRenderer
+               renderer: $.jqplot.CategoryAxisRenderer,
+               tickRenderer: $.jqplot.CanvasAxisTickRenderer,
+               ticks: ticks,
+               tickOptions: {
+                   angle: -30
+                }
+           },
+           yaxis: {
+               tickRenderer: $.jqplot.CanvasAxisTickRenderer,
+               max: 9000000,
+               min: 0,
+               tickOptions: {
+                   formatString: '%d',
+                   angle: -30
+                }
            }
        }
-       
-   });
-   
-   plot2 = $.jqplot('chart2',[l2, l3],{
-       title: 'Bar Chart',
+    });
+    
+    plot3 = $.jqplot('chart3',[l1],{
+       title: 'Global City Index',
        legend: {
            show: true,
-           location: 'nw'
-       },
+           rendererOptions: {
+               // numberColumns: 2,
+               fontSize: '10pt',
+           }
+        },
        seriesDefaults: {
-           renderer: $.jqplot.BarRenderer
-       },
-       axes: {
-           xaxis: {
-               renderer: $.jqplot.CategoryAxisRenderer
-           }
+           renderer: $.jqplot.FunnelRenderer
        }
-   });
-   
-   plot2b = $.jqplot('chart2b',[l2, l3],{
+    });
+    
+    plot4 = $.jqplot('chart4',[l2, l3],{
        title: 'Stacked Bar Chart',
        stackSeries: true,
        legend: {
                renderer: $.jqplot.CategoryAxisRenderer
            }
        }
-   });
-   
-   plot3 = $.jqplot('chart3',[l3],{
+    });
+    
+    plot5 = $.jqplot('chart5',[l3],{
        title: 'Pie Chart',
-       legend: {
-           show: true,
-           rendererOptions: {
-               numberColumns: 2,
-               fontSize: '8pt',
-           }
-       },
        seriesDefaults: {
            renderer: $.jqplot.PieRenderer
        }
-   });
-   
-   plot4 = $.jqplot('chart4',[l2, l3],{
+    });
+    
+    plot6 = $.jqplot('chart6',[l2, l3],{
        title: 'Donut Chart',
-       legend: {
-           show: true,
-           rendererOptions: {
-               numberColumns: 2,
-               fontSize: '8pt',
-           }
-       },
        seriesDefaults: {
            renderer: $.jqplot.DonutRenderer,
            rendererOptions: {
                ringMargin: 4
            }
        }
-   });
+    });
 
-   plot4 = $.jqplot('chart5',[l1],{
-       title: 'Funnel Chart',
-       legend: {
-           show: true,
-           rendererOptions: {
-               numberColumns: 2,
-               fontSize: '8pt',
-           }
+    e1 = plot1.themeEngine;
+    brass = e1.copy('default', 'brass');
+    brass.target.fontFamily = 'Georgia, "Times New Roman"';
+    brass.title.fontFamily = 'Georgia, "Times New Roman"';
+    brass.grid.backgroundColor = "rgb(216, 198, 114)";
+    brass.grid.drawGridlines = false;
+    brass.series[0].lineWidth = 6.5;
+    brass.series[0].markerOptions.show = false;
+    
+    temp = {
+        target: {
+            fontFamily: '"Comic Sans MS", cursive',
+            fontSize: '11pt',
+            color: "#907CEC"
         },
-       seriesDefaults: {
-           renderer: $.jqplot.FunnelRenderer
-       }
-   });
-   
- });
+        grid: {
+            backgroundColor: "#593D2B"
+        },
+        title: {
+            fontFamily: '"Comic Sans MS", cursive',
+            fontSize: '15pt'
+        },
+        series: [
+            {
+                color: "#DBBCAF",
+                lineWidth: 8,
+                markerOptions: {
+                    show: false
+                }
+            }
+        ]
+    };
+    chocolate = plot1.themeEngine.copy('default', 'chocolate', temp);
+    
+
+});
+
+function switchTheme(plot, theme) {
+    plot.activateTheme(theme);
+}
+
+
 </script>
   </head>
   <body>
 <?php include "nav.inc"; ?>
 <div id="chart1" class="plot" style="width:400px;height:260px;"></div>
-<div id="chart2" class="plot" style="width:400px;height:260px;"></div>
-<div id="chart2b" class="plot" style="width:400px;height:260px;"></div>
-<div id="chart3" class="plot" style="width:400px;height:260px;"></div>
+<div id="chart1b" class="plot" style="width:400px;height:260px;"></div>
+<div id="chart1c" class="plot" style="width:400px;height:260px;"></div>
+<div id="chart2" class="plot" style="width:700px;height:310px;"></div>
+<div id="chart3" class="plot" style="width:400px;height:320px;"></div>
 <div id="chart4" class="plot" style="width:400px;height:260px;"></div>
 <div id="chart5" class="plot" style="width:400px;height:260px;"></div>
+<div id="chart6" class="plot" style="width:400px;height:260px;"></div>
   </body>
 </html>

src/jqplot.core.js

         if (this.markerOptions.show == null) {
             this.markerOptions.show = this.showMarker;
         }
+        this.showMarker = this.markerOptions.show;
         // the markerRenderer is called within it's own scaope, don't want to overwrite series options!!
         this.markerRenderer.init(this.markerOptions);
     };
                 s = series[i];
                 switch (s.renderer.constructor) {
                     case $.jqplot.BarRenderer:
-                        switch (s.barDirection) {
-                            case 'vertical':
-                                // is it a single series or a stacked series
-                                if (true) {
-                                    x = gridpos.x;
-                                    y = gridpos.y;
-                                    bw = s.barWidth/2;
-                                    for (j=s.gridData.length-1; j>=0; j--) {
-                                        // p = s.gridData[j];
-                                        // pp = s._prevGridData[j] || [p[0], s.canvas._ctx.canvas.height];
-                                        // points = [[p[0]-bw, p[1]], [p[0]+bw, p[1]], [p[0]+bw, pp[1]], [p[0]-bw, pp[1]]];
-                                        // points = [p[0]-bw, p[1], bw, pp[1]-p[1]];
-                                        points = s._barPoints[j];
-                                        if (x>points[0][0] && x<points[2][0] && y>points[2][1] && y<points[0][1]) {
-                                            return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]};
-                                        }
-                                    }
-                                }
-                                // else side by side series
-                                else {
-                                    //
-                                }
-                                break;
-                                
-                            case 'horizontal':
-                                //
-                                break;
+                        x = gridpos.x;
+                        y = gridpos.y;
+                        for (j=s.gridData.length-1; j>=0; j--) {
+                            points = s._barPoints[j];
+                            if (x>points[0][0] && x<points[2][0] && y>points[2][1] && y<points[0][1]) {
+                                return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]};
+                            }
                         }
                         break;
                     
                         }         
                         break;           
                     
+                    case $.jqplot.LineRenderer:
+                        x = gridpos.x;
+                        y = gridpos.y;
+                        r = s.renderer;
+                        if (s.show) {
+                            if (s.fill) {
+                                x = gridpos.x;
+                                y = gridpos.y;
+                                // first check if it is in bounding box
+                                var inside = false;
+                                if (x>s._boundingBox[0][0] && x<s._boundingBox[1][0] && y>s._boundingBox[1][1] && y<s._boundingBox[0][1]) { 
+                                    // now check the crossing number         
+                                    
+                                    var numPoints = s._areaPoints.length;
+                                    var ii;
+                                    var j = numPoints-1;
+
+                                    for(var ii=0; ii < numPoints; ii++) { 
+                                    	var vertex1 = [s._areaPoints[ii][0], s._areaPoints[ii][1]];
+                                    	var vertex2 = [s._areaPoints[j][0], s._areaPoints[j][1]];
+
+                                    	if (vertex1[1] < y && vertex2[1] >= y || vertex2[1] < y && vertex1[1] >= y)	 {
+                                    		if (vertex1[0] + (y - vertex1[1]) / (vertex2[1] - vertex1[1]) * (vertex2[0] - vertex1[0]) < x) {
+                                    			inside = !inside;
+                                    		}
+                                    	}
+
+                                    	j = ii;
+                                    }        
+                                }
+                                if (inside) {
+                                    return {seriesIndex:i, pointIndex:null, gridData:s.gridData, data:s.data, points:s._areaPoints};
+                                }
+                                break;
+                                
+                            }
+                            else {
+                                t = s.markerRenderer.size/2+s.neighborThreshold;
+                                threshold = (t > 0) ? t : 0;
+                                for (var j=0; j<s.gridData.length; j++) {
+                                    p = s.gridData[j];
+                                    // neighbor looks different to OHLC chart.
+                                    if (r.constructor == $.jqplot.OHLCRenderer) {
+                                        if (r.candleStick) {
+                                            var yp = s._yaxis.series_u2p;
+                                            if (x >= p[0]-r._bodyWidth/2 && x <= p[0]+r._bodyWidth/2 && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
+                                                return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                            }
+                                        }
+                                        // if an open hi low close chart
+                                        else if (!r.hlc){
+                                            var yp = s._yaxis.series_u2p;
+                                            if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
+                                                return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                            }
+                                        }
+                                        // a hi low close chart
+                                        else {
+                                            var yp = s._yaxis.series_u2p;
+                                            if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) {
+                                                return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                            }
+                                        }
+                            
+                                    }
+                                    else {
+                                        d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
+                                        if (d <= threshold && (d <= d0 || d0 == null)) {
+                                           d0 = d;
+                                           return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                        }
+                                    }
+                                } 
+                            }
+                        }
+                        break;
+                        
                     default:
                         x = gridpos.x;
                         y = gridpos.y;
     }
     
     
+    // conpute a highlight color or array of highlight colors from given colors.
+    $.jqplot.computeHighlightColors  = function(colors) {
+        var ret;
+        if (typeof(colors) == "array") {
+            ret = [];
+            for (var i=0; i<colors.length; i++){
+                var rgba = $.jqplot.getColorComponents(colors[i]);
+                var newrgb = [rgba[0], rgba[1], rgba[2]];
+                var sum = newrgb[0] + newrgb[1] + newrgb[2];
+                for (var j=0; j<3; j++) {
+                    // when darkening, lowest color component can be is 60.
+                    newrgb[j] = (sum > 570) ?  newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
+                    newrgb[j] = parseInt(newrgb[j], 10);
+                }
+                ret.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
+            }
+        }
+        else {
+            var rgba = $.jqplot.getColorComponents(colors);
+            var newrgb = [rgba[0], rgba[1], rgba[2]];
+            var sum = newrgb[0] + newrgb[1] + newrgb[2];
+            for (var j=0; j<3; j++) {
+                // when darkening, lowest color component can be is 60.
+                newrgb[j] = (sum > 570) ?  newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
+                newrgb[j] = parseInt(newrgb[j], 10);
+            }
+            ret = 'rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')';
+        }
+        return ret;
+    }
         
    $.jqplot.ColorGenerator = function(colors) {
         var idx = 0;

src/jqplot.lineRenderer.js

     };
     
     // called with scope of series.
-    $.jqplot.LineRenderer.prototype.init = function(options) {
+    $.jqplot.LineRenderer.prototype.init = function(options, plot) {
+        // prop: highlightMouseOver
+        // True to highlight slice when moused over.
+        // This must be false to enable highlightMouseDown to highlight when clicking on an area on a filled plot.
+        this.highlightMouseOver = true;
+        // prop: highlightMouseDown
+        // True to highlight when a mouse button is pressed over a area on a filled plot.
+        // This will be disabled if highlightMouseOver is true.
+        this.highlightMouseDown = false;
+        // prop: highlightColor
+        // color to use when highlighting an area on a filled plot.
+        this.highlightColor = null;
+        
+        // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
+        if (options.highlightMouseDown && options.highlightMouseOver == null) {
+            options.highlightMouseOver = false;
+        }
+        
+        $.extend(true, this, {highlightMouseOver: options.highlightMouseOver, highlightMouseDown: options.highlightMouseDown, highlightColor: options.highlightColor});
+        
+        delete (options.highlightMouseOver);
+        delete (options.highlightMouseDown);
+        delete (options.highlightColor);
+        
         $.extend(true, this.renderer, options);
         // set the shape renderer options
         var opts = {lineJoin:'round', lineCap:'round', fill:this.fill, isarc:false, strokeStyle:this.color, fillStyle:this.fillColor, lineWidth:this.lineWidth, closePath:this.fill};
         }
         var sopts = {lineJoin:'round', lineCap:'round', fill:this.fill, isarc:false, angle:this.shadowAngle, offset:shadow_offset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.lineWidth, closePath:this.fill};
         this.renderer.shadowRenderer.init(sopts);
+        this._areaPoints = [];
+        this._boundingBox = [[0,0],[0,0]];
+        if (!this.highlightColor) {
+            this.highlightColor = $.jqplot.computeHighlightColors(this.fillColor);
+        }
+        
+
+        plot.postInitHooks.addOnce(postInit);
+        plot.postDrawHooks.addOnce(postPlotDraw);
+        plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
+        plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
+        plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
+        plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
+        plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
+
     };
     
     // Method: setGridData
         var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
         var fill = (opts.fill != undefined) ? opts.fill : this.fill;
         var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke;
+        var xmin, ymin, xmax, ymax;
         ctx.save();
         if (gd.length) {
             if (showLine) {
                         if (this.index == 0 || !this._stack) {
                         
                             var tempgd = [];
+                            this._areaPoints = [];
                             var pyzero = this._yaxis.series_u2p(this.fillToValue);
                             var pxzero = this._xaxis.series_u2p(this.fillToValue);
                             
                             if (this.fillAxis == 'y') {
                                 tempgd.push([gd[0][0], pyzero]);
+                                this._areaPoints.push([gd[0][0], pyzero]);
                                 
                                 for (var i=0; i<gd.length-1; i++) {
                                     tempgd.push(gd[i]);
+                                    this._areaPoints.push(gd[i]);
                                     // do we have an axis crossing?
                                     if (this._plotData[i][1] * this._plotData[i+1][1] < 0) {
                                         if (this._plotData[i][1] < 0) {
                                         
                                         var xintercept = gd[i][0] + (gd[i+1][0] - gd[i][0]) * (pyzero-gd[i][1])/(gd[i+1][1] - gd[i][1]);
                                         tempgd.push([xintercept, pyzero]);
+                                        this._areaPoints.push([xintercept, pyzero]);
                                         // now draw this shape and shadow.
                                         if (shadow) {
                                             this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
                                         this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
                                         // now empty temp array and continue
                                         tempgd = [[xintercept, pyzero]];
+                                        // this._areaPoints = [[xintercept, pyzero]];
                                     }   
                                 }
                                 if (this._plotData[gd.length-1][1] < 0) {
                                     opts.fillStyle = posfs;
                                 }
                                 tempgd.push(gd[gd.length-1]);
+                                this._areaPoints.push(gd[gd.length-1]);
                                 tempgd.push([gd[gd.length-1][0], pyzero]); 
+                                this._areaPoints.push([gd[gd.length-1][0], pyzero]); 
                             }
                             // now draw this shape and shadow.
                             if (shadow) {
                             var prev = this._prevGridData;
                             for (var i=prev.length; i>0; i--) {
                                 gd.push(prev[i-1]);
+                                this._areaPoints.push(prev[i-1]);
                             }
                             if (shadow) {
                                 this.renderer.shadowRenderer.draw(ctx, gd, opts);
                                 gd.push(prev[i-1]);
                             }
                         }
+                        this._areaPoints = gd;
                         if (shadow) {
                             this.renderer.shadowRenderer.draw(ctx, gd, opts);
                         }
                     this.renderer.shapeRenderer.draw(ctx, gd, opts);
                 }
             }
+            // calculate the bounding box
+            for (i=0; i<this._areaPoints.length; i++) {
+                var p = this._areaPoints[i];
+                if (this._boundingBox[0][0] > p[0]) {
+                    this._boundingBox[0][0] = p[0];
+                }
+                if (this._boundingBox[0][1] < p[1]) {
+                    this._boundingBox[0][1] = p[1];
+                }
+                if (this._boundingBox[1][0] < p[0]) {
+                    this._boundingBox[1][0] = p[0];
+                }
+                if (this._boundingBox[1][1] > p[1]) {
+                    this._boundingBox[1][1] = p[1];
+                }
+            }
         
             // now draw the markers
             if (this.markerRenderer.show && !fill) {
         // This is a no-op, shadows drawn with lines.
     };
     
+    // called with scope of plot.
+    // make sure to not leave anything highlighted.
+    function postInit(target, data, options) {
+        for (i=0; i<this.series.length; i++) {
+            if (this.series[i].renderer.constructor == $.jqplot.LineRenderer) {
+                // don't allow mouseover and mousedown at same time.
+                if (this.series[i].highlightMouseOver) {
+                    this.series[i].highlightMouseDown = false;
+                }
+            }
+        }
+        this.target.bind('mouseout', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
+    }  
+    
+    // called within context of plot
+    // create a canvas which we can draw on.
+    // insert it before the eventCanvas, so eventCanvas will still capture events.
+    function postPlotDraw() {
+        this.plugins.lineRenderer = {highlightedSeriesIndex:null};
+        this.plugins.lineRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
+        
+        this.eventCanvas._elem.before(this.plugins.lineRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-lineRenderer-highlight-canvas', this._plotDimensions));
+        var hctx = this.plugins.lineRenderer.highlightCanvas.setContext();
+    } 
+    
+    $.jqplot.LineRenderer.prototype.highlightBar = function(ctx, points, options) {
+        
+    };
+    
+    function highlight (plot, sidx, pidx, points) {
+        var s = plot.series[sidx];
+        var canvas = plot.plugins.lineRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        s._highlightedPoint = pidx;
+        plot.plugins.lineRenderer.highlightedSeriesIndex = sidx;
+        var opts = {fillStyle: s.highlightColor};
+        s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
+    }
+    
+    function unhighlight (plot) {
+        var canvas = plot.plugins.lineRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        for (var i=0; i<plot.series.length; i++) {
+            plot.series[i]._highlightedPoint = null;
+        }
+        plot.plugins.lineRenderer.highlightedSeriesIndex = null;
+        plot.target.trigger('jqplotDataUnhighlight');
+    }
+    
+    
+    function handleMove(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            plot.target.trigger('jqplotDataMouseOver', ins);
+            if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
+                plot.target.trigger('jqplotDataHighlight', ins);
+                highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+    
+    function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
+                plot.target.trigger('jqplotDataHighlight', ins);
+                highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+    
+    function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
+        var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
+        if (idx != null && plot.series[idx].highlightMouseDown) {
+            unhighlight(plot);
+        }
+    }
+    
+    function handleClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            plot.target.trigger('jqplotDataClick', ins);
+        }
+    }
+    
+    function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
+            if (idx != null && plot.series[idx].highlightMouseDown) {
+                unhighlight(plot);
+            }
+            plot.target.trigger('jqplotDataRightClick', ins);
+        }
+    }
+    
 })(jQuery);    

src/jqplot.markerRenderer.js

         options = options || {};
         // hack here b/c shape renderer uses canvas based color style options
         // and marker uses css style names.
-        if (options.color && !options.fillStyle) {
-            options.fillStyle = options.color;
-        }
-        if (options.color && !options.strokeStyle) {
-            options.strokeStyle = options.color;
-        }
-        switch (this.style) {
-            case 'diamond':
-                this.drawDiamond(x,y,ctx, false, options);
-                break;
-            case 'filledDiamond':
-                this.drawDiamond(x,y,ctx, true, options);
-                break;
-            case 'circle':
-                this.drawCircle(x,y,ctx, false, options);
-                break;
-            case 'filledCircle':
-                this.drawCircle(x,y,ctx, true, options);
-                break;
-            case 'square':
-                this.drawSquare(x,y,ctx, false, options);
-                break;
-            case 'filledSquare':
-                this.drawSquare(x,y,ctx, true, options);
-                break;
-            case 'x':
-                this.drawX(x,y,ctx, true, options);
-                break;
-            case 'plus':
-                this.drawPlus(x,y,ctx, true, options);
-                break;
-            case 'dash':
-                this.drawDash(x,y,ctx, true, options);
-                break;
-            default:
-                this.drawDiamond(x,y,ctx, false, options);
-                break;
+        if (options.show == null || options.show != false) {
+            if (options.color && !options.fillStyle) {
+                options.fillStyle = options.color;
+            }
+            if (options.color && !options.strokeStyle) {
+                options.strokeStyle = options.color;
+            }
+            switch (this.style) {
+                case 'diamond':
+                    this.drawDiamond(x,y,ctx, false, options);
+                    break;
+                case 'filledDiamond':
+                    this.drawDiamond(x,y,ctx, true, options);
+                    break;
+                case 'circle':
+                    this.drawCircle(x,y,ctx, false, options);
+                    break;
+                case 'filledCircle':
+                    this.drawCircle(x,y,ctx, true, options);
+                    break;
+                case 'square':
+                    this.drawSquare(x,y,ctx, false, options);
+                    break;
+                case 'filledSquare':
+                    this.drawSquare(x,y,ctx, true, options);
+                    break;
+                case 'x':
+                    this.drawX(x,y,ctx, true, options);
+                    break;
+                case 'plus':
+                    this.drawPlus(x,y,ctx, true, options);
+                    break;
+                case 'dash':
+                    this.drawDash(x,y,ctx, true, options);
+                    break;
+                default:
+                    this.drawDiamond(x,y,ctx, false, options);
+                    break;
+            }
         }
     };
 })(jQuery);    

src/jqplot.themeEngine.js

             for (n in th.target) {
                 plot.target.css(n, th.target[n]);
             }
-            for (n in th.legend) {
-                plot.legend._elem.css(n, th.legend[n]);
+            if (plot.legend.show) { 
+                for (n in th.legend) {
+                    plot.legend._elem.css(n, th.legend[n]);
+                }
             }
-            for (n in th.title) {
-                plot.title._elem.css(n, th.title[n]);
+            if (plot.title.show) {
+                for (n in th.title) {
+                    plot.title._elem.css(n, th.title[n]);
+                }
             }
             var i;
             for (i=0; i<th.series.length; i++) {
         
     };
     
-    $.jqplot.ThemeEngine.prototype.add = function(theme) {
+    $.jqplot.ThemeEngine.prototype.add = function(theme, name) {
+        if (name) {
+            theme._name = name;
+        }
         if (!this.themes.hasOwnProperty(theme._name)) {
             this.themes[theme._name] = theme;
         }
     // create and return a copy of the current active theme.
     $.jqplot.ThemeEngine.prototype.newTheme = function(name) {
         name = name || Date.parse(new Date());
-        var th = new $.jqplot.Theme(name);
-        $.extend(true, th, this.activeTheme);
+        // var th = new $.jqplot.Theme(name);
+        var th = $.extend(true, {}, this.activeTheme);
+        th._name = name;
         this.add(th);
         return th;      
     };
         throw new Error("jqplot.ThemeEngine Error: Old name or new name invalid");
     };
     
-    $.jqplot.ThemeEngine.prototype.copy = function (sourceName, targetName) {
+    $.jqplot.ThemeEngine.prototype.copy = function (sourceName, targetName, obj) {
         if (this.themes.hasOwnProperty(sourceName) && !this.themes.hasOwnProperty(targetName)) {
-            var th = new $.jqplot.Theme(targetName);
-            $.extend(true, th, this.themes[sourceName]);
+            // var th = new $.jqplot.Theme(targetName);
+            var th = $.extend(true, {}, this.themes[sourceName], obj);
             th._name = targetName;
             this.add(th);
             return th;

src/plugins/jqplot.barRenderer.js

                     }
                     
                     points.push([xstart, base+this.barWidth/2]);
+                    points.push([xstart, base-this.barWidth/2]);
+                    points.push([gridData[i][0], base-this.barWidth/2]);
                     points.push([gridData[i][0], base+this.barWidth/2]);
-                    points.push([gridData[i][0], base-this.barWidth/2]);
-                    points.push([xstart, base-this.barWidth/2]);
                     this._barPoints.push(points);
                     // now draw the shadows if not stacked.
                     // for stacked plots, they are predrawn by drawShadow
         var hctx = this.plugins.barRenderer.highlightCanvas.setContext();
     }   
     
-    $.jqplot.BarRenderer.prototype.highlightBar = function(ctx, points, options) {
-        
-    };
-    
     function highlight (plot, sidx, pidx, points) {
         var s = plot.series[sidx];
         var canvas = plot.plugins.barRenderer.highlightCanvas;