Thursday, February 11, 2016

Create your own Medusa Gauge Skin

Hi there,

Today I will show you how you can easily create your own skin for the Medusa gauge. Therefore the first thing we need to do is to have an idea of what we would like to visualize. For this post I've decided to create something like a LED bar graph that you might know from your home stereo equalizer. Usually it looks similar to this...at least when I was young :)



So the idea is to create a bar of rectangles or circles that will be filled dependent on the current value. And as you can see on the image above it would be nice to have different colors for values in range (green), warning (yellow) and over the limit (red).
I know I mentioned it couple of times but if you work with graphics (which we do here) it's always good to be able to use a vector drawing program to create a prototype.
It is no secret that I really love Adobe Fireworks because it has all vector features I need plus some bitmap capabilities and it's not confusing like Illustrator with all it's features.
I decided to go with circular led's and my prototype looks like follows...



I use the following values for my prototype

  • ledSize     = 10 px (width * 0.5)
  • ledSpacer = 1px (space between led's, width * 0.05)
  • ledBorder = 5 px (space on top, left, bottom and right side of led stripe, width * 0.25)
  • noOfLeds  = 20
  • width       = 20 px
  • height      = 229 px
This is in principle all we need to get started.
When it comes to coding we have different options in JavaFX on how to draw the led's
  • Use Regions with CSS
  • Use Circles optional with CSS
  • Use Canvas without CSS
Because we have 20 led's in our bar the two first approaches would need 20 nodes on the scene graph to visualize the led's. And because I like to save nodes on the scene graph I will go with the third option, using Canvas nodes without CSS.
The drawback when using the JavaFX Canvas is that you are responsible for drawing it, means everytime we want to set the current value we have to redraw the right Canvas node. Another drawback is that you can only hookup one mouse/touch listener to each Canvas. So if you want to have mouse events in different locations on your Canvas you need to implement the logic by yourself.
But because we only do visualization of values we don't need mouse/touch interaction anyway, so Canvas is fine.
So we will use two Canvas nodes, one for the background where we always draw all led's in an OFF state. This Canvas node only needs a redraw when the size of the control or the sections changes. The other Canvas node will be used to draw the currently active led's.

In principle this is all we need but I usually add another Pane to be able to give the control a specific background and border fill.
That means we will end up with 3 nodes for our custom control which is fine :)
The structure of a custom skin depends on your personal style of coding, if you take a look at my controls you will usually find the following methods

  • Constructor
  • init()                                           (primarily used for initial sizing)
  • initGraphics()                              (setup the scene graph once)
  • registerListeners()                       (register listeners to properties)
  • handleEvents(String eventType)   (handle events received by listeners)
  • redraw()                                      (reload graphic data and redraw)
  • resize()                                        (resize all components)
In addition you will often find different methods that will create or draw parts of the control.
In our case we will have an additional method for drawing the background named drawBackground() and another method to draw the currently active led's named setBar(double value).
I won't go through all the code because it's around 260 lines but we will take a look at the important parts.

Constructor

public CustomGaugeSkin(Gauge gauge) {
    super(gauge);
    if (gauge.isAutoScale()) gauge.calcAutoScale();
    range              = gauge.getRange();
    stepSize           = NO_OF_LEDS / range;
    barBackgroundColor = gauge.getBarBackgroundColor();
    barColor           = gauge.getBarColor();
    sectionsVisible    = gauge.getSectionsVisible();
    sections           = gauge.getSections();
    ledSize            = PREFERRED_WIDTH * 0.5;
    ledSpacer          = PREFERRED_WIDTH * 0.05;
    ledBorder          = PREFERRED_WIDTH * 0.25;

    init();
    initGraphics();
    registerListeners();
}

As you can see we only define some variables that we need before we call the init(), initGraphics() and registerListeners() methods.

The init() method is some kind of standard and not that interesting so let's take a look at the initGraphics() method where we set up the JavaFX scene graph.

initGraphics()

private void initGraphics() {
    backgroundCanvas = new Canvas(PREFERRED_WIDTH, PREFERRED_HEIGHT);
    backgroundCtx    = backgroundCanvas.getGraphicsContext2D();

    foregroundCanvas = new Canvas(PREFERRED_WIDTH, PREFERRED_HEIGHT);
    foregroundCtx    = foregroundCanvas.getGraphicsContext2D();

    ledInnerShadow   = new InnerShadow(BlurType.TWO_PASS_BOX, 
                                       Color.BLACK, 
                                       0.2 * PREFERRED_WIDTH, 
                                       0, 0, 0);
    ledDropShadow    = new DropShadow(BlurType.TWO_PASS_BOX, 
                                      getSkinnable().getBarColor(), 
                                      0.3 * PREFERRED_WIDTH, 
                                      0, 0, 0);

    pane = new Pane(backgroundCanvas, foregroundCanvas);
    pane.setBorder(new Border(new BorderStroke(getSkinnable().getBorderPaint(), 
                                               BorderStrokeStyle.SOLID,
                                               CornerRadii.EMPTY, 
                                               new BorderWidths(1))));
    pane.setBackground(new Background(new BackgroundFill(getSkinnable().getBackgroundPaint(), 
                                                         CornerRadii.EMPTY, 
                                                         Insets.EMPTY)));

    getChildren().setAll(pane);
}

