Optimal positioning of text

Hi All,

    I am trying to visualize some more text in an already rather
crowded 2D plot. As this new information I want to display is optional
(it depends on a use choice) but possibly very useful as information,
I would like to add it as text inside my plot, and possibly to add it
in a "smart" way (i.e., with minimum or no overlapping between this
new text and the rest of the plot, like legend does with loc=0).

Now, I have tried to steal the code from legend.py but I am not
getting anywhere, also because I am mixing display coordinates, data
coordinates and wx.DC text extents and I am getting crazy :frowning:

This is the code I have so far:

    def PositionAnnotation(self, dc, label, dash, x, y):
        """
        Position a matplotlib text instance without overlapping with
the rest of the data.

        :param `dc`: an instance of wx.ClientDC;
        :param `label`: the label to display (2 lines of text);
        :param `dash`: the actual TextWithDash matplotlib class to position
        :param `x`: a list of possible x location (this is fixed)
        :param `y`: a list of possible y location (this is fixed)
        """

        # Stolen from legend.py
        lines = []

       # Here I work with display coordinates (!)

        for handle in self.leftaxis.lines:
            path = handle.get_path()
            trans = handle.get_transform()
            tpath = trans.transform_path(path)
            lines.append(tpath)

        # End of stolen from legend.py

       # Here I work with screen/character coordinates (!)
        width, height, dummy = dc.GetMultiLineTextExtent(label)
        candidates = []

        for l, b in zip(x, y):

           # And here I work with data coordinates (!)

            dashBox = Bbox.from_bounds(l, b, width+5, height+5)
            badness = 0
            for line in lines:
                if line.intersects_bbox(dashBox):
                    badness += 1

            ox, oy = l, b
            if badness == 0:
                return ox, oy

            candidates.append((badness, (l, b)))

        # Stolen from legend.py

        # rather than use min() or list.sort(), do this so that we are assured
        # that in the case of two equal badnesses, the one first considered is
        # returned.
        # NOTE: list.sort() is stable.But leave as it is for now. -JJL
        minCandidate = candidates[0]
        for candidate in candidates:
            if candidate[0] < minCandidate[0]:
                minCandidate = candidate

        ox, oy = minCandidate[1]

        return ox, oy

This code is not doing anything useful as I always get a badness of 0,
although I can see that the new text overlaps quite a lot of other
artists.

Does anyone have some suggestion on how to improve the code?

Thank you in advance.

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.alice.it/infinity77/
http://thedoomedcity.blogspot.com/

A snippet of code seldom helps.
Is your x,y input in display coordinates?

Ideally, this must be checked in the drawing time (as in the legend).
With your current approach, there could be lots of possible reason
that this does not work.

Regards,

-JJ

···

On Sat, Feb 27, 2010 at 12:29 PM, Andrea Gavana <andrea.gavana@...287...> wrote:

This code is not doing anything useful as I always get a badness of 0,
although I can see that the new text overlaps quite a lot of other
artists.

Does anyone have some suggestion on how to improve the code?

This code is not doing anything useful as I always get a badness of 0,
although I can see that the new text overlaps quite a lot of other
artists.

Does anyone have some suggestion on how to improve the code?

A snippet of code seldom helps.
Is your x,y input in display coordinates?

No, data values.

Ideally, this must be checked in the drawing time (as in the legend).

This is what I am trying to do, but I messed up screen/data/canvas
coordinates and I can't get my head around it :frowning:

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.alice.it/infinity77/

···

On 28 February 2010 01:18, Jae-Joon Lee wrote:

On Sat, Feb 27, 2010 at 12:29 PM, Andrea Gavana <andrea.gavana@...287...> wrote:

If I read your correctly,

       for l, b in zip(x, y):

          # And here I work with data coordinates (!)

           dashBox = Bbox.from_bounds(l, b, width+5, height+5)
           badness = 0
           for line in lines:
               if line.intersects_bbox(dashBox):
                   badness += 1

x, y (therefore l, b) in data coordinate.
width, height?? this seems to be some wx specific coordinate, i have no idea.
lines (therefore line) in display coordinate.

converting x,y to display coordinate should straight forward. But I'm
not sure what kind of coordinate width and height has. Is it a method
of some class derived from matplotlib's Text?? If then, the extent of
the text can be measured using the get_window_extent method. This
requires a renderer instance, which should be known if the method is
called during the drawing time.

Again, post a complete but simple(!) code.

-JJ

···

On Sat, Feb 27, 2010 at 8:59 PM, Andrea Gavana <andrea.gavana@...287...> wrote:

On 28 February 2010 01:18, Jae-Joon Lee wrote:

On Sat, Feb 27, 2010 at 12:29 PM, Andrea Gavana <andrea.gavana@...287...> wrote:

