Archived

Documentation for Layer Cake v10, before Svelte 5 runes. Current docs at layercake.graphics

Menu

Multiline (html labels + quadtree tooltip)

A multiline example with a quadtree tooltip. This is an interesting example because the data exists in a few different structures:

  1. We're loading data from a "wide" format CSV file where each series has its own column name.

    [
      {
        month: 2015-03-31T22:00:00.000Z,
        apples: '3840',
        bananas: '1920',
        cherries: '960',
        dates: '400'
      },
      {
        month: 2015-02-28T23:00:00.000Z,
        apples: '1600',
        bananas: '1440',
        cherries: '960',
        dates: '400'
      },
      ...
    

    We need to first turn this into...

  2. ...a "long" format, where each type of fruit is grouped into its own array and each datapoint is a row. The column name becomes a property on the group whose name we define with the cKey variable.

    [
        {
            "fruit": "apples",
            "values": [
                {
                    "value": 3840,
                    "month": "2015-03-31T22:00:00.000Z",
                    "fruit": "apples"
                },
                {
                    "value": 1600,
                    "month": "2015-02-28T23:00:00.000Z",
                    "fruit": "apples"
                },
                ...
            ]
        },
        {
            "fruit": "bananas",
            "values": [
                {
                    "value": 1920,
                    "month": "2015-03-31T22:00:00.000Z",
                    "fruit": "bananas"
                },
        ...
    
  3. We also need a flat, ungrouped array of objects so that Layer Cake can measure the full data extents. This gets passed to the flatData prop so the scales know the full domain of the data.

    [
      { value: 3840, month: 2015-03-31T22:00:00.000Z, fruit: 'apples' },
      { value: 1600, month: 2015-02-28T23:00:00.000Z, fruit: 'apples' },
      { value: 640, month: 2015-01-31T23:00:00.000Z, fruit: 'apples' },
      { value: 320, month: 2014-12-31T23:00:00.000Z, fruit: 'apples' },
      { value: 1920, month: 2015-03-31T22:00:00.000Z, fruit: 'bananas' },
      ...
    

Layer Cake's groupLonger transform function does steps one and two.

<script>
  import { LayerCake, Svg, Html, groupLonger, flatten } from 'layercake';

  import { scaleOrdinal } from 'd3-scale';
  import { timeParse, timeFormat } from 'd3-time-format';
  import { format } from 'd3-format';

  import MultiLine from './_components/MultiLine.svelte';
  import AxisX from './_components/AxisX.svelte';
  import AxisY from './_components/AxisY.svelte';
  import GroupLabels from './_components/GroupLabels.html.svelte';
  import SharedTooltip from './_components/SharedTooltip.html.svelte';

  // The CSV rows are parsed, and their numbers typed, by @rollup/plugin-dsv. See vite.config.js
  import data from './_data/fruit.csv';

  // Name the x field so it can be told apart from the series fields
  const xKey = 'month';
  const yKey = 'value';
  const cKey = 'fruit';

  const seriesNames = Object.keys(data[0]).filter(d => d !== xKey);
  const seriesColors = ['#ffe4b8', '#ffb3c0', '#ff7ac7', '#ff00cc'];

  // Turn the date strings into Date objects, on copies so the imported rows stay as they are
  const parseDate = timeParse('%Y-%m-%d');
  const rows = data.map(d => ({ ...d, [xKey]: parseDate(d[xKey]) }));

  const formatLabelX = timeFormat('%b. %e');
  const formatLabelY = format('~s');

  // One tick per month in the data, oldest first so the snapped labels sit at the right ends
  const xTicks = rows.map(d => d[xKey]).sort((a, b) => a - b);

  // Reshape the wide rows into one group per series, each with its own list of points
  const groupedData = groupLonger(rows, seriesNames, {
    groupTo: cKey,
    valueTo: yKey
  });
</script>

<div class="chart-container">
  <LayerCake
    padding={{ top: 7, right: 10, bottom: 20, left: 25 }}
    x={xKey}
    y={yKey}
    c={cKey}
    yDomain={[0, null]}
    cScale={scaleOrdinal()}
    cRange={seriesColors}
    flatData={flatten(groupedData, 'values')}
    data={groupedData}
  >
    <Svg>
      <AxisX gridlines={false} ticks={xTicks} format={formatLabelX} snapLabels tickMarks />
      <AxisY ticks={4} format={formatLabelY} />
      <MultiLine />
    </Svg>

    <Html>
      <GroupLabels />
      <SharedTooltip formatTitle={formatLabelX} dataset={rows} />
    </Html>
  </LayerCake>
</div>

<style>
  /* Give the wrapper a width and height. LayerCake fills it. */
  .chart-container {
    width: 100%;
    height: 250px;
  }
</style>