Cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

How-to: Small text attached to right side of large text?

ANSWERED

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?

 

 

image.png

Best Answer
0 Votes
1 BEST ANSWER

Accepted Solutions

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.

View best answer in original post

Best Answer
19 REPLIES 19

 

Use text-anchor="end" for the big one and text-anchor="start" for the small ones. 

Best Answer
0 Votes

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.

Best Answer
0 Votes

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?

Peter McLennan
Gondwana Software
Best Answer

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!

Best Answer
0 Votes

I feel like there must be some more elegant solution.

Have you looked into: 

  • display-align: (before, center, after)

Or a G container with one element scaled?

https://dev.fitbit.com/guides/user-interface/svg/#overview

Best Answer
0 Votes

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).

Best Answer
0 Votes

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.

Best Answer

Very cunning; definitely thinking outside the (text) box.

Peter McLennan
Gondwana Software
Best Answer
0 Votes

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.

Best Answer
0 Votes

@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.

Best Answer
0 Votes

@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.

 

jstest-screenshot.png

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>

 

Best Answer
0 Votes

@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.

Best Answer
0 Votes

@SunsetRunner Reread one of your earlier posts .... my misunderstanding. You are centering the two combined fields. Of course you are right.

Best Answer
0 Votes

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.

Peter McLennan
Gondwana Software
Best Answer
0 Votes

@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

 

 

Best Answer

Thanks @bhwolf , this is the exact solution I was looking for to centre align image + text in the same line (same Y cords).

Best Answer
0 Votes

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

 

 

Best Answer
0 Votes

@SunsetRunner How about this solution

 

const ltbox = largeText.getBBox();
smallText.x =ltbox.width+3;

 

3 = the space you want between the text.

Best Answer

Thank you Patricio!

Best Answer
0 Votes