Skip to content

Commit

Permalink
Various fixes along with updates to gallery apps (#4467)
Browse files Browse the repository at this point in the history
* Update apps

* Various fixes for ranges, throttling and doc builds
  • Loading branch information
philippjfr committed Jun 9, 2020
1 parent 33f1538 commit 4cd6651
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 65 deletions.
33 changes: 25 additions & 8 deletions examples/gallery/apps/bokeh/game_of_life.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import numpy as np
import holoviews as hv
import panel as pn

from holoviews import opts
from holoviews.streams import Tap, Counter
from holoviews.streams import Tap, Counter, DoubleTap
from scipy.signal import convolve2d

renderer = hv.renderer('bokeh')
hv.extension('bokeh')

diehard = [[0, 0, 0, 0, 0, 0, 1, 0],
[1, 1, 0, 0, 0, 0, 0, 0],
Expand Down Expand Up @@ -66,14 +67,30 @@ def update(pattern, counter, x, y):
img.data = step(img.data)
return hv.Image(img)

# Set up plot which advances on counter and adds pattern on tap
title = 'Game of Life - Tap to place pattern, Doubletap to clear'
img_opts = opts.Image(cmap='gray', toolbar=None, height=400, width=800,
title=title, xaxis=None, yaxis=None)
img = hv.Image(np.zeros((100, 200), dtype=np.uint8))
counter, tap = Counter(transient=True), Tap(transient=True)
counter, tap = Counter(transient=True), Tap(transient=True),
pattern_dim = hv.Dimension('Pattern', values=sorted(shapes.keys()))
dmap = hv.DynamicMap(update, kdims=[pattern_dim], streams=[counter, tap])

doc = renderer.server_doc(dmap.redim.range(z=(0, 1)).opts(img_opts))
dmap.periodic(0.05, None)
doc.title = 'Game of Life'
plot = dmap.opts(
opts.Image(cmap='gray', clim=(0, 1), toolbar=None, responsive=True,
min_height=800, title=title, xaxis=None, yaxis=None)
)

# Add callback to clear on double tap
def reset_data(x, y):
img.data[:] = 0

reset = DoubleTap(transient=True, source=plot)
reset.add_subscriber(reset_data)

# Set up Panel app and periodic callback
panel = pn.pane.HoloViews(plot, center=True, widget_location='right')

def advance():
counter.event(counter=counter.counter+1)
panel.add_periodic_callback(advance, 50)

panel.servable('Game of Life')
70 changes: 33 additions & 37 deletions examples/gallery/apps/bokeh/gapminder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,27 @@
import pandas as pd
import numpy as np
import holoviews as hv
import panel as pn

from bokeh.io import curdoc
from bokeh.layouts import layout
from bokeh.models import Slider, Button
from bokeh.sampledata import gapminder
from holoviews import dim, opts

renderer = hv.renderer('bokeh')

# Declare dataset
panel = pd.Panel({'Fertility': gapminder.fertility,
'Population': gapminder.population,
'Life expectancy': gapminder.life_expectancy})
gapminder_df = panel.to_frame().reset_index().rename(columns={'minor': 'Year'})
gapminder_df = gapminder_df.merge(gapminder.regions.reset_index(), on='Country')
gapminder_df['Country'] = gapminder_df['Country'].astype('str')
gapminder_df['Group'] = gapminder_df['Group'].astype('str')
gapminder_df.Year = gapminder_df.Year.astype('f')
fertility = gapminder.fertility.reset_index().melt(id_vars='Country', var_name='Year', value_name='Fertility')
population = gapminder.population.reset_index().melt(id_vars='Country', var_name='Year', value_name='Population')
life_expectancy = gapminder.life_expectancy.reset_index().melt(id_vars='Country', var_name='Year', value_name='Life Expectancy')
gapminder_df = pd.merge(pd.merge(pd.merge(fertility, population), life_expectancy), gapminder.regions, on='Country')
gapminder_df.Year = gapminder_df.Year.astype('int')
ds = hv.Dataset(gapminder_df)

# Apply dimension labels and ranges
kdims = ['Fertility', 'Life expectancy']
kdims = ['Fertility', 'Life Expectancy']
vdims = ['Country', 'Population', 'Group']
dimensions = {
'Fertility' : dict(label='Children per woman (total fertility)', range=(0, 10)),
'Life expectancy': dict(label='Life expectancy at birth (years)', range=(15, 100)),
'Life Expectancy': dict(label='Life expectancy at birth (years)', range=(15, 100)),
'Population': ('population', 'Population')
}

Expand All @@ -46,45 +41,46 @@
# Combine Points and Text
hvgapminder = (gapminder_ds * text).opts(
opts.Points(alpha=0.6, color='Group', cmap='Set1', line_color='black',
size=np.sqrt(dim('Population'))*0.005, width=1000, height=600,
tools=['hover'], title='Gapminder Demo'),
opts.Text(text_font_size='52pt', text_color='lightgray'))

size=np.sqrt(dim('Population'))*0.005,
tools=['hover'], title='Gapminder Demo', responsive=True,
show_grid=True),
opts.Text(text_font_size='52pt', text_color='lightgray')
)

