While I Pondered…

Over many a quaint and curious volume of forgotten lore.

Showing Timelines using Timeline-Setter and Rails May 30, 2012

Filed under: Uncategorized — masonoise @ 5:44 am
Tags: , ,

Recently we wanted to add a nice-looking timeline view to some data in a web app, and looked around for good Javascript libraries. Perhaps the coolest one is Timeline JS from Verite, but while gorgeous it’s also super heavyweight, and pretty much demands a page all to itself. We wanted something more economical that could share the page with some other views, and decided on Timeline-Setter, which creates pretty little timelines with just enough view area to provide important information about each event.

However, as-provided, Timeline-Setter wants to exist as a command-line utility. You get a data file ready, run the utility, and it generates a set of HTML (and associated) files that you can drop into an app. That’s dandy if you have a set of data that doesn’t change often, or you want to perhaps run a cron to pre-generate a bunch of timelines. We needed something that could generate a timeline for a dynamic set of data, so we weren’t sure Timeline-Setter would work for us. However, looking it over I thought it seemed potentially usable in a dynamic way. I generated a static example using our data, read through what it had created, and deconstructed what it wanted in order to display the timeline, then wrote some code to dynamically generate the JSON data necessary. It wasn’t too difficult, and fairly shortly we had dynamic timelines going. I wanted to share the info here since it’s a pretty nice library that others should get a lot out of.

We’re using Jammit to handle our static assets, so we simply put “public/javascripts/timeline-setter.js” and “public/stylesheets/timeline-setter.css” into our assets.yml file, but you can use whatever standard approach you take to including JS and CSS into your pages. Once that’s done, you’re ready to go.

Timeline-Setter takes a pretty standard approach to placing itself in the page: it takes the ID of a DIV, and that’s the container which will hold the timeline. One note: we needed to include multiple timelines in a single page, so we had to do a little creative naming of the DIVs that hold the timelines, as you’ll see below.

<div id="timeline-<%= author.id %>" class="timeline_holder"></div>
<pre>
<script type="text/javascript">// <![CDATA[
    $(function () {
      var currentTimeline = TimelineSetter.Timeline.boot([<%= author.make_timeline_json %>], {"container":"#timeline-<%= author.id %>", "interval":"", "colorReset":true});
    });

// ]]></script>

The code here creates the DIV, gives it an ID, and then places a Javascript call to the Timeline-Setter boot() function, which tells it to generate the timeline. The first parameter is the JSON holding the data for the timeline; the second is a set of options, passed in as JSON. “container” of course is the ID of the DIV which will contain the timeline. Other options include “interval”, “formatter”, and “colorReset” among others. See the library’s page as listed at the beginning for details of the API, and in particular see the section headed “Configuring the Timeline JavaScript Embed” for the basics of calling the boot() function.

Now of course we need the make_timeline_json() method, which will take our object’s data and create the JSON needed by Timeline-Setter. As an example here, let’s pretend that we’re showing a timeline of books written by an author over the years.

class Author < ActiveRecord::Base
  def make_timeline_json
    timeline_list = []
    if (!birth_date.nil?)
      timeline_list << "{'html':'','date':'#{birth_date}','description':'Birth Date:','link':'','display_date':'','series':'Events','timestamp':#{(birth_date.to_time.to_i * 1000)}}"
    end
    books.each do |book|
      author_list = book.authors.map { |auth| "#{auth['name']}" }.join('; ')
      timeline_list << "{'html':'Authors: #{author_list}
Publisher: #{book.publisher}','date':'#{book.pub_date}','description':'#{book.title}','link':'','display_date':'','series':'Publications','timestamp':#{(book.pub_date.to_time.to_i * 1000)}}"
    end
    if (!death_date.nil?)
      timeline_list << "{'html':'','date':'#{death_date}','description':'Death Date:','link':'','display_date':'','series':'Events','timestamp':#{(death_date.to_time.to_i * 1000)}}"
    end
    return "#{timeline_list.join(',')}"
  end
end

Essentially, this method creates a JSON string containing a set of entries, each representing an event to place on the timeline. Each entry has several fields: html, date, description, link, display_date, series, and timestamp. Not all of these are used here, but with the basics you can experiment further. The important fields are:

  • html: This will be displayed in the event’s pop-up when clicked on.
  • description: Just what it says.
  • link: an optional URL which will be associated with a link in the event pop-up.
  • series: which “series” this event belongs to; see below for details on this.
  • timestamp: This is the timestamp associated with the event, used to construct the timeline in order.

A note about the “series” parameter: one very nice feature of Timeline-Setter is that you can display more than one set of events in a single timeline. Each set of events is called a “series”. In our example we’re creating two series: “Events” and “Publications”. Each will be shown in a different color, with a title (so the names of the series need to look nice, as they’ll be displayed) and a checkbox so that a viewer can hide and show each series individually. It’s extremely useful.

In the code above, you’ll see that we create the “Birth Date” and “Death Date” events individually, but in the middle we iterate over the books associated with this author. For each book we build a string of authors, semicolon-delimited, just to demonstrate one way to include another list of information in an event’s HTML. I have to admit that I’m not entirely certain why it’s necessary to multiply the timestamps by 1000 to get to the correct time, but it works fine…

And there you are. Hopefully anyone needing an economical, nice-looking timeline with dynamically-generated data can take advantage of this. But certainly, if you can work with static (or infrequently-updated) data, you may be able to use Timeline-Setter out of the box via a cron job or rake task — you could generate the CSV file for its command-line interface, run it, then copy the resulting files into your application. If you need dynamic timelines, though, I hope this post is helpful.

 

Bar and Pie Charts with RaphaelJS December 29, 2009

Filed under: Uncategorized — masonoise @ 7:46 pm
Tags: , , ,

At my last place we got some cool Google Analytics-style charts going using the excellent RaphaelJS library. This week I’ve been playing with the simpler bar and pie charts, and thought I’d share some findings. RaphaelJS is great, but the examples and documentation are still — like so many open source projects — slim. In particular, I found that the example provided for an interactive pie chart is broken, so I had to play around to get that working.

I did the bar chart first, and found after some searching in the Google Group archive that the labels aren’t quite working in the main branch, but thankfully someone else has a forked version of the bar chart file. If you want to get the labels working properly, go here: http://github.com/iamwilhelm/g.raphael and grab g.bar.js.

I set up a simple report page that displays the same data using either a bar chart or a pie chart depending on the parameter “chart”. The data is simply a count of totals for each month of the year, so there are up to 12 bars/segments shown in each chart. The controller gathers up the data from a query and also prepares the labels for the charts:

  def data_by_month
    @chart = params[:chart]
    months = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]

    monthly_totals = MyModel.get_monthly_totals()
    @monthly = []
    @month_names = []
    monthly_totals.each_with_index do |month, i|
      @monthly << month['count'].to_i
      month_name = months[i]
      if (@chart == 'pie')  # Need to add counts to the labels for pie charts
        month_name << " (#{month['count']})"
      end
      @month_names << month_name
    end
  end

One of the awkward things you’ll see above is that the code adds the numbers to the labels if it’s going to show a pie chart. That’s because unfortunately I haven’t yet found a way to show the numbers within the pie chart segments. So instead, I display the legend/labels to the side of the pie chart, with the numbers as part of the labels. For example, instead of just showing “January” it shows “January (35)”. Not great, naturally. The bar charts automatically show the numbers when you hover over the bars, so I didn’t need to do that in the bar chart case.

It’s worth quickly pointing out that when the code’s adding the values to the @monthly array, I had to do a .to_i on it. Otherwise the values went in as strings. Oddly enough, that worked fine for the bar chart, but the pie chart didn’t show any results.

The page itself is as follows:

<%= javascript_include_tag "raphael-min.js" -%>
<%= javascript_include_tag "g.raphael-min.js" -%>
<!-- Using g.bar.js from http://github.com/iamwilhelm/g.raphael with horz label fix -->
<%= javascript_include_tag "g.bar.js" -%>
<%= javascript_include_tag "g.pie-min.js" -%>

