<commit_before>/**
 * author:  <NAME>
 * email:   <EMAIL>
 * created: 2016-10-28T11:19:52.797Z
 */

import './style.scss';
import {axisLeft, axisBottom, AxisScale} from 'd3-axis';
import * as d3scale from 'd3-scale';
import {select, mouse, event as d3event} from 'd3-selection';
import {zoom as d3zoom, ZoomScale, ZoomTransform, D3ZoomEvent, zoomIdentity} from 'd3-zoom';
import {drag as d3drag} from 'd3-drag';
import {quadtree, Quadtree, QuadtreeInternalNode, QuadtreeLeaf} from 'd3-quadtree';
import {circleSymbol, ISymbol, ISymbolRenderer, ERenderMode} from './symbol';
import * as _symbol from './symbol';
import merge from './merge';
import {findAll, forEachLeaf, isLeafNode, hasOverlap, getTreeSize, findByTester, getFirstLeaf, ABORT_TRAVERSAL, CONTINUE_TRAVERSAL, IBoundsPredicate, ITester} from './quadtree';
import Lasso from './lasso';
import {cssprefix, DEBUG, debuglog} from './constants';
import showTooltip from './tooltip';

/**
 * a d3 scale essentially
 */
export interface IScale extends AxisScale<number>, ZoomScale {
  range(range:number[]);
  range(): number[];
  domain(): number[];
  domain(domain:number[]);
  invert(v:number): number;
  copy(): IScale;
}

export interface IAccessor<T> {
  (v:T) : number;
}

/**
 * scatterplot options
 */
export interface IScatterplotOptions<T> {
  /**
   * margin for the scatterplot area
   * default (left=40, top=10, right=10, bottom=20)
   */
  margin?: {
    left?: number;
    top?: number;
    right?: number;
    bottom?: number;
  };

  /**
   * min max scaling factor
   * default: 0.1, 10
   */
  scaleExtent?: [number, number];

  /**
   * x accessor of the data
   * default: d.x
   * @param d
   */
  x?:IAccessor<T>;

  /**
   * x axis label
   * default: x
   */
  xlabel?: string;

  /**
   * y axis label
   * default: x
   */
  ylabel?: string;

  /**
   * y accessor of the data
   * default: d.y
   * @param d
   */
  y?:IAccessor<T>;

  /**
   * d3 x scale
   * default: linear scale with a domain from 0...100
   */
  xscale?:IScale;

  /**
   * d3 y scale
   * default: linear scale with a domain from 0...100
   */
  yscale?:IScale;

  /**
   * symbol used to render an data point
   * default: steelblue circle
   */
  symbol?:ISymbol<T>;

  /**
   * the radius in pixel in which a mouse click will be searched
   * default: 10
   */
  clickRadius?: number;

  /**
   * delay before a tooltip will be shown after a mouse was moved
   * default: 500
   */
  tooltipDelay?: number;

  /**
   * delay before a full redraw is shown during zooming
   */
  zoomDelay?: number;

  /**
   * shows the tooltip
   * default: simple popup similar to bootstrap
   * if `null` or `false` tooltips are disabled
   * @param parent the scatterplot html element
   * @param items items to show, empty to hide tooltip
   * @param x the x position relative to the plot
   * @param y the y position relative to the plot
   */
  showTooltip?(parent:HTMLElement, items:T[], x:number, y:number);

  /**
   * hook when the selection has changed
   * default: none
   */
  onSelectionChanged?();

  /**
   * determines whether the given mouse is a selection or panning event, if `null` or `false` selection is disabled
   * default: event.ctrlKey || event.altKey
   *
   */
  isSelectEvent?(event:MouseEvent) : boolean; //=> event.ctrlKey || event.altKey

  /**
   * interval time when the lasso is evaluated
   * default: 200
   */
  lassoInterval?: number;
}

//normalized range the quadtree is defined
const NORMALIZED_RANGE = [0, 100];

/**
 * reasons why a new render pass is needed
 */
export enum ERenderReason {
  DIRTY,
  SELECTION_CHANGED,
  ZOOMED,
  PERFORM_SCALE_AND_TRANSLATE,
  AFTER_SCALE_AND_TRANSLATE,
  PERFORM_TRANSLATE,
  AFTER_TRANSLATE,
  PERFORM_SCALE,
  AFTER_SCALE
}


/**
 * a class for rendering a scatterplot in a canvas
 */
export default class Scatterplot<T> {

  private props:IScatterplotOptions<T> = {
    margin: {
      left: 32,
      top: 10,
      bottom: 32,
      right: 10
    },
    clickRadius: 10,
    scaleExtent: [0.1, 10],
    x: (d) => (<any>d).x,
    y: (d) => (<any>d).y,

    xlabel: 'x',
    ylabel: 'y',

    xscale: <IScale>d3scale.scaleLinear().domain([0, 100]),
    yscale: <IScale>d3scale.scaleLinear().domain([0, 100]),

    symbol: <ISymbol<T>>circleSymbol(),

    tooltipDelay: 500,

    zoomDelay: 300,

    showTooltip: showTooltip,

    onSelectionChanged: ()=>undefined,

    isSelectEvent: (event:MouseEvent) => event.ctrlKey || event.altKey,

    lassoInterval: 100
  };


  private normalized2pixel = {
    x: d3scale.scaleLinear().domain(NORMALIZED_RANGE),
    y: d3scale.scaleLinear().domain(NORMALIZED_RANGE)
  };
  private canvasDataLayer:HTMLCanvasElement;
  private canvasSelectionLayer:HTMLCanvasElement;
  private tree:Quadtree<T>;
  private selectionTree:Quadtree<T>;

  /**
   * timout handle when the tooltip is shown
   * @type {number}
   */
  private showTooltipHandle = -1;

  private lasso = new Lasso();

  private currentTransform:ZoomTransform = zoomIdentity;
  private zoomStartTransform:ZoomTransform;
  private zommHandle = -1;
  private dragHandle = -1;

  constructor(data:T[], private parent:HTMLElement, props?:IScatterplotOptions<T>) {
    this.props = merge(this.props, props);

    //init dom
    parent.innerHTML = `
      <canvas class="${cssprefix}-data-layer"></canvas>
      <canvas class="${cssprefix}-selection-layer"></canvas>
      <svg class="${cssprefix}-axis-left" style="width: ${this.props.margin.left + 2}px;"><commit_msg>hide not selectionlayer if there are no selections<commit_after>/**
 * author:  <NAME>
 * email:   <EMAIL>
 * created: 2016-10-28T11:19:52.797Z
 */

import './style.scss';
import {axisLeft, axisBottom, AxisScale} from 'd3-axis';
import * as d3scale from 'd3-scale';
import {select, mouse, event as d3event} from 'd3-selection';
import {zoom as d3zoom, ZoomScale, ZoomTransform, D3ZoomEvent, zoomIdentity} from 'd3-zoom';
import {drag as d3drag} from 'd3-drag';
import {quadtree, Quadtree, QuadtreeInternalNode, QuadtreeLeaf} from 'd3-quadtree';
import {circleSymbol, ISymbol, ISymbolRenderer, ERenderMode} from './symbol';
import * as _symbol from './symbol';
import merge from './merge';
import {findAll, forEachLeaf, isLeafNode, hasOverlap, getTreeSize, findByTester, getFirstLeaf, ABORT_TRAVERSAL, CONTINUE_TRAVERSAL, IBoundsPredicate, ITester} from './quadtree';
import Lasso from './lasso';
import {cssprefix, DEBUG, debuglog} from './constants';
import showTooltip from './tooltip';

/**
 * a d3 scale essentially
 */
export interface IScale extends AxisScale<number>, ZoomScale {
  range(range:number[]);
  range(): number[];
  domain(): number[];
  domain(domain:number[]);
  invert(v:number): number;
  copy(): IScale;
}

export interface IAccessor<T> {
  (v:T) : number;
}

/**
 * scatterplot options
 */
export interface IScatterplotOptions<T> {
  /**
   * margin for the scatterplot area
   * default (left=40, top=10, right=10, bottom=20)
   */
  margin?: {
    left?: number;
    top?: number;
    right?: number;
    bottom?: number;
  };

  /**
   * min max scaling factor
   * default: 0.1, 10
   */
  scaleExtent?: [number, number];

  /**
   * x accessor of the data
   * default: d.x
   * @param d
   */
  x?:IAccessor<T>;

  /**
   * x axis label
   * default: x
   */
  xlabel?: string;

  /**
   * y axis label
   * default: x
   */
  ylabel?: string;

  /**
   * y accessor of the data
   * default: d.y
   * @param d
   */
  y?:IAccessor<T>;

  /**
   * d3 x scale
   * default: linear scale with a domain from 0...100
   */
  xscale?:IScale;

  /**
   * d3 y scale
   * default: linear scale with a domain from 0...100
   */
  yscale?:IScale;

  /**
   * symbol used to render an data point
   * default: steelblue circle
   */
  symbol?:ISymbol<T>;

  /**
   * the radius in pixel in which a mouse click will be searched
   * default: 10
   */
  clickRadius?: number;

  /**
   * delay before a tooltip will be shown after a mouse was moved
   * default: 500
   */
  tooltipDelay?: number;

  /**
   * delay before a full redraw is shown during zooming
   */
  zoomDelay?: number;

  /**
   * shows the tooltip
   * default: simple popup similar to bootstrap
   * if `null` or `false` tooltips are disabled
   * @param parent the scatterplot html element
   * @param items items to show, empty to hide tooltip
   * @param x the x position relative to the plot
   * @param y the y position relative to the plot
   */
  showTooltip?(parent:HTMLElement, items:T[], x:number, y:number);

  /**
   * hook when the selection has changed
   * default: none
   */
  onSelectionChanged?();

  /**
   * determines whether the given mouse is a selection or panning event, if `null` or `false` selection is disabled
   * default: event.ctrlKey || event.altKey
   *
   */
  isSelectEvent?(event:MouseEvent) : boolean; //=> event.ctrlKey || event.altKey

  /**
   * interval time when the lasso is evaluated
   * default: 200
   */
  lassoInterval?: number;
}

//normalized range the quadtree is defined
const NORMALIZED_RANGE = [0, 100];

/**
 * reasons why a new render pass is needed
 */
export enum ERenderReason {
  DIRTY,
  SELECTION_CHANGED,
  ZOOMED,
  PERFORM_SCALE_AND_TRANSLATE,
  AFTER_SCALE_AND_TRANSLATE,
  PERFORM_TRANSLATE,
  AFTER_TRANSLATE,
  PERFORM_SCALE,
  AFTER_SCALE
}


/**
 * a class for rendering a scatterplot in a canvas
 */
export default class Scatterplot<T> {

  private props:IScatterplotOptions<T> = {
    margin: {
      left: 32,
      top: 10,
      bottom: 32,
      right: 10
    },
    clickRadius: 10,
    scaleExtent: [0.1, 10],
    x: (d) => (<any>d).x,
    y: (d) => (<any>d).y,

    xlabel: 'x',
    ylabel: 'y',

    xscale: <IScale>d3scale.scaleLinear().domain([0, 100]),
    yscale: <IScale>d3scale.scaleLinear().domain([0, 100]),

    symbol: <ISymbol<T>>circleSymbol(),

    tooltipDelay: 500,

    zoomDelay: 300,

    showTooltip: showTooltip,

    onSelectionChanged: ()=>undefined,

    isSelectEvent: (event:MouseEvent) => event.ctrlKey || event.altKey,

    lassoInterval: 100
  };


  private normalized2pixel = {
    x: d3scale.scaleLinear().domain(NORMALIZED_RANGE),
    y: d3scale.scaleLinear().domain(NORMALIZED_RANGE)
  };
  private canvasDataLayer:HTMLCanvasElement;
  private canvasSelectionLayer:HTMLCanvasElement;
  private tree:Quadtree<T>;
  private selectionTree:Quadtree<T>;

  /**
   * timout handle when the tooltip is shown
   * @type {number}
   */
  private showTooltipHandle = -1;

  private lasso = new Lasso();

  private currentTransform:ZoomTransform = zoomIdentity;
  private zoomStartTransform:ZoomTransform;
  private zommHandle = -1;
  private dragHandle = -1;

  constructor(data:T[], private parent:HTMLElement, props?:IScatterplotOptions<T>) {
    this.props = merge(this.props, props);

    //init dom
    parent.innerHTML = `
      <canvas class="${cssprefix}-data-layer"></canvas>
      <canvas class="${cssprefix}-selection-layer" ${!this.isSelectAble() ? 'style="visibility: hidden"': ''}></canvas>
      <svg class="${cssprefix}-axis-left" style="width: ${this.props.margin.left + 2}px;">
{'fraction_non_alphanumeric': 0.1346257889990983, 'fraction_numerical': 0.018575293056807936, 'mean_word_length': 3.3425998433829287, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 61, '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': '858959', 'n_tokens_mistral': 3861, 'n_tokens_neox': 3682, 'n_words': 1040}