25
$\begingroup$

UPDATE

There may be some confusion as to the specification of the problem. kguler's answer doesn't really answer the question. I'll try to elaborate a bit at the bottom of the post.


One problem with the solutions of the custom arrow shaft question is the specification of the edge thickness. In the design of the arrows, the thickness of the edge in relation to the overall size of the arrow is part of the effect one wants to achieve.

Mathematica graphics

Unfortunately, the two methods that can be used to specify the edge thickness, namely Thickness and AbsoluteThickness are not sufficient for this goal. As an easy illustration of the problem, I'll draw some circles here.

Thickness

Graphics[{FaceForm[], EdgeForm[{Thickness[0.1], Blue}], Disk[{0, 0}]}, ImageSize -> 200]

Add some identical (!) copies of the same circle:

Graphics[{FaceForm[], EdgeForm[{Thickness[0.1], Blue}], 
         Disk[{0, 0}], Disk[{3, 0}], Disk[{6, 0}]}, ImageSize -> 600]

Mathematica graphics

The number in the Thickness specification is a fraction of the horizontal image extent.

AbsoluteThickness

Graphics[{FaceForm[], EdgeForm[{AbsoluteThickness[20], Blue}], Disk[{0, 0}]}, ImageSize -> 200]

Mathematica graphics

Now, let's reduce the image size:

Graphics[{FaceForm[], EdgeForm[{AbsoluteThickness[20], Blue}], Disk[{0, 0}]}, ImageSize -> 70]

Mathematica graphics

In both cases, the ratio between the edge thickness and the object size changes. Sometimes that may be desirable, but here it isn't.

What is needed is either a thickness specification that is related to object size or a status variable from which the current image extent can be read, so that with known object size the edge thickness can be scaled. The latter would be something akin to CurrentValue or so.

Something self-referential like

