diff --git a/src/traces/bar/layout_attributes.js b/src/traces/bar/layout_attributes.js index eb47d03ccc4..cee00a29c07 100644 --- a/src/traces/bar/layout_attributes.js +++ b/src/traces/bar/layout_attributes.js @@ -51,5 +51,19 @@ module.exports = { 'Sets the gap (in plot fraction) between bars of', 'the same location coordinate.' ].join(' ') - } + }, + barshowtotal: { + valType: 'boolean', + dflt: false, + role: 'style', + editType: 'plot', + description: 'Display the total value of each bar.' + }, + totaltemplate: { + valType: 'string', + dflt: '%{total}', + role: 'style', + editType: 'plot', + description: 'Template to display the total value of each bar.' + }, }; diff --git a/src/traces/bar/layout_defaults.js b/src/traces/bar/layout_defaults.js index 99324539921..f125b8c1178 100644 --- a/src/traces/bar/layout_defaults.js +++ b/src/traces/bar/layout_defaults.js @@ -47,4 +47,6 @@ module.exports = function(layoutIn, layoutOut, fullData) { coerce('bargap', (shouldBeGapless && !gappedAnyway) ? 0 : 0.2); coerce('bargroupgap'); + var showTotal = coerce('barshowtotal'); + if(showTotal) coerce('totaltemplate'); }; diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 9866bd57156..9f74cd5f77b 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -243,6 +243,9 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) } appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback); + if(fullLayout.barshowtotal) { + appendBarTotal(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback); + } if(plotinfo.layerClipId) { Drawing.hideOutsideRangePoint(di, bar.select('text'), xa, ya, trace.xcalendar, trace.ycalendar); @@ -267,7 +270,7 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCom var textPosition; function appendTextNode(bar, text, font) { - var textSelection = Lib.ensureSingle(bar, 'text') + var textSelection = Lib.ensureSingle(bar, 'text', 'bartext-' + textPosition) .text(text) .attr({ 'class': 'bartext bartext-' + textPosition, @@ -287,7 +290,7 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCom var isHorizontal = (trace.orientation === 'h'); var text = getText(fullLayout, cd, i, xa, ya); - textPosition = getTextPosition(trace, i); + textPosition = fullLayout.barshowtotal ? 'inside' : getTextPosition(trace, i); // compute text position var inStackOrRelativeMode = @@ -435,6 +438,48 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCom .attr('transform', Lib.getTextTransform(transform)); } +// total for stacked bars +function appendBarTotal(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback) { + var fullLayout = gd._fullLayout; + // get trace attributes + var trace = cd[0].trace; + var isHorizontal = (trace.orientation === 'h'); + var inStackOrRelativeMode = + opts.mode === 'stack' || + opts.mode === 'relative'; + var calcBar = cd[i]; + var isOutmostBar = !inStackOrRelativeMode || calcBar._outmost; + + if(isOutmostBar) { + var layoutFont = fullLayout.font; + var font = style.getOutsideTextFont(trace, i, layoutFont); + var totalTemplate = fullLayout.totaltemplate; + var obj = {total: isHorizontal ? calcBar.x : calcBar.y}; + var totalText = Lib.texttemplateString(totalTemplate, obj, fullLayout._d2locale, {}, obj, trace._meta || {}); + var totalSelection = Lib.ensureSingle(bar, 'text', 'bartext-outside') + .text(totalText) + .attr({ + 'class': 'bartext bartext-outside', + 'text-anchor': 'middle', + // prohibit tex interpretation until we can handle + // tex and regular text together + 'data-notex': 1 + }) + .call(Drawing.font, font) + .call(svgTextUtils.convertToTspans, gd); + + var textBB = Drawing.bBox(totalSelection.node()); + var transform = toMoveOutsideBar(x0, x1, y0, y1, textBB, { + isHorizontal: isHorizontal, + constrained: false, + angle: trace.textangle + }); + + transition(totalSelection, fullLayout, opts, makeOnCompleteCallback) + .attr('transform', Lib.getTextTransform(transform)); + } +} + function getRotateFromAngle(angle) { return (angle === 'auto') ? 0 : angle; } diff --git a/test/image/mocks/bar_stackrelative_negative_total.json b/test/image/mocks/bar_stackrelative_negative_total.json new file mode 100644 index 00000000000..54047621a40 --- /dev/null +++ b/test/image/mocks/bar_stackrelative_negative_total.json @@ -0,0 +1,34 @@ +{ + "data":[ + { + "name":"Col1", + "y":["-1","2","3","4","5"], + "x":["1","2","3","4","5"], + "type":"bar" + }, + { + "name":"Col2", + "y":["2","3","4","-3","2"], + "x":["1","2","3","4","5"], + "type":"bar" + }, + { + "name":"Col3", + "y":["5","4","3","-2","1"], + "x":["1","2","3","4","5"], + "type":"bar" + }, + { + "name":"Col4", + "y":["-3","0","1","0","-3"], + "x":["1","2","3","4","5"], + "type":"bar" + } + ], + "layout":{ + "height":400, + "width":400, + "barshowtotal": true, + "barmode":"relative" + } +}