Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Transforms [rebase of timelyportfolio's work] #936

Merged
merged 34 commits into from
Sep 26, 2016

Conversation

monfera
Copy link
Contributor

@monfera monfera commented Sep 14, 2016

Rebase and lint of @timelyportfolio's PR #859
Issue #917

@monfera monfera changed the title Transforms [rebase of timelyportfolio's work] [WIP] Transforms [rebase of timelyportfolio's work] Sep 14, 2016
@monfera
Copy link
Contributor Author

monfera commented Sep 14, 2016

PS. never mind, they pass after registering the transforms, I guess these should eventually be done centrally right under src.

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 master where the tests are also present)

@timelyportfolio
Copy link
Contributor

timelyportfolio commented Sep 14, 2016

Like this ?https://github.com/timelyportfolio/plotly.js/blob/9b65721f645c3fd1a9c17c0ceca7a9c29234e30a/lib/index.js#L32-L35

We left groupby out since was not fully fleshed out, but I believe the intention was to register by default also.

@monfera
Copy link
Contributor Author

monfera commented Sep 14, 2016

@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)

@monfera
Copy link
Contributor Author

monfera commented Sep 14, 2016

@etpinard (not transform related question) I just noticed that if I supply a scalar e.g. y: 10, then it'll ignore it and just use a running sequence of [0, 1, .., x.length - 1] - I guess it's intentional.

(Current groupby chokes if I don't supply an array for y which is all well as in the 1st round I'm just going around making breaking test cases, then bring in compliance)

@etpinard etpinard added this to the v1.18.0 milestone Sep 14, 2016
@etpinard
Copy link
Contributor

@monfera don't worry about the current groubpy transform in test/jasmine/assets/.

That file was only meant for testing purposes.

dflt: '='
},
value: {
valType: 'number',
valType: 'any',
dflt: 0
},
filtersrc: {
Copy link
Contributor

@rreusser rreusser Sep 14, 2016

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

you mean ids ?

Copy link
Contributor

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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.

// 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) {
Copy link
Contributor

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)

Copy link
Contributor Author

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?

Copy link
Contributor

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;
Copy link
Contributor

Choose a reason for hiding this comment

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

very nicely done.

Copy link
Contributor

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.

Copy link
Contributor

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!

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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);
Copy link
Contributor

@etpinard etpinard Sep 22, 2016

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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 deepExtendas 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.

Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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).

Copy link
Contributor Author

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

  1. the user doesn't specify a transforms.groups
  2. the user does specify a transforms.groups but it's null, [], undefined
  3. 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.

Copy link
Contributor

Choose a reason for hiding this comment

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

  1. the user doesn't specify a transforms.groups

skip over the transform

  1. the user does specify a transforms.groups but it's null, [], undefined

same here, skip over the transform

  1. 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) {
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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']
Copy link
Contributor

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?

Copy link
Contributor

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 ?

Copy link
Contributor Author

@monfera monfera Sep 23, 2016

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).

Copy link
Contributor

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
Copy link
Contributor

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;
Copy link
Contributor

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]

??

Copy link
Contributor Author

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.

Copy link
Contributor

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
Copy link
Contributor

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,

Copy link
Contributor Author

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)

Copy link
Contributor

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).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, done

Copy link
Contributor

@etpinard etpinard left a 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)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚡ comment please

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

@etpinard etpinard merged commit fdeed44 into plotly:master Sep 26, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature something new
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants