Color Bar
Here’s a ColorBar with tick labels, using the new tick_side prop:
<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}   tick_side={tick_side}   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:
<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"
/> For more control, you can also manually add a ColorBar inside a custom TableInset (which overrides the automatic color bar):
<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 title=top
tick=secondary title=top
tick=inside title=bottom
tick=primary title=bottom
tick=secondary title=bottom
tick=inside title=left
tick=primary title=left
tick=secondary title=left
tick=inside title=right
tick=primary title=right
tick=secondary title=right
tick=inside <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()).
<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
<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:
<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'.
<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.
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>