PullMonkey Blog


06 Jan

POST OFC Graph as Image


I was asked recently (well sort of) to give an example of saving an image to the server. If you look at teethgrinder's example for this, you will see that he has made available an external interface to do just that - POST your graph as png raw data to your server for storage. This has many benefits such as saving the image for use in a PDF report or for printing, since we know at times it is a bit troublesome to print the embedded flash object.

I think the main problem people are having with this is the receiving of the image data post - see the upload_image method below. Also, teethgrinder's example never really says where to make the post_image() call. So I touch on both in the code below.

Here is an example of the png that is saved when I did this for the chart in the previous example:

OFC Saved Image


Well, let's just get right in to the code.

The controller contains the same code as my last post with only a few minor changes to the index method and the addition of the upload_image method.
In the controller, I have this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TestItController < ApplicationController
  def index
    # note the user of open_flash_chart_object_from_hash instead of just open_flash_chart_object
    # this allows you to pass in the id of the div you want the the chart to be in
    # this is useful for when we need to findSWF by this id
    @graph = open_flash_chart_object_from_hash("/test_it/chart", :div_name => "my_chart")
  end

  # added to recieve the post data for the OFC png image of the OFC graph
  def upload_image
    name = "tmp_image.png" || params[:name]
    # the save_image method that is provided by the OFC swf file sends raw post data, so get to it like this
    data = request.raw_post
    File.open("#{RAILS_ROOT}/tmp/#{name}", "wb") { |f| f.write(data) } if data
    render :nothing => true
  end

  def chart
    # same code from here - http://pullmonkey.com/2010/01/05/open-flash-chart-ii-x-axis-date-and-time/ 
    ...
  end
end




So just note the use of open_flash_chart_object_from_hash() in the index method, this way we can pass in the id of the div.

In the view, I have this:

1
2
3
4
5
<%= javascript_include_tag 'swfobject.js' %>
<%= @graph %>
<%= save_as_image("http://localhost:3000/test_it/upload_image?name=tmp.png", :id => "my_chart") %>
<br/>
<%= button_to_function "Save Image", "post_image()" %>



Really the only difference from what we would normally have in our view is that I am using the save image setup method that was added to the open flash chart ruby on rails plugin in the last couple hours (as of this post). The save_image method takes some arguments, mainly the url to post the image data to and the id of the chart we setup in the controller.



14 Sep

THINning it out


Been having problems with swap space and memory on my slicehost servers.  And it is all apache's and mongrel's fault.  That used to be the cool combination and now it is an ugly, sluggish beast.  Just recently, I switched to nginx (to replace apache) and thin (to replace mongrel).  So far so good, major speed improvements and definitely memory consumption improvements.

I started out by switching everything over the nginx while keeping the mongrels alive, that was actually pretty easy.  Information was available everywhere.

Thinning everything via capistrano took a while, that wasn't as well documented.  Thin was documented, capistrano was documented, but easy solutions as to how to combine the two were difficult to find.

Here's the solution I was able to come up with -

Capistrano

My config for using mongrel used to look something like this -

set :stages, %w(staging production)
set :default_stage, "production"

require "capistrano/ext/multistage"
require "mongrel_cluster/recipes"

set :application, "myapplication.com"
set :user, "appuser"set :repository"http://svn.myapplication.com/myapp/trunk"
set :deploy_to, "/var/www/#{application}"

role :app, application
role :web, application
role :db,  application, :primary => true

set :runner, user
set :keep_releases, 3
set(:mongrel_conf) { "#{current_path}/config/mongrel_cluster.yml" }

deploy.task :after_update_code, :roles => [:web] do
desc "Copying the right mongrel cluster config for the current stage environment."
run "cp -f #{release_path}/config/mongrel_#{stage}.yml #{release_path}/config/mongrel_cluster.yml"
end

... <other things like symlinks>

Now that we are moving from mongrel to thin, no need for two lines in particular, one being the line that requires mongrel_cluster recipes and the other that sets the mongrel_cluster yaml config path.  A third line changes from mongrel_cluster.yml to thin_cluster.yml.  You get something like this:

