X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph.js;h=85ab1a9ea37ef8f7dbfa057dfce7ee255fe9e973;hb=44f29e932dcbe83c1bfaebb8735579392e27a5df;hp=5030123675ca070ea98cf73676d5d7ba06456d75;hpb=1ac37a0d1b5ae358b007cf569715d479af80f076;p=dygraphs.git diff --git a/dygraph.js b/dygraph.js index 5030123..85ab1a9 100644 --- a/dygraph.js +++ b/dygraph.js @@ -409,9 +409,14 @@ Dygraph.prototype.rollPeriod = function() { * 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]; @@ -564,7 +569,7 @@ Dygraph.prototype.toDataYCoord = function(y, axis) { /** * 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. @@ -585,8 +590,8 @@ Dygraph.prototype.toPercentYCoord = function(y, axis) { 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 { @@ -597,6 +602,26 @@ Dygraph.prototype.toPercentYCoord = function(y, axis) { } /** + * 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 xRange = this.xAxisRange(); + return (x - xRange[0]) / (xRange[1] - xRange[0]); +} + +/** * Returns the number of columns (including the independent variable). */ Dygraph.prototype.numColumns = function() { @@ -1006,6 +1031,35 @@ Dygraph.startPan = function(event, g, context) { context.initialLeftmostDate = xRange[0]; context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1); + if (g.attr_("panEdgeFraction")) { + var maxXPixelsToDraw = g.width_ * g.attr_("panEdgeFraction"); + 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_("panEdgeFraction"); + + 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); + + 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; @@ -1041,7 +1095,18 @@ Dygraph.movePan = function(event, g, context) { 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]); + maxDate = minDate + context.dateRange; + } + } + g.dateWindow_ = [minDate, maxDate]; // y-axis scaling is automatic unless this is a full 2D pan. @@ -1052,10 +1117,22 @@ Dygraph.movePan = function(event, g, context) { 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) ]; @@ -1273,6 +1350,11 @@ Dygraph.prototype.createDragInterface_ = function() { px: 0, py: 0, + // Values for use with panEdgeFraction, 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) { @@ -1485,12 +1567,12 @@ Dygraph.prototype.doUnzoom_ = function() { * @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; @@ -2635,18 +2717,25 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { seriesForAxis[idx].push(series); } + // If no series are defined or visible then fill in some reasonable defaults. + if (seriesForAxis.length == 0) { + var axis = this.axes_[0]; + axis.computedValueRange = [0, 1]; + var ret = + Dygraph.numericTicks(axis.computedValueRange[0], + axis.computedValueRange[1], + this, + axis); + axis.ticks = ret.ticks; + this.numYDigits_ = ret.numDigits; + return; + } + // 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]; @@ -2682,8 +2771,18 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { 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 @@ -3240,6 +3339,11 @@ Dygraph.prototype.parseDataTable_ = function(data) { annotations.push(ann); } } + + // Strip out infinities, which give dygraphs problems later on. + for (var j = 0; j < row.length; j++) { + if (!isFinite(row[j])) row[j] = null; + } } else { for (var j = 0; j < cols - 1; j++) { row.push([ data.getValue(i, 1 + 2 * j), data.getValue(i, 2 + 2 * j) ]); @@ -3248,11 +3352,6 @@ Dygraph.prototype.parseDataTable_ = function(data) { if (ret.length > 0 && row[0] < ret[ret.length - 1][0]) { outOfOrder = true; } - - // Strip out infinities, which give dygraphs problems later on. - for (var j = 0; j < row.length; j++) { - if (!isFinite(row[j])) row[j] = null; - } ret.push(row); } @@ -4020,6 +4119,13 @@ Dygraph.OPTIONS_REFERENCE = // "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." + }, + "panEdgeFraction": { + "default": "null", + "labels": ["Axis Display", "Interactive Elements"], + "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." } } ; //