diff --git a/src/lib/coerce.js b/src/lib/coerce.js index c21a5410b7f..9c67cb32014 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -192,7 +192,7 @@ exports.valObjects = { propOut.set(dflt); return; } - if(opts.extras.indexOf(v) !== -1) { + if((opts.extras || []).indexOf(v) !== -1) { propOut.set(v); return; } diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index 68d7db06cca..28db0d1343e 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -92,6 +92,17 @@ module.exports = { 'then the default is *lines+markers*. Otherwise, *lines*.' ].join(' ') }, + hoveron: { + valType: 'flaglist', + flags: ['points', 'fills'], + role: 'info', + description: [ + 'Do the hover effects highlight individual points (markers or', + 'line points) or do they highlight filled regions?', + 'If the fill is *toself* or *tonext* and there are no markers', + 'or text, then the default is *fills*, otherwise it is *points*.' + ].join(' ') + }, line: { color: { valType: 'color', diff --git a/src/traces/scatter/defaults.js b/src/traces/scatter/defaults.js index 3690160053f..12c94bb8a03 100644 --- a/src/traces/scatter/defaults.js +++ b/src/traces/scatter/defaults.js @@ -53,8 +53,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleTextDefaults(traceIn, traceOut, layout, coerce); } + var dfltHoverOn = []; + if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { coerce('marker.maxdisplayed'); + dfltHoverOn.push('points'); } coerce('fill'); @@ -63,6 +66,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); } + if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { + dfltHoverOn.push('fills'); + } + coerce('hoveron', dfltHoverOn.join('+') || 'points'); + errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); }; diff --git a/src/traces/scatter/hover.js b/src/traces/scatter/hover.js index 737a6509149..373d68d9e3c 100644 --- a/src/traces/scatter/hover.js +++ b/src/traces/scatter/hover.js @@ -9,9 +9,12 @@ 'use strict'; +var Lib = require('../../lib'); var Fx = require('../../plots/cartesian/graph_interact'); +var constants = require('../../plots/cartesian/constants'); var ErrorBars = require('../../components/errorbars'); var getTraceColor = require('./get_trace_color'); +var Color = require('../../components/color'); module.exports = function hoverPoints(pointData, xval, yval, hovermode) { @@ -19,51 +22,137 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { trace = cd[0].trace, xa = pointData.xa, ya = pointData.ya, - dx = function(di) { - // scatter points: d.mrc is the calculated marker radius - // adjust the distance so if you're inside the marker it - // always will show up regardless of point size, but - // prioritize smaller points - var rad = Math.max(3, di.mrc || 0); - return Math.max(Math.abs(xa.c2p(di.x) - xa.c2p(xval)) - rad, 1 - 3 / rad); - }, - dy = function(di) { - var rad = Math.max(3, di.mrc || 0); - return Math.max(Math.abs(ya.c2p(di.y) - ya.c2p(yval)) - rad, 1 - 3 / rad); - }, - dxy = function(di) { - var rad = Math.max(3, di.mrc || 0), - dx = Math.abs(xa.c2p(di.x) - xa.c2p(xval)), - dy = Math.abs(ya.c2p(di.y) - ya.c2p(yval)); - return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad); - }, - distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy); - - Fx.getClosest(cd, distfn, pointData); - - // skip the rest (for this trace) if we didn't find a close point - if(pointData.index === false) return; - - // the closest data point - var di = cd[pointData.index], - xc = xa.c2p(di.x, true), - yc = ya.c2p(di.y, true), - rad = di.mrc || 1; - - pointData.color = getTraceColor(trace, di); - - pointData.x0 = xc - rad; - pointData.x1 = xc + rad; - pointData.xLabelVal = di.x; - - pointData.y0 = yc - rad; - pointData.y1 = yc + rad; - pointData.yLabelVal = di.y; - - if(di.tx) pointData.text = di.tx; - else if(trace.text) pointData.text = trace.text; - - ErrorBars.hoverInfo(di, trace, pointData); - - return [pointData]; + xpx = xa.c2p(xval), + ypx = ya.c2p(yval), + pt = [xpx, ypx]; + + // look for points to hover on first, then take fills only if we + // didn't find a point + if(trace.hoveron.indexOf('points') !== -1) { + var dx = function(di) { + // scatter points: d.mrc is the calculated marker radius + // adjust the distance so if you're inside the marker it + // always will show up regardless of point size, but + // prioritize smaller points + var rad = Math.max(3, di.mrc || 0); + return Math.max(Math.abs(xa.c2p(di.x) - xpx) - rad, 1 - 3 / rad); + }, + dy = function(di) { + var rad = Math.max(3, di.mrc || 0); + return Math.max(Math.abs(ya.c2p(di.y) - ypx) - rad, 1 - 3 / rad); + }, + dxy = function(di) { + var rad = Math.max(3, di.mrc || 0), + dx = xa.c2p(di.x) - xpx, + dy = ya.c2p(di.y) - ypx; + return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad); + }, + distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy); + + Fx.getClosest(cd, distfn, pointData); + + // skip the rest (for this trace) if we didn't find a close point + if(pointData.index !== false) { + + // the closest data point + var di = cd[pointData.index], + xc = xa.c2p(di.x, true), + yc = ya.c2p(di.y, true), + rad = di.mrc || 1; + + Lib.extendFlat(pointData, { + color: getTraceColor(trace, di), + + x0: xc - rad, + x1: xc + rad, + xLabelVal: di.x, + + y0: yc - rad, + y1: yc + rad, + yLabelVal: di.y + }); + + if(di.tx) pointData.text = di.tx; + else if(trace.text) pointData.text = trace.text; + + ErrorBars.hoverInfo(di, trace, pointData); + + return [pointData]; + } + } + + // even if hoveron is 'fills', only use it if we have polygons too + if(trace.hoveron.indexOf('fills') !== -1 && trace._polygons) { + var polygons = trace._polygons, + polygonsIn = [], + inside = false, + xmin = Infinity, + xmax = -Infinity, + ymin = Infinity, + ymax = -Infinity, + i, j, polygon, pts, xCross, x0, x1, y0, y1; + + for(i = 0; i < polygons.length; i++) { + polygon = polygons[i]; + // TODO: this is not going to work right for curved edges, it will + // act as though they're straight. That's probably going to need + // the elements themselves to capture the events. Worth it? + if(polygon.contains(pt)) { + inside = !inside; + // TODO: need better than just the overall bounding box + polygonsIn.push(polygon); + ymin = Math.min(ymin, polygon.ymin); + ymax = Math.max(ymax, polygon.ymax); + } + } + + if(inside) { + // find the overall left-most and right-most points of the + // polygon(s) we're inside at their combined vertical midpoint. + // This is where we will draw the hover label. + // Note that this might not be the vertical midpoint of the + // whole trace, if it's disjoint. + var yAvg = (ymin + ymax) / 2; + for(i = 0; i < polygonsIn.length; i++) { + pts = polygonsIn[i].pts; + for(j = 1; j < pts.length; j++) { + y0 = pts[j - 1][1]; + y1 = pts[j][1]; + if((y0 > yAvg) !== (y1 >= yAvg)) { + x0 = pts[j - 1][0]; + x1 = pts[j][0]; + xCross = x0 + (x1 - x0) * (yAvg - y0) / (y1 - y0); + xmin = Math.min(xmin, xCross); + xmax = Math.max(xmax, xCross); + } + } + } + + // get only fill or line color for the hover color + var color = Color.defaultLine; + if(Color.opacity(trace.fillcolor)) color = trace.fillcolor; + else if(Color.opacity((trace.line || {}).color)) { + color = trace.line.color; + } + + Lib.extendFlat(pointData, { + // never let a 2D override 1D type as closest point + distance: constants.MAXDIST + 10, + x0: xmin, + x1: xmax, + y0: yAvg, + y1: yAvg, + color: color + }); + + delete pointData.index; + + if(trace.text && !Array.isArray(trace.text)) { + pointData.text = String(trace.text); + } + else pointData.text = trace.name; + + return [pointData]; + } + } }; diff --git a/src/traces/scatter/plot.js b/src/traces/scatter/plot.js index 2044c854fa1..d2388f40c22 100644 --- a/src/traces/scatter/plot.js +++ b/src/traces/scatter/plot.js @@ -15,6 +15,8 @@ var Lib = require('../../lib'); var Drawing = require('../../components/drawing'); var ErrorBars = require('../../components/errorbars'); +var polygonTester = require('../../lib/polygon').tester; + var subTypes = require('./subtypes'); var arraysToCalcdata = require('./arrays_to_calcdata'); var linePoints = require('./line_points'); @@ -41,6 +43,7 @@ module.exports = function plot(gd, plotinfo, cdscatter) { // BUILD LINES AND FILLS var prevpath = '', + prevPolygons = [], ownFillEl3, ownFillDir, tonext, nexttonext; scattertraces.each(function(d) { @@ -125,12 +128,23 @@ module.exports = function plot(gd, plotinfo, cdscatter) { linear: line.shape === 'linear' }); + // since we already have the pixel segments here, use them to make + // polygons for hover on fill + // TODO: can we skip this if hoveron!=fills? That would mean we + // need to redraw when you change hoveron... + var thisPolygons = trace._polygons = new Array(segments.length), + i; + + for(i = 0; i < segments.length; i++) { + trace._polygons[i] = polygonTester(segments[i]); + } + if(segments.length) { var pt0 = segments[0][0], lastSegment = segments[segments.length - 1], pt1 = lastSegment[lastSegment.length - 1]; - for(var i = 0; i < segments.length; i++) { + for(i = 0; i < segments.length; i++) { var pts = segments[i]; thispath = pathfn(pts); thisrevpath = revpathfn(pts); @@ -185,8 +199,10 @@ module.exports = function plot(gd, plotinfo, cdscatter) { // existing curve or off the end of it tonext.attr('d', fullpath + 'L' + prevpath.substr(1) + 'Z'); } + trace._polygons = trace._polygons.concat(prevPolygons); } prevpath = revpath; + prevPolygons = thisPolygons; } }); diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index 5dd23f7a674..fb28327b348 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -117,6 +117,7 @@ module.exports = { hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { flags: ['a', 'b', 'c', 'text', 'name'] }), + hoveron: scatterAttrs.hoveron, _nestedModules: { 'marker.colorbar': 'Colorbar' } diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js index 169d922a958..18bbcf039d3 100644 --- a/src/traces/scatterternary/defaults.js +++ b/src/traces/scatterternary/defaults.js @@ -81,8 +81,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleTextDefaults(traceIn, traceOut, layout, coerce); } + var dfltHoverOn = []; + if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { coerce('marker.maxdisplayed'); + dfltHoverOn.push('points'); } coerce('fill'); @@ -92,4 +95,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } coerce('hoverinfo', (layout._dataLength === 1) ? 'a+b+c+text' : undefined); + + if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { + dfltHoverOn.push('fills'); + } + coerce('hoveron', dfltHoverOn.join('+') || 'points'); }; diff --git a/src/traces/scatterternary/hover.js b/src/traces/scatterternary/hover.js index 212b7d18d83..67de2f3b9b0 100644 --- a/src/traces/scatterternary/hover.js +++ b/src/traces/scatterternary/hover.js @@ -17,6 +17,10 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { var scatterPointData = scatterHover(pointData, xval, yval, hovermode); if(!scatterPointData || scatterPointData[0].index === false) return; + // if hovering on a fill, we don't show any point data so the label is + // unchanged from what scatter gives us. + if(scatterPointData[0].index === undefined) return scatterPointData; + var newPointData = scatterPointData[0], cdi = newPointData.cd[newPointData.index]; diff --git a/test/jasmine/assets/create_graph_div.js b/test/jasmine/assets/create_graph_div.js index 0788b7f5dbc..9791d46018c 100644 --- a/test/jasmine/assets/create_graph_div.js +++ b/test/jasmine/assets/create_graph_div.js @@ -4,5 +4,11 @@ module.exports = function createGraphDiv() { var gd = document.createElement('div'); gd.id = 'graph'; document.body.appendChild(gd); + + // force the graph to be at position 0,0 no matter what + gd.style.position = 'fixed'; + gd.style.left = 0; + gd.style.top = 0; + return gd; }; diff --git a/test/jasmine/tests/click_test.js b/test/jasmine/tests/click_test.js index c9f70b4dcd9..c08b56ea711 100644 --- a/test/jasmine/tests/click_test.js +++ b/test/jasmine/tests/click_test.js @@ -14,8 +14,8 @@ describe('Test click interactions:', function() { var mockCopy, gd; - var pointPos = [351, 223], - blankPos = [70, 363]; + var pointPos = [344, 216], + blankPos = [63, 356]; var autoRangeX = [-3.011967491973726, 2.1561305597186564], autoRangeY = [-0.9910086301469277, 1.389382716298284]; @@ -46,7 +46,7 @@ describe('Test click interactions:', function() { setTimeout(function() { click(x, y); - resolve(); + setTimeout(function() { resolve(); }, DBLCLICKDELAY / 2); }, DBLCLICKDELAY / 2); }); } @@ -530,8 +530,8 @@ describe('Test click interactions:', function() { return drag(100, 100, 200, 200, DBLCLICKDELAY / 2); }).then(function() { - expect(gd.layout.xaxis.range).toBeCloseToArray([-2.70624901567643, -1.9783478816352495]); - expect(gd.layout.yaxis.range).toBeCloseToArray([0.5007032802920716, 1.2941670624404753]); + expect(gd.layout.xaxis.range).toBeCloseToArray([-2.6480169249531356, -1.920115790911955]); + expect(gd.layout.yaxis.range).toBeCloseToArray([0.4372261777201992, 1.2306899598686027]); done(); }); @@ -697,14 +697,14 @@ describe('Test click interactions:', function() { expect(gd.layout.xaxis.range).toBeCloseToArray(autoRangeX); expect(gd.layout.yaxis.range).toBeCloseToArray(autoRangeY); - drag(100, 100, 400, 300).then(function() { - expect(gd.layout.xaxis.range).toBeCloseToArray([-2.70624901, -0.52254561]); - expect(gd.layout.yaxis.range).toBeCloseToArray([-0.29276050, 1.294167062]); + drag(93, 93, 393, 293).then(function() { + expect(gd.layout.xaxis.range).toBeCloseToArray([-2.69897000, -0.515266602]); + expect(gd.layout.yaxis.range).toBeCloseToArray([-0.30069513, 1.2862324246]); - return drag(100, 100, 400, 300); + return drag(93, 93, 393, 293); }).then(function() { - expect(gd.layout.xaxis.range).toBeCloseToArray([-2.57707219, -1.65438061]); - expect(gd.layout.yaxis.range).toBeCloseToArray([0.172738250, 1.230689959]); + expect(gd.layout.xaxis.range).toBeCloseToArray([-2.56671754, -1.644025966]); + expect(gd.layout.yaxis.range).toBeCloseToArray([0.159513853, 1.2174655634]); done(); }); @@ -721,8 +721,8 @@ describe('Test click interactions:', function() { var plot = gd._fullLayout._plots.xy.plot; - mouseEvent('mousemove', 400, 250); - mouseEvent('scroll', 400, 250, { deltaX: 0, deltaY: -1000 }); + mouseEvent('mousemove', 393, 243); + mouseEvent('scroll', 393, 243, { deltaX: 0, deltaY: -1000 }); var transform = plot.attr('transform'); @@ -735,7 +735,7 @@ describe('Test click interactions:', function() { var translate = Lib.getTranslate(mockEl), scale = Lib.getScale(mockEl); - expect([translate.x, translate.y]).toBeCloseToArray([62.841, 99.483]); + expect([translate.x, translate.y]).toBeCloseToArray([61.070, 97.712]); expect([scale.x, scale.y]).toBeCloseToArray([1.221, 1.221]); }); }); @@ -751,11 +751,11 @@ describe('Test click interactions:', function() { expect(gd.layout.xaxis.range).toBeCloseToArray(autoRangeX); expect(gd.layout.yaxis.range).toBeCloseToArray(autoRangeY); - drag(100, 100, 400, 300).then(function() { + drag(93, 93, 393, 293).then(function() { expect(gd.layout.xaxis.range).toBeCloseToArray([-5.19567089, -0.02757284]); expect(gd.layout.yaxis.range).toBeCloseToArray([0.595918934, 2.976310280]); - return drag(100, 100, 400, 300); + return drag(93, 93, 393, 293); }).then(function() { expect(gd.layout.xaxis.range).toBeCloseToArray([-7.37937429, -2.21127624]); expect(gd.layout.yaxis.range).toBeCloseToArray([2.182846498, 4.563237844]); diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index ec61cd122cd..2b3951fb8cb 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -406,7 +406,7 @@ describe('hover info', function() { it('should display the correct format when ticklabels true', function() { Plotly.plot(this.gd, data, layout); - mouseEvent('mousemove', 310, 220); + mouseEvent('mousemove', 303, 213); var hovers = d3.selectAll('g.hovertext'); @@ -417,7 +417,7 @@ describe('hover info', function() { it('should display the correct format when ticklabels false', function() { layout.yaxis.showticklabels = false; Plotly.plot(this.gd, data, layout); - mouseEvent('mousemove', 310, 220); + mouseEvent('mousemove', 303, 213); var hovers = d3.selectAll('g.hovertext'); @@ -445,27 +445,27 @@ describe('hover info', function() { }); it('should show text labels', function() { - mouseEvent('mousemove', 115, 310); + mouseEvent('mousemove', 108, 303); var hovers = d3.selectAll('g.hovertext'); expect(hovers.size()).toEqual(1); expect(hovers.select('text')[0][0].textContent).toEqual('test'); }); it('should show number labels', function() { - mouseEvent('mousemove', 370, 180); + mouseEvent('mousemove', 363, 173); var hovers = d3.selectAll('g.hovertext'); expect(hovers.size()).toEqual(1); expect(hovers.select('text')[0][0].textContent).toEqual('42'); }); it('should not show null text labels', function() { - mouseEvent('mousemove', 236, 246); + mouseEvent('mousemove', 229, 239); var hovers = d3.selectAll('g.hovertext'); expect(hovers.size()).toEqual(0); }); it('should not show undefined text labels', function() { - mouseEvent('mousemove', 500, 115); + mouseEvent('mousemove', 493, 108); var hovers = d3.selectAll('g.hovertext'); expect(hovers.size()).toEqual(0); }); @@ -586,7 +586,7 @@ describe('hover info on overlaid subplots', function() { var mock = require('@mocks/autorange-tozero-rangemode.json'); Plotly.plot(createGraphDiv(), mock.data, mock.layout).then(function() { - mouseEvent('mousemove', 775, 352); + mouseEvent('mousemove', 768, 345); var axisText = d3.selectAll('g.axistext'), hoverText = d3.selectAll('g.hovertext'); @@ -630,8 +630,8 @@ describe('hover after resizing', function() { layout = { width: 600, height: 500 }, gd = createGraphDiv(); - var pos0 = [311, 409], - pos1 = [407, 128]; + var pos0 = [305, 403], + pos1 = [401, 122]; Plotly.plot(gd, data, layout).then(function() { return assertLabelCount(pos0, 1, 'before resize, showing pt label'); @@ -652,3 +652,60 @@ describe('hover after resizing', function() { }).then(done); }); }); + +describe('hover on fill', function() { + 'use strict'; + + afterEach(destroyGraphDiv); + + function assertLabelsCorrect(mousePos, labelPos, labelText) { + return new Promise(function(resolve) { + mouseEvent('mousemove', mousePos[0], mousePos[1]); + + setTimeout(function() { + var hoverText = d3.selectAll('g.hovertext'); + expect(hoverText.size()).toEqual(1); + expect(hoverText.text()).toEqual(labelText); + + var transformParts = hoverText.attr('transform').split('('); + expect(transformParts[0]).toEqual('translate'); + var transformCoords = transformParts[1].split(')')[0].split(','); + expect(+transformCoords[0]).toBeCloseTo(labelPos[0], -1, labelText + ':x'); + expect(+transformCoords[1]).toBeCloseTo(labelPos[1], -1, labelText + ':y'); + + resolve(); + }, constants.HOVERMINTIME); + }); + } + + it('should always show one label in the right place', function(done) { + var mock = Lib.extendDeep({}, require('@mocks/scatter_fill_self_next.json')); + mock.data.forEach(function(trace) { trace.hoveron = 'fills'; }); + + Plotly.plot(createGraphDiv(), mock.data, mock.layout).then(function() { + return assertLabelsCorrect([242, 142], [249.175, 133.8], 'trace 2'); + }).then(function() { + return assertLabelsCorrect([242, 292], [231.125, 210], 'trace 1'); + }).then(function() { + return assertLabelsCorrect([147, 252], [158.925, 248.1], 'trace 0'); + }).then(done); + }); + + it('should work for scatterternary too', function(done) { + var mock = Lib.extendDeep({}, require('@mocks/ternary_fill.json')); + + Plotly.plot(createGraphDiv(), mock.data, mock.layout).then(function() { + // hover over a point when that's closest, even if you're over + // a fill, because by default we have hoveron='points+fills' + return assertLabelsCorrect([237, 150], [240.0, 144], + 'trace 2Component A: 0.8Component B: 0.1Component C: 0.1'); + }).then(function() { + // the rest are hovers over fills + return assertLabelsCorrect([237, 170], [247.7, 166], 'trace 2'); + }).then(function() { + return assertLabelsCorrect([237, 218], [266.75, 265], 'trace 1'); + }).then(function() { + return assertLabelsCorrect([237, 251], [247.7, 254], 'trace 0'); + }).then(done); + }); +}); diff --git a/test/jasmine/tests/hover_pie_test.js b/test/jasmine/tests/hover_pie_test.js index e7472b33951..75610e36834 100644 --- a/test/jasmine/tests/hover_pie_test.js +++ b/test/jasmine/tests/hover_pie_test.js @@ -54,8 +54,8 @@ describe('pie hovering', function() { unhoverData = data; }); - mouseEvent('mouseover', width / 2, height / 2); - mouseEvent('mouseout', width / 2, height / 2); + mouseEvent('mouseover', width / 2 - 7, height / 2 - 7); + mouseEvent('mouseout', width / 2 - 7, height / 2 - 7); expect(hoverData.points.length).toEqual(1); expect(unhoverData.points.length).toEqual(1); @@ -82,9 +82,9 @@ describe('pie hovering', function() { hoverData.push(data); }); - mouseEvent('mouseover', 180, 140); + mouseEvent('mouseover', 173, 133); setTimeout(function() { - mouseEvent('mouseover', 240, 200); + mouseEvent('mouseover', 233, 193); expect(count).toEqual(2); expect(hoverData[0]).not.toEqual(hoverData[1]); done(); @@ -100,11 +100,11 @@ describe('pie hovering', function() { unhoverData.push(data); }); - mouseEvent('mouseover', 180, 140); - mouseEvent('mouseout', 180, 140); + mouseEvent('mouseover', 173, 133); + mouseEvent('mouseout', 173, 133); setTimeout(function() { - mouseEvent('mouseover', 240, 200); - mouseEvent('mouseout', 240, 200); + mouseEvent('mouseover', 233, 193); + mouseEvent('mouseout', 233, 193); expect(count).toEqual(2); expect(unhoverData[0]).not.toEqual(unhoverData[1]); done(); @@ -130,7 +130,7 @@ describe('pie hovering', function() { Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() { - mouseEvent('mouseover', 230, 150); + mouseEvent('mouseover', 223, 143); var labels = Plotly.d3.selectAll('.hovertext .nums .line'); @@ -152,7 +152,7 @@ describe('pie hovering', function() { Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() { - mouseEvent('mouseover', 230, 150); + mouseEvent('mouseover', 223, 143); var labels = Plotly.d3.selectAll('.hovertext .nums .line'); diff --git a/test/jasmine/tests/modebar_test.js b/test/jasmine/tests/modebar_test.js index b8a5c460e12..3c4c884169e 100644 --- a/test/jasmine/tests/modebar_test.js +++ b/test/jasmine/tests/modebar_test.js @@ -660,7 +660,9 @@ describe('ModeBar', function() { yaxis2: { anchor: 'x2', range: [0, 4] - } + }, + width: 600, + height: 500 }; gd = createGraphDiv(); @@ -696,7 +698,7 @@ describe('ModeBar', function() { buttonZoomIn.click(); buttonAutoScale.click(); - assertRange(gd._fullLayout.xaxis.range, [-0.1375913, 2.137591]); + assertRange(gd._fullLayout.xaxis.range, [-0.1584327, 2.1584327]); assertRange(gd._fullLayout.yaxis.range, [0.92675159, 2.073248]); assertRange(gd._fullLayout.xaxis2.range, [-0.5, 2.5]); assertRange(gd._fullLayout.yaxis2.range, [0, 2.105263]); diff --git a/test/jasmine/tests/range_slider_test.js b/test/jasmine/tests/range_slider_test.js index a603c21b324..2ca0ebe89c3 100644 --- a/test/jasmine/tests/range_slider_test.js +++ b/test/jasmine/tests/range_slider_test.js @@ -12,6 +12,8 @@ describe('the range slider', function() { rangeSlider, children; + var sliderY = 393; + describe('when specified as visible', function() { beforeEach(function(done) { @@ -57,7 +59,7 @@ describe('the range slider', function() { dataMinStart = rangeSlider.getAttribute('data-min'), diff = end - start; - slide(start, 400, end, 400).then(function() { + slide(start, sliderY, end, sliderY).then(function() { var maskMin = children[2], handleMin = children[5]; @@ -67,19 +69,25 @@ describe('the range slider', function() { }).then(done); }); + function testTranslate1D(node, val) { + var transformParts = node.getAttribute('transform').split('('); + expect(transformParts[0]).toEqual('translate'); + expect(+transformParts[1].split(')')[0]).toBeCloseTo(val, 0); + } + it('should react to resizing the maximum handle', function(done) { - var start = 705, - end = 500, + var start = 695, + end = 490, dataMaxStart = rangeSlider.getAttribute('data-max'), diff = end - start; - slide(start, 400, end, 400).then(function() { + slide(start, sliderY, end, sliderY).then(function() { var maskMax = children[3], handleMax = children[6]; - expect(rangeSlider.getAttribute('data-max')).toEqual(String(+dataMaxStart + diff)); - expect(maskMax.getAttribute('width')).toEqual(String(-diff)); - expect(handleMax.getAttribute('transform')).toBe('translate(' + (+dataMaxStart + diff) + ')'); + expect(+rangeSlider.getAttribute('data-max')).toBeCloseTo(+dataMaxStart + diff, 0); + expect(+maskMax.getAttribute('width')).toBeCloseTo(-diff); + testTranslate1D(handleMax, +dataMaxStart + diff); }).then(done); }); @@ -89,13 +97,13 @@ describe('the range slider', function() { dataMinStart = rangeSlider.getAttribute('data-min'), diff = end - start; - slide(start, 400, end, 400).then(function() { + slide(start, sliderY, end, sliderY).then(function() { var maskMin = children[2], handleMin = children[5]; - expect(rangeSlider.getAttribute('data-min')).toEqual(String(+dataMinStart + diff)); - expect(maskMin.getAttribute('width')).toEqual(String(diff)); - expect(handleMin.getAttribute('transform')).toEqual('translate(' + (+dataMinStart + diff - 3) + ')'); + expect(+rangeSlider.getAttribute('data-min')).toBeCloseTo(String(+dataMinStart + diff)); + expect(+maskMin.getAttribute('width')).toBeCloseTo(String(diff)); + testTranslate1D(handleMin, +dataMinStart + diff - 3); }).then(done); }); @@ -105,13 +113,13 @@ describe('the range slider', function() { dataMaxStart = rangeSlider.getAttribute('data-max'), diff = end - start; - slide(start, 400, end, 400).then(function() { + slide(start, sliderY, end, sliderY).then(function() { var maskMax = children[3], handleMax = children[6]; - expect(rangeSlider.getAttribute('data-max')).toEqual(String(+dataMaxStart + diff)); - expect(maskMax.getAttribute('width')).toEqual(String(-diff)); - expect(handleMax.getAttribute('transform')).toEqual('translate(' + (+dataMaxStart + diff) + ')'); + expect(+rangeSlider.getAttribute('data-max')).toBeCloseTo(+dataMaxStart + diff); + expect(+maskMax.getAttribute('width')).toBeCloseTo(-diff); + testTranslate1D(handleMax, +dataMaxStart + diff); }).then(done); @@ -124,14 +132,14 @@ describe('the range slider', function() { rangeDiff2, rangeDiff3; - slide(start, 400, end, 400).then(function() { + slide(start, sliderY, end, sliderY).then(function() { rangeDiff2 = gd._fullLayout.xaxis.range[1] - gd._fullLayout.xaxis.range[0]; expect(rangeDiff2).toBeLessThan(rangeDiff1); }).then(function() { start = 400; end = 200; - return slide(start, 400, end, 400); + return slide(start, sliderY, end, sliderY); }).then(function() { rangeDiff3 = gd._fullLayout.xaxis.range[1] - gd._fullLayout.xaxis.range[0]; expect(rangeDiff3).toBeLessThan(rangeDiff2); @@ -142,8 +150,8 @@ describe('the range slider', function() { Plotly.relayout(gd, 'xaxis.range', [10, 20]) .then(function() { expect(gd._fullLayout.xaxis.range).toEqual([10, 20]); - expect(+rangeSlider.getAttribute('data-min')).toBeCloseTo(125.51, 0); - expect(+rangeSlider.getAttribute('data-max')).toBeCloseTo(251.02, 0); + expect(+rangeSlider.getAttribute('data-min')).toBeCloseTo(124.69, -1); + expect(+rangeSlider.getAttribute('data-max')).toBeCloseTo(249.39, -1); }) .then(done); }); @@ -152,7 +160,7 @@ describe('the range slider', function() { Plotly.relayout(gd, 'xaxis.range[0]', 10) .then(function() { expect(gd._fullLayout.xaxis.range[0]).toEqual(10); - expect(+rangeSlider.getAttribute('data-min')).toBeCloseTo(125.51, 0); + expect(+rangeSlider.getAttribute('data-min')).toBeCloseTo(124.69, -1); }) .then(done); }); diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index 5a9f7406c4d..cfc6e132353 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -60,6 +60,38 @@ describe('Test scatter', function() { expect(traceOut.visible).toBe(false); }); + it('should correctly assign \'hoveron\' default', function() { + traceIn = { + x: [1, 2, 3], + y: [1, 2, 3], + mode: 'lines+markers', + fill: 'tonext' + }; + + // fills and markers, you get both hover types + // you need visible: true here, as that normally gets set + // outside of the module supplyDefaults + traceOut = {visible: true}; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.hoveron).toBe('points+fills'); + + // but with only lines (or just fill) and fill tonext or toself + // you get fills + traceIn.mode = 'lines'; + traceOut = {visible: true}; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.hoveron).toBe('fills'); + + // with the wrong fill you always get points + // only area fills default to hoveron points. Vertical or + // horizontal fills don't have the same physical meaning, + // they're generally just filling their own slice, so they + // default to hoveron points. + traceIn.fill = 'tonexty'; + traceOut = {visible: true}; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.hoveron).toBe('points'); + }); }); describe('isBubble', function() { diff --git a/test/jasmine/tests/scatterternary_test.js b/test/jasmine/tests/scatterternary_test.js index 470ec336cb4..21da09876f0 100644 --- a/test/jasmine/tests/scatterternary_test.js +++ b/test/jasmine/tests/scatterternary_test.js @@ -158,6 +158,39 @@ describe('scatterternary defaults', function() { supplyDefaults(traceIn, traceOut, defaultColor, layout); expect(traceOut.hoverinfo).toBe('a+b+c+text'); }); + + it('should correctly assign \'hoveron\' default', function() { + traceIn = { + a: [1, 2, 3], + b: [1, 2, 3], + c: [1, 2, 3], + mode: 'lines+markers', + fill: 'tonext' + }; + + // fills and markers, you get both hover types + // you need visible: true here, as that normally gets set + // outside of the module supplyDefaults + traceOut = {visible: true}; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.hoveron).toBe('points+fills'); + + // but with only lines (or just fill) and fill tonext or toself + // you get fills + traceIn.mode = 'lines'; + traceOut = {visible: true}; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.hoveron).toBe('fills'); + + // without a fill you always get points. For scatterternary, unlike + // scatter, every allowed fill but 'none' is an area fill (rather than + // a vertical / horizontal fill) so they all should default to + // hoveron points. + traceIn.fill = 'none'; + traceOut = {visible: true}; + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.hoveron).toBe('points'); + }); }); describe('scatterternary calc', function() { diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index f709d3b962c..a4fd9bb4997 100644 --- a/test/jasmine/tests/select_test.js +++ b/test/jasmine/tests/select_test.js @@ -13,6 +13,9 @@ var customMatchers = require('../assets/custom_matchers'); describe('select box and lasso', function() { var mock = require('@mocks/14.json'); + var selectPath = [[93, 193], [143, 193]]; + var lassoPath = [[316, 171], [318, 239], [335, 243], [328, 169]]; + beforeEach(function() { jasmine.addMatchers(customMatchers); }); @@ -186,7 +189,7 @@ describe('select box and lasso', function() { doubleClickData = data; }); - drag([[100, 200], [150, 200]]); + drag(selectPath); expect(selectingCnt).toEqual(1, 'with the correct selecting count'); expect(selectingData.points).toEqual([{ @@ -201,7 +204,7 @@ describe('select box and lasso', function() { y: 12.5 }], 'with the correct selecting points'); assertRange(selectingData.range, { - x: [0.0019667582669138295, 0.004546754982054625], + x: [0.002000, 0.0046236], y: [0.10209191961595454, 24.512223978291406] }, 'with the correct selecting range'); @@ -218,7 +221,7 @@ describe('select box and lasso', function() { y: 12.5 }], 'with the correct selected points'); assertRange(selectedData.range, { - x: [0.0019667582669138295, 0.004546754982054625], + x: [0.002000, 0.0046236], y: [0.10209191961595454, 24.512223978291406] }, 'with the correct selected range'); @@ -262,7 +265,7 @@ describe('select box and lasso', function() { doubleClickData = data; }); - drag([[331, 178], [333, 246], [350, 250], [343, 176]]); + drag(lassoPath); expect(selectingCnt).toEqual(3, 'with the correct selecting count'); expect(selectingData.points).toEqual([{ @@ -291,9 +294,6 @@ describe('select box and lasso', function() { var mockCopy = Lib.extendDeep({}, mock); mockCopy.layout.dragmode = 'select'; - var selectPath = [[100, 200], [150, 200]]; - var lassoPath = [[331, 178], [333, 246], [350, 250], [343, 176]]; - var gd = createGraphDiv(); var selectedPtLength; diff --git a/test/jasmine/tests/ternary_test.js b/test/jasmine/tests/ternary_test.js index bf631dfd54d..c00df866914 100644 --- a/test/jasmine/tests/ternary_test.js +++ b/test/jasmine/tests/ternary_test.js @@ -186,8 +186,8 @@ describe('ternary plots', function() { it('should respond zoom drag interactions', function(done) { assertRange(gd, [0.231, 0.2, 0.11]); - drag([[390, 220], [300, 250]]); - assertRange(gd, [0.4486, 0.2480, 0.1453]); + drag([[383, 213], [293, 243]]); + assertRange(gd, [0.4435, 0.2462, 0.1523]); doubleClick(pointPos[0], pointPos[1]).then(function() { assertRange(gd, [0, 0, 0]);