<commit_before>import React, { PropTypes } from 'react';
import classNames from 'classnames';
import d3Scale from 'd3-scale';
import { bindAll, identity, map } from 'lodash';

import Track from './track';
import Fill from './fill';
import Handle from './handle';

import style from './style.css';

const propTypes = {
  /** Height and width of Slider component. */
  height: PropTypes.number,
  width: PropTypes.number,

  /** Extents of slider values. */
  minValue: PropTypes.number.isRequired,
  maxValue: PropTypes.number.isRequired,

  /**
   * Initial selected value.
   * If number, a single slider handle will be rendered.
   * If object with keys 'min' and 'max', two slider handles will be rendered.
   */
  value: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.shape({
      min: PropTypes.number,
      max: PropTypes.number
    })
  ]).isRequired,

  /**
   * Function applied to the selected value prior to rendering.
   * Params:
   *   value - selected value
   *
   * Returns:
   *   'string'
   *
   * Default:
   *   _.identity
   */
  labelFunc: PropTypes.func,

  /** Include fill in the track to indicate value. */
  fill: PropTypes.bool,

  /** Style for the fill color. */
  fillColor: PropTypes.string,

  /**
   * Callback function when value is changed.
   * Params:
   *   value - object with keys ['min'] and 'max'
   *   key - key of most recent value change.
   */
  onChange: PropTypes.func.isRequired
};

const defaultProps = {
  height: 24,
  width: 200,
  labelFunc: identity,
  fill: false,
  fillColor: '#ccc'
};

function getValues(value) {
  if (typeof value === 'number') {
    return { max: value };
  }
  return value;
}

export default class Slider extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      render: false,
      values: getValues(props.value),
      scale: d3Scale.scaleLinear()
        .clamp(true)
        .domain([props.minValue, props.maxValue]),
      snapTarget: {}
    };

    bindAll(this, [
      'onHandleMove',
      'onTrackClick',
      'renderHandle',
      'renderFill',
      'trackRef'
    ]);
  }

  componentDidMount() {
    this.receiveTrackWidth(this.props);
  }

  componentWillReceiveProps(newProps) {
    // If the extents or width changes, the scale and snapTarget must be recalculated.
    if ((this.props.minValue !== newProps.minValue) ||
      (this.props.maxValue !== newProps.maxValue)) {
      this.state.scale.domain([newProps.minValue, newProps.maxValue]);
      this.receiveTrackWidth(newProps);
    }

    this.setState({ values: getValues(newProps.value) });
  }

  componentDidUpdate(prevProps) {
    if (this.props.width !== prevProps.width) {
      this.receiveTrackWidth(this.props);
    }
  }

  onHandleMove(key, offset) {
    return (event) => {
      const value = this.state.scale.invert(event.pageX + offset);

      if (this.state.values[key] !== value) {
        const values = { ...this.state.values, [key]: value };

        if (values.min === undefined || values.min <= values.max) {
          this.props.onChange({ ...values }, key);
        }
      }
    };
  }

  onTrackClick(event) {
    const { values } = this.state;

    const value = this.state.scale.invert(event.snap.x);

    /* Determine which handle is closer. 'min' == true, 'max' == false */
    const comp = values.min !== undefined &&
      (Math.abs(values.min - value) < Math.abs(values.max - value) || values.min > value);

    const key = comp ? 'min' : 'max';

    if (values[key] !== value) {
      this.props.onChange({ ...values, [key]: value }, key);
    }
  }

  receiveTrackWidth(props) {
    this.setState({
      render: true,
      scale: this.state.scale.range([0, this._track.width]),
      snapTarget: { x: this._track.width / (props.maxValue - props.minValue) }
    });
  }

  trackRef(ref) {
    this._track = ref;
  }

  renderHandle() {
    const { values } = this.state;

    return map(values, (value, key) => {
      let direction = 'left';
      if (key === 'max' && 'min' in values) {
        direction = 'right';
      }

      return (
        <Handle
          key={ key }
          name={ key }
          direction={ direction }
          position={ this.state.scale(value) }
          onMove={ this.onHandleMove }
          label={ value }
          labelFunc={ this.props.labelFunc }
          snapTarget={ this.state.snapTarget }
          className={ classNames({ [style.connected]: values.min === values.max }) }
        />
      );
    });
  }

  renderFill() {
    const { values, scale } = this.state;

    return map(values, (value, key) => {
      let direction = 'left';
      if (key === 'max' && 'min' in values) {
        direction = 'right';
      }

      return (
        <Fill
          key={ key }
          direction={ direction }
          width={ scale(value) }
          fillStyle={ { backgroundColor: this.props.fillColor } }
        />
      );
    });
  }

  render() {
    const { height, width } = this.props;
    const { render } = this.state;
<commit_msg>document `getValues` function. tweak to allow an array for values. use `min` for value key if single value.<commit_after>import React, { PropTypes } from 'react';
import classNames from 'classnames';
import d3Scale from 'd3-scale';
import { bindAll, identity, map, zipObject } from 'lodash';

import Track from './track';
import Fill from './fill';
import Handle from './handle';

import style from './style.css';

const propTypes = {
  /** Height and width of Slider component. */
  height: PropTypes.number,
  width: PropTypes.number,

  /** Extents of slider values. */
  minValue: PropTypes.number.isRequired,
  maxValue: PropTypes.number.isRequired,

  /**
   * Initial selected value.
   * If number, a single slider handle will be rendered.
   * If object with keys 'min' and 'max', two slider handles will be rendered.
   */
  value: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.array,
    PropTypes.shape({
      min: PropTypes.number,
      max: PropTypes.number
    })
  ]).isRequired,

  /**
   * Function applied to the selected value prior to rendering.
   * Params:
   *   value - selected value
   *
   * Returns:
   *   'string'
   *
   * Default:
   *   _.identity
   */
  labelFunc: PropTypes.func,

  /** Include fill in the track to indicate value. */
  fill: PropTypes.bool,

  /** Style for the fill color. */
  fillColor: PropTypes.string,

  /**
   * Callback function when value is changed.
   * Params:
   *   value - object with keys ['min'] and 'max'
   *   key - key of most recent value change.
   */
  onChange: PropTypes.func.isRequired
};

const defaultProps = {
  height: 24,
  width: 200,
  labelFunc: identity,
  fill: false,
  fillColor: '#ccc'
};

/**
 * Evaluate value and return a compatible object with keys 'min' and 'max'.
 * @param value
 * @returns {Object}
 */
function getValues(value) {
  if (typeof value === 'number') {
    return { min: value };
  } else if (Array.isArray(value)) {
    return zipObject(['min', 'max'], value);
  }
  return value;
}

export default class Slider extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      render: false,
      values: getValues(props.value),
      scale: d3Scale.scaleLinear()
        .clamp(true)
        .domain([props.minValue, props.maxValue]),
      snapTarget: {}
    };

    bindAll(this, [
      'onHandleMove',
      'onTrackClick',
      'renderHandle',
      'renderFill',
      'trackRef'
    ]);
  }

  componentDidMount() {
    this.receiveTrackWidth(this.props);
  }

  componentWillReceiveProps(newProps) {
    // If the extents or width changes, the scale and snapTarget must be recalculated.
    if ((this.props.minValue !== newProps.minValue) ||
      (this.props.maxValue !== newProps.maxValue)) {
      this.state.scale.domain([newProps.minValue, newProps.maxValue]);
      this.receiveTrackWidth(newProps);
    }

    this.setState({ values: getValues(newProps.value) });
  }

  componentDidUpdate(prevProps) {
    if (this.props.width !== prevProps.width) {
      this.receiveTrackWidth(this.props);
    }
  }

  onHandleMove(key, offset) {
    return (event) => {
      const value = this.state.scale.invert(event.pageX + offset);

      if (this.state.values[key] !== value) {
        const values = { ...this.state.values, [key]: value };

        if (values.max === undefined || values.min <= values.max) {
          this.props.onChange({ ...values }, key);
        }
      }
    };
  }

  onTrackClick(event) {
    const { values } = this.state;

    const value = this.state.scale.invert(event.snap.x);

    /* Determine which handle is closer. 'min' == true, 'max' == false */
    const comp = values.min !== undefined &&
      (Math.abs(values.min - value) < Math.abs(values.max - value) || values.min > value);

    const key = comp ? 'min' : 'max';

    if (values[key] !== value) {
      this.props.onChange({ ...values, [key]: value }, key);
    }
  }

  receiveTrackWidth(props) {
    this.setState({
      render: true,
      scale: this.state.scale.range([0, this._track.width]),
      snapTarget: { x: this._track.width / (props.maxValue - props.minValue) }
    });
  }

  trackRef(ref) {
    this._track = ref;
  }

  renderHandle() {
    const { values } = this.state;

    return map(values, (value, key) => {
      const direction = key === 'min' ? 'left' : 'right';

      return (
        <Handle
          key={ key }
          name={ key }
          direction={ direction }
          position={ this.state.scale(value) }
          onMove={ this.onHandleMove }
          label={ value }
          labelFunc={ this.props.labelFunc }
          snapTarget={ this.state.snapTarget }
          className={ classNames({ [style.connected]: values.min === values.max }) }
        />
      );
    });
  }

  renderFill() {
    const { values, scale } = this.state;

    return map(values, (value, key) => {
      const direction = key === 'min' ? 'left' : 'right';

      return (
        <Fill
          key={ key }
          direction={ direction }
          width={ scale(value) }
          fillStyle={ { backgroundColor: this.props.fillColor } }
        />
      );
    });
  }

  render() {
    const { height, width } = this.props;
    const { render } = this.state;

{'fraction_non_alphanumeric': 0.14518791881130427, 'fraction_numerical': 0.0017480819656210548, 'mean_word_length': 2.4167219641672197, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 15, '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': '2986068', 'n_tokens_mistral': 3222, 'n_tokens_neox': 3164, 'n_words': 806}