Interactive pie chart with KineticJS on HTML5 Canvas

My previous post was testing canvas animation library CreateJS, heavily promoted by Adobe and Microsoft as a good alternative for Action Script and Flash solutions. To be honest I wasn’t very satisfied the way it was implemented, because I really wanted to see fully objective JavaScript. But even though the improvement, in comparison to pure HTML5 Canvas syntax was impressive, I decided not give up and search for other – maybe better – libraries. After a few seconds spent on Google I found KineticJS, an older brother of CreateJS dedicated also to work with HTML5 canvas.


KineticJS

Eric Rowell – the author of the library – guarantees that it has high performance animations and given support and structures for transitions, node nesting, layering, filtering, caching, event handling. The library is also highly customizable: we can include or exclude any part of it – except core objects. What I really liked, was highly developed support for animation elements organizing structures:

  • Layers
  • Sprites
  • Groups
  • Collections

These gives us control over the mess and gently mange them, if we have more complex animations. Just for that feature I decided to test it out. I had to, because it was looking very promising. My decision was simple: compare both libraries KineticJS and CreateJS by implementing very similar tutorial. This time we’re gonna make pie chart using KineticJS.

Below you have the source code and live demo:

source code link

Download the source code

live demo link

See it working in the browser

Implementation

As in previous tutorial I will be writing my code in CoffeeScript. It emphases objective part of JavaScript and gives him very pleasant Python-like syntax. To learn about this meta-language you can read my articles why I have given up on Javascript and how to make good Coffee or visit CoffeeScript homepage.

Lets start with simple HTML5 template including div with set id that will work as our container for canvas element:

<!DOCTYPE html>
  <head>
    <!-- Included needed dependencies -->
    <script src='http://code.jquery.com/jquery-1.9.0.min.js'></script>
    <script src='http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.3.2.min.js'></script>
    <!-- I will use the browser CoffeeScript compiler. In production compile it to JavaScript -->
    <script src='http://jashkenas.github.com/coffee-script/extras/coffee-script.js'></script>
  </head>
  <body style='background: #8bd3fc;'>
    <div id="chart" style="width:900px; height: 600px; margin: 0 auto;"></div>
    <script type='text/coffeescript'>
      'use strict'
      <!-- Our script goes here -->
    </script>
  </body>
</html>

Chart

Our core class Chart will be responsible for core functionality and handling the JSON data provided to chart via AJAX. Sample data I used you can download from here:

Link to download file - tutorial webdesign

Download sample JSON

class Chart
  constructor: (containerId, height, width) ->
    @stage = new Kinetic.Stage({
      container : containerId
      height    : height
      width     : width
    })

    # I'm gonna create two layers to store parts of 
    # animation separately
    
    # Firstly init the preloader layer
    @preloaderLayer = new Kinetic.Layer({
      height    : height
      width     : width
    })

    # Init the chart layer
    @chartLayer = new Kinetic.Layer({
      height    : height
      width     : width
      opacity   : 0
    })

  # Download JSON data via AJAX
  loadData: (url) =>
    $.ajax({
      url: url,
      dataType: 'json',
      cache: true
    }).complete( (data) => @parseData(data.responseText))
  parseData: (data) =>

$( ->
  new Chart('chart', 600, 900).loadData('json/data.json')
)

Preloader

Downloading the data might take a while, so let’s create Preloader class before our data will be parsed to give user sensation of processing:

