Skip to content

Commit 13f0f6a

Browse files
committed
bar: update algorithm to position bar texts
* Inside texts are rotated only if the rotation helps shrink the text as little as possible. * Outside texts are rotated only if the rotation doesn't leave the text perpendicular to the bar.
1 parent 3fd0d18 commit 13f0f6a

File tree

1 file changed

+147
-86
lines changed

1 file changed

+147
-86
lines changed

src/traces/bar/plot.js

Lines changed: 147 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -272,136 +272,197 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) {
272272
}
273273
}
274274

275-
// compute translate transform
275+
// set text transform
276276
var transform;
277277
if(textPosition === 'outside') {
278278
transform = getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB,
279279
trace.orientation);
280280
}
281281
else {
282-
transform = getTransformToMoveInsideBar(x0, x1, y0, y1, textBB);
282+
transform = getTransformToMoveInsideBar(x0, x1, y0, y1, textBB,
283+
trace.orientation);
283284
}
284285

285286
textSelection.attr('transform', transform);
286287
}
287288

288-
function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB) {
289+
function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) {
289290
// compute text and target positions
290-
var barWidth = Math.abs(x1 - x0),
291-
barHeight = Math.abs(y1 - y0),
292-
textWidth = textBB.width,
291+
var textWidth = textBB.width,
293292
textHeight = textBB.height,
294-
barX = (x0 + x1) / 2,
295-
barY = (y0 + y1) / 2,
296293
textX = (textBB.left + textBB.right) / 2,
297-
textY = (textBB.top + textBB.bottom) / 2;
298-
299-
// apply 3px target padding
300-
var targetWidth = barWidth - 2 * TEXTPAD,
301-
targetHeight = barHeight - 2 * TEXTPAD;
302-
303-
return getTransform(
304-
textX, textY, textWidth, textHeight,
305-
barX, barY, targetWidth, targetHeight);
306-
}
294+
textY = (textBB.top + textBB.bottom) / 2,
295+
barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD,
296+
barHeight = Math.abs(y1 - y0) - 2 * TEXTPAD,
297+
targetWidth,
298+
targetHeight,
299+
targetX,
300+
targetY;
301+
302+
// compute rotation and scale
303+
var needsRotating,
304+
scale;
305+
306+
if(textWidth <= barWidth && textHeight <= barHeight) {
307+
// no scale or rotation is required
308+
needsRotating = false;
309+
scale = 1;
310+
}
311+
else if(textWidth <= barHeight && textHeight <= barWidth) {
312+
// only rotation is required
313+
needsRotating = true;
314+
scale = 1;
315+
}
316+
else if((textWidth < textHeight) === (barWidth < barHeight)) {
317+
// only scale is required
318+
needsRotating = false;
319+
scale = Math.min(barWidth / textWidth, barHeight / textHeight);
320+
}
321+
else {
322+
// both scale and rotation are required
323+
needsRotating = true;
324+
scale = Math.min(barHeight / textWidth, barWidth / textHeight);
325+
}
307326

308-
function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) {
309327
// compute text and target positions
310-
var textWidth = textBB.width,
311-
textHeight = textBB.height,
312-
textX = (textBB.left + textBB.right) / 2,
313-
textY = (textBB.top + textBB.bottom) / 2;
328+
if(needsRotating) {
329+
targetWidth = scale * textHeight;
330+
targetHeight = scale * textWidth;
331+
}
332+
else {
333+
targetWidth = scale * textWidth;
334+
targetHeight = scale * textHeight;
335+
}
314336

315-
var targetWidth, targetHeight,
316-
targetX, targetY;
317337
if(orientation === 'h') {
318338
if(x1 < x0) {
319339
// bar end is on the left hand side
320-
targetWidth = textWidth + 2 * TEXTPAD; // padding included
321-
targetHeight = Math.abs(y1 - y0) - 2 * TEXTPAD;
322-
targetX = x1 - targetWidth / 2;
340+
targetX = x1 + TEXTPAD + targetWidth / 2;
323341
targetY = (y0 + y1) / 2;
324342
}
325343
else {
326-
targetWidth = textWidth + 2 * TEXTPAD; // padding included
327-
targetHeight = Math.abs(y1 - y0) - 2 * TEXTPAD;
328-
targetX = x1 + targetWidth / 2;
344+
targetX = x1 - TEXTPAD - targetWidth / 2;
329345
targetY = (y0 + y1) / 2;
330346
}
331347
}
332348
else {
333349
if(y1 > y0) {
334350
// bar end is on the bottom
335-
targetWidth = Math.abs(x1 - x0);
336-
targetHeight = 2 + textHeight; // padding included
337351
targetX = (x0 + x1) / 2;
338-
targetY = y1 + targetHeight / 2;
352+
targetY = y1 - TEXTPAD - targetHeight / 2;
339353
}
340354
else {
341-
targetWidth = Math.abs(x1 - x0);
342-
targetHeight = 2 + textHeight; // padding included
343355
targetX = (x0 + x1) / 2;
344-
targetY = y1 - targetHeight / 2;
356+
targetY = y1 + TEXTPAD + targetHeight / 2;
345357
}
346358
}
347359

348-
return getTransform(
349-
textX, textY, textWidth, textHeight,
350-
targetX, targetY, targetWidth, targetHeight);
360+
return getTransform(textX, textY, targetX, targetY, scale, needsRotating);
351361
}
352362

353-
/**
354-
* Compute SVG transform to move a text box into a target box
355-
*
356-
* @param {number} textX X pixel coord of the text box center
357-
* @param {number} textY Y pixel coord of the text box center
358-
* @param {number} textWidth text box width
359-
* @param {number} textHeight text box height
360-
* @param {number} targetX X pixel coord of the target box center
361-
* @param {number} targetY Y pixel coord of the target box center
362-
* @param {number} targetWidth target box width
363-
* @param {number} targetHeight target box height
364-
*
365-
* @returns {string} SVG transform
366-
*/
367-
function getTransform(
368-
textX, textY, textWidth, textHeight,
369-
targetX, targetY, targetWidth, targetHeight) {
370-
371-
// compute translate transform
372-
var translateX = targetX - textX,
373-
translateY = targetY - textY,
374-
translate = 'translate(' + translateX + ' ' + translateY + ')';
375-
376-
// if bar text doesn't fit, compute rotate and scale transforms
377-
var doesntFit = (textWidth > targetWidth || textHeight > targetHeight),
378-
rotate, scale, scaleX, scaleY;
379-
380-
if(doesntFit) {
381-
var textIsHorizontal = (textWidth > textHeight),
382-
targetIsHorizontal = (targetWidth > targetHeight);
383-
if(textIsHorizontal !== targetIsHorizontal) {
384-
rotate = 'rotate(-90 ' + textX + ' ' + textY + ')';
385-
scaleX = targetWidth / textHeight;
386-
scaleY = targetHeight / textWidth;
363+
function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) {
364+
// In order to handle both orientations with the same algorithm,
365+
// *textWidth* is defined as the text length in the direction of *barWidth*.
366+
var barWidth,
367+
textWidth,
368+
textHeight;
369+
if(orientation === 'h') {
370+
barWidth = Math.abs(y1 - y0) - 2 * TEXTPAD;
371+
textWidth = textBB.height;
372+
textHeight = textBB.width;
373+
}
374+
else {
375+
barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD;
376+
textWidth = textBB.width;
377+
textHeight = textBB.height;
378+
}
379+
380+
// compute rotation and scale
381+
var needsRotating,
382+
scale;
383+
if(textWidth <= barWidth) {
384+
// no scale or rotation
385+
needsRotating = false;
386+
scale = 1;
387+
}
388+
else if(textHeight <= textWidth) {
389+
// only scale
390+
// (don't rotate to prevent having text perpendicular to the bar)
391+
needsRotating = false;
392+
scale = barWidth / textWidth;
393+
}
394+
else if(textHeight <= barWidth) {
395+
// only rotation
396+
needsRotating = true;
397+
scale = 1;
398+
}
399+
else {
400+
// both scale and rotation
401+
// (rotation prevents having text perpendicular to the bar)
402+
needsRotating = true;
403+
scale = barWidth / textHeight;
404+
}
405+
406+
// compute text and target positions
407+
var textX = (textBB.left + textBB.right) / 2,
408+
textY = (textBB.top + textBB.bottom) / 2,
409+
targetWidth,
410+
targetHeight,
411+
targetX,
412+
targetY;
413+
if(needsRotating) {
414+
targetWidth = scale * textBB.height;
415+
targetHeight = scale * textBB.width;
416+
}
417+
else {
418+
targetWidth = scale * textBB.width;
419+
targetHeight = scale * textBB.height;
420+
}
421+
422+
if(orientation === 'h') {
423+
if(x1 < x0) {
424+
// bar end is on the left hand side
425+
targetX = x1 - TEXTPAD - targetWidth / 2;
426+
targetY = (y0 + y1) / 2;
387427
}
388428
else {
389-
scaleX = targetWidth / textWidth;
390-
scaleY = targetHeight / textHeight;
429+
targetX = x1 + TEXTPAD + targetWidth / 2;
430+
targetY = (y0 + y1) / 2;
391431
}
432+
}
433+
else {
434+
if(y1 > y0) {
435+
// bar end is on the bottom
436+
targetX = (x0 + x1) / 2;
437+
targetY = y1 + TEXTPAD + targetHeight / 2;
438+
}
439+
else {
440+
targetX = (x0 + x1) / 2;
441+
targetY = y1 - TEXTPAD - targetHeight / 2;
442+
}
443+
}
392444

393-
if(scaleX > 1) scaleX = 1;
394-
if(scaleY > 1) scaleY = 1;
445+
return getTransform(textX, textY, targetX, targetY, scale, needsRotating);
446+
}
395447

396-
if(scaleX !== 1 || scaleY !== 1) {
397-
scale = 'scale(' + scaleX + ' ' + scaleY + ')';
398-
}
448+
function getTransform(textX, textY, targetX, targetY, scale, needsRotating) {
449+
var transformScale,
450+
transformRotate,
451+
transformTranslate;
452+
453+
if(scale < 1) transformScale = 'scale(' + scale + ') ';
454+
else {
455+
scale = 1;
456+
transformScale = '';
399457
}
400458

401-
// compute transform
402-
var transform = translate;
403-
if(scale) transform += ' ' + scale;
404-
if(rotate) transform += ' ' + rotate;
459+
transformRotate = (needsRotating) ?
460+
'rotate(-90 ' + textX + ' ' + textY + ') ' : '';
461+
462+
// Note that scaling also affects the center of the text box
463+
var translateX = (targetX - scale * textX),
464+
translateY = (targetY - scale * textY);
465+
transformTranslate = 'translate(' + translateX + ' ' + translateY + ')';
405466

406-
return transform;
467+
return transformTranslate + transformScale + transformRotate;
407468
}

0 commit comments

Comments
 (0)