Look out honey cos I’m using technology

My last post gave some examples of YQL queries that could be used to access the data for the Science Museum’s Cosmic Collections competition. Looking at XML feeds is pretty dry, though, so I thought it would be fun to put together a demo which uses a YQL query to visualise the data. I’ve also been looking at Raphaël recently, a small library for working with SVG in web pages. Inspired by the Royal Observatory’s Flickr touchscreen, and knowing next to nothing about either jQuery or SVG, I wrote some quick code with jQuery and Raphaël to display photos of the Science Museum objects as a pile of polaroid photos.

Here’s the code, with some explanation in the comments that hopefully shows how to extract the Science Museum data from the query results and do something useful with it. The event handling code seems a little dodgy to me ( I really know very little about SVG or jQuery) but seems to work in Opera, Firefox and Safari. You can try it on the demo page. Click to pick up a photo and move it. Click again to drop it.

A small update: Here’s a second demo using drag-and-drop instead of mouse clicks. I’ve also added the focusable attribute to the SVG rectangles, to make them accessible from the keyboard. This only seems to work in Opera.

// URL of the environment file, which points to the URL
// of the nmsi.cosmosculture YQL table definition.
var env = 'http://eatyourgreens.org.uk/yql/nmsi.env';
// YQL query to select everything from the Cosmic Collections dataset.
// Change this query to change the objects displayed in the page.
var yql = "select * from nmsi.cosmosculture";
// Example alternate query - fetch everything linked to the Moon
// var yql = "select * from nmsi.cosmosculture where LinkedCelestialBodies.CelestialBody.CommonName = 'Moon'";

// Encode the query and env file URL in a call to the YQL web service.
// Specify JSON as the return format.
var url = 'http://query.yahooapis.com/v1/public/yql?q='+ encodeURIComponent(yql) + '&env='+encodeURIComponent(env)+'&diagnostics=false&format=json&callback=?';

// Set a canvas for Raphael to draw on.
var height = 600;
var width = 800;
var paper = Raphael('canvas', width, height);

// Set some global variables to use when we are dragging elements around the canvas.
var startx = 0;
var starty = 0;
var dragging = false;
var draggedSet = null;

// Call the YQL web service and pass the json result to a callback function
$.getJSON(url, function(json){
//  Get the array of museum objects from the query result.
	var items = json.query.results.MuseumObject;
// Loop through the items array
	$.each(items, function(i, item) {
// Ignore items which don't have a photo
			var src = item.Image.Source;
// The smallest available image size is 'Inline'.
			src = src.replace("Medium","Inline");
			src = 'http://www.sciencemuseum.org.uk'+src;
// Generate a random x,y position for the photo
 			var x = 10 + (width-110) * Math.random();
 			var y = 10 + (height-110) * Math.random();
// Generate a random angle between 350 and 10 degrees.
 			var rot = 10*Math.random();
 			if(Math.random() < 0.5) rot = 360-rot;
// Each photo is built from a set consisting of a white rectangle and the photo
 				var s = paper.set();
 				s.push(paper.rect(x,y,110,140).attr('fill','white'),paper.image(src, x+5, y+5, 100, 100));
// Rotate the set by our random angle
// When the set is clicked, if already dragging, drop the photo.
// Otherwise, bring to the front and store the mouse
// coordinates for future use.
 				s.click(function(e) {
 					if (dragging) {
 					}else {
 						dragging = true;
 						startx = e.clientX;
 						starty = e.clientY;
 						draggedSet = s;
// Listen for mouse movement on the document.
// If dragging, move the dragged set to the
// x,y coordinates of the mouse.
// Store the current coordinates for the start
// of the next move.
 				document.onmousemove = function(e){
 					if(dragging) {
 						dx = e.clientX - startx;
 						dy = e.clientY - starty;

 						for (var j=0; j < 2; j++) {
 							var node = draggedSet[j];
 							node.attr({x: node.attr('x')+dx, y: node.attr('y')+dy});

 						startx = e.clientX;
 						starty = e.clientY;