Rysowanie strzałek na stronie HTML w celu wizualizacji powiązań semantycznych między zakresami tekstowymi

Mam stronę HTML z zaznaczonymi fragmentami tekstu coś takiego:

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

Tzn. każda span ma ID i odnosi się do zera lub więcej span poprzez ich ID.

Chciałbym wizualizować te odniesienia jako strzałki.

Dwa pytania:

  • Jak mogę odwzorować ID zakresu na współrzędne ekranu renderowania zakresu?
  • Jak rysować strzałki przechodzące z jednego renderingu do drugiego?

Rozwiązanie powinno działać w Firefoksie praca w innych przeglądarkach jest plusem, ale nie jest to naprawdę konieczne. Rozwiązanie może używać jQuery lub innej lekkiej biblioteki JavaScript.

Author: Ilmari Karonen, 2009-02-16

10 answers

Masz kilka opcji: svg lub canvas.

Wygląda na to, że nie potrzebujesz tych strzałek, aby mieć jakąś konkretną matematyczną formę, po prostu potrzebujesz ich, aby przejść między elementami.

Spróbuj WireIt . Zobacz to Demo WireIt (który został wycofany ). Używa znacznika canvas dla każdego pojedynczego przewodu pomiędzy pływającym dialogiem divs, następnie rozmiary i pozycje każdego elementu canvas, aby nadać wygląd połączenia linia w odpowiednim miejscu. Być może będziesz musiał zaimplementować dodatkowy obrotowy grot strzałkowy, chyba że nie masz nic przeciwko strzałkom przychodzącym do każdego elementu pod tym samym kątem.

Edytuj: demo zostało wycofane .

Edit : Ignoruj tę odpowiedź, @Phil H

 25
Author: Crescent Fresh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:14

To wzbudziło moje zainteresowanie na tyle długo, aby stworzyć mały test. Kod znajduje się poniżej i możesz zobaczyć go w akcji

zrzut ekranu

Wyświetla wszystkie zakresy na stronie (może warto ograniczyć to tylko do tych z identyfikatorami zaczynającymi się od T, jeśli jest to odpowiednie) i używa atrybutu 'ids' do budowania listy linków. Używając elementu canvas za przęsłami, rysuje strzałki łukowe na przemian powyżej i poniżej przęseł dla każdego przęsła źródłowego.

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

And you just need a wywołanie funkcji draw ():

<body onload="draw();"> 

Następnie płótno za zestawem przęseł.

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

Przyszłe modyfikacje, o ile widzę:

  • spłaszczenie wierzchołka dłuższych strzałek
  • Refaktoryzacja, aby móc rysować nie-poziome strzałki: dodać nowe płótno dla każdego?
  • użyj lepszej procedury, aby uzyskać całkowite przesunięcia elementów canvas i span.

[edytuj grudzień 2011: poprawione, dzięki @ Palo]

Mam nadzieję, że to równie przydatne, co zabawne.
 72
Author: Phil H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Wielką biblioteką dla strzałek jest JointJS , która jest oparta na Raphaelu, jak pokazano powyżej. Z JointJS możesz łatwo rysować strzałki z krzywymi lub wierzchołkami bez żadnych skomplikowanych rzeczy; -)

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

Definiuje strzałkę 'j34', która łączy dwa elementy js S3 Z s4. Wszystko inne można odczytać w dokumentacji JointJS.

 4
Author: eraser,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-10 08:24:28

Jeśli nie potrzebujesz zakrzywionych strzałek, możesz użyć absolutnie umieszczonych div powyżej lub poniżej listy. Możesz następnie użyć css do stylizacji tych div plus kilka obrazów, które składają się na głowę strzałki. Poniżej znajduje się przykład użycia zestawu ikon z projektu jQuery UI (przepraszam za długi adres URL).

Oto CSS na początek:

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

Teraz możemy zacząć montować arrow div. Na przykład, aby styl strzałki Z "wymaga" do "promotor" w powyższym przykładzie, można można zrobić lewej, dolnej i prawej krawędzi na div z Grafiką strzałki w górę w lewym górnym rogu div.

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

Style inline będą musiały zostać zastosowane przez skrypt po ustaleniu lokalizacji rzeczy, które trzeba będzie połączyć. Załóżmy, że Twoja lista wygląda tak:

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

Następnie następujący skrypt ustawi strzałkę:

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

Śmiało i wklej powyższe przykłady do pustej strony html. Jest trochę schludny.

 2
Author: Josh Bush,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-10 08:25:05

Możesz wypróbować tę bibliotekę grafiki wektorowej JavaScript - to bardzo sprytna rzecz, mam nadzieję, że pomoże.

EDIT: ponieważ ten link jest martwy, oto kolejny link z Archive.org .

 2
Author: Kieron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-17 18:52:38

Staram się iść z otwartych technologii internetowych, gdzie to możliwe, ale prawda jest taka, że HTML & JavaScript (lub jQuery) nie są narzędzia do tego konkretnego zadania( smutne, ale prawdziwe), zwłaszcza, że diagramy rysujesz wzrost złożoności.

Z drugiej strony, Flash został stworzony do tego. Znacznie mniej kodu ActionScript 3.0 byłoby wymagane, aby analizować ten XML, układ tekstu (z większą kontrolą nad czcionkami i super / indeksami dolnymi) i renderować krzywe (zobacz flash.wyświetlacz.Klasy graficznej metody jak curveTo). Ogólnie rzecz biorąc, będziesz patrzył na mniej kodu, lepszą konserwację, mniej hacków, szerszą kompatybilność i bardziej stabilne biblioteki rysunków.

Powodzenia z projektem.
 1
Author: Jaysen Marais,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-10 08:22:26

Jak już inni wspominali, Javascript i html nie są dobrymi narzędziami do tego typu rzeczy.

John Resig napisał implementację Processing.org w JavaScript . Używa elementu canvas, więc będzie działał w nowoczesnych wersjach Firefoksa, ale nie będzie działał we wszystkich przeglądarkach. Jeśli zależy Ci tylko na Firefoksie, to prawdopodobnie będzie to droga.

Możesz używać SVG, ale ponownie nie jest to obsługiwane we wszystkich przeglądarkach.

 1
Author: Buddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-10 08:26:48

Potrzebowałem podobnego rozwiązania i szukałem biblioteki JavaScript RaphaelJS . Na przykład możesz narysować prostą strzałkę z (x1,y1) do (x2,y2) za pomocą:

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];
}
Nie wymyśliłem, jak narysować zakrzywioną strzałkę, ale jestem pewien, że to możliwe.
 0
Author: Panagiotis Panagi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-10 08:27:29

Możesz uzyskać zakrzywione końcówki strzałek za pomocą garstki position:absolute divów z background-image ustawionymi na przezroczyste gify... zestaw na początek (góra i dół)... a bacground:repeat div dla środka rozpostartego, a druga para dla końców (góra i dół).

 0
Author: Scott Evernden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-10 08:28:40

Możesz użyć tej biblioteki : wystarczy dodać adnotacje do linii SVG z identyfikatorami elementu source & target. Używa MutationObserver do obserwacji zmian w połączonych elementach.

 0
Author: thSoft,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-29 15:54:47