-
-/**
- * Add ticks to the x-axis based on a date range.
- * @param {Number} startDate Start of the date window (millis since epoch)
- * @param {Number} endDate End of the date window (millis since epoch)
- * @return {Array.<Object>} Array of {label, value} tuples.
- * @public
- */
-Dygraph.dateTicker = function(startDate, endDate, self) {
- var chosen = -1;
- for (var i = 0; i < Dygraph.NUM_GRANULARITIES; i++) {
- var num_ticks = self.NumXTicks(startDate, endDate, i);
- if (self.width_ / num_ticks >= self.attr_('pixelsPerXLabel')) {
- chosen = i;
- break;
- }
- }
-
- if (chosen >= 0) {
- return self.GetXAxis(startDate, endDate, chosen);
- } else {
- // TODO(danvk): signal error.
- }
-};
-
-// This is a list of human-friendly values at which to show tick marks on a log
-// scale. It is k * 10^n, where k=1..9 and n=-39..+39, so:
-// ..., 1, 2, 3, 4, 5, ..., 9, 10, 20, 30, ..., 90, 100, 200, 300, ...
-// NOTE: this assumes that Dygraph.LOG_SCALE = 10.
-Dygraph.PREFERRED_LOG_TICK_VALUES = function() {
- var vals = [];
- for (var power = -39; power <= 39; power++) {
- var range = Math.pow(10, power);
- for (var mult = 1; mult <= 9; mult++) {
- var val = range * mult;
- vals.push(val);
- }
- }
- return vals;
-}();
-
-// val is the value to search for
-// arry is the value over which to search
-// if abs > 0, find the lowest entry greater than val
-// if abs < 0, find the highest entry less than val
-// if abs == 0, find the entry that equals val.
-// Currently does not work when val is outside the range of arry's values.
-Dygraph.binarySearch = function(val, arry, abs, low, high) {
- if (low == null || high == null) {
- low = 0;
- high = arry.length - 1;
- }
- if (low > high) {
- return -1;
- }
- if (abs == null) {
- abs = 0;
- }
- var validIndex = function(idx) {
- return idx >= 0 && idx < arry.length;
- }
- var mid = parseInt((low + high) / 2);
- var element = arry[mid];
- if (element == val) {
- return mid;
- }
- if (element > val) {
- if (abs > 0) {
- // Accept if element > val, but also if prior element < val.
- var idx = mid - 1;
- if (validIndex(idx) && arry[idx] < val) {
- return mid;
- }
- }
- return Dygraph.binarySearch(val, arry, abs, low, mid - 1);
- }
- if (element < val) {
- if (abs < 0) {
- // Accept if element < val, but also if prior element > val.
- var idx = mid + 1;
- if (validIndex(idx) && arry[idx] > val) {
- return mid;
- }
- }
- return Dygraph.binarySearch(val, arry, abs, mid + 1, high);
- }
-};
-
-/**
- * Determine the number of significant figures in a Number up to the specified
- * precision. Note that there is no way to determine if a trailing '0' is
- * significant or not, so by convention we return 1 for all of the following
- * inputs: 1, 1.0, 1.00, 1.000 etc.
- * @param {Number} x The input value.
- * @param {Number} opt_maxPrecision Optional maximum precision to consider.
- * Default and maximum allowed value is 13.
- * @return {Number} The number of significant figures which is >= 1.
- */
-Dygraph.significantFigures = function(x, opt_maxPrecision) {
- var precision = Math.max(opt_maxPrecision || 13, 13);
-
- // Convert the number to its exponential notation form and work backwards,
- // ignoring the 'e+xx' bit. This may seem like a hack, but doing a loop and
- // dividing by 10 leads to roundoff errors. By using toExponential(), we let
- // the JavaScript interpreter handle the low level bits of the Number for us.
- var s = x.toExponential(precision);
- var ePos = s.lastIndexOf('e'); // -1 case handled by return below.
-
- for (var i = ePos - 1; i >= 0; i--) {
- if (s[i] == '.') {
- // Got to the decimal place. We'll call this 1 digit of precision because
- // we can't know for sure how many trailing 0s are significant.
- return 1;
- } else if (s[i] != '0') {
- // Found the first non-zero digit. Return the number of characters
- // except for the '.'.
- return i; // This is i - 1 + 1 (-1 is for '.', +1 is for 0 based index).
- }
- }
-
- // Occurs if toExponential() doesn't return a string containing 'e', which
- // should never happen.
- return 1;
-};
-
-/**
- * Add ticks when the x axis has numbers on it (instead of dates)
- * TODO(konigsberg): Update comment.
- *
- * @param {Number} minV minimum value
- * @param {Number} maxV maximum value
- * @param self
- * @param {function} attribute accessor function.
- * @return {Array.<Object>} Array of {label, value} tuples.
- * @public
- */
-Dygraph.numericTicks = function(minV, maxV, self, axis_props, vals) {
- var attr = function(k) {
- if (axis_props && axis_props.hasOwnProperty(k)) return axis_props[k];
- return self.attr_(k);
- };
-
- var ticks = [];
- if (vals) {
- for (var i = 0; i < vals.length; i++) {
- ticks.push({v: vals[i]});
- }
- } else {
- if (axis_props && attr("logscale")) {
- var pixelsPerTick = attr('pixelsPerYLabel');
- // NOTE(konigsberg): Dan, should self.height_ be self.plotter_.area.h?
- var nTicks = Math.floor(self.height_ / pixelsPerTick);
- var minIdx = Dygraph.binarySearch(minV, Dygraph.PREFERRED_LOG_TICK_VALUES, 1);
- var maxIdx = Dygraph.binarySearch(maxV, Dygraph.PREFERRED_LOG_TICK_VALUES, -1);
- if (minIdx == -1) {
- minIdx = 0;
- }
- if (maxIdx == -1) {
- maxIdx = Dygraph.PREFERRED_LOG_TICK_VALUES.length - 1;
- }
- // Count the number of tick values would appear, if we can get at least
- // nTicks / 4 accept them.
- var lastDisplayed = null;
- if (maxIdx - minIdx >= nTicks / 4) {
- var axisId = axis_props.yAxisId;
- for (var idx = maxIdx; idx >= minIdx; idx--) {
- var tickValue = Dygraph.PREFERRED_LOG_TICK_VALUES[idx];
- var domCoord = axis_props.g.toDomYCoord(tickValue, axisId);
- var tick = { v: tickValue };
- if (lastDisplayed == null) {
- lastDisplayed = {
- tickValue : tickValue,
- domCoord : domCoord
- };
- } else {
- if (domCoord - lastDisplayed.domCoord >= pixelsPerTick) {
- lastDisplayed = {
- tickValue : tickValue,
- domCoord : domCoord
- };
- } else {
- tick.label = "";
- }
- }
- ticks.push(tick);
- }
- // Since we went in backwards order.
- ticks.reverse();
- }
- }
-
- // ticks.length won't be 0 if the log scale function finds values to insert.
- if (ticks.length == 0) {
- // 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];
- } 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++) {
- 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;
- }
-
- // 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} );
- }
- }
- }
-
- // Add formatted labels to the ticks.
- var k;
- var k_labels = [];
- if (attr("labelsKMB")) {
- k = 1000;
- k_labels = [ "K", "M", "B", "T" ];
- }
- if (attr("labelsKMG2")) {
- if (k) self.warn("Setting both labelsKMB and labelsKMG2. Pick one!");
- k = 1024;
- k_labels = [ "k", "M", "G", "T" ];
- }
- var formatter = attr('yAxisLabelFormatter') ?
- attr('yAxisLabelFormatter') : attr('yValueFormatter');
-
- // Determine the number of decimal places needed for the labels below by
- // taking the maximum number of significant figures for any label. We must
- // take the max because we can't tell if trailing 0s are significant.
- var numDigits = 0;
- for (var i = 0; i < ticks.length; i++) {
- numDigits = Math.max(Dygraph.significantFigures(ticks[i].v), numDigits);
- }
-
- // Add labels to the ticks.
- for (var i = 0; i < ticks.length; i++) {
- if (ticks[i].label !== undefined) continue; // Use current label.
- var tickV = ticks[i].v;
- var absTickV = Math.abs(tickV);
- var label = (formatter !== undefined) ?
- formatter(tickV, numDigits) : tickV.toPrecision(numDigits);
- if (k_labels.length > 0) {
- // Round up to an appropriate unit.
- var n = k*k*k*k;
- for (var j = 3; j >= 0; j--, n /= k) {
- if (absTickV >= n) {
- label = formatter(tickV / n, numDigits) + k_labels[j];
- break;
- }
- }
- }
- ticks[i].label = label;