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():
1234567891011121314151617181920212223
moduleOFCclassBasedefmethod_missing(method_name, *args, &blk)case method_name.to_swhen/(.*)=/# 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 methodself.instance_variable_set("@#{$1}", args[0])when/^set_(.*)/# backwards compatible ... the user can still use the same set_y_legend methods if they wantself.instance_variable_set("@#{$1}", args[0])elseif inst = self.instance_variable_get("@#{method_name}") instelse# if the method/attribute is missing and it is not a set method then hmmmm better let the user knowsuperendendendendend
This just basically allows me to do this:
12345678910111213
classFoo < OFC::Baseend 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:
Ok, so on to const_missing() and what we can do with that:
123456
defOFC.const_missing(const) klass = Class.new OFC::BaseObject.const_set const, klassreturn klassend
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:
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
12345
# 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.
> pullmonkey$ ./script/generate controller test_it# <And more stuff >
Get our assets
1234567
# 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.
12345678910
> pullmonkey$ vi app/controllers/test_it_controller.rb# mine looks like this:classTestItController < ApplicationController include OFC::Examples::AreaHollowdefindex@graph = open_flash_chart_object(600,300, "/test_it/area_hollow")endend
Edit our view
12345
> pullmonkey$ vi app/views/test_it/index.html.erb# mine looks like this:<%= javascript_include_tag 'swfobject'%><%= @graph %>
Start 'er up
12345
> pullmonkey$ ./script/server# browse to the test_it indexhttp://localhost:3000/test_it
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:
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
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.
classTestItController < ApplicationControllerdefindex@graph = open_flash_chart_object(600,300,"/test_it/graph_code", true, "/", "open-flash-chart-bar-clicking.swf")enddefgraph_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_sendend
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:
1234567891011121314151617181920
classTestItController < ApplicationController## EDIT dont need this line with the latest plugin.#include OpenFlashChartdefindex 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 }endendend
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.
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:
<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.
This article is a follow on to Part 1. In this article, I will discuss how we can change between various charts on the fly - meaning, changing the SWFObject without sending a request or rerendering the page.
As promised there are still more topics to come on OFC and Javascript:
In this example, we make use of the link_to_ofc_load method that basically creates a link_to_function along with the function that we call to load the chart data into the swfobject chart. It takes two arguments, the link text and the id of the div whose swf we will load new data into.
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.
This article (and the work behind it -- meaning get the latest from github) is generously sponsored by Harry Seldon who wants to be able to pass data around using javascript. There are quite a few benefits to this, learn more from teethgrinder's tutorial on the same topic.
This example opens up a lot of possibilities and I thank Harry for pointing me to it. So more to come on OFC and Javascript. For a taste of what is to come, check these out:
I do a lot behind the scenes but if you look, you will see a few new things here.
The data comes from the @chart.js_open_flash_chart_object(...) call which sets up a few javascript callback methods to send the data to the SWF object. It takes 3 required arguments div_name (the id of the div that houses the chart), width and height and one optional argument base_url which defaults to "/".
If you look at the HTML source, it is quite a bit different, we simply embed the SWF object. We do not need to point to the data method since there actually isn't one.
One difference between this Rails example and php example (from teethgrinder) is that prototype which comes standard with rails, has a JSON implementation for javascript, so we do not need the json2.js file, but make sure to include prototype.js in your apps.
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! and Harry, I hope this helps, otherwise please drop me a line.
classTestItController < ApplicationControllerdefindex@graph = open_flash_chart_object(600,300,"/test_it/graph_code")enddefgraph_code chart = OpenFlashChart.new title = Title.new("Scatter points") chart.set_title(title) scatter = Scatter.new('#FFD800', 10) # color, dot size scatter.values = [ScatterValue.new(50,30),ScatterValue.new(305,400),ScatterValue.new(61,500,15), # x, y, dot sizeScatterValue.new(600,550),ScatterValue.new(459,300),ScatterValue.new(180,789) ] chart.add_element(scatter) x = XAxis.new x.set_range(0, 650, 100) #min, max, steps# alternatively, you can use x.set_range(0,65000) and x.set_step(10000) x.colour = '#00FF00'# have to set the x axis labels because of scatter bug here - http://sourceforge.net/forum/message.php?msg_id=4812326 x.set_grid_colour('#00F0FF') chart.set_x_axis(x) y = YAxis.new y.set_range(0,800,200) y.colour = '#FF0000' y.set_grid_colour('#FF00FF') chart.set_y_axis(y) render :text => chart.to_sendend
classTestItController < ApplicationControllerdefindex@graph1 = open_flash_chart_object(600,300,"/test_it/graph_one")@graph2 = open_flash_chart_object(600,300,"/test_it/graph_two")@graph3 = open_flash_chart_object(600,300,"/test_it/graph_three")# and so on ...enddefgraph_one ... # put some OFC2 code here like in my examplesenddefgraph_two ... # put some OFC2 code hereenddefgraph_three ... # put some OFC2 code hereendend