set :stages, %w(staging production)
set :default_stage, "production"

require "capistrano/ext/multistage"

set :application, "myapplication.com"
set :user, "appuser"

set :repository"http://svn.myapplication.com/myapp/trunk"
set :deploy_to, "/var/www/#{application}"

role :app, application
role :web, application
role :db,  application, :primary => true

set :runner, user
set :keep_releases, 3

deploy.task :after_update_code, :roles => [:web] do
desc "Copying the right mongrel cluster config for the current stage environment."
run "cp -f #{release_path}/config/thin_#{stage}.yml #{release_path}/config/thin_cluster.yml"
end
... <other things like symlinks>

Now we need to implement what mongrel recipes was doing for us, start, stop and restart but in terms of thin (added this to the bottom of my deploy.rb):

namespace :deploy do
desc "Restart the Thin processes on the app server."
task :restart do
run "thin restart -C #{release_path}/config/thin_cluster.yml"
end
desc "Start the Thin processes on the app server."
task :start do
run "thin start -C #{release_path}/config/thin_cluster.yml"
end
desc "Stop the Thin processes on the app server."
task :stop do
run "thin stop -C #{release_path}/config/thin_cluster.yml"
end
end

Here's what my thin_cluster.yml looks like:

---
log: log/thin.log
address: 127.0.0.1
port: 9000
chdir: /var/www/myapp.com/current
environment: production
pid: tmp/pids/thin.pid
user: www-user
group: www-data
servers: 3

That's it and it has worked out nicely so far.


30 Apr

Open Flash Chart II - fully automated


Just as an attention grabber - we are going after this example in this article:

Keeping up

Ok, seeing that the php versions of open flash chart and open flash chart swf files continually change along with with the API (not saying this is a bad thing), I wanted to come up with an even more abstract solution. The goal is to not have to worry when the swf file is released with the latest set of graphs or changes its API. I simply don't want to worry about this method or that method, or this class or that class.

Feedback

This article will sort of act as a tutorial for those interested in metaprogramming and as a set of instructions for those looking to experiment with the latest version of the OFC II Rails Plugin that I am currently toying with. I would like to hear feedback, but just remember that phase 1 of this release will be very basic, meaning none of the ajaxy stuff. It will come, just not yet.

Let's see what we can get away with

I am already using method_missing() for pretty much everything in the OFC II Rails Plugin that is being used now. But every time new classes are added, I have to sit down and basically convert the php class to ruby - just plain tedious, not really what I had planned when I started all this. Ok, so method_missing() was great, but let me introduce (or possibly reintroduce) you to const_missing(), basically method_missing() but instead of methods, we can create classes or modules or other objects on the fly. This will definitely help when the php version gets a new class. Instead of getting hounded to update the rails version to be 100% like the php version, everything will just work, no updates to code required. Well, we hope ! So check this out:



Here is what we did with method_missing():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

module OFC
  class Base
    def method_missing(method_name, *args, &blk)
      case method_name.to_s
      when /(.*)=/   # i.e., if it is something x_legend=
        # if the user wants to set an instance variable then let them
        # the other args (args[0]) are ignored since it is a set method
        self.instance_variable_set("@#{$1}", args[0])
      when /^set_(.*)/
        # backwards compatible ... the user can still use the same set_y_legend methods if they want
        self.instance_variable_set("@#{$1}", args[0])
      else
          if inst = self.instance_variable_get("@#{method_name}")
            inst
          else
            # if the method/attribute is missing and it is not a set method then hmmmm better let the user know
            super
          end
      end
    end
  end
end

This just basically allows me to do this:

1
2
3
4
5
6
7
8
9
10
11
12
13

  class Foo < OFC::Base
  end

  foo = Foo.new

  foo.some_random_attribute = "Hello"  #=> "Hello"
  foo.some_random_attribute  #=> "Hello"
  foo.some_random_undefined_attribute  #=> Method Missing error (calls super)

  # too be like php, for easier conversion
  foo.set_some_random_attribute("Good Bye")  #=> "Good Bye"
  foo.some_random_attribute  #=> "Good Bye"

