4

* Project Description

I am creating a responsive tournament Brackets tree web Page.

* Problem

My Problem is that I want to connect each Bracket "each bracket is a div of its own" to the next one with a decorated line, in general I want two connect two divs with a line that I can decorate.

- Example

Example1

Example1

Example2

Example2

* what I tried

I tried using the CanvasRenderingContext2D but I didn't have much luck in it, or at least didn't know how to fully utilize it

* What I want/ What's expected

I want to be able to connect the two brackets/divs with a line that can be bent and moved "by the developer not the user" to design it.

  • p.s: This only needs to be shown on screens larger than "xs" so mobile devices is not a concern.

* My Code

Stackblitz Project

- Used Libraries and versions

  • Angular V:5.1
  • Flex-Layout "angular" V: 2.00 beta 12
  • Angular Material V: 5.01
yousif fayed
  • 331
  • 1
  • 4
  • 20
  • Hello @yousif fayed. You can try: https://www.cssscript.com/draw-svg-paths-two-elements-leader-line/ Or: https://stackoverflow.com/questions/6278152/draw-a-connecting-line-between-two-elements – Vương Hữu Thiện Mar 15 '21 at 02:33
  • I have sent you the latest answer below. Based on my js library. https://stackblitz.com/edit/angular-connect-with-lines?file=src/app/app.component.ts – Vương Hữu Thiện Mar 24 '21 at 04:56

2 Answers2

4

<div style="position:absolute; left:0; top:0; width: 100px; height: 50px; background:red;"></div>
<div style="position:absolute; left:250px; top:150px; width: 100px; height: 50px; background: blue;"></div>

<svg style="position:absolute; left:0; top:0;" width="300" height="300">
    <line x1="100" y1="50" x2="250" y2="150" stroke="black"/>
</svg>

Example:

In app.component.html file:

<div class="cont">
  <div *ngFor="let item of arr_item; index as i" class="block d-flex align-items-center justify-content-between">
    <div class="d-flex flex-column" #left>
      <div *ngFor="let val of item.left" class="box box-left">
        {{val}}
      </div>
    </div>
    <div class="d-flex flex-column align-items-end" #right>
      <div *ngFor="let val of item.right" class="box box-right">
        {{val}}
      </div>
    </div>
  </div>
  <svg style="position: absolute; z-index: -1; width:100%; height:100%; top: 0; left: 0;">
    <polyline *ngFor="let item of polylines" 
      [attr.points]="item.points"
      [attr.fill]="item.fill"
      [attr.stroke]="item.stroke"
      [attr.stroke-width]="item.strokeWidth"
    />
  </svg>
</div>

In app.component.css file:

.cont {
  position: relative;
}
.box {
  padding: 10px;
  border: 1px solid #292929;
  width: fit-content;
  margin: 10px;
  max-width: 200px;
}
.block {
  margin: 10px;
}

In app.component.ts file:

import {
  AfterViewInit,
  Component,
  ElementRef,
  OnInit,
  QueryList,
  ViewChildren
} from "@angular/core";

export class Polyline {
  points: string;
  fill: string;
  stroke: string;
  strokeWidth: number;
  constructor(
    points?: string,
    fill?: string,
    stroke?: string,
    strokeWidth?: number
  ) {
    this.points = points || "";
    this.fill = fill || "none";
    this.stroke = stroke || "blue";
    this.strokeWidth = strokeWidth || 3;
  }
}

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit, AfterViewInit {
  arr_item = [
    {
      left: ["Lorem Ipsum", "ABC"],
      right: ["Hello World", "Hello"]
    },
    {
      left: [
        "Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
        "Lorem Ipsum is simply dummy text"
      ],
      right: [""]
    },
    {
      left: [
        "",
        "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s"
      ],
      right: ["Rainbow"]
    }
  ];

  @ViewChildren("left", { read: ElementRef }) left: QueryList<ElementRef>;
  @ViewChildren("right", { read: ElementRef }) right: QueryList<ElementRef>;

  polylines: Polyline[] = [];

  ngAfterViewInit() {
    this.drawLines();
  }

  ngOnInit(): void {
    window.addEventListener("resize", () => {
      this.drawLines();
    });
  }

  getPos_line(ele) {
    var className = ele.getAttribute("class");
    if (className.indexOf("left") !== -1) {
      return {
        x: Math.round(ele.offsetLeft + ele.offsetWidth),
        y: Math.round(ele.offsetTop + ele.offsetHeight / 2)
      };
    } else {
      return {
        x: Math.round(ele.offsetLeft),
        y: Math.round(ele.offsetTop + ele.offsetHeight / 2)
      };
    }
  }

  getMaxX_Left(arr: any) {
    if (arr.length >= 1) {
      var maxX = this.getPos_line(arr[0]).x;
      for (var i = 0; i < arr.length; i++) {
        var posLine = this.getPos_line(arr[i]);
        if (posLine.x > maxX) {
          maxX = posLine.x;
        }
      }
      return maxX;
    } else {
      return null;
    }
  }

  getMinX_Right(arr: any) {
    if (arr.length >= 1) {
      var minX = this.getPos_line(arr[0]).x;
      for (var i = 0; i < arr.length; i++) {
        var posLine = this.getPos_line(arr[i]);
        if (posLine.x < minX) {
          minX = posLine.x;
        }
      }
      return minX;
    } else {
      return null;
    }
  }

  getCenY(left, right) {
    if (left.offsetHeight >= right.offsetHeight) {
      return Math.round(left.offsetTop + left.offsetHeight / 2);
    } else {
      return Math.round(right.offsetTop + right.offsetHeight / 2);
    }
  }

  drawLines() {
    this.polylines = [];
    for (var i = 0; i < this.arr_item.length; i++) {
      var polylines_temp: Polyline[] = [];
      var left_children = this.left.get(i).nativeElement.children;
      var right_children = this.right.get(i).nativeElement.children;
      var left_childrenLen = left_children.length;
      var right_childrenLen = right_children.length;
      var maxX_left = this.getMaxX_Left(left_children);
      var minX_right = this.getMinX_Right(right_children);
      var cenY = this.getCenY(
        this.left.get(i).nativeElement,
        this.right.get(i).nativeElement
      );
      var cenX = Math.round((maxX_left + minX_right) / 2);

      var space = 0;
      if (left_childrenLen > 1 && right_childrenLen > 1) {
        space = 10;
      }

      for (var j = 0; j < left_childrenLen; j++) {
        var posLine = this.getPos_line(left_children[j]);
        polylines_temp.push(
          new Polyline(`
            ${posLine.x},${posLine.y} 
            ${cenX - space},${posLine.y} 
            ${cenX - space},${cenY} 
            ${cenX},${cenY} 
          `)
        );
      }
      for (var j = 0; j < right_childrenLen; j++) {
        var posLine = this.getPos_line(right_children[j]);
        polylines_temp.push(
          new Polyline(`
            ${cenX},${cenY} 
            ${cenX + space},${cenY} 
            ${cenX + space},${posLine.y}  
            ${posLine.x},${posLine.y} 
          `)
        );
      }
      this.polylines = this.polylines.concat(polylines_temp);
    }
  }
}

