Repository: bokeh/bokeh

Issue:
issue with bokeh.client after layout PR
@mattpap can you fill in the details here? (How to reproduce, etc). It's been awhile and this keeps falling off my radar because there has not been an issue. 


Discussion:
I've been using this code:
```python
from bokeh.command.util import build_single_handler_application
from bokeh.client import push_session
app = build_single_handler_application("./examples/app/selection_histogram.py")
doc = app.create_document()
push_session(doc, session_id="selection_histogram")
```
The effect of running this after the layout PR, are extraneous axes, which are created in `Figure.__init__`. Previously those axes were overwritten by changing `Plot.renderers`, but that doesn't happen anymore, so axes persist.
@mattpap I'm not sure I am seeing that, at least not on master.  I don't see anything render at all, but the client sends back some updates to the server after initialization, which causes an error:
```
ValueError: expected an element of List(Instance(Renderer)), got seq with invalid items [LinearScale(id='1011', ...)]
```
That error appears to be because this event is part of the update:

```
'events': [{'kind': 'ModelChanged',
   'model': {'id': '1003', 'type': 'Plot'},
   'attr': 'below',
   'new': [{'type': 'LinearScale', 'id': '1011'}]},
```

That is, the client is sending back a `LinearScale` in the list of objects for `Plot.below` which definitely seems to be a mistake. Is it possible something in the new layout code is putting the scale in `below` by mistake?
More weirdness, on the client, the toolbar `tools` property has things that are not tools in it after the session is pulled:

<img width="231" alt="screen shot 2019-02-07 at 17 29 44" src="https://user-images.githubusercontent.com/1078448/52449773-18582380-2afe-11e9-8abf-92c74bc4157b.png">

This does not appear to be what the script sends to the server, thought:

> 2019-02-07 16:50:39,401 Patching attribute 'tools' of Toolbar(id='1028', ...) with [{'id': '1023', 'type': 'PanTool'}, {'id': '1024', 'type': 'WheelZoomTool'}, {'id': '1025', 'type': 'BoxSelectTool'}, {'id': '1026', 'type': 'LassoSelectTool'}, {'id': '1027', 'type': 'ResetTool'}]
What you are seeing is an issue with simple IDs (I presume). I forgot to add that I needed to run both this code (in my case in IPython) and bokeh server with BOKEH_SIMPLE_IDS=~~true~~false, to make it work at all. Otherwise it failed in various ways.
@mattpap this is a problem with simple ids. Setting `BOKEH_SIMPLE_IDS=no` lets things work. We can certainly do that just to get the tests running for now. I will have to investigate tomorrow to see where the id generation is tripping things up. 

edit: sorry I didn't see your earlier reply. For me things only work with `BOKEH_SIMPLE_IDS=no` and then I see the extra axes but at least it plots
> For me things only work with `BOKEH_SIMPLE_IDS=no`

Yeah, I meant that. Too tired to write the right thing.
@mattpap as a starting point to discuss a real solution, here is a trivial patch that fixes both issues (the extra axes, and the problem with simple ids):
```
(base) ❯ git diff .
diff --git a/bokeh/plotting/figure.py b/bokeh/plotting/figure.py
index c9b0210cc..157ee55ef 100644
--- a/bokeh/plotting/figure.py
+++ b/bokeh/plotting/figure.py
@@ -149,6 +149,9 @@ class Figure(Plot):

         super(Figure, self).__init__(*arg, **kw)

+        if kw.get("_block_events", False):
+            return
+
         self.x_range = _get_range(opts.x_range)
         self.y_range = _get_range(opts.y_range)
```

It seems to me that that to make this more reasonable/maintainable we could encapsulate the few places any actual "work" is done in `Model` initializers into a distinguished method  (as a convention) and then make sure that that method is not invoked when `_block_events` is true (we might want to rename that as well). Thoughts?
I figured that this will be necessary. Though this assumes that Figure's super-classes behave (which may be the case). I think that the long term solution would be to split model initialization into `__new__` and `__init__`, where `__new__` would set up low-level, property-related stuff and `__init__` would focus on high level initialization (like `Figure` requires). Then in serialization code we would initialize empty models by manually calling `__new__` and then filling in property values from JSON.
Sure but I don't think there is really any essential difference? `__new__` typically calls `object.__new__` via `super`, in order to call `__init__` on the instance. I don't see how you could avoid the need for  some optional parameter to `__new__` that could be used to condition whether the `super` call happens or not. 

