Coder Social home page Coder Social logo

perfect-arrows's Introduction

Perfect Arrows

A set of functions for drawing perfect arrows between points and shapes.

Example

👉 Demo

Edit example

Other languages

Português (pt-BR)

Installation

npm i perfect-arrows

or

yarn add perfect-arrows

Usage

The functions in this library provide only the information needed to draw an arrow. You'll need to draw the arrow yourself using your technology of choice. See below for an example React component with SVG.

getArrow(x0, y0, x1, y1, options)

The getArrow function accepts the position of two points and returns an array containing information for:

  • three points: a start, end, and control point
  • three angles: an end, start, and center

You can use this information to draw an arc and arrow-heads. You can use the options object to tweak the return values.

const arrow = getArrow(0, 0, 100, 200, {
  bow: 0,
  stretch: 0.5,
  stretchMin: 0,
  stretchMax: 420,
  padStart: 0,
  padEnd: 0,
  flip: false,
  straights: true,
})

const [sx, sy, cx, cy, ex, ey, ae, as, sc] = arrow

Arguments

Argument Type Description
x0 number The x position of the starting point.
y0 number The y position of the starting point.
x1 number The x position of the ending point.
y1 number The y position of the ending point.
options object An (optional) object containing one or more of the options described below.

Options

Option Type Default Description
bow number 0 A value representing the natural bow of the arrow. At 0, all lines will be straight.
stretch number .5 The effect that the arrow's length will have, relative to its minStretch and maxStretch, on the bow of the arrow. At 0, the stretch will have no effect.
minStretch number 0 The length of the arrow where the line should be most stretched. Shorter distances than this will have no additional effect on the bow of the arrow.
maxStretch number 420 The length of the arrow at which the stretch should have no effect.
padStart number 0 How far the arrow's starting point should be from the provided start point.
padEnd number 0 How far the arrow's ending point should be from the provided end point.
flip boolean false Whether to reflect the arrow's bow angle.
straights boolean true Whether to use straight lines at 45 degree angles.

Returns

Argument Type Description
x0 number The x position of the (padded) starting point.
y0 number The y position of the (padded) starting point.
x1 number The x position of the (padded) ending point.
y1 number The y position of the (padded) ending point.
ae number The angle (in radians) for an ending arrowhead.
as number The angle (in radians) for a starting arrowhead.
ac number The angle (in radians) for a center arrowhead.

Example: A React Arrow Component

import * as React from "react"
import { getArrow } from "perfect-arrows"

export function PerfectArrow() {
  const p1 = { x: 64, y: 64 }
  const p2 = { x: 128, y: 96 }

  const arrow = getArrow(p1.x, p1.y, p2.x, p2.y, {
    padEnd: 20,
  })

  const [sx, sy, cx, cy, ex, ey, ae, as, ec] = arrow

  const endAngleAsDegrees = ae * (180 / Math.PI)

  return (
    <svg
      viewBox="0 0 720 480"
      style={{ width: 720, height: 480 }}
      stroke="#000"
      fill="#000"
      strokeWidth={3}
    >
      <circle cx={sx} cy={sy} r={4} />
      <path d={`M${sx},${sy} Q${cx},${cy} ${ex},${ey}`} fill="none" />
      <polygon
        points="0,-6 12,0, 0,6"
        transform={`translate(${ex},${ey}) rotate(${endAngleAsDegrees})`}
      />
    </svg>
  )
}

getBoxToBoxArrow(x0, y0, w0, h0, x1, y1, w1, h1, options)

The getBoxToBoxArrow function accepts the position and dimensions of two boxes (or rectangles) and returns an array containing information for:

  • three points: a start, end, and control point
  • three angles: an end, start, and center

You can use this information to draw an arc and arrow-heads. You can use the options object to tweak the return values.

Note: The options and values returned by getBoxToBoxArrow are in the same format as the options and values for getArrow.

const arrow = getBoxToBoxArrow(0, 0, 96, 128, 400, 200, 128, 96, {
  bow: 0,
  stretch: 0.5,
  stretchMin: 0,
  stretchMax: 420,
  padStart: 0,
  padEnd: 0,
  flip: false,
  straights: true,
})

const [sx, sy, cx, cy, ex, ey, ae, as, sc] = arrow

Arguments

Argument Type Description
x0 number The x position of the first rectangle.
y0 number The y position of the first rectangle.
w0 number The width of the first rectangle.
h0 number The height of the first rectangle.
x1 number The x position of the second rectangle.
y1 number The y position of the second rectangle.
w1 number The width of the second rectangle.
h1 number The height of the second rectangle.
options object An (optional) object containing one or more of the options described below.

Options

See options in getArrow above. (Both functions use the same options object.)

Returns

See returns in getArrow above. (Both functions return the same set of values.)

Example: A React Box-to-box Arrow Component

import * as React from "react"
import { getBoxToBoxArrow } from "perfect-arrows"

