Heatmap Table

A sortable, colorable data table component with heatmap-style cell coloring, column grouping, drag-and-drop column reordering, and customizable cell rendering.

Basic Usage

A simple table with sortable columns and automatic heatmap coloring based on cell values:

Click column headers to sort
Formula Eabove hull Egap Eform
Fe₂O₃0.002.2−8.5
TiO₂0.003.2−9.8
ZnO0.023.4−3.6
Cu₂O0.052.1−1.7
SiO₂0.008.9−9.1
Al₂O₃0.008.8−17.4
MgO0.007.8−6.2
CaTiO₃0.033.5−16.1
<script>
  import { HeatmapTable } from 'matterviz'

  const data = [
    [`Fe₂O₃`, 0.0, 2.2, -8.5],
    [`TiO₂`, 0.0, 3.2, -9.8],
    [`ZnO`, 0.02, 3.4, -3.6],
    [`Cu₂O`, 0.05, 2.1, -1.7],
    [`SiO₂`, 0.0, 8.9, -9.1],
    [`Al₂O₃`, 0.0, 8.8, -17.4],
    [`MgO`, 0.0, 7.8, -6.2],
    [`CaTiO₃`, 0.03, 3.5, -16.1],
  ].map(([v1, v2, v3, v4]) => ({
    Formula: v1,
    'E<sub>above hull</sub>': v2,
    'E<sub>gap</sub>': v3,
    'E<sub>form</sub>': v4,
  }))

  // deno-fmt-ignore
  const columns = [
    { label: `Formula` },
    { label: `E<sub>above hull</sub>`, better: `lower`, color_scale: `interpolateRdYlGn`, format: `.2f` },
    { label: `E<sub>gap</sub>`, better: `higher`, color_scale: `interpolateViridis`, format: `.1f` },
    { label: `E<sub>form</sub>`, better: `lower`, color_scale: `interpolateBlues`, format: `.1f` },
  ]
</script>

<HeatmapTable
  {data}
  {columns}
  sort_hint={{ text: `Click column headers to sort`, position: `top`, permanent: true }}
  style="margin: 0 auto"
/>

Color Scales and Scale Types

Choose from various D3 color scales and switch between linear and logarithmic scaling. Log scale is useful for properties spanning many orders of magnitude like electrical conductivity:

Material Egap Conductivity κlattice
Silicon1.120.00156150
Copper0.005.96e+7401
Diamond5.471e-132,200
Germanium0.672.260
GaAs1.421e-855
SiC3.260.000001490
GaN3.401e-10130
InP1.351e-768
<script>
  import { HeatmapTable } from 'matterviz'

  const data = [
    [`Silicon`, 1.12, 1.56e-3, 150],
    [`Copper`, 0, 5.96e7, 401],
    [`Diamond`, 5.47, 1e-13, 2200],
    [`Germanium`, 0.67, 2.2, 60],
    [`GaAs`, 1.42, 1e-8, 55],
    [`SiC`, 3.26, 1e-6, 490],
    [`GaN`, 3.4, 1e-10, 130],
    [`InP`, 1.35, 1e-7, 68],
  ].map(([v1, v2, v3, v4]) => ({
    Material: v1,
    'E<sub>gap</sub>': v2,
    Conductivity: v3,
    'κ<sub>lattice</sub>': v4,
  }))

  let scale_type = $state(`log`)
  // deno-fmt-ignore
  const columns = [
    { label: `Material` },
    { label: `E<sub>gap</sub>`, better: `higher`, color_scale: `interpolatePlasma`, format: `.2f`, description: `Band gap (eV)` },
    { label: `Conductivity`, better: `higher`, color_scale: `interpolateYlOrRd`, scale_type, format: `.3~`, description: `Electrical conductivity (S/m)` },
    { label: `κ<sub>lattice</sub>`, better: `higher`, color_scale: `interpolateBlues`, format: `,.0f`, description: `Thermal conductivity (W/m·K)` },
  ]
</script>

<label style="display: block; margin-bottom: 1em">
  Conductivity Scale:
  <select bind:value={scale_type}>
    <option value="linear">Linear</option>
    <option value="log">Logarithmic</option>
  </select>
</label>

<HeatmapTable {data} {columns} style="margin: 0 auto" />

Drag-and-Drop Column Reordering

Columns can be reordered by dragging within the same group. Useful for comparing specific metrics side-by-side:

Drag column headers to reorder. Current order: (default)

Structure MAE RMSE Max Error
Perovskite0.0420.0890.940.31
Spinel0.0380.0760.960.28
Rocksalt0.0510.1020.910.45
Wurtzite0.0290.0580.970.19
Fluorite0.0440.0910.930.33
Pyrite0.0350.0710.950.24
Zincblende0.0310.0630.960.21
Rutile0.0470.0950.920.38
<script>
  import { HeatmapTable } from 'matterviz'

  const data = [
    [`Perovskite`, 0.042, 0.089, 0.94, 0.31],
    [`Spinel`, 0.038, 0.076, 0.96, 0.28],
    [`Rocksalt`, 0.051, 0.102, 0.91, 0.45],
    [`Wurtzite`, 0.029, 0.058, 0.97, 0.19],
    [`Fluorite`, 0.044, 0.091, 0.93, 0.33],
    [`Pyrite`, 0.035, 0.071, 0.95, 0.24],
    [`Zincblende`, 0.031, 0.063, 0.96, 0.21],
    [`Rutile`, 0.047, 0.095, 0.92, 0.38],
  ].map(([v1, v2, v3, v4, v5]) => ({
    Structure: v1,
    MAE: v2,
    RMSE: v3,
    'R²': v4,
    'Max Error': v5,
  }))

  // deno-fmt-ignore
  const columns = [
    { label: `Structure` },
    { label: `MAE`, better: `lower`, color_scale: `interpolateRdYlGn`, format: `.3f` },
    { label: `RMSE`, better: `lower`, color_scale: `interpolateRdYlGn`, format: `.3f` },
    { label: ``, better: `higher`, color_scale: `interpolateViridis`, format: `.2f` },
    { label: `Max Error`, better: `lower`, color_scale: `interpolateOranges`, format: `.2f` },
  ]

  let column_order = $state([])
</script>

<p style="color: var(--text-muted); margin-bottom: 1em">
  Drag column headers to reorder. Current order: {column_order.join(`, `) || `(default)`}
</p>

<HeatmapTable {data} {columns} bind:column_order style="margin: 0 auto" />

Large Table with Scrolling

A comprehensive ML model benchmark comparison with sticky first column. Scroll horizontally to compare models across different datasets:

↔️ Scroll horizontally to see all datasets  |  ↕️ Scroll vertically for all models  |  Model column stays pinned

