Site Vectors (Force, Magmom, Spin)

Structures can carry per-site vector data in their properties dict. Recognized keys include force, magmom, spin and prefixed variants like force_DFT or magmom_MLFF. When multiple vector keys are present, each gets its own color, toggle, and per-key scale slider. Only one layer draws by default, picked in the order magmoms, spins, forces; the others stay one toggle away under Visibility.

Color Modes

auto maps force* to element colors and magmom/spin to signed-z colors. Other modes are element, spin direction, magnitude (vector_color_scale), and uniform (vector_color); multi-key data gets a distinct, editable color per key.

Interactive example

svelte<script lang="ts">
  import { calc_lattice_params, Structure } from 'matterviz'
  import type { ElementSymbol, Matrix3x3, Vec3 } from 'matterviz'

  // periodic lattice from its matrix, with lengths, angles and volume derived
  // site of one fully occupied element, labeled by it
  const site = (
    element: ElementSymbol,
    abc: Vec3,
    xyz: Vec3,
    properties: Record<string, unknown>,
  ) => ({
    species: [{ element, occu: 1 }],
    abc,
    xyz,
    label: element,
    properties,
  })
  const lattice = (matrix: Matrix3x3) => ({
    matrix,
    pbc: [true, true, true],
    ...calc_lattice_params(matrix),
  })
  const fe_bcc = lattice([
    [-1.435, 1.435, 1.435],
    [1.435, -1.435, 1.435],
    [1.435, 1.435, -1.435],
  ])
  const nacl = lattice([
    [5.64, 0, 0],
    [0, 5.64, 0],
    [0, 0, 5.64],
  ])
  const tio2 = lattice([
    [4.594, 0, 0],
    [0, 4.594, 0],
    [0, 0, 2.959],
  ])

  const examples = [
    {
      title: `Fe BCC — single force`,
      desc: `Single force key, element-colored arrows`,
      structure: {
        lattice: fe_bcc,
        sites: [
          site(`Fe`, [0, 0, 0], [0, 0, 0], { force: [0.8, -0.5, 1.2] }),
          site(`Fe`, [0.5, 0.5, 0.5], [0.718, 0.718, 0.718], { force: [-0.8, 0.5, -1.2] }),
        ],
      },
    },
    {
      title: `Fe BCC — antiferromagnetic magmoms`,
      desc: `Scalar magmom ±2.2 μB along z, spin-direction coloring (red=up, blue=down)`,
      structure: {
        lattice: fe_bcc,
        sites: [
          site(`Fe`, [0, 0, 0], [0, 0, 0], { magmom: [0, 0, 2.2] }),
          site(`Fe`, [0.5, 0.5, 0.5], [0.718, 0.718, 0.718], { magmom: [0, 0, -2.2] }),
        ],
      },
    },
    {
      title: `Fe BCC — canted spins`,
      desc: `Non-collinear spin vectors tilted off-axis`,
      structure: {
        lattice: fe_bcc,
        sites: [
          site(`Fe`, [0, 0, 0], [0, 0, 0], { spin: [1.5, 0.8, 1.8] }),
          site(`Fe`, [0.5, 0.5, 0.5], [0.718, 0.718, 0.718], { spin: [-1.2, 1.0, -1.6] }),
        ],
      },
    },
    {
      title: `NaCl — ionic forces`,
      desc: `Forces on a rock-salt structure with different element colors per sublattice`,
      structure: {
        lattice: nacl,
        sites: [
          site(`Na`, [0, 0, 0], [0, 0, 0], { force: [0.3, 0.1, -0.5] }),
          site(`Cl`, [0.5, 0.5, 0], [2.82, 2.82, 0], { force: [-0.2, 0.4, 0.3] }),
          site(`Na`, [0.5, 0, 0.5], [2.82, 0, 2.82], { force: [0.1, -0.6, 0.2] }),
          site(`Cl`, [0, 0.5, 0.5], [0, 2.82, 2.82], { force: [-0.3, 0.2, -0.4] }),
        ],
      },
    },
    {
      title: `TiO₂ rutile — force comparison`,
      desc: `force_DFT vs force_MLFF on a multi-element oxide, both drawn via vector_configs. Use Origin Gap to separate overlapping arrows`,
      vector_configs: {
        force_DFT: { visible: true, color: `#e74c3c` },
        force_MLFF: { visible: true, color: `#3498db` },
      },
      structure: {
        lattice: tio2,
        sites: [
          {
            species: [{ element: `Ti`, occu: 1 }],
            abc: [0, 0, 0],
            xyz: [0, 0, 0],
            label: `Ti`,
            properties: {
              force_DFT: [0.1, -0.2, 0.8],
              force_MLFF: [0.12, -0.18, 0.75],
            },
          },
          {
            species: [{ element: `Ti`, occu: 1 }],
            abc: [0.5, 0.5, 0.5],
            xyz: [2.297, 2.297, 1.48],
            label: `Ti`,
            properties: {
              force_DFT: [-0.1, 0.2, -0.8],
              force_MLFF: [-0.15, 0.25, -0.82],
            },
          },
          {
            species: [{ element: `O`, occu: 1 }],
            abc: [0.305, 0.305, 0],
            xyz: [1.401, 1.401, 0],
            label: `O`,
            properties: {
              force_DFT: [0.4, 0.4, -0.1],
              force_MLFF: [0.38, 0.42, -0.08],
            },
          },
          {
            species: [{ element: `O`, occu: 1 }],
            abc: [0.695, 0.695, 0],
            xyz: [3.193, 3.193, 0],
            label: `O`,
            properties: {
              force_DFT: [-0.4, -0.4, 0.1],
              force_MLFF: [-0.35, -0.45, 0.15],
            },
          },
        ],
      },
    },
    {
      title: `Fe BCC — forces + magmoms`,
      desc: `Three vector layers: force_DFT, force_MLFF, and magmom. Only magmom draws by default (magmoms, then spins, then forces); toggle the forces under Visibility`,
      structure: {
        lattice: fe_bcc,
        sites: [
          {
            species: [{ element: `Fe`, occu: 1 }],
            abc: [0, 0, 0],
            xyz: [0, 0, 0],
            label: `Fe`,
            properties: {
              force_DFT: [0.6, -0.3, 0.2],
              force_MLFF: [0.55, -0.28, 0.18],
              magmom: [0, 0, 2.2],
            },
          },
          {
            species: [{ element: `Fe`, occu: 1 }],
            abc: [0.5, 0.5, 0.5],
            xyz: [0.718, 0.718, 0.718],
            label: `Fe`,
            properties: {
              force_DFT: [-0.6, 0.3, -0.2],
              force_MLFF: [-0.65, 0.35, -0.25],
              magmom: [0, 0, -2.2],
            },
          },
        ],
      },
    },
    {
      title: `BaTiO₃ 3×3×3 supercell — 135 atoms`,
      desc: `Large perovskite supercell with randomized force vectors on every atom`,
      structure: (() => {
        // BaTiO3 perovskite: 5 atoms/cell × 3×3×3 = 135 atoms
        const lat_a = 4.01
        const basis = [
          { elem: `Ba`, frac: [0, 0, 0] },
          { elem: `Ti`, frac: [0.5, 0.5, 0.5] },
          { elem: `O`, frac: [0.5, 0.5, 0] },
          { elem: `O`, frac: [0.5, 0, 0.5] },
          { elem: `O`, frac: [0, 0.5, 0.5] },
        ]
        const n_rep = 3
        const super_a = lat_a * n_rep
        const sites = []
        // seeded pseudo-random in [-0.5, 0.5) for reproducible vectors
        let seed = 42
        const rand = () => {
          seed = (Math.imul(seed, 1103515245) + 12345) & 0x7fffffff
          return seed / 0x80000000 - 0.5
        }
        for (let idx_x = 0; idx_x < n_rep; idx_x++) {
          for (let idx_y = 0; idx_y < n_rep; idx_y++) {
            for (let idx_z = 0; idx_z < n_rep; idx_z++) {
              for (const { elem, frac } of basis) {
                const abc = [
                  (frac[0] + idx_x) / n_rep,
                  (frac[1] + idx_y) / n_rep,
                  (frac[2] + idx_z) / n_rep,
                ]
                const xyz = [abc[0] * super_a, abc[1] * super_a, abc[2] * super_a]
                sites.push({
                  species: [{ element: elem, occu: 1 }],
                  abc,
                  xyz,
                  label: elem,
                  properties: { force: [rand() * 0.8, rand() * 0.8, rand() * 0.8] },
                })
              }
            }
          }
        }
        return {
          lattice: lattice([
            [super_a, 0, 0],
            [0, super_a, 0],
            [0, 0, super_a],
          ]),
          sites,
        }
      })(),
    },
    {
      title: `TiO₂ rutile — spins + forces`,
      desc: `Combining spin vectors on Ti atoms with force vectors on all atoms`,
      structure: {
        lattice: tio2,
        sites: [
          site(`Ti`, [0, 0, 0], [0, 0, 0], {
            force: [0.1, -0.15, 0.3],
            spin: [0.5, 0.5, 0.8],
          }),
          site(`Ti`, [0.5, 0.5, 0.5], [2.297, 2.297, 1.48], {
            force: [-0.1, 0.15, -0.3],
            spin: [-0.5, -0.5, 0.8],
          }),
          site(`O`, [0.305, 0.305, 0], [1.401, 1.401, 0], { force: [0.3, 0.3, -0.05] }),
          site(`O`, [0.695, 0.695, 0], [3.193, 3.193, 0], { force: [-0.3, -0.3, 0.05] }),
        ],
      },
    },
  ]
</script>

<ul class="vector-grid">
  {#each examples as { title, desc, structure, vector_configs } (title)}
    <li>
      <h3>{title}</h3>
      <p>{desc}</p>
      <Structure {structure} scene_props={{ show_bonds: `never`, vector_configs }} />
    </li>
  {/each}
</ul>
Props of Structure