Skip to content

Commit

Permalink
Merge pull request #4420 from plotly/consistent-text-mode-and-pie-ori…
Browse files Browse the repository at this point in the history
…entation

Consistent text mode for bar-like & pie-like traces and feature to control text orientation inside pie/sunburst slices
  • Loading branch information
archmoj authored Dec 23, 2019
2 parents 571ce6b + 2686c46 commit 97ba9c3
Show file tree
Hide file tree
Showing 70 changed files with 6,212 additions and 228 deletions.
69 changes: 43 additions & 26 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1182,31 +1182,48 @@ lib.isHidden = function(gd) {
return !display || display === 'none';
};

lib.getTextTransform = function(opts) {
var textX = opts.textX;
var textY = opts.textY;
var targetX = opts.targetX;
var targetY = opts.targetY;
var scale = opts.scale;
var rotate = opts.rotate;

var transformScale;
var transformRotate;
var transformTranslate;

if(scale < 1) transformScale = 'scale(' + scale + ') ';
else {
scale = 1;
transformScale = '';
}

transformRotate = (rotate) ?
'rotate(' + rotate + ' ' + textX + ' ' + textY + ') ' : '';

// Note that scaling also affects the center of the text box
var translateX = (targetX - scale * textX);
var translateY = (targetY - scale * textY);
transformTranslate = 'translate(' + translateX + ' ' + translateY + ')';
/** Return transform text for bar bar-like rectangles and pie-like slices
* @param {object} transform
* - targetX: desired position on the x-axis
* - targetY: desired position on the y-axis
* - textX: width of text
* - textY: height of text
* - scale: (optional) scale applied after translate
* - rotate: (optional) rotation applied after scale
* - noCenter: when defined no extra arguments needed in rotation
*/
lib.getTextTransform = function(transform) {
var noCenter = transform.noCenter;
var textX = transform.textX;
var textY = transform.textY;
var targetX = transform.targetX;
var targetY = transform.targetY;
var rotate = transform.rotate;
var scale = transform.scale;
if(!scale) scale = 0;
else if(scale > 1) scale = 1;

return (
'translate(' +
(targetX - scale * textX) + ',' +
(targetY - scale * textY) +
')' +
(scale < 1 ?
'scale(' + scale + ')' : ''
) +
(rotate ?
'rotate(' + rotate +
(noCenter ? '' : ' ' + textX + ' ' + textY) +
')' : ''
)
);
};

return transformTranslate + transformScale + transformRotate;
lib.ensureUniformFontSize = function(gd, baseFont) {
var out = lib.extendFlat({}, baseFont);
out.size = Math.max(
baseFont.size,
gd._fullLayout.uniformtext.minsize || 0
);
return out;
};
30 changes: 30 additions & 0 deletions src/plots/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,36 @@ module.exports = {
}),
editType: 'layoutstyle'
},
uniformtext: {
mode: {
valType: 'enumerated',
values: [false, 'hide', 'show'],
dflt: false,
role: 'info',
editType: 'plot',
description: [
'Determines how the font size for various text',
'elements are uniformed between each trace type.',
'If the computed text sizes were smaller than',
'the minimum size defined by `uniformtext.minsize`',
'using *hide* option hides the text; and',
'using *show* option shows the text without further downscaling.',
'Please note that if the size defined by `minsize` is greater than',
'the font size defined by trace, then the `minsize` is used.'
].join(' ')
},
minsize: {
valType: 'number',
min: 0,
dflt: 0,
role: 'info',
editType: 'plot',
description: [
'Sets the minimum text size between traces of the same type.'
].join(' ')
},
editType: 'plot'
},
autosize: {
valType: 'boolean',
role: 'info',
Expand Down
5 changes: 5 additions & 0 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,11 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) {
coerce('title.pad.b');
coerce('title.pad.l');

var uniformtextMode = coerce('uniformtext.mode');
if(uniformtextMode) {
coerce('uniformtext.minsize');
}

// Make sure that autosize is defaulted to *true*
// on layouts with no set width and height for backward compatibly,
// in particular https://plot.ly/javascript/responsive-fluid-layout/
Expand Down
93 changes: 63 additions & 30 deletions src/traces/bar/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,14 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback)
Registry.getComponentMethod('errorbars', 'plot')(gd, bartraces, plotinfo, opts);
}

