Long-time readers of this blog will remember some JavaFX examples in which I query the Freebase.com database, which holds structured information on just about any topic. Today's post uses JavaFX, with some help from the JFXtras open source project, to query the Freebase database for groups that an artist has played with, and for the artists that have played in those groups. For example, the screenshot of the BandmatesFX app below shows the results of querying this info on Eric Clapton:
To indicate which artist you'd like to query, type the Freebase ID (which as shown above is usually their Wikipedia ID with "/en/" appended to the beginning). Pressing the Enter key or clicking the Search button initiates the search, which sends a query articulated in the JSON-based Metaweb Query Language (MQL) The left column of the grid shows the groups that Clapton has played in, and the other images in each row are the artists that have played with that row's group. Hovering over an image of a band or artist causes the name (in this case Steve Winwood) to appear at the top of the app.
Clicking on an artist performs the same behavior described above on the chosen artist, resulting in the following screenshot that shows bands and artists that Winwood has played with.
Clicking Dave Mason (who was a bandmate with Steve Winwood in Traffic) as shown above will produce the bands and artists with which he has played, ad infinitum.
One of the key enablers of this application is the JSONHandler class (developed by Jim Clarke) found in the JFXtras project. Take a look at the following listing of the main script in this application to see how it is used.
/*
* BandmatesMain.fx
*
* Uses Freebase and JFXtras with JavaFX to explore connections
* between musical artists
*
* Developed by James L. Weaver to demonstrate using JavaFX and JFXtras
*/
package javafxpert;
import javafx.io.http.HttpRequest;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextBox;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.*;
import javafx.stage.Stage;
import org.jfxtras.data.pull.*;
import org.jfxtras.scene.layout.*;
/**
* Artist for which we're searching bandmates
*/
var artistToSearch:String = "/en/eric_clapton";
/**
* Hover text that contains the name of the artist or group
*/
var nameHoverText:String;
/**
* A reference to the HTTP request, for the purpose of monitoring progress
*/
var req:HttpRequest;
/**
* The root class that will hold the object graph from the JSON results
*/
var freebaseResult:FreebaseResult;
/**
* The base URL for the freebase query
*/
def freebaseURL = "http://www.freebase.com/api/service/mqlread?";
/**
* The base URL for a freebase image
*/
def freebaseImageURL = "http://img.freebase.com/api/trans/image_thumb";
/**
* Create the Freebase query and invoke the JSON handler
*/
function obtainGroupsForArtist(artistFreebaseId:String) {
artistToSearch = artistFreebaseId;
var queryUrl = "{freebaseURL}query=\{\"query\":"
" \{ "
" \"/common/topic/image\": [\{ "
" \"id\": null "
" \}], "
" \"/music/group_member/membership\": [\{ "
" \"group\": \{ "
" \"name\": null, "
" \"id\": null, "
" \"/common/topic/image\": [\{ "
" \"id\": null "
" \}], "
" \"/music/musical_group/member\": [\{ "
" \"member\": \{ "
" \"name\": null, "
" \"id\": null, "
" \"/common/topic/image\": [\{ "
" \"id\": null "
" \}] "
" \} "
" \}] "
" \} "
" \}], "
" \"id\": \"{artistFreebaseId}\", "
" \"name\": null, "
" \"type\": \"/music/artist\" "
" \} \}";
println("queryUrl:{queryUrl}");
var albumHandler:JSONHandler = JSONHandler {
rootClass: "javafxpert.FreebaseResult"
onDone: function(obj, isSequence): Void {
freebaseResult = obj as FreebaseResult;
println("# of bands:{sizeof freebaseResult.result.musicGroupMemberMembership}");
req.stop();
}
};
req = HttpRequest {
location: queryUrl
onInput: function(is: java.io.InputStream) {
albumHandler.parse(is);
}
};
req.start();
}
Stage {
title: "BandmatesFX"
scene: Scene {
width: 1000
height: 750
content: [
VBox {
layoutX: 10
layoutY: 10
spacing: 10
content: [
HBox {
spacing: 10
content: [
ImageView {
image: bind Image {
url: "{freebaseImageURL}{artistToSearch}?maxheight=80"
}
},
TextBox {
text: bind artistToSearch with inverse
columns: 20
font: Font.font("arial", 14)
action:function():Void {
obtainGroupsForArtist(artistToSearch);
}
},
Button {
text: "Search"
strong: true
action:function():Void {
obtainGroupsForArtist(artistToSearch);
}
},
ProgressIndicator {
progress: bind req.progress
},
Label {
text: bind nameHoverText
font: Font.font("arial", FontWeight.BOLD, 18)
}
]
},
Grid {
rows: bind for (grp in freebaseResult.result.musicGroupMemberMembership)
Row {
cells: [
ImageView {
image: Image {
url: "{freebaseImageURL}{grp.group.id}?maxheight=80"
}
onMouseEntered:function(me:MouseEvent):Void {
nameHoverText = grp.group.name;
}
onMouseExited:function(me:MouseEvent):Void {
nameHoverText = "";
}
},
for (mmbr in freebaseResult.result.
musicGroupMemberMembership[indexof grp].
group.musicMusicalGroupMember)
ImageView {
image: Image {
url: "{freebaseImageURL}{mmbr.member.id}?maxheight=80"
backgroundLoading: true
}
onMousePressed:function(me:MouseEvent):Void {
artistToSearch = mmbr.member.id;
obtainGroupsForArtist(mmbr.member.id);
}
onMouseEntered:function(me:MouseEvent):Void {
nameHoverText = mmbr.member.name;
}
onMouseExited:function(me:MouseEvent):Void {
nameHoverText = "";
}
}
]
}
}
]
}
]
}
}
As you can see in the listing above, we construct an MQL query that will return information about the bands, and the artists that have played in those bands. We then supply the JSONHandler with a rootClass, an instance of which will be created and populated with a graph of objects that represents the JSON stream returned from the Freebase server. JSONHandler uses JavaFX reflection to create instances of classes that correspond to each element name, shown below:
package javafxpert;
public class FreebaseResult {
public var code: String;
public var result: Result;
public var status: String;
public var transactionId: String;
}
package javafxpert;
public class Result {
public var commonTopicImage: ArtistImage[] ;
public var musicGroupMemberMembership: MusicGroupMemberMembership[];
public var id: String;
public var name: String;
public var type: String;
}
package javafxpert;
public class ArtistImage {
public var id: String;
}
package javafxpert;
public class MusicGroupMemberMembership {
public var group: Group;
}
package javafxpert;
public class Group {
public var commonTopicImage: ArtistImage[] ;
public var musicMusicalGroupMember: MusicMusicalGroupMember[];
public var name: String on replace {
name = name.replaceAll("&", "&");
};
public var id: String;
}
package javafxpert;
public class MusicMusicalGroupMember {
public var member: Member;
}
package javafxpert;
public class Member {
public var commonTopicImage: ArtistImage[] ;
public var name: String on replace {
name = name.replaceAll("&", "&");
};
public var id: String;
}
As you can see, JSONHandler makes quick work out of populating object graphs from JSON streams, doing the heavy lifting for you.
Another facility of the JFXtras project that we're using is the Grid layout container, as shown previously in the BandmatesMain.fx code listing above. While you're looking at that listing, notice how easy it is in JavaFX 1.2 to create a ProgressIndicator that reflects the progress of a task such as HttpRequest.
To get the most out of this post, please build and run this application, putting the JFXtras JAR file (that you can get from the JFXtras Core open source project) on the classpath. If you'd like to short-circuit the JavaFX learning process, click this Java Web Start link:
Also, this is the first cut of the BandmatesFX app, so please leave a comment with ideas for additional features. And if you possess the graphics design gene (as I admittedly don't), I'd be honored if you'd create a mock-up to make this app look graphically pleasing.
Regards,
Jim Weaver
Recent Comments