Color Bar

ColorBar with tick labels; compare tick_side:

Interactive example

svelte<script lang="ts">
  import { ColorBar } from 'matterviz'
</script>

{#each [// [scale, tick_side, tick_labels, range, label_text]
  [`interpolateViridis`, `primary`, [0, 0.25, 0.5, 0.75, 1], [0, 1]], [`interpolateMagma`, `secondary`, 10, [100, 1631]], [`interpolateCividis`, `primary`, 4, [-99.9812, -10]]] as [scale, tick_side, tick_labels, range]}
  <ColorBar
    title="scale={scale} &emsp; tick_side={tick_side} &emsp; range={range}"
    {scale}
    {tick_side}
    {tick_labels}
    {range}
    tick_format=".4"
    title_style="padding: 3pt;"
    --cbar-padding="3em"
  />
{/each}

You can make fat and skinny bars:

Interactive example

svelte<script lang="ts">
  import { ColorBar } from 'matterviz'

  const wrapper_style = 'margin: auto;'
</script>

<ColorBar {wrapper_style} bar_style="width: 10em; height: 8pt" />
<br />
<ColorBar
  title="Viridis"
  {wrapper_style}
  bar_style="width: 4em; height: 2em"
  tick_labels={2}
/>
<br />
<ColorBar {wrapper_style} --cbar-width="10em" --cbar-height="2em" tick_labels={3} />
<br />

PeriodicTable shows a ColorBar when heatmap_values is provided.

Interactive example

svelte<script lang="ts">
  import type { D3InterpolateName } from 'matterviz/colors'
  import { element_data, type ChemicalElement } from 'matterviz/element'
  import { ELEM_HEATMAP_LABELS } from 'matterviz/labels'
  import { ColorScaleSelect } from 'matterviz/plot'
  import { PeriodicTable, PropertySelect } from 'matterviz/periodic-table'

  let color_scale = $state<D3InterpolateName>(`interpolateCividis`)
  let heatmap_key = $state<keyof ChemicalElement | null>(`atomic_radius`)
  let heat_label = $derived(
    Object.keys(ELEM_HEATMAP_LABELS).find(
      (label) => ELEM_HEATMAP_LABELS[label] === heatmap_key,
    ),
  )
  let heatmap_values = $derived.by(() => {
    const key = heatmap_key
    return key ? element_data.map((element) => element[key]) : []
  })
</script>

<form>
  <ColorScaleSelect bind:value={color_scale} min_select={1} />
  <PropertySelect bind:key={heatmap_key} />
</form>

<PeriodicTable
  {heatmap_values}
  style="margin: 2em auto 4em"
  bind:color_scale
  color_bar_props={{ title: heat_label }}
  links="name"
/>

For more control, you can also manually add a ColorBar inside a custom TableInset (which overrides the automatic color bar):

Interactive example

svelte<script lang="ts">
  import { element_data } from 'matterviz/element'
  import { ColorBar } from 'matterviz/plot'
  import { PeriodicTable, TableInset } from 'matterviz/periodic-table'

  const heatmap_values = element_data.map((element) => element.atomic_mass)
  const heat_range = [Math.min(...heatmap_values), Math.max(...heatmap_values)]
</script>

<PeriodicTable
  {heatmap_values}
  style="margin: 2em auto"
  color_scale="interpolateInferno"
  color_scale_range={heat_range}
>
  {#snippet inset()}
    <TableInset style="place-items: center; padding: 2em">
      <ColorBar
        scale="interpolateInferno"
        title="Atomic Mass (u)"
        range={heat_range}
        tick_labels={5}
        tick_side="primary"
        --cbar-width="calc(100% - 2em)"
      />
    </TableInset>
  {/snippet}
</PeriodicTable>

Example demonstrating title_side and tick_side interaction:

Interactive example

svelte<script lang="ts">
  import { ColorBar } from 'matterviz'

  const title_sides = [`top`, `bottom`, `left`, `right`]
  const tick_sides = [`primary`, `secondary`, `inside`]
</script>

<section>
  {#each title_sides as title_side, l_idx (title_side)}
    {#each tick_sides as tick_side, t_idx (tick_side)}
      {@const orientation =
        title_side === `top` || title_side === `bottom` ? `horizontal` : `vertical`}
      {@const bar_style =
        orientation === `horizontal`
          ? `width: 150px; height: 20px;`
          : `width: 20px; height: 150px;`}
      {@const num_ticks = l_idx + t_idx + 2}
      {@const current_range = [l_idx * 10, (l_idx + 1) * 10 + t_idx * 20]}
      <div>
        <code>title={title_side}<br />tick={tick_side}</code>
        <ColorBar
          {title_side}
          {tick_side}
          {orientation}
          {bar_style}
          title="Label"
          tick_labels={num_ticks}
          range={current_range}
          --cbar-tick-overlap-offset="10px"
        />
      </div>
    {/each}
  {/each}
</section>

Date/Time Ranges

You can format tick labels for date/time ranges by providing a D3 format string via the tick_format prop. The color bar accepts ranges as milliseconds since the epoch (standard JavaScript Date.getTime()).

Interactive example

svelte<script lang="ts">
  import { ColorBar } from 'matterviz'

  // Example date range (e.g. start and end of 2024)
  const date_range = [
    new Date(2024, 0, 1).getTime(), // Jan 1, 2024
    new Date(2024, 11, 31).getTime(), // Dec 31, 2024
  ]
</script>

<div
  style="display: flex; flex-wrap: wrap; justify-content: center; gap: 2em 4em; align-items: center; padding-inline: 1.5em"
>
  <ColorBar
    title="YYYY-MM-DD"
    range={date_range}
    tick_format="%Y-%m-%d"
    bar_style="width: 200px"
    tick_labels={2}
  />

  <ColorBar
    title="Month Day"
    range={date_range}
    bar_style="width: 500px"
    tick_format="%b %d"
    tick_labels={7}
  />

  <ColorBar
    title="Vertical - Mmm DD, YY"
    range={date_range}
    tick_format="%b %d, '%y"
    tick_labels={4}
    orientation="vertical"
    bar_style="height: 200px;"
  />
</div>

Interactive Property and Color Scale Selection

Use property_options, selected_property_key, and on_property_change for property selection. The caller owns the data: set loading while fetching, then update the selected key and range together when the request succeeds. Palette selection follows the same pattern: on_color_scale_change requests a key, and the caller commits it through selected_color_scale_key. The caller derives scale from that same key so the chart and legend share one color mapping. Palette options only provide keys and labels.

Interactive example

svelte<script lang="ts">
  import { ColorBar } from 'matterviz'

  // Property options with different data ranges
  const property_options = [
    { key: `formation_energy`, label: `Formation Energy`, unit: `eV/atom` },
    { key: `band_gap`, label: `Band Gap`, unit: `eV` },
    { key: `volume`, label: `Volume`, unit: `ų/atom` },
    { key: `density`, label: `Density`, unit: `g/cm³` },
    { key: `bulk_modulus`, label: `Bulk Modulus`, unit: `GPa` },
  ]

  // Simulated data ranges for each property
  const property_ranges = {
    formation_energy: [-2.5, 1.5],
    band_gap: [0, 8],
    volume: [8, 45],
    density: [1.5, 22],
    bulk_modulus: [5, 450],
  }

  const color_scales = {
    viridis: `interpolateViridis`,
    plasma: `interpolatePlasma`,
    inferno: `interpolateInferno`,
    magma: `interpolateMagma`,
    cividis: `interpolateCividis`,
    turbo: `interpolateTurbo`,
  }
  const color_scale_options = Object.keys(color_scales).map((key) => ({
    key,
    label: key[0].toUpperCase() + key.slice(1),
  }))

  // State
  let selected_property = $state(`formation_energy`)
  let selected_color_scale = $state(`viridis`)
  let current_range = $derived(property_ranges[selected_property])
</script>

<ColorBar
  title="Formation Energy (eV/atom)"
  range={current_range}
  tick_labels={5}
  {property_options}
  selected_property_key={selected_property}
  on_property_change={(key) => (selected_property = key)}
  {color_scale_options}
  scale={color_scales[selected_color_scale]}
  selected_color_scale_key={selected_color_scale}
  on_color_scale_change={(key) => (selected_color_scale = key)}
  --cbar-width="600px"
  --cbar-padding="2em"
/>

Vertical orientation with the title on different sides:

Interactive example

svelte<script lang="ts">
  import { ColorBar } from 'matterviz'

  const property_options = [
    { key: `energy`, label: `Energy`, unit: `eV` },
    { key: `force`, label: `Force`, unit: `eV/Å` },
    { key: `stress`, label: `Stress`, unit: `GPa` },
  ]

  const color_scales = {
    blues: `interpolateBlues`,
    reds: `interpolateReds`,
    greens: `interpolateGreens`,
  }
  const color_scale_options = Object.keys(color_scales).map((key) => ({
    key,
    label: key[0].toUpperCase() + key.slice(1),
  }))

  const ranges = {
    energy: [-5, 2],
    force: [0, 15],
    stress: [-100, 100],
  }

  let prop_left = $state(`energy`)
  let prop_right = $state(`force`)
  let scale_left = $state(`blues`)
  let scale_right = $state(`blues`)
  let range_left = $derived(ranges[prop_left])
  let range_right = $derived(ranges[prop_right])
</script>

<div
  style="display: flex; flex-wrap: wrap; gap: 4em; justify-content: center; align-items: center"
>
  <ColorBar
    title="Energy"
    range={range_left}
    orientation="vertical"
    title_side="left"
    {property_options}
    selected_property_key={prop_left}
    on_property_change={(key) => (prop_left = key)}
    {color_scale_options}
    scale={color_scales[scale_left]}
    selected_color_scale_key={scale_left}
    on_color_scale_change={(key) => (scale_left = key)}
    bar_style="height: 200px;"
  />

  <ColorBar
    title="Force"
    range={range_right}
    orientation="vertical"
    title_side="right"
    {property_options}
    selected_property_key={prop_right}
    on_property_change={(key) => (prop_right = key)}
    {color_scale_options}
    scale={color_scales[scale_right]}
    selected_color_scale_key={scale_right}
    on_color_scale_change={(key) => (scale_right = key)}
    bar_style="height: 200px;"
  />
</div>

Large Value Ranges (Linear and Log)

Large numeric ranges on linear and log scales (scale_type='log'). Log needs a positive range (min and max > 0). The linear bar uses SI-prefixed ticks via tick_format='.1s'; bars without tick_format use the default tick formatter.

Interactive example

svelte<script lang="ts">
  import { ColorBar } from 'matterviz'
</script>

<div
  style="display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 20em), 1fr)); gap: 4em; place-items: center; margin: 2em 0; padding-inline: 1em"
>
  <ColorBar
    title="Large Linear Range (0 to 1e6)"
    range={[0, 1e6]}
    tick_labels={5}
    tick_format=".1s"
  />

  <ColorBar
    title="Large Log Range (1 to 1e9)"
    range={[1, 1e9]}
    scale_type="log"
    bar_style="width: 400px"
    tick_labels={10}
  />

  <ColorBar
    title="Vertical Log Range (10 to 1e7)"
    range={[10, 1e7]}
    scale_type="log"
    orientation="vertical"
  />

  <ColorBar
    title="Vertical Linear Range<br>(10 to 1e7) with line breaks"
    title_style="margin: 1em;"
    range={[10, 1e7]}
    orientation="vertical"
  />

  <ColorBar
    title="Small Log Range (0.01 to 100)"
    range={[0.01, 100]}
    scale_type="log"
    tick_format=".3"
    bar_style="width: 400px"
    tick_labels={5}
  />
</div>

Arcsinh Scale: Symmetric Ranges Including Negative Values

Interactive example

svelte<script lang="ts">
  import { ColorBar } from 'matterviz'

  const scale_types = [`linear`, `log`, `arcsinh`]
  let scale_type = $state(`arcsinh`)
  let threshold = $state(1)
</script>

<div style="display: flex; gap: 2em; margin-bottom: 1em; align-items: center; flex-wrap: wrap">
  <fieldset>
    <legend>Scale Type</legend>
    {#each scale_types as scale (scale)}
      <label style="margin-right: 0.5em">
        <input type="radio" bind:group={scale_type} value={scale} />
        {scale}
      </label>
    {/each}
  </fieldset>

  {#if scale_type === `arcsinh`}
    <label>
      Threshold: {threshold}
      <input type="range" bind:value={threshold} min="0.1" max="100" step="0.1" />
    </label>
  {/if}
</div>

<div
  style="display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 20em), 1fr)); gap: 3em; place-items: center; margin: 1em 0; padding-inline: 1em"
>
  <ColorBar
    title="Symmetric Range (-1000 to 1000)"
    range={[-1000, 1000]}
    scale_type={scale_type === `arcsinh` ? { type: `arcsinh`, threshold } : scale_type}
    scale="interpolateRdBu"
    tick_labels={7}
    bar_style="width: 350px"
  />

  <ColorBar
    title="Asymmetric Range (-100 to 1000)"
    range={[-100, 1000]}
    scale_type={scale_type === `arcsinh` ? { type: `arcsinh`, threshold } : scale_type}
    scale="interpolatePuOr"
    tick_labels={6}
    bar_style="width: 350px"
  />

  <ColorBar
    title="Vertical Arcsinh (-500 to 500)"
    range={[-500, 500]}
    scale_type={scale_type === `arcsinh` ? { type: `arcsinh`, threshold } : scale_type}
    orientation="vertical"
    scale="interpolateBrBG"
    bar_style="height: 200px"
  />

  <ColorBar
    title="Near-Zero Focus (-10 to 10)"
    range={[-10, 10]}
    scale_type={scale_type === `arcsinh` ? { type: `arcsinh`, threshold } : scale_type}
    scale="interpolatePiYG"
    tick_labels={5}
    bar_style="width: 350px"
  />
</div>