Skip to content

Commit 0bfed6e

Browse files
committed
add padding to autorange if bars have text and textposition='outside' to avoid text being clipped
1 parent 4f7a513 commit 0bfed6e

File tree

1 file changed

+113
-6
lines changed

1 file changed

+113
-6
lines changed

src/traces/bar/cross_trace_calc.js

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ var Registry = require('../../registry');
88
var Axes = require('../../plots/cartesian/axes');
99
var getAxisGroup = require('../../plots/cartesian/constraints').getAxisGroup;
1010
var Sieve = require('./sieve.js');
11+
var TEXTPAD = require('./constants').TEXTPAD;
12+
var Drawing = require('../../components/drawing');
13+
var d3 = require('@plotly/d3');
1114

1215
/*
1316
* Bar chart stacking/grouping positioning and autoscaling calculations
@@ -567,10 +570,14 @@ function setBaseAndTop(sa, sieve) {
567570
}
568571
}
569572

570-
fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
573+
var extremesOpts = {
571574
tozero: tozero,
572575
padded: true
573-
});
576+
};
577+
578+
addTextPadding(extremesOpts, fullTrace, sa);
579+
580+
fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, extremesOpts);
574581
}
575582
}
576583

@@ -641,12 +648,17 @@ function stackBars(sa, sieve, opts) {
641648

642649
// if barnorm is set, let normalizeBars update the axis range
643650
if(!opts.norm) {
644-
fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
651+
var extremesOpts = {
645652
// N.B. we don't stack base with 'base',
646653
// so set tozero:true always!
647654
tozero: true,
648655
padded: true
649-
});
656+
};
657+
658+
// Add text padding if needed
659+
addTextPadding(extremesOpts, fullTrace, sa);
660+
661+
fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, extremesOpts);
650662
}
651663
}
652664
}
@@ -752,10 +764,13 @@ function normalizeBars(sa, sieve, opts) {
752764
}
753765
}
754766

755-
fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
767+
var extremesOpts = {
756768
tozero: tozero,
757769
padded: padded
758-
});
770+
};
771+
addTextPadding(extremesOpts, fullTrace, sa);
772+
773+
fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, extremesOpts);
759774
}
760775
}
761776

@@ -864,6 +879,98 @@ function getAxisLetter(ax) {
864879
return ax._id.charAt(0);
865880
}
866881

882+
function measureTextWidth(txt, font) {
883+
var element = document.createElementNS('http://www.w3.org/2000/svg', 'text');
884+
var sel = d3.select(element);
885+
sel.text(txt)
886+
.attr('x', 0)
887+
.attr('y', 0)
888+
.attr('data-unformatted', txt)
889+
.call(Drawing.font, font);
890+
891+
var width = Drawing.bBox(sel.node()).width;
892+
893+
// Clean up the temporary element
894+
element.remove();
895+
896+
return width;
897+
}
898+
899+
function findExtremeIndices(values) {
900+
var maxPositiveIdx = -1, maxNegativeIdx = -1;
901+
var maxPositiveVal = -Infinity, maxNegativeVal = Infinity;
902+
903+
for(var i = 0; i < values.length; i++) {
904+
if(values[i] > 0 && values[i] > maxPositiveVal) {
905+
maxPositiveVal = values[i];
906+
maxPositiveIdx = i;
907+
}
908+
if(values[i] < 0 && values[i] < maxNegativeVal) {
909+
maxNegativeVal = values[i];
910+
maxNegativeIdx = i;
911+
}
912+
}
913+
return {positive: maxPositiveIdx, negative: maxNegativeIdx};
914+
}
915+
916+
function getMaxLineCount(texts) {
917+
var maxLines = 1;
918+
for(var i = 0; i < texts.length; i++) {
919+
if(texts[i]) {
920+
var lineCount = (texts[i].toString().match(/<br>/gi) || []).length + 1;
921+
maxLines = Math.max(maxLines, lineCount);
922+
}
923+
}
924+
return maxLines;
925+
}
926+
927+
function addTextPadding(extremesOpts, trace, sa) {
928+
var textpos = trace.textposition;
929+
if(!trace.text || !textpos) return;
930+
931+
var hasOutsideText = Array.isArray(textpos) ?
932+
textpos.indexOf('outside') !== -1 : textpos === 'outside';
933+
if(!hasOutsideText) return;
934+
935+
var font = trace.outsidetextfont || trace.textfont;
936+
var fontSize = font && font.size;
937+
if(!fontSize) return;
938+
939+
if(Array.isArray(fontSize)) {
940+
fontSize = Math.max.apply(Math, fontSize.filter(function(s) { return typeof s === 'number'; }));
941+
if(!fontSize) return;
942+
}
943+
944+
var isHorizontal = trace.orientation === 'h';
945+
var values = isHorizontal ? trace.x : trace.y;
946+
if(!values) return;
947+
948+
var extremes = findExtremeIndices(values);
949+
var positivePadding = 0, negativePadding = 0;
950+
951+
if(isHorizontal) {
952+
if(extremes.positive >= 0 && trace.text[extremes.positive]) {
953+
positivePadding = measureTextWidth(trace.text[extremes.positive], font) + TEXTPAD;
954+
}
955+
if(extremes.negative >= 0 && trace.text[extremes.negative]) {
956+
negativePadding = measureTextWidth(trace.text[extremes.negative], font) + TEXTPAD;
957+
}
958+
} else {
959+
// For vertical bars, calculate height based on text for each extreme bar
960+
if(extremes.positive >= 0 && trace.text[extremes.positive]) {
961+
var positiveTexts = [trace.text[extremes.positive]];
962+
positivePadding = fontSize * getMaxLineCount(positiveTexts) + TEXTPAD;
963+
}
964+
if(extremes.negative >= 0 && trace.text[extremes.negative]) {
965+
var negativeTexts = [trace.text[extremes.negative]];
966+
negativePadding = fontSize * getMaxLineCount(negativeTexts) + TEXTPAD;
967+
}
968+
}
969+
970+
if(extremes.positive >= 0 && positivePadding > 0) extremesOpts.ppadplus = positivePadding;
971+
if(extremes.negative >= 0 && negativePadding > 0) extremesOpts.ppadminus = negativePadding;
972+
}
973+
867974
module.exports = {
868975
crossTraceCalc: crossTraceCalc,
869976
setGroupPositions: setGroupPositions

0 commit comments

Comments
 (0)