Skip to content

Transform Widget UI

Overview

The Transform Widget UI prefab allows you to quickly attach 3D UI widgets for manipulating position, rotation, and scale to elements in a selection. These prefabs use pointer down and out events and pointer behaviors to perform the interaction. The mesh based UI and behavior can be customized through the method options, or after creation via selection. We use pointer based interactions as it better supports multiple platforms and one handed interaction for better accessability. These methods are members of the Selection class and will create a UI for each node in a selection. As such, it is recommended to call the prefab functions from root node selections.

Usage

js
let root = anu.bind("cot") //Returns a selection of the single root transform node

root.positionUI(options?: {})
    .rotateUI(options?: {})
    .scaleUI(options?: {})

Options

Position Widget UI

PropertyValueDefault
name(string) name prefix for UI and UI parentnode.name + "PositionUI"
width(number) the width of the capsule meshhalf the width of the parent node bounding box
radius(number) the radius of the capsule mesh5% the width
position(Vector3) the starting position of the capsule meshbottom front position of the parent node bounding box
offset(Vector3) the offset from the starting position2.5 times the radius -y axis
material(Material) the material of the capsule meshStandardMaterial
diffuseColor(Color3Color4) the diffuse color of the material if not defined
visibility(number) the visibility of the capsule 0-11
behavior(SixDofDragBehavior) the behavior attached to the parent nodeSixDofDragBehavior forceCameraOnDragStart=true rotateAroundYOnly=true
billboard(number) the billboard mode setting for the parent mesh0

Rotation Widget UI

PropertyValueDefault
name(string) name prefix for UI and UI parentnode.name + "RotationUI"
axis({xy
diameter(number) the diameter of the torus mesh5% the width of the parent node bounding box
thickness(number) the thickness of the torus meshhalf the diameter
position(Vector3) the starting position of the capsule meshbottom front position of the parent node bounding box
offset(Vector3) the offset from the starting position2.5 times the radius + 2.5 times the diameter -y axis
material(Material) the material of the torus meshStandardMaterial
diffuseColor(Color3Color4) the diffuse color of the material if not defined
visibility(number) the visibility of the torus 0-11
billboard(number) the billboard mode setting for the parent mesh0

Scale Widget UI

PropertyValueDefault
name(string) name prefix for UI and UI parentnode.name + "ScaleUI"
diameter(number) the radius of the sphere mesh5% the width of the parent bounding box
position(Vector3) the starting position of the sphere meshbottom front position of the parent node bounding box
offset(Vector3) the offset from the starting position2.5 times the radius -y axis and the width of the hypothetical positionUI z axis
material(Material) the material of the sphere meshStandardMaterial
diffuseColor(Color3Color4) the diffuse color of the material if not defined
visibility(number) the visibility of the sphere 0-11
behavior(PointerDragBehavior) the behavior attached to the parent nodePointerDragBehavior dragAxis (0,1,0) moveAttached=false
billboard(number) the billboard mode setting for the parent mesh0
minimum(number) minimum scale factorinfinity
maximum(number) maximum scale factorinfinity

Examples

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

import * as anu from '@jpmorganchase/anu';
import * as d3 from "d3";
import { Scene, HemisphericLight, ArcRotateCamera, Vector3, ActionManager, InterpolateValueAction } from '@babylonjs/core';
import iris from './data/iris.json' assert {type: 'json'};  //Our data

export function transformWidget(engine) {

  //Setup basic Babylon variables
  const scene = new Scene(engine);

  new HemisphericLight('light1', new Vector3(0, 10, 0), scene);

  const camera = new ArcRotateCamera("Camera", -(Math.PI / 4) * 3, Math.PI / 4, 10, new Vector3(0, 0, 0), scene);
  camera.wheelPrecision = 20;
  camera.minZ = 0;
  camera.attachControl(true);
  camera.position = new Vector3(2, 0, -5.5);

  //D3 scales
  let scaleX = d3.scaleLinear().domain(d3.extent(d3.map(iris, (d) => {return d.sepalLength}))).range([-1,1]).nice();
  let scaleY = d3.scaleLinear().domain(d3.extent(d3.map(iris, (d) => {return d.petalLength}))).range([-1,1]).nice();
  let scaleZ = d3.scaleLinear().domain(d3.extent(d3.map(iris, (d) => {return d.sepalWidth}))).range([-1,1]).nice();
  let scaleC = d3.scaleOrdinal(anu.ordinalChromatic('d310').toStandardMaterial());

  //Create CoT
  let CoT = anu.create("cot", "cot");
  let chart = anu.selectName('cot', scene);

  //Create spheres with simple mouse over animations
  let spheres = chart.bind('sphere', { diameter: 0.05 }, iris)
                     .position((d) => new Vector3(scaleX(d.sepalLength), scaleY(d.petalLength), scaleZ(d.sepalWidth)))
                     .material((d) => scaleC(d.species))
                     .action((d,n,i) => new InterpolateValueAction(
                           ActionManager.OnPointerOverTrigger,
                           n,
                           'scaling',
                           new Vector3(1.2, 1.2, 1.2),
                           100
                       ))
                       .action((d,n,i) => new InterpolateValueAction(
                         ActionManager.OnPointerOutTrigger,
                         n,
                         'scaling',
                         new Vector3(1, 1, 1),
                         100
                       ));

  //Create axes
  anu.createAxes('test', scene, { parent: chart, scale: { x: scaleX, y: scaleY, z: scaleZ } })

  //Enable Anu's UI prefab to allow for position, rotation, and scaling
  chart.positionUI()
       .rotateUI()
       .scaleUI({ minimum: 0.5, maximum: 2 });

  return scene;
};