Model MP JARVIS OQMD AFLOW MC3D GNoME WBM COD ICSD Perovskites
MACE-MP-00.830.910.880.790.920.850.870.900.820.86
CHGNet0.790.880.840.760.890.810.830.860.780.82
M3GNet0.750.840.800.720.850.770.790.820.740.78
ALIGNN0.810.890.860.770.900.830.850.880.800.84
SchNet0.680.760.720.650.770.700.720.750.670.71
DimeNet++0.770.860.820.740.870.790.810.840.760.80
GemNet-T0.800.880.850.760.890.820.840.870.790.83
NequIP0.820.900.870.780.910.840.860.890.810.85
PaiNN0.760.850.810.730.860.780.800.830.750.79
CGCNN0.650.730.690.620.740.670.690.720.640.68
MEGNet0.710.790.750.680.800.730.750.780.700.74
BOWSR0.580.660.620.550.670.600.620.650.570.61
Wrenformer0.730.810.770.700.820.750.770.800.720.76
SevenNet0.840.920.890.800.930.860.880.910.830.87
EquiformerV20.850.930.900.810.940.870.890.920.840.88
Graphormer0.720.800.760.690.810.740.760.790.710.75
TorchMD-NET0.780.870.830.750.880.800.820.850.770.81
SpookyNet0.740.830.790.710.840.760.780.810.730.77
ForceNet0.690.770.730.660.780.710.730.760.680.72
SphereNet0.750.840.800.720.850.770.790.820.740.78
ComENet0.700.780.740.670.790.720.740.770.690.73
EGNN0.660.740.700.630.750.680.700.730.650.69
VisNet0.770.860.820.740.870.790.810.840.760.80
Allegro0.810.890.860.770.900.830.850.880.800.84
SO3krates0.760.850.810.730.860.780.800.830.750.79
MACE-OFF0.860.940.910.820.950.880.900.930.850.89
Orb0.790.880.840.760.890.810.830.860.780.82
FAENet0.670.750.710.640.760.690.710.740.660.70
<script>
  import { HeatmapTable } from 'matterviz'

  const models = [
    [`MACE-MP-0`, 0.83, 0.91, 0.88, 0.79, 0.92, 0.85, 0.87, 0.90, 0.82, 0.86],
    [`CHGNet`, 0.79, 0.88, 0.84, 0.76, 0.89, 0.81, 0.83, 0.86, 0.78, 0.82],
    [`M3GNet`, 0.75, 0.84, 0.80, 0.72, 0.85, 0.77, 0.79, 0.82, 0.74, 0.78],
    [`ALIGNN`, 0.81, 0.89, 0.86, 0.77, 0.90, 0.83, 0.85, 0.88, 0.80, 0.84],
    [`SchNet`, 0.68, 0.76, 0.72, 0.65, 0.77, 0.70, 0.72, 0.75, 0.67, 0.71],
    [`DimeNet++`, 0.77, 0.86, 0.82, 0.74, 0.87, 0.79, 0.81, 0.84, 0.76, 0.80],
    [`GemNet-T`, 0.80, 0.88, 0.85, 0.76, 0.89, 0.82, 0.84, 0.87, 0.79, 0.83],
    [`NequIP`, 0.82, 0.90, 0.87, 0.78, 0.91, 0.84, 0.86, 0.89, 0.81, 0.85],
    [`PaiNN`, 0.76, 0.85, 0.81, 0.73, 0.86, 0.78, 0.80, 0.83, 0.75, 0.79],
    [`CGCNN`, 0.65, 0.73, 0.69, 0.62, 0.74, 0.67, 0.69, 0.72, 0.64, 0.68],
    [`MEGNet`, 0.71, 0.79, 0.75, 0.68, 0.80, 0.73, 0.75, 0.78, 0.70, 0.74],
    [`BOWSR`, 0.58, 0.66, 0.62, 0.55, 0.67, 0.60, 0.62, 0.65, 0.57, 0.61],
    [`Wrenformer`, 0.73, 0.81, 0.77, 0.70, 0.82, 0.75, 0.77, 0.80, 0.72, 0.76],
    [`SevenNet`, 0.84, 0.92, 0.89, 0.80, 0.93, 0.86, 0.88, 0.91, 0.83, 0.87],
    [`EquiformerV2`, 0.85, 0.93, 0.90, 0.81, 0.94, 0.87, 0.89, 0.92, 0.84, 0.88],
    [`Graphormer`, 0.72, 0.80, 0.76, 0.69, 0.81, 0.74, 0.76, 0.79, 0.71, 0.75],
    [`TorchMD-NET`, 0.78, 0.87, 0.83, 0.75, 0.88, 0.80, 0.82, 0.85, 0.77, 0.81],
    [`SpookyNet`, 0.74, 0.83, 0.79, 0.71, 0.84, 0.76, 0.78, 0.81, 0.73, 0.77],
    [`ForceNet`, 0.69, 0.77, 0.73, 0.66, 0.78, 0.71, 0.73, 0.76, 0.68, 0.72],
    [`SphereNet`, 0.75, 0.84, 0.80, 0.72, 0.85, 0.77, 0.79, 0.82, 0.74, 0.78],
    [`ComENet`, 0.70, 0.78, 0.74, 0.67, 0.79, 0.72, 0.74, 0.77, 0.69, 0.73],
    [`EGNN`, 0.66, 0.74, 0.70, 0.63, 0.75, 0.68, 0.70, 0.73, 0.65, 0.69],
    [`VisNet`, 0.77, 0.86, 0.82, 0.74, 0.87, 0.79, 0.81, 0.84, 0.76, 0.80],
    [`Allegro`, 0.81, 0.89, 0.86, 0.77, 0.90, 0.83, 0.85, 0.88, 0.80, 0.84],
    [`SO3krates`, 0.76, 0.85, 0.81, 0.73, 0.86, 0.78, 0.80, 0.83, 0.75, 0.79],
    [`MACE-OFF`, 0.86, 0.94, 0.91, 0.82, 0.95, 0.88, 0.90, 0.93, 0.85, 0.89],
    [`Orb`, 0.79, 0.88, 0.84, 0.76, 0.89, 0.81, 0.83, 0.86, 0.78, 0.82],
    [`FAENet`, 0.67, 0.75, 0.71, 0.64, 0.76, 0.69, 0.71, 0.74, 0.66, 0.70],
  ]

  const benchmarks = `MP JARVIS OQMD AFLOW MC3D GNoME WBM COD ICSD Perovskites`
    .split(` `)

  const data = models.map(([name, ...scores]) => {
    const row = { Model: name }
    benchmarks.forEach((bench, idx) => {
      row[bench] = scores[idx]
    })
    return row
  })

  const columns = [
    { label: `Model`, sticky: true, style: `min-width: 120px; font-weight: 600;` },
    ...benchmarks.map((bench) => ({
      label: bench,
      better: `higher`,
      color_scale: `interpolateViridis`,
      format: `.2f`,
      style: `min-width: 95px;`,
    })),
  ]
</script>

<p style="color: var(--text-muted); margin-bottom: 0.5em; font-size: 0.9em">
  ↔️ Scroll horizontally to see all datasets &nbsp;|&nbsp; ↕️ Scroll vertically for all
  models &nbsp;|&nbsp; Model column stays pinned
</p>

<HeatmapTable {data} {columns} scroll_style="max-height: 400px;" style="margin: 0 auto" />

Column Groups with Scrolling

Crystal structure prediction results grouped by property type. The sticky first column stays visible while scrolling through different metric groups:

LatticeElectronicTransport
Structure a Strain N atoms Gap Accuracy σ κ ε
mp-149 (Si)5.430.00281.120.891e+115098.0
mp-2534 (GaAs)5.650.00581.420.745e+05546.0
mp-22862 (CsPbI₃)6.290.018201.730.585e-1038.0
mp-2657 (ZnO)3.250.00343.440.915e+1609.1
mp-66 (Diamond)3.570.00185.470.951e-132,2005.7
mp-5020 (NaCl)5.640.00488.500.821e-1775.9
mp-8062 (Fe₂O₃)5.030.012302.200.761e-72025.0
mp-5229 (TiO₂)4.590.008123.200.881e-129110.0
mp-1960 (MgO)4.210.00287.800.931e-15609.7
mp-804 (SiC)4.360.00383.260.911e-64909.7
mp-830 (GaN)3.190.00443.400.871e-101309.0
mp-20194 (InP)5.870.00681.350.791e-76812.4
<script>
  import { HeatmapTable } from 'matterviz'

  const structures = [
    [`mp-149 (Si)`, 5.43, 0.002, 8, 1.12, 0.89, 12.1, 150, 98],
    [`mp-2534 (GaAs)`, 5.65, 0.005, 8, 1.42, 0.74, 5.3, 55, 46],
    [`mp-22862 (CsPbI₃)`, 6.29, 0.018, 20, 1.73, 0.58, 0.5, 0.4, 38],
    [`mp-2657 (ZnO)`, 3.25, 0.003, 4, 3.44, 0.91, 54, 60, 9.1],
    [`mp-66 (Diamond)`, 3.57, 0.001, 8, 5.47, 0.95, 1e-13, 2200, 5.7],
    [`mp-5020 (NaCl)`, 5.64, 0.004, 8, 8.5, 0.82, 1e-17, 6.5, 5.9],
    [`mp-8062 (Fe₂O₃)`, 5.03, 0.012, 30, 2.2, 0.76, 1e-7, 20, 25],
    [`mp-5229 (TiO₂)`, 4.59, 0.008, 12, 3.2, 0.88, 1e-12, 8.5, 110],
    [`mp-1960 (MgO)`, 4.21, 0.002, 8, 7.8, 0.93, 1e-15, 60, 9.7],
    [`mp-804 (SiC)`, 4.36, 0.003, 8, 3.26, 0.91, 1e-6, 490, 9.7],
    [`mp-830 (GaN)`, 3.19, 0.004, 4, 3.4, 0.87, 1e-10, 130, 9.0],
    [`mp-20194 (InP)`, 5.87, 0.006, 8, 1.35, 0.79, 1e-7, 68, 12.4],
  ]

  const data = structures.map((
    [id, a, strain, natoms, gap, acc, cond, therm, diel],
  ) => ({
    'Structure': id,
    'a (Lattice)': a,
    'Strain (Lattice)': strain,
    'N atoms (Lattice)': natoms,
    'Gap (Electronic)': gap,
    'Accuracy (Electronic)': acc,
    'σ (Transport)': cond,
    'κ (Transport)': therm,
    'ε (Transport)': diel,
  }))

  // deno-fmt-ignore
  const columns = [
    { label: `Structure`, sticky: true, style: `min-width: 145px;` },
    { label: `a`, group: `Lattice`, color_scale: `interpolateBlues`, format: `.2f`, style: `min-width: 70px;`, description: `Lattice parameter (Å)` },
    { label: `Strain`, group: `Lattice`, better: `lower`, color_scale: `interpolateRdYlGn`, format: `.3f`, style: `min-width: 80px;` },
    { label: `N atoms`, group: `Lattice`, color_scale: `interpolatePurples`, format: `d`, style: `min-width: 85px;` },
    { label: `Gap`, group: `Electronic`, better: `higher`, color_scale: `interpolatePlasma`, format: `.2f`, style: `min-width: 70px;`, description: `Band gap (eV)` },
    { label: `Accuracy`, group: `Electronic`, better: `higher`, color_scale: `interpolateViridis`, format: `.2f`, style: `min-width: 90px;` },
    { label: `σ`, group: `Transport`, better: `higher`, color_scale: `interpolateOranges`, format: `.0e`, style: `min-width: 75px;`, scale_type: `log`, description: `Electrical conductivity (S/m)` },
    { label: `κ`, group: `Transport`, better: `higher`, color_scale: `interpolateReds`, format: `,.0f`, style: `min-width: 70px;`, description: `Thermal conductivity (W/m·K)` },
    { label: `ε`, group: `Transport`, color_scale: `interpolateGreens`, format: `.1f`, style: `min-width: 65px;`, description: `Dielectric constant` },
  ]