export function PerfectArrow() {
  const p1 = { x: 64, y: 64, w: 64, h: 64 }
  const p2 = { x: 128, y: 96, w: 64, h: 64 }

  const arrow = getBoxToBoxArrow(
    p1.x,
    p1.y,
    p1.w,
    p1.h,
    p2.x,
    p2.y,
    p2.w,
    p2.h,
    {
      bow: 0.2,
      stretch: 0.5,
      stretchMin: 40,
      stretchMax: 420,
      padStart: 0,
      padEnd: 20,
      flip: false,
      straights: true,
    }
  )

  const [sx, sy, cx, cy, ex, ey, ae, as, ec] = arrow

  const endAngleAsDegrees = ae * (180 / Math.PI)

  return (
    <svg
      viewBox="0 0 1280 720"
      style={{ width: 1280, height: 720 }}
      stroke="#000"
      fill="#000"
      strokeWidth={3}
    >
      <circle cx={sx} cy={sy} r={4} />
      <path d={`M${sx},${sy} Q${cx},${cy} ${ex},${ey}`} fill="none" />
      <polygon
        points="0,-6 12,0, 0,6"
        transform={`translate(${ex},${ey}) rotate(${endAngleAsDegrees})`}
      />
    </svg>
  )
}

Author

@steveruizok

perfect-arrows's People

Contributors

airhorns avatar dependabot[bot] avatar mohanaravind avatar nosdoska avatar omurilo avatar steveruizok avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

perfect-arrows's Issues

Wrong position of arrow if rectangle is rotated

I want to achieve the same behaviour as tldraw has (arrows have right position even for rotated elements), could you advice how to do it? As I understand perfect-arrows lib doesn't support it

Arrows from a box back to itself don't look so good

Definitely for state machines and potentially for other use cases as well it'd be awesome to have support for arrows that go from a box back to itself. This would represent a transition from one state to the same state in a state machine-y world which is super valid if you ask me. Here's what happens now:

Screen Shot 2020-09-18 at 8 41 09 AM

I would be happy to work on (or get someone to work on) this but I think it'd be cool to agree on what the perfect version of an arrow of this nature would look like! My thoughts:

  • in the event that there are multiple self arrows, it'd be good to hint to the algorithm to put them in different places so they don't overlap. This is possible for box to box arrows with flip and bow right now
  • it'd be good to be able to adjust the style (padding etc) to match whatever you might be doing with the other box to box arrows
  • probably super out of scope for now, but it'd be really great to somehow ask for as few crossing arrows as possible for aesthetics. In the meantime, maybe we'd want self-arrows to bias towards start points on the boxes that the box-to-box arrows bias away from.

My suggestion would be a perfect circle anchored at one of the corners of the box, with the same start and end padding behaviour as the box-to-box function, and maybe with its centre offset out a little along the ray intersecting the anchor corner and the centre of the box.

I'm also not sure if the preferred way to do this is a new public function, or adding a check to the box-to-box function to see if the boxes are the same. I'd suggest a new function to disambiguate between transitions from one box to itself, and from one to another box that is perfectly on top of it.

I added a test in the example here if you'd like to play with the current implementation's output: https://github.com/gadget-inc/perfect-arrows/tree/box-to-self

Arrows to small boxes next to big boxes unnecessarily cross the big box

boxes

Maybe this is by design, but I'd kind of expect the arrow to shrink smaller and smaller between the two boxes until it no longer fit before starting on the opposite side of the box. I am working around this right now by making the big box appear on top of the line with a bigger zIndex, but it still stinks if there is any bowing or if one wanted to use the control midpoint to position a label or something like that.

I took a look and the issue is related to getSegmentRoundedRectangleIntersectionPoints but my geometry fu is not strong enough to immediately see how to fix this. Happy to work on a PR if someone could give me a pointer!

TypeError: Cannot read property '0' of undefined

Hi, I've been working on a whiteboard project and use perfect-arrow to draw connections. However, although 99% of the time the arrows render perfectly, there are sometimes the app crashed and gave me this error message. Through the debugging process I'm pretty sure the pos and rect information are successfully passed into the getBoxToBoxArrow function. Do you have any idea what might cause this problem? Thanks!

getBoxToBoxArrow
src/lib/getBoxToBoxArrow.ts:254

251 |     ph1,
252 |     padEnd
253 |   )
> 254 | } else {
255 | ^ // Calculate an angle based on distance, overlap and intermediacy 256 | const distOffset1 = modulate(distEffect, [0.75, 1], [0, 1], true) 257 |
src/components/WhiteBoard/SpaceView.js:562
559 | const end = whiteBoardData.cardInstances.find(
560 |     cardInstance => cardInstance.cardInstanceId === connection.toCardInstanceId
561 | );
> 562 | const arrowData = getBoxToBoxArrow(
563 | begin.pos.x,
564 | begin.pos.y,
565 | begin.rect.width,

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.