3JSROOT.define([], () => {
7 if (typeof GO4 != "object") {
8 let e1 = new Error("condition.js requires GO4 to be already loaded");
9 e1.source = "condition.js";
13 // =========================================================================================
15 class ConditionEditor extends JSROOT.BasePainter {
17 constructor(dom, cond) {
20 this.changes = ["dummy", "init"];
25 return ((this.cond._typename == "TGo4PolyCond") || (this.cond._typename == "TGo4ShapedCond"));
29 return (this.cond._typename == "TGo4ShapedCond");
32 /** @summary add identifier of changed element to list, make warning sign visible */
34 // first avoid duplicate keys:
35 for (let index = 0; index < this.changes.length; index++) {
36 if (this.changes[index]== key) return;
38 this.changes.push(key);
39 console.log("Mark changed :%s", key);
40 this.selectDom().select(".buttonChangeLabel").style("display", null);// show warning sign
43 /** @summary clear changed elements' ist, make warning sign invisible */
45 for (let index = 0; index < this.changes.length ; index++) {
46 let removed = this.changes.pop();
47 console.log("Clear changes removed :%s", removed);
49 this.selectDom().select(".buttonChangeLabel").style("display", "none"); // hide warning sign
52 /** @summary scan changed value list and return optionstring to be send to server */
53 evaluateChanges(optionstring) {
54 let dom = this.selectDom(),
55 len = this.changes.length;
56 for (let index = 0; index < len ; index++) {
57 //let cursor=changes.pop();
58 let key = this.changes[index];
59 //console.log("Evaluate change key:%s", key);
61 // here mapping of key to editor field:
64 let xmin = dom.select(".cond_xmin").property("value"),
65 xmax = dom.select(".cond_xmax").property("value");
66 optionstring += `&xmin=${xmin}&xmax=${xmax}`;
67 this.cond.fLow1 = xmin;
68 this.cond.fUp1 = xmax;
69 if (this.cond.fiDim == 2) {
70 let ymin = dom.select(".cond_ymin").property("value"),
71 ymax = dom.select(".cond_ymax").property("value");
72 this.cond.fLow2 = ymin;
73 this.cond.fUp2 = ymax;
74 optionstring += `&ymin=${ymin}&ymax=${ymax}`;
76 } else if (key == "polygon") {
77 let npoints = dom.select(".cut_points").property("value");
78 optionstring += "&npolygon="+npoints;
79 let values = dom.selectAll(".cut_values input").nodes();
80 // TODO: set display of polygon points
81 if (values.length != npoints*2)
82 return console.error('mismatch', values.length, npoints*2);
84 // always copy last point!
85 values[npoints*2-2].value = values[0].value;
86 values[npoints*2-1].value = values[1].value;
88 for(let i = 0; i < npoints; ++i) {
89 let x = values[i*2].value,
90 y = values[i*2+1].value;
91 optionstring += `&x${i}=${x}&y${i}=${y}`;
93 } else if (key == "ellinpts") {
94 let val = dom.select(".cond_ellipse_points").property("value");
95 optionstring += `&${key}=${val}`;
96 } else if (key == "ellicx") {
97 let val = dom.select(".cond_ellipse_cx").property("value");
98 optionstring += `&${key}=${val}`;
99 } else if (key == "ellicy") {
100 let val = dom.select(".cond_ellipse_cy").property("value");
101 optionstring += `&${key}=${val}`;
102 } else if (key == "ellia1") {
103 let val = dom.select(".cond_ellipse_a1").property("value");
104 optionstring += `&${key}=${val}`;
105 } else if (key == "ellia2") {
106 let val = dom.select(".cond_ellipse_a2").property("value");
107 optionstring += `&${key}=${val}`;
108 } else if (key == "ellishape") {
109 let val = dom.select(".cond_ellipse_iscircle").property("value");
110 optionstring += `&${key}=${val}`;
111 } else if (key == "ellith") {
112 let val = dom.select(".cond_ellipse_theta").property("value");
113 optionstring += `&${key}=${val}`;
114 } else if (key == "resultmode") {
115 let val = dom.select(".cond_execmode").property("value");
116 optionstring += `&${key}=${val}`;
117 } else if (key == "invertmode") {
118 let val = dom.select(".cond_invertmode").property("value");
119 optionstring += `&${key}=${val}`;
120 } else if (key == "visible") {
121 let arg = dom.select(".cond_visible").property("checked") ? 1 : 0;
122 optionstring += `&${key}=${arg}`;
123 } else if (key == "labeldraw") {
124 let arg = dom.select(".cond_label").property("checked") ? 1 : 0;
125 this.cond.fbLabelDraw = arg;
126 optionstring += `&${key}=${arg}`;
127 } else if (key == "limitsdraw"){
128 let arg = dom.select(".cond_limits").property("checked") ? 1 : 0;
129 this.cond.fbLimitsDraw = arg;
130 optionstring += `&${key}=${arg}`;
131 } else if (key == "intdraw") {
132 let arg = dom.select(".cond_integr").property("checked") ? 1 : 0;
133 this.cond.fbIntDraw = arg;
134 optionstring += `&${key}=${arg}`;
135 } else if (key == "xmeandraw") {
136 let arg = dom.select(".cond_xmean").property("checked") ? 1 : 0;
137 this.cond.fbXMeanDraw = arg;
138 optionstring += `&${key}=${arg}`;
139 } else if (key == "xrmsdraw") {
140 let arg = dom.select(".cond_xrms").property("checked") ? 1 : 0;
141 this.cond.fbXRMSDraw = arg;
142 optionstring += `&${key}=${arg}`;
143 } else if (key == "ymeandraw") {
144 let arg = dom.select(".cond_ymean").property("checked") ? 1 : 0;
145 this.cond.fbYMeanDraw = arg;
146 optionstring += `&${key}=${arg}`;
147 } else if (key == "yrmsdraw") {
148 let arg = dom.select(".cond_yrms").property("checked") ? 1 : 0;
149 this.cond.fbYRMSDraw = arg;
150 optionstring += `&${key}=${arg}`;
151 } else if (key == "xmaxdraw") {
152 let arg = dom.select(".cond_maxx").property("checked") ? 1 : 0;
153 this.cond.fbXMaxDraw = arg;
154 optionstring += `&${key}=${arg}`;
155 } else if (key=="ymaxdraw") {
156 let arg = dom.select(".cond_maxy").property("checked") ? 1 : 0;
157 this.cond.fbYMaxDraw = arg;
158 optionstring += `&${key}=${arg}`;
159 } else if (key=="cmaxdraw") {
160 let arg = dom.select(".cond_max").property("checked") ? 1 : 0;
161 this.cond.fbCMaxDraw = arg;
162 optionstring += `&${key}=${arg}`;
164 console.log(`Warning: evaluateChanges found unknown key: ${key}`);
167 console.log(`Resulting option string: ${optionstring}`);
173 /** @summary change dimension of polygon
174 * @desc this only changes display of condition, not condition itself!
175 * note that condition is still changed in analysis only by evaluateChanges
176 * local condition copy is unchanged until we can display it somewhere. */
177 changePolygonDimension() {
178 if(!this.isPolyCond()) return;
180 let dom = this.selectDom(),
181 oldpoints = this.cond.fxCut.fNpoints,
182 npoints = dom.select(".cut_points").property("value");
184 //if(npoints==oldpoints) return; // no dimension change, do nothing - disabled, error if we again go back to original condition dimension
185 if (this.cond.fxCut) {
186 let body = dom.select(".cut_values tbody").html(""); // clear old contents
187 if (npoints > oldpoints) {
188 // insert last but one point into table:
189 // first points are unchanged:
190 for (let i = 0; i < oldpoints - 1; i++) {
191 let x = this.cond.fxCut.fX[i], y = this.cond.fxCut.fY[i];
192 body.append("tr").html(`<td><input type="text" value="${x}"/></td><td><input type="text" value="${y}"/></td>`);
194 // inserted points will reproduce values of last but one point:
195 let insx = this.cond.fxCut.fX[oldpoints - 2],
196 insy = this.cond.fxCut.fY[oldpoints - 2];
197 for (let i = oldpoints - 1; i < npoints - 1; i++)
198 body.append("tr").html(`<td><input type="text" value="${insx}"/></td><td><input type="text" value="${insy}"/></td>`);
199 // final point is kept as last point of old polygon, should
200 // match first point for closed tcutg:
201 let lastx = this.cond.fxCut.fX[oldpoints - 1],
202 lasty = this.cond.fxCut.fY[oldpoints - 1];
203 body.append("tr").html(`<td><input type="text" value="${lastx}" disabled/></td><td><input type="text" value="${lasty}" disabled/></td>`);
205 // remove last but one point from table:
206 for (let i = 0; i < npoints - 1; i++) {
207 let x = this.cond.fxCut.fX[i], y = this.cond.fxCut.fY[i];
208 body.append("tr").html(`<td><input type="text" value="${x}"/></td><td><input type="text" value="${y}"/></td>`);
210 // final point is kept as last point of old polygon, should
211 // match first point for closed tcutg:
212 let lastx = this.cond.fxCut.fX[oldpoints - 1],
213 lasty = this.cond.fxCut.fY[oldpoints - 1];
214 body.append("tr").html(`<td><input type="text" value="${lastx}" disabled/></td><td><input type="text" value="${lasty}" disabled/> </td>`);
217 this.markChanged("polygon");
220 /** @summary enable/disable tab by index */
221 changeTab(action, indx) {
223 let dom = this.selectDom(),
224 btns = dom.select('.cond_tabs_header').selectAll("button").nodes(),
225 tabs = dom.selectAll('.tabs_body>div').nodes();
227 if (btns.length != tabs.length)
228 return console.error('mismatch in tabs sizes', btns.length, tabs.length);
230 d3.select(btns[indx]).attr('disabled', (action=="enable") ? null : "true");
232 // if that tab selected, find any other suitable
233 if ((action == "disable") && d3.select(btns[indx]).classed("active_btn")) {
235 btns.forEach((btn,k) => {
236 if (!d3.select(btn).attr('disabled') && (k != indx) && (best < 0)) best = k;
240 d3.select(tabs[indx]).style("display", "none");
241 d3.select(tabs[best]).style("display", null);
242 d3.select(btns[indx]).classed("active_btn", false);
243 d3.select(btns[best]).classed("active_btn", true);
248 /** @summary refresh condition editor */
252 dom = this.selectDom();
254 dom.select(".cond_name").text(cond.fName);
255 dom.select(".cond_type").text(cond._typename);
257 dom.select(".cond_execmode").node().value = cond.fbEnabled ? 0 : (cond.fbResult ? 1 : 2);
259 dom.select(".cond_invertmode").node().value = cond.fbTrue ? 0 : 1;
261 dom.select(".cond_xmin").property("value", cond.fLow1).on("change", () => this.markChanged("limits"));
262 dom.select(".cond_xmax").property("value", cond.fUp1).on("change", () => this.markChanged("limits"));
264 dom.select(".cond_ymin").property("value", cond.fLow2).on("change", () => this.markChanged("limits"));
265 dom.select(".cond_ymax").property("value", cond.fUp2).on("change", () => this.markChanged("limits"));
267 dom.select(".cond_ymin").attr('disabled', true);
268 dom.select(".cond_ymax").attr('disabled', true);
271 if(this.isPolyCond()) {
272 this.changeTab("enable", 1); // enable/disable by tab index
273 this.changeTab("disable", 0); // enable/disable by tab index
274 if (this.cond.fxCut) {
275 let numpoints = this.cond.fxCut.fNpoints;
276 dom.select(".cut_points").property("value", numpoints);
277 let body = dom.select(".cut_values tbody").html("").on("change", () => editor.markChanged("polygon"));
279 for(let i = 0; i < numpoints; i++) {
280 let x = this.cond.fxCut.fX[i];
281 let y = this.cond.fxCut.fY[i];
282 let row = body.append("tr").html(`<td><input type="text" value="${x}"/></td><td><input type="text" value="${y}"/> </td>`);
283 if ((i == numpoints-1) && (numpoints > 1)) row.selectAll("input").property("disabled", true); // disable last row
286 if(this.isEllipseCond()) {
287 this.changeTab( "enable", 2 ); // enable/disable by tab index
288 let numpoints = this.cond.fiResolution;
289 dom.select(".cond_ellipse_points").property("value", numpoints);
290 dom.select(".cond_ellipse_cx").property("value", cond.fdCenterX).on("change", () => this.markChanged("ellicx"));
291 dom.select(".cond_ellipse_cy").property("value", cond.fdCenterY).on("change", () => this.markChanged("ellicy"));
292 dom.select(".cond_ellipse_a1").property("value", cond.fdRadius1).on("change", () => this.markChanged("ellia1"));
293 dom.select(".cond_ellipse_a2").property("value", cond.fdRadius2).property('disabled', cond.fbIsCircle).on("change", () => this.markChanged("ellia2"));
294 dom.select(".cond_ellipse_theta").property("value", cond.fdTheta).property('disabled', cond.fbIsCircle).on("change", () => {
295 this.markChanged("ellith");
296 dom.select(".cond_ellipse_theta_slider").property("value", dom.select(".cond_ellipse_theta").property("value") % 360);
299 let options = dom.select(".cond_ellipse_iscircle").node().options;
300 for (let i = 0; i < options.length; i++)
301 options[i].selected = (options[i].value == cond.fiShapeType);
303 dom.select(".cond_ellipse_theta_slider")
304 .property("value",cond.fdTheta)
305 .attr("disbaled", cond.fbIsCircle)
306 .on("change", () => {
307 this.markChanged("ellith");
308 dom.select(".cond_ellipse_theta").property("value", dom.select(".cond_ellipse_theta_slider").property("value"));
313 this.changeTab( "enable", 0 );
314 this.changeTab( "disable", 1 ); // enable/disable by tab index
315 this.changeTab( "disable", 2 ); // enable/disable by tab index
318 dom.select(".cond_counts").text(cond.fiCounts);
319 dom.select(".cond_true").text(cond.fiTrueCounts);
320 dom.select(".cond_percent").text((cond.fiCounts > 0 ? 100. * cond.fiTrueCounts / cond.fiCounts : 0.).toFixed(2) + "%");
322 // todo: get keywords from condition class definition
323 // problem: static letiables are not streamed by default
325 dom.select(".cond_visible")
326 .property('checked', cond.fbVisible)
327 .on("click", function() { cond.fbVisible = this.checked; editor.markChanged("visible")});
329 dom.select(".cond_limits")
330 .property('checked', cond.fbLimitsDraw)
331 .on("click", function() { cond.fbLimitsDraw = this.checked; editor.markChanged("limitsdraw")});
333 dom.select(".cond_label")
334 .property('checked', cond.fbLabelDraw)
335 .on("click", function() { cond.fbLabelDraw = this.checked; editor.markChanged("labeldraw")});
337 dom.select(".cond_integr")
338 .property('checked', cond.fbIntDraw)
339 .on("click", function() { cond.fbIntDraw = this.checked; editor.markChanged("intdraw")});
341 dom.select(".cond_maxx")
342 .property('checked', cond.fbXMaxDraw)
343 .on("click", function() { cond.fbXMaxDraw = this.checked; editor.markChanged("xmaxdraw")});
345 dom.select(".cond_max")
346 .property('checked', cond.fbCMaxDraw)
347 .on("click", function() { cond.fbCMaxDraw = this.checked; editor.markChanged("cmaxdraw")});
349 dom.select(".cond_maxy")
350 .property('checked', cond.fbYMaxDraw)
351 .on("click", function() { cond.fbYMaxDraw = this.checked; editor.markChanged("ymaxdraw")});
353 dom.select(".cond_xmean")
354 .property('checked', cond.fbXMeanDraw)
355 .on("click", function() { cond.fbXMeanDraw = this.checked; editor.markChanged("xmeandraw")});
357 dom.select(".cond_xrms")
358 .property('checked', cond.fbXRMSDraw)
359 .on("click", function() { cond.fbXRMSDraw = this.checked; editor.markChanged("xrmsdraw")});
361 dom.select(".cond_ymean")
362 .property('checked', cond.fbYMeanDraw)
363 .on("click", function() { cond.fbYMeanDraw = this.checked; editor.markChanged("ymeandraw")});
365 dom.select(".cond_yrms")
366 .property('checked', cond.fbYRMSDraw)
367 .on("click", function() { cond.fbYRMSDraw = this.checked; editor.markChanged("yrmsdraw")});
369 editor.clearChanges();
372 /** @summary fill condition editor */
374 this.setTopPainter();
378 dom = this.selectDom();
380 // assign tabs buttons handlers
381 dom.select('.cond_tabs_header').selectAll("button").on("click", function() {
382 let btn = d3.select(this);
384 dom.select('.cond_tabs_header').selectAll("button").each(function() {
385 d3.select(this).classed("active_btn", false);
388 btn.classed("active_btn", true);
390 dom.selectAll('.tabs_body>div').each(function() {
391 let tab = d3.select(this);
392 tab.style('display', tab.attr('id') == btn.attr("for") ? null : "none");
396 // mark first tab as active
397 dom.select('.cond_tabs_header').select("button").classed("active_btn", true);
399 dom.select(".cond_execmode").on("change", () => this.markChanged("resultmode"));
401 dom.select(".cond_invertmode").on("change", () => this.markChanged("invertmode"));
403 if(this.isEllipseCond()) {
404 dom.select(".cond_ellipse_iscircle").on("change", () => {
405 cond.fiShapeType = parseInt(dom.select(".cond_ellipse_iscircle").property("value"));
407 switch(cond.fiShapeType) {
408 case 2: flags = [true, true, true, null]; break; // circle
409 case 3: flags = [null, null, null, null]; break; // ellipse
410 case 4: flags = [null, null, null, true]; break; // box
411 default: flags = [true, true, true, null]; // free style
413 dom.select(".cond_ellipse_a2").attr('disabled', flags[0]);
414 dom.select(".cond_ellipse_theta").attr('disabled', flags[1]);
415 dom.select(".cond_ellipse_theta_slider").attr('disabled', flags[2]);
416 dom.select(".cond_ellipse_points").attr('disabled', flags[3]);
417 this.markChanged("ellishape");
421 dom.select(".buttonGetCondition")
422 .style('background-image', "url(" + GO4.source_dir + "icons/right.png)")
424 if (JSROOT.hpainter) JSROOT.hpainter.display(this.getItemName());
425 else console.log("hierarhy painter object not found!");
428 dom.select(".buttonSetCondition")
429 .style('background-image', "url(" + GO4.source_dir + "icons/left.png)")
431 let options = this.evaluateChanges(""); // complete option string from all changed elements
432 console.log("set condition " + this.getItemName() + ", options="+options);
433 GO4.ExecuteMethod(this, "UpdateFromUrl",options)
435 console.log("set condition done.");
438 console.log("set condition FAILED.", err);
442 dom.select(".buttonChangeLabel")
443 .style('background-image', "url(" + GO4.source_dir + "icons/info1.png)");
445 dom.select(".buttonDrawCondition")
446 .style('background-image', "url(" + GO4.source_dir + "icons/chart.png)")
448 // TODO: implement correctly after MDI is improved, need to find out active frame and location of bound histogram
450 if (JSROOT.hpainter) {
451 this.evaluateChanges("");
453 if (JSROOT.hpainter.updateOnOtherFrames(this, this.cond)) return;
455 JSROOT.hpainter.drawOnSuitableHistogram(this, this.cond, editor.cond.fiDim==2);
460 let baseurl = editor.getItemName() + "/",
461 drawurl = baseurl + "draw.htm";
463 console.log("draw condition to next window with url="+drawurl);
464 //window.open(drawurl);
465 window.open(drawurl,'_blank');
469 dom.select(".buttonClearCondition")
470 .style('background-image', "url(" + GO4.source_dir + "icons/clear.png)")
472 GO4.ExecuteMethod(this, "UpdateFromUrl", "&resetcounters=1")
474 console.log("reset condition counters done.");
475 if(JSROOT.hpainter) JSROOT.hpainter.display(editor.getItemName());
477 console.log("reset condition counters FAILED.", err);
481 dom.select(".cut_points").on("change", () => this.changePolygonDimension());
483 dom.select(".cond_ellipse_points").on("change", () => this.markChanged("ellinpts"));
485 this.refreshEditor();
489 return JSROOT.httpRequest(GO4.source_dir + "html/condeditor.htm", "text").then(src => {
490 this.selectDom().html(src);
496 redrawObject(obj/*, opt */) {
497 if (obj._typename != this.cond._typename) return false;
498 this.cond = JSROOT.clone(obj); // does this also work with polygon condition?
499 this.refreshEditor();
504 GO4.ConditionEditor = ConditionEditor;