# Define custom widgets
def animate_update():
year = slider.value + 1
if year > end:
year = start
year = int(start)
slider.value = year

# Update the holoviews plot by calling update with the new year.
def slider_update(attrname, old, new):
hvplot.update((new,))

callback_id = None
def slider_update(event):
hvplot.update((event.new,))

def animate():
global callback_id
if button.label == '► Play':
button.label = '❚❚ Pause'
callback_id = doc.add_periodic_callback(animate_update, 200)
def animate(event):
if button.name == '► Play':
button.name = '❚❚ Pause'
callback.start()
else:
button.label = '► Play'
doc.remove_periodic_callback(callback_id)
button.name = '► Play'
callback.stop()

start, end = ds.range('Year')
slider = Slider(start=start, end=end, value=start, step=1, title="Year")
slider.on_change('value', slider_update)
slider = pn.widgets.IntSlider(start=int(start), end=int(end), value=int(start), name="Year")
slider.param.watch(slider_update, 'value')

button = Button(label='► Play', width=60)
button = pn.widgets.Button(name='► Play', width=60, align='end')
button.on_click(animate)
callback = button.add_periodic_callback(animate_update, 200, start=False)

# Get HoloViews plot and attach document
doc = curdoc()
hvplot = renderer.get_plot(hvgapminder, doc)
hvplot = renderer.get_plot(hvgapminder)
hvplot.update((1964,))

# Make a bokeh layout and add it as the Document root
plot = layout([[hvplot.state], [slider, button]], sizing_mode='fixed')
doc.add_root(plot)
# Create a Panel layout and make it servable
pn.Column(
hvplot.state,
pn.Row(slider, button),
sizing_mode='stretch_both'
).servable('Gapminder Demo')
2 changes: 1 addition & 1 deletion examples/gallery/apps/bokeh/mandelbrot.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def get_fractal(x_range, y_range):

# Apply options
dmap.opts(
opts.Histogram(framewise=True, logy=True, width=200),
opts.Histogram(framewise=True, logy=True, width=200, xlim=(1, None)),
opts.Image(cmap='fire', logz=True, height=600, width=600,
xaxis=None, yaxis=None))

Expand Down
15 changes: 5 additions & 10 deletions examples/gallery/apps/bokeh/streaming_psutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,39 +22,34 @@ def get_cpu_data():
df['time'] = pd.Timestamp.now()
return df


# Define DynamicMap callbacks returning Elements

def mem_stack(data):
data = pd.melt(data, 'index', var_name='Type', value_name='Usage')
areas = hv.Dataset(data).to(hv.Area, 'index', 'Usage')
return hv.Area.stack(areas.overlay()).relabel('Memory')

