otsdaq_utilities  v2_05_02_indev
JSRootPainter.more.js
1 
5 (function( factory ) {
6  if ( typeof define === "function" && define.amd ) {
7  // AMD. Register as an anonymous module.
8  define( ['d3', 'JSRootPainter', 'JSRootMath'], factory );
9  } else {
10 
11  if (typeof d3 != 'object')
12  throw new Error('This extension requires d3.v3.js', 'JSRootPainter.more.js');
13 
14  if (typeof JSROOT == 'undefined')
15  throw new Error('JSROOT is not defined', 'JSRootPainter.more.js');
16 
17  if (typeof JSROOT.Painter != 'object')
18  throw new Error('JSROOT.Painter not defined', 'JSRootPainter.more.js');
19 
20  // Browser globals
21  factory(d3, JSROOT);
22  }
23 } (function(d3, JSROOT) {
24 
25  JSROOT.ToolbarIcons.th2color = {
26  recs: [{x:0,y:256,w:13,h:39,f:'rgb(38,62,168)'},{x:13,y:371,w:39,h:39},{y:294,h:39},{y:256,h:39},{y:218,h:39},{x:51,y:410,w:39,h:39},{y:371,h:39},{y:333,h:39},{y:294},{y:256,h:39},{y:218,h:39},{y:179,h:39},{y:141,h:39},{y:102,h:39},{y:64},{x:90,y:448,w:39,h:39},{y:410},{y:371,h:39},{y:333,h:39,f:'rgb(22,82,205)'},{y:294},{y:256,h:39,f:'rgb(16,100,220)'},{y:218,h:39},{y:179,h:39,f:'rgb(22,82,205)'},{y:141,h:39},{y:102,h:39,f:'rgb(38,62,168)'},{y:64},{y:0,h:27},{x:128,y:448,w:39,h:39},{y:410},{y:371,h:39},{y:333,h:39,f:'rgb(22,82,205)'},{y:294,f:'rgb(20,129,214)'},{y:256,h:39,f:'rgb(9,157,204)'},{y:218,h:39,f:'rgb(14,143,209)'},{y:179,h:39,f:'rgb(20,129,214)'},{y:141,h:39,f:'rgb(16,100,220)'},{y:102,h:39,f:'rgb(22,82,205)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{y:0,h:27},{x:166,y:486,h:14},{y:448,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39,f:'rgb(20,129,214)'},{y:294,f:'rgb(82,186,146)'},{y:256,h:39,f:'rgb(179,189,101)'},{y:218,h:39,f:'rgb(116,189,129)'},{y:179,h:39,f:'rgb(82,186,146)'},{y:141,h:39,f:'rgb(14,143,209)'},{y:102,h:39,f:'rgb(16,100,220)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:205,y:486,w:39,h:14},{y:448,h:39},{y:410},{y:371,h:39,f:'rgb(16,100,220)'},{y:333,h:39,f:'rgb(9,157,204)'},{y:294,f:'rgb(149,190,113)'},{y:256,h:39,f:'rgb(244,198,59)'},{y:218,h:39},{y:179,h:39,f:'rgb(226,192,75)'},{y:141,h:39,f:'rgb(13,167,195)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(22,82,205)'},{y:26,h:39,f:'rgb(38,62,168)'},{x:243,y:448,w:39,h:39},{y:410},{y:371,h:39,f:'rgb(18,114,217)'},{y:333,h:39,f:'rgb(30,175,179)'},{y:294,f:'rgb(209,187,89)'},{y:256,h:39,f:'rgb(251,230,29)'},{y:218,h:39,f:'rgb(249,249,15)'},{y:179,h:39,f:'rgb(226,192,75)'},{y:141,h:39,f:'rgb(30,175,179)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:282,y:448,h:39},{y:410},{y:371,h:39,f:'rgb(18,114,217)'},{y:333,h:39,f:'rgb(14,143,209)'},{y:294,f:'rgb(149,190,113)'},{y:256,h:39,f:'rgb(226,192,75)'},{y:218,h:39,f:'rgb(244,198,59)'},{y:179,h:39,f:'rgb(149,190,113)'},{y:141,h:39,f:'rgb(9,157,204)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:320,y:448,w:39,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39,f:'rgb(20,129,214)'},{y:294,f:'rgb(46,183,164)'},{y:256,h:39},{y:218,h:39,f:'rgb(82,186,146)'},{y:179,h:39,f:'rgb(9,157,204)'},{y:141,h:39,f:'rgb(20,129,214)'},{y:102,h:39,f:'rgb(16,100,220)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:358,y:448,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39},{y:294,f:'rgb(16,100,220)'},{y:256,h:39,f:'rgb(20,129,214)'},{y:218,h:39,f:'rgb(14,143,209)'},{y:179,h:39,f:'rgb(18,114,217)'},{y:141,h:39,f:'rgb(22,82,205)'},{y:102,h:39,f:'rgb(38,62,168)'},{y:64},{y:26,h:39},{x:397,y:448,w:39,h:39},{y:371,h:39},{y:333,h:39},{y:294,f:'rgb(22,82,205)'},{y:256,h:39},{y:218,h:39},{y:179,h:39,f:'rgb(38,62,168)'},{y:141,h:39},{y:102,h:39},{y:64},{y:26,h:39},{x:435,y:410,h:39},{y:371,h:39},{y:333,h:39},{y:294},{y:256,h:39},{y:218,h:39},{y:179,h:39},{y:141,h:39},{y:102,h:39},{y:64},{x:474,y:256,h:39},{y:179,h:39}]
27  };
28 
29  JSROOT.ToolbarIcons.th2colorz = {
30  recs: [{x:128,y:486,w:256,h:26,f:'rgb(38,62,168)'},{y:461,f:'rgb(22,82,205)'},{y:435,f:'rgb(16,100,220)'},{y:410,f:'rgb(18,114,217)'},{y:384,f:'rgb(20,129,214)'},{y:358,f:'rgb(14,143,209)'},{y:333,f:'rgb(9,157,204)'},{y:307,f:'rgb(13,167,195)'},{y:282,f:'rgb(30,175,179)'},{y:256,f:'rgb(46,183,164)'},{y:230,f:'rgb(82,186,146)'},{y:205,f:'rgb(116,189,129)'},{y:179,f:'rgb(149,190,113)'},{y:154,f:'rgb(179,189,101)'},{y:128,f:'rgb(209,187,89)'},{y:102,f:'rgb(226,192,75)'},{y:77,f:'rgb(244,198,59)'},{y:51,f:'rgb(253,210,43)'},{y:26,f:'rgb(251,230,29)'},{y:0,f:'rgb(249,249,15)'}]
31  };
32 
33  JSROOT.ToolbarIcons.th2draw3d = {
34  path: "M172.768,0H51.726C23.202,0,0.002,23.194,0.002,51.712v89.918c0,28.512,23.2,51.718,51.724,51.718h121.042 c28.518,0,51.724-23.2,51.724-51.718V51.712C224.486,23.194,201.286,0,172.768,0z M177.512,141.63c0,2.611-2.124,4.745-4.75,4.745 H51.726c-2.626,0-4.751-2.134-4.751-4.745V51.712c0-2.614,2.125-4.739,4.751-4.739h121.042c2.62,0,4.75,2.125,4.75,4.739 L177.512,141.63L177.512,141.63z "+
35  "M460.293,0H339.237c-28.521,0-51.721,23.194-51.721,51.712v89.918c0,28.512,23.2,51.718,51.721,51.718h121.045 c28.521,0,51.721-23.2,51.721-51.718V51.712C512.002,23.194,488.802,0,460.293,0z M465.03,141.63c0,2.611-2.122,4.745-4.748,4.745 H339.237c-2.614,0-4.747-2.128-4.747-4.745V51.712c0-2.614,2.133-4.739,4.747-4.739h121.045c2.626,0,4.748,2.125,4.748,4.739 V141.63z "+
36  "M172.768,256.149H51.726c-28.524,0-51.724,23.205-51.724,51.726v89.915c0,28.504,23.2,51.715,51.724,51.715h121.042 c28.518,0,51.724-23.199,51.724-51.715v-89.915C224.486,279.354,201.286,256.149,172.768,256.149z M177.512,397.784 c0,2.615-2.124,4.736-4.75,4.736H51.726c-2.626-0.006-4.751-2.121-4.751-4.736v-89.909c0-2.626,2.125-4.753,4.751-4.753h121.042 c2.62,0,4.75,2.116,4.75,4.753L177.512,397.784L177.512,397.784z "+
37  "M460.293,256.149H339.237c-28.521,0-51.721,23.199-51.721,51.726v89.915c0,28.504,23.2,51.715,51.721,51.715h121.045 c28.521,0,51.721-23.199,51.721-51.715v-89.915C512.002,279.354,488.802,256.149,460.293,256.149z M465.03,397.784 c0,2.615-2.122,4.736-4.748,4.736H339.237c-2.614,0-4.747-2.121-4.747-4.736v-89.909c0-2.626,2.121-4.753,4.747-4.753h121.045 c2.615,0,4.748,2.116,4.748,4.753V397.784z"
38  };
39 
40  JSROOT.Painter.CreateDefaultPalette = function() {
41 
42  function HLStoRGB(h, l, s) {
43  var r, g, b;
44  if (s < 1e-300) {
45  r = g = b = l; // achromatic
46  } else {
47  function hue2rgb(p, q, t) {
48  if (t < 0) t += 1;
49  if (t > 1) t -= 1;
50  if (t < 1 / 6) return p + (q - p) * 6 * t;
51  if (t < 1 / 2) return q;
52  if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
53  return p;
54  }
55  var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
56  var p = 2 * l - q;
57  r = hue2rgb(p, q, h + 1 / 3);
58  g = hue2rgb(p, q, h);
59  b = hue2rgb(p, q, h - 1 / 3);
60  }
61  return 'rgb(' + Math.round(r * 255) + ',' + Math.round(g * 255) + ',' + Math.round(b * 255) + ')';
62  }
63 
64  var palette = [], saturation = 1, lightness = 0.5, maxHue = 280, minHue = 0, maxPretty = 50;
65  for (var i = 0; i < maxPretty; ++i) {
66  var hue = (maxHue - (i + 1) * ((maxHue - minHue) / maxPretty)) / 360.0;
67  var rgbval = HLStoRGB(hue, lightness, saturation);
68  palette.push(rgbval);
69  }
70  return palette;
71  }
72 
73  JSROOT.Painter.CreateGrayPalette = function() {
74  var palette = [];
75  for (var i = 0; i < 50; ++i) {
76  var code = Math.round((i+2)/60 * 255 );
77  palette.push('rgb('+code+','+code+','+code+')');
78  }
79  return palette;
80  }
81 
82  JSROOT.Painter.CreateGradientColorTable = function(Stops, Red, Green, Blue, NColors, alpha) {
83  // skip all checks
84  var palette = [];
85 
86  for (var g = 1; g < Stops.length; g++) {
87  // create the colors...
88  var nColorsGradient = parseInt(Math.floor(NColors*Stops[g]) - Math.floor(NColors*Stops[g-1]));
89  for (var c = 0; c < nColorsGradient; c++) {
90  var col = Math.round(Red[g-1] + c * (Red[g] - Red[g-1])/nColorsGradient) + "," +
91  Math.round(Green[g-1] + c * (Green[g] - Green[g-1])/ nColorsGradient) + "," +
92  Math.round(Blue[g-1] + c * (Blue[g] - Blue[g-1])/ nColorsGradient);
93  palette.push("rgb("+col+")");
94  }
95  }
96 
97  return palette;
98  }
99 
100  JSROOT.Painter.GetColorPalette = function(col,alfa) {
101  if ((col == null) || (col==0)) col = JSROOT.gStyle.Palette;
102  if ((col>0) && (col<10)) return JSROOT.Painter.CreateGrayPalette();
103  if (col < 51) return JSROOT.Painter.CreateDefaultPalette();
104  if (col > 112) col = 57;
105 
106  var red, green, blue,
107  stops = [ 0.0000, 0.1250, 0.2500, 0.3750, 0.5000, 0.6250, 0.7500, 0.8750, 1.0000 ];
108  switch(col) {
109 
110  // Deep Sea
111  case 51:
112  red = [ 0, 9, 13, 17, 24, 32, 27, 25, 29];
113  green = [ 0, 0, 0, 2, 37, 74, 113, 160, 221];
114  blue = [ 28, 42, 59, 78, 98, 129, 154, 184, 221];
115  break;
116 
117  // Grey Scale
118  case 52:
119  red = [ 0, 32, 64, 96, 128, 160, 192, 224, 255];
120  green = [ 0, 32, 64, 96, 128, 160, 192, 224, 255];
121  blue = [ 0, 32, 64, 96, 128, 160, 192, 224, 255];
122  break;
123 
124  // Dark Body Radiator
125  case 53:
126  red = [ 0, 45, 99, 156, 212, 230, 237, 234, 242];
127  green = [ 0, 0, 0, 45, 101, 168, 238, 238, 243];
128  blue = [ 0, 1, 1, 3, 9, 8, 11, 95, 230];
129  break;
130 
131  // Two-color hue (dark blue through neutral gray to bright yellow)
132  case 54:
133  red = [ 0, 22, 44, 68, 93, 124, 160, 192, 237];
134  green = [ 0, 16, 41, 67, 93, 125, 162, 194, 241];
135  blue = [ 97, 100, 99, 99, 93, 68, 44, 26, 74];
136  break;
137 
138  // Rain Bow
139  case 55:
140  red = [ 0, 5, 15, 35, 102, 196, 208, 199, 110];
141  green = [ 0, 48, 124, 192, 206, 226, 97, 16, 0];
142  blue = [ 99, 142, 198, 201, 90, 22, 13, 8, 2];
143  break;
144 
145  // Inverted Dark Body Radiator
146  case 56:
147  red = [ 242, 234, 237, 230, 212, 156, 99, 45, 0];
148  green = [ 243, 238, 238, 168, 101, 45, 0, 0, 0];
149  blue = [ 230, 95, 11, 8, 9, 3, 1, 1, 0];
150  break;
151 
152  // Bird
153  case 57:
154  red = [ 0.2082*255, 0.0592*255, 0.0780*255, 0.0232*255, 0.1802*255, 0.5301*255, 0.8186*255, 0.9956*255, 0.9764*255];
155  green = [ 0.1664*255, 0.3599*255, 0.5041*255, 0.6419*255, 0.7178*255, 0.7492*255, 0.7328*255, 0.7862*255, 0.9832*255];
156  blue = [ 0.5293*255, 0.8684*255, 0.8385*255, 0.7914*255, 0.6425*255, 0.4662*255, 0.3499*255, 0.1968*255, 0.0539*255];
157  break;
158 
159  // Cubehelix
160  case 58:
161  red = [ 0.0000, 0.0956*255, 0.0098*255, 0.2124*255, 0.6905*255, 0.9242*255, 0.7914*255, 0.7596*255, 1.0000*255];
162  green = [ 0.0000, 0.1147*255, 0.3616*255, 0.5041*255, 0.4577*255, 0.4691*255, 0.6905*255, 0.9237*255, 1.0000*255];
163  blue = [ 0.0000, 0.2669*255, 0.3121*255, 0.1318*255, 0.2236*255, 0.6741*255, 0.9882*255, 0.9593*255, 1.0000*255];
164  break;
165 
166  // Green Red Violet
167  case 59:
168  red = [13, 23, 25, 63, 76, 104, 137, 161, 206];
169  green = [95, 67, 37, 21, 0, 12, 35, 52, 79];
170  blue = [ 4, 3, 2, 6, 11, 22, 49, 98, 208];
171  break;
172 
173  // Blue Red Yellow
174  case 60:
175  red = [0, 61, 89, 122, 143, 160, 185, 204, 231];
176  green = [0, 0, 0, 0, 14, 37, 72, 132, 235];
177  blue = [0, 140, 224, 144, 4, 5, 6, 9, 13];
178  break;
179 
180  // Ocean
181  case 61:
182  red = [ 14, 7, 2, 0, 5, 11, 55, 131, 229];
183  green = [105, 56, 26, 1, 42, 74, 131, 171, 229];
184  blue = [ 2, 21, 35, 60, 92, 113, 160, 185, 229];
185  break;
186 
187  // Color Printable On Grey
188  case 62:
189  red = [ 0, 0, 0, 70, 148, 231, 235, 237, 244];
190  green = [ 0, 0, 0, 0, 0, 69, 67, 216, 244];
191  blue = [ 0, 102, 228, 231, 177, 124, 137, 20, 244];
192  break;
193 
194  // Alpine
195  case 63:
196  red = [ 50, 56, 63, 68, 93, 121, 165, 192, 241];
197  green = [ 66, 81, 91, 96, 111, 128, 155, 189, 241];
198  blue = [ 97, 91, 75, 65, 77, 103, 143, 167, 217];
199  break;
200 
201  // Aquamarine
202  case 64:
203  red = [ 145, 166, 167, 156, 131, 114, 101, 112, 132];
204  green = [ 158, 178, 179, 181, 163, 154, 144, 152, 159];
205  blue = [ 190, 199, 201, 192, 176, 169, 160, 166, 190];
206  break;
207 
208  // Army
209  case 65:
210  red = [ 93, 91, 99, 108, 130, 125, 132, 155, 174];
211  green = [ 126, 124, 128, 129, 131, 121, 119, 153, 173];
212  blue = [ 103, 94, 87, 85, 80, 85, 107, 120, 146];
213  break;
214 
215  // Atlantic
216  case 66:
217  red = [ 24, 40, 69, 90, 104, 114, 120, 132, 103];
218  green = [ 29, 52, 94, 127, 150, 162, 159, 151, 101];
219  blue = [ 29, 52, 96, 132, 162, 181, 184, 186, 131];
220  break;
221 
222  // Aurora
223  case 67:
224  red = [ 46, 38, 61, 92, 113, 121, 132, 150, 191];
225  green = [ 46, 36, 40, 69, 110, 135, 131, 92, 34];
226  blue = [ 46, 80, 74, 70, 81, 105, 165, 211, 225];
227  break;
228 
229  // Avocado
230  case 68:
231  red = [ 0, 4, 12, 30, 52, 101, 142, 190, 237];
232  green = [ 0, 40, 86, 121, 140, 172, 187, 213, 240];
233  blue = [ 0, 9, 14, 18, 21, 23, 27, 35, 101];
234  break;
235 
236  // Beach
237  case 69:
238  red = [ 198, 206, 206, 211, 198, 181, 161, 171, 244];
239  green = [ 103, 133, 150, 172, 178, 174, 163, 175, 244];
240  blue = [ 49, 54, 55, 66, 91, 130, 184, 224, 244];
241  break;
242 
243  // Black Body
244  case 70:
245  red = [ 243, 243, 240, 240, 241, 239, 186, 151, 129];
246  green = [ 0, 46, 99, 149, 194, 220, 183, 166, 147];
247  blue = [ 6, 8, 36, 91, 169, 235, 246, 240, 233];
248  break;
249 
250  // Blue Green Yellow
251  case 71:
252  red = [ 22, 19, 19, 25, 35, 53, 88, 139, 210];
253  green = [ 0, 32, 69, 108, 135, 159, 183, 198, 215];
254  blue = [ 77, 96, 110, 116, 110, 100, 90, 78, 70];
255  break;
256 
257  // Brown Cyan
258  case 72:
259  red = [ 68, 116, 165, 182, 189, 180, 145, 111, 71];
260  green = [ 37, 82, 135, 178, 204, 225, 221, 202, 147];
261  blue = [ 16, 55, 105, 147, 196, 226, 232, 224, 178];
262  break;
263 
264  // CMYK
265  case 73:
266  red = [ 61, 99, 136, 181, 213, 225, 198, 136, 24];
267  green = [ 149, 140, 96, 83, 132, 178, 190, 135, 22];
268  blue = [ 214, 203, 168, 135, 110, 100, 111, 113, 22];
269  break;
270 
271  // Candy
272  case 74:
273  red = [ 76, 120, 156, 183, 197, 180, 162, 154, 140];
274  green = [ 34, 35, 42, 69, 102, 137, 164, 188, 197];
275  blue = [ 64, 69, 78, 105, 142, 177, 205, 217, 198];
276  break;
277 
278  // Cherry
279  case 75:
280  red = [ 37, 102, 157, 188, 196, 214, 223, 235, 251];
281  green = [ 37, 29, 25, 37, 67, 91, 132, 185, 251];
282  blue = [ 37, 32, 33, 45, 66, 98, 137, 187, 251];
283  break;
284 
285  // Coffee
286  case 76:
287  red = [ 79, 100, 119, 137, 153, 172, 192, 205, 250];
288  green = [ 63, 79, 93, 103, 115, 135, 167, 196, 250];
289  blue = [ 51, 59, 66, 61, 62, 70, 110, 160, 250];
290  break;
291 
292  // Dark Rain Bow
293  case 77:
294  red = [ 43, 44, 50, 66, 125, 172, 178, 155, 157];
295  green = [ 63, 63, 85, 101, 138, 163, 122, 51, 39];
296  blue = [ 121, 101, 58, 44, 47, 55, 57, 44, 43];
297  break;
298 
299  // Dark Terrain
300  case 78:
301  red = [ 0, 41, 62, 79, 90, 87, 99, 140, 228];
302  green = [ 0, 57, 81, 93, 85, 70, 71, 125, 228];
303  blue = [ 95, 91, 91, 82, 60, 43, 44, 112, 228];
304  break;
305 
306  // Fall
307  case 79:
308  red = [ 49, 59, 72, 88, 114, 141, 176, 205, 222];
309  green = [ 78, 72, 66, 57, 59, 75, 106, 142, 173];
310  blue = [ 78, 55, 46, 40, 39, 39, 40, 41, 47];
311  break;
312 
313  // Fruit Punch
314  case 80:
315  red = [ 243, 222, 201, 185, 165, 158, 166, 187, 219];
316  green = [ 94, 108, 132, 135, 125, 96, 68, 51, 61];
317  blue = [ 7, 9, 12, 19, 45, 89, 118, 146, 118];
318  break;
319 
320  // Fuchsia
321  case 81:
322  red = [ 19, 44, 74, 105, 137, 166, 194, 206, 220];
323  green = [ 19, 28, 40, 55, 82, 110, 159, 181, 220];
324  blue = [ 19, 42, 68, 96, 129, 157, 188, 203, 220];
325  break;
326 
327  // Grey Yellow
328  case 82:
329  red = [ 33, 44, 70, 99, 140, 165, 199, 211, 216];
330  green = [ 38, 50, 76, 105, 140, 165, 191, 189, 167];
331  blue = [ 55, 67, 97, 124, 140, 166, 163, 129, 52];
332  break;
333 
334  // Green Brown Terrain
335  case 83:
336  red = [ 0, 33, 73, 124, 136, 152, 159, 171, 223];
337  green = [ 0, 43, 92, 124, 134, 126, 121, 144, 223];
338  blue = [ 0, 43, 68, 76, 73, 64, 72, 114, 223];
339  break;
340 
341  // Green Pink
342  case 84:
343  red = [ 5, 18, 45, 124, 193, 223, 205, 128, 49];
344  green = [ 48, 134, 207, 230, 193, 113, 28, 0, 7];
345  blue = [ 6, 15, 41, 121, 193, 226, 208, 130, 49];
346  break;
347 
348  // Island
349  case 85:
350  red = [ 180, 106, 104, 135, 164, 188, 189, 165, 144];
351  green = [ 72, 126, 154, 184, 198, 207, 205, 190, 179];
352  blue = [ 41, 120, 158, 188, 194, 181, 145, 100, 62];
353  break;
354 
355  // Lake
356  case 86:
357  red = [ 57, 72, 94, 117, 136, 154, 174, 192, 215];
358  green = [ 0, 33, 68, 109, 140, 171, 192, 196, 209];
359  blue = [ 116, 137, 173, 201, 200, 201, 203, 190, 187];
360  break;
361 
362  // Light Temperature
363  case 87:
364  red = [ 31, 71, 123, 160, 210, 222, 214, 199, 183];
365  green = [ 40, 117, 171, 211, 231, 220, 190, 132, 65];
366  blue = [ 234, 214, 228, 222, 210, 160, 105, 60, 34];
367  break;
368 
369  // Light Terrain
370  case 88:
371  red = [ 123, 108, 109, 126, 154, 172, 188, 196, 218];
372  green = [ 184, 138, 130, 133, 154, 175, 188, 196, 218];
373  blue = [ 208, 130, 109, 99, 110, 122, 150, 171, 218];
374  break;
375 
376  // Mint
377  case 89:
378  red = [ 105, 106, 122, 143, 159, 172, 176, 181, 207];
379  green = [ 252, 197, 194, 187, 174, 162, 153, 136, 125];
380  blue = [ 146, 133, 144, 155, 163, 167, 166, 162, 174];
381  break;
382 
383  // Neon
384  case 90:
385  red = [ 171, 141, 145, 152, 154, 159, 163, 158, 177];
386  green = [ 236, 143, 100, 63, 53, 55, 44, 31, 6];
387  blue = [ 59, 48, 46, 44, 42, 54, 82, 112, 179];
388  break;
389 
390  // Pastel
391  case 91:
392  red = [ 180, 190, 209, 223, 204, 228, 205, 152, 91];
393  green = [ 93, 125, 147, 172, 181, 224, 233, 198, 158];
394  blue = [ 236, 218, 160, 133, 114, 132, 162, 220, 218];
395  break;
396 
397  // Pearl
398  case 92:
399  red = [ 225, 183, 162, 135, 115, 111, 119, 145, 211];
400  green = [ 205, 177, 166, 135, 124, 117, 117, 132, 172];
401  blue = [ 186, 165, 155, 135, 126, 130, 150, 178, 226];
402  break;
403 
404  // Pigeon
405  case 93:
406  red = [ 39, 43, 59, 63, 80, 116, 153, 177, 223];
407  green = [ 39, 43, 59, 74, 91, 114, 139, 165, 223];
408  blue = [ 39, 50, 59, 70, 85, 115, 151, 176, 223];
409  break;
410 
411  // Plum
412  case 94:
413  red = [ 0, 38, 60, 76, 84, 89, 101, 128, 204];
414  green = [ 0, 10, 15, 23, 35, 57, 83, 123, 199];
415  blue = [ 0, 11, 22, 40, 63, 86, 97, 94, 85];
416  break;
417 
418  // Red Blue
419  case 95:
420  red = [ 94, 112, 141, 165, 167, 140, 91, 49, 27];
421  green = [ 27, 46, 88, 135, 166, 161, 135, 97, 58];
422  blue = [ 42, 52, 81, 106, 139, 158, 155, 137, 116];
423  break;
424 
425  // Rose
426  case 96:
427  red = [ 30, 49, 79, 117, 135, 151, 146, 138, 147];
428  green = [ 63, 60, 72, 90, 94, 94, 68, 46, 16];
429  blue = [ 18, 28, 41, 56, 62, 63, 50, 36, 21];
430  break;
431 
432  // Rust
433  case 97:
434  red = [ 0, 30, 63, 101, 143, 152, 169, 187, 230];
435  green = [ 0, 14, 28, 42, 58, 61, 67, 74, 91];
436  blue = [ 39, 26, 21, 18, 15, 14, 14, 13, 13];
437  break;
438 
439  // Sandy Terrain
440  case 98:
441  red = [ 149, 140, 164, 179, 182, 181, 131, 87, 61];
442  green = [ 62, 70, 107, 136, 144, 138, 117, 87, 74];
443  blue = [ 40, 38, 45, 49, 49, 49, 38, 32, 34];
444  break;
445 
446  // Sienna
447  case 99:
448  red = [ 99, 112, 148, 165, 179, 182, 183, 183, 208];
449  green = [ 39, 40, 57, 79, 104, 127, 148, 161, 198];
450  blue = [ 15, 16, 18, 33, 51, 79, 103, 129, 177];
451  break;
452 
453  // Solar
454  case 100:
455  red = [ 99, 116, 154, 174, 200, 196, 201, 201, 230];
456  green = [ 0, 0, 8, 32, 58, 83, 119, 136, 173];
457  blue = [ 5, 6, 7, 9, 9, 14, 17, 19, 24];
458  break;
459 
460  // South West
461  case 101:
462  red = [ 82, 106, 126, 141, 155, 163, 142, 107, 66];
463  green = [ 62, 44, 69, 107, 135, 152, 149, 132, 119];
464  blue = [ 39, 25, 31, 60, 73, 68, 49, 72, 188];
465  break;
466 
467  // Starry Night
468  case 102:
469  red = [ 18, 29, 44, 72, 116, 158, 184, 208, 221];
470  green = [ 27, 46, 71, 105, 146, 177, 189, 190, 183];
471  blue = [ 39, 55, 80, 108, 130, 133, 124, 100, 76];
472  break;
473 
474  // Sunset
475  case 103:
476  red = [ 0, 48, 119, 173, 212, 224, 228, 228, 245];
477  green = [ 0, 13, 30, 47, 79, 127, 167, 205, 245];
478  blue = [ 0, 68, 75, 43, 16, 22, 55, 128, 245];
479  break;
480 
481  // Temperature Map
482  case 104:
483  red = [ 34, 70, 129, 187, 225, 226, 216, 193, 179];
484  green = [ 48, 91, 147, 194, 226, 229, 196, 110, 12];
485  blue = [ 234, 212, 216, 224, 206, 110, 53, 40, 29];
486  break;
487 
488  // Thermometer
489  case 105:
490  red = [ 30, 55, 103, 147, 174, 203, 188, 151, 105];
491  green = [ 0, 65, 138, 182, 187, 175, 121, 53, 9];
492  blue = [ 191, 202, 212, 208, 171, 140, 97, 57, 30];
493  break;
494 
495  // Valentine
496  case 106:
497  red = [ 112, 97, 113, 125, 138, 159, 178, 188, 225];
498  green = [ 16, 17, 24, 37, 56, 81, 110, 136, 189];
499  blue = [ 38, 35, 46, 59, 78, 103, 130, 152, 201];
500  break;
501 
502  // Visible Spectrum
503  case 107:
504  red = [ 18, 72, 5, 23, 29, 201, 200, 98, 29];
505  green = [ 0, 0, 43, 167, 211, 117, 0, 0, 0];
506  blue = [ 51, 203, 177, 26, 10, 9, 8, 3, 0];
507  break;
508 
509  // Water Melon
510  case 108:
511  red = [ 19, 42, 64, 88, 118, 147, 175, 187, 205];
512  green = [ 19, 55, 89, 125, 154, 169, 161, 129, 70];
513  blue = [ 19, 32, 47, 70, 100, 128, 145, 130, 75];
514  break;
515 
516  // Cool
517  case 109:
518  red = [ 33, 31, 42, 68, 86, 111, 141, 172, 227];
519  green = [ 255, 175, 145, 106, 88, 55, 15, 0, 0];
520  blue = [ 255, 205, 202, 203, 208, 205, 203, 206, 231];
521  break;
522 
523  // Copper
524  case 110:
525  red = [ 0, 25, 50, 79, 110, 145, 181, 201, 254];
526  green = [ 0, 16, 30, 46, 63, 82, 101, 124, 179];
527  blue = [ 0, 12, 21, 29, 39, 49, 61, 74, 103];
528  break;
529 
530  // Gist Earth
531  case 111:
532  red = [ 0, 13, 30, 44, 72, 120, 156, 200, 247];
533  green = [ 0, 36, 84, 117, 141, 153, 151, 158, 247];
534  blue = [ 0, 94, 100, 82, 56, 66, 76, 131, 247];
535  break;
536 
537  // Viridis
538  case 112:
539  red = [ 26, 51, 43, 33, 28, 35, 74, 144, 246];
540  green = [ 9, 24, 55, 87, 118, 150, 180, 200, 222];
541  blue = [ 30, 96, 112, 114, 112, 101, 72, 35, 0];
542  break;
543 
544  default:
545  return JSROOT.Painter.CreateDefaultPalette();
546  }
547 
548  return JSROOT.Painter.CreateGradientColorTable(stops, red, green, blue, 255, alfa);
549  }
550 
551  // ==============================================================================
552 
553 
554  JSROOT.Painter.drawEllipse = function(divid, obj, opt) {
555 
556  this.SetDivId(divid);
557 
558  this.Redraw = function() {
559  var ellipse = this.GetObject();
560 
561  if(!this.lineatt) this.lineatt = JSROOT.Painter.createAttLine(ellipse);
562  if (!this.fillatt) this.fillatt = this.createAttFill(ellipse);
563 
564  // create svg:g container for ellipse drawing
565  this.RecreateDrawG(this.main_painter() == null);
566 
567  var x = this.AxisToSvg("x", ellipse.fX1);
568  var y = this.AxisToSvg("y", ellipse.fY1);
569  var rx = this.AxisToSvg("x", ellipse.fX1 + ellipse.fR1) - x;
570  var ry = y - this.AxisToSvg("y", ellipse.fY1 + ellipse.fR2);
571 
572  if ((ellipse.fPhimin == 0) && (ellipse.fPhimax == 360) && (ellipse.fTheta == 0)) {
573  // this is simple case, which could be drawn with svg:ellipse
574  this.draw_g
575  .append("svg:ellipse")
576  .attr("cx", x.toFixed(1)).attr("cy", y.toFixed(1))
577  .attr("rx", rx.toFixed(1)).attr("ry", ry.toFixed(1))
578  .call(this.lineatt.func).call(this.fillatt.func);
579  return;
580  }
581 
582  // here svg:path is used to draw more complex figure
583 
584  var ct = Math.cos(Math.PI*ellipse.fTheta/180.);
585  var st = Math.sin(Math.PI*ellipse.fTheta/180.);
586 
587  var dx1 = rx * Math.cos(ellipse.fPhimin*Math.PI/180.);
588  var dy1 = ry * Math.sin(ellipse.fPhimin*Math.PI/180.);
589  var x1 = dx1*ct - dy1*st;
590  var y1 = -dx1*st - dy1*ct;
591 
592  var dx2 = rx * Math.cos(ellipse.fPhimax*Math.PI/180.);
593  var dy2 = ry * Math.sin(ellipse.fPhimax*Math.PI/180.);
594  var x2 = dx2*ct - dy2*st;
595  var y2 = -dx2*st - dy2*ct;
596 
597  this.draw_g
598  .attr("transform","translate("+x.toFixed(1)+","+y.toFixed(1)+")")
599  .append("svg:path")
600  .attr("d", "M 0,0" +
601  " L " + x1.toFixed(1) + "," + y1.toFixed(1) +
602  " A " + rx.toFixed(1) + " " + ry.toFixed(1) + " " + -ellipse.fTheta.toFixed(1) + " 1 0 " + x2.toFixed(1) + "," + y2.toFixed(1) +
603  " L 0,0 Z")
604  .call(this.lineatt.func).call(this.fillatt.func);
605  }
606 
607  this.Redraw(); // actual drawing
608  return this.DrawingReady();
609  }
610 
611  // =============================================================================
612 
613  JSROOT.Painter.drawLine = function(divid, obj, opt) {
614 
615  this.SetDivId(divid);
616 
617  this.Redraw = function() {
618  var line = this.GetObject(),
619  lineatt = JSROOT.Painter.createAttLine(line);
620 
621  // create svg:g container for line drawing
622  this.RecreateDrawG(this.main_painter() == null);
623 
624  this.draw_g
625  .append("svg:line")
626  .attr("x1", this.AxisToSvg("x", line.fX1).toFixed(1))
627  .attr("y1", this.AxisToSvg("y", line.fY1).toFixed(1))
628  .attr("x2", this.AxisToSvg("x", line.fX2).toFixed(1))
629  .attr("y2", this.AxisToSvg("y", line.fY2).toFixed(1))
630  .call(lineatt.func);
631  }
632 
633  this.Redraw(); // actual drawing
634 
635  return this.DrawingReady();
636  }
637 
638  // =============================================================================
639 
640  JSROOT.Painter.drawBox = function(divid, obj, opt) {
641 
642  this.SetDivId(divid);
643 
644  this.Redraw = function() {
645  var box = this.GetObject(),
646  lineatt = JSROOT.Painter.createAttLine(box),
647  fillatt = this.createAttFill(box);
648 
649  // create svg:g container for line drawing
650  this.RecreateDrawG(this.main_painter() == null);
651 
652  var x1 = Math.round(this.AxisToSvg("x", box.fX1)),
653  x2 = Math.round(this.AxisToSvg("x", box.fX2)),
654  y1 = Math.round(this.AxisToSvg("y", box.fY1)),
655  y2 = Math.round(this.AxisToSvg("y", box.fY2));
656 
657  this.draw_g
658  .append("svg:rect")
659  .attr("x", Math.min(x1,x2))
660  .attr("y", Math.min(y1,y2))
661  .attr("width", Math.abs(x2-x1))
662  .attr("height", Math.abs(y1-y2))
663  .call(lineatt.func)
664  .call(fillatt.func);
665  }
666 
667  this.Redraw(); // actual drawing
668 
669  return this.DrawingReady();
670  }
671 
672  // ======================================================================================
673 
674  JSROOT.Painter.drawArrow = function(divid, obj, opt) {
675 
676  this.SetDivId(divid);
677 
678  this.Redraw = function() {
679  var arrow = this.GetObject();
680  if (!this.lineatt) this.lineatt = JSROOT.Painter.createAttLine(arrow);
681  if (!this.fillatt) this.fillatt = this.createAttFill(arrow);
682 
683  var wsize = Math.max(this.pad_width(), this.pad_height()) * arrow.fArrowSize;
684  if (wsize<3) wsize = 3;
685  var hsize = wsize * Math.tan(arrow.fAngle/2 * (Math.PI/180));
686 
687  // create svg:g container for line drawing
688  this.RecreateDrawG(this.main_painter() == null);
689 
690  var x1 = this.AxisToSvg("x", arrow.fX1),
691  y1 = this.AxisToSvg("y", arrow.fY1),
692  x2 = this.AxisToSvg("x", arrow.fX2),
693  y2 = this.AxisToSvg("y", arrow.fY2),
694  right_arrow = "M0,0" + " L"+wsize.toFixed(1) +","+hsize.toFixed(1) + " L0," + (hsize*2).toFixed(1),
695  left_arrow = "M" + wsize.toFixed(1) + ", 0" + " L 0," + hsize.toFixed(1) + " L " + wsize.toFixed(1) + "," + (hsize*2).toFixed(1),
696  m_start = null, m_mid = null, m_end = null, defs = null,
697  oo = arrow.fOption, len = oo.length;
698 
699  if (oo.indexOf("<")==0) {
700  var closed = (oo.indexOf("<|") == 0);
701  if (!defs) defs = this.draw_g.append("defs");
702  m_start = "jsroot_arrowmarker_" + JSROOT.Painter.arrowcnt++;
703  var beg = defs.append("svg:marker")
704  .attr("id", m_start)
705  .attr("markerWidth", wsize.toFixed(1))
706  .attr("markerHeight", (hsize*2).toFixed(1))
707  .attr("refX", "0")
708  .attr("refY", hsize.toFixed(1))
709  .attr("orient", "auto")
710  .attr("markerUnits", "userSpaceOnUse")
711  .append("svg:path")
712  .style("fill","none")
713  .attr("d", left_arrow + (closed ? " Z" : ""))
714  .call(this.lineatt.func);
715  if (closed) beg.call(this.fillatt.func);
716  }
717 
718  var midkind = 0;
719  if (oo.indexOf("->-")>=0) midkind = 1; else
720  if (oo.indexOf("-|>-")>=0) midkind = 11; else
721  if (oo.indexOf("-<-")>=0) midkind = 2; else
722  if (oo.indexOf("-<|-")>=0) midkind = 12;
723 
724  if (midkind > 0) {
725  var closed = midkind > 10;
726  if (!defs) defs = this.draw_g.append("defs");
727  m_mid = "jsroot_arrowmarker_" + JSROOT.Painter.arrowcnt++;
728 
729  var mid = defs.append("svg:marker")
730  .attr("id", m_mid)
731  .attr("markerWidth", wsize.toFixed(1))
732  .attr("markerHeight", (hsize*2).toFixed(1))
733  .attr("refX", (wsize*0.5).toFixed(1))
734  .attr("refY", hsize.toFixed(1))
735  .attr("orient", "auto")
736  .attr("markerUnits", "userSpaceOnUse")
737  .append("svg:path")
738  .style("fill","none")
739  .attr("d", ((midkind % 10 == 1) ? right_arrow : left_arrow) +
740  ((midkind > 10) ? " Z" : ""))
741  .call(this.lineatt.func);
742  if (midkind > 10) mid.call(this.fillatt.func);
743  }
744 
745  if (oo.lastIndexOf(">") == len-1) {
746  var closed = (oo.lastIndexOf("|>") == len-2) && (len>1);
747  if (!defs) defs = this.draw_g.append("defs");
748  m_end = "jsroot_arrowmarker_" + JSROOT.Painter.arrowcnt++;
749  var end = defs.append("svg:marker")
750  .attr("id", m_end)
751  .attr("markerWidth", wsize.toFixed(1))
752  .attr("markerHeight", (hsize*2).toFixed(1))
753  .attr("refX", wsize.toFixed(1))
754  .attr("refY", hsize.toFixed(1))
755  .attr("orient", "auto")
756  .attr("markerUnits", "userSpaceOnUse")
757  .append("svg:path")
758  .style("fill","none")
759  .attr("d", right_arrow + (closed ? " Z" : ""))
760  .call(this.lineatt.func);
761  if (closed) end.call(this.fillatt.func);
762  }
763 
764  var path = this.draw_g
765  .append("svg:path")
766  .attr("d", "M" + x1.toFixed(1) + "," + y1.toFixed(1) +
767  ((m_mid == null) ? "" : "L" + (x1/2+x2/2).toFixed(1) + "," + (y1/2+y2/2).toFixed(1)) +
768  " L" + x2.toFixed(1) + "," + y2.toFixed(1))
769  .call(this.lineatt.func);
770 
771  if (m_start!=null) path.style("marker-start","url(#" + m_start + ")");
772  if (m_mid!=null) path.style("marker-mid","url(#" + m_mid + ")");
773  if (m_end!=null) path.style("marker-end","url(#" + m_end + ")");
774  }
775 
776  if (!('arrowcnt' in JSROOT.Painter)) JSROOT.Painter.arrowcnt = 0;
777 
778  this.Redraw(); // actual drawing
779  return this.DrawingReady();
780  }
781 
782  // ================================================================================
783 
784  JSROOT.Painter.BuildSvgPath = function(kind, bins, height, ndig) {
785  // function used to provide svg:path for the smoothed curves
786  // reuse code from d3.js. Used in TF1 and TGraph painters
787  // kind should contain "bezier" or "line". If first symbol "L", than it used to continue drawing
788 
789  var smooth = kind.indexOf("bezier") >= 0;
790 
791  if (ndig===undefined) ndig = smooth ? 2 : 0;
792  if (height===undefined) height = 0;
793 
794  function jsroot_d3_svg_lineSlope(p0, p1) {
795  return (p1.gry - p0.gry) / (p1.grx - p0.grx);
796  }
797  function jsroot_d3_svg_lineFiniteDifferences(points) {
798  var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = jsroot_d3_svg_lineSlope(p0, p1);
799  while (++i < j) {
800  m[i] = (d + (d = jsroot_d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
801  }
802  m[i] = d;
803  return m;
804  }
805  function jsroot_d3_svg_lineMonotoneTangents(points) {
806  var d, a, b, s, m = jsroot_d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1;
807  while (++i < j) {
808  d = jsroot_d3_svg_lineSlope(points[i], points[i + 1]);
809  if (Math.abs(d) < 1e-6) {
810  m[i] = m[i + 1] = 0;
811  } else {
812  a = m[i] / d;
813  b = m[i + 1] / d;
814  s = a * a + b * b;
815  if (s > 9) {
816  s = d * 3 / Math.sqrt(s);
817  m[i] = s * a;
818  m[i + 1] = s * b;
819  }
820  }
821  }
822  i = -1;
823  while (++i <= j) {
824  s = (points[Math.min(j, i + 1)].grx - points[Math.max(0, i - 1)].grx) / (6 * (1 + m[i] * m[i]));
825  points[i].dgrx = s || 0;
826  points[i].dgry = m[i]*s || 0;
827  }
828  }
829 
830  var res = {}, bin = bins[0], prev, maxy = Math.max(bin.gry, height+5),
831  currx = Math.round(bin.grx), curry = Math.round(bin.gry), dx, dy;
832 
833  res.path = ((kind.charAt(0) == "L") ? "L" : "M") +
834  bin.grx.toFixed(ndig) + "," + bin.gry.toFixed(ndig);
835 
836  // just calculate all deltas, can be used to build exclusion
837  if (smooth || kind.indexOf('calc')>=0)
838  jsroot_d3_svg_lineMonotoneTangents(bins);
839 
840  if (smooth) {
841  res.path += "c" + bin.dgrx.toFixed(ndig) + "," + bin.dgry.toFixed(ndig) + ",";
842  }
843 
844  for(n=1; n<bins.length; ++n) {
845  prev = bin;
846  bin = bins[n];
847  if (smooth) {
848  if (n > 1) res.path += "s";
849  res.path += (bin.grx-bin.dgrx-prev.grx).toFixed(ndig) + "," + (bin.gry-bin.dgry-prev.gry).toFixed(ndig) + "," + (bin.grx-prev.grx).toFixed(ndig) + "," + (bin.gry-prev.gry).toFixed(ndig);
850  maxy = Math.max(maxy, prev.gry);
851  } else {
852  dx = Math.round(bin.grx - currx);
853  dy = Math.round(bin.gry - curry);
854  res.path += "l" + dx + "," + dy;
855  currx+=dx; curry+=dy;
856  maxy = Math.max(maxy, curry);
857  }
858  }
859 
860  if (height>0)
861  res.close = "L" + bin.grx.toFixed(ndig) +"," + maxy.toFixed(ndig) +
862  "L" + bins[0].grx.toFixed(ndig) +"," + maxy.toFixed(ndig) + "Z";
863 
864  return res;
865  }
866 
867 
868  // ===================================================================================
869 
870  JSROOT.Painter.drawFunction = function(divid, tf1, opt) {
871  this.bins = null;
872 
873  this.Eval = function(x) {
874  return this.GetObject().evalPar(x);
875  }
876 
877  this.CreateBins = function(ignore_zoom) {
878  var main = this.main_painter(), gxmin = 0, gxmax = 0, tf1 = this.GetObject();
879 
880  if ((main!==null) && !ignore_zoom) {
881  if (main.zoom_xmin !== main.zoom_xmax) {
882  gxmin = main.zoom_xmin;
883  gxmax = main.zoom_xmax;
884  } else {
885  gxmin = main.xmin;
886  gxmax = main.xmax;
887  }
888  }
889 
890  if (tf1.fSave.length > 0) {
891  // in the case where the points have been saved, useful for example
892  // if we don't have the user's function
893  var np = tf1.fSave.length - 2,
894  xmin = tf1.fSave[np],
895  xmax = tf1.fSave[np+1],
896  dx = (xmax - xmin) / (np-1),
897  res = [];
898 
899  for (var n=0; n < np; ++n) {
900  var xx = xmin + dx*n;
901  // check if points need to be displayed at all, keep at least 4-5 points for Bezier curves
902  if ((gxmin !== gxmax) && ((xx + 2*dx < gxmin) || (xx - 2*dx > gxmax))) continue;
903 
904  res.push({ x: xx, y: tf1.fSave[n] });
905  }
906  return res;
907  }
908 
909  var xmin = tf1.fXmin, xmax = tf1.fXmax, logx = false;
910 
911  if (gxmin !== gxmax) {
912  if (gxmin > xmin) xmin = gxmin;
913  if (gxmax < xmax) xmax = gxmax;
914  }
915 
916  if ((main!==null) && main.options.Logx && (xmin>0) && (xmax>0)) {
917  logx = true;
918  xmin = Math.log(xmin);
919  xmax = Math.log(xmax);
920  }
921 
922  var np = Math.max(tf1.fNpx, 101);
923  var dx = (xmax - xmin) / (np - 1);
924 
925  var res = [];
926  for (var n=0; n < np; n++) {
927  var xx = xmin + n*dx;
928  if (logx) xx = Math.exp(xx);
929  var yy = this.Eval(xx);
930  if (!isNaN(yy)) res.push({ x : xx, y : yy });
931  }
932  return res;
933  }
934 
935  this.CreateDummyHisto = function() {
936 
937  var xmin = 0, xmax = 1, ymin = 0, ymax = 1;
938 
939  var bins = this.CreateBins(true);
940 
941  if (bins!==null) {
942 
943  xmin = xmax = bins[0].x;
944  ymin = ymax = bins[0].y;
945 
946  bins.forEach(function(bin) {
947  xmin = Math.min(bin.x, xmin);
948  xmax = Math.max(bin.x, xmax);
949  ymin = Math.min(bin.y, ymin);
950  ymax = Math.max(bin.y, ymax);
951  });
952 
953  if (ymax > 0.0) ymax *= 1.05;
954  if (ymin < 0.0) ymin *= 1.05;
955  }
956 
957  var histo = JSROOT.Create("TH1I"),
958  tf1 = this.GetObject();
959 
960  histo.fName = tf1.fName + "_hist";
961  histo.fTitle = tf1.fTitle;
962 
963  histo.fXaxis.fXmin = xmin;
964  histo.fXaxis.fXmax = xmax;
965  histo.fYaxis.fXmin = ymin;
966  histo.fYaxis.fXmax = ymax;
967 
968  return histo;
969  }
970 
971  this.ProcessTooltipFunc = function(pnt) {
972  var cleanup = false;
973 
974  if ((pnt === null) || (this.bins===null)) {
975  cleanup = true;
976  } else
977  if ((this.bins.length==0) || (pnt.x < this.bins[0].grx) || (pnt.x > this.bins[this.bins.length-1].grx)) {
978  cleanup = true;
979  }
980 
981  if (cleanup) {
982  if (this.draw_g !== null)
983  this.draw_g.select(".tooltip_bin").remove();
984  return null;
985  }
986 
987  var min = 100000, best = -1, bin;
988 
989  for(var n=0; n<this.bins.length; ++n) {
990  bin = this.bins[n];
991  var dist = Math.abs(bin.grx - pnt.x);
992  if (dist < min) { min = dist; best = n; }
993  }
994 
995  bin = this.bins[best];
996 
997  var gbin = this.draw_g.select(".tooltip_bin");
998  var radius = this.lineatt.width + 3;
999 
1000  if (gbin.empty())
1001  gbin = this.draw_g.append("svg:circle")
1002  .attr("class","tooltip_bin")
1003  .style("pointer-events","none")
1004  .attr("r", radius)
1005  .call(this.lineatt.func)
1006  .call(this.fillatt.func);
1007 
1008  var res = { x: bin.grx,
1009  y: bin.gry,
1010  color1: this.lineatt.color,
1011  color2: this.fillatt.color,
1012  lines: [],
1013  exact : (Math.abs(bin.grx - pnt.x) < radius) && (Math.abs(bin.gry - pnt.y) < radius) };
1014 
1015  res.changed = gbin.property("current_bin") !== best;
1016  res.menu = res.exact;
1017  res.menu_dist = Math.sqrt((bin.grx-pnt.x)*(bin.grx-pnt.x) + (bin.gry-pnt.y)*(bin.grx-pnt.x));
1018 
1019  if (res.changed)
1020  gbin.attr("cx", bin.grx)
1021  .attr("cy", bin.gry)
1022  .property("current_bin", best);
1023 
1024  var name = this.GetTipName();
1025  if (name.length > 0) res.lines.push(name);
1026 
1027  var pmain = this.main_painter();
1028  if (pmain!==null)
1029  res.lines.push("x = " + pmain.AxisAsText("x",bin.x) + " y = " + pmain.AxisAsText("y",bin.y));
1030 
1031  return res;
1032  }
1033 
1034  this.Redraw = function() {
1035 
1036  var w = this.frame_width(), h = this.frame_height(), tf1 = this.GetObject();
1037 
1038  this.RecreateDrawG(false, "main_layer");
1039 
1040  // recalculate drawing bins when necessary
1041  this.bins = this.CreateBins(false);
1042 
1043  var pthis = this;
1044  var pmain = this.main_painter();
1045  var name = this.GetTipName("\n");
1046 
1047  if (!this.lineatt)
1048  this.lineatt = JSROOT.Painter.createAttLine(tf1);
1049  this.lineatt.used = false;
1050  if (!this.fillatt)
1051  this.fillatt = this.createAttFill(tf1, undefined, undefined, 1);
1052  this.fillatt.used = false;
1053 
1054  var n, bin;
1055  // first calculate graphical coordinates
1056  for(n=0; n<this.bins.length; ++n) {
1057  bin = this.bins[n];
1058  //bin.grx = Math.round(pmain.grx(bin.x));
1059  //bin.gry = Math.round(pmain.gry(bin.y));
1060  bin.grx = pmain.grx(bin.x);
1061  bin.gry = pmain.gry(bin.y);
1062  }
1063 
1064  if (this.bins.length > 2) {
1065 
1066  var h0 = h; // use maximal frame height for filling
1067  if ((pmain.hmin!==undefined) && (pmain.hmin>=0)) {
1068  h0 = Math.round(pmain.gry(0));
1069  if ((h0 > h) || (h0 < 0)) h0 = h;
1070  }
1071 
1072  var path = JSROOT.Painter.BuildSvgPath("bezier", this.bins, h0, 2);
1073 
1074  if (this.lineatt.color != "none")
1075  this.draw_g.append("svg:path")
1076  .attr("class", "line")
1077  .attr("d", path.path)
1078  .style("fill", "none")
1079  .call(this.lineatt.func);
1080 
1081  if (this.fillatt.color != "none")
1082  this.draw_g.append("svg:path")
1083  .attr("class", "area")
1084  .attr("d", path.path + path.close)
1085  .style("stroke", "none")
1086  .call(this.fillatt.func);
1087  }
1088 
1089  delete this.ProcessTooltip;
1090 
1091  if (JSROOT.gStyle.Tooltip > 0)
1092  this.ProcessTooltip = this.ProcessTooltipFunc;
1093  }
1094 
1095  this.CanZoomIn = function(axis,min,max) {
1096  if (axis!=="x") return false;
1097 
1098  var tf1 = this.GetObject();
1099 
1100  if (tf1.fSave.length > 0) {
1101  // in the case where the points have been saved, useful for example
1102  // if we don't have the user's function
1103  var nb_points = tf1.fNpx;
1104 
1105  var xmin = tf1.fSave[nb_points + 1];
1106  var xmax = tf1.fSave[nb_points + 2];
1107 
1108  return Math.abs(xmin - xmax) / nb_points < Math.abs(min - max);
1109  }
1110 
1111  // if function calculated, one always could zoom inside
1112  return true;
1113  }
1114 
1115  this.SetDivId(divid, -1);
1116  if (this.main_painter() === null) {
1117  var histo = this.CreateDummyHisto();
1118  JSROOT.Painter.drawHistogram1D(divid, histo, "AXIS");
1119  }
1120  this.SetDivId(divid);
1121  this.Redraw();
1122  return this.DrawingReady();
1123  }
1124 
1125  // ====================================================================
1126 
1127  JSROOT.Painter.drawHStack = function(divid, stack, opt) {
1128  // paint the list of histograms
1129  // By default, histograms are shown stacked.
1130  // -the first histogram is paint
1131  // -then the sum of the first and second, etc
1132 
1133  // 'this' pointer set to created painter instance
1134  this.nostack = false;
1135  this.firstpainter = null;
1136  this.painters = new Array; // keep painters to be able update objects
1137 
1138  this.SetDivId(divid);
1139 
1140  if (!('fHists' in stack) || (stack.fHists.arr.length == 0)) return this.DrawingReady();
1141 
1142  this['BuildStack'] = function() {
1143  // build sum of all histograms
1144  // Build a separate list fStack containing the running sum of all histograms
1145 
1146  var stack = this.GetObject();
1147 
1148  if (!('fHists' in stack)) return false;
1149  var nhists = stack.fHists.arr.length;
1150  if (nhists <= 0) return false;
1151  var lst = JSROOT.Create("TList");
1152  lst.Add(JSROOT.clone(stack.fHists.arr[0]));
1153  for (var i=1;i<nhists;++i) {
1154  var hnext = JSROOT.clone(stack.fHists.arr[i]);
1155  var hprev = lst.arr[i-1];
1156 
1157  if ((hnext.fNbins != hprev.fNbins) ||
1158  (hnext.fXaxis.fXmin != hprev.fXaxis.fXmin) ||
1159  (hnext.fXaxis.fXmax != hprev.fXaxis.fXmax)) {
1160  JSROOT.console("When drawing THStack, cannot sum-up histograms " + hnext.fName + " and " + hprev.fName);
1161  delete hnext;
1162  delete lst;
1163  return false;
1164  }
1165 
1166  // trivial sum of histograms
1167  for (var n = 0; n < hnext.fArray.length; ++n)
1168  hnext.fArray[n] += hprev.fArray[n];
1169 
1170  lst.Add(hnext);
1171  }
1172  stack.fStack = lst;
1173  return true;
1174  }
1175 
1176  this['GetHistMinMax'] = function(hist, witherr) {
1177  var res = { min : 0, max : 0 };
1178  var domin = false, domax = false;
1179  if (hist.fMinimum != -1111)
1180  res.min = hist.fMinimum;
1181  else
1182  domin = true;
1183  if (hist.fMaximum != -1111)
1184  res.max = hist.fMaximum;
1185  else
1186  domax = true;
1187 
1188  if (domin || domax) {
1189  var left = 1, right = hist.fXaxis.fNbins;
1190 
1191  if (hist.fXaxis.TestBit(JSROOT.EAxisBits.kAxisRange)) {
1192  left = hist.fXaxis.fFirst;
1193  right = hist.fXaxis.fLast;
1194  }
1195  for (var bin = left; bin<=right; ++bin) {
1196  var val = hist.getBinContent(bin);
1197  var err = witherr ? hist.getBinError(bin) : 0;
1198  if (domin && ((bin==left) || (val-err < res.min))) res.min = val-err;
1199  if (domax && ((bin==left) || (val+err > res.max))) res.max = val+err;
1200  }
1201  }
1202 
1203  return res;
1204  }
1205 
1206  this['GetMinMax'] = function(opt) {
1207  var res = { min : 0, max : 0 },
1208  iserr = (opt.indexOf('e')>=0),
1209  stack = this.GetObject();
1210 
1211  if (this.nostack) {
1212  for (var i = 0; i < stack.fHists.arr.length; ++i) {
1213  var resh = this.GetHistMinMax(stack.fHists.arr[i], iserr);
1214  if (i==0) res = resh; else {
1215  if (resh.min < res.min) res.min = resh.min;
1216  if (resh.max > res.max) res.max = resh.max;
1217  }
1218  }
1219 
1220  if (stack.fMaximum != -1111)
1221  res.max = stack.fMaximum;
1222  else
1223  res.max *= 1.05;
1224 
1225  if (stack.fMinimum != -1111) res.min = stack.fMinimum;
1226  } else {
1227  res.min = this.GetHistMinMax(stack.fStack.arr[0], iserr).min;
1228  res.max = this.GetHistMinMax(stack.fStack.arr[stack.fStack.arr.length-1], iserr).max * 1.05;
1229  }
1230 
1231  var pad = this.root_pad();
1232  if ((pad!=null) && (pad.fLogy > 0)) {
1233  if (res.min<0) res.min = res.max * 1e-4;
1234  }
1235 
1236  return res;
1237  }
1238 
1239  this['DrawNextHisto'] = function(indx, opt) {
1240  var hist = null,
1241  stack = this.GetObject(),
1242  nhists = stack.fHists.arr.length;
1243 
1244  if (indx>=nhists) return this.DrawingReady();
1245 
1246  if (indx<0) hist = stack.fHistogram; else
1247  if (this.nostack) hist = stack.fHists.arr[indx];
1248  else hist = stack.fStack.arr[nhists - indx - 1];
1249 
1250  var hopt = hist.fOption;
1251  if ((opt != "") && (hopt.indexOf(opt) == -1)) hopt += opt;
1252  if (indx>=0) hopt += "same";
1253  var subp = JSROOT.draw(this.divid, hist, hopt);
1254  if (indx<0) this.firstpainter = subp;
1255  else this.painters.push(subp);
1256  subp.WhenReady(this.DrawNextHisto.bind(this, indx+1, opt));
1257  }
1258 
1259  this['drawStack'] = function(opt) {
1260  var pad = this.root_pad(),
1261  stack = this.GetObject(),
1262  histos = stack.fHists,
1263  nhists = histos.arr.length;
1264 
1265  if (opt == null) opt = "";
1266  else opt = opt.toLowerCase();
1267 
1268  var lsame = false;
1269  if (opt.indexOf("same") != -1) {
1270  lsame = true;
1271  opt.replace("same", "");
1272  }
1273  this.nostack = opt.indexOf("nostack") < 0 ? false : true;
1274 
1275  // when building stack, one could fail to sum up histograms
1276  if (!this.nostack)
1277  this.nostack = ! this.BuildStack();
1278 
1279  var mm = this.GetMinMax(opt);
1280 
1281  if (stack.fHistogram === null) {
1282  // compute the min/max of each axis
1283  var xmin = 0, xmax = 0, ymin = 0, ymax = 0;
1284  for (var i = 0; i < nhists; ++i) {
1285  var h = histos.arr[i];
1286  if (i == 0 || h.fXaxis.fXmin < xmin)
1287  xmin = h.fXaxis.fXmin;
1288  if (i == 0 || h.fXaxis.fXmax > xmax)
1289  xmax = h.fXaxis.fXmax;
1290  if (i == 0 || h.fYaxis.fXmin < ymin)
1291  ymin = h.fYaxis.fXmin;
1292  if (i == 0 || h.fYaxis.fXmax > ymax)
1293  ymax = h.fYaxis.fXmax;
1294  }
1295 
1296  var h = stack.fHists.arr[0];
1297  stack.fHistogram = JSROOT.Create("TH1I");
1298  stack.fHistogram.fName = "unnamed";
1299  stack.fHistogram.fXaxis = JSROOT.clone(h.fXaxis);
1300  stack.fHistogram.fYaxis = JSROOT.clone(h.fYaxis);
1301  stack.fHistogram.fXaxis.fXmin = xmin;
1302  stack.fHistogram.fXaxis.fXmax = xmax;
1303  stack.fHistogram.fYaxis.fXmin = ymin;
1304  stack.fHistogram.fYaxis.fXmax = ymax;
1305  }
1306  stack.fHistogram.fTitle = stack.fTitle;
1307  var histo = stack.fHistogram;
1308  if (!histo.TestBit(JSROOT.TH1StatusBits.kIsZoomed)) {
1309  if (pad && pad.fLogy)
1310  histo.fMaximum = mm.max * (1 + 0.2 * JSROOT.log10(mm.max / mm.min));
1311  else
1312  histo.fMaximum = mm.max;
1313  if (pad && pad.fLogy)
1314  histo.fMinimum = mm.min / (1 + 0.5 * JSROOT.log10(mm.max / mm.min));
1315  else
1316  histo.fMinimum = mm.min;
1317  }
1318 
1319  this.DrawNextHisto(!lsame ? -1 : 0, opt);
1320  return this;
1321  }
1322 
1323  this['UpdateObject'] = function(obj) {
1324  if (!this.MatchObjectType(obj)) return false;
1325 
1326  var isany = false;
1327  if (this.firstpainter)
1328  if (this.firstpainter.UpdateObject(obj.fHistogram)) isany = true;
1329 
1330  var nhists = obj.fHists.arr.length;
1331  for (var i = 0; i < nhists; ++i) {
1332  var hist = this.nostack ? obj.fHists.arr[i] : obj.fStack.arr[nhists - i - 1];
1333  if (this.painters[i].UpdateObject(hist)) isany = true;
1334  }
1335 
1336  return isany;
1337  }
1338 
1339  return this.drawStack(opt);
1340  }
1341 
1342  // =======================================================================
1343 
1344  JSROOT.TGraphPainter = function(graph) {
1345  JSROOT.TObjectPainter.call(this, graph);
1346  this.ownhisto = false; // indicate if graph histogram was drawn for axes
1347  this.bins = null;
1348  this.xmin = this.ymin = this.xmax = this.ymax = 0;
1349  }
1350 
1351  JSROOT.TGraphPainter.prototype = Object.create(JSROOT.TObjectPainter.prototype);
1352 
1353  JSROOT.TGraphPainter.prototype.Redraw = function() {
1354  this.DrawBins();
1355  }
1356 
1357  JSROOT.TGraphPainter.prototype.DecodeOptions = function(opt) {
1358  this.draw_all = true;
1359  JSROOT.extend(this, { optionLine:0, optionAxis:0, optionCurve:0, optionRect:0,
1360  optionMark:0, optionBar:0, optionR:0, optionE:0, optionEF:0,
1361  optionFill:0, optionZ:0, optionBrackets:0,
1362  opt:"LP", out_of_range: false, has_errors: false, draw_errors: false, is_bent:false });
1363 
1364  var graph = this.GetObject();
1365 
1366  this.is_bent = graph._typename == 'TGraphBentErrors';
1367  this.has_errors = (graph._typename == 'TGraphErrors' ||
1368  graph._typename == 'TGraphAsymmErrors' ||
1369  this.is_bent || graph._typename.match(/^RooHist/));
1370  this.draw_errors = this.has_errors;
1371 
1372  if ((opt != null) && (opt != "")) {
1373  this.opt = opt.toUpperCase();
1374  this.opt.replace('SAME', '');
1375  }
1376  if (this.opt.indexOf('L') != -1)
1377  this.optionLine = 1;
1378  if (this.opt.indexOf('F') != -1)
1379  this.optionFill = 1;
1380  if (this.opt.indexOf('A') != -1)
1381  this.optionAxis = 1;
1382  if (this.opt.indexOf('C') != -1) {
1383  this.optionCurve = 1;
1384  if (this.optionFill==0) this.optionLine = 1;
1385  }
1386  if (this.opt.indexOf('*') != -1)
1387  this.optionMark = 2;
1388  if (this.opt.indexOf('P') != -1)
1389  this.optionMark = 1;
1390  if (this.opt.indexOf('B') != -1) {
1391  this.optionBar = 1;
1392  this.draw_errors = false;
1393  }
1394  if (this.opt.indexOf('R') != -1)
1395  this.optionR = 1;
1396 
1397  if (this.opt.indexOf('[]') != -1) {
1398  this.optionBrackets = 1;
1399  this.draw_errors = false;
1400  }
1401 
1402  if (this.opt.indexOf('0') != -1) {
1403  this.optionMark = 1;
1404  this.draw_errors = true;
1405  this.out_of_range = true;
1406  }
1407 
1408  if (this.opt.indexOf('1') != -1) {
1409  if (this.optionBar == 1) this.optionBar = 2;
1410  }
1411  if (this.opt.indexOf('2') != -1)
1412  this.optionRect = 1;
1413 
1414  if (this.opt.indexOf('3') != -1) {
1415  this.optionEF = 1;
1416  this.optionLine = 0;
1417  this.draw_errors = false;
1418  }
1419  if (this.opt.indexOf('4') != -1) {
1420  this.optionEF = 2;
1421  this.optionLine = 0;
1422  this.draw_errors = false;
1423  }
1424 
1425  if (this.opt.indexOf('2') != -1 || this.opt.indexOf('5') != -1) this.optionE = 1;
1426 
1427  // special case - one could use scg:path to draw many pixels (
1428  if ((this.optionMark==1) && (graph.fMarkerStyle==1)) this.optionMark = 3;
1429 
1430  // if no drawing option is selected and if opt<>' ' nothing is done.
1431  if (this.optionLine + this.optionFill + this.optionMark + this.optionBar + this.optionE +
1432  this.optionEF + this.optionRect + this.optionBrackets == 0) {
1433  if (this.opt.length == 0)
1434  this.optionLine = 1;
1435  }
1436 
1437  if (graph._typename == 'TGraphErrors') {
1438  var maxEX = d3.max(graph.fEX);
1439  var maxEY = d3.max(graph.fEY);
1440  if (maxEX < 1.0e-300 && maxEY < 1.0e-300)
1441  this.draw_errors = false;
1442  }
1443  }
1444 
1445  JSROOT.TGraphPainter.prototype.CreateBins = function() {
1446  var gr = this.GetObject();
1447  if (gr===null) return;
1448 
1449  var p, kind = 0, npoints = gr.fNpoints;
1450  if ((gr._typename==="TCutG") && (npoints>3)) npoints--;
1451 
1452  if (gr._typename == 'TGraphErrors') kind = 1; else
1453  if (gr._typename == 'TGraphAsymmErrors' || gr._typename == 'TGraphBentErrors'
1454  || gr._typename.match(/^RooHist/)) kind = 2;
1455 
1456  this.bins = [];
1457 
1458  for (p=0;p<npoints;++p) {
1459  var bin = { x: gr.fX[p], y: gr.fY[p] };
1460  if (kind === 1) {
1461  bin.exlow = bin.exhigh = gr.fEX[p];
1462  bin.eylow = bin.eyhigh = gr.fEY[p];
1463  } else
1464  if (kind === 2) {
1465  bin.exlow = gr.fEXlow[p];
1466  bin.exhigh = gr.fEXhigh[p];
1467  bin.eylow = gr.fEYlow[p];
1468  bin.eyhigh = gr.fEYhigh[p];
1469  }
1470  this.bins.push(bin);
1471 
1472  if (p===0) {
1473  this.xmin = this.xmax = bin.x;
1474  this.ymin = this.ymax = bin.y;
1475  }
1476 
1477  if (kind > 0) {
1478  this.xmin = Math.min(this.xmin, bin.x - bin.exlow, bin.x + bin.exhigh);
1479  this.xmax = Math.max(this.xmax, bin.x - bin.exlow, bin.x + bin.exhigh);
1480  this.ymin = Math.min(this.ymin, bin.y - bin.eylow, bin.y + bin.eyhigh);
1481  this.ymax = Math.max(this.ymax, bin.y - bin.eylow, bin.y + bin.eyhigh);
1482  } else {
1483  this.xmin = Math.min(this.xmin, bin.x);
1484  this.xmax = Math.max(this.xmax, bin.x);
1485  this.ymin = Math.min(this.ymin, bin.y);
1486  this.ymax = Math.max(this.ymax, bin.y);
1487  }
1488 
1489  }
1490  }
1491 
1492  JSROOT.TGraphPainter.prototype.CreateHistogram = function() {
1493  // bins should be created
1494 
1495  var xmin = this.xmin, xmax = this.xmax, ymin = this.ymin, ymax = this.ymax;
1496 
1497  if (xmin >= xmax) xmax = xmin+1;
1498  if (ymin >= ymax) ymax = ymin+1;
1499  var dx = (xmax-xmin)*0.1, dy = (ymax-ymin)*0.1,
1500  uxmin = xmin - dx, uxmax = xmax + dx,
1501  minimum = ymin - dy, maximum = ymax + dy;
1502 
1503  if ((uxmin<0) && (xmin>=0)) uxmin = xmin*0.9;
1504  if ((uxmax>0) && (xmax<=0)) uxmax = 0;
1505 
1506  var graph = this.GetObject();
1507 
1508  if (graph.fMinimum != -1111) minimum = ymin = graph.fMinimum;
1509  if (graph.fMaximum != -1111) maximum = ymax = graph.fMaximum;
1510  if ((minimum < 0) && (ymin >=0)) minimum = 0.9*ymin;
1511 
1512  var histo = JSROOT.CreateTH1(100);
1513  histo.fName = graph.fName + "_h";
1514  histo.fTitle = graph.fTitle;
1515  histo.fXaxis.fXmin = uxmin;
1516  histo.fXaxis.fXmax = uxmax;
1517  histo.fYaxis.fXmin = minimum;
1518  histo.fYaxis.fXmax = maximum;
1519  histo.fMinimum = minimum;
1520  histo.fMaximum = maximum;
1521  histo.fBits = histo.fBits | JSROOT.TH1StatusBits.kNoStats;
1522  return histo;
1523  }
1524 
1525  JSROOT.TGraphPainter.prototype.OptimizeBins = function(filter_func) {
1526  if ((this.bins.length < 30) && !filter_func) return this.bins;
1527 
1528  var selbins = null;
1529  if (typeof filter_func == 'function') {
1530  for (var n = 0; n < this.bins.length; ++n) {
1531  if (filter_func(this.bins[n],n)) {
1532  if (selbins==null)
1533  selbins = (n==0) ? [] : this.bins.slice(0, n);
1534  } else {
1535  if (selbins != null) selbins.push(this.bins[n]);
1536  }
1537  }
1538  }
1539  if (selbins == null) selbins = this.bins;
1540 
1541  if ((selbins.length < 5000) || (JSROOT.gStyle.OptimizeDraw == 0)) return selbins;
1542  var step = Math.floor(selbins.length / 5000);
1543  if (step < 2) step = 2;
1544  var optbins = [];
1545  for (var n = 0; n < selbins.length; n+=step)
1546  optbins.push(selbins[n]);
1547 
1548  return optbins;
1549  }
1550 
1551  JSROOT.TGraphPainter.prototype.TooltipText = function(d, asarray) {
1552  var pmain = this.main_painter(), lines = [];
1553 
1554  lines.push(this.GetTipName());
1555  lines.push("x = " + pmain.AxisAsText("x", d.x));
1556  lines.push("y = " + pmain.AxisAsText("y", d.y));
1557 
1558  if (this.draw_errors && (pmain.x_kind=='normal') && ('exlow' in d) && ((d.exlow!=0) || (d.exhigh!=0)))
1559  lines.push("error x = -" + pmain.AxisAsText("x", d.exlow) +
1560  "/+" + pmain.AxisAsText("x", d.exhigh));
1561 
1562  if ((this.draw_errors || (this.optionEF > 0)) && (pmain.y_kind=='normal') && ('eylow' in d) && ((d.eylow!=0) || (d.eyhigh!=0)) )
1563  lines.push("error y = -" + pmain.AxisAsText("y", d.eylow) +
1564  "/+" + pmain.AxisAsText("y", d.eyhigh));
1565 
1566  if (asarray) return lines;
1567 
1568  var res = "";
1569  for (var n=0;n<lines.length;++n) res += ((n>0 ? "\n" : "") + lines[n]);
1570  return res;
1571  }
1572 
1573  JSROOT.TGraphPainter.prototype.DrawBins = function() {
1574 
1575  this.RecreateDrawG(false, "main_layer");
1576 
1577  var pthis = this,
1578  pmain = this.main_painter(),
1579  w = this.frame_width(),
1580  h = this.frame_height(),
1581  graph = this.GetObject(),
1582  excl_width = 0;
1583 
1584  if (!this.lineatt)
1585  this.lineatt = JSROOT.Painter.createAttLine(graph, undefined, true);
1586  if (!this.fillatt)
1587  this.fillatt = this.createAttFill(graph, undefined, undefined, 1);
1588  this.fillatt.used = false;
1589 
1590  if (this.fillatt) this.fillatt.used = false; // mark used only when really used
1591  this.draw_kind = "none"; // indicate if special svg:g were created for each bin
1592  this.marker_size = 0; // indicate if markers are drawn
1593 
1594  if (this.lineatt.excl_side!=0) {
1595  excl_width = this.lineatt.excl_side * this.lineatt.excl_width;
1596  if (this.lineatt.width>0) this.optionLine = 1;
1597  }
1598 
1599  var drawbins = null;
1600 
1601  if (this.optionEF > 0) {
1602 
1603  drawbins = this.OptimizeBins();
1604 
1605  // build lower part
1606  for (var n=0;n<drawbins.length;++n) {
1607  var bin = drawbins[n];
1608  bin.grx = pmain.grx(bin.x);
1609  bin.gry = pmain.gry(bin.y - bin.eylow);
1610  }
1611 
1612  var path1 = JSROOT.Painter.BuildSvgPath(this.optionEF > 1 ? "bezier" : "line", drawbins),
1613  bins2 = [];
1614 
1615  for (var n=drawbins.length-1;n>=0;--n) {
1616  var bin = drawbins[n];
1617  bin.gry = pmain.gry(bin.y + bin.eyhigh);
1618  bins2.push(bin);
1619  }
1620 
1621  // build upper part (in reverse direction
1622  var path2 = JSROOT.Painter.BuildSvgPath(this.optionEF > 1 ? "Lbezier" : "Lline", bins2);
1623 
1624  this.draw_g.append("svg:path")
1625  .attr("d", path1.path + path2.path + "Z")
1626  .style("stroke", "none")
1627  .call(this.fillatt.func);
1628  this.draw_kind = "lines";
1629  }
1630 
1631  if (this.optionLine == 1 || this.optionFill == 1 || (excl_width!==0)) {
1632 
1633  var close_symbol = "";
1634  if (graph._typename=="TCutG") this.optionFill = 1;
1635 
1636  if (this.optionFill == 1) {
1637  close_symbol = "Z"; // always close area if we want to fill it
1638  excl_width=0;
1639  }
1640 
1641  if (drawbins===null) drawbins = this.OptimizeBins();
1642 
1643  for (var n=0;n<drawbins.length;++n) {
1644  var bin = drawbins[n];
1645  bin.grx = pmain.grx(bin.x);
1646  bin.gry = pmain.gry(bin.y);
1647  }
1648 
1649  var kind = "line"; // simple line
1650  if (this.optionCurve === 1) kind = "bezier"; else
1651  if (excl_width!==0) kind+="calc"; // we need to calculated deltas to build exclusion points
1652 
1653  var path = JSROOT.Painter.BuildSvgPath(kind, drawbins);
1654 
1655  if (excl_width!==0) {
1656  var extrabins = [];
1657  for (var n=drawbins.length-1;n>=0;--n) {
1658  var bin = drawbins[n];
1659  var dlen = Math.sqrt(bin.dgrx*bin.dgrx + bin.dgry*bin.dgry);
1660  // shift point, using
1661  bin.grx += excl_width*bin.dgry/dlen;
1662  bin.gry -= excl_width*bin.dgrx/dlen;
1663  extrabins.push(bin);
1664  }
1665 
1666  var path2 = JSROOT.Painter.BuildSvgPath("L" + ((this.optionCurve === 1) ? "bezier" : "line"), extrabins);
1667 
1668  this.draw_g.append("svg:path")
1669  .attr("d", path.path + path2.path + "Z")
1670  .style("stroke", "none")
1671  .call(this.fillatt.func)
1672  .style('opacity', 0.75);
1673  }
1674 
1675  if (this.optionLine || this.optionFill) {
1676  var elem = this.draw_g.append("svg:path")
1677  .attr("d", path.path + close_symbol);
1678  if (this.optionLine)
1679  elem.call(this.lineatt.func);
1680  else
1681  elem.style('stroke','none');
1682 
1683  if (this.optionFill > 0)
1684  elem.call(this.fillatt.func);
1685  else
1686  elem.style('fill','none');
1687  }
1688 
1689  this.draw_kind = "lines";
1690  }
1691 
1692  var nodes = null;
1693 
1694  if (this.draw_errors || this.optionRect || this.optionBrackets || this.optionBar) {
1695 
1696  drawbins = this.OptimizeBins(function(pnt,i) {
1697 
1698  var grx = pmain.grx(pnt.x);
1699 
1700  // when drawing bars, take all points
1701  if (!pthis.optionBar && ((grx<0) || (grx>w))) return true;
1702 
1703  var gry = pmain.gry(pnt.y);
1704 
1705  if (!pthis.optionBar && !pthis.out_of_range && ((gry<0) || (gry>h))) return true;
1706 
1707  pnt.grx1 = Math.round(grx);
1708  pnt.gry1 = Math.round(gry);
1709 
1710  if (pthis.has_errors) {
1711  pnt.grx0 = Math.round(pmain.grx(pnt.x - pnt.exlow) - grx);
1712  pnt.grx2 = Math.round(pmain.grx(pnt.x + pnt.exhigh) - grx);
1713  pnt.gry0 = Math.round(pmain.gry(pnt.y - pnt.eylow) - gry);
1714  pnt.gry2 = Math.round(pmain.gry(pnt.y + pnt.eyhigh) - gry);
1715 
1716  if (pthis.is_bent) {
1717  pnt.grdx0 = Math.round(pmain.gry(pnt.y + graph.fEXlowd[i]) - gry);
1718  pnt.grdx2 = Math.round(pmain.gry(pnt.y + graph.fEXhighd[i]) - gry);
1719  pnt.grdy0 = Math.round(pmain.grx(pnt.x + graph.fEYlowd[i]) - grx);
1720  pnt.grdy2 = Math.round(pmain.grx(pnt.x + graph.fEYhighd[i]) - grx);
1721  } else {
1722  pnt.grdx0 = pnt.grdx2 = pnt.grdy0 = pnt.grdy2 = 0;
1723  }
1724  }
1725 
1726  return false;
1727  });
1728 
1729  this.draw_kind = "nodes";
1730 
1731  // here are up to five elements are collected, try to group them
1732  nodes = this.draw_g.selectAll(".grpoint")
1733  .data(drawbins)
1734  .enter()
1735  .append("svg:g")
1736  .attr("class", "grpoint")
1737  .attr("transform", function(d) { return "translate(" + d.grx1 + "," + d.gry1 + ")"; });
1738  }
1739 
1740  if (this.optionBar) {
1741  // calculate bar width
1742  for (var i=1;i<drawbins.length-1;++i)
1743  drawbins[i].width = Math.max(2, (drawbins[i+1].grx1 - drawbins[i-1].grx1) / 2 - 2);
1744 
1745  // first and last bins
1746  switch (drawbins.length) {
1747  case 0: break;
1748  case 1: drawbins[0].width = w/4; break; // patalogic case of single bin
1749  case 2: drawbins[0].width = drawbins[1].width = (drawbins[1].grx1-drawbins[0].grx1)/2; break;
1750  default:
1751  drawbins[0].width = drawbins[1].width;
1752  drawbins[drawbins.length-1].width = drawbins[drawbins.length-2].width;
1753  }
1754 
1755  var yy0 = Math.round(pmain.gry(0));
1756 
1757  nodes.append("svg:rect")
1758  .attr("x", function(d) { return Math.round(-d.width/2); })
1759  .attr("y", function(d) {
1760  d.bar = true; // element drawn as bar
1761  if (pthis.optionBar!==1) return 0;
1762  return (d.gry1 > yy0) ? yy0-d.gry1 : 0;
1763  })
1764  .attr("width", function(d) { return Math.round(d.width); })
1765  .attr("height", function(d) {
1766  if (pthis.optionBar!==1) return h > d.gry1 ? h - d.gry1 : 0;
1767  return Math.abs(yy0 - d.gry1);
1768  })
1769  .call(this.fillatt.func);
1770  }
1771 
1772  if (this.optionRect)
1773  nodes.filter(function(d) { return (d.exlow > 0) && (d.exhigh > 0) && (d.eylow > 0) && (d.eyhigh > 0); })
1774  .append("svg:rect")
1775  .attr("x", function(d) { d.rect = true; return d.grx0; })
1776  .attr("y", function(d) { return d.gry2; })
1777  .attr("width", function(d) { return d.grx2 - d.grx0; })
1778  .attr("height", function(d) { return d.gry0 - d.gry2; })
1779  .call(this.fillatt.func);
1780 
1781  if (this.optionBrackets) {
1782  nodes.filter(function(d) { return (d.eylow > 0) || (d.eyhigh > 0); })
1783  .append("svg:path")
1784  .call(this.lineatt.func)
1785  .style('fill', "none")
1786  .attr("d", function(d) {
1787  d.bracket = true;
1788  return ((d.eylow > 0) ? "M-5,"+(d.gry0-3)+"v3h10v-3" : "") +
1789  ((d.eyhigh > 0) ? "M-5,"+(d.gry2+3)+"v-3h10v3" : "");
1790  });
1791  }
1792 
1793  if (this.draw_errors) {
1794  // to show end of error markers, use line width attribute
1795  var lw = this.lineatt.width + JSROOT.gStyle.EndErrorSize,
1796  vv = "m0," + lw + "v-" + 2*lw,
1797  hh = "m" + lw + ",0h-" + 2*lw;
1798  lw = Math.floor((this.lineatt.width-1)/2); // one shoud take into account half of end-cup line width
1799  nodes.filter(function(d) { return (d.exlow > 0) || (d.exhigh > 0) || (d.eylow > 0) || (d.eyhigh > 0); })
1800  .append("svg:path")
1801  .call(this.lineatt.func)
1802  .style('fill', "none")
1803  .attr("d", function(d) {
1804  d.error = true;
1805  return ((d.exlow > 0) ? "M0,0L"+(d.grx0+lw)+","+d.grdx0+vv : "") +
1806  ((d.exhigh > 0) ? "M0,0L"+(d.grx2-lw)+","+d.grdx2+vv : "") +
1807  ((d.eylow > 0) ? "M0,0L"+d.grdy0+","+(d.gry0-lw)+hh : "") +
1808  ((d.eyhigh > 0) ? "M0,0L"+d.grdy2+","+(d.gry2+lw)+hh : "");
1809  });
1810  }
1811 
1812  if (this.optionMark > 0) {
1813  // for tooltips use markers only if nodes where not created
1814  var step = Math.max(1, Math.round(this.bins.length / 50000)),
1815  path = "", n, pnt, grx, gry, marker_kind;
1816 
1817  if (this.optionMark==2) marker_kind = 3; else
1818  if (this.optionMark==3) marker_kind = 1;
1819 
1820  if (!this.markeratt)
1821  this.markeratt = JSROOT.Painter.createAttMarker(graph,marker_kind);
1822  else
1823  this.markeratt.Change(undefined, marker_kind);
1824 
1825  this.marker_size = this.markeratt.size;
1826 
1827  this.markeratt.reset_pos();
1828 
1829  for (n=0;n<this.bins.length;n+=step) {
1830  pnt = this.bins[n];
1831  grx = pmain.grx(pnt.x);
1832  if ((grx > -this.marker_size) && (grx < w+this.marker_size)) {
1833  gry = pmain.gry(pnt.y);
1834  if ((gry >-this.marker_size) && (gry < h+this.marker_size)) {
1835  path += this.markeratt.create(grx, gry);
1836  }
1837  }
1838  }
1839 
1840  if (path.length>0) {
1841  this.draw_g.append("svg:path")
1842  .attr("d", path)
1843  .call(this.markeratt.func);
1844  if ((nodes===null) && (this.draw_kind=="none"))
1845  this.draw_kind = (this.optionMark==3) ? "path" : "mark";
1846  }
1847  }
1848  }
1849 
1850  JSROOT.TGraphPainter.prototype.ProcessTooltip = function(pnt) {
1851  if (pnt === null) {
1852  if (this.draw_g !== null)
1853  this.draw_g.select(".tooltip_bin").remove();
1854  return null;
1855  }
1856 
1857  if ((this.draw_kind=="lines") || (this.draw_kind=="path") || (this.draw_kind=="mark"))
1858  return this.ProcessTooltipForPath(pnt);
1859 
1860  if (this.draw_kind!="nodes") return null;
1861 
1862  var width = this.frame_width(),
1863  height = this.frame_height(),
1864  pmain = this.main_painter(),
1865  painter = this,
1866  findbin = null, best_dist2 = 1e10, best = null;
1867 
1868  this.draw_g.selectAll('.grpoint').each(function() {
1869  var d = d3.select(this).datum();
1870  if (d===undefined) return;
1871  var dist2 = Math.pow(pnt.x - d.grx1, 2);
1872  if (pnt.nproc===1) dist2 += Math.pow(pnt.y - d.gry1, 2);
1873  if (dist2 >= best_dist2) return;
1874 
1875  var rect = null;
1876 
1877  if (d.error || d.rect || d.marker || d.bracket) {
1878  rect = { x1: Math.min(-3, d.grx0), x2: Math.max(3, d.grx2), y1: Math.min(-3, d.gry2), y2: Math.max(3, d.gry0) };
1879  if (d.bracket) { rect.x1 = -5; rect.x2 = 5; }
1880  } else
1881  if (d.bar) {
1882  rect = { x1: -d.width/2, x2: d.width/2, y1: 0, y2: height - d.gry1 };
1883 
1884  if (painter.optionBar===1) {
1885  var yy0 = pmain.gry(0);
1886  rect.y1 = (d.gry1 > yy0) ? yy0-d.gry1 : 0;
1887  rect.y2 = (d.gry1 > yy0) ? 0 : yy0-d.gry1;
1888  }
1889  } else {
1890  rect = { x1: -5, x2: 5, y1: -5, y2: 5 };
1891  }
1892  var matchx = (pnt.x >= d.grx1 + rect.x1) && (pnt.x <= d.grx1 + rect.x2);
1893  var matchy = (pnt.y >= d.gry1 + rect.y1) && (pnt.y <= d.gry1 + rect.y2);
1894 
1895  if (matchx && (matchy || (pnt.nproc > 1))) {
1896  best_dist2 = dist2;
1897  findbin = this;
1898  best = rect;
1899  best.exact = matchx && matchy;
1900  }
1901  });
1902 
1903  var ttrect = this.draw_g.select(".tooltip_bin");
1904 
1905  if (findbin == null) {
1906  ttrect.remove();
1907  return null;
1908  }
1909 
1910  var d = d3.select(findbin).datum();
1911 
1912  var res = { x: d.grx1, y: d.gry1,
1913  color1: this.lineatt.color,
1914  lines: this.TooltipText(d, true) };
1915  if (this.fillatt && this.fillatt.used) res.color2 = this.fillatt.color;
1916 
1917  if (best.exact) res.exact = true;
1918  res.menu = res.exact; // activate menu only when exactly locate bin
1919  res.menu_dist = 3; // distance alwyas fixed
1920 
1921  if (ttrect.empty())
1922  ttrect = this.draw_g.append("svg:rect")
1923  .attr("class","tooltip_bin h1bin")
1924  .style("pointer-events","none");
1925 
1926  res.changed = ttrect.property("current_bin") !== findbin;
1927 
1928  if (res.changed)
1929  ttrect.attr("x", d.grx1 + best.x1)
1930  .attr("width", best.x2 - best.x1)
1931  .attr("y", d.gry1 + best.y1)
1932  .attr("height", best.y2 - best.y1)
1933  .style("opacity", "0.3")
1934  .property("current_bin", findbin);
1935 
1936  return res;
1937  }
1938 
1939  JSROOT.TGraphPainter.prototype.ProcessTooltipForPath = function(pnt) {
1940 
1941  if (this.bins === null) return null;
1942 
1943  var islines = (this.draw_kind=="lines"),
1944  ismark = (this.draw_kind=="mark"),
1945  bestbin = null,
1946  bestdist = 1e10,
1947  pmain = this.main_painter(),
1948  dist, grx, gry, n, bin;
1949 
1950  for (n=0;n<this.bins.length;++n) {
1951  bin = this.bins[n];
1952 
1953  grx = pmain.grx(bin.x);
1954  dist = pnt.x-grx;
1955 
1956  if (islines) {
1957  if ((n==0) && (dist < -10)) { bestbin = null; break; } // check first point
1958  } else {
1959  gry = pmain.gry(bin.y);
1960  dist = dist*dist + (pnt.y-gry)*(pnt.y-gry);
1961  }
1962 
1963  if (Math.abs(dist) < bestdist) {
1964  bestdist = dist;
1965  bestbin = bin;
1966  }
1967  }
1968 
1969  // check last point
1970  if ((dist > 10) && islines) bestbin = null;
1971 
1972  var radius = Math.max(this.lineatt.width + 3, 4);
1973 
1974  if (this.marker_size > 0) radius = Math.max(Math.round(this.marker_size*7), radius);
1975 
1976  if (bestbin !== null)
1977  bestdist = Math.sqrt(Math.pow(pnt.x-pmain.grx(bestbin.x),2) + Math.pow(pnt.y-pmain.gry(bestbin.y),2));
1978 
1979  // console.log('draw kind', this.draw_kind, 'best dist', bestdist,'best bin', bestbin);
1980 
1981  if (!islines && !ismark && (bestdist>radius)) bestbin = null;
1982 
1983  if (ismark && (bestbin!==null)) {
1984  if ((pnt.nproc == 1) && (bestdist>radius)) bestbin = null; else
1985  if ((this.bins.length==1) && (bestdist>3*radius)) bestbin = null;
1986  }
1987 
1988  var ttbin = this.draw_g.select(".tooltip_bin");
1989 
1990  if (bestbin===null) {
1991  ttbin.remove();
1992  return null;
1993  }
1994 
1995  var res = { x: pmain.grx(bestbin.x), y: pmain.gry(bestbin.y),
1996  color1: this.lineatt.color,
1997  lines: this.TooltipText(bestbin, true) };
1998 
1999  if (this.fillatt && this.fillatt.used) res.color2 = this.fillatt.color;
2000 
2001  if (!islines) {
2002  res.color1 = JSROOT.Painter.root_colors[this.GetObject().fMarkerColor];
2003  if (!res.color2) res.color2 = res.color1;
2004  }
2005 
2006  if (ttbin.empty())
2007  ttbin = this.draw_g.append("svg:g")
2008  .attr("class","tooltip_bin");
2009 
2010  var gry1, gry2;
2011 
2012  if ((this.optionEF > 0) && islines) {
2013  gry1 = pmain.gry(bestbin.y - bestbin.eylow);
2014  gry2 = pmain.gry(bestbin.y + bestbin.eyhigh);
2015  } else {
2016  gry1 = gry2 = pmain.gry(bestbin.y);
2017  }
2018 
2019  res.exact = (Math.abs(pnt.x - res.x) <= radius) &&
2020  ((Math.abs(pnt.y - gry1) <= radius) || (Math.abs(pnt.y - gry2) <= radius));
2021 
2022  res.menu = res.exact;
2023  res.menu_dist = Math.sqrt((pnt.x-res.x)*(pnt.x-res.x) + Math.pow(Math.min(Math.abs(pnt.y-gry1),Math.abs(pnt.y-gry2)),2));
2024 
2025  res.changed = ttbin.property("current_bin") !== bestbin;
2026 
2027  if (res.changed) {
2028  ttbin.selectAll("*").remove(); // first delete all childs
2029  ttbin.property("current_bin", bestbin);
2030 
2031  if (ismark) {
2032  ttbin.append("svg:rect")
2033  .attr("class","h1bin")
2034  .style("pointer-events","none")
2035  .style("opacity", "0.3")
2036  .attr("x", (res.x - radius).toFixed(1))
2037  .attr("y", (res.y - radius).toFixed(1))
2038  .attr("width", (2*radius).toFixed(1))
2039  .attr("height", (2*radius).toFixed(1));
2040  } else {
2041  ttbin.append("svg:circle").attr("cy", gry1.toFixed(1))
2042  if (Math.abs(gry1-gry2) > 1)
2043  ttbin.append("svg:circle").attr("cy", gry2.toFixed(1));
2044 
2045  var elem = ttbin.selectAll("circle")
2046  .attr("r", radius)
2047  .attr("cx", res.x.toFixed(1));
2048 
2049  if (!islines) {
2050  elem.style('stroke', res.color1 == 'black' ? 'green' : 'black').style('fill','none');
2051  } else {
2052  if (this.optionLine)
2053  elem.call(this.lineatt.func);
2054  else
2055  elem.style('stroke','black');
2056  if (this.optionFill > 0)
2057  elem.call(this.fillatt.func);
2058  else
2059  elem.style('fill','none');
2060  }
2061  }
2062  }
2063 
2064  return res;
2065  }
2066 
2067  JSROOT.TGraphPainter.prototype.UpdateObject = function(obj) {
2068  if (!this.MatchObjectType(obj)) return false;
2069 
2070  // if our own histogram was used as axis drawing, we need update histogram as well
2071  if (this.ownhisto)
2072  this.main_painter().UpdateObject(obj.fHistogram);
2073 
2074  var graph = this.GetObject();
2075  // TODO: make real update of TGraph object content
2076  graph.fX = obj.fX;
2077  graph.fY = obj.fY;
2078  graph.fNpoints = obj.fNpoints;
2079  this.CreateBins();
2080  return true;
2081  }
2082 
2083  JSROOT.TGraphPainter.prototype.CanZoomIn = function(axis,min,max) {
2084  // allow to zoom TGraph only when at least one point in the range
2085 
2086  var gr = this.GetObject();
2087  if ((gr===null) || (axis!=="x")) return false;
2088 
2089  for (var n=0; n < gr.fNpoints; ++n)
2090  if ((min < gr.fX[n]) && (gr.fX[n] < max)) return true;
2091 
2092  return false;
2093  }
2094 
2095  JSROOT.TGraphPainter.prototype.ButtonClick = function(funcname) {
2096 
2097  if (funcname !== "ToggleZoom") return false;
2098 
2099  var main = this.main_painter();
2100  if (main === null) return false;
2101 
2102  if ((this.xmin===this.xmax) && (this.ymin = this.ymax)) return false;
2103 
2104  main.Zoom(this.xmin, this.xmax, this.ymin, this.ymax);
2105 
2106  return true;
2107  }
2108 
2109 
2110  JSROOT.TGraphPainter.prototype.DrawNextFunction = function(indx, callback) {
2111  // method draws next function from the functions list
2112 
2113  var graph = this.GetObject();
2114 
2115  if ((graph.fFunctions === null) || (indx >= graph.fFunctions.arr.length))
2116  return JSROOT.CallBack(callback);
2117 
2118  var func = graph.fFunctions.arr[indx];
2119  var opt = graph.fFunctions.opt[indx];
2120 
2121  var painter = JSROOT.draw(this.divid, func, opt);
2122  if (painter) return painter.WhenReady(this.DrawNextFunction.bind(this, indx+1, callback));
2123 
2124  this.DrawNextFunction(indx+1, callback);
2125  }
2126 
2127  JSROOT.Painter.drawGraph = function(divid, graph, opt) {
2128  JSROOT.extend(this, new JSROOT.TGraphPainter(graph));
2129 
2130  this.CreateBins();
2131 
2132  this.SetDivId(divid, -1); // just to get access to existing elements
2133 
2134  if (this.main_painter() == null) {
2135  if (graph.fHistogram == null)
2136  graph.fHistogram = this.CreateHistogram();
2137  JSROOT.Painter.drawHistogram1D(divid, graph.fHistogram, "AXIS");
2138  this.ownhisto = true;
2139  }
2140 
2141  this.SetDivId(divid);
2142  this.DecodeOptions(opt);
2143  this.DrawBins();
2144 
2145  this.DrawNextFunction(0, this.DrawingReady.bind(this));
2146 
2147  return this;
2148  }
2149 
2150  // =============================================================
2151 
2152  JSROOT.Painter.drawMultiGraph = function(divid, mgraph, opt) {
2153  // function call with bind(painter)
2154 
2155  this.firstpainter = null;
2156  this.autorange = false;
2157  this.painters = []; // keep painters to be able update objects
2158 
2159  this.SetDivId(divid, -1); // it may be no element to set divid
2160 
2161  this.UpdateObject = function(obj) {
2162  if (!this.MatchObjectType(obj)) return false;
2163 
2164  var mgraph = this.GetObject(),
2165  graphs = obj.fGraphs;
2166 
2167  mgraph.fTitle = obj.fTitle;
2168 
2169  var isany = false;
2170  if (this.firstpainter) {
2171  var histo = obj.fHistogram;
2172  if (this.autorange && (histo == null))
2173  histo = this.ScanGraphsRange(graphs);
2174  if (this.firstpainter.UpdateObject(histo)) isany = true;
2175  }
2176 
2177  for (var i = 0; i < graphs.arr.length; ++i) {
2178  if (i<this.painters.length)
2179  if (this.painters[i].UpdateObject(graphs.arr[i])) isany = true;
2180  }
2181 
2182  return isany;
2183  }
2184 
2185  this.ComputeGraphRange = function(res, gr) {
2186  // Compute the x/y range of the points in this graph
2187  if (gr.fNpoints == 0) return;
2188  if (res.first) {
2189  res.xmin = res.xmax = gr.fX[0];
2190  res.ymin = res.ymax = gr.fY[0];
2191  res.first = false;
2192  }
2193  for (var i=0; i < gr.fNpoints; ++i) {
2194  res.xmin = Math.min(res.xmin, gr.fX[i]);
2195  res.xmax = Math.max(res.xmax, gr.fX[i]);
2196  res.ymin = Math.min(res.ymin, gr.fY[i]);
2197  res.ymax = Math.max(res.ymax, gr.fY[i]);
2198  }
2199  return res;
2200  }
2201 
2202  this['padtoX'] = function(pad, x) {
2203  // Convert x from pad to X.
2204  if (pad.fLogx && (x < 50))
2205  return Math.exp(2.302585092994 * x);
2206  return x;
2207  }
2208 
2209  this.ScanGraphsRange = function(graphs, histo, pad) {
2210  var mgraph = this.GetObject(),
2211  maximum, minimum, dx, dy, uxmin = 0, uxmax = 0, logx = false, logy = false,
2212  rw = { xmin: 0, xmax: 0, ymin: 0, ymax: 0, first: true };
2213 
2214  if (pad!=null) {
2215  logx = pad.fLogx;
2216  logy = pad.fLogy;
2217  rw.xmin = pad.fUxmin;
2218  rw.xmax = pad.fUxmax;
2219  rw.ymin = pad.fUymin;
2220  rw.ymax = pad.fUymax;
2221  rw.first = false;
2222  }
2223  if (histo!=null) {
2224  minimum = histo.fYaxis.fXmin;
2225  maximum = histo.fYaxis.fXmax;
2226  if (pad!=null) {
2227  uxmin = this.padtoX(pad, rw.xmin);
2228  uxmax = this.padtoX(pad, rw.xmax);
2229  }
2230  } else {
2231  this.autorange = true;
2232 
2233  for (var i = 0; i < graphs.arr.length; ++i)
2234  this.ComputeGraphRange(rw, graphs.arr[i]);
2235 
2236  if (rw.xmin == rw.xmax) rw.xmax += 1.;
2237  if (rw.ymin == rw.ymax) rw.ymax += 1.;
2238  dx = 0.05 * (rw.xmax - rw.xmin);
2239  dy = 0.05 * (rw.ymax - rw.ymin);
2240  uxmin = rw.xmin - dx;
2241  uxmax = rw.xmax + dx;
2242  if (logy) {
2243  if (rw.ymin <= 0) rw.ymin = 0.001 * rw.ymax;
2244  minimum = rw.ymin / (1 + 0.5 * JSROOT.log10(rw.ymax / rw.ymin));
2245  maximum = rw.ymax * (1 + 0.2 * JSROOT.log10(rw.ymax / rw.ymin));
2246  } else {
2247  minimum = rw.ymin - dy;
2248  maximum = rw.ymax + dy;
2249  }
2250  if (minimum < 0 && rw.ymin >= 0)
2251  minimum = 0;
2252  if (maximum > 0 && rw.ymax <= 0)
2253  maximum = 0;
2254  }
2255 
2256  if (uxmin < 0 && rw.xmin >= 0) {
2257  if (logx) uxmin = 0.9 * rw.xmin;
2258  else uxmin = 0;
2259  }
2260  if (uxmax > 0 && rw.xmax <= 0) {
2261  if (logx) uxmax = 1.1 * rw.xmax;
2262  else uxmax = 0;
2263  }
2264 
2265  if (mgraph.fMinimum != -1111)
2266  rw.ymin = minimum = mgraph.fMinimum;
2267  if (mgraph.fMaximum != -1111)
2268  rw.ymax = maximum = mgraph.fMaximum;
2269 
2270  if (minimum < 0 && rw.ymin >= 0) {
2271  if (logy) minimum = 0.9 * rw.ymin;
2272  }
2273  if (maximum > 0 && rw.ymax <= 0) {
2274  if (logy) maximum = 1.1 * rw.ymax;
2275  }
2276  if (minimum <= 0 && logy)
2277  minimum = 0.001 * maximum;
2278  if (uxmin <= 0 && logx) {
2279  if (uxmax > 1000)
2280  uxmin = 1;
2281  else
2282  uxmin = 0.001 * uxmax;
2283  }
2284 
2285  // Create a temporary histogram to draw the axis (if necessary)
2286  if (!histo) {
2287  histo = JSROOT.Create("TH1I");
2288  histo.fTitle = mgraph.fTitle;
2289  histo.fXaxis.fXmin = uxmin;
2290  histo.fXaxis.fXmax = uxmax;
2291  }
2292 
2293  histo.fYaxis.fXmin = minimum;
2294  histo.fYaxis.fXmax = maximum;
2295 
2296  return histo;
2297  }
2298 
2299  this.DrawAxis = function() {
2300  // draw special histogram
2301 
2302  var mgraph = this.GetObject();
2303 
2304  var histo = this.ScanGraphsRange(mgraph.fGraphs, mgraph.fHistogram, this.root_pad());
2305 
2306  // histogram painter will be first in the pad, will define axis and
2307  // interactive actions
2308  this.firstpainter = JSROOT.Painter.drawHistogram1D(this.divid, histo, "AXIS");
2309  }
2310 
2311  this.DrawNextFunction = function(indx, callback) {
2312  // method draws next function from the functions list
2313 
2314  var mgraph = this.GetObject();
2315 
2316  if ((mgraph.fFunctions == null) || (indx >= mgraph.fFunctions.arr.length))
2317  return JSROOT.CallBack(callback);
2318 
2319  var func = mgraph.fFunctions.arr[indx];
2320  var opt = mgraph.fFunctions.opt[indx];
2321 
2322  var painter = JSROOT.draw(this.divid, func, opt);
2323  if (painter) return painter.WhenReady(this.DrawNextFunction.bind(this, indx+1, callback));
2324 
2325  this.DrawNextFunction(indx+1, callback);
2326  }
2327 
2328  this.DrawNextGraph = function(indx, opt) {
2329  var graphs = this.GetObject().fGraphs;
2330 
2331  // at the end of graphs drawing draw functions (if any)
2332  if (indx >= graphs.arr.length)
2333  return this.DrawNextFunction(0, this.DrawingReady.bind(this));
2334 
2335  var drawopt = graphs.opt[indx];
2336  if ((drawopt==null) || (drawopt == "")) drawopt = opt;
2337  var subp = JSROOT.draw(this.divid, graphs.arr[indx], drawopt);
2338  this.painters.push(subp);
2339  subp.WhenReady(this.DrawNextGraph.bind(this, indx+1, opt));
2340  }
2341 
2342  if (opt == null) opt = "";
2343  opt = opt.toUpperCase().replace("3D","").replace("FB",""); // no 3D supported, FB not clear
2344 
2345  if ((opt.indexOf("A") >= 0) || (this.main_painter()==null)) {
2346  opt = opt.replace("A","");
2347  this.DrawAxis();
2348  }
2349  this.SetDivId(divid);
2350 
2351  this.DrawNextGraph(0, opt);
2352 
2353  return this;
2354  }
2355 
2356  // ==============================================================================
2357 
2358  JSROOT.Painter.drawLegend = function(divid, obj, opt) {
2359 
2360  JSROOT.extend(this, new JSROOT.TPavePainter(obj));
2361 
2362  this.SetDivId(divid);
2363 
2364  this.DrawLegendItems = function(w, h) {
2365 
2366  var legend = this.GetObject(),
2367  nlines = legend.fPrimitives.arr.length,
2368  ncols = legend.fNColumns,
2369  nrows = nlines;
2370 
2371  if (ncols<2) ncols = 1; else { while ((nrows-1)*ncols >= nlines) nrows--; }
2372 
2373  this.StartTextDrawing(legend.fTextFont, h / (nlines * 1.2));
2374 
2375  var tcolor = JSROOT.Painter.root_colors[legend.fTextColor],
2376  column_width = Math.round(w/ncols),
2377  padding_x = Math.round(0.03*w/ncols),
2378  padding_y = Math.round(0.03*h),
2379  step_y = (h - 2*padding_y)/nrows,
2380  any_opt = false;
2381 
2382  for (var i = 0; i < nlines; ++i) {
2383  var leg = legend.fPrimitives.arr[i];
2384  var lopt = leg.fOption.toLowerCase();
2385 
2386  var icol = i % ncols, irow = (i - icol) / ncols;
2387 
2388  var x0 = icol * column_width;
2389  var tpos_x = x0 + Math.round(legend.fMargin*column_width);
2390 
2391  var pos_y = Math.round(padding_y + irow*step_y); // top corner
2392  var mid_y = Math.round(padding_y + (irow+0.5)*step_y); // center line
2393 
2394  var o_fill = leg, o_marker = leg, o_line = leg;
2395 
2396  var mo = leg.fObject;
2397 
2398  var painter = null;
2399 
2400  if ((mo !== null) && (typeof mo == 'object')) {
2401  if ('fLineColor' in mo) o_line = mo;
2402  if ('fFillColor' in mo) o_fill = mo;
2403  if ('fMarkerColor' in mo) o_marker = mo;
2404 
2405  painter = this.FindPainterFor(mo);
2406  }
2407 
2408  // Draw fill pattern (in a box)
2409  if (lopt.indexOf('f') != -1) {
2410  var fillatt = (painter && painter.fillatt) ? painter.fillatt : this.createAttFill(o_fill);
2411  // box total height is yspace*0.7
2412  // define x,y as the center of the symbol for this entry
2413  this.draw_g.append("svg:rect")
2414  .attr("x", x0 + padding_x)
2415  .attr("y", Math.round(pos_y+step_y*0.1))
2416  .attr("width", tpos_x - 2*padding_x - x0)
2417  .attr("height", Math.round(step_y*0.8))
2418  .call(fillatt.func);
2419  }
2420 
2421  // Draw line
2422  if (lopt.indexOf('l') != -1) {
2423  var lineatt = (painter && painter.lineatt) ? painter.lineatt : JSROOT.Painter.createAttLine(o_line)
2424  this.draw_g.append("svg:line")
2425  .attr("x1", x0 + padding_x)
2426  .attr("y1", mid_y)
2427  .attr("x2", tpos_x - padding_x)
2428  .attr("y2", mid_y)
2429  .call(lineatt.func);
2430  }
2431 
2432  // Draw error
2433  if (lopt.indexOf('e') != -1 && (lopt.indexOf('l') == -1 || lopt.indexOf('f') != -1)) {
2434  }
2435 
2436  // Draw Polymarker
2437  if (lopt.indexOf('p') != -1) {
2438  var marker = (painter && painter.markeratt) ? painter.markeratt : JSROOT.Painter.createAttMarker(o_marker);
2439  this.draw_g
2440  .append("svg:path")
2441  .attr("d", marker.create((x0 + tpos_x)/2, mid_y))
2442  .call(marker.func);
2443  }
2444 
2445  var pos_x = tpos_x;
2446  if (lopt.length>0) any_opt = true;
2447  else if (!any_opt) pos_x = x0 + padding_x;
2448 
2449  this.DrawText("start", pos_x, pos_y, x0+column_width-pos_x-padding_x, step_y, leg.fLabel, tcolor);
2450  }
2451 
2452  // rescale after all entries are shown
2453  this.FinishTextDrawing();
2454  }
2455 
2456  this.PaveDrawFunc = this.DrawLegendItems;
2457 
2458  this.Redraw();
2459 
2460  return this.DrawingReady();
2461  }
2462 
2463  // ===========================================================================
2464 
2465  JSROOT.Painter.drawPaletteAxis = function(divid,palette,opt) {
2466 
2467  // disable draw of shadow element of TPave
2468  palette.fBorderSize = 1;
2469  palette.fShadowColor = 0;
2470 
2471  JSROOT.extend(this, new JSROOT.TPavePainter(palette));
2472 
2473  this.SetDivId(divid);
2474 
2475  this.z_handle = new JSROOT.TAxisPainter(palette.fAxis, true);
2476  this.z_handle.SetDivId(divid, -1);
2477 
2478  this['MakeIcon'] = function(contour, z) {
2479  var h = this.frame_height();
2480  var res = "";
2481  var prev = { x : -1, y : -1, width: 0, height: 0, fill:"" };
2482  for (var i=0;i<contour.length-1;++i) {
2483  var z0 = z(contour[i]);
2484  var z1 = z(contour[i+1]);
2485  var col = this.main_painter().getValueColor(contour[i]);
2486 
2487  var pnt = { x: 128, width: 256, y: Math.round(z1/h*512) , height: Math.round((z0-z1)/h*512), fill: col };
2488 
2489  if (res.length == 0) res = "["; else res+=",";
2490 
2491  var separ = "{";
2492  if (pnt.x != prev.x) { res += separ + "x:" + Math.round(pnt.x); separ =","; }
2493  if (pnt.y != prev.y) { res += separ + "y:" + Math.round(pnt.y); separ =","; }
2494  if (pnt.width != prev.width) { res += separ + "w:" + Math.round(pnt.width); separ =","; }
2495  if (pnt.height != prev.height) { res += separ + "h:" + Math.round(pnt.height); separ =","; }
2496  if (pnt.fill != prev.fill) { res += separ + "f:'" + pnt.fill + "'"; separ =","; }
2497  res += "}";
2498 
2499  prev = pnt;
2500  }
2501 
2502  res += "]";
2503  }
2504 
2505  this.DrawAxisPalette = function(s_width, s_height) {
2506 
2507  var pthis = this, palette = this.GetObject(), axis = palette.fAxis;
2508 
2509  var nbr1 = axis.fNdiv % 100;
2510  if (nbr1<=0) nbr1 = 8;
2511 
2512  var pos_x = parseInt(this.draw_g.attr("x")), // pave position
2513  pos_y = parseInt(this.draw_g.attr("y")),
2514  width = this.pad_width(),
2515  height = this.pad_height(),
2516  axisOffset = axis.fLabelOffset * width,
2517  contour = this.main_painter().fContour,
2518  zmin = 0, zmax = this.main_painter().gmaxbin;
2519 
2520  if (contour!==null) {
2521  zmin = contour[0];
2522  zmax = contour[contour.length-1];
2523  }
2524 
2525  var z = null, z_kind = "normal";
2526 
2527  if (this.main_painter().options.Logz) {
2528  z = d3.scale.log();
2529  z_kind = "log";
2530  } else {
2531  z = d3.scale.linear();
2532  }
2533  z.domain([zmin, zmax]).range([s_height,0]);
2534 
2535  if ((contour==null) || this._can_move)
2536  // we need such rect to correctly calculate size
2537  this.draw_g.append("svg:rect")
2538  .attr("x", 0)
2539  .attr("y", 0)
2540  .attr("width", s_width)
2541  .attr("height", s_height)
2542  .attr("fill", 'white');
2543  else
2544  for (var i=0;i<contour.length-1;++i) {
2545  var z0 = z(contour[i]),
2546  z1 = z(contour[i+1]),
2547  col = this.main_painter().getValueColor(contour[i]);
2548 
2549  var r = this.draw_g.append("svg:rect")
2550  .attr("x", 0)
2551  .attr("y", z1.toFixed(1))
2552  .attr("width", s_width)
2553  .attr("height", (z0-z1).toFixed(1))
2554  .style("fill", col)
2555  .style("stroke", col);
2556 
2557 
2558  if (JSROOT.gStyle.Tooltip > 0)
2559  r.on('mouseover', function() {
2560  d3.select(this).transition().duration(100).style("stroke", "black").style("stroke-width", "2");
2561  }).on('mouseout', function() {
2562  d3.select(this).transition().duration(100).style("stroke", d3.select(this).style('fill')).style("stroke-width", "");
2563  }).append("svg:title").text(contour[i].toFixed(2) + " - " + contour[i+1].toFixed(2));
2564 
2565  if (JSROOT.gStyle.Zooming)
2566  r.on("dblclick", function() { pthis.main_painter().Unzoom("z"); });
2567  }
2568 
2569 
2570  this.z_handle.SetAxisConfig("zaxis", z_kind, z, zmin, zmax, zmin, zmax);
2571 
2572  this.z_handle.DrawAxis(this.draw_g, s_width, s_height, "translate(" + s_width + ", 0)");
2573 
2574  if (this._can_move && ('getBoundingClientRect' in this.draw_g.node())) {
2575  this._can_move = false; // do it only once
2576 
2577  var rect = this.draw_g.node().getBoundingClientRect();
2578 
2579  var shift = (pos_x + parseInt(rect.width)) - Math.round(0.995*width) + 3;
2580 
2581  if (shift>0) {
2582  this.draw_g.attr("x", pos_x - shift).attr("y", pos_y)
2583  .attr("transform", "translate(" + (pos_x-shift) + ", " + pos_y + ")");
2584  palette.fX1NDC -= shift/width;
2585  palette.fX2NDC -= shift/width;
2586  return;
2587  }
2588  }
2589 
2590  if (!JSROOT.gStyle.Zooming) return;
2591 
2592  var evnt = null, doing_zoom = false, sel1 = 0, sel2 = 0, zoom_rect = null;
2593 
2594  function moveRectSel() {
2595 
2596  if (!doing_zoom) return;
2597 
2598  d3.event.preventDefault();
2599  var m = d3.mouse(evnt);
2600 
2601  if (m[1] < sel1) sel1 = m[1]; else sel2 = m[1];
2602 
2603  zoom_rect.attr("y", sel1)
2604  .attr("height", Math.abs(sel2-sel1));
2605  }
2606 
2607  function endRectSel() {
2608  if (!doing_zoom) return;
2609 
2610  d3.event.preventDefault();
2611  d3.select(window).on("mousemove.colzoomRect", null)
2612  .on("mouseup.colzoomRect", null);
2613  zoom_rect.remove();
2614  zoom_rect = null;
2615  doing_zoom = false;
2616 
2617  var zmin = Math.min(z.invert(sel1), z.invert(sel2));
2618  var zmax = Math.max(z.invert(sel1), z.invert(sel2));
2619 
2620  pthis.main_painter().Zoom(undefined, undefined, undefined, undefined, zmin, zmax);
2621  }
2622 
2623  function startRectSel() {
2624 
2625  // ignore when touch selection is actiavated
2626  if (doing_zoom) return;
2627  doing_zoom = true;
2628 
2629  d3.event.preventDefault();
2630 
2631  evnt = this;
2632  var origin = d3.mouse(evnt);
2633 
2634  sel1 = sel2 = origin[1];
2635 
2636  zoom_rect = pthis.draw_g
2637  .append("svg:rect")
2638  .attr("class", "zoom")
2639  .attr("id", "colzoomRect")
2640  .attr("x", "0")
2641  .attr("width", s_width)
2642  .attr("y", sel1)
2643  .attr("height", 5);
2644 
2645  d3.select(window).on("mousemove.colzoomRect", moveRectSel)
2646  .on("mouseup.colzoomRect", endRectSel, true);
2647 
2648  d3.event.stopPropagation();
2649  }
2650 
2651  this.draw_g.select(".axis_zoom")
2652  .on("mousedown", startRectSel)
2653  .on("dblclick", function() { pthis.main_painter().Unzoom("z"); });
2654  }
2655 
2656  this.ShowContextMenu = function(evnt) {
2657  this.main_painter().ShowContextMenu("z", evnt, this.GetObject().fAxis);
2658  }
2659 
2660  this.Redraw = function() {
2661  this.Enabled = true;
2662  var main = this.main_painter();
2663  this.UseContextMenu = (main !== null);
2664  if ((main !== null) && main.options)
2665  this.Enabled = (main.options.Zscale > 0) && (main.options.Color > 0) && (main.options.Lego === 0);
2666 
2667  this.DrawPave();
2668  }
2669 
2670  this.PaveDrawFunc = this.DrawAxisPalette;
2671 
2672  // workaround to let copmlete pallete draw when actual palette colors already there
2673  this.CompleteDraw = this.Redraw;
2674 
2675  this._can_move = (opt === 'canmove');
2676 
2677  this.Redraw();
2678 
2679  return this.DrawingReady();
2680  }
2681 
2682  // ==================== painter for TH2 histograms ==============================
2683 
2684  JSROOT.TH2Painter = function(histo) {
2685  JSROOT.THistPainter.call(this, histo);
2686  this.fContour = null; // contour levels
2687  this.fUserContour = false; // are this user-defined levels
2688  this.fPalette = null;
2689  }
2690 
2691  JSROOT.TH2Painter.prototype = Object.create(JSROOT.THistPainter.prototype);
2692 
2693  JSROOT.TH2Painter.prototype.FillHistContextMenu = function(menu) {
2694  if (!this.draw_content) return;
2695 
2696  // painter automatically bind to mene callbacks
2697  menu.add("Auto zoom-in", this.AutoZoom);
2698 
2699  menu.addDrawMenu("Draw with", ["col", "colz", "scat", "box", "text", "lego"], function(arg) {
2700  this.options = this.DecodeOptions(arg);
2701  if (this.options.Zscale > 0)
2702  // draw new palette, resize frame if required
2703  this.DrawNewPalette(true);
2704  this.RedrawPad();
2705  if (this.options.Lego == 0) this.AddInteractive();
2706  });
2707  }
2708 
2709  JSROOT.TH2Painter.prototype.ButtonClick = function(funcname) {
2710  if (JSROOT.THistPainter.prototype.ButtonClick.call(this, funcname)) return true;
2711 
2712  if (this !== this.main_painter()) return false;
2713 
2714  switch(funcname) {
2715  case "ToggleColor": this.ToggleColor(); break;
2716  case "ToggleColorZ":
2717  if (this.options.Lego == 0 && this.options.Color > 0) this.ToggleColz();
2718  break;
2719  case "Toggle3D":
2720  this.options.Lego = this.options.Lego > 0 ? 0 : 1;
2721  this.RedrawPad();
2722  break;
2723  default: return false;
2724  }
2725 
2726  // all methods here should not be processed further
2727  return true;
2728  }
2729 
2730  JSROOT.TH2Painter.prototype.FillToolbar = function() {
2731  JSROOT.THistPainter.prototype.FillToolbar.call(this);
2732 
2733  var pp = this.pad_painter(true);
2734  if (pp===null) return;
2735 
2736  pp.AddButton(JSROOT.ToolbarIcons.th2color, "Toggle color", "ToggleColor");
2737  pp.AddButton(JSROOT.ToolbarIcons.th2colorz, "Toggle color palette", "ToggleColorZ");
2738  pp.AddButton(JSROOT.ToolbarIcons.th2draw3d, "Toggle 3D mode", "Toggle3D");
2739  }
2740 
2741  JSROOT.TH2Painter.prototype.ToggleColor = function() {
2742 
2743  var toggle = true;
2744 
2745  if (this.options.Lego > 0) { this.options.Lego = 0; toggle = false; }
2746 
2747  if (this.options.Color == 0) {
2748  this.options.Color = ('LastColor' in this.options) ? this.options.LastColor : 1;
2749  } else
2750  if (toggle) {
2751  this.options.LastColor = this.options.Color;
2752  this.options.Color = 0;
2753  }
2754 
2755  if ((this.options.Color > 0) && (this.options.Zscale > 0))
2756  this.DrawNewPalette(true);
2757 
2758  this.RedrawPad();
2759  }
2760 
2761  JSROOT.TH2Painter.prototype.FindPalette = function(remove) {
2762 
2763  var funcs = this.GetObject().fFunctions;
2764  if (funcs === null) return null;
2765 
2766  for (var i = 0; i < funcs.arr.length; ++i) {
2767  var func = funcs.arr[i];
2768  if (func._typename !== 'TPaletteAxis') continue;
2769  if (remove) {
2770  funcs.RemoveAt(i);
2771  if (this.pad_painter())
2772  this.pad_painter().RemovePrimitive(func);
2773  return null;
2774  }
2775  return func;
2776  }
2777 
2778  return null;
2779  }
2780 
2781  JSROOT.TH2Painter.prototype.DrawNewPalette = function(force_resize) {
2782  // only when create new palette, one could change frame size
2783 
2784  var pal = this.FindPalette(), histo = this.GetObject();
2785 
2786  if ((pal !== null) && !force_resize) return;
2787 
2788  if (pal === null) {
2789  pal = JSROOT.Create('TPave');
2790 
2791  JSROOT.extend(pal, { _typename: "TPaletteAxis", fName: "TPave", fH: null, fAxis: null,
2792  fX1NDC: 0.91, fX2NDC: 0.95, fY1NDC: 0.1, fY2NDC: 0.9, fInit: 1 } );
2793 
2794  pal.fAxis = JSROOT.Create('TGaxis');
2795 
2796  // set values from base classes
2797 
2798  JSROOT.extend(pal.fAxis, { fTitle: histo.fZaxis.fTitle,
2799  fLineColor: 1, fLineSyle: 1, fLineWidth: 1,
2800  fTextAngle: 0, fTextSize: 0.04, fTextAlign: 11, fTextColor: 1, fTextFont: 42 });
2801 
2802  if (histo.fFunctions == null)
2803  histo.fFunctions = JSROOT.Create("TList");
2804 
2805  // place colz in the beginning, that stat box is always drawn on the top
2806  histo.fFunctions.AddFirst(pal);
2807  }
2808 
2809  var frame_painter = this.frame_painter();
2810 
2811  // keep palette width
2812  pal.fX2NDC = frame_painter.fX2NDC + 0.01 + (pal.fX2NDC - pal.fX1NDC);
2813  pal.fX1NDC = frame_painter.fX2NDC + 0.01;
2814  pal.fY1NDC = frame_painter.fY1NDC;
2815  pal.fY2NDC = frame_painter.fY2NDC;
2816 
2817  var pal_painter = this.FindPainterFor(pal);
2818 
2819  if (pal_painter === null) {
2820  // when histogram drawn on sub pad, let draw new axis object on the same pad
2821  this.svg_canvas().property('current_pad', this.pad_name);
2822  pal_painter = JSROOT.draw(this.divid, pal, "canmove");
2823  this.svg_canvas().property('current_pad', '');
2824  } else {
2825  pal_painter._can_move = true;
2826  pal_painter.Redraw();
2827  }
2828 
2829  if (pal.fX1NDC < frame_painter.fX2NDC) {
2830  frame_painter.fX2NDC = pal.fX1NDC - 0.01;
2831  frame_painter.Redraw();
2832  }
2833  }
2834 
2835  JSROOT.TH2Painter.prototype.ToggleColz = function() {
2836  if (this.options.Zscale > 0) {
2837  this.options.Zscale = 0;
2838  } else {
2839  this.options.Zscale = 1;
2840  this.DrawNewPalette(true);
2841  }
2842 
2843  this.RedrawPad();
2844  }
2845 
2846  JSROOT.TH2Painter.prototype.AutoZoom = function() {
2847  var i1 = this.GetSelectIndex("x", "left", -1),
2848  i2 = this.GetSelectIndex("x", "right", 1),
2849  j1 = this.GetSelectIndex("y", "left", -1),
2850  j2 = this.GetSelectIndex("y", "right", 1),
2851  i,j, histo = this.GetObject();
2852 
2853  if ((i1 == i2) || (j1 == j2)) return;
2854 
2855  // first find minimum
2856  var min = histo.getBinContent(i1 + 1, j1 + 1);
2857  for (i = i1; i < i2; ++i)
2858  for (j = j1; j < j2; ++j)
2859  if (histo.getBinContent(i + 1, j + 1) < min)
2860  min = histo.getBinContent(i + 1, j + 1);
2861  if (min>0) return; // if all points positive, no chance for autoscale
2862 
2863  var ileft = i2, iright = i1, jleft = j2, jright = j1;
2864 
2865  for (i = i1; i < i2; ++i)
2866  for (j = j1; j < j2; ++j)
2867  if (histo.getBinContent(i + 1, j + 1) > min) {
2868  if (i < ileft) ileft = i;
2869  if (i >= iright) iright = i + 1;
2870  if (j < jleft) jleft = j;
2871  if (j >= jright) jright = j + 1;
2872  }
2873 
2874  var xmin, xmax, ymin, ymax, isany = false;
2875 
2876  if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) {
2877  xmin = this.GetBinX(ileft);
2878  xmax = this.GetBinX(iright);
2879  isany = true;
2880  }
2881 
2882  if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) {
2883  ymin = this.GetBinY(jleft);
2884  ymax = this.GetBinY(jright);
2885  isany = true;
2886  }
2887 
2888  if (isany) this.Zoom(xmin, xmax, ymin, ymax);
2889  }
2890 
2891 
2892  JSROOT.TH2Painter.prototype.ScanContent = function() {
2893  var i,j,histo = this.GetObject();
2894 
2895  this.nbinsx = histo.fXaxis.fNbins;
2896  this.nbinsy = histo.fYaxis.fNbins;
2897 
2898  // used in CreateXY method
2899 
2900  this.CreateAxisFuncs(true);
2901 
2902  // global min/max, used at the moment in 3D drawing
2903  this.gminbin = this.gmaxbin = histo.getBinContent(1, 1);
2904  this.gmin0bin = null;
2905  for (i = 0; i < this.nbinsx; ++i) {
2906  for (j = 0; j < this.nbinsy; ++j) {
2907  var bin_content = histo.getBinContent(i+1, j+1);
2908  if (bin_content < this.gminbin) this.gminbin = bin_content; else
2909  if (bin_content > this.gmaxbin) this.gmaxbin = bin_content;
2910  if (bin_content > 0)
2911  if ((this.gmin0bin===null) || (this.gmin0bin > bin_content)) this.gmin0bin = bin_content;
2912  }
2913  }
2914 
2915  // this value used for logz scale drawing
2916  if (this.gmin0bin === null) this.gmin0bin = this.gmaxbin*1e-4;
2917 
2918  // used to enable/disable stat box
2919  this.draw_content = this.gmaxbin > 0;
2920 
2921  // apply selected user X range if no other range selection was done
2922  if (this.is_main_painter() && (this.zoom_xmin === this.zoom_xmax) &&
2923  this.histo.fXaxis.TestBit(JSROOT.EAxisBits.kAxisRange) &&
2924  (this.histo.fXaxis.fFirst !== this.histo.fXaxis.fLast) &&
2925  ((this.histo.fXaxis.fFirst>1) || (this.histo.fXaxis.fLast <= this.nbinsx))) {
2926  this.zoom_xmin = this.histo.fXaxis.fFirst > 1 ? this.GetBinX(this.histo.fXaxis.fFirst-1) : this.xmin;
2927  this.zoom_xmax = this.histo.fXaxis.fLast <= this.nbinsx ? this.GetBinX(this.histo.fXaxis.fLast) : this.xmax;
2928  }
2929 
2930  // apply selected user Y range if no other range selection was done
2931  if (this.is_main_painter() && (this.zoom_ymin === this.zoom_ymax) &&
2932  this.histo.fYaxis.TestBit(JSROOT.EAxisBits.kAxisRange) &&
2933  (this.histo.fYaxis.fFirst !== this.histo.fYaxis.fLast) &&
2934  ((this.histo.fYaxis.fFirst>1) || (this.histo.fYaxis.fLast <= this.nbinsy))) {
2935  this.zoom_ymin = this.histo.fYaxis.fFirst > 1 ? this.GetBinY(this.histo.fYaxis.fFirst-1) : this.ymin;
2936  this.zoom_ymax = this.histo.fYaxis.fLast <= this.nbinsy ? this.GetBinY(this.histo.fYaxis.fLast) : this.ymax;
2937  }
2938  }
2939 
2940  JSROOT.TH2Painter.prototype.CountStat = function(cond) {
2941  var histo = this.GetObject(),
2942  stat_sum0 = 0, stat_sumx1 = 0, stat_sumy1 = 0,
2943  stat_sumx2 = 0, stat_sumy2 = 0, stat_sumxy = 0,
2944  xleft = this.GetSelectIndex("x", "left"),
2945  xright = this.GetSelectIndex("x", "right"),
2946  yleft = this.GetSelectIndex("y", "left"),
2947  yright = this.GetSelectIndex("y", "right"),
2948  xi, xside, xx, yi, yside, yy, zz,
2949  res = { entries: 0, integral: 0, meanx: 0, meany: 0, rmsx: 0, rmsy: 0, matrix: [0,0,0,0,0,0,0,0,0], xmax: 0, ymax:0, wmax: null };
2950 
2951  for (xi = 0; xi <= this.nbinsx + 1; ++xi) {
2952  xside = (xi <= xleft) ? 0 : (xi > xright ? 2 : 1);
2953  xx = this.GetBinX(xi - 0.5);
2954 
2955  for (yi = 0; yi <= this.nbinsy + 1; ++yi) {
2956  yside = (yi <= yleft) ? 0 : (yi > yright ? 2 : 1);
2957  yy = this.GetBinY(yi - 0.5);
2958 
2959  zz = histo.getBinContent(xi, yi);
2960 
2961  res.entries += zz;
2962 
2963  res.matrix[yside * 3 + xside] += zz;
2964 
2965  if ((xside != 1) || (yside != 1)) continue;
2966 
2967  if ((cond!=null) && !cond(xx,yy)) continue;
2968 
2969  if ((res.wmax==null) || (zz>res.wmax)) { res.wmax = zz; res.xmax = xx; res.ymax = yy; }
2970 
2971  stat_sum0 += zz;
2972  stat_sumx1 += xx * zz;
2973  stat_sumy1 += yy * zz;
2974  stat_sumx2 += xx * xx * zz;
2975  stat_sumy2 += yy * yy * zz;
2976  stat_sumxy += xx * yy * zz;
2977  }
2978  }
2979 
2980  if (!this.IsAxisZoomed("x") && !this.IsAxisZoomed("y") && (histo.fTsumw > 0)) {
2981  stat_sum0 = histo.fTsumw;
2982  stat_sumx1 = histo.fTsumwx;
2983  stat_sumx2 = histo.fTsumwx2;
2984  stat_sumy1 = histo.fTsumwy;
2985  stat_sumy2 = histo.fTsumwy2;
2986  stat_sumxy = histo.fTsumwxy;
2987  }
2988 
2989  if (stat_sum0 > 0) {
2990  res.meanx = stat_sumx1 / stat_sum0;
2991  res.meany = stat_sumy1 / stat_sum0;
2992  res.rmsx = Math.sqrt(stat_sumx2 / stat_sum0 - res.meanx * res.meanx);
2993  res.rmsy = Math.sqrt(stat_sumy2 / stat_sum0 - res.meany * res.meany);
2994  }
2995 
2996  if (res.wmax===null) res.wmax = 0;
2997  res.integral = stat_sum0;
2998 
2999  if (histo.fEntries > 1) res.entries = histo.fEntries;
3000 
3001  return res;
3002  }
3003 
3004  JSROOT.TH2Painter.prototype.FillStatistic = function(stat, dostat, dofit) {
3005  if (this.GetObject() === null) return false;
3006 
3007  var pave = stat.GetObject(),
3008  data = this.CountStat(),
3009  print_name = Math.floor(dostat % 10),
3010  print_entries = Math.floor(dostat / 10) % 10,
3011  print_mean = Math.floor(dostat / 100) % 10,
3012  print_rms = Math.floor(dostat / 1000) % 10,
3013  print_under = Math.floor(dostat / 10000) % 10,
3014  print_over = Math.floor(dostat / 100000) % 10,
3015  print_integral = Math.floor(dostat / 1000000) % 10,
3016  print_skew = Math.floor(dostat / 10000000) % 10,
3017  print_kurt = Math.floor(dostat / 100000000) % 10;
3018 
3019  if (print_name > 0)
3020  pave.AddText(this.GetObject().fName);
3021 
3022  if (print_entries > 0)
3023  pave.AddText("Entries = " + stat.Format(data.entries,"entries"));
3024 
3025  if (print_mean > 0) {
3026  pave.AddText("Mean x = " + stat.Format(data.meanx));
3027  pave.AddText("Mean y = " + stat.Format(data.meany));
3028  }
3029 
3030  if (print_rms > 0) {
3031  pave.AddText("Std Dev x = " + stat.Format(data.rmsx));
3032  pave.AddText("Std Dev y = " + stat.Format(data.rmsy));
3033  }
3034 
3035  if (print_integral > 0) {
3036  pave.AddText("Integral = " + stat.Format(data.matrix[4],"entries"));
3037  }
3038 
3039  if (print_skew > 0) {
3040  pave.AddText("Skewness x = <undef>");
3041  pave.AddText("Skewness y = <undef>");
3042  }
3043 
3044  if (print_kurt > 0)
3045  pave.AddText("Kurt = <undef>");
3046 
3047  if ((print_under > 0) || (print_over > 0)) {
3048  var m = data.matrix;
3049 
3050  pave.AddText("" + m[6].toFixed(0) + " | " + m[7].toFixed(0) + " | " + m[7].toFixed(0));
3051  pave.AddText("" + m[3].toFixed(0) + " | " + m[4].toFixed(0) + " | " + m[5].toFixed(0));
3052  pave.AddText("" + m[0].toFixed(0) + " | " + m[1].toFixed(0) + " | " + m[2].toFixed(0));
3053  }
3054 
3055  // adjust the size of the stats box wrt the number of lines
3056  var nlines = pave.fLines.arr.length,
3057  stath = nlines * JSROOT.gStyle.StatFontSize;
3058  if (stath <= 0 || 3 == (JSROOT.gStyle.StatFont % 10)) {
3059  stath = 0.25 * nlines * JSROOT.gStyle.StatH;
3060  pave.fY1NDC = 0.93 - stath;
3061  pave.fY2NDC = 0.93;
3062  }
3063 
3064  return true;
3065  }
3066 
3067  JSROOT.TH2Painter.prototype.CreateContour = function(nlevels, zmin, zmax, zminpositive) {
3068  if (nlevels<1) nlevels = 20;
3069  this.fContour = [];
3070  this.zmin = zmin;
3071  this.zmax = zmax;
3072 
3073  if (this.options.Logz) {
3074  if (this.zmax <= 0) this.zmax = 1.;
3075  if (this.zmin <= 0)
3076  this.zmin = (zminpositive > 0) ? 0.3*zminpositive : 0.0001*this.zmax;
3077  if (this.zmin >= this.zmax) this.zmin = 0.0001*this.zmax;
3078 
3079  var logmin = Math.log(this.zmin)/Math.log(10);
3080  var logmax = Math.log(this.zmax)/Math.log(10);
3081  var dz = (logmax-logmin)/nlevels;
3082  this.fContour.push(this.zmin);
3083  for (var level=1; level<nlevels; level++)
3084  this.fContour.push(Math.exp((logmin + dz*level)*Math.log(10)));
3085  this.fContour.push(this.zmax);
3086  } else {
3087  if ((this.zmin == this.zmax) && (this.zmin != 0)) {
3088  this.zmax += 0.01*Math.abs(this.zmax);
3089  this.zmin -= 0.01*Math.abs(this.zmin);
3090  }
3091  var dz = (this.zmax-this.zmin)/nlevels;
3092  for (var level=0; level<=nlevels; level++)
3093  this.fContour.push(this.zmin + dz*level);
3094  }
3095  }
3096 
3097  JSROOT.TH2Painter.prototype.getContourIndex = function(zc) {
3098  // return contour index, which corresponds to the z content value
3099 
3100  if (this.fContour == null) {
3101  // if not initialized, first create contour array
3102  // difference from ROOT - fContour includes also last element with maxbin, which makes easier to build logz
3103  var histo = this.GetObject();
3104 
3105  this.fUserContour = false;
3106  if ((histo.fContour!=null) && (histo.fContour.length>1) && histo.TestBit(JSROOT.TH1StatusBits.kUserContour)) {
3107  this.fContour = JSROOT.clone(histo.fContour);
3108  this.fUserContour = true;
3109  } else {
3110  var nlevels = 20, zmin = this.minbin, zmax = this.maxbin;
3111  if (histo.fContour != null) nlevels = histo.fContour.length;
3112  if (this.zoom_zmin != this.zoom_zmax) {
3113  zmin = this.zoom_zmin;
3114  zmax = this.zoom_zmax;
3115  }
3116  this.CreateContour(nlevels, zmin, zmax, this.minposbin);
3117  }
3118  }
3119 
3120  if (this.fUserContour || this.options.Logz) {
3121  var cntr = this.fContour, l = 0, r = this.fContour.length-1, mid;
3122  if (zc < cntr[0]) return -1;
3123  if (zc >= cntr[r]) return r;
3124  while (l < r-1) {
3125  mid = Math.round((l+r)/2);
3126  if (cntr[mid] > zc) r = mid; else l = mid;
3127  }
3128  return l;
3129  }
3130 
3131  return Math.floor(0.01+(zc-this.zmin)*(this.fContour.length-1)/(this.zmax-this.zmin));
3132  }
3133 
3134  JSROOT.TH2Painter.prototype.getValueColor = function(zc, asindx) {
3135  var index = this.getContourIndex(zc);
3136 
3137  if (index<0) {
3138  // do not draw bin where color is negative, only with col0 option minimal values are shown
3139  if (this.options.Color !== 111) return null;
3140  index = 0;
3141  }
3142 
3143  if (this.fPalette == null)
3144  this.fPalette = JSROOT.Painter.GetColorPalette(this.options.Palette);
3145 
3146  var theColor = Math.floor((index+0.99)*this.fPalette.length/(this.fContour.length-1));
3147  if (theColor > this.fPalette.length-1) theColor = this.fPalette.length-1;
3148  return asindx ? theColor : this.fPalette[theColor];
3149  }
3150 
3151  JSROOT.TH2Painter.prototype.CompressAxis = function(arr, maxlen, regular) {
3152  if (arr.length <= maxlen) return;
3153 
3154  // check filled bins
3155  var left = 0, right = arr.length-2; // last bin does not have count
3156  while ((left < right) && (arr[left].cnt===0)) ++left;
3157  while ((left < right) && (arr[right].cnt===0)) --right;
3158  if (right-left < maxlen) return;
3159 
3160  function RemoveNulls() {
3161  var j = right;
3162  while (j>=left) {
3163  while ((j>=left) && (arr[j]!==null)) --j;
3164  var j2 = j;
3165  while ((j>=0) && (arr[j]===null)) --j;
3166  if (j < j2) { arr.splice(j+1, j2-j); right -= (j2-j); }
3167  --j;
3168  }
3169  };
3170 
3171  if (!regular) {
3172  var grdist = Math.abs(arr[right+1].gr - arr[left].gr) / maxlen;
3173  var i = left;
3174  while (i <= right) {
3175  var gr0 = arr[i++].gr;
3176  // remove points which are not far away from current
3177  while ((i <= right) && (Math.abs(arr[i+1].gr - gr0) < grdist)) arr[i++] = null;
3178  }
3179  RemoveNulls();
3180  }
3181 
3182  if (regular || ((right-left) > 1.5*maxlen)) {
3183  // just remove regular number of bins
3184  var period = Math.floor((right-left) / maxlen);
3185  if (period<2) period = 2;
3186  var i = left;
3187  while (++i <= right) {
3188  for (var k=1;k<period;++k)
3189  if (++i <= right) arr[i] = null;
3190  }
3191  RemoveNulls();
3192  }
3193  }
3194 
3195  JSROOT.TH2Painter.prototype.CreateDrawBins = function(w, h) {
3196  // used only for lego plot now
3197  var histo = this.GetObject(),
3198  i1 = this.GetSelectIndex("x", "left", 0),
3199  i2 = this.GetSelectIndex("x", "right", 1),
3200  j1 = this.GetSelectIndex("y", "left", 0),
3201  j2 = this.GetSelectIndex("y", "right", 1),
3202  name = this.GetTipName("\n"),
3203  xx = [], yy = [], i, j, x, y,
3204  nbins = 0, binz = 0, sumz = 0;
3205 
3206  for (i = i1; i <= i2; ++i) {
3207  x = this.GetBinX(i);
3208  if (this.options.Logx && (x <= 0)) { i1 = i+1; continue; }
3209  xx.push({indx:i, axis: x, gr: this.grx(x), cnt:0});
3210  }
3211 
3212  for (j = j1; j <= j2; ++j) {
3213  y = this.GetBinY(j);
3214  if (this.options.Logy && (y <= 0)) { j1 = j+1; continue; }
3215  yy.push({indx:j, axis:y, gr:this.gry(y), cnt:0});
3216  }
3217 
3218  // first found min/max values in selected range, and number of non-zero bins
3219  this.maxbin = this.minbin = histo.getBinContent(i1 + 1, j1 + 1);
3220  for (i = i1; i < i2; ++i) {
3221  for (j = j1; j < j2; ++j) {
3222  binz = histo.getBinContent(i + 1, j + 1);
3223  if (binz != 0) nbins++;
3224  if (binz>this.maxbin) this.maxbin = binz; else
3225  if (binz<this.minbin) this.minbin = binz;
3226  }
3227  }
3228 
3229  if (((this.options.Optimize > 0) && (nbins>1000)) || (this.options.Optimize > 10)) {
3230  // if there are many non-empty points, check if all of them are selected
3231  // probably we do not need to optimize axis
3232 
3233  nbins = 0;
3234  for (i = i1; i < i2; ++i) {
3235  for (j = j1; j < j2; ++j) {
3236  binz = histo.getBinContent(i+1, j+1);
3237  if ((binz == 0) || (binz < this.minbin)) continue;
3238  nbins++;
3239  xx[i-i1].cnt+=1;
3240  yy[j-j1].cnt+=1;
3241  }
3242  }
3243  }
3244 
3245  if (((this.options.Optimize > 0) && (nbins>1000)) || (this.options.Optimize > 10)) {
3246  var numx = this.options.Optimize > 10 ? 10 : 40;
3247  var numy = numx;
3248 
3249  var coef = Math.abs(xx[0].gr - xx[xx.length-1].gr) / Math.abs(yy[0].gr - yy[yy.length-1].gr);
3250  if (coef > 1.) numy = Math.max(10, Math.round(numx / coef));
3251  else numx = Math.max(10, Math.round(numy * coef));
3252 
3253  if ((this.options.Optimize > 1) || (xx.length > 50))
3254  this.CompressAxis(xx, numx, !this.options.Logx && this.regularx);
3255 
3256  if ((this.options.Optimize > 1) || (yy.length > 50))
3257  this.CompressAxis(yy, numy, !this.options.Logy && this.regulary);
3258  }
3259 
3260  var local_bins = [];
3261 
3262  for (i = 0; i < xx.length-1; ++i) {
3263 
3264  for (j = 0; j < yy.length-1; ++j) {
3265 
3266  sumz = binz = histo.getBinContent(xx[i].indx + 1, yy[j].indx + 1);
3267 
3268  if ((xx[i+1].indx > xx[i].indx+1) || (yy[j+1].indx > yy[j].indx+1)) {
3269  sumz = 0;
3270  // check all other pixels inside range
3271  for (var i1 = xx[i].indx;i1 < xx[i+1].indx;++i1)
3272  for (var j1 = yy[j].indx;j1 < yy[j+1].indx;++j1) {
3273  var morez = histo.getBinContent(i1 + 1, j1 + 1);
3274  binz = Math.max(binz, morez);
3275  sumz += morez;
3276  }
3277  }
3278 
3279  if ((binz == 0) || (binz < this.minbin)) continue;
3280 
3281  var point = {
3282  x1: xx[i].axis,
3283  x2: xx[i+1].axis,
3284  y1: yy[j].axis,
3285  y2: yy[j+1].axis,
3286  z: binz
3287  };
3288 
3289  if (JSROOT.gStyle.Tooltip > 0) {
3290  if (this.x_kind == 'labels')
3291  point.tip = name + "x = " + this.AxisAsText("x", xx[i].axis) + "<br/>";
3292  else {
3293  point.tip = name + "x = [" + this.AxisAsText("x", xx[i].axis) + ", " + this.AxisAsText("x", xx[i+1].axis) + ")";
3294 
3295  if (xx[i].indx + 1 == xx[i+1].indx)
3296  point.tip += " bin=" + xx[i].indx + "<br/>";
3297  else
3298  point.tip += " bins=[" + xx[i].indx + "," + (xx[i+1].indx-1) + "]<br/>";
3299  }
3300  if (this.y_kind == 'labels')
3301  point.tip += "y = " + this.AxisAsText("y", yy[j].axis) + "<br/>";
3302  else {
3303  point.tip += "y = [" + this.AxisAsText("y", yy[j].axis) + ", " + this.AxisAsText("y", yy[j+1].axis) + ")";
3304  if (yy[j].indx + 1 == yy[j+1].indx)
3305  point.tip += " bin=" + yy[j].indx + "<br/>";
3306  else
3307  point.tip += " bins=[" + yy[j].indx + "," + (yy[j+1].indx-1) + "]<br/>";
3308  }
3309 
3310  if (sumz == binz)
3311  point.tip += "entries = " + JSROOT.FFormat(sumz, JSROOT.gStyle.StatFormat);
3312  else
3313  point.tip += "sum = " + JSROOT.FFormat(sumz, JSROOT.gStyle.StatFormat) +
3314  " max = " + JSROOT.FFormat(binz, JSROOT.gStyle.StatFormat);
3315  }
3316  local_bins.push(point);
3317  }
3318  }
3319 
3320  return local_bins;
3321  }
3322 
3323  JSROOT.TH2Painter.prototype.PrepareColorDraw = function(dorounding, pixel_density) {
3324  var histo = this.GetObject(), i, j, x, y, binz, binarea,
3325  res = {
3326  i1: this.GetSelectIndex("x", "left", 0),
3327  i2: this.GetSelectIndex("x", "right", 1),
3328  j1: this.GetSelectIndex("y", "left", 0),
3329  j2: this.GetSelectIndex("y", "right", 1),
3330  grx: [], gry: [], min: 0, max: 0
3331  };
3332 
3333  if (pixel_density) dorounding = true;
3334 
3335  // calculate graphical coordinates in advance
3336  for (i = res.i1; i <= res.i2; ++i) {
3337  x = this.GetBinX(i);
3338  if (this.options.Logx && (x <= 0)) { res.i1 = i+1; continue; }
3339  res.grx[i] = this.grx(x);
3340  if (dorounding) res.grx[i] = Math.round(res.grx[i]);
3341  }
3342 
3343  for (j = res.j1; j <= res.j2; ++j) {
3344  y = this.GetBinY(j);
3345  if (this.options.Logy && (y <= 0)) { res.j1 = j+1; continue; }
3346  res.gry[j] = this.gry(y);
3347  if (dorounding) res.gry[j] = Math.round(res.gry[j]);
3348  }
3349 
3350  // find min/max values in selected range
3351 
3352  binz = histo.getBinContent(res.i1 + 1, res.j1 + 1);
3353  this.maxbin = this.minbin = this.minposbin = null;
3354 
3355  for (i = res.i1; i < res.i2; ++i) {
3356  for (j = res.j1; j < res.j2; ++j) {
3357  binz = histo.getBinContent(i + 1, j + 1);
3358  if (pixel_density) {
3359  binarea = (res.grx[i+1]-res.grx[i])*(res.gry[j]-res.gry[j+1]);
3360  if (binarea <= 0) continue;
3361  res.max = Math.max(res.max, binz);
3362  if ((binz>0) && ((binz<res.min) || (res.min===0))) res.min = binz;
3363  binz = binz/binarea;
3364  }
3365  if (this.maxbin===null) {
3366  this.maxbin = this.minbin = binz;
3367  } else {
3368  this.maxbin = Math.max(this.maxbin, binz);
3369  this.minbin = Math.min(this.minbin, binz);
3370  }
3371  if (binz > 0)
3372  if ((this.minposbin===null) || (binz<this.minposbin)) this.minposbin = binz;
3373  }
3374  }
3375 
3376  this.fContour = null; // z-scale ranges when drawing with color
3377  this.fUserContour = false;
3378 
3379  return res;
3380  }
3381 
3382 /*
3383  JSROOT.TH2Painter.prototype.MakeIcon = function() {
3384  this.options.Optimize = 100;
3385 
3386  var w = this.frame_width(), h = this.frame_height();
3387 
3388  var bins = this.CreateDrawBins(w, h, 0, 0);
3389 
3390  var prev = { x : -1, y : -1, width: 0, height: 0, fill:"" };
3391  var res = "";
3392  for (var i=0;i<bins.length;++i) {
3393  var pnt = bins[i];
3394 
3395  pnt.x *= 512/w;
3396  pnt.width *= 512/w;
3397  pnt.y *= 512/h;
3398  pnt.height *= 512/h;
3399 
3400  if (res.length == 0) res = "["; else res+=",";
3401 
3402  var separ = "{";
3403  if (pnt.x != prev.x) { res += separ + "x:" + Math.round(pnt.x); separ =","; }
3404  if (pnt.y != prev.y) { res += separ + "y:" + Math.round(pnt.y); separ =","; }
3405  if (pnt.width != prev.width) { res += separ + "w:" + Math.round(pnt.width); separ =","; }
3406  if (pnt.height != prev.height) { res += separ + "h:" + Math.round(pnt.height); separ =","; }
3407  if (pnt.fill != prev.fill) { res += separ + "f:'" + pnt.fill + "'"; separ =","; }
3408  res += "}";
3409 
3410  prev = pnt;
3411  }
3412 
3413  res += "]";
3414  //console.log('len = ',res.length);
3415  //console.log(res);
3416  }
3417 */
3418 
3419  JSROOT.TH2Painter.prototype.DrawBinsColor = function(w,h) {
3420  var histo = this.GetObject(),
3421  handle = this.PrepareColorDraw(true),
3422  colPaths = [], currx = [], curry = [],
3423  colindx, cmd1, cmd2, i, j, binz;
3424 
3425  // now start build
3426  for (i = handle.i1; i < handle.i2; ++i) {
3427  for (j = handle.j1; j < handle.j2; ++j) {
3428  binz = histo.getBinContent(i + 1, j + 1);
3429  if ((binz == 0) || (binz < this.minbin)) continue;
3430 
3431  colindx = this.getValueColor(binz, true);
3432  if (colindx === null) continue;
3433 
3434  cmd1 = "M"+handle.grx[i]+","+handle.gry[j+1];
3435  if (colPaths[colindx] === undefined) {
3436  colPaths[colindx] = cmd1;
3437  } else{
3438  cmd2 = "m" + (handle.grx[i]-currx[colindx]) + "," + (handle.gry[j+1]-curry[colindx]);
3439  colPaths[colindx] += (cmd2.length < cmd1.length) ? cmd2 : cmd1;
3440  }
3441 
3442  currx[colindx] = handle.grx[i];
3443  curry[colindx] = handle.gry[j+1];
3444 
3445  colPaths[colindx] += "v" + (handle.gry[j] - handle.gry[j+1]) +
3446  "h" + (handle.grx[i+1] - handle.grx[i]) +
3447  "v" + (handle.gry[j+1] - handle.gry[j]) + "z";
3448  }
3449  }
3450 
3451  for (colindx=0;colindx<colPaths.length;++colindx)
3452  if (colPaths[colindx] !== undefined)
3453  this.draw_g
3454  .append("svg:path")
3455  .attr("palette-index", colindx)
3456  .attr("fill", this.fPalette[colindx])
3457  .attr("d", colPaths[colindx]);
3458 
3459  return handle;
3460  }
3461 
3462  JSROOT.TH2Painter.prototype.DrawBinsText = function(w, h, handle) {
3463  var histo = this.GetObject(),
3464  i,j,binz,colindx,binw,binh,lbl;
3465 
3466  if (handle===null) handle = this.PrepareColorDraw(false);
3467 
3468  var text_g = this.draw_g
3469  .append("svg:g")
3470  .attr("class","th2_text");
3471 
3472  this.StartTextDrawing(42, 20, text_g);
3473 
3474  for (i = handle.i1; i < handle.i2; ++i)
3475  for (j = handle.j1; j < handle.j2; ++j) {
3476  binz = histo.getBinContent(i + 1, j + 1);
3477  if ((binz == 0) || (binz < this.minbin)) continue;
3478 
3479  colindx = this.getValueColor(binz, true);
3480  if (colindx === null) continue;
3481 
3482  binw = handle.grx[i+1] - handle.grx[i];
3483  binh = handle.gry[j] - handle.gry[j+1];
3484  lbl = Math.round(binz);
3485 
3486  if (lbl === binz)
3487  lbl = binz.toString();
3488  else
3489  lbl = JSROOT.FFormat(binz, JSROOT.gStyle.StatFormat);
3490 
3491  this.DrawText(22, Math.round(handle.grx[i] + binw*0.1), Math.round(handle.gry[j+1] + binh*0.1),
3492  Math.round(binw*0.8), Math.round(binh*0.8),
3493  lbl, "black", 0, text_g);
3494  }
3495 
3496  this.FinishTextDrawing(text_g);
3497 
3498  return handle;
3499  }
3500 
3501  JSROOT.TH2Painter.prototype.DrawBinsBox = function(w,h) {
3502  var histo = this.GetObject(),
3503  handle = this.PrepareColorDraw(false),
3504  i, j, binz, colPaths = [], currx = [], curry = [],
3505  colindx, zdiff, dgrx, dgry, ww, hh, cmd1, cmd2;
3506 
3507  var xfactor = 1, yfactor = 1, uselogz = false, logmin = 0, logmax = 1;
3508  if (this.options.Logz && (this.maxbin>0)) {
3509  uselogz = true;
3510  logmax = Math.log(this.maxbin);
3511  logmin = (this.minbin > 0) ? Math.log(this.minbin) : logmax - 10;
3512  if (logmin >= logmax) logmin = logmax - 10;
3513  xfactor = 0.5 / (logmax - logmin);
3514  yfactor = 0.5 / (logmax - logmin);
3515  } else {
3516  xfactor = 0.5 / (this.maxbin - this.minbin);
3517  yfactor = 0.5 / (this.maxbin - this.minbin);
3518  }
3519 
3520  // now start build
3521  for (i = handle.i1; i < handle.i2; ++i) {
3522  for (j = handle.j1; j < handle.j2; ++j) {
3523  binz = histo.getBinContent(i + 1, j + 1);
3524  if ((binz == 0) || (binz < this.minbin)) continue;
3525 
3526  zdiff = uselogz ? (logmax - ((binz>0) ? Math.log(binz) : logmin)) : this.maxbin - binz;
3527 
3528  ww = handle.grx[i+1] - handle.grx[i];
3529  hh = handle.gry[j] - handle.gry[j+1];
3530 
3531  dgrx = zdiff * xfactor * ww;
3532  dgry = zdiff * yfactor * hh;
3533 
3534  ww = Math.max(Math.round(ww - 2*dgrx), 1);
3535  hh = Math.max(Math.round(hh - 2*dgry), 1);
3536 
3537  if (colPaths[i]===undefined) colPaths[i] = "";
3538  colPaths[i] += "M" + Math.round(handle.grx[i] + dgrx) + "," + Math.round(handle.gry[j+1] + dgry) +
3539  "v" + hh + "h" + ww + "v-" + hh + "z";
3540  }
3541  }
3542 
3543  for (i=0;i<colPaths.length;++i)
3544  if (colPaths[i] !== undefined)
3545  this.draw_g.append("svg:path")
3546  .attr("hist-column", i)
3547  .attr("d", colPaths[i])
3548  .call(this.lineatt.func)
3549  .call(this.fillatt.func);
3550 
3551  return handle;
3552  }
3553 
3554  JSROOT.TH2Painter.prototype.DrawBinsScatter = function(w,h) {
3555  var histo = this.GetObject(),
3556  handle = this.PrepareColorDraw(true, true),
3557  colPaths = [], currx = [], curry = [], cell_w = [], cell_h = [],
3558  colindx, cmd1, cmd2, i, j, binz, cw, ch, factor = 1.;
3559 
3560  // limit filling factor, do not try to produce as many points as filled area;
3561  if (this.maxbin > 0.7) factor = 0.7/this.maxbin;
3562 
3563  var nlevels = Math.round(handle.max - handle.min);
3564  this.CreateContour((nlevels > 50) ? 50 : nlevels, this.minposbin, this.maxbin, this.minposbin);
3565 
3566  // now start build
3567  for (i = handle.i1; i < handle.i2; ++i) {
3568  for (j = handle.j1; j < handle.j2; ++j) {
3569  binz = histo.getBinContent(i + 1, j + 1);
3570  if ((binz == 0) || (binz < this.minbin)) continue;
3571 
3572  cw = handle.grx[i+1] - handle.grx[i];
3573  ch = handle.gry[j] - handle.gry[j+1];
3574  if (cw*ch <= 0) continue;
3575 
3576  colindx = this.getContourIndex(binz/cw/ch);
3577  if (colindx < 0) continue;
3578 
3579  cmd1 = "M"+handle.grx[i]+","+handle.gry[j+1];
3580  if (colPaths[colindx] === undefined) {
3581  colPaths[colindx] = cmd1;
3582  cell_w[colindx] = cw;
3583  cell_h[colindx] = ch;
3584  } else{
3585  cmd2 = "m" + (handle.grx[i]-currx[colindx]) + "," + (handle.gry[j+1] - curry[colindx]);
3586  colPaths[colindx] += (cmd2.length < cmd1.length) ? cmd2 : cmd1;
3587  cell_w[colindx] = Math.max(cell_w[colindx], cw);
3588  cell_h[colindx] = Math.max(cell_h[colindx], ch);
3589  }
3590 
3591  currx[colindx] = handle.grx[i];
3592  curry[colindx] = handle.gry[j+1];
3593 
3594  colPaths[colindx] += "v"+ch+"h"+cw+"v-"+ch+"z";
3595  }
3596  }
3597 
3598  var layer = this.svg_frame().select('.main_layer');
3599  var defs = layer.select("defs");
3600  if (defs.empty() && (colPaths.length>0))
3601  defs = layer.insert("svg:defs",":first-child");
3602 
3603  if (!this.markeratt)
3604  this.markeratt = JSROOT.Painter.createAttMarker(histo);
3605 
3606  for (colindx=0;colindx<colPaths.length;++colindx)
3607  if ((colPaths[colindx] !== undefined) && (colindx<this.fContour.length)) {
3608  var pattern_class = "scatter_" + colindx;
3609  var pattern = defs.select('.'+pattern_class);
3610  if (pattern.empty())
3611  pattern = defs.append('svg:pattern')
3612  .attr("class", pattern_class)
3613  .attr("id", "jsroot_scatter_pattern_" + JSROOT.id_counter++)
3614  .attr("patternUnits","userSpaceOnUse");
3615  else
3616  pattern.selectAll("*").remove();
3617 
3618  var npix = Math.round(factor*this.fContour[colindx]*cell_w[colindx]*cell_h[colindx]);
3619  if (npix<1) npix = 1;
3620 
3621  var arrx = new Float32Array(npix), arry = new Float32Array(npix);
3622 
3623  if (npix===1) {
3624  arrx[0] = arry[0] = 0.5;
3625  } else {
3626  for (var n=0;n<npix;++n) {
3627  arrx[n] = Math.random();
3628  arry[n] = Math.random();
3629  }
3630  }
3631 
3632  // arrx.sort();
3633 
3634  this.markeratt.reset_pos();
3635 
3636  var path = "";
3637 
3638  for (var n=0;n<npix;++n)
3639  path += this.markeratt.create(arrx[n] * cell_w[colindx], arry[n] * cell_h[colindx]);
3640 
3641  pattern.attr("width", cell_w[colindx])
3642  .attr("height", cell_h[colindx])
3643  .append("svg:path")
3644  .attr("d",path)
3645  .call(this.markeratt.func);
3646 
3647  this.draw_g
3648  .append("svg:path")
3649  .attr("scatter-index", colindx)
3650  .attr("fill", 'url(#' + pattern.attr("id") + ')')
3651  .attr("d", colPaths[colindx]);
3652  }
3653 
3654  return handle;
3655  }
3656 
3657  JSROOT.TH2Painter.prototype.DrawBins = function() {
3658 
3659  this.RecreateDrawG(false, "main_layer");
3660 
3661  var w = this.frame_width(), h = this.frame_height();
3662 
3663  var handle = null;
3664 
3665  // if (this.lineatt.color == 'none') this.lineatt.color = 'cyan';
3666 
3667  if (this.options.Color + this.options.Box + this.options.Scat + this.options.Text == 0)
3668  this.options.Scat = 1;
3669 
3670  if (this.options.Color > 0)
3671  handle = this.DrawBinsColor(w, h);
3672  else
3673  if (this.options.Scat > 0)
3674  handle = this.DrawBinsScatter(w, h);
3675  else
3676  if (this.options.Box > 0)
3677  handle = this.DrawBinsBox(w, h);
3678 
3679  if (this.options.Text>0)
3680  handle = this.DrawBinsText(w, h, handle);
3681 
3682  this.tt_handle = handle;
3683  }
3684 
3685  JSROOT.TH2Painter.prototype.GetBinTips = function (i, j) {
3686  var lines = [];
3687 
3688  lines.push(this.GetTipName());
3689 
3690  if (this.x_kind == 'labels')
3691  lines.push("x = " + this.AxisAsText("x", this.GetBinX(i)));
3692  else
3693  lines.push("x = [" + this.AxisAsText("x", this.GetBinX(i)) + ", " + this.AxisAsText("x", this.GetBinX(i+1)) + ")");
3694 
3695  if (this.y_kind == 'labels')
3696  lines.push("y = " + this.AxisAsText("y", this.GetBinY(j)));
3697  else
3698  lines.push("y = [" + this.AxisAsText("y", this.GetBinY(j)) + ", " + this.AxisAsText("y", this.GetBinY(j+1)) + ")");
3699 
3700  lines.push("bin = " + i + ", " + j);
3701 
3702  var binz = this.GetObject().getBinContent(i+1,j+1);
3703  if (binz === Math.round(binz))
3704  lines.push("entries = " + binz);
3705  else
3706  lines.push("entries = " + JSROOT.FFormat(binz, JSROOT.gStyle.StatFormat));
3707 
3708  return lines;
3709  }
3710 
3711  JSROOT.TH2Painter.prototype.ProcessTooltip = function(pnt) {
3712  if (pnt==null) {
3713  if (this.draw_g !== null)
3714  this.draw_g.select(".tooltip_bin").remove();
3715  this.ProvideUserTooltip(null);
3716  return null;
3717  }
3718 
3719  var histo = this.GetObject(),
3720  h = this.tt_handle,
3721  i, j, find = 0;
3722 
3723  // search bin position
3724  for (i = h.i1; i < h.i2; ++i)
3725  if ((pnt.x>=h.grx[i]) && (pnt.x<=h.grx[i+1])) { ++find; break; }
3726 
3727  for (j = h.j1; j <= h.j2; ++j)
3728  if ((pnt.y>=h.gry[j+1]) && (pnt.y<=h.gry[j])) { ++find; break; }
3729 
3730  var ttrect = this.draw_g.select(".tooltip_bin");
3731 
3732  var binz = (find === 2) ? histo.getBinContent(i+1,j+1) : -100;
3733 
3734  // console.log('find = ' + find + ' binz = ' + binz + ' minbin ' + this.minbin);
3735 
3736  if ((find !== 2) || (binz === 0) || (binz < this.minbin)) {
3737  ttrect.remove();
3738  this.ProvideUserTooltip(null);
3739  return null;
3740  }
3741 
3742  var res = { x: pnt.x, y: pnt.y,
3743  color1: this.lineatt.color, color2: this.fillatt.color,
3744  lines: this.GetBinTips(i, j), exact: true, menu: true };
3745 
3746  if (this.options.Color > 0) res.color2 = this.getValueColor(binz);
3747 
3748  if (ttrect.empty())
3749  ttrect = this.draw_g.append("svg:rect")
3750  .attr("class","tooltip_bin h1bin")
3751  .style("pointer-events","none");
3752 
3753  res.changed = ttrect.property("current_bin") !== i*10000 + j;
3754 
3755  if (res.changed)
3756  ttrect.attr("x", h.grx[i])
3757  .attr("width", h.grx[i+1] - h.grx[i])
3758  .attr("y", h.gry[j+1])
3759  .attr("height", h.gry[j] - h.gry[j+1])
3760  .style("opacity", "0.7")
3761  .property("current_bin", i*10000 + j);
3762 
3763  if (this.IsUserTooltipCallback() && res.changed) {
3764  this.ProvideUserTooltip({ obj: histo, name: histo.fName,
3765  bin: histo.getBin(i+1, j+1), cont: binz, binx: i+1, biny: j+1,
3766  grx: pnt.x, gry: pnt.y });
3767  }
3768 
3769  return res;
3770  }
3771 
3772  JSROOT.TH2Painter.prototype.CanZoomIn = function(axis,min,max) {
3773  // check if it makes sense to zoom inside specified axis range
3774  if ((axis=="x") && (this.GetIndexX(max,0.5) - this.GetIndexX(min,0) > 1)) return true;
3775 
3776  if ((axis=="y") && (this.GetIndexY(max,0.5) - this.GetIndexY(min,0) > 1)) return true;
3777 
3778  if (axis=="z") return true;
3779 
3780  return false;
3781  }
3782 
3783  JSROOT.TH2Painter.prototype.Draw2D = function(call_back) {
3784 
3785  if (typeof this['Create3DScene'] == 'function')
3786  this.Create3DScene(-1);
3787 
3788  this.DrawAxes();
3789 
3790  this.DrawGrids();
3791 
3792  this.DrawBins();
3793 
3794  this.DrawTitle();
3795 
3796  JSROOT.CallBack(call_back);
3797  }
3798 
3799  JSROOT.TH2Painter.prototype.CheckResize = function(size) {
3800  // no painter - no resize
3801  var pad_painter = this.pad_painter();
3802  var changed = true, force = (this.options.Lego > 0) && !JSROOT.browser.isFirefox;
3803  if (pad_painter)
3804  changed = pad_painter.CheckCanvasResize(size, force);
3805  if (changed && (this.options.Lego > 0) && (typeof this['Resize3D'] == 'function'))
3806  this.Resize3D();
3807  return changed;
3808  }
3809 
3810 
3811  JSROOT.TH2Painter.prototype.Draw3D = function(call_back) {
3812  JSROOT.AssertPrerequisites('3d', function() {
3813  this['Create3DScene'] = JSROOT.Painter.HPainter_Create3DScene;
3814  this['Draw3DBins'] = JSROOT.Painter.TH2Painter_Draw3DBins;
3815  this['Draw3D'] = JSROOT.Painter.TH2Painter_Draw3D;
3816  this['Draw3D'](call_back);
3817  }.bind(this));
3818  }
3819 
3820  JSROOT.TH2Painter.prototype.Redraw = function() {
3821  this.CreateXY();
3822 
3823  var func_name = (this.options.Lego > 0) ? "Draw3D" : "Draw2D";
3824 
3825  this[func_name]();
3826  }
3827 
3828  JSROOT.Painter.drawHistogram2D = function(divid, histo, opt) {
3829  // create painter and add it to canvas
3830  JSROOT.extend(this, new JSROOT.TH2Painter(histo));
3831 
3832  this.SetDivId(divid, 1);
3833 
3834  // here we deciding how histogram will look like and how will be shown
3835  this.options = this.DecodeOptions(opt);
3836 
3837  this.CheckPadOptions();
3838 
3839  this.ScanContent();
3840 
3841  // check if we need to create palette
3842  if (this.create_canvas && (this.options.Zscale > 0))
3843  // draw new palette, resize frame if required
3844  this.DrawNewPalette(true);
3845 
3846  // create X/Y only when frame is adjusted, probably should be done differently
3847  this.CreateXY();
3848 
3849  // check if we need to create statbox
3850  if (JSROOT.gStyle.AutoStat && this.create_canvas)
3851  this.CreateStat();
3852 
3853  var func_name = this.options.Lego > 0 ? "Draw3D" : "Draw2D";
3854 
3855  this[func_name](function() {
3856  this.DrawNextFunction(0, function() {
3857  if (this.options.Lego == 0) {
3858  this.AddInteractive();
3859  if (this.options.AutoZoom) this.AutoZoom();
3860  }
3861  this.FillToolbar();
3862  this.DrawingReady();
3863  }.bind(this));
3864 
3865  }.bind(this));
3866 
3867  return this;
3868  }
3869 
3870  return JSROOT.Painter;
3871 
3872 }));