<commit_before>const React = require('react');
const h = require('react-hyperscript');
import { isEqual } from 'lodash';

import Point from './Point';
import { autoScale } from '../utils/mapping';
import styles from './ScatterPlot.css';

const RADIUS = 10;  // for now

/**
 * Renders a single scatter plot on a parent svg g
 * props:
 *   dataset: miso dataset
 *   m:
 *   n:
 *   xName
 *   yName
 *   plotKey
 *   sideLength
 */
export default class ScatterPlot extends React.Component {

  constructor(props) {
    super(props);
    this._calcState();
  }

  _calcState() {
    let sideLength = this.props.sideLength;

    let ds = this.props.dataset;
    let xCol = ds.column(this.props.xName);
    let yCol = ds.column(this.props.yName);
    let xValues = xCol.data;
    let yValues = yCol.data;

    let xScale = autoScale(xValues, [0, sideLength]);
    let yScale = autoScale(yValues, [0, sideLength]);

    this.state = {
      sideLength,
      points: xValues.map((v, i) => {
        return {
          x: xScale(v),
          y: yScale(yValues[i])
        };
      })
    };
  }

  componentWillReceiveProps(nextProps) {
    // Does not happen on initial.
    // Calc what you need put it in state.
    let needsUpdate = (this.props.dataset !== nextProps.dataset) ||
      (this.props.sideLength !== nextProps.sideLength);
    if (needsUpdate) {
      this._calcState();
    }
  }

  // shouldComponentUpdate(nextProps, nextState) {
  //   console.log('should update ?');
  //   return true;
  // }

  render() {
    // draw a minimal L-shaped axis
    const axis = h('polyline', {
      points: `0,0 0,${this.state.sideLength} ${this.state.sideLength},${this.state.sideLength}`,
      strokeWidth: 1,
      stroke: '#888888',  // need access to theme here
      // className: 'axis',
      fill: 'none'
    });

    const bg = h('rect', {
      height: this.state.sideLength,
      width: this.state.sideLength,
      x: 0,
      y: 0,
      className: styles.bg,

      onMouseMove: (e) => {
        if (!e.metaKey) {
          if (e.buttons) {
            this._brush(e.clientX, e.clientY);
          } else {
            // only needs to send it once to clear it
            this._hover(e.clientX, e.clientY);
          }
        }
      },

      onMouseDown: (e) => {
        if (e.buttons) {
          if (e.metaKey) {
            // toggle loop mode
            this.props.toggleLoopMode(this.props.m, this.props.n);
          } else {
            this._brush(e.clientX, e.clientY);
          }
        }
      },

      onMouseUp: (e) => {
        this._hover(e.clientX, e.clientY);
      }
    });

    const radius = this.state.sideLength < 100 ? 1 : 3;
    const points = h('g', this.state.points.map((p, i) => {
      return h(Point, {
        x: p.x,
        y: p.y,
        radius,
        color: '#0000ff',  // get from theme
        id: String(i),
        className: 'point'
      });
    }));

    const children = [axis, bg, points];

    return h('g', {
      transform: `translate(${this.props.xOffset}, ${this.props.yOffset})`,
      width: this.props.sideLength,
      height: this.props.sideLength,
      className: styles.scatterplot
    }, children);
  }

  /**
   * get points that are under brush
   * by filtering through this.state.points<commit_msg>trying to improve mouse event handling on ScatterPlot; still sucky<commit_after>const React = require('react');
const h = require('react-hyperscript');
import { isEqual } from 'lodash';

import Point from './Point';
import { autoScale } from '../utils/mapping';
import styles from './ScatterPlot.css';

const RADIUS = 10;  // for now

/**
 * Renders a single scatter plot on a parent svg g
 * props:
 *   dataset: miso dataset
 *   m:
 *   n:
 *   xName
 *   yName
 *   plotKey
 *   sideLength
 */
export default class ScatterPlot extends React.Component {

  constructor(props) {
    super(props);
    this._calcState();
  }

  _calcState() {
    let sideLength = this.props.sideLength;

    let ds = this.props.dataset;
    let xCol = ds.column(this.props.xName);
    let yCol = ds.column(this.props.yName);
    let xValues = xCol.data;
    let yValues = yCol.data;

    let xScale = autoScale(xValues, [0, sideLength]);
    let yScale = autoScale(yValues, [0, sideLength]);

    this.state = {
      sideLength,
      points: xValues.map((v, i) => {
        return {
          x: xScale(v),
          y: yScale(yValues[i])
        };
      })
    };
  }

  componentWillReceiveProps(nextProps) {
    // Does not happen on initial.
    // Calc what you need put it in state.
    let needsUpdate = (this.props.dataset !== nextProps.dataset) ||
      (this.props.sideLength !== nextProps.sideLength);
    if (needsUpdate) {
      this._calcState();
    }
  }

  // shouldComponentUpdate(nextProps, nextState) {
  //   console.log('should update ?');
  //   return true;
  // }

  render() {
    // draw a minimal L-shaped axis
    const axis = h('polyline', {
      points: `0,0 0,${this.state.sideLength} ${this.state.sideLength},${this.state.sideLength}`,
      strokeWidth: 1,
      stroke: '#888888',  // need access to theme here
      // className: 'axis',
      fill: 'none'
    });

    const bg = h('rect', {
      height: this.state.sideLength,
      width: this.state.sideLength,
      x: 0,
      y: 0,
      className: styles.bg,

      // onClick: (e) => {
      //   if (e.buttons) {
      //     if (e.metaKey) {
      //       // toggle loop mode
      //       this.props.toggleLoopMode(this.props.m, this.props.n);
      //     } else {
      //       this._brush(e.clientX, e.clientY);
      //     }
      //   }
      // },

      onMouseDown: (e) => {
        if (e.buttons) {
          if (e.metaKey) {
            // toggle loop mode
            this.props.toggleLoopMode(this.props.m, this.props.n);
          } else {
            this._brush(e.clientX, e.clientY);
          }
        }
      },

      onMouseMove: (e) => {
        if (!e.metaKey) {
          if (e.buttons) {
            this._brush(e.clientX, e.clientY);
          } else {
            // only needs to send it once to clear it
            this._hover(e.clientX, e.clientY);
          }
        }
      },

      onMouseUp: (e) => {
        if (!e.metaKey) {
          this._hover(e.clientX, e.clientY);
        }
      }
    });

    const radius = this.state.sideLength < 100 ? 1 : 3;
    const points = h('g', this.state.points.map((p, i) => {
      return h(Point, {
        x: p.x,
        y: p.y,
        radius,
        color: '#0000ff',  // get from theme
        id: String(i),
        className: 'point'
      });
    }));

    const children = [bg, axis, points];

    return h('g', {
      transform: `translate(${this.props.xOffset}, ${this.props.yOffset})`,
      width: this.props.sideLength,
      height: this.props.sideLength,
      className: styles.scatterplot
    }, children);
  }

  /**
   * get points that are under brush
   * by filtering through this.state.points
{'fraction_non_alphanumeric': 0.1481375358166189, 'fraction_numerical': 0.0071633237822349575, 'mean_word_length': 1.9368952461085402, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 14, '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': '4095345', 'n_tokens_mistral': 2347, 'n_tokens_neox': 2259, 'n_words': 534}