Monday, May 1, 2017

Formatting numbers in Medusa

Aloha,

Because of the the current project in Singapore I do not have a lot of time to code JavaFX but I try make use of every minute I can find :)
Last week someone found an interesting bug in Medusa that I was not aware of because it never showed up in my tests.
The following screenshot of the bugreport directly shows the problem...


The gauges on the screenshot use the Medusa DashboardSkin and the interesting part is the visualization of the value text.
In Medusa I have a method to adjust the font size so that the given text will fit into a given width. Means if the width of the given text will exceed the given width the font size will be decreased as along as the text will fit in the given width.
This behaviour leads to the different font size used for e.g. -54.90 and 0.0 in the screenshot above.
Well if you just use one gauge or all gauges are always in the same range this is no problem but if you have multiple gauges with values in different ranges (which is often the case) it simply doesn't look good.
So I've started to fix this problem and figured out that it was not that easy as I first thought.

The solution to this problem (at least the one that I've found...there might be better ones) is as follows.
First I figure out the longest text for the value. Therefor I measure the length of the minValue and the maxValue text. When doing this I have to take the number of decimals into account that was set by the user. Doing this gives me the maximum number of characters that can appear in the value text incl. the minus for negative values.
With having this numbers I can make use of the String.format() method to make sure the text will be formatted the right way.
But before I can do that I also have to take into account that instead of rounding the values I would like to simply cut of the decimals after the given number of decimals.
Therefor I have to take into account to use Math.floor() for positive and Math.ceil() for negative values when using String.format().
You might think why not simply use NumberFormat in combination with DecimalFormat because with this you can use patterns like "000.00" and a RoundingMode to format numbers but here you will run into problems with negative numbers and their minus character.
Well like I mentioned my solution might not be the perfect one but it works for Medusa and because of the limited time I have it is good enough for me :)

Here is a method that will format a given value in the range of the given min- and maxValue, the given number of decimals and the given locale...

public static String formatNumber(final Locale LOCALE, final double MIN_VALUE, 
                                  final double MAX_VALUE, final int DECIMALS, 
                                  final double VALUE) {
    StringBuilder sb        = new StringBuilder("%.").append(DECIMALS).append("f");
    String        f         = sb.toString();
    int           minLength = String.format(Locale.US, f, MIN_VALUE).length();
    int           maxLength = String.format(Locale.US, f, MAX_VALUE).length();
    int           length    = Math.max(minLength, maxLength);

    StringBuilder formatStringBuilder = new StringBuilder("%").append(length).append(".").append(DECIMALS).append("f");
    String        formatString        = formatStringBuilder.toString();

    double value = VALUE;
    if (value > 0) {
        value = Math.floor(VALUE * Math.pow(10, DECIMALS)) / Math.pow(10, DECIMALS);
    } else if (value < 0) {
        value = Math.ceil(VALUE * Math.pow(10, DECIMALS)) / Math.pow(10, DECIMALS);
    }

    return String.format(LOCALE, formatString, value);
}

I don't know if this might be useful for someone else but even if not it fixed the bug in Medusa which is all I wanted :)

That's all for today, so...keep coding...


No comments:

Post a Comment