FormulaFilter

An interactive search filter for chemical formulas. The search mode is automatically inferred from input format:

Interactive Demo

1 of 156:
Li Fe O 3
  <script>
  import { Formula, FormulaFilter } from 'matterviz/composition'

  let value = $state(`Li,Fe`)
  let mode = $state(`elements`)

  // Generate compounds programmatically
  const els = `Li Na K Mg Ca Fe Co Ni Cu Zn Mn Ti Al`.split(` `)
  const materials = []
  for (const el of els) {
    for (const amt of [1, 2]) {
      materials.push({ [el]: amt, O: 2 }, { [el]: amt, O: 3 }, { [el]: amt, S: 2 })
    }
  }
  for (let idx = 0; idx < els.length - 1; idx++) {
    for (let jdx = idx + 1; jdx < els.length; jdx++) {
      materials.push({ [els[idx]]: 1, [els[jdx]]: 1, O: 3 })
    }
  }

  const to_str = (comp) => Object.entries(comp).map(([el, n]) => `${el}${n}`).join(``)

  const filtered = $derived.by(() => {
    if (!value) return materials.slice(0, 60)
    if (mode === `exact`) {
      // Exact formula match - compare stringified compositions
      const target = value.toLowerCase()
      return materials.filter((comp) => to_str(comp).toLowerCase() === target)
    }
    const query_els = value.split(/[,\-]/).map((s) => s.trim()).filter(Boolean)
    if (mode === `chemsys`) {
      // Chemical system: only these elements, no others
      return materials.filter((comp) => {
        const comp_els = Object.keys(comp)
        return query_els.every((el) => el in comp) &&
          comp_els.every((el) => query_els.includes(el))
      })
    }
    // Contains elements mode
    return materials.filter((comp) => query_els.every((el) => el in comp))
  })
</script>

<FormulaFilter bind:value bind:search_mode={mode} />

<div style="margin-top: 1em">
  <strong style="font-size: 0.85em; opacity: 0.7">{filtered.length} of {
      materials.length
    }:</strong>
  <div style="display: flex; flex-wrap: wrap; gap: 6pt; margin-top: 6pt">
    {#each filtered.slice(0, 40) as comp}
      <span
        style="padding: 3pt 6pt; background: rgba(77, 182, 255, 0.1); border-radius: 4px"
      >
        <Formula formula={to_str(comp)} />
      </span>
    {:else}
      <span style="opacity: 0.5">No matches</span>
    {/each}
    {#if filtered.length > 40}<span style="opacity: 0.5"
      >+{filtered.length - 40} more</span>{/if}
  </div>
</div>

Try these examples:

Include/Exclude Filters

Li O 2 Li 2O 3 Li S 2 Li Na O 3 Li Mg O 3 Li Ca O 3 Li Fe O 3 Li Co O 3 Li Ni O 3 Li Cu O 3 Li Zn O 3 Li Mn O 3
12 of 80
  <script>
  import { Formula, FormulaFilter } from 'matterviz/composition'

  let include = $state(`Li`), exclude = $state(``)

  const els = [`Li`, `Na`, `Mg`, `Ca`, `Fe`, `Co`, `Ni`, `Cu`, `Zn`, `Mn`, `Ti`, `Al`]
  const materials = []
  for (const el of els) {
    materials.push({ [el]: 1, O: 2 }, { [el]: 2, O: 3 }, { [el]: 1, S: 2 })
  }
  for (let idx = 0; idx < 8; idx++) {
    for (let jdx = idx + 1; jdx < 10; jdx++) {
      materials.push({ [els[idx]]: 1, [els[jdx]]: 1, O: 3 })
    }
  }

  const to_str = (comp) => Object.entries(comp).map(([el, n]) => `${el}${n}`).join(``)
  const results = $derived.by(() => {
    let mats = materials
    if (include) {
      mats = mats.filter((c) => include.split(`,`).every((el) => el.trim() in c))
    }
    if (exclude) {
      mats = mats.filter((c) => !exclude.split(`,`).some((el) => el.trim() in c))
    }
    return mats
  })
</script>

<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1em; margin-bottom: 1em">
  <FormulaFilter bind:value={include} />
  <FormulaFilter bind:value={exclude} style="--filter-bg: rgba(239, 68, 68, 0.05)" />
</div>

<div style="display: flex; flex-wrap: wrap; gap: 6pt">
  {#each results.slice(0, 30) as comp}
    <span
      style="padding: 3pt 6pt; background: rgba(16, 185, 129, 0.15); border-radius: 4px"
    >
      <Formula formula={to_str(comp)} />
    </span>
  {:else}<span style="opacity: 0.5">No matches</span>{/each}
  {#if results.length > 30}<span style="opacity: 0.5"
    >+{results.length - 30} more</span>{/if}
</div>
<div style="margin-top: 6pt; font-size: 0.8em; opacity: 0.6">
  {results.length} of {materials.length}
</div>