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:
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:
12345678910111213141516171819202122
classTestItController < ApplicationControllerdefindex# 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 graphdefupload_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_postFile.open("#{RAILS_ROOT}/tmp/#{name}", "wb") { |f| f.write(data) } if data render :nothing => trueenddefchart# same code from here - http://pullmonkey.com/2010/01/05/open-flash-chart-ii-x-axis-date-and-time/ ...endend
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:
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.
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.