41

次のようなマークアップされたテキスト スパンを含む HTML ページがあります。

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

つまり、各スパンには ID があり、ID を介して 0 個以上のスパンを参照します。

これらの参照を矢印として視覚化したいと思います。

2 つの質問:

  • スパンの ID をスパンのレンダリングの画面座標にマップするにはどうすればよいですか?
  • あるレンダリングから別のレンダリングに向かう矢印を描くにはどうすればよいですか?

ソリューションは Firefox で動作するはずです。他のブラウザーで動作することはプラスですが、実際には必要ではありません。このソリューションでは、jQuery またはその他の軽量な JavaScript ライブラリを使用できます。

4

10 に答える 10

71

これは、ちょっとしたテストを作成するのに十分長い間、私の興味を引き付けました。コードは以下にあり、実際の動作を確認できます

スクリーンショット

ページ上のすべてのスパンを一覧表示し (適切であれば、ID が T で始まるものだけに制限したい場合があります)、「ids」属性を使用してリンクのリストを作成します。スパンの背後にあるキャンバス要素を使用して、各ソース スパンのスパンの上と下に交互に円弧の矢印を描画します。

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

そして、 draw() 関数への呼び出しが必要なだけです:

<body onload="draw();"> 

次に、一連のスパンの背後にあるキャンバス。

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

私が見る限り、将来の変更:

  • 長い矢印の上部を平らにする
  • 非水平矢印を描画できるようにリファクタリング: それぞれに新しいキャンバスを追加しますか?
  • canvas 要素と span 要素の合計オフセットを取得するには、より適切なルーチンを使用してください。

[2011 年 12 月編集: 修正、@Palo に感謝]

それが楽しかったのと同じくらい役に立つことを願っています。

于 2009-03-08T16:03:12.463 に答える
25

いくつかのオプションがあります:svgまたはcanvas

見た目からすると、これらの矢印を特定の数学的形式にする必要はなく、要素間を移動するだけで済みます。

WireItをお試しください。このWireItデモ非推奨)をご覧ください。canvasフローティングダイアログ間の個々のワイヤにタグを使用し、div各要素のサイズと位置canvasを調整して、適切な場所に接続線の外観を与えます。同じ角度で各要素に入る矢印を気にしない限り、追加の回転矢印を実装する必要がある場合があります。

編集デモは非推奨になりました。

編集:この答えを無視してください、@PhilHはそれを釘付けにしました

于 2009-02-16T19:07:30.283 に答える
4

矢印の優れたライブラリは、上記の Raphael に基づくJointJSです。JointJS を使用すると、複雑なことをしなくても、曲線や頂点で簡単に矢印を描くことができます ;-)

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

これは、2 つの js アイテム s3 と s4 を接続する矢印 'j34' を定義します。他のすべては、JointJS のドキュメントで読むことができます。

于 2012-02-22T08:37:14.317 に答える
2

この JavaScript Vector Graphics Libraryを試すことができます。これは非常に巧妙なものです。役に立てば幸いです。

編集: このリンクは無効になっているため、ここにArchive.orgからの別のリンクがあります。

于 2009-02-25T16:09:28.127 に答える
1

他の人が述べたように、Javascript と html は、この種のことには適していません。

John Resig はJavaScript で Processing.org の実装を書きました。canvas 要素を使用しているため、最新バージョンの Firefox で動作しますが、すべてのブラウザーで動作するとは限りません。Firefox だけに関心がある場合は、おそらくこれが最適です。

SVG を使用できる場合もありますが、これもすべてのブラウザーでサポートされているわけではありません。

于 2009-02-28T02:09:09.757 に答える
0

position:absolute透明なGIFに設定された少数のdivを使用して、湾曲した矢印の端を取得できますbackground-image...最初のセット(上と下)...bacground:repeat展開可能な中央のdiv、および端(上と下)の別のペア。

于 2009-02-28T20:23:03.607 に答える
0

このライブラリを使用できます: SVG 行にソース要素とターゲット要素の ID で注釈を付けるだけです。MutationObserverを使用して、接続された要素の変化を観察します。

于 2015-01-29T15:54:47.840 に答える
0

同様の解決策が必要で、RaphaelJS JavaScript Libraryを調べていました。たとえば、次のように から(x1,y1)への直線矢印を描くことができます(x2,y2)

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];
}

曲がった矢印を描く方法はわかりませんが、可能だと確信しています。

于 2011-11-17T09:16:21.107 に答える