diff --git a/src/traces/heatmap/calc.js b/src/traces/heatmap/calc.js index c10b549fa98..e8611ccc82e 100644 --- a/src/traces/heatmap/calc.js +++ b/src/traces/heatmap/calc.js @@ -173,28 +173,35 @@ function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { dv, i; - if(Array.isArray(arrayIn) && !isHist && (ax.type !== 'category')) { + var isArrayOfTwoItemsOrMore = Array.isArray(arrayIn) && arrayIn.length > 1; + + if(isArrayOfTwoItemsOrMore && !isHist && (ax.type !== 'category')) { arrayIn = arrayIn.map(ax.d2c); var len = arrayIn.length; // given vals are brick centers - // hopefully length==numbricks, but use this method even if too few are supplied + // hopefully length === numbricks, but use this method even if too few are supplied // and extend it linearly based on the last two points if(len <= numbricks) { // contour plots only want the centers if(isContour || isGL2D) arrayOut = arrayIn.slice(0, numbricks); - else if(numbricks === 1) arrayOut = [arrayIn[0] - 0.5, arrayIn[0] + 0.5]; + else if(numbricks === 1) { + arrayOut = [arrayIn[0] - 0.5, arrayIn[0] + 0.5]; + } else { arrayOut = [1.5 * arrayIn[0] - 0.5 * arrayIn[1]]; + for(i = 1; i < len; i++) { arrayOut.push((arrayIn[i - 1] + arrayIn[i]) * 0.5); } + arrayOut.push(1.5 * arrayIn[len - 1] - 0.5 * arrayIn[len - 2]); } if(len < numbricks) { var lastPt = arrayOut[arrayOut.length - 1], delta = lastPt - arrayOut[arrayOut.length - 2]; + for(i = len; i < numbricks; i++) { lastPt += delta; arrayOut.push(lastPt); @@ -202,7 +209,7 @@ function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { } } else { - // hopefully length==numbricks+1, but do something regardless: + // hopefully length === numbricks+1, but do something regardless: // given vals are brick boundaries return isContour ? arrayIn.slice(0, numbricks) : // we must be strict for contours @@ -211,7 +218,9 @@ function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { } else { dv = dvIn || 1; - if(v0In === undefined) v0 = 0; + + if(Array.isArray(arrayIn) && arrayIn.length === 1) v0 = arrayIn[0]; + else if(v0In === undefined) v0 = 0; else if(isHist || ax.type === 'category') v0 = v0In; else v0 = ax.d2c(v0In); @@ -219,6 +228,7 @@ function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { arrayOut.push(v0 + dv * i); } } + return arrayOut; } diff --git a/test/jasmine/assets/custom_matchers.js b/test/jasmine/assets/custom_matchers.js index 1e5c52a4678..fc1e710bd19 100644 --- a/test/jasmine/assets/custom_matchers.js +++ b/test/jasmine/assets/custom_matchers.js @@ -4,15 +4,16 @@ module.exports = { toBeCloseToArray: function() { return { compare: function(actual, expected, precision) { - if(precision !== 0) { - precision = Math.pow(10, -precision) / 2 || 0.005; - } + precision = coercePosition(precision); var tested = actual.map(function(element, i) { return Math.abs(expected[i] - element) < precision; }); - var passed = tested.indexOf(false) < 0; + var passed = ( + expected.length === actual.length && + tested.indexOf(false) < 0 + ); return { pass: passed, @@ -20,5 +21,59 @@ module.exports = { }; } }; + }, + + // toBeCloseTo... but for 2D arrays + toBeCloseTo2DArray: function() { + return { + compare: function(actual, expected, precision) { + precision = coercePosition(precision); + + var passed = true; + + if(expected.length !== actual.length) passed = false; + else { + for(var i = 0; i < expected.length; ++i) { + if(expected[i].length !== actual[i].length) { + passed = false; + break; + } + + for(var j = 0; j < expected[i].length; ++j) { + var isClose = Math.abs(expected[i][j] - actual[i][j]) < precision; + + if(!isClose) { + passed = false; + break; + } + } + } + } + + var message = [ + 'Expected', + arrayToStr(actual.map(arrayToStr)), + 'to be close to', + arrayToStr(expected.map(arrayToStr)) + ].join(' '); + + return { + pass: passed, + message: message + }; + } + }; } }; + +function coercePosition(precision) { + if(precision !== 0) { + precision = Math.pow(10, -precision) / 2 || 0.005; + } + + return precision; +} + +function arrayToStr(array) { + return '[ ' + array.join(', ') + ' ]'; +} diff --git a/test/jasmine/tests/contour_test.js b/test/jasmine/tests/contour_test.js index 1b7b36e2186..0cc1fd0c1be 100644 --- a/test/jasmine/tests/contour_test.js +++ b/test/jasmine/tests/contour_test.js @@ -1,135 +1,242 @@ var Plots = require('@src/plots/plots'); +var Lib = require('@src/lib'); + var Contour = require('@src/traces/contour'); var makeColorMap = require('@src/traces/contour/make_color_map'); var colorScales = require('@src/components/colorscale/scales'); +var customMatchers = require('../assets/custom_matchers'); + + +describe('contour defaults', function() { + 'use strict'; + + var traceIn, + traceOut; + + var defaultColor = '#444', + layout = { + font: Plots.layoutAttributes.font + }; + + var supplyDefaults = Contour.supplyDefaults; + + beforeEach(function() { + traceOut = {}; + }); + + it('should set autocontour to false when contours is supplied', function() { + traceIn = { + type: 'contour', + z: [[10, 10.625, 12.5, 15.625], + [5.625, 6.25, 8.125, 11.25], + [2.5, 3.125, 5.0, 8.125], + [0.625, 1.25, 3.125, 6.25]], + contours: { + start: 4, + end: 14, + size: 0.5 + } + }; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.autocontour).toBe(false); + + traceIn = { + type: 'contour', + z: [[10, 10.625, 12.5, 15.625], + [5.625, 6.25, 8.125, 11.25], + [2.5, 3.125, 5.0, 8.125], + [0.625, 1.25, 3.125, 6.25]] + }; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.autocontour).toBe(true); + }); +}); -describe('Test contour', function() { +describe('contour makeColorMap', function() { 'use strict'; - describe('supplyDefaults', function() { - var traceIn, - traceOut; + it('should make correct color map function (\'fill\' coloring case)', function() { + var trace = { + contours: { + coloring: 'fill', + start: -1.5, + size: 0.5, + end: 2.005 + }, + colorscale: [[ + 0, 'rgb(12,51,131)' + ], [ + 0.25, 'rgb(10,136,186)' + ], [ + 0.5, 'rgb(242,211,56)' + ], [ + 0.75, 'rgb(242,143,56)' + ], [ + 1, 'rgb(217,30,30)' + ]] + }; + + var colorMap = makeColorMap(trace); + + expect(colorMap.domain()).toEqual( + [-1.75, -0.75, 0.25, 1.25, 2.25] + ); + + expect(colorMap.range()).toEqual([ + 'rgb(12,51,131)', 'rgb(10,136,186)', 'rgb(242,211,56)', + 'rgb(242,143,56)', 'rgb(217,30,30)' + ]); + }); + + it('should make correct color map function (\'heatmap\' coloring case)', function() { + var trace = { + contours: { + coloring: 'heatmap', + start: 1.5, + size: 0.5, + end: 5.505 + }, + colorscale: colorScales.RdBu, + zmin: 1, + zmax: 6 + }; + + var colorMap = makeColorMap(trace); + + expect(colorMap.domain()).toEqual( + [1, 2.75, 3.5, 4, 4.5, 6] + ); + + expect(colorMap.range()).toEqual([ + 'rgb(5,10,172)', 'rgb(106,137,247)', 'rgb(190,190,190)', + 'rgb(220,170,132)', 'rgb(230,145,90)', 'rgb(178,10,28)' + ]); + }); + + it('should make correct color map function (\'lines\' coloring case)', function() { + var trace = { + contours: { + coloring: 'lines', + start: 1.5, + size: 0.5, + end: 5.505 + }, + colorscale: colorScales.RdBu + }; + + var colorMap = makeColorMap(trace); + + expect(colorMap.domain()).toEqual( + [1.5, 2.9, 3.5, 3.9, 4.3, 5.5] + ); + + expect(colorMap.range()).toEqual([ + 'rgb(5,10,172)', 'rgb(106,137,247)', 'rgb(190,190,190)', + 'rgb(220,170,132)', 'rgb(230,145,90)', 'rgb(178,10,28)' + ]); + }); +}); - var defaultColor = '#444', - layout = { - font: Plots.layoutAttributes.font - }; +describe('contour calc', function() { + 'use strict'; - var supplyDefaults = Contour.supplyDefaults; + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); - beforeEach(function() { - traceOut = {}; + function _calc(opts) { + var base = { type: 'contour' }, + trace = Lib.extendFlat({}, base, opts), + gd = { data: [trace] }; + + Plots.supplyDefaults(gd); + var fullTrace = gd._fullData[0]; + + return Contour.calc(gd, fullTrace)[0]; + } + + it('should fill in bricks if x/y not given', function() { + var out = _calc({ + z: [[1, 2, 3], [3, 1, 2]] }); - it('should set autocontour to false when contours is supplied', function() { - traceIn = { - type: 'contour', - z: [[10, 10.625, 12.5, 15.625], - [5.625, 6.25, 8.125, 11.25], - [2.5, 3.125, 5.0, 8.125], - [0.625, 1.25, 3.125, 6.25]], - contours: { - start: 4, - end: 14, - size: 0.5 - } - }; - supplyDefaults(traceIn, traceOut, defaultColor, layout); - expect(traceOut.autocontour).toBe(false); - - traceIn = { - type: 'contour', - z: [[10, 10.625, 12.5, 15.625], - [5.625, 6.25, 8.125, 11.25], - [2.5, 3.125, 5.0, 8.125], - [0.625, 1.25, 3.125, 6.25]] - }; - supplyDefaults(traceIn, traceOut, defaultColor, layout); - expect(traceOut.autocontour).toBe(true); + expect(out.x).toBeCloseToArray([0, 1, 2]); + expect(out.y).toBeCloseToArray([0, 1]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); + }); + + it('should fill in bricks with x0/dx + y0/dy', function() { + var out = _calc({ + z: [[1, 2, 3], [3, 1, 2]], + x0: 10, + dx: 0.5, + y0: -2, + dy: -2 }); + + expect(out.x).toBeCloseToArray([10, 10.5, 11]); + expect(out.y).toBeCloseToArray([-2, -4]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); }); - describe('makeColorMap', function() { - it('should make correct color map function (\'fill\' coloring case)', function() { - var trace = { - contours: { - coloring: 'fill', - start: -1.5, - size: 0.5, - end: 2.005 - }, - colorscale: [[ - 0, 'rgb(12,51,131)' - ], [ - 0.25, 'rgb(10,136,186)' - ], [ - 0.5, 'rgb(242,211,56)' - ], [ - 0.75, 'rgb(242,143,56)' - ], [ - 1, 'rgb(217,30,30)' - ]] - }; - - var colorMap = makeColorMap(trace); - - expect(colorMap.domain()).toEqual( - [-1.75, -0.75, 0.25, 1.25, 2.25] - ); - - expect(colorMap.range()).toEqual([ - 'rgb(12,51,131)', 'rgb(10,136,186)', 'rgb(242,211,56)', - 'rgb(242,143,56)', 'rgb(217,30,30)' - ]); + it('should convert x/y coordinates into bricks', function() { + var out = _calc({ + x: [1, 2, 3], + y: [2, 6], + z: [[1, 2, 3], [3, 1, 2]] }); - it('should make correct color map function (\'heatmap\' coloring case)', function() { - var trace = { - contours: { - coloring: 'heatmap', - start: 1.5, - size: 0.5, - end: 5.505 - }, - colorscale: colorScales.RdBu, - zmin: 1, - zmax: 6 - }; - - var colorMap = makeColorMap(trace); - - expect(colorMap.domain()).toEqual( - [1, 2.75, 3.5, 4, 4.5, 6] - ); - - expect(colorMap.range()).toEqual([ - 'rgb(5,10,172)', 'rgb(106,137,247)', 'rgb(190,190,190)', - 'rgb(220,170,132)', 'rgb(230,145,90)', 'rgb(178,10,28)' - ]); + expect(out.x).toBeCloseToArray([1, 2, 3]); + expect(out.y).toBeCloseToArray([2, 6]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); + }); + + it('should trim brick-link /y coordinates', function() { + var out = _calc({ + x: [1, 2, 3, 4], + y: [2, 6, 10], + z: [[1, 2, 3], [3, 1, 2]] }); - it('should make correct color map function (\'lines\' coloring case)', function() { - var trace = { - contours: { - coloring: 'lines', - start: 1.5, - size: 0.5, - end: 5.505 - }, - colorscale: colorScales.RdBu - }; - - var colorMap = makeColorMap(trace); - - expect(colorMap.domain()).toEqual( - [1.5, 2.9, 3.5, 3.9, 4.3, 5.5] - ); - - expect(colorMap.range()).toEqual([ - 'rgb(5,10,172)', 'rgb(106,137,247)', 'rgb(190,190,190)', - 'rgb(220,170,132)', 'rgb(230,145,90)', 'rgb(178,10,28)' - ]); + expect(out.x).toBeCloseToArray([1, 2, 3]); + expect(out.y).toBeCloseToArray([2, 6]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); + }); + + it('should handle 1-xy + 1-brick case', function() { + var out = _calc({ + x: [2], + y: [3], + z: [[1]] }); + + expect(out.x).toBeCloseToArray([2]); + expect(out.y).toBeCloseToArray([3]); + expect(out.z).toBeCloseTo2DArray([[1]]); + }); + + it('should handle 1-xy + multi-brick case', function() { + var out = _calc({ + x: [2], + y: [3], + z: [[1, 2, 3], [3, 1, 2]] + }); + + expect(out.x).toBeCloseToArray([2, 3, 4]); + expect(out.y).toBeCloseToArray([3, 4]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); + }); + + it('should handle 0-xy + multi-brick case', function() { + var out = _calc({ + x: [], + y: [], + z: [[1, 2, 3], [3, 1, 2]] + }); + + expect(out.x).toBeCloseToArray([0, 1, 2]); + expect(out.y).toBeCloseToArray([0, 1]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); }); }); diff --git a/test/jasmine/tests/heatmap_test.js b/test/jasmine/tests/heatmap_test.js index 17340fda0cd..dae549a81b0 100644 --- a/test/jasmine/tests/heatmap_test.js +++ b/test/jasmine/tests/heatmap_test.js @@ -1,196 +1,301 @@ +var Plots = require('@src/plots/plots'); +var Lib = require('@src/lib'); + var convertColumnXYZ = require('@src/traces/heatmap/convert_column_xyz'); var Heatmap = require('@src/traces/heatmap'); -var Plots = require('@src/plots/plots'); + +var customMatchers = require('../assets/custom_matchers'); -describe('Test heatmap', function() { +describe('heatmap supplyDefaults', function() { 'use strict'; - describe('supplyDefaults', function() { - var traceIn, - traceOut; + var traceIn, + traceOut; - var defaultColor = '#444', - layout = { - font: Plots.layoutAttributes.font - }; + var defaultColor = '#444', + layout = { + font: Plots.layoutAttributes.font + }; - var supplyDefaults = Heatmap.supplyDefaults; + var supplyDefaults = Heatmap.supplyDefaults; - beforeEach(function() { - traceOut = {}; - }); + beforeEach(function() { + traceOut = {}; + }); - it('should set visible to false when z is empty', function() { - traceIn = { - z: [] - }; - supplyDefaults(traceIn, traceOut, defaultColor, layout); - expect(traceOut.visible).toBe(false); - - traceIn = { - z: [[]] - }; - supplyDefaults(traceIn, traceOut, defaultColor, layout); - expect(traceOut.visible).toBe(false); - - traceIn = { - z: [[], [], []] - }; - supplyDefaults(traceIn, traceOut, defaultColor, layout); - expect(traceOut.visible).toBe(false); - - traceIn = { - type: 'heatmap', - z: [[1, 2], []] - }; - traceOut = Plots.supplyDataDefaults(traceIn, 0, layout); - - traceIn = { - type: 'heatmap', - z: [[], [1, 2], [1, 2, 3]] - }; - traceOut = Plots.supplyDataDefaults(traceIn, 0, layout); - expect(traceOut.visible).toBe(true); - expect(traceOut.visible).toBe(true); - }); + it('should set visible to false when z is empty', function() { + traceIn = { + z: [] + }; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).toBe(false); - it('should set visible to false when z is non-numeric', function() { - traceIn = { - type: 'heatmap', - z: [['a', 'b'], ['c', 'd']] - }; - supplyDefaults(traceIn, traceOut, defaultColor, layout); - expect(traceOut.visible).toBe(false); - }); + traceIn = { + z: [[]] + }; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).toBe(false); - it('should set visible to false when z isn\'t column not a 2d array', function() { - traceIn = { - x: [1, 1, 1, 2, 2], - y: [1, 2, 3, 1, 2], - z: [1, ['this is considered a column'], 1, 2, 3] - }; - supplyDefaults(traceIn, traceOut, defaultColor, layout); - expect(traceOut.visible).not.toBe(false); - - traceIn = { - x: [1, 1, 1, 2, 2], - y: [1, 2, 3, 1, 2], - z: [[0], ['this is not considered a column'], 1, ['nor 2d']] - }; - supplyDefaults(traceIn, traceOut, defaultColor, layout); - expect(traceOut.visible).toBe(false); - }); + traceIn = { + z: [[], [], []] + }; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).toBe(false); + + traceIn = { + type: 'heatmap', + z: [[1, 2], []] + }; + traceOut = Plots.supplyDataDefaults(traceIn, 0, layout); + + traceIn = { + type: 'heatmap', + z: [[], [1, 2], [1, 2, 3]] + }; + traceOut = Plots.supplyDataDefaults(traceIn, 0, layout); + expect(traceOut.visible).toBe(true); + expect(traceOut.visible).toBe(true); + }); + + it('should set visible to false when z is non-numeric', function() { + traceIn = { + type: 'heatmap', + z: [['a', 'b'], ['c', 'd']] + }; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).toBe(false); + }); + + it('should set visible to false when z isn\'t column not a 2d array', function() { + traceIn = { + x: [1, 1, 1, 2, 2], + y: [1, 2, 3, 1, 2], + z: [1, ['this is considered a column'], 1, 2, 3] + }; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).not.toBe(false); + + traceIn = { + x: [1, 1, 1, 2, 2], + y: [1, 2, 3, 1, 2], + z: [[0], ['this is not considered a column'], 1, ['nor 2d']] + }; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).toBe(false); + }); + +}); + +describe('heatmap convertColumnXYZ', function() { + 'use strict'; + + var trace; + + function makeMockAxis() { + return { + d2c: function(v) { return v; } + }; + } + + var xa = makeMockAxis(), + ya = makeMockAxis(); + + it('should convert x/y/z columns to z(x,y)', function() { + trace = { + x: [1, 1, 1, 2, 2, 2], + y: [1, 2, 3, 1, 2, 3], + z: [1, 2, 3, 4, 5, 6] + }; + + convertColumnXYZ(trace, xa, ya); + expect(trace.x).toEqual([1, 2]); + expect(trace.y).toEqual([1, 2, 3]); + expect(trace.z).toEqual([[1, 4], [2, 5], [3, 6]]); + }); + + it('should convert x/y/z columns to z(x,y) with uneven dimensions', function() { + trace = { + x: [1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3], + y: [1, 2, 1, 2, 3], + z: [1, 2, 4, 5, 6] + }; + convertColumnXYZ(trace, xa, ya); + expect(trace.x).toEqual([1, 2]); + expect(trace.y).toEqual([1, 2, 3]); + expect(trace.z).toEqual([[1, 4], [2, 5], [, 6]]); }); - describe('convertColumnXYZ', function() { - var trace; + it('should convert x/y/z columns to z(x,y) with missing values', function() { + trace = { + x: [1, 1, 2, 2, 2], + y: [1, 2, 1, 2, 3], + z: [1, null, 4, 5, 6] + }; - function makeMockAxis() { - return { - d2c: function(v) { return v; } - }; - } + convertColumnXYZ(trace, xa, ya); + expect(trace.x).toEqual([1, 2]); + expect(trace.y).toEqual([1, 2, 3]); + expect(trace.z).toEqual([[1, 4], [null, 5], [, 6]]); + }); + + it('should convert x/y/z/text columns to z(x,y) and text(x,y)', function() { + trace = { + x: [1, 1, 1, 2, 2, 2], + y: [1, 2, 3, 1, 2, 3], + z: [1, 2, 3, 4, 5, 6], + text: ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + }; + + convertColumnXYZ(trace, xa, ya); + expect(trace.text).toEqual([['a', 'd'], ['b', 'e'], ['c', 'f']]); + }); + + it('should convert x/y/z columns to z(x,y) with out-of-order data', function() { + /*eslint no-sparse-arrays: 0*/ + + trace = { + x: [ + 50076, -42372, -19260, 3852, 26964, -65484, -42372, -19260, + 3852, 26964, -88596, -65484, -42372, -19260, 3852, 26964, 50076, 73188, + -65484, -42372, -19260, 3852, 26964, 50076, -42372, -19260, 3852, 26964, + -88596, -65484, -42372, -19260, 3852, 26964, 50076, 73188, -88596, -65484, + -42372, -19260, 3852, 26964, 50076, 73188 + ], + y: [ + 51851.8, 77841.4, 77841.4, 77841.4, 77841.4, 51851.8, 51851.8, 51851.8, + 51851.8, 51851.8, -26117, -26117, -26117, -26117, -26117, -26117, -26117, -26117, + -52106.6, -52106.6, -52106.6, -52106.6, -52106.6, -52106.6, -78096.2, -78096.2, + -78096.2, -78096.2, -127.4, -127.4, -127.4, -127.4, -127.4, -127.4, -127.4, -127.4, + 25862.2, 25862.2, 25862.2, 25862.2, 25862.2, 25862.2, 25862.2, 25862.2 + ], + z: [ + 4.361856, 4.234497, 4.321701, 4.450315, 4.416136, 4.210373, + 4.32009, 4.246728, 4.293992, 4.316364, 3.908434, 4.433257, 4.364234, 4.308714, 4.275516, + 4.126979, 4.296483, 4.320471, 4.339848, 4.39907, 4.345006, 4.315032, 4.295618, 4.262052, + 4.154291, 4.404264, 4.33847, 4.270931, 4.032226, 4.381492, 4.328922, 4.24046, 4.349151, + 4.202861, 4.256402, 4.28972, 3.956225, 4.337909, 4.31226, 4.259435, 4.146854, 4.235799, + 4.238752, 4.299876 + ] + }; + + convertColumnXYZ(trace, xa, ya); + expect(trace.x).toEqual( + [-88596, -65484, -42372, -19260, 3852, 26964, 50076, 73188]); + expect(trace.y).toEqual( + [-78096.2, -52106.6, -26117, -127.4, 25862.2, 51851.8, 77841.4]); + expect(trace.z).toEqual([ + [,, 4.154291, 4.404264, 4.33847, 4.270931,,, ], + [, 4.339848, 4.39907, 4.345006, 4.315032, 4.295618, 4.262052,, ], + [3.908434, 4.433257, 4.364234, 4.308714, 4.275516, 4.126979, 4.296483, 4.320471], + [4.032226, 4.381492, 4.328922, 4.24046, 4.349151, 4.202861, 4.256402, 4.28972], + [3.956225, 4.337909, 4.31226, 4.259435, 4.146854, 4.235799, 4.238752, 4.299876], + [, 4.210373, 4.32009, 4.246728, 4.293992, 4.316364, 4.361856,, ], + [,, 4.234497, 4.321701, 4.450315, 4.416136,,, ] + ]); + }); +}); + +describe('heatmap calc', function() { + 'use strict'; + + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); + + function _calc(opts) { + var base = { type: 'heatmap' }, + trace = Lib.extendFlat({}, base, opts), + gd = { data: [trace] }; - var xa = makeMockAxis(), - ya = makeMockAxis(); + Plots.supplyDefaults(gd); + var fullTrace = gd._fullData[0]; - it('should convert x/y/z columns to z(x,y)', function() { - trace = { - x: [1, 1, 1, 2, 2, 2], - y: [1, 2, 3, 1, 2, 3], - z: [1, 2, 3, 4, 5, 6] - }; + return Heatmap.calc(gd, fullTrace)[0]; + } - convertColumnXYZ(trace, xa, ya); - expect(trace.x).toEqual([1, 2]); - expect(trace.y).toEqual([1, 2, 3]); - expect(trace.z).toEqual([[1, 4], [2, 5], [3, 6]]); + it('should fill in bricks if x/y not given', function() { + var out = _calc({ + z: [[1, 2, 3], [3, 1, 2]] }); - it('should convert x/y/z columns to z(x,y) with uneven dimensions', function() { - trace = { - x: [1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3], - y: [1, 2, 1, 2, 3], - z: [1, 2, 4, 5, 6] - }; - - convertColumnXYZ(trace, xa, ya); - expect(trace.x).toEqual([1, 2]); - expect(trace.y).toEqual([1, 2, 3]); - expect(trace.z).toEqual([[1, 4], [2, 5], [, 6]]); + expect(out.x).toBeCloseToArray([-0.5, 0.5, 1.5, 2.5]); + expect(out.y).toBeCloseToArray([-0.5, 0.5, 1.5]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); + }); + + it('should fill in bricks with x0/dx + y0/dy', function() { + var out = _calc({ + z: [[1, 2, 3], [3, 1, 2]], + x0: 10, + dx: 0.5, + y0: -2, + dy: -2 }); - it('should convert x/y/z columns to z(x,y) with missing values', function() { - trace = { - x: [1, 1, 2, 2, 2], - y: [1, 2, 1, 2, 3], - z: [1, null, 4, 5, 6] - }; - - convertColumnXYZ(trace, xa, ya); - expect(trace.x).toEqual([1, 2]); - expect(trace.y).toEqual([1, 2, 3]); - expect(trace.z).toEqual([[1, 4], [null, 5], [, 6]]); + expect(out.x).toBeCloseToArray([9.75, 10.25, 10.75, 11.25]); + expect(out.y).toBeCloseToArray([-1, -3, -5]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); + }); + + it('should convert x/y coordinates into bricks', function() { + var out = _calc({ + x: [1, 2, 3], + y: [2, 6], + z: [[1, 2, 3], [3, 1, 2]] + }); + + expect(out.x).toBeCloseToArray([0.5, 1.5, 2.5, 3.5]); + expect(out.y).toBeCloseToArray([0, 4, 8]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); + }); + + it('should respect brick-link /y coordinates', function() { + var out = _calc({ + x: [1, 2, 3, 4], + y: [2, 6, 10], + z: [[1, 2, 3], [3, 1, 2]] }); - it('should convert x/y/z/text columns to z(x,y) and text(x,y)', function() { - trace = { - x: [1, 1, 1, 2, 2, 2], - y: [1, 2, 3, 1, 2, 3], - z: [1, 2, 3, 4, 5, 6], - text: ['a', 'b', 'c', 'd', 'e', 'f', 'g'] - }; + expect(out.x).toBeCloseToArray([1, 2, 3, 4]); + expect(out.y).toBeCloseToArray([2, 6, 10]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); + }); - convertColumnXYZ(trace, xa, ya); - expect(trace.text).toEqual([['a', 'd'], ['b', 'e'], ['c', 'f']]); + it('should handle 1-xy + 1-brick case', function() { + var out = _calc({ + x: [2], + y: [3], + z: [[1]] }); - it('should convert x/y/z columns to z(x,y) with out-of-order data', function() { - /*eslint no-sparse-arrays: 0*/ - - trace = { - x: [ - 50076, -42372, -19260, 3852, 26964, -65484, -42372, -19260, - 3852, 26964, -88596, -65484, -42372, -19260, 3852, 26964, 50076, 73188, - -65484, -42372, -19260, 3852, 26964, 50076, -42372, -19260, 3852, 26964, - -88596, -65484, -42372, -19260, 3852, 26964, 50076, 73188, -88596, -65484, - -42372, -19260, 3852, 26964, 50076, 73188 - ], - y: [ - 51851.8, 77841.4, 77841.4, 77841.4, 77841.4, 51851.8, 51851.8, 51851.8, - 51851.8, 51851.8, -26117, -26117, -26117, -26117, -26117, -26117, -26117, -26117, - -52106.6, -52106.6, -52106.6, -52106.6, -52106.6, -52106.6, -78096.2, -78096.2, - -78096.2, -78096.2, -127.4, -127.4, -127.4, -127.4, -127.4, -127.4, -127.4, -127.4, - 25862.2, 25862.2, 25862.2, 25862.2, 25862.2, 25862.2, 25862.2, 25862.2 - ], - z: [ - 4.361856, 4.234497, 4.321701, 4.450315, 4.416136, 4.210373, - 4.32009, 4.246728, 4.293992, 4.316364, 3.908434, 4.433257, 4.364234, 4.308714, 4.275516, - 4.126979, 4.296483, 4.320471, 4.339848, 4.39907, 4.345006, 4.315032, 4.295618, 4.262052, - 4.154291, 4.404264, 4.33847, 4.270931, 4.032226, 4.381492, 4.328922, 4.24046, 4.349151, - 4.202861, 4.256402, 4.28972, 3.956225, 4.337909, 4.31226, 4.259435, 4.146854, 4.235799, - 4.238752, 4.299876 - ] - }; - - convertColumnXYZ(trace, xa, ya); - expect(trace.x).toEqual( - [-88596, -65484, -42372, -19260, 3852, 26964, 50076, 73188]); - expect(trace.y).toEqual( - [-78096.2, -52106.6, -26117, -127.4, 25862.2, 51851.8, 77841.4]); - expect(trace.z).toEqual([ - [,, 4.154291, 4.404264, 4.33847, 4.270931,,, ], - [, 4.339848, 4.39907, 4.345006, 4.315032, 4.295618, 4.262052,, ], - [3.908434, 4.433257, 4.364234, 4.308714, 4.275516, 4.126979, 4.296483, 4.320471], - [4.032226, 4.381492, 4.328922, 4.24046, 4.349151, 4.202861, 4.256402, 4.28972], - [3.956225, 4.337909, 4.31226, 4.259435, 4.146854, 4.235799, 4.238752, 4.299876], - [, 4.210373, 4.32009, 4.246728, 4.293992, 4.316364, 4.361856,, ], - [,, 4.234497, 4.321701, 4.450315, 4.416136,,, ] - ]); + expect(out.x).toBeCloseToArray([1.5, 2.5]); + expect(out.y).toBeCloseToArray([2.5, 3.5]); + expect(out.z).toBeCloseTo2DArray([[1]]); + }); + + it('should handle 1-xy + multi-brick case', function() { + var out = _calc({ + x: [2], + y: [3], + z: [[1, 2, 3], [3, 1, 2]] }); + expect(out.x).toBeCloseToArray([1.5, 2.5, 3.5, 4.5]); + expect(out.y).toBeCloseToArray([2.5, 3.5, 4.5]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); }); + it('should handle 0-xy + multi-brick case', function() { + var out = _calc({ + x: [], + y: [], + z: [[1, 2, 3], [3, 1, 2]] + }); + + expect(out.x).toBeCloseToArray([-0.5, 0.5, 1.5, 2.5]); + expect(out.y).toBeCloseToArray([-0.5, 0.5, 1.5]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); + }); });