otsdaq_utilities  v2_05_02_indev
JSRootPainter.v7hist.js
1 
4 (function( factory ) {
5  if ( typeof define === "function" && define.amd ) {
6  define( ['JSRootPainter', 'd3'], factory );
7  } else if (typeof exports === 'object' && typeof module !== 'undefined') {
8  factory(require("./JSRootCore.js"), require("d3"));
9  } else {
10  if (typeof d3 != 'object')
11  throw new Error('This extension requires d3.js', 'JSRootPainter.v7hist.js');
12  if (typeof JSROOT == 'undefined')
13  throw new Error('JSROOT is not defined', 'JSRootPainter.v7hist.js');
14  if (typeof JSROOT.Painter != 'object')
15  throw new Error('JSROOT.Painter not defined', 'JSRootPainter.v7hist.js');
16  factory(JSROOT, d3);
17  }
18 } (function(JSROOT, d3) {
19 
20  "use strict";
21 
22  JSROOT.sources.push("v7hist");
23 
24 
25  // =============================================================
26 
27  function THistPainter(histo) {
28  JSROOT.TObjectPainter.call(this, histo);
29  this.csstype = "hist";
30  this.draw_content = true;
31  this.nbinsx = 0;
32  this.nbinsy = 0;
33  this.accept_drops = true; // indicate that one can drop other objects like doing Draw("same")
34  this.mode3d = false;
35  this.zoom_changed_interactive = 0;
36  }
37 
38  THistPainter.prototype = Object.create(JSROOT.TObjectPainter.prototype);
39 
40  // function ensure that frame is drawn on the canvas
41  THistPainter.prototype.PrepareFrame = function(divid) {
42  this.SetDivId(divid, -1);
43 
44  if (!this.frame_painter()) {
45  var pad = this.root_pad(),
46  fr = pad ? pad.fFrame : null;
47  JSROOT.v7.drawFrame(divid, fr);
48  }
49 
50  this.SetDivId(divid, 1);
51  }
52 
53  THistPainter.prototype.GetHImpl = function(obj) {
54  if (obj && obj.fHistImpl)
55  return obj.fHistImpl.fIO;
56  return null;
57  }
58 
59  THistPainter.prototype.GetHisto = function() {
60  var obj = this.GetObject(), histo = this.GetHImpl(obj);
61 
62  if (histo && !histo.getBinContent) {
63  console.log('histo type', histo._typename);
64  if (histo.fAxes._1) {
65  histo.getBin = function(x, y) { return (x + this.fAxes._0.fNBins * y); }
66  histo.getBinContent = function(x, y) { return this.fStatistics.fBinContent[this.getBin(x, y)]; }
67  histo.getBinError = function(x,y) {
68  var bin = this.getBin(x,y);
69  if (this.fStatistics.fSumWeightsSquared)
70  return Math.sqrt(this.fStatistics.fSumWeightsSquared[bin]);
71  return Math.sqrt(Math.abs(this.fStatistics.fBinContent[bin]));
72  }
73  } else {
74 
75  histo.getBinContent = function(bin) {
76  return this.fStatistics.fBinContent[bin];
77  }
78  histo.getBinError = function(bin) {
79  if (this.fStatistics.fSumWeightsSquared)
80  return Math.sqrt(this.fStatistics.fSumWeightsSquared[bin]);
81  return Math.sqrt(Math.abs(this.getBinContent(bin)));
82  }
83  }
84  }
85 
86  return histo;
87  }
88 
89  THistPainter.prototype.IsTProfile = function() {
90  return false;
91  }
92 
93  THistPainter.prototype.IsTH1K = function() {
94  return false;
95  }
96 
97  THistPainter.prototype.IsTH2Poly = function() {
98  return false;
99  }
100 
101  THistPainter.prototype.DecodeOptions = function(opt) {
102  if (!this.options) this.options = { Hist : 1 };
103  }
104 
105  THistPainter.prototype.Clear3DScene = function() {
106  var fp = this.frame_painter();
107  if (fp && typeof fp.Create3DScene === 'function')
108  fp.Create3DScene(-1);
109  this.mode3d = false;
110  }
111 
112  THistPainter.prototype.Cleanup = function() {
113 
114  // clear all 3D buffers
115  this.Clear3DScene();
116 
117  delete this.fPalette;
118  delete this.fContour;
119  delete this.options;
120 
121  JSROOT.TObjectPainter.prototype.Cleanup.call(this);
122  }
123 
124  THistPainter.prototype.Dimension = function() {
125  return 1;
126  }
127 
128  THistPainter.prototype.ScanContent = function(when_axis_changed) {
129  // function will be called once new histogram or
130  // new histogram content is assigned
131  // one should find min,max,nbins, maxcontent values
132  // if when_axis_changed === true specified, content will be scanned after axis zoom changed
133 
134  alert("HistPainter.prototype.ScanContent not implemented");
135  }
136 
137  THistPainter.prototype.DrawAxes = function() {
138  // return true when axes was drawn
139  var main = this.frame_painter();
140  if (!main) return false;
141 
142  if (this.is_main_painter() && this.draw_content) {
143  main.CleanupAxes();
144  main.xmin = main.xmax = 0;
145  main.ymin = main.ymax = 0;
146  main.zmin = main.zmax = 0;
147  main.SetAxesRanges(this.xmin, this.xmax, this.ymin, this.ymax);
148  }
149 
150  return main.DrawAxes(true);
151  }
152 
153  THistPainter.prototype.CheckHistDrawAttributes = function() {
154 
155 /* if (this.options._pfc || this.options._plc || this.options._pmc) {
156  if (!this.pallette && JSROOT.Painter.GetColorPalette)
157  this.palette = JSROOT.Painter.GetColorPalette();
158 
159  var pp = this.pad_painter();
160  if (this.palette && pp) {
161  var icolor = pp.GetAutoColor(this);
162 
163  if (this.options._pfc) { this.histo.fFillColor = icolor; delete this.fillatt; }
164  if (this.options._plc) { this.histo.fLineColor = icolor; delete this.lineatt; }
165  if (this.options._pmc) { this.histo.fMarkerColor = icolor; delete this.markeratt; }
166  }
167 
168  this.options._pfc = this.options._plc = this.options._pmc = false;
169  }
170 */
171 
172  this.createAttFill( { pattern: 0, color: 0 });
173 
174  var lcol = this.v7EvalColor( "line_color", "black"),
175  lwidth = this.v7EvalAttr( "line_width", 1);
176 
177  this.createAttLine({ color: lcol || 'black', width : parseInt(lwidth) || 1 });
178  }
179 
180  THistPainter.prototype.UpdateObject = function(obj, opt) {
181 
182  var origin = this.GetObject();
183 
184  if (obj !== origin) {
185 
186  if (!this.MatchObjectType(obj)) return false;
187 
188  var horigin = this.GetHImpl(origin),
189  hobj = this.GetHImpl(obj);
190 
191  if (!horigin || !hobj) return false;
192 
193  // make it easy - copy statistics without axes
194  horigin.fStatistics = hobj.fStatistics;
195 
196  // special tratement for webcanvas - also name can be changed
197  // if (this.snapid !== undefined)
198  // histo.fName = obj.fName;
199 
200  // histo.fTitle = obj.fTitle;
201  }
202 
203  this.ScanContent();
204 
205  this.histogram_updated = true; // indicate that object updated
206 
207  return true;
208  }
209 
210  THistPainter.prototype.GetAxis = function(name) {
211  var histo = this.GetHisto();
212  if (!histo || !histo.fAxes) return null;
213 
214  var axis = histo.fAxes._0;
215 
216  switch(name) {
217  case "x": axis = histo.fAxes._0; break;
218  case "y": axis = histo.fAxes._1; break;
219  case "z": axis = histo.fAxes._2; break;
220  }
221  if (!axis || axis.GetBinCoord) return axis;
222 
223  if (axis._typename == "ROOT::Experimental::RAxisEquidistant") {
224  axis.min = axis.fLow;
225  axis.max = axis.fLow + (axis.fNBins-2)/axis.fInvBinWidth;
226 
227  axis.GetBinCoord = function(bin) { return this.fLow + bin/this.fInvBinWidth; };
228  axis.FindBin = function(x,add) { return Math.floor((x - this.fLow)*this.fInvBinWidth + add); };
229 
230  } else {
231  axis.min = axis.fBinBorders[0];
232  axis.max = axis.fBinBorders[axis.fNBins - 2];
233  axis.GetBinCoord = function(bin) {
234  var indx = Math.round(bin);
235  if (indx <= 0) return this.fBinBorders[0];
236  if (indx > this.fNBins - 2) return this.fBinBorders[this.fNBins - 2];
237  if (indx==bin) return this.fBinBorders[indx];
238  var indx2 = (bin < indx) ? indx - 1 : indx + 1;
239  return this.fBinBorders[indx] * Math.abs(bin-indx2) + this.fBinBorders[indx2] * Math.abs(bin-indx);
240  };
241  axis.FindBin = function(x,add) {
242  for (var k = 1; k < this.fBinBorders.length; ++k)
243  if (x < this.fBinBorders[k]) return Math.floor(k-1+add);
244  return this.fNBins - 2;
245  };
246  }
247 
248  return axis;
249  }
250 
251  THistPainter.prototype.CreateAxisFuncs = function(with_y_axis, with_z_axis) {
252  // here functions are defined to convert index to axis value and back
253  // introduced to support non-equidistant bins
254 
255  var histo = this.GetHisto();
256  if (!histo) return;
257 
258  var axis = this.GetAxis("x");
259  this.xmin = axis.min;
260  this.xmax = axis.max;
261 
262  if (!with_y_axis || !this.nbinsy) return;
263 
264  axis = this.GetAxis("y");
265  this.ymin = axis.min;
266  this.ymax = axis.max;
267  if (!with_z_axis || !this.nbinsz) return;
268 
269  axis = this.GetAxis("z");
270  this.zmin = axis.min;
271  this.zmax = axis.max;
272  }
273 
274  THistPainter.prototype.AddInteractive = function() {
275  // only first painter in list allowed to add interactive functionality to the frame
276 
277  if (this.is_main_painter()) {
278  var fp = this.frame_painter();
279  if (fp) fp.AddInteractive();
280  }
281  }
282 
283 
284  THistPainter.prototype.DrawBins = function() {
285  alert("HistPainter.DrawBins not implemented");
286  }
287 
288  THistPainter.prototype.ToggleTitle = function(arg) {
289  return false;
290  }
291 
292  THistPainter.prototype.DrawTitle = function() {
293  }
294 
295  THistPainter.prototype.UpdateStatWebCanvas = function() {
296  }
297 
298  THistPainter.prototype.ToggleStat = function(arg) {
299  }
300 
301  THistPainter.prototype.GetSelectIndex = function(axis, size, add) {
302  // be aware - here indexes starts from 0
303  var indx = 0,
304  main = this.frame_painter(),
305  taxis = this.GetAxis(axis),
306  nbins = this['nbins'+axis] || 0,
307  min = main ? main['zoom_' + axis + 'min'] : 0,
308  max = main ? main['zoom_' + axis + 'max'] : 0;
309 
310  if ((min !== max) && taxis) {
311  if (size == "left")
312  indx = taxis.FindBin(min, add || 0);
313  else
314  indx = taxis.FindBin(max, (add || 0) + 0.5);
315  if (indx<0) indx = 0; else if (indx>nbins) indx = nbins;
316  } else {
317  indx = (size == "left") ? 0 : nbins;
318  }
319 
320  return indx;
321  }
322 
323  THistPainter.prototype.FindStat = function() {
324  return null;
325  }
326 
327  THistPainter.prototype.IgnoreStatsFill = function() {
328  return true;
329  }
330 
331  THistPainter.prototype.CreateStat = function(force) {
332  return null;
333  }
334 
335  THistPainter.prototype.ButtonClick = function(funcname) {
336  // TODO: move to frame painter
337  switch(funcname) {
338  case "ToggleZoom":
339  if ((this.zoom_xmin !== this.zoom_xmax) || (this.zoom_ymin !== this.zoom_ymax) || (this.zoom_zmin !== this.zoom_zmax)) {
340  this.Unzoom();
341  var fp = this.frame_painter(); if (fp) fp.zoom_changed_interactive = 0;
342  return true;
343  }
344  if (this.draw_content && (typeof this.AutoZoom === 'function')) {
345  this.AutoZoom();
346  return true;
347  }
348  break;
349  case "ToggleLogX": this.frame_painter().ToggleLog("x"); break;
350  case "ToggleLogY": this.frame_painter().ToggleLog("y"); break;
351  case "ToggleLogZ": this.frame_painter().ToggleLog("z"); break;
352  case "ToggleStatBox": this.ToggleStat(); return true; break;
353  }
354  return false;
355  }
356 
357  THistPainter.prototype.FillToolbar = function(not_shown) {
358  var pp = this.pad_painter();
359  if (!pp) return;
360 
361  pp.AddButton(JSROOT.ToolbarIcons.auto_zoom, 'Toggle between unzoom and autozoom-in', 'ToggleZoom', "Ctrl *");
362  pp.AddButton(JSROOT.ToolbarIcons.arrow_right, "Toggle log x", "ToggleLogX", "PageDown");
363  pp.AddButton(JSROOT.ToolbarIcons.arrow_up, "Toggle log y", "ToggleLogY", "PageUp");
364  if (this.Dimension() > 1)
365  pp.AddButton(JSROOT.ToolbarIcons.arrow_diag, "Toggle log z", "ToggleLogZ");
366  if (this.draw_content)
367  pp.AddButton(JSROOT.ToolbarIcons.statbox, 'Toggle stat box', "ToggleStatBox");
368  if (!not_shown) pp.ShowButtons();
369  }
370 
371  THistPainter.prototype.Get3DToolTip = function(indx) {
372  var histo = this.GetHisto(),
373  tip = { bin: indx, name: histo.fName || "histo", title: histo.fTitle };
374  switch (this.Dimension()) {
375  case 1:
376  tip.ix = indx; tip.iy = 1;
377  tip.value = histo.getBinContent(tip.ix);
378  tip.error = histo.getBinError(indx);
379  tip.lines = this.GetBinTips(indx-1);
380  break;
381  case 2:
382  tip.ix = indx % (this.nbinsx + 2);
383  tip.iy = (indx - tip.ix) / (this.nbinsx + 2);
384  tip.value = histo.getBinContent(tip.ix, tip.iy);
385  tip.error = histo.getBinError(indx);
386  tip.lines = this.GetBinTips(tip.ix-1, tip.iy-1);
387  break;
388  case 3:
389  tip.ix = indx % (this.nbinsx+2);
390  tip.iy = ((indx - tip.ix) / (this.nbinsx+2)) % (this.nbinsy+2);
391  tip.iz = (indx - tip.ix - tip.iy * (this.nbinsx+2)) / (this.nbinsx+2) / (this.nbinsy+2);
392  tip.value = this.GetObject().getBinContent(tip.ix, tip.iy, tip.iz);
393  tip.error = histo.getBinError(indx);
394  tip.lines = this.GetBinTips(tip.ix-1, tip.iy-1, tip.iz-1);
395  break;
396  }
397 
398  return tip;
399  }
400 
401  THistPainter.prototype.CreateContour = function(nlevels, zmin, zmax, zminpositive) {
402 
403  if (nlevels<1) nlevels = JSROOT.gStyle.fNumberContours;
404  this.fContour = [];
405  this.colzmin = zmin;
406  this.colzmax = zmax;
407 
408  if (this.root_pad().fLogz) {
409  if (this.colzmax <= 0) this.colzmax = 1.;
410  if (this.colzmin <= 0)
411  if ((zminpositive===undefined) || (zminpositive <= 0))
412  this.colzmin = 0.0001*this.colzmax;
413  else
414  this.colzmin = ((zminpositive < 3) || (zminpositive>100)) ? 0.3*zminpositive : 1;
415  if (this.colzmin >= this.colzmax) this.colzmin = 0.0001*this.colzmax;
416 
417  var logmin = Math.log(this.colzmin)/Math.log(10),
418  logmax = Math.log(this.colzmax)/Math.log(10),
419  dz = (logmax-logmin)/nlevels;
420  this.fContour.push(this.colzmin);
421  for (var level=1; level<nlevels; level++)
422  this.fContour.push(Math.exp((logmin + dz*level)*Math.log(10)));
423  this.fContour.push(this.colzmax);
424  this.fCustomContour = true;
425  } else {
426  if ((this.colzmin === this.colzmax) && (this.colzmin !== 0)) {
427  this.colzmax += 0.01*Math.abs(this.colzmax);
428  this.colzmin -= 0.01*Math.abs(this.colzmin);
429  }
430  var dz = (this.colzmax-this.colzmin)/nlevels;
431  for (var level=0; level<=nlevels; level++)
432  this.fContour.push(this.colzmin + dz*level);
433  }
434 
435  var main = this.frame_painter();
436 
437  if (this.Dimension() < 3) {
438  main.zmin = this.zmin = this.colzmin;
439  main.zmax = this.zmax = this.colzmax;
440  }
441 
442  main.fContour = this.fContour;
443  main.fCustomContour = this.fCustomContour;
444  main.colzmin = this.colzmin;
445  main.colzmax = this.colzmax;
446 
447  return this.fContour;
448  }
449 
450  THistPainter.prototype.GetContour = function() {
451  if (this.fContour) return this.fContour;
452 
453  var main = this.frame_painter();
454  if (main && main.fContour) {
455  this.fContour = main.fContour;
456  this.fCustomContour = main.fCustomContour;
457  this.colzmin = main.colzmin;
458  this.colzmax = main.colzmax;
459  return this.fContour;
460  }
461 
462  // if not initialized, first create contour array
463  // difference from ROOT - fContour includes also last element with maxbin, which makes easier to build logz
464  var histo = this.GetHisto(), nlevels = JSROOT.gStyle.fNumberContours,
465  zmin = this.minbin, zmax = this.maxbin, zminpos = this.minposbin;
466  if (zmin === zmax) { zmin = this.gminbin; zmax = this.gmaxbin; zminpos = this.gminposbin }
467  //if (histo.fContour) nlevels = histo.fContour.length;
468  //if ((this.options.minimum !== -1111) && (this.options.maximum != -1111)) {
469  // zmin = this.options.minimum;
470  // zmax = this.options.maximum;
471  //}
472  if (main.zoom_zmin != main.zoom_zmax) {
473  zmin = main.zoom_zmin;
474  zmax = main.zoom_zmax;
475  }
476 
477  //if (histo.fContour && (histo.fContour.length>1) && histo.TestBit(JSROOT.TH1StatusBits.kUserContour)) {
478  // this.fContour = JSROOT.clone(histo.fContour);
479  // this.fCustomContour = true;
480  // this.colzmin = zmin;
481  // this.colzmax = zmax;
482  // if (zmax > this.fContour[this.fContour.length-1]) this.fContour.push(zmax);
483  // if (this.Dimension()<3) {
484  // this.zmin = this.colzmin;
485  // this.zmax = this.colzmax;
486  // }
487  // return this.fContour;
488  //}
489 
490  this.fCustomContour = false;
491 
492  return this.CreateContour(nlevels, zmin, zmax, zminpos);
493  }
494 
495  THistPainter.prototype.FillContextMenu = function(menu) {
496 
497  var histo = this.GetHisto();
498 
499  menu.add("header:v7histo::anyname");
500 
501  if (this.draw_content) {
502  menu.addchk(this.ToggleStat('only-check'), "Show statbox", function() { this.ToggleStat(); });
503 
504  if (typeof this.FillHistContextMenu == 'function')
505  this.FillHistContextMenu(menu);
506  }
507 
508  if (this.options.Mode3D) {
509  // menu for 3D drawings
510 
511  if (menu.size() > 0)
512  menu.add("separator");
513 
514  var main = this.main_painter() || this,
515  fp = this.frame_painter(),
516  axis_painter = fp;
517 
518  menu.addchk(main.IsTooltipAllowed(), 'Show tooltips', function() {
519  main.SetTooltipAllowed("toggle");
520  });
521 
522  menu.addchk(axis_painter.enable_highlight, 'Highlight bins', function() {
523  axis_painter.enable_highlight = !axis_painter.enable_highlight;
524  if (!axis_painter.enable_highlight && main.BinHighlight3D && main.mode3d) main.BinHighlight3D(null);
525  });
526 
527  if (fp && fp.Render3D) {
528  menu.addchk(main.options.FrontBox, 'Front box', function() {
529  main.options.FrontBox = !main.options.FrontBox;
530  fp.Render3D();
531  });
532  menu.addchk(main.options.BackBox, 'Back box', function() {
533  main.options.BackBox = !main.options.BackBox;
534  fp.Render3D();
535  });
536  }
537 
538  if (this.draw_content) {
539  menu.addchk(!this.options.Zero, 'Suppress zeros', function() {
540  this.options.Zero = !this.options.Zero;
541  this.RedrawPad();
542  });
543 
544  if ((this.options.Lego==12) || (this.options.Lego==14)) {
545  menu.addchk(this.options.Zscale, "Z scale", function() {
546  this.ToggleColz();
547  });
548  if (this.FillPaletteMenu) this.FillPaletteMenu(menu);
549  }
550  }
551 
552  if (main.control && typeof main.control.reset === 'function')
553  menu.add('Reset camera', function() {
554  main.control.reset();
555  });
556  }
557 
558  this.FillAttContextMenu(menu);
559 
560  if (this.histogram_updated && this.zoom_changed_interactive)
561  menu.add('Let update zoom', function() {
562  this.zoom_changed_interactive = 0;
563  });
564 
565  return true;
566  }
567 
568 
570  THistPainter.prototype.getContourIndex = function(zc) {
571 
572  var cntr = this.GetContour();
573 
574  if (this.fCustomContour) {
575  var l = 0, r = cntr.length-1, mid;
576  if (zc < cntr[0]) return -1;
577  if (zc >= cntr[r]) return r;
578  while (l < r-1) {
579  mid = Math.round((l+r)/2);
580  if (cntr[mid] > zc) r = mid; else l = mid;
581  }
582  return l;
583  }
584 
585  // bins less than zmin not drawn
586  if (zc < this.colzmin) return this.options.Zero ? -1 : 0;
587 
588  // if bin content exactly zmin, draw it when col0 specified or when content is positive
589  if (zc===this.colzmin) return ((this.colzmin != 0) || !this.options.Zero || this.IsTH2Poly()) ? 0 : -1;
590 
591  return Math.floor(0.01+(zc-this.colzmin)*(cntr.length-1)/(this.colzmax-this.colzmin));
592  }
593 
596  THistPainter.prototype.getContourColor = function(zc, asindx) {
597  var zindx = this.getContourIndex(zc);
598  if (zindx < 0) return null;
599 
600  var cntr = this.GetContour(),
601  palette = this.GetPalette(),
602  indx = palette.calcColorIndex(zindx, cntr.length);
603 
604  return asindx ? indx : palette.getColor(indx);
605  }
606 
607  THistPainter.prototype.GetPalette = function(force) {
608  if (!this.fPalette || force)
609  this.fPalette = this.get_palette(true, this.options.Palette);
610  return this.fPalette;
611  }
612 
613  THistPainter.prototype.FillPaletteMenu = function(menu) {
614 
615  var curr = this.options.Palette, hpainter = this;
616  if ((curr===null) || (curr===0)) curr = JSROOT.gStyle.Palette;
617 
618  function change(arg) {
619  hpainter.options.Palette = parseInt(arg);
620  hpainter.GetPalette(true);
621  hpainter.Redraw(); // redraw histogram
622  };
623 
624  function add(id, name, more) {
625  menu.addchk((id===curr) || more, '<nobr>' + name + '</nobr>', id, change);
626  };
627 
628  menu.add("sub:Palette");
629 
630  add(50, "ROOT 5", (curr>=10) && (curr<51));
631  add(51, "Deep Sea");
632  add(52, "Grayscale", (curr>0) && (curr<10));
633  add(53, "Dark body radiator");
634  add(54, "Two-color hue");
635  add(55, "Rainbow");
636  add(56, "Inverted dark body radiator");
637  add(57, "Bird", (curr>112));
638  add(58, "Cubehelix");
639  add(59, "Green Red Violet");
640  add(60, "Blue Red Yellow");
641  add(61, "Ocean");
642  add(62, "Color Printable On Grey");
643  add(63, "Alpine");
644  add(64, "Aquamarine");
645  add(65, "Army");
646  add(66, "Atlantic");
647 
648  menu.add("endsub:");
649  }
650 
651  THistPainter.prototype.DrawColorPalette = function(enabled, postpone_draw, can_move) {
652  // only when create new palette, one could change frame size
653 
654  return null;
655  }
656 
657  THistPainter.prototype.ToggleColz = function() {
658  var can_toggle = this.options.Mode3D ? (this.options.Lego === 12 || this.options.Lego === 14 || this.options.Surf === 11 || this.options.Surf === 12) :
659  this.options.Color || this.options.Contour;
660 
661  if (can_toggle) {
662  this.options.Zscale = !this.options.Zscale;
663  this.DrawColorPalette(this.options.Zscale, false, true);
664  }
665  }
666 
667  THistPainter.prototype.ToggleMode3D = function() {
668  this.options.Mode3D = !this.options.Mode3D;
669 
670  if (this.options.Mode3D) {
671  if (!this.options.Surf && !this.options.Lego && !this.options.Error) {
672  if ((this.nbinsx>=50) || (this.nbinsy>=50))
673  this.options.Lego = this.options.Color ? 14 : 13;
674  else
675  this.options.Lego = this.options.Color ? 12 : 1;
676 
677  this.options.Zero = false; // do not show zeros by default
678  }
679  }
680 
681  this.CopyOptionsToOthers();
682  this.InteractiveRedraw("pad", "drawopt");
683  }
684 
685  THistPainter.prototype.PrepareColorDraw = function(args) {
686 
687  if (!args) args = { rounding: true, extra: 0, middle: 0 };
688 
689  if (args.extra === undefined) args.extra = 0;
690  if (args.middle === undefined) args.middle = 0;
691 
692  var histo = this.GetHisto(), xaxis = this.GetAxis("x"), yaxis = this.GetAxis("y"),
693  pmain = this.frame_painter(),
694  hdim = this.Dimension(),
695  i, j, x, y, binz, binarea,
696  res = {
697  i1: this.GetSelectIndex("x", "left", 0 - args.extra),
698  i2: this.GetSelectIndex("x", "right", 1 + args.extra),
699  j1: (hdim===1) ? 0 : this.GetSelectIndex("y", "left", 0 - args.extra),
700  j2: (hdim===1) ? 1 : this.GetSelectIndex("y", "right", 1 + args.extra),
701  min: 0, max: 0, sumz: 0, xbar1: 0, xbar2: 1, ybar1: 0, ybar2: 1
702  };
703  res.grx = new Float32Array(res.i2+1);
704  res.gry = new Float32Array(res.j2+1);
705 
706  if (args.original) {
707  res.original = true;
708  res.origx = new Float32Array(res.i2+1);
709  res.origy = new Float32Array(res.j2+1);
710  }
711 
712  if (args.pixel_density) args.rounding = true;
713 
714  // calculate graphical coordinates in advance
715  for (i = res.i1; i <= res.i2; ++i) {
716  x = xaxis.GetBinCoord(i + args.middle);
717  if (pmain.logx && (x <= 0)) { res.i1 = i+1; continue; }
718  if (res.origx) res.origx[i] = x;
719  res.grx[i] = pmain.grx(x);
720  if (args.rounding) res.grx[i] = Math.round(res.grx[i]);
721 
722  if (args.use3d) {
723  if (res.grx[i] < -pmain.size_xy3d) { res.i1 = i; res.grx[i] = -pmain.size_xy3d; }
724  if (res.grx[i] > pmain.size_xy3d) { res.i2 = i; res.grx[i] = pmain.size_xy3d; }
725  }
726  }
727 
728  if (hdim===1) {
729  res.gry[0] = pmain.gry(0);
730  res.gry[1] = pmain.gry(1);
731  } else
732  for (j = res.j1; j <= res.j2; ++j) {
733  y = yaxis.GetBinCoord(j + args.middle);
734  if (pmain.logy && (y <= 0)) { res.j1 = j+1; continue; }
735  if (res.origy) res.origy[j] = y;
736  res.gry[j] = pmain.gry(y);
737  if (args.rounding) res.gry[j] = Math.round(res.gry[j]);
738 
739  if (args.use3d) {
740  if (res.gry[j] < -pmain.size_xy3d) { res.j1 = j; res.gry[j] = -pmain.size_xy3d; }
741  if (res.gry[j] > pmain.size_xy3d) { res.j2 = j; res.gry[j] = pmain.size_xy3d; }
742  }
743  }
744 
745  // find min/max values in selected range
746 
747  binz = histo.getBinContent(res.i1 + 1, res.j1 + 1);
748  this.maxbin = this.minbin = this.minposbin = null;
749 
750  for (i = res.i1; i < res.i2; ++i) {
751  for (j = res.j1; j < res.j2; ++j) {
752  binz = histo.getBinContent(i + 1, j + 1);
753  res.sumz += binz;
754  if (args.pixel_density) {
755  binarea = (res.grx[i+1]-res.grx[i])*(res.gry[j]-res.gry[j+1]);
756  if (binarea <= 0) continue;
757  res.max = Math.max(res.max, binz);
758  if ((binz>0) && ((binz<res.min) || (res.min===0))) res.min = binz;
759  binz = binz/binarea;
760  }
761  if (this.maxbin===null) {
762  this.maxbin = this.minbin = binz;
763  } else {
764  this.maxbin = Math.max(this.maxbin, binz);
765  this.minbin = Math.min(this.minbin, binz);
766  }
767  if (binz > 0)
768  if ((this.minposbin===null) || (binz<this.minposbin)) this.minposbin = binz;
769  }
770  }
771 
772  // force recalculation of z levels
773  this.fContour = null;
774  this.fCustomContour = false;
775 
776  return res;
777  }
778 
779  // ======= TH1 painter================================================
780 
781  function TH1Painter(histo) {
782  THistPainter.call(this, histo);
783  this.wheel_zoomy = false;
784  }
785 
786  TH1Painter.prototype = Object.create(THistPainter.prototype);
787 
788  TH1Painter.prototype.ConvertTH1K = function() {
789  var histo = this.GetObject();
790 
791  if (histo.fReady) return;
792 
793  var arr = histo.fArray; // array of values
794  histo.fNcells = histo.fXaxis.fNbins + 2;
795  histo.fArray = new Float64Array(histo.fNcells);
796  for (var n=0;n<histo.fNcells;++n) histo.fArray[n] = 0;
797  for (var n=0;n<histo.fNIn;++n) histo.Fill(arr[n]);
798  histo.fReady = true;
799  }
800 
801  TH1Painter.prototype.ScanContent = function(when_axis_changed) {
802  // if when_axis_changed === true specified, content will be scanned after axis zoom changed
803 
804  var histo = this.GetHisto();
805  if (!histo) return;
806 
807  if (!this.nbinsx && when_axis_changed) when_axis_changed = false;
808 
809  if (!when_axis_changed) {
810  this.nbinsx = this.GetAxis("x").fNBins - 2;
811  this.nbinsy = 0;
812  this.CreateAxisFuncs(false);
813  }
814 
815  var left = this.GetSelectIndex("x", "left"),
816  right = this.GetSelectIndex("x", "right");
817 
818  if (when_axis_changed) {
819  if ((left === this.scan_xleft) && (right === this.scan_xright)) return;
820  }
821 
822  this.scan_xleft = left;
823  this.scan_xright = right;
824 
825  var hmin = 0, hmin_nz = 0, hmax = 0, hsum = 0, first = true, value, err;
826 
827  for (var i = 0; i < this.nbinsx; ++i) {
828  value = histo.getBinContent(i+1);
829  hsum += value;
830 
831  if ((i<left) || (i>=right)) continue;
832 
833  if (value > 0)
834  if ((hmin_nz == 0) || (value<hmin_nz)) hmin_nz = value;
835  if (first) {
836  hmin = hmax = value;
837  first = false;;
838  }
839 
840  err = 0;
841 
842  hmin = Math.min(hmin, value - err);
843  hmax = Math.max(hmax, value + err);
844  }
845 
846  // account overflow/underflow bins
847  hsum += histo.getBinContent(0) + histo.getBinContent(this.nbinsx + 1);
848 
849  this.stat_entries = hsum;
850 
851  this.hmin = hmin;
852  this.hmax = hmax;
853 
854  this.ymin_nz = hmin_nz; // value can be used to show optimal log scale
855 
856  if ((this.nbinsx == 0) || ((Math.abs(hmin) < 1e-300 && Math.abs(hmax) < 1e-300))) {
857  this.draw_content = false;
858  } else {
859  this.draw_content = true;
860  }
861 
862  if (this.draw_content) {
863  if (hmin >= hmax) {
864  if (hmin == 0) { this.ymin = 0; this.ymax = 1; }
865  else if (hmin < 0) { this.ymin = 2 * hmin; this.ymax = 0; }
866  else { this.ymin = 0; this.ymax = hmin * 2; }
867  } else {
868  var dy = (hmax - hmin) * 0.05;
869  this.ymin = hmin - dy;
870  if ((this.ymin < 0) && (hmin >= 0)) this.ymin = 0;
871  this.ymax = hmax + dy;
872  }
873  }
874  }
875 
876  TH1Painter.prototype.CountStat = function(cond) {
877  var profile = this.IsTProfile(),
878  histo = this.GetHisto(), xaxis = this.GetAxis("x"),
879  left = this.GetSelectIndex("x", "left"),
880  right = this.GetSelectIndex("x", "right"),
881  stat_sumw = 0, stat_sumwx = 0, stat_sumwx2 = 0, stat_sumwy = 0, stat_sumwy2 = 0,
882  i, xx = 0, w = 0, xmax = null, wmax = null,
883  fp = this.frame_painter(),
884  res = { name: "histo", meanx: 0, meany: 0, rmsx: 0, rmsy: 0, integral: 0, entries: this.stat_entries, xmax:0, wmax:0 };
885 
886  for (i = left; i < right; ++i) {
887  xx = xaxis.GetBinCoord(i+0.5);
888 
889  if (cond && !cond(xx)) continue;
890 
891  if (profile) {
892  w = histo.fBinEntries[i + 1];
893  stat_sumwy += histo.fArray[i + 1];
894  stat_sumwy2 += histo.fSumw2[i + 1];
895  } else {
896  w = histo.getBinContent(i + 1);
897  }
898 
899  if ((xmax===null) || (w>wmax)) { xmax = xx; wmax = w; }
900 
901  stat_sumw += w;
902  stat_sumwx += w * xx;
903  stat_sumwx2 += w * xx * xx;
904  }
905 
906  // when no range selection done, use original statistic from histogram
907  if (!fp.IsAxisZoomed("x") && histo.fTsumw) {
908  stat_sumw = histo.fTsumw;
909  stat_sumwx = histo.fTsumwx;
910  stat_sumwx2 = histo.fTsumwx2;
911  }
912 
913  res.integral = stat_sumw;
914 
915  if (stat_sumw > 0) {
916  res.meanx = stat_sumwx / stat_sumw;
917  res.meany = stat_sumwy / stat_sumw;
918  res.rmsx = Math.sqrt(Math.abs(stat_sumwx2 / stat_sumw - res.meanx * res.meanx));
919  res.rmsy = Math.sqrt(Math.abs(stat_sumwy2 / stat_sumw - res.meany * res.meany));
920  }
921 
922  if (xmax!==null) {
923  res.xmax = xmax;
924  res.wmax = wmax;
925  }
926 
927  return res;
928  }
929 
930  TH1Painter.prototype.FillStatistic = function(stat, dostat, dofit) {
931 
932  // no need to refill statistic if histogram is dummy
933  if (this.IgnoreStatsFill()) return false;
934 
935  var data = this.CountStat(),
936  print_name = dostat % 10,
937  print_entries = Math.floor(dostat / 10) % 10,
938  print_mean = Math.floor(dostat / 100) % 10,
939  print_rms = Math.floor(dostat / 1000) % 10,
940  print_under = Math.floor(dostat / 10000) % 10,
941  print_over = Math.floor(dostat / 100000) % 10,
942  print_integral = Math.floor(dostat / 1000000) % 10,
943  print_skew = Math.floor(dostat / 10000000) % 10,
944  print_kurt = Math.floor(dostat / 100000000) % 10;
945 
946  // make empty at the beginning
947  stat.ClearPave();
948 
949  if (print_name > 0)
950  stat.AddText(data.name);
951 
952  if (this.IsTProfile()) {
953 
954  if (print_entries > 0)
955  stat.AddText("Entries = " + stat.Format(data.entries,"entries"));
956 
957  if (print_mean > 0) {
958  stat.AddText("Mean = " + stat.Format(data.meanx));
959  stat.AddText("Mean y = " + stat.Format(data.meany));
960  }
961 
962  if (print_rms > 0) {
963  stat.AddText("Std Dev = " + stat.Format(data.rmsx));
964  stat.AddText("Std Dev y = " + stat.Format(data.rmsy));
965  }
966 
967  } else {
968 
969  if (print_entries > 0)
970  stat.AddText("Entries = " + stat.Format(data.entries,"entries"));
971 
972  if (print_mean > 0)
973  stat.AddText("Mean = " + stat.Format(data.meanx));
974 
975  if (print_rms > 0)
976  stat.AddText("Std Dev = " + stat.Format(data.rmsx));
977 
978  if (print_under > 0)
979  stat.AddText("Underflow = " + stat.Format(histo.getBinContent(0), "entries"));
980 
981  if (print_over > 0)
982  stat.AddText("Overflow = " + stat.Format(histo.getBinContent(this.nbinsx+1), "entries"));
983 
984  if (print_integral > 0)
985  stat.AddText("Integral = " + stat.Format(data.integral,"entries"));
986 
987  if (print_skew > 0)
988  stat.AddText("Skew = <not avail>");
989 
990  if (print_kurt > 0)
991  stat.AddText("Kurt = <not avail>");
992  }
993 
994  // if (dofit) stat.FillFunctionStat(this.FindFunction('TF1'), dofit);
995 
996  return true;
997  }
998 
999  TH1Painter.prototype.DrawBars = function(width, height) {
1000 
1001  this.CreateG(true);
1002 
1003  var left = this.GetSelectIndex("x", "left", -1),
1004  right = this.GetSelectIndex("x", "right", 1),
1005  pmain = this.frame_painter(),
1006  pthis = this,
1007  histo = this.GetHisto(), xaxis = this.GetAxis("x"),
1008  i, x1, x2, grx1, grx2, y, gry1, gry2, w,
1009  bars = "", barsl = "", barsr = "",
1010  side = (this.options.BarStyle > 10) ? this.options.BarStyle % 10 : 0;
1011 
1012  if (side>4) side = 4;
1013  gry2 = pmain.swap_xy ? 0 : height;
1014  if ((this.options.BaseLine !== false) && !isNaN(this.options.BaseLine))
1015  if (this.options.BaseLine >= pmain.scale_ymin)
1016  gry2 = Math.round(pmain.gry(this.options.BaseLine));
1017 
1018  for (i = left; i < right; ++i) {
1019  x1 = xaxis.GetBinCoord(i);
1020  x2 = xaxis.GetBinCoord(i+1);
1021 
1022  if (pmain.logx && (x2 <= 0)) continue;
1023 
1024  grx1 = Math.round(pmain.grx(x1));
1025  grx2 = Math.round(pmain.grx(x2));
1026 
1027  y = histo.getBinContent(i+1);
1028  if (pmain.logy && (y < pmain.scale_ymin)) continue;
1029  gry1 = Math.round(pmain.gry(y));
1030 
1031  w = grx2 - grx1;
1032  grx1 += Math.round(this.options.fBarOffset/1000*w);
1033  w = Math.round(this.options.fBarWidth/1000*w);
1034 
1035  if (pmain.swap_xy)
1036  bars += "M"+gry2+","+grx1 + "h"+(gry1-gry2) + "v"+w + "h"+(gry2-gry1) + "z";
1037  else
1038  bars += "M"+grx1+","+gry1 + "h"+w + "v"+(gry2-gry1) + "h"+(-w)+ "z";
1039 
1040  if (side > 0) {
1041  grx2 = grx1 + w;
1042  w = Math.round(w * side / 10);
1043  if (pmain.swap_xy) {
1044  barsl += "M"+gry2+","+grx1 + "h"+(gry1-gry2) + "v" + w + "h"+(gry2-gry1) + "z";
1045  barsr += "M"+gry2+","+grx2 + "h"+(gry1-gry2) + "v" + (-w) + "h"+(gry2-gry1) + "z";
1046  } else {
1047  barsl += "M"+grx1+","+gry1 + "h"+w + "v"+(gry2-gry1) + "h"+(-w)+ "z";
1048  barsr += "M"+grx2+","+gry1 + "h"+(-w) + "v"+(gry2-gry1) + "h"+w + "z";
1049  }
1050  }
1051  }
1052 
1053  if (this.fillatt.empty()) this.fillatt.SetSolidColor("blue");
1054 
1055  if (bars.length > 0)
1056  this.draw_g.append("svg:path")
1057  .attr("d", bars)
1058  .call(this.fillatt.func);
1059 
1060  if (barsl.length > 0)
1061  this.draw_g.append("svg:path")
1062  .attr("d", barsl)
1063  .call(this.fillatt.func)
1064  .style("fill", d3.rgb(this.fillatt.color).brighter(0.5).toString());
1065 
1066  if (barsr.length > 0)
1067  this.draw_g.append("svg:path")
1068  .attr("d", barsr)
1069  .call(this.fillatt.func)
1070  .style("fill", d3.rgb(this.fillatt.color).darker(0.5).toString());
1071  }
1072 
1073  TH1Painter.prototype.DrawFilledErrors = function(width, height) {
1074  this.CreateG(true);
1075 
1076  var left = this.GetSelectIndex("x", "left", -1),
1077  right = this.GetSelectIndex("x", "right", 1),
1078  pmain = this.frame_painter(),
1079  histo = this.GetHisto(), xaxis = this.GetAxis("x"),
1080  i, x, grx, y, yerr, gry1, gry2,
1081  bins1 = [], bins2 = [];
1082 
1083  for (i = left; i < right; ++i) {
1084  x = xaxis.GetBinCoord(i+0.5);
1085  if (pmain.logx && (x <= 0)) continue;
1086  grx = Math.round(pmain.grx(x));
1087 
1088  y = histo.getBinContent(i+1);
1089  yerr = histo.getBinError(i+1);
1090  if (pmain.logy && (y-yerr < pmain.scale_ymin)) continue;
1091 
1092  gry1 = Math.round(pmain.gry(y + yerr));
1093  gry2 = Math.round(pmain.gry(y - yerr));
1094 
1095  bins1.push({grx:grx, gry: gry1});
1096  bins2.unshift({grx:grx, gry: gry2});
1097  }
1098 
1099  var kind = (this.options.ErrorKind === 4) ? "bezier" : "line",
1100  path1 = JSROOT.Painter.BuildSvgPath(kind, bins1),
1101  path2 = JSROOT.Painter.BuildSvgPath("L"+kind, bins2);
1102 
1103  if (this.fillatt.empty()) this.fillatt.setSolidColor("blue");
1104 
1105  this.draw_g.append("svg:path")
1106  .attr("d", path1.path + path2.path + "Z")
1107  .style("stroke", "none")
1108  .call(this.fillatt.func);
1109  }
1110 
1111  TH1Painter.prototype.DrawBins = function() {
1112  // new method, create svg:path expression ourself directly from histogram
1113  // all points will be used, compress expression when too large
1114 
1115  var width = this.frame_width(), height = this.frame_height(), options = this.options;
1116 
1117  if (!this.draw_content || (width<=0) || (height<=0))
1118  return this.RemoveDrawG();
1119 
1120  this.CheckHistDrawAttributes();
1121 
1122  if (options.Bar)
1123  return this.DrawBars(width, height);
1124 
1125  if ((options.ErrorKind === 3) || (options.ErrorKind === 4))
1126  return this.DrawFilledErrors(width, height);
1127 
1128  this.CreateG(true);
1129 
1130  var left = this.GetSelectIndex("x", "left", -1),
1131  right = this.GetSelectIndex("x", "right", 2),
1132  pmain = this.frame_painter(),
1133  pthis = this, histo = this.GetHisto(), xaxis = this.GetAxis("x"),
1134  res = "", lastbin = false,
1135  startx, currx, curry, x, grx, y, gry, curry_min, curry_max, prevy, prevx, i, bestimin, bestimax,
1136  exclude_zero = !options.Zero,
1137  show_errors = options.Error,
1138  show_markers = options.Mark,
1139  show_line = options.Line,
1140  show_text = options.Text,
1141  text_profile = show_text && (this.options.TextKind == "E") && this.IsTProfile() && histo.fBinEntries,
1142  path_fill = null, path_err = null, path_marker = null, path_line = null,
1143  endx = "", endy = "", dend = 0, my, yerr1, yerr2, bincont, binerr, mx1, mx2, midx,
1144  mpath = "", text_col, text_angle, text_size;
1145 
1146  //if (show_errors && !show_markers && (histo.fMarkerStyle > 1))
1147  // show_markers = true;
1148 
1149  if (options.ErrorKind === 2) {
1150  if (this.fillatt.empty()) show_markers = true;
1151  else path_fill = "";
1152  } else
1153  if (options.Error) path_err = "";
1154 
1155  if (show_line) path_line = "";
1156 
1157  if (show_markers) {
1158  // draw markers also when e2 option was specified
1159  this.createAttMarker({ attr: histo, style: this.options.MarkStyle });
1160  if (this.markeratt.size > 0) {
1161  // simply use relative move from point, can optimize in the future
1162  path_marker = "";
1163  this.markeratt.reset_pos();
1164  } else {
1165  show_markers = false;
1166  }
1167  }
1168 
1169  if (show_text) {
1170  text_col = this.get_color(histo.fMarkerColor);
1171  text_angle = -1*options.TextAngle;
1172  text_size = 20;
1173 
1174  if ((options.fMarkerSize!==1) && text_angle)
1175  text_size = 0.02 * height * options.fMarkerSize;
1176 
1177  if (!text_angle && !options.TextKind) {
1178  var space = width / (right - left + 1);
1179  if (space < 3 * text_size) {
1180  text_angle = 270;
1181  text_size = Math.round(space*0.7);
1182  }
1183  }
1184 
1185  this.StartTextDrawing(42, text_size, this.draw_g, text_size);
1186  }
1187 
1188  // if there are too many points, exclude many vertical drawings at the same X position
1189  // instead define min and max value and made min-max drawing
1190  var use_minmax = ((right-left) > 3*width);
1191 
1192  if (options.ErrorKind === 1) {
1193  var lw = this.lineatt.width + JSROOT.gStyle.fEndErrorSize;
1194  endx = "m0," + lw + "v-" + 2*lw + "m0," + lw;
1195  endy = "m" + lw + ",0h-" + 2*lw + "m" + lw + ",0";
1196  dend = Math.floor((this.lineatt.width-1)/2);
1197  }
1198 
1199  var draw_markers = show_errors || show_markers;
1200 
1201  if (draw_markers || show_text || show_line) use_minmax = true;
1202 
1203  function draw_bin(besti) {
1204  bincont = histo.getBinContent(besti+1);
1205  if (!exclude_zero || (bincont!==0)) {
1206  mx1 = Math.round(pmain.grx(xaxis.GetBinLowEdge(besti+1)));
1207  mx2 = Math.round(pmain.grx(xaxis.GetBinLowEdge(besti+2)));
1208  midx = Math.round((mx1+mx2)/2);
1209  my = Math.round(pmain.gry(bincont));
1210  yerr1 = yerr2 = 20;
1211  if (show_errors) {
1212  binerr = histo.getBinError(besti+1);
1213  yerr1 = Math.round(my - pmain.gry(bincont + binerr)); // up
1214  yerr2 = Math.round(pmain.gry(bincont - binerr) - my); // down
1215  }
1216 
1217  if (show_text) {
1218  var cont = text_profile ? histo.fBinEntries[besti+1] : bincont;
1219 
1220  if (cont!==0) {
1221  var lbl = (cont === Math.round(cont)) ? cont.toString() : JSROOT.FFormat(cont, JSROOT.gStyle.fPaintTextFormat);
1222 
1223  if (text_angle)
1224  pthis.DrawText({ align: 12, x: midx, y: Math.round(my - 2 - text_size/5), width: 0, height: 0, rotate: text_angle, text: lbl, color: text_col, latex: 0 });
1225  else
1226  pthis.DrawText({ align: 22, x: Math.round(mx1 + (mx2-mx1)*0.1), y: Math.round(my-2-text_size), width: Math.round((mx2-mx1)*0.8), height: text_size, text: lbl, color: text_col, latex: 0 });
1227  }
1228  }
1229 
1230  if (show_line && (path_line !== null))
1231  path_line += ((path_line.length===0) ? "M" : "L") + midx + "," + my;
1232 
1233  if (draw_markers) {
1234  if ((my >= -yerr1) && (my <= height + yerr2)) {
1235  if (path_fill !== null)
1236  path_fill += "M" + mx1 +","+(my-yerr1) +
1237  "h" + (mx2-mx1) + "v" + (yerr1+yerr2+1) + "h-" + (mx2-mx1) + "z";
1238  if (path_marker !== null)
1239  path_marker += pthis.markeratt.create(midx, my);
1240  if (path_err !== null) {
1241  if (pthis.options.errorX > 0) {
1242  var mmx1 = Math.round(midx - (mx2-mx1)*pthis.options.errorX),
1243  mmx2 = Math.round(midx + (mx2-mx1)*pthis.options.errorX);
1244  path_err += "M" + (mmx1+dend) +","+ my + endx + "h" + (mmx2-mmx1-2*dend) + endx;
1245  }
1246  path_err += "M" + midx +"," + (my-yerr1+dend) + endy + "v" + (yerr1+yerr2-2*dend) + endy;
1247  }
1248  }
1249  }
1250  }
1251  }
1252 
1253  for (i = left; i <= right; ++i) {
1254 
1255  x = xaxis.GetBinCoord(i);
1256 
1257  if (pmain.logx && (x <= 0)) continue;
1258 
1259  grx = Math.round(pmain.grx(x));
1260 
1261  lastbin = (i === right);
1262 
1263  if (lastbin && (left<right)) {
1264  gry = curry;
1265  } else {
1266  y = histo.getBinContent(i+1);
1267  gry = Math.round(pmain.gry(y));
1268  }
1269 
1270  if (res.length === 0) {
1271  bestimin = bestimax = i;
1272  prevx = startx = currx = grx;
1273  prevy = curry_min = curry_max = curry = gry;
1274  res = "M"+currx+","+curry;
1275  } else
1276  if (use_minmax) {
1277  if ((grx === currx) && !lastbin) {
1278  if (gry < curry_min) bestimax = i; else
1279  if (gry > curry_max) bestimin = i;
1280  curry_min = Math.min(curry_min, gry);
1281  curry_max = Math.max(curry_max, gry);
1282  curry = gry;
1283  } else {
1284 
1285  if (draw_markers || show_text || show_line) {
1286  if (bestimin === bestimax) { draw_bin(bestimin); } else
1287  if (bestimin < bestimax) { draw_bin(bestimin); draw_bin(bestimax); } else {
1288  draw_bin(bestimax); draw_bin(bestimin);
1289  }
1290  }
1291 
1292  // when several points as same X differs, need complete logic
1293  if (!draw_markers && ((curry_min !== curry_max) || (prevy !== curry_min))) {
1294 
1295  if (prevx !== currx)
1296  res += "h"+(currx-prevx);
1297 
1298  if (curry === curry_min) {
1299  if (curry_max !== prevy)
1300  res += "v" + (curry_max - prevy);
1301  if (curry_min !== curry_max)
1302  res += "v" + (curry_min - curry_max);
1303  } else {
1304  if (curry_min !== prevy)
1305  res += "v" + (curry_min - prevy);
1306  if (curry_max !== curry_min)
1307  res += "v" + (curry_max - curry_min);
1308  if (curry !== curry_max)
1309  res += "v" + (curry - curry_max);
1310  }
1311 
1312  prevx = currx;
1313  prevy = curry;
1314  }
1315 
1316  if (lastbin && (prevx !== grx))
1317  res += "h"+(grx-prevx);
1318 
1319  bestimin = bestimax = i;
1320  curry_min = curry_max = curry = gry;
1321  currx = grx;
1322  }
1323  } else
1324  if ((gry !== curry) || lastbin) {
1325  if (grx !== currx) res += "h"+(grx-currx);
1326  if (gry !== curry) res += "v"+(gry-curry);
1327  curry = gry;
1328  currx = grx;
1329  }
1330  }
1331 
1332  var close_path = "";
1333  if (!this.fillatt.empty()) {
1334  var h0 = height + 3, gry0 = Math.round(pmain.gry(0));
1335  if (gry0 <= 0) h0 = -3; else if (gry0 < height) h0 = gry0;
1336  close_path = "L"+currx+","+h0 + "L"+startx+","+h0 + "Z";
1337  if (res.length>0) res += close_path;
1338  }
1339 
1340  if (draw_markers || show_line) {
1341  if ((path_fill !== null) && (path_fill.length > 0))
1342  this.draw_g.append("svg:path")
1343  .attr("d", path_fill)
1344  .call(this.fillatt.func);
1345 
1346  if ((path_err !== null) && (path_err.length > 0))
1347  this.draw_g.append("svg:path")
1348  .attr("d", path_err)
1349  .call(this.lineatt.func);
1350 
1351  if ((path_line !== null) && (path_line.length > 0)) {
1352  if (!this.fillatt.empty())
1353  this.draw_g.append("svg:path")
1354  .attr("d", options.Fill ? (path_line + close_path) : res)
1355  .attr("stroke", "none")
1356  .call(this.fillatt.func);
1357 
1358  this.draw_g.append("svg:path")
1359  .attr("d", path_line)
1360  .attr("fill", "none")
1361  .call(this.lineatt.func);
1362  }
1363 
1364  if ((path_marker !== null) && (path_marker.length > 0))
1365  this.draw_g.append("svg:path")
1366  .attr("d", path_marker)
1367  .call(this.markeratt.func);
1368 
1369  } else
1370  if (res && options.Hist) {
1371  this.draw_g.append("svg:path")
1372  .attr("d", res)
1373  .style("stroke-linejoin","miter")
1374  .call(this.lineatt.func)
1375  .call(this.fillatt.func);
1376  }
1377 
1378  if (show_text)
1379  this.FinishTextDrawing(this.draw_g);
1380 
1381  }
1382 
1383  TH1Painter.prototype.GetBinTips = function(bin) {
1384  var tips = [],
1385  name = this.GetTipName(),
1386  pmain = this.frame_painter(),
1387  histo = this.GetHisto(), xaxis = this.GetAxis("x"),
1388  x1 = xaxis.GetBinCoord(bin),
1389  x2 = xaxis.GetBinCoord(bin+1),
1390  cont = histo.getBinContent(bin+1),
1391  xlbl = "", xnormal = false;
1392 
1393  if (name.length>0) tips.push(name);
1394 
1395  if (pmain.x_kind === 'labels') xlbl = pmain.AxisAsText("x", x1); else
1396  if (pmain.x_kind === 'time') xlbl = pmain.AxisAsText("x", (x1+x2)/2); else
1397  { xnormal = true; xlbl = "[" + pmain.AxisAsText("x", x1) + ", " + pmain.AxisAsText("x", x2) + ")"; }
1398 
1399  if (this.options.Error || this.options.Mark) {
1400  tips.push("x = " + xlbl);
1401  tips.push("y = " + pmain.AxisAsText("y", cont));
1402  if (this.options.Error) {
1403  if (xnormal) tips.push("error x = " + ((x2 - x1) / 2).toPrecision(4));
1404  tips.push("error y = " + histo.getBinError(bin + 1).toPrecision(4));
1405  }
1406  } else {
1407  tips.push("bin = " + (bin+1));
1408  tips.push("x = " + xlbl);
1409  if (histo['$baseh']) cont -= histo['$baseh'].getBinContent(bin+1);
1410  if (cont === Math.round(cont))
1411  tips.push("entries = " + cont);
1412  else
1413  tips.push("entries = " + JSROOT.FFormat(cont, JSROOT.gStyle.fStatFormat));
1414  }
1415 
1416  return tips;
1417  }
1418 
1419  TH1Painter.prototype.ProcessTooltip = function(pnt) {
1420  if ((pnt === null) || !this.draw_content || this.options.Mode3D) {
1421  if (this.draw_g !== null)
1422  this.draw_g.select(".tooltip_bin").remove();
1423  return null;
1424  }
1425 
1426  var width = this.frame_width(),
1427  height = this.frame_height(),
1428  pmain = this.frame_painter(),
1429  painter = this,
1430  histo = this.GetHisto(), xaxis = this.GetAxis("x"),
1431  findbin = null, show_rect = true,
1432  grx1, midx, grx2, gry1, midy, gry2, gapx = 2,
1433  left = this.GetSelectIndex("x", "left", -1),
1434  right = this.GetSelectIndex("x", "right", 2),
1435  l = left, r = right;
1436 
1437  function GetBinGrX(i) {
1438  var xx = xaxis.GetBinCoord(i);
1439  return (pmain.logx && (xx<=0)) ? null : pmain.grx(xx);
1440  }
1441 
1442  function GetBinGrY(i) {
1443  var yy = histo.getBinContent(i + 1);
1444  if (pmain.logy && (yy < pmain.scale_ymin))
1445  return pmain.swap_xy ? -1000 : 10*height;
1446  return Math.round(pmain.gry(yy));
1447  }
1448 
1449  var pnt_x = pmain.swap_xy ? pnt.y : pnt.x,
1450  pnt_y = pmain.swap_xy ? pnt.x : pnt.y;
1451 
1452  while (l < r-1) {
1453  var m = Math.round((l+r)*0.5),
1454  xx = GetBinGrX(m);
1455  if ((xx === null) || (xx < pnt_x - 0.5)) {
1456  if (pmain.swap_xy) r = m; else l = m;
1457  } else if (xx > pnt_x + 0.5) {
1458  if (pmain.swap_xy) l = m; else r = m;
1459  } else { l++; r--; }
1460  }
1461 
1462  findbin = r = l;
1463  grx1 = GetBinGrX(findbin);
1464 
1465  if (pmain.swap_xy) {
1466  while ((l>left) && (GetBinGrX(l-1) < grx1 + 2)) --l;
1467  while ((r<right) && (GetBinGrX(r+1) > grx1 - 2)) ++r;
1468  } else {
1469  while ((l>left) && (GetBinGrX(l-1) > grx1 - 2)) --l;
1470  while ((r<right) && (GetBinGrX(r+1) < grx1 + 2)) ++r;
1471  }
1472 
1473  if (l < r) {
1474  // many points can be assigned with the same cursor position
1475  // first try point around mouse y
1476  var best = height;
1477  for (var m=l;m<=r;m++) {
1478  var dist = Math.abs(GetBinGrY(m) - pnt_y);
1479  if (dist < best) { best = dist; findbin = m; }
1480  }
1481 
1482  // if best distance still too far from mouse position, just take from between
1483  if (best > height/10)
1484  findbin = Math.round(l + (r-l) / height * pnt_y);
1485 
1486  grx1 = GetBinGrX(findbin);
1487  }
1488 
1489  grx1 = Math.round(grx1);
1490  grx2 = Math.round(GetBinGrX(findbin+1));
1491 
1492  if (this.options.Bar) {
1493  var w = grx2 - grx1;
1494  grx1 += Math.round(this.options.fBarOffset/1000*w);
1495  grx2 = grx1 + Math.round(this.options.fBarWidth/1000*w);
1496  }
1497 
1498  if (grx1 > grx2) { var d = grx1; grx1 = grx2; grx2 = d; }
1499 
1500  midx = Math.round((grx1+grx2)/2);
1501 
1502  midy = gry1 = gry2 = GetBinGrY(findbin);
1503 
1504  if (this.options.Bar) {
1505  show_rect = true;
1506 
1507  gapx = 0;
1508 
1509  gry1 = Math.round(pmain.gry(((this.options.BaseLine!==false) && (this.options.BaseLine > pmain.scale_ymin)) ? this.options.BaseLine : pmain.scale_ymin));
1510 
1511  if (gry1 > gry2) { var d = gry1; gry1 = gry2; gry2 = d; }
1512 
1513  if (!pnt.touch && (pnt.nproc === 1))
1514  if ((pnt_y<gry1) || (pnt_y>gry2)) findbin = null;
1515 
1516  } else if (this.options.Error || this.options.Mark || this.options.Line) {
1517 
1518  show_rect = true;
1519 
1520  var msize = 3;
1521  if (this.markeratt) msize = Math.max(msize, this.markeratt.GetFullSize());
1522 
1523  if (this.options.Error) {
1524  var cont = histo.getBinContent(findbin+1),
1525  binerr = histo.getBinError(findbin+1);
1526 
1527  gry1 = Math.round(pmain.gry(cont + binerr)); // up
1528  gry2 = Math.round(pmain.gry(cont - binerr)); // down
1529 
1530  if ((cont==0) && this.IsTProfile()) findbin = null;
1531 
1532  var dx = (grx2-grx1)*this.options.errorX;
1533  grx1 = Math.round(midx - dx);
1534  grx2 = Math.round(midx + dx);
1535  }
1536 
1537  // show at least 6 pixels as tooltip rect
1538  if (grx2 - grx1 < 2*msize) { grx1 = midx-msize; grx2 = midx+msize; }
1539 
1540  gry1 = Math.min(gry1, midy - msize);
1541  gry2 = Math.max(gry2, midy + msize);
1542 
1543  if (!pnt.touch && (pnt.nproc === 1))
1544  if ((pnt_y<gry1) || (pnt_y>gry2)) findbin = null;
1545 
1546  } else {
1547 
1548  // if histogram alone, use old-style with rects
1549  // if there are too many points at pixel, use circle
1550  show_rect = (pnt.nproc === 1) && (right-left < width);
1551 
1552  if (show_rect) {
1553  gry2 = height;
1554 
1555  if (!this.fillatt.empty()) {
1556  gry2 = Math.round(pmain.gry(0));
1557  if (gry2 < 0) gry2 = 0; else if (gry2 > height) gry2 = height;
1558  if (gry2 < gry1) { var d = gry1; gry1 = gry2; gry2 = d; }
1559  }
1560 
1561  // for mouse events pointer should be between y1 and y2
1562  if (((pnt.y < gry1) || (pnt.y > gry2)) && !pnt.touch) findbin = null;
1563  }
1564  }
1565 
1566  if (findbin!==null) {
1567  // if bin on boundary found, check that x position is ok
1568  if ((findbin === left) && (grx1 > pnt_x + gapx)) findbin = null; else
1569  if ((findbin === right-1) && (grx2 < pnt_x - gapx)) findbin = null; else
1570  // if bars option used check that bar is not match
1571  if ((pnt_x < grx1 - gapx) || (pnt_x > grx2 + gapx)) findbin = null; else
1572  // exclude empty bin if empty bins suppressed
1573  if (!this.options.Zero && (histo.getBinContent(findbin+1)===0)) findbin = null;
1574  }
1575 
1576  var ttrect = this.draw_g.select(".tooltip_bin");
1577 
1578  if ((findbin === null) || ((gry2 <= 0) || (gry1 >= height))) {
1579  ttrect.remove();
1580  return null;
1581  }
1582 
1583  var res = { name: "histo", title: histo.fTitle,
1584  x: midx, y: midy, exact: true,
1585  color1: this.lineatt ? this.lineatt.color : 'green',
1586  color2: this.fillatt ? this.fillatt.fillcoloralt('blue') : 'blue',
1587  lines: this.GetBinTips(findbin) };
1588 
1589  if (pnt.disabled) {
1590  // case when tooltip should not highlight bin
1591 
1592  ttrect.remove();
1593  res.changed = true;
1594  } else if (show_rect) {
1595 
1596  if (ttrect.empty())
1597  ttrect = this.draw_g.append("svg:rect")
1598  .attr("class","tooltip_bin h1bin")
1599  .style("pointer-events","none");
1600 
1601  res.changed = ttrect.property("current_bin") !== findbin;
1602 
1603  if (res.changed)
1604  ttrect.attr("x", pmain.swap_xy ? gry1 : grx1)
1605  .attr("width", pmain.swap_xy ? gry2-gry1 : grx2-grx1)
1606  .attr("y", pmain.swap_xy ? grx1 : gry1)
1607  .attr("height", pmain.swap_xy ? grx2-grx1 : gry2-gry1)
1608  .style("opacity", "0.3")
1609  .property("current_bin", findbin);
1610 
1611  res.exact = (Math.abs(midy - pnt_y) <= 5) || ((pnt_y>=gry1) && (pnt_y<=gry2));
1612 
1613  res.menu = true; // one could show context menu
1614  // distance to middle point, use to decide which menu to activate
1615  res.menu_dist = Math.sqrt((midx-pnt_x)*(midx-pnt_x) + (midy-pnt_y)*(midy-pnt_y));
1616 
1617  } else {
1618  var radius = this.lineatt.width + 3;
1619 
1620  if (ttrect.empty())
1621  ttrect = this.draw_g.append("svg:circle")
1622  .attr("class","tooltip_bin")
1623  .style("pointer-events","none")
1624  .attr("r", radius)
1625  .call(this.lineatt.func)
1626  .call(this.fillatt.func);
1627 
1628  res.exact = (Math.abs(midx - pnt.x) <= radius) && (Math.abs(midy - pnt.y) <= radius);
1629 
1630  res.menu = res.exact; // show menu only when mouse pointer exactly over the histogram
1631  res.menu_dist = Math.sqrt((midx-pnt.x)*(midx-pnt.x) + (midy-pnt.y)*(midy-pnt.y));
1632 
1633  res.changed = ttrect.property("current_bin") !== findbin;
1634 
1635  if (res.changed)
1636  ttrect.attr("cx", midx)
1637  .attr("cy", midy)
1638  .property("current_bin", findbin);
1639  }
1640 
1641  if (res.changed)
1642  res.user_info = { obj: histo, name: "histo",
1643  bin: findbin, cont: histo.getBinContent(findbin+1),
1644  grx: midx, gry: midy };
1645 
1646  return res;
1647  }
1648 
1649 
1650  TH1Painter.prototype.FillHistContextMenu = function(menu) {
1651 
1652  menu.add("Auto zoom-in", this.AutoZoom);
1653 
1654  var sett = JSROOT.getDrawSettings("ROOT." + this.GetObject()._typename, 'nosame');
1655 
1656  menu.addDrawMenu("Draw with", sett.opts, function(arg) {
1657  if (arg==='inspect')
1658  return this.ShowInspector();
1659 
1660  this.DecodeOptions(arg); // obsolete, should be implemented differently
1661 
1662  if (this.options.need_fillcol && this.fillatt && this.fillatt.empty())
1663  this.fillatt.Change(5,1001);
1664 
1665  // redraw all objects
1666  this.InteractiveRedraw("pad", "drawopt");
1667  });
1668  }
1669 
1670  TH1Painter.prototype.AutoZoom = function() {
1671  var left = this.GetSelectIndex("x", "left", -1),
1672  right = this.GetSelectIndex("x", "right", 1),
1673  dist = right - left, histo = this.GetHisto(), xaxis = this.GetAxis("x");
1674 
1675  if (dist == 0) return;
1676 
1677  // first find minimum
1678  var min = histo.getBinContent(left + 1);
1679  for (var indx = left; indx < right; ++indx)
1680  min = Math.min(min, histo.getBinContent(indx+1));
1681  if (min > 0) return; // if all points positive, no chance for autoscale
1682 
1683  while ((left < right) && (histo.getBinContent(left+1) <= min)) ++left;
1684  while ((left < right) && (histo.getBinContent(right) <= min)) --right;
1685 
1686  // if singular bin
1687  if ((left === right-1) && (left > 2) && (right < this.nbinsx-2)) {
1688  --left; ++right;
1689  }
1690 
1691  if ((right - left < dist) && (left < right))
1692  this.frame_painter().Zoom(xaxis.GetBinCoord(left), xaxis.GetBinCoord(right));
1693  }
1694 
1695  TH1Painter.prototype.CanZoomIn = function(axis,min,max) {
1696  var xaxis = this.GetAxis("x");
1697 
1698  if ((axis=="x") && (xaxis.FindBin(max,0.5) - xaxis.FindBin(min,0) > 1)) return true;
1699 
1700  if ((axis=="y") && (Math.abs(max-min) > Math.abs(this.ymax-this.ymin)*1e-6)) return true;
1701 
1702  // check if it makes sense to zoom inside specified axis range
1703  return false;
1704  }
1705 
1706  TH1Painter.prototype.CallDrawFunc = function(callback, resize) {
1707 
1708  var main = this.frame_painter();
1709 
1710  if (main && (main.mode3d !== this.options.Mode3D)) {
1711  // that to do with that case
1712  this.options.Mode3D = main.mode3d;
1713  }
1714 
1715  var funcname = this.options.Mode3D ? "Draw3D" : "Draw2D";
1716 
1717  this[funcname](callback, resize);
1718  }
1719 
1720  TH1Painter.prototype.Draw2D = function(call_back) {
1721 
1722  this.Clear3DScene();
1723  this.mode3d = false;
1724 
1725  // this.ScanContent(true);
1726 
1727  if (typeof this.DrawColorPalette === 'function')
1728  this.DrawColorPalette(false);
1729 
1730  if (this.DrawAxes())
1731  this.DrawBins();
1732  else
1733  console.log('FAIL DARWING AXES');
1734 
1735  // this.DrawTitle();
1736  // this.UpdateStatWebCanvas();
1737  this.AddInteractive();
1738  JSROOT.CallBack(call_back);
1739  }
1740 
1741  TH1Painter.prototype.Draw3D = function(call_back, resize) {
1742  this.mode3d = true;
1743  JSROOT.AssertPrerequisites('hist3d', function() {
1744  this.Draw3D(call_back, resize);
1745  }.bind(this));
1746  }
1747 
1748  TH1Painter.prototype.Redraw = function(resize) {
1749  this.CallDrawFunc(null, resize);
1750  }
1751 
1752  function drawHist1(divid, histo, opt) {
1753  // create painter and add it to canvas
1754  var painter = new TH1Painter(histo);
1755 
1756  painter.PrepareFrame(divid);
1757 
1758  painter.options = { Hist: true, Bar: false, Error: false, ErrorKind: -1, errorX: 0, Zero: false, Mark: false,
1759  Line: false, Fill: false, Lego: 0, Surf: 0,
1760  Text: false, TextAngle: 0, TextKind: "", AutoColor: 0,
1761  fBarOffset: 0, fBarWidth: 1000, fMarkerSize: 1, BaseLine: false, Mode3D: false };
1762 
1763  // here we deciding how histogram will look like and how will be shown
1764  // painter.DecodeOptions(opt);
1765 
1766  painter.ScanContent();
1767 
1768  // painter.CreateStat(); // only when required
1769 
1770  painter.CallDrawFunc(function() {
1771  // if (!painter.options.Mode3D && painter.options.AutoZoom) painter.AutoZoom();
1772  // painter.FillToolbar();
1773  painter.DrawingReady();
1774  });
1775 
1776  return painter;
1777  }
1778 
1779  // ==================== painter for TH2 histograms ==============================
1780 
1781  function TH2Painter(histo) {
1782  THistPainter.call(this, histo);
1783  this.fContour = null; // contour levels
1784  this.fCustomContour = false; // are this user-defined levels (can be irregular)
1785  this.fPalette = null;
1786  this.wheel_zoomy = true;
1787  }
1788 
1789  TH2Painter.prototype = Object.create(THistPainter.prototype);
1790 
1791  TH2Painter.prototype.Cleanup = function() {
1792  delete this.fCustomContour;
1793  delete this.tt_handle;
1794 
1795  THistPainter.prototype.Cleanup.call(this);
1796  }
1797 
1798  TH2Painter.prototype.Dimension = function() {
1799  return 2;
1800  }
1801 
1802  TH2Painter.prototype.ToggleProjection = function(kind, width) {
1803 
1804  if (kind=="Projections") kind = "";
1805 
1806  if ((typeof kind == 'string') && (kind.length>1)) {
1807  width = parseInt(kind.substr(1));
1808  kind = kind[0];
1809  }
1810 
1811  if (!width) width = 1;
1812 
1813  if (kind && (this.is_projection==kind)) {
1814  if (this.projection_width === width) {
1815  kind = "";
1816  } else {
1817  this.projection_width = width;
1818  return;
1819  }
1820  }
1821 
1822  delete this.proj_hist;
1823 
1824  var new_proj = (this.is_projection === kind) ? "" : kind;
1825  this.is_projection = ""; // disable projection redraw until callback
1826  this.projection_width = width;
1827 
1828  var canp = this.canv_painter();
1829  if (canp) canp.ToggleProjection(this.is_projection, this.RedrawProjection.bind(this, "toggling", new_proj));
1830  }
1831 
1832  TH2Painter.prototype.RedrawProjection = function(ii1, ii2, jj1, jj2) {
1833  // do nothing for the moment
1834 
1835  if (ii1 === "toggling") {
1836  this.is_projection = ii2;
1837  ii1 = ii2 = undefined;
1838  }
1839 
1840  if (!this.is_projection) return;
1841  }
1842 
1843  TH2Painter.prototype.ExecuteMenuCommand = function(method, args) {
1844  if (THistPainter.prototype.ExecuteMenuCommand.call(this,method, args)) return true;
1845 
1846  if ((method.fName == 'SetShowProjectionX') || (method.fName == 'SetShowProjectionY')) {
1847  this.ToggleProjection(method.fName[17], args && parseInt(args) ? parseInt(args) : 1);
1848  return true;
1849  }
1850 
1851  return false;
1852  }
1853 
1854  TH2Painter.prototype.FillHistContextMenu = function(menu) {
1855  // painter automatically bind to menu callbacks
1856 
1857  menu.add("sub:Projections", this.ToggleProjection);
1858  var kind = this.is_projection || "";
1859  if (kind) kind += this.projection_width;
1860  var kinds = ["X1", "X2", "X3", "X5", "X10", "Y1", "Y2", "Y3", "Y5", "Y10"];
1861  for (var k=0;k<kinds.length;++k)
1862  menu.addchk(kind==kinds[k], kinds[k], kinds[k], this.ToggleProjection);
1863  menu.add("endsub:");
1864 
1865  menu.add("Auto zoom-in", this.AutoZoom);
1866 
1867  var sett = JSROOT.getDrawSettings("ROOT." + this.GetObject()._typename, 'nosame');
1868 
1869  menu.addDrawMenu("Draw with", sett.opts, function(arg) {
1870  if (arg==='inspect')
1871  return this.ShowInspector();
1872  this.DecodeOptions(arg);
1873  this.InteractiveRedraw("pad", "drawopt");
1874  });
1875 
1876  if (this.options.Color)
1877  this.FillPaletteMenu(menu);
1878  }
1879 
1880  TH2Painter.prototype.ButtonClick = function(funcname) {
1881  if (THistPainter.prototype.ButtonClick.call(this, funcname)) return true;
1882 
1883  switch(funcname) {
1884  case "ToggleColor": this.ToggleColor(); break;
1885  case "ToggleColorZ": this.ToggleColz(); break;
1886  case "Toggle3D": this.ToggleMode3D(); break;
1887  default: return false;
1888  }
1889 
1890  // all methods here should not be processed further
1891  return true;
1892  }
1893 
1894  TH2Painter.prototype.FillToolbar = function() {
1895  THistPainter.prototype.FillToolbar.call(this, true);
1896 
1897  var pp = this.pad_painter();
1898  if (!pp) return;
1899 
1900  if (!this.IsTH2Poly())
1901  pp.AddButton(JSROOT.ToolbarIcons.th2color, "Toggle color", "ToggleColor");
1902  pp.AddButton(JSROOT.ToolbarIcons.th2colorz, "Toggle color palette", "ToggleColorZ");
1903  pp.AddButton(JSROOT.ToolbarIcons.th2draw3d, "Toggle 3D mode", "Toggle3D");
1904  pp.ShowButtons();
1905  }
1906 
1907  TH2Painter.prototype.ToggleColor = function() {
1908 
1909  if (this.options.Mode3D) {
1910  this.options.Mode3D = false;
1911  this.options.Color = true;
1912  } else {
1913  this.options.Color = !this.options.Color;
1914  }
1915 
1916  this._can_move_colz = true; // indicate that next redraw can move Z scale
1917 
1918  this.Redraw();
1919 
1920  // this.DrawColorPalette(this.options.Color && this.options.Zscale);
1921  }
1922 
1923  TH2Painter.prototype.AutoZoom = function() {
1924  if (this.IsTH2Poly()) return; // not implemented
1925 
1926  var i1 = this.GetSelectIndex("x", "left", -1),
1927  i2 = this.GetSelectIndex("x", "right", 1),
1928  j1 = this.GetSelectIndex("y", "left", -1),
1929  j2 = this.GetSelectIndex("y", "right", 1),
1930  i,j, histo = this.GetHisto(), xaxis = this.GetAxis("x"), yaxis = this.GetAxis("y");
1931 
1932  if ((i1 == i2) || (j1 == j2)) return;
1933 
1934  // first find minimum
1935  var min = histo.getBinContent(i1 + 1, j1 + 1);
1936  for (i = i1; i < i2; ++i)
1937  for (j = j1; j < j2; ++j)
1938  min = Math.min(min, histo.getBinContent(i+1, j+1));
1939  if (min > 0) return; // if all points positive, no chance for autoscale
1940 
1941  var ileft = i2, iright = i1, jleft = j2, jright = j1;
1942 
1943  for (i = i1; i < i2; ++i)
1944  for (j = j1; j < j2; ++j)
1945  if (histo.getBinContent(i + 1, j + 1) > min) {
1946  if (i < ileft) ileft = i;
1947  if (i >= iright) iright = i + 1;
1948  if (j < jleft) jleft = j;
1949  if (j >= jright) jright = j + 1;
1950  }
1951 
1952  var xmin, xmax, ymin, ymax, isany = false;
1953 
1954  if ((ileft === iright-1) && (ileft > i1+1) && (iright < i2-1)) { ileft--; iright++; }
1955  if ((jleft === jright-1) && (jleft > j1+1) && (jright < j2-1)) { jleft--; jright++; }
1956 
1957  if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) {
1958  xmin = xaxis.GetBinCoord(ileft);
1959  xmax = xaxis.GetBinCoord(iright);
1960  isany = true;
1961  }
1962 
1963  if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) {
1964  ymin = yaxis.GetBinCoord(jleft);
1965  ymax = yaxis.GetBinCoord(jright);
1966  isany = true;
1967  }
1968 
1969  if (isany) this.frame_painter().Zoom(xmin, xmax, ymin, ymax);
1970  }
1971 
1972  TH2Painter.prototype.ScanContent = function(when_axis_changed) {
1973 
1974  // no need to rescan histogram while result does not depend from axis selection
1975  if (when_axis_changed && this.nbinsx && this.nbinsy) return;
1976 
1977  var i, j, histo = this.GetHisto();
1978 
1979  this.nbinsx = this.GetAxis("x").fNBins - 2;
1980  this.nbinsy = this.GetAxis("y").fNBins - 2;
1981 
1982  // used in CreateXY method
1983 
1984  this.CreateAxisFuncs(true);
1985 
1986  if (this.IsTH2Poly()) {
1987  this.gminposbin = null;
1988  this.gminbin = this.gmaxbin = 0;
1989 
1990  for (var n=0, len=histo.fBins.arr.length; n<len; ++n) {
1991  var bin_content = histo.fBins.arr[n].fContent;
1992  if (n===0) this.gminbin = this.gmaxbin = bin_content;
1993 
1994  if (bin_content < this.gminbin) this.gminbin = bin_content; else
1995  if (bin_content > this.gmaxbin) this.gmaxbin = bin_content;
1996 
1997  if (bin_content > 0)
1998  if ((this.gminposbin===null) || (this.gminposbin > bin_content)) this.gminposbin = bin_content;
1999  }
2000  } else {
2001  // global min/max, used at the moment in 3D drawing
2002  this.gminbin = this.gmaxbin = histo.getBinContent(1, 1);
2003  this.gminposbin = null;
2004  for (i = 0; i < this.nbinsx; ++i) {
2005  for (j = 0; j < this.nbinsy; ++j) {
2006  var bin_content = histo.getBinContent(i+1, j+1);
2007  if (bin_content < this.gminbin) this.gminbin = bin_content; else
2008  if (bin_content > this.gmaxbin) this.gmaxbin = bin_content;
2009  if (bin_content > 0)
2010  if ((this.gminposbin===null) || (this.gminposbin > bin_content)) this.gminposbin = bin_content;
2011  }
2012  }
2013  }
2014 
2015  // this value used for logz scale drawing
2016  if (this.gminposbin === null) this.gminposbin = this.gmaxbin*1e-4;
2017 
2018  if (this.options.Axis > 0) { // Paint histogram axis only
2019  this.draw_content = false;
2020  } else {
2021  this.draw_content = this.gmaxbin > 0;
2022  if (!this.draw_content && this.options.Zero && this.IsTH2Poly()) {
2023  this.draw_content = true;
2024  this.options.Line = 1;
2025  }
2026  }
2027  }
2028 
2029  TH2Painter.prototype.CountStat = function(cond) {
2030  var histo = this.GetObject(),
2031  stat_sum0 = 0, stat_sumx1 = 0, stat_sumy1 = 0,
2032  stat_sumx2 = 0, stat_sumy2 = 0, stat_sumxy = 0,
2033  xside, yside, xx, yy, zz,
2034  fp = this.frame_painter(),
2035  res = { name: "histo", entries: 0, integral: 0, meanx: 0, meany: 0, rmsx: 0, rmsy: 0, matrix: [0,0,0,0,0,0,0,0,0], xmax: 0, ymax:0, wmax: null };
2036 
2037  if (this.IsTH2Poly()) {
2038 
2039  var len = histo.fBins.arr.length, i, bin, n, gr, ngr, numgraphs, numpoints,
2040  pmain = this.frame_painter();
2041 
2042  for (i=0;i<len;++i) {
2043  bin = histo.fBins.arr[i];
2044 
2045  xside = 1; yside = 1;
2046 
2047  if (bin.fXmin > pmain.scale_xmax) xside = 2; else
2048  if (bin.fXmax < pmain.scale_xmin) xside = 0;
2049  if (bin.fYmin > pmain.scale_ymax) yside = 2; else
2050  if (bin.fYmax < pmain.scale_ymin) yside = 0;
2051 
2052  xx = yy = numpoints = 0;
2053  gr = bin.fPoly; numgraphs = 1;
2054  if (gr._typename === 'TMultiGraph') { numgraphs = bin.fPoly.fGraphs.arr.length; gr = null; }
2055 
2056  for (ngr=0;ngr<numgraphs;++ngr) {
2057  if (!gr || (ngr>0)) gr = bin.fPoly.fGraphs.arr[ngr];
2058 
2059  for (n=0;n<gr.fNpoints;++n) {
2060  ++numpoints;
2061  xx += gr.fX[n];
2062  yy += gr.fY[n];
2063  }
2064  }
2065 
2066  if (numpoints > 1) {
2067  xx = xx / numpoints;
2068  yy = yy / numpoints;
2069  }
2070 
2071  zz = bin.fContent;
2072 
2073  res.entries += zz;
2074 
2075  res.matrix[yside * 3 + xside] += zz;
2076 
2077  if ((xside != 1) || (yside != 1)) continue;
2078 
2079  if ((cond!=null) && !cond(xx,yy)) continue;
2080 
2081  if ((res.wmax==null) || (zz>res.wmax)) { res.wmax = zz; res.xmax = xx; res.ymax = yy; }
2082 
2083  stat_sum0 += zz;
2084  stat_sumx1 += xx * zz;
2085  stat_sumy1 += yy * zz;
2086  stat_sumx2 += xx * xx * zz;
2087  stat_sumy2 += yy * yy * zz;
2088  stat_sumxy += xx * yy * zz;
2089  }
2090  } else {
2091  var xleft = this.GetSelectIndex("x", "left"),
2092  xright = this.GetSelectIndex("x", "right"),
2093  yleft = this.GetSelectIndex("y", "left"),
2094  yright = this.GetSelectIndex("y", "right"),
2095  xi, yi, xaxis = this.GetAxis("x"), yaxis = this.GetAxis("y");
2096 
2097  for (xi = 0; xi <= this.nbinsx + 1; ++xi) {
2098  xside = (xi <= xleft) ? 0 : (xi > xright ? 2 : 1);
2099  xx = xaxis.GetBinCoord(xi - 0.5);
2100 
2101  for (yi = 0; yi <= this.nbinsy + 1; ++yi) {
2102  yside = (yi <= yleft) ? 0 : (yi > yright ? 2 : 1);
2103  yy = yaxis.GetBinCoord(yi - 0.5);
2104 
2105  zz = histo.getBinContent(xi, yi);
2106 
2107  res.entries += zz;
2108 
2109  res.matrix[yside * 3 + xside] += zz;
2110 
2111  if ((xside != 1) || (yside != 1)) continue;
2112 
2113  if ((cond!=null) && !cond(xx,yy)) continue;
2114 
2115  if ((res.wmax==null) || (zz>res.wmax)) { res.wmax = zz; res.xmax = xx; res.ymax = yy; }
2116 
2117  stat_sum0 += zz;
2118  stat_sumx1 += xx * zz;
2119  stat_sumy1 += yy * zz;
2120  stat_sumx2 += xx * xx * zz;
2121  stat_sumy2 += yy * yy * zz;
2122  stat_sumxy += xx * yy * zz;
2123  }
2124  }
2125  }
2126 
2127  if (!fp.IsAxisZoomed("x") && !fp.IsAxisZoomed("y") && (histo.fTsumw > 0)) {
2128  stat_sum0 = histo.fTsumw;
2129  stat_sumx1 = histo.fTsumwx;
2130  stat_sumx2 = histo.fTsumwx2;
2131  stat_sumy1 = histo.fTsumwy;
2132  stat_sumy2 = histo.fTsumwy2;
2133  stat_sumxy = histo.fTsumwxy;
2134  }
2135 
2136  if (stat_sum0 > 0) {
2137  res.meanx = stat_sumx1 / stat_sum0;
2138  res.meany = stat_sumy1 / stat_sum0;
2139  res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx * res.meanx));
2140  res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany * res.meany));
2141  }
2142 
2143  if (res.wmax===null) res.wmax = 0;
2144  res.integral = stat_sum0;
2145 
2146  if (histo.fEntries > 1) res.entries = histo.fEntries;
2147 
2148  return res;
2149  }
2150 
2151  TH2Painter.prototype.FillStatistic = function(stat, dostat, dofit) {
2152 
2153  // no need to refill statistic if histogram is dummy
2154  if (this.IgnoreStatsFill()) return false;
2155 
2156  var data = this.CountStat(),
2157  print_name = Math.floor(dostat % 10),
2158  print_entries = Math.floor(dostat / 10) % 10,
2159  print_mean = Math.floor(dostat / 100) % 10,
2160  print_rms = Math.floor(dostat / 1000) % 10,
2161  print_under = Math.floor(dostat / 10000) % 10,
2162  print_over = Math.floor(dostat / 100000) % 10,
2163  print_integral = Math.floor(dostat / 1000000) % 10,
2164  print_skew = Math.floor(dostat / 10000000) % 10,
2165  print_kurt = Math.floor(dostat / 100000000) % 10;
2166 
2167  stat.ClearPave();
2168 
2169  if (print_name > 0)
2170  stat.AddText(data.name);
2171 
2172  if (print_entries > 0)
2173  stat.AddText("Entries = " + stat.Format(data.entries,"entries"));
2174 
2175  if (print_mean > 0) {
2176  stat.AddText("Mean x = " + stat.Format(data.meanx));
2177  stat.AddText("Mean y = " + stat.Format(data.meany));
2178  }
2179 
2180  if (print_rms > 0) {
2181  stat.AddText("Std Dev x = " + stat.Format(data.rmsx));
2182  stat.AddText("Std Dev y = " + stat.Format(data.rmsy));
2183  }
2184 
2185  if (print_integral > 0)
2186  stat.AddText("Integral = " + stat.Format(data.matrix[4],"entries"));
2187 
2188  if (print_skew > 0) {
2189  stat.AddText("Skewness x = <undef>");
2190  stat.AddText("Skewness y = <undef>");
2191  }
2192 
2193  if (print_kurt > 0)
2194  stat.AddText("Kurt = <undef>");
2195 
2196  if ((print_under > 0) || (print_over > 0)) {
2197  var m = data.matrix;
2198 
2199  stat.AddText("" + m[6].toFixed(0) + " | " + m[7].toFixed(0) + " | " + m[7].toFixed(0));
2200  stat.AddText("" + m[3].toFixed(0) + " | " + m[4].toFixed(0) + " | " + m[5].toFixed(0));
2201  stat.AddText("" + m[0].toFixed(0) + " | " + m[1].toFixed(0) + " | " + m[2].toFixed(0));
2202  }
2203 
2204  // if (dofit) stat.FillFunctionStat(this.FindFunction('TF1'), dofit);
2205 
2206  return true;
2207  }
2208 
2209  TH2Painter.prototype.DrawBinsColor = function(w,h) {
2210  var histo = this.GetHisto(),
2211  handle = this.PrepareColorDraw(),
2212  colPaths = [], currx = [], curry = [],
2213  colindx, cmd1, cmd2, i, j, binz;
2214 
2215  // now start build
2216  for (i = handle.i1; i < handle.i2; ++i) {
2217  for (j = handle.j1; j < handle.j2; ++j) {
2218  binz = histo.getBinContent(i + 1, j + 1);
2219  colindx = this.getContourColor(binz, true);
2220  if (binz===0) {
2221  if (!this.options.Zero) continue;
2222  if ((colindx === null) && this._show_empty_bins) colindx = 0;
2223  }
2224  if (colindx === null) continue;
2225 
2226  cmd1 = "M"+handle.grx[i]+","+handle.gry[j+1];
2227  if (colPaths[colindx] === undefined) {
2228  colPaths[colindx] = cmd1;
2229  } else{
2230  cmd2 = "m" + (handle.grx[i]-currx[colindx]) + "," + (handle.gry[j+1]-curry[colindx]);
2231  colPaths[colindx] += (cmd2.length < cmd1.length) ? cmd2 : cmd1;
2232  }
2233 
2234  currx[colindx] = handle.grx[i];
2235  curry[colindx] = handle.gry[j+1];
2236 
2237  colPaths[colindx] += "v" + (handle.gry[j] - handle.gry[j+1]) +
2238  "h" + (handle.grx[i+1] - handle.grx[i]) +
2239  "v" + (handle.gry[j+1] - handle.gry[j]) + "z";
2240  }
2241  }
2242 
2243  for (colindx=0;colindx<colPaths.length;++colindx)
2244  if (colPaths[colindx] !== undefined)
2245  this.draw_g
2246  .append("svg:path")
2247  .attr("palette-index", colindx)
2248  .attr("fill", this.fPalette.getColor(colindx))
2249  .attr("d", colPaths[colindx]);
2250 
2251  return handle;
2252  }
2253 
2254  TH2Painter.prototype.BuildContour = function(handle, levels, palette, contour_func) {
2255  var histo = this.GetHisto(), ddd = 0,
2256  painter = this,
2257  kMAXCONTOUR = 2004,
2258  kMAXCOUNT = 2000,
2259  // arguments used in the PaintContourLine
2260  xarr = new Float32Array(2*kMAXCONTOUR),
2261  yarr = new Float32Array(2*kMAXCONTOUR),
2262  itarr = new Int32Array(2*kMAXCONTOUR),
2263  lj = 0, ipoly, poly, polys = [], np, npmax = 0,
2264  x = [0.,0.,0.,0.], y = [0.,0.,0.,0.], zc = [0.,0.,0.,0.], ir = [0,0,0,0],
2265  i, j, k, n, m, ix, ljfill, count,
2266  xsave, ysave, itars, ix, jx;
2267 
2268  function BinarySearch(zc) {
2269  for (var kk=0;kk<levels.length;++kk)
2270  if (zc<levels[kk]) return kk-1;
2271  return levels.length-1;
2272  }
2273 
2274  function PaintContourLine(elev1, icont1, x1, y1, elev2, icont2, x2, y2) {
2275  /* Double_t *xarr, Double_t *yarr, Int_t *itarr, Double_t *levels */
2276  var vert = (x1 === x2),
2277  tlen = vert ? (y2 - y1) : (x2 - x1),
2278  n = icont1 +1,
2279  tdif = elev2 - elev1,
2280  ii = lj-1,
2281  maxii = kMAXCONTOUR/2 -3 + lj,
2282  icount = 0,
2283  xlen, pdif, diff, elev;
2284 
2285  while (n <= icont2 && ii <= maxii) {
2286 // elev = fH->GetContourLevel(n);
2287  elev = levels[n];
2288  diff = elev - elev1;
2289  pdif = diff/tdif;
2290  xlen = tlen*pdif;
2291  if (vert) {
2292  xarr[ii] = x1;
2293  yarr[ii] = y1 + xlen;
2294  } else {
2295  xarr[ii] = x1 + xlen;
2296  yarr[ii] = y1;
2297  }
2298  itarr[ii] = n;
2299  icount++;
2300  ii +=2;
2301  n++;
2302  }
2303  return icount;
2304  }
2305 
2306  var arrx = handle.original ? handle.origx : handle.grx,
2307  arry = handle.original ? handle.origy : handle.gry;
2308 
2309  for (j = handle.j1; j < handle.j2-1; ++j) {
2310 
2311  y[1] = y[0] = (arry[j] + arry[j+1])/2;
2312  y[3] = y[2] = (arry[j+1] + arry[j+2])/2;
2313 
2314  for (i = handle.i1; i < handle.i2-1; ++i) {
2315 
2316  zc[0] = histo.getBinContent(i+1, j+1);
2317  zc[1] = histo.getBinContent(i+2, j+1);
2318  zc[2] = histo.getBinContent(i+2, j+2);
2319  zc[3] = histo.getBinContent(i+1, j+2);
2320 
2321  for (k=0;k<4;k++)
2322  ir[k] = BinarySearch(zc[k]);
2323 
2324  if ((ir[0] !== ir[1]) || (ir[1] !== ir[2]) || (ir[2] !== ir[3]) || (ir[3] !== ir[0])) {
2325  x[3] = x[0] = (arrx[i] + arrx[i+1])/2;
2326  x[2] = x[1] = (arrx[i+1] + arrx[i+2])/2;
2327 
2328  if (zc[0] <= zc[1]) n = 0; else n = 1;
2329  if (zc[2] <= zc[3]) m = 2; else m = 3;
2330  if (zc[n] > zc[m]) n = m;
2331  n++;
2332  lj=1;
2333  for (ix=1;ix<=4;ix++) {
2334  m = n%4 + 1;
2335  ljfill = PaintContourLine(zc[n-1],ir[n-1],x[n-1],y[n-1],
2336  zc[m-1],ir[m-1],x[m-1],y[m-1]);
2337  lj += 2*ljfill;
2338  n = m;
2339  }
2340 
2341  if (zc[0] <= zc[1]) n = 0; else n = 1;
2342  if (zc[2] <= zc[3]) m = 2; else m = 3;
2343  if (zc[n] > zc[m]) n = m;
2344  n++;
2345  lj=2;
2346  for (ix=1;ix<=4;ix++) {
2347  if (n == 1) m = 4;
2348  else m = n-1;
2349  ljfill = PaintContourLine(zc[n-1],ir[n-1],x[n-1],y[n-1],
2350  zc[m-1],ir[m-1],x[m-1],y[m-1]);
2351  lj += 2*ljfill;
2352  n = m;
2353  }
2354  // Re-order endpoints
2355 
2356  count = 0;
2357  for (ix=1; ix<=lj-5; ix +=2) {
2358  //count = 0;
2359  while (itarr[ix-1] != itarr[ix]) {
2360  xsave = xarr[ix];
2361  ysave = yarr[ix];
2362  itars = itarr[ix];
2363  for (jx=ix; jx<=lj-5; jx +=2) {
2364  xarr[jx] = xarr[jx+2];
2365  yarr[jx] = yarr[jx+2];
2366  itarr[jx] = itarr[jx+2];
2367  }
2368  xarr[lj-3] = xsave;
2369  yarr[lj-3] = ysave;
2370  itarr[lj-3] = itars;
2371  if (count > kMAXCOUNT) break;
2372  count++;
2373  }
2374  }
2375 
2376  if (count > kMAXCOUNT) continue;
2377 
2378  for (ix=1; ix<=lj-2; ix +=2) {
2379 
2380  ipoly = itarr[ix-1];
2381 
2382  if ((ipoly >= 0) && (ipoly < levels.length)) {
2383  poly = polys[ipoly];
2384  if (!poly)
2385  poly = polys[ipoly] = JSROOT.CreateTPolyLine(kMAXCONTOUR*4, true);
2386 
2387  np = poly.fLastPoint;
2388  if (np < poly.fN-2) {
2389  poly.fX[np+1] = Math.round(xarr[ix-1]); poly.fY[np+1] = Math.round(yarr[ix-1]);
2390  poly.fX[np+2] = Math.round(xarr[ix]); poly.fY[np+2] = Math.round(yarr[ix]);
2391  poly.fLastPoint = np+2;
2392  npmax = Math.max(npmax, poly.fLastPoint+1);
2393  } else {
2394  // console.log('reject point??', poly.fLastPoint);
2395  }
2396  }
2397  }
2398  } // end of if (ir[0]
2399  } // end of j
2400  } // end of i
2401 
2402  var polysort = new Int32Array(levels.length), first = 0;
2403  //find first positive contour
2404  for (ipoly=0;ipoly<levels.length;ipoly++) {
2405  if (levels[ipoly] >= 0) { first = ipoly; break; }
2406  }
2407  //store negative contours from 0 to minimum, then all positive contours
2408  k = 0;
2409  for (ipoly=first-1;ipoly>=0;ipoly--) {polysort[k] = ipoly; k++;}
2410  for (ipoly=first;ipoly<levels.length;ipoly++) { polysort[k] = ipoly; k++;}
2411 
2412  var xp = new Float32Array(2*npmax),
2413  yp = new Float32Array(2*npmax);
2414 
2415  for (k=0;k<levels.length;++k) {
2416 
2417  ipoly = polysort[k];
2418  poly = polys[ipoly];
2419  if (!poly) continue;
2420 
2421  var colindx = palette.calcColorIndex(ipoly, levels.length),
2422  xx = poly.fX, yy = poly.fY, np = poly.fLastPoint+1,
2423  istart = 0, iminus, iplus, xmin = 0, ymin = 0, nadd;
2424 
2425  while (true) {
2426 
2427  iminus = npmax;
2428  iplus = iminus+1;
2429  xp[iminus]= xx[istart]; yp[iminus] = yy[istart];
2430  xp[iplus] = xx[istart+1]; yp[iplus] = yy[istart+1];
2431  xx[istart] = xx[istart+1] = xmin;
2432  yy[istart] = yy[istart+1] = ymin;
2433  while (true) {
2434  nadd = 0;
2435  for (i=2;i<np;i+=2) {
2436  if ((iplus < 2*npmax-1) && (xx[i] === xp[iplus]) && (yy[i] === yp[iplus])) {
2437  iplus++;
2438  xp[iplus] = xx[i+1]; yp[iplus] = yy[i+1];
2439  xx[i] = xx[i+1] = xmin;
2440  yy[i] = yy[i+1] = ymin;
2441  nadd++;
2442  }
2443  if ((iminus > 0) && (xx[i+1] === xp[iminus]) && (yy[i+1] === yp[iminus])) {
2444  iminus--;
2445  xp[iminus] = xx[i]; yp[iminus] = yy[i];
2446  xx[i] = xx[i+1] = xmin;
2447  yy[i] = yy[i+1] = ymin;
2448  nadd++;
2449  }
2450  }
2451  if (nadd == 0) break;
2452  }
2453 
2454  if ((iminus+1 < iplus) && (iminus>=0))
2455  contour_func(colindx, xp, yp, iminus, iplus, ipoly);
2456 
2457  istart = 0;
2458  for (i=2;i<np;i+=2) {
2459  if (xx[i] !== xmin && yy[i] !== ymin) {
2460  istart = i;
2461  break;
2462  }
2463  }
2464 
2465  if (istart === 0) break;
2466  }
2467  }
2468  }
2469 
2470  TH2Painter.prototype.DrawBinsContour = function(frame_w,frame_h) {
2471  var handle = this.PrepareColorDraw({ rounding: false, extra: 100, original: this.options.Proj != 0 }),
2472  levels = this.GetContour(),
2473  palette = this.GetPalette(),
2474  painter = this, main = this.frame_painter();
2475 
2476  function BuildPath(xp,yp,iminus,iplus) {
2477  var cmd = "", last = null, pnt = null, i;
2478  for (i=iminus;i<=iplus;++i) {
2479  pnt = null;
2480  switch (painter.options.Proj) {
2481  case 1: pnt = main.ProjectAitoff2xy(xp[i], yp[i]); break;
2482  case 2: pnt = main.ProjectMercator2xy(xp[i], yp[i]); break;
2483  case 3: pnt = main.ProjectSinusoidal2xy(xp[i], yp[i]); break;
2484  case 4: pnt = main.ProjectParabolic2xy(xp[i], yp[i]); break;
2485  }
2486  if (pnt) {
2487  pnt.x = main.grx(pnt.x);
2488  pnt.y = main.gry(pnt.y);
2489  } else {
2490  pnt = { x: xp[i], y: yp[i] };
2491  }
2492  pnt.x = Math.round(pnt.x);
2493  pnt.y = Math.round(pnt.y);
2494  if (!cmd) cmd = "M" + pnt.x + "," + pnt.y;
2495  else if ((pnt.x != last.x) && (pnt.y != last.y)) cmd += "l" + (pnt.x - last.x) + "," + (pnt.y - last.y);
2496  else if (pnt.x != last.x) cmd += "h" + (pnt.x - last.x);
2497  else if (pnt.y != last.y) cmd += "v" + (pnt.y - last.y);
2498  last = pnt;
2499  }
2500  return cmd;
2501  }
2502 
2503  if (this.options.Contour===14) {
2504  var dd = "M0,0h"+frame_w+"v"+frame_h+"h-"+frame_w;
2505  if (this.options.Proj) {
2506  var sz = handle.j2 - handle.j1, xd = new Float32Array(sz*2), yd = new Float32Array(sz*2);
2507  for (var i=0;i<sz;++i) {
2508  xd[i] = handle.origx[handle.i1];
2509  yd[i] = (handle.origy[handle.j1]*(i+0.5) + handle.origy[handle.j2]*(sz-0.5-i))/sz;
2510  xd[i+sz] = handle.origx[handle.i2];
2511  yd[i+sz] = (handle.origy[handle.j2]*(i+0.5) + handle.origy[handle.j1]*(sz-0.5-i))/sz;
2512  }
2513  dd = BuildPath(xd,yd,0,2*sz-1);
2514  }
2515 
2516  this.draw_g
2517  .append("svg:path")
2518  .attr("d", dd + "z")
2519  .style('stroke','none')
2520  .style("fill", palette.calcColor(0, levels.length));
2521  }
2522 
2523  this.BuildContour(handle, levels, palette,
2524  function(colindx,xp,yp,iminus,iplus) {
2525  var icol = palette.getColor(colindx),
2526  fillcolor = icol, lineatt = null;
2527 
2528  switch (painter.options.Contour) {
2529  case 1: break;
2530  case 11: fillcolor = 'none'; lineatt = new JSROOT.TAttLineHandler({ color: icol }); break;
2531  case 12: fillcolor = 'none'; lineatt = new JSROOT.TAttLineHandler({ color:1, style: (colindx%5 + 1), width: 1 }); break;
2532  case 13: fillcolor = 'none'; lineatt = painter.lineatt; break;
2533  case 14: break;
2534  }
2535 
2536  var elem = painter.draw_g
2537  .append("svg:path")
2538  .attr("class","th2_contour")
2539  .attr("d", BuildPath(xp,yp,iminus,iplus) + (fillcolor == 'none' ? "" : "z"))
2540  .style("fill", fillcolor);
2541 
2542  if (lineatt!==null)
2543  elem.call(lineatt.func);
2544  else
2545  elem.style('stroke','none');
2546  }
2547  );
2548 
2549  handle.hide_only_zeros = true; // text drawing suppress only zeros
2550 
2551  return handle;
2552  }
2553 
2554  TH2Painter.prototype.CreatePolyBin = function(pmain, bin, text_pos) {
2555  var cmd = "", ngr, ngraphs = 1, gr = null;
2556 
2557  if (bin.fPoly._typename=='TMultiGraph')
2558  ngraphs = bin.fPoly.fGraphs.arr.length;
2559  else
2560  gr = bin.fPoly;
2561 
2562  if (text_pos)
2563  bin._sumx = bin._sumy = bin._suml = 0;
2564 
2565  function AddPoint(x1,y1,x2,y2) {
2566  var len = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
2567  bin._sumx += (x1+x2)*len/2;
2568  bin._sumy += (y1+y2)*len/2;
2569  bin._suml += len;
2570  }
2571 
2572  for (ngr = 0; ngr < ngraphs; ++ ngr) {
2573  if (!gr || (ngr>0)) gr = bin.fPoly.fGraphs.arr[ngr];
2574 
2575  var npnts = gr.fNpoints, n,
2576  x = gr.fX, y = gr.fY,
2577  grx = Math.round(pmain.grx(x[0])),
2578  gry = Math.round(pmain.gry(y[0])),
2579  nextx, nexty;
2580 
2581  if ((npnts>2) && (x[0]==x[npnts-1]) && (y[0]==y[npnts-1])) npnts--;
2582 
2583  cmd += "M"+grx+","+gry;
2584 
2585  for (n=1;n<npnts;++n) {
2586  nextx = Math.round(pmain.grx(x[n]));
2587  nexty = Math.round(pmain.gry(y[n]));
2588  if (text_pos) AddPoint(grx,gry, nextx, nexty);
2589  if ((grx!==nextx) || (gry!==nexty)) {
2590  if (grx===nextx) cmd += "v" + (nexty - gry); else
2591  if (gry===nexty) cmd += "h" + (nextx - grx); else
2592  cmd += "l" + (nextx - grx) + "," + (nexty - gry);
2593  }
2594  grx = nextx; gry = nexty;
2595  }
2596 
2597  if (text_pos) AddPoint(grx, gry, Math.round(pmain.grx(x[0])), Math.round(pmain.gry(y[0])));
2598  cmd += "z";
2599  }
2600 
2601  if (text_pos) {
2602  if (bin._suml > 0) {
2603  bin._midx = Math.round(bin._sumx / bin._suml);
2604  bin._midy = Math.round(bin._sumy / bin._suml);
2605  } else {
2606  bin._midx = Math.round(pmain.grx((bin.fXmin + bin.fXmax)/2));
2607  bin._midy = Math.round(pmain.gry((bin.fYmin + bin.fYmax)/2));
2608  }
2609  }
2610 
2611  return cmd;
2612  }
2613 
2614  TH2Painter.prototype.DrawPolyBinsColor = function(w,h) {
2615  var histo = this.GetHisto(),
2616  pmain = this.frame_painter(),
2617  colPaths = [], textbins = [],
2618  colindx, cmd, bin, item,
2619  i, len = histo.fBins.arr.length;
2620 
2621  // force recalculations of contours
2622  this.fContour = null;
2623  this.fCustomContour = false;
2624 
2625  // use global coordinates
2626  this.maxbin = this.gmaxbin;
2627  this.minbin = this.gminbin;
2628  this.minposbin = this.gminposbin;
2629 
2630  for (i = 0; i < len; ++ i) {
2631  bin = histo.fBins.arr[i];
2632  colindx = this.getContourColor(bin.fContent, true);
2633  if (colindx === null) continue;
2634  if (bin.fContent === 0) {
2635  if (!this.options.Zero || !this.options.Line) continue;
2636  colindx = 0;
2637  }
2638 
2639  // check if bin outside visible range
2640  if ((bin.fXmin > pmain.scale_xmax) || (bin.fXmax < pmain.scale_xmin) ||
2641  (bin.fYmin > pmain.scale_ymax) || (bin.fYmax < pmain.scale_ymin)) continue;
2642 
2643  cmd = this.CreatePolyBin(pmain, bin, this.options.Text && bin.fContent);
2644 
2645  if (colPaths[colindx] === undefined)
2646  colPaths[colindx] = cmd;
2647  else
2648  colPaths[colindx] += cmd;
2649 
2650  if (this.options.Text) textbins.push(bin);
2651  }
2652 
2653  for (colindx=0;colindx<colPaths.length;++colindx)
2654  if (colPaths[colindx]) {
2655  item = this.draw_g
2656  .append("svg:path")
2657  .attr("palette-index", colindx)
2658  .attr("fill", colindx ? this.fPalette.getColor(colindx) : "none")
2659  .attr("d", colPaths[colindx]);
2660  if (this.options.Line)
2661  item.call(this.lineatt.func);
2662  }
2663 
2664  if (textbins.length > 0) {
2665  var text_col = this.get_color(histo.fMarkerColor),
2666  text_angle = -1*this.options.TextAngle,
2667  text_g = this.draw_g.append("svg:g").attr("class","th2poly_text"),
2668  text_size = 12;
2669 
2670  if ((histo.fMarkerSize!==1) && text_angle)
2671  text_size = Math.round(0.02*h*histo.fMarkerSize);
2672 
2673  this.StartTextDrawing(42, text_size, text_g, text_size);
2674 
2675  for (i = 0; i < textbins.length; ++ i) {
2676  bin = textbins[i];
2677 
2678  var lbl = "";
2679 
2680  if (!this.options.TextKind) {
2681  lbl = (Math.round(bin.fContent) === bin.fContent) ? bin.fContent.toString() :
2682  JSROOT.FFormat(bin.fContent, JSROOT.gStyle.fPaintTextFormat);
2683  } else {
2684  if (bin.fPoly) lbl = bin.fPoly.fName;
2685  if (lbl === "Graph") lbl = "";
2686  if (!lbl) lbl = bin.fNumber;
2687  }
2688 
2689  this.DrawText({ align: 22, x: bin._midx, y: bin._midy, rotate: text_angle, text: lbl, color: text_col, latex: 0, draw_g: text_g });
2690  }
2691 
2692  this.FinishTextDrawing(text_g, null);
2693  }
2694 
2695  return { poly: true };
2696  }
2697 
2698  TH2Painter.prototype.DrawBinsText = function(w, h, handle) {
2699  var histo = this.GetHisto(),
2700  i,j,binz,colindx,binw,binh,lbl,posx,posy,sizex,sizey;
2701 
2702  if (handle===null) handle = this.PrepareColorDraw({ rounding: false });
2703 
2704  var text_col = this.get_color(histo.fMarkerColor),
2705  text_angle = -1*this.options.TextAngle,
2706  text_g = this.draw_g.append("svg:g").attr("class","th2_text"),
2707  text_size = 20, text_offset = 0,
2708  profile2d = (this.options.TextKind == "E") &&
2709  this.MatchObjectType('TProfile2D') && (typeof histo.getBinEntries=='function');
2710 
2711  if ((histo.fMarkerSize!==1) && text_angle)
2712  text_size = Math.round(0.02*h*histo.fMarkerSize);
2713 
2714  if (this.options.fBarOffset!==0) text_offset = this.options.fBarOffset*1e-3;
2715 
2716  this.StartTextDrawing(42, text_size, text_g, text_size);
2717 
2718  for (i = handle.i1; i < handle.i2; ++i)
2719  for (j = handle.j1; j < handle.j2; ++j) {
2720  binz = histo.getBinContent(i+1, j+1);
2721  if ((binz === 0) && !this._show_empty_bins) continue;
2722 
2723  binw = handle.grx[i+1] - handle.grx[i];
2724  binh = handle.gry[j] - handle.gry[j+1];
2725 
2726  if (profile2d)
2727  binz = histo.getBinEntries(i+1, j+1);
2728 
2729  lbl = (binz === Math.round(binz)) ? binz.toString() :
2730  JSROOT.FFormat(binz, JSROOT.gStyle.fPaintTextFormat);
2731 
2732  if (text_angle /*|| (histo.fMarkerSize!==1)*/) {
2733  posx = Math.round(handle.grx[i] + binw*0.5);
2734  posy = Math.round(handle.gry[j+1] + binh*(0.5 + text_offset));
2735  sizex = 0;
2736  sizey = 0;
2737  } else {
2738  posx = Math.round(handle.grx[i] + binw*0.1);
2739  posy = Math.round(handle.gry[j+1] + binh*(0.1 + text_offset));
2740  sizex = Math.round(binw*0.8);
2741  sizey = Math.round(binh*0.8);
2742  }
2743 
2744  this.DrawText({ align: 22, x: posx, y: posy, width: sizex, height: sizey, rotate: text_angle, text: lbl, color: text_col, latex: 0, draw_g: text_g });
2745  }
2746 
2747  this.FinishTextDrawing(text_g, null);
2748 
2749  handle.hide_only_zeros = true; // text drawing suppress only zeros
2750 
2751  return handle;
2752  }
2753 
2754  TH2Painter.prototype.DrawBinsArrow = function(w, h) {
2755  var histo = this.GetHisto(), cmd = "",
2756  i,j,binz,colindx,binw,binh,lbl, loop, dn = 1e-30, dx, dy, xc,yc,
2757  dxn,dyn,x1,x2,y1,y2, anr,si,co,
2758  handle = this.PrepareColorDraw({ rounding: false }),
2759  scale_x = (handle.grx[handle.i2] - handle.grx[handle.i1])/(handle.i2 - handle.i1 + 1-0.03)/2,
2760  scale_y = (handle.gry[handle.j2] - handle.gry[handle.j1])/(handle.j2 - handle.j1 + 1-0.03)/2;
2761 
2762  for (var loop=0;loop<2;++loop)
2763  for (i = handle.i1; i < handle.i2; ++i)
2764  for (j = handle.j1; j < handle.j2; ++j) {
2765 
2766  if (i === handle.i1) {
2767  dx = histo.getBinContent(i+2, j+1) - histo.getBinContent(i+1, j+1);
2768  } else if (i === handle.i2-1) {
2769  dx = histo.getBinContent(i+1, j+1) - histo.getBinContent(i, j+1);
2770  } else {
2771  dx = 0.5*(histo.getBinContent(i+2, j+1) - histo.getBinContent(i, j+1));
2772  }
2773  if (j === handle.j1) {
2774  dy = histo.getBinContent(i+1, j+2) - histo.getBinContent(i+1, j+1);
2775  } else if (j === handle.j2-1) {
2776  dy = histo.getBinContent(i+1, j+1) - histo.getBinContent(i+1, j);
2777  } else {
2778  dy = 0.5*(histo.getBinContent(i+1, j+2) - histo.getBinContent(i+1, j));
2779  }
2780 
2781  if (loop===0) {
2782  dn = Math.max(dn, Math.abs(dx), Math.abs(dy));
2783  } else {
2784  xc = (handle.grx[i] + handle.grx[i+1])/2;
2785  yc = (handle.gry[j] + handle.gry[j+1])/2;
2786  dxn = scale_x*dx/dn;
2787  dyn = scale_y*dy/dn;
2788  x1 = xc - dxn;
2789  x2 = xc + dxn;
2790  y1 = yc - dyn;
2791  y2 = yc + dyn;
2792  dx = Math.round(x2-x1);
2793  dy = Math.round(y2-y1);
2794 
2795  if ((dx!==0) || (dy!==0)) {
2796  cmd += "M"+Math.round(x1)+","+Math.round(y1)+"l"+dx+","+dy;
2797 
2798  if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
2799  anr = Math.sqrt(2/(dx*dx + dy*dy));
2800  si = Math.round(anr*(dx + dy));
2801  co = Math.round(anr*(dx - dy));
2802  if ((si!==0) && (co!==0))
2803  cmd+="l"+(-si)+","+co + "m"+si+","+(-co) + "l"+(-co)+","+(-si);
2804  }
2805  }
2806  }
2807  }
2808 
2809  this.draw_g
2810  .append("svg:path")
2811  .attr("class","th2_arrows")
2812  .attr("d", cmd)
2813  .style("fill", "none")
2814  .call(this.lineatt.func);
2815 
2816  return handle;
2817  }
2818 
2819 
2820  TH2Painter.prototype.DrawBinsBox = function(w,h) {
2821 
2822  var histo = this.GetHisto(),
2823  handle = this.PrepareColorDraw({ rounding: false }),
2824  main = this.frame_painter();
2825 
2826  if (main.maxbin === main.minbin) {
2827  main.maxbin = this.gmaxbin;
2828  main.minbin = this.gminbin;
2829  main.minposbin = this.gminposbin;
2830  }
2831  if (main.maxbin === main.minbin)
2832  main.minbin = Math.min(0, main.maxbin-1);
2833 
2834  var absmax = Math.max(Math.abs(main.maxbin), Math.abs(main.minbin)),
2835  absmin = Math.max(0, main.minbin),
2836  i, j, binz, absz, res = "", cross = "", btn1 = "", btn2 = "",
2837  colindx, zdiff, dgrx, dgry, xx, yy, ww, hh, cmd1, cmd2,
2838  xyfactor = 1, uselogz = false, logmin = 0, logmax = 1;
2839 
2840  if (this.root_pad().fLogz && (absmax>0)) {
2841  uselogz = true;
2842  logmax = Math.log(absmax);
2843  if (absmin>0) logmin = Math.log(absmin); else
2844  if ((main.minposbin>=1) && (main.minposbin<100)) logmin = Math.log(0.7); else
2845  logmin = (main.minposbin > 0) ? Math.log(0.7*main.minposbin) : logmax - 10;
2846  if (logmin >= logmax) logmin = logmax - 10;
2847  xyfactor = 1. / (logmax - logmin);
2848  } else {
2849  xyfactor = 1. / (absmax - absmin);
2850  }
2851 
2852  // now start build
2853  for (i = handle.i1; i < handle.i2; ++i) {
2854  for (j = handle.j1; j < handle.j2; ++j) {
2855  binz = histo.getBinContent(i + 1, j + 1);
2856  absz = Math.abs(binz);
2857  if ((absz === 0) || (absz < absmin)) continue;
2858 
2859  zdiff = uselogz ? ((absz>0) ? Math.log(absz) - logmin : 0) : (absz - absmin);
2860  // area of the box should be proportional to absolute bin content
2861  zdiff = 0.5 * ((zdiff < 0) ? 1 : (1 - Math.sqrt(zdiff * xyfactor)));
2862  // avoid oversized bins
2863  if (zdiff < 0) zdiff = 0;
2864 
2865  ww = handle.grx[i+1] - handle.grx[i];
2866  hh = handle.gry[j] - handle.gry[j+1];
2867 
2868  dgrx = zdiff * ww;
2869  dgry = zdiff * hh;
2870 
2871  xx = Math.round(handle.grx[i] + dgrx);
2872  yy = Math.round(handle.gry[j+1] + dgry);
2873 
2874  ww = Math.max(Math.round(ww - 2*dgrx), 1);
2875  hh = Math.max(Math.round(hh - 2*dgry), 1);
2876 
2877  res += "M"+xx+","+yy + "v"+hh + "h"+ww + "v-"+hh + "z";
2878 
2879  if ((binz<0) && (this.options.BoxStyle === 10))
2880  cross += "M"+xx+","+yy + "l"+ww+","+hh + "M"+(xx+ww)+","+yy + "l-"+ww+","+hh;
2881 
2882  if ((this.options.BoxStyle === 11) && (ww>5) && (hh>5)) {
2883  var pww = Math.round(ww*0.1),
2884  phh = Math.round(hh*0.1),
2885  side1 = "M"+xx+","+yy + "h"+ww + "l"+(-pww)+","+phh + "h"+(2*pww-ww) +
2886  "v"+(hh-2*phh)+ "l"+(-pww)+","+phh + "z",
2887  side2 = "M"+(xx+ww)+","+(yy+hh) + "v"+(-hh) + "l"+(-pww)+","+phh + "v"+(hh-2*phh)+
2888  "h"+(2*pww-ww) + "l"+(-pww)+","+phh + "z";
2889  if (binz<0) { btn2+=side1; btn1+=side2; }
2890  else { btn1+=side1; btn2+=side2; }
2891  }
2892  }
2893  }
2894 
2895  if (res.length > 0) {
2896  var elem = this.draw_g.append("svg:path")
2897  .attr("d", res)
2898  .call(this.fillatt.func);
2899  if ((this.options.BoxStyle === 11) || !this.fillatt.empty())
2900  elem.style('stroke','none');
2901  else
2902  elem.call(this.lineatt.func);
2903  }
2904 
2905  if ((btn1.length>0) && (this.fillatt.color !== 'none'))
2906  this.draw_g.append("svg:path")
2907  .attr("d", btn1)
2908  .style("stroke","none")
2909  .call(this.fillatt.func)
2910  .style("fill", d3.rgb(this.fillatt.color).brighter(0.5).toString());
2911 
2912  if (btn2.length>0)
2913  this.draw_g.append("svg:path")
2914  .attr("d", btn2)
2915  .style("stroke","none")
2916  .call(this.fillatt.func)
2917  .style("fill", this.fillatt.color === 'none' ? 'red' : d3.rgb(this.fillatt.color).darker(0.5).toString());
2918 
2919  if (cross.length > 0) {
2920  var elem = this.draw_g.append("svg:path")
2921  .attr("d", cross)
2922  .style("fill", "none");
2923  if (this.lineatt.color !== 'none')
2924  elem.call(this.lineatt.func);
2925  else
2926  elem.style('stroke','black');
2927  }
2928 
2929  return handle;
2930  }
2931 
2932  TH2Painter.prototype.DrawCandle = function(w,h) {
2933  var histo = this.GetHisto(), yaxis = this.GetAxis("y"),
2934  handle = this.PrepareColorDraw(),
2935  pmain = this.frame_painter(), // used for axis values conversions
2936  i, j, y, sum0, sum1, sum2, cont, center, counter, integral, w, pnt,
2937  bars = "", markers = "", posy;
2938 
2939  // create attribute only when necessary
2940  if (histo.fMarkerColor === 1) histo.fMarkerColor = histo.fLineColor;
2941  this.createAttMarker({ attr: histo, style: 5 });
2942 
2943  // reset absolution position for markers
2944  this.markeratt.reset_pos();
2945 
2946  handle.candle = []; // array of drawn points
2947 
2948  // loop over visible x-bins
2949  for (i = handle.i1; i < handle.i2; ++i) {
2950  sum1 = 0;
2951  //estimate integral
2952  integral = 0;
2953  counter = 0;
2954  for (j = 0; j < this.nbinsy; ++j) {
2955  integral += histo.getBinContent(i+1,j+1);
2956  }
2957  pnt = { bin:i, meany:0, m25y:0, p25y:0, median:0, iqr:0, whiskerp:0, whiskerm:0};
2958  //estimate quantiles... simple function... not so nice as GetQuantiles
2959  for (j = 0; j < this.nbinsy; ++j) {
2960  cont = histo.getBinContent(i+1,j+1);
2961  posy = yaxis.GetBinCoord(j + 0.5);
2962  if (counter/integral < 0.001 && (counter + cont)/integral >=0.001) pnt.whiskerm = posy; // Lower whisker
2963  if (counter/integral < 0.25 && (counter + cont)/integral >=0.25) pnt.m25y = posy; // Lower edge of box
2964  if (counter/integral < 0.5 && (counter + cont)/integral >=0.5) pnt.median = posy; //Median
2965  if (counter/integral < 0.75 && (counter + cont)/integral >=0.75) pnt.p25y = posy; //Upper edge of box
2966  if (counter/integral < 0.999 && (counter + cont)/integral >=0.999) pnt.whiskerp = posy; // Upper whisker
2967  counter += cont;
2968  y = posy; // center of y bin coordinate
2969  sum1 += cont*y;
2970  }
2971  if (counter > 0) {
2972  pnt.meany = sum1/counter;
2973  }
2974  pnt.iqr = pnt.p25y-pnt.m25y;
2975 
2976  //Whiskers cannot exceed 1.5*iqr from box
2977  if ((pnt.m25y-1.5*pnt.iqr) > pnt.whsikerm) {
2978  pnt.whiskerm = pnt.m25y-1.5*pnt.iqr;
2979  }
2980  if ((pnt.p25y+1.5*pnt.iqr) < pnt.whiskerp) {
2981  pnt.whiskerp = pnt.p25y+1.5*pnt.iqr;
2982  }
2983 
2984  // exclude points with negative y when log scale is specified
2985  if (pmain.logy && (pnt.whiskerm<=0)) continue;
2986 
2987  w = handle.grx[i+1] - handle.grx[i];
2988  w *= 0.66;
2989  center = (handle.grx[i+1] + handle.grx[i]) / 2 + this.options.fBarOffset/1000*w;
2990  if (this.options.fBarWidth >0) w = w * this.options.fBarWidth / 1000;
2991 
2992  pnt.x1 = Math.round(center - w/2);
2993  pnt.x2 = Math.round(center + w/2);
2994  center = Math.round(center);
2995 
2996  pnt.y0 = Math.round(pmain.gry(pnt.median));
2997  // mean line
2998  bars += "M" + pnt.x1 + "," + pnt.y0 + "h" + (pnt.x2-pnt.x1);
2999 
3000  pnt.y1 = Math.round(pmain.gry(pnt.p25y));
3001  pnt.y2 = Math.round(pmain.gry(pnt.m25y));
3002 
3003  // rectangle
3004  bars += "M" + pnt.x1 + "," + pnt.y1 +
3005  "v" + (pnt.y2-pnt.y1) + "h" + (pnt.x2-pnt.x1) + "v-" + (pnt.y2-pnt.y1) + "z";
3006 
3007  pnt.yy1 = Math.round(pmain.gry(pnt.whiskerp));
3008  pnt.yy2 = Math.round(pmain.gry(pnt.whiskerm));
3009 
3010  // upper part
3011  bars += "M" + center + "," + pnt.y1 + "v" + (pnt.yy1-pnt.y1);
3012  bars += "M" + pnt.x1 + "," + pnt.yy1 + "h" + (pnt.x2-pnt.x1);
3013 
3014  // lower part
3015  bars += "M" + center + "," + pnt.y2 + "v" + (pnt.yy2-pnt.y2);
3016  bars += "M" + pnt.x1 + "," + pnt.yy2 + "h" + (pnt.x2-pnt.x1);
3017 
3018  //estimate outliers
3019  for (j = 0; j < this.nbinsy; ++j) {
3020  cont = histo.getBinContent(i+1,j+1);
3021  posy = yaxis.GetBinCoord(j + 0.5);
3022  if (cont > 0 && posy < pnt.whiskerm) markers += this.markeratt.create(center, posy);
3023  if (cont > 0 && posy > pnt.whiskerp) markers += this.markeratt.create(center, posy); }
3024 
3025  handle.candle.push(pnt); // keep point for the tooltip
3026  }
3027 
3028  if (bars.length > 0)
3029  this.draw_g.append("svg:path")
3030  .attr("d", bars)
3031  .call(this.lineatt.func)
3032  .call(this.fillatt.func);
3033 
3034  if (markers.length > 0)
3035  this.draw_g.append("svg:path")
3036  .attr("d", markers)
3037  .call(this.markeratt.func);
3038 
3039  return handle;
3040  }
3041 
3042  TH2Painter.prototype.DrawBinsScatter = function(w,h) {
3043  var histo = this.GetHisto(),
3044  handle = this.PrepareColorDraw({ rounding: true, pixel_density: true }),
3045  colPaths = [], currx = [], curry = [], cell_w = [], cell_h = [],
3046  colindx, cmd1, cmd2, i, j, binz, cw, ch, factor = 1.,
3047  scale = this.options.ScatCoef * ((this.gmaxbin) > 2000 ? 2000. / this.gmaxbin : 1.);
3048 
3049  JSROOT.seed(handle.sumz);
3050 
3051  if (scale*handle.sumz < 1e5) {
3052  // one can use direct drawing of scatter plot without any patterns
3053 
3054  this.createAttMarker({ attr: histo });
3055 
3056  this.markeratt.reset_pos();
3057 
3058  var path = "", k, npix;
3059  for (i = handle.i1; i < handle.i2; ++i) {
3060  cw = handle.grx[i+1] - handle.grx[i];
3061  for (j = handle.j1; j < handle.j2; ++j) {
3062  ch = handle.gry[j] - handle.gry[j+1];
3063  binz = histo.getBinContent(i + 1, j + 1);
3064 
3065  npix = Math.round(scale*binz);
3066  if (npix<=0) continue;
3067 
3068  for (k=0;k<npix;++k)
3069  path += this.markeratt.create(
3070  Math.round(handle.grx[i] + cw * JSROOT.random()),
3071  Math.round(handle.gry[j+1] + ch * JSROOT.random()));
3072  }
3073  }
3074 
3075  this.draw_g
3076  .append("svg:path")
3077  .attr("d", path)
3078  .call(this.markeratt.func);
3079 
3080  return handle;
3081  }
3082 
3083  // limit filling factor, do not try to produce as many points as filled area;
3084  if (this.maxbin > 0.7) factor = 0.7/this.maxbin;
3085 
3086  var nlevels = Math.round(handle.max - handle.min);
3087  this.CreateContour((nlevels > 50) ? 50 : nlevels, this.minposbin, this.maxbin, this.minposbin);
3088 
3089  // now start build
3090  for (i = handle.i1; i < handle.i2; ++i) {
3091  for (j = handle.j1; j < handle.j2; ++j) {
3092  binz = histo.getBinContent(i + 1, j + 1);
3093  if ((binz <= 0) || (binz < this.minbin)) continue;
3094 
3095  cw = handle.grx[i+1] - handle.grx[i];
3096  ch = handle.gry[j] - handle.gry[j+1];
3097  if (cw*ch <= 0) continue;
3098 
3099  colindx = this.getContourIndex(binz/cw/ch);
3100  if (colindx < 0) continue;
3101 
3102  cmd1 = "M"+handle.grx[i]+","+handle.gry[j+1];
3103  if (colPaths[colindx] === undefined) {
3104  colPaths[colindx] = cmd1;
3105  cell_w[colindx] = cw;
3106  cell_h[colindx] = ch;
3107  } else{
3108  cmd2 = "m" + (handle.grx[i]-currx[colindx]) + "," + (handle.gry[j+1] - curry[colindx]);
3109  colPaths[colindx] += (cmd2.length < cmd1.length) ? cmd2 : cmd1;
3110  cell_w[colindx] = Math.max(cell_w[colindx], cw);
3111  cell_h[colindx] = Math.max(cell_h[colindx], ch);
3112  }
3113 
3114  currx[colindx] = handle.grx[i];
3115  curry[colindx] = handle.gry[j+1];
3116 
3117  colPaths[colindx] += "v"+ch+"h"+cw+"v-"+ch+"z";
3118  }
3119  }
3120 
3121  var layer = this.svg_frame().select('.main_layer'),
3122  defs = layer.select("defs");
3123  if (defs.empty() && (colPaths.length>0))
3124  defs = layer.insert("svg:defs",":first-child");
3125 
3126  this.createAttMarker({ attr: histo });
3127 
3128  for (colindx=0;colindx<colPaths.length;++colindx)
3129  if ((colPaths[colindx] !== undefined) && (colindx<this.fContour.length)) {
3130  var pattern_class = "scatter_" + colindx,
3131  pattern = defs.select('.' + pattern_class);
3132  if (pattern.empty())
3133  pattern = defs.append('svg:pattern')
3134  .attr("class", pattern_class)
3135  .attr("id", "jsroot_scatter_pattern_" + JSROOT.id_counter++)
3136  .attr("patternUnits","userSpaceOnUse");
3137  else
3138  pattern.selectAll("*").remove();
3139 
3140  var npix = Math.round(factor*this.fContour[colindx]*cell_w[colindx]*cell_h[colindx]);
3141  if (npix<1) npix = 1;
3142 
3143  var arrx = new Float32Array(npix), arry = new Float32Array(npix);
3144 
3145  if (npix===1) {
3146  arrx[0] = arry[0] = 0.5;
3147  } else {
3148  for (var n=0;n<npix;++n) {
3149  arrx[n] = JSROOT.random();
3150  arry[n] = JSROOT.random();
3151  }
3152  }
3153 
3154  // arrx.sort();
3155 
3156  this.markeratt.reset_pos();
3157 
3158  var path = "";
3159 
3160  for (var n=0;n<npix;++n)
3161  path += this.markeratt.create(arrx[n] * cell_w[colindx], arry[n] * cell_h[colindx]);
3162 
3163  pattern.attr("width", cell_w[colindx])
3164  .attr("height", cell_h[colindx])
3165  .append("svg:path")
3166  .attr("d",path)
3167  .call(this.markeratt.func);
3168 
3169  this.draw_g
3170  .append("svg:path")
3171  .attr("scatter-index", colindx)
3172  .attr("fill", 'url(#' + pattern.attr("id") + ')')
3173  .attr("d", colPaths[colindx]);
3174  }
3175 
3176  return handle;
3177  }
3178 
3179  TH2Painter.prototype.DrawBins = function() {
3180 
3181  if (!this.draw_content)
3182  return this.RemoveDrawG();
3183 
3184  this.CheckHistDrawAttributes();
3185 
3186  this.CreateG(true);
3187 
3188  var w = this.frame_width(),
3189  h = this.frame_height(),
3190  handle = null;
3191 
3192  // if (this.lineatt.color == 'none') this.lineatt.color = 'cyan';
3193 
3194  if (this.IsTH2Poly()) {
3195  handle = this.DrawPolyBinsColor(w, h);
3196  } else {
3197  if (this.options.Scat)
3198  handle = this.DrawBinsScatter(w, h);
3199  else if (this.options.Color)
3200  handle = this.DrawBinsColor(w, h);
3201  else if (this.options.Box)
3202  handle = this.DrawBinsBox(w, h);
3203  else if (this.options.Arrow)
3204  handle = this.DrawBinsArrow(w, h);
3205  else if (this.options.Contour > 0)
3206  handle = this.DrawBinsContour(w, h);
3207  else if (this.options.Candle)
3208  handle = this.DrawCandle(w, h);
3209 
3210  if (this.options.Text)
3211  handle = this.DrawBinsText(w, h, handle);
3212 
3213  if (!handle)
3214  handle = this.DrawBinsScatter(w, h);
3215  }
3216 
3217  this.tt_handle = handle;
3218  }
3219 
3220  TH2Painter.prototype.GetBinTips = function (i, j) {
3221  var lines = [], pmain = this.frame_painter(),
3222  xaxis = this.GetAxis("y"), yaxis = this.GetAxis("y"),
3223  histo = this.GetHisto(),
3224  binz = histo.getBinContent(i+1,j+1);
3225 
3226  lines.push(this.GetTipName() || "histo<2>");
3227 
3228  if (pmain.x_kind == 'labels')
3229  lines.push("x = " + pmain.AxisAsText("x", xaxis.GetBinCoord(i)));
3230  else
3231  lines.push("x = [" + pmain.AxisAsText("x", xaxis.GetBinCoord(i)) + ", " + pmain.AxisAsText("x", xaxis.GetBinCoord(i+1)) + ")");
3232 
3233  if (pmain.y_kind == 'labels')
3234  lines.push("y = " + pmain.AxisAsText("y", yaxis.GetBinCoord(j)));
3235  else
3236  lines.push("y = [" + pmain.AxisAsText("y", yaxis.GetBinCoord(j)) + ", " + pmain.AxisAsText("y", yaxis.GetBinCoord(j+1)) + ")");
3237 
3238  lines.push("bin = " + i + ", " + j);
3239 
3240  if (histo.$baseh) binz -= histo.$baseh.getBinContent(i+1,j+1);
3241 
3242  if (binz === Math.round(binz))
3243  lines.push("entries = " + binz);
3244  else
3245  lines.push("entries = " + JSROOT.FFormat(binz, JSROOT.gStyle.fStatFormat));
3246 
3247  return lines;
3248  }
3249 
3250  TH2Painter.prototype.GetCandleTips = function(p) {
3251  var lines = [], main = this.frame_painter(), xaxis = this.GetAxis("y");
3252 
3253  lines.push(this.GetTipName() || "histo");
3254 
3255  lines.push("x = " + main.AxisAsText("x", xaxis.GetBinCoord(p.bin)));
3256 
3257  lines.push('mean y = ' + JSROOT.FFormat(p.meany, JSROOT.gStyle.fStatFormat))
3258  lines.push('m25 = ' + JSROOT.FFormat(p.m25y, JSROOT.gStyle.fStatFormat))
3259  lines.push('p25 = ' + JSROOT.FFormat(p.p25y, JSROOT.gStyle.fStatFormat))
3260 
3261  return lines;
3262  }
3263 
3264  TH2Painter.prototype.ProvidePolyBinHints = function(binindx, realx, realy) {
3265 
3266  var histo = this.GetHisto(),
3267  bin = histo.fBins.arr[binindx],
3268  pmain = this.frame_painter(),
3269  binname = bin.fPoly.fName,
3270  lines = [], numpoints = 0;
3271 
3272  if (binname === "Graph") binname = "";
3273  if (binname.length === 0) binname = bin.fNumber;
3274 
3275  if ((realx===undefined) && (realy===undefined)) {
3276  realx = realy = 0;
3277  var gr = bin.fPoly, numgraphs = 1;
3278  if (gr._typename === 'TMultiGraph') { numgraphs = bin.fPoly.fGraphs.arr.length; gr = null; }
3279 
3280  for (var ngr=0;ngr<numgraphs;++ngr) {
3281  if (!gr || (ngr>0)) gr = bin.fPoly.fGraphs.arr[ngr];
3282 
3283  for (var n=0;n<gr.fNpoints;++n) {
3284  ++numpoints;
3285  realx += gr.fX[n];
3286  realy += gr.fY[n];
3287  }
3288  }
3289 
3290  if (numpoints > 1) {
3291  realx = realx / numpoints;
3292  realy = realy / numpoints;
3293  }
3294  }
3295 
3296  lines.push(this.GetTipName() || "histo");
3297  lines.push("x = " + pmain.AxisAsText("x", realx));
3298  lines.push("y = " + pmain.AxisAsText("y", realy));
3299  if (numpoints > 0) lines.push("npnts = " + numpoints);
3300  lines.push("bin = " + binname);
3301  if (bin.fContent === Math.round(bin.fContent))
3302  lines.push("content = " + bin.fContent);
3303  else
3304  lines.push("content = " + JSROOT.FFormat(bin.fContent, JSROOT.gStyle.fStatFormat));
3305  return lines;
3306  }
3307 
3308  TH2Painter.prototype.ProcessTooltip = function(pnt) {
3309  if (!pnt || !this.draw_content || !this.draw_g || !this.tt_handle || this.options.Proj) {
3310  if (this.draw_g !== null)
3311  this.draw_g.select(".tooltip_bin").remove();
3312  return null;
3313  }
3314 
3315  var histo = this.GetHisto(),
3316  h = this.tt_handle, i,
3317  ttrect = this.draw_g.select(".tooltip_bin");
3318 
3319  if (h.poly) {
3320  // process tooltips from TH2Poly
3321 
3322  var pmain = this.frame_painter(),
3323  realx, realy, foundindx = -1;
3324 
3325  if (pmain.grx === pmain.x) realx = pmain.x.invert(pnt.x);
3326  if (pmain.gry === pmain.y) realy = pmain.y.invert(pnt.y);
3327 
3328  if ((realx!==undefined) && (realy!==undefined)) {
3329  var i, len = histo.fBins.arr.length, bin;
3330 
3331  for (i = 0; (i < len) && (foundindx < 0); ++ i) {
3332  bin = histo.fBins.arr[i];
3333 
3334  // found potential bins candidate
3335  if ((realx < bin.fXmin) || (realx > bin.fXmax) ||
3336  (realy < bin.fYmin) || (realy > bin.fYmax)) continue;
3337 
3338  // ignore empty bins with col0 option
3339  if ((bin.fContent === 0) && !this.options.Zero) continue;
3340 
3341  var gr = bin.fPoly, numgraphs = 1;
3342  if (gr._typename === 'TMultiGraph') { numgraphs = bin.fPoly.fGraphs.arr.length; gr = null; }
3343 
3344  for (var ngr=0;ngr<numgraphs;++ngr) {
3345  if (!gr || (ngr>0)) gr = bin.fPoly.fGraphs.arr[ngr];
3346  if (gr.IsInside(realx,realy)) {
3347  foundindx = i;
3348  break;
3349  }
3350  }
3351  }
3352  }
3353 
3354  if (foundindx < 0) {
3355  ttrect.remove();
3356  return null;
3357  }
3358 
3359  var res = { name: "histo", title: histo.fTitle || "title",
3360  x: pnt.x, y: pnt.y,
3361  color1: this.lineatt ? this.lineatt.color : 'green',
3362  color2: this.fillatt ? this.fillatt.fillcoloralt('blue') : 'blue',
3363  exact: true, menu: true,
3364  lines: this.ProvidePolyBinHints(foundindx, realx, realy) };
3365 
3366  if (pnt.disabled) {
3367  ttrect.remove();
3368  res.changed = true;
3369  } else {
3370 
3371  if (ttrect.empty())
3372  ttrect = this.draw_g.append("svg:path")
3373  .attr("class","tooltip_bin h1bin")
3374  .style("pointer-events","none");
3375 
3376  res.changed = ttrect.property("current_bin") !== foundindx;
3377 
3378  if (res.changed)
3379  ttrect.attr("d", this.CreatePolyBin(pmain, bin))
3380  .style("opacity", "0.7")
3381  .property("current_bin", foundindx);
3382  }
3383 
3384  if (res.changed)
3385  res.user_info = { obj: histo, name: histo.fName || "histo",
3386  bin: foundindx,
3387  cont: bin.fContent,
3388  grx: pnt.x, gry: pnt.y };
3389 
3390  return res;
3391 
3392  } else
3393 
3394  if (h.candle) {
3395  // process tooltips for candle
3396 
3397  var p;
3398 
3399  for (i=0;i<h.candle.length;++i) {
3400  p = h.candle[i];
3401  if ((p.x1 <= pnt.x) && (pnt.x <= p.x2) && (p.yy1 <= pnt.y) && (pnt.y <= p.yy2)) break;
3402  }
3403 
3404  if (i>=h.candle.length) {
3405  ttrect.remove();
3406  return null;
3407  }
3408 
3409  var res = { name: histo.fName || "histo", title: histo.fTitle || "title",
3410  x: pnt.x, y: pnt.y,
3411  color1: this.lineatt ? this.lineatt.color : 'green',
3412  color2: this.fillatt ? this.fillatt.fillcoloralt('blue') : 'blue',
3413  lines: this.GetCandleTips(p), exact: true, menu: true };
3414 
3415  if (pnt.disabled) {
3416  ttrect.remove();
3417  res.changed = true;
3418  } else {
3419 
3420  if (ttrect.empty())
3421  ttrect = this.draw_g.append("svg:rect")
3422  .attr("class","tooltip_bin h1bin")
3423  .style("pointer-events","none");
3424 
3425  res.changed = ttrect.property("current_bin") !== i;
3426 
3427  if (res.changed)
3428  ttrect.attr("x", p.x1)
3429  .attr("width", p.x2-p.x1)
3430  .attr("y", p.yy1)
3431  .attr("height", p.yy2- p.yy1)
3432  .style("opacity", "0.7")
3433  .property("current_bin", i);
3434  }
3435 
3436  if (res.changed)
3437  res.user_info = { obj: histo, name: histo.fName || "histo",
3438  bin: i+1, cont: p.median, binx: i+1, biny: 1,
3439  grx: pnt.x, gry: pnt.y };
3440 
3441  return res;
3442  }
3443 
3444  var i, j, binz = 0, colindx = null;
3445 
3446  // search bins position
3447  for (i = h.i1; i < h.i2; ++i)
3448  if ((pnt.x>=h.grx[i]) && (pnt.x<=h.grx[i+1])) break;
3449 
3450  for (j = h.j1; j < h.j2; ++j)
3451  if ((pnt.y>=h.gry[j+1]) && (pnt.y<=h.gry[j])) break;
3452 
3453  if ((i < h.i2) && (j < h.j2)) {
3454  binz = histo.getBinContent(i+1,j+1);
3455  if (this.is_projection) {
3456  colindx = 0; // just to avoid hide
3457  } else if (h.hide_only_zeros) {
3458  colindx = (binz === 0) && !this._show_empty_bins ? null : 0;
3459  } else {
3460  colindx = this.getContourColor(binz, true);
3461  if ((colindx === null) && (binz === 0) && this._show_empty_bins) colindx = 0;
3462  }
3463  }
3464 
3465  if (colindx === null) {
3466  ttrect.remove();
3467  return null;
3468  }
3469 
3470  var res = { name: histo.fName || "histo", title: histo.fTitle || "title",
3471  x: pnt.x, y: pnt.y,
3472  color1: this.lineatt ? this.lineatt.color : 'green',
3473  color2: this.fillatt ? this.fillatt.fillcoloralt('blue') : 'blue',
3474  lines: this.GetBinTips(i, j), exact: true, menu: true };
3475 
3476  if (this.options.Color) res.color2 = this.GetPalette().getColor(colindx);
3477 
3478  if (pnt.disabled && !this.is_projection) {
3479  ttrect.remove();
3480  res.changed = true;
3481  } else {
3482  if (ttrect.empty())
3483  ttrect = this.draw_g.append("svg:rect")
3484  .attr("class","tooltip_bin h1bin")
3485  .style("pointer-events","none");
3486 
3487  var i1 = i, i2 = i+1,
3488  j1 = j, j2 = j+1,
3489  x1 = h.grx[i1], x2 = h.grx[i2],
3490  y1 = h.gry[j2], y2 = h.gry[j1],
3491  binid = i*10000 + j;
3492 
3493  if (this.is_projection == "X") {
3494  x1 = 0; x2 = this.frame_width();
3495  if (this.projection_width > 1) {
3496  var dd = (this.projection_width-1)/2;
3497  if (j2+dd >= h.j2) { j2 = Math.min(Math.round(j2+dd), h.j2); j1 = Math.max(j2 - this.projection_width, h.j1); }
3498  else { j1 = Math.max(Math.round(j1-dd), h.j1); j2 = Math.min(j1 + this.projection_width, h.j2); }
3499  }
3500  y1 = h.gry[j2]; y2 = h.gry[j1];
3501  binid = j1*777 + j2*333;
3502  } else if (this.is_projection == "Y") {
3503  y1 = 0; y2 = this.frame_height();
3504  if (this.projection_width > 1) {
3505  var dd = (this.projection_width-1)/2;
3506  if (i2+dd >= h.i2) { i2 = Math.min(Math.round(i2+dd), h.i2); i1 = Math.max(i2 - this.projection_width, h.i1); }
3507  else { i1 = Math.max(Math.round(i1-dd), h.i1); i2 = Math.min(i1 + this.projection_width, h.i2); }
3508  }
3509  x1 = h.grx[i1], x2 = h.grx[i2],
3510  binid = i1*777 + i2*333;
3511  }
3512 
3513  res.changed = ttrect.property("current_bin") !== binid;
3514 
3515  if (res.changed)
3516  ttrect.attr("x", x1)
3517  .attr("width", x2 - x1)
3518  .attr("y", y1)
3519  .attr("height", y2 - y1)
3520  .style("opacity", "0.7")
3521  .property("current_bin", binid);
3522 
3523  if (this.is_projection && res.changed)
3524  this.RedrawProjection(i1, i2, j1, j2);
3525  }
3526 
3527  if (res.changed)
3528  res.user_info = { obj: histo, name: histo.fName || "histo",
3529  bin: histo.getBin(i+1, j+1), cont: binz, binx: i+1, biny: j+1,
3530  grx: pnt.x, gry: pnt.y };
3531 
3532  return res;
3533  }
3534 
3535  TH2Painter.prototype.CanZoomIn = function(axis,min,max) {
3536  // check if it makes sense to zoom inside specified axis range
3537 
3538  if (axis=="z") return true;
3539 
3540  var obj = this.GetAxis(axis);
3541 
3542  return (obj.FindBin(max,0.5) - obj.FindBin(min,0) > 1);
3543  }
3544 
3545  TH2Painter.prototype.Draw2D = function(call_back, resize) {
3546 
3547  this.mode3d = false;
3548  this.Clear3DScene();
3549 
3550  // draw new palette, resize frame if required
3551  // var pp = this.DrawColorPalette(this.options.Zscale && (this.options.Color || this.options.Contour), true);
3552 
3553  if (this.DrawAxes())
3554  this.DrawBins();
3555 
3556  // redraw palette till the end when contours are available
3557  // if (pp) pp.DrawPave();
3558 
3559  // this.DrawTitle();
3560 
3561  // this.UpdateStatWebCanvas();
3562 
3563  this.AddInteractive();
3564 
3565  JSROOT.CallBack(call_back);
3566  }
3567 
3568  TH2Painter.prototype.Draw3D = function(call_back, resize) {
3569  this.mode3d = true;
3570  JSROOT.AssertPrerequisites('hist3d', function() {
3571  this.Draw3D(call_back, resize);
3572  }.bind(this));
3573  }
3574 
3575  TH2Painter.prototype.CallDrawFunc = function(callback, resize) {
3576 
3577  var main = this.frame_painter();
3578 
3579  if (this.options.Mode3D !== main.mode3d) {
3580  this.options.Mode3D = main.mode3d;
3581  }
3582 
3583  var funcname = this.options.Mode3D ? "Draw3D" : "Draw2D";
3584 
3585  this[funcname](callback, resize);
3586  }
3587 
3588  TH2Painter.prototype.Redraw = function(resize) {
3589  this.CallDrawFunc(null, resize);
3590  }
3591 
3592  function drawHist2(divid, obj, opt) {
3593  // create painter and add it to canvas
3594  var painter = new TH2Painter(obj);
3595 
3596  painter.PrepareFrame(divid);
3597 
3598  painter.options = { Hist: false, Bar: false, Error: false, ErrorKind: -1, errorX: 0, Zero: false, Mark: false,
3599  Line: false, Fill: false, Lego: 0, Surf: 0,
3600  Text: true, TextAngle: 0, TextKind: "",
3601  fBarOffset: 0, fBarWidth: 1000, BaseLine: false, Mode3D: false, AutoColor: 0,
3602  Color: false, Scat: false, ScatCoef: 1, Candle: "", Box: false, BoxStyle: 0, Arrow: false, Contour: 0, Proj: 0 };
3603 
3604  // FIXME: options are changed now in ROOT7 part, need to adjust here;
3605  //if (obj.fOpts.fStyle.fIdx == 1) painter.options.Box = true;
3606  // else painter.options.Color = true;
3607  painter.options.Color = true;
3608 
3609  // here we deciding how histogram will look like and how will be shown
3610  painter.DecodeOptions(opt);
3611 
3612  if (painter.IsTH2Poly()) {
3613  if (painter.options.Mode3D) painter.options.Lego = 12; // lego always 12
3614  else if (!painter.options.Color) painter.options.Color = true; // default is color
3615  }
3616 
3617  painter._show_empty_bins = false;
3618 
3619  painter._can_move_colz = true;
3620 
3621  painter.ScanContent();
3622 
3623  // painter.CreateStat(); // only when required
3624 
3625  painter.CallDrawFunc(function() {
3626  //if (!this.options.Mode3D && this.options.AutoZoom) this.AutoZoom();
3627  // this.FillToolbar();
3628  //if (this.options.Project && !this.mode3d)
3629  // this.ToggleProjection(this.options.Project);
3630  painter.DrawingReady();
3631  });
3632 
3633  return painter;
3634  }
3635 
3636  JSROOT.v7.THistPainter = THistPainter;
3637  JSROOT.v7.TH1Painter = TH1Painter;
3638  JSROOT.v7.TH2Painter = TH2Painter;
3639 
3640  JSROOT.v7.drawHist1 = drawHist1;
3641  JSROOT.v7.drawHist2 = drawHist2;
3642 
3643  return JSROOT;
3644 
3645 }));