Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to add link label curved linlks. #39

Open
gdswapnil11 opened this issue Jun 21, 2019 · 14 comments
Open

How to add link label curved linlks. #39

gdswapnil11 opened this issue Jun 21, 2019 · 14 comments

Comments

@gdswapnil11
Copy link

gdswapnil11 commented Jun 21, 2019

Am using force-graph in my project and i am using 'linkCurvature ' for curve link and ' .linkCanvasObjectMode' and '.linkCanvasObject' for link label but problem is that am not able to get link label on curved links, how can i do so.

Here i share my code.

`

    fetch('../assets/json/miserables.json').then(res => res.json()).then(data => {
      this.Graph = ForceGraph()
      (document.getElementById('graph'))
        .graphData(data)
        .nodeId('id')
        .nodeLabel('name')
        .linkCurvature('curvature')
        .nodeAutoColorBy('group')
        .linkDirectionalArrowLength(1) // 0 off 1 on for arrow
        .linkDirectionalArrowRelPos(function (d) {
          return d.markerPosition; // 0 start, 0.5 middle, 1 end
        })
        .linkDirectionalArrowLength(5)
        .linkDirectionalArrowLength(5)
        .linkCanvasObjectMode(() => 'after')
    .linkCanvasObject((link, ctx) => {
      const MAX_FONT_SIZE = 4;
      const LABEL_NODE_MARGIN = this.Graph.nodeRelSize() * 1.5;
      const start = link.source;
      const end = link.target;
      // ignore unbound links
      if (typeof start !== 'object' || typeof end !== 'object') return;
      // calculate label positioning
      const textPos = Object.assign({},...['x', 'y'].map(c => ({
        [c]: start[c] + (end[c] - start[c]) / 2 // calc middle point
      })));
      const relLink = { x: end.x - start.x, y: end.y - start.y };
      const maxTextLength = Math.sqrt(Math.pow(relLink.x, 2) + Math.pow(relLink.y, 2)) - LABEL_NODE_MARGIN * 2;
      let textAngle = Math.atan2(relLink.y, relLink.x);
      // maintain label vertical orientation for legibility
      if (textAngle > Math.PI / 2) textAngle = -(Math.PI - textAngle);
      if (textAngle < -Math.PI / 2) textAngle = -(-Math.PI - textAngle);
      const label = link.linkType;
      // estimate fontSize to fit in link length
      ctx.font = '1px Sans-Serif';
      const fontSize = Math.min(MAX_FONT_SIZE, maxTextLength / ctx.measureText(label).width);
      ctx.font = `${fontSize}px Sans-Serif`;
      const textWidth = ctx.measureText(label).width;
      const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2); // some padding
      // draw text label (with background rect)
      ctx.save();
      ctx.translate(textPos.x, textPos.y);
      ctx.rotate(textAngle);
      ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
      ctx.fillRect(- bckgDimensions[0] / 2, - bckgDimensions[1] / 2, ...bckgDimensions);
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillStyle = 'darkgrey';
      ctx.setLineDash([5, 5]);
      ctx.fillText(label, 0, 0);
      ctx.restore();
    })

    .nodeCanvasObjectMode(() => 'after')
    .nodeCanvasObject((node, ctx, globalScale) => {
      const label = node.Name;
      const fontSize = 18 / globalScale;
      ctx.font = `${fontSize}px Arial`;
      const textWidth = ctx.measureText(label).width;
      const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2); // some padding
      ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
      ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2, ...bckgDimensions);
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillStyle = node.color;
      // ctx.fillStyle = '#7D8080';
      ctx.fillText(label, node.x, node.y);
    })

   
    .d3Force('collide', d3.forceCollide(30))
    
    .linkColor(function(d){
      return d.color;
    })
    
    .zoom(2)
    .nodeRelSize(10)
    .linkDirectionalParticles(1)
    .linkDirectionalParticleColor(function(){
      return "#6ECF9C";
    })
    .linkDirectionalArrowColor(function(){
      return "#757977";
    })
    .onNodeClick(node => {
      // Center/zoom on node
      this.Graph.centerAt(node.x, node.y, 1000);
      this.Graph.zoom(8, 1500);
    });
});`
@gdswapnil11
Copy link
Author

@vasturiano is there any solution.

@vasturiano
Copy link
Owner

@gdswapnil11 you should calculate where you wish to position your link labels in this part:

      const textPos = Object.assign({},...['x', 'y'].map(c => ({
        [c]: start[c] + (end[c] - start[c]) / 2 // calc middle point
      })));

This uses the halfway point in between the two nodes. If you wish it to be positioned elsewhere, simply change that calculation.

