Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ArrowPlot implementation for bokeh #1608

Merged
merged 2 commits into from
Jun 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions holoviews/plotting/bokeh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
GridSpace, GridMatrix, NdLayout, config)
from ...element import (Curve, Points, Scatter, Image, Raster, Path,
RGB, Histogram, Spread, HeatMap, Contours, Bars,
Box, Bounds, Ellipse, Polygons, BoxWhisker,
Box, Bounds, Ellipse, Polygons, BoxWhisker, Arrow,
ErrorBars, Text, HLine, VLine, Spline, Spikes,
Table, ItemTable, Area, HSV, QuadMesh, VectorField)
from ...core.options import Options, Cycle, Palette
Expand All @@ -17,7 +17,7 @@
except:
DFrame = None

from .annotation import TextPlot, LineAnnotationPlot, SplinePlot
from .annotation import TextPlot, LineAnnotationPlot, SplinePlot, ArrowPlot
from .callbacks import Callback # noqa (API import)
from .element import OverlayPlot, ElementPlot
from .chart import (PointPlot, CurvePlot, SpreadPlot, ErrorPlot, HistogramPlot,
Expand Down Expand Up @@ -79,6 +79,7 @@
VLine: LineAnnotationPlot,
Text: TextPlot,
Spline: SplinePlot,
Arrow: ArrowPlot,

# Tabular
Table: TablePlot,
Expand Down Expand Up @@ -150,6 +151,7 @@ def colormap_generator(palette):
# Annotations
options.HLine = Options('style', color=Cycle(), line_width=3, alpha=1)
options.VLine = Options('style', color=Cycle(), line_width=3, alpha=1)
options.Arrow = Options('style', arrow_size=10)

# Define composite defaults
options.GridMatrix = Options('plot', shared_xaxis=True, shared_yaxis=True,
Expand Down
84 changes: 82 additions & 2 deletions holoviews/plotting/bokeh/annotation.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
from collections import defaultdict

import numpy as np
from bokeh.models import Span
from bokeh.models import Span, Arrow
try:
from bokeh.models.arrow_heads import TeeHead, NormalHead
arrow_start = {'<->': NormalHead, '<|-|>': NormalHead}
arrow_end = {'->': NormalHead, '-[': TeeHead, '-|>': NormalHead, '-': None}
except:
from bokeh.models.arrow_heads import OpenHead, NormalHead
arrow_start = {'<->': NormalHead, '<|-|>': NormalHead}
arrow_end = {'->': NormalHead, '-[': OpenHead, '-|>': NormalHead, '-': None}

from ...element import HLine
from .element import ElementPlot, text_properties, line_properties
from .element import ElementPlot, CompositeElementPlot, text_properties, line_properties


class TextPlot(ElementPlot):
Expand Down Expand Up @@ -104,3 +112,75 @@ def get_data(self, element, ranges=None, empty=False):
'unsupported splines were skipped during plotting.')
data = {da: data[da] for da in data_attrs}
return (data, dict(zip(data_attrs, data_attrs)))



class ArrowPlot(CompositeElementPlot):

style_opts = (['arrow_%s' % p for p in line_properties+['size']] + text_properties)

_style_groups = {'arrow': 'arrow', 'label': 'text'}

_update_handles = ['glyph']

_plot_methods = dict(single='text')

def get_data(self, element, ranges=None, empty=False):
plot = self.state
label_mapping = dict(x='x', y='y', text='text')

# Compute arrow
x1, y1 = element.x, element.y
axrange = plot.x_range if self.invert_axes else plot.y_range
span = (axrange.end - axrange.start) / 6.
if element.direction == '^':
x2, y2 = x1, y1-span
label_mapping['text_baseline'] = 'top'
elif element.direction == '<':
x2, y2 = x1+span, y1
label_mapping['text_align'] = 'left'
label_mapping['text_baseline'] = 'middle'
elif element.direction == '>':
x2, y2 = x1-span, y1
label_mapping['text_align'] = 'right'
label_mapping['text_baseline'] = 'middle'
else:
x2, y2 = x1, y1+span
label_mapping['text_baseline'] = 'bottom'
arrow_opts = {'x_end': x1, 'y_end': y1,
'x_start': x2, 'y_start': y2}

# Define arrowhead
arrow_opts['arrow_start'] = arrow_start.get(element.arrowstyle, None)
arrow_opts['arrow_end'] = arrow_end.get(element.arrowstyle, NormalHead)

# Compute label
if self.invert_axes:
label_data = dict(x=[y2], y=[x2])
else:
label_data = dict(x=[x2], y=[y2])
label_data['text'] = [element.text]
return ({'label': label_data},
{'arrow': arrow_opts, 'label': label_mapping})

def _init_glyph(self, plot, mapping, properties, key):
"""
Returns a Bokeh glyph object.
"""
properties.pop('legend')
if key == 'arrow':
properties.pop('source')
arrow_end = mapping.pop('arrow_end')
arrow_start = mapping.pop('arrow_start')
start = arrow_start(**properties) if arrow_start else None
end = arrow_end(**properties) if arrow_end else None
glyph = Arrow(start=start, end=end, **dict(**mapping))
else:
properties = {p if p == 'source' else 'text_'+p: v
for p, v in properties.items()}
glyph, _ = super(ArrowPlot, self)._init_glyph(plot, mapping, properties, 'text')
plot.renderers.append(glyph)
return None, glyph

def get_extents(self, element, ranges=None):
return None, None, None, None
9 changes: 5 additions & 4 deletions holoviews/plotting/bokeh/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def _hover_opts(self, element):
dims += element.dimensions()
return list(util.unique_iterator(dims)), {}


def _init_tools(self, element, callbacks=[]):
"""
Processes the list of tools to be supplied to the plot.
Expand Down Expand Up @@ -960,13 +961,13 @@ def _init_glyphs(self, plot, element, ranges, source):
current_id = element._plot_id

self.handles['previous_id'] = current_id
for key, gdata in data.items():
source = self._init_datasource(gdata)
for key in dict(mapping, **data):
source = self._init_datasource(data.get(key, {}))
self.handles[key+'_source'] = source
properties = self._glyph_properties(plot, element, source, ranges)
properties = self._process_properties(key, properties)
with abbreviated_exception():
renderer, glyph = self._init_glyph(plot, mapping[key], properties, key)
renderer, glyph = self._init_glyph(plot, mapping.get(key, {}), properties, key)
self.handles[key+'_glyph'] = glyph
if isinstance(renderer, Renderer):
self.handles[key+'glyph_renderer'] = renderer
Expand All @@ -975,7 +976,7 @@ def _init_glyphs(self, plot, element, ranges, source):

# Update plot, source and glyph
with abbreviated_exception():
self._update_glyph(renderer, properties, mapping[key], glyph)
self._update_glyph(renderer, properties, mapping.get(key, {}), glyph)


def _process_properties(self, key, properties):
Expand Down