Skip to content

Commit

Permalink
Merge pull request #473 from plotly/initial-rangeslider-range
Browse files Browse the repository at this point in the history
Initial rangeslider ranges
  • Loading branch information
etpinard committed Apr 26, 2016
2 parents 486b01c + 02cb4e1 commit 223c43f
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 109 deletions.
16 changes: 16 additions & 0 deletions src/components/rangeslider/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ module.exports = {
role: 'style',
description: 'Sets the border color of the range slider.'
},
range: {
valType: 'info_array',
role: 'info',
items: [
{valType: 'number'},
{valType: 'number'}
],
description: [
'Sets the range of the range slider.',
'If not set, defaults to the full xaxis range.',
'If the axis `type` is *log*, then you must take the',
'log of your desired range.',
'If the axis `type` is *date*, then you must convert',
'the date to unix time in milliseconds.'
].join(' ')
},
thickness: {
valType: 'number',
dflt: 0.15,
Expand Down
24 changes: 16 additions & 8 deletions src/components/rangeslider/create_slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@


var Plotly = require('../../plotly');
var Axes = require('../../plots/cartesian/axes');
var Lib = require('../../lib');

var svgNS = require('../../constants/xmlns_namespaces').svg;
Expand All @@ -18,7 +19,7 @@ var helpers = require('./helpers');
var rangePlot = require('./range_plot');


module.exports = function createSlider(gd, minStart, maxStart) {
module.exports = function createSlider(gd) {
var fullLayout = gd._fullLayout,
sliderContainer = fullLayout._infolayer.selectAll('g.range-slider'),
options = fullLayout.xaxis.rangeslider,
Expand All @@ -29,8 +30,8 @@ module.exports = function createSlider(gd, minStart, maxStart) {
x = fullLayout.margin.l,
y = fullLayout.height - height - fullLayout.margin.b;

minStart = minStart || 0;
maxStart = maxStart || width;
var minStart = 0,
maxStart = width;

var slider = document.createElementNS(svgNS, 'g');
helpers.setAttributes(slider, {
Expand Down Expand Up @@ -177,8 +178,8 @@ module.exports = function createSlider(gd, minStart, maxStart) {
min = min || -Infinity;
max = max || Infinity;

var rangeMin = fullLayout.xaxis.range[0],
rangeMax = fullLayout.xaxis.range[1],
var rangeMin = options.range[0],
rangeMax = options.range[1],
range = rangeMax - rangeMin,
pixelMin = (min - rangeMin) / range * width,
pixelMax = (max - rangeMin) / range * width;
Expand Down Expand Up @@ -217,9 +218,8 @@ module.exports = function createSlider(gd, minStart, maxStart) {
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],
var rangeMin = options.range[0],
rangeMax = options.range[1],
range = rangeMax - rangeMin,
dataMin = min / width * range + rangeMin,
dataMax = max / width * range + rangeMin;
Expand All @@ -236,6 +236,11 @@ module.exports = function createSlider(gd, minStart, maxStart) {
}


// 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, [
Expand All @@ -248,6 +253,9 @@ module.exports = function createSlider(gd, minStart, maxStart) {
grabberMax
]);

// Set initially selected range
setRange(fullLayout.xaxis.range[0], fullLayout.xaxis.range[1]);

sliderContainer.data([0])
.enter().append(function() {
options.setRange = setRange;
Expand Down
16 changes: 14 additions & 2 deletions src/components/rangeslider/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,23 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, axName, coun
attributes, attr, dflt);
}

coerce('visible');
coerce('thickness');
coerce('bgcolor');
coerce('bordercolor');
coerce('borderwidth');
coerce('thickness');
coerce('visible');
coerce('range');

// Expand slider range to the axis range
if(containerOut.range && !layoutOut[axName].autorange) {
var outRange = containerOut.range,
axRange = layoutOut[axName].range;

outRange[0] = Math.min(outRange[0], axRange[0]);
outRange[1] = Math.max(outRange[1], axRange[1]);
} else {
layoutOut[axName]._needsExpand = true;
}

if(containerOut.visible) {
counterAxes.forEach(function(ax) {
Expand Down
4 changes: 2 additions & 2 deletions src/components/rangeslider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = {
supplyLayoutDefaults: supplyLayoutDefaults
};

function draw(gd, minStart, maxStart) {
function draw(gd) {
if(!gd._fullLayout.xaxis) return;

var fullLayout = gd._fullLayout,
Expand All @@ -41,7 +41,7 @@ function draw(gd, minStart, maxStart) {
var height = (fullLayout.height - fullLayout.margin.b - fullLayout.margin.t) * options.thickness,
offsetShift = Math.floor(options.borderwidth / 2);

if(sliderContainer[0].length === 0 && !fullLayout._hasGL2D) createSlider(gd, minStart, maxStart);
if(sliderContainer[0].length === 0 && !fullLayout._hasGL2D) createSlider(gd);

// Need to default to 0 for when making gl plots
var bb = fullLayout.xaxis._boundingBox ?
Expand Down
15 changes: 9 additions & 6 deletions src/components/rangeslider/range_plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ var svgNS = require('../../constants/xmlns_namespaces').svg;

module.exports = function rangePlot(gd, w, h) {

var traces = gd._fullData,
xaxis = gd._fullLayout.xaxis,
yaxis = gd._fullLayout.yaxis,
minX = xaxis.range[0],
maxX = xaxis.range[1],
var fullLayout = gd._fullLayout,
traces = gd._fullData,
xaxis = fullLayout.xaxis,
yaxis = fullLayout.yaxis,
minX = xaxis.rangeslider.range[0],
maxX = xaxis.rangeslider.range[1],
minY = yaxis.range[0],
maxY = yaxis.range[1];

Expand Down Expand Up @@ -62,7 +63,9 @@ module.exports = function rangePlot(gd, w, h) {
var posX = w * (x[k] - minX) / (maxX - minX),
posY = h * (1 - (y[k] - minY) / (maxY - minY));

pointPairs.push([posX, posY]);
if(!isNaN(posX) && !isNaN(posY)) {
pointPairs.push([posX, posY]);
}
}

// more trace type range plots can be added here
Expand Down
6 changes: 5 additions & 1 deletion src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ Plotly.plot = function(gd, data, layout, config) {

function drawAxes() {
// draw ticks, titles, and calculate axis scaling (._b, ._m)
RangeSlider.draw(gd);
return Plotly.Axes.doTicks(gd, 'redraw');
}

Expand Down Expand Up @@ -310,6 +309,7 @@ Plotly.plot = function(gd, data, layout, config) {
Shapes.drawAll(gd);
Plotly.Annotations.drawAll(gd);
Legend.draw(gd);
RangeSlider.draw(gd);
RangeSelector.draw(gd);
}

Expand Down Expand Up @@ -2191,6 +2191,10 @@ Plotly.relayout = function relayout(gd, astr, val) {
docalc = true;
}

if(pleafPlus.indexOf('rangeslider') !== -1) {
docalc = true;
}

// toggling log without autorange: need to also recalculate ranges
// logical XOR (ie are we toggling log)
if(pleaf==='type' && ((parentFull.type === 'log') !== (vi === 'log'))) {
Expand Down
156 changes: 86 additions & 70 deletions src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,91 +110,106 @@ axes.minDtick = function(ax,newDiff,newFirst,allow) {
}
};

axes.doAutoRange = function(ax) {
if(!ax._length) ax.setScale();
axes.getAutoRange = function(ax) {
var newRange = [];

if(ax.autorange && ax._min && ax._max &&
ax._min.length && ax._max.length) {
var minmin = ax._min[0].val,
maxmax = ax._max[0].val,
i;

for(i = 1; i < ax._min.length; i++) {
if(minmin !== maxmax) break;
minmin = Math.min(minmin, ax._min[i].val);
}
for(i = 1; i < ax._max.length; i++) {
if(minmin !== maxmax) break;
maxmax = Math.max(maxmax, ax._max[i].val);
}

var j,minpt,maxpt,minbest,maxbest,dp,dv,
mbest = 0,
axReverse = (ax.range && ax.range[1]<ax.range[0]);
// one-time setting to easily reverse the axis
// when plotting from code
if(ax.autorange==='reversed') {
axReverse = true;
ax.autorange = true;
}
for(i=0; i<ax._min.length; i++) {
minpt = ax._min[i];
for(j=0; j<ax._max.length; j++) {
maxpt = ax._max[j];
dv = maxpt.val-minpt.val;
dp = ax._length-minpt.pad-maxpt.pad;
if(dv>0 && dp>0 && dv/dp > mbest) {
minbest = minpt;
maxbest = maxpt;
mbest = dv/dp;
}
var minmin = ax._min[0].val,
maxmax = ax._max[0].val,
i;

for(i = 1; i < ax._min.length; i++) {
if(minmin !== maxmax) break;
minmin = Math.min(minmin, ax._min[i].val);
}
for(i = 1; i < ax._max.length; i++) {
if(minmin !== maxmax) break;
maxmax = Math.max(maxmax, ax._max[i].val);
}

var j,minpt,maxpt,minbest,maxbest,dp,dv,
mbest = 0,
axReverse = (ax.range && ax.range[1]<ax.range[0]);

// one-time setting to easily reverse the axis
// when plotting from code
if(ax.autorange === 'reversed') {
axReverse = true;
ax.autorange = true;
}

for(i=0; i<ax._min.length; i++) {
minpt = ax._min[i];
for(j=0; j<ax._max.length; j++) {
maxpt = ax._max[j];
dv = maxpt.val-minpt.val;
dp = ax._length-minpt.pad-maxpt.pad;
if(dv>0 && dp>0 && dv/dp > mbest) {
minbest = minpt;
maxbest = maxpt;
mbest = dv/dp;
}
}
if(minmin===maxmax) {
ax.range = axReverse ?
[minmin+1, ax.rangemode!=='normal' ? 0 : minmin-1] :
[ax.rangemode!=='normal' ? 0 : minmin-1, minmin+1];
}
else if(mbest) {
if(ax.type==='linear' || ax.type==='-') {
if(ax.rangemode==='tozero' && minbest.val>=0) {
}

if(minmin === maxmax) {
newRange = axReverse ?
[minmin+1, ax.rangemode!=='normal' ? 0 : minmin-1] :
[ax.rangemode!=='normal' ? 0 : minmin-1, minmin+1];
}
else if(mbest) {
if(ax.type==='linear' || ax.type==='-') {
if(ax.rangemode==='tozero' && minbest.val>=0) {
minbest = {val: 0, pad: 0};
}
else if(ax.rangemode==='nonnegative') {
if(minbest.val - mbest*minbest.pad<0) {
minbest = {val: 0, pad: 0};
}
else if(ax.rangemode==='nonnegative') {
if(minbest.val - mbest*minbest.pad<0) {
minbest = {val: 0, pad: 0};
}
if(maxbest.val<0) {
maxbest = {val: 1, pad: 0};
}
if(maxbest.val<0) {
maxbest = {val: 1, pad: 0};
}

// in case it changed again...
mbest = (maxbest.val-minbest.val) /
(ax._length-minbest.pad-maxbest.pad);
}

ax.range = [
minbest.val - mbest*minbest.pad,
maxbest.val + mbest*maxbest.pad
];
// in case it changed again...
mbest = (maxbest.val-minbest.val) /
(ax._length-minbest.pad-maxbest.pad);
}

// don't let axis have zero size
if(ax.range[0]===ax.range[1]) {
ax.range = [ax.range[0]-1, ax.range[0]+1];
}
newRange = [
minbest.val - mbest*minbest.pad,
maxbest.val + mbest*maxbest.pad
];

// maintain reversal
if(axReverse) {
ax.range.reverse();
}
// don't let axis have zero size
if(newRange[0] === newRange[1]) {
newRange = [newRange[0]-1, newRange[0]+1];
}

// maintain reversal
if(axReverse) {
newRange.reverse();
}
}

return newRange;
};

axes.doAutoRange = function(ax) {
if(!ax._length) ax.setScale();

// TODO do we really need this?
var hasDeps = (ax._min && ax._max && ax._min.length && ax._max.length);

if(ax.autorange && hasDeps) {
ax.range = axes.getAutoRange(ax);

// doAutoRange will get called on fullLayout,
// but we want to report its results back to layout
var axIn = ax._gd.layout[ax._name];

if(!axIn) ax._gd.layout[ax._name] = axIn = {};
if(axIn!==ax) {

if(axIn !== ax) {
axIn.range = ax.range.slice();
axIn.autorange = ax.autorange;
}
Expand Down Expand Up @@ -241,7 +256,8 @@ axes.saveRangeInitial = function(gd, overwrite) {
// and make it a tight bound if possible
var FP_SAFE = Number.MAX_VALUE/2;
axes.expand = function(ax, data, options) {
if(!ax.autorange || !data) return;
// if(!(ax.autorange || (ax.rangeslider || {}).visible) || !data) return;
if(!(ax.autorange || ax._needsExpand) || !data) return;
if(!ax._min) ax._min = [];
if(!ax._max) ax._max = [];
if(!options) options = {};
Expand Down
Binary file added test/image/baselines/range_slider_ranges.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 223c43f

Please sign in to comment.