pierpo / react-archer

🏹 Draw arrows between React elements 🖋
https://pierpo.github.io/react-archer/
MIT License
1.15k stars 67 forks source link

Arrows not positioning correctly with flexbox div(s) that have margin #205

Closed sachintha180 closed 3 months ago

sachintha180 commented 3 months ago

Hi, I'm creating a simple neural network visualization using react-archer. My experience with react-archer for this project has been amazing, but I've a bit stumped with the following:

I've created a component called Model.tsx:

import { ArcherContainer, ArcherElement } from "react-archer";

import FeatureVector from "./FeatureVector";
import { ModelProps } from "../@types/Model";

const LEFT_MARGIN = -30;
const TOP_MARGIN_STEP = 35;

const Model = ({ modelData }: ModelProps) => {
  return (
    <ArcherContainer>
      <section id="network">
        <div id="input-layer" className="layer">
          {modelData.inputs.map((vector, i) => (
            <ArcherElement
              key={`arrow-${i}`}
              id={`arrow-${i}`}
              relations={[
                {
                  targetId: "summation",
                  targetAnchor: "left",
                  sourceAnchor: "right",
                  order: i,
                },
              ]}
            >
              <div
                className="column"
                style={{
                  marginLeft: `${i === 0 ? 0 : LEFT_MARGIN}px`,
                  marginTop: `${TOP_MARGIN_STEP * i}px`,
                  zIndex: `${i + 1}`,
                }}
              >
                <FeatureVector key={`vector-${i}`} i={i} vector={vector} />
              </div>
            </ArcherElement>
          ))}
        </div>
        <div id="hidden-layer" className="layer">
          <ArcherElement id="summation">
            <div className="node">
              <p>+</p>
            </div>
          </ArcherElement>
        </div>
      </section>
    </ArcherContainer>
  );
};

export default Model;

The associated FeatureVector.tsx component is as follows:

import { FeatureVectorProps } from "../@types/FeatureVector";

const VECTOR_THRESHOLD = 3;

const MIN_NODE_WIDTH = 40;

const FeatureVector = ({ i, vector }: FeatureVectorProps) => {
  return (
    <>
      <div className="node blank id">
        <p>
          m<sub>{i + 1}</sub>
        </p>
      </div>
      {Array.from(Array(VECTOR_THRESHOLD).keys()).map((index) => (
        <div
          key={index}
          className="node"
          style={{
            minWidth: `${MIN_NODE_WIDTH}px`,
          }}
        >
          <p>
            {Number.isInteger(vector[index])
              ? vector[index]
              : vector[index].toFixed(2)}
          </p>
        </div>
      ))}
      {vector.length > VECTOR_THRESHOLD && (
        <>
          <div className="node blank">
            <p>&bull;</p>
            <p>&bull;</p>
            <p>&bull;</p>
          </div>
          <div key={vector.length - 1} className="node">
            <p>
              {Number.isInteger(vector[vector.length - 1])
                ? vector[vector.length - 1]
                : vector[vector.length - 1].toFixed(2)}
            </p>
          </div>
        </>
      )}
    </>
  );
};

export default FeatureVector;

The render looks like this:

Render of Model.tsx component

The issue is that the arrows have all positioned themselves to the middle of the entire flex column, and not just the div with class column.

My expectation is that the arrow starts at the middle of the div, as shown on the diagram below:

Expected render vs. Actual render

Maybe the solution has nothing to do with react-archer itself, but I couldn't really seem to figure it out hence this post.

I have also added an explicit marginLeft and marginTop property to the div.column elements to position them in the cascading fashion seen above - maybe this has something to do with this issue.

Thanks for looking into this!

sachintha180 commented 3 months ago

So I managed to resolve the issue by getting rid of the margins entirely. and using relative positioning for my div.column(s). I then added left and top properties to the columns' CSS to position them in cascading fashion:

Updated columns via relative positioning

I also made sure to align-items: flex-start to the #input-layer container (which holds all the columns) - which together with the above alterations fixed the issue.

pierpo commented 3 months ago

Hey! Thank you for the very detailed issue and solving it yourself ❤️ Do you think there needs to be job done on the lib's side to improve this?

sachintha180 commented 3 months ago

Hi, thanks for taking your time to go through the issue :love_you_gesture:.

And no, I don't think there's any changes required from the library to improve this behaviour.

Using relative positioning + left + top properties for the children and flex for the parent (which I believe is a much better approach to positioning the columns compared to the margins method I used earlier) when you have multiple cascading children works perfectly with react-arrows.

I do have one question though - I've added labels to my arrows via the relations.label property you've provided for all <ArcherElement> components; as follows:

Arrows w/ Labels between Columns

Is there any way these labels could be moved along the arrows? i.e. not always be positioned in the center.

Otherwise, is there any way we could grab the arrow's svg coordinates and position the labels ourselves?