Bar Plot

Crystal Structure Analysis

A simple bar plot showing lattice parameters across different crystal systems. Use the controls (gear icon) to toggle orientation, modes, and grid display. This example also demonstrates rounded corners (border_radius) and bar borders (stroke_color, stroke_width):

<script>
  import { BarPlot } from 'matterviz'

  const lattice_params = [
    {
      x: [1, 2, 3, 4, 5, 6, 7],
      y: [3.52, 4.05, 5.43, 6.08, 3.89, 4.95, 5.65],
      label: `Lattice Parameter a (Å)`,
      color: `#4c6ef5`,
      labels: [`Diamond`, `NaCl`, `Si`, `GaAs`, `GaN`, `ZnO`, `CdTe`],
    },
  ]
</script>

<BarPlot
  series={lattice_params}
  bar={{ border_radius: 4, stroke_color: `#364fc7`, stroke_width: 1.5 }}
  x_axis={{ label: `Crystal System` }}
  y_axis={{ label: `Lattice Parameter (Å)` }}
  style="height: 400px"
/>

Mode Comparison: Band Gap Measurements

Compare overlay, stacked, and grouped (side-by-side) modes using band gap data from different measurement techniques. Click legend items to toggle series visibility:

<script>
  import { BarPlot } from 'matterviz'

  const band_gaps = [
    {
      x: [1, 2, 3, 4, 5],
      y: [1.12, 1.43, 2.26, 3.4, 5.47],
      label: `Optical Absorption`,
      color: `#4c6ef5`,
    },
    {
      x: [1, 2, 3, 4, 5],
      y: [0.95, 1.25, 1.85, 2.98, 4.82],
      label: `Photoluminescence`,
      color: `#ff6b6b`,
    },
    {
      x: [1, 2, 3, 4, 5],
      y: [1.28, 1.62, 2.58, 3.75, 6.15],
      label: `DFT Calculation`,
      color: `#51cf66`,
    },
  ]

  let mode = $state(`grouped`)
</script>

<div style="margin-bottom: 1em; display: flex; gap: 1em">
  <label><input type="radio" bind:group={mode} value="overlay" /> Overlay</label>
  <label><input type="radio" bind:group={mode} value="stacked" /> Stacked (Sum)</label>
  <label><input type="radio" bind:group={mode} value="grouped" /> Grouped
    (Side-by-Side)</label>
</div>

<BarPlot
  series={band_gaps}
  {mode}
  x_axis={{ label: `Material (1=Si, 2=GaAs, 3=GaN, 4=ZnO, 5=Diamond)` }}
  y_axis={{ label: `Band Gap (eV)` }}
  style="height: 400px"
/>

Reaction Kinetics with Dual Y-Axes

Combine bars and lines with dual y-axes to show product formation alongside temperature. Temperature uses the right y2-axis with independent scaling. Line series support marker symbols using the markers prop (line, points, or line+points):

<script>
  import { BarPlot } from 'matterviz'

  const reaction_data = [
    {
      x: [0, 10, 20, 30, 40, 50, 60],
      y: [0, 12, 28, 45, 58, 67, 72],
      label: `Product A (mol)`,
      color: `#4c6ef5`,
    },
    {
      x: [0, 10, 20, 30, 40, 50, 60],
      y: [0, 8, 19, 32, 41, 48, 52],
      label: `Product B (mol)`,
      color: `#51cf66`,
    },
    {
      x: [0, 10, 20, 30, 40, 50, 60],
      y: [25, 80, 120, 140, 145, 142, 138],
      label: `Temperature (°C)`,
      color: `#ff6b6b`,
      render_mode: `line`,
      y_axis: `y2`,
      markers: `line+points`, // Show both line and marker points
      line_style: { stroke_width: 2, line_dash: `5,5` },
      point_style: { radius: 5, stroke: `white`, stroke_width: 2 },
    },
    {
      x: [0, 10, 20, 30, 40, 50, 60],
      y: [0, 20, 47, 77, 99, 115, 124],
      label: `Total Yield (mol)`,
      color: `#ffd43b`,
      render_mode: `line`,
      markers: `line+points`, // Show both line and marker points
      line_style: { stroke_width: 2 },
      point_style: { radius: 6, symbol_type: `Diamond` },
    },
  ]
</script>

<BarPlot
  series={reaction_data}
  mode="stacked"
  x_axis={{ label: `Time (minutes)` }}
  y_axis={{ label: `Product Amount (mol)` }}
  y2_axis={{ label: `Temperature (°C)`, format: `.0r` }}
  style="height: 450px"
/>

Sales vs Profit Margin (Dual Y-Axes)

A classic business use case: comparing raw sales (bars, left axis) with profit margins (line, right axis) that have different scales and units. The line uses markers: 'line+points' with custom point_style for triangle markers:

<script>
  import { BarPlot } from 'matterviz'

  const quarterly_data = [
    {
      x: [1, 2, 3, 4, 5, 6, 7, 8],
      y: [125000, 142000, 158000, 171000, 165000, 189000, 203000, 218000],
      label: `Sales Revenue ($)`,
      color: `#4c6ef5`,
      labels: [
        `Q1-22`,
        `Q2-22`,
        `Q3-22`,
        `Q4-22`,
        `Q1-23`,
        `Q2-23`,
        `Q3-23`,
        `Q4-23`,
      ],
    },
    {
      x: [1, 2, 3, 4, 5, 6, 7, 8],
      y: [12.5, 15.2, 18.3, 14.8, 13.1, 19.5, 21.2, 23.8],
      label: `Profit Margin (%)`,
      color: `#51cf66`,
      render_mode: `line`,
      y_axis: `y2`,
      markers: `line+points`,
      line_style: { stroke_width: 2 },
      point_style: {
        radius: 6,
        symbol_type: `Triangle`,
        stroke: `#2f9e44`,
        stroke_width: 2,
      },
      point_hover: { scale: 1.8, brightness: 1.3 },
    },
  ]
</script>

<BarPlot
  series={quarterly_data}
  x_axis={{ label: `Quarter` }}
  y_axis={{ label: `Revenue ($)`, format: `$,.0f` }}
  y2_axis={{ label: `Margin (%)`, format: `.1r` }}
  style="height: 400px"
/>

Element Abundance in Earth’s Crust

Horizontal bar charts work well for categorical data with long labels. This example demonstrates tick.label.inside which positions tick labels inside the plot area for a more compact design:

<script>
  import { BarPlot } from 'matterviz'

  const abundances = [
    {
      x: [1, 2, 3, 4, 5, 6, 7, 8],
      y: [461000, 277200, 82300, 50000, 41500, 26000, 23600, 20900],
      label: `Abundance (ppm)`,
      color: `#845ef7`,
      labels: [
        `Oxygen`,
        `Silicon`,
        `Aluminum`,
        `Iron`,
        `Calcium`,
        `Sodium`,
        `Magnesium`,
        `Potassium`,
      ],
    },
  ]

  let orientation = $state(`horizontal`)
  let inside = $state(false)
</script>

<label style="margin-bottom: 1em; display: inline-block">
  <input
    type="checkbox"
    checked={orientation === `horizontal`}
    onchange={(evt) => (orientation = evt.target.checked ? `horizontal` : `vertical`)}
  />
  Horizontal Orientation
</label>
<label style="margin-bottom: 1em; display: inline-block; margin-left: 2em">
  <input type="checkbox" bind:checked={inside} />
  Tick Labels Inside
</label>

<BarPlot
  series={abundances}
  {orientation}
  x_axis={{ label: `Abundance (ppm)`, format: `~s`, tick: { label: { inside } } }}
  y_axis={{ label: `Element`, format: `~s`, tick: { label: { inside } } }}
  style="height: 400px"
/>

Phase Stability with Interactive Tooltips

Add rich interactivity with custom tooltips, hover effects, and click handlers:

Clicked: Click a bar to see phase details
Hovered: Hover over a bar
<script>
  import { BarPlot } from 'matterviz'

  const phase_stability = [
    {
      x: [1, 2, 3, 4, 5],
      y: [85, 92, 78, 95, 88],
      label: `α-phase`,
      color: `#5c7cfa`,
      metadata: [
        { phase: `α-phase`, temp: `300K`, structure: `FCC`, stability: `High` },
        { phase: `α-phase`, temp: `500K`, structure: `FCC`, stability: `Very High` },
        { phase: `α-phase`, temp: `700K`, structure: `FCC`, stability: `Medium` },
        { phase: `α-phase`, temp: `900K`, structure: `FCC`, stability: `Very High` },
        { phase: `α-phase`, temp: `1100K`, structure: `FCC`, stability: `High` },
      ],
    },
    {
      x: [1, 2, 3, 4, 5],
      y: [70, 75, 88, 82, 90],
      label: `β-phase`,
      color: `#ff6b6b`,
      metadata: [
        { phase: `β-phase`, temp: `300K`, structure: `BCC`, stability: `Medium` },
        { phase: `β-phase`, temp: `500K`, structure: `BCC`, stability: `Medium` },
        { phase: `β-phase`, temp: `700K`, structure: `BCC`, stability: `High` },
        { phase: `β-phase`, temp: `900K`, structure: `BCC`, stability: `High` },
        { phase: `β-phase`, temp: `1100K`, structure: `BCC`, stability: `Very High` },
      ],
    },
  ]

  let clicked_info = $state(`Click a bar to see phase details`)
  let hovered_info = $state(`Hover over a bar`)

  function handle_click(data) {
    const { metadata, y } = data
    clicked_info =
      `${metadata.phase} at ${metadata.temp}: ${metadata.structure} structure, ${y}% stable (${metadata.stability})`
  }

  function handle_hover(data) {
    if (data) {
      const { metadata, y } = data
      hovered_info = `${metadata.phase} at ${metadata.temp}: ${y}% stability`
    } else hovered_info = `Hover over a bar`
  }

  const info_style =
    `margin: 1em 0; padding: 4pt 8pt; background: rgba(255,255,255,0.1); border-radius: var(--border-radius); font-size: 0.9em`
</script>

<BarPlot
  series={phase_stability}
  mode="grouped"
  x_axis={{ label: `Temperature Point` }}
  y_axis={{ label: `Stability (%)` }}
  on_bar_click={handle_click}
  on_bar_hover={handle_hover}
  style="height: 400px"
>
  {#snippet tooltip({ metadata, y })}
    <div style="font-weight: 600">{metadata.phase}</div>
    <div>Temp: {metadata.temp}</div>
    <div>Structure: {metadata.structure}</div>
    <div>Stability: {y}% ({metadata.stability})</div>
  {/snippet}
</BarPlot>

<div style={info_style}>
  <strong>Clicked:</strong> {clicked_info}
</div>
<div style={info_style}>
  <strong>Hovered:</strong> {hovered_info}
</div>

Formation Energy Diagram

Bar plots handle negative values automatically and display zero lines for reference. The threshold line uses markers: 'line' to show only the line without marker points:

<script>
  import { BarPlot } from 'matterviz'

  const formation_energies = [
    {
      x: [1, 2, 3, 4, 5, 6, 7, 8],
      y: [-2.45, 1.28, -1.82, 0.95, -3.12, 2.01, -0.67, -1.93],
      label: `Formation Energy`,
      color: `#4c6ef5`,
      labels: [
        `TiO₂`,
        `CuO`,
        `Al₂O₃`,
        `Fe₂O₃`,
        `MgO`,
        `ZnO`,
        `NiO`,
        `FeO`,
      ],
    },
    {
      x: [1, 2, 3, 4, 5, 6, 7, 8],
      y: [-2.0, -2.0, -2.0, -2.0, -2.0, -2.0, -2.0, -2.0],
      label: `Stability Threshold`,
      color: `#51cf66`,
      render_mode: `line`,
      markers: `line`, // Line only, no marker points
      line_style: {
        stroke_width: 2,
        line_dash: `8,4`,
      },
    },
  ]
</script>

<BarPlot
  series={formation_energies}
  x_axis={{ label: `Compound` }}
  y_axis={{ label: `Formation Energy (eV/atom)` }}
  style="height: 400px"
/>

Material Properties Over Time with Dual Axes

Custom formatting, tick control, and dual y-axes showing both material counts and computational performance metrics:

<script>
  import { BarPlot } from 'matterviz'

  const yearly_data = [
    {
      x: [1990, 1995, 2000, 2005, 2010, 2015, 2020],
      y: [120000, 185000, 275000, 420000, 680000, 1050000, 1600000],
      label: `Materials in Database`,
      color: `#4c6ef5`,
    },
    {
      x: [1990, 1995, 2000, 2005, 2010, 2015, 2020],
      y: [0.5, 2.3, 8.5, 28, 95, 340, 1200],
      label: `Compute Power (TFLOPS)`,
      color: `#ff6b6b`,
      render_mode: `line`,
      y_axis: `y2`,
      line_style: {
        stroke_width: 3,
        line_dash: `6,3`,
      },
    },
  ]

  let x_axis = $state({ label: `Year`, format: `d`, ticks: 7 })
  let y_axis = $state({ label: `Number of Materials`, format: `.2s`, ticks: 6 })
  let y2_axis = $state({ label: `Compute Power (TFLOPS)`, format: `.0r`, ticks: 6 })
</script>

<div style="display: flex; gap: 2em; margin-bottom: 1em; flex-wrap: wrap">
  <label>
    X Format:
    <select bind:value={x_axis.format}>
      <option value="d">Integer (2020)</option>
      <option value=".0f">Float (2020.0)</option>
    </select>
  </label>
  <label>
    Y1 Format:
    <select bind:value={y_axis.format}>
      <option value=".2s">Short (1.6M)</option>
      <option value=",.0f">Full (1,600,000)</option>
      <option value=".3s">Precise (1.60M)</option>
    </select>
  </label>
  <label>
    Y2 Format:
    <select bind:value={y2_axis.format}>
      <option value=".0f">Integer (1200)</option>
      <option value=".2s">Short (1.2k)</option>
      <option value=".1f">Decimal (1200.0)</option>
    </select>
  </label>
  <label>
    X Ticks: {x_axis.ticks}
    <input type="range" bind:value={x_axis.ticks} min="3" max="10" style="width: 100px">
  </label>
</div>

<BarPlot
  series={yearly_data}
  {x_axis}
  {y_axis}
  {y2_axis}
  style="height: 400px"
/>

Spectroscopy Data with Zoom

Interactive zoom and pan for exploring large datasets. Click and drag to zoom, double-click to reset. This example demonstrates color scaling on line markers using color_values and color_scale:

Instructions: Click and drag to zoom into a wavelength region. Double-click to reset the view. Marker colors show intensity via color scale.
<script>
  import { BarPlot } from 'matterviz'

  // deno-fmt-ignore
  const absorption_data = [0.05, 0.12, 0.28, 0.65, 1.45, 2.8, 4.2, 3.5, 2.1, 0.95, 0.38, 0.15, 0.06]
  // deno-fmt-ignore
  const theoretical_data = [0.02, 0.08, 0.22, 0.58, 1.35, 2.65, 4.0, 3.3, 1.95, 0.85, 0.32, 0.12, 0.05]

  const spectroscopy = [
    {
      x: [200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800],
      y: absorption_data,
      label: `Absorption Spectrum`,
      color: `#4c6ef5`,
    },
    {
      x: [200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800],
      y: theoretical_data,
      label: `Theoretical Fit`,
      color: `#ff6b6b`,
      render_mode: `line`,
      markers: `line+points`,
      // Color each point by its y-value using color_values
      color_values: theoretical_data,
      line_style: { stroke_width: 2 },
      point_style: { radius: 5, stroke: `white`, stroke_width: 1 },
    },
  ]
</script>

<div
  style="margin-bottom: 1em; padding: 8pt; background: rgba(255, 255, 255, 0.05); border-radius: 4px"
>
  <strong>Instructions:</strong> Click and drag to zoom into a wavelength region.
  Double-click to reset the view. Marker colors show intensity via color scale.
</div>

<BarPlot
  series={spectroscopy}
  x_axis={{ label: `Wavelength (nm)` }}
  y_axis={{ label: `Absorption Coefficient` }}
  color_scale={{ scheme: `interpolatePlasma` }}
  style="height: 450px"
/>

Line Marker Customization

Line series support full marker customization including symbol types, sizes, colors, and hover effects. Use the markers prop to control visibility (line, points, line+points, or none), and point_style / point_hover for styling:

<script>
  import { BarPlot } from 'matterviz'

  let markers = $state(`line+points`)
  let symbol_type = $state(`Circle`)
  let radius = $state(6)

  const symbol_types = [
    `Circle`,
    `Square`,
    `Triangle`,
    `Diamond`,
    `Star`,
    `Cross`,
    `Wye`,
  ]

  // Efficiency measurements with size scaled by sample count
  const efficiency_data = [
    {
      x: [1, 2, 3, 4, 5, 6],
      y: [82, 85, 91, 88, 94, 97],
      label: `Device Efficiency (%)`,
      color: `#4c6ef5`,
    },
    {
      x: [1, 2, 3, 4, 5, 6],
      y: [78, 83, 87, 92, 95, 98],
      label: `Model Prediction`,
      color: `#f783ac`,
      render_mode: `line`,
      markers, // Bind to toggle
      line_style: { stroke_width: 2 },
      // Size each point by sample count (more samples = larger marker)
      size_values: [10, 25, 50, 75, 100, 150],
      point_style: {
        symbol_type,
        radius, // Base radius, modified by size_values
        stroke: `white`,
        stroke_width: 2,
      },
      point_hover: { scale: 1.5, brightness: 1.2 },
    },
  ]

  let series = $derived(
    efficiency_data.map((srs) =>
      srs.render_mode === `line`
        ? {
          ...srs,
          markers,
          point_style: { ...srs.point_style, symbol_type, radius },
        }
        : srs
    ),
  )
</script>

<div
  style="display: flex; gap: 2em; margin-bottom: 1em; flex-wrap: wrap; align-items: center"
>
  <label>
    Markers:
    <select bind:value={markers}>
      <option value="line+points">Line + Points</option>
      <option value="line">Line Only</option>
      <option value="points">Points Only</option>
      <option value="none">None</option>
    </select>
  </label>
  <label>
    Symbol:
    <select bind:value={symbol_type}>
      {#each symbol_types as sym}
        <option value={sym}>{sym}</option>
      {/each}
    </select>
  </label>
  <label>
    Size: {radius}
    <input type="range" bind:value={radius} min="3" max="12" style="width: 80px">
  </label>
</div>

<BarPlot
  {series}
  x_axis={{ label: `Sample Batch` }}
  y_axis={{ label: `Efficiency (%)` }}
  size_scale={{ radius_range: [4, 12] }}
  style="height: 400px"
/>

Legend Grouping

When comparing results from different computational methods or experimental techniques, you can organize legend items into collapsible groups using the legend_group property. Click the group header to toggle visibility of all series in that group, or click the chevron (▶) to collapse/expand the group:

<script>
  import { BarPlot } from 'matterviz'

  // Comparing formation energies from different methods
  const grouped_series = [
    // DFT Methods group
    {
      x: [1, 2, 3, 4, 5],
      y: [-1.2, -0.8, -2.1, -1.5, -0.9],
      label: 'PBE',
      legend_group: 'DFT',
      color: '#3498db',
    },
    {
      x: [1, 2, 3, 4, 5],
      y: [-1.4, -0.9, -2.3, -1.7, -1.1],
      label: 'r2SCAN',
      legend_group: 'DFT',
      color: '#2980b9',
    },
    // ML Potentials group
    {
      x: [1, 2, 3, 4, 5],
      y: [-1.1, -0.7, -2.0, -1.4, -0.8],
      label: 'MACE',
      legend_group: 'ML Potentials',
      color: '#e74c3c',
    },
    {
      x: [1, 2, 3, 4, 5],
      y: [-1.3, -0.85, -2.15, -1.55, -0.95],
      label: 'CHGNet',
      legend_group: 'ML Potentials',
      color: '#c0392b',
    },
    // Experiment (ungrouped reference)
    {
      x: [1, 2, 3, 4, 5],
      y: [-1.25, -0.82, -2.05, -1.52, -0.88],
      label: 'Experiment',
      color: '#2ecc71',
    },
  ]
</script>

<BarPlot
  series={grouped_series}
  mode="grouped"
  x_axis={{ label: 'Compound (1=LiCoO₂, 2=LiFePO₄, 3=Li₂MnO₃, 4=LiNiO₂, 5=LiMn₂O₄)' }}
  y_axis={{ label: 'Formation Energy (eV/atom)' }}
  legend={{ draggable: true }}
  style="height: 400px"
/>

Multiple Plots in 2×2 Grid Layout

Display multiple bar plots in a responsive 2×2 grid:

Reaction Rates

Crystal Lattice

Band Gaps

Formation Energy

<script>
  import { BarPlot } from 'matterviz'

  const make_data = (fn, label_fn) => {
    const x_vals = Array.from({ length: 6 }, (_, idx) => idx + 1)
    return {
      x: x_vals,
      y: x_vals.map(fn),
      labels: x_vals.map(label_fn),
    }
  }

  const plots = [
    {
      title: `Reaction Rates`,
      data: make_data((x) => 2 * x + Math.random() * 3, (x) => `R${x}`),
      x_label: `Reaction`,
      y_label: `Rate (mol/s)`,
      color: `#4c6ef5`,
    },
    {
      title: `Crystal Lattice`,
      data: make_data((x) => 3 + Math.sqrt(x) * 2 + Math.random(), (x) => `L${x}`),
      x_label: `Structure`,
      y_label: `Lattice (Å)`,
      color: `#51cf66`,
    },
    {
      title: `Band Gaps`,
      data: make_data((x) => 1 + x * 0.5 + Math.random() * 0.3, (x) => `M${x}`),
      x_label: `Material`,
      y_label: `Gap (eV)`,
      color: `#ff6b6b`,
    },
    {
      title: `Formation Energy`,
      data: make_data((x) => -2 + x * 0.3 + Math.random() * 0.5, (x) => `C${x}`),
      x_label: `Compound`,
      y_label: `Energy (eV)`,
      color: `#ffd43b`,
    },
  ]
</script>

<div class="grid">
  {#each plots as { title, data, x_label, y_label, color }}
    <div class="cell">
      <h4>{title}</h4>
      <BarPlot
        series={[{ ...data, color }]}
        x_axis={{ label: x_label }}
        y_axis={{ label: y_label }}
      />
    </div>
  {/each}
</div>