Skip to content

andy-lee-eng/d3fc-webgl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

d3fc-webgl

A collection of components for rendering data series to canvas using WebGL, including points, line, bar, and area.

These can be used to replace the standard d3fc components for rendering to SVG or Canvas. d3fc series components

Main d3fc package

Installing

npm install d3fc-webgl

API Reference

This packages contains a number of D3 components that render various standard series types. They all share a common API, with the typical configuration requiring x and y scales together with a number of value accessors. There are SVG and Canvas versions of each series type, sharing the same configuration properties.

General API

Canvas rendering

The seriesWebglLine component has an API that is identical to the seriesCanvasLine counterpart, except that it requires a canvas context that was initialised for webgl.

const data = [
    {x: 0, y: 0},
    {x: 10, y: 5},
    {x: 20, y: 0}
];

const ctx = canvas.getContext('webgl');

const line = fcWebgl.seriesWebglLine()
    .crossValue(d => d.x)
    .mainValue(d => d.y)
    .xScale(xScale)
    .yScale(yScale)
    .context(ctx);

line(data);

Like the original canvas component, the WebGL components are invoked directly with the supplied data. This causes the component to render itself to the canvas.

Cartesian Chart

The WebGL series components can be rendered on their own, or be supplied as the series to a cartesianChart component. However, since we need to initialise the canvas for webgl, we can't use the original d3fc cartesianChart directly. Instead, we offer a new component that overrides the draw event of cartesianChart to draw to WebGL:

const chart = fcWebgl.cartesian(d3.scaleLinear(), d3.scaleLinear())
  .yDomain([0, 100])
  .xDomain([0, 100])
  .canvasPlotArea(fc.seriesWebglLine());

Decorate Pattern

The WebGL components implement the same decorate pattern as their Canvas counterparts, but since they're not actually doing 2d canvas rendering, it is more limited. You can still set fill and stroke styles as below:

const line = fcWebgl.seriesWebglBar()
  .decorate((context, d) => {
    context.strokeWidth = 2;
    context.strokeStyle = 'red';
    context.fillStyle = 'blue';
  });

For further details, consult the Decorate Pattern documentation.

Orientation

Most of the series renderers support both horizontal and vertical render orientations as specified by the orient property. In order to make it easy to change the orientation of a series, and to avoid redundant and repeated property names, a change in orientation is achieved by transposing the x and y scales.

The following example shows a simple bar series rendered in its default vertical orientation:

const data = [4, 6, 8, 6, 0, 10];

const xScale = d3.scaleLinear()
    .domain([0, data.length])
    .range([0, width]);

const yScale = d3.scaleLinear()
    .domain([0, 10])
    .range([height, 0]);

const ctx = canvas.getContext('webgl');

const barSeries = fcWebgl.seriesWebglBar()
    .xScale(xScale)
    .yScale(yScale)
    .crossValue((_, i) => i)
    .mainValue((d) => d)
    .context(ctx);

barSeries(data);

By setting its orient property to horizontal, the x and y scales are transposed. As a result, the domain for both the x and y scale have to be switched. The following shows the changes required:

const xScale = d3.scaleLinear()
    .domain([0, 10])           // domain changed
    .range([0, width]);

const yScale = d3.scaleLinear()
    .domain([0, data.length])  // domain changed
    .range([height, 0]);

const barSeries = fcWebgl.seriesWebglBar()
    .xScale(xScale)
    .yScale(yScale)
    .orient('horizontal')      // orient property updated
    .crossValue((_, i) => i)
    .mainValue((d) => d)
    .context(ctx);

barSeries(data);

This is part of the motivation behind naming the accessors mainValue and crossValue, rather than an orientation specific xValue / yValue.

Multi series

One series type that is worthy of note is the multi series. This component provides a convenient way to render multiple series, that share scales, to the same SVG or canvas.

The multi series renderers expose a series property which accepts an array of series renderers, together with the standard xScale and yScale properties. The following example shows how a multi series can be used to render both a line and bar series:

// a couple of series - value accessor configuration omitted for clarity
const barSeries = fcWebgl.seriesWebglBar();
const lineSeries = fcWebgl.seriesWebglLine();

const multiSeries = fcWebgl.seriesWebglMulti()
    .xScale(xScale)
    .yScale(yScale)
    .context(ctx)
    .series([barSeries, lineSeries]);

multiSeries(data)

Notice that you do not have to set the xScale, yScale and context properties on each series - they are propagated down from the multi series.

The multi series allows you to combine a range of different series types. If instead you have multiple data series that you want to render using the same series type, e.g. a chart containing multiple lines, the repeat series is an easier way to achieve this.

Note that you cannot mix WebGL and standard Canvas series types in the same multi series.

Auto bandwidth

The bar series has a notion of 'width'. It exposes a bandwidth property where you can supply the width as a value (in the screen coordinate system).

Rather than specify a bandwidth directly, you can adapt a series with the fc.autoBandwidth component, which will either obtain the bandwidth directly from the scale, or compute it based on the distribution of data.

When used with a bandScale, the scale is responsible for computing the width of each band. The fc.autoBandwidth component invokes the bandwidth function on the scale and uses the returned value to set the bandwidth on the series.

var xScale = d3.scaleBand()
    .domain(data.map(d => d.x))
    .rangeRound([0, width]);

var bar = fc.autoBandwidth(fcWebgl.seriesWebglBar())
    .align('left')
    .crossValue(function(d) { return d.x; })
    .mainValue(function(d) { return d.y; });

Notice in the above example that the align property of the bar is set to left, which reflects the band scale coordinate system.

NOTE: The d3 point scale is a special cased band scale that has a zero bandwidth. As a result, if you use the fc.autoBandwidth component in conjunction with a point scale, the series will also have a bandwidth of zero!

When used in conjunction with a linear scale, the fc.autoBandwidth component computes the bar width based on the smallest distance between consecutive datapoints:

var xScale = d3.scaleLinear()
    .domain([0, 10])
    .range([0, width]);

var bar = fc.autoBandwidth(fcWebgl.seriesWebglBar())
    .crossValue(function(d) { return d.x; })
    .mainValue(function(d) { return d.y; })
    .widthFraction(0.5);

The fc.autoBandwidth component, when adapting a series, adds a widthFraction property which determines the fraction of this distance that is used to set the bandwidth.

When using a multi, or repeat series, the fc.autoBandwidth component should be used to adapt the bar series directly, rather than adapting the multi or repeat series.

var bar = fcWebgl.seriesWebglBar()

var line = fcWebgl.seriesWebglLine();

var multi = fcWebgl.seriesWebglMulti()
    .xScale(xScale)
    .yScale(yScale)
    .series([fc.autoBandwidth(bar), line]);

Line

# fcWebgl.seriesWebglLine()

Constructs a new line renderer.

Properties

# seriesLine.crossValue(accessorFunc)
# seriesLine.mainValue(accessorFunc)

If accessorFunc is specified, sets the accessor to the specified function and returns this series. If accessorFunc is not specified, returns the current accessor. The accessorFunc(datum, index) function is called on each item of the data, returning the relevant value for the given accessor. The respective scale is applied to the value returned by the accessor before rendering.

# seriesLine.xScale(scale)
# seriesLine.yScale(scale)

If scale is specified, sets the scale and returns this series. If scale is not specified, returns the current scale.

# seriesLine.orient(orientation)

If orientation is specified, sets the orientation and returns this series. If orientation is not specified, returns the current orientation. The orientation value should be either horizontal (default) or vertical.

# seriesCanvasLine.context(ctx)

If ctx is specified, sets the canvas context and returns this series. If ctx is not specified, returns the current context.

Point

# fcWebgl.seriesWebglPoint()

Constructs a new point series renderer.

Properties

# seriesPoint.crossValue(accessorFunc)
# seriesPoint.mainValue(accessorFunc)

If accessorFunc is specified, sets the accessor to the specified function and returns this series. If accessorFunc is not specified, returns the current accessor. The accessorFunc(datum, index) function is called on each item of the data, returning the relevant value for the given accessor. The respective scale is applied to the value returned by the accessor before rendering.

# seriesPoint.xScale(scale)
# seriesPoint.yScale(scale)

If scale is specified, sets the scale and returns this series. If scale is not specified, returns the current scale.

# seriesPoint.orient(orientation)

If orientation is specified, sets the orientation and returns this series. If orientation is not specified, returns the current orientation. The orientation value should be either horizontal (default) or vertical.

# seriesPoint.type(type)

If type is specified, sets the symbol type to the specified function or symbol type and returns this point series renderer. If type is not specified, returns the current symbol type accessor.

This property is rebound from symbol.type.

# seriesPoint.size(size)

If size is specified, sets the area to the specified function or number and returns this point series renderer. If size is not specified, returns the current size accessor.

This property is rebound from symbol.size.

# seriesCanvasPoint.context(ctx)

If ctx is specified, sets the canvas context and returns this series. If ctx is not specified, returns the current context.

Area

# fc.seriesWebglArea()

Constructs a new area series renderer.

Properties

# seriesArea.crossValue(accessorFunc)
# seriesArea.mainValue(accessorFunc) # seriesArea.baseValue(accessorFunc)

If accessorFunc is specified, sets the accessor to the specified function and returns this series. If accessorFunc is not specified, returns the current accessor. The accessorFunc(datum, index) function is called on each item of the data, returning the relevant value for the given accessor. The respective scale is applied to the value returned by the accessor before rendering.

# seriesArea.orient(orientation)

If orientation is specified, sets the orientation and returns this series. If orientation is not specified, returns the current orientation. The orientation value should be either horizontal (default) or vertical.

# seriesArea.xScale(scale)
# seriesArea.yScale(scale)

If scale is specified, sets the scale and returns this series. If scale is not specified, returns the current scale.

# seriesCanvasArea.context(ctx)

If ctx is specified, sets the canvas context and returns this series. If ctx is not specified, returns the current context.

Bar

# fcWebgl.seriesWebglBar()

Constructs a new bar series renderer.

Properties

# seriesBar.crossValue(accessorFunc)
# seriesBar.mainValue(accessorFunc)
# seriesBar.baseValue(accessorFunc)

If accessorFunc is specified, sets the accessor to the specified function and returns this series. If accessorFunc is not specified, returns the current accessor. The accessorFunc(datum, index) function is called on each item of the data, returning the relevant value for the given accessor. The respective scale is applied to the value returned by the accessor before rendering.

# seriesBar.orient(orientation)

If orientation is specified, sets the orientation and returns this series. If orientation is not specified, returns the current orientation. The orientation value should be either horizontal (default) or vertical.

# seriesBar.xScale(scale)
# seriesBar.yScale(scale)

If scale is specified, sets the scale and returns this series. If scale is not specified, returns the current scale.

# seriesBar.bandwidth(bandwidthFunc)

If bandwidthFunc is specified, sets the bandwidth function and returns this series. If bandwidthFunc is not specified, returns the current bandwidth function.

# seriesCanvasArea.context(ctx)

If ctx is specified, sets the canvas context and returns this series. If ctx is not specified, returns the current context.

Multi

# fcWebgl.seriesWebglMulti()

Constructs a new multi series renderer.

Properties

# seriesMulti.series(seriesArray)

If seriesArray is specified, sets the array of series that this multi series should render and returns this series. If seriesArray is not specified, returns the current array of series.

# seriesMulti.xScale(scale)
# seriesMulti.yScale(scale)

If scale is specified, sets the scale and returns this series. If scale is not specified, returns the current scale.

# seriesMulti.mapping(mappingFunc)

If mappingFunc is specified, sets the mapping function to the specified function, and returns this series. If mappingFunc is not specified, returns the current mapping function.

When rendering the multi-series, the mapping function is invoked once for each of the series supplied via the series property. The purpose of the mapping function is to return the data supplied to each of these series. The default mapping is the identity function, (d) => d, which results in each series being supplied with the same data as the multi-series component.

The mapping function is invoked with the data bound to the multi-series, (data), the index of the current series (index) and the array of series (series). A common pattern for the mapping function is to switch on the series type. For example, a multi-series could be used to render a line series together with an upper bound, indicated by a line annotation. In this case, the following would be a suitable mapping function:

const multi = fcWebgl.seriesWebglMulti()
    .series([line, annotation)
    .mapping((data, index, series) => {
      switch(series[index]) {
        case line:
          return data.line;
        case annotation:
          return data.upperBound;
      }
    });

# seriesMulti.decorate(decorateFunc)

If decorateFunc is specified, sets the decorator function to the specified function, and returns this series. If decorateFunc is not specified, returns the current decorator function.

The decorate function is invoked for each of the associated series.

# seriesMulti.context(ctx)

If ctx is specified, sets the canvas context and returns this series. If ctx is not specified, returns the current context.

Repeat

# fcWebgl.seriesWebglRepeat()

Constructs a new repeat series renderer.

The repeat series is very similar in function to the multi series, both are designed to render multiple series from the same bound data. The repeat series uses the same series type for each data series, e.g. multiple lines series, or multiple area series.

The repeat series expects the data to be presented as an array of arrays. The following example demonstrates how it can be used to render multiple line series:

const data = [
  [1, 3, 4],
  [4, 5, 6]
];

const line = fcWebgl.seriesWebglLine();

const repeatSeries = fcWebgl.seriesWebglRepeat()
    .xScale(xScale)
    .yScale(yScale)
    .context(ctx)
    .series(line);

repeatSeries(data);

The repeat series also exposes an orient property which determines the 'orientation' of the series within the bound data. In the above example, setting orient to horizontal would result in the data being rendered as two series of three points (rather than three series of two points).

Properties

# seriesRepeat.series(series)

If series is specified, sets the series that this repeat series should render and returns this series. If series is not specified, returns the current series.

# seriesRepeat.orient(orientation)

If orientation is specified, sets the orientation and returns this series. If orientation is not specified, returns the current orientation. The orientation value should be either vertical (default) or horizontal.

# seriesRepeat.xScale(scale)
# seriesRepeat.yScale(scale)
# seriesRepeat.decorate(decorateFunc)
# seriesRepeat.context(ctx)

Please refer to the multi series for the documentation of these properties.

Grouped

# fcWebgl.seriesWebglGrouped(adaptedSeries)

Constructs a new grouped series by adapting the given series. This allows the rendering of grouped bars.

The grouped series is responsible for applying a suitable offset, along the cross-axis, to create clustered groups of bars. The grouped series rebinds all of the properties of the adapted series.

The following example shows the construction of a grouped bar series, where the scales and value accessors are configured:

var groupedBar = fcWebgl.seriesWebglGrouped(fcWebgl.seriesWebglBar())
    .xScale(x)
    .yScale(y)
    .crossValue(d => d[0])
    .mainValue(d => d[1]);

Rendering a grouped series requires a nested array of data, the default format expected by the grouped series expects each object in the array to have a values property with the nested data, however, this can be configured by the values accessor.

[
  {
    "key": "Under 5 Years",
    "values": [
      { "x": "AL", "y": 310 },
      { "x": "AK", "y": 52 },
      { "x": "AZ", "y": 515 }
    ]
  },
  {
    "key": "5 to 13 Years",
    "values": [
      { "x": "AL", "y": 552 },
      { "x": "AK", "y": 85 },
      { "x": "AZ", "y": 828 }
    ]
  }
]

The fc.group component from the d3fc-group package gives an easy way to construct this data from CSV / TSV.

With the data in the correct format, the series is rendered just like the other series types:

groupedWebglBar(series);

Properties

# grouped.bandwidth(bandwidthFunc)

If bandwidthFunc is specified, sets the bandwidth function and returns this series. If bandwidthFunc is not specified, returns the current bandwidth function.

# grouped.subPadding(padding)

If padding is specified, sets the sub-padding to the specified value which must be in the range [0, 1]. If padding is not specified, returns the current sub-padding. The sub-padding value determines the padding between the bars within each group.

Stacked

There is not an explicit series type for rendering stacked charts, it is a straightforward task to render stacked series with the existing components. This section illustrates this with a few examples.

The following code demonstrates how to render a stacked bar series to an SVG. Note that the axis configuration is omitted for clarity:

var barSeries = fc.seriesWebglBar()
    .xScale(x)
    .yScale(y)
    .crossValue(d => d.data.State)
    .mainValue(d => d[1])
    .baseValue(d => d[0])
    .context(ctx);

series.forEach(function(s, i) {
    barSeries
        .decorate(function(ctx) {
            ctx.fillStyle = color(i);
        })(s);
});

The d3 stack component is used to stack the data obtained from the d3 CSV parser. The decorate pattern is used to set the color for each bar series.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published