@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.

<svg data-interactive="top" style="width:700px;height:400px;border:1px solid #ccc;margin-left:auto;margin-right:auto;display:block;"></svg>

## 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].

```javascript
// 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.

```javascript
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:
```javascript
{
  '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.

```javascript
// 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).

<svg data-interactive="scatter" style="width:700px;height:300px;border:1px solid #ccc;margin-left:auto;margin-right:auto;display:block;"></svg>

`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))` | &gt; 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.

<svg data-interactive="timeSeries" style="width:700px;height:400px;border:1px solid #ccc;margin-left:auto;margin-right:auto;display:block;"></svg>

`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.

<svg data-interactive="boxplot" style="width:700px;height:300px;border:1px solid #ccc;margin-left:auto;margin-right:auto;display:block;"></svg>

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

```javascript
{
  '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.

```javascript
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:

```javascript
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]: https://github.com/wq/wq.app/blob/master/packages/chart
[wq.app module]: https://wq.io/docs/app
[d3.js]: http://d3js.org
[reusable charts]: http://bost.ocks.org/mike/chart/
[AMD]: https://wq.io/docs/amd
[@wq/chart/chartapp]: https://wq.io/docs/chartapp-js
[configurable function]: http://bost.ocks.org/mike/chart/#configuration
[d3 selection]: https://github.com/mbostock/d3/wiki/Selections
[@wq/chart/pandas]: https://wq.io/docs/pandas-js
[Django REST Pandas]: https://github.com/wq/django-rest-pandas
[PandasScatterSerializer]: https://github.com/wq/django-rest-pandas#pandasscatterserializer
[PandasUnstackedSerializer]: https://github.com/wq/django-rest-pandas#pandasunstackedserializer
[PandasBoxplotSerializer]: https://github.com/wq/django-rest-pandas#pandasboxplotserializer
[getter/setter functions]: http://bost.ocks.org/mike/chart/#reconfiguration
[viewBox]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox
[chart.base()]: https://github.com/wq/wq.app/blob/v0.7.1/js/wq/chart.js#L34-L713
[chart.scatter()]: https://github.com/wq/wq.app/blob/v0.7.1/js/wq/chart.js#L715-L886
[chart.timeSeries()]: https://github.com/wq/wq.app/blob/v0.7.1/js/wq/chart.js#L888-L920
[chart.boxplot()]: https://github.com/wq/wq.app/blob/v0.7.1/js/wq/chart.js#L963-L1089


@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/chartapp]: https://github.com/wq/wq.app/blob/master/packages/chart/src/chartapp.js
[@wq/app plugin]: https://wq.io/docs/app-plugins
[@wq/chart]: https://wq.io/docs/chart-js

@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] `DataFrame`s.  @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.

```javascript
// 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:

```bash
,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:
```javascript
[
  {
    '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.:

```bash
,val1,val2
site,SITE1,SITE1,
parameter,PARAM1,PARAM1
date,,
2014-01-01,0.6,0.3
```
Which will be parsed into:

```javascript
[
  {
    '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

```javascript
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()`.

[@wq/chart/pandas]: https://github.com/wq/wq.app/blob/master/packages/chart/src/pandas.js
[AMD]: https://wq.io/docs/amd
[Pandas]: http://pandas.pydata.org
[d3.js]: http://d3js.org/
[wq.app module]: https://wq.io/docs/app
[@wq/chart]: https://wq.io/docs/chart-js
[Django REST Pandas]: https://github.com/wq/django-rest-pandas
