// Initialize drag and drop functionality function initializeDragAndDrop() { const nodeItems = document.querySelectorAll('.node-item'); const canvas = document.getElementById('network-canvas'); let draggedNode = null; let offsetX, offsetY; let isDragging = false; let isConnecting = false; let startNode = null; let connectionLine = null; let nodeCounter = {}; // Track layers for proper architecture building let networkLayers = { layers: [], connections: [] }; // Add event listeners to draggable items nodeItems.forEach(item => { item.addEventListener('dragstart', handleDragStart); }); // Canvas events for dropping nodes canvas.addEventListener('dragover', handleDragOver); canvas.addEventListener('drop', handleDrop); // Handle drag start event function handleDragStart(e) { draggedNode = this; e.dataTransfer.setData('text/plain', this.getAttribute('data-type')); // Set a ghost image for drag (optional) const ghost = this.cloneNode(true); ghost.style.opacity = '0.5'; document.body.appendChild(ghost); e.dataTransfer.setDragImage(ghost, 0, 0); setTimeout(() => { document.body.removeChild(ghost); }, 0); } // Handle drag over event function handleDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; } // Handle drop event to create new nodes on the canvas function handleDrop(e) { e.preventDefault(); // Hide the canvas hint when nodes are added const canvasHint = document.querySelector('.canvas-hint'); if (canvasHint) { canvasHint.style.display = 'none'; } const nodeType = e.dataTransfer.getData('text/plain'); if (nodeType) { // Generate unique layer ID const layerId = window.neuralNetwork.getNextLayerId(nodeType); // Create a new node on the canvas const canvasNode = document.createElement('div'); canvasNode.className = `canvas-node ${nodeType}-node`; canvasNode.setAttribute('data-type', nodeType); canvasNode.setAttribute('data-id', layerId); // Set node position const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; canvasNode.style.left = `${x}px`; canvasNode.style.top = `${y}px`; // Get default config for this node type const nodeConfig = window.neuralNetwork.createNodeConfig(nodeType); // Create node content with input and output shape information let nodeName, inputShape, outputShape, parameters; switch(nodeType) { case 'input': nodeName = 'Input Layer'; inputShape = 'N/A'; outputShape = '[' + nodeConfig.shape.join(' × ') + ']'; parameters = nodeConfig.parameters; break; case 'hidden': const hiddenCount = document.querySelectorAll('.canvas-node[data-type="hidden"]').length; nodeConfig.units = hiddenCount === 0 ? 128 : 64; nodeName = `Hidden Layer ${hiddenCount + 1}`; // Input shape will be updated when connections are made inputShape = 'Connect input'; outputShape = `[${nodeConfig.units}]`; parameters = 'Connect input to calculate'; break; case 'output': nodeName = 'Output Layer'; inputShape = 'Connect input'; outputShape = `[${nodeConfig.units}]`; parameters = 'Connect input to calculate'; break; case 'conv': const convCount = document.querySelectorAll('.canvas-node[data-type="conv"]').length; nodeConfig.filters = 32 * (convCount + 1); nodeName = `Conv2D ${convCount + 1}`; inputShape = 'Connect input'; outputShape = 'Depends on input'; // Create parameter string parameters = `In: ?, Out: ${nodeConfig.filters}\nKernel: ${nodeConfig.kernelSize.join('×')}\nStride: ${nodeConfig.strides.join('×')}\nPadding: ${nodeConfig.padding}`; break; case 'pool': const poolCount = document.querySelectorAll('.canvas-node[data-type="pool"]').length; nodeName = `Pooling ${poolCount + 1}`; inputShape = 'Connect input'; outputShape = 'Depends on input'; parameters = `Pool size: ${nodeConfig.poolSize.join('×')}\nStride: ${nodeConfig.strides.join('×')}\nPadding: ${nodeConfig.padding}`; break; default: nodeName = 'Unknown Layer'; inputShape = 'N/A'; outputShape = 'N/A'; parameters = 'N/A'; } // Create node header const nodeHeader = document.createElement('div'); nodeHeader.className = 'node-header'; nodeHeader.textContent = nodeName; // Create node content const nodeContent = document.createElement('div'); nodeContent.className = 'node-content'; // Add shape information in a structured way const shapeInfo = document.createElement('div'); shapeInfo.className = 'shape-info'; shapeInfo.innerHTML = `
${parameters}`;
// Add connection ports
const inputPort = document.createElement('div');
inputPort.className = 'port input-port';
inputPort.setAttribute('data-port-type', 'input');
const outputPort = document.createElement('div');
outputPort.className = 'port output-port';
outputPort.setAttribute('data-port-type', 'output');
// Assemble the node
nodeContent.appendChild(shapeInfo);
nodeContent.appendChild(paramsSection);
canvasNode.appendChild(nodeHeader);
canvasNode.appendChild(nodeContent);
canvasNode.appendChild(inputPort);
canvasNode.appendChild(outputPort);
// Add node to the canvas
canvas.appendChild(canvasNode);
// Store node configuration
canvasNode.layerConfig = nodeConfig;
// Add event listeners for node manipulation
canvasNode.addEventListener('mousedown', startDrag);
inputPort.addEventListener('mousedown', (e) => {
e.stopPropagation();
});
outputPort.addEventListener('mousedown', (e) => {
e.stopPropagation();
startConnection(canvasNode, e);
});
// Double-click to edit node properties
canvasNode.addEventListener('dblclick', () => {
openLayerEditor(canvasNode);
});
// Right-click to delete
canvasNode.addEventListener('contextmenu', (e) => {
e.preventDefault();
deleteNode(canvasNode);
});
// Add to network layers for architecture building
networkLayers.layers.push({
id: layerId,
type: nodeType,
config: nodeConfig
});
// Notify about network changes
document.dispatchEvent(new CustomEvent('networkUpdated', {
detail: networkLayers
}));
updateConnections();
}
}
// Start dragging an existing node on the canvas
function startDrag(e) {
if (isConnecting) return;
// Only start drag if not clicking on buttons or ports
if (e.target.closest('.node-controls') || e.target.closest('.node-port')) {
return;
}
isDragging = true;
const target = e.target.closest('.canvas-node');
const rect = target.getBoundingClientRect();
// Calculate offset
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
document.addEventListener('mousemove', dragNode);
document.addEventListener('mouseup', stopDrag);
// Reference to the dragged node
draggedNode = target;
// Make the dragged node appear on top
draggedNode.style.zIndex = "100";
// Add dragging class for visual feedback
draggedNode.classList.add('dragging');
// Prevent default behavior
e.preventDefault();
}
// Drag node on the canvas
function dragNode(e) {
if (!isDragging) return;
const canvasRect = canvas.getBoundingClientRect();
let x = e.clientX - canvasRect.left - offsetX;
let y = e.clientY - canvasRect.top - offsetY;
// Constrain to canvas
x = Math.max(0, Math.min(canvasRect.width - draggedNode.offsetWidth, x));
y = Math.max(0, Math.min(canvasRect.height - draggedNode.offsetHeight, y));
draggedNode.style.left = `${x}px`;
draggedNode.style.top = `${y}px`;
// Update node position in network layers
const nodeId = draggedNode.getAttribute('data-id');
const layerIndex = networkLayers.layers.findIndex(layer => layer.id === nodeId);
if (layerIndex !== -1) {
networkLayers.layers[layerIndex].position = { x, y };
}
// Update connected lines if any
updateConnections();
}
// Stop dragging
function stopDrag() {
if (!isDragging) return;
isDragging = false;
document.removeEventListener('mousemove', dragNode);
document.removeEventListener('mouseup', stopDrag);
// Reset z-index and remove dragging class
if (draggedNode) {
draggedNode.style.zIndex = "10";
draggedNode.classList.remove('dragging');
// Trigger connections update one more time
updateConnections();
}
}
// Start creating a connection between nodes
function startConnection(node, e) {
isConnecting = true;
startNode = node;
// Create a temporary line
connectionLine = document.createElement('div');
connectionLine.className = 'connection temp-connection';
// Get start position (center of the port)
const portOut = node.querySelector('.port-out');
const portRect = portOut.getBoundingClientRect();
const canvasRect = canvas.getBoundingClientRect();
const startX = portRect.left + portRect.width / 2 - canvasRect.left;
const startY = portRect.top + portRect.height / 2 - canvasRect.top;
// Position the line
connectionLine.style.left = `${startX}px`;
connectionLine.style.top = `${startY}px`;
connectionLine.style.width = '0px';
connectionLine.style.transform = 'rotate(0deg)';
// Add active class to the starting port
portOut.classList.add('active-port');
// Highlight valid target ports
highlightValidConnectionTargets(node);
canvas.appendChild(connectionLine);
// Add event listeners for drawing the line
document.addEventListener('mousemove', drawConnection);
document.addEventListener('mouseup', cancelConnection);
e.preventDefault();
}
// Highlight valid targets for connection
function highlightValidConnectionTargets(sourceNode) {
const sourceType = sourceNode.getAttribute('data-type');
const sourceId = sourceNode.getAttribute('data-id');
document.querySelectorAll('.canvas-node').forEach(node => {
if (node !== sourceNode) {
const nodeType = node.getAttribute('data-type');
const nodeId = node.getAttribute('data-id');
const isValidTarget = isValidConnection(sourceType, nodeType, sourceId, nodeId);
const portIn = node.querySelector('.port-in');
if (isValidTarget) {
portIn.classList.add('valid-target');
} else {
portIn.classList.add('invalid-target');
}
}
});
}
// Remove highlights from all ports
function removePortHighlights() {
document.querySelectorAll('.port-in, .port-out').forEach(port => {
port.classList.remove('active-port', 'valid-target', 'invalid-target');
});
}
// Check if a connection between two node types is valid
function isValidConnection(sourceType, targetType, sourceId, targetId) {
// Basic hierarchy validation
if (sourceType === 'output' || targetType === 'input') {
return false; // Output can't have outgoing connections, Input can't have incoming
}
// Prevent cycles
const existingConnection = networkLayers.connections.find(
conn => conn.target === sourceId && conn.source === targetId
);
if (existingConnection) {
return false;
}
// Specific connection rules
switch(sourceType) {
case 'input':
return ['hidden', 'conv'].includes(targetType);
case 'conv':
return ['conv', 'pool', 'hidden'].includes(targetType);
case 'pool':
return ['conv', 'hidden'].includes(targetType);
case 'hidden':
return ['hidden', 'output'].includes(targetType);
default:
return false;
}
}
// Draw the connection line as mouse moves
function drawConnection(e) {
if (!isConnecting || !connectionLine) return;
const canvasRect = canvas.getBoundingClientRect();
const portOut = startNode.querySelector('.port-out');
const portRect = portOut.getBoundingClientRect();
// Calculate start and end points
const startX = portRect.left + portRect.width / 2 - canvasRect.left;
const startY = portRect.top + portRect.height / 2 - canvasRect.top;
const endX = e.clientX - canvasRect.left;
const endY = e.clientY - canvasRect.top;
// Calculate length and angle
const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI;
// Update line
connectionLine.style.width = `${length}px`;
connectionLine.style.transform = `rotate(${angle}deg)`;
// Highlight the port under cursor
document.querySelectorAll('.canvas-node').forEach(node => {
if (node !== startNode) {
const nodeRect = node.getBoundingClientRect();
const portIn = node.querySelector('.port-in');
const portInRect = portIn.getBoundingClientRect();
// Check if mouse is over the input port
if (e.clientX >= portInRect.left && e.clientX <= portInRect.right &&
e.clientY >= portInRect.top && e.clientY <= portInRect.bottom) {
portIn.classList.add('port-hover');
} else {
portIn.classList.remove('port-hover');
}
}
});
}
// Cancel connection creation
function cancelConnection(e) {
if (!isConnecting) return;
// Find if we're over a valid input port
let targetNode = null;
document.querySelectorAll('.canvas-node').forEach(node => {
if (node !== startNode) {
const portIn = node.querySelector('.port-in');
const portRect = portIn.getBoundingClientRect();
if (e.clientX >= portRect.left && e.clientX <= portRect.right &&
e.clientY >= portRect.top && e.clientY <= portRect.bottom) {
// Check if this would be a valid connection
const sourceType = startNode.getAttribute('data-type');
const targetType = node.getAttribute('data-type');
const sourceId = startNode.getAttribute('data-id');
const targetId = node.getAttribute('data-id');
if (isValidConnection(sourceType, targetType, sourceId, targetId)) {
targetNode = node;
}
}
}
});
// If we found a valid target, create the connection
if (targetNode) {
endConnection(targetNode);
} else {
// Otherwise, remove the temporary line
if (connectionLine && connectionLine.parentNode) {
connectionLine.parentNode.removeChild(connectionLine);
}
}
// Remove all port highlights
removePortHighlights();
document.querySelectorAll('.port-hover').forEach(port => {
port.classList.remove('port-hover');
});
// Reset variables
isConnecting = false;
startNode = null;
connectionLine = null;
// Remove event listeners
document.removeEventListener('mousemove', drawConnection);
document.removeEventListener('mouseup', cancelConnection);
}
// End creating a connection
function endConnection(targetNode) {
if (!isConnecting || !connectionLine || !startNode) return;
const sourceType = startNode.getAttribute('data-type');
const targetType = targetNode.getAttribute('data-type');
const sourceId = startNode.getAttribute('data-id');
const targetId = targetNode.getAttribute('data-id');
// Check if this is a valid connection
if (isValidConnection(sourceType, targetType, sourceId, targetId)) {
// Create a permanent SVG connection
const canvas = document.getElementById('network-canvas');
const svgContainer = document.querySelector('#network-canvas .svg-container') || createSVGContainer();
// Get positions for source and target nodes
const sourceRect = startNode.getBoundingClientRect();
const targetRect = targetNode.getBoundingClientRect();
const canvasRect = canvas.getBoundingClientRect();
// Calculate port positions
const sourcePort = startNode.querySelector('.output-port');
const targetPort = targetNode.querySelector('.input-port');
const sourcePortRect = sourcePort.getBoundingClientRect();
const targetPortRect = targetPort.getBoundingClientRect();
const startX = sourcePortRect.left + (sourcePortRect.width / 2) - canvasRect.left;
const startY = sourcePortRect.top + (sourcePortRect.height / 2) - canvasRect.top;
const endX = targetPortRect.left + (targetPortRect.width / 2) - canvasRect.left;
const endY = targetPortRect.top + (targetPortRect.height / 2) - canvasRect.top;
// Create the connection
const pathId = `connection-${sourceId}-${targetId}`;
const connectionPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
connectionPath.setAttribute('id', pathId);
connectionPath.setAttribute('class', 'connection-line');
// Curved path (bezier)
const dx = Math.abs(endX - startX) * 0.7;
const path = `M ${startX} ${startY} C ${startX + dx} ${startY}, ${endX - dx} ${endY}, ${endX} ${endY}`;
connectionPath.setAttribute('d', path);
// Add connection to SVG container
svgContainer.appendChild(connectionPath);
// Add to connections
networkLayers.connections.push({
id: pathId,
source: sourceId,
target: targetId,
sourceType: sourceType,
targetType: targetType
});
// Update input and output shapes
updateNodeShapes(sourceId, targetId);
// Notify about connection
document.dispatchEvent(new CustomEvent('networkUpdated', {
detail: networkLayers
}));
}
// Clean up
removePortHighlights();
if (connectionLine) {
connectionLine.remove();
connectionLine = null;
}
isConnecting = false;
startNode = null;
}
// Update input and output shapes when connections are made
function updateNodeShapes(sourceId, targetId) {
const sourceNode = document.querySelector(`.canvas-node[data-id="${sourceId}"]`);
const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`);
if (sourceNode && targetNode) {
const sourceConfig = sourceNode.layerConfig;
const targetConfig = targetNode.layerConfig;
// Update the target's input shape based on the source's output shape
if (sourceConfig && targetConfig) {
// Calculate output shape based on node type
let outputShape;
switch (sourceNode.getAttribute('data-type')) {
case 'input':
outputShape = sourceConfig.shape;
break;
case 'hidden':
outputShape = [sourceConfig.units];
break;
case 'output':
outputShape = [sourceConfig.units];
break;
case 'conv':
// For Conv2D, the output shape depends on the input and parameters
// This is a simplified calculation
if (targetConfig.inputShape) {
const h = targetConfig.inputShape[0];
const w = targetConfig.inputShape[1];
const kh = sourceConfig.kernelSize[0];
const kw = sourceConfig.kernelSize[1];
const sh = sourceConfig.strides[0];
const sw = sourceConfig.strides[1];
const padding = sourceConfig.padding;
let outHeight, outWidth;
if (padding === 'same') {
outHeight = Math.ceil(h / sh);
outWidth = Math.ceil(w / sw);
} else { // 'valid'
outHeight = Math.ceil((h - kh + 1) / sh);
outWidth = Math.ceil((w - kw + 1) / sw);
}
outputShape = [outHeight, outWidth, sourceConfig.filters];
} else {
outputShape = ['?', '?', sourceConfig.filters];
}
break;
case 'pool':
// For pooling, also depends on the input and parameters
if (targetConfig.inputShape) {
const h = targetConfig.inputShape[0];
const w = targetConfig.inputShape[1];
const c = targetConfig.inputShape[2];
const ph = sourceConfig.poolSize[0];
const pw = sourceConfig.poolSize[1];
const sh = sourceConfig.strides[0];
const sw = sourceConfig.strides[1];
const padding = sourceConfig.padding;
let outHeight, outWidth;
if (padding === 'same') {
outHeight = Math.ceil(h / sh);
outWidth = Math.ceil(w / sw);
} else { // 'valid'
outHeight = Math.ceil((h - ph + 1) / sh);
outWidth = Math.ceil((w - pw + 1) / sw);
}
outputShape = [outHeight, outWidth, c];
} else {
outputShape = ['?', '?', '?'];
}
break;
case 'linear':
outputShape = [sourceConfig.outputFeatures];
break;
default:
outputShape = ['?', '?', '?'];
}
// Update the target's input shape
targetConfig.inputShape = outputShape;
// Update UI
updateNodeDisplayShapes(sourceNode, targetNode);
}
}
}
// Update the displayed shapes in the UI
function updateNodeDisplayShapes(sourceNode, targetNode) {
if (sourceNode && targetNode) {
const sourceType = sourceNode.getAttribute('data-type');
const targetType = targetNode.getAttribute('data-type');
const sourceConfig = sourceNode.layerConfig;
const targetConfig = targetNode.layerConfig;
// Update source node output shape display
const sourceOutputElem = sourceNode.querySelector('.output-shape');
if (sourceOutputElem && sourceConfig) {
let outputText;
switch (sourceType) {
case 'input':
outputText = `[${sourceConfig.shape.join(' × ')}]`;
break;
case 'hidden':
case 'output':
outputText = `[${sourceConfig.units}]`;
break;
case 'conv':
if (sourceConfig.outputShape) {
outputText = `[${sourceConfig.outputShape.join(' × ')}]`;
} else {
outputText = `[? × ? × ${sourceConfig.filters}]`;
}
break;
case 'pool':
if (sourceConfig.outputShape) {
outputText = `[${sourceConfig.outputShape.join(' × ')}]`;
} else {
outputText = 'Depends on input';
}
break;
case 'linear':
outputText = `[${sourceConfig.outputFeatures}]`;
break;
default:
outputText = 'Unknown';
}
sourceOutputElem.textContent = outputText;
}
// Update target node input shape display
const targetInputElem = targetNode.querySelector('.input-shape');
if (targetInputElem && targetConfig && targetConfig.inputShape) {
targetInputElem.textContent = `[${targetConfig.inputShape.join(' × ')}]`;
// Update parameters section
const targetParamsElem = targetNode.querySelector('.params-display');
if (targetParamsElem) {
// Calculate and display parameters
let paramsText = '';
switch (targetType) {
case 'hidden':
const inputUnits = Array.isArray(targetConfig.inputShape) ?
targetConfig.inputShape.reduce((acc, val) => acc * val, 1) :
targetConfig.inputShape;
const biasParams = targetConfig.useBias ? targetConfig.units : 0;
const totalParams = (inputUnits * targetConfig.units) + biasParams;
paramsText = `In: ${inputUnits}, Out: ${targetConfig.units}\nParams: ${totalParams.toLocaleString()}\nDropout: ${targetConfig.dropoutRate}`;
break;
case 'output':
const outInputUnits = Array.isArray(targetConfig.inputShape) ?
targetConfig.inputShape.reduce((acc, val) => acc * val, 1) :
targetConfig.inputShape;
const outBiasParams = targetConfig.useBias ? targetConfig.units : 0;
const outTotalParams = (outInputUnits * targetConfig.units) + outBiasParams;
paramsText = `In: ${outInputUnits}, Out: ${targetConfig.units}\nParams: ${outTotalParams.toLocaleString()}\nActivation: ${targetConfig.activation}`;
break;
case 'conv':
const channels = targetConfig.inputShape[2] || '?';
const kernelH = targetConfig.kernelSize[0];
const kernelW = targetConfig.kernelSize[1];
const kernelParams = kernelH * kernelW * channels * targetConfig.filters;
const convBiasParams = targetConfig.useBias ? targetConfig.filters : 0;
const convTotalParams = kernelParams + convBiasParams;
paramsText = `In: ${channels}, Out: ${targetConfig.filters}\nKernel: ${targetConfig.kernelSize.join('×')}\nStride: ${targetConfig.strides.join('×')}\nPadding: ${targetConfig.padding}\nParams: ${convTotalParams.toLocaleString()}`;
break;
case 'pool':
paramsText = `Pool size: ${targetConfig.poolSize.join('×')}\nStride: ${targetConfig.strides.join('×')}\nPadding: ${targetConfig.padding}\nParams: 0`;
break;
case 'linear':
const linearInputs = targetConfig.inputFeatures;
const linearBiasParams = targetConfig.useBias ? targetConfig.outputFeatures : 0;
const linearTotalParams = (linearInputs * targetConfig.outputFeatures) + linearBiasParams;
paramsText = `In: ${linearInputs}, Out: ${targetConfig.outputFeatures}\nParams: ${linearTotalParams.toLocaleString()}\nLearning Rate: ${targetConfig.learningRate}\nLoss: ${targetConfig.lossFunction}`;
break;
}
targetParamsElem.textContent = paramsText;
}
}
}
}
// Delete a node and its connections
function deleteNode(node) {
if (!node) return;
const nodeId = node.getAttribute('data-id');
// Remove all connections to/from this node
document.querySelectorAll(`.connection[data-source="${nodeId}"], .connection[data-target="${nodeId}"]`).forEach(conn => {
conn.parentNode.removeChild(conn);
});
// Remove from network layers
networkLayers.layers = networkLayers.layers.filter(layer => layer.id !== nodeId);
networkLayers.connections = networkLayers.connections.filter(conn =>
conn.source !== nodeId && conn.target !== nodeId
);
// Remove the node
node.parentNode.removeChild(node);
// Update layer connectivity
updateLayerConnectivity();
}
// Open layer editor modal
function openLayerEditor(node) {
if (!node) return;
const nodeId = node.getAttribute('data-id');
const nodeType = node.getAttribute('data-type');
const nodeName = node.getAttribute('data-name');
const dimensions = node.getAttribute('data-dimensions');
// Trigger custom event
const event = new CustomEvent('openLayerEditor', {
detail: { id: nodeId, type: nodeType, name: nodeName, dimensions: dimensions }
});
document.dispatchEvent(event);
}
// Update connections when nodes are moved
function updateConnections() {
const connections = document.querySelectorAll('.connection');
connections.forEach(connection => {
const sourceId = connection.getAttribute('data-source');
const targetId = connection.getAttribute('data-target');
const sourceNode = document.querySelector(`.canvas-node[data-id="${sourceId}"]`);
const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`);
if (sourceNode && targetNode) {
const sourcePort = sourceNode.querySelector('.port-out');
const targetPort = targetNode.querySelector('.port-in');
if (sourcePort && targetPort) {
const sourceRect = sourcePort.getBoundingClientRect();
const targetRect = targetPort.getBoundingClientRect();
const canvasRect = canvas.getBoundingClientRect();
const startX = sourceRect.left + sourceRect.width / 2 - canvasRect.left;
const startY = sourceRect.top + sourceRect.height / 2 - canvasRect.top;
const endX = targetRect.left + targetRect.width / 2 - canvasRect.left;
const endY = targetRect.top + targetRect.height / 2 - canvasRect.top;
const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI;
connection.style.left = `${startX}px`;
connection.style.top = `${startY}px`;
connection.style.width = `${length}px`;
connection.style.transform = `rotate(${angle}deg)`;
}
} else {
// If either node is missing, remove the connection
if (connection.parentNode) {
connection.parentNode.removeChild(connection);
// Remove from the connections array
const connIndex = networkLayers.connections.findIndex(conn =>
conn.source === sourceId && conn.target === targetId
);
if (connIndex !== -1) {
networkLayers.connections.splice(connIndex, 1);
}
}
}
});
}
// Get the current network architecture
function getNetworkArchitecture() {
return networkLayers;
}
// Clear all nodes from the canvas
function clearAllNodes() {
// Clear all nodes and connections
document.querySelectorAll('.canvas-node, .connection').forEach(el => {
el.parentNode.removeChild(el);
});
// Reset network layers
networkLayers = {
layers: [],
connections: []
};
// Reset layer counter
window.neuralNetwork.resetLayerCounter();
// Show the canvas hint
const canvasHint = document.querySelector('.canvas-hint');
if (canvasHint) {
canvasHint.style.display = 'block';
}
// Trigger network updated event
const event = new CustomEvent('networkUpdated', { detail: networkLayers });
document.dispatchEvent(event);
}
// Export functions
window.dragDrop = {
getNetworkArchitecture,
clearAllNodes,
updateConnections
};
}