errorBars: false,
fractions: false,
wilsonInterval: true, // only relevant if fractions is true
- customBars: false
+ customBars: false,
+ fillGraph: false
};
// Various logging levels.
var self = this;
// Tracks whether the mouse is down right now
- var mouseDown = false;
+ var isZooming = false;
+ var isPanning = false;
var dragStartX = null;
var dragStartY = null;
var dragEndX = null;
var dragEndY = null;
var prevEndX = null;
+ var draggingDate = null;
+ var dateRange = null;
// Utility function to convert page-wide coordinates to canvas coords
var px = 0;
// Draw zoom rectangles when the mouse is down and the user moves around
Dygraph.addEvent(this.hidden_, 'mousemove', function(event) {
- if (mouseDown) {
+ if (isZooming) {
dragEndX = getX(event);
dragEndY = getY(event);
self.drawZoomRect_(dragStartX, dragEndX, prevEndX);
prevEndX = dragEndX;
+ } else if (isPanning) {
+ dragEndX = getX(event);
+ dragEndY = getY(event);
+
+ // Want to have it so that:
+ // 1. draggingDate appears at dragEndX
+ // 2. daterange = (dateWindow_[1] - dateWindow_[0]) is unaltered.
+
+ self.dateWindow_[0] = draggingDate - (dragEndX / self.width_) * dateRange;
+ self.dateWindow_[1] = self.dateWindow_[0] + dateRange;
+ self.drawGraph_(self.rawData_);
}
});
// Track the beginning of drag events
Dygraph.addEvent(this.hidden_, 'mousedown', function(event) {
- mouseDown = true;
px = Dygraph.findPosX(self.canvas_);
py = Dygraph.findPosY(self.canvas_);
dragStartX = getX(event);
dragStartY = getY(event);
+
+ if (event.altKey || event.shiftKey) {
+ if (!self.dateWindow_) return; // have to be zoomed in to pan.
+ isPanning = true;
+ dateRange = self.dateWindow_[1] - self.dateWindow_[0];
+ draggingDate = (dragStartX / self.width_) * dateRange +
+ self.dateWindow_[0];
+ } else {
+ isZooming = true;
+ }
});
// If the user releases the mouse button during a drag, but not over the
// canvas, then it doesn't count as a zooming action.
Dygraph.addEvent(document, 'mouseup', function(event) {
- if (mouseDown) {
- mouseDown = false;
+ if (isZooming || isPanning) {
+ isZooming = false;
dragStartX = null;
dragStartY = null;
}
+
+ if (isPanning) {
+ isPanning = false;
+ draggingDate = null;
+ dateRange = null;
+ }
});
// Temporarily cancel the dragging event when the mouse leaves the graph
Dygraph.addEvent(this.hidden_, 'mouseout', function(event) {
- if (mouseDown) {
+ if (isZooming) {
dragEndX = null;
dragEndY = null;
}
// If the mouse is released on the canvas during a drag event, then it's a
// zoom. Only do the zoom if it's over a large enough area (>= 10 pixels)
Dygraph.addEvent(this.hidden_, 'mouseup', function(event) {
- if (mouseDown) {
- mouseDown = false;
+ if (isZooming) {
+ isZooming = false;
dragEndX = getX(event);
dragEndY = getY(event);
var regionWidth = Math.abs(dragEndX - dragStartX);
dragStartX = null;
dragStartY = null;
}
+
+ if (isPanning) {
+ isPanning = false;
+ draggingDate = null;
+ dateRange = null;
+ }
});
// Double-clicking zooms back out
// Time granularity enumeration
Dygraph.SECONDLY = 0;
-Dygraph.TEN_SECONDLY = 1;
-Dygraph.THIRTY_SECONDLY = 2;
-Dygraph.MINUTELY = 3;
-Dygraph.TEN_MINUTELY = 4;
-Dygraph.THIRTY_MINUTELY = 5;
-Dygraph.HOURLY = 6;
-Dygraph.SIX_HOURLY = 7;
-Dygraph.DAILY = 8;
-Dygraph.WEEKLY = 9;
-Dygraph.MONTHLY = 10;
-Dygraph.QUARTERLY = 11;
-Dygraph.BIANNUAL = 12;
-Dygraph.ANNUAL = 13;
-Dygraph.DECADAL = 14;
-Dygraph.NUM_GRANULARITIES = 15;
+Dygraph.TWO_SECONDLY = 1;
+Dygraph.FIVE_SECONDLY = 2;
+Dygraph.TEN_SECONDLY = 3;
+Dygraph.THIRTY_SECONDLY = 4;
+Dygraph.MINUTELY = 5;
+Dygraph.TWO_MINUTELY = 6;
+Dygraph.FIVE_MINUTELY = 7;
+Dygraph.TEN_MINUTELY = 8;
+Dygraph.THIRTY_MINUTELY = 9;
+Dygraph.HOURLY = 10;
+Dygraph.TWO_HOURLY = 11;
+Dygraph.SIX_HOURLY = 12;
+Dygraph.DAILY = 13;
+Dygraph.WEEKLY = 14;
+Dygraph.MONTHLY = 15;
+Dygraph.QUARTERLY = 16;
+Dygraph.BIANNUAL = 17;
+Dygraph.ANNUAL = 18;
+Dygraph.DECADAL = 19;
+Dygraph.NUM_GRANULARITIES = 20;
Dygraph.SHORT_SPACINGS = [];
Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY] = 1000 * 1;
+Dygraph.SHORT_SPACINGS[Dygraph.TWO_SECONDLY] = 1000 * 2;
+Dygraph.SHORT_SPACINGS[Dygraph.FIVE_SECONDLY] = 1000 * 5;
Dygraph.SHORT_SPACINGS[Dygraph.TEN_SECONDLY] = 1000 * 10;
Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_SECONDLY] = 1000 * 30;
Dygraph.SHORT_SPACINGS[Dygraph.MINUTELY] = 1000 * 60;
+Dygraph.SHORT_SPACINGS[Dygraph.TWO_MINUTELY] = 1000 * 60 * 2;
+Dygraph.SHORT_SPACINGS[Dygraph.FIVE_MINUTELY] = 1000 * 60 * 5;
Dygraph.SHORT_SPACINGS[Dygraph.TEN_MINUTELY] = 1000 * 60 * 10;
Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_MINUTELY] = 1000 * 60 * 30;
Dygraph.SHORT_SPACINGS[Dygraph.HOURLY] = 1000 * 3600;
+Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY] = 1000 * 3600 * 2;
Dygraph.SHORT_SPACINGS[Dygraph.SIX_HOURLY] = 1000 * 3600 * 6;
Dygraph.SHORT_SPACINGS[Dygraph.DAILY] = 1000 * 86400;
Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY] = 1000 * 604800;
if (granularity < Dygraph.MONTHLY) {
// Generate one tick mark for every fixed interval of time.
var spacing = Dygraph.SHORT_SPACINGS[granularity];
- var format = '%d%b'; // e.g. "1 Jan"
- // TODO(danvk): be smarter about making sure this really hits a "nice" time.
- if (granularity < Dygraph.HOURLY) {
- start_time = spacing * Math.floor(0.5 + start_time / spacing);
+ var format = '%d%b'; // e.g. "1Jan"
+
+ // Find a time less than start_time which occurs on a "nice" time boundary
+ // for this granularity.
+ var g = spacing / 1000;
+ var d = new Date(start_time);
+ if (g <= 60) { // seconds
+ var x = d.getSeconds(); d.setSeconds(x - x % g);
+ } else {
+ d.setSeconds(0);
+ g /= 60;
+ if (g <= 60) { // minutes
+ var x = d.getMinutes(); d.setMinutes(x - x % g);
+ } else {
+ d.setMinutes(0);
+ g /= 60;
+
+ if (g <= 24) { // days
+ var x = d.getHours(); d.setHours(x - x % g);
+ } else {
+ d.setHours(0);
+ g /= 24;
+
+ if (g == 7) { // one week
+ d.setDate(d.getDate() - d.getDay());
+ }
+ }
+ }
}
+ start_time = d.getTime();
+
for (var t = start_time; t <= end_time; t += spacing) {
var d = new Date(t);
var frac = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
// Try labels every 1, 2, 5, 10, 20, 50, 100, etc.
// Calculate the resulting tick spacing (i.e. this.height_ / nTicks).
// The first spacing greater than pixelsPerYLabel is what we use.
- var mults = [1, 2, 5];
+ if (self.attr_("labelsKMG2")) {
+ var mults = [1, 2, 4, 8];
+ } else {
+ var mults = [1, 2, 5];
+ }
var scale, low_val, high_val, nTicks;
// TODO(danvk): make it possible to set this for x- and y-axes independently.
var pixelsPerTick = self.attr_('pixelsPerYLabel');
for (var i = -10; i < 50; i++) {
- var base_scale = Math.pow(10, i);
+ if (self.attr_("labelsKMG2")) {
+ var base_scale = Math.pow(16, i);
+ } else {
+ var base_scale = Math.pow(10, i);
+ }
for (var j = 0; j < mults.length; j++) {
scale = base_scale * mults[j];
low_val = Math.floor(minV / scale) * scale;
} else {
for (var j = 0; j < series.length; j++) {
var y = series[j][1];
- if (!y) continue;
+ if (y === null || isNaN(y)) continue;
if (maxY == null || y > maxY) {
maxY = y;
}
// Loop over all fields in the dataset
for (var i = 1; i < data[0].length; i++) {
+ if (!this.visibility()[i - 1]) continue;
+
var series = [];
for (var j = 0; j < data.length; j++) {
var date = data[j][0];
this.addXTicks_();
// Tell PlotKit to use this new data and render itself
+ this.layout_.updateOptions({dateWindow: this.dateWindow_});
this.layout_.evaluateWithError();
this.plotter_.clear();
this.plotter_.render();
- this.canvas_.getContext('2d').clearRect(0, 0,
- this.canvas_.width, this.canvas_.height);
+ this.canvas_.getContext('2d').clearRect(0, 0, this.canvas_.width,
+ this.canvas_.height);
};
/**
var ret = [];
for (var i = 0; i < rows; i++) {
var row = [];
- if (!data.getValue(i, 0)) continue;
+ if (typeof(data.getValue(i, 0)) === 'undefined' ||
+ data.getValue(i, 0) === null) {
+ this.warning("Ignoring row " + i +
+ " of DataTable because of undefined or null first column.");
+ continue;
+ }
+
if (indepType == 'date') {
row.push(data.getValue(i, 0).getTime());
} else {
};
/**
+ * Returns a boolean array of visibility statuses.
+ */
+Dygraph.prototype.visibility = function() {
+ // Do lazy-initialization, so that this happens after we know the number of
+ // data series.
+ if (!this.attr_("visibility")) {
+ this.attr_["visibility"] = [];
+ }
+ while (this.attr_("visibility").length < this.rawData_[0].length - 1) {
+ this.attr_("visibility").push(false);
+ }
+ return this.attr_("visibility");
+};
+
+/**
+ * Changes the visiblity of a series.
+ */
+Dygraph.prototype.setVisibility = function(num, value) {
+ var x = this.visibility();
+ if (num < 0 && num >= x.length) {
+ this.warn("invalid series number in setVisibility: " + num);
+ } else {
+ x[num] = value;
+ this.drawGraph_(this.rawData_);
+ }
+};
+
+/**
* Create a new canvas element. This is more complex than a simple
* document.createElement("canvas") because of IE and excanvas.
*/