hist2d/TScatterPainter.mjs

import { clTPaletteAxis, isFunc, create, kNoZoom } from '../core.mjs';
import { getColorPalette } from '../base/colors.mjs';
import { TAttMarkerHandler } from '../base/TAttMarkerHandler.mjs';
import { TGraphPainter } from './TGraphPainter.mjs';
import { HistContour } from './THistPainter.mjs';
import { TH2Painter } from './TH2Painter.mjs';

/**
 * @summary Painter for TScatter object.
 *
 * @private
 */

class TScatterPainter extends TGraphPainter {

   #color_palette; // color palette

   /** @summary Cleanup painter */
   cleanup() {
      this.clearHistPalette();
      super.cleanup();
   }

   /** @summary Return drawn graph object */
   getGraph() { return this.getObject()?.fGraph; }

   /** @summary Is TScatter object */
   isScatter() { return true; }

   /** @summary Return margins for histogram ranges */
   getHistRangeMargin() { return this.getObject()?.fMargin ?? 0.1; }

  /** @summary Draw axis histogram
    * @private */
   async drawAxisHisto() {
      const need_histo = !this.getHistogram(),
            histo = this.createHistogram(need_histo, need_histo);
      return TH2Painter.draw(this.getDrawDom(), histo, this.options.Axis + ';IGNORE_PALETTE');
   }

  /** @summary Provide palette, create if necessary
    * @private */
   getPalette() {
      const gr = this.getGraph();
      let pal = gr?.fFunctions?.arr?.find(func => (func._typename === clTPaletteAxis));

      if (!pal && gr) {
         pal = create(clTPaletteAxis);

         const fp = this.get_fp();
         Object.assign(pal, { fX1NDC: fp.fX2NDC + 0.005, fX2NDC: fp.fX2NDC + 0.05, fY1NDC: fp.fY1NDC, fY2NDC: fp.fY2NDC, fInit: 1, $can_move: true });
         Object.assign(pal.fAxis, { fChopt: '+', fLineColor: 1, fLineSyle: 1, fLineWidth: 1, fTextAngle: 0, fTextAlign: 11, fNdiv: 510 });
         gr.fFunctions.AddFirst(pal, '');
      }

      return pal;
   }

   /** @summary Update TScatter members
    * @private */
   _updateMembers(scatter, obj) {
      scatter.fBits = obj.fBits;
      scatter.fTitle = obj.fTitle;
      scatter.fNpoints = obj.fNpoints;
      scatter.fColor = obj.fColor;
      scatter.fSize = obj.fSize;
      scatter.fMargin = obj.fMargin;
      scatter.fMinMarkerSize = obj.fMinMarkerSize;
      scatter.fMaxMarkerSize = obj.fMaxMarkerSize;
      return super._updateMembers(scatter.fGraph, obj.fGraph);
   }

   /** @summary Return Z axis used for palette drawing
    * @private */
   getZaxis() {
      return this.getHistogram()?.fZaxis;
   }

   /** @summary Checks if it makes sense to zoom inside specified axis range */
   canZoomInside(axis, min, max) {
      if (axis !== 'z')
         return super.canZoomInside(axis, min, max);

      const levels = this.fContour?.getLevels();
      if (!levels)
         return false;
      // match at least full color level inside
      for (let i = 0; i < levels.length - 1; ++i) {
         if ((min <= levels[i]) && (max >= levels[i+1]))
            return true;
      }
      return false;
   }

   /** @summary Returns color palette associated with histogram
    * @desc Create if required, checks pad and canvas for custom palette */
   getHistPalette(force) {
      let pal = force ? null : this.#color_palette;
      if (pal)
         return pal;
      const pp = this.getPadPainter();
      if (isFunc(pp?.getCustomPalette))
         pal = pp.getCustomPalette();
      if (!pal)
         pal = getColorPalette(this.options.Palette, pp?.isGrayscale());
      this.#color_palette = pal;
      return pal;
   }

   /** @summary Remove palette */
   clearHistPalette() {
      this.#color_palette = undefined;
   }

   /** @summary Actual drawing of TScatter */
   async drawGraph() {
      const fp = this.get_fp(),
            hpainter = this.getMainPainter(),
            scatter = this.getObject(),
            hist = this.getHistogram();

      let scale = 1, offset = 0, palette;
      if (!fp || !hpainter || !scatter)
         return;

      if (scatter.fColor) {
         const pal = this.getPalette();
         if (pal)
            pal.$main_painter = this;

         palette = this.getHistPalette();

         let minc = scatter.fColor[0], maxc = scatter.fColor[0];
         for (let i = 1; i < scatter.fColor.length; ++i) {
             minc = Math.min(minc, scatter.fColor[i]);
             maxc = Math.max(maxc, scatter.fColor[i]);
         }
         if (maxc <= minc)
            maxc = minc < 0 ? 0.9*minc : (minc > 0 ? 1.1*minc : 1);
         else if ((minc > 0) && (minc < 0.3*maxc))
            minc = 0;
         this.fContour = new HistContour(minc, maxc);
         this.fContour.createNormal(30);
         this.fContour.configIndicies(0, 0);

         fp.zmin = minc;
         fp.zmax = maxc;

         if (!fp.zoomChangedInteractive('z') && hist && hist.fMinimum !== kNoZoom && hist.fMaximum !== kNoZoom) {
            fp.zoom_zmin = hist.fMinimum;
            fp.zoom_zmax = hist.fMaximum;
         }
      }

      if (scatter.fSize) {
         let mins = scatter.fSize[0], maxs = scatter.fSize[0];

         for (let i = 1; i < scatter.fSize.length; ++i) {
             mins = Math.min(mins, scatter.fSize[i]);
             maxs = Math.max(maxs, scatter.fSize[i]);
         }

         if (maxs <= mins)
            maxs = mins < 0 ? 0.9*mins : (mins > 0 ? 1.1*mins : 1);

         scale = (scatter.fMaxMarkerSize - scatter.fMinMarkerSize) / (maxs - mins);
         offset = mins;
      }

      this.createG(!fp.pad_layer);

      const funcs = fp.getGrFuncs(),
            is_zoom = (fp.zoom_zmin !== fp.zoom_zmax) && scatter.fColor,
            bins = this._getBins();

      for (let i = 0; i < bins.length; ++i) {
         if (is_zoom && ((scatter.fColor[i] < fp.zoom_zmin) || (scatter.fColor[i] > fp.zoom_zmax)))
            continue;

         const pnt = bins[i],
               grx = funcs.grx(pnt.x),
               gry = funcs.gry(pnt.y),
               size = scatter.fSize ? scatter.fMinMarkerSize + scale * (scatter.fSize[i] - offset) : scatter.fMarkerSize,
               color = scatter.fColor ? this.fContour.getPaletteColor(palette, scatter.fColor[i]) : this.getColor(scatter.fMarkerColor),
               handle = new TAttMarkerHandler({ color, size, style: scatter.fMarkerStyle });

          this.draw_g.append('svg:path')
                     .attr('d', handle.create(grx, gry))
                     .call(handle.func);
      }

      return this;
   }

   /** @summary Draw TScatter object */
   static async draw(dom, obj, opt) {
      return TGraphPainter._drawGraph(new TScatterPainter(dom, obj), opt);
   }

} // class TScatterPainter

export { TScatterPainter };