Skip to content

Make frame with nulls clear items & array containers - #1118

Merged
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls
Nov 11, 2016
Merged

Make frame with nulls clear items & array containers#1118
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls

Conversation

@etpinard

Copy link
Copy Markdown
Contributor

Proof of concept PR, attempting to address #1081 (comment) demonstrated in http://codepen.io/etpinard/pen/WoNryW and extending what #1041 put forward.

I'm looking for @rreusser's opinion.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rreusser what do you think of ⏬

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking now…

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@rreusserrreusser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. I think it makes sense. I'd also be okay with setting empty items to null, but I think that would require a bit more work to make sure nothing crashes when it references a property on null. 💃

Comment threadsrc/plots/plots.js Outdated
containerProp.set(null);
Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal);

if(!containerVal) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working through the logic. Just a note that false, undefined, null, and 0 will all trigger this condition. This is fine because this could only reasonably be an object or not an object, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine because this could only reasonably be an object or not an object, right?

Correct. That's what I'm thinking.

Maybe we could be slightly more strict and make this condition if(containerVal === undefined) which would be fulfilled when { annotations: undefined } and { annotations: null } but not for other falsy values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea in general that null is equivalent to "deliberately unset" as opposed to undefined which means "happens to not be specified," but I'm not fully aware of the implications here. I think most people would expect to be able to use them more or less equivalently, but I can see the argument for requiring explicit null in order to unset.

Comment threadsrc/plots/plots.js Outdated
destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcContainer[j]);
var srcObj = srcContainer[j];

if(srcObj === null) destContainer[j] = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. Could be null, but seems the most robust/backwards-compatible if it's at least an object.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad we agree here.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, what should non-plain-object items in layout array containers be coerced to?

See current behavior here: http://codepen.io/etpinard/pen/xRGgwW?

I'd vote for completely skipping over non-plain-object items (i.e. non-plain-object items won't show up in fullLayout). Any objections? Some may interpret this change as backward incompatible. @alexcjohnson thoughts?

Currently, updatemenus and sliders skip over non-plain-object buttons and step items (see here and here).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @bpostlethwaite too ⬆️

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote for completely skipping over non-plain-object items

It's always a mistake / error when we get values like this, right? rather than something with a real use case? Seems like the priorities are first don't break anything else so the rest of the plot still works, and second try to help the user figure out what they did wrong.

So erroring out is the wrong thing to do (don't break anything else - good catch!) but then we can either skip non-objects (as updatemenus.buttons does) or pretend they're empty objects (as updatemenus does). I kind of feel like pretending they're empty objects is better, because then the user can look at the array item by item and see what happened to their input. Also if we have anything that references these items by index it would still match up (though I don't know of anywhere we do that).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because then the user can look at the array item by item and see what happened to their input

good point here.

What if non-plain-object item were coerced to { visible: false } instead? Unless you can think of a situation were http://codepen.io/etpinard/pen/ENjdBX may be useful.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if non-plain-object item were coerced to { visible: false }

Sure, if we've guaranteed that every container supports visible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transforms are a container that use {enabled: false}, right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just say if(!Lib.isPlainObject(containerIn)) containerIn = {}; and let the ensuing coerce logic sort it out? Are there any containers that would end up visible/enabled if given an empty input? I guess maybe annotations and shapes, but this isn't useful for anyone, it was just a shortcut for creating them in the workspace... we could unwind that without causing any problems. But the point is, it shouldn't be a coerce (setting attributes of containerOut), it should just be replacing containerIn for the purpose of the logic that follows.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@etpinardetpinard added this to the v1.20.0 milestone Nov 7, 2016
Comment threadtest/jasmine/tests/plots_test.js Outdated
Plots.extendLayout(dest, src);

expect(dest).toEqual({
annotations: [{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like what I'd expect. 👍

});
});

it('clears container items when applying null src items', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks like what I'd expect. Am I correct in understanding that [undefined, undefined] would skip applying any changes?

});

it('clears container applying null src', function() {
var dest = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Ditto on behavior of undefined?

@rreusser

rreusser commented Nov 9, 2016

Copy link
Copy Markdown
Contributor

On my first pass through this I was focusing on the code but should have thought more about the tests. The behavior looks good to me, but some of the details here are still a little tricky. And if they're tricky for us, they're definitely tricky for users. Of course it's rather obscure use-cases so I think the key is that it's at least consistent/intuitive. @etpinard did you mention you were drying this up? Are there other thoughts or details I can help with? Other use-cases blocking this?

}
}

if(layout.annotations !== undefined && !Array.isArray(layout.annotations)) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this commit was enough to fix bug discovered in http://codepen.io/etpinard/pen/xRGgwW

* in handleItemDefaults relies on that fact.
*
*/
module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) {

@etpinardetpinardNov 10, 2016

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to plots/subplot_defaults.

The logic below is pretty trivial, but might as well 🔒 down the behavior for all array containers.

containerOut = layoutOut.annotations = [];
var opts = {
name: 'annotations',
handleItemDefaults: handleAnnotationDefaults

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌴

};

var expected = {
container: [null, null]

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N.B. I changed the behavior here as discussed in #1118 (comment) - see 36859f1

In brief, frames with array containers set to null extend the state with null. It is now up to the subsequent supplyDefaults calls to coerced those null items into {}, as in regular Plotly.plot calls.

@etpinardetpinard added the bug something broken label Nov 10, 2016
Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Annotations must be an array.');
delete layout.annotations;
}
var annotationsLen = (layout.annotations || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in handleArrayContainerDefaults you do Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [] - you want to do that here too so we still keep going if annotations isn't even an array?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch here. Thanks!

The only (I think) case where cont || [] vs Array.isArray(cont) ? ... matters is when someone inputs a string instead of an array.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, true.length doesn't break 😮

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Shapes must be an array.');
delete layout.shapes;
}
var shapesLen = (layout.shapes || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array.isArray again... I guess if you test this situation you may find even more of these...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

* links to supplementary data (e.g. fullData for layout components)
*
* - opts.itemIsNotPlainObject is mutated on every pass in case so logic
* in handleItemDefaults relies on that fact.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a little odd to me. Do you ever see a case where other parts of opts would need to be accessible to the item handler (or is there one already that I didn't notice?), or could we just pass itemIsNotPlainObject by itself as the last arg to the handler?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll make this the last arg.

I've ran into some problem with our multi-argument internal function lately, but yeah you're right, mutating opts is stupid.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 5de6ff0

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice unification. My usage comment (passing opts vs just itemIsNotPlainObject) is nonblocking, but I'm thinking we should poke even a little more into making sure we keep going no matter what garbage folks throw in (ie the comment about the container itself not being an array).

}

opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts);
opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that's fine. I still don't quite see where the handler will use the original opts but I guess it doesn't hurt to leave it in.

@alexcjohnson

Copy link
Copy Markdown
Collaborator

💃

@etpinard
etpinard merged commit 2897167 into masterNov 11, 2016
@etpinard
etpinard deleted the frame-extend-with-nulls branch November 11, 2016 15:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugsomething brokenfeaturesomething new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@etpinard@rreusser@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Make frame with nulls clear items & array containers by etpinard · Pull Request #1118 · plotly/plotly.js · GitHub
Skip to content

Make frame with nulls clear items & array containers - #1118

Merged
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls
Nov 11, 2016
Merged

Make frame with nulls clear items & array containers#1118
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls

Conversation

@etpinard

Copy link
Copy Markdown
Contributor

Proof of concept PR, attempting to address #1081 (comment) demonstrated in http://codepen.io/etpinard/pen/WoNryW and extending what #1041 put forward.

I'm looking for @rreusser's opinion.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rreusser what do you think of ⏬

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking now…

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@rreusserrreusser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. I think it makes sense. I'd also be okay with setting empty items to null, but I think that would require a bit more work to make sure nothing crashes when it references a property on null. 💃

Comment threadsrc/plots/plots.js Outdated
containerProp.set(null);
Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal);

