Repository: bokeh/bokeh

Issue:
Charts and plots not rendering with user defined title text_font_size in em
As the title states, If I add a defined font size to a title on a plot or a chart, that plot and/or chart does not render in Bokeh 0.12.3. This is a pretty large issue. Reproducible below:

```
#!/usr/bin/python
# -*- coding: utf-8 -*-
from bokeh import plotting
from bokeh.models import ColumnDataSource
from bokeh.io import curdoc, show
import pandas as pd

data = [{"1": 10, "2": 4, "3": 3,
         "4": 5, "5": 15, "6": 7,
         "7": 5, "8": 10}]

source = pd.DataFrame(data)
source = ColumnDataSource(source)

fig = plotting.figure(width=1200, height=900, title="BL-55-T55555")

fig.vbar(top="1", x=1, width=0.5, fill_color="blue", line_color="black", source=source)
fig.vbar(top="2", x=2, width=0.5, fill_color="green", line_color="black", source=source)
fig.vbar(top="3", x=3, width=0.5, fill_color="yellow", line_color="black", source=source)
fig.vbar(top="4", x=4, width=0.5, fill_color="orange", line_color="black", source=source)
fig.vbar(top="5", x=5, width=0.5, fill_color="red", line_color="black", source=source)
fig.vbar(top="6", x=6, width=0.5, fill_color="purple", line_color="black", source=source)
fig.vbar(top="7", x=7, width=0.5, fill_color="grey", line_color="black", source=source)
fig.vbar(top="8", x=8, width=0.5, color="black", source=source)

fig.title.text_font_size = '1.9em'

curdoc().add_root(fig)

show(fig)
```



Discussion:
@Rubyj, the issue is confined only to font sizes expressed in `em`, and there is a simple workaround for now:

```
fig.title.text_font_size = dict(value='1.9em')
```

ping @mattpap seems that `em` needs to be added to this expression? 

```
_font_size_re = re.compile("^[0-9]+(\.[0-9]+)?(ex|px|cm|mm|in|pt|pc)$")
```

@bryevdv Confirmed the workaround indeed works for now. Thanks.

@mattpap we should add a test when we fix this. 


Resolving patch:
diff --git a/bokeh/core/properties.py b/bokeh/core/properties.py
--- a/bokeh/core/properties.py
+++ b/bokeh/core/properties.py
@@ -1795,8 +1795,17 @@ class FontSizeSpec(DataSpec):
     ''' A DataSpec property that can be set to a font size fixed value,
     or a data source column name referring to column of font size data.
 
+    ``FontSizeSpec`` tries to determine if the a string value is a valid
+    CSS unit of length, e.g ``"10pt"`` or ``"1.5em"``. If the string can be
+    interpreted as a CSS length, then the DataSpec is a value spec.
+    Otherwise, setting a string value will result in a field spec.
+
+    A full list of all valid CSS length units can be found here:
+
+    https://drafts.csswg.org/css-values/#lengths
+
     '''
-    _font_size_re = re.compile("^[0-9]+(\.[0-9]+)?(ex|px|cm|mm|in|pt|pc)$")
+    _font_size_re = re.compile("^[0-9]+(\.[0-9]+)?(%|em|ex|ch|ic|rem|vw|vh|vi|vb|vmin|vmax|cm|mm|q|in|pc|pt|px)$", re.I)
 
     def __init__(self, default, help=None):
         super(FontSizeSpec, self).__init__(List(String), default=default, help=help)


Test patch:
diff --git a/bokeh/core/tests/test_properties.py b/bokeh/core/tests/test_properties.py
--- a/bokeh/core/tests/test_properties.py
+++ b/bokeh/core/tests/test_properties.py
@@ -481,14 +481,53 @@ def test_font_size_from_string(self):
         class Foo(HasProps):
             x = FontSizeSpec(default=None)
 
+        css_units = "%|em|ex|ch|ic|rem|vw|vh|vi|vb|vmin|vmax|cm|mm|q|in|pc|pt|px"
+
         a = Foo()
         self.assertIs(a.x, None)
-        a.x = '10pt'
-        self.assertEqual(a.x, dict(value='10pt'))
-        self.assertEqual(a.lookup('x').serializable_value(a), dict(value='10pt'))
-        a.x = '_10pt'
-        self.assertEqual(a.x, '_10pt')
-        self.assertEqual(a.lookup('x').serializable_value(a), dict(field='_10pt'))
+
+        for unit in css_units.split("|"):
+
+            v = '10%s' % unit
+            a.x = v
+            self.assertEqual(a.x, dict(value=v))
+            self.assertEqual(a.lookup('x').serializable_value(a), dict(value=v))
+
+            v = '10.2%s' % unit
+            a.x = v
+            self.assertEqual(a.x, dict(value=v))
+            self.assertEqual(a.lookup('x').serializable_value(a), dict(value=v))
+
+            f = '_10%s' % unit
+            a.x = f
+            self.assertEqual(a.x, f)
+            self.assertEqual(a.lookup('x').serializable_value(a), dict(field=f))
+
+            f = '_10.2%s' % unit
+            a.x = f
+            self.assertEqual(a.x, f)
+            self.assertEqual(a.lookup('x').serializable_value(a), dict(field=f))
+
+        for unit in css_units.upper().split("|"):
+            v = '10%s' % unit
+            a.x = v
+            self.assertEqual(a.x, dict(value=v))
+            self.assertEqual(a.lookup('x').serializable_value(a), dict(value=v))
+
+            v = '10.2%s' % unit
+            a.x = v
+            self.assertEqual(a.x, dict(value=v))
+            self.assertEqual(a.lookup('x').serializable_value(a), dict(value=v))
+
+            f = '_10%s' % unit
+            a.x = f
+            self.assertEqual(a.x, f)
+            self.assertEqual(a.lookup('x').serializable_value(a), dict(field=f))
+
+            f = '_10.2%s' % unit
+            a.x = f
+            self.assertEqual(a.x, f)
+            self.assertEqual(a.lookup('x').serializable_value(a), dict(field=f))
 
 class TestAngleSpec(unittest.TestCase):
     def test_default_none(self):