clean-combined-test: clean
@echo restoring combined
git checkout dygraph-dev.js
+ rm dygraph-combined.js.map
lint:
@./lint.sh
* Ensures that even if logscale is set globally, it doesn't impact the
* x axis.
*/
-DygraphOptionsTestCase.prototype.getLogscaleForX = function() {
+DygraphOptionsTestCase.prototype.testGetLogscaleForX = function() {
var opts = {
width: 480,
height: 320
var graph = document.getElementById("graph");
var g = new Dygraph(graph, data, opts);
- assertFalse(g.getOptionForAxis('logscale', 'x'));
- assertFalse(g.getOptionForAxis('logscale', 'y'));
+ assertFalse(!!g.getOptionForAxis('logscale', 'x'));
+ assertFalse(!!g.getOptionForAxis('logscale', 'y'));
g.updateOptions({ logscale : true });
- assertFalse(g.getOptionForAxis('logscale', 'x'));
- assertTrue(g.getOptionForAxis('logscale', 'y'));
+ assertFalse(!!g.getOptionForAxis('logscale', 'x'));
+ assertTrue(!!g.getOptionForAxis('logscale', 'y'));
+};
+
+// Helper to gather all warnings emitted by Dygraph constructor.
+// Removes everything after the first open parenthesis in each warning.
+// Returns them in a (possibly empty) list.
+var getWarnings = function(div, data, opts) {
+ var warnings = [];
+ var oldWarn = console.warn;
+ console.warn = function(message) {
+ warnings.push(message.replace(/ \(.*/, ''));
+ };
+ try {
+ new Dygraph(graph, data, opts);
+ } catch (e) {
+ }
+ console.warn = oldWarn;
+ return warnings;
+};
+
+DygraphOptionsTestCase.prototype.testLogWarningForNonexistentOption = function() {
+ if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') {
+ return; // this test won't pass in non-debug mode.
+ }
+
+ var graph = document.getElementById("graph");
+ var data = "X,Y,Y2,Y3\n" +
+ "1,-1,2,3";
+
+ var expectWarning = function(opts, badOptionName) {
+ DygraphOptions.resetWarnings_();
+ var warnings = getWarnings(graph, data, opts);
+ assertEquals(['Unknown option ' + badOptionName], warnings);
+ };
+ var expectNoWarning = function(opts) {
+ DygraphOptions.resetWarnings_();
+ var warnings = getWarnings(graph, data, opts);
+ assertEquals([], warnings);
+ };
+
+ expectNoWarning({});
+ expectWarning({nonExistentOption: true}, 'nonExistentOption');
+ expectWarning({series: {Y: {nonExistentOption: true}}}, 'nonExistentOption');
+ // expectWarning({Y: {nonExistentOption: true}});
+ expectWarning({axes: {y: {anotherNonExistentOption: true}}}, 'anotherNonExistentOption');
+ expectWarning({highlightSeriesOpts: {anotherNonExistentOption: true}}, 'anotherNonExistentOption');
+ expectNoWarning({highlightSeriesOpts: {strokeWidth: 20}});
+ expectNoWarning({strokeWidth: 20});
+};
+
+DygraphOptionsTestCase.prototype.testOnlyLogsEachWarningOnce = function() {
+ if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') {
+ return; // this test won't pass in non-debug mode.
+ }
+
+ var graph = document.getElementById("graph");
+ var data = "X,Y,Y2,Y3\n" +
+ "1,-1,2,3";
+
+ var warnings1 = getWarnings(graph, data, {nonExistent: true});
+ var warnings2 = getWarnings(graph, data, {nonExistent: true});
+ assertEquals(['Unknown option nonExistent'], warnings1);
+ assertEquals([], warnings2);
};
drawYGrid: false,
drawXAxis: false,
drawYAxis: false,
- Y: { fillGraph: true },
+ series: {
+ Y: { fillGraph: true },
+ },
colors: [ '#FF0000', '#0000FF' ],
fillAlpha: 0.15
};
showRangeSelector: true,
rangeSelectorHeight: 30,
rangeSelectorPlotFillColor: 'lightyellow',
- rangeSelectorPlotStyleColor: 'yellow',
labels: ['X', 'Y']
};
var data = [
*/
SimpleDrawingTestCase.prototype.testDrawSimpleDash = function() {
var opts = {
- drawXGrid: false,
- drawYGrid: false,
- drawXAxis: false,
- drawYAxis: false,
+ axes: {
+ x: {
+ drawGrid: false,
+ drawAxis: false
+ },
+ y: {
+ drawGrid: false,
+ drawAxis: false
+ }
+ },
+ series: {
'Y1': {strokePattern: [25, 7, 7, 7]},
- colors: ['#ff0000']
+ },
+ colors: ['#ff0000']
};
var graph = document.getElementById("graph");
var optionsForY1 = { };
optionsForY1['strokeWidth'] = 3;
- updatedOptions['Y1'] = optionsForY1;
+ updatedOptions['series'] = {'Y1': optionsForY1};
// These options will allow us to jump to renderGraph_()
// drawGraph_() will be skipped.
UpdateOptionsTestCase.prototype.testSingleSeriesRequiresNewPoints = function() {
var graphDiv = document.getElementById("graph");
var graph = new Dygraph(graphDiv, this.data, this.opts);
- var updatedOptions = { };
- var optionsForY1 = { };
- var optionsForY2 = { };
-
- // This will not require new points.
- optionsForY1['strokeWidth'] = 2;
- updatedOptions['Y1'] = optionsForY1;
-
- // This will require new points.
- optionsForY2['stepPlot'] = true;
- updatedOptions['Y2'] = optionsForY2;
+ var updatedOptions = {
+ series: {
+ Y1: {
+ strokeWidth: 2
+ },
+ Y2: {
+ stepPlot: true
+ }
+ }
+ };
// These options will not allow us to jump to renderGraph_()
// drawGraph_() must be called
assertEquals({r: 255, g: 0, b: 0}, Dygraph.toRGB_('red'));
};
+UtilsTestCase.prototype.testIsPixelChangingOptionList = function() {
+ var isPx = Dygraph.isPixelChangingOptionList;
+ assertTrue(isPx([], { axes: { y: { digitsAfterDecimal: 3 }}}));
+ assertFalse(isPx([], { axes: { y: { axisLineColor: 'blue' }}}));
+};
+
/*
UtilsTestCase.prototype.testDateSet = function() {
var base = new Date(1383455100000);
"axis": {
"default": "(none)",
"labels": ["Axis display"],
- "type": "string or object",
- "description": "Set to either an object ({}) filled with options for this axis or to the name of an existing data series with its own axis to re-use that axis. See tests for usage."
+ "type": "string",
+ "description": "Set to either 'y1' or 'y2' to assign a series to a y-axis (primary or secondary). Must be set per-series."
},
"pixelsPerXLabel": {
"default": "",
"type": "red, blue",
"description": "The color of the gridlines. This may be set on a per-axis basis to define each axis' grid separately."
},
+ "gridLinePattern": {
+ "default": "null",
+ "labels": ["Grid"],
+ "type": "array<integer>",
+ "example": "[10, 2, 5, 2]",
+ "description": "A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed gridlines."
+ },
"visibility": {
"default": "[true, true, ...]",
"labels": ["Data Line display"],
"type": "array or function",
"description": "A function (or array of functions) which plot each data series on the chart. TODO(danvk): more details! May be set per-series."
},
+ "axes": {
+ "default": "null",
+ "labels": ["Configuration"],
+ "type": "Object",
+ "description": "Defines per-axis options. Valid keys are 'x', 'y' and 'y2'. Only some options may be set on a per-axis basis. If an option may be set in this way, it will be noted on this page. See also documentation on <a href='http://dygraphs.com/per-axis.html'>per-series and per-axis options</a>."
+ },
"series": {
"default": "null",
"labels": ["Series"],
var DygraphOptions = (function() {
+// For "production" code, this gets set to false by uglifyjs.
+// Need to define it outside of "use strict", hence the nested IIFEs.
+if (typeof(DEBUG) === 'undefined') DEBUG=true;
+
+return (function() {
+
/*jshint sub:true */
/*global Dygraph:false */
"use strict";
Dygraph.update(this.yAxes_[1].options, axis_opts["y2"] || {});
}
Dygraph.update(this.xAxis_.options, axis_opts["x"] || {});
+
+ if (DEBUG) this.validateOptions_();
};
/**
return this.labels_;
};
+if (DEBUG) {
+
+/**
+ * Validate all options.
+ * This requires Dygraph.OPTIONS_REFERENCE, which is only available in debug builds.
+ * @private
+ */
+DygraphOptions.prototype.validateOptions_ = function() {
+ if (typeof Dygraph.OPTIONS_REFERENCE === 'undefined') {
+ throw 'Called validateOptions_ in prod build.';
+ }
+
+ var that = this;
+ var validateOption = function(optionName) {
+ if (!Dygraph.OPTIONS_REFERENCE[optionName]) {
+ that.warnInvalidOption_(optionName);
+ }
+ };
+
+ var optionsDicts = [this.xAxis_.options,
+ this.yAxes_[0].options,
+ this.yAxes_[1] && this.yAxes_[1].options,
+ this.global_,
+ this.user_,
+ this.highlightSeries_];
+ var names = this.seriesNames();
+ for (var i = 0; i < names.length; i++) {
+ var name = names[i];
+ if (this.series_.hasOwnProperty(name)) {
+ optionsDicts.push(this.series_[name].options);
+ }
+ }
+ for (var i = 0; i < optionsDicts.length; i++) {
+ var dict = optionsDicts[i];
+ if (!dict) continue;
+ for (var optionName in dict) {
+ if (dict.hasOwnProperty(optionName)) {
+ validateOption(optionName);
+ }
+ }
+ }
+};
+
+var WARNINGS = {}; // Only show any particular warning once.
+
+/**
+ * Logs a warning about invalid options.
+ * TODO: make this throw for testing
+ * @private
+ */
+DygraphOptions.prototype.warnInvalidOption_ = function(optionName) {
+ if (!WARNINGS[optionName]) {
+ WARNINGS[optionName] = true;
+ var isSeries = (this.labels_.indexOf(optionName) >= 0);
+ if (isSeries) {
+ console.warn('Use new-style per-series options (saw ' + optionName + ' as top-level options key). See http://bit.ly/1tceaJs');
+ } else {
+ console.warn('Unknown option ' + optionName + ' (full list of options at dygraphs.com/options.html');
+ throw "invalid option " + optionName;
+ }
+ }
+};
+
+// Reset list of previously-shown warnings. Used for testing.
+DygraphOptions.resetWarnings_ = function() {
+ WARNINGS = {};
+};
+
+}
+
return DygraphOptions;
})();
+})();
* search) and generic DOM-manipulation functions.
*/
-/*jshint globalstrict: true */
+(function() {
+
/*global Dygraph:false, G_vmlCanvasManager:false, Node:false */
"use strict";
})();
};
+// A whitelist of options that do not change pixel positions.
+var pixelSafeOptions = {
+ 'annotationClickHandler': true,
+ 'annotationDblClickHandler': true,
+ 'annotationMouseOutHandler': true,
+ 'annotationMouseOverHandler': true,
+ 'axisLabelColor': true,
+ 'axisLineColor': true,
+ 'axisLineWidth': true,
+ 'clickCallback': true,
+ 'drawCallback': true,
+ 'drawHighlightPointCallback': true,
+ 'drawPoints': true,
+ 'drawPointCallback': true,
+ 'drawXGrid': true,
+ 'drawYGrid': true,
+ 'fillAlpha': true,
+ 'gridLineColor': true,
+ 'gridLineWidth': true,
+ 'hideOverlayOnMouseOut': true,
+ 'highlightCallback': true,
+ 'highlightCircleSize': true,
+ 'interactionModel': true,
+ 'isZoomedIgnoreProgrammaticZoom': true,
+ 'labelsDiv': true,
+ 'labelsDivStyles': true,
+ 'labelsDivWidth': true,
+ 'labelsKMB': true,
+ 'labelsKMG2': true,
+ 'labelsSeparateLines': true,
+ 'labelsShowZeroValues': true,
+ 'legend': true,
+ 'panEdgeFraction': true,
+ 'pixelsPerYLabel': true,
+ 'pointClickCallback': true,
+ 'pointSize': true,
+ 'rangeSelectorPlotFillColor': true,
+ 'rangeSelectorPlotStrokeColor': true,
+ 'showLabelsOnHighlight': true,
+ 'showRoller': true,
+ 'strokeWidth': true,
+ 'underlayCallback': true,
+ 'unhighlightCallback': true,
+ 'zoomCallback': true
+};
+
/**
* This function will scan the option list and determine if they
* require us to recalculate the pixel positions of each point.
+ * TODO: move this into dygraph-options.js
* @param {!Array.<string>} labels a list of options to check.
* @param {!Object} attrs
* @return {boolean} true if the graph needs new points else false.
* @private
*/
Dygraph.isPixelChangingOptionList = function(labels, attrs) {
- // A whitelist of options that do not change pixel positions.
- var pixelSafeOptions = {
- 'annotationClickHandler': true,
- 'annotationDblClickHandler': true,
- 'annotationMouseOutHandler': true,
- 'annotationMouseOverHandler': true,
- 'axisLabelColor': true,
- 'axisLineColor': true,
- 'axisLineWidth': true,
- 'clickCallback': true,
- 'digitsAfterDecimal': true,
- 'drawCallback': true,
- 'drawHighlightPointCallback': true,
- 'drawPoints': true,
- 'drawPointCallback': true,
- 'drawXGrid': true,
- 'drawYGrid': true,
- 'fillAlpha': true,
- 'gridLineColor': true,
- 'gridLineWidth': true,
- 'hideOverlayOnMouseOut': true,
- 'highlightCallback': true,
- 'highlightCircleSize': true,
- 'interactionModel': true,
- 'isZoomedIgnoreProgrammaticZoom': true,
- 'labelsDiv': true,
- 'labelsDivStyles': true,
- 'labelsDivWidth': true,
- 'labelsKMB': true,
- 'labelsKMG2': true,
- 'labelsSeparateLines': true,
- 'labelsShowZeroValues': true,
- 'legend': true,
- 'maxNumberWidth': true,
- 'panEdgeFraction': true,
- 'pixelsPerYLabel': true,
- 'pointClickCallback': true,
- 'pointSize': true,
- 'rangeSelectorPlotFillColor': true,
- 'rangeSelectorPlotStrokeColor': true,
- 'showLabelsOnHighlight': true,
- 'showRoller': true,
- 'sigFigs': true,
- 'strokeWidth': true,
- 'underlayCallback': true,
- 'unhighlightCallback': true,
- 'xAxisLabelFormatter': true,
- 'xTicker': true,
- 'xValueFormatter': true,
- 'yAxisLabelFormatter': true,
- 'yValueFormatter': true,
- 'zoomCallback': true
- };
-
// Assume that we do not require new points.
// This will change to true if we actually do need new points.
- var requiresNewPoints = false;
// Create a dictionary of series names for faster lookup.
// If there are no labels, then the dictionary stays empty.
}
}
+ // Scan through a flat (i.e. non-nested) object of options.
+ // Returns true/false depending on whether new points are needed.
+ var scanFlatOptions = function(options) {
+ for (var property in options) {
+ if (options.hasOwnProperty(property) &&
+ !pixelSafeOptions[property]) {
+ return true;
+ }
+ }
+ return false;
+ };
+
// Iterate through the list of updated options.
for (var property in attrs) {
- // Break early if we already know we need new points from a previous option.
- if (requiresNewPoints) {
- break;
- }
- if (attrs.hasOwnProperty(property)) {
- // Find out of this field is actually a series specific options list.
- if (seriesNamesDictionary[property]) {
- // This property value is a list of options for this series.
- // If any of these sub properties are not pixel safe, set the flag.
- for (var subProperty in attrs[property]) {
- // Break early if we already know we need new points from a previous option.
- if (requiresNewPoints) {
- break;
- }
- if (attrs[property].hasOwnProperty(subProperty) && !pixelSafeOptions[subProperty]) {
- requiresNewPoints = true;
- }
+ if (!attrs.hasOwnProperty(property)) continue;
+
+ // Find out of this field is actually a series specific options list.
+ if (property == 'highlightSeriesOpts' ||
+ (seriesNamesDictionary[property] && !attrs.series)) {
+ // This property value is a list of options for this series.
+ if (scanFlatOptions(attrs[property])) return true;
+ } else if (property == 'series' || property == 'axes') {
+ // This is twice-nested options list.
+ var perSeries = attrs[property];
+ for (var series in perSeries) {
+ if (perSeries.hasOwnProperty(series) &&
+ scanFlatOptions(perSeries[series])) {
+ return true;
}
- // If this was not a series specific option list, check if its a pixel changing property.
- } else if (!pixelSafeOptions[property]) {
- requiresNewPoints = true;
}
+ } else {
+ // If this was not a series specific option list, check if it's a pixel
+ // changing property.
+ if (!pixelSafeOptions[property]) return true;
}
}
- return requiresNewPoints;
+ return false;
};
Dygraph.Circles = {
return null;
};
+
+})();
// For "production" code, this gets set to false by uglifyjs.
if (typeof(DEBUG) === 'undefined') DEBUG=true;
+var Dygraph = (function() {
/*jshint globalstrict: true */
/*global DygraphLayout:false, DygraphCanvasRenderer:false, DygraphOptions:false, G_vmlCanvasManager:false,ActiveXObject:false */
"use strict";
axisLineWidth: 0.3,
gridLineWidth: 0.3,
axisLabelColor: "black",
- axisLabelFont: "Arial", // TODO(danvk): is this implemented?
axisLabelWidth: 50,
drawYGrid: true,
drawXGrid: true,
console.warn("Unable to add default annotation CSS rule; display may be off.");
};
+
+return Dygraph;
+
+})();
var shapes = [];
var addShape = function(name, pointFn, highlightPointFn) {
shapes.push(name);
- opts[name] = {
+ if (!opts['series']) opts['series'] = {};
+ opts.series[name] = {
drawPointCallback: pointFn,
drawHighlightPointCallback: highlightPointFn
};
return ret;
},
{
- Y1: { fillGraph: true }
+ series: {
+ Y1: { fillGraph: true }
+ }
}
);
</script>
{
legend: 'always',
strokeWidth: 2,
- 'parabola': {
- strokePattern: null,
- drawPoints: true,
- pointSize: 4,
- highlightCircleSize: 6
- },
- 'line': {
- strokePattern: Dygraph.DASHED_LINE,
- strokeWidth: 1.0,
- drawPoints: true,
- pointSize: 1.5
- },
- 'another line': {
- strokePattern: [25, 5]
- },
- 'sine wave': {
- strokePattern: Dygraph.DOTTED_LINE,
- strokeWidth: 3,
- highlightCircleSize: 10
- },
- 'sine wave2': {
- strokePattern: Dygraph.DOT_DASH_LINE,
- strokeWidth: 2,
- highlightCircleSize: 3
+ series: {
+ 'parabola': {
+ strokePattern: null,
+ drawPoints: true,
+ pointSize: 4,
+ highlightCircleSize: 6
+ },
+ 'line': {
+ strokePattern: Dygraph.DASHED_LINE,
+ strokeWidth: 1.0,
+ drawPoints: true,
+ pointSize: 1.5
+ },
+ 'another line': {
+ strokePattern: [25, 5]
+ },
+ 'sine wave': {
+ strokePattern: Dygraph.DOTTED_LINE,
+ strokeWidth: 3,
+ highlightCircleSize: 10
+ },
+ 'sine wave2': {
+ strokePattern: Dygraph.DOT_DASH_LINE,
+ strokeWidth: 2,
+ highlightCircleSize: 3
+ }
}
}
);
data,
{
strokeWidth: 2,
- 'parabola': {
- strokeWidth: 0.0,
- drawPoints: true,
- pointSize: 4,
- highlightCircleSize: 6
- },
- 'line': {
- strokeWidth: 1.0,
- drawPoints: true,
- pointSize: 1.5
- },
- 'sine wave': {
- strokeWidth: 3,
- highlightCircleSize: 10
- },
- 'sine wave2': {
- strokePattern: [10, 2, 5, 2],
- strokeWidth: 2,
- highlightCircleSize: 3
+ series: {
+ 'parabola': {
+ strokeWidth: 0.0,
+ drawPoints: true,
+ pointSize: 4,
+ highlightCircleSize: 6
+ },
+ 'line': {
+ strokeWidth: 1.0,
+ drawPoints: true,
+ pointSize: 1.5
+ },
+ 'sine wave': {
+ strokeWidth: 3,
+ highlightCircleSize: 10
+ },
+ 'sine wave2': {
+ strokePattern: [10, 2, 5, 2],
+ strokeWidth: 2,
+ highlightCircleSize: 3
+ }
}
}
);
{
labels: ['Date', 'A', 'B'],
includeZero: true,
- "A": {
- strokeWidth: 2
- },
- "B": {
- plotter: barChartPlotter
+ series: {
+ "A": {
+ strokeWidth: 2
+ },
+ "B": {
+ plotter: barChartPlotter
+ }
}
});
NoisyData(),
{
errorBars: true,
- 'A': {
- plotter: Dygraph.Plotters.errorPlotter
- },
- 'B': {
- plotter: Dygraph.Plotters.linePlotter,
- strokePattern: Dygraph.DASHED_LINE
+ series: {
+ 'A': {
+ plotter: Dygraph.Plotters.errorPlotter
+ },
+ 'B': {
+ plotter: Dygraph.Plotters.linePlotter,
+ strokePattern: Dygraph.DASHED_LINE
+ }
}
});
labels: ["Date","GapSeries1","GapSeries2"],
showRoller: true,
stepPlot: true,
- GapSeries2: { axis: {} }
+ series: {
+ GapSeries2: {
+ axis: 'y2'
+ }
+ }
}
);
</script>
data,
{
labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
- 'Y3': {
- axis: {
- }
- },
- 'Y4': {
- axis: 'Y3' // use the same y-axis as series Y3
+ series: {
+ 'Y3': {
+ axis: 'y2'
+ },
+ 'Y4': {
+ axis: 'y2'
+ },
},
axes: {
y2: {
labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
width: 640,
height: 350,
- 'Y3': {
- axis: {
- }
- },
- 'Y4': {
- axis: 'Y3' // use the same y-axis as series Y3
+ series: {
+ 'Y3': { axis: 'y2' },
+ 'Y4': { axis: 'y2' }
},
xAxisLabelWidth: 100,
yAxisLabelWidth: 100,