Skip to content
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

Preserve ui state across Plotly.react redraws #3236

Merged
merged 35 commits into from
Nov 30, 2018
Merged
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3a70fee
make hover label test independent of selection order
alexcjohnson Nov 3, 2018
a86ca58
fix and :lock: bug with impliedEdits and groupby transform
alexcjohnson Oct 31, 2018
4ca328a
remove impliedEdits that got forgotten in #3044
alexcjohnson Oct 31, 2018
082ac38
alias `Lib.nestedProperty` separately in plot_api
alexcjohnson Oct 31, 2018
b134a52
fix obscure undo/redo bug with container array relayout removal
alexcjohnson Oct 31, 2018
c62cf25
clean up handleHover3d
alexcjohnson Nov 2, 2018
a11ec44
add _gui(Restyle|Relayout|Update) and _storeDirectGUIEdit and track c…
alexcjohnson Nov 2, 2018
10ddbb9
fix and :lock: problem with dragging colorbars in sub-containers
alexcjohnson Nov 6, 2018
4af2bf8
add and coerce uirevision attributes
alexcjohnson Nov 8, 2018
03baca7
use uirevisions in Plotly.react to preserve ui state
alexcjohnson Nov 9, 2018
6a9d0d7
add selectedpoints to uirevision framework
alexcjohnson Nov 9, 2018
a443747
break Plotly.react tests out of plot_api_test into plot_api_react_test
alexcjohnson Nov 9, 2018
54304b4
test uirevision + react logic
alexcjohnson Nov 9, 2018
86b90d4
remove `apply` arg from _storeDirectGUIEdit
alexcjohnson Nov 10, 2018
502082f
:palm_tree: uirevision tests
alexcjohnson Nov 10, 2018
5182f6a
fix and :lock: polar uirevisions
alexcjohnson Nov 10, 2018
89dde84
:lock: ternary uirevisions
alexcjohnson Nov 10, 2018
778ec15
fix and :lock: mapbox uirevisions
alexcjohnson Nov 10, 2018
21f5db5
fix and :lock: editable shapes & annotations uirevision
alexcjohnson Nov 11, 2018
8f0f075
:palm_tree: uirevision tests
alexcjohnson Nov 11, 2018
eb728fe
fix and :lock: editable: true uirevisions
alexcjohnson Nov 11, 2018
c95ae07
revert form of rangeslider relayout call (but it's still _guiRelayout)
alexcjohnson Nov 12, 2018
7ab8630
comment on uirevision<->uid in trace.uirevision attribute description
alexcjohnson Nov 12, 2018
756308f
record real initial axis (auto)range so we can update to autorange
alexcjohnson Nov 13, 2018
874a72a
remove now-obsolete comment about autorange/autofill
alexcjohnson Nov 13, 2018
090231b
fix and :lock: selectionrevision with groupby
alexcjohnson Nov 13, 2018
5a27c00
update trace.uirevision description with what it does/doesn't control
alexcjohnson Nov 13, 2018
2034941
partial preGUI fix for 3D camera
alexcjohnson Nov 19, 2018
6cfe7c5
Merge branch 'master' into ui-key
alexcjohnson Nov 29, 2018
8d11985
write camera.up back to layout only when necessary
alexcjohnson Nov 29, 2018
d9b3aea
Merge branch 'master' into ui-key
alexcjohnson Nov 29, 2018
0621e43
update uirevisions for new title attribute structure #3276
alexcjohnson Nov 30, 2018
bdef7d3
more permissive tickson rotation test for AJ's machine
alexcjohnson Nov 30, 2018
2b77911
looser acceptance for title centering, to work on AJ's machine
alexcjohnson Nov 30, 2018
067bd7d
biger tickwidth difference in tickson rotation test
alexcjohnson Nov 30, 2018
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
189 changes: 189 additions & 0 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2362,6 +2362,193 @@ exports._guiRestyle = guiEdit(restyle);
exports._guiRelayout = guiEdit(relayout);
exports._guiUpdate = guiEdit(update);

// For connecting edited layout attributes to uirevision attrs
// If no `attr` we use `match[1] + '.uirevision'`
// Ordered by most common edits first, to minimize our search time
var layoutUIControlPatterns = [
{pattern: /^hiddenlabels/, attr: 'legend.uirevision'},
{pattern: /^((x|y)axis\d*)\.((auto)?range|title)/, autofill: true},

// showspikes and modes include those nested inside scenes
{pattern: /axis\d*\.showspikes$/, attr: 'modebar.uirevision'},
{pattern: /(hover|drag)mode$/, attr: 'modebar.uirevision'},

{pattern: /^(scene\d*)\.camera/},
{pattern: /^(geo\d*)\.(projection|center)/},
{pattern: /^(ternary\d*\.[abc]axis)\.(min|title)$/},
{pattern: /^(polar\d*\.(radial|angular)axis)\./},
{pattern: /^(mapbox\d*)\.(center|zoom|bearing|pitch)/},

{pattern: /^legend\.(x|y)$/, attr: 'editrevision'},
{pattern: /^(shapes|annotations)/, attr: 'editrevision'},
{pattern: /^title$/, attr: 'editrevision'}
];
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved

// same for trace attributes: if `attr` is given it's in layout,
// or with no `attr` we use `trace.uirevision`
var traceUIControlPatterns = [
// "visible" includes trace.transforms[i].styles[j].value.visible
{pattern: /(^|value\.)visible$/, attr: 'legend.uirevision'},
{pattern: /^dimensions\[\d+\]\.constraintrange/},

// below this you must be in editable: true mode
// TODO: I still put name and title with `trace.uirevision`
// reasonable or should these be `editrevision`?
// Also applies to axis titles up in the layout section

// "name" also includes transform.styles
{pattern: /(^|value\.)name$/},
// including nested colorbar attributes (ie marker.colorbar)
{pattern: /colorbar\.title$/},
{pattern: /colorbar\.(x|y)$/, attr: 'editrevision'}
];

function findUIPattern(key, patternSpecs) {
for(var i = 0; i < patternSpecs.length; i++) {
var spec = patternSpecs[i];
var match = key.match(spec.pattern);
if(match) {
return {head: match[1], attr: spec.attr, autofill: spec.autofill};
}
}
}

// We're finding the new uirevision before supplyDefaults, so do the
// inheritance manually. Note that only `undefined` inherits - other
// falsy values are returned.
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
function getNewRev(revAttr, container) {
var newRev = nestedProperty(container, revAttr).get();
if(newRev !== undefined) return newRev;

var parts = revAttr.split('.');
parts.pop();
while(parts.length > 1) {
parts.pop();
newRev = nestedProperty(container, parts.join('.') + '.uirevision').get();
if(newRev !== undefined) return newRev;
}

return container.uirevision;
}

function getFullTraceIndexFromUid(uid, fullData) {
for(var i = 0; i < fullData.length; i++) {
if(fullData[i]._fullInput.uid === uid) return i;
}
return -1;
}

function getTraceIndexFromUid(uid, data, tracei) {
for(var i = 0; i < data.length; i++) {
if(data[i].uid === uid) return i;
}
// fall back on trace order, but only if user didn't provide a uid for that trace
return data[tracei].uid ? -1 : tracei;
}

function applyUIRevisions(data, layout, oldFullData, oldFullLayout) {
var layoutPreGUI = oldFullLayout._preGUI;
var key, revAttr, oldRev, newRev, match, preGUIVal, newNP, newVal;
for(key in layoutPreGUI) {
match = findUIPattern(key, layoutUIControlPatterns);
if(match) {
revAttr = match.attr || (match.head + '.uirevision');
oldRev = nestedProperty(oldFullLayout, revAttr).get();
newRev = oldRev && getNewRev(revAttr, layout);
if(newRev && (newRev === oldRev)) {
preGUIVal = layoutPreGUI[key];
if(preGUIVal === null) preGUIVal = undefined;
newNP = nestedProperty(layout, key);
newVal = newNP.get();
// TODO: This test for undefined is to account for the case where
// the value was filled in automatically in gd.layout,
// like axis.range/autorange. In principle though, if the initial
// plot had a value and the new plot removed that value, we would
// want the removal to override the GUI edit and generate a new
// auto value. But that would require figuring out what value was
// in gd.layout *before* the auto values were filled in, and
// storing *that* in preGUI... oh well, for now at least I limit
// this to attributes that get autofilled, which AFAICT among
// the GUI-editable attributes is just axis.range/autorange.
etpinard marked this conversation as resolved.
Show resolved Hide resolved
if(newVal === preGUIVal || (match.autofill && newVal === undefined)) {
newNP.set(nestedProperty(oldFullLayout, key).get());
continue;
}
}
}
else {
Lib.warn('unrecognized GUI edit: ' + key);
}
// if we got this far, the new value was accepted as the new starting
// point (either because it changed or revision changed)
// so remove it from _preGUI for next time.
delete layoutPreGUI[key];
}

// Now traces - try to match them up by uid (in case we added/deleted in
// the middle), then fall back on index.
// var tracei = -1;
// for(var fulli = 0; fulli < oldFullData.length; fulli++) {
var allTracePreGUI = oldFullLayout._tracePreGUI;
for(var uid in allTracePreGUI) {
var tracePreGUI = allTracePreGUI[uid];
var newTrace = null;
var fullInput;
for(key in tracePreGUI) {
// wait until we know we have preGUI values to look for traces
// but if we don't find both, stop looking at this uid
if(!newTrace) {
var fulli = getFullTraceIndexFromUid(uid, oldFullData);
if(fulli < 0) {
// Somehow we didn't even have this trace in oldFullData...
// I guess this could happen with `deleteTraces` or something
delete allTracePreGUI[uid];
break;
}
var fullTrace = oldFullData[fulli];
fullInput = fullTrace._fullInput;

var newTracei = getTraceIndexFromUid(uid, data, fullInput.index);
if(newTracei < 0) {
// No match in new data
delete allTracePreGUI[uid];
break;
}
newTrace = data[newTracei];
}

match = findUIPattern(key, traceUIControlPatterns);
if(match) {
if(match.attr) {
oldRev = nestedProperty(oldFullLayout, match.attr).get();
newRev = oldRev && getNewRev(match.attr, layout);
}
else {
oldRev = fullInput.uirevision;
// inheritance for trace.uirevision is simple, just layout.uirevision
newRev = newTrace.uirevision;
if(newRev === undefined) newRev = layout.uirevision;
}

if(newRev && newRev === oldRev) {
preGUIVal = tracePreGUI[key];
if(preGUIVal === null) preGUIVal = undefined;
newNP = nestedProperty(newTrace, key);
newVal = newNP.get();
if(newVal === preGUIVal || (match.autofill && newVal === undefined)) {
newNP.set(nestedProperty(fullInput, key).get());
continue;
}
}
}
else {
Lib.warn('unrecognized GUI edit: ' + key + ' in trace uid ' + uid);
}
delete tracePreGUI[key];
}
}
}

/**
* Plotly.react:
* A plot/update method that takes the full plot state (same API as plot/newPlot)
Expand Down Expand Up @@ -2424,6 +2611,8 @@ exports.react = function(gd, data, layout, config) {
gd.layout = layout || {};
helpers.cleanLayout(gd.layout);

applyUIRevisions(gd.data, gd.layout, oldFullData, oldFullLayout);

// "true" skips updating calcdata and remapping arrays from calcTransforms,
// which supplyDefaults usually does at the end, but we may need to NOT do
// if the diff (which we haven't determined yet) says we'll recalc
Expand Down