otsdaq_utilities  v2_05_02_indev
JSRootPainter.v7.js
1 
4 (function( factory ) {
5  if ( typeof define === "function" && define.amd ) {
6  define( ['JSRootPainter', 'd3'], factory );
7  } else if (typeof exports === 'object' && typeof module !== 'undefined') {
8  factory(require("./JSRootCore.js"), require("d3"));
9  } else {
10  if (typeof d3 != 'object')
11  throw new Error('This extension requires d3.js', 'JSRootPainter.v7.js');
12  if (typeof JSROOT == 'undefined')
13  throw new Error('JSROOT is not defined', 'JSRootPainter.v7.js');
14  if (typeof JSROOT.Painter != 'object')
15  throw new Error('JSROOT.Painter not defined', 'JSRootPainter.v7.js');
16  factory(JSROOT, d3);
17  }
18 } (function(JSROOT, d3) {
19 
20  "use strict";
21 
22  JSROOT.sources.push("v7");
23 
24  JSROOT.v7 = {}; // placeholder for v7-relevant code
25 
27  JSROOT.TObjectPainter.prototype.v7EvalAttr = function(name, dflt) {
28  var obj = this.GetObject();
29  if (!obj) return dflt;
30 
31  if (obj.fAttr && obj.fAttr.m) {
32  var value = obj.fAttr.m[name];
33  if (value) return value.v; // found value direct in attributes
34  }
35 
36  if (this.rstyle && this.rstyle.fBlocks) {
37  var blks = this.rstyle.fBlocks;
38  for (var k=0;k<blks.length;++k) {
39  var block = blks[k];
40 
41  var match = (this.csstype && (block.selector == this.csstype)) ||
42  (obj.fId && (block.selector == ("#" + obj.fId))) ||
43  (obj.fCssClass && (block.selector == ("." + obj.fCssClass)));
44 
45  if (match && block.map && block.map.m) {
46  var value = block.map.m[name];
47  if (value) return value.v;
48  }
49  }
50  }
51 
52  return dflt;
53  }
54 
56  JSROOT.TObjectPainter.prototype.v7EvalColor = function(name, dflt) {
57  var rgb = this.v7EvalAttr(name + "_rgb", "");
58 
59  if (rgb)
60  return "#" + rgb + this.v7EvalAttr(name + "_a", "");
61 
62  return this.v7EvalAttr(name + "_name", "") || dflt;
63  }
64 
65 
66  function TAxisPainter(axis, embedded) {
67  JSROOT.TObjectPainter.call(this, axis);
68 
69  this.embedded = embedded; // indicate that painter embedded into the histo painter
70 
71  this.name = "yaxis";
72  this.kind = "normal";
73  this.func = null;
74  this.order = 0; // scaling order for axis labels
75 
76  this.full_min = 0;
77  this.full_max = 1;
78  this.scale_min = 0;
79  this.scale_max = 1;
80  this.ticks = []; // list of major ticks
81  this.invert_side = false;
82  this.lbls_both_sides = false; // draw labels on both sides
83  }
84 
85  TAxisPainter.prototype = Object.create(JSROOT.TObjectPainter.prototype);
86 
87  TAxisPainter.prototype.Cleanup = function() {
88 
89  this.ticks = [];
90  this.func = null;
91  delete this.format;
92 
93  JSROOT.TObjectPainter.prototype.Cleanup.call(this);
94  }
95 
96  TAxisPainter.prototype.SetAxisConfig = function(name, kind, func, min, max, smin, smax) {
97  this.name = name;
98  this.kind = kind;
99  this.func = func;
100 
101  this.full_min = min;
102  this.full_max = max;
103  this.scale_min = smin;
104  this.scale_max = smax;
105  }
106 
107  TAxisPainter.prototype.format10Exp = function(order, value) {
108  var res = "";
109  if (value) {
110  value = Math.round(value/Math.pow(10,order));
111  if ((value!=0) && (value!=1)) res = value.toString() + (JSROOT.gStyle.Latex ? "#times" : "x");
112  }
113  res += "10";
114  if (JSROOT.gStyle.Latex > 1) return res + "^{" + order + "}";
115  var superscript_symbols = {
116  '0': '\u2070', '1': '\xB9', '2': '\xB2', '3': '\xB3', '4': '\u2074', '5': '\u2075',
117  '6': '\u2076', '7': '\u2077', '8': '\u2078', '9': '\u2079', '-': '\u207B'
118  };
119  var str = order.toString();
120  for (var n=0;n<str.length;++n) res += superscript_symbols[str[n]];
121  return res;
122  }
123 
124  TAxisPainter.prototype.CreateFormatFuncs = function() {
125 
126  var axis = this.GetObject(),
127  is_gaxis = (axis && axis._typename === 'TGaxis');
128 
129  delete this.format;// remove formatting func
130 
131  var ndiv = 508;
132  if (is_gaxis) ndiv = axis.fNdiv; else
133  if (axis) ndiv = Math.max(axis.fNdivisions, 4);
134 
135  this.nticks = ndiv % 100;
136  this.nticks2 = (ndiv % 10000 - this.nticks) / 100;
137  this.nticks3 = Math.floor(ndiv/10000);
138 
139  if (axis && !is_gaxis && (this.nticks > 7)) this.nticks = 7;
140 
141  var gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]);
142  if (gr_range<=0) gr_range = 100;
143 
144  if (this.kind == 'time') {
145  if (this.nticks > 8) this.nticks = 8;
146 
147  var scale_range = this.scale_max - this.scale_min,
148  tf1 = JSROOT.Painter.getTimeFormat(axis),
149  tf2 = JSROOT.Painter.chooseTimeFormat(scale_range / gr_range, false);
150 
151  if ((tf1.length == 0) || (scale_range < 0.1 * (this.full_max - this.full_min)))
152  tf1 = JSROOT.Painter.chooseTimeFormat(scale_range / this.nticks, true);
153 
154  this.tfunc1 = this.tfunc2 = d3.timeFormat(tf1);
155  if (tf2!==tf1)
156  this.tfunc2 = d3.timeFormat(tf2);
157 
158  this.format = function(d, asticks) {
159  return asticks ? this.tfunc1(d) : this.tfunc2(d);
160  }
161 
162  } else if (this.kind == 'log') {
163  if (this.nticks2 > 1) {
164  this.nticks *= this.nticks2; // all log ticks (major or minor) created centrally
165  this.nticks2 = 1;
166  }
167  this.noexp = axis ? axis.TestBit(JSROOT.EAxisBits.kNoExponent) : false;
168  if ((this.scale_max < 300) && (this.scale_min > 0.3)) this.noexp = true;
169  this.moreloglabels = axis ? axis.TestBit(JSROOT.EAxisBits.kMoreLogLabels) : false;
170 
171  this.format = function(d, asticks, notickexp_fmt) {
172  var val = parseFloat(d), rnd = Math.round(val);
173  if (!asticks)
174  return ((rnd === val) && (Math.abs(rnd)<1e9)) ? rnd.toString() : JSROOT.FFormat(val, notickexp_fmt || JSROOT.gStyle.fStatFormat);
175 
176  if (val <= 0) return null;
177  var vlog = JSROOT.log10(val);
178  if (this.moreloglabels || (Math.abs(vlog - Math.round(vlog))<0.001)) {
179  if (!this.noexp && !notickexp_fmt)
180  return this.format10Exp(Math.floor(vlog+0.01), val);
181 
182  return (vlog<0) ? val.toFixed(Math.round(-vlog+0.5)) : val.toFixed(0);
183  }
184  return null;
185  }
186  } else if (this.kind == 'labels') {
187  this.nticks = 50; // for text output allow max 50 names
188  var scale_range = this.scale_max - this.scale_min;
189  if (this.nticks > scale_range)
190  this.nticks = Math.round(scale_range);
191  this.nticks2 = 1;
192 
193  this.regular_labels = true;
194 
195  if (axis && axis.fNbins && axis.fLabels) {
196  if ((axis.fNbins != Math.round(axis.fXmax - axis.fXmin)) ||
197  (axis.fXmin != 0) || (axis.fXmax != axis.fNbins)) {
198  this.regular_labels = false;
199  }
200  }
201 
202  this.axis = axis;
203 
204  this.format = function(d) {
205  var indx = parseFloat(d);
206  if (!this.regular_labels)
207  indx = (indx - this.axis.fXmin)/(this.axis.fXmax - this.axis.fXmin) * this.axis.fNbins;
208  indx = Math.round(indx);
209  if ((indx<0) || (indx>=this.axis.fNbins)) return null;
210  for (var i = 0; i < this.axis.fLabels.arr.length; ++i) {
211  var tstr = this.axis.fLabels.arr[i];
212  if (tstr.fUniqueID === indx+1) return tstr.fString;
213  }
214  return null;
215  }
216  } else {
217 
218  this.order = 0;
219  this.ndig = 0;
220 
221  this.format = function(d, asticks, fmt) {
222  var val = parseFloat(d);
223  if (asticks && this.order) val = val / Math.pow(10, this.order);
224 
225  if (val === Math.round(val))
226  return (Math.abs(val)<1e9) ? val.toFixed(0) : val.toExponential(4);
227 
228  if (asticks) return (this.ndig>10) ? val.toExponential(this.ndig-11) : val.toFixed(this.ndig);
229 
230  return JSROOT.FFormat(val, fmt || JSROOT.gStyle.fStatFormat);
231  }
232  }
233  }
234 
235  TAxisPainter.prototype.ProduceTicks = function(ndiv, ndiv2) {
236  if (!this.noticksopt) return this.func.ticks(ndiv * (ndiv2 || 1));
237 
238  if (ndiv2) ndiv = (ndiv-1) * ndiv2;
239  var dom = this.func.domain(), ticks = [];
240  for (var n=0;n<=ndiv;++n)
241  ticks.push((dom[0]*(ndiv-n) + dom[1]*n)/ndiv);
242  return ticks;
243  }
244 
245  TAxisPainter.prototype.CreateTicks = function(only_major_as_array, optionNoexp, optionNoopt, optionInt) {
246  // function used to create array with minor/middle/major ticks
247 
248  if (optionNoopt && this.nticks && (this.kind == "normal")) this.noticksopt = true;
249 
250  var handle = { nminor: 0, nmiddle: 0, nmajor: 0, func: this.func };
251 
252  handle.minor = handle.middle = handle.major = this.ProduceTicks(this.nticks);
253 
254  if (only_major_as_array) {
255  var res = handle.major, delta = (this.scale_max - this.scale_min)*1e-5;
256  if (res[0] > this.scale_min + delta) res.unshift(this.scale_min);
257  if (res[res.length-1] < this.scale_max - delta) res.push(this.scale_max);
258  return res;
259  }
260 
261  if ((this.kind == 'labels') && !this.regular_labels) {
262  handle.lbl_pos = [];
263  for (var n=0;n<this.axis.fNbins;++n) {
264  var x = this.axis.fXmin + n / this.axis.fNbins * (this.axis.fXmax - this.axis.fXmin);
265  if ((x >= this.scale_min) && (x < this.scale_max)) handle.lbl_pos.push(x);
266  }
267  }
268 
269  if (this.nticks2 > 1) {
270  handle.minor = handle.middle = this.ProduceTicks(handle.major.length, this.nticks2);
271 
272  var gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]);
273 
274  // avoid black filling by middle-size
275  if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range/3.5)) {
276  handle.minor = handle.middle = handle.major;
277  } else
278  if ((this.nticks3 > 1) && (this.kind !== 'log')) {
279  handle.minor = this.ProduceTicks(handle.middle.length, this.nticks3);
280  if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range/1.7)) handle.minor = handle.middle;
281  }
282  }
283 
284  handle.reset = function() {
285  this.nminor = this.nmiddle = this.nmajor = 0;
286  }
287 
288  handle.next = function(doround) {
289  if (this.nminor >= this.minor.length) return false;
290 
291  this.tick = this.minor[this.nminor++];
292  this.grpos = this.func(this.tick);
293  if (doround) this.grpos = Math.round(this.grpos);
294  this.kind = 3;
295 
296  if ((this.nmiddle < this.middle.length) && (Math.abs(this.grpos - this.func(this.middle[this.nmiddle])) < 1)) {
297  this.nmiddle++;
298  this.kind = 2;
299  }
300 
301  if ((this.nmajor < this.major.length) && (Math.abs(this.grpos - this.func(this.major[this.nmajor])) < 1) ) {
302  this.nmajor++;
303  this.kind = 1;
304  }
305  return true;
306  }
307 
308  handle.last_major = function() {
309  return (this.kind !== 1) ? false : this.nmajor == this.major.length;
310  }
311 
312  handle.next_major_grpos = function() {
313  if (this.nmajor >= this.major.length) return null;
314  return this.func(this.major[this.nmajor]);
315  }
316 
317  this.order = 0;
318  this.ndig = 0;
319 
320  // at the moment when drawing labels, we can try to find most optimal text representation for them
321 
322  if ((this.kind == "normal") && (handle.major.length > 0)) {
323 
324  var maxorder = 0, minorder = 0, exclorder3 = false;
325 
326  if (!optionNoexp) {
327  var maxtick = Math.max(Math.abs(handle.major[0]),Math.abs(handle.major[handle.major.length-1])),
328  mintick = Math.min(Math.abs(handle.major[0]),Math.abs(handle.major[handle.major.length-1])),
329  ord1 = (maxtick > 0) ? Math.round(JSROOT.log10(maxtick)/3)*3 : 0,
330  ord2 = (mintick > 0) ? Math.round(JSROOT.log10(mintick)/3)*3 : 0;
331 
332  exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000
333 
334  if (maxtick || mintick) {
335  maxorder = Math.max(ord1,ord2) + 3;
336  minorder = Math.min(ord1,ord2) - 3;
337  }
338  }
339 
340  // now try to find best combination of order and ndig for labels
341 
342  var bestorder = 0, bestndig = this.ndig, bestlen = 1e10;
343 
344  for (var order = minorder; order <= maxorder; order+=3) {
345  if (exclorder3 && (order===3)) continue;
346  this.order = order;
347  this.ndig = 0;
348  var lbls = [], indx = 0, totallen = 0;
349  while (indx<handle.major.length) {
350  var lbl = this.format(handle.major[indx], true);
351  if (lbls.indexOf(lbl)<0) {
352  lbls.push(lbl);
353  totallen += lbl.length;
354  indx++;
355  continue;
356  }
357  if (++this.ndig > 11) break; // not too many digits, anyway it will be exponential
358  lbls = []; indx = 0; totallen = 0;
359  }
360 
361  // for order==0 we should virually remove "0." and extra label on top
362  if (!order && (this.ndig<4)) totallen-=(handle.major.length*2+3);
363 
364  if (totallen < bestlen) {
365  bestlen = totallen;
366  bestorder = this.order;
367  bestndig = this.ndig;
368  }
369  }
370 
371  this.order = bestorder;
372  this.ndig = bestndig;
373 
374  if (optionInt) {
375  if (this.order) console.warn('Axis painter - integer labels are configured, but axis order ' + this.order + ' is preferable');
376  if (this.ndig) console.warn('Axis painter - integer labels are configured, but ' + this.ndig + ' decimal digits are required');
377  this.ndig = 0;
378  this.order = 0;
379  }
380  }
381 
382  return handle;
383  }
384 
385  TAxisPainter.prototype.IsCenterLabels = function() {
386  if (this.kind === 'labels') return true;
387  if (this.kind === 'log') return false;
388  var axis = this.GetObject();
389  return axis && axis.TestBit(JSROOT.EAxisBits.kCenterLabels);
390  }
391 
392  TAxisPainter.prototype.AddTitleDrag = function(title_g, vertical, offset_k, reverse, axis_length) {
393  if (!JSROOT.gStyle.MoveResize) return;
394 
395  var pthis = this, drag_rect = null, prefix = "", drag_move,
396  acc_x, acc_y, new_x, new_y, sign_0, center_0, alt_pos;
397  if (JSROOT._test_d3_ === 3) {
398  prefix = "drag";
399  drag_move = d3.behavior.drag().origin(Object);
400  } else {
401  drag_move = d3.drag().subject(Object);
402  }
403 
404  drag_move
405  .on(prefix+"start", function() {
406 
407  d3.event.sourceEvent.preventDefault();
408  d3.event.sourceEvent.stopPropagation();
409 
410  var box = title_g.node().getBBox(), // check that elements visible, request precise value
411  axis = pthis.GetObject();
412 
413  new_x = acc_x = title_g.property('shift_x');
414  new_y = acc_y = title_g.property('shift_y');
415 
416  sign_0 = vertical ? (acc_x>0) : (acc_y>0); // sign should remain
417 
418  if (axis.TestBit(JSROOT.EAxisBits.kCenterTitle))
419  alt_pos = (reverse === vertical) ? axis_length : 0;
420  else
421  alt_pos = Math.round(axis_length/2);
422 
423  drag_rect = title_g.append("rect")
424  .classed("zoom", true)
425  .attr("x", box.x)
426  .attr("y", box.y)
427  .attr("width", box.width)
428  .attr("height", box.height)
429  .style("cursor", "move");
430 // .style("pointer-events","none"); // let forward double click to underlying elements
431  }).on("drag", function() {
432  if (!drag_rect) return;
433 
434  d3.event.sourceEvent.preventDefault();
435  d3.event.sourceEvent.stopPropagation();
436 
437  acc_x += d3.event.dx;
438  acc_y += d3.event.dy;
439 
440  var set_x = title_g.property('shift_x'),
441  set_y = title_g.property('shift_y');
442 
443  if (vertical) {
444  set_x = acc_x;
445  if (Math.abs(acc_y - set_y) > Math.abs(acc_y - alt_pos)) set_y = alt_pos;
446  } else {
447  set_y = acc_y;
448  if (Math.abs(acc_x - set_x) > Math.abs(acc_x - alt_pos)) set_x = alt_pos;
449  }
450 
451  if (sign_0 === (vertical ? (set_x>0) : (set_y>0))) {
452  new_x = set_x; new_y = set_y;
453  title_g.attr('transform', 'translate(' + new_x + ',' + new_y + ')');
454  }
455 
456  }).on(prefix+"end", function() {
457  if (!drag_rect) return;
458 
459  d3.event.sourceEvent.preventDefault();
460  d3.event.sourceEvent.stopPropagation();
461 
462  title_g.property('shift_x', new_x)
463  .property('shift_y', new_y);
464 
465  var axis = pthis.GetObject();
466 
467  axis.fTitleOffset = (vertical ? new_x : new_y) / offset_k;
468  if ((vertical ? new_y : new_x) === alt_pos) axis.InvertBit(JSROOT.EAxisBits.kCenterTitle);
469 
470  drag_rect.remove();
471  drag_rect = null;
472  });
473 
474  title_g.style("cursor", "move").call(drag_move);
475  }
476 
477  TAxisPainter.prototype.DrawAxis = function(vertical, layer, w, h, transform, reverse, second_shift, disable_axis_drawing, max_text_width) {
478  // function draws TAxis or TGaxis object
479 
480  var axis = this.GetObject(), chOpt = "",
481  is_gaxis = (axis && axis._typename === 'TGaxis'),
482  axis_g = layer, tickSize = 0.03,
483  scaling_size = 100, draw_lines = true,
484  pad_w = this.pad_width() || 10,
485  pad_h = this.pad_height() || 10;
486 
487  this.vertical = vertical;
488 
489  function myXor(a,b) { return ( a && !b ) || (!a && b); }
490 
491  // shift for second ticks set (if any)
492  if (!second_shift) second_shift = 0; else
493  if (this.invert_side) second_shift = -second_shift;
494 
495  if (is_gaxis) {
496  this.createAttLine({ attr: axis });
497  draw_lines = axis.fLineColor != 0;
498  chOpt = axis.fChopt;
499  tickSize = axis.fTickSize;
500  scaling_size = (vertical ? 1.7*h : 0.6*w);
501  } else {
502  this.createAttLine({ color: axis.fAxisColor, width: 1, style: 1 });
503  chOpt = myXor(vertical, this.invert_side) ? "-S" : "+S";
504  tickSize = axis.fTickLength;
505  scaling_size = (vertical ? pad_w : pad_h);
506  }
507 
508  if (!is_gaxis || (this.name === "zaxis")) {
509  axis_g = layer.select("." + this.name + "_container");
510  if (axis_g.empty())
511  axis_g = layer.append("svg:g").attr("class",this.name + "_container");
512  else
513  axis_g.selectAll("*").remove();
514  } else {
515  if (!disable_axis_drawing && draw_lines)
516  axis_g.append("svg:line")
517  .attr("x1",0).attr("y1",0)
518  .attr("x1",vertical ? 0 : w)
519  .attr("y1", vertical ? h : 0)
520  .call(this.lineatt.func);
521  }
522 
523  axis_g.attr("transform", transform || null);
524 
525  var side = 1, ticks_plusminus = 0,
526  text_scaling_size = Math.min(pad_w, pad_h),
527  optionPlus = (chOpt.indexOf("+")>=0),
528  optionMinus = (chOpt.indexOf("-")>=0),
529  optionSize = (chOpt.indexOf("S")>=0),
530  optionY = (chOpt.indexOf("Y")>=0),
531  optionUp = (chOpt.indexOf("0")>=0),
532  optionDown = (chOpt.indexOf("O")>=0),
533  optionUnlab = (chOpt.indexOf("U")>=0), // no labels
534  optionNoopt = (chOpt.indexOf("N")>=0), // no ticks position optimization
535  optionInt = (chOpt.indexOf("I")>=0), // integer labels
536  optionNoexp = axis.TestBit(JSROOT.EAxisBits.kNoExponent);
537 
538  if (is_gaxis && axis.TestBit(JSROOT.EAxisBits.kTickPlus)) optionPlus = true;
539  if (is_gaxis && axis.TestBit(JSROOT.EAxisBits.kTickMinus)) optionMinus = true;
540 
541  if (optionPlus && optionMinus) { side = 1; ticks_plusminus = 1; } else
542  if (optionMinus) { side = myXor(reverse,vertical) ? 1 : -1; } else
543  if (optionPlus) { side = myXor(reverse,vertical) ? -1 : 1; }
544 
545  tickSize = Math.round((optionSize ? tickSize : 0.03) * scaling_size);
546 
547  if (this.max_tick_size && (tickSize > this.max_tick_size)) tickSize = this.max_tick_size;
548 
549  this.CreateFormatFuncs();
550 
551  var res = "", res2 = "", lastpos = 0, lasth = 0;
552 
553  // first draw ticks
554 
555  this.ticks = [];
556 
557  var handle = this.CreateTicks(false, optionNoexp, optionNoopt, optionInt);
558 
559  while (handle.next(true)) {
560 
561  var h1 = Math.round(tickSize/4), h2 = 0;
562 
563  if (handle.kind < 3)
564  h1 = Math.round(tickSize/2);
565 
566  if (handle.kind == 1) {
567  // if not showing labels, not show large tick
568  if (!('format' in this) || (this.format(handle.tick,true)!==null)) h1 = tickSize;
569  this.ticks.push(handle.grpos); // keep graphical positions of major ticks
570  }
571 
572  if (ticks_plusminus > 0) h2 = -h1; else
573  if (side < 0) { h2 = -h1; h1 = 0; } else { h2 = 0; }
574 
575  if (res.length == 0) {
576  res = vertical ? ("M"+h1+","+handle.grpos) : ("M"+handle.grpos+","+(-h1));
577  res2 = vertical ? ("M"+(second_shift-h1)+","+handle.grpos) : ("M"+handle.grpos+","+(second_shift+h1));
578  } else {
579  res += vertical ? ("m"+(h1-lasth)+","+(handle.grpos-lastpos)) : ("m"+(handle.grpos-lastpos)+","+(lasth-h1));
580  res2 += vertical ? ("m"+(lasth-h1)+","+(handle.grpos-lastpos)) : ("m"+(handle.grpos-lastpos)+","+(h1-lasth));
581  }
582 
583  res += vertical ? ("h"+ (h2-h1)) : ("v"+ (h1-h2));
584  res2 += vertical ? ("h"+ (h1-h2)) : ("v"+ (h2-h1));
585 
586  lastpos = handle.grpos;
587  lasth = h2;
588  }
589 
590  if ((res.length > 0) && !disable_axis_drawing && draw_lines)
591  axis_g.append("svg:path").attr("d", res).call(this.lineatt.func);
592 
593  if ((second_shift!==0) && (res2.length>0) && !disable_axis_drawing && draw_lines)
594  axis_g.append("svg:path").attr("d", res2).call(this.lineatt.func);
595 
596  var labelsize = Math.round( (axis.fLabelSize < 1) ? axis.fLabelSize * text_scaling_size : axis.fLabelSize);
597  if ((labelsize <= 0) || (Math.abs(axis.fLabelOffset) > 1.1)) optionUnlab = true; // disable labels when size not specified
598 
599  // draw labels (on both sides, when needed)
600  if (!disable_axis_drawing && !optionUnlab) {
601 
602  var label_color = this.get_color(axis.fLabelColor),
603  labeloffset = Math.round(axis.fLabelOffset*text_scaling_size /*+ 0.5*labelsize*/),
604  center_lbls = this.IsCenterLabels(),
605  rotate_lbls = axis.TestBit(JSROOT.EAxisBits.kLabelsVert),
606  textscale = 1, maxtextlen = 0, lbls_tilt = false, labelfont = null,
607  label_g = [ axis_g.append("svg:g").attr("class","axis_labels") ],
608  lbl_pos = handle.lbl_pos || handle.major;
609 
610  if (this.lbls_both_sides)
611  label_g.push(axis_g.append("svg:g").attr("class","axis_labels").attr("transform", vertical ? "translate(" + w + ",0)" : "translate(0," + (-h) + ")"));
612 
613  for (var lcnt = 0; lcnt < label_g.length; ++lcnt) {
614 
615  if (lcnt > 0) side = -side;
616 
617  var lastpos = 0,
618  fix_coord = vertical ? -labeloffset*side : (labeloffset+2)*side + ticks_plusminus*tickSize;
619 
620  labelfont = JSROOT.Painter.getFontDetails(axis.fLabelFont, labelsize);
621 
622  this.StartTextDrawing(labelfont, 'font', label_g[lcnt]);
623 
624  for (var nmajor=0;nmajor<lbl_pos.length;++nmajor) {
625 
626  var lbl = this.format(lbl_pos[nmajor], true);
627  if (lbl === null) continue;
628 
629  var pos = Math.round(this.func(lbl_pos[nmajor])),
630  gap_before = (nmajor>0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor-1]))) : 0,
631  gap_after = (nmajor<lbl_pos.length-1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor+1])-pos)) : 0;
632 
633  if (center_lbls) {
634  var gap = gap_after || gap_before;
635  pos = Math.round(pos - (vertical ? 0.5*gap : -0.5*gap));
636  if ((pos < -5) || (pos > (vertical ? h : w) + 5)) continue;
637  }
638 
639  var arg = { text: lbl, color: label_color, latex: 1, draw_g: label_g[lcnt] };
640 
641  maxtextlen = Math.max(maxtextlen, lbl.length);
642 
643  if (vertical) {
644  arg.x = fix_coord;
645  arg.y = pos;
646  arg.align = rotate_lbls ? ((side<0) ? 23 : 20) : ((side<0) ? 12 : 32);
647  } else {
648  arg.x = pos;
649  arg.y = fix_coord;
650  arg.align = rotate_lbls ? ((side<0) ? 12 : 32) : ((side<0) ? 20 : 23);
651  }
652 
653  if (rotate_lbls) arg.rotate = 270;
654 
655  var textwidth = this.DrawText(arg);
656 
657  if (textwidth && ((!vertical && !rotate_lbls) || (vertical && rotate_lbls)) && (this.kind != 'log')) {
658  var maxwidth = gap_before*0.45 + gap_after*0.45;
659  if (!gap_before) maxwidth = 0.9*gap_after; else
660  if (!gap_after) maxwidth = 0.9*gap_before;
661  textscale = Math.min(textscale, maxwidth / textwidth);
662  } else if (vertical && max_text_width && !lcnt && (max_text_width - labeloffset > 20) && (textwidth > max_text_width - labeloffset)) {
663  textscale = Math.min(textscale, (max_text_width - labeloffset) / textwidth);
664  }
665 
666  if (lastpos && (pos!=lastpos) && ((vertical && !rotate_lbls) || (!vertical && rotate_lbls))) {
667  var axis_step = Math.abs(pos-lastpos);
668  textscale = Math.min(textscale, 0.9*axis_step/labelsize);
669  }
670 
671  lastpos = pos;
672  }
673 
674  if (this.order)
675  this.DrawText({ color: label_color,
676  x: vertical ? side*5 : w+5,
677  y: this.has_obstacle ? fix_coord : (vertical ? -3 : -3*side),
678  align: vertical ? ((side<0) ? 30 : 10) : ( myXor(this.has_obstacle, (side<0)) ? 13 : 10 ),
679  latex: 1,
680  text: '#times' + this.format10Exp(this.order),
681  draw_g: label_g[lcnt]
682  });
683 
684  }
685 
686  if ((textscale > 0.01) && (textscale < 0.7) && !vertical && !rotate_lbls && (maxtextlen > 5) && !this.lbls_both_sides) {
687  lbls_tilt = true;
688  textscale *= 3;
689  }
690 
691  for (var lcnt = 0; lcnt < label_g.length; ++lcnt) {
692  if ((textscale > 0.01) && (textscale < 1))
693  this.TextScaleFactor(1/textscale, label_g[lcnt]);
694 
695  this.FinishTextDrawing(label_g[lcnt]);
696  if (lbls_tilt)
697  label_g[lcnt].selectAll("text").each(function() {
698  var txt = d3.select(this), tr = txt.attr("transform");
699  txt.attr("transform", tr + " rotate(25)").style("text-anchor", "start");
700  });
701  }
702 
703  if (label_g.length > 1) side = -side;
704 
705  if (labelfont) labelsize = labelfont.size; // use real font size
706  }
707 
708  if (JSROOT.gStyle.Zooming && !this.disable_zooming) {
709  var r = axis_g.append("svg:rect")
710  .attr("class", "axis_zoom")
711  .style("opacity", "0")
712  .style("cursor", "crosshair");
713 
714  if (vertical)
715  r.attr("x", (side>0) ? (-2*labelsize - 3) : 3)
716  .attr("y", 0)
717  .attr("width", 2*labelsize + 3)
718  .attr("height", h)
719  else
720  r.attr("x", 0).attr("y", (side>0) ? 0 : -labelsize-3)
721  .attr("width", w).attr("height", labelsize + 3);
722  }
723 
724  if ((axis.fTitle.length > 0) && !disable_axis_drawing) {
725  var title_g = axis_g.append("svg:g").attr("class", "axis_title"),
726  title_fontsize = (axis.fTitleSize >= 1) ? axis.fTitleSize : Math.round(axis.fTitleSize * text_scaling_size),
727  title_offest_k = 1.6*(axis.fTitleSize<1 ? axis.fTitleSize : axis.fTitleSize/(this.pad_height("") || 10)),
728  center = axis.TestBit(JSROOT.EAxisBits.kCenterTitle),
729  rotate = axis.TestBit(JSROOT.EAxisBits.kRotateTitle) ? -1 : 1,
730  title_color = this.get_color(axis.fTitleColor),
731  shift_x = 0, shift_y = 0;
732 
733  this.StartTextDrawing(axis.fTitleFont, title_fontsize, title_g);
734 
735  var myxor = ((rotate<0) && !reverse) || ((rotate>=0) && reverse);
736 
737  if (vertical) {
738  title_offest_k *= -side*pad_w;
739 
740  shift_x = Math.round(title_offest_k*axis.fTitleOffset);
741 
742  if ((this.name == "zaxis") && is_gaxis && ('getBoundingClientRect' in axis_g.node())) {
743  // special handling for color palette labels - draw them always on right side
744  var rect = axis_g.node().getBoundingClientRect();
745  if (shift_x < rect.width - tickSize) shift_x = Math.round(rect.width - tickSize);
746  }
747 
748  shift_y = Math.round(center ? h/2 : (reverse ? h : 0));
749 
750  this.DrawText({ align: (center ? "middle" : (myxor ? "begin" : "end" )) + ";middle",
751  rotate: (rotate<0) ? 90 : 270,
752  text: axis.fTitle, color: title_color, draw_g: title_g });
753  } else {
754  title_offest_k *= side*pad_h;
755 
756  shift_x = Math.round(center ? w/2 : (reverse ? 0 : w));
757  shift_y = Math.round(title_offest_k*axis.fTitleOffset);
758  this.DrawText({ align: (center ? 'middle' : (myxor ? 'begin' : 'end')) + ";middle",
759  rotate: (rotate<0) ? 180 : 0,
760  text: axis.fTitle, color: title_color, draw_g: title_g });
761  }
762 
763  var axis_rect = null;
764  if (vertical && (axis.fTitleOffset == 0) && ('getBoundingClientRect' in axis_g.node()))
765  axis_rect = axis_g.node().getBoundingClientRect();
766 
767  this.FinishTextDrawing(title_g, function() {
768  if (axis_rect) {
769  var title_rect = title_g.node().getBoundingClientRect();
770  shift_x = (side>0) ? Math.round(axis_rect.left - title_rect.right - title_fontsize*0.3) :
771  Math.round(axis_rect.right - title_rect.left + title_fontsize*0.3);
772  }
773 
774  title_g.attr('transform', 'translate(' + shift_x + ',' + shift_y + ')')
775  .property('shift_x', shift_x)
776  .property('shift_y', shift_y);
777  });
778 
779 
780  this.AddTitleDrag(title_g, vertical, title_offest_k, reverse, vertical ? h : w);
781  }
782 
783  this.position = 0;
784 
785  if ('getBoundingClientRect' in axis_g.node()) {
786  var rect1 = axis_g.node().getBoundingClientRect(),
787  rect2 = this.svg_pad().node().getBoundingClientRect();
788 
789  this.position = rect1.left - rect2.left; // use to control left position of Y scale
790  }
791  }
792 
793  TAxisPainter.prototype.Redraw = function() {
794 
795  var gaxis = this.GetObject(),
796  x1 = this.AxisToSvg("x", gaxis.fX1),
797  y1 = this.AxisToSvg("y", gaxis.fY1),
798  x2 = this.AxisToSvg("x", gaxis.fX2),
799  y2 = this.AxisToSvg("y", gaxis.fY2),
800  w = x2 - x1, h = y1 - y2,
801  vertical = Math.abs(w) < Math.abs(h),
802  func = null, reverse = false, kind = "normal",
803  min = gaxis.fWmin, max = gaxis.fWmax,
804  domain_min = min, domain_max = max;
805 
806  if (gaxis.fChopt.indexOf("t")>=0) {
807  func = d3.scaleTime();
808  kind = "time";
809  this.toffset = JSROOT.Painter.getTimeOffset(gaxis);
810  domain_min = new Date(this.toffset + min*1000);
811  domain_max = new Date(this.toffset + max*1000);
812  } else if (gaxis.fChopt.indexOf("G")>=0) {
813  func = d3.scaleLog();
814  kind = "log";
815  } else {
816  func = d3.scaleLinear();
817  kind = "normal";
818  }
819 
820  func.domain([domain_min, domain_max]);
821 
822  if (vertical) {
823  if (h > 0) {
824  func.range([h,0]);
825  } else {
826  var d = y1; y1 = y2; y2 = d;
827  h = -h; reverse = true;
828  func.range([0,h]);
829  }
830  } else {
831  if (w > 0) {
832  func.range([0,w]);
833  } else {
834  var d = x1; x1 = x2; x2 = d;
835  w = -w; reverse = true;
836  func.range([w,0]);
837  }
838  }
839 
840  this.SetAxisConfig(vertical ? "yaxis" : "xaxis", kind, func, min, max, min, max);
841 
842  this.CreateG();
843 
844  this.DrawAxis(vertical, this.draw_g, w, h, "translate(" + x1 + "," + y2 +")", reverse);
845  }
846 
847  // ==========================================================================================
848 
849 
850  function TFramePainter(tframe) {
851  JSROOT.TooltipHandler.call(this, tframe);
852  this.mode3d = false;
853  this.shrink_frame_left = 0.;
854  this.x_kind = 'normal'; // 'normal', 'log', 'time', 'labels'
855  this.y_kind = 'normal'; // 'normal', 'log', 'time', 'labels'
856  this.xmin = this.xmax = 0; // no scale specified, wait for objects drawing
857  this.ymin = this.ymax = 0; // no scale specified, wait for objects drawing
858  this.axes_drawn = false;
859  this.keys_handler = null;
860  this.mode3d = false;
861  }
862 
863  TFramePainter.prototype = Object.create(JSROOT.TooltipHandler.prototype);
864 
865  TFramePainter.prototype.frame_painter = function() {
866  return this;
867  }
868 
871  TFramePainter.prototype.SetActive = function(on) {
872  // do nothing here - key handler is handled differently
873  }
874 
875  TFramePainter.prototype.GetTipName = function(append) {
876  var res = JSROOT.TooltipHandler.prototype.GetTipName.call(this) || "TFrame";
877  if (append) res+=append;
878  return res;
879  }
880 
881  TFramePainter.prototype.Shrink = function(shrink_left, shrink_right) {
882  this.fX1NDC += shrink_left;
883  this.fX2NDC -= shrink_right;
884  }
885 
886  TFramePainter.prototype.SetLastEventPos = function(pnt) {
887  // set position of last context menu event, can be
888  this.fLastEventPnt = pnt;
889  }
890 
891  TFramePainter.prototype.GetLastEventPos = function() {
892  // return position of last event
893  return this.fLastEventPnt;
894  }
895 
896  TFramePainter.prototype.UpdateAttributes = function(force) {
897  var tframe = this.GetObject();
898 
899  if ((this.fX1NDC === undefined) || (force && !this.modified_NDC)) {
900  JSROOT.extend(this, JSROOT.gStyle.FrameNDC);
901 
902  if (tframe && tframe.fPos && tframe.fSize) {
903  this.fX1NDC = tframe.fPos.fHoriz.fNormal.fVal;
904  this.fX2NDC = this.fX1NDC + tframe.fSize.fHoriz.fNormal.fVal;
905  this.fY1NDC = tframe.fPos.fVert.fNormal.fVal;
906  this.fY2NDC = this.fY1NDC + tframe.fSize.fVert.fNormal.fVal;
907  }
908  }
909 
910  if (this.fillatt === undefined) {
911  this.createAttFill({ pattern: 1001, color: 0 });
912 
913  // TODO: provide real fill color
914  this.fillatt.SetSolidColor('white');
915  }
916 
917  this.createAttLine({ color: 'black' });
918  }
919 
920  TFramePainter.prototype.ProjectAitoff2xy = function(l, b) {
921  var DegToRad = Math.PI/180,
922  alpha2 = (l/2)*DegToRad,
923  delta = b*DegToRad,
924  r2 = Math.sqrt(2),
925  f = 2*r2/Math.PI,
926  cdec = Math.cos(delta),
927  denom = Math.sqrt(1. + cdec*Math.cos(alpha2)),
928  res = {
929  x: cdec*Math.sin(alpha2)*2.*r2/denom/f/DegToRad,
930  y: Math.sin(delta)*r2/denom/f/DegToRad
931  };
932  // x *= -1.; // for a skymap swap left<->right
933  return res;
934  }
935 
936  TFramePainter.prototype.ProjectMercator2xy = function(l, b) {
937  var aid = Math.tan((Math.PI/2 + b/180*Math.PI)/2);
938  return { x: l, y: Math.log(aid) };
939  }
940 
941  TFramePainter.prototype.ProjectSinusoidal2xy = function(l, b) {
942  return { x: l*Math.cos(b/180*Math.PI), y: b };
943  }
944 
945  TFramePainter.prototype.ProjectParabolic2xy = function(l, b) {
946  return {
947  x: l*(2.*Math.cos(2*b/180*Math.PI/3) - 1),
948  y: 180*Math.sin(b/180*Math.PI/3)
949  };
950  }
951 
952  TFramePainter.prototype.RecalculateRange = function(Proj) {
953  // not yet used, could be useful in the future
954 
955  if (!Proj) return;
956 
957  var pnts = []; // all extremes which used to find
958  if (Proj == 1) {
959  // TODO : check x range not lower than -180 and not higher than 180
960  pnts.push(this.ProjectAitoff2xy(this.scale_xmin, this.scale_ymin));
961  pnts.push(this.ProjectAitoff2xy(this.scale_xmin, this.scale_ymax));
962  pnts.push(this.ProjectAitoff2xy(this.scale_xmax, this.scale_ymax));
963  pnts.push(this.ProjectAitoff2xy(this.scale_xmax, this.scale_ymin));
964  if (this.scale_ymin<0 && this.scale_ymax>0) {
965  // there is an 'equator', check its range in the plot..
966  pnts.push(this.ProjectAitoff2xy(this.scale_xmin*0.9999, 0));
967  pnts.push(this.ProjectAitoff2xy(this.scale_xmax*0.9999, 0));
968  }
969  if (this.scale_xmin<0 && this.scale_xmax>0) {
970  pnts.push(this.ProjectAitoff2xy(0, this.scale_ymin));
971  pnts.push(this.ProjectAitoff2xy(0, this.scale_ymax));
972  }
973  } else if (Proj == 2) {
974  if (this.scale_ymin <= -90 || this.scale_ymax >=90) {
975  console.warn("Mercator Projection", "Latitude out of range", this.scale_ymin, this.scale_ymax);
976  this.options.Proj = 0;
977  return;
978  }
979  pnts.push(this.ProjectMercator2xy(this.scale_xmin, this.scale_ymin));
980  pnts.push(this.ProjectMercator2xy(this.scale_xmax, this.scale_ymax));
981 
982  } else if (Proj == 3) {
983  pnts.push(this.ProjectSinusoidal2xy(this.scale_xmin, this.scale_ymin));
984  pnts.push(this.ProjectSinusoidal2xy(this.scale_xmin, this.scale_ymax));
985  pnts.push(this.ProjectSinusoidal2xy(this.scale_xmax, this.scale_ymax));
986  pnts.push(this.ProjectSinusoidal2xy(this.scale_xmax, this.scale_ymin));
987  if (this.scale_ymin<0 && this.scale_ymax>0) {
988  pnts.push(this.ProjectSinusoidal2xy(this.scale_xmin, 0));
989  pnts.push(this.ProjectSinusoidal2xy(this.scale_xmax, 0));
990  }
991  if (this.scale_xmin<0 && this.scale_xmax>0) {
992  pnts.push(this.ProjectSinusoidal2xy(0, this.scale_ymin));
993  pnts.push(this.ProjectSinusoidal2xy(0, this.scale_ymax));
994  }
995  } else if (Proj == 4) {
996  pnts.push(this.ProjectParabolic2xy(this.scale_xmin, this.scale_ymin));
997  pnts.push(this.ProjectParabolic2xy(this.scale_xmin, this.scale_ymax));
998  pnts.push(this.ProjectParabolic2xy(this.scale_xmax, this.scale_ymax));
999  pnts.push(this.ProjectParabolic2xy(this.scale_xmax, this.scale_ymin));
1000  if (this.scale_ymin<0 && this.scale_ymax>0) {
1001  pnts.push(this.ProjectParabolic2xy(this.scale_xmin, 0));
1002  pnts.push(this.ProjectParabolic2xy(this.scale_xmax, 0));
1003  }
1004  if (this.scale_xmin<0 && this.scale_xmax>0) {
1005  pnts.push(this.ProjectParabolic2xy(0, this.scale_ymin));
1006  pnts.push(this.ProjectParabolic2xy(0, this.scale_ymax));
1007  }
1008  }
1009 
1010  this.original_xmin = this.scale_xmin;
1011  this.original_xmax = this.scale_xmax;
1012  this.original_ymin = this.scale_ymin;
1013  this.original_ymax = this.scale_ymax;
1014 
1015  this.scale_xmin = this.scale_xmax = pnts[0].x;
1016  this.scale_ymin = this.scale_ymax = pnts[0].y;
1017 
1018  for (var n=1;n<pnts.length;++n) {
1019  this.scale_xmin = Math.min(this.scale_xmin, pnts[n].x);
1020  this.scale_xmax = Math.max(this.scale_xmax, pnts[n].x);
1021  this.scale_ymin = Math.min(this.scale_ymin, pnts[n].y);
1022  this.scale_ymax = Math.max(this.scale_ymax, pnts[n].y);
1023  }
1024  }
1025 
1026  TFramePainter.prototype.DrawGrids = function() {
1027  // grid can only be drawn by first painter
1028 
1029  var layer = this.svg_frame().select(".grid_layer");
1030 
1031  layer.selectAll(".xgrid").remove();
1032  layer.selectAll(".ygrid").remove();
1033 
1034  var h = this.frame_height(),
1035  w = this.frame_width(),
1036  grid, grid_style = JSROOT.gStyle.fGridStyle,
1037  grid_color = (JSROOT.gStyle.fGridColor > 0) ? this.get_color(JSROOT.gStyle.fGridColor) : "black";
1038 
1039  if ((grid_style < 0) || (grid_style >= JSROOT.Painter.root_line_styles.length)) grid_style = 11;
1040 
1041  // add a grid on x axis, if the option is set
1042  if (this.x_handle) {
1043  grid = "";
1044  for (var n=0;n<this.x_handle.ticks.length;++n)
1045  if (this.swap_xy)
1046  grid += "M0,"+this.x_handle.ticks[n]+"h"+w;
1047  else
1048  grid += "M"+this.x_handle.ticks[n]+",0v"+h;
1049 
1050  if (grid.length > 0)
1051  layer.append("svg:path")
1052  .attr("class", "xgrid")
1053  .attr("d", grid)
1054  .style('stroke',grid_color).style("stroke-width",JSROOT.gStyle.fGridWidth)
1055  .style("stroke-dasharray", JSROOT.Painter.root_line_styles[grid_style]);
1056  }
1057 
1058  // add a grid on y axis, if the option is set
1059  if (this.y_handle) {
1060  grid = "";
1061  for (var n=0;n<this.y_handle.ticks.length;++n)
1062  if (this.swap_xy)
1063  grid += "M"+this.y_handle.ticks[n]+",0v"+h;
1064  else
1065  grid += "M0,"+this.y_handle.ticks[n]+"h"+w;
1066 
1067  if (grid.length > 0)
1068  layer.append("svg:path")
1069  .attr("class", "ygrid")
1070  .attr("d", grid)
1071  .style('stroke',grid_color).style("stroke-width",JSROOT.gStyle.fGridWidth)
1072  .style("stroke-dasharray", JSROOT.Painter.root_line_styles[grid_style]);
1073  }
1074  }
1075 
1076  TFramePainter.prototype.AxisAsText = function(axis, value) {
1077  if (axis == "x") {
1078  if (this.x_kind == 'time')
1079  value = this.ConvertX(value);
1080  if (this.x_handle && ('format' in this.x_handle))
1081  return this.x_handle.format(value, false, JSROOT.gStyle.XValuesFormat);
1082  } else if (axis == "y") {
1083  if (this.y_kind == 'time')
1084  value = this.ConvertY(value);
1085  if (this.y_handle && ('format' in this.y_handle))
1086  return this.y_handle.format(value, false, JSROOT.gStyle.YValuesFormat);
1087  } else {
1088  if (this.z_handle && ('format' in this.z_handle))
1089  return this.z_handle.format(value, false, JSROOT.gStyle.ZValuesFormat);
1090  }
1091 
1092  return value.toPrecision(4);
1093  }
1094 
1095  TFramePainter.prototype.SetAxesRanges = function(xmin, xmax, ymin, ymax) {
1096  if (this.axes_drawn) return;
1097 
1098  if ((this.xmin == this.xmax) && (xmin!==xmax)) {
1099  this.xmin = xmin;
1100  this.xmax = xmax;
1101  }
1102  if ((this.ymin == this.ymax) && (ymin!==ymax)) {
1103  this.ymin = ymin;
1104  this.ymax = ymax;
1105  }
1106  }
1107 
1108  TFramePainter.prototype.DrawAxes = function(shrink_forbidden) {
1109  // axes can be drawn only for main histogram
1110 
1111  if (this.axes_drawn) return true;
1112 
1113  if ((this.xmin==this.xmax) || (this.ymin==this.ymax)) return false;
1114 
1115  this.CleanupAxes();
1116  this.CleanXY();
1117 
1118  this.CreateXY();
1119 
1120  var layer = this.svg_frame().select(".axis_layer"),
1121  w = this.frame_width(),
1122  h = this.frame_height(),
1123  axisx = JSROOT.Create("TAxis"), // temporary object for different attributes
1124  axisy = JSROOT.Create("TAxis");
1125 
1126  this.x_handle = new JSROOT.TAxisPainter(axisx, true);
1127  this.x_handle.SetDivId(this.divid, -1);
1128  this.x_handle.pad_name = this.pad_name;
1129 
1130  this.x_handle.SetAxisConfig("xaxis",
1131  (this.logx && (this.x_kind !== "time")) ? "log" : this.x_kind,
1132  this.x, this.xmin, this.xmax, this.scale_xmin, this.scale_xmax);
1133  this.x_handle.invert_side = false;
1134  this.x_handle.lbls_both_sides = false;
1135  this.x_handle.has_obstacle = false;
1136 
1137  this.y_handle = new JSROOT.TAxisPainter(axisy, true);
1138  this.y_handle.SetDivId(this.divid, -1);
1139  this.y_handle.pad_name = this.pad_name;
1140 
1141  this.y_handle.SetAxisConfig("yaxis",
1142  (this.logy && this.y_kind !== "time") ? "log" : this.y_kind,
1143  this.y, this.ymin, this.ymax, this.scale_ymin, this.scale_ymax);
1144  this.y_handle.invert_side = false; // ((this.options.AxisPos % 10) === 1) || (pad.fTicky > 1);
1145  this.y_handle.lbls_both_sides = false;
1146 
1147  var draw_horiz = this.swap_xy ? this.y_handle : this.x_handle,
1148  draw_vertical = this.swap_xy ? this.x_handle : this.y_handle,
1149  disable_axis_draw = false, show_second_ticks = false;
1150 
1151  if (!disable_axis_draw) {
1152  var pp = this.pad_painter();
1153  if (pp && pp._fast_drawing) disable_axis_draw = true;
1154  }
1155 
1156  if (!disable_axis_draw) {
1157  draw_horiz.DrawAxis(false, layer, w, h,
1158  draw_horiz.invert_side ? undefined : "translate(0," + h + ")",
1159  false, show_second_ticks ? -h : 0, disable_axis_draw);
1160 
1161  draw_vertical.DrawAxis(true, layer, w, h,
1162  draw_vertical.invert_side ? "translate(" + w + ",0)" : undefined,
1163  false, show_second_ticks ? w : 0, disable_axis_draw,
1164  draw_vertical.invert_side ? 0 : this.frame_x());
1165 
1166  this.DrawGrids();
1167  }
1168 
1169  if (!shrink_forbidden && JSROOT.gStyle.CanAdjustFrame && !disable_axis_draw) {
1170 
1171  var shrink = 0., ypos = draw_vertical.position;
1172 
1173  if ((-0.2*w < ypos) && (ypos < 0)) {
1174  shrink = -ypos/w + 0.001;
1175  this.shrink_frame_left += shrink;
1176  } else if ((ypos>0) && (ypos<0.3*w) && (this.shrink_frame_left > 0) && (ypos/w > this.shrink_frame_left)) {
1177  shrink = -this.shrink_frame_left;
1178  this.shrink_frame_left = 0.;
1179  }
1180 
1181  if (shrink != 0) {
1182  this.Shrink(shrink, 0);
1183  this.Redraw();
1184  this.DrawAxes(true);
1185  }
1186  }
1187 
1188  this.axes_drawn = true;
1189 
1190  return true;
1191  }
1192 
1193  TFramePainter.prototype.SizeChanged = function() {
1194  // function called at the end of resize of frame
1195  // One should apply changes to the pad
1196 
1197  /* var pad = this.root_pad();
1198 
1199  if (pad) {
1200  pad.fLeftMargin = this.fX1NDC;
1201  pad.fRightMargin = 1 - this.fX2NDC;
1202  pad.fBottomMargin = this.fY1NDC;
1203  pad.fTopMargin = 1 - this.fY2NDC;
1204  this.SetRootPadRange(pad);
1205  }
1206  */
1207 
1208  this.RedrawPad();
1209  }
1210 
1211  TFramePainter.prototype.CleanXY = function() {
1212  // remove all kinds of X/Y function for axes transformation
1213  delete this.x; delete this.grx;
1214  delete this.ConvertX; delete this.RevertX;
1215  delete this.y; delete this.gry;
1216  delete this.ConvertY; delete this.RevertY;
1217  delete this.z; delete this.grz;
1218  }
1219 
1220  TFramePainter.prototype.CleanupAxes = function() {
1221  // remove all axes drawings
1222  if (this.x_handle) {
1223  this.x_handle.Cleanup();
1224  delete this.x_handle;
1225  }
1226 
1227  if (this.y_handle) {
1228  this.y_handle.Cleanup();
1229  delete this.y_handle;
1230  }
1231 
1232  if (this.z_handle) {
1233  this.z_handle.Cleanup();
1234  delete this.z_handle;
1235  }
1236  if (this.draw_g) {
1237  this.draw_g.select(".grid_layer").selectAll("*").remove();
1238  this.draw_g.select(".axis_layer").selectAll("*").remove();
1239  }
1240  this.axes_drawn = false;
1241  }
1242 
1244  TFramePainter.prototype.CleanFrameDrawings = function() {
1245  // cleanup all 3D drawings if any
1246  if (typeof this.Create3DScene === 'function')
1247  this.Create3DScene(-1);
1248 
1249  this.CleanupAxes();
1250  this.CleanXY();
1251 
1252  this.xmin = this.xmax = 0;
1253  this.ymin = this.ymax = 0;
1254  this.zmin = this.zmax = 0;
1255 
1256  this.zoom_xmin = this.zoom_xmax = 0;
1257  this.zoom_ymin = this.zoom_ymax = 0;
1258  this.zoom_zmin = this.zoom_zmax = 0;
1259 
1260  this.scale_xmin = this.scale_xmax = 0;
1261  this.scale_ymin = this.scale_ymax = 0;
1262  this.scale_zmin = this.scale_zmax = 0;
1263 
1264  if (this.draw_g) {
1265  this.draw_g.select(".main_layer").selectAll("*").remove();
1266  this.draw_g.select(".upper_layer").selectAll("*").remove();
1267  }
1268  }
1269 
1270  TFramePainter.prototype.Cleanup = function() {
1271 
1272  this.CleanFrameDrawings();
1273 
1274  if (this.draw_g) {
1275  this.draw_g.selectAll("*").remove();
1276  this.draw_g.on("mousedown", null)
1277  .on("dblclick", null)
1278  .on("wheel", null)
1279  .on("contextmenu", null)
1280  .property('interactive_set', null);
1281  }
1282 
1283  if (this.keys_handler) {
1284  window.removeEventListener('keydown', this.keys_handler, false);
1285  this.keys_handler = null;
1286  }
1287 
1288  this.draw_g = null;
1289  delete this._click_handler;
1290  delete this._dblclick_handler;
1291 
1292  JSROOT.TooltipHandler.prototype.Cleanup.call(this);
1293  }
1294 
1295  TFramePainter.prototype.Redraw = function() {
1296 
1297  var pp = this.pad_painter();
1298  if (pp) pp.frame_painter_ref = this;
1299 
1300  if (this.mode3d) return;
1301 
1302  // first update all attributes from objects
1303  this.UpdateAttributes();
1304 
1305  var width = this.pad_width(),
1306  height = this.pad_height(),
1307  lm = Math.round(width * this.fX1NDC),
1308  w = Math.round(width * (this.fX2NDC - this.fX1NDC)),
1309  tm = Math.round(height * (1 - this.fY2NDC)),
1310  h = Math.round(height * (this.fY2NDC - this.fY1NDC)),
1311  rotate = false, fixpos = false;
1312 
1313  if (pp && pp.options) {
1314  if (pp.options.RotateFrame) rotate = true;
1315  if (pp.options.FixFrame) fixpos = true;
1316  }
1317 
1318  // this is svg:g object - container for every other items belonging to frame
1319  this.draw_g = this.svg_layer("primitives_layer").select(".root_frame");
1320 
1321  var top_rect, main_svg;
1322 
1323  if (this.draw_g.empty()) {
1324 
1325  var layer = this.svg_layer("primitives_layer");
1326 
1327  this.draw_g = layer.append("svg:g").attr("class", "root_frame");
1328 
1329  this.draw_g.append("svg:title").text("");
1330 
1331  top_rect = this.draw_g.append("svg:rect");
1332 
1333  // append for the moment three layers - for drawing and axis
1334  this.draw_g.append('svg:g').attr('class','grid_layer');
1335 
1336  main_svg = this.draw_g.append('svg:svg')
1337  .attr('class','main_layer')
1338  .attr("x", 0)
1339  .attr("y", 0)
1340  .attr('overflow', 'hidden');
1341 
1342  this.draw_g.append('svg:g').attr('class','axis_layer');
1343  this.draw_g.append('svg:g').attr('class','upper_layer');
1344  } else {
1345  top_rect = this.draw_g.select("rect");
1346  main_svg = this.draw_g.select(".main_layer");
1347  }
1348 
1349  this.axes_drawn = false;
1350 
1351  var trans = "translate(" + lm + "," + tm + ")";
1352  if (rotate) {
1353  trans += " rotate(-90) " + "translate(" + -h + ",0)";
1354  var d = w; w = h; h = d;
1355  }
1356 
1357  this._frame_x = lm;
1358  this._frame_y = tm;
1359  this._frame_width = w;
1360  this._frame_height = h;
1361 
1362  this.draw_g.attr("transform", trans);
1363 
1364  top_rect.attr("x", 0)
1365  .attr("y", 0)
1366  .attr("width", w)
1367  .attr("height", h)
1368  .call(this.fillatt.func)
1369  .call(this.lineatt.func);
1370 
1371  main_svg.attr("width", w)
1372  .attr("height", h)
1373  .attr("viewBox", "0 0 " + w + " " + h);
1374 
1375  // var tooltip_rect = this.draw_g.select(".interactive_rect");
1376  // if (JSROOT.BatchMode) return tooltip_rect.remove();
1377  if (JSROOT.BatchMode) return;
1378 
1379  this.draw_g.attr("x", lm)
1380  .attr("y", tm)
1381  .attr("width", w)
1382  .attr("height", h);
1383 
1384  if (!rotate && !fixpos)
1385  this.AddDrag({ obj: this, only_resize: true, minwidth: 20, minheight: 20,
1386  redraw: this.SizeChanged.bind(this) });
1387 
1388  var tooltip_rect = main_svg;
1389  tooltip_rect.style("pointer-events","visibleFill")
1390  .property('handlers_set', 0);
1391 
1392  //if (tooltip_rect.empty())
1393  // tooltip_rect =
1394  // this.draw_g
1395  // .append("rect")
1396  // .attr("class","interactive_rect")
1397  // .style('opacity',0)
1398  // .style('fill',"none")
1399  // .style("pointer-events","visibleFill")
1400  // .property('handlers_set', 0);
1401 
1402  var handlers_set = (pp && pp._fast_drawing) ? 0 : 1;
1403 
1404  if (tooltip_rect.property('handlers_set') != handlers_set) {
1405  var close_handler = handlers_set ? this.ProcessTooltipEvent.bind(this, null) : null,
1406  mouse_handler = handlers_set ? this.ProcessTooltipEvent.bind(this, { handler: true, touch: false }) : null;
1407 
1408  tooltip_rect.property('handlers_set', handlers_set)
1409  .on('mouseenter', mouse_handler)
1410  .on('mousemove', mouse_handler)
1411  .on('mouseleave', close_handler);
1412 
1413  if (JSROOT.touches) {
1414  var touch_handler = handlers_set ? this.ProcessTooltipEvent.bind(this, { handler: true, touch: true }) : null;
1415 
1416  tooltip_rect.on("touchstart", touch_handler)
1417  .on("touchmove", touch_handler)
1418  .on("touchend", close_handler)
1419  .on("touchcancel", close_handler);
1420  }
1421  }
1422 
1423  tooltip_rect.attr("x", 0)
1424  .attr("y", 0)
1425  .attr("width", w)
1426  .attr("height", h);
1427 
1428  var hintsg = this.hints_layer().select(".objects_hints");
1429  // if tooltips were visible before, try to reconstruct them after short timeout
1430  if (!hintsg.empty() && this.IsTooltipAllowed() && (hintsg.property("hints_pad") == this.pad_name))
1431  setTimeout(this.ProcessTooltipEvent.bind(this, hintsg.property('last_point')), 10);
1432  }
1433 
1435  TFramePainter.prototype.GetFrameRect = function() {
1436  return {
1437  x: this.frame_x(),
1438  y: this.frame_y(),
1439  width: this.frame_width(),
1440  height: this.frame_height(),
1441  transform: this.draw_g ? this.draw_g.attr("transform") : "",
1442  hint_delta_x: 0,
1443  hint_delta_y: 0
1444  }
1445  }
1446 
1449  TFramePainter.prototype.ProcessFrameClick = function(pnt, dblckick) {
1450 
1451  var pp = this.pad_painter();
1452  if (!pp) return;
1453 
1454  pnt.painters = true; // provide painters reference in the hints
1455  pnt.disabled = true; // do not invoke graphics
1456 
1457  // collect tooltips from pad painter - it has list of all drawn objects
1458  var hints = pp.GetTooltips(pnt), exact = null;
1459  for (var k=0; (k<hints.length) && !exact; ++k)
1460  if (hints[k] && hints[k].exact) exact = hints[k];
1461  //if (exact) console.log('Click exact', pnt, exact.painter.GetTipName());
1462  // else console.log('Click frame', pnt);
1463 
1464  var res;
1465 
1466  if (exact) {
1467  var handler = dblckick ? this._dblclick_handler : this._click_handler;
1468  if (handler) res = handler(exact.user_info, pnt);
1469  }
1470 
1471  if (!dblckick)
1472  pp.SelectObjectPainter(exact ? exact.painter : this,
1473  { x: pnt.x + (this._frame_x || 0), y: pnt.y + (this._frame_y || 0) });
1474 
1475  return res;
1476  }
1477 
1478  TFramePainter.prototype.ConfigureUserClickHandler = function(handler) {
1479  this._click_handler = handler && (typeof handler == 'function') ? handler : null;
1480  }
1481 
1482  TFramePainter.prototype.ConfigureUserDblclickHandler = function(handler) {
1483  this._dblclick_handler = handler && (typeof handler == 'function') ? handler : null;
1484  }
1485 
1486  TFramePainter.prototype.Zoom = function(xmin, xmax, ymin, ymax, zmin, zmax) {
1487  // function can be used for zooming into specified range
1488  // if both limits for each axis 0 (like xmin==xmax==0), axis will be unzoomed
1489 
1490  // disable zooming when axis conversion is enabled
1491  if (this.options && this.options.Proj) return false;
1492 
1493  if (xmin==="x") { xmin = xmax; xmax = ymin; ymin = undefined; } else
1494  if (xmin==="y") { ymax = ymin; ymin = xmax; xmin = xmax = undefined; } else
1495  if (xmin==="z") { zmin = xmax; zmax = ymin; xmin = xmax = ymin = undefined; }
1496 
1497  var zoom_x = (xmin !== xmax), zoom_y = (ymin !== ymax), zoom_z = (zmin !== zmax),
1498  unzoom_x = false, unzoom_y = false, unzoom_z = false;
1499 
1500  if (zoom_x) {
1501  var cnt = 0;
1502  if (xmin <= this.xmin) { xmin = this.xmin; cnt++; }
1503  if (xmax >= this.xmax) { xmax = this.xmax; cnt++; }
1504  if (cnt === 2) { zoom_x = false; unzoom_x = true; }
1505  } else {
1506  unzoom_x = (xmin === xmax) && (xmin === 0);
1507  }
1508 
1509  if (zoom_y) {
1510  var cnt = 0;
1511  if (ymin <= this.ymin) { ymin = this.ymin; cnt++; }
1512  if (ymax >= this.ymax) { ymax = this.ymax; cnt++; }
1513  if (cnt === 2) { zoom_y = false; unzoom_y = true; }
1514  } else {
1515  unzoom_y = (ymin === ymax) && (ymin === 0);
1516  }
1517 
1518  if (zoom_z) {
1519  var cnt = 0;
1520  // if (this.logz && this.ymin_nz && this.Dimension()===2) main_zmin = 0.3*this.ymin_nz;
1521  if (zmin <= this.zmin) { zmin = this.zmin; cnt++; }
1522  if (zmax >= this.zmax) { zmax = this.zmax; cnt++; }
1523  if (cnt === 2) { zoom_z = false; unzoom_z = true; }
1524  } else {
1525  unzoom_z = (zmin === zmax) && (zmin === 0);
1526  }
1527 
1528  var changed = false, fp = this;
1529 
1530  // first process zooming (if any)
1531  if (zoom_x || zoom_y || zoom_z)
1532  this.ForEachPainter(function(obj) {
1533  if (zoom_x && obj.CanZoomIn("x", xmin, xmax)) {
1534  fp.zoom_xmin = xmin;
1535  fp.zoom_xmax = xmax;
1536  changed = true;
1537  zoom_x = false;
1538  }
1539  if (zoom_y && obj.CanZoomIn("y", ymin, ymax)) {
1540  fp.zoom_ymin = ymin;
1541  fp.zoom_ymax = ymax;
1542  changed = true;
1543  zoom_y = false;
1544  }
1545  if (zoom_z && obj.CanZoomIn("z", zmin, zmax)) {
1546  fp.zoom_zmin = zmin;
1547  fp.zoom_zmax = zmax;
1548  changed = true;
1549  zoom_z = false;
1550  }
1551  });
1552 
1553  // and process unzoom, if any
1554  if (unzoom_x || unzoom_y || unzoom_z) {
1555  if (unzoom_x) {
1556  if (this.zoom_xmin !== this.zoom_xmax) changed = true;
1557  this.zoom_xmin = this.zoom_xmax = 0;
1558  }
1559  if (unzoom_y) {
1560  if (this.zoom_ymin !== this.zoom_ymax) changed = true;
1561  this.zoom_ymin = this.zoom_ymax = 0;
1562  }
1563  if (unzoom_z) {
1564  if (this.zoom_zmin !== this.zoom_zmax) changed = true;
1565  this.zoom_zmin = this.zoom_zmax = 0;
1566  }
1567  }
1568 
1569  if (changed) this.RedrawPad();
1570 
1571  return changed;
1572  }
1573 
1574  TFramePainter.prototype.IsAxisZoomed = function(axis) {
1575  return this['zoom_'+axis+'min'] !== this['zoom_'+axis+'max'];
1576  }
1577 
1578  TFramePainter.prototype.Unzoom = function(dox, doy, doz) {
1579  if (typeof dox === 'undefined') { dox = true; doy = true; doz = true; } else
1580  if (typeof dox === 'string') { doz = dox.indexOf("z")>=0; doy = dox.indexOf("y")>=0; dox = dox.indexOf("x")>=0; }
1581 
1582  var last = this.zoom_changed_interactive;
1583 
1584  if (dox || doy || doz) this.zoom_changed_interactive = 2;
1585 
1586  var changed = this.Zoom(dox ? 0 : undefined, dox ? 0 : undefined,
1587  doy ? 0 : undefined, doy ? 0 : undefined,
1588  doz ? 0 : undefined, doz ? 0 : undefined);
1589 
1590  // if unzooming has no effect, decrease counter
1591  if ((dox || doy || doz) && !changed)
1592  this.zoom_changed_interactive = (!isNaN(last) && (last>0)) ? last - 1 : 0;
1593 
1594  return changed;
1595 
1596  }
1597 
1598  TFramePainter.prototype.clearInteractiveElements = function() {
1599  JSROOT.Painter.closeMenu();
1600  if (this.zoom_rect) { this.zoom_rect.remove(); this.zoom_rect = null; }
1601  this.zoom_kind = 0;
1602 
1603  // enable tooltip in frame painter
1604  this.SwitchTooltip(true);
1605  }
1606 
1607  TFramePainter.prototype.mouseDoubleClick = function() {
1608  d3.event.preventDefault();
1609  var m = d3.mouse(this.svg_frame().node());
1610  this.clearInteractiveElements();
1611 
1612  var valid_x = (m[0] >= 0) && (m[0] <= this.frame_width()),
1613  valid_y = (m[1] >= 0) && (m[1] <= this.frame_height());
1614 
1615  if (valid_x && valid_y && this._dblclick_handler)
1616  if (this.ProcessFrameClick({ x: m[0], y: m[1] }, true)) return;
1617 
1618  var kind = "xyz";
1619  if (!valid_x) kind = this.swap_xy ? "x" : "y"; else
1620  if (!valid_y) kind = this.swap_xy ? "y" : "x";
1621  if (this.Unzoom(kind)) return;
1622  }
1623 
1624  TFramePainter.prototype.startRectSel = function() {
1625  // ignore when touch selection is activated
1626 
1627  if (this.zoom_kind > 100) return;
1628 
1629  // ignore all events from non-left button
1630  if ((d3.event.which || d3.event.button) !== 1) return;
1631 
1632  d3.event.preventDefault();
1633 
1634  var pos = d3.mouse(this.svg_frame().node());
1635 
1636  this.clearInteractiveElements();
1637  this.zoom_origin = pos;
1638 
1639  var w = this.frame_width(), h = this.frame_height();
1640 
1641  this.zoom_curr = [ Math.max(0, Math.min(w, this.zoom_origin[0])),
1642  Math.max(0, Math.min(h, this.zoom_origin[1])) ];
1643 
1644  if ((this.zoom_origin[0] < 0) || (this.zoom_origin[0] > w)) {
1645  this.zoom_kind = 3; // only y
1646  this.zoom_origin[0] = 0;
1647  this.zoom_origin[1] = this.zoom_curr[1];
1648  this.zoom_curr[0] = w;
1649  this.zoom_curr[1] += 1;
1650  } else if ((this.zoom_origin[1] < 0) || (this.zoom_origin[1] > h)) {
1651  this.zoom_kind = 2; // only x
1652  this.zoom_origin[0] = this.zoom_curr[0];
1653  this.zoom_origin[1] = 0;
1654  this.zoom_curr[0] += 1;
1655  this.zoom_curr[1] = h;
1656  } else {
1657  this.zoom_kind = 1; // x and y
1658  this.zoom_origin[0] = this.zoom_curr[0];
1659  this.zoom_origin[1] = this.zoom_curr[1];
1660  }
1661 
1662  d3.select(window).on("mousemove.zoomRect", this.moveRectSel.bind(this))
1663  .on("mouseup.zoomRect", this.endRectSel.bind(this), true);
1664 
1665  this.zoom_rect = null;
1666 
1667  // disable tooltips in frame painter
1668  this.SwitchTooltip(false);
1669 
1670  d3.event.stopPropagation();
1671  }
1672 
1673  TFramePainter.prototype.moveRectSel = function() {
1674 
1675  if ((this.zoom_kind == 0) || (this.zoom_kind > 100)) return;
1676 
1677  d3.event.preventDefault();
1678  var m = d3.mouse(this.svg_frame().node());
1679 
1680  m[0] = Math.max(0, Math.min(this.frame_width(), m[0]));
1681  m[1] = Math.max(0, Math.min(this.frame_height(), m[1]));
1682 
1683  switch (this.zoom_kind) {
1684  case 1: this.zoom_curr[0] = m[0]; this.zoom_curr[1] = m[1]; break;
1685  case 2: this.zoom_curr[0] = m[0]; break;
1686  case 3: this.zoom_curr[1] = m[1]; break;
1687  }
1688 
1689  if (this.zoom_rect===null)
1690  this.zoom_rect = this.svg_frame()
1691  .append("rect")
1692  .attr("class", "zoom")
1693  .attr("pointer-events","none");
1694 
1695  this.zoom_rect.attr("x", Math.min(this.zoom_origin[0], this.zoom_curr[0]))
1696  .attr("y", Math.min(this.zoom_origin[1], this.zoom_curr[1]))
1697  .attr("width", Math.abs(this.zoom_curr[0] - this.zoom_origin[0]))
1698  .attr("height", Math.abs(this.zoom_curr[1] - this.zoom_origin[1]));
1699  }
1700 
1701  TFramePainter.prototype.endRectSel = function() {
1702  if ((this.zoom_kind == 0) || (this.zoom_kind > 100)) return;
1703 
1704  d3.event.preventDefault();
1705 
1706  d3.select(window).on("mousemove.zoomRect", null)
1707  .on("mouseup.zoomRect", null);
1708 
1709  var m = d3.mouse(this.svg_frame().node()), changed = [true, true];
1710  m[0] = Math.max(0, Math.min(this.frame_width(), m[0]));
1711  m[1] = Math.max(0, Math.min(this.frame_height(), m[1]));
1712 
1713  switch (this.zoom_kind) {
1714  case 1: this.zoom_curr[0] = m[0]; this.zoom_curr[1] = m[1]; break;
1715  case 2: this.zoom_curr[0] = m[0]; changed[1] = false; break; // only X
1716  case 3: this.zoom_curr[1] = m[1]; changed[0] = false; break; // only Y
1717  }
1718 
1719  var xmin, xmax, ymin, ymax, isany = false,
1720  idx = this.swap_xy ? 1 : 0, idy = 1 - idx;
1721 
1722  if (changed[idx] && (Math.abs(this.zoom_curr[idx] - this.zoom_origin[idx]) > 10)) {
1723  xmin = Math.min(this.RevertX(this.zoom_origin[idx]), this.RevertX(this.zoom_curr[idx]));
1724  xmax = Math.max(this.RevertX(this.zoom_origin[idx]), this.RevertX(this.zoom_curr[idx]));
1725  isany = true;
1726  }
1727 
1728  if (changed[idy] && (Math.abs(this.zoom_curr[idy] - this.zoom_origin[idy]) > 10)) {
1729  ymin = Math.min(this.RevertY(this.zoom_origin[idy]), this.RevertY(this.zoom_curr[idy]));
1730  ymax = Math.max(this.RevertY(this.zoom_origin[idy]), this.RevertY(this.zoom_curr[idy]));
1731  isany = true;
1732  }
1733 
1734  var kind = this.zoom_kind, pnt = (kind===1) ? { x: this.zoom_origin[0], y: this.zoom_origin[1] } : null;
1735 
1736  this.clearInteractiveElements();
1737 
1738  if (isany) {
1739  this.zoom_changed_interactive = 2;
1740  this.Zoom(xmin, xmax, ymin, ymax);
1741  } else {
1742  switch (kind) {
1743  case 1:
1744  var fp = this.frame_painter();
1745  if (fp) fp.ProcessFrameClick(pnt);
1746  break;
1747  case 2:
1748  var pp = this.pad_painter();
1749  if (pp) pp.SelectObjectPainter(this.x_handle);
1750  break;
1751  case 3:
1752  var pp = this.pad_painter();
1753  if (pp) pp.SelectObjectPainter(this.y_handle);
1754  break;
1755  }
1756  }
1757 
1758  this.zoom_kind = 0;
1759  }
1760 
1761  TFramePainter.prototype.startTouchZoom = function() {
1762  // in case when zooming was started, block any other kind of events
1763  if (this.zoom_kind != 0) {
1764  d3.event.preventDefault();
1765  d3.event.stopPropagation();
1766  return;
1767  }
1768 
1769  var arr = d3.touches(this.svg_frame().node());
1770  this.touch_cnt+=1;
1771 
1772  // normally double-touch will be handled
1773  // touch with single click used for context menu
1774  if (arr.length == 1) {
1775  // this is touch with single element
1776 
1777  var now = new Date(), diff = now.getTime() - this.last_touch.getTime();
1778  this.last_touch = now;
1779 
1780  if ((diff < 300) && this.zoom_curr
1781  && (Math.abs(this.zoom_curr[0] - arr[0][0]) < 30)
1782  && (Math.abs(this.zoom_curr[1] - arr[0][1]) < 30)) {
1783 
1784  d3.event.preventDefault();
1785  d3.event.stopPropagation();
1786 
1787  this.clearInteractiveElements();
1788  this.Unzoom("xyz");
1789 
1790  this.last_touch = new Date(0);
1791 
1792  this.svg_frame().on("touchcancel", null)
1793  .on("touchend", null, true);
1794  } else
1795  if (JSROOT.gStyle.ContextMenu) {
1796  this.zoom_curr = arr[0];
1797  this.svg_frame().on("touchcancel", this.endTouchSel.bind(this))
1798  .on("touchend", this.endTouchSel.bind(this));
1799  d3.event.preventDefault();
1800  d3.event.stopPropagation();
1801  }
1802  }
1803 
1804  if ((arr.length != 2) || !JSROOT.gStyle.Zooming || !JSROOT.gStyle.ZoomTouch) return;
1805 
1806  d3.event.preventDefault();
1807  d3.event.stopPropagation();
1808 
1809  this.clearInteractiveElements();
1810 
1811  this.svg_frame().on("touchcancel", null)
1812  .on("touchend", null);
1813 
1814  var pnt1 = arr[0], pnt2 = arr[1], w = this.frame_width(), h = this.frame_height();
1815 
1816  this.zoom_curr = [ Math.min(pnt1[0], pnt2[0]), Math.min(pnt1[1], pnt2[1]) ];
1817  this.zoom_origin = [ Math.max(pnt1[0], pnt2[0]), Math.max(pnt1[1], pnt2[1]) ];
1818 
1819  if ((this.zoom_curr[0] < 0) || (this.zoom_curr[0] > w)) {
1820  this.zoom_kind = 103; // only y
1821  this.zoom_curr[0] = 0;
1822  this.zoom_origin[0] = w;
1823  } else if ((this.zoom_origin[1] > h) || (this.zoom_origin[1] < 0)) {
1824  this.zoom_kind = 102; // only x
1825  this.zoom_curr[1] = 0;
1826  this.zoom_origin[1] = h;
1827  } else {
1828  this.zoom_kind = 101; // x and y
1829  }
1830 
1831  this.SwitchTooltip(false);
1832 
1833  this.zoom_rect = this.svg_frame().append("rect")
1834  .attr("class", "zoom")
1835  .attr("id", "zoomRect")
1836  .attr("x", this.zoom_curr[0])
1837  .attr("y", this.zoom_curr[1])
1838  .attr("width", this.zoom_origin[0] - this.zoom_curr[0])
1839  .attr("height", this.zoom_origin[1] - this.zoom_curr[1]);
1840 
1841  d3.select(window).on("touchmove.zoomRect", this.moveTouchSel.bind(this))
1842  .on("touchcancel.zoomRect", this.endTouchSel.bind(this))
1843  .on("touchend.zoomRect", this.endTouchSel.bind(this));
1844  }
1845 
1846  TFramePainter.prototype.moveTouchSel = function() {
1847  if (this.zoom_kind < 100) return;
1848 
1849  d3.event.preventDefault();
1850 
1851  var arr = d3.touches(this.svg_frame().node());
1852 
1853  if (arr.length != 2)
1854  return this.clearInteractiveElements();
1855 
1856  var pnt1 = arr[0], pnt2 = arr[1];
1857 
1858  if (this.zoom_kind != 103) {
1859  this.zoom_curr[0] = Math.min(pnt1[0], pnt2[0]);
1860  this.zoom_origin[0] = Math.max(pnt1[0], pnt2[0]);
1861  }
1862  if (this.zoom_kind != 102) {
1863  this.zoom_curr[1] = Math.min(pnt1[1], pnt2[1]);
1864  this.zoom_origin[1] = Math.max(pnt1[1], pnt2[1]);
1865  }
1866 
1867  this.zoom_rect.attr("x", this.zoom_curr[0])
1868  .attr("y", this.zoom_curr[1])
1869  .attr("width", this.zoom_origin[0] - this.zoom_curr[0])
1870  .attr("height", this.zoom_origin[1] - this.zoom_curr[1]);
1871 
1872  if ((this.zoom_origin[0] - this.zoom_curr[0] > 10)
1873  || (this.zoom_origin[1] - this.zoom_curr[1] > 10))
1874  this.SwitchTooltip(false);
1875 
1876  d3.event.stopPropagation();
1877  }
1878 
1879  TFramePainter.prototype.endTouchSel = function() {
1880 
1881  this.svg_frame().on("touchcancel", null)
1882  .on("touchend", null);
1883 
1884  if (this.zoom_kind === 0) {
1885  // special case - single touch can ends up with context menu
1886 
1887  d3.event.preventDefault();
1888 
1889  var now = new Date();
1890 
1891  var diff = now.getTime() - this.last_touch.getTime();
1892 
1893  if ((diff > 500) && (diff<2000) && !this.frame_painter().IsTooltipShown()) {
1894  this.ShowContextMenu('main', { clientX: this.zoom_curr[0], clientY: this.zoom_curr[1] });
1895  this.last_touch = new Date(0);
1896  } else {
1897  this.clearInteractiveElements();
1898  }
1899  }
1900 
1901  if (this.zoom_kind < 100) return;
1902 
1903  d3.event.preventDefault();
1904  d3.select(window).on("touchmove.zoomRect", null)
1905  .on("touchend.zoomRect", null)
1906  .on("touchcancel.zoomRect", null);
1907 
1908  var xmin, xmax, ymin, ymax, isany = false,
1909  xid = this.swap_xy ? 1 : 0, yid = 1 - xid,
1910  changed = [true, true];
1911  if (this.zoom_kind === 102) changed[1] = false;
1912  if (this.zoom_kind === 103) changed[0] = false;
1913 
1914  if (changed[xid] && (Math.abs(this.zoom_curr[xid] - this.zoom_origin[xid]) > 10)) {
1915  xmin = Math.min(this.RevertX(this.zoom_origin[xid]), this.RevertX(this.zoom_curr[xid]));
1916  xmax = Math.max(this.RevertX(this.zoom_origin[xid]), this.RevertX(this.zoom_curr[xid]));
1917  isany = true;
1918  }
1919 
1920  if (changed[yid] && (Math.abs(this.zoom_curr[yid] - this.zoom_origin[yid]) > 10)) {
1921  ymin = Math.min(this.RevertY(this.zoom_origin[yid]), this.RevertY(this.zoom_curr[yid]));
1922  ymax = Math.max(this.RevertY(this.zoom_origin[yid]), this.RevertY(this.zoom_curr[yid]));
1923  isany = true;
1924  }
1925 
1926  this.clearInteractiveElements();
1927  this.last_touch = new Date(0);
1928 
1929  if (isany) {
1930  this.zoom_changed_interactive = 2;
1931  this.Zoom(xmin, xmax, ymin, ymax);
1932  }
1933 
1934  d3.event.stopPropagation();
1935  }
1936 
1937  TFramePainter.prototype.ShowContextMenu = function(kind, evnt, obj) {
1938  // ignore context menu when touches zooming is ongoing
1939  if (('zoom_kind' in this) && (this.zoom_kind > 100)) return;
1940 
1941  // this is for debug purposes only, when context menu is where, close is and show normal menu
1942  //if (!evnt && !kind && document.getElementById('root_ctx_menu')) {
1943  // var elem = document.getElementById('root_ctx_menu');
1944  // elem.parentNode.removeChild(elem);
1945  // return;
1946  //}
1947 
1948  var menu_painter = this, frame_corner = false, fp = this; // object used to show context menu
1949 
1950  if (!evnt) {
1951  d3.event.preventDefault();
1952  d3.event.stopPropagation(); // disable main context menu
1953  evnt = d3.event;
1954 
1955  if (kind === undefined) {
1956  var ms = d3.mouse(this.svg_frame().node()),
1957  tch = d3.touches(this.svg_frame().node()),
1958  pp = this.pad_painter(),
1959  pnt = null, sel = null;
1960 
1961  if (tch.length === 1) pnt = { x: tch[0][0], y: tch[0][1], touch: true }; else
1962  if (ms.length === 2) pnt = { x: ms[0], y: ms[1], touch: false };
1963 
1964  if ((pnt !== null) && (pp !== null)) {
1965  pnt.painters = true; // assign painter for every tooltip
1966  var hints = pp.GetTooltips(pnt), bestdist = 1000;
1967  for (var n=0;n<hints.length;++n)
1968  if (hints[n] && hints[n].menu) {
1969  var dist = ('menu_dist' in hints[n]) ? hints[n].menu_dist : 7;
1970  if (dist < bestdist) { sel = hints[n].painter; bestdist = dist; }
1971  }
1972  }
1973 
1974  if (sel!==null) menu_painter = sel;
1975 
1976  if (pnt!==null) frame_corner = (pnt.x>0) && (pnt.x<20) && (pnt.y>0) && (pnt.y<20);
1977 
1978  this.SetLastEventPos(pnt);
1979  }
1980  }
1981 
1982  // one need to copy event, while after call back event may be changed
1983  menu_painter.ctx_menu_evnt = evnt;
1984 
1985  JSROOT.Painter.createMenu(menu_painter, function(menu) {
1986  var domenu = menu.painter.FillContextMenu(menu, kind, obj);
1987 
1988  // fill frame menu by default - or append frame elements when activated in the frame corner
1989  if (fp && (!domenu || (frame_corner && (kind!=="frame") && (fp!=menu.painter))))
1990  domenu = fp.FillContextMenu(menu);
1991 
1992  if (domenu)
1993  menu.painter.FillObjectExecMenu(menu, kind, function() {
1994  // suppress any running zooming
1995  menu.painter.SwitchTooltip(false);
1996  menu.show(menu.painter.ctx_menu_evnt, menu.painter.SwitchTooltip.bind(menu.painter, true) );
1997  });
1998 
1999  }); // end menu creation
2000  }
2001 
2002  TFramePainter.prototype.FillContextMenu = function(menu, kind, obj) {
2003 
2004  // when fill and show context menu, remove all zooming
2005  this.clearInteractiveElements();
2006 
2007  if ((kind=="x") || (kind=="y")) {
2008  var faxis = null;
2009  //this.histo.fXaxis;
2010  //if (kind=="y") faxis = this.histo.fYaxis; else
2011  //if (kind=="z") faxis = obj ? obj : this.histo.fZaxis;
2012  menu.add("header: " + kind.toUpperCase() + " axis");
2013  menu.add("Unzoom", this.Unzoom.bind(this, kind));
2014 
2015  if (this[kind+"_kind"] == "normal")
2016  menu.addchk(this["log"+kind], "SetLog"+kind, this.ToggleLog.bind(this, kind) );
2017 
2018  // if ((kind === "z") && this.options.Zscale)
2019  // if (this.FillPaletteMenu) this.FillPaletteMenu(menu);
2020 
2021  if (faxis) {
2022  menu.addchk(faxis.TestBit(JSROOT.EAxisBits.kMoreLogLabels), "More log",
2023  function() { faxis.InvertBit(JSROOT.EAxisBits.kMoreLogLabels); this.RedrawPad(); });
2024  menu.addchk(faxis.TestBit(JSROOT.EAxisBits.kNoExponent), "No exponent",
2025  function() { faxis.InvertBit(JSROOT.EAxisBits.kNoExponent); this.RedrawPad(); });
2026  menu.add("sub:Labels");
2027  menu.addchk(faxis.TestBit(JSROOT.EAxisBits.kCenterLabels), "Center",
2028  function() { faxis.InvertBit(JSROOT.EAxisBits.kCenterLabels); this.RedrawPad(); });
2029  menu.addchk(faxis.TestBit(JSROOT.EAxisBits.kLabelsVert), "Rotate",
2030  function() { faxis.InvertBit(JSROOT.EAxisBits.kLabelsVert); this.RedrawPad(); });
2031  this.AddColorMenuEntry(menu, "Color", faxis.fLabelColor,
2032  function(arg) { faxis.fLabelColor = parseInt(arg); this.RedrawPad(); });
2033  this.AddSizeMenuEntry(menu,"Offset", 0, 0.1, 0.01, faxis.fLabelOffset,
2034  function(arg) { faxis.fLabelOffset = parseFloat(arg); this.RedrawPad(); } );
2035  this.AddSizeMenuEntry(menu,"Size", 0.02, 0.11, 0.01, faxis.fLabelSize,
2036  function(arg) { faxis.fLabelSize = parseFloat(arg); this.RedrawPad(); } );
2037  menu.add("endsub:");
2038  menu.add("sub:Title");
2039  menu.add("SetTitle", function() {
2040  var t = prompt("Enter axis title", faxis.fTitle);
2041  if (t!==null) { faxis.fTitle = t; this.RedrawPad(); }
2042  });
2043  menu.addchk(faxis.TestBit(JSROOT.EAxisBits.kCenterTitle), "Center",
2044  function() { faxis.InvertBit(JSROOT.EAxisBits.kCenterTitle); this.RedrawPad(); });
2045  menu.addchk(faxis.TestBit(JSROOT.EAxisBits.kRotateTitle), "Rotate",
2046  function() { faxis.InvertBit(JSROOT.EAxisBits.kRotateTitle); this.RedrawPad(); });
2047  this.AddColorMenuEntry(menu, "Color", faxis.fTitleColor,
2048  function(arg) { faxis.fTitleColor = parseInt(arg); this.RedrawPad(); });
2049  this.AddSizeMenuEntry(menu,"Offset", 0, 3, 0.2, faxis.fTitleOffset,
2050  function(arg) { faxis.fTitleOffset = parseFloat(arg); this.RedrawPad(); } );
2051  this.AddSizeMenuEntry(menu,"Size", 0.02, 0.11, 0.01, faxis.fTitleSize,
2052  function(arg) { faxis.fTitleSize = parseFloat(arg); this.RedrawPad(); } );
2053  menu.add("endsub:");
2054  menu.add("sub:Ticks");
2055  this.AddColorMenuEntry(menu, "Color", faxis.fAxisColor,
2056  function(arg) { faxis.fAxisColor = parseInt(arg); this.RedrawPad(); });
2057  this.AddSizeMenuEntry(menu, "Size", -0.05, 0.055, 0.01, faxis.fTickLength,
2058  function(arg) { faxis.fTickLength = parseFloat(arg); this.RedrawPad(); } );
2059  menu.add("endsub:");
2060  }
2061  return true;
2062  }
2063 
2064  var alone = menu.size()==0;
2065 
2066  if (alone)
2067  menu.add("header:Frame");
2068  else
2069  menu.add("separator");
2070 
2071  if (this.zoom_xmin !== this.zoom_xmax)
2072  menu.add("Unzoom X", this.Unzoom.bind(this,"x"));
2073  if (this.zoom_ymin !== this.zoom_ymax)
2074  menu.add("Unzoom Y", this.Unzoom.bind(this,"y"));
2075  if (this.zoom_zmin !== this.zoom_zmax)
2076  menu.add("Unzoom Z", this.Unzoom.bind(this,"z"));
2077  menu.add("Unzoom all", this.Unzoom.bind(this,"xyz"));
2078 
2079  menu.addchk(this.logx, "SetLogx", this.ToggleLog.bind(this,"x"));
2080  menu.addchk(this.logy, "SetLogy", this.ToggleLog.bind(this,"y"));
2081  // if (this.Dimension() == 2)
2082  // menu.addchk(pad.fLogz, "SetLogz", this.ToggleLog.bind(main,"z"));
2083  menu.add("separator");
2084 
2085 
2086  menu.addchk(this.IsTooltipAllowed(), "Show tooltips", function() {
2087  this.SetTooltipAllowed("toggle");
2088  });
2089  this.FillAttContextMenu(menu,alone ? "" : "Frame ");
2090  menu.add("separator");
2091  menu.add("Save as frame.png", function() { this.pad_painter().SaveAs("png", 'frame', 'frame.png'); });
2092  menu.add("Save as frame.svg", function() { this.pad_painter().SaveAs("svg", 'frame', 'frame.svg'); });
2093 
2094  return true;
2095  }
2096 
2102  TFramePainter.prototype.ShowAxisStatus = function(axis_name) {
2103  // method called normally when mouse enter main object element
2104 
2105  var status_func = this.GetShowStatusFunc();
2106 
2107  if (typeof status_func != "function") return;
2108 
2109  var taxis = null, hint_name = axis_name, hint_title = "TAxis",
2110  m = d3.mouse(this.svg_frame().node()), id = (axis_name=="x") ? 0 : 1;
2111 
2112  if (taxis) { hint_name = taxis.fName; hint_title = taxis.fTitle || "histogram TAxis object"; }
2113 
2114  if (this.swap_xy) id = 1-id;
2115 
2116  var axis_value = (axis_name=="x") ? this.RevertX(m[id]) : this.RevertY(m[id]);
2117 
2118  status_func(hint_name, hint_title, axis_name + " : " + this.AxisAsText(axis_name, axis_value), m[0]+","+m[1]);
2119  }
2120 
2121  TFramePainter.prototype.AddInteractive = function() {
2122  // only first painter in list allowed to add interactive functionality to the frame
2123 
2124  if (JSROOT.BatchMode || (!JSROOT.gStyle.Zooming && !JSROOT.gStyle.ContextMenu)) return;
2125 
2126  var pp = this.pad_painter();
2127  if (pp && pp._fast_drawing) return;
2128 
2129  var svg = this.svg_frame();
2130 
2131  if (svg.empty()) return;
2132 
2133  var svg_x = svg.selectAll(".xaxis_container"),
2134  svg_y = svg.selectAll(".yaxis_container");
2135 
2136  if (!svg.property('interactive_set')) {
2137  this.AddKeysHandler();
2138 
2139  this.last_touch = new Date(0);
2140  this.zoom_kind = 0; // 0 - none, 1 - XY, 2 - only X, 3 - only Y, (+100 for touches)
2141  this.zoom_rect = null;
2142  this.zoom_origin = null; // original point where zooming started
2143  this.zoom_curr = null; // current point for zooming
2144  this.touch_cnt = 0;
2145  }
2146 
2147  if (JSROOT.gStyle.Zooming && (!this.options || !this.options.Proj)) {
2148  if (JSROOT.gStyle.ZoomMouse) {
2149  svg.on("mousedown", this.startRectSel.bind(this));
2150  svg.on("dblclick", this.mouseDoubleClick.bind(this));
2151  }
2152  if (JSROOT.gStyle.ZoomWheel) {
2153  svg.on("wheel", this.mouseWheel.bind(this));
2154  }
2155  }
2156 
2157  if (JSROOT.touches && ((JSROOT.gStyle.Zooming && JSROOT.gStyle.ZoomTouch) || JSROOT.gStyle.ContextMenu))
2158  svg.on("touchstart", this.startTouchZoom.bind(this));
2159 
2160  if (JSROOT.gStyle.ContextMenu) {
2161  if (JSROOT.touches) {
2162  svg_x.on("touchstart", this.startTouchMenu.bind(this,"x"));
2163  svg_y.on("touchstart", this.startTouchMenu.bind(this,"y"));
2164  }
2165  svg.on("contextmenu", this.ShowContextMenu.bind(this));
2166  svg_x.on("contextmenu", this.ShowContextMenu.bind(this,"x"));
2167  svg_y.on("contextmenu", this.ShowContextMenu.bind(this,"y"));
2168  }
2169 
2170  svg_x.on("mousemove", this.ShowAxisStatus.bind(this,"x"));
2171  svg_y.on("mousemove", this.ShowAxisStatus.bind(this,"y"));
2172 
2173  svg.property('interactive_set', true);
2174  }
2175 
2176  TFramePainter.prototype.mouseWheel = function() {
2177  d3.event.stopPropagation();
2178 
2179  d3.event.preventDefault();
2180  this.clearInteractiveElements();
2181 
2182  var itemx = { name: "x", ignore: false },
2183  itemy = { name: "y", ignore: !this.AllowDefaultYZooming() },
2184  cur = d3.mouse(this.svg_frame().node()),
2185  w = this.frame_width(), h = this.frame_height();
2186 
2187  this.AnalyzeMouseWheelEvent(d3.event, this.swap_xy ? itemy : itemx, cur[0] / w, (cur[1] >=0) && (cur[1] <= h));
2188 
2189  this.AnalyzeMouseWheelEvent(d3.event, this.swap_xy ? itemx : itemy, 1 - cur[1] / h, (cur[0] >= 0) && (cur[0] <= w));
2190 
2191  this.Zoom(itemx.min, itemx.max, itemy.min, itemy.max);
2192 
2193  if (itemx.changed || itemy.changed) this.zoom_changed_interactive = 2;
2194  }
2195 
2196  TFramePainter.prototype.AllowDefaultYZooming = function() {
2197  // return true if default Y zooming should be enabled
2198  // it is typically for 2-Dim histograms or
2199  // when histogram not draw, defined by other painters
2200 
2201  var pad_painter = this.pad_painter();
2202  if (pad_painter && pad_painter.painters)
2203  for (var k = 0; k < pad_painter.painters.length; ++k) {
2204  var subpainter = pad_painter.painters[k];
2205  if (subpainter && (subpainter.wheel_zoomy!==undefined))
2206  return subpainter.wheel_zoomy;
2207  }
2208 
2209  return false;
2210  }
2211 
2212 
2213  TFramePainter.prototype.AnalyzeMouseWheelEvent = function(event, item, dmin, ignore) {
2214 
2215  item.min = item.max = undefined;
2216  item.changed = false;
2217  if (ignore && item.ignore) return;
2218 
2219  var delta = 0, delta_left = 1, delta_right = 1;
2220 
2221  if ('dleft' in item) { delta_left = item.dleft; delta = 1; }
2222  if ('dright' in item) { delta_right = item.dright; delta = 1; }
2223 
2224  if ('delta' in item) {
2225  delta = item.delta;
2226  } else if (event && event.wheelDelta !== undefined ) {
2227  // WebKit / Opera / Explorer 9
2228  delta = -event.wheelDelta;
2229  } else if (event && event.deltaY !== undefined ) {
2230  // Firefox
2231  delta = event.deltaY;
2232  } else if (event && event.detail !== undefined) {
2233  delta = event.detail;
2234  }
2235 
2236  if (delta===0) return;
2237  delta = (delta<0) ? -0.2 : 0.2;
2238 
2239  delta_left *= delta
2240  delta_right *= delta;
2241 
2242  var lmin = item.min = this["scale_"+item.name+"min"],
2243  lmax = item.max = this["scale_"+item.name+"max"],
2244  gmin = this[item.name+"min"],
2245  gmax = this[item.name+"max"];
2246 
2247  if ((item.min === item.max) && (delta<0)) {
2248  item.min = gmin;
2249  item.max = gmax;
2250  }
2251 
2252  if (item.min >= item.max) return;
2253 
2254  if ((dmin>0) && (dmin<1)) {
2255  if (this['log'+item.name]) {
2256  var factor = (item.min>0) ? JSROOT.log10(item.max/item.min) : 2;
2257  if (factor>10) factor = 10; else if (factor<0.01) factor = 0.01;
2258  item.min = item.min / Math.pow(10, factor*delta_left*dmin);
2259  item.max = item.max * Math.pow(10, factor*delta_right*(1-dmin));
2260  } else {
2261  var rx_left = (item.max - item.min), rx_right = rx_left;
2262  if (delta_left>0) rx_left = 1.001 * rx_left / (1-delta_left);
2263  item.min += -delta_left*dmin*rx_left;
2264 
2265  if (delta_right>0) rx_right = 1.001 * rx_right / (1-delta_right);
2266 
2267  item.max -= -delta_right*(1-dmin)*rx_right;
2268  }
2269  if (item.min >= item.max)
2270  item.min = item.max = undefined;
2271  else
2272  if (delta_left !== delta_right) {
2273  // extra check case when moving left or right
2274  if (((item.min < gmin) && (lmin===gmin)) ||
2275  ((item.max > gmax) && (lmax==gmax)))
2276  item.min = item.max = undefined;
2277  }
2278 
2279  } else {
2280  item.min = item.max = undefined;
2281  }
2282 
2283  item.changed = ((item.min !== undefined) && (item.max !== undefined));
2284  }
2285 
2286  TFramePainter.prototype.AddKeysHandler = function() {
2287  if (this.keys_handler || JSROOT.BatchMode || (typeof window == 'undefined')) return;
2288 
2289  this.keys_handler = this.ProcessKeyPress.bind(this);
2290 
2291  window.addEventListener('keydown', this.keys_handler, false);
2292  }
2293 
2294  TFramePainter.prototype.ProcessKeyPress = function(evnt) {
2295 
2296  var main = this.select_main();
2297  if (main.empty()) return;
2298 
2299  var key = "";
2300  switch (evnt.keyCode) {
2301  case 33: key = "PageUp"; break;
2302  case 34: key = "PageDown"; break;
2303  case 37: key = "ArrowLeft"; break;
2304  case 38: key = "ArrowUp"; break;
2305  case 39: key = "ArrowRight"; break;
2306  case 40: key = "ArrowDown"; break;
2307  case 42: key = "PrintScreen"; break;
2308  case 106: key = "*"; break;
2309  default: return false;
2310  }
2311 
2312  if (evnt.shiftKey) key = "Shift " + key;
2313  if (evnt.altKey) key = "Alt " + key;
2314  if (evnt.ctrlKey) key = "Ctrl " + key;
2315 
2316  var zoom = { name: "x", dleft: 0, dright: 0 };
2317 
2318  switch (key) {
2319  case "ArrowLeft": zoom.dleft = -1; zoom.dright = 1; break;
2320  case "ArrowRight": zoom.dleft = 1; zoom.dright = -1; break;
2321  case "Ctrl ArrowLeft": zoom.dleft = zoom.dright = -1; break;
2322  case "Ctrl ArrowRight": zoom.dleft = zoom.dright = 1; break;
2323  case "ArrowUp": zoom.name = "y"; zoom.dleft = 1; zoom.dright = -1; break;
2324  case "ArrowDown": zoom.name = "y"; zoom.dleft = -1; zoom.dright = 1; break;
2325  case "Ctrl ArrowUp": zoom.name = "y"; zoom.dleft = zoom.dright = 1; break;
2326  case "Ctrl ArrowDown": zoom.name = "y"; zoom.dleft = zoom.dright = -1; break;
2327  }
2328 
2329  if (zoom.dleft || zoom.dright) {
2330  if (!JSROOT.gStyle.Zooming) return false;
2331  // in 3dmode with orbit control ignore simple arrows
2332  if (this.mode3d && (key.indexOf("Ctrl")!==0)) return false;
2333  this.AnalyzeMouseWheelEvent(null, zoom, 0.5);
2334  this.Zoom(zoom.name, zoom.min, zoom.max);
2335  if (zoom.changed) this.zoom_changed_interactive = 2;
2336  evnt.stopPropagation();
2337  evnt.preventDefault();
2338  } else {
2339  var pp = this.pad_painter(),
2340  func = pp ? pp.FindButton(key) : "";
2341  if (func) {
2342  pp.PadButtonClick(func);
2343  evnt.stopPropagation();
2344  evnt.preventDefault();
2345  }
2346  }
2347 
2348  return true; // just process any key press
2349  }
2350 
2351  TFramePainter.prototype.CreateXY = function() {
2352  // here we create x,y objects which maps our physical coordinates into pixels
2353  // while only first painter really need such object, all others just reuse it
2354  // following functions are introduced
2355  // this.GetBin[X/Y] return bin coordinate
2356  // this.Convert[X/Y] converts root value in JS date when date scale is used
2357  // this.[x,y] these are d3.scale objects
2358  // this.gr[x,y] converts root scale into graphical value
2359  // this.Revert[X/Y] converts graphical coordinates to root scale value
2360 
2361  this.swap_xy = false;
2362  this.reverse_x = false;
2363  this.reverse_y = false;
2364 
2365  // if (this.options.BarStyle>=20) this.swap_xy = true;
2366  this.logx = this.logy = false;
2367 
2368  var w = this.frame_width(), h = this.frame_height();
2369 
2370  this.scale_xmin = this.xmin;
2371  this.scale_xmax = this.xmax;
2372 
2373  this.scale_ymin = this.ymin;
2374  this.scale_ymax = this.ymax;
2375 
2376  // if (opts.extra_y_space) {
2377  // var log_scale = this.swap_xy ? pad.fLogx : pad.fLogy;
2378  // if (log_scale && (this.scale_ymax > 0))
2379  // this.scale_ymax = Math.exp(Math.log(this.scale_ymax)*1.1);
2380  // else
2381  // this.scale_ymax += (this.scale_ymax - this.scale_ymin) * 0.1;
2382  // }
2383 
2384  //if (typeof this.RecalculateRange == "function")
2385  // this.RecalculateRange();
2386 
2387  if (this._xaxis_timedisplay) {
2388  this.x_kind = 'time';
2389  this.timeoffsetx = JSROOT.Painter.getTimeOffset(/*this.histo.fXaxis*/);
2390  this.ConvertX = function(x) { return new Date(this.timeoffsetx + x*1000); };
2391  this.RevertX = function(grx) { return (this.x.invert(grx) - this.timeoffsetx) / 1000; };
2392  } else {
2393  this.x_kind = 'normal'; // (this.histo.fXaxis.fLabels==null) ? 'normal' : 'labels';
2394  this.ConvertX = function(x) { return x; };
2395  this.RevertX = function(grx) { return this.x.invert(grx); };
2396  }
2397 
2398  if (this.zoom_xmin != this.zoom_xmax) {
2399  this.scale_xmin = this.zoom_xmin;
2400  this.scale_xmax = this.zoom_xmax;
2401  }
2402 
2403  if (this.x_kind == 'time') {
2404  this.x = d3.scaleTime();
2405  } else if (this.logx) {
2406  if (this.scale_xmax <= 0) this.scale_xmax = 1;
2407  if ((this.scale_xmin <= 0) || (this.scale_xmin >= this.scale_xmax))
2408  this.scale_xmin = this.scale_xmax * 0.0001;
2409 
2410  this.x = d3.scaleLog();
2411  } else {
2412  this.x = d3.scaleLinear();
2413  }
2414 
2415  var gr_range_x = this.reverse_x ? [ w, 0 ] : [ 0, w ],
2416  gr_range_y = this.reverse_y ? [ 0, h ] : [ h, 0 ];
2417 
2418  this.x.domain([this.ConvertX(this.scale_xmin), this.ConvertX(this.scale_xmax)])
2419  .range(this.swap_xy ? gr_range_y : gr_range_x);
2420 
2421  if (this.x_kind == 'time') {
2422  // we emulate scale functionality
2423  this.grx = function(val) { return this.x(this.ConvertX(val)); }
2424  } else if (this.logx) {
2425  this.grx = function(val) { return (val < this.scale_xmin) ? (this.swap_xy ? this.x.range()[0]+5 : -5) : this.x(val); }
2426  } else {
2427  this.grx = this.x;
2428  }
2429 
2430  if (this.zoom_ymin != this.zoom_ymax) {
2431  this.scale_ymin = this.zoom_ymin;
2432  this.scale_ymax = this.zoom_ymax;
2433  }
2434 
2435  if (this._yaxis_timedisplay) {
2436  this.y_kind = 'time';
2437  this.timeoffsety = JSROOT.Painter.getTimeOffset(/*this.histo.fYaxis*/);
2438  this.ConvertY = function(y) { return new Date(this.timeoffsety + y*1000); };
2439  this.RevertY = function(gry) { return (this.y.invert(gry) - this.timeoffsety) / 1000; };
2440  } else {
2441  this.y_kind = 'normal'; // !this.histo.fYaxis.fLabels ? 'normal' : 'labels';
2442  this.ConvertY = function(y) { return y; };
2443  this.RevertY = function(gry) { return this.y.invert(gry); };
2444  }
2445 
2446  if (this.logy) {
2447  if (this.scale_ymax <= 0) this.scale_ymax = 1;
2448  if ((this.scale_ymin <= 0) || (this.scale_ymin >= this.scale_ymax))
2449  this.scale_ymin = 3e-4 * this.scale_ymax;
2450 
2451  this.y = d3.scaleLog();
2452  } else if (this.y_kind == 'time') {
2453  this.y = d3.scaleTime();
2454  } else {
2455  this.y = d3.scaleLinear()
2456  }
2457 
2458  this.y.domain([ this.ConvertY(this.scale_ymin), this.ConvertY(this.scale_ymax) ])
2459  .range(this.swap_xy ? gr_range_x : gr_range_y);
2460 
2461  if (this.y_kind=='time') {
2462  // we emulate scale functionality
2463  this.gry = function(val) { return this.y(this.ConvertY(val)); }
2464  } else if (this.logy) {
2465  // make protection for log
2466  this.gry = function(val) { return (val < this.scale_ymin) ? (this.swap_xy ? -5 : this.y.range()[0]+5) : this.y(val); }
2467  } else {
2468  this.gry = this.y;
2469  }
2470 
2471  // this.SetRootPadRange();
2472  }
2473 
2475  TFramePainter.prototype.SetRootPadRange = function(pad, is3d) {
2476  // TODO: change of pad range and send back to root application
2477 /*
2478  if (!pad || this.options.Same) return;
2479 
2480  if (is3d) {
2481  // this is fake values, algorithm should be copied from TView3D class of ROOT
2482  pad.fLogx = pad.fLogy = 0;
2483  pad.fUxmin = pad.fUymin = -0.9;
2484  pad.fUxmax = pad.fUymax = 0.9;
2485  } else {
2486  pad.fLogx = (this.swap_xy ? this.logy : this.logx) ? 1 : 0;
2487  pad.fUxmin = this.scale_xmin;
2488  pad.fUxmax = this.scale_xmax;
2489  pad.fLogy = (this.swap_xy ? this.logx : this.logy) ? 1 : 0;
2490  pad.fUymin = this.scale_ymin;
2491  pad.fUymax = this.scale_ymax;
2492  }
2493 
2494  if (pad.fLogx) {
2495  pad.fUxmin = JSROOT.log10(pad.fUxmin);
2496  pad.fUxmax = JSROOT.log10(pad.fUxmax);
2497  }
2498  if (pad.fLogy) {
2499  pad.fUymin = JSROOT.log10(pad.fUymin);
2500  pad.fUymax = JSROOT.log10(pad.fUymax);
2501  }
2502 
2503  var rx = pad.fUxmax - pad.fUxmin,
2504  mx = 1 - pad.fLeftMargin - pad.fRightMargin,
2505  ry = pad.fUymax - pad.fUymin,
2506  my = 1 - pad.fBottomMargin - pad.fTopMargin;
2507 
2508  if (mx <= 0) mx = 0.01; // to prevent overflow
2509  if (my <= 0) my = 0.01;
2510 
2511  pad.fX1 = pad.fUxmin - rx/mx*pad.fLeftMargin;
2512  pad.fX2 = pad.fUxmax + rx/mx*pad.fRightMargin;
2513  pad.fY1 = pad.fUymin - ry/my*pad.fBottomMargin;
2514  pad.fY2 = pad.fUymax + ry/my*pad.fTopMargin;
2515  */
2516  }
2517 
2518  TFramePainter.prototype.ToggleLog = function(axis) {
2519  var painter = this.main_painter() || this,
2520  pad = this.root_pad();
2521  var curr = pad["fLog" + axis];
2522  // do not allow log scale for labels
2523  if (!curr) {
2524  var kind = this[axis+"_kind"];
2525  if (this.swap_xy && axis==="x") kind = this["y_kind"]; else
2526  if (this.swap_xy && axis==="y") kind = this["x_kind"];
2527  if (kind === "labels") return;
2528  }
2529 
2530  var pp = this.pad_painter(), canp = this.canv_painter();
2531  if (pp && pp.snapid && canp && canp._websocket) {
2532  console.warn('Change log scale on server here!!!!');
2533  // canp.SendWebsocket("OBJEXEC:" + pp.snapid + ":SetLog" + axis + (curr ? "(0)" : "(1)"));
2534  } else {
2535  pad["fLog" + axis] = curr ? 0 : 1;
2536  painter.RedrawPad();
2537  }
2538  }
2539 
2540  function drawFrame(divid, obj, opt) {
2541  var p = new TFramePainter(obj);
2542  if (opt == "3d") p.mode3d = true;
2543  p.SetDivId(divid, 2);
2544  p.Redraw();
2545  return p.DrawingReady();
2546  }
2547 
2548  // ===========================================================================
2549 
2550  function TPadPainter(pad, iscan) {
2551  JSROOT.TObjectPainter.call(this, pad);
2552  this.csstype = "pad";
2553  this.pad = pad;
2554  this.iscan = iscan; // indicate if working with canvas
2555  this.this_pad_name = "";
2556  if (!this.iscan && (pad !== null)) {
2557  if (pad.fObjectID)
2558  this.this_pad_name = "pad" + pad.fObjectID; // use objectid as padname
2559  else
2560  this.this_pad_name = "ppp" + JSROOT.id_counter++; // artificical name
2561  }
2562  this.painters = []; // complete list of all painters in the pad
2563  this.has_canvas = true;
2564  }
2565 
2566  TPadPainter.prototype = Object.create(JSROOT.TObjectPainter.prototype);
2567 
2568  TPadPainter.prototype.Cleanup = function() {
2569  // cleanup only pad itself, all child elements will be collected and cleanup separately
2570 
2571  for (var k=0;k<this.painters.length;++k)
2572  this.painters[k].Cleanup();
2573 
2574  var svg_p = this.svg_pad(this.this_pad_name);
2575  if (!svg_p.empty()) {
2576  svg_p.property('pad_painter', null);
2577  svg_p.property('mainpainter', null);
2578  if (!this.iscan) svg_p.remove();
2579  }
2580 
2581  delete this.frame_painter_ref;
2582  delete this.pads_cache;
2583  this.painters = [];
2584  this.pad = null;
2585  this.draw_object = null;
2586  this.pad_frame = null;
2587  this.this_pad_name = "";
2588  this.has_canvas = false;
2589 
2590  JSROOT.Painter.SelectActivePad({ pp: this, active: false });
2591 
2592  JSROOT.TObjectPainter.prototype.Cleanup.call(this);
2593  }
2594 
2599  TPadPainter.prototype.CleanPrimitives = function(selector) {
2600  if (!selector || (typeof selector !== 'function')) return;
2601 
2602  for (var k = this.painters.length-1; k >= 0; --k)
2603  if (selector(this.painters[k])) {
2604  this.painters[k].Cleanup();
2605  this.painters.splice(k, 1);
2606  }
2607  }
2608 
2613  TPadPainter.prototype.ForEachPainterInPad = function(userfunc, kind) {
2614  if (!kind) kind = "all";
2615  if (kind!="objects") userfunc(this);
2616  for (var k = 0; k < this.painters.length; ++k) {
2617  var sub = this.painters[k];
2618  if (typeof sub.ForEachPainterInPad === 'function') {
2619  if (kind!="objects") sub.ForEachPainterInPad(userfunc, kind);
2620  } else if (kind != "pads") userfunc(sub);
2621  }
2622  }
2623 
2624  TPadPainter.prototype.ButtonSize = function(fact) {
2625  return Math.round((!fact ? 1 : fact) * (this.iscan || !this.has_canvas ? 16 : 12));
2626  }
2627 
2628  TPadPainter.prototype.RegisterForPadEvents = function(receiver) {
2629  this.pad_events_receiver = receiver;
2630  }
2631 
2632  TPadPainter.prototype.SelectObjectPainter = function(_painter, pos) {
2633  // dummy function, redefined in the TCanvasPainter
2634 
2635  var istoppad = (this.iscan || !this.has_canvas),
2636  canp = istoppad ? this : this.canv_painter(),
2637  pp = _painter instanceof TPadPainter ? _painter : _painter.pad_painter();
2638 
2639  if (pos && !istoppad)
2640  this.CalcAbsolutePosition(this.svg_pad(this.this_pad_name), pos);
2641 
2642  JSROOT.Painter.SelectActivePad({ pp: pp, active: true });
2643 
2644  if (typeof canp.SelectActivePad == "function")
2645  canp.SelectActivePad(pp, _painter, pos);
2646 
2647  if (canp.pad_events_receiver)
2648  canp.pad_events_receiver({ what: "select", padpainter: pp, painter: _painter, position: pos });
2649  }
2650 
2653  TPadPainter.prototype.SetActive = function(on) {
2654  var fp = this.frame_painter();
2655  if (fp && (typeof fp.SetActive == 'function')) fp.SetActive(on);
2656  }
2657 
2658  TPadPainter.prototype.CreateCanvasSvg = function(check_resize, new_size) {
2659 
2660  var factor = null, svg = null, lmt = 5, rect = null, btns;
2661 
2662  if (check_resize > 0) {
2663 
2664  if (this._fixed_size) return (check_resize > 1); // flag used to force re-drawing of all subpads
2665 
2666  svg = this.svg_canvas();
2667 
2668  if (svg.empty()) return false;
2669 
2670  factor = svg.property('height_factor');
2671 
2672  rect = this.check_main_resize(check_resize, null, factor);
2673 
2674  if (!rect.changed) return false;
2675 
2676  btns = this.svg_layer("btns_layer");
2677 
2678  } else {
2679 
2680  var render_to = this.select_main();
2681 
2682  if (render_to.style('position')=='static')
2683  render_to.style('position','relative');
2684 
2685  svg = render_to.append("svg")
2686  .attr("class", "jsroot root_canvas")
2687  .property('pad_painter', this) // this is custom property
2688  .property('mainpainter', null) // this is custom property
2689  .property('current_pad', "") // this is custom property
2690  .property('redraw_by_resize', false); // could be enabled to force redraw by each resize
2691 
2692  svg.append("svg:title").text("ROOT canvas");
2693  var frect = svg.append("svg:rect").attr("class","canvas_fillrect")
2694  .attr("x",0).attr("y",0);
2695  if (!JSROOT.BatchMode)
2696  frect.style("pointer-events", "visibleFill")
2697  .on("dblclick", this.EnlargePad.bind(this))
2698  .on("click", this.SelectObjectPainter.bind(this, this))
2699  .on("mouseenter", this.ShowObjectStatus.bind(this));
2700 
2701  svg.append("svg:g").attr("class","primitives_layer");
2702  svg.append("svg:g").attr("class","info_layer");
2703  btns = svg.append("svg:g").attr("class","btns_layer")
2704  .property('leftside', JSROOT.gStyle.ToolBarSide == 'left')
2705  .property('vertical', JSROOT.gStyle.ToolBarVert);
2706 
2707  if (JSROOT.gStyle.ContextMenu)
2708  svg.select(".canvas_fillrect").on("contextmenu", this.ShowContextMenu.bind(this));
2709 
2710  factor = 0.66;
2711  if (this.pad && this.pad.fCw && this.pad.fCh && (this.pad.fCw > 0)) {
2712  factor = this.pad.fCh / this.pad.fCw;
2713  if ((factor < 0.1) || (factor > 10)) factor = 0.66;
2714  }
2715 
2716  if (this._fixed_size) {
2717  render_to.style("overflow","auto");
2718  rect = { width: this.pad.fCw, height: this.pad.fCh };
2719  } else {
2720  rect = this.check_main_resize(2, new_size, factor);
2721  }
2722  }
2723 
2724  // this.createAttFill({ attr: this.pad });
2725 
2726  if ((rect.width<=lmt) || (rect.height<=lmt)) {
2727  svg.style("display", "none");
2728  console.warn("Hide canvas while geometry too small w=",rect.width," h=",rect.height);
2729  rect.width = 200; rect.height = 100; // just to complete drawing
2730  } else {
2731  svg.style("display", null);
2732  }
2733 
2734  if (this._fixed_size) {
2735  svg.attr("x", 0)
2736  .attr("y", 0)
2737  .attr("width", rect.width)
2738  .attr("height", rect.height)
2739  .style("position", "absolute");
2740  } else {
2741  svg.attr("x", 0)
2742  .attr("y", 0)
2743  .style("width", "100%")
2744  .style("height", "100%")
2745  .style("position", "absolute")
2746  .style("left", 0)
2747  .style("top", 0)
2748  .style("right", 0)
2749  .style("bottom", 0);
2750  }
2751 
2752  // console.log('CANVAS SVG width = ' + rect.width + " height = " + rect.height);
2753 
2754  svg.attr("viewBox", "0 0 " + rect.width + " " + rect.height)
2755  .attr("preserveAspectRatio", "none") // we do not preserve relative ratio
2756  .property('height_factor', factor)
2757  .property('draw_x', 0)
2758  .property('draw_y', 0)
2759  .property('draw_width', rect.width)
2760  .property('draw_height', rect.height);
2761 
2762  //svg.select(".canvas_fillrect")
2763  // .attr("width", rect.width)
2764  // .attr("height", rect.height)
2765  // .call(this.fillatt.func);
2766 
2767  this._fast_drawing = JSROOT.gStyle.SmallPad && ((rect.width < JSROOT.gStyle.SmallPad.width) || (rect.height < JSROOT.gStyle.SmallPad.height));
2768 
2769  this.AlignBtns(btns, rect.width, rect.height, svg);
2770 
2771  return true;
2772  }
2773 
2774  TPadPainter.prototype.EnlargePad = function() {
2775 
2776  if (d3.event) {
2777  d3.event.preventDefault();
2778  d3.event.stopPropagation();
2779  }
2780 
2781  var svg_can = this.svg_canvas(),
2782  pad_enlarged = svg_can.property("pad_enlarged");
2783 
2784  if (this.iscan || !this.has_canvas || (!pad_enlarged && !this.HasObjectsToDraw() && !this.painters)) {
2785  if (this._fixed_size) return; // canvas cannot be enlarged in such mode
2786  if (!this.enlarge_main('toggle')) return;
2787  if (this.enlarge_main('state')=='off') svg_can.property("pad_enlarged", null);
2788  } else if (!pad_enlarged) {
2789  this.enlarge_main(true, true);
2790  svg_can.property("pad_enlarged", this.pad);
2791  } else if (pad_enlarged === this.pad) {
2792  this.enlarge_main(false);
2793  svg_can.property("pad_enlarged", null);
2794  } else {
2795  console.error('missmatch with pad double click events');
2796  }
2797 
2798  var was_fast = this._fast_drawing;
2799 
2800  this.CheckResize({ force: true });
2801 
2802  if (this._fast_drawing != was_fast)
2803  this.ShowButtons();
2804  }
2805 
2806  TPadPainter.prototype.CreatePadSvg = function(only_resize) {
2807  // returns true when pad is displayed and all its items should be redrawn
2808 
2809  if (!this.has_canvas) {
2810  this.CreateCanvasSvg(only_resize ? 2 : 0);
2811  return true;
2812  }
2813 
2814  var svg_parent = this.svg_pad(),
2815  svg_can = this.svg_canvas(),
2816  width = svg_parent.property("draw_width"),
2817  height = svg_parent.property("draw_height"),
2818  pad_enlarged = svg_can.property("pad_enlarged"),
2819  pad_visible = !pad_enlarged || (pad_enlarged === this.pad),
2820  w = width, h = height, x = 0, y = 0,
2821  svg_pad = null, svg_rect = null, btns = null;
2822 
2823  if (this.pad && this.pad.fPos && this.pad.fSize) {
2824  x = Math.round(width * this.pad.fPos.fHoriz.fArr[0]);
2825  y = Math.round(height * this.pad.fPos.fVert.fArr[0]);
2826  w = Math.round(width * this.pad.fSize.fHoriz.fArr[0]);
2827  h = Math.round(height * this.pad.fSize.fVert.fArr[0]);
2828  }
2829 
2830  if (pad_enlarged === this.pad) { w = width; h = height; x = y = 0; }
2831 
2832  if (only_resize) {
2833  svg_pad = this.svg_pad(this.this_pad_name);
2834  svg_rect = svg_pad.select(".root_pad_border");
2835  btns = this.svg_layer("btns_layer", this.this_pad_name);
2836  } else {
2837  svg_pad = svg_parent.select(".primitives_layer")
2838  .append("svg:svg") // here was g before, svg used to blend all drawin outside
2839  .classed("__root_pad_" + this.this_pad_name, true)
2840  .attr("pad", this.this_pad_name) // set extra attribute to mark pad name
2841  .property('pad_painter', this) // this is custom property
2842  .property('mainpainter', null); // this is custom property
2843  svg_rect = svg_pad.append("svg:rect").attr("class", "root_pad_border");
2844 
2845  svg_pad.append("svg:g").attr("class","primitives_layer");
2846  btns = svg_pad.append("svg:g").attr("class","btns_layer")
2847  .property('leftside', JSROOT.gStyle.ToolBarSide != 'left')
2848  .property('vertical', JSROOT.gStyle.ToolBarVert);
2849 
2850  if (JSROOT.gStyle.ContextMenu)
2851  svg_rect.on("contextmenu", this.ShowContextMenu.bind(this));
2852 
2853  if (!JSROOT.BatchMode)
2854  svg_rect.attr("pointer-events", "visibleFill") // get events also for not visible rect
2855  .on("dblclick", this.EnlargePad.bind(this))
2856  .on("click", this.SelectObjectPainter.bind(this, this))
2857  .on("mouseenter", this.ShowObjectStatus.bind(this));
2858  }
2859 
2860  this.createAttFill({ attr: this.pad });
2861 
2862  this.createAttLine({ attr: this.pad, color0: this.pad.fBorderMode == 0 ? 'none' : '' });
2863 
2864  svg_pad
2865  //.attr("transform", "translate(" + x + "," + y + ")") // is not handled for SVG
2866  .attr("display", pad_visible ? null : "none")
2867  .attr("viewBox", "0 0 " + w + " " + h) // due to svg
2868  .attr("preserveAspectRatio", "none") // due to svg, we do not preserve relative ratio
2869  .attr("x", x) // due to svg
2870  .attr("y", y) // due to svg
2871  .attr("width", w) // due to svg
2872  .attr("height", h) // due to svg
2873  .property('draw_x', x) // this is to make similar with canvas
2874  .property('draw_y', y)
2875  .property('draw_width', w)
2876  .property('draw_height', h);
2877 
2878  svg_rect.attr("x", 0)
2879  .attr("y", 0)
2880  .attr("width", w)
2881  .attr("height", h)
2882  .call(this.fillatt.func)
2883  .call(this.lineatt.func);
2884 
2885  this._fast_drawing = JSROOT.gStyle.SmallPad && ((w < JSROOT.gStyle.SmallPad.width) || (h < JSROOT.gStyle.SmallPad.height));
2886 
2887  if (svg_pad.property('can3d') === 1)
2888  // special case of 3D canvas overlay
2889  this.select_main()
2890  .select(".draw3d_" + this.this_pad_name)
2891  .style('display', pad_visible ? '' : 'none');
2892 
2893  this.AlignBtns(btns, w, h);
2894 
2895  return pad_visible;
2896  }
2897 
2898  TPadPainter.prototype.RemovePrimitive = function(obj) {
2899  if (!this.pad || !this.pad.fPrimitives) return;
2900  var indx = this.pad.fPrimitives.arr.indexOf(obj);
2901  if (indx>=0) this.pad.fPrimitives.RemoveAt(indx);
2902  }
2903 
2904  TPadPainter.prototype.FindPrimitive = function(exact_obj, classname, name) {
2905  if (!this.pad || !this.pad.fPrimitives) return null;
2906 
2907  for (var i=0; i < this.pad.fPrimitives.arr.length; i++) {
2908  var obj = this.pad.fPrimitives.arr[i];
2909 
2910  if ((exact_obj!==null) && (obj !== exact_obj)) continue;
2911 
2912  if ((classname !== undefined) && (classname !== null))
2913  if (obj._typename !== classname) continue;
2914 
2915  if ((name !== undefined) && (name !== null))
2916  if (obj.fName !== name) continue;
2917 
2918  return obj;
2919  }
2920 
2921  return null;
2922  }
2923 
2924  TPadPainter.prototype.HasObjectsToDraw = function() {
2925  // return true if any objects beside sub-pads exists in the pad
2926 
2927  var arr = this.pad ? this.pad.fPrimitives : null;
2928 
2929  if (arr)
2930  for (var n=0;n<arr.length;++n)
2931  if (arr[n] && arr[n]._typename != "ROOT::Experimental::RPadDisplayItem") return true;
2932 
2933  return false;
2934  }
2935 
2936  TPadPainter.prototype.DrawPrimitives = function(indx, callback, ppainter) {
2937 
2938  if (indx===0) {
2939  // flag used to prevent immediate pad redraw during normal drawing sequence
2940  this._doing_pad_draw = true;
2941 
2942  if (this.iscan)
2943  this._start_tm = this._lasttm_tm = new Date().getTime();
2944 
2945  // set number of primitves
2946  this._num_primitives = this.pad && this.pad.fPrimitives ? this.pad.fPrimitives.length : 0;
2947  }
2948 
2949  while (true) {
2950  if (ppainter && (typeof ppainter=='object')) ppainter._primitive = true; // mark painter as belonging to primitives
2951 
2952  if (!this.pad || (indx >= this.pad.fPrimitives.length)) {
2953  delete this._doing_pad_draw;
2954  delete this._current_primitive_indx;
2955 
2956  if (this._start_tm) {
2957  var spenttm = new Date().getTime() - this._start_tm;
2958  if (spenttm > 3000) console.log("Canvas drawing took " + (spenttm*1e-3).toFixed(2) + "s");
2959  delete this._start_tm;
2960  delete this._lasttm_tm;
2961  }
2962 
2963  return JSROOT.CallBack(callback);
2964  }
2965 
2966  // handle use to invoke callback only when necessary
2967  var handle = { func: this.DrawPrimitives.bind(this, indx+1, callback) };
2968 
2969  // set current index
2970  this._current_primitive_indx = indx;
2971 
2972  ppainter = JSROOT.draw(this.divid, this.pad.fPrimitives[indx], "", handle);
2973 
2974  indx++;
2975 
2976  if (!handle.completed) return;
2977 
2978  if (!JSROOT.BatchMode && this.iscan) {
2979  var curtm = new Date().getTime();
2980  if (curtm > this._lasttm_tm + 500) {
2981  this._lasttm_tm = curtm;
2982  ppainter._primitive = true; // mark primitive ourself
2983  return requestAnimationFrame(handle.func);
2984  }
2985  }
2986  }
2987  }
2988 
2989  TPadPainter.prototype.GetTooltips = function(pnt) {
2990  var painters = [], hints = [];
2991 
2992  // first count - how many processors are there
2993  if (this.painters !== null)
2994  this.painters.forEach(function(obj) {
2995  if ('ProcessTooltip' in obj) painters.push(obj);
2996  });
2997 
2998  if (pnt) pnt.nproc = painters.length;
2999 
3000  painters.forEach(function(obj) {
3001  var hint = obj.ProcessTooltip(pnt);
3002  if (!hint) hint = { user_info: null };
3003  hints.push(hint);
3004  if (hint && pnt && pnt.painters) hint.painter = obj;
3005  });
3006 
3007  return hints;
3008  }
3009 
3010  TPadPainter.prototype.FillContextMenu = function(menu) {
3011 
3012  if (this.pad)
3013  menu.add("header: " + this.pad._typename + "::" + this.pad.fName);
3014  else
3015  menu.add("header: Canvas");
3016 
3017  menu.addchk(this.IsTooltipAllowed(), "Show tooltips", this.SetTooltipAllowed.bind(this, "toggle"));
3018 
3019  if (!this._websocket) {
3020 
3021  function ToggleGridField(arg) {
3022  this.pad[arg] = this.pad[arg] ? 0 : 1;
3023  var main = this.svg_pad(this.this_pad_name).property('mainpainter');
3024  if (main && (typeof main.DrawGrids == 'function')) main.DrawGrids();
3025  }
3026 
3027  function SetTickField(arg) {
3028  this.pad[arg.substr(1)] = parseInt(arg[0]);
3029 
3030  var main = this.svg_pad(this.this_pad_name).property('mainpainter');
3031  if (main && (typeof main.DrawAxes == 'function')) main.DrawAxes();
3032  }
3033 
3034  menu.addchk(this.pad.fGridx, 'Grid x', 'fGridx', ToggleGridField);
3035  menu.addchk(this.pad.fGridy, 'Grid y', 'fGridy', ToggleGridField);
3036  menu.add("sub:Ticks x");
3037  menu.addchk(this.pad.fTickx == 0, "normal", "0fTickx", SetTickField);
3038  menu.addchk(this.pad.fTickx == 1, "ticks on both sides", "1fTickx", SetTickField);
3039  menu.addchk(this.pad.fTickx == 2, "labels up", "2fTickx", SetTickField);
3040  menu.add("endsub:");
3041  menu.add("sub:Ticks y");
3042  menu.addchk(this.pad.fTicky == 0, "normal", "0fTicky", SetTickField);
3043  menu.addchk(this.pad.fTicky == 1, "ticks on both side", "1fTicky", SetTickField);
3044  menu.addchk(this.pad.fTicky == 2, "labels right", "2fTicky", SetTickField);
3045  menu.add("endsub:");
3046 
3047  //menu.addchk(this.pad.fTickx, 'Tick x', 'fTickx', ToggleField);
3048  //menu.addchk(this.pad.fTicky, 'Tick y', 'fTicky', ToggleField);
3049 
3050  this.FillAttContextMenu(menu);
3051  }
3052 
3053  menu.add("separator");
3054 
3055  if (this.ToggleEventStatus)
3056  menu.addchk(this.HasEventStatus(), "Event status", this.ToggleEventStatus.bind(this));
3057 
3058  if (this.enlarge_main() || (this.has_canvas && this.HasObjectsToDraw()))
3059  menu.addchk((this.enlarge_main('state')=='on'), "Enlarge " + (this.iscan ? "canvas" : "pad"), this.EnlargePad.bind(this));
3060 
3061  var fname = this.this_pad_name;
3062  if (fname.length===0) fname = this.iscan ? "canvas" : "pad";
3063  menu.add("Save as "+fname+".png", fname+".png", this.SaveAs.bind(this, "png", false));
3064  menu.add("Save as "+fname+".svg", fname+".svg", this.SaveAs.bind(this, "svg", false));
3065 
3066  return true;
3067  }
3068 
3069  TPadPainter.prototype.ShowContextMenu = function(evnt) {
3070  if (!evnt) {
3071  // for debug purposes keep original context menu for small region in top-left corner
3072  var pos = d3.mouse(this.svg_pad(this.this_pad_name).node());
3073 
3074  if (pos && (pos.length==2) && (pos[0]>0) && (pos[0]<10) && (pos[1]>0) && pos[1]<10) return;
3075 
3076  d3.event.stopPropagation(); // disable main context menu
3077  d3.event.preventDefault(); // disable browser context menu
3078 
3079  // one need to copy event, while after call back event may be changed
3080  evnt = d3.event;
3081 
3082  var fp = this.frame_painter();
3083  if (fp) fp.SetLastEventPos();
3084  }
3085 
3086  JSROOT.Painter.createMenu(this, function(menu) {
3087 
3088  menu.painter.FillContextMenu(menu);
3089 
3090  menu.painter.FillObjectExecMenu(menu, "", function() { menu.show(evnt); });
3091  }); // end menu creation
3092  }
3093 
3094  TPadPainter.prototype.Redraw = function(resize) {
3095 
3096  // prevent redrawing
3097  if (this._doing_pad_draw) return console.log('Prevent redrawing', this.pad.fName);
3098 
3099  var showsubitems = true;
3100 
3101  if (this.iscan) {
3102  this.CreateCanvasSvg(2);
3103  } else {
3104  showsubitems = this.CreatePadSvg(true);
3105  }
3106 
3107  // even sub-pad is not visible, we should redraw sub-sub-pads to hide them as well
3108  for (var i = 0; i < this.painters.length; ++i) {
3109  var sub = this.painters[i];
3110  if (showsubitems || sub.this_pad_name) sub.Redraw(resize);
3111  }
3112  }
3113 
3114  TPadPainter.prototype.NumDrawnSubpads = function() {
3115  if (this.painters === undefined) return 0;
3116 
3117  var num = 0;
3118 
3119  for (var i = 0; i < this.painters.length; ++i) {
3120  var obj = this.painters[i].GetObject();
3121  if (obj && (obj._typename === "TPad")) num++;
3122  }
3123 
3124  return num;
3125  }
3126 
3127  TPadPainter.prototype.RedrawByResize = function() {
3128  if (this.access_3d_kind() === 1) return true;
3129 
3130  for (var i = 0; i < this.painters.length; ++i)
3131  if (typeof this.painters[i].RedrawByResize === 'function')
3132  if (this.painters[i].RedrawByResize()) return true;
3133 
3134  return false;
3135  }
3136 
3137  TPadPainter.prototype.CheckCanvasResize = function(size, force) {
3138 
3139  if (!this.iscan && this.has_canvas) return false;
3140 
3141  if ((size === true) || (size === false)) { force = size; size = null; }
3142 
3143  if (size && (typeof size === 'object') && size.force) force = true;
3144 
3145  if (!force) force = this.RedrawByResize();
3146 
3147  var changed = this.CreateCanvasSvg(force ? 2 : 1, size);
3148 
3149  // if canvas changed, redraw all its subitems.
3150  // If redrawing was forced for canvas, same applied for sub-elements
3151  if (changed)
3152  for (var i = 0; i < this.painters.length; ++i)
3153  this.painters[i].Redraw(force ? false : true);
3154 
3155  return changed;
3156  }
3157 
3158  TPadPainter.prototype.UpdateObject = function(obj) {
3159  if (!obj) return false;
3160 
3161  this.pad.fCw = obj.fCw;
3162  this.pad.fCh = obj.fCh;
3163  this.pad.fTitle = obj.fTitle;
3164 
3165  return true;
3166  }
3167 
3168  TPadPainter.prototype.DrawNextSnap = function(lst, indx, call_back, objpainter) {
3169  // function called when drawing next snapshot from the list
3170  // it is also used as callback for drawing of previous snap
3171 
3172  if (indx===-1) {
3173  // flag used to prevent immediate pad redraw during first draw
3174  this._doing_pad_draw = true;
3175  this._snaps_map = {}; // to control how much snaps are drawn
3176  this._num_primitives = lst ? lst.length : 0;
3177  }
3178 
3179  // workaround to insert v6 frame in list of primitives
3180  if (objpainter === "workaround") { --indx; objpainter = null; }
3181 
3182  while (true) {
3183 
3184  if (objpainter && lst && lst[indx] && objpainter.snapid === undefined) {
3185  // keep snap id in painter, will be used for the
3186  if (this.painters.indexOf(objpainter)<0) this.painters.push(objpainter);
3187  objpainter.snapid = lst[indx].fObjectID;
3188  objpainter.rstyle = lst[indx].fStyle;
3189  }
3190 
3191  objpainter = null;
3192 
3193  ++indx; // change to the next snap
3194 
3195  if (!lst || indx >= lst.length) {
3196  delete this._doing_pad_draw;
3197  delete this._snaps_map;
3198  delete this._current_primitive_indx;
3199  return JSROOT.CallBack(call_back, this);
3200  }
3201 
3202  var snap = lst[indx],
3203  snapid = snap.fObjectID,
3204  cnt = this._snaps_map[snapid];
3205 
3206  if (cnt) cnt++; else cnt=1;
3207  this._snaps_map[snapid] = cnt; // check how many objects with same snapid drawn, use them again
3208 
3209  this._current_primitive_indx = indx;
3210 
3211  // first appropriate painter for the object
3212  // if same object drawn twice, two painters will exists
3213  for (var k=0; k<this.painters.length; ++k) {
3214  if (this.painters[k].snapid === snapid)
3215  if (--cnt === 0) { objpainter = this.painters[k]; break; }
3216  }
3217 
3218  // function which should be called when drawing of next item finished
3219  var draw_callback = this.DrawNextSnap.bind(this, lst, indx, call_back);
3220 
3221  if (objpainter) {
3222 
3223  if (snap._typename == "ROOT::Experimental::RPadDisplayItem") // subpad
3224  return objpainter.RedrawPadSnap(snap, draw_callback);
3225 
3226  if (objpainter.UpdateObject(snap.fDrawable || snap.fObject, snap.fOption || ""))
3227  objpainter.Redraw();
3228 
3229  continue; // call next
3230  }
3231 
3232  if (snap._typename == "ROOT::Experimental::RPadDisplayItem") { // subpad
3233 
3234  var subpad = snap; // not subpad, but just attributes
3235 
3236  var padpainter = new TPadPainter(subpad, false);
3237  padpainter.DecodeOptions("");
3238  padpainter.SetDivId(this.divid); // pad painter will be registered in the canvas painters list
3239  padpainter.snapid = snap.fObjectID;
3240  padpainter.rstyle = snap.fStyle;
3241 
3242  padpainter.CreatePadSvg();
3243 
3244  if (snap.fPrimitives && snap.fPrimitives.length > 0) {
3245  padpainter.AddButton(JSROOT.ToolbarIcons.camera, "Create PNG", "PadSnapShot");
3246  padpainter.AddButton(JSROOT.ToolbarIcons.circle, "Enlarge pad", "EnlargePad");
3247 
3248  if (JSROOT.gStyle.ContextMenu)
3249  padpainter.AddButton(JSROOT.ToolbarIcons.question, "Access context menus", "PadContextMenus");
3250  }
3251 
3252  // we select current pad, where all drawing is performed
3253  var prev_name = padpainter.CurrentPadName(padpainter.this_pad_name);
3254 
3255  padpainter.DrawNextSnap(snap.fPrimitives, -1, function() {
3256  padpainter.CurrentPadName(prev_name);
3257  draw_callback(padpainter);
3258  });
3259  return;
3260  }
3261 
3262  var handle = { func: draw_callback };
3263 
3264  if (snap._typename === "ROOT::Experimental::RObjectDisplayItem")
3265  if (!this.frame_painter())
3266  return JSROOT.draw(this.divid, { _typename: "TFrame", $dummy: true }, "", function() {
3267  handle.func("workaround"); // call function with "workaround" as argument
3268  });
3269 
3270  // TODO - fDrawable is v7, fObject from v6, maybe use same data member?
3271  objpainter = JSROOT.draw(this.divid, snap.fDrawable || snap.fObject, snap.fOption || "", handle);
3272 
3273  if (!handle.completed) return; // if callback will be invoked, break while loop
3274  }
3275  }
3276 
3277  TPadPainter.prototype.FindSnap = function(snapid) {
3278 
3279  if (this.snapid === snapid) return this;
3280 
3281  if (!this.painters) return null;
3282 
3283  for (var k=0;k<this.painters.length;++k) {
3284  var sub = this.painters[k];
3285 
3286  if (typeof sub.FindSnap === 'function') sub = sub.FindSnap(snapid);
3287  else if (sub.snapid !== snapid) sub = null;
3288 
3289  if (sub) return sub;
3290  }
3291 
3292  return null;
3293  }
3294 
3295  TPadPainter.prototype.AddOnlineButtons = function() {
3296  this.AddButton(JSROOT.ToolbarIcons.camera, "Create PNG", "CanvasSnapShot", "Ctrl PrintScreen");
3297  if (JSROOT.gStyle.ContextMenu)
3298  this.AddButton(JSROOT.ToolbarIcons.question, "Access context menus", "PadContextMenus");
3299 
3300  if (this.enlarge_main('verify'))
3301  this.AddButton(JSROOT.ToolbarIcons.circle, "Enlarge canvas", "EnlargePad");
3302  }
3303 
3304  TPadPainter.prototype.RedrawPadSnap = function(snap, call_back) {
3305  // for the pad/canvas display item contains list of primitives plus pad attributes
3306 
3307  if (!snap || !snap.fPrimitives) return;
3308 
3309  // for the moment only window size attributes are provided
3310  var padattr = { fCw: snap.fWinSize[0], fCh: snap.fWinSize[1], fTitle: snap.fTitle };
3311 
3312  // if canvas size not specified in batch mode, temporary use 900x700 size
3313  if (this.batch_mode && this.iscan && (!padattr.fCw || !padattr.fCh)) { padattr.fCw = 900; padattr.fCh = 700; }
3314 
3315  if (this.iscan && snap.fTitle && document)
3316  document.title = snap.fTitle;
3317 
3318  if (this.iscan && snap.fTitle && document)
3319  document.title = snap.fTitle;
3320 
3321  if (this.snapid === undefined) {
3322  // first time getting snap, create all gui elements first
3323 
3324  this.snapid = snap.fObjectID;
3325 
3326  this.draw_object = padattr;
3327  this.pad = padattr;
3328  this.pad_frame = snap.fFrame;
3329 
3330  if (this.batch_mode && this.iscan)
3331  this._fixed_size = true;
3332 
3333  this.CreateCanvasSvg(0);
3334  this.SetDivId(this.divid); // now add to painters list
3335  this.AddOnlineButtons();
3336 
3337  this.DrawNextSnap(snap.fPrimitives, -1, call_back);
3338 
3339  return;
3340  }
3341 
3342  // update only pad/canvas attributes
3343  this.UpdateObject(padattr);
3344 
3345  // apply all changes in the object (pad or canvas)
3346  if (this.iscan) {
3347  this.CreateCanvasSvg(2);
3348  } else {
3349  this.CreatePadSvg(true);
3350  }
3351 
3352  var isanyfound = false, isanyremove = false;
3353 
3354  // find and remove painters which no longer exists in the list
3355  for (var k=0;k<this.painters.length;++k) {
3356  var sub = this.painters[k];
3357  if (sub.snapid===undefined) continue; // look only for painters with snapid
3358 
3359  for (var i=0;i<snap.fPrimitives.length;++i)
3360  if (snap.fPrimitives[i].fObjectID === sub.snapid) { sub = null; isanyfound = true; break; }
3361 
3362  if (sub) {
3363  // remove painter which does not found in the list of snaps
3364  this.painters.splice(k--,1);
3365  sub.Cleanup(); // cleanup such painter
3366  isanyremove = true;
3367  }
3368  }
3369 
3370  if (isanyremove) {
3371  delete this.pads_cache;
3372  }
3373 
3374  if (!isanyfound) {
3375  var svg_p = this.svg_pad(this.this_pad_name),
3376  fp = this.frame_painter();
3377  if (svg_p && !svg_p.empty())
3378  svg_p.property('mainpainter', null);
3379  for (var k=0;k<this.painters.length;++k)
3380  if (fp !== this.painters[k])
3381  this.painters[k].Cleanup();
3382  this.painters = [];
3383  if (fp) {
3384  this.painters.push(fp);
3385  fp.CleanFrameDrawings();
3386  }
3387  this.RemoveButtons();
3388  this.AddOnlineButtons();
3389  }
3390 
3391  var padpainter = this,
3392  prev_name = padpainter.CurrentPadName(padpainter.this_pad_name);
3393 
3394  padpainter.DrawNextSnap(snap.fPrimitives, -1, function() {
3395  padpainter.CurrentPadName(prev_name);
3396  call_back(padpainter);
3397  });
3398  }
3399 
3400  TPadPainter.prototype.CreateImage = function(format, call_back) {
3401  if (format=="pdf") {
3402  // use https://github.com/MrRio/jsPDF in the future here
3403  JSROOT.CallBack(call_back, btoa("dummy PDF file"));
3404  } else if ((format=="png") || (format=="jpeg") || (format=="svg")) {
3405  this.ProduceImage(true, format, function(res) {
3406  if ((format=="svg") || !res)
3407  return JSROOT.CallBack(call_back, res);
3408  var separ = res.indexOf("base64,");
3409  JSROOT.CallBack(call_back, (separ>0) ? res.substr(separ+7) : "");
3410  });
3411  } else {
3412  JSROOT.CallBack(call_back, "");
3413  }
3414  }
3415 
3416  TPadPainter.prototype.ItemContextMenu = function(name) {
3417  var rrr = this.svg_pad(this.this_pad_name).node().getBoundingClientRect();
3418  var evnt = { clientX: rrr.left+10, clientY: rrr.top + 10 };
3419 
3420  // use timeout to avoid conflict with mouse click and automatic menu close
3421  if (name=="pad")
3422  return setTimeout(this.ShowContextMenu.bind(this, evnt), 50);
3423 
3424  var selp = null, selkind;
3425 
3426  switch(name) {
3427  case "xaxis":
3428  case "yaxis":
3429  case "zaxis":
3430  selp = this.main_painter();
3431  selkind = name[0];
3432  break;
3433  case "frame":
3434  selp = this.frame_painter();
3435  break;
3436  default: {
3437  var indx = parseInt(name);
3438  if (!isNaN(indx)) selp = this.painters[indx];
3439  }
3440  }
3441 
3442  if (!selp || (typeof selp.FillContextMenu !== 'function')) return;
3443 
3444  JSROOT.Painter.createMenu(selp, function(menu) {
3445  if (selp.FillContextMenu(menu,selkind))
3446  setTimeout(menu.show.bind(menu, evnt), 50);
3447  });
3448  }
3449 
3450  TPadPainter.prototype.SaveAs = function(kind, full_canvas, filename) {
3451  if (!filename) {
3452  filename = this.this_pad_name;
3453  if (filename.length === 0) filename = this.iscan ? "canvas" : "pad";
3454  filename += "." + kind;
3455  }
3456  this.ProduceImage(full_canvas, kind, function(imgdata) {
3457  var a = document.createElement('a');
3458  a.download = filename;
3459  a.href = (kind != "svg") ? imgdata : "data:image/svg+xml;charset=utf-8,"+encodeURIComponent(imgdata);
3460  document.body.appendChild(a);
3461  a.addEventListener("click", function(e) {
3462  a.parentNode.removeChild(a);
3463  });
3464  a.click();
3465  });
3466  }
3467 
3468  TPadPainter.prototype.ProduceImage = function(full_canvas, file_format, call_back) {
3469 
3470  var use_frame = (full_canvas === "frame");
3471 
3472  var elem = use_frame ? this.svg_frame() : (full_canvas ? this.svg_canvas() : this.svg_pad(this.this_pad_name));
3473 
3474  if (elem.empty()) return JSROOT.CallBack(call_back);
3475 
3476  var painter = (full_canvas && !use_frame) ? this.canv_painter() : this;
3477 
3478  var items = []; // keep list of replaced elements, which should be moved back at the end
3479 
3480 // document.body.style.cursor = 'wait';
3481 
3482  if (!use_frame) // do not make transformations for the frame
3483  painter.ForEachPainterInPad(function(pp) {
3484 
3485  // console.log('Check painter pp', pp.this_pad_name);
3486 
3487  var item = { prnt: pp.svg_pad(pp.this_pad_name) };
3488  items.push(item);
3489 
3490  // remove buttons from each subpad
3491  var btns = pp.svg_layer("btns_layer", pp.this_pad_name);
3492  item.btns_node = btns.node();
3493  if (item.btns_node) {
3494  item.btns_prnt = item.btns_node.parentNode;
3495  item.btns_next = item.btns_node.nextSibling;
3496  btns.remove();
3497  }
3498 
3499  var main = pp.frame_painter_ref;
3500  if (!main || (typeof main.Render3D !== 'function')) return;
3501 
3502  var can3d = main.access_3d_kind();
3503 
3504  if ((can3d !== 1) && (can3d !== 2)) return;
3505 
3506  var sz2 = main.size_for_3d(2); // get size of DOM element as it will be embed
3507 
3508  var sz = (can3d == 2) ? sz : main.size_for_3d(1);
3509 
3510  // console.log('Render 3D', sz2);
3511 
3512  var canvas = main.renderer.domElement;
3513  main.Render3D(0); // WebGL clears buffers, therefore we should render scene and convert immediately
3514  var dataUrl = canvas.toDataURL("image/png");
3515 
3516  // console.log('canvas width height', canvas.width, canvas.height);
3517 
3518  // console.log('produced png image len = ', dataUrl.length, 'begin', dataUrl.substr(0,100));
3519 
3520  // remove 3D drawings
3521 
3522  if (can3d == 2) {
3523  item.foreign = item.prnt.select("." + sz2.clname);
3524  item.foreign.remove();
3525  }
3526 
3527  var svg_frame = main.svg_frame();
3528  item.frame_node = svg_frame.node();
3529  if (item.frame_node) {
3530  item.frame_next = item.frame_node.nextSibling;
3531  svg_frame.remove();
3532  }
3533 
3534  //var origin = main.apply_3d_size(sz3d, true);
3535  //origin.remove();
3536 
3537  // add svg image
3538  item.img = item.prnt.insert("image",".primitives_layer") // create image object
3539  .attr("x", sz2.x)
3540  .attr("y", sz2.y)
3541  .attr("width", canvas.width)
3542  .attr("height", canvas.height)
3543  .attr("href", dataUrl);
3544 
3545  }, "pads");
3546 
3547  function reEncode(data) {
3548  data = encodeURIComponent(data);
3549  data = data.replace(/%([0-9A-F]{2})/g, function(match, p1) {
3550  var c = String.fromCharCode('0x'+p1);
3551  return c === '%' ? '%25' : c;
3552  });
3553  return decodeURIComponent(data);
3554  }
3555 
3556  function reconstruct(res) {
3557  for (var k=0;k<items.length;++k) {
3558  var item = items[k];
3559 
3560  if (item.img)
3561  item.img.remove(); // delete embed image
3562 
3563  var prim = item.prnt.select(".primitives_layer");
3564 
3565  if (item.foreign) // reinsert foreign object
3566  item.prnt.node().insertBefore(item.foreign.node(), prim.node());
3567 
3568  if (item.frame_node) // reinsert frame as first in list of primitives
3569  prim.node().insertBefore(item.frame_node, item.frame_next);
3570 
3571  if (item.btns_node) // reinsert buttons
3572  item.btns_prnt.insertBefore(item.btns_node, item.btns_next);
3573  }
3574 
3575  JSROOT.CallBack(call_back, res);
3576  }
3577 
3578  var width = elem.property('draw_width'), height = elem.property('draw_height');
3579  if (use_frame) { width = this.frame_width(); height = this.frame_height(); }
3580 
3581  var svg = '<svg width="' + width + '" height="' + height + '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">' +
3582  elem.node().innerHTML +
3583  '</svg>';
3584 
3585  if (file_format == "svg")
3586  return reconstruct(svg); // return SVG file as is
3587 
3588  var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
3589 
3590  var image = new Image();
3591  image.onload = function() {
3592  // if (options.result==="image") return JSROOT.CallBack(call_back, image);
3593 
3594  // console.log('GOT IMAGE', image.width, image.height);
3595 
3596  var canvas = document.createElement('canvas');
3597  canvas.width = image.width;
3598  canvas.height = image.height;
3599  var context = canvas.getContext('2d');
3600  context.drawImage(image, 0, 0);
3601 
3602  reconstruct(canvas.toDataURL('image/' + file_format));
3603  }
3604 
3605  image.onerror = function(arg) {
3606  console.log('IMAGE ERROR', arg);
3607  reconstruct(null);
3608  }
3609 
3610  image.src = 'data:image/svg+xml;base64,' + window.btoa(reEncode(doctype + svg));
3611  }
3612 
3613 
3614  TPadPainter.prototype.PadButtonClick = function(funcname) {
3615 
3616  if (funcname == "CanvasSnapShot") return this.SaveAs("png", true);
3617 
3618  if (funcname == "EnlargePad") return this.EnlargePad();
3619 
3620  if (funcname == "PadSnapShot") return this.SaveAs("png", false);
3621 
3622  if (funcname == "PadContextMenus") {
3623 
3624  d3.event.preventDefault();
3625  d3.event.stopPropagation();
3626 
3627  if (JSROOT.Painter.closeMenu()) return;
3628 
3629  var pthis = this, evnt = d3.event;
3630 
3631  JSROOT.Painter.createMenu(pthis, function(menu) {
3632  menu.add("header:Menus");
3633 
3634  if (pthis.iscan)
3635  menu.add("Canvas", "pad", pthis.ItemContextMenu);
3636  else
3637  menu.add("Pad", "pad", pthis.ItemContextMenu);
3638 
3639  if (pthis.frame_painter())
3640  menu.add("Frame", "frame", pthis.ItemContextMenu);
3641 
3642  var main = pthis.main_painter();
3643 
3644  if (main) {
3645  menu.add("X axis", "xaxis", pthis.ItemContextMenu);
3646  menu.add("Y axis", "yaxis", pthis.ItemContextMenu);
3647  if ((typeof main.Dimension === 'function') && (main.Dimension() > 1))
3648  menu.add("Z axis", "zaxis", pthis.ItemContextMenu);
3649  }
3650 
3651  if (pthis.painters && (pthis.painters.length>0)) {
3652  menu.add("separator");
3653  var shown = [];
3654  for (var n=0;n<pthis.painters.length;++n) {
3655  var pp = pthis.painters[n];
3656  var obj = pp ? pp.GetObject() : null;
3657  if (!obj || (shown.indexOf(obj)>=0)) continue;
3658 
3659  var name = ('_typename' in obj) ? (obj._typename + "::") : "";
3660  if ('fName' in obj) name += obj.fName;
3661  if (name.length==0) name = "item" + n;
3662  menu.add(name, n, pthis.ItemContextMenu);
3663  }
3664  }
3665 
3666  menu.show(evnt);
3667  });
3668 
3669  return;
3670  }
3671 
3672  // click automatically goes to all sub-pads
3673  // if any painter indicates that processing completed, it returns true
3674  var done = false;
3675 
3676  for (var i = 0; i < this.painters.length; ++i) {
3677  var pp = this.painters[i];
3678 
3679  if (typeof pp.PadButtonClick == 'function')
3680  pp.PadButtonClick(funcname);
3681 
3682  if (!done && (typeof pp.ButtonClick == 'function'))
3683  done = pp.ButtonClick(funcname);
3684  }
3685  }
3686 
3687  TPadPainter.prototype.FindButton = function(keyname) {
3688  var group = this.svg_layer("btns_layer", this.this_pad_name), found_func = "";
3689  if (!group.empty())
3690  group.selectAll("svg").each(function() {
3691  if (d3.select(this).attr("key") === keyname)
3692  found_func = d3.select(this).attr("name");
3693  });
3694  return found_func;
3695  }
3696 
3697  TPadPainter.prototype.toggleButtonsVisibility = function(action) {
3698  var group = this.svg_layer("btns_layer", this.this_pad_name),
3699  btn = group.select("[name='Toggle']");
3700 
3701  if (btn.empty()) return;
3702 
3703  var state = btn.property('buttons_state');
3704 
3705  if (btn.property('timout_handler')) {
3706  if (action!=='timeout') clearTimeout(btn.property('timout_handler'));
3707  btn.property('timout_handler', null);
3708  }
3709 
3710  var is_visible = false;
3711  switch(action) {
3712  case 'enable': is_visible = true; break;
3713  case 'enterbtn': return; // do nothing, just cleanup timeout
3714  case 'timeout': is_visible = false; break;
3715  case 'toggle':
3716  state = !state;
3717  btn.property('buttons_state', state);
3718  is_visible = state;
3719  break;
3720  case 'disable':
3721  case 'leavebtn':
3722  if (!state) btn.property('timout_handler', setTimeout(this.toggleButtonsVisibility.bind(this,'timeout'), 500));
3723  return;
3724  }
3725 
3726  group.selectAll('svg').each(function() {
3727  if (this===btn.node()) return;
3728  d3.select(this).style('display', is_visible ? "" : "none");
3729  });
3730  }
3731 
3732  TPadPainter.prototype.RemoveButtons = function() {
3733  var group = this.svg_layer("btns_layer", this.this_pad_name);
3734  if (!group.empty()) {
3735  group.selectAll("*").remove();
3736  group.property("nextx", null);
3737  }
3738  }
3739 
3740  TPadPainter.prototype.RemoveButtons = function() {
3741  var group = this.svg_layer("btns_layer", this.this_pad_name);
3742  if (!group.empty()) {
3743  group.selectAll("*").remove();
3744  group.property("nextx", null);
3745  }
3746  }
3747 
3748  TPadPainter.prototype.AddButton = function(_btn, _tooltip, _funcname, _keyname) {
3749  if (!JSROOT.gStyle.ToolBar) return;
3750 
3751  if (!this._buttons) this._buttons = [];
3752  // check if there are duplications
3753 
3754  for (var k=0;k<this._buttons.length;++k)
3755  if (this._buttons[k].funcname == _funcname) return;
3756 
3757  this._buttons.push({ btn: _btn, tooltip: _tooltip, funcname: _funcname, keyname: _keyname });
3758 
3759  var iscan = this.iscan || !this.has_canvas;
3760  if (!iscan && (_funcname.indexOf("Pad")!=0) && (_funcname !== "EnlargePad")) {
3761  var cp = this.canv_painter();
3762  if (cp && (cp!==this)) cp.AddButton(_btn, _tooltip, _funcname);
3763  }
3764  }
3765 
3766  TPadPainter.prototype.ShowButtons = function() {
3767 
3768  if (!this._buttons) return;
3769 
3770  var group = this.svg_layer("btns_layer", this.this_pad_name);
3771  if (group.empty()) return;
3772 
3773  // clean all previous buttons
3774  group.selectAll("*").remove();
3775 
3776  var iscan = this.iscan || !this.has_canvas, ctrl,
3777  x = group.property('leftside') ? this.ButtonSize(1.25) : 0, y = 0;
3778 
3779  if (this._fast_drawing) {
3780  ctrl = JSROOT.ToolbarIcons.CreateSVG(group, JSROOT.ToolbarIcons.circle, this.ButtonSize(), "EnlargePad");
3781  ctrl.attr("name", "Enlarge").attr("x", 0).attr("y", 0)
3782  // .property("buttons_state", (JSROOT.gStyle.ToolBar!=='popup'))
3783  .on("click", this.PadButtonClick.bind(this, "EnlargePad"));
3784  } else {
3785  ctrl = JSROOT.ToolbarIcons.CreateSVG(group, JSROOT.ToolbarIcons.rect, this.ButtonSize(), "Toggle tool buttons");
3786 
3787  ctrl.attr("name", "Toggle").attr("x", 0).attr("y", 0)
3788  .property("buttons_state", (JSROOT.gStyle.ToolBar!=='popup'))
3789  .on("click", this.toggleButtonsVisibility.bind(this, 'toggle'))
3790  .on("mouseenter", this.toggleButtonsVisibility.bind(this, 'enable'))
3791  .on("mouseleave", this.toggleButtonsVisibility.bind(this, 'disable'));
3792 
3793  for (var k=0;k<this._buttons.length;++k) {
3794  var item = this._buttons[k];
3795 
3796  var svg = JSROOT.ToolbarIcons.CreateSVG(group, item.btn, this.ButtonSize(),
3797  item.tooltip + (iscan ? "" : (" on pad " + this.this_pad_name)) + (item.keyname ? " (keyshortcut " + item.keyname + ")" : ""));
3798 
3799  if (group.property('vertical'))
3800  svg.attr("x", y).attr("y", x);
3801  else
3802  svg.attr("x", x).attr("y", y);
3803 
3804  svg.attr("name", item.funcname)
3805  .style('display', (ctrl.property("buttons_state") ? '' : 'none'))
3806  .on("mouseenter", this.toggleButtonsVisibility.bind(this, 'enterbtn'))
3807  .on("mouseleave", this.toggleButtonsVisibility.bind(this, 'leavebtn'));
3808 
3809  if (item.keyname) svg.attr("key", item.keyname);
3810 
3811  svg.on("click", this.PadButtonClick.bind(this, item.funcname));
3812 
3813  x += this.ButtonSize(1.25);
3814  }
3815  }
3816 
3817  group.property("nextx", x);
3818 
3819  this.AlignBtns(group, this.pad_width(this.this_pad_name), this.pad_height(this.this_pad_name));
3820 
3821  if (group.property('vertical')) ctrl.attr("y", x);
3822  else if (!group.property('leftside')) ctrl.attr("x", x);
3823  }
3824 
3825  TPadPainter.prototype.AlignBtns = function(btns, width, height, svg) {
3826  var sz0 = this.ButtonSize(1.25), nextx = (btns.property('nextx') || 0) + sz0, btns_x, btns_y;
3827  if (btns.property('vertical')) {
3828  btns_x = btns.property('leftside') ? 2 : (width - sz0);
3829  btns_y = height - nextx;
3830  } else {
3831  btns_x = btns.property('leftside') ? 2 : (width - nextx);
3832  btns_y = height - sz0;
3833  }
3834 
3835  btns.attr("transform","translate("+btns_x+","+btns_y+")");
3836  }
3837 
3838  TPadPainter.prototype.GetCoordinate = function(pos) {
3839  var res = { x: 0, y: 0 };
3840 
3841  if (!pos) return res;
3842 
3843  function GetV(len, indx, dflt) {
3844  return (len.fArr && (len.fArr.length>indx)) ? len.fArr[indx] : dflt;
3845  }
3846 
3847  var w = this.pad_width(this.this_pad_name),
3848  h = this.pad_height(this.this_pad_name),
3849  h_norm = GetV(pos.fHoriz, 0, 0),
3850  h_pixel = GetV(pos.fHoriz, 1, 0),
3851  h_user = GetV(pos.fHoriz, 2),
3852  v_norm = GetV(pos.fVert, 0, 0),
3853  v_pixel = GetV(pos.fVert, 1, 0),
3854  v_user = GetV(pos.fVert, 2);
3855 
3856  if (!this.pad_frame || (h_user === undefined)) {
3857  res.x = h_norm * w + h_pixel;
3858  } else {
3859  // TO DO - user coordiantes
3860  }
3861 
3862  if (!this.pad_frame || (v_user === undefined)) {
3863  res.y = h - v_norm * h - v_pixel;
3864  } else {
3865  // TO DO - user coordiantes
3866  }
3867 
3868  return res;
3869  }
3870 
3871 
3872 // TPadPainter.prototype.DrawingReady = function(res_painter) {
3873 // var main = this.main_painter();
3874 // if (main && main.mode3d && typeof main.Render3D == 'function') main.Render3D(-2222);
3875 // TBasePainter.prototype.DrawingReady.call(this, res_painter);
3876 // }
3877 
3878  TPadPainter.prototype.DecodeOptions = function(opt) {
3879  var pad = this.GetObject();
3880  if (!pad) return;
3881 
3882  var d = new JSROOT.DrawOptions(opt);
3883 
3884  if (d.check('WEBSOCKET')) this.OpenWebsocket();
3885  if (!this.options) this.options = {};
3886 
3887  JSROOT.extend(this.options, { GlobalColors: true, LocalColors: false, IgnorePalette: false, RotateFrame: false, FixFrame: false });
3888 
3889  if (d.check('NOCOLORS') || d.check('NOCOL')) this.options.GlobalColors = this.options.LocalColors = false;
3890  if (d.check('LCOLORS') || d.check('LCOL')) { this.options.GlobalColors = false; this.options.LocalColors = true; }
3891  if (d.check('NOPALETTE') || d.check('NOPAL')) this.options.IgnorePalette = true;
3892  if (d.check('ROTATE')) this.options.RotateFrame = true;
3893  if (d.check('FIXFRAME')) this.options.FixFrame = true;
3894 
3895  if (d.check('WHITE')) pad.fFillColor = 0;
3896  if (d.check('LOGX')) pad.fLogx = 1;
3897  if (d.check('LOGY')) pad.fLogy = 1;
3898  if (d.check('LOGZ')) pad.fLogz = 1;
3899  if (d.check('LOG')) pad.fLogx = pad.fLogy = pad.fLogz = 1;
3900  if (d.check('GRIDX')) pad.fGridx = 1;
3901  if (d.check('GRIDY')) pad.fGridy = 1;
3902  if (d.check('GRID')) pad.fGridx = pad.fGridy = 1;
3903  if (d.check('TICKX')) pad.fTickx = 1;
3904  if (d.check('TICKY')) pad.fTicky = 1;
3905  if (d.check('TICK')) pad.fTickx = pad.fTicky = 1;
3906  }
3907 
3908  function drawPad(divid, pad, opt) {
3909  var painter = new TPadPainter(pad, false);
3910  painter.DecodeOptions(opt);
3911 
3912  painter.SetDivId(divid); // pad painter will be registered in the canvas painters list
3913 
3914  if (painter.svg_canvas().empty()) {
3915  painter.has_canvas = false;
3916  painter.this_pad_name = "";
3917  }
3918 
3919  painter.CreatePadSvg();
3920 
3921  if (painter.MatchObjectType("TPad") && (!painter.has_canvas || painter.HasObjectsToDraw())) {
3922  painter.AddButton(JSROOT.ToolbarIcons.camera, "Create PNG", "PadSnapShot");
3923 
3924  if ((painter.has_canvas && painter.HasObjectsToDraw()) || painter.enlarge_main('verify'))
3925  painter.AddButton(JSROOT.ToolbarIcons.circle, "Enlarge pad", "EnlargePad");
3926 
3927  if (JSROOT.gStyle.ContextMenu)
3928  painter.AddButton(JSROOT.ToolbarIcons.question, "Access context menus", "PadContextMenus");
3929  }
3930 
3931  // we select current pad, where all drawing is performed
3932  var prev_name = painter.has_canvas ? painter.CurrentPadName(painter.this_pad_name) : undefined;
3933 
3934  JSROOT.Painter.SelectActivePad({ pp: painter, active: false });
3935 
3936  // flag used to prevent immediate pad redraw during first draw
3937  painter.DrawPrimitives(0, function() {
3938  painter.ShowButtons();
3939  // we restore previous pad name
3940  painter.CurrentPadName(prev_name);
3941  painter.DrawingReady();
3942  });
3943 
3944  return painter;
3945  }
3946 
3947  // ==========================================================================================
3948 
3949  function TCanvasPainter(canvas) {
3950  // used for online canvas painter
3951  TPadPainter.call(this, canvas, true);
3952  this._websocket = null;
3953  this.tooltip_allowed = (JSROOT.gStyle.Tooltip > 0);
3954  }
3955 
3956  TCanvasPainter.prototype = Object.create(TPadPainter.prototype);
3957 
3958  TCanvasPainter.prototype.ChangeLayout = function(layout_kind, call_back) {
3959  var current = this.get_layout_kind();
3960  if (current == layout_kind) return JSROOT.CallBack(call_back, true);
3961 
3962  var origin = this.select_main('origin'),
3963  sidebar = origin.select('.side_panel'),
3964  main = this.select_main(), lst = [];
3965 
3966  while (main.node().firstChild)
3967  lst.push(main.node().removeChild(main.node().firstChild));
3968 
3969  if (!sidebar.empty()) JSROOT.cleanup(sidebar.node());
3970 
3971  this.set_layout_kind("simple"); // restore defaults
3972  origin.html(""); // cleanup origin
3973 
3974  if (layout_kind == 'simple') {
3975  main = origin;
3976  for (var k=0;k<lst.length;++k)
3977  main.node().appendChild(lst[k]);
3978  this.set_layout_kind(layout_kind);
3979  JSROOT.resize(main.node());
3980  return JSROOT.CallBack(call_back, true);
3981  }
3982 
3983  var pthis = this;
3984 
3985  JSROOT.AssertPrerequisites("jq2d", function() {
3986 
3987  var grid = new JSROOT.GridDisplay(origin.node(), layout_kind);
3988 
3989  if (layout_kind.indexOf("vert")==0) {
3990  main = d3.select(grid.GetFrame(0));
3991  sidebar = d3.select(grid.GetFrame(1));
3992  } else {
3993  main = d3.select(grid.GetFrame(1));
3994  sidebar = d3.select(grid.GetFrame(0));
3995  }
3996 
3997  main.classed("central_panel", true).style('position','relative');
3998  sidebar.classed("side_panel", true).style('position','relative');
3999 
4000  // now append all childs to the new main
4001  for (var k=0;k<lst.length;++k)
4002  main.node().appendChild(lst[k]);
4003 
4004  pthis.set_layout_kind(layout_kind, ".central_panel");
4005 
4006  // remove reference to MDIDisplay, solves resize problem
4007  origin.property('mdi', null);
4008 
4009  // resize main drawing and let draw extras
4010  JSROOT.resize(main.node());
4011 
4012  JSROOT.CallBack(call_back, true);
4013  });
4014  }
4015 
4016  TCanvasPainter.prototype.ToggleProjection = function(kind, call_back) {
4017  delete this.proj_painter;
4018 
4019  if (kind) this.proj_painter = 1; // just indicator that drawing can be preformed
4020 
4021  if (this.ShowUI5ProjectionArea)
4022  return this.ShowUI5ProjectionArea(kind, call_back);
4023 
4024  var layout = 'simple';
4025 
4026  if (kind == "X") layout = 'vert2_31'; else
4027  if (kind == "Y") layout = 'horiz2_13';
4028 
4029  this.ChangeLayout(layout, call_back);
4030  }
4031 
4032  TCanvasPainter.prototype.DrawProjection = function(kind,hist) {
4033  if (!this.proj_painter) return; // ignore drawing if projection not configured
4034 
4035  if (this.proj_painter === 1) {
4036 
4037  var canv = JSROOT.Create("TCanvas"), pthis = this, pad = this.root_pad(), main = this.main_painter(), drawopt;
4038 
4039  if (kind == "X") {
4040  canv.fLeftMargin = pad.fLeftMargin;
4041  canv.fRightMargin = pad.fRightMargin;
4042  canv.fLogx = main.logx ? 1 : 0;
4043  canv.fUxmin = main.logx ? JSROOT.log10(main.scale_xmin) : main.scale_xmin;
4044  canv.fUxmax = main.logx ? JSROOT.log10(main.scale_xmax) : main.scale_xmax;
4045  drawopt = "fixframe";
4046  } else {
4047  canv.fBottomMargin = pad.fBottomMargin;
4048  canv.fTopMargin = pad.fTopMargin;
4049  canv.fLogx = main.logy ? 1 : 0;
4050  canv.fUxmin = main.logy ? JSROOT.log10(main.scale_ymin) : main.scale_ymin;
4051  canv.fUxmax = main.logy ? JSROOT.log10(main.scale_ymax) : main.scale_ymax;
4052  drawopt = "rotate";
4053  }
4054 
4055  canv.fPrimitives.Add(hist, "hist");
4056 
4057  if (this.DrawInUI5ProjectionArea) {
4058  // copy frame attributes
4059  this.DrawInUI5ProjectionArea(canv, drawopt, function(painter) { pthis.proj_painter = painter; })
4060  } else {
4061  this.DrawInSidePanel(canv, drawopt, function(painter) { pthis.proj_painter = painter; })
4062  }
4063  } else {
4064  var hp = this.proj_painter.main_painter();
4065  if (hp) hp.UpdateObject(hist, "hist");
4066  this.proj_painter.RedrawPad();
4067  }
4068  }
4069 
4070  TCanvasPainter.prototype.DrawInSidePanel = function(canv, opt, call_back) {
4071  var side = this.select_main('origin').select(".side_panel");
4072  if (side.empty()) return JSROOT.CallBack(call_back, null);
4073  JSROOT.draw(side.node(), canv, opt, call_back);
4074  }
4075 
4076  TCanvasPainter.prototype.ShowMessage = function(msg) {
4077  JSROOT.progress(msg, 7000);
4078  }
4079 
4081  TCanvasPainter.prototype.SaveCanvasAsFile = function(fname) {
4082  var pthis = this, pnt = fname.indexOf(".");
4083  this.CreateImage(fname.substr(pnt+1), function(res) {
4084  pthis.SendWebsocket("SAVE:" + fname + ":" + res);
4085  })
4086  }
4087 
4088  TCanvasPainter.prototype.SendSaveCommand = function(fname) {
4089  this.SendWebsocket("PRODUCE:" + fname);
4090  }
4091 
4092  TCanvasPainter.prototype.SendWebsocket = function(msg, chid) {
4093  if (this._websocket)
4094  this._websocket.Send(msg, chid);
4095  }
4096 
4097  TCanvasPainter.prototype.CloseWebsocket = function(force) {
4098  if (this._websocket) {
4099  this._websocket.Close(force);
4100  this._websocket.Cleanup();
4101  delete this._websocket;
4102  }
4103  }
4104 
4105  TCanvasPainter.prototype.OpenWebsocket = function(socket_kind) {
4106  // create websocket for current object (canvas)
4107  // via websocket one recieved many extra information
4108 
4109  this.CloseWebsocket();
4110 
4111  this._websocket = new JSROOT.WebWindowHandle(socket_kind);
4112  this._websocket.SetReceiver(this);
4113  this._websocket.Connect();
4114  }
4115 
4116  TCanvasPainter.prototype.UseWebsocket = function(handle, href) {
4117  this.CloseWebsocket();
4118 
4119  this._websocket = handle;
4120  console.log('Use websocket', this._websocket.key);
4121  this._websocket.SetReceiver(this);
4122  this._websocket.Connect(href);
4123  }
4124 
4125  TCanvasPainter.prototype.WindowBeforeUnloadHanlder = function() {
4126  // when window closed, close socket
4127  this.CloseWebsocket(true);
4128  }
4129 
4130  TCanvasPainter.prototype.OnWebsocketOpened = function(handle) {
4131  // indicate that we are ready to recieve any following commands
4132  }
4133 
4134  TCanvasPainter.prototype.OnWebsocketClosed = function(handle) {
4135  JSROOT.CloseCurrentWindow();
4136  }
4137 
4138  TCanvasPainter.prototype.OnWebsocketMsg = function(handle, msg) {
4139  console.log("GET MSG " + msg.substr(0,30));
4140 
4141  if (msg == "CLOSE") {
4142  this.OnWebsocketClosed();
4143  this.CloseWebsocket(true);
4144  } else if (msg.substr(0,5)=='SNAP:') {
4145  msg = msg.substr(5);
4146  var p1 = msg.indexOf(":"),
4147  snapid = msg.substr(0,p1),
4148  snap = JSROOT.parse(msg.substr(p1+1));
4149  this.RedrawPadSnap(snap, function() {
4150  handle.Send("SNAPDONE:" + snapid); // send ready message back when drawing completed
4151  });
4152  } else if (msg.substr(0,4)=='JSON') {
4153  var obj = JSROOT.parse(msg.substr(4));
4154  // console.log("get JSON ", msg.length-4, obj._typename);
4155  this.RedrawObject(obj);
4156 
4157  } else if (msg.substr(0,5)=='MENU:') {
4158  // this is container with object id and list of menu items
4159  var lst = JSROOT.parse(msg.substr(5));
4160  // console.log("get MENUS ", typeof lst, 'nitems', lst.length, msg.length-4);
4161  if (typeof this._getmenu_callback == 'function')
4162  this._getmenu_callback(lst);
4163  } else if (msg.substr(0,4)=='CMD:') {
4164  msg = msg.substr(4);
4165  var p1 = msg.indexOf(":"),
4166  cmdid = msg.substr(0,p1),
4167  cmd = msg.substr(p1+1),
4168  reply = "REPLY:" + cmdid + ":";
4169  if ((cmd == "SVG") || (cmd == "PNG") || (cmd == "JPEG")) {
4170  this.CreateImage(cmd.toLowerCase(), function(res) {
4171  handle.Send(reply + res);
4172  });
4173  } else if (cmd.indexOf("ADDPANEL:") == 0) {
4174  var relative_path = cmd.substr(9);
4175  console.log('request panel = ' + relative_path);
4176  if (!this.ShowUI5Panel) {
4177  handle.Send(reply + "false");
4178  } else {
4179 
4180  var conn = new JSROOT.WebWindowHandle(handle.kind);
4181 
4182  // set interim receiver until first message arrives
4183  conn.SetReceiver({
4184  cpainter: this,
4185 
4186  OnWebsocketOpened: function(hhh) {
4187  console.log('Panel socket connected');
4188  },
4189 
4190  OnWebsocketMsg: function(panel_handle, msg) {
4191 
4192  var panel_name = (msg.indexOf("SHOWPANEL:")==0) ? msg.substr(10) : "";
4193  console.log('Panel get message ' + msg + " show " + panel_name);
4194 
4195  this.cpainter.ShowUI5Panel(panel_name, panel_handle, function(res) {
4196  handle.Send(reply + (res ? "true" : "false"));
4197  });
4198  },
4199 
4200  OnWebsocketClosed: function(hhh) {
4201  // if connection failed,
4202  handle.Send(reply + "false");
4203  },
4204 
4205  OnWebsocketError: function(hhh) {
4206  // if connection failed,
4207  handle.Send(reply + "false");
4208  }
4209 
4210  });
4211 
4212  var addr = handle.href;
4213  if (relative_path.indexOf("../")==0) {
4214  var ddd = addr.lastIndexOf("/",addr.length-2);
4215  addr = addr.substr(0,ddd) + relative_path.substr(2);
4216  } else {
4217  addr += relative_path;
4218  }
4219  // only when connection established, panel will be activated
4220  conn.Connect(addr);
4221  }
4222  } else {
4223  console.log('Unrecognized command ' + cmd);
4224  handle.Send(reply);
4225  }
4226  } else if ((msg.substr(0,7)=='DXPROJ:') || (msg.substr(0,7)=='DYPROJ:')) {
4227  var kind = msg[1],
4228  hist = JSROOT.parse(msg.substr(7));
4229  this.DrawProjection(kind, hist);
4230  } else if (msg.substr(0,5)=='SHOW:') {
4231  var that = msg.substr(5),
4232  on = that[that.length-1] == '1';
4233  this.ShowSection(that.substr(0,that.length-2), on);
4234  } else {
4235  console.log("unrecognized msg len:" + msg.length + " msg:" + msg.substr(0,20));
4236  }
4237  }
4238 
4239  TCanvasPainter.prototype.ShowSection = function(that, on) {
4240  switch(that) {
4241  case "Menu": break;
4242  case "StatusBar": break;
4243  case "Editor": break;
4244  case "ToolBar": break;
4245  case "ToolTips": this.SetTooltipAllowed(on); break;
4246  }
4247  }
4248 
4249  JSROOT.TCanvasStatusBits = {
4250  kShowEventStatus : JSROOT.BIT(15),
4251  kAutoExec : JSROOT.BIT(16),
4252  kMenuBar : JSROOT.BIT(17),
4253  kShowToolBar : JSROOT.BIT(18),
4254  kShowEditor : JSROOT.BIT(19),
4255  kMoveOpaque : JSROOT.BIT(20),
4256  kResizeOpaque : JSROOT.BIT(21),
4257  kIsGrayscale : JSROOT.BIT(22),
4258  kShowToolTips : JSROOT.BIT(23)
4259  };
4260 
4261  TCanvasPainter.prototype.CompeteCanvasSnapDrawing = function() {
4262  if (!this.pad) return;
4263 
4264  if (document) document.title = this.pad.fTitle;
4265 
4266  if (this._all_sections_showed) return;
4267  this._all_sections_showed = true;
4268  this.ShowSection("Menu", this.pad.TestBit(JSROOT.TCanvasStatusBits.kMenuBar));
4269  this.ShowSection("StatusBar", this.pad.TestBit(JSROOT.TCanvasStatusBits.kShowEventStatus));
4270  this.ShowSection("ToolBar", this.pad.TestBit(JSROOT.TCanvasStatusBits.kShowToolBar));
4271  this.ShowSection("Editor", this.pad.TestBit(JSROOT.TCanvasStatusBits.kShowEditor));
4272  this.ShowSection("ToolTips", this.pad.TestBit(JSROOT.TCanvasStatusBits.kShowToolTips));
4273  }
4274 
4275  TCanvasPainter.prototype.HasEventStatus = function() {
4276  return this.has_event_status;
4277  }
4278 
4279  function drawCanvas(divid, can, opt) {
4280  var nocanvas = !can;
4281  if (nocanvas) {
4282  console.log("No canvas specified");
4283  return null;
4284  // can = JSROOT.Create("ROOT::Experimental::TCanvas");
4285  }
4286 
4287  var painter = new TCanvasPainter(can);
4288  painter.normal_canvas = !nocanvas;
4289 
4290  painter.SetDivId(divid, -1); // just assign id
4291  painter.CreateCanvasSvg(0);
4292  painter.SetDivId(divid); // now add to painters list
4293 
4294  painter.AddButton(JSROOT.ToolbarIcons.camera, "Create PNG", "CanvasSnapShot", "Ctrl PrintScreen");
4295  if (JSROOT.gStyle.ContextMenu)
4296  painter.AddButton(JSROOT.ToolbarIcons.question, "Access context menus", "PadContextMenus");
4297 
4298  if (painter.enlarge_main('verify'))
4299  painter.AddButton(JSROOT.ToolbarIcons.circle, "Enlarge canvas", "EnlargePad");
4300 
4301  JSROOT.Painter.SelectActivePad({ pp: painter, active: false });
4302 
4303  painter.DrawPrimitives(0, function() {
4304  painter.ShowButtons();
4305  painter.DrawingReady();
4306  });
4307 
4308  return painter;
4309  }
4310 
4311  // JSROOT.addDrawFunc({ name: "ROOT::Experimental::RPadDisplayItem", icon: "img_canvas", func: drawPad, opt: "" });
4312 
4313  JSROOT.addDrawFunc({ name: "ROOT::Experimental::RHistDrawable<1>", icon: "img_histo1d", prereq: "v7hist", func: "JSROOT.v7.drawHist1", opt: "" });
4314  JSROOT.addDrawFunc({ name: "ROOT::Experimental::RHistDrawable<2>", icon: "img_histo2d", prereq: "v7hist", func: "JSROOT.v7.drawHist2", opt: "" });
4315  JSROOT.addDrawFunc({ name: "ROOT::Experimental::RText", icon: "img_text", prereq: "v7more", func: "JSROOT.v7.drawText", opt: "", direct: true, csstype: "text" });
4316  JSROOT.addDrawFunc({ name: "ROOT::Experimental::RLine", icon: "img_graph", prereq: "v7more", func: "JSROOT.v7.drawLine", opt: "", direct: true, csstype: "line" });
4317  JSROOT.addDrawFunc({ name: "ROOT::Experimental::RBox", icon: "img_graph", prereq: "v7more", func: "JSROOT.v7.drawBox", opt: "", direct: true, csstype: "box" });
4318  JSROOT.addDrawFunc({ name: "ROOT::Experimental::RMarker", icon: "img_graph", prereq: "v7more", func: "JSROOT.v7.drawMarker", opt: "", direct: true, csstype: "marker" });
4319  JSROOT.addDrawFunc({ name: "ROOT::Experimental::RLegend", icon: "img_graph", prereq: "v7more", func: "JSROOT.v7.drawLegend", opt: "", direct: true, csstype: "legend" });
4320 
4321  JSROOT.v7.TAxisPainter = TAxisPainter;
4322  JSROOT.v7.TFramePainter = TFramePainter;
4323  JSROOT.v7.TPadPainter = TPadPainter;
4324  JSROOT.v7.TCanvasPainter = TCanvasPainter;
4325  JSROOT.v7.drawFrame = drawFrame;
4326  JSROOT.v7.drawPad = drawPad;
4327  JSROOT.v7.drawCanvas = drawCanvas;
4328 
4329  return JSROOT;
4330 
4331 }));