Menu

Introduction edit this section

What is Layer Cake?

Layer Cake is a graphics framework for Svelte that removes the boilerplate from making responsive web graphics. it gives you common elements, like a coordinate system and scales, for you to start creating your own dataviz layers, like axes, plots and annotations.

Layer Cake is described as a framework and not a library because unlike Vega or HighCharts, it doesn't automatically create, for example, a scatter chart for you. It gives you the scales and the DOM element to encode chart elements from your data. This is because every chart ends up being custom in one way or another. Other libraries handle this usually by creating a complex JSON specification but learning that is a big investment and often mentally taxing.

The idea behind a Layer Cake chart is you can start from a basic scatter, line or bar chart template but because those chart layers live in your project, you can customize them however you want.

By organizing a graphic into layers, you can more easily reuse components from project to project. It also lets you easily move between web languages (SVG, Canvas, HTML, WebGL) by giving you a common coordinate system they can all use. That way, you can choose the best format for each element without worrying superimposing different elements on top of one another.

Layer Cake is more about having a system to organize your own custom components than it is a high-level charting library. It doesn't have any built-in concepts or strong opinions about how your data should be structured.

Layer Cake uses D3 scales. See more in the xScale, yScale, zScale and rScale sections of the Layer Cake Props API.

Getting started

Install Layer Cake in your devDependencies alongside Svelte.

npm install --save-dev layercake

The easiest way to get started is to clone down, or use degit to grab the starter template at https://github.com/mhkeller/layercake-template.

degit mhkeller/layercake-template my-chart
cd my-chart

The App.svelte file in this example is your main Svelte component. You can render a LayerCake inside a DOM element like so.

js/App.svelte
<script>
  import { LayerCake } from 'layercake';

  // Define some data
  const points = [
    {x: 0, y: 0},
    {x: 5, y: 10},
    {x: 10, y: 20},
    {x: 15, y: 30},
    {x: 20, y: 40}
  ];
</script>

<style>
  .chart-container {
    width: 100%;
    height: 300px;
  }
</style>

<div class="chart-container">
  <LayerCake
    data={ points }
    x='x'
    y='y'
  >
    <!-- Components go here -->
  </LayerCake>
</div>

Each of the chart examples can be downloaded or edited live in the browser by clicking on the "Download" or "Edit" buttons, respectively.

Layout components

Within the LayerCake component, you'll want to add at least one layout component, kind of like a wrapper. It can be Svg, Html, Canvas or WebGL. Within any of these is where you'll put your own custom layer components. Here's an example with a few different layout elements working together.

App.svelte
<script>
  import { LayerCake, Svg, Canvas, Html }  from 'layercake';

  // These are components that live in your project that
  // you can customize as you see fit
  import ScatterCanvas from './components/ScatterCanvas.svelte';
  import AxisX from './components/AxisX.svelte';
  import AxisY from './components/AxisY.svelte';
  import Annotations from './components/Annotations.svelte';

  // Set up some data
  const points = [
    {x: 0, y: 0},
    {x: 5, y: 10},
    {x: 10, y: 20},
    {x: 15, y: 30},
    {x: 20, y: 40}
  ];

  const annotationBlurbs = [
    { x: 10, y: 20, text: 'Look at this value!'}
  ];
</script>

<style>
  .chart-container {
    width: 100%;
    height: 300px;
  }
</style>

<div class="chart-container">
  <LayerCake
    x='x'
    y='y'
    data={points}
  >
    <Canvas>
      <ScatterCanvas fill={'blue'} r={3}/>
    </Canvas>

    <Svg>
      <AxisX/>
      <AxisY/>
    </Svg>

    <Html>
      <Annotations blurbs={annotationBlurbs} />
    </Html>

    <!-- If you wanted to, you could add another <Svg> again... -->
  </LayerCake>
</div>

Layout components have a few their own properties that let you customize behavior. Read more in the Layout Components section.

Layer components

The only components the Layer Cake module exports are LayerCake and those layout components, everything else that actually draws your chart is up to you to create. Inside those layer components you can access the scales and other values derived from your data. You do this with Svelte's getContext function.

Here's an example starting with a similar App.svelte file to the example above. We're creating a scatter chart in SVG.

