Diagrama (Arbol) + Canvas +  PureJS
<div id="canvas-container">
  <canvas id="diagramaCanvas"></canvas>
</div>



#canvas-container {
  overflow: auto;
  width: 100%;
  height: 100%;

}

#diagramaCanvas {
  width: 1000px;
  height: 1000px;

}
const canvas = document.getElementById("diagramaCanvas")
const ctx = canvas.getContext("2d")

const canvasContainer = document.getElementById("canvas-container")

const dpr = window.devicePixelRatio || 1
canvas.width = canvas.clientWidth * dpr
canvas.height = canvas.clientHeight * dpr
ctx.scale(dpr, dpr)

canvasContainer.style.width = canvas.width + "px"
canvasContainer.style.height = canvas.height + "px"

ctx.imageSmoothingEnabled = false

ctx.textBaseline = "middle"
ctx.font = "16px Arial"

const tree = {
  title: "Parent",
  children: [
    {
      title: "Child A",
      children: [
          {
          title: "Child A A - XX",
        },
        {
          title: "Child A A - Prueba Larga",
        },
        {
          title: "Child A B",
        },
      ],
    },
    {
      title: "Child B",
      children: [
        {
          title: "Child B A",
        },
      ],
    },
  ],
}

const nodoLado = 60
const separacionHorizontal = 100
const separacionVertical = 20

function calcularTotalNodosNivel(nodo) {
  if (!nodo || !nodo.children) {
    return 1
  } else {
    return nodo.children.reduce(
      (total, hijo) => total + calcularTotalNodosNivel(hijo),
      0
    )
  }
}

function dibujarArbol(nodo, posX, posY, nodoResaltado) {
  ctx.font = "16px Arial"
  const medidaTexto = ctx.measureText(nodo.title)
  const anchoTexto = medidaTexto.width
  const margenHorizontal = 10
  const anchoRect = anchoTexto + margenHorizontal

  if (nodo.children) {
    let yOffset =
      posY - (separacionVertical * (calcularTotalNodosNivel(nodo) - 1)) / 2
    nodo.children.forEach((hijo) => {
      const totalNodosHijo = calcularTotalNodosNivel(hijo)
      const hijoX = posX + separacionHorizontal + anchoRect
      const hijoY = yOffset
      dibujarLinea(posX + anchoRect, posY + 20 / 2, hijoX, hijoY + 20 / 2)
      dibujarArbol(hijo, hijoX, hijoY, nodoResaltado)
      yOffset += (nodoLado + separacionVertical) * totalNodosHijo
    })
  }

  const resaltado = nodo === nodoResaltado
  dibujarNodo(nodo, posX, posY, resaltado)
}

// function dibujarLineaCurva(startX, startY, endX, endY) {
//   const controlX = startX + (endX - startX) / 2;
//   const controlY = startY - separacionVertical * 2;
//   ctx.beginPath();
//   ctx.moveTo(startX, startY);
//   ctx.quadraticCurveTo(controlX, controlY, endX, endY);
//   ctx.strokeStyle = '#000000';
//   ctx.stroke();
// }


function dibujarLinea(x1, y1, x2, y2) {
  ctx.beginPath()
  ctx.moveTo(x1, y1)
  ctx.lineTo(x2, y2)
  ctx.strokeStyle = "#004D40"
  ctx.lineWidth = 2
  ctx.stroke()
  ctx.closePath()
}

function dibujarNodo(nodo, x, y, resaltado) {
  const texto = nodo.title
  const medidaTexto = ctx.measureText(texto)
  const anchoTexto = medidaTexto.width
  const margenHorizontal = 10
  const margenVertical = 10
  const anchoRect = anchoTexto + margenHorizontal
  const altoRect = 20 + margenVertical

  ctx.fillStyle = resaltado ? "#00BFA5" : "#009688"
  ctx.fillRect(x, y, anchoRect, altoRect)
  ctx.strokeStyle = "#004D40"
  ctx.lineWidth = 2
  ctx.strokeRect(x, y, anchoRect, altoRect)

  ctx.fillStyle = "white"
  ctx.font = "16px Arial"
  ctx.textAlign = "center"
  ctx.textBaseline = "middle"
  ctx.fillText(texto, x + anchoRect / 2, y + altoRect / 2)
}

function obtenerNodoClickeado(nodo, posX, posY, punteroX, punteroY) {
  ctx.font = "16px Arial"
  const medidaTexto = ctx.measureText(nodo.title)
  const anchoTexto = medidaTexto.width
  const margenHorizontal = 10
  const margenVertical = 10
  const anchoRect = anchoTexto + margenHorizontal
  const altoRect = 20 + margenVertical

  if (punteroDentroNodo(punteroX, punteroY, posX, posY, anchoRect, altoRect)) {
    return nodo
  }

  if (nodo.children) {
    let yOffset =
      posY - (separacionVertical * (calcularTotalNodosNivel(nodo) - 1)) / 2
    for (const hijo of nodo.children) {
      const totalNodosHijo = calcularTotalNodosNivel(hijo)
      const hijoX = posX + separacionHorizontal + anchoRect
      const hijoY = yOffset
      const nodoClickeado = obtenerNodoClickeado(
        hijo,
        hijoX,
        hijoY,
        punteroX,
        punteroY
      )
      if (nodoClickeado) {
        return nodoClickeado
      }
      yOffset += (nodoLado + separacionVertical) * totalNodosHijo
    }
  }

  return null
}
function punteroDentroNodo(x, y, nodoX, nodoY, anchoRect, altoRect) {
  return (
    x >= nodoX && x <= nodoX + anchoRect && y >= nodoY && y <= nodoY + altoRect
  )
}

function limpiarCanvas() {
  ctx.clearRect(0, 0, canvas.width, canvas.height)
}

let nodoResaltado = null

canvas.addEventListener("mousemove", (event) => {
  const rect = canvas.getBoundingClientRect()
  const x = event.clientX - rect.left
  const y = event.clientY - rect.top

  const nuevoNodoResaltado = obtenerNodoClickeado(tree, inicioX, inicioY, x, y)

  if (nuevoNodoResaltado !== nodoResaltado) {
    nodoResaltado = nuevoNodoResaltado
    limpiarCanvas()
    dibujarArbol(tree, inicioX, inicioY, nodoResaltado)
  }
})

canvas.addEventListener("click", (event) => {
  const rect = canvas.getBoundingClientRect()
  const x = event.clientX - rect.left
  const y = event.clientY - rect.top

  const nodoClickeado = obtenerNodoClickeado(tree, inicioX, inicioY, x, y)

  if (nodoClickeado) {
    console.log("Nodo clickeado:", nodoClickeado.title)
    // Aquí puedes agregar cualquier acción que desees realizar cuando se haga clic en un nodo
  }
})

const inicioX = nodoLado
const inicioY = ((canvas.height / dpr) - nodoLado) / 2

dibujarArbol(tree, inicioX, inicioY)

Diagrama (Arbol) + Canvas + PureJS

  • Files
  • Index
  • Style
  • Script
  • README
  • CDN Add
HTML

Pure JS: Codigo para generar un diagrama en forma de arbol de un objeto con hijos y capturar el evento click sobre uno de sus nodos

PLEASE WAIT...