* If the Dygraph has dates on the x-axis, these will be millis since epoch.
*/
Dygraph.prototype.xAxisRange = function() {
- if (this.dateWindow_) return this.dateWindow_;
+ return this.dateWindow_ ? this.dateWindow_ : this.xAxisExtremes();
+};
- // The entire chart is visible.
+/**
+ * Returns the lower- and upper-bound x-axis values of the
+ * data set.
+ */
+Dygraph.prototype.xAxisExtremes = function() {
var left = this.rawData_[0][0];
var right = this.rawData_[this.rawData_.length - 1][0];
return [left, right];
-};
+}
/**
* Returns the currently-visible y-range for an axis. This can be affected by
/**
* Converts a y for an axis to a percentage from the top to the
- * bottom of the div.
+ * bottom of the drawing area.
*
* If the coordinate represents a value visible on the canvas, then
* the value will be between 0 and 1, where 0 is the top of the canvas.
var pct;
if (!this.axes_[axis].logscale) {
- // yrange[1] - y is unit distance from the bottom.
- // yrange[1] - yrange[0] is the scale of the range.
+ // yRange[1] - y is unit distance from the bottom.
+ // yRange[1] - yRange[0] is the scale of the range.
// (yRange[1] - y) / (yRange[1] - yRange[0]) is the % from the bottom.
pct = (yRange[1] - y) / (yRange[1] - yRange[0]);
} else {
}
/**
+ * Converts an x value to a percentage from the left to the right of
+ * the drawing area.
+ *
+ * If the coordinate represents a value visible on the canvas, then
+ * the value will be between 0 and 1, where 0 is the left of the canvas.
+ * However, this method will return values outside the range, as
+ * values can fall outside the canvas.
+ *
+ * If x is null, this returns null.
+ */
+Dygraph.prototype.toPercentXCoord = function(x) {
+ if (x == null) {
+ return null;
+ }
+
+ var area = this.plotter_.area;
+ var xRange = this.xAxisRange();
+
+ var pct;
+ // xRange[1] - x is unit distance from the right.
+ // xRange[1] - xRange[0] is the scale of the range.
+ // (xRange[1] - x / (xRange[1] - xRange[0]) is the % from the right.
+ // 1 - (that) is the % distance from the left.
+ pct = (xRange[1] - x) / (xRange[1] - xRange[0]);
+ // There's a way to optimize that, but I'm copying the y-coord function
+ // and am lazy.
+ return 1 - pct;
+}
+
+/**
* Returns the number of columns (including the independent variable).
*/
Dygraph.prototype.numColumns = function() {
context.initialLeftmostDate = xRange[0];
context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1);
+ // TODO(konigsberg): do that clever "undefined" thing.
+ if (g.attr_("panFrame")) {
+ var maxXPixelsToDraw = g.width_ * g.attr_("panFrame");
+ var xExtremes = g.xAxisExtremes(); // I REALLY WANT TO CALL THIS xTremes!
+
+ var boundedLeftX = g.toDomXCoord(xExtremes[0]) - maxXPixelsToDraw;
+ var boundedRightX = g.toDomXCoord(xExtremes[1]) + maxXPixelsToDraw;
+
+ var boundedLeftDate = g.toDataXCoord(boundedLeftX);
+ var boundedRightDate = g.toDataXCoord(boundedRightX);
+ context.boundedDates = [boundedLeftDate, boundedRightDate];
+
+ var boundedValues = [];
+ var maxYPixelsToDraw = g.height_ * g.attr_("panFrame");
+
+ for (var i = 0; i < g.axes_.length; i++) {
+ var axis = g.axes_[i];
+ var yExtremes = axis.extremeRange;
+
+ var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw;
+ var boundedBottomY = g.toDomYCoord(yExtremes[1], i) - maxYPixelsToDraw;
+
+ var boundedTopValue = g.toDataYCoord(boundedTopY);
+ var boundedBottomValue = g.toDataYCoord(boundedBottomY);
+
+ console.log(yExtremes[0], yExtremes[1], boundedTopValue, boundedBottomValue);
+ // could reverse these, who knows?
+ boundedValues[i] = [boundedTopValue, boundedBottomValue];
+ }
+ context.boundedValues = boundedValues;
+ }
+
// Record the range of each y-axis at the start of the drag.
// If any axis has a valueRange or valueWindow, then we want a 2D pan.
context.is2DPan = false;
var minDate = context.initialLeftmostDate -
(context.dragEndX - context.dragStartX) * context.xUnitsPerPixel;
+ if (context.boundedDates) {
+ minDate = Math.max(minDate, context.boundedDates[0]);
+ }
var maxDate = minDate + context.dateRange;
+ if (context.boundedDates) {
+ if (maxDate > context.boundedDates[1]) {
+ // Adjust minDate, and recompute maxDate.
+ minDate = minDate - (maxDate - context.boundedDates[1]);
+ var maxDate = minDate + context.dateRange;
+ }
+ }
+
g.dateWindow_ = [minDate, maxDate];
// y-axis scaling is automatic unless this is a full 2D pan.
var pixelsDragged = context.dragEndY - context.dragStartY;
var unitsDragged = pixelsDragged * axis.unitsPerPixel;
+
+ var boundedValue = context.boundedValues ? context.boundedValues[i] : null;
// In log scale, maxValue and minValue are the logs of those values.
var maxValue = axis.initialTopValue + unitsDragged;
+ if (boundedValue) {
+ maxValue = Math.min(maxValue, boundedValue[1]);
+ }
var minValue = maxValue - axis.dragValueRange;
+ if (boundedValue) {
+ if (minValue < boundedValue[0]) {
+ // Adjust maxValue, and recompute minValue.
+ maxValue = maxValue - (minValue - boundedValue[0]);
+ minValue = maxValue - axis.dragValueRange;
+ }
+ }
if (axis.logscale) {
axis.valueWindow = [ Math.pow(Dygraph.LOG_SCALE, minValue),
Math.pow(Dygraph.LOG_SCALE, maxValue) ];
px: 0,
py: 0,
+ // Values for use with panFrame, which limit how far outside the
+ // graph's data boundaries it can be panned.
+ boundedDates: null, // [minDate, maxDate]
+ boundedValues: null, // [[minValue, maxValue] ...]
+
initializeMouseDown: function(event, g, context) {
// prevents mouse drags from selecting page text.
if (event.preventDefault) {
* @private
*/
Dygraph.prototype.mouseMove_ = function(event) {
- var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.mouseEventElement_);
- var points = this.layout_.points;
-
// This prevents JS errors when mousing over the canvas before data loads.
+ var points = this.layout_.points;
if (points === undefined) return;
+ var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.mouseEventElement_);
+
var lastx = -1;
var lasty = -1;
// Compute extreme values, a span and tick marks for each axis.
for (var i = 0; i < this.axes_.length; i++) {
var axis = this.axes_[i];
- if (axis.valueWindow) {
- // This is only set if the user has zoomed on the y-axis. It is never set
- // by a user. It takes precedence over axis.valueRange because, if you set
- // valueRange, you'd still expect to be able to pan.
- axis.computedValueRange = [axis.valueWindow[0], axis.valueWindow[1]];
- } else if (axis.valueRange) {
- // This is a user-set value range for this axis.
- axis.computedValueRange = [axis.valueRange[0], axis.valueRange[1]];
- } else {
+
+ {
// Calculate the extremes of extremes.
var series = seriesForAxis[i];
var minY = Infinity; // extremes[series[0]][0];
if (minY > 0) minAxisY = 0;
}
}
-
- axis.computedValueRange = [minAxisY, maxAxisY];
+ axis.extremeRange = [minAxisY, maxAxisY];
+ }
+ if (axis.valueWindow) {
+ // This is only set if the user has zoomed on the y-axis. It is never set
+ // by a user. It takes precedence over axis.valueRange because, if you set
+ // valueRange, you'd still expect to be able to pan.
+ axis.computedValueRange = [axis.valueWindow[0], axis.valueWindow[1]];
+ } else if (axis.valueRange) {
+ // This is a user-set value range for this axis.
+ axis.computedValueRange = [axis.valueRange[0], axis.valueRange[1]];
+ } else {
+ axis.computedValueRange = axis.extremeRange;
}
// Add ticks. By default, all axes inherit the tick positions of the
"labels": ["Annotations"],
"type": "boolean",
"description": "Only applies when Dygraphs is used as a GViz chart. Causes string columns following a data series to be interpreted as annotations on points in that series. This is the same format used by Google's AnnotatedTimeLine chart."
+ },
+ "panFrame": {
+ "default": "null",
+ "labels": ["Axis Display?"],
+ "type": "float",
+ "default": "null",
+ "description": "A value representing the farthest a graph may be panned, in percent of the display. For example, a value of 0.1 means that the graph can only be panned 10% pased the edges of the displayed values. null means no bounds."
}
}
; // </JSON>