internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: bokeh/bokeh

Issue:
Expose the wheel zoom speed in Python
The sensitivity of the wheel zoom cannot be configured on the Python side. However, there is a `speed` property which can be set on the JS side. It would be better to expose this property to the Python side too.



Discussion:
@liuyxpp we are actually in the middle of a big clean-up to ensure that the properties on the python and JS side match each other. 

@havocp WheelZoomTool has a default for `speed` on the JS side (https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/tool/gestures/wheel_zoom_tool.coffee#L119) but not a python property.

However running the test in #3099 doesn't pick up this discrepancy. Thoughts?

Maybe the test only picks up differences in defaults not when there's missing properties?

@birdsarah maybe https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/tool/gestures/wheel_zoom_tool.coffee#L113

great spot @bryevdv, do you know why that would be?

Why that line exists? @havoc added that to get things working at all. Currently the client models add all kind of new attributes with abandon and most of them should not get serialized back to the server. But a few like this one should be, but were just overlooked. We should have been more careful in the first place and made client only "viewmodel" objects to store any new, extra, "not intended to be serialized" data, and and kept the main models pure and 1-1 in sync with Python (no extra attrs added). We still should do this some day. 

> On Nov 15, 2015, at 00:16, Sarah Bird notifications@github.com wrote:
> 
> great spot @bryevdv, do you know why that would be?
> 
> —
> Reply to this email directly or view it on GitHub.

Python would raise an exception at one point if it saw an unknown property... now it just warns. So I was having to mark tons of props as nonserializable. It sounds like the better fix here was to simply add the prop to Python.

thanks for the background @havocp and @bryevdv still learning how the all the pieces of the properties mechanism works.

We can certainly expose the speed, but unless we can also find a way to make a given speed somewhat uniform across browsers I am dubious of the utility. But changing to `feature` in any case. 


Resolving patch:
diff --git a/bokeh/models/tools.py b/bokeh/models/tools.py
--- a/bokeh/models/tools.py
+++ b/bokeh/models/tools.py
@@ -197,6 +197,12 @@ class WheelZoomTool(Scroll):
     vertically across the height of the plot.
     """)
 
+    speed = Float(default=1/600, help="""
+    Speed at which the wheel zooms. Default is 1/600. Optimal range is between 
+    0.001 and 0.09. High values will be clipped. Speed may very between browsers.
+    """)
+
+
 
 class SaveTool(Action):
     """ *toolbar icon*: |save_icon|


Test patch:
Repository: bokeh/bokeh

Issue:
Docs should have descriptive page titles
currently every page is "Bokeh Docs"



Discussion:

Resolving patch:
diff --git a/sphinx/source/conf.py b/sphinx/source/conf.py
--- a/sphinx/source/conf.py
+++ b/sphinx/source/conf.py
@@ -123,7 +123,6 @@
 
 html_context = {
     'SITEMAP_BASE_URL': 'http://bokeh.pydata.org/en/', # Trailing slash is needed
-    'SITENAME': 'Bokeh Docs',
     'DESCRIPTION': 'Bokeh visualization library, documentation site.',
     'AUTHOR': 'Bokeh contributors',
     'VERSION': version,
@@ -164,7 +163,7 @@
         ('Releases', 'releases/%s' % version),
         ('Developer Guide', 'dev_guide'),
     ),
-    'ALL_VERSIONS': ['0.12.0', '0.11.0', '0.10.0', '0.9.3', '0.8.2'],
+    'ALL_VERSIONS': ['0.12.0', '0.11.0', '0.10.0'],
     'css_server': os.environ.get('BOKEH_DOCS_CSS_SERVER', 'bokehplots.com'),
 }
 


Test patch:
Repository: bokeh/bokeh

Issue:
multi_line and selection callback
Hi,

Is `multi_line` supposed to work with a selection callback? For example, when I draw a point plot from a data source, attaching a selection callback to the data source works well, i.e. it returns the index of the selected point, but when I use ''multi_line'' for the same data source and click a line, the callback gets nothing from `cb_obj.get('selected')`.



Discussion:
Selection hit testing has not been implemented for multi line yet. It would make a nice small PR for a new contributor. 


Resolving patch:
diff --git a/bokeh/models/sources.py b/bokeh/models/sources.py
--- a/bokeh/models/sources.py
+++ b/bokeh/models/sources.py
@@ -22,7 +22,7 @@ class DataSource(Model):
     selected = Dict(String, Dict(String, Any), default={
         '0d': {'glyph': None, 'indices': []},
         '1d': {'indices': []},
-        '2d': {'indices': []}
+        '2d': {}
     }, help="""
     A dict to indicate selected indices on different dimensions on this DataSource. Keys are:
 
diff --git a/examples/plotting/file/multi_line.py b/examples/plotting/file/multi_line.py
new file mode 100644
--- /dev/null
+++ b/examples/plotting/file/multi_line.py
@@ -0,0 +1,44 @@
+import numpy as np
+
+from bokeh.plotting import output_file, show, figure
+from bokeh.models import ColumnDataSource, HoverTool, TapTool
+from bokeh.layouts import gridplot
+from bokeh.palettes import Spectral6
+
+output_file('multi_line.html')
+
+PLOT_DEFAULTS = dict(plot_width=450, plot_height=300)
+
+from collections import defaultdict
+
+from scipy.stats import norm
+
+mass_spec = defaultdict(list)
+
+RT_x = np.linspace(118, 123, num=50)
+norm_dist = norm(loc=120.4).pdf(RT_x)
+
+# Generate several gaussian distributions and spectral lines
+for scale, mz in [(1.0, 83), (0.9, 55), (0.6, 98), (0.4, 43), (0.2, 39), (0.12, 29)]:
+    mass_spec["RT"].append(RT_x)
+    mass_spec["RT_intensity"].append(norm_dist*scale)
+    mass_spec["MZ"].append([mz, mz])
+    mass_spec["MZ_intensity"].append([0, scale])
+    mass_spec['MZ_tip'].append(mz)
+    mass_spec['Intensity_tip'].append(scale)
+
+source = ColumnDataSource(mass_spec)
+
+tooltips = [('MZ', '@MZ_tip'), ('Rel Intensity', '@Intensity_tip')]
+
+rt_plot = figure(tools=[HoverTool(tooltips=tooltips, line_policy='next'), TapTool()], **PLOT_DEFAULTS)
+rt_plot.multi_line(xs='RT', ys='RT_intensity', line_width=5, line_color=Spectral6, line_alpha=0.8, source=source)
+rt_plot.xaxis.axis_label = "Retention Time (sec)"
+rt_plot.yaxis.axis_label = "Intensity"
+
+mz_plot = figure(tools=[HoverTool(tooltips=tooltips, line_policy='next'), TapTool()], **PLOT_DEFAULTS)
+mz_plot.multi_line(xs='MZ', ys='MZ_intensity', line_width=5, line_color=Spectral6, line_alpha=0.8, source=source)
+mz_plot.xaxis.axis_label = "MZ"
+mz_plot.yaxis.axis_label = "Intensity"
+
+show(gridplot([[rt_plot, mz_plot]]))


Test patch:
Repository: bokeh/bokeh

Issue:
Simplify CHANGELOG
Currently the auto-generated `CHANGELOG` attempts to copy parts of the release notes over. This is both fragile (a migration guide section will confuse the output greatly) as well as duplicative. Proposed to clean up the existing `CHANGELOG` to only have issues, and modify the script to not perform the copying in the future. 



Discussion:

Resolving patch:
diff --git a/scripts/issues.py b/scripts/issues.py
--- a/scripts/issues.py
+++ b/scripts/issues.py
@@ -299,15 +299,6 @@ def write(func, endofline="", append=""):
             content = f.read()
             f.seek(0)
             write(f.write, '\n', content)
-        # Insert the summary points from the <tag>.rst file into the CHANGELOG
-        flines = []
-        with open("../CHANGELOG", "r") as f:
-            flines = f.readlines()
-        with open("../sphinx/source/docs/releases/" + rtag + ".rst", "r") as f:
-            starters = ("*", "  -", "  ")
-            flines[2:2] = ["  " + line for line in f.readlines() if line.startswith(starters)]
-        with open("../CHANGELOG", "w") as f:
-            f.writelines(flines)
     else:
         write(print)
 


Test patch:
Repository: bokeh/bokeh

Issue:
New feature: hide tooltip arrow
As asked on [Stack Overflow](https://stackoverflow.com/questions/38651060/how-to-remove-arrow-on-tooltip-from-bokeh-plot/), it could be great to have the possibility to hide the tooltip arrow.



Discussion:

Resolving patch:
diff --git a/bokeh/models/annotations.py b/bokeh/models/annotations.py
--- a/bokeh/models/annotations.py
+++ b/bokeh/models/annotations.py
@@ -662,3 +662,7 @@ class Tooltip(Annotation):
     inner_only = Bool(default=True, help="""
     Whether to display outside a central plot frame area.
     """)
+
+    show_arrow = Bool(default=True, help="""
+    Whether tooltip's arrow should be showed.
+    """)
diff --git a/bokeh/models/tools.py b/bokeh/models/tools.py
--- a/bokeh/models/tools.py
+++ b/bokeh/models/tools.py
@@ -682,6 +682,10 @@ class HoverTool(Inspection):
     Whether tooltip's arrow should appear in the horizontal or vertical dimension.
     """)
 
+    show_arrow = Bool(default=True, help="""
+    Whether tooltip's arrow should be showed.
+    """)
+
 DEFAULT_HELP_TIP = "Click the question mark to learn more about Bokeh plot tools."
 DEFAULT_HELP_URL = "http://bokeh.pydata.org/en/latest/docs/user_guide/tools.html"
 


Test patch:
Repository: bokeh/bokeh

Issue:
Checkbox example is not working as expected
The new checkbox example in master (examples/plotting/file/line_on_off.py) is not working as expected, the plotted lines are depend on how many checkbox are ticked and not on which one. The reason is that the js code is not checking for the values but for the existence of the index.

I have a fix for this with a PR coming, and opening this only as an associated issue.



Discussion:

Resolving patch:
diff --git a/examples/plotting/file/line_on_off.py b/examples/plotting/file/line_on_off.py
--- a/examples/plotting/file/line_on_off.py
+++ b/examples/plotting/file/line_on_off.py
@@ -12,24 +12,6 @@
 
 output_file("line_on_off.html", title="line_on_off.py example")
 
-code = """
-    if (0 in checkbox.active) {
-        l0.visible = true
-    } else {
-        l0.visible = false
-    }
-    if (1 in checkbox.active) {
-        l1.visible = true
-    } else {
-        l1.visible = false
-    }
-    if (2 in checkbox.active) {
-        l2.visible = true
-    } else {
-        l2.visible = false
-    }
-"""
-
 p = figure()
 props = dict(line_width=4, line_alpha=0.7)
 x = np.linspace(0, 4 * np.pi, 100)
@@ -37,9 +19,14 @@
 l1 = p.line(x, 4 * np.cos(x), color=Viridis3[1], legend="Line 1", **props)
 l2 = p.line(x, np.tan(x), color=Viridis3[2], legend="Line 2", **props)
 
-callback = CustomJS(code=code, args={})
-checkbox = CheckboxGroup(labels=["Line 0", "Line 1", "Line 2"], active=[0, 1, 2], callback=callback, width=100)
-callback.args = dict(l0=l0, l1=l1, l2=l2, checkbox=checkbox)
+checkbox = CheckboxGroup(labels=["Line 0", "Line 1", "Line 2"],
+                         active=[0, 1, 2], width=100)
+checkbox.callback = CustomJS(args=dict(l0=l0, l1=l1, l2=l2, checkbox=checkbox),
+                             lang="coffeescript", code="""
+l0.visible = 0 in checkbox.active;
+l1.visible = 1 in checkbox.active;
+l2.visible = 2 in checkbox.active;
+""")
 
 layout = row(checkbox, p)
 show(layout)


Test patch:
Repository: bokeh/bokeh

Issue:
devdeps.py doesn't check for test dependencies
Was just working @stvn66 to get tests running locally, and when running `devdeps.py` it didn't report that flake8 and boto weren't installed which are needed by the tests to run.

I may have been directing @stvn66 incorrectly though.

ping @damianavila 



Discussion:
devdeps.py get quickly out of date... we should be review to check that actually is checking the proper dependencies... changing to task.


Resolving patch:
diff --git a/scripts/devdeps.py b/scripts/devdeps.py
--- a/scripts/devdeps.py
+++ b/scripts/devdeps.py
@@ -9,15 +9,15 @@ def red(text): return "%s%s%s" % (colorama.Fore.RED, text, colorama.Style.RESET_
     def blue(text) : return text
     def red(text) : return text
 
-npm_missing = """
-npm packages do not appear to be installed.
-Please navigate to the bokehjs directory and type 'npm install'\n
-"""
 
 def npm_check():
     """Check that the bokehjs/node_modules directory exists, as its absence
     is the best indication that npm is not installed."""
 
+    npm_missing = """
+    npm packages do not appear to be installed.
+    Please navigate to the bokehjs directory and type 'npm install'\n
+    """
 
     if not path.exists('../bokehjs/node_modules'):
         print(red(npm_missing))
@@ -33,20 +33,20 @@ def depend_check(deps_name, *args):
             __import__(dependency)
         except ImportError:
             missing.append(dependency)
-            found = False
 
     print('-'*80)
-    if len(missing):
+
+    if missing:
         print(red("You are missing the following %s dependencies:") % deps_name)
 
         for dep in missing:
             name = pkg_info_dict.get(dep, dep)
             print(" * ", name)
         print()
-        return False
+
     else:
         print(blue("All %s dependencies installed!  You are good to go!\n") % deps_name)
-        return True
+
 
 if __name__ == '__main__':
 
@@ -58,6 +58,8 @@ def depend_check(deps_name, *args):
         'websocket': 'websocket-client',
         'pytest_selenium': 'pytest-selenium',
         'pytest_cov': 'pytest-cov',
+        'dateutil': 'python-dateutil',
+        'yaml': 'pyyaml',
     }
 
     dev_deps = [
@@ -67,7 +69,9 @@ def depend_check(deps_name, *args):
         'pytest_cov',
         'pytest_selenium',
         'mock',
-        'websocket'
+        'websocket',
+        'flake8',
+        'boto',
     ]
     depend_check('Dev', *dev_deps)
 
@@ -76,3 +80,13 @@ def depend_check(deps_name, *args):
         'pygments',
     ]
     depend_check('Docs', *docs_deps)
+
+    base_deps = [
+        'jinja2',
+        'numpy',
+        'dateutil',
+        'requests',
+        'tornado',
+        'yaml',
+    ]
+    depend_check('Base', *base_deps)


Test patch:
Repository: bokeh/bokeh

Issue:
Box plot example fails if no outliers exist in data
This is an (statistically improbable) edge case, but while experimenting with adapting the code in the boxplot example, I discovered that the code will fail in cases where no outliers exist in the data to be plotted. The problem is that the test on line 32 of `boxplot.py`:

``` Python
    if not out.loc[cat].empty:
```

only tests whether there are any outliers for the category, `cat`, in question at the time. If there are no outliers in any of the categories, the script fails here, because actually `out` itself is empty. Error below*:

```
Traceback (most recent call last):
[...]
  File "boxplot_script.py", line 74, in draw_boxplot
    if not out.loc[cat].empty:
  File "/path/to/anaconda/envs/Py35/lib/python3.5/site-packages/pandas/core/indexing.py", line 1296, in __getitem__
    return self._getitem_axis(key, axis=0)
  File "/path/to/anaconda/envs/Py35/lib/python3.5/site-packages/pandas/core/indexing.py", line 1466, in _getitem_axis
    self._has_valid_type(key, axis)
  File "/path/to/anaconda/envs/Py35/lib/python3.5/site-packages/pandas/core/indexing.py", line 1403, in _has_valid_type
    error()
  File "/path/to/anaconda/envs/Py35/lib/python3.5/site-packages/pandas/core/indexing.py", line 1390, in error
    (key, self.obj._get_axis_name(axis)))
KeyError: 'the label [abc] is not in the [index]'
```

I have made a fix for this in the example script, which I will submit as a PR in a couple of minutes.

*note, line number is different because this error is coming from my script, but the code for dealing with outliers is identical between the two scripts.



Discussion:

Resolving patch:
diff --git a/examples/plotting/file/boxplot.py b/examples/plotting/file/boxplot.py
--- a/examples/plotting/file/boxplot.py
+++ b/examples/plotting/file/boxplot.py
@@ -27,14 +27,15 @@ def outliers(group):
 out = groups.apply(outliers).dropna()
 
 # prepare outlier data for plotting, we need coordinates for every outlier.
-outx = []
-outy = []
-for cat in cats:
-    # only add outliers if they exist
-    if not out.loc[cat].empty:
-        for value in out[cat]:
-            outx.append(cat)
-            outy.append(value)
+if not out.empty:
+    outx = []
+    outy = []
+    for cat in cats:
+        # only add outliers if they exist
+        if not out.loc[cat].empty:
+            for value in out[cat]:
+                outx.append(cat)
+                outy.append(value)
 
 p = figure(tools="save", background_fill_color="#EFE8E2", title="", x_range=cats)
 
@@ -59,7 +60,8 @@ def outliers(group):
 p.rect(cats, upper.score, 0.2, 0.01, line_color="black")
 
 # outliers
-p.circle(outx, outy, size=6, color="#F38630", fill_alpha=0.6)
+if not out.empty:
+    p.circle(outx, outy, size=6, color="#F38630", fill_alpha=0.6)
 
 p.xgrid.grid_line_color = None
 p.ygrid.grid_line_color = "white"


Test patch:
Repository: bokeh/bokeh

Issue:
Split off bokehjs JS/TS API



Discussion:
Just to point out, this shaves off about 34 kB (out of 906 kB) from `bokeh.min.js`. @bryevdv, do you still think the change is worthwhile?

Yes definitely. Hopefully the JS API will also grow over time.  

> On Aug 17, 2016, at 14:50, Mateusz Paprocki notifications@github.com wrote:
> 
> Just to point out, this shaves off about 34 kB (out of 906 kB) from bokeh.min.js. @bryevdv, do you still think the change is worthwhile?
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub, or mute the thread.


Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -333,6 +333,9 @@ def size(*path):
         print("  - bokeh-widgets.min.js  : %6.1f KB" % size("js", "bokeh-widgets.min.js"))
         print("  - bokeh-widgets.min.css : %6.1f KB" % size("css", "bokeh-widgets.min.css"))
 
+        print("  - bokeh-api.js          : %6.1f KB" % size("js", "bokeh-api.js"))
+        print("  - bokeh-api.min.js      : %6.1f KB" % size("js", "bokeh-api.min.js"))
+
         print("  - bokeh-compiler.js     : %6.1f KB" % size("js", "bokeh-compiler.js"))
         print("  - bokeh-compiler.min.js : %6.1f KB" % size("js", "bokeh-compiler.min.js"))
     except Exception as e:


Test patch:
diff --git a/bokehjs/test/common/defaults.coffee b/bokehjs/test/common/defaults.coffee
--- a/bokehjs/test/common/defaults.coffee
+++ b/bokehjs/test/common/defaults.coffee
@@ -9,7 +9,7 @@ widget_defaults = require "./generated_defaults/widgets_defaults"
 mixins = utils.require "core/property_mixins"
 HasProps = utils.require "core/has_props"
 
-widget_locations = utils.require "models/widgets/main"
+widget_locations = utils.require("models/widgets/main").models
 Models.register_locations(widget_locations)
 
 all_view_model_names = []
diff --git a/bokehjs/test/size.coffee b/bokehjs/test/size.coffee
--- a/bokehjs/test/size.coffee
+++ b/bokehjs/test/size.coffee
@@ -11,6 +11,7 @@ LIMITS = {
   "css/bokeh.min.css":         74
   "js/bokeh-compiler.min.js":  178
   "js/bokeh-widgets.min.js":   290
+  "js/bokeh-api.min.js":        40
   "js/bokeh.min.js":           918
 }
Repository: bokeh/bokeh

Issue:
Minor: box plot example indexing
I've been studying and adapting the box plot example a lot recently and, IMO, the calls to `df.loc` and `df.iloc` in `boxplot.py` are a little confusing and could be made more explicit/readable/transferable by removing the numeric indexing and replacing with the string name of the column being accessed. To explain, what I'm proposing is that:

``` Python
# boxplot.py, lines 24-27
def outliers(group):
    cat = group.name
    return group[(group.score > upper.loc[cat][0]) | (group.score < lower.loc[cat][0])]['score']
out = groups.apply(outliers).dropna()
```

should become

``` Python
# boxplot.py, lines 24-27
def outliers(group):
    cat = group.name
    return group[(group.score > upper.loc[cat]['score']) | (group.score < lower.loc[cat]['score'])]['score']
out = groups.apply(outliers).dropna()
```

, and

``` Python
# boxplot.py, lines 45 & 46
upper.score = [min([x,y]) for (x,y) in zip(list(qmax.iloc[:,0]),upper.score) ]
lower.score = [max([x,y]) for (x,y) in zip(list(qmin.iloc[:,0]),lower.score) ]
```

should become

``` Python
# boxplot.py, lines 45 & 46
upper.score = [min([x,y]) for (x,y) in zip(list(qmax.loc[:,'score']),upper.score)]
lower.score = [max([x,y]) for (x,y) in zip(list(qmin.loc[:,'score']),lower.score)]
```

