Skip to content

Commit 826ed94

Browse files
committed
Merge pull request #336 from plotly/feature-range-slider
Feature: range-slider first implementation
2 parents be03333 + 271589c commit 826ed94

File tree

16 files changed

+1032
-34
lines changed

16 files changed

+1032
-34
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var colorAttributes = require('../color/attributes');
12+
13+
module.exports = {
14+
bgcolor: {
15+
valType: 'color',
16+
dflt: colorAttributes.background,
17+
role: 'style',
18+
description: 'Sets the background color of the range slider.'
19+
},
20+
bordercolor: {
21+
valType: 'color',
22+
dflt: colorAttributes.defaultLine,
23+
role: 'style',
24+
description: 'Sets the border color of the range slider.'
25+
},
26+
borderwidth: {
27+
valType: 'integer',
28+
dflt: 0,
29+
role: 'style',
30+
description: 'Sets the border color of the range slider.'
31+
},
32+
thickness: {
33+
valType: 'number',
34+
dflt: 0.15,
35+
min: 0,
36+
max: 1,
37+
role: 'style',
38+
description: [
39+
'The height of the range slider as a fraction of the',
40+
'total plot area height.'
41+
].join(' ')
42+
},
43+
visible: {
44+
valType: 'boolean',
45+
dflt: true,
46+
role: 'info',
47+
description: [
48+
'Determines whether or not the range slider will be visible.',
49+
'If visible, perpendicular axes will be set to `fixedrange`'
50+
].join(' ')
51+
}
52+
};
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
12+
var Plotly = require('../../plotly');
13+
var Lib = require('../../lib');
14+
15+
var svgNS = require('../../constants/xmlns_namespaces').svg;
16+
17+
var helpers = require('./helpers');
18+
var rangePlot = require('./range_plot');
19+
20+
21+
module.exports = function createSlider(gd, minStart, maxStart) {
22+
var fullLayout = gd._fullLayout,
23+
sliderContainer = fullLayout._infolayer.selectAll('g.range-slider'),
24+
options = fullLayout.xaxis.rangeslider,
25+
width = fullLayout._size.w,
26+
height = (fullLayout.height - fullLayout.margin.b - fullLayout.margin.t) * options.thickness,
27+
handleWidth = 2,
28+
offsetShift = Math.floor(options.borderwidth / 2),
29+
x = fullLayout.margin.l,
30+
y = fullLayout.height - height - fullLayout.margin.b;
31+
32+
minStart = minStart || 0;
33+
maxStart = maxStart || width;
34+
35+
var slider = document.createElementNS(svgNS, 'g');
36+
helpers.setAttributes(slider, {
37+
'class': 'range-slider',
38+
'data-min': minStart,
39+
'data-max': maxStart,
40+
'pointer-events': 'all',
41+
'transform': 'translate(' + x + ',' + y + ')'
42+
});
43+
44+
45+
var sliderBg = document.createElementNS(svgNS, 'rect'),
46+
borderCorrect = options.borderwidth % 2 === 0 ? options.borderwidth : options.borderwidth - 1;
47+
helpers.setAttributes(sliderBg, {
48+
'fill': options.bgcolor,
49+
'stroke': options.bordercolor,
50+
'stroke-width': options.borderwidth,
51+
'height': height + borderCorrect,
52+
'width': width + borderCorrect,
53+
'transform': 'translate(-' + offsetShift + ', -' + offsetShift + ')',
54+
'shape-rendering': 'crispEdges'
55+
});
56+
57+
58+
var maskMin = document.createElementNS(svgNS, 'rect');
59+
helpers.setAttributes(maskMin, {
60+
'x': 0,
61+
'width': minStart,
62+
'height': height,
63+
'fill': 'rgba(0,0,0,0.4)'
64+
});
65+
66+
67+
var maskMax = document.createElementNS(svgNS, 'rect');
68+
helpers.setAttributes(maskMax, {
69+
'x': maxStart,
70+
'width': width - maxStart,
71+
'height': height,
72+
'fill': 'rgba(0,0,0,0.4)'
73+
});
74+
75+
76+
var grabberMin = document.createElementNS(svgNS, 'g'),
77+
grabAreaMin = document.createElementNS(svgNS, 'rect'),
78+
handleMin = document.createElementNS(svgNS, 'rect');
79+
helpers.setAttributes(grabberMin, { 'transform': 'translate(' + (minStart - handleWidth - 1) + ')' });
80+
helpers.setAttributes(grabAreaMin, {
81+
'width': 10,
82+
'height': height,
83+
'x': -6,
84+
'fill': 'transparent',
85+
'cursor': 'col-resize'
86+
});
87+
helpers.setAttributes(handleMin, {
88+
'width': handleWidth,
89+
'height': height / 2,
90+
'y': height / 4,
91+
'rx': 1,
92+
'fill': 'white',
93+
'stroke': '#666',
94+
'shape-rendering': 'crispEdges'
95+
});
96+
helpers.appendChildren(grabberMin, [handleMin, grabAreaMin]);
97+
98+
99+
var grabberMax = document.createElementNS(svgNS, 'g'),
100+
grabAreaMax = document.createElementNS(svgNS, 'rect'),
101+
handleMax = document.createElementNS(svgNS, 'rect');
102+
helpers.setAttributes(grabberMax, { 'transform': 'translate(' + maxStart + ')' });
103+
helpers.setAttributes(grabAreaMax, {
104+
'width': 10,
105+
'height': height,
106+
'x': -2,
107+
'fill': 'transparent',
108+
'cursor': 'col-resize'
109+
});
110+
helpers.setAttributes(handleMax, {
111+
'width': handleWidth,
112+
'height': height / 2,
113+
'y': height / 4,
114+
'rx': 1,
115+
'fill': 'white',
116+
'stroke': '#666',
117+
'shape-rendering': 'crispEdges'
118+
});
119+
helpers.appendChildren(grabberMax, [handleMax, grabAreaMax]);
120+
121+
122+
var slideBox = document.createElementNS(svgNS, 'rect');
123+
helpers.setAttributes(slideBox, {
124+
'x': minStart,
125+
'width': maxStart - minStart,
126+
'height': height,
127+
'cursor': 'ew-resize',
128+
'fill': 'transparent'
129+
});
130+
131+
132+
slider.addEventListener('mousedown', function(event) {
133+
var target = event.target,
134+
startX = event.clientX,
135+
offsetX = startX - slider.getBoundingClientRect().left,
136+
minVal = slider.getAttribute('data-min'),
137+
maxVal = slider.getAttribute('data-max');
138+
139+
window.addEventListener('mousemove', mouseMove);
140+
window.addEventListener('mouseup', mouseUp);
141+
142+
function mouseMove(e) {
143+
var delta = +e.clientX - startX;
144+
145+
switch(target) {
146+
case slideBox:
147+
slider.style.cursor = 'ew-resize';
148+
setPixelRange(+maxVal + delta, +minVal + delta);
149+
break;
150+
151+
case grabAreaMin:
152+
slider.style.cursor = 'col-resize';
153+
setPixelRange(+minVal + delta, +maxVal);
154+
break;
155+
156+
case grabAreaMax:
157+
slider.style.cursor = 'col-resize';
158+
setPixelRange(+minVal, +maxVal + delta);
159+
break;
160+
161+
default:
162+
slider.style.cursor = 'ew-resize';
163+
setPixelRange(offsetX, offsetX + delta);
164+
break;
165+
}
166+
}
167+
168+
function mouseUp() {
169+
window.removeEventListener('mousemove', mouseMove);
170+
window.removeEventListener('mouseup', mouseUp);
171+
slider.style.cursor = 'auto';
172+
}
173+
});
174+
175+
176+
function setRange(min, max) {
177+
min = min || -Infinity;
178+
max = max || Infinity;
179+
180+
var rangeMin = fullLayout.xaxis.range[0],
181+
rangeMax = fullLayout.xaxis.range[1],
182+
range = rangeMax - rangeMin,
183+
pixelMin = (min - rangeMin) / range * width,
184+
pixelMax = (max - rangeMin) / range * width;
185+
186+
setPixelRange(pixelMin, pixelMax);
187+
}
188+
189+
190+
function setPixelRange(min, max) {
191+
192+
min = Lib.constrain(min, 0, width);
193+
max = Lib.constrain(max, 0, width);
194+
195+
if(max < min) {
196+
var temp = max;
197+
max = min;
198+
min = temp;
199+
}
200+
201+
helpers.setAttributes(slider, {
202+
'data-min': min,
203+
'data-max': max
204+
});
205+
206+
helpers.setAttributes(slideBox, {
207+
'x': min,
208+
'width': max - min
209+
});
210+
211+
helpers.setAttributes(maskMin, { 'width': min });
212+
helpers.setAttributes(maskMax, {
213+
'x': max,
214+
'width': width - max
215+
});
216+
217+
helpers.setAttributes(grabberMin, { 'transform': 'translate(' + (min - handleWidth - 1) + ')' });
218+
helpers.setAttributes(grabberMax, { 'transform': 'translate(' + max + ')' });
219+
220+
// call to set range on plot here
221+
var rangeMin = fullLayout.xaxis.range[0],
222+
rangeMax = fullLayout.xaxis.range[1],
223+
range = rangeMax - rangeMin,
224+
dataMin = min / width * range + rangeMin,
225+
dataMax = max / width * range + rangeMin;
226+
227+
Plotly.relayout(gd, 'xaxis.range', [dataMin, dataMax]);
228+
}
229+
230+
231+
var rangePlots = rangePlot(gd, width, height);
232+
233+
helpers.appendChildren(slider, [
234+
sliderBg,
235+
rangePlots,
236+
maskMin,
237+
maskMax,
238+
slideBox,
239+
grabberMin,
240+
grabberMax
241+
]);
242+
243+
sliderContainer.data([0])
244+
.enter().append(function() {
245+
options.setRange = setRange;
246+
return slider;
247+
});
248+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Lib = require('../../lib');
12+
13+
module.exports = {
14+
'linear': function(val) { return val; },
15+
'log': function(val) { return Math.log(val)/Math.log(10); },
16+
'date': function(val) { return Lib.dateTime2ms(val); },
17+
'category': function(_, i) { return i; }
18+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Lib = require('../../lib');
12+
var attributes = require('./attributes');
13+
14+
15+
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, axName, counterAxes) {
16+
17+
if(!layoutIn[axName].rangeslider) return;
18+
19+
var containerIn = typeof layoutIn[axName].rangeslider === 'object' ?
20+
layoutIn[axName].rangeslider : {},
21+
containerOut = layoutOut[axName].rangeslider = {};
22+
23+
function coerce(attr, dflt) {
24+
return Lib.coerce(containerIn, containerOut,
25+
attributes, attr, dflt);
26+
}
27+
28+
coerce('visible');
29+
coerce('thickness');
30+
coerce('bgcolor');
31+
coerce('bordercolor');
32+
coerce('borderwidth');
33+
34+
if(containerOut.visible) {
35+
counterAxes.forEach(function(ax) {
36+
var opposing = layoutOut[ax] || {};
37+
opposing.fixedrange = true;
38+
layoutOut[ax] = opposing;
39+
});
40+
}
41+
};

src/components/rangeslider/helpers.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
exports.setAttributes = function setAttributes(el, attributes) {
12+
for(var key in attributes) {
13+
el.setAttribute(key, attributes[key]);
14+
}
15+
};
16+
17+
18+
exports.appendChildren = function appendChildren(el, children) {
19+
for(var i = 0; i < children.length; i++) {
20+
if(children[i]) {
21+
el.appendChild(children[i]);
22+
}
23+
}
24+
};

0 commit comments

Comments
 (0)