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