-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Word wrap for long labels #382
Comments
Unfortunately, there isn't this option built into plotly.js at the moment. The text labels are created using SVG elements, or in WebGL where text-reflow is done manually and it's not quite as easy just constraining the width. This likely won't be of high priority for us in the immediate future, but we're always open to pull requests and are happy to help on them! |
Ok, understood. Would help with a PR if I had a clue how to do that :) Pardon my ignorance, but is there no way to introduce a JavaScript function that would be applied to all labels before they're fed to SVG/WebGL, where it becomes complicated?
that I can use internally to shorten the labels
Also, as a side question — is there a way to read the bar width in plotly so I don't have to adjust the width manually and can make it reflow properly when chart size is adjusted. |
Any update on this? |
@mdtusz I'm happy to try to help with this if you're able to point me in the right direction in the codebase. |
#1834 should see some foundation work on this. |
A workaround is to line break svg elements after rendering. This assumes that there are var insertLinebreaks = function (d) {
if (typeof d.text === 'string') {
var words = d.text.split(/<br[/]?>/);
var el = d3.select(this);
el.text('')
for (var i = 0; i < words.length; i++) {
var tspan = el.append('tspan').text(words[i]);
if (i > 0)
tspan.attr('x', 0).attr('dy', '15');
}
}
}; Then it's a matter of selecting svg text elements and calling the above function:
You can get fancier with the selector so line break just axis labels for instance. It could also be used in conjunction with above method by @eugenesvk to evaluate long strings and insert such line break markers. |
Has the plotly library managed to solve this issue now. Looking for some better alternatives. |
I also wanted to know if Plotly has solved for this yet - this discussion has been going on since 2016. Adding |
I'd also like to know whether Plotly intends to solve this. Thanks! |
Wondering if using |
No one from our team is actively working on this but we would happily work with anyone who wants to help :) |
still no updates on this right? cause it would be suuuuper clutch if this there was a way to wrap labels. I'm working with some long, dynamic violin plot labels and it would be so awesome if this feature were addressed |
@craragon77 SVG doesn't have line wrap / truncation as native features I think , so the code needs to explicitly break/wrap/recenter or truncate text. Below is code that selects and breaks or truncates text in a Plotly chart after rendering. The ideal solution would be for someone to package and test this nicely for a pull request to Plotly, but for expedience we just select break the text ourselves:
const _ = require('lodash')
module.exports = {
breakStr: function (text, maxLineLength, regexForceBreak, regexSuggestBreak) {
const resultArr = [];
maxLineLength = maxLineLength || 12
regexForceBreak = regexForceBreak || /<br[/]?>|\n|\&/g
regexSuggestBreak = /\s/g
function m(str) {
return !!(str && str.trim())
}
var splitStr = text.split(regexForceBreak);
splitStr.forEach(function (str) {
if (str.length <= maxLineLength) {
resultArr.push(str);
} else {
var tempStr = str;
while (tempStr) {
var suggestedBreakPoints = []
var convenientBreakPoint = Infinity
var match, fragment;
while ((match = regexSuggestBreak.exec(tempStr)) != null) {
suggestedBreakPoints.push(match.index + 1)
if (match.index <= maxLineLength) convenientBreakPoint = match.index;
}
if (tempStr.trim() && tempStr.length <= maxLineLength) {
fragment = tempStr
resultArr.push(fragment)
tempStr = ''
}
else if ((convenientBreakPoint <= maxLineLength) && (maxLineLength > maxLineLength - 5)) {
fragment = tempStr.substr(0, convenientBreakPoint)
resultArr.push(fragment)
tempStr = tempStr.substr(convenientBreakPoint + 1);
}
else {
fragment = tempStr.substr(0, maxLineLength) + "–"
resultArr.push(fragment)
tempStr = tempStr.substr(maxLineLength);
}
}
}
});
return resultArr;
},
/**
* @method svgLineBreaks
* @returns <function(d)> Returns a function that breaks an SVG text element into multiple lines
* @param maxLineLength Maximum number of lines before truncating with ellipses
* @param regexForceBreak Regex to suggest places to always, currently •|<br>|<br/>|\n
* @param regexSuggestBreak Regex to suggest preferred breaks, currently <wbr>
* @param verticalAlign e.g. 'middle' to optionally center mutiple lines at same ypos as original text
* @example svg.selectAll('g.x.axis g text').each(svgLineBreaks);
*/
breakElement: function (maxLineLength, regexForceBreak, regexSuggestBreak, verticalAlign) {
maxLineLength = maxLineLength || 12
regexForceBreak = regexForceBreak || /<br[/]?>|•|\n/ig
regexSuggestBreak = regexSuggestBreak || /•|<wbr>/ig
return function (d) {
var el = d3.select(this);
var bbox = el.node().getBBox()
var height = bbox.height
height = height ||16.5 //hack as Plotly might draw before rendered on screen, so height zero; this is just a guess
var result = {
el: this,
dy: 0,
lines: []
}
var text = el.text()
var total_dy = 0;
if (text) {
var lines = this_module.breakStr(text, maxLineLength, regexForceBreak, regexSuggestBreak)
result.lines = lines
for (var i = 0; i < lines.length; i++) {
if (i == 0) {
el.text(lines[0])
}
else {
var tspan = el.append('tspan')
tspan.text(lines[i]);
tspan.attr('x', el.attr('x') || 0).attr('dy', height);
total_dy += height
}
}
if (verticalAlign == 'middle') {
el.attr('y', el.attr('y') - (lines.length - 1) * height / 2)
}
}
result.dy = total_dy
return result
}
},
/**
* maxLineLength, regexForceBreak, regexSuggestBreak, adjust
* @param d3el
* @param selector
* @param options.maxLineLength
* @param options.regexForceBreak
* @param options.regexSuggestBreak
* @param options.position "middle"
*/
breakElements: function (d3el, selector, options) {
var self = this;
options = options || {}
var breaker = self.breakElement(options.maxLineLength || 12, options.regexForceBreak, options.regexSuggestBreak, options.verticalAlign)
var max_total_dy = 0
d3.select(d3el).selectAll(selector).each(function (d, i) {
var result = breaker.apply(this, arguments)
if (i > 0 && (result.dy > max_total_dy)) {
max_total_dy = result.dy
}
})
},
truncateElement: function (maxLineLength) {
return function (d) {
var el = d3.select(this)
var text = el.text()
if (text && text.length > maxLineLength) {
text = (text.substr(0, maxLineLength) + "…")
}
el.text(text)
}
}
} |
Wow, I came back to plotly after a year and I'm surprised to see this. It looks SO unprofessional to use any sort of labeling in plotly. It appears to others that there's a bug and I did something wrong, so I don't know why this isn't a bigger issue. It's enough to have made me use Tableau Public instead - and the entire advantage of plotly is customizability, so this has been a frustrating issue to rediscover several hours into making the plot |
Charting seems so simple at first but the diversity of users, use cases, and edge cases is wild. Plotly is an open source library, so theoretically we can add features we need such as this. |
I definitely understand that considering the number of tools I've looked at for what I thought was relatively simple (horizontal timeline with groups and text wrapping that my dad could edit), but of course, these things never are! I can't This is surprising to me because I don't think it's an edge case at all. It's essential to being able to label data on the graph - it's not presentable if you have overlapping labels/no linebreaks/uneven text sizes (although that's fixed I think), etc. And labeling data is essential for communication. It took a very long time and a lot of manual screenshotting for a non-web dev to make this chart look presentable with Plotly in this Dash App! I would say I spent at least 30% of my time with Plotly trying to get labels to look presentable, and it's why I no longer use the library if I can possibly help it |
I no longer work at Plotly or have any affiliation with them, so this is not an "official" response, but was a core maintainer when this issue was originally opened. The challenge is that there's not actually a reasonable solution to the problem that will be correct in all cases. Text wrapping in the charts is tricky because there are so many variables that are difficult to actually control for - fonts, font sizes, browsers, zoom level, etc. all contribute to the actual width of a given block of text. While yes it is a challenge to format text with long labels, it's par for the course with what plotly attempts to achieve - being an extensible and flexible library which doesn't really get in the way of making nearly any chart or graph visualization. There's alternatives on either side of its niche - D3 if you want to go lower level, and things like Tableau or Highcharts if you want to go higher level, but those come with their own set of compromises you'll have to adapt to. I don't imagine this issue will ever be resolved, as text wrapping simply isn't something that plotly.js is positioned for to handle automatically, nor should it (IMO) - it would be like asking React to automatically do the layout of your webapp. Frankly, I'm surprised I still get notifications about this issue more than 6 years later and it hasn't been closed yet 😆 All this to say I would love to be proven wrong and see a community contribution which does implement a robust solution. PR's are always welcome :) |
Workaround example for spacing and wrapping titles on subplots:
|
If you are using Python and pandas you can use wrap() to wrap long text labels. Here is some example code:
The output looks like |
It looks like one could make use of |
To my knowledge foreignObject does not work outside browsers - so this would break SVG export |
Thanks @alexcjohnson for the note. |
@alexcjohnson Update: It looks like using |
Is there an option to reflow long text labels?
<br>
in the code that I'd like to have Plotly be able to do with a simplewrap: true
optionJS code for this chart
The text was updated successfully, but these errors were encountered: