Skip to content

Commit

Permalink
touchup: include node height in layout
Browse files Browse the repository at this point in the history
so that edges can be drawn via layout calc without having some points
being too high/above the bottom of a node
  • Loading branch information
keyserj committed Nov 24, 2024
1 parent db856cd commit 2bae58e
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/web/topic/components/Node/EditableNode.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ContentIndicators } from "@/web/topic/components/Indicator/ContentIndic
import { StatusIndicators } from "@/web/topic/components/Indicator/StatusIndicators";

export const nodeWidthRem = 11;
const nodeHeightRem = 4.125;
const nodeHeightRem = 3.5625; // 1 line of text results in 57px height, / 16px = 3.5625rem

export const nodeWidthPx = nodeWidthRem * htmlDefaultFontSize;
export const nodeHeightPx = nodeHeightRem * htmlDefaultFontSize;
Expand Down
4 changes: 3 additions & 1 deletion src/web/topic/hooks/diagramHooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState } from "react";

import { Diagram, PositionedDiagram, PositionedNode } from "@/web/topic/utils/diagram";
import { isNode } from "@/web/topic/utils/graph";
import { NodePosition, layout } from "@/web/topic/utils/layout";
import {
useForceNodesIntoLayers,
Expand All @@ -19,7 +20,8 @@ export const useLayoutedDiagram = (diagram: Diagram) => {

// re-layout if this changes
const diagramHash = [...diagram.nodes, ...diagram.edges]
.map((graphPart) => graphPart.id)
// not 100% sure that it's worth re-laying out when node text changes, but we can easily remove if it doesn't seem like it
.map((graphPart) => (isNode(graphPart) ? graphPart.id + graphPart.data.label : graphPart.id))
.concat(
String(forceNodesIntoLayers),
String(layerNodeIslandsTogether),
Expand Down
43 changes: 41 additions & 2 deletions src/web/topic/utils/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,42 @@ const calculatePartition = (node: Node, edges: Edge[]) => {
}
};

const textAreaWidthPx = 164;
const textAreaPerRowPx = 16;
const heightPxOfOneRowDiv = textAreaPerRowPx + 1; // not sure why but a one-row text area seems to have an extra pixel
const maxRows = 3;
const maxTextAreaHeightPx = heightPxOfOneRowDiv + textAreaPerRowPx * (maxRows - 1);

/* eslint-disable functional/immutable-data */
// Create a hidden div to calculate the height of a node based on its text.
// Reusing this div instead of re-creating for each calculation reduces the average
// calculation time per node from ~0.125ms to ~0.05ms.
// Note: using a `div` improved performance over using a `textarea` from ~0.125ms to ~0.05ms
// (yes a similar additional gain to the gain of reusing the div).
const div = document.createElement("div");
div.style.width = `${textAreaWidthPx}px`;
div.style.fontSize = "16px";
div.style.lineHeight = "1";
div.style.fontFamily = "inherit";
div.style.overflow = "hidden";
div.style.visibility = "hidden";
div.tabIndex = -1;
div.ariaHidden = "true";
document.body.appendChild(div);
/* eslint-enable functional/immutable-data */

/**
* Create a hidden div to calculate the height of a node based on its text.
*/
const calculateNodeHeight = (label: string) => {
// eslint-disable-next-line functional/immutable-data
div.textContent = label;

const additionalHeightPx = Math.min(div.scrollHeight - heightPxOfOneRowDiv, maxTextAreaHeightPx);

return nodeHeightPx + scalePxViaDefaultFontSize(additionalHeightPx);
};

export interface NodePosition {
id: string;
x: number;
Expand Down Expand Up @@ -138,13 +174,16 @@ export const layout = async (
const graph: ElkNode = {
id: "elkgraph",
children: [...nodes]
.sort((node1, node2) => compareNodes(node1, node2))
.toSorted((node1, node2) => compareNodes(node1, node2))
.map((node) => {
return {
id: node.id,
width: nodeWidthPx,
height: nodeHeightPx,
height: calculateNodeHeight(node.data.label),
layoutOptions: {
// Some nodes can be taller than others (based on how long their text is),
// so align them all along the center.
"elk.alignment": "CENTER",
// Allow nodes to be partitioned into layers by type.
// This can get awkward if there are multiple problems with their own sets of criteria,
// solutions, components, effects; we might be able to improve that situation by modeling
Expand Down

0 comments on commit 2bae58e

Please sign in to comment.