Commits

Anonymous committed 6d94d63

Added support for filled line plots (a.k.a area charts). Working on stacked line/area plots.

  • Participants
  • Parent commits 9463bae

Comments (0)

Files changed (4)

-Title: jqPlot Charts
+Title: jqPlot Readme
 
 Pure JavaScript plotting plugin for jQuery.
 
 This software is licensed under the GPL version 2.0 and MIT licenses.
 
 The jqPlot home page is at <http://www.jqplot.com/>.
+
+Downloads can be found at <http://bitbucket.org/cleonello/jqplot/downloads/>.
+
+The mailing list is at <http://groups.google.com/group/jqplot-users>.
+
 Examples and unit tests are at <http://www.jqplot.com/tests/>.
+
 Documentation is at <http://www.jqplot.com/docs/>.
-The jqplot project page and source code is at <http://www.bitbucket.org/cleonello/jqplot/>.
-Downloads can be found at <http://bitbucket.org/cleonello/jqplot/downloads/>.
+
+The project page and source code is at <http://www.bitbucket.org/cleonello/jqplot/>.
+
+Bugs, issues, feature requests: <http://www.bitbucket.org/cleonello/jqplot/issues/>.
 
 To build a distribution from source you need to have ant <http://ant.apache.org> 
 installed.  There are 6 targets: clean, dist, min, tests, docs and all.  Use

File src/jqplot.core.js

         // name of y axis to associate with this series, either 'yaxis' or 'y2axis'.
         this.yaxis = 'yaxis';
         this._yaxis;
+        this.gridBorderWidth = 2.0;
         // prop: renderer
         // A class of a renderer which will draw the series, 
         // see <$.jqplot.LineRenderer>.
         this.shadowAngle = 45;
         // prop: shadowOffset
         // Shadow offset from line in pixels
-        this.shadowOffset = 1;
+        this.shadowOffset = 1.5;
         // prop: shadowDepth
         // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
         this.shadowDepth = 3;
         // prop: showMarker
         // wether or not to show the markers at the data points.
         this.showMarker = true;
+        // prop: index
+        // 0 based index of this series in the plot series array.
+        this.index;
+        // prop: fill
+        // true or false, wether to fill under lines or in bars.
+        // May not be implemented in all renderers.
+        this.fill = false;
+        // _stack is set by the Plot if the plot is a stacked chart.
+        // will stack lines or bars on top of one another to build a "mountain" style chart.
+        // May not be implemented in all renderers.
+        this._stack = false;
+        this._stackData = [];
+        this._plotData = [];
+        this._stackAxis = 'y';
         this.plugins = {};
     }
     
     Series.prototype = new $.jqplot.ElemContainer();
     Series.prototype.constructor = Series;
     
