Distribute several relationships between two elements

Started by William Malabry, February 12, 2022, 12:53:52 PM

Previous topic - Next topic

William Malabry

Hello. First post :)

I find the Eclipse bendpoint relationship system a bit annoying.
Especially in a very simple use case : two elements, several "straight" relationships (no bendpoints needed) between elements. The following script distribute them.

Simple to use : select two elements (A & B) with several relationships. And launch the script.

Some rules to know :
- A & B elements need to share a common part of their sides, horizontally or vertically.
- In the vertical case, labels positions are set to avoid superposition.
- Script will draw relationships in a certain order. If A is selected first then it will draw A=>B relationships, and then B=>A relationships.
- A & B could be nested in different visual objects.

Script is bit complex. No doubt it could be optimized. But it works quite well on Archi 4.8.

Hope this could help !

William

/*
 * Distribute relationships between elements
 */
 
console.clear();
//console.show();

var view = selection.parents().filter('archimate-diagram-model').first();

elements = selection.filter('element');
e1 = selection.filter('element').first();
e2 = selection.filter('element').not($(e1)).first();
if(!e1 || !e2) {  window.alert('2 elements joined by relation(s) have to be selected'); exit(); }
rels = $(e1).rels().filter(function(r) { return r.source.id === e2.id || r.target.id === e2.id; });
if(!rels.size()) {  window.alert('2 elements have to share relation(s)'); exit(); }

// get absolute position of e1 and e2 elements
function absolutePosition(accumulator, current) {
  return {x:(accumulator.x + current.bounds.x), y:(accumulator.y + current.bounds.y)}; 
}
var e1AbsPosition = Java.from($(e1).parents().not('folder').not('archimate-diagram-model')).reduce(absolutePosition, {x:e1.bounds.x, y:e1.bounds.y});
var e2AbsPosition = Java.from($(e2).parents().not('folder').not('archimate-diagram-model')).reduce(absolutePosition, {x:e2.bounds.x, y:e2.bounds.y});

// get absolute position of e1 and e2 centers
var e1CenterPosition = {x: e1AbsPosition.x + Math.round(e1.bounds.width/2), y: e1AbsPosition.y + Math.round(e1.bounds.height/2)};
var e2CenterPosition = {x: e2AbsPosition.x + Math.round(e2.bounds.width/2), y: e2AbsPosition.y + Math.round(e2.bounds.height/2)};

// parameters
var verticalMaxStep = 20;
var horizontalMaxStep = 30;

// determine which layout to apply
if ((e1AbsPosition.y >= e2AbsPosition.y && e1AbsPosition.y <= e2AbsPosition.y + e2.bounds.height)
  || (e2AbsPosition.y >= e1AbsPosition.y && e2AbsPosition.y <= e1AbsPosition.y + e1.bounds.height)) {
  layout = 'horizontal';
} else if((e1AbsPosition.x >= e2AbsPosition.x && e1AbsPosition.x <= e2AbsPosition.x + e2.bounds.width)
  || (e2AbsPosition.x >= e1AbsPosition.x && e2AbsPosition.x <= e1AbsPosition.x + e1.bounds.width)) {
  layout = 'vertical';
} else {
  layout = 'angled';
  window.alert('No common surface between the 2 elements');
  exit;


if (layout === 'horizontal') {
 
  // set x bendoints. Same for all relation : centered
  if(e1AbsPosition.x <= e2AbsPosition.x) {
    startX = e2AbsPosition.x - (e1AbsPosition.x + e1.bounds.width) + e1.bounds.width/2;
    endX = - (e2AbsPosition.x - (e1AbsPosition.x + e1.bounds.width) + e2.bounds.width/2);
  } else {
    startX = e1AbsPosition.x - (e2AbsPosition.x + e2.bounds.width) + e2.bounds.width/2;
    endX = - (e1AbsPosition.x - (e2AbsPosition.x + e2.bounds.width) + e1.bounds.width/2);
  }

  // determine e1 and e2 vertical intersection informations
  minAbsY = Math.max(e1AbsPosition.y, e2AbsPosition.y);
  maxAbsY = Math.min((e1AbsPosition.y + e1.bounds.height), (e2AbsPosition.y + e2.bounds.height));
  sharedHeight = maxAbsY - minAbsY;
 
  // determine step, vertical distance between flows
  step = Math.round(Math.min(sharedHeight/(rels.size()+1), verticalMaxStep));
  currentStep = Math.round(- step * (rels.size()-1)/2);
 
  // set y bendpoints on each relations
  rels.each(function (r) {
    r.deleteAllBendpoints();
    if (r.source.id === e1.id) {
      startY = minAbsY + sharedHeight/2 - e1CenterPosition.y + currentStep;
      endY = minAbsY + sharedHeight/2 - e2CenterPosition.y + currentStep;
    } else {
      startY = minAbsY + sharedHeight/2 - e2CenterPosition.y + currentStep;
      endY = minAbsY + sharedHeight/2 - e1CenterPosition.y + currentStep;
    }
    r.addRelativeBendpoint({ startX:startX, startY:startY, endX:endX, endY:endY }, 0);
    currentStep += step;
  });

} else if(layout === 'vertical') {


  // determine e1 and e2 horizontal intersection informations
  minAbsX = Math.max(e1AbsPosition.x, e2AbsPosition.x);
  maxAbsX = Math.min((e1AbsPosition.x + e1.bounds.width), (e2AbsPosition.x + e2.bounds.width));
  sharedWidth = maxAbsX - minAbsX;

  // determine stepX, horizontal distance between flows
  stepX = Math.round(Math.min(sharedWidth/(rels.size()+1), horizontalMaxStep));
  currentStepX = Math.round(- stepX * (rels.size()-1)/2);

  // determine stepY, vertical distance between flows
  minAbsY = Math.min((e1AbsPosition.y + e1.bounds.height), (e2AbsPosition.y + e2.bounds.height));
  maxAbsY = Math.max(e1AbsPosition.y, e2AbsPosition.y);
  RelationDistance = maxAbsY - minAbsY;
 
  stepY = Math.round(Math.min(RelationDistance/(rels.size()+1), verticalMaxStep));
  currentStepY = Math.round(- stepY * (rels.size()-1)/2);

  // set x bendpoints on each relations
  rels.each(function (r) {
    r.deleteAllBendpoints();
   
    if (r.source.id === e1.id) {
      startX = minAbsX + sharedWidth/2 - e1CenterPosition.x + currentStepX;
      endX = minAbsX + sharedWidth/2 - e2CenterPosition.x + currentStepX;
    } else {
      startX = minAbsX + sharedWidth/2 - e2CenterPosition.x + currentStepX;
      endX = minAbsX + sharedWidth/2 - e1CenterPosition.x + currentStepX;
    }
   
    // set y bendoints. Same for all relation : centered
    if(e1AbsPosition.y <= e2AbsPosition.y) {
      startY = e2AbsPosition.y - (e1AbsPosition.y + e1.bounds.height) + e1.bounds.height/2 + currentStepY;
      endY = - (e2AbsPosition.y - (e1AbsPosition.y + e1.bounds.height) + e2.bounds.height/2) + currentStepY;
    } else {
      startY = e1AbsPosition.y - (e2AbsPosition.y + e2.bounds.height) + e2.bounds.height/2 + currentStepY;
      endY = - (e1AbsPosition.y - (e2AbsPosition.y + e2.bounds.height) + e1.bounds.height/2) + currentStepY;
    }
   
    r.addRelativeBendpoint({ startX:startX, startY:startY, endX:endX, endY:endY }, 0);
    currentStepX += stepX;
    currentStepY += stepY;
  });
}


Jean-Baptiste Sarrodie

If you value and use Archi, please consider making a donation!
Ask your ArchiMate related questions to the ArchiMate Community's Discussion Board.

projetnumero9

Hi,
Thanks,
Just field tested it, and worked like a charm,

PN9

Xiaoqi

This is really nice enhancement! Works fine as well on my 4.9.2, thanks greatly!

jsimoncello

Many, many, many thanks, I was trying to do the same thing, not knowing where to start !

LapizLazuli

@William Malabry : Awesome, thanks for sharing.

Quote from: William Malabry on February 12, 2022, 12:53:52 PMHello. First post :)

"Mes pareils à deux fois ne se font point connaître
Et pour leurs coups d'essai veulent des coups de maître.
Don Rodrique, "Le Cid", Act II
"

First shot, maestro shot :)