otsdaq_utilities  v2_05_02_indev
JSRootPainter.js
1 
4 (function( factory ) {
5  if ( typeof define === "function" && define.amd ) {
6  // AMD. Register as an anonymous module.
7  define( ['JSRootCore', 'd3'], factory );
8  } else {
9 
10  if (typeof JSROOT == 'undefined')
11  throw new Error('JSROOT is not defined', 'JSRootPainter.js');
12 
13  if (typeof d3 != 'object')
14  throw new Error('d3 is not defined', 'JSRootPainter.js');
15 
16  if (typeof JSROOT.Painter == 'object')
17  throw new Error('JSROOT.Painter already defined', 'JSRootPainter.js');
18 
19  factory(JSROOT, d3);
20  }
21 } (function(JSROOT, d3) {
22 
23  // do it here while require.js does not provide method to load css files
24  if ( typeof define === "function" && define.amd )
25  JSROOT.loadScript('$$$style/JSRootPainter.css');
26 
27  // list of user painters, called with arguments func(vis, obj, opt)
28  JSROOT.DrawFuncs = {lst:[], cache:{}};
29 
30  // add draw function for the class
31  // List of supported draw options could be provided, separated with ';'
32  // Several different draw functions for the same class or kind could be specified
33  JSROOT.addDrawFunc = function(_name, _func, _opt) {
34  if ((arguments.length == 1) && (typeof arguments[0] == 'object')) {
35  JSROOT.DrawFuncs.lst.push(arguments[0]);
36  return arguments[0];
37  }
38  var handle = { name:_name, func:_func, opt:_opt };
39  JSROOT.DrawFuncs.lst.push(handle);
40  return handle;
41  }
42 
43  // icons taken from http://uxrepo.com/
44 
45  JSROOT.ToolbarIcons = {
46  camera: { path: 'M 152.00,304.00c0.00,57.438, 46.562,104.00, 104.00,104.00s 104.00-46.562, 104.00-104.00s-46.562-104.00-104.00-104.00S 152.00,246.562, 152.00,304.00z M 480.00,128.00L 368.00,128.00 c-8.00-32.00-16.00-64.00-48.00-64.00L 192.00,64.00 c-32.00,0.00-40.00,32.00-48.00,64.00L 32.00,128.00 c-17.60,0.00-32.00,14.40-32.00,32.00l0.00,288.00 c0.00,17.60, 14.40,32.00, 32.00,32.00l 448.00,0.00 c 17.60,0.00, 32.00-14.40, 32.00-32.00L 512.00,160.00 C 512.00,142.40, 497.60,128.00, 480.00,128.00z M 256.00,446.00c-78.425,0.00-142.00-63.574-142.00-142.00c0.00-78.425, 63.575-142.00, 142.00-142.00c 78.426,0.00, 142.00,63.575, 142.00,142.00 C 398.00,382.426, 334.427,446.00, 256.00,446.00z M 480.00,224.00l-64.00,0.00 l0.00-32.00 l 64.00,0.00 L 480.00,224.00 z' },
47  disk: { path: 'M384,0H128H32C14.336,0,0,14.336,0,32v448c0,17.656,14.336,32,32,32h448c17.656,0,32-14.344,32-32V96L416,0H384z M352,160 V32h32v128c0,17.664-14.344,32-32,32H160c-17.664,0-32-14.336-32-32V32h128v128H352z M96,288c0-17.656,14.336-32,32-32h256 c17.656,0,32,14.344,32,32v192H96V288z' },
48  question: { path: 'M256,512c141.375,0,256-114.625,256-256S397.375,0,256,0S0,114.625,0,256S114.625,512,256,512z M256,64 c63.719,0,128,36.484,128,118.016c0,47.453-23.531,84.516-69.891,110.016C300.672,299.422,288,314.047,288,320 c0,17.656-14.344,32-32,32c-17.664,0-32-14.344-32-32c0-40.609,37.25-71.938,59.266-84.031 C315.625,218.109,320,198.656,320,182.016C320,135.008,279.906,128,256,128c-30.812,0-64,20.227-64,64.672 c0,17.664-14.336,32-32,32s-32-14.336-32-32C128,109.086,193.953,64,256,64z M256,449.406c-18.211,0-32.961-14.75-32.961-32.969 c0-18.188,14.75-32.953,32.961-32.953c18.219,0,32.969,14.766,32.969,32.953C288.969,434.656,274.219,449.406,256,449.406z' },
49  undo: { path: 'M450.159,48.042c8.791,9.032,16.983,18.898,24.59,29.604c7.594,10.706,14.146,22.207,19.668,34.489 c5.509,12.296,9.82,25.269,12.92,38.938c3.113,13.669,4.663,27.834,4.663,42.499c0,14.256-1.511,28.863-4.532,43.822 c-3.009,14.952-7.997,30.217-14.953,45.795c-6.955,15.577-16.202,31.52-27.755,47.826s-25.88,32.9-42.942,49.807 c-5.51,5.444-11.787,11.67-18.834,18.651c-7.033,6.98-14.496,14.366-22.39,22.168c-7.88,7.802-15.955,15.825-24.187,24.069 c-8.258,8.231-16.333,16.203-24.252,23.888c-18.3,18.13-37.354,37.016-57.191,56.65l-56.84-57.445 c19.596-19.472,38.54-38.279,56.84-56.41c7.75-7.685,15.772-15.604,24.108-23.757s16.438-16.163,24.33-24.057 c7.894-7.893,15.356-15.33,22.402-22.312c7.034-6.98,13.312-13.193,18.821-18.651c22.351-22.402,39.165-44.648,50.471-66.738 c11.279-22.09,16.932-43.567,16.932-64.446c0-15.785-3.217-31.005-9.638-45.671c-6.422-14.665-16.229-28.504-29.437-41.529 c-3.282-3.282-7.358-6.395-12.217-9.325c-4.871-2.938-10.381-5.503-16.516-7.697c-6.121-2.201-12.815-3.992-20.058-5.373 c-7.242-1.374-14.9-2.064-23.002-2.064c-8.218,0-16.802,0.834-25.788,2.507c-8.961,1.674-18.053,4.429-27.222,8.271 c-9.189,3.842-18.456,8.869-27.808,15.089c-9.358,6.219-18.521,13.819-27.502,22.793l-59.92,60.271l93.797,94.058H0V40.91 l93.27,91.597l60.181-60.532c13.376-15.018,27.222-27.248,41.536-36.697c14.308-9.443,28.608-16.776,42.89-21.992 c14.288-5.223,28.505-8.74,42.623-10.557C294.645,0.905,308.189,0,321.162,0c13.429,0,26.389,1.185,38.84,3.562 c12.478,2.377,24.2,5.718,35.192,10.029c11.006,4.311,21.126,9.404,30.374,15.265C434.79,34.724,442.995,41.119,450.159,48.042z' },
50  arrow_right : { path : 'M30.796,226.318h377.533L294.938,339.682c-11.899,11.906-11.899,31.184,0,43.084c11.887,11.899,31.19,11.893,43.077,0 l165.393-165.386c5.725-5.712,8.924-13.453,8.924-21.539c0-8.092-3.213-15.84-8.924-21.551L338.016,8.925 C332.065,2.975,324.278,0,316.478,0c-7.802,0-15.603,2.968-21.539,8.918c-11.899,11.906-11.899,31.184,0,43.084l113.391,113.384 H30.796c-16.822,0-30.463,13.645-30.463,30.463C0.333,212.674,13.974,226.318,30.796,226.318z' },
51  arrow_up : { path : 'M295.505,629.446V135.957l148.193,148.206c15.555,15.559,40.753,15.559,56.308,0c15.555-15.538,15.546-40.767,0-56.304 L283.83,11.662C276.372,4.204,266.236,0,255.68,0c-10.568,0-20.705,4.204-28.172,11.662L11.333,227.859 c-7.777,7.777-11.666,17.965-11.666,28.158c0,10.192,3.88,20.385,11.657,28.158c15.563,15.555,40.762,15.555,56.317,0 l148.201-148.219v493.489c0,21.993,17.837,39.82,39.82,39.82C277.669,669.267,295.505,651.439,295.505,629.446z' },
52  arrow_diag : { path : 'M279.875,511.994c-1.292,0-2.607-0.102-3.924-0.312c-10.944-1.771-19.333-10.676-20.457-21.71L233.97,278.348 L22.345,256.823c-11.029-1.119-19.928-9.51-21.698-20.461c-1.776-10.944,4.031-21.716,14.145-26.262L477.792,2.149 c9.282-4.163,20.167-2.165,27.355,5.024c7.201,7.189,9.199,18.086,5.024,27.356L302.22,497.527 C298.224,506.426,289.397,511.994,279.875,511.994z M118.277,217.332l140.534,14.294c11.567,1.178,20.718,10.335,21.878,21.896 l14.294,140.519l144.09-320.792L118.277,217.332z' },
53  auto_zoom: { path : 'M505.441,242.47l-78.303-78.291c-9.18-9.177-24.048-9.171-33.216,0c-9.169,9.172-9.169,24.045,0.006,33.217l38.193,38.188 H280.088V80.194l38.188,38.199c4.587,4.584,10.596,6.881,16.605,6.881c6.003,0,12.018-2.297,16.605-6.875 c9.174-9.172,9.174-24.039,0.011-33.217L273.219,6.881C268.803,2.471,262.834,0,256.596,0c-6.229,0-12.202,2.471-16.605,6.881 l-78.296,78.302c-9.178,9.172-9.178,24.045,0,33.217c9.177,9.171,24.051,9.171,33.21,0l38.205-38.205v155.4H80.521l38.2-38.188 c9.177-9.171,9.177-24.039,0.005-33.216c-9.171-9.172-24.039-9.178-33.216,0L7.208,242.464c-4.404,4.403-6.881,10.381-6.881,16.611 c0,6.227,2.477,12.207,6.881,16.61l78.302,78.291c4.587,4.581,10.599,6.875,16.605,6.875c6.006,0,12.023-2.294,16.61-6.881 c9.172-9.174,9.172-24.036-0.005-33.211l-38.205-38.199h152.593v152.063l-38.199-38.211c-9.171-9.18-24.039-9.18-33.216-0.022 c-9.178,9.18-9.178,24.059-0.006,33.222l78.284,78.302c4.41,4.404,10.382,6.881,16.611,6.881c6.233,0,12.208-2.477,16.611-6.881 l78.302-78.296c9.181-9.18,9.181-24.048,0-33.205c-9.174-9.174-24.054-9.174-33.21,0l-38.199,38.188v-152.04h152.051l-38.205,38.199 c-9.18,9.175-9.18,24.037-0.005,33.211c4.587,4.587,10.596,6.881,16.604,6.881c6.01,0,12.024-2.294,16.605-6.875l78.303-78.285 c4.403-4.403,6.887-10.378,6.887-16.611C512.328,252.851,509.845,246.873,505.441,242.47z' },
54  statbox : {
55  path : 'M28.782,56.902H483.88c15.707,0,28.451-12.74,28.451-28.451C512.331,12.741,499.599,0,483.885,0H28.782 C13.074,0,0.331,12.741,0.331,28.451C0.331,44.162,13.074,56.902,28.782,56.902z' +
56  'M483.885,136.845H28.782c-15.708,0-28.451,12.741-28.451,28.451c0,15.711,12.744,28.451,28.451,28.451H483.88 c15.707,0,28.451-12.74,28.451-28.451C512.331,149.586,499.599,136.845,483.885,136.845z' +
57  'M483.885,273.275H28.782c-15.708,0-28.451,12.731-28.451,28.452c0,15.707,12.744,28.451,28.451,28.451H483.88 c15.707,0,28.451-12.744,28.451-28.451C512.337,286.007,499.599,273.275,483.885,273.275z' +
58  'M256.065,409.704H30.492c-15.708,0-28.451,12.731-28.451,28.451c0,15.707,12.744,28.451,28.451,28.451h225.585 c15.707,0,28.451-12.744,28.451-28.451C284.516,422.436,271.785,409.704,256.065,409.704z'
59  }
60  };
61 
62  JSROOT.Toolbar = function(container, buttons) {
63  if ((container !== undefined) && (typeof container.append == 'function')) {
64  this.element = container.append("div").attr('class','jsroot');
65  this.addButtons(buttons);
66  }
67  }
68 
69  JSROOT.Toolbar.prototype.addButtons = function(buttons) {
70  var pthis = this;
71 
72  this.buttonsNames = [];
73  buttons.forEach(function(buttonGroup) {
74  var group = pthis.element.append('div').attr('class', 'toolbar-group');
75 
76  buttonGroup.forEach(function(buttonConfig) {
77  var buttonName = buttonConfig.name;
78  if (!buttonName) {
79  throw new Error('must provide button \'name\' in button config');
80  }
81  if (pthis.buttonsNames.indexOf(buttonName) !== -1) {
82  throw new Error('button name \'' + buttonName + '\' is taken');
83  }
84  pthis.buttonsNames.push(buttonName);
85 
86  pthis.createButton(group, buttonConfig);
87  });
88  });
89  };
90 
91 
92  JSROOT.Toolbar.prototype.createButton = function(group, config) {
93 
94  var title = config.title;
95  if (title === undefined) title = config.name;
96 
97  if (typeof config.click !== 'function')
98  throw new Error('must provide button \'click\' function in button config');
99 
100  var button = group.append('a')
101  .attr('class','toolbar-btn')
102  .attr('rel', 'tooltip')
103  .attr('data-title', title)
104  .on('click', config.click);
105 
106  this.createIcon(button, config.icon || JSROOT.ToolbarIcons.question);
107  };
108 
109  JSROOT.Toolbar.prototype.createIcon = function(button, thisIcon) {
110  var size = thisIcon.size || 512;
111  var scale = thisIcon.scale || 1;
112 
113  var svg = button.append("svg:svg")
114  .attr('height', '1em')
115  .attr('width', '1em')
116  .attr('viewBox', [0, 0, size, size].join(' '))
117 
118  if ('recs' in thisIcon) {
119  var rec = {};
120  for (var n=0;n<thisIcon.recs.length;++n) {
121  JSROOT.extend(rec, thisIcon.recs[n]);
122  svg.append('rect').attr("x", rec.x).attr("y", rec.y)
123  .attr("width", rec.w).attr("height", rec.h)
124  .attr("fill", rec.f);
125  }
126  } else {
127  var elem = svg.append('svg:path').attr('d',thisIcon.path);
128  if (scale !== 1)
129  elem.attr('transform', 'scale(' + scale + ' ' + scale +')');
130  }
131  };
132 
133  JSROOT.Toolbar.prototype.removeAllButtons = function() {
134  this.element.remove();
135  };
136 
140  JSROOT.Painter = {};
141 
142  JSROOT.Painter.createMenu = function(maincallback, menuname) {
143  // dummy functions, forward call to the jquery function
144  document.body.style.cursor = 'wait';
145  JSROOT.AssertPrerequisites('jq2d', function() {
146  document.body.style.cursor = 'auto';
147  JSROOT.Painter.createMenu(maincallback, menuname);
148  });
149  }
150 
151  JSROOT.Painter.closeMenu = function(menuname) {
152  if (!menuname) menuname = 'root_ctx_menu';
153  var x = document.getElementById(menuname);
154  if (x) x.parentNode.removeChild(x);
155  }
156 
157  JSROOT.Painter.readStyleFromURL = function(url) {
158  var optimize = JSROOT.GetUrlOption("optimize", url);
159  if (optimize=="") JSROOT.gStyle.OptimizeDraw = 2; else
160  if (optimize!==null) {
161  JSROOT.gStyle.OptimizeDraw = parseInt(optimize);
162  if (isNaN(JSROOT.gStyle.OptimizeDraw)) JSROOT.gStyle.OptimizeDraw = 2;
163  }
164 
165  var inter = JSROOT.GetUrlOption("interactive", url);
166  if ((inter=="") || (inter=="1")) inter = "11111"; else
167  if (inter=="0") inter = "00000";
168  if ((inter!==null) && (inter.length==5)) {
169  JSROOT.gStyle.Tooltip = parseInt(inter.charAt(0));
170  JSROOT.gStyle.ContextMenu = (inter.charAt(1) != '0');
171  JSROOT.gStyle.Zooming = (inter.charAt(2) != '0');
172  JSROOT.gStyle.MoveResize = (inter.charAt(3) != '0');
173  JSROOT.gStyle.DragAndDrop = (inter.charAt(4) != '0');
174  }
175 
176  var tt = JSROOT.GetUrlOption("tooltip", url);
177  if (tt !== null) JSROOT.gStyle.Tooltip = parseInt(tt);
178 
179  var mathjax = JSROOT.GetUrlOption("mathjax", url);
180  if ((mathjax!==null) && (mathjax!="0")) JSROOT.gStyle.MathJax = 1;
181 
182  if (JSROOT.GetUrlOption("nomenu", url)!=null) JSROOT.gStyle.ContextMenu = false;
183  if (JSROOT.GetUrlOption("noprogress", url)!=null) JSROOT.gStyle.ProgressBox = false;
184  if (JSROOT.GetUrlOption("notouch", url)!=null) JSROOT.touches = false;
185 
186  JSROOT.gStyle.OptStat = JSROOT.GetUrlOption("optstat", url, JSROOT.gStyle.OptStat);
187  JSROOT.gStyle.OptFit = JSROOT.GetUrlOption("optfit", url, JSROOT.gStyle.OptFit);
188  JSROOT.gStyle.StatFormat = JSROOT.GetUrlOption("statfmt", url, JSROOT.gStyle.StatFormat);
189  JSROOT.gStyle.FitFormat = JSROOT.GetUrlOption("fitfmt", url, JSROOT.gStyle.FitFormat);
190 
191  var toolbar = JSROOT.GetUrlOption("toolbar", url);
192  if (toolbar !== null)
193  JSROOT.gStyle.ToolBar = (toolbar !== "0") && (toolbar !== "false");
194 
195  var palette = JSROOT.GetUrlOption("palette", url);
196  if (palette!==null) {
197  palette = parseInt(palette);
198  if (!isNaN(palette) && (palette>0) && (palette<113)) JSROOT.gStyle.Palette = palette;
199  }
200 
201  var embed3d = JSROOT.GetUrlOption("embed3d", url);
202  if (embed3d !== null) JSROOT.gStyle.Embed3DinSVG = parseInt(embed3d);
203 
204  var webgl = JSROOT.GetUrlOption("webgl", url);
205  if ((webgl === "0") || (webgl === "false")) JSROOT.gStyle.NoWebGL = false; else
206  if (webgl === "ie") JSROOT.gStyle.NoWebGL = !JSROOT.browser.isIE;
207  }
208 
209  JSROOT.Painter.Coord = {
210  kCARTESIAN : 1,
211  kPOLAR : 2,
212  kCYLINDRICAL : 3,
213  kSPHERICAL : 4,
214  kRAPIDITY : 5
215  }
216 
218  JSROOT.Painter.root_colors = function() {
219  var colorMap = ['white','black','red','green','blue','yellow','magenta','cyan','rgb(89,212,84)','rgb(89,84,217)', 'white'];
220  colorMap[110] = 'white';
221 
222  var moreCol = [
223  {col:11,str:'c1b7ad4d4d4d6666668080809a9a9ab3b3b3cdcdcde6e6e6f3f3f3cdc8accdc8acc3c0a9bbb6a4b3a697b8a49cae9a8d9c8f83886657b1cfc885c3a48aa9a1839f8daebdc87b8f9a768a926983976e7b857d9ad280809caca6c0d4cf88dfbb88bd9f83c89a7dc08378cf5f61ac8f94a6787b946971d45a549300ff7b00ff6300ff4b00ff3300ff1b00ff0300ff0014ff002cff0044ff005cff0074ff008cff00a4ff00bcff00d4ff00ecff00fffd00ffe500ffcd00ffb500ff9d00ff8500ff6d00ff5500ff3d00ff2600ff0e0aff0022ff003aff0052ff006aff0082ff009aff00b1ff00c9ff00e1ff00f9ff00ffef00ffd700ffbf00ffa700ff8f00ff7700ff6000ff4800ff3000ff1800ff0000'},
224  {col:201,str:'5c5c5c7b7b7bb8b8b8d7d7d78a0f0fb81414ec4848f176760f8a0f14b81448ec4876f1760f0f8a1414b84848ec7676f18a8a0fb8b814ecec48f1f1768a0f8ab814b8ec48ecf176f10f8a8a14b8b848ecec76f1f1'},
225  {col:390,str:'ffffcdffff9acdcd9affff66cdcd669a9a66ffff33cdcd339a9a33666633ffff00cdcd009a9a00666600333300'},
226  {col:406,str:'cdffcd9aff9a9acd9a66ff6666cd66669a6633ff3333cd33339a3333663300ff0000cd00009a00006600003300'},
227  {col:422,str:'cdffff9affff9acdcd66ffff66cdcd669a9a33ffff33cdcd339a9a33666600ffff00cdcd009a9a006666003333'},
228  {col:590,str:'cdcdff9a9aff9a9acd6666ff6666cd66669a3333ff3333cd33339a3333660000ff0000cd00009a000066000033'},
229  {col:606,str:'ffcdffff9affcd9acdff66ffcd66cd9a669aff33ffcd33cd9a339a663366ff00ffcd00cd9a009a660066330033'},
230  {col:622,str:'ffcdcdff9a9acd9a9aff6666cd66669a6666ff3333cd33339a3333663333ff0000cd00009a0000660000330000'},
231  {col:791,str:'ffcd9acd9a669a66339a6600cd9a33ffcd66ff9a00ffcd33cd9a00ffcd00ff9a33cd66006633009a3300cd6633ff9a66ff6600ff6633cd3300ff33009aff3366cd00336600339a0066cd339aff6666ff0066ff3333cd0033ff00cdff9a9acd66669a33669a009acd33cdff669aff00cdff339acd00cdff009affcd66cd9a339a66009a6633cd9a66ffcd00ff6633ffcd00cd9a00ffcd33ff9a00cd66006633009a3333cd6666ff9a00ff9a33ff6600cd3300ff339acdff669acd33669a00339a3366cd669aff0066ff3366ff0033cd0033ff339aff0066cd00336600669a339acd66cdff009aff33cdff009acd00cdffcd9aff9a66cd66339a66009a9a33cdcd66ff9a00ffcd33ff9a00cdcd00ff9a33ff6600cd33006633009a6633cd9a66ff6600ff6633ff3300cd3300ffff339acd00666600339a0033cd3366ff669aff0066ff3366cd0033ff0033ff9acdcd669a9a33669a0066cd339aff66cdff009acd009aff33cdff009a'},
232  {col:920,str:'cdcdcd9a9a9a666666333333'}];
233 
234  for (var indx = 0; indx < moreCol.length; ++indx) {
235  var entry = moreCol[indx];
236  for (var n=0; n < entry.str.length; n+=6) {
237  var num = parseInt(entry.col) + parseInt(n/6);
238  colorMap[num] = 'rgb(' + parseInt("0x" +entry.str.slice(n,n+2)) + "," + parseInt("0x" + entry.str.slice(n+2,n+4)) + "," + parseInt("0x" + entry.str.slice(n+4,n+6)) + ")";
239  }
240  }
241 
242  return colorMap;
243  }();
244 
245  JSROOT.Painter.MakeColorRGB = function(col) {
246  if ((col==null) || (col._typename != 'TColor')) return null;
247  var rgb = Math.round(col.fRed*255) + "," + Math.round(col.fGreen*255) + "," + Math.round(col.fBlue*255);
248  if ((col.fAlpha === undefined) || (col.fAlpha == 1.))
249  rgb = "rgb(" + rgb + ")";
250  else
251  rgb = "rgba(" + rgb + "," + col.fAlpha.toFixed(3) + ")";
252 
253  switch (rgb) {
254  case 'rgb(255,255,255)' : rgb = 'white'; break;
255  case 'rgb(0,0,0)' : rgb = 'black'; break;
256  case 'rgb(255,0,0)' : rgb = 'red'; break;
257  case 'rgb(0,255,0)' : rgb = 'green'; break;
258  case 'rgb(0,0,255)' : rgb = 'blue'; break;
259  case 'rgb(255,255,0)' : rgb = 'yellow'; break;
260  case 'rgb(255,0,255)' : rgb = 'magenta'; break;
261  case 'rgb(0,255,255)' : rgb = 'cyan'; break;
262  }
263  return rgb;
264  }
265 
266  JSROOT.Painter.adoptRootColors = function(objarr) {
267  if (!objarr || !objarr.arr) return;
268 
269  for (var n = 0; n < objarr.arr.length; ++n) {
270  var col = objarr.arr[n];
271  if ((col==null) || (col._typename != 'TColor')) continue;
272 
273  var num = col.fNumber;
274  if ((num<0) || (num>4096)) continue;
275 
276  var rgb = JSROOT.Painter.MakeColorRGB(col);
277  if (rgb == null) continue;
278 
279  if (JSROOT.Painter.root_colors[num] != rgb)
280  JSROOT.Painter.root_colors[num] = rgb;
281  }
282  }
283 
284  JSROOT.Painter.root_line_styles = new Array("", "", "3,3", "1,2",
285  "3,4,1,4", "5,3,1,3", "5,3,1,3,1,3,1,3", "5,5",
286  "5,3,1,3,1,3", "20,5", "20,10,1,10", "1,3");
287 
288  // Initialize ROOT markers
289  JSROOT.Painter.root_markers = new Array(
290  0, 100, 8, 7, 0, // 0..4
291  9, 100, 100, 100, 100, // 5..9
292  100, 100, 100, 100, 100, // 10..14
293  100, 100, 100, 100, 100, // 15..19
294  100, 103, 105, 104, 0, // 20..24
295  3, 4, 2, 1, 106, // 25..29
296  6, 7, 5, 102, 101); // 30..34
297 
299  JSROOT.Painter.createAttMarker = function(attmarker, style) {
300 
301  var marker_color = JSROOT.Painter.root_colors[attmarker.fMarkerColor];
302 
303  if ((style===null) || (style===undefined)) style = attmarker.fMarkerStyle;
304 
305  var res = { x0: 0, y0: 0, color: marker_color, style: style, size: 8, stroke: true, fill: true, marker: "", ndig: 0, used: true };
306 
307  res.Change = function(color, style, size) {
308 
309  if (color!==undefined) this.color = color;
310  if (style!==undefined) this.style = style;
311  if (size!==undefined) this.size = size; else size = this.size;
312 
313  this.x0 = this.y0 = 0;
314 
315  this.reset_pos = function() {
316  this.lastx = this.lasty = null;
317  }
318 
319  if ((this.style === 1) || (this.style === 777)) {
320  this.fill = false;
321  this.marker = "h1";
322  this.size = 1;
323 
324  // use special create function to handle relative position movements
325  this.create = function(x,y) {
326  var xx = Math.round(x), yy = Math.round(y), m1 = "M"+xx+","+yy+"h1";
327  var m2 = (this.lastx===null) ? m1 : ("m"+(xx-this.lastx)+","+(yy-this.lasty)+"h1");
328  this.lastx = xx+1; this.lasty = yy;
329  return (m2.length < m1.length) ? m2 : m1;
330  }
331 
332  this.reset_pos();
333  return true;
334  }
335 
336  var marker_kind = ((this.style>0) && (this.style<JSROOT.Painter.root_markers.length)) ? JSROOT.Painter.root_markers[this.style] : 100;
337  var shape = marker_kind % 100;
338 
339  this.fill = (marker_kind>=100);
340 
341  switch(this.style) {
342  case 1: size = this.size = 1; break;
343  case 6: size = this.size = 2; break;
344  case 7: size = this.size = 3; break;
345  default: this.size = size; size*=8;
346  }
347 
348  this.ndig = (size>7) ? 0 : ((size>2) ? 1 : 2);
349  if (shape == 6) this.ndig++;
350  var half = (size/2).toFixed(this.ndig), full = size.toFixed(this.ndig);
351 
352  switch(shape) {
353  case 0: // circle
354  this.x0 = -size/2;
355  this.marker = "a"+half+","+half+" 0 1,0 "+full+",0a"+half+","+half+" 0 1,0 -"+full+",0z";
356  break;
357  case 1: // cross
358  var d = (size/3).toFixed(res.ndig);
359  this.x0 = this.y0 = size/6;
360  this.marker = "h"+d+"v-"+d+"h-"+d+"v-"+d+"h-"+d+"v"+d+"h-"+d+"v"+d+"h"+d+"v"+d+"h"+d+"z";
361  break;
362  case 2: // diamond
363  this.x0 = -size/2;
364  this.marker = "l"+half+",-"+half+"l"+half+","+half+"l-"+half+","+half + "z";
365  break;
366  case 3: // square
367  this.x0 = this.y0 = -size/2;
368  this.marker = "v"+full+"h"+full+"v-"+full+"z";
369  break;
370  case 4: // triangle-up
371  this.y0 = size/2;
372  this.marker = "l-"+ half+",-"+full+"h"+full+"z";
373  break;
374  case 5: // triangle-down
375  this.y0 = -size/2;
376  this.marker = "l-"+ half+","+full+"h"+full+"z";
377  break;
378  case 6: // star
379  this.y0 = -size/2;
380  this.marker = "l" + (size/3).toFixed(res.ndig)+","+full +
381  "l-"+ (5/6*size).toFixed(res.ndig) + ",-" + (5/8*size).toFixed(res.ndig) +
382  "h" + full +
383  "l-" + (5/6*size).toFixed(res.ndig) + "," + (5/8*size).toFixed(res.ndig) + "z";
384  break;
385  case 7: // asterisk
386  this.x0 = this.y0 = -size/2;
387  this.marker = "l"+full+","+full +
388  "m0,-"+full+"l-"+full+","+full+
389  "m0,-"+half+"h"+full+"m-"+half+",-"+half+"v"+full;
390  break;
391  case 8: // plus
392  this.y0 = -size/2;
393  this.marker = "v"+full+"m-"+half+",-"+half+"h"+full;
394  break;
395  case 9: // mult
396  this.x0 = this.y0 = -size/2;
397  this.marker = "l"+full+","+full + "m0,-"+full+"l-"+full+","+full;
398  break;
399  default: // diamand
400  this.x0 = -size/2;
401  this.marker = "l"+half+",-"+half+"l"+half+","+half+"l-"+half+","+half + "z";
402  break;
403  }
404 
405  this.create = function(x,y) {
406  return "M" + (x+this.x0).toFixed(this.ndig)+ "," + (y+this.y0).toFixed(this.ndig) + this.marker;
407  }
408 
409  return true;
410  }
411 
412  res.Apply = function(selection) {
413  selection.style('stroke', this.stroke ? this.color : "none");
414  selection.style('fill', this.fill ? this.color : "none");
415  }
416 
417  res.func = res.Apply.bind(res);
418 
419  res.Change(marker_color, style, attmarker.fMarkerSize);
420 
421  return res;
422  }
423 
424  JSROOT.Painter.createAttLine = function(attline, borderw, can_excl) {
425 
426  var color = 0, _width = 0, style = 0;
427  if (typeof attline == 'string') {
428  if (attline=='black') { color = 1; _width = 1; } else
429  if (attline=='none') { _width = 0; }
430  } else
431  if (typeof attline == 'object') {
432  if ('fLineColor' in attline) color = attline.fLineColor;
433  if ('fLineWidth' in attline) _width = attline.fLineWidth;
434  if ('fLineStyle' in attline) style = attline.fLineStyle;
435  } else
436  if ((attline!==undefined) && !isNaN(attline)) {
437  color = attline;
438  }
439 
440  if (borderw!==undefined) _width = borderw;
441 
442  var line = {
443  used: true, // can mark object if it used or not,
444  color: JSROOT.Painter.root_colors[color],
445  width: _width,
446  dash: JSROOT.Painter.root_line_styles[style]
447  };
448 
449  if ((_width==0) || (color==0)) line.color = 'none';
450 
451  if (can_excl) {
452  line.excl_side = 0;
453  line.excl_width = 0;
454  if (Math.abs(line.width) > 99) {
455  // exclusion graph
456  line.excl_side = (line.width < 0) ? -1 : 1;
457  line.excl_width = Math.floor(line.width / 100) * 5;
458  line.width = line.width % 100; // line width
459  }
460  }
461 
462  // if custom color number used, use lightgrey color to show lines
463  if ((line.color === undefined) && (color>0))
464  line.color = 'lightgrey';
465 
466  line.Apply = function(selection) {
467  this.used = true;
468  if (this.color=='none') {
469  selection.style('stroke', null);
470  selection.style('stroke-width', null);
471  selection.style('stroke-dasharray', null);
472  } else {
473  selection.style('stroke', this.color);
474  selection.style('stroke-width', this.width);
475  if (this.dash && (this.dash.length>0))
476  selection.style('stroke-dasharray', this.dash);
477  }
478  }
479  line.func = line.Apply.bind(line);
480 
481  return line;
482  }
483 
484  JSROOT.Painter.clearCuts = function(chopt) {
485  /* decode string "chopt" and remove graphical cuts */
486  var left = chopt.indexOf('[');
487  var right = chopt.indexOf(']');
488  if ((left>=0) && (right>=0) && (left<right))
489  for (var i = left; i <= right; ++i) chopt[i] = ' ';
490  return chopt;
491  }
492 
493  JSROOT.Painter.root_fonts = new Array('Arial', 'Times New Roman',
494  'bTimes New Roman', 'biTimes New Roman', 'Arial',
495  'oArial', 'bArial', 'boArial', 'Courier New',
496  'oCourier New', 'bCourier New', 'boCourier New',
497  'Symbol', 'Times New Roman', 'Wingdings', 'Symbol', 'Verdana');
498 
499  JSROOT.Painter.getFontDetails = function(fontIndex, size) {
500 
501  var fontName = JSROOT.Painter.root_fonts[Math.floor(fontIndex / 10)];
502 
503  var res = { name: "Arial", size: 11, weight: null, style: null };
504 
505  if (size != undefined) res.size = Math.round(size);
506 
507  if (typeof fontName != 'string') fontName = "";
508 
509  while (fontName.length > 0) {
510  if (fontName.charAt(0)==='b') res.weight = "bold"; else
511  if (fontName.charAt(0)==='i') res.style = "italic"; else
512  if (fontName.charAt(0)==='o') res.style = "oblique"; else break;
513  fontName = fontName.substr(1);
514  }
515 
516  if (fontName == 'Symbol') {
517  res.weight = null;
518  res.style = null;
519  }
520 
521  res.name = fontName;
522 
523  res.SetFont = function(selection) {
524  selection.attr("font-family", this.name)
525  .attr("font-size", this.size)
526  .attr("xml:space","preserve");
527  if (this.weight!=null)
528  selection.attr("font-weight", this.weight);
529  if (this.style!=null)
530  selection.attr("font-style", this.style);
531  }
532 
533  res.asStyle = function(sz) {
534  return ((sz!=null) ? sz : this.size) + "px " + this.name;
535  }
536 
537  res.stringWidth = function(svg, line) {
538  /* compute the bounding box of a string by using temporary svg:text */
539  var text = svg.append("svg:text")
540  .attr("xml:space","preserve")
541  .style("opacity", 0)
542  .text(line);
543  this.SetFont(text);
544  var w = text.node().getBBox().width;
545  text.remove();
546  return w;
547  }
548 
549  res.func = res.SetFont.bind(res);
550 
551  return res;
552  }
553 
554  JSROOT.Painter.chooseTimeFormat = function(awidth, ticks) {
555  if (awidth < .5) return ticks ? "%S.%L" : "%M:%S.%L";
556  if (awidth < 30) return ticks ? "%Mm%S" : "%H:%M:%S";
557  awidth /= 60; if (awidth < 30) return ticks ? "%Hh%M" : "%d/%m %H:%M";
558  awidth /= 60; if (awidth < 12) return ticks ? "%d-%Hh" : "%d/%m/%y %Hh";
559  awidth /= 24; if (awidth < 15.218425) return ticks ? "%d/%m" : "%d/%m/%y";
560  awidth /= 30.43685; if (awidth < 6) return "%d/%m/%y";
561  awidth /= 12; if (awidth < 2) return ticks ? "%m/%y" : "%d/%m/%y";
562  return "%Y";
563  }
564 
565  JSROOT.Painter.getTimeFormat = function(axis) {
566  var idF = axis.fTimeFormat.indexOf('%F');
567  if (idF >= 0) return axis.fTimeFormat.substr(0, idF);
568  return axis.fTimeFormat;
569  }
570 
571  JSROOT.Painter.getTimeOffset = function(axis) {
572  var idF = axis.fTimeFormat.indexOf('%F');
573  if (idF < 0) return JSROOT.gStyle.TimeOffset;
574  var sof = axis.fTimeFormat.substr(idF + 2);
575  if (sof == '1995-01-01 00:00:00s0') return 788918400000;
576  // special case, used from DABC painters
577  if ((sof == "0") || (sof == "")) return 0;
578 
579  // decode time from ROOT string
580  var dt = new Date(0);
581  var pos = sof.indexOf("-"); dt.setFullYear(sof.substr(0,pos)); sof = sof.substr(pos+1);
582  pos = sof.indexOf("-"); dt.setMonth(parseInt(sof.substr(0,pos))-1); sof = sof.substr(pos+1);
583  pos = sof.indexOf(" "); dt.setDate(sof.substr(0,pos)); sof = sof.substr(pos+1);
584  pos = sof.indexOf(":"); dt.setHours(sof.substr(0,pos)); sof = sof.substr(pos+1);
585  pos = sof.indexOf(":"); dt.setMinutes(sof.substr(0,pos)); sof = sof.substr(pos+1);
586  pos = sof.indexOf("s"); dt.setSeconds(sof.substr(0,pos));
587  if (pos>0) { sof = sof.substr(pos+1); dt.setMilliseconds(sof); }
588  return dt.getTime();
589  }
590 
591  JSROOT.Painter.formatExp = function(label) {
592  var str = label;
593  if (parseFloat(str) == 1.0) return '1';
594  if (parseFloat(str) == 10.0) return '10';
595  var str = str.replace('e+', 'x10@');
596  var str = str.replace('e-', 'x10@-');
597  var _val = str.substring(0, str.indexOf('@'));
598  var _exp = str.substr(str.indexOf('@'));
599  _val = _val.replace('@', '');
600  _exp = _exp.replace('@', '');
601  var u, size = _exp.length;
602  for (var j = 0; j < size; ++j) {
603  var u, c = _exp.charAt(j);
604  if (c == '+') u = '\u207A'; else
605  if (c == '-') u = '\u207B'; else {
606  var e = parseInt(c);
607  if (e == 1) u = String.fromCharCode(0xB9); else
608  if (e > 1 && e < 4) u = String.fromCharCode(0xB0 + e); else
609  u = String.fromCharCode(0x2070 + e);
610  }
611  _exp = _exp.replace(c, u);
612  }
613  _val = _val.replace('1x', '');
614  return _val + _exp;
615  };
616 
617  JSROOT.Painter.translateExp = function(str) {
618  var lstr = str.match(/\^{[0-9]*}/gi);
619  if (lstr != null) {
620  var symbol = '';
621  for (var i = 0; i < lstr.length; ++i) {
622  symbol = lstr[i].replace(' ', '');
623  symbol = symbol.replace('^{', ''); // &sup
624  symbol = symbol.replace('}', ''); // ;
625  var size = symbol.length;
626  for (var j = 0; j < size; ++j) {
627  var c = symbol.charAt(j);
628  var u, e = parseInt(c);
629  if (e == 1) u = String.fromCharCode(0xB9);
630  else if (e > 1 && e < 4) u = String.fromCharCode(0xB0 + e);
631  else u = String.fromCharCode(0x2070 + e);
632  symbol = symbol.replace(c, u);
633  }
634  str = str.replace(lstr[i], symbol);
635  }
636  }
637  return str;
638  };
639 
640  JSROOT.Painter.symbols_map = {
641  // greek letters
642  '#alpha' : '\u03B1',
643  '#beta' : '\u03B2',
644  '#chi' : '\u03C7',
645  '#delta' : '\u03B4',
646  '#varepsilon' : '\u03B5',
647  '#phi' : '\u03C6',
648  '#gamma' : '\u03B3',
649  '#eta' : '\u03B7',
650  '#iota' : '\u03B9',
651  '#varphi' : '\u03C6',
652  '#kappa' : '\u03BA',
653  '#lambda' : '\u03BB',
654  '#mu' : '\u03BC',
655  '#nu' : '\u03BD',
656  '#omicron' : '\u03BF',
657  '#pi' : '\u03C0',
658  '#theta' : '\u03B8',
659  '#rho' : '\u03C1',
660  '#sigma' : '\u03C3',
661  '#tau' : '\u03C4',
662  '#upsilon' : '\u03C5',
663  '#varomega' : '\u03D6',
664  '#omega' : '\u03C9',
665  '#xi' : '\u03BE',
666  '#psi' : '\u03C8',
667  '#zeta' : '\u03B6',
668  '#Alpha' : '\u0391',
669  '#Beta' : '\u0392',
670  '#Chi' : '\u03A7',
671  '#Delta' : '\u0394',
672  '#Epsilon' : '\u0395',
673  '#Phi' : '\u03A6',
674  '#Gamma' : '\u0393',
675  '#Eta' : '\u0397',
676  '#Iota' : '\u0399',
677  '#vartheta' : '\u03D1',
678  '#Kappa' : '\u039A',
679  '#Lambda' : '\u039B',
680  '#Mu' : '\u039C',
681  '#Nu' : '\u039D',
682  '#Omicron' : '\u039F',
683  '#Pi' : '\u03A0',
684  '#Theta' : '\u0398',
685  '#Rho' : '\u03A1',
686  '#Sigma' : '\u03A3',
687  '#Tau' : '\u03A4',
688  '#Upsilon' : '\u03A5',
689  '#varsigma' : '\u03C2',
690  '#Omega' : '\u03A9',
691  '#Xi' : '\u039E',
692  '#Psi' : '\u03A8',
693  '#Zeta' : '\u0396',
694  '#varUpsilon' : '\u03D2',
695  '#epsilon' : '\u03B5',
696  // math symbols
697 
698  '#sqrt' : '\u221A',
699 
700  // from TLatex tables #2 & #3
701  '#leq' : '\u2264',
702  '#/' : '\u2044',
703  '#infty' : '\u221E',
704  '#voidb' : '\u0192',
705  '#club' : '\u2663',
706  '#diamond' : '\u2666',
707  '#heart' : '\u2665',
708  '#spade' : '\u2660',
709  '#leftrightarrow' : '\u2194',
710  '#leftarrow' : '\u2190',
711  '#uparrow' : '\u2191',
712  '#rightarrow' : '\u2192',
713  '#downarrow' : '\u2193',
714  '#circ' : '\u02C6', // ^
715  '#pm' : '\xB1',
716  '#doublequote' : '\u2033',
717  '#geq' : '\u2265',
718  '#times' : '\xD7',
719  '#propto' : '\u221D',
720  '#partial' : '\u2202',
721  '#bullet' : '\u2022',
722  '#divide' : '\xF7',
723  '#neq' : '\u2260',
724  '#equiv' : '\u2261',
725  '#approx' : '\u2248', // should be \u2245 ?
726  '#3dots' : '\u2026',
727  '#cbar' : '\u007C',
728  '#topbar' : '\xAF',
729  '#downleftarrow' : '\u21B5',
730  '#aleph' : '\u2135',
731  '#Jgothic' : '\u2111',
732  '#Rgothic' : '\u211C',
733  '#voidn' : '\u2118',
734  '#otimes' : '\u2297',
735  '#oplus' : '\u2295',
736  '#oslash' : '\u2205',
737  '#cap' : '\u2229',
738  '#cup' : '\u222A',
739  '#supseteq' : '\u2287',
740  '#supset' : '\u2283',
741  '#notsubset' : '\u2284',
742  '#subseteq' : '\u2286',
743  '#subset' : '\u2282',
744  '#int' : '\u222B',
745  '#in' : '\u2208',
746  '#notin' : '\u2209',
747  '#angle' : '\u2220',
748  '#nabla' : '\u2207',
749  '#oright' : '\xAE',
750  '#ocopyright' : '\xA9',
751  '#trademark' : '\u2122',
752  '#prod' : '\u220F',
753  '#surd' : '\u221A',
754  '#upoint' : '\u22C5',
755  '#corner' : '\xAC',
756  '#wedge' : '\u2227',
757  '#vee' : '\u2228',
758  '#Leftrightarrow' : '\u21D4',
759  '#Leftarrow' : '\u21D0',
760  '#Uparrow' : '\u21D1',
761  '#Rightarrow' : '\u21D2',
762  '#Downarrow' : '\u21D3',
763  '#LT' : '\x3C',
764  '#void1' : '\xAE',
765  '#copyright' : '\xA9',
766  '#void3' : '\u2122',
767  '#sum' : '\u2211',
768  '#arctop' : '',
769  '#lbar' : '',
770  '#arcbottom' : '',
771  '#void8' : '',
772  '#bottombar' : '\u230A',
773  '#arcbar' : '',
774  '#ltbar' : '',
775  '#AA' : '\u212B',
776  '#aa' : '\u00E5',
777  '#void06' : '',
778  '#GT' : '\x3E',
779  '#forall' : '\u2200',
780  '#exists' : '\u2203',
781  '#bar' : '',
782  '#vec' : '',
783  '#dot' : '\u22C5',
784  '#hat' : '\xB7',
785  '#ddot' : '',
786  '#acute' : '\acute',
787  '#grave' : '',
788  '#check' : '\u2713',
789  '#tilde' : '\u02DC',
790  '#slash' : '\u2044',
791  '#hbar' : '\u0127',
792  '#box' : '',
793  '#Box' : '',
794  '#parallel' : '',
795  '#perp' : '\u22A5',
796  '#odot' : '',
797  '#left' : '',
798  '#right' : ''
799  };
800 
801  JSROOT.Painter.translateLaTeX = function(string) {
802  var str = string;
803  str = this.translateExp(str);
804  while (str.indexOf('^{o}') != -1)
805  str = str.replace('^{o}', '\xBA');
806  var lstr = str.match(/\#sqrt{(.*?)}/gi);
807  if (lstr != null)
808  for (var i = 0; i < lstr.length; ++i) {
809  var symbol = lstr[i].replace(' ', '');
810  symbol = symbol.replace('#sqrt{', '#sqrt');
811  symbol = symbol.replace('}', '');
812  str = str.replace(lstr[i], symbol);
813  }
814  lstr = str.match(/\_{(.*?)}/gi);
815  if (lstr != null)
816  for (var i = 0; i < lstr.length; ++i) {
817  var symbol = lstr[i].replace(' ', '');
818  symbol = symbol.replace('_{', ''); // &sub
819  symbol = symbol.replace('}', ''); // ;
820  str = str.replace(lstr[i], symbol);
821  }
822  lstr = str.match(/\^{(.*?)}/gi);
823  if (lstr != null)
824  for (i = 0; i < lstr.length; ++i) {
825  var symbol = lstr[i].replace(' ', '');
826  symbol = symbol.replace('^{', ''); // &sup
827  symbol = symbol.replace('}', ''); // ;
828  str = str.replace(lstr[i], symbol);
829  }
830  while (str.indexOf('#/') != -1)
831  str = str.replace('#/', JSROOT.Painter.symbols_map['#/']);
832  for ( var x in JSROOT.Painter.symbols_map) {
833  while (str.indexOf(x) != -1)
834  str = str.replace(x, JSROOT.Painter.symbols_map[x]);
835  }
836 
837  // simple workaround for simple #splitline{first_line}{second_line}
838  if ((str.indexOf("#splitline{")==0) && (str.charAt(str.length-1)=="}")) {
839  var pos = str.indexOf("}{");
840  if ((pos>0) && (pos == str.lastIndexOf("}{"))) {
841  str = str.replace("}{", "\n");
842  str = str.slice(11, str.length-1);
843  }
844  }
845  return str;
846  }
847 
848  JSROOT.Painter.isAnyLatex = function(str) {
849  return (str.indexOf("#")>=0) || (str.indexOf("\\")>=0) || (str.indexOf("{")>=0);
850  }
851 
852  JSROOT.Painter.translateMath = function(str, kind, color) {
853  // function translate ROOT TLatex into MathJax format
854 
855  if (kind!=2) {
856  str = str.replace(/#LT/g, "\\langle");
857  str = str.replace(/#GT/g, "\\rangle");
858  str = str.replace(/#club/g, "\\clubsuit");
859  str = str.replace(/#spade/g, "\\spadesuit");
860  str = str.replace(/#heart/g, "\\heartsuit");
861  str = str.replace(/#diamond/g, "\\diamondsuit");
862  str = str.replace(/#voidn/g, "\\wp");
863  str = str.replace(/#voidb/g, "f");
864  str = str.replace(/#copyright/g, "(c)");
865  str = str.replace(/#ocopyright/g, "(c)");
866  str = str.replace(/#trademark/g, "TM");
867  str = str.replace(/#void3/g, "TM");
868  str = str.replace(/#oright/g, "R");
869  str = str.replace(/#void1/g, "R");
870  str = str.replace(/#3dots/g, "\\ldots");
871  str = str.replace(/#lbar/g, "\\mid");
872  str = str.replace(/#void8/g, "\\mid");
873  str = str.replace(/#divide/g, "\\div");
874  str = str.replace(/#Jgothic/g, "\\Im");
875  str = str.replace(/#Rgothic/g, "\\Re");
876  str = str.replace(/#doublequote/g, "\"");
877  str = str.replace(/#plus/g, "+");
878 
879  str = str.replace(/#diamond/g, "\\diamondsuit");
880  str = str.replace(/#voidn/g, "\\wp");
881  str = str.replace(/#voidb/g, "f");
882  str = str.replace(/#copyright/g, "(c)");
883  str = str.replace(/#ocopyright/g, "(c)");
884  str = str.replace(/#trademark/g, "TM");
885  str = str.replace(/#void3/g, "TM");
886  str = str.replace(/#oright/g, "R");
887  str = str.replace(/#void1/g, "R");
888  str = str.replace(/#3dots/g, "\\ldots");
889  str = str.replace(/#lbar/g, "\\mid");
890  str = str.replace(/#void8/g, "\\mid");
891  str = str.replace(/#divide/g, "\\div");
892  str = str.replace(/#Jgothic/g, "\\Im");
893  str = str.replace(/#Rgothic/g, "\\Re");
894  str = str.replace(/#doublequote/g, "\"");
895  str = str.replace(/#plus/g, "+");
896  str = str.replace(/#minus/g, "-");
897  str = str.replace(/#\//g, "/");
898  str = str.replace(/#upoint/g, ".");
899  str = str.replace(/#aa/g, "\\mathring{a}");
900  str = str.replace(/#AA/g, "\\mathring{A}");
901 
902  str = str.replace(/#omicron/g, "o");
903  str = str.replace(/#Alpha/g, "A");
904  str = str.replace(/#Beta/g, "B");
905  str = str.replace(/#Epsilon/g, "E");
906  str = str.replace(/#Zeta/g, "Z");
907  str = str.replace(/#Eta/g, "H");
908  str = str.replace(/#Iota/g, "I");
909  str = str.replace(/#Kappa/g, "K");
910  str = str.replace(/#Mu/g, "M");
911  str = str.replace(/#Nu/g, "N");
912  str = str.replace(/#Omicron/g, "O");
913  str = str.replace(/#Rho/g, "P");
914  str = str.replace(/#Tau/g, "T");
915  str = str.replace(/#Chi/g, "X");
916  str = str.replace(/#varomega/g, "\\varpi");
917 
918  str = str.replace(/#corner/g, "?");
919  str = str.replace(/#ltbar/g, "?");
920  str = str.replace(/#bottombar/g, "?");
921  str = str.replace(/#notsubset/g, "?");
922  str = str.replace(/#arcbottom/g, "?");
923  str = str.replace(/#cbar/g, "?");
924  str = str.replace(/#arctop/g, "?");
925  str = str.replace(/#topbar/g, "?");
926  str = str.replace(/#arcbar/g, "?");
927  str = str.replace(/#downleftarrow/g, "?");
928  str = str.replace(/#splitline/g, "\\genfrac{}{}{0pt}{}");
929  str = str.replace(/#it/g, "\\textit");
930  str = str.replace(/#bf/g, "\\textbf");
931 
932  str = str.replace(/#frac/g, "\\frac");
933  //str = str.replace(/#left{/g, "\\left\\{");
934  //str = str.replace(/#right}/g, "\\right\\}");
935  str = str.replace(/#left{/g, "\\lbrace");
936  str = str.replace(/#right}/g, "\\rbrace");
937  str = str.replace(/#left\[/g, "\\lbrack");
938  str = str.replace(/#right\]/g, "\\rbrack");
939  //str = str.replace(/#left/g, "\\left");
940  //str = str.replace(/#right/g, "\\right");
941  // processing of #[] #{} should be done
942  str = str.replace(/#\[\]{/g, "\\lbrack");
943  str = str.replace(/ } /g, "\\rbrack");
944  //str = str.replace(/#\[\]/g, "\\brack");
945  //str = str.replace(/#{}/g, "\\brace");
946  str = str.replace(/#\[/g, "\\lbrack");
947  str = str.replace(/#\]/g, "\\rbrack");
948  str = str.replace(/#{/g, "\\lbrace");
949  str = str.replace(/#}/g, "\\rbrace");
950  str = str.replace(/ /g, "\\;");
951 
952  for (var x in JSROOT.Painter.symbols_map) {
953  var y = "\\" + x.substr(1);
954  str = str.replace(new RegExp(x,'g'), y);
955  }
956  } else {
957  str = str.replace(/\\\^/g, "\\hat");
958  }
959 
960  if (typeof color != 'string') return "\\(" + str + "\\)";
961 
962  if (color.indexOf("rgb(")>=0)
963  color = color.replace(/rgb/g, "[RGB]")
964  .replace(/\(/g, '{')
965  .replace(/\)/g, '}');
966  return "\\(\\color{" + color + '}' + str + "\\)";
967  }
968 
969  // ==============================================================================
970 
971  JSROOT.TBasePainter = function() {
972  this.divid = null; // either id of element (preferable) or element itself
973  }
974 
975  JSROOT.TBasePainter.prototype.Cleanup = function() {
976  // generic method to cleanup painter
977  }
978 
979  JSROOT.TBasePainter.prototype.DrawingReady = function() {
980  // function should be called by the painter when first drawing is completed
981  this['_ready_called_'] = true;
982  if ('_ready_callback_' in this) {
983  JSROOT.CallBack(this['_ready_callback_'], this);
984  delete this['_ready_callback_'];
985  this['_ready_callback_'] = null;
986  }
987  return this;
988  }
989 
990  JSROOT.TBasePainter.prototype.WhenReady = function(callback) {
991  // call back will be called when painter ready with the drawing
992  if ('_ready_called_' in this) return JSROOT.CallBack(callback, this);
993  this['_ready_callback_'] = callback;
994  }
995 
996  JSROOT.TBasePainter.prototype.GetObject = function() {
997  return null;
998  }
999 
1000  JSROOT.TBasePainter.prototype.MatchObjectType = function(typ) {
1001  return false;
1002  }
1003 
1004  JSROOT.TBasePainter.prototype.UpdateObject = function(obj) {
1005  return false;
1006  }
1007 
1008  JSROOT.TBasePainter.prototype.RedrawPad = function(resize) {
1009  }
1010 
1011  JSROOT.TBasePainter.prototype.RedrawObject = function(obj) {
1012  if (this.UpdateObject(obj)) {
1013  var current = document.body.style.cursor;
1014  document.body.style.cursor = 'wait';
1015  this.RedrawPad();
1016  document.body.style.cursor = current;
1017  }
1018  }
1019 
1020  JSROOT.TBasePainter.prototype.CheckResize = function(arg) {
1021  return false; // indicate if resize is processed
1022  }
1023 
1024  JSROOT.TBasePainter.prototype.select_main = function() {
1025  // return d3.select for main element, defined with divid
1026  if ((this.divid === null) || (this.divid === undefined)) return d3.select(null);
1027  if ((typeof this.divid == "string") &&
1028  (this.divid.charAt(0) != "#")) return d3.select("#" + this.divid);
1029  return d3.select(this.divid);
1030  }
1031 
1032  JSROOT.TBasePainter.prototype.main_visible_rect = function() {
1033  // return rect with width/height which correcpond to the visible area of drawing region
1034 
1035  var render_to = this.select_main();
1036 
1037  var rect = render_to.node().getBoundingClientRect();
1038 
1039  var res = {};
1040 
1041  // this is size where canvas should be rendered
1042  res.width = Math.round(rect.width - this.GetStyleValue(render_to, 'padding-left') - this.GetStyleValue(render_to, 'padding-right'));
1043  res.height = Math.round(rect.height - this.GetStyleValue(render_to, 'padding-top') - this.GetStyleValue(render_to, 'padding-bottom'));
1044 
1045  return res;
1046  }
1047 
1048  JSROOT.TBasePainter.prototype.SetDivId = function(divid) {
1049  // base painter does not creates canvas or frames
1050  // it registered in the first child element
1051  if (arguments.length > 0)
1052  this.divid = divid;
1053  var main = this.select_main();
1054  var chld = main.node() ? main.node().firstChild : null;
1055  if (chld) chld.painter = this;
1056  }
1057 
1058  JSROOT.TBasePainter.prototype.SetItemName = function(name, opt) {
1059  if (typeof name === 'string') this._hitemname = name;
1060  else delete this._hitemname;
1061  // only upate draw option, never delete. null specified when update drawing
1062  if (typeof opt === 'string') this._hdrawopt = opt;
1063  }
1064 
1065  JSROOT.TBasePainter.prototype.GetItemName = function() {
1066  return ('_hitemname' in this) ? this._hitemname : null;
1067  }
1068 
1069  JSROOT.TBasePainter.prototype.GetItemDrawOpt = function() {
1070  return ('_hdrawopt' in this) ? this._hdrawopt : "";
1071  }
1072 
1073  JSROOT.TBasePainter.prototype.CanZoomIn = function(axis,left,right) {
1074  // check if it makes sense to zoom inside specified axis range
1075  return false;
1076  }
1077 
1078  JSROOT.TBasePainter.prototype.GetStyleValue = function(select, name) {
1079  var value = select.style(name);
1080  if (!value) return 0;
1081  value = parseFloat(value.replace("px",""));
1082  return isNaN(value) ? 0 : value;
1083  }
1084 
1085  // ==============================================================================
1086 
1087  JSROOT.TObjectPainter = function(obj) {
1088  JSROOT.TBasePainter.call(this);
1089  this.draw_g = null; // container for all drawn objects
1090  this.pad_name = ""; // name of pad where object is drawn
1091  this.main = null; // main painter, received from pad
1092  this.draw_object = ((obj!==undefined) && (typeof obj == 'object')) ? obj : null;
1093  }
1094 
1095  JSROOT.TObjectPainter.prototype = Object.create(JSROOT.TBasePainter.prototype);
1096 
1097  JSROOT.TObjectPainter.prototype.GetObject = function() {
1098  return this.draw_object;
1099  }
1100 
1101  JSROOT.TObjectPainter.prototype.MatchObjectType = function(arg) {
1102  if ((arg === undefined) || (arg === null) || (this.draw_object===null)) return false;
1103  if (typeof arg === 'string') return this.draw_object._typename === arg;
1104  return (typeof arg === 'object') && (this.draw_object._typename === arg._typename);
1105  }
1106 
1107  JSROOT.TObjectPainter.prototype.SetItemName = function(name, opt) {
1108  JSROOT.TBasePainter.prototype.SetItemName.call(this, name, opt);
1109  if (name=="") return;
1110  var can = this.svg_canvas();
1111  if (!can.empty()) can.select("title").text(name);
1112  else this.select_main().attr("title", name);
1113  }
1114 
1115  JSROOT.TObjectPainter.prototype.UpdateObject = function(obj) {
1116  // generic method to update object
1117  // just copy all members from source object
1118  if (!this.MatchObjectType(obj)) return false;
1119  JSROOT.extend(this.GetObject(), obj);
1120  return true;
1121  }
1122 
1123  JSROOT.TObjectPainter.prototype.GetTipName = function(append) {
1124  var res = this.GetItemName();
1125  if (res===null) res = "";
1126  if ((res.length === 0) && ('fName' in this.GetObject()))
1127  res = this.GetObject().fName;
1128  if (res.lenght > 20) res = res.substr(0,17)+"...";
1129  if ((res.length > 0) && (append!==undefined)) res += append;
1130  return res;
1131  }
1132 
1133  JSROOT.TObjectPainter.prototype.pad_painter = function(active_pad) {
1134  var can = active_pad ? this.svg_pad() : this.svg_canvas();
1135  return can.empty() ? null : can.property('pad_painter');
1136  }
1137 
1138  JSROOT.TObjectPainter.prototype.CheckResize = function(arg) {
1139  // no painter - no resize
1140  var pad_painter = this.pad_painter();
1141  if (pad_painter)
1142  return pad_painter.CheckCanvasResize(arg, false);
1143  return false;
1144  }
1145 
1146  JSROOT.TObjectPainter.prototype.RemoveDrawG = function() {
1147  // generic method to delete all graphical elements, associated with painter
1148  if (this.draw_g != null) {
1149  this.draw_g.remove();
1150  this.draw_g = null;
1151  }
1152  }
1153 
1157  JSROOT.TObjectPainter.prototype.RecreateDrawG = function(take_pad, layer) {
1158  if (this.draw_g) {
1159  // one should keep svg:g element on its place
1160  d3.selectAll(this.draw_g.node().childNodes).remove();
1161  } else
1162  if (take_pad) {
1163  if (typeof layer != 'string') layer = "text_layer";
1164  if (layer.charAt(0) == ".") layer = layer.substr(1);
1165  this.draw_g = this.svg_layer(layer).append("svg:g");
1166  } else {
1167  if (typeof layer != 'string') layer = ".main_layer";
1168  if (layer.charAt(0) != ".") layer = "." + layer;
1169  this.draw_g = this.svg_frame().select(layer).append("svg:g");
1170  }
1171 
1172  // set attributes for debugging
1173  if (this.draw_object!==null) {
1174  this.draw_g.attr('objname', this.draw_object.fName);
1175  this.draw_g.attr('objtype', this.draw_object._typename);
1176  }
1177 
1178  return this.draw_g;
1179  }
1180 
1182  JSROOT.TObjectPainter.prototype.svg_canvas = function() {
1183  return this.select_main().select(".root_canvas");
1184  }
1185 
1187  JSROOT.TObjectPainter.prototype.svg_pad = function(pad_name) {
1188  var c = this.svg_canvas();
1189  if (pad_name === undefined) pad_name = this.pad_name;
1190  if ((pad_name.length > 0) && !c.empty())
1191  c = c.select(".subpads_layer").select("[pad=" + pad_name + ']');
1192  return c;
1193  }
1194 
1196  JSROOT.TObjectPainter.prototype.svg_layer = function(name, pad_name) {
1197  var svg = this.svg_pad(pad_name);
1198  if (svg.empty()) return svg;
1199 
1200  var node = svg.node().firstChild;
1201 
1202  while (node!==null) {
1203  var elem = d3.select(node);
1204  if (elem.classed(name)) return elem;
1205  node = node.nextSibling;
1206  }
1207 
1208  return d3.select(null);
1209  }
1210 
1211  JSROOT.TObjectPainter.prototype.root_pad = function() {
1212  var pad_painter = this.pad_painter(true);
1213  return pad_painter ? pad_painter.pad : null;
1214  }
1215 
1217  JSROOT.TObjectPainter.prototype.ConvertToNDC = function(axis, value, isndc) {
1218  var pad = this.root_pad();
1219  if (isndc == null) isndc = false;
1220 
1221  if (isndc || (pad==null)) return value;
1222 
1223  if (axis=="y") {
1224  if (pad.fLogy)
1225  value = (value>0) ? JSROOT.log10(value) : pad.fUymin;
1226  return (value - pad.fY1) / (pad.fY2 - pad.fY1);
1227  }
1228  if (pad.fLogx)
1229  value = (value>0) ? JSROOT.log10(value) : pad.fUxmin;
1230  return (value - pad.fX1) / (pad.fX2 - pad.fX1);
1231  }
1232 
1236  JSROOT.TObjectPainter.prototype.AxisToSvg = function(axis, value, ndc) {
1237  var main = this.main_painter();
1238  if ((main!=null) && !ndc)
1239  return axis=="y" ? main.gry(value) : main.grx(value);
1240  if (!ndc) value = this.ConvertToNDC(axis, value);
1241  if (axis=="y") return (1-value)*this.pad_height();
1242  return value*this.pad_width();
1243  }
1244 
1245  JSROOT.TObjectPainter.prototype.PadToSvg = function(axis, value, ndc) {
1246  return this.AxisToSvg(axis,value,ndc);
1247  }
1248 
1250  JSROOT.TObjectPainter.prototype.svg_frame = function() {
1251  return this.svg_pad().select(".root_frame");
1252  }
1253 
1254  JSROOT.TObjectPainter.prototype.frame_painter = function() {
1255  var res = this.svg_frame().property('frame_painter');
1256  return (res===undefined) ? null : res;
1257  }
1258 
1259  JSROOT.TObjectPainter.prototype.pad_width = function(pad_name) {
1260  var res = this.svg_pad(pad_name).property("draw_width");
1261  return isNaN(res) ? 0 : res;
1262  }
1263 
1264  JSROOT.TObjectPainter.prototype.pad_height = function(pad_name) {
1265  var res = this.svg_pad(pad_name).property("draw_height");
1266  return isNaN(res) ? 0 : res;
1267  }
1268 
1269  JSROOT.TObjectPainter.prototype.frame_x = function() {
1270  var res = parseInt(this.svg_frame().attr("x"));
1271  return isNaN(res) ? 0 : res;
1272  }
1273 
1274  JSROOT.TObjectPainter.prototype.frame_y = function() {
1275  var res = parseInt(this.svg_frame().attr("y"));
1276  return isNaN(res) ? 0 : res;
1277  }
1278 
1279  JSROOT.TObjectPainter.prototype.frame_width = function() {
1280  var res = parseInt(this.svg_frame().attr("width"));
1281  return isNaN(res) ? 0 : res;
1282  }
1283 
1284  JSROOT.TObjectPainter.prototype.frame_height = function() {
1285  var res = parseInt(this.svg_frame().attr("height"));
1286  return isNaN(res) ? 0 : res;
1287  }
1288 
1289  JSROOT.TObjectPainter.prototype.embed_3d = function() {
1290  // returns embed mode for 3D drawings (three.js) inside SVG
1291  // 0 - no embedding, 3D drawing take full size of canvas
1292  // 1 - no embedding, canvas placed over svg with proper size (resize problem may appear)
1293  // 2 - normall embedding via ForeginObject, works only with Firefox
1294 
1295  if (JSROOT.gStyle.Embed3DinSVG < 2) return JSROOT.gStyle.Embed3DinSVG;
1296  if (JSROOT.browser.isFirefox /*|| JSROOT.browser.isWebKit*/)
1297  return JSROOT.gStyle.Embed3DinSVG; // use specified mode
1298  return 1; // default is overlay
1299  }
1300 
1301  JSROOT.TObjectPainter.prototype.size_for_3d = function(can3d) {
1302  // one uses frame sizes for the 3D drawing - like TH2/TH3 objects
1303 
1304  if (can3d === undefined) can3d = this.embed_3d();
1305 
1306  var pad = this.svg_pad();
1307 
1308  var clname = this.pad_name;
1309  if (clname == '') clname = 'canvas';
1310  clname = "draw3d_" + clname;
1311 
1312  if (pad.empty()) {
1313  // this is a case when object drawn without canvas
1314 
1315  var rect = this.main_visible_rect();
1316 
1317  if ((rect.height<10) && (rect.width>10)) {
1318  rect.height = Math.round(0.66*rect.width);
1319  this.select_main().style('height', rect.height + "px");
1320  }
1321  rect.x = 0; rect.y = 0; rect.clname = clname; rect.can3d = -1;
1322  return rect;
1323  }
1324 
1325  var elem = pad;
1326  if (can3d == 0) elem = this.svg_canvas();
1327 
1328  var size = { x: 0, y: 0, width: 100, height:100, clname: clname, can3d: can3d };
1329 
1330  if (this.frame_painter()!==null) {
1331  elem = this.svg_frame();
1332  size.x = elem.property("draw_x");
1333  size.y = elem.property("draw_y");
1334  }
1335 
1336  size.width = elem.property("draw_width");
1337  size.height = elem.property("draw_height");
1338 
1339  if ((this.frame_painter()===null) && (can3d > 0)) {
1340  size.x = Math.round(size.x + size.width*0.1);
1341  size.y = Math.round(size.y + size.height*0.1);
1342  size.width = Math.round(size.width*0.8);
1343  size.height = Math.round(size.height*0.8);
1344  }
1345 
1346  if (can3d === 1)
1347  this.CalcAbsolutePosition(this.svg_pad(), size);
1348 
1349  return size;
1350  }
1351 
1352  JSROOT.TObjectPainter.prototype.clear_3d_canvas = function() {
1353  var can3d = this.svg_pad().property('can3d');
1354  if (can3d === null) return;
1355 
1356  this.svg_pad().property('can3d', null);
1357 
1358  var size = this.size_for_3d(can3d);
1359 
1360  if (size.can3d === 0) {
1361  d3.select(this.svg_canvas().node().nextSibling).remove(); // remove html5 canvas
1362  this.svg_canvas().style('display', null); // show SVG canvas
1363  } else {
1364  if (this.svg_pad().empty()) return;
1365 
1366  this.apply_3d_size(size).remove();
1367 
1368  this.svg_frame().style('display', null);
1369  }
1370  }
1371 
1372  JSROOT.TObjectPainter.prototype.add_3d_canvas = function(size, canv) {
1373 
1374  if ((canv == null) || (size.can3d < -1)) return;
1375 
1376  if (size.can3d === -1) {
1377  // case when 3D object drawn without canvas
1378 
1379  var main = this.select_main().node();
1380  if (main !== null) {
1381  main.appendChild(canv);
1382  canv['painter'] = this;
1383  }
1384 
1385  return;
1386  }
1387 
1388  this.svg_pad().property('can3d', size.can3d);
1389 
1390  if (size.can3d === 0) {
1391  this.svg_canvas().style('display', 'none'); // hide SVG canvas
1392 
1393  this.svg_canvas().node().parentNode.appendChild(canv); // add directly
1394  } else {
1395  if (this.svg_pad().empty()) return;
1396 
1397  // first hide normal frame
1398  this.svg_frame().style('display', 'none');
1399 
1400  var elem = this.apply_3d_size(size);
1401 
1402  elem.attr('title','').node().appendChild(canv);
1403  }
1404  }
1405 
1406  JSROOT.TObjectPainter.prototype.apply_3d_size = function(size, onlyget) {
1407 
1408  if (size.can3d < 0) return d3.select(null);
1409 
1410  var elem;
1411 
1412  if (size.can3d > 1) {
1413 
1414  var layer = this.svg_layer("special_layer");
1415 
1416  elem = layer.select("." + size.clname);
1417  if (onlyget) return elem;
1418 
1419  if (elem.empty())
1420  elem = layer.append("foreignObject").attr("class", size.clname);
1421 
1422  elem.attr('x', size.x)
1423  .attr('y', size.y)
1424  .attr('width', size.width)
1425  .attr('height', size.height)
1426  .attr('viewBox', "0 0 " + size.width + " " + size.height)
1427  .attr('preserveAspectRatio','xMidYMid');
1428 
1429  } else {
1430  elem = d3.select(this.svg_canvas().node().parentNode).select("." + size.clname);
1431  if (onlyget) return elem;
1432 
1433  // force redraw by resize
1434  this.svg_canvas().property('redraw_by_resize', true);
1435 
1436  if (elem.empty()) {
1437  elem = d3.select(this.svg_canvas().node().parentNode)
1438  .append('div').attr("class", size.clname);
1439  }
1440 
1441  elem.style('position','absolute')
1442  .style('left', size.x + 'px')
1443  .style('top', size.y + 'px')
1444  .style('width', size.width + 'px')
1445  .style('height', size.height + 'px');
1446  }
1447 
1448  return elem;
1449  }
1450 
1451 
1453  JSROOT.TObjectPainter.prototype.main_painter = function() {
1454  if (this.main === null) {
1455  var svg_p = this.svg_pad();
1456  if (!svg_p.empty()) {
1457  this.main = svg_p.property('mainpainter');
1458  if (this.main === undefined) this.main = null;
1459  }
1460  }
1461  return this.main;
1462  }
1463 
1464  JSROOT.TObjectPainter.prototype.is_main_painter = function() {
1465  return this === this.main_painter();
1466  }
1467 
1468  JSROOT.TObjectPainter.prototype.SetDivId = function(divid, is_main) {
1469  // Assigns id of top element (normally <div></div> where drawing is done
1470  // is_main - -1 - not add to painters list,
1471  // 0 - normal painter (default),
1472  // 1 - major objects like TH1/TH2 (required canvas with frame)
1473  // 2 - if canvas missing, create it, but not set as main object
1474  // 3 - if canvas and (or) frame missing, create them, but not set as main object
1475  // 4 - major objects like TH3 (required canvas, but no frame)
1476  // 5 - major objects like TGeoVolume (do not require canvas)
1477  // In some situations canvas may not exists - for instance object drawn as html, not as svg.
1478  // In such case the only painter will be assigned to the first element
1479 
1480  if (divid !== undefined)
1481  this.divid = divid;
1482 
1483  if ((is_main === null) || (is_main === undefined)) is_main = 0;
1484 
1485  this.create_canvas = false;
1486 
1487  // SVG element where canvas is drawn
1488  var svg_c = this.svg_canvas();
1489 
1490  if (svg_c.empty() && (is_main > 0) && (is_main!==5)) {
1491  JSROOT.Painter.drawCanvas(divid, null, ((is_main == 2) || (is_main == 4)) ? "noframe" : "");
1492  svg_c = this.svg_canvas();
1493  this.create_canvas = true;
1494  }
1495 
1496  if (svg_c.empty()) {
1497  if ((is_main < 0) || (is_main===5) || this.iscan) return;
1498  var main = this.select_main();
1499  if (main.node() && main.node().firstChild)
1500  main.node().firstChild.painter = this;
1501  return;
1502  }
1503 
1504  // SVG element where current pad is drawn (can be canvas itself)
1505  this.pad_name = svg_c.property('current_pad');
1506 
1507  if (is_main < 0) return;
1508 
1509  // create TFrame element if not exists
1510  if (this.svg_frame().select(".main_layer").empty() && ((is_main == 1) || (is_main == 3))) {
1511  JSROOT.Painter.drawFrame(divid, null);
1512  if (this.svg_frame().empty()) return alert("Fail to draw dummy TFrame");
1513  }
1514 
1515  var svg_p = this.svg_pad();
1516  if (svg_p.empty()) return;
1517 
1518  if (svg_p.property('pad_painter') !== this)
1519  svg_p.property('pad_painter').painters.push(this);
1520 
1521  if (((is_main === 1) || (is_main === 4) || (is_main === 5)) && (svg_p.property('mainpainter') == null))
1522  // when this is first main painter in the pad
1523  svg_p.property('mainpainter', this);
1524  }
1525 
1526  JSROOT.TObjectPainter.prototype.CalcAbsolutePosition = function(sel, pos) {
1527  while (!sel.empty() && !sel.classed('root_canvas')) {
1528  if (sel.classed('root_frame') || sel.classed('root_pad')) {
1529  pos.x += sel.property("draw_x");
1530  pos.y += sel.property("draw_y");
1531  }
1532  sel = d3.select(sel.node().parentNode);
1533  }
1534  return pos;
1535  }
1536 
1537 
1538  JSROOT.TObjectPainter.prototype.createAttFill = function(attfill, pattern, color, kind) {
1539 
1540  // fill kind can be 1 or 2
1541  // 1 means object drawing where combination fillcolor==0 and fillstyle==1001 means no filling
1542  // 2 means all other objects where such combination is white-color filling
1543 
1544  var fill = { color: "none", colorindx: 0, pattern: 0, used: true, kind: 2 };
1545 
1546  if (kind!==undefined) fill.kind = kind;
1547 
1548  fill.Apply = function(selection) {
1549  this.used = true;
1550 
1551  selection.style('fill', this.color);
1552 
1553  if ('opacity' in this)
1554  selection.style('opacity', this.opacity);
1555 
1556  if ('antialias' in this)
1557  selection.style('antialias', this.antialias);
1558  }
1559  fill.func = fill.Apply.bind(fill);
1560 
1561  fill.Change = function(color, pattern, svg) {
1562  if ((color !== undefined) && !isNaN(color))
1563  this.colorindx = color;
1564 
1565  if ((pattern !== undefined) && !isNaN(pattern)) {
1566  this.pattern = pattern;
1567  delete this.opacity;
1568  delete this.antialias;
1569  }
1570 
1571  if (this.pattern < 1001) {
1572  this.color = 'none';
1573  return true;
1574  }
1575 
1576  if ((this.pattern === 1001) && (this.colorindx===0) && (this.kind===1)) {
1577  this.color = 'none';
1578  return true;
1579  }
1580 
1581  this.color = JSROOT.Painter.root_colors[this.colorindx];
1582  if (typeof this.color != 'string') this.color = "none";
1583 
1584  if (this.pattern === 1001) return true;
1585 
1586  if ((this.pattern >= 4000) && (this.pattern <= 4100)) {
1587  // special transparent colors (use for subpads)
1588  this.opacity = (this.pattern - 4000)/100;
1589  return true;
1590  }
1591 
1592  if ((svg===undefined) || svg.empty() || (this.pattern < 3000) || (this.pattern > 3025)) return false;
1593 
1594  var id = "pat_" + this.pattern + "_" + this.colorindx;
1595 
1596  var defs = svg.select('defs');
1597  if (defs.empty())
1598  defs = svg.insert("svg:defs",":first-child");
1599 
1600  var line_color = this.color;
1601  this.color = "url(#" + id + ")";
1602  this.antialias = false;
1603 
1604  if (!defs.select("."+id).empty()) return true;
1605 
1606  var patt = defs.append('svg:pattern').attr("id", id).attr("class",id).attr("patternUnits","userSpaceOnUse");
1607 
1608  switch (this.pattern) {
1609  case 3001:
1610  patt.attr("width", 2).attr("height", 2);
1611  patt.append('svg:rect').attr("x", 0).attr("y", 0).attr("width", 1).attr("height", 1);
1612  patt.append('svg:rect').attr("x", 1).attr("y", 1).attr("width", 1).attr("height", 1);
1613  break;
1614  case 3002:
1615  patt.attr("width", 4).attr("height", 2);
1616  patt.append('svg:rect').attr("x", 1).attr("y", 0).attr("width", 1).attr("height", 1);
1617  patt.append('svg:rect').attr("x", 3).attr("y", 1).attr("width", 1).attr("height", 1);
1618  break;
1619  case 3003:
1620  patt.attr("width", 4).attr("height", 4);
1621  patt.append('svg:rect').attr("x", 2).attr("y", 1).attr("width", 1).attr("height", 1);
1622  patt.append('svg:rect').attr("x", 0).attr("y", 3).attr("width", 1).attr("height", 1);
1623  break;
1624  case 3005:
1625  patt.attr("width", 8).attr("height", 8);
1626  patt.append("svg:line").attr("x1", 0).attr("y1", 0).attr("x2", 8).attr("y2", 8);
1627  break;
1628  case 3006:
1629  patt.attr("width", 4).attr("height", 4);
1630  patt.append("svg:line").attr("x1", 1).attr("y1", 0).attr("x2", 1).attr("y2", 3);
1631  break;
1632  case 3007:
1633  patt.attr("width", 4).attr("height", 4);
1634  patt.append("svg:line").attr("x1", 0).attr("y1", 1).attr("x2", 3).attr("y2", 1);
1635  break;
1636  case 3010: // bricks
1637  patt.attr("width", 10).attr("height", 10);
1638  patt.append("svg:line").attr("x1", 0).attr("y1", 2).attr("x2", 10).attr("y2", 2);
1639  patt.append("svg:line").attr("x1", 0).attr("y1", 7).attr("x2", 10).attr("y2", 7);
1640  patt.append("svg:line").attr("x1", 2).attr("y1", 0).attr("x2", 2).attr("y2", 2);
1641  patt.append("svg:line").attr("x1", 7).attr("y1", 2).attr("x2", 7).attr("y2", 7);
1642  patt.append("svg:line").attr("x1", 2).attr("y1", 7).attr("x2", 2).attr("y2", 10);
1643  break;
1644  case 3021: // stairs
1645  case 3022:
1646  patt.attr("width", 10).attr("height", 10);
1647  patt.append("svg:line").attr("x1", 0).attr("y1", 5).attr("x2", 5).attr("y2", 5);
1648  patt.append("svg:line").attr("x1", 5).attr("y1", 5).attr("x2", 5).attr("y2", 0);
1649  patt.append("svg:line").attr("x1", 5).attr("y1", 10).attr("x2", 10).attr("y2", 10);
1650  patt.append("svg:line").attr("x1", 10).attr("y1", 10).attr("x2", 10).attr("y2", 5);
1651  break;
1652  default: /* == 3004 */
1653  patt.attr("width", 8).attr("height", 8);
1654  patt.append("svg:line").attr("x1", 8).attr("y1", 0).attr("x2", 0).attr("y2", 8);
1655  break;
1656  }
1657 
1658  patt.selectAll('line').style("stroke",line_color).style("stroke-width", 1);
1659  patt.selectAll('rect').style("fill",line_color);
1660 
1661  return true;
1662  }
1663 
1664  if ((attfill!==null) && (typeof attfill == 'object')) {
1665  if ('fFillStyle' in attfill) pattern = attfill.fFillStyle;
1666  if ('fFillColor' in attfill) color = attfill.fFillColor;
1667  }
1668 
1669  fill.Change(color, pattern, this.svg_canvas());
1670 
1671  return fill;
1672  }
1673 
1674  JSROOT.TObjectPainter.prototype.ForEachPainter = function(userfunc) {
1675  // Iterate over all known painters
1676 
1677  var main = this.select_main();
1678  var painter = (main.node() && main.node().firstChild) ? main.node().firstChild['painter'] : null;
1679  if (painter!=null) return userfunc(painter);
1680 
1681  var pad_painter = this.pad_painter(true);
1682  if (pad_painter == null) return;
1683 
1684  userfunc(pad_painter);
1685  if ('painters' in pad_painter)
1686  for (var k = 0; k < pad_painter.painters.length; ++k)
1687  userfunc(pad_painter.painters[k]);
1688  }
1689 
1690  JSROOT.TObjectPainter.prototype.Cleanup = function() {
1691  // generic method to cleanup painters
1692  this.select_main().html("");
1693  }
1694 
1695  JSROOT.TObjectPainter.prototype.RedrawPad = function() {
1696  // call Redraw methods for each painter in the frame
1697  // if selobj specified, painter with selected object will be redrawn
1698  var pad_painter = this.pad_painter(true);
1699  if (pad_painter) pad_painter.Redraw();
1700  }
1701 
1702  JSROOT.TObjectPainter.prototype.SwitchTooltip = function(on) {
1703  var fp = this.frame_painter();
1704  if (fp) fp.ProcessTooltipEvent(null, on);
1705  }
1706 
1707  JSROOT.TObjectPainter.prototype.AddDrag = function(callback) {
1708  if (!JSROOT.gStyle.MoveResize) return;
1709 
1710  var pthis = this;
1711 
1712  var rect_width = function() { return Number(pthis.draw_g.attr("width")); }
1713  var rect_height = function() { return Number(pthis.draw_g.attr("height")); }
1714 
1715  var acc_x = 0, acc_y = 0, pad_w = 1, pad_h = 1, drag_tm = null;
1716 
1717  function detectRightButton(event) {
1718  if ('buttons' in event) return event.buttons === 2;
1719  else if ('which' in event) return event.which === 3;
1720  else if ('button' in event) return event.button === 2;
1721  return false;
1722  }
1723 
1724  var resize_corner1 = this.draw_g.select('.resize_corner1');
1725  if (resize_corner1.empty())
1726  resize_corner1 = this.draw_g
1727  .append("path")
1728  .attr('class','resize_corner1')
1729  .attr("d","M2,2 h15 v-5 h-20 v20 h5 Z");
1730 
1731  var resize_corner2 = this.draw_g.select('.resize_corner2');
1732  if (resize_corner2.empty())
1733  resize_corner2 = this.draw_g
1734  .append("path")
1735  .attr('class','resize_corner2')
1736  .attr("d","M-2,-2 h-15 v5 h20 v-20 h-5 Z");
1737 
1738  resize_corner1.style("opacity", "0")
1739  .style("cursor", "nw-resize");
1740 
1741  resize_corner2.style("opacity", "0")
1742  .style("cursor", "se-resize")
1743  .attr("transform", "translate(" + rect_width() + "," + rect_height() + ")");
1744 
1745  var drag_rect = null;
1746 
1747  function complete_drag() {
1748  drag_rect.style("cursor", "auto");
1749 
1750  var oldx = Number(pthis.draw_g.attr("x")),
1751  oldy = Number(pthis.draw_g.attr("y")),
1752  newx = Number(drag_rect.attr("x")),
1753  newy = Number(drag_rect.attr("y")),
1754  newwidth = Number(drag_rect.attr("width")),
1755  newheight = Number(drag_rect.attr("height"));
1756 
1757  var change_size = (newwidth !== rect_width()) || (newheight !== rect_height());
1758  var change_pos = (newx !== oldx) || (newy !== oldy);
1759 
1760  pthis.draw_g.attr('x', newx).attr('y', newy)
1761  .attr("transform", "translate(" + newx + "," + newy + ")")
1762  .attr('width', newwidth).attr('height', newheight);
1763 
1764  drag_rect.remove();
1765  drag_rect = null;
1766 
1767  pthis.SwitchTooltip(true);
1768 
1769  resize_corner2.attr("transform", "translate(" + newwidth + "," + newheight + ")");
1770 
1771  if (change_size || change_pos) {
1772  if (change_size && ('resize' in callback)) callback.resize(newwidth, newheight);
1773  if (change_pos && ('move' in callback)) callback.move(newx, newy, newx - oldxx, newy-oldy);
1774 
1775  if (change_size || change_pos) {
1776  if ('obj' in callback) {
1777  callback.obj.fX1NDC = newx / pthis.pad_width();
1778  callback.obj.fX2NDC = (newx + newwidth) / pthis.pad_width();
1779  callback.obj.fY1NDC = 1 - (newy + newheight) / pthis.pad_height();
1780  callback.obj.fY2NDC = 1 - newy / pthis.pad_height();
1781  }
1782  if ('redraw' in callback) callback.redraw();
1783  }
1784  }
1785 
1786  return change_size || change_pos;
1787  }
1788 
1789  var drag_move = d3.behavior.drag().origin(Object)
1790  .on("dragstart", function() {
1791  if (detectRightButton(d3.event.sourceEvent)) return;
1792 
1793  JSROOT.Painter.closeMenu(); // close menu
1794 
1795  pthis.SwitchTooltip(false); // disable tooltip
1796 
1797  d3.event.sourceEvent.preventDefault();
1798  d3.event.sourceEvent.stopPropagation();
1799 
1800  acc_x = 0; acc_y = 0;
1801  pad_w = pthis.pad_width() - rect_width();
1802  pad_h = pthis.pad_height() - rect_height();
1803 
1804  drag_tm = new Date();
1805 
1806  drag_rect = d3.select(pthis.draw_g.node().parentNode).append("rect")
1807  .classed("zoom", true)
1808  .attr("x", pthis.draw_g.attr("x"))
1809  .attr("y", pthis.draw_g.attr("y"))
1810  .attr("width", rect_width())
1811  .attr("height", rect_height())
1812  .style("cursor", "move")
1813  .style("pointer-events","none"); // let forward double click to underlying elements
1814  }).on("drag", function() {
1815  if (drag_rect == null) return;
1816 
1817  d3.event.sourceEvent.preventDefault();
1818 
1819  var x = Number(drag_rect.attr("x")), y = Number(drag_rect.attr("y"));
1820  var dx = d3.event.dx, dy = d3.event.dy;
1821 
1822  if ((acc_x<0) && (dx>0)) { acc_x+=dx; dx=0; if (acc_x>0) { dx=acc_x; acc_x=0; }}
1823  if ((acc_x>0) && (dx<0)) { acc_x+=dx; dx=0; if (acc_x<0) { dx=acc_x; acc_x=0; }}
1824  if ((acc_y<0) && (dy>0)) { acc_y+=dy; dy=0; if (acc_y>0) { dy=acc_y; acc_y=0; }}
1825  if ((acc_y>0) && (dy<0)) { acc_y+=dy; dy=0; if (acc_y<0) { dy=acc_y; acc_y=0; }}
1826 
1827  if (x+dx<0) { acc_x+=(x+dx); x=0; } else
1828  if (x+dx>pad_w) { acc_x+=(x+dx-pad_w); x=pad_w; } else x+=dx;
1829 
1830  if (y+dy<0) { acc_y+=(y+dy); y = 0; } else
1831  if (y+dy>pad_h) { acc_y+=(y+dy-pad_h); y=pad_h; } else y+=dy;
1832 
1833  drag_rect.attr("x", x).attr("y", y);
1834 
1835  d3.event.sourceEvent.stopPropagation();
1836  }).on("dragend", function() {
1837  if (drag_rect==null) return;
1838 
1839  d3.event.sourceEvent.preventDefault();
1840 
1841  if (complete_drag() === false)
1842  if(callback['ctxmenu'] && ((new Date()).getTime() - drag_tm.getTime() > 600)) {
1843  var rrr = resize_corner2.node().getBoundingClientRect();
1844  pthis.ShowContextMenu('main', { clientX: rrr.left, clientY: rrr.top } );
1845  }
1846  });
1847 
1848  var drag_resize = d3.behavior.drag().origin(Object)
1849  .on( "dragstart", function() {
1850  if (detectRightButton(d3.event.sourceEvent)) return;
1851 
1852  d3.event.sourceEvent.stopPropagation();
1853  d3.event.sourceEvent.preventDefault();
1854 
1855  pthis.SwitchTooltip(false); // disable tooltip
1856 
1857  acc_x = 0; acc_y = 0;
1858  pad_w = pthis.pad_width();
1859  pad_h = pthis.pad_height();
1860  drag_rect = d3.select(pthis.draw_g.node().parentNode).append("rect")
1861  .classed("zoom", true)
1862  .attr("x", pthis.draw_g.attr("x"))
1863  .attr("y", pthis.draw_g.attr("y"))
1864  .attr("width", rect_width())
1865  .attr("height", rect_height())
1866  .style("cursor", d3.select(this).style("cursor"));
1867  }).on("drag", function() {
1868  if (drag_rect == null) return;
1869 
1870  d3.event.sourceEvent.preventDefault();
1871 
1872  var w = Number(drag_rect.attr("width")), h = Number(drag_rect.attr("height")),
1873  x = Number(drag_rect.attr("x")), y = Number(drag_rect.attr("y"));
1874  var dx = d3.event.dx, dy = d3.event.dy;
1875  if ((acc_x<0) && (dx>0)) { acc_x+=dx; dx=0; if (acc_x>0) { dx=acc_x; acc_x=0; }}
1876  if ((acc_x>0) && (dx<0)) { acc_x+=dx; dx=0; if (acc_x<0) { dx=acc_x; acc_x=0; }}
1877  if ((acc_y<0) && (dy>0)) { acc_y+=dy; dy=0; if (acc_y>0) { dy=acc_y; acc_y=0; }}
1878  if ((acc_y>0) && (dy<0)) { acc_y+=dy; dy=0; if (acc_y<0) { dy=acc_y; acc_y=0; }}
1879 
1880  if (d3.select(this).classed('resize_corner1')) {
1881  if (x+dx < 0) { acc_x += (x+dx); w += x; x = 0; } else
1882  if (w-dx < 0) { acc_x -= (w-dx); x += w; w = 0; } else { x+=dx; w-=dx; }
1883  if (y+dy < 0) { acc_y += (y+dy); h += y; y = 0; } else
1884  if (h-dy < 0) { acc_y -= (h-dy); y += h; h = 0; } else { y+=dy; h-=dy; }
1885  } else {
1886  if (x+w+dx > pad_w) { acc_x += (x+w+dx-pad_w); w = pad_w-x; } else
1887  if (w+dx < 0) { acc_x += (w+dx); w = 0; } else w += dx;
1888  if (y+h+dy > pad_h) { acc_y += (y+h+dy-pad_h); h = pad_h-y; } else
1889  if (h+dy < 0) { acc_y += (h+dy); h=0; } else h += dy;
1890  }
1891 
1892  drag_rect.attr("x", x).attr("y", y).attr("width", w).attr("height", h);
1893 
1894  d3.event.sourceEvent.stopPropagation();
1895  }).on( "dragend", function() {
1896  if (drag_rect == null) return;
1897 
1898  d3.event.sourceEvent.preventDefault();
1899 
1900  complete_drag();
1901  });
1902 
1903  if (!('only_resize' in callback)) {
1904  this.draw_g.style("cursor", "move").call(drag_move);
1905  }
1906 
1907  resize_corner1.call(drag_resize);
1908  resize_corner2.call(drag_resize);
1909  }
1910 
1911  JSROOT.TObjectPainter.prototype.startTouchMenu = function(kind) {
1912  // method to let activate context menu via touch handler
1913 
1914  var arr = d3.touches(this.svg_frame().node());
1915  if (arr.length != 1) return;
1916 
1917  if (!kind || (kind=="")) kind = "main";
1918  var fld = "touch_" + kind;
1919 
1920  d3.event.preventDefault();
1921  d3.event.stopPropagation();
1922 
1923  this[fld] = { dt: new Date(), pos: arr[0] };
1924 
1925  this.svg_frame().on("touchcancel", this.endTouchMenu.bind(this, kind))
1926  .on("touchend", this.endTouchMenu.bind(this, kind));
1927  }
1928 
1929  JSROOT.TObjectPainter.prototype.endTouchMenu = function(kind) {
1930  var fld = "touch_" + kind;
1931 
1932  if (! (fld in this)) return;
1933 
1934  d3.event.preventDefault();
1935  d3.event.stopPropagation();
1936 
1937  var diff = new Date().getTime() - this[fld].dt.getTime();
1938 
1939  this.svg_frame().on("touchcancel", null)
1940  .on("touchend", null);
1941 
1942  if (diff>500) {
1943  var rect = this.svg_frame().node().getBoundingClientRect();
1944  this.ShowContextMenu(kind, { clientX: rect.left + this[fld].pos[0],
1945  clientY: rect.top + this[fld].pos[1] } );
1946  }
1947 
1948  delete this[fld];
1949  }
1950 
1951  JSROOT.TObjectPainter.prototype.AddColorMenuEntry = function(menu, name, value, set_func, fill_kind) {
1952  if (value === undefined) return;
1953  menu.add("sub:"+name, function() {
1954  // todo - use jqury dialog here
1955  var useid = (typeof value !== 'string');
1956  var col = prompt("Enter color " + (useid ? "(only id number)" : "(name or id)"), value);
1957  if (col == null) return;
1958  var id = parseInt(col);
1959  if (!isNaN(id) && (JSROOT.Painter.root_colors[id] !== undefined)) {
1960  col = JSROOT.Painter.root_colors[id];
1961  } else {
1962  if (useid) return;
1963  }
1964  set_func.bind(this)(useid ? id : col);
1965  });
1966  var useid = (typeof value !== 'string');
1967  for (var n=-1;n<11;++n) {
1968  if ((n<0) && useid) continue;
1969  if ((n==10) && (fill_kind!==1)) continue;
1970  var col = (n<0) ? 'none' : JSROOT.Painter.root_colors[n];
1971  if ((n==0) && (fill_kind==1)) col = 'none';
1972  var svg = "<svg width='100' height='18' style='margin:0px;background-color:" + col + "'><text x='4' y='12' style='font-size:12px' fill='" + (n==1 ? "white" : "black") + "'>"+col+"</text></svg>";
1973  menu.addchk((value == (useid ? n : col)), svg, (useid ? n : col), set_func);
1974  }
1975  menu.add("endsub:");
1976  }
1977 
1978  JSROOT.TObjectPainter.prototype.AddSizeMenuEntry = function(menu, name, min, max, step, value, set_func) {
1979  if (value === undefined) return;
1980 
1981  menu.add("sub:"+name, function() {
1982  // todo - use jqury dialog here
1983  var entry = value.toFixed(4);
1984  if (step>=0.1) entry = value.toFixed(2);
1985  if (step>=1) entry = value.toFixed(0);
1986  var val = prompt("Enter value of " + name, entry);
1987  if (val==null) return;
1988  var val = parseFloat(val);
1989  if (!isNaN(val)) set_func.bind(this)((step>=1) ? Math.round(val) : val);
1990  });
1991  for (var val=min;val<=max;val+=step) {
1992  var entry = val.toFixed(2);
1993  if (step>=0.1) entry = val.toFixed(1);
1994  if (step>=1) entry = val.toFixed(0);
1995  menu.addchk((Math.abs(value - val) < step/2), entry, val, set_func);
1996  }
1997  menu.add("endsub:");
1998  }
1999 
2000 
2001  JSROOT.TObjectPainter.prototype.FillAttContextMenu = function(menu, preffix) {
2002  // this method used to fill entries for different attributes of the object
2003  // like TAttFill, TAttLine, ....
2004  // all menu call-backs need to be rebind, while menu can be used from other painter
2005 
2006  if (!preffix) preffix = "";
2007 
2008  if (this.lineatt && this.lineatt.used) {
2009  menu.add("sub:"+preffix+"Line att");
2010  this.AddSizeMenuEntry(menu, "width", 1, 10, 1, this.lineatt.width,
2011  function(arg) { this.lineatt.width = parseInt(arg); this.Redraw(); }.bind(this));
2012  this.AddColorMenuEntry(menu, "color", this.lineatt.color,
2013  function(arg) { this.lineatt.color = arg; this.Redraw(); }.bind(this));
2014  menu.add("sub:style", function() {
2015  var id = prompt("Enter line style id (1-solid)", 1);
2016  if (id == null) return;
2017  id = parseInt(id);
2018  if (isNaN(id) || (JSROOT.Painter.root_line_styles[id] === undefined)) return;
2019  this.lineatt.dash = JSROOT.Painter.root_line_styles[id];
2020  this.Redraw();
2021  }.bind(this));
2022  for (var n=1;n<11;++n) {
2023  var style = JSROOT.Painter.root_line_styles[n];
2024 
2025  var svg = "<svg width='100' height='18'><text x='1' y='12' style='font-size:12px'>" + n + "</text><line x1='30' y1='8' x2='100' y2='8' stroke='black' stroke-width='3' stroke-dasharray='" + style + "'></line></svg>";
2026 
2027  menu.addchk((this.lineatt.dash==style), svg, style, function(arg) { this.lineatt.dash = arg; this.Redraw(); }.bind(this));
2028  }
2029  menu.add("endsub:");
2030  menu.add("endsub:");
2031 
2032  if (('excl_side' in this.lineatt) && (this.lineatt.excl_side!==0)) {
2033  menu.add("sub:Exclusion");
2034  menu.add("sub:side");
2035  for (var side=-1;side<=1;++side)
2036  menu.addchk((this.lineatt.excl_side==side), side, side, function(arg) {
2037  this.lineatt.excl_side = parseInt(arg);
2038  if ((this.lineatt.excl_width===0) && (this.lineatt.excl_side!=0)) this.lineatt.excl_width = 20;
2039  this.Redraw();
2040  }.bind(this));
2041  menu.add("endsub:");
2042 
2043  this.AddSizeMenuEntry(menu, "width", 10, 100, 10, this.lineatt.excl_width,
2044  function(arg) { this.lineatt.excl_width = parseInt(arg); this.Redraw(); }.bind(this));
2045 
2046  menu.add("endsub:");
2047  }
2048  }
2049 
2050  if (this.fillatt && this.fillatt.used) {
2051  menu.add("sub:"+preffix+"Fill att");
2052  this.AddColorMenuEntry(menu, "color", this.fillatt.colorindx,
2053  function(arg) { this.fillatt.Change(parseInt(arg), undefined, this.svg_canvas()); this.Redraw(); }.bind(this), this.fillatt.kind);
2054  menu.add("sub:style", function() {
2055  var id = prompt("Enter fill style id (1001-solid, 3000..3010)", this.fillatt.pattern);
2056  if (id == null) return;
2057  id = parseInt(id);
2058  if (isNaN(id)) return;
2059  this.fillatt.Change(undefined, id, this.svg_canvas());
2060  this.Redraw();
2061  }.bind(this));
2062 
2063  var supported = [1, 1001, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3010, 3021, 3022];
2064 
2065  var clone = JSROOT.clone(this.fillatt);
2066  if (clone.colorindx<=0) clone.colorindx = 1;
2067 
2068  for (var n=0; n<supported.length; ++n) {
2069 
2070  clone.Change(undefined, supported[n], this.svg_canvas());
2071 
2072  var svg = "<svg width='100' height='18'><text x='1' y='12' style='font-size:12px'>" + supported[n].toString() + "</text><rect x='40' y='0' width='60' height='18' stroke='none' fill='" + clone.color + "'></rect></svg>";
2073 
2074  menu.addchk(this.fillatt.pattern == supported[n], svg, supported[n], function(arg) {
2075  this.fillatt.Change(undefined, parseInt(arg), this.svg_canvas());
2076  this.Redraw();
2077  }.bind(this));
2078  }
2079  menu.add("endsub:");
2080  menu.add("endsub:");
2081  }
2082 
2083  if (this.markeratt && this.markeratt.used) {
2084  menu.add("sub:"+preffix+"Marker att");
2085  this.AddColorMenuEntry(menu, "color", this.markeratt.color,
2086  function(arg) { this.markeratt.Change(arg); this.Redraw(); }.bind(this));
2087  this.AddSizeMenuEntry(menu, "size", 0.5, 6, 0.5, this.markeratt.size,
2088  function(arg) { this.markeratt.Change(undefined, undefined, parseFloat(arg)); this.Redraw(); }.bind(this));
2089 
2090  menu.add("sub:style");
2091  var supported = [1,2,3,4,5,6,7,8,21,22,23,24,25,26,27,28,29,30,31,32,33,34];
2092 
2093  var clone = JSROOT.clone(this.markeratt);
2094  for (var n=0; n<supported.length; ++n) {
2095  clone.Change(undefined, supported[n], 1.7);
2096  clone.reset_pos();
2097  var svg = "<svg width='60' height='18'><text x='1' y='12' style='font-size:12px'>" + supported[n].toString() + "</text><path stroke='black' fill='" + (clone.fill ? "black" : "none") + "' d='" + clone.create(40,8) + "'></path></svg>";
2098 
2099  menu.addchk(this.markeratt.style == supported[n], svg, supported[n],
2100  function(arg) { this.markeratt.Change(undefined, parseInt(arg)); this.Redraw(); }.bind(this));
2101  }
2102  menu.add("endsub:");
2103  menu.add("endsub:");
2104  }
2105  }
2106 
2107  JSROOT.TObjectPainter.prototype.TextAttContextMenu = function(menu, prefix) {
2108  // for the moment, text attributes accessed directly from objects
2109 
2110  var obj = this.GetObject();
2111  if (!obj || !('fTextColor' in obj)) return;
2112 
2113  menu.add("sub:" + (prefix ? prefix : "Text"));
2114  this.AddColorMenuEntry(menu, "color", obj.fTextColor,
2115  function(arg) { this.GetObject().fTextColor = parseInt(arg); this.Redraw(); }.bind(this));
2116 
2117  var align = [11, 12, 13, 21, 22, 23, 31, 32, 33],
2118  hnames = ['left', 'centered' , 'right'],
2119  vnames = ['bottom', 'centered', 'top'];
2120 
2121  menu.add("sub:align");
2122  for (var n=0; n<align.length; ++n) {
2123  menu.addchk(align[n] == obj.fTextAlign,
2124  align[n], align[n],
2125  // align[n].toString() + "_h:" + hnames[Math.floor(align[n]/10) - 1] + "_v:" + vnames[align[n]%10-1], align[n],
2126  function(arg) { this.GetObject().fTextAlign = parseInt(arg); this.Redraw(); }.bind(this));
2127  }
2128  menu.add("endsub:");
2129 
2130  menu.add("sub:font");
2131  for (var n=1; n<16; ++n) {
2132  menu.addchk(n == Math.floor(obj.fTextFont/10), n, n,
2133  function(arg) { this.GetObject().fTextFont = parseInt(arg)*10+2; this.Redraw(); }.bind(this));
2134  }
2135  menu.add("endsub:");
2136 
2137  menu.add("endsub:");
2138  }
2139 
2140 
2141  JSROOT.TObjectPainter.prototype.FillContextMenu = function(menu) {
2142 
2143  var title = this.GetTipName();
2144  if (this.GetObject() && ('_typename' in this.GetObject()))
2145  title = this.GetObject()._typename + "::" + title;
2146 
2147  menu.add("header:"+ title);
2148 
2149  this.FillAttContextMenu(menu);
2150 
2151  return menu.size() > 0;
2152  }
2153 
2154 
2155  JSROOT.TObjectPainter.prototype.FindInPrimitives = function(objname) {
2156  // try to find object by name in list of pad primitives
2157  // used to find title drawing
2158 
2159  var painter = this.pad_painter(true);
2160  if ((painter === null) || (painter.pad === null)) return null;
2161 
2162  if (painter.pad.fPrimitives !== null)
2163  for (var n=0;n<painter.pad.fPrimitives.arr.length;++n) {
2164  var prim = painter.pad.fPrimitives.arr[n];
2165  if (('fName' in prim) && (prim.fName === objname)) return prim;
2166  }
2167 
2168  return null;
2169  }
2170 
2171  JSROOT.TObjectPainter.prototype.FindPainterFor = function(selobj,selname) {
2172  // try to find painter for sepcified object
2173  // can be used to find painter for some special objects, registered as
2174  // histogram functions
2175 
2176  var painter = this.pad_painter(true);
2177  var painters = (painter === null) ? null : painter.painters;
2178  if (painters === null) return null;
2179 
2180  for (var n = 0; n < painters.length; ++n) {
2181  var pobj = painters[n].GetObject();
2182  if (pobj===null) continue;
2183 
2184  if (selobj && (pobj === selobj)) return painters[n];
2185 
2186  if (selname && ('fName' in pobj) && (pobj.fName == selname)) return painters[n];
2187  }
2188 
2189  return null;
2190  }
2191 
2192  JSROOT.TObjectPainter.prototype.ConfigureUserTooltipCallback = function(call_back, user_timeout) {
2193  // hook for the users to get tooltip information when mouse cursor moves over frame area
2194  // call_back function will be called every time when new data is selected
2195  // when mouse leave frame area, call_back(null) will be called
2196 
2197  if ((call_back === undefined) || (typeof call_back !== 'function')) {
2198  delete this.UserTooltipCallback;
2199  return;
2200  }
2201 
2202  if (user_timeout===undefined) user_timeout = 500;
2203 
2204  this.UserTooltipCallback = call_back;
2205  this.UserTooltipTimeout = user_timeout;
2206  }
2207 
2208  JSROOT.TObjectPainter.prototype.IsUserTooltipCallback = function() {
2209  return typeof this.UserTooltipCallback == 'function';
2210  }
2211 
2212  JSROOT.TObjectPainter.prototype.ProvideUserTooltip = function(data) {
2213 
2214  if (!this.IsUserTooltipCallback()) return;
2215 
2216  if (this.UserTooltipTimeout <= 0)
2217  return this.UserTooltipCallback(data);
2218 
2219  if (typeof this.UserTooltipTHandle != 'undefined') {
2220  clearTimeout(this.UserTooltipTHandle);
2221  delete this.UserTooltipTHandle;
2222  }
2223 
2224  if (data==null)
2225  return this.UserTooltipCallback(data);
2226 
2227  this.UserTooltipTHandle = setTimeout(function(d) {
2228  // only after timeout user function will be called
2229  delete this.UserTooltipTHandle;
2230  this.UserTooltipCallback(d);
2231  }.bind(this, data), this.UserTooltipTimeout);
2232  }
2233 
2234  JSROOT.TObjectPainter.prototype.Redraw = function() {
2235  // basic method, should be reimplemented in all derived objects
2236  // for the case when drawing should be repeated, probably with different
2237  // options
2238  }
2239 
2240  JSROOT.TObjectPainter.prototype.StartTextDrawing = function(font_face, font_size, draw_g) {
2241  // we need to preserve font to be able rescle at the end
2242 
2243  if (!draw_g) draw_g = this.draw_g;
2244 
2245  var font = JSROOT.Painter.getFontDetails(font_face, font_size);
2246 
2247  draw_g.call(font.func);
2248 
2249  draw_g.property('text_font', font);
2250  draw_g.property('mathjax_use', false);
2251  draw_g.property('text_factor', 0.);
2252  draw_g.property('max_text_width', 0); // keep maximal text width, use it later
2253  }
2254 
2255  JSROOT.TObjectPainter.prototype.TextScaleFactor = function(value, draw_g) {
2256  // function used to remember maximal text scaling factor
2257  if (!draw_g) draw_g = this.draw_g;
2258  if (value && (value > draw_g.property('text_factor'))) draw_g.property('text_factor', value);
2259  }
2260 
2261  JSROOT.TObjectPainter.prototype.GetBoundarySizes = function(elem) {
2262  // getBBox does not work in mozilla when object is not displayed or not visisble :(
2263  // getBoundingClientRect() returns wrong sizes for MathJax
2264  // are there good solution?
2265 
2266  if (elem===null) { console.warn('empty node in GetBoundarySizes'); return { width:0, height:0 }; }
2267  var box = elem.getBoundingClientRect(); // works always, but returns sometimes wrong results
2268  if (parseInt(box.width) > 0) box = elem.getBBox(); // check that elements visible, request precise value
2269  var res = { width : parseInt(box.width), height : parseInt(box.height) };
2270  if ('left' in box) { res.x = parseInt(box.left); res.y = parseInt(box.right); } else
2271  if ('x' in box) { res.x = parseInt(box.x); res.y = parseInt(box.y); }
2272  return res;
2273  }
2274 
2275  JSROOT.TObjectPainter.prototype.FinishTextDrawing = function(draw_g, call_ready) {
2276  if (!draw_g) draw_g = this.draw_g;
2277  var pthis = this;
2278 
2279  var svgs = null;
2280 
2281  if (draw_g.property('mathjax_use')) {
2282  draw_g.property('mathjax_use', false);
2283 
2284  var missing = false;
2285  svgs = draw_g.selectAll(".math_svg");
2286 
2287  svgs.each(function() {
2288  var fo_g = d3.select(this);
2289  if (fo_g.node().parentNode !== draw_g.node()) return;
2290  var entry = fo_g.property('_element');
2291  if (d3.select(entry).select("svg").empty()) missing = true;
2292  });
2293 
2294  // is any svg missing we should wait until drawing is really finished
2295  if (missing) {
2296  JSROOT.AssertPrerequisites('mathjax', function() {
2297  if (typeof MathJax == 'object')
2298  MathJax.Hub.Queue(["FinishTextDrawing", pthis, draw_g, call_ready]);
2299  });
2300  return null;
2301  }
2302  }
2303 
2304  if (svgs==null) svgs = draw_g.selectAll(".math_svg");
2305 
2306  var painter = this, svg_factor = 0.;
2307 
2308  // adjust font size (if there are normal text)
2309  var f = draw_g.property('text_factor');
2310  if ((f>0) && ((f<0.9) || (f>1.))) {
2311  var font = draw_g.property('text_font');
2312  font.size = Math.floor(font.size/f);
2313  draw_g.call(font.func);
2314  }
2315 
2316  // first remove dummy divs and check scaling coefficient
2317  svgs.each(function() {
2318  var fo_g = d3.select(this);
2319  if (fo_g.node().parentNode !== draw_g.node()) return;
2320  var entry = fo_g.property('_element'),
2321  rotate = fo_g.property('_rotate');
2322 
2323  fo_g.property('_element', null);
2324 
2325  var vvv = d3.select(entry).select("svg");
2326  if (vvv.empty()) {
2327  JSROOT.console('MathJax SVG ouptut error');
2328  return;
2329  }
2330 
2331  vvv.remove();
2332  document.body.removeChild(entry);
2333 
2334  fo_g.append(function() { return vvv.node(); });
2335 
2336  if (fo_g.property('_scale')) {
2337  var box = painter.GetBoundarySizes(fo_g.node());
2338  svg_factor = Math.max(svg_factor, 1.05*box.width / fo_g.property('_width'),
2339  1.05*box.height / fo_g.property('_height'));
2340  }
2341  });
2342 
2343  svgs.each(function() {
2344  var fo_g = d3.select(this);
2345  // only direct parent
2346  if (fo_g.node().parentNode !== draw_g.node()) return;
2347 
2348  if (svg_factor > 0.) {
2349  var m = fo_g.select("svg"); // MathJax svg
2350  var mw = m.attr("width"), mh = m.attr("height");
2351  if ((typeof mw == 'string') && (typeof mh == 'string') && (mw.indexOf("ex") > 0) && (mh.indexOf("ex") > 0)) {
2352  mw = parseFloat(mw.substr(0,mw.length-2));
2353  mh = parseFloat(mh.substr(0,mh.length-2));
2354  if ((mw>0) && (mh>0)) {
2355  m.attr("width", (mw/svg_factor).toFixed(2)+"ex");
2356  m.attr("height", (mh/svg_factor).toFixed(2)+"ex");
2357  }
2358  } else {
2359  JSROOT.console('Fail to downscale MathJax output');
2360  }
2361  }
2362 
2363  var box = painter.GetBoundarySizes(fo_g.node()), // sizes before rotation
2364  align = fo_g.property('_align'),
2365  rotate = fo_g.property('_rotate'),
2366  fo_w = fo_g.property('_width'),
2367  fo_h = fo_g.property('_height'),
2368  tr = { x: fo_g.property('_x'), y: fo_g.property('_y') };
2369 
2370  var sign = { x:1, y:1 }, nx = "x", ny = "y";
2371  if (rotate == 180) { sign.x = sign.y = -1; } else
2372  if ((rotate == 270) || (rotate == 90)) {
2373  sign.x = (rotate===270) ? -1 : 1;
2374  sign.y = -sign.x;
2375  nx = "y"; ny = "x"; // replace names to which align applied
2376  }
2377 
2378  if (!fo_g.property('_scale')) fo_w = fo_h = 0;
2379 
2380  if (align[0] == 'middle') tr[nx] += sign.x*(fo_w - box.width)/2; else
2381  if (align[0] == 'end') tr[nx] += sign.x*(fo_w - box.width);
2382 
2383  if (align[1] == 'middle') tr[ny] += sign.y*(fo_h - box.height)/2; else
2384  if (align[1] == 'bottom') tr[ny] += sign.y*(fo_h - box.height);
2385 
2386  var trans = "translate("+tr.x+","+tr.y+")";
2387  if (rotate!==0) trans += " rotate("+rotate+",0,0)";
2388 
2389  fo_g.attr('transform', trans).attr('visibility', null);
2390  });
2391 
2392  // now hidden text after rescaling can be shown
2393  draw_g.selectAll('.hidden_text').attr('opacity', '1').classed('hidden_text',false);
2394 
2395  // if specified, call ready function
2396  JSROOT.CallBack(call_ready);
2397 
2398  return draw_g.property('max_text_width');
2399  }
2400 
2401  JSROOT.TObjectPainter.prototype.DrawText = function(align_arg, x, y, w, h, label, tcolor, latex_kind, draw_g) {
2402 
2403  if (!draw_g) draw_g = this.draw_g;
2404  var align;
2405 
2406  if (typeof align_arg == 'string') {
2407  align = align_arg.split(";");
2408  if (align.length==1) align.push('middle');
2409  } else {
2410  align = ['start', 'middle'];
2411  if ((align_arg / 10) >= 3) align[0] = 'end'; else
2412  if ((align_arg / 10) >= 2) align[0] = 'middle';
2413  if ((align_arg % 10) == 0) align[1] = 'bottom'; else
2414  if ((align_arg % 10) == 1) align[1] = 'bottom'; else
2415  if ((align_arg % 10) == 3) align[1] = 'top';
2416  }
2417 
2418  var scale = (w>0) && (h>0);
2419 
2420  if (latex_kind==null) latex_kind = 1;
2421  if (latex_kind<2)
2422  if (!JSROOT.Painter.isAnyLatex(label)) latex_kind = 0;
2423 
2424  var use_normal_text = ((JSROOT.gStyle.MathJax<1) && (latex_kind!==2)) || (latex_kind<1);
2425 
2426  // only Firefox can correctly rotate incapsulated SVG, produced by MathJax
2427  // if (!use_normal_text && (h<0) && !JSROOT.browser.isFirefox) use_normal_text = true;
2428 
2429  if (use_normal_text) {
2430  if (latex_kind>0) label = JSROOT.Painter.translateLaTeX(label);
2431 
2432  var pos_x = x.toFixed(1), pos_y = y.toFixed(1), pos_dy = null, middleline = false;
2433 
2434  if (w>0) {
2435  // adjust x position when scale into specified rectangle
2436  if (align[0]=="middle") pos_x = (x+w*0.5).toFixed(1); else
2437  if (align[0]=="end") pos_x = (x+w).toFixed(1);
2438  }
2439 
2440  if (h>0) {
2441  if (align[1] == 'bottom') pos_y = (y + h).toFixed(1); else
2442  if (align[1] == 'top') pos_dy = ".8em"; else {
2443  pos_y = (y + h/2).toFixed(1);
2444  if (JSROOT.browser.isIE) pos_dy = ".4em"; else middleline = true;
2445  }
2446  } else {
2447  if (align[1] == 'top') pos_dy = ".8em"; else
2448  if (align[1] == 'middle') {
2449  if (JSROOT.browser.isIE) pos_dy = ".4em"; else middleline = true;
2450  }
2451  }
2452 
2453  // use translate and then rotate to avoid complex sign calculations
2454  var trans = "translate("+pos_x+","+pos_y+")";
2455  if (!scale && (h<0)) trans += " rotate("+(-h)+",0,0)";
2456 
2457  var txt = draw_g.append("text")
2458  .attr("text-anchor", align[0])
2459  .attr("x", 0)
2460  .attr("y", 0)
2461  .attr("fill", tcolor ? tcolor : null)
2462  .attr("transform", trans)
2463  .text(label)
2464  if (pos_dy!=null) txt.attr("dy", pos_dy);
2465  if (middleline) txt.attr("dominant-baseline", "middle");
2466 
2467  var box = this.GetBoundarySizes(txt.node());
2468 
2469  if (scale) txt.classed('hidden_text',true).attr('opacity','0'); // hide rescale elements
2470 
2471  if (box.width > draw_g.property('max_text_width')) draw_g.property('max_text_width', box.width);
2472  if ((w>0) && scale) this.TextScaleFactor(1.05*box.width / w, draw_g);
2473  if ((h>0) && scale) this.TextScaleFactor(1.*box.height / h, draw_g);
2474 
2475  return box.width;
2476  }
2477 
2478  w = Math.round(w); h = Math.round(h);
2479  x = Math.round(x); y = Math.round(y);
2480 
2481  var rotate = 0;
2482 
2483  if (!scale && h<0) { rotate = Math.abs(h); h = 0; }
2484 
2485  var fo_g = draw_g.append("svg:g")
2486  .attr('class', 'math_svg')
2487  .attr('visibility','hidden')
2488  .property('_x',x) // used for translation later
2489  .property('_y',y)
2490  .property('_width',w) // used to check scaling
2491  .property('_height',h)
2492  .property('_scale', scale)
2493  .property('_rotate', rotate)
2494  .property('_align', align);
2495 
2496  var element = document.createElement("div");
2497  d3.select(element).style("visibility", "hidden")
2498  .style("overflow", "hidden")
2499  .style("position", "absolute")
2500  .html(JSROOT.Painter.translateMath(label, latex_kind, tcolor));
2501  document.body.appendChild(element);
2502 
2503  draw_g.property('mathjax_use', true); // one need to know that mathjax is used
2504  fo_g.property('_element', element);
2505 
2506  JSROOT.AssertPrerequisites('mathjax', function() {
2507  if (typeof MathJax == 'object')
2508  MathJax.Hub.Queue(["Typeset", MathJax.Hub, element]);
2509  });
2510 
2511  return 0;
2512  }
2513 
2514  // ===========================================================
2515 
2516  JSROOT.TFramePainter = function(tframe) {
2517  JSROOT.TObjectPainter.call(this, tframe);
2518  this.tooltip_enabled = true;
2519  }
2520 
2521  JSROOT.TFramePainter.prototype = Object.create(JSROOT.TObjectPainter.prototype);
2522 
2523  JSROOT.TFramePainter.prototype.Shrink = function(shrink_left, shrink_right) {
2524  this.fX1NDC += shrink_left;
2525  this.fX2NDC -= shrink_right;
2526  }
2527 
2528  JSROOT.TFramePainter.prototype.Redraw = function() {
2529 
2530  var width = this.pad_width(),
2531  height = this.pad_height(),
2532  tframe = this.GetObject(),
2533  root_pad = this.root_pad(),
2534  framecolor = this.createAttFill(null, 1001, 0),
2535  lineatt = JSROOT.Painter.createAttLine('black'),
2536  bordermode = 0, bordersize = 0,
2537  has_ndc = ('fX1NDC' in this);
2538 
2539  if (!has_ndc) {
2540  if (root_pad === null)
2541  JSROOT.extend(this, JSROOT.gStyle.FrameNDC);
2542  else
2543  JSROOT.extend(this, {
2544  fX1NDC: root_pad.fLeftMargin,
2545  fX2NDC: 1 - root_pad.fRightMargin,
2546  fY1NDC: root_pad.fTopMargin,
2547  fY2NDC: 1 - root_pad.fBottomMargin
2548  });
2549  }
2550 
2551  if (tframe !== null) {
2552  bordermode = tframe.fBorderMode;
2553  bordersize = tframe.fBorderSize;
2554  lineatt = JSROOT.Painter.createAttLine(tframe);
2555  framecolor = this.createAttFill(tframe);
2556  if (!has_ndc && (root_pad !== null)) {
2557  var xspan = width / Math.abs(root_pad.fX2 - root_pad.fX1),
2558  yspan = height / Math.abs(root_pad.fY2 - root_pad.fY1),
2559  px1 = (tframe.fX1 - root_pad.fX1) * xspan,
2560  py1 = (tframe.fY1 - root_pad.fY1) * yspan,
2561  px2 = (tframe.fX2 - root_pad.fX1) * xspan,
2562  py2 = (tframe.fY2 - root_pad.fY1) * yspan,
2563  pxl, pxt, pyl, pyt;
2564  if (px1 < px2) { pxl = px1; pxt = px2; }
2565  else { pxl = px2; pxt = px1; }
2566  if (py1 < py2) { pyl = py1; pyt = py2; }
2567  else { pyl = py2; pyt = py1; }
2568  this.fX1NDC = pxl / width;
2569  this.fY1NDC = pyl / height;
2570  this.fX2NDC = pxt / width;
2571  this.fY2NDC = pyt / height;
2572  }
2573  } else {
2574  if (root_pad)
2575  framecolor = this.createAttFill(null, root_pad.fFrameFillStyle, root_pad.fFrameFillColor);
2576  }
2577 
2578  // force white color for the frame
2579  if (framecolor.color == 'none') framecolor.color = 'white';
2580 
2581  if (this.fillatt === undefined) this.fillatt = framecolor;
2582  if (this.lineatt === undefined) this.lineatt = lineatt;
2583 
2584  var lm = Math.round(width * this.fX1NDC),
2585  w = Math.round(width * (this.fX2NDC - this.fX1NDC)),
2586  tm = Math.round(height * (1 - this.fY2NDC)),
2587  h = Math.round(height * (this.fY2NDC - this.fY1NDC));
2588 
2589 
2590  // this is svg:g object - container for every other items belonging to frame
2591  this.draw_g = this.svg_frame();
2592  if (this.draw_g.empty())
2593  return console.error('did not found frame layer');
2594 
2595  var top_rect = this.draw_g.select("rect"),
2596  main_svg = this.draw_g.select(".main_layer");
2597 
2598  if (main_svg.empty()) {
2599  this.draw_g.append("svg:title").text("");
2600 
2601  top_rect = this.draw_g.append("svg:rect");
2602 
2603  // append for the moment three layers - for drawing and axis
2604  this.draw_g.append('svg:g').attr('class','grid_layer');
2605 
2606  main_svg = this.draw_g.append('svg:svg')
2607  .attr('class','main_layer')
2608  .attr("x", 0)
2609  .attr("y", 0)
2610  .attr('overflow', 'hidden');
2611 
2612  this.draw_g.append('svg:g').attr('class','axis_layer');
2613  this.draw_g.append('svg:g').attr('class','upper_layer');
2614  }
2615 
2616  // simple way to access painter via frame container
2617  this.draw_g.property('frame_painter', this);
2618 
2619  this.draw_g.attr("x", lm)
2620  .attr("y", tm)
2621  .attr("width", w)
2622  .attr("height", h)
2623  .property('draw_x', lm)
2624  .property('draw_y', tm)
2625  .property('draw_width', w)
2626  .property('draw_height', h)
2627  .attr("transform", "translate(" + lm + "," + tm + ")");
2628 
2629  top_rect.attr("x", 0)
2630  .attr("y", 0)
2631  .attr("width", w)
2632  .attr("height", h)
2633  .call(this.fillatt.func)
2634  .call(this.lineatt.func);
2635 
2636  main_svg.attr("width", w)
2637  .attr("height", h)
2638  .attr("viewBox", "0 0 " + w + " " + h);
2639 
2640  this.AddDrag({ obj: this, only_resize: true, redraw: this.RedrawPad.bind(this) });
2641 
2642  var tooltip_rect = this.draw_g.select(".interactive_rect");
2643 
2644  if (JSROOT.gStyle.Tooltip === 0)
2645  return tooltip_rect.remove();
2646 
2647  var painter = this;
2648 
2649  function MouseMoveEvent() {
2650  var pnt = d3.mouse(tooltip_rect.node());
2651  painter.ProcessTooltipEvent({ x: pnt[0], y: pnt[1], touch: false });
2652  }
2653 
2654  function MouseCloseEvent() {
2655  painter.ProcessTooltipEvent(null);
2656  }
2657 
2658  function TouchMoveEvent() {
2659  var pnt = d3.touches(tooltip_rect.node());
2660  if (!pnt || pnt.length !== 1) return painter.ProcessTooltipEvent(null);
2661  painter.ProcessTooltipEvent({ x: pnt[0][0], y: pnt[0][1], touch: true });
2662  }
2663 
2664  function TouchCloseEvent() {
2665  painter.ProcessTooltipEvent(null);
2666  }
2667 
2668  if (tooltip_rect.empty()) {
2669  tooltip_rect =
2670  this.draw_g
2671  .append("rect")
2672  .attr("class","interactive_rect")
2673  .style("opacity","0")
2674  .style("fill","none")
2675  .style("pointer-events", "visibleFill")
2676  .on('mouseenter', MouseMoveEvent)
2677  .on('mousemove', MouseMoveEvent)
2678  .on('mouseleave', MouseCloseEvent);
2679 
2680  if (JSROOT.touches)
2681  tooltip_rect.on("touchstart", TouchMoveEvent)
2682  .on("touchmove", TouchMoveEvent)
2683  .on("touchend", TouchCloseEvent)
2684  .on("touchcancel", TouchCloseEvent);
2685  }
2686 
2687  tooltip_rect.attr("x", 0)
2688  .attr("y", 0)
2689  .attr("width", w)
2690  .attr("height", h);
2691 
2692  var hintsg = this.svg_layer("stat_layer").select(".objects_hints");
2693  // if tooltips were visible before, try to reconstruct them after short timeout
2694  if (!hintsg.empty() && (JSROOT.gStyle.Tooltip > 0))
2695  setTimeout(this.ProcessTooltipEvent.bind(this, hintsg.property('last_point')), 10);
2696  }
2697 
2698 
2699  JSROOT.TFramePainter.prototype.FillContextMenu = function(menu) {
2700  // fill context menu for the frame
2701  // it could be appended to the histogram menus
2702 
2703  var main = this.main_painter(), alone = menu.size()==0;
2704 
2705  if (alone)
2706  menu.add("header:Frame");
2707  else
2708  menu.add("separator");
2709 
2710  if (main) {
2711  if (main.zoom_xmin !== main.zoom_xmax)
2712  menu.add("Unzoom X", main.Unzoom.bind(main,"x"));
2713  if (main.zoom_ymin !== main.zoom_ymax)
2714  menu.add("Unzoom Y", main.Unzoom.bind(main,"y"));
2715  if (main.zoom_zmin !== main.zoom_zmax)
2716  menu.add("Unzoom Z", main.Unzoom.bind(main,"z"));
2717  menu.add("Unzoom all", main.Unzoom.bind(main,"xyz"));
2718 
2719  if (main.options) {
2720  menu.addchk(main.options.Logx, "SetLogx", main.ToggleLog.bind(main,"x"));
2721 
2722  menu.addchk(main.options.Logy, "SetLogy", main.ToggleLog.bind(main,"y"));
2723 
2724  if (main.Dimension() == 2)
2725  menu.addchk(main.options.Logz, "SetLogz", main.ToggleLog.bind(main,"z"));
2726  }
2727  menu.add("separator");
2728  }
2729 
2730  menu.addchk(this.tooltip_enabled, "Show tooltips", function() {
2731  this.tooltip_enabled = !this.tooltip_enabled;
2732  }.bind(this));
2733  this.FillAttContextMenu(menu,alone ? "" : "Frame ");
2734  menu.add("separator");
2735  menu.add("Save as frame.png", function(arg) {
2736  var top = this.svg_frame();
2737  if (!top.empty())
2738  JSROOT.AssertPrerequisites("savepng", function() {
2739  saveSvgAsPng(top.node(), "frame.png");
2740  });
2741  }.bind(this));
2742 
2743  return true;
2744  }
2745 
2746 
2747  JSROOT.TFramePainter.prototype.IsTooltipShown = function() {
2748  // return true if tooltip is shown, use to prevent some other action
2749  if (JSROOT.gStyle.Tooltip < 1) return false;
2750  return ! (this.svg_layer("stat_layer").select(".objects_hints").empty());
2751  }
2752 
2753  JSROOT.TFramePainter.prototype.ProcessTooltipEvent = function(pnt, enabled) {
2754 
2755  if (enabled !== undefined) this.tooltip_enabled = enabled;
2756 
2757  if ((pnt === undefined) || (JSROOT.gStyle.Tooltip < 1) || !this.tooltip_enabled) pnt = null;
2758 
2759  var hints = [], nhints = 0, maxlen = 0, lastcolor1 = 0, usecolor1 = false,
2760  textheight = 11, hmargin = 3, wmargin = 3, hstep = 1.2,
2761  height = this.frame_height(),
2762  width = this.frame_width(),
2763  pp = this.pad_painter(true);
2764 
2765  // collect tooltips from pad painter - it has list of all drawn objects
2766  if (pp!==null) hints = pp.GetTooltips(pnt);
2767 
2768  if (pnt && pnt.touch) textheight = 15;
2769 
2770  hints.forEach(function(hint) {
2771  if (hint === null) return;
2772 
2773  nhints++;
2774 
2775  for (var l=0;l<hint.lines.length;++l)
2776  maxlen = Math.max(maxlen, hint.lines[l].length);
2777 
2778  hint.height = hint.lines.length*textheight*hstep + 2*hmargin - textheight*(hstep-1);
2779 
2780  if ((hint.color1!== undefined) && (hint.color1!=='none')) {
2781  if ((lastcolor1!==0) && (lastcolor1 !== hint.color1)) usecolor1 = true;
2782  lastcolor1 = hint.color1;
2783  }
2784  });
2785 
2786  var layer = this.svg_layer("stat_layer"),
2787  hintsg = layer.select(".objects_hints"); // group with all tooltips
2788 
2789  // end of closing tooltips
2790  if ((pnt === null) || (hints.length===0) || (maxlen===0)) {
2791  hintsg.remove();
2792  return;
2793  }
2794 
2795  // we need to set pointer-events=none for all elements while hints
2796  // placed in front of so-called interactive rect in frame, used to catch mouse events
2797 
2798  if (hintsg.empty())
2799  hintsg = layer.append("svg:g")
2800  .attr("class", "objects_hints")
2801  .style("pointer-events","none");
2802 
2803  // copy transform attributes from frame itself
2804  hintsg.attr("transform", this.draw_g.attr("transform"));
2805 
2806  hintsg.property("last_point", pnt);
2807 
2808  var viewmode = hintsg.property('viewmode');
2809  if (viewmode === undefined) viewmode = "";
2810 
2811  var actualw = 0, posx = pnt.x + 15;
2812 
2813  if (nhints > 1) {
2814  // if there are many hints, place them left or right
2815 
2816  var bleft = 0.5, bright = 0.5;
2817 
2818  if (viewmode=="left") bright = 0.7; else
2819  if (viewmode=="right") bleft = 0.3;
2820 
2821  if (pnt.x <= bleft*width) {
2822  viewmode = "left";
2823  posx = 20;
2824  } else
2825  if (pnt.x >= bright*width) {
2826  viewmode = "right";
2827  posx = width - 100;
2828  } else {
2829  posx = hintsg.property('startx');
2830  }
2831  } else {
2832  viewmode = "single";
2833  }
2834 
2835  if (viewmode !== hintsg.property('viewmode')) {
2836  hintsg.property('viewmode', viewmode);
2837  hintsg.selectAll("*").remove();
2838  }
2839 
2840  var font = JSROOT.Painter.getFontDetails(160, textheight);
2841 
2842  var curry = 10;
2843 
2844  for (var n=0; n < hints.length; ++n) {
2845  var hint = hints[n];
2846  var group = hintsg.select(".painter_hint_"+n);
2847  if (hint===null) {
2848  group.remove();
2849  continue;
2850  }
2851 
2852  var was_empty = group.empty();
2853 
2854  if (was_empty)
2855  group = hintsg.append("svg:svg")
2856  .attr("class", "painter_hint_"+n)
2857  .style('overflow','hidden')
2858  .attr("opacity","0")
2859  .style("pointer-events","none");
2860 
2861  if (viewmode == "single")
2862  curry = pnt.touch ? pnt.y - hints[n].height - 5 : pnt.y + 15;
2863 
2864  group.attr("x", posx)
2865  .attr("y", curry);
2866 
2867  curry += hints[n].height + 5;
2868 
2869  if (!was_empty)
2870  group.selectAll("*").remove();
2871 
2872  group.attr("width", 100)
2873  .attr("height", hint.height);
2874 
2875  var r = group.append("rect")
2876  .attr("x",0)
2877  .attr("y",0)
2878  .attr("width", 100)
2879  .attr("height", hint.height)
2880  .attr("fill","lightgrey")
2881  .style("pointer-events","none");
2882 
2883  if (nhints > 1) {
2884  var col = usecolor1 ? hint.color1 : hint.color2;
2885  if ((col !== undefined) && (col!=='none'))
2886  r.attr("stroke", col).attr("stroke-width", hint.exact ? 3 : 1);
2887  }
2888 
2889  if (hint.lines != null) {
2890  for (var l=0;l<hint.lines.length;l++)
2891  if (hint.lines[l]!==null) {
2892  var txt = group.append("svg:text")
2893  .attr("text-anchor", "start")
2894  .attr("x", wmargin)
2895  .attr("y", hmargin + l*textheight*hstep)
2896  .attr("dy", ".8em")
2897  .attr("fill","black")
2898  .style("pointer-events","none")
2899  .call(font.func)
2900  .text(hint.lines[l]);
2901 
2902  var box = this.GetBoundarySizes(txt.node());
2903 
2904  actualw = Math.max(actualw, box.width);
2905  }
2906  }
2907 
2908  function translateFn() {
2909  // We only use 'd', but list d,i,a as params just to show can have them as params.
2910  // Code only really uses d and t.
2911  return function(d, i, a) {
2912  return function(t) {
2913  return t < 0.8 ? "0" : (t-0.8)*5;
2914  };
2915  };
2916  }
2917 
2918  if (was_empty)
2919  group.transition().duration(500).attrTween("opacity", translateFn());
2920  }
2921 
2922  actualw += 2*wmargin;
2923 
2924  if ((viewmode == "right") && (posx + actualw > width)) {
2925  posx = width - actualw - 20;
2926  hintsg.selectAll("svg").attr("x", posx);
2927  }
2928 
2929  if (actualw > 10)
2930  hintsg.selectAll("svg").attr("width", actualw)
2931  .select('rect').attr("width", actualw);
2932 
2933  hintsg.property('startx', posx);
2934  }
2935 
2936  JSROOT.Painter.drawFrame = function(divid, obj) {
2937  var p = new JSROOT.TFramePainter(obj);
2938  p.SetDivId(divid, 2);
2939  p.Redraw();
2940  return p.DrawingReady();
2941  }
2942 
2943  // ============================================================
2944 
2945  // base class for all objects, derived from TPave
2946  JSROOT.TPavePainter = function(pave) {
2947  JSROOT.TObjectPainter.call(this, pave);
2948  this.Enabled = true;
2949  this.UseContextMenu = true;
2950  this.UseTextColor = false; // indicates if text color used, enabled menu entry
2951  }
2952 
2953  JSROOT.TPavePainter.prototype = Object.create(JSROOT.TObjectPainter.prototype);
2954 
2955  JSROOT.TPavePainter.prototype.DrawPave = function(refill) {
2956  // this draw only basic TPave
2957 
2958  this.UseTextColor = false;
2959 
2960  if (!this.Enabled)
2961  return this.RemoveDrawG();
2962 
2963  var pt = this.GetObject();
2964 
2965  if (pt.fInit===0) {
2966  pt.fInit = 1;
2967  var pad = this.root_pad();
2968  if (pt.fOption.indexOf("NDC")>=0) {
2969  pt.fX1NDC = pt.fX1; pt.fX2NDC = pt.fX2;
2970  pt.fY1NDC = pt.fY1; pt.fY2NDC = pt.fY2;
2971  } else
2972  if (pad !== null) {
2973  if (pad.fLogx) {
2974  if (pt.fX1 > 0) pt.fX1 = JSROOT.log10(pt.fX1);
2975  if (pt.fX2 > 0) pt.fX2 = JSROOT.log10(pt.fX2);
2976  }
2977  if (pad.fLogy) {
2978  if (pt.fY1 > 0) pt.fY1 = JSROOT.log10(pt.fY1);
2979  if (pt.fY2 > 0) pt.fY2 = JSROOT.log10(pt.fY2);
2980  }
2981  pt.fX1NDC = (pt.fX1-pad.fX1) / (pad.fX2 - pad.fX1);
2982  pt.fY1NDC = (pt.fY1-pad.fY1) / (pad.fY2 - pad.fY1);
2983  pt.fX2NDC = (pt.fX2-pad.fX1) / (pad.fX2 - pad.fX1);
2984  pt.fY2NDC = (pt.fY2-pad.fY1) / (pad.fY2 - pad.fY1);
2985  } else {
2986  pt.fX1NDC = pt.fY1NDC = 0.1;
2987  pt.fX2NDC = pt.fY2NDC = 0.9;
2988  }
2989  }
2990 
2991  var pos_x = Math.round(pt.fX1NDC * this.pad_width()),
2992  pos_y = Math.round((1.0 - pt.fY2NDC) * this.pad_height()),
2993  width = Math.round((pt.fX2NDC - pt.fX1NDC) * this.pad_width()),
2994  height = Math.round((pt.fY2NDC - pt.fY1NDC) * this.pad_height()),
2995  lwidth = pt.fBorderSize;
2996 
2997  // container used to recalculate coordinates
2998  this.RecreateDrawG(true, this.IsStats() ? "stat_layer" : "text_layer");
2999 
3000  // position and size required only for drag functions
3001  this.draw_g
3002  .attr("x", pos_x)
3003  .attr("y", pos_y)
3004  .attr("width", width)
3005  .attr("height", height)
3006  .attr("transform", "translate(" + pos_x + "," + pos_y + ")");
3007 
3008  // add shadow decoration before main rect
3009  if ((lwidth > 1) && (pt.fShadowColor > 0))
3010  this.draw_g.append("svg:path")
3011  .attr("d","M" + width + "," + height +
3012  " v" + (-height + lwidth) + " h" + lwidth +
3013  " v" + height + " h" + (-width) +
3014  " v" + (-lwidth) + " Z")
3015  .style("fill", JSROOT.Painter.root_colors[pt.fShadowColor])
3016  .style("stroke", JSROOT.Painter.root_colors[pt.fShadowColor])
3017  .style("stroke-width", "1px");
3018 
3019  if (this.lineatt === undefined)
3020  this.lineatt = JSROOT.Painter.createAttLine(pt, lwidth>0 ? 1 : 0);
3021  if (this.fillatt === undefined)
3022  this.fillatt = this.createAttFill(pt);
3023 
3024  this.draw_g.append("rect")
3025  .attr("x", 0)
3026  .attr("y", 0)
3027  .attr("width", width)
3028  .attr("height", height)
3029  .style("pointer-events", "visibleFill")
3030  .call(this.fillatt.func)
3031  .call(this.lineatt.func);
3032 
3033  if ('PaveDrawFunc' in this)
3034  this.PaveDrawFunc(width, height, refill);
3035 
3036  this.AddDrag({ obj: pt, redraw: this.DrawPave.bind(this), ctxmenu: JSROOT.touches && JSROOT.gStyle.ContextMenu && this.UseContextMenu });
3037 
3038  if (this.UseContextMenu && JSROOT.gStyle.ContextMenu)
3039  this.draw_g.on("contextmenu", this.ShowContextMenu.bind(this) );
3040  }
3041 
3042  JSROOT.TPavePainter.prototype.DrawPaveLabel = function(width, height) {
3043  this.UseTextColor = true;
3044 
3045  var pave = this.GetObject();
3046 
3047  this.StartTextDrawing(pave.fTextFont, height/1.2);
3048 
3049  this.DrawText(pave.fTextAlign, 0, 0, width, height, pave.fLabel, JSROOT.Painter.root_colors[pave.fTextColor]);
3050 
3051  this.FinishTextDrawing();
3052  }
3053 
3054  JSROOT.TPavePainter.prototype.DrawPaveText = function(width, height, refill) {
3055 
3056  if (refill && this.IsStats()) this.FillStatistic();
3057 
3058  var pt = this.GetObject(),
3059  tcolor = JSROOT.Painter.root_colors[pt.fTextColor],
3060  lwidth = pt.fBorderSize,
3061  first_stat = 0,
3062  num_cols = 0,
3063  nlines = pt.fLines.arr.length,
3064  lines = [],
3065  maxlen = 0;
3066 
3067  // adjust font size
3068  for (var j = 0; j < nlines; ++j) {
3069  var line = pt.fLines.arr[j].fTitle;
3070  lines.push(line);
3071  if (j>0) maxlen = Math.max(maxlen, line.length);
3072  if (!this.IsStats() || (j == 0) || (line.indexOf('|') < 0)) continue;
3073  if (first_stat === 0) first_stat = j;
3074  var parts = line.split("|");
3075  if (parts.length > num_cols)
3076  num_cols = parts.length;
3077  }
3078 
3079  // for characters like 'p' or 'y' several more pixels required to stay in the box when drawn in last line
3080  var stepy = height / nlines, has_head = false, margin_x = pt.fMargin * width;
3081 
3082  this.StartTextDrawing(pt.fTextFont, height/(nlines * 1.2));
3083 
3084  if (nlines == 1) {
3085  this.DrawText(pt.fTextAlign, 0, 0, width, height, lines[0], tcolor);
3086  this.UseTextColor = true;
3087  } else {
3088  for (var j = 0; j < nlines; ++j) {
3089  var posy = j*stepy,
3090  jcolor = JSROOT.Painter.root_colors[pt.fLines.arr[j].fTextColor];
3091  if ((pt.fLines.arr[j].fTextColor == 0) || (jcolor===undefined)) {
3092  jcolor = tcolor;
3093  this.UseTextColor = true;
3094  }
3095 
3096  if (this.IsStats()) {
3097  if ((first_stat > 0) && (j >= first_stat)) {
3098  var parts = lines[j].split("|");
3099  for (var n = 0; n < parts.length; ++n)
3100  this.DrawText("middle",
3101  width * n / num_cols, posy,
3102  width/num_cols, stepy, parts[n], jcolor);
3103  } else if (lines[j].indexOf('=') < 0) {
3104  if (j==0) {
3105  has_head = true;
3106  if (lines[j].length > maxlen + 5)
3107  lines[j] = lines[j].substr(0,maxlen+2) + "...";
3108  }
3109  this.DrawText((j == 0) ? "middle" : "start",
3110  margin_x, posy, width-2*margin_x, stepy, lines[j], jcolor);
3111  } else {
3112  var parts = lines[j].split("="), sumw = 0;
3113  for (var n = 0; n < 2; ++n)
3114  sumw += this.DrawText((n == 0) ? "start" : "end",
3115  margin_x, posy, width-2*margin_x, stepy, parts[n], jcolor);
3116  this.TextScaleFactor(1.05*sumw/(width-2*margin_x), this.draw_g);
3117  }
3118  } else {
3119  this.DrawText(pt.fTextAlign, margin_x, posy, width-2*margin_x, stepy, lines[j], jcolor);
3120  }
3121  }
3122  }
3123 
3124  var maxtw = this.FinishTextDrawing();
3125 
3126  if ((lwidth > 0) && has_head) {
3127  this.draw_g.append("svg:line")
3128  .attr("x1", 0)
3129  .attr("y1", stepy.toFixed(1))
3130  .attr("x2", width)
3131  .attr("y2", stepy.toFixed(1))
3132  .call(this.lineatt.func);
3133  }
3134 
3135  if ((first_stat > 0) && (num_cols > 1)) {
3136  for (var nrow = first_stat; nrow < nlines; ++nrow)
3137  this.draw_g.append("svg:line")
3138  .attr("x1", 0)
3139  .attr("y1", (nrow * stepy).toFixed(1))
3140  .attr("x2", width)
3141  .attr("y2", (nrow * stepy).toFixed(1))
3142  .call(this.lineatt.func);
3143 
3144  for (var ncol = 0; ncol < num_cols - 1; ++ncol)
3145  this.draw_g.append("svg:line")
3146  .attr("x1", (width / num_cols * (ncol + 1)).toFixed(1))
3147  .attr("y1", (first_stat * stepy).toFixed(1))
3148  .attr("x2", (width / num_cols * (ncol + 1)).toFixed(1))
3149  .attr("y2", height)
3150  .call(this.lineatt.func);
3151  }
3152 
3153  if ((pt.fLabel.length>0) && !this.IsStats()) {
3154  var x = Math.round(width*0.25),
3155  y = Math.round(-height*0.02),
3156  w = Math.round(width*0.5),
3157  h = Math.round(height*0.04);
3158 
3159  var lbl_g = this.draw_g.append("svg:g");
3160 
3161  lbl_g.append("rect")
3162  .attr("x", x)
3163  .attr("y", y)
3164  .attr("width", w)
3165  .attr("height", h)
3166  .call(this.fillatt.func)
3167  .call(this.lineatt.func);
3168 
3169  this.StartTextDrawing(pt.fTextFont, h/1.5, lbl_g);
3170 
3171  this.DrawText(22, x, y, w, h, pt.fLabel, tcolor, 1, lbl_g);
3172 
3173  this.FinishTextDrawing(lbl_g);
3174 
3175  this.UseTextColor = true;
3176  }
3177  }
3178 
3179  JSROOT.TPavePainter.prototype.Format = function(value, fmt) {
3180  // method used to convert value to string according specified format
3181  // format can be like 5.4g or 4.2e or 6.4f
3182  if (!fmt) fmt = "stat";
3183 
3184  var pave = this.GetObject();
3185 
3186  if (fmt=="stat") {
3187  fmt = pave.fStatFormat;
3188  if (!fmt) fmt = JSROOT.gStyle.StatFormat;
3189  } else
3190  if (fmt=="fit") {
3191  fmt = pave.fFitFormat;
3192  if (!fmt) fmt = JSROOT.gStyle.FitFormat;
3193  } else
3194  if (fmt=="entries") {
3195  if (value < 1e9) return value.toFixed(0);
3196  fmt = "14.7g";
3197  } else
3198  if (fmt=="last") {
3199  fmt = this['lastformat'];
3200  }
3201 
3202  delete this['lastformat'];
3203 
3204  if (!fmt) fmt = "6.4g";
3205 
3206  var res = JSROOT.FFormat(value, fmt);
3207 
3208  this['lastformat'] = JSROOT.lastFFormat;
3209 
3210  return res;
3211  }
3212 
3213  JSROOT.TPavePainter.prototype.ShowContextMenu = function(evnt) {
3214  if (!evnt) {
3215  d3.event.stopPropagation(); // disable main context menu
3216  d3.event.preventDefault(); // disable browser context menu
3217 
3218  // one need to copy event, while after call back event may be changed
3219  evnt = d3.event;
3220  }
3221 
3222  var pthis = this, pave = this.GetObject();
3223 
3224  JSROOT.Painter.createMenu(function(menu) {
3225  menu.painter = pthis; // set as this in callbacks
3226  menu.add("header: " + pave._typename + "::" + pave.fName);
3227  if (pthis.IsStats()) {
3228 
3229  menu.add("SetStatFormat", function() {
3230  var fmt = prompt("Enter StatFormat", pave.fStatFormat);
3231  if (fmt!=null) {
3232  pave.fStatFormat = fmt;
3233  pthis.Redraw();
3234  }
3235  });
3236  menu.add("SetFitFormat", function() {
3237  var fmt = prompt("Enter FitFormat", pave.fFitFormat);
3238  if (fmt!=null) {
3239  pave.fFitFormat = fmt;
3240  pthis.Redraw();
3241  }
3242  });
3243  menu.add("separator");
3244  menu.add("sub:SetOptStat", function() {
3245  // todo - use jqury dialog here
3246  var fmt = prompt("Enter OptStat", pave.fOptStat);
3247  if (fmt!=null) { pave.fOptStat = parseInt(fmt); pthis.Redraw(); }
3248  });
3249  function AddStatOpt(pos, name) {
3250  var opt = (pos<10) ? pave.fOptStat : pave.fOptFit;
3251  opt = parseInt(parseInt(opt) / parseInt(Math.pow(10,pos % 10))) % 10;
3252  menu.addchk(opt, name, opt * 100 + pos, function(arg) {
3253  var newopt = (arg % 100 < 10) ? pave.fOptStat : pave.fOptFit;
3254  var oldopt = parseInt(arg / 100);
3255  newopt -= (oldopt>0 ? oldopt : -1) * parseInt(Math.pow(10, arg % 10));
3256  if (arg % 100 < 10) pave.fOptStat = newopt;
3257  else pave.fOptFit = newopt;
3258  pthis.Redraw();
3259  });
3260  }
3261 
3262  AddStatOpt(0, "Histogram name");
3263  AddStatOpt(1, "Entries");
3264  AddStatOpt(2, "Mean");
3265  AddStatOpt(3, "Std Dev");
3266  AddStatOpt(4, "Underflow");
3267  AddStatOpt(5, "Overflow");
3268  AddStatOpt(6, "Integral");
3269  AddStatOpt(7, "Skewness");
3270  AddStatOpt(8, "Kurtosis");
3271  menu.add("endsub:");
3272 
3273  menu.add("sub:SetOptFit", function() {
3274  // todo - use jqury dialog here
3275  var fmt = prompt("Enter OptStat", pave.fOptFit);
3276  if (fmt!=null) { pave.fOptFit = parseInt(fmt); pthis.Redraw(); }
3277  });
3278  AddStatOpt(10, "Fit parameters");
3279  AddStatOpt(11, "Par errors");
3280  AddStatOpt(12, "Chi square / NDF");
3281  AddStatOpt(13, "Probability");
3282  menu.add("endsub:");
3283 
3284  menu.add("separator");
3285  }
3286 
3287  if (pthis.UseTextColor)
3288  pthis.TextAttContextMenu(menu);
3289 
3290  pthis.FillAttContextMenu(menu);
3291 
3292  menu.show(evnt);
3293  }); // end menu creation
3294  }
3295 
3296  JSROOT.TPavePainter.prototype.IsStats = function() {
3297  return this.MatchObjectType('TPaveStats');
3298  }
3299 
3300  JSROOT.TPavePainter.prototype.FillStatistic = function() {
3301  var pave = this.GetObject(), main = this.main_painter();
3302 
3303  if (pave.fName !== "stats") return false;
3304  if ((main===null) || !('FillStatistic' in main)) return false;
3305 
3306  // no need to refill statistic if histogram is dummy
3307  if (main.IsDummyHisto()) return true;
3308 
3309  var dostat = new Number(pave.fOptStat);
3310  var dofit = new Number(pave.fOptFit);
3311  if (!dostat) dostat = JSROOT.gStyle.OptStat;
3312  if (!dofit) dofit = JSROOT.gStyle.OptFit;
3313 
3314  // make empty at the beginning
3315  pave.Clear();
3316 
3317  // we take statistic from first painter
3318  main.FillStatistic(this, dostat, dofit);
3319 
3320  return true;
3321  }
3322 
3323  JSROOT.TPavePainter.prototype.UpdateObject = function(obj) {
3324  if (!this.MatchObjectType(obj)) return false;
3325 
3326  var pave = this.GetObject();
3327 
3328  if (obj._typename === 'TPaveText') {
3329  pave.fLines = JSROOT.clone(obj.fLines);
3330  return true;
3331  } else
3332  if (obj._typename === 'TPaveLabel') {
3333  pave.fLabel = obj.fLabel;
3334  return true;
3335  }
3336 
3337  return false;
3338  }
3339 
3340  JSROOT.TPavePainter.prototype.Redraw = function() {
3341  // if pavetext artificially disabled, do not redraw it
3342 
3343  this.DrawPave(true);
3344  }
3345 
3346  JSROOT.Painter.drawPaveText = function(divid, pave, opt) {
3347 
3348  var painter = new JSROOT.TPavePainter(pave);
3349  painter.SetDivId(divid, 2);
3350 
3351  if ((typeof opt == 'string') && (opt.indexOf("onpad:")==0))
3352  painter.pad_name = opt.substr(6);
3353 
3354  switch (pave._typename) {
3355  case "TPaveLabel":
3356  painter.PaveDrawFunc = painter.DrawPaveLabel;
3357  break;
3358  case "TPaveStats":
3359  case "TPaveText":
3360  painter.PaveDrawFunc = painter.DrawPaveText;
3361  break;
3362  }
3363 
3364  painter.Redraw();
3365 
3366  return painter.DrawingReady();
3367  }
3368 
3369  // ===========================================================================
3370 
3371  JSROOT.TPadPainter = function(pad, iscan) {
3372  JSROOT.TObjectPainter.call(this, pad);
3373  this.pad = pad;
3374  this.iscan = iscan; // indicate if workign with canvas
3375  this.this_pad_name = "";
3376  if (!this.iscan && (pad !== null) && ('fName' in pad))
3377  this.this_pad_name = pad.fName.replace(" ", "_"); // avoid empty symbol in pad name
3378  this.painters = new Array; // complete list of all painters in the pad
3379  this.has_canvas = true;
3380  }
3381 
3382  JSROOT.TPadPainter.prototype = Object.create(JSROOT.TObjectPainter.prototype);
3383 
3384  JSROOT.TPadPainter.prototype.ButtonSize = function(fact) {
3385  return Math.round((!fact ? 1 : fact) * (this.iscan || !this.has_canvas ? 16 : 12));
3386  }
3387 
3388  JSROOT.TPadPainter.prototype.CreateCanvasSvg = function(check_resize, new_size) {
3389 
3390  var render_to = this.select_main();
3391 
3392  var rect = this.main_visible_rect();
3393 
3394  // this is size where canvas should be rendered
3395  var w = rect.width, h = rect.height;
3396 
3397  if ((typeof new_size == 'object') && (new_size!==null) && ('width' in new_size) && ('height' in new_size)) {
3398  w = new_size.width;
3399  h = new_size.height;
3400  }
3401 
3402  var factor = null, svg = null;
3403 
3404  if (check_resize > 0) {
3405 
3406  svg = this.svg_canvas();
3407 
3408  var oldw = svg.property('draw_width'), oldh = svg.property('draw_height');
3409 
3410  if ((w<=0) && (h<=0)) {
3411  svg.attr("visibility", "hidden");
3412  return false;
3413  } else {
3414  svg.attr("visibility", "visible");
3415  svg.select(".canvas_fillrect")
3416  .call(this.fillatt.func);
3417  }
3418 
3419  if (check_resize == 1) {
3420  if ((oldw == w) && (oldh == h)) return false;
3421  }
3422 
3423  factor = svg.property('height_factor');
3424 
3425  if (factor != null) {
3426  // if canvas was resize when created, resize height also now
3427  h = Math.round(w * factor);
3428  render_to.style('height', h+'px');
3429  }
3430 
3431  if ((check_resize==1) && (oldw>0) && (oldh>0) && !svg.property('redraw_by_resize'))
3432  if ((w/oldw>0.66) && (w/oldw<1.5) && (h/oldh>0.66) && (h/oldh<1.5)) {
3433  // not significant change in actual sizes, keep as it is
3434  // let browser scale SVG without our help
3435  return false;
3436  }
3437 
3438  } else {
3439 
3440  if ((h < 10) && (w > 0)) {
3441  // set aspect ratio for the place, where object will be drawn
3442 
3443  factor = 0.66;
3444 
3445  // for TCanvas reconstruct ratio between width and height
3446  if ((this.pad!==null) && ('fCw' in this.pad) && ('fCh' in this.pad) && (this.pad.fCw > 0)) {
3447  factor = this.pad.fCh / this.pad.fCw;
3448  if ((factor < 0.1) || (factor > 10))
3449  factor = 0.66;
3450  }
3451 
3452  h = Math.round(w * factor);
3453 
3454  render_to.style('height', h+'px');
3455  }
3456 
3457  svg = this.select_main()
3458  .append("svg")
3459  .attr("class", "jsroot root_canvas")
3460  .property('pad_painter', this) // this is custom property
3461  .property('mainpainter', null) // this is custom property
3462  .property('current_pad', "") // this is custom property
3463  .property('redraw_by_resize', false); // could be enabled to force redraw by each resize
3464 
3465  svg.append("svg:title").text("ROOT canvas");
3466  svg.append("svg:rect").attr("class","canvas_fillrect").attr("x",0).attr("y",0);
3467  svg.append("svg:g").attr("class","root_frame");
3468  svg.append("svg:g").attr("class","subpads_layer");
3469  svg.append("svg:g").attr("class","special_layer");
3470  svg.append("svg:g").attr("class","text_layer");
3471  svg.append("svg:g").attr("class","stat_layer");
3472  svg.append("svg:g").attr("class","btns_layer");
3473 
3474  this.fillatt = this.createAttFill(this.pad, 1001, 0);
3475 
3476  if (JSROOT.gStyle.ContextMenu)
3477  svg.select(".canvas_fillrect").on("contextmenu", this.ShowContextMenu.bind(this));
3478  }
3479 
3480  if ((w<=0) || (h<=0)) {
3481  svg.attr("visibility", "hidden");
3482  w = 200; h = 100; // just to complete drawing
3483  } else {
3484  svg.attr("visibility", "visible");
3485  }
3486 
3487  svg.attr("x", 0)
3488  .attr("y", 0)
3489  .attr("width", "100%")
3490  .attr("height", "100%")
3491  .attr("viewBox", "0 0 " + w + " " + h)
3492  .attr("preserveAspectRatio", "none") // we do not preserve relative ratio
3493  .property('height_factor', factor)
3494  .property('draw_x', 0)
3495  .property('draw_y', 0)
3496  .property('draw_width', w)
3497  .property('draw_height', h);
3498 
3499  svg.select(".canvas_fillrect")
3500  .attr("width",w)
3501  .attr("height",h)
3502  .call(this.fillatt.func);
3503 
3504  this.svg_layer("btns_layer").attr("transform","translate(2," + (h-this.ButtonSize(1.25)) + ")");
3505 
3506  return true;
3507  }
3508 
3509  JSROOT.TPadPainter.prototype.CreatePadSvg = function(only_resize) {
3510  if (!this.has_canvas)
3511  return this.CreateCanvasSvg(only_resize ? 2 : 0);
3512 
3513  var width = this.svg_canvas().property("draw_width"),
3514  height = this.svg_canvas().property("draw_height"),
3515  w = Math.round(this.pad.fAbsWNDC * width),
3516  h = Math.round(this.pad.fAbsHNDC * height),
3517  x = Math.round(this.pad.fAbsXlowNDC * width),
3518  y = Math.round(height - this.pad.fAbsYlowNDC * height) - h;
3519 
3520  var svg_pad = null, svg_rect = null, btns = null;
3521 
3522  if (only_resize) {
3523  svg_pad = this.svg_pad(this.this_pad_name);
3524  svg_rect = svg_pad.select(".root_pad_border");
3525  btns = this.svg_layer("btns_layer", this.this_pad_name);
3526  } else {
3527  svg_pad = this.svg_canvas().select(".subpads_layer")
3528  .append("g")
3529  .attr("class", "root_pad")
3530  .attr("pad", this.this_pad_name) // set extra attribute to mark pad name
3531  .property('pad_painter', this) // this is custom property
3532  .property('mainpainter', null); // this is custom property
3533  svg_rect = svg_pad.append("svg:rect").attr("class", "root_pad_border");
3534  svg_pad.append("svg:g").attr("class","root_frame");
3535  svg_pad.append("svg:g").attr("class","special_layer");
3536  svg_pad.append("svg:g").attr("class","text_layer");
3537  svg_pad.append("svg:g").attr("class","stat_layer");
3538  btns = svg_pad.append("svg:g").attr("class","btns_layer").property('nextx', 0);
3539 
3540  if (JSROOT.gStyle.ContextMenu)
3541  svg_pad.select(".root_pad_border").on("contextmenu", this.ShowContextMenu.bind(this));
3542 
3543  this.fillatt = this.createAttFill(this.pad, 1001, 0);
3544  this.lineatt = JSROOT.Painter.createAttLine(this.pad)
3545  if (this.pad.fBorderMode == 0) this.lineatt.color = 'none';
3546  }
3547 
3548  svg_pad.attr("transform", "translate(" + x + "," + y + ")")
3549  .property('draw_x', x) // this is to make similar with canvas
3550  .property('draw_y', y)
3551  .property('draw_width', w)
3552  .property('draw_height', h);
3553 
3554  svg_rect.attr("x", 0)
3555  .attr("y", 0)
3556  .attr("width", w)
3557  .attr("height", h)
3558  .call(this.fillatt.func)
3559  .call(this.lineatt.func);
3560 
3561  btns.attr("transform","translate("+ (w- btns.property('nextx') - this.ButtonSize(0.25)) + "," + (h-this.ButtonSize(1.25)) + ")");
3562  }
3563 
3564  JSROOT.TPadPainter.prototype.CheckColors = function(can) {
3565  if (can==null) return;
3566  for (var i = 0; i < can.fPrimitives.arr.length; ++i) {
3567  var obj = can.fPrimitives.arr[i];
3568  if (obj==null) continue;
3569  if ((obj._typename=="TObjArray") && (obj.name == "ListOfColors")) {
3570  JSROOT.Painter.adoptRootColors(obj);
3571  can.fPrimitives.arr.splice(i,1);
3572  can.fPrimitives.opt.splice(i,1);
3573  return;
3574  }
3575  }
3576  }
3577 
3578  JSROOT.TPadPainter.prototype.RemovePrimitive = function(obj) {
3579  if ((this.pad===null) || (this.pad.fPrimitives === null)) return;
3580  var indx = this.pad.fPrimitives.arr.indexOf(obj);
3581  if (indx>=0) this.pad.fPrimitives.RemoveAt(indx);
3582  }
3583 
3584  JSROOT.TPadPainter.prototype.FindPrimitive = function(exact_obj, classname, name) {
3585  if ((this.pad===null) || (this.pad.fPrimitives === null)) return null;
3586 
3587  for (var i=0; i < this.pad.fPrimitives.arr.length; i++) {
3588  var obj = this.pad.fPrimitives.arr[i];
3589 
3590  if ((exact_obj!==null) && (obj !== exact_obj)) continue;
3591 
3592  if ((classname !== undefined) && (classname !== null))
3593  if (obj._typename !== classname) continue;
3594 
3595  if ((name !== undefined) && (name !== null))
3596  if (obj.fName !== name) continue;
3597 
3598  return obj;
3599  }
3600 
3601  return null;
3602  }
3603 
3604  JSROOT.TPadPainter.prototype.HasObjectsToDraw = function() {
3605  // return true if any objects beside sub-pads exists in the pad
3606 
3607  if ((this.pad===null) || !this.pad.fPrimitives || (this.pad.fPrimitives.arr.length==0)) return false;
3608 
3609  for (var n=0;n<this.pad.fPrimitives.arr.length;++n)
3610  if (this.pad.fPrimitives.arr[n] && this.pad.fPrimitives.arr[n]._typename != "TPad") return true;
3611 
3612  return false;
3613  }
3614 
3615  JSROOT.TPadPainter.prototype.DrawPrimitive = function(indx, callback) {
3616  if ((this.pad===null) || (indx >= this.pad.fPrimitives.arr.length))
3617  return JSROOT.CallBack(callback);
3618 
3619  var pp = JSROOT.draw(this.divid, this.pad.fPrimitives.arr[indx], this.pad.fPrimitives.opt[indx]);
3620 
3621  if (pp === null) return this.DrawPrimitive(indx+1, callback);
3622 
3623  pp._primitive = true; // mark painter as belonging to primitives
3624  pp.WhenReady(this.DrawPrimitive.bind(this, indx+1, callback));
3625  }
3626 
3627 
3628  JSROOT.TPadPainter.prototype.GetTooltips = function(pnt) {
3629  var painters = [], hints = [];
3630 
3631  // first count - how many processors are there
3632  if (this.painters !== null)
3633  this.painters.forEach(function(obj) {
3634  if ('ProcessTooltip' in obj) painters.push(obj);
3635  });
3636 
3637  if (pnt) pnt.nproc = painters.length;
3638 
3639  painters.forEach(function(obj) {
3640  var hint = obj.ProcessTooltip(pnt);
3641  hints.push(hint);
3642  if (hint && pnt.painters) hint.painter = obj;
3643  });
3644 
3645  return hints;
3646  }
3647 
3648  JSROOT.TPadPainter.prototype.ShowContextMenu = function(evnt) {
3649  if (!evnt) {
3650 
3651  // for debug purposes keep original context menu for small region in top-left corner
3652  var pos = d3.mouse(this.svg_pad(this.this_pad_name).node());
3653  if (pos && (pos.length==2) && (pos[0]>0) && (pos[0]<10) && (pos[1]>0) && pos[1]<10) return;
3654 
3655  d3.event.stopPropagation(); // disable main context menu
3656  d3.event.preventDefault(); // disable browser context menu
3657 
3658  // one need to copy event, while after call back event may be changed
3659  evnt = d3.event;
3660  }
3661 
3662  var pthis = this;
3663 
3664  JSROOT.Painter.createMenu(function(menu) {
3665  menu.painter = pthis; // set as this in callbacks
3666  if (pthis.pad)
3667  menu.add("header: " + pthis.pad._typename + "::" + pthis.pad.fName);
3668  else
3669  menu.add("header: Canvas");
3670 
3671  if (pthis.iscan)
3672  menu.addchk((JSROOT.gStyle.Tooltip > 0), "Show tooltips", function() {
3673  JSROOT.gStyle.Tooltip = (JSROOT.gStyle.Tooltip === 0) ? 1 : -JSROOT.gStyle.Tooltip;
3674  });
3675 
3676  pthis.FillAttContextMenu(menu);
3677 
3678  menu.add("separator");
3679 
3680  var file_name = "canvas.png";
3681  if (!pthis.iscan) file_name = pthis.this_pad_name + ".png";
3682 
3683  menu.add("Save as "+file_name, file_name, function(arg) {
3684  // todo - use jqury dialog here
3685  var top = this.svg_pad(this.this_pad_name);
3686  if (!top.empty())
3687  JSROOT.AssertPrerequisites("savepng", function() {
3688  console.log('create', arg);
3689  top.selectAll(".btns_layer").style("display","none");
3690  saveSvgAsPng(top.node(), arg);
3691  top.selectAll(".btns_layer").style("display","");
3692  });
3693  });
3694 
3695  menu.show(evnt);
3696  }); // end menu creation
3697  }
3698 
3699  JSROOT.TPadPainter.prototype.Redraw = function(resize) {
3700  if (this.iscan)
3701  this.CreateCanvasSvg(2);
3702  else
3703  this.CreatePadSvg(true);
3704 
3705  // at the moment canvas painter donot redraw its subitems
3706  for (var i = 0; i < this.painters.length; ++i)
3707  this.painters[i].Redraw(resize);
3708  }
3709 
3710  JSROOT.TPadPainter.prototype.NumDrawnSubpads = function() {
3711  if (this.painters === undefined) return 0;
3712 
3713  var num = 0;
3714 
3715  for (var i = 0; i < this.painters.length; ++i) {
3716  var obj = this.painters[i].GetObject();
3717  if ((obj!==null) && (obj._typename === "TPad")) num++;
3718  }
3719 
3720  return num;
3721  }
3722 
3723  JSROOT.TPadPainter.prototype.CheckCanvasResize = function(size, force) {
3724  if (!this.iscan) return false;
3725 
3726  if ((size !== null) && (typeof size === 'object') && size.force) force = true;
3727 
3728  var changed = this.CreateCanvasSvg(force ? 2 : 1, size);
3729 
3730  // if canvas changed, redraw all its subitems
3731  if (changed)
3732  for (var i = 0; i < this.painters.length; ++i)
3733  this.painters[i].Redraw(true);
3734 
3735  return changed;
3736  }
3737 
3738  JSROOT.TPadPainter.prototype.UpdateObject = function(obj) {
3739 
3740  if ((obj == null) || !('fPrimitives' in obj)) return false;
3741 
3742  if (this.iscan) this.CheckColors(obj);
3743 
3744  if (obj.fPrimitives.arr.length !== this.pad.fPrimitives.arr.length) return false;
3745 
3746  var isany = false, p = 0;
3747  for (var n = 0; n < obj.fPrimitives.arr.length; ++n) {
3748  while (p < this.painters.length) {
3749  var pp = this.painters[p++];
3750  if (!('_primitive' in pp)) continue;
3751  if (pp.UpdateObject(obj.fPrimitives.arr[n])) isany = true;
3752  break;
3753  }
3754  }
3755 
3756  return isany;
3757  }
3758 
3759  JSROOT.TPadPainter.prototype.ItemContextMenu = function(name) {
3760  var rrr = this.svg_pad(this.this_pad_name).node().getBoundingClientRect();
3761  var evnt = { clientX: rrr.left+10, clientY: rrr.top + 10 };
3762 
3763  // use timeout to avoid conflict with mouse click and automatic menu close
3764  if (name=="pad")
3765  return setTimeout(this.ShowContextMenu.bind(this, evnt), 50);
3766 
3767  var selp = null, selkind;
3768 
3769  switch(name) {
3770  case "xaxis": selp = this.main_painter(); selkind = "x"; break;
3771  case "yaxis": selp = this.main_painter(); selkind = "y"; break;
3772  case "frame": selp = this.frame_painter(); break;
3773  default: {
3774  var indx = parseInt(name);
3775  if (!isNaN(indx)) selp = this.painters[indx];
3776  }
3777  }
3778 
3779  if (!selp || (typeof selp.FillContextMenu !== 'function')) return;
3780 
3781  JSROOT.Painter.createMenu(function(menu) {
3782  menu.painter = selp;
3783  if (selp.FillContextMenu(menu,selkind))
3784  setTimeout(menu.show.bind(menu, evnt), 50);
3785  });
3786 
3787  }
3788 
3789  JSROOT.TPadPainter.prototype.PadButtonClick = function(funcname) {
3790 
3791  var elem = null, filename = "";
3792 
3793  if (funcname == "CanvasSnapShot") {
3794  elem = this.svg_canvas();
3795  filename = (this.pad ? this.pad.fName : "jsroot_canvas") + ".png";
3796  } else
3797  if (funcname == "PadSnapShot") {
3798  elem = this.svg_pad(this.this_pad_name);
3799  filename = this.this_pad_name + ".png";
3800  }
3801  if ((elem!==null) && !elem.empty()) {
3802  var main = elem.property('mainpainter');
3803 
3804  if ((elem.property('can3d') === 1) && (main!==undefined) && (main.renderer!==undefined)) {
3805  var dataUrl = main.renderer.domElement.toDataURL("image/png");
3806  dataUrl.replace("image/png", "image/octet-stream");
3807  var link = document.createElement('a');
3808  if (typeof link.download === 'string') {
3809  document.body.appendChild(link); //Firefox requires the link to be in the body
3810  link.download = filename;
3811  link.href = dataUrl;
3812  link.click();
3813  document.body.removeChild(link); //remove the link when done
3814  }
3815  } else {
3816  JSROOT.AssertPrerequisites("savepng", function() {
3817  elem.selectAll(".btns_layer").style("display","none");
3818  saveSvgAsPng(elem.node(), filename);
3819  elem.selectAll(".btns_layer").style("display","");
3820  });
3821  }
3822  return;
3823  }
3824 
3825  if (funcname == "PadContextMenus") {
3826 
3827  var pthis = this, evnt = d3.event;
3828 
3829  d3.event.preventDefault();
3830  d3.event.stopPropagation();
3831 
3832  JSROOT.Painter.createMenu(function(menu) {
3833  menu.painter = pthis; // set as this in callbacks
3834  menu.add("header:Menus");
3835 
3836  if (pthis.iscan)
3837  menu.add("Canvas", "pad", pthis.ItemContextMenu);
3838  else
3839  menu.add("Pad", "pad", pthis.ItemContextMenu);
3840 
3841  if (pthis.frame_painter())
3842  menu.add("Frame", "frame", pthis.ItemContextMenu);
3843 
3844  if (pthis.main_painter()) {
3845  menu.add("X axis", "xaxis", pthis.ItemContextMenu);
3846  menu.add("Y axis", "yaxis", pthis.ItemContextMenu);
3847  }
3848 
3849  if (pthis.painters && (pthis.painters.length>0)) {
3850  menu.add("separator");
3851  var shown = [];
3852  for (var n=0;n<pthis.painters.length;++n) {
3853  var pp = pthis.painters[n];
3854  var obj = pp ? pp.GetObject() : null;
3855  if (!obj || (shown.indexOf(obj)>=0)) continue;
3856 
3857  var name = ('_typename' in obj) ? (obj._typename + "::") : "";
3858  if ('fName' in obj) name += obj.fName;
3859  if (name.length==0) name = "item" + n;
3860  menu.add(name, n, pthis.ItemContextMenu);
3861  }
3862  }
3863 
3864  menu.show(evnt);
3865  });
3866 
3867  return;
3868  }
3869 
3870  // click automatically goes to all sub-pads
3871  // if any painter indicates that processing completed, it returns true
3872  var done = false;
3873 
3874  for (var i = 0; i < this.painters.length; ++i) {
3875  var pp = this.painters[i];
3876 
3877  if (typeof pp.PadButtonClick == 'function')
3878  pp.PadButtonClick(funcname);
3879 
3880  if (!done && (typeof pp.ButtonClick == 'function'))
3881  done = pp.ButtonClick(funcname);
3882  }
3883  }
3884 
3885  JSROOT.TPadPainter.prototype.AddButton = function(btn, tooltip, funcname) {
3886 
3887  // do not add buttons when not allowed
3888  if (!JSROOT.gStyle.ToolBar) return;
3889 
3890  var group = this.svg_layer("btns_layer", this.this_pad_name);
3891  if (group.empty()) return;
3892 
3893  // avoid buttons with duplicate names
3894  if (!group.select("[name=" + funcname + ']').empty()) return;
3895 
3896  var x = group.property("nextx");
3897  if (x===undefined) x = 0;
3898 
3899  var iscan = this.iscan || !this.has_canvas;
3900 
3901  var svg = group.append("svg:svg")
3902  .attr("class", "svg_toolbar_btn")
3903  .attr("name", funcname)
3904  .attr("x", x)
3905  .attr("y", 0)
3906  .attr("width",this.ButtonSize()+"px")
3907  .attr("height",this.ButtonSize()+"px")
3908  .attr("viewBox", "0 0 512 512")
3909  .style("overflow","hidden");
3910 
3911  svg.append("svg:title").text(tooltip + (iscan ? "" : (" on pad " + this.this_pad_name)));
3912 
3913  if ('recs' in btn) {
3914  var rec = {};
3915  for (var n=0;n<btn.recs.length;++n) {
3916  JSROOT.extend(rec, btn.recs[n]);
3917  svg.append('rect').attr("x", rec.x).attr("y", rec.y)
3918  .attr("width", rec.w).attr("height", rec.h)
3919  .attr("fill", rec.f);
3920  }
3921  } else {
3922  svg.append('svg:path').attr('d',btn.path);
3923  }
3924 
3925  // special rect to correctly get mouse events for whole button area
3926  svg.append("svg:rect").attr("x",0).attr("y",0).attr("width",512).attr("height",512)
3927  .style("opacity","0").style("fill","none").style("pointer-events", "visibleFill");
3928 
3929  svg.on("click", this.PadButtonClick.bind(this, funcname));
3930 
3931  group.property("nextx", x+this.ButtonSize(1.25));
3932 
3933  if (!iscan)
3934  group.attr("transform","translate("+ (this.pad_width(this.this_pad_name) - group.property('nextx')-this.ButtonSize(0.25)) + "," + (this.pad_height(this.this_pad_name)-this.ButtonSize(1.25)) + ")");
3935 
3936  if (!iscan && (funcname.indexOf("Pad")!=0) && (this.pad_painter()!==this))
3937  this.pad_painter().AddButton(btn, tooltip, funcname);
3938  }
3939 
3940  JSROOT.Painter.drawCanvas = function(divid, can, opt) {
3941  var painter = new JSROOT.TPadPainter(can, true);
3942  if (can && opt && (opt.indexOf("white")>=0)) can.fFillColor = 0;
3943 
3944  painter.SetDivId(divid, -1); // just assign id
3945  painter.CheckColors(can);
3946  painter.CreateCanvasSvg(0);
3947  painter.SetDivId(divid); // now add to painters list
3948 
3949  painter.AddButton(JSROOT.ToolbarIcons.camera, "Create PNG", "CanvasSnapShot");
3950  painter.AddButton(JSROOT.ToolbarIcons.question, "Access context menus", "PadContextMenus");
3951 
3952  if (can==null) {
3953  if (opt.indexOf("noframe") < 0)
3954  JSROOT.Painter.drawFrame(divid, null);
3955  return painter.DrawingReady();
3956  }
3957 
3958  painter.DrawPrimitive(0, function() { painter.DrawingReady(); });
3959  return painter;
3960  }
3961 
3962  JSROOT.Painter.drawPad = function(divid, pad, opt) {
3963  var painter = new JSROOT.TPadPainter(pad, false);
3964 
3965  if (pad && opt && (opt.indexOf("white")>=0)) pad.fFillColor = 0;
3966 
3967  painter.SetDivId(divid); // pad painter will be registered in the canvas painters list
3968 
3969  if (painter.svg_canvas().empty()) {
3970  painter.has_canvas = false;
3971  painter.this_pad_name = "";
3972  }
3973 
3974  painter.CreatePadSvg();
3975 
3976  if (painter.MatchObjectType("TPad") && (!painter.has_canvas || painter.HasObjectsToDraw())) {
3977  painter.AddButton(JSROOT.ToolbarIcons.camera, "Create PNG", "PadSnapShot");
3978  painter.AddButton(JSROOT.ToolbarIcons.question, "Access context menus", "PadContextMenus");
3979  }
3980 
3981  var prev_name = "";
3982 
3983  if (painter.has_canvas) {
3984  // we select current pad, where all drawing is performed
3985  prev_name = painter.svg_canvas().property('current_pad');
3986  painter.svg_canvas().property('current_pad', pad.fName);
3987  }
3988 
3989  painter.DrawPrimitive(0, function() {
3990  // we restore previous pad name
3991  painter.svg_canvas().property('current_pad', prev_name);
3992  painter.DrawingReady();
3993  });
3994 
3995  return painter;
3996  }
3997 
3998  // =======================================================================
3999 
4000  JSROOT.TAxisPainter = function(axis, embedded) {
4001  JSROOT.TObjectPainter.call(this, axis);
4002 
4003  this.embedded = embedded; // indicate that painter embedded into the histo painter
4004 
4005  this.name = "yaxis";
4006  this.kind = "normal";
4007  this.func = null;
4008  this.order = 0; // scaling order for axis labels
4009 
4010  this.full_min = 0;
4011  this.full_max = 1;
4012  this.scale_min = 0;
4013  this.scale_max = 1;
4014  this.ticks = []; // list of major ticks
4015  }
4016 
4017  JSROOT.TAxisPainter.prototype = Object.create(JSROOT.TObjectPainter.prototype);
4018 
4019  JSROOT.TAxisPainter.prototype.SetAxisConfig = function(name, kind, func, min, max, smin, smax) {
4020  this.name = name;
4021  this.kind = kind;
4022  this.func = func;
4023 
4024  this.full_min = min;
4025  this.full_max = max;
4026  this.scale_min = smin;
4027  this.scale_max = smax;
4028  }
4029 
4030  JSROOT.TAxisPainter.prototype.CreateFormatFuncs = function() {
4031 
4032  var axis = this.GetObject(),
4033  is_gaxis = (axis && axis._typename === 'TGaxis');
4034 
4035  delete this.format;// remove formatting func
4036 
4037  var ndiv = 508;
4038  if (axis !== null)
4039  ndiv = Math.max(is_gaxis ? axis.fNdiv : axis.fNdivisions, 4) ;
4040 
4041  this.nticks = ndiv % 100;
4042  this.nticks2 = (ndiv % 10000 - this.nticks) / 100;
4043  this.nticks3 = Math.floor(ndiv/10000);
4044 
4045  if (axis && !is_gaxis && (this.nticks > 7)) this.nticks = 7;
4046 
4047  var gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]);
4048  if (gr_range<=0) gr_range = 100;
4049 
4050  if (this.kind == 'time') {
4051  if (this.nticks > 8) this.nticks = 8;
4052 
4053  var scale_range = this.scale_max - this.scale_min;
4054 
4055  var tf1 = JSROOT.Painter.getTimeFormat(axis);
4056  if ((tf1.length == 0) || (scale_range < 0.1 * (this.full_max - this.full_min)))
4057  tf1 = JSROOT.Painter.chooseTimeFormat(scale_range / this.nticks, true);
4058  var tf2 = JSROOT.Painter.chooseTimeFormat(scale_range / gr_range, false);
4059 
4060  this.tfunc1 = this.tfunc2 = d3.time.format(tf1);
4061  if (tf2!==tf1)
4062  this.tfunc2 = d3.time.format(tf2);
4063 
4064  this.format = function(d, asticks) {
4065  return asticks ? this.tfunc1(d) : this.tfunc2(d);
4066  }
4067 
4068  } else
4069  if (this.kind == 'log') {
4070  this.nticks2 = 1;
4071  this.noexp = axis ? axis.TestBit(JSROOT.EAxisBits.kNoExponent) : false;
4072  if ((this.scale_max < 300) && (this.scale_min > 0.3)) this.noexp = true;
4073  this.moreloglabels = axis ? axis.TestBit(JSROOT.EAxisBits.kMoreLogLabels) : false;
4074 
4075  this.format = function(d, asticks, notickexp) {
4076 
4077  var val = parseFloat(d);
4078 
4079  if (!asticks) {
4080  var rnd = Math.round(val);
4081  return ((rnd === val) && (Math.abs(rnd)<1e9)) ? rnd.toString() : val.toExponential(4);
4082  }
4083 
4084  if (val <= 0) return null;
4085  var vlog = JSROOT.log10(val);
4086  if (this.moreloglabels || (Math.abs(vlog - Math.round(vlog))<0.001)) {
4087  if (!this.noexp && !notickexp)
4088  return JSROOT.Painter.formatExp(val.toExponential(0));
4089  else
4090  if (vlog<0)
4091  return val.toFixed(Math.round(-vlog+0.5));
4092  else
4093  return val.toFixed(0);
4094  }
4095  return null;
4096  }
4097  } else
4098  if (this.kind == 'labels') {
4099  this.nticks = 50; // for text output allow max 50 names
4100  var scale_range = this.scale_max - this.scale_min;
4101  if (this.nticks > scale_range)
4102  this.nticks = Math.round(scale_range);
4103  this.nticks2 = 1;
4104 
4105  this.axis = axis;
4106 
4107  this.format = function(d) {
4108  var indx = Math.round(parseInt(d)) + 1;
4109  if ((indx<1) || (indx>this.axis.fNbins)) return null;
4110  for (var i = 0; i < this.axis.fLabels.arr.length; ++i) {
4111  var tstr = this.axis.fLabels.arr[i];
4112  if (tstr.fUniqueID == indx) return tstr.fString;
4113  }
4114  return null;
4115  }
4116  } else {
4117 
4118  this.range = Math.abs(this.scale_max - this.scale_min);
4119  if (this.range <= 0)
4120  this.ndig = -3;
4121  else
4122  this.ndig = Math.round(JSROOT.log10(this.nticks / this.range) + 0.7);
4123 
4124  this.format = function(d, asticks) {
4125  var val = parseFloat(d), rnd = Math.round(val);
4126  if (asticks) {
4127  if (this.order===0) {
4128  if (val === rnd) return rnd.toString();
4129  if (Math.abs(val) < 1e-10 * this.range) return 0;
4130  val = val.toFixed(this.ndig > 0 ? this.ndig : 0);
4131  if ((typeof d == 'string') && (d.length <= val.length+1)) return d;
4132  return val;
4133  }
4134  val = val / Math.pow(10, this.order);
4135  rnd = Math.round(val);
4136  if (val === rnd) return rnd.toString();
4137  return val.toFixed(this.ndig + this.order > 0 ? this.ndig + this.order : 0 );
4138  }
4139 
4140  if (val === rnd)
4141  return (Math.abs(rnd)<1e9) ? rnd.toString() : val.toExponential(4);
4142  return val.toFixed(this.ndig+2 > 0 ? this.ndig+2 : 0);
4143  }
4144  }
4145  }
4146 
4147  JSROOT.TAxisPainter.prototype.CreateTicks = function() {
4148  // function used to create array with minor/middle/major ticks
4149 
4150  var handle = { nminor: 0, nmiddle: 0, nmajor: 0, func: this.func };
4151 
4152  handle.minor = handle.middle = handle.major = this.func.ticks(this.nticks);
4153 
4154  if (this.nticks2 > 1) {
4155  handle.minor = handle.middle = this.func.ticks(handle.major.length * this.nticks2);
4156 
4157  var gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]);
4158 
4159  // avoid black filling by middle-size
4160  if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range/3.5)) {
4161  handle.minor = handle.middle = handle.major;
4162  } else
4163  if ((this.nticks3 > 1) && (this.kind !== 'log')) {
4164  handle.minor = this.func.ticks(handle.middle.length * this.nticks3);
4165  if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range/1.7)) handle.minor = handle.middle;
4166  }
4167  }
4168 
4169  handle.reset = function() {
4170  this.nminor = this.nmiddle = this.nmajor = 0;
4171  }
4172 
4173  handle.next = function(doround) {
4174  if (this.nminor >= this.minor.length) return false;
4175 
4176  this.tick = this.minor[this.nminor++];
4177  this.grpos = this.func(this.tick);
4178  if (doround) this.grpos = Math.round(this.grpos);
4179  this.kind = 3;
4180 
4181  if ((this.nmiddle < this.middle.length) && (Math.abs(this.grpos - this.func(this.middle[this.nmiddle])) < 1)) {
4182  this.nmiddle++;
4183  this.kind = 2;
4184  }
4185 
4186  if ((this.nmajor < this.major.length) && (Math.abs(this.grpos - this.func(this.major[this.nmajor])) < 1) ) {
4187  this.nmajor++;
4188  this.kind = 1;
4189  }
4190  return true;
4191  }
4192 
4193  handle.last_major = function() {
4194  return (this.kind !== 1) ? false : this.nmajor == this.major.length;
4195  }
4196 
4197  return handle;
4198  }
4199 
4200  JSROOT.TAxisPainter.prototype.DrawAxis = function(layer, w, h, transform, reverse) {
4201  // function draw complete TAxis
4202  // later will be used to draw TGaxis
4203 
4204  var axis = this.GetObject(),
4205  is_gaxis = (axis && axis._typename === 'TGaxis'),
4206  vertical = (this.name !== "xaxis"),
4207  side = (this.name === "zaxis") ? -1 : 1, both_sides = 0,
4208  axis_g = layer, tickSize = 10, scaling_size = 100, text_scaling_size = 100;
4209 
4210  if (is_gaxis) {
4211  if (!this.lineatt) this.lineatt = JSROOT.Painter.createAttLine(axis);
4212  scaling_size = (vertical ? this.pad_width() : this.pad_height());
4213  text_scaling_size = Math.min(this.pad_width(), this.pad_height());
4214  tickSize = Math.round(axis.fTickSize * scaling_size);
4215  } else {
4216  if (!this.lineatt) this.lineatt = JSROOT.Painter.createAttLine(axis.fAxisColor, 1);
4217  scaling_size = (vertical ? w : h);
4218  tickSize = Math.round(axis.fTickLength * scaling_size);
4219  text_scaling_size = Math.min(w,h);
4220  }
4221 
4222  if (!is_gaxis || (this.name === "zaxis")) {
4223  axis_g = layer.select("." + this.name + "_container");
4224  if (axis_g.empty())
4225  axis_g = layer.append("svg:g").attr("class",this.name+"_container");
4226  else
4227  axis_g.selectAll("*").remove();
4228  } else {
4229 
4230  if ((axis.fChopt.indexOf("-")>=0) && (axis.fChopt.indexOf("+")<0)) side = -1; else
4231  if (vertical && axis.fChopt=="+L") side = -1; else
4232  if ((axis.fChopt.indexOf("-")>=0) && (axis.fChopt.indexOf("+")>=0)) { side = 1; both_sides = 1; }
4233 
4234  axis_g.append("svg:line")
4235  .attr("x1",0).attr("y1",0)
4236  .attr("x1",vertical ? 0 : w)
4237  .attr("y1", vertical ? h : 0)
4238  .call(this.lineatt.func);
4239  }
4240 
4241  if (transform!== undefined)
4242  axis_g.attr("transform", transform);
4243 
4244  this.CreateFormatFuncs();
4245 
4246  var center = (this.kind == 'labels') ||
4247  (this.kind !== 'log' && axis.TestBit(JSROOT.EAxisBits.kCenterLabels));
4248 
4249  var res = "", lastpos = 0, lasth = 0, textscale = 1;
4250 
4251  // first draw ticks
4252 
4253  this.ticks = [];
4254 
4255  var handle = this.CreateTicks();
4256 
4257  while (handle.next(true)) {
4258  var h1 = Math.round(tickSize/4), h2 = 0;
4259 
4260  if (handle.kind < 3)
4261  h1 = Math.round(tickSize/2);
4262 
4263  if (handle.kind == 1) {
4264  // if not showing lables, not show large tick
4265  if (!('format' in this) || (this.format(handle.tick,true)!==null)) h1 = tickSize;
4266  this.ticks.push(handle.grpos); // keep graphical positions of major ticks
4267  }
4268 
4269  if (both_sides > 0) h2 = -h1; else
4270  if (side < 0) { h2 = -h1; h1 = 0; } else { h2 = 0; }
4271 
4272  if (res.length == 0) {
4273  res += vertical ? ("M"+h1+","+handle.grpos) : ("M"+handle.grpos+","+-h1);
4274  } else {
4275  res += vertical ? ("m"+(h1-lasth)+","+(handle.grpos-lastpos)) : ("m"+(handle.grpos-lastpos)+","+(lasth-h1));
4276  }
4277 
4278  res += vertical ? ("h"+ (h2-h1)) : ("v"+ (h1-h2));
4279 
4280  lastpos = handle.grpos;
4281  lasth = h2;
4282  }
4283 
4284  if (res.length > 0)
4285  axis_g.append("svg:path").attr("d", res).call(this.lineatt.func);
4286 
4287  var last = vertical ? h : 0,
4288  labelfont = JSROOT.Painter.getFontDetails(axis.fLabelFont, Math.round(axis.fLabelSize * (is_gaxis ? this.pad_height() : h))),
4289  label_color = JSROOT.Painter.root_colors[axis.fLabelColor],
4290  labeloffset = 3 + Math.round(axis.fLabelOffset * scaling_size),
4291  label_g = axis_g.append("svg:g")
4292  .attr("class","axis_labels")
4293  .call(labelfont.func);
4294 
4295  this.order = 0;
4296  if ((this.kind=="normal") && vertical && !axis.TestBit(JSROOT.EAxisBits.kNoExponent)) {
4297  var maxtick = Math.max(Math.abs(handle.major[0]),Math.abs(handle.major[handle.major.length-1]));
4298  for(var order=18;order>-18;order-=3) {
4299  if (order===0) continue;
4300  if ((order<0) && ((this.range>=0.1) || (maxtick>=1.))) break;
4301  var mult = Math.pow(10, order);
4302  if ((this.range > mult * 9.99999) || ((maxtick > mult*50) && (this.range > mult * 0.05))) {
4303  this.order = order;
4304  break;
4305  }
4306  }
4307  }
4308 
4309  for (var nmajor=0;nmajor<handle.major.length;++nmajor) {
4310  var pos = Math.round(this.func(handle.major[nmajor]));
4311  var lbl = this.format(handle.major[nmajor], true);
4312  if (lbl === null) continue;
4313 
4314  var t = label_g.append("svg:text").attr("fill", label_color).text(lbl);
4315 
4316  if (vertical)
4317  t.attr("x", -labeloffset*side)
4318  .attr("y", pos)
4319  .style("text-anchor", (side > 0) ? "end" : "start")
4320  .style("dominant-baseline", "middle");
4321  else
4322  t.attr("x", pos)
4323  .attr("y", 2+labeloffset*side + both_sides*tickSize)
4324  .attr("dy", (side > 0) ? ".7em" : "-.3em")
4325  .style("text-anchor", "middle");
4326 
4327  var tsize = this.GetBoundarySizes(t.node());
4328  var space_before = (nmajor > 0) ? (pos - last) : (vertical ? h/2 : w/2);
4329  var space_after = (nmajor < handle.major.length-1) ? (Math.round(this.func(handle.major[nmajor+1])) - pos) : space_before;
4330  var space = Math.min(Math.abs(space_before), Math.abs(space_after));
4331 
4332  if (vertical) {
4333 
4334  if ((space > 0) && (tsize.height > 5) && (this.kind !== 'log'))
4335  textscale = Math.min(textscale, space / tsize.height);
4336 
4337  if (center) {
4338  // if position too far top, remove label
4339  if (pos + space_after/2 - textscale*tsize.height/2 < -10)
4340  t.remove();
4341  else
4342  t.attr("y", Math.round(pos + space_after/2));
4343  }
4344 
4345  } else {
4346 
4347  // test if label consume too much space
4348  if ((space > 0) && (tsize.width > 10) && (this.kind !== 'log'))
4349  textscale = Math.min(textscale, space / tsize.width);
4350 
4351  if (center) {
4352  // if position too far right, remove label
4353  if (pos + space_after/2 - textscale*tsize.width/2 > w - 10)
4354  t.remove();
4355  else
4356  t.attr("x", Math.round(pos + space_after/2));
4357  }
4358  }
4359 
4360  last = pos;
4361  }
4362 
4363  if (this.order!==0) {
4364  var val = Math.pow(10,this.order).toExponential(0);
4365 
4366  var t = label_g.append("svg:text").attr("fill", label_color).text('\xD7' + JSROOT.Painter.formatExp(val));
4367 
4368  if (vertical)
4369  t.attr("x", labeloffset)
4370  .attr("y", 0)
4371  .style("text-anchor", "start")
4372  .style("dominant-baseline", "middle")
4373  .attr("dy", "-.5em");
4374  }
4375 
4376 
4377  if ((textscale>0) && (textscale<1.)) {
4378  // rotate X lables if they are too big
4379  if ((textscale < 0.7) && !vertical && (side>0)) {
4380  label_g.selectAll("text").each(function() {
4381  var txt = d3.select(this), x = txt.attr("x"), y = txt.attr("y") - 5;
4382 
4383  txt.attr("transform", "translate(" + x + "," + y + ") rotate(25)")
4384  .style("text-anchor", "start")
4385  .attr("x",null).attr("y",null);
4386  });
4387  textscale *= 3.5;
4388  }
4389  // round to upper boundary for calculated value like 4.4
4390  labelfont.size = Math.floor(labelfont.size * textscale + 0.7);
4391  label_g.call(labelfont.func);
4392  }
4393 
4394  if (axis.fTitle.length > 0) {
4395  var title_g = axis_g.append("svg:g").attr("class", "axis_title"),
4396  title_fontsize = Math.round(axis.fTitleSize * text_scaling_size),
4397  center = axis.TestBit(JSROOT.EAxisBits.kCenterTitle),
4398  rotate = axis.TestBit(JSROOT.EAxisBits.kRotateTitle) ? -1 : 1,
4399  title_color = JSROOT.Painter.root_colors[axis.fTitleColor];
4400 
4401  this.StartTextDrawing(axis.fTitleFont, title_fontsize, title_g);
4402 
4403  var myxor = ((rotate<0) && !reverse) || ((rotate>=0) && reverse);
4404 
4405  if (vertical) {
4406  var xoffset = -side*Math.round(labeloffset + (2-side/10) * axis.fTitleOffset*title_fontsize);
4407 
4408  if ((this.name == "zaxis") && is_gaxis && ('getBoundingClientRect' in axis_g.node())) {
4409  // special handling for color palette labels - draw them always on right side
4410  var rect = axis_g.node().getBoundingClientRect();
4411  if (xoffset < rect.width - tickSize) xoffset = Math.round(rect.width - tickSize);
4412  }
4413 
4414  this.DrawText((center ? "middle" : (myxor ? "begin" : "end" ))+ ";middle",
4415  xoffset,
4416  Math.round(center ? h/2 : (reverse ? h : 0)),
4417  0, (rotate<0 ? -90 : -270),
4418  axis.fTitle, title_color, 1, title_g);
4419  } else {
4420  this.DrawText((center ? 'middle' : (myxor ? 'begin' : 'end')) + ";middle",
4421  Math.round(center ? w/2 : (reverse ? 0 : w)),
4422  Math.round(side*(labeloffset + 1.9*title_fontsize*axis.fTitleOffset)),
4423  0, (rotate<0 ? -180 : 0),
4424  axis.fTitle, title_color, 1, title_g);
4425  }
4426 
4427  this.FinishTextDrawing(title_g);
4428  }
4429 
4430 
4431  if (JSROOT.gStyle.Zooming) {
4432  var r = axis_g.append("svg:rect")
4433  .attr("class", "axis_zoom")
4434  .style("opacity", "0")
4435  .style("cursor", "crosshair");
4436 
4437  if (vertical)
4438  r.attr("x", (side >0) ? (-2*labelfont.size - 3) : 3)
4439  .attr("y", 0)
4440  .attr("width", 2*labelfont.size + 3)
4441  .attr("height", h)
4442  else
4443  r.attr("x", 0).attr("y", 0)
4444  .attr("width", w).attr("height", labelfont.size + 3);
4445  }
4446 
4447  this.position = 0;
4448 
4449  if ('getBoundingClientRect' in axis_g.node()) {
4450  var rect1 = axis_g.node().getBoundingClientRect(),
4451  rect2 = this.svg_pad().node().getBoundingClientRect();
4452 
4453  this.position = rect1.left - rect2.left; // use to control left position of Y scale
4454  }
4455  }
4456 
4457  JSROOT.TAxisPainter.prototype.Redraw = function() {
4458 
4459  var gaxis = this.GetObject(),
4460  x1 = Math.round(this.AxisToSvg("x", gaxis.fX1)),
4461  y1 = Math.round(this.AxisToSvg("y", gaxis.fY1)),
4462  x2 = Math.round(this.AxisToSvg("x", gaxis.fX2)),
4463  y2 = Math.round(this.AxisToSvg("y", gaxis.fY2)),
4464  w = x2 - x1, h = y1 - y2;
4465 
4466  var name = w<5 ? "yaxis" : "xaxis",
4467  kind = "normal",
4468  func = null,
4469  min = gaxis.fWmin,
4470  max = gaxis.fWmax,
4471  reverse = false;
4472 
4473  if (gaxis.fChopt.indexOf("G")>=0) {
4474  func = d3.scale.log();
4475  kind = "log";
4476  } else {
4477  func = d3.scale.linear();
4478  }
4479 
4480  func.domain([min, max]);
4481 
4482  if (name == "yaxis") {
4483  if (h > 0) {
4484  func.range([h,0]);
4485  } else {
4486  var d = y1; y1 = y2; y2 = d;
4487  h = -h; reverse = true;
4488  func.range([0,h]);
4489  }
4490  } else {
4491  if (w > 0) {
4492  func.range([0,w]);
4493  } else {
4494  var d = x1; x1 = x2; x2 = d;
4495  w = -w; reverse = true;
4496  func.range([w,0]);
4497  }
4498  }
4499 
4500  this.SetAxisConfig(name, kind, func, min, max, min, max);
4501 
4502  this.RecreateDrawG(true, "text_layer");
4503 
4504  this.DrawAxis(this.draw_g, w, h, "translate(" + x1 + "," + y2 +")", reverse);
4505  }
4506 
4507 
4508  JSROOT.drawGaxis = function(divid, obj, opt) {
4509  var painter = new JSROOT.TAxisPainter(obj, false);
4510 
4511  painter.SetDivId(divid);
4512 
4513  painter.Redraw();
4514 
4515  return painter.DrawingReady();
4516  }
4517 
4518 
4519  // =============================================================
4520 
4521  JSROOT.THistPainter = function(histo) {
4522  JSROOT.TObjectPainter.call(this, histo);
4523  this.histo = histo;
4524  this.shrink_frame_left = 0.;
4525  this.draw_content = true;
4526  this.nbinsx = 0;
4527  this.nbinsy = 0;
4528  this.x_kind = 'normal'; // 'normal', 'time', 'labels'
4529  this.y_kind = 'normal'; // 'normal', 'time', 'labels'
4530  }
4531 
4532  JSROOT.THistPainter.prototype = Object.create(JSROOT.TObjectPainter.prototype);
4533 
4534  JSROOT.THistPainter.prototype.IsDummyHisto = function() {
4535  return (this.histo==null) || !this.draw_content || (this.options.Axis>0);
4536  }
4537 
4538  JSROOT.THistPainter.prototype.IsTProfile = function() {
4539  return this.MatchObjectType('TProfile');
4540  }
4541 
4542  JSROOT.THistPainter.prototype.IsTH2Poly = function() {
4543  return this.histo && this.histo._typename.match(/^TH2Poly/);
4544  }
4545 
4546  JSROOT.THistPainter.prototype.Dimension = function() {
4547  if (!this.histo) return 0;
4548  if (this.histo._typename.indexOf("TH2")==0) return 2;
4549  if (this.histo._typename.indexOf("TH3")==0) return 3;
4550  return 1;
4551  }
4552 
4553  JSROOT.THistPainter.prototype.DecodeOptions = function(opt) {
4554 
4555  if ((opt == null) || (opt == "")) opt = this.histo['fOption'];
4556 
4557  /* decode string 'opt' and fill the option structure */
4558  var hdim = this.Dimension();
4559  var option = {
4560  Axis: 0, Bar: 0, Curve: 0, Error: 0, Hist: 0, Line: 0,
4561  Mark: 0, Fill: 0, Same: 0, Scat: 0, Func: 0, Star: 0,
4562  Arrow: 0, Box: 0, Text: 0, Char: 0, Color: 0, Contour: 0,
4563  Lego: 0, Surf: 0, Off: 0, Tri: 0, Proj: 0, AxisPos: 0,
4564  Spec: 0, Pie: 0, List: 0, Zscale: 0, FrontBox: 1, BackBox: 1,
4565  System: JSROOT.Painter.Coord.kCARTESIAN,
4566  AutoColor : 0, NoStat : 0, AutoZoom : false,
4567  HighRes: 0, Zero: 0, Logx: 0, Logy: 0, Logz: 0, Gridx: 0, Gridy: 0,
4568  Palette:0, Optimize:JSROOT.gStyle.OptimizeDraw
4569  };
4570  // check for graphical cuts
4571  var chopt = opt.toUpperCase();
4572  chopt = JSROOT.Painter.clearCuts(chopt);
4573 
4574  // if (hdim > 1) option.Scat = 1; // default was scatter plot
4575 
4576  // use error plot only when any sumw2 bigger than 0
4577  if ((hdim===1) && (this.histo.fSumw2.length > 0))
4578  for (var n=0;n<this.histo.fSumw2.length;++n)
4579  if (this.histo.fSumw2[n] > 0) { option.Error = 2; break; }
4580 
4581  if (this.histo.fFunctions !== null) option.Func = 1;
4582 
4583  var i = chopt.indexOf('PAL');
4584  if (i>=0) {
4585  var i2 = i+3;
4586  while ((i2<chopt.length) && (chopt.charCodeAt(i2)>=48) && (chopt.charCodeAt(i2)<58)) ++i2;
4587  if (i2>i+3) {
4588  option.Palette = parseInt(chopt.substring(i+3,i2));
4589  chopt = chopt.replace(chopt.substring(i,i2),"");
4590  }
4591  }
4592 
4593  if (chopt.indexOf('NOOPTIMIZE') != -1) {
4594  option.Optimize = 0;
4595  chopt = chopt.replace('NOOPTIMIZE', '');
4596  }
4597 
4598  if (chopt.indexOf('OPTIMIZE') != -1) {
4599  option.Optimize = 2;
4600  chopt = chopt.replace('OPTIMIZE', '');
4601  }
4602 
4603  if (chopt.indexOf('AUTOCOL') != -1) {
4604  option.AutoColor = 1;
4605  option.Hist = 1;
4606  chopt = chopt.replace('AUTOCOL', '');
4607  }
4608  if (chopt.indexOf('AUTOZOOM') != -1) {
4609  option.AutoZoom = 1;
4610  option.Hist = 1;
4611  chopt = chopt.replace('AUTOZOOM', '');
4612  }
4613  if (chopt.indexOf('NOSTAT') != -1) {
4614  option.NoStat = 1;
4615  chopt = chopt.replace('NOSTAT', '');
4616  }
4617  if (chopt.indexOf('LOGX') != -1) {
4618  option.Logx = 1;
4619  chopt = chopt.replace('LOGX', '');
4620  }
4621  if (chopt.indexOf('LOGY') != -1) {
4622  option.Logy = 1;
4623  chopt = chopt.replace('LOGY', '');
4624  }
4625  if (chopt.indexOf('LOGZ') != -1) {
4626  option.Logz = 1;
4627  chopt = chopt.replace('LOGZ', '');
4628  }
4629 
4630  chopt = chopt.trim();
4631  while ((chopt.length>0) && (chopt[0]==',' || chopt[0]==';')) chopt = chopt.substr(1);
4632 
4633  var nch = chopt.length;
4634  if (!nch) option.Hist = 1;
4635 
4636  var l = chopt.indexOf('SPEC');
4637  if (l != -1) {
4638  option.Scat = 0;
4639  chopt = chopt.replace('SPEC', ' ');
4640  var bs = 0;
4641  l = chopt.indexOf('BF(');
4642  if (l != -1) bs = parseInt(chopt)
4643  option.Spec = Math.max(1600, bs);
4644  return option;
4645  }
4646  if (chopt.indexOf('GL') != -1) chopt = chopt.replace('GL', ' ');
4647  if (chopt.indexOf('X+') != -1) {
4648  option.AxisPos = 10;
4649  chopt = chopt.replace('X+', ' ');
4650  }
4651  if (chopt.indexOf('Y+') != -1) {
4652  option.AxisPos += 1;
4653  chopt = chopt.replace('Y+', ' ');
4654  }
4655  if ((option.AxisPos == 10 || option.AxisPos == 1) && (nch == 2))
4656  option.Hist = 1;
4657  if (option.AxisPos == 11 && nch == 4)
4658  option.Hist = 1;
4659  if (chopt.indexOf('SAMES') != -1) {
4660  if (nch == 5) option.Hist = 1;
4661  option.Same = 2;
4662  chopt = chopt.replace('SAMES', ' ');
4663  }
4664  if (chopt.indexOf('SAME') != -1) {
4665  if (nch == 4) option.Hist = 1;
4666  option.Same = 1;
4667  chopt = chopt.replace('SAME', ' ');
4668  }
4669  if (chopt.indexOf('PIE') != -1) {
4670  option.Pie = 1;
4671  chopt = chopt.replace('PIE', ' ');
4672  }
4673  l = chopt.indexOf('LEGO');
4674  if (l != -1) {
4675  option.Scat = 0;
4676  option.Lego = 1;
4677  chopt = chopt.replace('LEGO', ' ');
4678  if (chopt[l + 4] == '1') {
4679  option.Lego = 11;
4680  chopt[l + 4] = ' ';
4681  }
4682  if (chopt[l + 4] == '2') {
4683  option.Lego = 12;
4684  chopt[l + 4] = ' ';
4685  }
4686  if (chopt[l + 4] == '3') {
4687  option.Lego = 13;
4688  chopt[l + 4] = ' ';
4689  }
4690  l = chopt.indexOf('FB');
4691  if (l != -1) {
4692  option.FrontBox = 0;
4693  chopt = chopt.replace('FB', ' ');
4694  }
4695  l = chopt.indexOf('BB');
4696  if (l != -1) {
4697  option.BackBox = 0;
4698  chopt = chopt.replace('BB', ' ');
4699  }
4700  l = chopt.indexOf('0');
4701  if (l != -1) {
4702  option.Zero = 1;
4703  chopt = chopt.replace('0', ' ');
4704  }
4705  }
4706  l = chopt.indexOf('SURF');
4707  if (l != -1) {
4708  option.Scat = 0;
4709  option.Surf = 1;
4710  chopt = chopt.replace('SURF', ' ');
4711  if (chopt[l + 4] == '1') {
4712  option.Surf = 11;
4713  chopt[l + 4] = ' ';
4714  }
4715  if (chopt[l + 4] == '2') {
4716  option.Surf = 12;
4717  chopt[l + 4] = ' ';
4718  }
4719  if (chopt[l + 4] == '3') {
4720  option.Surf = 13;
4721  chopt[l + 4] = ' ';
4722  }
4723  if (chopt[l + 4] == '4') {
4724  option.Surf = 14;
4725  chopt[l + 4] = ' ';
4726  }
4727  if (chopt[l + 4] == '5') {
4728  option.Surf = 15;
4729  chopt[l + 4] = ' ';
4730  }
4731  if (chopt[l + 4] == '6') {
4732  option.Surf = 16;
4733  chopt[l + 4] = ' ';
4734  }
4735  if (chopt[l + 4] == '7') {
4736  option.Surf = 17;
4737  chopt[l + 4] = ' ';
4738  }
4739  l = chopt.indexOf('FB');
4740  if (l != -1) {
4741  option.FrontBox = 0;
4742  chopt = chopt.replace('FB', ' ');
4743  }
4744  l = chopt.indexOf('BB');
4745  if (l != -1) {
4746  option.BackBox = 0;
4747  chopt = chopt.replace('BB', ' ');
4748  }
4749  }
4750  l = chopt.indexOf('TF3');
4751  if (l != -1) {
4752  l = chopt.indexOf('FB');
4753  if (l != -1) {
4754  option.FrontBox = 0;
4755  chopt = chopt.replace('FB', ' ');
4756  }
4757  l = chopt.indexOf('BB');
4758  if (l != -1) {
4759  option.BackBox = 0;
4760  chopt = chopt.replace('BB', ' ');
4761  }
4762  }
4763  l = chopt.indexOf('ISO');
4764  if (l != -1) {
4765  l = chopt.indexOf('FB');
4766  if (l != -1) {
4767  option.FrontBox = 0;
4768  chopt = chopt.replace('FB', ' ');
4769  }
4770  l = chopt.indexOf('BB');
4771  if (l != -1) {
4772  option.BackBox = 0;
4773  chopt = chopt.replace('BB', ' ');
4774  }
4775  }
4776  l = chopt.indexOf('LIST');
4777  if (l != -1) {
4778  option.List = 1;
4779  chopt = chopt.replace('LIST', ' ');
4780  }
4781  l = chopt.indexOf('CONT');
4782  if (l != -1) {
4783  chopt = chopt.replace('CONT', ' ');
4784  if (hdim > 1) {
4785  option.Scat = 0;
4786  option.Contour = 1;
4787  if (chopt[l + 4] == '1') {
4788  option.Contour = 11;
4789  chopt[l + 4] = ' ';
4790  }
4791  if (chopt[l + 4] == '2') {
4792  option.Contour = 12;
4793  chopt[l + 4] = ' ';
4794  }
4795  if (chopt[l + 4] == '3') {
4796  option.Contour = 13;
4797  chopt[l + 4] = ' ';
4798  }
4799  if (chopt[l + 4] == '4') {
4800  option.Contour = 14;
4801  chopt[l + 4] = ' ';
4802  }
4803  if (chopt[l + 4] == '5') {
4804  option.Contour = 15;
4805  chopt[l + 4] = ' ';
4806  }
4807  } else {
4808  option.Hist = 1;
4809  }
4810  }
4811  l = chopt.indexOf('HBAR');
4812  if (l != -1) {
4813  option.Hist = 0;
4814  option.Bar = 20;
4815  chopt = chopt.replace('HBAR', ' ');
4816  if (chopt[l + 4] == '1') {
4817  option.Bar = 21;
4818  chopt[l + 4] = ' ';
4819  }
4820  if (chopt[l + 4] == '2') {
4821  option.Bar = 22;
4822  chopt[l + 4] = ' ';
4823  }
4824  if (chopt[l + 4] == '3') {
4825  option.Bar = 23;
4826  chopt[l + 4] = ' ';
4827  }
4828  if (chopt[l + 4] == '4') {
4829  option.Bar = 24;
4830  chopt[l + 4] = ' ';
4831  }
4832  }
4833  l = chopt.indexOf('BAR');
4834  if (l != -1) {
4835  option.Hist = 0;
4836  option.Bar = 10;
4837  chopt = chopt.replace('BAR', ' ');
4838  if (chopt[l + 3] == '1') {
4839  option.Bar = 11;
4840  chopt[l + 3] = ' ';
4841  }
4842  if (chopt[l + 3] == '2') {
4843  option.Bar = 12;
4844  chopt[l + 3] = ' ';
4845  }
4846  if (chopt[l + 3] == '3') {
4847  option.Bar = 13;
4848  chopt[l + 3] = ' ';
4849  }
4850  if (chopt[l + 3] == '4') {
4851  option.Bar = 14;
4852  chopt[l + 3] = ' ';
4853  }
4854  }
4855  l = chopt.indexOf('ARR');
4856  if (l != -1) {
4857  chopt = chopt.replace('ARR', ' ');
4858  if (hdim > 1) {
4859  option.Arrow = 1;
4860  option.Scat = 0;
4861  } else {
4862  option.Hist = 1;
4863  }
4864  }
4865  l = chopt.indexOf('BOX');
4866  if (l != -1) {
4867  chopt = chopt.replace('BOX', ' ');
4868  if (hdim > 1) {
4869  option.Scat = 0;
4870  option.Box = 1;
4871  if (chopt[l + 3] == '1') {
4872  option.Box = 11;
4873  chopt[l + 3] = ' ';
4874  }
4875  } else {
4876  option.Hist = 1;
4877  }
4878  }
4879 
4880  l = chopt.indexOf('COL');
4881  if (l!=-1) {
4882  var name = 'COL';
4883 
4884  if (chopt.charAt(l+3)=='0') { option.Color = 111; name += "0"; ++l; } else
4885  if (chopt.charAt(l+3)=='1') { option.Color = 1; name += "1"; ++l; } else
4886  if (chopt.charAt(l+3)=='2') { option.Color = 2; name += "2"; ++l; } else
4887  if (chopt.charAt(l+3)=='3') { option.Color = 3; name += "3"; ++l; } else
4888  option.Color = 1;
4889 
4890  if (chopt.charAt(l+4)=='Z') { option.Zscale = 1; name += 'Z'; }
4891  chopt = chopt.replace(name, '');
4892  if (hdim == 1) {
4893  option.Hist = 1;
4894  } else {
4895  option.Scat = 0;
4896  }
4897  }
4898 
4899  if (chopt.indexOf('CHAR') != -1) {
4900  option.Char = 1;
4901  chopt = chopt.replace('CHAR', ' ');
4902  option.Scat = 0;
4903  }
4904  l = chopt.indexOf('FUNC');
4905  if (l != -1) {
4906  option.Func = 2;
4907  chopt = chopt.replace('FUNC', ' ');
4908  option.Hist = 0;
4909  }
4910  l = chopt.indexOf('HIST');
4911  if (l != -1) {
4912  option.Hist = 2;
4913  chopt = chopt.replace('HIST', ' ');
4914  option.Func = 0;
4915  option.Error = 0;
4916  }
4917  if (chopt.indexOf('AXIS') != -1) {
4918  option.Axis = 1;
4919  chopt = chopt.replace('AXIS', ' ');
4920  }
4921  if (chopt.indexOf('AXIG') != -1) {
4922  option.Axis = 2;
4923  chopt = chopt.replace('AXIG', ' ');
4924  }
4925  if (chopt.indexOf('TEXT') != -1) {
4926  var angle = parseInt(chopt);
4927  if (!isNaN(angle)) {
4928  if (angle < 0)
4929  angle = 0;
4930  if (angle > 90)
4931  angle = 90;
4932  option.Text = 1000 + angle;
4933  } else {
4934  option.Text = 1;
4935  }
4936  chopt = chopt.replace('TEXT', ' ');
4937  l = chopt.indexOf('N');
4938  if (l != -1 && this.IsTH2Poly())
4939  option.Text += 3000;
4940  option.Scat = 0;
4941  }
4942  if (chopt.indexOf('SCAT') != -1) {
4943  option.Scat = 1;
4944  chopt = chopt.replace('SCAT', ' ');
4945  }
4946  if (chopt.indexOf('POL') != -1) {
4947  option.System = JSROOT.Painter.Coord.kPOLAR;
4948  chopt = chopt.replace('POL', ' ');
4949  }
4950  if (chopt.indexOf('CYL') != -1) {
4951  option.System = JSROOT.Painter.Coord.kCYLINDRICAL;
4952  chopt = chopt.replace('CYL', ' ');
4953  }
4954  if (chopt.indexOf('SPH') != -1) {
4955  option.System = JSROOT.Painter.Coord.kSPHERICAL;
4956  chopt = chopt.replace('SPH', ' ');
4957  }
4958  l = chopt.indexOf('PSR');
4959  if (l != -1) {
4960  option.System = JSROOT.Painter.Coord.kRAPIDITY;
4961  chopt = chopt.replace('PSR', ' ');
4962  }
4963  l = chopt.indexOf('TRI');
4964  if (l != -1) {
4965  option.Scat = 0;
4966  option.Color = 0;
4967  option.Tri = 1;
4968  chopt = chopt.replace('TRI', ' ');
4969  l = chopt.indexOf('FB');
4970  if (l != -1) {
4971  option.FrontBox = 0;
4972  chopt = chopt.replace('FB', ' ');
4973  }
4974  l = chopt.indexOf('BB');
4975  if (l != -1) {
4976  option.BackBox = 0;
4977  chopt = chopt.replace('BB', ' ');
4978  }
4979  l = chopt.indexOf('ERR');
4980  if (l != -1)
4981  chopt = chopt.replace('ERR', ' ');
4982  }
4983  l = chopt.indexOf('AITOFF');
4984  if (l != -1) {
4985  option.Proj = 1;
4986  chopt = chopt.replace('AITOFF', ' '); // Aitoff projection
4987  }
4988  l = chopt.indexOf('MERCATOR');
4989  if (l != -1) {
4990  option.Proj = 2;
4991  chopt = chopt.replace('MERCATOR', ' '); // Mercator projection
4992  }
4993  l = chopt.indexOf('SINUSOIDAL');
4994  if (l != -1) {
4995  option.Proj = 3;
4996  chopt = chopt.replace('SINUSOIDAL', ' '); // Sinusoidal projection
4997  }
4998  l = chopt.indexOf('PARABOLIC');
4999  if (l != -1) {
5000  option.Proj = 4;
5001  chopt = chopt.replace('PARABOLIC', ' '); // Parabolic projection
5002  }
5003  if (option.Proj > 0) {
5004  option.Scat = 0;
5005  option.Contour = 14;
5006  }
5007  if (chopt.indexOf('A') != -1)
5008  option.Axis = -1;
5009  if (chopt.indexOf('B') != -1)
5010  option.Bar = 1;
5011  if (chopt.indexOf('C') != -1) {
5012  option.Curve = 1;
5013  option.Hist = -1;
5014  }
5015  if (chopt.indexOf('F') != -1)
5016  option.Fill = 1;
5017  if (chopt.indexOf('][') != -1) {
5018  option.Off = 1;
5019  option.Hist = 1;
5020  }
5021  if (chopt.indexOf('F2') != -1) option.Fill = 2;
5022  if (chopt.indexOf('L') != -1) {
5023  option.Line = 1;
5024  option.Hist = -1;
5025  }
5026 
5027  if (chopt.indexOf('P') != -1) {
5028  option.Mark = 1;
5029  option.Hist = -1;
5030  if (chopt.indexOf('P0') != -1) option.Mark = 10;
5031  }
5032  if (chopt.indexOf('Z') != -1) option.Zscale = 1;
5033  if (chopt.indexOf('*') != -1) option.Star = 1;
5034  if (chopt.indexOf('H') != -1) option.Hist = 2;
5035  if (this.IsTH2Poly()) {
5036  if (option.Fill + option.Line + option.Mark != 0) option.Scat = 0;
5037  }
5038 
5039  if (chopt.indexOf('E') != -1) {
5040  if (hdim == 1) {
5041  option.Error = 1;
5042  if (chopt.indexOf('E0') != -1) option.Error = 10;
5043  if (chopt.indexOf('E1') != -1) option.Error = 11;
5044  if (chopt.indexOf('E2') != -1) option.Error = 12;
5045  if (chopt.indexOf('E3') != -1) option.Error = 13;
5046  if (chopt.indexOf('E4') != -1) option.Error = 14;
5047  if (chopt.indexOf('E5') != -1) option.Error = 15;
5048  if (chopt.indexOf('E6') != -1) option.Error = 16;
5049  if (chopt.indexOf('X0') != -1) {
5050  if (option.Error == 1) option.Error += 20;
5051  option.Error += 10;
5052  }
5053  if (option.Text && this.IsTProfile()) {
5054  option.Text += 2000;
5055  option.Error = 0;
5056  }
5057  } else {
5058  if (option.Error == 0) {
5059  option.Error = 100;
5060  option.Scat = 0;
5061  }
5062  if (option.Text) {
5063  option.Text += 2000;
5064  option.Error = 0;
5065  }
5066  }
5067  }
5068  if (chopt.indexOf('9') != -1) option.HighRes = 1;
5069  if (option.Surf == 15) {
5070  if (option.System == JSROOT.Painter.Coord.kPOLAR
5071  || option.System == JSROOT.Painter.Coord.kCARTESIAN) {
5072  option.Surf = 13;
5073  // Warning('MakeChopt','option SURF5 is not supported in Cartesian
5074  // and Polar modes');
5075  }
5076  }
5077 
5078  // Check options incompatibilities
5079  if (option.Bar == 1) option.Hist = -1;
5080 
5081  return option;
5082  }
5083 
5084  JSROOT.THistPainter.prototype.GetAutoColor = function(col) {
5085  if (this.options.AutoColor<=0) return col;
5086 
5087  var id = this.options.AutoColor;
5088  this.options.AutoColor = id % 8 + 1;
5089  return JSROOT.Painter.root_colors[id];
5090  }
5091 
5092  JSROOT.THistPainter.prototype.ScanContent = function() {
5093  // function will be called once new histogram or
5094  // new histogram content is assigned
5095  // one should find min,max,nbins, maxcontent values
5096 
5097  alert("HistPainter.prototype.ScanContent not implemented");
5098  }
5099 
5100  JSROOT.THistPainter.prototype.CheckPadOptions = function() {
5101 
5102  this.fillatt = this.createAttFill(this.histo, undefined, undefined, 1);
5103 
5104  this.lineatt = JSROOT.Painter.createAttLine(this.histo);
5105  var main = this.main_painter();
5106  if (main!==null) this.lineatt.color = main.GetAutoColor(this.lineatt.color);
5107 
5108  var pad = this.root_pad();
5109 
5110  if (pad!=null) {
5111  // Copy options from current pad
5112  this.options.Logx = pad.fLogx;
5113  this.options.Logy = pad.fLogy;
5114  this.options.Logz = pad.fLogz;
5115  this.options.Gridx = pad.fGridx;
5116  this.options.Gridy = pad.fGridy;
5117  }
5118 
5119  if (this.main_painter() !== this) return;
5120 
5121  this.zoom_xmin = this.zoom_xmax = 0;
5122  this.zoom_ymin = this.zoom_ymax = 0;
5123  this.zoom_zmin = this.zoom_zmax = 0;
5124 
5125  if ((pad==null) || !('fUxmin' in pad) || this.create_canvas) return;
5126 
5127  var min = pad.fUxmin, max = pad.fUxmax;
5128 
5129  // first check that non-default values are there
5130  if ((this.Dimension() < 3) && ((min !== 0) || (max !== 1))) {
5131  if (pad.fLogx > 0) {
5132  min = Math.exp(min * Math.log(10));
5133  max = Math.exp(max * Math.log(10));
5134  }
5135 
5136  if (min !== this.histo.fXaxis.fXmin || max !== this.histo.fXaxis.fXmax)
5137  if (min >= this.histo.fXaxis.fXmin && max <= this.histo.fXaxis.fXmax) {
5138  // set zoom values if only inside range
5139  this.zoom_xmin = min;
5140  this.zoom_xmax = max;
5141  }
5142  }
5143 
5144  min = pad.fUymin; max = pad.fUymax;
5145 
5146  if ((this.Dimension() == 2) && ((min !== 0) || (max !== 1))) {
5147  if (pad.fLogy > 0) {
5148  min = Math.exp(min * Math.log(10));
5149  max = Math.exp(max * Math.log(10));
5150  }
5151 
5152  if (min !== this.histo.fYaxis.fXmin || max !== this.histo.fYaxis.fXmax)
5153  if (min >= this.histo.fYaxis.fXmin && max <= this.histo.fYaxis.fXmax) {
5154  // set zoom values if only inside range
5155  this.zoom_ymin = min;
5156  this.zoom_ymax = max;
5157  }
5158  }
5159  }
5160 
5161  JSROOT.THistPainter.prototype.UpdateObject = function(obj) {
5162  if (!this.MatchObjectType(obj)) {
5163  alert("JSROOT.THistPainter.UpdateObject - wrong class " + obj._typename + " expected " + this.histo._typename);
5164  return false;
5165  }
5166 
5167  // TODO: simple replace of object does not help - one can have different
5168  // complex relations between histo and stat box, histo and colz axis,
5169  // one could have THStack or TMultiGraph object
5170  // The only that could be done is update of content
5171 
5172  // this.histo = obj;
5173 
5174  var histo = this.GetObject();
5175 
5176  histo.fEntries = obj.fEntries;
5177  histo.fTsumw = obj.fTsumw;
5178  histo.fTsumwx = obj.fTsumwx;
5179  histo.fTsumwx2 = obj.fTsumwx2;
5180  if (this.Dimension() == 2) {
5181  histo.fTsumwy = obj.fTsumwy;
5182  histo.fTsumwy2 = obj.fTsumwy2;
5183  histo.fTsumwxy = obj.fTsumwxy;
5184  }
5185  histo.fArray = obj.fArray;
5186  histo.fNcells = obj.fNcells;
5187  histo.fTitle = obj.fTitle;
5188  histo.fMinimum = obj.fMinimum;
5189  histo.fMaximum = obj.fMaximum;
5190  histo.fXaxis.fNbins = obj.fXaxis.fNbins;
5191  histo.fXaxis.fXmin = obj.fXaxis.fXmin;
5192  histo.fXaxis.fXmax = obj.fXaxis.fXmax;
5193  histo.fYaxis.fXmin = obj.fYaxis.fXmin;
5194  histo.fYaxis.fXmax = obj.fYaxis.fXmax;
5195  histo.fSumw2 = obj.fSumw2;
5196 
5197  if (this.IsTProfile()) {
5198  histo.fBinEntries = obj.fBinEntries;
5199  }
5200 
5201  this.ScanContent();
5202 
5203  return true;
5204  }
5205 
5206  JSROOT.THistPainter.prototype.CreateAxisFuncs = function(with_y_axis, with_z_axis) {
5207  // here functions are defined to convert index to axis value and back
5208  // introduced to support non-equidistant bins
5209 
5210  this.xmin = this.histo.fXaxis.fXmin;
5211  this.xmax = this.histo.fXaxis.fXmax;
5212 
5213  if (this.histo.fXaxis.fXbins.length == this.nbinsx+1) {
5214  this.regularx = false;
5215  this.GetBinX = function(bin) {
5216  var indx = Math.round(bin);
5217  if (indx <= 0) return this.xmin;
5218  if (indx > this.nbinsx) this.xmax;
5219  if (indx==bin) return this.histo.fXaxis.fXbins[indx];
5220  var indx2 = (bin < indx) ? indx - 1 : indx + 1;
5221  return this.histo.fXaxis.fXbins[indx] * Math.abs(bin-indx2) + this.histo.fXaxis.fXbins[indx2] * Math.abs(bin-indx);
5222  };
5223  this.GetIndexX = function(x,add) {
5224  for (var k = 1; k < this.histo.fXaxis.fXbins.length; ++k)
5225  if (x < this.histo.fXaxis.fXbins[k]) return Math.floor(k-1+add);
5226  return this.nbinsx;
5227  };
5228  } else {
5229  this.regularx = true;
5230  this.binwidthx = (this.xmax - this.xmin);
5231  if (this.nbinsx > 0)
5232  this.binwidthx = this.binwidthx / this.nbinsx;
5233 
5234  this.GetBinX = function(bin) { return this.xmin + bin*this.binwidthx; };
5235  this.GetIndexX = function(x,add) { return Math.floor((x - this.xmin) / this.binwidthx + add); };
5236  }
5237 
5238  this.ymin = this.histo.fYaxis.fXmin;
5239  this.ymax = this.histo.fYaxis.fXmax;
5240 
5241  if (!with_y_axis || (this.nbinsy==0)) return;
5242 
5243  if (this.histo.fYaxis.fXbins.length == this.nbinsy+1) {
5244  this.regulary = false;
5245  this.GetBinY = function(bin) {
5246  var indx = Math.round(bin);
5247  if (indx <= 0) return this.ymin;
5248  if (indx > this.nbinsy) this.ymax;
5249  if (indx==bin) return this.histo.fYaxis.fXbins[indx];
5250  var indx2 = (bin < indx) ? indx - 1 : indx + 1;
5251  return this.histo.fYaxis.fXbins[indx] * Math.abs(bin-indx2) + this.histo.fYaxis.fXbins[indx2] * Math.abs(bin-indx);
5252  };
5253  this.GetIndexY = function(y,add) {
5254  for (var k = 1; k < this.histo.fYaxis.fXbins.length; ++k)
5255  if (y < this.histo.fYaxis.fXbins[k]) return Math.floor(k-1+add);
5256  return this.nbinsy;
5257  };
5258  } else {
5259  this.regulary = true;
5260  this.binwidthy = (this.ymax - this.ymin);
5261  if (this.nbinsy > 0)
5262  this.binwidthy = this.binwidthy / this.nbinsy;
5263 
5264  this.GetBinY = function(bin) { return this.ymin+bin*this.binwidthy; };
5265  this.GetIndexY = function(y,add) { return Math.floor((y - this.ymin) / this.binwidthy + add); };
5266  }
5267 
5268  if (!with_z_axis || (this.nbinsz==0)) return;
5269 
5270  if (this.histo.fZaxis.fXbins.length == this.nbinsz+1) {
5271  this.regularz = false;
5272  this.GetBinZ = function(bin) {
5273  var indx = Math.round(bin);
5274  if (indx <= 0) return this.zmin;
5275  if (indx > this.nbinsz) this.zmax;
5276  if (indx==bin) return this.histo.fZaxis.fXbins[indx];
5277  var indx2 = (bin < indx) ? indx - 1 : indx + 1;
5278  return this.histo.fZaxis.fXbins[indx] * Math.abs(bin-indx2) + this.histo.fZaxis.fXbins[indx2] * Math.abs(bin-indx);
5279  };
5280  this.GetIndexZ = function(z,add) {
5281  for (var k = 1; k < this.histo.fZaxis.fXbins.length; ++k)
5282  if (z < this.histo.fZaxis.fXbins[k]) return Math.floor(k-1+add);
5283  return this.nbinsz;
5284  };
5285  } else {
5286  this.regularz = true;
5287  this.binwidthz = (this.zmax - this.zmin);
5288  if (this.nbinsz > 0)
5289  this.binwidthz = this.binwidthz / this.nbinsz;
5290 
5291  this.GetBinZ = function(bin) { return this.zmin+bin*this.binwidthz; };
5292  this.GetIndexZ = function(z,add) { return Math.floor((z - this.zmin) / this.binwidthz + add); };
5293  }
5294  }
5295 
5296 
5297  JSROOT.THistPainter.prototype.CreateXY = function() {
5298  // here we create x,y objects which maps our physical coordnates into pixels
5299  // while only first painter really need such object, all others just reuse it
5300  // following functions are introduced
5301  // this.GetBin[X/Y] return bin coordinate
5302  // this.Convert[X/Y] converts root value in JS date when date scale is used
5303  // this.[x,y] these are d3.scale objects
5304  // this.gr[x,y] converts root scale into graphical value
5305  // this.Revert[X/Y] converts graphical coordinates to root scale value
5306 
5307  if (!this.is_main_painter()) {
5308  this.x = this.main_painter().x;
5309  this.y = this.main_painter().y;
5310  return;
5311  }
5312 
5313  var w = this.frame_width(), h = this.frame_height();
5314 
5315  if (this.histo.fXaxis.fTimeDisplay) {
5316  this.x_kind = 'time';
5317  this.timeoffsetx = JSROOT.Painter.getTimeOffset(this.histo.fXaxis);
5318  this.ConvertX = function(x) { return new Date(this.timeoffsetx + x*1000); };
5319  this.RevertX = function(grx) { return (this.x.invert(grx) - this.timeoffsetx) / 1000; };
5320  } else {
5321  this.x_kind = (this.histo.fXaxis.fLabels==null) ? 'normal' : 'labels';
5322  this.ConvertX = function(x) { return x; };
5323  this.RevertX = function(grx) { return this.x.invert(grx); };
5324  }
5325 
5326  this.scale_xmin = this.xmin;
5327  this.scale_xmax = this.xmax;
5328  if (this.zoom_xmin != this.zoom_xmax) {
5329  this.scale_xmin = this.zoom_xmin;
5330  this.scale_xmax = this.zoom_xmax;
5331  }
5332  if (this.x_kind == 'time') {
5333  this.x = d3.time.scale();
5334  } else
5335  if (this.options.Logx) {
5336  if (this.scale_xmax <= 0) this.scale_xmax = 0;
5337 
5338  if ((this.scale_xmin <= 0) && (this.nbinsx>0))
5339  for (var i=0;i<this.nbinsx;++i) {
5340  this.scale_xmin = Math.max(this.scale_xmin, this.GetBinX(i));
5341  if (this.scale_xmin>0) break;
5342  }
5343 
5344  if ((this.scale_xmin <= 0) || (this.scale_xmin >= this.scale_xmax)) {
5345  this.scale_xmin = this.scale_xmax * 0.0001;
5346  }
5347 
5348  this.x = d3.scale.log();
5349  } else {
5350  this.x = d3.scale.linear();
5351  }
5352 
5353  this.x.domain([this.ConvertX(this.scale_xmin), this.ConvertX(this.scale_xmax)]).range([ 0, w ]);
5354 
5355  if (this.x_kind == 'time') {
5356  // we emulate scale functionality
5357  this.grx = function(val) { return this.x(this.ConvertX(val)); }
5358  } else
5359  if (this.options.Logx) {
5360  this.grx = function(val) { return (val < this.scale_xmin) ? -5 : this.x(val); }
5361  } else {
5362  this.grx = this.x;
5363  }
5364 
5365  this.scale_ymin = this.ymin;
5366  this.scale_ymax = this.ymax;
5367  if (this.zoom_ymin != this.zoom_ymax) {
5368  this.scale_ymin = this.zoom_ymin;
5369  this.scale_ymax = this.zoom_ymax;
5370  }
5371 
5372  if (this.histo.fYaxis.fTimeDisplay) {
5373  this.y_kind = 'time';
5374  this.timeoffsety = JSROOT.Painter.getTimeOffset(this.histo.fYaxis);
5375  this.ConvertY = function(y) { return new Date(this.timeoffsety + y*1000); };
5376  this.RevertY = function(gry) { return (this.y.invert(gry) - this.timeoffsety) / 1000; };
5377  } else {
5378  this.y_kind = ((this.Dimension()==2) && (this.histo.fYaxis.fLabels!=null)) ? 'labels' : 'normal';
5379  this.ConvertY = function(y) { return y; };
5380  this.RevertY = function(gry) { return this.y.invert(gry); };
5381  }
5382 
5383  if (this.options.Logy) {
5384  if (this.scale_ymax <= 0)
5385  this.scale_ymax = 1;
5386  else
5387  if ((this.zoom_ymin === this.zoom_ymax) && (this.Dimension()==1))
5388  this.scale_ymax*=1.8;
5389 
5390  if ((this.scale_ymin <= 0) && (this.nbinsy>0))
5391  for (var i=0;i<this.nbinsy;++i) {
5392  this.scale_ymin = Math.max(this.scale_ymin, this.GetBinY(i));
5393  if (this.scale_ymin>0) break;
5394  }
5395 
5396  if ((this.scale_ymin <= 0) && ('ymin_nz' in this) && (this.ymin_nz > 0))
5397  this.scale_ymin = 0.3*this.ymin_nz;
5398 
5399  if ((this.scale_ymin <= 0) || (this.scale_ymin >= this.scale_ymax))
5400  this.scale_ymin = 0.000001 * this.scale_ymax;
5401  this.y = d3.scale.log();
5402  } else
5403  if (this.y_kind=='time') {
5404  this.y = d3.time.scale();
5405  } else {
5406  this.y = d3.scale.linear()
5407  }
5408 
5409  this.y.domain([ this.ConvertY(this.scale_ymin), this.ConvertY(this.scale_ymax) ]).range([ h, 0 ]);
5410 
5411  if (this.y_kind=='time') {
5412  // we emulate scale functionality
5413  this.gry = function(val) { return this.y(this.ConvertY(val)); }
5414  } else
5415  if (this.options.Logy) {
5416  // make protecttion for log
5417  this.gry = function(val) { return (val < this.scale_ymin) ? h+5 : this.y(val); }
5418  } else {
5419  this.gry = this.y;
5420  }
5421  }
5422 
5423  JSROOT.THistPainter.prototype.DrawGrids = function() {
5424  // grid can only be drawn by first painter
5425  if (!this.is_main_painter()) return;
5426 
5427  var layer = this.svg_frame().select(".grid_layer");
5428 
5429  layer.selectAll(".xgrid").remove();
5430  layer.selectAll(".ygrid").remove();
5431  /* add a grid on x axis, if the option is set */
5432 
5433  // add a grid on x axis, if the option is set
5434  if (this.options.Gridx && this.x_handle) {
5435 
5436  var h = this.frame_height();
5437 
5438  layer.selectAll(".xgrid")
5439  .data(this.x_handle.ticks).enter()
5440  .append("svg:line")
5441  .attr("class", "xgrid")
5442  .attr("x1", function(d) { return d; })
5443  .attr("y1", h)
5444  .attr("x2", function(d) { return d; })
5445  .attr("y2",0)
5446  .style("stroke", "black")
5447  .style("stroke-width", 1)
5448  .style("stroke-dasharray", JSROOT.Painter.root_line_styles[11]);
5449  }
5450 
5451  // add a grid on y axis, if the option is set
5452  if (this.options.Gridy && this.y_handle) {
5453  var w = this.frame_width();
5454 
5455  layer.selectAll('.ygrid')
5456  .data(this.y_handle.ticks).enter()
5457  .append("svg:line")
5458  .attr("class", "ygrid")
5459  .attr("x1", 0)
5460  .attr("y1", function(d) { return d; })
5461  .attr("x2", w)
5462  .attr("y2", function(d) { return d; })
5463  .style("stroke", "black")
5464  .style("stroke-width", 1)
5465  .style("stroke-dasharray", JSROOT.Painter.root_line_styles[11]);
5466  }
5467  }
5468 
5469  JSROOT.THistPainter.prototype.DrawBins = function() {
5470  alert("HistPainter.DrawBins not implemented");
5471  }
5472 
5473  JSROOT.THistPainter.prototype.AxisAsText = function(axis, value) {
5474  if (axis == "x") {
5475  if (this.x_kind == 'time')
5476  value = this.ConvertX(value);
5477 
5478  if (this.x_handle!==null)
5479  if ('format' in this.x_handle)
5480  return this.x_handle.format(value);
5481 
5482  return value.toPrecision(4);
5483  }
5484 
5485  if (axis == "y") {
5486  if (this.y_kind == 'time')
5487  value = this.ConvertY(value);
5488 
5489  if (this.y_handle!==null)
5490  if ('format' in this.y_handle)
5491  return this.y_handle.format(value);
5492 
5493  return value.toPrecision(4);
5494  }
5495 
5496  return value.toPrecision(4);
5497  }
5498 
5499  JSROOT.THistPainter.prototype.DrawAxes = function(shrink_forbidden) {
5500  // axes can be drawn only for main histogram
5501 
5502  if (!this.is_main_painter()) return;
5503 
5504  var layer = this.svg_frame().select(".axis_layer"),
5505  w = this.frame_width(),
5506  h = this.frame_height();
5507 
5508  this.x_handle = new JSROOT.TAxisPainter(this.histo.fXaxis, true);
5509  this.x_handle.SetDivId(this.divid, -1);
5510 
5511  this.x_handle.SetAxisConfig("xaxis",
5512  (this.options.Logx && this.x_kind !== "time") ? "log" : this.x_kind,
5513  this.x, this.xmin, this.xmax, this.scale_xmin, this.scale_xmax);
5514 
5515  this.x_handle.DrawAxis(layer, w, h, "translate(0," + h + ")");
5516 
5517  this.y_handle = new JSROOT.TAxisPainter(this.histo.fYaxis, true);
5518  this.y_handle.SetDivId(this.divid, -1);
5519 
5520  this.y_handle.SetAxisConfig("yaxis",
5521  (this.options.Logy && this.y_kind !== "time") ? "log" : this.y_kind,
5522  this.y, this.ymin, this.ymax, this.scale_ymin, this.scale_ymax);
5523 
5524  this.y_handle.DrawAxis(layer, w, h);
5525 
5526  if (shrink_forbidden) return;
5527 
5528  var shrink = 0., ypos = this.y_handle.position;
5529 
5530  if (ypos < 0) {
5531  shrink = -ypos/w + 0.001;
5532  this.shrink_frame_left += shrink;
5533  } else
5534  if ((this.shrink_frame_left > 0) && (ypos/w > this.shrink_frame_left)) {
5535  shrink = -this.shrink_frame_left;
5536  this.shrink_frame_left = 0.;
5537  }
5538 
5539  if (shrink != 0) {
5540  this.frame_painter().Shrink(shrink, 0);
5541  this.frame_painter().Redraw();
5542  this.CreateXY();
5543  this.DrawAxes(true);
5544  }
5545  }
5546 
5547  JSROOT.THistPainter.prototype.DrawTitle = function() {
5548 
5549  // case when histogram drawn over other histogram (same option)
5550  if (!this.is_main_painter()) return;
5551 
5552  var tpainter = this.FindPainterFor(null, "title");
5553  var pavetext = (tpainter !== null) ? tpainter.GetObject() : null;
5554  if (pavetext === null) pavetext = this.FindInPrimitives("title");
5555  if ((pavetext !== null) && (pavetext._typename !== "TPaveText")) pavetext = null;
5556 
5557  var draw_title = !this.histo.TestBit(JSROOT.TH1StatusBits.kNoTitle);
5558 
5559  if (pavetext !== null) {
5560  pavetext.Clear();
5561  if (draw_title)
5562  pavetext.AddText(this.histo.fTitle);
5563  } else
5564  if (draw_title && (tpainter === null)) {
5565  pavetext = JSROOT.Create("TPaveText");
5566 
5567  JSROOT.extend(pavetext, { fName: "title", fX1NDC: 0.28, fY1NDC: 0.94, fX2NDC: 0.72, fY2NDC: 0.99 } );
5568  pavetext.AddText(this.histo.fTitle);
5569 
5570  JSROOT.Painter.drawPaveText(this.divid, pavetext);
5571  }
5572  }
5573 
5574  JSROOT.THistPainter.prototype.ToggleStat = function(arg) {
5575 
5576  var stat = this.FindStat(), statpainter = null;
5577 
5578  if (stat == null) {
5579  if (arg=='only-check') return false;
5580  // when statbox created first time, one need to draw it
5581  stat = this.CreateStat();
5582  } else {
5583  statpainter = this.FindPainterFor(stat);
5584  }
5585 
5586  if (arg=='only-check') return statpainter ? statpainter.Enabled : false;
5587 
5588  if (statpainter) {
5589  statpainter.Enabled = !statpainter.Enabled;
5590  // when stat box is drawed, it always can be draw individualy while it
5591  // should be last for colz RedrawPad is used
5592  statpainter.Redraw();
5593  return statpainter.Enabled;
5594  }
5595 
5596  JSROOT.draw(this.divid, stat, "onpad:" + this.pad_name);
5597 
5598  return true;
5599  }
5600 
5601  JSROOT.THistPainter.prototype.IsAxisZoomed = function(axis) {
5602  var obj = this.main_painter();
5603  if (obj == null) obj = this;
5604  if (axis === "x") return obj.zoom_xmin != obj.zoom_xmax;
5605  if (axis === "y") return obj.zoom_ymin != obj.zoom_ymax;
5606  return false;
5607  }
5608 
5609  JSROOT.THistPainter.prototype.GetSelectIndex = function(axis, size, add) {
5610  // be aware - here indexs starts from 0
5611  var indx = 0, obj = this.main_painter();
5612  if (obj == null) obj = this;
5613  var nbin = this['nbins'+axis];
5614  if (!nbin) nbin = 0;
5615  if (!add) add = 0;
5616 
5617  var func = 'GetIndex' + axis.toUpperCase(),
5618  min = obj['zoom_' + axis + 'min'],
5619  max = obj['zoom_' + axis + 'max'];
5620 
5621  if ((min != max) && (func in this)) {
5622  if (size == "left") {
5623  indx = this[func](min, add);
5624  } else {
5625  indx = this[func](max, add + 0.5);
5626  }
5627  } else {
5628  indx = (size == "left") ? 0 : nbin;
5629  }
5630 
5631  var taxis; // TAxis object of histogram, where user range can be stored
5632  if (this.histo) taxis = this.histo["f" + axis.toUpperCase() + "axis"];
5633  if (taxis) {
5634  if ((taxis.fFirst === taxis.fLast) || !taxis.TestBit(JSROOT.EAxisBits.kAxisRange) ||
5635  ((taxis.fFirst<=1) && (taxis.fLast>=nbin))) taxis = undefined;
5636  }
5637 
5638  if (size == "left") {
5639  if (indx < 0) indx = 0;
5640  if (taxis && taxis.fFirst>1 && (indx<taxis.fFirst)) indx = taxis.fFirst-1;
5641  } else {
5642  if (indx > nbin) indx = nbin;
5643  if (taxis && (taxis.fLast <= nbin) && (indx>taxis.fLast)) indx = taxis.fLast;
5644  }
5645 
5646  return indx;
5647  }
5648 
5649  JSROOT.THistPainter.prototype.FindStat = function() {
5650  if (this.histo.fFunctions !== null)
5651  for (var i = 0; i < this.histo.fFunctions.arr.length; ++i) {
5652  var func = this.histo.fFunctions.arr[i];
5653 
5654  if ((func._typename == 'TPaveStats') &&
5655  (func.fName == 'stats')) return func;
5656  }
5657 
5658  return null;
5659  }
5660 
5661  JSROOT.THistPainter.prototype.CreateStat = function() {
5662 
5663  if (!this.draw_content) return null;
5664 
5665  var stats = this.FindStat();
5666  if (stats != null) return stats;
5667 
5668  stats = JSROOT.Create('TPaveStats');
5669  JSROOT.extend(stats, { fName : 'stats',
5670  fOptStat: JSROOT.gStyle.OptStat,
5671  fOptFit: JSROOT.gStyle.OptFit,
5672  fBorderSize : 1} );
5673  JSROOT.extend(stats, JSROOT.gStyle.StatNDC);
5674  JSROOT.extend(stats, JSROOT.gStyle.StatText);
5675  JSROOT.extend(stats, JSROOT.gStyle.StatFill);
5676 
5677  if (this.histo._typename.match(/^TProfile/) || this.histo._typename.match(/^TH2/))
5678  stats.fY1NDC = 0.67;
5679 
5680  stats.AddText(this.histo.fName);
5681 
5682  if (this.histo.fFunctions === null)
5683  this.histo.fFunctions = JSROOT.Create("TList");
5684 
5685  this.histo.fFunctions.Add(stats,"");
5686 
5687  return stats;
5688  }
5689 
5690  JSROOT.THistPainter.prototype.FindF1 = function() {
5691  // search for TF1 object in list of functions, it is fitted function
5692  if (this.histo.fFunctions == null) return null;
5693  for (var i = 0; i < this.histo.fFunctions.arr.length; ++i) {
5694  var func = this.histo.fFunctions.arr[i];
5695  if (func._typename == 'TF1') return func;
5696  }
5697  return null;
5698  }
5699 
5700  JSROOT.THistPainter.prototype.DrawNextFunction = function(indx, callback) {
5701  // method draws next function from the functions list
5702 
5703  if (this.options.Same || (this.histo.fFunctions === null) || (indx >= this.histo.fFunctions.arr.length))
5704  return JSROOT.CallBack(callback);
5705 
5706  var func = this.histo.fFunctions.arr[indx],
5707  opt = this.histo.fFunctions.opt[indx],
5708  do_draw = false,
5709  func_painter = this.FindPainterFor(func);
5710 
5711  // no need to do something if painter for object was already done
5712  // object will be redraw automatically
5713  if (func_painter === null) {
5714  if (func._typename == 'TPaveText' || func._typename == 'TPaveStats') {
5715  do_draw = !this.histo.TestBit(JSROOT.TH1StatusBits.kNoStats) && (this.options.NoStat!=1);
5716  } else
5717  if (func._typename == 'TF1') {
5718  do_draw = !func.TestBit(JSROOT.BIT(9));
5719  } else
5720  do_draw = true;
5721  } else
5722  if (('CompleteDraw' in func_painter) && (typeof func_painter.CompleteDraw == 'function'))
5723  func_painter.CompleteDraw();
5724 
5725  if (do_draw) {
5726  var painter = JSROOT.draw(this.divid, func, opt);
5727  if (painter) return painter.WhenReady(this.DrawNextFunction.bind(this, indx+1, callback));
5728  }
5729 
5730  this.DrawNextFunction(indx+1, callback);
5731  }
5732 
5733  JSROOT.THistPainter.prototype.UnzoomUserRange = function(dox, doy, doz) {
5734 
5735  if (!this.histo) return false;
5736 
5737  function UnzoomTAxis(obj) {
5738  if (!obj) return false;
5739  if (!obj.TestBit(JSROOT.EAxisBits.kAxisRange)) return false;
5740  if (obj.fFirst === obj.fLast) return false;
5741  if ((obj.fFirst <= 1) && (obj.fLast >= obj.fNbins)) return false;
5742  obj.InvertBit(JSROOT.EAxisBits.kAxisRange);
5743  return true;
5744  }
5745 
5746  var res = false;
5747 
5748  if (dox) res |= UnzoomTAxis(this.histo.fXaxis);
5749  if (doy) res |= UnzoomTAxis(this.histo.fYaxis);
5750  if (doz) res |= UnzoomTAxis(this.histo.fZaxis);
5751 
5752  return res;
5753  }
5754 
5755  JSROOT.THistPainter.prototype.ToggleLog = function(axis) {
5756  var obj = this.main_painter();
5757  if (!obj) obj = this;
5758 
5759  var curr = obj.options["Log" + axis];
5760  // do not allow log scale for labels
5761  if (!curr && (this[axis+"_kind"] == "labels")) return;
5762  obj.options["Log" + axis] = curr ? 0 : 1;
5763  obj.RedrawPad();
5764  }
5765 
5766  JSROOT.THistPainter.prototype.Zoom = function(xmin, xmax, ymin, ymax, zmin, zmax) {
5767  // function can be used for zooming into specified range
5768  // if both ranges for axis 0 (like xmin==xmax==0), axis will be unzoomed
5769 
5770  var main = this.main_painter(),
5771  zoom_x = (xmin !== xmax), zoom_y = (ymin !== ymax), zoom_z = (zmin !== zmax),
5772  unzoom_x = false, unzoom_y = false, unzoom_z = false;
5773 
5774  if (zoom_x) {
5775  var cnt = 0;
5776  if (xmin <= main.xmin) { xmin = main.xmin; cnt++; }
5777  if (xmax >= main.xmax) { xmax = main.xmax; cnt++; }
5778  if (cnt === 2) { zoom_x = false; unzoom_x = true; }
5779  } else {
5780  unzoom_x = (xmin === xmax) && (xmin === 0);
5781  }
5782 
5783  if (zoom_y) {
5784  var cnt = 0;
5785  if (ymin <= main.ymin) { ymin = main.ymin; cnt++; }
5786  if (ymax >= main.ymax) { ymax = main.ymax; cnt++; }
5787  if (cnt === 2) { zoom_y = false; unzoom_y = true; }
5788  } else {
5789  unzoom_y = (ymin === ymax) && (ymin === 0);
5790  }
5791 
5792  if (zoom_z) {
5793  var cnt = 0;
5794  if (zmin <= main.zmin) { zmin = main.zmin; cnt++; }
5795  if (zmax >= main.zmax) { zmax = main.zmax; cnt++; }
5796  if (cnt === 2) { zoom_z = false; unzoom_z = true; }
5797  } else {
5798  unzoom_z = (zmin === zmax) && (zmin === 0);
5799  }
5800 
5801  var changed = false;
5802 
5803  // first process zooming (if any)
5804  if (zoom_x || zoom_y || zoom_z)
5805  main.ForEachPainter(function(obj) {
5806  if (zoom_x && obj.CanZoomIn("x", xmin, xmax)) {
5807  main.zoom_xmin = xmin;
5808  main.zoom_xmax = xmax;
5809  changed = true;
5810  zoom_x = false;
5811  }
5812  if (zoom_y && obj.CanZoomIn("y", ymin, ymax)) {
5813  main.zoom_ymin = ymin;
5814  main.zoom_ymax = ymax;
5815  changed = true;
5816  zoom_y = false;
5817  }
5818  if (zoom_z && obj.CanZoomIn("z", zmin, zmax)) {
5819  main.zoom_zmin = zmin;
5820  main.zoom_zmax = zmax;
5821  changed = true;
5822  zoom_z = false;
5823  }
5824  });
5825 
5826  // and process unzoom, if any
5827  if (unzoom_x || unzoom_y || unzoom_z) {
5828  if (unzoom_x) {
5829  if (main.zoom_xmin !== main.zoom_xmax) changed = true;
5830  main.zoom_xmin = main.zoom_xmax = 0;
5831  }
5832  if (unzoom_y) {
5833  if (main.zoom_ymin !== main.zoom_ymax) changed = true;
5834  main.zoom_ymin = main.zoom_ymax = 0;
5835  }
5836  if (unzoom_z) {
5837  if (main.zoom_zmin !== main.zoom_zmax) changed = true;
5838  main.zoom_zmin = main.zoom_zmax = 0;
5839  }
5840 
5841  // first try to unzoom main painter - it could have user range specified
5842  if (!changed) {
5843  changed = main.UnzoomUserRange(unzoom_x, unzoom_y, unzoom_z);
5844 
5845  // than try to unzoom all overlapped objects
5846  var pp = this.pad_painter(true);
5847  if (pp && pp.painters)
5848  pp.painters.forEach(function(paint){
5849  if (paint && (paint!==main) && (typeof paint.UnzoomUserRange == 'function'))
5850  if (paint.UnzoomUserRange(unzoom_x, unzoom_y, unzoom_z)) changed = true;
5851  });
5852  }
5853  }
5854 
5855  if (changed) this.RedrawPad();
5856 
5857  return changed;
5858  }
5859 
5860  JSROOT.THistPainter.prototype.Unzoom = function(dox, doy, doz) {
5861  if (typeof dox === 'undefined') { dox = true; doy = true; doz = true; } else
5862  if (typeof dox === 'string') { doz = dox.indexOf("z")>=0; doy = dox.indexOf("y")>=0; dox = dox.indexOf("x")>=0; }
5863 
5864  return this.Zoom(dox ? 0 : undefined, dox ? 0 : undefined,
5865  doy ? 0 : undefined, doy ? 0 : undefined,
5866  doz ? 0 : undefined, doz ? 0 : undefined);
5867  }
5868 
5869  JSROOT.THistPainter.prototype.clearInteractiveElements = function() {
5870  JSROOT.Painter.closeMenu();
5871  if (this.zoom_rect != null) { this.zoom_rect.remove(); this.zoom_rect = null; }
5872  this.zoom_kind = 0;
5873 
5874  // enable tooltip in frame painter
5875  this.SwitchTooltip(true);
5876  }
5877 
5878  JSROOT.THistPainter.prototype.mouseDoubleClick = function() {
5879  d3.event.preventDefault();
5880  var m = d3.mouse(this.svg_frame().node());
5881  this.clearInteractiveElements();
5882  var kind = "xyz";
5883  if (m[0] < 0) kind = "y"; else
5884  if (m[1] > this.frame_height()) kind = "x";
5885  this.Unzoom(kind);
5886  }
5887 
5888  JSROOT.THistPainter.prototype.startRectSel = function() {
5889  // ignore when touch selection is actiavated
5890 
5891  if (this.zoom_kind > 100) return;
5892 
5893  // ignore all events from non-left button
5894  if ((d3.event.which || d3.event.button) !== 1) return;
5895 
5896  d3.event.preventDefault();
5897 
5898  this.clearInteractiveElements();
5899  this.zoom_origin = d3.mouse(this.svg_frame().node());
5900 
5901  this.zoom_curr = [ Math.max(0, Math.min(this.frame_width(), this.zoom_origin[0])),
5902  Math.max(0, Math.min(this.frame_height(), this.zoom_origin[1])) ];
5903 
5904  if (this.zoom_origin[0] < 0) {
5905  this.zoom_kind = 3; // only y
5906  this.zoom_origin[0] = 0;
5907  this.zoom_origin[1] = this.zoom_curr[1];
5908  this.zoom_curr[0] = this.frame_width();
5909  this.zoom_curr[1] += 1;
5910  } else if (this.zoom_origin[1] > this.frame_height()) {
5911  this.zoom_kind = 2; // only x
5912  this.zoom_origin[0] = this.zoom_curr[0];
5913  this.zoom_origin[1] = 0;
5914  this.zoom_curr[0] += 1;
5915  this.zoom_curr[1] = this.frame_height();
5916  } else {
5917  this.zoom_kind = 1; // x and y
5918  this.zoom_origin[0] = this.zoom_curr[0];
5919  this.zoom_origin[1] = this.zoom_curr[1];
5920  }
5921 
5922  d3.select(window).on("mousemove.zoomRect", this.moveRectSel.bind(this))
5923  .on("mouseup.zoomRect", this.endRectSel.bind(this), true);
5924 
5925  this.zoom_rect = null;
5926 
5927  // disable tooltips in frame painter
5928  this.SwitchTooltip(false);
5929 
5930  d3.event.stopPropagation();
5931  }
5932 
5933  JSROOT.THistPainter.prototype.moveRectSel = function() {
5934 
5935  if ((this.zoom_kind == 0) || (this.zoom_kind > 100)) return;
5936 
5937  d3.event.preventDefault();
5938  var m = d3.mouse(this.svg_frame().node());
5939 
5940  m[0] = Math.max(0, Math.min(this.frame_width(), m[0]));
5941  m[1] = Math.max(0, Math.min(this.frame_height(), m[1]));
5942 
5943  switch (this.zoom_kind) {
5944  case 1: this.zoom_curr[0] = m[0]; this.zoom_curr[1] = m[1]; break;
5945  case 2: this.zoom_curr[0] = m[0]; break;
5946  case 3: this.zoom_curr[1] = m[1]; break;
5947  }
5948 
5949  if (this.zoom_rect===null)
5950  this.zoom_rect = this.svg_frame()
5951  .append("rect")
5952  .attr("class", "zoom")
5953  .attr("pointer-events","none");
5954 
5955  this.zoom_rect.attr("x", Math.min(this.zoom_origin[0], this.zoom_curr[0]))
5956  .attr("y", Math.min(this.zoom_origin[1], this.zoom_curr[1]))
5957  .attr("width", Math.abs(this.zoom_curr[0] - this.zoom_origin[0]))
5958  .attr("height", Math.abs(this.zoom_curr[1] - this.zoom_origin[1]));
5959  }
5960 
5961  JSROOT.THistPainter.prototype.endRectSel = function() {
5962  if ((this.zoom_kind == 0) || (this.zoom_kind > 100)) return;
5963 
5964  d3.event.preventDefault();
5965 
5966  d3.select(window).on("mousemove.zoomRect", null)
5967  .on("mouseup.zoomRect", null);
5968 
5969  var m = d3.mouse(this.svg_frame().node());
5970 
5971  m[0] = Math.max(0, Math.min(this.frame_width(), m[0]));
5972  m[1] = Math.max(0, Math.min(this.frame_height(), m[1]));
5973 
5974  switch (this.zoom_kind) {
5975  case 1: this.zoom_curr[0] = m[0]; this.zoom_curr[1] = m[1]; break;
5976  case 2: this.zoom_curr[0] = m[0]; break; // only X
5977  case 3: this.zoom_curr[1] = m[1]; break; // only Y
5978  }
5979 
5980  var xmin, xmax, ymin, ymax, isany = false;
5981 
5982  if ((this.zoom_kind != 3) && (Math.abs(this.zoom_curr[0] - this.zoom_origin[0]) > 10)) {
5983  xmin = Math.min(this.RevertX(this.zoom_origin[0]), this.RevertX(this.zoom_curr[0]));
5984  xmax = Math.max(this.RevertX(this.zoom_origin[0]), this.RevertX(this.zoom_curr[0]));
5985  isany = true;
5986  }
5987 
5988  if ((this.zoom_kind != 2) && (Math.abs(this.zoom_curr[1] - this.zoom_origin[1]) > 10)) {
5989  ymin = Math.min(this.RevertY(this.zoom_origin[1]), this.RevertY(this.zoom_curr[1]));
5990  ymax = Math.max(this.RevertY(this.zoom_origin[1]), this.RevertY(this.zoom_curr[1]));
5991  isany = true;
5992  }
5993 
5994  this.clearInteractiveElements();
5995 
5996  if (isany) this.Zoom(xmin, xmax, ymin, ymax);
5997  }
5998 
5999  JSROOT.THistPainter.prototype.startTouchZoom = function() {
6000  // in case when zooming was started, block any other kind of events
6001  if (this.zoom_kind != 0) {
6002  d3.event.preventDefault();
6003  d3.event.stopPropagation();
6004  return;
6005  }
6006 
6007  var arr = d3.touches(this.svg_frame().node());
6008  this.touch_cnt+=1;
6009 
6010  // normally double-touch will be handled
6011  // touch with single click used for context menu
6012  if (arr.length == 1) {
6013  // this is touch with single element
6014 
6015  var now = new Date();
6016  var diff = now.getTime() - this.last_touch.getTime();
6017  this.last_touch = now;
6018 
6019  if ((diff < 300) && (this.zoom_curr != null)
6020  && (Math.abs(this.zoom_curr[0] - arr[0][0]) < 30)
6021  && (Math.abs(this.zoom_curr[1] - arr[0][1]) < 30)) {
6022 
6023  d3.event.preventDefault();
6024  d3.event.stopPropagation();
6025 
6026  this.clearInteractiveElements();
6027  this.Unzoom("xyz");
6028 
6029  this.last_touch = new Date(0);
6030 
6031  this.svg_frame().on("touchcancel", null)
6032  .on("touchend", null, true);
6033  } else
6034  if (JSROOT.gStyle.ContextMenu) {
6035  this.zoom_curr = arr[0];
6036  this.svg_frame().on("touchcancel", this.endTouchSel.bind(this))
6037  .on("touchend", this.endTouchSel.bind(this));
6038  d3.event.preventDefault();
6039  d3.event.stopPropagation();
6040  }
6041  }
6042 
6043  if (arr.length != 2) return;
6044 
6045  d3.event.preventDefault();
6046  d3.event.stopPropagation();
6047 
6048  this.clearInteractiveElements();
6049 
6050  this.svg_frame().on("touchcancel", null)
6051  .on("touchend", null);
6052 
6053  var pnt1 = arr[0], pnt2 = arr[1];
6054 
6055  this.zoom_curr = [ Math.min(pnt1[0], pnt2[0]), Math.min(pnt1[1], pnt2[1]) ];
6056  this.zoom_origin = [ Math.max(pnt1[0], pnt2[0]), Math.max(pnt1[1], pnt2[1]) ];
6057 
6058  if (this.zoom_curr[0] < 0) {
6059  this.zoom_kind = 103; // only y
6060  this.zoom_curr[0] = 0;
6061  this.zoom_origin[0] = this.frame_width();
6062  } else if (this.zoom_origin[1] > this.frame_height()) {
6063  this.zoom_kind = 102; // only x
6064  this.zoom_curr[1] = 0;
6065  this.zoom_origin[1] = this.frame_height();
6066  } else {
6067  this.zoom_kind = 101; // x and y
6068  }
6069 
6070  this.SwitchTooltip(false);
6071 
6072  this.zoom_rect = this.svg_frame().append("rect")
6073  .attr("class", "zoom")
6074  .attr("id", "zoomRect")
6075  .attr("x", this.zoom_curr[0])
6076  .attr("y", this.zoom_curr[1])
6077  .attr("width", this.zoom_origin[0] - this.zoom_curr[0])
6078  .attr("height", this.zoom_origin[1] - this.zoom_curr[1]);
6079 
6080  d3.select(window).on("touchmove.zoomRect", this.moveTouchSel.bind(this))
6081  .on("touchcancel.zoomRect", this.endTouchSel.bind(this))
6082  .on("touchend.zoomRect", this.endTouchSel.bind(this));
6083  }
6084 
6085  JSROOT.THistPainter.prototype.moveTouchSel = function() {
6086  if (this.zoom_kind < 100) return;
6087 
6088  d3.event.preventDefault();
6089 
6090  var arr = d3.touches(this.svg_frame().node());
6091 
6092  if (arr.length != 2)
6093  return this.clearInteractiveElements();
6094 
6095  var pnt1 = arr[0], pnt2 = arr[1];
6096 
6097  if (this.zoom_kind != 103) {
6098  this.zoom_curr[0] = Math.min(pnt1[0], pnt2[0]);
6099  this.zoom_origin[0] = Math.max(pnt1[0], pnt2[0]);
6100  }
6101  if (this.zoom_kind != 102) {
6102  this.zoom_curr[1] = Math.min(pnt1[1], pnt2[1]);
6103  this.zoom_origin[1] = Math.max(pnt1[1], pnt2[1]);
6104  }
6105 
6106  this.zoom_rect.attr("x", this.zoom_curr[0])
6107  .attr("y", this.zoom_curr[1])
6108  .attr("width", this.zoom_origin[0] - this.zoom_curr[0])
6109  .attr("height", this.zoom_origin[1] - this.zoom_curr[1]);
6110 
6111  if ((this.zoom_origin[0] - this.zoom_curr[0] > 10)
6112  || (this.zoom_origin[1] - this.zoom_curr[1] > 10))
6113  this.SwitchTooltip(false);
6114 
6115  d3.event.stopPropagation();
6116  }
6117 
6118  JSROOT.THistPainter.prototype.endTouchSel = function() {
6119 
6120  this.svg_frame().on("touchcancel", null)
6121  .on("touchend", null);
6122 
6123  if (this.zoom_kind === 0) {
6124  // special case - single touch can ends up with context menu
6125 
6126  d3.event.preventDefault();
6127 
6128  var now = new Date();
6129 
6130  var diff = now.getTime() - this.last_touch.getTime();
6131 
6132  if ((diff > 500) && (diff<2000) && !this.frame_painter().IsTooltipShown()) {
6133  this.ShowContextMenu('main', { clientX: this.zoom_curr[0], clientY: this.zoom_curr[1] });
6134  this.last_touch = new Date(0);
6135  } else {
6136  this.clearInteractiveElements();
6137  }
6138  }
6139 
6140  if (this.zoom_kind < 100) return;
6141 
6142  d3.event.preventDefault();
6143  d3.select(window).on("touchmove.zoomRect", null)
6144  .on("touchend.zoomRect", null)
6145  .on("touchcancel.zoomRect", null);
6146 
6147  var xmin, xmax, ymin, ymax, isany = false;
6148 
6149  if ((this.zoom_kind != 103) && (Math.abs(this.zoom_curr[0] - this.zoom_origin[0]) > 10)) {
6150  xmin = Math.min(this.RevertX(this.zoom_origin[0]), this.RevertX(this.zoom_curr[0]));
6151  xmax = Math.max(this.RevertX(this.zoom_origin[0]), this.RevertX(this.zoom_curr[0]));
6152  isany = true;
6153  }
6154 
6155  if ((this.zoom_kind != 102) && (Math.abs(this.zoom_curr[1] - this.zoom_origin[1]) > 10)) {
6156  ymin = Math.min(this.RevertY(this.zoom_origin[1]), this.RevertY(this.zoom_curr[1]));
6157  ymax = Math.max(this.RevertY(this.zoom_origin[1]), this.RevertY(this.zoom_curr[1]));
6158  isany = true;
6159  }
6160 
6161  this.clearInteractiveElements();
6162  this.last_touch = new Date(0);
6163 
6164  if (isany) this.Zoom(xmin, xmax, ymin, ymax);
6165 
6166  d3.event.stopPropagation();
6167  }
6168 
6169  JSROOT.THistPainter.prototype.mouseWheel = function() {
6170  d3.event.stopPropagation();
6171 
6172  var delta = 0;
6173  switch (d3.event.deltaMode) {
6174  case 0: delta = d3.event.deltaY / this.pad_height() * 2; break; // DOM_DELTA_PIXEL
6175  case 1: delta = d3.event.deltaY / this.pad_height() * 40; break; // DOM_DELTA_LINE
6176  case 2: delta = d3.event.deltaY / this.pad_height() * 200; break; // DOM_DELTA_PAGE
6177  }
6178  if (delta===0) return;
6179 
6180  d3.event.preventDefault();
6181 
6182  this.clearInteractiveElements();
6183 
6184  if (delta < -0.2) delta = -0.2; else if (delta>0.2) delta = 0.2;
6185 
6186  var xmin = this.scale_xmin, xmax = this.scale_xmax, ymin = undefined, ymax = undefined;
6187 
6188  if ((xmin === xmax) && (delta<0)) { xmin = this.xmin; xmax = this.xmax; }
6189 
6190  var cur = d3.mouse(this.svg_frame().node());
6191 
6192  if (xmin < xmax) {
6193  var dmin = cur[0] / this.frame_width();
6194  if ((dmin>0) && (dmin<1)) {
6195  if (this.options.Logx) {
6196  var factor = (xmin>0) ? JSROOT.log10(xmax/xmin) : 2;
6197  if (factor>10) factor = 10; else if (factor<1.5) factor = 1.5;
6198  xmin = xmin / Math.pow(factor, delta*dmin);
6199  xmax = xmax * Math.pow(factor, delta*(1-dmin));
6200  } else {
6201  var rx = (xmax - xmin);
6202  if (delta>0) rx = 1.001 * rx / (1-delta);
6203  xmin += -delta*dmin*rx;
6204  xmax -= -delta*(1-dmin)*rx;
6205  }
6206  if (xmin >= xmax) xmin = xmax = undefined;
6207  } else {
6208  xmin = xmax = undefined;
6209  }
6210  }
6211 
6212  if ((this.Dimension() > 1) || (cur[0] < 0)) {
6213  ymin = this.scale_ymin; ymax = this.scale_ymax;
6214 
6215  if ((ymin === ymax) && (delta<0)) { ymin = this.ymin; ymax = this.ymax; }
6216 
6217  var dmin = 1 - cur[1] / this.frame_height();
6218 
6219  if ((ymin < ymax) && (dmin>0) && (dmin<1)) {
6220  if (this.options.Logy) {
6221  var factor = (ymin>0) ? JSROOT.log10(ymax/ymin) : 2;
6222  if (factor>10) factor = 10; else if (factor<1.5) factor = 1.5;
6223  ymin = ymin / Math.pow(factor, delta*dmin);
6224  ymax = ymax * Math.pow(factor, delta*(1-dmin));
6225  } else {
6226  var ry = (ymax - ymin);
6227  if (delta>0) ry = 1.001 * ry / (1-delta);
6228  ymin += -delta*dmin*ry;
6229  ymax -= -delta*(1-dmin)*ry;
6230  }
6231  if (ymin >= ymax) ymin = ymax = undefined;
6232  } else {
6233  ymin = ymax = undefined;
6234  }
6235  }
6236 
6237  this.Zoom(xmin,xmax,ymin,ymax);
6238  }
6239 
6240  JSROOT.THistPainter.prototype.AddInteractive = function() {
6241  // only first painter in list allowed to add interactive functionality to the frame
6242 
6243  if ((!JSROOT.gStyle.Zooming && !JSROOT.gStyle.ContextMenu) || !this.is_main_painter()) return;
6244 
6245  this.last_touch = new Date(0);
6246  this.zoom_kind = 0; // 0 - none, 1 - XY, 2 - only X, 3 - only Y, (+100 for touches)
6247  this.zoom_rect = null;
6248  this.zoom_origin = null; // original point where zooming started
6249  this.zoom_curr = null; // current point for zomming
6250  this.touch_cnt = 0;
6251 
6252  if (JSROOT.gStyle.Zooming) {
6253  this.svg_frame().on("mousedown", this.startRectSel.bind(this) );
6254  this.svg_frame().on("dblclick", this.mouseDoubleClick.bind(this) );
6255  this.svg_frame().on("wheel", this.mouseWheel.bind(this) );
6256  }
6257 
6258  if (JSROOT.touches && (JSROOT.gStyle.Zooming || JSROOT.gStyle.ContextMenu))
6259  this.svg_frame().on("touchstart", this.startTouchZoom.bind(this) );
6260 
6261  if (JSROOT.gStyle.ContextMenu) {
6262  if (JSROOT.touches) {
6263  this.svg_frame().selectAll(".xaxis_container")
6264  .on("touchstart", this.startTouchMenu.bind(this,"x") );
6265  this.svg_frame().selectAll(".yaxis_container")
6266  .on("touchstart", this.startTouchMenu.bind(this,"y") );
6267  }
6268  this.svg_frame().on("contextmenu", this.ShowContextMenu.bind(this) );
6269  this.svg_frame().selectAll(".xaxis_container")
6270  .on("contextmenu", this.ShowContextMenu.bind(this,"x"));
6271  this.svg_frame().selectAll(".yaxis_container")
6272  .on("contextmenu", this.ShowContextMenu.bind(this,"y"));
6273  }
6274  }
6275 
6276  JSROOT.THistPainter.prototype.ShowContextMenu = function(kind, evnt, obj) {
6277  // ignore context menu when touches zooming is ongoing
6278  if (('zoom_kind' in this) && (this.zoom_kind > 100)) return;
6279 
6280  // this is for debug purposes only, when context menu is where, close is and show normal menu
6281  //if (!evnt && !kind && document.getElementById('root_ctx_menu')) {
6282  // var elem = document.getElementById('root_ctx_menu');
6283  // elem.parentNode.removeChild(elem);
6284  // return;
6285  //}
6286 
6287  var menu_painter = this, frame_corner = false; // object used to show context menu
6288 
6289  if (!evnt) {
6290  d3.event.preventDefault();
6291  d3.event.stopPropagation(); // disable main context menu
6292  evnt = d3.event;
6293 
6294  if (kind === undefined) {
6295  var ms = d3.mouse(this.svg_frame().node());
6296  var tch = d3.touches(this.svg_frame().node());
6297  var pnt = null, pp = this.pad_painter(true), fp = this.frame_painter(), sel = null;
6298 
6299  if (tch.length === 1) pnt = { x: tch[0][0], y: tch[0][1], touch: true }; else
6300  if (ms.length === 2) pnt = { x: ms[0], y: ms[1], touch: false };
6301 
6302  if ((pnt !== null) && (pp !== null)) {
6303  pnt.painters = true; // assign painter for every tooltip
6304  var hints = pp.GetTooltips(pnt);
6305 
6306  var bestdist = 1000;
6307  for (var n=0;n<hints.length;++n)
6308  if (hints[n] && hints[n].menu) {
6309  var dist = ('menu_dist' in hints[n]) ? hints[n].menu_dist : 7;
6310  if (dist < bestdist) { sel = hints[n].painter; bestdist = dist; }
6311  }
6312  }
6313 
6314  if (sel!==null) menu_painter = sel; else
6315  if (fp!==null) kind = "frame";
6316 
6317  if (pnt!==null) frame_corner = (pnt.x>0) && (pnt.x<20) && (pnt.y>0) && (pnt.y<20);
6318  }
6319  }
6320 
6321  // one need to copy event, while after call back event may be changed
6322  menu_painter.ctx_menu_evnt = evnt;
6323 
6324  JSROOT.Painter.createMenu(function(menu) {
6325  menu.painter = this; // in all menu callbacks painter will be 'this' pointer
6326  var domenu = this.FillContextMenu(menu, kind, obj);
6327 
6328  // fill frame menu by default - or append frame elements when actiavted in the frame corner
6329  if (fp && (!domenu || (frame_corner && (kind!=="frame"))))
6330  domenu = fp.FillContextMenu(menu);
6331 
6332  if (domenu) {
6333  // suppress any running zomming
6334  this.SwitchTooltip(false);
6335  menu.show(this.ctx_menu_evnt, this.SwitchTooltip.bind(this, true) );
6336  }
6337 
6338 
6339  delete this.ctx_menu_evnt; // delete temporary variable
6340  }.bind(menu_painter) ); // end menu creation
6341  }
6342 
6343 
6344  JSROOT.THistPainter.prototype.ChangeUserRange = function(arg) {
6345  var taxis = this.histo['f'+arg+"axis"];
6346  if (!taxis) return;
6347 
6348  var curr = "[1," + taxis.fNbins+"]";
6349  if (taxis.TestBit(JSROOT.EAxisBits.kAxisRange))
6350  curr = "[" +taxis.fFirst+"," + taxis.fLast+"]";
6351 
6352  var res = prompt("Enter user range for axis " + arg + " like [1," + taxis.fNbins + "]", curr);
6353  if (res==null) return;
6354  res = JSON.parse(res);
6355 
6356  if (!res || (res.length!=2) || isNaN(res[0]) || isNaN(res[1])) return;
6357  taxis.fFirst = parseInt(res[0]);
6358  taxis.fLast = parseInt(res[1]);
6359 
6360  var newflag = (taxis.fFirst < taxis.fLast) && (taxis.fFirst >= 1) && (taxis.fLast<=taxis.fNbins);
6361  if (newflag != taxis.TestBit(JSROOT.EAxisBits.kAxisRange))
6362  taxis.InvertBit(JSROOT.EAxisBits.kAxisRange);
6363 
6364  this.Redraw();
6365  }
6366 
6367  JSROOT.THistPainter.prototype.FillContextMenu = function(menu, kind, obj) {
6368 
6369  // when fill and show context menu, remove all zooming
6370  this.clearInteractiveElements();
6371 
6372  if ((kind=="x") || (kind=="y") || (kind=="z")) {
6373  var faxis = this.histo.fXaxis;
6374  if (kind=="y") faxis = this.histo.fYaxis; else
6375  if (kind=="z") faxis = obj ? obj : this.histo.fZaxis;
6376  menu.add("header: " + kind.toUpperCase() + " axis");
6377  menu.add("Unzoom", this.Unzoom.bind(this, kind));
6378  menu.addchk(this.options["Log" + kind], "SetLog"+kind, this.ToggleLog.bind(this, kind) );
6379  menu.addchk(faxis.TestBit(JSROOT.EAxisBits.kMoreLogLabels), "More log",
6380  function() { faxis.InvertBit(JSROOT.EAxisBits.kMoreLogLabels); this.RedrawPad(); });
6381  menu.addchk(faxis.TestBit(JSROOT.EAxisBits.kNoExponent), "No exponent",
6382  function() { faxis.InvertBit(JSROOT.EAxisBits.kNoExponent); this.RedrawPad(); });
6383  if (faxis != null) {
6384  menu.add("sub:Labels");
6385  menu.addchk(faxis.TestBit(JSROOT.EAxisBits.kCenterLabels), "Center",
6386  function() { faxis.InvertBit(JSROOT.EAxisBits.kCenterLabels); this.RedrawPad(); });
6387  this.AddColorMenuEntry(menu, "Color", faxis.fLabelColor,
6388  function(arg) { faxis.fLabelColor = parseInt(arg); this.RedrawPad(); });
6389  this.AddSizeMenuEntry(menu,"Offset", 0, 0.1, 0.01, faxis.fLabelOffset,
6390  function(arg) { faxis.fLabelOffset = parseFloat(arg); this.RedrawPad(); } );
6391  this.AddSizeMenuEntry(menu,"Size", 0.02, 0.11, 0.01, faxis.fLabelSize,
6392  function(arg) { faxis.fLabelSize = parseFloat(arg); this.RedrawPad(); } );
6393  menu.add("endsub:");
6394  menu.add("sub:Title");
6395  menu.add("SetTitle", function() {
6396  var t = prompt("Enter axis title", faxis.fTitle);
6397  if (t!==null) { faxis.fTitle = t; this.RedrawPad(); }
6398  });
6399  menu.addchk(faxis.TestBit(JSROOT.EAxisBits.kCenterTitle), "Center",
6400  function() { faxis.InvertBit(JSROOT.EAxisBits.kCenterTitle); this.RedrawPad(); });
6401  menu.addchk(faxis.TestBit(JSROOT.EAxisBits.kRotateTitle), "Rotate",
6402  function() { faxis.InvertBit(JSROOT.EAxisBits.kRotateTitle); this.RedrawPad(); });
6403  this.AddColorMenuEntry(menu, "Color", faxis.fTitleColor,
6404  function(arg) { faxis.fTitleColor = parseInt(arg); this.RedrawPad(); });
6405  this.AddSizeMenuEntry(menu,"Offset", 0, 3, 0.2, faxis.fTitleOffset,
6406  function(arg) { faxis.fTitleOffset = parseFloat(arg); this.RedrawPad(); } );
6407  this.AddSizeMenuEntry(menu,"Size", 0.02, 0.11, 0.01, faxis.fTitleSize,
6408  function(arg) { faxis.fTitleSize = parseFloat(arg); this.RedrawPad(); } );
6409  menu.add("endsub:");
6410  }
6411  menu.add("sub:Ticks");
6412  this.AddColorMenuEntry(menu, "Color", faxis.fLineColor,
6413  function(arg) { faxis.fLineColor = parseInt(arg); this.RedrawPad(); });
6414  this.AddColorMenuEntry(menu, "Color", faxis.fAxisColor,
6415  function(arg) { faxis.fAxisColor = parseInt(arg); this.RedrawPad(); });
6416  this.AddSizeMenuEntry(menu,"Size", -0.05, 0.055, 0.01, faxis.fTickLength,
6417  function(arg) { faxis.fTickLength = parseFloat(arg); this.RedrawPad(); } );
6418  menu.add("endsub:");
6419  return true;
6420  }
6421 
6422  if (kind == "frame") {
6423  var fp = this.frame_painter();
6424  if (fp) return fp.FillContextMenu(menu);
6425  }
6426 
6427  menu.add("header:"+ this.histo._typename + "::" + this.histo.fName);
6428 
6429  if (this.draw_content) {
6430  menu.addchk(this.ToggleStat('only-check'), "Show statbox", function() { this.ToggleStat(); });
6431  if (this.Dimension() == 1) {
6432  menu.add("User range X", "X", this.ChangeUserRange);
6433  } else {
6434  menu.add("sub:User ranges");
6435  menu.add("X", "X", this.ChangeUserRange);
6436  menu.add("Y", "Y", this.ChangeUserRange);
6437  if (this.Dimension() > 2)
6438  menu.add("Z", "Z", this.ChangeUserRange);
6439  menu.add("endsub:")
6440  }
6441  }
6442 
6443  if ('FillHistContextMenu' in this)
6444  this.FillHistContextMenu(menu);
6445 
6446  this.FillAttContextMenu(menu);
6447 
6448  return true;
6449  }
6450 
6451  JSROOT.THistPainter.prototype.ButtonClick = function(funcname) {
6452  if (!this.is_main_painter()) return false;
6453  switch(funcname) {
6454  case "ToggleZoom":
6455  if ((this.zoom_xmin !== this.zoom_xmax) || (this.zoom_ymin !== this.zoom_ymax) || (this.zoom_zmin !== this.zoom_zmax)) {
6456  this.Unzoom();
6457  return true;
6458  }
6459  if (this.draw_content && ('AutoZoom' in this) && (this.Dimension() < 3)) {
6460  this.AutoZoom();
6461  return true;
6462  }
6463  break;
6464  case "ToggleLogX": this.ToggleLog("x"); break;
6465  case "ToggleLogY": this.ToggleLog("y"); break;
6466  case "ToggleLogZ": this.ToggleLog("z"); break;
6467  case "ToggleStatBox": this.ToggleStat(); return true; break;
6468  }
6469  return false;
6470  }
6471 
6472  JSROOT.THistPainter.prototype.FillToolbar = function() {
6473  var pp = this.pad_painter(true);
6474  if (pp===null) return;
6475 
6476  pp.AddButton(JSROOT.ToolbarIcons.auto_zoom, 'Toggle between unzoom and autozoom-in', 'ToggleZoom');
6477  pp.AddButton(JSROOT.ToolbarIcons.arrow_right, "Toggle log x", "ToggleLogX");
6478  pp.AddButton(JSROOT.ToolbarIcons.arrow_up, "Toggle log y", "ToggleLogY");
6479  if (this.Dimension() > 1)
6480  pp.AddButton(JSROOT.ToolbarIcons.arrow_diag, "Toggle log z", "ToggleLogZ");
6481  if (this.draw_content)
6482  pp.AddButton(JSROOT.ToolbarIcons.statbox, 'Toggle stat box', "ToggleStatBox");
6483  }
6484 
6485  // ======= TH1 painter================================================
6486 
6487  JSROOT.TH1Painter = function(histo) {
6488  JSROOT.THistPainter.call(this, histo);
6489  }
6490 
6491  JSROOT.TH1Painter.prototype = Object.create(JSROOT.THistPainter.prototype);
6492 
6493  JSROOT.TH1Painter.prototype.ScanContent = function() {
6494  // from here we analyze object content
6495  // therefore code will be moved
6496 
6497  var hmin = 0, hmin_nz = 0, hmax = 0, hsum = 0;
6498 
6499  var profile = this.IsTProfile();
6500 
6501  this.nbinsx = this.histo.fXaxis.fNbins;
6502  this.nbinsy = 0;
6503 
6504  for (var i = 0; i < this.nbinsx; ++i) {
6505  var value = this.histo.getBinContent(i + 1), err = 0;
6506  hsum += profile ? this.histo.fBinEntries[i + 1] : value;
6507  if (value > 0)
6508  if ((hmin_nz == 0) || (value<hmin_nz)) hmin_nz = value;
6509  if (this.options.Error > 0) err = this.histo.getBinError(i + 1);
6510  if (i == 0) {
6511  hmin = value - err; hmax = value + err;
6512  } else {
6513  hmin = Math.min(hmin, value - err);
6514  hmax = Math.max(hmax, value + err);
6515  }
6516  }
6517 
6518  // account overflow/underflow bins
6519  if (profile)
6520  hsum += this.histo.fBinEntries[0] + this.histo.fBinEntries[this.nbinsx + 1];
6521  else
6522  hsum += this.histo.getBinContent(0) + this.histo.getBinContent(this.nbinsx + 1);
6523 
6524  this.stat_entries = hsum;
6525  if (this.histo.fEntries>1) this.stat_entries = this.histo.fEntries;
6526 
6527  this.CreateAxisFuncs(false);
6528 
6529  this.hmin = hmin;
6530  this.hmax = hmax;
6531 
6532  this.ymin_nz = hmin_nz; // value can be used to show optimal log scale
6533 
6534  if ((this.nbinsx == 0) || ((Math.abs(hmin) < 1e-300 && Math.abs(hmax) < 1e-300))) {
6535  this.draw_content = false;
6536  hmin = this.ymin;
6537  hmax = this.ymax;
6538  } else {
6539  this.draw_content = true;
6540  }
6541 
6542  if (hmin >= hmax) {
6543  if (hmin == 0) { this.ymin = 0; this.ymax = 1; } else
6544  if (hmin < 0) { this.ymin = 2 * hmin; this.ymax = 0; }
6545  else { this.ymin = 0; this.ymax = hmin * 2; }
6546  } else {
6547  var dy = (hmax - hmin) * 0.05;
6548  this.ymin = hmin - dy;
6549  if ((this.ymin < 0) && (hmin >= 0)) this.ymin = 0;
6550  this.ymax = hmax + dy;
6551  }
6552 
6553  hmin = hmax = null;
6554  var set_zoom = false;
6555  if (this.histo.fMinimum != -1111) {
6556  hmin = this.histo.fMinimum;
6557  if (hmin < this.ymin)
6558  this.ymin = hmin;
6559  else
6560  set_zoom = true;
6561  }
6562  if (this.histo.fMaximum != -1111) {
6563  hmax = this.histo.fMaximum;
6564  if (hmax > this.ymax)
6565  this.ymax = hmax;
6566  else
6567  set_zoom = true;
6568  }
6569 
6570  if (set_zoom) {
6571  this.zoom_ymin = (hmin == null) ? this.ymin : hmin;
6572  this.zoom_ymax = (hmax == null) ? this.ymax : hmax;
6573  }
6574 
6575  // apply selected user range if no other range selection was done
6576  if (this.is_main_painter() && (this.zoom_xmin === this.zoom_xmax) &&
6577  this.histo.fXaxis.TestBit(JSROOT.EAxisBits.kAxisRange) &&
6578  (this.histo.fXaxis.fFirst !== this.histo.fXaxis.fLast) &&
6579  ((this.histo.fXaxis.fFirst>1) || (this.histo.fXaxis.fLast <= this.nbinsx))) {
6580  this.zoom_xmin = this.histo.fXaxis.fFirst > 1 ? this.GetBinX(this.histo.fXaxis.fFirst-1) : this.xmin;
6581  this.zoom_xmax = this.histo.fXaxis.fLast <= this.nbinsx ? this.GetBinX(this.histo.fXaxis.fLast) : this.xmax;
6582  }
6583 
6584  // If no any draw options specified, do not try draw histogram
6585  if (this.options.Bar == 0 && this.options.Hist == 0
6586  && this.options.Error == 0 && this.options.Same == 0) {
6587  this.draw_content = false;
6588  }
6589  if (this.options.Axis > 0) { // Paint histogram axis only
6590  this.draw_content = false;
6591  }
6592  }
6593 
6594  JSROOT.TH1Painter.prototype.CountStat = function(cond) {
6595  var profile = this.IsTProfile();
6596 
6597  var stat_sumw = 0, stat_sumwx = 0, stat_sumwx2 = 0, stat_sumwy = 0, stat_sumwy2 = 0;
6598 
6599  var left = this.GetSelectIndex("x", "left");
6600  var right = this.GetSelectIndex("x", "right");
6601 
6602  var xx = 0, w = 0, xmax = null, wmax = null;
6603 
6604  for (var i = left; i < right; ++i) {
6605  xx = this.GetBinX(i+0.5);
6606 
6607  if ((cond!=null) && !cond(xx)) continue;
6608 
6609  if (profile) {
6610  w = this.histo.fBinEntries[i + 1];
6611  stat_sumwy += this.histo.fArray[i + 1];
6612  stat_sumwy2 += this.histo.fSumw2[i + 1];
6613  } else {
6614  w = this.histo.getBinContent(i + 1);
6615  }
6616 
6617  if ((xmax==null) || (w>wmax)) { xmax = xx; wmax = w; }
6618 
6619  stat_sumw += w;
6620  stat_sumwx += w * xx;
6621  stat_sumwx2 += w * xx * xx;
6622  }
6623 
6624  // when no range selection done, use original statistic from histogram
6625  if (!this.IsAxisZoomed("x") && (this.histo.fTsumw>0)) {
6626  stat_sumw = this.histo.fTsumw;
6627  stat_sumwx = this.histo.fTsumwx;
6628  stat_sumwx2 = this.histo.fTsumwx2;
6629  }
6630 
6631  var res = { meanx: 0, meany: 0, rmsx: 0, rmsy: 0, integral: stat_sumw, entries: this.stat_entries, xmax:0, wmax:0 };
6632 
6633  if (stat_sumw > 0) {
6634  res.meanx = stat_sumwx / stat_sumw;
6635  res.meany = stat_sumwy / stat_sumw;
6636  res.rmsx = Math.sqrt(stat_sumwx2 / stat_sumw - res.meanx * res.meanx);
6637  res.rmsy = Math.sqrt(stat_sumwy2 / stat_sumw - res.meany * res.meany);
6638  }
6639 
6640  if (xmax!=null) {
6641  res.xmax = xmax;
6642  res.wmax = wmax;
6643  }
6644 
6645  return res;
6646  }
6647 
6648  JSROOT.TH1Painter.prototype.FillStatistic = function(stat, dostat, dofit) {
6649  if (!this.histo) return false;
6650 
6651  var pave = stat.GetObject(),
6652  data = this.CountStat(),
6653  print_name = dostat % 10,
6654  print_entries = Math.floor(dostat / 10) % 10,
6655  print_mean = Math.floor(dostat / 100) % 10,
6656  print_rms = Math.floor(dostat / 1000) % 10,
6657  print_under = Math.floor(dostat / 10000) % 10,
6658  print_over = Math.floor(dostat / 100000) % 10,
6659  print_integral = Math.floor(dostat / 1000000) % 10,
6660  print_skew = Math.floor(dostat / 10000000) % 10,
6661  print_kurt = Math.floor(dostat / 100000000) % 10;
6662 
6663  if (print_name > 0)
6664  pave.AddText(this.histo.fName);
6665 
6666  if (this.IsTProfile()) {
6667 
6668  if (print_entries > 0)
6669  pave.AddText("Entries = " + stat.Format(data.entries,"entries"));
6670 
6671  if (print_mean > 0) {
6672  pave.AddText("Mean = " + stat.Format(data.meanx));
6673  pave.AddText("Mean y = " + stat.Format(data.meany));
6674  }
6675 
6676  if (print_rms > 0) {
6677  pave.AddText("Std Dev = " + stat.Format(data.rmsx));
6678  pave.AddText("Std Dev y = " + stat.Format(data.rmsy));
6679  }
6680 
6681  } else {
6682 
6683  if (print_entries > 0)
6684  pave.AddText("Entries = " + stat.Format(data.entries,"entries"));
6685 
6686  if (print_mean > 0) {
6687  pave.AddText("Mean = " + stat.Format(data.meanx));
6688  }
6689 
6690  if (print_rms > 0) {
6691  pave.AddText("Std Dev = " + stat.Format(data.rmsx));
6692  }
6693 
6694  if (print_under > 0) {
6695  var res = (this.histo.fArray.length > 0) ? this.histo.fArray[0] : 0;
6696  pave.AddText("Underflow = " + stat.Format(res));
6697  }
6698 
6699  if (print_over > 0) {
6700  var res = (this.histo.fArray.length > 0) ? this.histo.fArray[this.histo.fArray.length - 1] : 0;
6701  pave.AddText("Overflow = " + stat.Format(res));
6702  }
6703 
6704  if (print_integral > 0) {
6705  pave.AddText("Integral = " + stat.Format(data.integral,"entries"));
6706  }
6707 
6708  if (print_skew > 0)
6709  pave.AddText("Skew = <not avail>");
6710 
6711  if (print_kurt > 0)
6712  pave.AddText("Kurt = <not avail>");
6713  }
6714 
6715  if (dofit!=0) {
6716  var f1 = this.FindF1();
6717  if (f1!=null) {
6718  var print_fval = dofit%10;
6719  var print_ferrors = Math.floor(dofit/10) % 10;
6720  var print_fchi2 = Math.floor(dofit/100) % 10;
6721  var print_fprob = Math.floor(dofit/1000) % 10;
6722 
6723  if (print_fchi2 > 0)
6724  pave.AddText("#chi^2 / ndf = " + stat.Format(f1.fChisquare,"fit") + " / " + f1.fNDF);
6725  if (print_fprob > 0)
6726  pave.AddText("Prob = " + (('Math' in JSROOT) ? stat.Format(JSROOT.Math.Prob(f1.fChisquare, f1.fNDF)) : "<not avail>"));
6727  if (print_fval > 0) {
6728  for(var n=0;n<f1.fNpar;++n) {
6729  var parname = f1.GetParName(n);
6730  var parvalue = f1.GetParValue(n);
6731  if (parvalue != null) parvalue = stat.Format(Number(parvalue),"fit");
6732  else parvalue = "<not avail>";
6733  var parerr = "";
6734  if (f1.fParErrors!=null) {
6735  parerr = stat.Format(f1.fParErrors[n],"last");
6736  if ((Number(parerr)==0.0) && (f1.fParErrors[n]!=0.0)) parerr = stat.Format(f1.fParErrors[n],"4.2g");
6737  }
6738 
6739  if ((print_ferrors > 0) && (parerr.length > 0))
6740  pave.AddText(parname + " = " + parvalue + " #pm " + parerr);
6741  else
6742  pave.AddText(parname + " = " + parvalue);
6743  }
6744  }
6745  }
6746  }
6747 
6748  // adjust the size of the stats box with the number of lines
6749  var nlines = pave.fLines.arr.length,
6750  stath = nlines * JSROOT.gStyle.StatFontSize;
6751  if ((stath <= 0) || (JSROOT.gStyle.StatFont % 10 === 3)) {
6752  stath = 0.25 * nlines * JSROOT.gStyle.StatH;
6753  pave.fY1NDC = 0.93 - stath;
6754  pave.fY2NDC = 0.93;
6755  }
6756 
6757  return true;
6758  }
6759 
6760 
6761  JSROOT.TH1Painter.prototype.DrawBins = function() {
6762  // new method, create svg:path expression ourself directly from histogram
6763  // all points will be used, compress expression when too large
6764 
6765  var width = this.frame_width(), height = this.frame_height();
6766 
6767  if (!this.draw_content || (width<=0) || (height<=0))
6768  return this.RemoveDrawG();
6769 
6770  this.RecreateDrawG(false, "main_layer");
6771 
6772  var left = this.GetSelectIndex("x", "left", -1),
6773  right = this.GetSelectIndex("x", "right", 2),
6774  pmain = this.main_painter(),
6775  pthis = this,
6776  res = "", lastbin = false,
6777  startx, currx, curry, x, grx, y, gry, curry_min, curry_max, prevy, prevx, i, besti,
6778  exclude_zero = (this.options.Error!==10) && (this.options.Mark!==10),
6779  show_errors = (this.options.Error > 0),
6780  show_markers = (this.options.Mark > 0),
6781  path_fill = null, path_err = null, path_marker = null,
6782  endx = "", endy = "", dend = 0, my, yerr1, yerr2, bincont, binerr, mx1, mx2, mpath = "";
6783 
6784  if (show_errors && !show_markers && (this.histo.fMarkerStyle > 1))
6785  show_markers = true;
6786 
6787  if (this.options.Error == 12) {
6788  if (this.fillatt.color=='none') show_markers = true;
6789  else path_fill = "";
6790  } else
6791  if (this.options.Error > 0) path_err = "";
6792 
6793  if (show_markers) {
6794  // draw markers also when e2 option was specified
6795  if (!this.markeratt)
6796  this.markeratt = JSROOT.Painter.createAttMarker(this.histo);
6797  if (this.markeratt.size > 0) {
6798  // simply use relative move from point, can optimize in the future
6799  path_marker = "";
6800  this.markeratt.reset_pos();
6801  } else {
6802  show_markers = false;
6803  }
6804  }
6805 
6806  // if there are too many points, exclude many vertical drawings at the same X position
6807  // instead define min and max value and made min-max drawing
6808  var use_minmax = ((right-left) > 3*width);
6809 
6810  if (this.options.Error == 11) {
6811  var lw = this.lineatt.width + JSROOT.gStyle.EndErrorSize;
6812  endx = "m0," + lw + "v-" + 2*lw + "m0," + lw;
6813  endy = "m" + lw + ",0h-" + 2*lw + "m" + lw + ",0";
6814  dend = Math.floor((this.lineatt.width-1)/2);
6815  }
6816 
6817  var draw_markers = show_errors || show_markers;
6818 
6819  if (draw_markers) use_minmax = true;
6820 
6821  for (i = left; i <= right; ++i) {
6822 
6823  x = this.GetBinX(i);
6824 
6825  if (this.options.Logx && (x <= 0)) continue;
6826 
6827  grx = Math.round(pmain.grx(x));
6828 
6829  lastbin = (i === right);
6830 
6831  if (lastbin && (left<right)) {
6832  gry = curry;
6833  } else {
6834  y = this.histo.getBinContent(i+1);
6835  if (this.options.Logy && (y < this.scale_ymin))
6836  gry = height + 1;
6837  else
6838  gry = Math.round(pmain.gry(y));
6839  }
6840 
6841  if (res.length === 0) {
6842  besti = i;
6843  prevx = startx = currx = grx;
6844  prevy = curry_min = curry_max = curry = gry;
6845  res = "M"+currx+","+curry;
6846  } else
6847  if (use_minmax) {
6848  if ((grx === currx) && !lastbin) {
6849  if (gry < curry_min) besti = i;
6850  curry_min = Math.min(curry_min, gry);
6851  curry_max = Math.max(curry_max, gry);
6852  curry = gry;
6853  } else {
6854 
6855  if (draw_markers) {
6856  bincont = this.histo.getBinContent(besti+1);
6857  if (!exclude_zero || (bincont!==0)) {
6858 
6859  mx1 = Math.round(pmain.grx(this.GetBinX(besti)));
6860  mx2 = Math.round(pmain.grx(this.GetBinX(besti+1)));
6861  my = Math.round(pmain.gry(bincont));
6862  yerr1 = yerr2 = 20;
6863  if (show_errors) {
6864  binerr = this.histo.getBinError(besti+1);
6865  yerr1 = Math.round(my - pmain.gry(bincont + binerr)); // up
6866  yerr2 = Math.round(pmain.gry(bincont - binerr) - my); // down
6867  }
6868 
6869  if ((my >= -yerr1) && (my <= height + yerr2)) {
6870  if (path_fill !== null)
6871  path_fill +="M" + mx1 +","+(my-yerr1) +
6872  "h" + (mx2-mx1) + "v" + (yerr1+yerr2+1) + "h-" + (mx2-mx1) + "z";
6873  if (path_err !== null)
6874  path_err +="M" + (mx1+dend) +","+ my + endx + "h" + (mx2-mx1-2*dend) + endx +
6875  "M" + Math.round((mx1+mx2)/2) +"," + (my-yerr1+dend) + endy + "v" + (yerr1+yerr2-2*dend) + endy;
6876  if (path_marker !== null)
6877  path_marker += this.markeratt.create((mx1+mx2)/2, my);
6878  }
6879  }
6880  }
6881 
6882  // when several points as same X differs, need complete logic
6883  if (!draw_markers && ((curry_min !== curry_max) || (prevy !== curry_min))) {
6884 
6885  if (prevx !== currx)
6886  res += "h"+(currx-prevx);
6887 
6888  if (curry === curry_min) {
6889  if (curry_max !== prevy)
6890  res += "v" + (curry_max - prevy);
6891  if (curry_min !== curry_max)
6892  res += "v" + (curry_min - curry_max);
6893  } else {
6894  if (curry_min !== prevy)
6895  res += "v" + (curry_min - prevy);
6896  if (curry_max !== curry_min)
6897  res += "v" + (curry_max - curry_min);
6898  if (curry !== curry_max)
6899  res += "v" + (curry - curry_max);
6900  }
6901 
6902  prevx = currx;
6903  prevy = curry;
6904  }
6905 
6906  if (lastbin && (prevx !== grx))
6907  res += "h"+(grx-prevx);
6908 
6909  besti = i;
6910  curry_min = curry_max = curry = gry;
6911  currx = grx;
6912  }
6913  } else
6914  if ((gry !== curry) || lastbin) {
6915  if (grx !== currx) res += "h"+(grx-currx);
6916  if (gry !== curry) res += "v"+(gry-curry);
6917  curry = gry;
6918  currx = grx;
6919  }
6920  }
6921 
6922  if ((this.fillatt.color !== 'none') && (res.length>0)) {
6923  var h0 = (height+3);
6924  if ((this.hmin>=0) && (pmain.gry(0) < height)) h0 = Math.round(pmain.gry(0));
6925  res += "L"+currx+","+h0 + "L"+startx+","+h0 + "Z";
6926  }
6927 
6928  if (draw_markers) {
6929 
6930  if ((path_fill !== null) && (path_fill.length > 0))
6931  this.draw_g.append("svg:path")
6932  .attr("d", path_fill)
6933  .call(this.fillatt.func);
6934 
6935  if ((path_err !== null) && (path_err.length > 0))
6936  this.draw_g.append("svg:path")
6937  .attr("d", path_err)
6938  .call(this.lineatt.func)
6939 
6940  if ((path_marker !== null) && (path_marker.length > 0))
6941  this.draw_g.append("svg:path")
6942  .attr("d", path_marker)
6943  .call(this.markeratt.func);
6944 
6945  } else
6946  if (res.length > 0) {
6947  this.draw_g.append("svg:path")
6948  .attr("d", res)
6949  .style("stroke-linejoin","miter")
6950  .call(this.lineatt.func)
6951  .call(this.fillatt.func);
6952  }
6953  }
6954 
6955  JSROOT.TH1Painter.prototype.GetBinTips = function(bin,asstr) {
6956  var tips = [], name = this.GetTipName(), pmain = this.main_painter();
6957  if (name.length>0) tips.push(name);
6958 
6959  var x1 = this.GetBinX(bin),
6960  x2 = this.GetBinX(bin+1),
6961  cont = this.histo.getBinContent(bin+1);
6962 
6963  if ((this.options.Error > 0) || (this.options.Mark > 0)) {
6964  tips.push("x = " + pmain.AxisAsText("x", (x1+x2)/2));
6965  tips.push("y = " + pmain.AxisAsText("y", cont));
6966  if (this.options.Error > 0) {
6967  tips.push("error x = " + ((x2 - x1) / 2).toPrecision(4));
6968  tips.push("error y = " + this.histo.getBinError(bin + 1).toPrecision(4));
6969  }
6970  } else {
6971  tips.push("bin = " + (bin+1));
6972 
6973  if (pmain.x_kind === 'labels')
6974  tips.push("x = " + pmain.AxisAsText("x", x1));
6975  else
6976  if (pmain.x_kind === 'time')
6977  tips.push("x = " + pmain.AxisAsText("x", (x1+x2)/2));
6978  else
6979  tips.push("x = [" + pmain.AxisAsText("x", x1) + ", " + pmain.AxisAsText("x", x2) + ")");
6980 
6981  if (cont === Math.round(cont))
6982  tips.push("entries = " + cont);
6983  else
6984  tips.push("entries = " + JSROOT.FFormat(cont, JSROOT.gStyle.StatFormat));
6985  }
6986 
6987  if (!asstr) return tips;
6988 
6989  var res = "";
6990  for (var n=0;n<tips.length;++n) res += (n>0 ? "\n" : "") + tips[n];
6991  return res;
6992  }
6993 
6994  JSROOT.TH1Painter.prototype.ProcessTooltip = function(pnt) {
6995  if ((pnt === null) || !this.draw_content) {
6996  if (this.draw_g !== null)
6997  this.draw_g.select(".tooltip_bin").remove();
6998  this.ProvideUserTooltip(null);
6999  return null;
7000  }
7001 
7002  var width = this.frame_width(),
7003  height = this.frame_height(),
7004  pmain = this.main_painter(),
7005  painter = this,
7006  findbin = null, show_rect = true,
7007  grx1, midx, grx2, gry1, midy, gry2,
7008  left = this.GetSelectIndex("x", "left", -1),
7009  right = this.GetSelectIndex("x", "right", 2),
7010  l = left, r = right;
7011 
7012  function GetBinGrX(i) {
7013  var x1 = painter.GetBinX(i);
7014  if ((x1<0) && painter.options.Logx) return null;
7015  return pmain.grx(x1);
7016  }
7017 
7018  function GetBinGrY(i) {
7019  var y = painter.histo.getBinContent(i + 1);
7020  if (painter.options.Logy && (y < painter.scale_ymin))
7021  return 10*height;
7022  return Math.round(pmain.gry(y));
7023  }
7024 
7025  while (l < r-1) {
7026  var m = Math.round((l+r)*0.5);
7027 
7028  var xx = GetBinGrX(m);
7029  if (xx === null) { l = m; continue; }
7030 
7031  if (xx < pnt.x - 0.5) l = m; else
7032  if (xx > pnt.x + 0.5) r = m; else { l++; r--; }
7033  }
7034 
7035  findbin = r = l;
7036  grx1 = GetBinGrX(findbin);
7037 
7038  while ((l>left) && (GetBinGrX(l-1) > grx1 - 1.0)) --l;
7039  while ((r<right) && (GetBinGrX(r+1) < grx1 + 1.0)) ++r;
7040 
7041  if (l < r) {
7042  // many points can be assigned with the same cursor position
7043  // first try point around mouse y
7044  var best = height;
7045  for (var m=l;m<=r;m++) {
7046  var dist = Math.abs(GetBinGrY(m) - pnt.y);
7047  if (dist < best) { best = dist; findbin = m; }
7048  }
7049 
7050  // if best distance still too far from mouse position, just take from between
7051  if (best > height/10)
7052  findbin = Math.round(l + (r-l) / height * pnt.y);
7053 
7054  grx1 = GetBinGrX(findbin);
7055  }
7056 
7057  grx1 = Math.round(grx1);
7058  grx2 = Math.round(GetBinGrX(findbin+1));
7059 
7060  midx = Math.round((grx1+grx2)/2);
7061 
7062  midy = gry1 = gry2 = GetBinGrY(findbin);
7063 
7064 
7065  if ((this.options.Error > 0) || (this.options.Mark > 0)) {
7066 
7067  show_rect = true;
7068 
7069  var msize = 3;
7070  if (this.markeratt) msize = Math.max(msize, 2+Math.round(this.markeratt.size * 4));
7071 
7072  // show at least 6 pixels as tooltip rect
7073  if (grx2 - grx1 < 2*msize) { grx1 = midx-msize; grx2 = midx+msize; }
7074 
7075  if (this.options.Error > 0) {
7076  var cont = this.histo.getBinContent(findbin+1);
7077  var binerr = this.histo.getBinError(findbin+1);
7078 
7079  gry1 = Math.round(pmain.gry(cont + binerr)); // up
7080  gry2 = Math.round(pmain.gry(cont - binerr)); // down
7081  }
7082 
7083  gry1 = Math.min(gry1, midy - msize);
7084  gry2 = Math.max(gry2, midy + msize);
7085 
7086  if (!pnt.touch && (pnt.nproc === 1))
7087  if ((pnt.y<gry1) || (pnt.y>gry2)) findbin = null;
7088 
7089  } else {
7090 
7091  // if histogram alone, use old-style with rects
7092  // if there are too many points at pixel, use circle
7093  show_rect = (pnt.nproc === 1) && (right-left < width);
7094 
7095  if (show_rect) {
7096  // for mouse events mouse pointer should be under the curve
7097  if ((pnt.y < gry1) && !pnt.touch) findbin = null;
7098 
7099  gry2 = height;
7100 
7101  if ((this.fillatt.color !== 'none') && (this.hmin>=0)) {
7102  gry2 = Math.round(pmain.gry(0));
7103  if ((gry2 > height) || (gry2 <= gry1)) gry2 = height;
7104  }
7105  }
7106  }
7107 
7108  if (findbin!==null) {
7109  // if bin on boundary found, check that x position is ok
7110  if ((findbin === left) && (grx1 > pnt.x + 2)) findbin = null; else
7111  if ((findbin === right-1) && (grx2 < pnt.x - 2)) findbin = null;
7112  }
7113 
7114 
7115  var ttrect = this.draw_g.select(".tooltip_bin");
7116 
7117  if ((findbin === null) || ((gry2 <= 0) || (gry1 >= height))) {
7118  ttrect.remove();
7119  this.ProvideUserTooltip(null);
7120  return null;
7121  }
7122 
7123  var res = { x: midx, y: midy,
7124  color1: this.lineatt.color, color2: this.fillatt.color,
7125  lines: this.GetBinTips(findbin) };
7126 
7127  if (show_rect) {
7128 
7129  if (ttrect.empty())
7130  ttrect = this.draw_g.append("svg:rect")
7131  .attr("class","tooltip_bin h1bin")
7132  .style("pointer-events","none");
7133 
7134  res.changed = ttrect.property("current_bin") !== findbin;
7135 
7136  if (res.changed)
7137  ttrect.attr("x", grx1)
7138  .attr("width", grx2-grx1)
7139  .attr("y", gry1)
7140  .attr("height", gry2-gry1)
7141  .style("opacity", "0.3")
7142  .property("current_bin", findbin);
7143 
7144  res.exact = (Math.abs(midy - pnt.y) <= 5) || ((pnt.y>=gry1) && (pnt.y<=gry2));
7145 
7146  res.menu = true; // one could show context menu
7147  // distance to middle point, use to decide which menu to activate
7148  res.menu_dist = Math.sqrt((midx-pnt.x)*(midx-pnt.x) + (midy-pnt.y)*(midy-pnt.y));
7149 
7150  } else {
7151  var radius = this.lineatt.width + 3;
7152 
7153  if (ttrect.empty())
7154  ttrect = this.draw_g.append("svg:circle")
7155  .attr("class","tooltip_bin")
7156  .style("pointer-events","none")
7157  .attr("r", radius)
7158  .call(this.lineatt.func)
7159  .call(this.fillatt.func);
7160 
7161  res.exact = (Math.abs(midx - pnt.x) <= radius) && (Math.abs(midy - pnt.y) <= radius);
7162 
7163  res.menu = res.exact; // show menu only when mouse pointer exactly over the histogram
7164  res.menu_dist = Math.sqrt((midx-pnt.x)*(midx-pnt.x) + (midy-pnt.y)*(midy-pnt.y));
7165 
7166  res.changed = ttrect.property("current_bin") !== findbin;
7167 
7168  if (res.changed)
7169  ttrect.attr("cx", midx)
7170  .attr("cy", midy)
7171  .property("current_bin", findbin);
7172  }
7173 
7174  if (this.IsUserTooltipCallback() && res.changed) {
7175  this.ProvideUserTooltip({ obj: this.histo, name: this.histo.fName,
7176  bin: findbin, cont: this.histo.getBinContent(findbin+1),
7177  grx: midx, gry: midy });
7178  }
7179 
7180  return res;
7181  }
7182 
7183 
7184  JSROOT.TH1Painter.prototype.FillHistContextMenu = function(menu) {
7185  if (!this.draw_content) return;
7186 
7187  menu.add("Auto zoom-in", this.AutoZoom.bind(this));
7188  menu.addDrawMenu("Draw with", ["hist", "p", "e", "e1", "pe2"], function(arg) {
7189  this.options = this.DecodeOptions(arg);
7190  this.Redraw();
7191  });
7192  }
7193 
7194  JSROOT.TH1Painter.prototype.AutoZoom = function() {
7195  var left = this.GetSelectIndex("x", "left", -1),
7196  right = this.GetSelectIndex("x", "right", 1),
7197  dist = right - left;
7198 
7199  if (dist == 0) return;
7200 
7201  // first find minimum
7202  var min = this.histo.getBinContent(left + 1);
7203  for (var indx = left; indx < right; ++indx)
7204  if (this.histo.getBinContent(indx + 1) < min)
7205  min = this.histo.getBinContent(indx + 1);
7206  if (min>0) return; // if all points positive, no chance for autoscale
7207 
7208  while ((left < right) && (this.histo.getBinContent(left + 1) <= min)) ++left;
7209  while ((left < right) && (this.histo.getBinContent(right) <= min)) --right;
7210 
7211  if ((right - left < dist) && (left < right))
7212  this.Zoom(this.GetBinX(left), this.GetBinX(right));
7213  }
7214 
7215  JSROOT.TH1Painter.prototype.CanZoomIn = function(axis,min,max) {
7216  if ((axis=="x") && (this.GetIndexX(max,0.5) - this.GetIndexX(min,0) > 1)) return true;
7217 
7218  if ((axis=="y") && (Math.abs(max-min) > Math.abs(this.ymax-this.ymin)*1e-6)) return true;
7219 
7220  // check if it makes sense to zoom inside specified axis range
7221  return false;
7222  }
7223 
7224  JSROOT.TH1Painter.prototype.Redraw = function() {
7225  this.CreateXY();
7226  this.DrawAxes();
7227  this.DrawGrids();
7228  this.DrawBins();
7229  this.DrawTitle();
7230  }
7231 
7232  JSROOT.Painter.drawHistogram1D = function(divid, histo, opt) {
7233  // create painter and add it to canvas
7234  var painter = new JSROOT.TH1Painter(histo);
7235 
7236  painter.SetDivId(divid, 1);
7237 
7238  // here we deciding how histogram will look like and how will be shown
7239  painter.options = painter.DecodeOptions(opt);
7240 
7241  painter.CheckPadOptions();
7242 
7243  painter.ScanContent();
7244 
7245  painter.CreateXY();
7246 
7247  painter.DrawAxes();
7248 
7249  painter.DrawGrids();
7250 
7251  painter.DrawBins();
7252 
7253  painter.DrawTitle();
7254 
7255  if (JSROOT.gStyle.AutoStat && painter.create_canvas)
7256  painter.CreateStat();
7257 
7258  painter.DrawNextFunction(0, function() {
7259 
7260  painter.AddInteractive();
7261 
7262  painter.FillToolbar();
7263 
7264  if (painter.options.AutoZoom) painter.AutoZoom();
7265 
7266  painter.DrawingReady();
7267  });
7268 
7269  return painter;
7270  }
7271 
7272  // =====================================================================================
7273 
7274  JSROOT.Painter.drawText = function(divid, text) {
7275  var painter = new JSROOT.TObjectPainter(text);
7276  painter.SetDivId(divid, 2);
7277 
7278  painter.Redraw = function() {
7279  var text = this.GetObject(),
7280  w = this.pad_width(), h = this.pad_height(),
7281  pos_x = text.fX, pos_y = text.fY,
7282  tcolor = JSROOT.Painter.root_colors[text.fTextColor],
7283  use_pad = true, latex_kind = 0, fact = 1.;
7284 
7285  if (text.TestBit(JSROOT.BIT(14))) {
7286  // NDC coordiantes
7287  pos_x = pos_x * w;
7288  pos_y = (1 - pos_y) * h;
7289  } else
7290  if (this.main_painter() !== null) {
7291  w = this.frame_width(); h = this.frame_height(); use_pad = false;
7292  pos_x = this.main_painter().grx(pos_x);
7293  pos_y = this.main_painter().gry(pos_y);
7294  } else
7295  if (this.root_pad() !== null) {
7296  pos_x = this.ConvertToNDC("x", pos_x) * w;
7297  pos_y = (1 - this.ConvertToNDC("y", pos_y)) * h;
7298  } else {
7299  text.fTextAlign = 22;
7300  pos_x = w/2;
7301  pos_y = h/2;
7302  if (text.fTextSize === 0) text.fTextSize = 0.05;
7303  if (text.fTextColor === 0) text.fTextColor = 1;
7304  }
7305 
7306  this.RecreateDrawG(use_pad, use_pad ? "text_layer" : "upper_layer");
7307 
7308  if (text._typename == 'TLatex') { latex_kind = 1; fact = 0.9; } else
7309  if (text._typename == 'TMathText') { latex_kind = 2; fact = 0.8; }
7310 
7311  this.StartTextDrawing(text.fTextFont, Math.round(text.fTextSize*Math.min(w,h)*fact));
7312 
7313  this.DrawText(text.fTextAlign, Math.round(pos_x), Math.round(pos_y), 0, 0, text.fTitle, tcolor, latex_kind);
7314 
7315  this.FinishTextDrawing();
7316  }
7317 
7318  return painter.DrawingReady();
7319  }
7320 
7321  // ================= painer of raw text ========================================
7322 
7323  JSROOT.RawTextPainter = function(txt) {
7324  JSROOT.TBasePainter.call(this);
7325  this.txt = txt;
7326  return this;
7327  }
7328 
7329  JSROOT.RawTextPainter.prototype = Object.create( JSROOT.TBasePainter.prototype );
7330 
7331  JSROOT.RawTextPainter.prototype.RedrawObject = function(obj) {
7332  this.txt = obj;
7333  this.Draw();
7334  return true;
7335  }
7336 
7337  JSROOT.RawTextPainter.prototype.Draw = function() {
7338  var txt = this.txt.value;
7339  if (txt==null) txt = "<undefined>";
7340 
7341  var mathjax = 'mathjax' in this.txt;
7342 
7343  if (!mathjax && !('as_is' in this.txt)) {
7344  var arr = txt.split("\n"); txt = "";
7345  for (var i = 0; i < arr.length; ++i)
7346  txt += "<pre>" + arr[i] + "</pre>";
7347  }
7348 
7349  var frame = this.select_main();
7350  var main = frame.select("div");
7351  if (main.empty())
7352  main = frame.append("div")
7353  .style('max-width','100%')
7354  .style('max-height','100%')
7355  .style('overflow','auto');
7356 
7357  main.html(txt);
7358 
7359  // (re) set painter to first child element
7360  this.SetDivId(this.divid);
7361 
7362  if (mathjax) {
7363  if (this['loading_mathjax']) return;
7364  this['loading_mathjax'] = true;
7365  var painter = this;
7366  JSROOT.AssertPrerequisites('mathjax', function() {
7367  painter['loading_mathjax'] = false;
7368  if (typeof MathJax == 'object') {
7369  MathJax.Hub.Queue(["Typeset", MathJax.Hub, frame.node()]);
7370  }
7371  });
7372  }
7373  }
7374 
7375  JSROOT.Painter.drawRawText = function(divid, txt, opt) {
7376  var painter = new JSROOT.RawTextPainter(txt);
7377  painter.SetDivId(divid);
7378  painter.Draw();
7379  return painter.DrawingReady();
7380  }
7381 
7382  // ===================== hierarchy scanning functions ==================================
7383 
7384  JSROOT.Painter.FolderHierarchy = function(item, obj) {
7385 
7386  if ((obj==null) || !('fFolders' in obj) || (obj.fFolders==null)) return false;
7387 
7388  if (obj.fFolders.arr.length===0) { item._more = false; return true; }
7389 
7390  item._childs = [];
7391 
7392  for ( var i = 0; i < obj.fFolders.arr.length; ++i) {
7393  var chld = obj.fFolders.arr[i];
7394  item._childs.push( {
7395  _name : chld.fName,
7396  _kind : "ROOT." + chld._typename,
7397  _obj : chld
7398  });
7399  }
7400  return true;
7401  }
7402 
7403  JSROOT.Painter.TaskHierarchy = function(item, obj) {
7404  // function can be used for different derived classes
7405  // we show not only child tasks, but all complex data members
7406 
7407  if ((obj==null) || !('fTasks' in obj) || (obj.fTasks==null)) return false;
7408 
7409  JSROOT.Painter.ObjectHierarchy(item, obj, { exclude: ['fTasks', 'fName'] } );
7410 
7411  if ((obj.fTasks.arr.length===0) && (item._childs.length==0)) { item._more = false; return true; }
7412 
7413  // item._childs = [];
7414 
7415  for ( var i = 0; i < obj.fTasks.arr.length; ++i) {
7416  var chld = obj.fTasks.arr[i];
7417  item._childs.push( {
7418  _name : chld.fName,
7419  _kind : "ROOT." + chld._typename,
7420  _obj : chld
7421  });
7422  }
7423  return true;
7424  }
7425 
7426 
7427  JSROOT.Painter.ListHierarchy = function(folder, lst) {
7428  if (lst._typename != 'TList' && lst._typename != 'TObjArray' && lst._typename != 'TClonesArray') return false;
7429 
7430  if ((lst.arr === undefined) || (lst.arr.length === 0)) {
7431  folder._more = false;
7432  return true;
7433  }
7434 
7435  folder._childs = [];
7436  for ( var i = 0; i < lst.arr.length; ++i) {
7437  var obj = lst.arr[i];
7438 
7439  var item;
7440 
7441  if (obj===null) {
7442  item = {
7443  _name: i.toString(),
7444  _kind: "ROOT.NULL",
7445  _title: "NULL",
7446  _obj: null
7447  }
7448  } else {
7449  item = {
7450  _name : obj.fName,
7451  _kind : "ROOT." + obj._typename,
7452  _title : obj.fTitle,
7453  _obj : obj
7454  };
7455  // if name is integer value, it should match array index
7456  if ((item._name === undefined) ||
7457  (!isNaN(parseInt(item._name)) && (parseInt(item._name)!==i))
7458  || (lst.arr.indexOf(obj)<i))
7459  item._name = i.toString();
7460 
7461  if (item._title === undefined)
7462  item._title = obj._typename ? item._kind : item._name;
7463  }
7464 
7465  folder._childs.push(item);
7466  }
7467  return true;
7468  }
7469 
7470  JSROOT.Painter.TreeHierarchy = function(node, obj) {
7471  if (obj._typename != 'TTree' && obj._typename != 'TNtuple') return false;
7472 
7473  node._childs = [];
7474 
7475  for ( var i = 0; i < obj.fBranches.arr.length; ++i) {
7476  var branch = obj.fBranches.arr[i];
7477  var nb_leaves = branch.fLeaves.arr.length;
7478 
7479  // display branch with only leaf as leaf
7480  if (nb_leaves == 1 && branch.fLeaves.arr[0].fName == branch.fName) nb_leaves = 0;
7481 
7482  var subitem = {
7483  _name : branch.fName,
7484  _kind : nb_leaves > 0 ? "ROOT.TBranch" : "ROOT.TLeafF"
7485  }
7486 
7487  node._childs.push(subitem);
7488 
7489  if (nb_leaves > 0) {
7490  subitem._childs = [];
7491  for (var j = 0; j < nb_leaves; ++j) {
7492  var leafitem = {
7493  _name : branch.fLeaves.arr[j].fName,
7494  _kind : "ROOT.TLeafF"
7495  }
7496  subitem._childs.push(leafitem);
7497  }
7498  }
7499  }
7500 
7501  return true;
7502  }
7503 
7504  JSROOT.Painter.KeysHierarchy = function(folder, keys, file, dirname) {
7505 
7506  if (keys === undefined) return false;
7507 
7508  folder._childs = [];
7509 
7510  for (var i = 0; i < keys.length; ++i) {
7511  var key = keys[i];
7512 
7513  var item = {
7514  _name : key.fName + ";" + key.fCycle,
7515  _cycle : key.fCycle,
7516  _kind : "ROOT." + key.fClassName,
7517  _title : key.fTitle,
7518  _keyname : key.fName,
7519  _readobj : null,
7520  _parent : folder
7521  };
7522 
7523  if ('fRealName' in key)
7524  item._realname = key.fRealName + ";" + key.fCycle;
7525 
7526  if (key.fClassName == 'TDirectory' || key.fClassName == 'TDirectoryFile') {
7527  var dir = null;
7528  if ((dirname!=null) && (file!=null)) dir = file.GetDir(dirname + key.fName);
7529  if (dir == null) {
7530  item._more = true;
7531  item._expand = function(node, obj) {
7532  // one can get expand call from child objects - ignore them
7533  return JSROOT.Painter.KeysHierarchy(node, obj.fKeys);
7534  }
7535  } else {
7536  // remove cycle number - we have already directory
7537  item._name = key.fName;
7538  JSROOT.Painter.KeysHierarchy(item, dir.fKeys, file, dirname + key.fName + "/");
7539  }
7540  } else
7541  if ((key.fClassName == 'TList') && (key.fName == 'StreamerInfo')) {
7542  item._name = 'StreamerInfo';
7543  item._kind = "ROOT.TStreamerInfoList";
7544  item._title = "List of streamer infos for binary I/O";
7545  item._readobj = file.fStreamerInfos;
7546  }
7547 
7548  folder._childs.push(item);
7549  }
7550 
7551  return true;
7552  }
7553 
7554  JSROOT.Painter.ObjectHierarchy = function(top, obj, args) {
7555  if ((top==null) || (obj==null)) return false;
7556 
7557  // check nosimple property in all parents
7558  var nosimple = true, prnt = top;
7559  while (prnt) {
7560  if ('_nosimple' in prnt) { nosimple = prnt._nosimple; break; }
7561  prnt = prnt._parent;
7562  }
7563 
7564  top._childs = [];
7565  if (!('_obj' in top))
7566  top._obj = obj;
7567  else
7568  if (top._obj !== obj) alert('object missmatch');
7569 
7570  if (!('_title' in top) && ('_typename' in obj))
7571  top._title = "ROOT." + obj._typename;
7572 
7573  for (var key in obj) {
7574  if (key == '_typename') continue;
7575  var fld = obj[key];
7576  if (typeof fld == 'function') continue;
7577  if (args && args.exclude && (args.exclude.indexOf(key)>=0)) continue;
7578 
7579  var item = {
7580  _parent : top,
7581  _name : key
7582  };
7583 
7584  if (fld === null) {
7585  item._value = item._title = "null";
7586  if (!nosimple) top._childs.push(item);
7587  continue;
7588  }
7589 
7590  var proto = Object.prototype.toString.apply(fld);
7591  var simple = false;
7592 
7593  if ((proto.lastIndexOf('Array]') == proto.length-6) && (proto.indexOf('[object')==0)) {
7594  item._title = item._kind + " len=" + fld.length;
7595  simple = (proto != '[object Array]');
7596  if (fld.length === 0) {
7597  item._value = "[ ]";
7598  item._more = false; // hpainter will not try to expand again
7599  } else {
7600  item._value = "[...]";
7601  item._more = true;
7602  item._expand = JSROOT.Painter.ObjectHierarchy;
7603  item._obj = fld;
7604  }
7605  } else
7606  if (typeof fld == 'object') {
7607  if ('_typename' in fld)
7608  item._kind = item._title = "ROOT." + fld._typename;
7609 
7610  // check if object already shown in hierarchy (circular dependency)
7611  var curr = top, inparent = false;
7612  while (curr && !inparent) {
7613  inparent = (curr._obj === fld);
7614  curr = curr._parent;
7615  }
7616 
7617  if (inparent) {
7618  item._value = "{ prnt }";
7619  simple = true;
7620  } else {
7621  item._obj = fld;
7622  item._value = "{ }";
7623  if (fld._typename == 'TColor') item._value = JSROOT.Painter.MakeColorRGB(fld);
7624  }
7625  } else
7626  if ((typeof fld == 'number') || (typeof fld == 'boolean')) {
7627  simple = true;
7628  if (key == 'fBits')
7629  item._value = "0x" + fld.toString(16);
7630  else
7631  item._value = fld.toString();
7632  item._vclass = 'h_value_num';
7633  } else
7634  if (typeof fld == 'string') {
7635  simple = true;
7636  item._value = '"' + fld + '"';
7637  item._vclass = 'h_value_str';
7638  } else {
7639  simple = true;
7640  alert('miss ' + key + ' ' + typeof fld);
7641  }
7642 
7643  if (!simple || !nosimple)
7644  top._childs.push(item);
7645  }
7646  return true;
7647  }
7648 
7649  // =========== painter of hierarchical structures =================================
7650 
7651  JSROOT.hpainter = null; // global pointer
7652 
7653  JSROOT.HierarchyPainter = function(name, frameid, backgr) {
7654  JSROOT.TBasePainter.call(this);
7655  this.name = name;
7656  this.h = null; // hierarchy
7657  this.with_icons = true;
7658  this.background = backgr;
7659  this.files_monitoring = (frameid == null); // by default files monitored when nobrowser option specified
7660  if (frameid != null) this.SetDivId(frameid);
7661 
7662  // remember only very first instance
7663  if (JSROOT.hpainter == null)
7664  JSROOT.hpainter = this;
7665  }
7666 
7667  JSROOT.HierarchyPainter.prototype = Object.create(JSROOT.TBasePainter.prototype);
7668 
7669  JSROOT.HierarchyPainter.prototype.Cleanup = function() {
7670  // clear drawing and browser
7671  this.clear(true);
7672  }
7673 
7674  JSROOT.HierarchyPainter.prototype.FileHierarchy = function(file) {
7675  var painter = this;
7676 
7677  var folder = {
7678  _name : file.fFileName,
7679  _kind : "ROOT.TFile",
7680  _file : file,
7681  _fullurl : file.fFullURL,
7682  _had_direct_read : false,
7683  // this is central get method, item or itemname can be used
7684  _get : function(item, itemname, callback) {
7685 
7686  var fff = this; // file item
7687 
7688  if ((item!=null) && (item._readobj != null))
7689  return JSROOT.CallBack(callback, item, item._readobj);
7690 
7691  if (item!=null) itemname = painter.itemFullName(item, fff);
7692  // var pos = fullname.lastIndexOf(";");
7693  // if (pos>0) fullname = fullname.slice(0, pos);
7694 
7695  function ReadFileObject(file) {
7696  if (fff._file==null) fff._file = file;
7697 
7698  if (file == null) return JSROOT.CallBack(callback, item, null);
7699 
7700  file.ReadObject(itemname, function(obj) {
7701 
7702  // if object was read even when item didnot exist try to reconstruct new hierarchy
7703  if ((item==null) && (obj!=null)) {
7704  // first try to found last read directory
7705  var d = painter.Find({name:itemname, top:fff, last_exists:true, check_keys:true });
7706  if ((d!=null) && ('last' in d) && (d.last!=fff)) {
7707  // reconstruct only subdir hierarchy
7708  var dir = file.GetDir(painter.itemFullName(d.last, fff));
7709  if (dir) {
7710  d.last._name = d.last._keyname;
7711  var dirname = painter.itemFullName(d.last, fff);
7712  JSROOT.Painter.KeysHierarchy(d.last, dir.fKeys, file, dirname + "/");
7713  }
7714  } else {
7715  // reconstruct full file hierarchy
7716  JSROOT.Painter.KeysHierarchy(fff, file.fKeys, file, "");
7717  }
7718  item = painter.Find({name:itemname, top: fff});
7719  }
7720 
7721  if (item!=null) {
7722  item._readobj = obj;
7723  // remove cycle number for objects supporting expand
7724  if ('_expand' in item) item._name = item._keyname;
7725  }
7726 
7727  JSROOT.CallBack(callback, item, obj);
7728  });
7729  }
7730 
7731  if (fff._file != null) {
7732  ReadFileObject(fff._file);
7733  } else {
7734  // try to reopen ROOT file
7735  new JSROOT.TFile(fff._fullurl, ReadFileObject);
7736  }
7737  }
7738  };
7739 
7740  JSROOT.Painter.KeysHierarchy(folder, file.fKeys, file, "");
7741 
7742  return folder;
7743  }
7744 
7745  JSROOT.HierarchyPainter.prototype.ForEach = function(callback, top) {
7746 
7747  if (top==null) top = this.h;
7748  if ((top==null) || (typeof callback != 'function')) return;
7749  function each_item(item) {
7750  callback(item);
7751  if ('_childs' in item)
7752  for (var n = 0; n < item._childs.length; ++n) {
7753  item._childs[n]._parent = item;
7754  each_item(item._childs[n]);
7755  }
7756  }
7757 
7758  each_item(top);
7759  }
7760 
7761  JSROOT.HierarchyPainter.prototype.Find = function(arg) {
7762  // search item in the hierarchy
7763  // One could specify simply item name or object with following arguments
7764  // name: item to search
7765  // force: specified elements will be created when not exists
7766  // last_exists: when specified last parent element will be returned
7767  // top: element to start search from
7768 
7769  function find_in_hierarchy(top, fullname) {
7770 
7771  if (!fullname || (fullname.length == 0) || (top==null)) return top;
7772 
7773  var pos = -1;
7774 
7775  function process_child(child) {
7776  // set parent pointer when searching child
7777  child._parent = top;
7778  if ((pos + 1 == fullname.length) || (pos < 0)) return child;
7779 
7780  return find_in_hierarchy(child, fullname.substr(pos + 1));
7781  }
7782 
7783  do {
7784  // we try to find element with slashes inside
7785  pos = fullname.indexOf("/", pos + 1);
7786 
7787  var localname = (pos < 0) ? fullname : fullname.substr(0, pos);
7788 
7789  // first try to find direct matched item
7790  if (typeof top._childs != 'undefined')
7791  for (var i = 0; i < top._childs.length; ++i)
7792  if (top._childs[i]._name == localname)
7793  return process_child(top._childs[i]);
7794 
7795  // if allowed, try to found item with key
7796  if (('check_keys' in arg) && (typeof top._childs != 'undefined'))
7797  for (var i = 0; i < top._childs.length; ++i) {
7798  if (top._childs[i]._name.indexOf(localname + ";")==0)
7799  return process_child(top._childs[i]);
7800  }
7801 
7802  if ('force' in arg) {
7803  // if didnot found element with given name we just generate it
7804  if (! ('_childs' in top)) top._childs = [];
7805  var child = { _name: localname };
7806  top._childs.push(child);
7807  return process_child(child);
7808  }
7809 
7810  // when search for the elements it could be allowed to check index
7811  if (arg.allow_index && (typeof top._childs != 'undefined')) {
7812  var indx = parseInt(localname);
7813  if (!isNaN(indx) && (indx>=0) && (indx<top._childs.length))
7814  return process_child(top._childs[indx]);
7815  }
7816 
7817  } while (pos > 0);
7818 
7819  return ('last_exists' in arg) && (top!=null) ? { last : top, rest : fullname } : null;
7820  }
7821 
7822  var top = this.h;
7823  var itemname = "";
7824 
7825  if (typeof arg == 'string') { itemname = arg; arg = {}; } else
7826  if (typeof arg == 'object') { itemname = arg.name; if ('top' in arg) top = arg.top; } else
7827  return null;
7828 
7829  return find_in_hierarchy(top, itemname);
7830  }
7831 
7832  JSROOT.HierarchyPainter.prototype.itemFullName = function(node, uptoparent) {
7833  var res = "";
7834 
7835  while (node && ('_parent' in node)) {
7836  if (res.length > 0) res = "/" + res;
7837  res = node._name + res;
7838  node = node._parent;
7839  if (uptoparent && (node === uptoparent)) break;
7840  }
7841 
7842  return res;
7843  }
7844 
7845  JSROOT.HierarchyPainter.prototype.ExecuteCommand = function(itemname, callback) {
7846  // execute item marked as 'Command'
7847  // If command requires additional arguments, they could be specified as extra arguments
7848  // Or they will be requested interactive
7849 
7850  var hitem = this.Find(itemname);
7851  var url = itemname + "/cmd.json";
7852  var pthis = this;
7853  var d3node = d3.select((typeof callback == 'function') ? null : callback);
7854 
7855  if ('_numargs' in hitem)
7856  for (var n = 0; n < hitem._numargs; ++n) {
7857  var argname = "arg" + (n+1);
7858  var argvalue = null;
7859  if (n+2<arguments.length) argvalue = arguments[n+2];
7860  if ((argvalue==null) && (typeof callback == 'object'))
7861  argvalue = prompt("Input argument " + argname + " for command " + hitem._name,"");
7862  if (argvalue==null) return;
7863  url += ((n==0) ? "?" : "&") + argname + "=" + argvalue;
7864  }
7865 
7866  if (!d3node.empty()) {
7867  d3node.style('background','yellow');
7868  if (hitem && hitem._title) d3node.attr('title', "Executing " + hitem._title);
7869  }
7870 
7871  JSROOT.NewHttpRequest(url, 'text', function(res) {
7872  if (typeof callback == 'function') return callback(res);
7873  if (d3node.empty()) return;
7874  var col = ((res!=null) && (res!='false')) ? 'green' : 'red';
7875  if (hitem && hitem._title) d3node.attr('title', hitem._title + " lastres=" + res);
7876  d3node.style('background', col).transition().duration(2000).each("end", function() { d3node.style('background', ''); });
7877  if ((col == 'green') && ('_hreload' in hitem)) pthis.reload();
7878  if ((col == 'green') && ('_update_item' in hitem)) pthis.updateItems(hitem._update_item.split(";"));
7879  }).send();
7880  }
7881 
7882  JSROOT.HierarchyPainter.prototype.RefreshHtml = function(callback) {
7883  if (this.divid == null) return JSROOT.CallBack(callback);
7884  var hpainter = this;
7885  JSROOT.AssertPrerequisites('jq2d', function() {
7886  hpainter.RefreshHtml(callback);
7887  });
7888  }
7889 
7890  JSROOT.HierarchyPainter.prototype.toggle = function(status, h) {
7891  var hitem = (h==null) ? this.h : h;
7892 
7893  if (!('_childs' in hitem)) {
7894  if (!status || this.with_icons || ((typeof hitem._expand) !== 'function')) return;
7895  this.expand(this.itemFullName(hitem));
7896  if ('_childs' in hitem) hitem._isopen = true;
7897  return;
7898  }
7899 
7900  if (hitem != this.h)
7901  if (status)
7902  hitem._isopen = true;
7903  else
7904  delete hitem._isopen;
7905 
7906  for (var i=0; i < hitem._childs.length; ++i)
7907  this.toggle(status, hitem._childs[i]);
7908 
7909  if (h==null) this.RefreshHtml();
7910  }
7911 
7912  JSROOT.HierarchyPainter.prototype.get = function(arg, call_back, options) {
7913  // get object item with specified name
7914  // depending from provided option, same item can generate different object types
7915 
7916  var itemname = (typeof arg == 'object') ? arg.arg : arg;
7917 
7918  var item = this.Find( { name: itemname, allow_index: true } );
7919 
7920  // if item not found, try to find nearest parent which could allow us to get inside
7921  var d = (item!=null) ? null : this.Find({ name: itemname, last_exists: true, check_keys: true, allow_index: true });
7922 
7923  // if item not found, try to expand hierarchy central function
7924  // implements not process get in central method of hierarchy item (if exists)
7925  // if last_parent found, try to expand it
7926  if ((d !== null) && ('last' in d) && (d.last !== null)) {
7927  var hpainter = this;
7928  var parentname = this.itemFullName(d.last);
7929 
7930  // this is indication that expand does not give us better path to searched item
7931  if ((typeof arg == 'object') && ('rest' in arg))
7932  if ((arg.rest == d.rest) || (arg.rest.length <= d.rest.length))
7933  return JSROOT.CallBack(call_back);
7934 
7935  return this.expand(parentname, function(res) {
7936  if (!res) JSROOT.CallBack(call_back);
7937  var newparentname = hpainter.itemFullName(d.last);
7938  hpainter.get( { arg: newparentname + "/" + d.rest, rest: d.rest }, call_back, options);
7939  });
7940  }
7941 
7942  // check if item already has assigned object
7943 
7944  if ((item !== null) && (typeof item._obj == 'object'))
7945  return JSROOT.CallBack(call_back, item, item._obj);
7946 
7947  // normally search _get method in the parent items
7948  var curr = item;
7949  while (curr != null) {
7950  if (('_get' in curr) && (typeof curr._get == 'function'))
7951  return curr._get(item, null, call_back, options);
7952  curr = ('_parent' in curr) ? curr._parent : null;
7953  }
7954 
7955  JSROOT.CallBack(call_back, item, null);
7956  }
7957 
7958  JSROOT.HierarchyPainter.prototype.draw = function(divid, obj, drawopt) {
7959  // just envelope, one should be able to redefine it for sub-classes
7960  return JSROOT.draw(divid, obj, drawopt);
7961  }
7962 
7963  JSROOT.HierarchyPainter.prototype.redraw = function(divid, obj, drawopt) {
7964  // just envelope, one should be able to redefine it for sub-classes
7965  return JSROOT.redraw(divid, obj, drawopt);
7966  }
7967 
7968  JSROOT.HierarchyPainter.prototype.player = function(itemname, option, call_back) {
7969  var item = this.Find(itemname);
7970 
7971  if (!item || !('_player' in item)) return JSROOT.CallBack(call_back, null);
7972 
7973  var hpainter = this;
7974 
7975  var prereq = ('_prereq' in item) ? item['_prereq'] : '';
7976 
7977  JSROOT.AssertPrerequisites(prereq, function() {
7978 
7979  var player_func = JSROOT.findFunction(item._player);
7980  if (player_func == null) return JSROOT.CallBack(call_back, null);
7981 
7982  hpainter.CreateDisplay(function(mdi) {
7983  var res = null;
7984  if (mdi) res = player_func(hpainter, itemname, option);
7985  JSROOT.CallBack(call_back, res);
7986  });
7987  });
7988  }
7989 
7990  JSROOT.HierarchyPainter.prototype.canDisplay = function(item, drawopt) {
7991  if (item == null) return false;
7992  if ('_player' in item) return true;
7993  if (drawopt == 'inspect') return true;
7994  var handle = JSROOT.getDrawHandle(item._kind, drawopt);
7995  return (handle!=null) && ('func' in handle);
7996  }
7997 
7998  JSROOT.HierarchyPainter.prototype.display = function(itemname, drawopt, call_back) {
7999  var h = this, painter = null, updating = false;
8000 
8001  function display_callback() {
8002  if (painter) painter.SetItemName(itemname, updating ? null : drawopt); // mark painter as created from hierarchy
8003  JSROOT.CallBack(call_back, painter, itemname);
8004  }
8005 
8006  h.CreateDisplay(function(mdi) {
8007 
8008  if (!mdi) return display_callback();
8009 
8010  var item = h.Find(itemname);
8011 
8012  if ((item!=null) && ('_player' in item))
8013  return h.player(itemname, drawopt, display_callback);
8014 
8015  updating = (typeof(drawopt)=='string') && (drawopt.indexOf("update:")==0);
8016 
8017  if (updating) {
8018  drawopt = drawopt.substr(7);
8019  if ((item==null) || ('_doing_update' in item)) return display_callback();
8020  item._doing_update = true;
8021  }
8022 
8023  if (item!=null) {
8024  if (!h.canDisplay(item, drawopt)) return display_callback();
8025  }
8026 
8027  var divid = "";
8028  if ((typeof(drawopt)=='string') && (drawopt.indexOf("divid:")>=0)) {
8029  var pos = drawopt.indexOf("divid:");
8030  divid = drawopt.slice(pos+6);
8031  drawopt = drawopt.slice(0, pos);
8032  }
8033 
8034  JSROOT.progress("Loading " + itemname);
8035 
8036  h.get(itemname, function(item, obj) {
8037 
8038  JSROOT.progress();
8039 
8040  if (updating && item) delete item['_doing_update'];
8041  if (obj==null) return display_callback();
8042 
8043  JSROOT.progress("Drawing " + itemname);
8044 
8045  if (divid.length > 0) {
8046  painter = updating ? h.redraw(divid, obj, drawopt) : h.draw(divid, obj, drawopt);
8047  } else {
8048  mdi.ForEachPainter(function(p, frame) {
8049  if (p.GetItemName() != itemname) return;
8050  // verify that object was drawn with same option as specified now (if any)
8051  if (!updating && (drawopt!=null) && (p.GetItemDrawOpt()!=drawopt)) return;
8052  painter = p;
8053  mdi.ActivateFrame(frame);
8054  painter.RedrawObject(obj);
8055  });
8056  }
8057 
8058  if (painter==null) {
8059  if (updating) {
8060  JSROOT.console("something went wrong - did not found painter when doing update of " + itemname);
8061  } else {
8062  var frame = mdi.FindFrame(itemname, true);
8063  d3.select(frame).html("");
8064  mdi.ActivateFrame(frame);
8065  painter = h.draw(d3.select(frame).attr("id"), obj, drawopt);
8066  if (JSROOT.gStyle.DragAndDrop)
8067  h.enable_dropping(frame, itemname);
8068  }
8069  }
8070 
8071  JSROOT.progress();
8072 
8073  if (painter === null) return display_callback();
8074 
8075  painter.WhenReady(display_callback);
8076 
8077  }, drawopt);
8078  });
8079  }
8080 
8081  JSROOT.HierarchyPainter.prototype.enable_dragging = function(element, itemname) {
8082  // here is not defined - implemented with jquery
8083  }
8084 
8085  JSROOT.HierarchyPainter.prototype.enable_dropping = function(frame, itemname) {
8086  // here is not defined - implemented with jquery
8087  }
8088 
8089  JSROOT.HierarchyPainter.prototype.dropitem = function(itemname, divid, call_back) {
8090  var h = this;
8091 
8092  h.get(itemname, function(item, obj) {
8093  if (obj!=null) {
8094  var painter = h.draw(divid, obj, "same");
8095  if (painter) painter.WhenReady(function() { painter.SetItemName(itemname); });
8096  }
8097 
8098  JSROOT.CallBack(call_back);
8099  });
8100 
8101  return true;
8102  }
8103 
8104  JSROOT.HierarchyPainter.prototype.updateItems = function(items) {
8105  // argument is item name or array of string with items name
8106  // only already drawn items will be update with same draw option
8107 
8108  if ((this.disp == null) || (items==null)) return;
8109 
8110  var draw_items = [], draw_options = [];
8111 
8112  this.disp.ForEachPainter(function(p) {
8113  var itemname = p.GetItemName();
8114  if ((itemname==null) || (draw_items.indexOf(itemname)>=0)) return;
8115  if (typeof items == 'array') {
8116  if (items.indexOf(itemname) < 0) return;
8117  } else {
8118  if (items != itemname) return;
8119  }
8120  draw_items.push(itemname);
8121  draw_options.push("update:" + p.GetItemDrawOpt());
8122  }, true); // only visible panels are considered
8123 
8124  if (draw_items.length > 0)
8125  this.displayAll(draw_items, draw_options);
8126  }
8127 
8128 
8129  JSROOT.HierarchyPainter.prototype.updateAll = function(only_auto_items, only_items) {
8130  // method can be used to fetch new objects and update all existing drawings
8131  // if only_auto_items specified, only automatic items will be updated
8132 
8133  if (this.disp == null) return;
8134 
8135  var allitems = [], options = [], hpainter = this;
8136 
8137  // first collect items
8138  this.disp.ForEachPainter(function(p) {
8139  var itemname = p.GetItemName();
8140  var drawopt = p.GetItemDrawOpt();
8141  if ((itemname==null) || (allitems.indexOf(itemname)>=0)) return;
8142 
8143  var item = hpainter.Find(itemname);
8144  if ((item==null) || ('_not_monitor' in item) || ('_player' in item)) return;
8145  var forced = false;
8146 
8147  if ('_always_monitor' in item) {
8148  forced = true;
8149  } else {
8150  var handle = JSROOT.getDrawHandle(item._kind);
8151  if (handle && ('monitor' in handle)) {
8152  if ((handle.monitor===false) || (handle.monitor=='never')) return;
8153  if (handle.monitor==='always') forced = true;
8154  }
8155  }
8156 
8157  if (forced || !only_auto_items) {
8158  allitems.push(itemname);
8159  options.push("update:" + drawopt);
8160  }
8161  }, true); // only visible panels are considered
8162 
8163  var painter = this;
8164 
8165  // force all files to read again (normally in non-browser mode)
8166  if (this.files_monitoring)
8167  this.ForEachRootFile(function(item) {
8168  painter.ForEach(function(fitem) { delete fitem._readobj; }, item);
8169  delete item._file;
8170  });
8171 
8172  this.displayAll(allitems, options);
8173  }
8174 
8175  JSROOT.HierarchyPainter.prototype.displayAll = function(items, options, call_back) {
8176 
8177  if ((items == null) || (items.length == 0)) return JSROOT.CallBack(call_back);
8178 
8179  var h = this;
8180 
8181  if (options == null) options = [];
8182  while (options.length < items.length)
8183  options.push("");
8184 
8185  if ((options.length == 1) &&( options[0] == "iotest")) {
8186  h.clear();
8187  d3.select("#" + h['disp_frameid']).html("<h2>Start I/O test "+ ('IO' in JSROOT ? "Mode=" + JSROOT.IO.Mode : "") + "</h2>")
8188 
8189  var tm0 = new Date();
8190  return h.get(items[0], function(item, obj) {
8191  var tm1 = new Date();
8192  d3.select("#" + h['disp_frameid']).append("h2").html("Item " + items[0] + " reading time = " + (tm1.getTime() - tm0.getTime()) + "ms");
8193  return JSROOT.CallBack(call_back);
8194  });
8195  }
8196 
8197  var dropitems = new Array(items.length);
8198 
8199  // First of all check that items are exists, look for cycle extension
8200  for (var i = 0; i < items.length; ++i) {
8201  dropitems[i] = null;
8202  if (h.Find(items[i])) continue;
8203  if (h.Find(items[i] + ";1")) { items[i] += ";1"; continue; }
8204 
8205  var pos = items[i].indexOf("+");
8206  if (pos>0) {
8207  dropitems[i] = items[i].split("+");
8208  items[i] = dropitems[i].shift();
8209  // allow to specify _same_ item in different file
8210  for (var j = 0; j < dropitems[i].length; ++j) {
8211  var pos = dropitems[i][j].indexOf("_same_");
8212  if ((pos>0) && (h.Find(dropitems[i][j])==null))
8213  dropitems[i][j] = dropitems[i][j].substr(0,pos) + items[i].substr(pos);
8214  }
8215  }
8216 
8217  // also check if subsequent items has _same_, than use name from first item
8218  var pos = items[i].indexOf("_same_");
8219  if ((pos>0) && !h.Find(items[i]) && (i>0))
8220  items[i] = items[i].substr(0,pos) + items[0].substr(pos);
8221  }
8222 
8223  // now check that items can be displayed
8224  for (var n = items.length-1; n>=0; n--) {
8225  var hitem = h.Find(items[n]);
8226  if ((hitem==null) || h.canDisplay(hitem, options[n])) continue;
8227  // try to expand specified item
8228  h.expand(items[n]);
8229  items.splice(n, 1);
8230  options.splice(n, 1);
8231  dropitems.splice(n,1);
8232  }
8233 
8234  if (items.length == 0) return JSROOT.CallBack(call_back);
8235 
8236  h.CreateDisplay(function(mdi) {
8237  if (!mdi) return JSROOT.CallBack(call_back);
8238 
8239  // Than create empty frames for each item
8240  for (var i = 0; i < items.length; ++i)
8241  if (options[i].indexOf('update:')!=0)
8242  mdi.CreateFrame(items[i]);
8243 
8244  // We start display of all items parallel
8245  for (var i = 0; i < items.length; ++i)
8246  h.display(items[i], options[i], function(painter, itemname) {
8247  // one cannot use index i in callback - it is asynchron
8248  var indx = items.indexOf(itemname);
8249  if (indx<0) return JSROOT.console('did not found item ' + itemname);
8250 
8251  items[indx] = "---"; // mark item as ready
8252 
8253  function DropNextItem() {
8254  if ((painter!=null) && (dropitems[indx]!=null) && (dropitems[indx].length>0))
8255  return h.dropitem(dropitems[indx].shift(), painter.divid, DropNextItem);
8256 
8257  var isany = false;
8258  for (var cnt = 0; cnt < items.length; ++cnt)
8259  if (items[cnt]!='---') isany = true;
8260 
8261  // only when items drawn and all sub-items dropped, one could perform call-back
8262  if (!isany) JSROOT.CallBack(call_back);
8263  }
8264 
8265  DropNextItem();
8266  });
8267  });
8268  }
8269 
8270  JSROOT.HierarchyPainter.prototype.reload = function() {
8271  var hpainter = this;
8272  if ('_online' in this.h)
8273  this.OpenOnline(this.h['_online'], function() {
8274  hpainter.RefreshHtml();
8275  });
8276  }
8277 
8278  JSROOT.HierarchyPainter.prototype.expand = function(itemname, call_back, d3cont) {
8279  var hpainter = this;
8280 
8281  var hitem = this.Find(itemname);
8282 
8283  if (!hitem && d3cont) return JSROOT.CallBack(call_back);
8284 
8285  function DoExpandItem(_item, _obj, _name) {
8286  if (!_name) _name = hpainter.itemFullName(_item);
8287 
8288  // try to use expand function
8289  if (_obj && _item && typeof _item._expand == 'function') {
8290  if (_item._expand(_item, _obj)) {
8291  _item._isopen = true;
8292  if (typeof hpainter.UpdateTreeNode == 'function')
8293  hpainter.UpdateTreeNode(_item, d3cont);
8294  JSROOT.CallBack(call_back, _item);
8295  return true;
8296  }
8297  }
8298 
8299  if (!('_expand' in _item)) {
8300  var handle = JSROOT.getDrawHandle(_item._kind);
8301  if (handle && ('expand' in handle)) {
8302  JSROOT.AssertPrerequisites(handle.prereq, function() {
8303  _item._expand = JSROOT.findFunction(handle.expand);
8304  if (typeof _item._expand != 'function') { delete _item._expand; return; }
8305  hpainter.expand(_name, call_back, d3cont);
8306  });
8307  return true;
8308  }
8309  }
8310 
8311  if (_obj && JSROOT.Painter.ObjectHierarchy(_item, _obj)) {
8312  _item._isopen = true;
8313  if (typeof hpainter.UpdateTreeNode == 'function')
8314  hpainter.UpdateTreeNode(_item, d3cont);
8315  JSROOT.CallBack(call_back, _item);
8316  return true;
8317  }
8318 
8319  return false;
8320  }
8321 
8322  if (hitem) {
8323  // item marked as it cannot be expanded
8324  if (('_more' in hitem) && !hitem._more) return JSROOT.CallBack(call_back);
8325 
8326  if (DoExpandItem(hitem, hitem._obj, itemname)) return;
8327  }
8328 
8329  JSROOT.progress("Loading " + itemname);
8330 
8331  this.get(itemname, function(item, obj) {
8332 
8333  JSROOT.progress();
8334 
8335  if (obj && DoExpandItem(item, obj)) return;
8336 
8337  JSROOT.CallBack(call_back);
8338  }, "hierarchy_expand" ); // indicate that we getting element for expand, can handle it differently
8339 
8340  }
8341 
8342  JSROOT.HierarchyPainter.prototype.GetTopOnlineItem = function(item) {
8343  if (item!=null) {
8344  while ((item!=null) && (!('_online' in item))) item = item._parent;
8345  return item;
8346  }
8347 
8348  if (this.h==null) return null;
8349  if ('_online' in this.h) return this.h;
8350  if ((this.h._childs!=null) && ('_online' in this.h._childs[0])) return this.h._childs[0];
8351  return null;
8352  }
8353 
8354 
8355  JSROOT.HierarchyPainter.prototype.ForEachJsonFile = function(call_back) {
8356  if (this.h==null) return;
8357  if ('_jsonfile' in this.h)
8358  return JSROOT.CallBack(call_back, this.h);
8359 
8360  if (this.h._childs!=null)
8361  for (var n = 0; n < this.h._childs.length; ++n) {
8362  var item = this.h._childs[n];
8363  if ('_jsonfile' in item) JSROOT.CallBack(call_back, item);
8364  }
8365  }
8366 
8367  JSROOT.HierarchyPainter.prototype.OpenJsonFile = function(filepath, call_back) {
8368  var isfileopened = false;
8369  this.ForEachJsonFile(function(item) { if (item._jsonfile==filepath) isfileopened = true; });
8370  if (isfileopened) return JSROOT.CallBack(call_back);
8371 
8372  var pthis = this;
8373  JSROOT.NewHttpRequest(filepath,'object', function(res) {
8374  if (res == null) return JSROOT.CallBack(call_back);
8375  var h1 = { _jsonfile : filepath, _kind : "ROOT." + res._typename, _jsontmp : res, _name: filepath.split("/").pop() };
8376  if ('fTitle' in res) h1._title = res.fTitle;
8377  h1._get = function(item,itemname,callback) {
8378  if ('_jsontmp' in item) {
8379  var res = item._jsontmp;
8380  delete item._jsontmp;
8381  return JSROOT.CallBack(callback, item, res);
8382  }
8383  JSROOT.NewHttpRequest(item._jsonfile, 'object', function(res) {
8384  return JSROOT.CallBack(callback, item, res);
8385  }).send(null);
8386  }
8387  if (pthis.h == null) pthis.h = h1; else
8388  if (pthis.h._kind == 'TopFolder') pthis.h._childs.push(h1); else {
8389  var h0 = pthis.h;
8390  var topname = ('_jsonfile' in h0) ? "Files" : "Items";
8391  pthis.h = { _name: topname, _kind: 'TopFolder', _childs : [h0, h1] };
8392  }
8393 
8394  pthis.RefreshHtml(call_back);
8395  }).send(null);
8396  }
8397 
8398  JSROOT.HierarchyPainter.prototype.ForEachRootFile = function(call_back) {
8399  if (this.h==null) return;
8400  if ((this.h._kind == "ROOT.TFile") && (this.h._file!=null))
8401  return JSROOT.CallBack(call_back, this.h);
8402 
8403  if (this.h._childs != null)
8404  for (var n = 0; n < this.h._childs.length; ++n) {
8405  var item = this.h._childs[n];
8406  if ((item._kind == 'ROOT.TFile') && ('_fullurl' in item))
8407  JSROOT.CallBack(call_back, item);
8408  }
8409  }
8410 
8411  JSROOT.HierarchyPainter.prototype.OpenRootFile = function(filepath, call_back) {
8412  // first check that file with such URL already opened
8413 
8414  var isfileopened = false;
8415  this.ForEachRootFile(function(item) { if (item._fullurl==filepath) isfileopened = true; });
8416  if (isfileopened) return JSROOT.CallBack(call_back);
8417 
8418  var pthis = this;
8419 
8420  JSROOT.OpenFile(filepath, function(file) {
8421  if (file == null) return JSROOT.CallBack(call_back);
8422  var h1 = pthis.FileHierarchy(file);
8423  h1._isopen = true;
8424  if (pthis.h == null) pthis.h = h1; else
8425  if (pthis.h._kind == 'TopFolder') pthis.h._childs.push(h1); else {
8426  var h0 = pthis.h;
8427  var topname = (h0._kind == "ROOT.TFile") ? "Files" : "Items";
8428  pthis.h = { _name: topname, _kind: 'TopFolder', _childs : [h0, h1] };
8429  }
8430 
8431  pthis.RefreshHtml(call_back);
8432  });
8433  }
8434 
8435  JSROOT.HierarchyPainter.prototype.GetFileProp = function(itemname) {
8436  var item = this.Find(itemname);
8437  if (item == null) return null;
8438 
8439  var subname = item._name;
8440  while (item._parent != null) {
8441  item = item._parent;
8442  if ('_file' in item) {
8443  return {
8444  fileurl : item._file.fURL,
8445  itemname : subname
8446  };
8447  }
8448  subname = item._name + "/" + subname;
8449  }
8450 
8451  return null;
8452  }
8453 
8454  JSROOT.MarkAsStreamerInfo = function(h,item,obj) {
8455  // this function used on THttpServer to mark streamer infos list
8456  // as fictional TStreamerInfoList class, which has special draw function
8457  if ((obj!=null) && (obj._typename=='TList'))
8458  obj._typename = 'TStreamerInfoList';
8459  }
8460 
8461  JSROOT.HierarchyPainter.prototype.GetOnlineItemUrl = function(item) {
8462  // returns URL, which could be used to request item from the online server
8463  if ((item!=null) && (typeof item == "string")) item = this.Find(item);
8464  var top = this.GetTopOnlineItem(item);
8465  if (item==null) return null;
8466 
8467  var urlpath = this.itemFullName(item, top);
8468  if (top && ('_online' in top) && (top._online!="")) urlpath = top._online + urlpath;
8469  return urlpath;
8470  }
8471 
8472  JSROOT.HierarchyPainter.prototype.GetOnlineItem = function(item, itemname, callback, option) {
8473  // method used to request object from the http server
8474 
8475  var url = itemname, h_get = false, req = "", req_kind = "object", pthis = this, draw_handle = null;
8476 
8477  if (option === 'hierarchy_expand') { h_get = true; option = undefined; }
8478 
8479  if (item != null) {
8480  url = this.GetOnlineItemUrl(item);
8481  var func = null;
8482  if ('_kind' in item) draw_handle = JSROOT.getDrawHandle(item._kind);
8483 
8484  if (h_get) {
8485  req = 'h.json?compact=3';
8486  item._expand = JSROOT.Painter.OnlineHierarchy; // use proper expand function
8487  } else
8488  if ('_make_request' in item) {
8489  func = JSROOT.findFunction(item._make_request);
8490  } else
8491  if ((draw_handle!=null) && ('make_request' in draw_handle)) {
8492  func = draw_handle.make_request;
8493  }
8494 
8495  if (typeof func == 'function') {
8496  // ask to make request
8497  var dreq = func(pthis, item, url, option);
8498  // result can be simple string or object with req and kind fields
8499  if (dreq!=null)
8500  if (typeof dreq == 'string') req = dreq; else {
8501  if ('req' in dreq) req = dreq.req;
8502  if ('kind' in dreq) req_kind = dreq.kind;
8503  }
8504  }
8505 
8506  if ((req.length==0) && (item._kind.indexOf("ROOT.")!=0))
8507  req = 'item.json.gz?compact=3';
8508  }
8509 
8510  if ((itemname==null) && (item!=null) && ('_cached_draw_object' in this) && (req.length == 0)) {
8511  // special handling for drawGUI when cashed
8512  var obj = this._cached_draw_object;
8513  delete this._cached_draw_object;
8514  return JSROOT.CallBack(callback, item, obj);
8515  }
8516 
8517  if (req.length == 0) req = 'root.json.gz?compact=3';
8518 
8519  if (url.length > 0) url += "/";
8520  url += req;
8521 
8522  var itemreq = JSROOT.NewHttpRequest(url, req_kind, function(obj) {
8523 
8524  var func = null;
8525 
8526  if (!h_get && (item!=null) && ('_after_request' in item)) {
8527  func = JSROOT.findFunction(item._after_request);
8528  } else
8529  if ((draw_handle!=null) && ('after_request' in draw_handle))
8530  func = draw_handle.after_request;
8531 
8532  if (typeof func == 'function') {
8533  var res = func(pthis, item, obj, option, itemreq);
8534  if ((res!=null) && (typeof res == "object")) obj = res;
8535  }
8536 
8537  JSROOT.CallBack(callback, item, obj);
8538  });
8539 
8540  itemreq.send(null);
8541  }
8542 
8543  JSROOT.Painter.OnlineHierarchy = function(node, obj) {
8544  // central function for expand of all online items
8545 
8546  if ((obj != null) && (node != null) && ('_childs' in obj)) {
8547 
8548  for (var n=0;n<obj._childs.length;++n)
8549  if (obj._childs[n]._more || obj._childs[n]._childs)
8550  obj._childs[n]._expand = JSROOT.Painter.OnlineHierarchy;
8551 
8552  node._childs = obj._childs;
8553  obj._childs = null;
8554  return true;
8555  }
8556 
8557  return false;
8558  }
8559 
8560  JSROOT.HierarchyPainter.prototype.OpenOnline = function(server_address, user_callback) {
8561  var painter = this;
8562 
8563  function AdoptHierarchy(result) {
8564  painter.h = result;
8565  if (painter.h == null) return;
8566 
8567  if (('_title' in painter.h) && (painter.h._title!='')) document.title = painter.h._title;
8568 
8569  result._isopen = true;
8570 
8571  // mark top hierarchy as online data and
8572  painter.h._online = server_address;
8573 
8574  painter.h._get = function(item, itemname, callback, option) {
8575  painter.GetOnlineItem(item, itemname, callback, option);
8576  }
8577 
8578  painter.h._expand = JSROOT.Painter.OnlineHierarchy;
8579 
8580  var scripts = "", modules = "";
8581  painter.ForEach(function(item) {
8582  if ('_childs' in item) item._expand = JSROOT.Painter.OnlineHierarchy;
8583 
8584  if ('_autoload' in item) {
8585  var arr = item._autoload.split(";");
8586  for (var n = 0; n < arr.length; ++n)
8587  if ((arr[n].length>3) &&
8588  ((arr[n].lastIndexOf(".js")==arr[n].length-3) ||
8589  (arr[n].lastIndexOf(".css")==arr[n].length-4))) {
8590  if (scripts.indexOf(arr[n])<0) scripts+=arr[n]+";";
8591  } else {
8592  if (modules.indexOf(arr[n])<0) modules+=arr[n]+";";
8593  }
8594  }
8595  });
8596 
8597  if (scripts.length > 0) scripts = "user:" + scripts;
8598 
8599  // use AssertPrerequisites, while it protect us from race conditions
8600  JSROOT.AssertPrerequisites(modules + scripts, function() {
8601 
8602  painter.ForEach(function(item) {
8603  if (!('_drawfunc' in item) || !('_kind' in item)) return;
8604  var typename = "kind:" + item._kind;
8605  if (item._kind.indexOf('ROOT.')==0) typename = item._kind.slice(5);
8606  var drawopt = item['_drawopt'];
8607  if (!JSROOT.canDraw(typename) || (drawopt!=null))
8608  JSROOT.addDrawFunc({ name: typename, func: item['_drawfunc'], script:item['_drawscript'], opt: drawopt});
8609  });
8610 
8611  JSROOT.CallBack(user_callback, painter);
8612  });
8613  }
8614 
8615  if (!server_address) server_address = "";
8616 
8617  if (typeof server_address == 'object') {
8618  var h = server_address;
8619  server_address = "";
8620  return AdoptHierarchy(h);
8621  }
8622 
8623  JSROOT.NewHttpRequest(server_address + "h.json?compact=3", 'object', AdoptHierarchy).send(null);
8624  }
8625 
8626  JSROOT.HierarchyPainter.prototype.GetOnlineProp = function(itemname) {
8627  var item = this.Find(itemname);
8628  if (item == null) return null;
8629 
8630  var subname = item._name;
8631  while (item._parent != null) {
8632  item = item._parent;
8633 
8634  if ('_online' in item) {
8635  return {
8636  server : item._online,
8637  itemname : subname
8638  };
8639  }
8640  subname = item._name + "/" + subname;
8641  }
8642 
8643  return null;
8644  }
8645 
8646  JSROOT.HierarchyPainter.prototype.FillOnlineMenu = function(menu, onlineprop, itemname) {
8647 
8648  var painter = this;
8649 
8650  var node = this.Find(itemname);
8651  var opts = JSROOT.getDrawOptions(node._kind, 'nosame');
8652  var handle = JSROOT.getDrawHandle(node._kind);
8653  var root_type = ('_kind' in node) ? node._kind.indexOf("ROOT.") == 0 : false;
8654 
8655  if (opts != null)
8656  menu.addDrawMenu("Draw", opts, function(arg) { painter.display(itemname, arg); });
8657 
8658  if ((node['_childs'] == null) && (node['_more'] || root_type))
8659  menu.add("Expand", function() { painter.expand(itemname); });
8660 
8661  if (handle && ('execute' in handle))
8662  menu.add("Execute", function() { painter.ExecuteCommand(itemname, menu.tree_node); });
8663 
8664  var drawurl = onlineprop.server + onlineprop.itemname + "/draw.htm";
8665  var separ = "?";
8666  if (this.IsMonitoring()) {
8667  drawurl += separ + "monitoring=" + this.MonitoringInterval();
8668  separ = "&";
8669  }
8670 
8671  if (opts != null)
8672  menu.addDrawMenu("Draw in new window", opts, function(arg) { window.open(drawurl+separ+"opt=" +arg); });
8673 
8674  if ((opts!=null) && (opts.length > 0) && root_type)
8675  menu.addDrawMenu("Draw as png", opts, function(arg) {
8676  window.open(onlineprop.server + onlineprop.itemname + "/root.png?w=400&h=300&opt=" + arg);
8677  });
8678 
8679  if ('_player' in node)
8680  menu.add("Player", function() { painter.player(itemname); });
8681  }
8682 
8683  JSROOT.HierarchyPainter.prototype.Adopt = function(h) {
8684  this.h = h;
8685  this.RefreshHtml();
8686  }
8687 
8688  JSROOT.HierarchyPainter.prototype.SetMonitoring = function(val) {
8689  this._monitoring_on = false;
8690  this._monitoring_interval = 3000;
8691 
8692  val = (val === undefined) ? 0 : parseInt(val);
8693 
8694  if (!isNaN(val) && (val>0)) {
8695  this._monitoring_on = true;
8696  this._monitoring_interval = Math.max(100,val);
8697  }
8698  }
8699 
8700  JSROOT.HierarchyPainter.prototype.MonitoringInterval = function(val) {
8701  // returns interval
8702  return ('_monitoring_interval' in this) ? this._monitoring_interval : 3000;
8703  }
8704 
8705  JSROOT.HierarchyPainter.prototype.EnableMonitoring = function(on) {
8706  this._monitoring_on = on;
8707  }
8708 
8709  JSROOT.HierarchyPainter.prototype.IsMonitoring = function() {
8710  return this._monitoring_on;
8711  }
8712 
8713  JSROOT.HierarchyPainter.prototype.SetDisplay = function(layout, frameid) {
8714  if ((frameid==null) && (typeof layout == 'object')) {
8715  this.disp = layout;
8716  this.disp_kind = 'custom';
8717  this.disp_frameid = null;
8718  } else {
8719  this.disp_kind = layout;
8720  this.disp_frameid = frameid;
8721  }
8722  }
8723 
8724  JSROOT.HierarchyPainter.prototype.GetLayout = function() {
8725  return this.disp_kind;
8726  }
8727 
8728  JSROOT.HierarchyPainter.prototype.clear = function(withbrowser) {
8729  if ('disp' in this) {
8730  this.disp.Reset();
8731  delete this.disp;
8732  }
8733 
8734  if (withbrowser) {
8735  this.select_main().html("");
8736  delete this.h;
8737  } else {
8738  // when only display cleared, try to clear all browser items
8739  this.ForEach(function(item) {
8740  if (('clear' in item) && (typeof item.clear=='function')) item.clear();
8741  });
8742  }
8743  }
8744 
8745  JSROOT.HierarchyPainter.prototype.GetDisplay = function() {
8746  return ('disp' in this) ? this.disp : null;
8747  }
8748 
8749  JSROOT.HierarchyPainter.prototype.CreateDisplay = function(callback) {
8750 
8751  var h = this;
8752 
8753  if ('disp' in this) {
8754  if ((h.disp.NumDraw() > 0) || (h.disp_kind == "custom")) return JSROOT.CallBack(callback, h.disp);
8755  h.disp.Reset();
8756  delete h.disp;
8757  }
8758 
8759  // check that we can found frame where drawing should be done
8760  if (document.getElementById(this.disp_frameid) == null)
8761  return JSROOT.CallBack(callback, null);
8762 
8763  if (h.disp_kind == "simple")
8764  h.disp = new JSROOT.SimpleDisplay(h.disp_frameid);
8765  else
8766  if (h.disp_kind.search("grid") == 0)
8767  h.disp = new JSROOT.GridDisplay(h.disp_frameid, h.disp_kind);
8768  else
8769  return JSROOT.AssertPrerequisites('jq2d', function() { h.CreateDisplay(callback); });
8770 
8771  JSROOT.CallBack(callback, h.disp);
8772  }
8773 
8774  JSROOT.HierarchyPainter.prototype.updateOnOtherFrames = function(painter, obj) {
8775  // function should update object drawings for other painters
8776  var mdi = this.disp;
8777  if (mdi==null) return false;
8778 
8779  var isany = false;
8780  mdi.ForEachPainter(function(p, frame) {
8781  if ((p===painter) || (p.GetItemName() != painter.GetItemName())) return;
8782  mdi.ActivateFrame(frame);
8783  p.RedrawObject(obj);
8784  isany = true;
8785  });
8786  return isany;
8787  }
8788 
8789  JSROOT.HierarchyPainter.prototype.CheckResize = function(size) {
8790  if ('disp' in this)
8791  this.disp.CheckMDIResize(null, size);
8792  }
8793 
8794  JSROOT.HierarchyPainter.prototype.StartGUI = function(h0, call_back) {
8795  var hpainter = this;
8796  var filesarr = JSROOT.GetUrlOptionAsArray("file;files");
8797  var jsonarr = JSROOT.GetUrlOptionAsArray("json");
8798  var filesdir = JSROOT.GetUrlOption("path");
8799  var expanditems = JSROOT.GetUrlOptionAsArray("expand");
8800  if (expanditems.length==0 && (JSROOT.GetUrlOption("expand")=="")) expanditems.push("");
8801 
8802  if (filesdir!=null) {
8803  for (var i=0;i<filesarr.length;++i) filesarr[i] = filesdir + filesarr[i];
8804  for (var i=0;i<jsonarr.length;++i) jsonarr[i] = filesdir + jsonarr[i];
8805  }
8806 
8807  var itemsarr = JSROOT.GetUrlOptionAsArray("item;items");
8808  if ((itemsarr.length==0) && JSROOT.GetUrlOption("item")=="") itemsarr.push("");
8809 
8810  var optionsarr = JSROOT.GetUrlOptionAsArray("opt;opts");
8811 
8812  var monitor = JSROOT.GetUrlOption("monitoring");
8813 
8814  if ((jsonarr.length==1) && (itemsarr.length==0) && (expanditems.length==0)) itemsarr.push("");
8815 
8816  if (!this.disp_kind) {
8817  var layout = JSROOT.GetUrlOption("layout");
8818  if ((typeof layout == "string") && (layout.length>0))
8819  this.disp_kind = layout;
8820  else
8821  switch (itemsarr.length) {
8822  case 0:
8823  case 1: this.disp_kind = 'simple'; break;
8824  case 2: this.disp_kind = 'grid 1x2'; break;
8825  default: this.disp_kind = 'flex';
8826  }
8827  }
8828 
8829  if (JSROOT.GetUrlOption('files_monitoring')!=null) this.files_monitoring = true;
8830 
8831  //JSROOT.RegisterForResize(this);
8832 
8833  this.SetMonitoring(monitor);
8834 
8835  function OpenAllFiles() {
8836  if (jsonarr.length>0)
8837  hpainter.OpenJsonFile(jsonarr.shift(), OpenAllFiles);
8838  else if (filesarr.length>0)
8839  hpainter.OpenRootFile(filesarr.shift(), OpenAllFiles);
8840  else if (expanditems.length>0)
8841  hpainter.expand(expanditems.shift(), OpenAllFiles);
8842  else
8843  hpainter.displayAll(itemsarr, optionsarr, function() {
8844  hpainter.RefreshHtml();
8845 
8846  JSROOT.RegisterForResize(hpainter);
8847 
8848  setInterval(function() { hpainter.updateAll(!hpainter.IsMonitoring()); }, hpainter.MonitoringInterval());
8849 
8850  JSROOT.CallBack(call_back);
8851  });
8852  }
8853 
8854  function AfterOnlineOpened() {
8855  // check if server enables monitoring
8856  if (('_monitoring' in hpainter.h) && (monitor==null)) {
8857  hpainter.SetMonitoring(hpainter.h._monitoring);
8858  }
8859 
8860  if (('_layout' in hpainter.h) && (layout==null)) {
8861  hpainter.disp_kind = hpainter.h._layout;
8862  }
8863 
8864  if (('_loadfile' in hpainter.h) && (filesarr.length==0)) {
8865  filesarr = JSROOT.ParseAsArray(hpainter.h._loadfile);
8866  }
8867 
8868  if (('_drawitem' in hpainter.h) && (itemsarr.length==0)) {
8869  itemsarr = JSROOT.ParseAsArray(hpainter.h._drawitem);
8870  optionsarr = JSROOT.ParseAsArray(hpainter.h['_drawopt']);
8871  }
8872 
8873  OpenAllFiles();
8874  }
8875 
8876  if (h0!=null) hpainter.OpenOnline(h0, AfterOnlineOpened);
8877  else OpenAllFiles();
8878  }
8879 
8880  JSROOT.BuildNobrowserGUI = function() {
8881  var myDiv = d3.select('#simpleGUI');
8882  var online = false, drawing = false;
8883 
8884  if (myDiv.empty()) {
8885  online = true;
8886  myDiv = d3.select('#onlineGUI');
8887  if (myDiv.empty()) { myDiv = d3.select('#drawGUI'); drawing = true; }
8888  if (myDiv.empty()) return alert('no div for simple nobrowser gui found');
8889  }
8890 
8891  JSROOT.Painter.readStyleFromURL();
8892 
8893  d3.select('html').style('height','100%');
8894  d3.select('body').style({ 'min-height':'100%', 'margin':'0px', "overflow": "hidden"});
8895 
8896  myDiv.style({"position":"absolute", "left":"1px", "top" :"1px", "bottom" :"1px", "right": "1px"});
8897 
8898  var hpainter = new JSROOT.HierarchyPainter('root', null);
8899  hpainter.SetDisplay(JSROOT.GetUrlOption("layout", null, "simple"), myDiv.attr('id'));
8900 
8901  var h0 = null;
8902  if (online) {
8903  var func = JSROOT.findFunction('GetCachedHierarchy');
8904  if (typeof func == 'function') h0 = func();
8905  if (typeof h0 != 'object') h0 = "";
8906  }
8907 
8908  hpainter.StartGUI(h0, function() {
8909  if (!drawing) return;
8910  var func = JSROOT.findFunction('GetCachedObject');
8911  var obj = (typeof func == 'function') ? JSROOT.JSONR_unref(func()) : null;
8912  if (obj!=null) hpainter['_cached_draw_object'] = obj;
8913  var opt = JSROOT.GetUrlOption("opt");
8914  hpainter.display("", opt);
8915  });
8916  }
8917 
8918  JSROOT.Painter.drawStreamerInfo = function(divid, lst) {
8919  var painter = new JSROOT.HierarchyPainter('sinfo', divid, 'white');
8920 
8921  painter.h = { _name : "StreamerInfo", _childs : [] };
8922 
8923  for ( var i = 0; i < lst.arr.length; ++i) {
8924  var entry = lst.arr[i]
8925 
8926  if (entry._typename == "TList") continue;
8927 
8928  if (typeof (entry.fName) == 'undefined') {
8929  JSROOT.console("strange element in StreamerInfo with type " + entry._typename);
8930  continue;
8931  }
8932 
8933  var item = {
8934  _name : entry.fName + ";" + entry.fClassVersion,
8935  _kind : "class " + entry.fName,
8936  _title : "class:" + entry.fName + ' version:' + entry.fClassVersion + ' checksum:' + entry.fCheckSum,
8937  _icon: "img_class",
8938  _childs : []
8939  };
8940 
8941  if (entry.fTitle != '') item._title += ' ' + entry.fTitle;
8942 
8943  painter.h._childs.push(item);
8944 
8945  if (typeof entry.fElements == 'undefined') continue;
8946  for ( var l = 0; l < entry.fElements.arr.length; ++l) {
8947  var elem = entry.fElements.arr[l];
8948  if ((elem == null) || (typeof (elem.fName) == 'undefined')) continue;
8949  var info = elem.fTypeName + " " + elem.fName + ";";
8950  if (elem.fTitle != '') info += " // " + elem.fTitle;
8951  item._childs.push({ _name : info, _title: elem.fTypeName, _kind:elem.fTypeName, _icon: (elem.fTypeName == 'BASE') ? "img_class" : "img_member" });
8952  }
8953  if (item._childs.length == 0) delete item._childs;
8954  }
8955 
8956 
8957  painter.RefreshHtml(function() {
8958  painter.SetDivId(divid);
8959  painter.DrawingReady();
8960  });
8961 
8962  return painter;
8963  }
8964 
8965  JSROOT.Painter.drawInspector = function(divid, obj) {
8966  var painter = new JSROOT.HierarchyPainter('inspector', divid, 'white');
8967  painter.default_by_click = "expand"; // that painter tries to do by default
8968  painter.with_icons = false;
8969  painter.h = { _name: "Object", _title: "ROOT." + obj._typename, _click_action: "expand", _nosimple: false };
8970  if ((typeof obj.fName === 'string') && (obj.fName.length>0))
8971  painter.h._name = obj.fName;
8972 
8973  JSROOT.Painter.ObjectHierarchy(painter.h, obj);
8974  painter.RefreshHtml(function() {
8975  painter.SetDivId(divid);
8976  painter.DrawingReady();
8977  });
8978 
8979  return painter;
8980  }
8981 
8982  // ================================================================
8983 
8984  // JSROOT.MDIDisplay - class to manage multiple document interface for drawings
8985 
8986  JSROOT.MDIDisplay = function(frameid) {
8987  this.frameid = frameid;
8988  d3.select("#"+this.frameid).property('mdi', this);
8989  }
8990 
8991  JSROOT.MDIDisplay.prototype.ForEachFrame = function(userfunc, only_visible) {
8992  // method dedicated to iterate over existing panels
8993  // provided userfunc is called with arguemnts (frame)
8994 
8995  console.warn("ForEachFrame not implemented in MDIDisplay");
8996  }
8997 
8998  JSROOT.MDIDisplay.prototype.ForEachPainter = function(userfunc, only_visible) {
8999  // method dedicated to iterate over existing panles
9000  // provided userfunc is called with arguemnts (painter, frame)
9001 
9002  this.ForEachFrame(function(frame) {
9003  var dummy = new JSROOT.TObjectPainter();
9004  dummy.SetDivId(d3.select(frame).attr('id'), -1);
9005  dummy.ForEachPainter(function(painter) { userfunc(painter, frame); });
9006  }, only_visible);
9007  }
9008 
9009  JSROOT.MDIDisplay.prototype.NumDraw = function() {
9010  var cnt = 0;
9011  this.ForEachFrame(function() { ++cnt; });
9012  return cnt;
9013  }
9014 
9015  JSROOT.MDIDisplay.prototype.FindFrame = function(searchtitle, force) {
9016  var found_frame = null;
9017 
9018  this.ForEachFrame(function(frame) {
9019  if (d3.select(frame).attr('frame_title') == searchtitle)
9020  found_frame = frame;
9021  });
9022 
9023  if ((found_frame == null) && force)
9024  found_frame = this.CreateFrame(searchtitle);
9025 
9026  return found_frame;
9027  }
9028 
9029  JSROOT.MDIDisplay.prototype.ActivateFrame = function(frame) {
9030  // do nothing by default
9031  }
9032 
9033  JSROOT.MDIDisplay.prototype.CheckMDIResize = function(only_frame_id, size) {
9034  // perform resize for each frame
9035  var resized_frame = null;
9036 
9037  this.ForEachPainter(function(painter, frame) {
9038 
9039  if ((only_frame_id !== null) && (d3.select(frame).attr('id') != only_frame_id)) return;
9040 
9041  if ((painter.GetItemName()!==null) && (typeof painter.CheckResize == 'function')) {
9042  // do not call resize for many painters on the same frame
9043  if (resized_frame === frame) return;
9044  painter.CheckResize(size);
9045  resized_frame = frame;
9046  }
9047  });
9048  }
9049 
9050  JSROOT.MDIDisplay.prototype.Reset = function() {
9051 
9052  this.ForEachFrame(function(frame) {
9053  JSROOT.cleanup(frame);
9054  });
9055 
9056  d3.select("#"+this.frameid).html("").property('mdi', null);
9057  }
9058 
9059  JSROOT.MDIDisplay.prototype.Draw = function(title, obj, drawopt) {
9060  // draw object with specified options
9061  if (!obj) return;
9062 
9063  if (!JSROOT.canDraw(obj._typename, drawopt)) return;
9064 
9065  var frame = this.FindFrame(title, true);
9066 
9067  this.ActivateFrame(frame);
9068 
9069  return JSROOT.redraw(d3.select(frame).attr("id"), obj, drawopt);
9070  }
9071 
9072 
9073  // ==================================================
9074 
9075  JSROOT.CustomDisplay = function() {
9076  JSROOT.MDIDisplay.call(this, "dummy");
9077  this.frames = {}; // array of configured frames
9078  }
9079 
9080  JSROOT.CustomDisplay.prototype = Object.create(JSROOT.MDIDisplay.prototype);
9081 
9082  JSROOT.CustomDisplay.prototype.AddFrame = function(divid, itemname) {
9083  if (!(divid in this.frames)) this.frames[divid] = "";
9084 
9085  this.frames[divid] += (itemname + ";");
9086  }
9087 
9088  JSROOT.CustomDisplay.prototype.ForEachFrame = function(userfunc, only_visible) {
9089  var ks = Object.keys(this.frames);
9090  for (var k = 0; k < ks.length; ++k) {
9091  var node = d3.select("#"+ks[k]);
9092  if (!node.empty())
9093  JSROOT.CallBack(userfunc, node.node());
9094  }
9095  }
9096 
9097  JSROOT.CustomDisplay.prototype.CreateFrame = function(title) {
9098  var ks = Object.keys(this.frames);
9099  for (var k = 0; k < ks.length; ++k) {
9100  var items = this.frames[ks[k]];
9101  if (items.indexOf(title+";")>=0)
9102  return d3.select("#"+ks[k]).node();
9103  }
9104  return null;
9105  }
9106 
9107  JSROOT.CustomDisplay.prototype.Reset = function() {
9108  JSROOT.MDIDisplay.prototype.Reset.call(this);
9109  this.ForEachFrame(function(frame) {
9110  d3.select(frame).html("");
9111  });
9112  }
9113 
9114  // ==================================================
9115 
9116  JSROOT.SimpleDisplay = function(frameid) {
9117  JSROOT.MDIDisplay.call(this, frameid);
9118  }
9119 
9120  JSROOT.SimpleDisplay.prototype = Object.create(JSROOT.MDIDisplay.prototype);
9121 
9122  JSROOT.SimpleDisplay.prototype.ForEachFrame = function(userfunc, only_visible) {
9123  var node = d3.select("#"+this.frameid + "_simple_display");
9124  if (!node.empty())
9125  JSROOT.CallBack(userfunc, node.node());
9126  }
9127 
9128  JSROOT.SimpleDisplay.prototype.CreateFrame = function(title) {
9129 
9130  JSROOT.cleanup(this.frameid+"_simple_display");
9131 
9132  return d3.select("#"+this.frameid)
9133  .html("")
9134  .append("div")
9135  .attr("id", this.frameid + "_simple_display")
9136  .style("width", "100%")
9137  .style("height", "100%")
9138  .style("overflow" ,"hidden")
9139  .attr("frame_title", title)
9140  .node();
9141  }
9142 
9143  JSROOT.SimpleDisplay.prototype.Reset = function() {
9144  JSROOT.MDIDisplay.prototype.Reset.call(this);
9145  // try to remove different properties from the div
9146  d3.select("#"+this.frameid).html("");
9147  }
9148 
9149  // ================================================
9150 
9151  JSROOT.GridDisplay = function(frameid, sizex, sizey) {
9152  // create grid display object
9153  // one could use followinf arguments
9154  // new JSROOT.GridDisplay('yourframeid','4x4');
9155  // new JSROOT.GridDisplay('yourframeid','3x2');
9156  // new JSROOT.GridDisplay('yourframeid', 3, 4);
9157 
9158  JSROOT.MDIDisplay.call(this, frameid);
9159  this.cnt = 0;
9160  if (typeof sizex == "string") {
9161  if (sizex.search("grid") == 0)
9162  sizex = sizex.slice(4).trim();
9163 
9164  var separ = sizex.search("x");
9165 
9166  if (separ > 0) {
9167  sizey = parseInt(sizex.slice(separ + 1));
9168  sizex = parseInt(sizex.slice(0, separ));
9169  } else {
9170  sizex = parseInt(sizex);
9171  sizey = sizex;
9172  }
9173 
9174  if (isNaN(sizex)) sizex = 3;
9175  if (isNaN(sizey)) sizey = 3;
9176  }
9177 
9178  if (!sizex) sizex = 3;
9179  if (!sizey) sizey = sizex;
9180  this.sizex = sizex;
9181  this.sizey = sizey;
9182  }
9183 
9184  JSROOT.GridDisplay.prototype = Object.create(JSROOT.MDIDisplay.prototype);
9185 
9186  JSROOT.GridDisplay.prototype.NumGridFrames = function() {
9187  return this.sizex*this.sizey;
9188  }
9189 
9190  JSROOT.GridDisplay.prototype.IsSingle = function() {
9191  return (this.sizex == 1) && (this.sizey == 1);
9192  }
9193 
9194  JSROOT.GridDisplay.prototype.ForEachFrame = function(userfunc, only_visible) {
9195  for (var cnt = 0; cnt < this.sizex * this.sizey; ++cnt) {
9196  var elem = this.IsSingle() ? d3.select("#"+this.frameid) : d3.select("#" + this.frameid + "_grid_" + cnt);
9197 
9198  if (!elem.empty() && elem.attr('frame_title') != '')
9199  JSROOT.CallBack(userfunc, elem.node());
9200  }
9201  }
9202 
9203  JSROOT.GridDisplay.prototype.CreateFrame = function(title) {
9204 
9205  var main = d3.select("#" + this.frameid);
9206  if (main.empty()) return null;
9207 
9208  var drawid = this.frameid;
9209 
9210  if (!this.IsSingle()) {
9211  var topid = this.frameid + '_grid';
9212  if (d3.select("#" + topid).empty()) {
9213  var rect = main.node().getBoundingClientRect();
9214  var h = Math.floor(rect.height / this.sizey) - 1;
9215  var w = Math.floor(rect.width / this.sizex) - 1;
9216 
9217  var content = "<div style='width:100%; height:100%; margin:0; padding:0; border:0; overflow:hidden'>"+
9218  "<table id='" + topid + "' style='width:100%; height:100%; table-layout:fixed; border-collapse: collapse;'>";
9219  var cnt = 0;
9220  for (var i = 0; i < this.sizey; ++i) {
9221  content += "<tr>";
9222  for (var j = 0; j < this.sizex; ++j)
9223  content += "<td><div id='" + topid + "_" + cnt++ + "' class='grid_cell'></div></td>";
9224  content += "</tr>";
9225  }
9226  content += "</table></div>";
9227 
9228  main.html(content);
9229  main.selectAll('.grid_cell').style({ 'width': w + 'px', 'height': h + 'px', 'overflow' : 'hidden'});
9230  }
9231 
9232  drawid = topid + "_" + this.cnt;
9233  if (++this.cnt >= this.sizex * this.sizey) this.cnt = 0;
9234  }
9235 
9236  JSROOT.cleanup(drawid);
9237 
9238  return d3.select("#" + drawid).html("").attr('frame_title', title).node();
9239  }
9240 
9241  JSROOT.GridDisplay.prototype.Reset = function() {
9242  JSROOT.MDIDisplay.prototype.Reset.call(this);
9243  if (this.IsSingle())
9244  d3.select("#" + this.frameid).attr('frame_title', null);
9245  this.cnt = 0;
9246  }
9247 
9248  JSROOT.GridDisplay.prototype.CheckMDIResize = function(frame_id, size) {
9249 
9250  if (!this.IsSingle()) {
9251  var main = d3.select("#" + this.frameid);
9252  var rect = main.node().getBoundingClientRect();
9253  var h = Math.floor(rect.height / this.sizey) - 1;
9254  var w = Math.floor(rect.width / this.sizex) - 1;
9255  main.selectAll('.grid_cell').style({ 'width': w + 'px', 'height': h + 'px'});
9256  }
9257 
9258  JSROOT.MDIDisplay.prototype.CheckMDIResize.call(this, frame_id, size);
9259  }
9260 
9261  // =========================================================================
9262 
9263  JSROOT.RegisterForResize = function(handle, delay) {
9264  // function used to react on browser window resize event
9265  // While many resize events could come in short time,
9266  // resize will be handled with delay after last resize event
9267  // handle can be function or object with CheckResize function
9268  // one could specify delay after which resize event will be handled
9269 
9270  if ((handle===null) || (handle === undefined)) return;
9271 
9272  var myInterval = null, myDelay = delay ? delay : 300;
9273 
9274  if (myDelay < 20) myDelay = 20;
9275 
9276  function ResizeTimer() {
9277  myInterval = null;
9278 
9279  document.body.style.cursor = 'wait';
9280  if (typeof handle == 'function') handle(); else
9281  if ((typeof handle == 'object') && (typeof handle.CheckResize == 'function')) handle.CheckResize(); else
9282  if (typeof handle == 'string') {
9283  var node = d3.select('#'+handle);
9284  if (!node.empty()) {
9285  var mdi = node.property('mdi');
9286  if (mdi) {
9287  mdi.CheckMDIResize();
9288  } else {
9289  JSROOT.resize(node.node());
9290  }
9291  }
9292  }
9293  document.body.style.cursor = 'auto';
9294  }
9295 
9296  function ProcessResize() {
9297  if (myInterval !== null) clearTimeout(myInterval);
9298  myInterval = setTimeout(ResizeTimer, myDelay);
9299  }
9300 
9301  window.addEventListener('resize', ProcessResize);
9302  }
9303 
9304  JSROOT.addDrawFunc({ name: "TCanvas", icon: "img_canvas", func: JSROOT.Painter.drawCanvas });
9305  JSROOT.addDrawFunc({ name: "TPad", icon: "img_canvas", func: JSROOT.Painter.drawPad });
9306  JSROOT.addDrawFunc({ name: "TSlider", icon: "img_canvas", func: JSROOT.Painter.drawPad });
9307  JSROOT.addDrawFunc({ name: "TFrame", icon: "img_frame", func: JSROOT.Painter.drawFrame });
9308  JSROOT.addDrawFunc({ name: "TPaveText", icon: "img_pavetext", func: JSROOT.Painter.drawPaveText });
9309  JSROOT.addDrawFunc({ name: "TPaveStats", icon: "img_pavetext", func: JSROOT.Painter.drawPaveText });
9310  JSROOT.addDrawFunc({ name: "TPaveLabel", icon: "img_pavelabel", func: JSROOT.Painter.drawPaveText });
9311  JSROOT.addDrawFunc({ name: "TLatex", icon:"img_text", func: JSROOT.Painter.drawText });
9312  JSROOT.addDrawFunc({ name: "TMathText", icon:"img_text", func: JSROOT.Painter.drawText });
9313  JSROOT.addDrawFunc({ name: "TText", icon:"img_text", func: JSROOT.Painter.drawText });
9314  JSROOT.addDrawFunc({ name: /^TH1/, icon: "img_histo1d", func: JSROOT.Painter.drawHistogram1D, opt:";hist;P;P0;E;E1;E2;same"});
9315  JSROOT.addDrawFunc({ name: "TProfile", icon: "img_profile", func: JSROOT.Painter.drawHistogram1D, opt:";E0;E1;E2;p;hist"});
9316  JSROOT.addDrawFunc({ name: /^TH2/, icon: "img_histo2d", prereq: "more2d", func: "JSROOT.Painter.drawHistogram2D", opt:";COL;COLZ;COL0Z;BOX;SCAT;TEXT;LEGO;same" });
9317  JSROOT.addDrawFunc({ name: /^TH3/, icon: 'img_histo3d', prereq: "3d", func: "JSROOT.Painter.drawHistogram3D" });
9318  JSROOT.addDrawFunc({ name: "THStack", prereq: "more2d", func: "JSROOT.Painter.drawHStack" });
9319  JSROOT.addDrawFunc({ name: "TPolyMarker3D", icon: 'img_histo3d', prereq: "3d", func: "JSROOT.Painter.drawPolyMarker3D" });
9320  JSROOT.addDrawFunc({ name: "TGraphPolargram" }); // just dummy entry to avoid drawing of this object
9321  JSROOT.addDrawFunc({ name: /^TGraph/, icon:"img_graph", prereq: "more2d", func: "JSROOT.Painter.drawGraph", opt:";L;P"});
9322  JSROOT.addDrawFunc({ name: "TCutG", icon:"img_graph", prereq: "more2d", func: "JSROOT.Painter.drawGraph", opt:";L;P"});
9323  JSROOT.addDrawFunc({ name: /^RooHist/, icon:"img_graph", prereq: "more2d", func: "JSROOT.Painter.drawGraph", opt:";L;P" });
9324  JSROOT.addDrawFunc({ name: /^RooCurve/, icon:"img_graph", prereq: "more2d", func: "JSROOT.Painter.drawGraph", opt:";L;P" });
9325  JSROOT.addDrawFunc({ name: "TMultiGraph", icon:"img_mgraph", prereq: "more2d", func: "JSROOT.Painter.drawMultiGraph" });
9326  JSROOT.addDrawFunc({ name: "TStreamerInfoList", icon:'img_question', func: JSROOT.Painter.drawStreamerInfo });
9327  JSROOT.addDrawFunc({ name: "TPaletteAxis", icon: "img_colz", prereq: "more2d", func: "JSROOT.Painter.drawPaletteAxis" });
9328  JSROOT.addDrawFunc({ name: "kind:Text", icon:"img_text", func: JSROOT.Painter.drawRawText });
9329  JSROOT.addDrawFunc({ name: "TF1", icon: "img_graph", prereq: "math;more2d", func: "JSROOT.Painter.drawFunction" });
9330  JSROOT.addDrawFunc({ name: "TEllipse", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawEllipse" });
9331  JSROOT.addDrawFunc({ name: "TLine", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawLine" });
9332  JSROOT.addDrawFunc({ name: "TArrow", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawArrow" });
9333  JSROOT.addDrawFunc({ name: "TGaxis", icon: "img_graph", func: JSROOT.drawGaxis });
9334  JSROOT.addDrawFunc({ name: "TLegend", icon: "img_pavelabel", prereq: "more2d", func: "JSROOT.Painter.drawLegend" });
9335  JSROOT.addDrawFunc({ name: "TBox", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawBox" });
9336  JSROOT.addDrawFunc({ name: "TWbox", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawBox" });
9337  JSROOT.addDrawFunc({ name: "TSliderBox", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawBox" });
9338  JSROOT.addDrawFunc({ name: "TGeoVolume", icon: 'img_histo3d', prereq: "geom", func: "JSROOT.Painter.drawGeometry", expand: "JSROOT.expandGeoVolume", opt:"all;count;limit;maxlvl2;" });
9339  JSROOT.addDrawFunc({ name: "TEveGeoShapeExtract", icon: 'img_histo3d', prereq: "geom", func: "JSROOT.Painter.drawGeometry", opt: ";count;limit;maxlvl2" });
9340  JSROOT.addDrawFunc({ name: "TGeoManager", icon: 'img_histo3d', prereq: "geom", expand: "JSROOT.expandGeoManagerHierarchy" });
9341  JSROOT.addDrawFunc({ name: /^TGeo/, icon: 'img_histo3d', prereq: "geom", func: "JSROOT.Painter.drawGeoObject", opt: "all" });
9342  // these are not draw functions, but provide extra info about correspondent classes
9343  JSROOT.addDrawFunc({ name: "kind:Command", icon: "img_execute", execute: true });
9344  JSROOT.addDrawFunc({ name: "TFolder", icon: "img_folder", icon2: "img_folderopen", noinspect: true, expand: JSROOT.Painter.FolderHierarchy });
9345  JSROOT.addDrawFunc({ name: "TTask", icon: "img_task", expand: JSROOT.Painter.TaskHierarchy, for_derived: true });
9346  JSROOT.addDrawFunc({ name: "TTree", icon: "img_tree", noinspect:true, expand: JSROOT.Painter.TreeHierarchy });
9347  JSROOT.addDrawFunc({ name: "TNtuple", icon: "img_tree", noinspect:true, expand: JSROOT.Painter.TreeHierarchy });
9348  JSROOT.addDrawFunc({ name: "TBranch", icon: "img_branch", noinspect:true });
9349  JSROOT.addDrawFunc({ name: /^TLeaf/, icon: "img_leaf" });
9350  JSROOT.addDrawFunc({ name: "TList", icon: "img_list", noinspect:true, expand: JSROOT.Painter.ListHierarchy });
9351  JSROOT.addDrawFunc({ name: "TObjArray", icon: "img_list", noinspect:true, expand: JSROOT.Painter.ListHierarchy });
9352  JSROOT.addDrawFunc({ name: "TClonesArray", icon: "img_list", noinspect:true, expand: JSROOT.Painter.ListHierarchy });
9353  JSROOT.addDrawFunc({ name: "TColor", icon: "img_color" });
9354  JSROOT.addDrawFunc({ name: "TFile", icon: "img_file", noinspect:true });
9355  JSROOT.addDrawFunc({ name: "TMemFile", icon: "img_file", noinspect:true });
9356  JSROOT.addDrawFunc({ name: "Session", icon: "img_globe" });
9357  JSROOT.addDrawFunc({ name: "kind:TopFolder", icon: "img_base" });
9358  JSROOT.addDrawFunc({ name: "kind:Folder", icon: "img_folder", icon2: "img_folderopen", noinspect:true });
9359 
9360  JSROOT.getDrawHandle = function(kind, selector) {
9361  // return draw handle for specified item kind
9362  // kind could be ROOT.TH1I for ROOT classes or just
9363  // kind string like "Command" or "Text"
9364  // selector can be used to search for draw handle with specified option (string)
9365  // or just sequence id
9366 
9367  if (typeof kind != 'string') return null;
9368  if (selector === "") selector = null;
9369 
9370  var first = null;
9371 
9372  if ((selector === null) && (kind in JSROOT.DrawFuncs.cache))
9373  return JSROOT.DrawFuncs.cache[kind];
9374 
9375  var search = (kind.indexOf("ROOT.")==0) ? kind.substr(5) : "kind:"+kind;
9376 
9377  var counter = 0;
9378  for (var i=0; i < JSROOT.DrawFuncs.lst.length; ++i) {
9379  var h = JSROOT.DrawFuncs.lst[i];
9380  if (typeof h.name == "string") {
9381  if (h.name != search) continue;
9382  } else {
9383  if (!search.match(h.name)) continue;
9384  }
9385 
9386  if (selector==null) {
9387  // store found handle in cache, can reuse later
9388  if (!(kind in JSROOT.DrawFuncs.cache)) JSROOT.DrawFuncs.cache[kind] = h;
9389  return h;
9390  } else
9391  if (typeof selector=='string') {
9392  if (first == null) first = h;
9393  // if drawoption specified, check it present in the list
9394  if ('opt' in h) {
9395  var opts = h.opt.split(';');
9396  for (var j=0; j < opts.length; ++j) opts[j] = opts[j].toLowerCase();
9397  if (opts.indexOf(selector.toLowerCase())>=0) return h;
9398  }
9399  } else {
9400  if (selector === counter) return h;
9401  }
9402  ++counter;
9403  }
9404 
9405  return first;
9406  }
9407 
9408  JSROOT.addStreamerInfos = function(lst) {
9409  if (lst === null) return;
9410 
9411  function CheckBaseClasses(si, lvl) {
9412  if (si.fElements == null) return null;
9413  if (lvl>10) return null; // protect against recursion
9414 
9415  for (var j=0; j<si.fElements.arr.length; ++j) {
9416  // extract streamer info for each class member
9417  var element = si.fElements.arr[j];
9418  if (element.fTypeName !== 'BASE') continue;
9419 
9420  var handle = JSROOT.getDrawHandle("ROOT." + element.fName);
9421  if (handle && !handle.for_derived) handle = null;
9422 
9423  // now try find that base class of base in the list
9424  if (handle === null)
9425  for (var k=0;k<lst.arr.length; ++k)
9426  if (lst.arr[k].fName === element.fName) {
9427  handle = CheckBaseClasses(lst.arr[k], lvl+1);
9428  break;
9429  }
9430 
9431  if (handle && handle.for_derived) return handle;
9432  }
9433  return null;
9434  }
9435 
9436  for (var n=0;n<lst.arr.length;++n) {
9437  var si = lst.arr[n];
9438  if (JSROOT.getDrawHandle("ROOT." + si.fName) !== null) continue;
9439 
9440  var handle = CheckBaseClasses(si, 0);
9441 
9442  if (!handle) continue;
9443 
9444  // console.log('Duplicate handle ' + handle.name + ' for derived class ' + si.fName);
9445 
9446  var newhandle = JSROOT.extend({}, handle);
9447  // delete newhandle.for_derived; // should we disable?
9448  newhandle.name = si.fName;
9449  JSROOT.DrawFuncs.lst.push(newhandle);
9450  }
9451  }
9452 
9453  // returns array with supported draw options for the specified class
9454  JSROOT.getDrawOptions = function(kind, selector) {
9455  if (typeof kind != 'string') return null;
9456  var allopts = null, isany = false, noinspect = false;
9457  for (var cnt=0;cnt<1000;++cnt) {
9458  var h = JSROOT.getDrawHandle(kind, cnt);
9459  if (h==null) break;
9460  if (h.noinspect) noinspect = true;
9461  if (!('func' in h)) break;
9462  isany = true;
9463  if (! ('opt' in h)) continue;
9464  var opts = h.opt.split(';');
9465  for (var i = 0; i < opts.length; ++i) {
9466  opts[i] = opts[i].toLowerCase();
9467  if ((selector=='nosame') && (opts[i].indexOf('same')==0)) continue;
9468 
9469  if (allopts===null) allopts = [];
9470  if (allopts.indexOf(opts[i])<0) allopts.push(opts[i]);
9471  }
9472  }
9473 
9474  if (isany && (allopts===null)) allopts = [""];
9475 
9476  // if no any handle found, let inspect ROOT-based objects
9477  if (!isany && kind.indexOf("ROOT.")==0) allopts = [];
9478 
9479  if (!noinspect && allopts)
9480  allopts.push("inspect");
9481 
9482  return allopts;
9483  }
9484 
9485  JSROOT.canDraw = function(classname) {
9486  return JSROOT.getDrawOptions("ROOT." + classname) !== null;
9487  }
9488 
9492  JSROOT.draw = function(divid, obj, opt) {
9493  if ((obj===null) || (typeof obj !== 'object')) return null;
9494 
9495  if (opt == 'inspect')
9496  return JSROOT.Painter.drawInspector(divid, obj);
9497 
9498  var handle = null, painter = null;
9499  if ('_typename' in obj) handle = JSROOT.getDrawHandle("ROOT." + obj._typename, opt);
9500  else if ('_kind' in obj) handle = JSROOT.getDrawHandle(obj._kind, opt);
9501 
9502  if ((handle==null) || !('func' in handle)) return null;
9503 
9504  function performDraw() {
9505  if ((painter===null) && ('painter_kind' in handle))
9506  painter = (handle.painter_kind == "base") ? new JSROOT.TBasePainter() : new JSROOT.TObjectPainter(obj);
9507 
9508  if (painter==null) return handle.func(divid, obj, opt);
9509 
9510  return handle.func.bind(painter)(divid, obj, opt, painter);
9511  }
9512 
9513  if (typeof handle.func == 'function') return performDraw();
9514 
9515  var funcname = "", prereq = "";
9516  if (typeof handle.func == 'object') {
9517  if ('func' in handle.func) funcname = handle.func.func;
9518  if ('script' in handle.func) prereq = "user:" + handle.func.script;
9519  } else
9520  if (typeof handle.func == 'string') {
9521  funcname = handle.func;
9522  if (('prereq' in handle) && (typeof handle.prereq == 'string')) prereq = handle.prereq;
9523  if (('script' in handle) && (typeof handle.script == 'string')) prereq += ";user:" + handle.script;
9524  }
9525 
9526  if (funcname.length==0) return null;
9527 
9528  if (prereq.length > 0) {
9529  // special handling for painters, which should be loaded via extra scripts
9530  // such painter get extra last argument - pointer on dummy painter object
9531 
9532  if (!('painter_kind' in handle))
9533  handle.painter_kind = (funcname.indexOf("JSROOT.Painter")==0) ? "object" : "base";
9534 
9535  painter = (handle.painter_kind == "base") ? new JSROOT.TBasePainter() : new JSROOT.TObjectPainter(obj);
9536 
9537  JSROOT.AssertPrerequisites(prereq, function() {
9538  var func = JSROOT.findFunction(funcname);
9539  if (func==null) {
9540  alert('Fail to find function ' + funcname + ' after loading ' + prereq);
9541  return null;
9542  }
9543 
9544  handle.func = func; // remember function once it found
9545  var ppp = performDraw();
9546 
9547  if (ppp !== painter)
9548  alert('Painter function ' + funcname + ' do not follow rules of dynamicaly loaded painters');
9549  });
9550 
9551  return painter;
9552  }
9553 
9554  var func = JSROOT.findFunction(funcname);
9555  if (func == null) return null;
9556 
9557  handle.func = func; // remember function once it found
9558  return performDraw();
9559  }
9560 
9566  JSROOT.redraw = function(divid, obj, opt) {
9567  if (obj==null) return;
9568 
9569  var dummy = new JSROOT.TObjectPainter();
9570  dummy.SetDivId(divid, -1);
9571  var can_painter = dummy.pad_painter();
9572 
9573  if (can_painter !== null) {
9574  if (obj._typename === "TCanvas") {
9575  can_painter.RedrawObject(obj);
9576  return can_painter;
9577  }
9578 
9579  for (var i = 0; i < can_painter.painters.length; ++i) {
9580  var painter = can_painter.painters[i];
9581  if (painter.MatchObjectType(obj._typename))
9582  if (painter.UpdateObject(obj)) {
9583  can_painter.RedrawPad();
9584  return painter;
9585  }
9586  }
9587  }
9588 
9589  if (can_painter)
9590  JSROOT.console("Cannot find painter to update object of type " + obj._typename);
9591 
9592  JSROOT.cleanup(divid);
9593 
9594  return JSROOT.draw(divid, obj, opt);
9595  }
9596 
9597  // Check resize of drawn element
9598  // As first argument divid one should use same argment as for the drawing
9599  // As second argument, one could specify "true" value to force redrawing of
9600  // the element even after minimal resize of the element
9601  // Or one just supply object with exact sizes like { width:300, height:200, force:true };
9602 
9603  JSROOT.resize = function(divid, arg) {
9604  if (arg === true) arg = { force: true }; else
9605  if (typeof arg !== 'object') arg = null;
9606 
9607  var dummy = new JSROOT.TObjectPainter(), done = false;
9608  dummy.SetDivId(divid, -1);
9609  dummy.ForEachPainter(function(painter) {
9610  if (!done && typeof painter['CheckResize'] == 'function')
9611  done = painter.CheckResize(arg);
9612  });
9613  }
9614 
9615  // for compatibility, keep old name
9616  JSROOT.CheckElementResize = JSROOT.resize;
9617 
9618  // safely remove all JSROOT objects from specified element
9619  JSROOT.cleanup = function(divid) {
9620  var dummy = new JSROOT.TObjectPainter();
9621  dummy.SetDivId(divid, -1);
9622  dummy.ForEachPainter(function(painter) {
9623  if (typeof painter['Cleanup'] === 'function')
9624  painter.Cleanup();
9625  });
9626  dummy.select_main().html("");
9627  }
9628 
9629  // function to display progress message in the left bottom corner
9630  // previous message will be overwritten
9631  // if no argument specified, any shown messages will be removed
9632  JSROOT.progress = function(msg) {
9633  var id = "jsroot_progressbox";
9634  var box = d3.select("#"+id);
9635 
9636  if (!JSROOT.gStyle.ProgressBox) return box.remove();
9637 
9638  if ((arguments.length == 0) || (msg === undefined) || (msg === null))
9639  return box.remove();
9640 
9641  if (box.empty()) {
9642  box = d3.select(document.body)
9643  .append("div")
9644  .attr("id", id);
9645  box.append("p");
9646  }
9647 
9648  box.select("p").html(msg);
9649  }
9650 
9651  return JSROOT;
9652 
9653 }));