From 168b447de4c93a7348b082bb18b673c874aa28c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= <etienne@plot.ly>
Date: Thu, 20 Oct 2016 12:44:34 -0400
Subject: [PATCH 1/6] rename *filtersrc* filter transform attribute *target*

---
 src/transforms/filter.js                    | 54 ++++++++-----
 test/jasmine/tests/finance_test.js          |  4 +-
 test/jasmine/tests/plotschema_test.js       |  2 +-
 test/jasmine/tests/transform_filter_test.js | 89 ++++++++++-----------
 test/jasmine/tests/transform_multi_test.js  | 18 ++---
 test/jasmine/tests/transition_test.js       |  6 +-
 6 files changed, 92 insertions(+), 81 deletions(-)

diff --git a/src/transforms/filter.js b/src/transforms/filter.js
index 042798ac4ef..213ad64816e 100644
--- a/src/transforms/filter.js
+++ b/src/transforms/filter.js
@@ -27,18 +27,21 @@ exports.attributes = {
             'Determines whether this filter transform is enabled or disabled.'
         ].join(' ')
     },
-    filtersrc: {
+    target: {
         valType: 'string',
         strict: true,
         noBlank: true,
         dflt: 'x',
         description: [
-            'Sets the variable in the parent trace object',
-            'by which the filter will be applied.',
+            'Sets the filter target by which the filter is applied.',
 
+            'If a string, *target* is assumed to be a reference to a data array',
+            'in the parent trace object.',
             'To filter about nested variables, use *.* to access them.',
-            'For example, set `filtersrc` to *marker.color* to filter',
-            'about the marker color array.'
+            'For example, set `target` to *marker.color* to filter',
+            'about the marker color array.',
+
+            'If an array, *target* is then the data array by which the filter is applied.'
         ].join(' ')
     },
     operation: {
@@ -77,7 +80,7 @@ exports.attributes = {
             'Sets the value or values by which to filter by.',
 
             'Values are expected to be in the same type as the data linked',
-            'to *filtersrc*.',
+            'to *target*.',
 
             'When `operation` is set to one of the inequality values',
             '(' + INEQUALITY_OPS + ')',
@@ -108,25 +111,24 @@ exports.supplyDefaults = function(transformIn) {
     if(enabled) {
         coerce('operation');
         coerce('value');
-        coerce('filtersrc');
+        coerce('target');
     }
 
     return transformOut;
 };
 
 exports.calcTransform = function(gd, trace, opts) {
-    var filtersrc = opts.filtersrc,
-        filtersrcOk = filtersrc && Array.isArray(Lib.nestedProperty(trace, filtersrc).get());
-
-    if(!opts.enabled || !filtersrcOk) return;
+    if(!opts.enabled) return;
 
-    var dataToCoord = getDataToCoordFunc(gd, trace, filtersrc),
-        filterFunc = getFilterFunc(opts, dataToCoord);
+    var target = opts.target,
+        filterArray = getFilterArray(trace, target),
+        len = filterArray.length;
 
-    var filterArr = Lib.nestedProperty(trace, filtersrc).get(),
-        len = filterArr.length;
+    if(!len) return;
 
-    var arrayAttrs = Lib.findArrayAttributes(trace),
+    var dataToCoord = getDataToCoordFunc(gd, trace, target),
+        filterFunc = getFilterFunc(opts, dataToCoord),
+        arrayAttrs = Lib.findArrayAttributes(trace),
         originalArrays = {};
 
     // copy all original array attribute values,
@@ -147,7 +149,7 @@ exports.calcTransform = function(gd, trace, opts) {
     }
 
     for(var i = 0; i < len; i++) {
-        var v = filterArr[i];
+        var v = filterArray[i];
 
         if(!filterFunc(v)) continue;
 
@@ -157,16 +159,26 @@ exports.calcTransform = function(gd, trace, opts) {
     }
 };
 
-function getDataToCoordFunc(gd, trace, filtersrc) {
-    var ax = axisIds.getFromTrace(gd, trace, filtersrc);
+function getFilterArray(trace, target) {
+    if(typeof target === 'string' && target) {
+        var array = Lib.nestedProperty(trace, target).get();
+
+        return Array.isArray(array) ? array : [];
+    }
+
+    return false;
+}
+
+function getDataToCoordFunc(gd, trace, target) {
+    var ax = axisIds.getFromTrace(gd, trace, target);
 
-    // if 'filtersrc' has corresponding axis
+    // if 'target' has corresponding axis
     // -> use setConvert method
     if(ax) return ax.d2c;
 
     // special case for 'ids'
     // -> cast to String
-    if(filtersrc === 'ids') return function(v) { return String(v); };
+    if(target === 'ids') return function(v) { return String(v); };
 
     // otherwise
     // -> cast to Number
diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js
index 3cf908235f9..6470dbb8fdb 100644
--- a/test/jasmine/tests/finance_test.js
+++ b/test/jasmine/tests/finance_test.js
@@ -423,7 +423,7 @@ describe('finance charts calc transforms:', function() {
             transforms: [{
                 type: 'filter',
                 operation: '>',
-                filtersrc: 'open',
+                target: 'open',
                 value: 33
             }]
         });
@@ -433,7 +433,7 @@ describe('finance charts calc transforms:', function() {
             transforms: [{
                 type: 'filter',
                 operation: '{}',
-                filtersrc: 'x',
+                target: 'x',
                 value: ['2016-09-01', '2016-09-10']
             }]
         });
diff --git a/test/jasmine/tests/plotschema_test.js b/test/jasmine/tests/plotschema_test.js
index e16aa5e8b82..fa822c23fdf 100644
--- a/test/jasmine/tests/plotschema_test.js
+++ b/test/jasmine/tests/plotschema_test.js
@@ -176,7 +176,7 @@ describe('plot schema', function() {
         var valObjects = plotSchema.transforms.filter.attributes,
             attrNames = Object.keys(valObjects);
 
-        ['operation', 'value', 'filtersrc'].forEach(function(k) {
+        ['operation', 'value', 'target'].forEach(function(k) {
             expect(attrNames).toContain(k);
         });
     });
diff --git a/test/jasmine/tests/transform_filter_test.js b/test/jasmine/tests/transform_filter_test.js
index fdba96d7609..6d86cd0f79c 100644
--- a/test/jasmine/tests/transform_filter_test.js
+++ b/test/jasmine/tests/transform_filter_test.js
@@ -7,7 +7,6 @@ var destroyGraphDiv = require('../assets/destroy_graph_div');
 var assertDims = require('../assets/assert_dims');
 var assertStyle = require('../assets/assert_style');
 
-
 describe('filter transforms defaults:', function() {
 
     var traceIn, traceOut;
@@ -28,7 +27,7 @@ describe('filter transforms defaults:', function() {
             enabled: true,
             operation: '=',
             value: 0,
-            filtersrc: 'x'
+            target: 'x'
         }]);
     });
 
@@ -50,29 +49,29 @@ describe('filter transforms defaults:', function() {
         }]);
     });
 
-    it('supplyTraceDefaults should coerce *filtersrc* as a strict / noBlank string', function() {
+    it('supplyTraceDefaults should coerce *target* as a strict / noBlank string', function() {
         traceIn = {
             x: [1, 2, 3],
             transforms: [{
                 type: 'filter',
             }, {
                 type: 'filter',
-                filtersrc: 0
+                target: 0
             }, {
                 type: 'filter',
-                filtersrc: ''
+                target: ''
             }, {
                 type: 'filter',
-                filtersrc: 'marker.color'
+                target: 'marker.color'
             }]
         };
 
         traceOut = Plots.supplyTraceDefaults(traceIn, 0, {});
 
-        expect(traceOut.transforms[0].filtersrc).toEqual('x');
-        expect(traceOut.transforms[1].filtersrc).toEqual('x');
-        expect(traceOut.transforms[2].filtersrc).toEqual('x');
-        expect(traceOut.transforms[3].filtersrc).toEqual('marker.color');
+        expect(traceOut.transforms[0].target).toEqual('x');
+        expect(traceOut.transforms[1].target).toEqual('x');
+        expect(traceOut.transforms[2].target).toEqual('x');
+        expect(traceOut.transforms[3].target).toEqual('marker.color');
     });
 });
 
@@ -106,13 +105,13 @@ describe('filter transforms calc:', function() {
         transforms: [{ type: 'filter' }]
     };
 
-    it('filters should skip if *filtersrc* isn\'t present in trace', function() {
+    it('filters should skip if *target* isn\'t present in trace', function() {
         var out = _transform([Lib.extendDeep({}, base, {
             transforms: [{
                 type: 'filter',
                 operation: '>',
                 value: 0,
-                filtersrc: 'z'
+                target: 'z'
             }]
         })]);
 
@@ -128,7 +127,7 @@ describe('filter transforms calc:', function() {
                 type: 'filter',
                 operation: '>',
                 value: '2016-10-01',
-                filtersrc: 'z'
+                target: 'z'
             }]
         })]);
 
@@ -146,7 +145,7 @@ describe('filter transforms calc:', function() {
                 type: 'filter',
                 operation: '>',
                 value: 0,
-                filtersrc: 'lon'
+                target: 'lon'
             }]
         };
 
@@ -158,7 +157,7 @@ describe('filter transforms calc:', function() {
                 type: 'filter',
                 operation: '<',
                 value: 0,
-                filtersrc: 'lat'
+                target: 'lat'
             }]
         };
 
@@ -177,7 +176,7 @@ describe('filter transforms calc:', function() {
                 type: 'filter',
                 operation: '>',
                 value: 0.2,
-                filtersrc: 'marker.color'
+                target: 'marker.color'
             }]
         })]);
 
@@ -193,7 +192,7 @@ describe('filter transforms calc:', function() {
                 enabled: false,
                 operation: '>',
                 value: 0,
-                filtersrc: 'x'
+                target: 'x'
             }]
         })]);
 
@@ -207,12 +206,12 @@ describe('filter transforms calc:', function() {
                 type: 'filter',
                 operation: '>',
                 value: 0,
-                filtersrc: 'x'
+                target: 'x'
             }, {
                 type: 'filter',
                 operation: '<',
                 value: 3,
-                filtersrc: 'x'
+                target: 'x'
             }]
         })]);
 
@@ -226,18 +225,18 @@ describe('filter transforms calc:', function() {
                 type: 'filter',
                 operation: '>',
                 value: 0,
-                filtersrc: 'x'
+                target: 'x'
             }, {
                 type: 'filter',
                 enabled: false,
                 operation: '>',
                 value: 2,
-                filtersrc: 'y'
+                target: 'y'
             }, {
                 type: 'filter',
                 operation: '<',
                 value: 2,
-                filtersrc: 'y'
+                target: 'y'
             }]
         })]);
 
@@ -260,7 +259,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '[]',
                     value: [-1, 1],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -276,7 +275,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '[)',
                     value: [-1, 1],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -288,7 +287,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '(]',
                     value: [-1, 1],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -300,7 +299,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '()',
                     value: [-1, 1],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -312,7 +311,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: ')(',
                     value: [-1, 1],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -328,7 +327,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: ')[',
                     value: [-1, 1],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -344,7 +343,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '](',
                     value: [-1, 1],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -360,7 +359,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '][',
                     value: [-1, 1],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -376,7 +375,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '{}',
                     value: [-2, 0],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -392,7 +391,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '}{',
                     value: [-2, 0],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -409,7 +408,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '>',
                     value: -1,
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })], {
                 xaxis: { type: 'category' }
@@ -443,7 +442,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '()',
                     value: ['a', 'c'],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -455,7 +454,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: ')(',
                     value: ['a', 'c'],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -467,7 +466,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '{}',
                     value: ['b', 'd'],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -479,7 +478,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '}{',
                     value: ['b', 'd'],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -513,7 +512,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '=',
                     value: ['2015-07-20'],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -525,7 +524,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '<',
                     value: '2016-01-01',
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -537,7 +536,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '>=',
                     value: '2016-08-01',
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -553,7 +552,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '[]',
                     value: ['2016-08-01', '2016-10-01'],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -565,7 +564,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: ')(',
                     value: ['2016-08-01', '2016-10-01'],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -577,7 +576,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '{}',
                     value: '2015-07-20',
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -589,7 +588,7 @@ describe('filter transforms calc:', function() {
                 transforms: [{
                     operation: '}{',
                     value: ['2016-08-01', '2016-09-01', '2016-10-21', '2016-12-02'],
-                    filtersrc: 'x'
+                    target: 'x'
                 }]
             })]);
 
@@ -603,7 +602,7 @@ describe('filter transforms calc:', function() {
             transforms: [{
                 operation: '{}',
                 value: ['p1', 'p2', 'n1'],
-                filtersrc: 'ids'
+                target: 'ids'
             }]
         })]);
 
diff --git a/test/jasmine/tests/transform_multi_test.js b/test/jasmine/tests/transform_multi_test.js
index 43499b73079..0159f6fada5 100644
--- a/test/jasmine/tests/transform_multi_test.js
+++ b/test/jasmine/tests/transform_multi_test.js
@@ -26,7 +26,7 @@ describe('general transforms:', function() {
             enabled: true,
             operation: '=',
             value: 0,
-            filtersrc: 'x'
+            target: 'x'
         }]);
     });
 
@@ -48,7 +48,7 @@ describe('general transforms:', function() {
                 type: 'filter',
                 operation: '>',
                 value: 0,
-                filtersrc: 'x'
+                target: 'x'
             }]
         };
 
@@ -65,7 +65,7 @@ describe('general transforms:', function() {
             enabled: true,
             operation: '=',
             value: 0,
-            filtersrc: 'x'
+            target: 'x'
         }, '- global first');
 
         expect(traceOut.transforms[1]).toEqual({
@@ -73,7 +73,7 @@ describe('general transforms:', function() {
             enabled: true,
             operation: '>',
             value: 0,
-            filtersrc: 'x'
+            target: 'x'
         }, '- trace second');
     });
 
@@ -88,7 +88,7 @@ describe('general transforms:', function() {
                 type: 'filter',
                 operation: '>',
                 value: 0,
-                filtersrc: 'x'
+                target: 'x'
             }]
         }];
 
@@ -104,7 +104,7 @@ describe('general transforms:', function() {
             type: 'filter',
             operation: '>',
             value: 0,
-            filtersrc: 'x'
+            target: 'x'
         }], msg);
 
         msg = 'supplying the transform defaults';
@@ -113,7 +113,7 @@ describe('general transforms:', function() {
             enabled: true,
             operation: '>',
             value: 0,
-            filtersrc: 'x'
+            target: 'x'
         }, msg);
 
         msg = 'keeping refs to user data';
@@ -123,7 +123,7 @@ describe('general transforms:', function() {
             type: 'filter',
             operation: '>',
             value: 0,
-            filtersrc: 'x'
+            target: 'x'
         }], msg);
 
         msg = 'keeping refs to full transforms array';
@@ -132,7 +132,7 @@ describe('general transforms:', function() {
             enabled: true,
             operation: '>',
             value: 0,
-            filtersrc: 'x'
+            target: 'x'
         }], msg);
 
         msg = 'setting index w.r.t user data';
diff --git a/test/jasmine/tests/transition_test.js b/test/jasmine/tests/transition_test.js
index ff54580b747..6f52f7612a1 100644
--- a/test/jasmine/tests/transition_test.js
+++ b/test/jasmine/tests/transition_test.js
@@ -71,7 +71,7 @@ function runTests(transitionDuration) {
                     enabled: true,
                     type: 'filter',
                     operation: '<',
-                    filtersrc: 'x',
+                    target: 'x',
                     value: 10
                 }
             }, [0]).then(function() {
@@ -79,7 +79,7 @@ function runTests(transitionDuration) {
                     enabled: true,
                     type: 'filter',
                     operation: '<',
-                    filtersrc: 'x',
+                    target: 'x',
                     value: 10
                 }]);
 
@@ -94,7 +94,7 @@ function runTests(transitionDuration) {
                     enabled: true,
                     type: 'filter',
                     operation: '>',
-                    filtersrc: 'x',
+                    target: 'x',
                     value: 10
                 }]);
             }).catch(fail).then(done);

