Skip to content

Embellished Chart

Visualization of the properties of fake planes using 3D models of planes as embellishment. Plane model is from the Babylon.js Assets library, licensed under CC BY 4.0. Note that this example may take a while to load.

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 { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic';    //Required to load meshes

//Create and export a function that takes a Babylon engine and returns a Babylon Scene
export async function embellishedChart(engine){   //Mark this function as async

  //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.4, 0), scene);
  camera.position = new BABYLON.Vector3(2, 0.75, -2);
  camera.wheelPrecision = 20;
  camera.minZ = 0;
  camera.attachControl(true);

  //Register the loaders so that we can load our model
  registerBuiltInLoaders();

  //Load a 3D model asynchronously, here we use one from the Babylon.js Assets library licensed under CC BY 4.0
  BABYLON.ImportMeshAsync('https://raw.githubusercontent.com/BabylonJS/Assets/refs/heads/master/meshes/aerobatic_plane.glb', scene).then(result => {
    //Get the mesh
    let planeMesh = result.meshes[0];
    
    //Create some fake data of planes
    let data = [
      {
        "model": "Plane A",
        "maxSpeed": 350,
        "weight": 2100,
        "maxAltitude": 11000
      },
      {
        "model": "Plane B",
        "maxSpeed": 150,
        "weight": 1600,
        "maxAltitude": 9500
      },
      {
        "model": "Plane C",
        "maxSpeed": 430,
        "weight": 3200,
        "maxAltitude": 11500
      },
      {
        "model": "Plane D",
        "maxSpeed": 230,
        "weight": 2100,
        "maxAltitude": 10500
      },
      {
        "model": "Plane E",
        "maxSpeed": 390,
        "weight": 2800,
        "maxAltitude": 12000
      },
      {
        "model": "Plane F",
        "maxSpeed": 190,
        "weight": 2500,
        "maxAltitude": 9000
      },
      {
        "model": "Plane G",
        "maxSpeed": 310,
        "weight": 1700,
        "maxAltitude": 10000
      },
      {
        "model": "Plane H",
        "maxSpeed": 270,
        "weight": 1500,
        "maxAltitude": 9500
      }
    ];

    //Create the D3 functions that we will use to scale our data dimensions to desired output ranges for our visualization
    let scaleX = d3.scaleBand().domain(Object.values(data.map(d => d.model))).range([-1,1]).paddingInner(1).paddingOuter(0.5);
    let scaleY = d3.scaleLinear().domain(d3.extent(data.map(d => d.maxAltitude))).range([-0.25, 0.25]);
    let scaleZ = d3.scaleLinear().domain([0, Math.max(...data.map(d => d.maxSpeed))]).range([-1, 1]).nice();
    let scaleSize = d3.scaleLinear().domain([0, Math.max(...data.map(d => d.weight))]).range([0, 2]);

    //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 clones of the plane mesh as children of our CoT for each row of our data and set their visual encodings using method chaining
    let planes = chart.bindClone(planeMesh, data)
                      .position((d) => new BABYLON.Vector3(scaleX(d.model), scaleY(d.maxAltitude) - 0.125, scaleZ(d.maxSpeed)))  //Offset y a little as the plane model's origin is not centered
                      .rotation(new BABYLON.Vector3(0, 0, 0))
                      .scaling((d) => new BABYLON.Vector3(scaleSize(d.weight), 1, scaleSize(d.weight)));
                      
    //Use the Axes prefab with our three D3 scales
    anu.createAxes('myAxes',
      {
        scale: { x: scaleX, y: scaleY, z: scaleZ },
        labelTicks: { y: scaleY.ticks(6)},
        parent: chart
      }
    );

    //Disable the original plane mesh to hide it
    planeMesh.setEnabled(false);
  });

  return scene;
}