Repository: bokeh/bokeh

Issue:
Issue with figure and legend when a dataframe is passed as a source
If you want a legend in your line when you pass a dataframe as a source, you will get a `ValueError`

`ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().`


**Code**

```
d = {'col1': [1, 2], 'col2': [3, 4], 'label' : ['something', 'something']}
df = pd.DataFrame(data=d)


trend = figure( tools="pan,box_zoom,reset,save",title="trends")

trend.line(source = df, x ='col1', y = 'col2', line_color="red", legend ='label')

show(p)
```


The issue can be fixed by creating a `ColumnDataSource`:


```
import pandas as pd
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.io import output_file, show

d = {'col1': [1, 2], 'col2': [3, 4], 'label' : ['something', 'something']}
df = pd.DataFrame(data=d)

p = figure( tools="pan,box_zoom,reset,save",title="trends")

source = ColumnDataSource(df)
p.line(source=source, x ='col1', y = 'col2', line_color="red", legend ='label')

show(p)

```



Discussion:
Just stumbled across this problem too on `0.12.13` (`Python 3.6.3 :: Anaconda custom (64-bit)`).

Error happens here in the code of Bokeh (`plotting/helper.py`):
```
def _get_legend_item_label(kwargs):
    legend = kwargs.pop('legend', None)
    source = kwargs.get('source')
    legend_item_label = None
    if legend:
        if isinstance(legend, string_types):
            # Do the simple thing first
            legend_item_label = value(legend)
            # But if there's a source - try and do something smart
            if source and hasattr(source, 'column_names'):
                if legend in source.column_names:
                    legend_item_label = field(legend)
        else:
            legend_item_label = legend
    return legend_item_label
```

In this line `source` is tried to convert to a truthy value:
` if source and hasattr(source, 'column_names'):`

Resolving patch:
diff --git a/bokeh/plotting/helpers.py b/bokeh/plotting/helpers.py
--- a/bokeh/plotting/helpers.py
+++ b/bokeh/plotting/helpers.py
@@ -246,7 +246,7 @@ def _get_legend_item_label(kwargs):
             # Do the simple thing first
             legend_item_label = value(legend)
             # But if there's a source - try and do something smart
-            if source and hasattr(source, 'column_names'):
+            if source is not None and hasattr(source, 'column_names'):
                 if legend in source.column_names:
                     legend_item_label = field(legend)
         else:
@@ -699,26 +699,30 @@ def _glyph_function(glyphclass, extra_docs=None):
 
     def func(self, **kwargs):
 
+        # Convert data source, if necesary
+        is_user_source = kwargs.get('source', None) is not None
+        if is_user_source:
+            source = kwargs['source']
+            if not isinstance(source, ColumnarDataSource):
+                try:
+                    # try converting the soruce to ColumnDataSource
+                    source = ColumnDataSource(source)
+                except ValueError as err:
+                    msg = "Failed to auto-convert {curr_type} to ColumnDataSource.\n Original error: {err}".format(
+                        curr_type=str(type(source)),
+                        err=err.message
+                    )
+                    reraise(ValueError, ValueError(msg), sys.exc_info()[2])
+
+                # update reddered_kws so that others can use the new source
+                kwargs['source'] = source
+
         # Process legend kwargs and remove legend before we get going
         legend_item_label = _get_legend_item_label(kwargs)
 
         # Need to check if user source is present before _pop_renderer_args
-        is_user_source = kwargs.get('source', None) is not None
         renderer_kws = _pop_renderer_args(kwargs)
         source = renderer_kws['data_source']
-        if not isinstance(source, ColumnarDataSource):
-            try:
-                # try converting the soruce to ColumnDataSource
-                source = ColumnDataSource(source)
-            except ValueError as err:
-                msg = "Failed to auto-convert {curr_type} to ColumnDataSource.\n Original error: {err}".format(
-                    curr_type=str(type(source)),
-                    err=err.message
-                )
-                reraise(ValueError, ValueError(msg), sys.exc_info()[2])
-
-            # update reddered_kws so that others can use the new source
-            renderer_kws['data_source'] = source
 
         # handle the main glyph, need to process literals
         glyph_ca = _pop_colors_and_alpha(glyphclass, kwargs)


Test patch:
diff --git a/bokeh/plotting/tests/test_figure.py b/bokeh/plotting/tests/test_figure.py
--- a/bokeh/plotting/tests/test_figure.py
+++ b/bokeh/plotting/tests/test_figure.py
@@ -271,19 +271,33 @@ def p():
     return plt.figure()
 
 
-def test_glyph_label_is_legend_if_column_in_datasouurce_is_added_as_legend(p, source):
+def test_glyph_label_is_legend_if_column_in_datasource_is_added_as_legend(p, source):
     p.circle(x='x', y='y', legend='label', source=source)
     legends = p.select(Legend)
     assert len(legends) == 1
     assert legends[0].items[0].label == {'field': 'label'}
 
 
-def test_glyph_label_is_value_if_column_not_in_datasouurce_is_added_as_legend(p, source):
+def test_glyph_label_is_value_if_column_not_in_datasource_is_added_as_legend(p, source):
     p.circle(x='x', y='y', legend='milk', source=source)
     legends = p.select(Legend)
     assert len(legends) == 1
     assert legends[0].items[0].label == {'value': 'milk'}
 
