Eliav2 / react-xarrows

Draw arrows (or lines) between components in React!
https://codesandbox.io/embed/github/Eliav2/react-xarrows/tree/master/examples?fontsize=14&hidenavigation=1&theme=dark
MIT License
584 stars 75 forks source link

offset anchors towards facing direction #92

Open sillydan1 opened 3 years ago

sillydan1 commented 3 years ago

Is your feature request related to a problem? Please describe. When a straight arrow is anchored to a circular/round element, neither middle nor auto is a preferable choice.

Describe the solution you'd like It would be nice if there were a variant of middle where you could provide a radius offset where the arrow would point towards the middle of the target element, but is radius-amount shorter.

Of course, this type of anchor would only really be useful on straight arrows.

sillydan1 commented 3 years ago

Problem Screenshot_2021-08-21_10-06-36

(emulated) Desired Outcome Screenshot_2021-08-21_10-07-34

Eliav2 commented 3 years ago

please refer to custom svg it the repo docs.

type headShapeType<T extends svgElemType> = {
    svgElem: SVGElementTagNameMap[T];
    offsetForward?: number;
};

try:

import { arrowShapes } from 'react-xarrows';
const customSvg = {
  svgElem: arrowShapes.arrow1;
  offsetForward: 1; //change
};

then change customSvg.offsetForward and pass it down to xarrow.
does this solves your issue?

sillydan1 commented 3 years ago

Oh! Didn't catch that part of the docs. My bad. This almost fixes the issue. If I set the offsetForward to some negative value, I get this result. Not excactly the desired effect, but very close. How would I get the arrow head to follow the line?

import Xarrow, {arrowShapes} from "react-xarrows";

const customSvg = {
    svgElem: arrowShapes.arrow1.svgElem,
    offsetForward: -1
};
Eliav2 commented 3 years ago

you could extend the line using offset option in the anchors. foe example const endAnchor= { position: 'auto'; offset: { x: 20,y:0 }; };

currently, you can only offset only with x and y but soon there will be an option to offset relative to the facing direction.

sillydan1 commented 3 years ago

position 'auto' does not work for me, since I need the arrow to point towards the middle.

currently, you can only offset only with x and y but soon there will be an option to offset relative to the facing direction.

That is excactly what I described and have implemented in the PR. Technically I would be able to calculate the x and y offsets using the same approach in my own application, but it would be nice if it was available as a library feature.

Eliav2 commented 3 years ago

understood, you are right and this is planned for the next release. will leave this issue open as a feature request until the next release that solves this.

sillydan1 commented 3 years ago

For anyone interested, this is how I ended up sidestepping this issue (please excuse the messy code) note that this code is not very performant:

import React, {Component} from "react";
import Xarrow from "react-xarrows";
import * as ReactDOM from "react-dom";

class Edge extends Component {
    state = {
        startOffset: {},
        endOffset: {}
    }

    dist(a, b) {
        return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
    }

    calculateOffset(startId, endId) {
        let start_el = document.getElementById(startId);
        let end_el = document.getElementById(endId);
        if(start_el === null || end_el === null)
            return {x:0, y:0};
        let start_box = start_el.getBoundingClientRect();
        let end_box = end_el.getBoundingClientRect();
        let start = {x: start_box.x + start_box.width/2, y: start_box.y + start_box.height/2};
        let end = {x: end_box.x + end_box.width/2, y: end_box.y + end_box.height/2};
        let radius = end_box.width / 2;
        let directionMagnitude = this.dist(start, end);
        if(directionMagnitude === 0)
            return {x:0, y:0};
        let directionNormalized = {
            x: (start.x - end.x) / directionMagnitude,
            y: (start.y - end.y) / directionMagnitude
        };
        return {
            x: (directionNormalized.x * radius),
            y: (directionNormalized.y * radius)
        };
    }

    updateOffsets = () => {
        const startOffset = this.props.startAnchor === 'middle' ? this.calculateOffset(this.props.end, this.props.start) : {x: 0, y: 0};
        const endOffset = this.props.endAnchor === 'middle' ? this.calculateOffset(this.props.start, this.props.end) : {x: 0, y: 0};
        this.setState({
            startOffset: startOffset,
            endOffset: endOffset
        });
    }

    componentDidMount() {
        // TODO: Once react-xarrows supports anchors with offset in facing direction, use that instead.
        ReactDOM.findDOMNode(this).addEventListener("DOMAttrModified", this.updateOffsets.bind(this));
    }

    render() {
        const {start, end, id, startAnchor, endAnchor} = this.props;
        return (
            <Xarrow start={start}
                    end={end}
                    path='straight'
                    startAnchor={{
                        position: startAnchor,
                        offset: this.state.startOffset
                    }}
                    endAnchor={{
                        position: endAnchor,
                        offset: this.state.endOffset
                    }}
            />
        );
    }
}

export default Edge;