var Plotly = require('@lib/index');

var BADNUM = require('@src/constants/numerical').BADNUM;
var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');

describe('calculated data and points', function() {

    var gd;

    beforeEach(function() {
        gd = createGraphDiv();
    });

    afterEach(destroyGraphDiv);

    describe('connectGaps', function() {

        it('should exclude null and undefined points when false', function() {
            Plotly.plot(gd, [{ x: [1, 2, 3, undefined, 5], y: [1, null, 3, 4, 5]}], {});

            expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({ x: BADNUM, y: BADNUM}));
            expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({ x: BADNUM, y: BADNUM}));
        });

        it('should exclude null and undefined points as categories when false', function() {
            Plotly.plot(gd, [{ x: [1, 2, 3, undefined, 5], y: [1, null, 3, 4, 5] }], { xaxis: { type: 'category' }});

            expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({ x: BADNUM, y: BADNUM}));
            expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({ x: BADNUM, y: BADNUM}));
        });
    });

    describe('category ordering', function() {

        describe('default category ordering reified', function() {

            it('should output categories in the given order by default', function() {

                Plotly.plot(gd, [{x: ['c', 'a', 'e', 'b', 'd'], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category'
                }});

                expect(gd.calcdata[0][0].y).toEqual(15);
                expect(gd.calcdata[0][1].y).toEqual(11);
                expect(gd.calcdata[0][2].y).toEqual(12);
                expect(gd.calcdata[0][3].y).toEqual(13);
                expect(gd.calcdata[0][4].y).toEqual(14);
            });

            it('should output categories in the given order if `trace` order is explicitly specified', function() {

                Plotly.plot(gd, [{x: ['c', 'a', 'e', 'b', 'd'], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'trace'
                    // Also, if axis tick order is made configurable, shouldn't we make trace order configurable?
                    // Trace order as in, if a line or curve is drawn through points, what's the trace sequence.
                    // These are two orthogonal concepts. Currently, the trace order is implied
                    // by the order the {x,y} arrays are specified.
                }});

                expect(gd.calcdata[0][0].y).toEqual(15);
                expect(gd.calcdata[0][1].y).toEqual(11);
                expect(gd.calcdata[0][2].y).toEqual(12);
                expect(gd.calcdata[0][3].y).toEqual(13);
                expect(gd.calcdata[0][4].y).toEqual(14);
            });
        });

        describe('domain alphanumerical category ordering', function() {

            it('should output categories in ascending domain alphanumerical order', function() {

                Plotly.plot(gd, [{x: ['c', 'a', 'e', 'b', 'd'], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'category ascending'
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 2, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 0, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 4, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 1, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 3, y: 14}));
            });

            it('should output categories in descending domain alphanumerical order', function() {

                Plotly.plot(gd, [{x: ['c', 'a', 'e', 'b', 'd'], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'category descending'
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 2, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 4, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 0, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 3, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 1, y: 14}));
            });

            it('should output categories in ascending domain alphanumerical order even if categories are all numbers', function() {

                Plotly.plot(gd, [{x: [3, 1, 5, 2, 4], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'category ascending'
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 2, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 0, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 4, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 1, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 3, y: 14}));
            });

            it('should output categories in categoryorder order even if category array is defined', function() {

                Plotly.plot(gd, [{x: ['c', 'a', 'e', 'b', 'd'], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'category ascending',
                    categoryarray: ['b', 'a', 'd', 'e', 'c'] // These must be ignored. Alternative: error?
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 2, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 0, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 4, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 1, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 3, y: 14}));
            });

            it('should output categories in ascending domain alphanumerical order, excluding undefined', function() {

                Plotly.plot(gd, [{x: ['c', undefined, 'e', 'b', 'd'], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'category ascending'
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 1, y: 15}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 3, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 0, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 2, y: 14}));
            });

            it('should combine duplicate categories', function() {
                Plotly.plot(gd, [{x: [ '1', '1'], y: [10, 20]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'category ascending'
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 0, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 0, y: 20}));

                expect(gd._fullLayout.xaxis._categories).toEqual(['1']);
            });
        });

        describe('explicit category ordering', function() {

            it('should output categories in explicitly supplied order, independent of trace order', function() {

                Plotly.plot(gd, [{x: ['c', 'a', 'e', 'b', 'd'], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'array',
                    categoryarray: ['b', 'a', 'd', 'e', 'c']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 4, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 3, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 0, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 2, y: 14}));
            });

            it('should output categories in explicitly supplied order even if category values are all numbers', function() {

                Plotly.plot(gd, [{x: [3, 1, 5, 2, 4], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'array',
                    categoryarray: [2, 1, 4, 5, 3]
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 4, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 3, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 0, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 2, y: 14}));
            });

            it('should output categories in explicitly supplied order, independent of trace order, pruned', function() {

                Plotly.plot(gd, [{x: ['c', undefined, 'e', 'b', 'd'], y: [15, 11, 12, null, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'array',
                    categoryarray: ['b', 'a', 'd', 'e', 'c']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 4, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({ x: BADNUM, y: BADNUM}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 3, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({ x: BADNUM, y: BADNUM}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 2, y: 14}));
            });

            it('should output categories in explicitly supplied order even if not all categories are present', function() {

                Plotly.plot(gd, [{x: ['c', 'a', 'e', 'b', 'd'], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'array',
                    categoryarray: ['b', 'x', 'a', 'd', 'z', 'e', 'c']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 6, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 2, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 5, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 0, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 3, y: 14}));
            });

            it('should output categories in explicitly supplied order even if some missing categories were at the beginning or end of categoryarray', function() {

                Plotly.plot(gd, [{x: ['c', 'a', 'e', 'b', 'd'], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'array',
                    categoryarray: ['y', 'b', 'x', 'a', 'd', 'z', 'e', 'c', 'q', 'k']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 7, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 3, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 6, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 1, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 4, y: 14}));

                // The auto-range feature currently eliminates unused category ticks on the left/right axis tails.
                // The below test case reifies this current behavior, and checks proper order of categories kept.

                var domTickTexts = Array.prototype.slice.call(document.querySelectorAll('g.xtick'))
                    .map(function(e) {return e.__data__.text;});

                expect(domTickTexts).toEqual(['b', 'x', 'a', 'd', 'z', 'e', 'c']);  // y, q and k has no data points
            });

            it('should output categories in explicitly supplied order even if some missing categories were at the beginning or end of categoryarray', function() {

                // The auto-range feature currently eliminates unutilized category ticks on the left/right edge
                // BUT keeps it if a data point with null is added; test is almost identical to the one above
                // except that it explicitly adds an axis tick for y

                Plotly.plot(gd, [{x: ['c', 'a', 'e', 'b', 'd', 'y'], y: [15, 11, 12, 13, 14, null]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'array',
                    categoryarray: ['y', 'b', 'x', 'a', 'd', 'z', 'e', 'c', 'q', 'k']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 7, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 3, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 6, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 1, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 4, y: 14}));

                var domTickTexts = Array.prototype.slice.call(document.querySelectorAll('g.xtick'))
                    .map(function(e) {return e.__data__.text;});

                expect(domTickTexts).toEqual(['y', 'b', 'x', 'a', 'd', 'z', 'e', 'c']);  // q, k has no data; y is null
            });

            it('should output categories in explicitly supplied order even if not all categories are present, and should interact with a null value orthogonally', function() {

                Plotly.plot(gd, [{x: ['c', 'a', 'e', 'b', 'd'], y: [15, null, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'array',
                    categoryarray: ['b', 'x', 'a', 'd', 'z', 'e', 'c']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 6, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: BADNUM, y: BADNUM}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 5, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 0, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 3, y: 14}));
            });

            it('should output categories in explicitly supplied order first, if not all categories are covered', function() {

                Plotly.plot(gd, [{x: ['c', 'a', 'e', 'b', 'd'], y: [15, 11, 12, 13, 14]}], { xaxis: {
                    type: 'category',
                    categoryorder: 'array',
                    categoryarray: ['b', 'a', 'x', 'c']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 3, y: 15}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 4, y: 12}));
                expect(gd.calcdata[0][3]).toEqual(jasmine.objectContaining({x: 0, y: 13}));
                expect(gd.calcdata[0][4]).toEqual(jasmine.objectContaining({x: 5, y: 14}));

                // The order of the rest is unspecified, no need to check. Alternative: make _both_ categoryorder and
                // categories effective; categories would take precedence and the remaining items would be sorted
                // based on the categoryorder. This of course means that the mere presence of categories triggers this
                // behavior, rather than an explicit 'explicit' categoryorder.
            });
        });

        describe('ordering tests in the presence of multiple traces - mutually exclusive', function() {

            it('baseline testing for the unordered, disjunct case', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Switch', 'Plug', 'Cord', 'Fuse', 'Bulb'];
                var x3 = ['Pump', 'Leak', 'Seals'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ]);

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 0, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 2, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 3, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 4, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 5, y: 22}));
                expect(gd.calcdata[1][3]).toEqual(jasmine.objectContaining({x: 6, y: 23}));
                expect(gd.calcdata[1][4]).toEqual(jasmine.objectContaining({x: 7, y: 24}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 8, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 9, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 10, y: 32}));
            });

            it('category order follows the trace order (even if categoryarray is specified)', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Switch', 'Plug', 'Cord', 'Fuse', 'Bulb'];
                var x3 = ['Pump', 'Leak', 'Seals'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], { xaxis: {
                    // type: 'category', // commented out to rely on autotyping for added realism
                    categoryorder: 'trace',
                    categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 0, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 2, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 3, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 4, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 5, y: 22}));
                expect(gd.calcdata[1][3]).toEqual(jasmine.objectContaining({x: 6, y: 23}));
                expect(gd.calcdata[1][4]).toEqual(jasmine.objectContaining({x: 7, y: 24}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 8, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 9, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 10, y: 32}));
            });

            it('category order is category ascending (even if categoryarray is specified)', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Switch', 'Plug', 'Cord', 'Fuse', 'Bulb'];
                var x3 = ['Pump', 'Leak', 'Seals'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], { xaxis: {
                    // type: 'category', // commented out to rely on autotyping for added realism
                    categoryorder: 'category ascending',
                    categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                    // this is the expected sorted outcome: ['Bearing','Bulb','Cord','Fuse','Gear','Leak','Motor','Plug','Pump','Seals','Switch']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 4, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 0, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 6, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 10, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 7, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 2, y: 22}));
                expect(gd.calcdata[1][3]).toEqual(jasmine.objectContaining({x: 3, y: 23}));
                expect(gd.calcdata[1][4]).toEqual(jasmine.objectContaining({x: 1, y: 24}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 8, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 5, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 9, y: 32}));
            });

            it('category order is category descending (even if categoryarray is specified)', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Switch', 'Plug', 'Cord', 'Fuse', 'Bulb'];
                var x3 = ['Pump', 'Leak', 'Seals'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], { xaxis: {
                    // type: 'category', // commented out to rely on autotyping for added realism
                    categoryorder: 'category descending',
                    categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                    // this is the expected sorted outcome: ["Switch", "Seals", "Pump", "Plug", "Motor", "Leak", "Gear", "Fuse", "Cord", "Bulb", "Bearing"]
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 6, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 10, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 4, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 0, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 3, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 8, y: 22}));
                expect(gd.calcdata[1][3]).toEqual(jasmine.objectContaining({x: 7, y: 23}));
                expect(gd.calcdata[1][4]).toEqual(jasmine.objectContaining({x: 9, y: 24}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 2, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 5, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 1, y: 32}));
            });

            it('category order follows categoryarray', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Switch', 'Plug', 'Cord', 'Fuse', 'Bulb'];
                var x3 = ['Pump', 'Leak', 'Seals'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], { xaxis: {
                    // type: 'category', // commented out to rely on autotyping for added realism
                    categoryorder: 'array',
                    categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 9, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 2, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 0, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 6, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 5, y: 22}));
                expect(gd.calcdata[1][3]).toEqual(jasmine.objectContaining({x: 8, y: 23}));
                expect(gd.calcdata[1][4]).toEqual(jasmine.objectContaining({x: 7, y: 24}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 4, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 10, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 3, y: 32}));
            });
        });

        describe('ordering tests in the presence of multiple traces - partially overlapping', function() {

            it('baseline testing for the unordered, partially overlapping case', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Switch', 'Plug', 'Cord', 'Fuse', 'Bulb'];
                var x3 = ['Pump', 'Leak', 'Bearing', 'Seals'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ]);

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 0, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 2, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 3, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 4, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 5, y: 22}));
                expect(gd.calcdata[1][3]).toEqual(jasmine.objectContaining({x: 6, y: 23}));
                expect(gd.calcdata[1][4]).toEqual(jasmine.objectContaining({x: 7, y: 24}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 8, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 9, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 1, y: 32}));
                expect(gd.calcdata[2][3]).toEqual(jasmine.objectContaining({x: 10, y: 33}));
            });

            it('category order follows the trace order (even if categoryarray is specified)', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Switch', 'Plug', 'Cord', 'Fuse', 'Bulb'];
                var x3 = ['Pump', 'Leak', 'Bearing', 'Seals'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], { xaxis: {
                    // type: 'category', // commented out to rely on autotyping for added realism
                    categoryorder: 'trace',
                    categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 0, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 2, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 3, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 4, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 5, y: 22}));
                expect(gd.calcdata[1][3]).toEqual(jasmine.objectContaining({x: 6, y: 23}));
                expect(gd.calcdata[1][4]).toEqual(jasmine.objectContaining({x: 7, y: 24}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 8, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 9, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 1, y: 32}));
                expect(gd.calcdata[2][3]).toEqual(jasmine.objectContaining({x: 10, y: 33}));
            });

            it('category order is category ascending (even if categoryarray is specified)', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Switch', 'Plug', 'Cord', 'Fuse', 'Bulb'];
                var x3 = ['Pump', 'Leak', 'Bearing', 'Seals'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], { xaxis: {
                    // type: 'category', // commented out to rely on autotyping for added realism
                    categoryorder: 'category ascending',
                    categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                    // this is the expected sorted outcome: ['Bearing','Bulb','Cord','Fuse','Gear','Leak','Motor','Plug','Pump','Seals','Switch']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 4, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 0, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 6, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 10, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 7, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 2, y: 22}));
                expect(gd.calcdata[1][3]).toEqual(jasmine.objectContaining({x: 3, y: 23}));
                expect(gd.calcdata[1][4]).toEqual(jasmine.objectContaining({x: 1, y: 24}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 8, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 5, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 0, y: 32}));
                expect(gd.calcdata[2][3]).toEqual(jasmine.objectContaining({x: 9, y: 33}));
            });

            it('category order is category descending (even if categoryarray is specified)', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Switch', 'Plug', 'Cord', 'Fuse', 'Bulb'];
                var x3 = ['Pump', 'Leak', 'Bearing', 'Seals'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], { xaxis: {
                    // type: 'category', // commented out to rely on autotyping for added realism
                    categoryorder: 'category descending',
                    categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                    // this is the expected sorted outcome: ["Switch", "Seals", "Pump", "Plug", "Motor", "Leak", "Gear", "Fuse", "Cord", "Bulb", "Bearing"]
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 6, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 10, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 4, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 0, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 3, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 8, y: 22}));
                expect(gd.calcdata[1][3]).toEqual(jasmine.objectContaining({x: 7, y: 23}));
                expect(gd.calcdata[1][4]).toEqual(jasmine.objectContaining({x: 9, y: 24}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 2, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 5, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 10, y: 32}));
                expect(gd.calcdata[2][3]).toEqual(jasmine.objectContaining({x: 1, y: 33}));
            });

            it('category order follows categoryarray', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Switch', 'Plug', 'Cord', 'Fuse', 'Bulb'];
                var x3 = ['Pump', 'Leak', 'Bearing', 'Seals'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], { xaxis: {
                    // type: 'category', // commented out to rely on autotyping for added realism
                    categoryorder: 'array',
                    categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 9, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 2, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 0, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 6, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 5, y: 22}));
                expect(gd.calcdata[1][3]).toEqual(jasmine.objectContaining({x: 8, y: 23}));
                expect(gd.calcdata[1][4]).toEqual(jasmine.objectContaining({x: 7, y: 24}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 4, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 10, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 1, y: 32}));
                expect(gd.calcdata[2][3]).toEqual(jasmine.objectContaining({x: 3, y: 33}));
            });
        });

        describe('ordering tests in the presence of multiple traces - fully overlapping', function() {

            it('baseline testing for the unordered, fully overlapping case', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Bearing', 'Gear', 'Motor'];
                var x3 = ['Motor', 'Gear', 'Bearing'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ]);

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 0, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 2, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 1, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 0, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 2, y: 22}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 2, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 0, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 1, y: 32}));
            });

            it('category order follows the trace order (even if categoryarray is specified)', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Bearing', 'Gear', 'Motor'];
                var x3 = ['Motor', 'Gear', 'Bearing'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], { xaxis: {
                    // type: 'category', // commented out to rely on autotyping for added realism
                    categoryorder: 'trace',
                    categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 0, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 2, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 1, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 0, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 2, y: 22}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 2, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 0, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 1, y: 32}));
            });

            it('category order is category ascending (even if categoryarray is specified)', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Bearing', 'Gear', 'Motor'];
                var x3 = ['Motor', 'Gear', 'Bearing'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], { xaxis: {
                    // type: 'category', // commented out to rely on autotyping for added realism
                    categoryorder: 'category ascending',
                    categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                    // this is the expected sorted outcome: ['Bearing','Bulb','Cord','Fuse','Gear','Leak','Motor','Plug','Pump','Seals','Switch']
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 1, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 0, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 2, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 0, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 1, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 2, y: 22}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 2, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 1, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 0, y: 32}));
            });

            it('category order is category descending (even if categoryarray is specified)', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Bearing', 'Gear', 'Motor'];
                var x3 = ['Motor', 'Gear', 'Bearing'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], { xaxis: {
                    // type: 'category', // commented out to rely on autotyping for added realism
                    categoryorder: 'category descending',
                    categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                    // this is the expected sorted outcome: ["Switch", "Seals", "Pump", "Plug", "Motor", "Leak", "Gear", "Fuse", "Cord", "Bulb", "Bearing"]
                }});

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 1, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 2, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 0, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 2, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 1, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 0, y: 22}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 0, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 1, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 2, y: 32}));
            });

            it('category order follows categoryarray', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Bearing', 'Gear', 'Motor'];
                var x3 = ['Motor', 'Gear', 'Bearing'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], {
                    xaxis: {
                        // type: 'category', // commented out to rely on autotyping for added realism
                        categoryorder: 'array',
                        categoryarray: ['Bearing', 'Motor', 'Gear']
                    }
                });

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 2, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 0, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 1, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 0, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 2, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 1, y: 22}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 1, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 2, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 0, y: 32}));
            });

            it('category order follows categoryarray even if data is sparse', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Bearing', 'Gear', 'Motor'];
                var x3 = ['Motor', 'Gear', 'Bearing'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;})},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;})},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;})}
                ], {
                    xaxis: {
                        // type: 'category', // commented out to rely on autotyping for added realism
                        categoryorder: 'array',
                        categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                    }
                });

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 9, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 2, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 1, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 9, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 2, y: 22}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 2, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 9, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 1, y: 32}));
            });
        });

        describe('ordering and stacking combined', function() {

            it('partially overlapping category order follows categoryarray and stacking produces expected results', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Switch', 'Plug', 'Cord', 'Fuse', 'Bulb'];
                var x3 = ['Pump', 'Leak', 'Bearing', 'Seals'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;}), type: 'bar'},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;}), type: 'bar'},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;}), type: 'bar'}
                ], {
                    barmode: 'stack',
                    xaxis: {
                        // type: 'category', // commented out to rely on autotyping for added realism
                        categoryorder: 'array',
                        categoryarray: ['Switch', 'Bearing', 'Motor', 'Seals', 'Pump', 'Cord', 'Plug', 'Bulb', 'Fuse', 'Gear', 'Leak']
                    }
                });

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 9, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 1, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 2, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 0, y: 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 6, y: 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 5, y: 22}));
                expect(gd.calcdata[1][3]).toEqual(jasmine.objectContaining({x: 8, y: 23}));
                expect(gd.calcdata[1][4]).toEqual(jasmine.objectContaining({x: 7, y: 24}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 4, y: 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 10, y: 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 1, y: 11 + 32}));
                expect(gd.calcdata[2][3]).toEqual(jasmine.objectContaining({x: 3, y: 33}));
            });

            it('fully overlapping - category order follows categoryarray and stacking produces expected results', function() {

                var x1 = ['Gear', 'Bearing', 'Motor'];
                var x2 = ['Bearing', 'Gear', 'Motor'];
                var x3 = ['Motor', 'Gear', 'Bearing'];

                Plotly.plot(gd, [
                    {x: x1, y: x1.map(function(d, i) {return i + 10;}), type: 'bar'},
                    {x: x2, y: x2.map(function(d, i) {return i + 20;}), type: 'bar'},
                    {x: x3, y: x3.map(function(d, i) {return i + 30;}), type: 'bar'}
                ], {
                    barmode: 'stack',
                    xaxis: {
                        // type: 'category', // commented out to rely on autotyping for added realism
                        categoryorder: 'array',
                        categoryarray: ['Bearing', 'Motor', 'Gear']
                    }
                });

                expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({x: 2, y: 10}));
                expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({x: 0, y: 11}));
                expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({x: 1, y: 12}));

                expect(gd.calcdata[1][0]).toEqual(jasmine.objectContaining({x: 0, y: 11 + 20}));
                expect(gd.calcdata[1][1]).toEqual(jasmine.objectContaining({x: 2, y: 10 + 21}));
                expect(gd.calcdata[1][2]).toEqual(jasmine.objectContaining({x: 1, y: 12 + 22}));

                expect(gd.calcdata[2][0]).toEqual(jasmine.objectContaining({x: 1, y: 12 + 22 + 30}));
                expect(gd.calcdata[2][1]).toEqual(jasmine.objectContaining({x: 2, y: 10 + 21 + 31}));
                expect(gd.calcdata[2][2]).toEqual(jasmine.objectContaining({x: 0, y: 11 + 20 + 32}));
            });
        });
    });

    describe('customdata', function() {
        it('should pass customdata to the calcdata points', function() {
            Plotly.plot(gd, [{
                x: [0, 1, 3],
                y: [4, 5, 7],
                customdata: ['a', 'b', {foo: 'bar'}]
            }], {});

            expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({data: 'a'}));
            expect(gd.calcdata[0][1]).toEqual(jasmine.objectContaining({data: 'b'}));
            expect(gd.calcdata[0][2]).toEqual(jasmine.objectContaining({data: {foo: 'bar'}}));
        });
    });
});
