What is the best way to incorporate arrowheads into the lines that have been sketched using canvas

I need assistance with incorporating arrowheads at the end of linear plots drawn on a coordinate grid using my custom function. You can view an example of the current setup in this JsFiddle: https://jsfiddle.net/zje14n92/1/

Below is the code snippet responsible for drawing the linear plots:

if (canvas1.getContext) {
canvas1.width  = x_axis * 2;
canvas1.height = y_axis * 2;
var ctx1 = canvas1.getContext("2d");

ctx1.font = "10px sans-serif";
ctx1.strokeText('   ', x_axis+50, 50);

ctx1.lineWidth = 1;

ctx1.beginPath();

ctx1.strokeStyle = 'black';

x = -x_max;
y = 4*x + 5; // Modify this line to change the equation
ctx1.moveTo(offsetX(x), offsetY(y));

while (x < x_max) { // Include broken line code here
    x += 0.1;
    y = 4*x+5; // Another line to modify equation
    ctx1.lineTo(offsetX(x), offsetY(y));
}
ctx1.stroke();

ctx1.closePath();

I've been trying to implement arrowheads at the endpoints similar to what I found in this JsFiddle (http://jsfiddle.net/m1erickson/Sg7EZ/), but I'm struggling to integrate it into my existing code. If anyone knows of a straightforward way to achieve this, I'd greatly appreciate your guidance!

var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
startRadians+=((this.x2>this.x1)?-90:90)*Math.PI/180;
        this.drawArrowhead(ctx,this.x1,this.y1,startRadians);
        // draw the ending arrowhead
        var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
        endRadians+=((this.x2>this.x1)?90:-90)*Math.PI/180;
        this.drawArrowhead(ctx,this.x2,this.y2,endRadians);
    }

    Line.prototype.drawArrowhead=function(ctx,x,y,radians){
        ctx.save();
        ctx.beginPath();
        ctx.translate(x, y);
        ctx.rotate(radians);
        ctx.moveTo(0,0);
        ctx.lineTo(5,20);
        ctx.lineTo(-5,20);
        ctx.closePath();
        ctx.restore();
        ctx.fill();
}

If there is a simple method to add arrowheads to the ends of lines, please advise. Thank you for any help provided!

Answer №1

To identify any points where your infinite plot line exits the canvas (a rectangle).

The plot line may exit the canvas at 0, 1, or 2 points. If it exits at 0 or 1 point, arrowheads are not required. If it exits at 2 points, place arrowheads at both exit points using the arrowhead fiddle.

In order to check for exit points, consider the canvas rectangle as four lines forming the rectangle. Then check for intersections between the plot line and each of these four rectangle lines.

This function detects intersections between two lines:

// Credit: http://paulbourke.net/geometry/pointlineplane/
// p0 & p1 represent points on the first line
// p2 & p3 represent points on the second line
// returns the intersection point (null if no intersection)
function line2lineIntersection(p0,p1,p2,p3) {

    var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
    var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
    var denominator  = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);        

    // Check for Coincident Lines
    if(unknownA==0 && unknownB==0 && denominator==0){return(null);}

    // Check for Parallel Lines
    if (denominator == 0) return null;

    // Criteria for Intersection of Line Segments
    unknownA /= denominator;
    unknownB /= denominator;

    var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1)

    if(!isIntersecting){return(null);}

    return({
        x: p0.x + unknownA * (p1.x-p0.x),
        y: p0.y + unknownA * (p1.y-p0.y)
    });
}

Identify intersections between the plot line and the 4 canvas rectangle lines:

var exitTop=line2lineIntersection(
    {x:0,y:0}, { x:canvas.width, y:0},
    yourLinePoint0, yourLinePoint1
);

var exitRight=line2lineIntersection(
    {x:canvas.width,y:0}, { x:canvas.width, y:canvas.height},
    yourLinePoint0, yourLinePoint1
);

var exitBottom=line2lineIntersection(
    {x:0,y:canvas.height}, { x:canvas.width, y:canvas.height},
    yourLinePoint0, yourLinePoint1
);