This code is not doing anything useful as I always get a badness of 0,
although I can see that the new text overlaps quite a lot of other
artists.

Does anyone have some suggestion on how to improve the code?

A snippet of code seldom helps.
Is your x,y input in display coordinates?

No, data values.

Ideally, this must be checked in the drawing time (as in the legend).

This is what I am trying to do, but I messed up screen/data/canvas
coordinates and I can't get my head around it :frowning:

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.alice.it/infinity77/
http://thedoomedcity.blogspot.com/

Hi Jae-Joon & All,

If I read your correctly,

  for l, b in zip\(x, y\):

     \# And here  I work with data coordinates \(\!\)

      dashBox = Bbox\.from\_bounds\(l, b, width\+5, height\+5\)
      badness = 0
      for line in lines:
          if line\.intersects\_bbox\(dashBox\):
              badness \+= 1

x, y (therefore l, b) in data coordinate.
width, height?? this seems to be some wx specific coordinate, i have no idea.
lines (therefore line) in display coordinate.

converting x,y to display coordinate should straight forward. But I'm
not sure what kind of coordinate width and height has. Is it a method
of some class derived from matplotlib's Text?? If then, the extent of
the text can be measured using the get_window_extent method. This
requires a renderer instance, which should be known if the method is
called during the drawing time.

Again, post a complete but simple(!) code.

OK, I think I got a complete code. Not super-simple, but simple enough
I believe. After you run it you'll see a bunch of points and lines
with some text. If you left-click inside the axis this will start the
calculations for the "optimal positioning". There are a couple of
problems:

1) The code I have looks only for optimal positioning with respect to
lines in the plot, it doesn't include texts (I don't know how to do
it); You'll see what I mean if you run the code, the "optimally"
positioned texts overlap with other text in the plot, and with
themselves too (i.e., one "optimally" positioned text overlap with
another "optimally" positioned text);
2) The code as it stands it's veeeeery slow. On my (relatively fast)
computer, it takes almost 6 seconds to "optimally" position 14 labels.

In order to run the code, you'll also need the "lines.txt" file, which
contains the main lines coordinates. Sorry about this extra file but I
wanted it to be as close as possible to my use-case.

Thank you in advance for your suggestions.

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.alice.it/infinity77/

MPL_Test.py (3.97 KB)

lines.txt (10.3 KB)

···

On 28 February 2010 03:09, Jae-Joon Lee wrote:

I tried to put some code to check overlapping texts.
The code runs, but I'm not sure if the code is correct. Texts still
overlap but I haven't checked if this is due to the incorrect code or
the input.
But I believe it gives you enough idea how to fix things.

You have several hundreds of candidate points for a few cases. No
doubt it takes so long.

Regards,

-JJ

MPL_Test_jj.py (4.34 KB)

···

On Mon, Mar 1, 2010 at 6:07 AM, Andrea Gavana <andrea.gavana@...287...> wrote:

Hi Jae-Joon & All,

On 28 February 2010 03:09, Jae-Joon Lee wrote:

If I read your correctly,

  for l, b in zip\(x, y\):

     \# And here  I work with data coordinates \(\!\)

      dashBox = Bbox\.from\_bounds\(l, b, width\+5, height\+5\)
      badness = 0
      for line in lines:
          if line\.intersects\_bbox\(dashBox\):
              badness \+= 1

x, y (therefore l, b) in data coordinate.
width, height?? this seems to be some wx specific coordinate, i have no idea.
lines (therefore line) in display coordinate.

converting x,y to display coordinate should straight forward. But I'm
not sure what kind of coordinate width and height has. Is it a method
of some class derived from matplotlib's Text?? If then, the extent of
the text can be measured using the get_window_extent method. This
requires a renderer instance, which should be known if the method is
called during the drawing time.

Again, post a complete but simple(!) code.

OK, I think I got a complete code. Not super-simple, but simple enough
I believe. After you run it you'll see a bunch of points and lines
with some text. If you left-click inside the axis this will start the
calculations for the "optimal positioning". There are a couple of
problems:

1) The code I have looks only for optimal positioning with respect to
lines in the plot, it doesn't include texts (I don't know how to do
it); You'll see what I mean if you run the code, the "optimally"
positioned texts overlap with other text in the plot, and with
themselves too (i.e., one "optimally" positioned text overlap with
another "optimally" positioned text);
2) The code as it stands it's veeeeery slow. On my (relatively fast)
computer, it takes almost 6 seconds to "optimally" position 14 labels.

In order to run the code, you'll also need the "lines.txt" file, which
contains the main lines coordinates. Sorry about this extra file but I
wanted it to be as close as possible to my use-case.

Thank you in advance for your suggestions.

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.alice.it/infinity77/
http://thedoomedcity.blogspot.com/