if(!containerVal) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working through the logic. Just a note that false, undefined, null, and 0 will all trigger this condition. This is fine because this could only reasonably be an object or not an object, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine because this could only reasonably be an object or not an object, right?

Correct. That's what I'm thinking.

Maybe we could be slightly more strict and make this condition if(containerVal === undefined) which would be fulfilled when { annotations: undefined } and { annotations: null } but not for other falsy values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea in general that null is equivalent to "deliberately unset" as opposed to undefined which means "happens to not be specified," but I'm not fully aware of the implications here. I think most people would expect to be able to use them more or less equivalently, but I can see the argument for requiring explicit null in order to unset.

Comment threadsrc/plots/plots.js Outdated
destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcContainer[j]);
var srcObj = srcContainer[j];

if(srcObj === null) destContainer[j] = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. Could be null, but seems the most robust/backwards-compatible if it's at least an object.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad we agree here.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, what should non-plain-object items in layout array containers be coerced to?

See current behavior here: http://codepen.io/etpinard/pen/xRGgwW?

I'd vote for completely skipping over non-plain-object items (i.e. non-plain-object items won't show up in fullLayout). Any objections? Some may interpret this change as backward incompatible. @alexcjohnson thoughts?

Currently, updatemenus and sliders skip over non-plain-object buttons and step items (see here and here).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @bpostlethwaite too ⬆️

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote for completely skipping over non-plain-object items

It's always a mistake / error when we get values like this, right? rather than something with a real use case? Seems like the priorities are first don't break anything else so the rest of the plot still works, and second try to help the user figure out what they did wrong.

So erroring out is the wrong thing to do (don't break anything else - good catch!) but then we can either skip non-objects (as updatemenus.buttons does) or pretend they're empty objects (as updatemenus does). I kind of feel like pretending they're empty objects is better, because then the user can look at the array item by item and see what happened to their input. Also if we have anything that references these items by index it would still match up (though I don't know of anywhere we do that).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because then the user can look at the array item by item and see what happened to their input

good point here.

What if non-plain-object item were coerced to { visible: false } instead? Unless you can think of a situation were http://codepen.io/etpinard/pen/ENjdBX may be useful.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if non-plain-object item were coerced to { visible: false }

Sure, if we've guaranteed that every container supports visible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transforms are a container that use {enabled: false}, right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just say if(!Lib.isPlainObject(containerIn)) containerIn = {}; and let the ensuing coerce logic sort it out? Are there any containers that would end up visible/enabled if given an empty input? I guess maybe annotations and shapes, but this isn't useful for anyone, it was just a shortcut for creating them in the workspace... we could unwind that without causing any problems. But the point is, it shouldn't be a coerce (setting attributes of containerOut), it should just be replacing containerIn for the purpose of the logic that follows.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@etpinardetpinard added this to the v1.20.0 milestone Nov 7, 2016
Comment threadtest/jasmine/tests/plots_test.js Outdated
Plots.extendLayout(dest, src);

expect(dest).toEqual({
annotations: [{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like what I'd expect. 👍

});
});

it('clears container items when applying null src items', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks like what I'd expect. Am I correct in understanding that [undefined, undefined] would skip applying any changes?

});

it('clears container applying null src', function() {
var dest = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Ditto on behavior of undefined?

@rreusser

rreusser commented Nov 9, 2016

Copy link
Copy Markdown
Contributor

On my first pass through this I was focusing on the code but should have thought more about the tests. The behavior looks good to me, but some of the details here are still a little tricky. And if they're tricky for us, they're definitely tricky for users. Of course it's rather obscure use-cases so I think the key is that it's at least consistent/intuitive. @etpinard did you mention you were drying this up? Are there other thoughts or details I can help with? Other use-cases blocking this?

}
}

if(layout.annotations !== undefined && !Array.isArray(layout.annotations)) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this commit was enough to fix bug discovered in http://codepen.io/etpinard/pen/xRGgwW

* in handleItemDefaults relies on that fact.
*
*/
module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) {

@etpinardetpinardNov 10, 2016

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to plots/subplot_defaults.

The logic below is pretty trivial, but might as well 🔒 down the behavior for all array containers.

containerOut = layoutOut.annotations = [];
var opts = {
name: 'annotations',
handleItemDefaults: handleAnnotationDefaults

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌴

};

var expected = {
container: [null, null]

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N.B. I changed the behavior here as discussed in #1118 (comment) - see 36859f1

In brief, frames with array containers set to null extend the state with null. It is now up to the subsequent supplyDefaults calls to coerced those null items into {}, as in regular Plotly.plot calls.

@etpinardetpinard added the bug something broken label Nov 10, 2016
Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Annotations must be an array.');
delete layout.annotations;
}
var annotationsLen = (layout.annotations || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in handleArrayContainerDefaults you do Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [] - you want to do that here too so we still keep going if annotations isn't even an array?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch here. Thanks!

The only (I think) case where cont || [] vs Array.isArray(cont) ? ... matters is when someone inputs a string instead of an array.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, true.length doesn't break 😮

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Shapes must be an array.');
delete layout.shapes;
}
var shapesLen = (layout.shapes || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array.isArray again... I guess if you test this situation you may find even more of these...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

* links to supplementary data (e.g. fullData for layout components)
*
* - opts.itemIsNotPlainObject is mutated on every pass in case so logic
* in handleItemDefaults relies on that fact.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a little odd to me. Do you ever see a case where other parts of opts would need to be accessible to the item handler (or is there one already that I didn't notice?), or could we just pass itemIsNotPlainObject by itself as the last arg to the handler?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll make this the last arg.

I've ran into some problem with our multi-argument internal function lately, but yeah you're right, mutating opts is stupid.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 5de6ff0

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice unification. My usage comment (passing opts vs just itemIsNotPlainObject) is nonblocking, but I'm thinking we should poke even a little more into making sure we keep going no matter what garbage folks throw in (ie the comment about the container itself not being an array).

}

opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts);
opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that's fine. I still don't quite see where the handler will use the original opts but I guess it doesn't hurt to leave it in.

@alexcjohnson

Copy link
Copy Markdown
Collaborator

💃

@etpinard
etpinard merged commit 2897167 into masterNov 11, 2016
@etpinard
etpinard deleted the frame-extend-with-nulls branch November 11, 2016 15:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugsomething brokenfeaturesomething new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@etpinard@rreusser@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Make frame with nulls clear items & array containers by etpinard · Pull Request #1118 · plotly/plotly.js · GitHub
Skip to content

Make frame with nulls clear items & array containers - #1118

Merged
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls
Nov 11, 2016
Merged

Make frame with nulls clear items & array containers#1118
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls

Conversation

@etpinard

Copy link
Copy Markdown
Contributor

Proof of concept PR, attempting to address #1081 (comment) demonstrated in http://codepen.io/etpinard/pen/WoNryW and extending what #1041 put forward.

I'm looking for @rreusser's opinion.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rreusser what do you think of ⏬

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking now…

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@rreusserrreusser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. I think it makes sense. I'd also be okay with setting empty items to null, but I think that would require a bit more work to make sure nothing crashes when it references a property on null. 💃

Comment threadsrc/plots/plots.js Outdated
containerProp.set(null);
Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal);

