tree.mjs

  1. import { BIT, settings, isArrayProto, isRootCollection, isObject, isFunc, isStr, getMethods,
  2. create, createHistogram, createTGraph, prROOT,
  3. clTObject, clTObjString, clTHashList, clTPolyMarker3D, clTH1, clTH2, clTH3, kNoStats } from './core.mjs';
  4. import { kChar, kShort, kInt, kFloat,
  5. kCharStar, kDouble, kDouble32,
  6. kUChar, kUShort, kUInt,
  7. kLong64, kULong64, kBool, kFloat16,
  8. kOffsetL, kOffsetP, kObject, kAny, kObjectp, kTString,
  9. kStreamer, kStreamLoop, kSTLp, kSTL, kBaseClass, clTBasket,
  10. R__unzip, TBuffer, createStreamerElement, createMemberStreamer } from './io.mjs';
  11. import * as jsroot_math from './base/math.mjs';
  12. // branch types
  13. const kLeafNode = 0, kBaseClassNode = 1, kObjectNode = 2, kClonesNode = 3,
  14. kSTLNode = 4, kClonesMemberNode = 31, kSTLMemberNode = 41,
  15. // branch bits
  16. // kDoNotProcess = BIT(10), // Active bit for branches
  17. // kIsClone = BIT(11), // to indicate a TBranchClones
  18. // kBranchObject = BIT(12), // branch is a TObject*
  19. // kBranchAny = BIT(17), // branch is an object*
  20. // kAutoDelete = BIT(15),
  21. kDoNotUseBufferMap = BIT(22), // If set, at least one of the entry in the branch will use the buffer's map of classname and objects.
  22. clTBranchElement = 'TBranchElement', clTBranchFunc = 'TBranchFunc';
  23. /**
  24. * @summary Class to read data from TTree
  25. *
  26. * @desc Instance of TSelector can be used to access TTree data
  27. */
  28. class TSelector {
  29. /** @summary constructor */
  30. constructor() {
  31. this._branches = []; // list of branches to read
  32. this._names = []; // list of member names for each branch in tgtobj
  33. this._directs = []; // indication if only branch without any children should be read
  34. this._break = 0;
  35. this.tgtobj = {};
  36. }
  37. /** @summary Add branch to the selector
  38. * @desc Either branch name or branch itself should be specified
  39. * Second parameter defines member name in the tgtobj
  40. * If selector.addBranch('px', 'read_px') is called,
  41. * branch will be read into selector.tgtobj.read_px member
  42. * If second parameter not specified, branch name (here 'px') will be used
  43. * If branch object specified as first parameter and second parameter missing,
  44. * then member like 'br0', 'br1' and so on will be assigned
  45. * @param {string|Object} branch - name of branch (or branch object itself}
  46. * @param {string} [name] - member name in tgtobj where data will be read
  47. * @param {boolean} [direct] - if only branch without any children should be read */
  48. addBranch(branch, name, direct) {
  49. if (!name)
  50. name = isStr(branch) ? branch : `br${this._branches.length}`;
  51. this._branches.push(branch);
  52. this._names.push(name);
  53. this._directs.push(direct);
  54. return this._branches.length - 1;
  55. }
  56. /** @summary returns number of branches used in selector */
  57. numBranches() { return this._branches.length; }
  58. /** @summary returns branch by index used in selector */
  59. getBranch(indx) { return this._branches[indx]; }
  60. /** @summary returns index of branch
  61. * @private */
  62. indexOfBranch(branch) { return this._branches.indexOf(branch); }
  63. /** @summary returns name of branch
  64. * @private */
  65. nameOfBranch(indx) { return this._names[indx]; }
  66. /** @summary function called during TTree processing
  67. * @abstract
  68. * @param {number} progress - current value between 0 and 1 */
  69. ShowProgress(/* progress */) {}
  70. /** @summary call this function to abort processing */
  71. Abort() { this._break = -1111; }
  72. /** @summary function called before start processing
  73. * @abstract
  74. * @param {object} tree - tree object */
  75. Begin(/* tree */) {}
  76. /** @summary function called when next entry extracted from the tree
  77. * @abstract
  78. * @param {number} entry - read entry number */
  79. Process(/* entry */) {}
  80. /** @summary function called at the very end of processing
  81. * @abstract
  82. * @param {boolean} res - true if all data were correctly processed */
  83. Terminate(/* res */) {}
  84. } // class TSelector
  85. // =================================================================
  86. /** @summary Checks array kind
  87. * @desc return 0 when not array
  88. * 1 - when arbitrary array
  89. * 2 - when plain (1-dim) array with same-type content
  90. * @private */
  91. function checkArrayPrototype(arr, check_content) {
  92. if (!isObject(arr)) return 0;
  93. const arr_kind = isArrayProto(Object.prototype.toString.apply(arr));
  94. if (!check_content || (arr_kind !== 1))
  95. return arr_kind;
  96. let typ, plain = true;
  97. for (let k = 0; k < arr.length; ++k) {
  98. const sub = typeof arr[k];
  99. if (!typ)
  100. typ = sub;
  101. if ((sub !== typ) || (isObject(sub) && checkArrayPrototype(arr[k]))) {
  102. plain = false;
  103. break;
  104. }
  105. }
  106. return plain ? 2 : 1;
  107. }
  108. /**
  109. * @summary Class to iterate over array elements
  110. *
  111. * @private
  112. */
  113. class ArrayIterator {
  114. /** @summary constructor */
  115. constructor(arr, select, tgtobj) {
  116. this.object = arr;
  117. this.value = 0; // value always used in iterator
  118. this.arr = []; // all arrays
  119. this.indx = []; // all indexes
  120. this.cnt = -1; // current index counter
  121. this.tgtobj = tgtobj;
  122. if (isObject(select))
  123. this.select = select; // remember indexes for selection
  124. else
  125. this.select = []; // empty array, undefined for each dimension means iterate over all indexes
  126. }
  127. /** @summary next element */
  128. next() {
  129. let obj, typ, cnt = this.cnt;
  130. if (cnt >= 0) {
  131. if (++this.fastindx < this.fastlimit) {
  132. this.value = this.fastarr[this.fastindx];
  133. return true;
  134. }
  135. while (--cnt >= 0) {
  136. if ((this.select[cnt] === undefined) && (++this.indx[cnt] < this.arr[cnt].length))
  137. break;
  138. }
  139. if (cnt < 0) return false;
  140. }
  141. while (true) {
  142. obj = (cnt < 0) ? this.object : (this.arr[cnt])[this.indx[cnt]];
  143. typ = obj ? typeof obj : 'any';
  144. if (typ === 'object') {
  145. if (obj._typename !== undefined) {
  146. if (isRootCollection(obj)) {
  147. obj = obj.arr;
  148. typ = 'array';
  149. } else
  150. typ = 'any';
  151. } else if (Number.isInteger(obj.length) && (checkArrayPrototype(obj) > 0))
  152. typ = 'array';
  153. else
  154. typ = 'any';
  155. }
  156. if (this.select[cnt + 1] === '$self$') {
  157. this.value = obj;
  158. this.fastindx = this.fastlimit = 0;
  159. this.cnt = cnt + 1;
  160. return true;
  161. }
  162. if ((typ === 'any') && isStr(this.select[cnt + 1])) {
  163. // this is extraction of the member from arbitrary class
  164. this.arr[++cnt] = obj;
  165. this.indx[cnt] = this.select[cnt]; // use member name as index
  166. continue;
  167. }
  168. if ((typ === 'array') && (obj.length || (this.select[cnt + 1] === '$size$'))) {
  169. this.arr[++cnt] = obj;
  170. switch (this.select[cnt]) {
  171. case undefined: this.indx[cnt] = 0; break;
  172. case '$last$': this.indx[cnt] = obj.length - 1; break;
  173. case '$size$':
  174. this.value = obj.length;
  175. this.fastindx = this.fastlimit = 0;
  176. this.cnt = cnt;
  177. return true;
  178. default:
  179. if (Number.isInteger(this.select[cnt])) {
  180. this.indx[cnt] = this.select[cnt];
  181. if (this.indx[cnt] < 0) this.indx[cnt] = obj.length - 1;
  182. } else {
  183. // this is compile variable as array index - can be any expression
  184. this.select[cnt].produce(this.tgtobj);
  185. this.indx[cnt] = Math.round(this.select[cnt].get(0));
  186. }
  187. }
  188. } else {
  189. if (cnt < 0)
  190. return false;
  191. this.value = obj;
  192. if (this.select[cnt] === undefined) {
  193. this.fastarr = this.arr[cnt];
  194. this.fastindx = this.indx[cnt];
  195. this.fastlimit = this.fastarr.length;
  196. } else
  197. this.fastindx = this.fastlimit = 0; // no any iteration on that level
  198. this.cnt = cnt;
  199. return true;
  200. }
  201. }
  202. // unreachable code
  203. // return false;
  204. }
  205. /** @summary reset iterator */
  206. reset() {
  207. this.arr = [];
  208. this.indx = [];
  209. delete this.fastarr;
  210. this.cnt = -1;
  211. this.value = 0;
  212. }
  213. } // class ArrayIterator
  214. /** @summary return TStreamerElement associated with the branch - if any
  215. * @desc unfortunately, branch.fID is not number of element in streamer info
  216. * @private */
  217. function findBrachStreamerElement(branch, file) {
  218. if (!branch || !file || (branch._typename !== clTBranchElement) || (branch.fID < 0) || (branch.fStreamerType < 0)) return null;
  219. const s_i = file.findStreamerInfo(branch.fClassName, branch.fClassVersion, branch.fCheckSum),
  220. arr = (s_i && s_i.fElements) ? s_i.fElements.arr : null;
  221. if (!arr) return null;
  222. let match_name = branch.fName,
  223. pos = match_name.indexOf('[');
  224. if (pos > 0) match_name = match_name.slice(0, pos);
  225. pos = match_name.lastIndexOf('.');
  226. if (pos > 0) match_name = match_name.slice(pos + 1);
  227. function match_elem(elem) {
  228. if (!elem) return false;
  229. if (elem.fName !== match_name) return false;
  230. if (elem.fType === branch.fStreamerType) return true;
  231. if ((elem.fType === kBool) && (branch.fStreamerType === kUChar)) return true;
  232. if (((branch.fStreamerType === kSTL) || (branch.fStreamerType === kSTL + kOffsetL) ||
  233. (branch.fStreamerType === kSTLp) || (branch.fStreamerType === kSTLp + kOffsetL)) &&
  234. (elem.fType === kStreamer)) return true;
  235. console.warn(`Should match element ${elem.fType} with branch ${branch.fStreamerType}`);
  236. return false;
  237. }
  238. // first check branch fID - in many cases gut guess
  239. if (match_elem(arr[branch.fID]))
  240. return arr[branch.fID];
  241. for (let k = 0; k < arr.length; ++k) {
  242. if ((k !== branch.fID) && match_elem(arr[k]))
  243. return arr[k];
  244. }
  245. console.error(`Did not found/match element for branch ${branch.fName} class ${branch.fClassName}`);
  246. return null;
  247. }
  248. /** @summary return class name of the object, stored in the branch
  249. * @private */
  250. function getBranchObjectClass(branch, tree, with_clones = false, with_leafs = false) {
  251. if (!branch || (branch._typename !== clTBranchElement)) return '';
  252. if ((branch.fType === kLeafNode) && (branch.fID === -2) && (branch.fStreamerType === -1)) {
  253. // object where all sub-branches will be collected
  254. return branch.fClassName;
  255. }
  256. if (with_clones && branch.fClonesName && ((branch.fType === kClonesNode) || (branch.fType === kSTLNode)))
  257. return branch.fClonesName;
  258. const s_elem = findBrachStreamerElement(branch, tree.$file);
  259. if ((branch.fType === kBaseClassNode) && s_elem && (s_elem.fTypeName === kBaseClass))
  260. return s_elem.fName;
  261. if (branch.fType === kObjectNode) {
  262. if (s_elem && ((s_elem.fType === kObject) || (s_elem.fType === kAny)))
  263. return s_elem.fTypeName;
  264. return clTObject;
  265. }
  266. if ((branch.fType === kLeafNode) && s_elem && with_leafs) {
  267. if ((s_elem.fType === kObject) || (s_elem.fType === kAny))
  268. return s_elem.fTypeName;
  269. if (s_elem.fType === kObjectp)
  270. return s_elem.fTypeName.slice(0, s_elem.fTypeName.length - 1);
  271. }
  272. return '';
  273. }
  274. /** @summary Get branch with specified id
  275. * @desc All sub-branches checked as well
  276. * @return {Object} branch
  277. * @private */
  278. function getTreeBranch(tree, id) {
  279. if (!Number.isInteger(id)) return;
  280. let res, seq = 0;
  281. function scan(obj) {
  282. obj?.fBranches?.arr.forEach(br => {
  283. if (seq++ === id) res = br;
  284. if (!res) scan(br);
  285. });
  286. }
  287. scan(tree);
  288. return res;
  289. }
  290. /** @summary Special branch search
  291. * @desc Name can include extra part, which will be returned in the result
  292. * @param {string} name - name of the branch
  293. * @return {Object} with 'branch' and 'rest' members
  294. * @private */
  295. function findBranchComplex(tree, name, lst = undefined, only_search = false) {
  296. let top_search = false, search = name, res = null;
  297. if (!lst) {
  298. top_search = true;
  299. lst = tree.fBranches;
  300. const pos = search.indexOf('[');
  301. if (pos > 0) search = search.slice(0, pos);
  302. }
  303. if (!lst?.arr.length)
  304. return null;
  305. for (let n = 0; n < lst.arr.length; ++n) {
  306. let brname = lst.arr[n].fName;
  307. if (brname.at(-1) === ']')
  308. brname = brname.slice(0, brname.indexOf('['));
  309. // special case when branch name includes STL map name
  310. if (search.indexOf(brname) && (brname.indexOf('<') > 0)) {
  311. const p1 = brname.indexOf('<'), p2 = brname.lastIndexOf('>');
  312. brname = brname.slice(0, p1) + brname.slice(p2 + 1);
  313. }
  314. if (brname === search) {
  315. res = { branch: lst.arr[n], rest: '' };
  316. break;
  317. }
  318. if (search.indexOf(brname))
  319. continue;
  320. // this is a case when branch name is in the begin of the search string
  321. // check where point is
  322. let pnt = brname.length;
  323. if (brname[pnt - 1] === '.') pnt--;
  324. if (search[pnt] !== '.') continue;
  325. res = findBranchComplex(tree, search, lst.arr[n].fBranches) ||
  326. findBranchComplex(tree, search.slice(pnt + 1), lst.arr[n].fBranches) ||
  327. { branch: lst.arr[n], rest: search.slice(pnt) };
  328. break;
  329. }
  330. if (top_search && !only_search && !res && (search.indexOf('br_') === 0)) {
  331. let p = 3;
  332. while ((p < search.length) && (search[p] >= '0') && (search[p] <= '9')) ++p;
  333. const br = (p > 3) ? getTreeBranch(tree, parseInt(search.slice(3, p))) : null;
  334. if (br) res = { branch: br, rest: search.slice(p) };
  335. }
  336. if (!top_search || !res)
  337. return res;
  338. if (name.length > search.length)
  339. res.rest += name.slice(search.length);
  340. return res;
  341. }
  342. /** @summary Search branch with specified name
  343. * @param {string} name - name of the branch
  344. * @return {Object} found branch
  345. * @private */
  346. function findBranch(tree, name) {
  347. const res = findBranchComplex(tree, name, tree.fBranches, true);
  348. return (!res || res.rest) ? null : res.branch;
  349. }
  350. /** summary Returns number of branches in the TTree
  351. * desc Checks also sub-branches in the branches
  352. * return {number} number of branches
  353. * private
  354. function getNumBranches(tree) {
  355. function count(obj) {
  356. if (!obj?.fBranches) return 0;
  357. let nchld = 0;
  358. obj.fBranches.arr.forEach(sub => { nchld += count(sub); });
  359. return obj.fBranches.arr.length + nchld;
  360. }
  361. return count(tree);
  362. }
  363. */
  364. /**
  365. * @summary object with single variable in TTree::Draw expression
  366. *
  367. * @private
  368. */
  369. class TDrawVariable {
  370. /** @summary constructor */
  371. constructor(globals) {
  372. this.globals = globals;
  373. this.code = '';
  374. this.brindex = []; // index of used branches from selector
  375. this.branches = []; // names of branches in target object
  376. this.brarray = []; // array specifier for each branch
  377. this.func = null; // generic function for variable calculation
  378. this.kind = undefined;
  379. this.buf = []; // buffer accumulates temporary values
  380. }
  381. /** @summary Parse variable
  382. * @desc when only_branch specified, its placed in the front of the expression */
  383. parse(tree, selector, code, only_branch, branch_mode) {
  384. const is_start_symbol = symb => {
  385. if ((symb >= 'A') && (symb <= 'Z')) return true;
  386. if ((symb >= 'a') && (symb <= 'z')) return true;
  387. return (symb === '_');
  388. }, is_next_symbol = symb => {
  389. if (is_start_symbol(symb)) return true;
  390. if ((symb >= '0') && (symb <= '9')) return true;
  391. return false;
  392. };
  393. if (!code) code = ''; // should be empty string at least
  394. this.code = (only_branch?.fName ?? '') + code;
  395. let pos = 0, pos2 = 0, br;
  396. while ((pos < code.length) || only_branch) {
  397. let arriter = [];
  398. if (only_branch) {
  399. br = only_branch;
  400. only_branch = undefined;
  401. } else {
  402. // first try to find branch
  403. pos2 = pos;
  404. while ((pos2 < code.length) && (is_next_symbol(code[pos2]) || code[pos2] === '.')) pos2++;
  405. if (code[pos2] === '$') {
  406. let repl = '';
  407. switch (code.slice(pos, pos2)) {
  408. case 'LocalEntry':
  409. case 'Entry': repl = 'arg.$globals.entry'; break;
  410. case 'Entries': repl = 'arg.$globals.entries'; break;
  411. }
  412. if (repl) {
  413. code = code.slice(0, pos) + repl + code.slice(pos2 + 1);
  414. pos += repl.length;
  415. continue;
  416. }
  417. }
  418. br = findBranchComplex(tree, code.slice(pos, pos2));
  419. if (!br) { pos = pos2 + 1; continue; }
  420. // when full id includes branch name, replace only part of extracted expression
  421. if (br.branch && (br.rest !== undefined)) {
  422. pos2 -= br.rest.length;
  423. branch_mode = undefined; // maybe selection of the sub-object done
  424. br = br.branch;
  425. }
  426. // when code ends with the point - means object itself will be accessed
  427. // sometime branch name itself ends with the point
  428. if ((pos2 >= code.length - 1) && (code.at(-1) === '.')) {
  429. arriter.push('$self$');
  430. pos2 = code.length;
  431. }
  432. }
  433. // now extract all levels of iterators
  434. while (pos2 < code.length) {
  435. if ((code[pos2] === '@') && (code.slice(pos2, pos2 + 5) === '@size') && !arriter.length) {
  436. pos2 += 5;
  437. branch_mode = true;
  438. break;
  439. }
  440. if (code[pos2] === '.') {
  441. // this is object member
  442. const prev = ++pos2;
  443. if ((code[prev] === '@') && (code.slice(prev, prev + 5) === '@size')) {
  444. arriter.push('$size$');
  445. pos2 += 5;
  446. break;
  447. }
  448. if (!is_start_symbol(code[prev])) {
  449. arriter.push('$self$'); // last point means extraction of object itself
  450. break;
  451. }
  452. while ((pos2 < code.length) && is_next_symbol(code[pos2])) pos2++;
  453. // this is looks like function call - do not need to extract member with
  454. if (code[pos2] === '(') { pos2 = prev - 1; break; }
  455. // this is selection of member, but probably we need to activate iterator for ROOT collection
  456. if (!arriter.length) {
  457. // TODO: if selected member is simple data type - no need to make other checks - just break here
  458. if ((br.fType === kClonesNode) || (br.fType === kSTLNode))
  459. arriter.push(undefined);
  460. else {
  461. const objclass = getBranchObjectClass(br, tree, false, true);
  462. if (objclass && isRootCollection(null, objclass))
  463. arriter.push(undefined);
  464. }
  465. }
  466. arriter.push(code.slice(prev, pos2));
  467. continue;
  468. }
  469. if (code[pos2] !== '[') break;
  470. // simple []
  471. if (code[pos2 + 1] === ']') { arriter.push(undefined); pos2 += 2; continue; }
  472. const prev = pos2++;
  473. let cnt = 0;
  474. while ((pos2 < code.length) && ((code[pos2] !== ']') || (cnt > 0))) {
  475. if (code[pos2] === '[') cnt++; else if (code[pos2] === ']') cnt--;
  476. pos2++;
  477. }
  478. const sub = code.slice(prev + 1, pos2);
  479. switch (sub) {
  480. case '':
  481. case '$all$': arriter.push(undefined); break;
  482. case '$last$': arriter.push('$last$'); break;
  483. case '$size$': arriter.push('$size$'); break;
  484. case '$first$': arriter.push(0); break;
  485. default:
  486. if (Number.isInteger(parseInt(sub)))
  487. arriter.push(parseInt(sub));
  488. else {
  489. // try to compile code as draw variable
  490. const subvar = new TDrawVariable(this.globals);
  491. if (!subvar.parse(tree, selector, sub)) return false;
  492. arriter.push(subvar);
  493. }
  494. }
  495. pos2++;
  496. }
  497. if (!arriter.length)
  498. arriter = undefined;
  499. else if ((arriter.length === 1) && (arriter[0] === undefined))
  500. arriter = true;
  501. let indx = selector.indexOfBranch(br);
  502. if (indx < 0) indx = selector.addBranch(br, undefined, branch_mode);
  503. branch_mode = undefined;
  504. this.brindex.push(indx);
  505. this.branches.push(selector.nameOfBranch(indx));
  506. this.brarray.push(arriter);
  507. // this is simple case of direct usage of the branch
  508. if ((pos === 0) && (pos2 === code.length) && (this.branches.length === 1)) {
  509. this.direct_branch = true; // remember that branch read as is
  510. return true;
  511. }
  512. const replace = `arg.var${this.branches.length - 1}`;
  513. code = code.slice(0, pos) + replace + code.slice(pos2);
  514. pos += replace.length;
  515. }
  516. // support usage of some standard TMath functions
  517. code = code.replace(/TMath::Exp\(/g, 'Math.exp(')
  518. .replace(/TMath::Abs\(/g, 'Math.abs(')
  519. .replace(/TMath::Prob\(/g, 'arg.$math.Prob(')
  520. .replace(/TMath::Gaus\(/g, 'arg.$math.Gaus(');
  521. this.func = new Function('arg', `return (${code})`);
  522. return true;
  523. }
  524. /** @summary Check if it is dummy variable */
  525. is_dummy() { return !this.branches.length && !this.func; }
  526. /** @summary Produce variable
  527. * @desc after reading tree branches into the object, calculate variable value */
  528. produce(obj) {
  529. this.length = 1;
  530. this.isarray = false;
  531. if (this.is_dummy()) {
  532. this.value = 1; // used as dummy weight variable
  533. this.kind = 'number';
  534. return;
  535. }
  536. const arg = { $globals: this.globals, $math: jsroot_math }, arrs = [];
  537. let usearrlen = -1;
  538. for (let n = 0; n < this.branches.length; ++n) {
  539. const name = `var${n}`;
  540. arg[name] = obj[this.branches[n]];
  541. // try to check if branch is array and need to be iterated
  542. if (this.brarray[n] === undefined)
  543. this.brarray[n] = (checkArrayPrototype(arg[name]) > 0) || isRootCollection(arg[name]);
  544. // no array - no pain
  545. if (this.brarray[n] === false) continue;
  546. // check if array can be used as is - one dimension and normal values
  547. if ((this.brarray[n] === true) && (checkArrayPrototype(arg[name], true) === 2)) {
  548. // plain array, can be used as is
  549. arrs[n] = arg[name];
  550. } else {
  551. const iter = new ArrayIterator(arg[name], this.brarray[n], obj);
  552. arrs[n] = [];
  553. while (iter.next()) arrs[n].push(iter.value);
  554. }
  555. if ((usearrlen < 0) || (usearrlen < arrs[n].length)) usearrlen = arrs[n].length;
  556. }
  557. if (usearrlen < 0) {
  558. this.value = this.direct_branch ? arg.var0 : this.func(arg);
  559. if (!this.kind) this.kind = typeof this.value;
  560. return;
  561. }
  562. if (usearrlen === 0) {
  563. // empty array - no any histogram should be filled
  564. this.length = 0;
  565. this.value = 0;
  566. return;
  567. }
  568. this.length = usearrlen;
  569. this.isarray = true;
  570. if (this.direct_branch)
  571. this.value = arrs[0]; // just use array
  572. else {
  573. this.value = new Array(usearrlen);
  574. for (let k = 0; k < usearrlen; ++k) {
  575. for (let n = 0; n < this.branches.length; ++n) {
  576. if (arrs[n])
  577. arg[`var${n}`] = arrs[n][k];
  578. }
  579. this.value[k] = this.func(arg);
  580. }
  581. }
  582. if (!this.kind) this.kind = typeof this.value[0];
  583. }
  584. /** @summary Get variable */
  585. get(indx) { return this.isarray ? this.value[indx] : this.value; }
  586. /** @summary Append array to the buffer */
  587. appendArray(tgtarr) { this.buf = this.buf.concat(tgtarr[this.branches[0]]); }
  588. } // class TDrawVariable
  589. /**
  590. * @summary Selector class for TTree::Draw function
  591. *
  592. * @private
  593. */
  594. class TDrawSelector extends TSelector {
  595. /** @summary constructor */
  596. constructor() {
  597. super();
  598. this.ndim = 0;
  599. this.vars = []; // array of expression variables
  600. this.cut = null; // cut variable
  601. this.hist = null;
  602. this.drawopt = '';
  603. this.hist_name = '$htemp';
  604. this.draw_title = 'Result of TTree::Draw';
  605. this.graph = false;
  606. this.hist_args = []; // arguments for histogram creation
  607. this.arr_limit = 1000; // number of accumulated items before create histogram
  608. this.htype = 'F';
  609. this.monitoring = 0;
  610. this.globals = {}; // object with global parameters, which could be used in any draw expression
  611. this.last_progress = 0;
  612. this.aver_diff = 0;
  613. }
  614. /** @summary Set draw selector callbacks */
  615. setCallback(result_callback, progress_callback) {
  616. this.result_callback = result_callback;
  617. this.progress_callback = progress_callback;
  618. }
  619. /** @summary Parse parameters */
  620. parseParameters(tree, args, expr) {
  621. if (!expr || !isStr(expr))
  622. return '';
  623. // parse parameters which defined at the end as expression;par1name:par1value;par2name:par2value
  624. let pos = expr.lastIndexOf(';');
  625. while (pos >= 0) {
  626. let parname = expr.slice(pos + 1), parvalue;
  627. expr = expr.slice(0, pos);
  628. pos = expr.lastIndexOf(';');
  629. const separ = parname.indexOf(':');
  630. if (separ > 0) { parvalue = parname.slice(separ + 1); parname = parname.slice(0, separ); }
  631. let intvalue = parseInt(parvalue);
  632. if (!parvalue || !Number.isInteger(intvalue))
  633. intvalue = undefined;
  634. switch (parname) {
  635. case 'elist':
  636. if ((parvalue.at(0) === '[') && (parvalue.at(-1) === ']')) {
  637. parvalue = parvalue.slice(1, parvalue.length - 1).replaceAll(/\s/g, '');
  638. args.elist = [];
  639. let p = 0, last_v = -1;
  640. const getInt = () => {
  641. const p0 = p;
  642. while ((p < parvalue.length) && (parvalue.charCodeAt(p) >= 48) && (parvalue.charCodeAt(p) < 58))
  643. p++;
  644. return parseInt(parvalue.slice(p0, p));
  645. };
  646. while (p < parvalue.length) {
  647. const v1 = getInt();
  648. if (v1 <= last_v) {
  649. console.log('position', p);
  650. throw Error(`Wrong entry id ${v1} in elist last ${last_v}`);
  651. }
  652. let v2 = v1;
  653. if (parvalue[p] === '.' && parvalue[p + 1] === '.') {
  654. p += 2;
  655. v2 = getInt();
  656. if (v2 < v1)
  657. throw Error(`Wrong entry id ${v2} in range ${v1}`);
  658. }
  659. if (parvalue[p] === ',' || p === parvalue.length) {
  660. for (let v = v1; v <= v2; ++v) {
  661. args.elist.push(v);
  662. last_v = v;
  663. }
  664. p++;
  665. } else
  666. throw Error('Wrong syntax for elist');
  667. }
  668. }
  669. break;
  670. case 'entries':
  671. case 'num':
  672. case 'numentries':
  673. if (parvalue === 'all')
  674. args.numentries = tree.fEntries;
  675. else if (parvalue === 'half')
  676. args.numentries = Math.round(tree.fEntries / 2);
  677. else if (intvalue !== undefined)
  678. args.numentries = intvalue;
  679. break;
  680. case 'first':
  681. if (intvalue !== undefined)
  682. args.firstentry = intvalue;
  683. break;
  684. case 'nmatch':
  685. if (intvalue !== undefined)
  686. this.nmatch = intvalue;
  687. break;
  688. case 'mon':
  689. case 'monitor':
  690. args.monitoring = (intvalue !== undefined) ? intvalue : 5000;
  691. break;
  692. case 'player':
  693. args.player = true;
  694. break;
  695. case 'dump':
  696. args.dump = true;
  697. break;
  698. case 'staged':
  699. args.staged = true;
  700. break;
  701. case 'maxseg':
  702. case 'maxrange':
  703. if (intvalue)
  704. tree.$file.fMaxRanges = intvalue;
  705. break;
  706. case 'accum':
  707. if (intvalue)
  708. this.arr_limit = intvalue;
  709. break;
  710. case 'htype':
  711. if (parvalue && (parvalue.length === 1)) {
  712. this.htype = parvalue.toUpperCase();
  713. if (['C', 'S', 'I', 'F', 'L', 'D'].indexOf(this.htype) < 0)
  714. this.htype = 'F';
  715. }
  716. break;
  717. case 'hbins':
  718. this.hist_nbins = parseInt(parvalue);
  719. if (!Number.isInteger(this.hist_nbins) || (this.hist_nbins <= 3))
  720. delete this.hist_nbins;
  721. else
  722. this.want_hist = true;
  723. break;
  724. case 'drawopt':
  725. args.drawopt = parvalue;
  726. break;
  727. case 'graph':
  728. args.graph = intvalue || true;
  729. break;
  730. }
  731. }
  732. pos = expr.lastIndexOf('>>');
  733. if (pos >= 0) {
  734. let harg = expr.slice(pos + 2).trim();
  735. expr = expr.slice(0, pos).trim();
  736. pos = harg.indexOf('(');
  737. if (pos > 0) {
  738. this.hist_name = harg.slice(0, pos);
  739. harg = harg.slice(pos);
  740. }
  741. if (harg === 'dump')
  742. args.dump = true;
  743. else if (harg === 'elist')
  744. args.dump_entries = true;
  745. else if (harg.indexOf('Graph') === 0)
  746. args.graph = true;
  747. else if (pos < 0) {
  748. this.want_hist = true;
  749. this.hist_name = harg;
  750. } else if ((harg[0] === '(') && (harg.at(-1) === ')')) {
  751. this.want_hist = true;
  752. harg = harg.slice(1, harg.length - 1).split(',');
  753. let isok = true;
  754. for (let n = 0; n < harg.length; ++n) {
  755. harg[n] = (n % 3 === 0) ? parseInt(harg[n]) : parseFloat(harg[n]);
  756. if (!Number.isFinite(harg[n])) isok = false;
  757. }
  758. if (isok)
  759. this.hist_args = harg;
  760. }
  761. }
  762. if (args.dump) {
  763. this.dump_values = true;
  764. args.reallocate_objects = true;
  765. if (args.numentries === undefined) {
  766. args.numentries = 10;
  767. args._dflt_entries = true;
  768. }
  769. }
  770. return expr;
  771. }
  772. /** @summary Create draw expression for N-dim with cut */
  773. createDrawExpression(tree, names, cut, args) {
  774. if (args.dump && names.length === 1 && names[0] === 'Entry$') {
  775. args.dump_entries = true;
  776. args.dump = false;
  777. }
  778. if (args.dump_entries) {
  779. this.dump_entries = true;
  780. this.hist = [];
  781. if (args._dflt_entries) {
  782. delete args._dflt_entries;
  783. delete args.numentries;
  784. }
  785. }
  786. let is_direct = !cut && !this.dump_entries;
  787. this.ndim = names.length;
  788. for (let n = 0; n < this.ndim; ++n) {
  789. this.vars[n] = new TDrawVariable(this.globals);
  790. if (!this.vars[n].parse(tree, this, names[n])) return false;
  791. if (!this.vars[n].direct_branch) is_direct = false;
  792. }
  793. this.cut = new TDrawVariable(this.globals);
  794. if (cut && !this.cut.parse(tree, this, cut))
  795. return false;
  796. if (!this.numBranches()) {
  797. console.warn('no any branch is selected');
  798. return false;
  799. }
  800. if (is_direct)
  801. this.ProcessArrays = this.ProcessArraysFunc;
  802. this.monitoring = args.monitoring;
  803. // force TPolyMarker3D drawing for 3D case
  804. if ((this.ndim === 3) && !this.want_hist && !args.dump)
  805. args.graph = true;
  806. this.graph = args.graph;
  807. if (args.drawopt !== undefined)
  808. this.drawopt = args.drawopt;
  809. else
  810. args.drawopt = this.drawopt = this.graph ? 'P' : '';
  811. return true;
  812. }
  813. /** @summary Parse draw expression */
  814. parseDrawExpression(tree, args) {
  815. // parse complete expression
  816. let expr = this.parseParameters(tree, args, args.expr), cut = '';
  817. // parse option for histogram creation
  818. this.draw_title = `drawing '${expr}' from ${tree.fName}`;
  819. let pos;
  820. if (args.cut)
  821. cut = args.cut;
  822. else {
  823. pos = expr.replace(/TMath::/g, 'TMath__').lastIndexOf('::'); // avoid confusion due-to :: in the namespace
  824. if (pos >= 0) {
  825. cut = expr.slice(pos + 2).trim();
  826. expr = expr.slice(0, pos).trim();
  827. }
  828. }
  829. args.parse_expr = expr;
  830. args.parse_cut = cut;
  831. // let names = expr.split(':'); // to allow usage of ? operator, we need to handle : as well
  832. let names = [], nbr1 = 0, nbr2 = 0, prev = 0;
  833. for (pos = 0; pos < expr.length; ++pos) {
  834. switch (expr[pos]) {
  835. case '(': nbr1++; break;
  836. case ')': nbr1--; break;
  837. case '[': nbr2++; break;
  838. case ']': nbr2--; break;
  839. case ':':
  840. if (expr[pos + 1] === ':') { pos++; continue; }
  841. if (!nbr1 && !nbr2 && (pos > prev)) names.push(expr.slice(prev, pos));
  842. prev = pos + 1;
  843. break;
  844. }
  845. }
  846. if (!nbr1 && !nbr2 && (pos > prev))
  847. names.push(expr.slice(prev, pos));
  848. if (args.staged) {
  849. args.staged_names = names;
  850. names = ['Entry$'];
  851. args.dump_entries = true;
  852. } else if (cut && args.dump_entries)
  853. names = ['Entry$'];
  854. else if ((names.length < 1) || (names.length > 3))
  855. return false;
  856. return this.createDrawExpression(tree, names, cut, args);
  857. }
  858. /** @summary Draw only specified branch */
  859. drawOnlyBranch(tree, branch, expr, args) {
  860. this.ndim = 1;
  861. if (expr.indexOf('dump') === 0)
  862. expr = ';' + expr;
  863. expr = this.parseParameters(tree, args, expr);
  864. this.monitoring = args.monitoring;
  865. if (args.dump) {
  866. this.dump_values = true;
  867. args.reallocate_objects = true;
  868. }
  869. if (this.dump_values) {
  870. this.hist = []; // array of dump objects
  871. this.leaf = args.leaf;
  872. // branch object remains, therefore we need to copy fields to see them all
  873. this.copy_fields = ((args.branch.fLeaves?.arr.length > 1) || args.branch.fBranches?.arr.length) && !args.leaf;
  874. this.addBranch(branch, 'br0', args.direct_branch); // add branch
  875. this.Process = this.ProcessDump;
  876. return true;
  877. }
  878. this.vars[0] = new TDrawVariable(this.globals);
  879. if (!this.vars[0].parse(tree, this, expr, branch, args.direct_branch))
  880. return false;
  881. this.draw_title = `drawing branch ${branch.fName} ${expr?' expr:'+expr:''} from ${tree.fName}`;
  882. this.cut = new TDrawVariable(this.globals);
  883. if (this.vars[0].direct_branch)
  884. this.ProcessArrays = this.ProcessArraysFunc;
  885. return true;
  886. }
  887. /** @summary Begin processing */
  888. Begin(tree) {
  889. this.globals.entries = tree.fEntries;
  890. if (this.monitoring)
  891. this.lasttm = new Date().getTime();
  892. }
  893. /** @summary Show progress */
  894. ShowProgress(/* value */) {}
  895. /** @summary Get bins for bits histogram */
  896. getBitsBins(nbits, res) {
  897. res.nbins = res.max = nbits;
  898. res.fLabels = create(clTHashList);
  899. for (let k = 0; k < nbits; ++k) {
  900. const s = create(clTObjString);
  901. s.fString = k.toString();
  902. s.fUniqueID = k + 1;
  903. res.fLabels.Add(s);
  904. }
  905. return res;
  906. }
  907. /** @summary Get min.max bins */
  908. getMinMaxBins(axisid, nbins) {
  909. const res = { min: 0, max: 0, nbins, k: 1, fLabels: null, title: '' };
  910. if (axisid >= this.ndim) return res;
  911. const arr = this.vars[axisid].buf;
  912. res.title = this.vars[axisid].code || '';
  913. if (this.vars[axisid].kind === 'object') {
  914. // this is any object type
  915. let typename, similar = true, maxbits = 8;
  916. for (let k = 0; k < arr.length; ++k) {
  917. if (!arr[k]) continue;
  918. if (!typename) typename = arr[k]._typename;
  919. if (typename !== arr[k]._typename) similar = false; // check all object types
  920. if (arr[k].fNbits) maxbits = Math.max(maxbits, arr[k].fNbits + 1);
  921. }
  922. if (typename && similar) {
  923. if ((typename === 'TBits') && (axisid === 0)) {
  924. this.fill1DHistogram = this.fillTBitsHistogram;
  925. if (maxbits % 8) maxbits = (maxbits & 0xfff0) + 8;
  926. if ((this.hist_name === 'bits') && (this.hist_args.length === 1) && this.hist_args[0])
  927. maxbits = this.hist_args[0];
  928. return this.getBitsBins(maxbits, res);
  929. }
  930. }
  931. }
  932. if (this.vars[axisid].kind === 'string') {
  933. res.lbls = []; // all labels
  934. for (let k = 0; k < arr.length; ++k) {
  935. if (res.lbls.indexOf(arr[k]) < 0)
  936. res.lbls.push(arr[k]);
  937. }
  938. res.lbls.sort();
  939. res.max = res.nbins = res.lbls.length;
  940. res.fLabels = create(clTHashList);
  941. for (let k = 0; k < res.lbls.length; ++k) {
  942. const s = create(clTObjString);
  943. s.fString = res.lbls[k];
  944. s.fUniqueID = k + 1;
  945. if (s.fString === '') s.fString = '<empty>';
  946. res.fLabels.Add(s);
  947. }
  948. } else if ((axisid === 0) && (this.hist_name === 'bits') && (this.hist_args.length <= 1)) {
  949. this.fill1DHistogram = this.fillBitsHistogram;
  950. return this.getBitsBins(this.hist_args[0] || 32, res);
  951. } else if (axisid * 3 + 2 < this.hist_args.length) {
  952. res.nbins = this.hist_args[axisid * 3];
  953. res.min = this.hist_args[axisid * 3 + 1];
  954. res.max = this.hist_args[axisid * 3 + 2];
  955. } else {
  956. let is_any = false;
  957. for (let i = 1; i < arr.length; ++i) {
  958. const v = arr[i];
  959. if (!Number.isFinite(v)) continue;
  960. if (is_any) {
  961. res.min = Math.min(res.min, v);
  962. res.max = Math.max(res.max, v);
  963. } else {
  964. res.min = res.max = v;
  965. is_any = true;
  966. }
  967. }
  968. if (!is_any) { res.min = 0; res.max = 1; }
  969. if (this.hist_nbins)
  970. nbins = res.nbins = this.hist_nbins;
  971. res.isinteger = (Math.round(res.min) === res.min) && (Math.round(res.max) === res.max);
  972. if (res.isinteger) {
  973. for (let k = 0; k < arr.length; ++k)
  974. if (arr[k] !== Math.round(arr[k])) { res.isinteger = false; break; }
  975. }
  976. if (res.isinteger) {
  977. res.min = Math.round(res.min);
  978. res.max = Math.round(res.max);
  979. if (res.max - res.min < nbins * 5) {
  980. res.min -= 1;
  981. res.max += 2;
  982. res.nbins = Math.round(res.max - res.min);
  983. } else {
  984. const range = (res.max - res.min + 2);
  985. let step = Math.floor(range / nbins);
  986. while (step * nbins < range) step++;
  987. res.max = res.min + nbins * step;
  988. }
  989. } else if (res.min >= res.max) {
  990. res.max = res.min;
  991. if (Math.abs(res.min) < 100) { res.min -= 1; res.max += 1; } else
  992. if (res.min > 0) { res.min *= 0.9; res.max *= 1.1; } else { res.min *= 1.1; res.max *= 0.9; }
  993. } else
  994. res.max += (res.max - res.min) / res.nbins;
  995. }
  996. res.k = res.nbins / (res.max - res.min);
  997. res.GetBin = function(value) {
  998. const bin = this.lbls?.indexOf(value) ?? Number.isFinite(value) ? Math.floor((value - this.min) * this.k) : this.nbins + 1;
  999. return bin < 0 ? 0 : ((bin > this.nbins) ? this.nbins + 1 : bin + 1);
  1000. };
  1001. return res;
  1002. }
  1003. /** @summary Create histogram which matches value in dimensions */
  1004. createHistogram(nbins, set_hist = false) {
  1005. if (!nbins) nbins = 20;
  1006. const x = this.getMinMaxBins(0, nbins),
  1007. y = this.getMinMaxBins(1, nbins),
  1008. z = this.getMinMaxBins(2, nbins);
  1009. let hist = null;
  1010. switch (this.ndim) {
  1011. case 1: hist = createHistogram(clTH1 + this.htype, x.nbins); break;
  1012. case 2: hist = createHistogram(clTH2 + this.htype, x.nbins, y.nbins); break;
  1013. case 3: hist = createHistogram(clTH3 + this.htype, x.nbins, y.nbins, z.nbins); break;
  1014. }
  1015. hist.fXaxis.fTitle = x.title;
  1016. hist.fXaxis.fXmin = x.min;
  1017. hist.fXaxis.fXmax = x.max;
  1018. hist.fXaxis.fLabels = x.fLabels;
  1019. if (this.ndim > 1) hist.fYaxis.fTitle = y.title;
  1020. hist.fYaxis.fXmin = y.min;
  1021. hist.fYaxis.fXmax = y.max;
  1022. hist.fYaxis.fLabels = y.fLabels;
  1023. if (this.ndim > 2) hist.fZaxis.fTitle = z.title;
  1024. hist.fZaxis.fXmin = z.min;
  1025. hist.fZaxis.fXmax = z.max;
  1026. hist.fZaxis.fLabels = z.fLabels;
  1027. hist.fName = this.hist_name;
  1028. hist.fTitle = this.draw_title;
  1029. hist.fOption = this.drawopt;
  1030. hist.$custom_stat = (this.hist_name === '$htemp') ? 111110 : 111111;
  1031. if (set_hist) {
  1032. this.hist = hist;
  1033. this.x = x;
  1034. this.y = y;
  1035. this.z = z;
  1036. } else
  1037. hist.fBits |= kNoStats;
  1038. return hist;
  1039. }
  1040. /** @summary Create output object - histogram, graph, dump array */
  1041. createOutputObject() {
  1042. if (this.hist || !this.vars[0].buf)
  1043. return;
  1044. if (this.dump_values) {
  1045. // just create array where dumped values will be collected
  1046. this.hist = [];
  1047. // reassign fill method
  1048. this.fill1DHistogram = this.fill2DHistogram = this.fill3DHistogram = this.dumpValues;
  1049. } else if (this.graph) {
  1050. const N = this.vars[0].buf.length;
  1051. let res = null;
  1052. if (this.ndim === 1) {
  1053. // A 1-dimensional graph will just have the x axis as an index
  1054. res = createTGraph(N, Array.from(Array(N).keys()), this.vars[0].buf);
  1055. res.fName = 'Graph';
  1056. res.fTitle = this.draw_title;
  1057. } else if (this.ndim === 2) {
  1058. res = createTGraph(N, this.vars[0].buf, this.vars[1].buf);
  1059. res.fName = 'Graph';
  1060. res.fTitle = this.draw_title;
  1061. delete this.vars[1].buf;
  1062. } else if (this.ndim === 3) {
  1063. res = create(clTPolyMarker3D);
  1064. res.fN = N;
  1065. res.fLastPoint = N - 1;
  1066. const arr = new Array(N*3);
  1067. for (let k = 0; k< N; ++k) {
  1068. arr[k*3] = this.vars[0].buf[k];
  1069. arr[k*3+1] = this.vars[1].buf[k];
  1070. arr[k*3+2] = this.vars[2].buf[k];
  1071. }
  1072. res.fP = arr;
  1073. res.$hist = this.createHistogram(10);
  1074. delete this.vars[1].buf;
  1075. delete this.vars[2].buf;
  1076. res.fName = 'Points';
  1077. }
  1078. this.hist = res;
  1079. } else {
  1080. const nbins = [200, 50, 20];
  1081. this.createHistogram(nbins[this.ndim], true);
  1082. }
  1083. const var0 = this.vars[0].buf, cut = this.cut.buf, len = var0.length;
  1084. if (!this.graph) {
  1085. switch (this.ndim) {
  1086. case 1: {
  1087. for (let n = 0; n < len; ++n)
  1088. this.fill1DHistogram(var0[n], cut ? cut[n] : 1);
  1089. break;
  1090. }
  1091. case 2: {
  1092. const var1 = this.vars[1].buf;
  1093. for (let n = 0; n < len; ++n)
  1094. this.fill2DHistogram(var0[n], var1[n], cut ? cut[n] : 1);
  1095. delete this.vars[1].buf;
  1096. break;
  1097. }
  1098. case 3: {
  1099. const var1 = this.vars[1].buf, var2 = this.vars[2].buf;
  1100. for (let n = 0; n < len; ++n)
  1101. this.fill3DHistogram(var0[n], var1[n], var2[n], cut ? cut[n] : 1);
  1102. delete this.vars[1].buf;
  1103. delete this.vars[2].buf;
  1104. break;
  1105. }
  1106. }
  1107. }
  1108. delete this.vars[0].buf;
  1109. delete this.cut.buf;
  1110. }
  1111. /** @summary Fill TBits histogram */
  1112. fillTBitsHistogram(xvalue, weight) {
  1113. if (!weight || !xvalue || !xvalue.fNbits || !xvalue.fAllBits) return;
  1114. const sz = Math.min(xvalue.fNbits + 1, xvalue.fNbytes * 8);
  1115. for (let bit = 0, mask = 1, b = 0; bit < sz; ++bit) {
  1116. if (xvalue.fAllBits[b] && mask) {
  1117. if (bit <= this.x.nbins)
  1118. this.hist.fArray[bit + 1] += weight;
  1119. else
  1120. this.hist.fArray[this.x.nbins + 1] += weight;
  1121. }
  1122. mask *= 2;
  1123. if (mask >= 0x100) { mask = 1; ++b; }
  1124. }
  1125. }
  1126. /** @summary Fill bits histogram */
  1127. fillBitsHistogram(xvalue, weight) {
  1128. if (!weight) return;
  1129. for (let bit = 0, mask = 1; bit < this.x.nbins; ++bit) {
  1130. if (xvalue & mask) this.hist.fArray[bit + 1] += weight;
  1131. mask *= 2;
  1132. }
  1133. }
  1134. /** @summary Fill 1D histogram */
  1135. fill1DHistogram(xvalue, weight) {
  1136. const bin = this.x.GetBin(xvalue);
  1137. this.hist.fArray[bin] += weight;
  1138. if (!this.x.lbls && Number.isFinite(xvalue)) {
  1139. this.hist.fTsumw += weight;
  1140. this.hist.fTsumwx += weight * xvalue;
  1141. this.hist.fTsumwx2 += weight * xvalue * xvalue;
  1142. }
  1143. }
  1144. /** @summary Fill 2D histogram */
  1145. fill2DHistogram(xvalue, yvalue, weight) {
  1146. const xbin = this.x.GetBin(xvalue),
  1147. ybin = this.y.GetBin(yvalue);
  1148. this.hist.fArray[xbin + (this.x.nbins + 2) * ybin] += weight;
  1149. if (!this.x.lbls && !this.y.lbls && Number.isFinite(xvalue) && Number.isFinite(yvalue)) {
  1150. this.hist.fTsumw += weight;
  1151. this.hist.fTsumwx += weight * xvalue;
  1152. this.hist.fTsumwy += weight * yvalue;
  1153. this.hist.fTsumwx2 += weight * xvalue * xvalue;
  1154. this.hist.fTsumwxy += weight * xvalue * yvalue;
  1155. this.hist.fTsumwy2 += weight * yvalue * yvalue;
  1156. }
  1157. }
  1158. /** @summary Fill 3D histogram */
  1159. fill3DHistogram(xvalue, yvalue, zvalue, weight) {
  1160. const xbin = this.x.GetBin(xvalue),
  1161. ybin = this.y.GetBin(yvalue),
  1162. zbin = this.z.GetBin(zvalue);
  1163. this.hist.fArray[xbin + (this.x.nbins + 2) * (ybin + (this.y.nbins + 2) * zbin)] += weight;
  1164. if (!this.x.lbls && !this.y.lbls && !this.z.lbls && Number.isFinite(xvalue) && Number.isFinite(yvalue) && Number.isFinite(zvalue)) {
  1165. this.hist.fTsumw += weight;
  1166. this.hist.fTsumwx += weight * xvalue;
  1167. this.hist.fTsumwy += weight * yvalue;
  1168. this.hist.fTsumwz += weight * zvalue;
  1169. this.hist.fTsumwx2 += weight * xvalue * xvalue;
  1170. this.hist.fTsumwy2 += weight * yvalue * yvalue;
  1171. this.hist.fTsumwz2 += weight * zvalue * zvalue;
  1172. this.hist.fTsumwxy += weight * xvalue * yvalue;
  1173. this.hist.fTsumwxz += weight * xvalue * zvalue;
  1174. this.hist.fTsumwyz += weight * yvalue * zvalue;
  1175. }
  1176. }
  1177. /** @summary Dump values */
  1178. dumpValues(v1, v2, v3, v4) {
  1179. let obj;
  1180. switch (this.ndim) {
  1181. case 1: obj = { x: v1, weight: v2 }; break;
  1182. case 2: obj = { x: v1, y: v2, weight: v3 }; break;
  1183. case 3: obj = { x: v1, y: v2, z: v3, weight: v4 }; break;
  1184. }
  1185. if (this.cut.is_dummy()) {
  1186. if (this.ndim === 1)
  1187. obj = v1;
  1188. else
  1189. delete obj.weight;
  1190. }
  1191. this.hist.push(obj);
  1192. }
  1193. /** @summary function used when all branches can be read as array
  1194. * @desc most typical usage - histogram filling of single branch */
  1195. ProcessArraysFunc(/* entry */) {
  1196. if (this.arr_limit || this.graph) {
  1197. const var0 = this.vars[0],
  1198. var1 = this.vars[1],
  1199. var2 = this.vars[2],
  1200. len = this.tgtarr.br0.length;
  1201. if (!var0.buf.length && (len >= this.arr_limit) && !this.graph) {
  1202. // special use case - first array large enough to create histogram directly base on it
  1203. var0.buf = this.tgtarr.br0;
  1204. if (var1) var1.buf = this.tgtarr.br1;
  1205. if (var2) var2.buf = this.tgtarr.br2;
  1206. } else {
  1207. for (let k = 0; k < len; ++k) {
  1208. var0.buf.push(this.tgtarr.br0[k]);
  1209. if (var1) var1.buf.push(this.tgtarr.br1[k]);
  1210. if (var2) var2.buf.push(this.tgtarr.br2[k]);
  1211. }
  1212. }
  1213. var0.kind = 'number';
  1214. if (var1) var1.kind = 'number';
  1215. if (var2) var2.kind = 'number';
  1216. this.cut.buf = null; // do not create buffer for cuts
  1217. if (!this.graph && (var0.buf.length >= this.arr_limit)) {
  1218. this.createOutputObject();
  1219. this.arr_limit = 0;
  1220. }
  1221. } else {
  1222. const br0 = this.tgtarr.br0, len = br0.length;
  1223. switch (this.ndim) {
  1224. case 1: {
  1225. for (let k = 0; k < len; ++k)
  1226. this.fill1DHistogram(br0[k], 1);
  1227. break;
  1228. }
  1229. case 2: {
  1230. const br1 = this.tgtarr.br1;
  1231. for (let k = 0; k < len; ++k)
  1232. this.fill2DHistogram(br0[k], br1[k], 1);
  1233. break;
  1234. }
  1235. case 3: {
  1236. const br1 = this.tgtarr.br1, br2 = this.tgtarr.br2;
  1237. for (let k = 0; k < len; ++k)
  1238. this.fill3DHistogram(br0[k], br1[k], br2[k], 1);
  1239. break;
  1240. }
  1241. }
  1242. }
  1243. }
  1244. /** @summary simple dump of the branch - no need to analyze something */
  1245. ProcessDump(/* entry */) {
  1246. const res = this.leaf ? this.tgtobj.br0[this.leaf] : this.tgtobj.br0;
  1247. if (res && this.copy_fields) {
  1248. if (checkArrayPrototype(res) === 0)
  1249. this.hist.push(Object.assign({}, res));
  1250. else
  1251. this.hist.push(res);
  1252. } else
  1253. this.hist.push(res);
  1254. }
  1255. /** @summary Normal TSelector Process handler */
  1256. Process(entry) {
  1257. this.globals.entry = entry; // can be used in any expression
  1258. this.cut.produce(this.tgtobj);
  1259. if (!this.dump_values && !this.cut.value)
  1260. return;
  1261. for (let n = 0; n < this.ndim; ++n)
  1262. this.vars[n].produce(this.tgtobj);
  1263. const var0 = this.vars[0], var1 = this.vars[1], var2 = this.vars[2], cut = this.cut;
  1264. if (this.dump_entries)
  1265. this.hist.push(entry);
  1266. else if (this.graph || this.arr_limit) {
  1267. switch (this.ndim) {
  1268. case 1:
  1269. for (let n0 = 0; n0 < var0.length; ++n0) {
  1270. var0.buf.push(var0.get(n0));
  1271. cut.buf?.push(cut.value);
  1272. }
  1273. break;
  1274. case 2:
  1275. for (let n0 = 0; n0 < var0.length; ++n0) {
  1276. for (let n1 = 0; n1 < var1.length; ++n1) {
  1277. var0.buf.push(var0.get(n0));
  1278. var1.buf.push(var1.get(n1));
  1279. cut.buf?.push(cut.value);
  1280. }
  1281. }
  1282. break;
  1283. case 3:
  1284. for (let n0 = 0; n0 < var0.length; ++n0) {
  1285. for (let n1 = 0; n1 < var1.length; ++n1) {
  1286. for (let n2 = 0; n2 < var2.length; ++n2) {
  1287. var0.buf.push(var0.get(n0));
  1288. var1.buf.push(var1.get(n1));
  1289. var2.buf.push(var2.get(n2));
  1290. cut.buf?.push(cut.value);
  1291. }
  1292. }
  1293. }
  1294. break;
  1295. }
  1296. if (!this.graph && (var0.buf.length >= this.arr_limit)) {
  1297. this.createOutputObject();
  1298. this.arr_limit = 0;
  1299. }
  1300. } else if (this.hist) {
  1301. switch (this.ndim) {
  1302. case 1:
  1303. for (let n0 = 0; n0 < var0.length; ++n0)
  1304. this.fill1DHistogram(var0.get(n0), cut.value);
  1305. break;
  1306. case 2:
  1307. for (let n0 = 0; n0 < var0.length; ++n0) {
  1308. for (let n1 = 0; n1 < var1.length; ++n1)
  1309. this.fill2DHistogram(var0.get(n0), var1.get(n1), cut.value);
  1310. }
  1311. break;
  1312. case 3:
  1313. for (let n0 = 0; n0 < var0.length; ++n0) {
  1314. for (let n1 = 0; n1 < var1.length; ++n1) {
  1315. for (let n2 = 0; n2 < var2.length; ++n2)
  1316. this.fill3DHistogram(var0.get(n0), var1.get(n1), var2.get(n2), cut.value);
  1317. }
  1318. }
  1319. break;
  1320. }
  1321. }
  1322. if (this.monitoring && this.hist && !this.dump_values) {
  1323. const now = new Date().getTime();
  1324. if (now - this.lasttm > this.monitoring) {
  1325. this.lasttm = now;
  1326. if (isFunc(this.progress_callback))
  1327. this.progress_callback(this.hist);
  1328. }
  1329. }
  1330. if ((this.nmatch !== undefined) && (--this.nmatch <= 0)) {
  1331. if (!this.hist)
  1332. this.createOutputObject();
  1333. this.Abort();
  1334. }
  1335. }
  1336. /** @summary Normal TSelector Terminate handler */
  1337. Terminate(res) {
  1338. if (res && !this.hist)
  1339. this.createOutputObject();
  1340. this.ShowProgress();
  1341. if (isFunc(this.result_callback))
  1342. this.result_callback(this.hist);
  1343. }
  1344. } // class TDrawSelector
  1345. /** @summary return type name of given member in the class
  1346. * @private */
  1347. function defineMemberTypeName(file, parent_class, member_name) {
  1348. const s_i = file.findStreamerInfo(parent_class),
  1349. arr = s_i?.fElements?.arr;
  1350. if (!arr)
  1351. return '';
  1352. let elem = null;
  1353. for (let k = 0; k < arr.length; ++k) {
  1354. if (arr[k].fTypeName === kBaseClass) {
  1355. const res = defineMemberTypeName(file, arr[k].fName, member_name);
  1356. if (res)
  1357. return res;
  1358. } else if (arr[k].fName === member_name) {
  1359. elem = arr[k];
  1360. break;
  1361. }
  1362. }
  1363. if (!elem)
  1364. return '';
  1365. let clname = elem.fTypeName;
  1366. if (clname.at(-1) === '*')
  1367. clname = clname.slice(0, clname.length - 1);
  1368. return clname;
  1369. }
  1370. /** @summary create fast list to assign all methods to the object
  1371. * @private */
  1372. function makeMethodsList(typename) {
  1373. const methods = getMethods(typename),
  1374. res = {
  1375. names: [],
  1376. values: [],
  1377. Create() {
  1378. const obj = {};
  1379. for (let n = 0; n < this.names.length; ++n)
  1380. obj[this.names[n]] = this.values[n];
  1381. return obj;
  1382. }
  1383. };
  1384. res.names.push('_typename');
  1385. res.values.push(typename);
  1386. for (const key in methods) {
  1387. res.names.push(key);
  1388. res.values.push(methods[key]);
  1389. }
  1390. return res;
  1391. }
  1392. /** @summary try to define classname for the branch member, scanning list of branches
  1393. * @private */
  1394. function detectBranchMemberClass(brlst, prefix, start) {
  1395. let clname = '';
  1396. for (let kk = (start || 0); kk < brlst.arr.length; ++kk) {
  1397. if ((brlst.arr[kk].fName.indexOf(prefix) === 0) && brlst.arr[kk].fClassName)
  1398. clname = brlst.arr[kk].fClassName;
  1399. }
  1400. return clname;
  1401. }
  1402. /** @summary Process selector for the tree
  1403. * @desc function similar to the TTree::Process
  1404. * @param {object} tree - instance of TTree class
  1405. * @param {object} selector - instance of {@link TSelector} class
  1406. * @param {object} [args] - different arguments
  1407. * @param {number} [args.firstentry] - first entry to process, 0 when not specified
  1408. * @param {number} [args.numentries] - number of entries to process, all when not specified
  1409. * @param {Array} [args.elist] - arrays of entries id to process
  1410. * @return {Promise} with TSelector instance */
  1411. async function treeProcess(tree, selector, args) {
  1412. if (!args) args = {};
  1413. if (!selector || !tree.$file || !selector.numBranches()) {
  1414. selector?.Terminate(false);
  1415. return Promise.reject(Error('required parameter missing for TTree::Process'));
  1416. }
  1417. // central handle with all information required for reading
  1418. const handle = {
  1419. tree, // keep tree reference
  1420. file: tree.$file, // keep file reference
  1421. selector, // reference on selector
  1422. arr: [], // list of branches
  1423. curr: -1, // current entry ID
  1424. current_entry: -1, // current processed entry
  1425. simple_read: true, // all baskets in all used branches are in sync,
  1426. process_arrays: true // one can process all branches as arrays
  1427. }, createLeafElem = (leaf, name) => {
  1428. // function creates TStreamerElement which corresponds to the elementary leaf
  1429. let datakind;
  1430. switch (leaf._typename) {
  1431. case 'TLeafF': datakind = kFloat; break;
  1432. case 'TLeafD': datakind = kDouble; break;
  1433. case 'TLeafO': datakind = kBool; break;
  1434. case 'TLeafB': datakind = leaf.fIsUnsigned ? kUChar : kChar; break;
  1435. case 'TLeafS': datakind = leaf.fIsUnsigned ? kUShort : kShort; break;
  1436. case 'TLeafI': datakind = leaf.fIsUnsigned ? kUInt : kInt; break;
  1437. case 'TLeafL': datakind = leaf.fIsUnsigned ? kULong64 : kLong64; break;
  1438. case 'TLeafC': datakind = kTString; break;
  1439. default: return null;
  1440. }
  1441. const elem = createStreamerElement(name || leaf.fName, datakind);
  1442. if (leaf.fLen > 1) {
  1443. elem.fType += kOffsetL;
  1444. elem.fArrayLength = leaf.fLen;
  1445. }
  1446. return elem;
  1447. }, findInHandle = branch => {
  1448. for (let k = 0; k < handle.arr.length; ++k) {
  1449. if (handle.arr[k].branch === branch)
  1450. return handle.arr[k];
  1451. }
  1452. return null;
  1453. };
  1454. let namecnt = 0;
  1455. function addBranchForReading(branch, target_object, target_name, read_mode) {
  1456. // central method to add branch for reading
  1457. // read_mode == true - read only this branch
  1458. // read_mode == '$child$' is just member of object from for STL or clones array
  1459. // read_mode == '<any class name>' is sub-object from STL or clones array, happens when such new object need to be created
  1460. // read_mode == '.member_name' select only reading of member_name instead of complete object
  1461. if (isStr(branch))
  1462. branch = findBranch(handle.tree, branch);
  1463. if (!branch) { console.error('Did not found branch'); return null; }
  1464. let item = findInHandle(branch);
  1465. if (item) {
  1466. console.error(`Branch ${branch.fName} already configured for reading`);
  1467. if (item.tgt !== target_object) console.error('Target object differs');
  1468. return null;
  1469. }
  1470. if (!branch.fEntries) {
  1471. console.warn(`Branch ${branch.fName} does not have entries`);
  1472. return null;
  1473. }
  1474. // console.log(`Add branch ${branch.fName}`);
  1475. item = {
  1476. branch,
  1477. tgt: target_object, // used target object - can be differ for object members
  1478. name: target_name,
  1479. index: -1, // index in the list of read branches
  1480. member: null, // member to read branch
  1481. type: 0, // keep type identifier
  1482. curr_entry: -1, // last processed entry
  1483. raw: null, // raw buffer for reading
  1484. basket: null, // current basket object
  1485. curr_basket: 0, // number of basket used for processing
  1486. read_entry: -1, // last entry which is already read
  1487. staged_entry: -1, // entry which is staged for reading
  1488. first_readentry: -1, // first entry to read
  1489. staged_basket: 0, // last basket staged for reading
  1490. eindx: 0, // index of last checked entry when selecting baskets
  1491. selected_baskets: [], // array of selected baskets, used when specific events are selected
  1492. numentries: branch.fEntries,
  1493. numbaskets: branch.fWriteBasket, // number of baskets which can be read from the file
  1494. counters: null, // branch indexes used as counters
  1495. ascounter: [], // list of other branches using that branch as counter
  1496. baskets: [], // array for read baskets,
  1497. staged_prev: 0, // entry limit of previous I/O request
  1498. staged_now: 0, // entry limit of current I/O request
  1499. progress_showtm: 0, // last time when progress was showed
  1500. getBasketEntry(k) {
  1501. if (!this.branch || (k > this.branch.fMaxBaskets)) return 0;
  1502. const res = (k < this.branch.fMaxBaskets) ? this.branch.fBasketEntry[k] : 0;
  1503. if (res) return res;
  1504. const bskt = (k > 0) ? this.branch.fBaskets.arr[k - 1] : null;
  1505. return bskt ? (this.branch.fBasketEntry[k - 1] + bskt.fNevBuf) : 0;
  1506. },
  1507. getTarget(tgtobj) {
  1508. // returns target object which should be used for the branch reading
  1509. if (!this.tgt) return tgtobj;
  1510. for (let k = 0; k < this.tgt.length; ++k) {
  1511. const sub = this.tgt[k];
  1512. if (!tgtobj[sub.name])
  1513. tgtobj[sub.name] = sub.lst.Create();
  1514. tgtobj = tgtobj[sub.name];
  1515. }
  1516. return tgtobj;
  1517. },
  1518. getEntry(entry) {
  1519. // This should be equivalent to TBranch::GetEntry() method
  1520. const shift = entry - this.first_entry;
  1521. let off;
  1522. if (!this.branch.TestBit(kDoNotUseBufferMap))
  1523. this.raw.clearObjectMap();
  1524. if (this.basket.fEntryOffset) {
  1525. off = this.basket.fEntryOffset[shift];
  1526. if (this.basket.fDisplacement)
  1527. this.raw.fDisplacement = this.basket.fDisplacement[shift];
  1528. } else
  1529. off = this.basket.fKeylen + this.basket.fNevBufSize * shift;
  1530. this.raw.locate(off - this.raw.raw_shift);
  1531. // this.member.func(this.raw, this.getTarget(tgtobj));
  1532. }
  1533. };
  1534. // last basket can be stored directly with the branch
  1535. while (item.getBasketEntry(item.numbaskets + 1)) item.numbaskets++;
  1536. // check all counters if we
  1537. const nb_leaves = branch.fLeaves?.arr?.length ?? 0,
  1538. leaf = (nb_leaves > 0) ? branch.fLeaves.arr[0] : null,
  1539. is_brelem = (branch._typename === clTBranchElement);
  1540. let elem = null, // TStreamerElement used to create reader
  1541. member = null, // member for actual reading of the branch
  1542. child_scan = 0, // scan child branches after main branch is appended
  1543. item_cnt = null, item_cnt2 = null, object_class;
  1544. if (branch.fBranchCount) {
  1545. item_cnt = findInHandle(branch.fBranchCount);
  1546. if (!item_cnt)
  1547. item_cnt = addBranchForReading(branch.fBranchCount, target_object, '$counter' + namecnt++, true);
  1548. if (!item_cnt) { console.error(`Cannot add counter branch ${branch.fBranchCount.fName}`); return null; }
  1549. let BranchCount2 = branch.fBranchCount2;
  1550. if (!BranchCount2 && (branch.fBranchCount.fStreamerType === kSTL) &&
  1551. ((branch.fStreamerType === kStreamLoop) || (branch.fStreamerType === kOffsetL + kStreamLoop))) {
  1552. // special case when count member from kStreamLoop not assigned as fBranchCount2
  1553. const elemd = findBrachStreamerElement(branch, handle.file),
  1554. arrd = branch.fBranchCount.fBranches.arr;
  1555. if (elemd?.fCountName && arrd) {
  1556. for (let k = 0; k < arrd.length; ++k) {
  1557. if (arrd[k].fName === branch.fBranchCount.fName + '.' + elemd.fCountName) {
  1558. BranchCount2 = arrd[k];
  1559. break;
  1560. }
  1561. }
  1562. }
  1563. if (!BranchCount2) console.error('Did not found branch for second counter of kStreamLoop element');
  1564. }
  1565. if (BranchCount2) {
  1566. item_cnt2 = findInHandle(BranchCount2);
  1567. if (!item_cnt2) item_cnt2 = addBranchForReading(BranchCount2, target_object, '$counter' + namecnt++, true);
  1568. if (!item_cnt2) { console.error(`Cannot add counter branch2 ${BranchCount2.fName}`); return null; }
  1569. }
  1570. } else if (nb_leaves === 1 && leaf && leaf.fLeafCount) {
  1571. const br_cnt = findBranch(handle.tree, leaf.fLeafCount.fName);
  1572. if (br_cnt) {
  1573. item_cnt = findInHandle(br_cnt);
  1574. if (!item_cnt) item_cnt = addBranchForReading(br_cnt, target_object, '$counter' + namecnt++, true);
  1575. if (!item_cnt) { console.error(`Cannot add counter branch ${br_cnt.fName}`); return null; }
  1576. }
  1577. }
  1578. function scanBranches(lst, master_target, chld_kind) {
  1579. if (!lst?.arr.length)
  1580. return true;
  1581. let match_prefix = branch.fName;
  1582. if (match_prefix.at(-1) === '.')
  1583. match_prefix = match_prefix.slice(0, match_prefix.length - 1);
  1584. if (isStr(read_mode) && (read_mode[0] === '.'))
  1585. match_prefix += read_mode;
  1586. match_prefix += '.';
  1587. for (let k = 0; k < lst.arr.length; ++k) {
  1588. const br = lst.arr[k];
  1589. if ((chld_kind > 0) && (br.fType !== chld_kind)) continue;
  1590. if (br.fType === kBaseClassNode) {
  1591. if (!scanBranches(br.fBranches, master_target, chld_kind))
  1592. return false;
  1593. continue;
  1594. }
  1595. const elem2 = findBrachStreamerElement(br, handle.file);
  1596. if (elem2?.fTypeName === kBaseClass) {
  1597. // if branch is data of base class, map it to original target
  1598. if (br.fTotBytes && !addBranchForReading(br, target_object, target_name, read_mode))
  1599. return false;
  1600. if (!scanBranches(br.fBranches, master_target, chld_kind))
  1601. return false;
  1602. continue;
  1603. }
  1604. let subname = br.fName, chld_direct = 1;
  1605. if (br.fName.indexOf(match_prefix) === 0)
  1606. subname = subname.slice(match_prefix.length);
  1607. else if (chld_kind > 0)
  1608. continue; // for defined children names prefix must be present
  1609. let p = subname.indexOf('[');
  1610. if (p > 0) subname = subname.slice(0, p);
  1611. p = subname.indexOf('<');
  1612. if (p > 0) subname = subname.slice(0, p);
  1613. if (chld_kind > 0) {
  1614. chld_direct = '$child$';
  1615. const pp = subname.indexOf('.');
  1616. if (pp > 0) chld_direct = detectBranchMemberClass(lst, branch.fName + '.' + subname.slice(0, pp + 1), k) || clTObject;
  1617. }
  1618. if (!addBranchForReading(br, master_target, subname, chld_direct)) return false;
  1619. }
  1620. return true;
  1621. }
  1622. if (branch._typename === 'TBranchObject') {
  1623. member = {
  1624. name: target_name,
  1625. typename: branch.fClassName,
  1626. virtual: leaf.fVirtual,
  1627. func(buf, obj) {
  1628. const clname = this.virtual ? buf.readFastString(buf.ntou1() + 1) : this.typename;
  1629. obj[this.name] = buf.classStreamer({}, clname);
  1630. }
  1631. };
  1632. } else if ((branch.fType === kClonesNode) || (branch.fType === kSTLNode)) {
  1633. elem = createStreamerElement(target_name, kInt);
  1634. if (!read_mode || (isStr(read_mode) && (read_mode[0] === '.')) || (read_mode === 1)) {
  1635. handle.process_arrays = false;
  1636. member = {
  1637. name: target_name,
  1638. conttype: branch.fClonesName || clTObject,
  1639. reallocate: args.reallocate_objects,
  1640. func(buf, obj) {
  1641. const size = buf.ntoi4();
  1642. let n = 0, arr = obj[this.name];
  1643. if (!arr || this.reallocate)
  1644. arr = obj[this.name] = new Array(size);
  1645. else {
  1646. n = arr.length;
  1647. arr.length = size; // reallocate array
  1648. }
  1649. while (n < size) arr[n++] = this.methods.Create(); // create new objects
  1650. }
  1651. };
  1652. if (isStr(read_mode) && (read_mode[0] === '.')) {
  1653. member.conttype = detectBranchMemberClass(branch.fBranches, branch.fName + read_mode);
  1654. if (!member.conttype) {
  1655. console.error(`Cannot select object ${read_mode} in the branch ${branch.fName}`);
  1656. return null;
  1657. }
  1658. }
  1659. member.methods = makeMethodsList(member.conttype);
  1660. child_scan = (branch.fType === kClonesNode) ? kClonesMemberNode : kSTLMemberNode;
  1661. }
  1662. } else if ((object_class = getBranchObjectClass(branch, handle.tree))) {
  1663. if (read_mode === true) {
  1664. console.warn(`Object branch ${object_class} can not have data to be read directly`);
  1665. return null;
  1666. }
  1667. handle.process_arrays = false;
  1668. const newtgt = new Array((target_object?.length || 0) + 1);
  1669. for (let l = 0; l < newtgt.length - 1; ++l)
  1670. newtgt[l] = target_object[l];
  1671. newtgt[newtgt.length - 1] = { name: target_name, lst: makeMethodsList(object_class) };
  1672. // this kind of branch does not have baskets and not need to be read
  1673. return scanBranches(branch.fBranches, newtgt, 0) ? item : null;
  1674. } else if (is_brelem && (nb_leaves === 1) && (leaf.fName === branch.fName) && (branch.fID === -1)) {
  1675. elem = createStreamerElement(target_name, branch.fClassName);
  1676. if (elem.fType === kAny) {
  1677. const streamer = handle.file.getStreamer(branch.fClassName, { val: branch.fClassVersion, checksum: branch.fCheckSum });
  1678. if (!streamer) {
  1679. elem = null;
  1680. console.warn('not found streamer!');
  1681. } else {
  1682. member = {
  1683. name: target_name,
  1684. typename: branch.fClassName,
  1685. streamer,
  1686. func(buf, obj) {
  1687. const res = { _typename: this.typename };
  1688. for (let n = 0; n < this.streamer.length; ++n)
  1689. this.streamer[n].func(buf, res);
  1690. obj[this.name] = res;
  1691. }
  1692. };
  1693. }
  1694. }
  1695. // elem.fType = kAnyP;
  1696. // only STL containers here
  1697. // if (!elem.fSTLtype) elem = null;
  1698. } else if (is_brelem && (nb_leaves <= 1)) {
  1699. elem = findBrachStreamerElement(branch, handle.file);
  1700. // this is basic type - can try to solve problem differently
  1701. if (!elem && branch.fStreamerType && (branch.fStreamerType < 20))
  1702. elem = createStreamerElement(target_name, branch.fStreamerType);
  1703. } else if (nb_leaves === 1) {
  1704. // no special constrains for the leaf names
  1705. elem = createLeafElem(leaf, target_name);
  1706. } else if ((branch._typename === 'TBranch') && (nb_leaves > 1)) {
  1707. // branch with many elementary leaves
  1708. const leaves = new Array(nb_leaves);
  1709. let isok = true;
  1710. for (let l = 0; l < nb_leaves; ++l) {
  1711. leaves[l] = createMemberStreamer(createLeafElem(branch.fLeaves.arr[l]), handle.file);
  1712. if (!leaves[l]) isok = false;
  1713. }
  1714. if (isok) {
  1715. member = {
  1716. name: target_name,
  1717. leaves,
  1718. func(buf, obj) {
  1719. let tgt = obj[this.name], l = 0;
  1720. if (!tgt)
  1721. obj[this.name] = tgt = {};
  1722. while (l < this.leaves.length)
  1723. this.leaves[l++].func(buf, tgt);
  1724. }
  1725. };
  1726. }
  1727. }
  1728. if (!elem && !member) {
  1729. console.warn(`Not supported branch ${branch.fName} type ${branch._typename}`);
  1730. return null;
  1731. }
  1732. if (!member) {
  1733. member = createMemberStreamer(elem, handle.file);
  1734. if ((member.base !== undefined) && member.basename) {
  1735. // when element represent base class, we need handling which differ from normal IO
  1736. member.func = function(buf, obj) {
  1737. if (!obj[this.name])
  1738. obj[this.name] = { _typename: this.basename };
  1739. buf.classStreamer(obj[this.name], this.basename);
  1740. };
  1741. }
  1742. }
  1743. if (item_cnt && isStr(read_mode)) {
  1744. member.name0 = item_cnt.name;
  1745. const snames = target_name.split('.');
  1746. if (snames.length === 1) {
  1747. // no point in the name - just plain array of objects
  1748. member.get = (arr, n) => arr[n];
  1749. } else if (read_mode === '$child$') {
  1750. console.error(`target name ${target_name} contains point, but suppose to be direct child`);
  1751. return null;
  1752. } else if (snames.length === 2) {
  1753. target_name = member.name = snames[1];
  1754. member.name1 = snames[0];
  1755. member.subtype1 = read_mode;
  1756. member.methods1 = makeMethodsList(member.subtype1);
  1757. member.get = function(arr, n) {
  1758. let obj1 = arr[n][this.name1];
  1759. if (!obj1) obj1 = arr[n][this.name1] = this.methods1.Create();
  1760. return obj1;
  1761. };
  1762. } else {
  1763. // very complex task - we need to reconstruct several embedded members with their types
  1764. // try our best - but not all data types can be reconstructed correctly
  1765. // while classname is not enough - there can be different versions
  1766. if (!branch.fParentName) {
  1767. console.error(`Not possible to provide more than 2 parts in the target name ${target_name}`);
  1768. return null;
  1769. }
  1770. target_name = member.name = snames.pop(); // use last element
  1771. member.snames = snames; // remember all sub-names
  1772. member.smethods = []; // and special handles to create missing objects
  1773. let parent_class = branch.fParentName; // unfortunately, without version
  1774. for (let k = 0; k < snames.length; ++k) {
  1775. const chld_class = defineMemberTypeName(handle.file, parent_class, snames[k]);
  1776. member.smethods[k] = makeMethodsList(chld_class || 'AbstractClass');
  1777. parent_class = chld_class;
  1778. }
  1779. member.get = function(arr, n) {
  1780. let obj1 = arr[n][this.snames[0]];
  1781. if (!obj1) obj1 = arr[n][this.snames[0]] = this.smethods[0].Create();
  1782. for (let k = 1; k < this.snames.length; ++k) {
  1783. let obj2 = obj1[this.snames[k]];
  1784. if (!obj2) obj2 = obj1[this.snames[k]] = this.smethods[k].Create();
  1785. obj1 = obj2;
  1786. }
  1787. return obj1;
  1788. };
  1789. }
  1790. // case when target is sub-object and need to be created before
  1791. if (member.objs_branch_func) {
  1792. // STL branch provides special function for the reading
  1793. member.func = member.objs_branch_func;
  1794. } else {
  1795. member.func0 = member.func;
  1796. member.func = function(buf, obj) {
  1797. const arr = obj[this.name0]; // objects array where reading is done
  1798. let n = 0;
  1799. while (n < arr.length)
  1800. this.func0(buf, this.get(arr, n++)); // read all individual object with standard functions
  1801. };
  1802. }
  1803. } else if (item_cnt) {
  1804. handle.process_arrays = false;
  1805. if ((elem.fType === kDouble32) || (elem.fType === kFloat16)) {
  1806. // special handling for compressed floats
  1807. member.stl_size = item_cnt.name;
  1808. member.func = function(buf, obj) {
  1809. obj[this.name] = this.readarr(buf, obj[this.stl_size]);
  1810. };
  1811. } else if (((elem.fType === kOffsetP + kDouble32) || (elem.fType === kOffsetP + kFloat16)) && branch.fBranchCount2) {
  1812. // special handling for variable arrays of compressed floats in branch - not tested
  1813. member.stl_size = item_cnt.name;
  1814. member.arr_size = item_cnt2.name;
  1815. member.func = function(buf, obj) {
  1816. const sz0 = obj[this.stl_size], sz1 = obj[this.arr_size], arr = new Array(sz0);
  1817. for (let n = 0; n < sz0; ++n)
  1818. arr[n] = (buf.ntou1() === 1) ? this.readarr(buf, sz1[n]) : [];
  1819. obj[this.name] = arr;
  1820. };
  1821. } else if (((elem.fType > 0) && (elem.fType < kOffsetL)) || (elem.fType === kTString) ||
  1822. (((elem.fType > kOffsetP) && (elem.fType < kOffsetP + kOffsetL)) && branch.fBranchCount2)) {
  1823. // special handling of simple arrays
  1824. member = {
  1825. name: target_name,
  1826. stl_size: item_cnt.name,
  1827. type: elem.fType,
  1828. func(buf, obj) {
  1829. obj[this.name] = buf.readFastArray(obj[this.stl_size], this.type);
  1830. }
  1831. };
  1832. if (branch.fBranchCount2) {
  1833. member.type -= kOffsetP;
  1834. member.arr_size = item_cnt2.name;
  1835. member.func = function(buf, obj) {
  1836. const sz0 = obj[this.stl_size], sz1 = obj[this.arr_size], arr = new Array(sz0);
  1837. for (let n = 0; n < sz0; ++n)
  1838. arr[n] = (buf.ntou1() === 1) ? buf.readFastArray(sz1[n], this.type) : [];
  1839. obj[this.name] = arr;
  1840. };
  1841. }
  1842. } else if ((elem.fType > kOffsetP) && (elem.fType < kOffsetP + kOffsetL) && member.cntname)
  1843. member.cntname = item_cnt.name;
  1844. else if (elem.fType === kStreamer) {
  1845. // with streamers one need to extend existing array
  1846. if (item_cnt2)
  1847. throw new Error('Second branch counter not supported yet with kStreamer');
  1848. // function provided by normal I/O
  1849. member.func = member.branch_func;
  1850. member.stl_size = item_cnt.name;
  1851. } else if ((elem.fType === kStreamLoop) || (elem.fType === kOffsetL + kStreamLoop)) {
  1852. if (item_cnt2) {
  1853. // special solution for kStreamLoop
  1854. member.stl_size = item_cnt.name;
  1855. member.cntname = item_cnt2.name;
  1856. member.func = member.branch_func; // this is special function, provided by base I/O
  1857. } else
  1858. member.cntname = item_cnt.name;
  1859. } else {
  1860. member.name = '$stl_member';
  1861. let loop_size_name;
  1862. if (item_cnt2) {
  1863. if (member.cntname) {
  1864. loop_size_name = item_cnt2.name;
  1865. member.cntname = '$loop_size';
  1866. } else
  1867. throw new Error('Second branch counter not used - very BAD');
  1868. }
  1869. const stlmember = {
  1870. name: target_name,
  1871. stl_size: item_cnt.name,
  1872. loop_size: loop_size_name,
  1873. member0: member,
  1874. func(buf, obj) {
  1875. const cnt = obj[this.stl_size], arr = new Array(cnt);
  1876. for (let n = 0; n < cnt; ++n) {
  1877. if (this.loop_size) obj.$loop_size = obj[this.loop_size][n];
  1878. this.member0.func(buf, obj);
  1879. arr[n] = obj.$stl_member;
  1880. }
  1881. delete obj.$stl_member;
  1882. delete obj.$loop_size;
  1883. obj[this.name] = arr;
  1884. }
  1885. };
  1886. member = stlmember;
  1887. }
  1888. } // if (item_cnt)
  1889. // set name used to store result
  1890. member.name = target_name;
  1891. item.member = member; // member for reading
  1892. if (elem) item.type = elem.fType;
  1893. item.index = handle.arr.length; // index in the global list of branches
  1894. if (item_cnt) {
  1895. item.counters = [item_cnt.index];
  1896. item_cnt.ascounter.push(item.index);
  1897. if (item_cnt2) {
  1898. item.counters.push(item_cnt2.index);
  1899. item_cnt2.ascounter.push(item.index);
  1900. }
  1901. }
  1902. handle.arr.push(item);
  1903. // now one should add all other child branches
  1904. if (child_scan)
  1905. if (!scanBranches(branch.fBranches, target_object, child_scan)) return null;
  1906. return item;
  1907. }
  1908. // main loop to add all branches from selector for reading
  1909. for (let nn = 0; nn < selector.numBranches(); ++nn) {
  1910. const item = addBranchForReading(selector.getBranch(nn), undefined, selector.nameOfBranch(nn), selector._directs[nn]);
  1911. if (!item) {
  1912. selector.Terminate(false);
  1913. return Promise.reject(Error(`Fail to add branch ${selector.nameOfBranch(nn)}`));
  1914. }
  1915. }
  1916. // check if simple reading can be performed and there are direct data in branch
  1917. for (let h = 1; (h < handle.arr.length) && handle.simple_read; ++h) {
  1918. const item = handle.arr[h], item0 = handle.arr[0];
  1919. if ((item.numentries !== item0.numentries) || (item.numbaskets !== item0.numbaskets))
  1920. handle.simple_read = false;
  1921. for (let n = 0; n < item.numbaskets; ++n) {
  1922. if (item.getBasketEntry(n) !== item0.getBasketEntry(n))
  1923. handle.simple_read = false;
  1924. }
  1925. }
  1926. // now calculate entries range
  1927. handle.firstentry = handle.lastentry = 0;
  1928. for (let nn = 0; nn < handle.arr.length; ++nn) {
  1929. const branch = handle.arr[nn].branch,
  1930. e1 = branch.fFirstEntry ?? (branch.fBasketBytes[0] ? branch.fBasketEntry[0] : 0);
  1931. handle.firstentry = Math.max(handle.firstentry, e1);
  1932. handle.lastentry = (nn === 0) ? (e1 + branch.fEntries) : Math.min(handle.lastentry, e1 + branch.fEntries);
  1933. }
  1934. if (handle.firstentry >= handle.lastentry) {
  1935. selector.Terminate(false);
  1936. return Promise.reject(Error('No any common events for selected branches'));
  1937. }
  1938. handle.process_min = handle.firstentry;
  1939. handle.process_max = handle.lastentry;
  1940. let resolveFunc, rejectFunc; // Promise methods
  1941. if (args.elist) {
  1942. args.firstentry = args.elist.at(0);
  1943. args.numentries = args.elist.at(-1) - args.elist.at(0) + 1;
  1944. handle.process_entries = args.elist;
  1945. handle.process_entries_indx = 0;
  1946. handle.process_arrays = false; // do not use arrays process for selected entries
  1947. }
  1948. if (Number.isInteger(args.firstentry) && (args.firstentry > handle.firstentry) && (args.firstentry < handle.lastentry))
  1949. handle.process_min = args.firstentry;
  1950. handle.current_entry = handle.staged_now = handle.process_min;
  1951. if (Number.isInteger(args.numentries) && (args.numentries > 0)) {
  1952. const max = handle.process_min + args.numentries;
  1953. if (max < handle.process_max) handle.process_max = max;
  1954. }
  1955. if (isFunc(selector.ProcessArrays) && handle.simple_read) {
  1956. // this is indication that selector can process arrays of values
  1957. // only strictly-matched tree structure can be used for that
  1958. for (let k = 0; k < handle.arr.length; ++k) {
  1959. const elem = handle.arr[k];
  1960. if ((elem.type <= 0) || (elem.type >= kOffsetL) || (elem.type === kCharStar))
  1961. handle.process_arrays = false;
  1962. }
  1963. if (handle.process_arrays) {
  1964. // create other members for fast processing
  1965. selector.tgtarr = {}; // object with arrays
  1966. for (let nn = 0; nn < handle.arr.length; ++nn) {
  1967. const item = handle.arr[nn],
  1968. elem = createStreamerElement(item.name, item.type);
  1969. elem.fType = item.type + kOffsetL;
  1970. elem.fArrayLength = 10;
  1971. elem.fArrayDim = 1;
  1972. elem.fMaxIndex[0] = 10; // 10 if artificial number, will be replaced during reading
  1973. item.arrmember = createMemberStreamer(elem, handle.file);
  1974. }
  1975. }
  1976. } else
  1977. handle.process_arrays = false;
  1978. /** read basket with tree data, selecting different files */
  1979. function readBaskets(bitems) {
  1980. function extractPlaces() {
  1981. // extract places to read and define file name
  1982. const places = [];
  1983. let filename = '';
  1984. for (let n = 0; n < bitems.length; ++n) {
  1985. if (bitems[n].done) continue;
  1986. const branch = bitems[n].branch;
  1987. if (!places.length)
  1988. filename = branch.fFileName;
  1989. else if (filename !== branch.fFileName)
  1990. continue;
  1991. bitems[n].selected = true; // mark which item was selected for reading
  1992. places.push(branch.fBasketSeek[bitems[n].basket], branch.fBasketBytes[bitems[n].basket]);
  1993. }
  1994. return places.length ? { places, filename } : null;
  1995. }
  1996. function readProgress(value) {
  1997. if ((handle.staged_prev === handle.staged_now) ||
  1998. (handle.process_max <= handle.process_min)) return;
  1999. const tm = new Date().getTime();
  2000. if (tm - handle.progress_showtm < 500) return; // no need to show very often
  2001. handle.progress_showtm = tm;
  2002. const portion = (handle.staged_prev + value * (handle.staged_now - handle.staged_prev)) /
  2003. (handle.process_max - handle.process_min);
  2004. return handle.selector.ShowProgress(portion);
  2005. }
  2006. function processBlobs(blobs, places) {
  2007. if (!blobs || ((places.length > 2) && (blobs.length * 2 !== places.length)))
  2008. return Promise.resolve(null);
  2009. if (places.length === 2)
  2010. blobs = [blobs];
  2011. function doProcessing(k) {
  2012. for (; k < bitems.length; ++k) {
  2013. if (!bitems[k].selected) continue;
  2014. bitems[k].selected = false;
  2015. bitems[k].done = true;
  2016. const blob = blobs.shift();
  2017. let buf = new TBuffer(blob, 0, handle.file);
  2018. const basket = buf.classStreamer({}, clTBasket);
  2019. if (basket.fNbytes !== bitems[k].branch.fBasketBytes[bitems[k].basket])
  2020. console.error(`mismatch in read basket sizes ${basket.fNbytes} != ${bitems[k].branch.fBasketBytes[bitems[k].basket]}`);
  2021. // items[k].obj = basket; // keep basket object itself if necessary
  2022. bitems[k].bskt_obj = basket; // only number of entries in the basket are relevant for the moment
  2023. if (basket.fKeylen + basket.fObjlen === basket.fNbytes) {
  2024. // use data from original blob
  2025. buf.raw_shift = 0;
  2026. bitems[k].raw = buf; // here already unpacked buffer
  2027. if (bitems[k].branch.fEntryOffsetLen > 0)
  2028. buf.readBasketEntryOffset(basket, buf.raw_shift);
  2029. continue;
  2030. }
  2031. // unpack data and create new blob
  2032. return R__unzip(blob, basket.fObjlen, false, buf.o).then(objblob => {
  2033. if (objblob) {
  2034. buf = new TBuffer(objblob, 0, handle.file);
  2035. buf.raw_shift = basket.fKeylen;
  2036. buf.fTagOffset = basket.fKeylen;
  2037. } else
  2038. throw new Error('FAIL TO UNPACK');
  2039. bitems[k].raw = buf; // here already unpacked buffer
  2040. if (bitems[k].branch.fEntryOffsetLen > 0)
  2041. buf.readBasketEntryOffset(basket, buf.raw_shift);
  2042. return doProcessing(k+1); // continue processing
  2043. });
  2044. }
  2045. const req = extractPlaces();
  2046. if (req)
  2047. return handle.file.readBuffer(req.places, req.filename, readProgress).then(blobs2 => processBlobs(blobs2)).catch(() => null);
  2048. return Promise.resolve(bitems);
  2049. }
  2050. return doProcessing(0);
  2051. }
  2052. const req = extractPlaces();
  2053. // extract places where to read
  2054. if (req)
  2055. return handle.file.readBuffer(req.places, req.filename, readProgress).then(blobs => processBlobs(blobs, req.places)).catch(() => { return null; });
  2056. return Promise.resolve(null);
  2057. }
  2058. let processBaskets = null;
  2059. function readNextBaskets() {
  2060. const bitems = [], max_ranges = tree.$file?.fMaxRanges || settings.MaxRanges,
  2061. select_entries = handle.process_entries !== undefined;
  2062. let total_size = 0, total_nsegm = 0, isany = true, is_direct = false, min_staged = handle.process_max;
  2063. while (isany && (total_size < settings.TreeReadBunchSize) && (!total_nsegm || (total_nsegm + handle.arr.length <= max_ranges))) {
  2064. isany = false;
  2065. // very important, loop over branches in reverse order
  2066. // let check counter branch after reading of normal branch is prepared
  2067. for (let n = handle.arr.length - 1; n >= 0; --n) {
  2068. const elem = handle.arr[n];
  2069. while (elem.staged_basket < elem.numbaskets) {
  2070. const k = elem.staged_basket++,
  2071. bskt_emin = elem.getBasketEntry(k),
  2072. bskt_emax = k < elem.numbaskets - 1 ? elem.getBasketEntry(k + 1) : bskt_emin + 1e6;
  2073. // first baskets can be ignored
  2074. if (bskt_emax <= handle.process_min)
  2075. continue;
  2076. // no need to read more baskets, process_max is not included
  2077. if (bskt_emin >= handle.process_max)
  2078. break;
  2079. if (elem.first_readentry < 0) {
  2080. // basket where reading will start
  2081. elem.curr_basket = k;
  2082. elem.first_readentry = elem.getBasketEntry(k); // remember which entry will be read first
  2083. } else if (select_entries) {
  2084. // all entries from process entries are analyzed
  2085. if (elem.eindx >= handle.process_entries.length)
  2086. break;
  2087. // check if this basket required
  2088. if ((handle.process_entries[elem.eindx] < bskt_emin) || (handle.process_entries[elem.eindx] >= bskt_emax))
  2089. continue;
  2090. // when all previous baskets were processed, continue with selected
  2091. if (elem.curr_basket < 0)
  2092. elem.curr_basket = k;
  2093. }
  2094. if (select_entries) {
  2095. // also check next entries which may belong to this basket
  2096. do
  2097. elem.eindx++;
  2098. while ((elem.eindx < handle.process_entries.length) && (handle.process_entries[elem.eindx] >= bskt_emin) && (handle.process_entries[elem.eindx] < bskt_emax));
  2099. // remember which baskets are required
  2100. elem.selected_baskets.push(k);
  2101. }
  2102. // check if basket already loaded in the branch
  2103. const bitem = {
  2104. id: n, // to find which element we are reading
  2105. branch: elem.branch,
  2106. basket: k,
  2107. raw: null // here should be result
  2108. }, bskt = elem.branch.fBaskets.arr[k];
  2109. if (bskt) {
  2110. bitem.raw = bskt.fBufferRef;
  2111. if (bitem.raw)
  2112. bitem.raw.locate(0); // reset pointer - same branch may be read several times
  2113. else
  2114. bitem.raw = new TBuffer(null, 0, handle.file); // create dummy buffer - basket has no data
  2115. bitem.raw.raw_shift = bskt.fKeylen;
  2116. if (bskt.fBufferRef && (elem.branch.fEntryOffsetLen > 0))
  2117. bitem.raw.readBasketEntryOffset(bskt, bitem.raw.raw_shift);
  2118. bitem.bskt_obj = bskt;
  2119. is_direct = true;
  2120. elem.baskets[k] = bitem;
  2121. } else {
  2122. bitems.push(bitem);
  2123. total_size += elem.branch.fBasketBytes[k];
  2124. total_nsegm++;
  2125. isany = true;
  2126. }
  2127. elem.staged_entry = elem.getBasketEntry(k + 1);
  2128. min_staged = Math.min(min_staged, elem.staged_entry);
  2129. break;
  2130. }
  2131. }
  2132. }
  2133. if ((total_size === 0) && !is_direct) {
  2134. handle.selector.Terminate(true);
  2135. return resolveFunc(handle.selector);
  2136. }
  2137. handle.staged_prev = handle.staged_now;
  2138. handle.staged_now = min_staged;
  2139. let portion = 0;
  2140. if (handle.process_max > handle.process_min)
  2141. portion = (handle.staged_prev - handle.process_min) / (handle.process_max - handle.process_min);
  2142. if (handle.selector.ShowProgress(portion) === 'break') {
  2143. handle.selector.Terminate(true);
  2144. return resolveFunc(handle.selector);
  2145. }
  2146. handle.progress_showtm = new Date().getTime();
  2147. if (total_size > 0)
  2148. return readBaskets(bitems).then(processBaskets);
  2149. if (is_direct)
  2150. return processBaskets([]); // directly process baskets
  2151. throw new Error('No any data is requested - never come here');
  2152. }
  2153. processBaskets = function(bitems) {
  2154. // this is call-back when next baskets are read
  2155. if (handle.selector._break || (bitems === null)) {
  2156. handle.selector.Terminate(false);
  2157. return resolveFunc(handle.selector);
  2158. }
  2159. // redistribute read baskets over branches
  2160. for (let n = 0; n < bitems.length; ++n)
  2161. handle.arr[bitems[n].id].baskets[bitems[n].basket] = bitems[n];
  2162. // now process baskets
  2163. let isanyprocessed = false;
  2164. while (true) {
  2165. let loopentries = 100000000, n, elem;
  2166. // first loop used to check if all required data exists
  2167. for (n = 0; n < handle.arr.length; ++n) {
  2168. elem = handle.arr[n];
  2169. if (!elem.raw || !elem.basket || (elem.first_entry + elem.basket.fNevBuf <= handle.current_entry)) {
  2170. delete elem.raw;
  2171. delete elem.basket;
  2172. if ((elem.curr_basket >= elem.numbaskets)) {
  2173. if (n === 0) {
  2174. handle.selector.Terminate(true);
  2175. return resolveFunc(handle.selector);
  2176. }
  2177. continue; // ignore non-master branch
  2178. }
  2179. // this is single response from the tree, includes branch, basket number, raw data
  2180. const bitem = elem.baskets[elem.curr_basket];
  2181. // basket not read
  2182. if (!bitem) {
  2183. // no data, but no any event processed - problem
  2184. if (!isanyprocessed) {
  2185. handle.selector.Terminate(false);
  2186. return rejectFunc(Error(`no data for ${elem.branch.fName} basket ${elem.curr_basket}`));
  2187. }
  2188. // try to read next portion of tree data
  2189. return readNextBaskets();
  2190. }
  2191. elem.raw = bitem.raw;
  2192. elem.basket = bitem.bskt_obj;
  2193. // elem.nev = bitem.fNevBuf; // number of entries in raw buffer
  2194. elem.first_entry = elem.getBasketEntry(bitem.basket);
  2195. bitem.raw = null; // remove reference on raw buffer
  2196. bitem.branch = null; // remove reference on the branch
  2197. bitem.bskt_obj = null; // remove reference on the branch
  2198. elem.baskets[elem.curr_basket++] = undefined; // remove from array
  2199. if (handle.process_entries !== undefined) {
  2200. elem.selected_baskets.shift();
  2201. // -1 means that basket is not yet found for following entries
  2202. elem.curr_basket = elem.selected_baskets.length ? elem.selected_baskets[0] : -1;
  2203. }
  2204. }
  2205. // define how much entries can be processed before next raw buffer will be finished
  2206. loopentries = Math.min(loopentries, elem.first_entry + elem.basket.fNevBuf - handle.current_entry);
  2207. }
  2208. // second loop extracts all required data
  2209. // do not read too much
  2210. if (handle.current_entry + loopentries > handle.process_max)
  2211. loopentries = handle.process_max - handle.current_entry;
  2212. if (handle.process_arrays && (loopentries > 1)) {
  2213. // special case - read all data from baskets as arrays
  2214. for (n = 0; n < handle.arr.length; ++n) {
  2215. elem = handle.arr[n];
  2216. elem.getEntry(handle.current_entry);
  2217. elem.arrmember.arrlength = loopentries;
  2218. elem.arrmember.func(elem.raw, handle.selector.tgtarr);
  2219. elem.raw = null;
  2220. }
  2221. handle.selector.ProcessArrays(handle.current_entry);
  2222. handle.current_entry += loopentries;
  2223. isanyprocessed = true;
  2224. } else {
  2225. // main processing loop
  2226. while (loopentries > 0) {
  2227. for (n = 0; n < handle.arr.length; ++n) {
  2228. elem = handle.arr[n];
  2229. // locate buffer offset at proper place
  2230. elem.getEntry(handle.current_entry);
  2231. elem.member.func(elem.raw, elem.getTarget(handle.selector.tgtobj));
  2232. }
  2233. handle.selector.Process(handle.current_entry);
  2234. isanyprocessed = true;
  2235. if (handle.process_entries) {
  2236. handle.process_entries_indx++;
  2237. if (handle.process_entries_indx >= handle.process_entries.length) {
  2238. handle.current_entry++;
  2239. loopentries = 0;
  2240. } else {
  2241. const next_entry = handle.process_entries[handle.process_entries_indx],
  2242. diff = next_entry - handle.current_entry;
  2243. handle.current_entry = next_entry;
  2244. loopentries -= diff;
  2245. }
  2246. } else {
  2247. handle.current_entry++;
  2248. loopentries--;
  2249. }
  2250. }
  2251. }
  2252. if (handle.current_entry >= handle.process_max) {
  2253. handle.selector.Terminate(true);
  2254. return resolveFunc(handle.selector);
  2255. }
  2256. }
  2257. };
  2258. return new Promise((resolve, reject) => {
  2259. resolveFunc = resolve;
  2260. rejectFunc = reject;
  2261. // call begin before first entry is read
  2262. handle.selector.Begin(tree);
  2263. readNextBaskets();
  2264. });
  2265. }
  2266. /** @summary implementation of TTree::Draw
  2267. * @param {object|string} args - different setting or simply draw expression
  2268. * @param {string} args.expr - draw expression
  2269. * @param {string} [args.cut=undefined] - cut expression (also can be part of 'expr' after '::')
  2270. * @param {string} [args.drawopt=undefined] - draw options for result histogram
  2271. * @param {number} [args.firstentry=0] - first entry to process
  2272. * @param {number} [args.numentries=undefined] - number of entries to process, all by default
  2273. * @param {Array} [args.elist=undefined] - array of entries id to process, all by default
  2274. * @param {boolean} [args.staged] - staged processing, first apply cut to select entries and then perform drawing for selected entries
  2275. * @param {object} [args.branch=undefined] - TBranch object from TTree itself for the direct drawing
  2276. * @param {function} [args.progress=undefined] - function called during histogram accumulation with obj argument
  2277. * @return {Promise} with produced object */
  2278. async function treeDraw(tree, args) {
  2279. if (isStr(args))
  2280. args = { expr: args };
  2281. if (!isStr(args.expr))
  2282. args.expr = '';
  2283. const selector = new TDrawSelector();
  2284. if (args.branch) {
  2285. if (!selector.drawOnlyBranch(tree, args.branch, args.expr, args))
  2286. return Promise.reject(Error(`Fail to create draw expression ${args.expr} for branch ${args.branch.fName}`));
  2287. } else if (!selector.parseDrawExpression(tree, args))
  2288. return Promise.reject(Error(`Fail to create draw expression ${args.expr}`));
  2289. selector.setCallback(null, args.progress);
  2290. return treeProcess(tree, selector, args).then(sel => {
  2291. if (!args.staged)
  2292. return sel;
  2293. delete args.dump_entries;
  2294. const selector2 = new TDrawSelector(),
  2295. args2 = Object.assign({}, args);
  2296. args2.staged = false;
  2297. args2.elist = sel.hist; // assign entries found in first selection
  2298. if (!selector2.createDrawExpression(tree, args.staged_names, '', args2))
  2299. return Promise.reject(Error(`Fail to create final draw expression ${args.expr}`));
  2300. ['arr_limit', 'htype', 'nmatch', 'want_hist', 'hist_nbins', 'hist_name', 'hist_args', 'draw_title']
  2301. .forEach(name => { selector2[name] = selector[name]; });
  2302. return treeProcess(tree, selector2, args2);
  2303. }).then(sel => sel.hist);
  2304. }
  2305. /** @summary Performs generic I/O test for all branches in the TTree
  2306. * @desc Used when 'testio' draw option for TTree is specified
  2307. * @private */
  2308. function treeIOTest(tree, args) {
  2309. const branches = [], names = [], nchilds = [];
  2310. function collectBranches(obj, prntname = '') {
  2311. if (!obj?.fBranches)
  2312. return 0;
  2313. let cnt = 0;
  2314. for (let n = 0; n < obj.fBranches.arr.length; ++n) {
  2315. const br = obj.fBranches.arr[n],
  2316. name = (prntname ? prntname + '/' : '') + br.fName;
  2317. branches.push(br);
  2318. names.push(name);
  2319. nchilds.push(0);
  2320. const pos = nchilds.length - 1;
  2321. cnt += (br.fLeaves?.arr?.length ?? 0);
  2322. const nchld = collectBranches(br, name);
  2323. cnt += nchld;
  2324. nchilds[pos] = nchld;
  2325. }
  2326. return cnt;
  2327. }
  2328. const numleaves = collectBranches(tree);
  2329. let selector;
  2330. names.push(`Total are ${branches.length} branches with ${numleaves} leaves`);
  2331. function testBranch(nbr) {
  2332. if (nbr >= branches.length)
  2333. return Promise.resolve(true);
  2334. if (selector?._break || args._break)
  2335. return Promise.resolve(true);
  2336. selector = new TSelector();
  2337. selector.addBranch(branches[nbr], 'br0');
  2338. selector.Process = function() {
  2339. if (this.tgtobj.br0 === undefined)
  2340. this.fail = true;
  2341. };
  2342. selector.Terminate = function(res) {
  2343. if (!isStr(res))
  2344. res = (!res || this.fails) ? 'FAIL' : 'ok';
  2345. names[nbr] = res + ' ' + names[nbr];
  2346. };
  2347. const br = branches[nbr],
  2348. object_class = getBranchObjectClass(br, tree),
  2349. num = br.fEntries,
  2350. skip_branch = object_class ? (nchilds[nbr] > 100) : !br.fLeaves?.arr?.length;
  2351. if (skip_branch || (num <= 0))
  2352. return testBranch(nbr+1);
  2353. const drawargs = { numentries: 10 },
  2354. first = br.fFirstEntry || 0,
  2355. last = br.fEntryNumber || (first + num);
  2356. if (num < drawargs.numentries)
  2357. drawargs.numentries = num;
  2358. else
  2359. drawargs.firstentry = first + Math.round((last - first - drawargs.numentries) * Math.random()); // select randomly
  2360. // keep console output for debug purposes
  2361. console.log(`test branch ${br.fName} first ${drawargs.firstentry || 0} num ${drawargs.numentries}`);
  2362. if (isFunc(args.showProgress))
  2363. args.showProgress(`br ${nbr}/${branches.length} ${br.fName}`);
  2364. return treeProcess(tree, selector, drawargs).then(() => testBranch(nbr+1));
  2365. }
  2366. return testBranch(0).then(() => {
  2367. if (isFunc(args.showProgress))
  2368. args.showProgress();
  2369. return names;
  2370. });
  2371. }
  2372. /** @summary Create hierarchy of TTree object
  2373. * @private */
  2374. function treeHierarchy(tree_node, obj) {
  2375. function createBranchItem(node, branch, tree, parent_branch) {
  2376. if (!node || !branch)
  2377. return false;
  2378. const nb_branches = branch.fBranches?.arr?.length ?? 0,
  2379. nb_leaves = branch.fLeaves?.arr?.length ?? 0;
  2380. function ClearName(arg) {
  2381. const pos = arg.indexOf('[');
  2382. if (pos > 0) arg = arg.slice(0, pos);
  2383. if (parent_branch && arg.indexOf(parent_branch.fName) === 0) {
  2384. arg = arg.slice(parent_branch.fName.length);
  2385. if (arg[0] === '.') arg = arg.slice(1);
  2386. }
  2387. return arg;
  2388. }
  2389. branch.$tree = tree; // keep tree pointer, later do it more smart
  2390. const subitem = {
  2391. _name: ClearName(branch.fName),
  2392. _kind: prROOT + branch._typename,
  2393. _title: branch.fTitle,
  2394. _obj: branch
  2395. };
  2396. if (!node._childs) node._childs = [];
  2397. node._childs.push(subitem);
  2398. if (branch._typename === clTBranchElement)
  2399. subitem._title += ` from ${branch.fClassName};${branch.fClassVersion}`;
  2400. if (nb_branches > 0) {
  2401. subitem._more = true;
  2402. subitem._expand = function(bnode, bobj) {
  2403. // really create all sub-branch items
  2404. if (!bobj) return false;
  2405. if (!bnode._childs) bnode._childs = [];
  2406. if ((bobj.fLeaves?.arr?.length === 1) &&
  2407. ((bobj.fType === kClonesNode) || (bobj.fType === kSTLNode))) {
  2408. bobj.fLeaves.arr[0].$branch = bobj;
  2409. bnode._childs.push({
  2410. _name: '@size',
  2411. _title: 'container size',
  2412. _kind: prROOT + 'TLeafElement',
  2413. _icon: 'img_leaf',
  2414. _obj: bobj.fLeaves.arr[0],
  2415. _more: false
  2416. });
  2417. }
  2418. for (let i = 0; i < bobj.fBranches.arr.length; ++i)
  2419. createBranchItem(bnode, bobj.fBranches.arr[i], bobj.$tree, bobj);
  2420. const object_class = getBranchObjectClass(bobj, bobj.$tree, true),
  2421. methods = object_class ? getMethods(object_class) : null;
  2422. if (methods && bobj.fBranches.arr.length) {
  2423. for (const key in methods) {
  2424. if (!isFunc(methods[key])) continue;
  2425. const s = methods[key].toString();
  2426. if ((s.indexOf('return') > 0) && (s.indexOf('function ()') === 0)) {
  2427. bnode._childs.push({
  2428. _name: key+'()',
  2429. _title: `function ${key} of class ${object_class}`,
  2430. _kind: prROOT + clTBranchFunc, // fictional class, only for drawing
  2431. _obj: { _typename: clTBranchFunc, branch: bobj, func: key },
  2432. _more: false
  2433. });
  2434. }
  2435. }
  2436. }
  2437. return true;
  2438. };
  2439. return true;
  2440. } else if (nb_leaves === 1) {
  2441. subitem._icon = 'img_leaf';
  2442. subitem._more = false;
  2443. } else if (nb_leaves > 1) {
  2444. subitem._childs = [];
  2445. for (let j = 0; j < nb_leaves; ++j) {
  2446. branch.fLeaves.arr[j].$branch = branch; // keep branch pointer for drawing
  2447. const leafitem = {
  2448. _name: ClearName(branch.fLeaves.arr[j].fName),
  2449. _kind: prROOT + branch.fLeaves.arr[j]._typename,
  2450. _obj: branch.fLeaves.arr[j]
  2451. };
  2452. subitem._childs.push(leafitem);
  2453. }
  2454. }
  2455. return true;
  2456. }
  2457. // protect against corrupted TTree objects
  2458. if (obj.fBranches === undefined)
  2459. return false;
  2460. tree_node._childs = [];
  2461. tree_node._tree = obj; // set reference, will be used later by TTree::Draw
  2462. obj.fBranches?.arr.forEach(branch => createBranchItem(tree_node, branch, obj));
  2463. return true;
  2464. }
  2465. export { kClonesNode, kSTLNode, clTBranchFunc,
  2466. TSelector, TDrawVariable, TDrawSelector, treeHierarchy, treeProcess, treeDraw, treeIOTest };