Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consistent text mode for bar-like & pie-like traces and feature to control text orientation inside pie/sunburst slices #4420

Merged
merged 20 commits into from
Dec 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0d3e289
add uniformtext attributes to layout
archmoj Dec 12, 2019
c095271
simplify transform function and expand to reuse in pie and sunburst
archmoj Dec 12, 2019
b234d7a
implement uniformtext for bar-like traces as well as funnelarea and t…
archmoj Dec 12, 2019
f0ac957
implement uniformtext and insidetext-orientation for pie and sunburst
archmoj Dec 12, 2019
4d5fb3d
add new mocks to test insidetextorientation and uniformtext
archmoj Dec 12, 2019
a27dc29
Consider first review
archmoj Dec 13, 2019
1479943
make tangential go towards noon and 6 and make radial go towards 3 and 9
archmoj Dec 16, 2019
eab205a
rewrite uniform text style
archmoj Dec 16, 2019
5348c58
add new mocks cases by Nicolas
archmoj Dec 18, 2019
15ce24d
apply zero scale for hiding text elements
archmoj Dec 19, 2019
0840d82
add new mocks using both inside-text-orientation and uniformtext
archmoj Dec 19, 2019
328ffd0
move reposition logic outside pie transformInsideText function
archmoj Dec 19, 2019
2a31685
apply next text position when it is not at the center
archmoj Dec 19, 2019
d1b50b1
Apply polar coordinates to move text inside the slice during sunburst…
archmoj Dec 19, 2019
2cb5687
sunburst handle no text when using various inside-text-orientation op…
archmoj Dec 20, 2019
beed042
add react tests for treemap and pie uniform text as well as inside te…
archmoj Dec 20, 2019
51d7e1b
add tests for updating text positions using different inside-text-pos…
archmoj Dec 20, 2019
603e0cc
wip
etpinard Dec 20, 2019
8784274
first attempt at fixing tests on etpinard's laptop
etpinard Dec 23, 2019
2686c46
Fix funnelarea, pie, sunburst and treemap tests
archmoj Dec 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
etpinard marked this conversation as resolved.
Show resolved Hide resolved
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