Skip to content

Commit fd3a5b8

Browse files
authored
Merge pull request #2126 from plotly/transform-inverse-mapping
Transform inverse mapping
2 parents 77d0858 + ce3a118 commit fd3a5b8

File tree

6 files changed

+128
-10
lines changed

6 files changed

+128
-10
lines changed

src/transforms/aggregate.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
var Axes = require('../plots/cartesian/axes');
1212
var Lib = require('../lib');
1313
var PlotSchema = require('../plot_api/plot_schema');
14+
var pointsAccessorFunction = require('./helpers').pointsAccessorFunction;
1415
var BADNUM = require('../constants/numerical').BADNUM;
1516

1617
exports.moduleType = 'transform';
@@ -215,20 +216,31 @@ exports.calcTransform = function(gd, trace, opts) {
215216
var groupArray = Lib.getTargetArray(trace, {target: groups});
216217
if(!groupArray) return;
217218

218-
var i, vi, groupIndex;
219+
var i, vi, groupIndex, newGrouping;
219220

220221
var groupIndices = {};
222+
var indexToPoints = {};
221223
var groupings = [];
224+
225+
var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts);
226+
222227
for(i = 0; i < groupArray.length; i++) {
223228
vi = groupArray[i];
224229
groupIndex = groupIndices[vi];
225230
if(groupIndex === undefined) {
226231
groupIndices[vi] = groupings.length;
227-
groupings.push([i]);
232+
newGrouping = [i];
233+
groupings.push(newGrouping);
234+
indexToPoints[groupIndices[vi]] = originalPointsAccessor(i);
235+
}
236+
else {
237+
groupings[groupIndex].push(i);
238+
indexToPoints[groupIndices[vi]] = (indexToPoints[groupIndices[vi]] || []).concat(originalPointsAccessor(i));
228239
}
229-
else groupings[groupIndex].push(i);
230240
}
231241

242+
opts._indexToPoints = indexToPoints;
243+
232244
var aggregations = opts.aggregations;
233245