Along the same lines, I have created an initialize method that takes any argument hash of variable/value pairs and calls variable=() which is handled by method missing as we saw above:

1
2
3
4
5
6
7
8

  class Foo < OFC::Base
  end

  foo = Foo.new(:x_axis => 5, :min => 10, :max => 90, :steps => 5, :elements => ["one", "two"])
  
  foo.x_axis #=> 5
  foo.min #=> 10

Ok, so on to const_missing() and what we can do with that:

1
2
3
4
5
6

  def OFC.const_missing(const)
    klass = Class.new OFC::Base
    Object.const_set const, klass
    return klass
  end

This says that any undefined (missing) constant of OFC should be defined as a new class that inherits from OFC::Base.

So when we say OFC::Foo, that has not been defined, so we will get back class OFC::Foo < OFC::Base;end; which will give us the initialize() method and method_missing() method from above. Let's see how this works:

1
2
3
4
5
6
7
8
9
10
11

  line = OFC::Line.new(:values => [1,2,3,nil,nil,5,6,7])
  line.values #=> [1,2,3,nil,nil,5,6,7]
  line.some_random_variable = "Hello" #=> "Hello"
  line.some_random_variable #=> "Hello"

  stacked_bar_chart = OFC::BarStack.new
  stacked_bar_chart.values = []
  stacked_bar_chart.values << [2,3,4]
  stacked_bar_chart.values << [5, {"val" => 5, "colour" => "#ff0000"}]
  stacked_bar_chart.keys = [{ "colour" => "#C4D318", "text" => "Kiting", "font-size" => 13 } ...]

So it all sort of came together right there. I've shown you all the code that comes with the Rails Open Flash Chart plugin now. No more definining idividual classes, no more trying to keep up with the never ending php version, and no more late nights converting php to ruby (!). About dang time.



Ok, but this is just the beginning, nothing has been set in stone, so like I said, give me your feedback, what works for you and what does not. And, hopefully, I will have solutions for you or you for me.


Example with new version (test version)

I am using rails 2.3.2, but I don't think it will matter what version you are using.

Create your new rails project

1
2
3
4
5

# create a new rails project 
> pullmonkey$ rails testing_it
#<Bunch of stuff is created ....>
> pullmonkey$ cd testing_it/

Install the plugin from the test branch

Note the -r test in this next step. The new version (test version) I am playing with is under the test branch and -r says what branch to pull from.

Also, you can use git:// instead of http:// below, but depending on your firewall restrictions http:// will probably work out best for you.

1
2
3

> pullmonkey$ ./script/plugin install http://github.com/pullmonkey/open_flash_chart.git -r test
# <Bunch more stuff ...>

Create a controller to play in

1
2
3

> pullmonkey$ ./script/generate controller test_it
# <And more stuff >

Get our assets

1
2
3
4
5
6
7

# first we will get swfobject.js
> pullmonkey$ cp vendor/plugins/open_flash_chart/assets/javascripts/swfobject.js public/javascripts/
# next the open flash chart swf (GET whatever is the latest version), right now that is here: http://teethgrinder.co.uk/open-flash-chart-2/open-flash-chart.swf
> pullmonkey$ cd public/
> pullmonkey$ wget http://teethgrinder.co.uk/open-flash-chart-2/open-flash-chart.swf
> pullmonkey$ cd ..

Edit our controller

Notice here that I just include one of the many examples from the plugin's examples directory. Definitely more to follow.

One thing you will notice about the examples, is that the php code is in the comments, so you can see how I would convert from the php examples to ruby. Please feel free to add your own examples, just fork the project.

1
2
3
4
5
6
7
8
9
10

> pullmonkey$ vi app/controllers/test_it_controller.rb
# mine looks like this:
class TestItController < ApplicationController
  include OFC::Examples::AreaHollow

  def index
    @graph = open_flash_chart_object(600,300, "/test_it/area_hollow")
  end
end

Edit our view

1
2
3
4
5

> pullmonkey$ vi app/views/test_it/index.html.erb
# mine looks like this:
<%= javascript_include_tag 'swfobject' %>
<%= @graph %>

