21

Background

DynamicLocation can be very useful:

LocatorPane[Dynamic@x,
  Graphics[
    { EdgeForm @ Thick, FaceForm @ None, Rectangle[BoxID -> "box"]
    , Arrow[{Dynamic[x], DynamicLocation["box", Automatic]}]
    }
    , PlotRange -> 2
  ]
]

enter image description here

but I don't know much about it. It was introduced to me by Szabolcs somewhere around this topic: Find inset bounding box in plot coordinates. It is extensively used in Graph related plots, e.g. to make edge arrows pointing neatly to the edge of a vertex shape.

As shown above it can be used to specify position in Graphics with respect to primitives' boxes. So we can point e.g. Arrow to a Recangle, without knowing it's position, which was previously marked by BoxID. (see more: BoxID in InputField focus)

It also accepts more arguments which can e.g. automatically point to the closest point on marked primitive. Something that would normally cost us calling the kernel for some region related procedures.

Question

But what arguments does it accept and what do they do? What are possible pitfalls in using it?

What did I try?

From Graph related documentation pages I extracted only examples with:

DynamicLocation[id_String, Automatic | None, _alignmentSpec]}]

Where alignmentSpec is {Left, Center} etc.

I also noticed that its behavior depends of parent graphics primitive (not id's owner):

LocatorPane[Dynamic@x,
 Graphics[
  {EdgeForm@Thick, FaceForm@None, Rectangle[BoxID -> "box"]
   , Rectangle[Dynamic[x], DynamicLocation["box", Automatic]]
   , Arrow[{Dynamic[x], DynamicLocation["box", Automatic]}]

   }
  , PlotRange -> 2]
 ]

So the same DynamicLocation is points to different positions for Rectangle and Arrow, keep that in mind in your answer.

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
Kuba
  • 136,707
  • 13
  • 279
  • 740
  • 1
    I note your example can apply to Polygon,but could you give me a Circle example? – yode Sep 06 '17 at 16:47
  • This looks like an interesting question, but I would appreciate a little background on the undocumented functions and options you using. – m_goldberg Sep 06 '17 at 16:51
  • @m_goldberg done, not so much more but it is all I know. – Kuba Sep 06 '17 at 19:09
  • @Kuba I bet they only use things that can be modeled as rectangular boxes. Inset works great. – b3m2a1 Sep 06 '17 at 19:16
  • @yode Circle example is now solved, see https://mathematica.stackexchange.com/q/155237/5478 – Kuba Sep 07 '17 at 13:24

1 Answers1

13

So I did a scrape of all the installation files (nb, m, tr) and this is what I got:

$dynamicLocationDump=CloudImport["https://www.wolframcloud.com/objects/b3m2a1/dynamic_location_dump.mx"];

It's an Association of files and what was found there. All of the files are to be ref pages, many for Combinatorica.

If we look at all of the stuff that's scraped, this is what we find:

$specs = Cases[$dynamicLocationDump // Values, _DynamicLocation, \[Infinity]] //
  DeleteDuplicatesBy[Rest]

{DynamicLocation["VertexID$1", Automatic, Center], 
 DynamicLocation["VertexID$1", None, Center], 
 DynamicLocation["EdgeLabelID$2", Automatic, Scaled[0.5]], 
 DynamicLocation["VertexID$2", Automatic, Right], 
 DynamicLocation["VertexID$4", Automatic, Top], 
 DynamicLocation["VertexID$1", Automatic, {Right, Top}], 
 DynamicLocation["VertexID$1", Automatic, Left], 
 DynamicLocation["VertexID$5", Automatic, {Right, Bottom}], 
 DynamicLocation["VertexID$6", Automatic, Bottom], 
 DynamicLocation["EdgeLabelID$1", Automatic, Scaled[0.9]], 
 DynamicLocation["EdgeLabelID$4", Automatic, Scaled[0.965]], 
 DynamicLocation["EdgeLabelID$5", Automatic, Scaled[1]], 
 DynamicLocation["EdgeLabelID$1", Automatic, Scaled[0.]], 
 DynamicLocation["EdgeLabelID$1", Automatic, Scaled[1.]], 
 DynamicLocation["EdgeLabelID$2", Automatic, Scaled[0.96]], 
 DynamicLocation["EdgeLabelID$2", Automatic, Scaled[0.955]], 
 DynamicLocation["EdgeLabelID$1", Automatic, Scaled[0]], 
 DynamicLocation["EdgeLabelID$1", Automatic, Scaled[1/4]], 
 DynamicLocation["EdgeLabelID$1", Automatic, Scaled[1/3]], 
 DynamicLocation["EdgeLabelID$1", Automatic, Scaled[1/2]], 
 DynamicLocation["VertexID$1", Automatic, {Left, Bottom}], 
 DynamicLocation["VertexID$1", Automatic, {Left, Top}], 
 DynamicLocation["EdgeLabelID$1", Automatic, Scaled[0.6]], 
 DynamicLocation["EdgeLabelID$3", Automatic, Scaled[0.3]], 
 DynamicLocation["EdgeLabelID$1", Automatic, Scaled[0.4]]}

This suggest you pretty much hit everything. Let's just do one last quick look:

LocatorPane[Dynamic@x,
 Graphics[{
   EdgeForm@Thick, FaceForm@None, Rectangle[BoxID -> "box"],
   {Arrow[{Dynamic[x], #}], Red, 
       Text[HoldForm[#], Offset[{1, RandomInteger[{-20, 0}]}, #]]} & /@

          DeleteDuplicatesBy[
      Reverse@$specs, {#[[2]], Head[#[[3]]]} &] /. s_String -> "box"
   }, PlotRange -> 2]]

img

Sorry about how tough that is to read. In any case, the only thing you didn't mention (I think) was the Automatic | None difference. None seems to mean the location within the parent primitive itself. Automatic appears to mean the nearest position along the primitive's boundary.

Update:

So I did a scrape of all the GraphicsBox-es and this is what I got:

$gbDynamicLocationDump=CloudImport["https://www.wolframcloud.com/objects/b3m2a1/graphics_dynamic_location_dump.mx"];

Then let's see what sort of things these BoxIDs are assinged to:

Cases[Values@$gbDynamicLocationDump, _[___, 
   BoxID -> _, ___], \[Infinity]] // DeleteDuplicatesBy[Head]

{TagBox[DiskBox[{0., 0.}, 0.0203996], "DynamicName", 
  BoxID -> "VertexID$1"]}

So we can see they always use a TagBox. And, moreover, the DiskBox is supported, just we need to specify it right:

LocatorPane[Dynamic@x,
 Graphics[{
   TagBox[DiskBox[{0, 0}, .1], "DynamicName", BoxID -> "box"],
   Arrow[{Dynamic[x], DynamicLocation["box", Automatic]}]
   },
  PlotRange -> 1]
 ]

disk-box

I don't know how to do this in top-level primitives, though, so if someone else could chime in with that it'd be great.

Update 2:

Here're some of the scraped up supported box styles:

Cases[Values@$gbScrape, _[___, BoxID -> _, ___], \[Infinity]] // 
     Map[First] // Flatten // Map[Head] // DeleteDuplicates // 
 DeleteCases[
  Hue | GrayLevel | Thickness | RGBColor | EdgeForm | Arrowheads]

{DiskBox, StyleBox, TagBox, InsetBox, ArrowBox, RectangleBox, \
CircleBox, FilledCurveBox, DynamicBox, PointBox, PolygonBox, LineBox}

Which basically suggests everything is supported. Let's look at one example pulled directly from the scrape:

$edgeBox = 
  TagBox[StyleBox[
    ArrowBox[
     BezierCurveBox[{DynamicLocation["VertexID$1", Automatic, 
        Center], {0.16929929238168392`, -0.060834906748745296`}, \
{0.06631873896502918`, -0.24007829047418064`}, \
{-0.06588137300327365`, -0.2866769520693191`}, \
{-0.32874748131339104`, -0.01720980682360899`}, \
{-0.27888503732236586`, 0.11379416848298976`}, {-0.09714320225168427`,
         0.21229843426110356`}, 
       DynamicLocation["VertexID$1", Automatic, Center]}, 
      SplineDegree -> 7]], Arrowheads[Medium], StripOnInput -> False],
    "DynamicName", BoxID -> "EdgeLabelID$1"];

LocatorPane[Dynamic@x,
 Graphics[{
   $edgeBox,
   FaceForm[None], EdgeForm[Black],
   Rectangle[Dynamic[x], Dynamic[-x], BoxID -> "VertexID$1"],
   Arrow[{Dynamic[x], DynamicLocation["EdgeLabelID$1", Automatic]}]
   },
  PlotRange -> 1]
 ]

bloop

It gives a sense of how powerful this can be

Here's just one last trick we can do:

LocatorPane[Dynamic@x, 
 Graphics[{$edgeBox, FaceForm[None], EdgeForm[Black], 
   Rectangle[Dynamic[x], Dynamic[-x], BoxID -> "VertexID$1"],
   Arrow[{Dynamic[x],
     Dynamic[
      y = FE`Evaluate@DynamicLocation["EdgeLabelID$1", Automatic]
      ]}]}, PlotRange -> 1]]

It looks the same, but y takes on this weird Perimeter value that clearly the FE uses:

y

Perimeter[{{-0.351329, -0.306885}, {-0.351329, 0.612441}, {0.362441, 
   0.612441}, {0.362441, -0.306885}}, BezierCurve, Automatic, \
{{0.344778, -0.12389}, {0.319816, -0.116786}, {0.295984, -0.113405}, \
{0.273094, -0.113086}, {0.25097, -0.115206}, {0.229447, -0.119181}, \
{0.208376, -0.12447}, {0.187628, -0.130578}, {0.167094, -0.137054}, \
{0.146685, -0.143494}, {0.126339, -0.149538}, {0.106013, -0.154875}, \
{0.0856889, -0.159237}, {0.0653717, -0.162401}, {0.0450872, \
-0.164185}, {0.0248819, -0.164449}, {0.00482093, -0.16309}, \
{-0.0150137, -0.160042}, {-0.0345254, -0.155269}, {-0.0536057, \
-0.148769}, {-0.0721368, -0.140563}, {-0.0899945, -0.130697}, \
{-0.107052, -0.119236}, {-0.123181, -0.106261}, {-0.13826, \
-0.0918662}, {-0.152171, -0.0761518}, {-0.16481, -0.0592238}, \
{-0.176085, -0.0411881}, {-0.185923, -0.0221469}, {-0.194274, \
-0.00219502}, {-0.201111, 0.0185843}, {-0.206439, 
   0.0401228}, {-0.210292, 0.0623712}, {-0.212744, 
   0.0853023}, {-0.213904, 0.108915}, {-0.213925, 
   0.133236}, {-0.213007, 0.158324}, {-0.211393, 
   0.184273}, {-0.209381, 0.211212}, {-0.207318, 
   0.239309}, {-0.205605, 0.268775}, {-0.204699, 
   0.299858}, {-0.205113, 0.332852}, {-0.207418, 
   0.368093}, {-0.212241, 0.405961}, {-0.220266, 
   0.446879}, {-0.232235, 0.491312}, {-0.248944, 
   0.539763}, {-0.271242, 0.592778}}, 0.00277778]

Note that despite the naming, this is not Perimeter as if you try to evaluate you get an error. Rather this is something handled in the FE itself. But we can use it:

Quiet@Graphics[{
   Arrow[{
     {0, 0},
     Perimeter[{{-0.3513294816166106`, -0.3068850371721661`}, \
{-0.3513294816166106`, 0.6124405927277219`}, {0.3624405927277219`, 
        0.6124405927277219`}, {0.3624405927277219`, \
-0.3068850371721661`}}, BezierCurve, 
      Automatic, {{0.34477777777777785`, -0.12389020453117933`}, \
{0.3198158080521647`, -0.11678572885825483`}, {0.2959835781061064`, \
-0.1134049116935066`}, {0.27309421626600816`, -0.11308626362062549`}, \
{0.25097018105818075`, -0.1152060089894028`}, {0.22944697314491685`, \
-0.11918089795392665`}, {0.20837622351438345`, \
-0.12447042298528227`}, {0.18762818539739076`, -0.1305784672904533`}, \
{0.1670936573840974`, -0.13705441256912174`}, {0.14668536521371014`, \
-0.14349373354006323`}, {0.12633882971023885`, \
-0.14953810666883527`}, {0.10601274833736543`, \
-0.15487506052845523`}, {0.08568891784548616`, -0.1592371952247659`}, \
{0.06537172548398723`, -0.16240099831818505`}, {0.04508723625181282`, \
-0.16418528467353688`}, {0.024881903659384697`, \
-0.16444928766966216`}, {0.004820931474933732`, \
-0.1630904292005044`}, {-0.015013686071698618`, \
-0.1600417958993694`}, {-0.034525418154727594`, \
-0.15526934901805498`}, {-0.05360571781450374`, \
-0.1487688953925486`}, {-0.07213678154294184`, \
-0.14056284692698964`}, {-0.08999452051924084`, \
-0.13069679602759415`}, {-0.10705171602283764`, -0.119235934418238`}, \
{-0.12318133155053107`, -0.10626134276939753`}, \
{-0.13825995416472048`, -0.09186617857214373`}, \
{-0.1521713375996978`, -0.07615178968888592`}, \
{-0.16481001965293363`, -0.0592237810125657`}, \
{-0.17608498638829806`, -0.04118806166599455`}, \
{-0.18592335567815732`, -0.02214690017303507`}, \
{-0.1942740526112855`, -0.002195015033320563`}, {-0.2011114492935343`,
         0.018584271867786717`}, {-0.20643894156819753`, 
        0.040122791582304446`}, {-0.21029243518301577`, 
        0.06237116672410781`}, {-0.21274371393075708`, 
        0.08530230496307703`}, {-0.2139036622903172`, 
        0.10891471995722232`}, {-0.2139253150952799`, 
        0.13323565229107964`}, {-0.2130067067568755`, 
        0.15832396298869578`}, {-0.21139349256828177`, 
        0.18427277216948668`}, {-0.20938131461720355`, 
        0.21121181541528777`}, {-0.2073178848336742`, 
        0.23930949041688565`}, {-0.20560475770002262`, 
        0.2687745664683503`}, {-0.2046987651499378`, 
        0.2998575293774527`}, {-0.20511308618358182`, 
        0.3328515343604829`}, {-0.20741792372567824`, 
        0.3680929394897675`}, {-0.21224076125353575`, 
        0.405961392262186`}, {-0.22026617172192897`, 
        0.44687944185699774`}, {-0.23223515131178862`, 
        0.4913116496512662`}, {-0.24894395052962903`, 
        0.5397631705612203`}, {-0.2712423751846815`, 
        0.592777777777778`}}, 0.002777777777777768`]
     }]
   }]

permi box

b3m2a1
  • 46,870
  • 3
  • 92
  • 239
  • I asked also because I failed to use it with Offset, which would be very useful, and blindly tried putting {ox, oy} inside. Couldn't catch a pattern or even the difference for the first or the second argument unless it was just Automatic. Thanks for digging. – Kuba Sep 06 '17 at 19:18
  • @Kuba I'm currently waiting for a scrape that will pull all of the GraphicsBox-es themselves and maybe there'll be something there where we can pull the different primitives supported. – b3m2a1 Sep 06 '17 at 19:20
  • @Kuba the scrape paid off. And it only had to look through ~2000 of the 18000 files. – b3m2a1 Sep 06 '17 at 19:30
  • Nice, I guess DynamicNamespace is related to "DynamicName". – Kuba Sep 06 '17 at 20:18
  • @Kuba I'm guessing so. I don't really know. I tried to figure out DynamicNamespace a while ago and it was painful. – b3m2a1 Sep 06 '17 at 20:19
  • It seems it can be used to avoid ids conflicts: LocatorPane[Dynamic@x, Graphics[{ TagBox[DiskBox[{1, 1}, .1], "DynamicName", BoxID -> "box"], DynamicNamespace[{ TagBox[DiskBox[{0, 0}, .1], "DynamicName", BoxID -> "box"], Arrow[{Dynamic[x], DynamicLocation["box", Automatic]}] }]}, PlotRange -> 1]]. Looks like we are getting somewhere with it finally. – Kuba Sep 06 '17 at 20:47
  • @Kuba interesting. That is useful. It's also used all over the WolframAlphaClient stuff if I remember correctly. It's clearly a localization scheme, but I couldn't figure out the particulars. – b3m2a1 Sep 06 '17 at 20:57
  • @Kuba this is what I got from a scrape for DynamicNamespace – b3m2a1 Sep 06 '17 at 21:27
  • 2
    You are a crazy man. :) – yode Sep 07 '17 at 01:05
  • @b3m2a1 There should be an option to convert DynamicLocation into absolute coordinates as graphs usually don't contain DynamicLocation at all. Failed to find it though. Any ideas? – Kuba Sep 11 '17 at 08:04
  • @Kuba I'm sure it's handled through that Perimeter thing. How to go from that to an explicit position I don't know, though. One thing worth noting is some of the old Graph code that used DynamicLocation doesn't any more. What happens if you use "DynamicToLiteral" on a DynamicLocation containing Graphics? – b3m2a1 Sep 11 '17 at 08:08
  • @b3m2a1 ah, it is a token, I knew there was something like that, I forgot and was confused searching for a symbol. Now you told me the name I remember, and yes, it works with DynamicLocation leaving everything else intact. Would be nice to have something more precise. – Kuba Sep 11 '17 at 12:31
  • @Kuba you can also use the front end packet FrontEnd`NotebookDynamicToLiteral, but it requires a CellObject or BoxObject (I mean, it's typesetting, so it can't do much else). Unfortunately it replaces the object, though. – b3m2a1 Sep 11 '17 at 15:47
  • @b3m2a1 yep, though I could live with working with boxes, it can accept BoxData :( – Kuba Sep 11 '17 at 16:03
  • @b3m2a1 I was just about to play with this again, but your cloud object expired... – M.R. Sep 08 '21 at 19:26
  • @M.R. what a pain. Do you mean $dynamicLocationDump? I can see if I have a local cache of it... – b3m2a1 Sep 08 '21 at 19:28