<commit_before>/// @file JSRootTree.js
/// Collect all TTree-relevant methods like reading and processing

(function( factory ) {
   if ( typeof define === "function" && define.amd ) {
      // AMD. Register as an anonymous module.
      define( ['JSRootCore', 'JSRootIOEvolution'], factory );
   } else {

      if (typeof JSROOT == 'undefined')
         throw new Error('JSROOT is not defined', 'JSRootTree.js');

      if (typeof JSROOT.IO != 'object')
         throw new Error('JSROOT.IO not defined', 'JSRootTree.js');

      // Browser globals
      factory(JSROOT);
   }
} (function(JSROOT) {
   
   JSROOT.BranchType = { kLeafNode: 0, kBaseClassNode: 1, kObjectNode: 2, kClonesNode: 3,
                         kSTLNode: 4, kClonesMemberNode: 31, kSTLMemberNode: 41 }; 
   
   JSROOT.TSelector = function() {
      // class to read data from TTree
      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 childs should be read
      this.is_integer = []; // array of 
      this.break_execution = 0;
      this.tgtobj = {};
   }
   
   JSROOT.TSelector.prototype.AddBranch = function(branch, name, direct) {
      // Add branch to the selector
      // 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 (!name) 
         name = (typeof branch === 'string') ? branch : ("br" + this.branches.length);
      this.branches.push(branch);
      this.names.push(name);
      this.directs.push(direct);
      return this.branches.length-1;
   }
   
   JSROOT.TSelector.prototype.indexOfBranch = function(branch) {
      return this.branches.indexOf(branch);
   }
   
   JSROOT.TSelector.prototype.nameOfBranch = function(indx) {
      return this.names[indx];
   }
   
   JSROOT.TSelector.prototype.IsInteger = function(nbranch) {
      return this.is_integer[nbranch];
   }
   
   JSROOT.TSelector.prototype.ShowProgress = function(value) {
      // this function can be used to check current TTree progress
   }
   
   JSROOT.TSelector.prototype.Abort = function() {
      // call this function to abort processing
      this.break_execution = -1111;
   }

   JSROOT.TSelector.prototype.Begin = function(tree) {
      // function called before start processing
   }

   JSROOT.TSelector.prototype.Process = function(entry) {
      // function called when next entry extracted from the tree
   }
   
   JSROOT.TSelector.prototype.Terminate = function(res) {
      // function called at the very end of processing
   }

   // =================================================================
   
   JSROOT.CheckArrayPrototype = function(arr, check_content) {
      // return 0 when not array
      // 1 - when arbitrary array
      // 2 - when plain (1-dim) array with same-type content 
      if (typeof arr !== 'object') return 0;
      var proto = Object.prototype.toString.apply(arr);
      if (proto.indexOf('[object')!==0) return 0;
      var pos = proto.indexOf('Array]');
      if (pos < 0) return 0;
      if (pos > 8) return 2; // this is typed array like Int32Array
      
      if (!check_content) return 1; //  
      var typ, plain = true;
      for (var k=0;k<arr.length;++k) {
         var sub = typeof arr[k];
         if (!typ) typ = sub;
         if (sub!==typ) { plain = false; break; }
         if ((sub=="object") && JSROOT.CheckArrayPrototype(arr[k])) { plain = false; break; } 
      }
      
      return plain ? 2 : 1;
   }
   
   JSROOT.IsRootCollection = function(obj) {
      if (!obj || (typeof obj !== 'object')) return false;
      
      var kind = obj.$kind || obj._typename;
      
      return ((obj.arr !== undefined) && (kind === 'TList') || (kind === 'TObjArray') || (kind === 'TClonesArray')); 
   }
   
   JSROOT.ArrayIterator = function(arr, select, tgtobj) {
      // class used to iterate over all array indexes until number value
      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 (typeof select === 'object')
         this.select = select; // remember indexes for selection
      else
         this.select = []; // empty array, undefined for each dimension means iterate over all indexes
   }
   
   JSROOT.ArrayIterator.prototype.next = function() {
      var obj, typ, cnt = this.cnt, seltyp;
      
      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) {
         
         if (cnt < 0) {
            obj = this.object;
         } else {
            obj = (this.arr[cnt])[this.indx[cnt]];
         }
      
         typ = obj ? typeof obj : "any";
         
         if ((typ === "object") && obj._typename) {
            if (JSROOT.IsRootCollection(obj)) obj = obj.arr;
                                         else typ = "any";
         } 
         
         if ((typ=="any") && (typeof this.select[cnt+1] ==="string")) {
            // 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 === "object") && !isNaN(obj.length) && (obj.length > 0) && (JSROOT.CheckArrayPrototype(obj)>0)) {
            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;
                  break;
               case "$self$":
                  this.value = obj;
                  this.fastindx = this.fastlimit = 0;
                  this.cnt = cnt;
                  return true;
                  break;
               default: 
                  if (!isNaN(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;
         } 
      }
      
      return false;
   }
   
   JSROOT.ArrayIterator.prototype.reset = function() {
      this.arr = [];
      this.indx = [];
      delete this.fastarr;
      this.cnt = -1;
      this.value = 0;
   }

   // ============================================================================
   
   JSROOT.TDrawVariable = function(globals) {
      // object with single variable in TTree::Draw expression
      this.globals = globals;
      
      this.code = "";
      this.brindex = []; // index of used branches from selector
      this.branches = []; // names of bracnhes 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
   }
   
   JSROOT.TDrawVariable.prototype.Parse = function(tree,selector,code,only_branch,direct_branch) {
      // when only_branch specified, its placed in the front of the expression 
      
      function is_start_symbol(symb) {
         if ((symb >= "A") && (symb <= "Z")) return true; 
         if ((symb >= "a") && (symb <= "z")) return true;
         return (symb === "_");
      }
      
      function 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 ? only_branch.fName : "") + code;

      var pos = 0, pos2 = 0, br = null;
      while ((pos < code.length) || only_branch) {

         var arriter = [];
         
         if (only_branch) {
            br = only_branch;
            only_branch = undefined;
         } else {
            // first try to find branch
            while ((pos < code.length) && !is_start_symbol(code[pos])) pos++;
            pos2 = pos;
            while ((pos2 < code.length) && (is_next_symbol(code[pos2]) || code[pos2]===".")) pos2++;
            
            if (code[pos2]=="$") {
               var repl = "";
               switch (code.substr(pos, pos2-pos)) {
                  case "LocalEntry":
                  case "Entry": repl = "arg.globals.entry"; break;
                  case "Entries": repl = "arg.globals.entries"; break;
               }
               if (repl) {
                  console.log('Replace ', code.substr(pos, pos2-pos), 'with', repl); 
                  code = code.substr(0, pos) + repl + code.substr(pos2+1);
                  pos = pos + repl.length;
                  continue;
               }
            }

            br = tree.FindBranch(code.substr(pos, pos2-pos), true);
            if (!br) { pos = pos2+1; continue; }

            // when full id includes branch name, replace only part of extracted expression 
            if (br.branch && br.rest) {
               pos2 -= br.rest.length;
               br = br.branch;
            } else 
            if (code[pos2-1]===".") {
               // when branch name ends with point, means object itself should be extracted
               arriter.push("$self$");
            }
         }
         
         // now extract all levels of iterators 
         while (pos2 < code.length) {
            
            if ((code[pos2]==="@") && (code.substr(pos2,5)=="@size") && (arriter.length==0)) {
               pos2+=5; 
               direct_branch = true; 
               break;
            } 

            if (code[pos2] === ".") {
               // this is object member
               var prev = ++pos2; 
               
               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++;
               
               arriter.push(code.substr(prev, pos2-prev));
               continue;
            }
            
            if (code[pos2]!=="[") break;
            
            // simple [] 
            if (code[pos2+1]=="]") { arriter.push(undefined); pos2+=2; continue; }

            var prev = pos2++, cnt = 0;
            while ((pos2 < code.length) && ((code[pos2]!="]") || (cnt>0))) {
               if (code[pos2]=='[') cnt++; else if (code[pos2]==']') cnt--; 
               pos2++;
            }
            var sub = code.substr(prev+1, pos2-prev-1);
            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 (!isNaN(parseInt(sub))) {
                     arriter.push(parseInt(sub)); 
                  } else {
                     // try to compile code as draw variable
                     var subvar = new JSROOT.TDrawVariable(this.globals);
                     // console.log("produce subvar with code", sub);
                     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;
         
         console.log('arriter', arriter);
         
         var indx = selector.indexOfBranch(br);
         if (indx<0) indx = selector.AddBranch(br, undefined, direct_branch);
         
         direct_branch = 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;
            return true; 
         }
         
         var replace = "arg.var" + (this.branches.length-1);
         
         code = code.substr(0, pos) + replace + code.substr(pos2);
         
         pos = pos + replace.length;
      }
      
      this.func = new Function("arg", "return (" + code + ")");
      
      return true;
   }
   
   JSROOT.TDrawVariable.prototype.IsInteger = function(selector) {
      // check if draw variable produces integer values
      // derived from type of data in the branch
      if ((this.kind !== "number") || !this.direct_branch) return false;
      
      return selector.IsInteger(this.brindex[0]);
   }
   
   JSROOT.TDrawVariable.prototype.is_dummy = function() {
      return this.branches.length === 0;
   }
   
   JSROOT.TDrawVariable.prototype.Produce = function(obj) {
      // after reading tree braches into the object, calculate variable value

      this.length = 1;
      this.isarray = false;
      
      if (this.is_dummy()) {
         this.value = 1.; // used as dummy weight variable
         this.kind = "number";
         return;
      }
      
      var arg = { globals: this.globals }, usearrlen = -1, arrs = [];
      for (var n=0;n<this.branches.length;++n) {
         var 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] = (JSROOT.CheckArrayPrototype(arg[name]) > 0) || JSROOT.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) && (JSROOT.CheckArrayPrototype(arg[name], true) === 2)) {
            // plain array, can be used as is
            arrs[n] = arg[name]; 
         } else {
            var iter = new JSROOT.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 fillied
         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 (var k=0;k<usearrlen;++k) {
            for (var 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];
   }
   
   JSROOT.TDrawVariable.prototype.get = function(indx) {
      return this.isarray ? this.value[indx] : this.value; 
   } 
   
   JSROOT.TDrawVariable.prototype.AppendArray = function(tgtarr) {
      // appeand array to the buffer
      
      this.buf = this.buf.concat(tgtarr[this.branches[0]]);
   }

   // =============================================================================

   JSROOT.TDrawSelector = function(callback) {
      JSROOT.TSelector.call(this);   
      
      this.ndim = 0;
      this.vars = []; // array of expression varibles 
      this.cut = null; // cut variable
      this.hist = null;
      this.histo_callback = callback;
      this.hist_name = "$htemp";
      this.hist_title = "Result of TTree::Draw";
      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 
   }

   JSROOT.TDrawSelector.prototype = Object.create(JSROOT.TSelector.prototype);
   
   JSROOT.TDrawSelector.prototype.ParseParameters = function(tree, args, expr) {
      
      if (!expr || (typeof expr !== "string")) return "";
      
      // parse parameters which defined at the end as expression;par1name:par1value;par2name:par2value
      var pos = expr.lastIndexOf(";");
      while (pos>0) {
         var parname = expr.substr(pos+1), parvalue = undefined;
         expr = expr.substr(0,pos);
         pos = expr.lastIndexOf(";");
         
         var separ = parname.indexOf(":");
         if (separ>0) { parvalue = parname.substr(separ+1); parname = parname.substr(0, separ);  }
         
         var intvalue = parseInt(parvalue);
         if (!parvalue || isNaN(intvalue)) intvalue = undefined;
         
         switch (parname) {
            case "num": 
            case "entries": 
            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 "mon":
            case "monitor":
               args.monitoring = (intvalue !== undefined) ? intvalue : 5000;
               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 ((this.htype!=="C") && (this.htype!=="S") && (this.htype!=="I") 
                       && (this.htype!=="F") && (this.htype!=="L") && (this.htype!=="D")) this.htype = "F";
               }
               break;
         }
      }
      
      pos = expr.lastIndexOf(">>");
      if (pos>=0) {
         var harg = expr.substr(pos+2).trim();
         expr = expr.substr(0,pos).trim();
         pos = harg.indexOf("(");
         if (pos>0) {
            this.hist_name = harg.substr(0, pos);
            harg = harg.substr(pos);
         }  
         if (harg === "dump") {
            this.dump_values = true;
            if (args.numentries===undefined) args.numentries = 10;
         } else
         if (pos<0) {
            this.hist_name = harg; 
         } else  
         if ((harg[0]=="(") && (harg[harg.length-1]==")"))  {
            harg = harg.substr(1,harg.length-2).split(",");
            var isok = true;
            for (var n=0;n<harg.length;++n) {
               harg[n] = (n%3===0) ? parseInt(harg[n]) : parseFloat(harg[n]);
               if (isNaN(harg[n])) isok = false;
            }
            if (isok) this.hist_args = harg; 
         }
      }

      return expr;
   }
  
   JSROOT.TDrawSelector.prototype.ParseDrawExpression = function(tree, args) {
      
      // parse complete expression
      var expr = this.ParseParameters(tree, args, args.expr);

      // parse option for histogram creation

      this.hist_title = "drawing '" + expr + "' from " + tree.fName;

      var pos = expr.lastIndexOf("::"), cut = "";
      if (pos>0) {
         cut = expr.substr(pos+2).trim();
         expr = expr.substr(0,pos).trim();
      }
      
      var names = expr.split(":");
      if ((names.length < 1) || (names.length > 3)) return false;

      this.ndim = names.length;

      var is_direct = !cut;

      for (var n=0;n<this.ndim;++n) {
         this.vars[n] = new JSROOT.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 JSROOT.TDrawVariable(this.globals);
      if (cut) 
         if (!this.cut.Parse(tree, this, cut)) return false;
      
      if (!this.branches.length) {
         console.log('no any branch is selected');
         return false;
      }
      
      if (is_direct) this.ProcessArrays = this.ProcessArraysFunc;
      
      this.monitoring = args.monitoring;
      
      return true;
   }
   
   JSROOT.TDrawSelector.prototype.DrawOnlyBranch = function(tree, branch, expr, args) {
      this.ndim = 1;
      
      expr = this.ParseParameters(tree, args, expr);
      
      if (expr === "dump") {
         this.dump_values = true;
         if (args.numentries===undefined) args.numentries = 10;
         expr = "";
      }

      this.vars[0] = new JSROOT.TDrawVariable(this.globals);
      if (!this.vars[0].Parse(tree, this, expr, branch, args.direct_branch)) return false;
      this.hist_title = "drawing branch '" + branch.fName + (expr ? "' expr:'" + expr : "") + "'  from " + tree.fName;
      
      this.cut = new JSROOT.TDrawVariable(this.globals);
      
      if (this.vars[0].direct_branch) this.ProcessArrays = this.ProcessArraysFunc;
      
      this.monitoring = args.monitoring;

      return true;
   }
   
   JSROOT.TDrawSelector.prototype.Begin = function(tree) {
      this.globals.entries = tree.fEntries;
      
      if (this.monitoring)
         this.lasttm = new Date().getTime();
   } 
   
   JSROOT.TDrawSelector.prototype.ShowProgress = function(value) {
      // this function should be defined not here
      
      if ((document === undefined) || (JSROOT.progress===undefined)) return;

      if ((value===undefined) || isNaN(value)) return JSROOT.progress();

      var main_box = document.createElement("p"),
          text_node = document.createTextNode("TTree draw " + Math.round((value*100)) + " %  "),
          selector = this;
      
      main_box.appendChild(text_node);
      main_box.title = "Click on element to break drawing";

      main_box.onclick = function() {
         if (++selector.break_execution<3) {
            main_box.title = "Tree draw will break after next I/O operation";
            return text_node.nodeValue = "Breaking ... ";
         }
         selector.Abort();
         JSROOT.progress();
      }

      JSROOT.progress(main_box);
   }
   
   JSROOT.TDrawSelector.prototype.GetBitsBins = function(nbits) {
      
      var res = { nbins: nbits, min: 0, max: nbits, k: 1., fLabels: JSROOT.Create("THashList") };
      
      for (var k=0;k<nbits;++k) {
         var s = JSROOT.Create("TObjString");
         s.fString = k.toString();
         s.fUniqueID = k+1;
         res.fLabels.Add(s);
      }
      return res;
   }

   JSROOT.TDrawSelector.prototype.GetMinMaxBins = function(axisid, nbins) {
      
      var res = { min: 0, max: 0, nbins: nbins, fLabels: null };
      
      if (axisid >= this.ndim) return res;
      
      var arr = this.vars[axisid].buf;
      
      if (this.vars[axisid].kind === "object") {
         // this is any object type
         var typename, similar = true, maxbits = 8;
         for (var 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)) {
               console.log('Provide special handling fot TBits');
               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);
            }
         }
         
         console.log('See object typename', typename, 'similar', similar);
      }
      
      
      if (this.vars[axisid].kind === "string") {
         res.lbls = []; // all labels
         
         for (var 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 = JSROOT.Create("THashList");
         for (var k=0;k<res.lbls.length;++k) {
            var s = JSROOT.Create("TObjString");
            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);
      } 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 {
      
         res.min = Math.min.apply(null, arr);
         res.max = Math.max.apply(null, arr);
         
         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
         if (this.vars[axisid].IsInteger(this) && (res.max-res.min >=1) && (res.max-res.min<nbins*10)) {
            res.min -= 1;
            res.max += 2;
            res.nbins = Math.round(res.max - res.min);
         } else {
            res.max += (res.max-res.min)/res.nbins;
         }
      }
      
      res.k = res.nbins/(res.max-res.min);

      res.GetBin = function(value) {
         var bin = this.lbls ? this.lbls.indexOf(value) : Math.floor((value-this.min)*this.k);
         return (bin<0) ? 0 : ((bin>this.nbins) ? this.nbins+1 : bin+1); 
      }

      return res;
   }
   
   JSROOT.TDrawSelector.prototype.CreateHistogram = function() {
      if (this.hist || !this.vars[0].buf) return;
      
      if (this.dump_values) {
         // just create array where dumped valus will be collected  
         this.hist = [];
         
         // reassign fill method
         this.Fill1DHistogram = this.Fill2DHistogram = this.Fill3DHistogram = this.DumpValue;  
      } else {
         
         this.x = this.GetMinMaxBins(0, (this.ndim > 1) ? 50 : 200);

         this.y = this.GetMinMaxBins(1, 50);

         this.z = this.GetMinMaxBins(2, 50);

         switch (this.ndim) {
            case 1: this.hist = JSROOT.CreateHistogram("TH1"+this.htype, this.x.nbins); break; 
            case 2: this.hist = JSROOT.CreateHistogram("TH2"+this.htype, this.x.nbins, this.y.nbins); break;
            case 3: this.hist = JSROOT.CreateHistogram("TH3"+this.htype, this.x.nbins, this.y.nbins, this.z.nbins); break;
         }

         this.hist.fXaxis.fTitle = this.vars[0].code;
         this.hist.fXaxis.fXmin = this.x.min;
         this.hist.fXaxis.fXmax = this.x.max;
         this.hist.fXaxis.fLabels = this.x.fLabels;

         if (this.ndim > 1) this.hist.fYaxis.fTitle = this.vars[1].code;
         this.hist.fYaxis.fXmin = this.y.min;
         this.hist.fYaxis.fXmax = this.y.max;
         this.hist.fYaxis.fLabels = this.y.fLabels;

         if (this.ndim > 2) this.hist.fZaxis.fTitle = this.vars[2].code;
         this.hist.fZaxis.fXmin = this.z.min;
         this.hist.fZaxis.fXmax = this.z.max;
         this.hist.fZaxis.fLabels = this.z.fLabels;

         this.hist.fName = this.hist_name;
         this.hist.fTitle = this.hist_title;
         this.hist.$custom_stat = (this.hist_name == "$htemp") ? 111110 : 111111;
      }
      
      var var0 = this.vars[0].buf, cut = this.cut.buf, len = var0.length; 
         
      switch (this.ndim) {
         case 1:
            for (var n=0;n<len;++n) 
               this.Fill1DHistogram(var0[n], cut ? cut[n] : 1.);
            break;
         case 2: 
            var var1 = this.vars[1].buf;
            for (var n=0;n<len;++n) 
               this.Fill2DHistogram(var0[n], var1[n], cut ? cut[n] : 1.);
            delete this.vars[1].buf;
            break;
         case 3:
            var var1 = this.vars[1].buf, var2 = this.vars[2].buf; 
            for (var n=0;n<len;++n) 
               this.Fill2DHistogram(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;
   }

   JSROOT.TDrawSelector.prototype.FillTBitsHistogram = function(xvalue, weight) {
      if (!weight || !xvalue || !xvalue.fNbits || !xvalue.fAllBits) return;
      
      var sz = Math.min(xvalue.fNbits+1, xvalue.fNbytes*8);
      
      for (var 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; }
      }
   }
   
   JSROOT.TDrawSelector.prototype.FillBitsHistogram = function(xvalue, weight) {
      if (!weight) return;
      
      for (var bit=0,mask=1;bit<this.x.nbins;++bit) {
         if (xvalue & mask) this.hist.fArray[bit+1] += weight;
         mask*=2;
      }
   }
   
   JSROOT.TDrawSelector.prototype.Fill1DHistogram = function(xvalue, weight) {
      var bin = this.x.GetBin(xvalue);
      this.hist.fArray[bin] += weight;
   }

   JSROOT.TDrawSelector.prototype.Fill2DHistogram = function(xvalue, yvalue, weight) {
      var xbin = this.x.GetBin(xvalue),
          ybin = this.y.GetBin(yvalue);
      
      this.hist.fArray[xbin+(this.x.nbins+2)*ybin] += weight;
   }

   JSROOT.TDrawSelector.prototype.Fill3DHistogram = function(xvalue, yvalue, zvalue, weight) {
      var 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;
   }
   
   JSROOT.TDrawSelector.prototype.DumpValue = function(v1, v2, v3, v4) {
      var 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);
   }
   
   JSROOT.TDrawSelector.prototype.ProcessArraysFunc = function(entry) {
      // function used when all bracnhes can be read as array
      // most typical usage - histogramming of single branch 
      
      
      if (this.arr_limit) {
         var var0 = this.vars[0], len = this.tgtarr.br0.length,
             var1 = this.vars[1], var2 = this.vars[2];
         if ((var0.buf.length===0) && (len>=this.arr_limit)) {
            // special usecase - first arraya 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 (var 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 (var0.buf.length >= this.arr_limit) {
            this.CreateHistogram();
            this.arr_limit = 0;
         }
      } else {
         var br0 = this.tgtarr.br0, len = br0.length;
         switch(this.ndim) {
            case 1:
               for (var k=0;k<len;++k)
                  this.Fill1DHistogram(br0[k], 1.);
               break;
            case 2:
               var br1 = this.tgtarr.br1;
               for (var k=0;k<len;++k) 
                  this.Fill2DHistogram(br0[k], br1[k], 1.);
               break;
            case 3:
               var br1 = this.tgtarr.br1, br2 = this.tgtarr.br2;
               for (var k=0;k<len;++k) 
                  this.Fill3DHistogram(br0[k], br1[k], br2[k], 1.);
               break;
         } 
      }
   }


   JSROOT.TDrawSelector.prototype.Process = function(entry) {
      
      this.globals.entry = entry; // can be used in any expression
      
      for (var n=0;n<this.ndim;++n)
         this.vars[n].Produce(this.tgtobj);
      
      this.cut.Produce(this.tgtobj);

      var var0 = this.vars[0], var1 = this.vars[1], var2 = this.vars[2], cut = this.cut;   

      if (this.arr_limit) {
         switch(this.ndim) {
            case 1:
              for (var n0=0;n0<var0.length;++n0) {
                 var0.buf.push(var0.get(n0));
                 cut.buf.push(cut.value);
              }
              break;
            case 2:
              for (var n0=0;n0<var0.length;++n0) 
                 for (var n1=0;n1<var1.length;++n1) {
                    var0.buf.push(var0.get(n0));
                    var1.buf.push(var1.get(n1));
                    cut.buf.push(cut.value);
                 }
              break;
            case 3:
               for (var n0=0;n0<var0.length;++n0)
                  for (var n1=0;n1<var1.length;++n1)
                     for (var n2=0;n2<var2.length;++n2) {
                        var0.buf.push(var0.get(n0));
                        var1.buf.push(var1.get(n1));
                        var2.buf.push(var2.get(n2));
                        cut.buf.push(cut.value);
                     }
               break;
         }
         if (var0.buf.length >= this.arr_limit) {
            this.CreateHistogram();
            this.arr_limit = 0;
         }
      } else
      if (this.hist) {
         switch(this.ndim) {
            case 1:
               for (var n0=0;n0<var0.length;++n0)
                  this.Fill1DHistogram(var0.get(n0), cut.value);
               break;
            case 2:
               for (var n0=0;n0<var0.length;++n0)
                  for (var n1=0;n1<var1.length;++n1)
                     this.Fill2DHistogram(var0.get(n0), var1.get(n1), cut.value);
               break;
            case 3:
               for (var n0=0;n0<var0.length;++n0)
                  for (var n1=0;n1<var1.length;++n1)
                     for (var n2=0;n2<var2.length;++n2)
                        this.Fill3DHistogram(var0.get(n0), var1.get(n1), var2.get(n2), cut.value);
               break;
         } 
      }
      
      if (this.monitoring && this.hist && !this.dump_values) {
         var now = new Date().getTime();
         if (now - this.lasttm > this.monitoring) { 
            this.lasttm = now;
            var drawopt = (this.ndim==2) ? "col" : "";
            JSROOT.CallBack(this.histo_callback, this.hist, drawopt, true);
         }
      }
   }
   
   JSROOT.TDrawSelector.prototype.Terminate = function(res) {
      if (res && !this.hist) this.CreateHistogram();
      
      this.ShowProgress();
      
      var drawopt = (this.ndim==2) ? "col" : "";
      if (this.dump_values) drawopt = "inspect";
      
      return JSROOT.CallBack(this.histo_callback, this.hist, drawopt);
   }
   
   // ======================================================================
   
   /** @namespace JSROOT.TreeMethods */
   JSROOT.TreeMethods = {}; // these are only TTree methods, which are automatically assigned to every TTree 

   /** @memberOf JSROOT.TreeMethods  */
   JSROOT.TreeMethods.Process = function(selector, args) {
      // function similar to the TTree::Process
      
      if (!args) args = {};
      
      if (!selector || !this.$file || !selector.branches) {
         console.error('required parameter missing for TTree::Process');
         if (selector) selector.Terminate(false);
         return false;
      }
      
      // central handle with all information required for reading
      var handle = {
          tree: this, // keep tree reference  
          file: this.$file, // keep file reference
          selector: selector, // reference on selector  
          arr: [], // list of branches 
          curr: -1,  // current entry ID
          current_entry: -1, // current processed entry
          simple_read: true, // all baskets in all used branches are in sync,
          process_arrays: true // one can process all branches as arrays
      };
      
      var namecnt = 0;
      
      function CreateLeafElem(leaf, name) {
         // function creates TStreamerElement which corresponds to the elementary leaf
         var datakind = 0;
         switch (leaf._typename) {
            case 'TLeafF': datakind = JSROOT.IO.kFloat; break;
            case 'TLeafD': datakind = JSROOT.IO.kDouble; break;
            case 'TLeafO': datakind = JSROOT.IO.kBool; break;
            case 'TLeafB': datakind = leaf.fIsUnsigned ? JSROOT.IO.kUChar : JSROOT.IO.kChar; break;
            case 'TLeafS': datakind = leaf.fIsUnsigned ? JSROOT.IO.kUShort : JSROOT.IO.kShort; break;
            case 'TLeafI': datakind = leaf.fIsUnsigned ? JSROOT.IO.kUInt : JSROOT.IO.kInt; break;
            case 'TLeafL': datakind = leaf.fIsUnsigned ? JSROOT.IO.kULong64 : JSROOT.IO.kLong64; break;
            case 'TLeafC': datakind = JSROOT.IO.kTString; break;
            default: return null;
         }
         var elem = JSROOT.IO.CreateStreamerElement(name || leaf.fName, "int");
         elem.fType = datakind;
         return elem;
      }

      function FindInHandle(branch) {
         for (var k=0;k<handle.arr.length;++k)
            if (handle.arr[k].branch === branch) return handle.arr[k];
         return null;
      }

      function AddBranchForReading(branch, target_object, target_name, is_direct) {
         // central method to add branch for reading
         // is_direct == true - read only this branch
         // is_direct == '$child$' is just member of object from for STL or clonesarray
         // is_direct == '<any class name>' is sub-object from STL or clonesarray, happens when such new object need to be created 

         if (typeof branch === 'string')
            branch = handle.tree.FindBranch(branch);
         
         if (!branch) { console.error('Did not found branch'); return null; }
         
         var item = FindInHandle(branch);
         
         if (item) {
            console.error('Branch already configured for reading', branch.fName);
            if (item.tgt !== target_object) console.error('Target object differs');
            return elem;
         }
         
         if (!branch.fEntries) {
            console.log('Branch ', branch.fName, ' does not have entries');
            return null;
         } 
         
         item = {
               branch: branch,
               tgt: target_object, // used target object - can be differ for object members
               name: target_name,
               index: -1, // index in the list of read branches
               member: null, // member to read branch
               type: 0, // keep type identifier
               curr_entry: -1, // last processed entry
               raw : null, // raw buffer for reading
               curr_basket: 0,  // number of basket used for processing
               read_entry: -1,  // last entry which is already read 
               staged_entry: -1, // entry which is staged for reading
               first_readentry: -1, // first entry to read
               staged_basket: 0,  // last basket staged for reading
               numentries: branch.fEntries,
               numbaskets: branch.fWriteBasket, // number of baskets which can be read from the file
               counters: null, // branch indexes used as counters
               ascounter: [], // list of other branches using that branch as counter 
               baskets: [] // array for read baskets,
         };

         // check all counters if we 
         var item_cnt = null, item_cnt2 = null;
         
         if (branch.fBranchCount) {
            
            item_cnt = FindInHandle(branch.fBranchCount);
            
            if (!item_cnt) item_cnt = AddBranchForReading(branch.fBranchCount, target_object, "$counter" + namecnt++, true); 
            
            if (!item_cnt) { console.error('Cannot add counter branch', branch.fBranchCount.fName); return null; }

            var BranchCount2 = branch.fBranchCount2;
            
            if (!BranchCount2 && (branch.fBranchCount.fStreamerType===JSROOT.IO.kSTL) && 
                ((branch.fStreamerType === JSROOT.IO.kStreamLoop) || (branch.fStreamerType === JSROOT.IO.kOffsetL+JSROOT.IO.kStreamLoop))) {
                 // special case when count member from kStreamLoop not assigned as fBranchCount2  
                 var s_i = handle.file.FindStreamerInfo(branch.fClassName,  branch.fClassVersion, branch.fCheckSum),
                     elem = s_i ? s_i.fElements.arr[branch.fID] : null,
                     arr = branch.fBranchCount.fBranches.arr  ;

                 if (elem && elem.fCountName && arr) 
                    for(var k=0;k<arr.length;++k) 
                       if (arr[k].fName === branch.fBranchCount.fName + "." + elem.fCountName) {
                          BranchCount2 = arr[k];
                          break;
                       }

                 if (!BranchCount2) console.error('Did not found branch for second counter of kStreamLoop element');
              }
            
            if (BranchCount2) {
               item_cnt2 = FindInHandle(BranchCount2);
               
               if (!item_cnt2) item_cnt2 = AddBranchForReading(BranchCount2, target_object, "$counter" + namecnt++, true); 
               
               if (!item_cnt2) { console.error('Cannot add counter branch2', BranchCount2.fName); return null; }
            }
         }
         
         var nb_branches = branch.fBranches ? branch.fBranches.arr.length : 0,
             nb_leaves = branch.fLeaves ? branch.fLeaves.arr.length : 0,
             leaf = (nb_leaves>0) ? branch.fLeaves.arr[0] : null,
             elem = null, // TStreamerElement used to create reader 
             member = null, // member for actual reading of the branch
             is_brelem = (branch._typename==="TBranchElement"),
             child_scan = 0; // scan child branches after main branch is appended
             
          function ScanBranches(lst, master_target, chld_kind) {
             if (!lst || !lst.arr.length) return true;

             for (var k=0;k<lst.arr.length;++k) {
                var br = lst.arr[k];
                if ((chld_kind>0) && (br.fType!==chld_kind)) {
                   console.log('Child type differ from expected', br.fType, chld_kind);
                   continue;
                }
                
                if (br.fType === JSROOT.BranchType.kBaseClassNode) {
                   if (!ScanBranches(br.fBranches, master_target, chld_kind)) return false;
                   continue;
                }
                if (br.fName.indexOf(branch.fName + ".")!==0) {
                   console.warn('Not expected branch name ', br.fName, 'for master', branch.fName);
                   continue;
                }

                var subname = br.fName.substr(branch.fName.length+1);
                var p = subname.indexOf('['); 
                if (p>0) subname = subname.substr(0,p);

                var chld_direct = true;
                if (chld_kind > 0) {
                   chld_direct = "$child$";
                   var pp = subname.indexOf(".");
                   if (pp>0) {
                      chld_direct = br.fClassName || "TObject";
                      var prefix = branch.fName + "." + subname.substr(0,pp+1);
                      for (var kk=k+1;kk<lst.arr.length;++kk)
                         if ((lst.arr[kk].fName.indexOf(prefix)===0) && lst.arr[kk].fClassName) chld_direct = lst.arr[kk].fClassName;
                   }
                }

                console.log('Add branch', branch.fName, 'target', subname, chld_direct);
                

                if (!AddBranchForReading(br, master_target, subname, chld_direct)) return false;
             }
             return true;
          }

          if (is_brelem && (branch.fType === JSROOT.BranchType.kObjectNode)) {
             
             if (is_direct === true) {
                console.log('kObjectNode does not have data to be readed directly');
                return null;
             }
             
             handle.process_arrays = false;
             
             // object where all sub-branches will be collected
             var tgt = target_object[target_name] = { _typename: "TObject" };

             var s_i = handle.file.FindStreamerInfo(branch.fClassName, branch.fClassVersion, branch.fCheckSum),
                 s_elem = s_i ? s_i.fElements.arr[branch.fID] : null;
             
             if (s_elem && s_elem.fType === JSROOT.IO.kObject) {
                tgt._typename = s_elem.fTypeName;
                console.log('Reconstruct object of type', tgt, s_elem.fTypeName);
             }
             if (!ScanBranches(branch.fBranches, tgt,  0)) return null;
             
             return item; // this kind of branch does not have baskets and not need to be read
         }
          
         if (is_brelem && ((branch.fType === JSROOT.BranchType.kClonesNode) || (branch.fType === JSROOT.BranchType.kSTLNode))) {

            elem = JSROOT.IO.CreateStreamerElement(target_name, "int");

            if (!is_direct) {
               handle.process_arrays = false;
               
               member = {
                  name: target_name,
                  conttype: branch.fClonesName || "TObject",
                  func: function(buf,obj) {
                     var size = buf.ntoi4(), n = 0;
                     obj[this.name] = new Array(size); // can one reuse memory later ???
                     while (n<size) obj[this.name][n++] = { _typename: this.conttype }; // create new objects
                  }
               }
               
               child_scan = (branch.fType === JSROOT.BranchType.kClonesNode) ? JSROOT.BranchType.kClonesMemberNode : JSROOT.BranchType.kSTLMemberNode; 
            }
          } else
       
          if (is_brelem && (nb_leaves === 1) && (leaf.fName === branch.fName) && (branch.fID==-1)) {

             elem = JSROOT.IO.CreateStreamerElement(target_name, branch.fClassName);
             
             console.log('TBranchElement with ID==-1 typename ', branch.fClassName, 'type', elem.fType);
             
             if (elem.fType === JSROOT.IO.kAny) {
                <commit_msg>Allow reading of kLeafNode branch<commit_after>/// @file JSRootTree.js
/// Collect all TTree-relevant methods like reading and processing

(function( factory ) {
   if ( typeof define === "function" && define.amd ) {
      // AMD. Register as an anonymous module.
      define( ['JSRootCore', 'JSRootIOEvolution'], factory );
   } else {

      if (typeof JSROOT == 'undefined')
         throw new Error('JSROOT is not defined', 'JSRootTree.js');

      if (typeof JSROOT.IO != 'object')
         throw new Error('JSROOT.IO not defined', 'JSRootTree.js');

      // Browser globals
      factory(JSROOT);
   }
} (function(JSROOT) {
   
   JSROOT.BranchType = { kLeafNode: 0, kBaseClassNode: 1, kObjectNode: 2, kClonesNode: 3,
                         kSTLNode: 4, kClonesMemberNode: 31, kSTLMemberNode: 41 }; 
   
   JSROOT.TSelector = function() {
      // class to read data from TTree
      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 childs should be read
      this.is_integer = []; // array of 
      this.break_execution = 0;
      this.tgtobj = {};
   }
   
   JSROOT.TSelector.prototype.AddBranch = function(branch, name, direct) {
      // Add branch to the selector
      // 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 (!name) 
         name = (typeof branch === 'string') ? branch : ("br" + this.branches.length);
      this.branches.push(branch);
      this.names.push(name);
      this.directs.push(direct);
      return this.branches.length-1;
   }
   
   JSROOT.TSelector.prototype.indexOfBranch = function(branch) {
      return this.branches.indexOf(branch);
   }
   
   JSROOT.TSelector.prototype.nameOfBranch = function(indx) {
      return this.names[indx];
   }
   
   JSROOT.TSelector.prototype.IsInteger = function(nbranch) {
      return this.is_integer[nbranch];
   }
   
   JSROOT.TSelector.prototype.ShowProgress = function(value) {
      // this function can be used to check current TTree progress
   }
   
   JSROOT.TSelector.prototype.Abort = function() {
      // call this function to abort processing
      this.break_execution = -1111;
   }

   JSROOT.TSelector.prototype.Begin = function(tree) {
      // function called before start processing
   }

   JSROOT.TSelector.prototype.Process = function(entry) {
      // function called when next entry extracted from the tree
   }
   
   JSROOT.TSelector.prototype.Terminate = function(res) {
      // function called at the very end of processing
   }

   // =================================================================
   
   JSROOT.CheckArrayPrototype = function(arr, check_content) {
      // return 0 when not array
      // 1 - when arbitrary array
      // 2 - when plain (1-dim) array with same-type content 
      if (typeof arr !== 'object') return 0;
      var proto = Object.prototype.toString.apply(arr);
      if (proto.indexOf('[object')!==0) return 0;
      var pos = proto.indexOf('Array]');
      if (pos < 0) return 0;
      if (pos > 8) return 2; // this is typed array like Int32Array
      
      if (!check_content) return 1; //  
      var typ, plain = true;
      for (var k=0;k<arr.length;++k) {
         var sub = typeof arr[k];
         if (!typ) typ = sub;
         if (sub!==typ) { plain = false; break; }
         if ((sub=="object") && JSROOT.CheckArrayPrototype(arr[k])) { plain = false; break; } 
      }
      
      return plain ? 2 : 1;
   }
   
   JSROOT.IsRootCollection = function(obj) {
      if (!obj || (typeof obj !== 'object')) return false;
      
      var kind = obj.$kind || obj._typename;
      
      return ((obj.arr !== undefined) && (kind === 'TList') || (kind === 'TObjArray') || (kind === 'TClonesArray')); 
   }
   
   JSROOT.ArrayIterator = function(arr, select, tgtobj) {
      // class used to iterate over all array indexes until number value
      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 (typeof select === 'object')
         this.select = select; // remember indexes for selection
      else
         this.select = []; // empty array, undefined for each dimension means iterate over all indexes
   }
   
   JSROOT.ArrayIterator.prototype.next = function() {
      var obj, typ, cnt = this.cnt, seltyp;
      
      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) {
         
         if (cnt < 0) {
            obj = this.object;
         } else {
            obj = (this.arr[cnt])[this.indx[cnt]];
         }
      
         typ = obj ? typeof obj : "any";
         
         if ((typ === "object") && obj._typename) {
            if (JSROOT.IsRootCollection(obj)) obj = obj.arr;
                                         else typ = "any";
         } 
         
         if ((typ=="any") && (typeof this.select[cnt+1] ==="string")) {
            // 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 === "object") && !isNaN(obj.length) && (obj.length > 0) && (JSROOT.CheckArrayPrototype(obj)>0)) {
            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;
                  break;
               case "$self$":
                  this.value = obj;
                  this.fastindx = this.fastlimit = 0;
                  this.cnt = cnt;
                  return true;
                  break;
               default: 
                  if (!isNaN(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;
         } 
      }
      
      return false;
   }
   
   JSROOT.ArrayIterator.prototype.reset = function() {
      this.arr = [];
      this.indx = [];
      delete this.fastarr;
      this.cnt = -1;
      this.value = 0;
   }

   // ============================================================================
   
   JSROOT.TDrawVariable = function(globals) {
      // object with single variable in TTree::Draw expression
      this.globals = globals;
      
      this.code = "";
      this.brindex = []; // index of used branches from selector
      this.branches = []; // names of bracnhes 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
   }
   
   JSROOT.TDrawVariable.prototype.Parse = function(tree,selector,code,only_branch,direct_branch) {
      // when only_branch specified, its placed in the front of the expression 
      
      function is_start_symbol(symb) {
         if ((symb >= "A") && (symb <= "Z")) return true; 
         if ((symb >= "a") && (symb <= "z")) return true;
         return (symb === "_");
      }
      
      function 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 ? only_branch.fName : "") + code;

      var pos = 0, pos2 = 0, br = null;
      while ((pos < code.length) || only_branch) {

         var arriter = [];
         
         if (only_branch) {
            br = only_branch;
            only_branch = undefined;
         } else {
            // first try to find branch
            while ((pos < code.length) && !is_start_symbol(code[pos])) pos++;
            pos2 = pos;
            while ((pos2 < code.length) && (is_next_symbol(code[pos2]) || code[pos2]===".")) pos2++;
            
            if (code[pos2]=="$") {
               var repl = "";
               switch (code.substr(pos, pos2-pos)) {
                  case "LocalEntry":
                  case "Entry": repl = "arg.globals.entry"; break;
                  case "Entries": repl = "arg.globals.entries"; break;
               }
               if (repl) {
                  console.log('Replace ', code.substr(pos, pos2-pos), 'with', repl); 
                  code = code.substr(0, pos) + repl + code.substr(pos2+1);
                  pos = pos + repl.length;
                  continue;
               }
            }

            br = tree.FindBranch(code.substr(pos, pos2-pos), true);
            if (!br) { pos = pos2+1; continue; }

            // when full id includes branch name, replace only part of extracted expression 
            if (br.branch && br.rest) {
               pos2 -= br.rest.length;
               br = br.branch;
            } else 
            if (code[pos2-1]===".") {
               // when branch name ends with point, means object itself should be extracted
               arriter.push("$self$");
            }
         }
         
         // now extract all levels of iterators 
         while (pos2 < code.length) {
            
            if ((code[pos2]==="@") && (code.substr(pos2,5)=="@size") && (arriter.length==0)) {
               pos2+=5; 
               direct_branch = true; 
               break;
            } 

            if (code[pos2] === ".") {
               // this is object member
               var prev = ++pos2; 
               
               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++;
               
               arriter.push(code.substr(prev, pos2-prev));
               continue;
            }
            
            if (code[pos2]!=="[") break;
            
            // simple [] 
            if (code[pos2+1]=="]") { arriter.push(undefined); pos2+=2; continue; }

            var prev = pos2++, cnt = 0;
            while ((pos2 < code.length) && ((code[pos2]!="]") || (cnt>0))) {
               if (code[pos2]=='[') cnt++; else if (code[pos2]==']') cnt--; 
               pos2++;
            }
            var sub = code.substr(prev+1, pos2-prev-1);
            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 (!isNaN(parseInt(sub))) {
                     arriter.push(parseInt(sub)); 
                  } else {
                     // try to compile code as draw variable
                     var subvar = new JSROOT.TDrawVariable(this.globals);
                     // console.log("produce subvar with code", sub);
                     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;
         
         console.log('arriter', arriter);
         
         var indx = selector.indexOfBranch(br);
         if (indx<0) indx = selector.AddBranch(br, undefined, direct_branch);
         
         direct_branch = 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;
            return true; 
         }
         
         var replace = "arg.var" + (this.branches.length-1);
         
         code = code.substr(0, pos) + replace + code.substr(pos2);
         
         pos = pos + replace.length;
      }
      
      this.func = new Function("arg", "return (" + code + ")");
      
      return true;
   }
   
   JSROOT.TDrawVariable.prototype.IsInteger = function(selector) {
      // check if draw variable produces integer values
      // derived from type of data in the branch
      if ((this.kind !== "number") || !this.direct_branch) return false;
      
      return selector.IsInteger(this.brindex[0]);
   }
   
   JSROOT.TDrawVariable.prototype.is_dummy = function() {
      return this.branches.length === 0;
   }
   
   JSROOT.TDrawVariable.prototype.Produce = function(obj) {
      // after reading tree braches into the object, calculate variable value

      this.length = 1;
      this.isarray = false;
      
      if (this.is_dummy()) {
         this.value = 1.; // used as dummy weight variable
         this.kind = "number";
         return;
      }
      
      var arg = { globals: this.globals }, usearrlen = -1, arrs = [];
      for (var n=0;n<this.branches.length;++n) {
         var 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] = (JSROOT.CheckArrayPrototype(arg[name]) > 0) || JSROOT.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) && (JSROOT.CheckArrayPrototype(arg[name], true) === 2)) {
            // plain array, can be used as is
            arrs[n] = arg[name]; 
         } else {
            var iter = new JSROOT.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 fillied
         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 (var k=0;k<usearrlen;++k) {
            for (var 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];
   }
   
   JSROOT.TDrawVariable.prototype.get = function(indx) {
      return this.isarray ? this.value[indx] : this.value; 
   } 
   
   JSROOT.TDrawVariable.prototype.AppendArray = function(tgtarr) {
      // appeand array to the buffer
      
      this.buf = this.buf.concat(tgtarr[this.branches[0]]);
   }

   // =============================================================================

   JSROOT.TDrawSelector = function(callback) {
      JSROOT.TSelector.call(this);   
      
      this.ndim = 0;
      this.vars = []; // array of expression varibles 
      this.cut = null; // cut variable
      this.hist = null;
      this.histo_callback = callback;
      this.hist_name = "$htemp";
      this.hist_title = "Result of TTree::Draw";
      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 
   }

   JSROOT.TDrawSelector.prototype = Object.create(JSROOT.TSelector.prototype);
   
   JSROOT.TDrawSelector.prototype.ParseParameters = function(tree, args, expr) {
      
      if (!expr || (typeof expr !== "string")) return "";
      
      // parse parameters which defined at the end as expression;par1name:par1value;par2name:par2value
      var pos = expr.lastIndexOf(";");
      while (pos>0) {
         var parname = expr.substr(pos+1), parvalue = undefined;
         expr = expr.substr(0,pos);
         pos = expr.lastIndexOf(";");
         
         var separ = parname.indexOf(":");
         if (separ>0) { parvalue = parname.substr(separ+1); parname = parname.substr(0, separ);  }
         
         var intvalue = parseInt(parvalue);
         if (!parvalue || isNaN(intvalue)) intvalue = undefined;
         
         switch (parname) {
            case "num": 
            case "entries": 
            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 "mon":
            case "monitor":
               args.monitoring = (intvalue !== undefined) ? intvalue : 5000;
               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 ((this.htype!=="C") && (this.htype!=="S") && (this.htype!=="I") 
                       && (this.htype!=="F") && (this.htype!=="L") && (this.htype!=="D")) this.htype = "F";
               }
               break;
         }
      }
      
      pos = expr.lastIndexOf(">>");
      if (pos>=0) {
         var harg = expr.substr(pos+2).trim();
         expr = expr.substr(0,pos).trim();
         pos = harg.indexOf("(");
         if (pos>0) {
            this.hist_name = harg.substr(0, pos);
            harg = harg.substr(pos);
         }  
         if (harg === "dump") {
            this.dump_values = true;
            if (args.numentries===undefined) args.numentries = 10;
         } else
         if (pos<0) {
            this.hist_name = harg; 
         } else  
         if ((harg[0]=="(") && (harg[harg.length-1]==")"))  {
            harg = harg.substr(1,harg.length-2).split(",");
            var isok = true;
            for (var n=0;n<harg.length;++n) {
               harg[n] = (n%3===0) ? parseInt(harg[n]) : parseFloat(harg[n]);
               if (isNaN(harg[n])) isok = false;
            }
            if (isok) this.hist_args = harg; 
         }
      }

      return expr;
   }
  
   JSROOT.TDrawSelector.prototype.ParseDrawExpression = function(tree, args) {
      
      // parse complete expression
      var expr = this.ParseParameters(tree, args, args.expr);

      // parse option for histogram creation

      this.hist_title = "drawing '" + expr + "' from " + tree.fName;

      var pos = expr.lastIndexOf("::"), cut = "";
      if (pos>0) {
         cut = expr.substr(pos+2).trim();
         expr = expr.substr(0,pos).trim();
      }
      
      var names = expr.split(":");
      if ((names.length < 1) || (names.length > 3)) return false;

      this.ndim = names.length;

      var is_direct = !cut;

      for (var n=0;n<this.ndim;++n) {
         this.vars[n] = new JSROOT.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 JSROOT.TDrawVariable(this.globals);
      if (cut) 
         if (!this.cut.Parse(tree, this, cut)) return false;
      
      if (!this.branches.length) {
         console.log('no any branch is selected');
         return false;
      }
      
      if (is_direct) this.ProcessArrays = this.ProcessArraysFunc;
      
      this.monitoring = args.monitoring;
      
      return true;
   }
   
   JSROOT.TDrawSelector.prototype.DrawOnlyBranch = function(tree, branch, expr, args) {
      this.ndim = 1;
      
      expr = this.ParseParameters(tree, args, expr);
      
      if (expr === "dump") {
         this.dump_values = true;
         if (args.numentries===undefined) args.numentries = 10;
         expr = "";
      }

      this.vars[0] = new JSROOT.TDrawVariable(this.globals);
      if (!this.vars[0].Parse(tree, this, expr, branch, args.direct_branch)) return false;
      this.hist_title = "drawing branch '" + branch.fName + (expr ? "' expr:'" + expr : "") + "'  from " + tree.fName;
      
      this.cut = new JSROOT.TDrawVariable(this.globals);
      
      if (this.vars[0].direct_branch) this.ProcessArrays = this.ProcessArraysFunc;
      
      this.monitoring = args.monitoring;

      return true;
   }
   
   JSROOT.TDrawSelector.prototype.Begin = function(tree) {
      this.globals.entries = tree.fEntries;
      
      if (this.monitoring)
         this.lasttm = new Date().getTime();
   } 
   
   JSROOT.TDrawSelector.prototype.ShowProgress = function(value) {
      // this function should be defined not here
      
      if ((document === undefined) || (JSROOT.progress===undefined)) return;

      if ((value===undefined) || isNaN(value)) return JSROOT.progress();

      var main_box = document.createElement("p"),
          text_node = document.createTextNode("TTree draw " + Math.round((value*100)) + " %  "),
          selector = this;
      
      main_box.appendChild(text_node);
      main_box.title = "Click on element to break drawing";

      main_box.onclick = function() {
         if (++selector.break_execution<3) {
            main_box.title = "Tree draw will break after next I/O operation";
            return text_node.nodeValue = "Breaking ... ";
         }
         selector.Abort();
         JSROOT.progress();
      }

      JSROOT.progress(main_box);
   }
   
   JSROOT.TDrawSelector.prototype.GetBitsBins = function(nbits) {
      
      var res = { nbins: nbits, min: 0, max: nbits, k: 1., fLabels: JSROOT.Create("THashList") };
      
      for (var k=0;k<nbits;++k) {
         var s = JSROOT.Create("TObjString");
         s.fString = k.toString();
         s.fUniqueID = k+1;
         res.fLabels.Add(s);
      }
      return res;
   }

   JSROOT.TDrawSelector.prototype.GetMinMaxBins = function(axisid, nbins) {
      
      var res = { min: 0, max: 0, nbins: nbins, fLabels: null };
      
      if (axisid >= this.ndim) return res;
      
      var arr = this.vars[axisid].buf;
      
      if (this.vars[axisid].kind === "object") {
         // this is any object type
         var typename, similar = true, maxbits = 8;
         for (var 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)) {
               console.log('Provide special handling fot TBits');
               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);
            }
         }
         
         console.log('See object typename', typename, 'similar', similar);
      }
      
      
      if (this.vars[axisid].kind === "string") {
         res.lbls = []; // all labels
         
         for (var 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 = JSROOT.Create("THashList");
         for (var k=0;k<res.lbls.length;++k) {
            var s = JSROOT.Create("TObjString");
            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);
      } 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 {
      
         res.min = Math.min.apply(null, arr);
         res.max = Math.max.apply(null, arr);
         
         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
         if (this.vars[axisid].IsInteger(this) && (res.max-res.min >=1) && (res.max-res.min<nbins*10)) {
            res.min -= 1;
            res.max += 2;
            res.nbins = Math.round(res.max - res.min);
         } else {
            res.max += (res.max-res.min)/res.nbins;
         }
      }
      
      res.k = res.nbins/(res.max-res.min);

      res.GetBin = function(value) {
         var bin = this.lbls ? this.lbls.indexOf(value) : Math.floor((value-this.min)*this.k);
         return (bin<0) ? 0 : ((bin>this.nbins) ? this.nbins+1 : bin+1); 
      }

      return res;
   }
   
   JSROOT.TDrawSelector.prototype.CreateHistogram = function() {
      if (this.hist || !this.vars[0].buf) return;
      
      if (this.dump_values) {
         // just create array where dumped valus will be collected  
         this.hist = [];
         
         // reassign fill method
         this.Fill1DHistogram = this.Fill2DHistogram = this.Fill3DHistogram = this.DumpValue;  
      } else {
         
         this.x = this.GetMinMaxBins(0, (this.ndim > 1) ? 50 : 200);

         this.y = this.GetMinMaxBins(1, 50);

         this.z = this.GetMinMaxBins(2, 50);

         switch (this.ndim) {
            case 1: this.hist = JSROOT.CreateHistogram("TH1"+this.htype, this.x.nbins); break; 
            case 2: this.hist = JSROOT.CreateHistogram("TH2"+this.htype, this.x.nbins, this.y.nbins); break;
            case 3: this.hist = JSROOT.CreateHistogram("TH3"+this.htype, this.x.nbins, this.y.nbins, this.z.nbins); break;
         }

         this.hist.fXaxis.fTitle = this.vars[0].code;
         this.hist.fXaxis.fXmin = this.x.min;
         this.hist.fXaxis.fXmax = this.x.max;
         this.hist.fXaxis.fLabels = this.x.fLabels;

         if (this.ndim > 1) this.hist.fYaxis.fTitle = this.vars[1].code;
         this.hist.fYaxis.fXmin = this.y.min;
         this.hist.fYaxis.fXmax = this.y.max;
         this.hist.fYaxis.fLabels = this.y.fLabels;

         if (this.ndim > 2) this.hist.fZaxis.fTitle = this.vars[2].code;
         this.hist.fZaxis.fXmin = this.z.min;
         this.hist.fZaxis.fXmax = this.z.max;
         this.hist.fZaxis.fLabels = this.z.fLabels;

         this.hist.fName = this.hist_name;
         this.hist.fTitle = this.hist_title;
         this.hist.$custom_stat = (this.hist_name == "$htemp") ? 111110 : 111111;
      }
      
      var var0 = this.vars[0].buf, cut = this.cut.buf, len = var0.length; 
         
      switch (this.ndim) {
         case 1:
            for (var n=0;n<len;++n) 
               this.Fill1DHistogram(var0[n], cut ? cut[n] : 1.);
            break;
         case 2: 
            var var1 = this.vars[1].buf;
            for (var n=0;n<len;++n) 
               this.Fill2DHistogram(var0[n], var1[n], cut ? cut[n] : 1.);
            delete this.vars[1].buf;
            break;
         case 3:
            var var1 = this.vars[1].buf, var2 = this.vars[2].buf; 
            for (var n=0;n<len;++n) 
               this.Fill2DHistogram(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;
   }

   JSROOT.TDrawSelector.prototype.FillTBitsHistogram = function(xvalue, weight) {
      if (!weight || !xvalue || !xvalue.fNbits || !xvalue.fAllBits) return;
      
      var sz = Math.min(xvalue.fNbits+1, xvalue.fNbytes*8);
      
      for (var 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; }
      }
   }
   
   JSROOT.TDrawSelector.prototype.FillBitsHistogram = function(xvalue, weight) {
      if (!weight) return;
      
      for (var bit=0,mask=1;bit<this.x.nbins;++bit) {
         if (xvalue & mask) this.hist.fArray[bit+1] += weight;
         mask*=2;
      }
   }
   
   JSROOT.TDrawSelector.prototype.Fill1DHistogram = function(xvalue, weight) {
      var bin = this.x.GetBin(xvalue);
      this.hist.fArray[bin] += weight;
   }

   JSROOT.TDrawSelector.prototype.Fill2DHistogram = function(xvalue, yvalue, weight) {
      var xbin = this.x.GetBin(xvalue),
          ybin = this.y.GetBin(yvalue);
      
      this.hist.fArray[xbin+(this.x.nbins+2)*ybin] += weight;
   }

   JSROOT.TDrawSelector.prototype.Fill3DHistogram = function(xvalue, yvalue, zvalue, weight) {
      var 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;
   }
   
   JSROOT.TDrawSelector.prototype.DumpValue = function(v1, v2, v3, v4) {
      var 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);
   }
   
   JSROOT.TDrawSelector.prototype.ProcessArraysFunc = function(entry) {
      // function used when all bracnhes can be read as array
      // most typical usage - histogramming of single branch 
      
      
      if (this.arr_limit) {
         var var0 = this.vars[0], len = this.tgtarr.br0.length,
             var1 = this.vars[1], var2 = this.vars[2];
         if ((var0.buf.length===0) && (len>=this.arr_limit)) {
            // special usecase - first arraya 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 (var 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 (var0.buf.length >= this.arr_limit) {
            this.CreateHistogram();
            this.arr_limit = 0;
         }
      } else {
         var br0 = this.tgtarr.br0, len = br0.length;
         switch(this.ndim) {
            case 1:
               for (var k=0;k<len;++k)
                  this.Fill1DHistogram(br0[k], 1.);
               break;
            case 2:
               var br1 = this.tgtarr.br1;
               for (var k=0;k<len;++k) 
                  this.Fill2DHistogram(br0[k], br1[k], 1.);
               break;
            case 3:
               var br1 = this.tgtarr.br1, br2 = this.tgtarr.br2;
               for (var k=0;k<len;++k) 
                  this.Fill3DHistogram(br0[k], br1[k], br2[k], 1.);
               break;
         } 
      }
   }


   JSROOT.TDrawSelector.prototype.Process = function(entry) {
      
      this.globals.entry = entry; // can be used in any expression
      
      for (var n=0;n<this.ndim;++n)
         this.vars[n].Produce(this.tgtobj);
      
      this.cut.Produce(this.tgtobj);

      var var0 = this.vars[0], var1 = this.vars[1], var2 = this.vars[2], cut = this.cut;   

      if (this.arr_limit) {
         switch(this.ndim) {
            case 1:
              for (var n0=0;n0<var0.length;++n0) {
                 var0.buf.push(var0.get(n0));
                 cut.buf.push(cut.value);
              }
              break;
            case 2:
              for (var n0=0;n0<var0.length;++n0) 
                 for (var n1=0;n1<var1.length;++n1) {
                    var0.buf.push(var0.get(n0));
                    var1.buf.push(var1.get(n1));
                    cut.buf.push(cut.value);
                 }
              break;
            case 3:
               for (var n0=0;n0<var0.length;++n0)
                  for (var n1=0;n1<var1.length;++n1)
                     for (var n2=0;n2<var2.length;++n2) {
                        var0.buf.push(var0.get(n0));
                        var1.buf.push(var1.get(n1));
                        var2.buf.push(var2.get(n2));
                        cut.buf.push(cut.value);
                     }
               break;
         }
         if (var0.buf.length >= this.arr_limit) {
            this.CreateHistogram();
            this.arr_limit = 0;
         }
      } else
      if (this.hist) {
         switch(this.ndim) {
            case 1:
               for (var n0=0;n0<var0.length;++n0)
                  this.Fill1DHistogram(var0.get(n0), cut.value);
               break;
            case 2:
               for (var n0=0;n0<var0.length;++n0)
                  for (var n1=0;n1<var1.length;++n1)
                     this.Fill2DHistogram(var0.get(n0), var1.get(n1), cut.value);
               break;
            case 3:
               for (var n0=0;n0<var0.length;++n0)
                  for (var n1=0;n1<var1.length;++n1)
                     for (var n2=0;n2<var2.length;++n2)
                        this.Fill3DHistogram(var0.get(n0), var1.get(n1), var2.get(n2), cut.value);
               break;
         } 
      }
      
      if (this.monitoring && this.hist && !this.dump_values) {
         var now = new Date().getTime();
         if (now - this.lasttm > this.monitoring) { 
            this.lasttm = now;
            var drawopt = (this.ndim==2) ? "col" : "";
            JSROOT.CallBack(this.histo_callback, this.hist, drawopt, true);
         }
      }
   }
   
   JSROOT.TDrawSelector.prototype.Terminate = function(res) {
      if (res && !this.hist) this.CreateHistogram();
      
      this.ShowProgress();
      
      var drawopt = (this.ndim==2) ? "col" : "";
      if (this.dump_values) drawopt = "inspect";
      
      return JSROOT.CallBack(this.histo_callback, this.hist, drawopt);
   }
   
   // ======================================================================
   
   /** @namespace JSROOT.TreeMethods */
   JSROOT.TreeMethods = {}; // these are only TTree methods, which are automatically assigned to every TTree 

   /** @memberOf JSROOT.TreeMethods  */
   JSROOT.TreeMethods.Process = function(selector, args) {
      // function similar to the TTree::Process
      
      if (!args) args = {};
      
      if (!selector || !this.$file || !selector.branches) {
         console.error('required parameter missing for TTree::Process');
         if (selector) selector.Terminate(false);
         return false;
      }
      
      // central handle with all information required for reading
      var handle = {
          tree: this, // keep tree reference  
          file: this.$file, // keep file reference
          selector: selector, // reference on selector  
          arr: [], // list of branches 
          curr: -1,  // current entry ID
          current_entry: -1, // current processed entry
          simple_read: true, // all baskets in all used branches are in sync,
          process_arrays: true // one can process all branches as arrays
      };
      
      var namecnt = 0;
      
      function CreateLeafElem(leaf, name) {
         // function creates TStreamerElement which corresponds to the elementary leaf
         var datakind = 0;
         switch (leaf._typename) {
            case 'TLeafF': datakind = JSROOT.IO.kFloat; break;
            case 'TLeafD': datakind = JSROOT.IO.kDouble; break;
            case 'TLeafO': datakind = JSROOT.IO.kBool; break;
            case 'TLeafB': datakind = leaf.fIsUnsigned ? JSROOT.IO.kUChar : JSROOT.IO.kChar; break;
            case 'TLeafS': datakind = leaf.fIsUnsigned ? JSROOT.IO.kUShort : JSROOT.IO.kShort; break;
            case 'TLeafI': datakind = leaf.fIsUnsigned ? JSROOT.IO.kUInt : JSROOT.IO.kInt; break;
            case 'TLeafL': datakind = leaf.fIsUnsigned ? JSROOT.IO.kULong64 : JSROOT.IO.kLong64; break;
            case 'TLeafC': datakind = JSROOT.IO.kTString; break;
            default: return null;
         }
         var elem = JSROOT.IO.CreateStreamerElement(name || leaf.fName, "int");
         elem.fType = datakind;
         return elem;
      }

      function FindInHandle(branch) {
         for (var k=0;k<handle.arr.length;++k)
            if (handle.arr[k].branch === branch) return handle.arr[k];
         return null;
      }

      function AddBranchForReading(branch, target_object, target_name, is_direct) {
         // central method to add branch for reading
         // is_direct == true - read only this branch
         // is_direct == '$child$' is just member of object from for STL or clonesarray
         // is_direct == '<any class name>' is sub-object from STL or clonesarray, happens when such new object need to be created 

         if (typeof branch === 'string')
            branch = handle.tree.FindBranch(branch);
         
         if (!branch) { console.error('Did not found branch'); return null; }
         
         var item = FindInHandle(branch);
         
         if (item) {
            console.error('Branch already configured for reading', branch.fName);
            if (item.tgt !== target_object) console.error('Target object differs');
            return elem;
         }
         
         if (!branch.fEntries) {
            console.log('Branch ', branch.fName, ' does not have entries');
            return null;
         } 
         
         item = {
               branch: branch,
               tgt: target_object, // used target object - can be differ for object members
               name: target_name,
               index: -1, // index in the list of read branches
               member: null, // member to read branch
               type: 0, // keep type identifier
               curr_entry: -1, // last processed entry
               raw : null, // raw buffer for reading
               curr_basket: 0,  // number of basket used for processing
               read_entry: -1,  // last entry which is already read 
               staged_entry: -1, // entry which is staged for reading
               first_readentry: -1, // first entry to read
               staged_basket: 0,  // last basket staged for reading
               numentries: branch.fEntries,
               numbaskets: branch.fWriteBasket, // number of baskets which can be read from the file
               counters: null, // branch indexes used as counters
               ascounter: [], // list of other branches using that branch as counter 
               baskets: [] // array for read baskets,
         };

         // check all counters if we 
         var item_cnt = null, item_cnt2 = null;
         
         if (branch.fBranchCount) {
            
            item_cnt = FindInHandle(branch.fBranchCount);
            
            if (!item_cnt) item_cnt = AddBranchForReading(branch.fBranchCount, target_object, "$counter" + namecnt++, true); 
            
            if (!item_cnt) { console.error('Cannot add counter branch', branch.fBranchCount.fName); return null; }

            var BranchCount2 = branch.fBranchCount2;
            
            if (!BranchCount2 && (branch.fBranchCount.fStreamerType===JSROOT.IO.kSTL) && 
                ((branch.fStreamerType === JSROOT.IO.kStreamLoop) || (branch.fStreamerType === JSROOT.IO.kOffsetL+JSROOT.IO.kStreamLoop))) {
                 // special case when count member from kStreamLoop not assigned as fBranchCount2  
                 var s_i = handle.file.FindStreamerInfo(branch.fClassName,  branch.fClassVersion, branch.fCheckSum),
                     elem = s_i ? s_i.fElements.arr[branch.fID] : null,
                     arr = branch.fBranchCount.fBranches.arr  ;

                 if (elem && elem.fCountName && arr) 
                    for(var k=0;k<arr.length;++k) 
                       if (arr[k].fName === branch.fBranchCount.fName + "." + elem.fCountName) {
                          BranchCount2 = arr[k];
                          break;
                       }

                 if (!BranchCount2) console.error('Did not found branch for second counter of kStreamLoop element');
              }
            
            if (BranchCount2) {
               item_cnt2 = FindInHandle(BranchCount2);
               
               if (!item_cnt2) item_cnt2 = AddBranchForReading(BranchCount2, target_object, "$counter" + namecnt++, true); 
               
               if (!item_cnt2) { console.error('Cannot add counter branch2', BranchCount2.fName); return null; }
            }
         }
         
         var nb_branches = branch.fBranches ? branch.fBranches.arr.length : 0,
             nb_leaves = branch.fLeaves ? branch.fLeaves.arr.length : 0,
             leaf = (nb_leaves>0) ? branch.fLeaves.arr[0] : null,
             elem = null, // TStreamerElement used to create reader 
             member = null, // member for actual reading of the branch
             is_brelem = (branch._typename==="TBranchElement"),
             child_scan = 0; // scan child branches after main branch is appended
             
          function ScanBranches(lst, master_target, chld_kind) {
             if (!lst || !lst.arr.length) return true;

             for (var k=0;k<lst.arr.length;++k) {
                var br = lst.arr[k];
                if ((chld_kind>0) && (br.fType!==chld_kind)) {
                   console.log('Child type differ from expected', br.fType, chld_kind);
                   continue;
                }
                
                if (br.fType === JSROOT.BranchType.kBaseClassNode) {
                   if (!ScanBranches(br.fBranches, master_target, chld_kind)) return false;
                   continue;
                }
                
                var subname = br.fName, chld_direct = 1; 
                
                if (branch.fType === JSROOT.BranchType.kObjectNode) {
                   if (br.fName.indexOf(branch.fName + ".")!==0) {
                      console.warn('Not expected branch name ', br.fName, 'for master', branch.fName);
                      continue;
                   }
                   subname = br.fName.substr(branch.fName.length+1);
                }
                
                var p = subname.indexOf('['); 
                if (p>0) subname = subname.substr(0,p);

                if (chld_kind > 0) {
                   chld_direct = "$child$";
                   var pp = subname.indexOf(".");
                   if (pp>0) {
                      chld_direct = br.fClassName || "TObject";
                      var prefix = branch.fName + "." + subname.substr(0,pp+1);
                      for (var kk=k+1;kk<lst.arr.length;++kk)
                         if ((lst.arr[kk].fName.indexOf(prefix)===0) && lst.arr[kk].fClassName) chld_direct = lst.arr[kk].fClassName;
                   }
                }

                console.log('Add branch', branch.fName, 'target', subname, chld_direct);

                if (!AddBranchForReading(br, master_target, subname, chld_direct)) return false;
             }
             return true;
          }

          if (is_brelem && (branch.fType === JSROOT.BranchType.kObjectNode)) {
             
             if (is_direct === true) {
                console.log('kObjectNode does not have data to be readed directly');
                return null;
             }
             
             handle.process_arrays = false;
             
             // object where all sub-branches will be collected
             var tgt = target_object[target_name] = { _typename: "TObject" };

             var s_i = handle.file.FindStreamerInfo(branch.fClassName, branch.fClassVersion, branch.fCheckSum),
                 s_elem = s_i ? s_i.fElements.arr[branch.fID] : null;
             
             if (s_elem && s_elem.fType === JSROOT.IO.kObject) {
                tgt._typename = s_elem.fTypeName;
                console.log('Reconstruct object of type', tgt, s_elem.fTypeName);
             }
             if (!ScanBranches(branch.fBranches, tgt,  0)) return null;
             
             return item; // this kind of branch does not have baskets and not need to be read
         }
          
          if (is_brelem && (branch.fType === JSROOT.BranchType.kLeafNode) && (branch.fID===-2) && (branch.fStreamerType===-1)) {
             if (is_direct === true) {
                console.log('Object branch does not have data to be readed directly');
                return null;
             }
             
             handle.process_arrays = false;
             
             // object where all sub-branches will be collected
             var tgt = target_object[target_name] = { _typename: branch.fClassName };
             if (!ScanBranches(branch.fBranches, tgt,  0)) return null;
             return item; // this kind of branch does not have baskets and not need to be read
          }
          
         if (is_brelem && ((branch.fType === JSROOT.BranchType.kClonesNode) || (branch.fType === JSROOT.BranchType.kSTLNode))) {

            elem = JSROOT.IO.CreateStreamerElement(target_name, "int");

            if (!is_direct) {
               handle.process_arrays = false;
               
               member = {
                  name: target_name,
                  conttype: branch.fClonesName || "TObject",
                  func: function(buf,obj) {
                     var size = buf.ntoi4(), n = 0;
                     obj[this.name] = new Array(size); // can one reuse memory later ???
                     while (n<size) obj[this.name][n++] = { _typename: this.conttype }; // create new objects
                  }
               }
               
               child_scan = (branch.fType === JSROOT.BranchType.kClonesNode) ? JSROOT.BranchType.kClonesMemberNode : JSROOT.BranchType.kSTLMemberNode; 
            }
          } else
       
          if (is_brelem && (nb_leaves === 1) && (leaf.fName === branch.fName) && (branch.fID==-1)) {

             elem = JSROOT.IO.CreateStreamerElement(target_name, branch.fClassName);
             
             console.log('TBranchElement with ID==-1 typename ', branch.fClassName, 'type', elem.fType);
             
             if (elem.fType === JSROOT.IO.kAny) {
                
{'fraction_non_alphanumeric': 0.13836555055193048, 'fraction_numerical': 0.011967854261761071, 'mean_word_length': 1.6622988255763376, 'pattern_counts': {'":': 48, '<': 141, '<?xml version=': 0, '>': 79, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6679907', 'n_tokens_mistral': 30590, 'n_tokens_neox': 30143, 'n_words': 6603}