Skip to content

Commit

Permalink
Merge f794851 into a49d691
Browse files Browse the repository at this point in the history
  • Loading branch information
jlstevens committed Apr 28, 2017
2 parents a49d691 + f794851 commit 2be08f0
Showing 1 changed file with 26 additions and 25 deletions.
51 changes: 26 additions & 25 deletions doc/Tutorials/Linked_Streams.ipynb
Expand Up @@ -16,24 +16,24 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"In previous notebooks we discovered how the [DynamicMap](Dynamic_Map.ipynb) class allows us to declare objects in a lazy way to enable exploratory analysis of large parameter spaces. In the [Streams](Streams.ipynb) tutorial we learned how to interactively push updates to existing plots by declaring Streams on a DynamicMap. In this tutorial we will extend the idea to so called **linked** Streams, which allow declaring complex interactions by exposing events generated by interacting with a plot. Being able to succinctly capture interactions with a plot and defining a simple Python based callback to define the interactivity allows generating much richer visualizations, revealing more detail about your data.\n",
"In previous notebooks we discovered how the [DynamicMap](Dynamic_Map.ipynb) class allows us to declare objects in a lazy way to enable exploratory analysis of large parameter spaces. In the [Streams](Streams.ipynb) tutorial we learned how to interactively push updates to existing plots by declaring Streams on a DynamicMap. In this tutorial we will extend the idea to so called *linked* Streams, which allows complex interactions to be declared by specifying which events should be exposed when a plot is interacted with. By passing information about live interactions to a simple Python based callback, you will be able to build richer, even more interactive visualizations that enable seemless data exploration.\n",
"\n",
"Some of the many possibilities this opens up include:\n",
"Some of the possibilities this opens up include:\n",
"\n",
"* Dynamically aggregating datasets of billions of datapoints depending on the plot axis ranges using the [datashader](https://anaconda.org/jbednar/holoviews_datashader/notebook) library.\n",
"* Responding to Tap and DoubleTap events to reveal more information in subplots.\n",
"* Responding to ``Tap`` and ``DoubleTap`` events to reveal more information in subplots.\n",
"* Computing statistics in response to selections applied with box- and lasso-select tools.\n",
"\n",
"Currently only the bokeh backend for HoloViews supports linked streams but in theory any backend can define callback which fire when a user zooms or pans or interacts with a plot.\n",
"Currently only the bokeh backend for HoloViews supports the linked streams system but the principles used should extend to any backend can define callbacks that fire when a user zooms or pans or interacts with a plot.\n",
"\n",
"<center><div class=\"alert alert-info\" role=\"alert\">To use and visualize <b>DynamicMap</b> or <b>Stream</b> objects you need to be running a live Jupyter server.<br>This tutorial assumes that it will be run in a live notebook environment.<br>\n",
"When viewed statically, DynamicMaps will only show the first available Element,<br></div></center>\n",
"\n",
"## Available Linked Streams\n",
"\n",
"There a huge number of ways one might want to interact with a plot. The HoloViews streams module aims to offer many of the most common interactions you might want to specify while making it easy to extend it with custom linked Streams. \n",
"There are a huge number of ways one might want to interact with a plot. The HoloViews streams module aims to expose many of the most common interactions you might want want to employ, while also supporting extensibility via custom linked Streams. \n",
"\n",
"We can see the full list of linked Stream types by iterating over the descendents of the ``LinkedStream`` baseclass."
"Here is the full list of linked Stream that are all descendents of the ``LinkedStream`` baseclass:"
]
},
{
Expand All @@ -43,14 +43,15 @@
"outputs": [],
"source": [
"from holoviews import streams\n",
"sorted([str(s) for s in param.descendents(streams.LinkedStream)])"
"listing = ', '.join(sorted([str(s.name) for s in param.descendents(streams.LinkedStream)]))\n",
"print('The linked stream classes supported by HoloViews are:\\n\\n{listing}'.format(listing=listing))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As you can see, most of these events are about specific interactions with a plot such as the current axis ranges (``Range[X][Y]``), the mouse pointer position (``Pointer[X][Y]``), click or tap positions (``Tap``, ``DoubleTap``). Additionally there a streams to access plotting selections made using box- and lasso-select tools (``Selection1D``), the plot size (``PlotSize``) and the ``Bounds`` of a selection. \n",
"As you can see, most of these events are about specific interactions with a plot such as the current axis ranges (the ``RangeX``, ``RangeY`` and ``RangeXY`` streams), the mouse pointer position (the ``PointerX``, ``PointerY`` and ``PointerXY`` streams), click or tap positions (``Tap``, ``DoubleTap``). Additionally there a streams to access plotting selections made using box- and lasso-select tools (``Selection1D``), the plot size (``PlotSize``) and the ``Bounds`` of a selection. \n",
"\n",
"Each of these linked Stream types has a corresponding backend specific ``Callback``, which defines which plot attributes or events to link the stream to and triggers events on the ``Stream`` in response to changes on the plot. Defining custom ``Stream`` and ``Callback`` types will be covered in the [Stream Callback Tutorial](Stream_Callbacks.ipynb)."
]
Expand All @@ -61,11 +62,11 @@
"source": [
"## Linking streams to plots\n",
"\n",
"In the [Streams](./Streams.ipynb) tutorial we discovered that streams have ``subscribers``, which allow defining user defined callbacks on events, but also allow plots to subscribe to Stream updates. Linked streams add another concept on top of ``subscribers``, the ``source`` of a Stream.\n",
"At the end of the [Streams](./Streams.ipynb) tutorial we discovered that streams have ``subscribers``, which allow defining user defined callbacks on events, but also allows HoloViews to install subscribers that allow plots to respond to Stream updates. Linked streams add another concept on top of ``subscribers``, namely the Stream ``source``.\n",
"\n",
"The source of a linked stream defines which plot element to receive events from. Any plot containing the ``source`` object will be linked to the stream and send events in response to changes.\n",
"The source of a linked stream defines which plot element to receive events from. Any plot containing the ``source`` object will be attached to the corresponding linked stream and will send event values in response to the appropriate interactions.\n",
"\n",
"Let's start with a simple example. We will declare one of the linked Streams from above, the ``PointerXY`` stream. This stream sends the current mouse position in plot axes coordinates, which may be continuous or categorical. The first thing to note is that since we have passed no ``source`` the attribute is ``None``."
"Let's start with a simple example. We will declare one of the linked Streams from above, the ``PointerXY`` stream. This stream sends the current mouse position in plot axes coordinates, which may be continuous or categorical. The first thing to note is that we haven't specified a ``source`` which means it uses the default value of ``None``."
]
},
{
Expand All @@ -75,14 +76,14 @@
"outputs": [],
"source": [
"pointer = streams.PointerXY()\n",
"print pointer.source"
"print(pointer.source)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Before continuing we can check which keywords the Stream makes available to a user by printing it or looking at its contents:"
"Before continuing, we can check the stream parameters that are made available to user callbacks from a given stream instance by looking at its contents:"
]
},
{
Expand All @@ -91,7 +92,7 @@
"metadata": {},
"outputs": [],
"source": [
"print('The %s stream contains: %r' % (pointer, pointer.contents))"
"print('The %s stream has contents %r' % (pointer, pointer.contents))"
]
},
{
Expand All @@ -105,7 +106,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"A Stream is automatically linked to the first DynamicMap we attach the Stream to, which we can confirm by inspecting the stream's ``source`` attribute:"
"A stream instance is automatically linked to the first ``DynamicMap`` we pass it to, which we can confirm by inspecting the stream's ``source`` attribute after supplying it to a ``DynamicMap``:"
]
},
{
Expand All @@ -122,7 +123,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"The DynamicMap we defined above simply defines returns a Points object containing the current x, y coordinate of our ``PointerXY`` stream and is now linked to any plot this object is displayed in:"
"The ``DynamicMap`` we defined above simply defines returns a ``Points`` object composed of a single point that marks the current ``x`` and ``y`` position supplied by our ``PointerXY`` stream. The stream is linked whenever this ``DynamicMap`` object is displayed as it is the stream source:"
]
},
{
Expand All @@ -138,7 +139,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"If you hover over the plot canvas above you can see how the point tracks the current mouse position. We can also inspect the last cursor position on the stream:"
"If you hover over the plot canvas above you can see that the point tracks the current mouse position. We can also inspect the last cursor position by examining the stream contents:"
]
},
{
Expand All @@ -147,7 +148,7 @@
"metadata": {},
"outputs": [],
"source": [
"pointer"
"pointer.contents"
]
},
{
Expand All @@ -156,7 +157,7 @@
"source": [
"#### Explicit linking\n",
"\n",
"In the example above we just let took advantage of the automatic linking to the object we attached the stream to. If we want to link the Stream to a different object we can pass an explicit source. Here we will create a 2D Image of sine gratings, declare that object as the ``source`` of the ``PointerXY`` stream, and then use the pointer stream to define a single point showing the cursor position on the image."
"In the example above, we took advantage of the fact that a ``DynamicMap`` automatically becomes the stream source if a source isn't explicitly specified. If we want to link the stream instance to a different object we can specify our our source explicitly. Here we will create a 2D ``Image`` of sine gratings, declare that is then declared as the ``source`` of the ``PointerXY`` stream. This pointer stream is then used to generate a single point that tracks the cursor when hovering over the image:"
]
},
{
Expand All @@ -177,7 +178,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Now if we display the ``Image`` and the ``DynamicMap`` together in a ``Layout``, the plot on the right will track the cursor position on image plot to the left, which we designated as the source:"
"Now if we display a ``Layout`` consisting of the ``Image`` acting as the source together with the ``DynamicMap``, the point shown on the right tracks the cursor position when hovering voer the image on the left:"
]
},
{
Expand All @@ -193,9 +194,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"This will even work across different cells. If we add the stream to another object and display it, the new plot will be linked to the cursor position on the image plot. \n",
"This will even work across different cells. If we use this particular stream instance in another ``DynamicMap`` and display it, this new visualization will also be supplied with the cursor position when hovering over the image. \n",
"\n",
"We will use the pointer x and y position to take cross-sections of the image at the hover location using the ``Image.sample`` method. We also ensure that the position does not go out of bounds."
"To illustrate this, we will now use the pointer ``x`` and ``y`` position to generate cross-sections of the image at the cursor position on the ``Image``, making use of the ``Image.sample`` method. Note the use of ``np.clip`` to make sure the cross-section is well defined when the cusor goes out of bounds:"
]
},
{
Expand All @@ -205,15 +206,15 @@
"outputs": [],
"source": [
"%%opts Curve {+framewise}\n",
"hv.DynamicMap(lambda x, y: img.sample(y=y if -.5<y<.5 else 0), streams=[pointer]) +\\\n",
"hv.DynamicMap(lambda x, y: img.sample(x=x if -.5<x<.5 else 0), streams=[pointer])"
"hv.DynamicMap(lambda x, y: img.sample(y=np.clip(y,-.49,.49)), streams=[pointer]) +\\\n",
"hv.DynamicMap(lambda x, y: img.sample(x=np.clip(x,-.49,.49)), streams=[pointer])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now try hovering over the ``Image`` above again and watch the cross-sections update as you move the cursor."
"Now when you hover over the ``Image`` above, you will see the cross-sections update while the point position to the right of the ``Image`` simultaneously updates."
]
},
{
Expand Down

0 comments on commit 2be08f0

Please sign in to comment.