<commit_before>import d3 from 'd3';
import _ from 'underscore';
import Grapher from './grapher';
import * as Helpers from './visualisation_helpers';

/**
 * Renders Kablammo visualization
 *
 * JSON received from server side is modified as JSON expected by kablammo's
 * graph.js.  All the relevant information including a SVG container where
 * visual needs to be rendered, is delegated to graph.js. graph.js renders
 * kablammo visualization and has all event handlers for events performed on
 * the visual.
 *
 * Event handlers related to downloading and viewing of alignments and images
 * have been extracted from grapher.js and interface.js and directly included
 * here.
 */

class Graph {
  static name() {
    return 'Graphical overview of aligning region(s)';
  }

  static className() {
    return 'kablammo';
  }

  static collapseId(props) {
    return 'kablammo_'+props.query.number+'_'+props.hit.number;
  }

  static dataName(props) {
      return 'Kablammo-'+props.query.id+'-'+props.hit.id;
  }

  constructor($svgContainer, props) {
    this._zoom_scale_by = 1.4;
    this._padding_x = 12;
    this._padding_y = 50;

    this._canvas_height = $svgContainer.height();
    this._canvas_width = $svgContainer.width();

    this._results = Helpers.get_seq_type(props.algorithm);
    this._query_id = props.query.id;
    this._subject_id = props.hit.id;
    this._query_length = props.query.length;
    this._subject_length = props.hit.length;
    // this._hsps = this.toKablammo(props.hit.hsps, props.query);
    this._hsps = props.hit.hsps;
    this._maxBitScore = props.query.hits[0].hsps[0].bit_score;

    this.svgContainer_d3 = d3.select($svgContainer[0]);
    this._svg = {};

    this._svg.jq = $(this._svg.raw);

    this._scales = this._create_scales();
    this._axis_ticks = 10;

    this.use_complement_coords = false;
    this.polygon_highlighting($svgContainer);
    this._initiate();
  }

  polygon_highlighting(svgContainer) {
    // Disable hover handlers and show alignment on selecting hsp.
    $('polygon').hover(
        function() {
            var gnode = $(this).parent();
            $(this).parent().parent().append(gnode);
        }
    )
  }

  _initiate() {
      this._svg.d3 =
          this.svgContainer_d3.insert('svg', ':first-child')
          .attr('height', this._canvas_height)
          .attr('width', this._canvas_width);
    this._svg.raw = this._svg.d3[0][0];
    this._render_graph();
  }

  _rotate_axis_labels(text, text_anchor, dx, dy) {
    text.style('text-anchor', text_anchor)
        .attr('x', dx)
        .attr('y', dy)
        // When axis orientation is "bottom", d3 automataically applies a 0.71em
        // dy offset to labels. As Inkscape does not seem to properly interpret
        // such values, force them to be zero. When calling this function, then,
        // you must compensate by adding 0.71em worth of offset to the dy value
        // you provide.
        .attr('dx', 0)
        .attr('dy', 0)
        .attr('transform', 'rotate(-90)');
  }

  _create_axis(scale, orientation, height, text_anchor, dx, dy, seq_type) {
    var formatter = Helpers.tick_formatter(scale, seq_type);
    var tvalues = scale.ticks();
    tvalues.pop();
    var axis = d3.svg.axis()
                 .ticks(this._axis_ticks)
                 .scale(scale)
                 .tickValues(tvalues.concat(scale.domain()))
                 .tickFormat(formatter)
                 .orient(orientation);

    var container = this._svg.d3.append('g')
       .attr('class', 'axis')
       .attr('transform', 'translate(0,' + height + ')')
       .call(axis);
    this._rotate_axis_labels(container.selectAll('text'), text_anchor, dx, dy);
    return container;
  }

  _is_domain_within_orig(original_domain, new_domain) {
    return original_domain[0] <= new_domain[0] && original_domain[1] >= new_domain[1];
  }

  _zoom_scale(scale, original_domain, zoom_from, scale_by) {
    var l = scale.domain()[0];
    var r = scale.domain()[1];

    l = zoom_from - (zoom_from - l) / scale_by;
    r = zoom_from + (r - zoom_from) / scale_by;

    l = Math.round(l);
    r = Math.round(r);
    if(r - l < this._axis_ticks)
      return;

    var new_domain = [l, r];
    if(this._is_domain_within_orig(original_domain, new_domain))
      scale.domain(new_domain);
    else
      scale.domain(original_domain);
  }

  _pan_scale(existing_scale, original_domain, delta) {
    var scale = (existing_scale.domain()[1] - existing_scale.domain()[0]) / (existing_scale.range()[1] - existing_scale.range()[0]);
    var scaled_delta = -delta * scale;

    var domain = existing_scale.domain();
    var l = domain[0] + scaled_delta;
    var r = domain[1] + scaled_delta;
    var new_domain = [l, r];

    if(this._is_domain_within_orig(original_domain, new_domain))
      existing_scale.domain(new_domain);
  }

  _render_polygons() {
    var self = this;

    // Remove all existing child elements.
    this._svg.d3.selectAll('*').remove();

    this._polygons = this._svg.d3.selectAll('polygon')
       .data(this._hsps)
       .enter()
       .append('g')
       .attr('class','polygon')

    this._polygons.append('polygon')
       .attr('class', 'hit')
       .attr('fill', function(hsp) {
         return self.determine_colour(hsp.bit_score / self._maxBitScore);
       }).attr('points', function(hsp) {
         // We create query_x_points such that the 0th element will *always* be
         // on the left of the 1st element, regardless of whether the axis is
         // drawn normally (i.e., ltr) or reversed (i.e., rtl). We do the same
         // for subject_x_points. As our parsing code guarantees start < end, we
         // decide on this ordering based on the reading frame, because it
         // determines whether our axis will be reversed or not.<commit_msg>Graphical HSP overview - don't let weaker HSP hide stronger ones ...<commit_after>import d3 from 'd3';
import _ from 'underscore';
import Grapher from './grapher';
import * as Helpers from './visualisation_helpers';

/**
 * Renders Kablammo visualization
 *
 * JSON received from server side is modified as JSON expected by kablammo's
 * graph.js.  All the relevant information including a SVG container where
 * visual needs to be rendered, is delegated to graph.js. graph.js renders
 * kablammo visualization and has all event handlers for events performed on
 * the visual.
 *
 * Event handlers related to downloading and viewing of alignments and images
 * have been extracted from grapher.js and interface.js and directly included
 * here.
 */

class Graph {
  static name() {
    return 'Graphical overview of aligning region(s)';
  }

  static className() {
    return 'kablammo';
  }

  static collapseId(props) {
    return 'kablammo_'+props.query.number+'_'+props.hit.number;
  }

  static dataName(props) {
      return 'Kablammo-'+props.query.id+'-'+props.hit.id;
  }

  constructor($svgContainer, props) {
    this._zoom_scale_by = 1.4;
    this._padding_x = 12;
    this._padding_y = 50;

    this._canvas_height = $svgContainer.height();
    this._canvas_width = $svgContainer.width();

    this._results = Helpers.get_seq_type(props.algorithm);
    this._query_id = props.query.id;
    this._subject_id = props.hit.id;
    this._query_length = props.query.length;
    this._subject_length = props.hit.length;
    // this._hsps = this.toKablammo(props.hit.hsps, props.query);
    this._hsps = props.hit.hsps;
    this._maxBitScore = props.query.hits[0].hsps[0].bit_score;

    this.svgContainer_d3 = d3.select($svgContainer[0]);
    this._svg = {};

    this._svg.jq = $(this._svg.raw);

    this._scales = this._create_scales();
    this.use_complement_coords = false;
    this._axis_ticks = 10;

    this._initiate();
    this.bindHoverHandler($svgContainer);
  }

  bindHoverHandler ($svgContainer) {
      // Raise polygon on hover.
      $svgContainer.find('polygon').hover(
          function () {
              var $g = $(this).parent();
              $g.parent().append($g);
          }
      );
  }

  _initiate() {
      this._svg.d3 =
          this.svgContainer_d3.insert('svg', ':first-child')
          .attr('height', this._canvas_height)
          .attr('width', this._canvas_width);
    this._svg.raw = this._svg.d3[0][0];
    this._render_graph();
  }

  _rotate_axis_labels(text, text_anchor, dx, dy) {
    text.style('text-anchor', text_anchor)
        .attr('x', dx)
        .attr('y', dy)
        // When axis orientation is "bottom", d3 automataically applies a 0.71em
        // dy offset to labels. As Inkscape does not seem to properly interpret
        // such values, force them to be zero. When calling this function, then,
        // you must compensate by adding 0.71em worth of offset to the dy value
        // you provide.
        .attr('dx', 0)
        .attr('dy', 0)
        .attr('transform', 'rotate(-90)');
  }

  _create_axis(scale, orientation, height, text_anchor, dx, dy, seq_type) {
    var formatter = Helpers.tick_formatter(scale, seq_type);
    var tvalues = scale.ticks();
    tvalues.pop();
    var axis = d3.svg.axis()
                 .ticks(this._axis_ticks)
                 .scale(scale)
                 .tickValues(tvalues.concat(scale.domain()))
                 .tickFormat(formatter)
                 .orient(orientation);

    var container = this._svg.d3.append('g')
       .attr('class', 'axis')
       .attr('transform', 'translate(0,' + height + ')')
       .call(axis);
    this._rotate_axis_labels(container.selectAll('text'), text_anchor, dx, dy);
    return container;
  }

  _is_domain_within_orig(original_domain, new_domain) {
    return original_domain[0] <= new_domain[0] && original_domain[1] >= new_domain[1];
  }

  _zoom_scale(scale, original_domain, zoom_from, scale_by) {
    var l = scale.domain()[0];
    var r = scale.domain()[1];

    l = zoom_from - (zoom_from - l) / scale_by;
    r = zoom_from + (r - zoom_from) / scale_by;

    l = Math.round(l);
    r = Math.round(r);
    if(r - l < this._axis_ticks)
      return;

    var new_domain = [l, r];
    if(this._is_domain_within_orig(original_domain, new_domain))
      scale.domain(new_domain);
    else
      scale.domain(original_domain);
  }

  _pan_scale(existing_scale, original_domain, delta) {
    var scale = (existing_scale.domain()[1] - existing_scale.domain()[0]) / (existing_scale.range()[1] - existing_scale.range()[0]);
    var scaled_delta = -delta * scale;

    var domain = existing_scale.domain();
    var l = domain[0] + scaled_delta;
    var r = domain[1] + scaled_delta;
    var new_domain = [l, r];

    if(this._is_domain_within_orig(original_domain, new_domain))
      existing_scale.domain(new_domain);
  }

  _render_polygons() {
    var self = this;

    // Remove all existing child elements.
    this._svg.d3.selectAll('*').remove();

    this._polygons = this._svg.d3.selectAll('polygon')
       .data(this._hsps.slice().reverse())
       .enter()
       .append('g')
       .attr('class','polygon')

    this._polygons.append('polygon')
       .attr('class', 'hit')
       .attr('fill', function(hsp) {
         return self.determine_colour(hsp.bit_score / self._maxBitScore);
       }).attr('points', function(hsp) {
         // We create query_x_points such that the 0th element will *always* be
         // on the left of the 1st element, regardless of whether the axis is
         // drawn normally (i.e., ltr) or reversed (i.e., rtl). We do the same
         // for subject_x_points. As our parsing code guarantees start < end, we
         // decide on this ordering based on the reading frame, because it
         // determines whether our axis will be reversed or not.
{'fraction_non_alphanumeric': 0.13187660668380463, 'fraction_numerical': 0.00856898029134533, 'mean_word_length': 2.8078303425774878, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5409828', 'n_tokens_mistral': 3822, 'n_tokens_neox': 3675, 'n_words': 901}