<commit_before>import {selector as parseSelector} from 'vega-event-selector';
import {Channel, ScaleChannel, X, Y} from '../../../channel';
import {stringValue} from '../../../util';
import {BRUSH as INTERVAL_BRUSH, projections as intervalProjections} from '../interval';
import {channelSignalName, SelectionComponent} from '../selection';
import {UnitModel} from './../../unit';
import {default as scalesCompiler, domain} from './scales';
import {TransformCompiler} from './transforms';


const ANCHOR = '_zoom_anchor';
const DELTA  = '_zoom_delta';

const zoom:TransformCompiler = {
  has: function(selCmpt) {
    return selCmpt.type === 'interval' && selCmpt.zoom;
  },

  signals: function(model, selCmpt, signals) {
    const name = selCmpt.name;
    const hasScales = scalesCompiler.has(selCmpt);
    const delta = name + DELTA;
    const {x, y} = intervalProjections(selCmpt);
    const sx = stringValue(model.scaleName(X));
    const sy = stringValue(model.scaleName(Y));
    let events = parseSelector(selCmpt.zoom, 'scope');

    if (!hasScales) {
      events = events.map((e) => (e.markname = name + INTERVAL_BRUSH, e));
    }

    signals.push({
      name: name + ANCHOR,
      on: [{
        events: events,
        update: hasScales ?
          `{x: invert(${sx}, x(unit)), y: invert(${sy}, y(unit))}` :
          `{x: x(unit), y: y(unit)}`
      }]
    }, {
      name: delta,
      on: [{
        events: events,
        force: true,
        update: 'pow(1.001, event.deltaY * pow(16, event.deltaMode))'
      }]
    });

    if (x !== null) {
      onDelta(model, selCmpt, 'x', 'width', signals);
    }

    if (y !== null) {
      onDelta(model, selCmpt, 'y', 'height', signals);
    }

    return signals;
  }
};

export {zoom as default};

function onDelta(model: UnitModel, selCmpt: SelectionComponent, channel: ScaleChannel, size: 'width' | 'height', signals: any[]) {
  const name = selCmpt.name;
  const hasScales = scalesCompiler.has(selCmpt);
  const signal:any = signals.filter((s:any) => {
    return s.name === channelSignalName(selCmpt, channel, hasScales ? 'data' : 'visual');
  })[0];
  const sizeSg = model.getSizeSignalRef(size).signal;
  const scaleType = model.getScaleComponent(channel).get('type');
  const base = hasScales ? domain(model, channel) : signal.name;
  const delta  = name + DELTA;
  const anchor = `${name}${ANCHOR}.${channel}`;
  const zoomFn = !hasScales ? 'zoomLinear' :
    scaleType === 'log' ? 'zoomLog' :
    scaleType === 'pow' ? 'zoomPow' : 'zoomLinear';
  const update = `${zoomFn}(${base}, ${anchor}, ${delta})`;

  signal.on.push({
    events: {signal: delta},
    update: hasScales ? update : `clampRange(${update}, 0, ${sizeSg})`
  });
}
<commit_msg>Ensure that x/y channels exist for zoom anchor (fixes #2653). (#2654)<commit_after>import {selector as parseSelector} from 'vega-event-selector';
import {Channel, ScaleChannel, X, Y} from '../../../channel';
import {stringValue} from '../../../util';
import {BRUSH as INTERVAL_BRUSH, projections as intervalProjections} from '../interval';
import {channelSignalName, SelectionComponent} from '../selection';
import {UnitModel} from './../../unit';
import {default as scalesCompiler, domain} from './scales';
import {TransformCompiler} from './transforms';


const ANCHOR = '_zoom_anchor';
const DELTA  = '_zoom_delta';

const zoom:TransformCompiler = {
  has: function(selCmpt) {
    return selCmpt.type === 'interval' && selCmpt.zoom;
  },

  signals: function(model, selCmpt, signals) {
    const name = selCmpt.name;
    const hasScales = scalesCompiler.has(selCmpt);
    const delta = name + DELTA;
    const {x, y} = intervalProjections(selCmpt);
    const sx = stringValue(model.scaleName(X));
    const sy = stringValue(model.scaleName(Y));
    let events = parseSelector(selCmpt.zoom, 'scope');

    if (!hasScales) {
      events = events.map((e) => (e.markname = name + INTERVAL_BRUSH, e));
    }

    signals.push({
      name: name + ANCHOR,
      on: [{
        events: events,
        update: !hasScales ? `{x: x(unit), y: y(unit)}` :
          '{' + [
            (sx ? `x: invert(${sx}, x(unit))` : ''),
            (sy ? `y: invert(${sy}, y(unit))` : '')
          ].filter((expr) => !!expr).join(', ') + '}'
      }]
    }, {
      name: delta,
      on: [{
        events: events,
        force: true,
        update: 'pow(1.001, event.deltaY * pow(16, event.deltaMode))'
      }]
    });

    if (x !== null) {
      onDelta(model, selCmpt, 'x', 'width', signals);
    }

    if (y !== null) {
      onDelta(model, selCmpt, 'y', 'height', signals);
    }

    return signals;
  }
};

export {zoom as default};

function onDelta(model: UnitModel, selCmpt: SelectionComponent, channel: ScaleChannel, size: 'width' | 'height', signals: any[]) {
  const name = selCmpt.name;
  const hasScales = scalesCompiler.has(selCmpt);
  const signal:any = signals.filter((s:any) => {
    return s.name === channelSignalName(selCmpt, channel, hasScales ? 'data' : 'visual');
  })[0];
  const sizeSg = model.getSizeSignalRef(size).signal;
  const scaleType = model.getScaleComponent(channel).get('type');
  const base = hasScales ? domain(model, channel) : signal.name;
  const delta  = name + DELTA;
  const anchor = `${name}${ANCHOR}.${channel}`;
  const zoomFn = !hasScales ? 'zoomLinear' :
    scaleType === 'log' ? 'zoomLog' :
    scaleType === 'pow' ? 'zoomPow' : 'zoomLinear';
  const update = `${zoomFn}(${base}, ${anchor}, ${delta})`;

  signal.on.push({
    events: {signal: delta},
    update: hasScales ? update : `clampRange(${update}, 0, ${sizeSg})`
  });
}

{'fraction_non_alphanumeric': 0.17843733237976042, 'fraction_numerical': 0.004291078133381012, 'mean_word_length': 3.356697819314642, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 8, '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': '5616101', 'n_tokens_mistral': 1917, 'n_tokens_neox': 1839, 'n_words': 460}