Sprite animations using WPF/Silverlight and JavaFX

Usually when you begin to write any sort of application that requires visual flare, animations are always on the cards.

Since WPF and JavaFX (and Apple’s Cocoa Touch) use a similar animation framework of a declarative nature, meaning, that the developer defines elements at time steps and then the framework works out the rest, it becomes “less intuitive” to find out how to render an image sprite (PNG for example) animation.

This blog post will provide some idea of how to implement a sprite based animation using WPF and JavaFX.

JavaFX

Here is what a basic sprite based animation class looks like in JavaFX

/*
 * SpriteAnimator.fx
 */

package javafxsprites;
import javafx.scene.CustomNode;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

/**
 * @author Mark Macumber
 */

public class SpriteAnimator extends CustomNode {
    var _currentFrame:Integer = 1;

    public var xPos:Number = 0.0;
    public var yPos:Number = 0.0;
    public var FPS:Integer = 30;

    var tl = Timeline {
	repeatCount: Timeline.INDEFINITE
	keyFrames : [
		KeyFrame {
			time : bind Duration.valueOf(1000/FPS)
            action: function() {
                _currentFrame++;
                if (_currentFrame > 11)
                _currentFrame = 1;
            }
		}
    ]
    };

    override protected function create () : Node {
        var img = ImageView {
            x: bind xPos,
            y: bind yPos,
            image: bind
                Image { url: "{__DIR__}sprites/{_currentFrame}.png"; }
        };
        tl.play();
        return img;
    }
}

Inside your sprites directory, just add 0.png, 1.png, etc… all the way through to the 11.png (or however many frames you have)

This is a pretty simple implementation, which essentially just updates the image property of the ImageView that is returned as part of the create() method.

Here is how you might use the class:

package javafxsprites;
import javafx.stage.Stage;
import javafx.scene.Scene;

/**
 * @author Mark Macumber
 */

Stage {
    title: "JavaFX and Sprites"
    scene: Scene {
        width: 400
        height: 250
        content: [
            SpriteAnimator{ xPos: 150, yPos: 40, FPS: 15, NumberOfFrames: 10 }
        ]
    }
}

Pretty Simple huh? :)

You might be wondering why I bothered to create a custom class called SpriteAnimator, instead of just using an ImageView which I update. The answer is really simple, “re-use and encapsulation”, you can make this class flexible, rich, and re-usable very easily.

WPF/Silverlight

The following class has been ripped from a project that I was working on, so it is quite involved and very feature rich, and hence, very large :)

Basically it allows you to create a custom UserControl which allows for sprite animations based on file-system images, in memory-byte array images or pre-built up Lists of Image classes.

The reason for this was for performance reasons, sometimes for larger animations in certain areas of the application where lots was going on, the byte[] animations performed better that the filesystem images. Also things like file size of the images, etc… all attributed to the performance improvements/decreases.

Usage

The usage of this class is simple, you create an Image on your page, then pass the Source property of that Image class into the constructor of this class. Then call start() and your image source will be updated using pretty simple dependency property techniques.

Some of the other features of this class are as follows:

  • Create a BitmapImage from a byte array (if, say, you read in an image from the file system or a stream into a byte[]) via the static CreateImageFromBytes method
  • Pause/Unpause the animation
  • Stop/Start the animation
  • Revers the animation
  • Change the framerate
  • Seek within the animation, this is useful is you have an animation that is time based

Warning:

This class was ripped and quickly cleaned up for this blog post, if there are any issues with it, they should be just minor issues, please let me know if you come across any issues and I will do my best to fix it up.

I do NOT provide any guarantees with this class, please use it at your own risk (I know, typical disclaimer)

using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Media;

namespace MarkMacumber.Helpers
{
	//Interface for any clean-ups required
	public interface ICleanupFilmstrip
	{
		void AfterCompleted();
	}

    public class FilmstripAnimator : UserControl
    {
        private enum RunMode
        {
            /// <summary>
            /// Run with pre loaded bitmap images
            /// </summary>
            Images,

            /// <summary>
            /// Run with bitmap images loaded on the fly from the file system
            /// </summary>
            FileSystem,

            /// <summary>
            /// Using byte arrays for the image source
            /// </summary>
            Bytes
        }

        private static readonly DependencyProperty CurrentFrameProperty = DependencyProperty.Register("CurrentFrame", typeof(int), typeof(FilmstripAnimator), new PropertyMetadata(0, OnCurrentFrameChanged));
        public static readonly DependencyProperty FrameRateProperty = DependencyProperty.Register("FrameRate", typeof(int), typeof(FilmstripAnimator), new PropertyMetadata(30));

        //different ways of accessing image data, file system, in memory bitmaps, in memory bytes
        private readonly List<BitmapImage> _filmstripImages;
        private readonly List<Byte[]> _filmstripBytes;
        private readonly List<string> _filmstripImagePaths;

