Skip to content

Filter-groupby interaction #1892

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

Merged
merged 12 commits into from
Jul 26, 2017
78 changes: 56 additions & 22 deletions src/transforms/groupby.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,34 +116,29 @@ exports.supplyDefaults = function(transformIn) {
* array of transformed traces
*/
exports.transform = function(data, state) {
var newTraces, i, j;
var newData = [];

for(var i = 0; i < data.length; i++) {
newData = newData.concat(transformOne(data[i], state));
for(i = 0; i < data.length; i++) {
newTraces = transformOne(data[i], state);

for(j = 0; j < newTraces.length; j++) {
newData.push(newTraces[j]);
}
}

return newData;
};

function initializeArray(newTrace, a) {
Lib.nestedProperty(newTrace, a).set([]);
}

function pasteArray(newTrace, trace, j, a) {
Lib.nestedProperty(newTrace, a).set(
Lib.nestedProperty(newTrace, a).get().concat([
Lib.nestedProperty(trace, a).get()[j]
])
);
}

function transformOne(trace, state) {
var i;
var i, j, k, attr, srcArray, groupName, newTrace, transforms, arrayLookup;

var opts = state.transform;
var groups = trace.transforms[state.transformIndex].groups;

if(!(Array.isArray(groups)) || groups.length === 0) {
return trace;
return [trace];
}

var groupNames = Lib.filterUnique(groups),
Expand All @@ -158,20 +153,59 @@ function transformOne(trace, state) {
styleLookup[styles[i].target] = styles[i].value;
}

// An index to map group name --> expanded trace index
var indexLookup = {};

for(i = 0; i < groupNames.length; i++) {
var groupName = groupNames[i];
groupName = groupNames[i];
indexLookup[groupName] = i;

// Start with a deep extend that just copies array references.
newTrace = newData[i] = Lib.extendDeepNoArrays({}, trace);
newTrace.name = groupName;

var newTrace = newData[i] = Lib.extendDeepNoArrays({}, trace);
// In order for groups to apply correctly to other transform data (e.g.
// a filter transform), we have to break the connection and clone the
// transforms so that each group writes grouped values into a different
// destination. This function does not break the array reference
// connection between the split transforms it creates. That's handled in
// initialize, which creates a new empty array for each arrayAttr.
transforms = newTrace.transforms;
newTrace.transforms = [];
for(j = 0; j < transforms.length; j++) {
newTrace.transforms[j] = Lib.extendDeepNoArrays({}, transforms[j]);
}

arrayAttrs.forEach(initializeArray.bind(null, newTrace));
// Initialize empty arrays for the arrayAttrs, to be split in the next step
for(j = 0; j < arrayAttrs.length; j++) {
Lib.nestedProperty(newTrace, arrayAttrs[j]).set([]);
}
}

for(var j = 0; j < len; j++) {
if(groups[j] !== groupName) continue;
// For each array attribute including those nested inside this and other
// transforms (small note that we technically only need to do this for
// transforms that have not yet been applied):
for(k = 0; k < arrayAttrs.length; k++) {
attr = arrayAttrs[k];

arrayAttrs.forEach(pasteArray.bind(0, newTrace, trace, j));
// Cache all the arrays to which we'll push:
for(j = 0, arrayLookup = []; j < groupNames.length; j++) {
arrayLookup[j] = Lib.nestedProperty(newData[j], attr).get();
}

newTrace.name = groupName;
// Get the input data:
srcArray = Lib.nestedProperty(trace, attr).get();

// Send each data point to the appropriate expanded trace:
for(j = 0; j < len; j++) {
// Map group data --> trace index --> array and push data onto it
arrayLookup[indexLookup[groups[j]]].push(srcArray[j]);
}
}

for(i = 0; i < groupNames.length; i++) {
groupName = groupNames[i];
newTrace = newData[i];

Plots.clearExpandedTraceDefaultColors(newTrace);

Expand Down
163 changes: 163 additions & 0 deletions test/jasmine/tests/transform_multi_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -727,3 +727,166 @@ describe('restyle applied on transforms:', function() {
});

});

describe('supplyDefaults with groupby + filter', function() {
function calcDatatoTrace(calcTrace) {
return calcTrace[0].trace;
}

function _transform(data, layout) {
var gd = {
data: data,
layout: layout || {}
};

Plots.supplyDefaults(gd);
Plots.doCalcdata(gd);

return gd.calcdata.map(calcDatatoTrace);
}

it('filter + groupby with blank target', function() {
var out = _transform([{
x: [1, 2, 3, 4, 5, 6, 7],
y: [4, 6, 5, 7, 6, 8, 9],
transforms: [{
type: 'filter',
operation: '<',
value: 6.5
}, {
type: 'groupby',
groups: [1, 1, 1, 2, 2, 2, 2]
}]
}]);

expect(out[0].x).toEqual([1, 2, 3]);
expect(out[0].y).toEqual([4, 6, 5]);

expect(out[1].x).toEqual([4, 5, 6]);
expect(out[1].y).toEqual([7, 6, 8]);
});

it('fiter + groupby', function() {
var out = _transform([{
x: [5, 4, 3],
y: [6, 5, 4],
}, {
x: [1, 2, 3, 4, 5, 6, 7],
y: [4, 6, 5, 7, 8, 9, 10],
transforms: [{
type: 'filter',
target: [1, 2, 3, 4, 5, 6, 7],
operation: '<',
value: 6.5
}, {
type: 'groupby',
groups: [1, 1, 1, 2, 2, 2, 2]
}]
}]);

expect(out[0].x).toEqual([5, 4, 3]);
expect(out[0].y).toEqual([6, 5, 4]);

expect(out[1].x).toEqual([1, 2, 3]);
expect(out[1].y).toEqual([4, 6, 5]);

expect(out[2].x).toEqual([4, 5, 6]);
expect(out[2].y).toEqual([7, 8, 9]);
});

it('groupby + filter', function() {
var out = _transform([{
x: [1, 2, 3, 4, 5, 6, 7],
y: [4, 6, 5, 7, 6, 8, 9],
transforms: [{
type: 'groupby',
groups: [1, 1, 1, 2, 2, 2, 2]
}, {
type: 'filter',
target: [1, 2, 3, 4, 5, 6, 7],
operation: '<',
value: 6.5
}]
}]);

expect(out[0].x).toEqual([1, 2, 3]);
expect(out[0].y).toEqual([4, 6, 5]);

expect(out[1].x).toEqual([4, 5, 6]);
expect(out[1].y).toEqual([7, 6, 8]);
});

it('groupby + groupby', function() {
var out = _transform([{
x: [1, 2, 3, 4, 5, 6, 7, 8],
y: [4, 6, 5, 7, 6, 8, 9, 10],
transforms: [{
type: 'groupby',
groups: [1, 1, 1, 1, 2, 2, 2, 2]
}, {
type: 'groupby',
groups: [3, 4, 3, 4, 3, 4, 3, 5],
}]
}]);
// | | | | | | | |
// v v v v v v v v
// Trace number: 0 1 0 1 2 3 2 4

expect(out.length).toEqual(5);
expect(out[0].x).toEqual([1, 3]);
expect(out[1].x).toEqual([2, 4]);
expect(out[2].x).toEqual([5, 7]);
expect(out[3].x).toEqual([6]);
expect(out[4].x).toEqual([8]);
});

it('groupby + groupby + filter', function() {
var out = _transform([{
x: [1, 2, 3, 4, 5, 6, 7, 8],
y: [4, 6, 5, 7, 6, 8, 9, 10],
transforms: [{
type: 'groupby',
groups: [1, 1, 1, 1, 2, 2, 2, 2]
}, {
type: 'groupby',
groups: [3, 4, 3, 4, 3, 4, 3, 5],
}, {
type: 'filter',
target: [1, 2, 3, 4, 5, 6, 7, 8],
operation: '<',
value: 4.5
}]
}]);
// | | | | | | | |
// v v v v v v v v
// Trace number: 0 1 0 1 2 3 2 4

expect(out.length).toEqual(5);
expect(out[0].x).toEqual([1, 3]);
expect(out[1].x).toEqual([2, 4]);
expect(out[2].x).toEqual([]);
expect(out[3].x).toEqual([]);
expect(out[4].x).toEqual([]);
});

it('fiter + filter', function() {
var out = _transform([{
x: [1, 2, 3, 4, 5, 6, 7],
y: [4, 6, 5, 7, 8, 9, 10],
transforms: [{
type: 'filter',
target: [1, 2, 3, 4, 5, 6, 7],
operation: '<',
value: 6.5
}, {
type: 'filter',
target: [1, 2, 3, 4, 5, 6, 7],
operation: '>',
value: 1.5
}]
}]);

expect(out[0].x).toEqual([2, 3, 4, 5, 6]);
expect(out[0].y).toEqual([6, 5, 7, 8, 9]);
});
});