javascript - trama - Dibujar flechas en una página HTML para visualizar enlaces semánticos entre tramos textuales
trama de los textos (10)
Tengo una página HTML con algunos espacios textuales marcados de la siguiente manera:
...
<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>
...
Es decir, cada tramo tiene una ID y se refiere a cero o más tramos a través de sus ID.
Me gustaría visualizar estas referencias como flechas.
Dos preguntas:
- ¿Cómo puedo asignar una ID de un tramo a las coordenadas de pantalla de la representación del tramo?
- ¿Cómo dibujo flechas que van de un renderizado a otro?
La solución debería funcionar en Firefox, trabajar en otros navegadores es una ventaja pero no realmente necesario. La solución podría usar jQuery o alguna otra biblioteca liviana de JavaScript.
Podrías probar esta biblioteca , es algo muy inteligente, espero que ayude.
EDITAR: Como este enlace está muerto, aquí hay otro enlace de Archive.org .
Como otros han mencionado, Javascript y html no son buenas herramientas para este tipo de cosas.
John Resig escribió una implementación de Processing.org en JavaScript . Utiliza el elemento canvas, por lo que funcionará en las versiones modernas de Firefox, pero no funcionará en todos los navegadores. Si solo te preocupa Firefox, probablemente este sea el camino a seguir.
Es posible que pueda usar SVG, pero una vez más, esto no es compatible con todos los navegadores.
Intento ir con tecnologías web abiertas siempre que sea posible, pero la verdad es que HTML y JavaScript (o jQuery) no son las herramientas para este trabajo en particular (triste pero cierto), especialmente a medida que los diagramas que estás dibujando aumentan en complejidad.
Por otro lado, Flash fue hecho para esto. Significativamente se necesitaría menos código de ActionScript 3.0 para analizar ese XML, diseñar su texto (con más control sobre las fuentes y super / subíndices) y renderizar las curvas (consulte los métodos de la clase flash.display.Graphics como curveTo
). En general, verá menos código, mejor capacidad de mantenimiento, menos ataques, mayor compatibilidad y bibliotecas de dibujo más estables.
Buena suerte con el proyecto.
Necesitaba una solución similar, y estaba investigando la Biblioteca JavaScript de RaphaelJS . Por ejemplo, puede dibujar una flecha recta desde (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];
}
No he descubierto cómo dibujar una flecha curva, pero estoy seguro de que es posible.
Puedes obtener las puntas de flecha curvadas usando un puñado de position:absolute
divs position:absolute
con background-image
configurada en GIF transparentes ... un conjunto para comenzar (arriba y abajo) ... un bacground:repeat
div para medio expandible, y otro par para los extremos (arriba y abajo).
Si no necesita flechas curvas, puede usar divs absolutamente posicionados arriba o debajo de la lista. Luego puede usar css para dar estilo a esos divs más un par de imágenes que conforman la punta de la flecha. A continuación se muestra un ejemplo que utiliza el conjunto de iconos del proyecto jQuery UI (lo siento por la URL larga).
Aquí está el CSS para comenzar:
<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>
Ahora podemos comenzar a armar divisiones de flecha. Por ejemplo, para aplicar un estilo a la flecha de "requiere" a "promotor" en el ejemplo anterior, puede hacer los bordes izquierdo, inferior y derecho en el gráfico div con el gráfico de flecha hacia arriba en la parte superior izquierda del div.
<div class=''below'' style="position:absolute;top:30px;left:30px;width:100px;height:16px">
<span class=''arrow left''></span>
</div>
Los estilos en línea tendrían que ser aplicados por script después de que descubrieras las ubicaciones de las cosas que necesitarías para conectarte. Digamos que su lista se ve así:
<span id="promoter">Promoter</span><span>Something Else</span><span id="requires">Requires</span>
Luego, el siguiente script posicionará su 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>
Continúe y pegue los ejemplos anteriores en una página html en blanco. Es algo ordenado.
Una gran biblioteca para flechas es JointJS que se basa en Raphael como se muestra arriba. Con JointJS puedes dibujar fácilmente flechas con curvas o vértices sin ningún tipo de complicación ;-)
var j34 = s3.joint(s4, uml.arrow).setVertices(["170 130", "250 120"]);
Esto define una flecha ''j34'' que conecta dos js elementos s3 con s4. Todo lo demás se puede leer en la documentación de JointJS.
Esto captó mi interés durante el tiempo suficiente para producir una pequeña prueba. El código está debajo, y puedes verlo en acción
Enumera todos los tramos de la página (puede restringirlo solo a aquellos con identificadores que comienzan con T si es adecuado) y utiliza el atributo ''ids'' para compilar la lista de enlaces. Usando un elemento de lienzo detrás de los tramos, dibuja flechas de arco alternativamente por encima y por debajo de los tramos para cada tramo fuente.
<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>
Y solo necesita una llamada a alguna parte de la función draw ():
<body onload="draw();">
Luego un lienzo detrás del conjunto de luces.
<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>
Modificaciones futuras, por lo que puedo ver:
- Aplanando la parte superior de las flechas más largas
- Refactorización para poder dibujar flechas no horizontales: agregar un nuevo lienzo para cada una?
- Use una rutina mejor para obtener las compensaciones totales de los elementos canvas y span.
[Editar diciembre de 2011: corregido, gracias @Palo]
Espero que sea tan útil como divertido.
Puede usar esta biblioteca : simplemente anote sus líneas SVG con los ID del elemento fuente y destino. Utiliza MutationObserver para observar los cambios en los elementos conectados.
Tienes un par de opciones: svg o canvas .
Por lo que se ve, no necesitas estas flechas para tener una forma matemática particular, solo las necesitas para pasar de un elemento a otro.
Prueba WireIt . Echa un vistazo a esta demostración de WireIt ( que ha quedado en desuso ). Utiliza una etiqueta de canvas
para cada cable individual entre las divisiones de diálogo flotantes, luego dimensiona y posiciona cada elemento de canvas
para dar la apariencia de una línea de conexión en el lugar correcto. Puede que tenga que implementar una punta de flecha giratoria adicional, a menos que no le importe que las flechas entren a cada elemento en el mismo ángulo.
Editar : la demostración ha quedado obsoleta .
Editar : Ignora esta respuesta, @Phil H la clavó