Home

wq/chart.js

wq version: 0.7.8 1.0
Docs > wq.app: Modules

wq/chart.js

wq/chart.js

wq/chart.js is a wq.app module providing reusable charts powered by the excellent d3.js library. Some basic chart types (scatter, timeSeries, boxplot) are included, as well as the ability to create new chart types. Any data source can be used, as long as enough information is provided to understand the structure of the data.

API

wq/chart.js is typically imported via AMD as chart, though any local variable name can be used. wq/chart.js is not a wq/app.js plugin by itself, but can easily be integrated with wq/app.js via wq/chartapp.js.

// myapp.js
define(['wq/chart', ...], function(chart, ...) {
    chart.timeSeries(...);
});

The chart functions each return a configurable function that can be called on a d3 selection that already has data bound to it. By convention, the generated chart function is referred to as plot to differentiate it from the chart module. However, any variable name can be used.

var svg = d3.select('svg#chart');
var plot = chart.timeSeries()
    .width(800)
    .height(300);
svg.datum([dataset]).call(plot);

The dataset in the example above would typically be a JavaScript object of the form:

{
  'id': 'temp-data',
  'label': 'Temperature',
  'units': 'C'
  'list': [
    {'date': '2013-09-26', 'value': 26},
    {'date': '2013-09-27', 'value': 23},
    // ...
  ]
}

The wq/pandas.js module can extract objects of this format from CSV files generated by Django REST Pandas.

Chart Options

The core chart generator (chart.base()) includes a number of setup routines that are utilized by each of the built-in chart types. All chart types inherit the base chart options, and some include additional options unique to each chart. All options have reasonable defaults, most of which can be re-configured using d3-style getter/setter functions.

Options

These options control basic chart formatting and layout.

