Commits

Anonymous committed d523313 Merge

Sync to jqplot repo

Comments (0)

Files changed (7)

examples/dynamicplot.html

+<!DOCTYPE html>
+
+<html lang="en">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <title></title>
+  <!--[if lt IE 9]><script language="javascript" type="text/javascript" src="../src/excanvas.js"></script><![endif]-->
+  
+  <link rel="stylesheet" type="text/css" href="../src/jquery.jqplot.css" />
+  <link rel="stylesheet" type="text/css" href="examples.css" />
+  
+  <!-- BEGIN: load jquery -->
+  <script language="javascript" type="text/javascript" src="../src/jquery-1.5.1.min.js"></script>
+  <!-- END: load jquery -->
+  
+  <!-- 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.dateAxisRenderer.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.canvasOverlay.js"></script>
+  
+  <!-- END: load jqplot -->
+<script language="javascript" type="text/javascript">
+
+var data = [['2011-04-05 16:00',  1332.63],
+['2011-04-04 16:00',  1332.87],
+['2011-04-01 16:00',  1332.41],
+['2011-03-31 16:00',  1325.83],
+['2011-03-30 16:00',  1328.26],
+['2011-03-29 16:00',  1319.44],
+['2011-03-28 16:00',  1310.19],
+['2011-03-25 16:00',  1313.8],
+['2011-03-24 16:00',  1309.66],
+['2011-03-23 16:00',  1297.54],
+['2011-03-22 16:00',  1293.77],
+['2011-03-21 16:00',  1298.38],
+['2011-03-18 16:00',  1279.21],
+['2011-03-17 16:00',  1273.72],
+['2011-03-16 16:00',  1256.88],
+['2011-03-15 16:00',  1281.87],
+['2011-03-14 16:00',  1296.39],
+['2011-03-11 16:00',  1304.28],
+['2011-03-10 16:00',  1295.11],
+['2011-03-09 16:00',  1320.02],
+['2011-03-08 16:00',  1321.82],
+['2011-03-07 16:00',  1310.13],
+['2011-03-04 16:00',  1321.15],
+['2011-03-03 16:00',  1330.97],
+['2011-03-02 16:00',  1308.44],
+['2011-03-01 16:00',  1306.33],
+['2011-02-28 16:00',  1327.22],
+['2011-02-25 16:00',  1319.88],
+['2011-02-24 16:00',  1306.1],
+['2011-02-23 16:00',  1307.4],
+['2011-02-22 16:00',  1315.44],
+['2011-02-18 16:00',  1343.01],
+['2011-02-17 16:00',  1340.43],
+['2011-02-16 16:00',  1336.32],
+['2011-02-15 16:00',  1328.01],
+['2011-02-14 16:00',  1332.32],
+['2011-02-11 16:00',  1329.15],
+['2011-02-10 16:00',  1321.87],
+['2011-02-09 16:00',  1320.88],
+['2011-02-08 16:00',  1324.57],
+['2011-02-07 16:00',  1319.05]];
+
+///////
+// Put plot options in seperate object as a convienence.
+// These will be reused when calling $.jqplot()
+///////
+var plotOptions = {
+  gridPadding: {top: 1},
+  grid: {shadow:false, borderWidth:1.0},
+  seriesDefaults: {
+    yaxis: 'y2axis'
+  },
+  axes: {
+      xaxis: {
+          renderer:$.jqplot.DateAxisRenderer,
+          tickOptions:{formatString:'%b %d'}, 
+      },
+      y2axis: {
+          tickOptions:{formatString:'%.4f'}
+      }
+  },
+  series: [{
+      showMarker: false
+  }],
+  // noDataIndicator option.  If no data is passed in on plot creation,
+  // A div will be placed roughly in the center of the plot.  Whatever
+  // text or html is given in the "indicator" option will be rendered 
+  // inside the div.
+  noDataIndicator: {
+    show: true,
+    // Here, an animated gif image is rendered with some loading text.
+    indicator: '<img src="ajax-loader.gif" /><br />Loading Data...',
+    // IMPORTANT: Have axes options set since y2axis is off by default
+    // and the yaxis is on be default.  This is necessary due to the way
+    // plots are constructed from data and we don't have any data
+    // when using the "noDataIndicator".
+    axes: {
+      xaxis: {
+        min: 0,
+        max: 5,
+        tickInterval: 1,
+        showTicks: false
+      },
+      yaxis: {
+        show: false
+      },
+      y2axis: {
+        show: true,
+        min: 0,
+        max: 8,
+        tickInterval: 2,
+        showTicks: false
+      }
+    }
+  },
+  // Canvas overlay provides display of arbitrary lines on the plot and
+  // provides a convienent api to manipulate those lines after creation.
+  canvasOverlay: {
+    show: true,
+    // An array of objects to draw over plot.  Here two horizontal lines.
+    // Options to control linne width, color, position and shadow are set.
+    // Can also provide a "name" option to refer to the line by name.
+    // IMPORTANT: set the proper yaxis to "bind" the line to.
+    objects: [
+      {dashedHorizontalLine: {
+        name: 'current',
+        y: 6,
+        lineWidth: 1.5,
+        color: 'rgb(60, 60, 60)',
+        yaxis: 'y2axis',
+        shadow: false,
+        dashPattern: [12, 12]
+      }}
+    ]
+  }
+};
+
+////////
+// Build the initial plot with no data.
+// It will show the animated gif indicator since we have
+// the "noDataIndicator" option set on the plot.  
+///////
+$(document).ready(function(){   
+    plot1 = $.jqplot('chart1',[],plotOptions);
+});
+
+
+///////
+// Functions to handle recreation of plot when data is available
+// and to simulate new data coming into the plot and plot updates.
+///////
+
+
+///////
+// Callback executed when "start" button on web page is pressed.  
+// Simulates recreation of plot when actual data is available.  Sequence is:
+//
+// 1) empty plot container.
+// 2) Recreate plot with call to $.jqplot()
+// 3) Update position of current price line based on data.
+// 4) Create the "pricePointer", a div inidcating the current price.
+// 5) Call updatePricePointer to set current price text and position div.
+// 6) Mock up streaming with a setInterval call.
+///////
+function startit(initialData) {
+  // 1) Empty container.
+  $('#chart1').empty();
+  // 2) Recreate plot.
+  // Note, only call the $.jqplot() method when entire plot needs recreated. 
+  plot1 = $.jqplot('chart1', [initialData], plotOptions);
+
+  // get handle on last data point and current price line.
+  var dp = initialData[initialData.length-1];
+  var co = plot1.plugins.canvasOverlay;
+  var current = co.get('current');
+
+  // 3) Update the limit and stop lines based on the latest data.
+  current.options.y = dp[1];
+  co.draw(plot1);
+
+  // 4) Create the 'pricePointer', element and append to the plot.
+  $('<div id="pricePointer" style="position:absolute;"></div>').appendTo(plot1.target);
+  // 5) updatePricePointer method sets text and position to value passed in.
+  updatePricePointer(dp[1]);
+
+  // 6) Stuff to mock up streaming.
+	counter = 0;
+	Interval = setInterval(runUpdate, 500);
+  d = dp = co = limit = stop = null;
+}
+
+///////
+// Function to generate an updated data value
+// for the last data point in the series.
+// This does not change the timestamp, just the data value.
+///////
+function getNewDataPoint() {
+  // Need to generate some mock data.  Will use the last data from
+  // the plot as a base set.  Adjust the close value by a random amount,
+
+  // a slight adjustment to current price
+  var val = Math.random()*10 * (Math.random() - 0.5);
+  var d = plot1.series[0].data;
+  var dp = d[d.length-1];
+  // get the current values.
+  var ts = dp[0];
+  var curclose = dp[1];
+      
+  // Set the new close value
+  var newval = curclose + val;
+
+  val = d = dp = curclose = null;
+  // Now return a new data set, note haven't changed or used the datetime  value.
+  return ([ts, newval]);
+}
+
+////////
+// function to simulate application loop, where app does something
+// every time it gets a new data point.
+////////
+function runUpdate() {
+
+  // Simulate 1000 iterations of streaming for testing purposes.
+  if (counter < 1000) {
+    var newdata = getNewDataPoint();
+    // updatePlot method each time have an updated data point.
+    updatePlot(newdata);
+    // update pointer div with latest data val.
+    updatePricePointer(newdata[1]);
+
+    // Stuff for mock streaming
+    counter++;
+    newdata = null;
+	}
+
+// Remove the setInterval after 1000 iterations.
+	else {
+		clearInterval(Interval);
+	}
+}
+
+////////
+// Function updating plot when last data point is updated.  If totally new
+// data is fed in, can recreate plot with call to $.jqplot();
+////////
+function updatePlot(newdata) {
+  
+  // get references for convienence.  
+  var d = plot1.series[0].data;
+  var dp = d[d.length-1];
+
+  // Update the data on the series in the plot.
+  // Becuase of some inconsistent reference handling in some browsers,
+  // it is safest to set individual data elements by value.
+  dp[0] = newdata[0];  // the datetme
+  dp[1] = newdata[1];  // Close
+
+  // Get handle on the canvas overlary for the current price line.
+  var co = plot1.plugins.canvasOverlay;
+  var current = co.get('current');
+
+  // Update the y value of the current price line.
+  // This does not redraw the lines, just updates the values.
+  current.options.y = dp[1];
+
+  // Check to see if we need to rescale the plot,
+  // if the data is now above/below the axes.
+  if (dp[1] > plot1.axes.y2axis.max) {
+    plot1.replot({resetAxes:true});
+    // Need to re-create the 'pricePointer' element  as it will be destoyed
+    // when the plot is recreated.
+    $('<div id="pricePointer" style="position:absolute;"></div>').appendTo(plot1.target);
+    // Set the "pricePointer" to the current price.
+    updatePricePointer(dp[1]);
+  }
+
+  else if (dp[1] < plot1.axes.y2axis.min) {
+    plot1.replot({resetAxes:true});
+    // Need to re-create the 'pricePointer' element  as it will be destoyed
+    // when the plot is recreated.
+    $('<div id="pricePointer" style="position:absolute;"></div>').appendTo(plot1.target);
+    // Set the "pricePointer" to the current price.
+    updatePricePointer(dp[1]);
+  }
+  
+  // If no axes rescaling needed, just redraw the series on the canvas
+  // and redraw the current price line at new positions.
+  else {
+    plot1.drawSeries({}, 0);
+    co.draw(plot1);
+  }
+  
+  d = dp = newdata = limit = stop = co = null;
+}
+
+////////
+// function to update the text and reposition the price pointer
+////////
+function updatePricePointer(val) {
+  // get handle on the price pointer element.
+  var div = $('#pricePointer');
+  // Get a handle on the plot axes and one of the y2axis ticks.
+  var axis = plot1.axes.y2axis;
+  var tick = axis._ticks[0];
+  // use the tick's formatter to format the value string.
+  var str = tick.formatter(tick.formatString, val);
+  // set the text of the pointer.
+  div.html('&laquo;&nbsp;'+str);
+  // Create css positioning strings for the pointer.
+  var left = plot1._gridPadding.left + plot1.grid._width + 3 + 'px';
+  // use the axis positioning functions to set the right y position.
+  var top = axis.u2p(val) - div.outerHeight()/2 + 'px';
+  // set the div in the right spot
+  div.css({left:left, top:top});
+
+  div = axis = tick = str = left = top = null;
+}
+
+function stopit() {
+	clearInterval(Interval);
+}
+	
+
+
+</script>
+
+<style type="text/css">
+  #pricePointer {
+    background-color: rgba(40,40,40,0.7);
+    color: rgb(240,240,240);
+    padding: 2px 2px 2px 0px;
+  }
+</style>
+
+  </head>
+  <body>
+<?php include "nav.inc"; ?>
+<div id="chart1" style="margin:20px;height:400px; width:800px;"></div>
+<div id="pricePointer" style="display:none;"></div>
+<button onclick="startit(data)">Start</button>
+<button onclick="stopit()">Stop</button>
+  </body>
+</html>
         // prop: lineWidth
         // width of the line in pixels.  May have different meanings depending on renderer.
         this.lineWidth = 2.5;
+        // prop: lineJoin
+        // Canvas lineJoin style between segments of series.
+        this.lineJoin = 'round';
+        // prop: lineCap
+        // Canvas lineCap style at ends of line.
+        this.lineCap = 'round';
         // prop: shadow
         // wether or not to draw a shadow on the line
         this.shadow = true;

src/jqplot.lineRenderer.js

         
         $.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 opts = {lineJoin:this.lineJoin, lineCap:this.lineCap, fill:this.fill, isarc:false, strokeStyle:this.color, fillStyle:this.fillColor, lineWidth:this.lineWidth, closePath:this.fill};
         this.renderer.shapeRenderer.init(opts);
         // set the shadow renderer options
         // scale the shadowOffset to the width of the line.
         else {
             var shadow_offset = this.shadowOffset*Math.atan((this.lineWidth/2.5))/0.785398163;
         }
-        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};
+        var sopts = {lineJoin:this.lineJoin, lineCap:this.lineCap, 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 = [[],[]];

src/jqplot.shapeRenderer.js

         var clearRect = (opts.clearRect != null) ? opts.clearRect : this.clearRect;
         var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
         ctx.lineWidth = opts.lineWidth || this.lineWidth;
-        ctx.lineJoin = opts.lineJoing || this.lineJoin;
+        ctx.lineJoin = opts.lineJoin || this.lineJoin;
         ctx.lineCap = opts.lineCap || this.lineCap;
         ctx.strokeStyle = (opts.strokeStyle || opts.color) || this.strokeStyle;
         ctx.fillStyle = opts.fillStyle || this.fillStyle;

src/plugins/jqplot.barRenderer.js

     
     $.jqplot.BarRenderer.prototype.draw = function(ctx, gridData, options) {
         var i;
-        var opts = (options != undefined) ? options : {};
+        // Ughhh, have to make a copy of options b/c it may be modified later.
+        var opts = $.extend({}, 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;

src/plugins/jqplot.dateAxisRenderer.js

         // if we already have ticks, use them.
         // ticks must be in order of increasing value.
         
+        min = ((this.min != null) ? new $.jsDate(this.min).getTime() : db.min);
+        max = ((this.max != null) ? new $.jsDate(this.max).getTime() : db.max);
+
+        var range = max - min;
+        
         if (userTicks.length) {
             // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
             for (i=0; i<userTicks.length; i++){
             this.max = this._ticks[this.numberTicks-1].value;
             this.daTickInterval = [(this.max - this.min) / (this.numberTicks - 1)/1000, 'seconds'];
         }
+
+        ////////
+        // We don't have any ticks yet, let's make some!
+        // Doing complete autoscaling, no user options specified
+        ////////
         
-        // we don't have any ticks yet, let's make some!
+        else if (this.tickInterval == null && this.min == null && this.max == null && this.numberTicks == null) {
+            var ret = $.jqplot.LinearTickGenerator(min, max); 
+            // calculate a padded max and min, points should be less than these
+            // so that they aren't too close to the edges of the plot.
+            // User can adjust how much padding is allowed with pad, padMin and PadMax options. 
+            var tumin = min + range*(this.padMin - 1);
+            var tumax = max - range*(this.padMax - 1);
+
+            if (min <=tumin || max >= tumax) {
+                tumin = min - range*(this.padMin - 1);
+                tumax = max + range*(this.padMax - 1);
+                ret = $.jqplot.LinearTickGenerator(tumin, tumax);
+            }
+
+            this.min = ret[0];
+            this.max = ret[1];
+            this.numberTicks = ret[2];
+            this.tickInterval = ret[4];
+            this.daTickInterval = [this.tickInterval/1000, 'seconds'];
+
+            for (var i=0; i<this.numberTicks; i++){
+                var min = new $.jsDate(this.min);
+                tt = min.add(i*this.daTickInterval[0], this.daTickInterval[1]).getTime();
+                var t = new this.tickRenderer(this.tickOptions);
+                // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
+                if (!this.showTicks) {
+                    t.showLabel = false;
+                    t.showMark = false;
+                }
+                else if (!this.showTickMarks) {
+                    t.showMark = false;
+                }
+                t.setTick(tt, this.name);
+                this._ticks.push(t);
+            }           
+        }
+
+        ////////
+        // Some option(s) specified, work around that.
+        ////////
+        
         else {		
             if (name == 'xaxis' || name == 'x2axis') {
                 dim = this._plotDimensions.width;
                     }
                 }
             }
-        
-            min = ((this.min != null) ? new $.jsDate(this.min).getTime() : db.min);
-            max = ((this.max != null) ? new $.jsDate(this.max).getTime() : db.max);
             
             // if min and max are same, space them out a bit
             if (min == max) {
                 max += adj;
             }
 
-            var range = max - min;
+            range = max - min;
 			
 			var optNumTicks = 2 + parseInt(Math.max(0, dim-100)/100, 10);
 			
                 this._ticks.push(t);
             }
         }
+
+
         if (this._daTickInterval == null) {
             this._daTickInterval = this.daTickInterval;    
         }

src/plugins/jqplot.ohlcRenderer.js

                     xmaxidx = i+1;
                 }
             }
+
+            var dwidth = this.gridData[xmaxidx-1][0] - this.gridData[xminidx][0];
+            var nvisiblePoints = xmaxidx - xminidx;
+            try {
+                var dinterval = Math.abs(this._xaxis.series_u2p(parseInt(this._xaxis._intervalStats[0].sortedIntervals[0].interval)) - this._xaxis.series_u2p(0)); 
+            }
+
+            catch (e) {
+                var dinterval = dwidth / nvisiblePoints;
+            }
             
             if (r.candleStick) {
                 if (typeof(r.bodyWidth) == 'number') {
                     r._bodyWidth = r.bodyWidth;
                 }
                 else {
-                    r._bodyWidth = Math.min(20, ctx.canvas.width/(xmaxidx - xminidx)/2);
+                    r._bodyWidth = Math.min(20, dinterval/1.75);
                 }
             }
             else {
                     r._tickLength = r.tickLength;
                 }
                 else {
-                    r._tickLength = Math.min(10, ctx.canvas.width/(xmaxidx - xminidx)/4);
+                    r._tickLength = Math.min(10, dinterval/3.5);
                 }
             }