@gdswapnil11
Copy link
Author

hey @vasturiano , i tried this, but can not find right solution ,can u provide me sample code for this.
i use curvature for curve links.

@vasturiano
Copy link
Owner

@gdswapnil11 which part are you not able to get to work properly?

@gdswapnil11
Copy link
Author

Capture

@vasturiano my output look like this, label on curved line are over lapping on one position so how do i manage as per link curvature.

@vasturiano
Copy link
Owner

@gdswapnil11 have you tried adjusting the textPos method as mentioned above?

@gdswapnil11
Copy link
Author

gdswapnil11 commented Jul 12, 2019

hey, @vasturiano yes i have tried adjusting the textPos, here is my sample code

ctx.fillText(label,0, curvature*50);

image

Still my output look like, i have another question that can we use ctx.bezierCurveTo() for curve text.

@aoloo
Copy link

aoloo commented Jan 20, 2021

@gdswapnil11 do you by chance have a repo of your implementation? I have a similar use case.

@Bomfim
Copy link

Bomfim commented Mar 17, 2021

Hey Guys, a little bit of patience and math then I solved the problem this way:
force-graph uses a third-party lib to render these curved links called bezier-js ,so i made a function that calculates a point along the curve and if you pass 0.5 as parameter you will reach the middle point of the curve. Thanks @vasturiano for the help.

function getQuadraticXY(t, sx, sy, cp1x, cp1y, ex, ey) {
  return {
    x: (1 - t) * (1 - t) * sx + 2 * (1 - t) * t * cp1x + t * t * ex,
    y: (1 - t) * (1 - t) * sy + 2 * (1 - t) * t * cp1y + t * t * ey,
  };
}

if (+link.curvature > 0) {
          textPos = getQuadraticXY(
            0.5,
            start.x,
            start.y,
            link.__controlPoints[0],
            link.__controlPoints[1],
            end.x,
            end.y
          );
        }

If anyone wants to see here is my code

@aoloo
Copy link

aoloo commented Mar 18, 2021

@Bomfim thank you

@federico-acn
Copy link

federico-acn commented Nov 4, 2022

Hi :)

Did anyone get anything better than this?
image

I also tried with this library https://github.com/Viglino/Canvas-TextPath but I find difficult to find the coordinates of the path

thanks

@moda20
Copy link

moda20 commented Jul 20, 2023

I tried this function and it worked correctly for curved links between 2 nodes, but not correctly for self referencing links (from A to A) @vasturiano can you help with this ? it's probably an issue that the start and end coordinates are the same
image

@moda20
Copy link

moda20 commented Jul 21, 2023

Ok, i was able to get this to work correctly, by updating the formula above to use 4 control pints instead of 2 which is the case for self referencing links :
i got the formula from here (which is from wikipedia) : https://stackoverflow.com/a/54216695
this is the full function usage :

CanvasRenderingContext2D.prototype.getQuadraticXY = function (t, sx, sy, cp1x, cp1y, ex, ey) {
    return {
      x: (1 - t) * (1 - t) * sx + 2 * (1 - t) * t * cp1x + t * t * ex,
      y: (1 - t) * (1 - t) * sy + 2 * (1 - t) * t * cp1y + t * t * ey,
    };
  };
 CanvasRenderingContext2D.prototype.getQuadraticXYFourWays = function (
    t,
    sx,
    sy,
    cp1x,
    cp1y,
    cp2x,
    cp2y,
    ex,
    ey,
  ) {
    return {
      x:
        (1 - t) * (1 - t) * (1 - t) * sx +
        3 * (1 - t) * (1 - t) * t * cp1x +
        3 * (1 - t) * t * t * cp2x +
        t * t * t * ex,
      y:
        (1 - t) * (1 - t) * (1 - t) * sy +
        3 * (1 - t) * (1 - t) * t * cp1y +
        3 * (1 - t) * t * t * cp2y +
        t * t * t * ey,
    };
  };
  
  (.....)
  
  if (+link.curvature > 0) {
      if (start.id === end.id) {
        textPos = this.getQuadraticXYFourWays(
          0.5,
          start.x,
          start.y,
          link.__controlPoints[0],
          link.__controlPoints[1],
          link.__controlPoints[2],
          link.__controlPoints[3],
          end.x,
          end.y,
        );
      } else {
        textPos = this.getQuadraticXY(
          0.5,
          start.x,
          start.y,
          link.__controlPoints[0],
          link.__controlPoints[1],
          end.x,
          end.y,
        );
      }
    }
  
  

@Bomfim
Copy link

Bomfim commented Jul 21, 2023

Awesome @moda20, you should be very proud dude

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants