Desenho setas em uma página HTML para visualizar ligações semânticas entre vãos textuais

StackOverflow https://stackoverflow.com/questions/554167

  •  23-08-2019
  •  | 
  •  

Pergunta

Eu tenho uma página HTML com alguns vãos textuais marcaram-se algo como isto:

...
<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. cada período tem um ID e refere-se a zero ou mais vãos através de seus IDs.

Eu gostaria de visualizar estas referências como setas.

Duas perguntas:

  • Como posso mapear um ID de uma extensão para as coordenadas de tela da prestação do espaço?
  • Como faço para desenhar setas indo de um rendering para outro?

A solução deve funcionar no Firefox, trabalhando em outros navegadores é uma vantagem, mas não é realmente necessário. A solução poderia usar jQuery, ou alguma outra biblioteca JavaScript leve.

Foi útil?

Solução

Você tem algumas opções: SVG ou lona .

A partir da aparência dele você não precisa estas setas para ter alguma forma matemática particular, você só precisa deles para ir entre os elementos.

Tente WireIt . Ter um olhar para este WireIt Demonstração (, que foi preterido ). Ele usa uma marcação canvas para cada fio individual entre os divs diálogo flutuante, em seguida, o dimensionamento e posicionamento de cada elemento canvas para dar a aparência de uma linha que liga apenas no ponto certo. Você pode ter que implementar uma seta de rotação adicional, a menos que você não se importa as setas que entram a cada elemento no mesmo ângulo.

Editar : a demo foi preterido .

Editar : Ignorar esta resposta, @Phil H acertou

Outras dicas

Este capturou meu interesse por muito tempo suficiente para produzir um pequeno teste. O código está abaixo, e você pode vê-lo em ação

Captura de tela

Ele lista todas as extensões na página (pode querer restringir que apenas aqueles com ids começando com T se que é adequado), e usa o atributo ids para construir a lista de links. Utilizando um elemento de tela atrás dos vãos, extrai setas arco alternadamente acima e abaixo dos vãos para cada intervalo de fonte.

<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> 

E você só precisa de um lugar chamada para o draw () função:

<body onload="draw();"> 

Em seguida, uma tela por trás do conjunto de spans.

<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> 

modificações futuras, tanto quanto eu posso ver:

  • Achatamento topo de flechas mais longas
  • refatoração para ser capaz de desenhar setas não horizontais: adicionar uma nova tela para cada
  • Use uma melhor rotina para obter os deslocamentos totais dos elementos de lona e span.

[Edit dezembro 2011: Fixo, graças @Palo]

A esperança que é tão útil como foi divertido.

A grande biblioteca de setas é JointJS que se baseia Raphael como mostrado acima. Com JointJS você pode facilmente desenhar setas com curvas ou vértices sem qualquer coisas complicadas; -)

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

Isto define uma seta 'J34' que conecta dois js itens S3 com S4. Tudo o resto pode ser lido na documentação de JointJS.

Você poderia tentar este biblioteca - é coisa muito inteligente, espero que ajude.

EDIT: Como esta ligação está morto, aqui é outro link de Archive.org .

Eu tento ir com tecnologias web abertas, sempre que possível, mas a verdade é que o HTML e JavaScript (ou jQuery) não são as ferramentas para este trabalho específico (triste mas é verdade), especialmente porque os diagramas que você está aumento de desenho na complexidade.

Por outro lado, Flash foi feito para isso. Significativamente menos código do ActionScript 3.0 seria necessário para analisar esse XML, layout de seu texto (com mais controle sobre as fontes e Super / subscritos) e tornar as curvas (ver a métodos flash.display.Graphics classe como curveTo). Em geral você vai estar a olhar para menos código, melhor manutenção, menos hacks, maior compatibilidade e bibliotecas de desenho mais estáveis.

Boa sorte com o projeto.

Se você não precisa de setas curvas, você poderia usar posicionado de forma absoluta divs acima ou abaixo da lista. Você poderia, então, usar CSS para estilizar essas divs além de um par de imagens que compõem a cabeça da seta. Abaixo está um exemplo usando o conjunto de ícone a partir do projeto jQuery UI (desculpe sobre a longa URL).

