|
|
const LEFT = 'LEFT'; |
|
|
const RIGHT = 'RIGHT'; |
|
|
|
|
|
const WAREHOUSE_COLUMNS = ['A', 'B', 'C', 'D', 'E']; |
|
|
const WAREHOUSE_ROWS = ['1', '2', '3']; |
|
|
|
|
|
const WAREHOUSE_PADDING_TOP = 100; |
|
|
const WAREHOUSE_PADDING_LEFT = 100; |
|
|
|
|
|
const SHELVING_PADDING = 80; |
|
|
const SHELVING_WIDTH = 150; |
|
|
const SHELVING_HEIGHT = 300; |
|
|
const SHELVING_ROWS = 10; |
|
|
|
|
|
const SHELVING_LINE_WIDTH = 5; |
|
|
const SHELVING_STROKE_STYLE = 'green'; |
|
|
const SHELVING_LINE_JOIN = 'bevel'; |
|
|
|
|
|
const TROLLEY_PATH_LINE_WIDTH = 5; |
|
|
const TROLLEY_PATH_LINE_JOIN = 'round'; |
|
|
|
|
|
const SHELVINGS_MAP = new Map(); |
|
|
|
|
|
function shelvingId(i, j) { |
|
|
return '(' + i + ',' + j + ')'; |
|
|
} |
|
|
|
|
|
class Point { |
|
|
x = 0; |
|
|
y = 0; |
|
|
|
|
|
constructor(x, y) { |
|
|
this.x = x; |
|
|
this.y = y; |
|
|
} |
|
|
} |
|
|
|
|
|
class ShelvingShape { |
|
|
id; |
|
|
x = 0; |
|
|
y = 0; |
|
|
width = 0; |
|
|
height = 0; |
|
|
|
|
|
constructor(id, x, y, width, height) { |
|
|
this.id = id; |
|
|
this.x = x; |
|
|
this.y = y; |
|
|
this.width = width; |
|
|
this.height = height; |
|
|
} |
|
|
} |
|
|
|
|
|
class WarehouseLocation { |
|
|
shelvingId; |
|
|
side; |
|
|
row; |
|
|
|
|
|
constructor(shelvingId, side, row) { |
|
|
this.shelvingId = shelvingId; |
|
|
this.side = side; |
|
|
this.row = row; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getWarehouseCanvasContext() { |
|
|
return document.getElementById('warehouseCanvas').getContext('2d'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function clearWarehouseCanvas() { |
|
|
const canvas = document.getElementById('warehouseCanvas'); |
|
|
const ctx = canvas.getContext('2d'); |
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0); |
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
const parentWidth = canvas.parentElement.clientWidth; |
|
|
const parentHeight = canvas.parentElement.clientHeight; |
|
|
const contentWidth = 2 * WAREHOUSE_PADDING_LEFT + (SHELVING_WIDTH + SHELVING_PADDING) * WAREHOUSE_COLUMNS.length; |
|
|
const contentHeight = 2 * WAREHOUSE_PADDING_TOP + (SHELVING_HEIGHT + SHELVING_PADDING) * WAREHOUSE_ROWS.length; |
|
|
const minToFillParent = Math.min(parentWidth / contentWidth, parentHeight / contentHeight); |
|
|
const xOffset = (parentWidth - (contentWidth * minToFillParent)) / 2; |
|
|
const yOffset = (parentHeight - (contentHeight * minToFillParent)) / 2; |
|
|
canvas.width = parentWidth; |
|
|
canvas.height = parentHeight; |
|
|
ctx.translate(xOffset, yOffset); |
|
|
ctx.scale(minToFillParent, minToFillParent); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function drawWarehouse() { |
|
|
const ctx = getWarehouseCanvasContext(); |
|
|
let x = WAREHOUSE_PADDING_LEFT; |
|
|
for (let column = 0; column < WAREHOUSE_COLUMNS.length; column++) { |
|
|
let y = WAREHOUSE_PADDING_TOP; |
|
|
for (let row = 0; row < WAREHOUSE_ROWS.length; row++) { |
|
|
const shelving = new ShelvingShape(shelvingId(WAREHOUSE_COLUMNS[column], WAREHOUSE_ROWS[row]), x, y, SHELVING_WIDTH, SHELVING_HEIGHT); |
|
|
drawShelving(ctx, shelving); |
|
|
SHELVINGS_MAP.set(shelving.id, shelving); |
|
|
y = y + SHELVING_HEIGHT + SHELVING_PADDING; |
|
|
} |
|
|
x = x + SHELVING_WIDTH + SHELVING_PADDING; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function drawShelving(ctx, shelving) { |
|
|
ctx.strokeStyle = SHELVING_STROKE_STYLE; |
|
|
ctx.lineJoin = SHELVING_LINE_JOIN; |
|
|
ctx.lineWidth = SHELVING_LINE_WIDTH; |
|
|
ctx.strokeRect(shelving.x, shelving.y, shelving.width, shelving.height); |
|
|
|
|
|
ctx.font = '30px serif'; |
|
|
ctx.textAlign = 'center'; |
|
|
ctx.fillStyle = SHELVING_STROKE_STYLE |
|
|
|
|
|
const shelvingTextParts = shelving.id.split(','); |
|
|
ctx.fillText(shelvingTextParts[0].substring(1) + shelvingTextParts[1].substring(0, shelvingTextParts[1].length - 1), |
|
|
shelving.x + shelving.width / 2, shelving.y + shelving.height / 2); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function drawTrolleyPath(strokeStyle, warehouseLocations, trolleyIndex, trolleyCount) { |
|
|
const ctx = getWarehouseCanvasContext(); |
|
|
ctx.lineJoin = TROLLEY_PATH_LINE_JOIN; |
|
|
ctx.lineWidth = TROLLEY_PATH_LINE_WIDTH; |
|
|
ctx.strokeStyle = strokeStyle; |
|
|
drawWarehousePath(warehouseLocations, trolleyIndex, trolleyCount); |
|
|
} |
|
|
|
|
|
function drawTrolleyText(strokeStyle, warehouseLocations, trolleyIndex, trolleyCount) { |
|
|
const ctx = getWarehouseCanvasContext(); |
|
|
ctx.lineJoin = TROLLEY_PATH_LINE_JOIN; |
|
|
ctx.lineWidth = TROLLEY_PATH_LINE_WIDTH; |
|
|
ctx.strokeStyle = strokeStyle; |
|
|
drawTextForTrolley(strokeStyle, warehouseLocations, trolleyIndex, trolleyCount); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function drawWarehousePath(warehouseLocations, trolleyIndex, trolleyCount) { |
|
|
const ctx = getWarehouseCanvasContext(); |
|
|
const startLocation = warehouseLocations[0]; |
|
|
const startShelving = SHELVINGS_MAP.get(startLocation.shelvingId); |
|
|
const startPoint = location2Point(startShelving, startLocation.side, startLocation.row, trolleyIndex, trolleyCount); |
|
|
let lastPoint = startPoint; |
|
|
let lastShelving = startShelving; |
|
|
let lastSide = startLocation.side; |
|
|
let lastRow = startLocation.row; |
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.moveTo(startPoint.x, startPoint.y); |
|
|
ctx.arc(startPoint.x, startPoint.y, 5, 0, 2 * Math.PI); |
|
|
|
|
|
for (let i = 1; i < warehouseLocations.length; i++) { |
|
|
const location = warehouseLocations[i]; |
|
|
const shelving = SHELVINGS_MAP.get(location.shelvingId); |
|
|
const side = location.side; |
|
|
const row = location.row; |
|
|
const point = location2Point(shelving, location.side, location.row, trolleyIndex, trolleyCount); |
|
|
drawWarehousePathBetweenShelves(ctx, trolleyIndex, trolleyCount, lastShelving, lastSide, lastRow, lastPoint, |
|
|
shelving, side, row, point); |
|
|
ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI); |
|
|
lastPoint = point; |
|
|
lastShelving = shelving; |
|
|
lastSide = side; |
|
|
lastRow = row; |
|
|
} |
|
|
ctx.stroke(); |
|
|
ctx.closePath(); |
|
|
} |
|
|
|
|
|
function drawTextForTrolley(strokeStyle, warehouseLocations, trolleyIndex, trolleyCount) { |
|
|
const ctx = getWarehouseCanvasContext(); |
|
|
|
|
|
ctx.fillStyle = strokeStyle; |
|
|
ctx.strokeStyle = "#000000"; |
|
|
let overlappingOrderTexts = []; |
|
|
const SHELVING_ROW_HEIGHT = SHELVING_HEIGHT / SHELVING_ROWS; |
|
|
const TEXT_SEPERATOR_HEIGHT = SHELVING_ROW_HEIGHT * 4; |
|
|
const SEPERATION_PER_TROLLEY = TEXT_SEPERATOR_HEIGHT / trolleyCount; |
|
|
for (let i = 0; i < warehouseLocations.length; i++) { |
|
|
const location = warehouseLocations[i]; |
|
|
const shelving = SHELVINGS_MAP.get(location.shelvingId); |
|
|
const point = location2Point(shelving, location.side, location.row, trolleyIndex, trolleyCount); |
|
|
const pointFlooredRow = Math.floor(point.y / TEXT_SEPERATOR_HEIGHT) * TEXT_SEPERATOR_HEIGHT; |
|
|
|
|
|
point.y = pointFlooredRow + SEPERATION_PER_TROLLEY * trolleyIndex; |
|
|
addToOverlap(overlappingOrderTexts, i.toString(10), point); |
|
|
} |
|
|
|
|
|
for (let i = 0; i < overlappingOrderTexts.length; i++) { |
|
|
const overlappingOrders = overlappingOrderTexts[i]; |
|
|
const text = '(' + overlappingOrders.orders.join(', ') + ')'; |
|
|
|
|
|
ctx.strokeText(text, overlappingOrders.x, overlappingOrders.y); |
|
|
ctx.fillText(text, overlappingOrders.x, overlappingOrders.y); |
|
|
} |
|
|
} |
|
|
|
|
|
function addToOverlap(overlappingOrderTexts, orderText, orderPoint) { |
|
|
const SHELVING_ROW_HEIGHT = SHELVING_HEIGHT / SHELVING_ROWS; |
|
|
for (let i = 0; i < overlappingOrderTexts.length; i++) { |
|
|
const overlappingOrderText = overlappingOrderTexts[i]; |
|
|
const distance = Math.abs(orderPoint.x - overlappingOrderText.x) + Math.abs(orderPoint.y - overlappingOrderText.y); |
|
|
if (distance < 2 * SHELVING_ROW_HEIGHT) { |
|
|
overlappingOrderText.orders.push(orderText); |
|
|
return; |
|
|
} |
|
|
} |
|
|
const newOverlappingOrderText = { |
|
|
orders: [orderText], |
|
|
x: orderPoint.x, |
|
|
y: orderPoint.y, |
|
|
}; |
|
|
overlappingOrderTexts.push(newOverlappingOrderText); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function drawWarehousePathBetweenShelves(ctx, trolleyIndex, trolleyCount, |
|
|
startShelving, startSide, startRow, startPoint, |
|
|
endShelving, endSide, endRow, endPoint) { |
|
|
ctx.moveTo(startPoint.x, startPoint.y); |
|
|
if (startShelving === endShelving) { |
|
|
if (startSide === endSide) { |
|
|
|
|
|
ctx.lineTo(endPoint.x, endPoint.y); |
|
|
} else { |
|
|
|
|
|
const isAbove = startRow + endRow < 2*SHELVING_ROWS - startRow - endRow; |
|
|
const aisleChangeStartPoint = location2AisleLane(startShelving, startSide, isAbove, trolleyIndex, trolleyCount); |
|
|
const aisleChangeEndPoint = location2AisleLane(endShelving, endSide, isAbove, trolleyIndex, trolleyCount); |
|
|
ctx.lineTo(aisleChangeStartPoint.x, aisleChangeStartPoint.y); |
|
|
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeEndPoint.y); |
|
|
ctx.lineTo(endPoint.x, endPoint.y); |
|
|
} |
|
|
} else if (startShelving.x === endShelving.x) { |
|
|
if (startSide === endSide) { |
|
|
|
|
|
ctx.lineTo(endPoint.x, endPoint.y); |
|
|
} else { |
|
|
|
|
|
if (startShelving.y < endShelving.y) { |
|
|
|
|
|
const aisleChangeStartPoint = location2AisleLane(endShelving, startSide, false, trolleyIndex, trolleyCount); |
|
|
const aisleChangeEndPoint = location2AisleLane(endShelving, endSide, false, trolleyIndex, trolleyCount); |
|
|
ctx.lineTo(aisleChangeStartPoint.x, aisleChangeStartPoint.y); |
|
|
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeEndPoint.y); |
|
|
ctx.lineTo(endPoint.x, endPoint.y); |
|
|
} else { |
|
|
|
|
|
const aisleChangeStartPoint = location2AisleLane(endShelving, startSide, true, trolleyIndex, trolleyCount); |
|
|
const aisleChangeEndPoint = location2AisleLane(endShelving, endSide, true, trolleyIndex, trolleyCount); |
|
|
ctx.lineTo(aisleChangeStartPoint.x, aisleChangeStartPoint.y); |
|
|
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeEndPoint.y); |
|
|
ctx.lineTo(endPoint.x, endPoint.y); |
|
|
} |
|
|
} |
|
|
} else if (startShelving.y === endShelving.y) { |
|
|
const startColumn = shelvingToColumn(startShelving); |
|
|
const endColumn = shelvingToColumn(endShelving); |
|
|
if (startSide === LEFT) { |
|
|
if (endSide === RIGHT && endColumn === startColumn - 1) { |
|
|
|
|
|
ctx.lineTo(endPoint.x, startPoint.y); |
|
|
ctx.lineTo(endPoint.x, endPoint.y); |
|
|
} else { |
|
|
drawWarehousePathBetweenColumns(ctx, trolleyIndex, trolleyCount, |
|
|
startShelving, startSide, startRow, startPoint, |
|
|
endShelving, endSide, endRow, endPoint); |
|
|
} |
|
|
} else { |
|
|
if (endSide === LEFT && endColumn === startColumn + 1) { |
|
|
|
|
|
ctx.lineTo(endPoint.x, startPoint.y); |
|
|
ctx.lineTo(endPoint.x, endPoint.y); |
|
|
} else { |
|
|
drawWarehousePathBetweenColumns(ctx, trolleyIndex, trolleyCount, |
|
|
startShelving, startSide, startRow, startPoint, |
|
|
endShelving, endSide, endRow, endPoint); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
drawWarehousePathBetweenColumns(ctx, trolleyIndex, trolleyCount, |
|
|
startShelving, startSide, startRow, startPoint, |
|
|
endShelving, endSide, endRow, endPoint); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function drawWarehousePathBetweenColumns(ctx, trolleyIndex, trolleyCount, |
|
|
startShelving, startSide, startRow, startPoint, |
|
|
endShelving, endSide, endRow, endPoint) { |
|
|
if (startShelving.y === endShelving.y) { |
|
|
const isAbove = startRow + endRow < 2*SHELVING_ROWS - startRow - endRow; |
|
|
const aisleChangeStartPoint = location2AisleLane(startShelving, startSide, isAbove, trolleyIndex, trolleyCount); |
|
|
const aisleChangeEndPoint = location2AisleLane(endShelving, endSide, isAbove, trolleyIndex, trolleyCount); |
|
|
ctx.lineTo(aisleChangeStartPoint.x, aisleChangeStartPoint.y); |
|
|
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeEndPoint.y); |
|
|
ctx.lineTo(endPoint.x, endPoint.y); |
|
|
} else { |
|
|
const isAbove = endShelving.y < startShelving.y; |
|
|
const aisleChangeStartPoint = location2AisleLane(startShelving, startSide, isAbove, trolleyIndex, trolleyCount); |
|
|
const aisleChangeEndPoint = location2AisleLane(endShelving, endSide, isAbove, trolleyIndex, trolleyCount); |
|
|
ctx.lineTo(aisleChangeStartPoint.x, aisleChangeStartPoint.y); |
|
|
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeStartPoint.y); |
|
|
ctx.lineTo(aisleChangeEndPoint.x, aisleChangeEndPoint.y); |
|
|
ctx.lineTo(endPoint.x, endPoint.y); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function location2Point(shelving, side, row, trolleyIndex, trolleyCount) { |
|
|
let x; |
|
|
let y; |
|
|
if (side === LEFT) { |
|
|
x = shelving.x - SHELVING_LINE_WIDTH - ((0.5 * SHELVING_PADDING) / trolleyCount) * trolleyIndex; |
|
|
} else { |
|
|
x = shelving.x + shelving.width + SHELVING_LINE_WIDTH + ((0.5 * SHELVING_PADDING) / trolleyCount) * trolleyIndex; |
|
|
} |
|
|
const rowWidth = shelving.height / SHELVING_ROWS; |
|
|
y = shelving.y + row * rowWidth; |
|
|
|
|
|
return new Point(x, y); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function location2AisleLane(shelving, side, isAbove, trolleyIndex, trolleyCount) { |
|
|
let x; |
|
|
let y; |
|
|
if (side === LEFT) { |
|
|
x = shelving.x - SHELVING_LINE_WIDTH - ((0.5 * SHELVING_PADDING) / trolleyCount) * trolleyIndex; |
|
|
} else { |
|
|
x = shelving.x + SHELVING_LINE_WIDTH + shelving.width + ((0.5 * SHELVING_PADDING) / trolleyCount) * trolleyIndex; |
|
|
} |
|
|
|
|
|
if (isAbove) { |
|
|
y = shelving.y - 0.25 * SHELVING_PADDING - (((0.25 * SHELVING_PADDING) / trolleyCount) * trolleyIndex); |
|
|
} else { |
|
|
y = shelving.y + shelving.height + 0.25 * SHELVING_PADDING + (((0.25 * SHELVING_PADDING) / trolleyCount) * trolleyIndex); |
|
|
} |
|
|
|
|
|
return new Point(x, y); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function shelvingToColumn(shelving) { |
|
|
const COLUMN_WIDTH = SHELVING_WIDTH + SHELVING_PADDING; |
|
|
return (shelving.x - WAREHOUSE_PADDING_LEFT) / COLUMN_WIDTH; |
|
|
} |