-
-
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
Changes from 24 commits
9bb2b10
582d210
d0ef359
9574f4f
77e2b27
aa1f1d3
6015768
6daeac5
3e0f209
d735b64
a820b0c
2dd3646
c30f94e
e9bde33
c6355e8
5765f66
02742e8
b23d2ff
55f9e0c
462b2f0
9cafe1e
87de31f
61f3385
57d0c13
7085729
392844d
1dd93bb
1ea2fd7
59b741d
a5dad2a
79dcbc3
aa70e4c
523f61b
4efe6a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/** | ||
* Copyright 2012-2016, Plotly, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
module.exports = require('../src/transforms/filter'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
|
||
'use strict'; | ||
|
||
var PlotSchema = require('./../plot_api/plot_schema') | ||
var d3 = require('d3'); | ||
var isNumeric = require('fast-isnumeric'); | ||
|
||
|
@@ -786,6 +787,27 @@ function applyTransforms(fullTrace, fullData, layout) { | |
var container = fullTrace.transforms, | ||
dataOut = [fullTrace]; | ||
|
||
var attributeSets = dataOut.map(function(trace) { | ||
|
||
var arraySplitAttributes = []; | ||
|
||
var stack = []; | ||
|
||
function callback(attr, attrName, attrs, level) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
||
stack = stack.slice(0, level).concat([attrName]); | ||
|
||
var splittableAttr = attr.valType === 'data_array' || attr.arrayOk === true | ||
if(splittableAttr) { | ||
arraySplitAttributes.push(stack.slice()); | ||
} | ||
} | ||
|
||
PlotSchema.crawl(trace._module.attributes, callback); | ||
|
||
return arraySplitAttributes; | ||
}); | ||
|
||
for(var i = 0; i < container.length; i++) { | ||
var transform = container[i], | ||
type = transform.type, | ||
|
@@ -796,6 +818,7 @@ function applyTransforms(fullTrace, fullData, layout) { | |
transform: transform, | ||
fullTrace: fullTrace, | ||
fullData: fullData, | ||
attributeSets: attributeSets, | ||
layout: layout | ||
}); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,20 @@ | ||
/** | ||
* Copyright 2012-2016, Plotly, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
'use strict'; | ||
|
||
var isNumeric = require('fast-isnumeric'); | ||
|
||
// var Lib = require('@src/lib'); | ||
var Lib = require('../../../../src/lib'); | ||
var Lib = require('../lib'); | ||
|
||
/* eslint no-unused-vars: 0*/ | ||
|
||
|
||
// so that Plotly.register knows what to do with it | ||
exports.moduleType = 'transform'; | ||
|
||
|
@@ -16,17 +25,21 @@ exports.name = 'filter'; | |
exports.attributes = { | ||
operation: { | ||
valType: 'enumerated', | ||
values: ['=', '<', '>'], | ||
values: ['=', '<', '>', 'within', 'notwithin', 'in', 'notin'], | ||
dflt: '=' | ||
}, | ||
value: { | ||
valType: 'number', | ||
valType: 'any', | ||
dflt: 0 | ||
}, | ||
filtersrc: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you mean There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you, yes. I was looking at the wrong branch. |
||
valType: 'enumerated', | ||
values: ['x', 'y'], | ||
dflt: 'x' | ||
dflt: 'x', | ||
ids: { | ||
valType: 'data_array', | ||
description: 'A list of keys for object constancy of data points during animation' | ||
} | ||
} | ||
}; | ||
|
||
|
@@ -54,6 +67,16 @@ exports.supplyDefaults = function(transformIn, fullData, layout) { | |
coerce('value'); | ||
coerce('filtersrc'); | ||
|
||
// numeric values as character should be converted to numeric | ||
if(Array.isArray(transformOut.value)) { | ||
transformOut.value = transformOut.value.map(function(v) { | ||
if(isNumeric(v)) v = +v; | ||
return v; | ||
}); | ||
} else { | ||
if(isNumeric(transformOut.value)) transformOut.value = +transformOut.value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer moving the array item type conversion down to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI I haven't touched anything in |
||
} | ||
|
||
// or some more complex logic using fullData and layout | ||
|
||
return transformOut; | ||
|
@@ -121,6 +144,16 @@ function transformOne(trace, state) { | |
|
||
function getFilterFunc(opts) { | ||
var value = opts.value; | ||
// if value is not array then coerce to | ||
// an array of [value,value] so the | ||
// filter function will work | ||
// but perhaps should just error out | ||
var valueArr = []; | ||
if(!Array.isArray(value)) { | ||
valueArr = [value, value]; | ||
} else { | ||
valueArr = value; | ||
} | ||
|
||
switch(opts.operation) { | ||
case '=': | ||
|
@@ -129,6 +162,30 @@ function getFilterFunc(opts) { | |
return function(v) { return v < value; }; | ||
case '>': | ||
return function(v) { return v > value; }; | ||
case 'within': | ||
return function(v) { | ||
// if character then ignore with no side effect | ||
function notDateNumber(d) { | ||
return !(isNumeric(d) || Lib.isDateTime(d)); | ||
} | ||
if(valueArr.some(notDateNumber)) { | ||
return true; | ||
} | ||
|
||
// keep the = ? | ||
return v >= Math.min.apply(null, valueArr) && | ||
v <= Math.max.apply(null, valueArr); | ||
}; | ||
case 'notwithin': | ||
return function(v) { | ||
// keep the = ? | ||
return !(v >= Math.min.apply(null, valueArr) && | ||
v <= Math.max.apply(null, valueArr)); | ||
}; | ||
case 'in': | ||
return function(v) { return valueArr.indexOf(v) >= 0; }; | ||
case 'notin': | ||
return function(v) { return valueArr.indexOf(v) === -1; }; | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,15 @@ | ||
/** | ||
* Copyright 2012-2016, Plotly, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
'use strict'; | ||
|
||
// var Lib = require('@src/lib'); | ||
var Lib = require('../../../../src/lib'); | ||
var Lib = require('../lib'); | ||
|
||
/* eslint no-unused-vars: 0*/ | ||
|
||
|
@@ -22,7 +30,7 @@ exports.attributes = { | |
valType: 'data_array', | ||
dflt: [] | ||
}, | ||
groupColors: { | ||
style: { | ||
valType: 'any', | ||
dflt: {} | ||
} | ||
|
@@ -53,7 +61,7 @@ exports.supplyDefaults = function(transformIn, fullData, layout) { | |
if(!active) return transformOut; | ||
|
||
coerce('groups'); | ||
coerce('groupColors'); | ||
coerce('style'); | ||
|
||
// or some more complex logic using fullData and layout | ||
|
||
|
@@ -82,14 +90,16 @@ exports.transform = function(data, state) { | |
|
||
var newData = []; | ||
|
||
data.forEach(function(trace) { | ||
newData = newData.concat(transformOne(trace, state)); | ||
data.forEach(function(trace, i) { | ||
|
||
newData = newData.concat(transformOne(trace, state, state.attributeSets[i])); | ||
}); | ||
|
||
return newData; | ||
}; | ||
|
||
function transformOne(trace, state) { | ||
function transformOne(trace, state, attributeSet) { | ||
|
||
var opts = state.transform; | ||
var groups = opts.groups; | ||
|
||
|
@@ -98,28 +108,67 @@ function transformOne(trace, state) { | |
}); | ||
|
||
var newData = new Array(groupNames.length); | ||
var len = Math.min(trace.x.length, trace.y.length, groups.length); | ||
var len = groups.length; | ||
|
||
var style = opts.style || {}; | ||
|
||
var topLevelAttributes = attributeSet | ||
.filter(function(array) {return Array.isArray(getDeepProp(trace, array));}); | ||
|
||
var initializeArray = function(newTrace, a) { | ||
setDeepProp(newTrace, a, []); | ||
}; | ||
|
||
var pasteArray = function(newTrace, trace, j, a) { | ||
getDeepProp(newTrace, a).push(getDeepProp(trace, a)[j]); | ||
}; | ||
|
||
// fixme the O(n**3) complexity | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True, we could probably do better in terms of performance, but There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, done |
||
for(var i = 0; i < groupNames.length; i++) { | ||
var groupName = groupNames[i]; | ||
|
||
// TODO is this the best pattern ??? | ||
// maybe we could abstract this out | ||
var newTrace = newData[i] = Lib.extendDeep({}, trace); | ||
|
||
newTrace.x = []; | ||
newTrace.y = []; | ||
topLevelAttributes.forEach(initializeArray.bind(null, newTrace)); | ||
|
||
for(var j = 0; j < len; j++) { | ||
if(groups[j] !== groupName) continue; | ||
|
||
newTrace.x.push(trace.x[j]); | ||
newTrace.y.push(trace.y[j]); | ||
topLevelAttributes.forEach(pasteArray.bind(0, newTrace, trace, j)); | ||
} | ||
|
||
newTrace.name = groupName; | ||
newTrace.marker.color = opts.groupColors[groupName]; | ||
|
||
// there's no need to coerce style[groupName] here | ||
// as another round of supplyDefaults is done on the transformed traces | ||
newTrace = Lib.extendDeep(newTrace, style[groupName] || {}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. very nice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... but maybe add a comment mentioning there's no need to coerce style[groupName] here as another round of supplyDefaults is done on the transformed traces. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had some false starts but as it turned out, almost all code was already present :-) |
||
} | ||
|
||
return newData; | ||
} | ||
|
||
function getDeepProp(thing, propArray) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. I'll take a look! Btw. I was also thinking about using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd vote for using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turns out that |
||
var result = thing; | ||
var i; | ||
for(i = 0; i < propArray.length; i++) { | ||
result = result[propArray[i]]; | ||
if(result === void(0)) { | ||
return result; | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
function setDeepProp(thing, propArray, value) { | ||
var current = thing; | ||
var i; | ||
for(i = 0; i < propArray.length - 1; i++) { | ||
if(current[propArray[i]] === void(0)) { | ||
current[propArray[i]] = {}; | ||
} | ||
current = current[propArray[i]]; | ||
} | ||
current[propArray[propArray.length - 1]] = value; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
'use strict'; | ||
|
||
var d3 = require('d3'); | ||
|
||
module.exports = function assertDims(dims) { | ||
var traces = d3.selectAll('.trace'); | ||
|
||
expect(traces.size()) | ||
.toEqual(dims.length, 'to have correct number of traces'); | ||
|
||
traces.each(function(_, i) { | ||
var trace = d3.select(this); | ||
var points = trace.selectAll('.point'); | ||
|
||
expect(points.size()) | ||
.toEqual(dims[i], 'to have correct number of pts in trace ' + i); | ||
}); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
'use strict'; | ||
|
||
var d3 = require('d3'); | ||
|
||
module.exports = function assertStyle(dims, color, opacity) { | ||
var N = dims.reduce(function(a, b) { | ||
return a + b; | ||
}); | ||
|
||
var traces = d3.selectAll('.trace'); | ||
expect(traces.size()) | ||
.toEqual(dims.length, 'to have correct number of traces'); | ||
|
||
expect(d3.selectAll('.point').size()) | ||
.toEqual(N, 'to have correct total number of points'); | ||
|
||
traces.each(function(_, i) { | ||
var trace = d3.select(this); | ||
var points = trace.selectAll('.point'); | ||
|
||
expect(points.size()) | ||
.toEqual(dims[i], 'to have correct number of pts in trace ' + i); | ||
|
||
points.each(function() { | ||
var point = d3.select(this); | ||
|
||
expect(point.style('fill')) | ||
.toEqual(color[i], 'to have correct pt color'); | ||
expect(+point.style('opacity')) | ||
.toEqual(opacity[i], 'to have correct pt opacity'); | ||
}); | ||
}); | ||
}; |
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 biggestPlotly
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 inv2.0.0
e.g.Plotly.crawlAttributes
?