Skip to content

Commit 6173e6e

Browse files
committed
[jsroot] dev 17/03/2026
1. Implement new data types in `RNtuple` - reduced float types kFloat16, kReal32Trunc, kReal32Quant - `std::vector` - `std::map`, `std::unordered_map`, `std::multimap`, `std::unordered_multimap` with `std::pair` - `std::set`, `std::unordered_set`, `std::multiset`, `std::unordered_multiset` - `std::array` - `std::variant` - `std::tuple` - `std::bitset` - `std::atomic` - simple custom classes - streamed types 1. Resort order of ranges in http request, fixing several long-standing problems #374 1. Implement for `TPie` 3d, text, title drawing including interactivity 1. Implement `TCanvas` support in `build3d` function #373 1. Implements `TTree` branches filtering via context menu #364 1. Let define alternative draw function #378 1. Implement padsN draw option for `THStack` and `TMultiGraph` 1. Support custom click handler for `TGraph` https://root-forum.cern.ch/t/64744 1. Use `resvg-js` backend for PNG support in node.js #391, thanks to https://github.com/OmarMesqq 1. Remove support for deprectaed `TH1K` class 1. Introduce `settings.ServerTimeout` global timeout for THttpServer operations 1. Let set custom color palette with `setColorPalette` function 1. Upgrade three.js r180 -> r183 1. Fix - paint frame border mode/size from `TCanvas` 1. Fix - interactivity for TH3 palette drawing #398 1. Fix - add `TLeafG` support in `TTree` #397 2. Fix - reset contour while drawing `TH3` 3. Fix - fix kFloat16/kDouble32 processing in `TTree` 1. Fix - correctly process `TLeafB` arrays in tree draw #384 2. Fix - better detect default ranges in `TGraph` histogram 3. Fix - convert BigInt before `RNtuple` drawing 4. Fix - pages and clusters processing in `RNtuple` #390 5. Fix - extra row for legend header, proper horizontal align #21173 1. Fix - proper paint axis labels on both sides when pad.fTickx/y = 2 2. Fix - recover io after bad http response
1 parent 417e957 commit 6173e6e

34 files changed

Lines changed: 12881 additions & 6789 deletions

js/LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License
22

3-
Copyright � 2013-2025 JavaScript ROOT authors
3+
Copyright � 2013-2026 JavaScript ROOT authors
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

js/build/jsroot.js

Lines changed: 5973 additions & 3108 deletions
Large diffs are not rendered by default.

js/changes.md

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,51 @@
22

33

44
## Changes in dev
5+
1. Implement new data types in `RNtuple`
6+
- reduced float types kFloat16, kReal32Trunc, kReal32Quant
7+
- `std::vector`
8+
- `std::map`, `std::unordered_map`, `std::multimap`, `std::unordered_multimap` with `std::pair`
9+
- `std::set`, `std::unordered_set`, `std::multiset`, `std::unordered_multiset`
10+
- `std::array`
11+
- `std::variant`
12+
- `std::tuple`
13+
- `std::bitset`
14+
- `std::atomic`
15+
- simple custom classes
16+
- streamed types
17+
1. Resort order of ranges in http request, fixing several long-standing problems #374
518
1. Implement for `TPie` 3d, text, title drawing including interactivity
6-
1. Remove support for deprectaed TH1K class
19+
1. Implement `TCanvas` support in `build3d` function #373
20+
1. Implements `TTree` branches filtering via context menu #364
21+
1. Let define alternative draw function #378
22+
1. Implement padsN draw option for `THStack` and `TMultiGraph`
23+
1. Support custom click handler for `TGraph` https://root-forum.cern.ch/t/64744
24+
1. Use `resvg-js` backend for PNG support in node.js #391, thanks to https://github.com/OmarMesqq
25+
1. Remove support for deprectaed `TH1K` class
26+
1. Introduce `settings.ServerTimeout` global timeout for THttpServer operations
27+
1. Let set custom color palette with `setColorPalette` function
28+
1. Upgrade three.js r180 -> r183
29+
1. Fix - paint frame border mode/size from `TCanvas`
30+
1. Fix - interactivity for TH3 palette drawing #398
31+
32+
33+
## Changes in 7.10.3
34+
1. Fix - add `TLeafG` support in `TTree` #397
35+
2. Fix - reset contour while drawing `TH3`
36+
3. Fix - fix kFloat16/kDouble32 processing in `TTree`
37+
38+
39+
## Changes in 7.10.2
40+
1. Fix - correctly process `TLeafB` arrays in tree draw #384
41+
2. Fix - better detect default ranges in `TGraph` histogram
42+
3. Fix - convert BigInt before `RNtuple` drawing
43+
4. Fix - pages and clusters processing in `RNtuple` #390
44+
5. Fix - extra row for legend header, proper horizontal align https://github.com/root-project/root/issues/21173
45+
46+
47+
## Changes in 7.10.1
748
1. Fix - proper paint axis labels on both sides when pad.fTickx/y = 2
8-
1. Fix - paint frame border mode/size from TCanvas
49+
2. Fix - recover io after bad http response
950

1051

1152
## Changes in 7.10.0
@@ -1469,11 +1510,11 @@
14691510
8. Fix several problems with markers drawing; implement plus, asterisk, mult symbols.
14701511
9. Implement custom layout, which allows to configure user-defined layout for displayed objects
14711512
10. Fix errors with scaling of axis labels.
1472-
11. Support also Y axis with custom labels like: http://jsroot.gsi.de/dev/?nobrowser&file=../files/atlas.root&item=LEDShapeHeightCorr_Gain0;1&opt=col
1513+
11. Support also Y axis with custom labels like: https://jsroot.gsi.de/dev/?nobrowser&file=https://jsroot.gsi.de/files/atlas.root&item=LEDShapeHeightCorr_Gain0;1&opt=col
14731514

14741515

14751516
## Changes in 3.7
1476-
1. Support of X axis with custom labels like: http://jsroot.gsi.de/dev/?nobrowser&json=../files/hist_xlabels.json
1517+
1. Support of X axis with custom labels like: https://jsroot.gsi.de/dev/?nobrowser&json=https://jsroot.gsi.de/files/hist_xlabels.json
14771518
2. Extend functionality of JSROOT.addDrawFunc() function. One could register type-specific
14781519
`make_request` and `after_request` functions; `icon`, `prereq`, `script`, `monitor` properties.
14791520
This let add more custom elements to the generic gui, implemented with JSROOT.HierarchyPainter
@@ -1660,7 +1701,7 @@
16601701
13. Provide example fileitem.htm how read and display item from ROOT file.
16611702
14. In default index.htm page one could specify 'file', 'layout',
16621703
'item' and 'items' parameters like:
1663-
<http://root.cern.ch/js/3.0/index.htm?file=../files/hsimple.root&layout=grid3x2&item=hpx;1>
1704+
<https://root.cern/js/3.0/index.htm?file=https://root.cern/js/files/hsimple.root&layout=grid3x2&item=hpx;1>
16641705
15. Support direct reading of objects from sub-sub-directories.
16651706
16. Introduce demo.htm, which demonstrates online usage of JSROOT.
16661707
17. One could use demo.htm directly with THttpServer providing address like:

js/index.htm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
</head>
1616
<body>
17-
<div id="simpleGUI" path="../files/" files="ct.root;exclusion.root;fillrandom.root;glbox.root;graph.root;hsimple.root;legends.root;rf107.root;stacks.root;zdemo.root">
17+
<div id="simpleGUI" path="https://root.cern/js/files/" files="ct.root;exclusion.root;fillrandom.root;glbox.root;graph.root;hsimple.root;legends.root;rf107.root;stacks.root;zdemo.root">
1818
loading modules ...
1919
</div>
2020
<script type="module">
@@ -50,7 +50,7 @@
5050
5151
Example:
5252
53-
https://root.cern/js/latest/?file=../files/hsimple.root&layout=grid2x2&item=[hpx;1,hpxpy;1]&opts=[,colz]
53+
https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&layout=grid2x2&item=[hpx;1,hpxpy;1]&opts=[,colz]
5454
5555
Page can be used to open files from other web servers like:
5656

js/modules/base/BasePainter.mjs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -872,9 +872,14 @@ async function svgToImage(svg, image_format, args) {
872872
return internals.makePDF ? internals.makePDF(svg, args) : null;
873873

874874
// required with df104.py/df105.py example with RCanvas or any special symbols in TLatex
875-
const doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
875+
const doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">',
876+
is_rgba = image_format === 'rgba';
876877

877878
if (isNodeJs()) {
879+
if (image_format === 'jpeg') {
880+
console.log('JPEG image format not supported in node.js, use PNG');
881+
return null;
882+
}
878883
svg = encodeURIComponent(doctype + svg);
879884
svg = svg.replace(/%([0-9A-F]{2})/g, (match, p1) => {
880885
const c = String.fromCharCode('0x' + p1);
@@ -883,6 +888,31 @@ async function svgToImage(svg, image_format, args) {
883888

884889
const img_src = 'data:image/svg+xml;base64,' + btoa_func(decodeURIComponent(svg));
885890

891+
// Use the newer and stabler `resvg-js` backend for converting SVG to PNG
892+
if (settings.UseResvgJs) {
893+
return import('@resvg/resvg-js').then(({ Resvg }) => {
894+
const rawSvg = decodeURIComponent(svg), // raw SVG XML
895+
resvg = new Resvg(rawSvg), // Initialize Resvg and create the PNG buffer
896+
renderData = resvg.render(),
897+
pngBuffer = renderData.asPng();
898+
899+
// Return raw RGBA pixels if caller requested it
900+
if (is_rgba) {
901+
return {
902+
width: renderData.width,
903+
height: renderData.height,
904+
data: renderData.pixels
905+
};
906+
}
907+
908+
if (args?.as_buffer)
909+
return pngBuffer;
910+
911+
return 'data:image/png;base64,' + pngBuffer.toString('base64');
912+
});
913+
}
914+
915+
// Fallback to `node-canvas`
886916
return import('canvas').then(async handle => {
887917
return handle.default.loadImage(img_src).then(img => {
888918
const canvas = handle.default.createCanvas(img.width, img.height);
@@ -892,7 +922,7 @@ async function svgToImage(svg, image_format, args) {
892922
if (args?.as_buffer)
893923
return canvas.toBuffer('image/' + image_format);
894924

895-
return image_format ? canvas.toDataURL('image/' + image_format) : canvas;
925+
return image_format && !is_rgba ? canvas.toDataURL('image/' + image_format) : canvas;
896926
});
897927
});
898928
}
@@ -914,7 +944,7 @@ async function svgToImage(svg, image_format, args) {
914944
if (args?.as_buffer && image_format)
915945
canvas.toBlob(blob => blob.arrayBuffer().then(resolveFunc), 'image/' + image_format);
916946
else
917-
resolveFunc(image_format ? canvas.toDataURL('image/' + image_format) : canvas);
947+
resolveFunc(image_format && !is_rgba ? canvas.toDataURL('image/' + image_format) : canvas);
918948
};
919949
image.onerror = function(arg) {
920950
URL.revokeObjectURL(img_src);

js/modules/base/ObjectPainter.mjs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,9 +1543,8 @@ class ObjectPainter extends BasePainter {
15431543
}
15441544

15451545
/** @summary Configure user-defined tooltip handler
1546-
* @desc Hook for the users to get tooltip information when mouse cursor moves over frame area
1546+
* @desc Hook for the users to get tooltip information when mouse cursor moves over then object
15471547
* Handler function will be called every time when new data is selected
1548-
* when mouse leave frame area, handler(null) will be called
15491548
* @param {function} handler - function called when tooltip is produced
15501549
* @param {number} [tmout = 100] - delay in ms before tooltip delivered */
15511550
configureUserTooltipHandler(handler, tmout = 100) {

js/modules/base/colors.mjs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,28 @@ function createGrayPalette() {
281281
return new ColorPalette(palette);
282282
}
283283

284+
/** @summary Set list of colors for specified color palette
285+
* @desc One also can redefine existing palette
286+
* Array should contain several colors in RGB format like `rgb(10,10,10)` or `#ff00ff`
287+
* @private */
288+
289+
const customPalettes = {};
290+
291+
function setColorPalette(id, colors) {
292+
if (!Number.isInteger(id) || (id < 0) || !colors?.length)
293+
return false;
294+
295+
customPalettes[id] = colors;
296+
return true;
297+
}
298+
284299
/** @summary Create color palette
285300
* @private */
286301
function getColorPalette(id, grayscale) {
287302
id = id || settings.Palette;
303+
if (customPalettes[id])
304+
return new ColorPalette(customPalettes[id], grayscale);
305+
288306
if ((id > 0) && (id < 10))
289307
return createGrayPalette();
290308
if (id < 51)
@@ -506,4 +524,5 @@ export { getColor, findColor, addColor, adoptRootColors, convertColor,
506524
getRootColors, getGrayColors,
507525
extendRootColors, getRGBfromTColor, createRootColors, toColor,
508526
kWhite, kBlack, kRed, kGreen, kBlue, kYellow, kMagenta, kCyan,
509-
ColorPalette, getColorPalette, clTLinearGradient, clTRadialGradient, decodeWebCanvasColors };
527+
ColorPalette, getColorPalette, setColorPalette,
528+
clTLinearGradient, clTRadialGradient, decodeWebCanvasColors };

js/modules/base/math.mjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66

77
/* eslint-disable curly */
8-
/* eslint-disable no-loss-of-precision */
98
/* eslint-disable no-useless-assignment */
109
/* eslint-disable no-use-before-define */
1110
/* eslint-disable no-else-return */

js/modules/core.mjs

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const version_id = 'dev',
66

77
/** @summary version date
88
* @desc Release date in format day/month/year like '14/04/2022' */
9-
version_date = '7/11/2025',
9+
version_date = '10/03/2026',
1010

1111
/** @summary version id and date
1212
* @desc Produced by concatenation of {@link version_id} and {@link version_date}
@@ -249,6 +249,8 @@ settings = {
249249
Render3DBatch: constants.Render3D.Default,
250250
/** @summary Way to embed 3D drawing in SVG, see {@link constants.Embed3D} for possible values */
251251
Embed3D: constants.Embed3D.Default,
252+
/** @summary Use `resvg-js` backend for converting SVGs in node.js */
253+
UseResvgJs: true,
252254
/** @summary Default canvas width */
253255
CanvasWidth: 1200,
254256
/** @summary Default canvas height */
@@ -355,6 +357,10 @@ settings = {
355357
* @desc Allows to retry files reading if original URL fails
356358
* @private */
357359
FilesRemap: { 'https://root.cern/': 'https://root-eos.web.cern.ch/' },
360+
/** @summary THttpServer read timeout in ms
361+
* @desc Configures timeout for requests to THttpServer
362+
* @default 0 */
363+
ServerTimeout: 0,
358364
/** @summary Configure xhr.withCredentials = true when submitting http requests from JSROOT */
359365
WithCredentials: false,
360366
/** @summary Skip streamer infos from the GUI */
@@ -957,13 +963,22 @@ function findFunction(name) {
957963

958964
/** @summary Method to create http request, without promise can be used only in browser environment
959965
* @private */
960-
function createHttpRequest(url, kind, user_accept_callback, user_reject_callback, use_promise) {
966+
function createHttpRequest(url, kind, user_accept_callback, user_reject_callback, use_promise, tmout) {
967+
function handle_error(xhr, message, code, abort_reason) {
968+
if (!xhr.did_abort) {
969+
xhr.did_abort = abort_reason || true;
970+
xhr.abort();
971+
}
972+
if (!xhr.did_error || abort_reason)
973+
console.warn(message);
974+
if (!xhr.did_error) {
975+
xhr.did_error = true;
976+
xhr.error_callback(Error(message), code);
977+
}
978+
}
961979
function configureXhr(xhr) {
962980
xhr.http_callback = isFunc(user_accept_callback) ? user_accept_callback.bind(xhr) : () => {};
963-
xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function(err) {
964-
console.warn(err.message);
965-
this.http_callback(null);
966-
}.bind(xhr);
981+
xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function() { this.http_callback(null); };
967982

968983
if (!kind)
969984
kind = 'buf';
@@ -999,11 +1014,8 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback
9991014

10001015
if (settings.HandleWrongHttpResponse && (method === 'GET') && isFunc(xhr.addEventListener)) {
10011016
xhr.addEventListener('progress', function(oEvent) {
1002-
if (oEvent.lengthComputable && this.expected_size && (oEvent.loaded > this.expected_size)) {
1003-
this.did_abort = true;
1004-
this.abort();
1005-
this.error_callback(Error(`Server sends more bytes ${oEvent.loaded} than expected ${this.expected_size}. Abort I/O operation`), 598);
1006-
}
1017+
if (oEvent.lengthComputable && this.expected_size && (oEvent.loaded > this.expected_size))
1018+
handle_error(this, `Server sends more bytes ${oEvent.loaded} than expected ${this.expected_size}. Abort I/O operation`, 598);
10071019
}.bind(xhr));
10081020
}
10091021

@@ -1013,11 +1025,8 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback
10131025

10141026
if ((this.readyState === 2) && this.expected_size) {
10151027
const len = parseInt(this.getResponseHeader('Content-Length'));
1016-
if (Number.isInteger(len) && (len > this.expected_size) && !settings.HandleWrongHttpResponse) {
1017-
this.did_abort = 'large';
1018-
this.abort();
1019-
return this.error_callback(Error(`Server response size ${len} larger than expected ${this.expected_size}. Abort I/O operation`), 599);
1020-
}
1028+
if (Number.isInteger(len) && (len > this.expected_size) && !settings.HandleWrongHttpResponse)
1029+
return handle_error(this, `Server response size ${len} larger than expected ${this.expected_size}. Abort I/O operation`, 599, 'large');
10211030
}
10221031

10231032
if (this.readyState !== 4)
@@ -1026,7 +1035,7 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback
10261035
if ((this.status !== 200) && (this.status !== 206) && !browser.qt6 &&
10271036
// in these special cases browsers not always set status
10281037
!((this.status === 0) && ((url.indexOf('file://') === 0) || (url.indexOf('blob:') === 0))))
1029-
return this.error_callback(Error(`Fail to load url ${url}`), this.status);
1038+
return handle_error(this, `Fail to load url ${url}`, this.status);
10301039

10311040
if (this.nodejs_checkzip && (this.getResponseHeader('content-encoding') === 'gzip')) {
10321041
// special handling of gzip JSON objects in Node.js
@@ -1071,6 +1080,11 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback
10711080
xhr.responseType = 'arraybuffer';
10721081
}
10731082

1083+
if (tmout && Number.isFinite(tmout)) {
1084+
xhr.timeout = tmout;
1085+
xhr.ontimeout = function() { handle_error(this, `Request ${url} timeout set ${tmout} ms`, 600, 'timeout'); };
1086+
}
1087+
10741088
return xhr;
10751089
}
10761090

0 commit comments

Comments
 (0)