App.svelte
<script>
  import { LayerCake, Svg } from 'layercake';
  import Scatter from './components/Scatter.svelte';

  const points = [
    {x: 0, y: 0},
    {x: 5, y: 10},
    {x: 10, y: 20},
    {x: 15, y: 30},
    {x: 20, y: 40}
  ];
</script>

<style>
  .chart-container {
    width: 100%;
    height: 300px;
  }
</style>

<div class="chart-container">
  <LayerCake
    x='x'
    y='y'
    data={ points }
  >
    <Svg>
      <!-- You can expose properties on your chart components to make them more reusable -->
      <Scatter fill={'blue'} r={3} />
    </Svg>
  </LayerCake>
</div>

This is what the scatter component looks like:

./components/Scatter.svelte
<script>
  // Import the getContext function from svelte
  import { getContext } from 'svelte';

  // Access the context using the 'LayerCake' keyword
  // Grab some helpful functions
  const { data, x, xScale, y, yScale } = getContext('LayerCake');

  export let fill = '#000';
  export let r = 5;
</script>

<g>
  {#each $data as d}
    <circle cx='{ $xScale($x(d)) }' cy='{ $yScale($y(d)) }' {fill} {r} />
  {/each}
</g>

You could hardcode the radius and fill in the component but exposing those variables to your App makes this component more reusable from project to project. Or, within a project, you could use the same layer component to render different charts of varying color.

A few notes on this component:

  1. Everything that you export from getContext('LayerCake') is a Svelte store so prefix them with $ in the template.
  2. This example is a bit verbose because we're calling our accessor functions and then our scale functions. You can combine these two steps with the built-in xGet and yGet functions. Like so:
./components/Scatter.svelte
<script>
  // Import the getContext function from svelte
  import { getContext } from 'svelte';

  // Access the context using the 'LayerCake' keyword
  // Grab some helpful functions
  const { data, xGet, yGet } = getContext('LayerCake');

  // Customizable defaults
  export let fill = '#000';
  export let r = 5;
</script>

<g>
  {#each $data as d}
    <circle cx='{ $xGet(d) }' cy='{ $yGet(d) }' {fill} {r} />
  {/each}
</g>

Many common chart types have example pages. See the gallery at https://layercake.graphics or use the dropdown menu at the top of the page to navigate to one.

Data-less cakes

You can also use Layer Cake to simply arrange SVG, HTML, Canvas and WebGL elements on top of one another, sharing the same dimensions. For example, this would be handy if you have some SVG artwork that you want to put on top of an HTML video player.

Here's an example that doesn't set any properties on the LayerCake component:

App.Svelte
<script>
  import { LayerCake, Svg, Html } from 'layercake';
  import Frame from './components/Frame.svelte';
  import VideoPlayer from './components/VideoPlayer.svelte';
</script>

<style>
  .chart-container {
    width: 100%;
    width: 300px;
  }
</style>

<div class="chart-container">
  <LayerCake>
    <Svg>
      <Frame/>
    </Svg>

    <Html>
      <VideoPlayer/>
    </Html>
  </LayerCake>
</div>

LayerCake props edit this section

These are the props you can set on the LayerCake component itself. You set them all like so:

<Layercake
  foo='foo'
  bar='bar'
>
</LayerCake>

data Array

A list of data items. If this is not a flat data array of objects, you'll also need to set flatData.

<LayerCake
  data={ myData }
>

x String|Function|Array

The key in each row of data that corresponds to the x-field. This can be a string or an accessor function. This property gets converted to a function when you access it through the context.

<LayerCake
  x='myX'
  <!-- is equivalent to... -->
  x={ d => d.myX }
>

You can also give this value an array of strings or arrays of functions. While it may seem counter-intuitive to have more than one x- or y-accessor, this is the case in stacked layouts and Cleveland dot plots. See the Stacked bar, Stacked area, Stacked colummn or Cleveland dot plot for complete examples.

Here's an overview using the d3.stack() to make a horizontal bar chart, which will have two values for the x-accessor.

const data = [
  {month: new Date(2015, 3, 1), apples: 3840, bananas: 1920, cherries: 960, dates: 400},
  {month: new Date(2015, 2, 1), apples: 1600, bananas: 1440, cherries: 960, dates: 400},
  {month: new Date(2015, 1, 1), apples: 640,  bananas: 960,  cherries: 640, dates: 400},
  {month: new Date(2015, 0, 1), apples: 320,  bananas: 480,  cherries: 640, dates: 400}
];

const stack = d3.stack()
  .keys(['apples', 'bananas', 'cherries', 'dates']);

const series = stack(data);

The data is now an array of values. The month values you can't see because sneakily stashes them as a property on the array, accessible as d.data.

[
  [[   0, 3840], [   0, 1600], [   0,  640], [   0,  320]], // apples
  [[3840, 5760], [1600, 3040], [ 640, 1600], [ 320,  800]], // bananas
  [[5760, 6720], [3040, 4000], [1600, 2240], [ 800, 1440]], // cherries
  [[6720, 7120], [4000, 4400], [2240, 2640], [1440, 1840]]  // dates
]

The x- and y-accessors would then look like this:

<LayerCake
  x={ [0, 1] }
  y={ d => d.data.month }
>

Calls to x(dataRow) in this scenario will return the two-value array. Calls to xGet(dataRow) will return a two-value array, mapped through the xScale.

y String|Function|Array

Same as x but for the y dimension.

z String|Function|Array

Same as x but for the z dimension.

r String|Function|Array

Same as x but for the r dimension.

padding Object

An object that can specify top, right, bottom, or left padding in pixels. Any unspecified values are filled in as 0. Padding operates like CSS box-sizing: border-box; where values are subtracted from the parent container's width and height, the same as a D3 margin convention.

<LayerCake
  padding={ { top: 20, right: 10, bottom: 0, left: 0 } }
  // equivalent to...
  padding={ { top: 20, right: 10 } }
>

Another way to set padding is to add it via normal CSS on your target div. The target element is assigned CSS of box-sizing: border-box; so padding settings won't affect the width or height. If you set any padding via CSS, the padding object will be ignored.

xScale d3.scaleLinear(undefined)

The D3 scale that should be used for the x-dimension. Pass in an instantiated D3 scale if you want to override the default d3.scaleLinear() or you want to add extra options.

See the Column chart for an example of passing in a d3.scaleBand() to override the default.

yScale d3.scaleLinear(undefined)

Same as xScale but for the y scale. The default is d3.scaleLinear().

zScale d3.scaleLinear(undefined)

Same as xScale but for the z scale. The default is d3.scaleLinear().

rScale d3.scaleSqrt(undefined)

Same as xScale but for the r scale. The default is d3.scaleSqrt().

xDomain Array:[min: Number, max: Number]

Set a min or max on the x scale. If you want to inherit the value from the data's extent, set that value to null.

<LayerCake
  xDomain={ [0, 100] } // Fixes the x scale's domain
  // or..
  xDomain={ [0, null] } // Fixes the min but allows the max to be whatever is in the data
>

yDomain Array:[min: Number, max: Number]

Same as xDomain but for the y scale.

zDomain Array:[min: Number, max: Number]

Same as xDomain but for the z scale.

rDomain Array:[min: Number, max: Number]

Same as xDomain but for the r scale.

xReverse Boolean=false

Reverse the default x domain. By default this is false and the domain is [0, width].

This is ignored if you set xRange.

yReverse Boolean=true

Reverse the default y domain. By default this is true and the domain is [height, 0]. Reverse the default x domain. By default this is false and the domain is [0, width].

This is ignored if you set yRange.

zReverse Boolean=false

Reverse the default z domain. By default this is false and the domain is [0, width].

This is ignored if you set zRange.

rReverse Boolean=false

Reverse the default r domain. By default this is false and the domain is [1, 25].

This is ignored if you set rRange.

xRange Function|Array:[min: Number, max: Number]

Override the default y range of [0, width] by setting it here to an array or function with argument ({ width, height}) that returns an array.

This overrides setting xReverse to true.

<LayerCake
  xRange={ [1, 100] }
>

It can also be a function:

<LayerCake
  xRange={ ({ width, height }) => [0, width / 2] }
>

yRange Function|Array:[min: Number, max: Number]

Same as xRange but for the y scale. Override the default y range of [0, height] by setting it here to an array or function with argument ({ width, height}) that returns an array.

This overrides setting yReverse to true.

zRange Function|Array:[min: Number, max: Number]

Same as xRange but for the z scale. Override the default z range of [0, width] by setting it here to an array or function with argument ({ width, height}) that returns an array.

This overrides setting zReverse to true.

rRange Function|Array:[min: Number, max: Number]

Same as xRange but for the r scale. Override the default y range of [1, 25] by setting it here to an array or function with argument ({ width, height}) that returns an array. The r scale defaults to d3.scaleSqrt so make sure you don't use a zero in your range.

This overrides setting rReverse to true.

xPadding Array:[leftPixels: Number, rightPixels: Number]

Assign a pixel value to add to the min or max of the x scale. This will increase the scales domain by the scale unit equivalent of the provided pixels. It uses D3 scale's invert function, so this only applies to continuous scales like scaleLinear. This is useful for adding extra space to a scatter plot so that your circles don't interfere with your y axis.

<LayerCake
  xPadding= { [10, 10] } // Add ten pixels of data units to both sides of the scale's domain
>

yPadding Array:[leftPixels: Number, rightPixels: Number]

Same as xPadding but for the y domain.

zPadding Array:[leftPixels: Number, rightPixels: Number]

Same as xPadding but for the z domain.

rPadding Array:[leftPixels: Number, rightPixels: Number]

Same as xPadding but for the r domain.

xNice Boolean=false

Applies D3's scale.nice() to the x domain. This is a separate option instead of being one you can apply to a passed in scale because D3's "nice" transformation only works on existing domains and does not use a state to be able to tell if your existing scale wants to be nice.

yNice Boolean=false

Same as xNice but for the y domain.

zNice Boolean=false

Same as xNice but for the z domain.

rNice Boolean=false

Same as xNice but for the r domain.

extents Object

Manually set the extents of the x, y or r scale as a two-dimensional array of the min and max you want. Setting values here will skip any dynamic extent calculation of the data for that dimension.

<LayerCake
  extents={{ x: [0, 100], y: [50, 100] }}
>

flatData Array

In order for Layer Cake to measure the extents of your data, it needs a flat array of items that the x, y and r accessors can find. If your data is not flat (often the case if your renderers prefer a nested format), you can tell it to measure extents against a flat version. This will not change the shape of the data that gets passed to components — it is only for extent calculation.

The library also exports a flattening function to handle common use cases if you need to flatten your data and you don't already have a flat version. See the flatten helper function for more info.

Here's an example showing passing different data formats for extent calculation versus what is used by layer components.

<script>
  import { LayerCake } from 'LayerCake';

  const data = [
    {
      key: 'apples',
      values: [{month: '2015-03-01', value: 3840}, ...]
    },
    {
      key: 'bananas',
      values: [{month: '2015-03-01', value: 1920}, ...]
    },
  ];

  const flatData = [
    {month: '2015-03-01', value: 3840, group: 'apples'},
    {month: '2015-02-01', value: 1600, group: 'apples'},
    {month: '2015-01-01', value: 640, group:  'apples'},
    {month: '2015-00-01', value: 320, group:  'apples'},

    {month: '2015-03-01', value: 1920, group: 'bananas'},
    {month: '2015-02-01', value: 1440, group: 'bananas'},
    {month: '2015-01-01', value: 960, group:  'bananas'},
    {month: '2015-00-01', value: 480, group:  'bananas'}
  ];
</script>

<style>
  .chart-container {
    width: 100%;
    height: 300px;
  }
</style>

<div class="chart-container">
  <LayerCake
    x='month'
    y='value'
    {data}
    {flatData}
  >
    <!-- Components go here -->
  </LayerCake>
</div>

ssr Boolean=false

Set whether this chart should be rendered server side. This is best used in conjunction with the ScaledSvg component or HTML components that are set to use percentage scales since you won't know the size of the container at render time.

Use it in conjunction with percentRange={true} to easily set up your scales for a percent coordinate systems.

percentRange Boolean=false

When rendering charts server side, you pretty much always want your scale range to be [0, 100] since you won't be able to base the range off of the target container's width. Use this convenience helper to set the min and max to be out of a hundred.

The default range for the y-scale will be [100, 0] because yReverse defaults to true. All of the range reverse functions will work as usual with this.

position String='relative'

Determine the positioning of the wrapper div. Set this to 'absolute' when you want to stack two <LayerCake> components on top of one another, such as when you have one that is server-side rendered and one client side like in the Annotated column example.

custom Object

Any extra configuration values you want available on the LayerCake context. This could be useful for color lookups or additional constants.

<LayerCake
  custom={ { size: 10, names: ['a', 'b', 'c'] } }
>

Computed context values edit this section

In addition to the values you set on the LayerCake component, additional properties are computed and exposed on the context.

activeGetters Array

A list containing an object for each key that is set. This used internally but it's exposed here in case it's useful.

[
  { field: 'x', accessor: '<function>' },
  { field: 'y', accessor: '<function>' },
  { field: 'z', accessor: '<function>' },
  { field: 'r', accessor: '<function>' }
]

aspectRatio Number

The aspect ratio of the chart, width / height. This is also exposed as a variable on the Layer Cake slot so you can access it with let:aspectRatio. For example, you could use it to selectively display some components over others:

<LayerCake
  let:aspectRatio
>
  {#if aspectRatio > 1}
    <LayoutOne/>
  {:else}
    <LayoutTwo/>
  {/if}
</LayerCake>

box Object

A bounding box object of the parent container with top, right, bottom, left, width and height numbers in pixels. Useful for creating tooltips.

config Object

A copy of some of the config properties set on the <LayerCake> component.

Some of these properties get changed by the time they end up on the context object. For example, the x, y, z and r properties can be strings or arrays but when they're exposed on the context, they are always a function. Sometimes, it's useful to refer to those original props such as in the Cleveland Dot Plot example, which uses the x accessor shorthand of providing a list of keys.

Or, xDomain and the other domain props can be used to set manual limits on the min or max of the domain scale. This can be different from what gets set on the context xDomain if the prop value contained any null values. If you want to refer to the original value for any reason, it's set on this object.

Having access to this field can help you not repeat yourself in specifying things twice or in scenarios where Layer Cake is doing a transformation on that original value, like in accessors or domain inputs, and you want to know about the original value.

containerWidth Number

The width of the parent container, the div element that contains the <LayerCake> component. This is also exposed as a variable on the Layer Cake slot so you can access it with let:containerWidth.

containerHeight Number

The height of the parent container, the div element that contains the <LayerCake> component. This is also exposed as a variable on the Layer Cake slot so you can access it with let:containerHeight.

extents Object

An object containing a key for x, y or r (if any are set), whose value is two-value array representing the min and max values for that field in the data.

This value could differ from the domain of your scale if you are manually setting a limit on your scale by setting any of the xDomain, yDomain, zDomain or rDomain settings. This is used internally to set domain things but it's also useful as a reference if you want to toggle between an arbitrary domain and the measured extents of the data, such as in the small multiples example.

{
  x: [0, 235],
  y: [0, 80],
  z: [0, 90],
  r: [0, 35]
}

width Number

The width of the drawable space for the chart. This is the width of the parent container taking into account any margin. This is also exposed as a variable on the Layer Cake slot so you can access it with let:width.

height Number

The width of the drawable space for the chart. This is the height of the parent container taking into account any margin. This is also exposed as a variable on the Layer Cake slot so you can access it with let:height.

x Function

The x accessor. This will always be a function regardless of whether you passed in a string or an array as a prop. If you passed in an array, it will return an array of equal length.

<LayerCake
  x='x'
  <!-- equivalent to -->
  x={ d => d.x }
>
<script>
  import { getContext } from 'svelte';
  const { data, x, y } = getContext('LayerCake');
</script>

{#each $data as d}
  <circle
    cx="{$xScale($x(d))}"
    cy="{$yScale($y(d))}"
  />
{/each}

y Function

Same as x but for the y dimension.

z Function

Same as x but for the z dimension.

r Function

Same as x but for the r dimension.

xDomain Array:[min: Number, max: Number]

The calculated extent of the x-dimension of the data. This is the extent of the data taking into account any manual settings passed in for xDomain.

For example, if the extent of the data is [10, 100] and you set the xDomain prop to [0, null], the xDomain on the context value is [0, 100].

It's equivalent to calling $xScale.domain().

yDomain Array:[min: Number, max: Number]

Same as xDomain above but for the y domain.

zDomain Array:[min: Number, max: Number]

Same as xDomain above but for the z domain.

rDomain Array:[min: Number, max: Number]

Same as xDomain above but for the r domain.

xRange Array:[min: Number, max: Number]

The range used for the x-scale. This is usually [0, width] unless it's been manually set via the xRange prop.

It's equivalent to calling $xScale.range().

yRange Array:[min: Number, max: Number]

Same as xRange above but for the y domain.

zRange Array:[min: Number, max: Number]

Same as xRange above but for the z domain.

rRange Array:[min: Number, max: Number]

Same as xRange above but for the r domain.

xGet(d: Object)

Often you want to get the x value from a row in your data and scale it like so: $xScale($x(d)). This function is shorthand for doing just that. Super handy!

Here's an example from a simple SVG line path generator:

import { getContext } from 'svelte';

const { data, xGet, yGet } = getContext('LayerCake');

$: path=  'M' + $data
  .map(d => {
    return $xGet(d) + ',' + $yGet(d);
  })
  .join('L');

yGet(d: Object)

Same as xGet but for the y scale.

zGet(d: Object)

Same as xGet but for the z scale.

rGet(d: Object)

Same as xGet but for the r scale.

xScale(d: Object)

The calculated scale for the x dimension.

yScale(d: Object)

Same as the above but for the y dimension.

zScale(d: Object)

Same as the above but for the z dimension.

rScale(d: Object)

Same as the above but for the r dimension.

Layout component props edit this section

Layer Cake comes with layout components that provide HTML, Svg, ScaledSvg, Canvas and WebGL containers for your custom components.

You must wrap your chart components in these layout components for them to appear properly scaled. For Html and Svg components, they create a <div> and <g>, respectively, that is the main element that accounts for any applied margin.

The Canvas and WebGL layout components do the same and also create the canvas contexts that are then available on the LayerCake context object.

All layout components take the following props:

The Svg and ScaledSvg layout components also accept:

And ScaledSvg additionally accepts:

The WebGL Component accepts:

zIndex Number|String

This lets you fine-tune your layering and is useful if you want your layers to build in a certain order but have a different appearance than their DOM order.

<LayerCake ...>
  <Svg
    zIndex=2
  >
  </Svg>
</LayerCake>

pointerEvents Boolean

Useful for tooltip layers that need to be display above chart elements but not capture mouse events. Defaults to no pointer-events CSS being set. Set to false to set pointer-events: none;

<LayerCake ...>
  <Html
    pointerEvents={false}
  >
  </Html>
</LayerCake>

viewBox String

On Svg components, this defaults to undefined and 0 0 100 100 for ScaledSvg.

<LayerCake ...>
  <Svg
    viewBox='0 0 100 50'
  >
  </Svg>
</LayerCake>

fixedAspectRatio Number

For ScaledSvg components, you can pass in a set aspect ratio. See the server-side rendered Map for an example.

<LayerCake ...>
  <ScaledSvg
    fixedAspectRatio={16/9}
  >
  </ScaledSvg>
</LayerCake>

contextAttributes Object

For WebGL components, you can pass in an object that gets passed as the second argument to canvas.getContext(). See the WebGL docs for more details on what those attributes can be.

<LayerCake ...>
  <WebGL
    contextAttributes={{
      antialias: false,
      depth: false,
      alpha: false
    }}
  >
  </WebGL>
</LayerCake>

Layout components edit this section

Here are examples using the four layout components: HTML, Svg, ScaledSvg, Canvas and WebGL containers.

Html

App.svelte
<script>
  import { LayerCake, Html } from 'layercake';
</script>

<style>
  .chart-container {
    width: 100%;
    height: 300px;
  }
</style>

<div class="chart-container">
  <LayerCake ...>
    <Html zIndex={1}> <!-- Optional z-index -->
      ...
    </Html>
  </LayerCake>
</div>

Svg

App.svelte
<script>
  import { LayerCake, Svg } from 'layercake';
</script>

<style>
  .chart-container {
    width: 100%;
    height: 300px;
  }
</style>

<div class="chart-container">
  <LayerCake ...>
    <Svg zIndex={2}> <!-- Optional z-index -->
    </Svg>
  </LayerCake>
</div>

ScaledSvg

Use this when you want to render SVGs server side, using Rich Harris's Pancake technique.

It's often used in conjunction with props ssr={true} and percentRange={true}.

App.svelte
<script>
  import { LayerCake, ScaledSvg } from 'layercake';
</script>

<style>
  .chart-container {
    width: 100%;
    height: 300px;
  }
</style>

<div class="chart-container"
  <LayerCake
    ssr={true}
    percentRange={true}
  >
    <ScaledSvg
      fixedAspectRatio={16/9} <!-- Optional fixed aspect ratio -->
    >
    </ScaledSvg>
  </LayerCake>
</div>

Canvas

App.svelte
<script>
  import { LayerCake, Canvas } from 'layercake';

  import CanvasLayer from './components/CanvasLayer.svelte'
</script>

<style>
  .chart-container {
    width: 100%;
    height: 300px;
  }
</style>

<div class="chart-container">
  <LayerCake ...>
    <Canvas zIndex={3}> <!-- Optional z-index -->
      <CanvasLayer/>
    </Canvas>
  </LayerCake>
</div>

In the component, you access the canvas context with const { ctx } = getContext('canvas');. This value is on a different context from the getContext('LayerCake') one because you could have multiple canvas layers and there wouldn't be an easy way to grab the right one. This way, the component always has access to just its parent Canvas component.

Since the ctx value is a normal 2d context, the underlying canvas element is accessible under ctx.canvas.

Here's an example showing a scatter plot.

./components/CanvasLayer.svelte
<script>
  import { getContext } from 'svelte';
  import { scaleCanvas } from 'layercake';

  const { data, xGet, yGet, width, height } = getContext('LayerCake');
  const { ctx } = getContext('canvas');

  $: {
    if ($ctx) {
      /* --------------------------------------------
       * If you were to have multiple canvas layers
       * maybe for some artistic layering purposes
       * put these reset functions in the first layer, not each one
       * since they should only run once per update
       */
      scaleCanvas($ctx, $width, $height);
      $ctx.clearRect(0, 0, $width, $height);

      /* --------------------------------------------
       * Draw the scatterplot
       */
      $data.forEach(d => {
        $ctx.beginPath();
        $ctx.arc($xGet(d), $yGet(d), 5, 0, 2 * Math.PI, false);
        $ctx.fillStyle = '#f0c';
        $ctx.fill();
      });
    }
  }
</script>

WebGL

App.svelte
<script>
  import { LayerCake, WebGL } from 'layercake';
</script>

<style>
  .chart-container {
    width: 100%;
    height: 300px;
  }
</style>

<div class="chart-container">
  <LayerCake ...>
    <WebGL zIndex={4}> <!-- Optional z-index -->
    </WebGL>
  </LayerCake>
</div>

In the component, you access the canvas context with const { gl } = getContext('gl');. This value is on a different context from the getContext('LayerCake') one because you could have multiple WebGL layers and there wouldn't be an easy way to grab the right one.

Since the gl value is a normal WebGL context, the underlying canvas element is accessible under gl.canvas.

See the WebGL scatter chart for a working example.

Helper functions edit this section

Layer Cake exposes some commonly-used helper functions. If you don't use them, they will be tree-shaken so there's no added bloat!

flatten(data: Array)

Flatten an array one-level down. Handy for preparing data from stacked layouts whose extents can easily be calculated.

This data:

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

Becomes this:

import { flatten } from 'layercake';

const flatData = flatten(data);
/*
  [{x: 0, y: 1}, {x: 1, y: 5}, {x: 2, y: 10},
   {x: 0, y: 10}, {x: 1, y: 15}, {x: 2, y: 20}]
*/

You can safely use this function on arrays of arrays of arrays, such as the output from d3.stack()

[
  [[   0, 3840], [   0, 1600], [   0,  640], [   0,  320]],
  [[3840, 5760], [1600, 3040], [ 640, 1600], [ 320,  800]],
  [[5760, 6720], [3040, 4000], [1600, 2240], [ 800, 1440]],
  [[6720, 7120], [4000, 4400], [2240, 2640], [1440, 1840]]
]

Becomes...

[ [ 0, 3840 ],
  [ 0, 1600 ],
  [ 0, 640 ],
  [ 0, 320 ],
  [ 3840, 5760 ],
  [ 1600, 3040 ],
  [ 640, 1600 ],
  [ 320, 800 ],
  [ 5760, 6720 ],
  [ 3040, 4000 ],
  [ 1600, 2240 ],
  [ 800, 1440 ],
  [ 6720, 7120 ],
  [ 4000, 4400 ],
  [ 2240, 2640 ],
  [ 1440, 1840 ]
]

scaleCanvas(ctx: Canvas 2d Context, width: Number, height: Number)

Scale your canvas size to retina screens. This function will modify the canvas, if necessary, and return an object with the new width and height as properties.

Such as in the Scatter canvas example:

Scatter.html
<script>
import { scaleCanvas } from 'layercake';

export default {
  onstate () {
    const { ctx, opts } = this.get();
    const { width, height, xGet, yGet, data, custom } = this.store.get();

    scaleCanvas(ctx, width, height);
    ctx.clearRect(0, 0, width, height);

    data.forEach(d => {
      ctx.beginPath();
      ctx.arc(xGet(d), yGet(d), custom.r, 0, 2 * Math.PI, false);
      ctx.fillStyle = opts.color;
      ctx.fill();
    });
  }
};
</script>

calcExtents(flatData: Array, fields: Array)

Calculate the extents of any of the keys specified in fields, which is an array of objects with field and accessor keys, representing the field name (x, y, r) and an accessor function.

For example, calculating the extents for the x and y fields, which are in the data as myX and myY would look like this:

const extents = calcExtents(flatData, [
  {field: 'x', accessor: d => d.myX },
  {field: 'y', accessor: d => d.myY }
]);

console.log(extents);
/*
{
  x: [0, 10],
  y: [-20, 20]
}
*/

Returns an object whose keys are the field names specified as the first item in the key group array followed by an array of [min, max].

You can also return an array if you have an object with keys where each one is more logically associated with the min or the max like this:

const timeData = [{start: 0, end: 1}, {start: -10000, end: 0}];

const extents = calcExtents(timeData, [
  { field: 'y', accessor: d => [d.start, d.end] }
]);

console.log(extents);
/*
{
  y: [-10000, 1]
}
*/

uniques(data: Array[, accessor: String|Function, transform: Boolean])

A function to get the unique values from a list. If accessor is specified, the uniqueness will be compared using that and, by default, the values in the returned list of unique values will be values returned by the accessor. Accessor can also be the string name of the key. Pass false to the transform argument if you want to return the original elements, which will be the first one that appears for every unique value.

This is different from Underscore's uniq because that function doesn't return the transformed value.

import { uniques } from 'layercake';

const data = [
  { year: '1990', x: 0, y: 1},
  { year: '1990', x: 5, y: 4},
  { year: '1991', x: 2, y: 5},
  { year: '1991', x: 6, y: 1},
  { year: '1992', x: 1, y: 6},
  { year: '1992', x: 7, y: 3},
  { year: '1993', x: 7, y: 8},
  { year: '1993', x: 3, y: 2}
];

const uniqueYears = unique(data, 'year');
// ['1990', '1991', '1992', '1993']

// this is equivalent to
const uniqueYears = unique(data, d => d.year);

// setting transform to `false` gives you the full row of the first unique element
const uniqueYears = unique(data, 'year', false);
/*
[
  {year: '1990', x: 0, y: 1},
  {year: '1991', x: 2, y: 5},
  {year: '1992', x: 1, y: 6},
  {year: '1993', x: 7, y: 8}
*/

raise(el: DOM Element)

Adapted from the raise method in d3-selection, this is a convenience function to re-insert the passed in element as the last child of its parent. Equivalent to:

el.parentNode.appendChild(el);

This is useful for hovering over SVG maps so that the hovered-over feature is not obstructed by neighboring shapes. See how it's used in the SVG map component.