otsdaq_utilities  v2_05_02_indev
JSRootPainter.js
1 (function( factory ) {
2  if ( typeof define === "function" && define.amd ) {
3  define( ['JSRootCore', 'd3'], factory );
4  } else if (typeof exports === 'object' && typeof module !== 'undefined') {
5  var jsroot = require("./JSRootCore.js");
6  factory(jsroot, require("d3"));
7  if (jsroot.nodejs) jsroot.Painter.readStyleFromURL("?interactive=0&tooltip=0&nomenu&noprogress&notouch&toolbar=0&webgl=0");
8  } else {
9  if (typeof JSROOT == 'undefined')
10  throw new Error('JSROOT is not defined', 'JSRootPainter.js');
11  if (typeof d3 != 'object')
12  throw new Error('d3 is not defined', 'JSRootPainter.js');
13  if (typeof JSROOT.Painter == 'object')
14  throw new Error('JSROOT.Painter already defined', 'JSRootPainter.js');
15  factory(JSROOT, d3);
16  }
17 } (function(JSROOT, d3) {
18 
19  "use strict";
20 
21  JSROOT.sources.push("2d");
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  if (!JSROOT._test_d3_) {
28  if ((typeof d3 == 'object') && d3.version && (d3.version[0]==="5")) {
29  if (d3.version !== '5.7.0')
30  console.log('Reuse existing d3.js ' + d3.version + ", expected 5.7.0");
31  JSROOT._test_d3_ = 5;
32  } else if ((typeof d3 == 'object') && d3.version && (d3.version[0]==="4")) {
33  if (d3.version !== '4.4.4')
34  console.warn('Try to use older d3.js ' + d3.version + ", expected 5.7.0");
35  JSROOT._test_d3_ = 4;
36  } else if ((typeof d3 == 'object') && d3.version && (d3.version[0]==="3")) {
37  console.error("Very old d3.js " + d3.version + " found, please UPGRADE");
38  d3.timeFormat = d3.time.format;
39  d3.scaleTime = d3.time.scale;
40  d3.scaleLog = d3.scale.log;
41  d3.scaleLinear = d3.scale.linear;
42  JSROOT._test_d3_ = 3;
43  } else {
44  console.error('Fail to identify d3.js version ' + (d3 ? d3.version : "???"));
45  }
46  }
47 
48  // list of user painters, called with arguments func(vis, obj, opt)
49  JSROOT.DrawFuncs = { lst:[], cache:{} };
50 
62  JSROOT.addDrawFunc = function(_name, _func, _opt) {
63  if ((arguments.length == 1) && (typeof arguments[0] == 'object')) {
64  JSROOT.DrawFuncs.lst.push(arguments[0]);
65  return arguments[0];
66  }
67  var handle = { name:_name, func:_func, opt:_opt };
68  JSROOT.DrawFuncs.lst.push(handle);
69  return handle;
70  }
71 
72  // icons taken from http://uxrepo.com/
73 
74  JSROOT.ToolbarIcons = {
75  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' },
76  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' },
77  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' },
78  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' },
79  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' },
80  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' },
81  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' },
82  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' },
83  statbox : {
84  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' +
85  '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' +
86  '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' +
87  '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'
88  },
89  circle: { path: "M256,256 m-150,0 a150,150 0 1,0 300,0 a150,150 0 1,0 -300,0" },
90  three_circles: { path: "M256,85 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 M256,255 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 M256,425 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 " },
91  diamand: { path: "M256,0L384,256L256,511L128,256z" },
92  rect: { path: "M80,80h352v352h-352z" },
93  cross: { path: "M80,40l176,176l176,-176l40,40l-176,176l176,176l-40,40l-176,-176l-176,176l-40,-40l176,-176l-176,-176z" },
94  vrgoggles: { size: "245.82 141.73", path: 'M175.56,111.37c-22.52,0-40.77-18.84-40.77-42.07S153,27.24,175.56,27.24s40.77,18.84,40.77,42.07S198.08,111.37,175.56,111.37ZM26.84,69.31c0-23.23,18.25-42.07,40.77-42.07s40.77,18.84,40.77,42.07-18.26,42.07-40.77,42.07S26.84,92.54,26.84,69.31ZM27.27,0C11.54,0,0,12.34,0,28.58V110.9c0,16.24,11.54,30.83,27.27,30.83H99.57c2.17,0,4.19-1.83,5.4-3.7L116.47,118a8,8,0,0,1,12.52-.18l11.51,20.34c1.2,1.86,3.22,3.61,5.39,3.61h72.29c15.74,0,27.63-14.6,27.63-30.83V28.58C245.82,12.34,233.93,0,218.19,0H27.27Z'},
95  CreateSVG : function(group,btn,size,title) {
96  var svg = group.append("svg:svg")
97  .attr("class", "svg_toolbar_btn")
98  .attr("width",size+"px")
99  .attr("height",size+"px")
100  .attr("viewBox", "0 0 512 512")
101  .style("overflow","hidden");
102 
103  if ('recs' in btn) {
104  var rec = {};
105  for (var n=0;n<btn.recs.length;++n) {
106  JSROOT.extend(rec, btn.recs[n]);
107  svg.append('rect').attr("x", rec.x).attr("y", rec.y)
108  .attr("width", rec.w).attr("height", rec.h)
109  .attr("fill", rec.f);
110  }
111  } else {
112  svg.append('svg:path').attr('d',btn.path);
113  }
114 
115  // special rect to correctly get mouse events for whole button area
116  svg.append("svg:rect").attr("x",0).attr("y",0).attr("width",512).attr("height",512)
117  .style('opacity',0).style('fill',"none").style("pointer-events","visibleFill")
118  .append("svg:title").text(title);
119 
120  return svg;
121  }
122  };
123 
124  // ==========================================================================================
125 
130  function DrawOptions(opt) {
131  this.opt = opt && (typeof opt=="string") ? opt.toUpperCase().trim() : "";
132  this.part = "";
133  }
134 
136  DrawOptions.prototype.empty = function() {
137  return this.opt.length === 0;
138  }
139 
141  DrawOptions.prototype.remain = function() {
142  return this.opt;
143  }
144 
146  DrawOptions.prototype.check = function(name,postpart) {
147  var pos = this.opt.indexOf(name);
148  if (pos < 0) return false;
149  this.opt = this.opt.substr(0, pos) + this.opt.substr(pos + name.length);
150  this.part = "";
151  if (!postpart) return true;
152 
153  var pos2 = pos;
154  while ((pos2 < this.opt.length) && (this.opt[pos2] !== ' ') && (this.opt[pos2] !== ',') && (this.opt[pos2] !== ';')) pos2++;
155  if (pos2 > pos) {
156  this.part = this.opt.substr(pos, pos2-pos);
157  this.opt = this.opt.substr(0, pos) + this.opt.substr(pos2);
158  }
159  return true;
160  }
161 
163  DrawOptions.prototype.partAsInt = function(offset, dflt) {
164  var val = this.part.replace(/^\D+/g, '');
165  val = val ? parseInt(val,10) : Number.NaN;
166  return isNaN(val) ? (dflt || 0) : val + (offset || 0);
167  }
168 
170  DrawOptions.prototype.partAsFloat = function(offset, dflt) {
171  var val = this.part.replace(/^\D+/g, '');
172  val = val ? parseFloat(val) : Number.NaN;
173  return isNaN(val) ? (dflt || 0) : val + (offset || 0);
174  }
175 
176  // ============================================================================================
177 
178  var Painter = {
179  Coord: {
180  kCARTESIAN : 1,
181  kPOLAR : 2,
182  kCYLINDRICAL : 3,
183  kSPHERICAL : 4,
184  kRAPIDITY : 5
185  },
186  root_colors: [],
187  root_line_styles: ["", "", "3,3", "1,2",
188  "3,4,1,4", "5,3,1,3", "5,3,1,3,1,3,1,3", "5,5",
189  "5,3,1,3,1,3", "20,5", "20,10,1,10", "1,3"],
190  root_markers: [ 0, 100, 8, 7, 0, // 0..4
191  9, 100, 100, 100, 100, // 5..9
192  100, 100, 100, 100, 100, // 10..14
193  100, 100, 100, 100, 100, // 15..19
194  100, 103, 105, 104, 0, // 20..24
195  3, 4, 2, 1, 106, // 25..29
196  6, 7, 5, 102, 101], // 30..34
197  root_fonts: ['Arial', 'iTimes New Roman',
198  'bTimes New Roman', 'biTimes New Roman', 'Arial',
199  'oArial', 'bArial', 'boArial', 'Courier New',
200  'oCourier New', 'bCourier New', 'boCourier New',
201  'Symbol', 'Times New Roman', 'Wingdings', 'iSymbol', 'Verdana'],
202  // taken from https://www.math.utah.edu/~beebe/fonts/afm-widths.html
203  root_fonts_aver_width: [ 0.537, 0.510,
204  0.535, 0.520, 0.537,
205  0.54, 0.556, 0.56, 0.6,
206  0.6, 0.6, 0.6,
207  0.587, 0.514, 0.896, 0.587, 0.55 ],
208  symbols_map: {
209  // greek letters
210  '#alpha': '\u03B1',
211  '#beta': '\u03B2',
212  '#chi': '\u03C7',
213  '#delta': '\u03B4',
214  '#varepsilon': '\u03B5',
215  '#phi': '\u03C6',
216  '#gamma': '\u03B3',
217  '#eta': '\u03B7',
218  '#iota': '\u03B9',
219  '#varphi': '\u03C6',
220  '#kappa': '\u03BA',
221  '#lambda': '\u03BB',
222  '#mu': '\u03BC',
223  '#nu': '\u03BD',
224  '#omicron': '\u03BF',
225  '#pi': '\u03C0',
226  '#theta': '\u03B8',
227  '#rho': '\u03C1',
228  '#sigma': '\u03C3',
229  '#tau': '\u03C4',
230  '#upsilon': '\u03C5',
231  '#varomega': '\u03D6',
232  '#omega': '\u03C9',
233  '#xi': '\u03BE',
234  '#psi': '\u03C8',
235  '#zeta': '\u03B6',
236  '#Alpha': '\u0391',
237  '#Beta': '\u0392',
238  '#Chi': '\u03A7',
239  '#Delta': '\u0394',
240  '#Epsilon': '\u0395',
241  '#Phi': '\u03A6',
242  '#Gamma': '\u0393',
243  '#Eta': '\u0397',
244  '#Iota': '\u0399',
245  '#vartheta': '\u03D1',
246  '#Kappa': '\u039A',
247  '#Lambda': '\u039B',
248  '#Mu': '\u039C',
249  '#Nu': '\u039D',
250  '#Omicron': '\u039F',
251  '#Pi': '\u03A0',
252  '#Theta': '\u0398',
253  '#Rho': '\u03A1',
254  '#Sigma': '\u03A3',
255  '#Tau': '\u03A4',
256  '#Upsilon': '\u03A5',
257  '#varsigma': '\u03C2',
258  '#Omega': '\u03A9',
259  '#Xi': '\u039E',
260  '#Psi': '\u03A8',
261  '#Zeta': '\u0396',
262  '#varUpsilon': '\u03D2',
263  '#epsilon': '\u03B5',
264 
265  // only required for MathJax to provide correct replacement
266  '#sqrt': '\u221A',
267  '#bar': '',
268 
269  // from TLatex tables #2 & #3
270  '#leq': '\u2264',
271  '#/': '\u2044',
272  '#infty': '\u221E',
273  '#voidb': '\u0192',
274  '#club': '\u2663',
275  '#diamond': '\u2666',
276  '#heart': '\u2665',
277  '#spade': '\u2660',
278  '#leftrightarrow': '\u2194',
279  '#leftarrow': '\u2190',
280  '#uparrow': '\u2191',
281  '#rightarrow': '\u2192',
282  '#downarrow': '\u2193',
283  '#circ': '\u02C6', // ^
284  '#pm': '\xB1',
285  '#doublequote': '\u2033',
286  '#geq': '\u2265',
287  '#times': '\xD7',
288  '#propto': '\u221D',
289  '#partial': '\u2202',
290  '#bullet': '\u2022',
291  '#divide': '\xF7',
292  '#neq': '\u2260',
293  '#equiv': '\u2261',
294  '#approx': '\u2248', // should be \u2245 ?
295  '#3dots': '\u2026',
296  '#cbar': '\x7C',
297  '#topbar': '\xAF',
298  '#downleftarrow': '\u21B5',
299  '#aleph': '\u2135',
300  '#Jgothic': '\u2111',
301  '#Rgothic': '\u211C',
302  '#voidn': '\u2118',
303  '#otimes': '\u2297',
304  '#oplus': '\u2295',
305  '#oslash': '\u2205',
306  '#cap': '\u2229',
307  '#cup': '\u222A',
308  '#supseteq': '\u2287',
309  '#supset': '\u2283',
310  '#notsubset': '\u2284',
311  '#subseteq': '\u2286',
312  '#subset': '\u2282',
313  '#int': '\u222B',
314  '#in': '\u2208',
315  '#notin': '\u2209',
316  '#angle': '\u2220',
317  '#nabla': '\u2207',
318  '#oright': '\xAE',
319  '#ocopyright': '\xA9',
320  '#trademark': '\u2122',
321  '#prod': '\u220F',
322  '#surd': '\u221A',
323  '#upoint': '\u02D9',
324  '#corner': '\xAC',
325  '#wedge': '\u2227',
326  '#vee': '\u2228',
327  '#Leftrightarrow': '\u21D4',
328  '#Leftarrow': '\u21D0',
329  '#Uparrow': '\u21D1',
330  '#Rightarrow': '\u21D2',
331  '#Downarrow': '\u21D3',
332  '#LT': '\x3C',
333  '#void1': '\xAE',
334  '#copyright': '\xA9',
335  '#void3': '\u2122',
336  '#sum': '\u2211',
337  '#arctop': '\u239B',
338  '#lbar': '\u23B8',
339  '#arcbottom': '\u239D',
340  '#void8': '',
341  '#bottombar': '\u230A',
342  '#arcbar': '\u23A7',
343  '#ltbar': '\u23A8',
344  '#AA': '\u212B',
345  '#aa': '\u00E5',
346  '#void06': '',
347  '#GT': '\x3E',
348  '#forall': '\u2200',
349  '#exists': '\u2203',
350  '#vec': '',
351  '#dot': '\u22C5',
352  '#hat': '\xB7',
353  '#ddot': '',
354  '#acute': '\acute',
355  '#grave': '',
356  '#check': '\u2713',
357  '#tilde': '\u02DC',
358  '#slash': '\u2044',
359  '#hbar': '\u0127',
360  '#box': '\u25FD',
361  '#Box': '\u2610',
362  '#parallel': '\u2225',
363  '#perp': '\u22A5',
364  '#odot': '\u2299',
365  '#left': '',
366  '#right': '',
367  '{}': ''
368  },
369  math_symbols_map: {
370  '#LT':"\\langle",
371  '#GT':"\\rangle",
372  '#club':"\\clubsuit",
373  '#spade':"\\spadesuit",
374  '#heart':"\\heartsuit",
375  '#diamond':"\\diamondsuit",
376  '#voidn':"\\wp",
377  '#voidb':"f",
378  '#copyright':"(c)",
379  '#ocopyright':"(c)",
380  '#trademark':"TM",
381  '#void3':"TM",
382  '#oright':"R",
383  '#void1':"R",
384  '#3dots':"\\ldots",
385  '#lbar':"\\mid",
386  '#void8':"\\mid",
387  '#divide':"\\div",
388  '#Jgothic':"\\Im",
389  '#Rgothic':"\\Re",
390  '#doublequote':"\"",
391  '#plus':"+",
392  '#minus':"-",
393  '#\/':"/",
394  '#upoint':".",
395  '#aa':"\\mathring{a}",
396  '#AA':"\\mathring{A}",
397  '#omicron':"o",
398  '#Alpha':"A",
399  '#Beta':"B",
400  '#Epsilon':"E",
401  '#Zeta':"Z",
402  '#Eta':"H",
403  '#Iota':"I",
404  '#Kappa':"K",
405  '#Mu':"M",
406  '#Nu':"N",
407  '#Omicron':"O",
408  '#Rho':"P",
409  '#Tau':"T",
410  '#Chi':"X",
411  '#varomega':"\\varpi",
412  '#corner':"?",
413  '#ltbar':"?",
414  '#bottombar':"?",
415  '#notsubset':"?",
416  '#arcbottom':"?",
417  '#cbar':"?",
418  '#arctop':"?",
419  '#topbar':"?",
420  '#arcbar':"?",
421  '#downleftarrow':"?",
422  '#splitline':"\\genfrac{}{}{0pt}{}",
423  '#it':"\\textit",
424  '#bf':"\\textbf",
425  '#frac':"\\frac",
426  '#left{':"\\lbrace",
427  '#right}':"\\rbrace",
428  '#left\\[':"\\lbrack",
429  '#right\\]':"\\rbrack",
430  '#\\[\\]{':"\\lbrack",
431  ' } ':"\\rbrack",
432  '#\\[':"\\lbrack",
433  '#\\]':"\\rbrack",
434  '#{':"\\lbrace",
435  '#}':"\\rbrace",
436  ' ':"\\;"
437  }
438  };
439 
440  JSROOT.Painter = Painter; // export here to avoid ambiguity
441 
442  Painter.convertSymbol = function(charactere) {
443  // example: '#pi' will give '\u03A0'
444  return Painter.symbols_map[charactere];
445  }
446 
447  Painter.createMenu = function(painter, maincallback) {
448  // dummy functions, forward call to the jquery function
449  document.body.style.cursor = 'wait';
450  JSROOT.AssertPrerequisites('hierarchy;jq2d;', function() {
451  document.body.style.cursor = 'auto';
452  Painter.createMenu(painter, maincallback);
453  });
454  }
455 
456  Painter.closeMenu = function(menuname) {
457  var x = document.getElementById(menuname || 'root_ctx_menu');
458  if (x) { x.parentNode.removeChild(x); return true; }
459  return false;
460  }
461 
462  Painter.readStyleFromURL = function(url) {
463  var optimize = JSROOT.GetUrlOption("optimize", url);
464  if (optimize=="") JSROOT.gStyle.OptimizeDraw = 2; else
465  if (optimize!==null) {
466  JSROOT.gStyle.OptimizeDraw = parseInt(optimize);
467  if (isNaN(JSROOT.gStyle.OptimizeDraw)) JSROOT.gStyle.OptimizeDraw = 2;
468  }
469 
470  var inter = JSROOT.GetUrlOption("interactive", url);
471  if (inter === "nomenu") JSROOT.gStyle.ContextMenu = false;
472  else if (inter !== null) {
473  if (!inter || (inter=="1")) inter = "111111"; else
474  if (inter=="0") inter = "000000";
475  if (inter.length === 6) {
476  if (inter[0] == "0") JSROOT.gStyle.ToolBar = false; else
477  if (inter[0] == "1") JSROOT.gStyle.ToolBar = 'popup'; else
478  if (inter[0] == "2") JSROOT.gStyle.ToolBar = true;
479  inter = inter.substr(1);
480  }
481  if (inter.length==5) {
482  JSROOT.gStyle.Tooltip = parseInt(inter[0]);
483  JSROOT.gStyle.ContextMenu = (inter[1] != '0');
484  JSROOT.gStyle.Zooming = (inter[2] != '0');
485  JSROOT.gStyle.MoveResize = (inter[3] != '0');
486  JSROOT.gStyle.DragAndDrop = (inter[4] != '0');
487  }
488  }
489 
490  var tt = JSROOT.GetUrlOption("tooltip", url);
491  if (tt !== null) JSROOT.gStyle.Tooltip = parseInt(tt);
492 
493  var mathjax = JSROOT.GetUrlOption("mathjax", url),
494  latex = JSROOT.GetUrlOption("latex", url);
495 
496  if ((mathjax!==null) && (mathjax!="0") && (latex===null)) latex = "math";
497  if (latex!==null) JSROOT.gStyle.Latex = latex; // decoding will be performed with the first text drawing
498 
499  if (JSROOT.GetUrlOption("nomenu", url)!==null) JSROOT.gStyle.ContextMenu = false;
500  if (JSROOT.GetUrlOption("noprogress", url)!==null) JSROOT.gStyle.ProgressBox = false;
501  if (JSROOT.GetUrlOption("notouch", url)!==null) JSROOT.touches = false;
502  if (JSROOT.GetUrlOption("adjframe", url)!==null) JSROOT.gStyle.CanAdjustFrame = true;
503 
504  var optstat = JSROOT.GetUrlOption("optstat", url);
505  if (optstat!==null) JSROOT.gStyle.fOptStat = parseInt(optstat);
506  var optfit = JSROOT.GetUrlOption("optfit", url);
507  if (optfit!==null) JSROOT.gStyle.fOptFit = parseInt(optfit);
508  JSROOT.gStyle.fStatFormat = JSROOT.GetUrlOption("statfmt", url, JSROOT.gStyle.fStatFormat);
509  JSROOT.gStyle.fFitFormat = JSROOT.GetUrlOption("fitfmt", url, JSROOT.gStyle.fFitFormat);
510 
511  var toolbar = JSROOT.GetUrlOption("toolbar", url);
512  if (toolbar !== null) {
513  var val = null;
514  if (toolbar.indexOf('popup')>=0) val = 'popup';
515  if (toolbar.indexOf('left')>=0) { JSROOT.gStyle.ToolBarSide = 'left'; val = 'popup'; }
516  if (toolbar.indexOf('right')>=0) { JSROOT.gStyle.ToolBarSide = 'right'; val = 'popup'; }
517  if (toolbar.indexOf('vert')>=0) { JSROOT.gStyle.ToolBarVert = true; val = 'popup'; }
518  if (toolbar.indexOf('show')>=0) val = true;
519  JSROOT.gStyle.ToolBar = val || ((toolbar.indexOf("0")<0) && (toolbar.indexOf("false")<0) && (toolbar.indexOf("off")<0));
520  }
521 
522  var palette = JSROOT.GetUrlOption("palette", url);
523  if (palette!==null) {
524  palette = parseInt(palette);
525  if (!isNaN(palette) && (palette>0) && (palette<113)) JSROOT.gStyle.Palette = palette;
526  }
527 
528  var embed3d = JSROOT.GetUrlOption("embed3d", url);
529  if (embed3d !== null) JSROOT.gStyle.Embed3DinSVG = parseInt(embed3d);
530 
531  var webgl = JSROOT.GetUrlOption("webgl", url);
532  if ((webgl === "0") || (webgl === "false")) JSROOT.gStyle.NoWebGL = true; else
533  if (webgl === "ie") JSROOT.gStyle.NoWebGL = !JSROOT.browser.isIE;
534 
535  var geosegm = JSROOT.GetUrlOption("geosegm", url);
536  if (geosegm!==null) JSROOT.gStyle.GeoGradPerSegm = Math.max(2, parseInt(geosegm));
537  var geocomp = JSROOT.GetUrlOption("geocomp", url);
538  if (geocomp!==null) JSROOT.gStyle.GeoCompressComp = (geocomp!=='0') && (geocomp!=='false');
539  }
540 
542  Painter.createRootColors = function() {
543  var colorMap = ['white','black','red','green','blue','yellow','magenta','cyan','rgb(89,212,84)','rgb(89,84,217)', 'white'];
544  colorMap[110] = 'white';
545 
546  var moreCol = [
547  {col:11,str:'c1b7ad4d4d4d6666668080809a9a9ab3b3b3cdcdcde6e6e6f3f3f3cdc8accdc8acc3c0a9bbb6a4b3a697b8a49cae9a8d9c8f83886657b1cfc885c3a48aa9a1839f8daebdc87b8f9a768a926983976e7b857d9ad280809caca6c0d4cf88dfbb88bd9f83c89a7dc08378cf5f61ac8f94a6787b946971d45a549300ff7b00ff6300ff4b00ff3300ff1b00ff0300ff0014ff002cff0044ff005cff0074ff008cff00a4ff00bcff00d4ff00ecff00fffd00ffe500ffcd00ffb500ff9d00ff8500ff6d00ff5500ff3d00ff2600ff0e0aff0022ff003aff0052ff006aff0082ff009aff00b1ff00c9ff00e1ff00f9ff00ffef00ffd700ffbf00ffa700ff8f00ff7700ff6000ff4800ff3000ff1800ff0000'},
548  {col:201,str:'5c5c5c7b7b7bb8b8b8d7d7d78a0f0fb81414ec4848f176760f8a0f14b81448ec4876f1760f0f8a1414b84848ec7676f18a8a0fb8b814ecec48f1f1768a0f8ab814b8ec48ecf176f10f8a8a14b8b848ecec76f1f1'},
549  {col:390,str:'ffffcdffff9acdcd9affff66cdcd669a9a66ffff33cdcd339a9a33666633ffff00cdcd009a9a00666600333300'},
550  {col:406,str:'cdffcd9aff9a9acd9a66ff6666cd66669a6633ff3333cd33339a3333663300ff0000cd00009a00006600003300'},
551  {col:422,str:'cdffff9affff9acdcd66ffff66cdcd669a9a33ffff33cdcd339a9a33666600ffff00cdcd009a9a006666003333'},
552  {col:590,str:'cdcdff9a9aff9a9acd6666ff6666cd66669a3333ff3333cd33339a3333660000ff0000cd00009a000066000033'},
553  {col:606,str:'ffcdffff9affcd9acdff66ffcd66cd9a669aff33ffcd33cd9a339a663366ff00ffcd00cd9a009a660066330033'},
554  {col:622,str:'ffcdcdff9a9acd9a9aff6666cd66669a6666ff3333cd33339a3333663333ff0000cd00009a0000660000330000'},
555  {col:791,str:'ffcd9acd9a669a66339a6600cd9a33ffcd66ff9a00ffcd33cd9a00ffcd00ff9a33cd66006633009a3300cd6633ff9a66ff6600ff6633cd3300ff33009aff3366cd00336600339a0066cd339aff6666ff0066ff3333cd0033ff00cdff9a9acd66669a33669a009acd33cdff669aff00cdff339acd00cdff009affcd66cd9a339a66009a6633cd9a66ffcd00ff6633ffcd00cd9a00ffcd33ff9a00cd66006633009a3333cd6666ff9a00ff9a33ff6600cd3300ff339acdff669acd33669a00339a3366cd669aff0066ff3366ff0033cd0033ff339aff0066cd00336600669a339acd66cdff009aff33cdff009acd00cdffcd9aff9a66cd66339a66009a9a33cdcd66ff9a00ffcd33ff9a00cdcd00ff9a33ff6600cd33006633009a6633cd9a66ff6600ff6633ff3300cd3300ffff339acd00666600339a0033cd3366ff669aff0066ff3366cd0033ff0033ff9acdcd669a9a33669a0066cd339aff66cdff009acd009aff33cdff009a'},
556  {col:920,str:'cdcdcd9a9a9a666666333333'}];
557 
558  for (var indx = 0; indx < moreCol.length; ++indx) {
559  var entry = moreCol[indx];
560  for (var n=0; n<entry.str.length; n+=6) {
561  var num = parseInt(entry.col) + parseInt(n/6);
562  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)) + ")";
563  }
564  }
565 
566  Painter.root_colors = colorMap;
567  }
568 
569  Painter.MakeColorRGB = function(col) {
570  if ((col==null) || (col._typename != 'TColor')) return null;
571  var rgb = Math.round(col.fRed*255) + "," + Math.round(col.fGreen*255) + "," + Math.round(col.fBlue*255);
572  if ((col.fAlpha === undefined) || (col.fAlpha == 1.))
573  rgb = "rgb(" + rgb + ")";
574  else
575  rgb = "rgba(" + rgb + "," + col.fAlpha.toFixed(3) + ")";
576 
577  switch (rgb) {
578  case 'rgb(255,255,255)': rgb = 'white'; break;
579  case 'rgb(0,0,0)': rgb = 'black'; break;
580  case 'rgb(255,0,0)': rgb = 'red'; break;
581  case 'rgb(0,255,0)': rgb = 'green'; break;
582  case 'rgb(0,0,255)': rgb = 'blue'; break;
583  case 'rgb(255,255,0)': rgb = 'yellow'; break;
584  case 'rgb(255,0,255)': rgb = 'magenta'; break;
585  case 'rgb(0,255,255)': rgb = 'cyan'; break;
586  }
587  return rgb;
588  }
589 
591  Painter.extendRootColors = function(jsarr, objarr) {
592  if (!jsarr) {
593  jsarr = [];
594  for (var n=0;n<this.root_colors.length;++n)
595  jsarr[n] = this.root_colors[n];
596  }
597 
598  if (!objarr) return jsarr;
599 
600  var rgb_array = objarr;
601  if (objarr._typename && objarr.arr) {
602  rgb_array = [];
603  for (var n = 0; n < objarr.arr.length; ++n) {
604  var col = objarr.arr[n];
605  if (!col || (col._typename != 'TColor')) continue;
606 
607  if ((col.fNumber>=0) && (col.fNumber<=10000))
608  rgb_array[col.fNumber] = Painter.MakeColorRGB(col);
609  }
610  }
611 
612 
613  for (var n = 0; n < rgb_array.length; ++n)
614  if (rgb_array[n] && (jsarr[n] != rgb_array[n]))
615  jsarr[n] = rgb_array[n];
616 
617  return jsarr;
618  }
619 
624  Painter.adoptRootColors = function(objarr) {
625  this.extendRootColors(this.root_colors, objarr);
626  }
627 
628  // =====================================================================
629 
637  function ColorPalette(arr) {
638  this.palette = arr;
639  }
640 
642  ColorPalette.prototype.calcColorIndex = function(i,len) {
643  var theColor = Math.floor((i+0.99)*this.palette.length/(len-1));
644  if (theColor > this.palette.length-1) theColor = this.palette.length-1;
645  return theColor;
646  }
647 
649  ColorPalette.prototype.getColor = function(indx) {
650  return this.palette[indx];
651  }
652 
654  ColorPalette.prototype.getLength = function() {
655  return this.palette.length;
656  }
657 
659  ColorPalette.prototype.calcColor = function(i,len) {
660  var indx = this.calcColorIndex(i,len);
661  return this.getColor(indx);
662  }
663 
664  // =============================================================================
665 
672  function TAttMarkerHandler(args) {
673  this.x0 = this.y0 = 0;
674  this.color = 'black';
675  this.style = 1;
676  this.size = 8;
677  this.scale = 1;
678  this.stroke = true;
679  this.fill = true;
680  this.marker = "";
681  this.ndig = 0;
682  this.used = true;
683  this.changed = false;
684 
685  this.func = this.Apply.bind(this);
686 
687  this.SetArgs(args);
688 
689  this.changed = false;
690  }
691 
700  TAttMarkerHandler.prototype.SetArgs = function(args) {
701  if ((typeof args == 'object') && (typeof args.fMarkerStyle == 'number')) args = { attr: args };
702 
703  if (args.attr) {
704  if (args.color === undefined) args.color = Painter.root_colors[args.attr.fMarkerColor];
705  if (!args.style || (args.style<0)) args.style = args.attr.fMarkerStyle;
706  if (!args.size) args.size = args.attr.fMarkerSize;
707  }
708 
709  this.Change(args.color, args.style, args.size);
710  }
711 
714  TAttMarkerHandler.prototype.reset_pos = function() {
715  this.lastx = this.lasty = null;
716  }
717 
726  TAttMarkerHandler.prototype.create = function(x,y) {
727  if (!this.optimized)
728  return "M" + (x+this.x0).toFixed(this.ndig)+ "," + (y+this.y0).toFixed(this.ndig) + this.marker;
729 
730  // use optimized handling with relative position
731  var xx = Math.round(x), yy = Math.round(y), m1 = "M"+xx+","+yy+"h1",
732  m2 = (this.lastx===null) ? m1 : ("m"+(xx-this.lastx)+","+(yy-this.lasty)+"h1");
733  this.lastx = xx+1; this.lasty = yy;
734  return (m2.length < m1.length) ? m2 : m1;
735  }
736 
738  TAttMarkerHandler.prototype.GetFullSize = function() {
739  return this.scale*this.size;
740  }
741 
743  TAttMarkerHandler.prototype.MarkerLength = function() {
744  return this.marker ? this.marker.length : 10;
745  }
746 
753  TAttMarkerHandler.prototype.Change = function(color, style, size) {
754  this.changed = true;
755 
756  if (color!==undefined) this.color = color;
757  if ((style!==undefined) && (style>=0)) this.style = style;
758  if (size!==undefined) this.size = size; else size = this.size;
759 
760  this.x0 = this.y0 = 0;
761 
762  if ((this.style === 1) || (this.style === 777)) {
763  this.fill = false;
764  this.marker = "h1";
765  this.size = 1;
766  this.optimized = true;
767  this.reset_pos();
768  return true;
769  }
770 
771  this.optimized = false;
772 
773  var marker_kind = Painter.root_markers[this.style];
774  if (marker_kind === undefined) marker_kind = 100;
775  var shape = marker_kind % 100;
776 
777  this.fill = (marker_kind>=100);
778 
779  switch(this.style) {
780  case 1: this.size = 1; this.scale = 1; break;
781  case 6: this.size = 2; this.scale = 1; break;
782  case 7: this.size = 3; this.scale = 1; break;
783  default: this.size = size; this.scale = 8;
784  }
785 
786  size = this.GetFullSize();
787 
788  this.ndig = (size>7) ? 0 : ((size>2) ? 1 : 2);
789  if (shape == 6) this.ndig++;
790  var half = (size/2).toFixed(this.ndig), full = size.toFixed(this.ndig);
791 
792  switch(shape) {
793  case 0: // circle
794  this.x0 = -parseFloat(half);
795  full = (parseFloat(half)*2).toFixed(this.ndig);
796  this.marker = "a"+half+","+half+",0,1,0,"+full+",0a"+half+","+half+",0,1,0,-"+full+",0z";
797  break;
798  case 1: // cross
799  var d = (size/3).toFixed(this.ndig);
800  this.x0 = this.y0 = size/6;
801  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";
802  break;
803  case 2: // diamond
804  this.x0 = -size/2;
805  this.marker = "l"+half+",-"+half+"l"+half+","+half+"l-"+half+","+half + "z";
806  break;
807  case 3: // square
808  this.x0 = this.y0 = -size/2;
809  this.marker = "v"+full+"h"+full+"v-"+full+"z";
810  break;
811  case 4: // triangle-up
812  this.y0 = size/2;
813  this.marker = "l-"+half+",-"+full+"h"+full+"z";
814  break;
815  case 5: // triangle-down
816  this.y0 = -size/2;
817  this.marker = "l-"+half+","+full+"h"+full+"z";
818  break;
819  case 6: // star
820  this.y0 = -size/2;
821  this.marker = "l" + (size/3).toFixed(this.ndig) + "," + full +
822  "l-" + (5/6*size).toFixed(this.ndig) + ",-" + (5/8*size).toFixed(this.ndig) +
823  "h" + full +
824  "l-" + (5/6*size).toFixed(this.ndig) + "," + (5/8*size).toFixed(this.ndig) + "z";
825  break;
826  case 7: // asterisk
827  this.x0 = this.y0 = -size/2;
828  this.marker = "l"+full+","+full +
829  "m0,-"+full+"l-"+full+","+full+
830  "m0,-"+half+"h"+full+"m-"+half+",-"+half+"v"+full;
831  break;
832  case 8: // plus
833  this.y0 = -size/2;
834  this.marker = "v"+full+"m-"+half+",-"+half+"h"+full;
835  break;
836  case 9: // mult
837  this.x0 = this.y0 = -size/2;
838  this.marker = "l"+full+","+full + "m0,-"+full+"l-"+full+","+full;
839  break;
840  default: // diamand
841  this.x0 = -size/2;
842  this.marker = "l"+half+",-"+half+"l"+half+","+half+"l-"+half+","+half+"z";
843  break;
844  }
845 
846  return true;
847  }
848 
849  TAttMarkerHandler.prototype.getStrokeColor = function() {
850  return this.stroke ? this.color : "none";
851  }
852 
853  TAttMarkerHandler.prototype.getFillColor = function() {
854  return this.fill ? this.color : "none";
855  }
856 
858  TAttMarkerHandler.prototype.Apply = function(selection) {
859  selection.style('stroke', this.stroke ? this.color : "none");
860  selection.style('fill', this.fill ? this.color : "none");
861  }
862 
865  TAttMarkerHandler.prototype.verifyDirectChange = function(painter) {
866  this.Change(this.color, parseInt(this.style), parseFloat(this.size));
867  }
868 
876  TAttMarkerHandler.prototype.CreateSample = function(svg, width, height) {
877  this.reset_pos();
878 
879  svg.append("path")
880  .attr("d", this.create(width/2, height/2))
881  .call(this.func);
882  }
883 
884  // =======================================================================
885 
892  function TAttLineHandler(args) {
893  this.func = this.Apply.bind(this);
894  this.used = true;
895  if (args._typename && (args.fLineStyle!==undefined)) args = { attr: args };
896 
897  this.SetArgs(args);
898  }
899 
909  TAttLineHandler.prototype.SetArgs = function(args) {
910  if (args.attr) {
911  args.color = args.color0 || Painter.root_colors[args.attr.fLineColor];
912  if (args.width===undefined) args.width = args.attr.fLineWidth;
913  args.style = args.attr.fLineStyle;
914  } else if (typeof args.color == 'string') {
915  if ((args.color !== 'none') && !args.width) args.width = 1;
916  } else if (typeof args.color == 'number') {
917  args.color = Painter.root_colors[args.color];
918  }
919 
920  if (args.width===undefined)
921  args.width = (args.color && args.color!='none') ? 1 : 0;
922 
923  this.color = (args.width===0) ? 'none' : args.color;
924  this.width = args.width;
925  this.style = args.style;
926 
927  if (args.can_excl) {
928  this.excl_side = this.excl_width = 0;
929  if (Math.abs(this.width) > 99) {
930  // exclusion graph
931  this.excl_side = (this.width < 0) ? -1 : 1;
932  this.excl_width = Math.floor(this.width / 100) * 5;
933  this.width = Math.abs(this.width % 100); // line width
934  }
935  }
936 
937  // if custom color number used, use lightgrey color to show lines
938  if (!this.color && (this.width > 0))
939  this.color = 'lightgrey';
940  }
941 
947  TAttLineHandler.prototype.ChangeExcl = function(side,width) {
948  if (width !== undefined) this.excl_width = width;
949  if (side !== undefined) {
950  this.excl_side = side;
951  if ((this.excl_width===0) && (this.excl_side!==0)) this.excl_width = 20;
952  }
953  this.changed = true;
954  }
955 
960  TAttLineHandler.prototype.empty = function() {
961  return this.color == 'none';
962  }
963 
970  TAttLineHandler.prototype.Apply = function(selection) {
971  this.used = true;
972  if (this.empty())
973  selection.style('stroke', null)
974  .style('stroke-width', null)
975  .style('stroke-dasharray', null);
976  else
977  selection.style('stroke', this.color)
978  .style('stroke-width', this.width)
979  .style('stroke-dasharray', Painter.root_line_styles[this.style] || null);
980  }
981 
987  TAttLineHandler.prototype.Change = function(color, width, style) {
988  if (color !== undefined) this.color = color;
989  if (width !== undefined) this.width = width;
990  if (style !== undefined) this.style = style;
991  this.changed = true;
992  }
993 
999  TAttLineHandler.prototype.CreateSample = function(svg, width, height) {
1000  svg.append("path")
1001  .attr("d","M0," + height/2+"h"+width)
1002  .call(this.func);
1003  }
1004 
1005  // =======================================================================
1006 
1007 
1016  function TAttFillHandler(args) {
1017  this.color = "none";
1018  this.colorindx = 0;
1019  this.pattern = 0;
1020  this.used = true;
1021  this.kind = args.kind || 2;
1022  this.changed = false;
1023  this.func = this.Apply.bind(this);
1024  this.SetArgs(args);
1025  this.changed = false; // unset change property that
1026  }
1027 
1029  TAttFillHandler.prototype.SetArgs = function(args) {
1030  if (args.attr && (typeof args.attr == 'object')) {
1031  if ((args.pattern===undefined) && (args.attr.fFillStyle!==undefined)) args.pattern = args.attr.fFillStyle;
1032  if ((args.color===undefined) && (args.attr.fFillColor!==undefined)) args.color = args.attr.fFillColor;
1033  }
1034  this.Change(args.color, args.pattern, args.svg, args.color_as_svg);
1035  }
1036 
1038  TAttFillHandler.prototype.Apply = function(selection) {
1039  this.used = true;
1040 
1041  selection.style('fill', this.fillcolor());
1042 
1043  if ('opacity' in this)
1044  selection.style('opacity', this.opacity);
1045 
1046  if ('antialias' in this)
1047  selection.style('antialias', this.antialias);
1048  }
1049 
1051  TAttFillHandler.prototype.fillcolor = function() {
1052  return this.pattern_url || this.color;
1053  }
1054 
1060  TAttFillHandler.prototype.fillcoloralt = function(altern) {
1061  return this.color && (this.color!="none") ? this.color : altern;
1062  }
1063 
1065  TAttFillHandler.prototype.empty = function() {
1066  var fill = this.fillcolor();
1067  return !fill || (fill == 'none');
1068  }
1069 
1072  TAttFillHandler.prototype.SetSolidColor = function(col) {
1073  delete this.pattern_url;
1074  this.color = col;
1075  this.pattern = 1001;
1076  }
1077 
1080  TAttFillHandler.prototype.isSolid = function(solid_color) {
1081  if (this.pattern !== 1001) return false;
1082  return !solid_color || solid_color==this.color;
1083  }
1084 
1087  TAttFillHandler.prototype.verifyDirectChange = function(painter) {
1088  if (typeof this.pattern == 'string') this.pattern = parseInt(this.pattern);
1089  if (isNaN(this.pattern)) this.pattern = 0;
1090 
1091  this.Change(this.color, this.pattern, painter ? painter.svg_canvas() : null, true);
1092  }
1093 
1101  TAttFillHandler.prototype.Change = function(color, pattern, svg, color_as_svg) {
1102  delete this.pattern_url;
1103  this.changed = true;
1104 
1105  if ((color !== undefined) && !isNaN(color) && !color_as_svg)
1106  this.colorindx = parseInt(color);
1107 
1108  if ((pattern !== undefined) && !isNaN(pattern)) {
1109  this.pattern = parseInt(pattern);
1110  delete this.opacity;
1111  delete this.antialias;
1112  }
1113 
1114  if ((this.pattern == 1000) && (this.colorindx === 0)) {
1115  this.pattern_url = 'white';
1116  return true;
1117  }
1118 
1119  if (this.pattern == 1000) this.pattern = 1001;
1120 
1121  if (this.pattern < 1001) {
1122  this.pattern_url = 'none';
1123  return true;
1124  }
1125 
1126  if (this.isSolid() && (this.colorindx===0) && (this.kind===1) && !color_as_svg) {
1127  this.pattern_url = 'none';
1128  return true;
1129  }
1130 
1131  var indx = this.colorindx;
1132 
1133  if (color_as_svg) {
1134  this.color = color;
1135  indx = 10000 + JSROOT.id_counter++; // use fictional unique index far away from existing color indexes
1136  } else {
1137  this.color = JSROOT.Painter.root_colors[indx];
1138  }
1139 
1140  if (typeof this.color != 'string') this.color = "none";
1141 
1142  if (this.isSolid()) return true;
1143 
1144  if ((this.pattern >= 4000) && (this.pattern <= 4100)) {
1145  // special transparent colors (use for subpads)
1146  this.opacity = (this.pattern - 4000)/100;
1147  return true;
1148  }
1149 
1150  if (!svg || svg.empty() || (this.pattern < 3000)) return false;
1151 
1152  var id = "pat_" + this.pattern + "_" + indx,
1153  defs = svg.select('.canvas_defs');
1154 
1155  if (defs.empty())
1156  defs = svg.insert("svg:defs",":first-child").attr("class","canvas_defs");
1157 
1158  this.pattern_url = "url(#" + id + ")";
1159  this.antialias = false;
1160 
1161  if (!defs.select("."+id).empty()) {
1162  if (color_as_svg) console.log('find id in def', id);
1163  return true;
1164  }
1165 
1166  var lines = "", lfill = null, fills = "", fills2 = "", w = 2, h = 2;
1167 
1168  switch (this.pattern) {
1169  case 3001: w = h = 2; fills = "M0,0h1v1h-1zM1,1h1v1h-1z"; break;
1170  case 3002: w = 4; h = 2; fills = "M1,0h1v1h-1zM3,1h1v1h-1z"; break;
1171  case 3003: w = h = 4; fills = "M2,1h1v1h-1zM0,3h1v1h-1z"; break;
1172  case 3004: w = h = 8; lines = "M8,0L0,8"; break;
1173  case 3005: w = h = 8; lines = "M0,0L8,8"; break;
1174  case 3006: w = h = 4; lines = "M1,0v4"; break;
1175  case 3007: w = h = 4; lines = "M0,1h4"; break;
1176  case 3008:
1177  w = h = 10;
1178  fills = "M0,3v-3h3ZM7,0h3v3ZM0,7v3h3ZM7,10h3v-3ZM5,2l3,3l-3,3l-3,-3Z";
1179  lines = "M0,3l5,5M3,10l5,-5M10,7l-5,-5M7,0l-5,5";
1180  break;
1181  case 3009: w = 12; h = 12; lines = "M0,0A6,6,0,0,0,12,0M6,6A6,6,0,0,0,12,12M6,6A6,6,0,0,1,0,12"; lfill = "none"; break;
1182  case 3010: w = h = 10; lines = "M0,2h10M0,7h10M2,0v2M7,2v5M2,7v3"; break; // bricks
1183  case 3011: w = 9; h = 18; lines = "M5,0v8M2,1l6,6M8,1l-6,6M9,9v8M6,10l3,3l-3,3M0,9v8M3,10l-3,3l3,3"; lfill = "none"; break;
1184  case 3012: w = 10; h = 20; lines = "M5,1A4,4,0,0,0,5,9A4,4,0,0,0,5,1M0,11A4,4,0,0,1,0,19M10,11A4,4,0,0,0,10,19"; lfill = "none"; break;
1185  case 3013: w = h = 7; lines = "M0,0L7,7M7,0L0,7"; lfill = "none"; break;
1186  case 3014: w = h = 16; lines = "M0,0h16v16h-16v-16M0,12h16M12,0v16M4,0v8M4,4h8M0,8h8M8,4v8"; lfill = "none"; break;
1187  case 3015: w = 6; h = 12; lines = "M2,1A2,2,0,0,0,2,5A2,2,0,0,0,2,1M0,7A2,2,0,0,1,0,11M6,7A2,2,0,0,0,6,11"; lfill = "none"; break;
1188  case 3016: w = 12; h = 7; lines = "M0,1A3,2,0,0,1,3,3A3,2,0,0,0,9,3A3,2,0,0,1,12,1"; lfill = "none"; break;
1189  case 3017: w = h = 4; lines = "M3,1l-2,2"; break;
1190  case 3018: w = h = 4; lines = "M1,1l2,2"; break;
1191  case 3019:
1192  w = h = 12;
1193  lines = "M1,6A5,5,0,0,0,11,6A5,5,0,0,0,1,6h-1h1A5,5,0,0,1,6,11v1v-1" +
1194  "A5,5,0,0,1,11,6h1h-1A5,5,0,0,1,6,1v-1v1A5,5,0,0,1,1,6";
1195  lfill = "none";
1196  break;
1197  case 3020: w = 7; h = 12; lines = "M1,0A2,3,0,0,0,3,3A2,3,0,0,1,3,9A2,3,0,0,0,1,12"; lfill = "none"; break;
1198  case 3021: w = h = 8; lines = "M8,2h-2v4h-4v2M2,0v2h-2"; lfill = "none"; break; // left stairs
1199  case 3022: w = h = 8; lines = "M0,2h2v4h4v2M6,0v2h2"; lfill = "none"; break; // right stairs
1200  case 3023: w = h = 8; fills = "M4,0h4v4zM8,4v4h-4z"; fills2 = "M4,0L0,4L4,8L8,4Z"; break;
1201  case 3024: w = h = 16; fills = "M0,8v8h2v-8zM8,0v8h2v-8M4,14v2h12v-2z"; fills2 = "M0,2h8v6h4v-6h4v12h-12v-6h-4z"; break;
1202  case 3025: w = h = 18; fills = "M5,13v-8h8ZM18,0v18h-18l5,-5h8v-8Z"; break;
1203  default:
1204  if ((this.pattern>3025) && (this.pattern<3100)) {
1205  // same as 3002, see TGX11.cxx, line 2234
1206  w = 4; h = 2; fills = "M1,0h1v1h-1zM3,1h1v1h-1z"; break;
1207  }
1208 
1209  var code = this.pattern % 1000,
1210  k = code % 10, j = ((code - k) % 100) / 10, i = (code - j*10 - k)/100;
1211  if (!i) break;
1212 
1213  var sz = i*12; // axis distance between lines
1214 
1215  w = h = 6*sz; // we use at least 6 steps
1216 
1217  function produce(dy,swap) {
1218  var pos = [], step = sz, y1 = 0, y2, max = h;
1219 
1220  // reduce step for smaller angles to keep normal distance approx same
1221  if (Math.abs(dy)<3) step = Math.round(sz/12*9);
1222  if (dy==0) { step = Math.round(sz/12*8); y1 = step/2; }
1223  else if (dy>0) max -= step; else y1 = step;
1224 
1225  while(y1<=max) {
1226  y2 = y1 + dy*step;
1227  if (y2 < 0) {
1228  var x2 = Math.round(y1/(y1-y2)*w);
1229  pos.push(0,y1,x2,0);
1230  pos.push(w,h-y1,w-x2,h);
1231  } else if (y2 > h) {
1232  var x2 = Math.round((h-y1)/(y2-y1)*w);
1233  pos.push(0,y1,x2,h);
1234  pos.push(w,h-y1,w-x2,0);
1235  } else {
1236  pos.push(0,y1,w,y2);
1237  }
1238  y1+=step;
1239  }
1240  for (var k=0;k<pos.length;k+=4)
1241  if (swap) lines += "M"+pos[k+1]+","+pos[k]+"L"+pos[k+3]+","+pos[k+2];
1242  else lines += "M"+pos[k]+","+pos[k+1]+"L"+pos[k+2]+","+pos[k+3];
1243  }
1244 
1245  switch (j) {
1246  case 0: produce(0); break;
1247  case 1: produce(1); break;
1248  case 2: produce(2); break;
1249  case 3: produce(3); break;
1250  case 4: produce(6); break;
1251  case 6: produce(3,true); break;
1252  case 7: produce(2,true); break;
1253  case 8: produce(1,true); break;
1254  case 9: produce(0,true); break;
1255  }
1256 
1257  switch (k) {
1258  case 0: if (j) produce(0); break;
1259  case 1: produce(-1); break;
1260  case 2: produce(-2); break;
1261  case 3: produce(-3); break;
1262  case 4: produce(-6); break;
1263  case 6: produce(-3,true); break;
1264  case 7: produce(-2,true); break;
1265  case 8: produce(-1,true); break;
1266  case 9: if (j!=9) produce(0,true); break;
1267  }
1268 
1269  break;
1270  }
1271 
1272  if (!fills && !lines) return false;
1273 
1274  var patt = defs.append('svg:pattern').attr("id",id).attr("class",id).attr("patternUnits","userSpaceOnUse")
1275  .attr("width", w).attr("height", h);
1276 
1277  if (fills2) {
1278  var col = d3.rgb(this.color);
1279  col.r = Math.round((col.r+255)/2); col.g = Math.round((col.g+255)/2); col.b = Math.round((col.b+255)/2);
1280  patt.append("svg:path").attr("d", fills2).style("fill", col);
1281  }
1282  if (fills) patt.append("svg:path").attr("d", fills).style("fill", this.color);
1283  if (lines) patt.append("svg:path").attr("d", lines).style('stroke', this.color).style("stroke-width", 1).style("fill", lfill);
1284 
1285  return true;
1286  }
1287 
1290  TAttFillHandler.prototype.CreateSample = function(sample_svg, width, height) {
1291 
1292  // we need to create extra handle to change
1293  var sample = new TAttFillHandler({ svg: sample_svg, pattern: this.pattern, color: this.color, color_as_svg: true });
1294 
1295  sample_svg.append("path")
1296  .attr("d","M0,0h" + width+"v"+height+"h-" + width + "z")
1297  .call(sample.func);
1298  }
1299 
1300  // ===========================================================================
1301 
1302  Painter.getFontDetails = function(fontIndex, size) {
1303 
1304  var res = { name: "Arial", size: Math.round(size || 11), weight: null, style: null },
1305  indx = Math.floor(fontIndex / 10),
1306  fontName = Painter.root_fonts[indx] || "";
1307 
1308  while (fontName.length > 0) {
1309  if (fontName[0]==='b') res.weight = "bold"; else
1310  if (fontName[0]==='i') res.style = "italic"; else
1311  if (fontName[0]==='o') res.style = "oblique"; else break;
1312  fontName = fontName.substr(1);
1313  }
1314 
1315  if (fontName == 'Symbol')
1316  res.weight = res.style = null;
1317 
1318  res.name = fontName;
1319  res.aver_width = Painter.root_fonts_aver_width[indx] || 0.55;
1320 
1321  res.setFont = function(selection, arg) {
1322  selection.attr("font-family", this.name);
1323  if (arg != 'without-size')
1324  selection.attr("font-size", this.size)
1325  .attr("xml:space", "preserve");
1326  if (this.weight)
1327  selection.attr("font-weight", this.weight);
1328  if (this.style)
1329  selection.attr("font-style", this.style);
1330  }
1331 
1332  res.func = res.setFont.bind(res);
1333 
1334  return res;
1335  }
1336 
1337  Painter.chooseTimeFormat = function(awidth, ticks) {
1338  if (awidth < .5) return ticks ? "%S.%L" : "%H:%M:%S.%L";
1339  if (awidth < 30) return ticks ? "%Mm%S" : "%H:%M:%S";
1340  awidth /= 60; if (awidth < 30) return ticks ? "%Hh%M" : "%d/%m %H:%M";
1341  awidth /= 60; if (awidth < 12) return ticks ? "%d-%Hh" : "%d/%m/%y %Hh";
1342  awidth /= 24; if (awidth < 15.218425) return ticks ? "%d/%m" : "%d/%m/%y";
1343  awidth /= 30.43685; if (awidth < 6) return "%d/%m/%y";
1344  awidth /= 12; if (awidth < 2) return ticks ? "%m/%y" : "%d/%m/%y";
1345  return "%Y";
1346  }
1347 
1349  Painter.getTimeFormat = function(axis) {
1350  var idF = axis.fTimeFormat.indexOf('%F');
1351  return (idF >= 0) ? axis.fTimeFormat.substr(0, idF) : axis.fTimeFormat;
1352  }
1353 
1355  Painter.getTimeOffset = function(axis) {
1356  var dflt_time_offset = 788918400000;
1357  if (!axis) return dflt_time_offset;
1358  var idF = axis.fTimeFormat.indexOf('%F');
1359  if (idF < 0) return JSROOT.gStyle.fTimeOffset*1000;
1360  var sof = axis.fTimeFormat.substr(idF + 2);
1361  // default string in axis offset
1362  if (sof.indexOf('1995-01-01 00:00:00s0')==0) return dflt_time_offset;
1363  // special case, used from DABC painters
1364  if ((sof == "0") || (sof == "")) return 0;
1365 
1366  // decode time from ROOT string
1367  function next(separ, min, max) {
1368  var pos = sof.indexOf(separ);
1369  if (pos < 0) { pos = ""; return min; }
1370  var val = parseInt(sof.substr(0,pos));
1371  sof = sof.substr(pos+1);
1372  if (isNaN(val) || (val<min) || (val>max)) { pos = ""; return min; }
1373  return val;
1374  }
1375 
1376  var year = next("-", 1970, 2300),
1377  month = next("-", 1, 12) - 1,
1378  day = next(" ", 1, 31),
1379  hour = next(":", 0, 23),
1380  min = next(":", 0, 59),
1381  sec = next("s", 0, 59),
1382  msec = next(" ", 0, 999);
1383 
1384  var dt = new Date(Date.UTC(year, month, day, hour, min, sec, msec));
1385 
1386  var offset = dt.getTime();
1387 
1388  // now also handle suffix like GMT or GMT -0600
1389  sof = sof.toUpperCase();
1390 
1391  if (sof.indexOf('GMT')==0) {
1392  offset += dt.getTimezoneOffset()*60000;
1393  sof = sof.substr(4).trim();
1394  if (sof.length > 3) {
1395  var p = 0, sign = 1000;
1396  if (sof[0]=='-') { p = 1; sign = -1000; }
1397  offset -= sign * (parseInt(sof.substr(p,2))*3600 + parseInt(sof.substr(p+2,2))*60);
1398  }
1399  }
1400 
1401  return offset;
1402  }
1403 
1404  Painter.translateLaTeX = function(str) {
1405  while ((str.length>2) && (str[0]=='{') && (str[str.length-1]=='}'))
1406  str = str.substr(1,str.length-2);
1407 
1408  if (!Painter.symbolsRegexCache) {
1409  // Create a single regex to detect any symbol to replace
1410  Painter.symbolsRegexCache = new RegExp('(' + Object.keys(Painter.symbols_map).join('|').replace(/\{/g, '\{').replace(/\\}/g, '\\}') + ')', 'g');
1411  }
1412 
1413  str = str.replace(Painter.symbolsRegexCache, Painter.convertSymbol);
1414 
1415  str = str.replace(/\{\}/g, "");
1416 
1417  return str;
1418  }
1419 
1420  Painter.approxTextWidth = function(font, label) {
1421  // returns approximate width of given label, required for reasonable scaling of text in node.js
1422 
1423  return label.length * font.size * font.aver_width;
1424  }
1425 
1426  Painter.isAnyLatex = function(str) {
1427  return (str.indexOf("#")>=0) || (str.indexOf("\\")>=0) || (str.indexOf("{")>=0);
1428  }
1429 
1431  Painter.translateMath = function(str, kind, color, painter) {
1432 
1433  if (kind != 2) {
1434  for (var x in Painter.math_symbols_map)
1435  str = str.replace(new RegExp(x,'g'), Painter.math_symbols_map[x]);
1436 
1437  for (var x in Painter.symbols_map)
1438  if (x.length > 2)
1439  str = str.replace(new RegExp(x,'g'), "\\" + x.substr(1));
1440 
1441  // replace all #color[]{} occurances
1442  var clean = "", first = true;
1443  while (str) {
1444  var p = str.indexOf("#color[");
1445  if ((p<0) && first) { clean = str; break; }
1446  first = false;
1447  if (p!=0) {
1448  var norm = (p<0) ? str : str.substr(0, p);
1449  clean += norm;
1450  if (p<0) break;
1451  }
1452 
1453  str = str.substr(p+7);
1454  p = str.indexOf("]{");
1455  if (p<=0) break;
1456  var colindx = parseInt(str.substr(0,p));
1457  if (isNaN(colindx)) break;
1458  var col = painter.get_color(colindx), cnt = 1;
1459  str = str.substr(p+2);
1460  p = -1;
1461  while (cnt && (++p<str.length)) {
1462  if (str[p]=='{') cnt++; else if (str[p]=='}') cnt--;
1463  }
1464  if (cnt!=0) break;
1465 
1466  var part = str.substr(0,p);
1467  str = str.substr(p+1);
1468  if (part)
1469  clean += "\\color{" + col + '}{' + part + "}";
1470  }
1471 
1472  str = clean;
1473  } else {
1474  str = str.replace(/\\\^/g, "\\hat");
1475  }
1476 
1477  if (typeof color != 'string') return "\\(" + str + "\\)";
1478 
1479  // MathJax SVG converter use colors in normal form
1480  //if (color.indexOf("rgb(")>=0)
1481  // color = color.replace(/rgb/g, "[RGB]")
1482  // .replace(/\(/g, '{')
1483  // .replace(/\)/g, '}');
1484  return "\\(\\color{" + color + '}{' + str + "}\\)";
1485  }
1486 
1494  Painter.BuildSvgPath = function(kind, bins, height, ndig) {
1495 
1496  var smooth = kind.indexOf("bezier") >= 0;
1497 
1498  if (ndig===undefined) ndig = smooth ? 2 : 0;
1499  if (height===undefined) height = 0;
1500 
1501  function jsroot_d3_svg_lineSlope(p0, p1) {
1502  return (p1.gry - p0.gry) / (p1.grx - p0.grx);
1503  }
1504  function jsroot_d3_svg_lineFiniteDifferences(points) {
1505  var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = jsroot_d3_svg_lineSlope(p0, p1);
1506  while (++i < j) {
1507  m[i] = (d + (d = jsroot_d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
1508  }
1509  m[i] = d;
1510  return m;
1511  }
1512  function jsroot_d3_svg_lineMonotoneTangents(points) {
1513  var d, a, b, s, m = jsroot_d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1;
1514  while (++i < j) {
1515  d = jsroot_d3_svg_lineSlope(points[i], points[i + 1]);
1516  if (Math.abs(d) < 1e-6) {
1517  m[i] = m[i + 1] = 0;
1518  } else {
1519  a = m[i] / d;
1520  b = m[i + 1] / d;
1521  s = a * a + b * b;
1522  if (s > 9) {
1523  s = d * 3 / Math.sqrt(s);
1524  m[i] = s * a;
1525  m[i + 1] = s * b;
1526  }
1527  }
1528  }
1529  i = -1;
1530  while (++i <= j) {
1531  s = (points[Math.min(j, i + 1)].grx - points[Math.max(0, i - 1)].grx) / (6 * (1 + m[i] * m[i]));
1532  points[i].dgrx = s || 0;
1533  points[i].dgry = m[i]*s || 0;
1534  }
1535  }
1536 
1537  var res = { path: "", close: "" }, bin = bins[0], prev, maxy = Math.max(bin.gry, height+5),
1538  currx = Math.round(bin.grx), curry = Math.round(bin.gry), dx, dy, npnts = bins.length;
1539 
1540  function conv(val) {
1541  var vvv = Math.round(val);
1542  if ((ndig==0) || (vvv===val)) return vvv.toString();
1543  var str = val.toFixed(ndig);
1544  while ((str[str.length-1] == '0') && (str.lastIndexOf(".") < str.length-1))
1545  str = str.substr(0,str.length-1);
1546  if (str[str.length-1] == '.')
1547  str = str.substr(0,str.length-1);
1548  if (str == "-0") str = "0";
1549  return str;
1550  }
1551 
1552  res.path = ((kind[0] == "L") ? "L" : "M") + conv(bin.grx) + "," + conv(bin.gry);
1553 
1554  // just calculate all deltas, can be used to build exclusion
1555  if (smooth || kind.indexOf('calc')>=0)
1556  jsroot_d3_svg_lineMonotoneTangents(bins);
1557 
1558  if (smooth) {
1559  // build smoothed curve
1560  res.path += "c" + conv(bin.dgrx) + "," + conv(bin.dgry) + ",";
1561  for(var n=1; n<npnts; ++n) {
1562  var prev = bin;
1563  bin = bins[n];
1564  if (n > 1) res.path += "s";
1565  res.path += conv(bin.grx-bin.dgrx-prev.grx) + "," + conv(bin.gry-bin.dgry-prev.gry) + "," + conv(bin.grx-prev.grx) + "," + conv(bin.gry-prev.gry);
1566  maxy = Math.max(maxy, prev.gry);
1567  }
1568  } else if (npnts < 10000) {
1569  // build simple curve
1570  for(var n=1; n<npnts; ++n) {
1571  bin = bins[n];
1572  dx = Math.round(bin.grx) - currx;
1573  dy = Math.round(bin.gry) - curry;
1574  if (dx && dy) res.path += "l"+dx+","+dy;
1575  else if (!dx && dy) res.path += "v"+dy;
1576  else if (dx && !dy) res.path += "h"+dx;
1577  currx += dx; curry += dy;
1578  maxy = Math.max(maxy, curry);
1579  }
1580  } else {
1581  // build line with trying optimize many vertical moves
1582  var lastx, lasty, cminy = curry, cmaxy = curry, prevy = curry;
1583  for(var n=1; n<npnts; ++n) {
1584  bin = bins[n];
1585  lastx = Math.round(bin.grx);
1586  lasty = Math.round(bin.gry);
1587  maxy = Math.max(maxy, lasty);
1588  dx = lastx - currx;
1589  if (dx===0) {
1590  // if X not change, just remember amplitude and
1591  cminy = Math.min(cminy, lasty);
1592  cmaxy = Math.max(cmaxy, lasty);
1593  prevy = lasty;
1594  continue;
1595  }
1596 
1597  if (cminy !== cmaxy) {
1598  if (cminy != curry) res.path += "v" + (cminy-curry);
1599  res.path += "v" + (cmaxy-cminy);
1600  if (cmaxy != prevy) res.path += "v" + (prevy-cmaxy);
1601  curry = prevy;
1602  }
1603  dy = lasty - curry;
1604  if (dy) res.path += "l"+dx+","+dy;
1605  else res.path += "h"+dx;
1606  currx = lastx; curry = lasty;
1607  prevy = cminy = cmaxy = lasty;
1608  }
1609 
1610  if (cminy != cmaxy) {
1611  if (cminy != curry) res.path += "v"+(cminy-curry);
1612  res.path += "v"+(cmaxy-cminy);
1613  if (cmaxy != prevy) res.path += "v"+(prevy-cmaxy);
1614  curry = prevy;
1615  }
1616 
1617  }
1618 
1619  if (height>0)
1620  res.close = "L"+conv(bin.grx)+","+conv(maxy) +
1621  "h"+conv(bins[0].grx-bin.grx) + "Z";
1622 
1623  return res;
1624  }
1625 
1626  // ==============================================================================
1627 
1628  function LongPollSocket(addr, _raw, _args) {
1629  this.path = addr;
1630  this.connid = null;
1631  this.req = null;
1632  this.raw = _raw;
1633  this.args = _args;
1634 
1635  this.nextrequest("", "connect");
1636  }
1637 
1638  LongPollSocket.prototype.nextrequest = function(data, kind) {
1639  var url = this.path, reqmode = "buf", post = null;
1640  if (kind === "connect") {
1641  url += this.raw ? "?raw_connect" : "?txt_connect";
1642  if (this.args) url += "&" + this.args;
1643  console.log('longpoll connect ' + url + ' raw = ' + this.raw);
1644  this.connid = "connect";
1645  } else if (kind === "close") {
1646  if ((this.connid===null) || (this.connid==="close")) return;
1647  url+="?connection="+this.connid + "&close";
1648  this.connid = "close";
1649  reqmode += ";sync"; // use sync mode to close connection before browser window closed
1650  } else if ((this.connid===null) || (typeof this.connid!=='number')) {
1651  return console.error("No connection");
1652  } else {
1653  url+="?connection="+this.connid;
1654  if (kind==="dummy") url+="&dummy";
1655  }
1656 
1657  if (data) {
1658  if (this.raw) {
1659  // special workaround to avoid POST request, use base64 coding
1660  url += "&post=" + btoa(data);
1661  } else {
1662  // send data with post request - most efficient way
1663  reqmode = "post";
1664  post = data;
1665  }
1666  }
1667 
1668  var req = JSROOT.NewHttpRequest(url, reqmode, function(res) {
1669  // this set to the request itself, res is response
1670 
1671  if (this.handle.req === this)
1672  this.handle.req = null; // get response for existing dummy request
1673 
1674  if (res === null)
1675  return this.handle.processreq(null);
1676 
1677  if (this.handle.raw) {
1678  // raw mode - all kind of reply data packed into binary buffer
1679  // first 4 bytes header "txt:" or "bin:"
1680  // after the "bin:" there is length of optional text argument like "bin:14 :optional_text"
1681  // and immedaitely after text binary data. Server sends binary data so, that offset should be multiple of 8
1682 
1683  var str = "", i = 0, u8Arr = new Uint8Array(res), offset = u8Arr.length;
1684  if (offset < 4) {
1685  console.error('longpoll got short message in raw mode ' + offset);
1686  return this.handle.processreq(null);
1687  }
1688 
1689  while(i<4) str += String.fromCharCode(u8Arr[i++]);
1690  if (str != "txt:") {
1691  str = "";
1692  while ((i<offset) && (String.fromCharCode(u8Arr[i]) != ':')) str += String.fromCharCode(u8Arr[i++]);
1693  ++i;
1694  offset = i + parseInt(str.trim());
1695  }
1696 
1697  str = "";
1698  while (i<offset) str += String.fromCharCode(u8Arr[i++]);
1699 
1700  if (str) {
1701  if (str == "<<nope>>") str = "";
1702  this.handle.processreq(str);
1703  }
1704  if (offset < u8Arr.length)
1705  this.handle.processreq(res, offset);
1706  } else if (this.getResponseHeader("Content-Type") == "application/x-binary") {
1707  // binary reply with optional header
1708  var extra_hdr = this.getResponseHeader("LongpollHeader");
1709  if (extra_hdr) this.handle.processreq(extra_hdr);
1710  this.handle.processreq(res, 0);
1711  } else {
1712  // text reply
1713  if (res && typeof res !== "string") {
1714  var str = "", u8Arr = new Uint8Array(res);
1715  for (var i = 0; i < u8Arr.length; ++i)
1716  str += String.fromCharCode(u8Arr[i]);
1717  res = str;
1718  }
1719  if (res == "<<nope>>") res = "";
1720  this.handle.processreq(res);
1721  }
1722  });
1723 
1724  req.handle = this;
1725  if (kind==="dummy") this.req = req; // remember last dummy request, wait for reply
1726  req.send(post);
1727  }
1728 
1729  LongPollSocket.prototype.processreq = function(res, _offset) {
1730  if (res===null) {
1731  if (typeof this.onerror === 'function') this.onerror("receive data with connid " + (this.connid || "---"));
1732  // if (typeof this.onclose === 'function') this.onclose();
1733  this.connid = null;
1734  return;
1735  }
1736 
1737  if (this.connid==="connect") {
1738  if (!res) {
1739  this.connid = null;
1740  if (typeof this.onerror === 'function') this.onerror("connection rejected");
1741  return;
1742  }
1743 
1744  this.connid = parseInt(res);
1745  console.log('Get new longpoll connection with id ' + this.connid);
1746  if (typeof this.onopen == 'function') this.onopen();
1747  } else if (this.connid==="close") {
1748  if (typeof this.onclose == 'function') this.onclose();
1749  return;
1750  } else {
1751  if ((typeof this.onmessage==='function') && res)
1752  this.onmessage({ data: res, offset: _offset });
1753  }
1754 
1755  if (!this.req) this.nextrequest("","dummy"); // send new poll request when necessary
1756  }
1757 
1758  LongPollSocket.prototype.send = function(str) {
1759  this.nextrequest(str);
1760  }
1761 
1762  LongPollSocket.prototype.close = function() {
1763  this.nextrequest("", "close");
1764  }
1765 
1766  // ========================================================================================
1767 
1768  function FileDumpSocket(receiver) {
1769  this.receiver = receiver;
1770  this.protocol = [];
1771  this.cnt = 0;
1772  JSROOT.NewHttpRequest("protocol.json", "text", this.get_protocol.bind(this)).send();
1773  }
1774 
1775  FileDumpSocket.prototype.get_protocol = function(res) {
1776  if (!res) return;
1777  this.protocol = JSON.parse(res);
1778  if (typeof this.onopen == 'function') this.onopen();
1779  this.next_operation();
1780  }
1781 
1782  FileDumpSocket.prototype.send = function(str) {
1783  if (this.protocol[this.cnt] == "send") {
1784  this.cnt++;
1785  setTimeout(this.next_operation.bind(this),10);
1786  }
1787  }
1788 
1789  FileDumpSocket.prototype.close = function() {
1790  }
1791 
1792  FileDumpSocket.prototype.next_operation = function() {
1793  // when file request running - just ignore
1794  if (this.wait_for_file) return;
1795  var fname = this.protocol[this.cnt];
1796  if (!fname) return;
1797  if (fname == "send") return; // waiting for send
1798  // console.log("getting file", fname, "wait", this.wait_for_file);
1799  this.wait_for_file = true;
1800  JSROOT.NewHttpRequest(fname, (fname.indexOf(".bin") > 0 ? "buf" : "text"), this.get_file.bind(this, fname)).send();
1801  this.cnt++;
1802  }
1803 
1804  FileDumpSocket.prototype.get_file = function(fname, res) {
1805  // console.log('got file', fname, typeof res, !!res);
1806  this.wait_for_file = false;
1807  if (!res) return;
1808  if (this.receiver.ProvideData)
1809  this.receiver.ProvideData(res, 0);
1810  setTimeout(this.next_operation.bind(this),10);
1811  }
1812 
1813  // ========================================================================================
1814 
1815 
1823  function WebWindowHandle(socket_kind) {
1824  this.kind = socket_kind;
1825  this.state = 0;
1826  this.cansend = 10;
1827  this.ackn = 10;
1828  }
1829 
1833  WebWindowHandle.prototype.GetUserArgs = function(field) {
1834  if (field && (typeof field == 'string')) {
1835  return (this.user_args && (typeof this.user_args == 'object')) ? this.user_args[field] : undefined;
1836  }
1837 
1838  return this.user_args;
1839  }
1840 
1847  WebWindowHandle.prototype.SetReceiver = function(obj) {
1848  this.receiver = obj;
1849  }
1850 
1852  WebWindowHandle.prototype.Cleanup = function() {
1853  delete this.receiver;
1854  this.Close(true);
1855  }
1856 
1859  WebWindowHandle.prototype.InvokeReceiver = function(method, arg, arg2) {
1860  if (this.receiver && (typeof this.receiver[method] == 'function'))
1861  this.receiver[method](this, arg, arg2);
1862  }
1863 
1866  WebWindowHandle.prototype.ProvideData = function(_msg, _len) {
1867  if (!this.msgqueue || !this.msgqueue.length)
1868  return this.InvokeReceiver("OnWebsocketMsg", _msg, _len);
1869  this.msgqueue.push({ ready: true, msg: _msg, len: _len});
1870  }
1871 
1874  WebWindowHandle.prototype.ReserveQueueItem = function() {
1875  if (!this.msgqueue) this.msgqueue = [];
1876  var item = { ready: false, msg: null, len: 0 };
1877  this.msgqueue.push(item);
1878  return item;
1879  }
1880 
1883  WebWindowHandle.prototype.DoneItem = function(item, _msg, _len) {
1884  item.ready = true;
1885  item.msg = _msg;
1886  item.len = _len;
1887  if (this._loop_msgqueue) return;
1888  this._loop_msgqueue = true;
1889  while ((this.msgqueue.length > 0) && this.msgqueue[0].ready) {
1890  var front = this.msgqueue.shift();
1891  this.InvokeReceiver("OnWebsocketMsg", front.msg, front.len);
1892  }
1893  if (this.msgqueue.length == 0)
1894  delete this.msgqueue;
1895  delete this._loop_msgqueue;
1896  }
1897 
1899  WebWindowHandle.prototype.Close = function(force) {
1900  if (this.timerid) {
1901  clearTimeout(this.timerid);
1902  delete this.timerid;
1903  }
1904 
1905  if (this._websocket && (this.state > 0)) {
1906  this.state = force ? -1 : 0; // -1 prevent socket from reopening
1907  this._websocket.onclose = null; // hide normal handler
1908  this._websocket.close();
1909  delete this._websocket;
1910  }
1911  }
1912 
1914  WebWindowHandle.prototype.CanSend = function(numsend) {
1915  return (this.cansend >= (numsend || 1));
1916  }
1917 
1919  WebWindowHandle.prototype.Send = function(msg, chid) {
1920  if (!this._websocket || (this.state<=0)) return false;
1921 
1922  if (isNaN(chid) || (chid===undefined)) chid = 1; // when not configured, channel 1 is used - main widget
1923 
1924  if (this.cansend <= 0) console.error('should be queued before sending cansend: ' + this.cansend);
1925 
1926  var prefix = this.ackn + ":" + this.cansend + ":" + chid + ":";
1927  this.ackn = 0;
1928  this.cansend--; // decrease number of allowed send packets
1929 
1930  this._websocket.send(prefix + msg);
1931  if (this.kind === "websocket") {
1932  if (this.timerid) clearTimeout(this.timerid);
1933  this.timerid = setTimeout(this.KeepAlive.bind(this), 10000);
1934  }
1935 
1936  return true;
1937  }
1938 
1941  WebWindowHandle.prototype.KeepAlive = function() {
1942  delete this.timerid;
1943  this.Send("KEEPALIVE", 0);
1944  }
1945 
1949  WebWindowHandle.prototype.CreateRelative = function(relative) {
1950  if (!relative || !this.kind || !this.href) return null;
1951 
1952  var handle = new WebWindowHandle(this.kind);
1953  console.log('Try to connect ', this.href + relative);
1954  handle.Connect(this.href + relative);
1955  return handle;
1956  }
1957 
1959  WebWindowHandle.prototype.Connect = function(href) {
1960 
1961  this.Close();
1962 
1963  var pthis = this, ntry = 0, args = (this.key ? ("key=" + this.key) : "");
1964 
1965  function retry_open(first_time) {
1966 
1967  if (pthis.state != 0) return;
1968 
1969  if (!first_time) console.log("try connect window again" + (new Date()).getTime());
1970 
1971  if (pthis._websocket) pthis._websocket.close();
1972  delete pthis._websocket;
1973 
1974  var conn = null;
1975  if (!href) {
1976  href = window.location.href;
1977  if (href && href.indexOf("#")>0) href = href.substr(0, href.indexOf("#"));
1978  if (href && href.lastIndexOf("/")>0) href = href.substr(0, href.lastIndexOf("/")+1);
1979  }
1980  pthis.href = href;
1981  ntry++;
1982 
1983  if (first_time) console.log('Opening web socket at ' + href);
1984 
1985  if (ntry>2) JSROOT.progress("Trying to connect " + href);
1986 
1987  var path = href;
1988 
1989  if (pthis.kind == "file") {
1990  path += "root.filedump";
1991  conn = new FileDumpSocket(pthis);
1992  console.log('configure protocol log ' + path);
1993  } else if ((pthis.kind === 'websocket') && first_time) {
1994  path = path.replace("http://", "ws://").replace("https://", "wss://") + "root.websocket";
1995  if (args) path += "?" + args;
1996  console.log('configure websocket ' + path);
1997  conn = new WebSocket(path);
1998  } else {
1999  path += "root.longpoll";
2000  console.log('configure longpoll ' + path);
2001  conn = new LongPollSocket(path, (pthis.kind === 'rawlongpoll'), args);
2002  }
2003 
2004  if (!conn) return;
2005 
2006  pthis._websocket = conn;
2007 
2008  conn.onopen = function() {
2009  if (ntry > 2) JSROOT.progress();
2010  pthis.state = 1;
2011 
2012  var key = pthis.key || "";
2013 
2014  pthis.Send("READY=" + key, 0); // need to confirm connection
2015  pthis.InvokeReceiver('OnWebsocketOpened');
2016  }
2017 
2018  conn.onmessage = function(e) {
2019  var msg = e.data;
2020 
2021  if (pthis.next_binary) {
2022  delete pthis.next_binary;
2023 
2024  if (msg instanceof Blob) {
2025  // this is case of websocket
2026  // console.log('Get Blob object - convert to buffer array');
2027  var reader = new FileReader, qitem = pthis.ReserveQueueItem();
2028  reader.onload = function(event) {
2029  // The file's text will be printed here
2030  pthis.DoneItem(qitem, event.target.result, 0);
2031  }
2032  reader.readAsArrayBuffer(msg, e.offset || 0);
2033  } else {
2034  // console.log('got array ' + (typeof msg) + ' len = ' + msg.byteLength);
2035  // this is from CEF or LongPoll handler
2036  pthis.ProvideData(msg, e.offset || 0);
2037  }
2038 
2039  return;
2040  }
2041 
2042  if (typeof msg != 'string') return console.log("unsupported message kind: " + (typeof msg));
2043 
2044  var i1 = msg.indexOf(":"),
2045  credit = parseInt(msg.substr(0,i1)),
2046  i2 = msg.indexOf(":", i1+1),
2047  cansend = parseInt(msg.substr(i1+1,i2-i1)),
2048  i3 = msg.indexOf(":", i2+1),
2049  chid = parseInt(msg.substr(i2+1,i3-i2));
2050 
2051  // console.log('msg(20)', msg.substr(0,20), credit, cansend, chid, i3);
2052 
2053  pthis.ackn++; // count number of received packets,
2054  pthis.cansend += credit; // how many packets client can send
2055 
2056  msg = msg.substr(i3+1);
2057 
2058  if (chid == 0) {
2059  console.log('GET chid=0 message', msg);
2060  if (msg == "CLOSE") {
2061  pthis.Close(true); // force closing of socket
2062  pthis.InvokeReceiver('OnWebsocketClosed');
2063  }
2064  } else if (msg == "$$binary$$") {
2065  pthis.next_binary = true;
2066  } else if (msg == "$$nullbinary$$") {
2067  pthis.ProvideData(new ArrayBuffer(0), 0);
2068  } else {
2069  pthis.ProvideData(msg);
2070  }
2071 
2072  if (pthis.ackn > 7)
2073  pthis.Send('READY', 0); // send dummy message to server
2074  }
2075 
2076  conn.onclose = function() {
2077  delete pthis._websocket;
2078  if (pthis.state > 0) {
2079  console.log('websocket closed');
2080  pthis.state = 0;
2081  pthis.InvokeReceiver('OnWebsocketClosed');
2082  }
2083  }
2084 
2085  conn.onerror = function (err) {
2086  console.log("websocket error " + err);
2087  if (pthis.state > 0) {
2088  pthis.InvokeReceiver('OnWebsocketError', err);
2089  pthis.state = 0;
2090  }
2091  }
2092 
2093  // only in interactive mode try to reconnect
2094  if (!JSROOT.BatchMode)
2095  setTimeout(retry_open, 3000); // after 3 seconds try again
2096 
2097  } // retry_open
2098 
2099  retry_open(true); // call for the first time
2100  }
2101 
2115  JSROOT.ConnectWebWindow = function(arg) {
2116  if (typeof arg == 'function') arg = { callback: arg }; else
2117  if (!arg || (typeof arg != 'object')) arg = {};
2118 
2119  if (arg.prereq) {
2120  if (arg.openui5src) JSROOT.openui5src = arg.openui5src;
2121  if (arg.openui5libs) JSROOT.openui5libs = arg.openui5libs;
2122  return JSROOT.AssertPrerequisites(arg.prereq, function() {
2123  delete arg.prereq; JSROOT.ConnectWebWindow(arg);
2124  }, arg.prereq_logdiv);
2125  }
2126 
2127  // special hold script, prevents headless browser from too early exit
2128  if ((JSROOT.GetUrlOption("batch_mode")!==null) && JSROOT.GetUrlOption("key") && (JSROOT.browser.isChromeHeadless || JSROOT.browser.isChrome))
2129  JSROOT.loadScript("root_batch_holder.js?key=" + JSROOT.GetUrlOption("key"));
2130 
2131  if (!arg.platform)
2132  arg.platform = JSROOT.GetUrlOption("platform");
2133 
2134  if (arg.platform == "qt5") JSROOT.browser.qt5 = true; else
2135  if (arg.platform == "cef3") JSROOT.browser.cef3 = true;
2136 
2137  if (arg.batch === undefined)
2138  arg.batch = (JSROOT.GetUrlOption("batch_mode")!==null); // && (JSROOT.browser.qt5 || JSROOT.browser.cef3 || JSROOT.browser.isChrome);
2139 
2140  if (arg.batch) JSROOT.BatchMode = true;
2141 
2142  if (!arg.socket_kind)
2143  arg.socket_kind = JSROOT.GetUrlOption("ws");
2144 
2145  if (!arg.socket_kind) {
2146  if (JSROOT.browser.qt5) arg.socket_kind = "rawlongpoll"; else
2147  if (JSROOT.browser.cef3) arg.socket_kind = "longpoll"; else arg.socket_kind = "websocket";
2148  }
2149 
2150  // only for debug purposes
2151  // arg.socket_kind = "longpoll";
2152 
2153  var handle = new WebWindowHandle(arg.socket_kind);
2154  handle.user_args = arg.user_args;
2155 
2156  if (window) {
2157  window.onbeforeunload = handle.Close.bind(handle, true);
2158  if (JSROOT.browser.qt5) window.onqt5unload = window.onbeforeunload;
2159  }
2160 
2161  if (arg.first_recv) {
2162  arg.receiver = {
2163  OnWebsocketOpened: function(handle) {
2164  },
2165 
2166  OnWebsocketMsg: function(handle, msg) {
2167  // console.log('Get message ' + msg + ' handle ' + !!handle);
2168  if (msg.indexOf(arg.first_recv)!=0)
2169  return handle.Close();
2170  arg.first_msg = msg.substr(arg.first_recv.length);
2171 
2172  if (!arg.prereq2) JSROOT.CallBack(arg.callback, handle, arg);
2173  },
2174 
2175  OnWebsocketClosed: function(handle) {
2176  // when connection closed, close panel as well
2177  JSROOT.CloseCurrentWindow();
2178  }
2179  };
2180  }
2181 
2182  handle.key = JSROOT.GetUrlOption("key");
2183 
2184  if (!arg.receiver)
2185  return JSROOT.CallBack(arg.callback, handle, arg);
2186 
2187  // when receiver is exists, it handles itself callbacks
2188  handle.SetReceiver(arg.receiver);
2189  handle.Connect();
2190 
2191  if (arg.prereq2) {
2192  JSROOT.AssertPrerequisites(arg.prereq2, function() {
2193  delete arg.prereq2; // indicate that func is loaded
2194  if (!arg.first_recv || arg.first_msg) JSROOT.CallBack(arg.callback, handle, arg);
2195  });
2196  } else if (!arg.first_recv) {
2197  JSROOT.CallBack(arg.callback, handle, arg);
2198  }
2199  }
2200 
2201  // ========================================================================================
2202 
2209  function TBasePainter() {
2210  this.divid = null; // either id of element (preferable) or element itself
2211  }
2212 
2222  TBasePainter.prototype.AccessTopPainter = function(on) {
2223  var main = this.select_main().node(),
2224  chld = main ? main.firstChild : null;
2225  if (!chld) return null;
2226  if (on===true) chld.painter = this; else
2227  if (on===false) delete chld.painter;
2228  return chld.painter;
2229  }
2230 
2232  TBasePainter.prototype.Cleanup = function(keep_origin) {
2233 
2234  var origin = this.select_main('origin');
2235  if (!origin.empty() && !keep_origin) origin.html("");
2236  if (this._changed_layout)
2237  this.set_layout_kind('simple');
2238  this.AccessTopPainter(false);
2239  this.divid = null;
2240  delete this._selected_main;
2241 
2242  if (this._hpainter && typeof this._hpainter.ClearPainter === 'function') this._hpainter.ClearPainter(this);
2243 
2244  delete this._changed_layout;
2245  delete this._hitemname;
2246  delete this._hdrawopt;
2247  delete this._hpainter;
2248  }
2249 
2253  TBasePainter.prototype.DrawingReady = function(res_painter) {
2254  this._ready_called_ = true;
2255  if (this._ready_callback_ !== undefined) {
2256  var callbacks = this._ready_callback_;
2257  if (!this._return_res_painter) res_painter = this;
2258 
2259  delete this._return_res_painter;
2260  delete this._ready_callback_;
2261 
2262  while (callbacks.length)
2263  JSROOT.CallBack(callbacks.shift(), res_painter);
2264  }
2265  return this;
2266  }
2267 
2271  TBasePainter.prototype.WhenReady = function(callback) {
2272  if (typeof callback !== 'function') return;
2273  if ('_ready_called_' in this) return JSROOT.CallBack(callback, this);
2274  if (this._ready_callback_ === undefined) this._ready_callback_ = [];
2275  this._ready_callback_.push(callback);
2276  }
2277 
2281  TBasePainter.prototype.ResetReady = function() {
2282  delete this._ready_called_;
2283  delete this._ready_callback_;
2284  }
2285 
2288  TBasePainter.prototype.GetObject = function() {
2289  }
2290 
2296  TBasePainter.prototype.MatchObjectType = function(typename) {
2297  }
2298 
2303  TBasePainter.prototype.UpdateObject = function(obj) {
2304  }
2305 
2309  TBasePainter.prototype.RedrawPad = function(resize) {
2310  }
2311 
2315  TBasePainter.prototype.RedrawObject = function(obj) {
2316  if (!this.UpdateObject(obj)) return false;
2317  var current = document.body.style.cursor;
2318  document.body.style.cursor = 'wait';
2319  this.RedrawPad();
2320  document.body.style.cursor = current;
2321  return true;
2322  }
2323 
2328  TBasePainter.prototype.CheckResize = function(arg) {
2329  }
2330 
2335  TBasePainter.prototype.select_main = function(is_direct) {
2336 
2337  if (!this.divid) return d3.select(null);
2338 
2339  var res = this._selected_main;
2340  if (!res) {
2341  if (typeof this.divid == "string") {
2342  var id = this.divid;
2343  if (id[0]!='#') id = "#" + id;
2344  res = d3.select(id);
2345  if (!res.empty()) this.divid = res.node();
2346  } else {
2347  res = d3.select(this.divid);
2348  }
2349  this._selected_main = res;
2350  }
2351 
2352  if (!res || res.empty() || (is_direct==='origin')) return res;
2353 
2354  var use_enlarge = res.property('use_enlarge'),
2355  layout = res.property('layout') || 'simple',
2356  layout_selector = (layout=='simple') ? "" : res.property('layout_selector');
2357 
2358  if (layout_selector) res = res.select(layout_selector);
2359 
2360  // one could redirect here
2361  if (!is_direct && !res.empty() && use_enlarge) res = d3.select("#jsroot_enlarge_div");
2362 
2363  return res;
2364  }
2365 
2369  TBasePainter.prototype.get_main_id = function() {
2370  var elem = this.select_main();
2371  if (elem.empty()) return "";
2372  var id = elem.attr("id");
2373  if (!id) {
2374  id = "jsroot_element_" + JSROOT.id_counter++;
2375  elem.attr("id", id);
2376  }
2377  return id;
2378  }
2379 
2383  TBasePainter.prototype.get_layout_kind = function() {
2384  var origin = this.select_main('origin'),
2385  layout = origin.empty() ? "" : origin.property('layout');
2386 
2387  return layout || 'simple';
2388  }
2389 
2393  TBasePainter.prototype.set_layout_kind = function(kind, main_selector) {
2394  // change layout settings
2395  var origin = this.select_main('origin');
2396  if (!origin.empty()) {
2397  if (!kind) kind = 'simple';
2398  origin.property('layout', kind);
2399  origin.property('layout_selector', (kind!='simple') && main_selector ? main_selector : null);
2400  this._changed_layout = (kind !== 'simple'); // use in cleanup
2401  }
2402  }
2403 
2411  TBasePainter.prototype.check_main_resize = function(check_level, new_size, height_factor) {
2412 
2413  var enlarge = this.enlarge_main('state'),
2414  main_origin = this.select_main('origin'),
2415  main = this.select_main(),
2416  lmt = 5; // minimal size
2417 
2418  if (enlarge !== 'on') {
2419  if (new_size && new_size.width && new_size.height)
2420  main_origin.style('width',new_size.width+"px")
2421  .style('height',new_size.height+"px");
2422  }
2423 
2424  var rect_origin = this.get_visible_rect(main_origin, true),
2425  can_resize = main_origin.attr('can_resize'),
2426  do_resize = false;
2427 
2428  if (can_resize == "height")
2429  if (height_factor && Math.abs(rect_origin.width*height_factor - rect_origin.height) > 0.1*rect_origin.width) do_resize = true;
2430 
2431  if (((rect_origin.height <= lmt) || (rect_origin.width <= lmt)) &&
2432  can_resize && can_resize !== 'false') do_resize = true;
2433 
2434  if (do_resize && (enlarge !== 'on')) {
2435  // if zero size and can_resize attribute set, change container size
2436 
2437  if (rect_origin.width > lmt) {
2438  height_factor = height_factor || 0.66;
2439  main_origin.style('height', Math.round(rect_origin.width * height_factor)+'px');
2440  } else if (can_resize !== 'height') {
2441  main_origin.style('width', '200px').style('height', '100px');
2442  }
2443  }
2444 
2445  var rect = this.get_visible_rect(main),
2446  old_h = main.property('draw_height'), old_w = main.property('draw_width');
2447 
2448  rect.changed = false;
2449 
2450  if (old_h && old_w && (old_h>0) && (old_w>0)) {
2451  if ((old_h !== rect.height) || (old_w !== rect.width))
2452  if ((check_level>1) || (rect.width/old_w<0.66) || (rect.width/old_w>1.5) ||
2453  (rect.height/old_h<0.66) && (rect.height/old_h>1.5)) rect.changed = true;
2454  } else {
2455  rect.changed = true;
2456  }
2457 
2458  return rect;
2459  }
2460 
2475  TBasePainter.prototype.enlarge_main = function(action, skip_warning) {
2476 
2477  var main = this.select_main(true),
2478  origin = this.select_main('origin');
2479 
2480  if (main.empty() || !JSROOT.gStyle.CanEnlarge || (origin.property('can_enlarge')===false)) return false;
2481 
2482  if (action===undefined) return true;
2483 
2484  if (action==='verify') return true;
2485 
2486  var state = origin.property('use_enlarge') ? "on" : "off";
2487 
2488  if (action === 'state') return state;
2489 
2490  if (action === 'toggle') action = (state==="off");
2491 
2492  var enlarge = d3.select("#jsroot_enlarge_div");
2493 
2494  if ((action === true) && (state!=="on")) {
2495  if (!enlarge.empty()) return false;
2496 
2497  enlarge = d3.select(document.body)
2498  .append("div")
2499  .attr("id","jsroot_enlarge_div");
2500 
2501  var rect1 = this.get_visible_rect(main),
2502  rect2 = this.get_visible_rect(enlarge);
2503 
2504  // if new enlarge area not big enough, do not do it
2505  if ((rect2.width <= rect1.width) || (rect2.height <= rect1.height))
2506  if (rect2.width*rect2.height < rect1.width*rect1.height) {
2507  if (!skip_warning)
2508  console.log('Enlarged area ' + rect2.width+"x"+rect2.height + ' smaller then original drawing ' + rect1.width+"x"+rect1.height);
2509  enlarge.remove();
2510  return false;
2511  }
2512 
2513  while (main.node().childNodes.length > 0)
2514  enlarge.node().appendChild(main.node().firstChild);
2515 
2516  origin.property('use_enlarge', true);
2517 
2518  return true;
2519  }
2520  if ((action === false) && (state!=="off")) {
2521 
2522  while (enlarge.node() && enlarge.node().childNodes.length > 0)
2523  main.node().appendChild(enlarge.node().firstChild);
2524 
2525  enlarge.remove();
2526  origin.property('use_enlarge', false);
2527  return true;
2528  }
2529 
2530  return false;
2531  }
2532 
2535  TBasePainter.prototype.GetStyleValue = function(elem, name) {
2536  if (!elem || elem.empty()) return 0;
2537  var value = elem.style(name);
2538  if (!value || (typeof value !== 'string')) return 0;
2539  value = parseFloat(value.replace("px",""));
2540  return isNaN(value) ? 0 : Math.round(value);
2541  }
2542 
2545  TBasePainter.prototype.get_visible_rect = function(elem, fullsize) {
2546 
2547  if (JSROOT.nodejs)
2548  return { width : parseInt(elem.attr("width")), height: parseInt(elem.attr("height")) };
2549 
2550  var rect = elem.node().getBoundingClientRect(),
2551  res = { width: Math.round(rect.width), height: Math.round(rect.height) };
2552 
2553  if (!fullsize) {
2554  // this is size exclude padding area
2555  res.width -= this.GetStyleValue(elem,'padding-left') + this.GetStyleValue(elem,'padding-right');
2556  res.height -= this.GetStyleValue(elem,'padding-top') - this.GetStyleValue(elem,'padding-bottom');
2557  }
2558 
2559  return res;
2560  }
2561 
2569  TBasePainter.prototype.SetDivId = function(divid) {
2570  if (divid !== undefined) {
2571  this.divid = divid;
2572  delete this._selected_main;
2573  }
2574 
2575  this.AccessTopPainter(true);
2576  }
2577 
2583  TBasePainter.prototype.SetItemName = function(name, opt, hpainter) {
2584  if (typeof name === 'string') this._hitemname = name;
2585  else delete this._hitemname;
2586  // only upate draw option, never delete. null specified when update drawing
2587  if (typeof opt === 'string') this._hdrawopt = opt;
2588 
2589  this._hpainter = hpainter;
2590  }
2591 
2594  TBasePainter.prototype.GetItemName = function() {
2595  return ('_hitemname' in this) ? this._hitemname : null;
2596  }
2597 
2600  TBasePainter.prototype.GetItemDrawOpt = function() {
2601  return ('_hdrawopt' in this) ? this._hdrawopt : "";
2602  }
2603 
2612  TBasePainter.prototype.CanZoomIn = function(axis,left,right) {
2613  }
2614 
2615  // ==============================================================================
2616 
2625  function TObjectPainter(obj, opt) {
2626  TBasePainter.call(this);
2627  this.draw_g = null; // container for all drawn objects
2628  this.pad_name = ""; // name of pad where object is drawn
2629  this.main = null; // main painter, received from pad
2630  if (typeof opt == "string") this.options = { original: opt };
2631  this.AssignObject(obj);
2632  }
2633 
2634  TObjectPainter.prototype = Object.create(TBasePainter.prototype);
2635 
2636  TObjectPainter.prototype.AssignObject = function(obj) {
2637  this.draw_object = ((obj!==undefined) && (typeof obj == 'object')) ? obj : null;
2638  }
2639 
2644  TObjectPainter.prototype.Cleanup = function() {
2645 
2646  this.RemoveDrawG();
2647 
2648  var keep_origin = true;
2649 
2650  if (this.is_main_painter()) {
2651  var pp = this.pad_painter();
2652  if (!pp || pp.normal_canvas === false) keep_origin = false;
2653  }
2654 
2655  // cleanup all existing references
2656  this.pad_name = "";
2657  this.main = null;
2658  this.draw_object = null;
2659  delete this.snapid;
2660 
2661  // remove attributes objects (if any)
2662  delete this.fillatt;
2663  delete this.lineatt;
2664  delete this.markeratt;
2665  delete this.bins;
2666  delete this.root_colors;
2667  delete this.options;
2668  delete this.options_store;
2669 
2670  // remove extra fields from v7 painters
2671  delete this.rstyle;
2672  delete this.csstype;
2673 
2674  TBasePainter.prototype.Cleanup.call(this, keep_origin);
2675  }
2676 
2678  TObjectPainter.prototype.GetObject = function() {
2679  return this.draw_object;
2680  }
2681 
2683  TObjectPainter.prototype.GetClassName = function() {
2684  var res = this.draw_object ? this.draw_object._typename : "";
2685  return res || "";
2686  }
2687 
2693  TObjectPainter.prototype.MatchObjectType = function(arg) {
2694  if (!arg || !this.draw_object) return false;
2695  if (typeof arg === 'string') return (this.draw_object._typename === arg);
2696  if (arg._typename) return (this.draw_object._typename === arg._typename);
2697  return this.draw_object._typename.match(arg);
2698  }
2699 
2704  TObjectPainter.prototype.SetItemName = function(name, opt, hpainter) {
2705  TBasePainter.prototype.SetItemName.call(this, name, opt, hpainter);
2706  if (this.no_default_title || (name=="")) return;
2707  var can = this.svg_canvas();
2708  if (!can.empty()) can.select("title").text(name);
2709  else this.select_main().attr("title", name);
2710  }
2711 
2714  TObjectPainter.prototype.OptionsStore = function(original) {
2715  if (!this.options) return;
2716  if (!original) original = "";
2717  var pp = original.indexOf(";;");
2718  if (pp>=0) original = original.substr(0,pp);
2719  this.options.original = original;
2720  this.options_store = JSROOT.extend({}, this.options);
2721  }
2722 
2727  TObjectPainter.prototype.OptionesChanged = function() {
2728  if (!this.options) return false;
2729  if (!this.options_store) return true;
2730 
2731  for (var k in this.options)
2732  if (this.options[k] !== this.options_store[k]) return true;
2733 
2734  return false;
2735  }
2736 
2740  TObjectPainter.prototype.OptionsAsString = function() {
2741  if (!this.options) return "";
2742 
2743  if (!this.OptionesChanged())
2744  return this.options.original || "";
2745 
2746  if (typeof this.options.asString == "function")
2747  return this.options.asString();
2748 
2749  return this.options.original || ""; // nothing better, return original draw option
2750  }
2751 
2757  TObjectPainter.prototype.UpdateObject = function(obj) {
2758  if (!this.MatchObjectType(obj)) return false;
2759  JSROOT.extend(this.GetObject(), obj);
2760  return true;
2761  }
2762 
2768  TObjectPainter.prototype.GetTipName = function(append) {
2769  var res = this.GetItemName(), obj = this.GetObject();
2770  if (!res) res = obj && obj.fName ? obj.fName : "";
2771  if (res.lenght > 20) res = res.substr(0,17) + "...";
2772  if (res && append) res += append;
2773  return res;
2774  }
2775 
2778  TObjectPainter.prototype.pad_painter = function(pad_name) {
2779  var elem = this.svg_pad(typeof pad_name == "string" ? pad_name : undefined);
2780  return elem.empty() ? null : elem.property('pad_painter');
2781  }
2782 
2785  TObjectPainter.prototype.canv_painter = function() {
2786  var elem = this.svg_canvas();
2787  return elem.empty() ? null : elem.property('pad_painter');
2788  }
2789 
2792  TObjectPainter.prototype.get_color = function(indx) {
2793  var jsarr = this.root_colors;
2794 
2795  if (!jsarr) {
2796  var pp = this.canv_painter();
2797  jsarr = this.root_colors = (pp && pp.root_colors) ? pp.root_colors : JSROOT.Painter.root_colors;
2798  }
2799 
2800  return jsarr[indx];
2801  }
2802 
2805  TObjectPainter.prototype.add_color = function(color) {
2806  var jsarr = this.root_colors;
2807  if (!jsarr) {
2808  var pp = this.canv_painter();
2809  jsarr = this.root_colors = (pp && pp.root_colors) ? pp.root_colors : JSROOT.Painter.root_colors;
2810  }
2811  var indx = jsarr.indexOf(color);
2812  if (indx >= 0) return indx;
2813  jsarr.push(color);
2814  return jsarr.length-1;
2815  }
2816 
2819  TObjectPainter.prototype.IsTooltipAllowed = function() {
2820  var src = this.canv_painter() || this;
2821  return src.tooltip_allowed ? true : false;
2822  }
2823 
2826  TObjectPainter.prototype.SetTooltipAllowed = function(on) {
2827  var src = this.canv_painter() || this;
2828  src.tooltip_allowed = (on == "toggle") ? !src.tooltip_allowed : on;
2829  }
2830 
2833  TObjectPainter.prototype.get_palette = function(force, palettedid) {
2834  if (!palettedid) {
2835  var pp = this.pad_painter();
2836  if (!pp) return null;
2837  if (pp.custom_palette) return pp.custom_palette;
2838  }
2839 
2840  var cp = this.canv_painter();
2841  if (!cp) return null;
2842  if (cp.custom_palette && !palettedid) return cp.custom_palette;
2843 
2844  if (force && JSROOT.Painter.GetColorPalette)
2845  cp.custom_palette = JSROOT.Painter.GetColorPalette(palettedid);
2846 
2847  return cp.custom_palette;
2848  }
2849 
2853  TObjectPainter.prototype.AttributeChange = function(class_name, member_name, new_value) {
2854  // only for objects in web canvas make sense to handle attributes changes from GED
2855  // console.log("Changed attribute class = " + class_name + " member = " + member_name + " value = " + new_value);
2856  }
2857 
2862  TObjectPainter.prototype.CheckResize = function(arg) {
2863  var pad_painter = this.canv_painter();
2864  if (!pad_painter) return false;
2865 
2866  // only canvas should be checked
2867  pad_painter.CheckCanvasResize(arg);
2868  return true;
2869  }
2870 
2873  TObjectPainter.prototype.RemoveDrawG = function() {
2874  if (this.draw_g) {
2875  this.draw_g.remove();
2876  this.draw_g = null;
2877  }
2878  }
2879 
2883  TObjectPainter.prototype.RecreateDrawG = function(usepad, layer) {
2884  // keep old function for a while - later
2885  console.warn("Obsolete RecreateDrawG is used, will be removed soon. Change to CreateG");
2886  return this.CreateG(usepad ? undefined : layer);
2887  }
2888 
2895  TObjectPainter.prototype.CreateG = function(frame_layer) {
2896  if (this.draw_g) {
2897  // one should keep svg:g element on its place
2898  // d3.selectAll(this.draw_g.node().childNodes).remove();
2899  this.draw_g.selectAll('*').remove();
2900  } else if (frame_layer) {
2901  var frame = this.svg_frame();
2902  if (frame.empty()) return frame;
2903  if (typeof frame_layer != 'string') frame_layer = "main_layer";
2904  var layer = frame.select("." + frame_layer);
2905  if (layer.empty()) layer = frame.select(".main_layer");
2906  this.draw_g = layer.append("svg:g");
2907  } else {
2908  var layer = this.svg_layer("primitives_layer");
2909  this.draw_g = layer.append("svg:g");
2910 
2911  // layer.selectAll(".most_upper_primitives").raise();
2912  var up = [], chlds = layer.node().childNodes;
2913  for (var n=0;n<chlds.length;++n)
2914  if (d3.select(chlds[n]).classed("most_upper_primitives")) up.push(chlds[n]);
2915 
2916  up.forEach(function(top) { d3.select(top).raise(); });
2917  }
2918 
2919  // set attributes for debugging
2920  if (this.draw_object) {
2921  this.draw_g.attr('objname', encodeURI(this.draw_object.fName || "name"));
2922  this.draw_g.attr('objtype', encodeURI(this.draw_object._typename || "type"));
2923  }
2924 
2925  this.draw_g.property('in_frame', !!frame_layer); // indicates coordinate system
2926 
2927  return this.draw_g;
2928  }
2929 
2932  TObjectPainter.prototype.svg_canvas = function() {
2933  return this.select_main().select(".root_canvas");
2934  }
2935 
2938  TObjectPainter.prototype.svg_pad = function(pad_name) {
2939  if (pad_name === undefined) pad_name = this.pad_name;
2940 
2941  var c = this.svg_canvas();
2942  if (!pad_name || c.empty()) return c;
2943 
2944  var cp = c.property('pad_painter');
2945  if (cp && cp.pads_cache && cp.pads_cache[pad_name])
2946  return d3.select(cp.pads_cache[pad_name]);
2947 
2948  c = c.select(".primitives_layer .__root_pad_" + pad_name);
2949  if (cp) {
2950  if (!cp.pads_cache) cp.pads_cache = {};
2951  cp.pads_cache[pad_name] = c.node();
2952  }
2953  return c;
2954  }
2955 
2958  TObjectPainter.prototype.svg_layer = function(name, pad_name) {
2959  var svg = this.svg_pad(pad_name);
2960  if (svg.empty()) return svg;
2961 
2962  if (name.indexOf("prim#")==0) {
2963  svg = svg.select(".primitives_layer");
2964  name = name.substr(5);
2965  }
2966 
2967  var node = svg.node().firstChild;
2968  while (node!==null) {
2969  var elem = d3.select(node);
2970  if (elem.classed(name)) return elem;
2971  node = node.nextSibling;
2972  }
2973 
2974  return d3.select(null);
2975  }
2976 
2980  TObjectPainter.prototype.CurrentPadName = function(new_name) {
2981  var svg = this.svg_canvas();
2982  if (svg.empty()) return "";
2983  var curr = svg.property('current_pad');
2984  if (new_name !== undefined) svg.property('current_pad', new_name);
2985  return curr;
2986  }
2987 
2990  TObjectPainter.prototype.root_pad = function() {
2991  var pad_painter = this.pad_painter();
2992  return pad_painter ? pad_painter.pad : null;
2993  }
2994 
3004  TObjectPainter.prototype.AxisToSvg = function(axis, value, ndc, noround) {
3005  var use_frame = this.draw_g && this.draw_g.property('in_frame'),
3006  main = use_frame ? this.frame_painter() : null;
3007 
3008  if (use_frame && main && main["gr"+axis]) {
3009  value = (axis=="y") ? main.gry(value) + (use_frame ? 0 : main.frame_y())
3010  : main.grx(value) + (use_frame ? 0 : main.frame_x());
3011  } else if (use_frame) {
3012  value = 0; // in principal error, while frame calculation requested
3013  } else {
3014  var pad = ndc ? null : this.root_pad();
3015  if (pad) {
3016  if (axis=="y") {
3017  if (pad.fLogy)
3018  value = (value>0) ? JSROOT.log10(value) : pad.fUymin;
3019  value = (value - pad.fY1) / (pad.fY2 - pad.fY1);
3020  } else {
3021  if (pad.fLogx)
3022  value = (value>0) ? JSROOT.log10(value) : pad.fUxmin;
3023  value = (value - pad.fX1) / (pad.fX2 - pad.fX1);
3024  }
3025  }
3026  value = (axis=="y") ? (1-value)*this.pad_height() : value*this.pad_width();
3027  }
3028 
3029  return noround ? value : Math.round(value);
3030  }
3031 
3041  TObjectPainter.prototype.SvgToAxis = function(axis, coord, ndc) {
3042  var use_frame = this.draw_g && this.draw_g.property('in_frame'),
3043  main = use_frame ? this.frame_painter() : null;
3044 
3045  if (use_frame) main = this.frame_painter();
3046 
3047  if (use_frame && main) {
3048  return (axis=="y") ? main.RevertY(coord - (use_frame ? 0 : main.frame_y()))
3049  : main.RevertX(coord - (use_frame ? 0 : main.frame_x()));
3050  } else if (use_frame) {
3051  return 0; // in principal error, while frame calculation requested
3052  }
3053 
3054  var value = (axis=="y") ? (1 - coord / this.pad_height()) : coord / this.pad_width();
3055  var pad = ndc ? null : this.root_pad();
3056 
3057  if (pad) {
3058  if (axis=="y") {
3059  value = pad.fY1 + value * (pad.fY2 - pad.fY1);
3060  if (pad.fLogy) value = Math.pow(10, value);
3061  } else {
3062  value = pad.fX1 + value * (pad.fX2 - pad.fX1);
3063  if (pad.fLogx) value = Math.pow(10, value);
3064  }
3065  }
3066 
3067  return value;
3068  }
3069 
3076  TObjectPainter.prototype.AxisToSvgFunc = function(isndc) {
3077  var func = {isndc: isndc}, use_frame = this.draw_g && this.draw_g.property('in_frame');
3078  if (use_frame) func.main = this.frame_painter();
3079  if (func.main && !isndc && func.main.grx && func.main.gry) {
3080  func.offx = func.main.frame_x();
3081  func.offy = func.main.frame_y();
3082  func.x = function(x) { return Math.round(this.main.grx(x) + this.offx); }
3083  func.y = function(y) { return Math.round(this.main.gry(y) + this.offy); }
3084  } else {
3085  if (!isndc) func.pad = this.root_pad(); // need for NDC conversion
3086  func.padh = this.pad_height();
3087  func.padw = this.pad_width();
3088  func.x = function(value) {
3089  if (this.pad) {
3090  if (this.pad.fLogx)
3091  value = (value>0) ? JSROOT.log10(value) : this.pad.fUxmin;
3092  value = (value - this.pad.fX1) / (this.pad.fX2 - this.pad.fX1);
3093  }
3094  return Math.round(value*this.padw);
3095  }
3096  func.y = function(value) {
3097  if (this.pad) {
3098  if (this.pad.fLogy)
3099  value = (value>0) ? JSROOT.log10(value) : this.pad.fUymin;
3100  value = (value - this.pad.fY1) / (this.pad.fY2 - this.pad.fY1);
3101  }
3102  return Math.round((1-value)*this.padh);
3103  }
3104  }
3105  return func;
3106  }
3107 
3112  TObjectPainter.prototype.svg_frame = function(pad_name) {
3113  return this.svg_layer("primitives_layer", pad_name).select(".root_frame");
3114  }
3115 
3121  TObjectPainter.prototype.pad_width = function(pad_name) {
3122  var res = this.svg_pad(pad_name);
3123  res = res.empty() ? 0 : res.property("draw_width");
3124  return isNaN(res) ? 0 : res;
3125  }
3126 
3132  TObjectPainter.prototype.pad_height = function(pad_name) {
3133  var res = this.svg_pad(pad_name);
3134  res = res.empty() ? 0 : res.property("draw_height");
3135  return isNaN(res) ? 0 : res;
3136  }
3137 
3140  TObjectPainter.prototype.frame_painter = function() {
3141  var pp = this.pad_painter();
3142  return pp ? pp.frame_painter_ref : null;
3143  }
3144 
3147  TObjectPainter.prototype.frame_property = function(name) {
3148  var pp = this.frame_painter();
3149  return pp && pp[name] ? pp[name] : 0;
3150  }
3151 
3153  TObjectPainter.prototype.frame_x = function() {
3154  return this.frame_property("_frame_x");
3155  }
3156 
3158  TObjectPainter.prototype.frame_y = function() {
3159  return this.frame_property("_frame_y");
3160  }
3161 
3163  TObjectPainter.prototype.frame_width = function() {
3164  return this.frame_property("_frame_width");
3165  }
3166 
3168  TObjectPainter.prototype.frame_height = function() {
3169  return this.frame_property("_frame_height");
3170  }
3171 
3182  TObjectPainter.prototype.embed_3d = function() {
3183  if (JSROOT.BatchMode) return 4; // in batch - directly create svg::image after rendering
3184  if (JSROOT.gStyle.Embed3DinSVG < 2) return JSROOT.gStyle.Embed3DinSVG;
3185  if (JSROOT.browser.isFirefox /*|| JSROOT.browser.isWebKit*/)
3186  return JSROOT.gStyle.Embed3DinSVG; // use specified mode
3187  return 1; // default is overlay
3188  }
3189 
3195  TObjectPainter.prototype.access_3d_kind = function(new_value) {
3196  var svg = this.svg_pad(this.this_pad_name);
3197  if (svg.empty()) return -1;
3198 
3199  // returns kind of currently created 3d canvas
3200  var kind = svg.property('can3d');
3201  if (new_value !== undefined) svg.property('can3d', new_value);
3202  return ((kind===null) || (kind===undefined)) ? -1 : kind;
3203  }
3204 
3210  TObjectPainter.prototype.size_for_3d = function(can3d) {
3211 
3212  if (can3d === undefined) can3d = this.embed_3d();
3213 
3214  var pad = this.svg_pad(this.this_pad_name),
3215  clname = "draw3d_" + (this.this_pad_name || this.pad_name || 'canvas');
3216 
3217  if (pad.empty()) {
3218  // this is a case when object drawn without canvas
3219 
3220  var rect = this.get_visible_rect(this.select_main());
3221 
3222  if ((rect.height<10) && (rect.width>10)) {
3223  rect.height = Math.round(0.66*rect.width);
3224  this.select_main().style('height', rect.height + "px");
3225  }
3226  rect.x = 0; rect.y = 0; rect.clname = clname; rect.can3d = -1;
3227  return rect;
3228  }
3229 
3230  var elem = pad, fp = this.frame_painter();
3231  if (can3d === 0) elem = this.svg_canvas();
3232 
3233  var size = { x: 0, y: 0, width: 100, height: 100, clname: clname, can3d: can3d };
3234 
3235  if (fp && !fp.mode3d) {
3236  elem = this.svg_frame();
3237  size.x = elem.property("draw_x");
3238  size.y = elem.property("draw_y");
3239  }
3240 
3241  size.width = elem.property("draw_width");
3242  size.height = elem.property("draw_height");
3243 
3244  if ((!fp || fp.mode3d) && (can3d > 0)) {
3245  size.x = Math.round(size.x + size.width*JSROOT.gStyle.fPadLeftMargin);
3246  size.y = Math.round(size.y + size.height*JSROOT.gStyle.fPadTopMargin);
3247  size.width = Math.round(size.width*(1 - JSROOT.gStyle.fPadLeftMargin - JSROOT.gStyle.fPadRightMargin));
3248  size.height = Math.round(size.height*(1- JSROOT.gStyle.fPadTopMargin - JSROOT.gStyle.fPadBottomMargin));
3249  }
3250 
3251  var pw = this.pad_width(this.this_pad_name), x2 = pw - size.x - size.width,
3252  ph = this.pad_height(this.this_pad_name), y2 = ph - size.y - size.height;
3253 
3254  if ((x2 >= 0) && (y2 >= 0)) {
3255  // while 3D canvas uses area also for the axis labels, extend area relative to normal frame
3256  size.x = Math.round(size.x * 0.3);
3257  size.y = Math.round(size.y * 0.9);
3258  size.width = pw - size.x - Math.round(x2*0.3);
3259  size.height = ph - size.y - Math.round(y2*0.5);
3260  }
3261 
3262  if (can3d === 1)
3263  this.CalcAbsolutePosition(this.svg_pad(this.this_pad_name), size);
3264 
3265  return size;
3266  }
3267 
3271  TObjectPainter.prototype.clear_3d_canvas = function() {
3272  var can3d = this.access_3d_kind(null);
3273  if (can3d < 0) {
3274  // remove first child from main element - if it is canvas
3275  var main = this.select_main().node();
3276  if (main && main.firstChild && main.firstChild.$jsroot) {
3277  delete main.firstChild.painter;
3278  main.removeChild(main.firstChild);
3279  }
3280  return can3d;
3281  }
3282 
3283  var size = this.size_for_3d(can3d);
3284 
3285  if (size.can3d === 0) {
3286  d3.select(this.svg_canvas().node().nextSibling).remove(); // remove html5 canvas
3287  this.svg_canvas().style('display', null); // show SVG canvas
3288  } else {
3289  if (this.svg_pad(this.this_pad_name).empty()) return;
3290 
3291  this.apply_3d_size(size).remove();
3292 
3293  this.svg_frame().style('display', null); // clear display property
3294  }
3295  return can3d;
3296  }
3297 
3300  TObjectPainter.prototype.add_3d_canvas = function(size, canv) {
3301 
3302  if (!canv || (size.can3d < -1)) return;
3303 
3304  if (size.can3d === -1) {
3305  // case when 3D object drawn without canvas
3306 
3307  var main = this.select_main().node();
3308  if (main !== null) {
3309  main.appendChild(canv);
3310  canv.painter = this;
3311  canv.$jsroot = true; // mark canvas as added by jsroot
3312  }
3313 
3314  return;
3315  }
3316 
3317  this.access_3d_kind(size.can3d);
3318 
3319  if (size.can3d === 0) {
3320  this.svg_canvas().style('display', 'none'); // hide SVG canvas
3321 
3322  this.svg_canvas().node().parentNode.appendChild(canv); // add directly
3323  } else {
3324  if (this.svg_pad(this.this_pad_name).empty()) return;
3325 
3326  // first hide normal frame
3327  this.svg_frame().style('display', 'none');
3328 
3329  var elem = this.apply_3d_size(size);
3330 
3331  elem.attr('title','').node().appendChild(canv);
3332  }
3333  }
3334 
3337  TObjectPainter.prototype.apply_3d_size = function(size, onlyget) {
3338 
3339  if (size.can3d < 0) return d3.select(null);
3340 
3341  var elem;
3342 
3343  if (size.can3d > 1) {
3344 
3345  elem = this.svg_layer(size.clname);
3346 
3347  // elem = layer.select("." + size.clname);
3348  if (onlyget) return elem;
3349 
3350  var svg = this.svg_pad();
3351 
3352  if ((size.can3d === 3) || (size.can3d === 4)) {
3353  // this is SVG mode or image mode - just create group to hold element
3354 
3355  if (elem.empty())
3356  elem = svg.insert("g",".primitives_layer").attr("class", size.clname);
3357 
3358  elem.attr("transform", "translate(" + size.x + "," + size.y + ")");
3359 
3360  } else {
3361 
3362  if (elem.empty())
3363  elem = svg.insert("foreignObject",".primitives_layer").attr("class", size.clname);
3364 
3365  elem.attr('x', size.x)
3366  .attr('y', size.y)
3367  .attr('width', size.width)
3368  .attr('height', size.height)
3369  .attr('viewBox', "0 0 " + size.width + " " + size.height)
3370  .attr('preserveAspectRatio','xMidYMid');
3371  }
3372 
3373  } else {
3374  var prnt = this.svg_canvas().node().parentNode;
3375 
3376  elem = d3.select(prnt).select("." + size.clname);
3377  if (onlyget) return elem;
3378 
3379  // force redraw by resize
3380  this.svg_canvas().property('redraw_by_resize', true);
3381 
3382  if (elem.empty())
3383  elem = d3.select(prnt).append('div').attr("class", size.clname + " jsroot_noselect");
3384 
3385  // our position inside canvas, but to set 'absolute' position we should use
3386  // canvas element offset relative to first parent with non-static position
3387  // now try to use getBoundingClientRect - it should be more precise
3388 
3389  var pos0 = prnt.getBoundingClientRect();
3390 
3391  while (prnt) {
3392  if (prnt === document) { prnt = null; break; }
3393  try {
3394  if (getComputedStyle(prnt).position !== 'static') break;
3395  } catch(err) {
3396  break;
3397  }
3398  prnt = prnt.parentNode;
3399  }
3400 
3401  var pos1 = prnt ? prnt.getBoundingClientRect() : { top: 0, left: 0 };
3402 
3403  var offx = Math.round(pos0.left - pos1.left),
3404  offy = Math.round(pos0.top - pos1.top);
3405 
3406  elem.style('position','absolute').style('left',(size.x+offx)+'px').style('top',(size.y+offy)+'px').style('width',size.width+'px').style('height',size.height+'px');
3407  }
3408 
3409  return elem;
3410  }
3411 
3417  TObjectPainter.prototype.main_painter = function(not_store, pad_name) {
3418  var res = this.main;
3419  if (!res) {
3420  var svg_p = this.svg_pad(pad_name);
3421  if (svg_p.empty()) {
3422  res = this.AccessTopPainter();
3423  } else {
3424  res = svg_p.property('mainpainter');
3425  }
3426  if (!res) res = null;
3427  if (!not_store) this.main = res;
3428  }
3429  return res;
3430  }
3431 
3433  TObjectPainter.prototype.is_main_painter = function() {
3434  return this === this.main_painter();
3435  }
3436 
3455  TObjectPainter.prototype.SetDivId = function(divid, is_main, pad_name) {
3456 
3457  if (divid !== undefined) {
3458  this.divid = divid;
3459  delete this._selected_main;
3460  }
3461 
3462  if (!is_main || isNaN(is_main)) is_main = 0;
3463 
3464  // check if element really exists
3465  if ((is_main >= 0) && this.select_main(true).empty()) {
3466  if (typeof divid == 'string') console.error('element with id ' + divid + ' not exists');
3467  else console.error('specified HTML element can not be selected with d3.select()');
3468  return;
3469  }
3470 
3471  this.create_canvas = false;
3472 
3473  // SVG element where canvas is drawn
3474  var svg_c = this.svg_canvas();
3475 
3476  if (svg_c.empty() && (is_main > 0) && (is_main!==5)) {
3477  JSROOT.Painter.drawCanvas(divid, null, ((is_main == 2) || (is_main == 4)) ? "noframe" : "");
3478  svg_c = this.svg_canvas();
3479  this.create_canvas = true;
3480  }
3481 
3482  if (svg_c.empty()) {
3483  if ((is_main < 0) || (is_main===5) || this.iscan) return;
3484  this.AccessTopPainter(true);
3485  return;
3486  }
3487 
3488  // SVG element where current pad is drawn (can be canvas itself)
3489  this.pad_name = pad_name;
3490  if (this.pad_name === undefined)
3491  this.pad_name = this.CurrentPadName();
3492 
3493  if (is_main < 0) return;
3494 
3495  // create TFrame element if not exists
3496  if (this.svg_frame().select(".main_layer").empty() && ((is_main == 1) || (is_main == 3) || (is_main == 4))) {
3497  if (typeof JSROOT.Painter.drawFrame == 'function')
3498  JSROOT.Painter.drawFrame(divid, null, (is_main == 4) ? "3d" : "");
3499  if ((is_main != 4) && this.svg_frame().empty()) return alert("Fail to draw dummy TFrame");
3500  }
3501 
3502  var svg_p = this.svg_pad();
3503  if (svg_p.empty()) return;
3504 
3505  var pp = svg_p.property('pad_painter');
3506  if (pp && (pp !== this))
3507  pp.painters.push(this);
3508 
3509  if (((is_main === 1) || (is_main === 4) || (is_main === 5)) && !svg_p.property('mainpainter'))
3510  // when this is first main painter in the pad
3511  svg_p.property('mainpainter', this);
3512  }
3513 
3516  TObjectPainter.prototype.CalcAbsolutePosition = function(sel, pos) {
3517  while (!sel.empty() && !sel.classed('root_canvas')) {
3518  var cl = sel.attr("class");
3519  if (cl && ((cl.indexOf("root_frame")>=0) || (cl.indexOf("__root_pad_")>=0))) {
3520  pos.x += sel.property("draw_x") || 0;
3521  pos.y += sel.property("draw_y") || 0;
3522  }
3523  sel = d3.select(sel.node().parentNode);
3524  }
3525  return pos;
3526  }
3527 
3536  TObjectPainter.prototype.createAttMarker = function(args) {
3537  if (!args || (typeof args !== 'object')) args = { std: true }; else
3538  if (args.fMarkerColor!==undefined && args.fMarkerStyle!==undefined && args.fMarkerSize!==undefined) args = { attr: args, std: false };
3539 
3540  if (args.std === undefined) args.std = true;
3541 
3542  var handler = args.std ? this.markeratt : null;
3543 
3544  if (!handler) handler = new TAttMarkerHandler(args);
3545  else if (!handler.changed || args.force) handler.SetArgs(args);
3546 
3547  if (args.std) this.markeratt = handler;
3548 
3549  // handler.used = false; // mark that line handler is not yet used
3550  return handler;
3551  }
3552 
3553 
3561  TObjectPainter.prototype.createAttLine = function(args) {
3562  if (!args || (typeof args !== 'object')) args = { std: true }; else
3563  if (args.fLineColor!==undefined && args.fLineStyle!==undefined && args.fLineWidth!==undefined) args = { attr: args, std: false };
3564 
3565  if (args.std === undefined) args.std = true;
3566 
3567  var handler = args.std ? this.lineatt : null;
3568 
3569  if (!handler) handler = new TAttLineHandler(args);
3570  else if (!handler.changed || args.force) handler.SetArgs(args);
3571 
3572  if (args.std) this.lineatt = handler;
3573 
3574  // handler.used = false; // mark that line handler is not yet used
3575  return handler;
3576  }
3577 
3594  TObjectPainter.prototype.createAttFill = function(args) {
3595  if (!args || (typeof args !== 'object')) args = { std: true }; else
3596  if (args._typename && args.fFillColor!==undefined && args.fFillStyle!==undefined) args = { attr: args, std: false };
3597 
3598  if (args.std === undefined) args.std = true;
3599 
3600  var handler = args.std ? this.fillatt : null;
3601 
3602  if (!args.svg) args.svg = this.svg_canvas();
3603 
3604  if (!handler) handler = new TAttFillHandler(args);
3605  else if (!handler.changed || args.force) handler.SetArgs(args);
3606 
3607  if (args.std) this.fillatt = handler;
3608 
3609  // handler.used = false; // mark that fill handler is not yet used
3610 
3611  return handler;
3612  }
3613 
3616  TObjectPainter.prototype.ForEachPainter = function(userfunc, kind) {
3617  // Iterate over all known painters
3618 
3619  // special case of the painter set as pointer of first child of main element
3620  var painter = this.AccessTopPainter();
3621  if (painter) {
3622  if (kind !== "pads") userfunc(painter);
3623  return;
3624  }
3625 
3626  // iterate over all painters from pad list
3627  var pp = this.pad_painter();
3628  if (pp) pp.ForEachPainterInPad(userfunc, kind);
3629  }
3630 
3634  TObjectPainter.prototype.InteractiveRedraw = function(arg, info, subelem) {
3635 
3636  if (arg == "pad") {
3637  this.RedrawPad();
3638  } else if (arg == "axes") {
3639  var main = this.main_painter(true, this.this_pad_name); // works for pad and any object drawn in the pad
3640  if (main && (typeof main.DrawAxes == 'function'))
3641  main.DrawAxes();
3642  else
3643  this.RedrawPad();
3644  } else if (arg !== false) {
3645  this.Redraw();
3646  }
3647 
3648  // inform GED that something changes
3649  var pp = this.pad_painter();
3650  if (pp && (typeof pp.InteractiveObjectRedraw == 'function'))
3651  pp.InteractiveObjectRedraw(this);
3652 
3653  // inform server that drawopt changes
3654  var canp = this.canv_painter();
3655  if (canp && (typeof canp.ProcessChanges == 'function'))
3656  canp.ProcessChanges(info, this, subelem);
3657  }
3658 
3660  TObjectPainter.prototype.RedrawPad = function() {
3661  var pad_painter = this.pad_painter();
3662  if (pad_painter) pad_painter.Redraw();
3663  }
3664 
3667  TObjectPainter.prototype.SwitchTooltip = function(on) {
3668  var fp = this.frame_painter();
3669  if (fp) fp.ProcessTooltipEvent(null, on);
3670  // this is 3D control object
3671  if (this.control && (typeof this.control.SwitchTooltip == 'function'))
3672  this.control.SwitchTooltip(on);
3673  }
3674 
3676  TObjectPainter.prototype.AddMove = function() {
3677 
3678  if (!JSROOT.gStyle.MoveResize || JSROOT.BatchMode ||
3679  !this.draw_g || this.draw_g.property("assigned_move")) return;
3680 
3681  function detectRightButton(event) {
3682  if ('buttons' in event) return event.buttons === 2;
3683  else if ('which' in event) return event.which === 3;
3684  else if ('button' in event) return event.button === 2;
3685  return false;
3686  }
3687 
3688  var prefix = "", drag_move, not_changed = true;
3689  if (JSROOT._test_d3_ === 3) {
3690  prefix = "drag";
3691  drag_move = d3.behavior.drag().origin(Object);
3692  } else {
3693  drag_move = d3.drag().subject(Object);
3694  }
3695 
3696  drag_move
3697  .on(prefix+"start", function() {
3698  if (detectRightButton(d3.event.sourceEvent)) return;
3699  d3.event.sourceEvent.preventDefault();
3700  d3.event.sourceEvent.stopPropagation();
3701  var pos = d3.mouse(this.draw_g.node());
3702  not_changed = true;
3703  if (this.moveStart)
3704  this.moveStart(pos[0], pos[1]);
3705  }.bind(this)).on("drag", function() {
3706  d3.event.sourceEvent.preventDefault();
3707  d3.event.sourceEvent.stopPropagation();
3708  not_changed = false;
3709  if (this.moveDrag)
3710  this.moveDrag(d3.event.dx, d3.event.dy);
3711  }.bind(this)).on(prefix+"end", function() {
3712  d3.event.sourceEvent.preventDefault();
3713  d3.event.sourceEvent.stopPropagation();
3714  if (this.moveEnd)
3715  this.moveEnd(not_changed);
3716  var cp = this.canv_painter();
3717  if (cp) cp.SelectObjectPainter(this);
3718  }.bind(this));
3719 
3720  this.draw_g
3721  .style("cursor", "move")
3722  .property("assigned_move", true)
3723  .call(drag_move);
3724  }
3725 
3728  TObjectPainter.prototype.AddDrag = function(callback) {
3729  if (!JSROOT.gStyle.MoveResize || JSROOT.BatchMode) return;
3730 
3731  var pthis = this, drag_rect = null, pp = this.pad_painter();
3732  if (pp && pp._fast_drawing) return;
3733 
3734  function detectRightButton(event) {
3735  if ('buttons' in event) return event.buttons === 2;
3736  else if ('which' in event) return event.which === 3;
3737  else if ('button' in event) return event.button === 2;
3738  return false;
3739  }
3740 
3741  function rect_width() { return Number(pthis.draw_g.attr("width")); }
3742  function rect_height() { return Number(pthis.draw_g.attr("height")); }
3743 
3744  function MakeResizeElements(group, width, height, handler) {
3745  function make(cursor,d) {
3746  var clname = "js_" + cursor.replace('-','_'),
3747  elem = group.select('.'+clname);
3748  if (elem.empty()) elem = group.append('path').classed(clname,true);
3749  elem.style('opacity', 0).style('cursor', cursor).attr('d',d);
3750  if (handler) elem.call(handler);
3751  }
3752 
3753  make("nw-resize", "M2,2h15v-5h-20v20h5Z");
3754  make("ne-resize", "M" + (width-2) + ",2h-15v-5h20v20h-5 Z");
3755  make("sw-resize", "M2," + (height-2) + "h15v5h-20v-20h5Z");
3756  make("se-resize", "M" + (width-2) + "," + (height-2) + "h-15v5h20v-20h-5Z");
3757 
3758  make("w-resize", "M-3,18h5v" + Math.max(0, height - 2*18) + "h-5Z");
3759  make("e-resize", "M" + (width+3) + ",18h-5v" + Math.max(0, height - 2*18) + "h5Z");
3760  make("n-resize", "M18,-3v5h" + Math.max(0, width - 2*18) + "v-5Z");
3761  make("s-resize", "M18," + (height+3) + "v-5h" + Math.max(0, width - 2*18) + "v5Z");
3762  }
3763 
3764  function complete_drag() {
3765  drag_rect.style("cursor", "auto");
3766 
3767  if (!pthis.draw_g) {
3768  drag_rect.remove();
3769  drag_rect = null;
3770  return false;
3771  }
3772 
3773  var oldx = Number(pthis.draw_g.attr("x")),
3774  oldy = Number(pthis.draw_g.attr("y")),
3775  newx = Number(drag_rect.attr("x")),
3776  newy = Number(drag_rect.attr("y")),
3777  newwidth = Number(drag_rect.attr("width")),
3778  newheight = Number(drag_rect.attr("height"));
3779 
3780  if (callback.minwidth && newwidth < callback.minwidth) newwidth = callback.minwidth;
3781  if (callback.minheight && newheight < callback.minheight) newheight = callback.minheight;
3782 
3783  var change_size = (newwidth !== rect_width()) || (newheight !== rect_height()),
3784  change_pos = (newx !== oldx) || (newy !== oldy);
3785 
3786  pthis.draw_g.attr('x', newx).attr('y', newy)
3787  .attr("transform", "translate(" + newx + "," + newy + ")")
3788  .attr('width', newwidth).attr('height', newheight);
3789 
3790  drag_rect.remove();
3791  drag_rect = null;
3792 
3793  pthis.SwitchTooltip(true);
3794 
3795  MakeResizeElements(pthis.draw_g, newwidth, newheight);
3796 
3797  if (change_size || change_pos) {
3798  if (change_size && ('resize' in callback)) callback.resize(newwidth, newheight);
3799  if (change_pos && ('move' in callback)) callback.move(newx, newy, newx - oldxx, newy-oldy);
3800 
3801  if (change_size || change_pos) {
3802  if ('obj' in callback) {
3803  callback.obj.fX1NDC = newx / pthis.pad_width();
3804  callback.obj.fX2NDC = (newx + newwidth) / pthis.pad_width();
3805  callback.obj.fY1NDC = 1 - (newy + newheight) / pthis.pad_height();
3806  callback.obj.fY2NDC = 1 - newy / pthis.pad_height();
3807  callback.obj.modified_NDC = true; // indicate that NDC was interactively changed, block in updated
3808  }
3809  if ('redraw' in callback) callback.redraw();
3810  }
3811  }
3812 
3813  return change_size || change_pos;
3814  }
3815 
3816  var prefix = "", drag_move, drag_resize;
3817  if (JSROOT._test_d3_ === 3) {
3818  prefix = "drag";
3819  drag_move = d3.behavior.drag().origin(Object);
3820  drag_resize = d3.behavior.drag().origin(Object);
3821  } else {
3822  drag_move = d3.drag().subject(Object);
3823  drag_resize = d3.drag().subject(Object);
3824  }
3825 
3826  drag_move
3827  .on(prefix+"start", function() {
3828  if (detectRightButton(d3.event.sourceEvent)) return;
3829 
3830  JSROOT.Painter.closeMenu(); // close menu
3831 
3832  pthis.SwitchTooltip(false); // disable tooltip
3833 
3834  d3.event.sourceEvent.preventDefault();
3835  d3.event.sourceEvent.stopPropagation();
3836 
3837  var handle = {
3838  acc_x1: Number(pthis.draw_g.attr("x")),
3839  acc_y1: Number(pthis.draw_g.attr("y")),
3840  pad_w: pthis.pad_width() - rect_width(),
3841  pad_h: pthis.pad_height() - rect_height(),
3842  drag_tm: new Date()
3843  };
3844 
3845  drag_rect = d3.select(pthis.draw_g.node().parentNode).append("rect")
3846  .classed("zoom", true)
3847  .attr("x", handle.acc_x1)
3848  .attr("y", handle.acc_y1)
3849  .attr("width", rect_width())
3850  .attr("height", rect_height())
3851  .style("cursor", "move")
3852  .style("pointer-events","none") // let forward double click to underlying elements
3853  .property('drag_handle', handle);
3854 
3855 
3856  }).on("drag", function() {
3857  if (!drag_rect) return;
3858 
3859  d3.event.sourceEvent.preventDefault();
3860  d3.event.sourceEvent.stopPropagation();
3861 
3862  var handle = drag_rect.property('drag_handle');
3863 
3864  handle.acc_x1 += d3.event.dx;
3865  handle.acc_y1 += d3.event.dy;
3866 
3867  drag_rect.attr("x", Math.min( Math.max(handle.acc_x1, 0), handle.pad_w))
3868  .attr("y", Math.min( Math.max(handle.acc_y1, 0), handle.pad_h));
3869 
3870  }).on(prefix+"end", function() {
3871  if (!drag_rect) return;
3872 
3873  d3.event.sourceEvent.preventDefault();
3874 
3875  var handle = drag_rect.property('drag_handle');
3876 
3877  if (complete_drag() === false) {
3878  var spent = (new Date()).getTime() - handle.drag_tm.getTime();
3879  if (callback.ctxmenu && (spent > 600)) {
3880  var rrr = resize_se.node().getBoundingClientRect();
3881  pthis.ShowContextMenu('main', { clientX: rrr.left, clientY: rrr.top } );
3882  } else if (callback.canselect && (spent <= 600)) {
3883  pthis.canv_painter().SelectObjectPainter(pthis);
3884  }
3885  }
3886  });
3887 
3888  drag_resize
3889  .on(prefix+"start", function() {
3890  if (detectRightButton(d3.event.sourceEvent)) return;
3891 
3892  d3.event.sourceEvent.stopPropagation();
3893  d3.event.sourceEvent.preventDefault();
3894 
3895  pthis.SwitchTooltip(false); // disable tooltip
3896 
3897  var handle = {
3898  acc_x1: Number(pthis.draw_g.attr("x")),
3899  acc_y1: Number(pthis.draw_g.attr("y")),
3900  pad_w: pthis.pad_width(),
3901  pad_h: pthis.pad_height()
3902  };
3903 
3904  handle.acc_x2 = handle.acc_x1 + rect_width();
3905  handle.acc_y2 = handle.acc_y1 + rect_height();
3906 
3907  drag_rect = d3.select(pthis.draw_g.node().parentNode)
3908  .append("rect")
3909  .classed("zoom", true)
3910  .style("cursor", d3.select(this).style("cursor"))
3911  .attr("x", handle.acc_x1)
3912  .attr("y", handle.acc_y1)
3913  .attr("width", handle.acc_x2 - handle.acc_x1)
3914  .attr("height", handle.acc_y2 - handle.acc_y1)
3915  .property('drag_handle', handle);
3916 
3917  }).on("drag", function() {
3918  if (!drag_rect) return;
3919 
3920  d3.event.sourceEvent.preventDefault();
3921  d3.event.sourceEvent.stopPropagation();
3922 
3923  var handle = drag_rect.property('drag_handle'),
3924  dx = d3.event.dx, dy = d3.event.dy, elem = d3.select(this);
3925 
3926  if (elem.classed('js_nw_resize')) { handle.acc_x1 += dx; handle.acc_y1 += dy; }
3927  else if (elem.classed('js_ne_resize')) { handle.acc_x2 += dx; handle.acc_y1 += dy; }
3928  else if (elem.classed('js_sw_resize')) { handle.acc_x1 += dx; handle.acc_y2 += dy; }
3929  else if (elem.classed('js_se_resize')) { handle.acc_x2 += dx; handle.acc_y2 += dy; }
3930  else if (elem.classed('js_w_resize')) { handle.acc_x1 += dx; }
3931  else if (elem.classed('js_n_resize')) { handle.acc_y1 += dy; }
3932  else if (elem.classed('js_e_resize')) { handle.acc_x2 += dx; }
3933  else if (elem.classed('js_s_resize')) { handle.acc_y2 += dy; }
3934 
3935  var x1 = Math.max(0, handle.acc_x1), x2 = Math.min(handle.acc_x2, handle.pad_w),
3936  y1 = Math.max(0, handle.acc_y1), y2 = Math.min(handle.acc_y2, handle.pad_h);
3937 
3938  drag_rect.attr("x", x1).attr("y", y1).attr("width", Math.max(0, x2-x1)).attr("height", Math.max(0, y2-y1));
3939 
3940  }).on(prefix+"end", function() {
3941  if (!drag_rect) return;
3942 
3943  d3.event.sourceEvent.preventDefault();
3944 
3945  complete_drag();
3946  });
3947 
3948  if (!callback.only_resize)
3949  this.draw_g.style("cursor", "move").call(drag_move);
3950 
3951  MakeResizeElements(this.draw_g, rect_width(), rect_height(), drag_resize);
3952  }
3953 
3956  TObjectPainter.prototype.startTouchMenu = function(kind) {
3957  // method to let activate context menu via touch handler
3958 
3959  var arr = d3.touches(this.svg_frame().node());
3960  if (arr.length != 1) return;
3961 
3962  if (!kind || (kind=="")) kind = "main";
3963  var fld = "touch_" + kind;
3964 
3965  d3.event.preventDefault();
3966  d3.event.stopPropagation();
3967 
3968  this[fld] = { dt: new Date(), pos: arr[0] };
3969 
3970  this.svg_frame().on("touchcancel", this.endTouchMenu.bind(this, kind))
3971  .on("touchend", this.endTouchMenu.bind(this, kind));
3972  }
3973 
3976  TObjectPainter.prototype.endTouchMenu = function(kind) {
3977  var fld = "touch_" + kind;
3978 
3979  if (! (fld in this)) return;
3980 
3981  d3.event.preventDefault();
3982  d3.event.stopPropagation();
3983 
3984  var diff = new Date().getTime() - this[fld].dt.getTime();
3985 
3986  this.svg_frame().on("touchcancel", null)
3987  .on("touchend", null);
3988 
3989  if (diff>500) {
3990  var rect = this.svg_frame().node().getBoundingClientRect();
3991  this.ShowContextMenu(kind, { clientX: rect.left + this[fld].pos[0],
3992  clientY: rect.top + this[fld].pos[1] } );
3993  }
3994 
3995  delete this[fld];
3996  }
3997 
4000  TObjectPainter.prototype.AddColorMenuEntry = function(menu, name, value, set_func, fill_kind) {
4001  if (value === undefined) return;
4002  menu.add("sub:"+name, function() {
4003  // todo - use jqury dialog here
4004  var useid = (typeof value !== 'string');
4005  var col = prompt("Enter color " + (useid ? "(only id number)" : "(name or id)"), value);
4006  if (col == null) return;
4007  var id = parseInt(col);
4008  if (!isNaN(id) && (JSROOT.Painter.root_colors[id] !== undefined)) {
4009  col = JSROOT.Painter.root_colors[id];
4010  } else {
4011  if (useid) return;
4012  }
4013  set_func.bind(this)(useid ? id : col);
4014  });
4015  var useid = (typeof value !== 'string');
4016  for (var n=-1;n<11;++n) {
4017  if ((n<0) && useid) continue;
4018  if ((n==10) && (fill_kind!==1)) continue;
4019  var col = (n<0) ? 'none' : JSROOT.Painter.root_colors[n];
4020  if ((n==0) && (fill_kind==1)) col = 'none';
4021  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>";
4022  menu.addchk((value == (useid ? n : col)), svg, (useid ? n : col), set_func);
4023  }
4024  menu.add("endsub:");
4025  }
4026 
4029  TObjectPainter.prototype.AddSizeMenuEntry = function(menu, name, min, max, step, value, set_func) {
4030  if (value === undefined) return;
4031 
4032  menu.add("sub:"+name, function() {
4033  // todo - use jqury dialog here
4034  var entry = value.toFixed(4);
4035  if (step>=0.1) entry = value.toFixed(2);
4036  if (step>=1) entry = value.toFixed(0);
4037  var val = prompt("Enter value of " + name, entry);
4038  if (val==null) return;
4039  var val = parseFloat(val);
4040  if (!isNaN(val)) set_func.bind(this)((step>=1) ? Math.round(val) : val);
4041  });
4042  for (var val=min;val<=max;val+=step) {
4043  var entry = val.toFixed(2);
4044  if (step>=0.1) entry = val.toFixed(1);
4045  if (step>=1) entry = val.toFixed(0);
4046  menu.addchk((Math.abs(value - val) < step/2), entry, val, set_func);
4047  }
4048  menu.add("endsub:");
4049  }
4050 
4053  TObjectPainter.prototype.ExecuteMenuCommand = function(method) {
4054 
4055  if (method.fName == "Inspect") {
4056  // primitve inspector, keep it here
4057  this.ShowInspector();
4058  return true;
4059  }
4060 
4061  var canvp = this.canv_painter();
4062  if (!canvp) return false;
4063 
4064  return false;
4065  }
4066 
4074  TObjectPainter.prototype.WebCanvasExec = function(exec, snapid) {
4075  if (!exec || (typeof exec != 'string')) return;
4076 
4077  if (!snapid) snapid = this.snapid;
4078  if (!snapid || (typeof snapid != 'string')) return;
4079 
4080  var canp = this.canv_painter();
4081  if (canp && !canp._readonly && canp._websocket)
4082  canp.SendWebsocket("OBJEXEC:" + snapid + ":" + exec);
4083  }
4084 
4087  TObjectPainter.prototype.FillObjectExecMenu = function(menu, kind, call_back) {
4088 
4089  var canvp = this.canv_painter();
4090 
4091  if (!this.snapid || !canvp || canvp._readonly || !canvp._websocket || canvp._getmenu_callback)
4092  return JSROOT.CallBack(call_back);
4093 
4094  function DoExecMenu(arg) {
4095  var execp = this.exec_painter || this,
4096  cp = execp.canv_painter(),
4097  item = execp.args_menu_items[parseInt(arg)];
4098 
4099  if (!item || !item.fName) return;
4100 
4101  // this is special entry, produced by TWebMenuItem, which recognizes editor entries itself
4102  if (item.fExec == "Show:Editor") {
4103  if (cp && (typeof cp.ActivateGed == 'function'))
4104  cp.ActivateGed(execp);
4105  return;
4106  }
4107 
4108  if (cp && (typeof cp.executeObjectMethod == 'function'))
4109  if (cp.executeObjectMethod(execp, item, execp.args_menu_id)) return;
4110 
4111  if (execp.ExecuteMenuCommand(item)) return;
4112 
4113  if (execp.args_menu_id)
4114  execp.WebCanvasExec(item.fExec, execp.args_menu_id);
4115  }
4116 
4117  function DoFillMenu(_menu, _reqid, _call_back, reply) {
4118 
4119  // avoid multiple call of the callback after timeout
4120  if (!canvp._getmenu_callback) return;
4121  delete canvp._getmenu_callback;
4122 
4123  if (reply && (_reqid !== reply.fId))
4124  console.error('missmatch between request ' + _reqid + ' and reply ' + reply.fId + ' identifiers');
4125 
4126  var items = reply ? reply.fItems : null;
4127 
4128  if (items && items.length) {
4129  _menu.add("separator");
4130 
4131  this.args_menu_items = items;
4132  this.args_menu_id = reply.fId;
4133 
4134  var lastclname;
4135 
4136  for (var n=0;n<items.length;++n) {
4137  var item = items[n];
4138 
4139  if (item.fClassName && lastclname && (lastclname!=item.fClassName)) {
4140  _menu.add("endsub:");
4141  lastclname = "";
4142  }
4143  if (lastclname != item.fClassName) {
4144  lastclname = item.fClassName;
4145  _menu.add("sub:" + lastclname);
4146  }
4147 
4148  if ((item.fChecked === undefined) || (item.fChecked < 0))
4149  _menu.add(item.fName, n, DoExecMenu);
4150  else
4151  _menu.addchk(item.fChecked, item.fName, n, DoExecMenu);
4152  }
4153 
4154  if (lastclname) _menu.add("endsub:");
4155  }
4156 
4157  JSROOT.CallBack(_call_back);
4158  }
4159 
4160  var reqid = this.snapid;
4161  if (kind) reqid += "#" + kind; // use # to separate object id from member specifier like 'x' or 'z'
4162 
4163  // if menu painter differs from this, remember it for further usage
4164  if (menu.painter)
4165  menu.painter.exec_painter = (menu.painter !== this) ? this : undefined;
4166 
4167  canvp._getmenu_callback = DoFillMenu.bind(this, menu, reqid, call_back);
4168 
4169  canvp.SendWebsocket('GETMENU:' + reqid); // request menu items for given painter
4170 
4171  setTimeout(canvp._getmenu_callback, 2000); // set timeout to avoid menu hanging
4172  }
4173 
4176  TObjectPainter.prototype.DeleteAtt = function() {
4177  delete this.lineatt;
4178  delete this.fillatt;
4179  delete this.markeratt;
4180  }
4181 
4186  TObjectPainter.prototype.GetColorExec = function(col, method) {
4187  var id = -1, arr = JSROOT.Painter.root_colors;
4188  if (typeof col == "string") {
4189  if (!col || (col == "none")) id = 0; else
4190  for (var k=1;k<arr.length;++k)
4191  if (arr[k] == col) { id = k; break; }
4192  if ((id < 0) && (col.indexOf("rgb")==0)) id = 9999;
4193  } else if (!isNaN(col) && arr[col]) {
4194  id = col;
4195  col = arr[id];
4196  }
4197 
4198  if (id < 0) return "";
4199 
4200  if (id >= 50) {
4201  // for higher color numbers ensure that such color exists
4202  var c = d3.color(col);
4203  id = "TColor::GetColor(" + c.r + "," + c.g + "," + c.b + ")";
4204  }
4205 
4206  return "exec:" + method + "(" + id + ")";
4207  }
4208 
4211  TObjectPainter.prototype.FillAttContextMenu = function(menu, preffix) {
4212  // this method used to fill entries for different attributes of the object
4213  // like TAttFill, TAttLine, ....
4214  // all menu call-backs need to be rebind, while menu can be used from other painter
4215 
4216  if (!preffix) preffix = "";
4217 
4218  if (this.lineatt && this.lineatt.used) {
4219  menu.add("sub:"+preffix+"Line att");
4220  this.AddSizeMenuEntry(menu, "width", 1, 10, 1, this.lineatt.width,
4221  function(arg) { this.lineatt.Change(undefined, parseInt(arg)); this.InteractiveRedraw(true, "exec:SetLineWidth(" + arg + ")"); }.bind(this));
4222  this.AddColorMenuEntry(menu, "color", this.lineatt.color,
4223  function(arg) { this.lineatt.Change(arg); this.InteractiveRedraw(true, this.GetColorExec(arg, "SetLineColor")); }.bind(this));
4224  menu.add("sub:style", function() {
4225  var id = prompt("Enter line style id (1-solid)", 1);
4226  if (id == null) return;
4227  id = parseInt(id);
4228  if (isNaN(id) || !JSROOT.Painter.root_line_styles[id]) return;
4229  this.lineatt.Change(undefined, undefined, id);
4230  this.InteractiveRedraw(true, "exec:SetLineStyle(" + id + ")");
4231  }.bind(this));
4232  for (var n=1;n<11;++n) {
4233 
4234  var dash = JSROOT.Painter.root_line_styles[n];
4235 
4236  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='" + dash + "'></line></svg>";
4237 
4238  menu.addchk((this.lineatt.style==n), svg, n, function(arg) { this.lineatt.Change(undefined, undefined, parseInt(arg)); this.InteractiveRedraw(true, "exec:SetLineStyle(" + arg + ")"); }.bind(this));
4239  }
4240  menu.add("endsub:");
4241  menu.add("endsub:");
4242 
4243  if (('excl_side' in this.lineatt) && (this.lineatt.excl_side!==0)) {
4244  menu.add("sub:Exclusion");
4245  menu.add("sub:side");
4246  for (var side=-1;side<=1;++side)
4247  menu.addchk((this.lineatt.excl_side==side), side, side, function(arg) {
4248  this.lineatt.ChangeExcl(parseInt(arg));
4249  this.InteractiveRedraw();
4250  }.bind(this));
4251  menu.add("endsub:");
4252 
4253  this.AddSizeMenuEntry(menu, "width", 10, 100, 10, this.lineatt.excl_width,
4254  function(arg) { this.lineatt.ChangeExcl(undefined, parseInt(arg)); this.InteractiveRedraw(); }.bind(this));
4255 
4256  menu.add("endsub:");
4257  }
4258  }
4259 
4260  if (this.fillatt && this.fillatt.used) {
4261  menu.add("sub:"+preffix+"Fill att");
4262  this.AddColorMenuEntry(menu, "color", this.fillatt.colorindx,
4263  function(arg) { this.fillatt.Change(parseInt(arg), undefined, this.svg_canvas()); this.InteractiveRedraw(true, this.GetColorExec(parseInt(arg), "SetFillColor")); }.bind(this), this.fillatt.kind);
4264  menu.add("sub:style", function() {
4265  var id = prompt("Enter fill style id (1001-solid, 3000..3010)", this.fillatt.pattern);
4266  if (id == null) return;
4267  id = parseInt(id);
4268  if (isNaN(id)) return;
4269  this.fillatt.Change(undefined, id, this.svg_canvas());
4270  this.InteractiveRedraw(true, "exec:SetFillStyle(" + id + ")");
4271  }.bind(this));
4272 
4273  var supported = [1, 1001, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3010, 3021, 3022];
4274 
4275  for (var n=0; n<supported.length; ++n) {
4276 
4277  var sample = this.createAttFill({ std: false, pattern: supported[n], color: this.fillatt.colorindx || 1 }),
4278  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='" + sample.fillcolor() + "'></rect></svg>";
4279 
4280  menu.addchk(this.fillatt.pattern == supported[n], svg, supported[n], function(arg) {
4281  this.fillatt.Change(undefined, parseInt(arg), this.svg_canvas());
4282  this.InteractiveRedraw(true, "exec:SetFillStyle(" + arg + ")");
4283  }.bind(this));
4284  }
4285  menu.add("endsub:");
4286  menu.add("endsub:");
4287  }
4288 
4289  if (this.markeratt && this.markeratt.used) {
4290  menu.add("sub:"+preffix+"Marker att");
4291  this.AddColorMenuEntry(menu, "color", this.markeratt.color,
4292  function(arg) { this.markeratt.Change(arg); this.InteractiveRedraw(true, this.GetColorExec(arg, "SetMarkerColor")); }.bind(this));
4293  this.AddSizeMenuEntry(menu, "size", 0.5, 6, 0.5, this.markeratt.size,
4294  function(arg) { this.markeratt.Change(undefined, undefined, parseFloat(arg)); this.InteractiveRedraw(true, "exec:SetMarkerSize(" + parseInt(arg) + ")"); }.bind(this));
4295 
4296  menu.add("sub:style");
4297  var supported = [1,2,3,4,5,6,7,8,21,22,23,24,25,26,27,28,29,30,31,32,33,34];
4298 
4299  for (var n=0; n<supported.length; ++n) {
4300 
4301  var clone = new TAttMarkerHandler({ style: supported[n], color: this.markeratt.color, size: 1.7 }),
4302  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>";
4303 
4304  menu.addchk(this.markeratt.style == supported[n], svg, supported[n],
4305  function(arg) { this.markeratt.Change(undefined, parseInt(arg)); this.InteractiveRedraw(true, "exec:SetMarkerStyle(" + arg + ")"); }.bind(this));
4306  }
4307  menu.add("endsub:");
4308  menu.add("endsub:");
4309  }
4310  }
4311 
4314  TObjectPainter.prototype.TextAttContextMenu = function(menu, prefix) {
4315  // for the moment, text attributes accessed directly from objects
4316 
4317  var obj = this.GetObject();
4318  if (!obj || !('fTextColor' in obj)) return;
4319 
4320  menu.add("sub:" + (prefix ? prefix : "Text"));
4321  this.AddColorMenuEntry(menu, "color", obj.fTextColor,
4322  function(arg) { this.GetObject().fTextColor = parseInt(arg); this.InteractiveRedraw(true, this.GetColorExec(parseInt(arg), "SetTextColor")); }.bind(this));
4323 
4324  var align = [11, 12, 13, 21, 22, 23, 31, 32, 33],
4325  hnames = ['left', 'centered' , 'right'],
4326  vnames = ['bottom', 'centered', 'top'];
4327 
4328  menu.add("sub:align");
4329  for (var n=0; n<align.length; ++n) {
4330  menu.addchk(align[n] == obj.fTextAlign,
4331  align[n], align[n],
4332  // align[n].toString() + "_h:" + hnames[Math.floor(align[n]/10) - 1] + "_v:" + vnames[align[n]%10-1], align[n],
4333  function(arg) { this.GetObject().fTextAlign = parseInt(arg); this.InteractiveRedraw(true, "exec:SetTextAlign("+arg+")"); }.bind(this));
4334  }
4335  menu.add("endsub:");
4336 
4337  menu.add("sub:font");
4338  for (var n=1; n<16; ++n) {
4339  menu.addchk(n == Math.floor(obj.fTextFont/10), n, n,
4340  function(arg) { this.GetObject().fTextFont = parseInt(arg)*10+2; this.InteractiveRedraw(true, "exec:SetTextFont("+this.GetObject().fTextFont+")"); }.bind(this));
4341  }
4342  menu.add("endsub:");
4343 
4344  menu.add("endsub:");
4345  }
4346 
4348  TObjectPainter.prototype.ShowInspector = function(obj) {
4349  var main = this.select_main(),
4350  rect = this.get_visible_rect(main),
4351  w = Math.round(rect.width*0.05) + "px",
4352  h = Math.round(rect.height*0.05) + "px",
4353  id = "root_inspector_" + JSROOT.id_counter++,
4354  cont = main.append("div")
4355  .attr("id", id)
4356  .attr("class", "jsroot_inspector")
4357  .style('position', 'absolute')
4358  .style('top', h)
4359  .style('bottom', h)
4360  .style('left', w)
4361  .style('right', w);
4362 
4363  if (!obj || (typeof obj !== 'object') || !obj._typename)
4364  obj = this.GetObject();
4365 
4366  JSROOT.draw(id, obj, 'inspect');
4367  }
4368 
4371  TObjectPainter.prototype.FillContextMenu = function(menu) {
4372  var title = this.GetTipName();
4373  if (this.GetObject() && ('_typename' in this.GetObject()))
4374  title = this.GetObject()._typename + "::" + title;
4375 
4376  menu.add("header:"+ title);
4377 
4378  this.FillAttContextMenu(menu);
4379 
4380  if (menu.size() > 0)
4381  menu.add('Inspect', this.ShowInspector);
4382 
4383  return menu.size() > 0;
4384  }
4385 
4388  TObjectPainter.prototype.GetShowStatusFunc = function() {
4389  // return function used to display object status
4390  // automatically disabled when drawing is enlarged - status line will be invisible
4391 
4392  var pp = this.canv_painter(), res = JSROOT.Painter.ShowStatus;
4393 
4394  if (pp && (typeof pp.ShowCanvasStatus === 'function')) res = pp.ShowCanvasStatus.bind(pp);
4395 
4396  if (res && (this.enlarge_main('state')==='on')) res = null;
4397 
4398  return res;
4399  }
4400 
4403  TObjectPainter.prototype.ShowObjectStatus = function() {
4404  // method called normally when mouse enter main object element
4405 
4406  var obj = this.GetObject(),
4407  status_func = this.GetShowStatusFunc();
4408 
4409  if (obj && status_func) status_func(this.GetItemName() || obj.fName, obj.fTitle || obj._typename, obj._typename);
4410  }
4411 
4412 
4416  TObjectPainter.prototype.FindInPrimitives = function(objname) {
4417 
4418  var painter = this.pad_painter();
4419  if (!painter || !painter.pad) return null;
4420 
4421  if (painter.pad.fPrimitives)
4422  for (var n=0;n<painter.pad.fPrimitives.arr.length;++n) {
4423  var prim = painter.pad.fPrimitives.arr[n];
4424  if (('fName' in prim) && (prim.fName === objname)) return prim;
4425  }
4426 
4427  return null;
4428  }
4429 
4434  TObjectPainter.prototype.FindPainterFor = function(selobj,selname,seltype) {
4435 
4436  var painter = this.pad_painter();
4437  var painters = painter ? painter.painters : null;
4438  if (!painters) return null;
4439 
4440  for (var n = 0; n < painters.length; ++n) {
4441  var pobj = painters[n].GetObject();
4442  if (!pobj) continue;
4443 
4444  if (selobj && (pobj === selobj)) return painters[n];
4445  if (!selname && !seltype) continue;
4446  if (selname && (pobj.fName !== selname)) continue;
4447  if (seltype && (pobj._typename !== seltype)) continue;
4448  return painters[n];
4449  }
4450 
4451  return null;
4452  }
4453 
4455  TObjectPainter.prototype.DeleteThis = function() {
4456  var pp = this.pad_painter();
4457  if (pp) {
4458  var k = pp.painters.indexOf(this);
4459  if (k>=0) pp.painters.splice(k,1);
4460  }
4461 
4462  this.Cleanup();
4463  }
4464 
4472  TObjectPainter.prototype.ConfigureUserTooltipCallback = function(call_back, user_timeout) {
4473 
4474  if (!call_back || (typeof call_back !== 'function')) {
4475  delete this.UserTooltipCallback;
4476  delete this.UserTooltipTimeout;
4477  return;
4478  }
4479 
4480  if (user_timeout===undefined) user_timeout = 500;
4481 
4482  this.UserTooltipCallback = call_back;
4483  this.UserTooltipTimeout = user_timeout;
4484  }
4485 
4493  TObjectPainter.prototype.ConfigureUserClickHandler = function(handler) {
4494  var fp = this.frame_painter();
4495  if (fp && typeof fp.ConfigureUserClickHandler == 'function')
4496  fp.ConfigureUserClickHandler(handler);
4497  }
4498 
4506  TObjectPainter.prototype.ConfigureUserDblclickHandler = function(handler) {
4507  var fp = this.frame_painter();
4508  if (fp && typeof fp.ConfigureUserDblclickHandler == 'function')
4509  fp.ConfigureUserDblclickHandler(handler);
4510  }
4511 
4515  TObjectPainter.prototype.IsUserTooltipCallback = function() {
4516  return typeof this.UserTooltipCallback == 'function';
4517  }
4518 
4522  TObjectPainter.prototype.ProvideUserTooltip = function(data) {
4523 
4524  if (!this.IsUserTooltipCallback()) return;
4525 
4526  if (this.UserTooltipTimeout <= 0)
4527  return this.UserTooltipCallback(data);
4528 
4529  if (typeof this.UserTooltipTHandle != 'undefined') {
4530  clearTimeout(this.UserTooltipTHandle);
4531  delete this.UserTooltipTHandle;
4532  }
4533 
4534  if (data==null)
4535  return this.UserTooltipCallback(data);
4536 
4537  this.UserTooltipTHandle = setTimeout(function(d) {
4538  // only after timeout user function will be called
4539  delete this.UserTooltipTHandle;
4540  this.UserTooltipCallback(d);
4541  }.bind(this, data), this.UserTooltipTimeout);
4542  }
4543 
4551  TObjectPainter.prototype.Redraw = function() {
4552  }
4553 
4558  TObjectPainter.prototype.StartTextDrawing = function(font_face, font_size, draw_g, max_font_size) {
4559  // we need to preserve font to be able rescale at the end
4560 
4561  if (!draw_g) draw_g = this.draw_g;
4562 
4563  var font = (font_size==='font') ? font_face : JSROOT.Painter.getFontDetails(font_face, font_size);
4564 
4565  var pp = this.pad_painter();
4566 
4567  draw_g.call(font.func);
4568 
4569  draw_g.property('draw_text_completed', false)
4570  .property('text_font', font)
4571  .property('mathjax_use', false)
4572  .property('text_factor', 0.)
4573  .property('max_text_width', 0) // keep maximal text width, use it later
4574  .property('max_font_size', max_font_size)
4575  .property("_fast_drawing", pp && pp._fast_drawing);
4576 
4577  if (draw_g.property("_fast_drawing"))
4578  draw_g.property("_font_too_small", (max_font_size && (max_font_size<5)) || (font.size < 4));
4579  }
4580 
4583  TObjectPainter.prototype.TextScaleFactor = function(value, draw_g) {
4584  if (!draw_g) draw_g = this.draw_g;
4585  if (value && (value > draw_g.property('text_factor'))) draw_g.property('text_factor', value);
4586  }
4587 
4592  TObjectPainter.prototype.GetBoundarySizes = function(elem) {
4593  if (elem===null) { console.warn('empty node in GetBoundarySizes'); return { width:0, height:0 }; }
4594  var box = elem.getBoundingClientRect(); // works always, but returns sometimes results in ex values, which is difficult to use
4595  if (parseFloat(box.width) > 0) box = elem.getBBox(); // check that elements visible, request precise value
4596  var res = { width : parseInt(box.width), height : parseInt(box.height) };
4597  if ('left' in box) { res.x = parseInt(box.left); res.y = parseInt(box.right); } else
4598  if ('x' in box) { res.x = parseInt(box.x); res.y = parseInt(box.y); }
4599  return res;
4600  }
4601 
4606  TObjectPainter.prototype.FinishTextDrawing = function(draw_g, call_ready) {
4607  if (!draw_g) draw_g = this.draw_g;
4608 
4609  if (draw_g.property('draw_text_completed')) {
4610  JSROOT.CallBack(call_ready);
4611  return draw_g.property('max_text_width');
4612  }
4613 
4614  if (call_ready) draw_g.node().text_callback = call_ready;
4615 
4616  var svgs = null;
4617 
4618  if (draw_g.property('mathjax_use')) {
4619 
4620  var missing = 0;
4621  svgs = draw_g.selectAll(".math_svg");
4622 
4623  svgs.each(function() {
4624  var fo_g = d3.select(this);
4625  if (fo_g.node().parentNode !== draw_g.node()) return;
4626  if (fo_g.select("svg").empty()) missing++;
4627  });
4628 
4629  // is any svg missing we should wait until drawing is really finished
4630  if (missing) return;
4631  }
4632 
4633  //if (!svgs) svgs = draw_g.selectAll(".math_svg");
4634 
4635  //var missing = 0;
4636  //svgs.each(function() {
4637  // var fo_g = d3.select(this);
4638  // if (fo_g.node().parentNode !== draw_g.node()) return;
4639  // var entry = fo_g.property('_element');
4640  // if (d3.select(entry).select("svg").empty()) missing++;
4641  //});
4642  //if (missing) console.warn('STILL SVG MISSING', missing);
4643 
4644  // adjust font size (if there are normal text)
4645  var painter = this,
4646  svg_factor = 0,
4647  f = draw_g.property('text_factor'),
4648  font = draw_g.property('text_font'),
4649  max_sz = draw_g.property('max_font_size'),
4650  font_size = font.size;
4651 
4652  if ((f > 0) && ((f < 0.9) || (f > 1)))
4653  font.size = Math.floor(font.size/f);
4654 
4655  if (max_sz && (font.size > max_sz))
4656  font.size = max_sz;
4657 
4658  if (font.size != font_size) {
4659  draw_g.call(font.func);
4660  font_size = font.size;
4661  }
4662 
4663  // first analyze all MathJax SVG and repair width/height attributes
4664  if (svgs)
4665  svgs.each(function() {
4666  var fo_g = d3.select(this);
4667  if (fo_g.node().parentNode !== draw_g.node()) return;
4668 
4669  var vvv = fo_g.select("svg");
4670  if (vvv.empty()) {
4671  console.log('MathJax SVG ouptut error');
4672  return;
4673  }
4674 
4675  function transform(value) {
4676  if (!value || (typeof value !== "string")) return null;
4677  if (value.indexOf("ex")!==value.length-2) return null;
4678  value = parseFloat(value.substr(0, value.length-2));
4679  return isNaN(value) ? null : value*font_size*0.5;
4680  }
4681 
4682  var width = transform(vvv.attr("width")),
4683  height = transform(vvv.attr("height")),
4684  valign = vvv.attr("style");
4685 
4686  if (valign && valign.indexOf("vertical-align:")==0 && valign.indexOf("ex;")==valign.length-3) {
4687  valign = transform(valign.substr(16, valign.length-17));
4688  } else {
4689  valign = null;
4690  }
4691 
4692  width = (!width || (width<=0.5)) ? 1 : Math.round(width);
4693  height = (!height || (height<=0.5)) ? 1 : Math.round(height);
4694 
4695  vvv.attr("width", width).attr('height', height).attr("style",null);
4696 
4697  if (!JSROOT.nodejs) {
4698  var box = painter.GetBoundarySizes(fo_g.node());
4699  width = 1.05*box.width; height = 1.05*box.height;
4700  }
4701 
4702  var arg = fo_g.property("_arg");
4703 
4704  arg.valign = valign;
4705 
4706  if (arg.scale)
4707  svg_factor = Math.max(svg_factor, width / arg.width, height / arg.height);
4708  });
4709 
4710  if (svgs)
4711  svgs.each(function() {
4712  var fo_g = d3.select(this);
4713  // only direct parent
4714  if (fo_g.node().parentNode !== draw_g.node()) return;
4715 
4716  var arg = fo_g.property("_arg"),
4717  m = fo_g.select("svg"), // MathJax svg
4718  mw = parseInt(m.attr("width")),
4719  mh = parseInt(m.attr("height"));
4720 
4721  if (!isNaN(mh) && !isNaN(mw)) {
4722  if (svg_factor > 0.) {
4723  mw = mw/svg_factor;
4724  mh = mh/svg_factor;
4725  m.attr("width", Math.round(mw)).attr("height", Math.round(mh));
4726  }
4727  } else {
4728  var box = painter.GetBoundarySizes(fo_g.node()); // sizes before rotation
4729  mw = box.width || mw || 100;
4730  mh = box.height || mh || 10;
4731  }
4732 
4733  if ((svg_factor > 0.) && arg.valign) arg.valign = arg.valign/svg_factor;
4734 
4735  if (arg.valign===null) arg.valign = (font_size - mh)/2;
4736 
4737  var sign = { x:1, y:1 }, nx = "x", ny = "y";
4738  if (arg.rotate == 180) { sign.x = sign.y = -1; } else
4739  if ((arg.rotate == 270) || (arg.rotate == 90)) {
4740  sign.x = (arg.rotate == 270) ? -1 : 1;
4741  sign.y = -sign.x;
4742  nx = "y"; ny = "x"; // replace names to which align applied
4743  }
4744 
4745  if (arg.align[0] == 'middle') arg[nx] += sign.x*(arg.width - mw)/2; else
4746  if (arg.align[0] == 'end') arg[nx] += sign.x*(arg.width - mw);
4747 
4748  if (arg.align[1] == 'middle') arg[ny] += sign.y*(arg.height - mh)/2; else
4749  if (arg.align[1] == 'bottom') arg[ny] += sign.y*(arg.height - mh); else
4750  if (arg.align[1] == 'bottom-base') arg[ny] += sign.y*(arg.height - mh - arg.valign);
4751 
4752  var trans = "translate("+arg.x+","+arg.y+")";
4753  if (arg.rotate) trans += " rotate("+arg.rotate+")";
4754 
4755  fo_g.attr('transform', trans).attr('visibility', null).property('_arg',null);
4756  });
4757 
4758  // now hidden text after rescaling can be shown
4759  draw_g.selectAll('.hidden_text').attr('visibility', null).attr('class', null).each(function() {
4760  // case when scaling is changed and we can shift text position only after final text size is defined
4761  var txt = d3.select(this),
4762  arg = txt.property("_arg");
4763 
4764  txt.property("_arg", null);
4765 
4766  if (!arg) return;
4767 
4768  if (JSROOT.nodejs) {
4769  if (arg.scale && (f>0)) { arg.box.width = arg.box.width/f; arg.box.height = arg.box.height/f; }
4770  } else if (!arg.plain && !arg.fast) {
4771  // exact box dimension only required when complex text was build
4772  arg.box = painter.GetBoundarySizes(txt.node());
4773  }
4774 
4775  // if (arg.text.length>20) console.log(arg.box, arg.align, arg.x, arg.y, 'plain', arg.plain, 'inside', arg.width, arg.height);
4776 
4777  if (arg.width) {
4778  // adjust x position when scale into specified rectangle
4779  if (arg.align[0]=="middle") arg.x += arg.width/2; else
4780  if (arg.align[0]=="end") arg.x += arg.width;
4781  }
4782 
4783  arg.dx = arg.dy = 0;
4784 
4785  if (arg.plain) {
4786  txt.attr("text-anchor", arg.align[0]);
4787  } else {
4788  txt.attr("text-anchor", "start");
4789  arg.dx = ((arg.align[0]=="middle") ? -0.5 : ((arg.align[0]=="end") ? -1 : 0)) * arg.box.width;
4790  }
4791 
4792  if (arg.height) {
4793  if (arg.align[1].indexOf('bottom')===0) arg.y += arg.height; else
4794  if (arg.align[1] == 'middle') arg.y += arg.height/2;
4795  }
4796 
4797  if (arg.plain) {
4798  if (arg.align[1] == 'top') txt.attr("dy", ".8em"); else
4799  if (arg.align[1] == 'middle') {
4800  if (JSROOT.browser.isIE || JSROOT.nodejs) txt.attr("dy", ".4em"); else txt.attr("dominant-baseline", "middle");
4801  }
4802  } else {
4803  arg.dy = ((arg.align[1] == 'top') ? (arg.top_shift || 1) : (arg.align[1] == 'middle') ? (arg.mid_shift || 0.5) : 0) * arg.box.height;
4804  }
4805 
4806  // if (arg.text.length>20) console.log(arg.x, arg.y, arg.dx, arg.dy);
4807 
4808  if (!arg.rotate) { arg.x += arg.dx; arg.y += arg.dy; arg.dx = arg.dy = 0; }
4809 
4810  // use translate and then rotate to avoid complex sign calculations
4811  var trans = (arg.x || arg.y) ? "translate("+Math.round(arg.x)+","+Math.round(arg.y)+")" : "";
4812  if (arg.rotate) trans += " rotate("+Math.round(arg.rotate)+")";
4813  if (arg.dx || arg.dy) trans += " translate("+Math.round(arg.dx)+","+Math.round(arg.dy)+")";
4814  if (trans) txt.attr("transform", trans);
4815 
4816  if (JSROOT.browser.isWebKit && draw_g.node().insertAdjacentHTML && arg.large_latex) {
4817  // this is workaround for sporadic placement problem in Chrome/Opera
4818  // Due to unclear reasons tspan elements placed wrongly
4819  // Full refresh of created elements (including text itself) solves problem
4820  var html = txt.node().outerHTML;
4821  txt.remove();
4822  draw_g.node().insertAdjacentHTML( 'beforeend', html );
4823  }
4824  });
4825 
4826  if (!call_ready) call_ready = draw_g.node().text_callback;
4827  draw_g.node().text_callback = null;
4828 
4829  draw_g.property('draw_text_completed', true);
4830 
4831  // if specified, call ready function
4832  JSROOT.CallBack(call_ready);
4833 
4834  return draw_g.property('max_text_width');
4835  }
4836 
4842  TObjectPainter.prototype.produceLatex = function(node, label, arg, curr) {
4843 
4844  if (!curr) {
4845  // initial dy = -0.1 is to move complete from very bottom line like with normal text drawing
4846  curr = { lvl: 0, x: 0, y: 0, dx: 0, dy: -0.1, fsize: arg.font_size, parent: null };
4847  arg.mainnode = node.node();
4848  }
4849 
4850  function extend_pos(pos, value) {
4851 
4852  var dx1, dx2, dy1, dy2;
4853 
4854  if (typeof value == 'string') {
4855  if (!pos.rect) pos.rect = { x: pos.x, y: pos.y, height: 0, width: 0 };
4856  dx1 = -pos.x;
4857  pos.x += value.length * arg.font.aver_width * pos.fsize;
4858  dx2 = pos.x;
4859  dy1 = -(pos.y-pos.fsize*1.1);
4860  dy2 = pos.y + pos.fsize*0.1;
4861  } else {
4862  if (!pos.rect) pos.rect = JSROOT.extend({}, value);
4863  dx1 = -value.x;
4864  dx2 = value.x+value.width;
4865  dy1 = -value.y;
4866  dy2 = value.y+value.height;
4867  }
4868 
4869  var rect = pos.rect;
4870 
4871  dx1 += rect.x;
4872  dx2 -= (rect.x+rect.width);
4873  dy1 += rect.y;
4874  dy2 -= (rect.y+rect.height);
4875 
4876  if (dx1>0) { rect.x -= dx1; rect.width += dx1; }
4877  if (dx2>0) rect.width += dx2;
4878  if (dy1>0) { rect.y -= dy1; rect.height += dy1; }
4879  if (dy2>0) rect.height+=dy2;
4880 
4881  if (pos.parent) return extend_pos(pos.parent, rect)
4882 
4883  // calculate dimensions for the
4884  arg.text_rect = rect;
4885 
4886  var h = rect.height, mid = rect.y + rect.height/2;
4887 
4888  if (h>0) {
4889  arg.mid_shift = -mid/h || 0.001; // relative shift to get latex middle at given point
4890  arg.top_shift = -rect.y/h || 0.001; // relative shift to get latex top at given point
4891  }
4892  }
4893 
4894  function makeem(value) {
4895  if (Math.abs(value)<1e-2) return null; // very small values not needed, attribute will be removed
4896  if (value==Math.round(value)) return Math.round(value) + "em";
4897  var res = value.toFixed(2);
4898  if (res.indexOf("0.")==0) res = res.substr(1); else
4899  if (res.indexOf("-0.")==0) res = "-." + res.substr(3);
4900  if (res[res.length-1]=='0') res = res.substr(0, res.length-1);
4901  return res+"em";
4902  }
4903 
4904  function get_boundary(painter, element, approx_rect) {
4905  // actually, it is workaround for getBBox() or getElementBounday,
4906  // which is not implemented for tspan element in Firefox
4907 
4908  if (JSROOT.nodejs || !element || element.empty())
4909  return approx_rect || { height: 0, width: 0 };
4910 
4911  var important = [], prnt = element.node();
4912 
4913  // if (element.node().getBBox && !JSROOT.browser.isFirefox) return element.node().getBBox();
4914 
4915  while (prnt && (prnt!=arg.mainnode)) {
4916  important.push(prnt);
4917  prnt = prnt.parentNode;
4918  }
4919 
4920  element.selectAll('tspan').each(function() { important.push(this) });
4921 
4922  var tspans = d3.select(arg.mainnode).selectAll('tspan');
4923 
4924  // this is just workaround to know that many elements are created and in Chrome we need to redo them once again
4925  if (tspans.size()>3) arg.large_latex = true;
4926 
4927  tspans.each(function() { if (important.indexOf(this)<0) d3.select(this).attr('display', 'none'); });
4928  var box = painter.GetBoundarySizes(arg.mainnode);
4929 
4930  tspans.each(function() { if (important.indexOf(this)<0) d3.select(this).attr('display', null); });
4931 
4932  return box;
4933  }
4934 
4935  var features = [
4936  { name: "#it{" }, // italic
4937  { name: "#bf{" }, // bold
4938  { name: "kern[", arg: 'float' }, // horizontal shift
4939  { name: "lower[", arg: 'float' }, // vertical shift
4940  { name: "scale[", arg: 'float' }, // font scale
4941  { name: "#color[", arg: 'int' },
4942  { name: "#font[", arg: 'int' },
4943  { name: "_{" }, // subscript
4944  { name: "^{" }, // superscript
4945  { name: "#bar{", accent: "\u02C9" }, // "\u0305"
4946  { name: "#hat{", accent: "\u02C6" }, // "\u0302"
4947  { name: "#check{", accent: "\u02C7" }, // "\u030C"
4948  { name: "#acute{", accent: "\u02CA" }, // "\u0301"
4949  { name: "#grave{", accent: "\u02CB" }, // "\u0300"
4950  { name: "#dot{", accent: "\u02D9" }, // "\u0307"
4951  { name: "#ddot{", accent: "\u02BA" }, // "\u0308"
4952  { name: "#tilde{", accent: "\u02DC" }, // "\u0303"
4953  { name: "#slash{", accent: "\u2215" }, // "\u0337"
4954  { name: "#vec{", accent: "\u02ED" }, // "\u0350" arrowhead
4955  { name: "#frac{" },
4956  { name: "#splitline{" },
4957  { name: "#sqrt[", arg: 'int' }, // root with arbitrary power (now only 3 or 4)
4958  { name: "#sqrt{" },
4959  { name: "#sum", special: '\u2211', w: 0.8, h: 0.9 },
4960  { name: "#int", special: '\u222B', w: 0.3, h: 1.0 },
4961  { name: "#left[", right: "#right]", braces: "[]" },
4962  { name: "#left(", right: "#right)", braces: "()" },
4963  { name: "#left{", right: "#right}", braces: "{}" },
4964  { name: "#left|", right: "#right|", braces: "||" },
4965  { name: "#[]{", braces: "[]" },
4966  { name: "#(){", braces: "()" },
4967  { name: "#{}{", braces: "{}" },
4968  { name: "#||{", braces: "||" }
4969  ];
4970 
4971  var isany = false, best, found, foundarg, pos, n, subnode, subnode1, subpos = null, prevsubpos = null;
4972 
4973  while (label) {
4974 
4975  best = label.length; found = null; foundarg = null;
4976 
4977  for(n=0;n<features.length;++n) {
4978  pos = label.indexOf(features[n].name);
4979  if ((pos>=0) && (pos<best)) { best = pos; found = features[n]; }
4980  }
4981 
4982  if (!found && !isany) {
4983  var s = JSROOT.Painter.translateLaTeX(label);
4984  if (!curr.lvl && (s==label)) return 0; // indicate that nothing found - plain string
4985  extend_pos(curr, s);
4986 
4987  if (curr.accent && (s.length==1)) {
4988  var elem = node.append('svg:tspan').text(s),
4989  rect = get_boundary(this, elem, { width : 10000 }),
4990  w = Math.min(rect.width/curr.fsize, 0.5); // at maximum, 0.5 should be used
4991 
4992  node.append('svg:tspan').attr('dx', makeem(curr.dx-w)).attr('dy', makeem(curr.dy-0.2)).text(curr.accent);
4993  curr.dy = 0.2;; // compensate hat
4994  curr.dx = Math.max(0.2, w-0.2); // extra horizontal gap
4995  curr.accent = false;
4996  } else {
4997  node.text(s);
4998  }
4999  return true;
5000  }
5001 
5002  if (best>0) {
5003  var s = JSROOT.Painter.translateLaTeX(label.substr(0,best));
5004  if (s.length>0) {
5005  extend_pos(curr, s);
5006  node.append('svg:tspan')
5007  .attr('dx', makeem(curr.dx))
5008  .attr('dy', makeem(curr.dy))
5009  .text(s);
5010  curr.dx = curr.dy = 0;
5011  }
5012  subpos = null; // indicate that last element is plain
5013  delete curr.special; // and any special handling is also over
5014  delete curr.next_super_dy; // remove potential shift
5015  }
5016 
5017  if (!found) return true;
5018 
5019  // remove preceeding block and tag itself
5020  label = label.substr(best + found.name.length);
5021 
5022  subnode1 = subnode = node.append('svg:tspan');
5023 
5024  prevsubpos = subpos;
5025 
5026  subpos = { lvl: curr.lvl+1, x: curr.x, y: curr.y, fsize: curr.fsize, dx:0, dy: 0, parent: curr };
5027 
5028  isany = true;
5029 
5030  if (found.arg) {
5031  pos = label.indexOf("]{");
5032  if (pos < 0) { console.log('missing argument for ', found.name); return false; }
5033  foundarg = label.substr(0,pos);
5034  if (found.arg == 'int') {
5035  foundarg = parseInt(foundarg);
5036  if (isNaN(foundarg)) { console.log('wrong int argument', label.substr(0,pos)); return false; }
5037  } else if (found.arg == 'float') {
5038  foundarg = parseFloat(foundarg);
5039  if (isNaN(foundarg)) { console.log('wrong float argument', label.substr(0,pos)); return false; }
5040  }
5041  label = label.substr(pos + 2);
5042  }
5043 
5044  var nextdy = curr.dy, nextdx = curr.dx, trav = null,
5045  scale = 1, left_brace = "{", right_brace = "}"; // this will be applied to the next element
5046 
5047  curr.dy = curr.dx = 0; // relative shift for elements
5048 
5049  if (found.special) {
5050  subnode.attr('dx', makeem(nextdx)).attr('dy', makeem(nextdy)).text(found.special);
5051  nextdx = nextdy = 0;
5052  curr.special = found;
5053 
5054  var rect = get_boundary(this, subnode);
5055  if (rect.width && rect.height) {
5056  found.w = rect.width/curr.fsize;
5057  found.h = rect.height/curr.fsize-0.1;
5058  }
5059  continue; // just create special node
5060  }
5061 
5062  if (found.braces) {
5063  // special handling of large braces
5064  subpos.left_cont = subnode.append('svg:tspan'); // container for left brace
5065  subpos.left = subpos.left_cont.append('svg:tspan').text(found.braces[0]);
5066  subnode1 = subnode.append('svg:tspan');
5067  subpos.left_rect = { y: curr.y - curr.fsize*1.1, height: curr.fsize*1.2, x: curr.x, width: curr.fsize*0.6 };
5068  extend_pos(curr, subpos.left_rect);
5069  subpos.braces = found; // indicate braces handling
5070  if (found.right) {
5071  left_brace = found.name;
5072  right_brace = found.right;
5073  }
5074  } else if (found.accent) {
5075  subpos.accent = found.accent;
5076  } else
5077  switch(found.name) {
5078  case "#color[":
5079  if (this.get_color(foundarg))
5080  subnode.attr('fill', this.get_color(foundarg));
5081  break;
5082  case "#kern[": // horizontal shift
5083  nextdx += foundarg;
5084  break;
5085  case "#lower[": // after vertical shift one need to compensate it back
5086  curr.dy -= foundarg;
5087  nextdy += foundarg;
5088  break;
5089  case "scale[":
5090  scale = foundarg;
5091  break;
5092  case "#font[":
5093  JSROOT.Painter.getFontDetails(foundarg).setFont(subnode,'without-size');
5094  break;
5095  case "#it{":
5096  curr.italic = true;
5097  trav = curr;
5098  while (trav = trav.parent)
5099  if (trav.italic!==undefined) {
5100  curr.italic = !trav.italic;
5101  break;
5102  }
5103  subnode.attr('font-style', curr.italic ? 'italic' : 'normal');
5104  break;
5105  case "#bf{":
5106  curr.bold = true;
5107  trav = curr;
5108  while (trav = trav.parent)
5109  if (trav.bold!==undefined) {
5110  curr.bold = !trav.bold;
5111  break;
5112  }
5113  subnode.attr('font-weight', curr.bold ? 'bold' : 'normal');
5114  break;
5115  case "_{":
5116  scale = 0.6;
5117  subpos.script = 'sub';
5118 
5119  if (curr.special) {
5120  curr.dx = curr.special.w;
5121  curr.dy = -0.7;
5122  nextdx -= curr.dx;
5123  nextdy -= curr.dy;
5124  } else {
5125  nextdx += 0.1*scale;
5126  nextdy += 0.4*scale;
5127  subpos.y += 0.4*subpos.fsize;
5128  curr.dy = -0.4*scale; // compensate vertical shift back
5129 
5130  if (prevsubpos && (prevsubpos.script === 'super')) {
5131  var rect = get_boundary(this, prevsubpos.node, prevsubpos.rect);
5132  subpos.width_limit = rect.width;
5133  nextdx -= (rect.width/subpos.fsize+0.1)*scale;
5134  }
5135  }
5136  break;
5137  case "^{":
5138  scale = 0.6;
5139  subpos.script = 'super';
5140 
5141  if (curr.special) {
5142  curr.dx = curr.special.w;
5143  curr.dy = curr.special.h;
5144  nextdx -= curr.dx;
5145  nextdy -= curr.dy;
5146  } else {
5147 
5148  curr.dy = 0.6*scale; // compensate vertical shift afterwards
5149  if (curr.next_super_dy) curr.dy -= curr.next_super_dy;
5150 
5151  nextdx += 0.1*scale;
5152  nextdy -= curr.dy;
5153 
5154  subpos.y -= 0.4*subpos.fsize;
5155 
5156  if (prevsubpos && (prevsubpos.script === 'sub')) {
5157  var rect = get_boundary(this, prevsubpos.node, prevsubpos.rect);
5158  subpos.width_limit = rect.width;
5159  nextdx -= (rect.width/subpos.fsize+0.1)*scale;
5160  }
5161  }
5162  break;
5163  case "#frac{":
5164  case "#splitline{":
5165  subpos.first = subnode;
5166  subpos.two_lines = true;
5167  subpos.need_middle = (found.name == "#frac{");
5168  subpos.x0 = subpos.x;
5169  nextdy -= 0.6;
5170  curr.dy = -0.6;
5171  break;
5172  case "#sqrt{":
5173  foundarg = 2;
5174  case "#sqrt[":
5175  subpos.square_root = subnode.append('svg:tspan');
5176  subpos.square_root.append('svg:tspan').text((foundarg==3) ? '\u221B' : ((foundarg==4) ? '\u221C' : '\u221A')); // unicode square, cubic and fourth root
5177  subnode1 = subnode.append('svg:tspan');
5178  subpos.sqrt_rect = { y: curr.y - curr.fsize*1.1, height: curr.fsize*1.2, x: 0, width: curr.fsize*0.7 };
5179  extend_pos(curr, subpos.sqrt_rect); // just dummy symbol instead of square root
5180  break;
5181  }
5182 
5183  if (scale!==1) {
5184  // handle centrally change of scale factor
5185  subnode.attr('font-size', Math.round(scale*100)+'%');
5186  subpos.fsize *= scale;
5187  nextdx = nextdx/scale;
5188  nextdy = nextdy/scale;
5189  }
5190 
5191  if (curr.special && !subpos.script) delete curr.special;
5192  delete curr.next_super_dy;
5193 
5194  subpos.node = subnode; // remember node where sublement is build
5195 
5196  while (true) {
5197  // loop need to create two lines for #frac or #splitline
5198  // normally only one sub-element is created
5199 
5200  // moving cursor with the tspan
5201  subpos.x += nextdx*subpos.fsize;
5202  subpos.y += nextdy*subpos.fsize;
5203 
5204  subnode.attr('dx', makeem(nextdx)).attr('dy', makeem(nextdy));
5205  nextdx = nextdy = 0;
5206 
5207  pos = -1; n = 1;
5208 
5209  while ((n!=0) && (++pos < label.length)) {
5210  if (label.indexOf(left_brace, pos) === pos) n++; else
5211  if (label.indexOf(right_brace, pos) === pos) n--;
5212  }
5213 
5214  if (n!=0) {
5215  console.log('mismatch with open ' + left_brace + ' and close ' + right_brace + ' braces in Latex', label);
5216  return false;
5217  }
5218 
5219  var sublabel = label.substr(0,pos);
5220 
5221  // if (subpos.square_root) sublabel = "#frac{a}{bc}";
5222 
5223  if (!this.produceLatex(subnode1, sublabel, arg, subpos)) return false;
5224 
5225  // takeover current possition and deltas
5226  curr.x = subpos.x;
5227  curr.y = subpos.y;
5228 
5229  curr.dx += subpos.dx*subpos.fsize/curr.fsize;
5230  curr.dy += subpos.dy*subpos.fsize/curr.fsize;
5231 
5232  label = label.substr(pos+right_brace.length);
5233 
5234  if (subpos.width_limit) {
5235  // special handling for the case when created element does not reach its minimal width
5236  // use when super-script and subscript should be combined together
5237 
5238  var rect = get_boundary(this, subnode1, subpos.rect);
5239  if (rect.width < subpos.width_limit)
5240  curr.dx += (subpos.width_limit-rect.width)/curr.fsize;
5241  delete subpos.width_limit;
5242  }
5243 
5244  if (curr.special) {
5245  // case over #sum or #integral one need to compensate width
5246  var rect = get_boundary(this, subnode1, subpos.rect);
5247  curr.dx -= rect.width/curr.fsize; // compensate width as much as we can
5248  }
5249 
5250  if (subpos.square_root) {
5251  // creating cap for square root
5252  // while overline symbol does not match with square root, use empty text with overline
5253  var len = 2, scale = 1, sqrt_dy = 0, yscale = 1,
5254  bs = get_boundary(this, subpos.square_root, subpos.sqrt_rect),
5255  be = get_boundary(this, subnode1, subpos.rect);
5256 
5257  // we can compare y coordinates while both nodes (root and element) on the same level
5258  if ((be.height > bs.height) && (bs.height > 0)) {
5259  yscale = be.height/bs.height*1.2;
5260  sqrt_dy = ((be.y+be.height) - (bs.y+bs.height))/curr.fsize/yscale;
5261  subpos.square_root.style('font-size', Math.round(100*yscale)+'%').attr('dy', makeem(sqrt_dy));
5262  }
5263 
5264  // we taking into account only element width
5265  len = be.width / subpos.fsize / yscale;
5266 
5267  var a = "", nn = Math.round(Math.max(len*3,2));
5268  while (nn--) a += '\u203E'; // unicode overline
5269 
5270  subpos.square_root.append('svg:tspan').attr("dy", makeem(-0.25)).text(a);
5271 
5272  subpos.square_root.append('svg:tspan').attr("dy", makeem(0.25-sqrt_dy)).attr("dx", makeem(-a.length/3-0.2)).text('\u2009'); // unicode tiny space
5273 
5274  break;
5275  }
5276 
5277  if (subpos.braces) {
5278  // handling braces
5279 
5280  var bs = get_boundary(this, subpos.left_cont, subpos.left_rect),
5281  be = get_boundary(this, subnode1, subpos.rect),
5282  yscale = 1, brace_dy = 0;
5283 
5284  // console.log('braces height', bs.height, ' entry height', be.height);
5285 
5286  if (1.2*bs.height < be.height) {
5287  // make scaling
5288  yscale = be.height/bs.height;
5289  // brace_dy = ((be.y+be.height) - (bs.y+bs.height))/curr.fsize/yscale - 0.15;
5290  brace_dy = 0;
5291  subpos.left.style('font-size', Math.round(100*yscale)+'%').attr('dy', makeem(brace_dy));
5292  // unicode tiny space, used to return cursor on vertical position
5293  subpos.left_cont.append('svg:tspan').attr("dx",makeem(-0.2))
5294  .attr("dy", makeem(-brace_dy*yscale)).text('\u2009');
5295  curr.next_super_dy = -0.3*yscale; // special shift for next comming superscript
5296  }
5297 
5298  subpos.left_rect.y = curr.y;
5299  subpos.left_rect.height *= yscale;
5300 
5301  extend_pos(curr, subpos.left_rect); // just dummy symbol instead of right brace for accounting
5302 
5303  var right_cont = subnode.append('svg:tspan')
5304  .attr("dx", makeem(curr.dx))
5305  .attr("dy", makeem(curr.dy));
5306 
5307  curr.dx = curr.dy = 0;
5308 
5309  if (yscale!=1) right_cont.append('svg:tspan').attr("dx",makeem(-0.2)).text('\u2009'); // unicode tiny space if larger brace is used
5310 
5311  var right = right_cont.append('svg:tspan').text(subpos.braces.braces[1]);
5312 
5313  if (yscale!=1) {
5314  right.style('font-size', Math.round(100*yscale)+'%').attr('dy', makeem(brace_dy));
5315  curr.dy = -brace_dy*yscale; // compensation of right brace
5316  }
5317 
5318  break;
5319  }
5320 
5321  if (subpos.first && subpos.second) {
5322  // when two lines created, adjust horizontal position and place divider if required
5323 
5324  var rect1 = get_boundary(this, subpos.first, subpos.rect1),
5325  rect2 = get_boundary(this, subpos.second, subpos.rect),
5326  l1 = rect1.width / subpos.fsize,
5327  l2 = rect2.width / subpos.fsize,
5328  l3 = Math.max(l2, l1);
5329 
5330  if (subpos.need_middle) {
5331  // starting from content len 1.2 two -- will be inserted
5332  l3 = Math.round(Math.max(l3,1)+0.3);
5333  var a = "";
5334  while (a.length < l3) a += '\u2014';
5335  node.append('svg:tspan')
5336  .attr("dx", makeem(-0.5*(l3+l2)))
5337  .attr("dy", makeem(curr.dy-0.2))
5338  .text(a);
5339  curr.dy = 0.2; // return to the normal level
5340  curr.dx = 0.2; // extra spacing
5341  } else {
5342  curr.dx = 0.2;
5343  if (l2<l1) curr.dx += 0.5*(l1-l2);
5344  }
5345 
5346  if (subpos.need_middle || arg.align[0]=='middle') {
5347  subpos.first.attr("dx", makeem(0.5*(l3-l1)));
5348  subpos.second.attr("dx", makeem(-0.5*(l2+l1)));
5349  } else if (arg.align[0]=='end') {
5350  if (l1<l2) subpos.first.attr("dx", makeem(l2-l1));
5351  subpos.second.attr("dx", makeem(-l2));
5352  } else {
5353  subpos.second.attr("dx", makeem(-l1));
5354  }
5355 
5356  delete subpos.first;
5357  delete subpos.second;
5358  }
5359 
5360  if (!subpos.two_lines) break;
5361 
5362  if (label[0] != '{') {
5363  console.log('missing { for second line', label);
5364  return false;
5365  }
5366 
5367  label = label.substr(1);
5368 
5369  subnode = subnode1 = node.append('svg:tspan');
5370 
5371  subpos.two_lines = false;
5372  subpos.rect1 = subpos.rect; // remember first rect
5373  delete subpos.rect; // reset rectangle calculations
5374  subpos.x = subpos.x0; // it is used only for SVG, make it more realistic
5375  subpos.second = subnode;
5376 
5377  nextdy = curr.dy + 1.6;
5378  curr.dy = -0.4;
5379  subpos.dx = subpos.dy = 0; // reset variable
5380  }
5381 
5382  }
5383 
5384  return true;
5385  }
5386 
5403  TObjectPainter.prototype.DrawText = function(arg) {
5404 
5405  var label = arg.text || "",
5406  align = ['start', 'middle'];
5407 
5408  if (typeof arg.align == 'string') {
5409  align = arg.align.split(";");
5410  if (align.length==1) align.push('middle');
5411  } else if (typeof arg.align == 'number') {
5412  if ((arg.align / 10) >= 3) align[0] = 'end'; else
5413  if ((arg.align / 10) >= 2) align[0] = 'middle';
5414  if ((arg.align % 10) == 0) align[1] = 'bottom'; else
5415  if ((arg.align % 10) == 1) align[1] = 'bottom-base'; else
5416  if ((arg.align % 10) == 3) align[1] = 'top';
5417  }
5418 
5419  arg.draw_g = arg.draw_g || this.draw_g;
5420  if (arg.latex===undefined) arg.latex = 1; // latex 0-text, 1-latex, 2-math
5421  arg.align = align;
5422  arg.x = arg.x || 0;
5423  arg.y = arg.y || 0;
5424  arg.scale = arg.width && arg.height && !arg.font_size;
5425  arg.width = arg.width || 0;
5426  arg.height = arg.height || 0;
5427 
5428  if (arg.draw_g.property("_fast_drawing")) {
5429  if (arg.scale) {
5430  // area too small - ignore such drawing
5431  if (arg.height < 4) return 0;
5432  } else if (arg.font_size) {
5433  // font size too small
5434  if (arg.font_size < 4) return 0;
5435  } else if (arg.draw_g.property("_font_too_small")) {
5436  // configure font is too small - ignore drawing
5437  return 0;
5438  }
5439  }
5440 
5441  if (JSROOT.gStyle.MathJax !== undefined) {
5442  switch (JSROOT.gStyle.MathJax) {
5443  case 0: JSROOT.gStyle.Latex = 2; break;
5444  case 2: JSROOT.gStyle.Latex = 4; break;
5445  default: JSROOT.gStyle.Latex = 3;
5446  }
5447  delete JSROOT.gStyle.MathJax;
5448  }
5449 
5450  if (typeof JSROOT.gStyle.Latex == 'string') {
5451  switch (JSROOT.gStyle.Latex) {
5452  case "off": JSROOT.gStyle.Latex = 0; break;
5453  case "symbols": JSROOT.gStyle.Latex = 1; break;
5454  case "MathJax":
5455  case "mathjax":
5456  case "math": JSROOT.gStyle.Latex = 3; break;
5457  case "AlwaysMathJax":
5458  case "alwaysmath":
5459  case "alwaysmathjax": JSROOT.gStyle.Latex = 4; break;
5460  default:
5461  var code = parseInt(JSROOT.gStyle.Latex);
5462  JSROOT.gStyle.Latex = (!isNaN(code) && (code>=0) && (code<=4)) ? code : 2;
5463  }
5464  }
5465 
5466  var font = arg.draw_g.property('text_font'),
5467  use_mathjax = (arg.latex == 2);
5468 
5469  if (arg.latex === 1)
5470  use_mathjax = (JSROOT.gStyle.Latex > 3) || ((JSROOT.gStyle.Latex == 3) && JSROOT.Painter.isAnyLatex(label));
5471 
5472  // only Firefox can correctly rotate incapsulated SVG, produced by MathJax
5473  // if (!use_normal_text && (h<0) && !JSROOT.browser.isFirefox) use_normal_text = true;
5474 
5475  if (!use_mathjax || arg.nomathjax) {
5476 
5477  var txt = arg.draw_g.append("svg:text");
5478 
5479  if (arg.color) txt.attr("fill", arg.color);
5480 
5481  if (arg.font_size) txt.attr("font-size", arg.font_size);
5482  else arg.font_size = font.size;
5483 
5484  arg.font = font; // use in latex conversion
5485 
5486  arg.plain = !arg.latex || (JSROOT.gStyle.Latex < 2) || (this.produceLatex(txt, label, arg) === 0);
5487 
5488  if (arg.plain) {
5489  if (arg.latex && (JSROOT.gStyle.Latex == 1)) label = Painter.translateLaTeX(label); // replace latex symbols
5490  txt.text(label);
5491  }
5492 
5493  // complete rectangle with very rougth size estimations
5494  arg.box = !JSROOT.nodejs && !JSROOT.gStyle.ApproxTextSize && !arg.fast ? this.GetBoundarySizes(txt.node()) :
5495  (arg.text_rect || { height: arg.font_size*1.2, width: JSROOT.Painter.approxTextWidth(font, label) });
5496 
5497  txt.attr('class','hidden_text')
5498  .attr('visibility','hidden') // hide elements until text drawing is finished
5499  .property("_arg", arg);
5500 
5501  if (arg.box.width > arg.draw_g.property('max_text_width')) arg.draw_g.property('max_text_width', arg.box.width);
5502  if (arg.scale) this.TextScaleFactor(1.05*arg.box.width/arg.width, arg.draw_g);
5503  if (arg.scale) this.TextScaleFactor(1.*arg.box.height/arg.height, arg.draw_g);
5504 
5505  return arg.box.width;
5506  }
5507 
5508  var mtext = JSROOT.Painter.translateMath(label, arg.latex, arg.color, this),
5509  fo_g = arg.draw_g.append("svg:g")
5510  .attr('class', 'math_svg')
5511  .attr('visibility','hidden')
5512  .property('_arg', arg);
5513 
5514  arg.draw_g.property('mathjax_use', true); // one need to know that mathjax is used
5515 
5516  if (JSROOT.nodejs) {
5517  // special handling for Node.js
5518 
5519  if (!JSROOT.nodejs_mathjax) {
5520  JSROOT.nodejs_mathjax = require("mathjax-node");
5521  JSROOT.nodejs_mathjax.config({
5522  TeX: { extensions: ["color.js"] },
5523  SVG: { mtextFontInherit: true, minScaleAdjust: 100, matchFontHeight: true, useFontCache: false }
5524  });
5525  JSROOT.nodejs_mathjax.start();
5526  }
5527 
5528  if ((mtext.indexOf("\\(")==0) && (mtext.lastIndexOf("\\)")==mtext.length-2))
5529  mtext = mtext.substr(2,mtext.length-4);
5530 
5531  JSROOT.nodejs_mathjax.typeset({
5532  jsroot_painter: this,
5533  jsroot_drawg: arg.draw_g,
5534  jsroot_fog: fo_g,
5535  ex: font.size,
5536  math: mtext,
5537  useFontCache: false,
5538  useGlobalCache: false,
5539  format: "TeX", // "TeX", "inline-TeX", "MathML"
5540  svg: true // svg:true,
5541  }, function (data, opt) {
5542  if (!data.errors) {
5543  opt.jsroot_fog.html(data.svg);
5544  } else {
5545  console.log('MathJax error', opt.math);
5546  opt.jsroot_fog.html("<svg></svg>");
5547  }
5548  opt.jsroot_painter.FinishTextDrawing(opt.jsroot_drawg);
5549  });
5550 
5551  return 0;
5552  }
5553 
5554  var element = document.createElement("p");
5555 
5556  d3.select(element).style('visibility',"hidden").style('overflow',"hidden").style('position',"absolute")
5557  .style("font-size",font.size+'px').style("font-family",font.name)
5558  .html('<mtext>' + mtext + '</mtext>');
5559  document.body.appendChild(element);
5560 
5561  fo_g.property('_element', element);
5562 
5563  var painter = this;
5564 
5565  JSROOT.AssertPrerequisites('mathjax', function() {
5566 
5567  MathJax.Hub.Typeset(element, ["FinishMathjax", painter, arg.draw_g, fo_g]);
5568 
5569  MathJax.Hub.Queue(["FinishMathjax", painter, arg.draw_g, fo_g]); // repeat once again, while Typeset not always invoke callback
5570  });
5571 
5572  return 0;
5573  }
5574 
5580  TObjectPainter.prototype.FinishMathjax = function(draw_g, fo_g, id) {
5581 
5582  if (fo_g.node().parentNode !== draw_g.node()) return;
5583 
5584  var entry = fo_g.property('_element');
5585  if (!entry) return;
5586 
5587  var vvv = d3.select(entry).select("svg");
5588 
5589  if (vvv.empty()) {
5590 
5591  var merr = d3.select(entry).select("merror"); // indication of error
5592 
5593  if (merr.empty()) return; // not yet finished
5594 
5595  console.warn('MathJax error', merr.text());
5596 
5597  var arg = fo_g.property('_arg');
5598 
5599  if (arg && arg.latex!=2) {
5600  arg.nomathjax = true;
5601  fo_g.remove(); // delete special entry
5602  this.DrawText(arg);
5603  } else
5604  fo_g.append("svg").attr('width', Math.min(20, merr.text().length + 5) + 'ex')
5605  .attr('height', '3ex')
5606  .style('vertical-align','0ex')
5607  .append("text")
5608  .style('font-size','12px')
5609  .style('fill','red')
5610  .attr('x','0')
5611  .attr('y','2ex')
5612  .text("Err: " + merr.text());
5613  } else {
5614  vvv.remove();
5615  fo_g.append(function() { return vvv.node(); });
5616  }
5617 
5618  fo_g.property('_element', null);
5619  document.body.removeChild(entry);
5620 
5621  this.FinishTextDrawing(draw_g); // check if all other elements are completed
5622  }
5623 
5624  // ===========================================================
5625 
5633  Painter.SelectActivePad = function(args) {
5634  if (args.active) {
5635  if (this.$active_pp && (typeof this.$active_pp.SetActive == 'function'))
5636  this.$active_pp.SetActive(false);
5637 
5638  this.$active_pp = args.pp;
5639 
5640  if (this.$active_pp && (typeof this.$active_pp.SetActive == 'function'))
5641  this.$active_pp.SetActive(true);
5642  } else if (this.$active_pp === args.pp) {
5643  delete this.$active_pp;
5644  }
5645  }
5646 
5651  Painter.GetActivePad = function() {
5652  return this.$active_pp;
5653  }
5654 
5655  // =====================================================================
5656 
5657  function TooltipHandler(obj) {
5658  JSROOT.TObjectPainter.call(this, obj);
5659  this.tooltip_enabled = true; // this is internally used flag to temporary disbale/enable tooltip
5660  }
5661 
5662  TooltipHandler.prototype = Object.create(TObjectPainter.prototype);
5663 
5664  TooltipHandler.prototype.hints_layer = function() {
5665  // return layer where frame tooltips are shown
5666  // only canvas info_layer can be used while other pads can overlay
5667 
5668  var pp = this.canv_painter();
5669  return pp ? pp.svg_layer("info_layer") : d3.select(null);
5670  }
5671 
5672  TooltipHandler.prototype.IsTooltipShown = function() {
5673  // return true if tooltip is shown, use to prevent some other action
5674  if (!this.tooltip_enabled || !this.IsTooltipAllowed()) return false;
5675  var hintsg = this.hints_layer().select(".objects_hints");
5676  return hintsg.empty() ? false : hintsg.property("hints_pad") == this.pad_name;
5677  }
5678 
5679  TooltipHandler.prototype.ProcessTooltipEvent = function(pnt, enabled) {
5680  // make central function which let show selected hints for the object
5681 
5682  if (enabled !== undefined) this.tooltip_enabled = enabled;
5683 
5684  if (pnt && pnt.handler) {
5685  // special use of interactive handler in the frame painter
5686  var rect = this.draw_g ? this.draw_g.select(".main_layer") : null;
5687  if (!rect || rect.empty()) {
5688  pnt = null; // disable
5689  } else if (pnt.touch) {
5690  var pos = d3.touches(rect.node());
5691  pnt = (pos && pos.length == 1) ? { touch: true, x: pos[0][0], y: pos[0][1] } : null;
5692  } else {
5693  var pos = d3.mouse(rect.node());
5694  pnt = { touch: false, x: pos[0], y: pos[1] };
5695  }
5696  }
5697 
5698  var hints = [], nhints = 0, maxlen = 0, lastcolor1 = 0, usecolor1 = false,
5699  textheight = 11, hmargin = 3, wmargin = 3, hstep = 1.2,
5700  frame_rect = this.GetFrameRect(),
5701  pad_width = this.pad_width(),
5702  pp = this.pad_painter(),
5703  font = JSROOT.Painter.getFontDetails(160, textheight),
5704  status_func = this.GetShowStatusFunc(),
5705  disable_tootlips = !this.IsTooltipAllowed() || !this.tooltip_enabled;
5706 
5707  if ((pnt === undefined) || (disable_tootlips && !status_func)) pnt = null;
5708  if (pnt && disable_tootlips) pnt.disabled = true; // indicate that highlighting is not required
5709  if (pnt) pnt.painters = true; // get also painter
5710 
5711  // collect tooltips from pad painter - it has list of all drawn objects
5712  if (pp) hints = pp.GetTooltips(pnt);
5713 
5714  if (pnt && pnt.touch) textheight = 15;
5715 
5716  for (var n = 0; n < hints.length; ++n) {
5717  var hint = hints[n];
5718  if (!hint) continue;
5719 
5720  if (hint.painter && (hint.user_info!==undefined))
5721  if (hint.painter.ProvideUserTooltip(hint.user_info)) {};
5722 
5723  if (!hint.lines || (hint.lines.length===0)) {
5724  hints[n] = null; continue;
5725  }
5726 
5727  // check if fully duplicated hint already exists
5728  for (var k=0;k<n;++k) {
5729  var hprev = hints[k], diff = false;
5730  if (!hprev || (hprev.lines.length !== hint.lines.length)) continue;
5731  for (var l=0;l<hint.lines.length && !diff;++l)
5732  if (hprev.lines[l] !== hint.lines[l]) diff = true;
5733  if (!diff) { hints[n] = null; break; }
5734  }
5735  if (!hints[n]) continue;
5736 
5737  nhints++;
5738 
5739  for (var l=0;l<hint.lines.length;++l)
5740  maxlen = Math.max(maxlen, hint.lines[l].length);
5741 
5742  hint.height = Math.round(hint.lines.length*textheight*hstep + 2*hmargin - textheight*(hstep-1));
5743 
5744  if ((hint.color1!==undefined) && (hint.color1!=='none')) {
5745  if ((lastcolor1!==0) && (lastcolor1 !== hint.color1)) usecolor1 = true;
5746  lastcolor1 = hint.color1;
5747  }
5748  }
5749 
5750  var layer = this.hints_layer(),
5751  hintsg = layer.select(".objects_hints"); // group with all tooltips
5752 
5753  if (status_func) {
5754  var title = "", name = "", info = "",
5755  hint = null, best_dist2 = 1e10, best_hint = null,
5756  coordinates = pnt ? Math.round(pnt.x)+","+Math.round(pnt.y) : "";
5757  // try to select hint with exact match of the position when several hints available
5758  for (var k=0; k < (hints ? hints.length : 0); ++k) {
5759  if (!hints[k]) continue;
5760  if (!hint) hint = hints[k];
5761  if (hints[k].exact && (!hint || !hint.exact)) { hint = hints[k]; break; }
5762 
5763  if (!pnt || (hints[k].x===undefined) || (hints[k].y===undefined)) continue;
5764 
5765  var dist2 = (pnt.x-hints[k].x)*(pnt.x-hints[k].x) + (pnt.y-hints[k].y)*(pnt.y-hints[k].y);
5766  if (dist2<best_dist2) { best_dist2 = dist2; best_hint = hints[k]; }
5767  }
5768 
5769  if ((!hint || !hint.exact) && (best_dist2 < 400)) hint = best_hint;
5770 
5771  if (hint) {
5772  name = (hint.lines && hint.lines.length>1) ? hint.lines[0] : hint.name;
5773  title = hint.title || "";
5774  info = hint.line;
5775  if (!info && hint.lines) info = hint.lines.slice(1).join(' ');
5776  }
5777 
5778  status_func(name, title, info, coordinates);
5779  }
5780 
5781  // end of closing tooltips
5782  if (!pnt || disable_tootlips || (hints.length===0) || (maxlen===0) || (nhints > 15)) {
5783  hintsg.remove();
5784  return;
5785  }
5786 
5787  // we need to set pointer-events=none for all elements while hints
5788  // placed in front of so-called interactive rect in frame, used to catch mouse events
5789 
5790  if (hintsg.empty())
5791  hintsg = layer.append("svg:g")
5792  .attr("class", "objects_hints")
5793  .style("pointer-events","none");
5794 
5795  var frame_shift = { x: 0, y: 0 }, trans = frame_rect.transform || "";
5796  if (!pp.iscan) {
5797  pp.CalcAbsolutePosition(this.svg_pad(), frame_shift);
5798  trans = "translate(" + frame_shift.x + "," + frame_shift.y + ") " + trans;
5799  }
5800 
5801  // copy transform attributes from frame itself
5802  hintsg.attr("transform", trans)
5803  .property("last_point", pnt)
5804  .property("hints_pad", this.pad_name);
5805 
5806  var viewmode = hintsg.property('viewmode') || "",
5807  actualw = 0, posx = pnt.x + frame_rect.hint_delta_x;
5808 
5809  if (nhints > 1) {
5810  // if there are many hints, place them left or right
5811 
5812  var bleft = 0.5, bright = 0.5;
5813 
5814  if (viewmode=="left") bright = 0.7; else
5815  if (viewmode=="right") bleft = 0.3;
5816 
5817  if (posx <= bleft*frame_rect.width) {
5818  viewmode = "left";
5819  posx = 20;
5820  } else if (posx >= bright*frame_rect.width) {
5821  viewmode = "right";
5822  posx = frame_rect.width - 60;
5823  } else {
5824  posx = hintsg.property('startx');
5825  }
5826  } else {
5827  viewmode = "single";
5828  posx += 15;
5829  }
5830 
5831  if (viewmode !== hintsg.property('viewmode')) {
5832  hintsg.property('viewmode', viewmode);
5833  hintsg.selectAll("*").remove();
5834  }
5835 
5836  var curry = 10, // normal y coordinate
5837  gapy = 10, // y coordinate, taking into account all gaps
5838  gapminx = -1111, gapmaxx = -1111,
5839  minhinty = -frame_shift.y,
5840  maxhinty = this.pad_height("") - frame_rect.y - frame_shift.y;
5841 
5842  function FindPosInGap(y) {
5843  for (var n=0;(n<hints.length) && (y < maxhinty); ++n) {
5844  var hint = hints[n];
5845  if (!hint) continue;
5846  if ((hint.y>=y-5) && (hint.y <= y+hint.height+5)) {
5847  y = hint.y+10;
5848  n = -1;
5849  }
5850  }
5851  return y;
5852  }
5853 
5854  for (var n=0; n < hints.length; ++n) {
5855  var hint = hints[n],
5856  group = hintsg.select(".painter_hint_"+n);
5857  if (hint===null) {
5858  group.remove();
5859  continue;
5860  }
5861 
5862  var was_empty = group.empty(), dx = 0, dy = 0;
5863 
5864  if (was_empty)
5865  group = hintsg.append("svg:svg")
5866  .attr("class", "painter_hint_"+n)
5867  .attr('opacity', 0) // use attribute, not style to make animation with d3.transition()
5868  .style('overflow','hidden')
5869  .style("pointer-events","none");
5870 
5871  if (viewmode == "single") {
5872  curry = pnt.touch ? (pnt.y - hint.height - 5) : Math.min(pnt.y + 15, maxhinty - hint.height - 3) + frame_rect.hint_delta_y;
5873  } else {
5874  gapy = FindPosInGap(gapy);
5875  if ((gapminx === -1111) && (gapmaxx === -1111)) gapminx = gapmaxx = hint.x;
5876  gapminx = Math.min(gapminx, hint.x);
5877  gapmaxx = Math.min(gapmaxx, hint.x);
5878  }
5879 
5880  group.attr("x", posx)
5881  .attr("y", curry)
5882  .property("curry", curry)
5883  .property("gapy", gapy);
5884 
5885  curry += hint.height + 5;
5886  gapy += hint.height + 5;
5887 
5888  if (!was_empty)
5889  group.selectAll("*").remove();
5890 
5891  group.attr("width", 60)
5892  .attr("height", hint.height);
5893 
5894  var r = group.append("rect")
5895  .attr("x",0)
5896  .attr("y",0)
5897  .attr("width", 60)
5898  .attr("height", hint.height)
5899  .attr("fill","lightgrey")
5900  .style("pointer-events","none");
5901 
5902  if (nhints > 1) {
5903  var col = usecolor1 ? hint.color1 : hint.color2;
5904  if ((col !== undefined) && (col!=='none'))
5905  r.attr("stroke", col).attr("stroke-width", hint.exact ? 3 : 1);
5906  }
5907 
5908  for (var l=0; l < (hint.lines ? hint.lines.length : 0); l++)
5909  if (hint.lines[l]!==null) {
5910  var txt = group.append("svg:text")
5911  .attr("text-anchor", "start")
5912  .attr("x", wmargin)
5913  .attr("y", hmargin + l*textheight*hstep)
5914  .attr("dy", ".8em")
5915  .attr("fill","black")
5916  .style("pointer-events","none")
5917  .call(font.func)
5918  .text(hint.lines[l]);
5919 
5920  var box = this.GetBoundarySizes(txt.node());
5921 
5922  actualw = Math.max(actualw, box.width);
5923  }
5924 
5925  function translateFn() {
5926  // We only use 'd', but list d,i,a as params just to show can have them as params.
5927  // Code only really uses d and t.
5928  return function(d, i, a) {
5929  return function(t) {
5930  return t < 0.8 ? "0" : (t-0.8)*5;
5931  };
5932  };
5933  }
5934 
5935  if (was_empty)
5936  if (JSROOT.gStyle.TooltipAnimation > 0)
5937  group.transition().duration(JSROOT.gStyle.TooltipAnimation).attrTween("opacity", translateFn());
5938  else
5939  group.attr('opacity',1);
5940  }
5941 
5942  actualw += 2*wmargin;
5943 
5944  var svgs = hintsg.selectAll("svg");
5945 
5946  if ((viewmode == "right") && (posx + actualw > frame_rect.width - 20)) {
5947  posx = frame_rect.width - actualw - 20;
5948  svgs.attr("x", posx);
5949  }
5950 
5951  if ((viewmode == "single") && (posx + actualw > pad_width - frame_rect.x) && (posx > actualw+20)) {
5952  posx -= (actualw + 20);
5953  svgs.attr("x", posx);
5954  }
5955 
5956  // if gap not very big, apply gapy coordinate to open view on the histogram
5957  if ((viewmode !== "single") && (gapy < maxhinty) && (gapy !== curry)) {
5958  if ((gapminx <= posx+actualw+5) && (gapmaxx >= posx-5))
5959  svgs.attr("y", function() { return d3.select(this).property('gapy'); });
5960  } else if ((viewmode !== 'single') && (curry > maxhinty)) {
5961  var shift = Math.max((maxhinty - curry - 10), minhinty);
5962  if (shift<0)
5963  svgs.attr("y", function() { return d3.select(this).property('curry') + shift; });
5964  }
5965 
5966  if (actualw > 10)
5967  svgs.attr("width",actualw)
5968  .select('rect').attr("width", actualw);
5969 
5970  hintsg.property('startx', posx);
5971  }
5972 
5973  JSROOT.TCanvasStatusBits = {
5974  kShowEventStatus : JSROOT.BIT(15),
5975  kAutoExec : JSROOT.BIT(16),
5976  kMenuBar : JSROOT.BIT(17),
5977  kShowToolBar : JSROOT.BIT(18),
5978  kShowEditor : JSROOT.BIT(19),
5979  kMoveOpaque : JSROOT.BIT(20),
5980  kResizeOpaque : JSROOT.BIT(21),
5981  kIsGrayscale : JSROOT.BIT(22),
5982  kShowToolTips : JSROOT.BIT(23)
5983  };
5984 
5985  JSROOT.EAxisBits = {
5986  kTickPlus : JSROOT.BIT(9),
5987  kTickMinus : JSROOT.BIT(10),
5988  kAxisRange : JSROOT.BIT(11),
5989  kCenterTitle : JSROOT.BIT(12),
5990  kCenterLabels : JSROOT.BIT(14),
5991  kRotateTitle : JSROOT.BIT(15),
5992  kPalette : JSROOT.BIT(16),
5993  kNoExponent : JSROOT.BIT(17),
5994  kLabelsHori : JSROOT.BIT(18),
5995  kLabelsVert : JSROOT.BIT(19),
5996  kLabelsDown : JSROOT.BIT(20),
5997  kLabelsUp : JSROOT.BIT(21),
5998  kIsInteger : JSROOT.BIT(22),
5999  kMoreLogLabels : JSROOT.BIT(23),
6000  kDecimals : JSROOT.BIT(11)
6001  };
6002 
6003  // ================= painter of raw text ========================================
6004 
6005 
6006  Painter.drawRawText = function(divid, txt, opt) {
6007 
6008  var painter = new TBasePainter();
6009  painter.txt = txt;
6010  painter.SetDivId(divid);
6011 
6012  painter.RedrawObject = function(obj) {
6013  this.txt = obj;
6014  this.Draw();
6015  return true;
6016  }
6017 
6018  painter.Draw = function() {
6019  var txt = (this.txt._typename && (this.txt._typename == "TObjString")) ? this.txt.fString : this.txt.value;
6020  if (typeof txt != 'string') txt = "<undefined>";
6021 
6022  var mathjax = this.txt.mathjax || (JSROOT.gStyle.Latex == 4);
6023 
6024  if (!mathjax && !('as_is' in this.txt)) {
6025  var arr = txt.split("\n"); txt = "";
6026  for (var i = 0; i < arr.length; ++i)
6027  txt += "<pre style='margin:0'>" + arr[i] + "</pre>";
6028  }
6029 
6030  var frame = this.select_main(),
6031  main = frame.select("div");
6032  if (main.empty())
6033  main = frame.append("div").style('max-width','100%').style('max-height','100%').style('overflow','auto');
6034  main.html(txt);
6035 
6036  // (re) set painter to first child element
6037  this.SetDivId(this.divid);
6038 
6039  if (mathjax)
6040  JSROOT.AssertPrerequisites('mathjax', function() {
6041  MathJax.Hub.Typeset(frame.node());
6042  });
6043  }
6044 
6045  painter.Draw();
6046  return painter.DrawingReady();
6047  }
6048 
6058  JSROOT.RegisterForResize = function(handle, delay) {
6059 
6060  if (!handle) return;
6061 
6062  var myInterval = null, myDelay = delay ? delay : 300;
6063 
6064  if (myDelay < 20) myDelay = 20;
6065 
6066  function ResizeTimer() {
6067  myInterval = null;
6068 
6069  document.body.style.cursor = 'wait';
6070  if (typeof handle == 'function') handle(); else
6071  if ((typeof handle == 'object') && (typeof handle.CheckResize == 'function')) handle.CheckResize(); else
6072  if (typeof handle == 'string') {
6073  var node = d3.select('#'+handle);
6074  if (!node.empty()) {
6075  var mdi = node.property('mdi');
6076  if (mdi) {
6077  mdi.CheckMDIResize();
6078  } else {
6079  JSROOT.resize(node.node());
6080  }
6081  }
6082  }
6083  document.body.style.cursor = 'auto';
6084  }
6085 
6086  function ProcessResize() {
6087  if (myInterval !== null) clearTimeout(myInterval);
6088  myInterval = setTimeout(ResizeTimer, myDelay);
6089  }
6090 
6091  window.addEventListener('resize', ProcessResize);
6092  }
6093 
6094  JSROOT.addDrawFunc({ name: "TCanvas", icon: "img_canvas", prereq: "v6", func: "JSROOT.Painter.drawCanvas", opt: ";grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz", expand_item: "fPrimitives" });
6095  JSROOT.addDrawFunc({ name: "TPad", icon: "img_canvas", prereq: "v6", func: "JSROOT.Painter.drawPad", opt: ";grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz", expand_item: "fPrimitives" });
6096  JSROOT.addDrawFunc({ name: "TSlider", icon: "img_canvas", prereq: "v6", func: "JSROOT.Painter.drawPad" });
6097  JSROOT.addDrawFunc({ name: "TFrame", icon: "img_frame", prereq: "v6", func: "JSROOT.Painter.drawFrame" });
6098  JSROOT.addDrawFunc({ name: "TPave", icon: "img_pavetext", prereq: "v6;hist", func: "JSROOT.Painter.drawPave" });
6099  JSROOT.addDrawFunc({ name: "TPaveText", icon: "img_pavetext", prereq: "v6;hist", func: "JSROOT.Painter.drawPave" });
6100  JSROOT.addDrawFunc({ name: "TPavesText", icon: "img_pavetext", prereq: "v6;hist", func: "JSROOT.Painter.drawPave" });
6101  JSROOT.addDrawFunc({ name: "TPaveStats", icon: "img_pavetext", prereq: "v6;hist", func: "JSROOT.Painter.drawPave" });
6102  JSROOT.addDrawFunc({ name: "TPaveLabel", icon: "img_pavelabel", prereq: "v6;hist", func: "JSROOT.Painter.drawPave" });
6103  JSROOT.addDrawFunc({ name: "TDiamond", icon: "img_pavelabel", prereq: "v6;hist", func: "JSROOT.Painter.drawPave" });
6104  JSROOT.addDrawFunc({ name: "TLatex", icon: "img_text", prereq: "more2d", func: "JSROOT.Painter.drawText", direct: true });
6105  JSROOT.addDrawFunc({ name: "TMathText", icon: "img_text", prereq: "more2d", func: "JSROOT.Painter.drawText", direct: true });
6106  JSROOT.addDrawFunc({ name: "TText", icon: "img_text", prereq: "more2d", func: "JSROOT.Painter.drawText", direct: true });
6107  JSROOT.addDrawFunc({ name: /^TH1/, icon: "img_histo1d", prereq: "v6;hist", func: "JSROOT.Painter.drawHistogram1D", opt:";hist;P;P0;E;E1;E2;E3;E4;E1X0;L;LF2;B;B1;A;TEXT;LEGO;same", ctrl: "l" });
6108  JSROOT.addDrawFunc({ name: "TProfile", icon: "img_profile", prereq: "v6;hist", func: "JSROOT.Painter.drawHistogram1D", opt:";E0;E1;E2;p;AH;hist"});
6109  JSROOT.addDrawFunc({ name: "TH2Poly", icon: "img_histo2d", prereq: "v6;hist", func: "JSROOT.Painter.drawHistogram2D", opt:";COL;COL0;COLZ;LCOL;LCOL0;LCOLZ;LEGO;TEXT;same", expand_item: "fBins", theonly: true });
6110  JSROOT.addDrawFunc({ name: "TProfile2Poly", sameas: "TH2Poly" });
6111  JSROOT.addDrawFunc({ name: "TH2PolyBin", icon: "img_histo2d", draw_field: "fPoly" });
6112  JSROOT.addDrawFunc({ name: /^TH2/, icon: "img_histo2d", prereq: "v6;hist", func: "JSROOT.Painter.drawHistogram2D", opt:";COL;COLZ;COL0;COL1;COL0Z;COL1Z;COLA;BOX;BOX1;PROJ;PROJX1;PROJX2;PROJX3;PROJY1;PROJY2;PROJY3;SCAT;TEXT;TEXTE;TEXTE0;CONT;CONT1;CONT2;CONT3;CONT4;ARR;SURF;SURF1;SURF2;SURF4;SURF6;E;A;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same", ctrl: "colz" });
6113  JSROOT.addDrawFunc({ name: "TProfile2D", sameas: "TH2" });
6114  JSROOT.addDrawFunc({ name: /^TH3/, icon: 'img_histo3d', prereq: "v6;hist3d", func: "JSROOT.Painter.drawHistogram3D", opt:";SCAT;BOX;BOX2;BOX3;GLBOX1;GLBOX2;GLCOL" });
6115  JSROOT.addDrawFunc({ name: "THStack", icon: "img_histo1d", prereq: "v6;hist", func: "JSROOT.Painter.drawHStack", expand_item: "fHists", opt: "NOSTACK;HIST;E;PFC;PLC" });
6116  JSROOT.addDrawFunc({ name: "TPolyMarker3D", icon: 'img_histo3d', prereq: "v6;hist3d", func: "JSROOT.Painter.drawPolyMarker3D" });
6117  JSROOT.addDrawFunc({ name: "TPolyLine3D", icon: 'img_graph', prereq: "3d", func: "JSROOT.Painter.drawPolyLine3D", direct: true });
6118  JSROOT.addDrawFunc({ name: "TGraphStruct" });
6119  JSROOT.addDrawFunc({ name: "TGraphNode" });
6120  JSROOT.addDrawFunc({ name: "TGraphEdge" });
6121  JSROOT.addDrawFunc({ name: "TGraphTime", icon:"img_graph", prereq: "more2d", func: "JSROOT.Painter.drawGraphTime", opt: "once;repeat;first", theonly: true });
6122  JSROOT.addDrawFunc({ name: "TGraph2D", icon:"img_graph", prereq: "v6;hist3d", func: "JSROOT.Painter.drawGraph2D", opt: ";P;PCOL", theonly: true });
6123  JSROOT.addDrawFunc({ name: "TGraph2DErrors", icon:"img_graph", prereq: "v6;hist3d", func: "JSROOT.Painter.drawGraph2D", opt: ";P;PCOL;ERR", theonly: true });
6124  JSROOT.addDrawFunc({ name: "TGraphPolargram", icon:"img_graph", prereq: "more2d", func: "JSROOT.Painter.drawGraphPolargram", theonly: true });
6125  JSROOT.addDrawFunc({ name: "TGraphPolar", icon:"img_graph", prereq: "more2d", func: "JSROOT.Painter.drawGraphPolar", opt: ";F;L;P;PE", theonly: true });
6126  JSROOT.addDrawFunc({ name: /^TGraph/, icon:"img_graph", prereq: "more2d", func: "JSROOT.Painter.drawGraph", opt: ";L;P" });
6127  JSROOT.addDrawFunc({ name: "TEfficiency", icon:"img_graph", prereq: "more2d", func: "JSROOT.Painter.drawEfficiency", opt: ";AP" });
6128  JSROOT.addDrawFunc({ name: "TCutG", sameas: "TGraph" });
6129  JSROOT.addDrawFunc({ name: /^RooHist/, sameas: "TGraph" });
6130  JSROOT.addDrawFunc({ name: /^RooCurve/, sameas: "TGraph" });
6131  JSROOT.addDrawFunc({ name: "RooPlot", icon: "img_canvas", prereq: "more2d", func: "JSROOT.Painter.drawRooPlot" });
6132  JSROOT.addDrawFunc({ name: "TMultiGraph", icon: "img_mgraph", prereq: "more2d", func: "JSROOT.Painter.drawMultiGraph", expand_item: "fGraphs" });
6133  JSROOT.addDrawFunc({ name: "TStreamerInfoList", icon: 'img_question', prereq: "hierarchy", func: "JSROOT.Painter.drawStreamerInfo" });
6134  JSROOT.addDrawFunc({ name: "TPaletteAxis", icon: "img_colz", prereq: "v6;hist", func: "JSROOT.Painter.drawPave" });
6135  JSROOT.addDrawFunc({ name: "TWebPainting", icon: "img_graph", prereq: "more2d", func: "JSROOT.Painter.drawWebPainting" });
6136  JSROOT.addDrawFunc({ name: "TCanvasWebSnapshot", icon: "img_canvas", prereq: "v6", func: "JSROOT.Painter.drawPadSnapshot" });
6137  JSROOT.addDrawFunc({ name: "TPadWebSnapshot", sameas: "TCanvasWebSnapshot" });
6138  JSROOT.addDrawFunc({ name: "kind:Text", icon: "img_text", func: JSROOT.Painter.drawRawText });
6139  JSROOT.addDrawFunc({ name: "TObjString", icon: "img_text", func: JSROOT.Painter.drawRawText });
6140  JSROOT.addDrawFunc({ name: "TF1", icon: "img_tf1", prereq: "math;more2d", func: "JSROOT.Painter.drawFunction" });
6141  JSROOT.addDrawFunc({ name: "TF2", icon: "img_tf2", prereq: "math;hist", func: "JSROOT.Painter.drawTF2" });
6142  JSROOT.addDrawFunc({ name: "TSpline3", icon: "img_tf1", prereq: "more2d", func: "JSROOT.Painter.drawSpline" });
6143  JSROOT.addDrawFunc({ name: "TSpline5", icon: "img_tf1", prereq: "more2d", func: "JSROOT.Painter.drawSpline" });
6144  JSROOT.addDrawFunc({ name: "TEllipse", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawEllipse", direct: true });
6145  JSROOT.addDrawFunc({ name: "TArc", sameas: 'TEllipse' });
6146  JSROOT.addDrawFunc({ name: "TCrown", sameas: 'TEllipse' });
6147  JSROOT.addDrawFunc({ name: "TPie", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawPie", direct: true });
6148  JSROOT.addDrawFunc({ name: "TLine", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawLine", direct: true });
6149  JSROOT.addDrawFunc({ name: "TArrow", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawArrow", direct: true });
6150  JSROOT.addDrawFunc({ name: "TPolyLine", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawPolyLine", direct: true });
6151  JSROOT.addDrawFunc({ name: "TCurlyLine", sameas: 'TPolyLine' });
6152  JSROOT.addDrawFunc({ name: "TCurlyArc", sameas: 'TPolyLine' });
6153  JSROOT.addDrawFunc({ name: "TGaxis", icon: "img_graph", prereq: "v6", func: "JSROOT.Painter.drawGaxis" });
6154  JSROOT.addDrawFunc({ name: "TLegend", icon: "img_pavelabel", prereq: "v6;hist", func: "JSROOT.Painter.drawPave" });
6155  JSROOT.addDrawFunc({ name: "TBox", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawBox", direct: true });
6156  JSROOT.addDrawFunc({ name: "TWbox", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawBox", direct: true });
6157  JSROOT.addDrawFunc({ name: "TSliderBox", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawBox", direct: true });
6158  JSROOT.addDrawFunc({ name: "TAxis3D", prereq: "v6;hist3d", func: "JSROOT.Painter.drawAxis3D" });
6159  JSROOT.addDrawFunc({ name: "TMarker", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawMarker", direct: true });
6160  JSROOT.addDrawFunc({ name: "TPolyMarker", icon: 'img_graph', prereq: "more2d", func: "JSROOT.Painter.drawPolyMarker", direct: true });
6161  JSROOT.addDrawFunc({ name: "TASImage", icon: 'img_mgraph', prereq: "more2d", func: "JSROOT.Painter.drawASImage" });
6162  JSROOT.addDrawFunc({ name: "TJSImage", icon: 'img_mgraph', prereq: "more2d", func: "JSROOT.Painter.drawJSImage", opt: ";scale;center" });
6163  JSROOT.addDrawFunc({ name: "TGeoVolume", icon: 'img_histo3d', prereq: "geom", func: "JSROOT.Painter.drawGeoObject", expand: "JSROOT.GEO.expandObject", opt:";more;all;count;projx;projz;wire;dflt", ctrl: "dflt" });
6164  JSROOT.addDrawFunc({ name: "TEveGeoShapeExtract", icon: 'img_histo3d', prereq: "geom", func: "JSROOT.Painter.drawGeoObject", expand: "JSROOT.GEO.expandObject", opt: ";more;all;count;projx;projz;wire;dflt", ctrl: "dflt" });
6165  JSROOT.addDrawFunc({ name: "ROOT::Experimental::TEveGeoShapeExtract", icon: 'img_histo3d', prereq: "geom", func: "JSROOT.Painter.drawGeoObject", expand: "JSROOT.GEO.expandObject", opt: ";more;all;count;projx;projz;wire;dflt", ctrl: "dflt" });
6166  JSROOT.addDrawFunc({ name: "TGeoOverlap", icon: 'img_histo3d', prereq: "geom", expand: "JSROOT.GEO.expandObject", func: "JSROOT.Painter.drawGeoObject", opt: ";more;all;count;projx;projz;wire;dflt", dflt: "dflt", ctrl: "expand" });
6167  JSROOT.addDrawFunc({ name: "TGeoManager", icon: 'img_histo3d', prereq: "geom", expand: "JSROOT.GEO.expandObject", func: "JSROOT.Painter.drawGeoObject", opt: ";more;all;count;projx;projz;wire;tracks;dflt", dflt: "expand", ctrl: "dflt" });
6168  JSROOT.addDrawFunc({ name: /^TGeo/, icon: 'img_histo3d', prereq: "geom", func: "JSROOT.Painter.drawGeoObject", opt: ";more;all;axis;compa;count;projx;projz;wire;dflt", ctrl: "dflt" });
6169  // these are not draw functions, but provide extra info about correspondent classes
6170  JSROOT.addDrawFunc({ name: "kind:Command", icon: "img_execute", execute: true });
6171  JSROOT.addDrawFunc({ name: "TFolder", icon: "img_folder", icon2: "img_folderopen", noinspect: true, prereq: "hierarchy", expand: "JSROOT.Painter.FolderHierarchy" });
6172  JSROOT.addDrawFunc({ name: "TTask", icon: "img_task", prereq: "hierarchy", expand: "JSROOT.Painter.TaskHierarchy", for_derived: true });
6173  JSROOT.addDrawFunc({ name: "TTree", icon: "img_tree", prereq: "tree;more2d", expand: 'JSROOT.Painter.TreeHierarchy', func: 'JSROOT.Painter.drawTree', dflt: "expand", opt: "player;testio", shift: "inspect" });
6174  JSROOT.addDrawFunc({ name: "TNtuple", icon: "img_tree", prereq: "tree;more2d", expand: 'JSROOT.Painter.TreeHierarchy', func: 'JSROOT.Painter.drawTree', dflt: "expand", opt: "player;testio", shift: "inspect" });
6175  JSROOT.addDrawFunc({ name: "TNtupleD", icon: "img_tree", prereq: "tree;more2d", expand: 'JSROOT.Painter.TreeHierarchy', func: 'JSROOT.Painter.drawTree', dflt: "expand", opt: "player;testio", shift: "inspect" });
6176  JSROOT.addDrawFunc({ name: "TBranchFunc", icon: "img_leaf_method", prereq: "tree;more2d", func: 'JSROOT.Painter.drawTree', opt: ";dump", noinspect: true });
6177  JSROOT.addDrawFunc({ name: /^TBranch/, icon: "img_branch", prereq: "tree;more2d", func: 'JSROOT.Painter.drawTree', dflt: "expand", opt: ";dump", ctrl: "dump", shift: "inspect", ignore_online: true });
6178  JSROOT.addDrawFunc({ name: /^TLeaf/, icon: "img_leaf", prereq: "tree;more2d", noexpand: true, func: 'JSROOT.Painter.drawTree', opt: ";dump", ctrl: "dump", ignore_online: true });
6179  JSROOT.addDrawFunc({ name: "TList", icon: "img_list", prereq: "hierarchy", func: "JSROOT.Painter.drawList", expand: "JSROOT.Painter.ListHierarchy", dflt: "expand" });
6180  JSROOT.addDrawFunc({ name: "THashList", sameas: "TList" });
6181  JSROOT.addDrawFunc({ name: "TObjArray", sameas: "TList" });
6182  JSROOT.addDrawFunc({ name: "TClonesArray", sameas: "TList" });
6183  JSROOT.addDrawFunc({ name: "TMap", sameas: "TList" });
6184  JSROOT.addDrawFunc({ name: "TColor", icon: "img_color" });
6185  JSROOT.addDrawFunc({ name: "TFile", icon: "img_file", noinspect:true });
6186  JSROOT.addDrawFunc({ name: "TMemFile", icon: "img_file", noinspect:true });
6187  JSROOT.addDrawFunc({ name: "TStyle", icon: "img_question", noexpand:true });
6188  JSROOT.addDrawFunc({ name: "Session", icon: "img_globe" });
6189  JSROOT.addDrawFunc({ name: "kind:TopFolder", icon: "img_base" });
6190  JSROOT.addDrawFunc({ name: "kind:Folder", icon: "img_folder", icon2: "img_folderopen", noinspect:true });
6191 
6192  JSROOT.addDrawFunc({ name: "ROOT::Experimental::TCanvas", icon: "img_canvas", prereq: "v7", func: "JSROOT.v7.drawCanvas", opt: "", expand_item: "fPrimitives" });
6193 
6194 
6195  JSROOT.getDrawHandle = function(kind, selector) {
6196  // return draw handle for specified item kind
6197  // kind could be ROOT.TH1I for ROOT classes or just
6198  // kind string like "Command" or "Text"
6199  // selector can be used to search for draw handle with specified option (string)
6200  // or just sequence id
6201 
6202  if (typeof kind != 'string') return null;
6203  if (selector === "") selector = null;
6204 
6205  var first = null;
6206 
6207  if ((selector === null) && (kind in JSROOT.DrawFuncs.cache))
6208  return JSROOT.DrawFuncs.cache[kind];
6209 
6210  var search = (kind.indexOf("ROOT.")==0) ? kind.substr(5) : "kind:"+kind, counter = 0;
6211  for (var i=0; i < JSROOT.DrawFuncs.lst.length; ++i) {
6212  var h = JSROOT.DrawFuncs.lst[i];
6213  if (typeof h.name == "string") {
6214  if (h.name != search) continue;
6215  } else {
6216  if (!search.match(h.name)) continue;
6217  }
6218 
6219  if (h.sameas !== undefined)
6220  return JSROOT.getDrawHandle("ROOT."+h.sameas, selector);
6221 
6222  if ((selector === null) || (selector === undefined)) {
6223  // store found handle in cache, can reuse later
6224  if (!(kind in JSROOT.DrawFuncs.cache)) JSROOT.DrawFuncs.cache[kind] = h;
6225  return h;
6226  } else if (typeof selector == 'string') {
6227  if (!first) first = h;
6228  // if drawoption specified, check it present in the list
6229 
6230  if (selector == "::expand") {
6231  if (('expand' in h) || ('expand_item' in h)) return h;
6232  } else
6233  if ('opt' in h) {
6234  var opts = h.opt.split(';');
6235  for (var j=0; j < opts.length; ++j) opts[j] = opts[j].toLowerCase();
6236  if (opts.indexOf(selector.toLowerCase())>=0) return h;
6237  }
6238  } else if (selector === counter) {
6239  return h;
6240  }
6241  ++counter;
6242  }
6243 
6244  return first;
6245  }
6246 
6250  JSROOT.addStreamerInfos = function(lst) {
6251  if (!lst) return;
6252 
6253  function CheckBaseClasses(si, lvl) {
6254  if (si.fElements == null) return null;
6255  if (lvl>10) return null; // protect against recursion
6256 
6257  for (var j=0; j<si.fElements.arr.length; ++j) {
6258  // extract streamer info for each class member
6259  var element = si.fElements.arr[j];
6260  if (element.fTypeName !== 'BASE') continue;
6261 
6262  var handle = JSROOT.getDrawHandle("ROOT." + element.fName);
6263  if (handle && !handle.for_derived) handle = null;
6264 
6265  // now try find that base class of base in the list
6266  if (handle === null)
6267  for (var k=0;k<lst.arr.length; ++k)
6268  if (lst.arr[k].fName === element.fName) {
6269  handle = CheckBaseClasses(lst.arr[k], lvl+1);
6270  break;
6271  }
6272 
6273  if (handle && handle.for_derived) return handle;
6274  }
6275  return null;
6276  }
6277 
6278  for (var n=0;n<lst.arr.length;++n) {
6279  var si = lst.arr[n];
6280  if (JSROOT.getDrawHandle("ROOT." + si.fName) !== null) continue;
6281 
6282  var handle = CheckBaseClasses(si, 0);
6283 
6284  if (!handle) continue;
6285 
6286  var newhandle = JSROOT.extend({}, handle);
6287  // delete newhandle.for_derived; // should we disable?
6288  newhandle.name = si.fName;
6289  JSROOT.DrawFuncs.lst.push(newhandle);
6290  }
6291  }
6292 
6296  JSROOT.getDrawSettings = function(kind, selector) {
6297  var res = { opts: null, inspect: false, expand: false, draw: false, handle: null };
6298  if (typeof kind != 'string') return res;
6299  var allopts = null, isany = false, noinspect = false, canexpand = false;
6300  if (typeof selector !== 'string') selector = "";
6301 
6302  for (var cnt=0;cnt<1000;++cnt) {
6303  var h = JSROOT.getDrawHandle(kind, cnt);
6304  if (!h) break;
6305  if (!res.handle) res.handle = h;
6306  if (h.noinspect) noinspect = true;
6307  if (h.expand || h.expand_item || h.can_expand) canexpand = true;
6308  if (!('func' in h)) break;
6309  isany = true;
6310  if (! ('opt' in h)) continue;
6311  var opts = h.opt.split(';');
6312  for (var i = 0; i < opts.length; ++i) {
6313  opts[i] = opts[i].toLowerCase();
6314  if ((selector.indexOf('nosame')>=0) && (opts[i].indexOf('same')==0)) continue;
6315 
6316  if (res.opts===null) res.opts = [];
6317  if (res.opts.indexOf(opts[i])<0) res.opts.push(opts[i]);
6318  }
6319  if (h.theonly) break;
6320  }
6321 
6322  if (selector.indexOf('noinspect')>=0) noinspect = true;
6323 
6324  if (isany && (res.opts===null)) res.opts = [""];
6325 
6326  // if no any handle found, let inspect ROOT-based objects
6327  if (!isany && (kind.indexOf("ROOT.")==0) && !noinspect) res.opts = [];
6328 
6329  if (!noinspect && res.opts)
6330  res.opts.push("inspect");
6331 
6332  res.inspect = !noinspect;
6333  res.expand = canexpand;
6334  res.draw = res.opts && (res.opts.length>0);
6335 
6336  return res;
6337  }
6338 
6341  JSROOT.getDrawOptions = function(kind, selector) {
6342  return JSROOT.getDrawSettings(kind).opts;
6343  }
6344 
6347  JSROOT.canDraw = function(classname) {
6348  return JSROOT.getDrawSettings("ROOT." + classname).opts !== null;
6349  }
6350 
6371  JSROOT.draw = function(divid, obj, opt, drawcallback) {
6372 
6373  var isdirectdraw = true; // indicates if extra callbacks (via AssertPrerequisites) was invoked to process
6374 
6375  function completeDraw(painter) {
6376  var callbackfunc = null, ishandle = false;
6377  if (typeof drawcallback == 'function') callbackfunc = drawcallback; else
6378  if (drawcallback && (typeof drawcallback == 'object') && (typeof drawcallback.func=='function')) {
6379  callbackfunc = drawcallback.func;
6380  ishandle = true;
6381  }
6382 
6383  if (ishandle && isdirectdraw) {
6384  // if there is no painter or drawing is already completed, return directly
6385  if (!painter || painter._ready_called_) { drawcallback.completed = true; return painter; }
6386  }
6387 
6388  if (painter && drawcallback && (typeof painter.WhenReady == 'function'))
6389  painter.WhenReady(callbackfunc);
6390  else
6391  JSROOT.CallBack(callbackfunc, painter);
6392  return painter;
6393  }
6394 
6395  if ((obj === null) || (typeof obj !== 'object')) return completeDraw(null);
6396 
6397  if (opt == 'inspect') {
6398  var func = JSROOT.findFunction("JSROOT.Painter.drawInspector");
6399  if (func) return completeDraw(func(divid, obj));
6400  JSROOT.AssertPrerequisites("hierarchy", function() {
6401  completeDraw(JSROOT.Painter.drawInspector(divid, obj));
6402  });
6403  return null;
6404  }
6405 
6406  var handle = null, painter = null;
6407  if ('_typename' in obj) handle = JSROOT.getDrawHandle("ROOT." + obj._typename, opt);
6408  else if ('_kind' in obj) handle = JSROOT.getDrawHandle(obj._kind, opt);
6409 
6410  if (!handle) return completeDraw(null);
6411 
6412  if (handle.draw_field && obj[handle.draw_field])
6413  return JSROOT.draw(divid, obj[handle.draw_field], opt, drawcallback);
6414 
6415  if (!handle.func) {
6416  if (opt && (opt.indexOf("same")>=0)) {
6417  var main_painter = JSROOT.GetMainPainter(divid);
6418  if (main_painter && (typeof main_painter.PerformDrop === 'function'))
6419  return main_painter.PerformDrop(obj, "", null, opt, completeDraw);
6420  }
6421 
6422  return completeDraw(null);
6423  }
6424 
6425  function performDraw() {
6426  if (handle.direct) {
6427  painter = new TObjectPainter(obj, opt);
6428  painter.csstype = handle.csstype;
6429  painter.SetDivId(divid, 2);
6430  painter.Redraw = handle.func;
6431  painter.Redraw();
6432  painter.DrawingReady();
6433  } else {
6434  painter = handle.func(divid, obj, opt);
6435 
6436  if (painter && !painter.options) painter.options = { original: opt || "" };
6437  }
6438 
6439  return completeDraw(painter);
6440  }
6441 
6442  if (typeof handle.func == 'function') return performDraw();
6443 
6444  var funcname = "", prereq = "";
6445  if (typeof handle.func == 'object') {
6446  if ('func' in handle.func) funcname = handle.func.func;
6447  if ('script' in handle.func) prereq = "user:" + handle.func.script;
6448  } else
6449  if (typeof handle.func == 'string') {
6450  funcname = handle.func;
6451  if (('prereq' in handle) && (typeof handle.prereq == 'string')) prereq = handle.prereq;
6452  if (('script' in handle) && (typeof handle.script == 'string')) prereq += ";user:" + handle.script;
6453  }
6454 
6455  if (funcname.length === 0) return completeDraw(null);
6456 
6457  // try to find function without prerequisites
6458  var func = JSROOT.findFunction(funcname);
6459  if (func) {
6460  handle.func = func; // remember function once it is found
6461  return performDraw();
6462  }
6463 
6464  if (prereq.length === 0) return completeDraw(null);
6465 
6466  isdirectdraw = false;
6467 
6468  JSROOT.AssertPrerequisites(prereq, function() {
6469  var func = JSROOT.findFunction(funcname);
6470  if (!func) {
6471  alert('Fail to find function ' + funcname + ' after loading ' + prereq);
6472  return completeDraw(null);
6473  }
6474 
6475  handle.func = func; // remember function once it found
6476 
6477  performDraw();
6478  });
6479 
6480  return painter;
6481  }
6482 
6493  JSROOT.redraw = function(divid, obj, opt, callback) {
6494  if (!obj) return JSROOT.CallBack(callback, null);
6495  var dummy = new TObjectPainter();
6496  dummy.SetDivId(divid, -1);
6497  var can_painter = dummy.canv_painter();
6498 
6499  var handle = null;
6500  if (obj._typename) handle = JSROOT.getDrawHandle("ROOT." + obj._typename);
6501  if (handle && handle.draw_field && obj[handle.draw_field])
6502  obj = obj[handle.draw_field];
6503 
6504  if (can_painter) {
6505  if (obj._typename === "TCanvas") {
6506  can_painter.RedrawObject(obj);
6507  JSROOT.CallBack(callback, can_painter);
6508  return can_painter;
6509  }
6510 
6511  for (var i = 0; i < can_painter.painters.length; ++i) {
6512  var painter = can_painter.painters[i];
6513  if (painter.MatchObjectType(obj._typename))
6514  if (painter.UpdateObject(obj, opt)) {
6515  can_painter.RedrawPad();
6516  JSROOT.CallBack(callback, painter);
6517  return painter;
6518  }
6519  }
6520  }
6521 
6522  if (can_painter)
6523  JSROOT.console("Cannot find painter to update object of type " + obj._typename);
6524 
6525  JSROOT.cleanup(divid);
6526 
6527  return JSROOT.draw(divid, obj, opt, callback);
6528  }
6529 
6537  JSROOT.StoreJSON = function(divid) {
6538  var p = new TObjectPainter;
6539  p.SetDivId(divid,-1);
6540 
6541  var canp = p.canv_painter();
6542  return canp ? canp.ProduceJSON() : "";
6543  }
6544 
6545 
6558  JSROOT.MakeSVG = function(args, callback) {
6559 
6560  if (!args) args = {};
6561 
6562  if (!args.object) return JSROOT.CallBack(callback, null);
6563 
6564  if (!args.width) args.width = 1200;
6565  if (!args.height) args.height = 800;
6566 
6567  function build(main) {
6568 
6569  main.attr("width", args.width).attr("height", args.height);
6570 
6571  main.style("width", args.width+"px").style("height", args.height+"px");
6572 
6573  JSROOT.svg_workaround = undefined;
6574 
6575  JSROOT.draw(main.node(), args.object, args.option || "", function(painter) {
6576 
6577  var has_workarounds = JSROOT.Painter.ProcessSVGWorkarounds && JSROOT.svg_workaround;
6578 
6579  main.select('svg').attr("xmlns", "http://www.w3.org/2000/svg")
6580  .attr("xmlns:xlink", "http://www.w3.org/1999/xlink")
6581  .attr("width", args.width)
6582  .attr("height", args.height)
6583  .attr("style", null).attr("class", null).attr("x", null).attr("y", null);
6584 
6585  var svg = main.html();
6586 
6587  if (JSROOT.nodejs)
6588  svg = svg.replace(/xlink_href_nodejs=/g,"xlink:href=");
6589 
6590  if (has_workarounds)
6591  svg = JSROOT.Painter.ProcessSVGWorkarounds(svg);
6592 
6593  svg = svg.replace(/url\(\&quot\;\#(\w+)\&quot\;\)/g,"url(#$1)") // decode all URL
6594  .replace(/ class=\"\w*\"/g,"") // remove all classes
6595  .replace(/<g transform=\"translate\(\d+\,\d+\)\"><\/g>/g,"") // remove all empty groups with transform
6596  .replace(/<g><\/g>/g,""); // remove all empty groups
6597 
6598  if (svg.indexOf("xlink:href")<0)
6599  svg = svg.replace(/ xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\"/g,"");
6600 
6601  main.remove();
6602 
6603  JSROOT.CallBack(callback, svg);
6604  });
6605  }
6606 
6607  if (!JSROOT.nodejs) {
6608  build(d3.select(window.document).append("div").style("visible", "hidden"));
6609  } else if (JSROOT.nodejs_document) {
6610  build(JSROOT.nodejs_window.d3.select('body').append('div'));
6611  } else {
6612  // use eval while old minifier is not able to parse newest Node.js syntax
6613  eval('const { JSDOM } = require("jsdom"); JSROOT.nodejs_window = (new JSDOM("<!DOCTYPE html>hello")).window;');
6614  JSROOT.nodejs_document = JSROOT.nodejs_window.document; // used with three.js
6615  JSROOT.nodejs_window.d3 = d3.select(JSROOT.nodejs_document); //get d3 into the dom
6616  build(JSROOT.nodejs_window.d3.select('body').append('div'));
6617  }
6618  }
6619 
6634  JSROOT.resize = function(divid, arg) {
6635  if (arg === true) arg = { force: true }; else
6636  if (typeof arg !== 'object') arg = null;
6637  var dummy = new TObjectPainter(), done = false;
6638  dummy.SetDivId(divid, -1);
6639  dummy.ForEachPainter(function(painter) {
6640  if (!done && typeof painter.CheckResize == 'function')
6641  done = painter.CheckResize(arg);
6642  });
6643  return done;
6644  }
6645 
6650  JSROOT.CheckElementResize = JSROOT.resize;
6651 
6654  JSROOT.GetMainPainter = function(divid) {
6655  var dummy = new JSROOT.TObjectPainter();
6656  dummy.SetDivId(divid, -1);
6657  return dummy.main_painter(true);
6658  }
6659 
6665  JSROOT.cleanup = function(divid) {
6666  var dummy = new TObjectPainter(), lst = [];
6667  dummy.SetDivId(divid, -1);
6668  dummy.ForEachPainter(function(painter) {
6669  if (lst.indexOf(painter) < 0) lst.push(painter);
6670  });
6671  for (var n=0;n<lst.length;++n) lst[n].Cleanup();
6672  dummy.select_main().html("");
6673  return lst;
6674  }
6675 
6683  JSROOT.progress = function(msg, tmout) {
6684  if (JSROOT.BatchMode || (typeof document === 'undefined')) return;
6685  var id = "jsroot_progressbox",
6686  box = d3.select("#"+id);
6687 
6688  if (!JSROOT.gStyle.ProgressBox) return box.remove();
6689 
6690  if ((arguments.length == 0) || !msg) {
6691  if ((tmout !== -1) || (!box.empty() && box.property("with_timeout"))) box.remove();
6692  return;
6693  }
6694 
6695  if (box.empty()) {
6696  box = d3.select(document.body)
6697  .append("div")
6698  .attr("id", id);
6699  box.append("p");
6700  }
6701 
6702  box.property("with_timeout", false);
6703 
6704  if (typeof msg === "string") {
6705  box.select("p").html(msg);
6706  } else {
6707  box.html("");
6708  box.node().appendChild(msg);
6709  }
6710 
6711  if (!isNaN(tmout) && (tmout>0)) {
6712  box.property("with_timeout", true);
6713  setTimeout(JSROOT.progress.bind(JSROOT,'',-1), tmout);
6714  }
6715  }
6716 
6722  JSROOT.CloseCurrentWindow = function() {
6723  if (!window) return;
6724  window.close();
6725  window.open('','_self').close();
6726  }
6727 
6728  Painter.createRootColors();
6729 
6730  JSROOT.LongPollSocket = LongPollSocket;
6731  JSROOT.WebWindowHandle = WebWindowHandle;
6732  JSROOT.DrawOptions = DrawOptions;
6733  JSROOT.ColorPalette = ColorPalette;
6734  JSROOT.TAttLineHandler = TAttLineHandler;
6735  JSROOT.TAttFillHandler = TAttFillHandler;
6736  JSROOT.TAttMarkerHandler = TAttMarkerHandler;
6737  JSROOT.TBasePainter = TBasePainter;
6738  JSROOT.TObjectPainter = TObjectPainter;
6739  JSROOT.TooltipHandler = TooltipHandler;
6740 
6741  return JSROOT;
6742 
6743 }));