Highcharts with Backbone
Highcharts is a pretty excellent charting library. It has a plethora of configuration options which is great for customization, but is quite daunting for simple charts. I will applying some convention over configuration in the examples below mainly to keep things simple.
I use Backbone for many things on the client since I see it as a general purpose tool for structuring your client-side apps (along with AMD modules). The simple idea here is to create Backbone View subclasses representing a the chart types. The data source will of course be a Backbone Model or Collection which keeps things cleanly abstracted.
Let's start with the data.
DataPoint and Series
Having specific classes provides the most flexibility in terms of customizing the options for a given series and/or data point. Highcharts provides a lot of flexibility in how series and data points are defined and customized. Each series can be customized independently which may make having formal classes desirable.
# Nothing special, just a subclass for the naming convenience
class DataPoint extends Backbone.Model
# To get the full series options, the toJSON method can be changed to
# nest the data points
class Series extends Backbone.Collection
model: DataPoint
initialize: (options) ->
# Other series options may be passed in
@options = options
# Override to nest the data points
toJSON: ->
options = _.clone @options
# Nest the data points to match the Highcharts options
options.data = super
return options
# Assuming the model attributes match the Highcharts point options
# calling the toJSON method would be good enough
model = new DataPoint x: 1, y: 20
# Produces { x: 1, y: 20 } which can be used directly in the
# series data array
point = model.toJSON()
# Create a collection with some data points
collection = new Series [...]
# Using Series, the toJSON method will produces options ready to be
# added the chart's series array
series = collection.toJSON()
This provides the greatest flexibility for customization, but the potential overhead for having a model instance for every data point may not make this feasible. TODO do a perf and memory test to measure this.. then write it up =)
To reduce the memory or need for configuration, the Series class could be a Model subclass instead. The options and data would be stored as-is.
Now let's look how to hook it up to a view.
Base Chart View
Chart options only differ very slightly between chart types (e.g. bar, line, scatter), thus we should start with a base view the encapsulates the default options and attach the view's element to the chart.renderTo option.
class Chart extends Backbone.View
defaultOptions:
chart: {}
initialize: (options) ->
@options = $.extend true, {}, @defaultOptions, options
@options.chart.renderTo = @el
render: ->
# Destroy previous chart
if @chart then @chart.destroy()
# Assume model-based series
if @model
@options.series = [@model.toJSON()]
# There are two potential usages of a collection. If the collection
# is a Series instance (as defined above), treat it as a single
# series. Otherwise assume it is a collection of multiple series
else if @collection
if @collection instanceof Series
@options.series = [@collection.toJSON()]
else
@options.series = @collection.toJSON()
@chart = new Highcharts.Chart @options
return @
The above provides the bare minimum in terms of rendering a chart given the model or collection, but currently, if the data changes there the chart will not update.
Highcharts provides various chart methods and series methods for altering the data in the chart and redrawing the chart at will. We can take advantage of the Backbone events to known when this happens.
class DynamicChart extends Chart
initialize: (options) ->
super options
# Bind to collection or model events for knowing when to redraw the chart
if @collection
if @collection instanceof Series
@collection.on 'add', @addPoint
else
@collection.on 'add', @addSeries
addPoint: (collection, model, options) =>
if not @chart then return
idx = collection.indexOf model
@chart.series[idx].addPoint model.toJSON()
addSeries: (collection, model, options) =>
if not @chart then return
@chart.addSeries model.toJSON()
Chart Subclasses
For convenience and readability, we can define subclasses for the supported chart types, with their respective default options in case there are subtle differences with the layout or colors.
class AreaChart extends Chart
defaultOptions:
chart:
type: 'area'
class AreaSplineChart extends Chart
defaultOptions:
chart:
type: 'areaspline'
class BarChart extends Chart
defaultOptions:
chart:
type: 'bar'
class ColumnChart extends Chart
defaultOptions:
chart:
type: 'column'
class LineChart extends Chart
defaultOptions:
chart:
type: 'line'
class PieChart extends Chart
defaultOptions:
chart:
type: 'pie'
class ScatterChart extends Chart
defaultOptions:
chart:
type: 'scatter'
class SplineChart extends Chart
defaultOptions:
chart:
type: 'spline'
This is only the beginning. There are a few other conveniences that can be implemented, such as:
- passing in view options that map to some nested Highcharts configuration
- the ability to pass data directly to the view for one-off charts (no need for a
ModelorCollection) - data parsers that return structures compatible with Highcarts
I will be making a library with this boilerplate code. Stay tuned for an upcoming post!