In the initGraphics() method you can see that we create the two Canvas nodes, two effects that we need for the led's and the Pane I mentioned above. Then we add the Canvas nodes to the Pane and add the pane to the Control itself.
The next method we will take a look at is the redraw() method.

redraw()

private void redraw() {
    pane.setBackground(new Background(new BackgroundFill(getSkinnable().getBackgroundPaint(), 
                                                         CornerRadii.EMPTY, 
                                                         Insets.EMPTY)));
    sectionsVisible    = getSkinnable().getSectionsVisible();
    barBackgroundColor = getSkinnable().getBarBackgroundColor();
    barColor           = getSkinnable().getBarColor();
    drawBackground();
    setBar(getSkinnable().getCurrentValue());
}

Here we just take the current values for the colors and then draw the background and set the led bar to the current value. Everytime the width or height of the Control will change the resize() method will be called which looks like follows...

resize()

private void resize() {
    width  = getSkinnable().getWidth() - 
             getSkinnable().getInsets().getLeft() - 
             getSkinnable().getInsets().getRight();
    height = getSkinnable().getHeight() - 
             getSkinnable().getInsets().getTop() - 
             getSkinnable().getInsets().getBottom();

    if (ASPECT_RATIO * width > height) {
        width = 1 / (ASPECT_RATIO / height);
    } else if (1 / (ASPECT_RATIO / height) > width) {
        height = ASPECT_RATIO * width;
    }

    if (width > 0 && height > 0) {
        pane.setMaxSize(width, height);
        pane.relocate((getSkinnable().getWidth() - width) * 0.5, 
                      (getSkinnable().getHeight() - height) * 0.5);

        ledInnerShadow.setRadius(0.2 * width);
        ledDropShadow.setRadius(0.25 * width);

        ledSize   = width * 0.5;
        ledSpacer = width * 0.05;
        ledBorder = 0.25 * width;

        backgroundCanvas.setWidth(width);
        backgroundCanvas.setHeight(height);

        foregroundCanvas.setWidth(width);
        foregroundCanvas.setHeight(height);
    }
}

As you can see we first of all get the available width and height in consideration of the given insets. After that we calculate the control width and height dependent on the aspect ratio.
If the width and height are bigger than 0 we resize the three Nodes (2xCanvas and 1xPane), set the radii of the shadows and calculate parameters like ledSize, ledSpacer and ledBorder.
So there are two more methods left that we should take a look at, first the drawBackground() method.

drawBackground()

private void drawBackground() {
    backgroundCtx.clearRect(0, 0, width, height);
    backgroundCtx.setFill(barBackgroundColor);
    int listSize  = sections.size();
    Section currentSection;
    for (int i = 0 ; i < NO_OF_LEDS ; i++) {
        if (sectionsVisible) {
            double value = (i + 1) / stepSize;
            for (int j = 0 ; j < listSize ; j++) {
                currentSection = sections.get(j);
                if (currentSection.contains(value)) {
                    backgroundCtx.setFill(currentSection.getColor().darker().darker());
                    break;
                } else {
                    backgroundCtx.setFill(barBackgroundColor);
                }
            }
        }
        backgroundCtx.save();
        backgroundCtx.setEffect(ledInnerShadow);
        backgroundCtx.fillOval(ledBorder, 
                               height - ledSize - (i * (ledSpacer + ledSize)) - ledBorder, 
                               ledSize, 
                               ledSize);
        backgroundCtx.restore();
    }
}

Here we first clear the GraphicsContext because we are responsible for the drawing and clearing. I won't go through all steps because I think the code is somehow self explaining. After clearing the GraphicsContext we iterate over the number of led's and draw each led with either the barBackgroundColor or if we have sections enabled with the section color for the given led. To make the switched off led's look a bit better we apply an InnerShadow to each led.
This method only needs to be called when either the sections or section visibility change or when the control was resized.
The last method we will take a look at is the setBar(double value) method.

setValue(double value)

