Dashboards Toolkit

An important part of being data driven is to have a daily feedback on data, here’s a couple of automated dashboards i’ve built recently:

/assets/images/dash2.png

/assets/images/dash1.png

In these most of the data is displayed as is, next iteration could enrich the data further with:

  • Adding the data of a year/6months ago for direct comparison could be interesting.
  • Fits to data, like a regression line that shows the overall tendency, plus allows to make predictions on next day/week/month values.
  • More of relative change plots, like the protovis index-chart are very useful.
  • Confidence intervals pointing out that the changes are unlikely to be by chance.
  • etc…

Tools & Code

Coded in ruby, it aggregates data from different sources, and based on an html template it generates html with the full dashboard. Its fully automated, and i make it run on daily basis using a cron job.

Uses highcharts as the javascript charting engine, which i can only say good things about, very nice looking and allows user interaction.

I placed on github the code i use as the base to build the dashboards, find it here: https://github.com/al3xandr3/Dashboard

A few bits:

Getting RescueTime Data

require 'open-uri'
require 'date'

key = "yourownkey"

res = {} 
open("https://www.rescuetime.com/anapi/data?key=#{key}&perspective=interval&format=csv&resolution_time=day&restrict_kind=activity") do |f|
  i=0    
  f.each do |l|    
    unless i==0
      t, sec, some, app, cat, prod = l.split(",")
      res[:week] += sec.to_i
      res[:day] += sec.to_i if Date.parse(t).day == Date.today.day
    end
    i+= 1
  end
end

print res

Getting Google Spreadsheets Data

require 'gdata/client'  
require 'gdata/http'  
require 'gdata/auth'
require 'open-uri'
require 'date'

client = GData::Client::Spreadsheets.new
client.clientlogin('yourmail@gmail.com', "yourpass")
key = "yourspreadsheetkey"
test = client.get("http://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=#{key}&fmcmd&exportFormat=csv")
values = []
i=0
test.body.each_line do |l|
  t,w,co,wa,h = l.gsub("\n","").split(',')
  unless i==0
    values << [Date.parse(t), w.to_f, wa.to_f, h.to_f] 
  end
  i+=1
end

print values

Getting imap mail attachments

require 'net/imap'
require 'date'

opts[:inbox]   ||= "Inbox"
opts[:search]  ||= ["SINCE", "8-Aug-2007"]
opts[:attach]  ||= ["CSV"]
opts[:savedir] ||= "."

imap = Net::IMAP.new('mail.server.com', :port => 993, :ssl => true)
imap.login('yourmail@server.com', 'yourpassw')    
imap.select(opts[:inbox])
imap.search(opts[:search]).each do |uid|
  msg = imap.fetch(uid, ["ENVELOPE","UID","BODY"])[0]
  body = msg.attr["BODY"]
  date = Date.parse(msg.attr["ENVELOPE"].date)
  i = 1
  while body.parts[i] != nil
    type = body.parts[i].subtype
    encoding = body.parts[i].encoding
    name = body.parts[i].param["NAME"] || date.to_s
    i+=1
    attachment = imap.fetch(uid, "BODY[#{i}]")[0].attr["BODY[#{i}]"]
    p "#{name}, #{type}, #{encoding}"
    if opts[:attach].include? type and not attachment.nil?
      File.open(opts[:savedir] + name,'wb+') do |f|
        if encoding == "BASE64"
          f.write(attachment.unpack('m')[0])
        else
          f.write(attachment)
        end          
      end
    end
  end  
end 

Posting html to a confluence wiki

require 'xmlrpc/client'

user = "username"
pass = "password"
area = "area"
page_name="page"
content = "<h1>Big Header</h1>"
confluence = XMLRPC::Client
      .new2("https://#{user}:#{pass}@confluence.server.com/rpc/xmlrpc")
      .proxy("confluence1")
page = confluence.getPage("", area, page_name)
page["content"] = "{html}#{content}{html}"
confluence.storePage("", page)

Creating a highcharts JS chart

require 'erb'
require 'date'

def line arg={}

  arg[:height] = arg[:height] || ""
  arg[:width] = arg[:width] || ""

  line_chart = %{
    <div id="<%= arg[:name] %>" style="height:<%= arg[:height] %>px;width:<%= arg[:width] %>px;"></div>
    <script type="text/javascript">
     var month = new Array("Jan","Feb","Mar","Apr","May","Jun",
                           "Jul","Aug","Sept","Oct","Nov","Dec");
     var chart;
     $(document).ready(function() {
     chart = new Highcharts.Chart({
        chart: {
           renderTo: '<%= arg[:name] %>',
           defaultSeriesType: 'line',
           marginRight: 40,
           marginBottom: 40
        },
        credits:{
          enabled:false
        },
        plotOptions: {
           line: {
              dataLabels: {
                 enabled: <%= arg[:data_labels] || false %>
              }
           }
        },
        title: {
           text: '<%= arg[:name] %>',
           x: -20 //center
        },
        subtitle: {
           text: '<%= arg[:subtitle] %>',
           x: -20
        },
        xAxis: {
           type: "datetime",
           title: {
              text: '<%= arg[:xlabel] %>'
           },
        },
        yAxis: {
           min: <%= arg[:ymin] || 0 %>,
           title: {
              text: '<%= arg[:ylabel] %>'
           },
        },
        tooltip: {
           formatter: function() {
             return (new Date(this.x)).getDate() + ' ' +   
                    month[(new Date(this.x)).getMonth()] + 
                     ': '+ this.y;
           }
        },
        legend: {
           layout: 'vertical',
           align: 'right',
           verticalAlign: 'top',
           x: 0,
           y: 0,
           borderWidth: 2
        },
        series: [{
           pointInterval: 24 * 3600 * 1000,
           pointStart: <%= arg[:start_time] %>,
           name: '<%= arg[:name] %>',
           data: <%= arg[:values] %>
        }]
       });
      });
    </script>
    }
  ERB.new(line_chart).result(binding)
end

c = line(:name => "My Fancy Chart",
     :subtitle => "subtitle",
     :xlabel => "y label",
     :ylabel => "y label",
     :start_time => (Date.today-7).to_time.to_i * 1000, 
     :values => [12.2, 13.3, 11.1, 15.5])
print c

Updated: