// TODO(danvk): move defaults from createStatusMessage_ here.
},
labelsSeparateLines: false,
+ labelsShowZeroValues: true,
labelsKMB: false,
labelsKMG2: false,
showLabelsOnHighlight: true,
connectSeparatedPoints: false,
stackedGraph: false,
- hideOverlayOnMouseOut: true
+ hideOverlayOnMouseOut: true,
+
+ stepPlot: false
};
// Various logging levels.
// The div might have been specified as percent of the current window size,
// convert that to an appropriate number of pixels.
if (div.style.width.indexOf("%") == div.style.width.length - 1) {
- // Minus ten pixels keeps scrollbars from showing up for a 100% width div.
- this.width_ = (this.width_ * self.innerWidth / 100) - 10;
+ this.width_ = div.offsetWidth;
}
if (div.style.height.indexOf("%") == div.style.height.length - 1) {
- this.height_ = (this.height_ * self.innerHeight / 100) - 10;
+ this.height_ = div.offsetHeight;
+ }
+
+ if (this.width_ == 0) {
+ this.error("dygraph has zero width. Please specify a width in pixels.");
+ }
+ if (this.height_ == 0) {
+ this.error("dygraph has zero height. Please specify a height in pixels.");
}
// TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_.
this.canvas_.height = this.height_;
this.canvas_.style.width = this.width_ + "px"; // for IE
this.canvas_.style.height = this.height_ + "px"; // for IE
- this.graphDiv.appendChild(this.canvas_);
// ... and for static parts of the chart.
this.hidden_ = this.createPlotKitCanvas_(this.canvas_);
+ // The interactive parts of the graph are drawn on top of the chart.
+ this.graphDiv.appendChild(this.hidden_);
+ this.graphDiv.appendChild(this.canvas_);
+ this.mouseEventElement_ = this.canvas_;
+
// Make sure we don't overdraw.
Dygraph.clipCanvas_(this.hidden_, this.clippingArea_);
Dygraph.clipCanvas_(this.canvas_, this.clippingArea_);
var dygraph = this;
- Dygraph.addEvent(this.hidden_, 'mousemove', function(e) {
+ Dygraph.addEvent(this.mouseEventElement_, 'mousemove', function(e) {
dygraph.mouseMove_(e);
});
- Dygraph.addEvent(this.hidden_, 'mouseout', function(e) {
+ Dygraph.addEvent(this.mouseEventElement_, 'mouseout', function(e) {
dygraph.mouseOut_(e);
});
h.height = this.height_;
h.style.width = this.width_ + "px"; // for IE
h.style.height = this.height_ + "px"; // for IE
- this.graphDiv.appendChild(h);
return h;
};
* been specified.
* @private
*/
-Dygraph.prototype.createStatusMessage_ = function(){
+Dygraph.prototype.createStatusMessage_ = function() {
+ var userLabelsDiv = this.user_attrs_["labelsDiv"];
+ if (userLabelsDiv && null != userLabelsDiv
+ && (typeof(userLabelsDiv) == "string" || userLabelsDiv instanceof String)) {
+ this.user_attrs_["labelsDiv"] = document.getElementById(userLabelsDiv);
+ }
if (!this.attr_("labelsDiv")) {
var divWidth = this.attr_('labelsDivWidth');
var messagestyle = {
var getY = function(e) { return Dygraph.pageX(e) - py };
// Draw zoom rectangles when the mouse is down and the user moves around
- Dygraph.addEvent(this.hidden_, 'mousemove', function(event) {
+ Dygraph.addEvent(this.mouseEventElement_, 'mousemove', function(event) {
if (isZooming) {
dragEndX = getX(event);
dragEndY = getY(event);
});
// Track the beginning of drag events
- Dygraph.addEvent(this.hidden_, 'mousedown', function(event) {
+ Dygraph.addEvent(this.mouseEventElement_, 'mousedown', function(event) {
px = Dygraph.findPosX(self.canvas_);
py = Dygraph.findPosY(self.canvas_);
dragStartX = getX(event);
});
// Temporarily cancel the dragging event when the mouse leaves the graph
- Dygraph.addEvent(this.hidden_, 'mouseout', function(event) {
+ Dygraph.addEvent(this.mouseEventElement_, 'mouseout', function(event) {
if (isZooming) {
dragEndX = null;
dragEndY = null;
// If the mouse is released on the canvas during a drag event, then it's a
// zoom. Only do the zoom if it's over a large enough area (>= 10 pixels)
- Dygraph.addEvent(this.hidden_, 'mouseup', function(event) {
+ Dygraph.addEvent(this.mouseEventElement_, 'mouseup', function(event) {
if (isZooming) {
isZooming = false;
dragEndX = getX(event);
});
// Double-clicking zooms back out
- Dygraph.addEvent(this.hidden_, 'dblclick', function(event) {
+ Dygraph.addEvent(this.mouseEventElement_, 'dblclick', function(event) {
if (self.dateWindow_ == null) return;
self.dateWindow_ = null;
self.drawGraph_(self.rawData_);
* @private
*/
Dygraph.prototype.mouseMove_ = function(event) {
- var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.hidden_);
+ var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.mouseEventElement_);
var points = this.layout_.points;
var lastx = -1;
// Extract the points we've selected
this.selPoints_ = [];
- var cumulative_sum = 0; // used only if we have a stackedGraph.
var l = points.length;
- var isStacked = this.attr_("stackedGraph");
if (!this.attr_("stackedGraph")) {
for (var i = 0; i < l; i++) {
if (points[i].xval == lastx) {
}
}
} else {
- // Stacked points need to be examined in reverse order.
+ // Need to 'unstack' points starting from the bottom
+ var cumulative_sum = 0;
for (var i = l - 1; i >= 0; i--) {
if (points[i].xval == lastx) {
- // Clone the point, since we need to 'unstack' it below.
- var p = {};
+ var p = {}; // Clone the point since we modify it
for (var k in points[i]) {
p[k] = points[i][k];
}
this.selPoints_.push(p);
}
}
+ this.selPoints_.reverse();
}
if (this.attr_("highlightCallback")) {
if (this.attr_('showLabelsOnHighlight')) {
// Set the status message to indicate the selected point(s)
for (var i = 0; i < this.selPoints_.length; i++) {
+ if (!this.attr_("labelsShowZeroValues") && this.selPoints_[i].yval == 0) continue;
if (!isOK(this.selPoints_[i].canvasy)) continue;
if (this.attr_("labelsSeparateLines")) {
replace += "<br/>";
var connectSeparatedPoints = this.attr_('connectSeparatedPoints');
- // For stacked series.
- var cumulative_y = [];
- var stacked_datasets = [];
+ // Loop over the fields (series). Go from the last to the first,
+ // because if they're stacked that's how we accumulate the values.
- // Loop over all fields in the dataset
- for (var i = 1; i < data[0].length; i++) {
+ var cumulative_y = []; // For stacked series.
+ var datasets = [];
+
+ // Loop over all fields and create datasets
+ for (var i = data[0].length - 1; i >= 1; i--) {
if (!this.visibility()[i - 1]) continue;
var series = [];
for (var j = 0; j < data.length; j++) {
- if (data[j][i] !== undefined || !connectSeparatedPoints) {
+ if (data[j][i] != null || !connectSeparatedPoints) {
var date = data[j][0];
series.push([date, data[j][i]]);
}
if (maxY === null || thisMaxY > maxY) maxY = thisMaxY;
if (bars) {
- var vals = [];
- for (var j=0; j<series.length; j++)
- vals[j] = [series[j][0],
- series[j][1][0], series[j][1][1], series[j][1][2]];
- this.layout_.addDataset(this.attr_("labels")[i], vals);
+ for (var j=0; j<series.length; j++) {
+ val = [series[j][0], series[j][1][0], series[j][1][1], series[j][1][2]];
+ series[j] = val;
+ }
} else if (this.attr_("stackedGraph")) {
- var vals = [];
var l = series.length;
var actual_y;
for (var j = 0; j < l; j++) {
- if (cumulative_y[series[j][0]] === undefined)
- cumulative_y[series[j][0]] = 0;
+ // If one data set has a NaN, let all subsequent stacked
+ // sets inherit the NaN -- only start at 0 for the first set.
+ var x = series[j][0];
+ if (cumulative_y[x] === undefined)
+ cumulative_y[x] = 0;
actual_y = series[j][1];
- cumulative_y[series[j][0]] += actual_y;
+ cumulative_y[x] += actual_y;
- vals[j] = [series[j][0], cumulative_y[series[j][0]]]
+ series[j] = [x, cumulative_y[x]]
- if (!maxY || cumulative_y[series[j][0]] > maxY)
- maxY = cumulative_y[series[j][0]];
+ if (!maxY || cumulative_y[x] > maxY)
+ maxY = cumulative_y[x];
}
- stacked_datasets.push([this.attr_("labels")[i], vals]);
- //this.layout_.addDataset(this.attr_("labels")[i], vals);
- } else {
- this.layout_.addDataset(this.attr_("labels")[i], series);
}
+
+ datasets[i] = series;
}
- if (stacked_datasets.length > 0) {
- for (var i = (stacked_datasets.length - 1); i >= 0; i--) {
- this.layout_.addDataset(stacked_datasets[i][0], stacked_datasets[i][1]);
- }
+ for (var i = 1; i < datasets.length; i++) {
+ if (!this.visibility()[i - 1]) continue;
+ this.layout_.addDataset(this.attr_("labels")[i], datasets[i]);
}
// Use some heuristics to come up with a good maxY value, unless it's been
this.attrs_.labels = lines[0].split(delim);
}
+ // Parse the x as a float or return null if it's not a number.
+ var parseFloatOrNull = function(x) {
+ if (x.length == 0) return null;
+ return parseFloat(x);
+ };
+
var xParser;
var defaultParserSet = false; // attempt to auto-detect x value type
var expectedCols = this.attr_("labels").length;
for (var j = 1; j < inFields.length; j++) {
// TODO(danvk): figure out an appropriate way to flag parse errors.
var vals = inFields[j].split("/");
- fields[j] = [parseFloat(vals[0]), parseFloat(vals[1])];
+ fields[j] = [parseFloatOrNull(vals[0]), parseFloatOrNull(vals[1])];
}
} else if (this.attr_("errorBars")) {
// If there are error bars, values are (value, stddev) pairs
for (var j = 1; j < inFields.length; j += 2)
- fields[(j + 1) / 2] = [parseFloat(inFields[j]),
- parseFloat(inFields[j + 1])];
+ fields[(j + 1) / 2] = [parseFloatOrNull(inFields[j]),
+ parseFloatOrNull(inFields[j + 1])];
} else if (this.attr_("customBars")) {
// Bars are a low;center;high tuple
for (var j = 1; j < inFields.length; j++) {
var vals = inFields[j].split(";");
- fields[j] = [ parseFloat(vals[0]),
- parseFloat(vals[1]),
- parseFloat(vals[2]) ];
+ fields[j] = [ parseFloatOrNull(vals[0]),
+ parseFloatOrNull(vals[1]),
+ parseFloatOrNull(vals[2]) ];
}
} else {
// Values are just numbers
for (var j = 1; j < inFields.length; j++) {
- fields[j] = parseFloat(inFields[j]);
+ fields[j] = parseFloatOrNull(inFields[j]);
}
}
if (ret.length > 0 && fields[0] < ret[ret.length - 1][0]) {
this.valueRange_ = attrs.valueRange;
}
Dygraph.update(this.user_attrs_, attrs);
+ Dygraph.update(this.renderOptions_, attrs);
this.labelsFromCSV_ = (this.attr_("labels") == null);