Color Bar

Here’s a ColorBar with tick labels, using the new tick_side prop:

color_scale=Viridis   tick_side=primary   range=0,1
00.250.50.751
color_scale=Magma   tick_side=secondary   range=100,1631
020040060080010001200140016001800
color_scale=Cividis   tick_side=primary   range=-99.9812,-10
−100−80−60−40−200
<script lang="ts">
  import { ColorBar } from 'matterviz'
</script>

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

You can make fat and skinny bars:

00.20.40.60.81

Viridis
00.51

00.51

<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.svelte now automatically shows a ColorBar when heatmap_values is provided!

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

  let color_scale = $state(`interpolateCividis`)
  let heatmap_key = $state(``)
  let heat_label = $state(``)
  let heatmap_values = $derived(
    heatmap_key ? element_data.map((el) => el[heatmap_key]) : [],
  )
</script>

<form>
  <ColorScaleSelect bind:value={color_scale} minSelect={1} selected={[color_scale]} />
  <PropertySelect bind:key={heatmap_key} bind:value={heat_label} />
</form>

<PeriodicTable
  {heatmap_values}
  style="margin: 2em auto 4em"
  bind:color_scale
  color_bar_props={{ title: heat_label }}
  links="name"
/>
1 H Hydrogen 2 He Helium 3 Li Lithium 4 Be Beryllium 5 B Boron 6 C Carbon 7 N Nitrogen 8 O Oxygen 9 F Fluorine 10 Ne Neon 11 Na Sodium 12 Mg Magnesium 13 Al Aluminium 14 Si Silicon 15 P Phosphorus 16 S Sulfur 17 Cl Chlorine 18 Ar Argon 19 K Potassium 20 Ca Calcium 21 Sc Scandium 22 Ti Titanium 23 V Vanadium 24 Cr Chromium 25 Mn Manganese 26 Fe Iron 27 Co Cobalt 28 Ni Nickel 29 Cu Copper 30 Zn Zinc 31 Ga Gallium 32 Ge Germanium 33 As Arsenic 34 Se Selenium 35 Br Bromine 36 Kr Krypton 37 Rb Rubidium 38 Sr Strontium 39 Y Yttrium 40 Zr Zirconium 41 Nb Niobium 42 Mo Molybdenum 43 Tc Technetium 44 Ru Ruthenium 45 Rh Rhodium 46 Pd Palladium 47 Ag Silver 48 Cd Cadmium 49 In Indium 50 Sn Tin 51 Sb Antimony 52 Te Tellurium 53 I Iodine 54 Xe Xenon 55 Cs Cesium 56 Ba Barium 57 La Lanthanum 58 Ce Cerium 59 Pr Praseodymium 60 Nd Neodymium 61 Pm Promethium 62 Sm Samarium 63 Eu Europium 64 Gd Gadolinium 65 Tb Terbium 66 Dy Dysprosium 67 Ho Holmium 68 Er Erbium 69 Tm Thulium 70 Yb Ytterbium 71 Lu Lutetium 72 Hf Hafnium 73 Ta Tantalum 74 W Tungsten 75 Re Rhenium 76 Os Osmium 77 Ir Iridium 78 Pt Platinum 79 Au Gold 80 Hg Mercury 81 Tl Thallium 82 Pb Lead 83 Bi Bismuth 84 Po Polonium 85 At Astatine 86 Rn Radon 87 Fr Francium 88 Ra Radium 89 Ac Actinium 90 Th Thorium 91 Pa Protactinium 92 U Uranium 93 Np Neptunium 94 Pu Plutonium 95 Am Americium 96 Cm Curium 97 Bk Berkelium 98 Cf Californium 99 Es Einsteinium 100 Fm Fermium 101 Md Mendelevium 102 No Nobelium 103 Lr Lawrencium 104 Rf Rutherfordium 105 Db Dubnium 106 Sg Seaborgium 107 Bh Bohrium 108 Hs Hassium 109 Mt Meitnerium 110 Ds Darmstadtium 111 Rg Roentgenium 112 Cn Copernicum 113 Nh Nihonium 114 Fl Flerovium 115 Mc Moscovium 116 Lv Livermorium 117 Ts Tennessine 118 Og Oganesson
57-71 La-Lu Lanthanides
89-103 Ac-Lr Actinides

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

