hist/RPavePainter.mjs

  1. import { settings } from '../core.mjs';
  2. import { makeTranslate } from '../base/BasePainter.mjs';
  3. import { RObjectPainter } from '../base/RObjectPainter.mjs';
  4. import { ensureRCanvas } from '../gpad/RCanvasPainter.mjs';
  5. import { addDragHandler } from '../gpad/TFramePainter.mjs';
  6. const ECorner = { kTopLeft: 1, kTopRight: 2, kBottomLeft: 3, kBottomRight: 4 };
  7. /**
  8. * @summary Painter for RPave class
  9. *
  10. * @private
  11. */
  12. class RPavePainter extends RObjectPainter {
  13. /** @summary Draw pave content
  14. * @desc assigned depending on pave class */
  15. async drawContent() { return this; }
  16. /** @summary Draw pave */
  17. async drawPave() {
  18. const rect = this.getPadPainter().getPadRect(),
  19. fp = this.getFramePainter();
  20. this.onFrame = fp && this.v7EvalAttr('onFrame', true);
  21. this.corner = this.v7EvalAttr('corner', ECorner.kTopRight);
  22. const visible = this.v7EvalAttr('visible', true),
  23. offsetx = this.v7EvalLength('offsetX', rect.width, 0.02),
  24. offsety = this.v7EvalLength('offsetY', rect.height, 0.02),
  25. pave_width = this.v7EvalLength('width', rect.width, 0.3),
  26. pave_height = this.v7EvalLength('height', rect.height, 0.3);
  27. this.createG();
  28. this.draw_g.classed('most_upper_primitives', true); // this primitive will remain on top of list
  29. if (!visible)
  30. return this;
  31. this.createv7AttLine('border_');
  32. this.createv7AttFill();
  33. const fr = this.onFrame ? fp.getFrameRect() : rect;
  34. let pave_x = 0, pave_y = 0;
  35. switch (this.corner) {
  36. case ECorner.kTopLeft:
  37. pave_x = fr.x + offsetx;
  38. pave_y = fr.y + offsety;
  39. break;
  40. case ECorner.kBottomLeft:
  41. pave_x = fr.x + offsetx;
  42. pave_y = fr.y + fr.height - offsety - pave_height;
  43. break;
  44. case ECorner.kBottomRight:
  45. pave_x = fr.x + fr.width - offsetx - pave_width;
  46. pave_y = fr.y + fr.height - offsety - pave_height;
  47. break;
  48. case ECorner.kTopRight:
  49. default:
  50. pave_x = fr.x + fr.width - offsetx - pave_width;
  51. pave_y = fr.y + offsety;
  52. }
  53. makeTranslate(this.draw_g, pave_x, pave_y);
  54. this.draw_g.append('svg:rect')
  55. .attr('x', 0)
  56. .attr('width', pave_width)
  57. .attr('y', 0)
  58. .attr('height', pave_height)
  59. .call(this.lineatt.func)
  60. .call(this.fillatt.func);
  61. this.pave_width = pave_width;
  62. this.pave_height = pave_height;
  63. // here should be fill and draw of text
  64. return this.drawContent().then(() => {
  65. if (!this.isBatchMode()) {
  66. // TODO: provide pave context menu as in v6
  67. if (settings.ContextMenu && this.paveContextMenu)
  68. this.draw_g.on('contextmenu', evnt => this.paveContextMenu(evnt));
  69. addDragHandler(this, { x: pave_x, y: pave_y, width: pave_width, height: pave_height,
  70. minwidth: 20, minheight: 20, redraw: d => this.sizeChanged(d) });
  71. }
  72. return this;
  73. });
  74. }
  75. /** @summary Process interactive moving of the stats box */
  76. sizeChanged(drag) {
  77. this.pave_width = drag.width;
  78. this.pave_height = drag.height;
  79. const pave_x = drag.x,
  80. pave_y = drag.y,
  81. rect = this.getPadPainter().getPadRect(),
  82. fr = this.onFrame ? this.getFramePainter().getFrameRect() : rect,
  83. changes = {};
  84. let offsetx, offsety;
  85. switch (this.corner) {
  86. case ECorner.kTopLeft:
  87. offsetx = pave_x - fr.x;
  88. offsety = pave_y - fr.y;
  89. break;
  90. case ECorner.kBottomLeft:
  91. offsetx = pave_x - fr.x;
  92. offsety = fr.y + fr.height - pave_y - this.pave_height;
  93. break;
  94. case ECorner.kBottomRight:
  95. offsetx = fr.x + fr.width - pave_x - this.pave_width;
  96. offsety = fr.y + fr.height - pave_y - this.pave_height;
  97. break;
  98. case ECorner.kTopRight:
  99. default:
  100. offsetx = fr.x + fr.width - pave_x - this.pave_width;
  101. offsety = pave_y - fr.y;
  102. }
  103. this.v7AttrChange(changes, 'offsetX', offsetx / rect.width);
  104. this.v7AttrChange(changes, 'offsetY', offsety / rect.height);
  105. this.v7AttrChange(changes, 'width', this.pave_width / rect.width);
  106. this.v7AttrChange(changes, 'height', this.pave_height / rect.height);
  107. this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server
  108. this.draw_g.selectChild('rect')
  109. .attr('width', this.pave_width)
  110. .attr('height', this.pave_height);
  111. this.drawContent();
  112. }
  113. /** @summary Redraw RPave object */
  114. async redraw(/* reason */) {
  115. return this.drawPave();
  116. }
  117. /** @summary draw RPave object */
  118. static async draw(dom, pave, opt) {
  119. const painter = new RPavePainter(dom, pave, opt, 'pave');
  120. return ensureRCanvas(painter, false).then(() => painter.drawPave());
  121. }
  122. }
  123. /**
  124. * @summary Painter for RLegend class
  125. *
  126. * @private
  127. */
  128. class RLegendPainter extends RPavePainter {
  129. /** @summary draw RLegend content */
  130. async drawContent() {
  131. const legend = this.getObject(),
  132. textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }),
  133. width = this.pave_width,
  134. height = this.pave_height,
  135. pp = this.getPadPainter();
  136. let nlines = legend.fEntries.length;
  137. if (legend.fTitle) nlines++;
  138. if (!nlines || !pp) return this;
  139. const stepy = height / nlines, margin_x = 0.02 * width;
  140. textFont.setSize(height/(nlines * 1.2));
  141. return this.startTextDrawingAsync(textFont, 'font').then(() => {
  142. let posy = 0;
  143. if (legend.fTitle) {
  144. this.drawText({ latex: 1, width: width - 2*margin_x, height: stepy, x: margin_x, y: posy, text: legend.fTitle });
  145. posy += stepy;
  146. }
  147. for (let i = 0; i < legend.fEntries.length; ++i) {
  148. const entry = legend.fEntries[i], w4 = Math.round(width/4);
  149. let objp = null;
  150. this.drawText({ latex: 1, width: 0.75*width - 3*margin_x, height: stepy, x: 2*margin_x + w4, y: posy, text: entry.fLabel });
  151. if (entry.fDrawableId !== 'custom')
  152. objp = pp.findSnap(entry.fDrawableId, true);
  153. else if (entry.fDrawable.fIO) {
  154. objp = new RObjectPainter(this.getPadPainter(), entry.fDrawable.fIO);
  155. if (entry.fLine) objp.createv7AttLine();
  156. if (entry.fFill) objp.createv7AttFill();
  157. if (entry.fMarker) objp.createv7AttMarker();
  158. }
  159. if (entry.fFill && objp?.fillatt) {
  160. this.draw_g
  161. .append('svg:path')
  162. .attr('d', `M${Math.round(margin_x)},${Math.round(posy + stepy*0.1)}h${w4}v${Math.round(stepy*0.8)}h${-w4}z`)
  163. .call(objp.fillatt.func);
  164. }
  165. if (entry.fLine && objp?.lineatt) {
  166. this.draw_g
  167. .append('svg:path')
  168. .attr('d', `M${Math.round(margin_x)},${Math.round(posy + stepy/2)}h${w4}`)
  169. .call(objp.lineatt.func);
  170. }
  171. if (entry.fError && objp?.lineatt) {
  172. this.draw_g
  173. .append('svg:path')
  174. .attr('d', `M${Math.round(margin_x + width/8)},${Math.round(posy + stepy*0.2)}v${Math.round(stepy*0.6)}`)
  175. .call(objp.lineatt.func);
  176. }
  177. if (entry.fMarker && objp?.markeratt) {
  178. this.draw_g.append('svg:path')
  179. .attr('d', objp.markeratt.create(margin_x + width/8, posy + stepy/2))
  180. .call(objp.markeratt.func);
  181. }
  182. posy += stepy;
  183. }
  184. return this.finishTextDrawing();
  185. });
  186. }
  187. /** @summary draw RLegend object */
  188. static async draw(dom, legend, opt) {
  189. const painter = new RLegendPainter(dom, legend, opt, 'legend');
  190. return ensureRCanvas(painter, false).then(() => painter.drawPave());
  191. }
  192. } // class RLegendPainter
  193. /**
  194. * @summary Painter for RPaveText class
  195. *
  196. * @private
  197. */
  198. class RPaveTextPainter extends RPavePainter {
  199. /** @summary draw RPaveText content */
  200. async drawContent() {
  201. const pavetext = this.getObject(),
  202. textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }),
  203. width = this.pave_width,
  204. height = this.pave_height,
  205. nlines = pavetext.fText.length;
  206. if (!nlines) return;
  207. const stepy = height / nlines, margin_x = 0.02 * width;
  208. textFont.setSize(height/(nlines * 1.2));
  209. return this.startTextDrawingAsync(textFont, 'font').then(() => {
  210. for (let i = 0, posy = 0; i < pavetext.fText.length; ++i, posy += stepy)
  211. this.drawText({ latex: 1, width: width - 2*margin_x, height: stepy, x: margin_x, y: posy, text: pavetext.fText[i] });
  212. return this.finishTextDrawing(undefined, true);
  213. });
  214. }
  215. /** @summary draw RPaveText object */
  216. static async draw(dom, pave, opt) {
  217. const painter = new RPaveTextPainter(dom, pave, opt, 'pavetext');
  218. return ensureRCanvas(painter, false).then(() => painter.drawPave());
  219. }
  220. } // class RPaveTextPainter
  221. export { RPavePainter, RLegendPainter, RPaveTextPainter };