function appendBarText(gd, plotinfo, bar, calcTrace, i, x0, x1, y0, y1, opts, makeOnCompleteCallback) {
function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;

var fullLayout = gd._fullLayout;
var textPosition;

function appendTextNode(bar, text, textFont) {
function appendTextNode(bar, text, font) {
var textSelection = Lib.ensureSingle(bar, 'text')
.text(text)
.attr({
Expand All @@ -254,25 +254,25 @@ function appendBarText(gd, plotinfo, bar, calcTrace, i, x0, x1, y0, y1, opts, ma
// tex and regular text together
'data-notex': 1
})
.call(Drawing.font, textFont)
.call(Drawing.font, font)
.call(svgTextUtils.convertToTspans, gd);

return textSelection;
}

// get trace attributes
var trace = calcTrace[0].trace;
var trace = cd[0].trace;
var isHorizontal = (trace.orientation === 'h');

var text = getText(fullLayout, calcTrace, i, xa, ya);
var text = getText(fullLayout, cd, i, xa, ya);
textPosition = getTextPosition(trace, i);

// compute text position
var inStackOrRelativeMode =
opts.mode === 'stack' ||
opts.mode === 'relative';

var calcBar = calcTrace[i];
var calcBar = cd[i];
var isOutmostBar = !inStackOrRelativeMode || calcBar._outmost;

if(!text ||
Expand All @@ -285,7 +285,7 @@ function appendBarText(gd, plotinfo, bar, calcTrace, i, x0, x1, y0, y1, opts, ma
}

var layoutFont = fullLayout.font;
var barColor = style.getBarColor(calcTrace[i], trace);
var barColor = style.getBarColor(cd[i], trace);
var insideTextFont = style.getInsideTextFont(trace, i, layoutFont, barColor);
var outsideTextFont = style.getOutsideTextFont(trace, i, layoutFont);

Expand Down Expand Up @@ -318,6 +318,7 @@ function appendBarText(gd, plotinfo, bar, calcTrace, i, x0, x1, y0, y1, opts, ma
var textBB;
var textWidth;
var textHeight;
var font;

if(textPosition === 'outside') {
if(!isOutmostBar && !calcBar.hasB) textPosition = 'inside';
Expand All @@ -327,7 +328,10 @@ function appendBarText(gd, plotinfo, bar, calcTrace, i, x0, x1, y0, y1, opts, ma
if(isOutmostBar) {
// draw text using insideTextFont and check if it fits inside bar
textPosition = 'inside';
textSelection = appendTextNode(bar, text, insideTextFont);

font = Lib.ensureUniformFontSize(gd, insideTextFont);

textSelection = appendTextNode(bar, text, font);

textBB = Drawing.bBox(textSelection.node()),
textWidth = textBB.width,
Expand Down Expand Up @@ -357,9 +361,9 @@ function appendBarText(gd, plotinfo, bar, calcTrace, i, x0, x1, y0, y1, opts, ma
}

if(!textSelection) {
textSelection = appendTextNode(bar, text,
(textPosition === 'outside') ?
outsideTextFont : insideTextFont);
font = Lib.ensureUniformFontSize(gd, (textPosition === 'outside') ? outsideTextFont : insideTextFont);

textSelection = appendTextNode(bar, text, font);

var currentTransform = textSelection.attr('transform');
textSelection.attr('transform', '');
Expand All @@ -374,32 +378,61 @@ function appendBarText(gd, plotinfo, bar, calcTrace, i, x0, x1, y0, y1, opts, ma
}
}

var angle = trace.textangle;

// compute text transform
var transform, constrained;
if(textPosition === 'outside') {
constrained =
trace.constraintext === 'both' ||
trace.constraintext === 'outside';

transform = Lib.getTextTransform(toMoveOutsideBar(x0, x1, y0, y1, textBB, {
transform = toMoveOutsideBar(x0, x1, y0, y1, textBB, {
isHorizontal: isHorizontal,
constrained: constrained,
angle: trace.textangle
}));
angle: angle
});
} else {
constrained =
trace.constraintext === 'both' ||
trace.constraintext === 'inside';

transform = Lib.getTextTransform(toMoveInsideBar(x0, x1, y0, y1, textBB, {
transform = toMoveInsideBar(x0, x1, y0, y1, textBB, {
isHorizontal: isHorizontal,
constrained: constrained,
angle: trace.textangle,
angle: angle,
anchor: trace.insidetextanchor
}));
});
}

transition(textSelection, opts, makeOnCompleteCallback).attr('transform', transform);
transform.fontSize = font.size;
recordMinTextSize(trace.type, transform, fullLayout);
calcBar.transform = transform;

transition(textSelection, opts, makeOnCompleteCallback)
.attr('transform', Lib.getTextTransform(transform));
}

function recordMinTextSize(
traceType, // in
transform, // inout
fullLayout // inout
) {
if(fullLayout.uniformtext.mode) {
var minKey = '_' + traceType + 'Text_minsize';
var minSize = fullLayout.uniformtext.minsize;
var size = transform.scale * transform.fontSize;

transform.hide = size < minSize;

fullLayout[minKey] = fullLayout[minKey] || Infinity;
if(!transform.hide) {
fullLayout[minKey] = Math.min(
fullLayout[minKey],
Math.max(size, minSize)
);
}
}
}

function getRotateFromAngle(angle) {
Expand Down Expand Up @@ -549,15 +582,15 @@ function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) {
};
}

function getText(fullLayout, calcTrace, index, xa, ya) {
var trace = calcTrace[0].trace;
function getText(fullLayout, cd, index, xa, ya) {
var trace = cd[0].trace;
var texttemplate = trace.texttemplate;

var value;
if(texttemplate) {
value = calcTexttemplate(fullLayout, calcTrace, index, xa, ya);
value = calcTexttemplate(fullLayout, cd, index, xa, ya);
} else if(trace.textinfo) {
value = calcTextinfo(calcTrace, index, xa, ya);
value = calcTextinfo(cd, index, xa, ya);
} else {
value = helpers.getValue(trace.text, index);
}
Expand All @@ -570,8 +603,8 @@ function getTextPosition(trace, index) {
return helpers.coerceEnumerated(attributeTextPosition, value);
}

function calcTexttemplate(fullLayout, calcTrace, index, xa, ya) {
var trace = calcTrace[0].trace;
function calcTexttemplate(fullLayout, cd, index, xa, ya) {
var trace = cd[0].trace;
var texttemplate = Lib.castOption(trace, index, 'texttemplate');
if(!texttemplate) return '';
var isWaterfall = (trace.type === 'waterfall');
Expand Down Expand Up @@ -599,7 +632,7 @@ function calcTexttemplate(fullLayout, calcTrace, index, xa, ya) {
return tickText(vAxis, +v, true).text;
}

var cdi = calcTrace[index];
var cdi = cd[index];
var obj = {};

obj.label = cdi.p;
Expand Down Expand Up @@ -640,8 +673,8 @@ function calcTexttemplate(fullLayout, calcTrace, index, xa, ya) {
return Lib.texttemplateString(texttemplate, obj, fullLayout._d3locale, pt, obj, trace._meta || {});
}

function calcTextinfo(calcTrace, index, xa, ya) {
var trace = calcTrace[0].trace;
function calcTextinfo(cd, index, xa, ya) {
var trace = cd[0].trace;
var isHorizontal = (trace.orientation === 'h');
var isWaterfall = (trace.type === 'waterfall');
var isFunnel = (trace.type === 'funnel');
Expand All @@ -657,7 +690,7 @@ function calcTextinfo(calcTrace, index, xa, ya) {
}

var textinfo = trace.textinfo;
var cdi = calcTrace[index];
var cdi = cd[index];

var parts = textinfo.split('+');
var text = [];
Expand All @@ -666,7 +699,7 @@ function calcTextinfo(calcTrace, index, xa, ya) {
var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };

if(hasFlag('label')) {
text.push(formatLabel(calcTrace[index].p));
text.push(formatLabel(cd[index].p));
}

if(hasFlag('text')) {
Expand Down Expand Up @@ -717,5 +750,5 @@ function calcTextinfo(calcTrace, index, xa, ya) {
module.exports = {
plot: plot,
toMoveInsideBar: toMoveInsideBar,
toMoveOutsideBar: toMoveOutsideBar
recordMinTextSize: recordMinTextSize
};
Loading

0 comments on commit 97ba9c3

Please sign in to comment.