1 H 1.01
2 He 4
3 Li 6.94
4 Be 9.01
5 B 10.8
6 C 12
7 N 14
8 O 16
9 F 19
10 Ne 20.2
11 Na 23
12 Mg 24.3
13 Al 27
14 Si 28.1
15 P 31
16 S 32.1
17 Cl 35.5
18 Ar 39.9
19 K 39.1
20 Ca 40.1
21 Sc 45
22 Ti 47.9
23 V 50.9
24 Cr 52
25 Mn 54.9
26 Fe 55.8
27 Co 58.9
28 Ni 58.7
29 Cu 63.5
30 Zn 65.4
31 Ga 69.7
32 Ge 72.6
33 As 74.9
34 Se 79
35 Br 79.9
36 Kr 83.8
37 Rb 85.5
38 Sr 87.6
39 Y 88.9
40 Zr 91.2
41 Nb 92.9
42 Mo 96
43 Tc 98
44 Ru 101
45 Rh 103
46 Pd 106
47 Ag 108
48 Cd 112
49 In 115
50 Sn 119
51 Sb 122
52 Te 128
53 I 127
54 Xe 131
55 Cs 133
56 Ba 137
57 La 139
58 Ce 140
59 Pr 141
60 Nd 144
61 Pm 145
62 Sm 150
63 Eu 152
64 Gd 157
65 Tb 159
66 Dy 163
67 Ho 165
68 Er 167
69 Tm 169
70 Yb 173
71 Lu 175
72 Hf 178
73 Ta 181
74 W 184
75 Re 186
76 Os 190
77 Ir 192
78 Pt 195
79 Au 197
80 Hg 201
81 Tl 204
82 Pb 207
83 Bi 209
84 Po 209
85 At 210
86 Rn 222
87 Fr 223
88 Ra 226
89 Ac 227
90 Th 232
91 Pa 231
92 U 238
93 Np 237
94 Pu 244
95 Am 243
96 Cm 247
97 Bk 247
98 Cf 251
99 Es 252
100 Fm 257
101 Md 258
102 No 259
103 Lr 266
104 Rf 267
105 Db 268
106 Sg 269
107 Bh 270
108 Hs 269
109 Mt 278
110 Ds 281
111 Rg 282
112 Cn 285
113 Nh 286
114 Fl 289
115 Mc 289
116 Lv 293
117 Ts 294
118 Og 294
57-71 La-Lu Lanthanides
89-103 Ac-Lr Actinides
<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((el) => el.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
        color_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:

title=top
tick=primary
Label
0510
title=top
tick=secondary
Label
0102030
title=top
tick=inside
Label
10203040
title=bottom
tick=primary
Label
101520
title=bottom
tick=secondary
Label
10203040
title=bottom
tick=inside
Label
20304050
title=left
tick=primary
Label
202224262830
title=left
tick=secondary
Label
20253035404550
title=left
tick=inside
Label
30405060
title=right
tick=primary
Label
303234363840
title=right
tick=secondary
Label
30354045505560
title=right
tick=inside
Label
40506070
<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"
          --cbar-tick-label-color={tick_side === `inside` ? `white` : `currentColor`}
        />
      </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()).

YYYY-MM-DD
2023-11-142024-07-032025-02-19
Month Day
Nov 14Jan 11Mar 09May 06Jul 03Aug 30Oct 27Dec 24Feb 19
Vertical - Mmm DD, YY
Nov 14, '23Mar 09, '24Jul 03, '24Oct 27, '24Feb 19, '25
<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; column; gap: 2em; align-items: center;">
  <ColorBar
    title="YYYY-MM-DD"
    range={date_range}
    tick_format="%Y-%m-%d"
    bar_style="width: 200px; margin-left: 3em;"
    tick_labels={2}
  />

  <ColorBar
    title="Month Day"
    range={date_range}
    bar_style="width: 500px; margin-left: 3em;"
    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

The ColorBar now supports interactive dropdowns for switching properties and color scales. Use property_options with a data_loader for lazy-loading property data, and color_scale_options for color scheme switching.

Switches: 0 | Last load: 0ms

−3−2−1012
<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],
  }

  // Color scale options
  const color_scale_options = [
    { key: `viridis`, label: `Viridis`, scale: `interpolateViridis` },
    { key: `plasma`, label: `Plasma`, scale: `interpolatePlasma` },
    { key: `inferno`, label: `Inferno`, scale: `interpolateInferno` },
    { key: `magma`, label: `Magma`, scale: `interpolateMagma` },
    { key: `cividis`, label: `Cividis`, scale: `interpolateCividis` },
    { key: `turbo`, label: `Turbo`, scale: `interpolateTurbo` },
  ]

  // State
  let selected_property = $state(`formation_energy`)
  let selected_color_scale = $state(`viridis`)
  let current_range = $state(property_ranges.formation_energy)
  let switch_count = $state(0)
  let last_load_time = $state(0)

  // Data loader with simulated delay
  async function data_loader(property_key: string) {
    const start = performance.now()
    // Simulate network delay (200-800ms)
    await new Promise((r) => setTimeout(r, 200 + Math.random() * 600))
    last_load_time = Math.round(performance.now() - start)
    switch_count++

    const opt = property_options.find((o) => o.key === property_key)
    return {
      range: property_ranges[property_key],
      title: opt ? `${opt.label} (${opt.unit})` : property_key,
    }
  }
