Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                
              
            
!

CSS

              
                body,html{margin:0;padding:0;overflow:hidden}*,::after,::before{box-sizing:border-box}
              
            
!

JS

              
                let centerX, centerY;
let radius;
let fixedVector;
let draggableVector;
let isDragging = false;
let dotProduct;
let animateButton;
let isAnimating = false;
let animateAngle;

function setup() {
  document.body.style.margin = "0";
  document.body.style.padding = "0";
  
  // Create a canvas that spans the full window dimensions.
  let cnv = createCanvas(windowWidth, windowHeight);
  cnv.style('display', 'block'); // Ensure no extra inline spacing

  updateDimensions();
  
  // Initialize vectors
  fixedVector = createVector(0, -radius); // Points up
  draggableVector = createVector(radius * cos(PI / 4), radius * sin(PI / 4)); // 45 degrees
  animateAngle = draggableVector.heading(); // Initial angle for animation
  
  // Create the Animate button and position it at the bottom-right.
  animateButton = createButton("Start Animation");
  animateButton.position(windowWidth - 120, windowHeight - 50);
  animateButton.mousePressed(toggleAnimation);
  
  // Use a light background for a modern feel.
  background(248, 250, 252);
}

function updateDimensions() {
  centerX = windowWidth / 2;
  centerY = windowHeight / 2;
  radius = min(windowWidth, windowHeight) * 0.25; // Slightly smaller for a cleaner look
}

function drawGrid() {
  stroke(230, 236, 240);
  strokeWeight(1);
  // Set spacing relative to canvas width (adjust as desired)
  let spacing = width / 20;
  
  // Draw vertical grid lines.
  for (let x = 0; x <= width; x += spacing) {
    line(x, 0, x, height);
  }
  
  // Draw horizontal grid lines.
  for (let y = 0; y <= height; y += spacing) {
    line(0, y, width, y);
  }
}

function drawLegend() {
  let legendX = centerX - (radius * 2.5);
  let legendY = centerY - (radius * 1.75);
  
  fill(0);
  noStroke();
  textAlign(LEFT);
  textSize(14);
  
  // Fixed Vector legend
  stroke(255, 0, 0);
  strokeWeight(2);
  line(legendX, legendY, legendX + 30, legendY);
  noStroke();
  fill(0);
  text("Fixed Vector (v1)", legendX + 40, legendY + 5);
  
  // Draggable Vector legend
  legendY += 25;
  stroke(0, 0, 255);
  strokeWeight(2);
  line(legendX, legendY, legendX + 30, legendY);
  fill(0, 0, 255);
  circle(legendX + 30, legendY, 8);
  noStroke();
  fill(0);
  text("Draggable Vector (v2)", legendX + 40, legendY + 5);
  
  // Angle legend
  legendY += 25;
  fill(0, 150, 0, 50);
  stroke(0, 150, 0);
  arc(legendX + 15, legendY + 5, 30, 30, 0, PI / 2, PIE);
  noStroke();
  fill(0);
  text("Angle between vectors", legendX + 40, legendY + 5);
  
  // Instructions
  legendY += 40;
  fill(100);
  textSize(12);
  text("• Drag the blue dot to move vector v2", legendX, legendY);
}

function drawCircumferenceValues(normalizedFixed) {
  const numPoints = 16;
  const dotSize = 3;
  const spacing = TWO_PI / numPoints;
  
  // Get the draggable vector’s angle (normalized to [0, TWO_PI))
  let draggableAngle = draggableVector.heading();
  draggableAngle = (draggableAngle + TWO_PI) % TWO_PI;
  // Convert the angle to a floating-point index (0 to numPoints)
  let normalizedIndex = draggableAngle / spacing;
  
  for (let i = 0; i < numPoints; i++) {
    let angle = spacing * i;
    let x = centerX + cos(angle) * radius;
    let y = centerY + sin(angle) * radius;
    
    // Calculate the dot product for this circumference point.
    let pointVector = createVector(cos(angle), sin(angle));
    let pointDot = normalizedFixed.dot(pointVector);
    
    // Compute the circular distance (in index space) between this point and the draggable vector.
    let diff = abs(i - normalizedIndex);
    diff = min(diff, numPoints - diff); // Account for circular wrap–around
    
    // Only the closest value and its immediate neighbors get influenced.
    let proximity = 0;
    if (diff <= 1) {
      proximity = 1 - 0.5 * diff;
    } else {
      proximity = 0;
    }
    
    // Draw a connection line with opacity based on influence.
    let lineOpacity = map(proximity, 0, 1, 100, 220);
    stroke(200, 220, 240, lineOpacity);
    strokeWeight(1);
    line(centerX, centerY, x, y);
    
    // Draw the dot on the circumference with size based on influence.
    let currentDotSize = map(proximity, 0, 1, dotSize, dotSize * 2);
    noStroke();
    fill(100, 120, 140, map(proximity, 0, 1, 140, 255));
    circle(x, y, currentDotSize * 2);
    
    // Draw the value label with a background rectangle.
    let labelOffset = 25;
    let labelX = x + cos(angle) * labelOffset;
    let labelY = y + sin(angle) * labelOffset;
    
    let bgOpacity = map(proximity, 0, 1, 150, 230);
    fill(248, 250, 252, bgOpacity);
    noStroke();
    rect(labelX - 20, labelY - 10, 40, 20, 5);
    
    // Draw the text (dot product value) with a shadow for readability.
    let textWeight = map(proximity, 0, 1, 14, 20);
    textAlign(CENTER, CENTER);
    textSize(textWeight);
    if (proximity > 0) {
      fill(0, 0, 0, 100);
      text(pointDot.toFixed(2), labelX + 1, labelY + 1);
    }
    fill(60, map(proximity, 0, 1, 150, 255));
    text(pointDot.toFixed(2), labelX, labelY);
  }
}

