Menu

Multiline (html labels + quadtree tooltip)Edit

Jan. 1
Feb. 1
Mar. 1
Apr. 1
0
1k
2k
3k
Apples
Bananas
Cherries
Dates

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 zKey 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' },
      ...
    

We're using a regular JavaScript transform to do steps one and two. See the client-side rendered example for how to do this with Layer Cake's groupLonger transform function.

<script>
  import { LayerCake, ScaledSvg, Html, 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.percent-range.html.svelte';
  import AxisY from './_components/AxisY.percent-range.html.svelte';
  import GroupLabels from './_components/GroupLabels.html.svelte';
  import SharedTooltip from './_components/SharedTooltip.percent-range.html.svelte';

  // This example loads csv data as json using @rollup/plugin-dsv
  import data from './_data/fruit.csv';

  /* --------------------------------------------
   * Set what is our x key to separate it from the other series
   */
  const xKey = 'month';
  const yKey = 'value';
  const zKey = 'fruit';

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

  const parseDate = timeParse('%Y-%m-%d');

  /* --------------------------------------------
   * Create a "long" format that is a grouped series of data points
   * Layer Cake uses this data structure and the key names
   * set in xKey, yKey and zKey to map your data into each scale.
   */
  const dataLong = seriesNames.map(key => {
    return {
      [zKey]: key,
      values: data.map(d => {
        // Put this in a conditional so that we don't recast the data on second render
        d[xKey] = typeof d[xKey] === 'string' ? parseDate(d[xKey]) : d[xKey];
        return {
          [yKey]: +d[key],
          [xKey]: d[xKey],
          [zKey]: key
        };
      })
    };
  });

  const formatLabelX = timeFormat('%b. %e');
  const formatLabelY = d => format(`~s`)(d);
</script>

<style>
  /*
    The wrapper div needs to have an explicit width and height in CSS.
    It can also be a flexbox child or CSS grid element.
    The point being it needs dimensions since the <LayerCake> element will
    expand to fill it.
  */
  .chart-container {
    width: 100%;
    height: 250px;
  }
</style>

<div class="chart-container">
  <LayerCake
    ssr
    percentRange
    padding={{ top: 7, right: 10, bottom: 20, left: 25 }}
    x={xKey}
    y={yKey}
    z={zKey}
    zScale={scaleOrdinal()}
    zRange={seriesColors}
    flatData={flatten(dataLong, 'values')}
    yDomain={[0, null]}
    data={dataLong}
  >
    <Html>
      <AxisX
        gridlines={false}
        ticks={data.map(d => d[xKey]).sort((a, b) => a - b)}
        format={formatLabelX}
        snapLabels
        tickMarks
      />
      <AxisY
        format={formatLabelY}
      />
    </Html>

    <ScaledSvg>
      <MultiLine/>
    </ScaledSvg>

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