[comp.sys.mac.hypercard] arrowheads at the end of lines.

jk4i+@andrew.cmu.edu (John McCall Kingsley, III) (12/04/89)

Hi,

I am drawing a directed graph to the screen, and would like to have
arrowheads on the ends of my links. Does anyone know a way of doing this
so that as the angle of the line changes, the arrow head changes with it.

I have played around with several different ideas, but have not been succesful
in making any of them work properly.

Thanks for your help.

Jack Kingsley

ba0k+@andrew.cmu.edu (Brian Patrick Arnold) (12/07/89)

Hello there,

I've spent some time developing influence diagrams to represent variable
dependencies of math eqns in HyperCard.  The key to drawing arrow heads
at any angle is to use the regular polygon tool with polySides set to 3.

Redrawing links presents a bit of a speed problem.  We have a solution
for redrawing links when nodes are moved, but we are not satisfied with
the performance and plan to write a MacApp-based compiled version.

Here is an example of how we draw a directed link between two buttons.

You need to have the following paint globals set:

  set pattern to 12 --  color arrow head black
  set filled to true -- for a solid arrow head
  set polysides to 3 -- arrow heads have 3 sides

You should set these globals in a handler that saves the old values so
that a complementary handler can restore the old values when through -
you also need to "choose browse tool" when through. You should write
SetDrawMode and RestoreDrawMode handlers as your needs require.

--------------------------------------------
ON DoDrawLink fromLinkName, toLinkName
  -- example for two buttons named fromLinkName and toLinkName

  SetDrawMode
  DrawALink the loc of cd btn fromLinkName, <option-return>
    the loc of cd btn toLinkName, <option-return>
    the width of cd btn toLinkName && the height of cd btn toLinkName
  RestoreDrawMode

END DoDrawLink

ON DrawALink fromLoc,toLoc,toSize
  --
  -- Draw a line from fromLoc to an arrow head flush with the rectangle
  -- toSize located at toLoc.  Side effect: leaves you with line tool.
  --
  -- fromLoc   starting point
  -- toLoc     end point
  -- toSize    "width && height" of a rectangle located at toLoc

  set cursor to busy -- feedback when redrawing

  -- compute angle of the line to be drawn
  put (item 1 of toLoc - item 1 of fromLoc) into delx
  put (item 2 of toLoc - item 2 of fromLoc) into dely
  put atan(dely/delx) into myAngle

  -- fudge a little ??? am I computing something incorrectly?
  IF delx < 0
  THEN add pi to myAngle

  -- scale endpoint (toLoc) to account for toSize rectangle overlap
  IF abs(dely/delx) < ((word 2 of toSize / word 1 of toSize)) THEN
    put (1-(abs(delx)-(word 1 of toSize)/2)/abs(delx)) into factor
  ELSE put (1-(abs(dely)-(word 2 of toSize)/2)/abs(dely)) into factor
  subtract round(delx*factor) from item 1 of toLoc
  subtract round(dely*factor) from item 2 of toLoc

  -- generate inset point for arrow head, "5" is arbitrary
  put toLoc into arrowLoc
  add round(5*cos(myAngle+pi)) to item 1 of arrowLoc
  add round(5*sin(myAngle+pi)) to item 2 of arrowLoc
  
  -- do the drawing
  choose regular polygon tool
  drag from arrowLoc to toLoc  -- should draw a triangle (arrowhead)
  choose line tool
  drag from fromLoc to arrowLoc -- should draw a line
END DrawALink
------------------------------------------------

We have tried to replace the trig (above) with more straightforward
calculations to speed up this algorithm, but this actually slowed things
down a bit.  The first "choose regular polygon tool" call takes a whole
1/2 second, but subsequent choose paint tool calls take almost no time.

When undrawing or erasing links, we use the same algorithm with pattern
set to 1 (white).  Then we use the select tool and select the arrow
space (from fromLoc to toLoc), and DoMenu "Transparent" to turn white
pixels into transparent pixels.  This may have unfavorable side effects.

If this is of any help, please let me know.  I also want to hear more
about anyone's work on directed graphs in HyperCard.

Brian Arnold
Research Programmer
Department of Engineering and Public Policy
Carnegie Mellon University

george@cs.qmc.ac.uk (George Coulouris) (12/07/89)

Funny you should ask that!  I happen to have the answer.

Actually, I did a MacApp example a few months ago that drew
directed graphs, so I dug out the Pascal code, changed the :='s
to 'put' and it ran (well almost, of course the Quickdraw
procedures "MoveTo" and "Line" had to be defined in HyperTalk).

Put the following script into a button to demonstrate it.

After pressing the button, click anywhere on the card and an
arrow will be drawn from 100,100 to the point you clicked at.

on mouseUp
  set the hilite of me to true
  wait until the mouse is down
  arrow 100,100,the MouseH, the MouseV, 10, 1
  set the hilite of me to false
end mouseUp

on arrow x1, y1, x2, y2, k, d
  -- k is the length of the arms of the arrow
  -- d is the fractional distance along the line at which arrow is placed
  put the tool into saveTool
  choose line tool
  MoveTo x1,y1
  Line x2-x1,y2-y1
  if x1 < x2 then put -1 into sign
  else put 1 into sign
  put atan((y1-y2)/(x1-x2)) into alpha -- the angle of the line
  put alpha+3.14/4 into beta -- plus 45 degrees
  put alpha-3.14/4 into gamma -- minus 45 degrees
  MoveTo trunc(x1+(x2-x1)*d), trunc(y1+(y2-y1)*d)
  line sign*trunc(k*cos(beta)), sign*trunc(k*sin(beta))
  MoveTo trunc(x1+(x2-x1)*d), trunc(y1+(y2-y1)*d)
  line sign*trunc(k*cos(gamma)), sign*trunc(k*sin(gamma))
  choose saveTool
end  arrow

on MoveTo x, y
  global curpos
  put x&","&y into curpos
end MoveTo

on line x, y
  global curpos
  drag from curpos to (item 1 of curpos + x),(item 2 of curpos + y)
end line

-- 


                                            | Computer Science Dept
ARPA/Internet:  george@cs.qmc.ac.uk         | Queen Mary and Westfield College
JANET:          george@uk.ac.qmc.cs         | Mile End Road
                                            | London E1 4NS England
Office phone: +44 1 975 5201 (direct line)
Home phone:   +44 1 485 5896

pepke@loligo (Eric Pepke) (12/08/89)

Why is it that everybody wants to use angles and trigonometric functions
to solve this problem and then acts surprised when it doesn't run very fast?

Here is an easy solution based on elementary vector arithmetic that uses 
one square root, two divides (which could easily be converted into one divide 
and two multiplies), a few multiplies, and NO trigonometric functions.  It's 
written in FORTRAN, and you have to convert it, but that's trivial.  FRSTPT 
is roughly equivalent to MoveTo, VECTOR is roughly equivalent to LineTo, and 
SQRT is the FORTRAN square root function.

      SUBROUTINE ARROW(X1, Y1, X2, Y2, WIDTH, LENGTH)
      REAL X1, Y1, X2, Y2, WIDTH, LENGTH
C
C     Eric Pepke, May 9, 1988
C
C     Draw an arrow from (X1, Y1) to (X2, Y2)
C     LENGTH is length of arrow head.  WIDTH is width of arrow head.
C     Both LENGTH and WIDTH are absolute.  This can easily be modified.
C     The arrow head is open.  This, too, can be easily modified.
C
C     X and Y components for normal vectors longitudinal and transverse
C     to the arrow, plus length of arrow
      REAL XL, YL, XT, YT, L
C     X and Y offsets for arrowhead based on longitudinal and
C     transverse contributions
      REAL DXL, DXT, DYL, DYT
C
C     Draw shaft of arrow
      CALL FRSTPT(X1, Y1)
      CALL VECTOR(X2, Y2)
C     Calculate longitudinal and transverse vectors
      XL = X2 - X1
      YL = Y2 - Y1
      L = SQRT(XL * XL + YL * YL)
      IF (L .GT. 0.0) THEN
C     Draw arrow head
C     First make longitudinal and transverse vectors
        XL = X2 - X1
        YL = Y2 - Y1
C     Omit the next two lines to make relative size arrow head
        XL = XL / L
        YL = YL / L
C
        XT = -YL
        YT = XL
C     Calculate the X and Y offsets
        DXL = LENGTH * XL
        DXT = WIDTH * XT
        DYL = LENGTH * YL
        DYT = WIDTH * YT
C     Draw one side of the arrow
        CALL VECTOR(X2 - DXL + DXT, Y2 - DYL + DYT)
C     Draw the other side
C     Change the next line to a VECTOR for a closed arrowhead
        CALL FRSTPT(X2 - DXL - DXT, Y2 - DYL - DYT)
        CALL VECTOR(X2, Y2)
      ENDIF
      END

Eric Pepke                                     INTERNET: pepke@gw.scri.fsu.edu
Supercomputer Computations Research Institute  MFENET:   pepke@fsu
Florida State University                       SPAN:     scri::pepke
Tallahassee, FL 32306-4052                     BITNET:   pepke@fsu

Disclaimer: My employers seldom even LISTEN to my opinions.
Meta-disclaimer: Any society that needs disclaimers has too many lawyers.

ba0k+@andrew.cmu.edu (Brian Patrick Arnold) (12/09/89)

Hello there,

   no offense to Mr. Pepke, but if you re-read my last post:

>We have tried to replace the trig [see my last post] with more
>straightforward calculations to speed up this algorithm, but this
>actually slowed things down a bit. 

Avoiding trig:

----------------------------------
ON DrawALink fromLoc,toLoc,toSize
  --| Draw an arrow between Locs, with arrow head adjusted to touch
  --| an edge of the rectangle of toSize at toLoc.
  --| Assumes SetDrawMode has been called to set line width
  --| and polysides=3.
  --| Modified by Max to speed up and avoid trig calcs. June 18/89
  put (item 1 of toLoc - item 1 of fromLoc) into delx
  put (item 2 of toLoc - item 2 of fromLoc) into dely
  
  -- Scale endpoint (toLoc) to bring arrow to edge of button.
  -- w,h is the signed vector from the center of the button
  -- to the corner of the button nearest to fromLoc.
  put Round(((word 1 of toSize)/2)*delX/Abs(delX)) into w
  put Round(((word 2 of toSize)/2)*delY/Abs(delY)) into h
  
  IF abs(dely/delx) < abs(h/w)
  THEN    -- Arrow to left or right of button rectangle
    subtract w from item 1 of toLoc
    subtract Round(Abs(w)*dely/Abs(delx)) from item 2 of toLoc
  ELSE    -- Arrow to top or bottom of button rectangle
    subtract h from item 2 of toLoc
    subtract Round(Abs(h)*delx/Abs(dely)) from item 1 of toLoc
  END IF
  
  -- generate center point for arrow head (an equilateral triangle)
  put 5 into headR   -- Radius of arrow head
  put toLoc into arrowLoc
  put Sqrt(delX*delX + delY*delY) into hypotenuse
  subtract Round(headR*delX/hypotenuse) from item 1 of arrowLoc
  subtract Round(headR*delY/hypotenuse) from item 2 of arrowLoc
  
  -- do the drawing
  choose regular polygon tool
  drag from arrowLoc to toLoc  -- draw a triangle
  choose line tool
  drag from fromLoc to arrowLoc
END DrawALink
----------------------------------

What's different about this code compared to the Fortran example is that
we scale the endpoint to be flush with a rectangle at the destination. 
Also, using the polygon tool saves you from drawing a couple of extra
lines.

We found this version to be a bit slower than our trig version.  I
attribute this to interpreted HyperTalk execution overhead.  If you
remain unconvinced, you can do your own timing tests.  I recommend the
trig version for arrow drawing as presented in my last post, or writing
an XCMD to do the math.

- Brian

pepke@loligo (Eric Pepke) (12/09/89)

In article <sZTyxfu00Uh_M1fXtU@andrew.cmu.edu> ba0k+@andrew.cmu.edu (Brian Patrick Arnold) writes:
>
>Hello there,
>
>   no offense to Mr. Pepke, but if you re-read my last post:
>
>>We have tried to replace the trig [see my last post] with more
>>straightforward calculations to speed up this algorithm, but this
>>actually slowed things down a bit. 

None taken!  I apologize for not reading your posting all the way through the
first time.

> [example code]
>we scale the endpoint to be flush with a rectangle at the destination. 
>Also, using the polygon tool saves you from drawing a couple of extra
>lines.
>
>We found this version to be a bit slower than our trig version.  I
>attribute this to interpreted HyperTalk execution overhead.  If you
>remain unconvinced, you can do your own timing tests.  I recommend the
>trig version for arrow drawing as presented in my last post, or writing
>an XCMD to do the math.

Well, I just converted my basic non-trig algorithm to HyperTalk and found 
it to be slightly faster than your trig algorithm (about 7%).  To keep from 
comparing apples and oranges, I used your clipping code verbatim and also your 
polygon trick.  (I usually use lines, because that's what most of our 
researchers seem to prefer.)  I did the test on a Mac II with an Apple
color board set to two colors, System 6.0.4, Multifinder with nothing else
running except After Dark in the background.

Here is the converted algorithm:

ON DrawALink fromLoc,toLoc,toSize
  --
  -- Draw a line from fromLoc to an arrow head flush with the rectangle
  -- toSize located at toLoc.  Side effect: leaves you with polygon tool.
  --
  -- fromLoc   starting point
  -- toLoc     end point
  -- toSize    "width && height" of a rectangle located at toLoc
  
  set cursor to busy -- feedback when redrawing
  
  -- Calculate longitudinal unit vector
  put item 1 of toLoc - item 1 of fromLoc into XL
  put item 2 of toLoc  - item 2 of fromLoc into YL
  
  -- scale endpoint (toLoc) to account for toSize rectangle overlap
  IF abs(YL/XL) < ((word 2 of toSize / word 1 of toSize)) THEN
    put (1-(abs(XL)-(word 1 of toSize)/2)/abs(XL)) into factor
  ELSE put (1-(abs(YL)-(word 2 of toSize)/2)/abs(YL)) into factor
  subtract round(XL*factor) from item 1 of toLoc
  subtract round(YL*factor) from item 2 of toLoc
  
  --Normalize longitudinal vector
  put SQRT(XL * XL + YL * YL) into L
  divide XL by L
  divide YL by L
  
  --Draw arrow
  choose line tool
  drag from fromloc to toLoc
  choose regular polygon tool
  drag from round(item 1 of toLoc - 5 * XL), <option-return> 
  round (item 2 of toLoc - 5 * YL) to toLoc
END DrawALink
  
Eric Pepke                                     INTERNET: pepke@gw.scri.fsu.edu
Supercomputer Computations Research Institute  MFENET:   pepke@fsu
Florida State University                       SPAN:     scri::pepke
Tallahassee, FL 32306-4052                     BITNET:   pepke@fsu

Disclaimer: My employers seldom even LISTEN to my opinions.
Meta-disclaimer: Any society that needs disclaimers has too many lawyers.

a_dent@vaxa.uwa.oz (12/14/89)

66666666666In article <QZSVM7u00WB60VEUVB@andrew.cmu.edu>, jk4i+@andrew.cmu.edu (John McCall Kingsley, III) writes:
> Hi,
> 
> I am drawing a directed graph to the screen, and would like to have
> arrowheads on the ends of my links. Does anyone know a way of doing this
> so that as the angle of the line changes, the arrow head changes with it.
> 
> I have played around with several different ideas, but have not been succesful
> in making any of them work properly.
> 
> Thanks for your help.
> 

The guys who wrote MacSurf have a library of such items in Pascal
(I think).  I can chase them up if you like.  The problem is NOT
trivial!  But I do know that they sell their libraries so you may
be able to get something.

Andy Dent,  Southern Hemisphere Mac Consultant and Programmer