Link to Stackblitz

Image Result

Vương Hữu Thiện
  • 1,460
  • 14
  • 21
  • Hello @yousiffayed. My answer is a suggestion. I posted a cssscript library and stackoverflow link in the comments section below your question. – Vương Hữu Thiện Mar 19 '21 at 03:31
  • 1
    great script! Do you also can provide a version where the tree from top->down? – Lars Apr 10 '23 at 05:53
  • Hi @Lars I fixed it in the link below. https://stackblitz.com/edit/angular-connect-with-lines-hae5yl?file=src/app/app.component.ts I sent a reply via Mail. Let me know if you need further assistance. – Vương Hữu Thiện Apr 15 '23 at 20:38
0
clearcanv() {
while (this.svg1.lastChild) {
  this.svg1.removeChild(this.svg1.lastChild);
}
this.lines = [];
console.log(this.lines.length);}

drawcanv() {
var body = document.body,
  html = document.documentElement;

var pheight = Math.max(
  body.scrollHeight,
  body.offsetHeight,
  html.clientHeight,
  html.scrollHeight,
  html.offsetHeight
);
this.svg1.setAttribute("id", "svg");
var j2 = 0;
for (let i = 0; i < this.stages.length; i++) {
  j2 = 0;
  for (let j = 0; j < this.stages[i].Hist.length; j += 2) {
    //Get Position of elements
    var element1A = document.getElementById(i.toString() + j.toString());
    var element1B = document.getElementById(
      i.toString() + (j + 1).toString()
    );
    var element2 = document.getElementById(
      (i + 1).toString() + j2.toString()
    );
    const x1A = element1A.offsetLeft + element1A.clientWidth;
    const x1B = element1B.offsetLeft + element1B.clientWidth;
    const x2 = element2.offsetLeft;
    const y1A =
      element1A.offsetTop + element1A.getBoundingClientRect().height / 2;
    const y1B =
      element1B.offsetTop + element1B.getBoundingClientRect().height / 2;
    const y2 =
      element2.offsetTop + element2.getBoundingClientRect().height / 2;
    const halfwayx = Math.abs(x1A + x2) / 2;
    const halfwayy = Math.abs(y1A + y1B) / 2;
    //=======================================================================================
    this.DrawLine(x1A, halfwayx, y1A, y1A, pheight);
    this.DrawLine(x1B, halfwayx, y1B, y1B, pheight);
    this.DrawLine(halfwayx, halfwayx, y1A, halfwayy, pheight);
    this.DrawLine(halfwayx, halfwayx, y1B, halfwayy, pheight);
    j2++;
  }
}}
DrawLine(xs: number, xd: number, ys: number, yd: number, PageHeight: Number) {
//creating a line and adding design
this.lines.push(
  document.createElementNS("http://www.w3.org/2000/svg", "line")
);
this.lines[this.lines.length - 1].setAttribute("stroke-width", "1px");
this.lines[this.lines.length - 1].setAttribute("stroke", "#000000");
this.lines[this.lines.length - 1].setAttribute(
  "marker-end",
  "url(#triangle)"
);
//=======================================================================================
//appending to and styling the svg
this.svg1.appendChild(this.lines[this.lines.length - 1]);
document.body.appendChild(this.svg1);
this.svg1.style.position = "absolute";
this.svg1.style.top = "0";
this.svg1.style.left = "0";
this.svg1.style.width = "100%";
this.svg1.style.height = PageHeight.toString();
this.svg1.style.zIndex = "-2";
//==================================================================s=====================
//Drawing the line.
this.lines[this.lines.length - 1].setAttribute("x1", `${xs}`);
this.lines[this.lines.length - 1].setAttribute("x2", `${xd}`);
this.lines[this.lines.length - 1].setAttribute("y1", `${ys}`);
this.lines[this.lines.length - 1].setAttribute("y2", `${yd}`);
//=======================================================================================}
yousif fayed
  • 331
  • 1
  • 4
  • 20