Dygraph.DEFAULT_ATTRS = {
highlightCircleSize: 3,
highlightSeriesOpts: null,
- highlightSeriesBackgroundFade: 0,
- highlightSeriesAnimated: false,
+ highlightSeriesBackgroundAlpha: 0.5,
labelsDivWidth: 250,
labelsDivStyles: {
var sources = [];
sources.push(this.attrs_);
- if (this.user_attrs_) sources.push(this.user_attrs_);
- if (this.user_attrs_ && seriesName) {
- if (this.user_attrs_.hasOwnProperty(seriesName)) {
- sources.push(this.user_attrs_[seriesName]);
- }
- if (seriesName === this.highlightSet_ &&
- this.user_attrs_.hasOwnProperty('highlightSeriesOpts')) {
- sources.push(this.user_attrs_['highlightSeriesOpts']);
+ if (this.user_attrs_) {
+ sources.push(this.user_attrs_);
+ if (seriesName) {
+ if (this.user_attrs_.hasOwnProperty(seriesName)) {
+ sources.push(this.user_attrs_[seriesName]);
+ }
+ if (seriesName === this.highlightSet_ &&
+ this.user_attrs_.hasOwnProperty('highlightSeriesOpts')) {
+ sources.push(this.user_attrs_['highlightSeriesOpts']);
+ }
}
}
div.className = "dygraph-legend";
for (var name in messagestyle) {
if (messagestyle.hasOwnProperty(name)) {
- div.style[name] = messagestyle[name];
+ try {
+ div.style[name] = messagestyle[name];
+ } catch (e) {
+ this.warn("You are using unsupported css properties for your browser in labelsDivStyles");
+ }
}
}
this.graphDiv.appendChild(div);
var l = points.length;
for (var i = 0; i < l; i++) {
var point = points[i];
- if (point === null) continue;
+ if (!Dygraph.isValidPoint(point)) continue;
var dist = Math.abs(point.canvasx - domX);
- if (minDistX !== null && dist >= minDistX) continue;
- minDistX = dist;
- idx = i;
+ if (minDistX === null || dist < minDistX) {
+ minDistX = dist;
+ idx = i;
+ }
}
return this.idxToRow_(idx);
};
/**
- * Given canvas X,Y coordinates, find the closest point
+ * Given canvas X,Y coordinates, find the closest point.
+ *
+ * This finds the individual data point across all visible series
+ * that's closest to the supplied DOM coordinates using the standard
+ * Euclidean X,Y distance.
+ *
* @param {Number} domX graph-relative DOM X coordinate
* @param {Number} domY graph-relative DOM Y coordinate
* Returns: {row, seriesName, point}
var len = this.layout_.setPointsLengths[setIdx];
for (var i = 0; i < len; ++i) {
var point = points[first + i];
- if (point === null) continue;
+ if (!Dygraph.isValidPoint(point)) continue;
dx = point.canvasx - domX;
dy = point.canvasy - domY;
dist = dx * dx + dy * dy;
- if (minDist !== null && dist >= minDist) continue;
- minDist = dist;
- closestPoint = point;
- closestSeries = setIdx;
- idx = i;
+ if (minDist === null || dist < minDist) {
+ minDist = dist;
+ closestPoint = point;
+ closestSeries = setIdx;
+ idx = i;
+ }
}
}
var name = this.layout_.setNames[closestSeries];
/**
* Given canvas X,Y coordinates, find the touched area in a stacked graph.
+ *
+ * This first finds the X data point closest to the supplied DOM X coordinate,
+ * then finds the series which puts the Y coordinate on top of its filled area,
+ * using linear interpolation between adjacent point pairs.
+ *
* @param {Number} domX graph-relative DOM X coordinate
* @param {Number} domY graph-relative DOM Y coordinate
* Returns: {row, seriesName, point}
var len = this.layout_.setPointsLengths[setIdx];
if (row >= len) continue;
var p1 = points[first + row];
+ if (!Dygraph.isValidPoint(p1)) continue;
var py = p1.canvasy;
if (domX > p1.canvasx && row + 1 < len) {
// interpolate series Y value using next point
var p2 = points[first + row + 1];
- var dx = p2.canvasx - p1.canvasx;
- if (dx > 0) {
- var r = (domX - p1.canvasx) / dx;
- py += r * (p2.canvasy - p1.canvasy);
+ if (Dygraph.isValidPoint(p2)) {
+ var dx = p2.canvasx - p1.canvasx;
+ if (dx > 0) {
+ var r = (domX - p1.canvasx) / dx;
+ py += r * (p2.canvasy - p1.canvasy);
+ }
}
} else if (domX < p1.canvasx && row > 0) {
// interpolate series Y value using previous point
var p0 = points[first + row - 1];
- var dx = p1.canvasx - p0.canvasx;
- if (dx > 0) {
- var r = (p1.canvasx - domX) / dx;
- py += r * (p0.canvasy - p1.canvasy);
+ if (Dygraph.isValidPoint(p0)) {
+ var dx = p1.canvasx - p0.canvasx;
+ if (dx > 0) {
+ var r = (p1.canvasx - domX) / dx;
+ py += r * (p0.canvasy - p1.canvasy);
+ }
}
}
// Stop if the point (domX, py) is above this series' upper edge
- if (setIdx > 0 && py >= domY) break;
- closestPoint = p1;
- closestSeries = setIdx;
+ if (setIdx == 0 || py < domY) {
+ closestPoint = p1;
+ closestSeries = setIdx;
+ }
}
var name = this.layout_.setNames[closestSeries];
return {
var canvasx = canvasCoords[0];
var canvasy = canvasCoords[1];
- var mouseoverCallback = this.attr_("mouseoverCallback");
- if (mouseoverCallback) {
- var highlightRow = this.idxToRow_(idx);
- var ret = mouseoverCallback(this, event);
- if (ret) return;
- }
-
var highlightSeriesOpts = this.attr_("highlightSeriesOpts");
var selectionChanged = false;
if (highlightSeriesOpts) {
Dygraph.prototype.animateSelection_ = function(direction) {
var totalSteps = 10;
var millis = 30;
- if (this.fadeLevel === undefined) {
- this.fadeLevel = 0;
- this.animateId = 0;
- }
+ if (this.fadeLevel === undefined) this.fadeLevel = 0;
+ if (this.animateId === undefined) this.animateId = 0;
var start = this.fadeLevel;
var steps = direction < 0 ? start : totalSteps - start;
if (steps <= 0) {
var thisId = ++this.animateId;
var that = this;
- Dygraph.repeatAndCleanup(function(n) {
- // ignore simultaneous animations
- if (that.animateId != thisId) return;
-
- that.fadeLevel += direction;
- if (that.fadeLevel === 0) {
- that.clearSelection();
- } else {
- that.updateSelection_(that.fadeLevel / totalSteps);
- }
- },
- steps, millis, function() {});
+ Dygraph.repeatAndCleanup(
+ function(n) {
+ // ignore simultaneous animations
+ if (that.animateId != thisId) return;
+
+ that.fadeLevel += direction;
+ if (that.fadeLevel === 0) {
+ that.clearSelection();
+ } else {
+ that.updateSelection_(that.fadeLevel / totalSteps);
+ }
+ },
+ steps, millis, function() {});
};
/**
var ctx = this.canvas_ctx_;
if (this.attr_('highlightSeriesOpts')) {
ctx.clearRect(0, 0, this.width_, this.height_);
- var alpha = this.attr_('highlightSeriesBackgroundFade');
+ var alpha = 1.0 - this.attr_('highlightSeriesBackgroundAlpha');
if (alpha) {
- if (this.attr_('highlightSeriesAnimate')) {
+ // Activating background fade includes an animation effect for a gradual
+ // fade. TODO(klausw): make this independently configurable if it causes
+ // issues? Use a shared preference to control animations?
+ var animateBackgroundFade = true;
+ if (animateBackgroundFade) {
if (opt_animFraction === undefined) {
// start a new animation
this.animateSelection_(1);
ctx.fillRect(0, 0, this.width_, this.height_);
}
var setIdx = this.datasetIndexFromSetName_(this.highlightSet_);
- var underlay = this.attr_('highlightUnderlay');
- if (underlay) underlay(this, ctx, setIdx);
this.plotter_._drawLine(ctx, setIdx);
} else if (this.previousVerticalX_ >= 0) {
// Determine the maximum highlight circle size.
if (!Dygraph.isOK(pt.canvasy)) continue;
var circleSize = this.attr_('highlightCircleSize', pt.name);
- ctx.beginPath();
- ctx.fillStyle = this.plotter_.colors[pt.name];
- ctx.arc(canvasx, pt.canvasy, circleSize, 0, 2 * Math.PI, false);
- ctx.fill();
+ var callback = this.attr_("drawHighlightPointCallback", pt.name);
+ var color = this.plotter_.colors[pt.name];
+ if (!callback) {
+ callback = Dygraph.Circles.DEFAULT;
+ }
+ ctx.lineWidth = this.attr_('strokeWidth', pt.name);
+ ctx.strokeStyle = color;
+ ctx.fillStyle = color;
+ callback(this.g, pt.name, ctx, canvasx, pt.canvasy,
+ color, circleSize);
}
ctx.restore();