diff --git a/draftlogs/6189_fix.md b/draftlogs/6189_fix.md new file mode 100644 index 00000000000..ddf1ac2645c --- /dev/null +++ b/draftlogs/6189_fix.md @@ -0,0 +1 @@ + - Fix undesirable missing hover labels of `box` & `violin` traces [[#6189](https://github.com/plotly/plotly.js/pull/6189)] diff --git a/src/traces/box/hover.js b/src/traces/box/hover.js index 6ae73777347..479d434f42c 100644 --- a/src/traces/box/hover.js +++ b/src/traces/box/hover.js @@ -44,7 +44,6 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) { var trace = cd[0].trace; var t = cd[0].t; var isViolin = trace.type === 'violin'; - var closeBoxData = []; var pLetter, vLetter, pAxis, vAxis, vVal, pVal, dx, dy, dPos, hoverPseudoDistance, spikePseudoDistance; @@ -141,22 +140,30 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) { pointData.spikeDistance = dxy(di) * spikePseudoDistance / hoverPseudoDistance; pointData[spikePosAttr] = pAxis.c2p(di.pos, true); - // box plots: each "point" gets many labels - var usedVals = {}; - var attrs = ['med', 'q1', 'q3', 'min', 'max']; + var hasMean = trace.boxmean || (trace.meanline || {}).visible; + var hasFences = trace.boxpoints || trace.points; - if(trace.boxmean || (trace.meanline || {}).visible) { - attrs.push('mean'); - } - if(trace.boxpoints || trace.points) { - attrs.push('lf', 'uf'); + // labels with equal values (e.g. when min === q1) should still be presented in the order they have when they're unequal + var attrs = + (hasFences && hasMean) ? ['max', 'uf', 'q3', 'med', 'mean', 'q1', 'lf', 'min'] : + (hasFences && !hasMean) ? ['max', 'uf', 'q3', 'med', 'q1', 'lf', 'min'] : + (!hasFences && hasMean) ? ['max', 'q3', 'med', 'mean', 'q1', 'min'] : + ['max', 'q3', 'med', 'q1', 'min']; + + var rev = vAxis.range[1] < vAxis.range[0]; + + if(trace.orientation === (rev ? 'v' : 'h')) { + attrs.reverse(); } + var spikeDistance = pointData.spikeDistance; + var spikePosition = pointData[spikePosAttr]; + + var closeBoxData = []; for(var i = 0; i < attrs.length; i++) { var attr = attrs[i]; - if(!(attr in di) || (di[attr] in usedVals)) continue; - usedVals[di[attr]] = true; + if(!(attr in di)) continue; // copy out to a new object for each value to label var val = di[attr]; @@ -176,17 +183,27 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) { pointData2[vLetter + 'err'] = di.sd; } - // only keep name and spikes on the first item (median) - pointData.name = ''; - pointData.spikeDistance = undefined; - pointData[spikePosAttr] = undefined; - // no hovertemplate support yet pointData2.hovertemplate = false; closeBoxData.push(pointData2); } + // only keep name and spikes on the median + pointData.name = ''; + pointData.spikeDistance = undefined; + pointData[spikePosAttr] = undefined; + for(var k = 0; k < closeBoxData.length; k++) { + if(closeBoxData[k].attr !== 'med') { + closeBoxData[k].name = ''; + closeBoxData[k].spikeDistance = undefined; + closeBoxData[k][spikePosAttr] = undefined; + } else { + closeBoxData[k].spikeDistance = spikeDistance; + closeBoxData[k][spikePosAttr] = spikePosition; + } + } + return closeBoxData; } diff --git a/src/traces/violin/hover.js b/src/traces/violin/hover.js index eb716dc80ae..883350e4032 100644 --- a/src/traces/violin/hover.js +++ b/src/traces/violin/hover.js @@ -56,11 +56,19 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode, opts) { kdePointData[vLetter + 'Label'] = vLetter + ': ' + Axes.hoverLabelText(vAxis, vVal, trace[vLetter + 'hoverformat']) + ', ' + cd[0].t.labels.kde + ' ' + kdeVal.toFixed(3); // move the spike to the KDE point - kdePointData.spikeDistance = closeBoxData[0].spikeDistance; + var medId = 0; + for(var k = 0; k < closeBoxData.length; k++) { + if(closeBoxData[k].attr === 'med') { + medId = k; + break; + } + } + + kdePointData.spikeDistance = closeBoxData[medId].spikeDistance; var spikePosAttr = pLetter + 'Spike'; - kdePointData[spikePosAttr] = closeBoxData[0][spikePosAttr]; - closeBoxData[0].spikeDistance = undefined; - closeBoxData[0][spikePosAttr] = undefined; + kdePointData[spikePosAttr] = closeBoxData[medId][spikePosAttr]; + closeBoxData[medId].spikeDistance = undefined; + closeBoxData[medId][spikePosAttr] = undefined; // no hovertemplate support yet kdePointData.hovertemplate = false; diff --git a/test/jasmine/tests/box_test.js b/test/jasmine/tests/box_test.js index ac271d4df97..8af768907e6 100644 --- a/test/jasmine/tests/box_test.js +++ b/test/jasmine/tests/box_test.js @@ -743,8 +743,8 @@ describe('Test box hover:', function() { fig.layout.hovermode = 'x'; return fig; }, - nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'], - name: ['radishes', '', '', '', ''], + nums: ['median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7'], + name: ['radishes', '', '', '', '', '', ''], axis: 'day 1' }, { desc: 'with mean', @@ -755,8 +755,8 @@ describe('Test box hover:', function() { fig.layout.hovermode = 'x'; return fig; }, - nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', 'mean: 0.45'], - name: ['radishes', '', '', '', '', ''], + nums: ['median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7', 'mean: 0.45'], + name: ['radishes', '', '', '', '', '', '', ''], axis: 'day 1' }, { desc: 'with sd', @@ -768,10 +768,10 @@ describe('Test box hover:', function() { return fig; }, nums: [ - 'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', + 'median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7', 'mean ± σ: 0.45 ± 0.2362908' ], - name: ['radishes', '', '', '', '', ''], + name: ['radishes', '', '', '', '', '', '', ''], axis: 'day 1' }, { desc: 'with boxpoints fences', @@ -782,10 +782,11 @@ describe('Test box hover:', function() { }, pos: [350, 200], nums: [ + '23.25', 'median: 8.15', 'min: 0.75', 'q1: 6.8', 'q3: 10.25', 'max: 23.25', 'lower fence: 5.25', 'upper fence: 12' ], - name: ['', '', '', '', '', '', ''], + name: ['', '', '', '', '', '', '', ''], axis: 'trace 0' }, { desc: 'with overlaid boxes', @@ -795,12 +796,22 @@ describe('Test box hover:', function() { return fig; }, nums: [ - 'q1: 0.3', 'median: 0.45', 'q3: 0.6', 'max: 1', 'median: 0.55', 'min: 0', 'q1: 0.1', - 'q3: 0.6', 'max: 0.7', 'median: 0.45', 'q1: 0.2', 'q3: 0.6', 'max: 0.9' + 'median: 0.45', 'median: 0.45', 'median: 0.55', + 'min: 0', 'min: 0.1', 'min: 0.2', + 'lower fence: 0', 'lower fence: 0.1', 'lower fence: 0.2', + 'q1: 0.1', 'q1: 0.2', 'q1: 0.3', + 'q3: 0.6', 'q3: 0.6', 'q3: 0.6', + 'upper fence: 0.7', 'upper fence: 0.9', 'upper fence: 1', + 'max: 0.7', 'max: 0.9', 'max: 1' ], name: [ - '', 'kale', '', '', 'radishes', '', '', - '', '', 'carrots', '', '', '' + 'carrots', 'kale', 'radishes', + '', '', '', + '', '', '', + '', '', '', + '', '', '', + '', '', '', + '', '', '' ], axis: 'day 1' }, { @@ -841,8 +852,8 @@ describe('Test box hover:', function() { return fig; }, pos: [215, 200], - nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'], - name: ['radishes', '', '', '', ''], + nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', 'lower fence: 0', 'upper fence: 0.7'], + name: ['radishes', '', '', '', '', '', ''], axis: 'day 1' }, { desc: 'hoveron boxes+points | hovermode x (box AND closest point)', @@ -855,8 +866,8 @@ describe('Test box hover:', function() { fig.layout.hovermode = 'x'; return fig; }, - nums: ['0.6', 'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'], - name: ['radishes', 'radishes', '', '', '', ''], + nums: ['0.6', 'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', 'lower fence: 0', 'upper fence: 0.7'], + name: ['radishes', 'radishes', '', '', '', '', '', ''], axis: 'day 1' }, { desc: 'text items on hover', @@ -909,20 +920,32 @@ describe('Test box hover:', function() { }, pos: [430, 130], nums: [ - 'max: 1', 'mean ± σ: 0.6833333 ± 0.2409472', 'min: 0.3', - 'q1: 0.5', 'q3: 0.9', 'median: 0.7'], - name: ['', '', '', '', '', 'carrots'], - axis: 'day 2', - hOrder: [0, 4, 5, 1, 3, 2] + 'median: 0.7', + 'min: 0.3', + 'q1: 0.5', + 'q3: 0.9', + 'max: 1', + 'lower fence: 0.3', + 'upper fence: 1', + 'mean ± σ: 0.6833333 ± 0.2409472', + ], + name: ['carrots', '', '', '', '', '', '', ''], + axis: 'day 2' }, { desc: 'orientation:h | hovermode:closest', mock: require('@mocks/box_grouped_horz.json'), pos: [430, 130], nums: [ - '(max: 1, day 2)', '(mean ± σ: 0.6833333 ± 0.2409472, day 2)', '(min: 0.3, day 2)', - '(q1: 0.5, day 2)', '(q3: 0.9, day 2)', '(median: 0.7, day 2)'], - name: ['', '', '', '', '', 'carrots'], - hOrder: [0, 4, 5, 1, 3, 2] + '(median: 0.7, day 2)', + '(min: 0.3, day 2)', + '(q1: 0.5, day 2)', + '(q3: 0.9, day 2)', + '(max: 1, day 2)', + '(lower fence: 0.3, day 2)', + '(upper fence: 1, day 2)', + '(mean ± σ: 0.6833333 ± 0.2409472, day 2)' + ], + name: ['carrots', '', '', '', '', '', '', ''], }, { desc: 'on boxpoints with numeric positions | hovermode:closest', mock: { @@ -967,8 +990,8 @@ describe('Test box hover:', function() { } }, pos: [200, 200], - nums: ['median: 2', 'q1: 1.5', 'q3: 2.5', 'max: 3', 'min: 1'], - name: ['', '', '', '', ''], + nums: ['median: 2', 'q1: 1.5', 'q3: 2.5', 'max: 3', 'min: 1', 'lower fence: 1', 'upper fence: 3'], + name: ['', '', '', '', '', '', ''], axis: 'trace 0' }, { desc: 'q1/median/q3 signature on boxes', @@ -987,8 +1010,8 @@ describe('Test box hover:', function() { } }, pos: [200, 200], - nums: ['median: 2', 'q1: 1', 'q3: 3'], - name: ['', '', ''], + nums: ['median: 2', 'min: 1', 'q1: 1', 'q3: 3', 'max: 3', 'lower fence: 1', 'upper fence: 3'], + name: ['', '', '', '', '', '', ''], axis: 'A' }, { desc: 'q1/median/q3 signature on points', diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 0461344a3f1..66ea5a85b58 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -2833,21 +2833,25 @@ describe('hover on traces with (x|y)period positioning', function() { .then(function() { _hover(385, 355); }) .then(function() { assertHoverLabelContent({ - name: ['', '', '', 'box (v)', ''], + name: ['', '', '', 'box (v)', '', '', ''], nums: [ '(Jan 2001, min: 2)', + '(Jan 2001, lower fence: 2)', '(Jan 2001, q1: 4)', - '(Jan 2001, q3: 8)', '(Jan 2001, median: 6)', - '(Jan 2001, max: 10)', + '(Jan 2001, q3: 8)', + '(Jan 2001, upper fence: 10)', + '(Jan 2001, max: 10)' ] }); }) .then(function() { _hover(475, 120); }) .then(function() { assertHoverLabelContent({ - name: ['', '', '', '', 'box (h)'], + name: ['', '', '', '', '', '', 'box (h)'], nums: [ + '(upper fence: 8, Jan 2004)', + '(lower fence: 0, Jan 2004)', '(max: 8, Jan 2004)', '(min: 0, Jan 2004)', '(q1: 2, Jan 2004)', @@ -4673,7 +4677,13 @@ describe('hovermode: (x|y)unified', function() { assertLabel({title: '3', items: [ 'trace 0 : 2', + 'min: 1', + 'lower fence: 1', + 'q1: 1', 'trace 1 : median: 1', + 'q3: 1', + 'upper fence: 1', + 'max: 1', 'trace 3 : 2', 'trace 2 : 2', 'trace 5 : 2', @@ -6262,7 +6272,18 @@ describe('hover on traces with (x|y)hoverformat', function() { {type: 'scattergl', nums: '(02/01/2000, 1.00)'}, {type: 'histogram', nums: '(02/01/2000, 1.00)'}, {type: 'bar', nums: '(02/01/2000, 1.00)'}, - {type: 'box', nums: '(02/01/2000, median: 1.00)'}, + {type: 'box', + name: ['', '', '', '', '', '', ''], + nums: [ + '(02/01/2000, median: 1.00)', + '(02/01/2000, max: 1.00)', + '(02/01/2000, upper fence: 1.00)', + '(02/01/2000, q3: 1.00)', + '(02/01/2000, q1: 1.00)', + '(02/01/2000, lower fence: 1.00)', + '(02/01/2000, min: 1.00)' + ] + }, {type: 'ohlc', nums: '02/01/2000\nopen: 4.00\nhigh: 5.00\nlow: 2.00\nclose: 3.00 ▼'}, {type: 'candlestick', nums: '02/01/2000\nopen: 4.00\nhigh: 5.00\nlow: 2.00\nclose: 3.00 ▼'}, {type: 'waterfall', nums: '(02/01/2000, 1.00)\n1.00 ▲\nInitial: 0.00'}, @@ -6286,7 +6307,7 @@ describe('hover on traces with (x|y)hoverformat', function() { .then(function() { _hover(200, 200); }) .then(function() { assertHoverLabelContent({ - name: '', + name: t.name ? t.name : '', nums: t.nums }); }) diff --git a/test/jasmine/tests/violin_test.js b/test/jasmine/tests/violin_test.js index 9487a94605e..961c5efe03d 100644 --- a/test/jasmine/tests/violin_test.js +++ b/test/jasmine/tests/violin_test.js @@ -443,10 +443,10 @@ describe('Test violin hover:', function() { return fig; }, nums: [ - 'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', + 'median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7', 'y: 0.9266848, kde: 0.182' ], - name: ['radishes', '', '', '', '', ''], + name: ['radishes', '', '', '', '', '', '', ''], axis: 'day 1' }, { desc: 'with mean', @@ -458,26 +458,41 @@ describe('Test violin hover:', function() { return fig; }, nums: [ - 'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', 'mean: 0.45', + 'median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7', 'mean: 0.45', 'y: 0.9266848, kde: 0.182' ], - name: ['radishes', '', '', '', '', '', ''], + name: ['radishes', '', '', '', '', '', '', '', ''], axis: 'day 1' }, { desc: 'with overlaid violins', patch: function(fig) { fig.layout.violinmode = 'overlay'; fig.layout.hovermode = 'x'; + fig.layout.height = 700; + return fig; }, nums: [ - 'q3: 0.6', 'median: 0.45', 'q3: 0.6', 'max: 1', 'y: 0.9266848, kde: 0.383', - 'median: 0.55', 'min: 0', 'q1: 0.3', 'q1: 0.2', 'max: 0.7', 'y: 0.9266848, kde: 0.182', - 'median: 0.45', 'q1: 0.1', 'q3: 0.6', 'max: 0.9', 'y: 0.9266848, kde: 0.435' + 'median: 0.45', 'median: 0.45', 'median: 0.55', + 'min: 0', 'min: 0.1', 'min: 0.2', + 'lower fence: 0', 'lower fence: 0.1', 'lower fence: 0.2', + 'q1: 0.1', 'q1: 0.2', 'q1: 0.3', + 'q3: 0.6', 'q3: 0.6', 'q3: 0.6', + 'upper fence: 0.7', 'upper fence: 0.9', 'upper fence: 1', + 'max: 0.7', 'max: 0.9', 'max: 1', + 'y: 1.211363, kde: 0.119', + 'y: 1.211363, kde: 0.168' ], name: [ - '', 'kale', '', '', '', 'radishes', '', '', '', '', - '', 'carrots', '', '', '', '' + 'carrots', 'kale', 'radishes', + '', '', '', + '', '', '', + '', '', '', + '', '', '', + '', '', '', + '', '', '', + '', + '', ], axis: 'day 1' }, { @@ -516,8 +531,8 @@ describe('Test violin hover:', function() { fig.layout.hovermode = 'x'; return fig; }, - nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'], - name: ['radishes', '', '', '', ''], + nums: ['median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7'], + name: ['radishes', '', '', '', '', '', ''], axis: 'day 1' }, { desc: 'hoveron violins+points | hovermode x (violin AND closest point)', @@ -531,8 +546,8 @@ describe('Test violin hover:', function() { return fig; }, pos: [207, 240], - nums: ['0.7', 'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'], - name: ['radishes', 'radishes', '', '', '', ''], + nums: ['0.7', 'median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7'], + name: ['radishes', 'radishes', '', '', '', '', '', ''], axis: 'day 1' }, { desc: 'text items on hover', @@ -608,14 +623,19 @@ describe('Test violin hover:', function() { return fig; }, nums: [ - 'max: 50.81', 'median: 18.24', 'min: 3.07', - 'q1: 13.8575', 'q3: 24.975', 'upper fence: 39.42' + 'max: 50.81', 'min: 3.07', 'median: 18.24', + 'q1: 13.8575', 'q3: 24.975', 'upper fence: 39.42', 'lower fence: 3.07' ], - name: ['', '', '', '', '', ''], + name: ['', '', '', '', '', '', ''], axis: 'Sat', hoverLabelPos: [ - [364, 270], [352, 270], [339, 270], - [346, 270], [349, 270], [387, 270] + [338, 270], + [377, 270], + [351, 270], + [363, 270], + [345, 270], + [385, 270], + [347, 270] ] }, { desc: 'single horizontal violin', @@ -625,10 +645,9 @@ describe('Test violin hover:', function() { return fig; }, pos: [310, 160], - nums: ['median: C', 'min: A', 'q1: B', 'q3: D', 'max: G', 'upper fence: D', 'x: C, kde: 1.005'], - name: ['categories', '', '', '', '', '', ''], + nums: ['median: C', 'min: A', 'q1: B', 'q3: D', 'max: G', 'upper fence: D', 'lower fence: A', 'x: C, kde: 1.005'], + name: ['categories', '', '', '', '', '', '', ''], axis: 'categories', - hOrder: [4, 5, 3, 6, 0, 2, 1], isRotated: true }, { desc: 'multiple horizontal violins', @@ -642,10 +661,9 @@ describe('Test violin hover:', function() { fig.layout.hovermode = 'y'; return fig; }, - nums: ['median: 0.4', 'min: 0.1', 'q1: 0.2', 'q3: 0.7', 'max: 0.9'], - name: ['kale', '', '', '', ''], + nums: ['median: 0.4', 'min: 0.1', 'lower fence: 0.1', 'q1: 0.2', 'q3: 0.7', 'upper fence: 0.9', 'max: 0.9'], + name: ['kale', '', '', '', '', '', ''], axis: 'day 2', - hOrder: [4, 3, 0, 2, 1], isRotated: true }, { desc: 'multiple horizontal violins (under hovermode:closest)', @@ -660,11 +678,10 @@ describe('Test violin hover:', function() { }, pos: [200, 175], nums: [ - '(median: 0.7, day 2)', '(min: 0.2, day 2)', '(q1: 0.5, day 2)', - '(q3: 0.8, day 2)', '(max: 0.9, day 2)' + '(median: 0.7, day 2)', '(min: 0.2, day 2)', '(lower fence: 0.2, day 2)', '(q1: 0.5, day 2)', + '(q3: 0.8, day 2)', '(upper fence: 0.9, day 2)', '(max: 0.9, day 2)' ], - name: ['radishes', '', '', '', ''], - hOrder: [4, 3, 0, 2, 1], + name: ['radishes', '', '', '', '', '', ''], isRotated: true }, { desc: 'hovering over single pt on horizontal violin should not rotate labels', @@ -693,10 +710,9 @@ describe('Test violin hover:', function() { return fig; }, pos: [430, 130], - nums: ['max: 0.9', 'min: 0.1', 'q1: 0.2', 'q3: 0.7', 'median: 0.4'], - name: ['', '', '', '', 'kale'], + nums: ['upper fence: 0.9', 'lower fence: 0.1', 'max: 0.9', 'min: 0.1', 'q1: 0.2', 'q3: 0.7', 'median: 0.4'], + name: ['', '', '', '', '', '', 'kale'], axis: '2018 - day 2', - hOrder: [0, 3, 4, 2, 1] }, { desc: 'orientation:h | hovermode:closest', mock: require('@mocks/violin_grouped_horz-multicategory.json'), @@ -709,12 +725,12 @@ describe('Test violin hover:', function() { }, pos: [430, 130], nums: [ + '(upper fence: 0.9, 2018 - day 2)', '(lower fence: 0.1, 2018 - day 2)', '(max: 0.9, 2018 - day 2)', '(min: 0.1, 2018 - day 2)', '(q1: 0.2, 2018 - day 2)', '(q3: 0.7, 2018 - day 2)', '(median: 0.4, 2018 - day 2)' ], - name: ['', '', '', '', 'kale'], - hOrder: [0, 3, 4, 2, 1] + name: ['', '', '', '', '', '', 'kale'] }, { desc: 'on points with numeric positions | orientation:h | hovermode:closest', mock: { @@ -822,7 +838,7 @@ describe('Test violin hover:', function() { actual = actual.sort(function(a, b) { return a[1].top - b[1].top; }); - expect(actual.length).toBe(8, '# of value hover labels'); + expect(actual.length).toBe(9, '# of value hover labels'); for(var i = 0; i < actual.length - 1; i++) { var a = actual[i];