Option Default Purpose
plot.width(val) 700 Sets the drawing width in pixels (including margins). The <svg> object should generally have the same dimensions and/or viewport.
plot.height(val) 300 Sets the drawing height in pixels (including margins).
plot.outerFill(val) #f3f3f3 Background color for the entire chart (including axes).
plot.innerFill(val) #eee Background color for the actual plot area.
plot.viewBox(val) auto SVG viewBox attribute. Computed based on plot width and height by default. Set to false to disable entirely. Useful for helping ensure chart scales appropriately on different screen sizes.
plot.legend(obj) auto Legend size and position ('right' or 'bottom'). If position is 'right', size refers to the width of the legend, while if position is 'bottom', size refers to the height. The default is to place the legend on the bottom if there are 5 or fewer datasets, and on the right if there are more.
plot.xscale(obj) auto Domain of x values in the dataset. If unset, will be automatically determined from the data and optionally "niced" to a round range. If set, should be an object of the form {'xmin': val, 'xmax': val}.
plot.xscalefn(fn) d3.scale.linear Actual d3 function to use to generate the scale.
plot.xnice(fn) null Function to use to generate a nice scale.
plot.xticks(val) null Explicitly set the number of ticks to use for the x axis.
plot.yscales(obj) auto Domain(s) of y values in the dataset. If unset, will be automatically determined from the data and niced to a round range. If there are multiple datasets with different units, a separate yscale will be computed for each unit. If set, should be an object of the form {unit1: {'ymin': val, 'ymax': val}, unit2: {'ymin': val, 'ymax': val}
plot.yscalefn(fn) d3.scale.linear Actual d3 function to use to generate the y scale(s)
plot.cscale(fn) d3.scale.category20() Color scale to use (one color for each dataset)

Margins

There is also a special method, plot.setMargin(name, margin), that can be used to "reserve" named spaces at the margins of the chart. Unlike other options, setMargin is not a getter/setter function. Instead, it takes two arguments: a unique margin name, and a margin object which should have at least one of left, right, top, or bottom set. The values should be distances measured in pixels. All of the set margins are aggregated by plot.getMargins() to determine the final margins for the chart.

// Reserve 30px at the top for a custom header
plot.setMargin("myheader", {'top': 30});

var allMargins = plot.getMargins();
// allMargins.top == 5 + 30 == 35

Margins for the legend and axes are set automatically using the setMargin() mechanism. The margin names padding, xaxis, yaxis, and legend are reserved for this purpose.

Accessors

Accessors control how the data object is parsed, i.e. how data properties are accessed. Accessors are simple functions that take an object and return a value. Overriding the default accessor makes it possible to chart data structures that are not of the format shown above.

Dataset Accessors

Option Default Purpose
plot.datasets(fn(rootObj)) rootObj.data or rootObj Returns the array of datasets within rootObj. If rootObj is already an array, it can be used directly.
plot.id(fn(dataset)) dataset.id Accesses the unique identifier for the dataset as a whole.
plot.label(fn(dataset)) dataset.label Accesses the label to be shown in the legend for this dataset.
plot.items(fn(dataset)) dataset.list Accessor for the actual data values to be plotted.
plot.yunits(fn(dataset)) dataset.units Units for the dataset y values (determines which y scale will be used).
plot.xunits(fn(dataset)) unset Units for the dataset x values. Defined differently by each chart type.
plot.xmin(fn(dataset)) d3.min(items(dataset),xvalue) Function to determine minimum x value of the dataset.
plot.xmax(fn(dataset)) d3.max(items(dataset),xvalue) Function to determine maximum x value of the dataset.
plot.ymin(fn(dataset)) d3.min(items(dataset),yvalue) Function to determine minimum y value of the dataset.
plot.ymax(fn(dataset)) d3.max(items(dataset),yvalue) Function to determine maximum y value of the dataset.
plot.xset(fn(rootObj)) all unique x values Function to access an array containing all unique x values across all datasets in rootObj. Not meant to be overridden.

Legend Accessors

Option Default Purpose
plot.legendItems(fn(rootObj)) datasets(rootObj) Returns an array of legend items. Can be overridden if there are fewer (or more) legend items than datasets for some reason.
plot.legendItemId(fn(legendItem)) id(legendItem) Returns the unique identifier for a legend item. Can be overridden if this is not the same as the dataset id.
plot.legendItemLabel(fn(legendItem)) label(legendItem) Returns the label for a legend item. Can be overridden if this is not the same as the dataset label.
plot.legendItemShape(fn(legItemId)) "rect" The name of an SVG tag to use for legend items.
plot.legendItemStyle(fn(legItemId)(sel)) plot.rectStyle Returns a function for the given legend item id (legItemId) that can set the appropriate attributes necessary to style the provided d3 selection (sel) which will be an SVG tag of the type specified by legendItemShape.

Accessors for Individual Values

Option Default Purpose
plot.xvalue(fn(d)) unset Accessor for x values of individual data points. Defined differently by each chart type.
plot.xscaled(fn(d)) xscale(xvalue(d)) Convenience function to access an x value and return its scaled equivalent. Not meant to be overridden.
plot.yvalue(fn(d)) unset Accessor for y values of individual data points. Defined differently by each chart type.
plot.yscaled(fn(scaleid)(d)) yscales[scaleid](yvalue(d)) Convenience function to access a function that can take a y value and return its scaled equivalent. (The nested function is needed since there may be more than one y axis). Not meant to be overridden.
plot.translate(fn(scaleid)(d)) "translate(x,y)" Returns a function that can generate a translate() string (for use as a SVG transform value), containing the xscaled and yscaled values for a given data point.
plot.itemid(fn(d)) xvalue(d)+'='+yvalue(d) Accessor for uniquely identifying individual data values.

Scatter plots

chart.scatter() returns a function useful for drawing basic x-y scatter plots and line charts. One or more datasets containing x and y values should be provided. All datasets should have the same units for x values, but can can have different y units if needed. Alternating left and right-side y axes will be created for each unique y unit (so it's best to have no more than two).

chart.scatter() can be used with Django REST Pandas' PandasScatterSerializer.

Default Overrides

chart.scatter() overrides the following base chart defaults:

Option scatter Default
plot.xvalue(fn(d)) d.x
plot.xunits(fn(dataset)) dataset.xunits
plot.yvalue(fn(d)) d.y
plot.yunits(fn(dataset)) dataset.yunits
plot.legendItemShape(fn(legItemId)) Same as pointShape() (see below)
plot.legendItemStyle(fn(legItemId)(sel)) Same as pointStyle() (see below)

Additional Options

chart.scatter() defines these additional options:

Option Default Purpose
plot.drawPointsIf(fn(dataset)) Up to 50 items Whether to draw points for the given dataset. Can be a function returning true if you always want points drawn.
plot.drawLinesIf(fn(dataset)) > 50 items Whether to draw lines for the given dataset. Can be a function returning true if you always want lines drawn. Specified separately from drawPointsIf() in case you want to have both lines and points for some reason.
plot.lineStyle(fn(datasetId)(sel)) Sets stroke with plot.cscale Used when the drawLinesIf() function returns true. Returns a function for the given dataset id that can the appropriate attributes necessary to style the provided d3 selection (sel) which will be an SVG <path>.
plot.pointShape(datasetId) "circle" Used when the drawPointsIf() function returns true. Specifies the shape to use when rendering points for each dataset. This value will also be used for legend items for consistency.
plot.pointStyle(fn(datasetId)(sel)) plot.circleStyle Returns a function for the given dataset id that can set the appropriate attributes necessary to style the provided d3 selection (sel) which will be an SVG tag of the type specified by pointShape.
plot.pointover(fn(datasetId)(d)) Adds highlight Returns a function for the given dataset id that will be called with the data for a point whenever the point is hovered over.
plot.pointout(fn(datasetId)(d)) Removes highlight " " the point is no longer being hovered over.
plot.pointLabel(fn(datasetId)(d)) "{datasetId} at {d.x}: {d.y}" Returns a function for the given dataset id that can generate tooltip labels for data points. These will be added via an SVG <title> tag. Note that the tooltip is distinct from the pointover() functionality even though both appear at the same time.

Time series plots

chart.timeSeries() is a simple extension to chart.scatter() that assumes the x values are times or dates.

chart.timeSeries() can be used with Django REST Pandas' PandasUnstackedSerializer.

Default Overrides

chart.timeSeries() overrides the following scatter chart defaults:

Option timeSeries Default
plot.xvalue(fn(d)) timeFormat.parse(d.date)
plot.yvalue(fn(d)) d.value
plot.xscalefn(fn) d3.time.scale
plot.xnice(fn) d3.time.year

Additional Options

chart.timeSeries() defines one additional option:

Option Default Purpose
plot.timeFormat(val) "%Y-%m-%d" Format string to use to parse time values.

Box & Whisker plots

chart.boxplot() returns a function useful for rendering simple box-and-whisker plots. The quartile data for each box should be precomputed.

The default implementation of chart.boxplot() assumes a dataset with roughly the following structure:

{
  'id': 'temp-data',
  'label': 'Temperature',
  'units': 'C'
  'list': [
    {
      'year': "2013",
      'value-whislo': 3,
      'value-q1', 8
      'value-median': 17,
      'value-q3': 20,
      'value-whishi': 25
    }
    // ...
  ]
}

The x value (year in the above example) is used to define an ordinal scale where each item on the x axis corresponds to a box. Thus, any text or numeric attribute can be defined as the x value, provided that the xvalue accessor is defined.

var plot = chart.boxplot().xvalue(function(d) { return d.year });

chart.boxplot() can be customized by overriding the following accessor methods:

Accessors for Individual Values

Option Default Purpose
plot.prefix(val) "value-" Prefix for boxplot value names
plot.whislo(fn(d)) prefix + "whislo" Accessor for the low whisker value
plot.q1(fn(d)) prefix + "q1" Accessor for the 25% quartile
plot.median(fn(d)) prefix + "median" Accessor for the median
plot.q3(fn(d)) prefix + "q3" Accessor for the 25% quartile
plot.whishi(fn(d)) prefix + "whislo" Accessor for the high whisker value

chart.boxplot() can be used with Django REST Pandas' PandasBoxplotSerializer.

Custom Charts

The base chart provides "hooks" that allow for specifying the chart rendering process before, during, and after each dataset is rendered. Each of the chart types above defines one or more of these functions. They can also be used if you want to define a new chart type or significantly alter the behavior of one of the existing types.

Option Purpose
plot.init(fn(datasets)) Initial chart configuration. If defined, the init function will be passed an array of all of the datasets.
plot.renderBackground(fn(dataset)) Render a background layer for each dataset
plot.render(fn(dataset)) Render the primary layer for each dataset.
plot.wrapup(fn(datasets, opts)) Wrapup routine, useful for drawing e.g. legends. opts will be an object containing computed widths and heights for the actual chart inner and outer drawing areas.
plot.rectStyle(dsid)(sel) Returns a function for the given dataset id (dsid) that can set the appropriate attributes necessary to style the provided d3 selection (sel), which would normally be an SVG <rect> tag.
plot.circleStyle(dsid)(sel) " " <circle> tag.

To define your own chart function generator, you could do something like the following:

function myChart() {
    var plot = chart.base()
        .render(render);

    function render(dataset) {
        var items = plot.items()(dataset);
        d3.select(this)
           .selectAll('g.data')
           .data(items)
           .append('g').attr('class', 'data')
           /* do something cool with d3 */
    }

    return plot;
}

var plot = myChart();
svg.datum([dataset]).call(plot);