<commit_before>/**
 * Created by sam on 04.11.2016.
 */

import {scale} from 'd3';
import Column, {IColumnDesc} from './Column';
import ValueColumn, {IValueColumnDesc} from './ValueColumn';
import StringColumn from './StringColumn';
import {FIRST_IS_NAN} from './missing';

export interface ICategoricalColumn {
  readonly categories: string[];
  readonly categoryLabels: string[];
  readonly categoryColors: string[];

  getCategories(row: any, index: number): string[];

  getColor(row: any, index: number): string | null;
}

export interface ICategory {
  name: string;

  /**
   * optional label of this category (the one to render)
   */
  label?: string;
  /**
   * associated value with this category
   */
  value?: any;
  /**
   * category color
   * @default next in d3 color 10 range
   */
  color?: string;
}

export interface IBaseCategoricalDesc {
  /**
   * separator to split  multi value
   * @defualt ;
   */
  separator?: string;

  categories: (string | ICategory)[];
}

export declare type ICategoricalDesc = IValueColumnDesc<string> & IBaseCategoricalDesc;

/**
 * checks whether the given column or description is a categorical column, i.e. the value is a list of categories
 * @param col
 * @returns {boolean}
 */
export function isCategoricalColumn(col: Column): col is ICategoricalColumn & Column;
export function isCategoricalColumn(col: IColumnDesc): col is ICategoricalDesc;
export function isCategoricalColumn(col: Column | IColumnDesc) {
  return (col instanceof Column && typeof (<any>col).getCategories === 'function' || (!(col instanceof Column) && (<IColumnDesc>col).type.match(/(categorical|ordinal|hierarchy)/) != null));
}

export interface ICategoricalFilter {
  filter: string[] | string | RegExp;
  filterMissing: boolean;
}

function isEqualFilter(a: ICategoricalFilter | null, b: ICategoricalFilter | null) {
  if (a === b) {
    return true;
  }
  if (a === null || b === null) {
    return false;
  }
  if (a.filterMissing !== b.filterMissing || (typeof a.filter !== typeof b.filter)) {
    return false;
  }
  if (Array.isArray(a.filter)) {
    return arrayEquals(<string[]>a.filter, <string[]>b.filter);
  }
  return String(a.filter) === String(b.filter);
}

function arrayEquals<T>(a: T[], b: T[]) {
  const al = a != null ? a.length : 0;
  const bl = b != null ? b.length : 0;
  if (al !== bl) {
    return false;
  }
  if (al === 0) {
    return true;
  }
  return a.every((ai, i) => ai === b[i]);
}

/**
 * column for categorical values
 */
export default class CategoricalColumn extends ValueColumn<string> implements ICategoricalColumn {
  /**
   * colors for each category
   * @type {Ordinal<string, string>}
   */
  private colors = scale.category10();

  /**
   * category labels by default the category name itself
   * @type {Array}
   */
  private catLabels = new Map<string, string>();

  /**
   * set of categories to show
   * @type {null}
   * @private
   */
  private currentFilter: ICategoricalFilter | null = null;

  /**
   * split multiple categories
   * @type {string}
   */
  private separator = ';';

  constructor(id: string, desc: ICategoricalDesc) {
    super(id, desc);
    this.separator = desc.separator || this.separator;
    this.initCategories(desc);
    //TODO infer categories from data

    this.setRendererList([
      {type: 'categorical', label: 'Color + Label'},
      {type: 'catcolor', label: 'Color'},
      {type: 'upset', label: 'Matrix'}
    ], [{type: 'categorical', label: 'Histogram'},
      {type: 'catcolor', label: 'Most Frequent Category Color + Label'},
      {type: 'catcolorshifted', label: 'Relative Most Frequent Category Color + Label'}]);
  }