        private Storyboard _filmstripStoryboard;

        private readonly RunMode _mode = RunMode.FileSystem;

        //view components
        public Image FilmstripImageHost;
        internal ImageBrush FilmstripImageBrushHost;

        //clean up tasks
        public ICleanUpFilmstrip CleanUp { get; set; }

        private static BitmapImage CreateImageFromBytes(byte[] filmstripBytes)
        {
            var memoryStream = new MemoryStream(filmstripBytes);

            var imageSource = new BitmapImage();
            imageSource.BeginInit();
            imageSource.CacheOption = BitmapCacheOption.None;
            imageSource.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
            imageSource.StreamSource = memoryStream;
            imageSource.EndInit();
            return imageSource;
        }

        public Filmstrip(List<Byte[]> filmstripBytes, ImageBrush imageToUpdate)
        {
            _mode = RunMode.Bytes;
            _filmstripBytes = filmstripBytes;
            FilmstripImageBrushHost = imageToUpdate;

            FilmstripImageBrushHost.ImageSource = CreateImageFromBytes(_filmstripBytes[0]);
            CreateStoryboard();
        }

        public Filmstrip(List<BitmapImage> filmstripImages, ImageBrush imageToUpdate)
        {
            _mode = RunMode.Images;
            _filmstripImages = filmstripImages;
            FilmstripImageHost = new Image { Stretch = Stretch.Fill };
            FilmstripImageBrushHost = imageToUpdate;
            FilmstripImageBrushHost.ImageSource = filmstripImages[0];

            CreateStoryboard();
        }

        public Filmstrip(List<string> filmstripImagePaths)
        {
            FilmstripImageHost = new Image { Stretch = Stretch.Fill };
            _filmstripImagePaths = filmstripImagePaths;
            Content = FilmstripImageHost;
            CreateStoryboard();
        }

        public Filmstrip(List<BitmapImage> filmstripImages)
        {
            _mode = RunMode.Images;

            FilmstripImageHost = new Image { Stretch = Stretch.Fill };
            _filmstripImages = filmstripImages;
            Content = FilmstripImageHost;
            CreateStoryboard();
        }

        private static void OnCurrentFrameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var filmstrip = (Filmstrip)d;
            if (filmstrip.FrameCount <= 0) return;

            //If we are using an ImageBrush then use the in memory bitmaps
            if (filmstrip.FilmstripImageBrushHost != null)
            {
                if (filmstrip._mode == RunMode.Images)
                {
                    filmstrip.FilmstripImageBrushHost.ImageSource = null;
                    BitmapImage newSource = filmstrip._filmstripImages[filmstrip.CurrentFrame];
                    filmstrip.FilmstripImageBrushHost.ImageSource = newSource;
                }
                else
                {
                    filmstrip.FilmstripImageBrushHost.ImageSource = null;
                    BitmapImage newByteImage = CreateImageFromBytes(filmstrip._filmstripBytes[filmstrip.CurrentFrame]);
                    filmstrip.FilmstripImageBrushHost.ImageSource = newByteImage;
                }
            }
            //If we are NOT using the ImageBrush but we still want to use images from memory
            else if (filmstrip._filmstripImages != null)
            {
                filmstrip.FilmstripImageHost.Source = null;
                BitmapImage newImageSource = filmstrip._filmstripImages[filmstrip.CurrentFrame];
                filmstrip.FilmstripImageHost.Source = newImageSource;
            }

            //Otherwise, use the images from the filesystem, which typically are for larger animations
            else
            {
                BitmapImage source = new BitmapImage(new Uri(filmstrip._filmstripImagePaths[filmstrip.CurrentFrame]));
                RenderOptions.SetBitmapScalingMode(source, BitmapScalingMode.LowQuality);
                filmstrip.FilmstripImageHost.Source = null;
                filmstrip.FilmstripImageHost.Source = source;
            }
        }

        private void CreateStoryboard()
        {
            CreateStoryboard(0);
        }

        private void CreateStoryboard(int from)
        {
            int? targetTo;
            Duration dur;
            if (_mode == RunMode.FileSystem)
            {
                targetTo = new int?((this._filmstripImagePaths.Count == 0) ? 0 : (this._filmstripImagePaths.Count - 1));
                dur = TimeSpan.FromSeconds(_filmstripImagePaths.Count);
            }
            else if (_mode == RunMode.Images)
            {
                targetTo = new int?((_filmstripImages.Count == 0) ? 0 : (_filmstripImages.Count - 1));
                dur = TimeSpan.FromSeconds(_filmstripImages.Count);
            }
            else
            {
                targetTo = new int?((_filmstripBytes.Count == 0) ? 0 : (_filmstripBytes.Count - 1));
                dur = TimeSpan.FromSeconds(_filmstripBytes.Count);
            }

            var element = new Int32Animation();
            element.From = from;
            element.To = targetTo;
            element.Duration = dur;
            Storyboard.SetTargetProperty(element, new PropertyPath(CurrentFrameProperty));
            _filmstripStoryboard = new Storyboard {SpeedRatio = this.FrameRate};
            _filmstripStoryboard.Children.Add(element);
            _filmstripStoryboard.Completed += FilmstripStoryboard_Completed;
        }

        private void FilmstripStoryboard_Completed(object sender, EventArgs e)
        {
            if (CleanUp != null)
                CleanUp.AfterCompleted();
            _filmstripStoryboard.Completed -= FilmstripStoryboard_Completed;
        }

        public void Pause()
        {
            _filmstripStoryboard.Pause(this);
        }

        public void Unpause()
        {
            _filmstripStoryboard.Resume(this);
        }

        public void Stop()
        {
            if (_filmstripStoryboard != null)
                _filmstripStoryboard.Stop(this);
        }

        public void Start()
        {
            Start(TimeSpan.Zero, true);
        }

        public void Start(TimeSpan beginTime, bool loop)
        {
            Start(beginTime, loop, false);
        }

        public void Start(TimeSpan beginTime, bool loop, bool reverse)
        {
            if (_filmstripStoryboard == null) return;

            if (beginTime != TimeSpan.Zero) _filmstripStoryboard.Children[0].BeginTime = new TimeSpan?(beginTime);
            if (loop) _filmstripStoryboard.Children[0].RepeatBehavior = RepeatBehavior.Forever;
            _filmstripStoryboard.Children[0].AutoReverse = reverse;
            _filmstripStoryboard.Begin(this, true);
        }

        /// <summary>
        /// From the current position, reverse the animation
        /// </summary>
        public void Reverse(int durationOfReverse)
        {
            _filmstripStoryboard.Pause(this);

            var element = _filmstripStoryboard.Children[0] as Int32Animation;

            element.From = CurrentFrame;
            element.To = 0;
            element.Duration = new Duration(new TimeSpan(0,0,0,0, durationOfReverse));
            Storyboard.SetTargetProperty(element, new PropertyPath(CurrentFrameProperty));

            //speed up reversal
            _filmstripStoryboard.SpeedRatio = 1;
            _filmstripStoryboard.Begin(this, true);
        }

        public void MoveFilmstripForward(int secondsToMoveForward)
        {
            var hasValue = _filmstripStoryboard.GetCurrentTime(this).HasValue;
            if (!hasValue) return;

            TimeSpan currentTime = (TimeSpan) _filmstripStoryboard.GetCurrentTime(this);
            TimeSpan timeToMoveForward = new TimeSpan(0, 0, secondsToMoveForward * this.FrameRate);
            TimeSpan newFilmstripLocation = currentTime.Add(timeToMoveForward);

            _filmstripStoryboard.Seek(this, newFilmstripLocation, TimeSeekOrigin.BeginTime);
        }

        public void MoveFilmstripBackward(int secondsToMoveBackward)
        {
            var hasValue = _filmstripStoryboard.GetCurrentTime(this).HasValue;
            if (!hasValue) return;
            TimeSpan currentTime = (TimeSpan) _filmstripStoryboard.GetCurrentTime(this);
            TimeSpan timeToMoveBackward = new TimeSpan(0, 0, secondsToMoveBackward * this.FrameRate);
            TimeSpan newFilmstripLocation = currentTime.Subtract(timeToMoveBackward);

            if (newFilmstripLocation.TotalMilliseconds < 1)
                newFilmstripLocation = new TimeSpan(0, 0, 0);

            this._filmstripStoryboard.Seek(this, newFilmstripLocation, TimeSeekOrigin.BeginTime);
        }

        public void AdjustFrameRate(int frameRate)
        {
            _filmstripStoryboard.SpeedRatio = frameRate;
            FrameRate = frameRate;
        }

        private int CurrentFrame
        {
            get
            {
                return (int)base.GetValue(CurrentFrameProperty);
            }
        }

        public int FrameCount
        {
            get
            {
                if (_mode == RunMode.FileSystem)
                    return _filmstripImagePaths.Count;
                if (_mode == RunMode.Bytes)
                    return _filmstripBytes.Count;
               return _filmstripImages.Count;
            }
        }

        public int FrameRate
        {
            get
            {
                return (int)base.GetValue(FrameRateProperty);
            }
            set
            {
                base.SetValue(FrameRateProperty, value);
            }
        }
    }
}

I have only just been learning about Apples world and the Cocoa-Touch framework, so when I know more about it I will be sure to post about it.

As always, please give feedback if you enjoyed this blog post, found some errors, or if you would like more information in general.

