Skip to content

Commit

Permalink
Merge pull request #1492 from ioam/bkcharts
Browse files Browse the repository at this point in the history
Handled bkcharts compatibility
  • Loading branch information
jlstevens committed May 29, 2017
2 parents bd7b264 + c6a58ed commit a353cd8
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 173 deletions.
7 changes: 5 additions & 2 deletions holoviews/plotting/bokeh/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import absolute_import

import numpy as np

from ...core import (Store, Overlay, NdOverlay, Layout, AdjointLayout,
Expand All @@ -15,11 +17,12 @@
DFrame = None

from .annotation import TextPlot, LineAnnotationPlot, SplinePlot
from .bkcharts import BoxPlot
from .callbacks import Callback # noqa (API import)
from .element import OverlayPlot
from .chart import (PointPlot, CurvePlot, SpreadPlot, ErrorPlot, HistogramPlot,
SideHistogramPlot, BoxPlot, BarPlot, SpikesPlot,
SideSpikesPlot, AreaPlot, VectorFieldPlot)
SideHistogramPlot, BarPlot, SpikesPlot, SideSpikesPlot,
AreaPlot, VectorFieldPlot)
from .path import PathPlot, PolygonPlot
from .plot import GridPlot, LayoutPlot, AdjointLayoutPlot
from .raster import (RasterPlot, RGBPlot, HeatmapPlot,
Expand Down
142 changes: 142 additions & 0 deletions holoviews/plotting/bokeh/bkcharts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import param
from bokeh.models import GlyphRenderer

from ...core.options import abbreviated_exception, SkipRendering
from ...core.spaces import DynamicMap
from .element import LegendPlot, line_properties
from ..util import match_spec
from .util import update_plot, bokeh_version

try:
if bokeh_version > '0.12.5':
from bkcharts import BoxPlot as BokehBoxPlot
else:
from bokeh.charts import BoxPlot as BokehBoxPlot
except:
BokehBoxPlot = None


class ChartPlot(LegendPlot):
"""
ChartPlot creates and updates Bokeh high-level Chart instances.
The current implementation requires creating a new Chart for each
frame and updating the existing Chart. Once Bokeh supports updating
Charts directly this workaround will no longer be required.
"""

def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
"""
Initializes a new plot object with the last available frame.
"""
# Get element key and ranges for frame
element = self.hmap.last
key = self.keys[-1]
ranges = self.compute_ranges(self.hmap, key, ranges)
ranges = match_spec(element, ranges)
self.current_ranges = ranges
self.current_frame = element
self.current_key = key

# Initialize plot, source and glyph
if plot is not None:
raise Exception("Can't overlay Bokeh Charts based plot properties")

init_element = element.clone(element.interface.concat(self.hmap.values()))
with abbreviated_exception():
plot = self._init_chart(init_element, ranges)

if plot.legend:
self._process_legend(plot)

self.handles['plot'] = plot
self.handles['glyph_renderers'] = [r for r in plot.renderers
if isinstance(r, GlyphRenderer)]
if self.dynamic and not self.static:
self._update_chart(key, element, ranges)
else:
properties = self._plot_properties(key, plot, element)
plot.update(**properties)

# Update plot, source and glyph
self.drawn = True

return plot

def update_frame(self, key, ranges=None, plot=None, element=None):
"""
Updates an existing plot with data corresponding
to the key.
"""
reused = isinstance(self.hmap, DynamicMap) and (self.overlaid or self.batched)
if not reused and element is None:
element = self._get_frame(key)
elif element is not None:
self.current_key = key
self.current_frame = element

if element is None or (not self.dynamic and self.static):
return

max_cycles = len(self.style._options)
self.style = self.lookup_options(element, 'style').max_cycles(max_cycles)

self.set_param(**self.lookup_options(element, 'plot').options)
ranges = self.compute_ranges(self.hmap, key, ranges)
ranges = match_spec(element, ranges)
self.current_ranges = ranges

self._update_chart(key, element, ranges)


def _update_chart(self, key, element, ranges):
with abbreviated_exception():
new_chart = self._init_chart(element, ranges)
old_chart = self.handles['plot']
update_plot(old_chart, new_chart)
properties = self._plot_properties(key, old_chart, element)
old_chart.update(**properties)


@property
def current_handles(self):
return self.state.select(type=(ColumnDataSource, DataRange1d, Range1d))


class BoxPlot(ChartPlot):
"""
BoxPlot generates a box and whisker plot from a BoxWhisker
Element. This allows plotting the median, mean and various
percentiles.
"""

style_opts = ['whisker_color', 'marker'] + line_properties

def _init_chart(self, element, ranges):
if BokehBoxPlot is None:
raise SkipRendering('BoxPlot requires bkcharts to be installed, '
'and will be replaced with a native implementation.')
properties = self.style[self.cyclic_index]
label = element.dimensions('key', True)
dframe = element.dframe()

# Fix for displaying datetimes which are not handled by bokeh
for kd in element.kdims:
col = dframe[kd.name]
if col.dtype.kind in ('M',):
dframe[kd.name] = [kd.pprint_value(v).replace(':', ';')
for v in col]

if not element.kdims:
dframe[''] = ''
label = ['']

return BokehBoxPlot(dframe, label=label, values=element.vdims[0].name,
**properties)


def _update_chart(self, key, element, ranges):
super(BoxPlot, self)._update_chart(key, element, ranges)
vdim = element.vdims[0].name
start, end = ranges[vdim]
self.state.y_range.start = start
self.state.y_range.end = end
136 changes: 2 additions & 134 deletions holoviews/plotting/bokeh/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,11 @@
from ...core.spaces import DynamicMap
from ...element import Bars
from ...operation import interpolate_curve
from ..util import compute_sizes, match_spec, get_min_distance, dim_axis_label
from ..util import compute_sizes, get_min_distance, dim_axis_label
from .element import (ElementPlot, ColorbarPlot, LegendPlot, line_properties,
fill_properties)
from .path import PathPlot, PolygonPlot
from .util import update_plot, bokeh_version, expand_batched_style, categorize_array

try:
if bokeh_version > '0.12.5':
from bkcharts import BoxPlot as BokehBoxPlot
else:
from bokeh.charts import BoxPlot as BokehBoxPlot
except:
BokehBoxPlot = None, None
from .util import bokeh_version, expand_batched_style, categorize_array


class PointPlot(LegendPlot, ColorbarPlot):
Expand Down Expand Up @@ -576,130 +568,6 @@ class SideSpikesPlot(SpikesPlot):



class ChartPlot(LegendPlot):
"""
ChartPlot creates and updates Bokeh high-level Chart instances.
The current implementation requires creating a new Chart for each
frame and updating the existing Chart. Once Bokeh supports updating
Charts directly this workaround will no longer be required.
"""

def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
"""
Initializes a new plot object with the last available frame.
"""
# Get element key and ranges for frame
element = self.hmap.last
key = self.keys[-1]
ranges = self.compute_ranges(self.hmap, key, ranges)
ranges = match_spec(element, ranges)
self.current_ranges = ranges
self.current_frame = element
self.current_key = key

# Initialize plot, source and glyph
if plot is not None:
raise Exception("Can't overlay Bokeh Charts based plot properties")

init_element = element.clone(element.interface.concat(self.hmap.values()))
with abbreviated_exception():
plot = self._init_chart(init_element, ranges)

if plot.legend:
self._process_legend(plot)

self.handles['plot'] = plot
self.handles['glyph_renderers'] = [r for r in plot.renderers
if isinstance(r, GlyphRenderer)]
if self.dynamic and not self.static:
self._update_chart(key, element, ranges)
else:
properties = self._plot_properties(key, plot, element)
plot.update(**properties)

# Update plot, source and glyph
self.drawn = True

return plot

def update_frame(self, key, ranges=None, plot=None, element=None):
"""
Updates an existing plot with data corresponding
to the key.
"""
reused = isinstance(self.hmap, DynamicMap) and (self.overlaid or self.batched)
if not reused and element is None:
element = self._get_frame(key)
elif element is not None:
self.current_key = key
self.current_frame = element

if element is None or (not self.dynamic and self.static):
return

max_cycles = len(self.style._options)
self.style = self.lookup_options(element, 'style').max_cycles(max_cycles)

self.set_param(**self.lookup_options(element, 'plot').options)
ranges = self.compute_ranges(self.hmap, key, ranges)
ranges = match_spec(element, ranges)
self.current_ranges = ranges

self._update_chart(key, element, ranges)


def _update_chart(self, key, element, ranges):
with abbreviated_exception():
new_chart = self._init_chart(element, ranges)
old_chart = self.handles['plot']
update_plot(old_chart, new_chart)
properties = self._plot_properties(key, old_chart, element)
old_chart.update(**properties)


@property
def current_handles(self):
return self.state.select(type=(ColumnDataSource, DataRange1d, Range1d))


class BoxPlot(ChartPlot):
"""
BoxPlot generates a box and whisker plot from a BoxWhisker
Element. This allows plotting the median, mean and various
percentiles.
"""

style_opts = ['whisker_color', 'marker'] + line_properties

def _init_chart(self, element, ranges):
properties = self.style[self.cyclic_index]
label = element.dimensions('key', True)
dframe = element.dframe()

# Fix for displaying datetimes which are not handled by bokeh
for kd in element.kdims:
col = dframe[kd.name]
if col.dtype.kind in ('M',):
dframe[kd.name] = [kd.pprint_value(v).replace(':', ';')
for v in col]

if not element.kdims:
dframe[''] = ''
label = ['']

return BokehBoxPlot(dframe, label=label, values=element.vdims[0].name,
**properties)


def _update_chart(self, key, element, ranges):
super(BoxPlot, self)._update_chart(key, element, ranges)
vdim = element.vdims[0].name
start, end = ranges[vdim]
self.state.y_range.start = start
self.state.y_range.end = end



class BarPlot(ColorbarPlot, LegendPlot):
"""
BarPlot allows generating single- or multi-category
Expand Down
42 changes: 10 additions & 32 deletions holoviews/plotting/bokeh/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,17 @@
from bokeh.document import Document
from bokeh.embed import notebook_div, autoload_server
from bokeh.io import load_notebook, curdoc, show as bkshow
from bokeh.models import (Row, Column, Plot, Model, ToolbarBox,
WidgetBox, Div, DataTable, Tabs)
from bokeh.plotting import Figure
from bokeh.models import Model
from bokeh.resources import CDN, INLINE
from bokeh.server.server import Server

from ...core import Store, HoloMap
from ..comms import JupyterComm, Comm
from ..plot import GenericElementPlot
from ..plot import Plot, GenericElementPlot
from ..renderer import Renderer, MIME_TYPES
from .widgets import BokehScrubberWidget, BokehSelectionWidget, BokehServerWidgets
from .util import compute_static_patch, serialize_json, attach_periodic, bokeh_version

try:
if bokeh_version > '0.12.5':
from bkcharts import Chart
else:
from bokeh.charts import Chart
except:
Chart = None
from .util import (compute_static_patch, serialize_json, attach_periodic,
bokeh_version, compute_plot_size)


class BokehRenderer(Renderer):
Expand Down Expand Up @@ -237,26 +228,13 @@ def get_size(self_or_cls, plot):
Returns a tuple of (width, height) in pixels.
"""
if not isinstance(plot, Model):
if isinstance(plot, Plot):
plot = plot.state
if isinstance(plot, Div):
# Cannot compute size for Div
return 0, 0
elif isinstance(plot, (Row, Column, ToolbarBox, WidgetBox, Tabs)):
if not plot.children: return 0, 0
if isinstance(plot, Row) or (isinstance(plot, ToolbarBox) and plot.toolbar_location not in ['right', 'left']):
w_agg, h_agg = (np.sum, np.max)
elif isinstance(plot, Tabs):
w_agg, h_agg = (np.max, np.max)
else:
w_agg, h_agg = (np.max, np.sum)
widths, heights = zip(*[self_or_cls.get_size(child) for child in plot.children])
width, height = w_agg(widths), h_agg(heights)
elif isinstance(plot, (Chart, Figure)):
width, height = plot.plot_width, plot.plot_height
elif isinstance(plot, (Plot, DataTable)):
width, height = plot.width, plot.height
return width, height
elif not isinstance(plot, Model):
raise ValueError('Can only compute sizes for HoloViews '
'and bokeh plot objects.')
return compute_plot_size(plot)


@classmethod
def load_nb(cls, inline=True):
Expand Down

0 comments on commit a353cd8

Please sign in to comment.