var exitLeft=line2lineIntersection(
    {x:0,y:0}, { x:0, y:canvas.height},
    yourLinePoint0, yourLinePoint1
);

var intersections=[];
if(exitTop){ intersections.push(exitTop); }
if(exitRight){ intersections.push(exitRight); }
if(exitBottom){ intersections.push(exitBottom); }
if(exitLeft){ intersections.push(exitLeft); }

if(intersections.length==2){
    // feed your 2 exit points into your arrow drawing script 
}

Proceed by feeding the 2 exit points into your arrow drawing script:

// create a new line object
var line=new Line(
    intersections[0].x, intersections[0].y,
    intersections[1].x, intersections[1].y
);

// draw the line
line.drawWithArrowheads(context);

Example code and Demo available via provided link

Click <a href="https://i.sstatic.net/qzTPq.png" rel="nofollow noreferrer">here</a> to view addition

[Include answer code in initial script]

https://i.sstatic.net/qzTPq.png

///////////
//
function Line(x1,y1,x2,y2){
    this.x1=x1;
    this.y1=y1;
    this.x2=x2;
    this.y2=y2;
}
Line.prototype.drawWithArrowheads=function(color){
    ctx.strokeStyle=color || "black";
    ctx.fillStyle=color || "black";
    ctx.lineWidth=1;   
    ctx.beginPath();
    ctx.moveTo(this.x1,this.y1);
    ctx.lineTo(this.x2,this.y2);
    ctx.stroke();
    var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
    startRadians+=((this.x2>this.x1)?-90:90)*Math.PI/180;
    this.drawArrowhead(ctx,this.x1,this.y1,startRadians);
    var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
    endRadians+=((this.x2>this.x1)?90:-90)*Math.PI/180;
    this.drawArrowhead(ctx,this.x2,this.y2,endRadians);
}
//
Line.prototype.drawArrowhead=function(ctx,x,y,radians){
    ctx.save();
    ctx.beginPath();
    ctx.translate(x,y);
    ctx.rotate(radians);
    ctx.moveTo(0,0);
    ctx.lineTo(5,20);
    ctx.lineTo(-5,20);
    ctx.closePath();
    ctx.restore();
    ctx.fill();
}
////////////////////
Specific styling properties mentioned in the script
<div class="relative">
  <canvas id="axes"></canvas>
  <canvas id="plot"></canvas>

Answer №2

The final 4 lines in the jsFiddle demonstrate how to use the code:

    // initialize a new line object
    var line = new Line(50, 50, 250, 275);
    // draw the line with arrowheads
    line.drawWithArrowheads(context);

(Remember to refer back to the top of the jsFiddle for the definition of Line).

In essence, this code calculates the angle at which the line is moving and then renders a triangle at that angle.

  • It saves the current state,
  • translates to the endpoint of the line,
  • rotates to align the arrowhead correctly,
  • creates the arrowhead shape,
  • fills in the shape, and finally,
  • restores the initial state.

The most challenging part is determining the angle needed to orient the arrowhead. For a multi-point line, you would likely use the last two points for this calculation.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The Hyperledger Sawtooth JavaScript SDK has encountered invalid submitted batches

