Repository: bokeh/bokeh

Issue:
SVG output bleeds past axis
Bokeh: 0.12.6
Python: 3.6.0
OS: MacOS 10.12.6
Browser: Safari 10.1.2 (12603.3.8) (also tried Chrome 60.0.3112.101 (Official Build) (64-bit) and FireFox 55.0.2 (64-bit))
Selenium: 3.5.0 (same issue in 3.3.3)
Pillow: 4.2.1-py36_0 (same issue with 4.0.0-py36_0)
phantomjs: 2.1.1
olefile: 0.44-py36_0

Description:
Items like lines and annuli(? plural of annulus) stay within axes normally, but they don't respect my boundaries with the svg backend. I've tried playing with background and border colors to no avail.

Possibly related, but in more elaborate examples the order of rendering is reverse from how I added things to the plot.

Minimal working example:
```
from bokeh.plotting import figure, show

p = figure(plot_width=400, plot_height=400, output_backend = 'svg')
p.annulus(x=[1, 2, 3], y=[1, 2, 3], inner_radius=0.1, outer_radius=0.25,
					color="orange", alpha=0.6)
show(p)
```

With backend='svg':
<img width="424" alt="rogueannuli" src="https://user-images.githubusercontent.com/26759579/29490698-698560e0-84f8-11e7-9266-849881d523e1.png">

With backend='canvas':
<img width="409" alt="containedannulicanvasbackend" src="https://user-images.githubusercontent.com/26759579/29490714-fec5017e-84f8-11e7-918b-d68782456571.png">


Discussion:
Supporting my order-of-rendering as (part of?) the source of the issue, some of my more complex plots come out like this: 
[24 Sex.pdf](https://github.com/bokeh/bokeh/files/1236743/24.Sex.pdf)
The annuli should be under the lines too. If I set each annulus to level='underlay' then that part is corrected. It just looks like the annuli, lines, and what not are possibly being rendered at the wrong time.
I am running into the same issue -glyphs, text, and line elements seem to be appearing outside of the axis when output_backend = 'svg' .

Bokeh 0.12.13 / Selenium 3.8.1 / PhantomJS 2.1.1

Any updates?
This is a duplicate of #6345.

Resolving patch:
diff --git a/examples/integration/glyphs/frame_clipping_multi_backend.py b/examples/integration/glyphs/frame_clipping_multi_backend.py
new file mode 100644
--- /dev/null
+++ b/examples/integration/glyphs/frame_clipping_multi_backend.py
@@ -0,0 +1,15 @@
+from bokeh.plotting import figure
+from bokeh.layouts import row
+from bokeh.io import save
+
+def make_figure(output_backend):
+    p = figure(plot_width=400, plot_height=400, output_backend=output_backend, title="Backend: %s" % output_backend)
+    p.circle(x=[1, 2, 3], y=[1, 2, 3], radius=0.25, color="blue", alpha=0.5)
+    p.annulus(x=[1, 2, 3], y=[1, 2, 3], inner_radius=0.1, outer_radius=0.20, color="orange")
+    return p
+
+canvas = make_figure("canvas")
+webgl  = make_figure("webgl")
+svg    = make_figure("svg")
+
+save(row(canvas, webgl, svg))


Test patch:
diff --git a/bokeh/io/tests/test_export.py b/bokeh/io/tests/test_export.py
--- a/bokeh/io/tests/test_export.py
+++ b/bokeh/io/tests/test_export.py
@@ -24,6 +24,7 @@
 from mock import patch
 from tempfile import NamedTemporaryFile
 import os
+import re
 
 # External imports
 from PIL import Image
@@ -138,36 +139,45 @@ def test_get_svgs_no_svg_present():
 @pytest.mark.unit
 @pytest.mark.selenium
 def test_get_svgs_with_svg_present():
-    layout = Plot(x_range=Range1d(), y_range=Range1d(),
-                  plot_height=20, plot_width=20, toolbar_location=None,
-                  outline_line_color=None, border_fill_color=None,
-                  background_fill_color="red", output_backend="svg")
 
-    svgs = bie.get_svgs(layout)
-    assert svgs[0] == ('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" '
-                       'width="20" height="20" style="width: 20px; height: 20px;"><defs/><g><g transform="scale(1,1) '
-                       'translate(0.5,0.5)"><rect fill="#FFFFFF" stroke="none" x="0" y="0" width="20" height="20"/>'
-                       '<rect fill="red" stroke="none" x="5" y="5" width="10" height="10"/><g/><g/><g/><g/></g></g></svg>')
+    def fix_ids(svg):
+        svg = re.sub(r'id="\w{12}"', 'id="X"', svg)
+        svg = re.sub(r'url\(#\w{12}\)', 'url(#X)', svg)
+        return svg
 
-@pytest.mark.unit
-@pytest.mark.selenium
-def test_get_svgs_with_svg_present_with_driver():
     layout = Plot(x_range=Range1d(), y_range=Range1d(),
                   plot_height=20, plot_width=20, toolbar_location=None,
                   outline_line_color=None, border_fill_color=None,
                   background_fill_color="red", output_backend="svg")
 
-    driver = webdriver.PhantomJS(service_log_path=os.path.devnull)
-
-    svgs = bie.get_svgs(layout)
+    svg0 = fix_ids(bie.get_svgs(layout)[0])
 
-    # Have to manually clean up the driver session
-    terminate_web_driver(driver)
-
-    assert svgs[0] == ('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" '
-                       'width="20" height="20" style="width: 20px; height: 20px;"><defs/><g><g transform="scale(1,1) '
-                       'translate(0.5,0.5)"><rect fill="#FFFFFF" stroke="none" x="0" y="0" width="20" height="20"/>'
-                       '<rect fill="red" stroke="none" x="5" y="5" width="10" height="10"/><g/><g/><g/><g/></g></g></svg>')
+    driver = webdriver.PhantomJS(service_log_path=os.path.devnull)
+    svg1 = fix_ids(bie.get_svgs(layout)[0])
+    terminate_web_driver(driver) # Have to manually clean up the driver session
+
+    svg2 = (
+        '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" '
+        'width="20" height="20" style="width: 20px; height: 20px;">'
+        '<defs>'
+            '<clipPath id="X"><path fill="none" stroke="none" d=" M 5 5 L 15 5 L 15 15 L 5 15 L 5 5 Z"/></clipPath>'
+            '<clipPath id="X"><path fill="none" stroke="none" d=" M 5 5 L 15 5 L 15 15 L 5 15 L 5 5 Z"/></clipPath>'
+        '</defs>'
+        '<g>'
+            '<g transform="scale(1,1) translate(0.5,0.5)">'
+                '<rect fill="#FFFFFF" stroke="none" x="0" y="0" width="20" height="20"/>'
+                '<rect fill="red" stroke="none" x="5" y="5" width="10" height="10"/>'
+                '<g/>'
+                '<g clip-path="url(#X)"><g/></g>'
+                '<g clip-path="url(#X)"><g/></g>'
+                '<g/>'
+            '</g>'
+        '</g>'
+        '</svg>'
+    )
+
+    assert svg0 == svg2
+    assert svg1 == svg2
 
 def test_save_layout_html_resets_plot_dims():
     initial_height, initial_width = 200, 250