UNPKG

jsroot

Version:
1,462 lines (1,230 loc) 108 kB
import { BIT, settings, isArrayProto, isRootCollection, isObject, isFunc, isStr, getMethods, create, createHistogram, createTGraph, prROOT, clTObject, clTObjString, clTHashList, clTPolyMarker3D, clTH1, clTH2, clTH3, kNoStats } from './core.mjs'; import { kChar, kShort, kInt, kFloat, kCharStar, kDouble, kDouble32, kUChar, kUShort, kUInt, kLong64, kULong64, kBool, kFloat16, kOffsetL, kOffsetP, kObject, kAny, kObjectp, kTString, kStreamer, kStreamLoop, kSTLp, kSTL, kBaseClass, clTBasket, R__unzip, TBuffer, createStreamerElement, createMemberStreamer } from './io.mjs'; import * as jsroot_math from './base/math.mjs'; // branch types const kLeafNode = 0, kBaseClassNode = 1, kObjectNode = 2, kClonesNode = 3, kSTLNode = 4, kClonesMemberNode = 31, kSTLMemberNode = 41, // branch bits // kDoNotProcess = BIT(10), // Active bit for branches // kIsClone = BIT(11), // to indicate a TBranchClones // kBranchObject = BIT(12), // branch is a TObject* // kBranchAny = BIT(17), // branch is an object* // kAutoDelete = BIT(15), kDoNotUseBufferMap = BIT(22), // If set, at least one of the entry in the branch will use the buffer's map of classname and objects. clTBranchElement = 'TBranchElement', clTBranchFunc = 'TBranchFunc'; /** * @summary Class to read data from TTree * * @desc Instance of TSelector can be used to access TTree data */ class TSelector { /** @summary constructor */ constructor() { this._branches = []; // list of branches to read this._names = []; // list of member names for each branch in tgtobj this._directs = []; // indication if only branch without any children should be read this._break = 0; this.tgtobj = {}; } /** @summary Add branch to the selector * @desc Either branch name or branch itself should be specified * Second parameter defines member name in the tgtobj * If selector.addBranch('px', 'read_px') is called, * branch will be read into selector.tgtobj.read_px member * If second parameter not specified, branch name (here 'px') will be used * If branch object specified as first parameter and second parameter missing, * then member like 'br0', 'br1' and so on will be assigned * @param {string|Object} branch - name of branch (or branch object itself} * @param {string} [name] - member name in tgtobj where data will be read * @param {boolean} [direct] - if only branch without any children should be read */ addBranch(branch, name, direct) { if (!name) name = isStr(branch) ? branch : `br${this._branches.length}`; this._branches.push(branch); this._names.push(name); this._directs.push(direct); return this._branches.length - 1; } /** @summary returns number of branches used in selector */ numBranches() { return this._branches.length; } /** @summary returns branch by index used in selector */ getBranch(indx) { return this._branches[indx]; } /** @summary returns index of branch * @private */ indexOfBranch(branch) { return this._branches.indexOf(branch); } /** @summary returns name of branch * @private */ nameOfBranch(indx) { return this._names[indx]; } /** @summary function called during TTree processing * @abstract * @param {number} progress - current value between 0 and 1 */ ShowProgress(/* progress */) {} /** @summary call this function to abort processing */ Abort() { this._break = -1111; } /** @summary function called before start processing * @abstract * @param {object} tree - tree object */ Begin(/* tree */) {} /** @summary function called when next entry extracted from the tree * @abstract * @param {number} entry - read entry number */ Process(/* entry */) {} /** @summary function called at the very end of processing * @abstract * @param {boolean} res - true if all data were correctly processed */ Terminate(/* res */) {} } // class TSelector // ================================================================= /** @summary Checks array kind * @desc return 0 when not array * 1 - when arbitrary array * 2 - when plain (1-dim) array with same-type content * @private */ function checkArrayPrototype(arr, check_content) { if (!isObject(arr)) return 0; const arr_kind = isArrayProto(Object.prototype.toString.apply(arr)); if (!check_content || (arr_kind !== 1)) return arr_kind; let typ, plain = true; for (let k = 0; k < arr.length; ++k) { const sub = typeof arr[k]; if (!typ) typ = sub; if ((sub !== typ) || (isObject(sub) && checkArrayPrototype(arr[k]))) { plain = false; break; } } return plain ? 2 : 1; } /** * @summary Class to iterate over array elements * * @private */ class ArrayIterator { /** @summary constructor */ constructor(arr, select, tgtobj) { this.object = arr; this.value = 0; // value always used in iterator this.arr = []; // all arrays this.indx = []; // all indexes this.cnt = -1; // current index counter this.tgtobj = tgtobj; if (isObject(select)) this.select = select; // remember indexes for selection else this.select = []; // empty array, undefined for each dimension means iterate over all indexes } /** @summary next element */ next() { let obj, typ, cnt = this.cnt; if (cnt >= 0) { if (++this.fastindx < this.fastlimit) { this.value = this.fastarr[this.fastindx]; return true; } while (--cnt >= 0) { if ((this.select[cnt] === undefined) && (++this.indx[cnt] < this.arr[cnt].length)) break; } if (cnt < 0) return false; } while (true) { obj = (cnt < 0) ? this.object : (this.arr[cnt])[this.indx[cnt]]; typ = obj ? typeof obj : 'any'; if (typ === 'object') { if (obj._typename !== undefined) { if (isRootCollection(obj)) { obj = obj.arr; typ = 'array'; } else typ = 'any'; } else if (Number.isInteger(obj.length) && (checkArrayPrototype(obj) > 0)) typ = 'array'; else typ = 'any'; } if (this.select[cnt + 1] === '$self$') { this.value = obj; this.fastindx = this.fastlimit = 0; this.cnt = cnt + 1; return true; } if ((typ === 'any') && isStr(this.select[cnt + 1])) { // this is extraction of the member from arbitrary class this.arr[++cnt] = obj; this.indx[cnt] = this.select[cnt]; // use member name as index continue; } if ((typ === 'array') && ((obj.length > 0) || (this.select[cnt + 1] === '$size$'))) { this.arr[++cnt] = obj; switch (this.select[cnt]) { case undefined: this.indx[cnt] = 0; break; case '$last$': this.indx[cnt] = obj.length - 1; break; case '$size$': this.value = obj.length; this.fastindx = this.fastlimit = 0; this.cnt = cnt; return true; default: if (Number.isInteger(this.select[cnt])) { this.indx[cnt] = this.select[cnt]; if (this.indx[cnt] < 0) this.indx[cnt] = obj.length - 1; } else { // this is compile variable as array index - can be any expression this.select[cnt].produce(this.tgtobj); this.indx[cnt] = Math.round(this.select[cnt].get(0)); } } } else { if (cnt < 0) return false; this.value = obj; if (this.select[cnt] === undefined) { this.fastarr = this.arr[cnt]; this.fastindx = this.indx[cnt]; this.fastlimit = this.fastarr.length; } else this.fastindx = this.fastlimit = 0; // no any iteration on that level this.cnt = cnt; return true; } } // unreachable code // return false; } /** @summary reset iterator */ reset() { this.arr = []; this.indx = []; delete this.fastarr; this.cnt = -1; this.value = 0; } } // class ArrayIterator /** @summary return TStreamerElement associated with the branch - if any * @desc unfortunately, branch.fID is not number of element in streamer info * @private */ function findBrachStreamerElement(branch, file) { if (!branch || !file || (branch._typename !== clTBranchElement) || (branch.fID < 0) || (branch.fStreamerType < 0)) return null; const s_i = file.findStreamerInfo(branch.fClassName, branch.fClassVersion, branch.fCheckSum), arr = (s_i && s_i.fElements) ? s_i.fElements.arr : null; if (!arr) return null; let match_name = branch.fName, pos = match_name.indexOf('['); if (pos > 0) match_name = match_name.slice(0, pos); pos = match_name.lastIndexOf('.'); if (pos > 0) match_name = match_name.slice(pos + 1); function match_elem(elem) { if (!elem) return false; if (elem.fName !== match_name) return false; if (elem.fType === branch.fStreamerType) return true; if ((elem.fType === kBool) && (branch.fStreamerType === kUChar)) return true; if (((branch.fStreamerType === kSTL) || (branch.fStreamerType === kSTL + kOffsetL) || (branch.fStreamerType === kSTLp) || (branch.fStreamerType === kSTLp + kOffsetL)) && (elem.fType === kStreamer)) return true; console.warn(`Should match element ${elem.fType} with branch ${branch.fStreamerType}`); return false; } // first check branch fID - in many cases gut guess if (match_elem(arr[branch.fID])) return arr[branch.fID]; for (let k = 0; k < arr.length; ++k) { if ((k !== branch.fID) && match_elem(arr[k])) return arr[k]; } console.error(`Did not found/match element for branch ${branch.fName} class ${branch.fClassName}`); return null; } /** @summary return class name of the object, stored in the branch * @private */ function getBranchObjectClass(branch, tree, with_clones = false, with_leafs = false) { if (!branch || (branch._typename !== clTBranchElement)) return ''; if ((branch.fType === kLeafNode) && (branch.fID === -2) && (branch.fStreamerType === -1)) { // object where all sub-branches will be collected return branch.fClassName; } if (with_clones && branch.fClonesName && ((branch.fType === kClonesNode) || (branch.fType === kSTLNode))) return branch.fClonesName; const s_elem = findBrachStreamerElement(branch, tree.$file); if ((branch.fType === kBaseClassNode) && s_elem && (s_elem.fTypeName === kBaseClass)) return s_elem.fName; if (branch.fType === kObjectNode) { if (s_elem && ((s_elem.fType === kObject) || (s_elem.fType === kAny))) return s_elem.fTypeName; return clTObject; } if ((branch.fType === kLeafNode) && s_elem && with_leafs) { if ((s_elem.fType === kObject) || (s_elem.fType === kAny)) return s_elem.fTypeName; if (s_elem.fType === kObjectp) return s_elem.fTypeName.slice(0, s_elem.fTypeName.length - 1); } return ''; } /** @summary Get branch with specified id * @desc All sub-branches checked as well * @return {Object} branch * @private */ function getTreeBranch(tree, id) { if (!Number.isInteger(id)) return; let res, seq = 0; function scan(obj) { obj?.fBranches?.arr.forEach(br => { if (seq++ === id) res = br; if (!res) scan(br); }); } scan(tree); return res; } /** @summary Special branch search * @desc Name can include extra part, which will be returned in the result * @param {string} name - name of the branch * @return {Object} with 'branch' and 'rest' members * @private */ function findBranchComplex(tree, name, lst = undefined, only_search = false) { let top_search = false, search = name, res = null; if (!lst) { top_search = true; lst = tree.fBranches; const pos = search.indexOf('['); if (pos > 0) search = search.slice(0, pos); } if (!lst || (lst.arr.length === 0)) return null; for (let n = 0; n < lst.arr.length; ++n) { let brname = lst.arr[n].fName; if (brname.at(-1) === ']') brname = brname.slice(0, brname.indexOf('[')); // special case when branch name includes STL map name if ((search.indexOf(brname) !== 0) && (brname.indexOf('<') > 0)) { const p1 = brname.indexOf('<'), p2 = brname.lastIndexOf('>'); brname = brname.slice(0, p1) + brname.slice(p2 + 1); } if (brname === search) { res = { branch: lst.arr[n], rest: '' }; break; } if (search.indexOf(brname) !== 0) continue; // this is a case when branch name is in the begin of the search string // check where point is let pnt = brname.length; if (brname[pnt - 1] === '.') pnt--; if (search[pnt] !== '.') continue; res = findBranchComplex(tree, search, lst.arr[n].fBranches); if (!res) res = findBranchComplex(tree, search.slice(pnt + 1), lst.arr[n].fBranches); if (!res) res = { branch: lst.arr[n], rest: search.slice(pnt) }; break; } if (top_search && !only_search && !res && (search.indexOf('br_') === 0)) { let p = 3; while ((p < search.length) && (search[p] >= '0') && (search[p] <= '9')) ++p; const br = (p > 3) ? getTreeBranch(tree, parseInt(search.slice(3, p))) : null; if (br) res = { branch: br, rest: search.slice(p) }; } if (!top_search || !res) return res; if (name.length > search.length) res.rest += name.slice(search.length); return res; } /** @summary Search branch with specified name * @param {string} name - name of the branch * @return {Object} found branch * @private */ function findBranch(tree, name) { const res = findBranchComplex(tree, name, tree.fBranches, true); return (!res || res.rest) ? null : res.branch; } /** summary Returns number of branches in the TTree * desc Checks also sub-branches in the branches * return {number} number of branches * private function getNumBranches(tree) { function count(obj) { if (!obj?.fBranches) return 0; let nchld = 0; obj.fBranches.arr.forEach(sub => { nchld += count(sub); }); return obj.fBranches.arr.length + nchld; } return count(tree); } */ /** * @summary object with single variable in TTree::Draw expression * * @private */ class TDrawVariable { /** @summary constructor */ constructor(globals) { this.globals = globals; this.code = ''; this.brindex = []; // index of used branches from selector this.branches = []; // names of branches in target object this.brarray = []; // array specifier for each branch this.func = null; // generic function for variable calculation this.kind = undefined; this.buf = []; // buffer accumulates temporary values } /** @summary Parse variable * @desc when only_branch specified, its placed in the front of the expression */ parse(tree, selector, code, only_branch, branch_mode) { const is_start_symbol = symb => { if ((symb >= 'A') && (symb <= 'Z')) return true; if ((symb >= 'a') && (symb <= 'z')) return true; return (symb === '_'); }, is_next_symbol = symb => { if (is_start_symbol(symb)) return true; if ((symb >= '0') && (symb <= '9')) return true; return false; }; if (!code) code = ''; // should be empty string at least this.code = (only_branch?.fName ?? '') + code; let pos = 0, pos2 = 0, br; while ((pos < code.length) || only_branch) { let arriter = []; if (only_branch) { br = only_branch; only_branch = undefined; } else { // first try to find branch pos2 = pos; while ((pos2 < code.length) && (is_next_symbol(code[pos2]) || code[pos2] === '.')) pos2++; if (code[pos2] === '$') { let repl = ''; switch (code.slice(pos, pos2)) { case 'LocalEntry': case 'Entry': repl = 'arg.$globals.entry'; break; case 'Entries': repl = 'arg.$globals.entries'; break; } if (repl) { code = code.slice(0, pos) + repl + code.slice(pos2 + 1); pos += repl.length; continue; } } br = findBranchComplex(tree, code.slice(pos, pos2)); if (!br) { pos = pos2 + 1; continue; } // when full id includes branch name, replace only part of extracted expression if (br.branch && (br.rest !== undefined)) { pos2 -= br.rest.length; branch_mode = undefined; // maybe selection of the sub-object done br = br.branch; } // when code ends with the point - means object itself will be accessed // sometime branch name itself ends with the point if ((pos2 >= code.length - 1) && (code.at(-1) === '.')) { arriter.push('$self$'); pos2 = code.length; } } // now extract all levels of iterators while (pos2 < code.length) { if ((code[pos2] === '@') && (code.slice(pos2, pos2 + 5) === '@size') && (arriter.length === 0)) { pos2 += 5; branch_mode = true; break; } if (code[pos2] === '.') { // this is object member const prev = ++pos2; if ((code[prev] === '@') && (code.slice(prev, prev + 5) === '@size')) { arriter.push('$size$'); pos2 += 5; break; } if (!is_start_symbol(code[prev])) { arriter.push('$self$'); // last point means extraction of object itself break; } while ((pos2 < code.length) && is_next_symbol(code[pos2])) pos2++; // this is looks like function call - do not need to extract member with if (code[pos2] === '(') { pos2 = prev - 1; break; } // this is selection of member, but probably we need to activate iterator for ROOT collection if (arriter.length === 0) { // TODO: if selected member is simple data type - no need to make other checks - just break here if ((br.fType === kClonesNode) || (br.fType === kSTLNode)) arriter.push(undefined); else { const objclass = getBranchObjectClass(br, tree, false, true); if (objclass && isRootCollection(null, objclass)) arriter.push(undefined); } } arriter.push(code.slice(prev, pos2)); continue; } if (code[pos2] !== '[') break; // simple [] if (code[pos2 + 1] === ']') { arriter.push(undefined); pos2 += 2; continue; } const prev = pos2++; let cnt = 0; while ((pos2 < code.length) && ((code[pos2] !== ']') || (cnt > 0))) { if (code[pos2] === '[') cnt++; else if (code[pos2] === ']') cnt--; pos2++; } const sub = code.slice(prev + 1, pos2); switch (sub) { case '': case '$all$': arriter.push(undefined); break; case '$last$': arriter.push('$last$'); break; case '$size$': arriter.push('$size$'); break; case '$first$': arriter.push(0); break; default: if (Number.isInteger(parseInt(sub))) arriter.push(parseInt(sub)); else { // try to compile code as draw variable const subvar = new TDrawVariable(this.globals); if (!subvar.parse(tree, selector, sub)) return false; arriter.push(subvar); } } pos2++; } if (arriter.length === 0) arriter = undefined; else if ((arriter.length === 1) && (arriter[0] === undefined)) arriter = true; let indx = selector.indexOfBranch(br); if (indx < 0) indx = selector.addBranch(br, undefined, branch_mode); branch_mode = undefined; this.brindex.push(indx); this.branches.push(selector.nameOfBranch(indx)); this.brarray.push(arriter); // this is simple case of direct usage of the branch if ((pos === 0) && (pos2 === code.length) && (this.branches.length === 1)) { this.direct_branch = true; // remember that branch read as is return true; } const replace = 'arg.var' + (this.branches.length - 1); code = code.slice(0, pos) + replace + code.slice(pos2); pos += replace.length; } // support usage of some standard TMath functions code = code.replace(/TMath::Exp\(/g, 'Math.exp(') .replace(/TMath::Abs\(/g, 'Math.abs(') .replace(/TMath::Prob\(/g, 'arg.$math.Prob(') .replace(/TMath::Gaus\(/g, 'arg.$math.Gaus('); this.func = new Function('arg', `return (${code})`); return true; } /** @summary Check if it is dummy variable */ is_dummy() { return (this.branches.length === 0) && !this.func; } /** @summary Produce variable * @desc after reading tree branches into the object, calculate variable value */ produce(obj) { this.length = 1; this.isarray = false; if (this.is_dummy()) { this.value = 1; // used as dummy weight variable this.kind = 'number'; return; } const arg = { $globals: this.globals, $math: jsroot_math }, arrs = []; let usearrlen = -1; for (let n = 0; n < this.branches.length; ++n) { const name = `var${n}`; arg[name] = obj[this.branches[n]]; // try to check if branch is array and need to be iterated if (this.brarray[n] === undefined) this.brarray[n] = (checkArrayPrototype(arg[name]) > 0) || isRootCollection(arg[name]); // no array - no pain if (this.brarray[n] === false) continue; // check if array can be used as is - one dimension and normal values if ((this.brarray[n] === true) && (checkArrayPrototype(arg[name], true) === 2)) { // plain array, can be used as is arrs[n] = arg[name]; } else { const iter = new ArrayIterator(arg[name], this.brarray[n], obj); arrs[n] = []; while (iter.next()) arrs[n].push(iter.value); } if ((usearrlen < 0) || (usearrlen < arrs[n].length)) usearrlen = arrs[n].length; } if (usearrlen < 0) { this.value = this.direct_branch ? arg.var0 : this.func(arg); if (!this.kind) this.kind = typeof this.value; return; } if (usearrlen === 0) { // empty array - no any histogram should be filled this.length = 0; this.value = 0; return; } this.length = usearrlen; this.isarray = true; if (this.direct_branch) this.value = arrs[0]; // just use array else { this.value = new Array(usearrlen); for (let k = 0; k < usearrlen; ++k) { for (let n = 0; n < this.branches.length; ++n) { if (arrs[n]) arg[`var${n}`] = arrs[n][k]; } this.value[k] = this.func(arg); } } if (!this.kind) this.kind = typeof this.value[0]; } /** @summary Get variable */ get(indx) { return this.isarray ? this.value[indx] : this.value; } /** @summary Append array to the buffer */ appendArray(tgtarr) { this.buf = this.buf.concat(tgtarr[this.branches[0]]); } } // class TDrawVariable /** * @summary Selector class for TTree::Draw function * * @private */ class TDrawSelector extends TSelector { /** @summary constructor */ constructor() { super(); this.ndim = 0; this.vars = []; // array of expression variables this.cut = null; // cut variable this.hist = null; this.drawopt = ''; this.hist_name = '$htemp'; this.draw_title = 'Result of TTree::Draw'; this.graph = false; this.hist_args = []; // arguments for histogram creation this.arr_limit = 1000; // number of accumulated items before create histogram this.htype = 'F'; this.monitoring = 0; this.globals = {}; // object with global parameters, which could be used in any draw expression this.last_progress = 0; this.aver_diff = 0; } /** @summary Set draw selector callbacks */ setCallback(result_callback, progress_callback) { this.result_callback = result_callback; this.progress_callback = progress_callback; } /** @summary Parse parameters */ parseParameters(tree, args, expr) { if (!expr || !isStr(expr)) return ''; // parse parameters which defined at the end as expression;par1name:par1value;par2name:par2value let pos = expr.lastIndexOf(';'); while (pos >= 0) { let parname = expr.slice(pos + 1), parvalue; expr = expr.slice(0, pos); pos = expr.lastIndexOf(';'); const separ = parname.indexOf(':'); if (separ > 0) { parvalue = parname.slice(separ + 1); parname = parname.slice(0, separ); } let intvalue = parseInt(parvalue); if (!parvalue || !Number.isInteger(intvalue)) intvalue = undefined; switch (parname) { case 'elist': if ((parvalue.at(0) === '[') && (parvalue.at(-1) === ']')) { parvalue = parvalue.slice(1, parvalue.length - 1).replaceAll(/\s/g, ''); args.elist = []; let p = 0, last_v = -1; const getInt = () => { const p0 = p; while ((p < parvalue.length) && (parvalue.charCodeAt(p) >= 48) && (parvalue.charCodeAt(p) < 58)) p++; return parseInt(parvalue.slice(p0, p)); }; while (p < parvalue.length) { const v1 = getInt(); if (v1 <= last_v) { console.log('position', p); throw Error(`Wrong entry id ${v1} in elist last ${last_v}`); } let v2 = v1; if (parvalue[p] === '.' && parvalue[p + 1] === '.') { p += 2; v2 = getInt(); if (v2 < v1) throw Error(`Wrong entry id ${v2} in range ${v1}`); } if (parvalue[p] === ',' || p === parvalue.length) { for (let v = v1; v <= v2; ++v) { args.elist.push(v); last_v = v; } p++; } else throw Error('Wrong syntax for elist'); } } break; case 'entries': case 'num': case 'numentries': if (parvalue === 'all') args.numentries = tree.fEntries; else if (parvalue === 'half') args.numentries = Math.round(tree.fEntries / 2); else if (intvalue !== undefined) args.numentries = intvalue; break; case 'first': if (intvalue !== undefined) args.firstentry = intvalue; break; case 'nmatch': if (intvalue !== undefined) this.nmatch = intvalue; break; case 'mon': case 'monitor': args.monitoring = (intvalue !== undefined) ? intvalue : 5000; break; case 'player': args.player = true; break; case 'dump': args.dump = true; break; case 'staged': args.staged = true; break; case 'maxseg': case 'maxrange': if (intvalue) tree.$file.fMaxRanges = intvalue; break; case 'accum': if (intvalue) this.arr_limit = intvalue; break; case 'htype': if (parvalue && (parvalue.length === 1)) { this.htype = parvalue.toUpperCase(); if (['C', 'S', 'I', 'F', 'L', 'D'].indexOf(this.htype) < 0) this.htype = 'F'; } break; case 'hbins': this.hist_nbins = parseInt(parvalue); if (!Number.isInteger(this.hist_nbins) || (this.hist_nbins <= 3)) delete this.hist_nbins; else this.want_hist = true; break; case 'drawopt': args.drawopt = parvalue; break; case 'graph': args.graph = intvalue || true; break; } } pos = expr.lastIndexOf('>>'); if (pos >= 0) { let harg = expr.slice(pos + 2).trim(); expr = expr.slice(0, pos).trim(); pos = harg.indexOf('('); if (pos > 0) { this.hist_name = harg.slice(0, pos); harg = harg.slice(pos); } if (harg === 'dump') args.dump = true; else if (harg === 'elist') args.dump_entries = true; else if (harg.indexOf('Graph') === 0) args.graph = true; else if (pos < 0) { this.want_hist = true; this.hist_name = harg; } else if ((harg[0] === '(') && (harg.at(-1) === ')')) { this.want_hist = true; harg = harg.slice(1, harg.length - 1).split(','); let isok = true; for (let n = 0; n < harg.length; ++n) { harg[n] = (n % 3 === 0) ? parseInt(harg[n]) : parseFloat(harg[n]); if (!Number.isFinite(harg[n])) isok = false; } if (isok) this.hist_args = harg; } } if (args.dump) { this.dump_values = true; args.reallocate_objects = true; if (args.numentries === undefined) { args.numentries = 10; args._dflt_entries = true; } } return expr; } /** @summary Create draw expression for N-dim with cut */ createDrawExpression(tree, names, cut, args) { if (args.dump && names.length === 1 && names[0] === 'Entry$') { args.dump_entries = true; args.dump = false; } if (args.dump_entries) { this.dump_entries = true; this.hist = []; if (args._dflt_entries) { delete args._dflt_entries; delete args.numentries; } } let is_direct = !cut && !this.dump_entries; this.ndim = names.length; for (let n = 0; n < this.ndim; ++n) { this.vars[n] = new TDrawVariable(this.globals); if (!this.vars[n].parse(tree, this, names[n])) return false; if (!this.vars[n].direct_branch) is_direct = false; } this.cut = new TDrawVariable(this.globals); if (cut && !this.cut.parse(tree, this, cut)) return false; if (!this.numBranches()) { console.warn('no any branch is selected'); return false; } if (is_direct) this.ProcessArrays = this.ProcessArraysFunc; this.monitoring = args.monitoring; // force TPolyMarker3D drawing for 3D case if ((this.ndim === 3) && !this.want_hist && !args.dump) args.graph = true; this.graph = args.graph; if (args.drawopt !== undefined) this.drawopt = args.drawopt; else args.drawopt = this.drawopt = this.graph ? 'P' : ''; return true; } /** @summary Parse draw expression */ parseDrawExpression(tree, args) { // parse complete expression let expr = this.parseParameters(tree, args, args.expr), cut = ''; // parse option for histogram creation this.draw_title = `drawing '${expr}' from ${tree.fName}`; let pos; if (args.cut) cut = args.cut; else { pos = expr.replace(/TMath::/g, 'TMath__').lastIndexOf('::'); // avoid confusion due-to :: in the namespace if (pos >= 0) { cut = expr.slice(pos + 2).trim(); expr = expr.slice(0, pos).trim(); } } args.parse_expr = expr; args.parse_cut = cut; // let names = expr.split(':'); // to allow usage of ? operator, we need to handle : as well let names = [], nbr1 = 0, nbr2 = 0, prev = 0; for (pos = 0; pos < expr.length; ++pos) { switch (expr[pos]) { case '(': nbr1++; break; case ')': nbr1--; break; case '[': nbr2++; break; case ']': nbr2--; break; case ':': if (expr[pos + 1] === ':') { pos++; continue; } if (!nbr1 && !nbr2 && (pos > prev)) names.push(expr.slice(prev, pos)); prev = pos + 1; break; } } if (!nbr1 && !nbr2 && (pos > prev)) names.push(expr.slice(prev, pos)); if (args.staged) { args.staged_names = names; names = ['Entry$']; args.dump_entries = true; } else if (cut && args.dump_entries) names = ['Entry$']; else if ((names.length < 1) || (names.length > 3)) return false; return this.createDrawExpression(tree, names, cut, args); } /** @summary Draw only specified branch */ drawOnlyBranch(tree, branch, expr, args) { this.ndim = 1; if (expr.indexOf('dump') === 0) expr = ';' + expr; expr = this.parseParameters(tree, args, expr); this.monitoring = args.monitoring; if (args.dump) { this.dump_values = true; args.reallocate_objects = true; } if (this.dump_values) { this.hist = []; // array of dump objects this.leaf = args.leaf; // branch object remains, therefore we need to copy fields to see them all this.copy_fields = ((args.branch.fLeaves && (args.branch.fLeaves.arr.length > 1)) || (args.branch.fBranches && (args.branch.fBranches.arr.length > 0))) && !args.leaf; this.addBranch(branch, 'br0', args.direct_branch); // add branch this.Process = this.ProcessDump; return true; } this.vars[0] = new TDrawVariable(this.globals); if (!this.vars[0].parse(tree, this, expr, branch, args.direct_branch)) return false; this.draw_title = `drawing branch ${branch.fName} ${expr?' expr:'+expr:''} from ${tree.fName}`; this.cut = new TDrawVariable(this.globals); if (this.vars[0].direct_branch) this.ProcessArrays = this.ProcessArraysFunc; return true; } /** @summary Begin processing */ Begin(tree) { this.globals.entries = tree.fEntries; if (this.monitoring) this.lasttm = new Date().getTime(); } /** @summary Show progress */ ShowProgress(/* value */) {} /** @summary Get bins for bits histogram */ getBitsBins(nbits, res) { res.nbins = res.max = nbits; res.fLabels = create(clTHashList); for (let k = 0; k < nbits; ++k) { const s = create(clTObjString); s.fString = k.toString(); s.fUniqueID = k + 1; res.fLabels.Add(s); } return res; } /** @summary Get min.max bins */ getMinMaxBins(axisid, nbins) { const res = { min: 0, max: 0, nbins, k: 1, fLabels: null, title: '' }; if (axisid >= this.ndim) return res; const arr = this.vars[axisid].buf; res.title = this.vars[axisid].code || ''; if (this.vars[axisid].kind === 'object') { // this is any object type let typename, similar = true, maxbits = 8; for (let k = 0; k < arr.length; ++k) { if (!arr[k]) continue; if (!typename) typename = arr[k]._typename; if (typename !== arr[k]._typename) similar = false; // check all object types if (arr[k].fNbits) maxbits = Math.max(maxbits, arr[k].fNbits + 1); } if (typename && similar) { if ((typename === 'TBits') && (axisid === 0)) { this.fill1DHistogram = this.fillTBitsHistogram; if (maxbits % 8) maxbits = (maxbits & 0xfff0) + 8; if ((this.hist_name === 'bits') && (this.hist_args.length === 1) && this.hist_args[0]) maxbits = this.hist_args[0]; return this.getBitsBins(maxbits, res); } } } if (this.vars[axisid].kind === 'string') { res.lbls = []; // all labels for (let k = 0; k < arr.length; ++k) { if (res.lbls.indexOf(arr[k]) < 0) res.lbls.push(arr[k]); } res.lbls.sort(); res.max = res.nbins = res.lbls.length; res.fLabels = create(clTHashList); for (let k = 0; k < res.lbls.length; ++k) { const s = create(clTObjString); s.fString = res.lbls[k]; s.fUniqueID = k + 1; if (s.fString === '') s.fString = '<empty>'; res.fLabels.Add(s); } } else if ((axisid === 0) && (this.hist_name === 'bits') && (this.hist_args.length <= 1)) { this.fill1DHistogram = this.fillBitsHistogram; return this.getBitsBins(this.hist_args[0] || 32, res); } else if (axisid * 3 + 2 < this.hist_args.length) { res.nbins = this.hist_args[axisid * 3]; res.min = this.hist_args[axisid * 3 + 1]; res.max = this.hist_args[axisid * 3 + 2]; } else { let is_any = false; for (let i = 1; i < arr.length; ++i) { const v = arr[i]; if (!Number.isFinite(v)) continue; if (is_any) { res.min = Math.min(res.min, v); res.max = Math.max(res.max, v); } else { res.min = res.max = v; is_any = true; } } if (!is_any) { res.min = 0; res.max = 1; } if (this.hist_nbins) nbins = res.nbins = this.hist_nbins; res.isinteger = (Math.round(res.min) === res.min) && (Math.round(res.max) === res.max); if (res.isinteger) { for (let k = 0; k < arr.length; ++k) if (arr[k] !== Math.round(arr[k])) { res.isinteger = false; break; } } if (res.isinteger) { res.min = Math.round(res.min); res.max = Math.round(res.max); if (res.max - res.min < nbins * 5) { res.min -= 1; res.max += 2; res.nbins = Math.round(res.max - res.min); } else { const range = (res.max - res.min + 2); let step = Math.floor(range / nbins); while (step * nbins < range) step++; res.max = res.min + nbins * step; } } else if (res.min >= res.max) { res.max = res.min; if (Math.abs(res.min) < 100) { res.min -= 1; res.max += 1; } else if (res.min > 0) { res.min *= 0.9; res.max *= 1.1; } else { res.min *= 1.1; res.max *= 0.9; } } else res.max += (res.max - res.min) / res.nbins; } res.k = res.nbins / (res.max - res.min); res.GetBin = function(value) { const bin = this.lbls?.indexOf(value) ?? Number.isFinite(value) ? Math.floor((value - this.min) * this.k) : this.nbins + 1; return bin < 0 ? 0 : ((bin > this.nbins) ? this.nbins + 1 : bin + 1); }; return res; } /** @summary Create histogram which matches value in dimensions */ createHistogram(nbins, set_hist = false) { if (!nbins) nbins = 20; const x = this.getMinMaxBins(0, nbins), y = this.getMinMaxBins(1, nbins), z = this.getMinMaxBins(2, nbins); let hist = null; switch (this.ndim) { case 1: hist = createHistogram(clTH1 + this.htype, x.nbins); break; case 2: hist = createHistogram(clTH2 + this.htype, x.nbins, y.nbins); break; case 3: hist = createHistogram(clTH3 + this.htype, x.nbins, y.nbins, z.nbins); break; } hist.fXaxis.fTitle = x.title; hist.fXaxis.fXmin = x.min; hist.fXaxis.fXmax = x.max; hist.fXaxis.fLabels = x.fLabels; if (this.ndim > 1) hist.fYaxis.fTitle = y.title; hist.fYaxis.fXmin = y.min; hist.fYaxis.fXmax = y.max; hist.fYaxis.fLabels = y.fLabels; if (this.ndim > 2) hist.fZaxis.fTitle = z.title; hist.fZaxis.fXmin = z.min; hist.fZaxis.fXmax = z.max; hist.fZaxis.fLabels = z.fLabels; hist.fName = this.hist_name; hist.fTitle = this.draw_title; hist.fOption = this.drawopt; hist.$custom_stat = (this.hist_name === '$htemp') ? 111110 : 111111; if (set_hist) { this.hist = hist; this.x = x; this.y = y; this.z = z; } else hist.fBits |= kNoStats; return hist; } /** @summary Create output object - histogram, graph, dump array */ createOutputObject() { if (this.hist || !this.vars[0].buf) return; if (this.dump_values) { // just create array where dumped values will be collected this.hist = []; // reassign fill method this.fill1DHistogram = this.fill2DHistogram = this.fill3DHistogram = this.dumpValues; } else if (this.graph) { const N = this.vars[0].buf.length; let res = null; if (this.ndim === 1) { // A 1-dimensional graph will just have the x axis as an index res = createTGraph(N, Array.from(Array(N).keys()), this.vars[0].buf); res.fName = 'Graph'; res.fTitle = this.draw_title; } else if (this.ndim === 2) { res = createTGraph(N, this.vars[0].buf, this.vars[1].buf); res.fName = 'Graph'; res.fTitle = this.draw_title; delete this.vars[1].buf; } else if (this.ndim === 3) { res = create(clTPolyMarker3D); res.fN = N; res.fLastPoint = N - 1; const arr = new Array(N*3); for (let k = 0; k< N; ++k) { arr[k*3] = this.vars[0].buf[k]; arr[k*3+1] = this.vars[1].buf[k]; arr[k*3+2] = this.vars[2].buf[k]; } res.fP = arr; res.$hist = this.createHistogram(10); delete this.vars[1].buf; delete this.vars[2].buf; res.fName = 'Points'; } this.hist = res; } else { const nbins = [200, 50, 20]; this.createHistogram(nbins[this.ndim], true); } const var0 = this.vars[0].buf, cut = this.cut.buf, len = var0.length; if (!this.graph) { switch (this.ndim) { case 1: { for (let n = 0; n < len; ++n) this.fill1DHistogram(var0[n], cut ? cut[n] : 1); break; } case 2: { const var1 = this.vars[1].buf; for (let n = 0; n < len; ++n) this.fill2DHistogram(var0[n], var1[n], cut ? cut[n] : 1); delete this.vars[1].buf; break; } case 3: { const var1 = this.vars[1].buf, var2 = this.vars[2].buf; for (let n = 0; n < len; ++n) this.fill3DHistogram(var0[n], var1[n], var2[n], cut ? cut[n] : 1); delete this.vars[1].buf; delete this.vars[2].buf; break; } } } delete this.vars[0].buf; delete this.cut.buf; } /** @summary Fill TBits histogram */ fillTBitsHistogram(xvalue, weight) { if (!weight || !xvalue || !xvalue.fNbits || !xvalue.fAllBits) return; const sz = Math.min(xvalue.fNbits + 1, xvalue.fNbytes * 8); for (let bit = 0, mask = 1, b = 0; bit < sz; ++bit) { if (xvalue.fAllBits[b] && mask) { if (bit <= this.x.nbins) this.hist.fArray[bit + 1] += weight; else this.hist.fArray[this.x.nbins + 1] += weight; } mask *= 2; if (mask >= 0x100) { mask = 1; ++b; } } } /** @summary Fill bits histogram */ fillBitsHistogram(xvalue, weight) { if (!weight) return; for (let bit = 0, mask = 1; bit < this.x.nbins; ++bit) { if (xvalue & mask) this.hist.fArray[bit + 1] += weight; mask *= 2; } } /** @summary Fill 1D histogram */ fill1DHistogram(xvalue, weight) { const bin = this.x.GetBin(xvalue); this.hist.fArray[bin] += weight; if (!this.x.lbls && Number.isFinite(xvalue)) { this.hist.fTsumw += weight; this.hist.fTsumwx += weight * xvalue; this.hist.fTsumwx2 += weight * xvalue * xvalue; } } /** @summary Fill 2D histogram */ fill2DHistogram(xvalue, yvalue, weight) { const xbin = this.x.GetBin(xvalue), ybin = this.y.GetBin(yvalue); this.hist.fArray[xbin + (this.x.nbins + 2) * ybin] += weight; if (!this.x.lbls && !this.y.lbls && Number.isFinite(xvalue) && Number.isFinite(yvalue)) { this.hist.fTsumw += weight; this.hist.fTsumwx += weight * xvalue; this.hist.fTsumwy += weight * yvalue; this.hist.fTsumwx2 += weight * xvalue * xvalue; this.hist.fTsumwxy += weight * xvalue * yvalue; this.hist.fTsumwy2 += weight * yvalue * yvalue; } } /** @summary Fill 3D histogram */ fill3DHistogram(xvalue, yvalue, zvalue, weight) { const xbin = this.x.GetBin(xvalue), ybin = this.y.GetBin(yvalue), zbin = this.z.GetBin(zvalue); this.hist.fArray[xbin + (this.x.nbins + 2) * (ybin + (this.y.nbins + 2) * zbin)] += weight; if (!this.x.lbls && !this.y.lbls && !this.z.lbls && Number.isFinite(xvalue) && Number.isFinite(yvalue) && Number.isFinite(zvalue)) { this.hist.fTsumw += weight; this.hist.fTsumwx += weight * xvalue; this.hist.fTsumwy += weight * yvalue; this.hist.fTsumwz += weight * zvalue; this.hist.fTsumwx2 += weight * xvalue * xvalue; this.hist.fTsumwy2 += weight * yvalue * yvalue; this.hist.fTsumwz2 += weight * zvalue * zvalue; this.hist.fTsumwxy += weight * xvalue * yvalue; this.hist.fTsumwxz += weight * xvalue * zvalue; this.hist.fTsumwyz += weight * yvalue * zvalue; } } /** @summary Dump values */ dumpValues(v1, v2, v3, v4) { let obj; switch (this.ndim) { case 1: obj = { x: v1, weight: v2 }; break; case 2: obj = { x: v1, y: v2, weight: v3 }; break; case 3: obj = { x: v1, y: v2, z: v3, weight: v4 }; break; } if (this.cut.is_dummy()) { if (this.ndim === 1) obj = v1; else delete obj.weight; } this.hist.push(obj); } /** @summary function used when all branches can be read as array * @desc most typical usage - histogram filling of single branch */ ProcessArraysFunc(/* entry */) { if (this.arr_limit || this.graph) { const var0 = this.vars[0], var1 = this.vars[1], var2 = this.vars[2], len = this.tgtarr.br0.length; if ((var0.buf.length === 0) && (len >= this.arr_limit) && !this.graph) { // special use case - first array large enough to create histogram directly base on it var0.buf = this.tgtarr.br0; if (var1) var1.buf = this.tgtarr.br1; if (var2) var2.buf = this.tgtarr.br2; } else { for (let k = 0; k < len; ++k) { var0.buf.push(this.tgtarr.br0[k]); if (var1) var1.buf.push(this.tgtarr.br1[k]); if (var2) var2.buf.push(this.tgtarr.br2[k]); } } var0.kind = 'number'; if (var1) var1.kind = 'number'; if (var2) var2.kind = 'number'; this.cut.buf = null; // do not create buffer for cuts if (!this.graph && (var0.buf.length >= this.arr_limit)) { this.createOutputObject(); this.arr_limit = 0; } } else { const br0 = this.tgtarr.br0, len = br0.length; switch (this.ndim) { case 1: { for (let k = 0; k < len; ++k) this.fill1DHistogram(br0[k], 1); break; } case 2: { c