How to use the JavaFX library Medusa to display weather data
Rachael Ellen
Posted on August 25, 2023
In this post, I’ll share how I used the Medusa JavaFX library in a Java Raspberry Pi based app to display weather data!
Introduction
My colleague recently asked me “can you make my Java application look more exciting?”. Music to my ears – a creative project is always a joy to work on!
The project in question was a weather station style app that runs on a Raspberry Pi, built with the Pi4J Java library to read sensors that measure humidity, temperature, and air pressure, and then integrated with the ArcGIS Maps SDK for Java to host the location and data online.
The idea was that the live weather data should be displayed on a screen also plugged into the Raspberry Pi, to give up to date data readings.
When I picked up the project, the app used a basic JavaFX UI display consisting of button and label components and looked like this:
We both agreed this could look a lot more engaging, and so developed the following criteria to upgrade the UI:
- The app should display a temperature reading (from the sensors connected to the Raspberry Pi sensors). The display should be able to show the reading, the unit, and show negative values.
- The app should also display a relative humidity percentage reading
- It should also show atmospheric pressure, in the style of a barometer where the pressure reading reflects the weather conditions (like the image below).
Building with JavaFX
JavaFX (an open source, next generation client application platform for desktop, mobile and embedded systems) has many useful out the box UI controls to build modern interactive desktop apps. These include buttons, checkboxes, list views, labels etc, that can be configured and styled in countless ways. I’ve using them for many years at work building mapping apps!
JavaFX is of course fully extendable beyond these core basic controls – however, there is no out the box offering for displaying gauge or dial like controls to show data, which is what was required for this app. This project was therefore a fun opportunity to build a visually engaging app, and so I looked around for open source JavaFX libraries that could deliver a gauge like interface easily.
That's when I came across Medusa, a JavaFX library for Gauges.
Medusa JavaFX library
The GitHub repo for Medusa is a great resource for numerous modern looking UI gauge controls, maintained by its creator Gerrit Grunwald. Gerrit also maintains the Charts JavaFX library that I have been enjoying experimenting with for other Java projects.
Finding the Medusa repo was a joy, as it saved me a lot of time and effort trying to design such a beautiful UI myself. It was also so good to see capabilities of JavaFX shine through in this repo - JavaFX doesn’t seem to get talked about all that often (maybe I'm just looking in the wrong places!) so much respect and thanks to the creator of this library for championing and showcasing what JavaFX is really capable of!
Trouble shooting Medusa
I initially had some difficulty figuring out how to build the controls in Medusa. Here are some resources that I wish I’d known about when getting started:
- You can find really helpful background and documentation on Medusa in its original release blog post.
- There is a demo you can run to see all the gauges running interactively (see above image). Clone the Medusa GitHub repo, and run the
DemoLauncher
class. There are also other demos you can run in the medusademo GitHub repo. - You can define your UI layout with Medusa gauges in FXML, allowing you to write a modern MVC style application. You just have to ensure your
Controller
class implements theInitializable
class, and override theinitialize
method. There is also an example demo for this in the medusademo repo. ```java
public class Controller implements Initializable {
@override
public void initialize(URL url, ResourceBundle rB){
}
}
- If you’re configuring a gauge and a value isn’t displaying, it’s probably because you’ll have to set the value visibility to `true`. The Medusa library controls a lot of the initial control behaviour under the hood, so be prepared to undo some of that when building your controls.
- You can use the `FGauge` class to makes your gauges look very pretty – basically give them a surround and some additional styling (see below).
![Two images side by side displaying circular gauges with different styles](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ah2tbaryiilzm3tptaff.png)
<figcaption>Left: With no `FGauge` styling. Right: With `FGauge` styling</figcaption>
## Using Medusa
I imported the Medusa JavaFX library into the app’s existing build.gradle script via a Gradle implementation:
`implementation 'eu.hansolo:medusa:17.1.7'`
I then ensured to add it to the list of modules required in the app’s module-info.java:
`requires eu.hansolo.medusa;`
_(you'll probably also have to add the following implementations and modules: `javafx.swing`, `eu.hansolo.toolboxfx`, `eu.hansolo.toolbox`)_
With hindsight, I would have set up the `main.fxml` in the resources directory straight away had I realized I could initialize the controls in FXML. What happened instead was I added all of the control UI configuration to the `Controller` class, and then retrospectively went back and refactored it all. So if you are reading this and finding yourself at the beginning of the project...**save yourself some time and get that `main.fxml` set up now**. It’ll save you refactoring later.
_Note: I couldn’t find a way to initialize the gauges which use `SectionBuilder` in FXML, so it seems this one does have to remain in the `Controller` class. Let me know if you have found another way!_
## Customising Medusa
I set up the digital style controls (`LCD` skin type) for displaying temperature and humidity readings in my `main.fxml` file. I chose a JavaFX `[Gridpane](https://openjfx.io/javadoc/20/javafx.graphics/javafx/scene/layout/GridPane.html)` to display them in, so that I could extend the app easily in future if there was more data sensors added to the app.
```xml
<GridPane maxWidth="Infinity" StackPane.alignment="CENTER">
<padding>
<Insets topRightBottomLeft="25"/>
</padding>
<Gauge fx:id="humidityGauge" GridPane.rowIndex="0" GridPane.columnIndex="0"
skinType="LCD"
maxWidth="Infinity"
maxHeight="75"
prefWidth="250"
prefHeight="100"
title="Humidity"
lcdDesign="GRAY_PURPLE"
oldValueVisible="false"
maxMeasuredValueVisible="false"
minMeasuredValueVisible="false"
unit="\%" />
<Gauge fx:id="digitalTempGauge" GridPane.rowIndex="0" GridPane.columnIndex="1"
skinType="LCD"
maxWidth="Infinity"
maxHeight="75"
prefWidth="250"
prefHeight="100"
title="Temperature"
lcdDesign="GRAY_PURPLE"
oldValueVisible="false"
maxMeasuredValueVisible="false"
minMeasuredValueVisible="false"
unit="ºC"/>
</GridPane>
For the atmospheric pressure gauge, since I wanted to mimic an old style barometer, I wanted to use the FGauge
class to give it nicer styling as if there was a glass encasing around the front of the dial. The gauge also had to be composed of a number of sections, and so I chose the SkinType.SECTION
. This gauge configuration requires the use of a SectionBuilder
, though it looks like you can’t use SectionBuilder
in FXML (again, let me know if you’ve found a way). So I built the pressure gauge in the controller class with:
barometerGauge = GaugeBuilder.create()
.skinType(Gauge.SkinType.SECTION)
.needleColor(lcdFontColor)
.title("Atmospheric Pressure")
.unit(" mbar")
.unitColor(Color.WHITE)
.titleColor(Color.WHITE)
.valueVisible(true)
.valueColor(Color.WHITE)
.markersVisible(true)
.decimals(0)
.minValue(940)
.maxValue(1060)
.animated(true)
.knobColor(Color.FLORALWHITE)
.highlightSections(true)
.sections(
SectionBuilder.create()
.start(1040)
.stop(1060)
.text("VERY DRY")
.color(lcdBackgroundColor)
.highlightColor(Color.FLORALWHITE)
.textColor(Gauge.DARK_COLOR)
.build(),
SectionBuilder.create()
.start(1020)
.stop(1040)
.text("FAIR")
.color(lcdBackgroundColor)
.highlightColor(Color.FLORALWHITE)
.textColor(Gauge.DARK_COLOR)
.build(),
SectionBuilder.create()
.start(1000)
.stop(1020)
.text("CHANGE")
.color(lcdBackgroundColor)
.highlightColor(Color.FLORALWHITE)
.textColor(Gauge.DARK_COLOR)
.build(),
SectionBuilder.create()
.start(970)
.stop(1000)
.text("RAIN")
.color(lcdBackgroundColor)
.highlightColor(Color.FLORALWHITE)
.textColor(Gauge.DARK_COLOR)
.build(),
SectionBuilder.create()
.start(940)
.stop(970)
.text("STORMY")
.color(lcdBackgroundColor)
.highlightColor(Color.FLORALWHITE)
.textColor(Gauge.DARK_COLOR)
.build())
.build();
FGauge barometerFGauge = new FGauge(barometerGauge, GaugeDesign.TILTED_BLACK, GaugeDesign.GaugeBackground.WHITE);
vBox.getChildren().addAll(barometerFGauge);
I enjoyed how configurable the gauge designs are – for example, in the barometer gauge, I was able to customize the needle and section background colour to match that of the LCD style temperature and humidity gauges to link colour schemes across the dials. I was also able to customize the highlight colour for when the needle swung to different sections, reflecting what the current weather condition is.
Conclusion
After the initial stumbling block of not being sure how to set up the gauges, once it was all pulled together I really enjoyed using the Medusa JavaFX library. The gauges are easily configurable, look really smooth and modern, and were easy enough to integrate into the existing app logic.
Additional bonus is that my colleague loved it and is happy with the makeover from the basic JavaFX UI to this more modern UI!
If you’d like to see the full JavaFX code used for this project, check out the Controller class here, and the main.fxml here.
And if you’d like to learn how to hook up a Raspberry Pi to a Java app, do check out my colleague’s blog on how to build a weather station on the ArcGIS Developers blog!
Posted on August 25, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.