Start 'er up

1
2
3
4
5

> pullmonkey$ ./script/server

# browse to the test_it index
http://localhost:3000/test_it

Our example


30 Apr

Spreadsheet Gem - data may have been lost


I've been using the spreadsheet gem lately for a couple projects I am working on to modify existing spreadsheets. I have quite often stumbled upon this error when opening modified spreadsheets in excel:


File error: data may have been lost

Like most microsoft errors, it was useless and the spreadsheet came up just fine. But that error was just so annoying, other spreadsheet applications (open office, excel on mac) opened without any problems. So after quite a bit of hacking and digging around, I finally tried setting the encoding, which defaults to UTF-8. Well it just so happens that the spreadsheet being modified was encoded with UTF-16LE.



So part one of my solution became this:

1
2

Spreadsheet.client_encoding = 'UTF-16LE'

Then doing a little more digging I decided that this would be a better long-term solution:

1
2
3

book = Spreadsheet.open spreadsheet_file
Spreadsheet.client_encoding = book.encoding

Well, hopefully it wasn't just me and someone will be able to save a bit of time with this.



17 Apr

Can you read this?


Got an email today, I have seen it before and I am sure it has been going around for years. This time, I thought that I would do an exercise and create a plugin that duplicates what I found in this email. See for yourself.



Here is the email I got:






What I gathered was that the only important letters are the first and last letter of each word, those have to be in the right order. So the rest of the letters can be in any random order. That is what I did - I created a plugin and put it out on github. You can install it like this:

./script/plugin install http://github.com/pullmonkey/can_you_read_this.git

And use it like this:

#in your views <%= can_you_read_this("hello, can you read this?") %>

Have fun.


11 Feb

Blog plugin tutorial for Ruby on Rails



Update: Bloggity does not require the Engines plugin to run if you are using Rails 2.3 or above (where the Engines plugin is baked in). -- Noted below by Bill.

Update: Added the plugin to github - simple_blog. It is not production ready or really all that usable quite yet.



Ok, so this a rant and I am sorry for that - but as simple as it is, I have been looking for a blog plugin lately. The problem with the plugins I find is that I don't want to have to deal with the engines plugin or have the controllers, models, views, etc ... all extracted into my applications code. I want it all external (hence a plugin) but let it be minimally configurable.



So in my recent search for a blog plugin for rails, I came across two that look very useful, but each with their flaws:

1) bloget - Everything is extracted to my code space. Why? Yes, I realize that it is most likely because I will want to override things, but get out of my space and keep to yourself! :)

Provide me a way to override things that I would need to (there really shouldn't be too many), after all it is ruby.

2) bloggity - Uses the engines plugin! I have nothing against the engines plugin (I think it is well written and documented) but for a freaking blog plugin?!? Why?



Is there a third option?

Glad you asked - yes, there is a third option - I hate to say it, but do it right! There's your third option.

Ok, but really, if there is a third option (a third plugin), I would love to hear about it.



Ok, so all that to lead up to a little plugin tutorial? Well, it got your attention didn't it?


Starting from scratch

Ok, I guess I will start from scratch. So let's get started.

Creating a plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pullmonkey$ ./script/generate plugin simple_blog
      create  vendor/plugins/simple_blog/lib
      create  vendor/plugins/simple_blog/tasks
      create  vendor/plugins/simple_blog/test
      create  vendor/plugins/simple_blog/README
      create  vendor/plugins/simple_blog/MIT-LICENSE
      create  vendor/plugins/simple_blog/Rakefile
      create  vendor/plugins/simple_blog/init.rb
      create  vendor/plugins/simple_blog/install.rb
      create  vendor/plugins/simple_blog/uninstall.rb
      create  vendor/plugins/simple_blog/lib/simple_blog.rb
      create  vendor/plugins/simple_blog/tasks/simple_blog_tasks.rake
      create  vendor/plugins/simple_blog/test/simple_blog_test.rb
      create  vendor/plugins/simple_blog/test/test_helper.rb

Create the app directories for your plugin

