draw/TRatioPlotPainter.mjs

  1. import { create, clTPad, clTLine, isFunc } from '../core.mjs';
  2. import { ObjectPainter } from '../base/ObjectPainter.mjs';
  3. import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs';
  4. import { TLinePainter } from './TLinePainter.mjs';
  5. /**
  6. * @summary Painter class for TRatioPlot
  7. *
  8. * @private
  9. */
  10. const k_upper_pad = 'upper_pad', k_lower_pad = 'lower_pad', k_top_pad = 'top_pad';
  11. class TRatioPlotPainter extends ObjectPainter {
  12. /** @summary Set grids range */
  13. setGridsRange(xmin, xmax, ymin, ymax, low_p) {
  14. const ratio = this.getObject();
  15. if (xmin === xmax) {
  16. const x_handle = this.getPadPainter()?.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad)?.getFramePainter()?.x_handle;
  17. if (!x_handle) return;
  18. if (xmin === 0) {
  19. // in case of unzoom full range should be used
  20. xmin = x_handle.full_min;
  21. xmax = x_handle.full_max;
  22. } else {
  23. // in case of y-scale zooming actual range has to be used
  24. xmin = x_handle.scale_min;
  25. xmax = x_handle.scale_max;
  26. }
  27. }
  28. ratio.fGridlines.forEach(line => {
  29. line.fX1 = xmin;
  30. line.fX2 = xmax;
  31. });
  32. const nlines = Math.min(ratio.fGridlines.length, ratio.fGridlinePositions.length);
  33. for (let i = 0; i < nlines; ++i) {
  34. const y = ratio.fGridlinePositions[i],
  35. line = ratio.fGridlines[i];
  36. if (ymin !== 'ignorey') {
  37. line.$do_not_draw = (ymin !== ymax) && ((y < ymin) || (y > ymax));
  38. line.fY1 = line.fY2 = y;
  39. }
  40. low_p?.findPainterFor(line)?.redraw();
  41. }
  42. }
  43. /** @summary Configure custom interactive handlers for ratio plot
  44. * @desc Should work for both new and old code */
  45. configureInteractive() {
  46. const ratio = this.getObject(),
  47. pp = this.getPadPainter(),
  48. up_p = pp.findPainterFor(ratio.fUpperPad, k_upper_pad, clTPad),
  49. up_fp = up_p?.getFramePainter(),
  50. low_p = pp.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad),
  51. low_fp = low_p?.getFramePainter();
  52. if (!up_p || !low_p)
  53. return;
  54. low_p.forEachPainterInPad(objp => {
  55. if (isFunc(objp?.testEditable))
  56. objp.testEditable(false);
  57. });
  58. this.setGridsRange(low_fp.scale_xmin, low_fp.scale_xmax, low_fp.scale_ymin, low_fp.scale_ymax, low_p);
  59. if (up_p._ratio_interactive && low_p._ratio_interactive)
  60. return;
  61. up_p._ratio_interactive = true;
  62. low_p._ratio_interactive = true;
  63. up_fp.o_zoom = up_fp.zoom;
  64. up_fp._ratio_low_fp = low_fp;
  65. up_fp._ratio_painter = this;
  66. up_fp.zoom = function(xmin, xmax, ymin, ymax, zmin, zmax) {
  67. return this.o_zoom(xmin, xmax, ymin, ymax, zmin, zmax).then(res => {
  68. this._ratio_painter.setGridsRange(up_fp.scale_xmin, up_fp.scale_xmax, 'ignory');
  69. return this._ratio_low_fp.o_zoom(up_fp.scale_xmin, up_fp.scale_xmax).then(() => res);
  70. });
  71. };
  72. up_fp.o_sizeChanged = up_fp.sizeChanged;
  73. up_fp.sizeChanged = function() {
  74. this.o_sizeChanged();
  75. this._ratio_low_fp.fX1NDC = this.fX1NDC;
  76. this._ratio_low_fp.fX2NDC = this.fX2NDC;
  77. this._ratio_low_fp.o_sizeChanged();
  78. };
  79. low_fp.o_zoom = low_fp.zoom;
  80. low_fp._ratio_up_fp = up_fp;
  81. low_fp._ratio_painter = this;
  82. low_fp.zoom = function(xmin, xmax, ymin, ymax, zmin, zmax) {
  83. if (xmin === xmax) {
  84. xmin = up_fp.xmin;
  85. xmax = up_fp.xmax;
  86. } else {
  87. if (xmin < up_fp.xmin) xmin = up_fp.xmin;
  88. if (xmax > up_fp.xmax) xmax = up_fp.xmax;
  89. }
  90. this._ratio_painter.setGridsRange(xmin, xmax, ymin, ymax);
  91. return this._ratio_up_fp.o_zoom(xmin, xmax).then(() => this.o_zoom(xmin, xmax, ymin, ymax, zmin, zmax));
  92. };
  93. low_fp.o_sizeChanged = low_fp.sizeChanged;
  94. low_fp.sizeChanged = function() {
  95. this.o_sizeChanged();
  96. this._ratio_up_fp.fX1NDC = this.fX1NDC;
  97. this._ratio_up_fp.fX2NDC = this.fX2NDC;
  98. this._ratio_up_fp.o_sizeChanged();
  99. };
  100. }
  101. /** @summary Redraw old TRatioPlot where object was in very end of list of primitives */
  102. async redrawOld() {
  103. const ratio = this.getObject(),
  104. pp = this.getPadPainter(),
  105. top_p = pp.findPainterFor(ratio.fTopPad, k_top_pad, clTPad),
  106. pad = pp.getRootPad(),
  107. mirrow_axis = (pad.fFrameFillStyle === 0) ? 1 : 0,
  108. tick_x = pad.fTickx || mirrow_axis,
  109. tick_y = pad.fTicky || mirrow_axis;
  110. top_p?.disablePadDrawing();
  111. const up_p = pp.findPainterFor(ratio.fUpperPad, k_upper_pad, clTPad),
  112. up_main = up_p?.getMainPainter(),
  113. up_fp = up_p?.getFramePainter(),
  114. low_p = pp.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad),
  115. low_main = low_p?.getMainPainter(),
  116. low_fp = low_p?.getFramePainter();
  117. let promise_up = Promise.resolve(true);
  118. if (up_p && up_main && up_fp && low_fp && !up_p._ratio_configured) {
  119. up_p._ratio_configured = true;
  120. up_main.options.Axis = 0; // draw both axes
  121. const h = up_main.getHisto();
  122. h.fYaxis.$use_top_pad = true; // workaround to use same scaling
  123. h.fXaxis.fLabelSize = 0; // do not draw X axis labels
  124. h.fXaxis.fTitle = ''; // do not draw X axis title
  125. up_p.getRootPad().fTickx = tick_x;
  126. up_p.getRootPad().fTicky = tick_y;
  127. promise_up = up_p.redrawPad();
  128. }
  129. return promise_up.then(() => {
  130. if (!low_p || !low_main || !low_fp || !up_fp || low_p._ratio_configured)
  131. return this;
  132. low_p._ratio_configured = true;
  133. low_main.options.Axis = 0; // draw both axes
  134. const h = low_main.getHisto();
  135. h.fXaxis.fTitle = 'x';
  136. h.fXaxis.$use_top_pad = true;
  137. h.fYaxis.$use_top_pad = true;
  138. low_p.getRootPad().fTickx = tick_x;
  139. low_p.getRootPad().fTicky = tick_y;
  140. const arr = [];
  141. // add missing lines in old ratio painter
  142. if ((ratio.fGridlinePositions.length > 0) && (ratio.fGridlines.length < ratio.fGridlinePositions.length)) {
  143. ratio.fGridlinePositions.forEach(gridy => {
  144. let found = false;
  145. ratio.fGridlines.forEach(line => {
  146. if ((line.fY1 === line.fY2) && (Math.abs(line.fY1 - gridy) < 1e-6)) found = true;
  147. });
  148. if (!found) {
  149. const line = create(clTLine);
  150. line.fX1 = up_fp.scale_xmin;
  151. line.fX2 = up_fp.scale_xmax;
  152. line.fY1 = line.fY2 = gridy;
  153. line.fLineStyle = 2;
  154. ratio.fGridlines.push(line);
  155. arr.push(TLinePainter.draw(low_p, line));
  156. }
  157. });
  158. }
  159. return Promise.all(arr)
  160. .then(() => low_fp.zoomSingle('x', up_fp.scale_xmin, up_fp.scale_xmax))
  161. .then(changed => { return changed ? true : low_p.redrawPad(); })
  162. .then(() => this);
  163. });
  164. }
  165. /** @summary Redraw TRatioPlot */
  166. async redraw() {
  167. const ratio = this.getObject(),
  168. pp = this.getPadPainter();
  169. if (this.$oldratio === undefined)
  170. this.$oldratio = Boolean(pp.findPainterFor(ratio.fTopPad, k_top_pad, clTPad));
  171. // configure ratio interactive at the end
  172. pp.$userInteractive = () => this.configureInteractive();
  173. if (this.$oldratio)
  174. return this.redrawOld();
  175. const pad = pp.getRootPad(),
  176. mirrow_axis = (pad.fFrameFillStyle === 0) ? 1 : 0,
  177. tick_x = pad.fTickx || mirrow_axis,
  178. tick_y = pad.fTicky || mirrow_axis;
  179. // do not draw primitives and pad itself
  180. ratio.fTopPad.$disable_drawing = true;
  181. ratio.fUpperPad.$ratio_pad = 'up'; // indicate drawing of the axes for main painter
  182. ratio.fUpperPad.fTickx = tick_x;
  183. ratio.fUpperPad.fTicky = tick_y;
  184. ratio.fLowerPad.$ratio_pad = 'low'; // indicate drawing of the axes for main painter
  185. ratio.fLowerPad.fTickx = tick_x;
  186. ratio.fLowerPad.fTicky = tick_y;
  187. return this;
  188. }
  189. /** @summary Draw TRatioPlot */
  190. static async draw(dom, ratio, opt) {
  191. const painter = new TRatioPlotPainter(dom, ratio, opt);
  192. return ensureTCanvas(painter, false).then(() => painter.redraw());
  193. }
  194. } // class TRatioPlotPainter
  195. export { TRatioPlotPainter };