function draw() {
  background(248, 250, 252);
  
  // If animation is enabled, update the draggable vector's angle.
  if (isAnimating) {
    animateAngle += 0.01;  // Adjust this value to change the speed of rotation.
    draggableVector = createVector(radius * cos(animateAngle), radius * sin(animateAngle));
  }
  
  drawGrid();
  
  // Draw the main circle with a subtle shadow effect.
  noFill();
  stroke(210, 220, 230);
  strokeWeight(3);
  circle(centerX, centerY, radius * 2 + 4);
  stroke(0, 20);
  circle(centerX, centerY, radius * 2);
  
  // Calculate angles for the vectors.
  let angle1 = fixedVector.heading();
  let angle2 = draggableVector.heading();
  
  let startAngle = angle1;
  let endAngle = angle2;
  let angleDiff = ((endAngle - startAngle + TWO_PI) % TWO_PI);
  if (angleDiff > PI) {
    angleDiff = TWO_PI - angleDiff;
    let temp = startAngle;
    startAngle = endAngle;
    endAngle = temp;
  }
  
  // Normalize vectors (used for computing the dot products).
  let normalizedFixed = p5.Vector.normalize(fixedVector);
  let normalizedDraggable = p5.Vector.normalize(draggableVector);
  dotProduct = normalizedFixed.dot(normalizedDraggable);
  
  // Draw circumference values before drawing the vectors for better layering.
  drawCircumferenceValues(normalizedFixed);
  
  // Draw the angle indicator.
  fill(0, 150, 0, 30);
  stroke(0, 150, 0);
  strokeWeight(2);
  let arcRadius = radius * 0.1;
  arc(centerX, centerY, arcRadius * 2, arcRadius * 2, startAngle, endAngle, PIE);
  fill(0, 0, 255, 15);
  stroke(0, 0, 255, 70);
  arc(centerX, centerY, radius * 2, radius * 2, startAngle, endAngle, PIE);
  
  // Draw the vectors with a modern style.
  // Fixed vector (red)
  stroke(255, 0, 0);
  strokeWeight(2);
  line(centerX, centerY, centerX + fixedVector.x, centerY + fixedVector.y);
  drawArrowhead(createVector(centerX, centerY), fixedVector, color(255, 0, 0));
  
  // Draggable vector (blue)
  stroke(0, 0, 255);
  line(centerX, centerY, centerX + draggableVector.x, centerY + draggableVector.y);
  drawArrowhead(createVector(centerX, centerY), draggableVector, color(0, 0, 255));
  
  // Draw the handle with a shadow effect.
  noStroke();
  fill(0, 0, 255, 50);
  circle(centerX + draggableVector.x, centerY + draggableVector.y, 14);
  fill(0, 0, 255);
  circle(centerX + draggableVector.x, centerY + draggableVector.y, 10);

  // Draw the angle label.
  let midAngle = startAngle + angleDiff / 2;
  let labelRadius = arcRadius * 2.75;
  let labelX = centerX + cos(midAngle) * labelRadius * 0.8;
  let labelY = centerY + sin(midAngle) * labelRadius * 0.8;
  fill(0, 150, 0);
  noStroke();
  textAlign(CENTER, CENTER);
  textSize(14);
  text(degrees(angleDiff).toFixed(1) + "°", labelX, labelY);
  
  drawDotProductCard();
  drawLegend();
}

function drawDotProductCard() {
  // Draw the card background with a shadow effect.
  fill(255);
  noStroke();
  rect(centerX - 100, centerY + radius + 50, 200, 60, 10);
  
  // Draw the dot product value.
  textAlign(CENTER);
  fill(100);
  textSize(14);
  text("Dot Product", centerX, centerY + radius + 70);
  fill(0);
  textSize(18);
  text(dotProduct.toFixed(3), centerX, centerY + radius + 90);
}

function drawArrowhead(base, vector, arrowColor) {
  const arrowSize = 12;
  const angle = vector.heading();
  
  push();
  translate(base.x + vector.x, base.y + vector.y);
  rotate(angle);
  fill(arrowColor);
  noStroke();
  triangle(0, 0, -arrowSize, arrowSize / 2, -arrowSize, -arrowSize / 2);
  pop();
}

function mousePressed() {
  // Only allow dragging when not animating.
  let d = dist(mouseX, mouseY, centerX + draggableVector.x, centerY + draggableVector.y);
  if (d < 10 && !isAnimating) {
    isDragging = true;
  }
}

function mouseReleased() {
  isDragging = false;
}

function mouseDragged() {
  // Disable manual dragging while animating.
  if (isAnimating) return;
  if (isDragging) {
    let mouseVector = createVector(mouseX - centerX, mouseY - centerY);
    mouseVector.setMag(radius);
    draggableVector = mouseVector;
  }
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  updateDimensions();
  fixedVector.setMag(radius);
  draggableVector.setMag(radius);
  // Reposition the animate button.
  animateButton.position(windowWidth - 120, windowHeight - 50);
}

function toggleAnimation() {
  isAnimating = !isAnimating;
  if (isAnimating) {
    animateButton.html("Stop Animation");
    // Sync the animation angle with the current draggable vector.
    animateAngle = draggableVector.heading();
  } else {
    animateButton.html("Start Animation");
  }
}

              
            
!
999px

Console