I guess the place to start is a survey of all the non-trivial model initializers and what they do before/after calling `super`
Here is everything in `bokeh.models`
```
./annotations.py:
  125 :     def __init__(self, *args, **kwargs):
  126 :         super(LegendItem, self).__init__(*args, **kwargs)
  127 +         if isinstance(self.label, string_types):
  128 +             # Allow convenience of setting label as a string
  129 +             self.label = value(self.label)
./axes.py:
  309 :     def __init__(self, dimension='lat', **kw):
  310 :         super(MercatorAxis, self).__init__(**kw)
  311 +
  312 +         # Just being careful. It would be defeat the purpose for anyone to actually
  313 +         # configure this axis with differnet kinds of tickers or formatters.
  314 +         if isinstance(self.ticker, MercatorTicker):
  315 +             self.ticker.dimension = dimension
  316 +         if isinstance(self.formatter, MercatorTickFormatter):
  317 +             self.formatter.dimension = dimension
./filters.py:
   58 :     def __init__(self, *args, **kw):
   59 +         if len(args) == 1 and "filter" not in kw:
   60 +             kw["filter"] = args[0]
   61 +
   62 :         super(Filter, self).__init__(**kw)
   72 :     def __init__(self, *args, **kw):
   73 +         if len(args) == 1 and "indices" not in kw:
   74 +             kw["indices"] = args[0]
   75 +
   76 :         super(IndexFilter, self).__init__(**kw)
   87 :     def __init__(self, *args, **kw):
   88 +         if len(args) == 1 and "booleans" not in kw:
   89 +             kw["booleans"] = args[0]
   90 +
   91 :         super(BooleanFilter, self).__init__(**kw)
  106 :     def __init__(self, *args, **kw):
  107 +         if len(args) == 2 and "column_name" not in kw and "group" not in kw:
  108 +             kw["column_name"] = args[0]
  109 +             kw["group"] = args[1]
  110 +
  111 :         super(GroupFilter, self).__init__(**kw)
./glyphs.py:
  437 :     def __init__(self, **kwargs):
  438 +         if 'palette' in kwargs and 'color_mapper' in kwargs:
  439 +             raise ValueError("only one of 'palette' and 'color_mapper' may be specified")
  440 +         elif 'color_mapper' not in kwargs:
  441 +             # Use a palette (given or default)
  442 +             palette = kwargs.pop('palette', 'Greys9')
  443 +             mapper = LinearColorMapper(palette)
  444 +             kwargs['color_mapper'] = mapper
  445 +
  446 :         super(Image, self).__init__(**kwargs)
./layouts.py:
  306 :     def __init__(self, *args, **kwargs):
  307 +
  308 +         if len(args) > 0 and "children" in kwargs:
  309 +             raise ValueError("'children' keyword cannot be used with positional arguments")
  310 +         elif len(args) > 0:
  311 +             kwargs["children"] = list(args)
  312 +
  313 :         super(Box, self).__init__(**kwargs)
./map_plots.py:
   77 :     def __init__(self, *args, **kw):
   78 +         from ..models.ranges import Range1d
   79 +         for r in ('x_range', 'y_range'):
   80 +             if r in kw and not isinstance(kw.get(r), Range1d):
   81 +                 raise ValueError('Invalid value for %r, MapPlot ranges may only be Range1d, not data ranges' % r)
   82 :         super(MapPlot, self).__init__(*args, **kw)
./mappers.py:
   80 :     def __init__(self, palette=None, **kwargs):
   81 +         if palette is not None:
   82 +             kwargs['palette'] = palette
   83 :         super(ColorMapper, self).__init__(**kwargs)
  132 :     def __init__(self, **kwargs):
  133 :         super(CategoricalColorMapper, self).__init__(**kwargs)
  134 +         palette = self.palette
  135 +         factors = self.factors
  136 +         if palette is not None and factors is not None:
  137 +             if len(palette) < len(factors):
  138 +                 extra_factors = factors[len(palette):]
  139 +                 warnings.warn("Palette length does not match number of factors. %s will be assigned to `nan_color` %s" % (extra_factors, self.nan_color))
./ranges.py:
  131 :     def __init__(self, *args, **kwargs):
  132 +         if args and ('start' in kwargs or 'end' in kwargs):
  133 +             raise ValueError("'start' and 'end' keywords cannot be used with positional arguments")
  134 +         if args and len(args) != 2:
  135 +             raise ValueError('Only Range1d(start, end) acceptable when using positional arguments')
  136 +
  137 +         if args:
  138 +             kwargs['start'] = args[0]
  139 +             kwargs['end'] = args[1]
  140 +
  141 :         super(Range1d, self).__init__(**kwargs)
  261 :     def __init__(self, *args, **kwargs):
  262 +         if kwargs.get('follow') is not None:
  263 +             kwargs['bounds'] = None
  264 :         super(DataRange1d, self).__init__(**kwargs)
  442 :     def __init__(self, *args, **kwargs):
  443 +         if args and "factors" in kwargs:
  444 +             raise ValueError("'factors' keyword cannot be used with positional arguments")
  445 +         elif args:
  446 +             kwargs['factors'] = list(args)
  447 :         super(FactorRange, self).__init__(**kwargs)
./renderers.py:
  153 :     def __init__(self, **kw):
  154 :         super(GlyphRenderer, self).__init__(**kw)
  155 +         if "view" not in kw:
  156 +             self.view = CDSView(source=self.data_source)
./sources.py:
  165 :     def __init__(self, *args, **kw):
  166 +         ''' If called with a single argument that is a dict or
  167 +         ``pandas.DataFrame``, treat that implicitly as the "data" attribute.
  168 +
  169 +         '''
  170 +         if len(args) == 1 and "data" not in kw:
  171 +             kw["data"] = args[0]
  172 +
  173 +         # TODO (bev) invalid to pass args and "data", check and raise exception
  174 +         raw_data = kw.pop("data", {})
  175 +
  183 :         super(ColumnDataSource, self).__init__(**kw)
  184 +         self.data.update(raw_data)
./transforms.py:
  344 :     def __init__(self, **kwargs):
  345 :         super(Interpolator, self).__init__(**kwargs)
./widgets/sliders.py:
   58 :     def __init__(self, **kwargs):
   59 +         if 'start' in kwargs and 'end' in kwargs:
   60 +             if kwargs['start'] == kwargs['end']:
   61 +                 raise ValueError("Slider 'start' and 'end' cannot be equal.")
   62 :         super(Widget, self).__init__(**kwargs)
./widgets/tables.py:
  556 :     def __init__(self, **kw):
  557 :         super(TableWidget, self).__init__(**kw)
  558 +         if "view" not in kw:
  559 +             self.view = CDSView(source=self.source)

```
Here is `Figure`
```
   def __init__(self, *arg, **kw):

        if 'plot_width' in kw and 'width' in kw:
            raise ValueError("Figure called with both 'plot_width' and 'width' supplied, supply only one")
        if 'plot_height' in kw and 'height' in kw:
            raise ValueError("Figure called with both 'plot_height' and 'height' supplied, supply only one")
        if 'height' in kw:
            kw['plot_height'] = kw.pop('height')
        if 'width' in kw:
            kw['plot_width'] = kw.pop('width')

        opts = FigureOptions(kw)

        title = kw.get("title", None)
        if isinstance(title, string_types):
            kw['title'] = Title(text=title)

        super(Figure, self).__init__(*arg, **kw)

        self.x_range = _get_range(opts.x_range)
        self.y_range = _get_range(opts.y_range)

        self.x_scale = _get_scale(self.x_range, opts.x_axis_type)
        self.y_scale = _get_scale(self.y_range, opts.y_axis_type)

        _process_axis_and_grid(self, opts.x_axis_type, opts.x_axis_location, opts.x_minor_ticks, opts.x_axis_label, self.x_range, 0)
        _process_axis_and_grid(self, opts.y_axis_type, opts.y_axis_location, opts.y_minor_ticks, opts.y_axis_label, self.y_range, 1)

        tool_objs, tool_map = _process_tools_arg(self, opts.tools, opts.tooltips)
        self.add_tools(*tool_objs)
        _process_active_tools(self.toolbar, tool_map, opts.active_drag, opts.active_inspect, opts.active_scroll, opts.active_tap)
```
Here is `GMapPlot`
```
    def __init__(self, **kw):

        if 'plot_width' in kw and 'width' in kw:
            raise ValueError("Figure called with both 'plot_width' and 'width' supplied, supply only one")
        if 'plot_height' in kw and 'height' in kw:
            raise ValueError("Figure called with both 'plot_height' and 'height' supplied, supply only one")
        if 'height' in kw:
            kw['plot_height'] = kw.pop('height')
        if 'width' in kw:
            kw['plot_width'] = kw.pop('width')

        opts = GMapFigureOptions(kw)

        title = kw.get("title", None)
        if isinstance(title, string_types):
            kw['title'] = Title(text=title)

        super(GMap, self).__init__(x_range=Range1d(), y_range=Range1d(), **kw)

        xf = MercatorTickFormatter(dimension="lon")
        xt = MercatorTicker(dimension="lon")
        self.add_layout(LinearAxis(formatter=xf, ticker=xt), 'below')

        yf = MercatorTickFormatter(dimension="lat")
        yt = MercatorTicker(dimension="lat")
        self.add_layout(LinearAxis(formatter=yf, ticker=yt), 'left')

        tool_objs, tool_map = _process_tools_arg(self, opts.tools)
        self.add_tools(*tool_objs)
        _process_active_tools(self.toolbar, tool_map, opts.active_drag, opts.active_inspect, opts.active_scroll, opts.active_tap)
```
> `__new__` typically calls `object.__new__` via `super`, in order to call `__init__` on the instance.

