i have three.js code for a 2d charting application

My requirements:
have a line where the user can click and drag anywhere on the line.
the 'endpoint' of the line for the given movement should be considered whichever is closest to the mouse cursor
the end of the line should not jump to the mouse cursor but rather the movement of the lines endpoint should exactly mirror the movement of the cursor

Currently the code renders candlesticks. Please implement the additional functionality 


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Three.js Instanced Candlestick Example with Uniform Borders</title>
  <style>
    body { margin: 0; }
    canvas { display: block; }
  </style>
</head>
<body>
  <script type="module">

import * as THREE from './three.module.js';

// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);

// Camera setup
const camera = new THREE.OrthographicCamera(
  window.innerWidth / -200,
  window.innerWidth / 200,
  window.innerHeight / 200,
  window.innerHeight / -200,
  1,
  1000
);
camera.position.set(0, 0, 500);

// Renderer setup
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Function to generate random OHLC data
function generateOHLCData(count) {
  const data = [];
  let open = 50; // Starting price
  for (let i = 0; i < count; i++) {
    let high = open + Math.random() * 10;
    let low = open - Math.random() * 10;
    let close = low + Math.random() * (high - low);
    data.push({ open, high, low, close });
    open = close; // Next open starts where the last close was
  }
  return data;
}

// Convert RGB to normalized color values (0 to 1)
function rgbToNormalized(r, g, b) {
  return [r / 255, g / 255, b / 255];
}

// Create instanced candlesticks with uniform borders and wicks
function createInstancedCandlesticks(data) {
  const barWidth = 1;
  const maxCandlesticks = data.length;

  // Materials
  const material = new THREE.MeshBasicMaterial({ vertexColors: true });

  // Geometries for the candlestick body and borders
  const geometryBar = new THREE.BoxGeometry(barWidth, 1, 0);

  // Create instanced mesh for candlesticks
  const instancedMesh = new THREE.InstancedMesh(geometryBar, material, maxCandlesticks);      

  const dummy = new THREE.Object3D();
  const wickGeometry = new THREE.BufferGeometry();
  const wickVertices = new Float32Array(maxCandlesticks * 6); // 2 points for each wick
  const wickColors = new Float32Array(maxCandlesticks * 6); // Color for each wick point

  // Define colors in normalized RGB
  const bullishColor = rgbToNormalized(0, 70, 0); // Custom RGB for bullish wicks (green)
  const bearishColor = rgbToNormalized(138, 0, 0);  // Custom RGB for bearish wicks (red)
  const colorArray = new Float32Array(maxCandlesticks * 3); // RGB for each instance

  let currentX = 0;

  data.forEach((d, index) => {
    const isBullish = d.close >= d.open;
    const height = Math.abs(d.close - d.open);
    const y = (d.close + d.open) / 2;

    // Set the position and scale for the body
    dummy.position.set(currentX, y, 0);
    dummy.scale.set(barWidth, height, 1);
    dummy.updateMatrix();

    instancedMesh.setMatrixAt(index, dummy.matrix);

    // Set color for each instance
    if (isBullish) {
      colorArray.set(bullishColor, index * 3); // Set bullish color
      wickColors.set([...bullishColor, ...bullishColor], index * 6); // Set wick color (two points)
    } else {
      colorArray.set(bearishColor, index * 3); // Set bearish color
      wickColors.set([...bearishColor, ...bearishColor], index * 6); // Set wick color (two points)
    }

    // Wick positions
    wickVertices.set([
      currentX, d.low, -0.5,  // Bottom of the wick
      currentX, d.high, -0.5   // Top of the wick
    ], index * 6);

    // Increment x for the next candlestick
    currentX += barWidth * 1.5;
  });

  // Add the instances to the scene
  scene.add(instancedMesh);

  // Create and add wick line segments to the scene
  wickGeometry.setAttribute('position', new THREE.BufferAttribute(wickVertices, 3));
  wickGeometry.setAttribute('color', new THREE.BufferAttribute(wickColors, 3)); // Add color to wicks
  const wickMaterial = new THREE.LineBasicMaterial({ vertexColors: true }); // Enable vertex colors
  const wickLineSegments = new THREE.LineSegments(wickGeometry, wickMaterial);
  scene.add(wickLineSegments);

  // Add color attribute to geometry
  geometryBar.setAttribute('color', new THREE.InstancedBufferAttribute(colorArray, 3));

  return { instancedMesh, wickLineSegments };
}

// Generate data and create instanced candlesticks
const ohlcData = generateOHLCData(100000); // Adjust the number as needed
const { instancedMesh, wickLineSegments } = createInstancedCandlesticks(ohlcData);

