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(); + }); + }); + + }); + +});