Repository: bokeh/bokeh

Issue:
add source parameter to Span or create SpanSet
This is not a bug but rather a feature request.

The `Label` text annotation currently has a very useful corresponding `LabelSet` companion that allows to define a `ColumnDataFrame` as `source` for the text boxes.

It would be helpful to have an equivalent `SpanSet` object to draw multiple spans from a `ColumnDataFrame`. Currently, to arrive at the same result one has to hack one's way using `ray` objects bound to a data frame.

Current way:
```python
# rays used as spans, y is a large negative number for vertical spans
p.ray(x='x', y='y', color='firebrick', length=0, angle=90, angle_units='deg', alpha='alpha', source=ray_source)
```

Proposed way:
```python
spans=SpanSet(location='x', dimension='height', line_color='firebrick', alpha='alpha', source=span_source)
p.add_layout(spans)
```

```


Discussion:
This seems reasonable. Checking, do you have the ability and interest to work this up in a PR? The `LabelSet` code should be a fairly reasonable comparison and we are happy to answey any dev-related questions. 
ping @agilly sorry I should have used your GH name in the previous message to notify you
Sorry for the delay. Interest, certainly; ability, not sure. This would certainly be a first. It might take some time until I get my bearings. So far I can see 3 files that would need to be modified/created:

- **bokehjs/src/coffee/models/annotations/index.ts**, modified with something like `export {SpanSet} from "./span_set"`
- **bokehjs/src/coffee/models/annotations/span_set.coffee**, which should be modelled after `label_set.coffee`
- **bokeh/models/annotations.py**, for the class definition.

Is there anything else (apart from the docs)? 
@agilly Yes that's pretty much it for the implementation. The PR would need also need a basic "test the default values" test like this:

https://github.com/bokeh/bokeh/blob/master/bokeh/models/tests/test_annotations.py#L223

I'm happy to advise or help contribute on any or all of those (including the docs which is just a line to add things to the refguide and a small section in the users guide)
Any update on this? Would be quite helpful to me
@behinger if there were any update, it would be here. Resources are very limited right now, we are happy to help/advise anyone who would like to work on a PR earlier than the core team is able to look at it. 

Resolving patch:
diff --git a/bokeh/models/annotations.py b/bokeh/models/annotations.py
--- a/bokeh/models/annotations.py
+++ b/bokeh/models/annotations.py
@@ -96,6 +96,7 @@
     'PolyAnnotation',
     'Slope',
     'Span',
+    'SpanSet',
     'TextAnnotation',
     'Title',
     'Tooltip',
@@ -999,6 +1000,56 @@ class Span(Annotation):
     The %s values for the span.
     """)
 
+class SpanSet(Annotation):
+    """ Render multiple horizontal or vertical line spans.
+
+    """
+
+    location = NumberSpec(help="""
+    The location of the spans, along ``dimension``.
+
+    Datetime values are also accepted, but note that they are immediately
+    converted to milliseconds-since-epoch.
+    """).accepts(Datetime, convert_datetime_type)
+
+    location_units = Enum(SpatialUnits, default='data', help="""
+    The unit type for the location attribute. Interpreted as "data space"
+    units by default.
+    """)
+
+    dimension = Enum(Dimension, default='width', help="""
+    The direction of the span can be specified by setting this property
+    to "height" (``y`` direction) or "width" (``x`` direction).
+    """)
+
+    x_range_name = String('default', help="""
+    A particular (named) x-range to use for computing screen locations when
+    rendering annotations on the plot. If unset, use the default x-range.
+    """)
+
+    y_range_name = String('default', help="""
+    A particular (named) y-range to use for computing screen locations when
+    rendering annotations on the plot. If unset, use the default y-range.
+    """)
+
+    source = Instance(DataSource, default=lambda: ColumnDataSource(), help="""
+    Local data source to use when rendering annotations on the plot.
+    """)
+
+    render_mode = Enum(RenderMode, default="canvas", help="""
+    Specifies whether the span is rendered as a canvas element or as a
+    CSS element overlaid on the canvas. The default mode is "canvas".
+
+    .. warning::
+        The line_dash and line_dash_offset attributes aren't supported if
+        the render_mode is set to "css"
+
+    """)
+
+    line_props = Include(ScalarLineProps, use_prefix=False, help="""
+    The %s values for the span.
+    """)
+
 class Title(TextAnnotation):
     ''' Render a single title box as an annotation.
 


Test patch:
diff --git a/tests/unit/bokeh/models/test_annotations.py b/tests/unit/bokeh/models/test_annotations.py
--- a/tests/unit/bokeh/models/test_annotations.py
+++ b/tests/unit/bokeh/models/test_annotations.py
@@ -50,6 +50,7 @@
     LegendItem,
     Slope,
     Span,
+    SpanSet,
     Title,
     Whisker,
 )
@@ -382,6 +383,28 @@ def test_Span_accepts_datetime_location() -> None:
     obj = Span(location = datetime(2018,8,7,0,0))
     assert obj.location == 1533600000000.0
 
+def test_SpanSet() -> None:
+    span_set = SpanSet()
+    assert span_set.location is None
+    assert span_set.location_units == 'data'
+    assert span_set.dimension == 'width'
+    assert span_set.x_range_name == 'default'
+    assert span_set.y_range_name == 'default'
+    assert span_set.level == 'annotation'
+    assert span_set.render_mode == 'canvas'
+    check_line_properties(span_set, "", 'black', 1.0)
+    check_properties_existence(span_set, [
+        "visible",
+        "location",
+        "location_units",
+        "dimension",
+        "x_range_name",
+        "y_range_name",
+        "level",
+        "render_mode",
+        "source"
+    ], LINE)
+
 def test_Title() -> None:
     title = Title()
     assert title.level == 'annotation'