-
-
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 all 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,52 @@ | ||
/** | ||
* 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 = { | ||
bgcolor: { | ||
valType: 'color', | ||
dflt: colorAttributes.background, | ||
role: 'style', | ||
description: 'Sets the background color of the range slider.' | ||
}, | ||
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.' | ||
}, | ||
thickness: { | ||
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(' ') | ||
}, | ||
visible: { | ||
valType: 'boolean', | ||
dflt: true, | ||
role: 'info', | ||
description: [ | ||
'Determines whether or not the range slider will be visible.', | ||
'If visible, perpendicular axes will be set to `fixedrange`' | ||
].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.thickness, | ||
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.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; | ||
|
||
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; } | ||
}; | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/** | ||
* 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, axName, counterAxes) { | ||
|
||
if(!layoutIn[axName].rangeslider) 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. we should be more strict here. 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. I did this so that we could say 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. I see your point. But at the moment, container attribute objects are either objects or undefined and nothing else. |
||
|
||
var containerIn = typeof layoutIn[axName].rangeslider === 'object' ? | ||
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 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. ah never mind. if |
||
layoutIn[axName].rangeslider : {}, | ||
containerOut = layoutOut[axName].rangeslider = {}; | ||
|
||
function coerce(attr, dflt) { | ||
return Lib.coerce(containerIn, containerOut, | ||
attributes, attr, dflt); | ||
} | ||
|
||
coerce('visible'); | ||
coerce('thickness'); | ||
coerce('bgcolor'); | ||
coerce('bordercolor'); | ||
coerce('borderwidth'); | ||
|
||
if(containerOut.visible) { | ||
counterAxes.forEach(function(ax) { | ||
var opposing = layoutOut[ax] || {}; | ||
opposing.fixedrange = true; | ||
layoutOut[ax] = opposing; | ||
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. 👍 |
||
}); | ||
} | ||
}; |
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.
These should probably be swapped out for the
ax._2_
methods, but I couldn't wrap my head around their usage and which one(s) are needed for this use case (i.e. raw data -> numeric relative coordinate value for plotting).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.
Looks like you're looking for
ax.d2c