Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
844 changes: 551 additions & 293 deletions build/jsroot.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Changes in dev
1. RNtuple support, thanks to Kriti Mahajan (https://github.com/Krmjn09)
1. Implement RTreeMapPainter to display RNTuple structure, thanks to Patryk Pilichowski (https://github.com/magnustymoteus)
1. Implement `build3d` function for supported classes #368
1. Let use hex colors in histogram draw options like "fill_00ff00" or "line_77aa1166"
1. Let configure exact axis ticks position via draw option like "xticks:[-3,-1,1,3]"
1. Support gStyle.fBarOffset for `TGraph` bar drawing
Expand Down
144 changes: 144 additions & 0 deletions demo/hist_build3d.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js model for histogram objects</title>
<meta charset="utf-8">
<link rel="shortcut icon" href="../img/RootIcon.ico"/>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
font-family: Monospace;
background-color: #000;
margin: 0px;
overflow: hidden;
}
</style>
<script type="importmap">
{ "imports": { "jsroot": "../modules/main.mjs", "three": "../modules/three.mjs" } }
</script>
</head>

<body>
</body>

<script type='module'>

import { decodeUrl, httpRequest, create, openFile, treeDraw, build3d } from 'jsroot';

import { Box3, Vector3, PerspectiveCamera, Scene, AmbientLight, DirectionalLight, DoubleSide,
MeshLambertMaterial, Mesh, TetrahedronGeometry, WebGLRenderer } from 'three';

import Stats from 'https://threejs.org/examples/jsm/libs/stats.module.js';

let container, stats, camera, scene, renderer, draw_size = 400, dummy = null,
d = decodeUrl();

function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}

function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}

function render() {
let timer = Date.now() * 0.0001;
camera.lookAt( scene.position );

for (let i = 0, l = scene.children.length; i < l; i++) {
let object = scene.children[ i ];
object.rotation.z = timer * 5;
}

renderer.render( scene, camera );
}

async function create3d(obj, opt, x = 0, lbl = '', scale = 1) {
return build3d(obj, opt).then(obj3d => {
obj3d.position.x = x;
if (scale !== 1)
obj3d.scale.set(scale, scale, scale);

scene.add(obj3d);

const latex = create('TLatex');
latex.fTitle = lbl;
latex.fTextAlign = 22; // center of text
latex.fTextColor = 3; // green
latex.fTextSize = 10; // absolute size

return build3d(latex);
}).then(text3d => {
// latex created in X/Y coordinates,
text3d.traverse(obj3d => obj3d.geometry?.rotateX(Math.PI / 2));

text3d.position.x = x;
text3d.position.z = -100;
scene.add(text3d);
});
}

container = document.createElement('div');
document.body.appendChild(container);

camera = new PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, draw_size*5 );
camera.position.y = draw_size*2;
camera.position.z = draw_size/2;
camera.far = draw_size * 5;
camera.up.set(0, 0, 1); // z scale up

scene = new Scene();
scene.add(new AmbientLight(0x404040));

let light = new DirectionalLight( 0xffffff );
light.position.set(0, 1, 0);
scene.add( light );

renderer = new WebGLRenderer({ antialias: true });
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor('white', 1);

container.appendChild( renderer.domElement );

stats = new Stats();
container.appendChild( stats.dom );

window.addEventListener( 'resize', onWindowResize, false );

let filename = 'https://root.cern/js/files/hsimple.root',
filename2 = 'https://jsroot.gsi.de/files/graph2d.root';

let file = await openFile(filename);
let hist2 = await file.readObject('hpxpy');

await create3d(hist2, 'lego2', 150, '#color[2]{TH2} #color[4]{lego} plot');

let tuple = await file.readObject('ntuple');
let hist3 = await treeDraw(tuple, 'px:py:pz;hbins:15');

await create3d(hist3, 'box3', -150, '#color[2]{TH3} #color[4]{box} plot');

let hist1 = await file.readObject('hpx');

await create3d(hist1, 'lego2', 400, '#color[2]{TH1} #color[4]{lego} plot');

let geom = await httpRequest('https://root.cern/js/files/geom/simple_alice.json.gz', 'object');
await create3d(geom, '', -400, '#color[2]{Geometry} build', 0.2);

let file2 = await openFile(filename2);
let gr2 = await file2.readObject('Graph2D');

await create3d(gr2, 'p', 650, '#color[2]{TGraph2D} drawing with #color[4]{p}');

camera.updateProjectionMatrix();

animate();

</script>

