diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js
index a57a0038248..0aa03245154 100644
--- a/src/components/dragelement/index.js
+++ b/src/components/dragelement/index.js
@@ -177,6 +177,8 @@ function coverSlip() {
return cover;
}
+dragElement.coverSlip = coverSlip;
+
function finishDrag(gd) {
gd._dragging = false;
if(gd._replotPending) Plotly.plot(gd);
diff --git a/src/components/rangeslider/constants.js b/src/components/rangeslider/constants.js
new file mode 100644
index 00000000000..0a60e7b33b3
--- /dev/null
+++ b/src/components/rangeslider/constants.js
@@ -0,0 +1,51 @@
+/**
+* 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 = {
+
+ // attribute container name
+ name: 'rangeslider',
+
+ // class names
+
+ containerClassName: 'rangeslider-container',
+ bgClassName: 'rangeslider-bg',
+ rangePlotClassName: 'rangeslider-rangeplot',
+
+ maskMinClassName: 'rangeslider-mask-min',
+ maskMaxClassName: 'rangeslider-mask-max',
+ slideBoxClassName: 'rangeslider-slidebox',
+
+ grabberMinClassName: 'rangeslider-grabber-min',
+ grabAreaMinClassName: 'rangeslider-grabarea-min',
+ handleMinClassName: 'rangeslider-handle-min',
+
+ grabberMaxClassName: 'rangeslider-grabber-max',
+ grabAreaMaxClassName: 'rangeslider-grabarea-max',
+ handleMaxClassName: 'rangeslider-handle-max',
+
+ // style constants
+
+ maskColor: 'rgba(0,0,0,0.4)',
+
+ slideBoxFill: 'transparent',
+ slideBoxCursor: 'ew-resize',
+
+ grabAreaFill: 'transparent',
+ grabAreaCursor: 'col-resize',
+ grabAreaWidth: 10,
+ grabAreaMinOffset: -6,
+ grabAreaMaxOffset: -2,
+
+ handleWidth: 2,
+ handleRadius: 1,
+ handleFill: '#fff',
+ handleStroke: '#666',
+};
diff --git a/src/components/rangeslider/create_slider.js b/src/components/rangeslider/create_slider.js
deleted file mode 100644
index 9256fa31d4d..00000000000
--- a/src/components/rangeslider/create_slider.js
+++ /dev/null
@@ -1,287 +0,0 @@
-/**
-* 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 Plotly = require('../../plotly');
-var Axes = require('../../plots/cartesian/axes');
-var Lib = require('../../lib');
-
-var svgNS = require('../../constants/xmlns_namespaces').svg;
-
-var helpers = require('./helpers');
-var rangePlot = require('./range_plot');
-
-
-module.exports = function createSlider(gd) {
- var fullLayout = gd._fullLayout,
- sliderContainer = fullLayout._infolayer.selectAll('g.range-slider'),
- options = fullLayout.xaxis.rangeslider,
- width = fullLayout._size.w,
- height = (fullLayout.height - fullLayout.margin.b - fullLayout.margin.t) * options.thickness,
- handleWidth = 2,
- offsetShift = Math.floor(options.borderwidth / 2),
- x = fullLayout.margin.l,
- y = fullLayout.height - height - fullLayout.margin.b;
-
- var minStart = 0,
- maxStart = width;
-
- var slider = document.createElementNS(svgNS, 'g');
- helpers.setAttributes(slider, {
- 'class': 'range-slider',
- 'data-min': minStart,
- 'data-max': maxStart,
- 'pointer-events': 'all',
- 'transform': 'translate(' + x + ',' + y + ')'
- });
-
-
- var sliderBg = document.createElementNS(svgNS, 'rect'),
- borderCorrect = options.borderwidth % 2 === 0 ? options.borderwidth : options.borderwidth - 1;
- helpers.setAttributes(sliderBg, {
- 'fill': options.bgcolor,
- 'stroke': options.bordercolor,
- 'stroke-width': options.borderwidth,
- 'height': height + borderCorrect,
- 'width': width + borderCorrect,
- 'transform': 'translate(-' + offsetShift + ', -' + offsetShift + ')',
- 'shape-rendering': 'crispEdges'
- });
-
-
- var maskMin = document.createElementNS(svgNS, 'rect');
- helpers.setAttributes(maskMin, {
- 'x': 0,
- 'width': minStart,
- 'height': height,
- 'fill': 'rgba(0,0,0,0.4)'
- });
-
-
- var maskMax = document.createElementNS(svgNS, 'rect');
- helpers.setAttributes(maskMax, {
- 'x': maxStart,
- 'width': width - maxStart,
- 'height': height,
- 'fill': 'rgba(0,0,0,0.4)'
- });
-
-
- var grabberMin = document.createElementNS(svgNS, 'g'),
- grabAreaMin = document.createElementNS(svgNS, 'rect'),
- handleMin = document.createElementNS(svgNS, 'rect');
- helpers.setAttributes(grabberMin, { 'transform': 'translate(' + (minStart - handleWidth - 1) + ')' });
- helpers.setAttributes(grabAreaMin, {
- 'width': 10,
- 'height': height,
- 'x': -6,
- 'fill': 'transparent',
- 'cursor': 'col-resize'
- });
- helpers.setAttributes(handleMin, {
- 'width': handleWidth,
- 'height': height / 2,
- 'y': height / 4,
- 'rx': 1,
- 'fill': 'white',
- 'stroke': '#666',
- 'shape-rendering': 'crispEdges'
- });
- helpers.appendChildren(grabberMin, [handleMin, grabAreaMin]);
-
-
- var grabberMax = document.createElementNS(svgNS, 'g'),
- grabAreaMax = document.createElementNS(svgNS, 'rect'),
- handleMax = document.createElementNS(svgNS, 'rect');
- helpers.setAttributes(grabberMax, { 'transform': 'translate(' + maxStart + ')' });
- helpers.setAttributes(grabAreaMax, {
- 'width': 10,
- 'height': height,
- 'x': -2,
- 'fill': 'transparent',
- 'cursor': 'col-resize'
- });
- helpers.setAttributes(handleMax, {
- 'width': handleWidth,
- 'height': height / 2,
- 'y': height / 4,
- 'rx': 1,
- 'fill': 'white',
- 'stroke': '#666',
- 'shape-rendering': 'crispEdges'
- });
- helpers.appendChildren(grabberMax, [handleMax, grabAreaMax]);
-
-
- var slideBox = document.createElementNS(svgNS, 'rect');
- helpers.setAttributes(slideBox, {
- 'x': minStart,
- 'width': maxStart - minStart,
- 'height': height,
- 'cursor': 'ew-resize',
- 'fill': 'transparent'
- });
-
-
- slider.addEventListener('mousedown', function(event) {
- var target = event.target,
- startX = event.clientX,
- offsetX = startX - slider.getBoundingClientRect().left,
- minVal = slider.getAttribute('data-min'),
- maxVal = slider.getAttribute('data-max');
-
- window.addEventListener('mousemove', mouseMove);
- window.addEventListener('mouseup', mouseUp);
-
- function mouseMove(e) {
- var delta = +e.clientX - startX,
- pixelMin,
- pixelMax;
-
- switch(target) {
- case slideBox:
- slider.style.cursor = 'ew-resize';
-
- pixelMin = +minVal + delta;
- pixelMax = +maxVal + delta;
-
- setPixelRange(pixelMin, pixelMax);
- setDataRange(pixelToData(pixelMin), pixelToData(pixelMax));
- break;
-
- case grabAreaMin:
- slider.style.cursor = 'col-resize';
-
- pixelMin = +minVal + delta;
- pixelMax = +maxVal;
-
- setPixelRange(pixelMin, pixelMax);
- setDataRange(pixelToData(pixelMin), pixelToData(pixelMax));
- break;
-
- case grabAreaMax:
- slider.style.cursor = 'col-resize';
-
- pixelMin = +minVal;
- pixelMax = +maxVal + delta;
-
- setPixelRange(pixelMin, pixelMax);
- setDataRange(pixelToData(pixelMin), pixelToData(pixelMax));
- break;
-
- default:
- slider.style.cursor = 'ew-resize';
-
- pixelMin = offsetX;
- pixelMax = offsetX + delta;
-
- setPixelRange(pixelMin, pixelMax);
- setDataRange(pixelToData(pixelMin), pixelToData(pixelMax));
- break;
- }
- }
-
- function mouseUp() {
- window.removeEventListener('mousemove', mouseMove);
- window.removeEventListener('mouseup', mouseUp);
- slider.style.cursor = 'auto';
- }
- });
-
- function pixelToData(pixel) {
- var rangeMin = options.range[0],
- rangeMax = options.range[1],
- range = rangeMax - rangeMin,
- dataValue = pixel / width * range + rangeMin;
-
- dataValue = Lib.constrain(dataValue, rangeMin, rangeMax);
-
- return dataValue;
- }
-
-
- function setRange(min, max) {
- min = min || -Infinity;
- max = max || Infinity;
-
- var rangeMin = options.range[0],
- rangeMax = options.range[1],
- range = rangeMax - rangeMin,
- pixelMin = (min - rangeMin) / range * width,
- pixelMax = (max - rangeMin) / range * width;
-
- setPixelRange(pixelMin, pixelMax);
- }
-
- function setDataRange(dataMin, dataMax) {
- window.requestAnimationFrame(function() {
- Plotly.relayout(gd, 'xaxis.range', [dataMin, dataMax]);
- });
- }
-
-
- function setPixelRange(pixelMin, pixelMax) {
-
- pixelMin = Lib.constrain(pixelMin, 0, width);
- pixelMax = Lib.constrain(pixelMax, 0, width);
-
- if(pixelMax < pixelMin) {
- var temp = pixelMax;
- pixelMax = pixelMin;
- pixelMin = temp;
- }
-
- helpers.setAttributes(slider, {
- 'data-min': pixelMin,
- 'data-max': pixelMax
- });
-
- helpers.setAttributes(slideBox, {
- 'x': pixelMin,
- 'width': pixelMax - pixelMin
- });
-
- helpers.setAttributes(maskMin, { 'width': pixelMin });
- helpers.setAttributes(maskMax, {
- 'x': pixelMax,
- 'width': width - pixelMax
- });
-
- helpers.setAttributes(grabberMin, { 'transform': 'translate(' + (pixelMin - handleWidth - 1) + ')' });
- helpers.setAttributes(grabberMax, { 'transform': 'translate(' + pixelMax + ')' });
- }
-
-
- // Set slider range using axis autorange if necessary.
- if(!options.range) {
- options.range = Axes.getAutoRange(fullLayout.xaxis);
- }
-
- var rangePlots = rangePlot(gd, width, height);
-
- helpers.appendChildren(slider, [
- sliderBg,
- rangePlots,
- maskMin,
- maskMax,
- slideBox,
- grabberMin,
- grabberMax
- ]);
-
- // Set initially selected range
- setRange(fullLayout.xaxis.range[0], fullLayout.xaxis.range[1]);
-
- sliderContainer.data([0])
- .enter().append(function() {
- options.setRange = setRange;
- return slider;
- });
-};
diff --git a/src/components/rangeslider/defaults.js b/src/components/rangeslider/defaults.js
index 936eb030680..6e745a5624c 100644
--- a/src/components/rangeslider/defaults.js
+++ b/src/components/rangeslider/defaults.js
@@ -15,15 +15,19 @@ var attributes = require('./attributes');
module.exports = function handleDefaults(layoutIn, layoutOut, axName, counterAxes) {
if(!layoutIn[axName].rangeslider) return;
- var containerIn = Lib.isPlainObject(layoutIn[axName].rangeslider) ?
- layoutIn[axName].rangeslider : {},
+ // not super proud of this (maybe store _ in axis object instead
+ if(!Lib.isPlainObject(layoutIn[axName].rangeslider)) {
+ layoutIn[axName].rangeslider = {};
+ }
+
+ var containerIn = layoutIn[axName].rangeslider,
containerOut = layoutOut[axName].rangeslider = {};
function coerce(attr, dflt) {
return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
}
- coerce('bgcolor');
+ coerce('bgcolor', layoutOut.plot_bgcolor);
coerce('bordercolor');
coerce('borderwidth');
coerce('thickness');
@@ -48,4 +52,7 @@ module.exports = function handleDefaults(layoutIn, layoutOut, axName, counterAxe
layoutOut[ax] = opposing;
});
}
+
+ // to map back range slider (auto) range
+ containerOut._input = containerIn;
};
diff --git a/src/components/rangeslider/draw.js b/src/components/rangeslider/draw.js
new file mode 100644
index 00000000000..4d235cd8dfb
--- /dev/null
+++ b/src/components/rangeslider/draw.js
@@ -0,0 +1,421 @@
+/**
+* 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 Axes = require('../../plots/cartesian/axes');
+var Lib = require('../../lib');
+
+var dragElement = require('../dragelement');
+var setCursor = require('../../lib/setcursor');
+
+var constants = require('./constants');
+var rangePlot = require('./range_plot');
+
+
+module.exports = function(gd) {
+ var fullLayout = gd._fullLayout,
+ rangeSliderData = makeRangeSliderData(fullLayout);
+
+ /*
+ *
+ *
+ * < .... range plot />
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ...
+ */
+
+ function keyFunction(axisOpts) {
+ return axisOpts._name;
+ }
+
+ var rangeSliders = fullLayout._infolayer
+ .selectAll('g.' + constants.containerClassName)
+ .data(rangeSliderData, keyFunction);
+
+ rangeSliders.enter().append('g')
+ .classed(constants.containerClassName, true)
+ .attr('pointer-events', 'all');
+
+ rangeSliders.exit().remove();
+
+ // remove push margin object(s)
+ if(rangeSliders.exit().size()) clearPushMargins(gd);
+
+ // return early if no range slider is visible
+ if(rangeSliderData.length === 0) return;
+
+ // for all present range sliders
+ rangeSliders.each(function(axisOpts) {
+ var rangeSlider = d3.select(this),
+ opts = axisOpts[constants.name];
+
+ // compute new slider range using axis autorange if necessary
+ // copy back range to input range slider container to skip
+ // this step in subsequent draw calls
+ if(!opts.range) {
+ opts._input.range = opts.range = Axes.getAutoRange(axisOpts);
+ }
+
+ // update range slider dimensions
+
+ var margin = fullLayout.margin,
+ graphSize = fullLayout._size,
+ domain = axisOpts.domain;
+
+ opts._id = constants.name + axisOpts._id;
+ opts._width = graphSize.w * (domain[1] - domain[0]);
+ opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness;
+ opts._offsetShift = Math.floor(opts.borderwidth / 2);
+
+ var x = margin.l + (graphSize.w * domain[0]),
+ y = fullLayout.height - opts._height - margin.b;
+
+ rangeSlider.attr('transform', 'translate(' + x + ',' + y + ')');
+
+ // update inner nodes
+
+ rangeSlider
+ .call(drawBg, gd, axisOpts, opts)
+ .call(drawRangePlot, gd, axisOpts, opts)
+ .call(drawMasks, gd, axisOpts, opts)
+ .call(drawSlideBox, gd, axisOpts, opts)
+ .call(drawGrabbers, gd, axisOpts, opts);
+
+ // update data <--> pixel coordinate conversion methods
+
+ var range0 = opts.range[0],
+ range1 = opts.range[1],
+ dist = range1 - range0;
+
+ opts.p2d = function(v) {
+ return (v / opts._width) * dist + range0;
+ };
+
+ opts.d2p = function(v) {
+ return (v - range0) / dist * opts._width;
+ };
+
+ // setup drag element
+ setupDragElement(rangeSlider, gd, axisOpts, opts);
+
+ // update current range
+ setPixelRange(rangeSlider, gd, axisOpts, opts);
+
+ // update margins
+
+ var bb = axisOpts._boundingBox ? axisOpts._boundingBox.height : 0;
+
+ Plots.autoMargin(gd, opts._id, {
+ x: 0, y: 0, l: 0, r: 0, t: 0,
+ b: opts._height + fullLayout.margin.b + bb,
+ pad: 15 + opts._offsetShift * 2
+ });
+ });
+};
+
+function makeRangeSliderData(fullLayout) {
+ if(!fullLayout.xaxis) return [];
+ if(!fullLayout.xaxis[constants.name]) return [];
+ if(!fullLayout.xaxis[constants.name].visible) return [];
+ if(fullLayout._has('gl2d')) return [];
+
+ return [fullLayout.xaxis];
+}
+
+function setupDragElement(rangeSlider, gd, axisOpts, opts) {
+ var slideBox = rangeSlider.select('rect.' + constants.slideBoxClassName).node(),
+ grabAreaMin = rangeSlider.select('rect.' + constants.grabAreaMinClassName).node(),
+ grabAreaMax = rangeSlider.select('rect.' + constants.grabAreaMaxClassName).node();
+
+ rangeSlider.on('mousedown', function() {
+ var event = d3.event,
+ target = event.target,
+ startX = event.clientX,
+ offsetX = startX - rangeSlider.node().getBoundingClientRect().left,
+ minVal = opts.d2p(axisOpts.range[0]),
+ maxVal = opts.d2p(axisOpts.range[1]);
+
+ var dragCover = dragElement.coverSlip();
+
+ dragCover.addEventListener('mousemove', mouseMove);
+ dragCover.addEventListener('mouseup', mouseUp);
+
+ function mouseMove(e) {
+ var delta = +e.clientX - startX;
+ var pixelMin, pixelMax, cursor;
+
+ switch(target) {
+ case slideBox:
+ cursor = 'ew-resize';
+ pixelMin = minVal + delta;
+ pixelMax = maxVal + delta;
+ break;
+
+ case grabAreaMin:
+ cursor = 'col-resize';
+ pixelMin = minVal + delta;
+ pixelMax = maxVal;
+ break;
+
+ case grabAreaMax:
+ cursor = 'col-resize';
+ pixelMin = minVal;
+ pixelMax = maxVal + delta;
+ break;
+
+ default:
+ cursor = 'ew-resize';
+ pixelMin = offsetX;
+ pixelMax = offsetX + delta;
+ break;
+ }
+
+ if(pixelMax < pixelMin) {
+ var tmp = pixelMax;
+ pixelMax = pixelMin;
+ pixelMin = tmp;
+ }
+
+ opts._pixelMin = pixelMin;
+ opts._pixelMax = pixelMax;
+
+ setCursor(d3.select(dragCover), cursor);
+ setDataRange(rangeSlider, gd, axisOpts, opts);
+ }
+
+ function mouseUp() {
+ dragCover.removeEventListener('mousemove', mouseMove);
+ dragCover.removeEventListener('mouseup', mouseUp);
+ Lib.removeElement(dragCover);
+ }
+ });
+}
+
+function setDataRange(rangeSlider, gd, axisOpts, opts) {
+
+ function clamp(v) {
+ return Lib.constrain(v, opts.range[0], opts.range[1]);
+ }
+
+ var dataMin = clamp(opts.p2d(opts._pixelMin)),
+ dataMax = clamp(opts.p2d(opts._pixelMax));
+
+ window.requestAnimationFrame(function() {
+ Plotly.relayout(gd, 'xaxis.range', [dataMin, dataMax]);
+ });
+}
+
+function setPixelRange(rangeSlider, gd, axisOpts, opts) {
+
+ function clamp(v) {
+ return Lib.constrain(v, 0, opts._width);
+ }
+
+ var pixelMin = clamp(opts.d2p(axisOpts.range[0])),
+ pixelMax = clamp(opts.d2p(axisOpts.range[1]));
+
+ rangeSlider.select('rect.' + constants.slideBoxClassName)
+ .attr('x', pixelMin)
+ .attr('width', pixelMax - pixelMin);
+
+ rangeSlider.select('rect.' + constants.maskMinClassName)
+ .attr('width', pixelMin);
+
+ rangeSlider.select('rect.' + constants.maskMaxClassName)
+ .attr('x', pixelMax)
+ .attr('width', opts._width - pixelMax);
+
+ rangeSlider.select('g.' + constants.grabberMinClassName)
+ .attr('transform', 'translate(' + (pixelMin - constants.handleWidth - 1) + ',0)');
+
+ rangeSlider.select('g.' + constants.grabberMaxClassName)
+ .attr('transform', 'translate(' + pixelMax + ',0)');
+}
+
+function drawBg(rangeSlider, gd, axisOpts, opts) {
+ var bg = rangeSlider.selectAll('rect.' + constants.bgClassName)
+ .data([0]);
+
+ bg.enter().append('rect')
+ .classed(constants.bgClassName, true)
+ .attr({
+ x: 0,
+ y: 0,
+ 'shape-rendering': 'crispEdges'
+ });
+
+ var borderCorrect = (opts.borderwidth % 2) === 0 ?
+ opts.borderwidth :
+ opts.borderwidth - 1;
+
+ var offsetShift = -opts._offsetShift;
+
+ bg.attr({
+ width: opts._width + borderCorrect,
+ height: opts._height + borderCorrect,
+ transform: 'translate(' + offsetShift + ',' + offsetShift + ')',
+ fill: opts.bgcolor,
+ stroke: opts.bordercolor,
+ 'stroke-width': opts.borderwidth,
+ });
+}
+
+function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
+ var rangePlots = rangePlot(gd, opts._width, opts._height);
+
+ var gRangePlot = rangeSlider.selectAll('g.' + constants.rangePlotClassName)
+ .data([0]);
+
+ gRangePlot.enter().append('g')
+ .classed(constants.rangePlotClassName, true);
+
+ gRangePlot.html(null);
+ gRangePlot.node().appendChild(rangePlots);
+}
+
+function drawMasks(rangeSlider, gd, axisOpts, opts) {
+ var maskMin = rangeSlider.selectAll('rect.' + constants.maskMinClassName)
+ .data([0]);
+
+ maskMin.enter().append('rect')
+ .classed(constants.maskMinClassName, true)
+ .attr({ x: 0, y: 0 });
+
+ maskMin.attr({
+ height: opts._height,
+ fill: constants.maskColor
+ });
+
+ var maskMax = rangeSlider.selectAll('rect.' + constants.maskMaxClassName)
+ .data([0]);
+
+ maskMax.enter().append('rect')
+ .classed(constants.maskMaxClassName, true)
+ .attr('y', 0);
+
+ maskMax.attr({
+ height: opts._height,
+ fill: constants.maskColor
+ });
+}
+
+function drawSlideBox(rangeSlider, gd, axisOpts, opts) {
+ var slideBox = rangeSlider.selectAll('rect.' + constants.slideBoxClassName)
+ .data([0]);
+
+ slideBox.enter().append('rect')
+ .classed(constants.slideBoxClassName, true)
+ .attr('y', 0)
+ .attr('cursor', constants.slideBoxCursor);
+
+ slideBox.attr({
+ height: opts._height,
+ fill: constants.slideBoxFill
+ });
+}
+
+function drawGrabbers(rangeSlider, gd, axisOpts, opts) {
+
+ //
+
+ var grabberMin = rangeSlider.selectAll('g.' + constants.grabberMinClassName)
+ .data([0]);
+ grabberMin.enter().append('g')
+ .classed(constants.grabberMinClassName, true);
+
+ var grabberMax = rangeSlider.selectAll('g.' + constants.grabberMaxClassName)
+ .data([0]);
+ grabberMax.enter().append('g')
+ .classed(constants.grabberMaxClassName, true);
+
+ //
+
+ var handleFixAttrs = {
+ x: 0,
+ width: constants.handleWidth,
+ rx: constants.handleRadius,
+ fill: constants.handleFill,
+ stroke: constants.handleStroke,
+ 'shape-rendering': 'crispEdges'
+ };
+
+ var handleDynamicAttrs = {
+ y: opts._height / 4,
+ height: opts._height / 2,
+ };
+
+ var handleMin = grabberMin.selectAll('rect.' + constants.handleMinClassName)
+ .data([0]);
+ handleMin.enter().append('rect')
+ .classed(constants.handleMinClassName, true)
+ .attr(handleFixAttrs);
+ handleMin.attr(handleDynamicAttrs);
+
+ var handleMax = grabberMax.selectAll('rect.' + constants.handleMaxClassName)
+ .data([0]);
+ handleMax.enter().append('rect')
+ .classed(constants.handleMaxClassName, true)
+ .attr(handleFixAttrs);
+ handleMax.attr(handleDynamicAttrs);
+
+ //
+
+ var grabAreaFixAttrs = {
+ width: constants.grabAreaWidth,
+ y: 0,
+ fill: constants.grabAreaFill,
+ cursor: constants.grabAreaCursor
+ };
+
+ var grabAreaMin = grabberMin.selectAll('rect.' + constants.grabAreaMinClassName)
+ .data([0]);
+ grabAreaMin.enter().append('rect')
+ .classed(constants.grabAreaMinClassName, true)
+ .attr(grabAreaFixAttrs);
+ grabAreaMin.attr({
+ x: constants.grabAreaMinOffset,
+ height: opts._height
+ });
+
+ var grabAreaMax = grabberMax.selectAll('rect.' + constants.grabAreaMaxClassName)
+ .data([0]);
+ grabAreaMax.enter().append('rect')
+ .classed(constants.grabAreaMaxClassName, true)
+ .attr(grabAreaFixAttrs);
+ grabAreaMax.attr({
+ x: constants.grabAreaMaxOffset,
+ height: opts._height
+ });
+}
+
+function clearPushMargins(gd) {
+ var pushMargins = gd._fullLayout._pushmargin || {},
+ keys = Object.keys(pushMargins);
+
+ for(var i = 0; i < keys.length; i++) {
+ var k = keys[i];
+
+ if(k.indexOf(constants.name) !== -1) {
+ Plots.autoMargin(gd, k);
+ }
+ }
+}
diff --git a/src/components/rangeslider/index.js b/src/components/rangeslider/index.js
index cfcf373ceb0..1c8f3645b7a 100644
--- a/src/components/rangeslider/index.js
+++ b/src/components/rangeslider/index.js
@@ -8,52 +8,10 @@
'use strict';
-
-var Plots = require('../../plots/plots');
-
-var createSlider = require('./create_slider');
-var layoutAttributes = require('./attributes');
-var handleDefaults = require('./defaults');
-
-
module.exports = {
moduleType: 'component',
name: 'rangeslider',
- layoutAttributes: layoutAttributes,
- handleDefaults: handleDefaults,
- draw: draw
+ layoutAttributes: require('./attributes'),
+ handleDefaults: require('./defaults'),
+ draw: require('./draw')
};
-
-function draw(gd) {
- if(!gd._fullLayout.xaxis) return;
-
- var fullLayout = gd._fullLayout,
- sliderContainer = fullLayout._infolayer.selectAll('g.range-slider'),
- options = fullLayout.xaxis.rangeslider;
-
-
- if(!options || !options.visible) {
- sliderContainer.data([])
- .exit().remove();
-
- Plots.autoMargin(gd, 'range-slider');
-
- return;
- }
-
-
- var height = (fullLayout.height - fullLayout.margin.b - fullLayout.margin.t) * options.thickness,
- offsetShift = Math.floor(options.borderwidth / 2);
-
- if(sliderContainer[0].length === 0 && !fullLayout._has('gl2d')) createSlider(gd);
-
- // Need to default to 0 for when making gl plots
- var bb = fullLayout.xaxis._boundingBox ?
- fullLayout.xaxis._boundingBox.height : 0;
-
- Plots.autoMargin(gd, 'range-slider', {
- x: 0, y: 0, l: 0, r: 0, t: 0,
- b: height + fullLayout.margin.b + bb,
- pad: 15 + offsetShift * 2
- });
-}
diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js
index 8b8be2bb265..1bb7d7b4e98 100644
--- a/src/plot_api/plot_api.js
+++ b/src/plot_api/plot_api.js
@@ -1696,7 +1696,6 @@ Plotly.relayout = function relayout(gd, astr, val) {
if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
return plotDone.then(function() {
- subroutines.setRangeSliderRange(gd, specs.eventData);
gd.emit('plotly_relayout', specs.eventData);
return gd;
});
@@ -2093,8 +2092,6 @@ Plotly.update = function update(gd, traceUpdate, layoutUpdate, traces) {
if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
return plotDone.then(function() {
- subroutines.setRangeSliderRange(gd, relayoutSpecs.eventData);
-
gd.emit('plotly_update', {
data: restyleSpecs.eventData,
layout: relayoutSpecs.eventData
diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js
index 758a778df92..cdef6e227b9 100644
--- a/src/plot_api/subroutines.js
+++ b/src/plot_api/subroutines.js
@@ -318,22 +318,3 @@ exports.doModeBar = function(gd) {
return Plots.previousPromises(gd);
};
-
-exports.setRangeSliderRange = function(gd, changes) {
- var fullLayout = gd._fullLayout;
-
- var newMin = changes['xaxis.range'] ? changes['xaxis.range'][0] : changes['xaxis.range[0]'],
- newMax = changes['xaxis.range'] ? changes['xaxis.range'][1] : changes['xaxis.range[1]'];
-
- var rangeSlider = fullLayout.xaxis && fullLayout.xaxis.rangeslider ?
- fullLayout.xaxis.rangeslider : {};
-
- if(rangeSlider.visible) {
- if(newMin || newMax) {
- fullLayout.xaxis.rangeslider.setRange(newMin, newMax);
- }
- else if(changes['xaxis.autorange']) {
- fullLayout.xaxis.rangeslider.setRange();
- }
- }
-};
diff --git a/test/jasmine/assets/custom_matchers.js b/test/jasmine/assets/custom_matchers.js
index 75d88f5bdb1..675bc02f70c 100644
--- a/test/jasmine/assets/custom_matchers.js
+++ b/test/jasmine/assets/custom_matchers.js
@@ -65,6 +65,26 @@ module.exports = {
msgExtra
].join(' ');
+ return {
+ pass: passed,
+ message: message
+ };
+ }
+ };
+ },
+
+ toBeWithin: function() {
+ return {
+ compare: function(actual, expected, tolerance, msgExtra) {
+ var passed = Math.abs(actual - expected) < tolerance;
+
+ var message = [
+ 'Expected', actual,
+ 'to be close to', expected,
+ 'within', tolerance,
+ msgExtra
+ ].join(' ');
+
return {
pass: passed,
message: message
diff --git a/test/jasmine/tests/range_slider_test.js b/test/jasmine/tests/range_slider_test.js
index 87ff2604b84..99f82f71d4d 100644
--- a/test/jasmine/tests/range_slider_test.js
+++ b/test/jasmine/tests/range_slider_test.js
@@ -1,10 +1,17 @@
var Plotly = require('@lib/index');
var Lib = require('@src/lib');
+
var RangeSlider = require('@src/components/rangeslider');
+var constants = require('@src/components/rangeslider/constants');
+var mock = require('../../image/mocks/range_slider.json');
+
var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
-var mock = require('../../image/mocks/range_slider.json');
var mouseEvent = require('../assets/mouse_event');
+var customMatchers = require('../assets/custom_matchers');
+
+var TOL = 6;
+
describe('the range slider', function() {
@@ -14,16 +21,33 @@ describe('the range slider', function() {
var sliderY = 393;
+ function getRangeSlider() {
+ var className = constants.containerClassName;
+ return document.getElementsByClassName(className)[0];
+ }
+
+ function testTranslate1D(node, val) {
+ var transformParts = node.getAttribute('transform').split('(');
+
+ expect(transformParts[0]).toEqual('translate');
+ expect(+transformParts[1].split(',0)')[0]).toBeWithin(val, TOL);
+ }
+
describe('when specified as visible', function() {
+ beforeAll(function() {
+ jasmine.addMatchers(customMatchers);
+ });
+
beforeEach(function(done) {
gd = createGraphDiv();
var mockCopy = Lib.extendDeep({}, mock);
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
- rangeSlider = document.getElementsByClassName('range-slider')[0];
+ rangeSlider = getRangeSlider();
children = rangeSlider.children;
+
done();
});
});
@@ -56,73 +80,73 @@ describe('the range slider', function() {
it('should react to resizing the minimum handle', function(done) {
var start = 85,
end = 140,
- dataMinStart = rangeSlider.getAttribute('data-min'),
diff = end - start;
+ expect(gd.layout.xaxis.range).toBeCloseToArray([0, 49]);
+
slide(start, sliderY, end, sliderY).then(function() {
var maskMin = children[2],
handleMin = children[5];
- expect(rangeSlider.getAttribute('data-min')).toEqual(String(+dataMinStart + diff));
+ expect(gd.layout.xaxis.range).toBeCloseToArray([4, 49], -0.5);
expect(maskMin.getAttribute('width')).toEqual(String(diff));
- expect(handleMin.getAttribute('transform')).toBe('translate(' + (diff - 3) + ')');
+ expect(handleMin.getAttribute('transform')).toBe('translate(' + (diff - 3) + ',0)');
}).then(done);
});
- function testTranslate1D(node, val) {
- var transformParts = node.getAttribute('transform').split('(');
- expect(transformParts[0]).toEqual('translate');
- expect(+transformParts[1].split(')')[0]).toBeCloseTo(val, 0);
- }
-
it('should react to resizing the maximum handle', function(done) {
var start = 695,
end = 490,
- dataMaxStart = rangeSlider.getAttribute('data-max'),
+ dataMaxStart = gd._fullLayout.xaxis.rangeslider.d2p(49),
diff = end - start;
+ expect(gd.layout.xaxis.range).toBeCloseToArray([0, 49]);
+
slide(start, sliderY, end, sliderY).then(function() {
var maskMax = children[3],
handleMax = children[6];
- expect(+rangeSlider.getAttribute('data-max')).toBeCloseTo(+dataMaxStart + diff, 0);
+ expect(gd.layout.xaxis.range).toBeCloseToArray([0, 32.77], -0.5);
expect(+maskMax.getAttribute('width')).toBeCloseTo(-diff);
- testTranslate1D(handleMax, +dataMaxStart + diff);
+
+ testTranslate1D(handleMax, dataMaxStart + diff);
}).then(done);
});
it('should react to moving the slidebox left to right', function(done) {
var start = 250,
end = 300,
- dataMinStart = rangeSlider.getAttribute('data-min'),
+ dataMinStart = gd._fullLayout.xaxis.rangeslider.d2p(0),
diff = end - start;
+ expect(gd.layout.xaxis.range).toBeCloseToArray([0, 49]);
+
slide(start, sliderY, end, sliderY).then(function() {
var maskMin = children[2],
handleMin = children[5];
- expect(+rangeSlider.getAttribute('data-min')).toBeCloseTo(String(+dataMinStart + diff));
+ expect(gd.layout.xaxis.range).toBeCloseToArray([3.96, 49], -0.5);
expect(+maskMin.getAttribute('width')).toBeCloseTo(String(diff));
- testTranslate1D(handleMin, +dataMinStart + diff - 3);
+ testTranslate1D(handleMin, dataMinStart + diff - 3);
}).then(done);
});
it('should react to moving the slidebox right to left', function(done) {
var start = 300,
end = 250,
- dataMaxStart = rangeSlider.getAttribute('data-max'),
+ dataMaxStart = gd._fullLayout.xaxis.rangeslider.d2p(49),
diff = end - start;
+ expect(gd.layout.xaxis.range).toBeCloseToArray([0, 49]);
+
slide(start, sliderY, end, sliderY).then(function() {
var maskMax = children[3],
handleMax = children[6];
- expect(+rangeSlider.getAttribute('data-max')).toBeCloseTo(+dataMaxStart + diff);
+ expect(gd.layout.xaxis.range).toBeCloseToArray([0, 45.04], -0.5);
expect(+maskMax.getAttribute('width')).toBeCloseTo(-diff);
- testTranslate1D(handleMax, +dataMaxStart + diff);
+ testTranslate1D(handleMax, dataMaxStart + diff);
}).then(done);
-
-
});
it('should resize the main plot when rangeslider has moved', function(done) {
@@ -147,22 +171,107 @@ describe('the range slider', function() {
});
it('should relayout with relayout "array syntax"', function(done) {
- Plotly.relayout(gd, 'xaxis.range', [10, 20])
- .then(function() {
- expect(gd._fullLayout.xaxis.range).toEqual([10, 20]);
- expect(+rangeSlider.getAttribute('data-min')).toBeCloseTo(124.69, -1);
- expect(+rangeSlider.getAttribute('data-max')).toBeCloseTo(249.39, -1);
- })
- .then(done);
+ Plotly.relayout(gd, 'xaxis.range', [10, 20]).then(function() {
+ var maskMin = children[2],
+ maskMax = children[3],
+ handleMin = children[5],
+ handleMax = children[6];
+
+ expect(+maskMin.getAttribute('width')).toBeWithin(125, TOL);
+ expect(+maskMax.getAttribute('width')).toBeWithin(365, TOL);
+ testTranslate1D(handleMin, 123.32);
+ testTranslate1D(handleMax, 252.65);
+ })
+ .then(done);
});
it('should relayout with relayout "element syntax"', function(done) {
- Plotly.relayout(gd, 'xaxis.range[0]', 10)
- .then(function() {
- expect(gd._fullLayout.xaxis.range[0]).toEqual(10);
- expect(+rangeSlider.getAttribute('data-min')).toBeCloseTo(124.69, -1);
- })
- .then(done);
+ Plotly.relayout(gd, 'xaxis.range[0]', 10).then(function() {
+ var maskMin = children[2],
+ maskMax = children[3],
+ handleMin = children[5],
+ handleMax = children[6];
+
+ expect(+maskMin.getAttribute('width')).toBeWithin(126, TOL);
+ expect(+maskMax.getAttribute('width')).toEqual(0);
+ testTranslate1D(handleMin, 123.32);
+ testTranslate1D(handleMax, 619);
+ })
+ .then(done);
+ });
+
+ it('should relayout with style options', function(done) {
+ var bg = children[0],
+ maskMin = children[2],
+ maskMax = children[3];
+
+ var maskMinWidth, maskMaxWidth;
+
+ Plotly.relayout(gd, 'xaxis.range', [5, 10]).then(function() {
+ maskMinWidth = +maskMin.getAttribute('width'),
+ maskMaxWidth = +maskMax.getAttribute('width');
+
+ return Plotly.relayout(gd, 'xaxis.rangeslider.bgcolor', 'red');
+ })
+ .then(function() {
+ expect(+maskMin.getAttribute('width')).toEqual(maskMinWidth);
+ expect(+maskMax.getAttribute('width')).toEqual(maskMaxWidth);
+
+ expect(bg.getAttribute('fill')).toBe('red');
+ expect(bg.getAttribute('stroke')).toBe('black');
+ expect(bg.getAttribute('stroke-width')).toBe('2');
+
+ return Plotly.relayout(gd, 'xaxis.rangeslider.bordercolor', 'blue');
+ })
+ .then(function() {
+ expect(+maskMin.getAttribute('width')).toEqual(maskMinWidth);
+ expect(+maskMax.getAttribute('width')).toEqual(maskMaxWidth);
+
+ expect(bg.getAttribute('fill')).toBe('red');
+ expect(bg.getAttribute('stroke')).toBe('blue');
+ expect(bg.getAttribute('stroke-width')).toBe('2');
+
+ return Plotly.relayout(gd, 'xaxis.rangeslider.borderwidth', 3);
+ })
+ .then(function() {
+ expect(+maskMin.getAttribute('width')).toEqual(maskMinWidth);
+ expect(+maskMax.getAttribute('width')).toEqual(maskMaxWidth);
+
+ expect(bg.getAttribute('fill')).toBe('red');
+ expect(bg.getAttribute('stroke')).toBe('blue');
+ expect(bg.getAttribute('stroke-width')).toBe('3');
+ })
+ .then(done);
+ });
+
+ it('should relayout on size / domain udpate', function(done) {
+ var maskMin = children[2],
+ maskMax = children[3];
+
+ Plotly.relayout(gd, 'xaxis.range', [5, 10]).then(function() {
+ expect(+maskMin.getAttribute('width')).toBeWithin(63.16, TOL);
+ expect(+maskMax.getAttribute('width')).toBeWithin(492.67, TOL);
+
+ return Plotly.relayout(gd, 'xaxis.domain', [0.3, 0.7]);
+ })
+ .then(function() {
+ var maskMin = children[2],
+ maskMax = children[3];
+
+ expect(+maskMin.getAttribute('width')).toBeWithin(25.26, TOL);
+ expect(+maskMax.getAttribute('width')).toBeWithin(197.06, TOL);
+
+ return Plotly.relayout(gd, 'width', 400);
+ })
+ .then(function() {
+ var maskMin = children[2],
+ maskMax = children[3];
+
+ expect(+maskMin.getAttribute('width')).toBeWithin(9.22, TOL);
+ expect(+maskMax.getAttribute('width')).toBeWithin(71.95, TOL);
+
+ })
+ .then(done);
});
});
@@ -177,7 +286,7 @@ describe('the range slider', function() {
it('should not add the slider to the DOM by default', function(done) {
Plotly.plot(gd, [{ x: [1, 2, 3], y: [2, 3, 4] }], {})
.then(function() {
- var rangeSlider = document.getElementsByClassName('range-slider')[0];
+ var rangeSlider = getRangeSlider();
expect(rangeSlider).not.toBeDefined();
})
.then(done);
@@ -187,7 +296,7 @@ describe('the range slider', function() {
Plotly.plot(gd, [{ x: [1, 2, 3], y: [2, 3, 4] }], {})
.then(function() { Plotly.relayout(gd, 'xaxis.rangeslider', 'exists'); })
.then(function() {
- var rangeSlider = document.getElementsByClassName('range-slider')[0];
+ var rangeSlider = getRangeSlider();
expect(rangeSlider).toBeDefined();
})
.then(done);
@@ -197,7 +306,7 @@ describe('the range slider', function() {
Plotly.plot(gd, [{ x: [1, 2, 3], y: [2, 3, 4] }], {})
.then(function() { Plotly.relayout(gd, 'xaxis.rangeslider.visible', true); })
.then(function() {
- var rangeSlider = document.getElementsByClassName('range-slider')[0];
+ var rangeSlider = getRangeSlider();
expect(rangeSlider).toBeDefined();
})
.then(done);
@@ -207,7 +316,7 @@ describe('the range slider', function() {
Plotly.plot(gd, [{ x: [1, 2, 3], y: [2, 3, 4] }], { xaxis: { rangeslider: { visible: true }}})
.then(function() { Plotly.relayout(gd, 'xaxis.rangeslider.visible', false); })
.then(function() {
- var rangeSlider = document.getElementsByClassName('range-slider')[0];
+ var rangeSlider = getRangeSlider();
expect(rangeSlider).not.toBeDefined();
})
.then(done);
@@ -252,13 +361,14 @@ describe('the range slider', function() {
thickness: 0.15,
bgcolor: '#fff',
borderwidth: 0,
- bordercolor: '#444'
+ bordercolor: '#444',
+ _input: layoutIn.xaxis.rangeslider
},
_needsExpand: true
},
yaxis: {
fixedrange: true
- }
+ },
};
RangeSlider.handleDefaults(layoutIn, layoutOut, axName, counterAxes);
@@ -278,7 +388,8 @@ describe('the range slider', function() {
thickness: 0.15,
bgcolor: '#fff',
borderwidth: 0,
- bordercolor: '#444'
+ bordercolor: '#444',
+ _input: layoutIn.xaxis.rangeslider
},
_needsExpand: true
},
@@ -310,7 +421,8 @@ describe('the range slider', function() {
thickness: 0.15,
bgcolor: '#fff',
borderwidth: 0,
- bordercolor: '#444'
+ bordercolor: '#444',
+ _input: layoutIn.xaxis.rangeslider
},
_needsExpand: true
},
@@ -336,7 +448,8 @@ describe('the range slider', function() {
thickness: 0.15,
bgcolor: '#fff',
borderwidth: 0,
- bordercolor: '#444'
+ bordercolor: '#444',
+ _input: {}
},
_needsExpand: true
},
@@ -362,7 +475,8 @@ describe('the range slider', function() {
bgcolor: '#fff',
borderwidth: 0,
bordercolor: '#444',
- range: [1, 10]
+ range: [1, 10],
+ _input: layoutIn.xaxis.rangeslider
},
range: [1, 10]
},
@@ -386,7 +500,8 @@ describe('the range slider', function() {
thickness: 0.15,
bgcolor: '#fff',
borderwidth: 0,
- bordercolor: '#444'
+ bordercolor: '#444',
+ _input: {}
},
range: [2, 40],
_needsExpand: true
@@ -398,6 +513,26 @@ describe('the range slider', function() {
expect(layoutOut).toEqual(expected);
});
+
+ it('should default \'bgcolor\' to layout \'plot_bgcolor\'', function() {
+ var layoutIn = {
+ xaxis: { rangeslider: true },
+ yaxis: {},
+ };
+
+ var layoutOut = {
+ xaxis: { range: [2, 40]},
+ yaxis: {},
+ plot_bgcolor: 'blue'
+ };
+
+ var axName = 'xaxis',
+ counterAxes = ['yaxis'];
+
+ RangeSlider.handleDefaults(layoutIn, layoutOut, axName, counterAxes);
+
+ expect(layoutOut.xaxis.rangeslider.bgcolor).toEqual('blue');
+ });
});
describe('in general', function() {
@@ -411,9 +546,9 @@ describe('the range slider', function() {
it('should plot when only x data is provided', function(done) {
Plotly.plot(gd, [{ x: [1, 2, 3] }], { xaxis: { rangeslider: {} }})
.then(function() {
- var rangeslider = document.getElementsByClassName('range-slider');
+ var rangeSlider = getRangeSlider();
- expect(rangeslider.length).toBe(1);
+ expect(rangeSlider).toBeDefined();
})
.then(done);
});
@@ -421,9 +556,9 @@ describe('the range slider', function() {
it('should plot when only y data is provided', function(done) {
Plotly.plot(gd, [{ y: [1, 2, 3] }], { xaxis: { rangeslider: {} }})
.then(function() {
- var rangeslider = document.getElementsByClassName('range-slider');
+ var rangeSlider = getRangeSlider();
- expect(rangeslider.length).toBe(1);
+ expect(rangeSlider).toBeDefined();
})
.then(done);
});