I am currently working on integrating a hyperledger sawtooth transaction using the javascript SDK. I am following the tutorial provided here: . /* *Create the transaction header */ const createTransactionHeader = function createTransactionHeader(payloadBy ...

Tips for embedding a file within a text box in HTML and enabling users to make direct edits

I am currently working on a feature that allows users to open a .txt or .html file from their file explorer and paste the contents into a textarea for editing and saving purposes. My question is whether it's possible to read the file content and auto ...

Avoiding content resizing when using a drawer in Material UI

My project features a drawer that expands, but I am encountering an issue where the content inside the drawer resizes when expanded. However, this is not the desired outcome. I want the expanded drawer to overlay the content without resizing it. How can I ...

Alignment of the table with a fixed header and scrollable body

Currently, I am experiencing an issue with aligning a table. My aim is to have a fixed header and a scrollable body for the table. To do this, I have aligned the table header and body using: display: table-header-group For the table body, I have applied ...

Loading vue.config.js Asynchronously for Pre-Rendering Meta Data

I am facing an issue with asynchronously loading data in my Vue.js application's config for use with Chris Fritz's PrerenderSPA webpack plugin. It seems that the routes are not being pre-rendered as expected. When I manually input the values, th ...

Encircling the most recently selected image with a border

I have successfully made some <img> elements act like <input type="radio"> buttons and now I want to visually indicate the user's selection by adding a border around the chosen image. My challenge is that there are multiple rows of images ...

Modifying the maximum value of a number field attribute in jQuery after a successful action

As I continue to learn jQuery, I encountered a situation with the following form: <form class="simple-checkout" enctype="multipart/form-data" method="POST" action="<?php echo admin_url('admin-ajax.php'); ?>"> <input type="hidd ...

Break down a string into an array containing a specific number of characters each

I'm currently working on a project that involves tweeting excerpts from a book daily via a small app. The book's content is stored in a text file and I need to split it into 140-character-long strings for posting. Initially, I tried using the s ...

How can I use Angular's $filter to select a specific property

Is there a convenient method in Angular to utilize the $filter service for retrieving an array containing only a specific property from an array of objects? var contacts = [ { name: 'John', id: 42 }, { name: 'M ...

Adjustable height within embedded object tag

I've been struggling to adjust the height of the content on my site automatically. I have attempted using iframes as well, but nothing seems to work despite trying numerous code examples from various sources including CSS and JS scripts. Any help wou ...

When utilizing the `express.static(__dirname)` function in Node.js with Express, the visitor may be directed to an incorrect HTML page

My website consists of a login page named login.html and an index page named index.html. I want to implement authentication so that only logged in users can access the index page. I have not set up the post method on the login HTML page. Therefore, I have ...

Exploring React-Query's Search Feature

Looking for guidance on optimizing my Product search implementation using react-query. The current solution is functional but could be streamlined. Any suggestions on simplifying this with react-query would be greatly appreciated. import { useEffect, use ...

Load information from a JavaScript object when the user clicks dynamically

My challenge involves utilizing a JavaScript object that contains video information such as title and source URL. Within my HTML, I have image placeholders and the goal is to trigger a modal pop-up (using Twitter Bootstrap modal) of the specific video when ...

What is the best way to create a fully clickable navbar item for the Bootstrap dropdown feature?

I'm struggling to make a button in a navbar fully clickable for the dropdown to open. Even when I try adding margin instead of padding, it only makes things worse. Can someone help me figure out what mistake I'm making here? Essentially, my goal ...

Modify the button text when it is hovered over

I am attempting to modify the text displayed on a button when hovering over it in a React component from Ant Design. However, I have not been successful so far. Button <div className={ status == "verified" ? `${styles.btn1} ${styles.btn1C ...

View content from a text file on a webpage

Hi everyone, I could really use some assistance with a project I'm currently working on. As someone who is new to programming, I am facing a challenge. My goal is to showcase the contents of a plain text file on a webpage. This text file, titled titl ...

Obtaining a value using the Node.js inquirer package

I'm currently working on a flashcard generator using the node.js inquirer package, but I'm struggling to capture the user's selection. When the user selects an option, I want to be able to log that choice, but right now it's just return ...

Encountering a JS error that states: "Uncaught SyntaxError: missing ) after argument list" when running the following code

I keep encountering the error message: "Uncaught SyntaxError: missing ) after argument list" when I try to call the delete function within the createHtmlview function. console.log("Delete Item is being called") } ...

Surprising Regex Match of ^ and special character

My regular expression is designed to break down calculator input strings, such as 12+3.4x5, into individual tokens like 12, +, 3.4, x, and 5 Here is the regular expression I am using: \d+\.?\d+|[\+-÷x] However, I am encountering une ...

Stop allowing the entry of zero after a minus sign

One of the features on our platform allows users to input a number that will be automatically converted to have a negative sign. However, we want to ensure that users are unable to manually add a negative sign themselves. We need to find a solution to pre ...