`__new__` doesn't call `__init__` on its own. Given class `C`, if you construct it with `C()` then `__new__` and `__init__` will be called consecutively, however, new instance can be also constructed with `C.__new__(C)`, and this won't call `__init__` (this is what I would use for deserialization purpose).
More or less, with the exception of `Title` for figures, the work before `super` calls seems to be extra-property valuation or conditioning. And after is more typically manipulating new models and object relationships. I suppose we could add a hook for `kwargs` conditioning, called by `__new__` and leave anything else in `__init__`

I will tinker with it some this weekend. 
Funny discovery, `_block_events` is no longer actually used by anything:
```
(base) ❯ grip _block_events
./bokeh/document/util.py:
  106 :         instance = cls(id=obj_id, _block_events=True)

~/work/bokeh bryanv/8627_client_model_init
```

Resolving patch:
diff --git a/bokeh/document/document.py b/bokeh/document/document.py
--- a/bokeh/document/document.py
+++ b/bokeh/document/document.py
@@ -375,7 +375,7 @@ def apply_json_patch(self, patch, setter=None):
                 if model_id in self._all_models:
                     references[model_id] = self._all_models[model_id]
 
-        initialize_references_json(references_json, references)
+        initialize_references_json(references_json, references, setter)
 
         for event_json in events_json:
 