-    Series.prototype.init = function() {
+    Series.prototype.init = function(index, gridbw) {
         // weed out any null values in the data.
+        this.index = index;
+        this.gridBorderWidth = gridbw;
         var d = this.data;
         for (var i=0; i<d.length; i++) {
-            if (d[i] == null || d[i][0] == null || d[i][1] == null) {
-                // For the time being, just delete null values
-                // could keep them if wanted to break lines on null.
-                d.splice(i,1);
-                continue;
+            if (! this.breakOnNull) {
+                if (d[i] == null || d[i][0] == null || d[i][1] == null) {
+                    d.splice(i,1);
+                    continue;
+                }
+            }
+            else {
+                if (d[i] == null || d[i][0] == null || d[i][1] == null) {
+                    // figure out what to do with null values
+                }
             }
         }
         this.renderer = new this.renderer();
     
     // data - optional data point array to draw using this series renderer
     // gridData - optional grid data point array to draw using this series renderer
+    // stackData - array of cumulative data for stacked plots.
     Series.prototype.draw = function(sctx, opts) {
         var options = (opts == undefined) ? {} : opts;
         // hooks get called even if series not shown
         // we don't clear canvas here, it would wipe out all other series as well.
         for (var j=0; j<$.jqplot.preDrawSeriesHooks.length; j++) {
-            $.jqplot.preDrawSeriesHooks[j].call(this.series[i], sctx, options);
+            $.jqplot.preDrawSeriesHooks[j].call(this, sctx, options);
         }
         if (this.show) {
             this.renderer.setGridData.call(this);
             if (!options.preventJqPlotSeriesDrawTrigger) {
                 $(sctx.canvas).trigger('jqplotSeriesDraw', [this.data, this.gridData]);
             }
-            var data = options.data || this.data;
+            var data = [];
+            if (options.data) {
+                data = options.data;
+            }
+            else if (!this._stack) {
+                data = this.data;
+            }
+            else {
+                // var sidx = this._stackAxis == 'x' ? 0 : 1;
+                // var idx = s ? 0 : 1;
+                // for (var i=0; i<this.data.length; i++) {
+                //     var temp = [];
+                //     temp[sidx] = this._stackData[i][sidx];
+                //     temp[idx] = this.data[i][idx];
+                //     data.push(temp);
+                // }
+                //data = this._stackData;
+                data = this._plotData;
+            }
             var gridData = options.gridData || this.renderer.makeGridData.call(this, data);
         
             this.renderer.draw.call(this, sctx, gridData, options);
         this.title = new Title();
         // container to hold all of the merged options.  Convienence for plugins.
         this.options = {};
+        // prop: stackSeries
+        // true or false, creates a stack or "mountain" plot.
+        // Not all series renderers may implement this option.
+        this.stackSeries = false;
+        // array to hold the cumulative stacked series data.
+        // used to ajust the individual series data, which won't have access to other
+        // series data.
+        this._stackData = [];
+        // array that holds the data to be plotted. This will be the series data
+        // merged with the the appropriate data from _stackData according to the stackAxis.
+        this._plotData = [];
         // Namespece to hold plugins.  Generally non-renderer plugins add themselves to here.
         this.plugins = {};
             
             
             this.title.init();
             this.legend.init();
-                        
             for (var i=0; i<this.series.length; i++) {
+                this.populatePlotData();
                 for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) {
                     $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, data, options);
                 }
                 this.series[i]._plotDimensions = this._plotDimensions;
-                this.series[i].init();
+                this.series[i].init(i, this.grid.borderWidth);
                 for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) {
                     $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, data, options);
                 }
             }
         };  
         
+        // populate the _stackData and _plotData arrays for the plot and the series.
+        this.populatePlotData = function() {
+            for (var i=0; i<this.series.length; i++) {
+                // if a stacked chart, compute the stacked data
+                if (this.stackSeries) {
+                    this.series[i]._stack = true;
+                    var sidx = this.series[i]._stackAxis == 'x' ? 0 : 1;
+                    var idx = sidx ? 0 : 1;
+                    // push the current data into stackData
+                    //this._stackData.push(this.series[i].data);
+                    var temp = $.extend(true, [], this.series[i].data);
+                    // create the data that will be plotted for this series
+                    var plotdata = $.extend(this, [], this.series[i].data);
+                    // for first series, nothing to add to stackData.
+                    for (var j=0; j<i; j++) {
+                        var cd = this.series[j].data;
+                        for (var k=0; k<cd.length; k++) {
+                            temp[k][0] += cd[k][0];
+                            temp[k][1] += cd[k][1];
+                            // only need to sum up the stack axis column of data
+                            plotdata[k][sidx] += cd[k][sidx];
+                        }
+                    }
+                    this._plotData.push(plotdata);
+                    this._stackData.push(temp);
+                    this.series[i]._stackData = this._stackData[i];
+                    this.series[i]._plotData = this._plotData[i];
+                }
+                else {
+                    this._stackData.push(this.series[i].data);
+                    this.series[i]._stackData = this.series[i].data;
+                    this._plotData.push(this.series[i].data);
+                    this.series[i]._plotData = this.series[i].data;
+                }
+            }
+        };
+        
         this.getNextSeriesColor = function() {
             var c = this.seriesColors[seriesColorsIndex];
             seriesColorsIndex++;
                 $.jqplot.preParseOptionsHooks[i].call(this, options);
             }
             this.options = $.extend(true, {}, this.defaults, options);
+            this.stackSeries = this.options.stackSeries;
             this._gridPadding = this.options.gridPadding;
             for (var n in this.axes) {
                 var axis = this.axes[n];
                 return temp;
             };
 