if(!containerVal) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working through the logic. Just a note that false, undefined, null, and 0 will all trigger this condition. This is fine because this could only reasonably be an object or not an object, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine because this could only reasonably be an object or not an object, right?

Correct. That's what I'm thinking.

Maybe we could be slightly more strict and make this condition if(containerVal === undefined) which would be fulfilled when { annotations: undefined } and { annotations: null } but not for other falsy values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea in general that null is equivalent to "deliberately unset" as opposed to undefined which means "happens to not be specified," but I'm not fully aware of the implications here. I think most people would expect to be able to use them more or less equivalently, but I can see the argument for requiring explicit null in order to unset.

Comment threadsrc/plots/plots.js Outdated
destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcContainer[j]);
var srcObj = srcContainer[j];

if(srcObj === null) destContainer[j] = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. Could be null, but seems the most robust/backwards-compatible if it's at least an object.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad we agree here.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, what should non-plain-object items in layout array containers be coerced to?

See current behavior here: http://codepen.io/etpinard/pen/xRGgwW?

I'd vote for completely skipping over non-plain-object items (i.e. non-plain-object items won't show up in fullLayout). Any objections? Some may interpret this change as backward incompatible. @alexcjohnson thoughts?

Currently, updatemenus and sliders skip over non-plain-object buttons and step items (see here and here).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @bpostlethwaite too ⬆️

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote for completely skipping over non-plain-object items

It's always a mistake / error when we get values like this, right? rather than something with a real use case? Seems like the priorities are first don't break anything else so the rest of the plot still works, and second try to help the user figure out what they did wrong.

So erroring out is the wrong thing to do (don't break anything else - good catch!) but then we can either skip non-objects (as updatemenus.buttons does) or pretend they're empty objects (as updatemenus does). I kind of feel like pretending they're empty objects is better, because then the user can look at the array item by item and see what happened to their input. Also if we have anything that references these items by index it would still match up (though I don't know of anywhere we do that).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because then the user can look at the array item by item and see what happened to their input

good point here.

What if non-plain-object item were coerced to { visible: false } instead? Unless you can think of a situation were http://codepen.io/etpinard/pen/ENjdBX may be useful.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if non-plain-object item were coerced to { visible: false }

Sure, if we've guaranteed that every container supports visible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transforms are a container that use {enabled: false}, right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just say if(!Lib.isPlainObject(containerIn)) containerIn = {}; and let the ensuing coerce logic sort it out? Are there any containers that would end up visible/enabled if given an empty input? I guess maybe annotations and shapes, but this isn't useful for anyone, it was just a shortcut for creating them in the workspace... we could unwind that without causing any problems. But the point is, it shouldn't be a coerce (setting attributes of containerOut), it should just be replacing containerIn for the purpose of the logic that follows.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@etpinardetpinard added this to the v1.20.0 milestone Nov 7, 2016
Comment threadtest/jasmine/tests/plots_test.js Outdated
Plots.extendLayout(dest, src);

expect(dest).toEqual({
annotations: [{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like what I'd expect. 👍

});
});

it('clears container items when applying null src items', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks like what I'd expect. Am I correct in understanding that [undefined, undefined] would skip applying any changes?

});

it('clears container applying null src', function() {
var dest = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Ditto on behavior of undefined?

@rreusser

rreusser commented Nov 9, 2016

Copy link
Copy Markdown
Contributor

On my first pass through this I was focusing on the code but should have thought more about the tests. The behavior looks good to me, but some of the details here are still a little tricky. And if they're tricky for us, they're definitely tricky for users. Of course it's rather obscure use-cases so I think the key is that it's at least consistent/intuitive. @etpinard did you mention you were drying this up? Are there other thoughts or details I can help with? Other use-cases blocking this?

}
}

if(layout.annotations !== undefined && !Array.isArray(layout.annotations)) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this commit was enough to fix bug discovered in http://codepen.io/etpinard/pen/xRGgwW

* in handleItemDefaults relies on that fact.
*
*/
module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) {

@etpinardetpinardNov 10, 2016

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to plots/subplot_defaults.

The logic below is pretty trivial, but might as well 🔒 down the behavior for all array containers.

containerOut = layoutOut.annotations = [];
var opts = {
name: 'annotations',
handleItemDefaults: handleAnnotationDefaults

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌴

};

var expected = {
container: [null, null]

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N.B. I changed the behavior here as discussed in #1118 (comment) - see 36859f1

In brief, frames with array containers set to null extend the state with null. It is now up to the subsequent supplyDefaults calls to coerced those null items into {}, as in regular Plotly.plot calls.

@etpinardetpinard added the bug something broken label Nov 10, 2016
Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Annotations must be an array.');
delete layout.annotations;
}
var annotationsLen = (layout.annotations || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in handleArrayContainerDefaults you do Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [] - you want to do that here too so we still keep going if annotations isn't even an array?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch here. Thanks!

The only (I think) case where cont || [] vs Array.isArray(cont) ? ... matters is when someone inputs a string instead of an array.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, true.length doesn't break 😮

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Shapes must be an array.');
delete layout.shapes;
}
var shapesLen = (layout.shapes || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array.isArray again... I guess if you test this situation you may find even more of these...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

* links to supplementary data (e.g. fullData for layout components)
*
* - opts.itemIsNotPlainObject is mutated on every pass in case so logic
* in handleItemDefaults relies on that fact.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a little odd to me. Do you ever see a case where other parts of opts would need to be accessible to the item handler (or is there one already that I didn't notice?), or could we just pass itemIsNotPlainObject by itself as the last arg to the handler?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll make this the last arg.

I've ran into some problem with our multi-argument internal function lately, but yeah you're right, mutating opts is stupid.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 5de6ff0

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice unification. My usage comment (passing opts vs just itemIsNotPlainObject) is nonblocking, but I'm thinking we should poke even a little more into making sure we keep going no matter what garbage folks throw in (ie the comment about the container itself not being an array).

}

opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts);
opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that's fine. I still don't quite see where the handler will use the original opts but I guess it doesn't hurt to leave it in.

@alexcjohnson

Copy link
Copy Markdown
Collaborator

💃

@etpinard
etpinard merged commit 2897167 into masterNov 11, 2016
@etpinard
etpinard deleted the frame-extend-with-nulls branch November 11, 2016 15:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugsomething brokenfeaturesomething new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@etpinard@rreusser@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Make frame with nulls clear items & array containers by etpinard · Pull Request #1118 · plotly/plotly.js · GitHub
Skip to content

Make frame with nulls clear items & array containers - #1118

Merged
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls
Nov 11, 2016
Merged

Make frame with nulls clear items & array containers#1118
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls

Conversation

@etpinard

Copy link
Copy Markdown
Contributor

Proof of concept PR, attempting to address #1081 (comment) demonstrated in http://codepen.io/etpinard/pen/WoNryW and extending what #1041 put forward.

I'm looking for @rreusser's opinion.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rreusser what do you think of ⏬

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking now…

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@rreusserrreusser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. I think it makes sense. I'd also be okay with setting empty items to null, but I think that would require a bit more work to make sure nothing crashes when it references a property on null. 💃

Comment threadsrc/plots/plots.js Outdated
containerProp.set(null);
Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal);

if(!containerVal) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working through the logic. Just a note that false, undefined, null, and 0 will all trigger this condition. This is fine because this could only reasonably be an object or not an object, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine because this could only reasonably be an object or not an object, right?

Correct. That's what I'm thinking.

Maybe we could be slightly more strict and make this condition if(containerVal === undefined) which would be fulfilled when { annotations: undefined } and { annotations: null } but not for other falsy values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea in general that null is equivalent to "deliberately unset" as opposed to undefined which means "happens to not be specified," but I'm not fully aware of the implications here. I think most people would expect to be able to use them more or less equivalently, but I can see the argument for requiring explicit null in order to unset.

Comment threadsrc/plots/plots.js Outdated
destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcContainer[j]);
var srcObj = srcContainer[j];

if(srcObj === null) destContainer[j] = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. Could be null, but seems the most robust/backwards-compatible if it's at least an object.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad we agree here.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, what should non-plain-object items in layout array containers be coerced to?

See current behavior here: http://codepen.io/etpinard/pen/xRGgwW?

I'd vote for completely skipping over non-plain-object items (i.e. non-plain-object items won't show up in fullLayout). Any objections? Some may interpret this change as backward incompatible. @alexcjohnson thoughts?

Currently, updatemenus and sliders skip over non-plain-object buttons and step items (see here and here).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @bpostlethwaite too ⬆️

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote for completely skipping over non-plain-object items

It's always a mistake / error when we get values like this, right? rather than something with a real use case? Seems like the priorities are first don't break anything else so the rest of the plot still works, and second try to help the user figure out what they did wrong.

So erroring out is the wrong thing to do (don't break anything else - good catch!) but then we can either skip non-objects (as updatemenus.buttons does) or pretend they're empty objects (as updatemenus does). I kind of feel like pretending they're empty objects is better, because then the user can look at the array item by item and see what happened to their input. Also if we have anything that references these items by index it would still match up (though I don't know of anywhere we do that).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because then the user can look at the array item by item and see what happened to their input

good point here.

What if non-plain-object item were coerced to { visible: false } instead? Unless you can think of a situation were http://codepen.io/etpinard/pen/ENjdBX may be useful.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if non-plain-object item were coerced to { visible: false }

Sure, if we've guaranteed that every container supports visible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transforms are a container that use {enabled: false}, right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just say if(!Lib.isPlainObject(containerIn)) containerIn = {}; and let the ensuing coerce logic sort it out? Are there any containers that would end up visible/enabled if given an empty input? I guess maybe annotations and shapes, but this isn't useful for anyone, it was just a shortcut for creating them in the workspace... we could unwind that without causing any problems. But the point is, it shouldn't be a coerce (setting attributes of containerOut), it should just be replacing containerIn for the purpose of the logic that follows.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@etpinardetpinard added this to the v1.20.0 milestone Nov 7, 2016
Comment threadtest/jasmine/tests/plots_test.js Outdated
Plots.extendLayout(dest, src);

expect(dest).toEqual({
annotations: [{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like what I'd expect. 👍

});
});

it('clears container items when applying null src items', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks like what I'd expect. Am I correct in understanding that [undefined, undefined] would skip applying any changes?

});

it('clears container applying null src', function() {
var dest = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Ditto on behavior of undefined?

@rreusser

rreusser commented Nov 9, 2016

Copy link
Copy Markdown
Contributor

On my first pass through this I was focusing on the code but should have thought more about the tests. The behavior looks good to me, but some of the details here are still a little tricky. And if they're tricky for us, they're definitely tricky for users. Of course it's rather obscure use-cases so I think the key is that it's at least consistent/intuitive. @etpinard did you mention you were drying this up? Are there other thoughts or details I can help with? Other use-cases blocking this?

}
}

if(layout.annotations !== undefined && !Array.isArray(layout.annotations)) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this commit was enough to fix bug discovered in http://codepen.io/etpinard/pen/xRGgwW

* in handleItemDefaults relies on that fact.
*
*/
module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) {

@etpinardetpinardNov 10, 2016

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to plots/subplot_defaults.

The logic below is pretty trivial, but might as well 🔒 down the behavior for all array containers.

containerOut = layoutOut.annotations = [];
var opts = {
name: 'annotations',
handleItemDefaults: handleAnnotationDefaults

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌴

};

var expected = {
container: [null, null]

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N.B. I changed the behavior here as discussed in #1118 (comment) - see 36859f1

In brief, frames with array containers set to null extend the state with null. It is now up to the subsequent supplyDefaults calls to coerced those null items into {}, as in regular Plotly.plot calls.

@etpinardetpinard added the bug something broken label Nov 10, 2016
Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Annotations must be an array.');
delete layout.annotations;
}
var annotationsLen = (layout.annotations || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in handleArrayContainerDefaults you do Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [] - you want to do that here too so we still keep going if annotations isn't even an array?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch here. Thanks!

The only (I think) case where cont || [] vs Array.isArray(cont) ? ... matters is when someone inputs a string instead of an array.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, true.length doesn't break 😮

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Shapes must be an array.');
delete layout.shapes;
}
var shapesLen = (layout.shapes || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array.isArray again... I guess if you test this situation you may find even more of these...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

* links to supplementary data (e.g. fullData for layout components)
*
* - opts.itemIsNotPlainObject is mutated on every pass in case so logic
* in handleItemDefaults relies on that fact.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a little odd to me. Do you ever see a case where other parts of opts would need to be accessible to the item handler (or is there one already that I didn't notice?), or could we just pass itemIsNotPlainObject by itself as the last arg to the handler?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll make this the last arg.

I've ran into some problem with our multi-argument internal function lately, but yeah you're right, mutating opts is stupid.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 5de6ff0

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice unification. My usage comment (passing opts vs just itemIsNotPlainObject) is nonblocking, but I'm thinking we should poke even a little more into making sure we keep going no matter what garbage folks throw in (ie the comment about the container itself not being an array).

}

opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts);
opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that's fine. I still don't quite see where the handler will use the original opts but I guess it doesn't hurt to leave it in.

@alexcjohnson

Copy link
Copy Markdown
Collaborator

💃

@etpinard
etpinard merged commit 2897167 into masterNov 11, 2016
@etpinard
etpinard deleted the frame-extend-with-nulls branch November 11, 2016 15:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugsomething brokenfeaturesomething new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@etpinard@rreusser@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Make frame with nulls clear items & array containers by etpinard · Pull Request #1118 · plotly/plotly.js · GitHub
Skip to content

Make frame with nulls clear items & array containers - #1118

Merged
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls
Nov 11, 2016
Merged

Make frame with nulls clear items & array containers#1118
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls

Conversation

@etpinard

Copy link
Copy Markdown
Contributor

Proof of concept PR, attempting to address #1081 (comment) demonstrated in http://codepen.io/etpinard/pen/WoNryW and extending what #1041 put forward.

I'm looking for @rreusser's opinion.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rreusser what do you think of ⏬

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking now…

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@rreusserrreusser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. I think it makes sense. I'd also be okay with setting empty items to null, but I think that would require a bit more work to make sure nothing crashes when it references a property on null. 💃

Comment threadsrc/plots/plots.js Outdated
containerProp.set(null);
Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal);

