2

I have a simple html table with some cells which I create with the td tag. Now I need to draw an arrow from cell A2 to C2 for example. I thought about creating a jQuery function to get the XY coordinates of the middle of cell A2 and C2 and then somehow draw an arrow.

What is a good way of achieving the look and feel as shown in the picture?

Table cells with arrow

romellem
  • 5,792
  • 1
  • 32
  • 64
codebird456
  • 505
  • 8
  • 19
  • 3
    You can try to add a floating image over the table, using css position: https://developer.mozilla.org/en-US/docs/Web/CSS/position. – Rodrigo Jan 17 '18 at 21:20
  • Thanks. Does that work also if the arrow has to be in variable size which means sometimes shorter and sometimes longer? – codebird456 Jan 17 '18 at 21:35
  • For this you can stretch your image, or use different images, or use a canvas, like in the solutions below. – Rodrigo Jan 18 '18 at 00:18

2 Answers2

1

You can do this dynamically by using a canvas element to contain your arrow, which you will dynamically draw via the 2D context drawing functions. You can absolutely position the canvas element using javascript.

Here is sample code:

HTML:

<canvas id="canvas" width="100" height="100">
</canvas>



 <table>
 <tr><td>1</td><td>2</td></tr>
 <tr><td>3</td><td>4</td></tr>
 <tr><td>5</td><td>6</td></tr>
 </table>

Javascript:

 var canvas = document.querySelector('canvas');
 var ctx = canvas.getContext('2d');

 canvas.style.top = 7 + "px";
 canvas.style.left = 7 + "px";

 var fromx = 0;
 var fromy = 12;
 var tox = 25;
 var toy = 12;

 var headlen = 10;   // length of head in pixels
 var angle = Math.atan2(toy-fromy,tox-fromx);
 var arrowSize = 2;
 var headlen = 10;

 var angle = Math.atan2(toy-fromy,tox-fromx);
 //starting path of the arrow from the start square to the end square and drawing the stroke
 ctx.beginPath();
ctx.moveTo(fromx, fromy);
 ctx.lineTo(tox, toy);
 ctx.strokeStyle = "#cc0000";
 ctx.lineWidth = arrowSize;
 ctx.stroke();

 //starting a new path from the head of the arrow to one of the sides of the point
 ctx.beginPath();
 ctx.moveTo(tox, toy);
 ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));

 //path from the side point of the arrow, to the other side point
 ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7));

 //path from the side point back to the tip of the arrow, and then again to the opposite side point
 ctx.lineTo(tox, toy);
 ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));

 //draws the paths created above
 ctx.strokeStyle = "#cc0000";
 ctx.lineWidth = arrowSize;
 ctx.stroke();
 ctx.fillStyle = "#cc0000";
 ctx.fill();

CSS:

 #canvas {
        position: absolute;
        top: 0px;
        left: 0px;
        width: 100px;
        height: 100px;
 }

You can programmatically obtain the positioning information of your table elements in order to set the correct values for fromx, fromy, etc., and for setting canvas.style.top and canvas.style.left.

I borrowed the arrow drawing code from Draw arrow on canvas tag

Alex
  • 877
  • 5
  • 10
  • if you did this, could you still get mouse events for the table cells? like hover? or would the canvas lay on top of the table, blocking the mouse? – FalcoGer Aug 14 '19 at 07:35
1

First, we can get the center of the cells relative to the document. Then, we can simply draw an arrow from the beginning to the end on a canvas (relative to the document). The trick is to set pointer-events: none on the canvas so we do not accidentally interact with it.

// gets the center of a table cell relative to the document
function getCellCenter(table, row, column) {
  var tableRow = $(table).find('tr')[row];
  var tableCell = $(tableRow).find('td')[column];

  var offset = $(tableCell).offset();
  var width = $(tableCell).innerWidth();
  var height = $(tableCell).innerHeight();
  
  return {
    x: offset.left + width / 2,
    y: offset.top + height / 2
  }
}

// draws an arrow on the document from the start to the end offsets
function drawArrow(start, end) {

  // create a canvas to draw the arrow on
  var canvas = document.createElement('canvas');
  canvas.width = $('body').innerWidth();
  canvas.height = $('body').innerHeight();
  $(canvas).css('position', 'absolute');
  $(canvas).css('pointer-events', 'none');
  $(canvas).css('top', '0');
  $(canvas).css('left', '0');
  $(canvas).css('opacity', '0.85');
  $('body').append(canvas);
  
  // get the drawing context
  var ctx = canvas.getContext('2d');
  ctx.fillStyle = 'steelblue';
  ctx.strokeStyle = 'steelblue';
  
  // draw line from start to end
  ctx.beginPath();
  ctx.moveTo(start.x, start.y);
  ctx.lineTo(end.x, end.y);
  ctx.lineWidth = 2;
  ctx.stroke();
  
  // draw circle at beginning of line
  ctx.beginPath();  
  ctx.arc(start.x, start.y, 4, 0, Math.PI * 2, true);
  ctx.fill();

  // draw pointer at end of line (needs rotation)
  ctx.beginPath();  
  var angle = Math.atan2(end.y - start.y, end.x - start.x);
  ctx.translate(end.x, end.y);
  ctx.rotate(angle);
  ctx.moveTo(0, 0);
  ctx.lineTo(-10, -7);
  ctx.lineTo(-10, 7);
  ctx.lineTo(0, 0);
  ctx.fill();

  // reset canvas context
  ctx.setTransform(1, 0, 0, 1, 0, 0);  
  
  return canvas;
}

// finds the center of the start and end cells, and then calls drawArrow
function drawArrowOnTable(table, startRow, startColumn, endRow, endColumn) {
  drawArrow(
    getCellCenter($(table), startRow, startColumn),
    getCellCenter($(table), endRow, endColumn)
  );
}

// draw an arrow from (1, 0) to (2, 4)
drawArrowOnTable('table', 1, 0, 2, 4);
table, td {
  border-collapse: collapse;
}

td {
  border: 1px solid #ddd;
  padding: 6px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<table>
  <tr>
    <td>A1</td>
    <td>B1</td>
    <td>C1</td>
    <td>D1</td>
    <td>E1</td>
  </tr>

  <tr>
    <td>A2</td>
    <td>B2</td>
    <td>C2</td>
    <td>D2</td>
    <td>E2</td>
  </tr>

  <tr>
    <td>A3</td>
    <td>B3</td>
    <td>C3</td>
    <td>D3</td>
    <td>E3</td>
  </tr>
  
</table>

<pre>drawArrowOnTable('table', 1, 0, 2, 4);</pre>
</body>
</html>
Brian
  • 1,860
  • 1
  • 9
  • 14