draw/TRatioPlotPainter.mjs

import { create, clTPad, clTLine, isFunc } from '../core.mjs';
import { ObjectPainter } from '../base/ObjectPainter.mjs';
import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs';
import { TLinePainter } from './TLinePainter.mjs';


/**
 * @summary Painter class for TRatioPlot
 *
 * @private
 */

const k_upper_pad = 'upper_pad', k_lower_pad = 'lower_pad', k_top_pad = 'top_pad';

class TRatioPlotPainter extends ObjectPainter {

   /** @summary Set grids range */
   setGridsRange(xmin, xmax, ymin, ymax, low_p) {
      const ratio = this.getObject();
      if (xmin === xmax) {
         const x_handle = this.getPadPainter()?.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad)?.getFramePainter()?.x_handle;
         if (!x_handle) return;
         if (xmin === 0) {
            // in case of unzoom full range should be used
            xmin = x_handle.full_min;
            xmax = x_handle.full_max;
         } else {
            // in case of y-scale zooming actual range has to be used
            xmin = x_handle.scale_min;
            xmax = x_handle.scale_max;
         }
      }
      ratio.fGridlines.forEach(line => {
         line.fX1 = xmin;
         line.fX2 = xmax;
      });
      const nlines = Math.min(ratio.fGridlines.length, ratio.fGridlinePositions.length);
      for (let i = 0; i < nlines; ++i) {
         const y = ratio.fGridlinePositions[i],
               line = ratio.fGridlines[i];
         if (ymin !== 'ignorey') {
            line.$do_not_draw = (ymin !== ymax) && ((y < ymin) || (y > ymax));
            line.fY1 = line.fY2 = y;
         }
         low_p?.findPainterFor(line)?.redraw();
      }
   }

   /** @summary Configure custom interactive handlers for ratio plot
    * @desc Should work for both new and old code */
   configureInteractive() {
      const ratio = this.getObject(),
            pp = this.getPadPainter(),
            up_p = pp.findPainterFor(ratio.fUpperPad, k_upper_pad, clTPad),
            up_fp = up_p?.getFramePainter(),
            low_p = pp.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad),
            low_fp = low_p?.getFramePainter();

      if (!up_p || !low_p)
         return;

      low_p.forEachPainterInPad(objp => {
         if (isFunc(objp?.testEditable))
            objp.testEditable(false);
      });

      this.setGridsRange(low_fp.scale_xmin, low_fp.scale_xmax, low_fp.scale_ymin, low_fp.scale_ymax, low_p);

      if (up_p._ratio_interactive && low_p._ratio_interactive)
         return;

      up_p._ratio_interactive = true;
      low_p._ratio_interactive = true;

      up_fp.o_zoom = up_fp.zoom;
      up_fp._ratio_low_fp = low_fp;
      up_fp._ratio_painter = this;

      up_fp.zoom = function(xmin, xmax, ymin, ymax, zmin, zmax) {
         return this.o_zoom(xmin, xmax, ymin, ymax, zmin, zmax).then(res => {
            this._ratio_painter.setGridsRange(up_fp.scale_xmin, up_fp.scale_xmax, 'ignory');
            return this._ratio_low_fp.o_zoom(up_fp.scale_xmin, up_fp.scale_xmax).then(() => res);
         });
      };

      up_fp.o_sizeChanged = up_fp.sizeChanged;
      up_fp.sizeChanged = function() {
         this.o_sizeChanged();
         this._ratio_low_fp.fX1NDC = this.fX1NDC;
         this._ratio_low_fp.fX2NDC = this.fX2NDC;
         this._ratio_low_fp.o_sizeChanged();
      };

      low_fp.o_zoom = low_fp.zoom;
      low_fp._ratio_up_fp = up_fp;
      low_fp._ratio_painter = this;

      low_fp.zoom = function(xmin, xmax, ymin, ymax, zmin, zmax) {
         if (xmin === xmax) {
            xmin = up_fp.xmin;
            xmax = up_fp.xmax;
         } else {
            if (xmin < up_fp.xmin) xmin = up_fp.xmin;
            if (xmax > up_fp.xmax) xmax = up_fp.xmax;
         }
         this._ratio_painter.setGridsRange(xmin, xmax, ymin, ymax);
         return this._ratio_up_fp.o_zoom(xmin, xmax).then(() => this.o_zoom(xmin, xmax, ymin, ymax, zmin, zmax));
      };

      low_fp.o_sizeChanged = low_fp.sizeChanged;
      low_fp.sizeChanged = function() {
         this.o_sizeChanged();
         this._ratio_up_fp.fX1NDC = this.fX1NDC;
         this._ratio_up_fp.fX2NDC = this.fX2NDC;
         this._ratio_up_fp.o_sizeChanged();
      };
   }

   /** @summary Redraw old TRatioPlot where object was in very end of list of primitives */
   async redrawOld() {
      const ratio = this.getObject(),
            pp = this.getPadPainter(),
            top_p = pp.findPainterFor(ratio.fTopPad, k_top_pad, clTPad),
            pad = pp.getRootPad(),
            mirrow_axis = (pad.fFrameFillStyle === 0) ? 1 : 0,
            tick_x = pad.fTickx || mirrow_axis,
            tick_y = pad.fTicky || mirrow_axis;

      top_p?.disablePadDrawing();

      const up_p = pp.findPainterFor(ratio.fUpperPad, k_upper_pad, clTPad),
            up_main = up_p?.getMainPainter(),
            up_fp = up_p?.getFramePainter(),
            low_p = pp.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad),
            low_main = low_p?.getMainPainter(),
            low_fp = low_p?.getFramePainter();
      let promise_up = Promise.resolve(true);

      if (up_p && up_main && up_fp && low_fp && !up_p._ratio_configured) {
         up_p._ratio_configured = true;

         up_main.options.Axis = 0; // draw both axes

         const h = up_main.getHisto();

         h.fYaxis.$use_top_pad = true; // workaround to use same scaling
         h.fXaxis.fLabelSize = 0; // do not draw X axis labels
         h.fXaxis.fTitle = ''; // do not draw X axis title

         up_p.getRootPad().fTickx = tick_x;
         up_p.getRootPad().fTicky = tick_y;

         promise_up = up_p.redrawPad();
      }

      return promise_up.then(() => {
         if (!low_p || !low_main || !low_fp || !up_fp || low_p._ratio_configured)
            return this;

         low_p._ratio_configured = true;
         low_main.options.Axis = 0; // draw both axes
         const h = low_main.getHisto();
         h.fXaxis.fTitle = 'x';

         h.fXaxis.$use_top_pad = true;
         h.fYaxis.$use_top_pad = true;
         low_p.getRootPad().fTickx = tick_x;
         low_p.getRootPad().fTicky = tick_y;

         const arr = [];

         // add missing lines in old ratio painter
         if ((ratio.fGridlinePositions.length > 0) && (ratio.fGridlines.length < ratio.fGridlinePositions.length)) {
            ratio.fGridlinePositions.forEach(gridy => {
               let found = false;
               ratio.fGridlines.forEach(line => {
                  if ((line.fY1 === line.fY2) && (Math.abs(line.fY1 - gridy) < 1e-6)) found = true;
               });
               if (!found) {
                  const line = create(clTLine);
                  line.fX1 = up_fp.scale_xmin;
                  line.fX2 = up_fp.scale_xmax;
                  line.fY1 = line.fY2 = gridy;
                  line.fLineStyle = 2;
                  ratio.fGridlines.push(line);
                  arr.push(TLinePainter.draw(low_p, line));
               }
            });
         }

         return Promise.all(arr)
                       .then(() => low_fp.zoomSingle('x', up_fp.scale_xmin, up_fp.scale_xmax))
                       .then(changed => { return changed ? true : low_p.redrawPad(); })
                       .then(() => this);
      });
   }

   /** @summary Redraw TRatioPlot */
   async redraw() {
      const ratio = this.getObject(),
            pp = this.getPadPainter();

      if (this.$oldratio === undefined)
         this.$oldratio = Boolean(pp.findPainterFor(ratio.fTopPad, k_top_pad, clTPad));

      // configure ratio interactive at the end
      pp.$userInteractive = () => this.configureInteractive();

      if (this.$oldratio)
         return this.redrawOld();

      const pad = pp.getRootPad(),
            mirrow_axis = (pad.fFrameFillStyle === 0) ? 1 : 0,
            tick_x = pad.fTickx || mirrow_axis,
            tick_y = pad.fTicky || mirrow_axis;

      // do not draw primitives and pad itself
      ratio.fTopPad.$disable_drawing = true;

      ratio.fUpperPad.$ratio_pad = 'up'; // indicate drawing of the axes for main painter
      ratio.fUpperPad.fTickx = tick_x;
      ratio.fUpperPad.fTicky = tick_y;

      ratio.fLowerPad.$ratio_pad = 'low'; // indicate drawing of the axes for main painter
      ratio.fLowerPad.fTickx = tick_x;
      ratio.fLowerPad.fTicky = tick_y;

      return this;
   }

   /** @summary Draw TRatioPlot */
   static async draw(dom, ratio, opt) {
      const painter = new TRatioPlotPainter(dom, ratio, opt);
      return ensureTCanvas(painter, false).then(() => painter.redraw());
   }

} // class TRatioPlotPainter

export { TRatioPlotPainter };