Thanks
– Macca (Mark)

Migration to Wordpress

Hi everybody!

I know I know… I’ve been very slack getting together more posts, but honestly I have been very busy working with various different technologies. All of these learning’s have given me great insight into many of the different worlds that exist in the UI space of software development.

I hope to share many different ideas, thoughts, techniques and more over the next few months, so please watch this space.

I was lucky enough to work as the lead developer on a Microsoft Surface application that was used at the Australian Open 2010, this project was incredibly challenging and full of surprises, I learned a lot!

Also, you may or may not have noticed, that I moved to Wordpress. I need to give props to this blog that helped me migrate over from Blogger to Wordpress:

http://www.mamablogga.com/the-ultimate-guide-to-migrating-from-blogger-to-wordpress/

Thanks mamablogga

Anyways, I hope to get this blog up and running again very soon, so please watch this space.

Cheers,

Mark

Switch worlds from JavaFX to WPF and the designer workflow

As some of you may be aware, for a long time I have been eagerly following JavaFX. Playing with it, getting involved in the entire JavaFX world.

It was easy for me to get into it because at work I was also heavily involved in Java work. I was writing a Gwt application that used a Java back end and was working in an Ubuntu development environment, not to mention surrounded by other Java enthusiasts.

Over the last few years, I had really become more and more aware of front end technologies and the emergence of new UI/UX/Front end frameworks, such as Silverlight, Flex, JavaFX, JQuery, Dojo, etc… Seeing a pattern, I took it upon myself to ensure that I understood how each and every one worked (at least to some degree) and went from there.

Originally having a .Net background, I expressed my interest “up the chain” for some more .Net project work and was given the opportunity to work on an exciting new .Net WPF (Windows Presentation Foundation) project.

Very quickly it was detailed that UI/UX was of high importance and the skill sets that our team had, while very strong, lacked in some of the finer points of UI (at least from a creative point of view).

Over the last few weeks I have been really beginning to understand the value of the Designer/Developer workflow. Microsoft have done a wonderful job with the Expression Suite of tools that tie in Visual Studio and WPF apps together.

The one thing that I now know, is that in order for JavaFX to succeed, (which it has the potential for) Sun MUST create a powerful and complete work flow for designer/developers to with.

I know all about the plugins for JavaFX and Photoshop, but that’s not enough. What it really needs is the full designer, which must incorporate all attributes of JavaFX such as binding, styling, time lines (at a creative level) as well as the technical side.

I look forward to having a play with the designer that is currently under development and seeing where that is (which is looking freaking sweet).


(thanks to selmic.com for the pics)

At the moment however, it really feels like JavaFX has a lot of catching up to do, and with the release of WPF 4 just around the corner (beta 2 just being released), the benchmark is set high.

Final thought
The next year for JavaFX will make it or break it. The adoption rate must pick up and a complete stack, from development to creative must exist. I do, believe that it can be done, and I will be along for the ride pushing JavaFX where I can, especially to my colleagues and fellow onliners…

JavaFX and RSS cont…

Just a quick note to say that my RSS Feed Example as fully documented here, is now posted as a full loaded sample on the very popular JFXtras.org website.

The link can be found here.


(obligatory screen shot)

And to launch it right NOW click here.

Enjoy!

Mark

Posted in Examples JFXtras JavaFX RSS by Macca. No Comments

JavaFX and RSS

JavaFX is known to be a rich media platform, for use in creating a rich interface for the end user, but when you think of rich media you tend to think of streaming video, animations, graphics etc… what you tend to quickly forget about is RSS (and Atom for that matter).

It has become such a standard detail of nearly every piece of the web these days, that it has been taken for granted, which is why I thought I would post a quick blog entry about the RSS support in JavaFX.

Firstly, RSS is treated as a first citizen of information for use in any RIA (Rich Internet Application, what ever that means these days!). You get a massive amount of control and help while building any interaction with RSS.

The javafx.data.feed.rss package contains all of the classes that you need, but it ultimately boils down to the RssTask class. This class has hooks for the parsing of the feed, the time interval of the reloading of the feed, exception handling, etc…

There are also classes that represent the different items of the feed too, such as the Item class, which represents each individual RSS item (containing the properties such as title, link, comments, description, etc…).

Here is a quick code snippet that gets the RSS feed and fires a function when it parses each RSS item:

