Home Manual Reference Source Repository

src/shapes/crosshairs.js

import BaseShape from './base-shape';
import ns from '../core/namespace';


/**
 * A shape to display labelled crosshairs or similar positional
 * crosshairs/focus overlay.
 *
 * [example usage](./examples/layer-highlight.html)
 */
export default class Crosshairs extends BaseShape {
  getClassName() { return 'crosshairs'; }

  _getAccessorList() {
    return { cx: 0, cy: 0 };
  }

  _getDefaults() {
    return {
      color: '#000000',
      labelOffset: 0,
      opacity: 1
    };
  }

  render(renderingContext) {
    if (this.$el) { return this.$el; }

    this.$el = document.createElementNS(this.ns, 'g');

    this.$path = document.createElementNS(ns, 'path');
    this.$path.setAttributeNS(null, 'shape-rendering', 'geometricPrecision');
    this.$path.setAttributeNS(null, 'stroke-width', '1.5');
    this.$path.style.opacity = this.params.opacity;
    this.$path.style.stroke = this.params.color;
    this.$el.appendChild(this.$path);

    this.$labels = [
      document.createElementNS(this.ns, 'text'),
      document.createElementNS(this.ns, 'text')
    ];

    for (let i = 0; i < this.$labels.length; ++i) {
      const $label = this.$labels[i];
      $label.classList.add('label');
      $label.style.fontSize = '10px';
      $label.style.lineHeight = '10px';
      $label.style.fontFamily = 'monospace';
      $label.style.color = '#676767';
      $label.style.opacity = 0.9;
      $label.style.mozUserSelect = 'none';
      $label.style.webkitUserSelect = 'none';
      $label.style.userSelect = 'none';
      this.$el.appendChild($label);
    }

    return this.$el;
  }

  update(renderingContext, datum) {

    console.log("crosshairs update: datum = " + datum);
    
    const cx = this.cx(datum);
    const cy = this.cy(datum);

    if (typeof(this.lastCx) !== 'undefined') {
      if (this.lastCx === cx && this.lastCy === cy) {
	return;
      }
    }
    this.lastCx = cx;
    this.lastCy = cy;
    
    const x = Math.round(renderingContext.timeToPixel(cx)) + 0.5;
    const y = Math.round(renderingContext.valueToPixel(cy)) + 0.5;

    const minX = Math.floor(renderingContext.minX);
    const maxX = Math.ceil(renderingContext.maxX);
    const h = renderingContext.height;

      console.log("x = " + x + ", y = " + y + ", minX = " + minX + ", maxX = " +
                  maxX + ", h = " + h);
      
    this.$path.setAttributeNS(null, 'd',
                              `M${x},${0}L${x},${h}M${minX},${y}L${maxX},${y}`);

    const label = cy.toPrecision(4);
    const lw = label.length * 10;

    for (let i = 0; i < this.$labels.length; ++i) {

      const $label = this.$labels[i];

      while ($label.firstChild) {
        $label.removeChild($label.firstChild);
      }
      
      const $textLeft = document.createTextNode(label);
      $label.appendChild($textLeft);
      
      let lx = minX + 2;
      if (i == 1) {
        lx = maxX - lw - 2;
      }
      lx += this.params.labelOffset;
      
      $label.setAttributeNS(
        null, 'transform', `matrix(1, 0, 0, -1, ${lx}, ${h})`
      );

      let ly = h - y - 5;
      if (ly < 10) {
        ly = h - y + 15;
      }
      
      $label.setAttributeNS(null, 'y', ly);
    }
  }

  /**
   * The crosshairs cannot be selected.
   * @return {Boolean} false
   */
  inArea() { return false; }
}