From f99ee3b4c2a2a40d04de12ce25a15bb9127b0dac Mon Sep 17 00:00:00 2001 From: Moi Date: Wed, 6 Jan 2021 09:39:30 +0100 Subject: [PATCH 1/5] [Bars] Add possibility to display totals for bars Contributes to #85 --- src/traces/bar/layout_attributes.js | 14 ++++++++- src/traces/bar/layout_defaults.js | 2 ++ src/traces/bar/plot.js | 47 ++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/traces/bar/layout_attributes.js b/src/traces/bar/layout_attributes.js index 674d8770824..57c177bd912 100644 --- a/src/traces/bar/layout_attributes.js +++ b/src/traces/bar/layout_attributes.js @@ -63,5 +63,17 @@ module.exports = { 'Sets the gap (in plot fraction) between bars of', 'the same location coordinate.' ].join(' ') - } + }, + displaytotal: { + valType: 'boolean', + dflt: false, + role: 'style', + description: 'Display the total value of each bar.' + }, + totaltemplate: { + valType: 'string', + dflt: '%{total}', + role: 'style', + 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 ca6b8b39a7e..df749df2599 100644 --- a/src/traces/bar/layout_defaults.js +++ b/src/traces/bar/layout_defaults.js @@ -55,4 +55,6 @@ module.exports = function(layoutIn, layoutOut, fullData) { coerce('bargap', (shouldBeGapless && !gappedAnyway) ? 0 : 0.2); coerce('bargroupgap'); + coerce('displaytotal'); + coerce('totaltemplate'); }; diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index b5827dcd3fe..e41a4e78c56 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -251,6 +251,9 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) } appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback); + if(fullLayout.displaytotal) { + 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); @@ -275,7 +278,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, @@ -443,6 +446,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; } From 94930504cba43141fc1ae7cdf1fbc8f6494ecfe1 Mon Sep 17 00:00:00 2001 From: Moi Date: Wed, 6 Jan 2021 09:45:50 +0100 Subject: [PATCH 2/5] [Bars] Force text to be inside if total is to be displayed Contributes to #85 --- src/traces/bar/plot.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index e41a4e78c56..a98d0f7c30f 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -298,7 +298,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 = getTextPosition(trace, i, fullLayout.displaytotal); // compute text position var inStackOrRelativeMode = @@ -673,7 +673,8 @@ function getText(fullLayout, cd, index, xa, ya) { return helpers.coerceString(attributeText, value); } -function getTextPosition(trace, index) { +function getTextPosition(trace, index, forceInside) { + if(forceInside) return 'inside'; var value = helpers.getValue(trace.textposition, index); return helpers.coerceEnumerated(attributeTextPosition, value); } From 0743a992ba05a047a26593fa1f8fb48628105285 Mon Sep 17 00:00:00 2001 From: Moi Date: Thu, 7 Jan 2021 10:53:59 +0100 Subject: [PATCH 3/5] [Bars] Option to display totals on top of each bar Rename option Refactor code according to comments Contributes to #85 --- src/traces/bar/layout_attributes.js | 4 +++- src/traces/bar/layout_defaults.js | 4 ++-- src/traces/bar/plot.js | 5 ++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/traces/bar/layout_attributes.js b/src/traces/bar/layout_attributes.js index 57c177bd912..0375a507a52 100644 --- a/src/traces/bar/layout_attributes.js +++ b/src/traces/bar/layout_attributes.js @@ -64,16 +64,18 @@ module.exports = { 'the same location coordinate.' ].join(' ') }, - displaytotal: { + 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 df749df2599..845d2a9bab8 100644 --- a/src/traces/bar/layout_defaults.js +++ b/src/traces/bar/layout_defaults.js @@ -55,6 +55,6 @@ module.exports = function(layoutIn, layoutOut, fullData) { coerce('bargap', (shouldBeGapless && !gappedAnyway) ? 0 : 0.2); coerce('bargroupgap'); - coerce('displaytotal'); - coerce('totaltemplate'); + var showTotal = coerce('barshowtotal'); + if(showTotal) coerce('totaltemplate'); }; diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index a98d0f7c30f..87a8139e00a 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -298,7 +298,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, fullLayout.displaytotal); + textPosition = fullLayout.barshowtotal ? 'inside' : getTextPosition(trace, i); // compute text position var inStackOrRelativeMode = @@ -673,8 +673,7 @@ function getText(fullLayout, cd, index, xa, ya) { return helpers.coerceString(attributeText, value); } -function getTextPosition(trace, index, forceInside) { - if(forceInside) return 'inside'; +function getTextPosition(trace, index) { var value = helpers.getValue(trace.textposition, index); return helpers.coerceEnumerated(attributeTextPosition, value); } From 66852fb8d0f7b1584acc8974357c0e5120098717 Mon Sep 17 00:00:00 2001 From: Moi Date: Wed, 13 Jul 2022 18:14:51 +0200 Subject: [PATCH 4/5] [Bars] Fix option name for displaying totals --- src/traces/bar/plot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index c241024fc73..9f74cd5f77b 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -243,7 +243,7 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) } appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback); - if(fullLayout.displaytotal) { + if(fullLayout.barshowtotal) { appendBarTotal(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback); } From db3d1d6e5fd86f6e15dd9d62999490de15affe59 Mon Sep 17 00:00:00 2001 From: Moi Date: Wed, 13 Jul 2022 18:15:16 +0200 Subject: [PATCH 5/5] [Bars] Add test for displaying totals --- .../bar_stackrelative_negative_total.json | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 test/image/mocks/bar_stackrelative_negative_total.json 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" + } +}