#223 Charts & Graphs (revised)
Sep 15, 2012 | 12 minutes | Performance, Views
If you have a lot of data, consider adding a graph to provide an overview of it. Here I show how to use Morris.js to chart an Order model and visualize trends in the data.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
What would you recommend for charts in both html and a generated pdf? Ever tried one of these libs with v8 on the server?
Usually, I stick with LaTeX and PGF/TikZ to produce PDF graphs.
This has some disadvantages, though:
It's more of an advanced task (not the Ruby/Rails part, but the LaTeX one), but if you value the high quality of TeX'ed documents, it's definitely the way you should go.
Using v8 on the other hand sounds far to nice to be true :-)
I have a friend who wrote a Ruby wrapper to the Plotly REST API. You can use it to make interactive, web-based graphs. You can both download your graphs and embed them as an iframe.
It's on GitHub here: https://github.com/plotly.
You can see examples at https://plot.ly.
Great episode another option is: Google Charts
Is Google charts an option for proprietary data? I have an app that is strictly for internal use.
Any thoughts about jqPlot?
I use jqPlot in a project at work and so far I like it. It does have somethings which may be hard to implement. However you can use the gem I wrote plot simple(documentation) to get around this.
Morris.js looks super simple to setup and use.
My line charts required logarithmic Y-axis. So I had to settle on the Google Visualization API with the Visualr Gem. However, over time, I found it somewhat rigid. Therefore, I had to go to HighCharts which can do just about everything that I need to do. As Ryan points out, it is a commercial product though.
index_by works a lot better after a group by query compard to group_by.
Agreed - There's no need to use 'first' if you use index_by. The hash values are instances as expected.
Another option - use each_with_object
it's even better
unless I'm missing something important ^^
Found nvd3 which seems pretty nice.
Quite interesting should also be Dimensional Charting Javascript Library.
Should
Time.zone.now
be in a lambda?You can also use g Raphael which is the library Raphael provides for drawing charts
first of all, thanks... that's just what i've needed now.
But does anybody know why the video content is different from the Show Notes or even the Source Code?
Edit: Sry, didn't see the Notes.
I use amCharts. It has some pretty advanced charting features for those needing them. It is a commercial product but well maintained and totally worth it. Available in Flash and Javascript versions.
Google chart is also a good tool to display live data.
Considering that Morris is Javascript (and thus doesn't generate graphs server-side), it seems disappointingly complicated to use in the sort of situations demonstrated in this episode.
Specifically, since the data to be graphed is already present in the markup, it would be nice if Morris could be instructed to create the chart directly from that tabular data as represented by the markup itself. This would reduce duplication by not having specific Rails code for generating Morris-specific data, as well as removing clutter from the rendered markup which can make it more cumbersome to do certain debugging tasks, and which is only useful to Javascript anyway (and not, e.g. screen readers).
The above could imply a situation in which all of the data is present in the markup--as opposed to say, the latest data only, with the chart perhaps a representation of a longer time-frame--but as it is, Morris still would need all of that data to be present in the markup anyway (albeit in a data attribute). This could be solved nicely via AJAX, and perhaps fetching sampled data (if the data set is large), but such a request could still respond with a document fragment containing tabular markup, and thus still removing any need for separate chart-rendering code (but possibly adding a small amount of complexity for specifying things like sample resolution). This would also be a reasonable approach when only the chart itself is meant to be shown.
It's (sadly) out of date, but there was a good Javascript library that could in fact take data directly from the markup, and used the canvas to render its output: Bluff, isn't visually polished enough by today's standards, but is a good example of such functionality in action, and really and truly is easy to use.
I'm getting 'Graph Placeholder not found' despite the div id being on the page. Any ideas?
See Sanjeev's and my replies below
I am getting the 'Graph Placeholder not found' aswell, which breaks most of the JS on my site, can we get a solution on this please?
See Sanjeev's and my replies below
Hi i can't make the charts appearing using twitter bootstrap, is there any aditional task to do to?
Thanks
Hi,
i got the chart working under twitter bootstrap, I had to use a .js file rather than a coffee-script file for the code that created the graph.
I'm having issues with it too, running under bootstrap I get an error in the javascript console: "undefined is not an object (evaluating _ref.length)"
I'm using it with the test data, not even a dynamic dataset.
I just figured this out under the javascript debugger, for some reason the coffee script version does not set the xkeys or xkeys options, using plain old javascript works even under bootstrap.
Maybe try this: http://iqwen.net/question/58396
Anybody having trouble getting the data attribute working on rails 3.0.1?
I have added morris and raphael inside vendor/javascripts and did call in application.js also. In my orders.js.coffee i put
jQuery ->
Morris.Line
element: 'annual'
data: [
{y: '2012', a: 100}
{y: '2011', a: 75}
{y: '2010', a: 50}
{y: '2009', a: 75}
{y: '2008', a: 50}
{y: '2007', a: 75}
{y: '2006', a: 100}
]
xkey: 'y'
ykeys: ['a']
labels: ['Series A']
for testing purpose and did add in index. But chart did not appears.
nevermind, I figured it out, the code should have been
jQuery ->
Morris.Line({
element: 'annual'
data: [
{y: '2012', a: 100}
{y: '2011', a: 75}
{y: '2010', a: 50}
{y: '2009', a: 75}
{y: '2008', a: 50}
{y: '2007', a: 75}
{y: '2006', a: 100}
]
xkey: 'y'
ykeys: ['a']
labels: ['Series A']}
)
Thanks Sanjeev,,,for those who don't notice the subtle difference... Morris.Line({....}) needs to separate each different chart/graph call with different tags...otherwise it will only see the first and you'll get the "'Graph Placeholder not found'" or "Element not found" errors.
hey,
i released a gem for morris.js so you don't need to add the assets manually.
You can find it over at Github (https://github.com/beanieboi/morrisjs-rails)
very handy, thank you :)
So dumping orders array into data-attribute exposes all the attributes stored in db to Morris. What would be the best way to inject virtual attributes to the objects in array handed over to javascript side?
Thanks Ryan :-)
Hi guys,
I am new to rails. How do I make this example show the data by hour?
My database has two relevant columns: "data_time" and "values". I have a few values per hour, so, I wanted to plot them all. I was able to group by hour changing:
group("date(data_time)")
to:
group("strftime('%Y-%m-%d %H', data_time)")
But the graph still shows only per day. I suspect it has something to do with:
(start.to_date..Date.today).map do |date|
but I can't figure out what it is!
A bit late, but I hope this helps explain how to iterate over an arbitrary time period: http://stackoverflow.com/questions/501253/iterate-over-ruby-time-object-with-delta
I adapted Ryan's example to the code below, which works just fine on SQLite. However, on PostgreSQL (Heroku), I get the error
PGError: ERROR: column "users.created_at" must appear in the GROUP BY clause or be used in an aggregate function
Any ideas on making this work in PostgreSQL?
Here's the relevant code:
I fixed this by changing
created_at
in theselect
clause todate(created_at)
. In my case, I also needed to addunscoped.
before thewhere
clause in order to eliminate my model's default ordering byname
, which PG also complained about for the same reason, and I added aorder('date(created_at)')
for good measure.Here's the edited code that works in PostgreSQL:
Thank you John for the snippet! It was really helpful for me.
I follow everything exactly, but when I try to display the graph with the fake information, nothing displays. What could possibly be going wrong?
I am using Morris chart. How do i reverse the values on the Y axis.
I am tracking ranks hence lower the better.
http://www.siterankhistory.com
i am also using this service
thanks
http://www.printerdriverforwindows.com
I can't get the demo started. The console gives me this error Uncaught TypeError: Cannot call method 'match' of undefined.
I have created a stackoverflow thread as well to see if anyone else stumbled across this.
http://stackoverflow.com/questions/16267904/issue-getting-started-with-morris
Issue solved
How did you solve the issue? I am having the same problems. I'm currently using .getAttribute('data-orders'), and that seems to work fine, but it is still not displaying.
In case it helps anyone, I was running into the same error, and it turned out that I had made a typo in the script. I referred to the 'xkey' option as 'xkeys' which is not valid. Once I fixed that and reload the page, the demo graph showed.
I'm rather new to rails and javascript/coffee
could some1 please explain to me the logic behind this line:
data: $('statistics_chart').data('statistics')
what is the magic so to speak, behind the scenes?
you are populating the statistics_chart div with data from data-statistics attribute of that div, look in inspect element and you'll see the html5 attribute called data-statistics with an array of hashes inside.
Is there an easy way to populate my database with the test data shown in the screencast?
Thanks in advance!
How do you group count by month?
The following code works for grouping counts by month:
date_trunc
is a postgresql function.I've yet to find a way to format the date output to
"%b %Y"
instead of the default format.If anyone has a better solution please share!
I can get the sample data working in my Rails app (3.2.14), but when I try with real data, the chart doesn't show up. It appears my JSON string has an extra level of nesting in it, where it's {model_name: {properties}}, you can see my code and an example in my Gist. Any help is greatly appreciated!
It's worth noticing that the code presented in the video causes unexpected timezone issues.
As timestamps are stored in UTC when you use SQL's
date()
function you'll get the date in UTC which can be different. E.g (consider -0300 the local zone) 2013-12-01 01:00 -0300 -> (stored as) 2013-11-30 22:00 UTC => (afterdate()
) 2013-11-30 when you'd expect 2013-12-01.Rails makes conversions itself when fetching a timestamp column but since you're grouping in SQL Rails cannot do anything about it.
I'm still searching about this and if I find a optimal solution I'll post here.
I've developed a method called
group_date
that is added to ActiveRecord::Relation.You can read about it in this gist: https://gist.github.com/leods92/7164159
I guess here is a missing try
Ken, you are a champ! I've been working on this railscast for over 5 hours and kept getting no data on the y axis. Super frustrating, especially when Ryan demos his solution works with only .first.try(:total_price)
Any ideas on why Ryan's demo works in that format? I'm on win 7, rails 4.2
:)
Can you please check the problem that I have on stackoverflow, in two days nobody was able to help me, I'm I don't know what to do anymore.
http://stackoverflow.com/questions/19856714/display-in-a-graph-just-unique-values-for-a-column/19857667?noredirect=1#comment29536506_19857667
Thank you
I get ERROR! THERE somebody can help me?
ExecJS::RuntimeError in Host_informations#index
Showing /Users/liucc/omm/app/views/layouts/application.html.erb where line #37 raised:
SyntaxError: unexpected TERMINATOR
(in /Users/liucc/omm/app/assets/javascripts/host_informations.js.coffee)
host_informations.js.coffee:
jQuery ->
Morris.Line
element: 'annual'
data: [
{y: '2012', a: 100}
{y: '2011', a: 75}
{y: '2010', a: 50}
{y: '2009', a: 75}
{y: '2008', a: 50}
{y: '2007', a: 75}
{y: '2006', a: 100}
]
xkey: 'y'
ykeys: ['a']
labels: ['Series a']
How would you create the graph grouped by 2 variables month and employee (for instance)?
The morris graph expects this type of data for multiple lines
Morris.Line({
element: 'line-example',
data: [
{ y: '2006', a: 100, b: 90 },
{ y: '2007', a: 75, b: 65 },
{ y: '2008', a: 50, b: 40 },
{ y: '2009', a: 75, b: 65 },
{ y: '2010', a: 50, b: 40 },
{ y: '2011', a: 75, b: 65 },
{ y: '2012', a: 100, b: 90 }
],
xkey: 'y',
ykeys: ['a', 'b'],
labels: ['Series A', 'Series B']
});
very nice service, i keep using this service, thanks.
I had an issue with the content_tag escaping the json ?
so ended up using
<%= content_tag :div, "" , id: "Orders" , data: { orders: raw(@orders.to_json) } %>
V