diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js
index 592620c3820..c7b47a2ce1f 100644
--- a/src/components/legend/draw.js
+++ b/src/components/legend/draw.js
@@ -402,12 +402,12 @@ function repositionLegend(gd, traces) {
else {
// approximation to height offset to center the font
// to avoid getBoundingClientRect
- textY = tHeight * (0.3 + (1-tLines) / 2);
- text.attr('y',textY);
- tspans.attr('y',textY);
+ textY = tHeight * (0.3 + (1 - tLines) / 2);
+ text.attr('y', textY);
+ tspans.attr('y', textY);
}
- tHeightFull = Math.max(tHeight*tLines, 16) + 3;
+ tHeightFull = Math.max(tHeight * tLines, 16) + 3;
g.attr('transform',
'translate(' + borderwidth + ',' +
diff --git a/src/components/rangeselector/attributes.js b/src/components/rangeselector/attributes.js
new file mode 100644
index 00000000000..c0c58429bd9
--- /dev/null
+++ b/src/components/rangeselector/attributes.js
@@ -0,0 +1,97 @@
+/**
+* Copyright 2012-2016, Plotly, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+'use strict';
+
+var fontAttrs = require('../../plots/font_attributes');
+var colorAttrs = require('../color/attributes');
+var extendFlat = require('../../lib/extend').extendFlat;
+var buttonAttrs = require('./button_attributes');
+
+buttonAttrs = extendFlat(buttonAttrs, {
+ _isLinkedToArray: true,
+ description: [
+ 'Sets the specifications for each buttons.',
+ 'By default, a range selector comes with no buttons.'
+ ].join(' ')
+});
+
+module.exports = {
+ visible: {
+ valType: 'boolean',
+ role: 'info',
+ description: [
+ 'Determines whether or not this range selector is visible.',
+ 'Note that range selectors are only available for x axes of',
+ '`type` set to or auto-typed to *date*.'
+ ].join(' ')
+ },
+
+ buttons: buttonAttrs,
+
+ x: {
+ valType: 'number',
+ min: -2,
+ max: 3,
+ role: 'style',
+ description: 'Sets the x position (in normalized coordinates) of the range selector.'
+ },
+ xanchor: {
+ valType: 'enumerated',
+ values: ['auto', 'left', 'center', 'right'],
+ dflt: 'left',
+ role: 'info',
+ description: [
+ 'Sets the range selector\'s horizontal position anchor.',
+ 'This anchor binds the `x` position to the *left*, *center*',
+ 'or *right* of the range selector.'
+ ].join(' ')
+ },
+ y: {
+ valType: 'number',
+ min: -2,
+ max: 3,
+ role: 'style',
+ description: 'Sets the y position (in normalized coordinates) of the range selector.'
+ },
+ yanchor: {
+ valType: 'enumerated',
+ values: ['auto', 'top', 'middle', 'bottom'],
+ dflt: 'bottom',
+ role: 'info',
+ description: [
+ 'Sets the range selector\'s vertical position anchor',
+ 'This anchor binds the `y` position to the *top*, *middle*',
+ 'or *bottom* of the range selector.'
+ ].join(' ')
+ },
+
+ font: extendFlat({}, fontAttrs, {
+ description: 'Sets the font of the range selector button text.'
+ }),
+
+ bgcolor: {
+ valType: 'color',
+ dflt: colorAttrs.lightLine,
+ role: 'style',
+ description: 'Sets the background color of the range selector buttons.'
+ },
+ bordercolor: {
+ valType: 'color',
+ dflt: colorAttrs.defaultLine,
+ role: 'style',
+ description: 'Sets the color of the border enclosing the range selector.'
+ },
+ borderwidth: {
+ valType: 'number',
+ min: 0,
+ dflt: 0,
+ role: 'style',
+ description: 'Sets the width (in px) of the border enclosing the range selector.'
+ }
+};
diff --git a/src/components/rangeselector/button_attributes.js b/src/components/rangeselector/button_attributes.js
new file mode 100644
index 00000000000..5ebfd7a7faa
--- /dev/null
+++ b/src/components/rangeselector/button_attributes.js
@@ -0,0 +1,54 @@
+/**
+* Copyright 2012-2016, Plotly, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+'use strict';
+
+
+module.exports = {
+ step: {
+ valType: 'enumerated',
+ role: 'info',
+ values: ['month', 'year', 'day', 'hour', 'minute', 'second', 'all'],
+ dflt: 'month',
+ description: [
+ 'The unit of measurement that the `count` value will set the range by.'
+ ].join(' ')
+ },
+ stepmode: {
+ valType: 'enumerated',
+ role: 'info',
+ values: ['backward', 'todate'],
+ dflt: 'backward',
+ description: [
+ 'Sets the range update mode.',
+ 'If *backward*, the range update shifts the start of range',
+ 'back *count* times *step* milliseconds.',
+ 'If *todate*, the range update shifts the start of range',
+ 'back to the first timestamp from *count* times',
+ '*step* milliseconds back.',
+ 'For example, with `step` set to *year* and `count` set to *1*',
+ 'the range update shifts the start of the range back to',
+ 'January 01 of the current year.'
+ ].join(' ')
+ },
+ count: {
+ valType: 'number',
+ role: 'info',
+ min: 0,
+ dflt: 1,
+ description: [
+ 'Sets the number of steps to take to update the range.',
+ 'Use with `step` to specify the update interval.'
+ ].join(' ')
+ },
+ label: {
+ valType: 'string',
+ role: 'info',
+ description: 'Sets the text label to appear on the button.'
+ }
+};
diff --git a/src/components/rangeselector/constants.js b/src/components/rangeselector/constants.js
new file mode 100644
index 00000000000..e598ab45d69
--- /dev/null
+++ b/src/components/rangeselector/constants.js
@@ -0,0 +1,26 @@
+/**
+* Copyright 2012-2016, Plotly, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+'use strict';
+
+
+module.exports = {
+
+ // 'y' position pad above counter axis domain
+ yPad: 0.02,
+
+ // minimum button width (regardless of text size)
+ minButtonWidth: 30,
+
+ // buttons rect radii
+ rx: 3,
+ ry: 3,
+
+ // color given to active and hovered buttons
+ activeColor: '#d3d3d3'
+};
diff --git a/src/components/rangeselector/defaults.js b/src/components/rangeselector/defaults.js
new file mode 100644
index 00000000000..b2c02e846fe
--- /dev/null
+++ b/src/components/rangeselector/defaults.js
@@ -0,0 +1,85 @@
+/**
+* Copyright 2012-2016, Plotly, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+'use strict';
+
+var Lib = require('../../lib');
+
+var attributes = require('./attributes');
+var buttonAttrs = require('./button_attributes');
+var constants = require('./constants');
+
+
+module.exports = function rangeSelectorDefaults(containerIn, containerOut, layout, counterAxes) {
+ var selectorIn = containerIn.rangeselector || {},
+ selectorOut = containerOut.rangeselector = {};
+
+ function coerce(attr, dflt) {
+ return Lib.coerce(selectorIn, selectorOut, attributes, attr, dflt);
+ }
+
+ var buttons = buttonsDefaults(selectorIn, selectorOut);
+
+ var visible = coerce('visible', buttons.length > 0);
+ if(!visible) return;
+
+ var posDflt = getPosDflt(containerOut, layout, counterAxes);
+ coerce('x', posDflt[0]);
+ coerce('y', posDflt[1]);
+ Lib.noneOrAll(containerIn, containerOut, ['x', 'y']);
+
+ coerce('xanchor');
+ coerce('yanchor');
+
+ Lib.coerceFont(coerce, 'font', layout.font);
+
+ coerce('bgcolor');
+ coerce('bordercolor');
+ coerce('borderwidth');
+};
+
+function buttonsDefaults(containerIn, containerOut) {
+ var buttonsIn = containerIn.buttons || [],
+ buttonsOut = containerOut.buttons = [];
+
+ var buttonIn, buttonOut;
+
+ function coerce(attr, dflt) {
+ return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt);
+ }
+
+ for(var i = 0; i < buttonsIn.length; i++) {
+ buttonIn = buttonsIn[i];
+ buttonOut = {};
+
+ var step = coerce('step');
+ if(step !== 'all') {
+ coerce('stepmode');
+ coerce('count');
+ }
+
+ coerce('label');
+
+ buttonsOut.push(buttonOut);
+ }
+
+ return buttonsOut;
+}
+
+function getPosDflt(containerOut, layout, counterAxes) {
+ var anchoredList = counterAxes.filter(function(ax) {
+ return layout[ax].anchor === containerOut._id;
+ });
+
+ var posY = 0;
+ for(var i = 0; i < anchoredList.length; i++) {
+ posY = Math.max(layout[anchoredList[i]].domain[1], posY);
+ }
+
+ return [containerOut.domain[0], posY + constants.yPad];
+}
diff --git a/src/components/rangeselector/draw.js b/src/components/rangeselector/draw.js
new file mode 100644
index 00000000000..2eef35a05c6
--- /dev/null
+++ b/src/components/rangeselector/draw.js
@@ -0,0 +1,273 @@
+/**
+* Copyright 2012-2016, Plotly, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+
+'use strict';
+
+var d3 = require('d3');
+
+var Plotly = require('../../plotly');
+var Plots = require('../../plots/plots');
+var Color = require('../color');
+var Drawing = require('../drawing');
+var svgTextUtils = require('../../lib/svg_text_utils');
+var axisIds = require('../../plots/cartesian/axis_ids');
+var anchorUtils = require('../legend/anchor_utils');
+
+var constants = require('./constants');
+var getUpdateObject = require('./get_update_object');
+
+
+module.exports = function draw(gd) {
+ var fullLayout = gd._fullLayout;
+
+ var selectors = fullLayout._infolayer.selectAll('.rangeselector')
+ .data(makeSelectorData(gd), selectorKeyFunc);
+
+ selectors.enter().append('g')
+ .classed('rangeselector', true);
+
+ selectors.exit().remove();
+
+ selectors.style({
+ cursor: 'pointer',
+ 'pointer-events': 'all'
+ });
+
+ selectors.each(function(d) {
+ var selector = d3.select(this),
+ axisLayout = d,
+ selectorLayout = axisLayout.rangeselector;
+
+ var buttons = selector.selectAll('g.button')
+ .data(selectorLayout.buttons);
+
+ buttons.enter().append('g')
+ .classed('button', true);
+
+ buttons.exit().remove();
+
+ buttons.each(function(d) {
+ var button = d3.select(this);
+ var update = getUpdateObject(axisLayout, d);
+
+ d.isActive = isActive(axisLayout, d, update);
+
+ button.call(drawButtonRect, selectorLayout, d);
+ button.call(drawButtonText, selectorLayout, d);
+
+ button.on('click', function() {
+ if(gd._dragged) return;
+
+ Plotly.relayout(gd, update);
+ });
+
+ button.on('mouseover', function() {
+ d.isHovered = true;
+ button.call(drawButtonRect, selectorLayout, d);
+ });
+
+ button.on('mouseout', function() {
+ d.isHovered = false;
+ button.call(drawButtonRect, selectorLayout, d);
+ });
+ });
+
+ // N.B. this mutates selectorLayout
+ reposition(gd, buttons, selectorLayout, axisLayout._name);
+
+ selector.attr('transform', 'translate(' +
+ selectorLayout.lx + ',' + selectorLayout.ly +
+ ')');
+ });
+
+};
+
+function makeSelectorData(gd) {
+ var axes = axisIds.list(gd, 'x', true);
+ var data = [];
+
+ for(var i = 0; i < axes.length; i++) {
+ var axis = axes[i];
+
+ if(axis.rangeselector && axis.rangeselector.visible) {
+ data.push(axis);
+ }
+ }
+
+ return data;
+}
+
+function selectorKeyFunc(d) {
+ return d._id;
+}
+
+function isActive(axisLayout, opts, update) {
+ if(opts.step === 'all') {
+ return axisLayout.autorange === true;
+ }
+ else {
+ var keys = Object.keys(update);
+
+ return (
+ axisLayout.range[0] === update[keys[0]] &&
+ axisLayout.range[1] === update[keys[1]]
+ );
+ }
+}
+
+function drawButtonRect(button, selectorLayout, d) {
+ var rect = button.selectAll('rect')
+ .data([0]);
+
+ rect.enter().append('rect')
+ .classed('selector-rect', true);
+
+ rect.attr('shape-rendering', 'crispEdges');
+
+ rect.attr({
+ 'rx': constants.rx,
+ 'ry': constants.ry
+ });
+
+ rect.call(Color.stroke, selectorLayout.bordercolor)
+ .call(Color.fill, getFillColor(selectorLayout, d))
+ .style('stroke-width', selectorLayout.borderwidth + 'px');
+}
+
+function getFillColor(selectorLayout, d) {
+ return (d.isActive || d.isHovered) ?
+ constants.activeColor :
+ selectorLayout.bgcolor;
+}
+
+function drawButtonText(button, selectorLayout, d) {
+ function textLayout(s) {
+ svgTextUtils.convertToTspans(s);
+
+ // TODO do we need anything else here?
+ }
+
+ var text = button.selectAll('text')
+ .data([0]);
+
+ text.enter().append('text')
+ .classed('selector-text', true)
+ .classed('user-select-none', true);
+
+ text.attr('text-anchor', 'middle');
+
+ text.call(Drawing.font, selectorLayout.font)
+ .text(getLabel(d))
+ .call(textLayout);
+}
+
+function getLabel(opts) {
+ if(opts.label) return opts.label;
+
+ if(opts.step === 'all') return 'all';
+
+ return opts.count + opts.step.charAt(0);
+}
+
+function reposition(gd, buttons, opts, axName) {
+ opts.width = 0;
+ opts.height = 0;
+
+ var borderWidth = opts.borderwidth;
+
+ buttons.each(function() {
+ var button = d3.select(this),
+ text = button.select('.selector-text'),
+ tspans = text.selectAll('tspan');
+
+ var tHeight = opts.font.size * 1.3,
+ tLines = tspans[0].length || 1,
+ hEff = Math.max(tHeight * tLines, 16) + 3;
+
+ opts.height = Math.max(opts.height, hEff);
+ });
+
+ buttons.each(function() {
+ var button = d3.select(this),
+ rect = button.select('.selector-rect'),
+ text = button.select('.selector-text'),
+ tspans = text.selectAll('tspan');
+
+ var tWidth = text.node() && Drawing.bBox(text.node()).width,
+ tHeight = opts.font.size * 1.3,
+ tLines = tspans[0].length || 1;
+
+ var wEff = Math.max(tWidth + 10, constants.minButtonWidth);
+
+ // TODO add MathJax support
+
+ // TODO add buttongap attribute
+
+ button.attr('transform', 'translate(' +
+ (borderWidth + opts.width) + ',' + borderWidth +
+ ')');
+
+ rect.attr({
+ x: 0,
+ y: 0,
+ width: wEff,
+ height: opts.height
+ });
+
+ var textAttrs = {
+ x: wEff / 2,
+ y: opts.height / 2 - ((tLines - 1) * tHeight / 2) + 3
+ };
+
+ text.attr(textAttrs);
+ tspans.attr(textAttrs);
+
+ opts.width += wEff + 5;
+ });
+
+ buttons.selectAll('rect').attr('height', opts.height);
+
+ var graphSize = gd._fullLayout._size;
+ opts.lx = graphSize.l + graphSize.w * opts.x;
+ opts.ly = graphSize.t + graphSize.h * (1 - opts.y);
+
+ var xanchor = 'left';
+ if(anchorUtils.isRightAnchor(opts)) {
+ opts.lx -= opts.width;
+ xanchor = 'right';
+ }
+ if(anchorUtils.isCenterAnchor(opts)) {
+ opts.lx -= opts.width / 2;
+ xanchor = 'center';
+ }
+
+ var yanchor = 'top';
+ if(anchorUtils.isBottomAnchor(opts)) {
+ opts.ly -= opts.height;
+ yanchor = 'bottom';
+ }
+ if(anchorUtils.isMiddleAnchor(opts)) {
+ opts.ly -= opts.height / 2;
+ yanchor = 'middle';
+ }
+
+ opts.width = Math.ceil(opts.width);
+ opts.height = Math.ceil(opts.height);
+ opts.lx = Math.round(opts.lx);
+ opts.ly = Math.round(opts.ly);
+
+ Plots.autoMargin(gd, axName + '-range-selector', {
+ x: opts.x,
+ y: opts.y,
+ l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0),
+ r: opts.width * ({left: 1, center: 0.5}[xanchor] || 0),
+ b: opts.height * ({top: 1, middle: 0.5}[yanchor] || 0),
+ t: opts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0)
+ });
+}
diff --git a/src/components/rangeselector/get_update_object.js b/src/components/rangeselector/get_update_object.js
new file mode 100644
index 00000000000..740fce6c86b
--- /dev/null
+++ b/src/components/rangeselector/get_update_object.js
@@ -0,0 +1,56 @@
+/**
+* Copyright 2012-2016, Plotly, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+
+'use strict';
+
+var d3 = require('d3');
+
+
+module.exports = function getUpdateObject(axisLayout, buttonLayout) {
+ var axName = axisLayout._name;
+ var update = {};
+
+ if(buttonLayout.step === 'all') {
+ update[axName + '.autorange'] = true;
+ }
+ else {
+ var xrange = getXRange(axisLayout, buttonLayout);
+
+ update[axName + '.range[0]'] = xrange[0];
+ update[axName + '.range[1]'] = xrange[1];
+ }
+
+ return update;
+};
+
+function getXRange(axisLayout, buttonLayout) {
+ var currentRange = axisLayout.range;
+ var base = new Date(currentRange[1]);
+
+ var step = buttonLayout.step,
+ count = buttonLayout.count;
+
+ var range0;
+
+ switch(buttonLayout.stepmode) {
+ case 'backward':
+ range0 = d3.time[step].offset(base, -count).getTime();
+ break;
+
+ case 'todate':
+ var base2 = d3.time[step].offset(base, -(count - 1));
+
+ range0 = d3.time[step].floor(base2).getTime();
+ break;
+ }
+
+ var range1 = currentRange[1];
+
+ return [range0, range1];
+}
diff --git a/src/components/rangeselector/index.js b/src/components/rangeselector/index.js
new file mode 100644
index 00000000000..f9518eb46b1
--- /dev/null
+++ b/src/components/rangeselector/index.js
@@ -0,0 +1,16 @@
+/**
+* Copyright 2012-2016, Plotly, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+'use strict';
+
+
+exports.attributes = require('./attributes');
+
+exports.supplyLayoutDefaults = require('./defaults');
+
+exports.draw = require('./draw');
diff --git a/src/components/rangeslider/attributes.js b/src/components/rangeslider/attributes.js
index ec0ae8b878c..118d06bd0c6 100644
--- a/src/components/rangeslider/attributes.js
+++ b/src/components/rangeslider/attributes.js
@@ -26,6 +26,7 @@ module.exports = {
borderwidth: {
valType: 'integer',
dflt: 0,
+ min: 0,
role: 'style',
description: 'Sets the border color of the range slider.'
},
diff --git a/src/lib/nested_property.js b/src/lib/nested_property.js
index 71aa00c5a4e..411610ef396 100644
--- a/src/lib/nested_property.js
+++ b/src/lib/nested_property.js
@@ -119,7 +119,7 @@ function npGet(cont, parts) {
*/
function isDataArray(val, key) {
- var containers = ['annotations', 'shapes', 'range', 'domain'],
+ var containers = ['annotations', 'shapes', 'range', 'domain', 'buttons'],
isNotAContainer = containers.indexOf(key) === -1;
return Array.isArray(val) && isNotAContainer;
diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js
index 2793fc45919..ef17d83e1c0 100644
--- a/src/plot_api/plot_api.js
+++ b/src/plot_api/plot_api.js
@@ -26,6 +26,7 @@ var Drawing = require('../components/drawing');
var ErrorBars = require('../components/errorbars');
var Legend = require('../components/legend');
var RangeSlider = require('../components/rangeslider');
+var RangeSelector = require('../components/rangeselector');
var Shapes = require('../components/shapes');
var Titles = require('../components/titles');
var manageModeBar = require('../components/modebar/manage');
@@ -178,6 +179,7 @@ Plotly.plot = function(gd, data, layout, config) {
var i, cd, trace;
Legend.draw(gd);
+ RangeSelector.draw(gd);
for(i = 0; i < calcdata.length; i++) {
cd = calcdata[i];
@@ -307,6 +309,7 @@ Plotly.plot = function(gd, data, layout, config) {
Shapes.drawAll(gd);
Plotly.Annotations.drawAll(gd);
Legend.draw(gd);
+ RangeSelector.draw(gd);
}
function cleanUp() {
diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js
index 9dbed44d3f5..c7a3c9b2f84 100644
--- a/src/plot_api/plot_schema.js
+++ b/src/plot_api/plot_schema.js
@@ -131,7 +131,7 @@ function getLayoutAttributes() {
mergeValTypeAndRole(layoutAttributes);
// generate IS_LINKED_TO_ARRAY structure
- layoutAttributes = handleLinkedToArray(layoutAttributes);
+ handleLinkedToArray(layoutAttributes);
plotSchema.layout = { layoutAttributes: layoutAttributes };
}
@@ -304,19 +304,19 @@ function handleSubplotObjs(layoutAttributes) {
}
function handleLinkedToArray(layoutAttributes) {
- Object.keys(layoutAttributes).forEach(function(k) {
- var attr = extendDeep({}, layoutAttributes[k]);
+ function callback(attr, attrName, attrs) {
if(attr[IS_LINKED_TO_ARRAY] !== true) return;
- var itemName = k.substr(0, k.length-1); // TODO more robust logic
+ // TODO more robust logic
+ var itemName = attrName.substr(0, attrName.length - 1);
delete attr[IS_LINKED_TO_ARRAY];
- layoutAttributes[k] = { items: {} };
- layoutAttributes[k].items[itemName] = attr;
- layoutAttributes[k].role = 'object';
- });
+ attrs[attrName] = { items: {} };
+ attrs[attrName].items[itemName] = attr;
+ attrs[attrName].role = 'object';
+ }
- return layoutAttributes;
+ PlotSchema.crawl(layoutAttributes, callback);
}
diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js
index ae9dabf7602..ba30f95305f 100644
--- a/src/plots/cartesian/layout_attributes.js
+++ b/src/plots/cartesian/layout_attributes.js
@@ -7,11 +7,14 @@
*/
'use strict';
+
var Cartesian = require('./index');
var fontAttrs = require('../font_attributes');
var colorAttrs = require('../../components/color/attributes');
var extendFlat = require('../../lib/extend').extendFlat;
var rangeSliderAttrs = require('../../components/rangeslider/attributes');
+var rangeSelectorAttrs = require('../../components/rangeselector/attributes');
+
module.exports = {
title: {
@@ -81,7 +84,10 @@ module.exports = {
'January 1st 1970 to November 4th, 2013, set the range from 0 to 1380844800000.0'
].join(' ')
},
+
rangeslider: rangeSliderAttrs,
+ rangeselector: rangeSelectorAttrs,
+
fixedrange: {
valType: 'boolean',
dflt: false,
diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js
index ba0e6de27ad..0dea80e4b93 100644
--- a/src/plots/cartesian/layout_defaults.js
+++ b/src/plots/cartesian/layout_defaults.js
@@ -13,6 +13,7 @@ var Lib = require('../../lib');
var Plots = require('../plots');
var RangeSlider = require('../../components/rangeslider');
+var RangeSelector = require('../../components/rangeselector');
var constants = require('./constants');
var layoutAttributes = require('./layout_attributes');
@@ -132,6 +133,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions);
handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, positioningOptions);
+
layoutOut[axName] = axLayoutOut;
// so we don't have to repeat autotype unnecessarily,
@@ -142,12 +144,18 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
});
- // quick second pass for rangeslider defaults
+ // quick second pass for range slider and selector defaults
axesList.forEach(function(axName) {
var axLetter = axName.charAt(0),
+ axLayoutIn = layoutIn[axName],
+ axLayoutOut = layoutOut[axName],
counterAxes = {x: yaList, y: xaList}[axLetter];
RangeSlider.supplyLayoutDefaults(layoutIn, layoutOut, axName, counterAxes);
+
+ if(axLetter === 'x' && axLayoutOut.type === 'date') {
+ RangeSelector.supplyLayoutDefaults(axLayoutIn, axLayoutOut, layoutOut, counterAxes);
+ }
});
// plot_bgcolor only makes sense if there's a (2D) plot!
diff --git a/src/plots/plots.js b/src/plots/plots.js
index bc519f70687..e23e61ad17c 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -847,10 +847,12 @@ plots.sanitizeMargins = function(fullLayout) {
// o is {x,l,r,y,t,b} where x and y are plot fractions,
// the rest are pixels in each direction
// or leave o out to delete this entry (like if it's hidden)
-plots.autoMargin = function(gd,id,o) {
+plots.autoMargin = function(gd, id, o) {
var fullLayout = gd._fullLayout;
+
if(!fullLayout._pushmargin) fullLayout._pushmargin = {};
- if(fullLayout.margin.autoexpand!==false) {
+
+ if(fullLayout.margin.autoexpand !== false) {
if(!o) delete fullLayout._pushmargin[id];
else {
var pad = o.pad === undefined ? 12 : o.pad;
diff --git a/test/image/baselines/range_selector.png b/test/image/baselines/range_selector.png
new file mode 100644
index 00000000000..c8fbbddd96f
Binary files /dev/null and b/test/image/baselines/range_selector.png differ
diff --git a/test/image/baselines/range_selector_style.png b/test/image/baselines/range_selector_style.png
new file mode 100644
index 00000000000..def2526a93a
Binary files /dev/null and b/test/image/baselines/range_selector_style.png differ
diff --git a/test/image/mocks/range_selector.json b/test/image/mocks/range_selector.json
new file mode 100644
index 00000000000..01324ce5861
--- /dev/null
+++ b/test/image/mocks/range_selector.json
@@ -0,0 +1,554 @@
+{
+ "data": [
+ {
+ "mode": "lines",
+ "x": [
+ "1948-01-01",
+ "1948-04-10",
+ "1948-07-19",
+ "1948-10-27",
+ "1949-02-04",
+ "1949-05-15",
+ "1949-08-23",
+ "1949-12-01",
+ "1950-03-11",
+ "1950-06-19",
+ "1950-09-27",
+ "1951-01-05",
+ "1951-04-15",
+ "1951-07-24",
+ "1951-11-01",
+ "1952-02-09",
+ "1952-05-19",
+ "1952-08-27",
+ "1952-12-05",
+ "1953-03-15",
+ "1953-06-23",
+ "1953-10-01",
+ "1954-01-09",
+ "1954-04-19",
+ "1954-07-28",
+ "1954-11-05",
+ "1955-02-13",
+ "1955-05-24",
+ "1955-09-01",
+ "1955-12-10",
+ "1956-03-19",
+ "1956-06-27",
+ "1956-10-05",
+ "1957-01-13",
+ "1957-04-23",
+ "1957-08-01",
+ "1957-11-09",
+ "1958-02-17",
+ "1958-05-28",
+ "1958-09-05",
+ "1958-12-14",
+ "1959-03-24",
+ "1959-07-02",
+ "1959-10-10",
+ "1960-01-18",
+ "1960-04-27",
+ "1960-08-05",
+ "1960-11-13",
+ "1961-02-21",
+ "1961-06-01",
+ "1961-09-09",
+ "1961-12-18",
+ "1962-03-28",
+ "1962-07-06",
+ "1962-10-14",
+ "1963-01-22",
+ "1963-05-02",
+ "1963-08-10",
+ "1963-11-18",
+ "1964-02-26",
+ "1964-06-05",
+ "1964-09-13",
+ "1964-12-22",
+ "1965-04-01",
+ "1965-07-10",
+ "1965-10-18",
+ "1966-01-26",
+ "1966-05-06",
+ "1966-08-14",
+ "1966-11-22",
+ "1967-03-02",
+ "1967-06-10",
+ "1967-09-18",
+ "1967-12-27",
+ "1968-04-05",
+ "1968-07-14",
+ "1968-10-22",
+ "1969-01-30",
+ "1969-05-10",
+ "1969-08-18",
+ "1969-11-26",
+ "1970-03-06",
+ "1970-06-14",
+ "1970-09-22",
+ "1970-12-31",
+ "1971-04-10",
+ "1971-07-19",
+ "1971-10-27",
+ "1972-02-04",
+ "1972-05-14",
+ "1972-08-22",
+ "1972-11-30",
+ "1973-03-10",
+ "1973-06-18",
+ "1973-09-26",
+ "1974-01-04",
+ "1974-04-14",
+ "1974-07-23",
+ "1974-10-31",
+ "1975-02-08",
+ "1975-05-19",
+ "1975-08-27",
+ "1975-12-05",
+ "1976-03-14",
+ "1976-06-22",
+ "1976-09-30",
+ "1977-01-08",
+ "1977-04-18",
+ "1977-07-27",
+ "1977-11-04",
+ "1978-02-12",
+ "1978-05-23",
+ "1978-08-31",
+ "1978-12-09",
+ "1979-03-19",
+ "1979-06-27",
+ "1979-10-05",
+ "1980-01-13",
+ "1980-04-22",
+ "1980-07-31",
+ "1980-11-08",
+ "1981-02-16",
+ "1981-05-27",
+ "1981-09-04",
+ "1981-12-13",
+ "1982-03-23",
+ "1982-07-01",
+ "1982-10-09",
+ "1983-01-17",
+ "1983-04-27",
+ "1983-08-05",
+ "1983-11-13",
+ "1984-02-21",
+ "1984-05-31",
+ "1984-09-08",
+ "1984-12-17",
+ "1985-03-27",
+ "1985-07-05",
+ "1985-10-13",
+ "1986-01-21",
+ "1986-05-01",
+ "1986-08-09",
+ "1986-11-17",
+ "1987-02-25",
+ "1987-06-05",
+ "1987-09-13",
+ "1987-12-22",
+ "1988-03-31",
+ "1988-07-09",
+ "1988-10-17",
+ "1989-01-25",
+ "1989-05-05",
+ "1989-08-13",
+ "1989-11-21",
+ "1990-03-01",
+ "1990-06-09",
+ "1990-09-17",
+ "1990-12-26",
+ "1991-04-05",
+ "1991-07-14",
+ "1991-10-22",
+ "1992-01-30",
+ "1992-05-09",
+ "1992-08-17",
+ "1992-11-25",
+ "1993-03-05",
+ "1993-06-13",
+ "1993-09-21",
+ "1993-12-30",
+ "1994-04-09",
+ "1994-07-18",
+ "1994-10-26",
+ "1995-02-03",
+ "1995-05-14",
+ "1995-08-22",
+ "1995-11-30",
+ "1996-03-09",
+ "1996-06-17",
+ "1996-09-25",
+ "1997-01-03",
+ "1997-04-13",
+ "1997-07-22",
+ "1997-10-30",
+ "1998-02-07",
+ "1998-05-18",
+ "1998-08-26",
+ "1998-12-04",
+ "1999-03-14",
+ "1999-06-22",
+ "1999-09-30",
+ "2000-01-08",
+ "2000-07-03",
+ "2000-10-25",
+ "2001-02-02",
+ "2001-05-13",
+ "2001-08-21",
+ "2001-11-29",
+ "2002-03-09",
+ "2002-06-17",
+ "2002-09-25",
+ "2003-01-03",
+ "2003-04-13",
+ "2003-07-22",
+ "2003-10-30",
+ "2004-02-07",
+ "2004-05-17",
+ "2004-08-25",
+ "2004-12-03",
+ "2005-03-13",
+ "2005-06-21",
+ "2005-09-29",
+ "2006-01-07",
+ "2006-04-17",
+ "2006-07-26",
+ "2006-11-03",
+ "2007-02-11",
+ "2007-05-22",
+ "2007-08-30",
+ "2007-12-08",
+ "2008-03-17",
+ "2008-06-25",
+ "2008-10-03",
+ "2009-01-11",
+ "2009-04-21",
+ "2009-07-30",
+ "2009-11-07",
+ "2010-02-15",
+ "2010-05-26",
+ "2010-09-03",
+ "2010-12-12",
+ "2012-03-21",
+ "2012-06-29",
+ "2012-10-07",
+ "2013-01-15",
+ "2013-04-25",
+ "2013-08-03",
+ "2013-11-11",
+ "2014-02-19",
+ "2014-05-30",
+ "2014-09-07",
+ "2014-12-16",
+ "2015-03-26",
+ "2015-07-04",
+ "2015-10-12"
+ ],
+ "y": [
+ "8",
+ "8",
+ "16",
+ "6",
+ "-1",
+ "13",
+ "17",
+ "11",
+ "1",
+ "19",
+ "10",
+ "3",
+ "14",
+ "17",
+ "6",
+ "8",
+ "12",
+ "14",
+ "4",
+ "7",
+ "11",
+ "10",
+ "4",
+ "8",
+ "16",
+ "13",
+ "8",
+ "13",
+ "17",
+ "5",
+ "6",
+ "18",
+ "11",
+ "2",
+ "8",
+ "17",
+ "6",
+ "10",
+ "17",
+ "17",
+ "1",
+ "9",
+ "16",
+ "9",
+ "3",
+ "16",
+ "18",
+ "7",
+ "8",
+ "20",
+ "14",
+ "3",
+ "5",
+ "15",
+ "10",
+ "3",
+ "8",
+ "20",
+ "5",
+ "6",
+ "14",
+ "16",
+ "3",
+ "6",
+ "17",
+ "11",
+ "6",
+ "12",
+ "19",
+ "4",
+ "6",
+ "14",
+ "18",
+ "8",
+ "8",
+ "16",
+ "11",
+ "-4",
+ "12",
+ "18",
+ "6",
+ "9",
+ "14",
+ "15",
+ "3",
+ "5",
+ "24",
+ "4",
+ "0",
+ "17",
+ "18",
+ "8",
+ "7",
+ "12",
+ "16",
+ "-1",
+ "18",
+ "17",
+ "9",
+ "2",
+ "8",
+ "17",
+ "3",
+ "7",
+ "16",
+ "16",
+ "1",
+ "9",
+ "19",
+ "7",
+ "6",
+ "10",
+ "17",
+ "4",
+ "10",
+ "17",
+ "15",
+ "3",
+ "11",
+ "20",
+ "11",
+ "11",
+ "14",
+ "17",
+ "6",
+ "8",
+ "16",
+ "14",
+ "9",
+ "12",
+ "19",
+ "10",
+ "6",
+ "11",
+ "18",
+ "-1",
+ "5",
+ "18",
+ "9",
+ "6",
+ "11",
+ "23",
+ "8",
+ "4",
+ "14",
+ "16",
+ "3",
+ "10",
+ "18",
+ "11",
+ "6",
+ "19",
+ "19",
+ "7",
+ "9",
+ "12",
+ "17",
+ "2",
+ "8",
+ "18",
+ "9",
+ "12",
+ "10",
+ "22",
+ "7",
+ "12",
+ "17",
+ "12",
+ "7",
+ "11",
+ "20",
+ "12",
+ "10",
+ "19",
+ "18",
+ "9",
+ "13",
+ "12",
+ "13",
+ "6",
+ "8",
+ "18",
+ "12",
+ "11",
+ "13",
+ "18",
+ "3",
+ "8",
+ "13",
+ "12",
+ "5",
+ "13",
+ "10",
+ "7",
+ "13",
+ "16",
+ "6",
+ "6",
+ "14",
+ "18",
+ "10",
+ "10",
+ "23",
+ "7",
+ "6",
+ "16",
+ "16",
+ "6",
+ "10",
+ "17",
+ "16",
+ "9",
+ "8",
+ "21",
+ "12",
+ "10",
+ "13",
+ "21",
+ "2",
+ "8",
+ "17",
+ "16",
+ "8",
+ "16",
+ "26",
+ "7",
+ "8",
+ "11",
+ "21",
+ "12",
+ "5",
+ "18",
+ "16",
+ "3",
+ "14",
+ "21",
+ "11",
+ "6",
+ "15",
+ "21",
+ "9",
+ "16",
+ "24",
+ "14"
+ ],
+ "uid": "044155"
+ }
+ ],
+ "layout": {
+ "title": "range selector prototype",
+ "xaxis": {
+ "rangeselector": {
+ "buttons": [
+ {
+ "step": "month",
+ "stepmode": "backward",
+ "count": 1,
+ "label": "1m"
+ },
+ {
+ "step": "month",
+ "stepmode": "backward",
+ "count": 3,
+ "label": "3m"
+ },
+ {
+ "step": "year",
+ "stepmode": "todate",
+ "count": 1,
+ "label": "year
to date"
+ },
+ {
+ "step": "year",
+ "stepmode": "backward",
+ "count": 1,
+ "label": "1y"
+ },
+ {
+ "step": "all"
+ }
+ ]
+ },
+ "type": "date",
+ "range": [
+ -694292400000,
+ 1444622400000
+ ],
+ "autorange": true
+ },
+ "yaxis": {
+ "fixedrange": true,
+ "type": "linear",
+ "range": [
+ -5.666666666666666,
+ 27.666666666666668
+ ],
+ "autorange": true
+ },
+ "height": 450,
+ "width": 1000,
+ "autosize": true
+ }
+}
diff --git a/test/image/mocks/range_selector_style.json b/test/image/mocks/range_selector_style.json
new file mode 100644
index 00000000000..cbb3bc50a91
--- /dev/null
+++ b/test/image/mocks/range_selector_style.json
@@ -0,0 +1,1110 @@
+{
+ "data": [
+ {
+ "mode": "lines",
+ "x": [
+ "1948-01-01",
+ "1948-04-10",
+ "1948-07-19",
+ "1948-10-27",
+ "1949-02-04",
+ "1949-05-15",
+ "1949-08-23",
+ "1949-12-01",
+ "1950-03-11",
+ "1950-06-19",
+ "1950-09-27",
+ "1951-01-05",
+ "1951-04-15",
+ "1951-07-24",
+ "1951-11-01",
+ "1952-02-09",
+ "1952-05-19",
+ "1952-08-27",
+ "1952-12-05",
+ "1953-03-15",
+ "1953-06-23",
+ "1953-10-01",
+ "1954-01-09",
+ "1954-04-19",
+ "1954-07-28",
+ "1954-11-05",
+ "1955-02-13",
+ "1955-05-24",
+ "1955-09-01",
+ "1955-12-10",
+ "1956-03-19",
+ "1956-06-27",
+ "1956-10-05",
+ "1957-01-13",
+ "1957-04-23",
+ "1957-08-01",
+ "1957-11-09",
+ "1958-02-17",
+ "1958-05-28",
+ "1958-09-05",
+ "1958-12-14",
+ "1959-03-24",
+ "1959-07-02",
+ "1959-10-10",
+ "1960-01-18",
+ "1960-04-27",
+ "1960-08-05",
+ "1960-11-13",
+ "1961-02-21",
+ "1961-06-01",
+ "1961-09-09",
+ "1961-12-18",
+ "1962-03-28",
+ "1962-07-06",
+ "1962-10-14",
+ "1963-01-22",
+ "1963-05-02",
+ "1963-08-10",
+ "1963-11-18",
+ "1964-02-26",
+ "1964-06-05",
+ "1964-09-13",
+ "1964-12-22",
+ "1965-04-01",
+ "1965-07-10",
+ "1965-10-18",
+ "1966-01-26",
+ "1966-05-06",
+ "1966-08-14",
+ "1966-11-22",
+ "1967-03-02",
+ "1967-06-10",
+ "1967-09-18",
+ "1967-12-27",
+ "1968-04-05",
+ "1968-07-14",
+ "1968-10-22",
+ "1969-01-30",
+ "1969-05-10",
+ "1969-08-18",
+ "1969-11-26",
+ "1970-03-06",
+ "1970-06-14",
+ "1970-09-22",
+ "1970-12-31",
+ "1971-04-10",
+ "1971-07-19",
+ "1971-10-27",
+ "1972-02-04",
+ "1972-05-14",
+ "1972-08-22",
+ "1972-11-30",
+ "1973-03-10",
+ "1973-06-18",
+ "1973-09-26",
+ "1974-01-04",
+ "1974-04-14",
+ "1974-07-23",
+ "1974-10-31",
+ "1975-02-08",
+ "1975-05-19",
+ "1975-08-27",
+ "1975-12-05",
+ "1976-03-14",
+ "1976-06-22",
+ "1976-09-30",
+ "1977-01-08",
+ "1977-04-18",
+ "1977-07-27",
+ "1977-11-04",
+ "1978-02-12",
+ "1978-05-23",
+ "1978-08-31",
+ "1978-12-09",
+ "1979-03-19",
+ "1979-06-27",
+ "1979-10-05",
+ "1980-01-13",
+ "1980-04-22",
+ "1980-07-31",
+ "1980-11-08",
+ "1981-02-16",
+ "1981-05-27",
+ "1981-09-04",
+ "1981-12-13",
+ "1982-03-23",
+ "1982-07-01",
+ "1982-10-09",
+ "1983-01-17",
+ "1983-04-27",
+ "1983-08-05",
+ "1983-11-13",
+ "1984-02-21",
+ "1984-05-31",
+ "1984-09-08",
+ "1984-12-17",
+ "1985-03-27",
+ "1985-07-05",
+ "1985-10-13",
+ "1986-01-21",
+ "1986-05-01",
+ "1986-08-09",
+ "1986-11-17",
+ "1987-02-25",
+ "1987-06-05",
+ "1987-09-13",
+ "1987-12-22",
+ "1988-03-31",
+ "1988-07-09",
+ "1988-10-17",
+ "1989-01-25",
+ "1989-05-05",
+ "1989-08-13",
+ "1989-11-21",
+ "1990-03-01",
+ "1990-06-09",
+ "1990-09-17",
+ "1990-12-26",
+ "1991-04-05",
+ "1991-07-14",
+ "1991-10-22",
+ "1992-01-30",
+ "1992-05-09",
+ "1992-08-17",
+ "1992-11-25",
+ "1993-03-05",
+ "1993-06-13",
+ "1993-09-21",
+ "1993-12-30",
+ "1994-04-09",
+ "1994-07-18",
+ "1994-10-26",
+ "1995-02-03",
+ "1995-05-14",
+ "1995-08-22",
+ "1995-11-30",
+ "1996-03-09",
+ "1996-06-17",
+ "1996-09-25",
+ "1997-01-03",
+ "1997-04-13",
+ "1997-07-22",
+ "1997-10-30",
+ "1998-02-07",
+ "1998-05-18",
+ "1998-08-26",
+ "1998-12-04",
+ "1999-03-14",
+ "1999-06-22",
+ "1999-09-30",
+ "2000-01-08",
+ "2000-07-03",
+ "2000-10-25",
+ "2001-02-02",
+ "2001-05-13",
+ "2001-08-21",
+ "2001-11-29",
+ "2002-03-09",
+ "2002-06-17",
+ "2002-09-25",
+ "2003-01-03",
+ "2003-04-13",
+ "2003-07-22",
+ "2003-10-30",
+ "2004-02-07",
+ "2004-05-17",
+ "2004-08-25",
+ "2004-12-03",
+ "2005-03-13",
+ "2005-06-21",
+ "2005-09-29",
+ "2006-01-07",
+ "2006-04-17",
+ "2006-07-26",
+ "2006-11-03",
+ "2007-02-11",
+ "2007-05-22",
+ "2007-08-30",
+ "2007-12-08",
+ "2008-03-17",
+ "2008-06-25",
+ "2008-10-03",
+ "2009-01-11",
+ "2009-04-21",
+ "2009-07-30",
+ "2009-11-07",
+ "2010-02-15",
+ "2010-05-26",
+ "2010-09-03",
+ "2010-12-12",
+ "2012-03-21",
+ "2012-06-29",
+ "2012-10-07",
+ "2013-01-15",
+ "2013-04-25",
+ "2013-08-03",
+ "2013-11-11",
+ "2014-02-19",
+ "2014-05-30",
+ "2014-09-07",
+ "2014-12-16",
+ "2015-03-26",
+ "2015-07-04",
+ "2015-10-12"
+ ],
+ "y": [
+ "8",
+ "8",
+ "16",
+ "6",
+ "-1",
+ "13",
+ "17",
+ "11",
+ "1",
+ "19",
+ "10",
+ "3",
+ "14",
+ "17",
+ "6",
+ "8",
+ "12",
+ "14",
+ "4",
+ "7",
+ "11",
+ "10",
+ "4",
+ "8",
+ "16",
+ "13",
+ "8",
+ "13",
+ "17",
+ "5",
+ "6",
+ "18",
+ "11",
+ "2",
+ "8",
+ "17",
+ "6",
+ "10",
+ "17",
+ "17",
+ "1",
+ "9",
+ "16",
+ "9",
+ "3",
+ "16",
+ "18",
+ "7",
+ "8",
+ "20",
+ "14",
+ "3",
+ "5",
+ "15",
+ "10",
+ "3",
+ "8",
+ "20",
+ "5",
+ "6",
+ "14",
+ "16",
+ "3",
+ "6",
+ "17",
+ "11",
+ "6",
+ "12",
+ "19",
+ "4",
+ "6",
+ "14",
+ "18",
+ "8",
+ "8",
+ "16",
+ "11",
+ "-4",
+ "12",
+ "18",
+ "6",
+ "9",
+ "14",
+ "15",
+ "3",
+ "5",
+ "24",
+ "4",
+ "0",
+ "17",
+ "18",
+ "8",
+ "7",
+ "12",
+ "16",
+ "-1",
+ "18",
+ "17",
+ "9",
+ "2",
+ "8",
+ "17",
+ "3",
+ "7",
+ "16",
+ "16",
+ "1",
+ "9",
+ "19",
+ "7",
+ "6",
+ "10",
+ "17",
+ "4",
+ "10",
+ "17",
+ "15",
+ "3",
+ "11",
+ "20",
+ "11",
+ "11",
+ "14",
+ "17",
+ "6",
+ "8",
+ "16",
+ "14",
+ "9",
+ "12",
+ "19",
+ "10",
+ "6",
+ "11",
+ "18",
+ "-1",
+ "5",
+ "18",
+ "9",
+ "6",
+ "11",
+ "23",
+ "8",
+ "4",
+ "14",
+ "16",
+ "3",
+ "10",
+ "18",
+ "11",
+ "6",
+ "19",
+ "19",
+ "7",
+ "9",
+ "12",
+ "17",
+ "2",
+ "8",
+ "18",
+ "9",
+ "12",
+ "10",
+ "22",
+ "7",
+ "12",
+ "17",
+ "12",
+ "7",
+ "11",
+ "20",
+ "12",
+ "10",
+ "19",
+ "18",
+ "9",
+ "13",
+ "12",
+ "13",
+ "6",
+ "8",
+ "18",
+ "12",
+ "11",
+ "13",
+ "18",
+ "3",
+ "8",
+ "13",
+ "12",
+ "5",
+ "13",
+ "10",
+ "7",
+ "13",
+ "16",
+ "6",
+ "6",
+ "14",
+ "18",
+ "10",
+ "10",
+ "23",
+ "7",
+ "6",
+ "16",
+ "16",
+ "6",
+ "10",
+ "17",
+ "16",
+ "9",
+ "8",
+ "21",
+ "12",
+ "10",
+ "13",
+ "21",
+ "2",
+ "8",
+ "17",
+ "16",
+ "8",
+ "16",
+ "26",
+ "7",
+ "8",
+ "11",
+ "21",
+ "12",
+ "5",
+ "18",
+ "16",
+ "3",
+ "14",
+ "21",
+ "11",
+ "6",
+ "15",
+ "21",
+ "9",
+ "16",
+ "24",
+ "14"
+ ],
+ "name": "raw",
+ "uid": "3154a7"
+ },
+ {
+ "mode": "lines",
+ "line": {
+ "dash": "dash"
+ },
+ "x": [
+ "1948-01-01",
+ "1948-04-10",
+ "1948-07-19",
+ "1948-10-27",
+ "1949-02-04",
+ "1949-05-15",
+ "1949-08-23",
+ "1949-12-01",
+ "1950-03-11",
+ "1950-06-19",
+ "1950-09-27",
+ "1951-01-05",
+ "1951-04-15",
+ "1951-07-24",
+ "1951-11-01",
+ "1952-02-09",
+ "1952-05-19",
+ "1952-08-27",
+ "1952-12-05",
+ "1953-03-15",
+ "1953-06-23",
+ "1953-10-01",
+ "1954-01-09",
+ "1954-04-19",
+ "1954-07-28",
+ "1954-11-05",
+ "1955-02-13",
+ "1955-05-24",
+ "1955-09-01",
+ "1955-12-10",
+ "1956-03-19",
+ "1956-06-27",
+ "1956-10-05",
+ "1957-01-13",
+ "1957-04-23",
+ "1957-08-01",
+ "1957-11-09",
+ "1958-02-17",
+ "1958-05-28",
+ "1958-09-05",
+ "1958-12-14",
+ "1959-03-24",
+ "1959-07-02",
+ "1959-10-10",
+ "1960-01-18",
+ "1960-04-27",
+ "1960-08-05",
+ "1960-11-13",
+ "1961-02-21",
+ "1961-06-01",
+ "1961-09-09",
+ "1961-12-18",
+ "1962-03-28",
+ "1962-07-06",
+ "1962-10-14",
+ "1963-01-22",
+ "1963-05-02",
+ "1963-08-10",
+ "1963-11-18",
+ "1964-02-26",
+ "1964-06-05",
+ "1964-09-13",
+ "1964-12-22",
+ "1965-04-01",
+ "1965-07-10",
+ "1965-10-18",
+ "1966-01-26",
+ "1966-05-06",
+ "1966-08-14",
+ "1966-11-22",
+ "1967-03-02",
+ "1967-06-10",
+ "1967-09-18",
+ "1967-12-27",
+ "1968-04-05",
+ "1968-07-14",
+ "1968-10-22",
+ "1969-01-30",
+ "1969-05-10",
+ "1969-08-18",
+ "1969-11-26",
+ "1970-03-06",
+ "1970-06-14",
+ "1970-09-22",
+ "1970-12-31",
+ "1971-04-10",
+ "1971-07-19",
+ "1971-10-27",
+ "1972-02-04",
+ "1972-05-14",
+ "1972-08-22",
+ "1972-11-30",
+ "1973-03-10",
+ "1973-06-18",
+ "1973-09-26",
+ "1974-01-04",
+ "1974-04-14",
+ "1974-07-23",
+ "1974-10-31",
+ "1975-02-08",
+ "1975-05-19",
+ "1975-08-27",
+ "1975-12-05",
+ "1976-03-14",
+ "1976-06-22",
+ "1976-09-30",
+ "1977-01-08",
+ "1977-04-18",
+ "1977-07-27",
+ "1977-11-04",
+ "1978-02-12",
+ "1978-05-23",
+ "1978-08-31",
+ "1978-12-09",
+ "1979-03-19",
+ "1979-06-27",
+ "1979-10-05",
+ "1980-01-13",
+ "1980-04-22",
+ "1980-07-31",
+ "1980-11-08",
+ "1981-02-16",
+ "1981-05-27",
+ "1981-09-04",
+ "1981-12-13",
+ "1982-03-23",
+ "1982-07-01",
+ "1982-10-09",
+ "1983-01-17",
+ "1983-04-27",
+ "1983-08-05",
+ "1983-11-13",
+ "1984-02-21",
+ "1984-05-31",
+ "1984-09-08",
+ "1984-12-17",
+ "1985-03-27",
+ "1985-07-05",
+ "1985-10-13",
+ "1986-01-21",
+ "1986-05-01",
+ "1986-08-09",
+ "1986-11-17",
+ "1987-02-25",
+ "1987-06-05",
+ "1987-09-13",
+ "1987-12-22",
+ "1988-03-31",
+ "1988-07-09",
+ "1988-10-17",
+ "1989-01-25",
+ "1989-05-05",
+ "1989-08-13",
+ "1989-11-21",
+ "1990-03-01",
+ "1990-06-09",
+ "1990-09-17",
+ "1990-12-26",
+ "1991-04-05",
+ "1991-07-14",
+ "1991-10-22",
+ "1992-01-30",
+ "1992-05-09",
+ "1992-08-17",
+ "1992-11-25",
+ "1993-03-05",
+ "1993-06-13",
+ "1993-09-21",
+ "1993-12-30",
+ "1994-04-09",
+ "1994-07-18",
+ "1994-10-26",
+ "1995-02-03",
+ "1995-05-14",
+ "1995-08-22",
+ "1995-11-30",
+ "1996-03-09",
+ "1996-06-17",
+ "1996-09-25",
+ "1997-01-03",
+ "1997-04-13",
+ "1997-07-22",
+ "1997-10-30",
+ "1998-02-07",
+ "1998-05-18",
+ "1998-08-26",
+ "1998-12-04",
+ "1999-03-14",
+ "1999-06-22",
+ "1999-09-30",
+ "2000-01-08",
+ "2000-07-03",
+ "2000-10-25",
+ "2001-02-02",
+ "2001-05-13",
+ "2001-08-21",
+ "2001-11-29",
+ "2002-03-09",
+ "2002-06-17",
+ "2002-09-25",
+ "2003-01-03",
+ "2003-04-13",
+ "2003-07-22",
+ "2003-10-30",
+ "2004-02-07",
+ "2004-05-17",
+ "2004-08-25",
+ "2004-12-03",
+ "2005-03-13",
+ "2005-06-21",
+ "2005-09-29",
+ "2006-01-07",
+ "2006-04-17",
+ "2006-07-26",
+ "2006-11-03",
+ "2007-02-11",
+ "2007-05-22",
+ "2007-08-30",
+ "2007-12-08",
+ "2008-03-17",
+ "2008-06-25",
+ "2008-10-03",
+ "2009-01-11",
+ "2009-04-21",
+ "2009-07-30",
+ "2009-11-07",
+ "2010-02-15",
+ "2010-05-26",
+ "2010-09-03",
+ "2010-12-12",
+ "2012-03-21",
+ "2012-06-29",
+ "2012-10-07",
+ "2013-01-15",
+ "2013-04-25",
+ "2013-08-03",
+ "2013-11-11",
+ "2014-02-19",
+ "2014-05-30",
+ "2014-09-07",
+ "2014-12-16",
+ "2015-03-26",
+ "2015-07-04",
+ "2015-10-12"
+ ],
+ "y": [
+ -3.25,
+ -3.25,
+ 4.75,
+ -5.25,
+ -12.25,
+ 1.75,
+ 5.75,
+ -0.25,
+ -10.25,
+ 7.75,
+ -1.25,
+ -8.25,
+ 2.75,
+ 5.75,
+ -5.25,
+ -3.25,
+ 0.75,
+ 2.75,
+ -7.25,
+ -4.25,
+ -0.25,
+ -1.25,
+ -7.25,
+ -3.25,
+ 4.75,
+ 1.75,
+ -3.25,
+ 1.75,
+ 5.75,
+ -6.25,
+ -5.25,
+ 6.75,
+ -0.25,
+ -9.25,
+ -3.25,
+ 5.75,
+ -5.25,
+ -1.25,
+ 5.75,
+ 5.75,
+ -10.25,
+ -2.25,
+ 4.75,
+ -2.25,
+ -8.25,
+ 4.75,
+ 6.75,
+ -4.25,
+ -3.25,
+ 8.75,
+ 2.75,
+ -8.25,
+ -6.25,
+ 3.75,
+ -1.25,
+ -8.25,
+ -3.25,
+ 8.75,
+ -6.25,
+ -5.25,
+ 2.75,
+ 4.75,
+ -8.25,
+ -5.25,
+ 5.75,
+ -0.25,
+ -5.25,
+ 0.75,
+ 7.75,
+ -7.25,
+ -5.25,
+ 2.75,
+ 6.75,
+ -3.25,
+ -3.25,
+ 4.75,
+ -0.25,
+ -15.25,
+ 0.75,
+ 6.75,
+ -5.25,
+ -2.25,
+ 2.75,
+ 3.75,
+ -8.25,
+ -6.25,
+ 12.75,
+ -7.25,
+ -11.25,
+ 5.75,
+ 6.75,
+ -3.25,
+ -4.25,
+ 0.75,
+ 4.75,
+ -12.25,
+ 6.75,
+ 5.75,
+ -2.25,
+ -9.25,
+ -3.25,
+ 5.75,
+ -8.25,
+ -4.25,
+ 4.75,
+ 4.75,
+ -10.25,
+ -2.25,
+ 7.75,
+ -4.25,
+ -5.25,
+ -1.25,
+ 5.75,
+ -7.25,
+ -1.25,
+ 5.75,
+ 3.75,
+ -8.25,
+ -0.25,
+ 8.75,
+ -0.25,
+ -0.25,
+ 2.75,
+ 5.75,
+ -5.25,
+ -3.25,
+ 4.75,
+ 2.75,
+ -2.25,
+ 0.75,
+ 7.75,
+ -1.25,
+ -5.25,
+ -0.25,
+ 6.75,
+ -12.25,
+ -6.25,
+ 6.75,
+ -2.25,
+ -5.25,
+ -0.25,
+ 11.75,
+ -3.25,
+ -7.25,
+ 2.75,
+ 4.75,
+ -8.25,
+ -1.25,
+ 6.75,
+ -0.25,
+ -5.25,
+ 7.75,
+ 7.75,
+ -4.25,
+ -2.25,
+ 0.75,
+ 5.75,
+ -9.25,
+ -3.25,
+ 6.75,
+ -2.25,
+ 0.75,
+ -1.25,
+ 10.75,
+ -4.25,
+ 0.75,
+ 5.75,
+ 0.75,
+ -4.25,
+ -0.25,
+ 8.75,
+ 0.75,
+ -1.25,
+ 7.75,
+ 6.75,
+ -2.25,
+ 1.75,
+ 0.75,
+ 1.75,
+ -5.25,
+ -3.25,
+ 6.75,
+ 0.75,
+ -0.25,
+ 1.75,
+ 6.75,
+ -8.25,
+ -3.25,
+ 1.75,
+ 0.75,
+ -6.25,
+ 1.75,
+ -1.25,
+ -4.25,
+ 1.75,
+ 4.75,
+ -5.25,
+ -5.25,
+ 2.75,
+ 6.75,
+ -1.25,
+ -1.25,
+ 11.75,
+ -4.25,
+ -5.25,
+ 4.75,
+ 4.75,
+ -5.25,
+ -1.25,
+ 5.75,
+ 4.75,
+ -2.25,
+ -3.25,
+ 9.75,
+ 0.75,
+ -1.25,
+ 1.75,
+ 9.75,
+ -9.25,
+ -3.25,
+ 5.75,
+ 4.75,
+ -3.25,
+ 4.75,
+ 14.75,
+ -4.25,
+ -3.25,
+ -0.25,
+ 9.75,
+ 0.75,
+ -6.25,
+ 6.75,
+ 4.75,
+ -8.25,
+ 2.75,
+ 9.75,
+ -0.25,
+ -5.25,
+ 3.75,
+ 9.75,
+ -2.25,
+ 4.75,
+ 12.75,
+ 2.75
+ ],
+ "xaxis": "x2",
+ "yaxis": "y2",
+ "name": "dev from mean",
+ "uid": "d16487"
+ }
+ ],
+ "layout": {
+ "xaxis": {
+ "rangeselector": {
+ "buttons": [
+ {
+ "step": "month",
+ "stepmode": "backward",
+ "count": 1,
+ "label": "1m"
+ },
+ {
+ "step": "year",
+ "stepmode": "todate",
+ "count": 1,
+ "label": "year
to date"
+ },
+ {
+ "step": "all"
+ }
+ ],
+ "x": 1.05,
+ "xanchor": "left",
+ "y": 0.2,
+ "yanchor": "bottom",
+ "bgcolor": "#d3d3d3",
+ "borderwidth": 2,
+ "bordercolor": "#ff7f0e"
+ },
+ "domain": [
+ 0.52,
+ 1
+ ],
+ "type": "date",
+ "range": [
+ -694292400000,
+ 1444622400000
+ ],
+ "autorange": true
+ },
+ "yaxis": {
+ "fixedrange": true,
+ "domain": [
+ 0,
+ 0.35
+ ],
+ "type": "linear",
+ "range": [
+ -5.666666666666667,
+ 27.666666666666668
+ ],
+ "autorange": true
+ },
+ "xaxis2": {
+ "anchor": "y2",
+ "rangeselector": {
+ "buttons": [
+ {},
+ {
+ "step": "year",
+ "stepmode": "todate",
+ "count": 1,
+ "label": "year
to
date"
+ },
+ {
+ "step": "all",
+ "label": "BACK TO START"
+ }
+ ],
+ "x": 0.3,
+ "xanchor": "right",
+ "y": 0.3,
+ "yanchor": "top",
+ "font": {
+ "color": "blue",
+ "size": 14,
+ "family": "Raleway, sans-serif"
+ }
+ },
+ "range": [
+ 1420088400000,
+ 1444622400000
+ ],
+ "type": "date"
+ },
+ "yaxis2": {
+ "anchor": "x2",
+ "fixedrange": true,
+ "domain": [
+ 0.6,
+ 1
+ ],
+ "type": "linear",
+ "range": [
+ -16.916666666666668,
+ 16.416666666666668
+ ],
+ "autorange": true
+ },
+ "legend": {
+ "x": 0,
+ "y": 1.02,
+ "yanchor": "bottom"
+ },
+ "height": 450,
+ "width": 1000,
+ "autosize": true
+ }
+}
diff --git a/test/jasmine/tests/plotschema_test.js b/test/jasmine/tests/plotschema_test.js
index 9e7a9fcfc0d..b57be39ea3a 100644
--- a/test/jasmine/tests/plotschema_test.js
+++ b/test/jasmine/tests/plotschema_test.js
@@ -1,6 +1,7 @@
var Plotly = require('@lib/index');
var Lib = require('@src/lib');
+
describe('plot schema', function() {
'use strict';
@@ -112,17 +113,32 @@ describe('plot schema', function() {
expect(list).toEqual(astrs);
});
- it('layout.annotations and layout.shapes should contain `items`', function() {
- var astrs = ['annotations', 'shapes'];
+ it('should convert _isLinkedToArray attributes to items object', function() {
+ var astrs = [
+ 'annotations', 'shapes',
+ 'xaxis.rangeselector.buttons', 'yaxis.rangeselector.buttons'
+ ];
astrs.forEach(function(astr) {
- expect(
- isPlainObject(
- Lib.nestedProperty(
- plotSchema.layout.layoutAttributes, astr
- ).get().items
- )
- ).toBe(true);
+ var np = Lib.nestedProperty(
+ plotSchema.layout.layoutAttributes, astr
+ );
+
+ var name = np.parts[np.parts.length - 1],
+ itemName = name.substr(0, name.length - 1);
+
+ var itemsObj = np.get().items,
+ itemObj = itemsObj[itemName];
+
+ // N.B. the specs below must be satisfied for plotly.py
+ expect(isPlainObject(itemsObj)).toBe(true);
+ expect(itemsObj.role).toBeUndefined();
+ expect(Object.keys(itemsObj).length).toEqual(1);
+ expect(isPlainObject(itemObj)).toBe(true);
+ expect(itemObj.role).toBe('object');
+
+ var role = np.get().role;
+ expect(role).toEqual('object');
});
});
@@ -158,4 +174,5 @@ describe('plot schema', function() {
}
);
});
+
});
diff --git a/test/jasmine/tests/range_selector_test.js b/test/jasmine/tests/range_selector_test.js
new file mode 100644
index 00000000000..ebe8bc5d802
--- /dev/null
+++ b/test/jasmine/tests/range_selector_test.js
@@ -0,0 +1,485 @@
+var RangeSelector = require('@src/components/rangeselector');
+var getUpdateObject = require('@src/components/rangeselector/get_update_object');
+var constants = require('@src/components/rangeselector/constants');
+
+var d3 = require('d3');
+var Plotly = require('@lib');
+var Lib = require('@src/lib');
+var Color = require('@src/components/color');
+var createGraphDiv = require('../assets/create_graph_div');
+var destroyGraphDiv = require('../assets/destroy_graph_div');
+var getRectCenter = require('../assets/get_rect_center');
+var mouseEvent = require('../assets/mouse_event');
+
+
+describe('[range selector suite]', function() {
+ 'use strict';
+
+ describe('defaults:', function() {
+ var supplyLayoutDefaults = RangeSelector.supplyLayoutDefaults;
+
+ function supply(containerIn, containerOut) {
+ containerOut.domain = [0, 1];
+
+ var layout = {
+ yaxis: { domain: [0, 1] }
+ };
+
+ var counterAxes = ['yaxis'];
+
+ supplyLayoutDefaults(containerIn, containerOut, layout, counterAxes);
+ }
+
+ it('should set \'visible\' to false when no buttons are present', function() {
+ var containerIn = {};
+ var containerOut = {};
+
+ supply(containerIn, containerOut);
+
+ expect(containerOut.rangeselector)
+ .toEqual({
+ visible: false,
+ buttons: []
+ });
+ });
+
+ it('should coerce an empty button object', function() {
+ var containerIn = {
+ rangeselector: {
+ buttons: [{}]
+ }
+ };
+ var containerOut = {};
+
+ supply(containerIn, containerOut);
+
+ expect(containerIn.rangeselector.buttons).toEqual([{}]);
+ expect(containerOut.rangeselector.buttons).toEqual([{
+ step: 'month',
+ stepmode: 'backward',
+ count: 1
+ }]);
+ });
+
+ it('should coerce all buttons present', function() {
+ var containerIn = {
+ rangeselector: {
+ buttons: [{
+ step: 'year',
+ count: 10
+ },{
+ count: 6
+ }]
+ }
+ };
+ var containerOut = {};
+
+ supply(containerIn, containerOut, {}, []);
+
+ expect(containerOut.rangeselector.visible).toBe(true);
+ expect(containerOut.rangeselector.buttons).toEqual([
+ { step: 'year', stepmode: 'backward', count: 10 },
+ { step: 'month', stepmode: 'backward', count: 6 }
+ ]);
+ });
+
+ it('should not coerce \'stepmode\' and \'count\', for \'step\' all buttons', function() {
+ var containerIn = {
+ rangeselector: {
+ buttons: [{
+ step: 'all',
+ label: 'full range'
+ }]
+ }
+ };
+ var containerOut = {};
+
+ supply(containerIn, containerOut, {}, []);
+
+ expect(containerOut.rangeselector.buttons).toEqual([{
+ step: 'all',
+ label: 'full range'
+ }]);
+ });
+
+ it('should use axis and counter axis to determine \'x\' and \'y\' defaults (case 1 y)', function() {
+ var containerIn = {
+ rangeselector: { buttons: [{}] }
+ };
+ var containerOut = {
+ _id: 'x',
+ domain: [0, 0.5]
+ };
+ var layout = {
+ xaxis: containerIn,
+ yaxis: {
+ anchor: 'x',
+ domain: [0, 0.45]
+ }
+ };
+ var counterAxes = ['yaxis'];
+
+ supplyLayoutDefaults(containerIn, containerOut, layout, counterAxes);
+
+ expect(containerOut.rangeselector.x).toEqual(0);
+ expect(containerOut.rangeselector.y).toBeCloseTo(0.47);
+ });
+
+ it('should use axis and counter axis to determine \'x\' and \'y\' defaults (case multi y)', function() {
+ var containerIn = {
+ rangeselector: { buttons: [{}] }
+ };
+ var containerOut = {
+ _id: 'x',
+ domain: [0.5, 1]
+ };
+ var layout = {
+ xaxis: containerIn,
+ yaxis: {
+ anchor: 'x',
+ domain: [0, 0.25]
+ },
+ yaxis2: {
+ anchor: 'x',
+ domain: [0.3, 0.55]
+ },
+ yaxis3: {
+ anchor: 'x',
+ domain: [0.6, 0.85]
+ }
+ };
+ var counterAxes = ['yaxis', 'yaxis2', 'yaxis3'];
+
+ supplyLayoutDefaults(containerIn, containerOut, layout, counterAxes);
+
+ expect(containerOut.rangeselector.x).toEqual(0.5);
+ expect(containerOut.rangeselector.y).toBeCloseTo(0.87);
+ });
+ });
+
+ describe('getUpdateObject:', function() {
+ var axisLayout = {
+ _name: 'xaxis',
+ range: [
+ (new Date(1948, 0, 1)).getTime(),
+ (new Date(2015, 10, 30)).getTime()
+ ]
+ };
+
+ function assertRanges(update, range0, range1) {
+ expect(update['xaxis.range[0]']).toEqual(range0.getTime());
+ expect(update['xaxis.range[1]']).toEqual(range1.getTime());
+ }
+
+ it('should return update object (1 month backward case)', function() {
+ var buttonLayout = {
+ step: 'month',
+ stepmode: 'backward',
+ count: 1
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 9, 30), new Date(2015, 10, 30));
+ });
+
+ it('should return update object (3 months backward case)', function() {
+ var buttonLayout = {
+ step: 'month',
+ stepmode: 'backward',
+ count: 3
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 7, 30), new Date(2015, 10, 30));
+ });
+
+ it('should return update object (6 months backward case)', function() {
+ var buttonLayout = {
+ step: 'month',
+ stepmode: 'backward',
+ count: 6
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 4, 30), new Date(2015, 10, 30));
+ });
+
+ it('should return update object (5 months to-date case)', function() {
+ var buttonLayout = {
+ step: 'month',
+ stepmode: 'todate',
+ count: 5
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 6, 1), new Date(2015, 10, 30));
+ });
+
+ it('should return update object (1 year to-date case)', function() {
+ var buttonLayout = {
+ step: 'year',
+ stepmode: 'todate',
+ count: 1
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 0, 1), new Date(2015, 10, 30));
+ });
+
+ it('should return update object (10 year to-date case)', function() {
+ var buttonLayout = {
+ step: 'year',
+ stepmode: 'todate',
+ count: 10
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2006, 0, 1), new Date(2015, 10, 30));
+ });
+
+ it('should return update object (1 year backward case)', function() {
+ var buttonLayout = {
+ step: 'year',
+ stepmode: 'backward',
+ count: 1
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2014, 10, 30), new Date(2015, 10, 30));
+ });
+
+ it('should return update object (reset case)', function() {
+ var buttonLayout = {
+ step: 'all'
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ expect(update).toEqual({ 'xaxis.autorange': true });
+ });
+
+ it('should return update object (10 day backward case)', function() {
+ var buttonLayout = {
+ step: 'day',
+ stepmode: 'backward',
+ count: 10
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 10, 20), new Date(2015, 10, 30));
+ });
+
+ it('should return update object (5 hour backward case)', function() {
+ var buttonLayout = {
+ step: 'hour',
+ stepmode: 'backward',
+ count: 5
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 10, 29, 19), new Date(2015, 10, 30));
+ });
+
+ it('should return update object (15 minute backward case)', function() {
+ var buttonLayout = {
+ step: 'minute',
+ stepmode: 'backward',
+ count: 15
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 10, 29, 23, 45), new Date(2015, 10, 30));
+ });
+
+ it('should return update object (10 second backward case)', function() {
+ var buttonLayout = {
+ step: 'second',
+ stepmode: 'backward',
+ count: 10
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 10, 29, 23, 59, 50), new Date(2015, 10, 30));
+ });
+
+ it('should return update object (12 hour to-date case)', function() {
+ var buttonLayout = {
+ step: 'hour',
+ stepmode: 'todate',
+ count: 12
+ };
+
+ axisLayout.range[1] = new Date(2015, 10, 30, 12).getTime();
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 10, 30, 1), new Date(2015, 10, 30, 12));
+ });
+
+ it('should return update object (15 minute backward case)', function() {
+ var buttonLayout = {
+ step: 'minute',
+ stepmode: 'todate',
+ count: 20
+ };
+
+ axisLayout.range[1] = new Date(2015, 10, 30, 12, 20).getTime();
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 10, 30, 12, 1), new Date(2015, 10, 30, 12, 20));
+ });
+
+ it('should return update object (2 second to-date case)', function() {
+ var buttonLayout = {
+ step: 'second',
+ stepmode: 'todate',
+ count: 2
+ };
+
+ axisLayout.range[1] = new Date(2015, 10, 30, 12, 20, 2).getTime();
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ assertRanges(update, new Date(2015, 10, 30, 12, 20, 1), new Date(2015, 10, 30, 12, 20, 2));
+ });
+
+ it('should return update object with correct axis names', function() {
+ var axisLayout = {
+ _name: 'xaxis5',
+ range: [
+ (new Date(1948, 0, 1)).getTime(),
+ (new Date(2015, 10, 30)).getTime()
+ ]
+ };
+
+ var buttonLayout = {
+ step: 'month',
+ stepmode: 'backward',
+ count: 1
+ };
+
+ var update = getUpdateObject(axisLayout, buttonLayout);
+
+ expect(update).toEqual({
+ 'xaxis5.range[0]': new Date(2015, 9, 30).getTime(),
+ 'xaxis5.range[1]': new Date(2015, 10, 30).getTime()
+ });
+
+ });
+ });
+
+ describe('interactions:', function() {
+ var mock = require('@mocks/range_selector.json');
+
+ var gd, mockCopy;
+
+ beforeEach(function(done) {
+ gd = createGraphDiv();
+ mockCopy = Lib.extendDeep({}, mock);
+
+ Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done);
+ });
+
+ afterEach(destroyGraphDiv);
+
+ function assertNodeCount(query, cnt) {
+ expect(d3.selectAll(query).size()).toEqual(cnt);
+ }
+
+ function checkActiveButton(activeIndex) {
+ d3.selectAll('.button').each(function(d, i) {
+ expect(d.isActive).toBe(activeIndex === i);
+ });
+ }
+
+ it('should display the correct nodes', function() {
+ assertNodeCount('.rangeselector', 1);
+ assertNodeCount('.button', mockCopy.layout.xaxis.rangeselector.buttons.length);
+ });
+
+ it('should be able to be removed by `relayout`', function(done) {
+ Plotly.relayout(gd, 'xaxis.rangeselector.visible', false).then(function() {
+ assertNodeCount('.rangeselector', 0);
+ assertNodeCount('.button', 0);
+ done();
+ });
+
+ });
+
+ it('should update range and active button when clicked', function() {
+ var range0 = gd.layout.xaxis.range[0];
+ var buttons = d3.selectAll('.button').select('rect');
+
+ checkActiveButton(buttons.size() - 1);
+
+ var pos0 = getRectCenter(buttons[0][0]);
+ var posReset = getRectCenter(buttons[0][buttons.size()-1]);
+
+ mouseEvent('click', pos0[0], pos0[1]);
+ expect(gd.layout.xaxis.range[0]).toBeGreaterThan(range0);
+
+ checkActiveButton(0);
+
+ mouseEvent('click', posReset[0], posReset[1]);
+ expect(gd.layout.xaxis.range[0]).toEqual(range0);
+
+ checkActiveButton(buttons.size() - 1);
+ });
+
+ it('should change color on mouse over', function() {
+ var button = d3.select('.button').select('rect');
+ var pos = getRectCenter(button.node());
+
+ var fillColor = Color.rgb(gd._fullLayout.xaxis.rangeselector.bgcolor);
+ var activeColor = Color.rgb(constants.activeColor);
+
+ expect(button.style('fill')).toEqual(fillColor);
+
+ mouseEvent('mouseover', pos[0], pos[1]);
+ expect(button.style('fill')).toEqual(activeColor);
+
+ mouseEvent('mouseout', pos[0], pos[1]);
+ expect(button.style('fill')).toEqual(fillColor);
+ });
+
+ it('should update is active relayout calls', function(done) {
+ var buttons = d3.selectAll('.button').select('rect');
+
+ // 'all' should be active at first
+ checkActiveButton(buttons.size() - 1);
+
+ var update = {
+ 'xaxis.range[0]': (new Date(2015, 9, 30)).getTime(),
+ 'xaxis.range[1]': (new Date(2015, 10, 30)).getTime()
+ };
+
+ Plotly.relayout(gd, update).then(function() {
+
+ // '1m' should be active after the relayout
+ checkActiveButton(0);
+
+ return Plotly.relayout(gd, 'xaxis.autorange', true);
+ }).then(function() {
+
+ // 'all' should be after an autoscale
+ checkActiveButton(buttons.size() - 1);
+
+ done();
+ });
+ });
+
+ });
+
+});