var rssTask = RssTask {interval: 30slocation: "http://feeds.feedburner.com/javaposse"onStart: function() {   println("loading RSS feed...");}onItem: function(rssItem:Item) {   //process each item}onException:function(e:Exception){   println("There was an error: {e.getMessage()}");}onDone: function(){ println("done reading RSS"); }};rssTask.start();

This RssTask will fetch the RSS feed for the popular JavaPosse podcast (which I highly recommend by the way, easily the best podcast around).

So I decided to whip up a little desktop app that parses the feed and will just display a simple little list of items using the title, etc…

package rssfeedexample;

import javafx.scene.Scene;import javafx.data.feed.rss.RssTask;import javafx.data.feed.rss.Item;import javafx.stage.Stage;import java.lang.Exception;import javafx.scene.Group;import javafx.scene.image.Image;import javafx.scene.image.ImageView;import javafx.scene.shape.Rectangle;import javafx.scene.paint.Color;import javafx.scene.CustomNode;import javafx.scene.Node;import javafx.scene.text.Font;import javafx.scene.text.Text;import javafx.scene.control.ScrollBar;import javafx.scene.control.Hyperlink;import javafx.stage.Alert;

/*** @author Mark Macumber*/

var theRssItems:RssRectangleItem[] = [];

var rssTask = RssTask {interval: 30slocation: "http://feeds.feedburner.com/javaposse"onStart: function() {    println("loading RSS feed...");}onItem: function(rssItem:Item) {    insert RssRectangleItem{title: rssItem.title; linkAddress: rssItem.link} into theRssItems;}onException:function(e:Exception){    println("There was an error: {e.getMessage()}");}onDone: function(){ println("done reading RSS"); scrollBar.disable = false; }};rssTask.start();

var scrollBar : ScrollBar = ScrollBar {translateX: bind (435 - scrollBar.width)translateY: 0vertical: trueheight: 415blockIncrement: 415unitIncrement: 30min: 0max: 415focusTraversable: falseblocksMouse: truedisable: true};

class RssRectangleItem extends CustomNode{public var title:String;public var linkAddress:String;var cont:Rectangle;var i = sizeof theRssItems;override function create():Node {    cont = Rectangle {        width: 250        height: 60        stroke: Color.BLACK        fill: Color.WHITE        arcHeight: 12        arcWidth: 12        x: 55        y: bind (i * 65) - (scrollBar.value);    };

    var img = ImageView {        x: bind cont.x + 5        y: bind cont.y + 15        image:            Image {                url:"{__DIR__}feedIcon.png"            }    };

    var titleText = Text {        font : Font { size: 10 }        x: bind img.x + 45,        y: bind img.y        content: bind title        wrappingWidth: 200    };

    var link = Hyperlink{        text: "[link...]";        layoutX: bind titleText.x;        layoutY: bind titleText.y + 20;        action: function():Void{            Alert.inform("Link Alert", "take me to the link...{linkAddress}");        }    };

    Group {        content: [cont, img, titleText, link]    }}}

Stage {title: "JavaFX RSS Example"width: 450height: 450scene: Scene {    content: bind [ theRssItems,scrollBar ]}}

The only thing you will need to do to get this code to run, is to get the RSS image (posted below)

Here is a quick screen shot too:

And this is the RSS image that I have used:

It is also at this point that I would like to make sure I don’t forget about the Atom support in JavaFX, there is pretty much the same amount of support for easily getting Atom working quickly within JavaFX.

I will hopefully be uploading this example to the JFXtras website so that people can download the code, and run an example through JNLP, so watch this space!

If you have any issues running the code, please let me know.

Until next time,

Good Coding!

Posted in JavaFX RSS by Macca. 2 Comments

Updated SpaceMissionFX to JavaFX 1.2

“Some” of you may know of the SpaceMissionFX game that I wrote a while back to get familiar with JavaFX.

Well that was written in JavaFX 1.1 and I have decided that it is time to update it and make it compatible with 1.2 (also, I have not posted in a while!)…

In case you were wondering, there weren’t many API changes required, which was a great relief!

So here is a few more screens and the link to the jfxtras.org samples site:


(in game action)


(destroying an asteroid)


(death by asteroid)

I have also uploaded the project, including the JNLP, netbeans project and the source code to the jfxtras.org website.

You can find it here

As always, I love feedback, so if you like what you see, or want to know more, please let me know!

Until next time…

JavaFX Graphing and Charting with Hudson pt 2

In my last post on building some bar charts with the data gathered from the Hudson REST api, http://markmacumber.blogspot.com/2009/07/javafx-graphing-and-charting-with.html.

In this article I would like to extend the basic functionality of that graph and also highlight broken builds with RED bars.

The basic idea behind this small enhancement, is to structure the XML parsing in a more efficient manor and clean up some of the code.

I also wanted to add a floating dialog over each bar to show details of the build (which you could easily extend to add more details).

I will be using the EXACT same Java code to access the XML from Hudson, as posted in the previous blog entry.

Here is the source code:

package hudsongraphs;

import javafx.stage.Stage;import javafx.scene.Scene;import javafx.scene.chart.BarChart;import javafx.scene.chart.part.CategoryAxis;import javafx.scene.chart.part.NumberAxis;import javafx.scene.text.Font;import javafx.scene.paint.Color;

import javafx.scene.input.MouseEvent;import javafx.scene.shape.Rectangle;import javafx.scene.text.Text;

import hudsonremoteaccess.Hudson;

import org.dom4j.Document;import org.dom4j.tree.DefaultElement;

/*** Hudson Graphing:* Sample JavaFX application that will make a remote call to the Hudson* API's, parse the XML and generate a nice bar-graph of build information.* @author Mark Macumber*/var hudson:Hudson = new Hudson();

var xmlDoc:Document = hudson.getXMLHudsonQuery();var builds = xmlDoc.selectNodes("//build");

var numbers:String[] = [];

//the actual bar char datavar barChartData:BarChart.Data[];

var currentMouseX:Number = 0;var currentMouseY:Number = 0;

//Text that will float in the dialogvar buildText:Text = Text {font : Font { size: 12 }x: bind currentMouseX + 12,y: bind currentMouseY + 20};

//floating rectangle that will follow the mouse on each buildvar floatingRect:Rectangle = Rectangle{width: 120height: 50arcHeight: 5arcWidth: 5fill: Color.WHITESMOKEstroke: Color.BLACKx: bind currentMouseXy: bind currentMouseYvisible: false};

for (build in builds) {var idx = indexof build;var bDuration = getElement(build as DefaultElement, "duration");var durationInMilliseconds = Number.valueOf(bDuration);var durationInMinutes = ((durationInMilliseconds/1000)/60);

var bNumber = getElement(build as DefaultElement, "number");var bResult = getElement(build as DefaultElement, "result");var bDisplayName = getElement(build as DefaultElement, "fullDisplayName");

insert bNumber into numbers;

var barChart:BarChart.Data =BarChart.Data {   category: bNumber   value: durationInMinutes};

var chartNode = Rectangle{onMouseMoved: function( e: MouseEvent ):Void {   currentMouseX = e.sceneX + 15;   currentMouseY = e.sceneY + 5;   floatingRect.visible = true;   buildText.content = "{bDisplayName}\nDuration: {durationInMinutes as Integer}mins";}x: 0y: 0width: bind barChart.widthheight: bind barChart.heightfill: if (bResult == "SUCCESS") Color.GREEN else Color.RED;};

barChart.bar = chartNode;insert barChart into barChartData;}

function getElement(element:DefaultElement, xpath:String):String{(element.selectNodes(xpath).get(0) as DefaultElement).getData() as String;}

Stage {title: "Hudson Build Duration Timeline"width: 800height: 450scene: Scene {content: [BarChart {title: "Hudson Build Duration Timeline"width: 750titleFont: Font { size: 24 }categoryGap: 2categoryAxis: CategoryAxis { categories: numbers}valueAxis: NumberAxis { label: "Build Duration (minutes)" upperBound: 500 tickUnit: 100}data: [ BarChart.Series {   name: "Build #'s"   fill: Color.GREEN   data: barChartData }]},floatingRect,buildText]}}

As you can see, the code is actually pretty strait forward, and the enhancements has been mainly around how to parse the XML, and create the floating box, which, I have to admit, was a fair amount of trial an error :)

Here is a screen shot:

The full source code is available via the JFXtras project, in which this sample has been checked in for all to see and use in the “samples” project.

You will find it under the MarkMacumberFX\HudsonGraphs

There is also an article published on the JFXtras.org samples page that contains all the links you will need to run the JNLP file, etc…

I will be hoping to continually update this code and make more and more graphs, to enhance the Hudson reporting, and hopefully getting some kind of real integration with Hudson.

Here is a direct link to the JNLP file:

Enjoy
Mark!

JavaFX Graphing and Charting with Hudson

With the release of JavaFX 1.2, came a whole heap of API changes and new features.

Some of these new features include the charting API, this impressive API includes charts for Bar Graphs, Pie Charts, Bubble Charts, Lines Charts, and more. My own graphing API that I was building was solid, but did not cover as many areas as the official API.

I thought I would go through a great demo of what you can achieve with this API, in particular I want to build a small JavaFX applet, that sends a request to a Hudson server, get some data and build some graphs.

Hudson (one of the most popular Continuous Integration applications on the market), has an API built into it that allows a user to send a http request to get previous build information, as well as other remote information.

Goal

My intention here is to expose a nice way to use JavaFX, Java and the Charting API to create a practical applet that can give developers a visual overview of their build history.

Overview

This implementation actually has a very small number of steps involved:

  1. Build a small Java library that queries Hudson for XML
  2. Build a JavaFX app that uses that library to parse and build a simple Bar Graph

#1, The Java Library

This java library will make use of the popular XML library dom4J to parse the Hudson data from a simple http request.

The hudson server:

I want to make this demonstration accessible to anyone that wants to run it, so I needed to find a public Hudson server, I did a quick Google search and came across (via the Huson wiki page) the publicly accessible deadlock netbeans Hudson server.

FYI, to quickly summarize, you send a http request to the hudson server like http://deadlock.netbeans.org/hudson/api/xml and you get XML back… (you can also get JSON on a per build basic or for the whole project!).

The java code for the request is as follows:

package hudsonremoteaccess;

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.URL;import java.net.URLConnection;import java.util.logging.Level;import java.util.logging.Logger;import javax.xml.transform.Transformer;import javax.xml.transform.TransformerException;import javax.xml.transform.TransformerFactory;import javax.xml.transform.stream.StreamSource;import org.dom4j.Document;import org.dom4j.DocumentHelper;import org.dom4j.io.DocumentResult;import org.dom4j.io.DocumentSource;

/*** @author Mark Macumber*/public class Hudson {

public Document getXMLHudsonQuery() { try {     String webUrl = "http://deadlock.netbeans.org/hudson/job/FindBugs/api/xml?depth=1&wrapper=builds&xpath=//build&exclude=//build/artifact&exclude=//build/culprit&exclude=//build/changeSet";     Document xmlDocument = DocumentHelper.parseText(getDataFromUrl(webUrl));     xmlDocument = applyXsltStylesheet(xmlDocument);     return xmlDocument; } catch (Exception ex) {     System.err.println(ex.getMessage()); } return null;}

private Document applyXsltStylesheet(Document document){ try {     // load the transformer using JAXP     TransformerFactory factory = TransformerFactory.newInstance();

     InputStream is = this.getClass().getResourceAsStream("/hudsonremoteaccess/resources/styleSheet.xsl");     Transformer transformer = factory.newTransformer(new StreamSource(is));

     // now lets style the given document     DocumentSource source = new DocumentSource( document );     DocumentResult result = new DocumentResult();     transformer.transform( source, result );

     return result.getDocument(); } catch (TransformerException ex) {     Logger.getLogger(Hudson.class.getName()).log(Level.SEVERE, null, ex); } return null;}

private String getDataFromUrl(String webUrl) { try {     URL url = new URL(webUrl);     URLConnection connection = url.openConnection();     StringBuilder builder = extractDataFromStream(connection);     String response = builder.toString();     return response; } catch (Exception e) {     System.err.println("Something went wrong...");     e.printStackTrace(); } return null;}

private StringBuilder extractDataFromStream(URLConnection connection) throws IOException { String line; StringBuilder builder = new StringBuilder(); InputStream is = connection.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); while ((line = reader.readLine()) != null) {     builder.append(line); } return builder;}}

FYI, you will need to reference the jaxen and dom4j JAR files to run the XML parser and query the results…

#2, The JavaFX code…

This example will show the build time for the Hudson project, many many other graphs are possible, but for the purposes of this demo, I will just be doing one…

As you can see below, the code (with a reference to the Java library above) is very simple.

package hudsongraphs;

import javafx.stage.Stage;import javafx.scene.Scene;import hudsonremoteaccess.Hudson;import org.dom4j.Document;import org.dom4j.Element;import javafx.scene.chart.BarChart;import javafx.scene.chart.part.CategoryAxis;import javafx.scene.chart.part.NumberAxis;import javafx.scene.text.Font;import javafx.scene.paint.Color;

/*** @author Mark Macumber*/var hudson:Hudson = new Hudson();var xmlDoc:Document = hudson.getXMLHudsonQuery();var buildNumbers = xmlDoc.selectNodes("//number");var buildDurations = xmlDoc.selectNodes("//duration");var numbers:String[] = [];var durations:Number[] = [];

for (bNumber in buildNumbers) {insert (bNumber as Element).getData() as String into numbers;var bDuration = buildDurations.get(indexof bNumber);var durationInMilliseconds = Number.valueOf((bDuration as Element).getData() as String);var durationInMinutes = ((durationInMilliseconds/1000)/60);insert durationInMinutes into durations;}

