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
2 changes: 1 addition & 1 deletion js/LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License

Copyright � 2013-2025 JavaScript ROOT authors
Copyright � 2013-2026 JavaScript ROOT authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
9,081 changes: 5,973 additions & 3,108 deletions js/build/jsroot.js

Large diffs are not rendered by default.

51 changes: 46 additions & 5 deletions js/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,51 @@


## Changes in dev
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. Remove support for deprectaed TH1K class
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


## Changes in 7.10.3
1. Fix - add `TLeafG` support in `TTree` #397
2. Fix - reset contour while drawing `TH3`
3. Fix - fix kFloat16/kDouble32 processing in `TTree`


## Changes in 7.10.2
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 https://github.com/root-project/root/issues/21173


## Changes in 7.10.1
1. Fix - proper paint axis labels on both sides when pad.fTickx/y = 2
1. Fix - paint frame border mode/size from TCanvas
2. Fix - recover io after bad http response


## Changes in 7.10.0
Expand Down Expand Up @@ -1469,11 +1510,11 @@
8. Fix several problems with markers drawing; implement plus, asterisk, mult symbols.
9. Implement custom layout, which allows to configure user-defined layout for displayed objects
10. Fix errors with scaling of axis labels.
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
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


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

</head>
<body>
<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">
<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">
loading modules ...
</div>
<script type="module">
Expand Down Expand Up @@ -50,7 +50,7 @@

Example:

https://root.cern/js/latest/?file=../files/hsimple.root&layout=grid2x2&item=[hpx;1,hpxpy;1]&opts=[,colz]
https://root.cern/js/latest/?file=https://root.cern/js/files/hsimple.root&layout=grid2x2&item=[hpx;1,hpxpy;1]&opts=[,colz]

Page can be used to open files from other web servers like:

