Skip to content

Commit 628494f

Browse files
authored
Merge pull request #115 from jpmorganchase/treemap
Added a treemap example to the gallery Selection.bind() and its siblings have been updated so that they now inherit the data of their direct parent by default. This can still be overridden by manually setting the data array. This can now be done using an operator function (d,n,i) => [] as with other operators.
2 parents 8247c49 + 222aa87 commit 628494f

11 files changed

Lines changed: 194 additions & 479 deletions

File tree

anu-examples/examples/ScatterPlots/ScatterplotThinInstance.js

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -50,44 +50,44 @@ export const scatterplotThinInstance = function(engine){
5050
let root = anu.create('box', 'root', {size: 1});
5151
//root.scaling = new Vector3(0.1,0.1,0.1);
5252

53-
let thinInstance2 = anu.bindThinInstance(root, iris)
54-
.thinInstanceSetBuffer('matrix', (d,n,i) => {
55-
var bufferMatrices = new Float32Array(n.thinInstanceCount * 16 * 3);
56-
d.forEach((e, i) => {
57-
let matrix = Matrix.Scaling(0.1,0.1,0.1)
58-
matrix.copyToArray(bufferMatrices, i * 16);
59-
});
60-
return bufferMatrices;
61-
62-
})
63-
.thinInstanceSetBuffer('matrix', (d,n,i) => {
64-
var bufferMatrices = new Float32Array(n.thinInstanceCount * 16 * 3);
65-
let matricies = n.thinInstanceGetWorldMatrices();
66-
d.forEach((e, i) => {
67-
let matrix = matricies[i].multiply( Matrix.Translation(scaleX(e.sepalLength), scaleY(e.petalLength), scaleZ(e.sepalWidth)))
68-
matrix.copyToArray(bufferMatrices, i * 16);
69-
});
70-
return bufferMatrices;
71-
})
53+
// let thinInstance2 = anu.bindThinInstance(root, iris)
54+
// .thinInstanceSetBuffer('matrix', (d,n,i) => {
55+
// var bufferMatrices = new Float32Array(n.thinInstanceCount * 16 * 3);
56+
// d.forEach((e, i) => {
57+
// let matrix = Matrix.Scaling(0.1,0.1,0.1)
58+
// matrix.copyToArray(bufferMatrices, i * 16);
59+
// });
60+
// return bufferMatrices;
61+
62+
// })
63+
// .thinInstanceSetBuffer('matrix', (d,n,i) => {
64+
// var bufferMatrices = new Float32Array(n.thinInstanceCount * 16 * 3);
65+
// let matricies = n.thinInstanceGetWorldMatrices();
66+
// d.forEach((e, i) => {
67+
// let matrix = matricies[i].multiply( Matrix.Translation(scaleX(e.sepalLength), scaleY(e.petalLength), scaleZ(e.sepalWidth)))
68+
// matrix.copyToArray(bufferMatrices, i * 16);
69+
// });
70+
// return bufferMatrices;
71+
// })
7272

7373

7474
let thinInstance = chart.bindThinInstance(root, iris)
75-
.thinInstanceScaling(new Vector3(0.05,0.05,0.05))
75+
.thinInstanceScaling(() => new Vector3(Math.random(), Math.random(), Math.random()))
7676
.thinInstancePosition((d,n,i) => new Vector3(scaleX(d.sepalLength), scaleY(d.petalLength), scaleZ(d.sepalWidth)))
77-
.thinInstanceRotation(() => Vector3.Random())
78-
//.thinInstanceColor((d,n,i) => scaleC(d.species))
79-
.thinInstanceRegisterAttribute("color", 4)
80-
.thinInstanceSetAttribute("color", [0.5,0.5,0.5,1])
81-
.thinInstanceMatrixAt(0, (d,n,i) => Matrix.Translation(-1,-1,-1).multiply(n.thinInstanceGetWorldMatrices()[i]))
82-
.thinInstanceMatrixFor((d,n,i) => d.species == "setosa", Matrix.Translation(1,1,1))
83-
.thinInstancePositionFor((d,n,i) => d.species == "setosa", new Vector3(1,1,1))
84-
.thinInstanceScalingFor((d,n,i) => d.species == "setosa", new Vector3(0.1,0.1,0.1))
85-
.thinInstanceRotationFor((d,n,i) => d.species == "setosa", new Vector3(0,0,0))
86-
.thinInstanceColorFor((d,n,i) => d.species == "virginica", new Color4(0,0,0, 1))
87-
.thinInstancePositionAt(0, new Vector3(-1,-1,-1))
88-
.thinInstanceScalingAt(0, new Vector3(1,1,1))
89-
.thinInstanceRotationAt(0, new Vector3(0,0,0))
90-
.thinInstanceColorAt(0, new Color4(0,0,0,1))
77+
// .thinInstanceRotation(() => Vector3.Random())
78+
// //.thinInstanceColor((d,n,i) => scaleC(d.species))
79+
// .thinInstanceRegisterAttribute("color", 4)
80+
// .thinInstanceSetAttribute("color", [0.5,0.5,0.5,1])
81+
// .thinInstanceMatrixAt(0, (d,n,i) => Matrix.Translation(-1,-1,-1).multiply(n.thinInstanceGetWorldMatrices()[i]))
82+
// .thinInstanceMatrixFor((d,n,i) => d.species == "setosa", Matrix.Translation(1,1,1))
83+
// .thinInstancePositionFor((d,n,i) => d.species == "setosa", new Vector3(1,1,1))
84+
// .thinInstanceScalingFor((d,n,i) => new Vector3(Math.random(), Math.random(), Math.random()))
85+
// .thinInstanceRotationFor((d,n,i) => d.species == "setosa", new Vector3(0,0,0))
86+
// .thinInstanceColorFor((d,n,i) => d.species == "virginica", new Color4(0,0,0, 1))
87+
// .thinInstancePositionAt(0, new Vector3(-1,-1,-1))
88+
// .thinInstanceScalingAt(0, new Vector3(1,1,1))
89+
// .thinInstanceRotationAt(0, new Vector3(0,0,0))
90+
// .thinInstanceColorAt(0, new Color4(0,0,0,1))
9191

9292
let data = [{id:"a"},{id:"b"},{id:"c"},{id:"d"},{id:"e"}]
9393

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"name":"flare","children":[{"name":"analytics","children":[{"name":"cluster","children":[{"name":"AgglomerativeCluster","value":3938},{"name":"CommunityStructure","value":3812},{"name":"HierarchicalCluster","value":6714},{"name":"MergeEdge","value":743}]},{"name":"graph","children":[{"name":"BetweennessCentrality","value":3534},{"name":"LinkDistance","value":5731},{"name":"MaxFlowMinCut","value":7840},{"name":"ShortestPaths","value":5914},{"name":"SpanningTree","value":3416}]},{"name":"optimization","children":[{"name":"AspectRatioBanker","value":7074}]}]},{"name":"animate","children":[{"name":"Easing","value":17010},{"name":"FunctionSequence","value":5842},{"name":"interpolate","children":[{"name":"ArrayInterpolator","value":1983},{"name":"ColorInterpolator","value":2047},{"name":"DateInterpolator","value":1375},{"name":"Interpolator","value":8746},{"name":"MatrixInterpolator","value":2202},{"name":"NumberInterpolator","value":1382},{"name":"ObjectInterpolator","value":1629},{"name":"PointInterpolator","value":1675},{"name":"RectangleInterpolator","value":2042}]},{"name":"ISchedulable","value":1041},{"name":"Parallel","value":5176},{"name":"Pause","value":449},{"name":"Scheduler","value":5593},{"name":"Sequence","value":5534},{"name":"Transition","value":9201},{"name":"Transitioner","value":19975},{"name":"TransitionEvent","value":1116},{"name":"Tween","value":6006}]},{"name":"data","children":[{"name":"converters","children":[{"name":"Converters","value":721},{"name":"DelimitedTextConverter","value":4294},{"name":"GraphMLConverter","value":9800},{"name":"IDataConverter","value":1314},{"name":"JSONConverter","value":2220}]},{"name":"DataField","value":1759},{"name":"DataSchema","value":2165},{"name":"DataSet","value":586},{"name":"DataSource","value":3331},{"name":"DataTable","value":772},{"name":"DataUtil","value":3322}]},{"name":"display","children":[{"name":"DirtySprite","value":8833},{"name":"LineSprite","value":1732},{"name":"RectSprite","value":3623},{"name":"TextSprite","value":10066}]},{"name":"flex","children":[{"name":"FlareVis","value":4116}]},{"name":"physics","children":[{"name":"DragForce","value":1082},{"name":"GravityForce","value":1336},{"name":"IForce","value":319},{"name":"NBodyForce","value":10498},{"name":"Particle","value":2822},{"name":"Simulation","value":9983},{"name":"Spring","value":2213},{"name":"SpringForce","value":1681}]},{"name":"query","children":[{"name":"AggregateExpression","value":1616},{"name":"And","value":1027},{"name":"Arithmetic","value":3891},{"name":"Average","value":891},{"name":"BinaryExpression","value":2893},{"name":"Comparison","value":5103},{"name":"CompositeExpression","value":3677},{"name":"Count","value":781},{"name":"DateUtil","value":4141},{"name":"Distinct","value":933},{"name":"Expression","value":5130},{"name":"ExpressionIterator","value":3617},{"name":"Fn","value":3240},{"name":"If","value":2732},{"name":"IsA","value":2039},{"name":"Literal","value":1214},{"name":"Match","value":3748},{"name":"Maximum","value":843},{"name":"methods","children":[{"name":"add","value":593},{"name":"and","value":330},{"name":"average","value":287},{"name":"count","value":277},{"name":"distinct","value":292},{"name":"div","value":595},{"name":"eq","value":594},{"name":"fn","value":460},{"name":"gt","value":603},{"name":"gte","value":625},{"name":"iff","value":748},{"name":"isa","value":461},{"name":"lt","value":597},{"name":"lte","value":619},{"name":"max","value":283},{"name":"min","value":283},{"name":"mod","value":591},{"name":"mul","value":603},{"name":"neq","value":599},{"name":"not","value":386},{"name":"or","value":323},{"name":"orderby","value":307},{"name":"range","value":772},{"name":"select","value":296},{"name":"stddev","value":363},{"name":"sub","value":600},{"name":"sum","value":280},{"name":"update","value":307},{"name":"variance","value":335},{"name":"where","value":299},{"name":"xor","value":354},{"name":"_","value":264}]},{"name":"Minimum","value":843},{"name":"Not","value":1554},{"name":"Or","value":970},{"name":"Query","value":13896},{"name":"Range","value":1594},{"name":"StringUtil","value":4130},{"name":"Sum","value":791},{"name":"Variable","value":1124},{"name":"Variance","value":1876},{"name":"Xor","value":1101}]},{"name":"scale","children":[{"name":"IScaleMap","value":2105},{"name":"LinearScale","value":1316},{"name":"LogScale","value":3151},{"name":"OrdinalScale","value":3770},{"name":"QuantileScale","value":2435},{"name":"QuantitativeScale","value":4839},{"name":"RootScale","value":1756},{"name":"Scale","value":4268},{"name":"ScaleType","value":1821},{"name":"TimeScale","value":5833}]},{"name":"util","children":[{"name":"Arrays","value":8258},{"name":"Colors","value":10001},{"name":"Dates","value":8217},{"name":"Displays","value":12555},{"name":"Filter","value":2324},{"name":"Geometry","value":10993},{"name":"heap","children":[{"name":"FibonacciHeap","value":9354},{"name":"HeapNode","value":1233}]},{"name":"IEvaluable","value":335},{"name":"IPredicate","value":383},{"name":"IValueProxy","value":874},{"name":"math","children":[{"name":"DenseMatrix","value":3165},{"name":"IMatrix","value":2815},{"name":"SparseMatrix","value":3366}]},{"name":"Maths","value":17705},{"name":"Orientation","value":1486},{"name":"palette","children":[{"name":"ColorPalette","value":6367},{"name":"Palette","value":1229},{"name":"ShapePalette","value":2059},{"name":"SizePalette","value":2291}]},{"name":"Property","value":5559},{"name":"Shapes","value":19118},{"name":"Sort","value":6887},{"name":"Stats","value":6557},{"name":"Strings","value":22026}]},{"name":"vis","children":[{"name":"axis","children":[{"name":"Axes","value":1302},{"name":"Axis","value":24593},{"name":"AxisGridLine","value":652},{"name":"AxisLabel","value":636},{"name":"CartesianAxes","value":6703}]},{"name":"controls","children":[{"name":"AnchorControl","value":2138},{"name":"ClickControl","value":3824},{"name":"Control","value":1353},{"name":"ControlList","value":4665},{"name":"DragControl","value":2649},{"name":"ExpandControl","value":2832},{"name":"HoverControl","value":4896},{"name":"IControl","value":763},{"name":"PanZoomControl","value":5222},{"name":"SelectionControl","value":7862},{"name":"TooltipControl","value":8435}]},{"name":"data","children":[{"name":"Data","value":20544},{"name":"DataList","value":19788},{"name":"DataSprite","value":10349},{"name":"EdgeSprite","value":3301},{"name":"NodeSprite","value":19382},{"name":"render","children":[{"name":"ArrowType","value":698},{"name":"EdgeRenderer","value":5569},{"name":"IRenderer","value":353},{"name":"ShapeRenderer","value":2247}]},{"name":"ScaleBinding","value":11275},{"name":"Tree","value":7147},{"name":"TreeBuilder","value":9930}]},{"name":"events","children":[{"name":"DataEvent","value":2313},{"name":"SelectionEvent","value":1880},{"name":"TooltipEvent","value":1701},{"name":"VisualizationEvent","value":1117}]},{"name":"legend","children":[{"name":"Legend","value":20859},{"name":"LegendItem","value":4614},{"name":"LegendRange","value":10530}]},{"name":"operator","children":[{"name":"distortion","children":[{"name":"BifocalDistortion","value":4461},{"name":"Distortion","value":6314},{"name":"FisheyeDistortion","value":3444}]},{"name":"encoder","children":[{"name":"ColorEncoder","value":3179},{"name":"Encoder","value":4060},{"name":"PropertyEncoder","value":4138},{"name":"ShapeEncoder","value":1690},{"name":"SizeEncoder","value":1830}]},{"name":"filter","children":[{"name":"FisheyeTreeFilter","value":5219},{"name":"GraphDistanceFilter","value":3165},{"name":"VisibilityFilter","value":3509}]},{"name":"IOperator","value":1286},{"name":"label","children":[{"name":"Labeler","value":9956},{"name":"RadialLabeler","value":3899},{"name":"StackedAreaLabeler","value":3202}]},{"name":"layout","children":[{"name":"AxisLayout","value":6725},{"name":"BundledEdgeRouter","value":3727},{"name":"CircleLayout","value":9317},{"name":"CirclePackingLayout","value":12003},{"name":"DendrogramLayout","value":4853},{"name":"ForceDirectedLayout","value":8411},{"name":"IcicleTreeLayout","value":4864},{"name":"IndentedTreeLayout","value":3174},{"name":"Layout","value":7881},{"name":"NodeLinkTreeLayout","value":12870},{"name":"PieLayout","value":2728},{"name":"RadialTreeLayout","value":12348},{"name":"RandomLayout","value":870},{"name":"StackedAreaLayout","value":9121},{"name":"TreeMapLayout","value":9191}]},{"name":"Operator","value":2490},{"name":"OperatorList","value":5248},{"name":"OperatorSequence","value":4190},{"name":"OperatorSwitch","value":2581},{"name":"SortOperator","value":2023}]},{"name":"Visualization","value":16540}]}]}

