Tuesday, October 03, 2006

Sparklines What??

While looking information about embedding images to display in eruby scripts I was directed to a link that showed how to use the data: URI (RFC2397). This URI schema allows inclusion of small data items as "immediate" data, as if it had been included externally. This means that we can create an IMG html tag from image data directly instead of a URL to a external file like "/path/to/file/img.png".

But what caught my attention was not the data: URI but what it was being used for. It was being used to display Sparklines?. Reading some of the online pages of the paper that describe them I found them very interesting and usefull. The link with the original description can be found here.

See all those small word-sized graphs all around? well those are sparklines and the paper defines them as Intense, Simple, Word-Sized Graphics.

How can we create such pretty little graphs? In this link Joe Gregorio explains how to generate this Sparklines in Python and display them using the data: URI for emdebbing in HTML pages.

This work by Gregorio was then adapted for Ruby by the people at RedHanded to generate BMP and PNG Sparklines. And there is already a Sparklines library for Ruby with some Rails helpers so we can create cool word-sized graphs in Rails applications.

Of course I could not resist to create some Sparklines and even now that I just installed a testing server with eruby so here is my little test taken from the RedHanded page:


<%

require 'base64'
require 'cgi'
require 'zlib'

def build_png_chunk(type,data)
to_check = type + data
return [data.length].pack("N") + to_check + [Zlib.crc32(to_check)].pack("N")
end

def build_png(image_rows)
header = [137, 80, 78, 71, 13, 10, 26, 10].pack("C*")
raw_data = image_rows.map { |row| [0] + row }.flatten.pack("C*")
ihdr_data = [image_rows.first.length,image_rows.length,8,2,0,0,0].pack("NNCCCCC")
ihdr = build_png_chunk("IHDR", ihdr_data)
idat = build_png_chunk("IDAT", Zlib::Deflate.deflate(raw_data))
iend = build_png_chunk("IEND", "")

return header + ihdr + idat + iend
end

def bumpspark( results )
white, red, grey = [0xFF,0xFF,0xFF], [0,0,0xFF], [0x99,0x99,0x99]

rows = results.inject([]) do |ary, r|
ary << [white]*15 << [white]*15
ary.last[r/9,4] = [(r > 50 and red or grey)]*4
ary
end.transpose

return build_png(rows)
end

def build_data_url(type,data)
data = Base64.encode64(data).delete("\n")
return "data:#{type};base64,#{CGI.escape(data)}"
end

# Here I generate an array with 100 random elements.
data1 = []

100.times do |i|
data1[i] = rand(100)
end

%>

<img src="<%=build_data_url("image/png",bumpspark(data1))%>"></img> Random Graph



A little about data:URI

Since I was actually looking for data:URI instead of Sparklines on the first place I think I will talk a little about this URI format. Usually when we want to display a dynamically generate image in a web page we generate the image and write it somewhere in the HTTP server. Then we use the path of the saved file as the src attribute in a IMG tag to display it on the client's browser.

With the data:URI now we can put the image data directly in the src attribute of the IMG tag and it will be displayed. This way there is no need to save any file on the server that sometimes can be a tedious task.

Here is a little example:

<%

require 'RMagick'
require 'base64'
require 'cgi'

include Magick

# Function to generate the data:URI
def build_url(data)
data = Base64.encode64(data).delete("\n")
return "data:image/jpeg;base64,#{CGI.escape(data)}"
end

# Function to generate the PNG Image
def build_img
grad = GradientFill.new(0, 0, 170, 0, "#99eb24", "#3c7f05}")
green_grad = Image.new(170, 170, grad)
green_grad.format = "PNG"

gc = Draw.new
gc.annotate(green_grad, 0, 0, 0, 0, "Ruby\n+\nMagick!\n+\ndata:URI") do
gc.font = "/usr/share/fonts/ttf-bitstream-vera/Vera.ttf"
gc.gravity = CenterGravity
gc.stroke = 'none'
gc.fill = 'yellow'
gc.pointsize = 24
gc.font_weight = BoldWeight
end

green_grad.rotate!(-20)

return green_grad
end


%>

<img src="<%=build_url(build_img.to_blob)%>"></img>



The function to generate the image with rMagick was taken from here and the the function to generate the data:URI from the RedHanded example.

The data:URL has the following format:

data:[<mediatype>][;base64],<data>


Where data: is is the protocol scheme (like http: for HTTP pages or ftp: for FTP etc.). The mediatype is the mime type of the data (i.e. image/png, text/plain, etc.). The base64 indicates that the data is base64 encoded and if not present then the data is asumed to be ASCII encoded. Finally the data is the base64 encoded raw data to be send to the client. In our case the mediatype is "image/png" and the data is the image data we get from the to_blob method of the rMagick Image object.

Little Details

For the examples here to run the rMagick library must be installed on the system. I installed the rMagick library using Gentoo portage and to use it in the source code it is enough to call require RMagick and the subsequent include Magick. If rMagick is installed via gems then it may be necessary to also require the rubygems with require "rubygems" before RMagick.

Also to note is that Internet Explorer does not support the data:URI so these examples will not run on IE. I have tested these examples on Firefox only.

For more information on ImageMagick and example see Anthony's example page and the ImageMagick page. For more information on rMagick look at this link.

No comments:

Post a Comment