<script type="text/javascript">
  //<![CDATA[
  window.onload = function () {
    var r = Raphael("holder", 800, 500),
        fin = function () {
            this.flag = r.g.popup(this.bar.x, this.bar.y, this.bar.value || "0").insertBefore(this);
        },
        fout = function () {
            this.flag.animate({opacity: 0}, 300, function () {this.remove();});
        };
    r.g.txtattr.font = "18px 'Fontin Sans', Fontin-Sans, sans-serif";

    r.g.text(160, 10, "Totals by Month for <%= @year -%>");

    <% if (@chart == 'bar') %>
    r.g.barchart(30, 30, 500, 420, [<%= @monthly.to_json -%>]).hover(fin, fout).label([<%= @month_names.to_json -%>]);
    <% else %>
    var pie = r.g.piechart(280, 260, 200, <%= @monthly.to_json -%>, {legend: <%= @month_names.to_json -%>,
        legendpos: "east"});
    pie.hover(function () {
                    this.sector.stop();
                    this.sector.scale(1.1, 1.1, this.cx, this.cy);
                    if (this.label) {
                        this.label[0].stop();
                        this.label[0].scale(1.5);
                        this.label[1].attr({"font-weight": 800});
                    }
                }, function () {
                    this.sector.animate({scale: [1, 1, this.cx, this.cy]}, 500, "bounce");
                    if (this.label) {
                        this.label[0].animate({scale: 1}, 500, "bounce");
                        this.label[1].attr({"font-weight": 400});
                    }
                });
    <% end %>
  };
  //]]>
</script>

<br/><br/>

<h1>Reports : Totals by Month</h1>
<%= link_to('Bar Chart', totals_by_month_path(:chart => 'bar')) %> |
<%= link_to('Pie Chart', totals_by_month_path(:chart => 'pie')) %>

<br/><br/>

<div id="holder"></div>

So, what’s up here? A lot of this code was taken from the RaphaelJS examples, and then munged a bit. After including the files, the onload() function sets up the Raphael object, which is called “holder” to match the id of the <div> down below, which is where the charts will be placed. The functions fin() and fout() are used by the bar chart (so yeah, they should be put inside an if block but I haven’t done that yet). They display/remove the value and do the animation during hover events. The text() call is just to put a title on the chart. In this case it probably makes more sense to leave that in the plain HTML, but it was in the example code so I left it in order to experiment.

If the chart is a bar chart, the line

r.g.barchart(30, 30, 500, 420, [<%= @monthly.to_json -%>]).hover(fin, fout).label([<%= @month_names.to_json -%>]);

is used. Obviously the first bit of code takes the monthly values, which is an array, and drops in the JSON equivalent. Those are the numbers which will be used to create the bar chart. The second bit of code is what places the labels, which in this case are the month names. This threw me off initially, because the function expects an array of arrays, just like the values. This is because it supports “stacked” bar charts — the extra square brackets around the values/labels are important. Without those around the labels, I was getting one character per bar instead of the full string, which was very confusing.

This is what the bar chart looks like (obviously not showing the hover-over which displays the number for each bar):

For the pie chart, the basic call is very similar. The values are passed in the same way, but then the “legend” attribute is used to pass the names of the months (with the totals added in this case). The “legendpos” attribute positions the labels to the north/east/south/west of the chart itself. I started on the west but had trouble with alignment — the chart was getting positioned too close to the labels and would end up partially covering the labels on rollover. Putting the legend on the east worked around that problem for now. The hover() functions were taken verbatim from a pie chart example.

This is what the pie chart looks like (obviously not showing the hover-over, which enlarges the segment and the corresponding legend item):

As you can see, getting these working is very straightforward — the only issues that held me up were due to incomplete documentation or examples not quite working correctly. Hopefully this post can help speed things up for others. RaphaelJS is very powerful library, and the charting plugin that provides the easy charts is a great add-on. Enjoy.

 

 
Follow

Get every new post delivered to your Inbox.