+def test_glyph_label_is_legend_if_column_in_df_datasource_is_added_as_legend(p):
+    source = pd.DataFrame(data=dict(x=[1, 2, 3], y=[1, 2, 3], label=['a', 'b', 'c']))
+    p.circle(x='x', y='y', legend='label', source=source)
+    legends = p.select(Legend)
+    assert len(legends) == 1
+    assert legends[0].items[0].label == {'field': 'label'}
+
+
+def test_glyph_label_is_value_if_column_not_in_df_datasource_is_added_as_legend(p):
+    source = pd.DataFrame(data=dict(x=[1, 2, 3], y=[1, 2, 3], label=['a', 'b', 'c']))
+    p.circle(x='x', y='y', legend='milk', source=source)
+    legends = p.select(Legend)
+    assert len(legends) == 1
+    assert legends[0].items[0].label == {'value': 'milk'}
 
 def test_glyph_label_is_just_added_directly_if_not_string(p, source):
     p.circle(x='x', y='y', legend={'field': 'milk'}, source=source)
@@ -347,3 +361,16 @@ def test_compound_legend_behavior_initiated_if_labels_are_same_on_multiple_rende
     assert len(legends) == 1
     assert legends[0].items[0].renderers == [square, circle]
     assert legends[0].items[0].label == {'field': 'label'}
+
+# XXX (bev) this doesn't work yet because compound behaviour depends on renderer sources
+# matching, but passing a df means every renderer gets its own new source
+# def test_compound_legend_behavior_initiated_if_labels_are_same_on_multiple_renderers_and_are_field_with_df_source(p):
+#     source = pd.DataFrame(data=dict(x=[1, 2, 3], y=[1, 2, 3], label=['a', 'b', 'c']))
+#     # label is a field
+#     square = p.square(x='x', y='y', legend='label', source=source)
+#     circle = p.circle(x='x', y='y', legend='label', source=source)
+#     legends = p.select(Legend)
+#     assert len(legends) == 1
+#     print(legends[0].items[0].renderers)
+#     assert legends[0].items[0].renderers == [square, circle]
+#     assert legends[0].items[0].label == {'field': 'label'}
diff --git a/bokeh/plotting/tests/test_helpers.py b/bokeh/plotting/tests/test_helpers.py
--- a/bokeh/plotting/tests/test_helpers.py
+++ b/bokeh/plotting/tests/test_helpers.py
@@ -7,10 +7,49 @@
 from bokeh.models.ranges import Range1d, DataRange1d, FactorRange
 from bokeh.models.scales import LinearScale, LogScale, CategoricalScale
 from bokeh.plotting import Figure
-from bokeh.plotting.helpers import (_get_legend_item_label, _get_scale,
-                                    _get_range, _stack, _graph, _glyph_function,
-                                    _RENDERER_ARGS)
-
+from bokeh.plotting.helpers import _get_scale,_get_range, _stack, _graph, _glyph_function, _RENDERER_ARGS
+
+import bokeh.plotting.helpers as bph
+
+class Test__get_legend_item_label(object):
+
+    def test_legend_None(self):
+        kwargs = {
+            'legend': None
+        }
+        assert bph._get_legend_item_label(kwargs) is None
+
+
+    def test_if_legend_is_something_exotic_that_it_is_passed_directly_to_label(self):
+        kwargs = {
+            'legend': {'field': 'milk'}
+        }
+        label = bph._get_legend_item_label(kwargs)
+        assert label == {'field': 'milk'}
+
+    def test_if_legend_is_a_string_but_no_source_then_label_is_set_as_value(self):
+        kwargs = {
+            'legend': 'label'
+        }
+        label = bph._get_legend_item_label(kwargs)
+        assert label == {'value': 'label'}
+
+    def test_if_legend_is_a_string_and_source_with_that_column_then_field(self):
+        kwargs = {
+            'legend': 'label',
+            'source': ColumnDataSource(dict(label=[1, 2]))
+        }
+        label = bph._get_legend_item_label(kwargs)
+        assert label == {'field': 'label'}
+
+
+    def test_if_legend_is_a_string_and_source_without_column_name_then_value(self):
+        kwargs = {
+            'legend': 'not_a_column_label',
+            'source': ColumnDataSource(dict(label=[1, 2]))
+        }
+        label = bph._get_legend_item_label(kwargs)
+        assert label == {'value': 'not_a_column_label'}
 
 def test__stack_raises_when_spec_in_kwargs():
     with pytest.raises(ValueError) as e:
@@ -125,40 +164,6 @@ def test__graph_properly_handle_edge_property_mixins():
     assert r.hover_glyph.line_width == 23
     assert r.muted_glyph.line_width == 23
 
-# _get_legend_item_label
-def test_if_legend_is_something_exotic_that_it_is_passed_directly_to_label():
-    kwargs = {
-        'legend': {'field': 'milk'}
-    }
-    label = _get_legend_item_label(kwargs)
-    assert label == {'field': 'milk'}
-
-
-def test_if_legend_is_a_string_but_no_source_then_label_is_set_as_value():
-    kwargs = {
-        'legend': 'label'
-    }
-    label = _get_legend_item_label(kwargs)
-    assert label == {'value': 'label'}
-
-
-def test_if_legend_is_a_string_and_source_with_that_column_then_field():
-    kwargs = {
-        'legend': 'label',
-        'source': ColumnDataSource(dict(label=[1, 2]))
-    }
-    label = _get_legend_item_label(kwargs)
-    assert label == {'field': 'label'}
-
-
-def test_if_legend_is_a_string_and_source_without_column_name_then_value():
-    kwargs = {
-        'legend': 'not_a_column_label',
-        'source': ColumnDataSource(dict(label=[1, 2]))
-    }
-    label = _get_legend_item_label(kwargs)
-    assert label == {'value': 'not_a_column_label'}
-
 def test__get_scale_numeric_range_linear_axis():
     s = _get_scale(Range1d(), "linear")
     assert isinstance(s, LinearScale)