Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/transforms/aggregate.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@
var Axes = require('../plots/cartesian/axes');
var Lib = require('../lib');
var PlotSchema = require('../plot_api/plot_schema');
var pointsAccessorFunction = require('./helpers').pointsAccessorFunction;
var BADNUM = require('../constants/numerical').BADNUM;

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

var i, vi, groupIndex;
var i, vi, groupIndex, newGrouping;

var groupIndices = {};
var indexToPoints = {};
var groupings = [];

var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts);

for(i = 0; i < groupArray.length; i++) {
vi = groupArray[i];
groupIndex = groupIndices[vi];
if(groupIndex === undefined) {
groupIndices[vi] = groupings.length;
groupings.push([i]);
newGrouping = [i];
groupings.push(newGrouping);
indexToPoints[groupIndices[vi]] = originalPointsAccessor(i);
}
else {
groupings[groupIndex].push(i);
indexToPoints[groupIndices[vi]] = (indexToPoints[groupIndices[vi]] || []).concat(originalPointsAccessor(i));
}
else groupings[groupIndex].push(i);
}

opts._indexToPoints = indexToPoints;

var aggregations = opts.aggregations;

for(i = 0; i < aggregations.length; i++) {
12 changes: 11 additions & 1 deletion src/transforms/filter.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@
var Lib = require('../lib');
var Registry = require('../registry');
var Axes = require('../plots/cartesian/axes');
var pointsAccessorFunction = require('./helpers').pointsAccessorFunction;

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

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

var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts);

// loop through filter array, fill trace arrays if passed
for(var i = 0; i < len; i++) {
var passed = filterFunc(targetArray[i]);
if(passed) forAllAttrs(fillFn, i);
if(passed) {
forAllAttrs(fillFn, i);
indexToPoints[index++] = originalPointsAccessor(i);
}
}

opts._indexToPoints = indexToPoints;
};

function getFilterFunc(opts, d2c, targetCalendar) {
24 changes: 24 additions & 0 deletions src/transforms/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright 2012-2017, 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';

exports.pointsAccessorFunction = function(transforms, opts) {
var tr;
var prevIndexToPoints;
for(var i = 0; i < transforms.length; i++) {
tr = transforms[i];
if(tr === opts) break;
if(!tr._indexToPoints || tr.enabled === false) continue;
prevIndexToPoints = tr._indexToPoints;
}
var originalPointsAccessor = prevIndexToPoints ?
function(i) {return prevIndexToPoints[i];} :
function(i) {return [i];};
return originalPointsAccessor;
};
20 changes: 17 additions & 3 deletions test/jasmine/tests/transform_aggregate_test.js
Original file line number Diff line number Diff line change
@@ -59,6 +59,10 @@ describe('aggregate', function() {
expect(traceOut.marker.opacity).toEqual([0.6, 'boo']);
expect(traceOut.marker.line.color).toEqual(['the end', 3.3]);
expect(traceOut.marker.line.width).toEqual([4, 1]);

var transform = traceOut.transforms[0];
var inverseMapping = transform._indexToPoints;
expect(inverseMapping).toEqual({0: [0, 2, 3, 4], 1: [1]});
});

it('handles all funcs except sum for date data', function() {
@@ -163,6 +167,10 @@ describe('aggregate', function() {
expect(traceOut.y).toEqual(['b', undefined]);
// category average: can result in fractional categories -> rounds (0.5 rounds to 1)
expect(traceOut.text).toEqual(['b', 'b']);

var transform = traceOut.transforms[0];
var inverseMapping = transform._indexToPoints;
expect(inverseMapping).toEqual({0: [0, 1], 1: [2, 3]});
});

it('can aggregate on an existing data array', function() {
@@ -185,10 +193,12 @@ describe('aggregate', function() {
expect(traceOut.x).toEqual([8, 7]);
expect(traceOut.y).toBeCloseToArray([16 / 3, 7], 5);
expect(traceOut.marker.size).toEqual([10, 20]);

var transform = traceOut.transforms[0];
var inverseMapping = transform._indexToPoints;
expect(inverseMapping).toEqual({0: [0, 1, 4], 1: [2, 3]});
});

// Regression test - throws before fix:
// https://github.com/plotly/plotly.js/issues/2024
it('can handle case where aggregation array is missing', function() {
Plotly.newPlot(gd, [{
x: [1, 2, 3, 4, 5],
@@ -205,6 +215,10 @@ describe('aggregate', function() {
expect(traceOut.x).toEqual([1, 3]);
expect(traceOut.y).toEqual([2, 6]);
expect(traceOut.marker.size).toEqual([10, 20]);

var transform = traceOut.transforms[0];
var inverseMapping = transform._indexToPoints;
expect(inverseMapping).toEqual({0: [0, 1, 4], 1: [2, 3]});
});

it('handles median, mode, rms, & stddev for numeric data', function() {
@@ -257,7 +271,7 @@ describe('aggregate', function() {
aggregations: [
{target: 'x', func: 'sum'},
{target: 'x', func: 'avg'},
{target: 'y', func: 'avg'},
{target: 'y', func: 'avg'}
]
}]
}]);
10 changes: 8 additions & 2 deletions test/jasmine/tests/transform_filter_test.js
Original file line number Diff line number Diff line change
@@ -62,7 +62,7 @@ describe('filter transforms defaults:', function() {
traceIn = {
x: [1, 2, 3],
transforms: [{
type: 'filter',
type: 'filter'
}, {
type: 'filter',
target: 0
@@ -143,6 +143,7 @@ describe('filter transforms calc:', function() {
expect(out[0].x).toEqual([0, 1]);
expect(out[0].y).toEqual([1, 2]);
expect(out[0].z).toEqual(['2016-10-21', '2016-12-02']);
expect(out[0].transforms[0]._indexToPoints).toEqual({0: [3], 1: [4]});
});

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

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

expect(out[0].x).toEqual([1, 2]);
expect(out[0].y).toEqual([2, 3]);
expect(out[0].transforms[0]._indexToPoints).toEqual({0: [4], 1: [5], 2: [6]});
expect(out[0].transforms[1]._indexToPoints).toEqual({0: [4], 1: [5]});
});

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

expect(out[0].x).toEqual([3]);
expect(out[0].y).toEqual([1]);
expect(out[0].transforms[0]._indexToPoints).toEqual({0: [4], 1: [5], 2: [6]});
expect(out[0].transforms[2]._indexToPoints).toEqual({0: [6]});
});

it('should preserve gaps in data when `preservegaps` is turned on', function() {
54 changes: 53 additions & 1 deletion test/jasmine/tests/transform_multi_test.js
Original file line number Diff line number Diff line change
@@ -245,7 +245,7 @@ describe('multiple transforms:', function() {
groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'],
styles: [{
target: 'a',
value: {marker: {color: 'red'}},
value: {marker: {color: 'red'}}
}, {
target: 'b',
value: {marker: {color: 'blue'}}
@@ -277,8 +277,60 @@ describe('multiple transforms:', function() {
}]
}];

var mockData2 = [{
x: [1, 2, 3, 4, 5],
y: [2, 3, 1, 7, 9],
marker: {size: [10, 20, 20, 20, 10]},
transforms: [
{
type: 'filter',
operation: '>',
value: 2,
target: 'y'
},
{
type: 'aggregate',
groups: 'marker.size',
aggregations: [
{target: 'x', func: 'sum'}, // 20: 6, 10: 5
{target: 'y', func: 'avg'} // 20: 5, 10: 9
]
},
{
type: 'filter',
operation: '<',
value: 6,
target: 'x'
}
]
}];

afterEach(destroyGraphDiv);

it('Plotly.plot should plot the transform traces - filter|aggregate|filter', function(done) {
var data = Lib.extendDeep([], mockData2);

Plotly.plot(gd, data).then(function() {
expect(gd.data.length).toEqual(1);

// this would be the result if we didn't have a second filter - kept for test case overview
// expect(gd._fullData[0].x).toEqual([6, 5]);
// expect(gd._fullData[0].y).toEqual([5, 9]);
// expect(gd._fullData[0].marker.size).toEqual([20, 10]);

expect(gd._fullData[0].x).toEqual([5]);
expect(gd._fullData[0].y).toEqual([9]);
expect(gd._fullData[0].marker.size).toEqual([10]);

expect(gd._fullData[0].transforms[0]._indexToPoints).toEqual({0: [1], 1: [3], 2: [4]});
expect(gd._fullData[0].transforms[1]._indexToPoints).toEqual({0: [1, 3], 1: [4]});
expect(gd._fullData[0].transforms[2]._indexToPoints).toEqual({0: [4]});

done();
});
});


it('Plotly.plot should plot the transform traces', function(done) {
var data = Lib.extendDeep([], mockData0);