-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Feature range slider #336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature range slider #336
Changes from 49 commits
4c28de7
866d1d2
d2973f3
eb619ef
854442d
b0c16cd
2666162
c8d87d0
598d69f
3ecdcdc
fb863f6
5857574
028b46e
4143a65
8cfd258
fe51426
09af762
f381299
149348c
0ebc894
80571f3
c4d19ad
242458d
4f4ffeb
665c061
c008328
0a63f2a
771546c
47a6829
ab9ba72
25071ae
37e93d5
00c0732
4d238e9
21d6bdc
0e9bc90
7fd37ae
0b32a36
88e855b
df58e62
358243d
6bb10c0
0fd5c37
288c4a3
767a821
67674b6
2deed2e
7ead461
5214897
771b113
fb91908
2dd5e8e
83375d2
cb50997
6736bbb
c613c6c
c55bf02
cbe2c26
271589c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** | ||
* 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 colorAttributes = require('../color/attributes'); | ||
|
||
module.exports = { | ||
visible: { | ||
valType: 'boolean', | ||
dflt: false, | ||
description: 'Determines whether or not the range slider will be visible.' | ||
}, | ||
bordercolor: { | ||
valType: 'color', | ||
dflt: colorAttributes.defaultLine, | ||
role: 'style', | ||
description: 'Sets the border color of the range slider.' | ||
}, | ||
borderwidth: { | ||
valType: 'integer', | ||
dflt: 0, | ||
role: 'style', | ||
description: 'Sets the border color of the range slider.' | ||
}, | ||
backgroundcolor: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be |
||
valType: 'color', | ||
dflt: colorAttributes.background, | ||
role: 'style', | ||
description: 'Sets the background color of the range slider.' | ||
}, | ||
height: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We're using @chriddyp @cldougl @cpsievert thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 for consistency ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's go with |
||
valType: 'number', | ||
dflt: 0.15, | ||
min: 0, | ||
max: 1, | ||
role: 'style', | ||
description: [ | ||
'The height of the range slider as a fraction of the', | ||
'total plot area height.' | ||
].join(' ') | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
/** | ||
* 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 Lib = require('../../lib'); | ||
|
||
var svgNS = require('../../constants/xmlns_namespaces').svg; | ||
|
||
var helpers = require('./helpers'); | ||
var rangePlot = require('./range_plot'); | ||
|
||
|
||
module.exports = function createSlider(gd, minStart, maxStart) { | ||
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.height, | ||
handleWidth = 2, | ||
offsetShift = Math.floor(options.borderwidth / 2), | ||
x = fullLayout.margin.l, | ||
y = fullLayout.height - height - fullLayout.margin.b; | ||
|
||
minStart = minStart || 0; | ||
maxStart = 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.backgroundcolor, | ||
'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; | ||
|
||
switch(target) { | ||
case slideBox: | ||
slider.style.cursor = 'ew-resize'; | ||
setPixelRange(+maxVal + delta, +minVal + delta); | ||
break; | ||
|
||
case grabAreaMin: | ||
slider.style.cursor = 'col-resize'; | ||
setPixelRange(+minVal + delta, +maxVal); | ||
break; | ||
|
||
case grabAreaMax: | ||
slider.style.cursor = 'col-resize'; | ||
setPixelRange(+minVal, +maxVal + delta); | ||
break; | ||
|
||
default: | ||
slider.style.cursor = 'ew-resize'; | ||
setPixelRange(offsetX, offsetX + delta); | ||
break; | ||
} | ||
} | ||
|
||
function mouseUp() { | ||
window.removeEventListener('mousemove', mouseMove); | ||
window.removeEventListener('mouseup', mouseUp); | ||
slider.style.cursor = 'auto'; | ||
} | ||
}); | ||
|
||
|
||
function setRange(min, max) { | ||
min = min || -Infinity; | ||
max = max || Infinity; | ||
|
||
var rangeMin = fullLayout.xaxis.range[0], | ||
rangeMax = fullLayout.xaxis.range[1], | ||
range = rangeMax - rangeMin, | ||
pixelMin = (min - rangeMin) / range * width, | ||
pixelMax = (max - rangeMin) / range * width; | ||
|
||
setPixelRange(pixelMin, pixelMax); | ||
} | ||
|
||
|
||
function setPixelRange(min, max) { | ||
|
||
min = Lib.constrain(min, 0, width); | ||
max = Lib.constrain(max, 0, width); | ||
|
||
if(max < min) { | ||
var temp = max; | ||
max = min; | ||
min = temp; | ||
} | ||
|
||
helpers.setAttributes(slider, { | ||
'data-min': min, | ||
'data-max': max | ||
}); | ||
|
||
helpers.setAttributes(slideBox, { | ||
'x': min, | ||
'width': max - min | ||
}); | ||
|
||
helpers.setAttributes(maskMin, { 'width': min }); | ||
helpers.setAttributes(maskMax, { | ||
'x': max, | ||
'width': width - max | ||
}); | ||
|
||
helpers.setAttributes(grabberMin, { 'transform': 'translate(' + (min - handleWidth - 1) + ')' }); | ||
helpers.setAttributes(grabberMax, { 'transform': 'translate(' + max + ')' }); | ||
|
||
// call to set range on plot here | ||
var rangeMin = fullLayout.xaxis.range[0], | ||
rangeMax = fullLayout.xaxis.range[1], | ||
range = rangeMax - rangeMin, | ||
dataMin = min / width * range + rangeMin, | ||
dataMax = max / width * range + rangeMin; | ||
|
||
Plotly.relayout(gd, 'xaxis.range', [dataMin, dataMax]); | ||
} | ||
|
||
|
||
var rangePlots = rangePlot(gd, width, height); | ||
|
||
helpers.appendChildren(slider, [ | ||
sliderBg, | ||
rangePlots, | ||
maskMin, | ||
maskMax, | ||
slideBox, | ||
grabberMin, | ||
grabberMax | ||
]); | ||
|
||
sliderContainer.data([0]) | ||
.enter().append(function() { | ||
options.setRange = setRange; | ||
return slider; | ||
}); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/** | ||
* 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'); | ||
|
||
module.exports = { | ||
'linear': function(val) { return val; }, | ||
'log': function(val) { return Math.log(val)/Math.log(10); }, | ||
'date': function(val) { return Lib.dateTime2ms(val); }, | ||
'category': function(_, i) { return i; } | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These should probably be swapped out for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like you're looking for |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/** | ||
* 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'); | ||
|
||
|
||
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { | ||
|
||
if(!layoutIn.xaxis || !layoutOut.xaxis) return; | ||
|
||
var containerIn = layoutIn.xaxis.rangeslider || {}, | ||
containerOut = layoutOut.xaxis.rangeslider = {}; | ||
|
||
if(!containerIn.visible) return; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
|
||
function coerce(attr, dflt) { | ||
return Lib.coerce(containerIn, containerOut, | ||
attributes, attr, dflt); | ||
} | ||
|
||
coerce('visible'); | ||
coerce('height'); | ||
coerce('backgroundcolor'); | ||
coerce('bordercolor'); | ||
coerce('borderwidth'); | ||
|
||
if(containerOut.visible) { | ||
layoutOut.yaxis.fixedrange = true; | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* 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.setAttributes = function setAttributes(el, attributes) { | ||
for(var key in attributes) { | ||
el.setAttribute(key, attributes[key]); | ||
} | ||
}; | ||
|
||
|
||
exports.appendChildren = function appendChildren(el, children) { | ||
for(var i = 0; i < children.length; i++) { | ||
if(children[i]) { | ||
el.appendChild(children[i]); | ||
} | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A blank range slider (
layout.xaxis.rangeslider = {}
) should be considered visible.