draw/TGraphPolarPainter.mjs

  1. import { settings, gStyle, create, BIT, clTPaveText, kTitle } from '../core.mjs';
  2. import { scaleLinear, pointer as d3_pointer } from '../d3.mjs';
  3. import { DrawOptions, buildSvgCurve, makeTranslate } from '../base/BasePainter.mjs';
  4. import { ObjectPainter, getElementMainPainter } from '../base/ObjectPainter.mjs';
  5. import { TPavePainter, kPosTitle } from '../hist/TPavePainter.mjs';
  6. import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs';
  7. import { TooltipHandler } from '../gpad/TFramePainter.mjs';
  8. import { assignContextMenu, kNoReorder } from '../gui/menu.mjs';
  9. const kNoTitle = BIT(17);
  10. /**
  11. * @summary Painter for TGraphPolargram objects.
  12. *
  13. * @private */
  14. class TGraphPolargramPainter extends TooltipHandler {
  15. /** @summary Create painter
  16. * @param {object|string} dom - DOM element for drawing or element id
  17. * @param {object} polargram - object to draw */
  18. constructor(dom, polargram, opt) {
  19. super(dom, polargram, opt);
  20. this.$polargram = true; // indicate that this is polargram
  21. this.zoom_rmin = this.zoom_rmax = 0;
  22. this.t0 = 0;
  23. this.mult = 1;
  24. this.decodeOptions(opt);
  25. this.setTooltipEnabled(true);
  26. }
  27. /** @summary Returns true if fixed coordinates are configured */
  28. isNormalAngles() {
  29. const polar = this.getObject();
  30. return polar?.fRadian || polar?.fGrad || polar?.fDegree;
  31. }
  32. /** @summary Decode draw options */
  33. decodeOptions(opt) {
  34. const d = new DrawOptions(opt);
  35. this.setOptions({
  36. rdot: d.check('RDOT'),
  37. rangle: d.check('RANGLE', true) ? d.partAsInt() : 0,
  38. NoLabels: d.check('N'),
  39. OrthoLabels: d.check('O')
  40. });
  41. this.storeDrawOpt(opt);
  42. }
  43. /** @summary Set angles range displayed by the polargram */
  44. setAnglesRange(tmin, tmax, set_obj) {
  45. if (tmin >= tmax)
  46. tmax = tmin + 1;
  47. if (set_obj) {
  48. const polar = this.getObject();
  49. polar.fRwtmin = tmin;
  50. polar.fRwtmax = tmax;
  51. }
  52. this.t0 = tmin;
  53. this.mult = 2*Math.PI/(tmax - tmin);
  54. }
  55. /** @summary Translate coordinates */
  56. translate(input_angle, radius, keep_float) {
  57. // recalculate angle
  58. const angle = (input_angle - this.t0) * this.mult;
  59. let rx = this.r(radius),
  60. ry = rx/this.szx*this.szy,
  61. grx = rx * Math.cos(-angle),
  62. gry = ry * Math.sin(-angle);
  63. if (!keep_float) {
  64. grx = Math.round(grx);
  65. gry = Math.round(gry);
  66. rx = Math.round(rx);
  67. ry = Math.round(ry);
  68. }
  69. return { grx, gry, rx, ry };
  70. }
  71. /** @summary format label for radius ticks */
  72. format(radius) {
  73. if (radius === Math.round(radius)) return radius.toString();
  74. if (this.ndig > 10) return radius.toExponential(4);
  75. return radius.toFixed((this.ndig > 0) ? this.ndig : 0);
  76. }
  77. /** @summary Convert axis values to text */
  78. axisAsText(axis, value) {
  79. if (axis === 'r') {
  80. if (value === Math.round(value))
  81. return value.toString();
  82. if (this.ndig > 10)
  83. return value.toExponential(4);
  84. return value.toFixed(this.ndig+2);
  85. }
  86. value *= 180/Math.PI;
  87. return (value === Math.round(value)) ? value.toString() : value.toFixed(1);
  88. }
  89. /** @summary Returns coordinate of frame - without using frame itself */
  90. getFrameRect() {
  91. const pp = this.getPadPainter(),
  92. pad = pp.getRootPad(true),
  93. w = pp.getPadWidth(),
  94. h = pp.getPadHeight(),
  95. rect = {};
  96. if (pad) {
  97. rect.szx = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fLeftMargin, pad.fRightMargin))*w);
  98. rect.szy = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fBottomMargin, pad.fTopMargin))*h);
  99. } else {
  100. rect.szx = Math.round(0.5*w);
  101. rect.szy = Math.round(0.5*h);
  102. }
  103. rect.width = 2 * rect.szx;
  104. rect.height = 2 * rect.szy;
  105. rect.x = Math.round(w / 2 - rect.szx);
  106. rect.y = Math.round(h / 2 - rect.szy);
  107. rect.hint_delta_x = rect.szx;
  108. rect.hint_delta_y = rect.szy;
  109. rect.transform = makeTranslate(rect.x, rect.y) || '';
  110. return rect;
  111. }
  112. /** @summary Process mouse event */
  113. mouseEvent(kind, evnt) {
  114. let pnt = null;
  115. if (kind !== 'leave') {
  116. const pos = d3_pointer(evnt, this.getG()?.node());
  117. pnt = { x: pos[0], y: pos[1], touch: false };
  118. }
  119. this.processFrameTooltipEvent(pnt);
  120. }
  121. /** @summary Process mouse wheel event */
  122. mouseWheel(evnt) {
  123. evnt.stopPropagation();
  124. evnt.preventDefault();
  125. this.processFrameTooltipEvent(null); // remove all tooltips
  126. const polar = this.getObject();
  127. if (!polar) return;
  128. let delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail);
  129. if (!delta) return;
  130. delta = (delta < 0) ? -0.2 : 0.2;
  131. let rmin = this.scale_rmin, rmax = this.scale_rmax;
  132. const range = rmax - rmin;
  133. // rmin -= delta*range;
  134. rmax += delta*range;
  135. if ((rmin < polar.fRwrmin) || (rmax > polar.fRwrmax))
  136. rmin = rmax = 0;
  137. if ((this.zoom_rmin !== rmin) || (this.zoom_rmax !== rmax)) {
  138. this.zoom_rmin = rmin;
  139. this.zoom_rmax = rmax;
  140. this.redrawPad();
  141. }
  142. }
  143. /** @summary Process mouse double click event */
  144. mouseDoubleClick() {
  145. if (this.zoom_rmin || this.zoom_rmax) {
  146. this.zoom_rmin = this.zoom_rmax = 0;
  147. this.redrawPad();
  148. }
  149. }
  150. /** @summary Draw polargram polar labels */
  151. async drawPolarLabels(polar, nmajor) {
  152. const fontsize = Math.round(polar.fPolarTextSize * this.szy * 2),
  153. o = this.getOptions();
  154. return this.startTextDrawingAsync(polar.fPolarLabelFont, fontsize)
  155. .then(() => {
  156. const lbls = (nmajor === 8) ? ['0', '#frac{#pi}{4}', '#frac{#pi}{2}', '#frac{3#pi}{4}', '#pi', '#frac{5#pi}{4}', '#frac{3#pi}{2}', '#frac{7#pi}{4}'] : ['0', '#frac{2#pi}{3}', '#frac{4#pi}{3}'],
  157. aligns = [12, 11, 21, 31, 32, 33, 23, 13];
  158. for (let n = 0; n < nmajor; ++n) {
  159. const angle = -n*2*Math.PI/nmajor;
  160. this.getG().append('svg:path')
  161. .attr('d', `M0,0L${Math.round(this.szx*Math.cos(angle))},${Math.round(this.szy*Math.sin(angle))}`)
  162. .call(this.lineatt.func);
  163. let align = 12, rotate = 0;
  164. if (o.OrthoLabels) {
  165. rotate = -n/nmajor*360;
  166. if ((rotate > -271) && (rotate < -91)) {
  167. align = 32; rotate += 180;
  168. }
  169. } else {
  170. const aindx = Math.round(16 - angle/Math.PI*4) % 8; // index in align table, here absolute angle is important
  171. align = aligns[aindx];
  172. }
  173. this.drawText({ align, rotate,
  174. x: Math.round((this.szx + fontsize)*Math.cos(angle)),
  175. y: Math.round((this.szy + fontsize/this.szx*this.szy)*(Math.sin(angle))),
  176. text: lbls[n],
  177. color: this.getColor(polar.fPolarLabelColor), latex: 1 });
  178. }
  179. return this.finishTextDrawing();
  180. });
  181. }
  182. /** @summary Redraw polargram */
  183. async redraw() {
  184. if (!this.isMainPainter())
  185. return;
  186. const polar = this.getObject(),
  187. o = this.getOptions(),
  188. rect = this.getPadPainter().getFrameRect(),
  189. g = this.createG();
  190. makeTranslate(g, Math.round(rect.x + rect.width/2), Math.round(rect.y + rect.height/2));
  191. this.szx = rect.szx;
  192. this.szy = rect.szy;
  193. this.scale_rmin = polar.fRwrmin;
  194. this.scale_rmax = polar.fRwrmax;
  195. if (this.zoom_rmin !== this.zoom_rmax) {
  196. this.scale_rmin = this.zoom_rmin;
  197. this.scale_rmax = this.zoom_rmax;
  198. }
  199. this.r = scaleLinear().domain([this.scale_rmin, this.scale_rmax]).range([0, this.szx]);
  200. if (polar.fRadian) {
  201. polar.fRwtmin = 0; polar.fRwtmax = 2*Math.PI;
  202. } else if (polar.fDegree) {
  203. polar.fRwtmin = 0; polar.fRwtmax = 360;
  204. } else if (polar.fGrad) {
  205. polar.fRwtmin = 0; polar.fRwtmax = 200;
  206. }
  207. this.setAnglesRange(polar.fRwtmin, polar.fRwtmax);
  208. const ticks = this.r.ticks(5);
  209. let nminor = Math.floor((polar.fNdivRad % 10000) / 100),
  210. nmajor = polar.fNdivPol % 100;
  211. if (nmajor !== 3)
  212. nmajor = 8;
  213. this.createAttLine({ attr: polar });
  214. if (!this.gridatt)
  215. this.gridatt = this.createAttLine({ color: polar.fLineColor, style: 2, width: 1, std: false });
  216. const range = Math.abs(polar.fRwrmax - polar.fRwrmin);
  217. this.ndig = (range <= 0) ? -3 : Math.round(Math.log10(ticks.length / range));
  218. // verify that all radius labels are unique
  219. let lbls = [], indx = 0;
  220. while (indx<ticks.length) {
  221. const lbl = this.format(ticks[indx]);
  222. if (lbls.indexOf(lbl) >= 0) {
  223. if (++this.ndig>10) break;
  224. lbls = []; indx = 0; continue;
  225. }
  226. lbls.push(lbl);
  227. indx++;
  228. }
  229. let exclude_last = false;
  230. const pointer_events = this.isBatchMode() ? null : 'visibleFill';
  231. if ((ticks.at(-1) < polar.fRwrmax) && (this.zoom_rmin === this.zoom_rmax)) {
  232. ticks.push(polar.fRwrmax);
  233. exclude_last = true;
  234. }
  235. return this.startTextDrawingAsync(polar.fRadialLabelFont, Math.round(polar.fRadialTextSize * this.szy * 2)).then(() => {
  236. const axis_angle = - (o.rangle || polar.fAxisAngle) / 180 * Math.PI,
  237. ca = Math.cos(axis_angle),
  238. sa = Math.sin(axis_angle);
  239. for (let n = 0; n < ticks.length; ++n) {
  240. let rx = this.r(ticks[n]),
  241. ry = rx / this.szx * this.szy;
  242. g.append('ellipse')
  243. .attr('cx', 0)
  244. .attr('cy', 0)
  245. .attr('rx', Math.round(rx))
  246. .attr('ry', Math.round(ry))
  247. .style('fill', 'none')
  248. .style('pointer-events', pointer_events)
  249. .call(this.lineatt.func);
  250. if ((n < ticks.length - 1) || !exclude_last) {
  251. const halign = ca > 0.7 ? 1 : (ca > 0 ? 3 : (ca > -0.7 ? 1 : 3)),
  252. valign = Math.abs(ca) < 0.7 ? 1 : 3;
  253. this.drawText({ align: 10 * halign + valign,
  254. x: Math.round(rx*ca),
  255. y: Math.round(ry*sa),
  256. text: this.format(ticks[n]),
  257. color: this.getColor(polar.fRadialLabelColor), latex: 0 });
  258. if (o.rdot) {
  259. g.append('ellipse')
  260. .attr('cx', Math.round(rx * ca))
  261. .attr('cy', Math.round(ry * sa))
  262. .attr('rx', 3)
  263. .attr('ry', 3)
  264. .style('fill', 'red');
  265. }
  266. }
  267. if ((nminor > 1) && ((n < ticks.length - 1) || !exclude_last)) {
  268. const dr = (ticks[1] - ticks[0]) / nminor;
  269. for (let nn = 1; nn < nminor; ++nn) {
  270. const gridr = ticks[n] + dr*nn;
  271. if (gridr > this.scale_rmax) break;
  272. rx = this.r(gridr);
  273. ry = rx / this.szx * this.szy;
  274. g.append('ellipse')
  275. .attr('cx', 0)
  276. .attr('cy', 0)
  277. .attr('rx', Math.round(rx))
  278. .attr('ry', Math.round(ry))
  279. .style('fill', 'none')
  280. .style('pointer-events', pointer_events)
  281. .call(this.gridatt.func);
  282. }
  283. }
  284. }
  285. if (ca < 0.999) {
  286. g.append('path')
  287. .attr('d', `M0,0L${Math.round(this.szx*ca)},${Math.round(this.szy*sa)}`)
  288. .style('pointer-events', pointer_events)
  289. .call(this.lineatt.func);
  290. }
  291. return this.finishTextDrawing();
  292. }).then(() => {
  293. return o.NoLabels ? true : this.drawPolarLabels(polar, nmajor);
  294. }).then(() => {
  295. nminor = Math.floor((polar.fNdivPol % 10000) / 100);
  296. if (nminor > 1) {
  297. for (let n = 0; n < nmajor * nminor; ++n) {
  298. if (n % nminor === 0) continue;
  299. const angle = -n*2*Math.PI/nmajor/nminor;
  300. g.append('svg:path')
  301. .attr('d', `M0,0L${Math.round(this.szx*Math.cos(angle))},${Math.round(this.szy*Math.sin(angle))}`)
  302. .call(this.gridatt.func);
  303. }
  304. }
  305. if (this.isBatchMode())
  306. return;
  307. assignContextMenu(this, kNoReorder);
  308. this.assignZoomHandler(g);
  309. });
  310. }
  311. /** @summary Fill TGraphPolargram context menu */
  312. fillContextMenuItems(menu) {
  313. const pp = this.getObject(), o = this.getOptions();
  314. menu.sub('Axis range');
  315. menu.addchk(pp.fRadian, 'Radian', flag => { pp.fRadian = flag; pp.fDegree = pp.fGrad = false; this.interactiveRedraw('pad', flag ? 'exec:SetToRadian()' : 'exec:SetTwoPi()'); }, 'Handle data angles as radian range 0..2*Pi');
  316. menu.addchk(pp.fDegree, 'Degree', flag => { pp.fDegree = flag; pp.fRadian = pp.fGrad = false; this.interactiveRedraw('pad', flag ? 'exec:SetToDegree()' : 'exec:SetTwoPi()'); }, 'Handle data angles as degree range 0..360');
  317. menu.addchk(pp.fGrad, 'Grad', flag => { pp.fGrad = flag; pp.fRadian = pp.fDegree = false; this.interactiveRedraw('pad', flag ? 'exec:SetToGrad()' : 'exec:SetTwoPi()'); }, 'Handle data angles as grad range 0..200');
  318. menu.endsub();
  319. menu.addSizeMenu('Axis angle', 0, 315, 45, o.rangle || pp.fAxisAngle, v => {
  320. o.rangle = pp.fAxisAngle = v;
  321. this.interactiveRedraw('pad', `exec:SetAxisAngle(${v})`);
  322. });
  323. }
  324. /** @summary Assign zoom handler to element
  325. * @private */
  326. assignZoomHandler(elem) {
  327. elem.on('mouseenter', evnt => this.mouseEvent('enter', evnt))
  328. .on('mousemove', evnt => this.mouseEvent('move', evnt))
  329. .on('mouseleave', evnt => this.mouseEvent('leave', evnt));
  330. if (settings.Zooming)
  331. elem.on('dblclick', evnt => this.mouseDoubleClick(evnt));
  332. if (settings.Zooming && settings.ZoomWheel)
  333. elem.on('wheel', evnt => this.mouseWheel(evnt));
  334. }
  335. /** @summary Draw TGraphPolargram */
  336. static async draw(dom, polargram, opt) {
  337. const main = getElementMainPainter(dom);
  338. if (main) {
  339. if (main.getObject() === polargram)
  340. return main;
  341. throw Error('Cannot superimpose TGraphPolargram with any other drawings');
  342. }
  343. const painter = new TGraphPolargramPainter(dom, polargram, opt);
  344. return ensureTCanvas(painter, false).then(() => {
  345. painter.setAsMainPainter();
  346. return painter.redraw();
  347. }).then(() => painter);
  348. }
  349. } // class TGraphPolargramPainter
  350. /**
  351. * @summary Painter for TGraphPolar objects.
  352. *
  353. * @private
  354. */
  355. class TGraphPolarPainter extends ObjectPainter {
  356. /** @summary Decode options for drawing TGraphPolar */
  357. decodeOptions(opt) {
  358. const d = new DrawOptions(opt || 'L'),
  359. rdot = d.check('RDOT'),
  360. rangle = d.check('RANGLE', true) ? d.partAsInt() : 0,
  361. o = this.setOptions({
  362. mark: d.check('P'),
  363. err: d.check('E'),
  364. fill: d.check('F'),
  365. line: d.check('L'),
  366. curve: d.check('C'),
  367. radian: d.check('R'),
  368. degree: d.check('D'),
  369. grad: d.check('G'),
  370. Axis: d.check('N') ? 'N' : ''
  371. }, opt);
  372. if (d.check('O'))
  373. o.Axis += 'O';
  374. if (rdot)
  375. o.Axis += '_rdot';
  376. if (rangle)
  377. o.Axis += `_rangle${rangle}`;
  378. this.storeDrawOpt(opt);
  379. }
  380. /** @summary Update TGraphPolar with polargram */
  381. updateObject(obj, opt) {
  382. if (!this.matchObjectType(obj))
  383. return false;
  384. if (opt && (opt !== this.getOptions().original))
  385. this.decodeOptions(opt);
  386. if (this._draw_axis && obj.fPolargram)
  387. this.getMainPainter().updateObject(obj.fPolargram);
  388. delete obj.fPolargram;
  389. // copy all properties but not polargram
  390. Object.assign(this.getObject(), obj);
  391. return true;
  392. }
  393. /** @summary Redraw TGraphPolar */
  394. redraw() {
  395. return this.drawGraphPolar().then(() => this.updateTitle());
  396. }
  397. /** @summary Drawing TGraphPolar */
  398. async drawGraphPolar() {
  399. const graph = this.getObject(),
  400. o = this.getOptions(),
  401. main = this.getMainPainter();
  402. if (!graph || !main?.$polargram)
  403. return;
  404. if (o.mark)
  405. this.createAttMarker({ attr: graph });
  406. if (o.err || o.line || o.curve)
  407. this.createAttLine({ attr: graph });
  408. if (o.fill)
  409. this.createAttFill({ attr: graph });
  410. const g = this.createG();
  411. if (this._draw_axis && !main.isNormalAngles()) {
  412. const has_err = graph.fEX?.length;
  413. let rwtmin = graph.fX[0],
  414. rwtmax = graph.fX[0];
  415. for (let n = 0; n < graph.fNpoints; ++n) {
  416. rwtmin = Math.min(rwtmin, graph.fX[n] - (has_err ? graph.fEX[n] : 0));
  417. rwtmax = Math.max(rwtmax, graph.fX[n] + (has_err ? graph.fEX[n] : 0));
  418. }
  419. rwtmax += (rwtmax - rwtmin) / graph.fNpoints;
  420. main.setAnglesRange(rwtmin, rwtmax, true);
  421. }
  422. g.attr('transform', main.getG().attr('transform'));
  423. let mpath = '', epath = '';
  424. const bins = [], pointer_events = this.isBatchMode() ? null : 'visibleFill';
  425. for (let n = 0; n < graph.fNpoints; ++n) {
  426. if (graph.fY[n] > main.scale_rmax)
  427. continue;
  428. if (o.err) {
  429. const p1 = main.translate(graph.fX[n], graph.fY[n] - graph.fEY[n]),
  430. p2 = main.translate(graph.fX[n], graph.fY[n] + graph.fEY[n]),
  431. p3 = main.translate(graph.fX[n] + graph.fEX[n], graph.fY[n]),
  432. p4 = main.translate(graph.fX[n] - graph.fEX[n], graph.fY[n]);
  433. epath += `M${p1.grx},${p1.gry}L${p2.grx},${p2.gry}` +
  434. `M${p3.grx},${p3.gry}A${p4.rx},${p4.ry},0,0,1,${p4.grx},${p4.gry}`;
  435. }
  436. const pos = main.translate(graph.fX[n], graph.fY[n]);
  437. if (o.mark)
  438. mpath += this.markeratt.create(pos.grx, pos.gry);
  439. if (o.curve || o.line || o.fill)
  440. bins.push(pos);
  441. }
  442. if ((o.fill || o.line) && bins.length) {
  443. const lpath = buildSvgCurve(bins, { line: true });
  444. if (o.fill) {
  445. g.append('svg:path')
  446. .attr('d', lpath + 'Z')
  447. .style('pointer-events', pointer_events)
  448. .call(this.fillatt.func);
  449. }
  450. if (o.line) {
  451. g.append('svg:path')
  452. .attr('d', lpath)
  453. .style('fill', 'none')
  454. .style('pointer-events', pointer_events)
  455. .call(this.lineatt.func);
  456. }
  457. }
  458. if (o.curve && bins.length) {
  459. g.append('svg:path')
  460. .attr('d', buildSvgCurve(bins))
  461. .style('fill', 'none')
  462. .style('pointer-events', pointer_events)
  463. .call(this.lineatt.func);
  464. }
  465. if (epath) {
  466. g.append('svg:path')
  467. .attr('d', epath)
  468. .style('fill', 'none')
  469. .style('pointer-events', pointer_events)
  470. .call(this.lineatt.func);
  471. }
  472. if (mpath) {
  473. g.append('svg:path')
  474. .attr('d', mpath)
  475. .style('pointer-events', pointer_events)
  476. .call(this.markeratt.func);
  477. }
  478. if (!this.isBatchMode()) {
  479. assignContextMenu(this, kNoReorder);
  480. main.assignZoomHandler(g);
  481. }
  482. }
  483. /** @summary Create polargram object */
  484. createPolargram(gr) {
  485. const o = this.getOptions();
  486. if (!gr.fPolargram) {
  487. gr.fPolargram = create('TGraphPolargram');
  488. if (o.radian)
  489. gr.fPolargram.fRadian = true;
  490. else if (o.degree)
  491. gr.fPolargram.fDegree = true;
  492. else if (o.grad)
  493. gr.fPolargram.fGrad = true;
  494. }
  495. let rmin = gr.fY[0] || 0, rmax = rmin;
  496. const has_err = gr.fEY?.length;
  497. for (let n = 0; n < gr.fNpoints; ++n) {
  498. rmin = Math.min(rmin, gr.fY[n] - (has_err ? gr.fEY[n] : 0));
  499. rmax = Math.max(rmax, gr.fY[n] + (has_err ? gr.fEY[n] : 0));
  500. }
  501. gr.fPolargram.fRwrmin = rmin - (rmax-rmin)*0.1;
  502. gr.fPolargram.fRwrmax = rmax + (rmax-rmin)*0.1;
  503. return gr.fPolargram;
  504. }
  505. /** @summary Provide tooltip at specified point */
  506. extractTooltip(pnt) {
  507. if (!pnt) return null;
  508. const graph = this.getObject(),
  509. main = this.getMainPainter();
  510. let best_dist2 = 1e10, bestindx = -1, bestpos = null;
  511. for (let n = 0; n < graph.fNpoints; ++n) {
  512. const pos = main.translate(graph.fX[n], graph.fY[n]),
  513. dist2 = (pos.grx - pnt.x)**2 + (pos.gry - pnt.y)**2;
  514. if (dist2 < best_dist2) {
  515. best_dist2 = dist2;
  516. bestindx = n;
  517. bestpos = pos;
  518. }
  519. }
  520. let match_distance = 5;
  521. if (this.markeratt?.used)
  522. match_distance = this.markeratt.getFullSize();
  523. if (Math.sqrt(best_dist2) > match_distance)
  524. return null;
  525. const res = {
  526. name: this.getObject().fName, title: this.getObject().fTitle,
  527. x: bestpos.grx, y: bestpos.gry,
  528. color1: (this.markeratt?.used ? this.markeratt.color : undefined) ?? (this.fillatt?.used ? this.fillatt.color : undefined) ?? this.lineatt?.color,
  529. exact: Math.sqrt(best_dist2) < 4,
  530. lines: [this.getObjectHint()],
  531. binindx: bestindx,
  532. menu_dist: match_distance,
  533. radius: match_distance
  534. };
  535. res.lines.push(`r = ${main.axisAsText('r', graph.fY[bestindx])}`,
  536. `phi = ${main.axisAsText('phi', graph.fX[bestindx])}`);
  537. if (graph.fEY && graph.fEY[bestindx])
  538. res.lines.push(`error r = ${main.axisAsText('r', graph.fEY[bestindx])}`);
  539. if (graph.fEX && graph.fEX[bestindx])
  540. res.lines.push(`error phi = ${main.axisAsText('phi', graph.fEX[bestindx])}`);
  541. return res;
  542. }
  543. /** @summary Only redraw histogram title
  544. * @return {Promise} with painter */
  545. async updateTitle() {
  546. // case when histogram drawn over other histogram (same option)
  547. if (!this._draw_axis)
  548. return this;
  549. const tpainter = this.getPadPainter()?.findPainterFor(null, kTitle, clTPaveText),
  550. pt = tpainter?.getObject();
  551. if (!tpainter || !pt)
  552. return this;
  553. const gr = this.getObject(),
  554. draw_title = !gr.TestBit(kNoTitle) && (gStyle.fOptTitle > 0);
  555. pt.Clear();
  556. if (draw_title) pt.AddText(gr.fTitle);
  557. return tpainter.redraw().then(() => this);
  558. }
  559. /** @summary Draw histogram title
  560. * @return {Promise} with painter */
  561. async drawTitle() {
  562. // case when histogram drawn over other histogram (same option)
  563. if (!this._draw_axis)
  564. return this;
  565. const gr = this.getObject(),
  566. st = gStyle,
  567. draw_title = !gr.TestBit(kNoTitle) && (st.fOptTitle > 0),
  568. pp = this.getPadPainter();
  569. let pt = pp.findInPrimitives(kTitle, clTPaveText);
  570. if (pt) {
  571. pt.Clear();
  572. if (draw_title)
  573. pt.AddText(gr.fTitle);
  574. return this;
  575. }
  576. pt = create(clTPaveText);
  577. Object.assign(pt, { fName: kTitle, fFillColor: st.fTitleColor, fFillStyle: st.fTitleStyle, fBorderSize: st.fTitleBorderSize,
  578. fTextFont: st.fTitleFont, fTextSize: st.fTitleFontSize, fTextColor: st.fTitleTextColor, fTextAlign: 22 });
  579. if (draw_title)
  580. pt.AddText(gr.fTitle);
  581. return TPavePainter.draw(pp, pt, kPosTitle)
  582. .then(p => { p?.setSecondaryId(this, kTitle); return this; });
  583. }
  584. /** @summary Show tooltip */
  585. showTooltip(hint) {
  586. let ttcircle = this.getG()?.selectChild('.tooltip_bin');
  587. if (!hint || !this.getG()) {
  588. ttcircle?.remove();
  589. return;
  590. }
  591. if (ttcircle.empty()) {
  592. ttcircle = this.getG().append('svg:ellipse')
  593. .attr('class', 'tooltip_bin')
  594. .style('pointer-events', 'none');
  595. }
  596. hint.changed = ttcircle.property('current_bin') !== hint.binindx;
  597. if (hint.changed) {
  598. ttcircle.attr('cx', hint.x)
  599. .attr('cy', hint.y)
  600. .attr('rx', Math.round(hint.radius))
  601. .attr('ry', Math.round(hint.radius))
  602. .style('fill', 'none')
  603. .style('stroke', hint.color1)
  604. .property('current_bin', hint.binindx);
  605. }
  606. }
  607. /** @summary Process tooltip event */
  608. processTooltipEvent(pnt) {
  609. const hint = this.extractTooltip(pnt);
  610. if (!pnt || !pnt.disabled)
  611. this.showTooltip(hint);
  612. return hint;
  613. }
  614. /** @summary Draw TGraphPolar */
  615. static async draw(dom, graph, opt) {
  616. const painter = new TGraphPolarPainter(dom, graph, opt);
  617. painter.decodeOptions(opt);
  618. const main = painter.getMainPainter();
  619. if (main && !main.$polargram) {
  620. console.error('Cannot superimpose TGraphPolar with plain histograms');
  621. return null;
  622. }
  623. let pr = Promise.resolve(null);
  624. if (!main) {
  625. // indicate that axis defined by this graph
  626. painter._draw_axis = true;
  627. pr = TGraphPolargramPainter.draw(dom, painter.createPolargram(graph), painter.options.Axis);
  628. }
  629. return pr.then(gram_painter => {
  630. gram_painter?.setSecondaryId(painter, 'polargram');
  631. painter.addToPadPrimitives();
  632. return painter.drawGraphPolar();
  633. }).then(() => painter.drawTitle());
  634. }
  635. } // class TGraphPolarPainter
  636. export { TGraphPolargramPainter, TGraphPolarPainter };