Pack Information Into a Bullet Chart

Learn how to write a simple JavaFX application to create a sequentially animated bullet chart.

What's a Bullet Chart?

The bullet chart, described in Stephen Few's Information Dashboard Design, packs a lot of information into a compact space.

Components of a bullet chartFigure 1: Components of a Bullet Chart

In Figure 1, the green bars display the actual values for each category being measured. The vertical black line shows the quantity that was predicted, so it is easy to see which categories exceeded predictions and which fell short. The areas shaded in gray behind the bar define the range of poor, average, and good performance for that category. With this type of chart it would be possible to show graphically, for example, that a measure exceeded expectations but still performed poorly.

The simple bullet chart application described in this article is based on an example shown in the book and is in the form of an executive dashboard, showing key metrics.

How the Code Works

In the real world, you would probably want to draw the data from a database, but in this application, the data is defined in a series of variables, as shown in the following source code snippet from Main.fx.

Source Code: Variables Encoding the Data
var expected: Number[] = [150,250,50,4.5,17]; //predicted values
var achieved: Number[] =[100,270,60,4,12];  //actual values
var below: Number[] = [50,150,30,2.5,5];  //upper boundary for poor region
var avg: Number[] = [120,220,50,4,10];  //upper boundary for average region
var good: Number[] = [230,360,70,5,20];  //upper boundary for good region
var txt: String[] = ["       Profit", "  Turnover", "       Gross",
    " Cust. Sat.", " Iterations"];  //Bar titles
var subtxt: String[] = ["1000 USD", "1000 USD", "1000 USD",
    "       1-5", "          %"];  //Bar subtitles
var startdelay: Duration[] = [1s,7s,13s,19s,26s]; //Start time for each bar
var endtime: Duration[] = [6s,12s,18s,24s,30s] //End time for each bar

The text and subtxt variables are used to create the bar titles and subtitles on the left side. Note the spaces preceding the values in these two variables. Although the Text class has a textAlignment variable, it only works well with bulk text. For labels like the bar titles, it is better to use the default left alignment and add spaces to push the text to the right.

The bulk of the work to create this application lies in arranging the screen layout. The data values must be converted to pixels on the screen to be drawn correctly. The formulas in the application are easier to understand if you can visualize the horizontal layout of the bars on the screen, as shown in Figure 2.

Horizontal Layout of the BarsFigure 2: Horizontal Layout of the Bars

The maximum length of each bar is 340 - 100 = 240 pixels. The values for each bar are different. Because the variable good represents the maximum value possible for the bar, the value of good will always appear at x:340 pixels from the left side of the screen, or 240 pixels to the right of where the bar begins. To calculate the horizontal position at which the actual value will be drawn, you can set up a ratio comparison, because three of the values are known, and solve for x, as shown in Figure 3.

Converting Bar Data to PixelsFigure 3: Converting Bar Data to Pixels

The timeline for animation therefore uses this formula to calculate the length of the animated bar, as shown in the following code snippet from Bullet.fx.

Source Code: The KeyFrame Instance in the Timeline
KeyFrame {
    time: endtime
    canSkip: true
    values: [
        len => 240 * achieved / good tween Interpolator.LINEAR
    ]
} 

Other parts of the code use similar formulas. For example, to calculate the horizontal position for the thin vertical rectangle that represents the predicted value (represented by the variable expected), the same formula is used, and 100 is added to account for the distance between the left side of the screen and the starting point of the bar. This calculation is shown in the following code snippet from Bullet.fx.

Source Code: Calculation of the Expected Value for the x Variable in the Rectangle Instance
Rectangle {
    cache: true
    x: 100 + 240 * expected / good,
    y: 50
    width: 3,
    height: 36
    fill: Color.BLACK
},

The animation in this application is sequential, meaning the bars are drawn one after another. This sequencing requires two KeyFrame instances for each bar, one defining the start point and one defining the end point of the animation.

Source Code: The Timeline Instance to Animate the Bars
public var t1 = Timeline {
        repeatCount: 1
        keyFrames: [
            KeyFrame {
                time: startdelay
                canSkip: true
                values: [
                    len => 0
                ]
            } 
            KeyFrame {
                time: endtime
                canSkip: true
                values: [
                    len => 240 * achieved / good tween Interpolator.LINEAR
                ]
            } 
        ]
    }

In other respects, this application is a great deal like the animated line graph and bar graph applications presented in earlier articles in this series. See the Related LInks section below for links to these articles. In the bar charts, the bars were vertical, and the animation moved from bottom to top. The height of the animated bar was specified with the translateY variable in the Rectangle object instance for each bar. Because the bars in this bullet chart are horizontal, the length of the animated bar is specified with the width variable in the Rectangle object instance.

Try It

Here are some simple changes you can make to the code to test your understanding.

  • Move the legend to the bottom right of the screen.
  • Try some other types of animation. For example, the Interpolator class has a number of variables besides LINEAR. Substitute some of the other variables and see what happens to the animation.
  • Animate the vertical bars for the predicted values.
  • Add more tick marks at smaller intervals.
  • Change the animation from horizontal to vertical. Try animating a shape from top to bottom as well as bottom to top.
  • Integrate the application with a real data source. See the Pet Catalog application in the Related Links for the use of a RESTful web service to retrieve data.

Related Links