Skip to content

Multiple Histograms

Multiple histograms as 3D layers using the penguins dataset to show the distribution of beak length of three different penguin species. Uses the bin() functions from D3.

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/penguins.json';

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

  //Create an empty Scene
  const scene = new BABYLON.Scene(engine);
  //Add some lighting
  new BABYLON.HemisphericLight('light1', new BABYLON.Vector3(0, 10, 0), 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.5, 0), scene);
  camera.position = new BABYLON.Vector3(0, 1, -2)
  camera.wheelPrecision = 20;
  camera.minZ = 0;
  camera.attachControl(true);

  //We need to bin our data for each penguin species in our dataset
  //Create our D3 bin generator with a set domain that encompasses all penguins in the entire dataset
  let bin = d3.bin().domain([30, 65]).value(d => d['Beak Length (mm)']).thresholds(30);
  //Bin our data for each variable
  let binsAdel = bin(data.filter(d => d.Species === 'Adelie'));
  let binsChin = bin(data.filter(d => d.Species === 'Chinstrap'));
  let binsGent = bin(data.filter(d => d.Species === 'Gentoo'));

  //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([30, 65]).range([-1, 1]);
  let scaleY = d3.scaleLinear().domain([0, 25]).range([0, 1]);
  let scaleZ = d3.scaleBand().domain(['Adelie', 'Chinstrap', 'Gentoo']).range([0, 0.35]).paddingInner(1).paddingOuter(0.5);
  let scaleC = d3.scaleOrdinal(anu.ordinalChromatic('d310').toStandardMaterial());
  
  //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 box meshes as children of our CoT to form a histogram for Adelie penguins
  let barsAdel = chart.bind('box', { height: 1, width: 2 / binsAdel.length, depth: 0.1 }, binsAdel)
                  .positionX((d) => scaleX((d.x0 + d.x1) / 2))
                  .positionZ(scaleZ('Adelie'))
                  .scalingY((d) => scaleY(d.length))
                  .positionY((d) => scaleY(d.length) / 2)
                  .material(scaleC('Adelie'));

  //Create box meshes as children of our CoT to form a histogram for Chinstrap penguins
  let barsChin = chart.bind('box', { height: 1, width: 2 / binsChin.length, depth: 0.1 }, binsChin)
                  .positionX((d) => scaleX((d.x0 + d.x1) / 2))
                  .positionZ(scaleZ('Chinstrap'))
                  .scalingY((d) => scaleY(d.length))
                  .positionY((d) => scaleY(d.length) / 2)
                  .material(scaleC('Chinstrap'));

  //Create box meshes as children of our CoT to form a histogram for Gentoo penguins
  let barsGent = chart.bind('box', { height: 1, width: 2 / binsGent.length, depth: 0.1 }, binsGent)
                  .positionX((d) => scaleX((d.x0 + d.x1) / 2))
                  .positionZ(scaleZ('Gentoo'))
                  .scalingY((d) => scaleY(d.length))
                  .positionY((d) => scaleY(d.length) / 2)
                  .material(scaleC('Gentoo'));

  //Use the Axes prefab with our three D3 scales
  anu.createAxes('myAxes',
    {
      scale: { x: scaleX, y: scaleY, z: scaleZ },
      labelOptions: { z: { align: 'right'}},
      labelProperties: { z: { 'rotation.z': Math.PI / 3 } },  // Rotate the z-axis labels
      parent: chart
  });

  return scene;
}