42 votes

Dessiner des flèches sur une page HTML pour visualiser les liens sémantiques entre les étendues textuelles

J'ai une page HTML avec un texte s'étend marqué quelque chose comme ceci

...
<span id="T2" class="Protein">p50</span>
...
<span id="T3" class="Protein">p65</span>
...
<span id="T34" ids="T2 T3" class="Positive_regulation">recruitment</span>
...

I. e. chaque travée a un ID, et se réfère à zéro, une ou plusieurs travées via leurs Identifiants.

Je voudrais visualiser ces références comme des flèches, comme sur ce dessin.

Spans connected by arrows

Deux questions:

  • Comment puis-je carte un ID d'une portée à l'écran des coordonnées du rendu de la durée?
  • Comment puis-je dessiner des flèches allant d'un rendu à l'autre?

La solution doit travailler dans Firefox, travaillant dans d'autres navigateurs est un plus mais n'est pas vraiment nécessaire. La solution pourrait utiliser jQuery ou autre léger bibliothèque Javascript.

72voto

Phil H Points 10133

Cela a attiré mon intérêt pour assez longtemps pour produire un petit test. Le code est ci-dessous, et vous pouvez les voir en action

screenshot

Il répertorie tous les travées de la page (peut vouloir limiter aux seuls avec des id de départ avec T si c'est approprié), et utilise le 'id' attribut pour créer la liste de liens. À l'aide d'un élément canvas derrière les travées, il tire à l'arc des flèches alternativement au-dessus et au-dessous de la portée pour chaque source de durée.

<script type="application/x-javascript"> 

function generateNodeSet() {
  var spans = document.getElementsByTagName("span");
  var retarr = [];
  for(var i=0;i<spans.length; i++) { 
     retarr[retarr.length] = spans[i].id; 
  } 
  return retarr; 
} 

function generateLinks(nodeIds) { 
  var retarr = []; 
  for(var i=0; i<nodeIds.length; i++) { 
    var id = nodeIds[i];
    var span = document.getElementById(id); 
    var atts = span.attributes; 
    var ids_str = false; 
    if((atts.getNamedItem) && (atts.getNamedItem('ids'))) { 
      ids_str = atts.getNamedItem('ids').value; 
    } 
    if(ids_str) { 
      retarr[id] = ids_str.split(" ");
    }
  } 
  return retarr; 
} 

// degrees to radians, because most people think in degrees
function degToRad(angle_degrees) {
   return angle_degrees/180*Math.PI;
}
// draw a horizontal arc
//   ctx: canvas context;
//   inax: first x point
//   inbx: second x point
//   y: y value of start and end
//   alpha_degrees: (tangential) angle of start and end
//   upside: true for arc above y, false for arc below y.
function drawHorizArc(ctx, inax, inbx, y, alpha_degrees, upside)
{
  var alpha = degToRad(alpha_degrees);
  var startangle = (upside ? ((3.0/2.0)*Math.PI + alpha) : ((1.0/2.0)*Math.PI - alpha));
  var endangle = (upside ? ((3.0/2.0)*Math.PI - alpha) : ((1.0/2.0)*Math.PI + alpha));

  var ax=Math.min(inax,inbx);
  var bx=Math.max(inax,inbx);

  // tan(alpha) = o/a = ((bx-ax)/2) / o
  // o = ((bx-ax)/2/tan(alpha))
  // centre of circle is (bx+ax)/2, y-o
  var circleyoffset = ((bx-ax)/2)/Math.tan(alpha);
  var circlex = (ax+bx)/2.0;
  var circley = y + (upside ? 1 : -1) * circleyoffset;
  var radius = Math.sqrt(Math.pow(circlex-ax,2) + Math.pow(circley-y,2));

  ctx.beginPath();
  if(upside) {
      ctx.moveTo(bx,y);
    ctx.arc(circlex,circley,radius,startangle,endangle,1);
  } else {
    ctx.moveTo(bx,y);
    ctx.arc(circlex,circley,radius,startangle,endangle,0);
  }
  ctx.stroke();
}


// draw the head of an arrow (not the main line)
//  ctx: canvas context
//  x,y: coords of arrow point
//  angle_from_north_clockwise: angle of the line of the arrow from horizontal
//  upside: true=above the horizontal, false=below
//  barb_angle: angle between barb and line of the arrow
//  filled: fill the triangle? (true or false)
function drawArrowHead(ctx, x, y, angle_from_horizontal_degrees, upside, //mandatory
                       barb_length, barb_angle_degrees, filled) {        //optional
   (barb_length==undefined) && (barb_length=13);
   (barb_angle_degrees==undefined) && (barb_angle_degrees = 20);
   (filled==undefined) && (filled=true);
   var alpha_degrees = (upside ? -1 : 1) * angle_from_horizontal_degrees; 

   //first point is end of one barb
   var plus = degToRad(alpha_degrees - barb_angle_degrees);
   a = x + (barb_length * Math.cos(plus));
   b = y + (barb_length * Math.sin(plus));

   //final point is end of the second barb
   var minus = degToRad(alpha_degrees + barb_angle_degrees);
   c = x + (barb_length * Math.cos(minus));
   d = y + (barb_length * Math.sin(minus));

   ctx.beginPath();
   ctx.moveTo(a,b);
   ctx.lineTo(x,y);
   ctx.lineTo(c,d);
   if(filled) {
    ctx.fill();
   } else {
    ctx.stroke();
   }
   return true;
}

// draw a horizontal arcing arrow
//  ctx: canvas context
//  inax: start x value
//  inbx: end x value
//  y: y value
//  alpha_degrees: angle of ends to horizontal (30=shallow, >90=silly)
function drawHorizArcArrow(ctx, inax, inbx, y,                 //mandatory
                           alpha_degrees, upside, barb_length) { //optional
   (alpha_degrees==undefined) && (alpha_degrees=45);
   (upside==undefined) && (upside=true);
   drawHorizArc(ctx, inax, inbx, y, alpha_degrees, upside);
   if(inax>inbx) { 
    drawArrowHead(ctx, inbx, y, alpha_degrees*0.9, upside, barb_length); 
   } else { 
    drawArrowHead(ctx, inbx, y, (180-alpha_degrees*0.9), upside, barb_length); 
   }
   return true;
}


function drawArrow(ctx,fromelem,toelem,    //mandatory
                     above, angle) {        //optional
  (above==undefined) && (above = true);
  (angle==undefined) && (angle = 45); //degrees 
  midfrom = fromelem.offsetLeft + (fromelem.offsetWidth / 2) - left - tofromseparation/2; 
  midto   =   toelem.offsetLeft + (  toelem.offsetWidth / 2) - left + tofromseparation/2;
  //var y = above ? (fromelem.offsetTop - top) : (fromelem.offsetTop + fromelem.offsetHeight - top);
  var y = fromelem.offsetTop + (above ? 0 : fromelem.offsetHeight) - canvasTop;
  drawHorizArcArrow(ctx, midfrom, midto, y, angle, above);
}

    var canvasTop = 0;
function draw() { 
  var canvasdiv = document.getElementById("canvas");
  var spanboxdiv = document.getElementById("spanbox");
  var ctx = canvasdiv.getContext("2d");

  nodeset = generateNodeSet(); 
  linkset = generateLinks(nodeset);
  tofromseparation = 20;

  left = canvasdiv.offsetLeft - spanboxdiv.offsetLeft;
  canvasTop = canvasdiv.offsetTop - spanboxdiv.offsetTop; 
  for(var key in linkset) {  
    for (var i=0; i<linkset[key].length; i++) {  
      fromid = key; 
      toid = linkset[key][i]; 
      var above = (i%2==1);
      drawArrow(ctx,document.getElementById(fromid),document.getElementById(toid),above);
    } 
  } 
} 

</script> 

Et vous avez juste besoin d'un appel, quelque part au tirage au sort() fonction:

<body onload="draw();"> 

Puis une toile derrière l'ensemble des travées.

<canvas style='border:1px solid red' id="canvas" width="800" height="7em"></canvas><br /> 
<div id="spanbox" style='float:left; position:absolute; top:75px; left:50px'>
<span id="T2">p50</span>
...
<span id="T3">p65</span> 
...
<span id="T34" ids="T2 T3">recruitment</span>
</div> 

Modifications futures, aussi loin que je peux voir:

  • Aplatir le haut de plus de flèches
  • Refactorisation pour être en mesure d'attirer les non-flèches horizontales: ajouter une nouvelle toile pour chaque?
  • D'utilisation une meilleure routine pour obtenir le montant total des compensations de la toile et des éléments span.

[Edit Déc 2011: Corrigé, merci @Palo]

J'espère que c'est aussi utile que c'était amusant.

25voto

Crescent Fresh Points 54070

Vous avez deux options: svg ou canvas.

Apparemment vous n'avez pas besoin de ces flèches d'avoir une quelconque forme mathématique, vous avez juste besoin d'aller entre les éléments.

Essayez WireIt. Jetez un oeil à cette WireIt Démo (qui a été abandonné). Il utilise un canvas balise pour chaque individu de fil entre la boîte de dialogue flottante divs, puis les tailles et les positions de chaque canvas d'élément pour donner l'apparence d'une ligne de connexion à la bonne place. Vous pourriez avoir à mettre en œuvre un supplément de rotation de pointe de flèche, à moins que vous n'ayez pas l'esprit de la flèches à venir pour chaque élément dans le même angle.

Edit: la démo a été désapprouvée.

Edit: Ignorer cette réponse, @Phil H cloué

4voto

eraser Points 21

Une excellente bibliothèque pour les flèches est JointJS qui est basée sur Raphael comme indiqué ci-dessus. Avec JointJS, vous pouvez facilement dessiner des flèches avec des courbes ou des sommets sans aucune complication ;-)

 var j34 = s3.joint(s4, uml.arrow).setVertices(["170 130", "250 120"]);
 

Ceci définit une flèche 'j34' qui relie deux éléments js s3 à s4. Tout le reste peut être lu dans la documentation de JointJS.

2voto

Kieron Points 10261

Vous pouvez essayer cette bibliothèque - c'est des trucs très intelligents, j'espère que ça aide.

EDIT: Comme ce lien est mort, voici un autre lien d' Archive.org .

1voto

Jaysen Marais Points 755

J'essaye d'y aller avec des technologies web ouvertes dans la mesure du possible, mais la vérité est que le HTML et le JavaScript (jQuery) ne sont pas les outils pour ce travail particulier (triste mais vrai), d'autant que les diagrammes que vous êtes le dessin augmentation de la complexité.

D'autre part, le Flash a été faite pour cela. Beaucoup moins de code ActionScript 3.0 serait nécessaire à l'analyse que XML, mise en page de votre texte (avec plus de contrôle sur les polices et les super/indices) et de rendre les courbes (voir le flash.d'affichage.La classe Graphics méthodes comme curveTo). Dans l'ensemble, vous allez être à la recherche à moins de code, une meilleure maintenabilité, de moins en moins de hacks, une plus grande compatibilité et plus stable dessin bibliothèques.

Bonne chance avec le projet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X