</script>

<HeatmapTable {data} {columns} scroll_style="max-width: 750px;" style="margin: 0 auto" />

Interactive Features

Enable search, export, column visibility, row selection, and pagination for a full-featured data exploration experience:

ID Formula Spacegroup Crystal Eform Eabove hull Egap DOS eff Density Volume Bulk mod Stability
mp-1234Fe₂O₃R-3cTrigonal−2.510.002.23.45.24302.7231Stable
mp-390TiO₂P4₂/mnmTetragonal−3.420.003.22.14.2362.4211Stable
mp-2133ZnOP6₃mcHexagonal−1.520.023.41.85.6123.8142Stable
mp-361Cu₂OPn-3mCubic−0.680.052.14.26.1077.8111Metastable
mp-6930SiO₂P3₁21Trigonal−4.130.008.90.92.65113.037Stable
mp-1143Al₂O₃R-3cTrigonal−3.470.008.81.23.99254.8252Stable
mp-1265MgOFm-3mCubic−3.010.007.81.03.5818.7163Stable
mp-4019CaTiO₃PnmaOrthorhombic−3.890.033.52.84.04228.2175Stable
mp-5986BaTiO₃P4mmTetragonal−3.720.003.23.16.0264.3162Stable
mp-5229SrTiO₃Pm-3mCubic−3.650.003.32.95.1259.6183Stable
mp-22526LiCoO₂R-3mTrigonal−2.180.002.74.55.0532.9145Stable
mp-19017LiFePO₄PnmaOrthorhombic−3.210.003.83.93.60291.498Stable
mp-149SiFd-3mCubic0.000.001.11.12.3340.998Stable
mp-32GaAsF-43mCubic−0.310.001.40.85.3245.275Stable
mp-661CdTeF-43mCubic−0.420.001.50.95.8654.842Stable
mp-2534SnO₂P4₂/mnmTetragonal−2.540.003.62.46.9571.5205Stable
mp-2657WO₃P2₁/cMonoclinic−2.890.012.63.27.1652.1194Stable
mp-764V₂O₅PmmnOrthorhombic−2.670.002.32.73.36178.952Stable
mp-540NiOFm-3mCubic−1.230.004.35.16.6718.3195Stable
mp-48CoOFm-3mCubic−1.150.042.44.86.4419.5180Metastable
mp-1968MnO₂P4₂/mnmTetragonal−2.130.000.36.25.0356.2267Stable
mp-2691Cr₂O₃R-3cTrigonal−3.780.003.43.65.2296.7238Stable
mp-1960MoS₂P6₃/mmcHexagonal−1.240.001.82.35.06106.445Stable
mp-1821WS₂P6₃/mmcHexagonal−1.180.001.92.17.50107.256Stable
mp-10695Li₃PS₄PnmaOrthorhombic−1.870.084.11.51.88412.824Metastable
mp-3163Na₃AlF₆P2₁/cMonoclinic−4.520.006.80.82.97226.751Stable
mp-7000CuFeS₂I-42dTetragonal−0.890.000.55.84.19145.275Stable
mp-35GaNP6₃mcHexagonal−0.820.003.41.66.1522.9210Stable
mp-13AlNP6₃mcHexagonal−1.470.006.21.23.2620.9201Stable
mp-804SiCF-43mCubic−0.350.002.41.43.2220.7225Stable
Shift+click for multi-sort
<script>
  import { HeatmapTable } from 'matterviz'

  // Comprehensive materials database with realistic properties
  // deno-fmt-ignore
  const data = [
    [`mp-1234`, `Fe₂O₃`, `R-3c`, `Trigonal`, -2.51, 0.0, 2.2, 3.4, 5.24, 302.7, 231, `Stable`],
    [`mp-390`, `TiO₂`, `P4₂/mnm`, `Tetragonal`, -3.42, 0.0, 3.2, 2.1, 4.23, 62.4, 211, `Stable`],
    [`mp-2133`, `ZnO`, `P6₃mc`, `Hexagonal`, -1.52, 0.02, 3.4, 1.8, 5.61, 23.8, 142, `Stable`],
    [`mp-361`, `Cu₂O`, `Pn-3m`, `Cubic`, -0.68, 0.05, 2.1, 4.2, 6.10, 77.8, 111, `Metastable`],
    [`mp-6930`, `SiO₂`, `P3₁21`, `Trigonal`, -4.13, 0.0, 8.9, 0.9, 2.65, 113.0, 37, `Stable`],
    [`mp-1143`, `Al₂O₃`, `R-3c`, `Trigonal`, -3.47, 0.0, 8.8, 1.2, 3.99, 254.8, 252, `Stable`],
    [`mp-1265`, `MgO`, `Fm-3m`, `Cubic`, -3.01, 0.0, 7.8, 1.0, 3.58, 18.7, 163, `Stable`],
    [`mp-4019`, `CaTiO₃`, `Pnma`, `Orthorhombic`, -3.89, 0.03, 3.5, 2.8, 4.04, 228.2, 175, `Stable`],
    [`mp-5986`, `BaTiO₃`, `P4mm`, `Tetragonal`, -3.72, 0.0, 3.2, 3.1, 6.02, 64.3, 162, `Stable`],
    [`mp-5229`, `SrTiO₃`, `Pm-3m`, `Cubic`, -3.65, 0.0, 3.3, 2.9, 5.12, 59.6, 183, `Stable`],
    [`mp-22526`, `LiCoO₂`, `R-3m`, `Trigonal`, -2.18, 0.0, 2.7, 4.5, 5.05, 32.9, 145, `Stable`],
    [`mp-19017`, `LiFePO₄`, `Pnma`, `Orthorhombic`, -3.21, 0.0, 3.8, 3.9, 3.60, 291.4, 98, `Stable`],
    [`mp-149`, `Si`, `Fd-3m`, `Cubic`, 0.0, 0.0, 1.12, 1.1, 2.33, 40.9, 98, `Stable`],
    [`mp-32`, `GaAs`, `F-43m`, `Cubic`, -0.31, 0.0, 1.42, 0.8, 5.32, 45.2, 75, `Stable`],
    [`mp-661`, `CdTe`, `F-43m`, `Cubic`, -0.42, 0.0, 1.5, 0.9, 5.86, 54.8, 42, `Stable`],
    [`mp-2534`, `SnO₂`, `P4₂/mnm`, `Tetragonal`, -2.54, 0.0, 3.6, 2.4, 6.95, 71.5, 205, `Stable`],
    [`mp-2657`, `WO₃`, `P2₁/c`, `Monoclinic`, -2.89, 0.01, 2.6, 3.2, 7.16, 52.1, 194, `Stable`],
    [`mp-764`, `V₂O₅`, `Pmmn`, `Orthorhombic`, -2.67, 0.0, 2.3, 2.7, 3.36, 178.9, 52, `Stable`],
    [`mp-540`, `NiO`, `Fm-3m`, `Cubic`, -1.23, 0.0, 4.3, 5.1, 6.67, 18.3, 195, `Stable`],
    [`mp-48`, `CoO`, `Fm-3m`, `Cubic`, -1.15, 0.04, 2.4, 4.8, 6.44, 19.5, 180, `Metastable`], // codespell:ignore
    [`mp-1968`,`MnO₂`, `P4₂/mnm`, `Tetragonal`, -2.13, 0.0, 0.3, 6.2, 5.03, 56.2, 267, `Stable` ],
    [`mp-2691`, `Cr₂O₃`, `R-3c`, `Trigonal`, -3.78, 0.0, 3.4, 3.6, 5.22, 96.7, 238, `Stable` ],
    [`mp-1960`, `MoS₂`, `P6₃/mmc`, `Hexagonal`, -1.24, 0.0, 1.8, 2.3, 5.06, 106.4, 45, `Stable` ],
    [`mp-1821`, `WS₂`, `P6₃/mmc`, `Hexagonal`, -1.18, 0.0, 1.9, 2.1, 7.50, 107.2, 56, `Stable` ],
    [`mp-10695`, `Li₃PS₄`, `Pnma`, `Orthorhombic`, -1.87, 0.08, 4.1, 1.5, 1.88, 412.8, 24, `Metastable` ],
    [`mp-3163`, `Na₃AlF₆`, `P2₁/c`, `Monoclinic`, -4.52, 0.0, 6.8, 0.8, 2.97, 226.7, 51, `Stable` ],
    [`mp-7000`, `CuFeS₂`, `I-42d`, `Tetragonal`, -0.89, 0.0, 0.5, 5.8, 4.19, 145.2, 75, `Stable` ],
    [`mp-35`, `GaN`, `P6₃mc`, `Hexagonal`, -0.82, 0.0, 3.4, 1.6, 6.15, 22.9, 210, `Stable` ],
    [`mp-13`, `AlN`, `P6₃mc`, `Hexagonal`, -1.47, 0.0, 6.2, 1.2, 3.26, 20.9, 201, `Stable` ],
    [`mp-804`, `SiC`, `F-43m`, `Cubic`, -0.35, 0.0, 2.4, 1.4, 3.22, 20.7, 225, `Stable`],
  ].map(([v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12]) => (
    { ID: v1, Formula: v2, Spacegroup: v3, Crystal: v4, 'E<sub>form</sub>': v5, 'E<sub>above hull</sub>': v6, 'E<sub>gap</sub>': v7, 'DOS eff': v8, Density: v9, Volume: v10, 'Bulk mod': v11, Stability: v12 })
  )
  // deno-fmt-ignore
  const columns = [
    { label: `ID`, sticky: true, style: `min-width: 80px;` },
    { label: `Formula`, style: `min-width: 90px;` },
    { label: `Spacegroup`, style: `min-width: 95px;` },
    { label: `Crystal`, style: `min-width: 100px;` },
    { label: `E<sub>form</sub>`, better: `lower`, color_scale: `interpolateRdYlGn`, format: `.2f`, description: `Formation energy (eV/atom)` },
    { label: `E<sub>above hull</sub>`, better: `lower`, color_scale: `interpolateRdYlGn`, format: `.2f`, description: `Energy above hull (eV/atom)` },
    { label: `E<sub>gap</sub>`, better: `higher`, color_scale: `interpolateViridis`, format: `.1f`, description: `Electronic bandgap (eV)` },
    { label: `DOS eff`, color_scale: `interpolatePlasma`, format: `.1f`, description: `Effective DOS mass` },
    { label: `Density`, color_scale: `interpolateBlues`, format: `.2f`, description: `Density (g/cm³)` },
    { label: `Volume`, color_scale: `interpolatePurples`, format: `.1f`, description: `Cell volume (ų)` },
    { label: `Bulk mod`, better: `higher`, color_scale: `interpolateOranges`, format: `d`, description: `Bulk modulus (GPa)` },
    { label: `Stability` },
  ]

  let selected_rows = $state([])
</script>

<HeatmapTable
  {data}
  {columns}
  scroll_style="max-height: 450px;"
  search
  export_data
  show_column_toggle
  show_row_select
  bind:selected_rows
  sort_hint="Shift+click for multi-sort"
  style="margin: 0 auto"
/>

{#if selected_rows.length > 0}
  <p style="margin-top: 1em; color: var(--text-muted)">
    Selected: {selected_rows.map((r) => r.Formula).join(`, `)}
  </p>
{/if}

Pagination

For large datasets, pagination keeps the table responsive while allowing navigation through all data:

ID Formula Eform Egap Density
mp-1000Li₂O−1.670.409.26
mp-1001Na₂O−2.995.096.66
mp-1002K₂O−2.573.552.88
mp-1003Mg₂O−4.481.987.19
mp-1004Ca₂O−4.684.812.27
mp-1005Fe₂O−4.570.697.07
mp-1006Co₂O−2.074.577.71
mp-1007Ni₂O−5.314.897.57
mp-1008Cu₂O−1.805.999.40
mp-1009Zn₂O−5.076.872.77
mp-1010Li₂O−2.204.004.99
mp-1011Na₂O−4.014.696.01
mp-1012K₂O−4.130.043.72
mp-1013Mg₂O−4.806.985.96
mp-1014Ca₂O−4.402.183.21
<script>
  import { HeatmapTable } from 'matterviz'

  // Generate 100 materials
  const formulas = [`Li`, `Na`, `K`, `Mg`, `Ca`, `Fe`, `Co`, `Ni`, `Cu`, `Zn`]
  const anions = [`O`, `S`, `N`, `F`, `Cl`]
  const data = Array.from({ length: 100 }, (_, idx) => {
    const cat = formulas[idx % formulas.length]
    const an = anions[Math.floor(idx / 20)]
    return {
      ID: `mp-${1000 + idx}`,
      Formula: `${cat}${an}`,
      'E<sub>form</sub>': -(Math.random() * 5 + 1).toFixed(2),
      'E<sub>gap</sub>': (Math.random() * 8).toFixed(2),
      Density: (2 + Math.random() * 8).toFixed(2),
    }
  })

  const columns = [
    { label: `ID` },
    { label: `Formula` },
    {
      label: `E<sub>form</sub>`,
      better: `lower`,
      color_scale: `interpolateRdYlGn`,
      format: `.2f`,
    },
    {
      label: `E<sub>gap</sub>`,
      better: `higher`,
      color_scale: `interpolateViridis`,
      format: `.2f`,
    },
    { label: `Density`, color_scale: `interpolateBlues`, format: `.2f` },
  ]
</script>

<HeatmapTable
  {data}
  {columns}
  pagination={{ page_size: 15 }}
  search
  style="margin: 0 auto"
/>

All Color Scales

Explore different D3 color scales. The better prop controls whether high or low values get the “good” end of the scale:

Material Eform Egap
Si0.001.12
Ge0.000.67
GaAs−0.741.42
ZnO−3.633.44
TiO₂−9.733.20
SiO₂−9.088.90
Al₂O₃−17.378.80
MgO−6.237.80
CaO−6.357.10
BaTiO₃−16.823.20
<script>
  import { HeatmapTable } from 'matterviz'

  const color_scales = [
    `interpolateViridis`,
    `interpolatePlasma`,
    `interpolateInferno`,
    `interpolateMagma`,
    `interpolateCividis`,
    `interpolateCool`,
    `interpolateWarm`,
    `interpolateRdYlBu`,
    `interpolateRdYlGn`,
    `interpolateSpectral`,
    `interpolatePurples`,
    `interpolateBlues`,
    `interpolateGreens`,
    `interpolateOranges`,
    `interpolateReds`,
  ]

  let selected_scale = $state(`interpolateViridis`)
  let better = $state(`higher`)

  // Formation energies and bandgaps for common materials
  const data = [
    [`Si`, 0, 1.12],
    [`Ge`, 0, 0.67],
    [`GaAs`, -0.74, 1.42],
    [`ZnO`, -3.63, 3.44],
    [`TiO₂`, -9.73, 3.20],
    [`SiO₂`, -9.08, 8.90],
    [`Al₂O₃`, -17.37, 8.80],
    [`MgO`, -6.23, 7.80],
    [`CaO`, -6.35, 7.10],
    [`BaTiO₃`, -16.82, 3.20],
  ].map(([v1, v2, v3]) => ({
    Material: v1,
    'E<sub>form</sub>': v2,
    'E<sub>gap</sub>': v3,
  }))

  let columns = $derived([
    { label: `Material` },
    { label: `E<sub>form</sub>`, better, color_scale: selected_scale, format: `.2f` },
    { label: `E<sub>gap</sub>`, better, color_scale: selected_scale, format: `.2f` },
  ])
</script>

<div style="display: flex; gap: 2em; margin-bottom: 1em; flex-wrap: wrap">
  <label>
    Color Scale:
    <select bind:value={selected_scale}>
      {#each color_scales as scale}
        <option value={scale}>{scale.replace(`interpolate`, ``)}</option>
      {/each}
    </select>
  </label>
  <label>
    Better:
    <select bind:value={better}>
      <option value="higher">Higher</option>
      <option value="lower">Lower</option>
    </select>
  </label>
</div>

<HeatmapTable {data} {columns} style="margin: 0 auto" />