  initCategories(desc: IBaseCategoricalDesc) {
    if (!desc.categories) {
      return;
    }
    const cats: string[] = [],
      cols = this.colors.range().slice(), //work on a copy since it will be manipulated
      labels = new Map<string, string>();
    desc.categories.forEach((cat, i) => {
      if (typeof cat === 'string') {
        //just the category value
        cats.push(cat);
        return;
      }
      //the name or value of the category
      cats.push(cat.name || cat.value);
      //optional label mapping
      if (cat.label) {
        labels.set(cat.name, cat.label);
      }
      //optional color
      if (cat.color) {
        cols[i] = cat.color;
      }
    });<commit_msg>Renaming and removing categorical column visualizations Caleydo/lineupjs#401<commit_after>/**
 * Created by sam on 04.11.2016.
 */

import {scale} from 'd3';
import Column, {IColumnDesc} from './Column';
import ValueColumn, {IValueColumnDesc} from './ValueColumn';
import StringColumn from './StringColumn';
import {FIRST_IS_NAN} from './missing';

export interface ICategoricalColumn {
  readonly categories: string[];
  readonly categoryLabels: string[];
  readonly categoryColors: string[];

  getCategories(row: any, index: number): string[];

  getColor(row: any, index: number): string | null;
}

export interface ICategory {
  name: string;

  /**
   * optional label of this category (the one to render)
   */
  label?: string;
  /**
   * associated value with this category
   */
  value?: any;
  /**
   * category color
   * @default next in d3 color 10 range
   */
  color?: string;
}

export interface IBaseCategoricalDesc {
  /**
   * separator to split  multi value
   * @defualt ;
   */
  separator?: string;

  categories: (string | ICategory)[];
}

export declare type ICategoricalDesc = IValueColumnDesc<string> & IBaseCategoricalDesc;

/**
 * checks whether the given column or description is a categorical column, i.e. the value is a list of categories
 * @param col
 * @returns {boolean}
 */
export function isCategoricalColumn(col: Column): col is ICategoricalColumn & Column;
export function isCategoricalColumn(col: IColumnDesc): col is ICategoricalDesc;
export function isCategoricalColumn(col: Column | IColumnDesc) {
  return (col instanceof Column && typeof (<any>col).getCategories === 'function' || (!(col instanceof Column) && (<IColumnDesc>col).type.match(/(categorical|ordinal|hierarchy)/) != null));
}

export interface ICategoricalFilter {
  filter: string[] | string | RegExp;
  filterMissing: boolean;
}

function isEqualFilter(a: ICategoricalFilter | null, b: ICategoricalFilter | null) {
  if (a === b) {
    return true;
  }
  if (a === null || b === null) {
    return false;
  }
  if (a.filterMissing !== b.filterMissing || (typeof a.filter !== typeof b.filter)) {
    return false;
  }
  if (Array.isArray(a.filter)) {
    return arrayEquals(<string[]>a.filter, <string[]>b.filter);
  }
  return String(a.filter) === String(b.filter);
}

function arrayEquals<T>(a: T[], b: T[]) {
  const al = a != null ? a.length : 0;
  const bl = b != null ? b.length : 0;
  if (al !== bl) {
    return false;
  }
  if (al === 0) {
    return true;
  }
  return a.every((ai, i) => ai === b[i]);
}

/**
 * column for categorical values
 */
export default class CategoricalColumn extends ValueColumn<string> implements ICategoricalColumn {
  /**
   * colors for each category
   * @type {Ordinal<string, string>}
   */
  private colors = scale.category10();

  /**
   * category labels by default the category name itself
   * @type {Array}
   */
  private catLabels = new Map<string, string>();

  /**
   * set of categories to show
   * @type {null}
   * @private
   */
  private currentFilter: ICategoricalFilter | null = null;

  /**
   * split multiple categories
   * @type {string}
   */
  private separator = ';';

  constructor(id: string, desc: ICategoricalDesc) {
    super(id, desc);
    this.separator = desc.separator || this.separator;
    this.initCategories(desc);
    //TODO infer categories from data

    this.setRendererList([
      {type: 'categorical', label: 'Color'},
      {type: 'upset', label: 'Matrix'}
    ], [{type: 'categorical', label: 'Histogram'}]);
  }

  initCategories(desc: IBaseCategoricalDesc) {
    if (!desc.categories) {
      return;
    }
    const cats: string[] = [],
      cols = this.colors.range().slice(), //work on a copy since it will be manipulated
      labels = new Map<string, string>();
    desc.categories.forEach((cat, i) => {
      if (typeof cat === 'string') {
        //just the category value
        cats.push(cat);
        return;
      }
      //the name or value of the category
      cats.push(cat.name || cat.value);
      //optional label mapping
      if (cat.label) {
        labels.set(cat.name, cat.label);
      }
      //optional color
      if (cat.color) {
        cols[i] = cat.color;
      }
    });
{'fraction_non_alphanumeric': 0.1425569176882662, 'fraction_numerical': 0.004319906596614127, 'mean_word_length': 3.2384957941613064, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 27, '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': '3603865', 'n_tokens_mistral': 2686, 'n_tokens_neox': 2615, 'n_words': 786}