draw/TBoxPainter.mjs

import { rgb as d3_rgb, select as d3_select } from '../d3.mjs';
import { DrawOptions, getBoxDecorations } from '../base/BasePainter.mjs';
import { ObjectPainter } from '../base/ObjectPainter.mjs';
import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs';
import { addMoveHandler } from '../gui/utils.mjs';
import { assignContextMenu } from '../gui/menu.mjs';

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

class TBoxPainter extends ObjectPainter {

   /** @summary start of drag handler
     * @private */
   moveStart(x, y) {
      const ww = Math.abs(this.x2 - this.x1), hh = Math.abs(this.y1 - this.y2);

      this.c_x1 = Math.abs(x - this.x2) > ww * 0.1;
      this.c_x2 = Math.abs(x - this.x1) > ww * 0.1;
      this.c_y1 = Math.abs(y - this.y2) > hh * 0.1;
      this.c_y2 = Math.abs(y - this.y1) > hh * 0.1;
      if (this.c_x1 !== this.c_x2 && this.c_y1 && this.c_y2)
         this.c_y1 = this.c_y2 = false;
      if (this.c_y1 !== this.c_y2 && this.c_x1 && this.c_x2)
         this.c_x1 = this.c_x2 = false;
   }

   /** @summary drag handler
     * @private */
   moveDrag(dx, dy) {
      if (this.c_x1) this.x1 += dx;
      if (this.c_x2) this.x2 += dx;
      if (this.c_y1) this.y1 += dy;
      if (this.c_y2) this.y2 += dy;

      const nodes = this.getG().selectAll('path').nodes(),
            pathes = this.getPathes();

      pathes.forEach((path, i) => d3_select(nodes[i]).attr('d', path));
   }

   /** @summary end of drag handler
     * @private */
   moveEnd(not_changed) {
      if (not_changed) return;
      const box = this.getObject(), X = this.swap_xy ? 'Y' : 'X', Y = this.swap_xy ? 'X' : 'Y';
      let exec = '';
      if (this.c_x1) { const v = this.svgToAxis('x', this.x1); box[`f${X}1`] = v; exec += `Set${X}1(${v});;`; }
      if (this.c_x2) { const v = this.svgToAxis('x', this.x2); box[`f${X}2`] = v; exec += `Set${X}2(${v});;`; }
      if (this.c_y1) { const v = this.svgToAxis('y', this.y1); box[`f${Y}1`] = v; exec += `Set${Y}1(${v});;`; }
      if (this.c_y2) { const v = this.svgToAxis('y', this.y2); box[`f${Y}2`] = v; exec += `Set${Y}2(${v});;`; }
      this.submitCanvExec(exec + 'Notify();;');
   }

   /** @summary Returns object ranges
     * @desc Can be used for newly created canvas */
   getUserRanges() {
      const box = this.getObject(),
            minx = Math.min(box.fX1, box.fX2),
            maxx = Math.max(box.fX1, box.fX2),
            miny = Math.min(box.fY1, box.fY2),
            maxy = Math.max(box.fY1, box.fY2);
      return { minx, miny, maxx, maxy };
   }

   /** @summary Create path */
   getPathes() {
      const xx = Math.round(Math.min(this.x1, this.x2)),
            yy = Math.round(Math.min(this.y1, this.y2)),
            ww = Math.round(Math.abs(this.x2 - this.x1)),
            hh = Math.round(Math.abs(this.y1 - this.y2)),
            path = `M${xx},${yy}h${ww}v${hh}h${-ww}z`;
      if (!this.borderMode)
         return [path];
      return [path].concat(getBoxDecorations(xx, yy, ww, hh, this.borderMode, this.borderSize, this.borderSize));
   }

   /** @summary Redraw box */
   redraw() {
      const box = this.getObject(),
            d = new DrawOptions(this.getDrawOpt()),
            fp = d.check('FRAME') ? this.getFramePainter() : null,
            draw_line = d.check('L');

      this.createAttLine({ attr: box });
      this.createAttFill({ attr: box });

      this.swap_xy = fp?.swap_xy();

      // if box filled, contour line drawn only with 'L' draw option:
      if (!this.fillatt.empty() && !draw_line)
         this.lineatt.color = 'none';

      const g = this.createG(fp);

      this.x1 = this.axisToSvg('x', box.fX1);
      this.x2 = this.axisToSvg('x', box.fX2);
      this.y1 = this.axisToSvg('y', box.fY1);
      this.y2 = this.axisToSvg('y', box.fY2);

      if (this.swap_xy)
         [this.x1, this.x2, this.y1, this.y2] = [this.y1, this.y2, this.x1, this.x2];

      this.borderMode = (box.fBorderMode && this.fillatt.hasColor()) ? box.fBorderMode : 0;
      this.borderSize = box.fBorderSize || 2;

      const paths = this.getPathes();

      g.append('svg:path')
       .attr('d', paths[0])
       .call(this.lineatt.func)
       .call(this.fillatt.func);

      if (this.borderMode) {
         g.append('svg:path')
          .attr('d', paths[1])
          .call(this.fillatt.func)
          .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatRgb());

         g.append('svg:path')
          .attr('d', paths[2])
          .call(this.fillatt.func)
          .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatRgb());
      }

      assignContextMenu(this);

      addMoveHandler(this);

      return this;
   }

   /** @summary Draw TLine object */
   static async draw(dom, obj, opt) {
      const painter = new TBoxPainter(dom, obj, opt);
      return ensureTCanvas(painter, false).then(() => painter.redraw());
   }

} // class TBoxPainter


export { TBoxPainter };