-            for (var i=0; i<this.data.length; i++) { 
+            for (var i=0; i<this.data.length; i++) { ;
                 var temp = new Series();
                 for (var j=0; j<$.jqplot.preParseSeriesOptionsHooks.length; j++) {
                     $.jqplot.preParseSeriesOptionsHooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]);
         };
     
         this.draw = function(){
+            console.log(this);
             for (var i=0; i<$.jqplot.preDrawHooks.length; i++) {
                 $.jqplot.preDrawHooks[i].call(this);
             }

File src/jqplot.lineRenderer.js

     $.jqplot.LineRenderer.prototype.init = function(options) {
         $.extend(true, this.renderer, options);
         // set the shape renderer options
-        var opts = {lineJoin:'miter', lineCap:'round', fill:false, isarc:false, strokeStyle:this.color, fillStyle:this.color, lineWidth:this.lineWidth, closePath:false};
+        var opts = {lineJoin:'miter', lineCap:'round', fill:this.fill, isarc:false, strokeStyle:this.color, fillStyle:this.color, lineWidth:this.lineWidth, closePath:this.fill};
         this.renderer.shapeRenderer.init(opts);
         // set the shadow renderer options
-        var sopts = {lineJoin:'miter', lineCap:'round', fill:false, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.lineWidth, closePath:false};
+        var sopts = {lineJoin:'miter', lineCap:'round', fill:this.fill, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.lineWidth, closePath:this.fill};
         this.renderer.shadowRenderer.init(sopts);
     };
     
+    // Method: setGridData
+    // converts the user data values to grid coordinates and stores them
+    // in the gridData array.
+    // Called with scope of a series.
     $.jqplot.LineRenderer.prototype.setGridData = function() {
         // recalculate the grid data
         var xp = this._xaxis.series_u2p;
         var yp = this._yaxis.series_u2p;
+        var gd = [];
+        var data = [];
         this.gridData = [];
-        this.gridData.push([xp.call(this._xaxis, this.data[0][0]), yp.call(this._yaxis, this.data[0][1])]);
-        for (var i=1; i<this.data.length; i++) {
-            this.gridData.push([xp.call(this._xaxis, this.data[i][0]), yp.call(this._yaxis, this.data[i][1])]);
+        // if (this._stack) {
+        //     var sidx = this._stackAxis == 'x' ? 0 : 1;
+        //     var idx = s ? 0 : 1;
+        //     for (var i=0; i<this.data.length; i++) {
+        //         var temp = [];
+        //         temp[sidx] = this._stackData[i][sidx];
+        //         temp[idx] = this.data[i][idx];
+        //         data.push(temp);
+        //     }
+        // }
+        // else {
+        //     data = this.data;
+        // }
+        data = this._plotData;
+//        gd.push([xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, data[0][1])]); //????
+        for (var i=0; i<this.data.length; i++) {
+            gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
         }
+        this.gridData = gd;
     };
     
+    // Method: makeGridData
+    // converts any arbitrary data values to grid coordinates and
+    // returns them.  This method exists so that plugins can use a series'
+    // linerenderer to generate grid data points without overwriting the
+    // grid data associated with that series.
+    // Called with scope of a series.
     $.jqplot.LineRenderer.prototype.makeGridData = function(data) {
         // recalculate the grid data
         var xp = this._xaxis.series_u2p;
         var yp = this._yaxis.series_u2p;
-        var gridData = [];
-        gridData.push([xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, data[0][1])]);
-        for (var i=1; i<data.length; i++) {
-            gridData.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
+        var gd = [];
+        // gd.push([xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, data[0][1])]);
+        for (var i=0; i<data.length; i++) {
+            gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
         }
-        return gridData;
+        return gd;
     };
+    
 
     // called within scope of series.
-    $.jqplot.LineRenderer.prototype.draw = function(ctx, gd, options) {
+    $.jqplot.LineRenderer.prototype.draw = function(ctx, gd, options, plot) {
         var i;
         var opts = (options != undefined) ? options : {};
         var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
         var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+        var fill = (opts.fill != undefined) ? opts.fill : this.fill;
         ctx.save();
         if (showLine) {
-            this.renderer.shapeRenderer.draw(ctx, gd, opts);
-        
+            // if we fill, we'll have to add points to close the curve.
+            if (fill) {
+                var gridymin = this._yaxis.series_u2p(this._yaxis.min) - this.gridBorderWidth / 2;
+                // IE doesn't return new length on unshift
+                gd.unshift([gd[0][0], gridymin]);
+                len = gd.length;
+                gd.push([gd[len - 1][0], gridymin]);
+            }
+            this.renderer.shapeRenderer.draw(ctx, gd, opts);                
+
             // now draw the shadows
             if (shadow) {
                 this.renderer.shadowRenderer.draw(ctx, gd, opts);
   
   <!-- BEGIN: load jqplot -->
   <script language="javascript" type="text/javascript" src="src/jquery.jqplot.js"></script>
-  <script language="javascript" type="text/javascript" src="src/plugins/jqplot.logAxisRenderer.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.highlighter.js"></script>
-  <script language="javascript" type="text/javascript" src="src/plugins/jqplot.dragable.js"></script>
+  <!-- // <script language="javascript" type="text/javascript" src="src/plugins/jqplot.logAxisRenderer.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.highlighter.js"></script>
+  // <script language="javascript" type="text/javascript" src="src/plugins/jqplot.dragable.js"></script> -->
   <!-- <script language="javascript" type="text/javascript" src="src/plugins/jqplot.trendline.js"></script>-->
   <!-- END: load jqplot -->
   <style type="text/css" media="screen">
   <script type="text/javascript" language="javascript">
   
   $(document).ready(function(){
-    // $.jqplot.installPath = 'src';
-    // $.jqplot.pluginsPath = 'src/plugins';
-    
-    // plot = $.jqplot('chart', [[4,2,5,9,1], [7,6,9,3,5]], {
-    //   series:[{dragable:{constrainTo:'x'}, trendline:{label:'tl 1'}}, {dragable:{constrainTo:'y'}, trendline:{label:'tl 2'}}],
-    //   axes:{xaxis:{ tickOptions:{formatString:'%.2f'}}, yaxis:{ tickOptions:{formatString:'%.2f'}}},
-    //   legend:{show:true},
-    //   title:'dP vs. Q relation',
-    //   cursor:{showTooltip:true}
-    //   });
       
 
     var cosPoints = [];
     for (var i = 0; i < 6.3; i += 0.6) {
         powPoints2.push([i, -3 - Math.pow(i / 4, 2)]);
     }
-    var largepoints = [15, 134.345321, 1452, 15432, 1134.2, 18.2];
-    var smallpoints = [2.34458232, 1.23, .87, .01234567, .0021, .000321, .0000456789];
-//    var allpoints = [15432, 1432, 132, 12, 1, .87, .01234567, .0021, .000321, .0000456789]
-    plot1 = $.jqplot('chart', [largepoints, smallpoints], {
-        // title: 'jqPlot',
+
+    var l1 = [4, 2, 3, 9, 3];
+    var l2 = [8, 1, 5, 6, 8];
+
+
+    plot1 = $.jqplot('chart', [l1, l2], {
+        stackSeries: false,
         grid:{background:'#fefbf3', borderWidth:2.5},
-        cursor:{showTooltip:true, followMouse:false, tooltipLocation:'sw'},
-        axes:{yaxis:{min:-3000},
-          xaxis:{}},
-          series:[{}, {yaxis:'y2axis'}]
-        // series: [
-        // {
-        //  
-        // },
-        // {
-        //     showLine: false,
-        //     lineWidth: 1.5,
-        //     markerOptions: {
-        //         style: 'diamond',
-        //         size: 12
-        //     }
-        // },
-        // {
-        //   //color: '#FFA372',
-        //   //color: '#D59622',
-        //   lineWidth: 3.5,
-        //     markerOptions: {
-        //         style: 'circle'
-        //     }
-        // },
-        // {
-        //     lineWidth: 6,
-        //     markerOptions: {
-        //         style: 'filledSquare',
-        //         size: 15,
-        //         shadowOffset: 1.8,
-        //         shadowDepth: 3,
-        //         shadowAngle: 55,
-        //         shadowAlpha: 0.08
-        //     },
-        //     // shadowOffset: 1.8,
-        //     // shadowDepth: 3,
-        //     // shadowAngle: 55,
-        //     // shadowAlpha: 0.08
-        // }]
+        seriesDefaults: {fill: true, showMarker: false, shadow: true},
+        axes:{xaxis:{pad:1.0, numberTicks:5}},
+        series:[{}, {color:'rgba(200, 45, 120, 0.5)'}]
     });
 
-    
-    // $('#chart').bind('jqplotSeriesDraw', function(ev, data, griddata) {
-    //   console.log('series draw: ', data, griddata);
-    // });
-    // 
-    // $('#chart').bind('jqplotSeriesPointChange', function(ev, s, p, d, g) {
-    //   console.log('point changed. series: ', s, ' point: ', p, ' data: ', d, ' grid data: ', g);
-    // });
+
   });
   </script>
   </head>
   <body>
-    <div id="chart" style="margin-top:20px; margin-left:20px; width:500px; height:300px;"></div>
+    <div id="chart" style="margin-top:20px; margin-left:20px; width:400px; height:270px;"></div>
   </body>
 </html>