Expand Down
36 changes: 33 additions & 3 deletions js/modules/base/BasePainter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -872,9 +872,14 @@ async function svgToImage(svg, image_format, args) {
return internals.makePDF ? internals.makePDF(svg, args) : null;

// required with df104.py/df105.py example with RCanvas or any special symbols in TLatex
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">';
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">',
is_rgba = image_format === 'rgba';

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

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

// Use the newer and stabler `resvg-js` backend for converting SVG to PNG
if (settings.UseResvgJs) {
return import('@resvg/resvg-js').then(({ Resvg }) => {
const rawSvg = decodeURIComponent(svg), // raw SVG XML
resvg = new Resvg(rawSvg), // Initialize Resvg and create the PNG buffer
renderData = resvg.render(),
pngBuffer = renderData.asPng();

// Return raw RGBA pixels if caller requested it
if (is_rgba) {
return {
width: renderData.width,
height: renderData.height,
data: renderData.pixels
};
}

if (args?.as_buffer)
return pngBuffer;

return 'data:image/png;base64,' + pngBuffer.toString('base64');
});
}

// Fallback to `node-canvas`
return import('canvas').then(async handle => {
return handle.default.loadImage(img_src).then(img => {
const canvas = handle.default.createCanvas(img.width, img.height);
Expand All @@ -892,7 +922,7 @@ async function svgToImage(svg, image_format, args) {
if (args?.as_buffer)
return canvas.toBuffer('image/' + image_format);

return image_format ? canvas.toDataURL('image/' + image_format) : canvas;
return image_format && !is_rgba ? canvas.toDataURL('image/' + image_format) : canvas;
});
});
}
Expand All @@ -914,7 +944,7 @@ async function svgToImage(svg, image_format, args) {
if (args?.as_buffer && image_format)
canvas.toBlob(blob => blob.arrayBuffer().then(resolveFunc), 'image/' + image_format);
else
resolveFunc(image_format ? canvas.toDataURL('image/' + image_format) : canvas);
resolveFunc(image_format && !is_rgba ? canvas.toDataURL('image/' + image_format) : canvas);
};
image.onerror = function(arg) {
URL.revokeObjectURL(img_src);
Expand Down
3 changes: 1 addition & 2 deletions js/modules/base/ObjectPainter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1543,9 +1543,8 @@ class ObjectPainter extends BasePainter {
}

/** @summary Configure user-defined tooltip handler
* @desc Hook for the users to get tooltip information when mouse cursor moves over frame area
* @desc Hook for the users to get tooltip information when mouse cursor moves over then object
* Handler function will be called every time when new data is selected
* when mouse leave frame area, handler(null) will be called
* @param {function} handler - function called when tooltip is produced
* @param {number} [tmout = 100] - delay in ms before tooltip delivered */
configureUserTooltipHandler(handler, tmout = 100) {
Expand Down
21 changes: 20 additions & 1 deletion js/modules/base/colors.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,28 @@ function createGrayPalette() {
return new ColorPalette(palette);
}

/** @summary Set list of colors for specified color palette
* @desc One also can redefine existing palette
* Array should contain several colors in RGB format like `rgb(10,10,10)` or `#ff00ff`
* @private */

const customPalettes = {};

function setColorPalette(id, colors) {
if (!Number.isInteger(id) || (id < 0) || !colors?.length)
return false;

customPalettes[id] = colors;
return true;
}

/** @summary Create color palette
* @private */
function getColorPalette(id, grayscale) {
id = id || settings.Palette;
if (customPalettes[id])
return new ColorPalette(customPalettes[id], grayscale);

if ((id > 0) && (id < 10))
return createGrayPalette();
if (id < 51)
Expand Down Expand Up @@ -506,4 +524,5 @@ export { getColor, findColor, addColor, adoptRootColors, convertColor,
getRootColors, getGrayColors,
extendRootColors, getRGBfromTColor, createRootColors, toColor,
kWhite, kBlack, kRed, kGreen, kBlue, kYellow, kMagenta, kCyan,
ColorPalette, getColorPalette, clTLinearGradient, clTRadialGradient, decodeWebCanvasColors };
ColorPalette, getColorPalette, setColorPalette,
clTLinearGradient, clTRadialGradient, decodeWebCanvasColors };
1 change: 0 additions & 1 deletion js/modules/base/math.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/

/* eslint-disable curly */
/* eslint-disable no-loss-of-precision */
/* eslint-disable no-useless-assignment */
/* eslint-disable no-use-before-define */
/* eslint-disable no-else-return */
Expand Down
48 changes: 31 additions & 17 deletions js/modules/core.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const version_id = 'dev',

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

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

/** @summary Method to create http request, without promise can be used only in browser environment
* @private */
function createHttpRequest(url, kind, user_accept_callback, user_reject_callback, use_promise) {
function createHttpRequest(url, kind, user_accept_callback, user_reject_callback, use_promise, tmout) {
function handle_error(xhr, message, code, abort_reason) {
if (!xhr.did_abort) {
xhr.did_abort = abort_reason || true;
xhr.abort();
}
if (!xhr.did_error || abort_reason)
console.warn(message);
if (!xhr.did_error) {
xhr.did_error = true;
xhr.error_callback(Error(message), code);
}
}
function configureXhr(xhr) {
xhr.http_callback = isFunc(user_accept_callback) ? user_accept_callback.bind(xhr) : () => {};
xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function(err) {
console.warn(err.message);
this.http_callback(null);
}.bind(xhr);
xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function() { this.http_callback(null); };

if (!kind)
kind = 'buf';
Expand Down Expand Up @@ -999,11 +1014,8 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback

if (settings.HandleWrongHttpResponse && (method === 'GET') && isFunc(xhr.addEventListener)) {
xhr.addEventListener('progress', function(oEvent) {
if (oEvent.lengthComputable && this.expected_size && (oEvent.loaded > this.expected_size)) {
this.did_abort = true;
this.abort();
this.error_callback(Error(`Server sends more bytes ${oEvent.loaded} than expected ${this.expected_size}. Abort I/O operation`), 598);
}
if (oEvent.lengthComputable && this.expected_size && (oEvent.loaded > this.expected_size))
handle_error(this, `Server sends more bytes ${oEvent.loaded} than expected ${this.expected_size}. Abort I/O operation`, 598);
}.bind(xhr));
}

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

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

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

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

if (tmout && Number.isFinite(tmout)) {
xhr.timeout = tmout;
xhr.ontimeout = function() { handle_error(this, `Request ${url} timeout set ${tmout} ms`, 600, 'timeout'); };
}

return xhr;
}

Expand Down
Loading
Loading