Stage {title: "Hudson Build Duration Timeline"width: 800height: 450scene: Scene {content: [ BarChart {   title: "Hudson Build Duration Timeline"   width: 750   titleFont: Font { size: 24 }   categoryGap: 2   categoryAxis: CategoryAxis {     categories: numbers   }   valueAxis: NumberAxis {     label: "Build Duration (minutes)"     upperBound: 500     tickUnit: 100   }   data: [     BarChart.Series {       name: "Build #'s"       fill: Color.GREEN       data: for (j in [0..<sizeof durations]) {         BarChart.Data {           category: numbers[j]           value: durations[j]           fill: Color.GREEN         }       }     }   ] }]}}

Basically, you just get the XML, parse it, then build the graph pieces using the JavaFX sequences.

Simple!!!

The screen shot of what this produces is below:

Pretty huh? :)

The above could EASILY be turned into an Applet that could be actually put onto a hosted site within Hudson, just to make it more “integrated” (Netbeans has the easy option to make an Applet out of a JavaFX application).

You should have all of the data to make the application above work, but if not, please do not hesitate to contact me.

I love feedback, please, if you have any advice, comments, criticisms, improvements, etc… please drop me a line.

Cheers,
Mark

Gwt FastTree and a spinner icon

During a project that makes use of the Google Gwt Incubator project, I was using the FastTree widget pretty actively, and found that no matter how ‘fast’ the FastTree could render the results, there were just some times when the async rpc calls would take a short while to complete, and it would look like nothing was happening.

