diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index c60a10197eb..31cc64ed3ff 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -67,9 +67,13 @@ module.exports = function plot(gd, plotinfo, cdModule, traceLayer, opts) { var bartraces = Lib.makeTraceGroups(traceLayer, cdModule, 'trace bars').each(function(cd) { var plotGroup = d3.select(this); var trace = cd[0].trace; + var isWaterfall = (trace.type === 'waterfall'); + var isFunnel = (trace.type === 'funnel'); + var isBar = (trace.type === 'bar'); + var shouldDisplayZeros = isBar || isFunnel; var adjustPixel = 0; - if(trace.type === 'waterfall' && trace.connector.visible && trace.connector.mode === 'between') { + if(isWaterfall && trace.connector.visible && trace.connector.mode === 'between') { adjustPixel = trace.connector.line.width / 2; } @@ -101,10 +105,11 @@ module.exports = function plot(gd, plotinfo, cdModule, traceLayer, opts) { var y0 = xy[1][0]; var y1 = xy[1][1]; - var isBlank = di.isBlank = ( - !isNumeric(x0) || !isNumeric(x1) || - !isNumeric(y0) || !isNumeric(y1) || - x0 === x1 || y0 === y1 + var isBlank = di.isBlank = !( + isNumeric(x0) && isNumeric(x1) && + isNumeric(y0) && isNumeric(y1) && + (x0 !== x1 || (shouldDisplayZeros && isHorizontal)) && + (y0 !== y1 || (shouldDisplayZeros && !isHorizontal)) ); // in waterfall mode `between` we need to adjust bar end points to match the connector width @@ -231,7 +236,7 @@ function appendBarText(gd, plotinfo, bar, calcTrace, i, x0, x1, y0, y1, opts) { if(!text || textPosition === 'none' || - (calcBar.isBlank && ( + ((calcBar.isBlank || x0 === x1 || y0 === y1) && ( textPosition === 'auto' || textPosition === 'inside'))) { bar.select('text').remove(); @@ -513,6 +518,8 @@ function getTextPosition(trace, index) { function calcTextinfo(calcTrace, index, xa, ya) { var trace = calcTrace[0].trace; var isHorizontal = (trace.orientation === 'h'); + var isWaterfall = (trace.type === 'waterfall'); + var isFunnel = (trace.type === 'funnel'); function formatLabel(u) { var pAxis = isHorizontal ? ya : xa; @@ -542,7 +549,7 @@ function calcTextinfo(calcTrace, index, xa, ya) { if(tx === 0 || tx) text.push(tx); } - if(trace.type === 'waterfall') { + if(isWaterfall) { var delta = +cdi.rawS || cdi.s; var final = cdi.v; var initial = final - delta; @@ -552,7 +559,7 @@ function calcTextinfo(calcTrace, index, xa, ya) { if(hasFlag('final')) text.push(formatNumber(final)); } - if(trace.type === 'funnel') { + if(isFunnel) { if(hasFlag('value')) text.push(formatNumber(cdi.s)); var nPercent = 0; diff --git a/test/image/baselines/bar-marker-line-colorscales.png b/test/image/baselines/bar-marker-line-colorscales.png index 50b1bc8520f..983601da6f2 100644 Binary files a/test/image/baselines/bar-marker-line-colorscales.png and b/test/image/baselines/bar-marker-line-colorscales.png differ diff --git a/test/image/baselines/bar_display_height_zero.png b/test/image/baselines/bar_display_height_zero.png new file mode 100644 index 00000000000..c69a36182ae Binary files /dev/null and b/test/image/baselines/bar_display_height_zero.png differ diff --git a/test/image/baselines/error_bar_bar_ids.png b/test/image/baselines/error_bar_bar_ids.png index 910fb032246..19fc0644359 100644 Binary files a/test/image/baselines/error_bar_bar_ids.png and b/test/image/baselines/error_bar_bar_ids.png differ diff --git a/test/image/baselines/funnel_horizontal_group_basic.png b/test/image/baselines/funnel_horizontal_group_basic.png index 321bb9f0cb0..59fa1b808a2 100644 Binary files a/test/image/baselines/funnel_horizontal_group_basic.png and b/test/image/baselines/funnel_horizontal_group_basic.png differ diff --git a/test/image/baselines/funnel_vertical_overlay_custom_arrays.png b/test/image/baselines/funnel_vertical_overlay_custom_arrays.png index a57b974c331..982db8a7259 100644 Binary files a/test/image/baselines/funnel_vertical_overlay_custom_arrays.png and b/test/image/baselines/funnel_vertical_overlay_custom_arrays.png differ diff --git a/test/image/mocks/bar_display_height_zero.json b/test/image/mocks/bar_display_height_zero.json new file mode 100644 index 00000000000..889aac57205 --- /dev/null +++ b/test/image/mocks/bar_display_height_zero.json @@ -0,0 +1,180 @@ +{ + "data": [ + { + "type": "bar", + "marker": { + "line": { + "width": 10 + } + }, + "x": [ + "A", + "B", + "C" + ], + "y": [ + 0, + null, + 1 + ], + "text": [ + 0, + null, + 1 + ], + "textposition": "auto", + "insidetextanchor": "middle", + "cliponaxis": false + }, + { + "type": "bar", + "marker": { + "line": { + "width": 10 + } + }, + "x": [ + "A", + "B", + "C" + ], + "y": [ + 0, + null, + 1 + ], + "text": [ + 0, + null, + 1 + ], + "textposition": "auto", + "insidetextanchor": "middle", + "cliponaxis": false, + "xaxis": "x2", + "yaxis": "y2" + }, + { + "type": "bar", + "marker": { + "line": { + "width": 10 + } + }, + "orientation": "h", + "x": [ + 0, + null, + 1 + ], + "y": [ + "A", + "B", + "C" + ], + "text": [ + 0, + null, + 1 + ], + "textposition": "auto", + "insidetextanchor": "middle", + "cliponaxis": false, + "xaxis": "x3", + "yaxis": "y3" + }, + { + "type": "bar", + "marker": { + "line": { + "width": 10 + } + }, + "orientation": "h", + "x": [ + 0, + null, + 1 + ], + "y": [ + "A", + "B", + "C" + ], + "text": [ + 0, + null, + 1 + ], + "textposition": "auto", + "insidetextanchor": "middle", + "cliponaxis": false, + "xaxis": "x4", + "yaxis": "y4" + } + ], + "layout": { + "showlegend": true, + "width": 800, + "height": 800, + "dragmode": "pan", + "xaxis": { + "domain": [ + 0, + 0.48 + ] + }, + "xaxis2": { + "autorange": "reversed", + "anchor": "y2", + "domain": [ + 0.52, + 1 + ] + }, + "xaxis3": { + "anchor": "y3", + "domain": [ + 0, + 0.48 + ] + }, + "xaxis4": { + "autorange": "reversed", + "anchor": "y4", + "domain": [ + 0.52, + 1 + ] + }, + "yaxis": { + "domain": [ + 0, + 0.48 + ] + }, + "yaxis2": { + "autorange": "reversed", + "anchor": "x2", + "domain": [ + 0.52, + 1 + ] + }, + "yaxis3": { + "anchor": "x3", + "domain": [ + 0.52, + 1 + ] + }, + "yaxis4": { + "autorange": "reversed", + "anchor": "x4", + "domain": [ + 0, + 0.48 + ] + } + } +} diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 51deb89951f..8fafc0a9ada 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -1900,6 +1900,110 @@ describe('A bar plot', function() { .catch(failTest) .then(done); }); + + it('should show/hide text in clipped and non-clipped layers', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/bar_cliponaxis-false.json')); + gd = createGraphDiv(); + + // only show one bar trace + fig.data = [fig.data[0]]; + + // add a non-bar trace to make sure its module layer gets clipped + fig.data.push({ + type: 'contour', + z: [[0, 0.5, 1], [0.5, 1, 3]] + }); + + function _assertClip(sel, exp, size, msg) { + if(exp === null) { + expect(sel.size()).toBe(0, msg + 'selection should not exist'); + } else { + assertClip(sel, exp, size, msg); + } + } + + function _assert(layerClips, barDisplays, barTextDisplays, barClips) { + var subplotLayer = d3.select('.plot'); + var barLayer = subplotLayer.select('.barlayer'); + + _assertClip(subplotLayer, layerClips[0], 1, 'subplot layer'); + _assertClip(subplotLayer.select('.contourlayer'), layerClips[1], 1, 'some other trace layer'); + _assertClip(barLayer, layerClips[2], 1, 'bar layer'); + + assertNodeDisplay( + barLayer.selectAll('.point'), + barDisplays, + 'bar points (never hidden by display attr)' + ); + assertNodeDisplay( + barLayer.selectAll('.bartext'), + barTextDisplays, + 'bar text' + ); + + assertClip( + barLayer.selectAll('.point > path'), + barClips[0], barClips[1], + 'bar clips' + ); + } + + Plotly.newPlot(gd, fig).then(function() { + _assert( + [false, true, false], + [null, null, null], + [null, null, 'none'], + [true, 3] + ); + return Plotly.restyle(gd, 'visible', false); + }) + .then(function() { + _assert( + [true, null, null], + [], + [], + [false, 0] + ); + return Plotly.restyle(gd, {visible: true, cliponaxis: null}); + }) + .then(function() { + _assert( + [true, false, false], + [null, null, null], + [null, null, null], + [false, 3] + ); + return Plotly.restyle(gd, 'cliponaxis', false); + }) + .then(function() { + _assert( + [false, true, false], + [null, null, null], + [null, null, 'none'], + [true, 3] + ); + return Plotly.relayout(gd, 'yaxis.range', [0, 1]); + }) + .then(function() { + _assert( + [false, true, false], + [null, null, null], + ['none', 'none', 'none'], + [true, 3] + ); + return Plotly.relayout(gd, 'yaxis.range', [0, 4]); + }) + .then(function() { + _assert( + [false, true, false], + [null, null, null], + [null, null, null], + [true, 3] + ); + }) + .catch(failTest) + .then(done); + }); }); describe('bar visibility toggling:', function() { @@ -2032,15 +2136,17 @@ describe('bar hover', function() { afterEach(destroyGraphDiv); - function getPointData(gd) { + function getPointData(gd, curveNumber) { + curveNumber = curveNumber || 0; + var cd = gd.calcdata; var subplot = gd._fullLayout._plots.xy; return { index: false, distance: 20, - cd: cd[0], - trace: cd[0][0].trace, + cd: cd[curveNumber], + trace: cd[curveNumber][0].trace, xa: subplot.xaxis, ya: subplot.yaxis, maxHoverDistance: 20 @@ -2317,108 +2423,33 @@ describe('bar hover', function() { }); }); - it('should show/hide text in clipped and non-clipped layers', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/bar_cliponaxis-false.json')); - gd = createGraphDiv(); - - // only show one bar trace - fig.data = [fig.data[0]]; - - // add a non-bar trace to make sure its module layer gets clipped - fig.data.push({ - type: 'contour', - z: [[0, 0.5, 1], [0.5, 1, 3]] + describe('should include info of height=0 bars on hover', function() { + var modes = ['stack', 'overlay', 'group']; + + modes.forEach(function(m) { + it('- under barmode:' + m, function(done) { + gd = createGraphDiv(); + + Plotly.plot(gd, [{ + type: 'bar', + y: [0, 1, 0] + }, { + type: 'bar', + y: [1, 0, 1] + }], { + barmode: m + }) + .then(function() { + var pt0 = Bar.hoverPoints(getPointData(gd, 0), 0, 1, 'x')[0]; + var pt1 = Bar.hoverPoints(getPointData(gd, 1), 0, 1, 'x')[0]; + + expect(pt0.yLabelVal).toBe(0, 'y label value for data[0]'); + expect(pt1.yLabelVal).toBe(1, 'y label value for data[1]'); + }) + .catch(failTest) + .then(done); + }); }); - - function _assertClip(sel, exp, size, msg) { - if(exp === null) { - expect(sel.size()).toBe(0, msg + 'selection should not exist'); - } else { - assertClip(sel, exp, size, msg); - } - } - - function _assert(layerClips, barDisplays, barTextDisplays, barClips) { - var subplotLayer = d3.select('.plot'); - var barLayer = subplotLayer.select('.barlayer'); - - _assertClip(subplotLayer, layerClips[0], 1, 'subplot layer'); - _assertClip(subplotLayer.select('.contourlayer'), layerClips[1], 1, 'some other trace layer'); - _assertClip(barLayer, layerClips[2], 1, 'bar layer'); - - assertNodeDisplay( - barLayer.selectAll('.point'), - barDisplays, - 'bar points (never hidden by display attr)' - ); - assertNodeDisplay( - barLayer.selectAll('.bartext'), - barTextDisplays, - 'bar text' - ); - - assertClip( - barLayer.selectAll('.point > path'), - barClips[0], barClips[1], - 'bar clips' - ); - } - - Plotly.newPlot(gd, fig).then(function() { - _assert( - [false, true, false], - [null, null, null], - [null, null, 'none'], - [true, 3] - ); - return Plotly.restyle(gd, 'visible', false); - }) - .then(function() { - _assert( - [true, null, null], - [], - [], - [false, 0] - ); - return Plotly.restyle(gd, {visible: true, cliponaxis: null}); - }) - .then(function() { - _assert( - [true, false, false], - [null, null, null], - [null, null, null], - [false, 3] - ); - return Plotly.restyle(gd, 'cliponaxis', false); - }) - .then(function() { - _assert( - [false, true, false], - [null, null, null], - [null, null, 'none'], - [true, 3] - ); - return Plotly.relayout(gd, 'yaxis.range', [0, 1]); - }) - .then(function() { - _assert( - [false, true, false], - [null, null, null], - ['none', 'none', 'none'], - [true, 3] - ); - return Plotly.relayout(gd, 'yaxis.range', [0, 4]); - }) - .then(function() { - _assert( - [false, true, false], - [null, null, null], - [null, null, null], - [true, 3] - ); - }) - .catch(failTest) - .then(done); }); });