@wq/chart

1.1.2 • Public • Published

@wq/chart

@wq/chart

@wq/chart 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 is typically imported via AMD as chart, though any local variable name can be used. @wq/chart is not a @wq/app plugin by itself, but can easily be integrated with @wq/app via @wq/chart/chartapp.

// 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/chart/pandas 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);

@wq/chart/chartapp

@wq/chart/chartapp

@wq/chart/chartapp.js is a @wq/app plugin providing integration with the @wq/chart API.

FIXME: WIP

@wq/chart/pandas

@wq/chart/pandas

@wq/chart/pandas is a wq.app module providing a simple JavaScript utility to load and parse CSV files generated by Pandas DataFrames. @wq/chart/pandas extends d3.js's built in CSV parser with a more robust way to handle the complex CSV headers Pandas DataFrames can produce. @wq/chart/pandas is primarily intended for use in conjunction with Django REST Pandas.

The typical workflow is something like this:

  1. Generate a CSV file via DataFrame.to_csv() (Django REST Pandas does this automatically)
  2. Load the file over the internet with @wq/chart/pandas
  3. Visualize the resulting data, perhaps with d3.js and/or @wq/chart.

API

@wq/chart/pandas is typically imported via AMD as pandas, though any local variable name can be used.

// myapp.js
define(['d3', 'wq/pandas', ...], function(d3, pandas, ...) {
    pandas.get(...);
});

The pandas module provides two functions: pandas.parse() and pandas.get(). For similarity with d3.csv(), these are also added to the d3 object as d3.pandas.parse() and d3.pandas(), respectively.

parse()

pandas.parse() parses a CSV string with the following structure:

,value,value,value
site,SITE1,SITE2,SITE3
parameter,PARAM1,PARAM1,PARAM2
date,,,
2014-01-01,0.5,0.5,0.2
2014-01-02,0.1,0.5,0.2

Into an array of datasets with the following structure:

[
  {
    'site': 'SITE1',
    'parameter': 'PARAM1',
    'list': [
      {'date': '2014-01-01', 'value': 0.5},
      {'date': '2014-01-02', 'value': 0.1}
    ]
  }
  // etc for SITE2/PARAM1 and SITE3/PARAM2...
]

pandas.parse() also supports multi-valued datasets, e.g.:

,val1,val2
site,SITE1,SITE1,
parameter,PARAM1,PARAM1
date,,
2014-01-01,0.6,0.3

Which will be parsed into:

[
  {
    'site': 'SITE1',
    'parameter': 'PARAM1',
    'list': [
      {'date': '2014-01-01', 'val1': 0.6, 'val2': 0.3}
    ]
  }
]

get()

pandas.get() is a simple wrapper around d3.xhr() to make it easy to load and parse pandas data from an external file or REST service.

Example Usage

define(['d3', 'wq/pandas'], function(d3, pandas) {

pandas.get('/data.csv' render);

function render(error, data) {
    d3.select('svg')
       .selectAll('rect')
       .data(data)
       // ...
}

});

@wq/chart provides some example charts that work well with the output of pandas.parse().

Versions

Current Tags

Version History

Package Sidebar

Install

npm i @wq/chart

Homepage

wq.io/wq.app

Weekly Downloads

4

Version

1.1.2

License

MIT

Unpacked Size

229 kB

Total Files

15

Last publish

Collaborators

  • sheppard