diagrams / diagrams-lib

Diagrams standard library
https://diagrams.github.io/
Other
138 stars 62 forks source link

allow trail sections as arrow shafts #151

Open EdwardMorehouse opened 10 years ago

EdwardMorehouse commented 10 years ago

I've been trying to use Diagrams' named subdiagrams and arrows to draw commuting (categorical) diagrams using connect and friends, and found that the arrows don't behave the way I expected.

For shafts that are not straight lines, when using connectOutside(') or head and tail gaps with connect('), I would like the trail that I specify for the shaft to connect the origins of the source and target diagrams, but only the section (as in Diagrams.Parametric) outside of their boundaries (in the case of connectOutside) or the gap distances away from their origins (in the case of gaps) to be stroked.

The problem with the current behavior is that it is very fiddly to get curved arrows, and especially self-loops, to look right. Is the mode of use described above something that might be supported in the future?

jeffreyrosenbluth commented 10 years ago

Can you provide an example diagram to us help understand what you are trying to do.

EdwardMorehouse commented 10 years ago

Does this help?

-Ed

On 2014-01-31 5:10 PM, Jeffrey Rosenbluth wrote:

Can you provide an example diagram to us help understand what you are trying to do.

— Reply to this email directly or view it on GitHub https://github.com/diagrams/diagrams-lib/issues/151#issuecomment-33847591.

{-# LANGUAGE NoMonomorphismRestriction #-}

import Diagrams.Prelude import Diagrams.Backend.SVG.CmdLine import Diagrams.TwoD.Path.Metafont

main = mainWith (diagram # pad 1.1)

compile with: ghc --make ArrowsExample.lhs ; ./ArrowsExample -o ArrowsExample.svg -w 800

Suppose I choose a shaft that I would like to use for arrows from things to themselves:

shaft = metafont ( origin .- leaving (unitY # rotateBy (1/6)) <> arriving unitX -. p2 (0,2) .- leaving unitX <> arriving (unit_Y # rotateBy (-1/6)) -. endpt origin ) # reverseTrail

It would be convenient to just be able to say something like:

try_1 = circle 0.25 # named "p_1" # connectOutside' (with & arrowShaft .~ shaft) "p_1" "p_1"

And have Diagrams figure out for me which section of the shaft trail is outside of the perimeter of the dot and use that as the arrow shaft.

But this fails with, Maybe.fromJust: Nothing

So instead of connectOutside' I can just use connect' with head and tail gaps:

try_2 = circle 0.25 # named "p_2" # connect' arrowOpts "p_2" "p_2" where arrowOpts = (with & arrowShaft .~ shaft & arrowHead .~ spike & headSize .~ 0.5 & headGap .~ 0.5 & tailGap .~ 0.5 )

But connect' doesn't use the section of the trail beyond the gaps for the shaft, rather the whole trail. Okay, so I can just take the section myself:

shaft' = section shaft (1/10) (9/10)

And now it should work, right?

try_3 = circle 0.25 # named "p_3" # connect' arrowOpts "p_3" "p_3" where arrowOpts = (with & arrowShaft .~ shaft' & arrowHead .~ spike & headSize .~ 0.5 & headGap .~ 0.5 & tailGap .~ 0.5 )

Except now the two endpoints aren't the same so it doesn't seem fair to use it to connect a point to itself. And indeed, the tip isn't really pointed in the right direction anymore. Also, the arrow has become rotated by an angle that's very difficult to figure out short of by trial-and-error.

This is what I meant by "fiddly".

Is there a way to do what I want -- basically atop the circle and the shaft, and draw the arrowhead tip at the intersection (plus gap, if any) -- short of using connectPerim' and calculating the angles involved?

diagram :: Diagram B R2 diagram = strokeTrail shaft ||| strutX 2 ||| try_2 ||| strutX 2 ||| strokeTrail shaft' ||| strutX 2 ||| try_3

jeffreyrosenbluth commented 10 years ago

I ran the examples and I understand the problem. Let me think about it and see it there is some way to get the semantics you are looking for.

jeffreyrosenbluth commented 10 years ago

Have you thought about using the connecPerim' funcition as in the dfa example in the tutorial, http://projects.haskell.org/diagrams/doc/arrow.html

jeffreyrosenbluth commented 10 years ago

Ed, I had a chance to look into the this issue a bit more, and I'm afraid the current implementation of arrows does not cover this case. The length of an arrow is defined to be the linear distance from its tip to its tail and the entire arrow is scaled to a give length. An arrow that starts and ends at the same point has zero length. You could do something like what I do in the DFA example in the tutorial as I mentioned above, but I'm not sure it will suffice for what you are trying to do.

Another possible work around is to use two arrows, one with no head starting at the dot and going to some point off the dot, and one with no tail starting at the point off the dot and ending at the dot.

The functionality you are looking for would be nice to have and I will look into adding it.

EdwardMorehouse commented 10 years ago

Hi Jeffrey,

Thanks for taking the time to look into this.

Yes, I certainly can use connectPerim' in the manner of the DFA example of the tutorial. But to get the result that I want, I must then manually calculate the angles and the parameters for the section. I do not (yet, anyway) know how to get Diagrams to do this for me, or else I would just write a connect'' that does it automatically.

I humbly submit that a natural use case with curved arrows is to have them placed as though they were connecting the origins of the source and target diagrams, but to have only the section outside of their envelopes -- modulo head and tail gaps -- to be stroked; with the tips (if any) then place at the ends of the stroked section.

I believe (but am not completely sure) that this is what TikZ does by default when drawing paths between "nodes" (basically, subdiagrams). The results usually "look right", I think because my mind extrapolates the paths of the arrows into (or behind) the nodes. The effect is especially pleasing when there are multiple curved arrows going into or out of a node.

Anyway, thanks again for looking into this. I was hoping that there was an easy answer, but if not then I will dig into Diagrams a bit more and see if I can figure out a way to do what I want.

With best wishes,

-Ed

On 2014-02-01 11:20 AM, Jeffrey Rosenbluth wrote:

Ed, I had a chance to look into the this issue a bit more, and I'm afraid the current implementation of arrows does not cover this case. The length of an arrow is defined to be the linear distance from its tip to its tail and the entire arrow is scaled to a give length. An arrow that starts and ends at the same point has zero length. You could do something like what I do in the DFA example in the tutorial as I mentioned above, but I'm not sure it will suffice for what you are trying to do.

Another possible work around is to use two arrows, one with no head starting at the dot and going to some point off the dot, and one with no tail starting at the point off the dot and ending at the dot.

The functionality you are looking for would be nice to have and I will look into adding it.

— Reply to this email directly or view it on GitHub https://github.com/diagrams/diagrams-lib/issues/151#issuecomment-33875719.