if(!containerVal) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working through the logic. Just a note that false, undefined, null, and 0 will all trigger this condition. This is fine because this could only reasonably be an object or not an object, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine because this could only reasonably be an object or not an object, right?

Correct. That's what I'm thinking.

Maybe we could be slightly more strict and make this condition if(containerVal === undefined) which would be fulfilled when { annotations: undefined } and { annotations: null } but not for other falsy values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea in general that null is equivalent to "deliberately unset" as opposed to undefined which means "happens to not be specified," but I'm not fully aware of the implications here. I think most people would expect to be able to use them more or less equivalently, but I can see the argument for requiring explicit null in order to unset.

Comment threadsrc/plots/plots.js Outdated
destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcContainer[j]);
var srcObj = srcContainer[j];

if(srcObj === null) destContainer[j] = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. Could be null, but seems the most robust/backwards-compatible if it's at least an object.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad we agree here.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, what should non-plain-object items in layout array containers be coerced to?

See current behavior here: http://codepen.io/etpinard/pen/xRGgwW?

I'd vote for completely skipping over non-plain-object items (i.e. non-plain-object items won't show up in fullLayout). Any objections? Some may interpret this change as backward incompatible. @alexcjohnson thoughts?

Currently, updatemenus and sliders skip over non-plain-object buttons and step items (see here and here).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @bpostlethwaite too ⬆️

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote for completely skipping over non-plain-object items

It's always a mistake / error when we get values like this, right? rather than something with a real use case? Seems like the priorities are first don't break anything else so the rest of the plot still works, and second try to help the user figure out what they did wrong.

So erroring out is the wrong thing to do (don't break anything else - good catch!) but then we can either skip non-objects (as updatemenus.buttons does) or pretend they're empty objects (as updatemenus does). I kind of feel like pretending they're empty objects is better, because then the user can look at the array item by item and see what happened to their input. Also if we have anything that references these items by index it would still match up (though I don't know of anywhere we do that).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because then the user can look at the array item by item and see what happened to their input

good point here.

What if non-plain-object item were coerced to { visible: false } instead? Unless you can think of a situation were http://codepen.io/etpinard/pen/ENjdBX may be useful.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if non-plain-object item were coerced to { visible: false }

Sure, if we've guaranteed that every container supports visible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transforms are a container that use {enabled: false}, right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just say if(!Lib.isPlainObject(containerIn)) containerIn = {}; and let the ensuing coerce logic sort it out? Are there any containers that would end up visible/enabled if given an empty input? I guess maybe annotations and shapes, but this isn't useful for anyone, it was just a shortcut for creating them in the workspace... we could unwind that without causing any problems. But the point is, it shouldn't be a coerce (setting attributes of containerOut), it should just be replacing containerIn for the purpose of the logic that follows.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@etpinardetpinard added this to the v1.20.0 milestone Nov 7, 2016
Comment threadtest/jasmine/tests/plots_test.js Outdated
Plots.extendLayout(dest, src);

expect(dest).toEqual({
annotations: [{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like what I'd expect. 👍

});
});

it('clears container items when applying null src items', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks like what I'd expect. Am I correct in understanding that [undefined, undefined] would skip applying any changes?

});

it('clears container applying null src', function() {
var dest = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Ditto on behavior of undefined?

@rreusser

rreusser commented Nov 9, 2016

Copy link
Copy Markdown
Contributor

On my first pass through this I was focusing on the code but should have thought more about the tests. The behavior looks good to me, but some of the details here are still a little tricky. And if they're tricky for us, they're definitely tricky for users. Of course it's rather obscure use-cases so I think the key is that it's at least consistent/intuitive. @etpinard did you mention you were drying this up? Are there other thoughts or details I can help with? Other use-cases blocking this?

}
}

if(layout.annotations !== undefined && !Array.isArray(layout.annotations)) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this commit was enough to fix bug discovered in http://codepen.io/etpinard/pen/xRGgwW

* in handleItemDefaults relies on that fact.
*
*/
module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) {

@etpinardetpinardNov 10, 2016

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to plots/subplot_defaults.

The logic below is pretty trivial, but might as well 🔒 down the behavior for all array containers.

containerOut = layoutOut.annotations = [];
var opts = {
name: 'annotations',
handleItemDefaults: handleAnnotationDefaults

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌴

};

var expected = {
container: [null, null]

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N.B. I changed the behavior here as discussed in #1118 (comment) - see 36859f1

In brief, frames with array containers set to null extend the state with null. It is now up to the subsequent supplyDefaults calls to coerced those null items into {}, as in regular Plotly.plot calls.

@etpinardetpinard added the bug something broken label Nov 10, 2016
Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Annotations must be an array.');
delete layout.annotations;
}
var annotationsLen = (layout.annotations || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in handleArrayContainerDefaults you do Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [] - you want to do that here too so we still keep going if annotations isn't even an array?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch here. Thanks!

The only (I think) case where cont || [] vs Array.isArray(cont) ? ... matters is when someone inputs a string instead of an array.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, true.length doesn't break 😮

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Shapes must be an array.');
delete layout.shapes;
}
var shapesLen = (layout.shapes || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array.isArray again... I guess if you test this situation you may find even more of these...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

* links to supplementary data (e.g. fullData for layout components)
*
* - opts.itemIsNotPlainObject is mutated on every pass in case so logic
* in handleItemDefaults relies on that fact.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a little odd to me. Do you ever see a case where other parts of opts would need to be accessible to the item handler (or is there one already that I didn't notice?), or could we just pass itemIsNotPlainObject by itself as the last arg to the handler?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll make this the last arg.

I've ran into some problem with our multi-argument internal function lately, but yeah you're right, mutating opts is stupid.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 5de6ff0

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice unification. My usage comment (passing opts vs just itemIsNotPlainObject) is nonblocking, but I'm thinking we should poke even a little more into making sure we keep going no matter what garbage folks throw in (ie the comment about the container itself not being an array).

}

opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts);
opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that's fine. I still don't quite see where the handler will use the original opts but I guess it doesn't hurt to leave it in.

@alexcjohnson

Copy link
Copy Markdown
Collaborator

💃

@etpinard
etpinard merged commit 2897167 into masterNov 11, 2016
@etpinard
etpinard deleted the frame-extend-with-nulls branch November 11, 2016 15:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugsomething brokenfeaturesomething new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@etpinard@rreusser@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Make frame with nulls clear items & array containers by etpinard · Pull Request #1118 · plotly/plotly.js · GitHub
Skip to content

Make frame with nulls clear items & array containers - #1118

Merged
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls
Nov 11, 2016
Merged

Make frame with nulls clear items & array containers#1118
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls

Conversation

@etpinard

Copy link
Copy Markdown
Contributor

Proof of concept PR, attempting to address #1081 (comment) demonstrated in http://codepen.io/etpinard/pen/WoNryW and extending what #1041 put forward.

I'm looking for @rreusser's opinion.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rreusser what do you think of ⏬

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking now…

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@rreusserrreusser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. I think it makes sense. I'd also be okay with setting empty items to null, but I think that would require a bit more work to make sure nothing crashes when it references a property on null. 💃

Comment threadsrc/plots/plots.js Outdated
containerProp.set(null);
Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal);

if(!containerVal) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working through the logic. Just a note that false, undefined, null, and 0 will all trigger this condition. This is fine because this could only reasonably be an object or not an object, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine because this could only reasonably be an object or not an object, right?

Correct. That's what I'm thinking.

Maybe we could be slightly more strict and make this condition if(containerVal === undefined) which would be fulfilled when { annotations: undefined } and { annotations: null } but not for other falsy values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea in general that null is equivalent to "deliberately unset" as opposed to undefined which means "happens to not be specified," but I'm not fully aware of the implications here. I think most people would expect to be able to use them more or less equivalently, but I can see the argument for requiring explicit null in order to unset.

Comment threadsrc/plots/plots.js Outdated
destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcContainer[j]);
var srcObj = srcContainer[j];

if(srcObj === null) destContainer[j] = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. Could be null, but seems the most robust/backwards-compatible if it's at least an object.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad we agree here.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, what should non-plain-object items in layout array containers be coerced to?

See current behavior here: http://codepen.io/etpinard/pen/xRGgwW?

I'd vote for completely skipping over non-plain-object items (i.e. non-plain-object items won't show up in fullLayout). Any objections? Some may interpret this change as backward incompatible. @alexcjohnson thoughts?

Currently, updatemenus and sliders skip over non-plain-object buttons and step items (see here and here).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @bpostlethwaite too ⬆️

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote for completely skipping over non-plain-object items

It's always a mistake / error when we get values like this, right? rather than something with a real use case? Seems like the priorities are first don't break anything else so the rest of the plot still works, and second try to help the user figure out what they did wrong.

So erroring out is the wrong thing to do (don't break anything else - good catch!) but then we can either skip non-objects (as updatemenus.buttons does) or pretend they're empty objects (as updatemenus does). I kind of feel like pretending they're empty objects is better, because then the user can look at the array item by item and see what happened to their input. Also if we have anything that references these items by index it would still match up (though I don't know of anywhere we do that).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because then the user can look at the array item by item and see what happened to their input

good point here.

What if non-plain-object item were coerced to { visible: false } instead? Unless you can think of a situation were http://codepen.io/etpinard/pen/ENjdBX may be useful.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if non-plain-object item were coerced to { visible: false }

Sure, if we've guaranteed that every container supports visible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transforms are a container that use {enabled: false}, right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just say if(!Lib.isPlainObject(containerIn)) containerIn = {}; and let the ensuing coerce logic sort it out? Are there any containers that would end up visible/enabled if given an empty input? I guess maybe annotations and shapes, but this isn't useful for anyone, it was just a shortcut for creating them in the workspace... we could unwind that without causing any problems. But the point is, it shouldn't be a coerce (setting attributes of containerOut), it should just be replacing containerIn for the purpose of the logic that follows.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@etpinardetpinard added this to the v1.20.0 milestone Nov 7, 2016
Comment threadtest/jasmine/tests/plots_test.js Outdated
Plots.extendLayout(dest, src);

expect(dest).toEqual({
annotations: [{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like what I'd expect. 👍

});
});

it('clears container items when applying null src items', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks like what I'd expect. Am I correct in understanding that [undefined, undefined] would skip applying any changes?

});

it('clears container applying null src', function() {
var dest = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Ditto on behavior of undefined?

@rreusser

rreusser commented Nov 9, 2016

Copy link
Copy Markdown
Contributor

On my first pass through this I was focusing on the code but should have thought more about the tests. The behavior looks good to me, but some of the details here are still a little tricky. And if they're tricky for us, they're definitely tricky for users. Of course it's rather obscure use-cases so I think the key is that it's at least consistent/intuitive. @etpinard did you mention you were drying this up? Are there other thoughts or details I can help with? Other use-cases blocking this?

}
}

if(layout.annotations !== undefined && !Array.isArray(layout.annotations)) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this commit was enough to fix bug discovered in http://codepen.io/etpinard/pen/xRGgwW

* in handleItemDefaults relies on that fact.
*
*/
module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) {

@etpinardetpinardNov 10, 2016

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to plots/subplot_defaults.

The logic below is pretty trivial, but might as well 🔒 down the behavior for all array containers.

containerOut = layoutOut.annotations = [];
var opts = {
name: 'annotations',
handleItemDefaults: handleAnnotationDefaults

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌴

};

var expected = {
container: [null, null]

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N.B. I changed the behavior here as discussed in #1118 (comment) - see 36859f1

In brief, frames with array containers set to null extend the state with null. It is now up to the subsequent supplyDefaults calls to coerced those null items into {}, as in regular Plotly.plot calls.

@etpinardetpinard added the bug something broken label Nov 10, 2016
Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Annotations must be an array.');
delete layout.annotations;
}
var annotationsLen = (layout.annotations || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in handleArrayContainerDefaults you do Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [] - you want to do that here too so we still keep going if annotations isn't even an array?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch here. Thanks!

The only (I think) case where cont || [] vs Array.isArray(cont) ? ... matters is when someone inputs a string instead of an array.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, true.length doesn't break 😮

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Shapes must be an array.');
delete layout.shapes;
}
var shapesLen = (layout.shapes || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array.isArray again... I guess if you test this situation you may find even more of these...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

* links to supplementary data (e.g. fullData for layout components)
*
* - opts.itemIsNotPlainObject is mutated on every pass in case so logic
* in handleItemDefaults relies on that fact.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a little odd to me. Do you ever see a case where other parts of opts would need to be accessible to the item handler (or is there one already that I didn't notice?), or could we just pass itemIsNotPlainObject by itself as the last arg to the handler?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll make this the last arg.

I've ran into some problem with our multi-argument internal function lately, but yeah you're right, mutating opts is stupid.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 5de6ff0

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice unification. My usage comment (passing opts vs just itemIsNotPlainObject) is nonblocking, but I'm thinking we should poke even a little more into making sure we keep going no matter what garbage folks throw in (ie the comment about the container itself not being an array).

}

opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts);
opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that's fine. I still don't quite see where the handler will use the original opts but I guess it doesn't hurt to leave it in.

@alexcjohnson

Copy link
Copy Markdown
Collaborator

💃

@etpinard
etpinard merged commit 2897167 into masterNov 11, 2016
@etpinard
etpinard deleted the frame-extend-with-nulls branch November 11, 2016 15:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugsomething brokenfeaturesomething new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@etpinard@rreusser@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Make frame with nulls clear items & array containers by etpinard · Pull Request #1118 · plotly/plotly.js · GitHub
Skip to content

Make frame with nulls clear items & array containers - #1118

Merged
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls
Nov 11, 2016
Merged

Make frame with nulls clear items & array containers#1118
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls

Conversation

@etpinard

Copy link
Copy Markdown
Contributor

Proof of concept PR, attempting to address #1081 (comment) demonstrated in http://codepen.io/etpinard/pen/WoNryW and extending what #1041 put forward.

I'm looking for @rreusser's opinion.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rreusser what do you think of ⏬

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking now…

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@rreusserrreusser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. I think it makes sense. I'd also be okay with setting empty items to null, but I think that would require a bit more work to make sure nothing crashes when it references a property on null. 💃

Comment threadsrc/plots/plots.js Outdated
containerProp.set(null);
Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal);

if(!containerVal) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working through the logic. Just a note that false, undefined, null, and 0 will all trigger this condition. This is fine because this could only reasonably be an object or not an object, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine because this could only reasonably be an object or not an object, right?

Correct. That's what I'm thinking.

Maybe we could be slightly more strict and make this condition if(containerVal === undefined) which would be fulfilled when { annotations: undefined } and { annotations: null } but not for other falsy values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea in general that null is equivalent to "deliberately unset" as opposed to undefined which means "happens to not be specified," but I'm not fully aware of the implications here. I think most people would expect to be able to use them more or less equivalently, but I can see the argument for requiring explicit null in order to unset.

Comment threadsrc/plots/plots.js Outdated
destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcContainer[j]);
var srcObj = srcContainer[j];

if(srcObj === null) destContainer[j] = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. Could be null, but seems the most robust/backwards-compatible if it's at least an object.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad we agree here.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, what should non-plain-object items in layout array containers be coerced to?

See current behavior here: http://codepen.io/etpinard/pen/xRGgwW?

I'd vote for completely skipping over non-plain-object items (i.e. non-plain-object items won't show up in fullLayout). Any objections? Some may interpret this change as backward incompatible. @alexcjohnson thoughts?

Currently, updatemenus and sliders skip over non-plain-object buttons and step items (see here and here).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @bpostlethwaite too ⬆️

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote for completely skipping over non-plain-object items

It's always a mistake / error when we get values like this, right? rather than something with a real use case? Seems like the priorities are first don't break anything else so the rest of the plot still works, and second try to help the user figure out what they did wrong.

So erroring out is the wrong thing to do (don't break anything else - good catch!) but then we can either skip non-objects (as updatemenus.buttons does) or pretend they're empty objects (as updatemenus does). I kind of feel like pretending they're empty objects is better, because then the user can look at the array item by item and see what happened to their input. Also if we have anything that references these items by index it would still match up (though I don't know of anywhere we do that).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because then the user can look at the array item by item and see what happened to their input

good point here.

What if non-plain-object item were coerced to { visible: false } instead? Unless you can think of a situation were http://codepen.io/etpinard/pen/ENjdBX may be useful.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if non-plain-object item were coerced to { visible: false }

Sure, if we've guaranteed that every container supports visible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transforms are a container that use {enabled: false}, right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just say if(!Lib.isPlainObject(containerIn)) containerIn = {}; and let the ensuing coerce logic sort it out? Are there any containers that would end up visible/enabled if given an empty input? I guess maybe annotations and shapes, but this isn't useful for anyone, it was just a shortcut for creating them in the workspace... we could unwind that without causing any problems. But the point is, it shouldn't be a coerce (setting attributes of containerOut), it should just be replacing containerIn for the purpose of the logic that follows.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@etpinardetpinard added this to the v1.20.0 milestone Nov 7, 2016
Comment threadtest/jasmine/tests/plots_test.js Outdated
Plots.extendLayout(dest, src);

expect(dest).toEqual({
annotations: [{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like what I'd expect. 👍

});
});

it('clears container items when applying null src items', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks like what I'd expect. Am I correct in understanding that [undefined, undefined] would skip applying any changes?

});

it('clears container applying null src', function() {
var dest = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Ditto on behavior of undefined?

@rreusser

rreusser commented Nov 9, 2016

Copy link
Copy Markdown
Contributor

On my first pass through this I was focusing on the code but should have thought more about the tests. The behavior looks good to me, but some of the details here are still a little tricky. And if they're tricky for us, they're definitely tricky for users. Of course it's rather obscure use-cases so I think the key is that it's at least consistent/intuitive. @etpinard did you mention you were drying this up? Are there other thoughts or details I can help with? Other use-cases blocking this?

}
}

if(layout.annotations !== undefined && !Array.isArray(layout.annotations)) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this commit was enough to fix bug discovered in http://codepen.io/etpinard/pen/xRGgwW

* in handleItemDefaults relies on that fact.
*
*/
module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) {

@etpinardetpinardNov 10, 2016

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to plots/subplot_defaults.

The logic below is pretty trivial, but might as well 🔒 down the behavior for all array containers.

containerOut = layoutOut.annotations = [];
var opts = {
name: 'annotations',
handleItemDefaults: handleAnnotationDefaults

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌴

};

var expected = {
container: [null, null]

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N.B. I changed the behavior here as discussed in #1118 (comment) - see 36859f1

In brief, frames with array containers set to null extend the state with null. It is now up to the subsequent supplyDefaults calls to coerced those null items into {}, as in regular Plotly.plot calls.

@etpinardetpinard added the bug something broken label Nov 10, 2016
Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Annotations must be an array.');
delete layout.annotations;
}
var annotationsLen = (layout.annotations || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in handleArrayContainerDefaults you do Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [] - you want to do that here too so we still keep going if annotations isn't even an array?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch here. Thanks!

The only (I think) case where cont || [] vs Array.isArray(cont) ? ... matters is when someone inputs a string instead of an array.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, true.length doesn't break 😮

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Shapes must be an array.');
delete layout.shapes;
}
var shapesLen = (layout.shapes || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array.isArray again... I guess if you test this situation you may find even more of these...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

* links to supplementary data (e.g. fullData for layout components)
*
* - opts.itemIsNotPlainObject is mutated on every pass in case so logic
* in handleItemDefaults relies on that fact.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a little odd to me. Do you ever see a case where other parts of opts would need to be accessible to the item handler (or is there one already that I didn't notice?), or could we just pass itemIsNotPlainObject by itself as the last arg to the handler?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll make this the last arg.

I've ran into some problem with our multi-argument internal function lately, but yeah you're right, mutating opts is stupid.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 5de6ff0

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice unification. My usage comment (passing opts vs just itemIsNotPlainObject) is nonblocking, but I'm thinking we should poke even a little more into making sure we keep going no matter what garbage folks throw in (ie the comment about the container itself not being an array).

}

opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts);
opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that's fine. I still don't quite see where the handler will use the original opts but I guess it doesn't hurt to leave it in.

@alexcjohnson

Copy link
Copy Markdown
Collaborator

💃

@etpinard
etpinard merged commit 2897167 into masterNov 11, 2016
@etpinard
etpinard deleted the frame-extend-with-nulls branch November 11, 2016 15:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugsomething brokenfeaturesomething new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@etpinard@rreusser@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Make frame with nulls clear items & array containers by etpinard · Pull Request #1118 · plotly/plotly.js · GitHub
Skip to content

Make frame with nulls clear items & array containers - #1118

Merged
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls
Nov 11, 2016
Merged

Make frame with nulls clear items & array containers#1118
etpinard merged 11 commits into
masterfrom
frame-extend-with-nulls

Conversation

@etpinard

Copy link
Copy Markdown
Contributor

Proof of concept PR, attempting to address #1081 (comment) demonstrated in http://codepen.io/etpinard/pen/WoNryW and extending what #1041 put forward.

I'm looking for @rreusser's opinion.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rreusser what do you think of ⏬

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking now…

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@rreusserrreusser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. I think it makes sense. I'd also be okay with setting empty items to null, but I think that would require a bit more work to make sure nothing crashes when it references a property on null. 💃

Comment threadsrc/plots/plots.js Outdated
containerProp.set(null);
Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal);

