Disegnare frecce su una pagina HTML per visualizzare collegamenti semantici tra span testuali
-
23-08-2019 - |
Domanda
Ho una pagina HTML con alcuni intervalli testuali contrassegnati in questo modo:
...
<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>
...
Cioè.ogni span ha un ID e fa riferimento a zero o più span tramite i relativi ID.
Vorrei visualizzare questi riferimenti come frecce.
Due domande:
- Come posso mappare un ID di una campata alle coordinate dello schermo del rendering della campata?
- Come faccio a disegnare le frecce che passano da un rendering all'altro?
La soluzione dovrebbe funzionare in Firefox, lavorare in altri browser è un vantaggio ma non è realmente necessario.La soluzione potrebbe utilizzare jQuery o qualche altra libreria JavaScript leggera.
Soluzione
Hai un paio di opzioni: svg o tela .
Dagli sguardi di esso non ti servono queste frecce per avere una particolare forma matematica, basta loro di andare tra gli elementi.
WireIt . Date un'occhiata a questo WireIt Demo ( che è stato deprecato ). Esso utilizza un tag canvas
per ogni singolo filo tra i div
s dialogo galleggianti, quindi dimensioni e posizioni ciascun elemento canvas
per dare l'aspetto di una linea di collegamento proprio al punto giusto. Potrebbe essere necessario implementare una punta di freccia rotante aggiuntivo, a meno che non si mente le frecce entrano ad ogni elemento con la stessa angolazione.
Modifica : la demo è stato deprecato .
Modifica : Ignorare questa risposta, @Phil H è inchiodato
testuali-campate -links-tra-Altri suggerimenti
Questa catturato il mio interesse per un tempo sufficiente a produrre un piccolo test. Il codice è al di sotto, e si può vederlo in azione
E 'elenca tutte le campate nella pagina (potrebbe voler limitare tale solo a quelli con gli ID che iniziano con T se questo è idoneo), e usa l'attributo 'id' per costruire la lista di link. Utilizzando un elemento di tela dietro le campate, trae frecce arco alternativamente sopra e sotto le campate per ogni campata sorgente.
<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 basta una chiamata da qualche parte per la funzione draw ():
<body onload="draw();">
Poi una tela dietro la serie di campate.
<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>
modifiche future, per quanto posso vedere:
- appiattimento all'inizio della frecce più lunghe
- refactoring per essere in grado di disegnare frecce non orizzontali: aggiungere una nuova tela per ogni
- Utilizzare una routine migliore per ottenere gli offset totale degli elementi di tela e span.
[Modifica dicembre 2011: fisso, grazie @Palo]
La speranza che è tanto utile quanto è stato divertente.
Una grande libreria per frecce è JointJS che si basa su Raphael come mostrato sopra. Con JointJS si può facilmente disegnare frecce con curve o vertici senza alcun roba complicata; -)
var j34 = s3.joint(s4, uml.arrow).setVertices(["170 130", "250 120"]);
Questo definisce una freccia 'J34' che collega due js Articoli s3 con s4. Tutto il resto si può leggere nella documentazione di JointJS.
Si potrebbe provare a questa libreria - è roba molto intelligente, spero che aiuta.
EDIT: Come questo link è morto, ecco un altro link da Archive.org .
cerco di andare con le tecnologie web aperte per quanto possibile, ma la verità è che l'HTML e JavaScript (o jQuery) non sono gli strumenti di questo particolare lavoro (triste ma vero), tanto più che gli schemi si sta disegnando in aumento complessità.
D'altra parte, Flash è stato fatto per questo. Significativamente meno codice ActionScript 3.0 sarebbe necessario per analizzare che XML, il layout il testo (con un maggiore controllo sui tipi di carattere e super / indici) e rendere le curve (si veda il classe flash.display.Graphics metodi come curveTo
). Nel complesso si troverà di fronte al meno codice, una migliore manutenibilità, un minor numero di hack, maggiore compatibilità e le librerie di disegno più stabili.
Buona fortuna con il progetto.
Se non avete bisogno di frecce curve, è possibile utilizzare posizionato in modo assoluto div sopra o sotto la lista. È quindi possibile utilizzare i CSS per lo stile quei div più un paio di immagini che compongono la punta della freccia. Di seguito è riportato un esempio utilizzando il set di icone dal progetto jQuery UI (dispiace per il lungo URL).
Ecco il CSS per ottenere le cose iniziato:
<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>
Ora possiamo iniziare ad assemblare div freccia. Per esempio, per lo stile della freccia da "richiede" a "promoter" nel tuo esempio di cui sopra, si potrebbe fare a sinistra, in basso, e confina a destra sul div con e rivolto verso l'alto freccia grafico in alto a sinistra del div.
<div class='below' style="position:absolute;top:30px;left:30px;width:100px;height:16px">
<span class='arrow left'></span>
</div>
sarebbe bisogno Gli stili inline da applicarsi da parte di script dopo aver capito le posizioni delle cose che avrebbero bisogno di connettersi. Diciamo che la vostra lista simile a questa:
<span id="promoter">Promoter</span><span>Something Else</span><span id="requires">Requires</span>
Poi il seguente script posizionerà la freccia:
<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>
Vai avanti e incollare gli esempi di cui sopra in una pagina html vuota. È una specie di pulito.
Come altri hanno detto, JavaScript e HTML non sono buoni strumenti per questo genere di cose.
John Resig scritto un'implementazione di Processing.org in JavaScript . Esso utilizza l'elemento canvas, in modo da funzionare in versioni moderne di Firefox, ma non funziona in tutti i browser. Se vi interessa soltanto Firefox, questo sarebbe probabilmente la strada da percorrere.
Potreste essere in grado di utilizzare SVG, ma ancora una volta, questo non è supportato in tutti i browser.
ho bisogno di una soluzione simile, e stavo guardando in RaphaelJS JavaScript Library . Per esempio è possibile disegnare una freccia dritta da (x1,y1)
a (x2,y2)
con:
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];
}
Non ho capire come disegnare una freccia curva, ma sono sicuro che sia possibile.
Si potrebbe ottenere la freccia curva termina con una manciata di div position:absolute
con background-image
impostato GIF trasparenti ... un insieme di inizio (superiore e inferiore) ... un div bacground:repeat
per mezzo espandibile, e un'altra coppia per le estremità (superiore e inferiore).
Puoi usare questa biblioteca:annota semplicemente le tue righe SVG con gli ID dell'elemento source e target.Utilizza MutationObserver osservare i cambiamenti negli elementi collegati.