(note removal of trailing space character from list comprehensions on lines 45 & 46.)



Discussion:

Resolving patch:
diff --git a/examples/plotting/file/boxplot.py b/examples/plotting/file/boxplot.py
--- a/examples/plotting/file/boxplot.py
+++ b/examples/plotting/file/boxplot.py
@@ -23,7 +23,7 @@
 # find the outliers for each category
 def outliers(group):
     cat = group.name
-    return group[(group.score > upper.loc[cat][0]) | (group.score < lower.loc[cat][0])]['score']
+    return group[(group.score > upper.loc[cat]['score']) | (group.score < lower.loc[cat]['score'])]['score']
 out = groups.apply(outliers).dropna()
 
 # prepare outlier data for plotting, we need coordinates for every outlier.
@@ -42,8 +42,8 @@ def outliers(group):
 # if no outliers, shrink lengths of stems to be no longer than the minimums or maximums
 qmin = groups.quantile(q=0.00)
 qmax = groups.quantile(q=1.00)
-upper.score = [min([x,y]) for (x,y) in zip(list(qmax.iloc[:,0]),upper.score) ]
-lower.score = [max([x,y]) for (x,y) in zip(list(qmin.iloc[:,0]),lower.score) ]
+upper.score = [min([x,y]) for (x,y) in zip(list(qmax.loc[:,'score']),upper.score)]
+lower.score = [max([x,y]) for (x,y) in zip(list(qmin.loc[:,'score']),lower.score)]
 
 # stems
 p.segment(cats, upper.score, cats, q3.score, line_width=2, line_color="black")


Test patch:
Repository: bokeh/bokeh

Issue:
VBar / HBar legend missing glyphs



Discussion:

Resolving patch:
diff --git a/examples/plotting/file/bar_chart.py b/examples/plotting/file/bar_chart.py
new file mode 100644
--- /dev/null
+++ b/examples/plotting/file/bar_chart.py
@@ -0,0 +1,62 @@
+import pandas as pd
+
+from bokeh.io import show, curdoc
+from bokeh.layouts import layout
+from bokeh.models import ColumnDataSource, FactorRange
+from bokeh.plotting import figure
+from bokeh.sampledata.degrees import data
+from bokeh.themes import Theme
+
+data = data.set_index('Year')
+categories = data.columns.tolist()
+categories.reverse()
+
+curdoc().theme = Theme(json={'attrs': {
+    'Figure': {
+        'toolbar_location': None,
+        'outline_line_color': None,
+        'min_border_right': 10,
+    },
+    'Axis': {
+        'major_tick_in': None,
+        'minor_tick_out': None,
+        'minor_tick_in': None,
+        'axis_line_color': '#CAC6B6',
+        'major_tick_line_color': '#CAC6B6',
+    },
+    'Legend': {
+        'background_fill_alpha': 0.8,
+    }
+}})
+
+
+def _make_source_for_year(year):
+    # Get data out of dataframe for a given year
+    year_df = pd.DataFrame(data.loc[year]).reset_index()
+    year_df = year_df.rename(columns={year: 'percent_female', 'index': 'category'})
+    source = ColumnDataSource(year_df)
+    return source
+
+
+def all_for_year(year):
+    source = _make_source_for_year(year)
+    bar_opts = dict(y='category', height=0.5)
+    p = figure(title=str(year), y_range=FactorRange(factors=categories), x_range=(0, 100), tools='')
+    p.grid.grid_line_color = None
+    p.hbar(left=0, right='percent_female', color='#AE9E59', legend='Female', source=source, **bar_opts)
+    p.hbar(left='percent_female', right=100, color='#CAC6B6', legend='Male', source=source, **bar_opts)
+    return p
+
+
+def two_categories_over_time():
+    bar_opts = dict(width=0.3, alpha=0.8)
+    p = figure(title="Percentage of women graduating over time in two fields.", y_range=(0, 100), tools='')
+    p.vbar(bottom=0, top=data['Psychology'], x=data.index - 0.2, color='#4F4478', legend='Psychology', **bar_opts)
+    p.vbar(bottom=0, top=data['Engineering'], x=data.index + 0.2, color='#827F8B', legend='Engineering', **bar_opts)
+    return p
+
+l = layout([
+    [all_for_year(1970), all_for_year(2010)],
+    [two_categories_over_time()],
+], sizing_mode='stretch_both')
+show(l)


Test patch: