<commit_before>import * as log from '../log';

import {Axis} from '../axis';
import {X, Y, X2, Y2, Channel, UNIT_CHANNELS,  UNIT_SCALE_CHANNELS, NONSPATIAL_SCALE_CHANNELS, supportMark} from '../channel';
import {defaultConfig, Config, CellConfig} from '../config';
import {SOURCE, SUMMARY} from '../data';
import {Encoding} from '../encoding';
import * as vlEncoding from '../encoding'; // TODO: remove
import {ChannelDef, FieldDef, FieldRefOption, field, normalize, isFieldDef, isValueDef} from '../fielddef';
import {Legend} from '../legend';
import {Mark, TEXT as TEXTMARK, FILL_STROKE_CONFIG} from '../mark';
import {Scale, ScaleConfig, hasDiscreteDomain} from '../scale';
import {ExtendedUnitSpec} from '../spec';
import {duplicate, extend, isArray, mergeDeep, Dict} from '../util';
import {VgData} from '../vega.schema';

import {parseAxisComponent} from './axis/parse';
import {applyConfig} from './common';
import {initMarkConfig, initTextConfig} from './config';
import {assembleData, parseUnitData} from './data/data';
import {parseLegendComponent} from './legend/parse';
import {assembleLayout, parseUnitLayout} from './layout';
import {Model} from './model';
import {parseMark} from './mark/mark';
import initScale from './scale/init';
import parseScaleComponent from './scale/parse';
import {stack, StackProperties} from '../stack';

/**
 * Internal model of Vega-Lite specification for the compiler.
 */
export class UnitModel extends Model {
  /**
   * Fixed width for the unit visualization.
   * If undefined (e.g., for ordinal scale), the width of the
   * visualization will be calculated dynamically.
   */
  private _width: number;

  /**
   * Fixed height for the unit visualization.
   * If undefined (e.g., for ordinal scale), the height of the
   * visualization will be calculated dynamically.
   */
  private _height: number;

  private readonly _mark: Mark;
  private readonly _encoding: Encoding;
  private readonly _stack: StackProperties;

  constructor(spec: ExtendedUnitSpec, parent: Model, parentGivenName: string) {
    super(spec, parent, parentGivenName);

    // use top-level width / height or parent's top-level width / height

    // FIXME: once facet supports width/height, this is no longer correct!
    const providedWidth = spec.width !== undefined ? spec.width :
      parent ? parent['width'] : undefined; // only exists if parent is layer
    const providedHeight = spec.height !== undefined ? spec.height :
      parent ? parent['height'] : undefined; // only exists if parent is layer

    const mark = this._mark = spec.mark;
    const encoding = this._encoding = this._initEncoding(mark, spec.encoding || {});

    // TODO?: ideally we should use config only inside this constructor
    const config = this._config = this._initConfig(spec.config, parent);

    // FIXME move stacked out of config as it's not really a theme.
    // calculate stack properties
    this._stack = stack(mark, encoding, config.mark.stacked);
    this._scale =  this._initScale(mark, encoding, config, providedWidth, providedHeight);

    // TODO?: refactor these to be a part of the model as they are not really just config
    config.mark = initMarkConfig(mark, encoding, this._scale, this._stack, config);
    if (mark === 'text') { // FIXME: maybe we should refactor this
      config.text = initTextConfig(encoding, config);
    }

    this._axis = this._initAxis(encoding, config);
    this._legend = this._initLegend(encoding, config);

    // width / height
    this._initSize(mark, this._scale,
      providedWidth,
      providedHeight,
      config.cell, config.scale
    );
  }

  private _initEncoding(mark: Mark, encoding: Encoding) {
    // clone to prevent side effect to the original spec
    encoding = duplicate(encoding);

    Object.keys(encoding).forEach((channel: any) => {
      if (!supportMark(channel, mark)) {
        // Drop unsupported channel

        log.warn(log.message.incompatibleChannel(channel, mark));
        delete encoding[channel];
        return;
      }

      if (isArray(encoding[channel])) {
        // Array of fieldDefs for detail channel (or production rule)
        encoding[channel] = encoding[channel].reduce((channelDefs: ChannelDef[], channelDef: ChannelDef) => {
          if (!isFieldDef(channelDef) && !isValueDef(channelDef)) { // TODO: datum
            log.warn(log.message.emptyFieldDef(channelDef, channel));
          } else {
            channelDefs.push(normalize(channelDef, channel));
          }
          return channelDefs;
        }, []);
      } else {
        const fieldDef = encoding[channel];
        if (fieldDef.field === undefined && fieldDef.value === undefined) { // TODO: datum
          log.warn(log.message.emptyFieldDef(fieldDef, channel));
          delete encoding[channel];
          return;
        }
        normalize(fieldDef, channel);
      }
    });
    return encoding;
  }

