Pikchr

Alignment of objects based on their bounding boxes?
Login

Alignment of objects based on their bounding boxes?

(1) By Robert Engelhardt (engelhro) on 2021-02-13 23:52:29 [source]

Hello,

Maybe I'm not seeing the obvious or missed something in the docs, so I'm looking for external input to untangle the knot in my thinking:

I recently wanted to align two objects horizontally with a certain vertical offset between them (the problem with the directions switched is analogous) where the two objects had different widths (or heights in the other case).

My issue is the following:

  • I can't use the "diagonal" corners (e.g. the ".nw" point of one object and the ".ne" of the other object) for shapes that are not fully rectangular (e.g. a rounded box, or worse for other objects like circles) as these corner points are located on the object boundary and not on the bounding box, and as a result the vertical (or horizontal) distance would be incorrect.

  • Of course I can resort to the top/bottom/left/right points (e.g. ".n" of the first object and ".s" of the other object) for alignment, but then I need to take the dimension of both objects into account and explicitly calculate the correct shift. That's easy but it stills seems unnecessarily complex given that the sizes of the objects shouldn't really matter when aligning them (at least from the users point of view).

Example, with an intended vertical offset of 1 cm and left-aligned boxes:

object A object B (incorrect placement) object A object C (correct placement) 1 cm  < 1cm   1cm needs to be calculated
	boxht = 1.5cm
	boxrad = 0.5cm

// Simple, but incorrect (less than 1 cm apart vertically)
A1:	box "object A" wid 3cm
B:	box "object B" "(incorrect" "placement)" wid 2cm \
		with .nw at 1cm below A1.sw
	dot at A1.sw color red
	dot at B.nw color red

// With calculation, but correct (1 cm apart vertically)
A2:	box "object A" wid 3cm at 5cm right of A1
C:	box "object C" "(correct" "placement)" wid 2cm \
		with .n at A2.s - (A2.wid-2cm)/2, 1cm
	dot at A2.s color red
	dot at C.n color red

// Illustrate distances
X1: line thin color gray from 3mm left of A1.sw go left 5mm
X2: line thin color gray from 3mm left of B.nw go left 5mm
	arrow <-> thin color gray from X1 down until even with X2 "1 cm " rjust
X3:	line thin color gray from A1.ht/2 below 3mm right of A1.e to A1.ht/2 below 3mm left of A2.w
X4:	line thin color gray from (X3.w, B.n) go right 5mm
X5:	line thin color gray from (X3.e, C.n) go left 5mm
	arrow <-> thin color gray from X4 up until even with X3 "< 1cm " rjust
	arrow <-> thin color gray from X5 up until even with X3 " 1cm" ljust

X6:	line thin color gray from 3mm above C.n go up until even with 3mm below A2.s
X7: line thin color gray from 3mm below A2.s down until even with 3mm above C.n
	arrow <-> thin color gray from X6 to X7
	text "needs to be" ljust "calculated" ljust color gray at 2mm right of X7.c

What's the most elegant way to solve this? Or, let's say, the most Pikchr idiomatic approach?

I guess I'm searching for an easy way to position not an object but an object's boundary box relative to another object (or, again, its bounding box). One way I could imagine would be to specify the location-attribute with offsets separately for the x and y axis, something like:

<object A> with .w.x at <object B>.w.x and .n.y at <distance> below <object B>.s.y

to left align <object A> and <object B> with vertical <distance> between their northmost and southmost points (I have to admit that it's ugly that .w.x and .n.y are not positions, but mere values for the two axes, so the "at…" notation looks a little bit odd here).

In this notation the second appearance of <object B> could even be replaced by an <object C>, so <object A> could be placed in relation to two different objects for the horizontal and vertical axis.

What do you think?


Second question with regard to my example above: for box <object C> I need to specify the value of 2cm twice: the first time to define the width, and the second time to calculate the horizontal offset based on that width (and <object A>s width). Is there a way to prevent that and have that value given only once (making it easier to change it later)? There is no "this.wid", and "C.wid" doesn't work either (at least here during the definition of object C itself).

(2) By drh on 2021-02-14 00:42:21 in reply to 1 [link] [source]

Add added the "this" keyword on a branch. Using "this" allows the location of object C to be specified as:

  with .n at A2.s - (A2.wid-this.wid)/2, 1cm

Instead of as

  with .n at A2.s - (A2.wid-2cm)/2, 1cm

So that the hard-coded "2cm" distance does not need to repeated. (NB: But not in Fossil yet, because the change is still on a branch and has not been installed on Fossil.)

My only concern is that attributes of "this" are in a state of flux while the line is being parsed, and I'm not certain we want to guarantee what the value of a "this" attribute is at any particular point along that line. I think the currently implementation uses any "width", "height", or "radius" value computed to the left. But I'm not sure about other attributes. Certainly the "this" keyword would need to be carefully documented to describe its limitations.

I don't have a better solution for the positioning problem at this point. It seems like computing the value of .n is the easiest solution.

(3) By drh on 2021-02-14 01:51:55 in reply to 2 [link] [source]

Maybe the location of B should be specified (relative to A) like this:

  ... with .c at (A.w.x+0.5*this.width, A.s.y-1cm-0.5*this.height)

Or maybe this:

  ... with .n at (A.w.x+0.5*this.width, A.s.y-1cm)

Both of these make use of the new (experiemental) "this." label, which makes me more likely to want to merge that to trunk.

(4) By drh on 2021-02-14 02:35:36 in reply to 2 [link] [source]

I have merged the "this" enhancement to trunk, and integrated into Fossil, and recompiled for the server that hosts this repo. So you can now try it out. Here is the revised drawing:

object A object C (correct placement) X: 0.5*this.wid right of the green dot Y: 1cm below the blue dot
	boxht = 1.5cm
	boxrad = 0.5cm

// With calculation, but correct (1 cm apart vertically)
A2:	box "object A" wid 3cm
C:	box "object C" "(correct" "placement)" wid 2cm \
		with .n at (0.5*this.wid right of A2.w, 1cm below A2.s)
	dot at A2.s color blue
	dot at C.n color red
        dot at A2.w color green

  arrow <- thin from C.n then up 0.5cm right 0.5cm then right 0.5cm color gray
  box invis "X: 0.5*this.wid right of the green dot" ljust small \
       "Y: 1cm below the blue dot" ljust small fit color gray

(5) By Robert Engelhardt (engelhro) on 2021-02-22 11:24:53 in reply to 4 [link] [source]

I have merged the "this" enhancement to trunk, and integrated into Fossil, […] So you can now try it out.

Thanks a lot for the quick reaction and implementing "this" (with the restrictions you mentioned)! That certainly helps and makes things easier for the use case above.