Showing posts with label medusa. Show all posts
Showing posts with label medusa. Show all posts

Friday, December 31, 2021

Harmony...finally

 Aloha,

When you create different libraries and components you find yourself writing the same code in different places over time. In principle that's ok, except you combine those libraries and components. In this case you suddenly have the same classes twice or even more often in your code base. When I started creating Medusa, TilesFX and Charts I did not really think about the possibility to combine those libraries in one project at some point in the future. The main reason for this is that I never plan to create those libraries but they simply grow from components to libraries over time. 

The thing that started me thinking about to re-use more code between those libraries was a project where I needed TilesFX and Charts in the same project. Both of these libraries came with a Country class with different properties and methods. Now in that project I needed both of them and I needed to write some ugly code to convert between them

That was the starting point of the Countries library which you can now find either on github and on maven central.

But then I saw that there are other classes that I more or less use in both libraries and I decided to put those shared classes in a separate project. Because there are projects that use JavaFX and others which don't, I decided to create two projects:

  • eu.hansolo.toolbox
  • eu.hansolo.toolboxfx

Toolbox:

This library contains the code from my Evt project, meaning to say an event system which is similar to the JavaFX events.

Then I also added the code from my Properties project to the Toolbox. The properties are very similar to the JavaFX properties incl. binding. And in the Toolbox they will use the Evt events for property changes.

There are now also tuples in the Toolbox which sometimes can come in handy. They are not that fancy and their getters and setters do look like getA(), get(B) and setA(), setB(). Not so nice but useful.

The last thing I've added is the code from my UnitConverter which contains all kinds of different units and a converter that can convert between them (in the same category e.g. Temperature).

Then there is a Helper method that contains all sorts of methods that I use here and there in my code e.g. clamp() etc.

ToolboxFX:

Then there is the ToolboxFX library which depends on JavaFX but that does not only contain JavaFX related stuff. Here you will find things like my ConicalGradient, FontMetrix, GradientLookup, the Fonts that I do use often and other stuff like Point, Bounds, CornerRadii, Dimension, Location Po, CatmullRom etc.

ToolboxFX depends on Toolbox so you need to add Toolbox too if you use ToolboxFX.

This is stuff that I use a lot in the Charts library but also in TilesFX and Medusa.

But that's not enough, I've also separated the HeatMap from Charts and Countries and put it in a separate project.

So what does that mean for you as a user of one of my libraries?

  • Update your dependencies
    • TilesFX depends on:
      • eu.hansolo:toolbox:17.0.6
      • eu.hansolo:toolboxfx:17.0.15
      • eu.hansolo.fx:heatmap:17.0.3
      • eu.hansolo.fx:countries:17.0.16
    • Medusa depends on:
      • eu.hansolo:toolbox:17.0.6
      • eu.hansolo:toolboxfx:17.0.15
    • Charts depends on:
      • eu.hansolo:toolbox:17.0.6
      • eu.hansolo:toolboxfx:17.0.15
      • eu.hansolo.fx:heatmap:17.0.3
      • eu.hansolo.fx:countries:17.0.16
  • Use the new event system
    • If you make use of things like TileEvent, you should change to TileEvt etc. The best way to see how it works is to take a look at the Demo classes within the library source code.

ATTENTION: The libraries are not backwards compatible due to the new event system !!!


