auto-layouter
Version:
Graph layout and routing for JavaScript
240 lines (212 loc) • 6.07 kB
JavaScript
import Box from './box'
import Point from './point'
export function toPaddingBox (padding) {
return {
width: 2 * padding,
height: 2 * padding,
x: -padding,
y: -padding
}
}
export function toBox (box) {
const { x, y, width, height } = box
return new Box(x, y, width, height)
}
export function toPoint ({ x, y }) {
return new Point(x, y)
}
export function getKey (point) {
return point.toString()
}
/**
* 计算两点之间的曼哈顿距离
*
* @export
* @param {Point} a
* @param {Point} b
* @returns
*/
export function manhattanDistance (a, b) {
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y)
}
export function chebyshevDistance (a, b) {
const dx = Math.abs(a.x - b.x)
const dy = Math.abs(a.y - b.y)
return dx + dy + (Math.SQRT2 - 2) * Math.min(dx, dy)
}
export function euclideanDistance (a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2))
}
/**
* 把点P进行归一化处理
*
* @export
* @param {*} point
* @returns
*/
export function normalizePoint (point) {
return new Point(
point.x === 0 ? 0 : Math.abs(point.x) / point.x,
point.y === 0 ? 0 : Math.abs(point.y) / point.y
)
}
/**
* 计算方向在网格上的偏移量
*
* @export
* @param {*} directions
* @param {*} grid
* @param {*} step
*/
export function getGridOffsets (directions, grid, step) {
directions.forEach(direction => {
direction.gridOffsetX = (direction.offsetX / step) * grid.x
direction.gridOffsetY = (direction.offsetY / step) * grid.y
})
}
/**
* 确定终点相对于起点的方位
*
* @export
* @param {*} start
* @param {*} end
* @param {*} numDirections
* @param {*} grid
* @param {*} opt
* @returns
*/
export function getDirectionAngle (start, end, numDirections, grid, step) {
const quadrant = 360 / numDirections
const angleTheta = start.theta(fixAngleEnd(start, end, grid, step))
const normalizedAngle = normalizeAngle(angleTheta + (quadrant / 2))
return quadrant * Math.floor(normalizedAngle / quadrant)
}
/**
* 根据网格大小修正终点坐标
*
* @export
* @param {*} start
* @param {*} end
* @param {*} grid
* @param {*} step
* @returns
*/
export function fixAngleEnd (start, end, grid, step) {
const diffX = end.x - start.x
const diffY = end.y - start.y
const gridStepsX = diffX / grid.x
const gridStepsY = diffY / grid.y
const distanceX = gridStepsX * step
const distanceY = gridStepsY * step
return new Point(start.x + distanceX, start.y + distanceY)
}
export function normalizeAngle (angle) {
return (angle % 360) + (angle < 0 ? 360 : 0)
}
/**
* 获得两个方向的转变向量
*
* @export
* @param {*} angle1
* @param {*} angle2
* @returns
*/
export function getDirectionChange (angle1, angle2) {
const directionChange = Math.abs(angle1 - angle2)
return (directionChange > 180) ? (360 - directionChange) : directionChange
}
export function align (point, grid, precision) {
return roundPoint(snapToGrid(point.clone(), grid), precision)
}
function _snapToGrid (value, gridSize) {
return gridSize * Math.round(value / gridSize)
}
/**
* 计算一个点,使得点P正好落到网格上
*
* @export
* @param {Point} point
* @param {Object} grid
* @returns
*/
export function snapToGrid (point, grid) {
const source = grid.source
const snappedX = _snapToGrid(point.x - source.x, grid.x) + source.x
const snappedY = _snapToGrid(point.y - source.y, grid.y) + source.y
return new Point(snappedX, snappedY)
}
export function roundPoint (point, precision) {
const f = Math.pow(10, precision || 0)
point.x = Math.round(point.x * f) / f
point.y = Math.round(point.y * f) / f
return point
}
function diagonalCost (step) {
return Math.ceil(Math.sqrt(step * step << 1))
}
export function getDirections (step, orientation) {
const d = diagonalCost(step)
const directions = [
{ offsetX: step, offsetY: 0, cost: step },
{ offsetX: step, offsetY: step, cost: d },
{ offsetX: 0, offsetY: step, cost: step },
{ offsetX: -step, offsetY: step, cost: d },
{ offsetX: -step, offsetY: 0, cost: step },
{ offsetX: -step, offsetY: -step, cost: d },
{ offsetX: 0, offsetY: -step, cost: step },
{ offsetX: step, offsetY: -step, cost: d }
]
directions.forEach(direction => {
var point1 = new Point(0, 0)
var point2 = new Point(direction.offsetX, direction.offsetY)
direction.angle = normalizeAngle(point1.theta(point2))
})
return orientation === 4 ? [directions[0], directions[2], directions[4], directions[6]] : directions
}
export function getPenalties (step) {
return {
0: 0,
45: step,
90: step * 2
}
}
export function offsetPoint (point, grid, direction, precision) {
return align(new Point(point.x + grid.x * direction[0], point.y + grid.y * direction[1]), grid, precision)
}
export function paddingPoint (point, padding, direction) {
return new Point(point.x + padding * direction[0], point.y + padding * direction[1])
}
/**
* 根据起点和终点的距离,结合步进长度,计算网格大小
*
* @export
* @param {Number} step
* @param {Point} source
* @param {Point} target
* @returns
*/
export function getGrid (step, source, target) {
return {
source: Object.assign({}, source),
x: getGridDimension(target.x - source.x, step),
y: getGridDimension(target.y - source.y, step)
}
}
/**
* 计算网格大小
*
* @export
* @param {Number} diff
* @param {Number} step
* @returns
*/
export function getGridDimension (diff, step) {
if (!diff) return step
const absDiff = Math.abs(diff)
const numSteps = Math.round(absDiff / step)
if (!numSteps) return absDiff
const roundedDiff = numSteps * step
const remainder = absDiff - roundedDiff
const stepCorrection = remainder / numSteps
return step + stepCorrection
}