instancedMesh.frustumCulled = true;
wickLineSegments.frustumCulled = true;

// Zoom logic (based on mouse wheel and key modifiers)
let zoomSpeed = 0.8;
let visibilityUpdateTimeout; // Timeout variable for visibility updates

function onWheel(event) {
  event.preventDefault();

  const rect = renderer.domElement.getBoundingClientRect();
  const mouseX = ((event.clientX - rect.left) / rect.width) * 2 - 1;
  const mouseY = -((event.clientY - rect.top) / rect.height) * 2 + 1;

  const mouseWorld = new THREE.Vector3(mouseX, mouseY, 0).unproject(camera);
  let zoom = 1 - event.deltaY * 0.001 * zoomSpeed;

  const ctrlPressed = event.ctrlKey;
  const shiftPressed = event.shiftKey;

  if (ctrlPressed) {
    camera.left = (camera.left - mouseWorld.x) * zoom + mouseWorld.x;
    camera.right = (camera.right - mouseWorld.x) * zoom + mouseWorld.x;
  } else if (shiftPressed) {
    camera.top = (camera.top - mouseWorld.y) * zoom + mouseWorld.y;
    camera.bottom = (camera.bottom - mouseWorld.y) * zoom + mouseWorld.y;
  } else {
    camera.left = (camera.left - mouseWorld.x) * zoom + mouseWorld.x;
    camera.right = (camera.right - mouseWorld.x) * zoom + mouseWorld.x;
    camera.top = (camera.top - mouseWorld.y) * zoom + mouseWorld.y;
    camera.bottom = (camera.bottom - mouseWorld.y) * zoom + mouseWorld.y;
  }

  camera.updateProjectionMatrix();

  // Clear previous timeout if it exists
  clearTimeout(visibilityUpdateTimeout);
  
  // Set a new timeout to update visibility after a delay
  visibilityUpdateTimeout = setTimeout(() => {
    updateCandlestickVisibility();
  }, 100); // Adjust the delay as needed (100ms in this case)
}

// Function to update candlestick visibility based on zoom level
function updateCandlestickVisibility() {
  const visibleWidth = camera.right - camera.left;
  const candlestickWidth = 1.5; // Width of each candlestick including spacing
  const visibleCandlesticks = visibleWidth / candlestickWidth;

  // Adjust the threshold for visibility based on zoom level
  const threshold = (visibleCandlesticks > 700) ? 700 : visibleCandlesticks;

  if (visibleCandlesticks > threshold) {
    instancedMesh.visible = false; // Hide bodies if too many are visible
    wickLineSegments.visible = true; // Always show wicks
  } else {
    instancedMesh.visible = true; // Show bodies when zoomed in
    wickLineSegments.visible = true; // Always show wicks
  }
}

// Prevent default browser behavior for wheel scrolling with Control or Shift key
window.addEventListener('wheel', onWheel, { passive: false });

// Prevent default context menu on right-click
window.addEventListener('contextmenu', (event) => {
  event.preventDefault();
});

// Render loop
function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

// Handle window resize
window.addEventListener('resize', () => {
  camera.left = window.innerWidth / -200;
  camera.right = window.innerWidth / 200;
  camera.top = window.innerHeight / 200;
  camera.bottom = window.innerHeight / -200;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  updateCandlestickVisibility();
});

// Start animation
animate();

// Panning functionality
let isPanning = false; // Track panning state
let previousMousePosition = new THREE.Vector2(); // Store previous mouse position

// Mouse down event for right-click
function onMouseDown(event) {
  if (event.button === 2) { // Right mouse button
    isPanning = true;
    previousMousePosition.set(event.clientX, event.clientY);
  }
}

// Mouse move event for panning
function onMouseMove(event) {
  if (isPanning) {
    const deltaX = event.clientX - previousMousePosition.x;
    const deltaY = event.clientY - previousMousePosition.y;

    const zoomFactorX = camera.right - camera.left;
    const zoomFactorY = camera.top - camera.bottom;

    const panX = (deltaX / window.innerWidth) * zoomFactorX;
    const panY = (deltaY / window.innerHeight) * zoomFactorY;

    camera.left -= panX;
    camera.right -= panX;
    camera.top += panY;
    camera.bottom += panY;
    camera.updateProjectionMatrix();

    previousMousePosition.set(event.clientX, event.clientY); // Update previous position

    // Update candlestick visibility after panning
    updateCandlestickVisibility();
  }
}

// Mouse up event to stop panning
function onMouseUp(event) {
  if (event.button === 2) { // Right mouse button
    isPanning = false;
  }
}

// Add event listeners for mouse actions
window.addEventListener('mousedown', onMouseDown);
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);

  </script>
</body>
</html>