class Preloader
  _interval = null

  constructor: (layer) ->
    @layer = layer

    # The preloader will be put into 
    # container for easier maipulation
    @container = new Kinetic.Container({
      height    : 50
      width     : 90
      x         : @layer.getWidth() / 2 - 45
      y         : @layer.getHeight() / 2 - 20
    })
    return @

  initialize: ->
    for i in [0...3]
      # Draw rectangles
      rect = new Kinetic.Rect({
        height  : 10
        width   : 10
        fill    : 'white'
        x       : i * 25
        y       : @container.getHeight() / 2 - 5
        offset  : {x:5, y:5}
      })

      # Add animation to rectangles
      @animate(rect, i)
      @container.add(rect)

    # Add some simple preloader text
    text = new Kinetic.Text({
      text: 'Loading the data...'
      fill: '#4d7a93'
      fontSize: 10
      fontStyle: 'bold'
      fontFamily: 'Arial'
      x: -15
      y: 32
      align: 'center'
    })
    @container.add(text)
    return @

  # Javascript maintains only one Interval per clousure
  # so that animate has to be fired for each object
  animate: (elem, i) ->
    _interval = setInterval( ->
      setTimeout( ->
        elem.transitionTo({
        scale: {
          x: if i==1 then 1.8 else 1.3,
          y: if i==1 then 1.8 else 1.3}
        duration: 0.3
        callback: ->
          elem.transitionTo({
          scale: {x:1, y:1}
          duration: 0.3
          })
        })
      , i*300)
    , 1200)

  addToStage: ->
    @layer.add(@container)
    return @

  removeFromStage: ->
    clearInterval(_interval)

    # After animation we call callback
    # to fire the removal of container
    @container.transitionTo({
      opacity: 0
      duration: 0.7
      callback: =>
        @layer.remove(@container)
    })
    return @

After that we should initialize our Preloader class in constructor of Chart class:

class Chart
  constructor: (containerId, height, width) ->
    ...

    # Init preloader
    @preloader = new Preloader(@preloaderLayer)
      .initialize()
      .addToStage()
    @stage.add(@preloaderLayer)
Preloader preview - kineticjs tutorial - html5 canvas coffeescript

Test the work of preloader on your browser.

We have to extract the data from grabbed JSON and populate them our chart by implementing as in previous post parseData method in Chart class and right after that lets remove our Preloder to prepare the canvas to display the pie chart:

class Chart
  constructor: (containerId, height, width) ->
    ...

  # Download JSON data via AJAX
  loadData: (url) =>
    ...
 
  # Parse downloaded HTML to extract the data
  parseData: (data) =>
    items = JSON.parse(data)
    for item, index in items.items
      # We will create all chart objects in here

    @preloader.removeFromStage()

Pies

Our pies has be a custom shape. To make them canvas syntax has to be used by calling the context of the canvas and drawing arc and lines. it’s important to know that shape will be positioned absolutely to canvas and we have to keep that in mind while positioning custom elements on the layer.

class Pie
  constructor: (@index, @arc, @startArc, @radius, @layer) ->
    # Our pie container positioning the rotation point
    # on center of the pie chart
    @pieContainer = new Kinetic.Container({
      x: @layer.getWidth() / 2 + 130
      y: @layer.getHeight() / 2
      rotation: @startArc
      name: 'pie_'+@index
    })
    return @

  initialize: ->
    that = @
    # Create pie shape by drawing simple custom shape
    # using canvas syntax
    pie = new Kinetic.Shape({
      drawFunc: (canvas) ->
        context = canvas.getContext('2d')
        context.beginPath()
        context.arc(0, 0 ,that.radius, 0, that.arc + 0.003)
        context.lineTo(0, 0)
        context.closePath()
        canvas.fillStroke(@)
      # Color it by value (green<->red)
      fill : 'hsl(' + (100 *that.arc) + ',100%,57%)'
    })
    @pieContainer.add(pie)
    return @

  addToStage: ->
    @layer.add(@pieContainer)
    return @

Next modify Chart class to initialize constructors of the class with proper data and fade in chartLayer.

class Chart
  _startArc = 0

  ...

  # Parse downloaded HTML to extract the data
  parseData: (data) =>
    items = JSON.parse(data)
    for item, index in items.items
      new Pie(index, item.value, _startArc, 3/9*@stage.getHeight(), @chartLayer)
        .initialize()
        .addToStage()
      _startArc += item.value

    @preloader.removeFromStage()
    @stage.add(@chartLayer)
    @chartLayer.transitionTo({
      opacity: 1
      duration: 0.7
    })

To add unusual look to our pie chart we’ll put next to each pie small circle label with percentage information about this particular slice of the circle.

