Skip to content

[DO NOT MERGE] slider-generating transform #1099

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

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ Plotly.register([
//
Plotly.register([
require('./filter'),
require('./groupby')
require('./groupby'),
require('./populate-slider')
]);

module.exports = Plotly;
11 changes: 11 additions & 0 deletions lib/populate-slider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* 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';

module.exports = require('../src/transforms/populate-slider');
2 changes: 2 additions & 0 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ Plotly.plot = function(gd, data, layout, config) {

if(!gd.layout || graphWasEmpty) gd.layout = helpers.cleanLayout(layout);

if(!gd._transitionData) Plots.createTransitionData(gd);

// if the user is trying to drag the axes, allow new data and layout
// to come in but don't allow a replot.
if(gd._dragging && !gd._transitioning) {
Expand Down
7 changes: 7 additions & 0 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,8 @@ plots.purge = function(gd) {
// remove modebar
if(fullLayout._modeBar) fullLayout._modeBar.destroy();

gd._transitionData._interruptCallbacks.length = 0;

if(gd._transitionData && gd._transitionData._animationRaf) {
window.cancelAnimationFrame(gd._transitionData._animationRaf);
}
Expand Down Expand Up @@ -1798,6 +1800,11 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts)
}

function completeTransition(callback) {
// This a simple workaround for tests which purge the graph before animations
// have completed. That's not a very common case, so this is the simplest
// fix.
if(!gd._transitionData) return;

flushCallbacks(gd._transitionData._interruptCallbacks);

return Promise.resolve().then(function() {
Expand Down
259 changes: 259 additions & 0 deletions src/transforms/populate-slider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/**
* 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 supplySliderDefaults = require('../components/sliders/defaults');

exports.moduleType = 'transform';

exports.name = 'populate-slider';

exports.attributes = {
filterindex: {
valType: 'integer',
role: 'info',
dflt: 0,
description: [
'Array index of the filter transform. If not provided, it will use the',
'first available filter transform for this trace'
].join(' ')
},
sliderindex: {
valType: 'integer',
role: 'info',
dflt: 0,
description: [
'Array index of the slider component. If not provided, it will create',
'a new slider in the plot layout'
].join(' ')
},
framegroup: {
valType: 'string',
role: 'info',
description: 'A group name for the generated set of frames'
},
enabled: {
valType: 'boolean',
role: 'info',
dflt: true,
description: 'Whether the transform is ignored or not.'
},
animationopts: {
valType: 'any',
role: 'info'
}
};

exports.supplyDefaults = function(transformIn) {
var transformOut = {};

function coerce(attr, dflt) {
return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt);
}

var enabled = coerce('enabled');

if(enabled) {
coerce('sliderindex');
coerce('filterindex');
coerce('framegroup');
coerce('animationopts');
}

return transformOut;
};

exports.transform = function(dataOut) {
return dataOut;
};

function computeGroups(fullData) {
var i, j, nTrans, trace, transforms, addTransform, filterTransform, target, sliderindex, filterindex;
var allGroupHash = {};
var addTransforms = {};
// iterate through *all* traces looking for anything with an populate-slider transform
for(i = 0; i < fullData.length; i++) {
// Bail out if no transforms:
trace = fullData[i];
if(!trace || !trace.visible) continue;
transforms = fullData[i].transforms;
if(!transforms) continue;
nTrans = transforms.length;

// Find the add-slider transform for this trace:
for(j = 0; j < nTrans; j++) {
addTransform = transforms[j];
if(addTransform.type === exports.name) break;
}

// Bail out if either no add transform or not enabled:
if(j === nTrans || !addTransform.enabled) continue;

sliderindex = addTransform.sliderindex;
filterindex = addTransform.filterindex;

// Find the filter transform for this slider:
if(!filterindex) {
for(filterindex = 0; filterindex < nTrans; filterindex++) {
filterTransform = transforms[filterindex];
if(filterTransform.type === 'filter') break;
}
if(filterindex === nTrans) continue;
addTransform.filterindex = filterindex;
}

// Bail out if this transform is disabled or not handled:
if(!filterTransform.enabled) continue;
if(!Array.isArray((target = filterTransform.target))) continue;
addTransform._filterTransform = filterTransform;

// Store the add transform for later use:
if(!addTransforms[sliderindex]) addTransforms[sliderindex] = {};
addTransforms[sliderindex][trace.index] = addTransform;

if(!allGroupHash[sliderindex]) allGroupHash[sliderindex] = {};
for(j = 0; j < target.length; j++) {
allGroupHash[sliderindex][target[j]] = true;
}
}

return {
bySlider: allGroupHash,
transformsByTrace: addTransforms
};
}

function createSteps(idx, slider, groups, transforms) {
var i, transform;
var traceIndices = Object.keys(transforms);
var animationopts = {};
for(i = 0; i < traceIndices.length; i++) {
transform = transforms[traceIndices[i]];
animationopts = Lib.extendDeep(animationopts, transform.animationopts);
}
if(Array.isArray(slider.steps)) {
slider.steps.length = 0;
} else {
slider.steps = [];
}

// Iterate through all unique target values for this slider step:
for(i = 0; i < groups.length; i++) {
var label = groups[i];
var frameName = 'slider-' + idx + '-' + label;

slider.steps[i] = {
label: label,
value: label,
args: [[frameName], animationopts],
method: 'animate',
};
}
}

function computeFrameGroup(sliderindex, transforms) {
var i, framegroup;
var keys = Object.keys(transforms);
for(i = 0; i < keys.length; i++) {
framegroup = transforms[keys[i]].framegroup;
if(framegroup) return framegroup;
}

return 'populate-slider-group-' + sliderindex;
}

function createFrames(sliderindex, framegroup, groups, transforms, transitionData) {
var i, j, group, frame, frameIndex, transform;

var traceIndices = Object.keys(transforms);
var frameIndices = {};
var frames = transitionData._frames;
var existingFrameIndices = [];
for(i = 0; i < frames.length; i++) {
if(frames[i] === null || frames[i].group === framegroup) {
existingFrameIndices.push(i);
}
}

// Now create the frames:
for(i = 0; i < groups.length; i++) {
group = groups[i];
frame = frames[existingFrameIndices[i]] || {
name: 'slider-' + sliderindex + '-' + groups[i],
group: framegroup
};

frameIndex = existingFrameIndices[i];
if(frameIndex === undefined) {
frameIndex = frames.length;
}
frameIndices[group] = frameIndex;

// Overwrite the frame at this position with the frame corresponding
// to this frame of groups:
frames[frameIndex] = frame;

if(!frame.data) frame.data = [];
if(!frame.traces) frame.traces = [];
for(j = 0; j < traceIndices.length; j++) {
transform = transforms[traceIndices[j]];
frame.traces[j] = parseInt(traceIndices[j]);
frame.data[j] = {};
frame.data[j]['transforms[' + transform.filterindex + '].value'] = [group];
}
}

// null out the remaining frames that were created by this transform
for(i = groups.length; i < existingFrameIndices.length; i++) {
frames[existingFrameIndices[i]] = null;
}

recomputeFrameHash(transitionData);
}

function recomputeFrameHash(transitionData) {
var frame;
var frames = transitionData._frames;
var hash = transitionData._frameHash = {};
for(var i = 0; i < frames.length; i++) {
frame = frames[i];
if(frame && frame.name) {
hash[frame.name] = frame;
}
}
}

exports.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData, transitionData) {
var sliders, i, sliderindex, slider, transforms, framegroup;

var groups = computeGroups(fullData);
var sliderIndices = Object.keys(groups.bySlider);

// Bail out if there are no options:
if(!sliderIndices.length) return layoutIn;

sliders = layoutIn.sliders = layoutIn.sliders || [];

for(i = 0; i < sliderIndices.length; i++) {
sliderindex = sliderIndices[i];
slider = sliders[sliderindex] = sliders[sliderindex] || {};
transforms = groups.transformsByTrace[sliderindex];
groups = Object.keys(groups.bySlider[sliderindex]);

framegroup = computeFrameGroup(sliderindex, transforms);

createSteps(sliderindex, slider, groups, transforms);
createFrames(sliderindex, framegroup, groups, transforms, transitionData);
}

supplySliderDefaults(layoutIn, layoutOut);

return layoutIn;
};
Loading