Привлечение стрелок на странице HTML для визуализации семантических связей между текстовыми промежутками

Существует рекурсивный подход для достижения этой цели:

const input = {condition: "and", rules: [ { data: "123"}, {condition: "and", rules: [{data:"456"}, {condition: "and", rules: [{value: "456"}]} ] } ]}

function test (obj) {
	if(!Object.keys(obj).includes('rules')) return;
	obj.rules.forEach(x => x.data ? x.data = 'foo' : x);
	return test(obj.rules.find(x => !Object.keys(x).includes('data')));
}

test(input)

console.log(input);

примечание: эта функция изменяет входной объект.

39
задан Ilmari Karonen 22 September 2015 в 16:30
поделиться

7 ответов

У Вас есть пара опций: svg или холст .

От взглядов его Вам не нужны эти стрелки, чтобы иметь какую-то конкретную математическую форму, Вам просто нужны они для движения между элементами.

Попытка WireIt. Взгляните на этот Демонстрация WireIt (, который был удержан от использования ). Это использует canvas тег для каждого отдельного провода между плавающим диалоговым окном div с, затем размеры и положениями каждый canvas элемент для предоставления появления соединительной линии в просто правильном пятне. Вам, вероятно, придется реализовать дополнительный наконечник стрелки вращения, если Вы не возражаете против стрелок, входящих к каждому элементу под тем же углом.

Редактирование : демонстрация была удержана от использования .

Редактирование : Проигнорируйте этот ответ, , @Phil H закрепил его

24
ответ дан Community 27 November 2019 в 02:13
поделиться

Это получило мой интерес довольно долго для создания небольшого теста. Код ниже, и Вы можете видеть его в действии

screenshot

, Это перечисляет все промежутки на странице (мог бы хотеть ограничить это просто теми, которые имеют идентификаторы, запускающиеся с T, если это подходит), и использует 'идентификационный' атрибут для создания списка из ссылок. Используя элемент холста позади промежутков, это поочередно привлекает стрелки дуги выше и ниже промежутков для каждого исходного промежутка.

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

И Вам просто нужен вызов где-нибудь к ничьей () функция:

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

будущие модификации, насколько я вижу:

  • Выравнивание вершины более длинных стрелок
  • Рефакторинг, чтобы быть в состоянии потянуть негоризонтальные стрелки: добавить новый холст для каждого?
  • Использование лучшая стандартная программа для получения общих смещений холста и элементов промежутка.

[декабрь 2011 Редактирования: Фиксированный, @Palo спасибо]

Hope это так же полезно, как это была забава.

71
ответ дан Phil H 27 November 2019 в 02:13
поделиться

Вы могли попробовать эта библиотека - это - очень умный материал, надежда, которой это помогает.

РЕДАКТИРОВАНИЕ: Поскольку эта ссылка является битой, вот другая ссылка от Archive.org .

2
ответ дан Kieron 27 November 2019 в 02:13
поделиться

Если Вам не нужны изогнутые стрелки, Вы могли бы использовать абсолютно расположенные отделения выше или ниже списка. Вы могли тогда использовать css для моделирования тех отделений плюс несколько изображений, которые составляют наконечник стрелки. Ниже пример с помощью набора значка из проекта UI jQuery (извините о длинном URL).

Вот CSS для запущения вещей:

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

Теперь мы можем начать собирать отделения стрелки, Например, разрабатывать стрелку от "требует" до "покровителя" в Вашем примере выше, Вы могли сделать оставленный, нижняя часть, и право граничит с отделением с и стрелкой вверх, графической в верхнем левом из отделения

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

, встроенные стили были бы потребностью, которая будет применена сценарием, после выяснения местоположений вещей, которые необходимо будет соединить. Скажем, то, что Ваш список похож на это:

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

Тогда следующий сценарий расположит Вашу стрелку:

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

Разрешение и вставка примеры выше в пустую страницу HTML. Это довольно аккуратно.

2
ответ дан bluish 27 November 2019 в 02:13
поделиться

Я пытаюсь пойти с открытыми веб-технологиями везде, где возможный, но истина является тем HTML & JavaScript (или jQuery) не является инструментами для этого конкретного задания (печальный, но верный), тем более, что схемы Вы привлекаете увеличение сложности.

, С другой стороны, Flash был сделан для этого. Значительно меньше кода ActionScript 3.0 потребовалось бы, чтобы анализировать это XML, расположение Ваш текст (с большим количеством управления шрифтами & супер / нижние индексы) и рендеринг кривые (см. flash.display. Графические методы класса как curveTo). В целом Вы будете смотреть на меньшее количество кода, лучшей пригодности для обслуживания, меньше взломов, более широкой совместимости и более стабильных библиотек рисунка.

Удача с проектом.

2
ответ дан bluish 27 November 2019 в 02:13
поделиться

Как другие упомянули, JavaScript и HTML не являются хорошими инструментами для этого вида вещи.

John Resig записал реализация Processing.org в JavaScript. Это использует элемент холста, таким образом, это будет работать в современных версиях Firefox, но это не будет работать во всех браузерах. Если бы Вы только заботитесь о Firefox, это, вероятно, было бы способом пойти.

Вы могли бы быть в состоянии использовать SVG, но снова, это не поддерживается во всех браузерах.

1
ответ дан bluish 27 November 2019 в 02:13
поделиться

Вы могли получить кривые концы стрелки с помощью горстки position:absolute отделения с background-image набор к прозрачному GIFs... набор для начала (вершина и нижняя часть)... bacground:repeat отделение в течение потребляемой середины и другая пара для концов (вершина и нижняя часть).

0
ответ дан bluish 27 November 2019 в 02:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: