SVG: Bezier Curves that start at a different point than the origin point used to compute the path

431 views Asked by At

I'm trying to draw a path from one foreignObject to another.

I'd like the path to be oriented / set according to the centre of each object, but only start some distance away from the object. For example, here are straight-lined paths from one object to two different objects: notice that the starting point for the paths is not the same; rather, it has been adjusted to lie on the line connecting the two objects.

enter image description here

If the path is a straight line, this is easy enough to achieve: simply start and end the path at a displacement of Δr along the straight line defined by the centre points of the objects.

However, I'm not sure how one would achieve this, in the case of a Bezier curve (quadratic or cubic).

enter image description here

If it were possible to make part of the path transparent (i.e. set the stroke colour for different parts of the path), then one could just use the centre points and set the the first Δs px to transparent; however, I'm not aware of any way of doing this.

Alternatively, if it were possible to set the start and end points of a path independently of the points used to compute the path, this would address all cases (linear, Bézier quadratic or cubic).

Another option might be to use the dash-array property, but this would require knowing the length of the path. e.g. if the path length is S, then setting the dash-array to x-calc(S-2x)-x would also achieve the desired result.

Is there any way of achieving this programmatically?

I don't mind legwork, so even just pointers in the right direction would be appreciated.

2

There are 2 answers

4
Dominik Mokriš On

Here's an idea: use de Casteljau algorithm twice to trim off the beginning and the end portions of your curve.

Say you were asked to evaluate a cubic Bézier curve defined by the control points C_{0,0}, C_{1,0}, C_{2,0} and C_{3,0} at a particular parameter t between 0 and 1. (I assume that the parameter interval of the curve is [0,1] and I give the control points such strange names in the anticipation of the following. Have a look at the Wikipedia article if you work with a curve degree different from 3.)

You would proceed as follows:

for j = 1, ..., 3
  for i = 0, ..., 3 - j
    C_{i, j} = (1 - t) * C_{i, j-1} + t * C_{i+1, j-1}

The point C_{0, 3} is the value of your curve at the parameter value t. The whole thing is much easier to understand with a picture (I took t=0.5):

de Casteljau algorithm for a cubic curve

However, the algorithm gives you more information. For instance, the control points C_{0,0}, C_{0,1}, C_{0,2} and C_{0,3} are the control polygon a curve which is equal to your curve restricted to the interval [0, t]; similarly, C_{0,3}, C_{1,2}, C_{2,1} and C_{3,0} give you a Bézier curve equal to your curve restricted to [t, 1]. This means that you can use de Casteljau algorithm to divide your curve in two at a prescribed interval.

In your case, you would:

  1. Start with the curve you show in the bottom picture.
  2. Use de Casteljau algorithm to split your curve at a parameter t_0 close to 0 (I would start with t_0 = 0.1 and see what happens).
  3. Throw away the part defined on [0, t_0] and keep only that defined on [t_0, 1].
  4. Take a parameter t_1 close to 1 and split the remaining part from 3.
  5. Keep the beginning and throw away the (short) end.

Note that this way you will be splitting you curve according to parameter values and not based on its length. If your curves are similar in shape, this is not a problem but if they would differ significantly, you might have to invest some effort at finding suitable values of t_0 and t_1 programmatically.

Another issue is the choice of t_1. I suppose that due to symmetry, you would want to split your curve into [0, t_0], [t_0, 1 - t_0], [1 - t_0, 1]. Taking t_2 = 1 - t_1 would not do, because t_2 refers to the parameter interval of the result of step 3 and that is again [0, 1]! Instead, you would need something like t_2 = (1 - t_1)^2.

0
Rax Adaam On

A basic solution using layers:

  • Layer 1: paths;
  • Layer 2: elements coloured the same as the background which are slightly larger than the elements layered on top of them (for the emoji, I used circles, for text, I used the same text with a larger pt-size);
  • Layer 3: the elements themselves.

Pros & Cons

Pros:

  • quick;
  • easy;

Cons:

  • can't use markers (because they are masked by the background-coloured objects);
  • may require some tinkering to find the most appropriate type of background element;

Here is a screenshot of a sample output:

enter image description here