</html>
19 changes: 14 additions & 5 deletions modules/base/base3d.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,14 @@ function getRender3DKind(render3d, is_batch) {
if (is_batch === undefined)
is_batch = isBatchMode();

if (!render3d) render3d = is_batch ? settings.Render3DBatch : settings.Render3D;
if (!render3d)
render3d = is_batch ? settings.Render3DBatch : settings.Render3D;
const rc = constants.Render3D;

if (render3d === rc.Default) render3d = is_batch ? rc.WebGLImage : rc.WebGL;
if (is_batch && (render3d === rc.WebGL)) render3d = rc.WebGLImage;
if (render3d === rc.Default)
render3d = is_batch ? rc.WebGLImage : rc.WebGL;
if (is_batch && (render3d === rc.WebGL))
render3d = rc.WebGLImage;

return render3d;
}
Expand Down Expand Up @@ -518,11 +521,14 @@ async function createRender3D(width, height, render3d, args) {

render3d = getRender3DKind(render3d);

if (!args) args = { antialias: true, alpha: true };
if (!args)
args = { antialias: true, alpha: true };

let promise;

if (render3d === rc.SVG) {
if (render3d === rc.None)
promise = Promise.resolve(null);
else if (render3d === rc.SVG) {
// SVG rendering
const r = createSVGRenderer(false, 0, doc);
r.jsroot_dom = doc.createElementNS(nsSVG, 'svg');
Expand Down Expand Up @@ -565,6 +571,9 @@ async function createRender3D(width, height, render3d, args) {
}

return promise.then(renderer => {
if (!renderer)
return renderer;

if (!renderer.jsroot_dom)
renderer.jsroot_dom = renderer.domElement;
else
Expand Down
6 changes: 5 additions & 1 deletion modules/core.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const version_id = 'dev',

/** @summary version date
* @desc Release date in format day/month/year like '14/04/2022' */
version_date = '25/09/2025',
version_date = '29/09/2025',

/** @summary version id and date
* @desc Produced by concatenation of {@link version_id} and {@link version_date}
Expand Down Expand Up @@ -165,13 +165,17 @@ const constants = {
WebGLImage: 2,
/** @summary Use SVG rendering, slow, imprecise and not interactive, not recommended */
SVG: 3,
/** @summary Disable renderer, used for three.js model creation, only for internal use recommended */
None: 4,
fromString(s) {
if ((s === 'webgl') || (s === 'gl'))
return this.WebGL;
if (s === 'img')
return this.WebGLImage;
if (s === 'svg')
return this.SVG;
if (s === 'none')
return this.None;
return this.Default;
}
},
Expand Down
28 changes: 26 additions & 2 deletions modules/draw.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ drawFuncs = { lst: [
{ name: clTDiamond, sameas: clTPave },
{ name: clTLegend, icon: 'img_pavelabel', sameas: clTPave },
{ name: clTPaletteAxis, icon: 'img_colz', sameas: clTPave },
{ name: clTLatex, icon: 'img_text', draw: () => import_more().then(h => h.drawText), direct: true },
{ name: clTLatex, icon: 'img_text', draw: () => import_more().then(h => h.drawText), build3d: () => import('./hist/hist3d.mjs').then(h => h.build3dlatex), direct: true },
{ name: clTMathText, sameas: clTLatex },
{ name: clTText, sameas: clTLatex },
{ name: clTLink, sameas: clTText },
Expand Down Expand Up @@ -548,6 +548,30 @@ async function redraw(dom, obj, opt) {
return draw(dom, obj, opt);
}

/** @summary Create three.js model for object
* @param {object} obj - object
* @param {string} opt - draw options
* @return {Promise} with three.js model */

async function build3d(obj, opt) {
if (!isObject(obj) || !obj?._typename)
return Promise.reject(Error('not an object in build3d'));

const handle = getDrawHandle(getKindForType(obj._typename));
if (!handle?.class && !handle.build3d)
return Promise.reject(Error(`not able to create three.js for ${obj._typename}`));

if (handle.build3d)
return handle.build3d().then(func => func(obj, opt));

return handle.class().then(cl => {
if (!isFunc(cl?.build3d))
return Promise.reject(Error(`painter class for ${obj._typename} does not implement build3d method`));

return cl.build3d(obj, opt);
});
}

/** @summary Scan streamer infos for derived classes
* @desc Assign draw functions for such derived classes
* @private */
Expand Down Expand Up @@ -756,4 +780,4 @@ Object.assign(internals, { addStreamerInfosForPainter, addDrawFunc, setDefaultDr
Object.assign(internals.jsroot, { draw, redraw, makeSVG, makeImage, addDrawFunc });

export { addDrawFunc, getDrawHandle, canDrawHandle, getDrawSettings, setDefaultDrawOpt,
draw, redraw, cleanup, makeSVG, makeImage, assignPadPainterDraw };
draw, redraw, cleanup, build3d, makeSVG, makeImage, assignPadPainterDraw };
Loading
Loading