qlik-oss/sn-org-chart

View on GitHub
src/tree/path.js

Summary

Maintainability
A
2 hrs
Test Coverage
B
82%
import { haveNoChildren } from "../utils/tree-utils";
import constants from "./size-constants";

export function getPoints(d, topId, { depthSpacing, isVertical, x, y }, navigationMode) {
  // TODO: Generalize to make all directions work, currently on only ttb working
  const { cardWidth, cardHeight, buttonHeight, cardPadding, buttonMargin } = constants;
  const points = [];
  const halfCard = { x: cardWidth / 2, y: cardHeight / 2 };
  const start = { x: d.xActual, y: d.yActual };

  // TODO: fix so auto mode does not get a path to parent not showing
  if (d.parent && d.parent.data.id !== "Root") {
    const halfDepth = depthSpacing / 2;
    const yOffset = navigationMode === "expandAll" ? cardHeight : cardHeight + cardPadding + buttonHeight;
    const end = { x: x(d.parent) + halfCard.x, y: y(d.parent) + yOffset };

    if (navigationMode !== "expandAll" && haveNoChildren(d.parent.children)) {
      // to leafs
      points.push(
        isVertical
          ? [
              { x: start.x, y: start.y + halfCard.y },
              { x: end.x - halfCard.x, y: start.y + halfCard.y },
              { x: end.x - halfCard.x, y: end.y + buttonMargin },
              { x: end.x, y: end.y + buttonMargin },
              { x: end.x, y: end.y },
            ]
          : [
              { x: start.x, y: start.y },
              { x: start.x, y: end.y - halfCard.y },
              { x: end.x + halfDepth, y: end.y - halfCard.y },
              { x: end.x + halfDepth, y: end.y },
              { x: end.x, y: end.y },
            ],
      );
    } else if (start.x === x(d.parent) || start.y === y(d.parent)) {
      // straight line
      points.push([
        { x: start.x + halfCard.x, y: start.y },
        { x: end.x, y: end.y },
      ]);
    } else {
      // to nodes with children
      points.push(
        isVertical
          ? [
              { x: start.x + halfCard.x, y: start.y },
              { x: start.x + halfCard.x, y: start.y - cardPadding },
              { x: end.x, y: start.y - cardPadding },
              { x: end.x, y: end.y },
            ]
          : [
              { x: start.x, y: start.y },
              { x: start.x - cardPadding, y: start.y },
              { x: start.x - cardPadding, y: end.y },
              { x: end.x, y: end.y },
            ],
      );
    }
  } else if (d.parent) {
    // to up button or dummy root
    points.push([
      { x: start.x + halfCard.x, y: start.y },
      { x: start.x + halfCard.x, y: start.y - cardPadding },
    ]);
  }

  if (d.children && d.data.id !== "Root") {
    // to expand button
    points.push([
      { x: start.x + halfCard.x, y: start.y + cardHeight },
      { x: start.x + halfCard.x, y: start.y + cardHeight + cardPadding },
    ]);
  }

  return points;
}

export function getPath(points) {
  // gets the path from first to last points, making turns with radius r at intermediate points
  const { r } = constants;
  let pathString = `M ${points[0].x} ${points[0].y} `;
  let dir;
  const setDir = (i) => {
    const delta = {
      x: points[i].x - points[i - 1].x,
      y: points[i].y - points[i - 1].y,
    };
    dir = {
      x: (delta.x > 0) - (delta.x < 0) || +delta.x,
      y: (delta.y > 0) - (delta.y < 0) || +delta.y,
    };
  };
  setDir(1);
  for (let i = 1; i < points.length; ++i) {
    const point = points[i];
    if (i < points.length - 1) {
      pathString += `L ${point.x - dir.x * r} ${point.y - dir.y * r} `;
      setDir(i + 1);
      pathString += `Q ${point.x} ${point.y} ${point.x + dir.x * r} ${point.y + dir.y * r} `;
    } else {
      // Don't add curve for last point
      pathString += `L ${point.x} ${point.y} `;
    }
  }

  return pathString;
}

export default function createPaths(node, positioning, topId, navigationMode) {
  node
    .append("path")
    .attr("class", "sn-org-path")
    .attr("id", (d) => d.data.id)
    .attr("d", (d) => {
      let path = "";
      const pointSets = getPoints(d, topId, positioning, navigationMode);
      pointSets.forEach((points) => {
        path += getPath(points).slice(0, -1);
      });

      return path;
    });
}