Aqui está o CSS para iniciar as coisas:

<style>
 .below{
     border-bottom:1px solid #000;
     border-left:1px solid #000;
     border-right:1px solid #000;
 }
 .below span{
    background-position:0px -16px;
    top:-8px;
 }
 .above{
     border-top:1px solid #000;
     border-left:1px solid #000;
     border-right:1px solid #000;
 }
 .above span{
    background-position:-64px -16px;
    bottom:-8px;
 }

 .arrow{
    position:absolute;
    display:block;
    background-image:url(http://jquery-ui.googlecode.com/svn/trunk/themes/base/images/ui-icons_454545_256x240.png);
    width:16px;
    height:16px;
    margin:0;
    padding:0;
 }

.left{left:-8px;}

.right{right:-9px;}

</style>

Agora podemos começar a montar divs de seta. Por exemplo, ao estilo da seta de "requer" a "promotor" no seu exemplo acima, você poderia fazer esquerda, inferior e bordas direita na div com e para cima enfrentando gráfico seta no canto superior esquerdo da div.

<div class='below' style="position:absolute;top:30px;left:30px;width:100px;height:16px">
   <span class='arrow left'></span>
</div>

Os estilos inline seria necessidade de ser aplicado por script depois que você descobriu os locais dos coisas que você precisa para se conectar. Vamos dizer que sua lista esta aparência:

<span id="promoter">Promoter</span><span>Something Else</span><span id="requires">Requires</span>

Em seguida, o script a seguir irá posicionar sua flecha:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script> 
<script>
$(function(){
 var promoterPos=$("#promoter").offset();
 var requiresPos=$("#requires").offset();
 $("<div class='below'><span class='arrow left'></span></div>")
 .css({position:"absolute",left:promoterPos.left,right:promoterPos.top+$("#promoter").height()})
 .width(requiresPos.left-promoterPos.left)
 .height(16)
 .appendTo("body");
});
</script>

Vá em frente e colar os exemplos acima em uma página html em branco. É uma espécie de arrumado.

Como já foi mencionado, JavaScript e HTML não são boas ferramentas para esse tipo de coisa.

John Resig escreveu uma implementação de Processing.org em JavaScript . Ele usa o elemento de tela, por isso vai trabalhar em versões modernas de Firefox, mas não irá funcionar em todos os navegadores. Se você só se preocupam com o Firefox, este provavelmente seria o caminho a percorrer.

Você pode ser capaz de usar SVG, mas novamente, isso não é suportado em todos os navegadores.

Eu precisava de uma solução semelhante, e eu estava olhando para RaphaelJS JavaScript Biblioteca . Por exemplo, você pode desenhar uma seta reta desde (x1,y1) para (x2,y2) com:

Raphael.fn.arrow = function (x1, y1, x2, y2, size) {
  var angle = Math.atan2(x1-x2,y2-y1);
  angle = (angle / (2 * Math.PI)) * 360;
  var arrowPath = this.path(“M” + x2 + ” ” + y2 + ” L” + (x2 - size) + ” ” + (y2 - size) + ” L” + (x2 - size) + ” ” + (y2 + size) + ” L” + x2 + ” ” + y2 ).attr(“fill”,”black”).rotate((90+angle),x2,y2);
  var linePath = this.path(“M” + x1 + ” ” + y1 + ” L” + x2 + ” ” + y2);
  return [linePath,arrowPath];
}

Eu não descobrir como desenhar uma seta curvada, mas eu tenho certeza que é possível.

Você pode obter as pontas de setas curvas usando um punhado de divs position:absolute com o conjunto background-image para GIFs transparentes ... um conjunto para o início (superior e inferior) ... um div bacground:repeat por meio expansível, e outro par para os fins (superior e inferior).

Você pode usar este biblioteca: basta anotar suas linhas SVG com os ids do elemento de origem e de destino . Ele usa MutationObserver para observar mudanças nos elementos ligados.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top