class Pie
  constructor: (@index, @arc, @startArc, @radius, @layer) ->
    ...

    # Separate label container that doesn't
    # mess with rotation props
    @labelContainer = new Kinetic.Container({
      name: 'label_'+@index
    })
    return @

  initialize: ->
    ...

    # Percentage data label
    @label = new Kinetic.Text({
      text: parseFloat((that.arc / (2*Math.PI))*100).toFixed(1)+'%'
      fontSize: 11
      fill: '"#033a59"'
      fontStyle: 'bold'
      fontFamily: 'Arial'
      offset: [10,5]
      x: Math.cos(that.startArc+that.arc / 2)*280 + (that.layer.getWidth() / 2) + 130
      y: Math.sin(that.startArc+that.arc / 2)*280 + (that.layer.getHeight() / 2)
    })

    # Label will be put into small cicle next to pie
    @labelCircle = new Kinetic.Circle({
      radius: 20
      fill : 'rgba(255,255,255,0.6)'
      offset: [-2,0]
      x: Math.cos(that.startArc+that.arc / 2)*280 + (that.layer.getWidth() / 2) + 130
      y: Math.sin(that.startArc+that.arc / 2)*280 + (that.layer.getHeight() / 2)
    })

    @labelContainer.add(@labelCircle)
    @labelContainer.add(@label)
    return @

  addToStage: ->
    @layer.add(@pieContainer)
    @layer.add(@labelContainer)
    return @

Now you can open the browser and test if pie with label appears after hiding the preloader.

createjs pie chart labels tutorial

After loading the data pie appears on the screen.

Countries table

Cell class is not exactly table, because I store only the separate cells to render them with 2 pixel offset, but colored the same way as pies intrigue and introduce to interaction.
It contains built in Rect class rendering rectangles, text and another Rect as decor on left side.

class Cell
  # Some constants to easier manipulate data
  _cellHeight = 20
  _cellWidth = 200
  _offsetTop = 85

  constructor: (@index,@desc, @arc, @startArc, @layer) ->
    @cellContainer = new Kinetic.Container()

  initialize: ->
    that = @
    # Cell background
    @cell = new Kinetic.Rect({
      width: _cellWidth
      height: _cellHeight
      fill :  if @index%2 then 'rgba(255,255,255,0.5)' else 'rgba(255,255,255,0.7)'
      x: 50
      y: @index*(_cellHeight+2) + _offsetTop
    })

    # Simple decor on the left
    # colored the same as pie
    @cellDecor = new Kinetic.Rect({
      width: 10
      height: _cellHeight
      fill :  'hsl(' + (100 *that.arc) + ',100%,57%)'
      x: 50
      y: @index*(_cellHeight+2)+ _offsetTop
    })

    # Text of the cell
    @cellText = new Kinetic.Text({
      text: that.desc
      fontSize: 10
      fill: '#033a59'
      fontStyle: 'bold'
      fontFamily: 'Arial'
      offset: [10,5]
      x: 80
      y: @index*(_cellHeight+2) + _offsetTop + _cellHeight/2
    })

    @cellContainer.add(@cell)
    @cellContainer.add(@cellDecor)
    @cellContainer.add(@cellText)
    return @

  addToStage: ->
    @layer.add(@cellContainer)

We should also initialize them in parseData method within Chart class:

class Chart
  ...
  
  # Parse downloaded HTML to extract the data
  parseData: (data) =>
    items = JSON.parse(data)
    for item, index in items.items
      ...

      new Cell(index,item.desc, item.value, _startArc, @chartLayer)
        .initialize()
        .addToStage()
      _startArc += item.value

    @preloader.removeFromStage()
    @stage.add(@chartLayer)
    @chartLayer.transitionTo({
      opacity: 1
      duration: 0.7
    })

Connecting lines

The last visual elements are be connectors between table cells and centers of the pies. They contain Polyline class and Circle. Most complex part here is positioning functions that take care of proper placements points of the polyline, but I think the code is self-explaining.

class Cell
  ...

  constructor: (@index,@desc, @arc, @startArc, @layer) ->
    ...

    # Polyline connecting cell with pie
    @cellLine = new Kinetic.Line({
      # Array of points stored in x,y pairs
      points: [
        # Draw stright line 50px
        _cellWidth+50, @index*(_cellHeight+2) + _offsetTop + _cellHeight/2,
        _cellWidth+100, @index*(_cellHeight+2) + _offsetTop + _cellHeight/2
        # Angeled line
        _cellWidth+150, Math.sin(that.startArc+that.arc / 2)*140 + (that.layer.getHeight() / 2)
        # Taget ceneter of pie
        Math.cos(that.startArc+that.arc / 2)*140 + (that.layer.getWidth() / 2) + 130, Math.sin(that.startArc+that.arc / 2)*140 + (that.layer.getHeight() / 2)
      ]
      stroke: '#fff'
      strokeWidth: 2
    })

    # Simple circle decor on the center of
    # pie and end of polyline
    @lineDecor = new Kinetic.Circle({
      fill :  '#fff'
      radius: 6
      x: Math.cos(that.startArc+that.arc / 2)*140 + (that.layer.getWidth() / 2) + 130
      y: Math.sin(that.startArc+that.arc / 2)*140 + (that.layer.getHeight() / 2)
    })

    ...
    # Its important to add them to chartLayer over everything else 
    @layer.add(@cellLine)
    @layer.add(@lineDecor)
    return @