From 046dd3270122e5030cbd4e3f8469f22a4790b6be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= <etienne@plot.ly>
Date: Thu, 20 Oct 2016 12:44:56 -0400
Subject: [PATCH 2/6] add backward-compatibility map for *filtersrc*

---
 src/plot_api/helpers.js             | 18 ++++++++++++++++++
 test/jasmine/tests/plot_api_test.js | 29 +++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+)

diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js
index 35d9a0495f1..d7b5ea03af8 100644
--- a/src/plot_api/helpers.js
+++ b/src/plot_api/helpers.js
@@ -340,6 +340,24 @@ exports.cleanData = function(data, existingData) {
             }
         }
 
+        // transforms backward compatibility fixes
+        if(Array.isArray(trace.transforms)) {
+            var transforms = trace.transforms;
+
+            for(i = 0; i < transforms.length; i++) {
+                var transform = transforms[i];
+
+                if(!Lib.isPlainObject(transform)) continue;
+
+                if(transform.type === 'filter') {
+                    if(transform.filtersrc) {
+                        transform.target = transform.filtersrc;
+                        delete transform.filtersrc;
+                    }
+                }
+            }
+        }
+
         // prune empty containers made before the new nestedProperty
         if(emptyContainer(trace, 'line')) delete trace.line;
         if('marker' in trace) {
diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js
index c42ebf273e9..723a8f5f581 100644
--- a/test/jasmine/tests/plot_api_test.js
+++ b/test/jasmine/tests/plot_api_test.js
@@ -995,6 +995,35 @@ describe('Test plot api', function() {
 
             expect(gd.data[1].contours).toBeUndefined();
         });
+
+        it('should rename *filtersrc* to *target* in filter transforms', function() {
+            var data = [{
+                transforms: [{
+                    type: 'filter',
+                    filtersrc: 'y'
+                }, {
+                    type: 'filter',
+                    operation: '<'
+                }]
+            }, {
+                transforms: [{
+                    type: 'filter',
+                    target: 'y'
+                }]
+            }];
+
+            Plotly.plot(gd, data);
+
+            var trace0 = gd.data[0],
+                trace1 = gd.data[1];
+
+            expect(trace0.transforms.length).toEqual(2);
+            expect(trace0.transforms[0].filtersrc).toBeUndefined();
+            expect(trace0.transforms[0].target).toEqual('y');
+
+            expect(trace1.transforms.length).toEqual(1);
+            expect(trace1.transforms[0].target).toEqual('y');
+        });
     });
 
     describe('Plotly.update should', function() {

From c80f144b6d8c2398493ba3a872b9833e4341f79f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= <etienne@plot.ly>
Date: Thu, 20 Oct 2016 15:24:09 -0400
Subject: [PATCH 3/6] transforms: link up tranform module to fullData transform
 container

- so that findArrayAttributes can track arrayOk attribute
  within transforms containers.
---
 src/lib/coerce.js                           | 11 +++++++++++
 src/plots/plots.js                          |  1 +
 test/jasmine/tests/transform_filter_test.js |  6 +++++-
 test/jasmine/tests/transform_multi_test.js  | 19 +++++++++++++------
 4 files changed, 30 insertions(+), 7 deletions(-)

diff --git a/src/lib/coerce.js b/src/lib/coerce.js
index 11f9153cac1..989daa6a331 100644
--- a/src/lib/coerce.js
+++ b/src/lib/coerce.js
@@ -458,6 +458,17 @@ exports.findArrayAttributes = function(trace) {
 
     exports.crawl(trace._module.attributes, callback);
 
+    if(trace.transforms) {
+        var transforms = trace.transforms;
+
+        for(var i = 0; i < transforms.length; i++) {
+            var transform = transforms[i];
+
+            stack = ['transforms[' + i + ']'];
+            exports.crawl(transform._module.attributes, callback, 1);
+        }
+    }
+
     // Look into the fullInput module attributes for array attributes
     // to make sure that 'custom' array attributes are detected.
     //
diff --git a/src/plots/plots.js b/src/plots/plots.js
index 277a3b8211a..f2108497b58 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -807,6 +807,7 @@ function supplyTransformDefaults(traceIn, traceOut, layout) {
         if(_module && _module.supplyDefaults) {
             transformOut = _module.supplyDefaults(transformIn, traceOut, layout, traceIn);
             transformOut.type = type;
+            transformOut._module = _module;
         }
         else {
             transformOut = Lib.extendFlat({}, transformIn);
diff --git a/test/jasmine/tests/transform_filter_test.js b/test/jasmine/tests/transform_filter_test.js
index 6d86cd0f79c..69a46b9b412 100644
--- a/test/jasmine/tests/transform_filter_test.js
+++ b/test/jasmine/tests/transform_filter_test.js
@@ -1,4 +1,6 @@
 var Plotly = require('@lib/index');
+var Filter = require('@lib/filter');
+
 var Plots = require('@src/plots/plots');
 var Lib = require('@src/lib');
 
@@ -27,7 +29,8 @@ describe('filter transforms defaults:', function() {
             enabled: true,
             operation: '=',
             value: 0,
-            target: 'x'
+            target: 'x',
+            _module: Filter
         }]);
     });
 
@@ -46,6 +49,7 @@ describe('filter transforms defaults:', function() {
         expect(traceOut.transforms).toEqual([{
             type: 'filter',
             enabled: false,
+            _module: Filter
         }]);
     });
 
diff --git a/test/jasmine/tests/transform_multi_test.js b/test/jasmine/tests/transform_multi_test.js
index 0159f6fada5..73c264f50e9 100644
--- a/test/jasmine/tests/transform_multi_test.js
+++ b/test/jasmine/tests/transform_multi_test.js
@@ -1,4 +1,6 @@
 var Plotly = require('@lib/index');
+var Filter = require('@lib/filter');
+
 var Plots = require('@src/plots/plots');
 var Lib = require('@src/lib');
 
@@ -26,7 +28,8 @@ describe('general transforms:', function() {
             enabled: true,
             operation: '=',
             value: 0,
-            target: 'x'
+            target: 'x',
+            _module: Filter
         }]);
     });
 
