<commit_before>import React, { PropTypes } from 'react';
import * as d3 from 'd3';

export default class Punchcard extends React.Component {

  // property validation
  static propTypes = {
    data: PropTypes.array.isRequired,
    onItemClick: PropTypes.func,
    loading: PropTypes.bool,
    noDataAvailableMsg: PropTypes.string,
    punchcardOptions: PropTypes.object,
    selectAccessor: PropTypes.func,
    textValueFormatter: PropTypes.func,
    headerMargin: PropTypes.number
  };

  static defaultProps = {
    data: [],
    onItemClick: null,
    loading: true,
    noDataAvailableMsg: 'No commodities data available for this canal in the selected year.',
    radiusExtent: [2, 10],
    textValueFormatter: d => d,
    headerMargin: 0
  };

  constructor(props) {
    super(props);
  }

  componentWillMount() {}

  componentDidMount() {
    this.renderVisualization();
  }

  componentDidUpdate() {
    // Blow away what's there. If we want pretty transitions,
    // remove this and handle transitions in d3Punchcard.
    d3Punchcard.destroy(this.refs.content);

    this.renderVisualization();
  }

  componentWillUnmount() {
    d3Punchcard.destroy(this.refs.content);
  }

  render() {
    return (
      <div className='panorama punchcard'>
        { this.renderPlaceholder() }
        <div className='content' ref='content' style={ { marginTop: this.props.headerMargin + 'px' } }></div>
      </div>
    );
  }

  renderVisualization() {
    if (!this.isEmpty(this.props.data) && this.refs.content) {
      d3Punchcard.update(this.refs.content, {...this.props});
    }
  }

  renderPlaceholder() {
    let {loading, noDataAvailableMsg} = this.props;

    // TODO: provide links to years with data, if they exist for this canal.
    if (loading) {
      return (
        <div className='placeholder' ref='placeholder'>
          <h4>Loading...</h4>
        </div>
      );
    }else if (!this.props.loading && this.isEmpty(this.props.data)) {
      return (
        <div className='placeholder' ref='placeholder'>
          <h4>{noDataAvailableMsg}</h4>
        </div>
      );
    } else {
      return null;
    }
  }

  isEmpty(val) {
    return val.length < 1;
  }
}


const d3Punchcard = {

  _setTextValueFormatter: d => d,
  setTextValueFormatter: function(fn) {
    this._setTextValueFormatter = fn;
  },

  // layout constants
  ROW_HEIGHT: 25,
  COMMODITY_TEXT_OFFSET_Y: 5,

  /**
   * Logic for updating d3 component with new data.
   *
   * @param  {Node}    HTMLElement to which d3 will attach
   * @param  {Object}  Categorized map of items (TODO: document expected format)
   * @param  {Object}  Flat map of items (TODO: document expected format)
   */
  update: function (node, props) {
    let {colorScale, valueAccessor, onItemClick, textValueFormatter, selected, selectAccessor} = props;

    if (typeof textValueFormatter === 'function') this.setTextValueFormatter(textValueFormatter);

    const MAX_RAD = props.radiusExtent[1];
    const scope = this;

      // scale by normalizedValue of all items
    const rScale = d3.scale.sqrt()
      .range(props.radiusExtent)
      .domain([1, props.radiusMaxValue]);

    const rScaleDomain = rScale.domain();
    const rDomainMid = rScaleDomain[0] + Math.sqrt(0.25) * (rScaleDomain[1] - rScaleDomain[0]);

    // <div> for each category
    let categoryNodes = d3.select(node)
      .selectAll('div')
      .data(props.data)
      .enter().append('div')
      .attr('style', (d) => `color: ${ colorScale(props.colorAccessor(d)) };`)
      .attr('class', 'category');

    // each with a heading...
    categoryNodes
      .append('h4')
      .text((d) => d.name);

    // ...and an <svg>
    categoryNodes = categoryNodes
      .append('svg')
      .attr('height', (d) => d.commodities.length * scope.ROW_HEIGHT)
      .style('stroke', (d) => colorScale(props.colorAccessor(d)))
      .style('fill', (d) => colorScale(props.colorAccessor(d)));

    // width of each category svg, minus padding for scrollbar,
    // with cross-browser support.
    this.categoryNodeWidth = (categoryNodes.node().offsetWidth || categoryNodes.node().getBoundingClientRect().width) - 2.5 * this.ROW_HEIGHT;

    // <g> for each item within each category<commit_msg>Fix SVG element offsetWidth warning in Chrome<commit_after>import React, { PropTypes } from 'react';
import * as d3 from 'd3';

export default class Punchcard extends React.Component {

  // property validation
  static propTypes = {
    data: PropTypes.array.isRequired,
    onItemClick: PropTypes.func,
    loading: PropTypes.bool,
    noDataAvailableMsg: PropTypes.string,
    punchcardOptions: PropTypes.object,
    selectAccessor: PropTypes.func,
    textValueFormatter: PropTypes.func,
    headerMargin: PropTypes.number
  };

  static defaultProps = {
    data: [],
    onItemClick: null,
    loading: true,
    noDataAvailableMsg: 'No commodities data available for this canal in the selected year.',
    radiusExtent: [2, 10],
    textValueFormatter: d => d,
    headerMargin: 0
  };

  constructor(props) {
    super(props);
  }

  componentWillMount() {}

  componentDidMount() {
    this.renderVisualization();
  }

  componentDidUpdate() {
    // Blow away what's there. If we want pretty transitions,
    // remove this and handle transitions in d3Punchcard.
    d3Punchcard.destroy(this.refs.content);

    this.renderVisualization();
  }

  componentWillUnmount() {
    d3Punchcard.destroy(this.refs.content);
  }

  render() {
    return (
      <div className='panorama punchcard'>
        { this.renderPlaceholder() }
        <div className='content' ref='content' style={ { marginTop: this.props.headerMargin + 'px' } }></div>
      </div>
    );
  }

  renderVisualization() {
    if (!this.isEmpty(this.props.data) && this.refs.content) {
      d3Punchcard.update(this.refs.content, {...this.props});
    }
  }

  renderPlaceholder() {
    let {loading, noDataAvailableMsg} = this.props;

    // TODO: provide links to years with data, if they exist for this canal.
    if (loading) {
      return (
        <div className='placeholder' ref='placeholder'>
          <h4>Loading...</h4>
        </div>
      );
    }else if (!this.props.loading && this.isEmpty(this.props.data)) {
      return (
        <div className='placeholder' ref='placeholder'>
          <h4>{noDataAvailableMsg}</h4>
        </div>
      );
    } else {
      return null;
    }
  }

  isEmpty(val) {
    return val.length < 1;
  }
}


const d3Punchcard = {

  _setTextValueFormatter: d => d,
  setTextValueFormatter: function(fn) {
    this._setTextValueFormatter = fn;
  },

  // layout constants
  ROW_HEIGHT: 25,
  COMMODITY_TEXT_OFFSET_Y: 5,

  /**
   * Logic for updating d3 component with new data.
   *
   * @param  {Node}    HTMLElement to which d3 will attach
   * @param  {Object}  Categorized map of items (TODO: document expected format)
   * @param  {Object}  Flat map of items (TODO: document expected format)
   */
  update: function (node, props) {
    let {colorScale, valueAccessor, onItemClick, textValueFormatter, selected, selectAccessor} = props;

    if (typeof textValueFormatter === 'function') this.setTextValueFormatter(textValueFormatter);

    const MAX_RAD = props.radiusExtent[1];
    const scope = this;

      // scale by normalizedValue of all items
    const rScale = d3.scale.sqrt()
      .range(props.radiusExtent)
      .domain([1, props.radiusMaxValue]);

    const rScaleDomain = rScale.domain();
    const rDomainMid = rScaleDomain[0] + Math.sqrt(0.25) * (rScaleDomain[1] - rScaleDomain[0]);

    // <div> for each category
    let categoryNodes = d3.select(node)
      .selectAll('div')
      .data(props.data)
      .enter().append('div')
      .attr('style', (d) => `color: ${ colorScale(props.colorAccessor(d)) };`)
      .attr('class', 'category');

    // each with a heading...
    categoryNodes
      .append('h4')
      .text((d) => d.name);

    // ...and an <svg>
    categoryNodes = categoryNodes
      .append('svg')
      .attr('height', (d) => d.commodities.length * scope.ROW_HEIGHT)
      .style('stroke', (d) => colorScale(props.colorAccessor(d)))
      .style('fill', (d) => colorScale(props.colorAccessor(d)));

    // width of each category svg, minus padding for scrollbar,
    // with cross-browser support.
    let boundsRect = categoryNodes.node().getBoundingClientRect(),
      width = boundsRect ? boundsRect.width : categoryNodes.node().offsetWidth;
    this.categoryNodeWidth = (width || 0) - 2.5 * this.ROW_HEIGHT;

    // <g> for each item within each category
{'fraction_non_alphanumeric': 0.13395675043834016, 'fraction_numerical': 0.008065458796025715, 'mean_word_length': 2.975836431226766, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 47, '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': '5359420', 'n_tokens_mistral': 2690, 'n_tokens_neox': 2624, 'n_words': 636}