diff --git a/bokeh/document/util.py b/bokeh/document/util.py
--- a/bokeh/document/util.py
+++ b/bokeh/document/util.py
@@ -26,6 +26,7 @@
 # External imports
 
 # Bokeh imports
+from ..core.has_props import HasProps
 from ..model import get_class
 
 #-----------------------------------------------------------------------------
@@ -81,6 +82,11 @@ def initialize_references_json(references_json, references, setter=None):
 
         instance = references[obj_id]
 
+        # We want to avoid any Model specific initialization that happens with
+        # Slider(...) when reconstituting from JSON, but we do need to perform
+        # general HasProps machinery that sets properties, so call it explicitly
+        HasProps.__init__(instance)
+
         instance.update_from_json(obj_attrs, models=references, setter=setter)
 
 def instantiate_references_json(references_json):
@@ -103,7 +109,7 @@ def instantiate_references_json(references_json):
         obj_type = obj.get('subtype', obj['type'])
 
         cls = get_class(obj_type)
-        instance = cls(id=obj_id, _block_events=True)
+        instance = cls.__new__(cls, id=obj_id)
         if instance is None:
             raise RuntimeError('Error loading model from JSON (type: %s, id: %s)' % (obj_type, obj_id))
         references[instance.id] = instance
diff --git a/bokeh/model.py b/bokeh/model.py
--- a/bokeh/model.py
+++ b/bokeh/model.py
@@ -254,10 +254,32 @@ class Model(with_metaclass(MetaModel, HasProps, PropertyCallbackManager, EventCa
 
     '''
 
+    def __new__(cls, *args, **kwargs):
+        obj =  super(Model, cls).__new__(cls)
+        obj._id = kwargs.pop("id", make_id())
+        obj._document = None
+        obj._temp_document = None
+        return obj
+
     def __init__(self, **kwargs):
-        self._id = kwargs.pop("id", make_id())
-        self._document = None
-        self._temp_document = None
+
+        # "id" is popped from **kw in __new__, so in an ideal world I don't
+        # think it should be here too. But Python does this, so it is:
+        #
+        # class Foo(object):
+        #     def __new__(cls, *args, **kw):
+        #         obj = super(Foo, cls).__new__(cls)
+        #         obj.bar = kw.pop("bar", 111)
+        #         print("__new__  :", id(kw), kw)
+        #         return obj
+        #     def __init__(self, **kw):
+        #         print("__init__ :", id(kw), kw)
+        #
+        # >>> f = Foo(bar=10)
+        # __new__  : 4405522296 {}
+        # __init__ : 4405522296 {'bar': 10}
+        kwargs.pop("id", None)
+
         super(Model, self).__init__(**kwargs)
         default_theme.apply_to_model(self)
 


Test patch:
diff --git a/tests/baselines/examples/app/apply_theme b/tests/baselines/examples/app/apply_theme
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/apply_theme
@@ -0,0 +1,5 @@
+Plot bbox=[0, 0, 800, 600]
+  LinearAxis bbox=[37, 573, 733, 27]
+  LinearAxis bbox=[0, 5, 37, 568]
+  Title bbox=[37, 5, 733, 0]
+  ToolbarPanel bbox=[770, 5, 30, 568]
diff --git a/tests/baselines/examples/app/clustering b/tests/baselines/examples/app/clustering
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/clustering
@@ -0,0 +1,11 @@
+Row bbox=[0, 0, 820, 400]
+  Column bbox=[0, 0, 420, 136]
+    Row bbox=[0, 0, 420, 56]
+      Select bbox=[5, 5, 200, 46]
+      Select bbox=[215, 5, 200, 46]
+    Slider bbox=[5, 61, 410, 30]
+    Slider bbox=[5, 101, 410, 30]
+  Plot bbox=[420, 0, 400, 400]
+    LinearAxis bbox=[21, 376, 374, 24]
+    LinearAxis bbox=[0, 29, 21, 347]
+    Title bbox=[21, 0, 374, 29]
diff --git a/tests/baselines/examples/app/crossfilter b/tests/baselines/examples/app/crossfilter
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/crossfilter
@@ -0,0 +1,11 @@
+Row bbox=[0, 0, 1110, 600]
+  Column bbox=[0, 0, 310, 224]
+    Select bbox=[5, 5, 300, 46]
+    Select bbox=[5, 61, 300, 46]
+    Select bbox=[5, 117, 300, 46]
+    Select bbox=[5, 173, 300, 46]
+  Plot bbox=[310, 0, 800, 600]
+    LinearAxis bbox=[52, 555, 718, 45]
+    LinearAxis bbox=[0, 29, 52, 526]
+    Title bbox=[52, 0, 718, 29]
+    ToolbarPanel bbox=[770, 29, 30, 526]
diff --git a/tests/baselines/examples/app/dash b/tests/baselines/examples/app/dash
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/dash
@@ -0,0 +1,17 @@
+Column bbox=[0, 0, 984, 241.9]
+  Plot bbox=[0, 0, 984, 180.4]
+    DatetimeAxis bbox=[52, 156.4, 927, 24]
+    LinearAxis bbox=[0, 5, 52, 151.4]
+    Title bbox=[52, 5, 927, 0]
+  Plot bbox=[0, 180.4, 984, 61.5]
+    DatetimeAxis bbox=[52, 37.5, 927, 24]
+    Title bbox=[52, 5, 927, 0]
+Plot bbox=[0, 0, 984, 574]
+  LinearAxis bbox=[5, 569, 974, 0]
+  LinearAxis bbox=[5, 5, 0, 564]
+  Title bbox=[5, 5, 974, 0]
+Plot bbox=[0, 0, 984, 574]
+  LinearAxis bbox=[59, 0, 920, 24]
+  CategoricalAxis bbox=[0, 24, 59, 545]
+  Title bbox=[59, 0, 920, 0]
+DataTable bbox=[0, 0, 974, 619.8181818181819]
diff --git a/tests/baselines/examples/app/export_csv b/tests/baselines/examples/app/export_csv
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/export_csv
@@ -0,0 +1,5 @@
+Row bbox=[0, 0, 1120, 410]
+  Column bbox=[0, 0, 310, 81]
+    RangeSlider bbox=[5, 5, 300, 30]
+    Button bbox=[5, 45, 300, 31]
+  DataTable bbox=[315, 5, 800, 400]
diff --git a/tests/baselines/examples/app/faces b/tests/baselines/examples/app/faces
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/faces
@@ -0,0 +1,6 @@
+Plot bbox=[0, 0, 984, 599.625]
+  Title bbox=[60, 0, 864, 0]
+Plot bbox=[0, 0, 984, 230.625]
+  LinearAxis bbox=[60, 224.625, 864, 6]
+  LinearAxis bbox=[54, 0, 6, 224.625]
+  Title bbox=[60, 0, 864, 0]
diff --git a/tests/baselines/examples/app/fourier_animated b/tests/baselines/examples/app/fourier_animated
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/fourier_animated
@@ -0,0 +1,11 @@
+Column bbox=[0, 0, 800, 600]
+  Plot bbox=[0, 0, 800, 300]
+    LinearAxis bbox=[31, 276, 739, 24]
+    LinearAxis bbox=[0, 29, 31, 247]
+    Title bbox=[31, 0, 739, 29]
+    ToolbarPanel bbox=[770, 29, 30, 247]
+  Plot bbox=[0, 300, 800, 300]
+    LinearAxis bbox=[31, 276, 739, 24]
+    LinearAxis bbox=[0, 29, 31, 247]
+    Title bbox=[31, 0, 739, 29]
+    ToolbarPanel bbox=[770, 29, 30, 247]
diff --git a/tests/baselines/examples/app/image_blur b/tests/baselines/examples/app/image_blur
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/image_blur
@@ -0,0 +1,7 @@
+Column bbox=[0, 0, 600, 640]
+  Plot bbox=[0, 0, 600, 600]
+    LinearAxis bbox=[31, 576, 539, 24]
+    LinearAxis bbox=[0, 5, 31, 571]
+    Title bbox=[31, 5, 539, 0]
+    ToolbarPanel bbox=[570, 5, 30, 571]
+  Slider bbox=[5, 605, 590, 30]
diff --git a/tests/baselines/examples/app/line_on_off b/tests/baselines/examples/app/line_on_off
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/line_on_off
@@ -0,0 +1,7 @@
+Row bbox=[0, 0, 662, 600]
+  CheckboxGroup bbox=[5, 5, 52, 48]
+  Plot bbox=[62, 0, 600, 600]
+    LinearAxis bbox=[28, 576, 542, 24]
+    LinearAxis bbox=[0, 5, 28, 571]
+    Title bbox=[28, 5, 542, 0]
+    ToolbarPanel bbox=[570, 5, 30, 571]
diff --git a/tests/baselines/examples/app/movies b/tests/baselines/examples/app/movies
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/movies
@@ -0,0 +1,19 @@
+Column bbox=[0, 0, 1010, 728]
+  Row bbox=[0, 0, 810, 128]
+    Div bbox=[5, 5, 800, 118]
+  Row bbox=[0, 128, 1010, 600]
+    Column bbox=[0, 0, 310, 480]
+      Slider bbox=[5, 5, 300, 30]
+      Slider bbox=[5, 45, 300, 30]
+      Select bbox=[5, 85, 300, 46]
+      Slider bbox=[5, 141, 300, 30]
+      Slider bbox=[5, 181, 300, 30]
+      Slider bbox=[5, 221, 300, 30]
+      TextInput bbox=[5, 261, 300, 46]
+      TextInput bbox=[5, 317, 300, 46]
+      Select bbox=[5, 373, 300, 46]
+      Select bbox=[5, 429, 300, 46]
+    Plot bbox=[310, 0, 700, 600]
+      LinearAxis bbox=[52, 555, 643, 45]
+      LinearAxis bbox=[0, 29, 52, 526]
+      Title bbox=[52, 0, 643, 29]
diff --git a/tests/baselines/examples/app/ohlc b/tests/baselines/examples/app/ohlc
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/ohlc
@@ -0,0 +1,16 @@
+Column bbox=[0, 0, 1030, 791]
+  Row bbox=[0, 0, 930, 41]
+    Slider bbox=[5, 5, 300, 30]
+    Slider bbox=[315, 5, 300, 30]
+    Select bbox=[625, 5, 300, 31]
+  Row bbox=[0, 41, 1030, 750]
+    ToolbarBox bbox=[0, 0, 30, 147]
+      ProxyToolbar
+    GridBox bbox=[30, 0, 1000, 750]
+      Plot bbox=[0, 0, 1000, 500]
+        LinearAxis bbox=[994, 0, 6, 500]
+        Title bbox=[5, 0, 989, 0]
+      Plot bbox=[0, 500, 1000, 250]
+        LinearAxis bbox=[5, 244, 989, 6]
+        LinearAxis bbox=[994, 0, 6, 244]
+        Title bbox=[5, 0, 989, 0]
diff --git a/tests/baselines/examples/app/population b/tests/baselines/examples/app/population
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/population
@@ -0,0 +1,13 @@
+Row bbox=[0, 0, 910, 680]
+  Column bbox=[0, 0, 600, 680]
+    Plot bbox=[0, 0, 600, 500]
+      LinearAxis bbox=[62, 455, 533, 45]
+      CategoricalAxis bbox=[0, 29, 62, 426]
+      Title bbox=[62, 0, 533, 29]
+    Plot bbox=[0, 500, 600, 180]
+      LinearAxis bbox=[62, 125, 533, 55]
+      LinearAxis bbox=[6, 29, 56, 96]
+      Title bbox=[62, 0, 533, 29]
+  Column bbox=[600, 0, 310, 112]
+    Select bbox=[5, 5, 300, 46]
+    Select bbox=[5, 61, 300, 46]
diff --git a/tests/baselines/examples/app/selection_histogram b/tests/baselines/examples/app/selection_histogram
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/selection_histogram
@@ -0,0 +1,12 @@
+GridBox bbox=[0, 0, 800, 800]
+  Plot bbox=[0, 0, 600, 600]
+    Title bbox=[50, 0, 120, 29]
+    ToolbarPanel bbox=[393, 0, 175, 30]
+  Plot bbox=[600, 0, 200, 600]
+    LinearAxis bbox=[10, 568, 159, 32]
+    LinearAxis bbox=[169, 30, 31, 538]
+    Title bbox=[10, 30, 159, 0]
+  Plot bbox=[0, 600, 600, 200]
+    LinearAxis bbox=[50, 176, 518, 24]
+    LinearAxis bbox=[568, 10, 32, 166]
+    Title bbox=[50, 10, 518, 0]
diff --git a/tests/baselines/examples/app/sliders b/tests/baselines/examples/app/sliders
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/sliders
@@ -0,0 +1,12 @@
+Row bbox=[0, 0, 710, 400]
+  Column bbox=[0, 0, 310, 216]
+    TextInput bbox=[5, 5, 300, 46]
+    Slider bbox=[5, 61, 300, 30]
+    Slider bbox=[5, 101, 300, 30]
+    Slider bbox=[5, 141, 300, 30]
+    Slider bbox=[5, 181, 300, 30]
+  Plot bbox=[310, 0, 400, 400]
+    LinearAxis bbox=[21, 376, 349, 24]
+    LinearAxis bbox=[0, 29, 21, 347]
+    Title bbox=[21, 0, 349, 29]
+    ToolbarPanel bbox=[370, 29, 30, 347]
diff --git a/tests/baselines/examples/app/taylor b/tests/baselines/examples/app/taylor
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/taylor
@@ -0,0 +1,10 @@
+Column bbox=[0, 0, 800, 518]
+  Column bbox=[0, 0, 310, 118]
+    TextInput bbox=[5, 5, 300, 46]
+    Slider bbox=[5, 61, 300, 30]
+    PreText bbox=[5, 101, 0, 12]
+  Plot bbox=[0, 118, 800, 400]
+    LinearAxis bbox=[34, 376, 736, 24]
+    LinearAxis bbox=[0, 29, 34, 347]
+    Title bbox=[34, 0, 736, 29]
+    ToolbarPanel bbox=[770, 29, 30, 347]
diff --git a/tests/baselines/examples/app/weather b/tests/baselines/examples/app/weather
new file mode 100644
--- /dev/null
+++ b/tests/baselines/examples/app/weather
@@ -0,0 +1,8 @@
+Row bbox=[0, 0, 1110, 600]
+  Plot bbox=[0, 0, 800, 600]
+    DatetimeAxis bbox=[53, 576, 742, 24]
+    LinearAxis bbox=[0, 29, 53, 547]
+    Title bbox=[53, 0, 742, 29]
+  Column bbox=[800, 0, 310, 112]
+    Select bbox=[5, 5, 300, 46]
+    Select bbox=[5, 61, 300, 46]