if(!containerVal) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working through the logic. Just a note that false, undefined, null, and 0 will all trigger this condition. This is fine because this could only reasonably be an object or not an object, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine because this could only reasonably be an object or not an object, right?

Correct. That's what I'm thinking.

Maybe we could be slightly more strict and make this condition if(containerVal === undefined) which would be fulfilled when { annotations: undefined } and { annotations: null } but not for other falsy values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea in general that null is equivalent to "deliberately unset" as opposed to undefined which means "happens to not be specified," but I'm not fully aware of the implications here. I think most people would expect to be able to use them more or less equivalently, but I can see the argument for requiring explicit null in order to unset.

Comment threadsrc/plots/plots.js Outdated
destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcContainer[j]);
var srcObj = srcContainer[j];

if(srcObj === null) destContainer[j] = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. Could be null, but seems the most robust/backwards-compatible if it's at least an object.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad we agree here.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, what should non-plain-object items in layout array containers be coerced to?

See current behavior here: http://codepen.io/etpinard/pen/xRGgwW?

I'd vote for completely skipping over non-plain-object items (i.e. non-plain-object items won't show up in fullLayout). Any objections? Some may interpret this change as backward incompatible. @alexcjohnson thoughts?

Currently, updatemenus and sliders skip over non-plain-object buttons and step items (see here and here).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @bpostlethwaite too ⬆️

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote for completely skipping over non-plain-object items

It's always a mistake / error when we get values like this, right? rather than something with a real use case? Seems like the priorities are first don't break anything else so the rest of the plot still works, and second try to help the user figure out what they did wrong.

So erroring out is the wrong thing to do (don't break anything else - good catch!) but then we can either skip non-objects (as updatemenus.buttons does) or pretend they're empty objects (as updatemenus does). I kind of feel like pretending they're empty objects is better, because then the user can look at the array item by item and see what happened to their input. Also if we have anything that references these items by index it would still match up (though I don't know of anywhere we do that).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because then the user can look at the array item by item and see what happened to their input

good point here.

What if non-plain-object item were coerced to { visible: false } instead? Unless you can think of a situation were http://codepen.io/etpinard/pen/ENjdBX may be useful.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if non-plain-object item were coerced to { visible: false }

Sure, if we've guaranteed that every container supports visible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transforms are a container that use {enabled: false}, right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just say if(!Lib.isPlainObject(containerIn)) containerIn = {}; and let the ensuing coerce logic sort it out? Are there any containers that would end up visible/enabled if given an empty input? I guess maybe annotations and shapes, but this isn't useful for anyone, it was just a shortcut for creating them in the workspace... we could unwind that without causing any problems. But the point is, it shouldn't be a coerce (setting attributes of containerOut), it should just be replacing containerIn for the purpose of the logic that follows.

Comment threadtest/jasmine/tests/plots_test.js Outdated
});
});

describe('extendLayout', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@etpinardetpinard added this to the v1.20.0 milestone Nov 7, 2016
Comment threadtest/jasmine/tests/plots_test.js Outdated
Plots.extendLayout(dest, src);

expect(dest).toEqual({
annotations: [{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like what I'd expect. 👍

});
});

it('clears container items when applying null src items', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks like what I'd expect. Am I correct in understanding that [undefined, undefined] would skip applying any changes?

});

it('clears container applying null src', function() {
var dest = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Ditto on behavior of undefined?

@rreusser

rreusser commented Nov 9, 2016

Copy link
Copy Markdown
Contributor

On my first pass through this I was focusing on the code but should have thought more about the tests. The behavior looks good to me, but some of the details here are still a little tricky. And if they're tricky for us, they're definitely tricky for users. Of course it's rather obscure use-cases so I think the key is that it's at least consistent/intuitive. @etpinard did you mention you were drying this up? Are there other thoughts or details I can help with? Other use-cases blocking this?

}
}

if(layout.annotations !== undefined && !Array.isArray(layout.annotations)) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this commit was enough to fix bug discovered in http://codepen.io/etpinard/pen/xRGgwW

* in handleItemDefaults relies on that fact.
*
*/
module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) {

@etpinardetpinardNov 10, 2016

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to plots/subplot_defaults.

The logic below is pretty trivial, but might as well 🔒 down the behavior for all array containers.

containerOut = layoutOut.annotations = [];
var opts = {
name: 'annotations',
handleItemDefaults: handleAnnotationDefaults

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌴

};

var expected = {
container: [null, null]

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N.B. I changed the behavior here as discussed in #1118 (comment) - see 36859f1

In brief, frames with array containers set to null extend the state with null. It is now up to the subsequent supplyDefaults calls to coerced those null items into {}, as in regular Plotly.plot calls.

@etpinardetpinard added the bug something broken label Nov 10, 2016
Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Annotations must be an array.');
delete layout.annotations;
}
var annotationsLen = (layout.annotations || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in handleArrayContainerDefaults you do Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [] - you want to do that here too so we still keep going if annotations isn't even an array?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch here. Thanks!

The only (I think) case where cont || [] vs Array.isArray(cont) ? ... matters is when someone inputs a string instead of an array.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, true.length doesn't break 😮

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

Comment threadsrc/plot_api/helpers.js Outdated
Lib.warn('Shapes must be an array.');
delete layout.shapes;
}
var shapesLen = (layout.shapes || []).length;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array.isArray again... I guess if you test this situation you may find even more of these...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9de2f77

* links to supplementary data (e.g. fullData for layout components)
*
* - opts.itemIsNotPlainObject is mutated on every pass in case so logic
* in handleItemDefaults relies on that fact.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a little odd to me. Do you ever see a case where other parts of opts would need to be accessible to the item handler (or is there one already that I didn't notice?), or could we just pass itemIsNotPlainObject by itself as the last arg to the handler?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll make this the last arg.

I've ran into some problem with our multi-argument internal function lately, but yeah you're right, mutating opts is stupid.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 5de6ff0

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice unification. My usage comment (passing opts vs just itemIsNotPlainObject) is nonblocking, but I'm thinking we should poke even a little more into making sure we keep going no matter what garbage folks throw in (ie the comment about the container itself not being an array).

}

opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts);
opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that's fine. I still don't quite see where the handler will use the original opts but I guess it doesn't hurt to leave it in.

@alexcjohnson

Copy link
Copy Markdown
Collaborator

💃

@etpinard
etpinard merged commit 2897167 into masterNov 11, 2016
@etpinard
etpinard deleted the frame-extend-with-nulls branch November 11, 2016 15:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugsomething brokenfeaturesomething new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@etpinard@rreusser@alexcjohnson