GSI Object Oriented Online Offline (Go4)  GO4-6.2.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
go4canvas.js
Go to the documentation of this file.
1 // $Id: go4canvas.js 3523 2022-01-27 10:13:19Z linev $
2 
3 "use strict";
4 
5 JSROOT.require("painter").then(jsrp => {
6  // cannot use JSROOT.define syntax here
7 
8  if (typeof GO4 == 'undefined')
9  globalThis.GO4 = { version: "6.1.4", web_canvas: true, id_counter: 1 };
10 
11  function findPainter(painter, obj, name, typ) {
12  let pp = painter.getPadPainter();
13  return pp ? pp.findPainterFor(obj, name, typ) : null;
14  }
15 
16  class MarkerPainter extends JSROOT.ObjectPainter{
17  constructor(dom, marker) {
18  super(dom, marker);
19  this.pave = null; // drawing of stat
20  }
21 
22  moveDrag(dx,dy) {
23  this.grx += dx;
24  this.gry += dy;
25  this.draw_g.select('path').attr("d",this.markeratt.create(this.grx, this.gry));
26  }
27 
28  moveEnd() {
29  let marker = this.getObject();
30  marker.fX = this.svgToAxis("x", this.grx);
31  marker.fY = this.svgToAxis("y", this.gry);
32  let exec = `SetXY(${marker.fX},${marker.fY})`;
33  this.submitCanvExec(exec);
34  this.drawLabel();
35  }
36 
37  drawMarker() {
38  let g = this.createG(); // can draw in complete pad
39 
40  let marker = this.getObject();
41 
42  this.createAttMarker({ attr: marker });
43 
44  this.grx = this.axisToSvg("x", marker.fX);
45  this.gry = this.axisToSvg("y", marker.fY);
46 
47  let path = this.markeratt.create(this.grx, this.gry);
48 
49  if (path)
50  g.append("svg:path")
51  .attr("d", path)
52  .call(this.markeratt.func);
53 
54  if (typeof this.AddMove == 'function')
55  this.AddMove();
56  else
57  JSROOT.require(['interactive'])
58  .then(inter => inter.addMoveHandler(this));
59  }
60 
61  fillLabels(marker) {
62  let lbls = [],
63  rect = this.getFramePainter().getFrameRect(),
64  main = this.getMainPainter(), hint = null;
65 
66  if (main && typeof main.processTooltipEvent == 'function')
67  hint = main.processTooltipEvent({ enabled: false, x: this.grx - rect.x, y: this.gry - rect.y });
68 
69  lbls.push(marker.fxName + ((hint && hint.name) ? (" : " + hint.name) : ""));
70 
71  if (marker.fbXDraw)
72  lbls.push("X = " + jsrp.floatToString(marker.fX, "6.4g"));
73 
74  if (marker.fbYDraw)
75  lbls.push("Y = " + jsrp.floatToString(marker.fY, "6.4g"));
76 
77  if (hint && hint.user_info) {
78  if (marker.fbXbinDraw) {
79  let bin = "<undef>";
80  if (hint.user_info.binx !== undefined) bin = hint.user_info.binx; else
81  if (hint.user_info.bin !== undefined) bin = hint.user_info.bin;
82  lbls.push("Xbin = " + bin);
83  }
84 
85  if (marker.fbYbinDraw) {
86  lbls.push("Ybin = " + ((hint.user_info.biny !== undefined) ? hint.user_info.biny : "<undef>"));
87  }
88 
89  if (marker.fbContDraw)
90  lbls.push("Cont = " + hint.user_info.cont);
91  }
92 
93  return lbls;
94  }
95 
96  drawLabel() {
97 
98  let marker = this.getObject();
99 
100  if (!marker.fbHasLabel) return;
101 
102  let pave_painter = findPainter(this, this.pave);
103 
104  if (!pave_painter) {
105  this.pave = JSROOT.create("TPaveStats");
106  this.pave.fName = "stats_" + marker.fName;
107 
108  let pp = this.getPadPainter(),
109  pad_width = pp.getPadWidth(),
110  pad_height = pp.getPadHeight();
111 
112  let px = this.grx / pad_width + 0.02,
113  py = this.gry / pad_height - 0.02;
114  JSROOT.extend(this.pave, { fX1NDC: px, fY1NDC: py - 0.15, fX2NDC: px + 0.2, fY2NDC: py, fBorderSize: 1, fFillColor: 0, fFillStyle: 1001 });
115 
116  let st = JSROOT.gStyle;
117  JSROOT.extend(this.pave, { fFillColor: st.fStatColor, fFillStyle: st.fStatStyle, fTextAngle: 0, fTextSize: st.fStatFontSize,
118  fTextAlign: 12, fTextColor: st.fStatTextColor, fTextFont: st.fStatFont });
119  } else {
120  this.pave.Clear();
121  }
122 
123  let lbls = this.fillLabels(marker);
124  for (let k = 0; k < lbls.length; ++k)
125  this.pave.AddText(lbls[k]);
126 
127  if (pave_painter)
128  pave_painter.redraw();
129  else
130  JSROOT.draw(this.divid, this.pave, "").then(p => { if (p) p.$secondary = true; });
131  }
132 
133  redrawObject(obj) {
134  if (!this.updateObject(obj)) return false;
135  this.redraw(); // no need to redraw complete pad
136  return true;
137  }
138 
139  cleanup(arg) {
140  if (this.pave) {
141  let pp = findPainter(this, this.pave);
142  if (pp) {
143  pp.removeFromPadPrimitives();
144  pp.cleanup();
145  }
146  delete this.pave;
147  }
148 
149  super.cleanup(arg);
150  }
151 
152  redraw() {
153  this.drawMarker();
154  this.drawLabel();
155  }
156 
157  // fillContextMenu(menu) {
158  // let marker = this.getObject();
159  // menu.add("header:"+ marker._typename + "::" + marker.fxName);
160  // function select(name,exec) {
161  // let marker = this.getObject();
162  // marker[name] = !marker[name];
163  // this.submitCanvExec(exec + (marker[name] ? '(true)' : '(false)'));
164  // this.redraw();
165  // }
166  // menu.addchk(marker.fbHasLabel, 'Label', select.bind(this, 'fbHasLabel', 'SetLabelDraw'));
167  // menu.addchk(marker.fbHasConnector, 'Connector', select.bind(this, 'fbHasConnector', 'SetLineDraw'));
168  // menu.addchk(marker.fbXDraw, 'Draw X', select.bind(this, 'fbXDraw', 'SetXDraw'));
169  // menu.addchk(marker.fbYDraw, 'Draw Y', select.bind(this, 'fbYDraw', 'SetYDraw'));
170  // menu.addchk(marker.fbXbinDraw, 'Draw X bin', select.bind(this, 'fbXbinDraw', 'SetXbinDraw'));
171  // menu.addchk(marker.fbYbinDraw, 'Draw Y bin', select.bind(this, 'fbYbinDraw', 'SetYbinDraw'));
172  // menu.addchk(marker.fbContDraw, 'Draw content', select.bind(this, 'fbContDraw', 'SetContDraw'));
173  // return true;
174  // }
175 
176  processTooltipEvent(pnt) {
177  if (!pnt) return null;
178 
179  let marker = this.getObject(),
180  rect = this.getFramePainter().getFrameRect(),
181  fx = rect.x,
182  fy = rect.y,
183  marker_sz = this.markeratt.getFullSize();
184 
185  let hint = { name: marker.fxName,
186  title: marker.fxName,
187  painter: this,
188  menu: true,
189  x: this.grx - fx,
190  y: this.gry - fy,
191  color1: this.markeratt.color };
192 
193  let dist = Math.sqrt(Math.pow(pnt.x - hint.x, 2) + Math.pow(pnt.y - hint.y, 2));
194 
195  hint.menu_dist = dist;
196 
197  if (dist < 2.5 * marker_sz) hint.exact = true;
198 
199  if (hint.exact)
200  hint.lines = this.fillLabels(marker);
201 
202  // res.menu = res.exact; // activate menu only when exactly locate bin
203  // res.menu_dist = 3; // distance always fixed
204 
205  return hint;
206  }
207 
208  ShowTooltip(hint) {}
209  }
210 
211  GO4.drawGo4Marker = function(dom, obj /*, option */) {
212  let painter = new MarkerPainter(dom, obj);
213  painter.drawMarker();
214  painter.drawLabel();
215  painter.addToPadPrimitives();
216  return Promise.resolve(painter);
217  }
218 
219  // =========================================================================
220 
221  class ConditionPainter extends JSROOT.ObjectPainter {
222  constructor(dom, cond) {
223  super(dom, cond);
224  this.pave = null; // drawing of stat
225  }
226 
227  Test(x,y) {
228  // JAM: need to put this here, since condition object will lose internal definition after cloning it again!
229  let cond = this.getObject();
230  if (!cond.fbEnabled)
231  return cond.fbResult;
232 
233  if (cond.fxCut)
234  return cond.fxCut.IsInside(x,y) ? cond.fbTrue : cond.fbFalse;
235 
236  if ((x < cond.fLow1) || (x > cond.fUp1)) return cond.fbFalse;
237 
238  if ((cond.fiDim==2) && ((y < cond.fLow2) || (y > cond.fUp2))) return cond.fbFalse;
239 
240  return cond.fbTrue;
241  }
242 
243 
244  isPolyCond() {
245  return this.matchObjectType("TGo4PolyCond") || this.matchObjectType("TGo4ShapedCond");
246  }
247 
248  isEllipseCond() {
249  return this.matchObjectType("TGo4ShapedCond");
250  }
251 
252  afterCutDraw(p) {
253  if (!p || !this.snapid || p._oldexec) return;
254  p.snapid = this.snapid + "#member_fxCut";
255  p._condpainter = this;
256 
257  // catch TCutG exec and mark condition as modified
258  p._oldexec = p.submitCanvExec;
259  p.submitCanvExec = function(exec, arg) {
260  this._oldexec(exec, arg);
261  p._condpainter.submitCanvExec("SetChanged()");
262  }
263  }
264 
265  drawCondition(interactive) {
266 
267  let cond = this.getObject();
268 
269  if (!cond || !cond.fbVisible) return;
270 
271  if (this.isPolyCond()) {
272  if (cond.fxCut) {
273  // look here if cut is already drawn in divid:
274  let cutpaint = findPainter(this, null, cond.fName, 'TCutG');
275 
276  if (cutpaint) {
277  if (cutpaint.updateObject(cond.fxCut)) cutpaint.redraw();
278  this.afterCutDraw(cutpaint);
279  } else {
280  cond.fxCut.fFillStyle = 3006;
281  cond.fxCut.fFillColor = 2;
282  JSROOT.draw(this.divid, cond.fxCut, "LF").then(p => this.afterCutDraw(p));
283  }
284 
285  }
286  return;
287  }
288 
289 
290  let g = this.createG(true); // drawing performed inside frame
291 
292  if ((cond.fFillStyle==1001) && (cond.fFillColor==19)) {
293  cond.fFillStyle = 3006;
294  cond.fFillColor = 2;
295  }
296 
297  this.createAttFill({attr: cond});
298  this.createAttLine({attr: cond});
299 
300  this.grx1 = this.axisToSvg("x", cond.fLow1);
301  this.grx2 = this.axisToSvg("x", cond.fUp1);
302 
303  if (cond.fiDim == 2) {
304  this.gry1 = this.axisToSvg("y", cond.fUp2);
305  this.gry2 = this.axisToSvg("y", cond.fLow2);
306  this.candy = true;
307  } else {
308  this.gry1 = 0;
309  this.gry2 = this.getFramePainter().getFrameHeight();
310  this.candy = false;
311  }
312 
313  g.append("svg:rect")
314  .attr("x", this.grx1)
315  .attr("y", this.gry1)
316  .attr("width", this.grx2 - this.grx1)
317  .attr("height", this.gry2 - this.gry1)
318  .call(this.lineatt.func)
319  .call(this.fillatt.func);
320 
321  if (typeof this.AddMove == 'function')
322  this.AddMove();
323  else
324  JSROOT.require(['interactive'])
325  .then(inter => inter.addMoveHandler(this));
326  }
327 
328  moveStart(x,y) {
329  this.swapx = this.swapy = false;
330  this.dx1 = Math.abs(x-this.grx1) < 5;
331  this.dx2 = Math.abs(x-this.grx2) < 5;
332  this.dy1 = Math.abs(y-this.gry1) < 5;
333  this.dy2 = Math.abs(y-this.gry2) < 5;
334  if (!this.dx1 && !this.dx2 && !this.dy1 && !this.dy2)
335  this.dx1 = this.dx2 = this.dy1 = this.dy2 = true;
336  if (!this.candy) this.dy1 = this.dy2 = false;
337  }
338 
339  moveDrag(dx,dy) {
340  if (this.dx1) this.grx1 += dx;
341  if (this.dx2) this.grx2 += dx;
342  if (this.grx1 > this.grx2) {
343  this.swapx = true;
344  let tempx = this.grx1; this.grx1 = this.grx2; this.grx2 = tempx;
345  tempx = this.dx1; this.dx1 = this.dx2; this.dx2 = tempx;
346  }
347  if (this.dy1) this.gry1 += dy;
348  if (this.dy2) this.gry2 += dy;
349  if (this.gry1 > this.gry2) {
350  this.swapy = true;
351  let tempy = this.gry1; this.gry1 = this.gry2; this.gry2 = tempy;
352  tempy = this.dy1; this.dy1 = this.dy2; this.dy2 = tempy;
353  }
354  this.draw_g.select('rect').attr("x",this.grx1).attr("y", this.gry1)
355  .attr("width", this.grx2 - this.grx1).attr("height", this.gry2 - this.gry1);
356  }
357 
358  moveEnd() {
359  let cond = this.getObject(), exec = "";
360  if (this.dx1 || this.swapx) { cond.fLow1 = this.svgToAxis("x", this.grx1); exec += "SetXLow(" + cond.fLow1 + ");;"; }
361  if (this.dx2 || this.swapx) { cond.fUp1 = this.svgToAxis("x", this.grx2); exec += "SetXUp(" + cond.fUp1 + ");;"; }
362  if (this.dy2 || this.swapy) { cond.fLow2 = this.svgToAxis("y", this.gry2); exec += "SetYLow(" + cond.fLow2 + ");;"; }
363  if (this.dy1 || this.swapy) { cond.fUp2 = this.svgToAxis("y", this.gry1); exec += "SetYUp(" + cond.fUp2 + ");;"; }
364  if (exec) {
365  this.submitCanvExec(exec + "SetChanged()");
366  this.drawLabel();
367  }
368  }
369 
370  drawLabel() {
371 
372  let cond = this.getObject(), painter = this, stat = {};
373 
374  if (!cond.fbLabelDraw || !cond.fbVisible) return;
375 
376  let pave_painter = findPainter(this, this.pave);
377 
378  if (!pave_painter) {
379  this.pave = JSROOT.create("TPaveStats");
380  this.pave.fName = "stats_" + cond.fName;
381  JSROOT.extend(this.pave, { fX1NDC: 0.1, fY1NDC: 0.4, fX2NDC: 0.4, fY2NDC: 0.65, fBorderSize: 1, fFillColor: 0, fFillStyle: 1001 });
382 
383  let st = JSROOT.gStyle;
384  JSROOT.extend(this.pave, { fFillColor: st.fStatColor, fFillStyle: st.fStatStyle, fTextAngle: 0, fTextSize: st.fStatFontSize,
385  fTextAlign: 12, fTextColor: st.fStatTextColor, fTextFont: st.fStatFont});
386  } else {
387  this.pave.Clear();
388  }
389 
390  this.pave.AddText(cond.fName);
391 
392  this.pave.AddText("Counts = " + cond.fiCounts);
393 
394  if (cond.fbLimitsDraw)
395  if (this.isPolyCond()) {
396  let res = { xmin: 0, xmax: 0, ymin: 0, ymax: 0 };
397  if (cond.fxCut.fNpoints > 0) {
398  res.xmin = res.xmax = cond.fxCut.fX[0];
399  res.ymin = res.ymax = cond.fxCut.fY[0];
400  for (let i=1; i<cond.fxCut.fNpoints; i++) {
401  res.xmin = Math.min(res.xmin, cond.fxCut.fX[i]);
402  res.xmax = Math.max(res.xmax, cond.fxCut.fX[i]);
403  res.ymin = Math.min(res.ymin, cond.fxCut.fY[i]);
404  res.ymax = Math.max(res.ymax, cond.fxCut.fY[i]);
405  }
406  }
407  this.pave.AddText("Xmin = " + res.xmin);
408  this.pave.AddText("Xmax = " + res.xmax);
409  this.pave.AddText("Ymin = " + res.ymin);
410  this.pave.AddText("Ymax = " + res.ymax);
411  } else {
412  this.pave.AddText("Xmin = " + cond.fLow1);
413  this.pave.AddText("Xmax = " + cond.fUp1);
414  if (cond.fiDim==2) {
415  this.pave.AddText("Ymin = " + cond.fLow2);
416  this.pave.AddText("Ymax = " + cond.fUp2);
417  }
418  }
419 
420  stat = this.getMainPainter().countStat((x,y) => painter.Test(x,y));
421 
422  if (cond.fbIntDraw) this.pave.AddText("Integral = " + jsrp.floatToString(stat.integral, "14.7g"));
423 
424  if (cond.fbXMeanDraw) this.pave.AddText("Mean x = " + jsrp.floatToString(stat.meanx, "6.4g"));
425 
426  if (cond.fbXRMSDraw) this.pave.AddText("RMS x = " + jsrp.floatToString(stat.rmsx, "6.4g"));
427 
428  if (cond.fiDim==2) {
429  if (cond.fbYMeanDraw) this.pave.AddText("Mean y = " + jsrp.floatToString(stat.meany, "6.4g"));
430  if (cond.fbYRMSDraw) this.pave.AddText("RMS y = " + jsrp.floatToString(stat.rmsy, "6.4g"));
431  }
432 
433  if (cond.fbXMaxDraw) this.pave.AddText("X max = " + jsrp.floatToString(stat.xmax, "6.4g"));
434 
435  if (cond.fiDim==2)
436  if (cond.fbYMaxDraw) this.pave.AddText("Y max = " + jsrp.floatToString(stat.ymax, "6.4g"));
437  if (cond.fbCMaxDraw) this.pave.AddText("C max = " + jsrp.floatToString(stat.wmax, "14.7g"));
438 
439  if (!pave_painter)
440  JSROOT.draw(this.divid, this.pave, "");
441  else
442  pave_painter.redraw();
443  }
444 
445  // fillContextMenu(menu) {
446  // let cond = this.getObject();
447  // menu.add("header:"+ cond._typename + "::" + cond.fName);
448  // function select(name,exec) {
449  // let cond = this.getObject();
450  // cond[name] = !cond[name];
451  // this.submitCanvExec(exec + (cond[name] ? '(true)' : '(false)'));
452  // this.redraw();
453  // }
454  // menu.addchk(cond.fbLabelDraw, 'Label', select.bind(this, 'fbLabelDraw', 'SetLabelDraw'));
455  // menu.addchk(cond.fbLimitsDraw, 'Limits', select.bind(this, 'fbLimitsDraw', 'SetLimitsDraw'));
456  // menu.addchk(cond.fbIntDraw, 'Integral', select.bind(this, 'fbIntDraw', 'SetIntDraw'));
457  // menu.addchk(cond.fbXMeanDraw, 'X mean', select.bind(this, 'fbXMeanDraw', 'SetXMeanDraw'));
458  // menu.addchk(cond.fbXRMSDraw, 'X rms', select.bind(this, 'fbXRMSDraw', 'SetXRMSDraw'));
459  // menu.addchk(cond.fbXMaxDraw, 'X max', select.bind(this, 'fbXMaxDraw', 'SetXMaxDraw'));
460  // menu.addchk(cond.fbYMeanDraw, 'Y mean', select.bind(this, 'fbYMeanDraw', 'SetYMeanDraw'));
461  // menu.addchk(cond.fbYRMSDraw, 'Y rms', select.bind(this, 'fbYRMSDraw', 'SetYRMSDraw'));
462  // menu.addchk(cond.fbYMaxDraw, 'Y max', select.bind(this, 'fbYMaxDraw', 'SetYMaxDraw'));
463  // return true;
464  // }
465 
466  processTooltipEvent(pnt) {
467  if (!pnt) return null;
468 
469  let cond = this.getObject(),
470  hint = { name: cond.fName,
471  title: cond.fTitle,
472  painter: this,
473  menu: true,
474  x: pnt.x,
475  y: pnt.y };
476 
477  if (this.isPolyCond()) {
478 
479  } else {
480 
481  hint.color1 = this.fillatt.color;
482  hint.color2 = this.lineatt.color;
483 
484  hint.menu_dist = Math.sqrt(Math.pow(pnt.x - (this.grx1 + this.grx2)/2, 2) + Math.pow(pnt.y - (this.gry1 + this.gry2)/2, 2));
485  hint.exact = (this.grx1 <= pnt.x) && (pnt.x <= this.grx2) && (this.gry1 <= pnt.y) && (pnt.y <= this.gry2);
486  if (Math.abs(this.grx1 - pnt.x) < 5) hint.sidex = -1;
487  if (Math.abs(this.grx2 - pnt.x) < 5) hint.sidex = 1;
488  if (cond.fiDim == 2) {
489  if (Math.abs(this.gry1 - pnt.y) < 5) hint.sidey = 1;
490  if (Math.abs(this.gry2 - pnt.y) < 5) hint.sidey = -1;
491  }
492  }
493 
494  if (hint.exact)
495  hint.lines = ["condition", cond.fName ];
496 
497  return hint;
498  }
499 
500  redrawObject(obj) {
501  if (!this.updateObject(obj)) return false;
502  this.redraw(); // no need to redraw complete pad
503  return true;
504  }
505 
506  cleanup(arg) {
507  if (this.pave) {
508  let pp = findPainter(this, this.pave);
509  if (pp) {
510  pp.removeFromPadPrimitives();
511  pp.cleanup();
512  }
513  delete this.pave;
514  }
515  super.cleanup(arg);
516  }
517 
518  redraw() {
519  this.drawCondition();
520  this.drawLabel();
521  }
522 
523  getDomId() {
524  let elem = this.selectDom();
525  if (elem.empty()) return "";
526  let id = elem.attr("id");
527  if (!id) {
528  id = "go4_element_" + GO4.id_counter++;
529  elem.attr("id", id);
530  }
531  return id;
532  }
533 
534  }
535 
536  GO4.drawGo4Cond = function(dom, cond, option) {
537 
538  if (!option) option = "";
539 
540  let condpainter = new ConditionPainter(dom, cond),
541  elem = condpainter.selectDom();
542 
543  if (GO4.web_canvas || (option.indexOf('same') >= 0) || condpainter.getMainPainter()) {
544  condpainter.drawCondition();
545  condpainter.drawLabel();
546  condpainter.addToPadPrimitives();
547  return Promise.resolve(condpainter);
548  }
549 
550  // from here normal code for plain THttpServer
551  if ((option=='editor') || !cond.fxHistoName) {
552  // failure, should never happens!
553  if (!GO4.source_dir) return null;
554 
555  let rect = elem.node().getBoundingClientRect();
556  if ((rect.height < 10) && (rect.width > 10)) elem.style("height", Math.round(rect.width*0.4) + "px");
557  return JSROOT.require(GO4.source_dir + 'html/condition.js').then(() => {
558  let editor = new GO4.ConditionEditor(dom, cond);
559  return editor.drawEditor();
560  });
561  }
562 
563  if (!JSROOT.hpainter) return;
564 
565  let histofullpath = null;
566 
567  JSROOT.hpainter.forEachItem(h => {
568  if ((h._name == cond.fxHistoName) && h._kind && (h._kind.indexOf("ROOT.TH")==0))
569  histofullpath = JSROOT.hpainter.itemFullName(h);
570  });
571 
572  if (histofullpath === null) {
573  histofullpath = "../../Histograms/" + cond.fxHistoName;
574 
575  let hitem = JSROOT.hpainter.findItem({ name: histofullpath, force: true });
576 
577  hitem._kind = "ROOT.TH1I";
578  }
579 
580  function drawCond(hpainter) {
581  if (!hpainter) return console.log("fail to draw histogram " + histofullpath);
582  condpainter.drawCondition();
583  condpainter.drawLabel();
584  condpainter.addToPadPrimitives();
585  return condpainter;
586  }
587 
588  return JSROOT.hpainter.display(histofullpath, "divid:" + condpainter.getDomId()).then(hp => drawCond(hp));
589  }
590 
591  GO4.drawCondArray = function(divid, obj, option) {
592  let arr = obj.condarr.arr,
593  num = obj.fiNumCond,
594  first;
595  for (let k = 0; k < num; ++k) {
596  let p = GO4.drawGo4Cond(divid, arr[k], "");
597  if (k == 0) first = p;
598  }
599  return first; // return first condition as result of drawing
600  }
601 
602  // =======================================================================
603 
604  if (GO4.web_canvas) {
605  jsrp.addDrawFunc({ name: "TGo4Marker", func: GO4.drawGo4Marker });
606  jsrp.addDrawFunc({ name: "TGo4WinCond", func: GO4.drawGo4Cond });
607  jsrp.addDrawFunc({ name: "TGo4PolyCond", func: GO4.drawGo4Cond });
608  jsrp.addDrawFunc({ name: "TGo4ShapedCond", func: GO4.drawGo4Cond });
609  jsrp.addDrawFunc({ name: "TGo4CondArray", func: GO4.drawCondArray });
610  }
611 
612 });
int main(int argc, char **argv)