diagrams / diagrams-lib

Diagrams standard library
https://diagrams.github.io/
Other
138 stars 62 forks source link

Independent x/y scales do not respect normalized (or global) units for text #348

Open cfhammill opened 4 years ago

cfhammill commented 4 years ago

I was trying to make a diagram composed of an unknown number of sub-diagrams of different sizes, and I wanted them to all have the same label size. Unexpectedly (to me) scaling x or y independently didn't preserve the global font size, making fonts appear stretched.

Here are a few examples:

figBig = (text "hi" # fontSizeN 0.1 <> square 1)
                         ===
      (scale 2 (text "hi" # fontSizeN 0.1 <> square 1))

looks like: big

Here the font is left unscaled (as desired). But if I scale x or y independently

figWide = (text "hi" # fontSizeN 0.1 <> square 1)
                         ===
      (scaleX 2 (text "hi" # fontSizeN 0.1 <> square 1))      

figTall = (text "hi" # fontSizeN 0.1 <> square 1)
                         ===
      (scaleY 2 (text "hi" # fontSizeN 0.1 <> square 1))

I get wide and tall

where the fonts are stretched.

Is this the desired behaviour? And if so is there any way to avoid it?

byorgey commented 4 years ago

This is the expected behavior (although this is definitely an area where there are many possible choices and it's not obvious that any particular choice is "correct"). Nonuniform scaling will always cause the text to get squished or stretched although the overall size will still be preserved if the size is set using normalized or global units.

I think what you want is to wrap the text objects in a scale-invariant wrapper. Then they will be unaffected by scaling. It's a bit low-level, unfortunately, but something like this ought to work:

import Diagrams.TwoD.Text
import Diagrams.Transform.ScaleInv

scaleInvText :: (TypeableFloat n, Renderable (Text n) b)
  => String -> QDiagram b V2 n Any
scaleInvText txt = mkQD (Prim $ scaleInv (Text mempty (BoxAlignedText 0.5 0.5) txt) (arrowV (1 ^& 0))
    (pointEnvelope origin)
    mempty
    mempty
    mempty

This is untested, however, so please let us know how this works or if you run into any issues!

cfhammill commented 4 years ago

I'll give this a shot, thanks for the code and clarification!

cfhammill commented 4 years ago

So with a bit of fiddling I got this mostly working:

scaleInvText :: (TypeableFloat n, Renderable (TwoDT.Text n) b) => String -> QDiagram b V2 n Any
scaleInvText txt =
  mkQD (Prim $ scaleInv (TwoDT.Text mempty (TwoDT.BoxAlignedText 0.5 0.5) txt) (1 ^& 0))
        (pointEnvelope origin)
         mempty
         mempty
         mempty

which I think simplifies to:

scaleInvText txt =
  scaleInvPrim (TwoDT.Text mempty (TwoDT.BoxAlignedText 0.5 0.5) txt)  (1 ^& 0)

The only problem is now the text is mirrored vertically. I tried to correct with reflectY but it had no effect (note this effects both the original and the simplified version)

EDIT: one additional thing that tripped me, fill colour for the scale invariant text needs to be specified manually.

byorgey commented 4 years ago

Hmm, I think something about the low-level nature of the code means we are missing out on some higher-level transformations diagrams usually does automatically to mediate between a logical y-axis that points up and backends which use a downwards-pointing y-axis. But I am not exactly sure what's going on. I will try to look into it.

cfhammill commented 4 years ago

Thanks, and makes sense, although not sure why the reflectY was having no effect. It's not very pressing, I can always maintain the figure in a more abstract state so that the text gets added after the transformation.