From 9c5574001034f216788ca699be45c43c1e8c1414 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 25 Nov 2025 10:07:18 +0100 Subject: [PATCH 1/6] Let configure alternative draw fnction --- modules/gui/HierarchyPainter.mjs | 33 ++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/modules/gui/HierarchyPainter.mjs b/modules/gui/HierarchyPainter.mjs index 1e6f2e6c5..6870fc343 100644 --- a/modules/gui/HierarchyPainter.mjs +++ b/modules/gui/HierarchyPainter.mjs @@ -825,6 +825,8 @@ class HierarchyPainter extends BasePainter { #one_by_one; // process drop items one by one #topname; // top item name #cached_draw_object; // cached object for first draw + #draw_func; // alternative draw function + #redraw_func; // alternative redraw function /** @summary Create painter * @param {string} name - symbolic name @@ -855,6 +857,23 @@ class HierarchyPainter extends BasePainter { this.textcolor = settings.DarkMode ? '#eee' : '#111'; } + /** @summary Set alternative draw/redraw functions + * @desc If only only draw function specified - it also will be used for re-drawing + * @protected */ + setDrawFunc(_draw, _redraw) { + if (isFunc(_draw)) { + this.#draw_func = _draw; + this.#redraw_func = isFunc(_redraw) ? _redraw : _draw; + } + } + + /** @summary Invoke configured draw or redraw function + * @protected */ + async callDrawFunc(dom, obj, opt, doredraw) { + const func = doredraw ? (this.#redraw_func || redraw) : (this.#draw_func || draw); + return func(dom, obj, opt); + } + /** @summary Cleanup hierarchy painter * @desc clear drawing and browser */ cleanup() { @@ -2279,10 +2298,8 @@ class HierarchyPainter extends BasePainter { if (use_dflt_opt && !drawopt && handle?.dflt && (handle.dflt !== kExpand)) drawopt = handle.dflt; - if (dom) { - const func = updating ? redraw : draw; - return func(dom, obj, drawopt).then(p => complete(p)).catch(err => complete(null, err)); - } + if (dom) + return this.callDrawFunc(dom, obj, drawopt, updating).then(p => complete(p)).catch(err => complete(null, err)); let did_activate = false; const arr = []; @@ -2326,9 +2343,9 @@ class HierarchyPainter extends BasePainter { cleanup(frame); mdi.activateFrame(frame); - return draw(frame, obj, drawopt) - .then(p => complete(p)) - .catch(err => complete(null, err)); + return this.callDrawFunc(frame, obj, drawopt) + .then(p => complete(p)) + .catch(err => complete(null, err)); }); }); } @@ -2434,7 +2451,7 @@ class HierarchyPainter extends BasePainter { if (isFunc(dom?.addPadButtons)) dom.addPadButtons(); - return draw(dom, res.obj, opt).then(p => drop_complete(p, mp === p)); + return this.callDrawFunc(dom, res.obj, opt).then(p => drop_complete(p, mp === p)); }); } From f8ee010ea0bc6dd61177d9c5e72542e793fd8611 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 25 Nov 2025 10:49:36 +0100 Subject: [PATCH 2/6] Create batch display when no frame id is provided --- modules/gui/HierarchyPainter.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/gui/HierarchyPainter.mjs b/modules/gui/HierarchyPainter.mjs index 6870fc343..7b536c732 100644 --- a/modules/gui/HierarchyPainter.mjs +++ b/modules/gui/HierarchyPainter.mjs @@ -3609,7 +3609,7 @@ class HierarchyPainter extends BasePainter { delete this.disp; } - if (this.disp_kind === 'batch') { + if ((this.disp_kind === 'batch') || !this.disp_frameid || isNodeJs()) { const pr = isNodeJs() ? _loadJSDOM() : Promise.resolve(null); return pr.then(handle => { this.disp = new BatchDisplay(1200, 800, handle?.body); From 12a7b6e1acb0f5ca5df128dbc152d918a5caa96e Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 25 Nov 2025 10:50:51 +0100 Subject: [PATCH 3/6] Add hpainter.js demo for node It shows how file can be opened and items are draw --- demo/node/hpainter.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 demo/node/hpainter.js diff --git a/demo/node/hpainter.js b/demo/node/hpainter.js new file mode 100644 index 000000000..6db7605c6 --- /dev/null +++ b/demo/node/hpainter.js @@ -0,0 +1,23 @@ +import { version, HierarchyPainter } from 'jsroot'; + + +console.log(`JSROOT version ${version}`); + + +const hp = new HierarchyPainter('batch', null); + +hp.setDrawFunc((dom, obj, opt) => { + console.log(`trying to draw ${obj._typename}`); + return null; +}); + +await hp.openRootFile('https://root.cern/js/files/hsimple.root'); + +// display of TH2 histogram +await hp.display('hpxpy'); + +await hp.expandItem('ntuple'); + +// invoking TTree::Draw +await hp.display('ntuple/pz'); + From 437bb20e129f457ad5f627c832d608a8500f629b Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 25 Nov 2025 10:57:41 +0100 Subject: [PATCH 4/6] Do not explicitely create batch display when not configured --- modules/gui/HierarchyPainter.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/gui/HierarchyPainter.mjs b/modules/gui/HierarchyPainter.mjs index 7b536c732..12ec66051 100644 --- a/modules/gui/HierarchyPainter.mjs +++ b/modules/gui/HierarchyPainter.mjs @@ -3609,7 +3609,7 @@ class HierarchyPainter extends BasePainter { delete this.disp; } - if ((this.disp_kind === 'batch') || !this.disp_frameid || isNodeJs()) { + if (this.disp_kind === 'batch') { const pr = isNodeJs() ? _loadJSDOM() : Promise.resolve(null); return pr.then(handle => { this.disp = new BatchDisplay(1200, 800, handle?.body); @@ -3618,7 +3618,7 @@ class HierarchyPainter extends BasePainter { } // check that we can found frame where drawing should be done - if (!document.getElementById(this.disp_frameid)) + if (!this.disp_frameid || !document.getElementById(this.disp_frameid)) return null; if (isBatchMode()) From 0ec6dcc9cb9d9465c7d0bc6412071a73277a23c0 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 25 Nov 2025 10:57:50 +0100 Subject: [PATCH 5/6] Adjust hpainter.js demo --- demo/node/hpainter.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/demo/node/hpainter.js b/demo/node/hpainter.js index 6db7605c6..efb334e31 100644 --- a/demo/node/hpainter.js +++ b/demo/node/hpainter.js @@ -1,14 +1,18 @@ -import { version, HierarchyPainter } from 'jsroot'; +import { version, HierarchyPainter, draw } from 'jsroot'; console.log(`JSROOT version ${version}`); -const hp = new HierarchyPainter('batch', null); +const hp = new HierarchyPainter('hpainter'); +// configure batch display to properly handle DOM in the node.js +hp.setDisplay('batch'); + +// catch draw function calls hp.setDrawFunc((dom, obj, opt) => { console.log(`trying to draw ${obj._typename}`); - return null; + return draw(dom, obj, opt); }); await hp.openRootFile('https://root.cern/js/files/hsimple.root'); From f8d0e50acd8ab1fcff2218c74bc211761078b861 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 25 Nov 2025 11:08:43 +0100 Subject: [PATCH 6/6] Add example how SVG creation works in the hpainter batch --- demo/node/hpainter.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/demo/node/hpainter.js b/demo/node/hpainter.js index efb334e31..6f47452c9 100644 --- a/demo/node/hpainter.js +++ b/demo/node/hpainter.js @@ -1,3 +1,7 @@ +// demo how HierarchyPainter can be used without direct display +// in batch display one just able to create images + + import { version, HierarchyPainter, draw } from 'jsroot'; @@ -25,3 +29,14 @@ await hp.expandItem('ntuple'); // invoking TTree::Draw await hp.display('ntuple/pz'); + +// should be BatchDisplay +const disp = hp.getDisplay(); + +for (let id = 0; id < disp.numFrames(); ++id) { + const svg = await disp.makeSVG(id); + console.log(`Frame ${id} create svg size ${svg.length}`); + + // one can save svg plain file + // writeFileSync(`frame${id}.svg`, svg); +}