  /**
   * Init config by merging config from parent and, if applicable, from facet config
   */
  private _initConfig(specConfig: Config, parent: Model) {
    let config = mergeDeep(duplicate(defaultConfig), parent ? parent.config() : {}, specConfig);
    let hasFacetParent = false;
    while (parent !== null) {
      if (parent.isFacet()) {
        hasFacetParent = true;
        break;
      }
      parent = parent.parent();
    }

    if (hasFacetParent) {
      config.cell = extend({}, config.cell, config.facet.cell);
    }
    return config;
  }

  private _initScale(mark: Mark, encoding: Encoding, config: Config, topLevelWidth:number, topLevelHeight: number): Dict<Scale> {
    const xyRangeSteps: number[] = [];

    return UNIT_SCALE_CHANNELS.reduce(function(_scale, channel) {
      if (vlEncoding.channelHasField(encoding, channel) ||
          (channel === X && vlEncoding.channelHasField(encoding, X2)) ||
          (channel === Y && vlEncoding.channelHasField(encoding, Y2))
        ) {
        const scale = _scale[channel] = initScale(
          channel, encoding[channel], config, mark,
          channel === X ? topLevelWidth : channel === Y ? topLevelHeight : undefined,
          xyRangeSteps // for determine point / bar size
        );

        if (channel === X || channel === Y) {
          if (scale.rangeStep) {
            xyRangeSteps.push(scale.rangeStep);
          }
        }
      }
      return _scale;
    }, {});
  }

  // TODO: consolidate this with scale?  Current scale range is in parseScale (later),
  // but not in initScale because scale range depends on size,
  // but size depends on scale type and rangeStep
  private _initSize(mark: Mark, scale: Dict<Scale>, width: number, height: number, cellConfig: CellConfig, scaleConfig: ScaleConfig) {
    if (width !== undefined) {
      this._width = width;
    } else if (scale[X]) {
      if (!hasDiscreteDomain(scale[X].type) || !scale[X].rangeStep) {
        this._width = cellConfig.width;
      } // else: Do nothing, use dynamic width.
    } else { // No scale X
      if (mark === TEXTMARK) {
        // for text table without x/y scale we need wider rangeStep
        this._width = scaleConfig.textXRangeStep;
      } else {
        if (typeof scaleConfig.rangeStep === 'string') {
          throw new Error('_initSize does not handle string rangeSteps');
        }<commit_msg>rename TEXTMARK to TEXT_MARK per convention<commit_after>import * as log from '../log';

import {Axis} from '../axis';
import {X, Y, X2, Y2, Channel, UNIT_CHANNELS,  UNIT_SCALE_CHANNELS, NONSPATIAL_SCALE_CHANNELS, supportMark} from '../channel';
import {defaultConfig, Config, CellConfig} from '../config';
import {SOURCE, SUMMARY} from '../data';
import {Encoding} from '../encoding';
import * as vlEncoding from '../encoding'; // TODO: remove
import {ChannelDef, FieldDef, FieldRefOption, field, normalize, isFieldDef, isValueDef} from '../fielddef';
import {Legend} from '../legend';
import {Mark, TEXT as TEXT_MARK, FILL_STROKE_CONFIG} from '../mark';
import {Scale, ScaleConfig, hasDiscreteDomain} from '../scale';
import {ExtendedUnitSpec} from '../spec';
import {duplicate, extend, isArray, mergeDeep, Dict} from '../util';
import {VgData} from '../vega.schema';

import {parseAxisComponent} from './axis/parse';
import {applyConfig} from './common';
import {initMarkConfig, initTextConfig} from './config';
import {assembleData, parseUnitData} from './data/data';
import {parseLegendComponent} from './legend/parse';
import {assembleLayout, parseUnitLayout} from './layout';
import {Model} from './model';
import {parseMark} from './mark/mark';
import initScale from './scale/init';
import parseScaleComponent from './scale/parse';
import {stack, StackProperties} from '../stack';

/**
 * Internal model of Vega-Lite specification for the compiler.
 */
export class UnitModel extends Model {
  /**
   * Fixed width for the unit visualization.
   * If undefined (e.g., for ordinal scale), the width of the
   * visualization will be calculated dynamically.
   */
  private _width: number;

  /**
   * Fixed height for the unit visualization.
   * If undefined (e.g., for ordinal scale), the height of the
   * visualization will be calculated dynamically.
   */
  private _height: number;

  private readonly _mark: Mark;
  private readonly _encoding: Encoding;
  private readonly _stack: StackProperties;

  constructor(spec: ExtendedUnitSpec, parent: Model, parentGivenName: string) {
    super(spec, parent, parentGivenName);

    // use top-level width / height or parent's top-level width / height

    // FIXME: once facet supports width/height, this is no longer correct!
    const providedWidth = spec.width !== undefined ? spec.width :
      parent ? parent['width'] : undefined; // only exists if parent is layer
    const providedHeight = spec.height !== undefined ? spec.height :
      parent ? parent['height'] : undefined; // only exists if parent is layer

    const mark = this._mark = spec.mark;
    const encoding = this._encoding = this._initEncoding(mark, spec.encoding || {});

    // TODO?: ideally we should use config only inside this constructor
    const config = this._config = this._initConfig(spec.config, parent);

    // FIXME move stacked out of config as it's not really a theme.
    // calculate stack properties
    this._stack = stack(mark, encoding, config.mark.stacked);
    this._scale =  this._initScale(mark, encoding, config, providedWidth, providedHeight);

    // TODO?: refactor these to be a part of the model as they are not really just config
    config.mark = initMarkConfig(mark, encoding, this._scale, this._stack, config);
    if (mark === 'text') { // FIXME: maybe we should refactor this
      config.text = initTextConfig(encoding, config);
    }

    this._axis = this._initAxis(encoding, config);
    this._legend = this._initLegend(encoding, config);

    // width / height
    this._initSize(mark, this._scale,
      providedWidth,
      providedHeight,
      config.cell, config.scale
    );
  }

  private _initEncoding(mark: Mark, encoding: Encoding) {
    // clone to prevent side effect to the original spec
    encoding = duplicate(encoding);

    Object.keys(encoding).forEach((channel: any) => {
      if (!supportMark(channel, mark)) {
        // Drop unsupported channel

        log.warn(log.message.incompatibleChannel(channel, mark));
        delete encoding[channel];
        return;
      }

      if (isArray(encoding[channel])) {
        // Array of fieldDefs for detail channel (or production rule)
        encoding[channel] = encoding[channel].reduce((channelDefs: ChannelDef[], channelDef: ChannelDef) => {
          if (!isFieldDef(channelDef) && !isValueDef(channelDef)) { // TODO: datum
            log.warn(log.message.emptyFieldDef(channelDef, channel));
          } else {
            channelDefs.push(normalize(channelDef, channel));
          }
          return channelDefs;
        }, []);
      } else {
        const fieldDef = encoding[channel];
        if (fieldDef.field === undefined && fieldDef.value === undefined) { // TODO: datum
          log.warn(log.message.emptyFieldDef(fieldDef, channel));
          delete encoding[channel];
          return;
        }
        normalize(fieldDef, channel);
      }
    });
    return encoding;
  }

  /**
   * Init config by merging config from parent and, if applicable, from facet config
   */
  private _initConfig(specConfig: Config, parent: Model) {
    let config = mergeDeep(duplicate(defaultConfig), parent ? parent.config() : {}, specConfig);
    let hasFacetParent = false;
    while (parent !== null) {
      if (parent.isFacet()) {
        hasFacetParent = true;
        break;
      }
      parent = parent.parent();
    }

    if (hasFacetParent) {
      config.cell = extend({}, config.cell, config.facet.cell);
    }
    return config;
  }

  private _initScale(mark: Mark, encoding: Encoding, config: Config, topLevelWidth:number, topLevelHeight: number): Dict<Scale> {
    const xyRangeSteps: number[] = [];

    return UNIT_SCALE_CHANNELS.reduce(function(_scale, channel) {
      if (vlEncoding.channelHasField(encoding, channel) ||
          (channel === X && vlEncoding.channelHasField(encoding, X2)) ||
          (channel === Y && vlEncoding.channelHasField(encoding, Y2))
        ) {
        const scale = _scale[channel] = initScale(
          channel, encoding[channel], config, mark,
          channel === X ? topLevelWidth : channel === Y ? topLevelHeight : undefined,
          xyRangeSteps // for determine point / bar size
        );

        if (channel === X || channel === Y) {
          if (scale.rangeStep) {
            xyRangeSteps.push(scale.rangeStep);
          }
        }
      }
      return _scale;
    }, {});
  }

  // TODO: consolidate this with scale?  Current scale range is in parseScale (later),
  // but not in initScale because scale range depends on size,
  // but size depends on scale type and rangeStep
  private _initSize(mark: Mark, scale: Dict<Scale>, width: number, height: number, cellConfig: CellConfig, scaleConfig: ScaleConfig) {
    if (width !== undefined) {
      this._width = width;
    } else if (scale[X]) {
      if (!hasDiscreteDomain(scale[X].type) || !scale[X].rangeStep) {
        this._width = cellConfig.width;
      } // else: Do nothing, use dynamic width.
    } else { // No scale X
      if (mark === TEXT_MARK) {
        // for text table without x/y scale we need wider rangeStep
        this._width = scaleConfig.textXRangeStep;
      } else {
        if (typeof scaleConfig.rangeStep === 'string') {
          throw new Error('_initSize does not handle string rangeSteps');
        }
{'fraction_non_alphanumeric': 0.12438356164383561, 'fraction_numerical': 0.000547945205479452, 'mean_word_length': 3.0716675962074733, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 11, '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': '4469972', 'n_tokens_mistral': 4091, 'n_tokens_neox': 3992, 'n_words': 1279}