<commit_before>import * as R from 'ramda';
import { Maybe } from 'ramda-fantasy';
import * as XP from 'xod-project';
import { foldMaybe } from 'xod-func-tools';

export const BASE_SIZE_UNIT = 5;

export const NODE_HEIGHT = BASE_SIZE_UNIT * 13;

const GAP_BETWEEN_LINES = BASE_SIZE_UNIT * 8;

export const SLOT_SIZE = {
  WIDTH: BASE_SIZE_UNIT * 8 + 4,
  HEIGHT: NODE_HEIGHT + GAP_BETWEEN_LINES,
  GAP: GAP_BETWEEN_LINES,
};

export const GAP_IN_SLOTS = SLOT_SIZE.GAP / SLOT_SIZE.HEIGHT;

const PINVALUE_GAP = 4;
export const PINVALUE_WIDTH = SLOT_SIZE.WIDTH - PINVALUE_GAP / 2;

const PINLABEL_GAP = 8;
export const PINLABEL_WIDTH = SLOT_SIZE.WIDTH - PINLABEL_GAP / 2;

export const DEFAULT_PANNING_OFFSET = {
  x: 1.5, // In slots
  y: 0.75, // In slots
};

export const LINK_HOTSPOT_SIZE = {
  WIDTH: 8,
};

export const NODE_CORNER_RADIUS = 3;

export const RESIZE_HANDLE_SIZE = 12;
export const VARIADIC_HANDLE_WIDTH = 4;
export const VARIADIC_HANDLE_HEIGHT = BASE_SIZE_UNIT * 5;

export const PIN_RADIUS = 4;
export const PIN_INNER_RADIUS = PIN_RADIUS - 2;
export const PIN_RADIUS_WITH_OUTER_STROKE = PIN_RADIUS + 3;
export const PIN_RADIUS_WITH_SHADOW = PIN_RADIUS + 4; // TODO: rename
export const PIN_HIGHLIGHT_RADIUS = 15;
export const PIN_HOVER_HIGHLIGHT_RADIUS = PIN_RADIUS;
export const PIN_HOTSPOT_RADIUS = PIN_HIGHLIGHT_RADIUS;
export const PIN_OFFSET_FROM_NODE_EDGE = 0;

export const TEXT_OFFSET_FROM_PIN_BORDER = 3;

// =============================================================================
//
// Position / Size converters
//
// =============================================================================

// :: Size -> Size
export const pixelSizeToSlots = R.evolve({
  width: px => px / SLOT_SIZE.WIDTH,
  height: px => px / SLOT_SIZE.HEIGHT,
});

// :: Size -> Size
export const slotSizeToPixels = R.evolve({
  width: slots => slots * SLOT_SIZE.WIDTH,
  height: slots => R.dec(slots) * SLOT_SIZE.HEIGHT + NODE_HEIGHT,
});

// :: Position -> Position
export const pixelPositionToSlots = R.evolve({
  x: px => px / SLOT_SIZE.WIDTH,
  y: px => px / SLOT_SIZE.HEIGHT,
});

// :: Position -> Position
export const slotPositionToPixels = R.evolve({
  x: slots => Math.ceil(slots * SLOT_SIZE.WIDTH),
  y: slots => Math.ceil(slots * SLOT_SIZE.HEIGHT),
});

// =============================================================================
//
// Node layout calculations
//
// =============================================================================

// :: { input: Number, output: Number } -> Size
const nodeSizeByPinsInSlots = pinCountByDirection => ({
  width: Math.max(pinCountByDirection.input, pinCountByDirection.output, 1),
  height: 1,
});

// :: [Pin] -> Size
export const calcutaleNodeSizeFromPins = R.compose(
  nodeSizeByPinsInSlots,
  R.map(R.length),
  R.merge({ input: [], output: [] }),
  R.groupBy(R.prop('direction'))
);

// :: Number -> Number -> Position
const pinOrderToPosition = R.curry((nodeWidth, pinOrder) => ({
  x: pinOrder * SLOT_SIZE.WIDTH + SLOT_SIZE.WIDTH / 2,
  y: PIN_OFFSET_FROM_NODE_EDGE,
}));

// :: Size -> Pin -> Position
export const calculatePinPosition = R.curry((nodeSize, pin) => {
  const position = pinOrderToPosition(nodeSize.width, pin.order);

  // output pin positions start from the bottom
  return R.when(
    R.always(R.equals(pin.direction, 'output')),
    R.assoc('y', nodeSize.height - position.y)
  )(position);
});

/**
 * adds `size` to node and `position` to node's pins
 * @param node - dereferenced node
 */
export const addNodePositioning = node => {<commit_msg>fix(xod-client): make pixel perfect links<commit_after>import * as R from 'ramda';
import { Maybe } from 'ramda-fantasy';
import * as XP from 'xod-project';
import { foldMaybe } from 'xod-func-tools';

// SVG elements with thin stroke has to be moved by 0.5px
// to make pixel perfect stroke instead of blurred one
export const COMPENSATE_SVG = 0.5;

export const BASE_SIZE_UNIT = 5;

export const NODE_HEIGHT = BASE_SIZE_UNIT * 13;

const GAP_BETWEEN_LINES = BASE_SIZE_UNIT * 8;

export const SLOT_SIZE = {
  WIDTH: BASE_SIZE_UNIT * 8 + 4,
  HEIGHT: NODE_HEIGHT + GAP_BETWEEN_LINES,
  GAP: GAP_BETWEEN_LINES,
};

export const GAP_IN_SLOTS = SLOT_SIZE.GAP / SLOT_SIZE.HEIGHT;

const PINVALUE_GAP = 4;
export const PINVALUE_WIDTH = SLOT_SIZE.WIDTH - PINVALUE_GAP / 2;

const PINLABEL_GAP = 8;
export const PINLABEL_WIDTH = SLOT_SIZE.WIDTH - PINLABEL_GAP / 2;

export const DEFAULT_PANNING_OFFSET = {
  x: 1.5, // In slots
  y: 0.75, // In slots
};

export const LINK_HOTSPOT_SIZE = {
  WIDTH: 8,
};

export const NODE_CORNER_RADIUS = 3;

export const RESIZE_HANDLE_SIZE = 12;
export const VARIADIC_HANDLE_WIDTH = 4;
export const VARIADIC_HANDLE_HEIGHT = BASE_SIZE_UNIT * 5;

export const PIN_RADIUS = 4;
export const PIN_INNER_RADIUS = PIN_RADIUS - 2;
export const PIN_RADIUS_WITH_OUTER_STROKE = PIN_RADIUS + 3;
export const PIN_RADIUS_WITH_SHADOW = PIN_RADIUS + 4; // TODO: rename
export const PIN_HIGHLIGHT_RADIUS = 15;
export const PIN_HOVER_HIGHLIGHT_RADIUS = PIN_RADIUS;
export const PIN_HOTSPOT_RADIUS = PIN_HIGHLIGHT_RADIUS;
export const PIN_OFFSET_FROM_NODE_EDGE = 0;

export const TEXT_OFFSET_FROM_PIN_BORDER = 3;

// =============================================================================
//
// Position / Size converters
//
// =============================================================================

// :: Size -> Size
export const pixelSizeToSlots = R.evolve({
  width: px => px / SLOT_SIZE.WIDTH,
  height: px => px / SLOT_SIZE.HEIGHT,
});

// :: Size -> Size
export const slotSizeToPixels = R.evolve({
  width: slots => slots * SLOT_SIZE.WIDTH,
  height: slots => R.dec(slots) * SLOT_SIZE.HEIGHT + NODE_HEIGHT,
});

// :: Position -> Position
export const pixelPositionToSlots = R.evolve({
  x: px => px / SLOT_SIZE.WIDTH,
  y: px => px / SLOT_SIZE.HEIGHT,
});

// :: Position -> Position
export const slotPositionToPixels = R.evolve({
  x: slots => Math.ceil(slots * SLOT_SIZE.WIDTH),
  y: slots => Math.ceil(slots * SLOT_SIZE.HEIGHT),
});

// =============================================================================
//
// Node layout calculations
//
// =============================================================================

// :: { input: Number, output: Number } -> Size
const nodeSizeByPinsInSlots = pinCountByDirection => ({
  width: Math.max(pinCountByDirection.input, pinCountByDirection.output, 1),
  height: 1,
});

// :: [Pin] -> Size
export const calcutaleNodeSizeFromPins = R.compose(
  nodeSizeByPinsInSlots,
  R.map(R.length),
  R.merge({ input: [], output: [] }),
  R.groupBy(R.prop('direction'))
);

// :: Number -> Number -> Position
const pinOrderToPosition = R.curry((nodeWidth, pinOrder) => ({
  x: pinOrder * SLOT_SIZE.WIDTH + SLOT_SIZE.WIDTH / 2 - COMPENSATE_SVG,
  y: PIN_OFFSET_FROM_NODE_EDGE + COMPENSATE_SVG,
}));

// :: Size -> Pin -> Position
export const calculatePinPosition = R.curry((nodeSize, pin) => {
  const position = pinOrderToPosition(nodeSize.width, pin.order);

  // output pin positions start from the bottom
  return R.when(
    R.always(R.equals(pin.direction, 'output')),
    R.assoc('y', nodeSize.height - position.y)
  )(position);
});

/**
 * adds `size` to node and `position` to node's pins
 * @param node - dereferenced node
 */
export const addNodePositioning = node => {
{'fraction_non_alphanumeric': 0.2018875666803447, 'fraction_numerical': 0.009301053207495554, 'mean_word_length': 4.4936138241923365, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 47, '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': '4660427', 'n_tokens_mistral': 2687, 'n_tokens_neox': 2525, 'n_words': 604}