Repository: bokeh/bokeh

Issue:
Stacked Bars Failing to Update on Patch
#### ALL software version info (bokeh, python, notebook, OS, browser, any other relevant packages)
OS: Win 7 Pro SP1

Browsers:
Chrome Version 65.0.3325.146 (Official Build) (64-bit)
Firefox 52.7.3 (32-bit)

Python Packages:
altgraph==0.15
bokeh==0.12.15
future==0.16.0
Jinja2==2.10
macholib==1.9
MarkupSafe==1.0
numpy==1.13.3
packaging==17.1
pandas==0.21.1
param==1.6.0
pefile==2017.11.5
PyInstaller==3.3.1
pyodbc==4.0.21
pyparsing==2.2.0
pypiwin32==223
python-dateutil==2.6.1
pytz==2017.3
pywin32==223
PyYAML==3.12
six==1.11.0
tornado==4.5.2

#### Description of expected behavior and the observed behavior
Stacked bars should update when source values are patched. I checked that the source was being updated correctly and it was, and on the same page grouped figures using the same data (different source) were updating correctly. 
The stacked bars were not updating at all, despite the source and the other grouped bars updating. 


#### Complete, minimal, self-contained example code that reproduces the issue

```
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, curdoc
from bokeh.layouts import column
from bokeh.transform import dodge, stack
from bokeh.driving import bounce
import numpy as np
import pandas as pd

fruits = ['Apples', 'Pears', 'Nectarines', 'Plums']

years = ['2013', '2014', '2015', '2016', '2017', '2018']

colors = ['#c9d9d3', '#718dbf', '#e84d60', '#b84d67', '#776dbd']


data = {'years'      : years,
        'Apples'     : [2, 1, 4, 3, 2, 1],
        'Pears'      : [3, 2, 4, 4, 5, 1],
        'Nectarines' : [5, 4, 2, 1, 3, 1],
        'Plums'      : [2, 2, 2, 5, 3, 1]}


source = ColumnDataSource(data=data)

#figure for grouped bars
group_fig = figure(x_range=years, plot_height=250, title="Grouped Fruit Counts by Year",
           toolbar_location=None, tools="")

#figure for stacked bars
stack_fig = figure(x_range=years, plot_height=250, title="Stacked Fruit Counts by Year",
           toolbar_location=None, tools="")

#settings to simulate my setup
width_group = 0.8
gap = 0.02

#here I am calculating teh dodges/bar widths automatically
width_bar= round((width_group-((len(fruits)-1)*gap))/len(fruits), 3)
dodges = np.linspace(-0.5*(len(fruits)*width_bar+((len(fruits)-1)*gap))+(width_bar*.5),
                         0.5*(len(fruits)*width_bar+((len(fruits)-1)*gap))-(width_bar*.5),
                         len(fruits))

#loop to create grouped vbars
for bar, fruit in enumerate(fruits):
    
    group_fig.vbar(x=dodge('years', dodges[bar], range=group_fig.x_range),
           top=fruit,
           width=width_bar,
           source=source,
           color=colors[bar])

#loop that creates stacked vbars. I could not get vbar_stack to work in my other code. 
bottom_fields = []
top_fields = []
for bar, fruit in enumerate(fruits):
    top_fields.append(fruit)
    stack_fig.vbar(bottom=stack(*bottom_fields),
                   top=stack(*top_fields),
                   x='years',
                   width=width_group,
                   source=source,
                   color=colors[bar])
    bottom_fields.append(fruit)

#set up layout
column = column([group_fig, stack_fig])


#bounce wrapper to index columns
@bounce([1, 2, 3, 4])
def get_update(i):
    
    #adding one to last column in source
    df = source.to_df()
    df.at[5, df.columns[i]] = df[df.columns[i]][5] + 1
    patch = df.iloc[5].to_dict()
    for col in patch.keys():
        patch[col] = [(5, patch[col])]
    source.patch(patch)

curdoc().add_periodic_callback(get_update, 5000)
curdoc().add_root(column)
```

#### Stack traceback and/or browser JavaScript console output
[bokeh] document idle at 506 ms  bokeh.min.js:1:101116
Bokeh items were rendered successfully  bokeh.min.js:1:116410
[bokeh] Lost websocket 0 connection, 1001 ()  bokeh.min.js:1:5572
Id already exists ext-contextMenus.js:313
	set id chrome://browser/content/ext-contextMenus.js:313:13
	setProps chrome://browser/content/ext-contextMenus.js:286:7
	MenuItem chrome://browser/content/ext-contextMenus.js:268:3
	.contextMenus.createInternal chrome://browser/content/ext-contextMenus.js:500:24
	bound  self-hosted:915:17
	call resource://gre/modules/ExtensionParent.jsm:474:64
	receiveMessage resource://gre/modules/ExtensionParent.jsm:403:9
Error: An unexpected error occurred (unknown)
Id already exists ext-contextMenus.js:313
	set id chrome://browser/content/ext-contextMenus.js:313:13
	setProps chrome://browser/content/ext-contextMenus.js:286:7
	MenuItem chrome://browser/content/ext-contextMenus.js:268:3
	.contextMenus.createInternal chrome://browser/content/ext-contextMenus.js:500:24
	bound  self-hosted:915:17
	call resource://gre/modules/ExtensionParent.jsm:474:64
	receiveMessage resource://gre/modules/ExtensionParent.jsm:403:9
Error: An unexpected error occurred (unknown)
[bokeh] setting log level to: 'info'  bokeh.min.js:1:38620
Id already exists ext-contextMenus.js:313
	set id chrome://browser/content/ext-contextMenus.js:313:13
	setProps chrome://browser/content/ext-contextMenus.js:286:7
	MenuItem chrome://browser/content/ext-contextMenus.js:268:3
	.contextMenus.createInternal chrome://browser/content/ext-contextMenus.js:500:24
	bound  self-hosted:915:17
	call resource://gre/modules/ExtensionParent.jsm:474:64
	receiveMessage resource://gre/modules/ExtensionParent.jsm:403:9
Error: An unexpected error occurred (unknown)
[bokeh] Websocket connection 0 is now open  bokeh.min.js:1:5025
[bokeh] document idle at 480 ms  bokeh.min.js:1:101116
Bokeh items were rendered successfully  bokeh.min.js:1:116410

        
#### Screenshots or screencasts of the bug in action
![image](https://user-images.githubusercontent.com/32401880/39130471-2426d6f0-46db-11e8-853b-ef39588e4b1f.png)




Discussion:
I also noticed that the hover tool will be accurate, but the glyph does not update. 
I tried just using the stack() function outside of the loop to get the total in one bar, and got the same result, so maybe the stack function has a difficulty re-rendering after updates?

```
top_fields = fruits
stack_fig.vbar(top=stack(*top_fields),
                   x='years',
                   width=width_group,
                   source=source,
                   color=colors[bar])
```
Looks like expressions do some caching:

https://github.com/bokeh/bokeh/blob/master/bokehjs/src/lib/models/expressions/expression.ts#L36-L48

I don't recall the reason for this offhand, typically we try to avoid complicated caching unless it is unavoidable. A quick thing might be to try having `_v_compute` skip all that work, and just `return this.v_compute(source)` unconditionally. Otherwise if that caching is needed, then some way to invalidate will have to be found. 
Tho invalidation probably just means responding to `patch` and `stream` here:

https://github.com/bokeh/bokeh/blob/master/bokehjs/src/lib/models/expressions/expression.ts#L38

in addition to `change`

Resolving patch:
diff --git a/bokeh/models/expressions.py b/bokeh/models/expressions.py
--- a/bokeh/models/expressions.py
+++ b/bokeh/models/expressions.py
@@ -39,11 +39,9 @@ class Expression(Model):
         v_compute: (source) ->
             # compute an array of values
 
-    Note that the result of this call will be automatically saved and re-used
-    for each ``source`` that is passed in. If a ``source`` is changed, then
-    the saved value for that source is discarded, and the next call will
-    re-compute (and save) a new value. If you wish to prevent this caching, you
-    may implement ``_v_compute: (source)`` instead.
+    .. note::
+        If you wish for results to be cached per source, and updated only if
+        the source changes, implement `_v_compute: (souce)` instead.
 
     '''
     pass


Test patch:
diff --git a/bokehjs/test/models/expressions/stack.coffee b/bokehjs/test/models/expressions/stack.coffee
--- a/bokehjs/test/models/expressions/stack.coffee
+++ b/bokehjs/test/models/expressions/stack.coffee
@@ -45,3 +45,27 @@ describe "Stack", ->
       source.data = {foo: [10, 20, 30], bar: [0.01, 0.02, 0.03]}
       ret = s.v_compute(source)
       expect(ret).to.deep.equal new Float64Array([10.01, 20.02, 30.03])
+
+    it "should should re-compute if a source patches", ->
+      source = new ColumnDataSource({data: {foo: [1, 2, 3], bar: [0.1, 0.2, 0.3]}})
+      s = new Stack({fields: ['foo', 'bar']})
+      ret = s.v_compute(source)
+      expect(ret).to.deep.equal new Float64Array([1.1, 2.2, 3.3])
+
+      source.patch({"foo": [[1, 12]]})
+      ret = s.v_compute(source)
+      expect(ret).to.deep.equal new Float64Array([1.1, 12.2, 3.3])
+
+      source.patch({"bar": [[0, 1.1]]})
+      ret = s.v_compute(source)
+      expect(ret).to.deep.equal new Float64Array([2.1, 12.2, 3.3])
+
+    it "should should re-compute if a source streams", ->
+      source = new ColumnDataSource({data: {foo: [1, 2, 3], bar: [0.1, 0.2, 0.3]}})
+      s = new Stack({fields: ['foo', 'bar']})
+      ret = s.v_compute(source)
+      expect(ret).to.deep.equal new Float64Array([1.1, 2.2, 3.3])
+
+      source.stream({foo: [4], bar: [0.4]})
+      ret = s.v_compute(source)
+      expect(ret).to.deep.equal new Float64Array([1.1, 2.2, 3.3, 4.4])