private void setBar(final double VALUE) {
    foregroundCtx.clearRect(0, 0, width, height);
    int            activeLeds = (int) Math.floor(VALUE * stepSize);
    int            listSize   = sections.size();
    Section        currentSection;
    RadialGradient gradient;
    foregroundCtx.setFill(barColor);
    for (int i = 0 ; i < activeLeds ; i++) {
        if (sectionsVisible) {
            double value = (i + 1) / stepSize;
            for (int j = 0 ; j < listSize ; j++) {
                currentSection = sections.get(j);
                if (currentSection.contains(value)) {
                    gradient = new RadialGradient(0, 
                                                  0, 
                                                  ledSize, 
                                                  height - ledSize - (i * (ledSpacer + ledSize)), 
                                                  ledBorder, 
                                                  false , 
                                                  CycleMethod.NO_CYCLE, 
                                                  new Stop(0, currentSection.getColor()), 
                                                  new Stop(0.55, currentSection.getColor()), 
                                                  new Stop(0.85, currentSection.getColor().darker()), 
                                                  new Stop(1, Color.rgb(0, 0, 0, 0.65)));
                    foregroundCtx.setFill(gradient);
                    ledDropShadow.setColor(currentSection.getColor());
                    break;
                } else {
                    gradient = new RadialGradient(0, 
                                                  0, 
                                                  ledSize, 
                                                  height - ledSize - (i * (ledSpacer + ledSize)), 
                                                  ledBorder, 
                                                  false , 
                                                  CycleMethod.NO_CYCLE, 
                                                  new Stop(0, barColor), 
                                                  new Stop(0.55, barColor), 
                                                  new Stop(0.85, barColor.darker()), 
                                                  new Stop(1, Color.rgb(0, 0, 0, 0.65)));
                    foregroundCtx.setFill(gradient);
                    ledDropShadow.setColor(barColor);
                }
            }
        } else {
            gradient = new RadialGradient(0, 
                                          0, 
                                          ledSize, 
                                          height - ledSize - (i * (ledSpacer + ledSize)), 
                                          ledBorder, 
                                          false , 
                                          CycleMethod.NO_CYCLE, 
                                          new Stop(0, barColor), 
                                          new Stop(0.55, barColor), 
                                          new Stop(0.85, barColor.darker()), 
                                          new Stop(1, Color.rgb(0, 0, 0, 0.65)));
            foregroundCtx.setFill(gradient);
            ledDropShadow.setColor(barColor);
        }
        foregroundCtx.save();
        foregroundCtx.setEffect(ledDropShadow);
        foregroundCtx.fillOval(ledBorder, 
                               height - ledSize - (i * (ledSpacer + ledSize)) - ledBorder, 
                               ledSize, 
                               ledSize);
        foregroundCtx.restore();
    }
}

In principle this method does the same as the drawBackground() method with the difference that it only draws as many led's as calculated from the given value. Means the given value will be converted to a number of led's that will be active. Then we iterate over this number and dependent on sections visible/available we color each led either with the barColor or with the appropriate section color.

Well that's nearly all that is needed to create our custom skin for the Medusa Gauge. 

You can find the complete code on github...

If we would like to use our new skin we need to simply set it like follows...
Gauge gauge = GaugeBuilder.create()
                          .backgroundPaint(Gauge.DARK_COLOR)
                          .barBackgroundColor(Color.DARKRED)
                          .barColor(Color.RED)
                          .minValue(0)
                          .maxValue(100)
                          .sectionsVisible(true)
                          .sections(new Section(0, 70, Color.LIME),
                                    new Section(70,85, Color.YELLOW),
                                    new Section(85, 100, Color.RED))
                          .build();

gauge.setSkin(new CustomGaugeSkin(gauge));
So we define a gauge with some parameters like min- and maxValue, colors for the bar and barBackground and some sections.
If you start the CustomGaugeSkinDemo from the Medusa demo project (github) you will see the following output...


In this demo you see 10 gauges with our new custom skin and the scene graph will only contain 40 nodes which is nice.

So that's it for today...keep coding... :)

Monday, February 8, 2016

Building a compass with Medusa

Here we go again...
A couple of days ago the question came up if it is possible to create a compass control with the Medusa gauge? To be honest a dedicated control would fit better but it is possible. The problem with a compass is that it has a range from 0-359 degrees and usually there is something like zero-crossing. Means that the needle always takes the shortest distance to the new position, no matter where it is. So if the needle is pointing to 10 degrees and the next value is 340 degrees the needle would rotate counter clockwise to the new value which is different from the usual gauge behavior where the needle would go clockwise.

I've implemented an experimental feature in Medusa 3.1 named needleBehavior which could either be NeedleBehavior.STANDARD or NeedleBehavior.OPTIMIZED. Only the GaugeSkin makes use of the behavior and also only if it is animated (otherwise it doesn't make sense).
I've only tested it with the following control so be warned to NOT use it with the standard gauges because I'm pretty sure it will lead to wrong behavior.
But long story short, let's take a look at the gauge setup for a compass like control...
Gauge gauge = GaugeBuilder.create()
                          .minValue(0)
                          .maxValue(359)
                          .startAngle(180)
                          .angleRange(360)
                          .build();
So first of all we created a normal gauge with a range from 0-359. It should have 0 degree on top which means we set the startAngle to 180 and the angleRange to 360 for a full circle.



Now we have to disable the autoscaling feature to get exactly our required range from 0-359 and replace the tick labels with our custom ones ("N", "E", "S", "W") in a bigger size.
Gauge gauge = GaugeBuilder.create()
                          .minValue(0)
                          .maxValue(359)
                          .startAngle(180)
                          .angleRange(360)
                          .autoScale(false)
                          .customTickLabelsEnabled(true)
                          .customTickLabels("N", "", "", "", "", "", "", "", "",
                                            "E", "", "", "", "", "", "", "", "",
                                            "S", "", "", "", "", "", "", "", "",
                                            "W", "", "", "", "", "", "", "", "")
                          .customTickLabelFontSize(72)
                          .build();
This will give us the following visualization...


Well that's not bad but the tick marks are annoying so let's get rid of that and the needle could also be a bit bigger.
Gauge gauge = GaugeBuilder.create()                             
                          .minValue(0)
                          .maxValue(359)
                          .startAngle(180)
                          .angleRange(360)
                          .autoScale(false)
                          .customTickLabelsEnabled(true)
                          .customTickLabels("N", "", "", "", "", "", "", "", "",
                                            "E", "", "", "", "", "", "", "", "",
                                            "S", "", "", "", "", "", "", "", "",
                                            "W", "", "", "", "", "", "", "", "")
                          .customTickLabelFontSize(72)
                          .minorTickMarksVisible(false)
                          .mediumTickMarksVisible(false)
                          .majorTickMarksVisible(false)
                          .needleType(NeedleType.FAT)
                          .build();
And with this modifications it will look like this...


Ok, we are getting there, so the value text has to be removed, the knob and needle could have a more flat style and a border around the control would also be nice...well let's do it...
Gauge gauge = GaugeBuilder.create()
                          .minValue(0)
                          .maxValue(359)
                          .startAngle(180)
                          .angleRange(360)
                          .autoScale(false)
                          .customTickLabelsEnabled(true)
                          .customTickLabels("N", "", "", "", "", "", "", "", "",
                                            "E", "", "", "", "", "", "", "", "",
                                            "S", "", "", "", "", "", "", "", "",
                                            "W", "", "", "", "", "", "", "", "")
                          .customTickLabelFontSize(72)
                          .minorTickMarksVisible(false)
                          .mediumTickMarksVisible(false)
                          .majorTickMarksVisible(false)
                          .valueVisible(false)
                          .needleType(NeedleType.FAT)
                          .needleShape(NeedleShape.FLAT)
                          .knobType(KnobType.FLAT)
                          .knobColor(Gauge.DARK_COLOR)
                          .borderPaint(Gauge.DARK_COLOR)
                          .build();
So with this modifications in place our compass looks not too bad...


So now let's switch on the animation and needleBehavior like follows...
Gauge gauge = GaugeBuilder.create()
                          .minValue(0)
                          .maxValue(359)
                          .startAngle(180)
                          .angleRange(360)
                          .autoScale(false)
                          .customTickLabelsEnabled(true)
                          .customTickLabels("N", "", "", "", "", "", "", "", "",
                                            "E", "", "", "", "", "", "", "", "",
                                            "S", "", "", "", "", "", "", "", "",
                                            "W", "", "", "", "", "", "", "", "")
                          .customTickLabelFontSize(72)
                          .minorTickMarksVisible(false)
                          .mediumTickMarksVisible(false)
                          .majorTickMarksVisible(false)
                          .valueVisible(false)
                          .needleType(NeedleType.FAT)
                          .needleShape(NeedleShape.FLAT)
                          .knobType(KnobType.FLAT)
                          .knobColor(Gauge.DARK_COLOR)
                          .borderPaint(Gauge.DARK_COLOR)
                          .animated(true)
                          .animationDuration(500)
                          .needleBehavior(NeedleBehavior.OPTIMIZED)
                          .build();
If you now set the value of the gauge you will (hopefully) see that the needle always takes the optimized way to the next value (means the shortest angle distance). 

ATTENTION:
Keep in mind that as soon as you hook this control up to a real device you should always switch of the animation because values from real devices might come in at high rate.

Because we switched of the value text you might want to add a separate Label that contains the current value of the gauge. Therefore you simply add it as follows...
Label value = new Label("0°");
value.setFont(Fonts.latoBold(72));
value.setAlignment(Pos.CENTER);
And to update the Label with the value of the gauge we simply add a listener to the valueProperty() of the gauge like this...
gauge.valueProperty().addListener(o -> {
    value.setText(String.format(Locale.US, "%.0f°", gauge.getValue()));
});
If you put those controls in a VBox you will get something like this...


And that's it...not too bad right :)

The code can be found on github...

Keep coding...

Friday, February 5, 2016

Fun with Medusa Clocks...

Hi there,

Time for some fun again...
When I was searching the web for cool stuff I stumbled upon the following clock...



The idea is really great and I thought by myself that it might be nice to have that clock in JavaFX.

As you might have seen I was playing around with clocks anyway so why not use the Medusa Clock to create the Clock of Clocks...

First of all it was needed to create a new skin with really fat hands and so I've added the FatClockSkin to Medusa 3.1 which looks very similar to the clocks above, here it is...



With this visualization in place the next challenge was to get the right behavior of the clock which was not that easy. The problem is that in the real clock the hands are controlled separately which means you can place the minute hand to 45 minutes and the hour hand to 6 directly. Well with the Medusa clock that's not possible because I calculate the angle of the hands from the given time which means in the case of 5:45 my clock will look like follows...



As you can see the hour pointer is not pointing exactly to the 6 which would be needed for the Clock of Clocks. To emulate that behavior I've added a property named discreteHours(). If this property is enabled the hour hand will only jump to the next hour when the hour switches from e.g. 5 to 6. So to get the right behavior we need to set the time to 6:45 instead of 5:45 and the clock will look like follows...



That looks much better :)

The last thing that I needed to implement was the possibility to animate the hands from a given time to another time. Usually the clock just has a property named running which will let the clock run automatically with the given time. But in this case I needed the ability to set the clock to a time and animate it in a given duration to another time. Therefore I've implemented the same properties that are also part of the Medusa Gauge. Animated and animationDuration. 

ATTENTION:
If animated == true the clock can NOT be started by calling setRunning(true).

With these new features in place I could start to build the clock. It is too much code to show it all here, so let me try to explain shortly what I did.
The clock is made out of 4 numbers where each number contains 6 clocks. So I've created a GridPane for each number with the 6 clocks.
Each clock is created with the following method...
private Clock createClock() {
    Clock clock = ClockBuilder.create()
                              .skinType(ClockSkinType.FAT)
                              .backgroundPaint(Color.WHITE)
                              .prefSize(100, 100)
                              .animationDuration(7500)
                              .animated(true)
                              .discreteMinutes(false)
                              .discreteHours(true)
                              .hourTickMarkColor(Color.rgb(200, 200, 200))
                              .minuteTickMarkColor(Color.rgb(200, 200, 200))
                              .tickLabelColor(Color.rgb(200, 200, 200))
                              .build();
    clock.setEffect(new DropShadow(5, 0, 5, Color.rgb(0, 0, 0, 0.65)));
    return clock;
}
With this method I can easily create the GridPane with the following method...
private GridPane createNumberGrid() {
    GridPane grid = new GridPane();
    grid.add(createClock(), 0, 0);
    grid.add(createClock(), 1, 0);
    grid.add(createClock(), 0, 1);
    grid.add(createClock(), 1, 1);
    grid.add(createClock(), 0, 2);
    grid.add(createClock(), 1, 2);
    grid.setHgap(10);
    grid.setVgap(10);
    return grid;
}
The most work was needed to create a lookup table with the right time for each clock and for each number (0-9). Therefore I've simply created 6 LocalTime arrays that contains the correct time for each number and clock. This is the array for the upper left clock of each number...
private LocalTime[]    upperLeft  = { LocalTime.of(6, 15),   // 0 
                                      LocalTime.of(8, 40),   // 1 
                                      LocalTime.of(3, 15),   // 2
                                      LocalTime.of(3, 15),   // 3
                                      LocalTime.of(6, 30),   // 4
                                      LocalTime.of(6, 15),   // 5
                                      LocalTime.of(6, 15),   // 6
                                      LocalTime.of(3, 15),   // 7
                                      LocalTime.of(6, 15),   // 8
                                      LocalTime.of(6, 15) }; // 9
And so there is a similar array for the upper left, upper right, mid left, mid right, lower left and lower right clock.
Now I only have to run an AnimationTimer where I check the time every 10 seconds and set the right time for each clock.

If you are interested in the code you can find it on github...

The result of the work looks like this...



Because there are clocks that are not needed to visualize a number (like in 1 or 7) those not needed clocks will always be set to 7:40. This is the same as in the real clock.

Here is also a little video of the clock in action...


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

Monday, February 1, 2016

Taking control by using the Medusa Clock

Hi there,

Last weekend I've got the idea that it might make sense to add sections and areas to the Medusa Clock control. The visualization looks like follows...



Sections and Areas
The orange part is the area and the green part the section. Sections will be drawn on top of areas. The same principle exists in the Medusa Gauge control. The Gauge uses instances of the Section class and the Clock of the TimeSection class. Both of these classes have a method named checkForValue(). This method takes a double (Gauge) or a LocalTime (Clock) as a parameter and checks if this value is inside of the section that is defined by a start and a stop value.
The Section and TimeSection class will fire events for the following cases

  • The current value entered the section
  • The current value left the section
With this approach you can attach a listener to the section and enable the setCheckSectionForValue(true) in either the Gauge or Clock control to activate this functionality.
In a clock control that can be really useful to control things like switching lights for example. Let's take a look at a simple example.
We will switch on a light in our garden from 7pm to 10pm by using the TimeSection in the Clock control.
The first thing we need to do is creating the TimeSection like follows:

TimeSection gardenLightOn =
    TimeSectionBuilder.create()
                      .start(LocalTime.of(19, 00, 00))
                      .stop(LocalTime.of(22, 00, 00))
                      .onTimeSectionEntered(event -> 
                          System.out.println("Garden light on"))
                      .onTimeSectionLeft(event -> 
                          System.out.println("Garden light off"))
                      .color(Color.rgb(255, 128, 0, 0.5))
                      .build();

As you can see we define the start and stop values by LocalTime instances and add an EventHandler with the onTimeSectionEntered() and onTimeSectionLeft() calls. Then we define a color for the section and that's it.
Now we have to create the Clock control and set the sections like follows:

Clock clock = ClockBuilder.create()
                          .sectionsVisible(true)
                          .sections(gardenLightOn)
                          .checkSectionsForValue(true)
                          .running(true)
                          .build();

The result of this will then look like this...


As soon as it will be 7pm you will see an output on the console with "Garden light on" and at 10pm it will say "Garden light off".
So in this case we only used a System.out.println command to show some action but you might want to send a MQTT message to your device that switches on or off the lights in the garden.
The area is in principle exactly the same just with a different visualization as you can see on the first image on this post. Oh and before I forget it...you can add multiple sections and areas to the Clock and also the Gauge control.
This was one way to use the Medusa Clock to control something but there is another one.

Alarms
You could also use Alarms to control things.
The Medusa Clock also takes a list of Alarm objects. You could either create a simple Alarm and react on the Alarm in the setOnAlarm() method with an EventHandler or you can create an Alarm that executes a so called Command. But let's take a look at the code to create a simple Alarm...

Alarm simpleAlarm =
    AlarmBuilder.create()
                .time(LocalDateTime.now().plusSeconds(2))
                .repetition(Repetition.ONCE)
                .text("Simple Alarm")
                .build();

As you can see it's really simple, you just define the time and the repetition, add a text and now we can add the Alarm to the Clock as follows...

Clock clock = ClockBuilder.create()
                    .alarmsEnabled(true)
                    .alarms(simpleAlarm)
                    .onAlarm(alarmEvent ->
                        System.out.println(alarmEvent.ALARM.getText()))
                    .running(true)
                    .build();

Two seconds after starting our code we will see the "Simple Alarm" message on the console...very easy right :)
In case you would like to create specific Alarms you will find the @FunctionalInterface called Command that only has one method named execute(). This method will be called when an Alarm is triggered and a Command is provided. So with this you could create a Command for switching on/off lights like follows...

class LightOn implements Command {
    @Override public void execute() {
        System.out.println("Switch light on");
    }
}
class LightOff implements Command {
    @Override public void execute() {
        System.out.println("Switch light off");
    }
}

LightOn  lightOn  = new LightOn();
LightOff lightOff = new LightOff();

Alarm alarmLightOn = 
    AlarmBuilder.create()
                .time(LocalDateTime.now().plusSeconds(5))
                .repetition(Repetition.ONCE)
                .text("Light On")
                .command(lightOn)
                .build();

Alarm alarmLightOff = 
    AlarmBuilder.create()
                .time(LocalDateTime.now().plusSeconds(10))
                .repetition(Repetition.ONCE)
                .text("Light off")
                .command(lightOff)
                .build();

So we created two classes that implement the Command interface and in this case we only output something on the console but you could implement whatever you want in that classes. Then we create two Alarms that take the created command classes.
Now we just have to create the clock control and add the alarms like this...

Clock clock = ClockBuilder.create()
                    .alarmsEnabled(true)
                    .alarms(alarmLightOn, alarmLightOff)
                    .onAlarm(event ->
                        System.out.println(LocalTime.now() +
                                           ":" +
                                           event.ALARM.getText()))
                    .running(true)
                    .build();

When you start that code you should see something like the following on your console...


So the first Alarm was triggered 5 sec after the program started and 10 sec after the program was started the second Alarm was triggered.

To be honest I was not sure if it makes sense to implement that functionality in a Clock control but this makes it very easy for me to use a JavaFX Clock control on e.g. a Raspberry Pi with a display and at the same time I'm able to control things with it without using extra code :)

The code can be found on github...

So that's it for today...keep coding... :)

Friday, January 29, 2016

Building a KPI dashboard using Medusa

Hi everyone,

Today I'll show you how to build a simple KPI (Key Performance Indicator) dashboard by using Medusa. If you google for "KPI Dashboard" images you will get an idea of what I'm talking about. When visualizing KPI's you usually have a value and either some colored sections that indicates something like poor, average and good or you have a threshold indicator that shows the target value. Or you have both :)
Interestingly the management prefers gauges where the analysts prefer bulletcharts to visualize KPI's. The problem with gauges is that they use a lot of space with no real benefit (except that they look good).
Let's take a look at some typical KPI's like

  • Revenue
  • Profit
  • Sales
For each of those KPI's we have a maximum value, a target value and three zones for
  • Poor (red)
  • Average (yellow)
  • Good (green)
If we visualize this using gauges we create a factory method getGauge() that could look like follows...

private Gauge getGauge(final String TITLE, final String UNIT, 
                       final double MAX_VALUE, final double THRESHOLD, 
                       final Section... SECTIONS) {
    return GaugeBuilder.create()
                       .skinType(HORIZONTAL)
                       .animated(true)
                       .thresholdColor(GRAY)
                       .title(TITLE)
                       .unit(UNIT)
                       .decimals(0)
                       .minorTickMarksVisible(false)
                       .mediumTickMarksVisible(false)
                       .majorTickMarksVisible(false)
                       .tickLabelLocation(TickLabelLocation.OUTSIDE)
                       .tickLabelOrientation(TickLabelOrientation.HORIZONTAL)
                       .maxValue(MAX_VALUE)
                       .threshold(THRESHOLD)
                       .areasVisible(true)
                       .areas(SECTIONS)
                       .knobColor(Color.BLACK)
                       .needleSize(NeedleSize.THICK)
                       .needleColor(Color.BLACK)
                       .thresholdVisible(true)
                       .thresholdColor(GRAY)
                       .build();
}

If we use this method to create the three gauges the result will look similar to this...

As you can see this takes a lot of space but it might look nice to one or the other.
Now that we saw the gauge approach let's take a look at the BulletChart approach. Therefore we create another factory method that will create the BulletCharts for us and it could look like follows...

private Gauge getBulletChart(final String TITLE, final String UNIT, 
                             final double MAX_VALUE, final double THRESHOLD, 
                             final Section... SECTIONS) {
    return GaugeBuilder.create()
                       .skinType(BULLET_CHART)
                       .animated(true)
                       .thresholdColor(GRAY)
                       .title(TITLE)
                       .unit(UNIT)
                       .maxValue(MAX_VALUE)
                       .threshold(THRESHOLD)
                       .sectionsVisible(true)
                       .sections(SECTIONS)
                       .build();
}

If we create the KPI dashboard using this method it will look similar to this...

As you can see we save some space by using the BulletChartSkin and it also doesn't look that fancy but simply shows the facts.

The demo code could be found over at github...

And that's it for today...keep coding...

Wednesday, January 27, 2016

Building a fuel gauge using Medusa

Hi there,

Today I will show you how you could build a simple fuel gauge with Medusa. To give you an idea on what I'm talking about, here is an image of a VDO fuel gauge that you can buy...



To get as close as possible to this design we'll use a horizontal gauge that comes with an angle range of 180 degrees. By using the GaugeBuilder it will look like this...

Gauge gauge = GaugeBuilder.create()
                          .skinType(SkinType.HORIZONTAL)
                          .prefSize(500, 250)
                          .build();
It will look as follows...



Because most of the car dashboards are colored dark we'll adjust the colors as follows...

Gauge gauge = GaugeBuilder.create()
                          .skinType(SkinType.HORIZONTAL)
                          .prefSize(500, 250)
                          .foregroundBaseColor(Color.rgb(249, 249, 249))
                          .knobColor(Color.BLACK)
                          .build();
To get a nice result we also have to set the background of our container (e.g. a StackPane) to a nice dark gradient. This will give us the following visualization...



In the next step we have to adjust the scale (from 0.0 to 1.0), hide the value text and add a section with a red color for the reserve (from 0.0 to 0.2). The code to realize this looks like this...
Gauge gauge = GaugeBuilder.create()
                          .skinType(SkinType.HORIZONTAL)
                          .prefSize(500, 250)
                          .foregroundBaseColor(Color.rgb(249, 249, 249))
                          .knobColor(Color.BLACK)
                          .maxValue(1.0)
                          .valueVisible(false)
                          .sectionsVisible(true)
                          .sections(new Section(0.0, 0.2, Color.rgb(255, 10, 1)))
                          .build();
which will make the gauge look like follows...



That's not bad but now let's get rid of the minor and medium tick marks, add a title ("FUEL") and set another tick mark type for the major tick marks (TickMarkType.BOX)
Gauge gauge = GaugeBuilder.create()
                          .skinType(SkinType.HORIZONTAL)
                          .prefSize(500, 250)
                          .foregroundBaseColor(Color.rgb(249, 249, 249))
                          .knobColor(Color.BLACK)
                          .maxValue(1.0)
                          .valueVisible(false)
                          .sectionsVisible(true)
                          .sections(new Section(0.0, 0.2, Color.rgb(255, 10, 1)))
                          .minorTickMarksVisible(false)
                          .mediumTickMarksVisible(false)
                          .majorTickMarkType(TickMarkType.BOX)
                          .title("FUEL")
                          .build();
So far so good, now it should look like this...



In principle this is ok except the tick labels that show only "0" and "1" because the tick label decimals are set to 0. If we set them to 1 like in the following code...
Gauge gauge = GaugeBuilder.create()
                          .skinType(SkinType.HORIZONTAL)
                          .prefSize(500, 250)
                          .foregroundBaseColor(Color.rgb(249, 249, 249))
                          .knobColor(Color.BLACK)
                          .maxValue(1.0)
                          .valueVisible(false)
                          .sectionsVisible(true)
                          .sections(new Section(0.0, 0.2, Color.rgb(255, 10, 1)))
                          .minorTickMarksVisible(false)
                          .mediumTickMarksVisible(false)
                          .majorTickMarkType(TickMarkType.BOX)
                          .title("FUEL")
                          .tickLabelDecimals(1)
                          .build();
we will get the following result...



So with this we are getting there, maybe the needle could be a bit bigger and with a more round look. Also the color of the needle could be more orange and a shadow would also be nice. So here you go...
Gauge gauge = GaugeBuilder.create()
                          .skinType(SkinType.HORIZONTAL)
                          .prefSize(500, 250)
                          .foregroundBaseColor(Color.rgb(249, 249, 249))
                          .knobColor(Color.BLACK)
                          .maxValue(1.0)
                          .valueVisible(false)
                          .sectionsVisible(true)
                          .sections(new Section(0.0, 0.2, Color.rgb(255, 10, 1)))
                          .minorTickMarksVisible(false)
                          .mediumTickMarksVisible(false)
                          .majorTickMarkType(TickMarkType.BOX)
                          .title("FUEL")
                          .tickLabelDecimals(1)
                          .needleSize(NeedleSize.THICK)
                          .needleShape(NeedleShape.ROUND)
                          .needleColor(Color.rgb(255, 61, 10))
                          .shadowsEnabled(true)
                          .build();
And the result will look like follows...



Well that is ok'ish but still the tick labels are a bit confusing. I would prefer something like "E" at the lower end of the scale, "F" at the upper end and "1/2" in the middle but how to realize that? I've added the ability to use custom tick labels and used a very simple implementation means it's a simple list of Strings. So we have 11 tick labels here which means we'll need a list of 11 Strings with the appropriate labels. To get it even closer to the original one we set the angle range to 90 degrees. So the final code will look like this...
Gauge gauge = GaugeBuilder.create()
                          .skinType(SkinType.HORIZONTAL)
                          .prefSize(500, 250)
                          .foregroundBaseColor(Color.rgb(249, 249, 249))
                          .knobColor(Color.BLACK)
                          .maxValue(1.0)
                          .valueVisible(false)
                          .sectionsVisible(true)
                          .sections(new Section(0.0, 0.2, Color.rgb(255, 10, 1)))
                          .minorTickMarksVisible(false)
                          .mediumTickMarksVisible(false)
                          .majorTickMarkType(TickMarkType.BOX)
                          .title("FUEL")
                          .needleSize(NeedleSize.THICK)
                          .needleShape(NeedleShape.ROUND)
                          .needleColor(Color.rgb(255, 61, 10))
                          .shadowsEnabled(true)
                          .angleRange(90)
                          .customTickLabelsEnabled(true)
                          .customTickLabels("E", "", "", "", "", "1/2", "", "", "", "", "F")
                          .build();
and this will give us the following result...



So that's nearly all you can do with Medusa but if you would like to replace the text "FUEL" with an icon you could easily put the icon (a JavaFX Region styled with CSS) and the gauge in a StackPane. And because the Medusa gauge comes with a transparent background you will see the icon. To do so you simply have to remove the .title("FUEL") line from the code above and add the following code to your start() method.
Region    fuelIcon = new Region();
fuelIcon.getStyleClass().setAll("icon");

StackPane pane = new StackPane(fuelIcon, gauge);
pane.setPadding(new Insets(10));
LinearGradient gradient = new LinearGradient(0, 0, 0, pane.getLayoutBounds().getHeight(),
                                             false, CycleMethod.NO_CYCLE,
                                             new Stop(0.0, Color.rgb(38, 38, 38)),
                                             new Stop(1.0, Color.rgb(15, 15, 15)));
pane.setBackground(new Background(new BackgroundFill(gradient, CornerRadii.EMPTY, Insets.EMPTY)));

Scene scene = new Scene(pane);
scene.getStylesheets().add(Test.class.getResource("styles.css").toExternalForm()); 
As you can see we add a css file named "styles.css" to the scene and the set the ".icon" style class for the fuelIcon region. The css file looks like this...
.icon {
    -fx-max-width        : 36px;
    -fx-max-height       : 41px;
    -fx-background-color : rgb(249, 249, 249);
    -fx-scale-shape      : true;
    -fx-shape            : "M 25.775 17.025 L 25.775 8.645 L 28.3633 11.2324 L 29.5547 17.6875
                            C 29.6035 18.0293 29.7988 18.332 30.0918 18.5273 L 33.2949 20.666
                            L 33.2949 30.6855 C 33.2754 31.2617 33.0508 31.9551 32.0449 31.9551
                            C 31.0293 31.9551 30.7949 31.2617 30.7754 30.6953 L 30.7754 23.166
                            C 30.7754 19.0669 27.9706 17.4574 25.775 17.025 ZM 25.7754 4.6699
                            C 25.7754 2.4141 23.9492 0.5879 21.6934 0.5879 L 9.7793 0.5879
                            C 7.5332 0.5879 5.6973 2.4141 5.6973 4.6699 L 5.6973 5.6074
                            L 5.6973 29.1328 L 5.6973 33.2148 L 9.7793 33.2148 L 21.6934 33.2148
                            L 25.7754 33.2148 L 25.7754 29.1328 L 25.775 19.605
                            C 26.8895 19.9367 28.2754 20.8192 28.2754 23.166 L 28.2754 30.6953
                            C 28.2754 32.209 29.2813 34.4648 32.0449 34.4648
                            C 34.7988 34.4648 35.8047 32.209 35.8047 30.6953 L 35.8047 20.7246
                            C 35.8145 20.7051 35.8145 20.6758 35.8145 20.666
                            C 35.8145 20.5488 35.7949 20.4414 35.7656 20.334 L 34.5352 12.9316
                            C 34.4766 12.5801 34.2813 12.2773 33.9883 12.0918 L 30.3262 9.6504
                            L 25.775 5.1 L 25.7754 4.6699 ZM 22.0156 5.8613 L 22.0156 12.1211
                            C 22.0156 13.166 21.166 14.0059 20.1309 14.0059 L 11.3516 14.0059
                            C 10.3066 14.0059 9.4668 13.166 9.4668 12.1211 L 9.4668 5.8613
                            C 9.4668 4.8164 10.3066 3.9766 11.3516 3.9766 L 20.1309 3.9766
                            C 21.166 3.9766 22.0156 4.8164 22.0156 5.8613 ZM 29.2227 35.7148
                            L 2.25 35.7148 C 1.3809 35.7148 0.6777 36.418 0.6777 37.2871
                            L 0.6777 38.2246 L 0.6777 39.1719 L 0.6777 40.7344 L 2.25 40.7344
                            L 29.2227 40.7344 L 30.7949 40.7344 L 30.7949 39.1719 L 30.7949 38.2246
                            L 30.7949 37.2871 C 30.7949 36.418 30.0918 35.7148 29.2227 35.7148 Z ";
}
As you can see we make use of SVG in the -fx-shape css property and set it's max size to 36x41 pixels. With this modifications in place our fuel gauge will then look like this...



et voilĂ  here you go, a fuel gauge...
And if you prefer a vertical gauge instead of the horizontal one you can simply exchange the call .skinType(SkinType.HORIZONTAL) with .skinType(SkinType.VERTICAL) in the example above and you will get something like this...



Please find the code for this demo over at github...

That's it for today, I hope you enjoy Java(FX) as much as I do, keep coding...