Hi. I'd like to create perfect arrowed arcs to illustrate objects relating to one another in a cycle, (bidirectional arrows). What's the easiest way? Thanks.
These forums are now read-only. Please visit our new forums to participate in discussion. A new account will be required to post in the new forums. For more info on the switch, see this post. Thank you!
|
Member
2012-02-09, 08:00 PM
Hi. I'd like to create perfect arrowed arcs to illustrate objects relating to one another in a cycle, (bidirectional arrows). What's the easiest way? Thanks.
Post 1
|
Guest
2012-02-10, 01:44 AM
It may be worth looking at this stencil (and the comments below it, which will give a sense of the difficulty of doing this in the current version of OG).
It's the kind of thing that MS SmartArt (in any of the Office applications) does fairly quickly, but which requires quite a lot of thoughtfulness and resource in OG ...
Post 2
|
Guest
2012-02-10, 02:33 AM
If you do need to use OG, the quickest approximation may be to use automatic radial (or force-directed) layout with a simple outline (parent node + N children), and set the spoke links (parent to child) to a line thickness of 0pt, to hide them.
(If you don't need a hub node, force-directed automatic layout may be enough, and simpler). It may make things easier to switch off automatic layout while you are adding the radial (peer to peer) links ... This approach doesn't, however, give you the curved radial arcs that you're looking for. -- Last edited by RobTrew; 2012-02-10 at 03:46 AM..
Post 3
|
Guest
2012-02-11, 03:08 PM
A very rough first draft of a script which links up a set of selected shapes into a cycle, automatically drawing well-formed arc arrows.
I'll try to add a basic user interface at some point, to allow for interactive adjustment and add some options (arrow width, gaps, block arrow versus line arrow, automatically arrange the shapes evenly around the circle etc etc). Code:
property pTitle : "Arc Cycle" property pVer : "0.08" property pblnBlockArrow : true property pArrowWidth : 30 property pArcAnglePoints : 0.25 property pHeadArrow : true property pTailArrow : false property pblnSpreadEvenly : true property pstrLineArrowHead : "FilledArrow" property plstLineArrowColor : {1.0, 0.0, 0.0} property prLineArrowThickness : 2 property pblnLineArrowShadow : true property pGap : 1 / 400 property pDeg : 180 / pi -- FUNCTION: -- CREATES A CYCLE DIAGRAM, IN WHICH A SET OF SHAPES ARE LINKED IN A CIRCLE -- BY ARC ARROWS -- USAGE: -- SELECT A FEW SHAPES ARRANGED IN A ROUGH CIRCLE -- RUN THE SCRIPT -- ADJUST THE PROPERTIES ABOVE -- NEXT: -- I'LL ADD A BASIC USER INTERFACE (AT SOME POINT) TO ALLOW INTERACTIVE ADJUSTMENTS on run tell application id "OGfl" tell front window -- CHOOSE A SET OF SHAPES (CIRCLES PROBABLY) TO BE JOINED UP BY ARC ARROWS -- NO NEED FOR THEM TO BE NEATLY ARRANGED set lstSeln to my ShapesOnly(selection as list) if length of lstSeln < 2 then display alert "Select shapes which will form circle" return end if set oCanvas to its canvas set automatic layout of layout info of oCanvas to false -- GET THE CENTROID OF THE CLUSTER OF SHAPES set lstCenter to my GetCentroid(lstSeln) -- AND MOVE THEM A LITTLE TO EQUALIZE THEIR RADII FROM A CENTRAL POINT -- (TO DO - OFFER A CHOICE OF DISTRIBUTING THEM EVENLY AROUND THE CIRCLE) set rRadius to my EqualizeRadius(lstCenter, lstSeln) -- GET A LIST OF THE SHAPES SORTED IN THE ORDER OF THEIR ANGLES AROUND THE CENTER set lstShapeRecs to my ReadShapes(lstCenter, lstSeln, oCanvas) if pblnSpreadEvenly then my SpreadEvenly(lstShapeRecs, lstCenter, rRadius) end tell end tell --READ THE FIRST SHAPE AND THE AMOUNT OF ARC ABSORBED BY ITS RADIUS set {rStartAngle, oLastShape} to item -1 of lstShapeRecs set rPreGap to my ShapeGap(oLastShape, rRadius) -- ALLOW FOR SOME ADDITIONAL SPACE BETWEEN ARROWS AND THE LINKED NODES set rExtraGap to (pGap * 360) -- DRAW A CURVED ARC (ARROW) BETWEEN EACH SUCCESSIVE PAIR OF SHAPES repeat with oRec in lstShapeRecs set {rAngle, oShape} to oRec set rPostGap to my ShapeGap(oShape, rRadius) set oNewShape to MakeArc(pblnBlockArrow, lstCenter, rRadius, rStartAngle + rPreGap + rExtraGap, rAngle - rPostGap - rExtraGap, ¬ pArrowWidth, pArcAnglePoints, pHeadArrow, pTailArrow, oCanvas) set {rStartAngle, oLastShape, rPreGap} to {rAngle, oShape, rPostGap} end repeat end run on SpreadEvenly(lstShapeRecs, {oX, oY}, rRadius) set lngShapes to length of lstShapeRecs set rDelta to 360 / lngShapes set rTheta to 0 repeat with recShape in lstShapeRecs set item 1 of recShape to rTheta my RadialPlaceShape(item 2 of recShape, {oX, oY}, rTheta, rRadius) set rTheta to rTheta + rDelta end repeat end SpreadEvenly -- HOW MUCH ARC IS TAKEN UP BY THIS SHAPE (ASSUMES A CIRCLE) on ShapeGap(oShape, rRadius) tell application id "OGfl" tell oShape set {rWidth, rHeight} to size end tell set rShapeRad to (rWidth + rHeight) / 4 end tell return (rShapeRad / (pi * rRadius)) * 180 end ShapeGap -- CENTER, RADIUS, FROM DEGREES, TO DEGREES, WIDTH, -- BEZIER POINTS PER DEGREE OF ARC, HEAD ARROW, TAIL ARROW on MakeArc(pblnBlockArrow, {rX, rY}, rRadius, rFrom, rTo, rWidth, rPointsPerDegree, blnHeadArrow, blnTailArrow, oCanvas) -- (CURRENTLY A SIMPLE FUNCTION OF THE ARROW WIDTH) set {rStart, rEnd} to {rFrom, rTo} if pblnBlockArrow then -- CALCULATE THE LENGTH OF ANY ARROWS if (blnHeadArrow or blnTailArrow) then set rArrow to (rWidth / (pi * rRadius)) * 180 if blnHeadArrow then set rEnd to rTo - rArrow if blnTailArrow then set rStart to rFrom + rArrow end if end if -- AND THE AMOUNT OF ARC WHICH IT TRAVELS set rArc to rEnd - rStart if rArc < 0 then set rArc to (rEnd + 360) - rStart -- HOW MANY POINTS WILL WE USE TO DRAW THE SHAFT ? set lngPoints to (rArc * rPointsPerDegree) as integer set rDelta to rArc / lngPoints if pblnBlockArrow then set oGraphic to BlockArrow(oCanvas, {rX, rY}, rFrom, rTo, rStart, rEnd, rDelta, lngPoints, rWidth, blnHeadArrow, blnTailArrow, rRadius) else set oGraphic to ArcArrow(oCanvas, {rX, rY}, rFrom, rTo, rDelta, lngPoints, blnHeadArrow, blnTailArrow, rRadius) end if return oGraphic end MakeArc on ArcArrow(oCanvas, {rX, rY}, rFrom, rTo, rDelta, lngPoints, blnHeadArrow, blnTailArrow, rRadius) set rTheta to rFrom set lstPoints to {} repeat with i from 1 to lngPoints set end of lstPoints to {rX + (rRadius * (sin(rTheta))), rY - rRadius * (cos(rTheta))} set rTheta to rTheta + rDelta end repeat set end of lstPoints to {rX + (rRadius * (sin(rTheta))), rY - rRadius * (cos(rTheta))} -- property pstrLineArrowHead : "FilledArrow" -- property plstLineArrowColor : {1.0, 0.0, 0.0} -- property plngLineArrowThickness : 2 -- property pblnLineArrowShadow : true tell application id "OGfl" tell oCanvas set recStyle to {draws stroke:true, thickness:prLineArrowThickness, draws shadow:pblnLineArrowShadow, stroke color:plstLineArrowColor} if blnHeadArrow then set recStyle to recStyle & {head type:pstrLineArrowHead} if blnTailArrow then set recStyle to recStyle & {tail type:pstrLineArrowHead} set oLine to (make new line with properties {point list:lstPoints, line type:curved} & recStyle) end tell end tell return oLine end ArcArrow on BlockArrow(oCanvas, {rX, rY}, rFrom, rTo, rStart, rEnd, rDelta, lngPoints, rWidth, blnHeadArrow, blnTailArrow, rRadius) -- GET THE INNER AND OUTER RADII OF THE BLOCK ARROW set rW to rWidth / 2 set {rRadOut, rRadin} to {rRadius + rW, rRadius - rW} -- COLLECT THE POINTS FOR THE OUTER AND INNER FLANKS OF THE ARROW set {lstOuter, lstInner} to {{}, {}} set rTheta to rStart repeat with i from 1 to lngPoints set {dX, dY} to {sin(rTheta), cos(rTheta)} set end of lstOuter to {rX + (rRadOut * dX), rY - rRadOut * dY} set end of lstInner to {rX + (rRadin * dX), rY - rRadin * dY} set rTheta to rTheta + rDelta end repeat set {dX, dY} to {sin(rTheta), cos(rTheta)} set end of lstOuter to {rX + (rRadOut * dX), rY - rRadOut * dY} set end of lstInner to {rX + (rRadin * dX), rY - rRadin * dY} -- MAKE HEAD AND/OR TAIL ARROWS IF REQUIRED set {lstHead, lstTail} to {{}, {}} set {rInnerBarb, rOuterBarb} to {rRadius - rWidth, rRadius + rWidth} -- TAIL ARROW ? if blnTailArrow then -- inner barb, tip, outer barb set lstTail to {{rX + (rInnerBarb * (sin(rStart))), rY - rInnerBarb * (cos(rStart))}, ¬ {rX + (rRadius * (sin(rFrom))), rY - rRadius * (cos(rFrom))}, ¬ {rX + (rOuterBarb * (sin(rStart))), rY - rOuterBarb * (cos(rStart))}} end if -- HEAD ARROW ? if blnHeadArrow then set lstHead to {{rX + (rOuterBarb * (sin(rEnd))), rY - rOuterBarb * (cos(rEnd))}, ¬ {rX + (rRadius * (sin(rTo))), rY - rRadius * (cos(rTo))}, ¬ {rX + (rInnerBarb * (sin(rEnd))), rY - rInnerBarb * (cos(rEnd))}} end if -- JOIN THE SHAFT TO ANY ARROW TIPS set lstPoints to (lstOuter & lstHead & reverse of lstInner & lstTail) -- GATHER THE POINTS IN THE TRIPLET FORMAT EXPECTED FOR CUSTOM SHAPES set oFirstPoint to item 1 of lstPoints set lstBezier to {oFirstPoint, oFirstPoint} repeat with i from 2 to length of lstPoints set oPoint to item i of lstPoints set lstBezier to lstBezier & {oPoint, oPoint, oPoint} end repeat set end of lstBezier to oFirstPoint -- AND RETURN A CUSTOM SHAPE tell application id "OGfl" tell oCanvas to set oShape to make new shape with properties {point list:lstBezier} end tell return oShape end BlockArrow -- PRUNE OUT ANY SELECTED LINES on ShapesOnly(lstSeln) set lstClean to {} tell application id "OGfl" repeat with oGraphic in lstSeln if class of oGraphic is shape then set end of lstClean to oGraphic end repeat end tell lstClean end ShapesOnly -- FIND THE CENTER OF A SHAPE on GetCenter(oShape) tell application id "OGfl" tell oShape to set {{oX, oY}, {rWidth, rHeight}} to {origin, size} {oX + rWidth / 2, oY + rHeight / 2} end tell end GetCenter -- READ THE SHAPES INTO A LIST SORTED BY THEIR ANGLE AROUND THE CENTRE on ReadShapes({rX, rY}, lstShapes, oCanvas) set lngShapes to length of lstShapes if lngShapes < 1 then return -- GET THE RADIUS FROM THE FIRST SHAPE tell application id "OGfl" set shpFirst to first item of lstShapes tell shpFirst to set {{oX, oY}, {rWidth, rHeight}} to {origin, size} set {cX, cY} to {oX + rWidth / 2, oY + rHeight / 2} set {dX, dY} to {cX - rX, cY - rY} set rRadius to (dX ^ 2 + dY ^ 2) ^ 0.5 -- loop through the remaining shapes -- getting the angles -- arcsin(dx/rRadius set strCmd to "echo 'echo " set lstAngle to {} set strWatch to "" repeat with i from 1 to lngShapes set oShape to item i of lstShapes tell oShape to set {{oX, oY}, {rWidth, rHeight}} to {origin, size} set {cX, cY} to {oX + rWidth / 2, oY + rHeight / 2} set {dX, dY} to {cX - rX, rY - cY} if dX > rRadius then set strSinTheta to "1" else if dX < -rRadius then set strSinTheta to "-1" else set strSinTheta to (dX / rRadius) as string end if if dY > rRadius then set strCosTheta to "1" else if dY < -rRadius then set strCosTheta to "-1" else set strCosTheta to (dY / rRadius) as string end if set strCmd to (strCmd & "$((asin(" & strSinTheta & "))) $((acos(" & strCosTheta as string) & "))) " end repeat set strCmd to strCmd & "' | ksh" set my text item delimiters to space set lstData to text items of (do shell script strCmd) set lstAngles to {} set str to "" set j to 1 repeat with i from 1 to (lngShapes * 2) by 2 set {sX, sY} to {(item i of lstData) as number, (item (i + 1) of lstData) as number} set oShape to item j of lstShapes set str to str & my GetDegrees(sX, sY) & tab & id of oShape & tab & text of oShape & linefeed set j to j + 1 end repeat set lst to paragraphs of (do shell script "echo " & quoted form of texts 1 thru -2 of str & " | sort -n | cut -f 1,2") set my text item delimiters to tab tell oCanvas repeat with i from 1 to length of lst set lstParts to text items of item i of lst set item 2 of lstParts to shape id ((item 2 of lstParts) as integer) set item i of lst to lstParts end repeat end tell set my text item delimiters to space return lst end tell end ReadShapes -- REWRITE RADIANS AS DEGREES on GetDegrees(sX, sY) if sX > 0 then sY * pDeg else 360 - (sY * pDeg) end if end GetDegrees -- PULL/PUSH SHAPES TO A NORMALIZED RADIUS AROUND A POINT on EqualizeRadius({rX, rY}, lstSeln) -- first get the average radius set lngShapes to length of lstSeln if lngShapes < 1 then return set lstRads to {} repeat with oShape in lstSeln set {dX, dY} to (my Point2Shape({rX, rY}, oShape)) set end of lstRads to {(dX ^ 2 + dY ^ 2) ^ 0.5, {dX, dY}} end repeat set rSum to 0 repeat with oRad in lstRads set rSum to rSum + (item 1 of oRad) end repeat set rAvgRad to rSum / lngShapes -- then move each shape to that radius -- rescale the dx and dy from the centre in proportion to the radius change tell application id "OGfl" repeat with i from 1 to lngShapes set {rRad, {dX, dY}} to item i of lstRads if rRad ≠ rAvgRad then set rPropn to rAvgRad / rRad set {dX, dY} to {dX * rPropn - dX, dY * rPropn - dY} set oShape to item i of lstSeln set {xOld, yOld} to origin of oShape set origin of oShape to {xOld + dX, yOld + dY} end if end repeat end tell return rAvgRad end EqualizeRadius on RadialPlaceShape(oShape, {oX, oY}, rTheta, rRadius) tell application id "OGfl" -- Rewrite the Theta and Radius to a DX, DY set {dX, dY} to {rRadius * (my sin(rTheta)), rRadius * (my cos(rTheta))} -- Apply that to the origin set {X1, Y1} to {oX + dX, oY - dY} -- Place the shape by its top-left corner, allowing for its height and width set {rWidth, rHeight} to size of oShape set origin of oShape to {X1 - rWidth / 2, Y1 - rHeight / 2} end tell end RadialPlaceShape on Point2Shape({rX, rY}, shp) tell application id "OGfl" tell shp to set {{rX1, rY1}, {rWidth1, rHeight1}} to {its origin, its size} end tell set dX to (rX1 + rWidth1 / 2) - rX set dY to (rY1 + rHeight1 / 2) - rY {dX, dY} end Point2Shape -- FIND A POINT WHOSE COORDINATES ARE AT THE MEAN OF A SET OF CENTRES on GetCentroid(lstShapes) set lngShapes to length of lstShapes if lngShapes < 1 then return missing value set {rXSum, rYSum} to {0, 0} tell application id "OGfl" repeat with oShape in lstShapes tell oShape to set {{rX, rY}, {rWidth, rHeight}} to {origin, size} set rXSum to rXSum + rX + (rWidth / 2) set rYSum to rYSum + rY + (rHeight / 2) end repeat end tell {rXSum / lngShapes, rYSum / lngShapes} end GetCentroid -- BASIC TRIG FUNCTIONS PUBLISHED BY OTHERS -- COULD USE THE KSH SHELL, BUT THESE ARE PROBABLY FASTER ... -- Trig functions from: -- http://lists.apple.com/archives/applescript-users/2004/Feb/msg00939.html on cos(x) -- degrees local myCos, numerator, denominator, factor set myCos to 0 if (x = 90) or (x = 270) then set myCos to 0 else set x to (x - (((x / 360) div 1) * 360)) * (pi / 180) set {myCos, numerator, denominator, factor} to {0, 1, 1, -(x ^ 2)} repeat with i from 2 to 40 by 2 set myCos to myCos + numerator / denominator set numerator to numerator * factor set denominator to denominator * i * (i - 1) end repeat end if return myCos end cos ---------------------------- on sin(x) -- degrees local mySin, numerator, denomintator, factor set mySin to 0 if (x = 180) or (x = 360) then set mySin to 0 else set x to (x - (((x / 360) div 1) * 360)) * (pi / 180) set {mySin, numerator, denominator, factor} to {0, x, 1, -(x ^ 2)} repeat with i from 3 to 40 by 2 set mySin to mySin + numerator / denominator set numerator to numerator * factor set denominator to denominator * i * (i - 1) end repeat end if return mySin end sin Last edited by RobTrew; 2012-02-12 at 02:40 PM..
Post 4
|
Thread Tools | Search this Thread |
Display Modes | |
|
![]() |
||||
Thread | Thread Starter | Forum | Replies | Last Post |
Arcs and dimensions? | ralphg | OmniGraffle General | 0 | 2010-08-03 03:45 PM |
Converting adjustable arcs | kelvSYC | OmniGraffle General | 0 | 2010-07-06 12:19 PM |
OmniGraffle needs ability to draw arcs | PulpMysteryFan | OmniGraffle General | 3 | 2008-01-14 07:15 AM |
Any word on arcs? | nestorph | OmniGraffle General | 0 | 2007-11-18 08:19 PM |