12-09-2017 19:59
12-09-2017 19:59
I'm trying to set up a view so that there is a set of large numbers followed by a set of small numbers. Think something like:
123.45
The problem is, if I use two <text> elements (one with a large size, one with a small size) and use absolute positioning, I can end up with the following scenarios, neither of which are desirable:
12 .34 (too much space between numbers)
12.34 (too much white space on the left)
Or worse, if the large text were to have four digits (e.g. 1234.56), they would begin to overlap the small ones. Therefore, I would like the right <text> block to be attached to the right side of the left <text> block.
I know that what I'm looking for can be achieved, as it has been accomplished by the built-in timer app. The small text on the right is attached to the right so that the entire timer is centered, rather than being centered on the decimal point. (Pictured below.)
I have investigated using the .width attribute of the left text box to position the one on the right; however, it is always 0 unless I set it manually.
How can I achieve this?
Answered! Go to the Best Answer.
12-10-2017 13:13
12-10-2017 13:13
Okay, I found a solution. It's somewhat hacky, but it works to perfectly align the two fields.
Essentially, what it does is set the initial large-text width to 1 and ensure that overflowing text is clipped. Then, when the large-text is updated, we decrease the width of the large-text box to make it as small as possible. Then, we increase the width of the large-text box until it's no longer overflowing. At this point, we can use the x and width properties to set the small text's x property.
First, the CSS setup:
#large-text { text-overflow: clip; width: 1; }
Then, the text code, that has to run every time the contents of #large-text change:
let $largeText = document.getElementById("large-text");
let $smallText = document.getElementById("small-text");
// Reduce the width of the text field until text overflows while (!$largeText.textOverflowing) { $largeText.width--; } // Increase the width of the text field until text is no longer overflowing while ($largeText.textOverflowing) { $largeText.width++; }
$smallText.x = $largeText.x + $largeText.width;
I don't know if this has any measurable effect on battery life, but it seems to have no major performance impact, at least.
12-10-2017 03:24
12-10-2017 03:24
Use text-anchor="end" for the big one and text-anchor="start" for the small ones.
12-10-2017 09:49
12-10-2017 09:49
Thanks for the reply!
Unfortunately, that won't work. This results in the second "undesirable" scenario I outlined in my original post. If you imagine the watch face outlined by the | below...
| 1.23 |
|12.34 |
1|23.45 |
The large text with text-anchor:end overflows to the left while there's still lots of screen real estate left. The way it should work is like this:
|1.23 |
|12.34 |
|123.45 |
Which means that the left boundary of the small text needs to move to the right as the large text contains more characters.
12-10-2017 11:53
12-10-2017 11:53
Here's an ugly suggestion: use a fixed-width font and pad the first field with spaces before the number.
Alternatively, use a fixed-width font and work out the width of each character, then vary the element's width attribute depending on the number of characters to be displayed.
Yuck?
12-10-2017 12:01
12-10-2017 12:01
Hmm... kind of hacky, but not a terrible idea. In fact, I'm wondering if that's how the built-in Timer app I showed in my original post worked. The 1s seem to have the same width as the 8, so maybe they used a calculation related to that.
Definitely open to other suggestions but I will use that idea for now. Thanks!
12-10-2017 12:12
12-10-2017 12:12
I feel like there must be some more elegant solution.
Have you looked into:
Or a G container with one element scaled?
12-10-2017 12:29
12-10-2017 12:29
It looks like display-align is only for vertical alignment in a <textarea>.
I think the g-container might have more possibilities. I'm still looking into it, but it seems like you can only scale individual elements, which would still mean that the small-text element would need to be positioned based on the large-text element (even if it were scaled using a g-container). I don't see any way to scale down only part of a single text element (i.e. the small-text portion).
12-10-2017 13:13
12-10-2017 13:13
Okay, I found a solution. It's somewhat hacky, but it works to perfectly align the two fields.
Essentially, what it does is set the initial large-text width to 1 and ensure that overflowing text is clipped. Then, when the large-text is updated, we decrease the width of the large-text box to make it as small as possible. Then, we increase the width of the large-text box until it's no longer overflowing. At this point, we can use the x and width properties to set the small text's x property.
First, the CSS setup:
#large-text { text-overflow: clip; width: 1; }
Then, the text code, that has to run every time the contents of #large-text change:
let $largeText = document.getElementById("large-text");
let $smallText = document.getElementById("small-text");
// Reduce the width of the text field until text overflows while (!$largeText.textOverflowing) { $largeText.width--; } // Increase the width of the text field until text is no longer overflowing while ($largeText.textOverflowing) { $largeText.width++; }
$smallText.x = $largeText.x + $largeText.width;
I don't know if this has any measurable effect on battery life, but it seems to have no major performance impact, at least.
12-10-2017 22:56
12-10-2017 22:56
Very cunning; definitely thinking outside the (text) box.
01-15-2018 14:19 - edited 01-15-2018 14:21
01-15-2018 14:19 - edited 01-15-2018 14:21
Isn't the following a less 'hacky' way to solve the problem?
styles.css
.textright { font-family: Seville-Regular; font-size: 80; fill: yellow; text-anchor: end } .textleft { font-family: Seville-Regular; font-size: 35; fill: white; text-anchor: start }
index.gui
<svg> <text class="textright" x="85%-2" y="50%" >9999.99</text> <text class="textleft" x="85%+2" y="50%" >mi</text> </svg>
That's not to say the overflow method of deducing width doesn't have a place in the programming arsenal in the absence of the SDK providing width. For height that is a different matter of course.
01-15-2018 14:57
01-15-2018 14:57
@SunsetRunner That only works if one of your text fields (in your example, textleft) is fixed-width. Otherwise, there will be whitespace left over when the text becomes narrower, or will overflow if it becomes wider. In my case, both fields are variable width, so this wouldn't work.
I can see its application to your specific example, though (using a fixed "unit" field), so hopefully it helps someone who has that use case.
Honestly, I am still looking for a more performant way of doing this, but haven't come across one yet. I've been meaning to look into Bounded and DOMRect but haven't yet.
01-15-2018 15:06 - edited 01-15-2018 15:08
01-15-2018 15:06 - edited 01-15-2018 15:08
@SunsetRunner:
There are two text-anchors: end and start
The end anchor effectively makes a right aligned field. The start anchor is for left aligned.
Your large text would be anchored end, small text anchored start. I have it working.
working with the following gui
<svg> <text class="textright" x="85%-2" y="75" >99.99</text> <text class="textleft" x="85%+2" y="75" >mi</text> <text class="textright" x="85%-2" y="155" >999.99</text> <text class="textleft" x="85%+2" y="155" >km</text> <text class="textright" x="85%-2" y="235" >1999.99</text> <text class="textleft" x="85%+2" y="235" >m</text> </svg>
01-15-2018 15:25
01-15-2018 15:25
@SunsetRunner Actually, that image illustrates exactly why it doesn't work if the second field is dynamic.
Imagine the right-hand field is a number, like in my initial example. Something like .1 would be be like "m", with some whitespace to the right. .11 would be more like "mi", with less white space. .111 would be more like "km", hugging close to the right. Once you transition into .1111 or .11111, it's going to flow off the screen.
01-15-2018 23:29
01-15-2018 23:29
@SunsetRunner Reread one of your earlier posts .... my misunderstanding. You are centering the two combined fields. Of course you are right.
01-16-2018 00:45
01-16-2018 00:45
I may need to do something similar. My case could be more demanding because it's for image captions, which could vary from 0 to say 1,000 px.
Using @SunsetRunner's cunning solution verbatim could require 1,000 iterations, so I'll modify it by using a binary search. This should cut down the maximum number of iterations to 10. It's still kludgy but should perform better.
01-16-2018 06:24
01-16-2018 06:24
@SunsetRunner wrote:
I have investigated using the .width attribute of the left text box to position the one on the right; however, it is always 0 unless I set it manually.
What about using the bounding box to get the width? I've got text I'm trying to center between two other pieces of text. Based on the bounding box, I calculate a center point, and use that. Could do something similar:
txtSteps.getBBox().width
01-17-2018 02:55
01-17-2018 02:55
Thanks @bhwolf , this is the exact solution I was looking for to centre align image + text in the same line (same Y cords).
07-08-2018 07:29
07-08-2018 07:29
I don't suppose there is an example of this somewhere. i looked through github and didn't see it used...but there are a bunch in there and I may have missed it.
Thanks
08-25-2019 11:31
08-25-2019 11:31
@SunsetRunner How about this solution
const ltbox = largeText.getBBox();
smallText.x =ltbox.width+3;
3 = the space you want between the text.
10-27-2021 13:56
10-27-2021 13:56
Thank you Patricio!