-
-
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
[WIP] Transforms [rebase of timelyportfolio's work] #936
Conversation
…ster `filter` with plotly
PS. never mind, they pass after registering the transforms, I guess these should eventually be done centrally right under Currently, tests pass because some tests are commented out for now. @timelyportfolio @etpinard did the commented out tests use to pass? (I mean in the feature branch rather than |
We left |
@timelyportfolio thanks, exactly, probably sticking it in the big list unless @etpinard you think otherwise (there may have been a reason to not include it in the feature branch) |
@etpinard (not transform related question) I just noticed that if I supply a scalar e.g. (Current |
@monfera don't worry about the current groubpy transform in That file was only meant for testing purposes. |
dflt: '=' | ||
}, | ||
value: { | ||
valType: 'number', | ||
valType: 'any', | ||
dflt: 0 | ||
}, | ||
filtersrc: { |
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 we add ids
(edit: not identifiers
) to the filtersrc
enum? The previous blocker was the animation PR getting merged, but now that it's merged, it's probably a good idea. Without that, I believe @timelyportfolio had to use floating point equaity in order to use the lasso selector and then filter to the selected set. Since in
already uses indexOf, I think this perhaps really is a one-liner.
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.
you mean ids
?
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.
Thank you, yes. I was looking at the wrong branch. ids
.
return v; | ||
}); | ||
} else { | ||
if(isNumeric(transformOut.value)) transformOut.value = +transformOut.value; |
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 would prefer moving the array item type conversion down to the transform
step - supplyDefaults (even the transform version) should try to avoid diving into arrays.
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.
FYI I haven't touched anything in filter.js
so it's now as it is in @timelyportfolio's branch (and I haven't yet pushed the changes to groupby.js
. If I understand the task correctly, I need to work on groupby
as part of the first sprint on it so I'll get back on it in a bit.
(cherry picked from commit 7e967fe)
…f more cases inside
// list of underscore attributes to keep in schema as is | ||
crawler.UNDERSCORE_ATTRS = [crawler.IS_SUBPLOT_OBJ, crawler.IS_LINKED_TO_ARRAY, crawler.DEPRECATED]; | ||
|
||
crawler.crawl = function(attrs, callback, specifiedLevel) { |
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 here, I'd put this in coerce.js
(if ok)
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.
@etpinard same question here, how would it best fit, as a separate export or ... pls specify?
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.
Similarly here:
export.crawl
in coerce.js
@@ -56,16 +56,17 @@ PlotSchema.get = function() { | |||
return plotSchema; | |||
}; | |||
|
|||
PlotSchema.crawl = function(attrs, callback) { | |||
PlotSchema.crawl = function (attrs, callback, specifiedLevel) { | |||
var level = specifiedLevel || 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.
very nicely done.
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 it possible to add a docstring on the usage? This feels very useful, but I don't immediately comprehend what it does.
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.
Or maybe this isn't the method that needs the docstring, but from what I understand, these methods are critical for organizing the messy parts of working with the plot schema, so would be great to make preferred logic clear!
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.
As I had to get familiar with it, I can add docs. Caveat, the nearly identical crawler was already in place (in plot_schema.js
) and I'm not the biggest Plotly
schema expert.
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. Sorry for the extra burden. Not much necessary at all. Just a couple signposts to direct traffic. I just feel like we've been facing very similar challenges with these things, and I suspect a bug that I need to fix involves some of the same problems you've addressed here.
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.
Definitely, at least any gaps in my understanding can be cleared :-) Sometimes I'm thinking of adding verbiage but end up being more concerned with the large lifecycle functions, some of which are a few hundred lines.
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.
True, the crawler is already accessible publicly on Plotly.PlotSchema.crawl
though it isn't an official plotly.js method yet.
Maybe we should add it the main Plotly
object in v2.0.0
e.g. Plotly.crawlAttributes
?
} | ||
} | ||
|
||
crawler.crawl(trace._module.attributes, callback); |
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 the crawler able to catch nested attributes like error_y.array
declared here ?
If not, it's not a big deal. I'm not a big fan of the _nestedAttributes
pattern anyway. We could simple require
nested attribute in the root attribute modules to make this work.
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 don't know offhand, if it's not a big deal either way, I'll take a quick look and either cover it or reify it with a test case.
} | ||
|
||
return newData; | ||
} | ||
|
||
function getDeepProp(thing, propArray) { |
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.
getDeepProp
and setDeepProp
would be nice additions to src/lib/nested_property.js
.
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'll take a look! Btw. I was also thinking about using deepExtend
as an alternative to the attrib path arrays and setDeepProp
but I had performance concerns, it's already in a deeply nested loop so we might want to iterate on performance at some point.
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 using setDeepProp
- though I haven't ran any benchmarking.
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.
Turns out that nested_property.js
yields one single export, which is a function, do you want that I refactor its single usage (by coerce.js
) such that nested_property.js
can export multiple things?
expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); | ||
expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); | ||
|
||
expect(gd._fullData.length).toEqual(0); // fixme: it passes with 0; shouldn't it be 1? (one implied group) |
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.
Good point here.
What should a groupby transform with no groups
field do?
In that case maybe it would be less surprising to have groupby not do anything (i.e. the identity transform).
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 it should also do the same as when transforms.groups.length === 0
. Which leads to the question, what if transforms.groups.length < x.length
and also, what we do now when arrays are of non-shared length.
What the code currently does is, it iterates through the elements of groups
on 0 -> length - 1
i.e. the result will be empty.
While I can conditionalize it to do something differently (e.g. if(transforms.groups === void(0) || transforms.groups.length === 0) return;
or some such) the user may pass e.g. one element in groups
and gets another behavior.
@etpinard what's the desired outcome if
- the user doesn't specify a
transforms.groups
- the user does specify a
transforms.groups
but it'snull
,[]
,undefined
- the user specifies a
transforms.groups
array but its length is shorter than the length of the (shortest?) data array
I'm almost leaning toward leaving the code as it is (i.e. no added logic), in favor of letting others work with groupby
as early as possible, but it's probably because I'm not yet familiar with strong conventions in this realm so I'll gladly do what's deemed best.
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.
- the user doesn't specify a transforms.groups
skip over the transform
- the user does specify a transforms.groups but it's null, [], undefined
same here, skip over the transform
- the user specifies a transforms.groups array but its length is shorter than the length of the (shortest?) data array
The resulting extended should as many points as items in groups
. In other words, skip over trace items that can't be paired up with a groups
item
}); | ||
|
||
// passes; looks OK | ||
it('Plotly.plot should plot the transform traces', function(done) { |
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(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); | ||
expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); | ||
|
||
expect(gd._fullData.length).toEqual(1); // todo: confirm this result is OK |
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 result is ok.
Non-array transforms
values should be ignored.
expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); | ||
expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); | ||
|
||
expect(gd._fullData.length).toEqual(1); // todo: confirm this result is OK |
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 is the expected result.
} | ||
}, | ||
b: { | ||
mode: 'markers+lines', // heterogeonos attributes are OK: group 'a' doesn't need to define 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.
very nice
line: { | ||
width: [4, 2, 4, 2, 2, 3, 3], | ||
// a general, not overridden array will be interpreted per group | ||
color: ['orange', 'red', 'green', 'cyan'] |
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.
so what's the output here?
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, transforms[0].style[a_or_b].maker.color
overrides the trace marker.color
?
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.
Trace belonging to group a
gets the marker.line.color
from the split array: ['orange', 'red', 'cyan', 'pink']
Trace belonging to group b
gets the marker.line.color
from transforms.style.b.marker.line.color
: yellow
I updated the test case to demonstrate it (will push soon).
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.
Looks good. Thanks!
exports.UNDERSCORE_ATTRS = [exports.IS_SUBPLOT_OBJ, exports.IS_LINKED_TO_ARRAY, exports.DEPRECATED]; | ||
|
||
/** | ||
* Crawl the attribute tree, recursively calling a callback function |
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 for 📚
var opts = state.transform; | ||
var groups = opts.groups; | ||
var groups = trace.transforms[state.transformIndex].groups; |
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.
what's the difference here between
state.transform
// and
trace.transforms[state.transformIndex]
??
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.
@etpinard the latter is the pre-transform information. We want to obtain the groups. If a filter
applies before a groupby
then state.transform.groups
will be an array that's already culled by the filter.
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 very much
var arrayAttributes = attributeSet | ||
.filter(function(array) {return Array.isArray(Lib.nestedProperty(trace, array).get());}); | ||
|
||
// fixme the O(n**3) complexity |
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.
True, we could probably do better in terms of performance, but O(n^3)
where n
is the number of attributes doesn't bother me too much,
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'll remove the comment and iterate on performance either separately or as needed (as you see fit)
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.
Remove the comment for now. Like I said big loops over attributes don't bother me (as opposed to big loops over data arrays).
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, done
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.
@monfera looks like all the functionality is now working. Nice work!
We'll merge this in after ⚡ing that obsolete comment.
expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); | ||
expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); | ||
|
||
expect(gd._fullData.length).toEqual(1); // fixme not: good, okay it's 1 here (one implied group / thing) |
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.
⚡ comment please
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, removed!
* Example of filled closure variable (expected to be initialized to []): | ||
* [["marker","size"],["marker","line","width"],["marker","line","color"]] | ||
*/ | ||
function callback(attr, attrName, attrs, level) { |
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.
Interesting! Thanks for the docs. This might be applicable in a few situations.
Rebase and lint of @timelyportfolio's PR #859
Issue #917