</script>

<p>
  Switches: {switch_count} | Last load: {last_load_time}ms
</p>

<ColorBar
  title="Formation Energy (eV/atom)"
  range={current_range}
  tick_labels={5}
  {property_options}
  bind:selected_property_key={selected_property}
  {data_loader}
  on_property_change={(key, range) => (current_range = range)}
  {color_scale_options}
  bind:selected_color_scale_key={selected_color_scale}
  --cbar-width="600px"
  --cbar-padding="2em"
/>

Here’s an example with vertical orientation and title on different sides:

−6−4−202
051015
<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_scale_options = [
    { key: `blues`, label: `Blues`, scale: `interpolateBlues` },
    { key: `reds`, label: `Reds`, scale: `interpolateReds` },
    { key: `greens`, label: `Greens`, scale: `interpolateGreens` },
  ]

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

  let prop_left = $state(`energy`)
  let prop_right = $state(`force`)
  let range_left = $state(ranges.energy)
  let range_right = $state(ranges.force)

  async function loader_left(key) {
    await new Promise((r) => setTimeout(r, 300))
    const opt = property_options.find((o) => o.key === key)
    return { range: ranges[key], title: opt?.label }
  }

  async function loader_right(key) {
    await new Promise((r) => setTimeout(r, 300))
    const opt = property_options.find((o) => o.key === key)
    return { range: ranges[key], title: opt?.label }
  }
</script>

<div style="display: flex; gap: 4em; justify-content: center; align-items: center">
  <ColorBar
    title="Energy"
    range={range_left}
    orientation="vertical"
    title_side="left"
    {property_options}
    bind:selected_property_key={prop_left}
    data_loader={loader_left}
    on_property_change={(_, range) => (range_left = range)}
    {color_scale_options}
    bar_style="height: 200px;"
  />

  <ColorBar
    title="Force"
    range={range_right}
    orientation="vertical"
    title_side="right"
    {property_options}
    bind:selected_property_key={prop_right}
    data_loader={loader_right}
    on_property_change={(_, range) => (range_right = range)}
    {color_scale_options}
    bar_style="height: 200px;"
  />
</div>

Large Value Ranges (Linear and Log)

Demonstrating the color bar with large numeric ranges, using both linear and logarithmic scales (scale_type='log'). Log scales require a positive range (both min and max > 0). Scientific notation is used for tick labels via tick_format='.0e'.

Large Linear Range (0 to 1e6)
0200k400k600k800k1M
Large Log Range (1 to 1e9)
1101001k10k100k1M10M100M1G
Vertical Log Range (10 to 1e7)
101001k10k100k1M10M
Vertical Linear Range
(10 to 1e7) with line breaks
02M4M6M8M10M
Small Log Range (0.01 to 100)
0.010.1110100
<script lang="ts">
  import { ColorBar } from 'matterviz'
</script>

<div
  style="display: grid; grid-template-columns: 1fr 1fr; gap: 4em; place-items: center; margin: 2em 0"
>
  <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

The arcsinh scale (scale_type='arcsinh') handles ranges that span both positive and negative values—something log scale cannot do. The gradient smoothly transitions through zero, with symmetric ticks like -1000, -100, -10, 0, 10, 100, 1000.

Scale Type
Symmetric Range (-1000 to 1000)
−1k−100−10−1−0.100.11101001k
Asymmetric Range (-100 to 1000)
−100−10−1−0.100.11101001k
Vertical Arcsinh (-500 to 500)
−500−100−10−1−0.100.1110100500
Near-Zero Focus (-10 to 10)
−10−1−0.100.1110

Try switching to "log" to see it fail on negative ranges. Arcsinh handles all ranges smoothly.

<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: 1fr 1fr; gap: 3em; place-items: center; margin: 1em 0"
>
  <ColorBar
    title="Symmetric Range (-1000 to 1000)"
    range={[-1000, 1000]}
    scale_type={scale_type === `arcsinh` ? { type: `arcsinh`, threshold } : scale_type}
    color_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}
    color_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"
    color_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}
    color_scale="interpolatePiYG"
    tick_labels={5}
    bar_style="width: 350px"
  />
</div>

<p style="font-size: 0.9em; opacity: 0.8; text-align: center">
  Try switching to "log" to see it fail on negative ranges. Arcsinh handles all ranges
  smoothly.
</p>