1
2
3
4
5
6
7
8
9
10
pullmonkey$ cd vendor/plugins/simple_blog/  # pretty important
pullmonkey$ ls
init.rb  install.rb  lib  MIT-LICENSE  Rakefile  README  tasks  test  uninstall.rb
pullmonkey$ mkdir app
pullmonkey$ mkdir -p app/models
pullmonkey$ mkdir -p app/controllers
pullmonkey$ mkdir -p app/views
pullmonkey$ mkdir -p app/helpers
pullmonkey$ ls app/
controllers  helpers  models  views

Well that was easy, so let's move on.




Models, Views, Controllers and Helpers - Living as one in my plugin

Models

Ok, so we have a clear path for where our models, controllers, views, and helpers should live, right?

For simplicity, let's just have a post and comment model - you have all seen this a billion times.

Models: vendor/plugins/simple_blog/app/models/post.rb

1
2
3
class Post < ActiveRecord::Base
  has_many :comments
end

Models: vendor/plugins/simple_blog/app/models/comment.rb

1
2
3
class Comment < ActiveRecord::Base
  belongs_to :post
end

And there you have it.

So what do you do to tell your rails application about your models?

Simple - inside vendor/plugins/simple_blog/init.rb - add these lines

1
2
3
model_path = File.join(directory, 'app', 'models')
$LOAD_PATH << model_path
ActiveSupport::Dependencies.load_paths << model_path

Ok, so let's test it out.

Step 1 - we will need some default migrations for the model to use.

Post migration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pullmonkey$ ./script/generate migration post
# This is what mine looks like
class Post < ActiveRecord::Migration
  def self.up
    create_table :posts do |t|
      t.string :subject
      t.text   :body
      t.timestamps
    end
  end

  def self.down
    drop_table :posts
  end
end

And then the comment migration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pullmonkey$ ./script/generate migration comment
# This is what mine looks like
  def self.up
    create_table :comments do |t|
      t.string :username
      t.text   :body
      t.references :post
      t.timestamps
    end
  end

  def self.down
    drop_table :comments
  end

Run the migrations:


pullmonkey$ rake db:migrate

