Skip to content

Commit 658a096

Browse files
authored
Merge pull request #1892 from plotly/filter-groupby-combo
Filter-groupby interaction
2 parents 768b492 + 88b5673 commit 658a096

File tree

2 files changed

+219
-22
lines changed

2 files changed

+219
-22
lines changed

src/transforms/groupby.js

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -116,34 +116,29 @@ exports.supplyDefaults = function(transformIn) {
116116
* array of transformed traces
117117
*/
118118
exports.transform = function(data, state) {
119+
var newTraces, i, j;
119120
var newData = [];
120121

121-
for(var i = 0; i < data.length; i++) {
122-
newData = newData.concat(transformOne(data[i], state));
122+
for(i = 0; i < data.length; i++) {
123+
newTraces = transformOne(data[i], state);
124+
125+
for(j = 0; j < newTraces.length; j++) {
126+
newData.push(newTraces[j]);
127+
}
123128
}
124129

125130
return newData;
126131
};
127132

128-
function initializeArray(newTrace, a) {
129-
Lib.nestedProperty(newTrace, a).set([]);
130-
}
131-
132-
function pasteArray(newTrace, trace, j, a) {
133-
Lib.nestedProperty(newTrace, a).set(
134-
Lib.nestedProperty(newTrace, a).get().concat([
135-
Lib.nestedProperty(trace, a).get()[j]
136-
])
137-
);
138-
}
139133

140134
function transformOne(trace, state) {
141-
var i;
135+
var i, j, k, attr, srcArray, groupName, newTrace, transforms, arrayLookup;
136+
142137
var opts = state.transform;
143138
var groups = trace.transforms[state.transformIndex].groups;
144139

145140
if(!(Array.isArray(groups)) || groups.length === 0) {
146-
return trace;
141+
return [trace];
147142
}
148143

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

156+
// An index to map group name --> expanded trace index
157+
var indexLookup = {};
158+
161159
for(i = 0; i < groupNames.length; i++) {
162-
var groupName = groupNames[i];
160+
groupName = groupNames[i];
161+
indexLookup[groupName] = i;
162+
163+
// Start with a deep extend that just copies array references.
164+
newTrace = newData[i] = Lib.extendDeepNoArrays({}, trace);
165+
newTrace.name = groupName;
163166

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

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

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

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

174-
newTrace.name = groupName;
196+
// Get the input data:
197+
srcArray = Lib.nestedProperty(trace, attr).get();
198+
199+
// Send each data point to the appropriate expanded trace:
200+
for(j = 0; j < len; j++) {
201+
// Map group data --> trace index --> array and push data onto it
202+
arrayLookup[indexLookup[groups[j]]].push(srcArray[j]);
203+
}
204+
}
205+
206+
for(i = 0; i < groupNames.length; i++) {
207+
groupName = groupNames[i];
208+
newTrace = newData[i];
175209

176210
Plots.clearExpandedTraceDefaultColors(newTrace);
177211

test/jasmine/tests/transform_multi_test.js

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,3 +727,166 @@ describe('restyle applied on transforms:', function() {
727727
});
728728

729729
});
730+
731+
describe('supplyDefaults with groupby + filter', function() {
732+
function calcDatatoTrace(calcTrace) {
733+
return calcTrace[0].trace;
734+
}
735+
736+
function _transform(data, layout) {
737+
var gd = {
738+
data: data,
739+
layout: layout || {}
740+
};
741+
742+
Plots.supplyDefaults(gd);
743+
Plots.doCalcdata(gd);
744+
745+
return gd.calcdata.map(calcDatatoTrace);
746+
}
747+
748+
it('filter + groupby with blank target', function() {
749+
var out = _transform([{
750+
x: [1, 2, 3, 4, 5, 6, 7],
751+
y: [4, 6, 5, 7, 6, 8, 9],
752+
transforms: [{
753+
type: 'filter',
754+
operation: '<',
755+
value: 6.5
756+
}, {
757+
type: 'groupby',
758+
groups: [1, 1, 1, 2, 2, 2, 2]
759+
}]
760+
}]);
761+
762+
expect(out[0].x).toEqual([1, 2, 3]);
763+
expect(out[0].y).toEqual([4, 6, 5]);
764+
765+
expect(out[1].x).toEqual([4, 5, 6]);
766+
expect(out[1].y).toEqual([7, 6, 8]);
767+
});
768+
769+
it('fiter + groupby', function() {
770+
var out = _transform([{
771+
x: [5, 4, 3],
772+
y: [6, 5, 4],
773+
}, {
774+
x: [1, 2, 3, 4, 5, 6, 7],
775+
y: [4, 6, 5, 7, 8, 9, 10],
776+
transforms: [{
777+
type: 'filter',
778+
target: [1, 2, 3, 4, 5, 6, 7],
779+
operation: '<',
780+
value: 6.5
781+
}, {
782+
type: 'groupby',
783+
groups: [1, 1, 1, 2, 2, 2, 2]
784+
}]
785+
}]);
786+
787+
expect(out[0].x).toEqual([5, 4, 3]);
788+
expect(out[0].y).toEqual([6, 5, 4]);
789+
790+
expect(out[1].x).toEqual([1, 2, 3]);
791+
expect(out[1].y).toEqual([4, 6, 5]);
792+
793+
expect(out[2].x).toEqual([4, 5, 6]);
794+
expect(out[2].y).toEqual([7, 8, 9]);
795+
});
796+
797+
it('groupby + filter', function() {
798+
var out = _transform([{
799+
x: [1, 2, 3, 4, 5, 6, 7],
800+
y: [4, 6, 5, 7, 6, 8, 9],
801+
transforms: [{
802+
type: 'groupby',
803+
groups: [1, 1, 1, 2, 2, 2, 2]
804+
}, {
805+
type: 'filter',
806+
target: [1, 2, 3, 4, 5, 6, 7],
807+
operation: '<',
808+
value: 6.5
809+
}]
810+
}]);
811+
812+
expect(out[0].x).toEqual([1, 2, 3]);
813+
expect(out[0].y).toEqual([4, 6, 5]);
814+
815+
expect(out[1].x).toEqual([4, 5, 6]);
816+
expect(out[1].y).toEqual([7, 6, 8]);
817+
});
818+
819+
it('groupby + groupby', function() {
820+
var out = _transform([{
821+
x: [1, 2, 3, 4, 5, 6, 7, 8],
822+
y: [4, 6, 5, 7, 6, 8, 9, 10],
823+
transforms: [{
824+
type: 'groupby',
825+
groups: [1, 1, 1, 1, 2, 2, 2, 2]
826+
}, {
827+
type: 'groupby',
828+
groups: [3, 4, 3, 4, 3, 4, 3, 5],
829+
}]
830+
}]);
831+
// | | | | | | | |
832+
// v v v v v v v v
833+
// Trace number: 0 1 0 1 2 3 2 4
834+
835+
expect(out.length).toEqual(5);
836+
expect(out[0].x).toEqual([1, 3]);
837+
expect(out[1].x).toEqual([2, 4]);
838+
expect(out[2].x).toEqual([5, 7]);
839+
expect(out[3].x).toEqual([6]);
840+
expect(out[4].x).toEqual([8]);
841+
});
842+
843+
it('groupby + groupby + filter', function() {
844+
var out = _transform([{
845+
x: [1, 2, 3, 4, 5, 6, 7, 8],
846+
y: [4, 6, 5, 7, 6, 8, 9, 10],
847+
transforms: [{
848+
type: 'groupby',
849+
groups: [1, 1, 1, 1, 2, 2, 2, 2]
850+
}, {
851+
type: 'groupby',
852+
groups: [3, 4, 3, 4, 3, 4, 3, 5],
853+
}, {
854+
type: 'filter',
855+
target: [1, 2, 3, 4, 5, 6, 7, 8],
856+
operation: '<',
857+
value: 4.5
858+
}]
859+
}]);
860+
// | | | | | | | |
861+
// v v v v v v v v
862+
// Trace number: 0 1 0 1 2 3 2 4
863+
864+
expect(out.length).toEqual(5);
865+
expect(out[0].x).toEqual([1, 3]);
866+
expect(out[1].x).toEqual([2, 4]);
867+
expect(out[2].x).toEqual([]);
868+
expect(out[3].x).toEqual([]);
869+
expect(out[4].x).toEqual([]);
870+
});
871+
872+
it('fiter + filter', function() {
873+
var out = _transform([{
874+
x: [1, 2, 3, 4, 5, 6, 7],
875+
y: [4, 6, 5, 7, 8, 9, 10],
876+
transforms: [{
877+
type: 'filter',
878+
target: [1, 2, 3, 4, 5, 6, 7],
879+
operation: '<',
880+
value: 6.5
881+
}, {
882+
type: 'filter',
883+
target: [1, 2, 3, 4, 5, 6, 7],
884+
operation: '>',
885+
value: 1.5
886+
}]
887+
}]);
888+
889+
expect(out[0].x).toEqual([2, 3, 4, 5, 6]);
890+
expect(out[0].y).toEqual([6, 5, 7, 8, 9]);
891+
});
892+
});

0 commit comments

Comments
 (0)