Skip to content

Vector Field

Vector field visualization of airflow using instanced cones to indicate direction. Based on an existing example from DXR, but with pre-processed data to convert the directional vector in Unity to more generic Euler angles.

js
// SPDX-License-Identifier: Apache-2.0
// Copyright : J.P. Morgan Chase & Co.

import * as anu from '@jpmorganchase/anu';
import * as BABYLON from '@babylonjs/core';
import * as d3 from 'd3';
import data from './data/jet_vector_field.json';

//Create and export a function that takes a Babylon engine and returns a Babylon Scene
export function vectorField(engine){

  //Create an empty Scene
  const scene = new BABYLON.Scene(engine);
  //Add some lighting
  new BABYLON.HemisphericLight('light1', new BABYLON.Vector3(0, 10, -5), scene);
  //Add a camera that rotates around the origin and adjust its properties
  const camera = new BABYLON.ArcRotateCamera('Camera', 0, 0, 0, new BABYLON.Vector3(0, -0.25, 0), scene);
  camera.position = new BABYLON.Vector3(-2, 0.25, -3);
  camera.wheelPrecision = 20;
  camera.minZ = 0;
  camera.attachControl(true);

  //Create the D3 functions that we will use to scale our data dimensions to desired output ranges for our visualization
  let scaleX = d3.scaleLinear().domain([0,90]).range([-1.5,1.5]).nice();
  let scaleY = d3.scaleLinear().domain([0,30]).range([-0.5,0.5]).nice();
  let scaleZ = d3.scaleLinear().domain([0,30]).range([-0.5,0.5]).nice();
  //Create a color scale that will output colors based on our specified hex values. Note that this function returns a string 'rgb(r, g, b)'
  let scaleC = d3.scaleLinear().domain([0,10,15,20]).range(["#FFFFFF", "#FFFA00", "#FF6F00", "#FF0000"]);
  //Create a scale for our opacity
  let scaleO = d3.scaleLinear().domain([0,10,15,30]).range([0,0.5,0.8,1]);

  //We use Mesh instancing here for better performance, first we create a Mesh that serves as the root Node  
  let rootCone = anu.create('cylinder', 'rootCone', { height: 0.05, diameterTop: 0, diameterBottom: 0.015});
  rootCone.isVisible = false;
  rootCone.registerInstancedBuffer('color', 4);   //We need an InstancedBuffer to set the color of instances
  rootCone.hasVertexAlpha = true;   //Required to set transparency via the color channel's alpha value

  //Create a Center of Transform TransformNode that serves the parent node for all our meshes that make up our chart
  let CoT = anu.create('cot', 'cot');
  //Select our CoT so that we have it as a Selection object
  let chart = anu.selectName('cot', scene);
  
  //Create instanced cone meshes from our rootCone as children of our CoT for each row of our data and set their visual encodings using method chaining
  let vectors = chart.bindInstance(rootCone, data)
    .position((d,n,i) => new BABYLON.Vector3(scaleX(d.Position_x), scaleY(d.Position_y), scaleZ(d.Position_z)))
    .rotation((d,n,i) => new BABYLON.Vector3(d.Rotation_x, d.Rotation_y, d.Rotation_z))
    .setInstancedBuffer('color', (d) => {
      //D3 color scales return in the format 'rgb(r, g, b)', so we need to extract its values at renormalize in the range 0..1
      let rgb = scaleC(d.Velocity_length).replace('rgb(', '').replace(')', '').split(',');
      return new BABYLON.Color4(rgb[0]/ 255, rgb[1] / 255, rgb[2] / 255, scaleO(d.Velocity_length));
    })

  //Use the Axes prefab with our three D3 scales
  anu.createAxes('myAxes',
    {
      scale: { x: scaleX, y: scaleY, z: scaleZ },
      grid: false,
      background: false,
      parent: chart
  });

  return scene;
}