pl = Graphics[{Text[Norm@PlotRange[pl][[2]] // Dynamic, {10, 10}]}]

could work, but is rather ugly and, frankly, scary. When I changed PlotRange to ImageSize I got my frontend hanging.

Any ideas?


UPDATE

Trying to further clarify the question.


First, some definitions:

Mathematica graphics

We have one or more objects with a given object size. In this case, circles with a radius of one. This size is given in Mathematica's internal, dimensionless canvas coordinate system.

We want to specify the object edge thickness in terms of this object size. However, MMA doesn't have that capability. You have to either specify the EdgeForm as Thickness (which measures edge thickness as a fraction of the horizontal PlotRange) or as AbsoluteThickness (which measures edge thickness in terms of printer's points, 1/72 of an inch).

The PlotRange is the dimensionless size of MMA's canvas as displayed in the figure. It is fully independent of the ImageSize, which determines the graphics's physical size in printer's points.

This leads to the following problem :

Graphics[{FaceForm[], EdgeForm[{Thickness[0.1], Blue}], Disk[{0, 0}]}]

Mathematica graphics

Graphics[{FaceForm[], EdgeForm[{Thickness[0.1], Blue}], Disk[{0, 0}], Disk[{3, 0}], Disk[{6, 0}]}]

Mathematica graphics

Both figures have the same ImageSize, but the PlotRange has increased (more of MMA's internal canvas is revealed), which means that edge thickness related to object size increases too, since the thickness in canvas terms increases whereas the object remains at the same canvas size. The circle's edge now takes up most of the circle's area, which wasn't the case before.

If we specify thickness with AbsoluteThickness we get:

Graphics[{FaceForm[], EdgeForm[{AbsoluteThickness[10], Blue}], Disk[{0, 0}]}]

Mathematica graphics

Graphics[{FaceForm[], EdgeForm[{AbsoluteThickness[10], Blue}], 
          Disk[{0, 0}], Disk[{3, 0}], Disk[{6, 0}]}]

Mathematica graphics

In this case, the edge thickness in points remains the same, but the object size in points decreases because the ImageSize in points is the same, but more objects have to be fitted in that same space, so they get less points allotted per object. Again, the edge thickness related to the object size changes.

Introducing ThicknessC as the desired edge thickness and ObjectSizeC the object size, both in canvas terms, we have

ThicknessC = C*ObjectSizeC 

with C the desired constant ratio of these two variables. C is responsible for the appearance of the object. For instance, we might want to have a circle where the edge thickness is 1/10 of its diameter, so C = 1/10.

Since Thickness is a fraction of the horizontal PlotRange we have:

ThicknessC = Thickness*PlotRange (hor.)

and hence, to get the desired edge thickness ThicknessC, we have to specify a Thickness given by:

Thickness =  C * ObjectSizeC / PlotRange (hor.)

Similarly, we have:

Thickness = AbsoluteThickness / ImageSize

and hence,

AbsoluteThickness = C * ObjectSizeC * ImageSize / PlotRange (hor.)

It seems that any object related edge thickness specification minimally needs to know the final PlotRange of the figure, which, on its turn, is determined by all objects in the figure.

If we're going to use AbsoluteThickness (but why would we bother?) we'd also need awareness of the ImageSize in addition to knowledge about PlotRange.

So, there you have it. The problem can be solved if we can obtain the final PlotRange. The question to be asked is: How can we determine the PlotRange of a figure while we are drawing it?

Of course, PlotRange can be set manually, or be obtained after the fact, but that would mean something ugly like:

pr1 = -Subtract @@PlotRange[
        Graphics[{FaceForm[], EdgeForm[{Thickness[0.3], Blue}], 
                Disk[{0, 0}], Disk[{3, 0}], Disk[{6, 0}]}]][[1]];
pr2 = -Subtract @@ PlotRange[
        Graphics[{FaceForm[], EdgeForm[{Thickness[0.3], Blue}], Disk[{0, 0}]}]][[1]];
Graphics[{FaceForm[], EdgeForm[{Thickness[0.3 pr2/pr1], Blue}], 
          Disk[{0, 0}], Disk[{3, 0}], Disk[{6, 0}]}]
Graphics[{FaceForm[], EdgeForm[{Thickness[0.3], Blue}], Disk[{0, 0}]}]

Mathematica graphics

This works, but isn't elegant.

$\endgroup$

2 Answers 2

7
$\begingroup$

Perhaps you are looking for Inset?

disk = Graphics[{EdgeForm[{Black, Thickness[0.1]}], LightBlue, Disk[]}];

Graphics[{
  Inset[disk, {0, 0}, {0, 0}, 1],
  Inset[disk, {2, 0}, {0, 0}, 2],
  Inset[disk, {4, 0}, {0, 0}, 1.5]
}]

Show[%, ImageSize -> 200]

Mathematica graphics

Mathematica graphics

$\endgroup$
8
  • $\begingroup$ The problem with scaling the circle itself is that I can't put three on a row anymore. I have to go now, so can't try, but could you check this? $\endgroup$ Commented Oct 9, 2012 at 22:09
  • $\begingroup$ +1 Inset seems to solve the problem of the thickness/size ratio. Aligning and scaling of complex objects will need some attention, but that will be doable. Are you aware of any disadvantages of building up graphics from many Inset-s? I have yet to test this in combination with Graph, but will do that tomorrow. $\endgroup$ Commented Oct 11, 2012 at 21:47
  • $\begingroup$ @Sjoerd I have created a Graphics object with lots of Insets without problem (and Text is implemented as Inset IIRC), but I cannot recall creating one with many levels of Insets, meaning one inside another; that remains to be tested. $\endgroup$
    – Mr.Wizard
    Commented Oct 11, 2012 at 21:54
  • $\begingroup$ I used this in an update to my designer's arrow answer. Works very well! You got my accept. A minor complaint I have is about the whitespace it generates. See note over there. Try the code without the "ShrinkWrap" set to True. $\endgroup$ Commented Oct 14, 2012 at 22:16
  • 1
    $\begingroup$ BTW Did you notice that using Inset allowed me to strip GeometricTransform and FindGeometricTransform from the code? Inset is powerful, although there are details you have to take care of, like the PlotRangePadding, which will prevent precise scaling and positioning when forgotten. $\endgroup$ Commented Oct 14, 2012 at 22:36
11
$\begingroup$

Update: Could not get CurrentValue[..., "ImageSize"] to pick up the correct image size.

Based on Sjoerd's update, an alternative approach would be to post process a graphics object to transform the thickness primitives using the aspect ratio, plot range and image size of the input graphics. This can be done in a number of ways using AbsoluteOptions (getting PlotRange or AspectRatio) to calculate a new thickness.

Sjoerd's example (slightly modified)

 grpSjrd1 = Graphics[{FaceForm[], EdgeForm[{Thickness[0.1], Blue}], Disk[{0, 0}],
     Disk[{3, 0}], Disk[{6, 0}]}, ImageSize -> 300]
grpSjrd2 = Graphics[{FaceForm[], EdgeForm[{Thickness[0.1], Blue}], Disk[{0, 0}]},
    ImageSize -> 300]

enter image description here

ClearAll[thicknessUnits]; 
thicknessUnits[gr_Graphics, sc_] :=
 With[{units = 
       AbsoluteOptions[gr, AspectRatio][[1, 2]]},
  gr /. EdgeForm[{beg___, Thickness[thcknss_], end___}] :> 
     EdgeForm[{beg, Thickness[sc units ], end}]]

Usage examples:

Grid[Transpose[
  {thicknessUnits[grpSjrd1, #],thicknessUnits[grpSjrd2, #]} & /@ 
      {.03, .05, .1, .2}], Spacings -> {3, 2}]

enter image description here

Alternatively, one can use the PlotRange instead of AspectRatio:

ClearAll[thicknessUnitsB]; 
thicknessUnitsB[gr_Graphics, sc_] :=
  With[{units =-Subtract @@ AbsoluteOptions[gr, PlotRange][[1, 2, #]] & /@ 
      {1, 2} // (#[[2]]/#[[1]]) & },
   gr /. EdgeForm[{beg___, Thickness[thcknss_], end___}] :> 
   EdgeForm[{beg, Thickness[sc units ], end}]]

Leftover from original post ... (holding for now until I give up struggling with CurrentValue[...])

Using CurrentValue["Magnification"] seems more promising:

Column[Style[Graphics[{FaceForm[],
  Dynamic@EdgeForm[{Thickness[.02 CurrentValue["Magnification"]], Blue}],
  Disk[{0, 0}], Disk[{3, 0}], Disk[{6, 0}]}, ImageSize -> 600], 
Magnification -> #] & /@ {.2, .5, 1., 1.5}]

enter image description here

$\endgroup$
8
  • $\begingroup$ Thanks! "ImageSize" was a nice find. It wasn't on my CurrentValue list. I'm not sure about "Magnification"'s utility. I have to check it but it looks like the thickness/object size doesn't remain constant in the last plot. $\endgroup$ Commented Oct 9, 2012 at 5:32
  • $\begingroup$ I'm afraid this doesn't work. Graphics[{Text[CurrentValue["ImageSize"]]}, ImageSize -> 100] returns {350,350} not 100. Addition of Dynamic doesn't help either. $\endgroup$ Commented Oct 9, 2012 at 18:28
  • $\begingroup$ The magnification part doesn't work either. The ratio of the circle radius to line thickness for the largest two sets of circles differs by a factor of about 1.5, so it isn't an invariant as was asked for. $\endgroup$ Commented Oct 9, 2012 at 18:32
  • $\begingroup$ Please, see the update to my question. $\endgroup$ Commented Oct 9, 2012 at 21:30
  • $\begingroup$ SjoerdC, sorry took a while to respond. I could not get CurrentValue[... "ImageSize"] to pick up the image size of the object it resides in. In the light of the update to your question, I will put together an updated post. $\endgroup$
    – kglr
    Commented Oct 10, 2012 at 22:53

Not the answer you're looking for? Browse other questions tagged or ask your own question.