Repository: plotly/plotly.py

Issue:
How to convert long mpl annotations that modify the figure's margins
See

http://nbviewer.ipython.org/github/etpinard/plotly-misc-nbs/blob/etienne/matplotlylib/annotations-test.ipynb#2.-Long-annotations-and-margins

mpl modifies the figure's margins when one long annotation is placed on a figure, plotly does not.

How should we handle this?



Discussion:
Alright, so, there's no simple way to tell from mpl upon figure opening if the figure is going to need to be bigger. However, the reason you see the long annotation is b.c. you're using mpl in IPython! Try this:
http://nbviewer.ipython.org/gist/theengineear/61fc798db51672299785

You'll note that the GUI that opens clips off the text just like Plotly would, and that's because that's just where the figure ends!

This may not be a real issue. I might suggest we throw a warning when an annotation runs over the edge of a plot in matplotylib. 

What do you think?

Oh wow! I did not see that coming!

Randal's figure rendered with `.show()` yields:

![screenshot from 2014-07-04 15 25 08](https://cloud.githubusercontent.com/assets/6675409/3484636/fdf34e44-03b0-11e4-905f-72dc6e190919.png)

But, with `plt.savefig()` and within the notebook, the figure's margins are adjusted.

I suspect that `plt.savefig()` has no affect on the mpl object. 

I think issuing a warning is the right call.

The warning should make a reference to the Margin object and the Annotation object(s) that caused the issue.

Moreover, maybe we include a few words about how `plt.savefig()` and IPython NBs _adjust_ the margins so that matplotlylib user can correct their mpl code accordingly. 

~~I don't know if the NB actually adjusts anything, the margins are still the same, it's just that the background of the notebook is white, so there's extra canvas to draw on perhaps? i'm not sure how mpl draws in a NB.~~ Nevermind, yeah IPython definitely adjust accordingly.

Yeah, i'll just work on a warning.


Resolving patch:
diff --git a/plotly/matplotlylib/mpltools.py b/plotly/matplotlylib/mpltools.py
--- a/plotly/matplotlylib/mpltools.py
+++ b/plotly/matplotlylib/mpltools.py
@@ -35,6 +35,21 @@ def check_bar_match(old_bar, new_bar):
         return False
 
 
+def check_corners(inner_obj, outer_obj):
+    inner_corners = inner_obj.get_window_extent().corners()
+    outer_corners = outer_obj.get_window_extent().corners()
+    if inner_corners[0][0] < outer_corners[0][0]:
+        return False
+    elif inner_corners[0][1] < outer_corners[0][1]:
+        return False
+    elif inner_corners[3][0] > outer_corners[3][0]:
+        return False
+    elif inner_corners[3][1] > outer_corners[3][1]:
+        return False
+    else:
+        return True
+
+
 def convert_affine_trans(dpi=None, aff=None):
     if aff is not None and dpi is not None:
         try:
diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py
--- a/plotly/matplotlylib/renderer.py
+++ b/plotly/matplotlylib/renderer.py
@@ -11,6 +11,10 @@
 from . import mpltools
 from .. graph_objs import *
 
+# Warning format
+def warning_on_one_line(message, category, filename, lineno, file=None, line=None):
+    return '%s:%s: %s:\n\n%s\n\n' % (filename, lineno, category.__name__, message)
+warnings.formatwarning = warning_on_one_line
 
 class PlotlyRenderer(Renderer):
     """A renderer class inheriting from base for rendering mpl plots in plotly.
@@ -498,6 +502,14 @@ def draw_text(self, **props):
 
         """
         self.msg += "    Attempting to draw an mpl text object\n"
+        if not mpltools.check_corners(props['mplobj'], self.mpl_fig):
+            warnings.warn("Looks like the annotation(s) you are trying \n" 
+                          "to draw lies/lay outside the given figure size.\n\n"
+                          "Therefore, the resulting Plotly figure may not be \n"
+                          "large enough to view the full text. To adjust \n"
+                          "the size of the figure, use the 'width' and \n"
+                          "'height' keys in the Layout object. Alternatively,\n"
+                          "use the Margin object to adjust the figure's margins.")
         align = props['mplobj']._multialignment
         if not align:
             align = props['style']['halign'] # mpl default
diff --git a/plotly/tools.py b/plotly/tools.py
--- a/plotly/tools.py
+++ b/plotly/tools.py
@@ -14,6 +14,11 @@
 from . import utils
 from . import exceptions
 
+# Warning format
+def warning_on_one_line(message, category, filename, lineno, file=None, line=None):
+    return '%s:%s: %s:\n\n%s\n\n' % (filename, lineno, category.__name__, message)
+warnings.formatwarning = warning_on_one_line
+
 try:
     from . import matplotlylib
     _matplotlylib_imported = True
@@ -526,11 +531,12 @@ def _replace_newline(obj):
     elif isinstance(obj, (str, unicode)):
         s = obj.replace('\n', '<br>')
         if s != obj:
-            warnings.warn("Looks like you used a newline character: '\\n'.\n"
-                          "Plotly uses a subset of HTML escape characters "
-                          "to do things like newline, bold, italics, etc.\n"
-                          "Your newline characters have been converted to "
-                          "'<br>' so they'll show up right!")
+            warnings.warn("Looks like you used a newline character: '\\n'.\n\n"
+                          "Plotly uses a subset of HTML escape characters\n"
+                          "to do things like newline (<br>), bold (<b></b>),\n"
+                          "italics (<i></i>), etc. Your newline characters \n"
+                          "have been converted to '<br>' so they will show \n"
+                          "up right on your Plotly figure!")
         return s
     else:
         return obj  # we return the actual reference... but DON'T mutate.


Test patch: