-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
restyle/relayout refactor #1999
Conversation
@@ -21,6 +20,7 @@ module.exports = { | |||
valType: 'boolean', | |||
role: 'info', | |||
dflt: true, | |||
editType: 'docalcAutorange', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would docalc+autorange
be a sensible name for this? It sounds like an enum camelcased and concatenated, but I could be mistaken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. Unless I'm mistaken, this adds no new labels. That's just plugging into what already exists. Still seems like maybe an enum in disguise, but nothing to change here then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One additional note while digging through here is that the do-
prefix seems to be carried forward from what was originally just internal variable names. Would editType: 'calcAutorange'
or editType: 'calc+autorange'
make sense? It seems a bit friendlier if you're trying to make sense of the schema as a standalone concept.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docalcAutorange
is a single thing, means "do a recalc only if there's an autoranged axis related to this attribute" - Actually, ideally we'd specify what to do if there isn't an autoranged axis... so like docalcAutorange+dostyle
or something.
Would
editType: 'calcAutorange'
make sense?
Yeah totally, good idea! And to help us out (to figure out what editType
a new attribute should get) I should probably describe somewhere what these flags actually mean in terms of the code paths they lead to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe calcIfAutorange
?
to figure out what editType a new attribute should get
FWIW, I have a history of making bad choices here for lack of really understanding the implications of some schema concepts. If you want I can try making docs/schema.md
that explains, at the very least, what some of the schema items link up with. Some of the concepts like _isLinkedToArray
get a little subtle. Even for the fifth time interacting with it, I think a brief description with links to a couple relevant spots in the code would help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe
calcIfAutorange
?
🏆
If you want I can try making
docs/schema.md
Definitely time for something like that! @etpinard thoughts about how/where to do this? Should it be a standalone thing, or somehow included in Plotly.PlotSchema.get()
or...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My vote would be for not including in the code so that it's as accessible and readable as possible without having to realize it's an API function and instantiate anything in order to access.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd vote for adding a attribute-like edit type declaration object in edit_types.js
with a description
field. For example,
var traceEditTypes: {
docalc: {
description: [
// ....
].join(' ')
},
// ....
}
That description
field would get stripped off by the compress_attributes
transform. Plotly.PlotSchema.get().defs.editTypes
would then return the list of edit types with their description.
I see your point @rreusser about relying an API function to show docs being less-than-ideal. But since we already have a hard time keeping https://github.com/plotly/documentation/ up to speed, adding another layer of static (e.g. a github wiki or a docs/
directory) documentation sounds dangerous. No docs is better than bad docs, right? That said, it wouldn't hurt adding a few lines in our CONTRIBUTING.md over the edit types.
font: extendFlat({}, fontAttrs, { | ||
font: fontAttrs({ | ||
editType: 'docalcAutorange', | ||
colorEditType: 'doarraydraw', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you point me toward what doarraydraw
does? It's not entirely clear from the name, and if it appears somewhere else in the source, it's not jumping out at me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind. Found it in the suppressed diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, where is doarraydraw
being used? I can't find it (even in the suppressed diffs 🙃 )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think at one point I had none
instead of doarraydraw
but that looked weird. Then at another point I had a block just for debugging that checked that everything labeled doarraydraw
was captured by an arrayContainers
block and threw an error otherwise, but I removed that once I was satisfied that it was never getting hit... happy to change it if you think it would make more sense another way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, doarraydraw
is just a placeholder for edits in array containers that are handled by applyContainerArrayChanges
in manage_array.js
.
In this case, I prefer setting editType: 'doarraydraw'
over editType: 'none'
. Just make sure that this behavior is documented in the edit type description
field (cc #1999 (comment)).
@@ -13,6 +13,8 @@ module.exports = { | |||
valType: 'boolean', | |||
role: 'info', | |||
dflt: true, | |||
editType: 'docalc', | |||
impliedEdits: {zmin: undefined, zmax: undefined}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same with impliedEdits. I assume these are attributes that are automatically applied within the same attr path, but I'm unable to find where it's implemented and it's not immediately clear what the ^
prefix means.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind. Github was suppressing the plot_api diff. Found it.
}), | ||
color: extendFlat({}, fontAttrs.color) | ||
}, | ||
font: fontAttrs, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️
* an attribute string, such as 'annotations[3].x'. The "current location" | ||
* is the attribute string minus the last component ('annotations[3]') | ||
* @param {string} relativeAttr: | ||
* a route to the desired attribute string, using '^' to ascend |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I think I understand. So relative to a.b.c
, ^foo
would be a.foo
. I don't know a solution, but one challenge with the schema is that it implements a lot of very precise semantics that can only be understood by locating the part of the code that handles them and working backwards (which isn't always possible since you might not even know that python is doing something with role
, for example). I'm tempted to take a stab at docs/schema.md
.
src/lib/relative_attr.js
Outdated
'use strict'; | ||
|
||
|
||
var ASCEND = /^(.*)(\.[^\.\[\]]+|\[\d\])$/; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a very short comment about what this matches? In particular figuring out whether ^
is negation or an actual caret takes a good amount of staring and recalling js-specific regex escaping rules since this particular use could very reasonably involve either one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-reading, matches foo.string
or foo[5]
or foo.bar[5]
, I think, while capturing everything before the last chunk (as well as the last chunk but only so it can be thrown away). It's not so bad, I guess. Oh, so ASCEND
refers to the way it chops off the last attr chunk. I see.
|
||
if(valObject && valObject.impliedEdits && newVal !== null) { | ||
for(var impliedKey in valObject.impliedEdits) { | ||
doextra(Lib.relativeAttr(ai, impliedKey), valObject.impliedEdits[impliedKey], i); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One question about implied edits is whether it moves away from declarative plots. That is, if you set foo
(which, e.g., implicitly also sets bar: false
), is it valid to then set bar: true
? Is there pathway that might accidentally circumvent the implied edits and lead to a bit of inconsistency? Would it be better/worse to think of implied edits as a concern of the plotly API consumer rather than the plotly API itself? Like the workspace would recognize the attribute you're modifying has an implied edit and send both of those to the API? It seems nice from an end-user perspective; my only concern is that it might be surprising/confusing to modify an attribute and find that additional changes have been made to your plot state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR by itself isn't moving toward or away from anything as far as users are concerned... all of these impliedEdits
were previously handled by dedicated blocks in _restyle
/_relayout
. Without them, a call like relayout(gd, 'xaxis.range', [5, 10])
wouldn't do anything if xaxis.autorange
was true.
Looking ahead though to Plotly.react
, I'm hoping that by having impliedEdits
in the schema we can help people make these implied changes in a transparent way - so if you have a range
control that modifies the state and passes it back to the plot in Plotly.react
, it would be able to look up the implied changes we think it should make, but the control would have final say over whether to apply those changes or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Thanks for the clarification.
if(valObject) { | ||
/* | ||
* In occasional cases we can't the innermost valObject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"can't ____ the innermost…"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh never mind. This is in the removed section. 👍
@@ -208,6 +216,182 @@ exports.findArrayAttributes = function(trace) { | |||
return arrayAttributes; | |||
}; | |||
|
|||
/* | |||
* Find the valObject for one attribute in an existing trace |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is a valObject
just a value? Or is it a nested property object? Or the attribute definition?
Or something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Less vaguely, valObjects
are defined here, but I'm trying to process how the return value relates to those definitions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hah yeah, coerce.valObjects
describes valObject
s but it isn't itself at set of valObject
s. What we mean by it in this context is a schema entry describing one attribute. I'll see what I can do in terms of documenting this better...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a big deal. Just another part of the code I haven't gone too deep in. To clear up a common misconception though, naming in CS is a very easy problem (randomly generated strings will do). Naming for maximum interpretability is the hard part. 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like coerce.valObjects
should be named coerce.valObjectMeta
instead. To me and to PlotSchema.isValObject
a val object is any object with a valType
key - which is essentially what getTraceValObject
and getLayoutValObject
return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
valObjectMeta
- and descriptions for editType
and impliedEdits
that make their way into the schema - in 96cc57f
src/plot_api/plot_api.js
Outdated
var typeAttr = axAttr + '.type'; | ||
|
||
if(layoutUpdate[axAttr] === undefined && layoutUpdate[typeAttr] === undefined) { | ||
Lib.nestedProperty(gd.layout, typeAttr).set(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much better than delete /* */
👌
package.json
Outdated
@@ -119,6 +119,7 @@ | |||
"gzip-size": "^3.0.0", | |||
"image-size": "^0.5.1", | |||
"jasmine-core": "^2.4.1", | |||
"jsdom": "^9.5.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use the latest version jsdom@11
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because 9.5.0
is what we have in streambed, and it looked like it was going to take me more than 5 minutes to figure out the new API 🙈
But you're right, not exactly a good reason - I'll try to upgrade it.
tasks/util/make_schema.js
Outdated
// jsdom by itself doesn't support getContext, and adding the npm canvas | ||
// package is annoying and platform-dependent. | ||
// see https://github.com/tmpvar/jsdom/issues/1782 | ||
w.HTMLCanvasElement.prototype.getContext = function() { return null; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dunno why it didn't happen with the old version, but with the new jsdom without this patch you get an ugly but nonblocking error Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, no need to add a dependency for this 👌
src/plot_api/plot_api.js
Outdated
@@ -2152,10 +2154,6 @@ Plotly.update = function update(gd, traceUpdate, layoutUpdate, traces) { | |||
// so we auto-set them again | |||
var axLetters = ['x', 'y', 'z']; | |||
function clearAxisTypes(gd, traces, layoutUpdate) { | |||
if(typeof traces === 'number') traces = [traces]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks!
closes plotly/streambed#8975 (but the fix was earlier in this PR)
test/jasmine/assets/check_ticks.js
Outdated
|
||
var d3 = require('d3'); | ||
|
||
module.exports = function checkTicks(axLetter, vals, msg) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been trying to centralized common custom assertions like this one in:
https://github.com/plotly/plotly.js/blob/master/test/jasmine/assets/custom_assertions.js
I'll let decide whether separate files or a common module is better for stuff like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh that's nice - yeah, I'll put checkTicks
in there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test/jasmine/assets/negate_if.js
Outdated
* if(xIsSomethingElse) expect(x).not.toBe(0); | ||
* else expect(x).toBe(0); | ||
*/ | ||
module.exports = function negateIf(expectation, negate) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking but I wonder if there's a way to rewrite this as a custom jasmine matcher. So that we have something like:
expect(x).negateIf(negate).toBe(0)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect(x).negateIf(negate).toBe(0)
would definitely be more readable - I'll give it a shot.
test/jasmine/tests/histogram_test.js
Outdated
@@ -463,6 +463,10 @@ describe('Test histogram', function() { | |||
describe('plot / restyle', function() { | |||
var gd; | |||
|
|||
beforeAll(function() { | |||
jasmine.addMatchers(customMatchers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(note-to-self comment) I'm getting tired of these jasmine.addMatchers
. I'll try to find a way to add our custom matchers globally once and for all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️ 🎉 🥇 👏 💯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
global addMatchers, and turning negateIf
into a method (which I could only see how to do globally) ->0729921 hopefully it works right on circle 🙏
💃 💃 💃 |
Something we've wanted to do for a looong time... get (nearly all) the attribute names and regex tests out of
_restyle
and_relayout
, and use the schema instead:editType
as an optional field in the attribute schema, this makes it mandatory and uses it as the key way to determine what flavor of redraw to invoke.impliedEdits
for correlated changes - for examplerange
hasimpliedEdits: {autorange: false}
because setting an explicit range is expected to disable the associated autorange. Note that the converseautorange.impliedEdits: {range: undefined}
is used as it was before just to record the previous values for use in undo/redo, which we've discussed removing in v2.Also includes a fix for #1325 as that came along essentially for free with generalizing our attribute string regexes.
I want to do a bit more testing of specific restyle/relayout calls to make sure we're doing the right redraws in all cases - especially of base/module interactions like
hoverlabel
, and module/module interactions likeheatmapgl
/colorbar
. But I'd appreciate a high-level review ASAP @etpinard @rreusser