draw/more.mjs

  1. import { BIT, isFunc, clTLatex, clTLink, clTMathText, clTAnnotation } from '../core.mjs';
  2. import { BasePainter, makeTranslate, DrawOptions } from '../base/BasePainter.mjs';
  3. import { addMoveHandler } from '../gui/utils.mjs';
  4. import { assignContextMenu } from '../gui/menu.mjs';
  5. /** @summary Draw TText
  6. * @private */
  7. async function drawText() {
  8. const text = this.getObject(),
  9. pp = this.getPadPainter(),
  10. fp = this.getFramePainter(),
  11. is_url = text.fName.startsWith('http://') || text.fName.startsWith('https://');
  12. let pos_x = text.fX, pos_y = text.fY, use_frame = false,
  13. fact = 1,
  14. annot = this.matchObjectType(clTAnnotation);
  15. this.createAttText({ attr: text });
  16. if (annot && fp?.mode3d && isFunc(fp?.convert3DtoPadNDC)) {
  17. const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ);
  18. pos_x = pos.x;
  19. pos_y = pos.y;
  20. this.isndc = true;
  21. annot = '3d';
  22. } else if (text.TestBit(BIT(14))) {
  23. // NDC coordinates
  24. this.isndc = true;
  25. } else if (pp.getRootPad(true)) {
  26. // force pad coordinates
  27. const d = new DrawOptions(this.getDrawOpt());
  28. use_frame = d.check('FRAME');
  29. } else {
  30. // place in the middle
  31. this.isndc = true;
  32. pos_x = pos_y = 0.5;
  33. text.fTextAlign = 22;
  34. }
  35. this.createG(use_frame ? 'frame2d' : undefined, is_url);
  36. this.draw_g.attr('transform', null); // remove transform from interactive changes
  37. this.pos_x = this.axisToSvg('x', pos_x, this.isndc);
  38. this.pos_y = this.axisToSvg('y', pos_y, this.isndc);
  39. this.swap_xy = use_frame && fp?.swap_xy;
  40. if (this.swap_xy)
  41. [this.pos_x, this.pos_y] = [this.pos_y, this.pos_x];
  42. const arg = this.textatt.createArg({ x: this.pos_x, y: this.pos_y, text: text.fTitle, latex: 0 });
  43. if ((text._typename === clTLatex) || annot)
  44. arg.latex = 1;
  45. else if (text._typename === clTMathText) {
  46. arg.latex = 2;
  47. fact = 0.8;
  48. }
  49. if (is_url) {
  50. this.draw_g.attr('href', text.fName);
  51. if (!this.isBatchMode())
  52. this.draw_g.append('svg:title').text(`link on ${text.fName}`);
  53. }
  54. return this.startTextDrawingAsync(this.textatt.font, this.textatt.getSize(pp, fact /* , 0.05 */))
  55. .then(() => this.drawText(arg))
  56. .then(() => this.finishTextDrawing())
  57. .then(() => {
  58. if (this.isBatchMode())
  59. return this;
  60. if (pp.isButton() && !pp.isEditable()) {
  61. this.draw_g.on('click', () => this.getCanvPainter().selectActivePad(pp));
  62. return this;
  63. }
  64. this.pos_dx = this.pos_dy = 0;
  65. if (!this.moveDrag) {
  66. this.moveDrag = function(dx, dy) {
  67. this.pos_dx += dx;
  68. this.pos_dy += dy;
  69. makeTranslate(this.draw_g, this.pos_dx, this.pos_dy);
  70. };
  71. }
  72. if (!this.moveEnd) {
  73. this.moveEnd = function(not_changed) {
  74. if (not_changed) return;
  75. const txt = this.getObject();
  76. let fx = this.svgToAxis('x', this.pos_x + this.pos_dx, this.isndc),
  77. fy = this.svgToAxis('y', this.pos_y + this.pos_dy, this.isndc);
  78. if (this.swap_xy)
  79. [fx, fy] = [fy, fx];
  80. txt.fX = fx;
  81. txt.fY = fy;
  82. this.submitCanvExec(`SetX(${fx});;SetY(${fy});;`);
  83. };
  84. }
  85. if (annot !== '3d')
  86. addMoveHandler(this, true, is_url);
  87. else {
  88. fp.processRender3D = true;
  89. this.handleRender3D = () => {
  90. const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ),
  91. new_x = this.axisToSvg('x', pos.x, true),
  92. new_y = this.axisToSvg('y', pos.y, true);
  93. makeTranslate(this.draw_g, new_x - this.pos_x, new_y - this.pos_y);
  94. };
  95. }
  96. assignContextMenu(this);
  97. this.fillContextMenuItems = function(menu) {
  98. menu.add('Change text', () => menu.input('Enter new text', text.fTitle).then(t => {
  99. text.fTitle = t;
  100. this.interactiveRedraw('pad', `exec:SetTitle("${t}")`);
  101. }));
  102. };
  103. if (this.matchObjectType(clTLink)) {
  104. this.draw_g.style('cursor', 'pointer')
  105. .on('click', () => this.submitCanvExec('ExecuteEvent(kButton1Up, 0, 0);;'));
  106. }
  107. return this;
  108. });
  109. }
  110. /** @summary Draw TEllipse
  111. * @private */
  112. function drawEllipse() {
  113. const ellipse = this.getObject(),
  114. closed_ellipse = (ellipse.fPhimin === 0) && (ellipse.fPhimax === 360),
  115. is_crown = (ellipse._typename === 'TCrown');
  116. this.createAttLine({ attr: ellipse });
  117. this.createAttFill({ attr: ellipse });
  118. this.createG();
  119. const funcs = this.getAxisToSvgFunc(),
  120. x = funcs.x(ellipse.fX1),
  121. y = funcs.y(ellipse.fY1),
  122. rx = is_crown && (ellipse.fR1 <= 0) ? (funcs.x(ellipse.fX1 + ellipse.fR2) - x) : (funcs.x(ellipse.fX1 + ellipse.fR1) - x),
  123. ry = y - funcs.y(ellipse.fY1 + ellipse.fR2),
  124. dr = Math.PI/180;
  125. let path = '';
  126. if (is_crown && (ellipse.fR1 > 0)) {
  127. const ratio = ellipse.fYXRatio ?? 1,
  128. rx1 = rx, ry2 = ratio * ry,
  129. ry1 = ratio * (y - funcs.y(ellipse.fY1 + ellipse.fR1)),
  130. rx2 = funcs.x(ellipse.fX1 + ellipse.fR2) - x;
  131. if (closed_ellipse) {
  132. path = `M${-rx1},0A${rx1},${ry1},0,1,0,${rx1},0A${rx1},${ry1},0,1,0,${-rx1},0` +
  133. `M${-rx2},0A${rx2},${ry2},0,1,0,${rx2},0A${rx2},${ry2},0,1,0,${-rx2},0`;
  134. } else {
  135. const large_arc = (ellipse.fPhimax-ellipse.fPhimin>=180) ? 1 : 0,
  136. a1 = ellipse.fPhimin*dr, a2 = ellipse.fPhimax*dr,
  137. dx1 = Math.round(rx1*Math.cos(a1)), dy1 = Math.round(ry1*Math.sin(a1)),
  138. dx2 = Math.round(rx1*Math.cos(a2)), dy2 = Math.round(ry1*Math.sin(a2)),
  139. dx3 = Math.round(rx2*Math.cos(a1)), dy3 = Math.round(ry2*Math.sin(a1)),
  140. dx4 = Math.round(rx2*Math.cos(a2)), dy4 = Math.round(ry2*Math.sin(a2));
  141. path = `M${dx2},${dy2}A${rx1},${ry1},0,${large_arc},0,${dx1},${dy1}` +
  142. `L${dx3},${dy3}A${rx2},${ry2},0,${large_arc},1,${dx4},${dy4}Z`;
  143. }
  144. } else if (ellipse.fTheta === 0) {
  145. if (closed_ellipse)
  146. path = `M${-rx},0A${rx},${ry},0,1,0,${rx},0A${rx},${ry},0,1,0,${-rx},0Z`;
  147. else {
  148. const x1 = Math.round(rx * Math.cos(ellipse.fPhimin*dr)),
  149. y1 = Math.round(ry * Math.sin(ellipse.fPhimin*dr)),
  150. x2 = Math.round(rx * Math.cos(ellipse.fPhimax*dr)),
  151. y2 = Math.round(ry * Math.sin(ellipse.fPhimax*dr));
  152. path = `M0,0L${x1},${y1}A${rx},${ry},0,1,1,${x2},${y2}Z`;
  153. }
  154. } else {
  155. const ct = Math.cos(ellipse.fTheta*dr),
  156. st = Math.sin(ellipse.fTheta*dr),
  157. phi1 = ellipse.fPhimin*dr,
  158. phi2 = ellipse.fPhimax*dr,
  159. np = 200,
  160. dphi = (phi2-phi1) / (np - (closed_ellipse ? 0 : 1));
  161. let lastx = 0, lasty = 0;
  162. if (!closed_ellipse) path = 'M0,0';
  163. for (let n = 0; n < np; ++n) {
  164. const angle = phi1 + n*dphi,
  165. dx = ellipse.fR1 * Math.cos(angle),
  166. dy = ellipse.fR2 * Math.sin(angle),
  167. px = funcs.x(ellipse.fX1 + dx*ct - dy*st) - x,
  168. py = funcs.y(ellipse.fY1 + dx*st + dy*ct) - y;
  169. if (!path)
  170. path = `M${px},${py}`;
  171. else if (lastx === px)
  172. path += `v${py-lasty}`;
  173. else if (lasty === py)
  174. path += `h${px-lastx}`;
  175. else
  176. path += `l${px-lastx},${py-lasty}`;
  177. lastx = px; lasty = py;
  178. }
  179. path += 'Z';
  180. }
  181. this.x = x;
  182. this.y = y;
  183. makeTranslate(this.draw_g.append('svg:path'), x, y)
  184. .attr('d', path)
  185. .call(this.lineatt.func)
  186. .call(this.fillatt.func);
  187. assignContextMenu(this);
  188. addMoveHandler(this);
  189. this.moveDrag = function(dx, dy) {
  190. this.x += dx;
  191. this.y += dy;
  192. makeTranslate(this.draw_g.select('path'), this.x, this.y);
  193. };
  194. this.moveEnd = function(not_changed) {
  195. if (not_changed) return;
  196. const ell = this.getObject();
  197. ell.fX1 = this.svgToAxis('x', this.x);
  198. ell.fY1 = this.svgToAxis('y', this.y);
  199. this.submitCanvExec(`SetX1(${ell.fX1});;SetY1(${ell.fY1});;Notify();;`);
  200. };
  201. }
  202. /** @summary Draw TPie
  203. * @private */
  204. function drawPie() {
  205. this.createG();
  206. const pie = this.getObject(),
  207. nb = pie.fPieSlices.length,
  208. xc = this.axisToSvg('x', pie.fX),
  209. yc = this.axisToSvg('y', pie.fY),
  210. rx = this.axisToSvg('x', pie.fX + pie.fRadius) - xc,
  211. ry = this.axisToSvg('y', pie.fY + pie.fRadius) - yc;
  212. makeTranslate(this.draw_g, xc, yc);
  213. // Draw the slices
  214. let total = 0,
  215. af = (pie.fAngularOffset*Math.PI)/180,
  216. x1 = Math.round(rx*Math.cos(af)),
  217. y1 = Math.round(ry*Math.sin(af));
  218. for (let n = 0; n < nb; n++)
  219. total += pie.fPieSlices[n].fValue;
  220. for (let n = 0; n < nb; n++) {
  221. const slice = pie.fPieSlices[n];
  222. this.createAttLine({ attr: slice });
  223. this.createAttFill({ attr: slice });
  224. af += slice.fValue/total*2*Math.PI;
  225. const x2 = Math.round(rx*Math.cos(af)),
  226. y2 = Math.round(ry*Math.sin(af));
  227. this.draw_g
  228. .append('svg:path')
  229. .attr('d', `M0,0L${x1},${y1}A${rx},${ry},0,0,0,${x2},${y2}z`)
  230. .call(this.lineatt.func)
  231. .call(this.fillatt.func);
  232. x1 = x2; y1 = y2;
  233. }
  234. }
  235. /** @summary Draw TMarker
  236. * @private */
  237. function drawMarker() {
  238. const marker = this.getObject(),
  239. kMarkerNDC = BIT(14);
  240. this.isndc = marker.TestBit(kMarkerNDC);
  241. const use_frame = this.isndc ? false : new DrawOptions(this.getDrawOpt()).check('FRAME'),
  242. swap_xy = use_frame && this.getFramePainter()?.swap_xy;
  243. this.createAttMarker({ attr: marker });
  244. this.createG(use_frame ? 'frame2d' : undefined);
  245. let x = this.axisToSvg('x', marker.fX, this.isndc),
  246. y = this.axisToSvg('y', marker.fY, this.isndc);
  247. if (swap_xy)
  248. [x, y] = [y, x];
  249. const path = this.markeratt.create(x, y);
  250. if (path) {
  251. this.draw_g.append('svg:path')
  252. .attr('d', path)
  253. .call(this.markeratt.func);
  254. }
  255. assignContextMenu(this);
  256. addMoveHandler(this);
  257. this.dx = this.dy = 0;
  258. this.moveDrag = function(dx, dy) {
  259. this.dx += dx;
  260. this.dy += dy;
  261. makeTranslate(this.draw_g.select('path'), this.dx, this.dy);
  262. };
  263. this.moveEnd = function(not_changed) {
  264. if (not_changed) return;
  265. const mrk = this.getObject();
  266. let fx = this.svgToAxis('x', this.axisToSvg('x', mrk.fX, this.isndc) + this.dx, this.isndc),
  267. fy = this.svgToAxis('y', this.axisToSvg('y', mrk.fY, this.isndc) + this.dy, this.isndc);
  268. if (swap_xy)
  269. [fx, fy] = [fy, fx];
  270. mrk.fX = fx;
  271. mrk.fY = fy;
  272. this.submitCanvExec(`SetX(${fx});;SetY(${fy});;Notify();;`);
  273. this.redraw();
  274. };
  275. }
  276. /** @summary Draw TPolyMarker
  277. * @private */
  278. function drawPolyMarker() {
  279. const poly = this.getObject(),
  280. func = this.getAxisToSvgFunc();
  281. this.createAttMarker({ attr: poly });
  282. this.createG();
  283. let path = '';
  284. for (let n = 0; n <= poly.fLastPoint; ++n)
  285. path += this.markeratt.create(func.x(poly.fX[n]), func.y(poly.fY[n]));
  286. if (path) {
  287. this.draw_g.append('svg:path')
  288. .attr('d', path)
  289. .call(this.markeratt.func);
  290. }
  291. assignContextMenu(this);
  292. addMoveHandler(this);
  293. this.dx = this.dy = 0;
  294. this.moveDrag = function(dx, dy) {
  295. this.dx += dx;
  296. this.dy += dy;
  297. makeTranslate(this.draw_g.select('path'), this.dx, this.dy);
  298. };
  299. this.moveEnd = function(not_changed) {
  300. if (not_changed) return;
  301. const poly2 = this.getObject(),
  302. func2 = this.getAxisToSvgFunc();
  303. let exec = '';
  304. for (let n = 0; n <= poly2.fLastPoint; ++n) {
  305. const x = this.svgToAxis('x', func2.x(poly2.fX[n]) + this.dx),
  306. y = this.svgToAxis('y', func2.y(poly2.fY[n]) + this.dy);
  307. poly2.fX[n] = x;
  308. poly2.fY[n] = y;
  309. exec += `SetPoint(${n},${x},${y});;`;
  310. }
  311. this.submitCanvExec(exec + 'Notify();;');
  312. this.redraw();
  313. };
  314. }
  315. /** @summary Draw JS image
  316. * @private */
  317. function drawJSImage(dom, obj, opt) {
  318. const painter = new BasePainter(dom),
  319. main = painter.selectDom(),
  320. img = main.append('img').attr('src', obj.fName).attr('title', obj.fTitle || obj.fName);
  321. if (opt && opt.indexOf('scale') >= 0)
  322. img.style('width', '100%').style('height', '100%');
  323. else if (opt && opt.indexOf('center') >= 0) {
  324. main.style('position', 'relative');
  325. img.attr('style', 'margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);');
  326. }
  327. painter.setTopPainter();
  328. return painter;
  329. }
  330. export { drawText, drawEllipse, drawPie, drawMarker, drawPolyMarker, drawJSImage };