X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph.js;h=6ab7606b40cfa76b2cce602668f118b6e773b104;hb=0747928aa252cd8da06439faa3c456dcc75f4128;hp=f3f210714903c47fcd8767455e980d9223f726a8;hpb=ee672584f7df30a1ed7a702bd0ead39f783c3493;p=dygraphs.git diff --git a/dygraph.js b/dygraph.js index f3f2107..6ab7606 100644 --- a/dygraph.js +++ b/dygraph.js @@ -79,6 +79,12 @@ Dygraph.DEFAULT_WIDTH = 480; Dygraph.DEFAULT_HEIGHT = 320; Dygraph.AXIS_LINE_WIDTH = 0.3; +Dygraph.LOG_SCALE = 10; +Dygraph.LOG_BASE_E_OF_TEN = Math.log(Dygraph.LOG_SCALE); +Dygraph.log10 = function(x) { + return Math.log(x) / Dygraph.LOG_BASE_E_OF_TEN; +} + // Default attribute values. Dygraph.DEFAULT_ATTRS = { highlightCircleSize: 3, @@ -128,6 +134,8 @@ Dygraph.DEFAULT_ATTRS = { stepPlot: false, avoidMinZero: false, + + interactionModel: null // will be set to Dygraph.defaultInteractionModel. }; // Various logging levels. @@ -158,7 +166,7 @@ Dygraph.prototype.__old_init__ = function(div, file, labels, attrs) { /** * Initializes the Dygraph. This creates a new DIV and constructs the PlotKit - * and context <canvas> inside of it. See the constructor for details + * and context <canvas> inside of it. See the constructor for details. * on the parameters. * @param {Element} div the Element to render the graph into. * @param {String | Function} file Source data @@ -353,44 +361,149 @@ Dygraph.prototype.yAxisRanges = function() { * If specified, do this conversion for the coordinate system of a particular * axis. Uses the first axis by default. * Returns a two-element array: [X, Y] + * + * Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord + * instead of toDomCoords(null, y, axis). */ Dygraph.prototype.toDomCoords = function(x, y, axis) { - var ret = [null, null]; + return [ this.toDomXCoord(x), this.toDomYCoord(y, axis) ]; +}; + +/** + * Convert from data x coordinates to canvas/div X coordinate. + * If specified, do this conversion for the coordinate system of a particular + * axis. Uses the first axis by default. + * returns a single value or null if x is null. + */ +Dygraph.prototype.toDomXCoord = function(x) { + if (x == null) { + return null; + }; + var area = this.plotter_.area; - if (x !== null) { - var xRange = this.xAxisRange(); - ret[0] = area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w; - } + var xRange = this.xAxisRange(); + return area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w; +} - if (y !== null) { - var yRange = this.yAxisRange(axis); - ret[1] = area.y + (yRange[1] - y) / (yRange[1] - yRange[0]) * area.h; - } +/** + * Convert from data x coordinates to canvas/div Y coordinate and optional + * axis. Uses the first axis by default. + * + * returns a single value or null if y is null. + */ +Dygraph.prototype.toDomYCoord = function(y, axis) { + var pct = this.toPercentYCoord(y, axis); - return ret; -}; + if (pct == null) { + return null; + } + return area.y + pct * area.h; +} /** * Convert from canvas/div coords to data coordinates. * If specified, do this conversion for the coordinate system of a particular * axis. Uses the first axis by default. - * Returns a two-element array: [X, Y] + * Returns a two-element array: [X, Y]. + * + * Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord + * instead of toDataCoords(null, y, axis). */ Dygraph.prototype.toDataCoords = function(x, y, axis) { - var ret = [null, null]; + return [ this.toDataXCoord(x), this.toDataYCoord(y, axis) ]; +}; + +/** + * Convert from canvas/div x coordinate to data coordinate. + * + * If x is null, this returns null. + */ +Dygraph.prototype.toDataXCoord = function(x) { + if (x == null) { + return null; + } + var area = this.plotter_.area; - if (x !== null) { - var xRange = this.xAxisRange(); - ret[0] = xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]); + var xRange = this.xAxisRange(); + return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]); +}; + +/** + * Convert from canvas/div y coord to value. + * + * If y is null, this returns null. + * if axis is null, this uses the first axis. + */ +Dygraph.prototype.toDataYCoord = function(y, axis) { + if (y == null) { + return null; } - if (y !== null) { - var yRange = this.yAxisRange(axis); - ret[1] = yRange[0] + (area.h - y) / area.h * (yRange[1] - yRange[0]); + var area = this.plotter_.area; + var yRange = this.yAxisRange(axis); + + if (!this.attr_("logscale")) { + return yRange[0] + (area.h - y) / area.h * (yRange[1] - yRange[0]); + } else { + // Computing the inverse of toDomCoord. + var pct = (y - area.y) / area.h + + // Computing the inverse of toPercentYCoord. The function was arrived at with + // the following steps: + // + // Original calcuation: + // pct = (logr1 - Dygraph.log10(y)) / (logr1 - Dygraph.log10(yRange[0])); + // + // Move denominator to both sides: + // pct * (logr1 - Dygraph.log10(yRange[0])) = logr1 - Dygraph.log10(y); + // + // subtract logr1, and take the negative value. + // logr1 - (pct * (logr1 - Dygraph.log10(yRange[0]))) = Dygraph.log10(y); + // + // Swap both sides of the equation, and we can compute the log of the + // return value. Which means we just need to use that as the exponent in + // e^exponent. + // Dygraph.log10(y) = logr1 - (pct * (logr1 - Dygraph.log10(yRange[0]))); + + var logr1 = Dygraph.log10(yRange[1]); + var exponent = logr1 - (pct * (logr1 - Dygraph.log10(yRange[0]))); + var value = Math.pow(Dygraph.LOG_SCALE, exponent); + return value; + } +}; + +/** + * Converts a y for an axis to a percentage from the top to the + * bottom of the div. + * + * 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. + * However, this method will return values outside the range, as + * values can fall outside the canvas. + * + * If y is null, this returns null. + * if axis is null, this uses the first axis. + */ +Dygraph.prototype.toPercentYCoord = function(y, axis) { + if (y == null) { + return null; } - return ret; -}; + var area = this.plotter_.area; + var yRange = this.yAxisRange(axis); + + var pct; + if (!this.attr_("logscale")) { + // 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 { + var logr1 = Dygraph.log10(yRange[1]); + pct = (logr1 - Dygraph.log10(y)) / (logr1 - Dygraph.log10(yRange[0])); + } + return pct; +} /** * Returns the number of columns (including the independent variable). @@ -431,12 +544,8 @@ Dygraph.addEvent = function(el, evt, fn) { }; -// -// An attempt at scroll wheel management. -// // Based on the article at // http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel - Dygraph.cancelEvent = function(e) { e = e ? e : window.event; if (e.stopPropagation) { @@ -451,7 +560,6 @@ Dygraph.cancelEvent = function(e) { return false; } - /** * Generates interface elements for the Dygraph: a containing div, a div to * display the current point, and a textbox to adjust the rolling average @@ -792,17 +900,14 @@ Dygraph.prototype.dragGetY_ = function(e, context) { return Dygraph.pageY(e) - context.py }; +// Called in response to an interaction model operation that +// should start the default panning behavior. +// +// It's used in the default callback for "mousedown" operations. +// Custom interaction model builders can use it to provide the default +// panning behavior. +// Dygraph.startPan = function(event, g, context) { - // have to be zoomed in to pan. - var zoomedY = false; - for (var i = 0; i < g.axes_.length; i++) { - if (g.axes_[i].valueWindow || g.axes_[i].valueRange) { - zoomedY = true; - break; - } - } - if (!g.dateWindow_ && !zoomedY) return; - context.isPanning = true; var xRange = g.xAxisRange(); context.dateRange = xRange[1] - xRange[0]; @@ -814,8 +919,7 @@ Dygraph.startPan = function(event, g, context) { var axis = g.axes_[i]; var yRange = g.yAxisRange(i); axis.dragValueRange = yRange[1] - yRange[0]; - var r = g.toDataCoords(null, context.dragStartY, i); - axis.draggingValue = r[1]; + axis.draggingValue = g.toDataYCoord(context.dragStartY, i); if (axis.valueWindow || axis.valueRange) context.is2DPan = true; } @@ -824,6 +928,13 @@ Dygraph.startPan = function(event, g, context) { context.draggingDate = (context.dragStartX / g.width_) * context.dateRange + xRange[0]; }; +// Called in response to an interaction model operation that +// responds to an event that pans the view. +// +// It's used in the default callback for "mousemove" operations. +// Custom interaction model builders can use it to provide the default +// panning behavior. +// Dygraph.movePan = function(event, g, context) { context.dragEndX = g.dragGetX_(event, context); context.dragEndY = g.dragGetY_(event, context); @@ -842,11 +953,18 @@ Dygraph.movePan = function(event, g, context) { // y-axis scaling is automatic unless this is a full 2D pan. if (context.is2DPan) { // Adjust each axis appropriately. + // NOTE(konigsberg): I don't think this computation for y_frac is correct. + // I think it doesn't take into account the display of the x axis. + // See, when I tested this with console.log(y_frac), and move the mouse + // cursor to the botom, the largest y_frac was 0.94, and not 1.0. That + // could also explain why panning tends to start with a small jumpy shift. var y_frac = context.dragEndY / g.height_; + for (var i = 0; i < g.axes_.length; i++) { var axis = g.axes_[i]; var maxValue = axis.draggingValue + y_frac * axis.dragValueRange; var minValue = maxValue - axis.dragValueRange; + console.log(axis.draggingValue, axis.dragValueRange, minValue, maxValue, y_frac); axis.valueWindow = [ minValue, maxValue ]; } } @@ -854,6 +972,13 @@ Dygraph.movePan = function(event, g, context) { g.drawGraph_(); } +// Called in response to an interaction model operation that +// responds to an event that ends panning. +// +// It's used in the default callback for "mouseup" operations. +// Custom interaction model builders can use it to provide the default +// panning behavior. +// Dygraph.endPan = function(event, g, context) { context.isPanning = false; context.is2DPan = false; @@ -862,10 +987,24 @@ Dygraph.endPan = function(event, g, context) { context.valueRange = null; } +// Called in response to an interaction model operation that +// responds to an event that starts zooming. +// +// It's used in the default callback for "mousedown" operations. +// Custom interaction model builders can use it to provide the default +// zooming behavior. +// Dygraph.startZoom = function(event, g, context) { context.isZooming = true; } +// Called in response to an interaction model operation that +// responds to an event that defines zoom boundaries. +// +// It's used in the default callback for "mousemove" operations. +// Custom interaction model builders can use it to provide the default +// zooming behavior. +// Dygraph.moveZoom = function(event, g, context) { context.dragEndX = g.dragGetX_(event, context); context.dragEndY = g.dragGetY_(event, context); @@ -891,6 +1030,14 @@ Dygraph.moveZoom = function(event, g, context) { context.prevDragDirection = context.dragDirection; } +// Called in response to an interaction model operation that +// responds to an event that performs a zoom based on previously defined +// bounds.. +// +// It's used in the default callback for "mouseup" operations. +// Custom interaction model builders can use it to provide the default +// zooming behavior. +// Dygraph.endZoom = function(event, g, context) { context.isZooming = false; context.dragEndX = g.dragGetX_(event, context); @@ -941,51 +1088,56 @@ Dygraph.endZoom = function(event, g, context) { context.dragStartY = null; } -// Track the beginning of drag events -Dygraph.prototype.defaultMouseDownFunction = function(event, g, context) { - context.initializeMouseDown(event, g, context); +Dygraph.defaultInteractionModel = { + // Track the beginning of drag events + mousedown: function(event, g, context) { + context.initializeMouseDown(event, g, context); - if (event.altKey || event.shiftKey) { - Dygraph.startPan(event, g, context); - } else { - Dygraph.startZoom(event, g, context); - } -}; + if (event.altKey || event.shiftKey) { + Dygraph.startPan(event, g, context); + } else { + Dygraph.startZoom(event, g, context); + } + }, -// Draw zoom rectangles when the mouse is down and the user moves around -Dygraph.prototype.defaultMouseMoveFunction = function(event, g, context) { - if (context.isZooming) { - Dygraph.moveZoom(event, g, context); - } else if (context.isPanning) { - Dygraph.movePan(event, g, context); - } -}; + // Draw zoom rectangles when the mouse is down and the user moves around + mousemove: function(event, g, context) { + if (context.isZooming) { + Dygraph.moveZoom(event, g, context); + } else if (context.isPanning) { + Dygraph.movePan(event, g, context); + } + }, -Dygraph.prototype.defaultMouseUpFunction = function(event, g, context) { - if (context.isZooming) { - Dygraph.endZoom(event, g, context); - } else if (context.isPanning) { - Dygraph.endPan(event, g, context); - } -}; + mouseup: function(event, g, context) { + if (context.isZooming) { + Dygraph.endZoom(event, g, context); + } else if (context.isPanning) { + Dygraph.endPan(event, g, context); + } + }, -Dygraph.prototype.defaultMouseOutFunction = function(event, g, context) { // Temporarily cancel the dragging event when the mouse leaves the graph - if (context.isZooming) { - context.dragEndX = null; - context.dragEndY = null; - } -}; + mouseout: function(event, g, context) { + if (context.isZooming) { + context.dragEndX = null; + context.dragEndY = null; + } + }, -// Double-clicking zooms back out -Dygraph.prototype.defaultMouseDoubleClickFunction = function(event, g, context) { // Disable zooming out if panning. - if (event.altKey || event.shiftKey) { - return; + dblclick: function(event, g, context) { + if (event.altKey || event.shiftKey) { + return; + } + // TODO(konigsberg): replace g.doUnzoom()_ with something that is + // friendlier to public use. + g.doUnzoom_(); } - g.doUnzoom_(); }; +Dygraph.DEFAULT_ATTRS.interactionModel = Dygraph.defaultInteractionModel; + /** * Set up all the mouse handlers needed to capture dragging behavior for zoom * events. @@ -994,43 +1146,43 @@ Dygraph.prototype.defaultMouseDoubleClickFunction = function(event, g, context) Dygraph.prototype.createDragInterface_ = function() { var context = { // Tracks whether the mouse is down right now - isZooming : false, - isPanning : false, // is this drag part of a pan? - is2DPan : false, // if so, is that pan 1- or 2-dimensional? - dragStartX : null, - dragStartY : null, - dragEndX : null, - dragEndY : null, - dragDirection : null, - prevEndX : null, - prevEndY : null, - prevDragDirection : null, + isZooming: false, + isPanning: false, // is this drag part of a pan? + is2DPan: false, // if so, is that pan 1- or 2-dimensional? + dragStartX: null, + dragStartY: null, + dragEndX: null, + dragEndY: null, + dragDirection: null, + prevEndX: null, + prevEndY: null, + prevDragDirection: null, // TODO(danvk): update this comment // draggingDate and draggingValue represent the [date,value] point on the // graph at which the mouse was pressed. As the mouse moves while panning, // the viewport must pan so that the mouse position points to // [draggingDate, draggingValue] - draggingDate : null, + draggingDate: null, // TODO(danvk): update this comment // The range in second/value units that the viewport encompasses during a // panning operation. - dateRange : null, + dateRange: null, // Utility function to convert page-wide coordinates to canvas coords - px : 0, - py : 0, + px: 0, + py: 0, - initializeMouseDown : function(event, g, context) { - // prevents mouse drags from selecting page text. + initializeMouseDown: function(event, g, context) { + // prevents mouse drags from selecting page text. if (event.preventDefault) { event.preventDefault(); // Firefox, Chrome, etc. } else { event.returnValue = false; // IE - event.cancelBubble = true; + event.cancelBubble = true; } - + context.px = Dygraph.findPosX(g.canvas_); context.py = Dygraph.findPosY(g.canvas_); context.dragStartX = g.dragGetX_(event, context); @@ -1038,30 +1190,24 @@ Dygraph.prototype.createDragInterface_ = function() { } }; - // Defines default behavior if there are no event handlers. - var handlers = this.user_attrs_.interactionModel || { - 'mousedown' : this.defaultMouseDownFunction, - 'mousemove' : this.defaultMouseMoveFunction, - 'mouseup' : this.defaultMouseUpFunction, - 'mouseout' : this.defaultMouseOutFunction, - 'dblclick' : this.defaultMouseDoubleClickFunction - }; + var interactionModel = this.attr_("interactionModel"); + + // Self is the graph. + var self = this; - // Function that binds g and context to the handler. - var bindHandler = function(handler, g) { + // Function that binds the graph and context to the handler. + var bindHandler = function(handler) { return function(event) { - handler(event, g, context); + handler(event, self, context); }; }; - for (var eventName in handlers) { + for (var eventName in interactionModel) { + if (!interactionModel.hasOwnProperty(eventName)) continue; Dygraph.addEvent(this.mouseEventElement_, eventName, - bindHandler(handlers[eventName], this)); + bindHandler(interactionModel[eventName])); } - // Self is the graph. - var self = this; - // 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) { @@ -1086,7 +1232,7 @@ Dygraph.prototype.createDragInterface_ = function() { /** * Draw a gray zoom rectangle over the desired area of the canvas. Also clears * up any previous zoom rectangles that were drawn. This could be optimized to - * avoid extra redrawing, but it's tricky to avoid contexts with the status + * avoid extra redrawing, but it's tricky to avoid interactions with the status * dots. * * @param {Number} direction the direction of the zoom rectangle. Acceptable @@ -1148,10 +1294,8 @@ Dygraph.prototype.drawZoomRect_ = function(direction, startX, endX, startY, endY Dygraph.prototype.doZoomX_ = function(lowX, highX) { // Find the earliest and latest dates contained in this canvasx range. // Convert the call to date ranges of the raw data. - var r = this.toDataCoords(lowX, null); - var minDate = r[0]; - r = this.toDataCoords(highX, null); - var maxDate = r[0]; + var minDate = this.toDataXCoord(lowX); + var maxDate = this.toDataXCoord(highX); this.doZoomXDates_(minDate, maxDate); }; @@ -1187,10 +1331,10 @@ Dygraph.prototype.doZoomY_ = function(lowY, highY) { // coordinates increase as you go up the screen. var valueRanges = []; for (var i = 0; i < this.axes_.length; i++) { - var hi = this.toDataCoords(null, lowY, i); - var low = this.toDataCoords(null, highY, i); - this.axes_[i].valueWindow = [low[1], hi[1]]; - valueRanges.push([low[1], hi[1]]); + var hi = this.toDataYCoord(lowY, i); + var low = this.toDataYCoord(highY, i); + this.axes_[i].valueWindow = [low, hi]; + valueRanges.push([low, hi]); } this.drawGraph_(); @@ -1507,7 +1651,9 @@ Dygraph.hmsString_ = function(date) { * @private */ Dygraph.dateAxisFormatter = function(date, granularity) { - if (granularity >= Dygraph.MONTHLY) { + if (granularity >= Dygraph.DECADAL) { + return date.strftime('%Y'); + } else if (granularity >= Dygraph.MONTHLY) { return date.strftime('%b %y'); } else { var frac = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() + date.getMilliseconds(); @@ -1609,7 +1755,8 @@ Dygraph.QUARTERLY = 16; Dygraph.BIANNUAL = 17; Dygraph.ANNUAL = 18; Dygraph.DECADAL = 19; -Dygraph.NUM_GRANULARITIES = 20; +Dygraph.CENTENNIAL = 20; +Dygraph.NUM_GRANULARITIES = 21; Dygraph.SHORT_SPACINGS = []; Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY] = 1000 * 1; @@ -1645,6 +1792,7 @@ Dygraph.prototype.NumXTicks = function(start_time, end_time, granularity) { if (granularity == Dygraph.BIANNUAL) num_months = 2; if (granularity == Dygraph.ANNUAL) num_months = 1; if (granularity == Dygraph.DECADAL) { num_months = 1; year_mod = 10; } + if (granularity == Dygraph.CENTENNIAL) { num_months = 1; year_mod = 100; } var msInYear = 365.2524 * 24 * 3600 * 1000; var num_years = 1.0 * (end_time - start_time) / msInYear; @@ -1717,6 +1865,11 @@ Dygraph.prototype.GetXAxis = function(start_time, end_time, granularity) { } else if (granularity == Dygraph.DECADAL) { months = [ 0 ]; year_mod = 10; + } else if (granularity == Dygraph.CENTENNIAL) { + months = [ 0 ]; + year_mod = 100; + } else { + this.warn("Span of dates is too long"); } var start_year = new Date(start_time).getFullYear(); @@ -1763,6 +1916,8 @@ Dygraph.dateTicker = function(startDate, endDate, self) { /** * Add ticks when the x axis has numbers on it (instead of dates) + * TODO(konigsberg): Update comment. + * * @param {Number} startDate Start of the date window (millis since epoch) * @param {Number} endDate End of the date window (millis since epoch) * @param self @@ -1782,43 +1937,61 @@ Dygraph.numericTicks = function(minV, maxV, self, axis_props, vals) { ticks.push({v: vals[i]}); } } else { - // Basic idea: - // 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. - // TODO(danvk): version that works on a log scale. - if (attr("labelsKMG2")) { - var mults = [1, 2, 4, 8]; + if (self.attr_("logscale")) { + // As opposed to the other ways for computing ticks, we're just going + // for nearby values. There's no reasonable way to scale the values + // (unless we want to show strings like "log(" + x + ")") in which case + // x can be integer values. + + // so compute height / pixelsPerTick and move on. + var pixelsPerTick = attr('pixelsPerYLabel'); + var nTicks = Math.floor(self.height_ / pixelsPerTick); + var vv = minV; + + // Construct the set of ticks. + for (var i = 0; i < nTicks; i++) { + ticks.push( {v: vv} ); + vv = vv * Dygraph.LOG_SCALE; + } } 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 = attr('pixelsPerYLabel'); - for (var i = -10; i < 50; i++) { + // Basic idea: + // 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. + // TODO(danvk): version that works on a log scale. if (attr("labelsKMG2")) { - var base_scale = Math.pow(16, i); + var mults = [1, 2, 4, 8]; } else { - var base_scale = Math.pow(10, i); + var mults = [1, 2, 5]; } - for (var j = 0; j < mults.length; j++) { - scale = base_scale * mults[j]; - low_val = Math.floor(minV / scale) * scale; - high_val = Math.ceil(maxV / scale) * scale; - nTicks = Math.abs(high_val - low_val) / scale; - var spacing = self.height_ / nTicks; - // wish I could break out of both loops at once... + var scale, low_val, high_val, nTicks; + // TODO(danvk): make it possible to set this for x- and y-axes independently. + var pixelsPerTick = attr('pixelsPerYLabel'); + for (var i = -10; i < 50; i++) { + if (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; + high_val = Math.ceil(maxV / scale) * scale; + nTicks = Math.abs(high_val - low_val) / scale; + var spacing = self.height_ / nTicks; + // wish I could break out of both loops at once... + if (spacing > pixelsPerTick) break; + } if (spacing > pixelsPerTick) break; } - if (spacing > pixelsPerTick) break; - } - // Construct the set of ticks. - // Allow reverse y-axis if it's explicitly requested. - if (low_val > high_val) scale *= -1; - for (var i = 0; i < nTicks; i++) { - var tickV = low_val + i * scale; - ticks.push( {v: tickV} ); + // Construct the set of ticks. + // Allow reverse y-axis if it's explicitly requested. + if (low_val > high_val) scale *= -1; + for (var i = 0; i < nTicks; i++) { + var tickV = low_val + i * scale; + ticks.push( {v: tickV} ); + } } } @@ -1931,7 +2104,6 @@ Dygraph.prototype.predraw_ = function() { }; /** -======= * Update the graph with new data. This method is called when the viewing area * has changed. If the underlying data or options have changed, predraw_ will * be called before drawGraph_ is called. @@ -2187,6 +2359,7 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { // Compute extreme values, a span and tick marks for each axis. for (var i = 0; i < this.axes_.length; i++) { + var isLogScale = this.attr_("logscale"); 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 @@ -2211,18 +2384,26 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { var span = maxY - minY; // special case: if we have no sense of scale, use +/-10% of the sole value. if (span == 0) { span = maxY; } - var maxAxisY = maxY + 0.1 * span; - var minAxisY = minY - 0.1 * span; - // Try to include zero and make it minAxisY (or maxAxisY) if it makes sense. - if (!this.attr_("avoidMinZero")) { - if (minAxisY < 0 && minY >= 0) minAxisY = 0; - if (maxAxisY > 0 && maxY <= 0) maxAxisY = 0; - } + var maxAxisY; + var minAxisY; + if (isLogScale) { + var maxAxisY = maxY + 0.1 * span; + var minAxisY = minY; + } else { + var maxAxisY = maxY + 0.1 * span; + var minAxisY = minY - 0.1 * span; - if (this.attr_("includeZero")) { - if (maxY < 0) maxAxisY = 0; - if (minY > 0) minAxisY = 0; + // Try to include zero and make it minAxisY (or maxAxisY) if it makes sense. + if (!this.attr_("avoidMinZero")) { + if (minAxisY < 0 && minY >= 0) minAxisY = 0; + if (maxAxisY > 0 && maxY <= 0) maxAxisY = 0; + } + + if (this.attr_("includeZero")) { + if (maxY < 0) maxAxisY = 0; + if (minY > 0) minAxisY = 0; + } } axis.computedValueRange = [minAxisY, maxAxisY];