<commit_before>/**
 * Visualization of genotypes
 * @flow
 */
'use strict';

import type {VcfDataSource} from '../sources/VcfDataSource';
import type {VariantContext} from '../data/vcf';
import type {DataCanvasRenderingContext2D} from 'data-canvas';
import type {VizProps} from '../VisualizationWrapper';
import type {Scale} from './d3utils';

import React from 'react';
import _ from 'underscore';


import d3utils from './d3utils';
import shallowEquals from 'shallow-equals';
import ContigInterval from '../ContigInterval';
import canvasUtils from './canvas-utils';
import TiledCanvas from './TiledCanvas';
import dataCanvas from 'data-canvas';
import style from '../style';
import utils from '../utils';
import type {State, NetworkStatus} from './pileuputils';

var MONSTER_REQUEST = 10000;
var LABEL_WIDTH = 100;

class GenotypeTiledCanvas extends TiledCanvas {
  options: Object;
  source: VcfDataSource;
  sampleIds: string[];

  constructor(source: VcfDataSource, sampleIds: string[], options: Object) {
    super();
    this.source = source;
    this.options = options;
    this.sampleIds = sampleIds;
  }

  update(newOptions: Object) {
    this.options = newOptions;
  }

  heightForRef(ref: string): number {
    return yForRow(this.sampleIds.length);
  }

  render(ctx: DataCanvasRenderingContext2D,
         scale: (x: number)=>number,
         range: ContigInterval<string>,
         originalRange: ?ContigInterval<string>,
         resolution: ?number) {
    var relaxedRange =
        new ContigInterval(range.contig, range.start() - 1, range.stop() + 1);

    // relaxed range is just for this tile. make sure to get resolution for whole
    // viewing area
    var vGenotypes = this.source.getGenotypesInRange(relaxedRange, resolution);
    renderGenotypes(ctx, scale, relaxedRange, vGenotypes, this.sampleIds);
  }
}
// Draw genotypes
function renderGenotypes(ctx: DataCanvasRenderingContext2D,
                    scale: (num: number) => number,
                    range: ContigInterval<string>,
                    genotypes: VariantContext[],
                    sampleIds) {
    // draw genotypes
    genotypes.forEach(genotype => {
        var variant = genotype.variant;
        var keys = genotype.sampleIds;
        ctx.pushObject(variant);
        ctx.fillStyle = style.GENOTYPE_FILL;
        ctx.strokeStyle = style.GENOTYPE_FILL;
        var x = Math.round(scale(variant.position));
        var width = Math.round(scale(variant.end)) - x;
        keys.forEach(sampleId => {
          var y = yForRow(sampleIds.indexOf(sampleId));
          ctx.fillRect(x - 0.2, y, width, style.GENOTYPE_HEIGHT);
          ctx.strokeRect(x - 0.2, y, width, style.GENOTYPE_HEIGHT);
        });
        ctx.popObject();
    });
}

function yForRow(row) {
  return row * (style.GENOTYPE_HEIGHT + style.GENOTYPE_SPACING);
}

class GenotypeTrack extends React.Component {
  props: VizProps & {source: VcfDataSource};
  state: State;
  tiles: GenotypeTiledCanvas;
  sampleIds: string[];

  constructor(props: Object) {
    super(props);
    this.state = {
      networkStatus: null
    };
    this.sampleIds = props.source.getSamples();
  }

  render(): any {
    // These styles allow vertical scrolling to see the full pileup.
    // Adding a vertical scrollbar shrinks the visible area, but we have to act
    // as though it doesn't, since adjusting the scale would put it out of sync
    // with other tracks.
    var containerStyles = {
      'height': '100%'
    };

    var labelStyles = {
      'float': 'left',
      'overflow': 'hidden',
      'width:': `${ LABEL_WIDTH }px`
    };

    var canvasStyles = {
      'overflow': 'hidden'
    };

    var statusEl = null,
        networkStatus = this.state.networkStatus;
    if (networkStatus) {
      statusEl = (
        <div ref='status' className='network-status-small'>
          <div className='network-status-message-small'>
            Loading Genotypes…
          </div>
        </div>
      );
    }
    var rangeLength = this.props.range.stop - this.props.range.start;
    // If range is too large, do not render 'canvas'
    if (rangeLength >= MONSTER_REQUEST) {
       return (
        <div>
            <div className='center'>
              Zoom in to see genotypes
            </div>
            <canvas onClick={this.handleClick.bind(this)} />
          </div>
          );
    } else {
      return (
        <div>
          {statusEl}
          <div ref='container' style={containerStyles}>
            <div style={labelStyles} ><canvas ref='labelCanvas' /></div>
            <div style={canvasStyles}><canvas ref='canvas' onClick={this.handleClick.bind(this)} /></div>
          </div>
        </div>
      );
    }
  }

  componentDidMount() {
        this.tiles = new GenotypeTiledCanvas(this.props.source,
        this.sampleIds, this.props.options);

        // Visualize new data as it comes in from the network.
        this.props.source.on('newdata', (range) => {
          this.tiles.invalidateRange(range);
          this.updateVisualization();
        });
        this.props.source.on('networkprogress', e => {
          this.setState({networkStatus: e});
        });
        this.props.source.on('networkdone', e => {
          this.setState({networkStatus: null});
        });

        this.updateVisualization();
  }

  getScale(): Scale {
    return d3utils.getTrackScale(this.props.range, this.props.width - LABEL_WIDTH);
  }

  componentDidUpdate(prevProps: any, prevState: any) {
    if (!shallowEquals(prevProps, this.props) ||
        !shallowEquals(prevState, this.state)) {
          this.tiles.update(this.props.options);
          this.tiles.invalidateAll();
          this.updateVisualization();
    }
  }

  // draws genotype lines to visually separate genotype rows
  drawLines(ctx: DataCanvasRenderingContext2D) {
    var width = this.props.width;

    // draw background for each row
    if (this.sampleIds !== null) {
      ctx.font = "9px Arial";
      this.sampleIds.forEach(sampleId => {
        ctx.pushObject(sampleId);
        var y = yForRow(this.sampleIds.indexOf(sampleId));
        ctx.fillStyle = style.BACKGROUND_FILL;
        ctx.fillRect(0, y, width, style.GENOTYPE_HEIGHT);
        ctx.popObject();
      });
    }
  }

  // draws sample names on side bar. This needs to be only rendered once.
  drawLabels() {
    // if already drawn, return
    var labelCanvas = (this.refs.labelCanvas : HTMLCanvasElement),
        width = this.props.width;

    // Hold off until height & width are known.
    if (width === 0 || typeof labelCanvas == 'undefined') return;

    var height = yForRow(this.sampleIds.length);


    // only render once on load.
    if (labelCanvas.clientHeight != height) {
      var labelCtx = dataCanvas.getDataContext(canvasUtils.getContext(labelCanvas));
      d3utils.sizeCanvas(labelCanvas, LABEL_WIDTH, height);

      // draw label for each row
      if (this.sampleIds !== null) {
        labelCtx.font = "9px Arial";
        this.sampleIds.forEach(sampleId => {
          labelCtx.pushObject(sampleId);
          var y = yForRow(this.sampleIds.indexOf(sampleId));
          labelCtx.fillStyle = "black";
          labelCtx.fillText(sampleId, 0, y+style.GENOTYPE_HEIGHT);
          labelCtx.popObject();
        });
      }
    }
  }

  updateVisualization() {
    var canvas = (this.refs.canvas : HTMLCanvasElement),
        width = this.props.width;

    // Hold off until height & width are known.
    if (width === 0 || typeof canvas == 'undefined') return;

    var height = yForRow(this.sampleIds.length);
    d3utils.sizeCanvas(canvas, width - LABEL_WIDTH, height);

    var ctx = dataCanvas.getDataContext(canvasUtils.getContext(canvas));
    this.drawLabels();
    this.renderScene(ctx);
  }

  renderScene(ctx: DataCanvasRenderingContext2D) {
    var range = this.props.range,
        interval = new ContigInterval(range.contig, range.start, range.stop),
        scale = this.getScale();

    ctx.reset();
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    // render lines after rectangle has been cleared
    this.drawLines(ctx);

    this.tiles.renderToScreen(ctx, interval, scale);
    ctx.restore();
  }

  handleClick(reactEvent: any) {
    var ev = reactEvent.nativeEvent,
        x = ev.offsetX;

    var genomeRange = this.props.range,
        // allow some buffering so click isn't so sensitive
        range = new ContigInterval(genomeRange.contig, genomeRange.start-1, genomeRange.stop+1),
        scale = this.getScale(),
        // leave padding of 2px to reduce click specificity
        clickStart = Math.floor(scale.invert(x)) - 2,
        clickEnd = clickStart + 2,
        // If click-tracking gets slow, this range could be narrowed to one
        // closer to the click coordinate, rather than the whole visible range.
        vGenotypes = this.props.source.getGenotypesInRange(range, 1);

    var genotype = _.find(vGenotypes, f => utils.tupleRangeOverlaps([[f.variant.position], [f.variant.end]], [[clickStart], [clickEnd]]));
    var alert = window.alert || console.log;
    if (genotype) {
      var variantString = `variant: ${JSON.stringify(genotype.variant)}`;
      var samples = `samples with variant: ${JSON.stringify(genotype.sampleIds)}`;
      alert(`${variantString}\n${samples}`);
    }
  }
}

GenotypeTrack.displayName = 'genotypes';

module.exports = GenotypeTrack;
<commit_msg>Adjusting canvas height based GenotypeTrack height<commit_after>/**
 * Visualization of genotypes
 * @flow
 */
'use strict';

import type {VcfDataSource} from '../sources/VcfDataSource';
import type {VariantContext} from '../data/vcf';
import type {DataCanvasRenderingContext2D} from 'data-canvas';
import type {VizProps} from '../VisualizationWrapper';
import type {Scale} from './d3utils';

import React from 'react';
import _ from 'underscore';


import d3utils from './d3utils';
import shallowEquals from 'shallow-equals';
import ContigInterval from '../ContigInterval';
import canvasUtils from './canvas-utils';
import TiledCanvas from './TiledCanvas';
import dataCanvas from 'data-canvas';
import style from '../style';
import utils from '../utils';
import type {State, NetworkStatus} from './pileuputils';

var MONSTER_REQUEST = 10000;
var LABEL_WIDTH = 100;

class GenotypeTiledCanvas extends TiledCanvas {
  options: Object;
  source: VcfDataSource;
  sampleIds: string[];

  constructor(source: VcfDataSource, sampleIds: string[], options: Object) {
    super();
    this.source = source;
    this.options = options;
    this.sampleIds = sampleIds;
  }

  update(newOptions: Object) {
    this.options = newOptions;
  }

  heightForRef(ref: string): number {
    return yForRow(this.sampleIds.length);
  }

  render(ctx: DataCanvasRenderingContext2D,
         scale: (x: number)=>number,
         range: ContigInterval<string>,
         originalRange: ?ContigInterval<string>,
         resolution: ?number) {
    var relaxedRange =
        new ContigInterval(range.contig, range.start() - 1, range.stop() + 1);

    // relaxed range is just for this tile. make sure to get resolution for whole
    // viewing area
    var vGenotypes = this.source.getGenotypesInRange(relaxedRange, resolution);
    renderGenotypes(ctx, scale, relaxedRange, vGenotypes, this.sampleIds);
  }
}
// Draw genotypes
function renderGenotypes(ctx: DataCanvasRenderingContext2D,
                    scale: (num: number) => number,
                    range: ContigInterval<string>,
                    genotypes: VariantContext[],
                    sampleIds) {
    // draw genotypes
    genotypes.forEach(genotype => {
        var variant = genotype.variant;
        var keys = genotype.sampleIds;
        ctx.pushObject(variant);
        ctx.fillStyle = style.GENOTYPE_FILL;
        ctx.strokeStyle = style.GENOTYPE_FILL;
        var x = Math.round(scale(variant.position));
        var width = Math.round(scale(variant.end)) - x;
        keys.forEach(sampleId => {
          var y = yForRow(sampleIds.indexOf(sampleId));
          ctx.fillRect(x - 0.2, y, width, style.GENOTYPE_HEIGHT);
          ctx.strokeRect(x - 0.2, y, width, style.GENOTYPE_HEIGHT);
        });
        ctx.popObject();
    });
}

function yForRow(row) {
  return row * (style.GENOTYPE_HEIGHT + style.GENOTYPE_SPACING);
}

class GenotypeTrack extends React.Component {
  props: VizProps & {source: VcfDataSource};
  state: State;
  tiles: GenotypeTiledCanvas;
  sampleIds: string[];

  constructor(props: Object) {
    super(props);
    this.state = {
      networkStatus: null
    };
    this.sampleIds = props.source.getSamples();
  }

  render(): any {
    // These styles allow vertical scrolling to see the full pileup.
    // Adding a vertical scrollbar shrinks the visible area, but we have to act
    // as though it doesn't, since adjusting the scale would put it out of sync
    // with other tracks.
    var containerStyles = {
      'height': '100%'
    };

    var labelStyles = {
      'float': 'left',
      'overflow': 'hidden',
      'width:': `${ LABEL_WIDTH }px`
    };

    var canvasStyles = {
      'overflow': 'hidden'
    };

    var statusEl = null,
        networkStatus = this.state.networkStatus;
    if (networkStatus) {
      statusEl = (
        <div ref='status' className='network-status-small'>
          <div className='network-status-message-small'>
            Loading Genotypes…
          </div>
        </div>
      );
    }
    var rangeLength = this.props.range.stop - this.props.range.start;
    // If range is too large, do not render 'canvas'
    if (rangeLength >= MONSTER_REQUEST) {
       return (
        <div>
            <div className='center'>
              Zoom in to see genotypes
            </div>
            <canvas onClick={this.handleClick.bind(this)} />
          </div>
          );
    } else {
      return (
        <div>
          {statusEl}
          <div ref='container' style={containerStyles}>
            <div style={labelStyles} ><canvas ref='labelCanvas' /></div>
            <div style={canvasStyles}><canvas ref='canvas' onClick={this.handleClick.bind(this)} /></div>
          </div>
        </div>
      );
    }
  }

  componentDidMount() {
        this.tiles = new GenotypeTiledCanvas(this.props.source,
        this.sampleIds, this.props.options);

        // Visualize new data as it comes in from the network.
        this.props.source.on('newdata', (range) => {
          this.tiles.invalidateRange(range);
          this.updateVisualization();
        });
        this.props.source.on('networkprogress', e => {
          this.setState({networkStatus: e});
        });
        this.props.source.on('networkdone', e => {
          this.setState({networkStatus: null});
        });

        let height = yForRow(this.sampleIds.length);

         // This is very un-react-y, but we are doing this to avoid making prop changes on multiple classes.
        var searchNode = this;
        while (searchNode) {

          // Cycle the next parent node
          searchNode = ReactDOM.findDOMNode(searchNode).parentNode;

          // If the current node has a class name with greater than or equal to 1 classes, it might be the GenotypeTrack
          if (searchNode && searchNode.className && searchNode.className.split(' ') && searchNode.className.split(' ')[1]) {
            
            let name = searchNode.className.split(' ')[1];

            // If the class name matches the GenotypeTrack, we can make changes
            if (name == GenotypeTrack.displayName) {
              let searchNodeHeight = $(searchNode).context.clientHeight;

              // If the height of the GenotypeTrack is less than the current height, we make the change
              if (height < searchNodeHeight) {
                $(searchNode).css('flex', '0 0 ' + height + 'px');
              }

              break;
            }
          }
        }

        this.updateVisualization();
  }

  getScale(): Scale {
    return d3utils.getTrackScale(this.props.range, this.props.width - LABEL_WIDTH);
  }

  componentDidUpdate(prevProps: any, prevState: any) {
    if (!shallowEquals(prevProps, this.props) ||
        !shallowEquals(prevState, this.state)) {
          this.tiles.update(this.props.options);
          this.tiles.invalidateAll();
          this.updateVisualization();
    }
  }

  // draws genotype lines to visually separate genotype rows
  drawLines(ctx: DataCanvasRenderingContext2D) {
    var width = this.props.width;

    // draw background for each row
    if (this.sampleIds !== null) {
      ctx.font = "9px Arial";
      this.sampleIds.forEach(sampleId => {
        ctx.pushObject(sampleId);
        var y = yForRow(this.sampleIds.indexOf(sampleId));
        ctx.fillStyle = style.BACKGROUND_FILL;
        ctx.fillRect(0, y, width, style.GENOTYPE_HEIGHT);
        ctx.popObject();
      });
    }
  }

  // draws sample names on side bar. This needs to be only rendered once.
  drawLabels() {
    // if already drawn, return
    var labelCanvas = (this.refs.labelCanvas : HTMLCanvasElement),
        width = this.props.width;

    // Hold off until height & width are known.
    if (width === 0 || typeof labelCanvas == 'undefined') return;

    var height = yForRow(this.sampleIds.length);


    // only render once on load.
    if (labelCanvas.clientHeight != height) {
      var labelCtx = dataCanvas.getDataContext(canvasUtils.getContext(labelCanvas));
      d3utils.sizeCanvas(labelCanvas, LABEL_WIDTH, height);

      // draw label for each row
      if (this.sampleIds !== null) {
        labelCtx.font = "9px Arial";
        this.sampleIds.forEach(sampleId => {
          labelCtx.pushObject(sampleId);
          var y = yForRow(this.sampleIds.indexOf(sampleId));
          labelCtx.fillStyle = "black";
          labelCtx.fillText(sampleId, 0, y+style.GENOTYPE_HEIGHT);
          labelCtx.popObject();
        });
      }
    }
  }

  updateVisualization() {
    var canvas = (this.refs.canvas : HTMLCanvasElement),
        width = this.props.width;

    // Hold off until height & width are known.
    if (width === 0 || typeof canvas == 'undefined') return;

    var height = yForRow(this.sampleIds.length);
    d3utils.sizeCanvas(canvas, width - LABEL_WIDTH, height);

    var ctx = dataCanvas.getDataContext(canvasUtils.getContext(canvas));
    this.drawLabels();
    this.renderScene(ctx);
  }

  renderScene(ctx: DataCanvasRenderingContext2D) {
    var range = this.props.range,
        interval = new ContigInterval(range.contig, range.start, range.stop),
        scale = this.getScale();

    ctx.reset();
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    // render lines after rectangle has been cleared
    this.drawLines(ctx);

    this.tiles.renderToScreen(ctx, interval, scale);
    ctx.restore();
  }

  handleClick(reactEvent: any) {
    var ev = reactEvent.nativeEvent,
        x = ev.offsetX;

    var genomeRange = this.props.range,
        // allow some buffering so click isn't so sensitive
        range = new ContigInterval(genomeRange.contig, genomeRange.start-1, genomeRange.stop+1),
        scale = this.getScale(),
        // leave padding of 2px to reduce click specificity
        clickStart = Math.floor(scale.invert(x)) - 2,
        clickEnd = clickStart + 2,
        // If click-tracking gets slow, this range could be narrowed to one
        // closer to the click coordinate, rather than the whole visible range.
        vGenotypes = this.props.source.getGenotypesInRange(range, 1);

    var genotype = _.find(vGenotypes, f => utils.tupleRangeOverlaps([[f.variant.position], [f.variant.end]], [[clickStart], [clickEnd]]));
    var alert = window.alert || console.log;
    if (genotype) {
      var variantString = `variant: ${JSON.stringify(genotype.variant)}`;
      var samples = `samples with variant: ${JSON.stringify(genotype.sampleIds)}`;
      alert(`${variantString}\n${samples}`);
    }
  }
}

GenotypeTrack.displayName = 'genotypes';

module.exports = GenotypeTrack;

{'fraction_non_alphanumeric': 0.12271658075171413, 'fraction_numerical': 0.004454231519943947, 'mean_word_length': 2.6377207354815217, 'pattern_counts': {'":': 0, '<': 48, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6486188', 'n_tokens_mistral': 5965, 'n_tokens_neox': 5786, 'n_words': 1462}