From 9bb2b10344e24dca88caf2f56c40ef82abb95e01 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Wed, 3 Aug 2016 15:57:06 -0500 Subject: [PATCH 01/34] move `transforms` to a "proper" place --- {test/jasmine/assets => src}/transforms/filter.js | 0 {test/jasmine/assets => src}/transforms/groupby.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {test/jasmine/assets => src}/transforms/filter.js (100%) rename {test/jasmine/assets => src}/transforms/groupby.js (100%) diff --git a/test/jasmine/assets/transforms/filter.js b/src/transforms/filter.js similarity index 100% rename from test/jasmine/assets/transforms/filter.js rename to src/transforms/filter.js diff --git a/test/jasmine/assets/transforms/groupby.js b/src/transforms/groupby.js similarity index 100% rename from test/jasmine/assets/transforms/groupby.js rename to src/transforms/groupby.js From 582d210fbc793be70ae07bcad27f8882214f2326 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Wed, 3 Aug 2016 17:07:41 -0500 Subject: [PATCH 02/34] add `in` \ `notin` and `within` \ `notwithin` filter transforms; register `filter` with plotly --- lib/filter.js | 9 + lib/index.js | 5 + src/transforms/filter.js | 33 +- src/transforms/groupby.js | 2 +- test/jasmine/tests/transforms_test.js | 489 +++++++++++++++++++++----- 5 files changed, 447 insertions(+), 91 deletions(-) create mode 100644 lib/filter.js diff --git a/lib/filter.js b/lib/filter.js new file mode 100644 index 00000000000..e7e4ecf078e --- /dev/null +++ b/lib/filter.js @@ -0,0 +1,9 @@ +/** +* Copyright 2012-2016, 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. +*/ + +module.exports = require('../src/transforms/filter'); diff --git a/lib/index.js b/lib/index.js index 1f91cda0c33..7f767ebe8e1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -30,4 +30,9 @@ Plotly.register([ require('./scattermapbox') ]); +// add transforms +Plotly.register([ + require('./filter') +]); + module.exports = Plotly; diff --git a/src/transforms/filter.js b/src/transforms/filter.js index 8ac7e9b1f27..518f45836a2 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -1,11 +1,10 @@ 'use strict'; // var Lib = require('@src/lib'); -var Lib = require('../../../../src/lib'); +var Lib = require('../lib'); /* eslint no-unused-vars: 0*/ - // so that Plotly.register knows what to do with it exports.moduleType = 'transform'; @@ -16,11 +15,11 @@ exports.name = 'filter'; exports.attributes = { operation: { valType: 'enumerated', - values: ['=', '<', '>'], + values: ['=', '<', '>', 'within', 'notwithin', 'in', 'notin'], dflt: '=' }, value: { - valType: 'number', + valType: 'any', dflt: 0 }, filtersrc: { @@ -121,6 +120,16 @@ function transformOne(trace, state) { function getFilterFunc(opts) { var value = opts.value; + // if value is not array then coerce to + // an array of [value,value] so the + // filter function will work + // but perhaps should just error out + var value_arr = []; + if (!Array.isArray(value)) { + value_arr = [value, value]; + } else { + value_arr = value; + } switch(opts.operation) { case '=': @@ -129,6 +138,22 @@ function getFilterFunc(opts) { return function(v) { return v < value; }; case '>': return function(v) { return v > value; }; + case 'within': + return function(v) { + // keep the = ? + return v >= Math.min.apply( null, value ) && + v <= Math.max.apply( null, value ); + }; + case 'notwithin': + return function(v) { + // keep the = ? + return !(v >= Math.min.apply( null, value ) && + v <= Math.max.apply( null, value )); + }; + case 'in': + return function(v) { return value.indexOf(v) >= 0 }; + case 'notin': + return function(v) { return value.indexOf(v) === -1 }; } } diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index 50a3b9f0949..810fa5aecbd 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -1,7 +1,7 @@ 'use strict'; // var Lib = require('@src/lib'); -var Lib = require('../../../../src/lib'); +var Lib = require('../lib'); /* eslint no-unused-vars: 0*/ diff --git a/test/jasmine/tests/transforms_test.js b/test/jasmine/tests/transforms_test.js index 1344518c8d7..68c07ca4f5b 100644 --- a/test/jasmine/tests/transforms_test.js +++ b/test/jasmine/tests/transforms_test.js @@ -7,8 +7,9 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); Plotly.register([ - require('../assets/transforms/filter'), - require('../assets/transforms/groupby') + //register filter in build + //require('@src/transforms/filter'), + require('@src/transforms/groupby') ]); @@ -329,6 +330,406 @@ describe('one-to-one transforms:', function() { }); }); + + it('supplyTraceDefaults should supply the transform defaults', function() { + var traceIn = { + y: [2, 1, 2], + transforms: [{ type: 'filter' }] + }; + + var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + + expect(traceOut.transforms).toEqual([{ + type: 'filter', + operation: '=', + value: 0, + filtersrc: 'x' + }]); + }); + + it('supplyTraceDefaults should accept numeric as character', function() { + var traceIn = { + x: '1', + transforms: [{ + type: 'filter', + value: '0' + }] + }; + + var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + + expect(traceOut.transforms).toEqual([{ + type: 'filter', + operation: '=', + value: 0, + filtersrc: 'x' + }]); + + // should also convert if array + var traceIn = { + x: '1', + transforms: [{ + type: 'filter', + value: ['0'] + }] + }; + + var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + + expect(traceOut.transforms).toEqual([{ + type: 'filter', + operation: '=', + value: [0], + filtersrc: 'x' + }]); + }); + + it('supplyDataDefaults should apply the transform', function() { + var dataIn = [{ + x: [-2, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + transforms: [{ + type: 'filter', + operation: '>', + value: 0, + filtersrc: 'x' + }] + }]; + + var dataOut = []; + Plots.supplyDataDefaults(dataIn, dataOut, {}, []); + + // does not mutate user data + expect(dataIn[0].x).toEqual([-2, -1, -2, 0, 1, 2, 3]); + expect(dataIn[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + expect(dataIn[0].transforms).toEqual([{ + type: 'filter', + operation: '>', + value: 0, + filtersrc: 'x' + }]); + + // applies transform + expect(dataOut[0].x).toEqual([1, 2, 3]); + expect(dataOut[0].y).toEqual([2, 3, 1]); + + // TODO what is the expected behavior ??? +// expect(dataOut[0].transforms).toEqual([]); + + // keep ref to user data + expect(dataOut[0]._input.x).toEqual([-2, -1, -2, 0, 1, 2, 3]); + expect(dataOut[0]._input.y).toEqual([1, 2, 3, 1, 2, 3, 1]); + expect(dataOut[0]._input.transforms).toEqual([{ + type: 'filter', + operation: '>', + value: 0, + filtersrc: 'x' + }]); + + // keep ref to full transforms array + expect(dataOut[0]._fullInput.transforms).toEqual([{ + type: 'filter', + operation: '>', + value: 0, + filtersrc: 'x' + }]); + + // set index w.r.t. fullData + expect(dataOut[0].index).toEqual(0); + + // TODO do we really need this ??? + // set _index w.r.t. user data + expect(dataOut[0].index).toEqual(0); + }); + + it('filters should chain as AND', function(){ + var dataIn = [{ + x: [-2, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + transforms: [ + { + type: 'filter', + operation: '>', + value: 0, + filtersrc: 'x' + }, + { + type: 'filter', + operation: '<', + value: 3, + filtersrc: 'x' + } + ] + }]; + + var dataOut = []; + Plots.supplyDataDefaults(dataIn, dataOut, {}, []); + + // applies transform + expect(dataOut[0].x).toEqual([1, 2]); + expect(dataOut[0].y).toEqual([2, 3]); + }); + + it('filters should handle range numeric values within and notwithin', function() { + var dataIn = [{ + x: [-2, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + transforms: [{ + type: 'filter', + operation: 'within', + value: [-1, 1], + filtersrc: 'x' + }] + }]; + + var dataOut = []; + Plots.supplyDataDefaults(dataIn, dataOut, {}, []); + + // leave this section guarding against mutation + // for now but can probably eliminate later + // does not mutate user data + expect(dataIn[0].x).toEqual([-2, -1, -2, 0, 1, 2, 3]); + expect(dataIn[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + expect(dataIn[0].transforms).toEqual([{ + type: 'filter', + operation: 'within', + value: [-1, 1], + filtersrc: 'x' + }]); + + // applies transform + expect(dataOut[0].x).toEqual([-1, 0, 1]); + expect(dataOut[0].y).toEqual([2, 1, 2]); + }); + + it('filters should ignore character values within and notwithin', function() { + var dataIn = [{ + x: ['a', 'b', 'c', 'd'], + y: [1, 2, 3, 4], + transforms: [{ + type: 'filter', + operation: 'within', + value: ['a', 'c'], + filtersrc: 'x' + }] + }]; + + var dataOut = []; + Plots.supplyDataDefaults(dataIn, dataOut, {}, []); + + // leave this section guarding against mutation + // for now but can probably eliminate later + // does not mutate user data + expect(dataIn[0].x).toEqual(['a', 'b', 'c', 'd']); + expect(dataIn[0].y).toEqual([1, 2, 3, 4]); + expect(dataIn[0].transforms).toEqual([{ + type: 'filter', + operation: 'within', + value: ['a', 'c'], + filtersrc: 'x' + }]); + + // applies transform + expect(dataOut[0].x).toEqual(['a', 'b', 'c', 'd']); + expect(dataOut[0].y).toEqual([1, 2, 3, 4]); + }); + + it('filters should handle numeric values in', function() { + var dataIn = [{ + x: [-2, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + transforms: [{ + type: 'filter', + operation: 'in', + value: [-2, 0], + filtersrc: 'x' + }] + }]; + + var dataOut = []; + Plots.supplyDataDefaults(dataIn, dataOut, {}, []); + + // applies transform + expect(dataOut[0].x).toEqual([-2, -2, 0]); + expect(dataOut[0].y).toEqual([1, 3, 1]); + }); + + it('filters should handle numeric values in', function() { + var dataIn = [{ + x: [-2, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + transforms: [{ + type: 'filter', + operation: 'notin', + value: [-2, 0], + filtersrc: 'x' + }] + }]; + + var dataOut = []; + Plots.supplyDataDefaults(dataIn, dataOut, {}, []); + + // applies transform + expect(dataOut[0].x).toEqual([-1, 1, 2, 3]); + expect(dataOut[0].y).toEqual([2, 2, 3, 1]); + }); + + + it('filters should handle strings with in', function() { + var dataIn = [{ + x: ['y', 't', 'b', 'm', 'p', 'l', 'o'], + y: [1, 2, 3, 1, 5, 10, 20], + transforms: [{ + type: 'filter', + operation: 'in', + value: ['p', 'l', 'o'], + filtersrc: 'x' + }] + }]; + + + var dataOut = []; + Plots.supplyDataDefaults(dataIn, dataOut, {}, []); + + // applies transform + expect(dataOut[0].x).toEqual(['p', 'l', 'o']); + expect(dataOut[0].y).toEqual([5, 10, 20]); + }); + + it('filters should handle strings with in', function() { + var dataIn = [{ + x: ['y', 't', 'b', 'm', 'p', 'l', 'o'], + y: [1, 2, 3, 1, 5, 10, 20], + transforms: [{ + type: 'filter', + operation: 'notin', + value: ['p', 'l', 'o'], + filtersrc: 'x' + }] + }]; + + + var dataOut = []; + Plots.supplyDataDefaults(dataIn, dataOut, {}, []); + + // applies transform + expect(dataOut[0].x).toEqual(['y', 't', 'b', 'm']); + expect(dataOut[0].y).toEqual([1, 2, 3, 1]); + }); + + + it('Plotly.plot should plot the transform trace', function(done) { + var data = Lib.extendDeep([], mockData0); + + Plotly.plot(createGraphDiv(), data).then(function(gd) { + assertDims([3]); + + var uid = data[0].uid; + expect(gd._fullData[0].uid).toEqual(uid + '0'); + + done(); + }); + }); + + it('Plotly.restyle should work', function(done) { + var data = Lib.extendDeep([], mockData0); + data[0].marker = { color: 'red' }; + + var gd = createGraphDiv(); + var dims = [3]; + + Plotly.plot(gd, data).then(function() { + expect(gd._fullData[0].marker.color).toEqual('red'); + assertStyle(dims, ['rgb(255, 0, 0)'], [1]); + + return Plotly.restyle(gd, 'marker.color', 'blue'); + }).then(function() { + expect(gd._fullData[0].marker.color).toEqual('blue'); + assertStyle(dims, ['rgb(0, 0, 255)'], [1]); + + return Plotly.restyle(gd, 'marker.color', 'red'); + }).then(function() { + expect(gd._fullData[0].marker.color).toEqual('red'); + assertStyle(dims, ['rgb(255, 0, 0)'], [1]); + + return Plotly.restyle(gd, 'transforms[0].value', 2.5); + }).then(function() { + assertStyle([1], ['rgb(255, 0, 0)'], [1]); + + done(); + }); + }); + + it('Plotly.extendTraces should work', function(done) { + var data = Lib.extendDeep([], mockData0); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data[0].x.length).toEqual(7); + expect(gd._fullData[0].x.length).toEqual(3); + + assertDims([3]); + + return Plotly.extendTraces(gd, { + x: [ [-3, 4, 5] ], + y: [ [1, -2, 3] ] + }, [0]); + }).then(function() { + expect(gd.data[0].x.length).toEqual(10); + expect(gd._fullData[0].x.length).toEqual(5); + + assertDims([5]); + + done(); + }); + }); + + it('Plotly.deleteTraces should work', function(done) { + var data = Lib.extendDeep([], mockData1); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + assertDims([3, 4]); + + return Plotly.deleteTraces(gd, [1]); + }).then(function() { + assertDims([3]); + + return Plotly.deleteTraces(gd, [0]); + }).then(function() { + assertDims([]); + + done(); + }); + + }); + + it('toggling trace visibility should work', function(done) { + var data = Lib.extendDeep([], mockData1); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + assertDims([3, 4]); + + return Plotly.restyle(gd, 'visible', 'legendonly', [1]); + }).then(function() { + assertDims([3]); + + return Plotly.restyle(gd, 'visible', false, [0]); + }).then(function() { + assertDims([]); + + return Plotly.restyle(gd, 'visible', [true, true], [0, 1]); + }).then(function() { + assertDims([3, 4]); + + done(); + }); + }); + }); describe('one-to-many transforms:', function() { @@ -358,34 +759,6 @@ describe('one-to-many transforms:', function() { afterEach(destroyGraphDiv); - it('supplyDataDefaults should apply the transform while', function() { - var dummyTrace0 = { - x: [-2, -2, 1, 2, 3], - y: [1, 2, 2, 3, 1], - }; - - var dummyTrace1 = { - x: [-1, 2, 3], - y: [2, 3, 1], - }; - - var dataIn = [ - dummyTrace0, - Lib.extendDeep({}, mockData0[0]), - dummyTrace1, - Lib.extendDeep({}, mockData1[0]) - ]; - - var dataOut = []; - Plots.supplyDataDefaults(dataIn, dataOut, {}, []); - - expect(dataOut.map(function(trace) { return trace.index; })) - .toEqual([0, 1, 1, 2, 3, 3], 'setting index w.r.t user data'); - - expect(dataOut.map(function(trace) { return trace._expandedIndex; })) - .toEqual([0, 1, 2, 3, 4, 5], 'setting index w.r.t full data'); - }); - it('Plotly.plot should plot the transform traces', function(done) { var data = Lib.extendDeep([], mockData0); @@ -563,34 +936,6 @@ describe('multiple transforms:', function() { afterEach(destroyGraphDiv); - it('supplyDataDefaults should apply the transform while', function() { - var dummyTrace0 = { - x: [-2, -2, 1, 2, 3], - y: [1, 2, 2, 3, 1], - }; - - var dummyTrace1 = { - x: [-1, 2, 3], - y: [2, 3, 1], - }; - - var dataIn = [ - dummyTrace0, - Lib.extendDeep({}, mockData0[0]), - Lib.extendDeep({}, mockData1[0]), - dummyTrace1 - ]; - - var dataOut = []; - Plots.supplyDataDefaults(dataIn, dataOut, {}, []); - - expect(dataOut.map(function(trace) { return trace.index; })) - .toEqual([0, 1, 1, 2, 2, 3], 'setting index w.r.t user data'); - - expect(dataOut.map(function(trace) { return trace._expandedIndex; })) - .toEqual([0, 1, 2, 3, 4, 5], 'setting index w.r.t full data'); - }); - it('Plotly.plot should plot the transform traces', function(done) { var data = Lib.extendDeep([], mockData0); @@ -788,34 +1133,6 @@ describe('multiple traces with transforms:', function() { afterEach(destroyGraphDiv); - it('supplyDataDefaults should apply the transform while', function() { - var dummyTrace0 = { - x: [-2, -2, 1, 2, 3], - y: [1, 2, 2, 3, 1], - }; - - var dummyTrace1 = { - x: [-1, 2, 3], - y: [2, 3, 1], - }; - - var dataIn = [ - dummyTrace0, - Lib.extendDeep({}, mockData0[0]), - Lib.extendDeep({}, mockData0[1]), - dummyTrace1 - ]; - - var dataOut = []; - Plots.supplyDataDefaults(dataIn, dataOut, {}, []); - - expect(dataOut.map(function(trace) { return trace.index; })) - .toEqual([0, 1, 2, 2, 3], 'setting index w.r.t user data'); - - expect(dataOut.map(function(trace) { return trace._expandedIndex; })) - .toEqual([0, 1, 2, 3, 4], 'setting index w.r.t full data'); - }); - it('Plotly.plot should plot the transform traces', function(done) { var data = Lib.extendDeep([], mockData0); From d0ef359fb30ab096fb62982f44f3c2495129d670 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Thu, 4 Aug 2016 05:45:05 -0500 Subject: [PATCH 03/34] use value array for array comparisons and camelCase --- src/transforms/filter.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/transforms/filter.js b/src/transforms/filter.js index 518f45836a2..9464577d515 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -124,11 +124,11 @@ function getFilterFunc(opts) { // an array of [value,value] so the // filter function will work // but perhaps should just error out - var value_arr = []; + var valueArr = []; if (!Array.isArray(value)) { - value_arr = [value, value]; + valueArr = [value, value]; } else { - value_arr = value; + valueArr = value; } switch(opts.operation) { @@ -141,19 +141,19 @@ function getFilterFunc(opts) { case 'within': return function(v) { // keep the = ? - return v >= Math.min.apply( null, value ) && - v <= Math.max.apply( null, value ); + return v >= Math.min.apply( null, valueArr ) && + v <= Math.max.apply( null, valueArr ); }; case 'notwithin': return function(v) { // keep the = ? - return !(v >= Math.min.apply( null, value ) && - v <= Math.max.apply( null, value )); + return !(v >= Math.min.apply( null, valueArr ) && + v <= Math.max.apply( null, valueArr )); }; case 'in': - return function(v) { return value.indexOf(v) >= 0 }; + return function(v) { return valueArr.indexOf(v) >= 0 }; case 'notin': - return function(v) { return value.indexOf(v) === -1 }; + return function(v) { return valueArr.indexOf(v) === -1 }; } } From 9574f4f5f30dac6bfb8e1c39a5d44a804a2b8650 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Fri, 12 Aug 2016 08:07:01 -0500 Subject: [PATCH 04/34] accept '0' and ['0'] in filter --- src/transforms/filter.js | 12 +++++++++ test/jasmine/tests/transforms_test.js | 37 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/transforms/filter.js b/src/transforms/filter.js index 9464577d515..7a494d7df28 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -1,5 +1,7 @@ 'use strict'; +var isNumeric = require('fast-isnumeric'); + // var Lib = require('@src/lib'); var Lib = require('../lib'); @@ -53,6 +55,16 @@ exports.supplyDefaults = function(transformIn, fullData, layout) { coerce('value'); coerce('filtersrc'); + // numeric values as character should be converted to numeric + if(Array.isArray(transformOut.value)) { + transformOut.value = transformOut.value.map(function(v) { + if(isNumeric(v)) v = +v; + return v; + }) + } else { + if(isNumeric(transformOut.value)) transformOut.value = +transformOut.value; + } + // or some more complex logic using fullData and layout return transformOut; diff --git a/test/jasmine/tests/transforms_test.js b/test/jasmine/tests/transforms_test.js index 68c07ca4f5b..0ac441bef29 100644 --- a/test/jasmine/tests/transforms_test.js +++ b/test/jasmine/tests/transforms_test.js @@ -384,6 +384,43 @@ describe('one-to-one transforms:', function() { }]); }); + it('supplyTraceDefaults should accept numeric as character', function() { + var traceIn = { + x: '1', + transforms: [{ + type: 'filter', + value: '0' + }] + }; + + var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + + expect(traceOut.transforms).toEqual([{ + type: 'filter', + operation: '=', + value: 0, + filtersrc: 'x' + }]); + + // should also convert if array + var traceIn = { + x: '1', + transforms: [{ + type: 'filter', + value: ['0'] + }] + }; + + var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + + expect(traceOut.transforms).toEqual([{ + type: 'filter', + operation: '=', + value: [0], + filtersrc: 'x' + }]); + }); + it('supplyDataDefaults should apply the transform', function() { var dataIn = [{ x: [-2, -1, -2, 0, 1, 2, 3], From 77e2b27dfcc013fccc4118e270b2a2088c45625d Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Fri, 12 Aug 2016 08:52:13 -0500 Subject: [PATCH 05/34] ignore character values for `within` filter --- src/transforms/filter.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/transforms/filter.js b/src/transforms/filter.js index 7a494d7df28..9e61bac9780 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -152,6 +152,14 @@ function getFilterFunc(opts) { return function(v) { return v > value; }; case 'within': return function(v) { + // if character then ignore with no side effect + function notDateNumber(d){ + return !(isNumeric(d) || d instanceof Date); + } + if(valueArr.some(notDateNumber)) { + return true; + } + // keep the = ? return v >= Math.min.apply( null, valueArr ) && v <= Math.max.apply( null, valueArr ); From aa1f1d3488d3a3d363690657a7d18df18c735a12 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Fri, 12 Aug 2016 10:03:11 -0500 Subject: [PATCH 06/34] use `Lib.isDateTime` --- src/transforms/filter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transforms/filter.js b/src/transforms/filter.js index 9e61bac9780..df5468b36c1 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -154,8 +154,8 @@ function getFilterFunc(opts) { return function(v) { // if character then ignore with no side effect function notDateNumber(d){ - return !(isNumeric(d) || d instanceof Date); - } + return !(isNumeric(d) || Lib.isDateTime(d)); + }; if(valueArr.some(notDateNumber)) { return true; } From 60157686886a2e348bb16f244b198e0f9c098398 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Wed, 17 Aug 2016 09:16:54 -0500 Subject: [PATCH 07/34] clean and lint --- src/transforms/filter.js | 29 ++++++++----- test/jasmine/tests/transforms_test.js | 62 ++++++++++++++------------- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/transforms/filter.js b/src/transforms/filter.js index df5468b36c1..845f99eb036 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -1,3 +1,12 @@ +/** + * Copyright 2012-2016, 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'; var isNumeric = require('fast-isnumeric'); @@ -60,7 +69,7 @@ exports.supplyDefaults = function(transformIn, fullData, layout) { transformOut.value = transformOut.value.map(function(v) { if(isNumeric(v)) v = +v; return v; - }) + }); } else { if(isNumeric(transformOut.value)) transformOut.value = +transformOut.value; } @@ -137,7 +146,7 @@ function getFilterFunc(opts) { // filter function will work // but perhaps should just error out var valueArr = []; - if (!Array.isArray(value)) { + if(!Array.isArray(value)) { valueArr = [value, value]; } else { valueArr = value; @@ -153,27 +162,27 @@ function getFilterFunc(opts) { case 'within': return function(v) { // if character then ignore with no side effect - function notDateNumber(d){ + function notDateNumber(d) { return !(isNumeric(d) || Lib.isDateTime(d)); - }; + } if(valueArr.some(notDateNumber)) { return true; } // keep the = ? - return v >= Math.min.apply( null, valueArr ) && - v <= Math.max.apply( null, valueArr ); + return v >= Math.min.apply(null, valueArr) && + v <= Math.max.apply(null, valueArr); }; case 'notwithin': return function(v) { // keep the = ? - return !(v >= Math.min.apply( null, valueArr ) && - v <= Math.max.apply( null, valueArr )); + return !(v >= Math.min.apply(null, valueArr) && + v <= Math.max.apply(null, valueArr)); }; case 'in': - return function(v) { return valueArr.indexOf(v) >= 0 }; + return function(v) { return valueArr.indexOf(v) >= 0; }; case 'notin': - return function(v) { return valueArr.indexOf(v) === -1 }; + return function(v) { return valueArr.indexOf(v) === -1; }; } } diff --git a/test/jasmine/tests/transforms_test.js b/test/jasmine/tests/transforms_test.js index 0ac441bef29..0925cd39914 100644 --- a/test/jasmine/tests/transforms_test.js +++ b/test/jasmine/tests/transforms_test.js @@ -35,15 +35,17 @@ describe('one-to-one transforms:', function() { }] }]; + var traceIn, traceOut; + afterEach(destroyGraphDiv); it('supplyTraceDefaults should supply the transform defaults', function() { - var traceIn = { + traceIn = { y: [2, 1, 2], transforms: [{ type: 'filter' }] }; - var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); expect(traceOut.transforms).toEqual([{ type: 'filter', @@ -54,18 +56,18 @@ describe('one-to-one transforms:', function() { }); it('supplyTraceDefaults should not bail if transform module is not found', function() { - var traceIn = { + traceIn = { y: [2, 1, 2], transforms: [{ type: 'invalid' }] }; - var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); expect(traceOut.y).toBe(traceIn.y); }); it('supplyTraceDefaults should honored global transforms', function() { - var traceIn = { + traceIn = { y: [2, 1, 2], transforms: [{ type: 'filter', @@ -81,7 +83,7 @@ describe('one-to-one transforms:', function() { }] }; - var traceOut = Plots.supplyTraceDefaults(traceIn, 0, layout); + traceOut = Plots.supplyTraceDefaults(traceIn, 0, layout); expect(traceOut.transforms[0]).toEqual({ type: 'filter', @@ -140,7 +142,7 @@ describe('one-to-one transforms:', function() { it('supplyDataDefaults should apply the transform while', function() { var dataIn = [{ x: [-2, -2, 1, 2, 3], - y: [1, 2, 2, 3, 1], + y: [1, 2, 2, 3, 1] }, { x: [-2, -1, -2, 0, 1, 2, 3], y: [1, 2, 3, 1, 2, 3, 1], @@ -332,12 +334,12 @@ describe('one-to-one transforms:', function() { it('supplyTraceDefaults should supply the transform defaults', function() { - var traceIn = { + traceIn = { y: [2, 1, 2], transforms: [{ type: 'filter' }] }; - var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); expect(traceOut.transforms).toEqual([{ type: 'filter', @@ -348,7 +350,7 @@ describe('one-to-one transforms:', function() { }); it('supplyTraceDefaults should accept numeric as character', function() { - var traceIn = { + traceIn = { x: '1', transforms: [{ type: 'filter', @@ -356,7 +358,7 @@ describe('one-to-one transforms:', function() { }] }; - var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); expect(traceOut.transforms).toEqual([{ type: 'filter', @@ -366,7 +368,7 @@ describe('one-to-one transforms:', function() { }]); // should also convert if array - var traceIn = { + traceIn = { x: '1', transforms: [{ type: 'filter', @@ -374,7 +376,7 @@ describe('one-to-one transforms:', function() { }] }; - var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); expect(traceOut.transforms).toEqual([{ type: 'filter', @@ -385,7 +387,7 @@ describe('one-to-one transforms:', function() { }); it('supplyTraceDefaults should accept numeric as character', function() { - var traceIn = { + traceIn = { x: '1', transforms: [{ type: 'filter', @@ -393,7 +395,7 @@ describe('one-to-one transforms:', function() { }] }; - var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); expect(traceOut.transforms).toEqual([{ type: 'filter', @@ -403,7 +405,7 @@ describe('one-to-one transforms:', function() { }]); // should also convert if array - var traceIn = { + traceIn = { x: '1', transforms: [{ type: 'filter', @@ -411,7 +413,7 @@ describe('one-to-one transforms:', function() { }] }; - var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); expect(traceOut.transforms).toEqual([{ type: 'filter', @@ -479,23 +481,23 @@ describe('one-to-one transforms:', function() { expect(dataOut[0].index).toEqual(0); }); - it('filters should chain as AND', function(){ + it('filters should chain as AND', function() { var dataIn = [{ x: [-2, -1, -2, 0, 1, 2, 3], y: [1, 2, 3, 1, 2, 3, 1], transforms: [ - { - type: 'filter', - operation: '>', - value: 0, - filtersrc: 'x' - }, - { - type: 'filter', - operation: '<', - value: 3, - filtersrc: 'x' - } + { + type: 'filter', + operation: '>', + value: 0, + filtersrc: 'x' + }, + { + type: 'filter', + operation: '<', + value: 3, + filtersrc: 'x' + } ] }]; From 6daeac57159ab4e7a9a4c18bc403ff863570a335 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Wed, 17 Aug 2016 09:35:16 -0500 Subject: [PATCH 08/34] remove filter register in validate_test --- test/jasmine/tests/validate_test.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/jasmine/tests/validate_test.js b/test/jasmine/tests/validate_test.js index 8a55323a6bd..40220481f7e 100644 --- a/test/jasmine/tests/validate_test.js +++ b/test/jasmine/tests/validate_test.js @@ -1,12 +1,6 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); -Plotly.register([ - // until they become official - require('../assets/transforms/filter') -]); - - describe('Plotly.validate', function() { function assertErrorContent(obj, code, cont, trace, path, astr, msg) { From 3e0f2091e93144e91fb5e3376b2ceca09aaade0e Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Wed, 17 Aug 2016 09:36:54 -0500 Subject: [PATCH 09/34] try to get header correct --- src/transforms/filter.js | 1 - src/transforms/groupby.js | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/transforms/filter.js b/src/transforms/filter.js index 845f99eb036..5ad950b0e0b 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var isNumeric = require('fast-isnumeric'); diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index 810fa5aecbd..dcbe789168c 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -1,3 +1,11 @@ +/** + * Copyright 2012-2016, 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'; // var Lib = require('@src/lib'); From d735b641f533b0403ab2b9709d28978840e39a75 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Wed, 14 Sep 2016 10:16:49 +0200 Subject: [PATCH 10/34] minor cleanup --- src/transforms/filter.js | 12 ++++++------ src/transforms/groupby.js | 12 ++++++------ test/jasmine/tests/plotschema_test.js | 5 ----- test/jasmine/tests/transforms_test.js | 7 ------- 4 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/transforms/filter.js b/src/transforms/filter.js index 5ad950b0e0b..a15295eb0f4 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -1,10 +1,10 @@ /** - * Copyright 2012-2016, 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. - */ +* Copyright 2012-2016, 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'; diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index dcbe789168c..2b2865cb945 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -1,10 +1,10 @@ /** - * Copyright 2012-2016, 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. - */ +* Copyright 2012-2016, 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'; diff --git a/test/jasmine/tests/plotschema_test.js b/test/jasmine/tests/plotschema_test.js index 17195fc67e2..013db8d2279 100644 --- a/test/jasmine/tests/plotschema_test.js +++ b/test/jasmine/tests/plotschema_test.js @@ -1,11 +1,6 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); -Plotly.register([ - // until they become official - require('../assets/transforms/filter') -]); - describe('plot schema', function() { 'use strict'; diff --git a/test/jasmine/tests/transforms_test.js b/test/jasmine/tests/transforms_test.js index 0925cd39914..7e9050bc655 100644 --- a/test/jasmine/tests/transforms_test.js +++ b/test/jasmine/tests/transforms_test.js @@ -6,13 +6,6 @@ var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -Plotly.register([ - //register filter in build - //require('@src/transforms/filter'), - require('@src/transforms/groupby') -]); - - describe('one-to-one transforms:', function() { 'use strict'; From a820b0cd9921552a97fd0e78badc1b7ec6a5cf73 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Wed, 14 Sep 2016 10:44:49 +0200 Subject: [PATCH 11/34] commenting out tests not yet passing --- test/jasmine/tests/transforms_test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/jasmine/tests/transforms_test.js b/test/jasmine/tests/transforms_test.js index 7e9050bc655..f3addce1a74 100644 --- a/test/jasmine/tests/transforms_test.js +++ b/test/jasmine/tests/transforms_test.js @@ -764,6 +764,7 @@ describe('one-to-one transforms:', function() { }); +/* describe('one-to-many transforms:', function() { 'use strict'; @@ -1316,7 +1317,7 @@ describe('multiple traces with transforms:', function() { }); }); - +*/ function assertDims(dims) { var traces = d3.selectAll('.trace'); From 2dd364669e89f812c8600873962a4666ea10a2a2 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Wed, 14 Sep 2016 11:41:12 +0200 Subject: [PATCH 12/34] require the transforms --- test/jasmine/tests/plotschema_test.js | 5 +++++ test/jasmine/tests/transforms_test.js | 8 ++++++-- test/jasmine/tests/validate_test.js | 5 +++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/test/jasmine/tests/plotschema_test.js b/test/jasmine/tests/plotschema_test.js index 013db8d2279..ffe72a93ccc 100644 --- a/test/jasmine/tests/plotschema_test.js +++ b/test/jasmine/tests/plotschema_test.js @@ -1,6 +1,11 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); +Plotly.register([ + require('@src/transforms/filter'), + require('@src/transforms/groupby') +]); + describe('plot schema', function() { 'use strict'; diff --git a/test/jasmine/tests/transforms_test.js b/test/jasmine/tests/transforms_test.js index f3addce1a74..1633c511ec3 100644 --- a/test/jasmine/tests/transforms_test.js +++ b/test/jasmine/tests/transforms_test.js @@ -6,6 +6,11 @@ var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +Plotly.register([ + require('@src/transforms/filter'), + require('@src/transforms/groupby') +]); + describe('one-to-one transforms:', function() { 'use strict'; @@ -764,7 +769,6 @@ describe('one-to-one transforms:', function() { }); -/* describe('one-to-many transforms:', function() { 'use strict'; @@ -1317,7 +1321,7 @@ describe('multiple traces with transforms:', function() { }); }); -*/ + function assertDims(dims) { var traces = d3.selectAll('.trace'); diff --git a/test/jasmine/tests/validate_test.js b/test/jasmine/tests/validate_test.js index 40220481f7e..4d6655c957a 100644 --- a/test/jasmine/tests/validate_test.js +++ b/test/jasmine/tests/validate_test.js @@ -1,6 +1,11 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); +Plotly.register([ + require('@src/transforms/filter'), + require('@src/transforms/groupby') +]); + describe('Plotly.validate', function() { function assertErrorContent(obj, code, cont, trace, path, astr, msg) { From c30f94edc9a710dce292feb8bd5469326ed65eeb Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 16 Sep 2016 10:13:04 +0200 Subject: [PATCH 13/34] split large and growing test file to three --- test/jasmine/assets/assert_dims.js | 18 + test/jasmine/assets/assert_style.js | 33 + ...forms_test.js => transform_filter_test.js} | 604 +----------------- test/jasmine/tests/transform_groupby_test.js | 181 ++++++ test/jasmine/tests/transform_multi_test.js | 396 ++++++++++++ 5 files changed, 631 insertions(+), 601 deletions(-) create mode 100644 test/jasmine/assets/assert_dims.js create mode 100644 test/jasmine/assets/assert_style.js rename test/jasmine/tests/{transforms_test.js => transform_filter_test.js} (55%) create mode 100644 test/jasmine/tests/transform_groupby_test.js create mode 100644 test/jasmine/tests/transform_multi_test.js diff --git a/test/jasmine/assets/assert_dims.js b/test/jasmine/assets/assert_dims.js new file mode 100644 index 00000000000..2db7f297b4f --- /dev/null +++ b/test/jasmine/assets/assert_dims.js @@ -0,0 +1,18 @@ +'use strict'; + +var d3 = require('d3'); + +module.exports = function assertDims(dims) { + var traces = d3.selectAll('.trace'); + + expect(traces.size()) + .toEqual(dims.length, 'to have correct number of traces'); + + traces.each(function(_, i) { + var trace = d3.select(this); + var points = trace.selectAll('.point'); + + expect(points.size()) + .toEqual(dims[i], 'to have correct number of pts in trace ' + i); + }); +}; diff --git a/test/jasmine/assets/assert_style.js b/test/jasmine/assets/assert_style.js new file mode 100644 index 00000000000..c6684da041e --- /dev/null +++ b/test/jasmine/assets/assert_style.js @@ -0,0 +1,33 @@ +'use strict'; + +var d3 = require('d3'); + +module.exports = function assertStyle(dims, color, opacity) { + var N = dims.reduce(function(a, b) { + return a + b; + }); + + var traces = d3.selectAll('.trace'); + expect(traces.size()) + .toEqual(dims.length, 'to have correct number of traces'); + + expect(d3.selectAll('.point').size()) + .toEqual(N, 'to have correct total number of points'); + + traces.each(function(_, i) { + var trace = d3.select(this); + var points = trace.selectAll('.point'); + + expect(points.size()) + .toEqual(dims[i], 'to have correct number of pts in trace ' + i); + + points.each(function() { + var point = d3.select(this); + + expect(point.style('fill')) + .toEqual(color[i], 'to have correct pt color'); + expect(+point.style('opacity')) + .toEqual(opacity[i], 'to have correct pt opacity'); + }); + }); +}; diff --git a/test/jasmine/tests/transforms_test.js b/test/jasmine/tests/transform_filter_test.js similarity index 55% rename from test/jasmine/tests/transforms_test.js rename to test/jasmine/tests/transform_filter_test.js index 1633c511ec3..4e7bed11757 100644 --- a/test/jasmine/tests/transforms_test.js +++ b/test/jasmine/tests/transform_filter_test.js @@ -2,13 +2,13 @@ var Plotly = require('@lib/index'); var Plots = require('@src/plots/plots'); var Lib = require('@src/lib'); -var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +var assertDims = require('../assets/assert_dims'); +var assertStyle = require('../assets/assert_style'); Plotly.register([ - require('@src/transforms/filter'), - require('@src/transforms/groupby') + require('@src/transforms/filter') ]); describe('one-to-one transforms:', function() { @@ -768,601 +768,3 @@ describe('one-to-one transforms:', function() { }); }); - -describe('one-to-many transforms:', function() { - 'use strict'; - - var mockData0 = [{ - mode: 'markers', - x: [1, -1, -2, 0, 1, 2, 3], - y: [1, 2, 3, 1, 2, 3, 1], - transforms: [{ - type: 'groupby', - groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], - groupColors: { a: 'red', b: 'blue' } - }] - }]; - - var mockData1 = [Lib.extendDeep({}, mockData0[0]), { - mode: 'markers', - x: [20, 11, 12, 0, 1, 2, 3], - y: [1, 2, 3, 2, 5, 2, 0], - transforms: [{ - type: 'groupby', - groups: ['b', 'a', 'b', 'b', 'b', 'a', 'a'], - groupColors: { a: 'green', b: 'black' } - }] - }]; - - afterEach(destroyGraphDiv); - - it('Plotly.plot should plot the transform traces', function(done) { - var data = Lib.extendDeep([], mockData0); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - expect(gd.data.length).toEqual(1); - expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); - expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - - expect(gd._fullData.length).toEqual(2); - expect(gd._fullData[0].x).toEqual([1, -1, 0, 3]); - expect(gd._fullData[0].y).toEqual([1, 2, 1, 1]); - expect(gd._fullData[1].x).toEqual([-2, 1, 2]); - expect(gd._fullData[1].y).toEqual([3, 2, 3]); - - assertDims([4, 3]); - - done(); - }); - }); - - it('Plotly.restyle should work', function(done) { - var data = Lib.extendDeep([], mockData0); - data[0].marker = { size: 20 }; - - var gd = createGraphDiv(); - var dims = [4, 3]; - - Plotly.plot(gd, data).then(function() { - assertStyle(dims, - ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [1, 1] - ); - - return Plotly.restyle(gd, 'marker.opacity', 0.4); - }).then(function() { - assertStyle(dims, - ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [0.4, 0.4] - ); - - expect(gd._fullData[0].marker.opacity).toEqual(0.4); - expect(gd._fullData[1].marker.opacity).toEqual(0.4); - - return Plotly.restyle(gd, 'marker.opacity', 1); - }).then(function() { - assertStyle(dims, - ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [1, 1] - ); - - expect(gd._fullData[0].marker.opacity).toEqual(1); - expect(gd._fullData[1].marker.opacity).toEqual(1); - - return Plotly.restyle(gd, { - 'transforms[0].groupColors': { a: 'green', b: 'red' }, - 'marker.opacity': 0.4 - }); - }).then(function() { - assertStyle(dims, - ['rgb(0, 128, 0)', 'rgb(255, 0, 0)'], - [0.4, 0.4] - ); - - done(); - }); - }); - - it('Plotly.extendTraces should work', function(done) { - var data = Lib.extendDeep([], mockData0); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - expect(gd.data[0].x.length).toEqual(7); - expect(gd._fullData[0].x.length).toEqual(4); - expect(gd._fullData[1].x.length).toEqual(3); - - assertDims([4, 3]); - - return Plotly.extendTraces(gd, { - x: [ [-3, 4, 5] ], - y: [ [1, -2, 3] ], - 'transforms[0].groups': [ ['b', 'a', 'b'] ] - }, [0]); - }).then(function() { - expect(gd.data[0].x.length).toEqual(10); - expect(gd._fullData[0].x.length).toEqual(5); - expect(gd._fullData[1].x.length).toEqual(5); - - assertDims([5, 5]); - - done(); - }); - }); - - it('Plotly.deleteTraces should work', function(done) { - var data = Lib.extendDeep([], mockData1); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - assertDims([4, 3, 4, 3]); - - return Plotly.deleteTraces(gd, [1]); - }).then(function() { - assertDims([4, 3]); - - return Plotly.deleteTraces(gd, [0]); - }).then(function() { - assertDims([]); - - done(); - }); - }); - - it('toggling trace visibility should work', function(done) { - var data = Lib.extendDeep([], mockData1); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - assertDims([4, 3, 4, 3]); - - return Plotly.restyle(gd, 'visible', 'legendonly', [1]); - }).then(function() { - assertDims([4, 3]); - - return Plotly.restyle(gd, 'visible', false, [0]); - }).then(function() { - assertDims([]); - - return Plotly.restyle(gd, 'visible', [true, true], [0, 1]); - }).then(function() { - assertDims([4, 3, 4, 3]); - - done(); - }); - }); - -}); - -describe('multiple transforms:', function() { - 'use strict'; - - var mockData0 = [{ - mode: 'markers', - x: [1, -1, -2, 0, 1, 2, 3], - y: [1, 2, 3, 1, 2, 3, 1], - transforms: [{ - type: 'groupby', - groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], - groupColors: { a: 'red', b: 'blue' } - }, { - type: 'filter', - operation: '>' - }] - }]; - - var mockData1 = [Lib.extendDeep({}, mockData0[0]), { - mode: 'markers', - x: [20, 11, 12, 0, 1, 2, 3], - y: [1, 2, 3, 2, 5, 2, 0], - transforms: [{ - type: 'groupby', - groups: ['b', 'a', 'b', 'b', 'b', 'a', 'a'], - groupColors: { a: 'green', b: 'black' } - }, { - type: 'filter', - operation: '<', - value: 10 - }] - }]; - - afterEach(destroyGraphDiv); - - it('Plotly.plot should plot the transform traces', function(done) { - var data = Lib.extendDeep([], mockData0); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - expect(gd.data.length).toEqual(1); - expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); - expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - - expect(gd._fullData.length).toEqual(2); - expect(gd._fullData[0].x).toEqual([1, 3]); - expect(gd._fullData[0].y).toEqual([1, 1]); - expect(gd._fullData[1].x).toEqual([1, 2]); - expect(gd._fullData[1].y).toEqual([2, 3]); - - assertDims([2, 2]); - - done(); - }); - }); - - it('Plotly.plot should plot the transform traces (reverse case)', function(done) { - var data = Lib.extendDeep([], mockData0); - - data[0].transforms.reverse(); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - expect(gd.data.length).toEqual(1); - expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); - expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - - expect(gd._fullData.length).toEqual(2); - expect(gd._fullData[0].x).toEqual([1, 1, 3]); - expect(gd._fullData[0].y).toEqual([1, 2, 1]); - expect(gd._fullData[1].x).toEqual([2]); - expect(gd._fullData[1].y).toEqual([3]); - - assertDims([3, 1]); - - done(); - }); - }); - - it('Plotly.restyle should work', function(done) { - var data = Lib.extendDeep([], mockData0); - data[0].marker = { size: 20 }; - - var gd = createGraphDiv(); - var dims = [2, 2]; - - Plotly.plot(gd, data).then(function() { - assertStyle(dims, - ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [1, 1] - ); - - return Plotly.restyle(gd, 'marker.opacity', 0.4); - }).then(function() { - assertStyle(dims, - ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [0.4, 0.4] - ); - - expect(gd._fullData[0].marker.opacity).toEqual(0.4); - expect(gd._fullData[1].marker.opacity).toEqual(0.4); - - return Plotly.restyle(gd, 'marker.opacity', 1); - }).then(function() { - assertStyle(dims, - ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [1, 1] - ); - - expect(gd._fullData[0].marker.opacity).toEqual(1); - expect(gd._fullData[1].marker.opacity).toEqual(1); - - return Plotly.restyle(gd, { - 'transforms[0].groupColors': { a: 'green', b: 'red' }, - 'marker.opacity': 0.4 - }); - }).then(function() { - assertStyle(dims, - ['rgb(0, 128, 0)', 'rgb(255, 0, 0)'], - [0.4, 0.4] - ); - - done(); - }); - }); - - it('Plotly.extendTraces should work', function(done) { - var data = Lib.extendDeep([], mockData0); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - expect(gd.data[0].x.length).toEqual(7); - expect(gd._fullData[0].x.length).toEqual(2); - expect(gd._fullData[1].x.length).toEqual(2); - - assertDims([2, 2]); - - return Plotly.extendTraces(gd, { - x: [ [-3, 4, 5] ], - y: [ [1, -2, 3] ], - 'transforms[0].groups': [ ['b', 'a', 'b'] ] - }, [0]); - }).then(function() { - expect(gd.data[0].x.length).toEqual(10); - expect(gd._fullData[0].x.length).toEqual(3); - expect(gd._fullData[1].x.length).toEqual(3); - - assertDims([3, 3]); - - done(); - }); - }); - - it('Plotly.deleteTraces should work', function(done) { - var data = Lib.extendDeep([], mockData1); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - assertDims([2, 2, 2, 2]); - - return Plotly.deleteTraces(gd, [1]); - }).then(function() { - assertDims([2, 2]); - - return Plotly.deleteTraces(gd, [0]); - }).then(function() { - assertDims([]); - - done(); - }); - }); - - it('toggling trace visibility should work', function(done) { - var data = Lib.extendDeep([], mockData1); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - assertDims([2, 2, 2, 2]); - - return Plotly.restyle(gd, 'visible', 'legendonly', [1]); - }).then(function() { - assertDims([2, 2]); - - return Plotly.restyle(gd, 'visible', false, [0]); - }).then(function() { - assertDims([]); - - return Plotly.restyle(gd, 'visible', [true, true]); - }).then(function() { - assertDims([2, 2, 2, 2]); - - done(); - }); - }); - -}); - -describe('multiple traces with transforms:', function() { - 'use strict'; - - var mockData0 = [{ - mode: 'markers', - x: [1, -1, -2, 0, 1, 2, 3], - y: [1, 2, 3, 1, 2, 3, 1], - marker: { color: 'green' }, - name: 'filtered', - transforms: [{ - type: 'filter', - operation: '>', - value: 1 - }] - }, { - mode: 'markers', - x: [20, 11, 12, 0, 1, 2, 3], - y: [1, 2, 3, 2, 5, 2, 0], - transforms: [{ - type: 'groupby', - groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], - groupColors: { a: 'red', b: 'blue' } - }, { - type: 'filter', - operation: '>' - }] - }]; - - afterEach(destroyGraphDiv); - - it('Plotly.plot should plot the transform traces', function(done) { - var data = Lib.extendDeep([], mockData0); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - expect(gd.data.length).toEqual(2); - expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); - expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - expect(gd.data[1].x).toEqual([20, 11, 12, 0, 1, 2, 3]); - expect(gd.data[1].y).toEqual([1, 2, 3, 2, 5, 2, 0]); - - expect(gd._fullData.length).toEqual(3); - expect(gd._fullData[0].x).toEqual([2, 3]); - expect(gd._fullData[0].y).toEqual([3, 1]); - expect(gd._fullData[1].x).toEqual([20, 11, 3]); - expect(gd._fullData[1].y).toEqual([1, 2, 0]); - expect(gd._fullData[2].x).toEqual([12, 1, 2]); - expect(gd._fullData[2].y).toEqual([3, 5, 2]); - - assertDims([2, 3, 3]); - - done(); - }); - }); - - it('Plotly.restyle should work', function(done) { - var data = Lib.extendDeep([], mockData0); - data[0].marker.size = 20; - - var gd = createGraphDiv(); - var dims = [2, 3, 3]; - - Plotly.plot(gd, data).then(function() { - assertStyle(dims, - ['rgb(0, 128, 0)', 'rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [1, 1, 1] - ); - - return Plotly.restyle(gd, 'marker.opacity', 0.4); - }).then(function() { - assertStyle(dims, - ['rgb(0, 128, 0)', 'rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [0.4, 0.4, 0.4] - ); - - gd._fullData.forEach(function(trace) { - expect(trace.marker.opacity).toEqual(0.4); - }); - - return Plotly.restyle(gd, 'marker.opacity', 1); - }).then(function() { - assertStyle(dims, - ['rgb(0, 128, 0)', 'rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [1, 1, 1] - ); - - gd._fullData.forEach(function(trace) { - expect(trace.marker.opacity).toEqual(1); - }); - - return Plotly.restyle(gd, { - 'transforms[0].groupColors': { a: 'green', b: 'red' }, - 'marker.opacity': [0.4, 0.6] - }); - }).then(function() { - assertStyle(dims, - ['rgb(0, 128, 0)', 'rgb(0, 128, 0)', 'rgb(255, 0, 0)'], - [0.4, 0.6, 0.6] - ); - - done(); - }); - }); - - it('Plotly.extendTraces should work', function(done) { - var data = Lib.extendDeep([], mockData0); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - assertDims([2, 3, 3]); - - return Plotly.extendTraces(gd, { - x: [ [-3, 4, 5] ], - y: [ [1, -2, 3] ], - 'transforms[0].groups': [ ['b', 'a', 'b'] ] - }, [1]); - }).then(function() { - assertDims([2, 4, 4]); - - return Plotly.extendTraces(gd, { - x: [ [5, 7, 10] ], - y: [ [1, -2, 3] ] - }, [0]); - }).then(function() { - assertDims([5, 4, 4]); - - done(); - }); - }); - - it('Plotly.deleteTraces should work', function(done) { - var data = Lib.extendDeep([], mockData0); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - assertDims([2, 3, 3]); - - return Plotly.deleteTraces(gd, [1]); - }).then(function() { - assertDims([2]); - - return Plotly.deleteTraces(gd, [0]); - }).then(function() { - assertDims([]); - - done(); - }); - }); - - it('toggling trace visibility should work', function(done) { - var data = Lib.extendDeep([], mockData0); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - assertDims([2, 3, 3]); - - return Plotly.restyle(gd, 'visible', 'legendonly', [1]); - }).then(function() { - assertDims([2]); - - return Plotly.restyle(gd, 'visible', false, [0]); - }).then(function() { - assertDims([]); - - return Plotly.restyle(gd, 'visible', [true, true]); - }).then(function() { - assertDims([2, 3, 3]); - - return Plotly.restyle(gd, 'visible', 'legendonly', [0]); - }).then(function() { - assertDims([3, 3]); - - done(); - }); - }); - -}); - -function assertDims(dims) { - var traces = d3.selectAll('.trace'); - - expect(traces.size()) - .toEqual(dims.length, 'to have correct number of traces'); - - traces.each(function(_, i) { - var trace = d3.select(this); - var points = trace.selectAll('.point'); - - expect(points.size()) - .toEqual(dims[i], 'to have correct number of pts in trace ' + i); - }); -} - -function assertStyle(dims, color, opacity) { - var N = dims.reduce(function(a, b) { - return a + b; - }); - - var traces = d3.selectAll('.trace'); - expect(traces.size()) - .toEqual(dims.length, 'to have correct number of traces'); - - expect(d3.selectAll('.point').size()) - .toEqual(N, 'to have correct total number of points'); - - traces.each(function(_, i) { - var trace = d3.select(this); - var points = trace.selectAll('.point'); - - expect(points.size()) - .toEqual(dims[i], 'to have correct number of pts in trace ' + i); - - points.each(function() { - var point = d3.select(this); - - expect(point.style('fill')) - .toEqual(color[i], 'to have correct pt color'); - expect(+point.style('opacity')) - .toEqual(opacity[i], 'to have correct pt opacity'); - }); - }); -} diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js new file mode 100644 index 00000000000..0750211aa36 --- /dev/null +++ b/test/jasmine/tests/transform_groupby_test.js @@ -0,0 +1,181 @@ +var Plotly = require('@lib/index'); +var Lib = require('@src/lib'); + +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var assertDims = require('../assets/assert_dims'); +var assertStyle = require('../assets/assert_style'); + +Plotly.register([ + require('@src/transforms/groupby') +]); + +describe('one-to-many transforms:', function() { + 'use strict'; + + var mockData0 = [{ + mode: 'markers', + x: [1, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + transforms: [{ + type: 'groupby', + groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + groupColors: { a: 'red', b: 'blue' } + }] + }]; + + var mockData1 = [Lib.extendDeep({}, mockData0[0]), { + mode: 'markers', + x: [20, 11, 12, 0, 1, 2, 3], + y: [1, 2, 3, 2, 5, 2, 0], + transforms: [{ + type: 'groupby', + groups: ['b', 'a', 'b', 'b', 'b', 'a', 'a'], + groupColors: { a: 'green', b: 'black' } + }] + }]; + + afterEach(destroyGraphDiv); + + it('Plotly.plot should plot the transform traces', function(done) { + var data = Lib.extendDeep([], mockData0); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data.length).toEqual(1); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + expect(gd._fullData.length).toEqual(2); + expect(gd._fullData[0].x).toEqual([1, -1, 0, 3]); + expect(gd._fullData[0].y).toEqual([1, 2, 1, 1]); + expect(gd._fullData[1].x).toEqual([-2, 1, 2]); + expect(gd._fullData[1].y).toEqual([3, 2, 3]); + + assertDims([4, 3]); + + done(); + }); + }); + + it('Plotly.restyle should work', function(done) { + var data = Lib.extendDeep([], mockData0); + data[0].marker = { size: 20 }; + + var gd = createGraphDiv(); + var dims = [4, 3]; + + Plotly.plot(gd, data).then(function() { + assertStyle(dims, + ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [1, 1] + ); + + return Plotly.restyle(gd, 'marker.opacity', 0.4); + }).then(function() { + assertStyle(dims, + ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [0.4, 0.4] + ); + + expect(gd._fullData[0].marker.opacity).toEqual(0.4); + expect(gd._fullData[1].marker.opacity).toEqual(0.4); + + return Plotly.restyle(gd, 'marker.opacity', 1); + }).then(function() { + assertStyle(dims, + ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [1, 1] + ); + + expect(gd._fullData[0].marker.opacity).toEqual(1); + expect(gd._fullData[1].marker.opacity).toEqual(1); + + return Plotly.restyle(gd, { + 'transforms[0].groupColors': { a: 'green', b: 'red' }, + 'marker.opacity': 0.4 + }); + }).then(function() { + assertStyle(dims, + ['rgb(0, 128, 0)', 'rgb(255, 0, 0)'], + [0.4, 0.4] + ); + + done(); + }); + }); + + it('Plotly.extendTraces should work', function(done) { + var data = Lib.extendDeep([], mockData0); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data[0].x.length).toEqual(7); + expect(gd._fullData[0].x.length).toEqual(4); + expect(gd._fullData[1].x.length).toEqual(3); + + assertDims([4, 3]); + + return Plotly.extendTraces(gd, { + x: [ [-3, 4, 5] ], + y: [ [1, -2, 3] ], + 'transforms[0].groups': [ ['b', 'a', 'b'] ] + }, [0]); + }).then(function() { + expect(gd.data[0].x.length).toEqual(10); + expect(gd._fullData[0].x.length).toEqual(5); + expect(gd._fullData[1].x.length).toEqual(5); + + assertDims([5, 5]); + + done(); + }); + }); + + it('Plotly.deleteTraces should work', function(done) { + var data = Lib.extendDeep([], mockData1); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + assertDims([4, 3, 4, 3]); + + return Plotly.deleteTraces(gd, [1]); + }).then(function() { + assertDims([4, 3]); + + return Plotly.deleteTraces(gd, [0]); + }).then(function() { + assertDims([]); + + done(); + }); + }); + + it('toggling trace visibility should work', function(done) { + var data = Lib.extendDeep([], mockData1); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + assertDims([4, 3, 4, 3]); + + return Plotly.restyle(gd, 'visible', 'legendonly', [1]); + }).then(function() { + assertDims([4, 3]); + + return Plotly.restyle(gd, 'visible', false, [0]); + }).then(function() { + assertDims([]); + + return Plotly.restyle(gd, 'visible', [true, true], [0, 1]); + }).then(function() { + assertDims([4, 3, 4, 3]); + + done(); + }); + }); + +}); diff --git a/test/jasmine/tests/transform_multi_test.js b/test/jasmine/tests/transform_multi_test.js new file mode 100644 index 00000000000..137540134c9 --- /dev/null +++ b/test/jasmine/tests/transform_multi_test.js @@ -0,0 +1,396 @@ +var Plotly = require('@lib/index'); +var Lib = require('@src/lib'); + +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var assertDims = require('../assets/assert_dims'); +var assertStyle = require('../assets/assert_style'); + + +Plotly.register([ + require('@src/transforms/filter'), + require('@src/transforms/groupby') +]); + +describe('multiple transforms:', function() { + 'use strict'; + + var mockData0 = [{ + mode: 'markers', + x: [1, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + transforms: [{ + type: 'groupby', + groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + groupColors: { a: 'red', b: 'blue' } + }, { + type: 'filter', + operation: '>' + }] + }]; + + var mockData1 = [Lib.extendDeep({}, mockData0[0]), { + mode: 'markers', + x: [20, 11, 12, 0, 1, 2, 3], + y: [1, 2, 3, 2, 5, 2, 0], + transforms: [{ + type: 'groupby', + groups: ['b', 'a', 'b', 'b', 'b', 'a', 'a'], + groupColors: { a: 'green', b: 'black' } + }, { + type: 'filter', + operation: '<', + value: 10 + }] + }]; + + afterEach(destroyGraphDiv); + + it('Plotly.plot should plot the transform traces', function(done) { + var data = Lib.extendDeep([], mockData0); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data.length).toEqual(1); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + expect(gd._fullData.length).toEqual(2); + expect(gd._fullData[0].x).toEqual([1, 3]); + expect(gd._fullData[0].y).toEqual([1, 1]); + expect(gd._fullData[1].x).toEqual([1, 2]); + expect(gd._fullData[1].y).toEqual([2, 3]); + + assertDims([2, 2]); + + done(); + }); + }); + + it('Plotly.plot should plot the transform traces (reverse case)', function(done) { + var data = Lib.extendDeep([], mockData0); + + data[0].transforms.reverse(); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data.length).toEqual(1); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + expect(gd._fullData.length).toEqual(2); + expect(gd._fullData[0].x).toEqual([1, 1, 3]); + expect(gd._fullData[0].y).toEqual([1, 2, 1]); + expect(gd._fullData[1].x).toEqual([2]); + expect(gd._fullData[1].y).toEqual([3]); + + assertDims([3, 1]); + + done(); + }); + }); + + it('Plotly.restyle should work', function(done) { + var data = Lib.extendDeep([], mockData0); + data[0].marker = { size: 20 }; + + var gd = createGraphDiv(); + var dims = [2, 2]; + + Plotly.plot(gd, data).then(function() { + assertStyle(dims, + ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [1, 1] + ); + + return Plotly.restyle(gd, 'marker.opacity', 0.4); + }).then(function() { + assertStyle(dims, + ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [0.4, 0.4] + ); + + expect(gd._fullData[0].marker.opacity).toEqual(0.4); + expect(gd._fullData[1].marker.opacity).toEqual(0.4); + + return Plotly.restyle(gd, 'marker.opacity', 1); + }).then(function() { + assertStyle(dims, + ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [1, 1] + ); + + expect(gd._fullData[0].marker.opacity).toEqual(1); + expect(gd._fullData[1].marker.opacity).toEqual(1); + + return Plotly.restyle(gd, { + 'transforms[0].groupColors': { a: 'green', b: 'red' }, + 'marker.opacity': 0.4 + }); + }).then(function() { + assertStyle(dims, + ['rgb(0, 128, 0)', 'rgb(255, 0, 0)'], + [0.4, 0.4] + ); + + done(); + }); + }); + + it('Plotly.extendTraces should work', function(done) { + var data = Lib.extendDeep([], mockData0); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data[0].x.length).toEqual(7); + expect(gd._fullData[0].x.length).toEqual(2); + expect(gd._fullData[1].x.length).toEqual(2); + + assertDims([2, 2]); + + return Plotly.extendTraces(gd, { + x: [ [-3, 4, 5] ], + y: [ [1, -2, 3] ], + 'transforms[0].groups': [ ['b', 'a', 'b'] ] + }, [0]); + }).then(function() { + expect(gd.data[0].x.length).toEqual(10); + expect(gd._fullData[0].x.length).toEqual(3); + expect(gd._fullData[1].x.length).toEqual(3); + + assertDims([3, 3]); + + done(); + }); + }); + + it('Plotly.deleteTraces should work', function(done) { + var data = Lib.extendDeep([], mockData1); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + assertDims([2, 2, 2, 2]); + + return Plotly.deleteTraces(gd, [1]); + }).then(function() { + assertDims([2, 2]); + + return Plotly.deleteTraces(gd, [0]); + }).then(function() { + assertDims([]); + + done(); + }); + }); + + it('toggling trace visibility should work', function(done) { + var data = Lib.extendDeep([], mockData1); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + assertDims([2, 2, 2, 2]); + + return Plotly.restyle(gd, 'visible', 'legendonly', [1]); + }).then(function() { + assertDims([2, 2]); + + return Plotly.restyle(gd, 'visible', false, [0]); + }).then(function() { + assertDims([]); + + return Plotly.restyle(gd, 'visible', [true, true]); + }).then(function() { + assertDims([2, 2, 2, 2]); + + done(); + }); + }); + +}); + +describe('multiple traces with transforms:', function() { + 'use strict'; + + var mockData0 = [{ + mode: 'markers', + x: [1, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + marker: { color: 'green' }, + name: 'filtered', + transforms: [{ + type: 'filter', + operation: '>', + value: 1 + }] + }, { + mode: 'markers', + x: [20, 11, 12, 0, 1, 2, 3], + y: [1, 2, 3, 2, 5, 2, 0], + transforms: [{ + type: 'groupby', + groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + groupColors: { a: 'red', b: 'blue' } + }, { + type: 'filter', + operation: '>' + }] + }]; + + afterEach(destroyGraphDiv); + + it('Plotly.plot should plot the transform traces', function(done) { + var data = Lib.extendDeep([], mockData0); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data.length).toEqual(2); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + expect(gd.data[1].x).toEqual([20, 11, 12, 0, 1, 2, 3]); + expect(gd.data[1].y).toEqual([1, 2, 3, 2, 5, 2, 0]); + + expect(gd._fullData.length).toEqual(3); + expect(gd._fullData[0].x).toEqual([2, 3]); + expect(gd._fullData[0].y).toEqual([3, 1]); + expect(gd._fullData[1].x).toEqual([20, 11, 3]); + expect(gd._fullData[1].y).toEqual([1, 2, 0]); + expect(gd._fullData[2].x).toEqual([12, 1, 2]); + expect(gd._fullData[2].y).toEqual([3, 5, 2]); + + assertDims([2, 3, 3]); + + done(); + }); + }); + + it('Plotly.restyle should work', function(done) { + var data = Lib.extendDeep([], mockData0); + data[0].marker.size = 20; + + var gd = createGraphDiv(); + var dims = [2, 3, 3]; + + Plotly.plot(gd, data).then(function() { + assertStyle(dims, + ['rgb(0, 128, 0)', 'rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [1, 1, 1] + ); + + return Plotly.restyle(gd, 'marker.opacity', 0.4); + }).then(function() { + assertStyle(dims, + ['rgb(0, 128, 0)', 'rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [0.4, 0.4, 0.4] + ); + + gd._fullData.forEach(function(trace) { + expect(trace.marker.opacity).toEqual(0.4); + }); + + return Plotly.restyle(gd, 'marker.opacity', 1); + }).then(function() { + assertStyle(dims, + ['rgb(0, 128, 0)', 'rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [1, 1, 1] + ); + + gd._fullData.forEach(function(trace) { + expect(trace.marker.opacity).toEqual(1); + }); + + return Plotly.restyle(gd, { + 'transforms[0].groupColors': { a: 'green', b: 'red' }, + 'marker.opacity': [0.4, 0.6] + }); + }).then(function() { + assertStyle(dims, + ['rgb(0, 128, 0)', 'rgb(0, 128, 0)', 'rgb(255, 0, 0)'], + [0.4, 0.6, 0.6] + ); + + done(); + }); + }); + + it('Plotly.extendTraces should work', function(done) { + var data = Lib.extendDeep([], mockData0); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + assertDims([2, 3, 3]); + + return Plotly.extendTraces(gd, { + x: [ [-3, 4, 5] ], + y: [ [1, -2, 3] ], + 'transforms[0].groups': [ ['b', 'a', 'b'] ] + }, [1]); + }).then(function() { + assertDims([2, 4, 4]); + + return Plotly.extendTraces(gd, { + x: [ [5, 7, 10] ], + y: [ [1, -2, 3] ] + }, [0]); + }).then(function() { + assertDims([5, 4, 4]); + + done(); + }); + }); + + it('Plotly.deleteTraces should work', function(done) { + var data = Lib.extendDeep([], mockData0); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + assertDims([2, 3, 3]); + + return Plotly.deleteTraces(gd, [1]); + }).then(function() { + assertDims([2]); + + return Plotly.deleteTraces(gd, [0]); + }).then(function() { + assertDims([]); + + done(); + }); + }); + + it('toggling trace visibility should work', function(done) { + var data = Lib.extendDeep([], mockData0); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + assertDims([2, 3, 3]); + + return Plotly.restyle(gd, 'visible', 'legendonly', [1]); + }).then(function() { + assertDims([2]); + + return Plotly.restyle(gd, 'visible', false, [0]); + }).then(function() { + assertDims([]); + + return Plotly.restyle(gd, 'visible', [true, true]); + }).then(function() { + assertDims([2, 3, 3]); + + return Plotly.restyle(gd, 'visible', 'legendonly', [0]); + }).then(function() { + assertDims([3, 3]); + + done(); + }); + }); + +}); From e9bde336a70ec1fbf5625c04c4d99f899010b54e Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 16 Sep 2016 09:53:13 +0200 Subject: [PATCH 14/34] groupby generalization (cherry picked from commit 7e967fe) --- src/transforms/groupby.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index 2b2865cb945..d7cb3924871 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -30,7 +30,7 @@ exports.attributes = { valType: 'data_array', dflt: [] }, - groupColors: { + styles: { valType: 'any', dflt: {} } @@ -61,7 +61,7 @@ exports.supplyDefaults = function(transformIn, fullData, layout) { if(!active) return transformOut; coerce('groups'); - coerce('groupColors'); + coerce('styles'); // or some more complex logic using fullData and layout @@ -126,7 +126,7 @@ function transformOne(trace, state) { } newTrace.name = groupName; - newTrace.marker.color = opts.groupColors[groupName]; + newTrace = Lib.extendDeep(newTrace, opts.styles[groupName]); } return newData; From c6355e857a3f60ff8e01c43ade5203a348274517 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 16 Sep 2016 12:10:45 +0200 Subject: [PATCH 15/34] switch from 'styles' to 'style' --- src/transforms/groupby.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index d7cb3924871..e324ef899fe 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -30,7 +30,7 @@ exports.attributes = { valType: 'data_array', dflt: [] }, - styles: { + style: { valType: 'any', dflt: {} } @@ -61,7 +61,7 @@ exports.supplyDefaults = function(transformIn, fullData, layout) { if(!active) return transformOut; coerce('groups'); - coerce('styles'); + coerce('style'); // or some more complex logic using fullData and layout @@ -126,7 +126,7 @@ function transformOne(trace, state) { } newTrace.name = groupName; - newTrace = Lib.extendDeep(newTrace, opts.styles[groupName]); + newTrace = Lib.extendDeep(newTrace, opts.style[groupName]); } return newData; From 5765f669e803f347b4cea9134b92afef0b61dc7b Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 16 Sep 2016 12:28:44 +0200 Subject: [PATCH 16/34] groupby generalization - update test cases --- test/jasmine/tests/transform_groupby_test.js | 6 +++--- test/jasmine/tests/transform_multi_test.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index 0750211aa36..c0ae0755a4f 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -20,7 +20,7 @@ describe('one-to-many transforms:', function() { transforms: [{ type: 'groupby', groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], - groupColors: { a: 'red', b: 'blue' } + style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } }] }]; @@ -31,7 +31,7 @@ describe('one-to-many transforms:', function() { transforms: [{ type: 'groupby', groups: ['b', 'a', 'b', 'b', 'b', 'a', 'a'], - groupColors: { a: 'green', b: 'black' } + style: { a: {marker: {color: 'green'}}, b: {marker: {color: 'black'}} } }] }]; @@ -93,7 +93,7 @@ describe('one-to-many transforms:', function() { expect(gd._fullData[1].marker.opacity).toEqual(1); return Plotly.restyle(gd, { - 'transforms[0].groupColors': { a: 'green', b: 'red' }, + 'transforms[0].style': { a: {marker: {color: 'green'}}, b: {marker: {color: 'red'}} }, 'marker.opacity': 0.4 }); }).then(function() { diff --git a/test/jasmine/tests/transform_multi_test.js b/test/jasmine/tests/transform_multi_test.js index 137540134c9..dc912d04d83 100644 --- a/test/jasmine/tests/transform_multi_test.js +++ b/test/jasmine/tests/transform_multi_test.js @@ -22,7 +22,7 @@ describe('multiple transforms:', function() { transforms: [{ type: 'groupby', groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], - groupColors: { a: 'red', b: 'blue' } + style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } }, { type: 'filter', operation: '>' @@ -36,7 +36,7 @@ describe('multiple transforms:', function() { transforms: [{ type: 'groupby', groups: ['b', 'a', 'b', 'b', 'b', 'a', 'a'], - groupColors: { a: 'green', b: 'black' } + style: { a: {marker: {color: 'green'}}, b: {marker: {color: 'black'}} } }, { type: 'filter', operation: '<', @@ -126,7 +126,7 @@ describe('multiple transforms:', function() { expect(gd._fullData[1].marker.opacity).toEqual(1); return Plotly.restyle(gd, { - 'transforms[0].groupColors': { a: 'green', b: 'red' }, + 'transforms[0].style': { a: {marker: {color: 'green'}}, b: {marker: {color: 'red'}} }, 'marker.opacity': 0.4 }); }).then(function() { @@ -234,7 +234,7 @@ describe('multiple traces with transforms:', function() { transforms: [{ type: 'groupby', groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], - groupColors: { a: 'red', b: 'blue' } + style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } }, { type: 'filter', operation: '>' @@ -305,7 +305,7 @@ describe('multiple traces with transforms:', function() { }); return Plotly.restyle(gd, { - 'transforms[0].groupColors': { a: 'green', b: 'red' }, + 'transforms[0].style': { a: {marker: {color: 'green'}}, b: {marker: {color: 'red'}} }, 'marker.opacity': [0.4, 0.6] }); }).then(function() { From 02742e8faecf26a02bf22e731da2a650388ca210 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 16 Sep 2016 13:39:19 +0200 Subject: [PATCH 17/34] adding ids to filtersrc --- src/transforms/filter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/transforms/filter.js b/src/transforms/filter.js index a15295eb0f4..897a9710978 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -35,7 +35,11 @@ exports.attributes = { filtersrc: { valType: 'enumerated', values: ['x', 'y'], - dflt: 'x' + dflt: 'x', + ids: { + valType: 'data_array', + description: 'A list of keys for object constancy of data points during animation' + } } }; From b23d2ff9ca9ad6d1f341ec302e75ff27398a5a15 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 16 Sep 2016 14:02:59 +0200 Subject: [PATCH 18/34] [spacing][technical] just wrapping around test case in anticipation of more cases inside --- test/jasmine/tests/transform_groupby_test.js | 282 ++++++++++--------- 1 file changed, 143 insertions(+), 139 deletions(-) diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index c0ae0755a4f..461bd84d3bc 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -10,172 +10,176 @@ Plotly.register([ require('@src/transforms/groupby') ]); -describe('one-to-many transforms:', function() { - 'use strict'; - - var mockData0 = [{ - mode: 'markers', - x: [1, -1, -2, 0, 1, 2, 3], - y: [1, 2, 3, 1, 2, 3, 1], - transforms: [{ - type: 'groupby', - groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], - style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } - }] - }]; - - var mockData1 = [Lib.extendDeep({}, mockData0[0]), { - mode: 'markers', - x: [20, 11, 12, 0, 1, 2, 3], - y: [1, 2, 3, 2, 5, 2, 0], - transforms: [{ - type: 'groupby', - groups: ['b', 'a', 'b', 'b', 'b', 'a', 'a'], - style: { a: {marker: {color: 'green'}}, b: {marker: {color: 'black'}} } - }] - }]; - - afterEach(destroyGraphDiv); - - it('Plotly.plot should plot the transform traces', function(done) { - var data = Lib.extendDeep([], mockData0); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - expect(gd.data.length).toEqual(1); - expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); - expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - - expect(gd._fullData.length).toEqual(2); - expect(gd._fullData[0].x).toEqual([1, -1, 0, 3]); - expect(gd._fullData[0].y).toEqual([1, 2, 1, 1]); - expect(gd._fullData[1].x).toEqual([-2, 1, 2]); - expect(gd._fullData[1].y).toEqual([3, 2, 3]); - - assertDims([4, 3]); - - done(); +describe('groupby', function() { + + describe('one-to-many transforms:', function() { + 'use strict'; + + var mockData0 = [{ + mode: 'markers', + x: [1, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + transforms: [{ + type: 'groupby', + groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } + }] + }]; + + var mockData1 = [Lib.extendDeep({}, mockData0[0]), { + mode: 'markers', + x: [20, 11, 12, 0, 1, 2, 3], + y: [1, 2, 3, 2, 5, 2, 0], + transforms: [{ + type: 'groupby', + groups: ['b', 'a', 'b', 'b', 'b', 'a', 'a'], + style: { a: {marker: {color: 'green'}}, b: {marker: {color: 'black'}} } + }] + }]; + + afterEach(destroyGraphDiv); + + it('Plotly.plot should plot the transform traces', function(done) { + var data = Lib.extendDeep([], mockData0); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data.length).toEqual(1); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + expect(gd._fullData.length).toEqual(2); + expect(gd._fullData[0].x).toEqual([1, -1, 0, 3]); + expect(gd._fullData[0].y).toEqual([1, 2, 1, 1]); + expect(gd._fullData[1].x).toEqual([-2, 1, 2]); + expect(gd._fullData[1].y).toEqual([3, 2, 3]); + + assertDims([4, 3]); + + done(); + }); }); - }); - it('Plotly.restyle should work', function(done) { - var data = Lib.extendDeep([], mockData0); - data[0].marker = { size: 20 }; - - var gd = createGraphDiv(); - var dims = [4, 3]; - - Plotly.plot(gd, data).then(function() { - assertStyle(dims, - ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [1, 1] - ); - - return Plotly.restyle(gd, 'marker.opacity', 0.4); - }).then(function() { - assertStyle(dims, - ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [0.4, 0.4] - ); - - expect(gd._fullData[0].marker.opacity).toEqual(0.4); - expect(gd._fullData[1].marker.opacity).toEqual(0.4); - - return Plotly.restyle(gd, 'marker.opacity', 1); - }).then(function() { - assertStyle(dims, - ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], - [1, 1] - ); - - expect(gd._fullData[0].marker.opacity).toEqual(1); - expect(gd._fullData[1].marker.opacity).toEqual(1); - - return Plotly.restyle(gd, { - 'transforms[0].style': { a: {marker: {color: 'green'}}, b: {marker: {color: 'red'}} }, - 'marker.opacity': 0.4 + it('Plotly.restyle should work', function(done) { + var data = Lib.extendDeep([], mockData0); + data[0].marker = { size: 20 }; + + var gd = createGraphDiv(); + var dims = [4, 3]; + + Plotly.plot(gd, data).then(function() { + assertStyle(dims, + ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [1, 1] + ); + + return Plotly.restyle(gd, 'marker.opacity', 0.4); + }).then(function() { + assertStyle(dims, + ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [0.4, 0.4] + ); + + expect(gd._fullData[0].marker.opacity).toEqual(0.4); + expect(gd._fullData[1].marker.opacity).toEqual(0.4); + + return Plotly.restyle(gd, 'marker.opacity', 1); + }).then(function() { + assertStyle(dims, + ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + [1, 1] + ); + + expect(gd._fullData[0].marker.opacity).toEqual(1); + expect(gd._fullData[1].marker.opacity).toEqual(1); + + return Plotly.restyle(gd, { + 'transforms[0].style': { a: {marker: {color: 'green'}}, b: {marker: {color: 'red'}} }, + 'marker.opacity': 0.4 + }); + }).then(function() { + assertStyle(dims, + ['rgb(0, 128, 0)', 'rgb(255, 0, 0)'], + [0.4, 0.4] + ); + + done(); }); - }).then(function() { - assertStyle(dims, - ['rgb(0, 128, 0)', 'rgb(255, 0, 0)'], - [0.4, 0.4] - ); - - done(); }); - }); - it('Plotly.extendTraces should work', function(done) { - var data = Lib.extendDeep([], mockData0); + it('Plotly.extendTraces should work', function(done) { + var data = Lib.extendDeep([], mockData0); - var gd = createGraphDiv(); + var gd = createGraphDiv(); - Plotly.plot(gd, data).then(function() { - expect(gd.data[0].x.length).toEqual(7); - expect(gd._fullData[0].x.length).toEqual(4); - expect(gd._fullData[1].x.length).toEqual(3); + Plotly.plot(gd, data).then(function() { + expect(gd.data[0].x.length).toEqual(7); + expect(gd._fullData[0].x.length).toEqual(4); + expect(gd._fullData[1].x.length).toEqual(3); - assertDims([4, 3]); + assertDims([4, 3]); - return Plotly.extendTraces(gd, { - x: [ [-3, 4, 5] ], - y: [ [1, -2, 3] ], - 'transforms[0].groups': [ ['b', 'a', 'b'] ] - }, [0]); - }).then(function() { - expect(gd.data[0].x.length).toEqual(10); - expect(gd._fullData[0].x.length).toEqual(5); - expect(gd._fullData[1].x.length).toEqual(5); + return Plotly.extendTraces(gd, { + x: [ [-3, 4, 5] ], + y: [ [1, -2, 3] ], + 'transforms[0].groups': [ ['b', 'a', 'b'] ] + }, [0]); + }).then(function() { + expect(gd.data[0].x.length).toEqual(10); + expect(gd._fullData[0].x.length).toEqual(5); + expect(gd._fullData[1].x.length).toEqual(5); - assertDims([5, 5]); + assertDims([5, 5]); - done(); + done(); + }); }); - }); - it('Plotly.deleteTraces should work', function(done) { - var data = Lib.extendDeep([], mockData1); + it('Plotly.deleteTraces should work', function(done) { + var data = Lib.extendDeep([], mockData1); - var gd = createGraphDiv(); + var gd = createGraphDiv(); - Plotly.plot(gd, data).then(function() { - assertDims([4, 3, 4, 3]); + Plotly.plot(gd, data).then(function() { + assertDims([4, 3, 4, 3]); - return Plotly.deleteTraces(gd, [1]); - }).then(function() { - assertDims([4, 3]); + return Plotly.deleteTraces(gd, [1]); + }).then(function() { + assertDims([4, 3]); - return Plotly.deleteTraces(gd, [0]); - }).then(function() { - assertDims([]); + return Plotly.deleteTraces(gd, [0]); + }).then(function() { + assertDims([]); - done(); + done(); + }); }); - }); - it('toggling trace visibility should work', function(done) { - var data = Lib.extendDeep([], mockData1); + it('toggling trace visibility should work', function(done) { + var data = Lib.extendDeep([], mockData1); - var gd = createGraphDiv(); + var gd = createGraphDiv(); - Plotly.plot(gd, data).then(function() { - assertDims([4, 3, 4, 3]); + Plotly.plot(gd, data).then(function() { + assertDims([4, 3, 4, 3]); - return Plotly.restyle(gd, 'visible', 'legendonly', [1]); - }).then(function() { - assertDims([4, 3]); + return Plotly.restyle(gd, 'visible', 'legendonly', [1]); + }).then(function() { + assertDims([4, 3]); - return Plotly.restyle(gd, 'visible', false, [0]); - }).then(function() { - assertDims([]); + return Plotly.restyle(gd, 'visible', false, [0]); + }).then(function() { + assertDims([]); - return Plotly.restyle(gd, 'visible', [true, true], [0, 1]); - }).then(function() { - assertDims([4, 3, 4, 3]); + return Plotly.restyle(gd, 'visible', [true, true], [0, 1]); + }).then(function() { + assertDims([4, 3, 4, 3]); - done(); + done(); + }); }); + }); }); From 55f9e0c2cd4ff3020dee8eeceab06bfb045bee9c Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 16 Sep 2016 14:06:40 +0200 Subject: [PATCH 19/34] symmetry / degeneracy test cases (manually cherrypicked) --- test/jasmine/tests/transform_groupby_test.js | 169 +++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index 461bd84d3bc..a60da605231 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -182,4 +182,173 @@ describe('groupby', function() { }); + // these tests can be shortened, once the meaning of edge cases gets clarified + describe('symmetry/degeneracy testing of one-to-many transforms on arbitrary arrays where there is no grouping (implicit 1):', function() { + 'use strict'; + + var mockData = [{ + mode: 'markers', + x: [1, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + + // everything is present: + transforms: [{ + type: 'groupby', + groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } + }] + }]; + + var mockData0 = [{ + mode: 'markers', + x: [1, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + + // groups, styles not present + transforms: [{ + type: 'groupby' + // groups not present + // styles not present + }] + }]; + + // transform attribute with empty list + var mockData1 = [{ + mode: 'markers', + x: [1, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + + // transforms is present but there are no items in it + transforms: [ /* list is empty */ ] + }]; + + // transform attribute with null value + var mockData2 = [{ + mode: 'markers', + x: [1, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + transforms: null + }]; + + // no transform is present at all + var mockData3 = [{ + mode: 'markers', + x: [1, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1] + }]; + + afterEach(destroyGraphDiv); + + it('Plotly.plot should plot the transform traces', function(done) { + var data = Lib.extendDeep([], mockData); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data.length).toEqual(1); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + expect(gd._fullData.length).toEqual(2); // two groups + expect(gd._fullData[0].x).toEqual([1, -1, 0, 3]); + expect(gd._fullData[0].y).toEqual([1, 2, 1, 1]); + expect(gd._fullData[1].x).toEqual([-2, 1, 2]); + expect(gd._fullData[1].y).toEqual([3, 2, 3]); + + assertDims([4, 3]); + + done(); + }); + }); + + // passes; maybe not for the good reasons (see fixme comments) + it('Plotly.plot should plot the transform traces', function(done) { + var data = Lib.extendDeep([], mockData0); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data.length).toEqual(1); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + expect(gd._fullData.length).toEqual(0); // fixme: it passes with 0; shouldn't it be 1? (one implied group) + + /* since the array is of zero length, the below items are obv. meaningless to test + expect(gd._fullData[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd._fullData[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + */ + + assertDims([]); // fixme: same thing, looks like zero dimensionality + + done(); + }); + }); + + // passes; looks OK + it('Plotly.plot should plot the transform traces', function(done) { + var data = Lib.extendDeep([], mockData1); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data.length).toEqual(1); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + expect(gd._fullData.length).toEqual(1); // fixme not: good, okay it's 1 here (one implied group / thing) + expect(gd._fullData[0].x).toEqual([ 1, -1, -2, 0, 1, 2, 3 ]); + expect(gd._fullData[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + assertDims([7]); + + done(); + }); + }); + + // passes OK; see todo comments + it('Plotly.plot should plot the transform traces', function(done) { + var data = Lib.extendDeep([], mockData2); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data.length).toEqual(1); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + expect(gd._fullData.length).toEqual(1); // todo: confirm this result is OK + + expect(gd._fullData[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd._fullData[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + assertDims([7]); // todo: confirm this result is OK + + done(); + }); + }); + + // passes OK; see todo comments + it('Plotly.plot should plot the transform traces', function(done) { + var data = Lib.extendDeep([], mockData3); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + expect(gd.data.length).toEqual(1); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + expect(gd._fullData.length).toEqual(1); // todo: confirm this result is OK + + expect(gd._fullData[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd._fullData[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); + + assertDims([7]); // todo: confirm this result is OK + + done(); + }); + }); + }); + }); From 462b2f018d5e7c53ad1a03645fb8a156bc2890be Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 16 Sep 2016 14:29:58 +0200 Subject: [PATCH 20/34] test heterogeneous attributes, deep nesting and overridden attributes --- test/jasmine/tests/transform_groupby_test.js | 115 +++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index a60da605231..5c180d5baba 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -351,4 +351,119 @@ describe('groupby', function() { }); }); + describe('grouping with basic, heterogenous and overridden attributes', function() { + 'use strict'; + + afterEach(destroyGraphDiv); + + function test(mockData) { + + return function(done) { + var data = Lib.extendDeep([], mockData); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + + expect(gd.data.length).toEqual(1); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([0, 1, 2, 3, 5, 4, 6]); + + expect(gd._fullData.length).toEqual(2); + expect(gd._fullData[0].x).toEqual([1, -1, 0, 3]); + expect(gd._fullData[0].y).toEqual([0, 1, 3, 6]); + expect(gd._fullData[1].x).toEqual([-2, 1, 2]); + expect(gd._fullData[1].y).toEqual([2, 5, 4]); + + assertDims([4, 3]); + + done(); + }); + }; + } + + // basic test + var mockData1 = [{ + mode: 'markers', + x: [1, -1, -2, 0, 1, 2, 3], + y: [0, 1, 2, 3, 5, 4, 6], + transforms: [{ + type: 'groupby', + groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } + }] + }]; + + // heterogenously present attributes + var mockData2 = [{ + mode: 'markers', + x: [1, -1, -2, 0, 1, 2, 3], + y: [0, 1, 2, 3, 5, 4, 6], + transforms: [{ + type: 'groupby', + groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + style: { + a: { + marker: { + color: 'orange', + size: 20, + line: { + color: 'red', + width: 1 + } + } + }, + b: { + mode: 'markers+lines', // heterogeonos attributes are OK: group "a" doesn't need to define this + marker: { + color: 'cyan', + size: 15, + line: { + color: 'purple', + width: 4 + }, + opacity: 0.5, + symbol: 'triangle-up' + }, + line: { + width: 1, + color: 'purple' + } + } + } + }] + }]; + + // attributes set at top level and partially overridden in the group item level + var mockData3 = [{ + mode: 'markers+lines', + x: [1, -1, -2, 0, 1, 2, 3], + y: [0, 1, 2, 3, 5, 4, 6], + marker: { + color: 'darkred', // general "default" color + line: { + width: 8, + // a general, not overridden array will be interpreted per group + color: ['orange', 'red', 'green', 'cyan'] + } + }, + line: {color: 'red'}, + transforms: [{ + type: 'groupby', + groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + style: { + a: {marker: {size: 30}}, + // override general color: + b: {marker: {size: 15, color: 'lightblue'}, line: {color: 'purple'}} + } + }] + }]; + + // this passes OK as expected + it('`data` preserves user supplied input but `gd._fullData` reflects the grouping', test(mockData1)); + it('passes with lots of attributes and heterogenous attrib presence', test(mockData2)); + it('passes with group styles partially overriding top level aesthetics', test(mockData3)); + + }); + }); From 9cafe1ef9f7ceb826e6fac532cd9f34a8c72aadc Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 16 Sep 2016 15:10:34 +0200 Subject: [PATCH 21/34] groupby: don't mandate styling for each group, or any style at all --- src/transforms/groupby.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index e324ef899fe..df183614b7a 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -108,6 +108,8 @@ function transformOne(trace, state) { var newData = new Array(groupNames.length); var len = Math.min(trace.x.length, trace.y.length, groups.length); + var style = opts.style || {}; + for(var i = 0; i < groupNames.length; i++) { var groupName = groupNames[i]; @@ -126,7 +128,7 @@ function transformOne(trace, state) { } newTrace.name = groupName; - newTrace = Lib.extendDeep(newTrace, opts.style[groupName]); + newTrace = Lib.extendDeep(newTrace, style[groupName] || {}); } return newData; From 87de31f4a72082662bd7d889199b08a214e4e5bc Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 16 Sep 2016 15:16:16 +0200 Subject: [PATCH 22/34] groupby test for no style present --- test/jasmine/tests/transform_groupby_test.js | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index 5c180d5baba..37012ce1e16 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -459,10 +459,33 @@ describe('groupby', function() { }] }]; + var mockData4 = [{ + mode: 'markers+lines', + x: [1, -1, -2, 0, 1, 2, 3], + y: [0, 1, 2, 3, 5, 4, 6], + transforms: [{ + type: 'groupby', + groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + style: {/* can be empty, or of partial group id coverage */} + }] + }]; + + var mockData5 = [{ + mode: 'markers+lines', + x: [1, -1, -2, 0, 1, 2, 3], + y: [0, 1, 2, 3, 5, 4, 6], + transforms: [{ + type: 'groupby', + groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'] + }] + }]; + // this passes OK as expected it('`data` preserves user supplied input but `gd._fullData` reflects the grouping', test(mockData1)); it('passes with lots of attributes and heterogenous attrib presence', test(mockData2)); it('passes with group styles partially overriding top level aesthetics', test(mockData3)); + it('passes with no explicit styling for the individual group', test(mockData4)); + it('passes with no explicit styling in the group transform at all', test(mockData5)); }); From 61f33854475aedde823bafa67a415c7a2e21e3ea Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Wed, 21 Sep 2016 10:30:07 +0200 Subject: [PATCH 23/34] hierarchical property split (squashed) --- src/transforms/groupby.js | 91 ++++++++++++++++++-- test/jasmine/tests/transform_groupby_test.js | 49 +++++++++-- 2 files changed, 124 insertions(+), 16 deletions(-) diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index df183614b7a..1dc026ec88c 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -91,13 +91,20 @@ exports.transform = function(data, state) { var newData = []; data.forEach(function(trace) { - newData = newData.concat(transformOne(trace, state)); + + var splittingAttributes = []; + + var attributes = trace._module.attributes; + crawl(attributes, splittingAttributes); + + newData = newData.concat(transformOne(trace, state, splittingAttributes)); }); return newData; }; -function transformOne(trace, state) { +function transformOne(trace, state, splittingAttributes) { + var opts = state.transform; var groups = opts.groups; @@ -106,10 +113,22 @@ function transformOne(trace, state) { }); var newData = new Array(groupNames.length); - var len = Math.min(trace.x.length, trace.y.length, groups.length); + var len = groups.length; var style = opts.style || {}; + var topLevelAttributes = splittingAttributes + .filter(function(array) {return Array.isArray(getDeepProp(trace, array));}); + + var initializeArray = function(newTrace, a) { + setDeepProp(newTrace, a, []); + }; + + var pasteArray = function(newTrace, trace, j, a) { + getDeepProp(newTrace, a).push(getDeepProp(trace, a)[j]); + }; + + // fixme the O(n**3) complexity for(var i = 0; i < groupNames.length; i++) { var groupName = groupNames[i]; @@ -117,19 +136,77 @@ function transformOne(trace, state) { // maybe we could abstract this out var newTrace = newData[i] = Lib.extendDeep({}, trace); - newTrace.x = []; - newTrace.y = []; + topLevelAttributes.forEach(initializeArray.bind(null, newTrace)); for(var j = 0; j < len; j++) { if(groups[j] !== groupName) continue; - newTrace.x.push(trace.x[j]); - newTrace.y.push(trace.y[j]); + topLevelAttributes.forEach(pasteArray.bind(0, newTrace, trace, j)); } newTrace.name = groupName; + + // there's no need to coerce style[groupName] here + // as another round of supplyDefaults is done on the transformed traces newTrace = Lib.extendDeep(newTrace, style[groupName] || {}); } return newData; } + +function getDeepProp(thing, propArray) { + var result = thing; + var i; + for(i = 0; i < propArray.length; i++) { + result = result[propArray[i]]; + if(result === void(0)) { + return result; + } + } + return result; +} + +function setDeepProp(thing, propArray, value) { + var current = thing; + var i; + for(i = 0; i < propArray.length - 1; i++) { + if(current[propArray[i]] === void(0)) { + current[propArray[i]] = {}; + } + current = current[propArray[i]]; + } + current[propArray[propArray.length - 1]] = value; +} + +// fixme check if similar functions in plot_schema.js can be reused +function crawl(attrs, list, path) { + path = path || []; + + Object.keys(attrs).forEach(function(attrName) { + var attr = attrs[attrName]; + var _path = path.slice(); + _path.push(attrName); + + if(attrName.charAt(0) === '_') return; + + callback(attr, list, _path); + + if(isValObject(attr)) return; + if(isPlainObject(attr)) crawl(attr, list, _path); + }); +} + +function isValObject(obj) { + return obj && obj.valType !== undefined; +} + +function callback(attr, list, path) { + // see schema.defs for complete list of 'val types' + if(attr.valType === 'data_array' || attr.arrayOk === true) { + list.push(path); + } +} + +function isPlainObject(obj) { + return Object.prototype.toString.call(obj) === '[object Object]'; +} diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index 37012ce1e16..5e69b900131 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -366,14 +366,22 @@ describe('groupby', function() { Plotly.plot(gd, data).then(function() { expect(gd.data.length).toEqual(1); + expect(gd.data[0].ids).toEqual(['q', 'w', 'r', 't', 'y', 'u', 'i']); expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); expect(gd.data[0].y).toEqual([0, 1, 2, 3, 5, 4, 6]); + expect(gd.data[0].marker.line.width).toEqual([4, 2, 4, 2, 2, 3, 3]); expect(gd._fullData.length).toEqual(2); + + expect(gd._fullData[0].ids).toEqual(['q', 'w', 't', 'i']); expect(gd._fullData[0].x).toEqual([1, -1, 0, 3]); expect(gd._fullData[0].y).toEqual([0, 1, 3, 6]); + expect(gd._fullData[0].marker.line.width).toEqual([4, 2, 2, 3]); + + expect(gd._fullData[1].ids).toEqual(['r', 'y', 'u']); expect(gd._fullData[1].x).toEqual([-2, 1, 2]); expect(gd._fullData[1].y).toEqual([2, 5, 4]); + expect(gd._fullData[1].marker.line.width).toEqual([4, 2, 3]); assertDims([4, 3]); @@ -385,8 +393,10 @@ describe('groupby', function() { // basic test var mockData1 = [{ mode: 'markers', + ids: ['q', 'w', 'r', 't', 'y', 'u', 'i'], x: [1, -1, -2, 0, 1, 2, 3], y: [0, 1, 2, 3, 5, 4, 6], + marker: {line: {width: [4, 2, 4, 2, 2, 3, 3]}}, transforms: [{ type: 'groupby', groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], @@ -397,8 +407,10 @@ describe('groupby', function() { // heterogenously present attributes var mockData2 = [{ mode: 'markers', + ids: ['q', 'w', 'r', 't', 'y', 'u', 'i'], x: [1, -1, -2, 0, 1, 2, 3], y: [0, 1, 2, 3, 5, 4, 6], + marker: {line: {width: [4, 2, 4, 2, 2, 3, 3]}}, transforms: [{ type: 'groupby', groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], @@ -408,25 +420,22 @@ describe('groupby', function() { color: 'orange', size: 20, line: { - color: 'red', - width: 1 + color: 'red' } } }, b: { - mode: 'markers+lines', // heterogeonos attributes are OK: group "a" doesn't need to define this + mode: 'markers+lines', // heterogeonos attributes are OK: group 'a' doesn't need to define this marker: { color: 'cyan', size: 15, line: { - color: 'purple', - width: 4 + color: 'purple' }, opacity: 0.5, symbol: 'triangle-up' }, line: { - width: 1, color: 'purple' } } @@ -437,12 +446,13 @@ describe('groupby', function() { // attributes set at top level and partially overridden in the group item level var mockData3 = [{ mode: 'markers+lines', + ids: ['q', 'w', 'r', 't', 'y', 'u', 'i'], x: [1, -1, -2, 0, 1, 2, 3], y: [0, 1, 2, 3, 5, 4, 6], marker: { - color: 'darkred', // general "default" color + color: 'darkred', // general 'default' color line: { - width: 8, + width: [4, 2, 4, 2, 2, 3, 3], // a general, not overridden array will be interpreted per group color: ['orange', 'red', 'green', 'cyan'] } @@ -461,8 +471,10 @@ describe('groupby', function() { var mockData4 = [{ mode: 'markers+lines', + ids: ['q', 'w', 'r', 't', 'y', 'u', 'i'], x: [1, -1, -2, 0, 1, 2, 3], y: [0, 1, 2, 3, 5, 4, 6], + marker: {line: {width: [4, 2, 4, 2, 2, 3, 3]}}, transforms: [{ type: 'groupby', groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], @@ -472,20 +484,39 @@ describe('groupby', function() { var mockData5 = [{ mode: 'markers+lines', + ids: ['q', 'w', 'r', 't', 'y', 'u', 'i'], x: [1, -1, -2, 0, 1, 2, 3], y: [0, 1, 2, 3, 5, 4, 6], + marker: { + line: {width: [4, 2, 4, 2, 2, 3, 3]}, + size: 10, + color: ['red', '#eee', 'lightgreen', 'blue', 'red', '#eee', 'lightgreen'] + }, transforms: [{ type: 'groupby', groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'] }] }]; - // this passes OK as expected + var mockData6 = [{ + mode: 'markers+lines', + ids: ['q', 'w', 'r', 't', 'y', 'u', 'i'], + x: [1, -1, -2, 0, 1, 2, 3], + y: [0, 1, 2, 3, 5, 4, 6], + marker: {line: {width: [4, 2, 4, 2, 2, 3, 3]}}, + transforms: [{ + type: 'groupby', + groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } + }] + }]; + it('`data` preserves user supplied input but `gd._fullData` reflects the grouping', test(mockData1)); it('passes with lots of attributes and heterogenous attrib presence', test(mockData2)); it('passes with group styles partially overriding top level aesthetics', test(mockData3)); it('passes with no explicit styling for the individual group', test(mockData4)); it('passes with no explicit styling in the group transform at all', test(mockData5)); + it('passes with no explicit styling in the group transform at all', test(mockData6)); }); From 57d0c13a6f9358fcd8e8051bc58c239b1491cf32 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Thu, 22 Sep 2016 12:20:13 +0200 Subject: [PATCH 24/34] reuse the plotly_schema crawler and fix some crawling issues (squashed) --- src/plot_api/plot_schema.js | 7 +++--- src/plots/plots.js | 23 +++++++++++++++++++ src/transforms/groupby.js | 46 ++++--------------------------------- 3 files changed, 31 insertions(+), 45 deletions(-) diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index 7a6ca5030b1..53aa9667c6f 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -56,16 +56,17 @@ PlotSchema.get = function() { return plotSchema; }; -PlotSchema.crawl = function(attrs, callback) { +PlotSchema.crawl = function (attrs, callback, specifiedLevel) { + var level = specifiedLevel || 0; Object.keys(attrs).forEach(function(attrName) { var attr = attrs[attrName]; if(UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return; - callback(attr, attrName, attrs); + callback(attr, attrName, attrs, level); if(PlotSchema.isValObject(attr)) return; - if(Lib.isPlainObject(attr)) PlotSchema.crawl(attr, callback); + if(Lib.isPlainObject(attr)) PlotSchema.crawl(attr, callback, level + 1); }); }; diff --git a/src/plots/plots.js b/src/plots/plots.js index b4ffa470551..68eca3baecf 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -9,6 +9,7 @@ 'use strict'; +var PlotSchema = require('./../plot_api/plot_schema') var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); @@ -786,6 +787,27 @@ function applyTransforms(fullTrace, fullData, layout) { var container = fullTrace.transforms, dataOut = [fullTrace]; + var attributeSets = dataOut.map(function(trace) { + + var arraySplitAttributes = []; + + var stack = []; + + function callback(attr, attrName, attrs, level) { + + stack = stack.slice(0, level).concat([attrName]); + + var splittableAttr = attr.valType === 'data_array' || attr.arrayOk === true + if(splittableAttr) { + arraySplitAttributes.push(stack.slice()); + } + } + + PlotSchema.crawl(trace._module.attributes, callback); + + return arraySplitAttributes; + }); + for(var i = 0; i < container.length; i++) { var transform = container[i], type = transform.type, @@ -796,6 +818,7 @@ function applyTransforms(fullTrace, fullData, layout) { transform: transform, fullTrace: fullTrace, fullData: fullData, + attributeSets: attributeSets, layout: layout }); } diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index 1dc026ec88c..3285260315d 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -90,20 +90,15 @@ exports.transform = function(data, state) { var newData = []; - data.forEach(function(trace) { + data.forEach(function(trace, i) { - var splittingAttributes = []; - - var attributes = trace._module.attributes; - crawl(attributes, splittingAttributes); - - newData = newData.concat(transformOne(trace, state, splittingAttributes)); + newData = newData.concat(transformOne(trace, state, state.attributeSets[i])); }); return newData; }; -function transformOne(trace, state, splittingAttributes) { +function transformOne(trace, state, attributeSet) { var opts = state.transform; var groups = opts.groups; @@ -117,7 +112,7 @@ function transformOne(trace, state, splittingAttributes) { var style = opts.style || {}; - var topLevelAttributes = splittingAttributes + var topLevelAttributes = attributeSet .filter(function(array) {return Array.isArray(getDeepProp(trace, array));}); var initializeArray = function(newTrace, a) { @@ -177,36 +172,3 @@ function setDeepProp(thing, propArray, value) { } current[propArray[propArray.length - 1]] = value; } - -// fixme check if similar functions in plot_schema.js can be reused -function crawl(attrs, list, path) { - path = path || []; - - Object.keys(attrs).forEach(function(attrName) { - var attr = attrs[attrName]; - var _path = path.slice(); - _path.push(attrName); - - if(attrName.charAt(0) === '_') return; - - callback(attr, list, _path); - - if(isValObject(attr)) return; - if(isPlainObject(attr)) crawl(attr, list, _path); - }); -} - -function isValObject(obj) { - return obj && obj.valType !== undefined; -} - -function callback(attr, list, path) { - // see schema.defs for complete list of 'val types' - if(attr.valType === 'data_array' || attr.arrayOk === true) { - list.push(path); - } -} - -function isPlainObject(obj) { - return Object.prototype.toString.call(obj) === '[object Object]'; -} From 7085729d9022d0a24d6ca1651c9a48b019a7d744 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Thu, 22 Sep 2016 12:41:14 +0200 Subject: [PATCH 25/34] resolved circular dependency by extracting the crawler --- src/lib/index.js | 1 + src/lib/is_val_object.js | 15 +++++++++++++ src/plot_api/crawler.js | 35 +++++++++++++++++++++++++++++ src/plot_api/plot_schema.js | 45 +++++++++++-------------------------- src/plots/plots.js | 6 ++--- 5 files changed, 67 insertions(+), 35 deletions(-) create mode 100644 src/lib/is_val_object.js create mode 100644 src/plot_api/crawler.js diff --git a/src/lib/index.js b/src/lib/index.js index cb1cbd88ac8..463ee867d86 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -15,6 +15,7 @@ var lib = module.exports = {}; lib.nestedProperty = require('./nested_property'); lib.isPlainObject = require('./is_plain_object'); +lib.isValObject = require('./is_val_object'); lib.isArray = require('./is_array'); var coerceModule = require('./coerce'); diff --git a/src/lib/is_val_object.js b/src/lib/is_val_object.js new file mode 100644 index 00000000000..f98f14465d3 --- /dev/null +++ b/src/lib/is_val_object.js @@ -0,0 +1,15 @@ +/** +* Copyright 2012-2016, 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'; + +// returns true for a valid value object and false for tree nodes in the attribute hierarchy +module.exports = function(obj) { + return obj && obj.valType !== undefined; +}; diff --git a/src/plot_api/crawler.js b/src/plot_api/crawler.js new file mode 100644 index 00000000000..c62fe49b827 --- /dev/null +++ b/src/plot_api/crawler.js @@ -0,0 +1,35 @@ +/** +* Copyright 2012-2016, 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'; + +var Lib = require('../lib'); + +var crawler = module.exports = {}; + +crawler.IS_SUBPLOT_OBJ = '_isSubplotObj'; +crawler.IS_LINKED_TO_ARRAY = '_isLinkedToArray'; +crawler.DEPRECATED = '_deprecated'; + +// list of underscore attributes to keep in schema as is +crawler.UNDERSCORE_ATTRS = [crawler.IS_SUBPLOT_OBJ, crawler.IS_LINKED_TO_ARRAY, crawler.DEPRECATED]; + +crawler.crawl = function(attrs, callback, specifiedLevel) { + var level = specifiedLevel || 0; + Object.keys(attrs).forEach(function(attrName) { + var attr = attrs[attrName]; + + if(crawler.UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return; + + callback(attr, attrName, attrs, level); + + if(Lib.isValObject(attr)) return; + if(Lib.isPlainObject(attr)) crawler.crawl(attr, callback, level + 1); + }); +}; diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index 53aa9667c6f..8e42653038a 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -13,6 +13,7 @@ var Plotly = require('../plotly'); var Registry = require('../registry'); var Plots = require('../plots/plots'); var Lib = require('../lib'); +var crawler = require('./crawler'); // FIXME polar attribute are not part of Plotly yet var polarAreaAttrs = require('../plots/polar/area_attributes'); @@ -23,13 +24,7 @@ var extendDeep = Lib.extendDeep; var extendDeepAll = Lib.extendDeepAll; var NESTED_MODULE = '_nestedModules', - COMPOSED_MODULE = '_composedModules', - IS_SUBPLOT_OBJ = '_isSubplotObj', - IS_LINKED_TO_ARRAY = '_isLinkedToArray', - DEPRECATED = '_deprecated'; - -// list of underscore attributes to keep in schema as is -var UNDERSCORE_ATTRS = [IS_SUBPLOT_OBJ, IS_LINKED_TO_ARRAY, DEPRECATED]; + COMPOSED_MODULE = '_composedModules'; var plotSchema = { traces: {}, @@ -56,23 +51,9 @@ PlotSchema.get = function() { return plotSchema; }; -PlotSchema.crawl = function (attrs, callback, specifiedLevel) { - var level = specifiedLevel || 0; - Object.keys(attrs).forEach(function(attrName) { - var attr = attrs[attrName]; - - if(UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return; +PlotSchema.crawl = crawler.crawl; - callback(attr, attrName, attrs, level); - - if(PlotSchema.isValObject(attr)) return; - if(Lib.isPlainObject(attr)) PlotSchema.crawl(attr, callback, level + 1); - }); -}; - -PlotSchema.isValObject = function(obj) { - return obj && obj.valType !== undefined; -}; +PlotSchema.isValObject = Lib.isValObject; function getTraceAttributes(type) { var globalAttributes = Plots.attributes, @@ -132,13 +113,13 @@ function getLayoutAttributes() { // FIXME polar layout attributes layoutAttributes = assignPolarLayoutAttrs(layoutAttributes); - // add IS_SUBPLOT_OBJ attribute + // add crawler.IS_SUBPLOT_OBJ attribute layoutAttributes = handleSubplotObjs(layoutAttributes); layoutAttributes = removeUnderscoreAttrs(layoutAttributes); mergeValTypeAndRole(layoutAttributes); - // generate IS_LINKED_TO_ARRAY structure + // generate crawler.IS_LINKED_TO_ARRAY structure handleLinkedToArray(layoutAttributes); plotSchema.layout = { layoutAttributes: layoutAttributes }; @@ -159,7 +140,7 @@ function getTransformAttributes(name) { function getDefs() { plotSchema.defs = { valObjects: Lib.valObjects, - metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role']) + metaKeys: crawler.UNDERSCORE_ATTRS.concat(['description', 'role']) }; } @@ -242,7 +223,7 @@ function mergeValTypeAndRole(attrs) { } } - PlotSchema.crawl(attrs, callback); + crawler.crawl(attrs, callback); } // helper methods @@ -268,7 +249,7 @@ function getModule(arg) { function removeUnderscoreAttrs(attributes) { Object.keys(attributes).forEach(function(k) { if(k.charAt(0) === '_' && - UNDERSCORE_ATTRS.indexOf(k) === -1) delete attributes[k]; + crawler.UNDERSCORE_ATTRS.indexOf(k) === -1) delete attributes[k]; }); return attributes; } @@ -322,7 +303,7 @@ function handleSubplotObjs(layoutAttributes) { isSubplotObj = subplotRegistry.attrRegex.test(k); } - if(isSubplotObj) layoutAttributes[k][IS_SUBPLOT_OBJ] = true; + if(isSubplotObj) layoutAttributes[k][crawler.IS_SUBPLOT_OBJ] = true; }); }); @@ -332,17 +313,17 @@ function handleSubplotObjs(layoutAttributes) { function handleLinkedToArray(layoutAttributes) { function callback(attr, attrName, attrs) { - if(attr[IS_LINKED_TO_ARRAY] !== true) return; + if(attr[crawler.IS_LINKED_TO_ARRAY] !== true) return; // TODO more robust logic var itemName = attrName.substr(0, attrName.length - 1); - delete attr[IS_LINKED_TO_ARRAY]; + delete attr[crawler.IS_LINKED_TO_ARRAY]; attrs[attrName] = { items: {} }; attrs[attrName].items[itemName] = attr; attrs[attrName].role = 'object'; } - PlotSchema.crawl(layoutAttributes, callback); + crawler.crawl(layoutAttributes, callback); } diff --git a/src/plots/plots.js b/src/plots/plots.js index 68eca3baecf..7254003f228 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -9,7 +9,7 @@ 'use strict'; -var PlotSchema = require('./../plot_api/plot_schema') +var crawler = require('./../plot_api/crawler'); var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); @@ -797,13 +797,13 @@ function applyTransforms(fullTrace, fullData, layout) { stack = stack.slice(0, level).concat([attrName]); - var splittableAttr = attr.valType === 'data_array' || attr.arrayOk === true + var splittableAttr = attr.valType === 'data_array' || attr.arrayOk === true; if(splittableAttr) { arraySplitAttributes.push(stack.slice()); } } - PlotSchema.crawl(trace._module.attributes, callback); + crawler.crawl(trace._module.attributes, callback); return arraySplitAttributes; }); From 392844d74861cef1cc3fbb0436f88391b68fc86c Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Thu, 22 Sep 2016 16:52:38 +0200 Subject: [PATCH 26/34] sync up filter and groupby; fix combined test case which used to pass incorrectly --- src/plots/plots.js | 3 ++- src/transforms/filter.js | 2 +- src/transforms/groupby.js | 2 +- test/jasmine/tests/transform_multi_test.js | 12 ++++++------ 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 7254003f228..54880959ce2 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -819,7 +819,8 @@ function applyTransforms(fullTrace, fullData, layout) { fullTrace: fullTrace, fullData: fullData, attributeSets: attributeSets, - layout: layout + layout: layout, + transformIndex: i }); } } diff --git a/src/transforms/filter.js b/src/transforms/filter.js index 897a9710978..9bbe2700f57 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -34,7 +34,7 @@ exports.attributes = { }, filtersrc: { valType: 'enumerated', - values: ['x', 'y'], + values: ['x', 'y', 'ids'], dflt: 'x', ids: { valType: 'data_array', diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index 3285260315d..0e210ea5e24 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -101,7 +101,7 @@ exports.transform = function(data, state) { function transformOne(trace, state, attributeSet) { var opts = state.transform; - var groups = opts.groups; + var groups = trace.transforms[state.transformIndex].groups; var groupNames = groups.filter(function(g, i, self) { return self.indexOf(g) === i; diff --git a/test/jasmine/tests/transform_multi_test.js b/test/jasmine/tests/transform_multi_test.js index dc912d04d83..ae1b7abd94b 100644 --- a/test/jasmine/tests/transform_multi_test.js +++ b/test/jasmine/tests/transform_multi_test.js @@ -71,7 +71,7 @@ describe('multiple transforms:', function() { it('Plotly.plot should plot the transform traces (reverse case)', function(done) { var data = Lib.extendDeep([], mockData0); - data[0].transforms.reverse(); + data[0].transforms.slice().reverse(); var gd = createGraphDiv(); @@ -81,12 +81,12 @@ describe('multiple transforms:', function() { expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); expect(gd._fullData.length).toEqual(2); - expect(gd._fullData[0].x).toEqual([1, 1, 3]); - expect(gd._fullData[0].y).toEqual([1, 2, 1]); - expect(gd._fullData[1].x).toEqual([2]); - expect(gd._fullData[1].y).toEqual([3]); + expect(gd._fullData[0].x).toEqual([1, 3]); + expect(gd._fullData[0].y).toEqual([1, 1]); + expect(gd._fullData[1].x).toEqual([1, 2]); + expect(gd._fullData[1].y).toEqual([2, 3]); - assertDims([3, 1]); + assertDims([2, 2]); done(); }); From 1dd93bb1e309bff34870e887847e94c04af68fb4 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Thu, 22 Sep 2016 18:11:52 +0200 Subject: [PATCH 27/34] PR feedback: comment and test case updates --- src/plot_api/crawler.js | 23 +++++++++++++++++ src/plots/plots.js | 12 +++++++++ test/jasmine/tests/transform_groupby_test.js | 26 +++++++++++--------- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/plot_api/crawler.js b/src/plot_api/crawler.js index c62fe49b827..1b613b17c6a 100644 --- a/src/plot_api/crawler.js +++ b/src/plot_api/crawler.js @@ -20,6 +20,29 @@ crawler.DEPRECATED = '_deprecated'; // list of underscore attributes to keep in schema as is crawler.UNDERSCORE_ATTRS = [crawler.IS_SUBPLOT_OBJ, crawler.IS_LINKED_TO_ARRAY, crawler.DEPRECATED]; +/** + * Crawl the attribute tree, recursively calling a callback function + * + * @param {object} attrs + * The node of the attribute tree (e.g. the root) from which recursion originates + * @param {Function} callback + * A callback function with the signature: + * @callback callback + * @param {object} attr an attribute + * @param {String} attrName name string + * @param {object[]} attrs all the attributes + * @param {Number} level the recursion level, 0 at the root + * @param {Number} [specifiedLevel] + * The level in the tree, in order to let the callback function detect descend or backtrack, + * typically unsupplied (implied 0), just used by the self-recursive call. + * The necessity arises because the tree traversal is not controlled by callback return values. + * The decision to not use callback return values for controlling tree pruning arose from + * the goal of keeping the crawler backwards compatible. Observe that one of the pruning conditions + * precedes the callback call. + * + * @return {object} transformOut + * copy of transformIn that contains attribute defaults + */ crawler.crawl = function(attrs, callback, specifiedLevel) { var level = specifiedLevel || 0; Object.keys(attrs).forEach(function(attrName) { diff --git a/src/plots/plots.js b/src/plots/plots.js index 54880959ce2..1e026703712 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -793,6 +793,18 @@ function applyTransforms(fullTrace, fullData, layout) { var stack = []; + /** + * A closure that gathers attribute paths into its enclosed arraySplitAttributes + * Attribute paths are collected iff their leaf node is a splittable attribute + * @callback callback + * @param {object} attr an attribute + * @param {String} attrName name string + * @param {object[]} attrs all the attributes + * @param {Number} level the recursion level, 0 at the root + * @closureVariable {String[][]} arraySplitAttributes the set of gathered attributes + * Example of filled closure variable (expected to be initialized to []): + * [["marker","size"],["marker","line","width"],["marker","line","color"]] + */ function callback(attr, attrName, attrs, level) { stack = stack.slice(0, level).concat([attrName]); diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index 5e69b900131..fa54218fd86 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -261,7 +261,6 @@ describe('groupby', function() { }); }); - // passes; maybe not for the good reasons (see fixme comments) it('Plotly.plot should plot the transform traces', function(done) { var data = Lib.extendDeep([], mockData0); @@ -285,7 +284,6 @@ describe('groupby', function() { }); }); - // passes; looks OK it('Plotly.plot should plot the transform traces', function(done) { var data = Lib.extendDeep([], mockData1); @@ -306,7 +304,6 @@ describe('groupby', function() { }); }); - // passes OK; see todo comments it('Plotly.plot should plot the transform traces', function(done) { var data = Lib.extendDeep([], mockData2); @@ -317,18 +314,17 @@ describe('groupby', function() { expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - expect(gd._fullData.length).toEqual(1); // todo: confirm this result is OK + expect(gd._fullData.length).toEqual(1); expect(gd._fullData[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); expect(gd._fullData[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - assertDims([7]); // todo: confirm this result is OK + assertDims([7]); done(); }); }); - // passes OK; see todo comments it('Plotly.plot should plot the transform traces', function(done) { var data = Lib.extendDeep([], mockData3); @@ -339,12 +335,12 @@ describe('groupby', function() { expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - expect(gd._fullData.length).toEqual(1); // todo: confirm this result is OK + expect(gd._fullData.length).toEqual(1); expect(gd._fullData[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); expect(gd._fullData[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - assertDims([7]); // todo: confirm this result is OK + assertDims([7]); done(); }); @@ -354,7 +350,7 @@ describe('groupby', function() { describe('grouping with basic, heterogenous and overridden attributes', function() { 'use strict'; - afterEach(destroyGraphDiv); + //afterEach(destroyGraphDiv); function test(mockData) { @@ -453,8 +449,7 @@ describe('groupby', function() { color: 'darkred', // general 'default' color line: { width: [4, 2, 4, 2, 2, 3, 3], - // a general, not overridden array will be interpreted per group - color: ['orange', 'red', 'green', 'cyan'] + color: ['orange', 'red', 'green', 'cyan', 'magenta', 'blue', 'pink'] } }, line: {color: 'red'}, @@ -464,7 +459,7 @@ describe('groupby', function() { style: { a: {marker: {size: 30}}, // override general color: - b: {marker: {size: 15, color: 'lightblue'}, line: {color: 'purple'}} + b: {marker: {size: 15, line: {color: 'yellow'}}, line: {color: 'purple'}} } }] }]; @@ -512,10 +507,17 @@ describe('groupby', function() { }]; it('`data` preserves user supplied input but `gd._fullData` reflects the grouping', test(mockData1)); + it('passes with lots of attributes and heterogenous attrib presence', test(mockData2)); + it('passes with group styles partially overriding top level aesthetics', test(mockData3)); + expect(gd._fullData[0].marker.line.color).toEqual(['orange', 'red', 'cyan', 'pink']); + expect(gd._fullData[1].marker.line.color).toEqual('yellow'); + it('passes with no explicit styling for the individual group', test(mockData4)); + it('passes with no explicit styling in the group transform at all', test(mockData5)); + it('passes with no explicit styling in the group transform at all', test(mockData6)); }); From 1ea2fd7bcd387c5c9606e0c40ddca3b3e737f1f2 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 23 Sep 2016 19:38:00 +0200 Subject: [PATCH 28/34] PR feedback: test with empty grouping information --- test/jasmine/tests/transform_groupby_test.js | 52 ++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index fa54218fd86..932cd381302 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -522,4 +522,56 @@ describe('groupby', function() { }); + describe('passes with no `groups`', function() { + 'use strict'; + + afterEach(destroyGraphDiv); + + var mockData = [{ + mode: 'markers+lines', + ids: ['q', 'w', 'r', 't', 'y', 'u', 'i'], + x: [1, -1, -2, 0, 1, 2, 3], + y: [0, 1, 2, 3, 5, 4, 6], + marker: {size: 20, line: {width: [4, 2, 4, 2, 2, 3, 3]}}, + transforms: [{ + type: 'groupby', + //groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } + }] + }]; + + it("passes", function(done) { + var data = Lib.extendDeep([], mockData); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + +/* + expect(gd.data.length).toEqual(1); + expect(gd.data[0].ids).toEqual(['q', 'w', 'r', 't', 'y', 'u', 'i']); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([0, 1, 2, 3, 5, 4, 6]); + expect(gd.data[0].marker.line.width).toEqual([4, 2, 4, 2, 2, 3, 3]); + + expect(gd._fullData.length).toEqual(2); + + expect(gd._fullData[0].ids).toEqual(['q', 'w', 't', 'i']); + expect(gd._fullData[0].x).toEqual([1, -1, 0, 3]); + expect(gd._fullData[0].y).toEqual([0, 1, 3, 6]); + expect(gd._fullData[0].marker.line.width).toEqual([4, 2, 2, 3]); + + expect(gd._fullData[1].ids).toEqual(['r', 'y', 'u']); + expect(gd._fullData[1].x).toEqual([-2, 1, 2]); + expect(gd._fullData[1].y).toEqual([2, 5, 4]); + expect(gd._fullData[1].marker.line.width).toEqual([4, 2, 3]); + + assertDims([4, 3]); +*/ + + done(); + }); + }); + + }); }); From 59b741d2433eb5b8ba432598ea672974bd027687 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 23 Sep 2016 20:37:05 +0200 Subject: [PATCH 29/34] PR feedback: moved isValObject into coerce --- src/lib/coerce.js | 7 +++++++ src/lib/index.js | 2 +- src/lib/is_val_object.js | 15 --------------- test/jasmine/tests/transform_groupby_test.js | 15 +++++++++++---- 4 files changed, 19 insertions(+), 20 deletions(-) delete mode 100644 src/lib/is_val_object.js diff --git a/src/lib/coerce.js b/src/lib/coerce.js index 2467b870d3e..06588b99b3c 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -355,3 +355,10 @@ exports.validate = function(value, opts) { valObject.coerceFunction(value, propMock, failed, opts); return out !== failed; }; + +/* + * returns true for a valid value object and false for tree nodes in the attribute hierarchy + */ +exports.isValObject = function(obj) { + return obj && obj.valType !== undefined; +}; diff --git a/src/lib/index.js b/src/lib/index.js index 463ee867d86..fbac5084b40 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -15,7 +15,6 @@ var lib = module.exports = {}; lib.nestedProperty = require('./nested_property'); lib.isPlainObject = require('./is_plain_object'); -lib.isValObject = require('./is_val_object'); lib.isArray = require('./is_array'); var coerceModule = require('./coerce'); @@ -24,6 +23,7 @@ lib.coerce = coerceModule.coerce; lib.coerce2 = coerceModule.coerce2; lib.coerceFont = coerceModule.coerceFont; lib.validate = coerceModule.validate; +lib.isValObject = coerceModule.isValObject; var datesModule = require('./dates'); lib.dateTime2ms = datesModule.dateTime2ms; diff --git a/src/lib/is_val_object.js b/src/lib/is_val_object.js deleted file mode 100644 index f98f14465d3..00000000000 --- a/src/lib/is_val_object.js +++ /dev/null @@ -1,15 +0,0 @@ -/** -* Copyright 2012-2016, 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'; - -// returns true for a valid value object and false for tree nodes in the attribute hierarchy -module.exports = function(obj) { - return obj && obj.valType !== undefined; -}; diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index 932cd381302..24a412f5ace 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -10,7 +10,7 @@ Plotly.register([ require('@src/transforms/groupby') ]); -describe('groupby', function() { +fdescribe('groupby', function() { describe('one-to-many transforms:', function() { 'use strict'; @@ -350,7 +350,7 @@ describe('groupby', function() { describe('grouping with basic, heterogenous and overridden attributes', function() { 'use strict'; - //afterEach(destroyGraphDiv); + afterEach(destroyGraphDiv); function test(mockData) { @@ -511,8 +511,15 @@ describe('groupby', function() { it('passes with lots of attributes and heterogenous attrib presence', test(mockData2)); it('passes with group styles partially overriding top level aesthetics', test(mockData3)); - expect(gd._fullData[0].marker.line.color).toEqual(['orange', 'red', 'cyan', 'pink']); - expect(gd._fullData[1].marker.line.color).toEqual('yellow'); + it('passes extended tests with group styles partially overriding top level aesthetics', function (done) { + var data = Lib.extendDeep([], mockData3); + var gd = createGraphDiv(); + Plotly.plot(gd, data).then(function() { + expect(gd._fullData[0].marker.line.color).toEqual(['orange', 'red', 'cyan', 'pink']); + expect(gd._fullData[1].marker.line.color).toEqual('yellow'); + done(); + }); + } ); it('passes with no explicit styling for the individual group', test(mockData4)); From a5dad2a466f552069ffd53b18bb509d007015135 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 23 Sep 2016 21:35:23 +0200 Subject: [PATCH 30/34] PR feedback: moved crawler and its constituent parts into coerce --- src/lib/coerce.js | 51 ++++++++++++++++- src/lib/index.js | 5 ++ src/plot_api/crawler.js | 58 -------------------- src/plot_api/plot_schema.js | 17 +++--- src/plots/plots.js | 3 +- test/jasmine/tests/transform_groupby_test.js | 10 ++-- 6 files changed, 68 insertions(+), 76 deletions(-) delete mode 100644 src/plot_api/crawler.js diff --git a/src/lib/coerce.js b/src/lib/coerce.js index 06588b99b3c..b974f323016 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -12,12 +12,17 @@ var isNumeric = require('fast-isnumeric'); var tinycolor = require('tinycolor2'); var nestedProperty = require('./nested_property'); +var isPlainObject = require('./is_plain_object'); var getColorscale = require('../components/colorscale/get_scale'); var colorscaleNames = Object.keys(require('../components/colorscale/scales')); var idRegex = /^([2-9]|[1-9][0-9]+)$/; +function isValObject(obj) { + return obj && obj.valType !== undefined; +} + exports.valObjects = { data_array: { // You can use *dflt=[] to force said array to exist though. @@ -359,6 +364,48 @@ exports.validate = function(value, opts) { /* * returns true for a valid value object and false for tree nodes in the attribute hierarchy */ -exports.isValObject = function(obj) { - return obj && obj.valType !== undefined; +exports.isValObject = isValObject; + +exports.IS_SUBPLOT_OBJ = '_isSubplotObj'; +exports.IS_LINKED_TO_ARRAY = '_isLinkedToArray'; +exports.DEPRECATED = '_deprecated'; + +// list of underscore attributes to keep in schema as is +exports.UNDERSCORE_ATTRS = [exports.IS_SUBPLOT_OBJ, exports.IS_LINKED_TO_ARRAY, exports.DEPRECATED]; + +/** + * Crawl the attribute tree, recursively calling a callback function + * + * @param {object} attrs + * The node of the attribute tree (e.g. the root) from which recursion originates + * @param {Function} callback + * A callback function with the signature: + * @callback callback + * @param {object} attr an attribute + * @param {String} attrName name string + * @param {object[]} attrs all the attributes + * @param {Number} level the recursion level, 0 at the root + * @param {Number} [specifiedLevel] + * The level in the tree, in order to let the callback function detect descend or backtrack, + * typically unsupplied (implied 0), just used by the self-recursive call. + * The necessity arises because the tree traversal is not controlled by callback return values. + * The decision to not use callback return values for controlling tree pruning arose from + * the goal of keeping the crawler backwards compatible. Observe that one of the pruning conditions + * precedes the callback call. + * + * @return {object} transformOut + * copy of transformIn that contains attribute defaults + */ +exports.crawl = function(attrs, callback, specifiedLevel) { + var level = specifiedLevel || 0; + Object.keys(attrs).forEach(function(attrName) { + var attr = attrs[attrName]; + + if(exports.UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return; + + callback(attr, attrName, attrs, level); + + if(isValObject(attr)) return; + if(isPlainObject(attr)) exports.crawl(attr, callback, level + 1); + }); }; diff --git a/src/lib/index.js b/src/lib/index.js index fbac5084b40..2cbe9af07c3 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -24,6 +24,11 @@ lib.coerce2 = coerceModule.coerce2; lib.coerceFont = coerceModule.coerceFont; lib.validate = coerceModule.validate; lib.isValObject = coerceModule.isValObject; +lib.crawl = coerceModule.crawl; +lib.IS_SUBPLOT_OBJ = coerceModule.IS_SUBPLOT_OBJ; +lib.IS_LINKED_TO_ARRAY = coerceModule.IS_LINKED_TO_ARRAY; +lib.DEPRECATED = coerceModule.DEPRECATED; +lib.UNDERSCORE_ATTRS = coerceModule.UNDERSCORE_ATTRS; var datesModule = require('./dates'); lib.dateTime2ms = datesModule.dateTime2ms; diff --git a/src/plot_api/crawler.js b/src/plot_api/crawler.js deleted file mode 100644 index 1b613b17c6a..00000000000 --- a/src/plot_api/crawler.js +++ /dev/null @@ -1,58 +0,0 @@ -/** -* Copyright 2012-2016, 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'; - -var Lib = require('../lib'); - -var crawler = module.exports = {}; - -crawler.IS_SUBPLOT_OBJ = '_isSubplotObj'; -crawler.IS_LINKED_TO_ARRAY = '_isLinkedToArray'; -crawler.DEPRECATED = '_deprecated'; - -// list of underscore attributes to keep in schema as is -crawler.UNDERSCORE_ATTRS = [crawler.IS_SUBPLOT_OBJ, crawler.IS_LINKED_TO_ARRAY, crawler.DEPRECATED]; - -/** - * Crawl the attribute tree, recursively calling a callback function - * - * @param {object} attrs - * The node of the attribute tree (e.g. the root) from which recursion originates - * @param {Function} callback - * A callback function with the signature: - * @callback callback - * @param {object} attr an attribute - * @param {String} attrName name string - * @param {object[]} attrs all the attributes - * @param {Number} level the recursion level, 0 at the root - * @param {Number} [specifiedLevel] - * The level in the tree, in order to let the callback function detect descend or backtrack, - * typically unsupplied (implied 0), just used by the self-recursive call. - * The necessity arises because the tree traversal is not controlled by callback return values. - * The decision to not use callback return values for controlling tree pruning arose from - * the goal of keeping the crawler backwards compatible. Observe that one of the pruning conditions - * precedes the callback call. - * - * @return {object} transformOut - * copy of transformIn that contains attribute defaults - */ -crawler.crawl = function(attrs, callback, specifiedLevel) { - var level = specifiedLevel || 0; - Object.keys(attrs).forEach(function(attrName) { - var attr = attrs[attrName]; - - if(crawler.UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return; - - callback(attr, attrName, attrs, level); - - if(Lib.isValObject(attr)) return; - if(Lib.isPlainObject(attr)) crawler.crawl(attr, callback, level + 1); - }); -}; diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index 8e42653038a..52f5d913b79 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -13,7 +13,6 @@ var Plotly = require('../plotly'); var Registry = require('../registry'); var Plots = require('../plots/plots'); var Lib = require('../lib'); -var crawler = require('./crawler'); // FIXME polar attribute are not part of Plotly yet var polarAreaAttrs = require('../plots/polar/area_attributes'); @@ -51,7 +50,7 @@ PlotSchema.get = function() { return plotSchema; }; -PlotSchema.crawl = crawler.crawl; +PlotSchema.crawl = Lib.crawl; PlotSchema.isValObject = Lib.isValObject; @@ -140,7 +139,7 @@ function getTransformAttributes(name) { function getDefs() { plotSchema.defs = { valObjects: Lib.valObjects, - metaKeys: crawler.UNDERSCORE_ATTRS.concat(['description', 'role']) + metaKeys: Lib.UNDERSCORE_ATTRS.concat(['description', 'role']) }; } @@ -223,7 +222,7 @@ function mergeValTypeAndRole(attrs) { } } - crawler.crawl(attrs, callback); + Lib.crawl(attrs, callback); } // helper methods @@ -249,7 +248,7 @@ function getModule(arg) { function removeUnderscoreAttrs(attributes) { Object.keys(attributes).forEach(function(k) { if(k.charAt(0) === '_' && - crawler.UNDERSCORE_ATTRS.indexOf(k) === -1) delete attributes[k]; + Lib.UNDERSCORE_ATTRS.indexOf(k) === -1) delete attributes[k]; }); return attributes; } @@ -303,7 +302,7 @@ function handleSubplotObjs(layoutAttributes) { isSubplotObj = subplotRegistry.attrRegex.test(k); } - if(isSubplotObj) layoutAttributes[k][crawler.IS_SUBPLOT_OBJ] = true; + if(isSubplotObj) layoutAttributes[k][Lib.IS_SUBPLOT_OBJ] = true; }); }); @@ -313,17 +312,17 @@ function handleSubplotObjs(layoutAttributes) { function handleLinkedToArray(layoutAttributes) { function callback(attr, attrName, attrs) { - if(attr[crawler.IS_LINKED_TO_ARRAY] !== true) return; + if(attr[Lib.IS_LINKED_TO_ARRAY] !== true) return; // TODO more robust logic var itemName = attrName.substr(0, attrName.length - 1); - delete attr[crawler.IS_LINKED_TO_ARRAY]; + delete attr[Lib.IS_LINKED_TO_ARRAY]; attrs[attrName] = { items: {} }; attrs[attrName].items[itemName] = attr; attrs[attrName].role = 'object'; } - crawler.crawl(layoutAttributes, callback); + Lib.crawl(layoutAttributes, callback); } diff --git a/src/plots/plots.js b/src/plots/plots.js index 1e026703712..696a0742249 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -9,7 +9,6 @@ 'use strict'; -var crawler = require('./../plot_api/crawler'); var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); @@ -815,7 +814,7 @@ function applyTransforms(fullTrace, fullData, layout) { } } - crawler.crawl(trace._module.attributes, callback); + Lib.crawl(trace._module.attributes, callback); return arraySplitAttributes; }); diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index 24a412f5ace..23cc8af809d 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -10,7 +10,7 @@ Plotly.register([ require('@src/transforms/groupby') ]); -fdescribe('groupby', function() { +describe('groupby', function() { describe('one-to-many transforms:', function() { 'use strict'; @@ -511,7 +511,7 @@ fdescribe('groupby', function() { it('passes with lots of attributes and heterogenous attrib presence', test(mockData2)); it('passes with group styles partially overriding top level aesthetics', test(mockData3)); - it('passes extended tests with group styles partially overriding top level aesthetics', function (done) { + it('passes extended tests with group styles partially overriding top level aesthetics', function(done) { var data = Lib.extendDeep([], mockData3); var gd = createGraphDiv(); Plotly.plot(gd, data).then(function() { @@ -519,7 +519,7 @@ fdescribe('groupby', function() { expect(gd._fullData[1].marker.line.color).toEqual('yellow'); done(); }); - } ); + }); it('passes with no explicit styling for the individual group', test(mockData4)); @@ -542,12 +542,12 @@ fdescribe('groupby', function() { marker: {size: 20, line: {width: [4, 2, 4, 2, 2, 3, 3]}}, transforms: [{ type: 'groupby', - //groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], + // groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } }] }]; - it("passes", function(done) { + it('passes', function(done) { var data = Lib.extendDeep([], mockData); var gd = createGraphDiv(); From 79dcbc33258995044e4e1d504e0664ac07420ea4 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 23 Sep 2016 21:44:36 +0200 Subject: [PATCH 31/34] Minor: var rename --- src/transforms/groupby.js | 6 +++--- test/jasmine/tests/transform_groupby_test.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index 0e210ea5e24..b0c132bf6a8 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -112,7 +112,7 @@ function transformOne(trace, state, attributeSet) { var style = opts.style || {}; - var topLevelAttributes = attributeSet + var arrayAttributes = attributeSet .filter(function(array) {return Array.isArray(getDeepProp(trace, array));}); var initializeArray = function(newTrace, a) { @@ -131,12 +131,12 @@ function transformOne(trace, state, attributeSet) { // maybe we could abstract this out var newTrace = newData[i] = Lib.extendDeep({}, trace); - topLevelAttributes.forEach(initializeArray.bind(null, newTrace)); + arrayAttributes.forEach(initializeArray.bind(null, newTrace)); for(var j = 0; j < len; j++) { if(groups[j] !== groupName) continue; - topLevelAttributes.forEach(pasteArray.bind(0, newTrace, trace, j)); + arrayAttributes.forEach(pasteArray.bind(0, newTrace, trace, j)); } newTrace.name = groupName; diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index 23cc8af809d..cb7f6bc1f23 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -532,7 +532,7 @@ describe('groupby', function() { describe('passes with no `groups`', function() { 'use strict'; - afterEach(destroyGraphDiv); + //afterEach(destroyGraphDiv); var mockData = [{ mode: 'markers+lines', @@ -547,7 +547,7 @@ describe('groupby', function() { }] }]; - it('passes', function(done) { + fit('passes', function(done) { var data = Lib.extendDeep([], mockData); var gd = createGraphDiv(); From aa70e4c2235c8bda28269deb07e35106a21c1840 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 23 Sep 2016 21:58:26 +0200 Subject: [PATCH 32/34] PR feedback: don't split if `groups` is unsupplied, non-array or of zero length --- src/transforms/groupby.js | 68 +++++------ test/jasmine/tests/transform_groupby_test.js | 115 ++++++++++--------- 2 files changed, 96 insertions(+), 87 deletions(-) diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index b0c132bf6a8..35695f33d6f 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -98,6 +98,38 @@ exports.transform = function(data, state) { return newData; }; +function getDeepProp(thing, propArray) { + var result = thing; + var i; + for(i = 0; i < propArray.length; i++) { + result = result[propArray[i]]; + if(result === void(0)) { + return result; + } + } + return result; +} + +function setDeepProp(thing, propArray, value) { + var current = thing; + var i; + for(i = 0; i < propArray.length - 1; i++) { + if(current[propArray[i]] === void(0)) { + current[propArray[i]] = {}; + } + current = current[propArray[i]]; + } + current[propArray[propArray.length - 1]] = value; +} + +function initializeArray(newTrace, a) { + setDeepProp(newTrace, a, []); +} + +function pasteArray(newTrace, trace, j, a) { + getDeepProp(newTrace, a).push(getDeepProp(trace, a)[j]); +} + function transformOne(trace, state, attributeSet) { var opts = state.transform; @@ -107,6 +139,10 @@ function transformOne(trace, state, attributeSet) { return self.indexOf(g) === i; }); + if(!(Array.isArray(groups)) || groups.length === 0) { + return trace; + } + var newData = new Array(groupNames.length); var len = groups.length; @@ -115,14 +151,6 @@ function transformOne(trace, state, attributeSet) { var arrayAttributes = attributeSet .filter(function(array) {return Array.isArray(getDeepProp(trace, array));}); - var initializeArray = function(newTrace, a) { - setDeepProp(newTrace, a, []); - }; - - var pasteArray = function(newTrace, trace, j, a) { - getDeepProp(newTrace, a).push(getDeepProp(trace, a)[j]); - }; - // fixme the O(n**3) complexity for(var i = 0; i < groupNames.length; i++) { var groupName = groupNames[i]; @@ -148,27 +176,3 @@ function transformOne(trace, state, attributeSet) { return newData; } - -function getDeepProp(thing, propArray) { - var result = thing; - var i; - for(i = 0; i < propArray.length; i++) { - result = result[propArray[i]]; - if(result === void(0)) { - return result; - } - } - return result; -} - -function setDeepProp(thing, propArray, value) { - var current = thing; - var i; - for(i = 0; i < propArray.length - 1; i++) { - if(current[propArray[i]] === void(0)) { - current[propArray[i]] = {}; - } - current = current[propArray[i]]; - } - current[propArray[propArray.length - 1]] = value; -} diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index cb7f6bc1f23..1b145cc6fa2 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -271,14 +271,8 @@ describe('groupby', function() { expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - expect(gd._fullData.length).toEqual(0); // fixme: it passes with 0; shouldn't it be 1? (one implied group) - - /* since the array is of zero length, the below items are obv. meaningless to test - expect(gd._fullData[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); - expect(gd._fullData[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - */ - - assertDims([]); // fixme: same thing, looks like zero dimensionality + expect(gd._fullData.length).toEqual(1); + assertDims([7]); done(); }); @@ -493,19 +487,6 @@ describe('groupby', function() { }] }]; - var mockData6 = [{ - mode: 'markers+lines', - ids: ['q', 'w', 'r', 't', 'y', 'u', 'i'], - x: [1, -1, -2, 0, 1, 2, 3], - y: [0, 1, 2, 3, 5, 4, 6], - marker: {line: {width: [4, 2, 4, 2, 2, 3, 3]}}, - transforms: [{ - type: 'groupby', - groups: ['a', 'a', 'b', 'a', 'b', 'b', 'a'], - style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } - }] - }]; - it('`data` preserves user supplied input but `gd._fullData` reflects the grouping', test(mockData1)); it('passes with lots of attributes and heterogenous attrib presence', test(mockData2)); @@ -525,16 +506,43 @@ describe('groupby', function() { it('passes with no explicit styling in the group transform at all', test(mockData5)); - it('passes with no explicit styling in the group transform at all', test(mockData6)); - }); describe('passes with no `groups`', function() { 'use strict'; - //afterEach(destroyGraphDiv); + afterEach(destroyGraphDiv); - var mockData = [{ + function test(mockData) { + + return function(done) { + var data = Lib.extendDeep([], mockData); + + var gd = createGraphDiv(); + + Plotly.plot(gd, data).then(function() { + + expect(gd.data.length).toEqual(1); + expect(gd.data[0].ids).toEqual(['q', 'w', 'r', 't', 'y', 'u', 'i']); + expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd.data[0].y).toEqual([0, 1, 2, 3, 5, 4, 6]); + expect(gd.data[0].marker.line.width).toEqual([4, 2, 4, 2, 2, 3, 3]); + + expect(gd._fullData.length).toEqual(1); + + expect(gd._fullData[0].ids).toEqual(['q', 'w', 'r', 't', 'y', 'u', 'i']); + expect(gd._fullData[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); + expect(gd._fullData[0].y).toEqual([0, 1, 2, 3, 5, 4, 6]); + expect(gd._fullData[0].marker.line.width).toEqual([4, 2, 4, 2, 2, 3, 3]); + + assertDims([7]); + + done(); + }); + }; + } + + var mockData0 = [{ mode: 'markers+lines', ids: ['q', 'w', 'r', 't', 'y', 'u', 'i'], x: [1, -1, -2, 0, 1, 2, 3], @@ -547,38 +555,35 @@ describe('groupby', function() { }] }]; - fit('passes', function(done) { - var data = Lib.extendDeep([], mockData); - - var gd = createGraphDiv(); - - Plotly.plot(gd, data).then(function() { - -/* - expect(gd.data.length).toEqual(1); - expect(gd.data[0].ids).toEqual(['q', 'w', 'r', 't', 'y', 'u', 'i']); - expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); - expect(gd.data[0].y).toEqual([0, 1, 2, 3, 5, 4, 6]); - expect(gd.data[0].marker.line.width).toEqual([4, 2, 4, 2, 2, 3, 3]); - - expect(gd._fullData.length).toEqual(2); - - expect(gd._fullData[0].ids).toEqual(['q', 'w', 't', 'i']); - expect(gd._fullData[0].x).toEqual([1, -1, 0, 3]); - expect(gd._fullData[0].y).toEqual([0, 1, 3, 6]); - expect(gd._fullData[0].marker.line.width).toEqual([4, 2, 2, 3]); - - expect(gd._fullData[1].ids).toEqual(['r', 'y', 'u']); - expect(gd._fullData[1].x).toEqual([-2, 1, 2]); - expect(gd._fullData[1].y).toEqual([2, 5, 4]); - expect(gd._fullData[1].marker.line.width).toEqual([4, 2, 3]); + var mockData1 = [{ + mode: 'markers+lines', + ids: ['q', 'w', 'r', 't', 'y', 'u', 'i'], + x: [1, -1, -2, 0, 1, 2, 3], + y: [0, 1, 2, 3, 5, 4, 6], + marker: {size: 20, line: {width: [4, 2, 4, 2, 2, 3, 3]}}, + transforms: [{ + type: 'groupby', + groups: [], + style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } + }] + }]; - assertDims([4, 3]); -*/ + var mockData2 = [{ + mode: 'markers+lines', + ids: ['q', 'w', 'r', 't', 'y', 'u', 'i'], + x: [1, -1, -2, 0, 1, 2, 3], + y: [0, 1, 2, 3, 5, 4, 6], + marker: {size: 20, line: {width: [4, 2, 4, 2, 2, 3, 3]}}, + transforms: [{ + type: 'groupby', + groups: null, + style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } + }] + }]; - done(); - }); - }); + it('passes with no groups', test(mockData0)); + it('passes with empty groups', test(mockData1)); + it('passes with falsey groups', test(mockData2)); }); }); From 523f61b564661d70050f23bb4327ceca77ea6602 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Fri, 23 Sep 2016 23:06:19 +0200 Subject: [PATCH 33/34] PR feedback: disuse in favor of Lib.nestedProperty getter/setter --- src/plots/plots.js | 4 +++- src/transforms/groupby.js | 34 +++++++--------------------------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 696a0742249..195981f6185 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -816,7 +816,9 @@ function applyTransforms(fullTrace, fullData, layout) { Lib.crawl(trace._module.attributes, callback); - return arraySplitAttributes; + return arraySplitAttributes.map(function(path) { + return path.join('.'); + }); }); for(var i = 0; i < container.length; i++) { diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index 35695f33d6f..c50ee686f7b 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -98,36 +98,16 @@ exports.transform = function(data, state) { return newData; }; -function getDeepProp(thing, propArray) { - var result = thing; - var i; - for(i = 0; i < propArray.length; i++) { - result = result[propArray[i]]; - if(result === void(0)) { - return result; - } - } - return result; -} - -function setDeepProp(thing, propArray, value) { - var current = thing; - var i; - for(i = 0; i < propArray.length - 1; i++) { - if(current[propArray[i]] === void(0)) { - current[propArray[i]] = {}; - } - current = current[propArray[i]]; - } - current[propArray[propArray.length - 1]] = value; -} - function initializeArray(newTrace, a) { - setDeepProp(newTrace, a, []); + Lib.nestedProperty(newTrace, a).set([]); } function pasteArray(newTrace, trace, j, a) { - getDeepProp(newTrace, a).push(getDeepProp(trace, a)[j]); + Lib.nestedProperty(newTrace, a).set( + Lib.nestedProperty(newTrace, a).get().concat([ + Lib.nestedProperty(trace, a).get()[j] + ]) + ); } function transformOne(trace, state, attributeSet) { @@ -149,7 +129,7 @@ function transformOne(trace, state, attributeSet) { var style = opts.style || {}; var arrayAttributes = attributeSet - .filter(function(array) {return Array.isArray(getDeepProp(trace, array));}); + .filter(function(array) {return Array.isArray(Lib.nestedProperty(trace, array).get());}); // fixme the O(n**3) complexity for(var i = 0; i < groupNames.length; i++) { From 4efe6a903fd8c0f1723d33da35fa96c1ee98e635 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Mon, 26 Sep 2016 18:25:54 +0200 Subject: [PATCH 34/34] PR feedback: pruning in-progress comments --- src/transforms/groupby.js | 1 - test/jasmine/tests/transform_groupby_test.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index c50ee686f7b..a2a9e97816b 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -131,7 +131,6 @@ function transformOne(trace, state, attributeSet) { var arrayAttributes = attributeSet .filter(function(array) {return Array.isArray(Lib.nestedProperty(trace, array).get());}); - // fixme the O(n**3) complexity for(var i = 0; i < groupNames.length; i++) { var groupName = groupNames[i]; diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js index 1b145cc6fa2..d37ef142d25 100644 --- a/test/jasmine/tests/transform_groupby_test.js +++ b/test/jasmine/tests/transform_groupby_test.js @@ -288,7 +288,7 @@ describe('groupby', function() { expect(gd.data[0].x).toEqual([1, -1, -2, 0, 1, 2, 3]); expect(gd.data[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]); - expect(gd._fullData.length).toEqual(1); // fixme not: good, okay it's 1 here (one implied group / thing) + expect(gd._fullData.length).toEqual(1); expect(gd._fullData[0].x).toEqual([ 1, -1, -2, 0, 1, 2, 3 ]); expect(gd._fullData[0].y).toEqual([1, 2, 3, 1, 2, 3, 1]);