diff --git a/src/lib/queue.js b/src/lib/queue.js index 25572003897..2425b67564e 100644 --- a/src/lib/queue.js +++ b/src/lib/queue.js @@ -9,7 +9,9 @@ 'use strict'; -var Plotly = require('../plotly'); +var Lib = require('../lib'); +var config = require('../plot_api/plot_config'); + /** * Copy arg array *without* removing `undefined` values from objects. @@ -28,8 +30,8 @@ function copyArgArray(gd, args) { if(arg === gd) copy[i] = arg; else if(typeof arg === 'object') { copy[i] = Array.isArray(arg) ? - Plotly.Lib.extendDeep([], arg) : - Plotly.Lib.extendDeepAll({}, arg); + Lib.extendDeep([], arg) : + Lib.extendDeepAll({}, arg); } else copy[i] = arg; } @@ -82,11 +84,17 @@ queue.add = function(gd, undoFunc, undoArgs, redoFunc, redoArgs) { gd.undoQueue.beginSequence = false; // we unshift to handle calls for undo in a forward for loop later - queueObj.undo.calls.unshift(undoFunc); - queueObj.undo.args.unshift(undoArgs); - queueObj.redo.calls.push(redoFunc); - queueObj.redo.args.push(redoArgs); + if(queueObj) { + queueObj.undo.calls.unshift(undoFunc); + queueObj.undo.args.unshift(undoArgs); + queueObj.redo.calls.push(redoFunc); + queueObj.redo.args.push(redoArgs); + } + if(gd.undoQueue.queue.length > config.queueLength) { + gd.undoQueue.queue.shift(); + gd.undoQueue.index--; + } }; /** diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 2dfbd3bf1dd..3cf9366e167 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1263,9 +1263,7 @@ Plotly.extendTraces = function extendTraces(gd, update, indices, maxPoints) { var promise = Plotly.redraw(gd); var undoArgs = [gd, undo.update, indices, undo.maxPoints]; - if(Queue) { - Queue.add(gd, Plotly.prependTraces, undoArgs, extendTraces, arguments); - } + Queue.add(gd, Plotly.prependTraces, undoArgs, extendTraces, arguments); return promise; }; @@ -1292,9 +1290,7 @@ Plotly.prependTraces = function prependTraces(gd, update, indices, maxPoints) { var promise = Plotly.redraw(gd); var undoArgs = [gd, undo.update, indices, undo.maxPoints]; - if(Queue) { - Queue.add(gd, Plotly.extendTraces, undoArgs, prependTraces, arguments); - } + Queue.add(gd, Plotly.extendTraces, undoArgs, prependTraces, arguments); return promise; }; @@ -1342,7 +1338,7 @@ Plotly.addTraces = function addTraces(gd, traces, newIndices) { // i.e., we can simply redraw and be done if(typeof newIndices === 'undefined') { promise = Plotly.redraw(gd); - if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); return promise; } @@ -1365,10 +1361,10 @@ Plotly.addTraces = function addTraces(gd, traces, newIndices) { // if we're here, the user has defined specific places to place the new traces // this requires some extra work that moveTraces will do - if(Queue) Queue.startSequence(gd); - if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + Queue.startSequence(gd); + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); promise = Plotly.moveTraces(gd, currentIndices, newIndices); - if(Queue) Queue.stopSequence(gd); + Queue.stopSequence(gd); return promise; }; @@ -1409,8 +1405,7 @@ Plotly.deleteTraces = function deleteTraces(gd, indices) { } var promise = Plotly.redraw(gd); - - if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); return promise; }; @@ -1508,8 +1503,7 @@ Plotly.moveTraces = function moveTraces(gd, currentIndices, newIndices) { gd.data = newData; var promise = Plotly.redraw(gd); - - if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); + Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs); return promise; }; @@ -1955,9 +1949,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) { // now all attribute mods are done, as are redo and undo // so we can save them - if(Queue) { - Queue.add(gd, restyle, [gd, undoit, traces], restyle, [gd, redoit, traces]); - } + Queue.add(gd, restyle, [gd, undoit, traces], restyle, [gd, redoit, traces]); // do we need to force a recalc? var autorangeOn = false; @@ -2375,9 +2367,7 @@ Plotly.relayout = function relayout(gd, astr, val) { } // now all attribute mods are done, as are // redo and undo so we can save them - if(Queue) { - Queue.add(gd, relayout, [gd, undoit], relayout, [gd, redoit]); - } + Queue.add(gd, relayout, [gd, undoit], relayout, [gd, redoit]); // calculate autosizing - if size hasn't changed, // will remove h&w so we don't need to redraw diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 7465e7a729c..8cd10023178 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -11,7 +11,7 @@ var Lib = require('../lib'); /** - * This will be transfered over to gd and overridden by + * This will be transferred over to gd and overridden by * config args to Plotly.plot. * * The defaults are the appropriate settings for plotly.js, @@ -26,6 +26,9 @@ module.exports = { // we can edit titles, move annotations, etc editable: false, + // set the length of the undo/redo queue + queueLength: 0, + // plot will respect layout.autosize=true and infer its container size autosizable: false, diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index b736d31a73e..aede6b72b30 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -2,6 +2,7 @@ var Lib = require('@src/lib'); var setCursor = require('@src/lib/setcursor'); var d3 = require('d3'); +var Plotly = require('@lib'); var PlotlyInternal = require('@src/plotly'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); @@ -1042,3 +1043,73 @@ describe('Test lib.js:', function() { }); }); }); + +describe('Queue', function() { + 'use strict'; + + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(function() { + destroyGraphDiv(); + Plotly.setPlotConfig({ queueLength: 0 }); + }); + + it('should not fill in undoQueue by default', function(done) { + Plotly.plot(gd, [{ + y: [2, 1, 2] + }]).then(function() { + expect(gd.undoQueue).toBeUndefined(); + + return Plotly.restyle(gd, 'marker.color', 'red'); + }).then(function() { + expect(gd.undoQueue.index).toEqual(0); + expect(gd.undoQueue.queue).toEqual([]); + + return Plotly.relayout(gd, 'title', 'A title'); + }).then(function() { + expect(gd.undoQueue.index).toEqual(0); + expect(gd.undoQueue.queue).toEqual([]); + + done(); + }); + }); + + it('should fill in undoQueue up to value found in *queueLength* config', function(done) { + Plotly.setPlotConfig({ queueLength: 2 }); + + Plotly.plot(gd, [{ + y: [2, 1, 2] + }]).then(function() { + expect(gd.undoQueue).toBeUndefined(); + + return Plotly.restyle(gd, 'marker.color', 'red'); + }).then(function() { + expect(gd.undoQueue.index).toEqual(1); + expect(gd.undoQueue.queue[0].undo.args[0][1]['marker.color']).toEqual([undefined]); + expect(gd.undoQueue.queue[0].redo.args[0][1]['marker.color']).toEqual('red'); + + return Plotly.relayout(gd, 'title', 'A title'); + }).then(function() { + expect(gd.undoQueue.index).toEqual(2); + expect(gd.undoQueue.queue[1].undo.args[0][1].title).toEqual(undefined); + expect(gd.undoQueue.queue[1].redo.args[0][1].title).toEqual('A title'); + + return Plotly.restyle(gd, 'mode', 'markers'); + }).then(function() { + expect(gd.undoQueue.index).toEqual(2); + expect(gd.undoQueue.queue[2]).toBeUndefined(); + + expect(gd.undoQueue.queue[1].undo.args[0][1].mode).toEqual([undefined]); + expect(gd.undoQueue.queue[1].redo.args[0][1].mode).toEqual('markers'); + + expect(gd.undoQueue.queue[0].undo.args[0][1].title).toEqual(undefined); + expect(gd.undoQueue.queue[0].redo.args[0][1].title).toEqual('A title'); + + done(); + }); + }); +}); diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js index 7c92bbad4d0..fa3e7e2b5fe 100644 --- a/test/jasmine/tests/plot_api_test.js +++ b/test/jasmine/tests/plot_api_test.js @@ -420,6 +420,7 @@ describe('Test plot api', function() { describe('Plotly.ExtendTraces', function() { var gd; + beforeEach(function() { gd = { data: [