That was all just setup - now for the actual testing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pullmonkey$ ./script/console
Loading development environment (Rails 2.2.2)
>> Comment.new
=> #<Comment id: nil, username: nil, body: nil, post_id: nil, created_at: nil, updated_at: nil>
>> Post.new
=> #<Post id: nil, subject: nil, body: nil, created_at: nil, updated_at: nil>
>> p = Post.create(:subject => "Test 1", :body => "My Body")
=> #<Post id: 1, subject: "Test 1", body: "My Body", created_at: "2009-02-11 19:09:25", updated_at: "2009-02-11 19:09:25">
>> p.body
=> "My Body"
>> p.subject
=> "Test 1"
>> p.new_record?
=> false
>> p.comments
=> []
>> c = Comment.create(:username => 'pullmonkey', :body => "this is simple")
=> #<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: nil, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:01">
>> p.comments << c
=> [#<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: 1, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:06">]
>> p.comments
=> [#<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: 1, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:06">]
>> Post.first.comments
=> [#<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: 1, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:06">]

That's probably good enough. We have a working model and relationships. The best part is that all the code is still in the plugin.

What does my code space contain?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pullmonkey$ ls -l app/**
app/controllers:
total 4
-rw-rw-r--  1 pullmonkey pullmonkey 720 Feb 11 11:00 application.rb

app/helpers:
total 4
-rw-rw-r--  1 pullmonkey pullmonkey 115 Feb 11 11:00 application_helper.rb

app/models:
total 0

app/views:
total 4
drwxrwxr-x  2 pullmonkey pullmonkey 4096 Feb 11 11:00 layouts



Just the defaults - neat :)


Controllers

In much the same way as models, we can easily use controllers from our plugin. No extracting, no engines plugin.

Controllers: vendor/plugins/simple_blog/app/controllers/posts_controller.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

  def show
    @post = Post.find(params[:id])
  end

  def new
    @post = Post.new
  end

  def create
    if @post = Post.create(params[:post])
      flash[:notice] = "Post Created"
      redirect_to :action => 'index'
    else
      flash[:error] = "Post Not Created"
      render :action => 'new'
    end
  end
  #.... more code
end

Controllers: vendor/plugins/simple_blog/app/controllers/comments_controller.rb

1
2
3
4
5
6
class CommentsController < ApplicationController
  def index
    @comments = Comment.find_all_by_post_id(params[:post_id])
  end
  #.... more code
end

Now, to register the controllers, add the following to vendor/plugins/simple_blog/init.rb:

1
2
3
4
controller_path = File.join(directory, 'app', 'controllers')
$LOAD_PATH << controller_path
ActiveSupport::Dependencies.load_paths << controller_path
config.controller_paths << controller_path

Ok, before we can really test this we will need to do the views, so keep going.



Views

Create your view directories:

1
2
pullmonkey$ mkdir -p app/views/posts
pullmonkey$ mkdir -p app/views/comments

Create your views:
For this example, I will just create one, then we will test it.

Views: vendor/plugins/simple_blog/app/views/posts/index.html.erb

1
2
3
4
5
6
7
8
9
10
11
<h1>Posts</h1>
<% @posts.each do |post| -%>
  <h2><%= h post.subject %></h2>
  <%= post.body %>
  <h3>Comments</h3>
  <% post.comments.each do |comment| -%>
    <b>by <%= comment.username %></b><br/>
    <%= comment.body %><br/>
    <br/>
  <% end -%>
<% end -%>

Append your view paths:

If you don't do this next step, you will very likely see an error message like this:

Missing template posts/index.erb in view path /home/pullmonkey/rails_projects/simple_blog/app/views:


So let's add it.

There are at least two ways to do this. 1) Added to your controllers individually or 2) Add to application controller globally.

I prefer the less obtrusive, so let's go with number 1.

For this test, we will just work with the posts controller, so open it up again and add this line:


self.append_view_path(File.join(File.dirname(__FILE__), '..', 'views'))

So your file should look like this now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class PostsController < ApplicationController
  self.append_view_path(File.join(File.dirname(__FILE__), '..', 'views'))

  def index
    @posts = Post.all
  end

  def show
    @post = Post.find(params[:id])
  end

  def new
    @post = Post.new
  end

  def create
    if @post = Post.create(params[:post])
      flash[:notice] = "Post Created"
      redirect_to :action => 'index'
    else
      flash[:error] = "Post Not Created"
      render :action => 'new'
    end
  end
end

Time to test

Start your web server - ./script/server

Browse to http://localhost:3000/posts

You should see the post we created up above via Post.create(...) and its associated comment that we also created above.



Note:Feel free to overwrite any of the views. This can be done simply for the posts index view by creating the same file under RAILS_ROOT/app/views/posts/index.html.erb and doing what you'd like.



That's it for part 1

Ok, so that's part 1. The goal was to keep everything external and I think we succeeded (aside from migrations).

No offense to those that use engines or extract files into one's application's space, we all have our ways - the above is what I prefer.




Part 2 will consist mainly of filling this out a bit more and further discussion on adding helpers, routes and migrations to your plugin without interfering in the application's code space.



As always, have fun and good luck!



12 Jan

Open Flash Chart II - OFC Object Creators


Thought I would try and make things a little more flexible. In doing so, two new OFC Object creators came to life. You all may recall the very basic:

open_flash_chart_object(600,300,'/test_it/graph_test')


And maybe not, well either way, I am going to describe its functionality here plus the functionality of the two new object creators.

open_flash_chart_object()

Usage:

This method returns only the graph html:

@graph = open_flash_chart_object(....)

Arguments

  • width (required)
  • height (required)
  • url (required)
  • use_swfobject (optional and defaults to true)
  • base (optional and defaults to "/")
  • swf_file_name (optional and defaults to "open-flash-chart.swf")

open_flash_chart_object_and_div_name()

Usage:

This method will return, not only the html for the graph but also the div_name for use with javascript manipulation:

@graph, @div_name = open_flash_chart_object_and_div_name(...)

Arguments

  • width (required)
  • height (required)
  • url (required)
  • use_swfobject (optional and defaults to true)
  • base (optional and defaults to "/")
  • swf_file_name (optional and defaults to "open-flash-chart.swf")

open_flash_chart_object_from_hash()

Usage:

This method will return the graph html, but gives you absolute control over quite a few things, most importantly div_name.

@graph = open_flash_chart_object_from_hash(...)

Additional Usage:

@graph = open_flash_chart_object_from_hash("/test_it/graph_code", :div_name => 'my_div_name', :width => 600)
@graph = open_flash_chart_object_from_hash("/test_it/graph_code", :base => '/projects', :height => 600)

Arguments

  • url (required)
  • options (optional)
    • div_name (defaults to "flash_content_[random string]")
    • base (defaults to "/")
    • swf_file_name (defaults to "open-flash-chart.swf")
    • width (defaults to 550)
    • height (defaults to 300)
    • protocol (defaults to "http")
    • obj_id (defaults to "chart_[random string]")



Well, there it is, good luck and have fun.



08 Jan

Open Flash Chart II - Bar Graphs with on-click


Building on line graph clicking, thanks to the support of a few other people (mentioned throughout the article) we now have bar graph clicking as well. The only down side (if you want to call it that) is that it is experimental in the sense that the open flash chart swf object had to be updated, and the update is not part of the official OFC release (at least not at the time of this writing). No big deal though, just be aware. It is however part of the OFC rails plugin release.

Big thanks goes to Eric for his work on the action script for the bar clicking open-flash-chart swf file - see this forum entry for more details.

Obvious thanks also goes to monk.e.boy.


Ok, so two things to note for this to work:

  1. Pull the latest from github and make sure to get Eric's swf file (under the assets directory - open-flash-chart-bar-clicking.swf ) and place it under RAILS_ROOT/public
  2. The call to open_flash_chart_object() has changed to accept an optional parameter for the swf file name. I am leaving the original for use as open-flash-chart.swf (which is the default for the swf_file_name param) and added Eric's as open-flash-chart-bar-clicking.swf. See the example below for usage.



The changes that were made can be found here.




Here is the graph we are after in this example (click the bars to see what happens):






More Open Flash Chart II examples.




And here is the code (the controller):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

class TestItController < ApplicationController
  def index
    @graph = open_flash_chart_object(600,300,"/test_it/graph_code", true, "/", "open-flash-chart-bar-clicking.swf")
  end

  def graph_code
    title = Title.new("Bar on-click Example")
    bar = BarGlass.new
    # NOTE ... the next two lines are if you want each bar to have a different response when clicked
    bar_values = (1..9).to_a.map{|x| bv = BarValue.new(x); bv.on_click = "alert('hello, my value is #{x}')"; bv}
    bar.set_values(bar_values)
    # if you want a more generic response across all bars, then the following lines would do:
    # bar.on_click = "alert('hello there')"
    # bar.set_values((1..9).to_a)
    chart = OpenFlashChart.new
    chart.set_title(title)
    chart.add_element(bar)
    render :text => chart.to_s
  end
end



And in your view (index.html.erb):

1
2
3
4

<script type="text/javascript" src="/javascripts/swfobject.js"></script>
<%= @graph %>





Good Luck!





25 Oct

Open Flash Chart II - Updates, Radar Charts and Scatter Lines


Recently, some good work has gone into the Open Flash Chart plugin. First, it is now up to date with the official php version of OFC. That means we have Radar Charts and Scatter Line capabilities now.


Also, some nice people have helped this plugin along to maturity. I would like to thank David and Harry for their work.



David made some very much needed improvements and brought Open Flash Chart II plugin up to speed with Rails 2.x.
So take note, this is the "modern" way to work with the charts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class TestItController < ApplicationController
  ## EDIT dont need this line with the latest plugin.
  #include OpenFlashChart
  
  def index
    respond_to do |wants|
      wants.html {
        @graph = open_flash_chart_object( 600, 300, url_for( :action => 'index', :format => :json ) )
      }
      wants.json { 
        # Edit:: don't do the OpenFlashChart::Base stuff anymore
        #chart = OpenFlashChart::Base.new( :title => Title.new("MY TITLE") ) do |c|
        chart = OpenFlashChart.new( :title => Title.new("MY TITLE") ) do |c|
          c << BarGlass.new( :values => (1..10).sort_by{rand} )
        end
        render :text => chart, :layout => false
      }
    end
  end
end

Open Flash Chart was put in to the appropriate OpenFlashChart namespace/module, to ensure we don't run into any conflicts. It also now takes blocks which will be helpful. The example above is courtesy of David.




Harry, has done a great job of providing examples for the latest features of Open Flash Chart.

Check out his Radar Chart Example.

Also, check out his Scatter Line Chart Example.





Thank you for the great work guys.




23 Sep

Open Flash Chart II - Javascript (Part 3)


This article is a follow on to Part 1 and Part 2. In this article, I will discuss how we can change between various charts on the fly - meaning, changing the SWFObject without rerendering the page but this time we can do it without storing everything in javascript variables initially. We will use an Ajax request to grab our data off the server.

As promised there are still more topics to come on OFC and Javascript:



Here is the graph and interface we are after in this example:



Load Original Chart (Bar Graph)||Load Chart from server data (Line Graph)


More Open Flash Chart II examples.

And here is the code (the controller):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

class TestItController < ApplicationController
  def index
    title = Title.new("MY TITLE - original")
    bar = BarGlass.new
    bar.set_values([1,2,3,4,5,6,7,8,9])
    @chart = OpenFlashChart.new
    @chart.set_title(title)
    @chart.add_element(bar)
  end

  def some_server_data
     title = Title.new("Multiple Lines")

    data1 = []
    data2 = []
    data3 = []

    10.times do |x|
      data1 << rand(5) + 1
      data2 << rand(6) + 7
      data3 << rand(5) + 14
    end

    line_dot = LineDot.new
    line_dot.text = "Line Dot"
    line_dot.width = 4
    line_dot.colour = '#DFC329'
    line_dot.dot_size = 5
    line_dot.values = data1

    line_hollow = LineHollow.new
    line_hollow.text = "Line Hollow"
    line_hollow.width = 1
    line_hollow.colour = '#6363AC'
    line_hollow.dot_size = 5
    line_hollow.values = data2

    line = Line.new
    line.text = "Line"
    line.width = 1
    line.colour = '#5E4725'
    line.dot_size = 5
    line.values = data3

    y = YAxis.new
    y.set_range(0,20,5)

    x_legend = XLegend.new("MY X Legend")
    x_legend.set_style('{font-size: 20px; color: #778877}')

    y_legend = YLegend.new("MY Y Legend")
    y_legend.set_style('{font-size: 20px; color: #770077}')

    chart =OpenFlashChart.new
    chart.set_title(title)
    chart.set_x_legend(x_legend)
    chart.set_y_legend(y_legend)
    chart.y_axis = y

    chart.add_element(line_dot)
    chart.add_element(line_hollow)
    chart.add_element(line)

    render :text => chart.to_s
  end
end



And in your view (index.html.erb):

1
2
3
4
5
6
7
8
9
10
11
12

<html>
  <head>
    <%= javascript_include_tag :defaults, 'swfobject' %>
  </head>
  <body>
    <%= @chart.js_open_flash_chart_object("my_chart_js_1", 550,300) %>
    <br/><br/>
    <%= @chart.link_to_ofc_load("Load Original Chart", "my_chart_js_1") %> ||
    <%= @chart.link_to_remote_ofc_load("Load Chart from server data", "my_chart_js_1", "/test_it/some_server_data") %>
  </body>
</html>

In this example, we make use of the link_to_remote_ofc_load method that basically creates a link_to_remote along with the function that we call to load the chart data into the swfobject chart from the server. It takes three arguments, the link text, the id of the div whose swf we will load new data into and the url from which to fetch the data.


For more information on the javascript callbacks that I setup here, you will want to view the page source and read about it further over at teethgrinder's tutorial.


Good Luck!