docs/anu-examples/spheres_bind.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const spheresBind = function(engine){
1212
new HemisphericLight('light1', new Vector3(0, 10, 0), scene)
1313

1414
const camera = new ArcRotateCamera("Camera", -(Math.PI / 2), (Math.PI / 2), 5, new Vector3(0, 0, 0), scene);
15+
camera.position = new Vector3(0,4,-8)
1516
camera.attachControl(true)
1617

1718
//Use a top level bind to create a Selection containing
@@ -32,7 +33,7 @@ export const spheresBind = function(engine){
3233
//Each sphere under cot will now be the parent of a box mesh.
3334
let boxes = spheres.bind('box')
3435

35-
boxes.positionY(-1)
36+
boxes.positionZ(d => d)
3637

3738
// Inspector.Show(scene, {
3839
// embedMode: true,

docs/anu-examples/treemap.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright : J.P. Morgan Chase & Co.
3+
4+
import * as anu from '@jpmorganchase/anu';
5+
import * as BABYLON from '@babylonjs/core';
6+
import * as d3 from 'd3';
7+
import data from './data/flare-d3.json';
8+
9+
//Create and export a function that takes a Babylon engine and returns a Babylon Scene
10+
export function treemap(engine){
11+
12+
//Create an empty Scene
13+
const scene = new BABYLON.Scene(engine);
14+
//Add some lighting
15+
new BABYLON.HemisphericLight('light1', new BABYLON.Vector3(0, 10, -5), scene);
16+
//Add a camera that rotates around the origin and adjust its properties
17+
const camera = new BABYLON.ArcRotateCamera('Camera', 0, 0, 0, new BABYLON.Vector3(0, 0, 0), scene);
18+
camera.position = new BABYLON.Vector3(0, 0, -10);
19+
camera.wheelPrecision = 20;
20+
camera.minZ = 0;
21+
camera.attachControl(true);
22+
23+
let width = 5;
24+
let height = 5;
25+
26+
// Compute the layout.
27+
const root = d3.treemap() //d3.treemap() will calculate the tree map leaf sizes and layout
28+
.tile(d3.treemapSquarify) //Squarify is the default but d3 also provides other tile methods like treemapBinary
29+
.size([width,height]) //These need to be positive values or it will not work correctly, we will account for this later
30+
.padding(0.01) // This value is relative to the total Width x Height
31+
(d3.hierarchy(data)
32+
.sum(d => d.value)
33+
.sort((a, b) => b.value - a.value));
34+
35+
let tree = anu.bind('cot').name("treemap") //Create a overall parent for out treemap
36+
37+
let leafs = tree.bind('cot', {}, root.leaves()) //Append a parent for each leaf in our data generated from treemap()
38+
.positionX((d) => (((d.x1 - d.x0) / 2) + d.x0)) //We need to reset our origin point to top-left from middle then add the offset
39+
.positionY((d) => -(((d.y1 - d.y0) / 2) + d.y0))
40+
41+
let rootBox = anu.create('box') //Since we have many boxes to render lets use instances, so we create a root mesh to instance.
42+
rootBox.registerInstancedBuffer("color", 4); //Set up our color buffer to add color to each instance
43+
rootBox.instancedBuffers.color = new BABYLON.Color4(1,1,1,1);
44+
rootBox.setEnabled(false) //Disable our root box so it is invisible and skipped for rendering
45+
46+
let depth = d3.scaleLinear().domain(d3.extent(root.leaves().map(d => d.value))).range([0,1.5]) //Create a depth scale to encode value of each leaf
47+
let color = d3.scaleOrdinal(anu.ordinalChromatic('pastel1').toColor4()) //Create a color scale to color parent catagories
48+
49+
let boxes = leafs.bindInstance(rootBox) // using our root mesh bind instances from our leafs selection, our data will be inherited by each instanced box
50+
.scalingX(d => (d.x1 - d.x0) ) // scale width from our treemap() generator
51+
.scalingY(d => (d.y1 - d.y0) ) // scale height
52+
.scalingZ(d => depth(d.value) ) // scale depth
53+
.setInstancedBuffer("color", (d) => { while (d.depth > 1) d = d.parent; return color(d.data.name); }); //set the color buffer with our scale to the parent name
54+
55+
let labels = leafs.bind("planeText", {text: d => d.data.name, size: d => (d.x1 - d.x0) * 0.1}) // bind text to each leaf, inherit the data to get the leaf name and size
56+
.positionZ(d => (-depth(d.value) / 2) - 0.01) // move up each label to be in front of each box.
57+
58+
59+
tree.positionX(-(width/2)).positionY(height/2) // re-center our treemap in the scene
60+
61+
return scene;
62+
}

docs/examples/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,11 @@ layout: page
6666
</div>
6767
</div>
6868
<div class='section'>
69-
<h2>Networks</h2>
69+
<h2>Networks and Hierarchies</h2>
7070
<hr>
7171
<div class="cards">
7272
<cardImg title="Node Link 3D" img="/anu/assets/example-images/nodeLink3D.webp" link="/anu/examples/node_link_3d"></cardImg>
73+
<cardImg title="Treemap" img="/anu/assets/example-images/treemap.webp" link="/anu/examples/treemap"></cardImg>
7374
</div>
7475
</div>
7576
<div class='section'>

docs/examples/treemap.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
aside: false
3+
---
4+
<script setup>
5+
import { treemap } from '../anu-examples/treemap.js'
6+
</script>
7+
8+
# Treemap
9+
An example of a 3D treemap using `d3.treemap()` adapted from [@D3](https://observablehq.com/@d3/treemap/2).
10+
11+
<singleView :scene="treemap" />
12+
13+
::: code-group
14+
<<< @/./anu-examples/treemap.js
15+
:::

docs/guide/using_selections.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ let spheres = cot.bind('sphere', { diameter: 1 }, [-2, 0, 2])
154154

155155
<inlineView scene="cot_bind" :inspector="true" />
156156

157-
If we call bind() on a Selection with more than one Node, it will repeat the method for each Node in the Selection. This is how the majority of Selection methods function. Here we call bind() on our Selection of spheres. This will create a box Mesh as a child of each sphere.
157+
If we call bind() on a Selection with more than one Node, it will repeat the method for each Node in the Selection. This is how the majority of Selection methods function. Here we call bind() on our Selection of spheres. This will create a box Mesh as a child of each sphere with each box inheriting its parent spheres data by default. We can override this behavior by passing a new data array to our bind method `bind('mesh-type', {options}, [new data])` or using a function that returns a new data array as with other operators `bind('mesh-type', {options}, (d,n,i) => [new data])`
158158

159159
::: code-group
160160
```js [js]
@@ -163,9 +163,9 @@ let cot = anu.bind('cot');
163163
let spheres = cot.bind('sphere', { diameter: 1 }, [-2, 0, 2])
164164
.positionX(d => d);
165165

166-
//Calling bind() on our Selection of spheres will bind new Nodes as children of each sphere
166+
//Calling bind() on our Selection of spheres will bind new Nodes as children of each sphere and by default will inherit the data bound to its parent
167167
let boxes = spheres.bind('box');
168-
.positionY(-1);
168+
.positionY(d=>d);
169169
```
170170
:::
171171

0 commit comments

Comments
 (0)