Run your browser now and see the effects. It will looks a bit messy but I am gonna fix that by adding interactivity to the chart.

createjs pie table chart labels tutorial

Our table is finished.

Animations

Our interactivity will rely on two mouse events provided by KineticJS library mouseenter and mouseleave bouned to countries table’s cells.
But before that we have to prepare our classes by modifying opacity of some elements:

class Pie
  constructor: (@index, @arc, @startArc, @radius, @layer) ->
    @pieContainer = new Kinetic.Container({
     ...

     opacity: 0.8
    })
    
   @labelContainer = new Kinetic.Container({
      ...
      
    opacity: 0
    })
    return @

class Cell
  ...

  initialize: ->
    ...

    @cellDecor = new Kinetic.Rect({
      ...

      opacity: 0.6
    })

    ...

    @cellLine = new Kinetic.Line({
      ...

      opacity: 0
    })

    @lineDecor = new Kinetic.Circle({
      ...

      opacity: 0      
    })

Now we can bind our animation to addToStage method of Cell class:

class Cell
  ...

  addToStage: ->
    ...

    @cellContainer.on('mouseenter', =>
      # Set cursor to hand
      document.body.style.cursor = "pointer"
      # Fade in decors and line connector
      @cellDecor.transitionTo({
        duration: 0.1
        opacity: 1
      })
      @cellLine.transitionTo({
        duration: 0.1
        opacity: 1
      })
      @lineDecor.transitionTo({
        duration: 0.1
        opacity: 1
      })
      # Find label container by name and fade it in
      labelContainer = @layer.getStage().get('.label_'+@index)
      labelContainer.apply('transitionTo',{
        duration: 0.2
        opacity: 1
      })
      # Find pie container by name, fade in and scale it to stand out
      pieContainer = @layer.getStage().get('.pie_'+@index)
      pieContainer.apply('transitionTo',{
        scale: {x: 1.25, y: 1.25}
        duration: 0.4
        opacity: 1
        easing: 'ease-out'
      })
    )

    @cellContainer.on('mouseleave', =>
      # Restore cursor to default
      document.body.style.cursor = "default"
      # Fade out decors and polyline and pie label
      @cellDecor.transitionTo({
        duration: 0.1
        opacity: 0.6
      })
      @cellLine.transitionTo({
        duration: 0.1
        opacity: 0
      })
      @lineDecor.transitionTo({
        duration: 0.1
        opacity: 0
      })
      labelContainer = @layer.getStage().get('.label_'+@index)
      labelContainer.apply('transitionTo',{
        duration: 0.2
        opacity: 0
      })
      # Return pie to its original size and opacity
      pieContainer = @layer.getStage().get('.pie_'+@index)
      pieContainer.apply('transitionTo',{
        scale: {x: 1, y: 1}
        duration: 0.2
        opacity: 0.8
        easing: 'ease-out'
      })
    )
    return @

Ok. There’s nothing left to do. You can enjoy the effects of your work.

createjs pie table chart final tutorial

Finished interactive pie chart.

Conclusions

I really enjoyed defining objects properties the way it is done in Kinetic. Associations array that stores all properties is great idea, but I can not say that about animations. They were purely in JavaScript and especially the one in preloader, where I had to use intervals and timeouts, terrified me. I would expect at least the solution like one provided CreateJS. Elegant and gentle. Saving the horror of dealing with undebuggable code.

But despite that coding with KinteticJS was much easier than with CreateJS, and I don’t know if it was because I spent some time with that kind of libraries or it’s simply much better organised. The decision is up to you. I’m gonna write article comparing them more carefully this week to you better sight at both of them and ActionScript v3 as contrast.

If you read it, you should share it: