Building an Analog Clock with Timelines

By Vaibhav Choudhary, October 10, 2008

With the JavaFX technology it is easy to build your own customized analog clock with a simple graphics operation and binding.

Understanding the Code

The code shown in Figure 1 creates an analog clock. Most of the drawing is based on public attributes, enabling you to easily customize the look by changing a few settings.

Source Code
package analogclock;

import javafx.stage.*;
import javafx.scene.*;
import javafx.geometry.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.transform.*;
import javafx.animation.*;
import javafx.scene.image.*;
import javafx.scene.shape.*;

// Java Legacy
import java.util.*;
import java.lang.Math;
import java.lang.System;

var months:String[] = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN",
				"JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];

public class AnalogClock extends CustomNode {

    var radius: Number = 80;    // Decides the main size of the clock.
    var centerX: Number = 115 ; // shifting the X center
    var centerY: Number = 144 ; // shifting the Y center
    var hours:Number;
    var minutes:Number;
    var seconds:Number;
    var date:String;
    var month:String;
    var year:String;
    var combination: String;

    // intializing the watch
    init {
        var timeline = Timeline {
            repeatCount: Timeline.INDEFINITE
            keyFrames : [
                KeyFrame {
                    time : 1s
                    action: function() {
                        actionOnTick();
                        }
                }
            ]
        }
        timeline.play();
    }
    function createCalendar() {
        def calendar = Calendar.getInstance();
        calendar.setTime(new Date()); // fix for mobile
        calendar
    }
    // action taken on one tick
    public function actionOnTick () {
        var calendar = createCalendar();
        seconds = calendar.get(Calendar.SECOND);
        minutes = calendar.get(Calendar.MINUTE);
        hours = calendar.get(Calendar.HOUR);
        date = String.valueOf(calendar.get(Calendar.DATE));
        if(date.length()!=2)  {
            date = "0{date}";
        }
        month = months[(calendar.get(Calendar.MONTH))];
        year = String.valueOf((calendar.get(Calendar.YEAR))); // 1900 offset
        combination = "{date}-{month}-{year}";
    }

    // overriding the method create()
    public override function create(): Node {
        return Group {
            transforms: [
                Transform.translate(centerX, centerY)
            ]
            content: [
                Circle {
                    radius: radius + 20
                    fill: RadialGradient {
                        centerX: 0
                        centerY: 0
                        radius: radius + 20
                        proportional: false
                        stops: [

                            Stop { offset: 0.9 color: Color.SILVER },
                            Stop { offset: 1.0 color: Color.BLACK }
                        ]
                    }

                },
                Circle {
                    radius: radius + 10
                    stroke: Color.BLACK
                    fill: RadialGradient {
                        centerX: 0
                        centerY: 0
                        radius: 90
                        proportional: false
                        stops: [
                            Stop { offset: 0.0 color: Color.WHITE },
                            Stop { offset: 1.0 color: Color.CADETBLUE }

                        ]
                    }

                },
                Rectangle {
                    x: -35, y: 2*radius/3 - 15
                    width: 71 height: 20
                    fill: Color.GRAY
                    opacity:0.4
                    strokeWidth: 2
                    stroke: Color.BLACK
                    arcHeight:10
                    arcWidth:10
                },
                Text {
                    font: Font {
                        size: 11
				name: "Arial"
                    }
                    x: -31 , y: 2 * radius / 3
                    content: bind combination
                },
                //setting the main digits 3,6,9,12
                for (i in [3, 6, 9, 12])
                Text {
                    transforms:bind [
                        Transform.translate(-5, 5)
                    ]
                    x: radius * (( i + 0 ) mod 2 * ( 2 - i / 3))
                    y: radius * (( i + 1 ) mod 2 * ( 3 - i / 3))
                    content: "{i}"
                    font: Font {
                        size: 11
                        name: "Arial"
                    }
                },
                // making dots on rest of the place
                for (i in [1..12])
                if (i mod 3 != 0 ) then Circle {
                    transforms:Rotate { angle: 30 * i }
                    centerX: radius
                    radius: 3
                    fill: Color.BLACK
                } else [ ],

                // circle at the core center
                Circle {
                    radius: 5
                    fill: Color.BLACK
                },
                // one more circle inside the above circle
                Circle {
                    radius: 3
                    fill: Color.GRAY
                },
                // second arm
                Line {
                    transforms: Rotate { angle: bind seconds * 6 }
                    endY: -radius - 3
                    strokeWidth: 2
                    stroke: Color.RED

                },
                // hour arm
                Path {
                    transforms: Rotate {
				angle: bind (hours + minutes / 60) * 30 - 90
			  }
                    fill: Color.BLACK
                    elements: [
                        MoveTo {x: 4, y: 4},
                        ArcTo {x: 4 y: -4 radiusX: 1 radiusY: 1},
                        LineTo{ x: radius - 15  y: 0},
                    ]
                },
                // minute arm
                Path {
                    transforms: Rotate { angle: bind minutes * 6 - 90 }
                    fill: Color.BLACK
                    elements: [
                        MoveTo {x: 4, y: 4},
                        ArcTo {x: 4 y: -4 radiusX: 1 radiusY: 1},
                        LineTo{ x: radius  y: 0},
                    ]
                }

            ]
        };
        }
}

Figure 1: AnalogClock.fx Class

Changing the data member radius, enables you to make the clock bigger or smaller.

Source Code
var radius: Number = 80;    // Decides the main size of the clock.

Figure 2: Data Member radius

Customizing the Code

To further customize the clock, change a few of the attributes and use a gradient for the clock fill.

Source Code
Circle {
    radius: radius + 10
    stroke: Color.BLACK
    fill: RadialGradient {
          centerX: 0
          centerY: 0
          radius: 90
          proportional: false
          stops: [
              Stop { offset: 0.0 color: Color.WHITE },
              Stop { offset: 1.0 color: Color.YELLOWGREEN }
          ]
    }
},

Figure 3: Change Colors and Gradient


Figure 4: Clock With a Different Shade