So I quickly came across the need to implement a “spinner” on the tree. Basically, what I wanted was a nice little gif animation that would replace the expander icon on the node that he user was expanding, which would allow the user to see that there is actually something happening while they wait for their results.

I was hoping that the FastTree widget would provide this for free, but alas, this is not the case. So I started investigating different ways to implement this need, and my results were actually very simple!

I started by extending the ListeningFastTreeItem class, which basically allows the developer to handle/listen to different events of the tree, like expanding, collapsing, clicking on, losing focus, etc…

public class MyTreeItem extends ListeningFastTreeItem

This then allows you to override different methods, such as beforeOpen() (as seen below) which, as described by the java doc is “Called before the tree item is opened.” and is where I chose to implement the spinner icon.

The idea behind the implementation is that I simply want to add a CSS class to the expanding node while it waits for the results from the server, which I can then use in a custom CSS file to implement the icon. Then once my call to the server is complete, I simply remove that CSS class.

@Overridepublic void beforeOpen() {this.addStyleName("loading");loadChildren(id);}

The code above simply calls a the built in “addStyleName()” method, to, surprisingly, add a style name to the current element that is expanding. Then I call a custom defined method, loadChildren() which will call the server using a standard Gwt rpc call and get the results.

My custom CSS class is implemented below and is really the key to this implementation:

.gwt-FastTreeItem .loading .open {background-image: url('treeLoaderIcon.gif');}

The 2 styles, gwt-FastTreeItem and open are Google Gwt class names, and the loading class name (as listed above) is my custom CSS class (also listed above).

The treeLoaderIcon.gif is also a custom loading icon, you can use any old icon here!

At this point in time, while the server is still loading is data, you should have a spinner working.

The next part, is to remove the spinner once the child nodes have been loading and are back on the client.

Luckily, this is simply, as stated below:

theTreeItem.removeStyleName("loading");

removeStyleName() is a built in Gwt method to remove a given style name. This line of code should be put inside your callback onSuccess() method of your Gwt rpc call.

that’s it!

If you have any questions, please don’t hesitate to ask me, leave a comment or email me!

Till next time!

Posted in Ajax CSS FastTree Gwt Incubator Java by Macca. 1 Comment

Make a rectangle grow backwards in JavaFX

I came across a small issue when I was developing some home projects that I’ve got going (namely JFXtras) and I wanted to make a rectangle “grow backwards”.

i.e. the rectangle needs to grow upwards, not downwards when increasing the “height” of a rectangle.

Actually, the solution is quite simple, all you need to do is to create a simple Timeline, that will do 2 things:

  1. Increase the height of the rectangle
  2. Decrease the y-position of the rectangle (at the same speed as the increase of the height)

thats it!

Below is a quick code sample that you can cut and paste to see it in action.

package reversi;

import javafx.animation.Interpolator;import javafx.animation.KeyFrame;import javafx.animation.Timeline;import javafx.scene.paint.Color;import javafx.scene.Scene;import javafx.scene.shape.Rectangle;import javafx.stage.Stage;

def startYPos = 250.0;def desiredHeight = 200.0;

var rectYPosition = startYPos;var rectHeight = 100.0;

var rect = Rectangle {   x: 50,   y: bind rectYPosition,   height: bind rectHeight   width: 250,   height: 100   fill: Color.BLACK}

Timeline {   keyFrames: [       KeyFrame {           time: 0s           values: [rectYPosition => startYPos tween Interpolator.EASEOUT,                    rectHeight => 0 tween Interpolator.EASEOUT]       },       KeyFrame{           time: 2s           values: [rectYPosition => startYPos - desiredHeight tween Interpolator.EASEOUT,                   rectHeight => desiredHeight tween Interpolator.EASEOUT]       }   ]}.play();

Stage {   title: "Reverse Growing"   width: 350   height: 300   scene: Scene { content: rect }}

Pretty simple really isn’t it?

If anyone has a cleaner, easier solution to this small, yet relevant problem, I would love to hear it.

Cheers till next time!
Mark