@@ -65,7 +68,8 @@ describe('general transforms:', function() {
             enabled: true,
             operation: '=',
             value: 0,
-            target: 'x'
+            target: 'x',
+            _module: Filter
         }, '- global first');
 
         expect(traceOut.transforms[1]).toEqual({
@@ -73,7 +77,8 @@ describe('general transforms:', function() {
             enabled: true,
             operation: '>',
             value: 0,
-            target: 'x'
+            target: 'x',
+            _module: Filter
         }, '- trace second');
     });
 
@@ -113,7 +118,8 @@ describe('general transforms:', function() {
             enabled: true,
             operation: '>',
             value: 0,
-            target: 'x'
+            target: 'x',
+            _module: Filter
         }, msg);
 
         msg = 'keeping refs to user data';
@@ -123,7 +129,7 @@ describe('general transforms:', function() {
             type: 'filter',
             operation: '>',
             value: 0,
-            target: 'x'
+            target: 'x',
         }], msg);
 
         msg = 'keeping refs to full transforms array';
@@ -132,7 +138,8 @@ describe('general transforms:', function() {
             enabled: true,
             operation: '>',
             value: 0,
-            target: 'x'
+            target: 'x',
+            _module: Filter
         }], msg);
 
         msg = 'setting index w.r.t user data';

From e39d44700439620988eb192f785a971108033ee9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= <etienne@plot.ly>
Date: Thu, 20 Oct 2016 15:27:39 -0400
Subject: [PATCH 4/6] filter: auto-type filter target arrays

- use auto-typed axis data-to-calc method to filter by target array
---
 src/transforms/filter.js                    | 23 ++++++-
 test/jasmine/tests/transform_filter_test.js | 68 +++++++++++++++++++++
 2 files changed, 89 insertions(+), 2 deletions(-)

diff --git a/src/transforms/filter.js b/src/transforms/filter.js
index 213ad64816e..775c16c89d1 100644
--- a/src/transforms/filter.js
+++ b/src/transforms/filter.js
@@ -9,6 +9,7 @@
 'use strict';
 
 var Lib = require('../lib');
+var Plots = require('../plots/plots');
 var axisIds = require('../plots/cartesian/axis_ids');
 
 var INEQUALITY_OPS = ['=', '<', '>=', '>', '<='];
@@ -31,6 +32,7 @@ exports.attributes = {
         valType: 'string',
         strict: true,
         noBlank: true,
+        arrayOk: true,
         dflt: 'x',
         description: [
             'Sets the filter target by which the filter is applied.',
@@ -165,12 +167,29 @@ function getFilterArray(trace, target) {
 
         return Array.isArray(array) ? array : [];
     }
+    else if(Array.isArray(target)) return target.slice();
 
     return false;
 }
 
 function getDataToCoordFunc(gd, trace, target) {
-    var ax = axisIds.getFromTrace(gd, trace, target);
+    var ax;
+
+    // In the case of an array target, make a mock data array
+    // and call supplyDefaults to the data type and
+    // setup the data-to-calc method.
+    if(Array.isArray(target)) {
+        var mockGd = {
+            data: [{ x: target }],
+            layout: {}
+        };
+
+        Plots.supplyDefaults(mockGd);
+        ax = mockGd._fullLayout.xaxis;
+    }
+    else {
+        ax = axisIds.getFromTrace(gd, trace, target);
+    }
 
     // if 'target' has corresponding axis
     // -> use setConvert method
@@ -180,7 +199,7 @@ function getDataToCoordFunc(gd, trace, target) {
     // -> cast to String
     if(target === 'ids') return function(v) { return String(v); };
 
-    // otherwise
+    // otherwise (e.g. numeric-array of 'marker.color' or 'marker.size')
     // -> cast to Number
     return function(v) { return +v; };
 }
diff --git a/test/jasmine/tests/transform_filter_test.js b/test/jasmine/tests/transform_filter_test.js
index 69a46b9b412..722b810c7a2 100644
--- a/test/jasmine/tests/transform_filter_test.js
+++ b/test/jasmine/tests/transform_filter_test.js
@@ -614,6 +614,74 @@ describe('filter transforms calc:', function() {
         expect(out[0].y).toEqual([2, 2, 3]);
         expect(out[0].ids).toEqual(['n1', 'p1', 'p2']);
     });
+
+    describe('filters should handle array *target* values', function() {
+        var _base = Lib.extendDeep({}, base);
+
+        function _assert(out, x, y, markerColor) {
+            expect(out[0].x).toEqual(x, '- x coords');
+            expect(out[0].y).toEqual(y, '- y coords');
+            expect(out[0].marker.color).toEqual(markerColor, '- marker.color arrayOk');
+            expect(out[0].marker.size).toEqual(20, '- marker.size style');
+        }
+
+        it('with numeric items', function() {
+            var out = _transform([Lib.extendDeep({}, _base, {
+                transforms: [{
+                    target: [1, 1, 0, 0, 1, 0, 1],
+                    operation: '{}',
+                    value: 0
+                }]
+            })]);
+
+            _assert(out, [-2, 0, 2], [3, 1, 3], [0.3, 0.1, 0.3]);
+            expect(out[0].transforms[0].target).toEqual([0, 0, 0]);
+        });
+
+        it('with categorical items', function() {
+            var out = _transform([Lib.extendDeep({}, _base, {
+                transforms: [{
+                    target: ['a', 'a', 'b', 'b', 'a', 'b', 'a'],
+                    operation: '{}',
+                    value: 'b'
+                }]
+            })]);
+
+            _assert(out, [-2, 0, 2], [3, 1, 3], [0.3, 0.1, 0.3]);
+            expect(out[0].transforms[0].target).toEqual(['b', 'b', 'b']);
+        });
+
+        it('with dates items', function() {
+            var out = _transform([Lib.extendDeep({}, _base, {
+                transforms: [{
+                    target: ['2015-07-20', '2016-08-01', '2016-09-01', '2016-10-21', '2016-12-02'],
+                    operation: '<',
+                    value: '2016-01-01'
+                }]
+            })]);
+
+            _assert(out, [-2], [1], [0.1]);
+            expect(out[0].transforms[0].target).toEqual(['2015-07-20']);
+        });
+
+        it('with multiple transforms (dates) ', function() {
+            var out = _transform([Lib.extendDeep({}, _base, {
+                transforms: [{
+                    target: ['2015-07-20', '2016-08-01', '2016-09-01', '2016-10-21', '2016-12-02'],
+                    operation: '>',
+                    value: '2016-01-01'
+                }, {
+                    type: 'filter',
+                    target: ['2015-07-20', '2016-08-01', '2016-09-01', '2016-10-21', '2016-12-02'],
+                    operation: '<',
+                    value: '2016-09-01'
+                }]
+            })]);
+
+            _assert(out, [-1], [2], [0.2]);
+            expect(out[0].transforms[0].target).toEqual(['2016-08-01']);
+        });
+    });
 });
 
 describe('filter transforms interactions', function() {

From 48ed67552df2fb7c12e4aa96da91cc18b1da3d7c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= <etienne@plot.ly>
Date: Thu, 20 Oct 2016 15:42:53 -0400
Subject: [PATCH 5/6] test: fixup transition transform cases to use
 objectContaining

---
 test/jasmine/tests/transition_test.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/test/jasmine/tests/transition_test.js b/test/jasmine/tests/transition_test.js
index 6f52f7612a1..86348f394e2 100644
--- a/test/jasmine/tests/transition_test.js
+++ b/test/jasmine/tests/transition_test.js
@@ -75,13 +75,13 @@ function runTests(transitionDuration) {
                     value: 10
                 }
             }, [0]).then(function() {
-                expect(gd._fullData[0].transforms).toEqual([{
+                expect(gd._fullData[0].transforms).toEqual([jasmine.objectContaining({
                     enabled: true,
                     type: 'filter',
                     operation: '<',
                     target: 'x',
                     value: 10
-                }]);
+                })]);
 
                 return Plots.transition(gd, [{
                     'transforms[0].operation': '>'
@@ -90,13 +90,13 @@ function runTests(transitionDuration) {
                     {duration: transitionDuration, easing: 'cubic-in-out'}
                 );
             }).then(function() {
-                expect(gd._fullData[0].transforms).toEqual([{
+                expect(gd._fullData[0].transforms).toEqual([jasmine.objectContaining({
                     enabled: true,
                     type: 'filter',
                     operation: '>',
                     target: 'x',
                     value: 10
-                }]);
+                })]);
             }).catch(fail).then(done);
         });
 

From 14fd327fd7e9918e56807031b4dbfc783c46a015 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= <etienne@plot.ly>
Date: Mon, 24 Oct 2016 14:04:55 -0400
Subject: [PATCH 6/6] filter: swap Plots.supplyDefaults call for more granular
 approach

- for the Array.isArray(target) case
---
 src/transforms/filter.js | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/transforms/filter.js b/src/transforms/filter.js
index 775c16c89d1..8c9a23b7071 100644
--- a/src/transforms/filter.js
+++ b/src/transforms/filter.js
@@ -9,8 +9,9 @@
 'use strict';
 
 var Lib = require('../lib');
-var Plots = require('../plots/plots');
 var axisIds = require('../plots/cartesian/axis_ids');
+var autoType = require('../plots/cartesian/axis_autotype');
+var setConvert = require('../plots/cartesian/set_convert');
 
 var INEQUALITY_OPS = ['=', '<', '>=', '>', '<='];
 var INTERVAL_OPS = ['[]', '()', '[)', '(]', '][', ')(', '](', ')['];
@@ -179,13 +180,11 @@ function getDataToCoordFunc(gd, trace, target) {
     // and call supplyDefaults to the data type and
     // setup the data-to-calc method.
     if(Array.isArray(target)) {
-        var mockGd = {
-            data: [{ x: target }],
-            layout: {}
+        ax = {
+            type: autoType(target),
+            _categories: []
         };
-
-        Plots.supplyDefaults(mockGd);
-        ax = mockGd._fullLayout.xaxis;
+        setConvert(ax);
     }
     else {
         ax = axisIds.getFromTrace(gd, trace, target);