The new versions of TilesFX, Charts and Medusa that will make use of the shared libraries will all start with version 17.1.0. There is still a lot of stuff to streamline (e.g. removing methods from the libraries Helper classes because they are already covered by Helper  in Toolbox and HelperFX in ToolboxFX but for that I need more time.

So here are all libraries that are new or have changed:

I will probably also use the Toolbox and ToolboxFX in future components and libraries.
So that was my holiday project and I'm really happy with it because now I could more easily use combinations of my libraries in projects.

I'm pretty sure there are still some things that do not work correctly, so please, if you stumble upon a problem do not hesitate to file an issue with some example code in the github repo.

I wish all of you a Happy New Year...and hopefully we will get rid of that Covid thing pretty soon...so stay healthy...and keep coding...


Wednesday, February 6, 2019

Style it baby...

Aloha,

The people that know me know that I first was not a big fan of CSS in JavaFX and a year after that I was THE big fan of CSS but another year later I switched back to code only. There are several reasons for that but mainly it was about the performance of the CSS implementation and about missing features in CSS that made me mix up code and CSS which I did not like.
So I switched to code only in my Medusa and TilesFX library where I also use JavaFX Canvas which content is not styleable by CSS in the way the other nodes are.
Well don't get me wrong, I'm not completely against CSS in JavaFX, esp. for application level stuff it is great and fast enough. But when it comes to controls I do not really like it.
I knew that someday someone will ask me how to style a Medusa gauge or a TilesFX tile and here we go...

So if you really need to style a Medusa gauge by using CSS here is an example on how to do it.
In principle you need to create a new skin that makes use of CSS, in this example let's create a styleable version of the new PlainAmpSkin. It is simply a copy of the existing skin class where I removed the code that directly sets the colors and gradients and added some style classes.
The idea is to extend the Gauge class and in this new class add some styleable properties for the things that cannot be styled directly (e.g. the tickmarks and ticklabels because they are drawn in a Canvas node).
The styleable properties will be triggered by loading a css stylesheet that contains the defined style classes. So we simply add listeners to the styleable properties and trigger the appropriate properties in the Gauge class with the values from the styleable properties.
I hope you understand what I'm talking about :)
So the example is as follows:

1. We create a StyleableGauge class that has a styleable property named styleableTickmarkColor as follows:

public class StyleableGauge extends Gauge {

    private static final StyleablePropertyFactory<StyleableGauge> FACTORY = 
        new StyleablePropertyFactory<>(Control.getClassCssMetaData());
    
    private static final CssMetaData<StyleableGauge, Color> TICKMARK_COLOR = 
        FACTORY.createColorCssMetaData("-tickmark-color", 
        g -> g.styleableTickmarkColor, Color.rgb(220, 220, 220), false);
    
    private final StyleableProperty<Color> styleableTickmarkColor;


    
    public StyleableGauge() {
        this(SkinType.GAUGE);
    }
    public StyleableGauge(@NamedArg("SKIN_TYPE") final SkinType SKIN_TYPE) {
        super(SKIN_TYPE);
        styleableTickmarkColor = 
            new SimpleStyleableObjectProperty<>(TICKMARK_COLOR, 
                                                this, 
                                                "tickmark-color");
    }


    public Color getStyleableTickmarkColor() { 
        return styleableTickmarkColor.getValue(); 
    }
    public void setStyleableTickmarkColor(final Color COLOR) { 
        styleableTickmarkColor.setValue(COLOR); 
    }
    public ObjectProperty<Color> styleableTickmarkColorProperty() { 
        return (ObjectProperty<Color>) styleableTickmarkColor; 
    }


    @Override public String getUserAgentStylesheet() {
        return StyleableGauge.class.getResource("custom-plain-amp.css").toExternalForm();
    }

    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
        return FACTORY.getCssMetaData(); }
    @Override public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() { 
        return FACTORY.getCssMetaData(); 
    }
}



2. We create a css file named "custom-plain-amp.css" that looks as follows:

.gauge {
    BRIGHT_COLOR   : rgb(220, 220, 220);

    -tickmark-color: BRIGHT_COLOR;
}

3. Now we need to create our custom skin named "CustomPlainAmpSkin".
In this skin we make use of the css style classes. Because the skin file simply is too long to show it here.
The complete example can be found in the medusademo project on github.

With this you can style even canvas node content indirectly by triggering "standard" properties in the control by making use of "styleable" properties. 
I would not recommend to use this approach as the standard approach but it is good enough to make use of CSS even if the controls do not support CSS styling directly.

I hope that was more or less clear for you to understand...otherwise just let me know :)

Oh and do not forget to keep coding... ;)

Friday, February 1, 2019

Friday Fun LXI Part 2

Aloha,
Found some additional time today to close some bugs in my Medusa JavaFX library and in addition I've quickly created another skin called PlainAmpSkin.
There was the request for the ability to switch off the black part of the AmpSkin and just show the scale to save place.
Instead of adding another property to the Gauge class I've simply created another skin based on the AmpSkin but without the black frame.
Here are images of both skins to give you a better understanding of what I'm talking about...



Both controls have the same size but it is obvious that the lower control makes better use of the available space.
So I hope this new skin will fit the needs of the people that requested the feature.
This additional skin and some bugfixes can be found in the latest Medusa release which is now 8.2.

As always you can find the 

source at github

binary at bintray

and on maven central.

So that's it for today...enjoy your weekend and keep coding...

Sunday, August 26, 2018

Medusa Morphing Clock

Aloha,

During my vacation in Greece I've saw a nice video on youtube about a DIY morphing led matrix clock. There is a nice instruction on how to build this clock over here.
If you just would like to see what it looked like, here is a little video from that clock...


When I saw the animation of the 7-Segment elements I had the idea to add this as a clock skin to my Medusa library.
Yesterday I've added a skin that shows the same behaviour so that one can now use it within Medusa.

Here is a little video of how it looks:



I did not add the colons because in my version one can define a color for the hour, minute and second digits and I had no property for the colon color. So I simply did not at them :)

Here is a little code snippet that shows how to use the new skin:

Clock clock = ClockBuilder.create()
                          .skinType(ClockSkinType.MORPHING)
                          .locale(Locale.GERMAN)
                          .hourColor(Color.web("#0288FF"))
                          .minuteColor(Color.web("#0288FF"))
                          .secondColor(Color.web("#0288FF"))
                          .time(ZonedDateTime.of(2018, 8, 26, 6, 58, 0, 0, ZoneId.systemDefault()))
                          .running(true)

                          .build();

As you can see it is used in the same way as the other skins in Medusa. It will switch to am/pm mode if you set the local to Locale.US, otherwise it will use 24h format.

The skin will be part of the next Medusa release but is already in the source code.
As always the stuff can be found here:

Source at github

Binary at bintray

Enjoy your weekend and...keep coding... 

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


Thursday, January 19, 2017

Customize your dashboard

Aloha,

This is just a short post about the TilesFX library (github, bintray, maven) in combination with other libraries like 


The TilesFX standard tiles are nice and might be good for most of the typical dashboard content but what if you need more than that?
Let's say you would like to see a listview or one of your custom controls on such tile...
Well for that reason I've added the CustomTileSkin which has the title and the text property of the standard tiles and which comes with an area where you can put your own JavaFX nodes.
So this is what it looks like...



As you can see the area for custom JavaFX nodes fills most of the tile so that you should have enough space for your components. If you setTextVisible(false) the area will even grow a bit more in y-direction.
To give you an idea on how to use other controls within TilesFX I've created a little demo project which you can find on github at

https://github.com/HanSolo/tilesfxdemo

If you would like to start the demo from the command line you simple can call gradle Demo on the shell as follows:



This will start the Main class of the project and will show you the following screen...




In this demo you will see nearly all of the standard tiles in TilesFX (except the WeatherTileSkin) and in addition there is also one tile that simply uses an icon from the Ikonli library, nine tiles that shows some gauges from my Medusa library and three regulators from my Regulators library.

The Tile class in TilesFX defines some colors that fit nicely to the standard tiles, so you can choose between GRAY, RED, GREEN, BLUE, ORANGE, YELLOW_ORANGE, YELLOW and MAGENTA. In addition also the standard FOREGROUND and BACKGROUND color are available in the Tile class. As you can see in the image above I made use of the Tile.BLUE color for all the Medusa gauges.

I hope this demo will help you to make use of TilesFX for your own dashboards.

That's it for today...so keep coding... :)

Tuesday, December 6, 2016

Medusa Sparkline Skin

Aloha,

While working on the gauges for our customer I figured out that with the given feature set in Medusa it would also be possible to create a skin that is similar to so called Sparklines.
The idea is to store a given number of values in an internal list and just show them in a little line graph, e.g. the last 10 measured values.
In addition the moving average of the visualized data will be shown and the standard deviation of the data.
It just tooks me a couple of hours last Monday night to get it done and I hope it will be somehow useful.
Here is a little screenshot of the new skin...


It has the following features

  • Title
  • Current Value
  • Unit
  • min value (of shown data)
  • max value (of shown data)
  • moving average (of shown data)
  • standard deviation (of shown data)

The term "shown data" means that for example the minimum value is not the absolut ever measured minimum but only the minimum of the visible data in the line graph.
If you need the absolut measured minimum you can ask the gauge for the getMinMeasuredValue() and/or getMaxMeasuredValue(). 
The number of datapoints that will show up in the line graph can be configured by calling the setAveragingPeriod(Integer) method (the default is 10).
The color of the line can be set by using setBarColor(Color).

This skin is part of the current Medusa release which is already 6.5 and you can find it here


That's it for today...so keep coding :)

Wednesday, November 30, 2016

Medusa KPI Skins

At the current project I'm working on a JavaFX dashboard using gauges...which is awesome :)
And so I was looking for interesting designs that might be useful for a dashboard visualizing KPI's (Key Performance Indicators). So I stumbled upon two interesting designs that might be a good fit for a grid or tile based dashboard layout.
So here they are...



The TileTextKpiSkin on the left side shows the current value by the text, a bar, the maximum value it could reach (here 100) and the value in percentage.
The TileKpiSkin on the right side shows the current value by it's number, a defined threshold (75) and a needle that shows where the current value is in the given range.
Both tiles also show a title on the upper left corner.
Well...it's nothing special but didn't take long to implement it so why not :)

Both skins are part of the latest Medusa version (6.3) which you can find here


*** UPDATE ***
After playing around with the new skins I came to the conclusion that it would also make sense to have the ability to visualize sections. So I've added them to both skins and they will be available with the next version (6.4) of Medusa.
Here is a little screenshot of how they will look like...


So as soon as you have sections enabled the threshold won't be shown on the left skin (TileKpiSkin) but only the active section.
On the right right skin (TileTextKpiSkin) the bar and the percentage text will either be colored with the default value (barColor) or with the color of the active section. I've also added the unit text for the right skin.

That's it for today...so keep coding... 

Friday, November 18, 2016

Medusa Industrial Clock Skin

And another one...
Yesterday I was in Stuttgart and on my way back one of my train connections was canceled. This gave me another hour of coding in the Starbucks at the Cologne train station. I mean coffee and code...what could be more productive ;)
So I was searching the picture app on my mac and stumbled upon a clock that I would like to implement for a long time, to be honest it have been two clocks but take a look yourself, here they are...



As you can see it is the same watch face just in two different colors. This clock looks similar to one of the Apple Watch watch faces and so I decided to create a new clock skin for Medusa.
Here is my implementation in Medusa...



The only thing that's missing is the glossy overlay but that can be added later by yourself. The new skin is named IndustrialClockSkin and will be available in the upcoming release 6.3 of Medusa.

To create the dark version of the clock you need to create it like follows...

clock = ClockBuilder.create()
                    .skinType(ClockSkinType.INDUSTRIAL)
                    .prefSize(400, 400)
                    .locale(Locale.GERMANY)
                    .shadowsEnabled(true)                    
                    .running(true)
                    .backgroundPaint(Color.web("#1f1e23"))
                    .hourColor(Color.web("#dad9db"))
                    .minuteColor(Color.web("#dad9db"))
                    .secondColor(Color.web("#d1222b"))
                    .hourTickMarkColor(Color.web("#9f9fa1"))
                    .minuteTickMarkColor(Color.web("#9f9fa1"))
                    .build();

For those of you that would like to use before...just grab the code from github...

That's it for today, enjoy the upcoming weekend and...keep coding ;)

Wednesday, November 9, 2016

Medusa DesignClock Skin

Aloha,

Last weekend I was looking for some inspiration for new UI controls and stumbled upon a really interesting watch design. The moment I saw it I knew I need to create a JavaFX control of it.
Well lucky me with Medusa in place I only had to create the clock skin which was an easy task (after I wrapped my brain around the transformation and clipping stuff).
So here is the original version that I've found on the web...



And here is a little video of my Medusa version...



I think my version is close enough to the original one and because it's now part of Medusa you can enjoy it too :)

It will be part of the next Medusa release which will be 6.2 but if you can't wait feel free to check out the latest version of Medusa at github.

That's it for today...so keep coding...

Tuesday, November 1, 2016

Medusa update...

Aloha,

Last weekend I've found some time to give some love the Medusa project again. Besides some minor cosmetics I've decided to add a moving average feature to the Gauge class.
Usually I would say that such a feature should not be part of a control but in this case I've decided to add it anyway. It's just nice if a Gauge or LCD display could display a moving average of the measured values. And for this reason I've added all the needed properties to the Gauge class.
These properties are

  • averagingEnabled (switch averaging in general on/off)
  • averagingPeriod (defines the number of values to average over from 1-1000)
  • averageVisible (defines the visibility of an average indicator)
  • averageColor (defines the color for an average indicator)

In addition you will now find new methods in the Gauge class to get the average. These methods are

  • getAveragingWindow() (returns the list of Data objects used for the average)
  • getAverage() (returns the moving average over the given averagePeriod)
  • getTimeBasedAverageOf(Duration) (returns the moving average over a given duration)

The default value for the moving average period is 10 which means if averaging is enabled the getAverage() method will always return the average over last 10 measured values.
Because sometimes you are more interested in the average over the last 10 minutes or 60 seconds, I've decided to add a method that will calculate the average for a given duration.
Means if you call the getTimeBasedAverageOf(Duration.ofMinutes(10)) method it will calculate the average from the values that have been measured within the last 10 minutes. Please keep in mind that you have to know how often you measure/display data using the gauge to get proper results.
So it doesn't make sense to calculate the time based average of the last 10 seconds if the value was not changed within the last 10 seconds.
As a start I've implemented the new feature in the Gauge.skin and Lcd.skin class. In the Gauge.skin there is a new indicator which looks like the threshold indicator but with a different color (Magenta as default) and in the Lcd.skin it will show the average value instead of the lastMeasured value in the lower center text (if averageVisible == true).
Keep in mind that you have to enable averaging otherwise the value won't change.

On the following screenshot of the Lcd.skin you can see how it looks like:


This feature will be part of the next Medusa release which will be 6.1 so stay tuned.

And that's it for today...so keep coding... :)

Friday, July 29, 2016

Friday Fun XXXV

Hi there,

Well I could not withstand to create another Friday Fun control before my vacation which is another little gauge that might be useful for one or the other dashboard.
Last weekend I saw a similar gauge on a tweet and thought by myself that should be an easy one...so here is the gauge from the tweet...




...and what should I say...it was easy :)

Here is a little screenshot of my version...



This is not really something special but I think it might be useful. So on the outside you see what I call sections (green, yellow and red) and the bar that represents the current value of the gauge will always be drawn in the color of the current section.
If you switch off the visibility of the sections (setSectionsVisible(false)) the sections will disappear and the bar will be drawn in the barColor.
You could add the sections by calling the setSections(Section... sections) method or add single sections by calling the addSection(Section section) method.

And if you would like to see it in action, here is a little video..



As I said nothing special but if you are interested in the code you can find it on github as always.

That's it for today, so enjoy the summer and keep coding...

Friday, July 8, 2016

Friday Fun XXXIII

Aloha,

Today I just have a little control that I want to share with you, it's not really something special but sometimes could be useful, I named it ChargeControl. It could be used to visualize the charge of a battery, signal strength or goal reached etc.
The really nice thing about this control is that I wrote it in 30 minutes which simply shows how effective it can be to use JavaFX (if you know what you are doing ;) ).
So here is what it looks like...


As you can see it's not really amazing but it works which was the most important thing. Again I make use of Medusa for the model and one can set values from 0.0 - 1.0.
This control is really light-weight because it only contains Regions that are styled by CSS which makes the amount of code to realize it really small.

Well what should I say...I guess that's it already again and if you are interested in the code you will find it as always on github.

Enjoy the upcoming weekend and do never forget...keep coding ;)

Friday, April 8, 2016

Friday Fun XXVI

And Friday again...finally :)
Today I have another little fun JavaFX component for you which took me some time to build. Those of you that own an Apple Watch (unfortunately I don't have one) might know this control already.
It is the Fitness Gauge that looks like this...



Well in principle this is an easy to do control...BUT...if you take close look the engineers/designers at Apple did again a fantastic job by creating this control. First of all it has a conical gradient on each arc (from a darker to a brighter color). Then it has this neat drop shadow at the end of the arc when it overlaps. 
This seems to be easy to implement but it's not...believe me :)
After some tweaking I got a solution that I'm quite happy with and this is what it looks like...



As you can see it's not really the same but it's not bad :)
You can set each bar individually, the max value for each bar and the color. The control is based on my Medusa library and uses three Medusa Gauges for the data model.

Here is also a little video that gives you an impression how it looks like in action...




So I think that's it for today...just a little fun...enjoy and btw if you need the code...here you go...

Github repository

Keep coding...

Friday, February 26, 2016

FridayFun XXIII

Finally Friday again...
I just recognized that it's more than a year ago that I posted a Friday Fun Component...so here you go...

Last Monday I was skimming the web for interesting controls and stumbled upon the following image.



This might be a very handy gauge for some visualizations like health dashboards etc. In principle it would be very easy to realize something like this because it just contains a few visual parts.
The interesting part of that control is the wavy shaped top edge of the inner fill. If you would like to keep it static this is also no big deal BUT...would it not be cooler if the surface will be animated???
I found some code on the web that did something similar and ported it to Java.

Long story short, here is a little video of the control in action...



I've added the code to the medusa demo project that you can find on github (it is called FunLevelGauge).
Instead of using a special Skin for the Medusa Gauge I've simply took a Region and added a Medusa Gauge as member variable.
The code for the Gauge is very simple and looks like this...

Gauge gauge = GaugeBuilder.create()
                          .minValue(0)
                          .maxValue(1)
                          .animated(true)
                          .build();
As you can see this is really nothing special. For the visualization I used a JavaFX Canvas node that I clipped with a simple JavaFX Circle element.
The ring around the control is again a JavaFX Circle that has a transparent fill and a stroke with the same color as the inner part.
The JavaFX Text node in the center of the control changes it's color dependent on the fill level of the control. The color for the text will be derived from the fill color.
If you would like to play around with the waviness of the surface you might want to play around with the following parameters...
  • detail        (no of points used for the wave)
  • friction            
  • density 
In the example I add an impulse to the wave every second to keep the wave moving but you could also think about to add an impulse only when a new level was set like this...
public void setLevel(final double LEVEL) {
    gauge.setValue(LEVEL);
    Point p;
    for( int i = 0 ; i < detail + 1 ; i++ ) {
        p = particles.get(i);
        p.y = size * (1d - LEVEL);
        p.originalY = p.y;
    }
    text.setText(String.format(Locale.US, "%.0f%%", 100 * LEVEL));
    text.setX((size - text.getLayoutBounds().getWidth()) * 0.5);
    text.setFill(LEVEL < 0.45 ? darkerColor : brighterColor);
    impulse();
}
Therefore you just have to add the call the impulse() method to the setLevel() method and remove the following code from the AnimationTimer
if (now > lastImpulseCall + impulseInterval) {
    impulse();
    lastImpulseCall = now;
}


Please keep in mind that this is (like the name says) a Fun Component and that there is always room for improvements but it's good enough to give you some ideas...

And that's it for today, enjoy the upcoming weekend and keep coding...

Wednesday, February 24, 2016

Building a multi gauge with Medusa

Today I will show you how to build what I call a multi gauge, so a gauge that shows more than one single value but for example shows three different values. This comes in handy when you don't have much space to visualize data.

Here is an example of such a gauge
So in this case the RPM is the most important value that you check very often where the temperature and oil are not checked that often which explains the smaller size. To build something similar with Medusa we need to take three Gauge objects.
  • 1x GaugeSkin (RPM)
  • 2x Horizontal Skins (TEMP and OIL)
Overlaying those controls in one area is not a big problem because the standard background fill of the Medusa gauges is transparent, so we simply have to put all three gauges in one layout container.
So the idea is as follows, we create a new class named MultiGauge that extends Region. Therefore I use the following skeleton..

public class MultiGauge extends Region {
    private static final double  PREFERRED_WIDTH  = 250;
    private static final double  PREFERRED_HEIGHT = 250;
    private static final double  MINIMUM_WIDTH    = 50;
    private static final double  MINIMUM_HEIGHT   = 50;
    private static final double  MAXIMUM_WIDTH    = 1024;
    private static final double  MAXIMUM_HEIGHT   = 1024;
    private static       double  aspectRatio;
    private              boolean keepAspect;
    private              double  size;
    private              double  width;
    private              double  height;
    private              Pane    pane;


    // ******************** Constructors **************************************
    public MultiGauge() {
        getStylesheets().add(MultiGauge.class.getResource("styles.css").toExternalForm());
        getStyleClass().add(getClass().getSimpleName().toLowerCase());
        aspectRatio = PREFERRED_HEIGHT / PREFERRED_WIDTH;
        keepAspect  = true;
        init();
        initGraphics();
        registerListeners();
    }


    // ******************** Initialization ************************************
    private void init() {
        if (Double.compare(getPrefWidth(), 0.0) <= 0 || Double.compare(getPrefHeight(), 0.0) <= 0 ||
            Double.compare(getWidth(), 0.0) <= 0 || Double.compare(getHeight(), 0.0) <= 0) {
            if (getPrefWidth() > 0 && getPrefHeight() > 0) {
                setPrefSize(getPrefWidth(), getPrefHeight());
            } else {
                setPrefSize(PREFERRED_WIDTH, PREFERRED_HEIGHT);
            }
        }

        if (Double.compare(getMinWidth(), 0.0) <= 0 || Double.compare(getMinHeight(), 0.0) <= 0) {
            setMinSize(MINIMUM_WIDTH, MINIMUM_HEIGHT);
        }

        if (Double.compare(getMaxWidth(), 0.0) <= 0 || Double.compare(getMaxHeight(), 0.0) <= 0) {
            setMaxSize(MAXIMUM_WIDTH, MAXIMUM_HEIGHT);
        }
    }

    private void initGraphics() {

        pane = new Pane();

        getChildren().setAll(pane);
    }

    private void registerListeners() {
        widthProperty().addListener(o -> resize());
        heightProperty().addListener(o -> resize());
    }


    // ******************** Methods *******************************************
    private void handleControlPropertyChanged(final String PROPERTY) {
        if ("".equals(PROPERTY)) {

        }
    }

    
    // ******************** Resizing ******************************************
    private void resize() {
        width  = getWidth() - getInsets().getLeft() - getInsets().getRight();
        height = getHeight() - getInsets().getTop() - getInsets().getBottom();
        size   = width < height ? width : height;

        if (keepAspect) {
            if (aspectRatio * width > height) {
                width = 1 / (aspectRatio / height);
            } else if (1 / (aspectRatio / height) > width) {
                height = aspectRatio * width;
            }
        }
        
        if (width > 0 && height > 0) {
            // Use for square controls where width == height            pane.setMaxSize(size, size);
            pane.relocate((getWidth() - size) * 0.5, (getHeight() - size) * 0.5);

            // Use for rectangular controls width != height            pane.setMaxSize(width, height);
            pane.relocate((getWidth() - width) * 0.5, (getHeight() - height) * 0.5);
            
        }
    }
}

With this template you could easily compose a new control out of existing controls. So the main work we have to do is to create the three gauges and put them in the right location in the new control.
Let's start with the rpmGauge control. Because I've explained styling a Medusa gauge already in my blogpost "Building a fuel gauge using Medusa" I won't go through all the details but directly give you the code, here it is...

rpmGauge = GaugeBuilder.create()
                       .borderPaint(Color.WHITE)
                       .foregroundBaseColor(Color.WHITE)
                       .prefSize(400, 400)
                       .startAngle(290)
                       .angleRange(220)
                       .minValue(0)
                       .maxValue(4000)
                       .valueVisible(false)
                       .minorTickMarksVisible(false)
                       .majorTickMarkType(TickMarkType.BOX)
                       .mediumTickMarkType(TickMarkType.BOX)
                       .title("RPM\nx100")
                       .needleShape(NeedleShape.ROUND)
                       .needleSize(NeedleSize.THICK)
                       .needleColor(Color.rgb(234, 67, 38))
                       .knobColor(Gauge.DARK_COLOR)
                       .customTickLabelsEnabled(true)
                       .customTickLabelFontSize(40)
                       .customTickLabels("0", "", "10", "", "20", "", "30", "", "40")
                       .animated(true)
                       .build();

We add this code block to the initGraphics() method and we also have to add the rpmGauge as a member variable.
To give you an idea how it would look like, here is a little screenshot...


So the next thing we have to do is adding the temperature and oil gauge. Long story short, here is the code...

tempGauge = GaugeBuilder.create()
                        .skinType(SkinType.HORIZONTAL)
                        .prefSize(170, 170)
                        .autoScale(false)
                        .foregroundBaseColor(Color.WHITE)
                        .title("TEMP")
                        .valueVisible(false)
                        .angleRange(90)
                        .minValue(100)
                        .maxValue(250)
                        .needleShape(NeedleShape.ROUND)
                        .needleSize(NeedleSize.THICK)
                        .needleColor(Color.rgb(234, 67, 38))
                        .minorTickMarksVisible(false)
                        .mediumTickMarksVisible(false)
                        .majorTickMarkType(TickMarkType.BOX)
                        .knobColor(Gauge.DARK_COLOR)
                        .customTickLabelsEnabled(true)
                        .customTickLabelFontSize(36)
                        .customTickLabels("100", "", "", "", "", "", "", "175", "", "", "", "", "", "", "", "250")
                        .animated(true)
                        .build();

oilGauge = GaugeBuilder.create()
                       .skinType(SkinType.HORIZONTAL)
                       .prefSize(170, 170)
                       .foregroundBaseColor(Color.WHITE)
                       .title("OIL")
                       .valueVisible(false)
                       .angleRange(90)
                       .needleShape(NeedleShape.ROUND)
                       .needleSize(NeedleSize.THICK)
                       .needleColor(Color.rgb(234, 67, 38))
                       .minorTickMarksVisible(false)
                       .mediumTickMarksVisible(false)
                       .majorTickMarkType(TickMarkType.BOX)
                       .knobColor(Gauge.DARK_COLOR)
                       .customTickLabelsEnabled(true)
                       .customTickLabelFontSize(36)
                       .customTickLabels("0", "", "", "", "", "50", "", "", "", "", "100")
                       .animated(true)
                       .build();

After adding both gauges to our control it will now look like this...


It's not perfect but also not too bad...and it works :)
The only thing that we now miss is how to resize and relocate the gauges correctly, well that's pretty simple and here is the related code from the resize() method of our MultiGauge control...

private void resize() {
    double width  = getWidth() - getInsets().getLeft() - getInsets().getRight();
    double height = getHeight() - getInsets().getTop() - getInsets().getBottom();
    size          = width < height ? width : height;

    if (size > 0) {
        pane.setMaxSize(size, size);
        pane.relocate((getWidth() - size) * 0.5, (getHeight() - size) * 0.5);

        rpmGauge.setPrefSize(size, size);

        tempGauge.setPrefSize(size * 0.425, size * 0.425);
        tempGauge.relocate(size * 0.1, size * 0.5625);

        oilGauge.setPrefSize(size * 0.425, size * 0.425);
        oilGauge.relocate(size * 0.475, size * 0.5625);
    }
}

As you can see it is really simple to resize and relocate the controls within our MultiGauge control. To get access to the gauges I simply added three get methods for each gauge and that's all it needs :)

You will find the complete example on github.

That's it for today...so keep coding... :)