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/lib/coerce.js b/src/lib/coerce.js index 2467b870d3e..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. @@ -355,3 +360,52 @@ 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 = 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 cb1cbd88ac8..2cbe9af07c3 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -23,6 +23,12 @@ lib.coerce = coerceModule.coerce; 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/plot_schema.js b/src/plot_api/plot_schema.js index 7a6ca5030b1..52f5d913b79 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -23,13 +23,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,22 +50,9 @@ PlotSchema.get = function() { return plotSchema; }; -PlotSchema.crawl = function(attrs, callback) { - Object.keys(attrs).forEach(function(attrName) { - var attr = attrs[attrName]; - - if(UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return; +PlotSchema.crawl = Lib.crawl; - callback(attr, attrName, attrs); - - if(PlotSchema.isValObject(attr)) return; - if(Lib.isPlainObject(attr)) PlotSchema.crawl(attr, callback); - }); -}; - -PlotSchema.isValObject = function(obj) { - return obj && obj.valType !== undefined; -}; +PlotSchema.isValObject = Lib.isValObject; function getTraceAttributes(type) { var globalAttributes = Plots.attributes, @@ -131,13 +112,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 }; @@ -158,7 +139,7 @@ function getTransformAttributes(name) { function getDefs() { plotSchema.defs = { valObjects: Lib.valObjects, - metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role']) + metaKeys: Lib.UNDERSCORE_ATTRS.concat(['description', 'role']) }; } @@ -241,7 +222,7 @@ function mergeValTypeAndRole(attrs) { } } - PlotSchema.crawl(attrs, callback); + Lib.crawl(attrs, callback); } // helper methods @@ -267,7 +248,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]; + Lib.UNDERSCORE_ATTRS.indexOf(k) === -1) delete attributes[k]; }); return attributes; } @@ -321,7 +302,7 @@ function handleSubplotObjs(layoutAttributes) { isSubplotObj = subplotRegistry.attrRegex.test(k); } - if(isSubplotObj) layoutAttributes[k][IS_SUBPLOT_OBJ] = true; + if(isSubplotObj) layoutAttributes[k][Lib.IS_SUBPLOT_OBJ] = true; }); }); @@ -331,17 +312,17 @@ function handleSubplotObjs(layoutAttributes) { function handleLinkedToArray(layoutAttributes) { function callback(attr, attrName, attrs) { - if(attr[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[IS_LINKED_TO_ARRAY]; + delete attr[Lib.IS_LINKED_TO_ARRAY]; attrs[attrName] = { items: {} }; attrs[attrName].items[itemName] = attr; attrs[attrName].role = 'object'; } - PlotSchema.crawl(layoutAttributes, callback); + Lib.crawl(layoutAttributes, callback); } diff --git a/src/plots/plots.js b/src/plots/plots.js index b4ffa470551..195981f6185 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -786,6 +786,41 @@ function applyTransforms(fullTrace, fullData, layout) { var container = fullTrace.transforms, dataOut = [fullTrace]; + var attributeSets = dataOut.map(function(trace) { + + var arraySplitAttributes = []; + + 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]); + + var splittableAttr = attr.valType === 'data_array' || attr.arrayOk === true; + if(splittableAttr) { + arraySplitAttributes.push(stack.slice()); + } + } + + Lib.crawl(trace._module.attributes, callback); + + return arraySplitAttributes.map(function(path) { + return path.join('.'); + }); + }); + for(var i = 0; i < container.length; i++) { var transform = container[i], type = transform.type, @@ -796,7 +831,9 @@ function applyTransforms(fullTrace, fullData, layout) { transform: transform, fullTrace: fullTrace, fullData: fullData, - layout: layout + attributeSets: attributeSets, + layout: layout, + transformIndex: i }); } } diff --git a/test/jasmine/assets/transforms/filter.js b/src/transforms/filter.js similarity index 63% rename from test/jasmine/assets/transforms/filter.js rename to src/transforms/filter.js index 8ac7e9b1f27..9bbe2700f57 100644 --- a/test/jasmine/assets/transforms/filter.js +++ b/src/transforms/filter.js @@ -1,11 +1,20 @@ +/** +* 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'); + // 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,17 +25,21 @@ exports.name = 'filter'; exports.attributes = { operation: { valType: 'enumerated', - values: ['=', '<', '>'], + values: ['=', '<', '>', 'within', 'notwithin', 'in', 'notin'], dflt: '=' }, value: { - valType: 'number', + valType: 'any', dflt: 0 }, filtersrc: { valType: 'enumerated', - values: ['x', 'y'], - dflt: 'x' + values: ['x', 'y', 'ids'], + dflt: 'x', + ids: { + valType: 'data_array', + description: 'A list of keys for object constancy of data points during animation' + } } }; @@ -54,6 +67,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; @@ -121,6 +144,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 valueArr = []; + if(!Array.isArray(value)) { + valueArr = [value, value]; + } else { + valueArr = value; + } switch(opts.operation) { case '=': @@ -129,6 +162,30 @@ function getFilterFunc(opts) { return function(v) { return v < value; }; case '>': 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) || Lib.isDateTime(d)); + } + if(valueArr.some(notDateNumber)) { + return true; + } + + // keep the = ? + 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)); + }; + case 'in': + return function(v) { return valueArr.indexOf(v) >= 0; }; + case 'notin': + return function(v) { return valueArr.indexOf(v) === -1; }; } } diff --git a/test/jasmine/assets/transforms/groupby.js b/src/transforms/groupby.js similarity index 63% rename from test/jasmine/assets/transforms/groupby.js rename to src/transforms/groupby.js index 50a3b9f0949..a2a9e97816b 100644 --- a/test/jasmine/assets/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -1,7 +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'; // var Lib = require('@src/lib'); -var Lib = require('../../../../src/lib'); +var Lib = require('../lib'); /* eslint no-unused-vars: 0*/ @@ -22,7 +30,7 @@ exports.attributes = { valType: 'data_array', dflt: [] }, - groupColors: { + style: { valType: 'any', dflt: {} } @@ -53,7 +61,7 @@ exports.supplyDefaults = function(transformIn, fullData, layout) { if(!active) return transformOut; coerce('groups'); - coerce('groupColors'); + coerce('style'); // or some more complex logic using fullData and layout @@ -82,23 +90,46 @@ exports.transform = function(data, state) { var newData = []; - data.forEach(function(trace) { - newData = newData.concat(transformOne(trace, state)); + data.forEach(function(trace, i) { + + newData = newData.concat(transformOne(trace, state, state.attributeSets[i])); }); return newData; }; -function transformOne(trace, state) { +function initializeArray(newTrace, a) { + Lib.nestedProperty(newTrace, a).set([]); +} + +function pasteArray(newTrace, trace, j, a) { + Lib.nestedProperty(newTrace, a).set( + Lib.nestedProperty(newTrace, a).get().concat([ + Lib.nestedProperty(trace, a).get()[j] + ]) + ); +} + +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; }); + if(!(Array.isArray(groups)) || groups.length === 0) { + return trace; + } + 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 arrayAttributes = attributeSet + .filter(function(array) {return Array.isArray(Lib.nestedProperty(trace, array).get());}); for(var i = 0; i < groupNames.length; i++) { var groupName = groupNames[i]; @@ -107,18 +138,19 @@ function transformOne(trace, state) { // maybe we could abstract this out var newTrace = newData[i] = Lib.extendDeep({}, trace); - newTrace.x = []; - newTrace.y = []; + arrayAttributes.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]); + arrayAttributes.forEach(pasteArray.bind(0, newTrace, trace, j)); } newTrace.name = groupName; - newTrace.marker.color = opts.groupColors[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; 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/plotschema_test.js b/test/jasmine/tests/plotschema_test.js index 17195fc67e2..ffe72a93ccc 100644 --- a/test/jasmine/tests/plotschema_test.js +++ b/test/jasmine/tests/plotschema_test.js @@ -2,8 +2,8 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); Plotly.register([ - // until they become official - require('../assets/transforms/filter') + require('@src/transforms/filter'), + require('@src/transforms/groupby') ]); describe('plot schema', function() { diff --git a/test/jasmine/tests/transform_filter_test.js b/test/jasmine/tests/transform_filter_test.js new file mode 100644 index 00000000000..4e7bed11757 --- /dev/null +++ b/test/jasmine/tests/transform_filter_test.js @@ -0,0 +1,770 @@ +var Plotly = require('@lib/index'); +var Plots = require('@src/plots/plots'); +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') +]); + +describe('one-to-one transforms:', function() { + 'use strict'; + + var mockData0 = [{ + x: [-2, -1, -2, 0, 1, 2, 3], + y: [1, 2, 3, 1, 2, 3, 1], + transforms: [{ + type: 'filter', + operation: '>' + }] + }]; + + var mockData1 = [Lib.extendDeep({}, mockData0[0]), { + x: [20, 11, 12, 0, 1, 2, 3], + y: [1, 2, 3, 2, 5, 2, 0], + transforms: [{ + type: 'filter', + operation: '<', + value: 10 + }] + }]; + + var traceIn, traceOut; + + afterEach(destroyGraphDiv); + + it('supplyTraceDefaults should supply the transform defaults', function() { + traceIn = { + y: [2, 1, 2], + transforms: [{ type: 'filter' }] + }; + + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + + expect(traceOut.transforms).toEqual([{ + type: 'filter', + operation: '=', + value: 0, + filtersrc: 'x' + }]); + }); + + it('supplyTraceDefaults should not bail if transform module is not found', function() { + traceIn = { + y: [2, 1, 2], + transforms: [{ type: 'invalid' }] + }; + + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + + expect(traceOut.y).toBe(traceIn.y); + }); + + it('supplyTraceDefaults should honored global transforms', function() { + traceIn = { + y: [2, 1, 2], + transforms: [{ + type: 'filter', + operation: '>', + value: '0', + filtersrc: 'x' + }] + }; + + var layout = { + _globalTransforms: [{ + type: 'filter' + }] + }; + + traceOut = Plots.supplyTraceDefaults(traceIn, 0, layout); + + expect(traceOut.transforms[0]).toEqual({ + type: 'filter', + operation: '=', + value: 0, + filtersrc: 'x' + }, '- global first'); + + expect(traceOut.transforms[1]).toEqual({ + type: 'filter', + operation: '>', + value: 0, + filtersrc: 'x' + }, '- trace second'); + }); + + it('should pass correctly arguments to transform methods', function() { + var transformIn = { type: 'fake' }; + var transformOut = {}; + + var dataIn = [{ + transforms: [transformIn] + }]; + + var layout = {}; + + function assertSupplyDefaultsArgs(_transformIn, traceOut, _layout) { + expect(_transformIn).toBe(transformIn); + expect(_layout).toBe(layout); + + return transformOut; + } + + function assertTransformArgs(dataOut, opts) { + expect(dataOut[0]._input).toBe(dataIn[0]); + expect(opts.transform).toBe(transformOut); + expect(opts.fullTrace._input).toBe(dataIn[0]); + expect(opts.layout).toBe(layout); + + return dataOut; + } + + var fakeTransformModule = { + moduleType: 'transform', + name: 'fake', + attributes: {}, + supplyDefaults: assertSupplyDefaultsArgs, + transform: assertTransformArgs + }; + + Plotly.register(fakeTransformModule); + Plots.supplyDataDefaults(dataIn, [], layout); + delete Plots.transformsRegistry.fake; + }); + + it('supplyDataDefaults should apply the transform while', function() { + var dataIn = [{ + x: [-2, -2, 1, 2, 3], + y: [1, 2, 2, 3, 1] + }, { + 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, {}, []); + + var msg; + + msg = 'does not mutate user data'; + expect(dataIn[1].x).toEqual([-2, -1, -2, 0, 1, 2, 3], msg); + expect(dataIn[1].y).toEqual([1, 2, 3, 1, 2, 3, 1], msg); + expect(dataIn[1].transforms).toEqual([{ + type: 'filter', + operation: '>', + value: '0', + filtersrc: 'x' + }], msg); + + msg = 'applies transform'; + expect(dataOut[1].x).toEqual([1, 2, 3], msg); + expect(dataOut[1].y).toEqual([2, 3, 1], msg); + + msg = 'supplying the transform defaults'; + expect(dataOut[1].transforms[0]).toEqual({ + type: 'filter', + operation: '>', + value: 0, + filtersrc: 'x' + }, msg); + + msg = 'keeping refs to user data'; + expect(dataOut[1]._input.x).toEqual([-2, -1, -2, 0, 1, 2, 3], msg); + expect(dataOut[1]._input.y).toEqual([1, 2, 3, 1, 2, 3, 1], msg); + expect(dataOut[1]._input.transforms).toEqual([{ + type: 'filter', + operation: '>', + value: '0', + filtersrc: 'x' + }], msg); + + msg = 'keeping refs to full transforms array'; + expect(dataOut[1]._fullInput.transforms).toEqual([{ + type: 'filter', + operation: '>', + value: 0, + filtersrc: 'x' + }], msg); + + msg = 'setting index w.r.t user data'; + expect(dataOut[0].index).toEqual(0, msg); + expect(dataOut[1].index).toEqual(1, msg); + + msg = 'setting _expandedIndex w.r.t full data'; + expect(dataOut[0]._expandedIndex).toEqual(0, msg); + expect(dataOut[1]._expandedIndex).toEqual(1, msg); + }); + + 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]; + + var uid; + function assertUid(gd) { + expect(gd._fullData[0].uid) + .toEqual(uid + '0', 'should preserve uid on restyle'); + } + + Plotly.plot(gd, data).then(function() { + uid = gd.data[0].uid; + + expect(gd._fullData[0].marker.color).toEqual('red'); + assertUid(gd); + assertStyle(dims, ['rgb(255, 0, 0)'], [1]); + + return Plotly.restyle(gd, 'marker.color', 'blue'); + }).then(function() { + expect(gd._fullData[0].marker.color).toEqual('blue'); + assertUid(gd); + assertStyle(dims, ['rgb(0, 0, 255)'], [1]); + + return Plotly.restyle(gd, 'marker.color', 'red'); + }).then(function() { + expect(gd._fullData[0].marker.color).toEqual('red'); + assertUid(gd); + assertStyle(dims, ['rgb(255, 0, 0)'], [1]); + + return Plotly.restyle(gd, 'transforms[0].value', 2.5); + }).then(function() { + assertUid(gd); + 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(); + }); + }); + + + it('supplyTraceDefaults should supply the transform defaults', function() { + traceIn = { + y: [2, 1, 2], + transforms: [{ type: 'filter' }] + }; + + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + + expect(traceOut.transforms).toEqual([{ + type: 'filter', + operation: '=', + value: 0, + filtersrc: 'x' + }]); + }); + + it('supplyTraceDefaults should accept numeric as character', function() { + traceIn = { + x: '1', + transforms: [{ + type: 'filter', + value: '0' + }] + }; + + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + + expect(traceOut.transforms).toEqual([{ + type: 'filter', + operation: '=', + value: 0, + filtersrc: 'x' + }]); + + // should also convert if array + traceIn = { + x: '1', + transforms: [{ + type: 'filter', + value: ['0'] + }] + }; + + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + + expect(traceOut.transforms).toEqual([{ + type: 'filter', + operation: '=', + value: [0], + filtersrc: 'x' + }]); + }); + + it('supplyTraceDefaults should accept numeric as character', function() { + traceIn = { + x: '1', + transforms: [{ + type: 'filter', + value: '0' + }] + }; + + traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); + + expect(traceOut.transforms).toEqual([{ + type: 'filter', + operation: '=', + value: 0, + filtersrc: 'x' + }]); + + // should also convert if array + traceIn = { + x: '1', + transforms: [{ + type: 'filter', + value: ['0'] + }] + }; + + 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(); + }); + }); + +}); diff --git a/test/jasmine/tests/transform_groupby_test.js b/test/jasmine/tests/transform_groupby_test.js new file mode 100644 index 00000000000..d37ef142d25 --- /dev/null +++ b/test/jasmine/tests/transform_groupby_test.js @@ -0,0 +1,589 @@ +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('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 + }); + }).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(); + }); + }); + + }); + + // 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(); + }); + }); + + 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(1); + assertDims([7]); + + done(); + }); + }); + + 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); + 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(); + }); + }); + + 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); + + 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(); + }); + }); + + 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); + + 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(); + }); + }); + }); + + 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].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(); + }); + }; + } + + // 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'], + style: { a: {marker: {color: 'red'}}, b: {marker: {color: 'blue'}} } + }] + }]; + + // 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'], + style: { + a: { + marker: { + color: 'orange', + size: 20, + line: { + color: 'red' + } + } + }, + b: { + mode: 'markers+lines', // heterogeonos attributes are OK: group 'a' doesn't need to define this + marker: { + color: 'cyan', + size: 15, + line: { + color: 'purple' + }, + opacity: 0.5, + symbol: 'triangle-up' + }, + line: { + color: 'purple' + } + } + } + }] + }]; + + // 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 + line: { + width: [4, 2, 4, 2, 2, 3, 3], + color: ['orange', 'red', 'green', 'cyan', 'magenta', 'blue', 'pink'] + } + }, + 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, line: {color: 'yellow'}}, line: {color: 'purple'}} + } + }] + }]; + + 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'], + style: {/* can be empty, or of partial group id coverage */} + }] + }]; + + 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'] + }] + }]; + + 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 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)); + + it('passes with no explicit styling in the group transform at all', test(mockData5)); + + }); + + describe('passes with no `groups`', 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].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], + 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'}} } + }] + }]; + + 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'}} } + }] + }]; + + 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'}} } + }] + }]; + + it('passes with no groups', test(mockData0)); + it('passes with empty groups', test(mockData1)); + it('passes with falsey groups', test(mockData2)); + + }); +}); diff --git a/test/jasmine/tests/transform_multi_test.js b/test/jasmine/tests/transform_multi_test.js new file mode 100644 index 00000000000..ae1b7abd94b --- /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'], + style: { a: {marker: {color: 'red'}}, b: {marker: {color: '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'], + style: { a: {marker: {color: 'green'}}, b: {marker: {color: '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.slice().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, 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.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].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(); + }); + }); + + 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'], + style: { a: {marker: {color: 'red'}}, b: {marker: {color: '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].style': { a: {marker: {color: 'green'}}, b: {marker: {color: '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(); + }); + }); + +}); diff --git a/test/jasmine/tests/transforms_test.js b/test/jasmine/tests/transforms_test.js deleted file mode 100644 index 1344518c8d7..00000000000 --- a/test/jasmine/tests/transforms_test.js +++ /dev/null @@ -1,1014 +0,0 @@ -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'); - -Plotly.register([ - require('../assets/transforms/filter'), - require('../assets/transforms/groupby') -]); - - -describe('one-to-one transforms:', function() { - 'use strict'; - - var mockData0 = [{ - x: [-2, -1, -2, 0, 1, 2, 3], - y: [1, 2, 3, 1, 2, 3, 1], - transforms: [{ - type: 'filter', - operation: '>' - }] - }]; - - var mockData1 = [Lib.extendDeep({}, mockData0[0]), { - x: [20, 11, 12, 0, 1, 2, 3], - y: [1, 2, 3, 2, 5, 2, 0], - transforms: [{ - type: 'filter', - operation: '<', - value: 10 - }] - }]; - - afterEach(destroyGraphDiv); - - 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 not bail if transform module is not found', function() { - var traceIn = { - y: [2, 1, 2], - transforms: [{ type: 'invalid' }] - }; - - var traceOut = Plots.supplyTraceDefaults(traceIn, 0, {}); - - expect(traceOut.y).toBe(traceIn.y); - }); - - it('supplyTraceDefaults should honored global transforms', function() { - var traceIn = { - y: [2, 1, 2], - transforms: [{ - type: 'filter', - operation: '>', - value: '0', - filtersrc: 'x' - }] - }; - - var layout = { - _globalTransforms: [{ - type: 'filter' - }] - }; - - var traceOut = Plots.supplyTraceDefaults(traceIn, 0, layout); - - expect(traceOut.transforms[0]).toEqual({ - type: 'filter', - operation: '=', - value: 0, - filtersrc: 'x' - }, '- global first'); - - expect(traceOut.transforms[1]).toEqual({ - type: 'filter', - operation: '>', - value: 0, - filtersrc: 'x' - }, '- trace second'); - }); - - it('should pass correctly arguments to transform methods', function() { - var transformIn = { type: 'fake' }; - var transformOut = {}; - - var dataIn = [{ - transforms: [transformIn] - }]; - - var layout = {}; - - function assertSupplyDefaultsArgs(_transformIn, traceOut, _layout) { - expect(_transformIn).toBe(transformIn); - expect(_layout).toBe(layout); - - return transformOut; - } - - function assertTransformArgs(dataOut, opts) { - expect(dataOut[0]._input).toBe(dataIn[0]); - expect(opts.transform).toBe(transformOut); - expect(opts.fullTrace._input).toBe(dataIn[0]); - expect(opts.layout).toBe(layout); - - return dataOut; - } - - var fakeTransformModule = { - moduleType: 'transform', - name: 'fake', - attributes: {}, - supplyDefaults: assertSupplyDefaultsArgs, - transform: assertTransformArgs - }; - - Plotly.register(fakeTransformModule); - Plots.supplyDataDefaults(dataIn, [], layout); - delete Plots.transformsRegistry.fake; - }); - - it('supplyDataDefaults should apply the transform while', function() { - var dataIn = [{ - x: [-2, -2, 1, 2, 3], - y: [1, 2, 2, 3, 1], - }, { - 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, {}, []); - - var msg; - - msg = 'does not mutate user data'; - expect(dataIn[1].x).toEqual([-2, -1, -2, 0, 1, 2, 3], msg); - expect(dataIn[1].y).toEqual([1, 2, 3, 1, 2, 3, 1], msg); - expect(dataIn[1].transforms).toEqual([{ - type: 'filter', - operation: '>', - value: '0', - filtersrc: 'x' - }], msg); - - msg = 'applies transform'; - expect(dataOut[1].x).toEqual([1, 2, 3], msg); - expect(dataOut[1].y).toEqual([2, 3, 1], msg); - - msg = 'supplying the transform defaults'; - expect(dataOut[1].transforms[0]).toEqual({ - type: 'filter', - operation: '>', - value: 0, - filtersrc: 'x' - }, msg); - - msg = 'keeping refs to user data'; - expect(dataOut[1]._input.x).toEqual([-2, -1, -2, 0, 1, 2, 3], msg); - expect(dataOut[1]._input.y).toEqual([1, 2, 3, 1, 2, 3, 1], msg); - expect(dataOut[1]._input.transforms).toEqual([{ - type: 'filter', - operation: '>', - value: '0', - filtersrc: 'x' - }], msg); - - msg = 'keeping refs to full transforms array'; - expect(dataOut[1]._fullInput.transforms).toEqual([{ - type: 'filter', - operation: '>', - value: 0, - filtersrc: 'x' - }], msg); - - msg = 'setting index w.r.t user data'; - expect(dataOut[0].index).toEqual(0, msg); - expect(dataOut[1].index).toEqual(1, msg); - - msg = 'setting _expandedIndex w.r.t full data'; - expect(dataOut[0]._expandedIndex).toEqual(0, msg); - expect(dataOut[1]._expandedIndex).toEqual(1, msg); - }); - - 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]; - - var uid; - function assertUid(gd) { - expect(gd._fullData[0].uid) - .toEqual(uid + '0', 'should preserve uid on restyle'); - } - - Plotly.plot(gd, data).then(function() { - uid = gd.data[0].uid; - - expect(gd._fullData[0].marker.color).toEqual('red'); - assertUid(gd); - assertStyle(dims, ['rgb(255, 0, 0)'], [1]); - - return Plotly.restyle(gd, 'marker.color', 'blue'); - }).then(function() { - expect(gd._fullData[0].marker.color).toEqual('blue'); - assertUid(gd); - assertStyle(dims, ['rgb(0, 0, 255)'], [1]); - - return Plotly.restyle(gd, 'marker.color', 'red'); - }).then(function() { - expect(gd._fullData[0].marker.color).toEqual('red'); - assertUid(gd); - assertStyle(dims, ['rgb(255, 0, 0)'], [1]); - - return Plotly.restyle(gd, 'transforms[0].value', 2.5); - }).then(function() { - assertUid(gd); - 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() { - '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('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); - - 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('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); - - 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('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); - - 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/validate_test.js b/test/jasmine/tests/validate_test.js index 8a55323a6bd..4d6655c957a 100644 --- a/test/jasmine/tests/validate_test.js +++ b/test/jasmine/tests/validate_test.js @@ -2,11 +2,10 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); Plotly.register([ - // until they become official - require('../assets/transforms/filter') + require('@src/transforms/filter'), + require('@src/transforms/groupby') ]); - describe('Plotly.validate', function() { function assertErrorContent(obj, code, cont, trace, path, astr, msg) {