234246
for(i = 0; i < aggregations.length; i++) {

src/transforms/filter.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
var Lib = require('../lib');
1212
var Registry = require('../registry');
1313
var Axes = require('../plots/cartesian/axes');
14+
var pointsAccessorFunction = require('./helpers').pointsAccessorFunction;
1415

1516
var COMPARISON_OPS = ['=', '!=', '<', '>=', '>', '<='];
1617
var INTERVAL_OPS = ['[]', '()', '[)', '(]', '][', ')(', '](', ')['];
@@ -170,6 +171,8 @@ exports.calcTransform = function(gd, trace, opts) {
170171
var d2c = Axes.getDataToCoordFunc(gd, trace, target, targetArray);
171172
var filterFunc = getFilterFunc(opts, d2c, targetCalendar);
172173
var originalArrays = {};
174+
var indexToPoints = {};
175+
var index = 0;
173176

174177
function forAllAttrs(fn, index) {
175178
for(var j = 0; j < arrayAttrs.length; j++) {
@@ -203,11 +206,18 @@ exports.calcTransform = function(gd, trace, opts) {
203206
// copy all original array attribute values, and clear arrays in trace
204207
forAllAttrs(initFn);
205208

209+
var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts);
210+
206211
// loop through filter array, fill trace arrays if passed
207212
for(var i = 0; i < len; i++) {
208213
var passed = filterFunc(targetArray[i]);
209-
if(passed) forAllAttrs(fillFn, i);
214+
if(passed) {
215+
forAllAttrs(fillFn, i);
216+
indexToPoints[index++] = originalPointsAccessor(i);
217+
}
210218
}
219+
220+
opts._indexToPoints = indexToPoints;
211221
};
212222

213223
function getFilterFunc(opts, d2c, targetCalendar) {

src/transforms/helpers.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
exports.pointsAccessorFunction = function(transforms, opts) {
12+
var tr;
13+
var prevIndexToPoints;
14+
for(var i = 0; i < transforms.length; i++) {
15+
tr = transforms[i];
16+
if(tr === opts) break;
17+
if(!tr._indexToPoints || tr.enabled === false) continue;
18+
prevIndexToPoints = tr._indexToPoints;
19+
}
20+
var originalPointsAccessor = prevIndexToPoints ?
21+
function(i) {return prevIndexToPoints[i];} :
22+
function(i) {return [i];};
23+
return originalPointsAccessor;
24+
};

test/jasmine/tests/transform_aggregate_test.js

+17-3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ describe('aggregate', function() {
5959
expect(traceOut.marker.opacity).toEqual([0.6, 'boo']);
6060
expect(traceOut.marker.line.color).toEqual(['the end', 3.3]);
6161
expect(traceOut.marker.line.width).toEqual([4, 1]);
62+
63+
var transform = traceOut.transforms[0];
64+
var inverseMapping = transform._indexToPoints;
65+
expect(inverseMapping).toEqual({0: [0, 2, 3, 4], 1: [1]});
6266
});
6367

6468
it('handles all funcs except sum for date data', function() {
@@ -163,6 +167,10 @@ describe('aggregate', function() {
163167
expect(traceOut.y).toEqual(['b', undefined]);
164168
// category average: can result in fractional categories -> rounds (0.5 rounds to 1)
165169
expect(traceOut.text).toEqual(['b', 'b']);
170+
171+
var transform = traceOut.transforms[0];
172+
var inverseMapping = transform._indexToPoints;
173+
expect(inverseMapping).toEqual({0: [0, 1], 1: [2, 3]});
166174
});
167175

168176
it('can aggregate on an existing data array', function() {
@@ -185,10 +193,12 @@ describe('aggregate', function() {
185193
expect(traceOut.x).toEqual([8, 7]);
186194
expect(traceOut.y).toBeCloseToArray([16 / 3, 7], 5);
187195
expect(traceOut.marker.size).toEqual([10, 20]);
196+
197+
var transform = traceOut.transforms[0];
198+
var inverseMapping = transform._indexToPoints;
199+
expect(inverseMapping).toEqual({0: [0, 1, 4], 1: [2, 3]});
188200
});
189201

190-
// Regression test - throws before fix:
191-
// https://github.com/plotly/plotly.js/issues/2024
192202
it('can handle case where aggregation array is missing', function() {
193203
Plotly.newPlot(gd, [{
194204
x: [1, 2, 3, 4, 5],
@@ -205,6 +215,10 @@ describe('aggregate', function() {
205215
expect(traceOut.x).toEqual([1, 3]);
206216
expect(traceOut.y).toEqual([2, 6]);
207217
expect(traceOut.marker.size).toEqual([10, 20]);
218+
219+
var transform = traceOut.transforms[0];
220+
var inverseMapping = transform._indexToPoints;
221+
expect(inverseMapping).toEqual({0: [0, 1, 4], 1: [2, 3]});
208222
});
209223

210224
it('handles median, mode, rms, & stddev for numeric data', function() {
@@ -257,7 +271,7 @@ describe('aggregate', function() {
257271
aggregations: [
258272
{target: 'x', func: 'sum'},
259273
{target: 'x', func: 'avg'},
260-
{target: 'y', func: 'avg'},
274+
{target: 'y', func: 'avg'}
261275
]
262276
}]
263277
}]);

test/jasmine/tests/transform_filter_test.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe('filter transforms defaults:', function() {
6262
traceIn = {
6363
x: [1, 2, 3],
6464
transforms: [{
65-
type: 'filter',
65+
type: 'filter'
6666
}, {
6767
type: 'filter',
6868
target: 0
@@ -143,6 +143,7 @@ describe('filter transforms calc:', function() {
143143
expect(out[0].x).toEqual([0, 1]);
144144
expect(out[0].y).toEqual([1, 2]);
145145
expect(out[0].z).toEqual(['2016-10-21', '2016-12-02']);
146+
expect(out[0].transforms[0]._indexToPoints).toEqual({0: [3], 1: [4]});
146147
});
147148

148149
it('should use the calendar from the target attribute if target is a string', function() {
@@ -261,13 +262,14 @@ describe('filter transforms calc:', function() {
261262
expect(out[0].x).toEqual([-2, 2, 3]);
262263
expect(out[0].y).toEqual([3, 3, 1]);
263264
expect(out[0].marker.color).toEqual([0.3, 0.3, 0.4]);
265+
expect(out[0].transforms[0]._indexToPoints).toEqual({0: [2], 1: [5], 2: [6]});
264266
});
265267

266268
it('filters should handle array on base trace attributes', function() {
267269
var out = _transform([Lib.extendDeep({}, base, {
268270
hoverinfo: ['x', 'y', 'text', 'name', 'none', 'skip', 'all'],
269271
hoverlabel: {
270-
bgcolor: ['red', 'green', 'blue', 'black', 'yellow', 'cyan', 'pink'],
272+
bgcolor: ['red', 'green', 'blue', 'black', 'yellow', 'cyan', 'pink']
271273
},
272274
transforms: [{
273275
type: 'filter',
@@ -314,6 +316,8 @@ describe('filter transforms calc:', function() {
314316

315317
expect(out[0].x).toEqual([1, 2]);
316318
expect(out[0].y).toEqual([2, 3]);
319+
expect(out[0].transforms[0]._indexToPoints).toEqual({0: [4], 1: [5], 2: [6]});
320+
expect(out[0].transforms[1]._indexToPoints).toEqual({0: [4], 1: [5]});
317321
});
318322

319323
it('filters should chain as AND (case 2)', function() {
@@ -339,6 +343,8 @@ describe('filter transforms calc:', function() {
339343

340344
expect(out[0].x).toEqual([3]);
341345
expect(out[0].y).toEqual([1]);
346+
expect(out[0].transforms[0]._indexToPoints).toEqual({0: [4], 1: [5], 2: [6]});
347+
expect(out[0].transforms[2]._indexToPoints).toEqual({0: [6]});
342348
});
343349

344350
it('should preserve gaps in data when `preservegaps` is turned on', function() {

test/jasmine/tests/transform_multi_test.js

+53-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ describe('multiple transforms:', function() {
245245
groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'],
246246
styles: [{
247247
target: 'a',
248-
value: {marker: {color: 'red'}},
248+
value: {marker: {color: 'red'}}
249249
}, {
250250
target: 'b',
251251
value: {marker: {color: 'blue'}}
@@ -277,8 +277,60 @@ describe('multiple transforms:', function() {
277277
}]
278278
}];
279279

280+
var mockData2 = [{
281+
x: [1, 2, 3, 4, 5],
282+
y: [2, 3, 1, 7, 9],
283+
marker: {size: [10, 20, 20, 20, 10]},
284+
transforms: [
285+
{
286+
type: 'filter',
287+
operation: '>',
288+
value: 2,
289+
target: 'y'
290+
},
291+
{
292+
type: 'aggregate',
293+
groups: 'marker.size',
294+
aggregations: [
295+
{target: 'x', func: 'sum'}, // 20: 6, 10: 5
296+
{target: 'y', func: 'avg'} // 20: 5, 10: 9
297+
]
298+
},
299+
{
300+
type: 'filter',
301+
operation: '<',
302+
value: 6,
303+
target: 'x'
304+
}
305+
]
306+
}];
307+
280308
afterEach(destroyGraphDiv);
281309

310+
it('Plotly.plot should plot the transform traces - filter|aggregate|filter', function(done) {
311+
var data = Lib.extendDeep([], mockData2);
312+
313+
Plotly.plot(gd, data).then(function() {
314+
expect(gd.data.length).toEqual(1);
315+
316+
// this would be the result if we didn't have a second filter - kept for test case overview
317+
// expect(gd._fullData[0].x).toEqual([6, 5]);
318+
// expect(gd._fullData[0].y).toEqual([5, 9]);
319+
// expect(gd._fullData[0].marker.size).toEqual([20, 10]);
320+
321+
expect(gd._fullData[0].x).toEqual([5]);
322+
expect(gd._fullData[0].y).toEqual([9]);
323+
expect(gd._fullData[0].marker.size).toEqual([10]);
324+
325+
expect(gd._fullData[0].transforms[0]._indexToPoints).toEqual({0: [1], 1: [3], 2: [4]});
326+
expect(gd._fullData[0].transforms[1]._indexToPoints).toEqual({0: [1, 3], 1: [4]});
327+
expect(gd._fullData[0].transforms[2]._indexToPoints).toEqual({0: [4]});
328+
329+
done();
330+
});
331+
});
332+
333+
282334
it('Plotly.plot should plot the transform traces', function(done) {
283335
var data = Lib.extendDeep([], mockData0);
284336

0 commit comments

Comments
 (0)