Menu

SyncedBrushWrapper.svelte component

Draws a line chart with a brush under it. Drag on the brush to zoom the chart above to that slice of the rows. Bind min and max to keep several of these in step, as the synced brush example does.

Param Default Required Description
min (number | null) $bindable(null)
no
Where the brush starts, as a share of the rows from 0 to 1. Bind to it to sync charts.
max (number | null) $bindable(null)
no
Where the brush ends, as a share of the rows from 0 to 1. Bind to it to sync charts.
xKey string 'x'
no
The field holding each row's x value.
yKey string 'y'
no
The field holding each row's y value.
data Array<Object> []
no
The rows to draw.
stroke string '#00e047'
no
The line's stroke color. The area under it is the same color at low opacity.

Used in these examples:

<!--
  @component
  Draws a line chart with a brush under it. Drag on the brush to zoom the chart above to that slice of the rows. Bind `min` and `max` to keep several of these in step, as the [synced brush example](https://layercake.graphics/example/SyncedBrush) does.
 -->
<script>
  import { LayerCake, Svg, Html } from 'layercake';

  import Line from './Line.svelte';
  import Area from './Area.svelte';
  import AxisX from './AxisX.svelte';
  import AxisY from './AxisY.svelte';
  import Brush from './Brush.html.svelte';

  /**
   * @typedef {Object} Props
   * @property {number|null} [min=null] - Where the brush starts, as a share of the rows from 0 to 1. Bind to it to sync charts.
   * @property {number|null} [max=null] - Where the brush ends, as a share of the rows from 0 to 1. Bind to it to sync charts.
   * @property {string} [xKey='x'] - The field holding each row's x value.
   * @property {string} [yKey='y'] - The field holding each row's y value.
   * @property {Array<Object>} [data=[]] - The rows to draw.
   * @property {string} [stroke='#00e047'] - The line's stroke color. The area under it is the same color at low opacity.
   */

  /** @type {Props} */
  let {
    min = $bindable(null),
    max = $bindable(null),
    xKey = 'x',
    yKey = 'y',
    data = [],
    stroke = '#00e047'
  } = $props();

  // The slice of rows inside the brush, with at least two so the line still draws when the brush is very narrow
  let brushedData = $derived.by(() => {
    const start = Math.max(0, Math.floor((min ?? 0) * data.length));
    const end = Math.min(data.length, Math.ceil((max ?? 1) * data.length));
    const brushed = data.slice(start, end);
    if (brushed.length < 2 && data.length >= 2) {
      return data.slice(start, start + 2);
    }
    return brushed;
  });
</script>

<div class="chart-wrapper">
  <div class="chart-container">
    <LayerCake
      padding={{ bottom: 20, left: 25 }}
      x={xKey}
      y={yKey}
      yDomain={[0, null]}
      data={brushedData}
    >
      <Svg>
        <!-- Whole-number ticks only, and every other one when there are too many -->
        <AxisX
          ticks={ticks => {
            const filtered = ticks.filter(t => t % 1 === 0);
            if (filtered.length > 7) {
              return filtered.filter((t, i) => i % 2 === 0);
            }
            return filtered;
          }}
        />
        <AxisY ticks={2} />
        <Line {stroke} />
        <Area fill={`${stroke}10`} />
      </Svg>
    </LayerCake>
  </div>

  <div class="brush-container">
    <LayerCake padding={{ top: 5 }} x={xKey} y={yKey} yDomain={[0, null]} {data}>
      <Svg>
        <Line {stroke} />
        <Area fill={`${stroke}10`} />
      </Svg>
      <Html>
        <Brush bind:min bind:max />
      </Html>
    </LayerCake>
  </div>
</div>

<style>
  /* Give the wrapper a width and height. LayerCake fills it. */
  .chart-wrapper {
    width: 48%;
    height: 40%;
  }
  .chart-container {
    width: 100%;
    height: 80%;
  }
  .brush-container {
    width: 100%;
    height: 20%;
  }
</style>