Revision 58d6304a8688ac75ce1144d67ee25fc6669e60ed authored by Alexander Veit on 27 May 2022, 19:52:17 UTC, committed by Alexander Veit on 27 May 2022, 19:52:17 UTC
2 parent s c4468de + 1fcadc2
Raw File
HorizontalTiled1DPixiTrack.js
import Tiled1DPixiTrack from './Tiled1DPixiTrack';
import AxisPixi from './AxisPixi';

import { tileProxy } from './services';
import { colorToHex, showMousePosition } from './utils';

// Configs
import { GLOBALS } from './configs';

class HorizontalTiled1DPixiTrack extends Tiled1DPixiTrack {
  constructor(context, options) {
    super(context, options);
    const { animate, isShowGlobalMousePosition } = context;

    this.constIndicator = new GLOBALS.PIXI.Graphics();
    this.pMain.addChild(this.constIndicator);

    this.axis = new AxisPixi(this);
    this.pBase.addChild(this.axis.pAxis);

    this.animate = animate;
    this.options = options;
    this.isShowGlobalMousePosition = isShowGlobalMousePosition;

    this.pubSubs = [];

    if (this.options.showMousePosition && !this.hideMousePosition) {
      this.hideMousePosition = showMousePosition(
        this,
        this.is2d,
        this.isShowGlobalMousePosition(),
      );
    }
  }

  rerender(options, force) {
    const strOptions = JSON.stringify(options);

    if (!force && strOptions === this.prevOptions) return;

    super.rerender(options, force);

    this.prevOptions = strOptions;
    this.options = options;

    if (this.options.showMousePosition && !this.hideMousePosition) {
      this.hideMousePosition = showMousePosition(
        this,
        this.is2d,
        this.isShowGlobalMousePosition(),
      );
    }

    if (!this.options.showMousePosition && this.hideMousePosition) {
      this.hideMousePosition();
      this.hideMousePosition = undefined;
    }
  }

  calculateZoomLevel() {
    // offset by 2 because 1D tiles are more dense than 2D tiles
    // 1024 points per tile vs 256 for 2D tiles
    if (this.tilesetInfo.resolutions) {
      const zoomIndexX = tileProxy.calculateZoomLevelFromResolutions(
        this.tilesetInfo.resolutions,
        this._xScale,
        this.tilesetInfo.min_pos[0],
        this.tilesetInfo.max_pos[0] - 2,
      );

      return zoomIndexX;
    }

    // the tileProxy calculateZoomLevel function only cares about the
    // difference between the minimum and maximum position
    const xZoomLevel = tileProxy.calculateZoomLevel(
      this._xScale,
      this.tilesetInfo.min_pos[0],
      this.tilesetInfo.max_pos[0],
      this.tilesetInfo.bins_per_dimension || this.tilesetInfo.tile_size,
    );

    let zoomLevel = Math.min(xZoomLevel, this.maxZoom);
    zoomLevel = Math.max(zoomLevel, 0);

    return zoomLevel;
  }

  /**
   * Which scale should we use for calculating tile positions?
   *
   * Horizontal tracks should use the xScale and vertical tracks
   * should use the yScale
   *
   * This function should be overwritten by HorizontalTiled1DPixiTrack.js
   * and VerticalTiled1DPixiTrack.js
   */
  relevantScale() {
    return this._xScale;
  }

  draw() {
    super.draw();
    this.drawConstIndicator();
  }

  drawAxis(valueScale) {
    // either no axis position is specified
    if (
      !this.options.axisPositionVertical &&
      !this.options.axisPositionHorizontal
    ) {
      this.axis.clearAxis();
      return;
    }

    if (
      this.options.axisPositionVertical &&
      this.options.axisPositionVertical === 'hidden'
    ) {
      this.axis.clearAxis();
      return;
    }

    if (
      this.options.axisPositionHorizontal &&
      this.options.axisPositionHorizontal === 'hidden'
    ) {
      this.axis.clearAxis();
      return;
    }

    const margin = this.options.axisMargin || 0;

    if (
      this.options.axisPositionHorizontal === 'left' ||
      this.options.axisPositionVertical === 'top'
    ) {
      // left axis are shown at the beginning of the plot
      this.axis.pAxis.position.x = this.position[0] + margin;
      this.axis.pAxis.position.y = this.position[1];

      this.axis.drawAxisRight(valueScale, this.dimensions[1]);
    } else if (
      this.options.axisPositionHorizontal === 'outsideLeft' ||
      this.options.axisPositionVertical === 'outsideTop'
    ) {
      // left axis are shown at the beginning of the plot
      this.axis.pAxis.position.x = this.position[0] + margin;
      this.axis.pAxis.position.y = this.position[1];

      this.axis.drawAxisLeft(valueScale, this.dimensions[1]);
    } else if (
      this.options.axisPositionHorizontal === 'right' ||
      this.options.axisPositionVertical === 'bottom'
    ) {
      this.axis.pAxis.position.x =
        this.position[0] + this.dimensions[0] - margin;
      this.axis.pAxis.position.y = this.position[1];
      this.axis.drawAxisLeft(valueScale, this.dimensions[1]);
    } else if (
      this.options.axisPositionHorizontal === 'outsideRight' ||
      this.options.axisPositionVertical === 'outsideBottom'
    ) {
      this.axis.pAxis.position.x =
        this.position[0] + this.dimensions[0] - margin;
      this.axis.pAxis.position.y = this.position[1];
      this.axis.drawAxisRight(valueScale, this.dimensions[1]);
    }
  }

  mouseMoveZoomHandler(absX = this.mouseX, absY = this.mouseY) {
    if (
      typeof absX === 'undefined' ||
      !this.areAllVisibleTilesLoaded() ||
      !this.tilesetInfo
    )
      return;

    let dataPosX = 0;
    let dataPosY = 0;
    let orientation = '1d-horizontal';

    if (this.isLeftModified) {
      dataPosX = absY - this.position[1];
      dataPosY = absX - this.position[0];
      orientation = '1d-vertical';
    } else {
      dataPosX = absX - this.position[0];
      dataPosY = absY - this.position[1];
    }

    const relX = absX - this.position[0];
    const relY = absY - this.position[1];
    const dataX = this._xScale.invert(dataPosX);
    const dataY = this._yScale.invert(dataPosY);

    const data = this.getDataAtPos(dataPosX);
    if (!data) return;

    this.onMouseMoveZoom({
      trackId: this.id,
      data,
      absX,
      absY,
      relX,
      relY,
      dataX,
      dataY,
      orientation,
    });
  }

  drawConstIndicator() {
    if (!this.constIndicator) {
      // this can happen if we receive a tilesetInfo in the TiledPixiTrack
      // constructor before we get a chance to initialize this object
      return;
    }

    this.constIndicator.clear();
    while (this.constIndicator.children[0]) {
      this.constIndicator.removeChild(this.constIndicator.children[0]);
    }

    if (!this.options.constIndicators || !this.valueScale) return;

    this.options.constIndicators.forEach(
      ({
        color = 'black',
        opacity = 1.0,
        label = null,
        labelColor = 'black',
        labelOpacity = 1.0,
        labelPosition = 'leftTop',
        labelSize = 12,
        value = 0,
      } = {}) => {
        const colorHex = colorToHex(color);
        const labelColorHex = colorToHex(labelColor);

        this.constIndicator.beginFill(colorHex, opacity);

        const y = this.valueScale(value);
        let xOffset = 0;
        let widthOffset = 0;

        if (label) {
          const labelG = new GLOBALS.PIXI.Text(label, {
            fontFamily: 'Arial',
            fontSize: labelSize,
            fill: labelColorHex,
          });
          labelG.alpha = labelOpacity;

          switch (labelPosition) {
            case 'right':
              labelG.anchor.x = 1;
              labelG.anchor.y = 0.5;
              labelG.x = this.position[0] + this.dimensions[0] - 6;
              labelG.y = y;
              widthOffset = labelG.width + 8;
              break;

            case 'rightBottom':
              labelG.anchor.x = 1;
              labelG.anchor.y = 0;
              labelG.x = this.position[0] + this.dimensions[0] - 6;
              labelG.y = y;
              break;

            case 'rightTop':
              labelG.anchor.x = 1;
              labelG.anchor.y = 1;
              labelG.x = this.position[0] + this.dimensions[0] - 6;
              labelG.y = y;
              break;

            case 'left':
              labelG.anchor.x = 0;
              labelG.anchor.y = 0.5;
              labelG.x = this.position[0] + 2;
              labelG.y = y;
              xOffset = labelG.width + 4;
              break;

            case 'leftBottom':
              labelG.anchor.x = 0;
              labelG.anchor.y = 0;
              labelG.x = this.position[0] + 2;
              labelG.y = y;
              break;

            case 'leftTop':
            default:
              labelG.anchor.x = 0;
              labelG.anchor.y = 1;
              labelG.x = this.position[0] + 2;
              labelG.y = y;
              break;
          }
          this.constIndicator.addChild(labelG);
        }

        this.constIndicator.drawRect(
          this.position[0] + xOffset,
          y,
          this.dimensions[0] - widthOffset,
          1,
        );
      },
    );
  }

  exportSVG() {
    let track = null;
    let base = null;

    if (super.exportSVG) {
      [base, track] = super.exportSVG();
    } else {
      base = document.createElement('g');
      track = base;
    }

    base.setAttribute('class', 'horizontal-tiled-1d-track');
    const output = document.createElement('g');

    track.appendChild(output);

    if (this.options.constIndicators) {
      this.options.constIndicators.forEach(
        ({
          color = 'black',
          opacity = 1.0,
          label = null,
          labelColor = 'black',
          labelOpacity = 1.0,
          labelPosition = 'leftTop',
          labelSize = 12,
          value = 0,
        } = {}) => {
          const y = this.valueScale(value);

          if (label) {
            const labelEl = document.createElement('text');
            labelEl.textContent = label;

            labelEl.setAttribute('x', this.position[0]);
            labelEl.setAttribute('y', y);
            labelEl.setAttribute(
              'style',
              `font-family: 'Arial'; font-size: ${labelSize}px; fill: ${labelColor}; fill-opacity: ${labelOpacity};`,
            );

            switch (labelPosition) {
              case 'rightBottom':
                labelEl.setAttribute(
                  'x',
                  this.position[0] + this.dimensions[0] - 6,
                );
                labelEl.setAttribute('y', y + labelSize + 2);
                labelEl.setAttribute('text-anchor', 'end');
                break;

              case 'right':
              case 'rightTop':
                labelEl.setAttribute(
                  'x',
                  this.position[0] + this.dimensions[0] - 6,
                );
                labelEl.setAttribute('y', y - 2);
                labelEl.setAttribute('text-anchor', 'end');
                break;

              case 'leftBottom':
                labelEl.setAttribute('x', this.position[0] + 2);
                labelEl.setAttribute('y', y + labelSize + 2);
                break;

              case 'left':
              case 'leftTop':
              default:
                labelEl.setAttribute('x', this.position[0] + 2);
                labelEl.setAttribute('y', y - 2);
                break;
            }

            output.appendChild(labelEl);
          }

          const line = document.createElement('line');
          line.setAttribute('x1', this.position[0]);
          line.setAttribute('y1', y);
          line.setAttribute('x2', this.dimensions[0]);
          line.setAttribute('y2', y);
          line.setAttribute('stroke', color);
          line.setAttribute('stroke-opacity', opacity);

          output.appendChild(line);
        },
      );
    }

    return [base, track];
  }
}

export default HorizontalTiled1DPixiTrack;
back to top