def cpu_box(data):
return hv.BoxWhisker(data, 'CPU', 'Utilization').relabel('CPU Usage')

def cpu_box(data):
return hv.BoxWhisker(data, 'CPU', 'Utilization', label='CPU Usage')

# Set up StreamingDataFrame and add async callback

cpu_stream = hv.streams.Buffer(get_cpu_data(), 800, index=False)
mem_stream = hv.streams.Buffer(get_mem_data())

def cb():
cpu_stream.send(get_cpu_data())
mem_stream.send(get_mem_data())


# Define DynamicMaps and display plot

cpu_dmap = hv.DynamicMap(cpu_box, streams=[cpu_stream])
mem_dmap = hv.DynamicMap(mem_stack, streams=[mem_stream])

plot = (cpu_dmap + mem_dmap).opts(
opts.Area(height=400, width=400, ylim=(0, 100)),
opts.Area(height=400, width=400, ylim=(0, 100), framewise=True),
opts.BoxWhisker(box_fill_color=dim('CPU').str(), cmap='Category20',
width=500, height=400, ylim=(0, 100)))
width=500, height=400, ylim=(0, 100))
)

# Render plot and attach periodic callback

doc = renderer.server_doc(plot)
doc.add_periodic_callback(cb, 0.05)
5 changes: 3 additions & 2 deletions holoviews/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -975,8 +975,9 @@ def max_range(ranges, combined=True):
if not is_nan(v) and v is not None]))
return arr[0], arr[-1]
elif arr.dtype.kind in 'M':
return ((arr.min(), arr.max()) if combined else
(arr[:, 0].min(), arr[:, 1].min()))
drange = ((arr.min(), arr.max()) if combined else
(arr[:, 0].min(), arr[:, 1].max()))
return drange

if combined:
return (np.nanmin(arr), np.nanmax(arr))
Expand Down
2 changes: 1 addition & 1 deletion holoviews/plotting/bokeh/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ def _schedule_callback(self, cb, timeout=None, offset=True):
from tornado.ioloop import IOLoop
IOLoop.current().call_later(int(timeout)/1000., cb)
else:
cb()
pn.state.curdoc.add_timeout_callback(cb, int(timeout))

def on_change(self, attr, old, new):
"""
Expand Down
6 changes: 4 additions & 2 deletions holoviews/plotting/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,9 +815,11 @@ def _compute_group_range(cls, group, elements, ranges, framewise, top_level):
if d not in prev_ranges:
prev_ranges[d] = {}
prev_ranges[d][g] = drange
elif g == 'factors':
prev_ranges[d][g] = drange
else:
prev_ranges[d][g] = util.max_range([drange, prange],
combined=g=='hard')
prev_ranges[d][g] = util.max_range([prange, drange],
combined=g=='hard')
else:
ranges[group] = OrderedDict(dim_ranges)

Expand Down
7 changes: 3 additions & 4 deletions holoviews/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,8 +669,9 @@ def renderer(name):
"""
try:
if name not in Store.renderers:
if Store.current_backend:
prev_backend = Store.current_backend
prev_backend = Store.current_backend
if Store.current_backend not in Store.renderers:
prev_backend = None
extension(name)
if prev_backend:
Store.set_current_backend(prev_backend)
Expand Down Expand Up @@ -834,8 +835,6 @@ def render(obj, backend=None, toolbar=None, **kwargs):
The rendered representation of the HoloViews object, e.g.
if backend='matplotlib' a matplotlib Figure or FuncAnimation
"""
if (backend == 'bokeh' or (backend is None and Store.current_backend == 'bokeh')) and toolbar is None:
obj = obj.opts(toolbar=None, backend='bokeh', clone=True)
backend = backend or Store.current_backend
renderer_obj = renderer(backend)
if kwargs:
Expand Down

0 comments on commit 4cd6651

Please sign in to comment.