Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit effaa6d

Browse files
committed
Add fallbacks for manual manipulation of slider/frames
1 parent 2dbdf07 commit effaa6d

File tree

3 files changed

+108
-8
lines changed

3 files changed

+108
-8
lines changed

src/components/sliders/draw.js

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,18 @@ module.exports = function draw(gd) {
7272
computeLabelSteps(sliderOpts);
7373

7474
Plots.manageCommandObserver(gd, sliderOpts, sliderOpts.steps, function(data) {
75-
if(sliderOpts.active === data.index) return;
76-
if(sliderOpts._dragging) return;
77-
78-
setActive(gd, gSlider, sliderOpts, data.index, false, true);
75+
// NB: Same as below. This is *not* always the same as sliderOpts since
76+
// if a new set of steps comes in, the reference in this callback would
77+
// be invalid. We need to refetch it from the slider group, which is
78+
// the join data that creates this slider. So if this slider still exists,
79+
// the group should be valid, *to the best of my knowledge.* If not,
80+
// we'd have to look it up by d3 data join index/key.
81+
var opts = gSlider.data()[0];
82+
83+
if(opts.active === data.index) return;
84+
if(opts._dragging) return;
85+
86+
setActive(gd, gSlider, opts, data.index, false, true);
7987
});
8088

8189
drawSlider(gd, d3.select(this), sliderOpts);
@@ -225,6 +233,15 @@ function findDimensions(gd, sliderOpts) {
225233
}
226234

227235
function drawSlider(gd, sliderGroup, sliderOpts) {
236+
// This is related to the other long notes in this file regarding what happens
237+
// when slider steps disappear. This particular fix handles what happens when
238+
// the *current* slider step is removed. The drawing functions will error out
239+
// when they fail to find it, so the fix for now is that it will just draw the
240+
// slider in the first position but will not execute the command.
241+
if(sliderOpts.active >= sliderOpts.steps.length) {
242+
sliderOpts.active = 0;
243+
}
244+
228245
// These are carefully ordered for proper z-ordering:
229246
sliderGroup
230247
.call(drawCurrentValue, sliderOpts)
@@ -251,9 +268,9 @@ function drawCurrentValue(sliderGroup, sliderOpts, valueOverride) {
251268

252269
switch(sliderOpts.currentvalue.xanchor) {
253270
case 'right':
254-
// This is anchored left and adjusted by the width of the longest label
255-
// so that the prefix doesn't move. The goal of this is to emphasize
256-
// what's actually changing and make the update less distracting.
271+
// This is anchored left and adjusted by the width of the longest label
272+
// so that the prefix doesn't move. The goal of this is to emphasize
273+
// what's actually changing and make the update less distracting.
257274
x0 = sliderOpts.inputAreaLength - constants.currentValueInset - sliderOpts.currentValueMaxWidth;
258275
textAnchor = 'left';
259276
break;
@@ -402,11 +419,21 @@ function setActive(gd, sliderGroup, sliderOpts, index, doCallback, doTransition)
402419
}
403420
}
404421

405-
function attachGripEvents(item, gd, sliderGroup, sliderOpts) {
422+
function attachGripEvents(item, gd, sliderGroup) {
406423
var node = sliderGroup.node();
407424
var $gd = d3.select(gd);
408425

426+
// NB: This is *not* the same as sliderOpts itself! These callbacks
427+
// are in a closure so this array won't actually be correct if the
428+
// steps have changed since this was initialized. The sliderGroup,
429+
// however, has not changed since that *is* the slider, so it must
430+
// be present to receive mouse events.
431+
function getSliderOpts() {
432+
return sliderGroup.data()[0];
433+
}
434+
409435
item.on('mousedown', function() {
436+
var sliderOpts = getSliderOpts();
410437
gd.emit('plotly_sliderstart', {slider: sliderOpts});
411438

412439
var grip = sliderGroup.select('.' + constants.gripRectClass);
@@ -420,11 +447,13 @@ function attachGripEvents(item, gd, sliderGroup, sliderOpts) {
420447
sliderOpts._dragging = true;
421448

422449
$gd.on('mousemove', function() {
450+
var sliderOpts = getSliderOpts();
423451
var normalizedPosition = positionToNormalizedValue(sliderOpts, d3.mouse(node)[0]);
424452
handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, false);
425453
});
426454

427455
$gd.on('mouseup', function() {
456+
var sliderOpts = getSliderOpts();
428457
sliderOpts._dragging = false;
429458
grip.call(Color.fill, sliderOpts.bgcolor);
430459
$gd.on('mouseup', null);

src/plots/plots.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,6 +1546,23 @@ plots.computeFrame = function(gd, frameName) {
15461546
return result;
15471547
};
15481548

1549+
/*
1550+
* Recompute the lookup table that maps frame name -> frame object. addFrames/
1551+
* deleteFrames already manages this data one at a time, so the only time this
1552+
* is necessary is if you poke around manually in `gd._transitionData_frames`
1553+
* and create and haven't updated the lookup table.
1554+
*/
1555+
plots.recomputeFrameHash = function(gd) {
1556+
var hash = gd._transitionData._frameHash = {};
1557+
var frames = gd._transitionData._frames;
1558+
for(var i = 0; i < frames.length; i++) {
1559+
var frame = frames[i];
1560+
if(frame && frame.name) {
1561+
hash[frame.name] = frame;
1562+
}
1563+
}
1564+
};
1565+
15491566
/**
15501567
* Extend an object, treating container arrays very differently by extracting
15511568
* their contents and merging them separately.

test/jasmine/tests/sliders_test.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ describe('sliders initialization', function() {
191191

192192
Plotly.plot(gd, [{x: [1, 2, 3]}], {
193193
sliders: [{
194+
transition: {duration: 0},
194195
steps: [
195196
{method: 'restyle', args: [], label: 'first'},
196197
{method: 'restyle', args: [], label: 'second'},
@@ -209,6 +210,59 @@ describe('sliders initialization', function() {
209210
});
210211
});
211212

213+
describe('ugly internal manipulation of steps', function() {
214+
'use strict';
215+
var gd;
216+
217+
beforeEach(function(done) {
218+
gd = createGraphDiv();
219+
220+
Plotly.plot(gd, [{x: [1, 2, 3]}], {
221+
sliders: [{
222+
transition: {duration: 0},
223+
steps: [
224+
{method: 'restyle', args: [], label: 'first'},
225+
{method: 'restyle', args: [], label: 'second'},
226+
]
227+
}]
228+
}).then(done);
229+
});
230+
231+
afterEach(function() {
232+
Plotly.purge(gd);
233+
destroyGraphDiv();
234+
});
235+
236+
it('adds and removes slider steps gracefully', function(done) {
237+
expect(gd._fullLayout.sliders[0].active).toEqual(0);
238+
239+
// Set the active index higher than it can go:
240+
Plotly.relayout(gd, {'sliders[0].active': 2}).then(function() {
241+
// Confirm nothing changed
242+
expect(gd._fullLayout.sliders[0].active).toEqual(0);
243+
244+
// Add an option manually without calling API functions:
245+
gd.layout.sliders[0].steps.push({method: 'restyle', args: [], label: 'first'});
246+
247+
// Now that it's been added, restyle and try again:
248+
return Plotly.relayout(gd, {'sliders[0].active': 2});
249+
}).then(function() {
250+
// Confirm it's been changed:
251+
expect(gd._fullLayout.sliders[0].active).toEqual(2);
252+
253+
// Remove the option:
254+
gd.layout.sliders[0].steps.pop();
255+
256+
// And redraw the plot:
257+
return Plotly.redraw(gd);
258+
}).then(function() {
259+
// The selected option no longer exists, so confirm it's
260+
// been fixed during the process of updating/drawing it:
261+
expect(gd._fullLayout.sliders[0].active).toEqual(0);
262+
}).catch(fail).then(done);
263+
});
264+
});
265+
212266
describe('sliders interactions', function() {
213267
'use strict';
214268

0 commit comments

Comments
 (0)